Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/profiles/templates/profile_base.html
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ <h2>Your BornHack Account</h2>
</a>
</li>

{% url 'profiles:facility_feedback_list' as profile_facility_feedback_url %}
<li class="nav-item">
<a class="nav-link{% if request.path == profile_facility_feedback_url %} active{% endif %}" href="{{ profile_facility_feedback_url }}">
Facility Feedback
</a>
</li>

<li class="nav-item">
<a class="nav-link" href="{% url 'maps_user_location_redirect' %}">
User Locations
Expand Down
37 changes: 37 additions & 0 deletions src/profiles/templates/profile_facility_feedback.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{% extends 'profile_base.html' %}
{% load django_bootstrap5 %}
{% load formatting %}

{% block title %}
Feedback | {{ block.super }}
{% endblock %}

{% block profile_content %}
<div class="card">
<div class="card-header">
<h4 class="card-title">Your facility feedback</h4>
</div>
<div class="card-body">
<table class="table">
<tr>
<th>Facility</th>
<th>Quick Feedback</th>
<th>Comment</th>
<th>Urgent</th>
<th>Handled</th>
<th>Created</th>
</tr>
{% for feedback in feedback_list %}
<tr>
<td>{{ feedback.facility }}</td>
<td><i class="{{ feedback.quick_feedback.icon }} fa-2x"></i> {{ feedback.quick_feedback }}</td>
<td>{{ feedback.comment|default:"N/A" }}</td>
<td class="{% if feedback.urgent %}text-bg-danger{% endif %}">{{ feedback.urgent|yesno }}</td>
<td>{{ feedback.handled|yesno }}</td>
<td>{{ feedback.created }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
{% endblock profile_content %}
4 changes: 4 additions & 0 deletions src/profiles/urls.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
"""URLs for the user profiles app."""

from __future__ import annotations

from django.urls import path

from .views import ProfileApiView
from .views import ProfileDetail
from .views import ProfileFacilityFeedbackListView
from .views import ProfileOIDCView
from .views import ProfilePermissionList
from .views import ProfileSessionThemeSwitchView
Expand All @@ -17,4 +20,5 @@
path("api/", ProfileApiView.as_view(), name="api"),
path("permissions/", ProfilePermissionList.as_view(), name="permissions_list"),
path("oidc/", ProfileOIDCView.as_view(), name="oidc"),
path("facility_feedback/", ProfileFacilityFeedbackListView.as_view(), name="facility_feedback_list"),
]
64 changes: 54 additions & 10 deletions src/profiles/views.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
"""Views for the user profile pages."""

from __future__ import annotations

from typing import TYPE_CHECKING

from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import Permission
from django.db.models import Q
from django.http import HttpRequest
from django.http import HttpResponse
from django.http import HttpResponseForbidden
from django.shortcuts import redirect
from django.urls import reverse_lazy
Expand All @@ -16,23 +22,36 @@
from oauth2_provider.views.generic import ScopedProtectedResourceView

from bornhack.oauth_validators import BornhackOAuth2Validator
from facilities.models import FacilityFeedback

if TYPE_CHECKING:
from typing import ClassVar

from django.db.models import QuerySet
from django.forms import BaseForm
from django.forms import Form

from .forms import OIDCForm
from .models import Profile


class ProfileDetail(LoginRequiredMixin, DetailView):
"""User profile details view."""

model = Profile
template_name = "profile_detail.html"
active_menu = "profile"

def get_object(self, queryset=None):
def get_object(self, queryset: QuerySet = None) -> QuerySet:
"""Method for fetching user profile."""
return Profile.objects.get(user=self.request.user)


class ProfileUpdate(LoginRequiredMixin, UpdateView):
"""View for updating the user profile."""

model = Profile
fields = [
fields: ClassVar[list[str]] = [
"name",
"description",
"public_credit_name",
Expand All @@ -44,10 +63,12 @@ class ProfileUpdate(LoginRequiredMixin, UpdateView):
success_url = reverse_lazy("profiles:detail")
template_name = "profile_form.html"

def get_object(self, queryset=None):
def get_object(self, queryset: QuerySet = None) -> QuerySet:
"""Method for fetching user profile."""
return Profile.objects.get(user=self.request.user)

def form_valid(self, form, **kwargs):
def form_valid(self, form: BaseForm, **kwargs) -> HttpResponse:
"""Check form valid."""
if "public_credit_name" in form.changed_data and form.cleaned_data["public_credit_name"]:
# user changed the name (to something non blank)
form.instance.public_credit_name_approved = False
Expand All @@ -59,9 +80,12 @@ def form_valid(self, form, **kwargs):


class ProfileApiView(JsonView, ScopedProtectedResourceView):
required_scopes = ["profile:read"]
"""View for showing user profile in json."""

required_scopes: ClassVar[list[str]] = ["profile:read"]

def get_context_data(self, **kwargs):
def get_context_data(self, **kwargs) -> dict:
"""Method to fetch the data."""
context = super().get_context_data(**kwargs)
context["user"] = {
"username": self.request.user.username,
Expand All @@ -76,11 +100,13 @@ def get_context_data(self, **kwargs):


class ProfilePermissionList(LoginRequiredMixin, ListView):
"""View to list all user permissions."""

model = Permission
template_name = "permission_list.html"
context_object_name = "permissions"

def get_queryset(self, *args, **kwargs):
def get_queryset(self, *args, **kwargs) -> QuerySet:
"""Get real Permission objects so we have the perm descriptions as well."""
query = Q()
# loop over users permissions and build an OR query
Expand All @@ -100,7 +126,8 @@ def get_queryset(self, *args, **kwargs):
class ProfileSessionThemeSwitchView(View):
"""View for setting the Session theme."""

def get(self, request, *args, **kwargs):
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""GET method for setting the Session theme."""
theme = request.GET.get("theme") or "default"
next_url = request.GET.get("next") or "/"
if theme in dict(Profile.THEME_CHOICES) and next_url[:1] == "/":
Expand All @@ -113,22 +140,27 @@ def get(self, request, *args, **kwargs):


class ProfileOIDCView(LoginRequiredMixin, FormView):
"""View for showing OIDC permissions."""

template_name = "oidc.html"
form_class = OIDCForm

def setup(self, *args, **kwargs) -> None:
"""Method for initial setup of the view."""
super().setup(*args, **kwargs)
validator = BornhackOAuth2Validator()
self.scopes = validator.oidc_claim_scope
self.claims = validator.get_additional_claims(request=self.request)

def get_form(self, form_class=None):
def get_form(self, form_class: type[Form] | None = None) -> Form:
"""Method for returning the form."""
if form_class is None:
form_class = self.get_form_class()
self.initial["scopes"] = self.request.GET.getlist(key="scopes")
return form_class(**self.get_form_kwargs())

def get_context_data(self, **kwargs):
def get_context_data(self, **kwargs) -> dict:
"""Method for collecting data used in template."""
context = super().get_context_data(**kwargs)
context["claims"] = {}
for claim, value in self.claims.items():
Expand All @@ -140,3 +172,15 @@ def get_context_data(self, **kwargs):
context["all_scopes"] = sorted(set(self.scopes.values()))
del context["all_scopes"][context["all_scopes"].index("openid")]
return context


class ProfileFacilityFeedbackListView(LoginRequiredMixin, ListView):
"""View for showing all user feedback on facilities."""

model = FacilityFeedback
template_name = "profile_facility_feedback.html"
context_object_name = "feedback_list"

def get_queryset(self, *args, **kwargs) -> QuerySet:
"""Update queryset to only include own feedback."""
return super().get_queryset().filter(user=self.request.user).order_by("handled", "created")
Loading