Skip to content
Snippets Groups Projects
Commit 75d5c187 authored by Nik | Klampfradler's avatar Nik | Klampfradler
Browse files

Merge branch 'feature/lesson-documentation-overview' into 'master'

Overview of all register objects

Closes #137

See merge request !152
parents 976b2ef4 6f73dfb2
No related branches found
No related tags found
1 merge request!152Overview of all register objects
Pipeline #6463 passed
Showing
with 891 additions and 242 deletions
from typing import Sequence
from django.contrib import messages
from django.contrib.humanize.templatetags.humanize import apnumber
from django.http import HttpRequest
from django.template.loader import get_template
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)
template = get_template("alsijil/notifications/check.html")
for teacher, items in grouped_by_teachers.items():
msg = template.render({"items": items})
title = _("{} wants you to check some class register entries.").format(
request.user.person.addressing_name
)
n = Notification(
title=title,
description=msg,
sender=request.user.person.addressing_name,
recipient=teacher,
link=request.build_absolute_uri(reverse("overview_me")),
)
n.save()
count_teachers = len(grouped_by_teachers.keys())
count_items = len(selected_items)
messages.success(
request,
_(
"We have successfully sent notifications to "
"{count_teachers} persons for {count_items} lessons."
).format(count_teachers=apnumber(count_teachers), count_items=apnumber(count_items)),
)
send_request_to_check_entry.short_description = _("Notify teacher to check data")
from datetime import datetime from datetime import datetime, timedelta
from typing import Optional, Sequence
from django import forms from django import forms
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db.models import Count, Q 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 _ from django.utils.translation import gettext_lazy as _
from django_select2.forms import ModelSelect2MultipleWidget, ModelSelect2Widget, Select2Widget from django_select2.forms import ModelSelect2MultipleWidget, ModelSelect2Widget, Select2Widget
...@@ -10,11 +13,13 @@ from guardian.shortcuts import get_objects_for_user ...@@ -10,11 +13,13 @@ from guardian.shortcuts import get_objects_for_user
from material import Fieldset, Layout, Row from material import Fieldset, Layout, Row
from aleksis.apps.chronos.managers import TimetableType from aleksis.apps.chronos.managers import TimetableType
from aleksis.apps.chronos.models import TimePeriod from aleksis.apps.chronos.models import Subject, TimePeriod
from aleksis.core.models import Group, Person 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.core_helpers import get_site_preferences
from aleksis.core.util.predicates import check_global_permission from aleksis.core.util.predicates import check_global_permission
from .actions import send_request_to_check_entry
from .models import ( from .models import (
ExcuseType, ExcuseType,
ExtraMark, ExtraMark,
...@@ -254,3 +259,66 @@ class GroupRoleAssignmentEditForm(forms.ModelForm): ...@@ -254,3 +259,66 @@ class GroupRoleAssignmentEditForm(forms.ModelForm):
class Meta: class Meta:
model = GroupRoleAssignment model = GroupRoleAssignment
fields = ["date_start", "date_end"] fields = ["date_start", "date_end"]
class FilterRegisterObjectForm(forms.Form):
"""Form for filtering register objects in ``RegisterObjectTable``."""
layout = Layout(
Row("school_term", "date_start", "date_end"), Row("has_documentation", "group", "subject")
)
school_term = forms.ModelChoiceField(queryset=None, label=_("School term"))
has_documentation = forms.NullBooleanField(label=_("Has lesson documentation"))
group = forms.ModelChoiceField(queryset=None, label=_("Group"), required=False)
subject = forms.ModelChoiceField(queryset=None, label=_("Subject"), required=False)
date_start = forms.DateField(label=_("Start date"))
date_end = forms.DateField(label=_("End date"))
@classmethod
def get_initial(cls):
date_end = timezone.now().date()
date_start = date_end - timedelta(days=30)
return {
"school_term": SchoolTerm.current,
"date_start": date_start,
"date_end": date_end,
}
def __init__(
self,
request: HttpRequest,
*args,
for_person: bool = True,
groups: Optional[Sequence[Group]] = None,
**kwargs
):
self.request = request
person = self.request.user.person
kwargs["initial"] = self.get_initial()
super().__init__(*args, **kwargs)
self.fields["school_term"].queryset = SchoolTerm.objects.all()
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 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) | Q(extra_lessons__groups__in=groups)
).distinct()
self.fields["subject"].queryset = subject_qs
class RegisterObjectActionForm(ListActionForm):
"""Action form for managing register objects for use with ``RegisterObjectTable``."""
actions = [send_request_to_check_entry]
...@@ -78,6 +78,17 @@ MENUS = { ...@@ -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"), "name": _("Excuse types"),
"url": "excuse_types", "url": "excuse_types",
......
from django.core.exceptions import ValidationError
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from dynamic_preferences.preferences import Section from dynamic_preferences.preferences import Section
from dynamic_preferences.types import BooleanPreference from dynamic_preferences.types import BooleanPreference, IntegerPreference
from aleksis.core.registries import person_preferences_registry, site_preferences_registry from aleksis.core.registries import person_preferences_registry, site_preferences_registry
...@@ -109,3 +110,17 @@ class ShowGroupRolesInLessonView(BooleanPreference): ...@@ -109,3 +110,17 @@ class ShowGroupRolesInLessonView(BooleanPreference):
name = "group_roles_in_lesson_view" name = "group_roles_in_lesson_view"
default = True default = True
verbose_name = _("Show assigned group roles in lesson view") verbose_name = _("Show assigned group roles in lesson view")
@person_preferences_registry.register
class RegisterObjectsTableItemsPerPage(IntegerPreference):
"""Preference how many items are shown per page in ``RegisterObjectTable``."""
section = alsijil
name = "register_objects_table_items_per_page"
default = 100
verbose_name = _("Items per page in lessons table")
def validate(self, value):
if value < 1:
raise ValidationError(_("Each page must show at least one item."))
from rules import add_perm from rules import add_perm
from aleksis.core.models import Group
from aleksis.core.util.predicates import ( from aleksis.core.util.predicates import (
has_any_object,
has_global_perm, has_global_perm,
has_object_perm, has_object_perm,
has_person, has_person,
...@@ -299,3 +301,9 @@ delete_group_role_assignment_predicate = ( ...@@ -299,3 +301,9 @@ delete_group_role_assignment_predicate = (
has_global_perm("alsjil.assign_grouprole") | is_group_role_assignment_group_owner has_global_perm("alsjil.assign_grouprole") | is_group_role_assignment_group_owner
) )
add_perm("alsijil.delete_grouproleassignment", delete_group_role_assignment_predicate) 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)
...@@ -4,6 +4,8 @@ from django.utils.translation import gettext_lazy as _ ...@@ -4,6 +4,8 @@ from django.utils.translation import gettext_lazy as _
import django_tables2 as tables import django_tables2 as tables
from django_tables2.utils import A from django_tables2.utils import A
from aleksis.core.util.tables import SelectColumn
class ExtraMarkTable(tables.Table): class ExtraMarkTable(tables.Table):
class Meta: class Meta:
...@@ -78,3 +80,51 @@ class GroupRoleTable(tables.Table): ...@@ -78,3 +80,51 @@ class GroupRoleTable(tables.Table):
self.columns.hide("edit") self.columns.hide("edit")
if not request.user.has_perm("alsijil.delete_grouprole"): if not request.user.has_perm("alsijil.delete_grouprole"):
self.columns.hide("delete") self.columns.hide("delete")
def _get_link(value, record):
return record["register_object"].get_alsijil_url(record.get("week"))
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"}
status = tables.Column(accessor="register_object")
date = tables.Column(order_by="date_sort", linkify=_get_link)
period = tables.Column(order_by="period_sort", linkify=_get_link)
groups = tables.Column(linkify=_get_link)
teachers = tables.Column(linkify=_get_link)
subject = tables.Column(linkify=_get_link)
topic = tables.Column(linkify=_get_link)
homework = tables.Column(linkify=_get_link)
group_note = tables.Column(linkify=_get_link)
def render_status(self, value, record):
return render_to_string(
"alsijil/partials/lesson_status_icon.html",
dict(
week=record.get("week"),
has_documentation=record.get("has_documentation", False),
substitution=record.get("substitution"),
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", "...")
{# -*- 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 %}
{% include "alsijil/partials/objects_table.html" %}
<script src="{% static "js/multi_select.js" %}"></script>
{% endblock %}
{% load i18n %}{% trans "Please check if the following class register entries are complete and correct:" %}
{% for entry in items %}
- {{ entry.register_object }} ({{ entry.date }})
{% endfor %}
\ No newline at end of file
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
{% now_datetime as now_dt %} {% now_datetime as now_dt %}
{% if register_object.has_documentation %} {% if has_documentation or register_object.has_documentation %}
<i class="material-icons green{% firstof color_suffix "-text"%} tooltipped {{ css_class }}" data-position="bottom" data-tooltip="{% trans "Data complete" %}" title="{% trans "Data complete" %}">check_circle</i> <i class="material-icons green{% firstof color_suffix "-text"%} tooltipped {{ css_class }}" data-position="bottom" data-tooltip="{% trans "Data complete" %}" title="{% trans "Data complete" %}">check_circle</i>
{% elif not register_object.period %} {% elif not register_object.period %}
{% period_to_time_start week register_object.raw_period_from_on_day as time_start %} {% period_to_time_start week register_object.raw_period_from_on_day as time_start %}
...@@ -19,13 +19,13 @@ ...@@ -19,13 +19,13 @@
{% period_to_time_start week register_object.period as time_start %} {% period_to_time_start week register_object.period as time_start %}
{% period_to_time_end week register_object.period as time_end %} {% period_to_time_end week register_object.period as time_end %}
{% if register_object.get_substitution.cancelled %} {% if substitution.cancelled or register_object.get_substitution.cancelled %}
<i class="material-icons red{% firstof color_suffix "-text"%} tooltipped {{ css_class }}" data-position="bottom" data-tooltip="{% trans "Lesson cancelled" %}" title="{% trans "Lesson cancelled" %}">cancel</i> <i class="material-icons red{% firstof color_suffix "-text"%} tooltipped {{ css_class }}" data-position="bottom" data-tooltip="{% trans "Lesson cancelled" %}" title="{% trans "Lesson cancelled" %}">cancel</i>
{% elif now_dt > time_end %} {% elif now_dt > time_end %}
<i class="material-icons red{% firstof color_suffix "-text"%} tooltipped {{ css_class }}" data-position="bottom" data-tooltip="{% trans "Missing data" %}" title="{% trans "Missing data" %}">history</i> <i class="material-icons red{% firstof color_suffix "-text"%} tooltipped {{ css_class }}" data-position="bottom" data-tooltip="{% trans "Missing data" %}" title="{% trans "Missing data" %}">history</i>
{% elif now_dt > time_start and now_dt < time_end %} {% elif now_dt > time_start and now_dt < time_end %}
<i class="material-icons orange{% firstof color_suffix "-text"%} tooltipped {{ css_class }}" data-position="bottom" data-tooltip="{% trans "Pending" %}" title="{% trans "Pending" %}">more_horiz</i> <i class="material-icons orange{% firstof color_suffix "-text"%} tooltipped {{ css_class }}" data-position="bottom" data-tooltip="{% trans "Pending" %}" title="{% trans "Pending" %}">more_horiz</i>
{% elif register_object.get_substitution %} {% elif substitution or register_object.get_substitution %}
<i class="material-icons orange{% firstof color_suffix "-text"%} tooltipped {{ css_class }}" data-position="bottom" data-tooltip="{% trans "Substitution" %}" title="{% trans "Substitution" %}">update</i> <i class="material-icons orange{% firstof color_suffix "-text"%} tooltipped {{ css_class }}" data-position="bottom" data-tooltip="{% trans "Substitution" %}" title="{% trans "Substitution" %}">update</i>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% load i18n material_form django_tables2 %}
<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 %}
<div class="card">
<div class="card-content">
<form action="" method="post">
{% csrf_token %}
<div class="row">
<div class="col s12 {% if action_form %}m4 l4 xl6{% endif %}">
<div class="card-title">{% trans "Lesson table" %}</div>
</div>
{% if action_form %}
<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>
{% endif %}
</div>
{% render_table table %}
</form>
</div>
</div>
{% endif %}
...@@ -100,4 +100,5 @@ urlpatterns = [ ...@@ -100,4 +100,5 @@ urlpatterns = [
views.AssignGroupRoleMultipleView.as_view(), views.AssignGroupRoleMultipleView.as_view(),
name="assign_group_role_multiple", name="assign_group_role_multiple",
), ),
path("all/", views.AllRegisterObjectsView.as_view(), name="all_register_objects"),
] ]
from typing import List, Optional, Union from datetime import date
from operator import itemgetter
from typing import Any, Dict, Iterable, List, Optional, Sequence, Union
from django.db.models.expressions import Exists, OuterRef from django.db.models.expressions import Exists, OuterRef
from django.db.models.query import Prefetch, QuerySet from django.db.models.query import Prefetch, QuerySet
from django.db.models.query_utils import Q from django.db.models.query_utils import Q
from django.http import HttpRequest from django.http import HttpRequest
from django.utils.formats import date_format
from django.utils.translation import gettext as _
from calendarweek import CalendarWeek from calendarweek import CalendarWeek
from aleksis.apps.alsijil.forms import FilterRegisterObjectForm
from aleksis.apps.alsijil.models import LessonDocumentation from aleksis.apps.alsijil.models import LessonDocumentation
from aleksis.apps.chronos.models import Event, ExtraLesson, LessonPeriod from aleksis.apps.chronos.models import Event, ExtraLesson, Holiday, LessonPeriod
from aleksis.apps.chronos.util.chronos_helpers import get_el_by_pk from aleksis.apps.chronos.util.chronos_helpers import get_el_by_pk
...@@ -99,3 +104,281 @@ def register_objects_sorter(register_object: Union[LessonPeriod, Event, ExtraLes ...@@ -99,3 +104,281 @@ def register_objects_sorter(register_object: Union[LessonPeriod, Event, ExtraLes
return register_object.period_from_on_day return register_object.period_from_on_day
else: else:
return 0 return 0
def _filter_register_objects_by_dict(
filter_dict: Dict[str, Any],
register_objects: QuerySet[Union[LessonPeriod, Event, ExtraLesson]],
label_: str,
) -> QuerySet[Union[LessonPeriod, Event, ExtraLesson]]:
"""Filter register objects by a dictionary generated through ``FilterRegisterObjectForm``."""
if label_ == LessonPeriod.label_:
register_objects = register_objects.filter(
lesson__validity__school_term=filter_dict.get("school_term")
)
else:
register_objects = register_objects.filter(school_term=filter_dict.get("school_term"))
register_objects = register_objects.distinct()
if (
filter_dict.get("date_start")
and filter_dict.get("date_end")
and label_ != LessonPeriod.label_
):
register_objects = register_objects.within_dates(
filter_dict.get("date_start"), filter_dict.get("date_end")
)
if filter_dict.get("person"):
if label_ == LessonPeriod.label_:
register_objects = register_objects.filter(
Q(lesson__teachers=filter_dict.get("person"))
| Q(substitutions__teachers=filter_dict.get("person"))
)
else:
register_objects = register_objects.filter_teacher(filter_dict.get("person"))
if filter_dict.get("group"):
register_objects = register_objects.filter_group(filter_dict.get("group"))
if filter_dict.get("groups"):
register_objects = register_objects.filter_groups(filter_dict.get("groups"))
if filter_dict.get("subject"):
if label_ == LessonPeriod.label_:
register_objects = register_objects.filter(
Q(lesson__subject=filter_dict.get("subject"))
| Q(substitutions__subject=filter_dict.get("subject"))
)
elif label_ == Event.label_:
# As events have no subject, we exclude them at all
register_objects = register_objects.none()
else:
register_objects = register_objects.filter(subject=filter_dict.get("subject"))
return register_objects
def _generate_dicts_for_lesson_periods(
filter_dict: Dict[str, Any],
lesson_periods: QuerySet[LessonPeriod],
documentations: Optional[Iterable[LessonDocumentation]] = None,
holiday_days: Optional[Sequence[date]] = None,
) -> List[Dict[str, Any]]:
"""Generate a list of dicts for use with ``RegisterObjectTable``."""
if not holiday_days:
holiday_days = []
date_start = lesson_periods.first().lesson.validity.date_start
date_end = lesson_periods.last().lesson.validity.date_end
if (
filter_dict["filter_date"]
and filter_dict.get("date_start") > date_start
and filter_dict.get("date_start") < date_end
):
date_start = filter_dict.get("date_start")
if (
filter_dict["filter_date"]
and filter_dict.get("date_end") < date_end
and filter_dict.get("date_end") > date_start
):
date_end = filter_dict.get("date_end")
weeks = CalendarWeek.weeks_within(date_start, date_end)
register_objects = []
for lesson_period in lesson_periods:
for week in weeks:
day = week[lesson_period.period.weekday]
# Skip all lesson periods in holidays
if day in holiday_days:
continue
# Ensure that the lesson period is in filter range and validity range
if (
lesson_period.lesson.validity.date_start
<= day
<= lesson_period.lesson.validity.date_end
) and (
not filter_dict.get("filter_date")
or (filter_dict.get("date_start") <= day <= filter_dict.get("date_end"))
):
sub = lesson_period.get_substitution()
# Skip lesson period if the person isn't a teacher
# or substitution teacher of this lesson period
if filter_dict.get("person") and (
filter_dict.get("person") not in lesson_period.lesson.teachers.all() and not sub
):
continue
teachers = (
sub.teacher_names
if sub and sub.teachers.all()
else lesson_period.lesson.teacher_names
)
if (
filter_dict.get("subject")
and filter_dict.get("subject") != lesson_period.get_subject()
):
continue
# Filter matching documentations and annotate if they exist
filtered_documentations = list(
filter(
lambda d: d.week == week.week
and d.year == week.year
and d.lesson_period_id == lesson_period.pk,
documentations
if documentations is not None
else lesson_period.documentations.all(),
)
)
has_documentation = bool(filtered_documentations)
if filter_dict.get(
"has_documentation"
) is not None and has_documentation != filter_dict.get("has_documentation"):
continue
# Build table entry
entry = {
"pk": f"lesson_period_{lesson_period.pk}_{week.year}_{week.week}",
"week": week,
"has_documentation": has_documentation,
"substitution": sub,
"register_object": lesson_period,
"date": date_format(day),
"date_sort": day,
"period": f"{lesson_period.period.period}.",
"period_sort": lesson_period.period.period,
"groups": lesson_period.lesson.group_names,
"teachers": teachers,
"subject": lesson_period.get_subject().name,
}
if has_documentation:
doc = filtered_documentations[0]
entry["topic"] = doc.topic
entry["homework"] = doc.homework
entry["group_note"] = doc.group_note
register_objects.append(entry)
return register_objects
def _generate_dicts_for_events_and_extra_lessons(
filter_dict: Dict[str, Any],
register_objects_start: Sequence[Union[Event, ExtraLesson]],
documentations: Optional[Iterable[LessonDocumentation]] = None,
) -> List[Dict[str, Any]]:
"""Generate a list of dicts for use with ``RegisterObjectTable``."""
register_objects = []
for register_object in register_objects_start:
filtered_documentations = list(
filter(
lambda d: getattr(d, f"{register_object.label_}_id") == register_object.pk,
documentations
if documentations is not None
else register_object.documentations.all(),
)
)
has_documentation = bool(filtered_documentations)
if filter_dict.get(
"has_documentation"
) is not None and has_documentation != filter_dict.get("has_documentation"):
continue
if isinstance(register_object, ExtraLesson):
day = date_format(register_object.day)
day_sort = register_object.day
period = f"{register_object.period.period}."
period_sort = register_object.period.period
else:
day = (
f"{date_format(register_object.date_start)}"
f"{date_format(register_object.date_end)}"
)
day_sort = register_object.date_start
period = f"{register_object.period_from.period}.–{register_object.period_to.period}."
period_sort = register_object.period_from.period
# Build table entry
entry = {
"pk": f"{register_object.label_}_{register_object.pk}",
"has_documentation": has_documentation,
"register_object": register_object,
"date": day,
"date_sort": day_sort,
"period": period,
"period_sort": period_sort,
"groups": register_object.group_names,
"teachers": register_object.teacher_names,
"subject": register_object.subject.name
if isinstance(register_object, ExtraLesson)
else _("Event"),
}
if has_documentation:
doc = filtered_documentations[0]
entry["topic"] = doc.topic
entry["homework"] = doc.homework
entry["group_note"] = doc.group_note
register_objects.append(entry)
return register_objects
def generate_list_of_all_register_objects(filter_dict: Dict[str, Any]) -> List[Dict[str, Any]]:
"""Generate a list of all register objects.
This list can be filtered using ``filter_dict``. The following keys are supported:
- ``school_term`` (defaults to the current school term)
- ``date_start`` and ``date_end`` (defaults to the last thirty days)
- ``groups`` and/or ``groups``
- ``person``
- ``subject``
"""
# Always force a value for school term, start and end date so that queries won't get too big
initial_filter_data = FilterRegisterObjectForm.get_initial()
filter_dict["school_term"] = filter_dict.get("school_term", initial_filter_data["school_term"])
filter_dict["date_start"] = filter_dict.get("date_start", initial_filter_data["date_start"])
filter_dict["date_end"] = filter_dict.get("date_end", initial_filter_data["date_end"])
filter_dict["filter_date"] = bool(filter_dict.get("date_start")) and bool(
filter_dict.get("date_end")
)
# Get all holidays in the selected school term to sort all data in holidays out
holidays = Holiday.objects.within_dates(
filter_dict["school_term"].date_start, filter_dict["school_term"].date_end
)
holiday_days = holidays.get_all_days()
lesson_periods = _filter_register_objects_by_dict(
filter_dict,
LessonPeriod.objects.order_by("lesson__validity__date_start"),
LessonPeriod.label_,
)
events = _filter_register_objects_by_dict(
filter_dict, Event.objects.exclude_holidays(holidays), Event.label_
)
extra_lessons = _filter_register_objects_by_dict(
filter_dict, ExtraLesson.objects.exclude_holidays(holidays), ExtraLesson.label_
)
# Prefetch documentations for all register objects and substitutions for all lesson periods
# in order to prevent extra queries
documentations = LessonDocumentation.objects.not_empty().filter(
Q(event__in=events)
| Q(extra_lesson__in=extra_lessons)
| Q(lesson_period__in=lesson_periods)
)
if lesson_periods:
register_objects = _generate_dicts_for_lesson_periods(
filter_dict, lesson_periods, documentations, holiday_days
)
register_objects += _generate_dicts_for_events_and_extra_lessons(
filter_dict, list(events) + list(extra_lessons), documentations
)
# Sort table entries by date and period and configure table
register_objects = sorted(register_objects, key=itemgetter("date_sort", "period_sort"))
return register_objects
return []
...@@ -13,12 +13,14 @@ from django.urls import reverse, reverse_lazy ...@@ -13,12 +13,14 @@ from django.urls import reverse, reverse_lazy
from django.utils import timezone from django.utils import timezone
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.views import View
from django.views.decorators.cache import never_cache from django.views.decorators.cache import never_cache
from django.views.generic import DetailView from django.views.generic import DetailView
import reversion import reversion
from calendarweek import CalendarWeek from calendarweek import CalendarWeek
from django_tables2 import SingleTableView from django_tables2 import RequestConfig, SingleTableView
from guardian.shortcuts import get_objects_for_user
from reversion.views import RevisionMixin from reversion.views import RevisionMixin
from rules.contrib.views import PermissionRequiredMixin, permission_required from rules.contrib.views import PermissionRequiredMixin, permission_required
...@@ -35,16 +37,19 @@ from aleksis.core.mixins import ( ...@@ -35,16 +37,19 @@ from aleksis.core.mixins import (
from aleksis.core.models import Group, Person, SchoolTerm from aleksis.core.models import Group, Person, SchoolTerm
from aleksis.core.util import messages from aleksis.core.util import messages
from aleksis.core.util.core_helpers import get_site_preferences, objectgetter_optional from aleksis.core.util.core_helpers import get_site_preferences, objectgetter_optional
from aleksis.core.util.predicates import check_global_permission
from .forms import ( from .forms import (
AssignGroupRoleForm, AssignGroupRoleForm,
ExcuseTypeForm, ExcuseTypeForm,
ExtraMarkForm, ExtraMarkForm,
FilterRegisterObjectForm,
GroupRoleAssignmentEditForm, GroupRoleAssignmentEditForm,
GroupRoleForm, GroupRoleForm,
LessonDocumentationForm, LessonDocumentationForm,
PersonalNoteFormSet, PersonalNoteFormSet,
RegisterAbsenceForm, RegisterAbsenceForm,
RegisterObjectActionForm,
SelectForm, SelectForm,
) )
from .models import ( from .models import (
...@@ -55,9 +60,16 @@ from .models import ( ...@@ -55,9 +60,16 @@ from .models import (
LessonDocumentation, LessonDocumentation,
PersonalNote, PersonalNote,
) )
from .tables import ExcuseTypeTable, ExtraMarkTable, GroupRoleTable from .tables import (
ExcuseTypeTable,
ExtraMarkTable,
GroupRoleTable,
RegisterObjectSelectTable,
RegisterObjectTable,
)
from .util.alsijil_helpers import ( from .util.alsijil_helpers import (
annotate_documentations, annotate_documentations,
generate_list_of_all_register_objects,
get_register_object_by_pk, get_register_object_by_pk,
get_timetable_instance_by_pk, get_timetable_instance_by_pk,
register_objects_sorter, register_objects_sorter,
...@@ -898,6 +910,20 @@ def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResp ...@@ -898,6 +910,20 @@ def overview_person(request: HttpRequest, id_: Optional[int] = None) -> HttpResp
context["excuse_types"] = excuse_types context["excuse_types"] = excuse_types
context["extra_marks"] = extra_marks 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, 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)
items_per_page = request.user.person.preferences[
"alsijil__register_objects_table_items_per_page"
]
RequestConfig(request, paginate={"per_page": items_per_page}).configure(table)
context["register_object_table"] = table
return render(request, "alsijil/class_register/person.html", context) return render(request, "alsijil/class_register/person.html", context)
...@@ -1236,3 +1262,51 @@ class GroupRoleAssignmentDeleteView( ...@@ -1236,3 +1262,51 @@ class GroupRoleAssignmentDeleteView(
def get_success_url(self) -> str: def get_success_url(self) -> str:
pk = self.object.groups.first().pk pk = self.object.groups.first().pk
return reverse("assigned_group_roles", args=[pk]) return reverse("assigned_group_roles", args=[pk])
class AllRegisterObjectsView(PermissionRequiredMixin, View):
"""Provide overview of all register objects for coordinators."""
permission_required = "alsijil.view_register_objects_list"
def get_context_data(self, request):
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)
self.action_form = RegisterObjectActionForm(request, register_objects, request.POST or None)
context["action_form"] = self.action_form
if register_objects:
self.table = RegisterObjectSelectTable(register_objects)
items_per_page = request.user.person.preferences[
"alsijil__register_objects_table_items_per_page"
]
RequestConfig(request, paginate={"per_page": items_per_page}).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)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment