# # Copyright 2023 © AE UTBM # ae@utbm.fr / ae.info@utbm.fr # # This file is part of the website of the UTBM Student Association (AE UTBM), # https://ae.utbm.fr. # # You can find the source code of the website at https://github.com/ae-utbm/sith # # LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3) # SEE : https://raw.githubusercontent.com/ae-utbm/sith/master/LICENSE # OR WITHIN THE LOCAL FILE "LICENSE" # # """Views to manage Groups.""" from django import forms from django.contrib.auth.mixins import PermissionRequiredMixin from django.contrib.auth.models import Permission from django.contrib.messages.views import SuccessMessageMixin from django.core.exceptions import ImproperlyConfigured from django.shortcuts import get_object_or_404 from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ from django.views.generic import ListView from django.views.generic.edit import CreateView, DeleteView, UpdateView from core.auth.mixins import CanEditMixin from core.models import Group, User from core.views import DetailFormView from core.views.forms import PermissionGroupsForm from core.views.widgets.ajax_select import AutoCompleteSelectMultipleUser # Forms class EditMembersForm(forms.Form): """Add and remove members from a Group.""" def __init__(self, *args, **kwargs): self.current_users = kwargs.pop("users", []) super().__init__(*args, **kwargs) self.fields["users_added"] = forms.ModelMultipleChoiceField( label=_("Users to add to group"), help_text=_("Search users to add (one or more)."), required=False, widget=AutoCompleteSelectMultipleUser, queryset=User.objects.exclude(id__in=self.current_users).all(), ) self.fields["users_removed"] = forms.ModelMultipleChoiceField( User.objects.filter(id__in=self.current_users).all(), label=_("Users to remove from group"), required=False, widget=forms.CheckboxSelectMultiple, ) # Views class GroupListView(CanEditMixin, ListView): """Displays the Group list.""" model = Group queryset = Group.objects.filter(is_manually_manageable=True) ordering = ["name"] template_name = "core/group_list.jinja" class GroupEditView(CanEditMixin, UpdateView): """Edit infos of a Group.""" model = Group queryset = Group.objects.filter(is_manually_manageable=True) pk_url_kwarg = "group_id" template_name = "core/group_edit.jinja" fields = ["name", "description"] class GroupCreateView(PermissionRequiredMixin, CreateView): """Add a new Group.""" model = Group queryset = Group.objects.filter(is_manually_manageable=True) template_name = "core/create.jinja" fields = ["name", "description"] permission_required = "core.add_group" class GroupTemplateView(CanEditMixin, DetailFormView): """Display all users in a given Group Allow adding and removing users from it. """ model = Group queryset = Group.objects.filter(is_manually_manageable=True) form_class = EditMembersForm pk_url_kwarg = "group_id" template_name = "core/group_detail.jinja" def form_valid(self, form): resp = super().form_valid(form) data = form.clean() group = self.get_object() if data["users_removed"]: for user in data["users_removed"]: group.users.remove(user) if data["users_added"]: for user in data["users_added"]: group.users.add(user) group.save() return resp def get_success_url(self): return reverse_lazy( "core:group_detail", kwargs={"group_id": self.get_object().id} ) def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs["users"] = self.get_object().users.all() return kwargs class GroupDeleteView(CanEditMixin, DeleteView): """Delete a Group.""" model = Group queryset = Group.objects.filter(is_manually_manageable=True) pk_url_kwarg = "group_id" template_name = "core/delete_confirm.jinja" success_url = reverse_lazy("core:group_list") class PermissionGroupsUpdateView( PermissionRequiredMixin, SuccessMessageMixin, UpdateView ): """Manage the groups that have a specific permission. Notes: This is an `UpdateView`, but unlike typical `UpdateView`, it doesn't accept url arguments to retrieve the object to update. As such, a `PermissionGroupsUpdateView` can only deal with a single hardcoded permission. This is not a limitation, but an on-purpose design, mainly for security matters. Example: ```python class SubscriptionPermissionView(PermissionGroupsUpdateView): permission = "subscription.add_subscription" ``` """ permission_required = "auth.change_permission" template_name = "core/edit.jinja" form_class = PermissionGroupsForm permission = None success_message = _("Groups have been successfully updated.") def get_object(self, *args, **kwargs): if not self.permission: raise ImproperlyConfigured( f"{self.__class__.__name__} is missing the permission attribute. " "Please fill it with either a permission string " "or a Permission object." ) if isinstance(self.permission, Permission): return self.permission if isinstance(self.permission, str): try: app_label, codename = self.permission.split(".") except ValueError as e: raise ValueError( "Permission name should be in the form " "app_label.permission_codename." ) from e return get_object_or_404( Permission, codename=codename, content_type__app_label=app_label ) raise TypeError( f"{self.__class__.__name__}.permission " f"must be a string or a permission instance." ) def get_success_url(self): # if children classes define a success url, return it, # else stay on the same page return self.success_url or self.request.path