Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • AlekSIS/official/AlekSIS-App-Chronos
  • sunweaver/AlekSIS-App-Chronos
  • sggua/AlekSIS-App-Chronos
  • tincmeKdenka/AlekSIS-App-Chronos
  • ligquamacti/AlekSIS-App-Chronos
  • 1crotatilhe/AlekSIS-App-Chronos
  • 1compluningi/AlekSIS-App-Chronos
  • starwardcarfi/AlekSIS-App-Chronos
  • ceohecholeg/AlekSIS-App-Chronos
  • 7quecontranchi/AlekSIS-App-Chronos
  • 8evsubcesza/AlekSIS-App-Chronos
  • unscinKibdzu/AlekSIS-App-Chronos
  • delucPchondmu/AlekSIS-App-Chronos
13 results
Show changes
Commits on Source (22)
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
from django.contrib import admin from django.contrib import admin
from django.utils.html import format_html from django.utils.html import format_html
from guardian.admin import GuardedModelAdmin
from .models import ( from .models import (
Absence, Absence,
AbsenceReason, AbsenceReason,
...@@ -144,7 +146,7 @@ class LessonAdmin(admin.ModelAdmin): ...@@ -144,7 +146,7 @@ class LessonAdmin(admin.ModelAdmin):
admin.site.register(Lesson, LessonAdmin) admin.site.register(Lesson, LessonAdmin)
class RoomAdmin(admin.ModelAdmin): class RoomAdmin(GuardedModelAdmin):
list_display = ("short_name", "name") list_display = ("short_name", "name")
list_display_links = ("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): ...@@ -8,7 +8,7 @@ class Migration(migrations.Migration):
dependencies = [ dependencies = [
('core', '0010_external_link_widget'), ('core', '0010_external_link_widget'),
('chronos', '0004_substitution_extra_lesson_year'), ('chronos', '0005_add_permissions'),
] ]
operations = [ operations = [
......
...@@ -134,3 +134,12 @@ Announcement.field( ...@@ -134,3 +134,12 @@ Announcement.field(
) )
Group.foreign_key("subject", Subject, related_name="groups") 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): ...@@ -331,6 +331,7 @@ class Room(ExtensibleModel):
return reverse("timetable", args=["room", self.id]) return reverse("timetable", args=["room", self.id])
class Meta: class Meta:
permissions = (("view_room_timetable", _("Can view room timetable")),)
ordering = ["name", "short_name"] ordering = ["name", "short_name"]
verbose_name = _("Room") verbose_name = _("Room")
verbose_name_plural = _("Rooms") verbose_name_plural = _("Rooms")
...@@ -1069,7 +1070,9 @@ class ChronosGlobalPermissions(GlobalPermissionModel): ...@@ -1069,7 +1070,9 @@ class ChronosGlobalPermissions(GlobalPermissionModel):
class Meta: class Meta:
managed = False managed = False
permissions = ( 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_timetable_overview", _("Can view timetable overview")),
("view_lessons_day", _("Can view all lessons per day")), ("view_lessons_day", _("Can view all lessons per day")),
) )
...@@ -8,19 +8,19 @@ from aleksis.core.util.predicates import ( ...@@ -8,19 +8,19 @@ from aleksis.core.util.predicates import (
) )
from .models import LessonSubstitution 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
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) add_perm("chronos.view_timetable_overview", view_timetable_overview_predicate)
# View my timetable # View my timetable
add_perm("chronos.view_my_timetable", has_person) add_perm("chronos.view_my_timetable", has_person)
# View timetable # View timetable
view_timetable_predicate = has_person & ( view_timetable_predicate = has_person & has_timetable_perm
has_global_perm("chronos.view_all_timetables") | has_timetable_perm
)
add_perm("chronos.view_timetable", view_timetable_predicate) add_perm("chronos.view_timetable", view_timetable_predicate)
# View all lessons per day # View all lessons per day
......
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
{% endif %} {% endif %}
</div> </div>
<div class="col s4 m6 l4 xl3 right align-right no-print"> <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> <i class="material-icons center">print</i>
</a> </a>
</div> </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.http import HttpRequest, HttpResponseNotFound
from django.shortcuts import get_object_or_404 from django.shortcuts import get_object_or_404
from guardian.core import ObjectPermissionChecker
from aleksis.core.models import Group, Person from aleksis.core.models import Group, Person
from aleksis.core.util.predicates import check_global_permission
from ..managers import TimetableType from ..managers import TimetableType
from ..models import LessonPeriod, LessonSubstitution, Room 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( def get_el_by_pk(
request: HttpRequest, request: HttpRequest,
...@@ -39,3 +48,85 @@ def get_substitution_by_id(request: HttpRequest, id_: int, week: int): ...@@ -39,3 +48,85 @@ def get_substitution_by_id(request: HttpRequest, id_: int, week: int):
return LessonSubstitution.objects.filter( return LessonSubstitution.objects.filter(
week=wanted_week.week, year=wanted_week.year, lesson_period=lesson_period week=wanted_week.week, year=wanted_week.year, lesson_period=lesson_period
).first() ).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 ...@@ -3,18 +3,77 @@ from django.db.models import Model
from rules import predicate from rules import predicate
from aleksis.apps.chronos.models import Room
from aleksis.core.models import Group, Person 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 @predicate
def has_timetable_perm(user: User, obj: Model) -> bool: 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: Check if can access timetable.
return obj in user.person.member_of
elif obj.model is Person: Predicate which checks whether the user is allowed
return user.person == obj to access the requested timetable.
elif obj.model is Room: """
return True 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: else:
return False 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 datetime import datetime, timedelta
from typing import Optional 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.http import HttpRequest, HttpResponse, HttpResponseNotFound
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse from django.urls import reverse
...@@ -12,17 +12,23 @@ from django.views.decorators.cache import never_cache ...@@ -12,17 +12,23 @@ from django.views.decorators.cache import never_cache
from django_tables2 import RequestConfig from django_tables2 import RequestConfig
from rules.contrib.views import permission_required 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 import messages
from aleksis.core.util.core_helpers import get_site_preferences, has_person from aleksis.core.util.core_helpers import get_site_preferences, has_person
from aleksis.core.util.pdf import render_pdf from aleksis.core.util.pdf import render_pdf
from .forms import LessonSubstitutionForm from .forms import LessonSubstitutionForm
from .managers import TimetableType 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 .tables import LessonsTable
from .util.build import build_substitutions_list, build_timetable, build_weekdays 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.date import CalendarWeek, get_weeks_for_year
from .util.js import date_unix from .util.js import date_unix
...@@ -32,22 +38,8 @@ def all_timetables(request: HttpRequest) -> HttpResponse: ...@@ -32,22 +38,8 @@ def all_timetables(request: HttpRequest) -> HttpResponse:
"""View all timetables for persons, groups and rooms.""" """View all timetables for persons, groups and rooms."""
context = {} context = {}
teachers = ( user = request.user
Person.objects.alias(lessons_count=Count("lessons_as_teacher")) teachers, classes, rooms = get_teachers(user), get_classes(user), get_rooms(user)
.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")
)
context["teachers"] = teachers context["teachers"] = teachers
context["classes"] = classes context["classes"] = classes
......