diff --git a/aleksis/apps/chronos/admin.py b/aleksis/apps/chronos/admin.py index 5145472fdbf9eff3349c81d0cbf10a59c24442f2..a29e816f464aa23e62b26294852050a8ab1ae032 100644 --- a/aleksis/apps/chronos/admin.py +++ b/aleksis/apps/chronos/admin.py @@ -24,6 +24,8 @@ from .models import ( ) from .util.format import format_date_period, format_m2m +from guardian.admin import GuardedModelAdmin + def colour_badge(fg: str, bg: str, val: str): html = """ @@ -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") diff --git a/aleksis/apps/chronos/migrations/0005_add_permissions.py b/aleksis/apps/chronos/migrations/0005_add_permissions.py new file mode 100644 index 0000000000000000000000000000000000000000..9fcc2fba01d69514c27568e632e6ee1dba227672 --- /dev/null +++ b/aleksis/apps/chronos/migrations/0005_add_permissions.py @@ -0,0 +1,23 @@ +# 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'))}, + ), + ] diff --git a/aleksis/apps/chronos/model_extensions.py b/aleksis/apps/chronos/model_extensions.py index 269ce3fc292aeb4ff587982d1272f38d678f1743..b77770f84945f7cd98b2fc43cc43ce6dbffdd6a4 100644 --- a/aleksis/apps/chronos/model_extensions.py +++ b/aleksis/apps/chronos/model_extensions.py @@ -124,3 +124,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"), +) diff --git a/aleksis/apps/chronos/models.py b/aleksis/apps/chronos/models.py index a810fc9b17f1d6c6e353d70c1abb9a4fb90a83fe..9e755bebe14cac939d8379abb91ee4bdad0601db 100644 --- a/aleksis/apps/chronos/models.py +++ b/aleksis/apps/chronos/models.py @@ -317,6 +317,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") @@ -946,7 +947,9 @@ class ChronosGlobalPermissions(ExtensibleModel): 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")), ) diff --git a/aleksis/apps/chronos/rules.py b/aleksis/apps/chronos/rules.py index 7ee9d1e0dcfb7b937b24ef82bb1adf36fa10dc46..a31b3ca12fcff4a0289505f8d83d973061c38eb6 100644 --- a/aleksis/apps/chronos/rules.py +++ b/aleksis/apps/chronos/rules.py @@ -1,5 +1,6 @@ from rules import add_perm +from aleksis.core.models import Group, Person from aleksis.core.util.predicates import ( has_any_object, has_global_perm, @@ -7,20 +8,22 @@ from aleksis.core.util.predicates import ( has_person, ) -from .models import LessonSubstitution +from .models import LessonSubstitution, Room from .util.predicates import 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_object("chronos.view_timetable", Person) + | has_any_object("chronos.view_timetable", Group) + | has_any_object("chronos.view_timetable", Room) +) 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 diff --git a/aleksis/apps/chronos/templates/chronos/all.html b/aleksis/apps/chronos/templates/chronos/all.html index bedf5ca129d781ff2f13e198714871bf2c4b7b1c..8104f0849fc9fa0caaf32a2213554efd0e02ad29 100644 --- a/aleksis/apps/chronos/templates/chronos/all.html +++ b/aleksis/apps/chronos/templates/chronos/all.html @@ -2,7 +2,7 @@ {% extends 'core/base.html' %} -{% load i18n static %} +{% load i18n static rules %} {% block extra_head %} <link rel="stylesheet" href="{% static 'css/chronos/timetable.css' %}"> @@ -17,10 +17,13 @@ <h5>{% trans "Teachers" %}</h5> {% for teacher in teachers %} - <a class="waves-effect waves-light btn btn-timetable-quicklaunch primary" - href="{% url 'timetable' 'teacher' teacher.pk %}"> - {{ teacher.short_name }} - </a> + {% has_perm "chronos.view_timetable" user teacher as has_teacher_perm %} + {% if has_teacher_perm %} + <a class="waves-effect waves-light btn btn-timetable-quicklaunch primary" + href="{% url 'timetable' 'teacher' teacher.pk %}"> + {{ teacher.short_name }} + </a> + {% endif %} {% endfor %} </div> @@ -28,10 +31,13 @@ <h5>{% trans "Groups" %}</h5> {% for class in classes %} - <a class="waves-effect waves-light btn btn-timetable-quicklaunch primary" - href="{% url 'timetable' 'group' class.pk %}"> - {{ class.short_name }} - </a> + {% has_perm "chronos.view_timetable" user class as has_class_perm %} + {% if has_class_perm %} + <a class="waves-effect waves-light btn btn-timetable-quicklaunch primary" + href="{% url 'timetable' 'group' class.pk %}"> + {{ class.short_name }} + </a> + {% endif %} {% endfor %} </div> @@ -39,10 +45,13 @@ <h5>{% trans "Rooms" %}</h5> {% for room in rooms %} - <a class="waves-effect waves-light btn btn-timetable-quicklaunch primary" - href="{% url 'timetable' 'room' room.pk %}"> - {{ room.short_name }} - </a> + {% has_perm "chronos.view_timetable" user room as has_room_perm %} + {% if has_room_perm %} + <a class="waves-effect waves-light btn btn-timetable-quicklaunch primary" + href="{% url 'timetable' 'room' room.pk %}"> + {{ room.short_name }} + </a> + {% endif %} {% endfor %} </div> </div> diff --git a/aleksis/apps/chronos/util/predicates.py b/aleksis/apps/chronos/util/predicates.py index cdbe5e2c268579558b0f29a184ed82709eda764a..bc9c8d92f419b605302a782a9617025a1437f1cc 100644 --- a/aleksis/apps/chronos/util/predicates.py +++ b/aleksis/apps/chronos/util/predicates.py @@ -5,16 +5,29 @@ 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 @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 + if type(obj) is Group: + return ( + user.person.member_of.filter(id=obj.id).exists() + or user.person.primary_group == obj + or user.person.owner_of.filter(id=obj.id).exists() + or has_global_perm("chronos.view_all_group_timetables")(user) + or has_object_perm("core.view_group_timetable")(user, obj) + ) + elif type(obj) is Person: + return ( + user.person == obj + or has_global_perm("chronos.view_all_person_timetables")(user) + or has_object_perm("core.view_person_timetable")(user, obj) + ) + elif type(obj) is Room: + return has_global_perm("chronos.view_all_room_timetables")(user) or has_object_perm( + "chronos.view_room_timetable" + )(user, obj) else: return False