diff --git a/aleksis/apps/alsijil/forms.py b/aleksis/apps/alsijil/forms.py index 348b51bf85d2e52b631e33a1394e26b27d7a2bb5..8ea5ee1c553a3d40e82c3a015ed30e0206b7fd6a 100644 --- a/aleksis/apps/alsijil/forms.py +++ b/aleksis/apps/alsijil/forms.py @@ -6,20 +6,21 @@ from django.db.models import Count, Q from django.utils.translation import gettext_lazy as _ from django_global_request.middleware import get_request -from django_select2.forms import Select2Widget +from django_select2.forms import ModelSelect2MultipleWidget, ModelSelect2Widget, Select2Widget from guardian.shortcuts import get_objects_for_user from material import Fieldset, Layout, Row from aleksis.apps.chronos.managers import TimetableType from aleksis.apps.chronos.models import TimePeriod from aleksis.core.models import Group, Person +from aleksis.core.util.core_helpers import get_site_preferences from aleksis.core.util.predicates import check_global_permission from .models import ( - GroupRole, - GroupRoleAssignment, ExcuseType, ExtraMark, + GroupRole, + GroupRoleAssignment, LessonDocumentation, PersonalNote, ) @@ -177,3 +178,76 @@ class GroupRoleForm(forms.ModelForm): class Meta: model = GroupRole fields = ["name", "icon", "colour"] + + +class AssignGroupRoleForm(forms.ModelForm): + layout_base = ["groups", "person", "role", Row("date_start", "date_end")] + + groups = forms.ModelMultipleChoiceField( + label=_("Group"), + required=True, + queryset=Group.objects.all(), + widget=ModelSelect2MultipleWidget( + model=Group, + search_fields=["name__icontains", "short_name__icontains"], + attrs={"data-minimum-input-length": 0, "class": "browser-default",}, + ), + ) + person = forms.ModelChoiceField( + label=_("Person"), + required=True, + queryset=Person.objects.all(), + widget=ModelSelect2Widget( + model=Person, + dependent_fields={"groups": "member_of"}, + search_fields=[ + "first_name__icontains", + "last_name__icontains", + "short_name__icontains", + ], + attrs={"data-minimum-input-length": 0, "class": "browser-default"}, + ), + ) + + def __init__(self, request, *args, **kwargs): + self.request = request + initial = kwargs.get("initial", {}) + + # Build layout with or without groups field + base_layout = self.layout_base[:] + if "groups" in initial: + base_layout.remove("groups") + self.layout = Layout(*base_layout) + + super().__init__(*args, **kwargs) + + if "groups" in initial: + self.fields["groups"].required = False + + # Filter persons by permissions + if not self.request.user.has_perm("alsijil.assign_grouprole"): # Global permission + persons = Person.objects + if initial.get("groups"): + persons = persons.filter(member_of__in=initial["groups"]) + if get_site_preferences()["alsijil__group_owners_can_assign_roles_to_parents"]: + persons = persons.filter( + Q(member_of__owners=self.request.user.person) + | Q(children__member_of__owners=self.request.user.person) + ) + else: + persons = persons.filter(member_of__owners=self.request.user.person) + self.fields["person"].queryset = persons + + def clean_groups(self): + """Ensure that only permitted groups are used.""" + return self.initial["groups"] if "groups" in self.initial else self.cleaned_data["groups"] + + class Meta: + model = GroupRoleAssignment + fields = ["groups", "person", "role", "date_start", "date_end"] + + +class GroupRoleAssignmentEditForm(forms.ModelForm): + class Meta: + model = GroupRoleAssignment + fields = ["date_start", "date_end"] diff --git a/aleksis/apps/alsijil/models.py b/aleksis/apps/alsijil/models.py index 440e12c5715f96f22e162c71632b1bdb6580cc3a..8890c0d286110a06f3b651c51e886157270db36d 100644 --- a/aleksis/apps/alsijil/models.py +++ b/aleksis/apps/alsijil/models.py @@ -13,6 +13,7 @@ from aleksis.apps.alsijil.data_checks import ( PersonalNoteOnHolidaysDataCheck, ) from aleksis.apps.alsijil.managers import PersonalNoteManager +from aleksis.apps.chronos.managers import GroupPropertiesMixin from aleksis.apps.chronos.mixins import WeekRelatedMixin from aleksis.apps.chronos.models import LessonPeriod from aleksis.apps.chronos.util.date import get_current_year @@ -246,7 +247,7 @@ class GroupRole(ExtensibleModel): verbose_name_plural = _("Group roles") -class GroupRoleAssignment(ExtensibleModel): +class GroupRoleAssignment(GroupPropertiesMixin, ExtensibleModel): role = models.ForeignKey( GroupRole, on_delete=models.CASCADE, @@ -260,9 +261,7 @@ class GroupRoleAssignment(ExtensibleModel): verbose_name=_("Assigned person"), ) groups = models.ManyToManyField( - "core.Group", - related_name="group_roles", - verbose_name=_("Groups"), + "core.Group", related_name="group_roles", verbose_name=_("Groups"), ) date_start = models.DateField(verbose_name=_("Start date")) date_end = models.DateField( diff --git a/aleksis/apps/alsijil/preferences.py b/aleksis/apps/alsijil/preferences.py index 0e9706da1e41cdddbdcc9191b353abcb891f2fc6..010c1a6961053e2c5510bf83182a0e8cc30f5a09 100644 --- a/aleksis/apps/alsijil/preferences.py +++ b/aleksis/apps/alsijil/preferences.py @@ -74,3 +74,13 @@ class ActivateGroupRoles(BooleanPreference): name = "activate_group_roles" default = True verbose_name = _("Activate support for creating and assigning group roles") + + +@site_preferences_registry.register +class GroupOwnersCanAssignRolesToParents(BooleanPreference): + section = alsijil + name = "group_owners_can_assign_roles_to_parents" + default = False + verbose_name = _( + "Allow group owners to assign group roles to the parents of the group's members" + ) diff --git a/aleksis/apps/alsijil/rules.py b/aleksis/apps/alsijil/rules.py index 0273c2c748418dc326dfb8c82c9ad48642eae046..3b4d93f0ff69cf1d08084838aac339cfc36f4c1e 100644 --- a/aleksis/apps/alsijil/rules.py +++ b/aleksis/apps/alsijil/rules.py @@ -14,6 +14,7 @@ from .util.predicates import ( has_personal_note_group_perm, is_group_member, is_group_owner, + is_group_role_assignment_group_owner, is_lesson_parent_group_owner, is_lesson_participant, is_lesson_teacher, @@ -219,12 +220,13 @@ add_perm("alsijil.edit_extramark", edit_extramark_predicate) delete_extramark_predicate = view_extramarks_predicate & has_global_perm("alsijil.delete_extramark") add_perm("alsijil.delete_extramark", delete_extramark_predicate) +group_roles_activated_predicate = has_person & is_site_preference_set( + "alsijil", "activate_group_roles" +) # View group role list -view_group_roles_predicate = ( - has_person - & is_site_preference_set("alsijil", "activate_group_roles") - & has_global_perm("alsijil.view_grouprole") +view_group_roles_predicate = group_roles_activated_predicate & has_global_perm( + "alsijil.view_grouprole" ) add_perm("alsijil.view_grouproles", view_group_roles_predicate) @@ -241,3 +243,31 @@ delete_group_role_predicate = view_group_roles_predicate & has_global_perm( "alsijil.delete_grouprole" ) add_perm("alsijil.delete_grouprole", delete_group_role_predicate) + +view_assigned_group_roles_predicate = group_roles_activated_predicate & ( + is_group_owner + | has_global_perm("alsjil.assign_grouprole") + | has_object_perm("alsijil.assign_grouprole") +) +add_perm("alsijil.view_assigned_grouproles", view_assigned_group_roles_predicate) + +assign_group_role_person_predicate = group_roles_activated_predicate & ( + is_person_group_owner | has_global_perm("alsjil.assign_grouprole") +) +add_perm("alsijil.assign_grouprole_to_person", assign_group_role_person_predicate) + +assign_group_role_group_predicate = view_assigned_group_roles_predicate +add_perm("alsijil.assign_grouprole_for_group", assign_group_role_group_predicate) + +edit_group_role_assignment_predicate = group_roles_activated_predicate & ( + has_global_perm("alsjil.assign_grouprole") | is_group_role_assignment_group_owner +) +add_perm("alsijil.edit_grouproleassignment", edit_group_role_assignment_predicate) + +stop_group_role_assignment_predicate = edit_group_role_assignment_predicate +add_perm("alsijil.stop_grouproleassignment", stop_group_role_assignment_predicate) + +delete_group_role_assignment_predicate = group_roles_activated_predicate & ( + has_global_perm("alsjil.assign_grouprole") | is_group_role_assignment_group_owner +) +add_perm("alsijil.delete_grouproleassignment", delete_group_role_assignment_predicate) diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/groups.html b/aleksis/apps/alsijil/templates/alsijil/class_register/groups.html index a2bf6799df9a4f9390fd1a912d5eb9dab51c3cef..f300f3b8457eed468cb3bad31821eb8d94033cf8 100644 --- a/aleksis/apps/alsijil/templates/alsijil/class_register/groups.html +++ b/aleksis/apps/alsijil/templates/alsijil/class_register/groups.html @@ -1,6 +1,6 @@ {# -*- engine:django -*- #} {% extends "core/base.html" %} -{% load i18n static %} +{% load i18n static rules %} {% block browser_title %}{% blocktrans %}My groups{% endblocktrans %}{% endblock %} @@ -38,6 +38,13 @@ <i class="material-icons left">view_week</i> {% trans "Week view" %} </a> + {% has_perm "alsijil.view_assigned_grouproles" user group as can_view_assigned_group_roles %} + {% if can_view_assigned_group_roles %} + <a class="btn primary waves-effect waves-light" href="{% url 'assigned_group_roles' group.pk %}"> + <i class="material-icons left">assignment_ind</i> + {% trans "Roles" %} + </a> + {% endif %} <a class="btn primary waves-effect waves-light" href="{% url "full_register_group" group.pk %}" target="_blank"> <i class="material-icons left">print</i> @@ -75,6 +82,15 @@ {% trans "Week view" %} </a> </p> + {% has_perm "alsijil.view_assigned_grouproles" user group as can_view_assigned_group_roles %} + {% if can_view_assigned_group_roles %} + <p> + <a class="btn primary waves-effect waves-light" href="{% url 'assigned_group_roles' group.pk %}"> + <i class="material-icons left">assignment_ind</i> + {% trans "Roles" %} + </a> + </p> + {% endif %} <p> <a class="btn primary waves-effect waves-light" href="{% url "full_register_group" group.pk %}" target="_blank"> diff --git a/aleksis/apps/alsijil/templates/alsijil/group_role/assign.html b/aleksis/apps/alsijil/templates/alsijil/group_role/assign.html new file mode 100644 index 0000000000000000000000000000000000000000..3405e8da56700ab9e29574c8d6f2d0a0a4b5e09b --- /dev/null +++ b/aleksis/apps/alsijil/templates/alsijil/group_role/assign.html @@ -0,0 +1,33 @@ +{# -*- engine:django -*- #} + +{% extends "core/base.html" %} + +{% load i18n rules any_js material_form %} + +{% block browser_title %} + {% blocktrans with group=group.name %}Assign group role for {{ group }}{% endblocktrans %} +{% endblock %} +{% block page_title %} + {% blocktrans with group=group.name %}Assign group role for {{ group }}{% endblocktrans %} +{% endblock %} + +{% block extra_head %} + {{ form.media.css }} + {% include_css "select2-materialize" %} +{% endblock %} + +{% block content %} + <form action="" method="post"> + {% csrf_token %} + {% form form=form %}{% endform %} + + <button type="submit" class="btn green waves-effect waves-light"> + <i class="material-icons left">assignment_ind</i> + {% trans "Assign" %} + </button> + </form> + + {% include_js "select2-materialize" %} + {{ form.media.js }} +{% endblock %} + diff --git a/aleksis/apps/alsijil/templates/alsijil/group_role/assigned_list.html b/aleksis/apps/alsijil/templates/alsijil/group_role/assigned_list.html new file mode 100644 index 0000000000000000000000000000000000000000..123bc4c2e23d37f67599a3c7a7cc49bd9bae14f9 --- /dev/null +++ b/aleksis/apps/alsijil/templates/alsijil/group_role/assigned_list.html @@ -0,0 +1,144 @@ +{# -*- engine:django -*- #} + +{% extends "core/base.html" %} + +{% load i18n rules any_js material_form static %} +{% load render_table from django_tables2 %} + +{% block browser_title %} + {% blocktrans with group=object.name %}Group roles for {{ group }}{% endblocktrans %} +{% endblock %} +{% block page_title %} + {% blocktrans with group=object.name %}Group roles for {{ group }}{% endblocktrans %} +{% endblock %} + +{% block extra_head %} + {{ block.super }} + <link rel="stylesheet" href="{% static "css/alsijil/alsijil.css" %}"/> +{% endblock %} + +{% block content %} + {% url "assigned_group_roles" object.pk as back_url %} + + <p> + {% has_perm "alsijil.view_my_groups" user as can_view_group_overview %} + {% if can_view_group_overview %} + <a class="btn waves-effect waves-light" href="{% url "my_groups" %}"> + <i class="material-icons left">arrow_back</i> + {% trans "Back to my groups" %} + </a> + {% endif %} + + {% has_perm "alsijil.assign_grouprole_for_group" user object as can_assign_group_role %} + {% if can_assign_group_role %} + <a class="btn green waves-effect waves-light" href="{% url "assign_group_role" object.pk %}"> + <i class="material-icons left">assignment_ind</i> + {% trans "Assign a role to a person" %} + </a> + {% endif %} + </p> + + <div class="row"> + <div class="col s12"> + <ul class="tabs"> + <li class="tab"> + <a class="active" href="#current">{% trans "Current roles" %} ({{ today|date:"SHORT_DATE_FORMAT" }})</a> + </li> + <li class="tab"> + <a href="#all">{% trans "All assignments" %}</a> + </li> + </ul> + </div> + + <div id="current" class="col s12"> + <div class="collection"> + {% for role in roles %} + <div class="collection-item"> + <div class="row no-margin"> + <div class="col s12 m5 l4 xl3 no-padding"> + {% if can_assign_group_role %} + <a class="btn waves-effect waves-light right hide-on-med-and-up" + href="{% url "assign_group_role" object.pk role.pk %}"> + <i class="material-icons center">add</i> + </a> + {% endif %} + + <div class="btn-margin"> + {% include "alsijil/group_role/chip.html" with role=role %} + </div> + </div> + + <div class="col s12 m7 l8 xl9 no-padding"> + {% if can_assign_group_role %} + <a class="btn waves-effect waves-light right hide-on-small-only" + href="{% url "assign_group_role" object.pk role.pk %}"> + <i class="material-icons center">add</i> + </a> + {% endif %} + + {% for assignment in role.assignments.all %} + <a class="chip dropdown-trigger" href="#" + data-target="dropdown-{{ assignment.pk }}" title="{{ assignment }}">{{ assignment.person }} + {% if object not in assignment.groups.all %} + <small>({{ assignment.group_names }})</small> + {% endif %} + </a> + + {% include "alsijil/group_role/assignment_options.html" with assignment=assignment back_url=back_url %} + {% empty %} + <div class="grey-text darken-3">{% trans "No one assigned." %}</div> + {% endfor %} + </div> + </div> + </div> + {% endfor %} + </div> + + <div class="alert primary"> + <div> + <i class="material-icons left">info</i> + {% blocktrans %} + You can get some additional actions for each group role assignment if you click on the name of the + corresponding person. + {% endblocktrans %} + </div> + </div> + </div> + + + <div class="col s12 " id="all"> + <table class="responsive-table"> + <thead> + <tr> + <th class="chip-height">{% trans "Group role" %}</th> + <th>{% trans "Person" %}</th> + <th>{% trans "Start date" %}</th> + <th>{% trans "End date" %}</th> + <th>{% trans "Actions" %}</th> + </tr> + </thead> + {% for assignment in assignments %} + <tr> + <td> + {% include "alsijil/group_role/chip.html" with role=assignment.role %} + </td> + <td> + {{ assignment.person }} + </td> + <td>{{ assignment.date_start }}</td> + <td>{{ assignment.date_end|default:"–" }}</td> + <td> + <a class="btn waves-effect waves-light dropdown-trigger" href="#" + data-target="dropdown-{{ assignment.pk }}-d2"> + <i class="material-icons left">list</i> + {% trans "Actions" %} + </a> + {% include "alsijil/group_role/assignment_options.html" with assignment=assignment back_url=back_url suffix="-d2" %} + </td> + </tr> + {% endfor %} + </table> + </div> + </div> +{% endblock %} + diff --git a/aleksis/apps/alsijil/templates/alsijil/group_role/assignment_options.html b/aleksis/apps/alsijil/templates/alsijil/group_role/assignment_options.html new file mode 100644 index 0000000000000000000000000000000000000000..ff50720db9dacd5437fb4b43ecf19e9719138de3 --- /dev/null +++ b/aleksis/apps/alsijil/templates/alsijil/group_role/assignment_options.html @@ -0,0 +1,33 @@ +{# -*- engine:django -*- #} + +{% load i18n rules %} + +{% has_perm "alsijil.edit_grouproleassignment" user assignment as can_edit %} +{% has_perm "alsijil.stop_grouproleassignment" user assignment as can_stop %} +{% has_perm "alsijil.delete_grouproleassignment" user assignment as can_delete %} + +<ul id="dropdown-{{ assignment.pk }}{{ suffix }}" class="dropdown-content"> + {% if can_edit %} + <li> + <a href="{% url "edit_group_role_assignment" assignment.pk %}?next={{ back_url }}"> + <i class="material-icons left">edit</i> {% trans "Edit" %} + </a> + </li> + {% endif %} + + {% if not assignment.date_end and can_stop %} + <li> + <a href="#"> + <i class="material-icons left">stop</i> {% trans "Stop" %} + </a> + </li> + {% endif %} + + {% if can_delete %} + <li> + <a href="{% url "delete_group_role_assignment" assignment.pk %}?next={{ back_url }}" class="red-text"> + <i class="material-icons left">delete</i> {% trans "Delete" %} + </a> + </li> + {% endif %} +</ul> \ No newline at end of file diff --git a/aleksis/apps/alsijil/templates/alsijil/group_role/chip.html b/aleksis/apps/alsijil/templates/alsijil/group_role/chip.html new file mode 100644 index 0000000000000000000000000000000000000000..f50fc10e965c92762451a985dc30c28d78c64b08 --- /dev/null +++ b/aleksis/apps/alsijil/templates/alsijil/group_role/chip.html @@ -0,0 +1,6 @@ +{# -*- engine:django -*- #} + +<div class="chip white-text {{ role.colour|default:"black" }}"> + <i class="material-icons left">{{ role.icon|default:"assignment_ind" }}</i> + {{ role.name }} +</div> \ No newline at end of file diff --git a/aleksis/apps/alsijil/templates/alsijil/group_role/edit_assignment.html b/aleksis/apps/alsijil/templates/alsijil/group_role/edit_assignment.html new file mode 100644 index 0000000000000000000000000000000000000000..bc5038654980374fcd0c65639b7c4d3488871625 --- /dev/null +++ b/aleksis/apps/alsijil/templates/alsijil/group_role/edit_assignment.html @@ -0,0 +1,18 @@ +{# -*- engine:django -*- #} + +{% extends "core/base.html" %} + +{% load i18n rules material_form %} + +{% block browser_title %}{% blocktrans %}Edit group role assignment{% endblocktrans %}{% endblock %} +{% block page_title %}{% blocktrans %}Edit group role assignment{% endblocktrans %}{% endblock %} + +{% block content %} + <form action="" method="post"> + {% csrf_token %} + {% form form=form %}{% endform %} + + {% include "core/partials/save_button.html" %} + </form> +{% endblock %} + diff --git a/aleksis/apps/alsijil/urls.py b/aleksis/apps/alsijil/urls.py index cb9b45dac388203ac75b170aba1a5d6c2a757fd0..0e007075d04cb15e0d4fd649b896225f12beced2 100644 --- a/aleksis/apps/alsijil/urls.py +++ b/aleksis/apps/alsijil/urls.py @@ -57,4 +57,34 @@ urlpatterns = [ views.GroupRoleDeleteView.as_view(), name="delete_group_role", ), + path( + "groups/<int:pk>/group_roles/", + views.AssignedGroupRolesView.as_view(), + name="assigned_group_roles", + ), + path( + "groups/<int:pk>/group_roles/assign/", + views.AssignGroupRoleView.as_view(), + name="assign_group_role", + ), + path( + "groups/<int:pk>/group_roles/<int:role_pk>/assign/", + views.AssignGroupRoleView.as_view(), + name="assign_group_role", + ), + path( + "group_roles/assignments/<int:pk>/edit/", + views.GroupRoleAssignmentEditView.as_view(), + name="edit_group_role_assignment", + ), + path( + "group_roles/assignments/<int:pk>/stop/", + views.GroupRoleAssignmentStopView.as_view(), + name="stop_group_role_assignment", + ), + path( + "group_roles/assignments/<int:pk>/delete/", + views.GroupRoleAssignmentDeleteView.as_view(), + name="delete_group_role_assignment", + ), ] diff --git a/aleksis/apps/alsijil/util/predicates.py b/aleksis/apps/alsijil/util/predicates.py index 95c7825c2a1eceeb2aed222260816ec9575ad0a8..c1d6d063e7361e05468a8588efd65a7474e753c3 100644 --- a/aleksis/apps/alsijil/util/predicates.py +++ b/aleksis/apps/alsijil/util/predicates.py @@ -246,3 +246,18 @@ def is_personal_note_lesson_parent_group_owner(user: User, obj: PersonalNote) -> def is_teacher(user: User, obj: Person) -> bool: """Predicate which checks if the provided object is a teacher.""" return user.person.is_teacher + + +@predicate +def is_group_role_assignment_group_owner(user: User, obj: Union[Group, Person]) -> bool: + """Predicate for group owners of a group role assignment. + + Checks whether the person linked to the user is the owner of the groups + linked to the given group role assignment. + If there isn't provided a group role assignment, it will return `False`. + """ + if obj: + for group in obj.groups.all(): + if user.person in list(group.owners.all()): + return True + return False diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py index dac92a9f9e98db321156539e36951fe701e681db..b37becc357e78adcc680bcd4099594870973a9d7 100644 --- a/aleksis/apps/alsijil/views.py +++ b/aleksis/apps/alsijil/views.py @@ -6,6 +6,7 @@ from django.db.models import Count, Exists, OuterRef, Prefetch, Q, Subquery, Sum from django.http import Http404, HttpRequest, HttpResponse, HttpResponseNotFound from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse, reverse_lazy +from django.utils import timezone from django.utils.decorators import method_decorator from django.utils.translation import ugettext as _ from django.views.decorators.cache import never_cache @@ -21,30 +22,36 @@ from aleksis.apps.chronos.managers import TimetableType from aleksis.apps.chronos.models import Holiday, LessonPeriod, TimePeriod from aleksis.apps.chronos.util.build import build_weekdays from aleksis.apps.chronos.util.date import get_weeks_for_year, week_weekday_to_date -from aleksis.core.mixins import AdvancedCreateView, AdvancedDeleteView, AdvancedEditView +from aleksis.core.mixins import ( + AdvancedCreateView, + AdvancedDeleteView, + AdvancedEditView, + SuccessNextMixin, +) from aleksis.core.models import Group, Person, SchoolTerm from aleksis.core.util import messages from aleksis.core.util.core_helpers import get_site_preferences, objectgetter_optional from .forms import ( AssignGroupRoleForm, - GroupRoleForm, ExcuseTypeForm, ExtraMarkForm, + GroupRoleAssignmentEditForm, + GroupRoleForm, LessonDocumentationForm, PersonalNoteFormSet, RegisterAbsenceForm, SelectForm, ) from .models import ( - GroupRole, - GroupRoleAssignment, ExcuseType, ExtraMark, + GroupRole, + GroupRoleAssignment, LessonDocumentation, PersonalNote, ) -from .tables import GroupRoleTable, ExcuseTypeTable, ExtraMarkTable +from .tables import ExcuseTypeTable, ExtraMarkTable, GroupRoleTable from .util.alsijil_helpers import get_lesson_period_by_pk, get_timetable_instance_by_pk @@ -902,3 +909,117 @@ class GroupRoleDeleteView(PermissionRequiredMixin, RevisionMixin, AdvancedDelete template_name = "core/pages/delete.html" success_url = reverse_lazy("group_roles") success_message = _("The group role has been deleted.") + + +class AssignedGroupRolesView(PermissionRequiredMixin, DetailView): + permission_required = "alsijil.view_assigned_grouproles" + model = Group + template_name = "alsijil/group_role/assigned_list.html" + + def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: + context = super().get_context_data() + + today = timezone.now().date() + context["today"] = today + + self.roles = GroupRole.objects.prefetch_related( + Prefetch( + "assignments", + queryset=GroupRoleAssignment.objects.filter( + Q(date_start__lte=today) & (Q(date_end__gte=today) | Q(date_end__isnull=True)) + ) + .filter(Q(groups=self.object) | Q(groups__child_groups=self.object)) + .distinct(), + ) + ) + context["roles"] = self.roles + assignments = ( + GroupRoleAssignment.objects.filter( + Q(groups=self.object) | Q(groups__child_groups=self.object) + ) + .distinct() + .order_by("-date_start") + ) + context["assignments"] = assignments + return context + + +@method_decorator(never_cache, name="dispatch") +class AssignGroupRoleView(PermissionRequiredMixin, AdvancedCreateView): + model = GroupRoleAssignment + form_class = AssignGroupRoleForm + permission_required = "alsijil.assign_grouprole_for_group" + template_name = "alsijil/group_role/assign.html" + success_message = _("The group role has been assigned.") + + def get_success_url(self) -> str: + return reverse("assigned_group_roles", args=[self.group.pk]) + + def get_permission_object(self): + self.group = get_object_or_404(Group, pk=self.kwargs.get("pk")) + try: + self.role = GroupRole.objects.get(pk=self.kwargs.get("role_pk")) + except GroupRole.DoesNotExist: + self.role = None + return self.group + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs["request"] = self.request + kwargs["initial"] = {"role": self.role, "groups": [self.group]} + return kwargs + + def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: + context = super().get_context_data(**kwargs) + context["role"] = self.role + context["group"] = self.group + return context + + +@method_decorator(never_cache, name="dispatch") +class GroupRoleAssignmentEditView(PermissionRequiredMixin, SuccessNextMixin, AdvancedEditView): + """Edit view for group role assignments.""" + + model = GroupRoleAssignment + form_class = GroupRoleAssignmentEditForm + permission_required = "alsijil.edit_grouproleassignment" + template_name = "alsijil/group_role/edit_assignment.html" + success_message = _("The group role assignment has been saved.") + + def get_default_success_url(self) -> str: + pk = self.object.groups.first().pk + return reverse("assigned_group_roles", args=[pk]) + + +@method_decorator(never_cache, "dispatch") +class GroupRoleAssignmentStopView(PermissionRequiredMixin, SuccessNextMixin, DetailView): + model = GroupRoleAssignment + permission_required = "alsijil.stop_grouproleassignment" + + def get_default_success_url(self) -> str: + pk = self.object.groups.first().pk + return reverse("assigned_group_roles", args=[pk]) + + def get(self, request, *args, **kwargs): + self.object = self.get_object() + if not self.object.date_end: + self.object.date_end = timezone.now().date() + self.object.save() + messages.success(request, _("The group role assignment has been stopped.")) + return redirect(self.get_success_url()) + + +@method_decorator(never_cache, "dispatch") +class GroupRoleAssignmentDeleteView( + PermissionRequiredMixin, RevisionMixin, SuccessNextMixin, AdvancedDeleteView +): + """Delete view for group role assignments.""" + + model = GroupRoleAssignment + permission_required = "alsijil.delete_grouproleassignment" + template_name = "core/pages/delete.html" + success_message = _("The group role assignment has been deleted.") + + def get_default_success_url(self) -> str: + pk = self.object.groups.first().pk + return reverse("assigned_group_roles", args=[pk])