Skip to content
Snippets Groups Projects
Verified Commit 56388f38 authored by Lloyd Meins's avatar Lloyd Meins :thought_balloon:
Browse files

Merge branch 'master' into 124-check-generation-of-regular-timetable

parents 0f371ed0 1453676d
No related branches found
No related tags found
1 merge request!153Resolve "Check generation of regular timetable"
Pipeline #10833 passed
......@@ -3,6 +3,8 @@
from django.contrib import admin
from django.utils.html import format_html
from guardian.admin import GuardedModelAdmin
from .models import (
Absence,
AbsenceReason,
......@@ -144,7 +146,7 @@ class LessonAdmin(admin.ModelAdmin):
admin.site.register(Lesson, LessonAdmin)
class RoomAdmin(admin.ModelAdmin):
class RoomAdmin(GuardedModelAdmin):
list_display = ("short_name", "name")
list_display_links = ("short_name", "name")
......
# Generated by Django 3.1.3 on 2020-12-22 12:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('chronos', '0004_substitution_extra_lesson_year'),
]
operations = [
migrations.AlterModelOptions(
name='room',
options={'ordering': ['name', 'short_name'],
'permissions': (('view_room_timetable', 'Can view room timetable'),),
'verbose_name': 'Room', 'verbose_name_plural': 'Rooms'},
),
migrations.AlterModelOptions(
name='chronosglobalpermissions',
options={'managed': False, 'permissions': (('view_all_room_timetables', 'Can view all room timetables'), ('view_all_group_timetables', 'Can view all group timetables'), ('view_all_person_timetables', 'Can view all person timetables'), ('view_timetable_overview', 'Can view timetable overview'), ('view_lessons_day', 'Can view all lessons per day'))},
),
]
......@@ -8,7 +8,7 @@ class Migration(migrations.Migration):
dependencies = [
('core', '0010_external_link_widget'),
('chronos', '0004_substitution_extra_lesson_year'),
('chronos', '0005_add_permissions'),
]
operations = [
......
......@@ -134,3 +134,12 @@ Announcement.field(
)
Group.foreign_key("subject", Subject, related_name="groups")
# Dynamically add extra permissions to Group and Person models in core
# Note: requires migrate afterwards
Group.add_permission(
"view_group_timetable", _("Can view group timetable"),
)
Person.add_permission(
"view_person_timetable", _("Can view person timetable"),
)
......@@ -331,6 +331,7 @@ class Room(ExtensibleModel):
return reverse("timetable", args=["room", self.id])
class Meta:
permissions = (("view_room_timetable", _("Can view room timetable")),)
ordering = ["name", "short_name"]
verbose_name = _("Room")
verbose_name_plural = _("Rooms")
......@@ -1069,7 +1070,9 @@ class ChronosGlobalPermissions(GlobalPermissionModel):
class Meta:
managed = False
permissions = (
("view_all_timetables", _("Can view all timetables")),
("view_all_room_timetables", _("Can view all room timetables")),
("view_all_group_timetables", _("Can view all group timetables")),
("view_all_person_timetables", _("Can view all person timetables")),
("view_timetable_overview", _("Can view timetable overview")),
("view_lessons_day", _("Can view all lessons per day")),
)
......@@ -8,19 +8,19 @@ from aleksis.core.util.predicates import (
)
from .models import LessonSubstitution
from .util.predicates import has_timetable_perm
from .util.predicates import has_any_timetable_object, has_timetable_perm
# View timetable overview
view_timetable_overview_predicate = has_person & has_global_perm("chronos.view_timetable_overview")
view_timetable_overview_predicate = has_person & (
has_any_timetable_object | has_global_perm("chronos.view_timetable_overview")
)
add_perm("chronos.view_timetable_overview", view_timetable_overview_predicate)
# View my timetable
add_perm("chronos.view_my_timetable", has_person)
# View timetable
view_timetable_predicate = has_person & (
has_global_perm("chronos.view_all_timetables") | has_timetable_perm
)
view_timetable_predicate = has_person & has_timetable_perm
add_perm("chronos.view_timetable", view_timetable_predicate)
# View all lessons per day
......
......@@ -36,7 +36,7 @@
{% endif %}
</div>
<div class="col s4 m6 l4 xl3 right align-right no-print">
<a class="waves-effect waves-teal btn-flat btn-flat-medium right hide-on-small-and-down" href="{% url "timetable_print" type.value pk %}" id="print">
<a class="waves-effect waves-teal btn-flat btn-flat-medium right hide-on-small-and-down" href="{% url "timetable_print" type.value pk %}">
<i class="material-icons center">print</i>
</a>
</div>
......
from typing import Optional
from typing import TYPE_CHECKING, Optional
from django.db.models import Count, Q
from django.http import HttpRequest, HttpResponseNotFound
from django.shortcuts import get_object_or_404
from guardian.core import ObjectPermissionChecker
from aleksis.core.models import Group, Person
from aleksis.core.util.predicates import check_global_permission
from ..managers import TimetableType
from ..models import LessonPeriod, LessonSubstitution, Room
if TYPE_CHECKING:
from django.contrib.auth import get_user_model
User = get_user_model() # noqa
def get_el_by_pk(
request: HttpRequest,
......@@ -39,3 +48,85 @@ def get_substitution_by_id(request: HttpRequest, id_: int, week: int):
return LessonSubstitution.objects.filter(
week=wanted_week.week, year=wanted_week.year, lesson_period=lesson_period
).first()
def get_teachers(user: "User"):
"""Get the teachers whose timetables are allowed to be seen by current user."""
checker = ObjectPermissionChecker(user)
teachers = (
Person.objects.annotate(lessons_count=Count("lessons_as_teacher"))
.filter(lessons_count__gt=0)
.order_by("short_name", "last_name")
)
if not check_global_permission(user, "chronos.view_all_person_timetables"):
checker.prefetch_perms(teachers)
wanted_teachers = set()
for teacher in teachers:
if checker.has_perm("core.view_person_timetable", teacher):
wanted_teachers.add(teacher.pk)
teachers = teachers.filter(Q(pk=user.person.pk) | Q(pk__in=wanted_teachers))
return teachers
def get_classes(user: "User"):
"""Get the classes whose timetables are allowed to be seen by current user."""
checker = ObjectPermissionChecker(user)
classes = (
Group.objects.for_current_school_term_or_all()
.annotate(
lessons_count=Count("lessons"), child_lessons_count=Count("child_groups__lessons"),
)
.filter(
Q(lessons_count__gt=0, parent_groups=None)
| Q(child_lessons_count__gt=0, parent_groups=None)
)
.order_by("short_name", "name")
)
if not check_global_permission(user, "chronos.view_all_group_timetables"):
checker.prefetch_perms(classes)
wanted_classes = set()
for _class in classes:
if checker.has_perm("core.view_group_timetable", _class):
wanted_classes.add(_class.pk)
classes = classes.filter(
Q(pk__in=wanted_classes) | Q(members=user.person) | Q(owners=user.person)
)
if user.person.primary_group:
classes = classes.filter(Q(pk=user.person.primary_group.pk))
return classes
def get_rooms(user: "User"):
"""Get the rooms whose timetables are allowed to be seen by current user."""
checker = ObjectPermissionChecker(user)
rooms = (
Room.objects.annotate(lessons_count=Count("lesson_periods"))
.filter(lessons_count__gt=0)
.order_by("short_name", "name")
)
if not check_global_permission(user, "chronos.view_all_room_timetables"):
checker.prefetch_perms(rooms)
wanted_rooms = set()
for room in rooms:
if checker.has_perm("chronos.view_room_timetable", room):
wanted_rooms.add(room.pk)
rooms = rooms.filter(Q(pk__in=wanted_rooms))
return rooms
......@@ -3,18 +3,77 @@ from django.db.models import Model
from rules import predicate
from aleksis.apps.chronos.models import Room
from aleksis.core.models import Group, Person
from aleksis.core.util.predicates import has_global_perm, has_object_perm
from ..models import Room
from .chronos_helpers import get_classes, get_rooms, get_teachers
@predicate
def has_timetable_perm(user: User, obj: Model) -> bool:
"""Predicate which checks whether the user is allowed to access the requested timetable."""
if obj.model is Group:
return obj in user.person.member_of
elif obj.model is Person:
return user.person == obj
elif obj.model is Room:
return True
"""
Check if can access timetable.
Predicate which checks whether the user is allowed
to access the requested timetable.
"""
if isinstance(obj, Group):
return has_group_timetable_perm(user, obj)
elif isinstance(obj, Person):
return has_person_timetable_perm(user, obj)
elif isinstance(obj, Room):
return has_room_timetable_perm(user, obj)
else:
return False
@predicate
def has_group_timetable_perm(user: User, obj: Group) -> bool:
"""
Check if can access group timetable.
Predicate which checks whether the user is allowed
to access the requested group timetable.
"""
return (
obj in user.person.member_of.all()
or user.person.primary_group == obj
or obj in user.person.owner_of.all()
or has_global_perm("chronos.view_all_group_timetables")(user)
or has_object_perm("core.view_group_timetable")(user, obj)
)
@predicate
def has_person_timetable_perm(user: User, obj: Person) -> bool:
"""
Check if can access person timetable.
Predicate which checks whether the user is allowed
to access the requested person timetable.
"""
return (
user.person == obj
or has_global_perm("chronos.view_all_person_timetables")(user)
or has_object_perm("core.view_person_timetable")(user, obj)
)
@predicate
def has_room_timetable_perm(user: User, obj: Room) -> bool:
"""
Check if can access room timetable.
Predicate which checks whether the user is allowed
to access the requested room timetable.
"""
return has_global_perm("chronos.view_all_room_timetables")(user) or has_object_perm(
"chronos.view_room_timetable"
)(user, obj)
@predicate
def has_any_timetable_object(user: User) -> bool:
"""Predicate which checks whether there are any timetables the user is allowed to access."""
return get_classes(user).exists() or get_rooms(user).exists() or get_teachers(user).exists()
from datetime import datetime, timedelta
from typing import Optional
from django.db.models import Count, Q
from django.db.models import Q
from django.http import HttpRequest, HttpResponse, HttpResponseNotFound
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
......@@ -12,17 +12,23 @@ from django.views.decorators.cache import never_cache
from django_tables2 import RequestConfig
from rules.contrib.views import permission_required
from aleksis.core.models import Announcement, Group, Person
from aleksis.core.models import Announcement, Group
from aleksis.core.util import messages
from aleksis.core.util.core_helpers import get_site_preferences, has_person
from aleksis.core.util.pdf import render_pdf
from .forms import LessonSubstitutionForm
from .managers import TimetableType
from .models import Absence, Holiday, LessonPeriod, LessonSubstitution, Room, TimePeriod
from .models import Absence, Holiday, LessonPeriod, LessonSubstitution, TimePeriod
from .tables import LessonsTable
from .util.build import build_substitutions_list, build_timetable, build_weekdays
from .util.chronos_helpers import get_el_by_pk, get_substitution_by_id
from .util.chronos_helpers import (
get_classes,
get_el_by_pk,
get_rooms,
get_substitution_by_id,
get_teachers,
)
from .util.date import CalendarWeek, get_weeks_for_year
from .util.js import date_unix
......@@ -32,22 +38,8 @@ def all_timetables(request: HttpRequest) -> HttpResponse:
"""View all timetables for persons, groups and rooms."""
context = {}
teachers = (
Person.objects.alias(lessons_count=Count("lessons_as_teacher"))
.filter(lessons_count__gt=0)
.order_by("short_name", "last_name")
)
groups = Group.objects.for_current_school_term_or_all().alias(
lessons_count=Count("lessons"), child_lessons_count=Count("child_groups__lessons"),
)
classes = groups.filter(lessons_count__gt=0, parent_groups=None) | groups.filter(
child_lessons_count__gt=0, parent_groups=None
).order_by("short_name", "name")
rooms = (
Room.objects.alias(lessons_count=Count("lesson_periods"))
.filter(lessons_count__gt=0)
.order_by("short_name", "name")
)
user = request.user
teachers, classes, rooms = get_teachers(user), get_classes(user), get_rooms(user)
context["teachers"] = teachers
context["classes"] = classes
......
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