From d479a3cde984199322df7d3a38419cd7ed495a05 Mon Sep 17 00:00:00 2001 From: Jonathan Weth <git@jonathanweth.de> Date: Wed, 6 Apr 2022 21:35:01 +0200 Subject: [PATCH 01/11] Add missing migration --- .../0004_alter_seatingplan_subject.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 aleksis/apps/stoelindeling/migrations/0004_alter_seatingplan_subject.py diff --git a/aleksis/apps/stoelindeling/migrations/0004_alter_seatingplan_subject.py b/aleksis/apps/stoelindeling/migrations/0004_alter_seatingplan_subject.py new file mode 100644 index 0000000..9f0fb57 --- /dev/null +++ b/aleksis/apps/stoelindeling/migrations/0004_alter_seatingplan_subject.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2.12 on 2022-04-06 17:54 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('chronos', '0011_alter_timeperiod_weekday'), + ('stoelindeling', '0003_seat_negative_x_y'), + ] + + operations = [ + migrations.AlterField( + model_name='seatingplan', + name='subject', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='chronos.subject', verbose_name='Subject'), + ), + ] -- GitLab From 412153bfc87e954ee963802373ce421adabd3d06 Mon Sep 17 00:00:00 2001 From: Jonathan Weth <git@jonathanweth.de> Date: Sat, 9 Apr 2022 16:22:52 +0200 Subject: [PATCH 02/11] Add intergration of seating plans --- aleksis/apps/stoelindeling/forms.py | 71 ++++++++++++++++++- .../apps/stoelindeling/model_extensions.py | 46 ++++++++++++ aleksis/apps/stoelindeling/rules.py | 4 ++ .../stoelindeling/seating_plan/copy.html | 26 +++++++ aleksis/apps/stoelindeling/urls.py | 5 ++ aleksis/apps/stoelindeling/views.py | 62 ++++++++++++++-- 6 files changed, 206 insertions(+), 8 deletions(-) create mode 100644 aleksis/apps/stoelindeling/model_extensions.py create mode 100644 aleksis/apps/stoelindeling/templates/stoelindeling/seating_plan/copy.html diff --git a/aleksis/apps/stoelindeling/forms.py b/aleksis/apps/stoelindeling/forms.py index e5f8c0e..a5067b7 100644 --- a/aleksis/apps/stoelindeling/forms.py +++ b/aleksis/apps/stoelindeling/forms.py @@ -1,4 +1,5 @@ from django import forms +from django.utils.translation import gettext as _ from django_select2.forms import ModelSelect2Widget from material import Layout, Row @@ -23,11 +24,26 @@ class SeatingPlanCreateForm(forms.ModelForm): super().__init__(*args, **kwargs) qs = Group.objects.all() - if not self.request.user.has_perm("stoelindeling.view_seatingplan"): + if not self.request.user.has_perm("stoelindeling.add_seatingplan"): qs = qs.filter(owners=self.request.user.person) self.fields["group"].queryset = qs + def clean(self): + cleaned_data = super().clean() + if ( + cleaned_data["group"].subject + and cleaned_data["subject"] + and cleaned_data["group"].subject != cleaned_data["subject"] + ): + raise forms.ValidationError( + _( + "The group you selected has a fixed subject ({}). " + "You are not allowed to use another subject than it." + ).format(cleaned_data["group"].subject) + ) + return cleaned_data + class Meta: model = SeatingPlan fields = ["group", "subject", "room"] @@ -47,6 +63,59 @@ class SeatingPlanCreateForm(forms.ModelForm): } +class SeatingPlanCopyForm(forms.ModelForm): + layout = Layout( + Row( + "room", + "group", + "subject", + ) + ) + + def __init__(self, *args, **kwargs): + self.request = kwargs.pop("request") + super().__init__(*args, **kwargs) + + self.fields["room"].disabled = True + + qs = Group.objects.filter(parent_groups=self.instance.group) + if not self.request.user.has_perm("stoelindeling.add_seatingplan"): + qs = qs.filter(owners=self.request.user.person) + + self.fields["group"].queryset = qs + + def clean(self): + cleaned_data = super().clean() + if cleaned_data["group"] == self.instance.group: + raise forms.ValidationError(_("Group cannot be the same as the original.")) + if ( + cleaned_data["group"].subject + and cleaned_data["subject"] + and cleaned_data["group"].subject != cleaned_data["subject"] + ): + raise forms.ValidationError( + _( + "The group you selected has a fixed subject ({}). " + "You are not allowed to use another subject than it." + ).format(cleaned_data["group"].subject) + ) + return cleaned_data + + class Meta: + model = SeatingPlan + fields = ["room", "group", "subject"] + widgets = { + "group": ModelSelect2Widget( + search_fields=["name__icontains", "short_name__icontains"], + attrs={"data-minimum-input-length": 0, "class": "browser-default"}, + ), + "subject": ModelSelect2Widget( + search_fields=["name__icontains", "short_name__icontains"], + attrs={"data-minimum-input-length": 0, "class": "browser-default"}, + ), + } + + class SeatingPlanForm(forms.ModelForm): layout = Layout( Row( diff --git a/aleksis/apps/stoelindeling/model_extensions.py b/aleksis/apps/stoelindeling/model_extensions.py new file mode 100644 index 0000000..558f186 --- /dev/null +++ b/aleksis/apps/stoelindeling/model_extensions.py @@ -0,0 +1,46 @@ +from django.apps import apps + +from aleksis.apps.chronos.models import Room, Subject +from aleksis.apps.stoelindeling.models import SeatingPlan +from aleksis.core.models import Group + + +@Group.method +def get_seating_plan(self, room: Room, subject: Subject = None): + """Find a seating plan for a group.""" + qs = SeatingPlan.objects.filter(group=self, room=room) + if subject: + qs = qs.filter(subject=subject) + if qs.exists(): + return qs.first() + if subject: + qs = SeatingPlan.objects.filter(group=self, room=room) + if qs.exists(): + return qs.first() + qs = SeatingPlan.objects.filter(group__child_groups=self, room=room) + return qs.first() + + +if apps.is_installed("aleksis.apps.alsijil"): + from aleksis.apps.alsijil.models import Event, ExtraLesson, LessonPeriod + + def seating_plan_lesson_period(self): + if self.get_groups().count() == 1: + return self.get_groups().all()[0].get_seating_plan(self.get_room(), self.get_subject()) + return None + + LessonPeriod.property_(seating_plan_lesson_period, name="seating_plan") + + def seating_plan_extra_lesson(self): + if self.groups.count() == 1: + return self.groups.all()[0].get_seating_plan(self.room, self.subject) + return None + + ExtraLesson.property_(seating_plan_extra_lesson, name="seating_plan") + + def seating_plan_event(self): + if self.groups.count() == 1 and self.rooms.count() == 1: + return self.groups.all()[0].get_seating_plan(self.rooms.all()[0]) + return None + + Event.property_(seating_plan_event, name="seating_plan") diff --git a/aleksis/apps/stoelindeling/rules.py b/aleksis/apps/stoelindeling/rules.py index 3ad3c08..f01f7e1 100644 --- a/aleksis/apps/stoelindeling/rules.py +++ b/aleksis/apps/stoelindeling/rules.py @@ -32,6 +32,10 @@ add_seatingplan_predicate = view_seatingplans_predicate & has_global_perm( ) add_perm("stoelindeling.add_seatingplan_rule", add_seatingplan_predicate) +# Copy seating plan +copy_seatingplan_predicate = view_seatingplan_predicate & add_seatingplan_predicate +add_perm("stoelindeling.copy_seatingplan_rule", copy_seatingplan_predicate) + # Edit seating plan edit_seatingplan_predicate = view_seatingplans_predicate & ( has_global_perm("stoelindeling.change_seatingplan") diff --git a/aleksis/apps/stoelindeling/templates/stoelindeling/seating_plan/copy.html b/aleksis/apps/stoelindeling/templates/stoelindeling/seating_plan/copy.html new file mode 100644 index 0000000..0c7ebf3 --- /dev/null +++ b/aleksis/apps/stoelindeling/templates/stoelindeling/seating_plan/copy.html @@ -0,0 +1,26 @@ +{# -*- engine:django -*- #} + +{% extends "core/base.html" %} +{% load material_form i18n any_js %} + +{% block browser_title %}{% blocktrans %}Copy seating plan{% endblocktrans %}{% endblock %} +{% block page_title %}{% blocktrans %}Copy seating plan{% endblocktrans %}{% endblock %} + +{% block extra_head %} + {{ form.media.css }} + {% include_css "select2-materialize" %} +{% endblock %} + +{% block content %} + <p class="flow-text"> + {% trans "Seating plan to copy" %}: {{ object }} + </p> + <form method="post"> + {% csrf_token %} + {% form form=form %}{% endform %} + {% include "core/partials/save_button.html" %} + </form> + + {% include_js "select2-materialize" %} + {{ form.media.js }} +{% endblock %} diff --git a/aleksis/apps/stoelindeling/urls.py b/aleksis/apps/stoelindeling/urls.py index 94c22cc..0e1ce6b 100644 --- a/aleksis/apps/stoelindeling/urls.py +++ b/aleksis/apps/stoelindeling/urls.py @@ -19,6 +19,11 @@ urlpatterns = [ views.SeatingPlanEditView.as_view(), name="edit_seating_plan", ), + path( + "seating_plans/<int:pk>/copy/", + views.SeatingPlanCopyView.as_view(), + name="copy_seating_plan", + ), path( "seating_plans/<int:pk>/delete/", views.SeatingPlanDeleteView.as_view(), diff --git a/aleksis/apps/stoelindeling/views.py b/aleksis/apps/stoelindeling/views.py index 51b7189..65025df 100644 --- a/aleksis/apps/stoelindeling/views.py +++ b/aleksis/apps/stoelindeling/views.py @@ -1,6 +1,6 @@ from django.contrib import messages from django.shortcuts import redirect -from django.urls import reverse_lazy +from django.urls import reverse, reverse_lazy from django.utils.decorators import method_decorator from django.utils.translation import gettext as _ from django.views.decorators.cache import never_cache @@ -10,9 +10,15 @@ from django_tables2 import SingleTableView from reversion.views import RevisionMixin from rules.contrib.views import PermissionRequiredMixin -from aleksis.core.mixins import AdvancedCreateView, AdvancedDeleteView, AdvancedEditView +from aleksis.core.mixins import ( + AdvancedCreateView, + AdvancedDeleteView, + AdvancedEditView, + SuccessNextMixin, +) -from .forms import SeatFormSet, SeatingPlanCreateForm, SeatingPlanForm +from aleksis.core.views import LoginView +from .forms import SeatFormSet, SeatingPlanCopyForm, SeatingPlanCreateForm, SeatingPlanForm from .models import Seat, SeatingPlan from .tables import SeatingPlanTable from .util.perms import get_allowed_seating_plans @@ -39,7 +45,7 @@ class SeatingPlanDetailView(PermissionRequiredMixin, DetailView): @method_decorator(never_cache, name="dispatch") -class SeatingPlanCreateView(PermissionRequiredMixin, AdvancedCreateView): +class SeatingPlanCreateView(PermissionRequiredMixin, SuccessNextMixin, AdvancedCreateView): """Create view for seating plans.""" model = SeatingPlan @@ -56,7 +62,7 @@ class SeatingPlanCreateView(PermissionRequiredMixin, AdvancedCreateView): @method_decorator(never_cache, name="dispatch") -class SeatingPlanEditView(PermissionRequiredMixin, AdvancedEditView): +class SeatingPlanEditView(PermissionRequiredMixin, SuccessNextMixin, AdvancedEditView): """Edit view for seating plans.""" model = SeatingPlan @@ -65,6 +71,9 @@ class SeatingPlanEditView(PermissionRequiredMixin, AdvancedEditView): template_name = "stoelindeling/seating_plan/edit.html" success_message = _("The seating plan has been saved.") + def get_success_url(self): + return LoginView.get_redirect_url(self) or reverse("seating_plan", args=[self.object.pk]) + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -112,13 +121,52 @@ class SeatingPlanEditView(PermissionRequiredMixin, AdvancedEditView): Seat.objects.bulk_update(objects_to_update, ["x", "y", "seated"]) messages.success(self.request, _("The seating plan has been updated.")) - return redirect("seating_plan", self.object.pk) + return redirect(self.get_success_url()) return super().form_invalid(form) @method_decorator(never_cache, name="dispatch") -class SeatingPlanDeleteView(PermissionRequiredMixin, RevisionMixin, AdvancedDeleteView): +class SeatingPlanCopyView(PermissionRequiredMixin, SuccessNextMixin, AdvancedEditView): + """Copy view for seating plans.""" + + model = SeatingPlan + form_class = SeatingPlanCopyForm + permission_required = "stoelindeling.copy_seatingplan_rule" + template_name = "stoelindeling/seating_plan/copy.html" + + def get_success_url(self): + return reverse("edit_seating_plan", args=[self.new_object.pk]) # FiXME NEXT URL + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs["request"] = self.request + return kwargs + + def form_valid(self, form): + context = self.get_context_data() + + self.new_object = self.object + self.new_object.pk = None + self.new_object.group = form.cleaned_data["group"] + self.new_object.subject = form.cleaned_data["subject"] + self.new_object.save() + + for seat in self.object.seats.all(): + if seat.person in self.new_object.group.members.all(): + new_seat = seat + new_seat.pk = None + new_seat.seating_plan = self.new_object + new_seat.save() + + messages.success(self.request, _("The seating plan has been copied successfully.")) + return redirect(self.get_success_url()) + + +@method_decorator(never_cache, name="dispatch") +class SeatingPlanDeleteView( + PermissionRequiredMixin, RevisionMixin, SuccessNextMixin, AdvancedDeleteView +): """Delete view for seating plans.""" model = SeatingPlan -- GitLab From 7c1ecce6db68c09de421883f7ba2a665b9834e23 Mon Sep 17 00:00:00 2001 From: Jonathan Weth <git@jonathanweth.de> Date: Sat, 9 Apr 2022 16:59:06 +0200 Subject: [PATCH 03/11] Fix permissions --- aleksis/apps/stoelindeling/rules.py | 17 +++++++++-------- aleksis/apps/stoelindeling/util/perms.py | 14 +++++++++++++- aleksis/apps/stoelindeling/views.py | 10 +++++++++- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/aleksis/apps/stoelindeling/rules.py b/aleksis/apps/stoelindeling/rules.py index f01f7e1..1617bdf 100644 --- a/aleksis/apps/stoelindeling/rules.py +++ b/aleksis/apps/stoelindeling/rules.py @@ -5,10 +5,11 @@ from aleksis.core.util.predicates import ( has_global_perm, has_object_perm, has_person, + is_group_owner, ) from .models import SeatingPlan -from .util.perms import is_group_owner +from .util.perms import is_plan_group_owner # View seating plan list view_seatingplans_predicate = has_person & ( @@ -22,13 +23,13 @@ add_perm("stoelindeling.view_seatingplans_rule", view_seatingplans_predicate) view_seatingplan_predicate = has_person & ( has_global_perm("stoelindeling.view_seatingplan") | has_object_perm("stoelindeling.view_seatingplan") - | is_group_owner + | is_plan_group_owner ) add_perm("stoelindeling.view_seatingplan_rule", view_seatingplan_predicate) # Add seating plan -add_seatingplan_predicate = view_seatingplans_predicate & has_global_perm( - "stoelindeling.add_seatingplan" +add_seatingplan_predicate = view_seatingplans_predicate & ( + has_global_perm("stoelindeling.add_seatingplan") | is_group_owner | is_plan_group_owner ) add_perm("stoelindeling.add_seatingplan_rule", add_seatingplan_predicate) @@ -37,17 +38,17 @@ copy_seatingplan_predicate = view_seatingplan_predicate & add_seatingplan_predic add_perm("stoelindeling.copy_seatingplan_rule", copy_seatingplan_predicate) # Edit seating plan -edit_seatingplan_predicate = view_seatingplans_predicate & ( +edit_seatingplan_predicate = view_seatingplan_predicate & ( has_global_perm("stoelindeling.change_seatingplan") - | is_group_owner + | is_plan_group_owner | has_object_perm("stoelindeling.change_seatingplan") ) add_perm("stoelindeling.edit_seatingplan_rule", edit_seatingplan_predicate) # Delete seating plan -delete_seatingplan_predicate = view_seatingplans_predicate & ( +delete_seatingplan_predicate = view_seatingplan_predicate & ( has_global_perm("stoelindeling.delete_seatingplan") - | is_group_owner + | is_plan_group_owner | has_object_perm("stoelindeling.delete_seatingplan") ) add_perm("stoelindeling.delete_seatingplan_rule", delete_seatingplan_predicate) diff --git a/aleksis/apps/stoelindeling/util/perms.py b/aleksis/apps/stoelindeling/util/perms.py index c55d09b..0876019 100644 --- a/aleksis/apps/stoelindeling/util/perms.py +++ b/aleksis/apps/stoelindeling/util/perms.py @@ -3,12 +3,24 @@ from django.db.models import Q from guardian.shortcuts import get_objects_for_user from rules import predicate +from aleksis.core.models import Group + from ..models import SeatingPlan @predicate -def is_group_owner(user, seating_plan: SeatingPlan) -> bool: +def is_group_owner(user, group: Group) -> bool: + """Predicate which checks if the user is a owner of the group.""" + if not isinstance(group, Group): + return False + return user.person in group.owners.all() + + +@predicate +def is_plan_group_owner(user, seating_plan: SeatingPlan) -> bool: """Predicate which checks if the user is a owner of the seating plan's group.""" + if not isinstance(seating_plan, SeatingPlan): + return False return user.person in seating_plan.group.owners.all() diff --git a/aleksis/apps/stoelindeling/views.py b/aleksis/apps/stoelindeling/views.py index 65025df..0b3f4b9 100644 --- a/aleksis/apps/stoelindeling/views.py +++ b/aleksis/apps/stoelindeling/views.py @@ -16,8 +16,8 @@ from aleksis.core.mixins import ( AdvancedEditView, SuccessNextMixin, ) - from aleksis.core.views import LoginView + from .forms import SeatFormSet, SeatingPlanCopyForm, SeatingPlanCreateForm, SeatingPlanForm from .models import Seat, SeatingPlan from .tables import SeatingPlanTable @@ -58,6 +58,14 @@ class SeatingPlanCreateView(PermissionRequiredMixin, SuccessNextMixin, AdvancedC def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs["request"] = self.request + initial = {} + if "room" in self.request.GET: + initial["room"] = self.request.GET["room"] + if "subject" in self.request.GET: + initial["subject"] = self.request.GET["subject"] + if "group" in self.request.GET: + initial["group"] = self.request.GET["group"] + kwargs["initial"] = initial return kwargs -- GitLab From e72d5a407d965f24acf55033491a3961c4a400eb Mon Sep 17 00:00:00 2001 From: Jonathan Weth <git@jonathanweth.de> Date: Mon, 11 Apr 2022 16:21:44 +0200 Subject: [PATCH 04/11] Fix migration dep --- .../stoelindeling/migrations/0004_alter_seatingplan_subject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aleksis/apps/stoelindeling/migrations/0004_alter_seatingplan_subject.py b/aleksis/apps/stoelindeling/migrations/0004_alter_seatingplan_subject.py index 9f0fb57..4182b55 100644 --- a/aleksis/apps/stoelindeling/migrations/0004_alter_seatingplan_subject.py +++ b/aleksis/apps/stoelindeling/migrations/0004_alter_seatingplan_subject.py @@ -7,7 +7,7 @@ import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ - ('chronos', '0011_alter_timeperiod_weekday'), + ('chronos', '0010_remove_subject_unique_name_per_site'), ('stoelindeling', '0003_seat_negative_x_y'), ] -- GitLab From a977b64bc655b4a1fbddcaeb808bbb87fcd87ee0 Mon Sep 17 00:00:00 2001 From: Jonathan Weth <git@jonathanweth.de> Date: Wed, 13 Apr 2022 16:03:40 +0200 Subject: [PATCH 05/11] Fix some permissions for use in Alsijil --- aleksis/apps/stoelindeling/rules.py | 17 ++++++++++++++--- aleksis/apps/stoelindeling/util/perms.py | 10 +++++++++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/aleksis/apps/stoelindeling/rules.py b/aleksis/apps/stoelindeling/rules.py index 9c73efe..93fb0cd 100644 --- a/aleksis/apps/stoelindeling/rules.py +++ b/aleksis/apps/stoelindeling/rules.py @@ -9,7 +9,7 @@ from aleksis.core.util.predicates import ( ) from .models import SeatingPlan -from .util.perms import is_plan_group_owner +from .util.perms import is_plan_child_group_owner, is_plan_group_owner # View seating plan list view_seatingplans_predicate = has_person & ( @@ -24,17 +24,28 @@ view_seatingplan_predicate = has_person & ( has_global_perm("stoelindeling.view_seatingplan") | has_object_perm("stoelindeling.view_seatingplan") | is_plan_group_owner + | is_plan_child_group_owner ) add_perm("stoelindeling.view_seatingplan_rule", view_seatingplan_predicate) +# +view_seatingplan_for_group_predicate = has_person & ( + has_global_perm("stoelindeling.view_seatingplan") | is_group_owner +) +add_perm("stoelindeling.view_seatingplan_for_group_rule", view_seatingplan_for_group_predicate) + # Add seating plan -create_seatingplan_predicate = view_seatingplans_predicate & ( +create_seatingplan_predicate = has_person & ( has_global_perm("stoelindeling.add_seatingplan") | is_group_owner | is_plan_group_owner ) add_perm("stoelindeling.create_seatingplan_rule", create_seatingplan_predicate) # Copy seating plan -copy_seatingplan_predicate = view_seatingplan_predicate & create_seatingplan_predicate +copy_seatingplan_predicate = view_seatingplan_for_group_predicate & create_seatingplan_predicate +add_perm("stoelindeling.copy_seatingplan_for_group_rule", copy_seatingplan_predicate) + +# Copy seating plan +copy_seatingplan_predicate = view_seatingplan_predicate add_perm("stoelindeling.copy_seatingplan_rule", copy_seatingplan_predicate) # Edit seating plan diff --git a/aleksis/apps/stoelindeling/util/perms.py b/aleksis/apps/stoelindeling/util/perms.py index 0876019..2b00e28 100644 --- a/aleksis/apps/stoelindeling/util/perms.py +++ b/aleksis/apps/stoelindeling/util/perms.py @@ -3,7 +3,7 @@ from django.db.models import Q from guardian.shortcuts import get_objects_for_user from rules import predicate -from aleksis.core.models import Group +from aleksis.core.models import Group, Person from ..models import SeatingPlan @@ -24,6 +24,14 @@ def is_plan_group_owner(user, seating_plan: SeatingPlan) -> bool: return user.person in seating_plan.group.owners.all() +@predicate +def is_plan_child_group_owner(user, seating_plan: SeatingPlan) -> bool: + """Predicate which checks if the user is an owner of the seating plan's child groups.""" + if not isinstance(seating_plan, SeatingPlan): + return False + return user.person in Person.objects.filter(owner_of__in=seating_plan.group.child_groups.all()) + + def get_allowed_seating_plans(user): """Get all seating plans the user is allowed to see.""" if not user.has_perm("stoelindeling.view_seatingplan"): -- GitLab From d423655fced1ce3ceed9d8d5aa2f315e0edbca63 Mon Sep 17 00:00:00 2001 From: Jonathan Weth <git@jonathanweth.de> Date: Wed, 13 Apr 2022 16:03:52 +0200 Subject: [PATCH 06/11] Reduce menu as there is just one menu item --- aleksis/apps/stoelindeling/menus.py | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/aleksis/apps/stoelindeling/menus.py b/aleksis/apps/stoelindeling/menus.py index 492015d..fc2ce01 100644 --- a/aleksis/apps/stoelindeling/menus.py +++ b/aleksis/apps/stoelindeling/menus.py @@ -4,26 +4,14 @@ MENUS = { "NAV_MENU_CORE": [ { "name": _("Seating plans"), - "url": "#", - "root": True, - "svg_icon": "mdi:seat-outline", + "url": "seating_plans", + "svg_icon": "mdi:view-list-outline", "validators": [ - "menu_generator.validators.is_authenticated", - "aleksis.core.util.core_helpers.has_person", + ( + "aleksis.core.util.predicates.permission_validator", + "stoelindeling.view_seatingplans_rule", + ), ], - "submenu": [ - { - "name": _("All seating plans"), - "url": "seating_plans", - "svg_icon": "mdi:view-list-outline", - "validators": [ - ( - "aleksis.core.util.predicates.permission_validator", - "stoelindeling.view_seatingplans_rule", - ), - ], - }, - ], - } + }, ] } -- GitLab From 5bae62cfe0c81cadcae12560524468b33267c02f Mon Sep 17 00:00:00 2001 From: magicfelix <felix@felix-zauberer.de> Date: Tue, 12 Apr 2022 14:43:33 +0200 Subject: [PATCH 07/11] Fix permission names --- aleksis/apps/stoelindeling/rules.py | 6 +++--- .../templates/stoelindeling/seating_plan/list.html | 4 ++-- .../templates/stoelindeling/seating_plan/view.html | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/aleksis/apps/stoelindeling/rules.py b/aleksis/apps/stoelindeling/rules.py index 1617bdf..a5346f5 100644 --- a/aleksis/apps/stoelindeling/rules.py +++ b/aleksis/apps/stoelindeling/rules.py @@ -28,10 +28,10 @@ view_seatingplan_predicate = has_person & ( add_perm("stoelindeling.view_seatingplan_rule", view_seatingplan_predicate) # Add seating plan -add_seatingplan_predicate = view_seatingplans_predicate & ( - has_global_perm("stoelindeling.add_seatingplan") | is_group_owner | is_plan_group_owner +create_seatingplan_predicate = view_seatingplans_predicate & has_global_perm( + "stoelindeling.add_seatingplan" ) -add_perm("stoelindeling.add_seatingplan_rule", add_seatingplan_predicate) +add_perm("stoelindeling.create_seatingplan_rule", create_seatingplan_predicate) # Copy seating plan copy_seatingplan_predicate = view_seatingplan_predicate & add_seatingplan_predicate diff --git a/aleksis/apps/stoelindeling/templates/stoelindeling/seating_plan/list.html b/aleksis/apps/stoelindeling/templates/stoelindeling/seating_plan/list.html index d7810ee..36ee743 100644 --- a/aleksis/apps/stoelindeling/templates/stoelindeling/seating_plan/list.html +++ b/aleksis/apps/stoelindeling/templates/stoelindeling/seating_plan/list.html @@ -9,8 +9,8 @@ {% block page_title %}{% blocktrans %}Seating plans{% endblocktrans %}{% endblock %} {% block content %} - {% has_perm "stoelindeling.create_seating_plan_rule" user as can_create_seating_plan %} - {% if can_create_seating_plan %} + {% has_perm "stoelindeling.create_seatingplan_rule" user as can_create_seatingplan %} + {% if can_create_seatingplan %} <a class="btn green waves-effect waves-light" href="{% url 'create_seating_plan' %}"> <i class="material-icons left iconify" data-icon="mdi:plus">add</i> {% trans "Create seating plan" %} diff --git a/aleksis/apps/stoelindeling/templates/stoelindeling/seating_plan/view.html b/aleksis/apps/stoelindeling/templates/stoelindeling/seating_plan/view.html index f0c8b9a..36fd6a5 100644 --- a/aleksis/apps/stoelindeling/templates/stoelindeling/seating_plan/view.html +++ b/aleksis/apps/stoelindeling/templates/stoelindeling/seating_plan/view.html @@ -18,8 +18,8 @@ {% endblock %} {% block content %} - {% has_perm "stoelindeling.edit_seating_plan_rule" user seating_plan as can_edit %} - {% has_perm "stoelindeling.delete_seating_plan_rule" user seating_plan as can_delete %} + {% has_perm "stoelindeling.edit_seatingplan_rule" user seating_plan as can_edit %} + {% has_perm "stoelindeling.delete_seatingplan_rule" user seating_plan as can_delete %} {% if can_edit %} <a class="btn waves-effect waves-light orange margin-bottom" href="{% url "edit_seating_plan" object.pk %}"> -- GitLab From 8fa12ebc103e79ed1ef42e20a7b7ecc86b0462f7 Mon Sep 17 00:00:00 2001 From: Jonathan Weth <git@jonathanweth.de> Date: Wed, 13 Apr 2022 16:03:40 +0200 Subject: [PATCH 08/11] Fix some permissions for use in Alsijil --- aleksis/apps/stoelindeling/rules.py | 19 +++++++++++++++---- aleksis/apps/stoelindeling/util/perms.py | 10 +++++++++- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/aleksis/apps/stoelindeling/rules.py b/aleksis/apps/stoelindeling/rules.py index a5346f5..93fb0cd 100644 --- a/aleksis/apps/stoelindeling/rules.py +++ b/aleksis/apps/stoelindeling/rules.py @@ -9,7 +9,7 @@ from aleksis.core.util.predicates import ( ) from .models import SeatingPlan -from .util.perms import is_plan_group_owner +from .util.perms import is_plan_child_group_owner, is_plan_group_owner # View seating plan list view_seatingplans_predicate = has_person & ( @@ -24,17 +24,28 @@ view_seatingplan_predicate = has_person & ( has_global_perm("stoelindeling.view_seatingplan") | has_object_perm("stoelindeling.view_seatingplan") | is_plan_group_owner + | is_plan_child_group_owner ) add_perm("stoelindeling.view_seatingplan_rule", view_seatingplan_predicate) +# +view_seatingplan_for_group_predicate = has_person & ( + has_global_perm("stoelindeling.view_seatingplan") | is_group_owner +) +add_perm("stoelindeling.view_seatingplan_for_group_rule", view_seatingplan_for_group_predicate) + # Add seating plan -create_seatingplan_predicate = view_seatingplans_predicate & has_global_perm( - "stoelindeling.add_seatingplan" +create_seatingplan_predicate = has_person & ( + has_global_perm("stoelindeling.add_seatingplan") | is_group_owner | is_plan_group_owner ) add_perm("stoelindeling.create_seatingplan_rule", create_seatingplan_predicate) # Copy seating plan -copy_seatingplan_predicate = view_seatingplan_predicate & add_seatingplan_predicate +copy_seatingplan_predicate = view_seatingplan_for_group_predicate & create_seatingplan_predicate +add_perm("stoelindeling.copy_seatingplan_for_group_rule", copy_seatingplan_predicate) + +# Copy seating plan +copy_seatingplan_predicate = view_seatingplan_predicate add_perm("stoelindeling.copy_seatingplan_rule", copy_seatingplan_predicate) # Edit seating plan diff --git a/aleksis/apps/stoelindeling/util/perms.py b/aleksis/apps/stoelindeling/util/perms.py index 0876019..2b00e28 100644 --- a/aleksis/apps/stoelindeling/util/perms.py +++ b/aleksis/apps/stoelindeling/util/perms.py @@ -3,7 +3,7 @@ from django.db.models import Q from guardian.shortcuts import get_objects_for_user from rules import predicate -from aleksis.core.models import Group +from aleksis.core.models import Group, Person from ..models import SeatingPlan @@ -24,6 +24,14 @@ def is_plan_group_owner(user, seating_plan: SeatingPlan) -> bool: return user.person in seating_plan.group.owners.all() +@predicate +def is_plan_child_group_owner(user, seating_plan: SeatingPlan) -> bool: + """Predicate which checks if the user is an owner of the seating plan's child groups.""" + if not isinstance(seating_plan, SeatingPlan): + return False + return user.person in Person.objects.filter(owner_of__in=seating_plan.group.child_groups.all()) + + def get_allowed_seating_plans(user): """Get all seating plans the user is allowed to see.""" if not user.has_perm("stoelindeling.view_seatingplan"): -- GitLab From ef6704856e6243754ca9a635cf303c12b9aeb283 Mon Sep 17 00:00:00 2001 From: Jonathan Weth <git@jonathanweth.de> Date: Wed, 13 Apr 2022 16:03:52 +0200 Subject: [PATCH 09/11] Reduce menu as there is just one menu item --- aleksis/apps/stoelindeling/menus.py | 26 +++++++------------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/aleksis/apps/stoelindeling/menus.py b/aleksis/apps/stoelindeling/menus.py index 492015d..fc2ce01 100644 --- a/aleksis/apps/stoelindeling/menus.py +++ b/aleksis/apps/stoelindeling/menus.py @@ -4,26 +4,14 @@ MENUS = { "NAV_MENU_CORE": [ { "name": _("Seating plans"), - "url": "#", - "root": True, - "svg_icon": "mdi:seat-outline", + "url": "seating_plans", + "svg_icon": "mdi:view-list-outline", "validators": [ - "menu_generator.validators.is_authenticated", - "aleksis.core.util.core_helpers.has_person", + ( + "aleksis.core.util.predicates.permission_validator", + "stoelindeling.view_seatingplans_rule", + ), ], - "submenu": [ - { - "name": _("All seating plans"), - "url": "seating_plans", - "svg_icon": "mdi:view-list-outline", - "validators": [ - ( - "aleksis.core.util.predicates.permission_validator", - "stoelindeling.view_seatingplans_rule", - ), - ], - }, - ], - } + }, ] } -- GitLab From 472eecc1356accd61d8e61d4378e4c1860d1e23f Mon Sep 17 00:00:00 2001 From: magicfelix <felix@felix-zauberer.de> Date: Wed, 13 Apr 2022 16:27:24 +0200 Subject: [PATCH 10/11] [CI] Publish package --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2f98ddb..fb6ad25 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -11,3 +11,5 @@ include: file: /ci/test/security.yml - project: "AlekSIS/official/AlekSIS" file: /ci/build/dist.yml + - project: "AlekSIS/official/AlekSIS" + file: /ci/publish/pypi.yml -- GitLab From 8dd17b560b3cd5539b82401d90707e07c6fca002 Mon Sep 17 00:00:00 2001 From: Jonathan Weth <git@jonathanweth.de> Date: Sun, 1 May 2022 18:21:56 +0200 Subject: [PATCH 11/11] Make code better readable and add docstrings --- .../apps/stoelindeling/model_extensions.py | 21 +++++++++++++------ aleksis/apps/stoelindeling/rules.py | 6 ++++-- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/aleksis/apps/stoelindeling/model_extensions.py b/aleksis/apps/stoelindeling/model_extensions.py index 558f186..c61ae15 100644 --- a/aleksis/apps/stoelindeling/model_extensions.py +++ b/aleksis/apps/stoelindeling/model_extensions.py @@ -1,3 +1,5 @@ +from typing import Union + from django.apps import apps from aleksis.apps.chronos.models import Room, Subject @@ -6,25 +8,30 @@ from aleksis.core.models import Group @Group.method -def get_seating_plan(self, room: Room, subject: Subject = None): +def get_seating_plan(self, room: Room, subject: Subject = None) -> Union[SeatingPlan, None]: """Find a seating plan for a group.""" qs = SeatingPlan.objects.filter(group=self, room=room) if subject: qs = qs.filter(subject=subject) if qs.exists(): return qs.first() - if subject: - qs = SeatingPlan.objects.filter(group=self, room=room) - if qs.exists(): - return qs.first() + + qs = SeatingPlan.objects.filter(group=self, room=room) + if qs.exists(): + return qs.first() + qs = SeatingPlan.objects.filter(group__child_groups=self, room=room) - return qs.first() + if qs.exists(): + return qs.first() + + return None if apps.is_installed("aleksis.apps.alsijil"): from aleksis.apps.alsijil.models import Event, ExtraLesson, LessonPeriod def seating_plan_lesson_period(self): + """Get the seating plan for a specific lesson period.""" if self.get_groups().count() == 1: return self.get_groups().all()[0].get_seating_plan(self.get_room(), self.get_subject()) return None @@ -32,6 +39,7 @@ if apps.is_installed("aleksis.apps.alsijil"): LessonPeriod.property_(seating_plan_lesson_period, name="seating_plan") def seating_plan_extra_lesson(self): + """Get the seating plan for an extra lesson.""" if self.groups.count() == 1: return self.groups.all()[0].get_seating_plan(self.room, self.subject) return None @@ -39,6 +47,7 @@ if apps.is_installed("aleksis.apps.alsijil"): ExtraLesson.property_(seating_plan_extra_lesson, name="seating_plan") def seating_plan_event(self): + """Get the seating plan for an event.""" if self.groups.count() == 1 and self.rooms.count() == 1: return self.groups.all()[0].get_seating_plan(self.rooms.all()[0]) return None diff --git a/aleksis/apps/stoelindeling/rules.py b/aleksis/apps/stoelindeling/rules.py index 93fb0cd..38dab4c 100644 --- a/aleksis/apps/stoelindeling/rules.py +++ b/aleksis/apps/stoelindeling/rules.py @@ -41,8 +41,10 @@ create_seatingplan_predicate = has_person & ( add_perm("stoelindeling.create_seatingplan_rule", create_seatingplan_predicate) # Copy seating plan -copy_seatingplan_predicate = view_seatingplan_for_group_predicate & create_seatingplan_predicate -add_perm("stoelindeling.copy_seatingplan_for_group_rule", copy_seatingplan_predicate) +copy_seatingplan_for_group_predicate = ( + view_seatingplan_for_group_predicate & create_seatingplan_predicate +) +add_perm("stoelindeling.copy_seatingplan_for_group_rule", copy_seatingplan_for_group_predicate) # Copy seating plan copy_seatingplan_predicate = view_seatingplan_predicate -- GitLab