From 72775aa9617d0dc018989602c0ad7bccff8f1271 Mon Sep 17 00:00:00 2001 From: Jonathan Weth <git@jonathanweth.de> Date: Sun, 5 Jun 2022 17:58:43 +0200 Subject: [PATCH] Add views for managing instructions --- aleksis/apps/alsijil/forms.py | 9 ++++ ...014_instruction.py => 0017_instruction.py} | 2 +- aleksis/apps/alsijil/models.py | 1 + aleksis/apps/alsijil/rules.py | 26 +++++++++- .../templates/alsijil/instruction/create.html | 15 ++++++ .../templates/alsijil/instruction/edit.html | 17 +++++++ .../templates/alsijil/instruction/list.html | 32 +++++++++++- aleksis/apps/alsijil/urls.py | 9 ++++ aleksis/apps/alsijil/util/predicates.py | 22 +++++++- aleksis/apps/alsijil/views.py | 51 ++++++++++++++++++- 10 files changed, 179 insertions(+), 5 deletions(-) rename aleksis/apps/alsijil/migrations/{0014_instruction.py => 0017_instruction.py} (99%) create mode 100644 aleksis/apps/alsijil/templates/alsijil/instruction/create.html create mode 100644 aleksis/apps/alsijil/templates/alsijil/instruction/edit.html diff --git a/aleksis/apps/alsijil/forms.py b/aleksis/apps/alsijil/forms.py index 6f4e65b20..e325586cb 100644 --- a/aleksis/apps/alsijil/forms.py +++ b/aleksis/apps/alsijil/forms.py @@ -31,6 +31,7 @@ from .models import ( ExtraMark, GroupRole, GroupRoleAssignment, + Instruction, LessonDocumentation, PersonalNote, ) @@ -383,3 +384,11 @@ class RegisterObjectActionForm(ListActionForm): """Action form for managing register objects for use with ``RegisterObjectTable``.""" actions = [send_request_to_check_entry] + + +class InstructionForm(forms.ModelForm): + layout = Layout("name", "icon", "pdf_file", "groups") + + class Meta: + model = Instruction + fields = ["name", "icon", "pdf_file", "groups"] diff --git a/aleksis/apps/alsijil/migrations/0014_instruction.py b/aleksis/apps/alsijil/migrations/0017_instruction.py similarity index 99% rename from aleksis/apps/alsijil/migrations/0014_instruction.py rename to aleksis/apps/alsijil/migrations/0017_instruction.py index 48525627f..11e200873 100644 --- a/aleksis/apps/alsijil/migrations/0014_instruction.py +++ b/aleksis/apps/alsijil/migrations/0017_instruction.py @@ -10,7 +10,7 @@ class Migration(migrations.Migration): dependencies = [ ('core', '0019_fix_uniqueness_per_site'), ('sites', '0002_alter_domain_unique'), - ('alsijil', '0013_fix_uniqueness_per_site'), + ('alsijil', '0016_add_not_counted_excuse_types'), ] operations = [ diff --git a/aleksis/apps/alsijil/models.py b/aleksis/apps/alsijil/models.py index 42189856b..90c2868ba 100644 --- a/aleksis/apps/alsijil/models.py +++ b/aleksis/apps/alsijil/models.py @@ -525,6 +525,7 @@ class Instruction(SchoolTermRelatedExtensibleModel): help_text=_( "The instruction will be shown for the members and owners of the selected groups." ), + related_name="instructions", ) def __str__(self): diff --git a/aleksis/apps/alsijil/rules.py b/aleksis/apps/alsijil/rules.py index 621f3e3b2..aa80713bd 100644 --- a/aleksis/apps/alsijil/rules.py +++ b/aleksis/apps/alsijil/rules.py @@ -11,12 +11,14 @@ from aleksis.core.util.predicates import ( ) from .util.predicates import ( + has_any_instruction, has_lesson_group_object_perm, has_person_group_object_perm, has_personal_note_group_perm, is_group_member, is_group_owner, is_group_role_assignment_group_owner, + is_instruction_for_person, is_lesson_original_teacher, is_lesson_parent_group_owner, is_lesson_participant, @@ -311,5 +313,27 @@ view_register_objects_list_predicate = has_person & ( ) add_perm("alsijil.view_register_objects_list_rule", view_register_objects_list_predicate) -view_instructions_predicate = has_person +view_instructions_predicate = has_person & ( + has_global_perm("alsijil.view_instruction") | has_any_instruction +) add_perm("alsijil.view_instructions_rule", view_instructions_predicate) + +view_instruction_predicate = has_person & ( + has_global_perm("alsijil.view_instruction") + | is_instruction_for_person + | has_object_perm("alsijil.view_instruction") +) +add_perm("alsijil.view_instruction_rule", view_instruction_predicate) + +add_instruction_predicate = view_instructions_predicate & has_global_perm("alsijil.add_instruction") +add_perm("alsijil.add_instruction_rule", add_instruction_predicate) + +edit_instruction_predicate = view_instructions_predicate & ( + has_global_perm("alsijil.change_instruction") | has_object_perm("alsijil.change_instruction") +) +add_perm("alsijil.edit_instruction_rule", edit_instruction_predicate) + +delete_instruction_predicate = view_instructions_predicate & ( + has_global_perm("alsijil.delete_instruction") | has_object_perm("alsijil.delete_instruction") +) +add_perm("alsijil.delete_instruction_rule", delete_instruction_predicate) diff --git a/aleksis/apps/alsijil/templates/alsijil/instruction/create.html b/aleksis/apps/alsijil/templates/alsijil/instruction/create.html new file mode 100644 index 000000000..42761a813 --- /dev/null +++ b/aleksis/apps/alsijil/templates/alsijil/instruction/create.html @@ -0,0 +1,15 @@ +{# -*- engine:django -*- #} + +{% extends "core/base.html" %} +{% load material_form i18n %} + +{% block browser_title %}{% blocktrans %}Create instruction{% endblocktrans %}{% endblock %} +{% block page_title %}{% blocktrans %}Create instruction{% endblocktrans %}{% endblock %} + +{% block content %} + <form method="post" enctype="multipart/form-data"> + {% csrf_token %} + {% form form=form %}{% endform %} + {% include "core/partials/save_button.html" %} + </form> +{% endblock %} diff --git a/aleksis/apps/alsijil/templates/alsijil/instruction/edit.html b/aleksis/apps/alsijil/templates/alsijil/instruction/edit.html new file mode 100644 index 000000000..b9732e4c4 --- /dev/null +++ b/aleksis/apps/alsijil/templates/alsijil/instruction/edit.html @@ -0,0 +1,17 @@ +{# -*- engine:django -*- #} + +{% extends "core/base.html" %} +{% load material_form i18n %} + +{% block browser_title %}{% blocktrans %}Edit instruction{% endblocktrans %}{% endblock %} +{% block page_title %}{% blocktrans %}Edit instruction{% endblocktrans %}{% endblock %} + +{% block content %} + <form method="post" enctype="multipart/form-data"> + {% csrf_token %} + {% form form=form %}{% endform %} + {% include "core/partials/save_button.html" %} + </form> + + {{ form.media.js }} +{% endblock %} diff --git a/aleksis/apps/alsijil/templates/alsijil/instruction/list.html b/aleksis/apps/alsijil/templates/alsijil/instruction/list.html index da3c384b3..02320901a 100644 --- a/aleksis/apps/alsijil/templates/alsijil/instruction/list.html +++ b/aleksis/apps/alsijil/templates/alsijil/instruction/list.html @@ -2,21 +2,51 @@ {% extends "core/base.html" %} -{% load i18n %} +{% load i18n rules %} {% block browser_title %}{% blocktrans %}Instructions{% endblocktrans %}{% endblock %} {% block page_title %}{% blocktrans %}Instructions{% endblocktrans %}{% endblock %} {% block content %} + {% has_perm "alsijil.add_instruction_rule" user as can_add %} + {% if can_add %} + <a class="btn green waves-effect waves-light" href="{% url 'create_instruction' %}"> + <i class="material-icons left">add</i> + {% trans "Create instruction" %} + </a> + {% endif %} + <ul class="collection"> {% for instruction in instruction_list %} <li class="collection-item avatar"> <i class="material-icons materialize-circle primary-color">{{ instruction.icon|default:"rule" }}</i> <span class="title"> {{ instruction.name }}</span> + <div class="right"> + {% has_perm "alsijil.edit_instruction_rule" user as can_edit %} + {% has_perm "alsijil.delete_instruction_rule" user as can_delete %} + {% if can_edit %} + <a class="btn-flat waves-effect waves-orange orange-text" + href="{% url "edit_instruction" instruction.pk %}"> + <i class="material-icons left">edit</i> + {% trans "Edit" %} + </a> + {% endif %} + {% if can_delete %} + <a class="btn-flat waves-effect waves-red red-text" href="{% url "delete_instruction" instruction.pk %}"> + <i class="material-icons left">delete</i> + {% trans "Delete" %} + </a> + {% endif %} + <a class="btn-flat waves-effect waves-green right" href="{{ instruction.pdf_file.url }}" target="_blank"> <i class="material-icons left">picture_as_pdf</i> {% trans "Show PDF file with instruction" %} </a> + </div> + </li> + {% empty %} + <li class="collection-item"> + {% trans "No instructions available." %} </li> {% endfor %} </ul> diff --git a/aleksis/apps/alsijil/urls.py b/aleksis/apps/alsijil/urls.py index f7551eb8e..19f92a235 100644 --- a/aleksis/apps/alsijil/urls.py +++ b/aleksis/apps/alsijil/urls.py @@ -129,4 +129,13 @@ urlpatterns = [ ), path("all/", views.AllRegisterObjectsView.as_view(), name="all_register_objects"), path("instructions/", views.InstructionsListView.as_view(), name="instructions"), + path("instructions/create/", views.InstructionCreateView.as_view(), name="create_instruction"), + path( + "instructions/<int:pk>/edit/", views.InstructionEditView.as_view(), name="edit_instruction" + ), + path( + "instructions/<int:pk>/delete/", + views.InstructionDeleteView.as_view(), + name="delete_instruction", + ), ] diff --git a/aleksis/apps/alsijil/util/predicates.py b/aleksis/apps/alsijil/util/predicates.py index 1759a5446..377779d30 100644 --- a/aleksis/apps/alsijil/util/predicates.py +++ b/aleksis/apps/alsijil/util/predicates.py @@ -1,6 +1,7 @@ from typing import Any, Union from django.contrib.auth.models import User +from django.db.models import Q from rules import predicate @@ -8,7 +9,7 @@ from aleksis.apps.chronos.models import Event, ExtraLesson, LessonPeriod from aleksis.core.models import Group, Person from aleksis.core.util.predicates import check_object_permission -from ..models import PersonalNote +from ..models import Instruction, PersonalNote @predicate @@ -281,3 +282,22 @@ def is_group_role_assignment_group_owner(user: User, obj: Union[Group, Person]) def is_owner_of_any_group(user: User, obj): """Predicate which checks if the person is group owner of any group.""" return Group.objects.filter(owners=user.person).exists() + + +@predicate +def has_any_instruction(user: User, obj): + """Predicate which checks if the user has any instruction.""" + return Instruction.objects.filter( + Q(groups__members=user.person) | Q(groups__owners=user.person) + ).exists() + + +@predicate +def is_instruction_for_person(user: User, obj: Instruction): + """Predicate which checks if the instruction is for the person.""" + return ( + user + in Person.objects.filter( + Q(member_of__instructions=obj) | Q(owner_of__instructions=obj) + ).exists() + ) diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py index 17b6e2dee..f05b90fb4 100644 --- a/aleksis/apps/alsijil/views.py +++ b/aleksis/apps/alsijil/views.py @@ -51,6 +51,7 @@ from .forms import ( FilterRegisterObjectForm, GroupRoleAssignmentEditForm, GroupRoleForm, + InstructionForm, LessonDocumentationForm, PersonalNoteFormSet, PersonOverviewForm, @@ -1405,8 +1406,56 @@ class AllRegisterObjectsView(PermissionRequiredMixin, View): class InstructionsListView(PermissionRequiredMixin, ListView): - """Table of all excuse types.""" + """Table of all instructions.""" model = Instruction permission_required = "alsijil.view_instructions_rule" template_name = "alsijil/instruction/list.html" + + def get_queryset(self): + if self.request.user.has_perm("alsijil.view_instruction"): + return super().get_queryset() + return ( + super() + .get_queryset() + .filter( + Q(groups__members=self.request.user.person) + | Q(groups__owners=self.request.user.person) + ) + .distinct() + ) + + +@method_decorator(never_cache, name="dispatch") +class InstructionCreateView(PermissionRequiredMixin, AdvancedCreateView): + """Create view for instructions.""" + + model = Instruction + form_class = InstructionForm + permission_required = "alsijil.add_instruction_rule" + template_name = "alsijil/instruction/create.html" + success_url = reverse_lazy("instructions") + success_message = _("The instruction has been created.") + + +@method_decorator(never_cache, name="dispatch") +class InstructionEditView(PermissionRequiredMixin, AdvancedEditView): + """Edit view for instructions.""" + + model = Instruction + form_class = InstructionForm + permission_required = "alsijil.edit_instruction_rule" + template_name = "alsijil/instruction/edit.html" + success_url = reverse_lazy("instructions") + success_message = _("The instruction has been saved.") + + +@method_decorator(never_cache, "dispatch") +class InstructionDeleteView(PermissionRequiredMixin, RevisionMixin, AdvancedDeleteView): + """Delete view for instructions.""" + + model = Instruction + permission_required = "alsijil.delete_instruction_rule" + template_name = "core/pages/delete.html" + success_url = reverse_lazy("instructions") + success_message = _("The instruction has been deleted.") -- GitLab