diff --git a/aleksis/apps/alsijil/rules.py b/aleksis/apps/alsijil/rules.py index aa80713bda0ce2e643e03e1a0f2f484b7aea297b..24852047bf6d82d94dfecd154fb140e91ac94225 100644 --- a/aleksis/apps/alsijil/rules.py +++ b/aleksis/apps/alsijil/rules.py @@ -318,6 +318,9 @@ view_instructions_predicate = has_person & ( ) add_perm("alsijil.view_instructions_rule", view_instructions_predicate) +view_done_instructions_predicate = has_person & (has_global_perm("alsijil.view_instruction")) +add_perm("alsijil.view_done_instructions_rule", view_instructions_predicate) + view_instruction_predicate = has_person & ( has_global_perm("alsijil.view_instruction") | is_instruction_for_person diff --git a/aleksis/apps/alsijil/templates/alsijil/instruction/list.html b/aleksis/apps/alsijil/templates/alsijil/instruction/list.html index eddfc6f9cc0099083dd2a7a2bbc06bbf991af0a7..cd785928bc04634cc3e36e4b28deac549e2a4450 100644 --- a/aleksis/apps/alsijil/templates/alsijil/instruction/list.html +++ b/aleksis/apps/alsijil/templates/alsijil/instruction/list.html @@ -7,6 +7,26 @@ {% block browser_title %}{% blocktrans %}Instructions{% endblocktrans %}{% endblock %} {% block page_title %}{% blocktrans %}Instructions{% endblocktrans %}{% endblock %} +{% block nav_content %} + <ul class="tabs tabs-transparent tabs-icons tabs-fixed-width"> + <li class="tab"> + <a href="#all"> + <i class="material-icons">list</i> + {% trans "All instructions" %} + </a> + </li> + {% has_perm "alsijil.view_done_instructions_rule" user as can_view_done %} + {% if can_view_done %} + <li class="tab"> + <a href="#done"> + <i class="material-icons">done_all</i> + {% trans "Done instructions" %} + </a> + </li> + {% endif %} + </ul> +{% endblock %} + {% block content %} <div class="row no-margin"> <div class="col s12"> @@ -38,8 +58,7 @@ </div> </div> - - <div class="row"> + <div class="row" id="all"> {% for instruction in instruction_list %} <div class=" col s12 m12 l6 xl4"> <div class="card"> @@ -71,4 +90,35 @@ </div> {% endfor %} </div> + + {% has_perm "alsijil.view_done_instructions_rule" user as can_view_done %} + {% if can_view_done %} + <div class="row" id="done"> + <table> + <tr> + <th></th> + {% for instruction in object_list %} + <th>{{ instruction.name }}</th> + {% endfor %} + </tr> + {% for group, instructions in done_instructions.items %} + <tr> + <th>{{ group.name }}</th> + {% for instruction, el in instructions.items %} + {% with done=el.0 docs=el.1 %} + <td class="{% if done %}green-text green lighten-5{% else %}red-text red lighten-5{% endif %}"> + <i class="material-icons left">{% if done %}check{% else %}clear{% endif %}</i> + <small> + {% for doc in docs %} + {{ doc.date }}, {{ doc.register_object.teacher_short_names }}{% if not forloop.last %};{% endif %} + {% endfor %} + </small> + </td> + {% endwith %} + {% endfor %} + </tr> + {% endfor %} + </table> + </div> + {% endif %} {% endblock %} diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py index 4605624cddf8e914e0be81a8a2ef6a6e96f74adc..7bde1d8514dbcbc8b8ff047e0651c54d73b27737 100644 --- a/aleksis/apps/alsijil/views.py +++ b/aleksis/apps/alsijil/views.py @@ -1,3 +1,4 @@ +from collections import OrderedDict from contextlib import nullcontext from copy import deepcopy from datetime import date, datetime, timedelta @@ -1442,6 +1443,62 @@ class InstructionsListView(PermissionRequiredMixin, FilterView): .distinct() ) + def get_context_data(self, **kwargs) -> dict: + context = super().get_context_data(**kwargs) + + if not self.request.user.has_perm("alsijil.view_done_instructions_rule"): + return context + + qs = self.object_list + + docs = LessonDocumentation.objects.filter(done_instructions__in=qs) + done_instructions_by_group = {} + for doc in docs: + groups = doc.register_object.get_groups().all() + for group in groups: + parent_groups = group.parent_groups_recursive + group_members = set(group.members.all()) + selected_groups = list(parent_groups) + [group] + + match_found = False + for selected_group in selected_groups: + if selected_group == group and match_found: + # Use original group only if no matching parent group has been found + continue + + # Use parent groups if the members are the same + if set(selected_group.members.all()) == group_members: + match_found = True + done_instructions_by_group.setdefault( + selected_group, dict(docs={}, instructions=set()) + ) + instructions = list(doc.done_instructions.all()) + for instruction in instructions: + done_instructions_by_group[selected_group]["docs"].setdefault( + instruction, set() + ) + done_instructions_by_group[selected_group]["docs"][instruction].add(doc) + done_instructions_by_group[selected_group]["instructions"].update( + instructions + ) + + # Sort done instructions by group name + done_instructions_by_group = OrderedDict( + sorted(done_instructions_by_group.items(), key=lambda x: x[0].short_name or x[0].name) + ) + + # Build table with instructions on x-axis and groups on y-axis + table_by_groups = OrderedDict() + for group, el in done_instructions_by_group.items(): + done_instructions = el["instructions"] + table_by_groups.setdefault(group, OrderedDict()) + for instruction in qs: + docs = el["docs"].get(instruction, set()) + table_by_groups[group][instruction] = (instruction in done_instructions, docs) + + context["done_instructions"] = table_by_groups + return context + @method_decorator(never_cache, name="dispatch") class InstructionCreateView(PermissionRequiredMixin, AdvancedCreateView):