From afbdf221c3b0f31ec4ffbf509b2698ffd408983e Mon Sep 17 00:00:00 2001 From: mpeltriaux Date: Wed, 15 Oct 2025 17:09:40 +0200 Subject: [PATCH] # User view refactoring * refactors majority of user views into class based views * introduces BaseModalFormView and BaseView for even more generic usage * renames url identifier user:index into user:detail for more clarity --- konova/views/base.py | 18 +++- templates/navbars/navbar.html | 2 +- user/forms/user.py | 2 +- user/tests/test_views.py | 2 +- user/tests/unit/test_forms.py | 2 +- user/urls.py | 10 +- user/views/views.py | 177 +++++++++++++++------------------- 7 files changed, 103 insertions(+), 110 deletions(-) diff --git a/konova/views/base.py b/konova/views/base.py index abffa13d..e1548a95 100644 --- a/konova/views/base.py +++ b/konova/views/base.py @@ -17,12 +17,24 @@ from konova.utils.general import check_user_is_in_any_group from konova.utils.message_templates import MISSING_GROUP_PERMISSION -class BaseIndexView(View): +class BaseView(View): + _TEMPLATE: str = "CHANGE_ME" + _TAB_TITLE: str = "CHANGE_ME" + + class Meta: + abstract = True + + +class BaseModalFormView(BaseView): + _TEMPLATE = "modal/modal_form.html" + _TAB_TITLE = None + + +class BaseIndexView(BaseView): """ Base class for index views """ - _TEMPLATE: str = 'generic_index.html' - _TAB_TITLE: str = "CHANGE_ME" + _TEMPLATE = "generic_index.html" _INDEX_TABLE_CLS = None class Meta: diff --git a/templates/navbars/navbar.html b/templates/navbars/navbar.html index 9d1d6859..7ba2132d 100644 --- a/templates/navbars/navbar.html +++ b/templates/navbars/navbar.html @@ -56,7 +56,7 @@ {% if user.is_staff or user.is_superuser %} {% fa5_icon 'tools' %} {% trans 'Admin' %} {% endif %} - {% fa5_icon 'cogs' %} {% trans 'Settings' %} + {% fa5_icon 'cogs' %} {% trans 'Settings' %} {% fa5_icon 'sign-out-alt' %} {% trans 'Logout' %} diff --git a/user/forms/user.py b/user/forms/user.py index 190ce126..0f4c4f24 100644 --- a/user/forms/user.py +++ b/user/forms/user.py @@ -38,7 +38,7 @@ class UserNotificationForm(BaseForm): self.form_title = _("Edit notifications") self.form_caption = _("") self.action_url = reverse("user:notifications") - self.cancel_redirect = reverse("user:index") + self.cancel_redirect = reverse("user:detail") # Insert all notifications into form field by creating choices as tuples notifications = UserNotification.objects.filter( diff --git a/user/tests/test_views.py b/user/tests/test_views.py index fe4c854f..7b2d2406 100644 --- a/user/tests/test_views.py +++ b/user/tests/test_views.py @@ -26,7 +26,7 @@ class UserViewTestCase(BaseViewTestCase): self.team.users.add(self.superuser) self.team.admins.add(self.superuser) # Prepare urls - self.index_url = reverse("user:index", args=()) + self.index_url = reverse("user:detail", args=()) self.notification_url = reverse("user:notifications", args=()) self.api_token_url = reverse("user:api-token", args=()) self.contact_url = reverse("user:contact", args=(self.superuser.id,)) diff --git a/user/tests/unit/test_forms.py b/user/tests/unit/test_forms.py index 20e4333c..4a86945f 100644 --- a/user/tests/unit/test_forms.py +++ b/user/tests/unit/test_forms.py @@ -233,7 +233,7 @@ class UserNotificationFormTestCase(BaseTestCase): self.assertEqual(form.form_title, str(_("Edit notifications"))) self.assertEqual(form.form_caption, "") self.assertEqual(form.action_url, reverse("user:notifications")) - self.assertEqual(form.cancel_redirect, reverse("user:index")) + self.assertEqual(form.cancel_redirect, reverse("user:detail")) def test_save(self): selected_notification = UserNotification.objects.first() diff --git a/user/urls.py b/user/urls.py index c3127a1e..ce8616fd 100644 --- a/user/urls.py +++ b/user/urls.py @@ -15,15 +15,15 @@ from user.views.views import * app_name = "user" urlpatterns = [ - path("", index_view, name="index"), + path("", UserDetailView.as_view(), name="detail"), path("propagate/", PropagateUserView.as_view(), name="propagate"), - path("notifications/", notifications_view, name="notifications"), + path("notifications/", NotificationsView.as_view(), name="notifications"), path("token/api", APITokenView.as_view(), name="api-token"), path("token/api/new", new_api_token_view, name="api-token-new"), - path("contact/", contact_view, name="contact"), - path("team/", index_team_view, name="team-index"), + path("contact/", ContactView.as_view(), name="contact"), + path("team/", TeamIndexView.as_view(), name="team-index"), path("team/new", new_team_view, name="team-new"), - path("team/", data_team_view, name="team-data"), + path("team/", TeamDetailModalView.as_view(), name="team-data"), path("team//edit", edit_team_view, name="team-edit"), path("team//remove", remove_team_view, name="team-remove"), path("team//leave", leave_team_view, name="team-leave"), diff --git a/user/views/views.py b/user/views/views.py index 5d773926..e639703c 100644 --- a/user/views/views.py +++ b/user/views/views.py @@ -1,8 +1,10 @@ from django.contrib import messages from django.contrib.auth.decorators import login_required +from django.contrib.auth.mixins import LoginRequiredMixin from django.urls import reverse from konova.sub_settings.context_settings import TAB_TITLE_IDENTIFIER +from konova.views.base import BaseView, BaseModalFormView from user.forms.modals.team import NewTeamModalForm, EditTeamModalForm, RemoveTeamModalForm, LeaveTeamModalForm from user.forms.modals.user import UserContactForm from user.forms.team import TeamDataForm @@ -13,129 +15,108 @@ from django.shortcuts import render, redirect, get_object_or_404 from django.utils.translation import gettext_lazy as _ from konova.contexts import BaseContext -from konova.decorators import any_group_check, login_required_modal +from konova.decorators import login_required_modal -@login_required -@any_group_check -def index_view(request: HttpRequest): - """ Renders the user's data index view +class UserDetailView(LoginRequiredMixin, BaseView): + _TAB_TITLE = _("User settings") + _TEMPLATE = "user/index.html" - Args: - request (): - - Returns: - - """ - template = "user/index.html" - context = { - "user": request.user, - TAB_TITLE_IDENTIFIER: _("User settings"), - } - context = BaseContext(request, context).context - return render(request, template, context) + def get(self, request: HttpRequest): + context = { + "user": request.user, + TAB_TITLE_IDENTIFIER: self._TAB_TITLE, + } + context = BaseContext(request, context).context + return render(request, self._TEMPLATE, context) -@login_required -@any_group_check -def notifications_view(request: HttpRequest): - """ Renders the notifications settings view +class NotificationsView(LoginRequiredMixin, BaseView): + _TEMPLATE = "user/notifications.html" + _TAB_TITLE = _("User notifications") - Args: - request (): + def get(self, request: HttpRequest): + user = request.user + form = UserNotificationForm(user=user, data=None) + context = { + "user": user, + "form": form, + TAB_TITLE_IDENTIFIER: self._TAB_TITLE, + } + context = BaseContext(request, context).context + return render(request, self._TEMPLATE, context) - Returns: - - """ - template = "user/notifications.html" - user = request.user - - form = UserNotificationForm(user=user, data=request.POST or None) - if request.method == "POST": + def post(self, request: HttpRequest): + user = request.user + form = UserNotificationForm(user=user, data=request.POST) if form.is_valid(): form.save() messages.success( request, _("Notifications edited") ) - return redirect("user:index") - elif request.method == "GET": - # Implicit - pass - else: - raise NotImplementedError - - context = { - "user": user, - "form": form, - TAB_TITLE_IDENTIFIER: _("User notifications"), - } - context = BaseContext(request, context).context - return render(request, template, context) + return redirect("user:detail") + context = { + "user": user, + "form": form, + TAB_TITLE_IDENTIFIER: self._TAB_TITLE, + } + context = BaseContext(request, context).context + return render(request, self._TEMPLATE, context) -@login_required_modal -@login_required -def contact_view(request: HttpRequest, id: str): - """ Renders contact modal view of a users contact data +class ContactView(LoginRequiredMixin, BaseModalFormView): + def get(self, request: HttpRequest, id: str): + """ Renders contact modal view of a users contact data - Args: - request (HttpRequest): The incoming request - id (str): The user's id + Args: + request (HttpRequest): The incoming request + id (str): The user's id - Returns: + Returns: - """ - user = get_object_or_404(User, id=id) - form = UserContactForm(request.POST or None, instance=user, request=request) - template = "modal/modal_form.html" - context = { - "form": form, - } - context = BaseContext(request, context).context - return render( - request, - template, - context - ) + """ + user = get_object_or_404(User, id=id) + form = UserContactForm(request.POST or None, instance=user, request=request) + context = { + "form": form, + } + context = BaseContext(request, context).context + return render(request, self._TEMPLATE, context) -@login_required_modal -@login_required -def data_team_view(request: HttpRequest, id: str): - """ Renders team data +class TeamDetailModalView(LoginRequiredMixin, BaseModalFormView): + def get(self, request: HttpRequest, id: str): + """ Renders team data - Args: - request (HttpRequest): The incoming request - id (str): The team's id + Args: + request (HttpRequest): The incoming request + id (str): The team's id - Returns: + Returns: - """ - team = get_object_or_404(Team, id=id) - form = TeamDataForm(request.POST or None, instance=team, request=request) - template = "modal/modal_form.html" - context = { - "form": form, - } - context = BaseContext(request, context).context - return render( - request, - template, - context - ) + """ + team = get_object_or_404(Team, id=id) + form = TeamDataForm(request.POST or None, instance=team, request=request) + context = { + "form": form, + } + context = BaseContext(request, context).context + return render(request, self._TEMPLATE, context) -@login_required -def index_team_view(request: HttpRequest): - template = "user/team/index.html" - user = request.user - context = { - "teams": user.shared_teams, - "tab_title": _("Teams"), - } - context = BaseContext(request, context).context - return render(request, template, context) +class TeamIndexView(LoginRequiredMixin, BaseView): + _TEMPLATE = "user/team/index.html" + _TAB_TITLE = _("Teams") + + def get(self, request: HttpRequest): + user = request.user + context = { + "teams": user.shared_teams, + "tab_title": self._TAB_TITLE, + } + context = BaseContext(request, context).context + return render(request, self._TEMPLATE, context) @login_required_modal