diff --git a/aleksis/apps/alsijil/forms.py b/aleksis/apps/alsijil/forms.py index d1d47cb0bb23259aed7695c81cd0ed4ca60d5190..0d741f56a237b73ab42370b547b8c4386ed0811e 100644 --- a/aleksis/apps/alsijil/forms.py +++ b/aleksis/apps/alsijil/forms.py @@ -1,8 +1,10 @@ from datetime import datetime, timedelta +from typing import Optional, Sequence from django import forms from django.core.exceptions import ValidationError from django.db.models import Count, Q +from django.http import HttpRequest from django.utils import timezone from django.utils.translation import gettext_lazy as _ @@ -278,41 +280,35 @@ class FilterRegisterObjectForm(forms.Form): "date_end": date_end, } - def __init__(self, request, for_person=True, *args, **kwargs): + def __init__( + self, + request: HttpRequest, + *args, + for_person: bool = True, + groups: Optional[Sequence[Group]] = None, + **kwargs + ): self.request = request person = self.request.user.person - # Fill in initial data kwargs["initial"] = self.get_initial() super().__init__(*args, **kwargs) - # Build querysets self.fields["school_term"].queryset = SchoolTerm.objects.all() - # Filter selectable groups by permissions - group_qs = Group.objects.all() - if for_person: - group_qs = group_qs.filter( + if not groups and for_person: + groups = Group.objects.filter( Q(lessons__teachers=person) | Q(lessons__lesson_periods__substitutions__teachers=person) | Q(events__teachers=person) | Q(extra_lessons__teachers=person) ) - elif not check_global_permission(self.request.user, "alsijil.view_full_register"): - group_qs = group_qs.union( - group_qs.filter( - pk__in=get_objects_for_user( - self.request.user, "core.view_full_register_group", Group - ).values_list("pk", flat=True) - ) - ) - - # Flatten query by filtering groups by pk - groups_flat = Group.objects.filter(pk__in=list(group_qs.values_list("pk", flat=True))) - self.fields["group"].queryset = groups_flat + elif not for_person: + groups = Group.objects.all() + self.fields["group"].queryset = groups # Filter subjects by selectable groups subject_qs = Subject.objects.filter( - Q(lessons__groups__in=groups_flat) | Q(extra_lessons__groups__in=groups_flat) + Q(lessons__groups__in=groups) | Q(extra_lessons__groups__in=groups) ).distinct() self.fields["subject"].queryset = subject_qs diff --git a/aleksis/apps/alsijil/menus.py b/aleksis/apps/alsijil/menus.py index a7e6c8407ba49cf537bc728ffe2794a2a9bb01db..fe720477001be6e5992a71ac62b66fad00186780 100644 --- a/aleksis/apps/alsijil/menus.py +++ b/aleksis/apps/alsijil/menus.py @@ -78,6 +78,17 @@ MENUS = { ), ], }, + { + "name": _("All lessons"), + "url": "all_register_objects", + "icon": "list", + "validators": [ + ( + "aleksis.core.util.predicates.permission_validator", + "alsijil.view_register_objects_list", + ), + ], + }, { "name": _("Excuse types"), "url": "excuse_types", diff --git a/aleksis/apps/alsijil/rules.py b/aleksis/apps/alsijil/rules.py index f36db7aa5a3b532d2b143c109c7daf089391c96a..7358a9d4a0f556a86886e14583bb17b0639f40d7 100644 --- a/aleksis/apps/alsijil/rules.py +++ b/aleksis/apps/alsijil/rules.py @@ -1,6 +1,8 @@ from rules import add_perm +from aleksis.core.models import Group from aleksis.core.util.predicates import ( + has_any_object, has_global_perm, has_object_perm, has_person, @@ -273,3 +275,9 @@ delete_group_role_assignment_predicate = ( has_global_perm("alsjil.assign_grouprole") | is_group_role_assignment_group_owner ) add_perm("alsijil.delete_grouproleassignment", delete_group_role_assignment_predicate) + +view_register_objects_list_predicate = has_person & ( + has_any_object("core.view_full_register_group", Group) + | has_global_perm("core.view_full_register") +) +add_perm("alsijil.view_register_objects_list", view_register_objects_list_predicate) diff --git a/aleksis/apps/alsijil/templates/alsijil/class_register/all_objects.html b/aleksis/apps/alsijil/templates/alsijil/class_register/all_objects.html new file mode 100644 index 0000000000000000000000000000000000000000..7fd891a47740fa79ea8ff76371be83f7588f417e --- /dev/null +++ b/aleksis/apps/alsijil/templates/alsijil/class_register/all_objects.html @@ -0,0 +1,30 @@ +{# -*- engine:django -*- #} +{% extends "core/base.html" %} +{% load i18n rules static django_tables2 material_form %} + +{% block browser_title %}{% blocktrans %}All lessons{% endblocktrans %}{% endblock %} + +{% block page_title %} + {% blocktrans %}All lessons{% endblocktrans %} +{% endblock %} + +{% block extra_head %} + {{ block.super }} + <link rel="stylesheet" href="{% static 'css/alsijil/alsijil.css' %}"/> +{% 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> + {% if table %} + {% render_table table %} + + {% endif %} +{% endblock %} \ No newline at end of file diff --git a/aleksis/apps/alsijil/urls.py b/aleksis/apps/alsijil/urls.py index 16ba0d0b9c5c4c976c26222a8542d3e96a93b1aa..8fb31b625ba068fc3c6ba731fe39155f9974d76e 100644 --- a/aleksis/apps/alsijil/urls.py +++ b/aleksis/apps/alsijil/urls.py @@ -100,4 +100,5 @@ urlpatterns = [ views.AssignGroupRoleMultipleView.as_view(), name="assign_group_role_multiple", ), + path("all/", views.AllRegisterObjectsView.as_view(), name="all_register_objects"), ] diff --git a/aleksis/apps/alsijil/util/alsijil_helpers.py b/aleksis/apps/alsijil/util/alsijil_helpers.py index 3d30d2395a017c4338a96ee5625f235d9c1aea30..d708589e5921fa2237a9da80e719d9c50fce57f5 100644 --- a/aleksis/apps/alsijil/util/alsijil_helpers.py +++ b/aleksis/apps/alsijil/util/alsijil_helpers.py @@ -1,5 +1,5 @@ from operator import itemgetter -from typing import List, Optional, Union +from typing import Any, Dict, List, Optional, Union from django.db.models.expressions import Exists, OuterRef from django.db.models.query import Prefetch, QuerySet @@ -112,7 +112,7 @@ def register_objects_sorter(register_object: Union[LessonPeriod, Event, ExtraLes return 0 -def generate_list_of_all_register_objects(filter_dict: dict) -> List[dict]: +def generate_list_of_all_register_objects(filter_dict: Dict[str, Any]) -> List[dict]: # Get data for filtering initial_filter_data = FilterRegisterObjectForm.get_initial() # Always force a selected school term so that queries won't get to big @@ -120,6 +120,7 @@ def generate_list_of_all_register_objects(filter_dict: dict) -> List[dict]: filter_person = filter_dict.get("person") should_have_documentation = filter_dict.get("has_documentation") filter_group = filter_dict.get("group") + filter_groups = filter_dict.get("groups") filter_subject = filter_dict.get("subject") filter_date_start = filter_dict.get("date_start", initial_filter_data.get("date_start")) filter_date_end = filter_dict.get("date_end", initial_filter_data.get("date_end")) @@ -166,6 +167,10 @@ def generate_list_of_all_register_objects(filter_dict: dict) -> List[dict]: lesson_periods = lesson_periods.filter_group(filter_group) events = events.filter_group(filter_group) extra_lessons = extra_lessons.filter_group(filter_group) + if filter_groups: + lesson_periods = lesson_periods.filter_groups(filter_groups) + events = events.filter_groups(filter_groups) + extra_lessons = extra_lessons.filter_groups(filter_groups) if filter_subject: lesson_periods = lesson_periods.filter( Q(lesson__subject=filter_subject) | Q(substitutions__subject=filter_subject) @@ -293,7 +298,7 @@ def generate_list_of_all_register_objects(filter_dict: dict) -> List[dict]: f"{date_format(register_object.date_start)}" f"–{date_format(register_object.date_end)}" ) - day_sort = (register_object.date_start,) + day_sort = register_object.date_start period = ( f"{register_object.period_from.period}.–{register_object.period_to.period}." ) diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py index 273f19b1d4800d3243e02db88493c4c061e6af98..bf4261ddbdaa9871dbbf2c25e331b94bee9b5ee7 100644 --- a/aleksis/apps/alsijil/views.py +++ b/aleksis/apps/alsijil/views.py @@ -13,12 +13,14 @@ from django.urls import reverse, reverse_lazy from django.utils import timezone from django.utils.decorators import method_decorator from django.utils.translation import ugettext as _ +from django.views import View from django.views.decorators.cache import never_cache from django.views.generic import DetailView import reversion from calendarweek import CalendarWeek from django_tables2 import RequestConfig, SingleTableView +from guardian.shortcuts import get_objects_for_user from reversion.views import RevisionMixin from rules.contrib.views import PermissionRequiredMixin, permission_required @@ -35,6 +37,7 @@ from aleksis.core.mixins import ( from aleksis.core.models import Group, Person, SchoolTerm from aleksis.core.util import messages from aleksis.core.util.core_helpers import get_site_preferences, objectgetter_optional +from aleksis.core.util.predicates import check_global_permission from .forms import ( AssignGroupRoleForm, @@ -899,10 +902,11 @@ def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResp context["extra_marks"] = extra_marks # Build filter with own form and logic as django-filter can't work with different models - filter_form = FilterRegisterObjectForm(request, True, request.GET or None) + filter_form = FilterRegisterObjectForm(request, request.GET or None, for_person=True) filter_dict = filter_form.cleaned_data if filter_form.is_valid() else {} filter_dict["person"] = person context["filter_form"] = filter_form + register_objects = generate_list_of_all_register_objects(filter_dict) if register_objects: table = RegisterObjectTable(register_objects) @@ -1246,3 +1250,32 @@ class GroupRoleAssignmentDeleteView( def get_success_url(self) -> str: pk = self.object.groups.first().pk return reverse("assigned_group_roles", args=[pk]) + + +class AllRegisterObjectsView(PermissionRequiredMixin, View): + permission_required = "alsijil.view_register_objects_list" + + def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: + context = {} + # Filter selectable groups by permissions + groups = Group.objects.all() + if not check_global_permission(request.user, "alsijil.view_full_register"): + allowed_groups = get_objects_for_user( + self.request.user, "core.view_full_register_group", Group + ).values_list("pk", flat=True) + groups = groups.filter(Q(parent_groups__in=allowed_groups) | Q(pk__in=allowed_groups)) + + # Build filter with own form and logic as django-filter can't work with different models + filter_form = FilterRegisterObjectForm( + request, request.GET or None, for_person=False, groups=groups + ) + filter_dict = filter_form.cleaned_data if filter_form.is_valid() else {} + filter_dict["groups"] = groups + context["filter_form"] = filter_form + + register_objects = generate_list_of_all_register_objects(filter_dict) + if register_objects: + table = RegisterObjectTable(register_objects) + RequestConfig(request,).configure(table) # paginate={"per_page": 100} + context["table"] = table + return render(request, "alsijil/class_register/all_objects.html", context)