Skip to content
Snippets Groups Projects
Verified Commit 0e1ffed5 authored by Jonathan Weth's avatar Jonathan Weth :keyboard:
Browse files

Merge branch 'master' into 211-implement-vue-substitution-frontend

parents 40edcdd0 ccbc6ee0
No related branches found
No related tags found
1 merge request!310Resolve "Implement Vue substitution frontend"
......@@ -9,6 +9,12 @@ and this project adheres to `Semantic Versioning`_.
Unreleased
----------
Added
~~~~~
* New timetable interface based on calendar system.
* [Dev] LessonEvent and SupervisionEvent basing on calendar system.
`3.0.2`_ - 2023-09-10
---------------------
......
......@@ -5,7 +5,7 @@ export default {
name: "SelectTimetable",
props: {
value: {
type: String,
type: Object,
required: false,
default: null,
},
......
......@@ -816,7 +816,7 @@ class GroupPropertiesMixin:
return sep.join([group.short_name for group in self.get_groups()])
@property
def groups_to_show(self) -> models.QuerySet:
def groups_to_show(self) -> QuerySet[Group]:
groups = self.get_groups()
if (
groups.count() == 1
......@@ -869,13 +869,15 @@ class RoomPropertiesMixin:
class LessonEventQuerySet(PolymorphicQuerySet):
"""Queryset with special query methods for lesson events."""
def for_teacher(self, teacher: Union[int, Person]):
def for_teacher(self, teacher: Union[int, Person]) -> "LessonEventQuerySet":
"""Get all lesson events for a certain person as teacher (including amends)."""
amended = self.filter(Q(amended_by__isnull=False) & (Q(teachers=teacher))).values_list(
"amended_by__pk", flat=True
)
return self.filter(Q(teachers=teacher) | Q(pk__in=amended)).distinct()
def for_group(self, group: Union[int, Group]):
def for_group(self, group: Union[int, Group]) -> "LessonEventQuerySet":
"""Get all lesson events for a certain group (including amends/as parent group)."""
amended = self.filter(
Q(amended_by__isnull=False) & (Q(groups=group) | Q(groups__parent_groups=group))
).values_list("amended_by__pk", flat=True)
......@@ -883,19 +885,22 @@ class LessonEventQuerySet(PolymorphicQuerySet):
Q(groups=group) | Q(groups__parent_groups=group) | Q(pk__in=amended)
).distinct()
def for_room(self, room: Union[int, Room]):
def for_room(self, room: Union[int, Room]) -> "LessonEventQuerySet":
"""Get all lesson events for a certain room (including amends)."""
amended = self.filter(Q(amended_by__isnull=False) & (Q(rooms=room))).values_list(
"amended_by__pk", flat=True
)
return self.filter(Q(rooms=room) | Q(pk__in=amended)).distinct()
def for_course(self, course: Union[int, Course]):
def for_course(self, course: Union[int, Course]) -> "LessonEventQuerySet":
"""Get all lesson events for a certain course (including amends)."""
amended = self.filter(Q(amended_by__isnull=False) & (Q(course=course))).values_list(
"amended_by__pk", flat=True
)
return self.filter(Q(course=course) | Q(pk__in=amended)).distinct()
def for_person(self, person: Union[int, Person]):
def for_person(self, person: Union[int, Person]) -> "LessonEventQuerySet":
"""Get all lesson events for a certain person (as teacher/participant, including amends)."""
amended = self.filter(
Q(amended_by__isnull=False) & (Q(teachers=person) | Q(groups__members=person))
).values_list("amended_by__pk", flat=True)
......@@ -903,17 +908,34 @@ class LessonEventQuerySet(PolymorphicQuerySet):
Q(teachers=person) | Q(groups__members=person) | Q(pk__in=amended)
).distinct()
def related_to_person(self, person: Union[int, Person]):
def related_to_person(self, person: Union[int, Person]) -> "LessonEventQuerySet":
"""Get all lesson events a certain person is allowed to see.
This includes all lesson events the person is assigned to as
teacher/participant/group owner/parent group owner,
including those amended.
"""
amended = self.filter(
Q(amended_by__isnull=False)
& (Q(teachers=person) | Q(groups__members=person) | Q(groups__owners=person))
& (
Q(teachers=person)
| Q(groups__members=person)
| Q(groups__owners=person)
| Q(groups__parent_groups__owners=person)
)
).values_list("amended_by__pk", flat=True)
return self.filter(
Q(teachers=person) | Q(groups__members=person) | Q(groups__owners=person)
Q(teachers=person)
| Q(groups__members=person)
| Q(groups__owners=person)
| Q(groups__parent_groups__owners=person)
| Q(pk__in=amended)
).distinct()
def not_amended(self):
def not_amended(self) -> "LessonEventQuerySet":
"""Get all lesson events that are not amended."""
return self.filter(amended_by__isnull=True)
def not_amending(self):
def not_amending(self) -> "LessonEventQuerySet":
"""Get all lesson events that are not amending other events."""
return self.filter(amends__isnull=True)
......@@ -11,10 +11,11 @@ from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied, ValidationError
from django.core.validators import MinValueValidator
from django.db import models
from django.db.models import Max, Min, Q
from django.db.models import Max, Min, Q, QuerySet
from django.db.models.functions import Coalesce
from django.dispatch import receiver
from django.forms import Media
from django.http import HttpRequest
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils import timezone
......@@ -1324,6 +1325,8 @@ class ChronosGlobalPermissions(GlobalPermissionModel):
class LessonEvent(CalendarEvent):
"""Calendar feed for lessons."""
name = "lesson"
verbose_name = _("Lessons")
......@@ -1374,36 +1377,43 @@ class LessonEvent(CalendarEvent):
)
@property
def actual_groups(self: LessonEvent):
return self.groups.all() if self.amends else self.real_amends.groups.all()
def actual_groups(self: LessonEvent) -> QuerySet[Group]:
"""Get list of the groups of this lesson event."""
return self.amends.groups.all() if self.amends else self.groups.all()
@property
def all_members(self: LessonEvent) -> list[Person]:
"""Get list of all group members for this lesson event."""
return list(itertools.chain(*[list(g.members.all()) for g in self.actual_groups]))
@property
def all_teachers(self: LessonEvent) -> list[Person]:
"""Get list of all teachers for this lesson event."""
all_teachers = list(self.teachers.all())
if self.amends:
all_teachers += list(self.real_amends.teachers.all())
all_teachers += list(self.amends.teachers.all())
return all_teachers
@property
def group_names(self: LessonEvent) -> str:
"""Get comma-separated string with all group names."""
return ", ".join([g.name for g in self.actual_groups])
@property
def teacher_names(self: LessonEvent) -> str:
"""Get comma-separated string with all teacher names."""
return ", ".join([t.full_name for t in self.teachers.all()])
@property
def room_names(self: LessonEvent) -> str:
"""Get comma-separated string with all room names."""
return ", ".join([r.name for r in self.rooms.all()])
@property
def room_names_with_amends(self: LessonEvent) -> str:
"""Get comma-separated string with all room names (including amends)."""
my_room_names = self.room_names
amended_room_names = self.real_amends.room_names if self.amends else ""
amended_room_names = self.amends.room_names if self.amends else ""
if my_room_names and amended_room_names:
return _("{} (instead of {})").format(my_room_names, amended_room_names)
......@@ -1413,8 +1423,9 @@ class LessonEvent(CalendarEvent):
@property
def teacher_names_with_amends(self: LessonEvent) -> str:
"""Get comma-separated string with all teacher names (including amends)."""
my_teacher_names = self.teacher_names
amended_teacher_names = self.real_amends.teacher_names if self.amends else ""
amended_teacher_names = self.amends.teacher_names if self.amends else ""
if my_teacher_names and amended_teacher_names:
return _("{} (instead of {})").format(my_teacher_names, amended_teacher_names)
......@@ -1424,8 +1435,9 @@ class LessonEvent(CalendarEvent):
@property
def subject_name_with_amends(self: LessonEvent) -> str:
"""Get formatted subject name (including amends)."""
my_subject = self.subject.name if self.subject else ""
amended_subject = self.real_amends.subject.name if self.amends else ""
amended_subject = self.amends.subject.name if self.amends and self.amends.subject else ""
if my_subject and amended_subject:
return _("{} (instead of {})").format(my_subject, amended_subject)
......@@ -1435,26 +1447,21 @@ class LessonEvent(CalendarEvent):
return my_subject
return _("Lesson")
@property
def real_amends(self: LessonEvent) -> LessonEvent:
# FIXME THIS IS AWFUL SLOW
if self.amends:
return LessonEvent.objects.get(pk=self.amends.pk)
return self
@classmethod
def value_title(cls, reference_object: LessonEvent, request) -> str:
"""Get the title of the event."""
def value_title(cls, reference_object: LessonEvent, request: HttpRequest | None = None) -> str:
"""Get the title of the lesson event."""
if reference_object.title:
return reference_object.title
elif reference_object.subject or (
reference_object.amends and reference_object.real_amends.subject
reference_object.amends and reference_object.amends.subject
):
title = reference_object.subject_name_with_amends
if request.user.person in reference_object.teachers.all():
if request and request.user.person in reference_object.teachers.all():
title += " · " + reference_object.group_names
else:
elif request:
title += " · " + reference_object.teacher_names_with_amends
else:
title += f" · {reference_object.group_names} · {reference_object.teacher_names_with_amends}"
if reference_object.rooms.all().exists():
title += " · " + reference_object.room_names_with_amends
return title
......@@ -1462,47 +1469,59 @@ class LessonEvent(CalendarEvent):
return _("Lesson")
@classmethod
def value_description(cls, reference_object: LessonEvent, request) -> str:
def value_description(
cls, reference_object: LessonEvent, request: HttpRequest | None = None
) -> str:
"""Get the description of the lesson event."""
return render_to_string("chronos/lesson_event_description.txt", {"event": reference_object})
@classmethod
def value_color(cls, reference_object: LessonEvent, request) -> str:
"""Get the color of the event."""
def value_color(cls, reference_object: LessonEvent, request: HttpRequest | None = None) -> str:
"""Get the color of the lesson event."""
if reference_object.cancelled:
return "#eeeeee"
if reference_object.subject:
return reference_object.subject.colour_bg
if reference_object.amends and reference_object.real_amends.subject:
return reference_object.real_amends.subject.colour_bg
if reference_object.amends and reference_object.amends.subject:
return reference_object.amends.subject.colour_bg
return super().value_color(reference_object, request)
@classmethod
def value_attendee(cls, reference_object: LessonEvent, request) -> str:
"""Get the attendees of the event."""
def value_attendee(
cls, reference_object: LessonEvent, request: HttpRequest | None = None
) -> str:
"""Get the attendees of the lesson event."""
# Only teachers due to privacy concerns
attendees = [t.get_vcal_address(role="CHAIR") for t in reference_object.teachers.all()]
return [a for a in attendees if a]
@classmethod
def value_location(cls, reference_object: LessonEvent, request) -> str:
"""Get the location of the event."""
return ", ".join([r.name for r in reference_object.rooms.all()])
def value_location(
cls, reference_object: LessonEvent, request: HttpRequest | None = None
) -> str:
"""Get the location of the lesson event."""
return reference_object.room_names_with_amends
@classmethod
def value_status(cls, reference_object: LessonEvent, request) -> str:
"""Get the status of the event."""
def value_status(cls, reference_object: LessonEvent, request: HttpRequest | None = None) -> str:
"""Get the status of the lesson event."""
if reference_object.cancelled:
return "CANCELLED"
return "CONFIRMED"
@classmethod
def value_meta(cls, reference_object: LessonEvent, request) -> str:
"""Get the meta of the event."""
real_amends = reference_object.real_amends
def value_meta(cls, reference_object: LessonEvent, request: HttpRequest | None = None) -> str:
"""Get the meta of the lesson event.
These information will be primarly used in our own calendar frontend.
"""
return {
"id": reference_object.id,
"amended": bool(reference_object.amends),
"amends": cls.value_meta(real_amends, request) if reference_object.amends else None,
"amends": cls.value_meta(reference_object.amends, request)
if reference_object.amends
else None,
"teachers": [
{
"id": t.pk,
......@@ -1513,12 +1532,12 @@ class LessonEvent(CalendarEvent):
}
for t in reference_object.teachers.all()
],
"is_teacher": request.user.person in reference_object.all_teachers,
"is_teacher": request.user.person in reference_object.all_teachers if request else None,
"groups": [
{"id": g.pk, "name": g.name, "short_name": g.short_name}
for g in reference_object.actual_groups
],
"is_member": request.user.person in reference_object.all_members,
"is_member": request.user.person in reference_object.all_members if request else None,
"rooms": [
{"id": r.pk, "name": r.name, "short_name": r.short_name}
for r in reference_object.rooms.all()
......@@ -1537,11 +1556,13 @@ class LessonEvent(CalendarEvent):
}
@classmethod
def get_objects(cls, request, params=None) -> Iterable:
def get_objects(
cls, request: HttpRequest | None = None, params: dict[str, any] | None = None
) -> Iterable:
"""Return all objects that should be included in the calendar."""
objs = super().get_objects(request, params).not_instance_of(SupervisionEvent)
if not has_person(request.user):
if request and not has_person(request.user):
raise PermissionDenied()
if params:
......@@ -1557,7 +1578,7 @@ class LessonEvent(CalendarEvent):
if not_amending:
objs = objs.not_amending()
if "own" in params:
if request and "own" in params:
if own:
objs = objs.for_person(request.user.person)
else:
......@@ -1572,9 +1593,9 @@ class LessonEvent(CalendarEvent):
return objs.for_room(obj_id)
elif type_ == "COURSE":
return objs.for_course(obj_id)
elif "own" in params:
return objs
return objs.for_person(request.user.person)
if request:
return objs.for_person(request.user.person)
return objs
class Meta:
verbose_name = _("Lesson Event")
......@@ -1582,25 +1603,31 @@ class LessonEvent(CalendarEvent):
class SupervisionEvent(LessonEvent):
"""Calendar feed for supervisions."""
name = "supervision"
verbose_name = _("Supervisions")
objects = PolymorphicBaseManager.from_queryset(LessonEventQuerySet)()
@classmethod
def value_title(cls, reference_object: LessonEvent, request) -> str:
def value_title(cls, reference_object: LessonEvent, request: HttpRequest | None = None) -> str:
"""Get the title of the event."""
return _("Supervision: {}").format(reference_object.room_names)
@classmethod
def value_description(cls, reference_object: LessonEvent, request) -> str:
def value_description(
cls, reference_object: LessonEvent, request: HttpRequest | None = None
) -> str:
return render_to_string(
"chronos/supervision_event_description.txt", {"event": reference_object}
)
@classmethod
def get_objects(cls, request, params=None) -> Iterable:
def get_objects(
cls, request: HttpRequest | None = None, params: dict[str, any] | None = None
) -> Iterable:
"""Return all objects that should be included in the calendar."""
objs = cls.objects.instance_of(cls)
if params:
......@@ -1614,4 +1641,6 @@ class SupervisionEvent(LessonEvent):
return objs.for_group(obj_id)
elif type_ == "ROOM":
return objs.for_room(obj_id)
return objs.for_person(request.user.person)
if request:
return objs.for_person(request.user.person)
return objs
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