From 6ea8267d5815a367a1366744fb3e87c176af54c7 Mon Sep 17 00:00:00 2001 From: "Rudy (zarya)" Date: Sat, 23 Aug 2025 11:15:40 +0200 Subject: [PATCH 1/2] Add a list of facility feedback submitted by user to user profile page Fixes #1911 --- .../templates/profile_facility_feedback.html | 37 +++++++++++ src/profiles/urls.py | 4 ++ src/profiles/views.py | 64 ++++++++++++++++--- 3 files changed, 95 insertions(+), 10 deletions(-) create mode 100644 src/profiles/templates/profile_facility_feedback.html diff --git a/src/profiles/templates/profile_facility_feedback.html b/src/profiles/templates/profile_facility_feedback.html new file mode 100644 index 000000000..59b7815f1 --- /dev/null +++ b/src/profiles/templates/profile_facility_feedback.html @@ -0,0 +1,37 @@ +{% extends 'profile_base.html' %} +{% load django_bootstrap5 %} +{% load formatting %} + +{% block title %} + Feedback | {{ block.super }} +{% endblock %} + +{% block profile_content %} +
+
+

Your facility feedback

+
+
+ + + + + + + + + + {% for feedback in feedback_list %} + + + + + + + + + {% endfor %} +
FacilityQuick FeedbackCommentUrgentHandledCreated
{{ feedback.facility }} {{ feedback.quick_feedback }}{{ feedback.comment|default:"N/A" }}{{ feedback.urgent|yesno }}{{ feedback.handled|yesno }}{{ feedback.created }}
+
+
+{% endblock profile_content %} diff --git a/src/profiles/urls.py b/src/profiles/urls.py index ba8dea979..43ee21fa4 100644 --- a/src/profiles/urls.py +++ b/src/profiles/urls.py @@ -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 @@ -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"), ] diff --git a/src/profiles/views.py b/src/profiles/views.py index 4f5f443d9..dd5a11783 100644 --- a/src/profiles/views.py +++ b/src/profiles/views.py @@ -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 @@ -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", @@ -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 @@ -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, @@ -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 @@ -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] == "/": @@ -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(): @@ -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") From e5f69849d54a7f6646576314490d052354d34c9c Mon Sep 17 00:00:00 2001 From: "Rudy (zarya)" Date: Sat, 23 Aug 2025 11:20:40 +0200 Subject: [PATCH 2/2] Dont forget to add link to profile base --- src/profiles/templates/profile_base.html | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/profiles/templates/profile_base.html b/src/profiles/templates/profile_base.html index 8fc79b228..ffa28978e 100644 --- a/src/profiles/templates/profile_base.html +++ b/src/profiles/templates/profile_base.html @@ -92,6 +92,13 @@

Your BornHack Account

+ {% url 'profiles:facility_feedback_list' as profile_facility_feedback_url %} + +