diff --git a/aleksis/apps/alsijil/forms.py b/aleksis/apps/alsijil/forms.py index 9624e5208e614319527a95b33feae1fea4e84b6b..5d0c161296a1258e6fbb65fd4192dfcc4bc84cc4 100644 --- a/aleksis/apps/alsijil/forms.py +++ b/aleksis/apps/alsijil/forms.py @@ -15,7 +15,7 @@ from aleksis.apps.chronos.models import TimePeriod from aleksis.core.models import Group, Person from aleksis.core.util.predicates import check_global_permission -from .models import ExcuseType, ExtraMark, LessonDocumentation, PersonalNote +from .models import ClassRole, ExcuseType, ExtraMark, LessonDocumentation, PersonalNote class LessonDocumentationForm(forms.ModelForm): @@ -162,3 +162,11 @@ class ExcuseTypeForm(forms.ModelForm): class Meta: model = ExcuseType fields = ["short_name", "name"] + + +class ClassRoleForm(forms.ModelForm): + layout = Layout("name", "icon", "colour") + + class Meta: + model = ClassRole + fields = ["name", "icon", "colour"] diff --git a/aleksis/apps/alsijil/menus.py b/aleksis/apps/alsijil/menus.py index f90052a765147b8555840d84f24203b12f09b248..a0ac455ca38fe9b566a846e0dd726d7c8e4cd000 100644 --- a/aleksis/apps/alsijil/menus.py +++ b/aleksis/apps/alsijil/menus.py @@ -89,6 +89,17 @@ MENUS = { ), ], }, + { + "name": _("Manage class roles"), + "url": "class_roles", + "icon": "support_agent", + "validators": [ + ( + "aleksis.core.util.predicates.permission_validator", + "alsijil.view_classroles", + ), + ], + }, ], } ] diff --git a/aleksis/apps/alsijil/preferences.py b/aleksis/apps/alsijil/preferences.py index 4ced04f41ba56db628b4dc4438242a81bc36f5eb..055d5c9971247bac3dd99d7b86350950c115a1e6 100644 --- a/aleksis/apps/alsijil/preferences.py +++ b/aleksis/apps/alsijil/preferences.py @@ -66,3 +66,11 @@ class AllowEntriesInHolidays(BooleanPreference): name = "allow_entries_in_holidays" default = False verbose_name = _("Allow teachers to add data for lessons in holidays") + + +@site_preferences_registry.register +class ActivateClassRoles(BooleanPreference): + section = alsijil + name = "activate_class_roles" + default = True + verbose_name = _("Activate support for creating and assigning class roles") diff --git a/aleksis/apps/alsijil/rules.py b/aleksis/apps/alsijil/rules.py index f553c2773ef6e96aab87b585d01c389399cecd50..0d74385a39710ea6f3589ea722646866fbb4c53a 100644 --- a/aleksis/apps/alsijil/rules.py +++ b/aleksis/apps/alsijil/rules.py @@ -218,3 +218,26 @@ add_perm("alsijil.edit_extramark", edit_extramark_predicate) # Delete extra mark delete_extramark_predicate = view_extramarks_predicate & has_global_perm("alsijil.delete_extramark") add_perm("alsijil.delete_extramark", delete_extramark_predicate) + + +# View class role list +view_class_roles_predicate = ( + has_person + & is_site_preference_set("alsijil", "activate_class_roles") + & has_global_perm("alsijil.view_classrole") +) +add_perm("alsijil.view_classroles", view_class_roles_predicate) + +# Add class role +add_class_role_predicate = view_class_roles_predicate & has_global_perm("alsijil.add_classrole") +add_perm("alsijil.add_classrole", add_class_role_predicate) + +# Edit class role +edit_class_role_predicate = view_class_roles_predicate & has_global_perm("alsijil.change_classrole") +add_perm("alsijil.edit_classrole", edit_class_role_predicate) + +# Delete class role +delete_class_role_predicate = view_class_roles_predicate & has_global_perm( + "alsijil.delete_classrole" +) +add_perm("alsijil.delete_classrole", delete_class_role_predicate) diff --git a/aleksis/apps/alsijil/tables.py b/aleksis/apps/alsijil/tables.py index b9a8e68404d6b2672dfbb37d2433e55cad08cf08..0cfdc1ab2009792fb99c2e3ab8c03a289f2489d6 100644 --- a/aleksis/apps/alsijil/tables.py +++ b/aleksis/apps/alsijil/tables.py @@ -1,3 +1,4 @@ +from django.template.loader import render_to_string from django.utils.translation import gettext_lazy as _ import django_tables2 as tables @@ -48,3 +49,34 @@ class ExcuseTypeTable(tables.Table): self.columns.hide("edit") if not request.user.has_perm("alsijil.delete_excusetype"): self.columns.hide("delete") + + +class ClassRoleTable(tables.Table): + class Meta: + attrs = {"class": "highlight"} + + name = tables.LinkColumn("edit_excuse_type", args=[A("id")]) + edit = tables.LinkColumn( + "edit_class_role", + args=[A("id")], + text=_("Edit"), + attrs={"a": {"class": "btn-flat waves-effect waves-orange orange-text"}}, + ) + delete = tables.LinkColumn( + "delete_class_role", + args=[A("id")], + text=_("Delete"), + attrs={"a": {"class": "btn-flat waves-effect waves-red red-text"}}, + ) + + def render_name(self, value, record): + colour = record.colour or "black" + icon_name = record.icon or "support_agent" + context = dict(content=value, icon=icon_name, classes=f"{colour} white-text") + return render_to_string("components/materialize-chips.html", context) + + def before_render(self, request): + if not request.user.has_perm("alsijil.edit_classrole"): + self.columns.hide("edit") + if not request.user.has_perm("alsijil.delete_classrole"): + self.columns.hide("delete") diff --git a/aleksis/apps/alsijil/templates/alsijil/class_role/create.html b/aleksis/apps/alsijil/templates/alsijil/class_role/create.html new file mode 100644 index 0000000000000000000000000000000000000000..096d34ed94ec488dfc6dbb6df4c7a2d4cf9652f5 --- /dev/null +++ b/aleksis/apps/alsijil/templates/alsijil/class_role/create.html @@ -0,0 +1,15 @@ +{# -*- engine:django -*- #} + +{% extends "core/base.html" %} +{% load material_form i18n %} + +{% block browser_title %}{% blocktrans %}Create class role{% endblocktrans %}{% endblock %} +{% block page_title %}{% blocktrans %}Create class role{% endblocktrans %}{% endblock %} + +{% block content %} + <form method="post"> + {% csrf_token %} + {% form form=form %}{% endform %} + {% include "core/partials/save_button.html" %} + </form> +{% endblock %} diff --git a/aleksis/apps/alsijil/templates/alsijil/class_role/edit.html b/aleksis/apps/alsijil/templates/alsijil/class_role/edit.html new file mode 100644 index 0000000000000000000000000000000000000000..b01aa3dc95eda1ba07556ccbf7e14aa6ed253de0 --- /dev/null +++ b/aleksis/apps/alsijil/templates/alsijil/class_role/edit.html @@ -0,0 +1,17 @@ +{# -*- engine:django -*- #} + +{% extends "core/base.html" %} +{% load material_form i18n %} + +{% block browser_title %}{% blocktrans %}Edit class role{% endblocktrans %}{% endblock %} +{% block page_title %}{% blocktrans %}Edit class role{% endblocktrans %}{% endblock %} + +{% block content %} + + <form method="post"> + {% csrf_token %} + {% form form=form %}{% endform %} + {% include "core/partials/save_button.html" %} + </form> + +{% endblock %} diff --git a/aleksis/apps/alsijil/templates/alsijil/class_role/list.html b/aleksis/apps/alsijil/templates/alsijil/class_role/list.html new file mode 100644 index 0000000000000000000000000000000000000000..146b6501355e4797adcd3050647eae83d3b699f0 --- /dev/null +++ b/aleksis/apps/alsijil/templates/alsijil/class_role/list.html @@ -0,0 +1,22 @@ +{# -*- engine:django -*- #} + +{% extends "core/base.html" %} + +{% load i18n rules %} +{% load render_table from django_tables2 %} + +{% block browser_title %}{% blocktrans %}Class roles{% endblocktrans %}{% endblock %} +{% block page_title %}{% blocktrans %}Class roles{% endblocktrans %}{% endblock %} + +{% block content %} + {% has_perm "alsijil.add_classrole" user as add_class_role %} + {% if add_class_role %} + <a class="btn green waves-effect waves-light" href="{% url 'create_class_role' %}"> + <i class="material-icons left">add</i> + {% trans "Create class role" %} + </a> + {% endif %} + + {% render_table table %} +{% endblock %} + diff --git a/aleksis/apps/alsijil/templates/alsijil/class_role/warning.html b/aleksis/apps/alsijil/templates/alsijil/class_role/warning.html new file mode 100644 index 0000000000000000000000000000000000000000..d90d2e8205b1c91c18e74e02654fde3daebc4971 --- /dev/null +++ b/aleksis/apps/alsijil/templates/alsijil/class_role/warning.html @@ -0,0 +1,10 @@ +{% load i18n %} +<div class="alert warning"> + <p> + <i class="material-icons left">warning</i> + {% blocktrans %} + This function should only be used to define alternatives to the default excuse which also will be counted extra. + Don't use this to create a default excuse or if you don't divide between different types of excuse. + {% endblocktrans %} + </p> +</div> diff --git a/aleksis/apps/alsijil/urls.py b/aleksis/apps/alsijil/urls.py index 673154f496b081b194b95cdde9aeef8464f05519..752f5b8e9598016be0bf835c4f5ca20190af83fb 100644 --- a/aleksis/apps/alsijil/urls.py +++ b/aleksis/apps/alsijil/urls.py @@ -49,4 +49,12 @@ urlpatterns = [ views.ExcuseTypeDeleteView.as_view(), name="delete_excuse_type", ), + path("class_roles/", views.ClassRoleListView.as_view(), name="class_roles"), + path("class_roles/create/", views.ClassRoleCreateView.as_view(), name="create_class_role"), + path("class_roles/<int:pk>/edit/", views.ClassRoleEditView.as_view(), name="edit_class_role",), + path( + "class_roles/<int:pk>/delete/", + views.ClassRoleDeleteView.as_view(), + name="delete_class_role", + ), ] diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py index c5616940d55cb3bfb577d9ae5a2b6bd7c30bb4bb..8803a8851ac27074753083d52230472dfd069982 100644 --- a/aleksis/apps/alsijil/views.py +++ b/aleksis/apps/alsijil/views.py @@ -27,6 +27,7 @@ from aleksis.core.util import messages from aleksis.core.util.core_helpers import get_site_preferences, objectgetter_optional from .forms import ( + ClassRoleForm, ExcuseTypeForm, ExtraMarkForm, LessonDocumentationForm, @@ -34,8 +35,8 @@ from .forms import ( RegisterAbsenceForm, SelectForm, ) -from .models import ExcuseType, ExtraMark, LessonDocumentation, PersonalNote -from .tables import ExcuseTypeTable, ExtraMarkTable +from .models import ClassRole, ExcuseType, ExtraMark, LessonDocumentation, PersonalNote +from .tables import ClassRoleTable, ExcuseTypeTable, ExtraMarkTable from .util.alsijil_helpers import get_lesson_period_by_pk, get_timetable_instance_by_pk @@ -849,3 +850,47 @@ class ExcuseTypeDeleteView(PermissionRequiredMixin, RevisionMixin, AdvancedDelet template_name = "core/pages/delete.html" success_url = reverse_lazy("excuse_types") success_message = _("The excuse type has been deleted.") + + +class ClassRoleListView(PermissionRequiredMixin, SingleTableView): + """Table of all class roles.""" + + model = ClassRole + table_class = ClassRoleTable + permission_required = "alsijil.view_classroles" + template_name = "alsijil/class_role/list.html" + + +@method_decorator(never_cache, name="dispatch") +class ClassRoleCreateView(PermissionRequiredMixin, AdvancedCreateView): + """Create view for class roles.""" + + model = ClassRole + form_class = ClassRoleForm + permission_required = "alsijil.add_classrole" + template_name = "alsijil/class_role/create.html" + success_url = reverse_lazy("class_roles") + success_message = _("The class role has been created.") + + +@method_decorator(never_cache, name="dispatch") +class ClassRoleEditView(PermissionRequiredMixin, AdvancedEditView): + """Edit view for class roles.""" + + model = ClassRole + form_class = ClassRoleForm + permission_required = "alsijil.edit_classrole" + template_name = "alsijil/class_role/edit.html" + success_url = reverse_lazy("class_roles") + success_message = _("The class role has been saved.") + + +@method_decorator(never_cache, "dispatch") +class ClassRoleDeleteView(PermissionRequiredMixin, RevisionMixin, AdvancedDeleteView): + """Delete view for class roles.""" + + model = ClassRole + permission_required = "alsijil.delete_classrole" + template_name = "core/pages/delete.html" + success_url = reverse_lazy("class_roles") + success_message = _("The class role has been deleted.")