From 7936ea14df2077a897342891d40f72f2d332a435 Mon Sep 17 00:00:00 2001 From: Jonathan Weth <git@jonathanweth.de> Date: Wed, 10 Mar 2021 11:38:13 +0100 Subject: [PATCH] Add support for sending notifications in "All lessons" via actions --- aleksis/apps/alsijil/actions.py | 56 +++++++++++++++++++ aleksis/apps/alsijil/forms.py | 6 ++ aleksis/apps/alsijil/tables.py | 20 +++++++ .../alsijil/class_register/all_objects.html | 49 ++++++++++++---- aleksis/apps/alsijil/util/alsijil_helpers.py | 2 + aleksis/apps/alsijil/views.py | 31 ++++++++-- 6 files changed, 149 insertions(+), 15 deletions(-) create mode 100644 aleksis/apps/alsijil/actions.py diff --git a/aleksis/apps/alsijil/actions.py b/aleksis/apps/alsijil/actions.py new file mode 100644 index 000000000..cf276f386 --- /dev/null +++ b/aleksis/apps/alsijil/actions.py @@ -0,0 +1,56 @@ +from typing import Sequence + +from django.contrib import messages +from django.contrib.humanize.templatetags.humanize import apnumber +from django.http import HttpRequest +from django.urls import reverse +from django.utils.translation import gettext_lazy as _ + +from aleksis.core.models import Notification + + +def send_request_to_check_entry(modeladmin, request: HttpRequest, selected_items: Sequence[dict]): + """Send notifications to the teachers of the selected register objects. + + Action for use with ``RegisterObjectTable`` and ``RegisterObjectActionForm``. + """ + # Group class register entries by teachers so each teacher gets just one notification + grouped_by_teachers = {} + for entry in selected_items: + teachers = entry["register_object"].get_teachers().all() + for teacher in teachers: + grouped_by_teachers.setdefault(teacher, []) + grouped_by_teachers[teacher].append(entry) + + for teacher, items in grouped_by_teachers.items(): + title = _( + f"{request.user.person.addressing_name} wants you to check some class register entries." + ) + msg = _("Please check if the following class register entries are complete and correct:\n") + + # Add one line for each entry to check + for entry in items: + reg_object = entry["register_object"] + date = entry["date"] + msg += f"- {reg_object} ({date})\n" + + Notification.objects.create( + title=title, + description=msg, + sender=request.user.person.addressing_name, + recipient=teacher, + link=request.build_absolute_uri(reverse("overview_me")), + ) + + count_teachers = len(grouped_by_teachers.keys()) + count_items = len(list) + messages.success( + request, + _( + f"We have successfully sent notifications to " + f"{apnumber(count_teachers)} persons for {apnumber(count_items)} lessons." + ), + ) + + +send_request_to_check_entry.short_description = _("Notify teacher to check data") diff --git a/aleksis/apps/alsijil/forms.py b/aleksis/apps/alsijil/forms.py index 0d741f56a..1f912fc2f 100644 --- a/aleksis/apps/alsijil/forms.py +++ b/aleksis/apps/alsijil/forms.py @@ -14,10 +14,12 @@ from material import Fieldset, Layout, Row from aleksis.apps.chronos.managers import TimetableType from aleksis.apps.chronos.models import Subject, TimePeriod +from aleksis.core.forms import ListActionForm from aleksis.core.models import Group, Person, SchoolTerm from aleksis.core.util.core_helpers import get_site_preferences from aleksis.core.util.predicates import check_global_permission +from .actions import send_request_to_check_entry from .models import ( ExcuseType, ExtraMark, @@ -312,3 +314,7 @@ class FilterRegisterObjectForm(forms.Form): Q(lessons__groups__in=groups) | Q(extra_lessons__groups__in=groups) ).distinct() self.fields["subject"].queryset = subject_qs + + +class RegisterObjectActionForm(ListActionForm): + actions = [send_request_to_check_entry] diff --git a/aleksis/apps/alsijil/tables.py b/aleksis/apps/alsijil/tables.py index d78f899ba..c7d995c43 100644 --- a/aleksis/apps/alsijil/tables.py +++ b/aleksis/apps/alsijil/tables.py @@ -4,6 +4,8 @@ from django.utils.translation import gettext_lazy as _ import django_tables2 as tables from django_tables2.utils import A +from aleksis.core.tables import SelectColumn + class ExtraMarkTable(tables.Table): class Meta: @@ -81,6 +83,12 @@ class GroupRoleTable(tables.Table): class RegisterObjectTable(tables.Table): + """Table to show all register objects in an overview. + + .. warning:: + Works only with ``generate_list_of_all_register_objects``. + """ + class Meta: attrs = {"class": "highlight responsive-table"} @@ -103,3 +111,15 @@ class RegisterObjectTable(tables.Table): register_object=value, ), ) + + +class RegisterObjectSelectTable(RegisterObjectTable): + """Table to show all register objects with multi-select support. + + More information at ``RegisterObjectTable`` + """ + + selected = SelectColumn() + + class Meta(RegisterObjectTable.Meta): + sequence = ("selected", "...") diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/all_objects.html b/aleksis/apps/alsijil/templates/alsijil/class_register/all_objects.html index 7fd891a47..6a22b8a91 100644 --- a/aleksis/apps/alsijil/templates/alsijil/class_register/all_objects.html +++ b/aleksis/apps/alsijil/templates/alsijil/class_register/all_objects.html @@ -14,17 +14,46 @@ {% endblock %} {% block content %} - <h5>{% trans "Lesson filter" %}</h5> - <form action="" method="get"> - {% form form=filter_form %}{% endform %} - <button type="submit" class="btn waves-effect waves-light"> - <i class="material-icons left">refresh</i> - {% trans "Update filters" %} - </button> - </form> - <h5>{% trans "Lesson table" %}</h5> + <div class="card"> + <div class="card-content"> + <div class="card-title">{% trans "Lesson filter" %}</div> + <form action="" method="get"> + {% form form=filter_form %}{% endform %} + <button type="submit" class="btn waves-effect waves-light"> + <i class="material-icons left">refresh</i> + {% trans "Update filters" %} + </button> + </form> + </div> + </div> + {% if table %} - {% render_table table %} + <div class="card"> + <div class="card-content"> + <form action="" method="post"> + {% csrf_token %} + <div class="row"> + <div class="col s12 m4 l4 xl6"> + <div class="card-title">{% trans "Lesson table" %}</div> + </div> + <div class="col s12 m8 l8 xl6"> + <div class="col s12 m8"> + {% form form=action_form %}{% endform %}</div> + <div class="col s12 m4"> + + <button type="submit" class="btn waves-effect waves-primary"> + {% trans "Execute" %} + <i class="material-icons right">send</i> + </button> + </div> + </div> + </div> + {% render_table table %} + </form> + </div> + </div> {% endif %} + + <script src="{% static "js/multi_select.js" %}"></script> {% endblock %} \ No newline at end of file diff --git a/aleksis/apps/alsijil/util/alsijil_helpers.py b/aleksis/apps/alsijil/util/alsijil_helpers.py index d708589e5..8da8711e1 100644 --- a/aleksis/apps/alsijil/util/alsijil_helpers.py +++ b/aleksis/apps/alsijil/util/alsijil_helpers.py @@ -255,6 +255,7 @@ def generate_list_of_all_register_objects(filter_dict: Dict[str, Any]) -> List[d # Build table entry entry = { + "pk": f"{lesson_period.pk}_{week.year}_{week.week}", "week": week, "has_documentation": has_documentation, "substitution": sub, @@ -306,6 +307,7 @@ def generate_list_of_all_register_objects(filter_dict: Dict[str, Any]) -> List[d # Build table entry entry = { + "pk": str(register_object.pk), "has_documentation": has_documentation, "register_object": register_object, "date": day, diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py index bf4261ddb..99910ee78 100644 --- a/aleksis/apps/alsijil/views.py +++ b/aleksis/apps/alsijil/views.py @@ -49,6 +49,7 @@ from .forms import ( LessonDocumentationForm, PersonalNoteFormSet, RegisterAbsenceForm, + RegisterObjectActionForm, SelectForm, ) from .models import ( @@ -59,7 +60,13 @@ from .models import ( LessonDocumentation, PersonalNote, ) -from .tables import ExcuseTypeTable, ExtraMarkTable, GroupRoleTable, RegisterObjectTable +from .tables import ( + ExcuseTypeTable, + ExtraMarkTable, + GroupRoleTable, + RegisterObjectSelectTable, + RegisterObjectTable, +) from .util.alsijil_helpers import ( annotate_documentations, generate_list_of_all_register_objects, @@ -1255,7 +1262,7 @@ class GroupRoleAssignmentDeleteView( class AllRegisterObjectsView(PermissionRequiredMixin, View): permission_required = "alsijil.view_register_objects_list" - def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: + def get_context_data(self, request): context = {} # Filter selectable groups by permissions groups = Group.objects.all() @@ -1274,8 +1281,22 @@ class AllRegisterObjectsView(PermissionRequiredMixin, View): context["filter_form"] = filter_form register_objects = generate_list_of_all_register_objects(filter_dict) + + self.action_form = RegisterObjectActionForm(request, register_objects, request.POST or None) + context["action_form"] = self.action_form + if register_objects: - table = RegisterObjectTable(register_objects) - RequestConfig(request,).configure(table) # paginate={"per_page": 100} - context["table"] = table + self.table = RegisterObjectSelectTable(register_objects) + RequestConfig(request, paginate={"per_page": 100}).configure(self.table) + context["table"] = self.table + return context + + def get(self, request: HttpRequest) -> HttpResponse: + context = self.get_context_data(request) + return render(request, "alsijil/class_register/all_objects.html", context) + + def post(self, request: HttpRequest): + context = self.get_context_data(request) + if self.action_form.is_valid(): + self.action_form.execute() return render(request, "alsijil/class_register/all_objects.html", context) -- GitLab