From 8a375b6a7c2029d01393d64d7c274def39312e60 Mon Sep 17 00:00:00 2001
From: Jonathan Weth <git@jonathanweth.de>
Date: Fri, 17 Apr 2020 12:48:34 +0200
Subject: [PATCH 1/3] Add/update __str__ methods for models

---
 aleksis/apps/chronos/models.py | 70 +++++++++++++++++++++++++++++-----
 1 file changed, 60 insertions(+), 10 deletions(-)

diff --git a/aleksis/apps/chronos/models.py b/aleksis/apps/chronos/models.py
index 016204bc..c7689433 100644
--- a/aleksis/apps/chronos/models.py
+++ b/aleksis/apps/chronos/models.py
@@ -16,12 +16,14 @@ from django.http.request import QueryDict
 from django.urls import reverse
 from django.utils import timezone
 from django.utils.decorators import classproperty
+from django.utils.formats import date_format
 from django.utils.translation import gettext_lazy as _
 
 from calendarweek.django import CalendarWeek, i18n_day_names_lazy, i18n_day_abbrs_lazy
 from colorfield.fields import ColorField
 from django_global_request.middleware import get_request
 
+from aleksis.apps.chronos.util.format import format_m2m
 from aleksis.core.mixins import ExtensibleModel
 from aleksis.core.models import Group, Person, DashboardWidget
 
@@ -288,11 +290,9 @@ class TimePeriod(ExtensibleModel):
     time_end = models.TimeField(verbose_name=_("Time the period ends"))
 
     def __str__(self) -> str:
-        return "%s, %d. period (%s - %s)" % (
-            self.weekday,
+        return "{}, {}.".format(
+            self.get_weekday_display(),
             self.period,
-            self.time_start,
-            self.time_end,
         )
 
     @classmethod
@@ -407,7 +407,7 @@ class Subject(ExtensibleModel):
     )
 
     def __str__(self) -> str:
-        return "%s - %s" % (self.abbrev, self.name)
+        return "{} ({})".format(self.abbrev, self.name)
 
     class Meta:
         ordering = ["name", "abbrev"]
@@ -462,6 +462,13 @@ class Lesson(ExtensibleModel):
 
         return CalendarWeek(year=year, week=week)
 
+    def __str__(self):
+        return "{}, {}, {}".format(
+            format_m2m(self.groups),
+            self.subject.abbrev,
+            format_m2m(self.teachers),
+        )
+
     class Meta:
         ordering = ["date_start", "subject"]
         indexes = [models.Index(fields=["date_start", "date_end"])]
@@ -505,6 +512,14 @@ class LessonSubstitution(ExtensibleModel):
         else:
             return "substitution"
 
+    @property
+    def date(self):
+        week = CalendarWeek(week=self.week)
+        return week[self.lesson_period.period.weekday]
+
+    def __str__(self):
+        return "{}, {}".format(str(self.lesson_period), date_format(self.date))
+
     class Meta:
         unique_together = [["lesson_period", "week"]]
         ordering = [
@@ -567,11 +582,9 @@ class LessonPeriod(ExtensibleModel):
         return self.lesson.groups
 
     def __str__(self) -> str:
-        return "%s, %d., %s, %s" % (
-            self.period.get_weekday_display(),
-            self.period.period,
-            ", ".join(list(self.lesson.groups.values_list("short_name", flat=True))),
-            self.lesson.subject.name,
+        return "{}, {}".format(
+            str(self.period),
+            str(self.lesson)
         )
 
     class Meta:
@@ -651,6 +664,12 @@ class AbsenceReason(ExtensibleModel):
     short_name = models.CharField(verbose_name=_("Short name"), max_length=255)
     name = models.CharField(verbose_name=_("Name"), blank=True, null=True, max_length=255)
 
+    def __str__(self):
+        if self.name:
+            return "{} ({})".format(self.short_name, self.name)
+        else:
+            return self.short_name
+
     class Meta:
         verbose_name = _("Absence reason")
         verbose_name_plural = _("Absence reasons")
@@ -668,6 +687,16 @@ class Absence(ExtensibleModel):
     period_to = models.ForeignKey("TimePeriod", on_delete=models.CASCADE, verbose_name=_("Effective end period of absence"), null=True, related_name="+")
     comment = models.TextField(verbose_name=_("Comment"), blank=True, null=True)
 
+    def __str__(self):
+        if self.teacher:
+            return str(self.teacher)
+        elif self.group:
+            return str(self.group)
+        elif self.room:
+            return str(self.room)
+        else:
+            return _("Unknown absence")
+
     class Meta:
         ordering = ["date_start"]
         indexes = [models.Index(fields=["date_start", "date_end"])]
@@ -724,6 +753,9 @@ class Holiday(ExtensibleModel):
 
         return per_weekday
 
+    def __str__(self):
+        return self.title
+
     class Meta:
         ordering = ["date_start"]
         indexes = [models.Index(fields=["date_start", "date_end"])]
@@ -737,6 +769,9 @@ class SupervisionArea(ExtensibleModel):
     colour_fg = ColorField(default="#000000")
     colour_bg = ColorField()
 
+    def __str__(self):
+        return "{} ({})".format(self.name, self.short_name)
+
     class Meta:
         ordering = ["name"]
         verbose_name = _("Supervision area")
@@ -794,6 +829,9 @@ class Break(ExtensibleModel):
 
         return breaks
 
+    def __str__(self):
+        return "{} ({})".format(self.name, self.short_name)
+
     class Meta:
         ordering = ["after_period"]
         indexes = [models.Index(fields=["after_period", "before_period"])]
@@ -859,6 +897,9 @@ class Supervision(ExtensibleModel):
     def teachers(self):
         return [self.teacher]
 
+    def __str__(self):
+        return "{}, {}, {}".format(self.break_item, self.area, self.teacher)
+
     class Meta:
         ordering = ["area", "break_item"]
         verbose_name= _("Supervision")
@@ -874,6 +915,9 @@ class SupervisionSubstitution(ExtensibleModel):
     def teachers(self):
         return [self.teacher]
 
+    def __str__(self):
+        return "{}, {}".format(self.supervision, date_format(self.date))
+
     class Meta:
         ordering = ["date", "supervision"]
         verbose_name = _("Supervision substitution")
@@ -953,6 +997,12 @@ class Event(ExtensibleModel):
     rooms = models.ManyToManyField("Room", related_name="events", verbose_name=_("Rooms"))
     teachers = models.ManyToManyField("core.Person", related_name="events", verbose_name=_("Teachers"))
 
+    def __str__(self):
+        if self.title:
+            return self.title
+        else:
+            return _("Event {}".format(self.pk))
+
     class Meta:
         ordering = ["date_start"]
         indexes = [models.Index(fields=["period_from", "period_to", "date_start", "date_end"])]
-- 
GitLab


From e01c9671ef84cc63335c21b2f4395ac89d2e64a0 Mon Sep 17 00:00:00 2001
From: Jonathan Weth <git@jonathanweth.de>
Date: Fri, 17 Apr 2020 12:49:44 +0200
Subject: [PATCH 2/3] Add missing verbose names to models

---
 aleksis/apps/chronos/models.py | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/aleksis/apps/chronos/models.py b/aleksis/apps/chronos/models.py
index c7689433..e023bb05 100644
--- a/aleksis/apps/chronos/models.py
+++ b/aleksis/apps/chronos/models.py
@@ -385,6 +385,8 @@ class TimePeriod(ExtensibleModel):
         unique_together = [["weekday", "period"]]
         ordering = ["weekday", "period"]
         indexes = [models.Index(fields=["time_start", "time_end"])]
+        verbose_name = _("Time period")
+        verbose_name_plural = _("Time periods")
 
 
 class Subject(ExtensibleModel):
@@ -472,6 +474,8 @@ class Lesson(ExtensibleModel):
     class Meta:
         ordering = ["date_start", "subject"]
         indexes = [models.Index(fields=["date_start", "date_end"])]
+        verbose_name = _("Lesson")
+        verbose_name_plural = _("Lessons")
 
 
 class LessonSubstitution(ExtensibleModel):
@@ -534,6 +538,8 @@ class LessonSubstitution(ExtensibleModel):
                 name="either_substituted_or_cancelled",
             )
         ]
+        verbose_name = _("Lesson substitution")
+        verbose_name_plural = _("Lesson substitutions")
 
 
 class LessonPeriod(ExtensibleModel):
@@ -590,6 +596,8 @@ class LessonPeriod(ExtensibleModel):
     class Meta:
         ordering = ["lesson__date_start", "period__weekday", "period__period", "lesson__subject"]
         indexes = [models.Index(fields=["lesson", "period"])]
+        verbose_name = _("Lesson period")
+        verbose_name_plural = _("Lesson periods")
 
 
 class TimetableWidget(DashboardWidget):
-- 
GitLab


From 05ee82fd7bbf7881df78a6f04dfa469c569295dc Mon Sep 17 00:00:00 2001
From: Jonathan Weth <git@jonathanweth.de>
Date: Fri, 17 Apr 2020 12:50:52 +0200
Subject: [PATCH 3/3] Include models in Django admin

---
 aleksis/apps/chronos/admin.py       | 186 +++++++++++++++++++++++++++-
 aleksis/apps/chronos/util/format.py |   9 ++
 2 files changed, 193 insertions(+), 2 deletions(-)
 create mode 100644 aleksis/apps/chronos/util/format.py

diff --git a/aleksis/apps/chronos/admin.py b/aleksis/apps/chronos/admin.py
index b43af26c..816f9d16 100644
--- a/aleksis/apps/chronos/admin.py
+++ b/aleksis/apps/chronos/admin.py
@@ -1,5 +1,187 @@
 from django.contrib import admin
+from django.utils.html import format_html
 
-from .models import TimetableWidget
+from .models import (
+    TimetableWidget,
+    Lesson,
+    LessonSubstitution,
+    SupervisionSubstitution,
+    LessonPeriod,
+    Absence,
+    Event,
+    Holiday,
+    Supervision,
+    Subject,
+    SupervisionArea,
+    Room,
+    AbsenceReason,
+    Break,
+    TimePeriod,
+)
+from .util.format import format_date_period, format_m2m
 
-admin.site.register(TimetableWidget)
+
+def colour_badge(fg: str, bg: str, val: str):
+    html = """
+    <div style="
+        color: {};
+        background-color: {};
+        padding-top: 3px;
+        padding-bottom: 4px;
+        text-align: center;
+        border-radius: 3px;
+    ">{}</span>
+    """
+    return format_html(html, fg, bg, val)
+
+
+class AbsenceReasonAdmin(admin.ModelAdmin):
+    list_display = ("short_name", "name")
+    list_display_links = ("short_name", "name")
+
+
+admin.site.register(AbsenceReason, AbsenceReasonAdmin)
+
+
+class AbsenceAdmin(admin.ModelAdmin):
+    def start(self, obj):
+        return format_date_period(obj.date_start, obj.period_from)
+
+    def end(self, obj):
+        return format_date_period(obj.date_end, obj.period_to)
+
+    list_display = ("__str__", "reason", "start", "end")
+
+
+admin.site.register(Absence, AbsenceAdmin)
+
+
+class SupervisionInline(admin.TabularInline):
+    model = Supervision
+
+
+class BreakAdmin(admin.ModelAdmin):
+    list_display = ("__str__", "after_period", "before_period")
+    inlines = [SupervisionInline]
+
+
+admin.site.register(Break, BreakAdmin)
+
+
+class EventAdmin(admin.ModelAdmin):
+    def start(self, obj):
+        return format_date_period(obj.date_start, obj.period_from)
+
+    def end(self, obj):
+        return format_date_period(obj.date_end, obj.period_to)
+
+    def _groups(self, obj):
+        return format_m2m(obj.groups)
+
+    def _teachers(self, obj):
+        return format_m2m(obj.teachers)
+
+    def _rooms(self, obj):
+        return format_m2m(obj.rooms)
+
+    filter_horizontal = ("groups", "teachers", "rooms")
+    list_display = ("__str__", "_groups", "_teachers", "_rooms", "start", "end")
+
+
+admin.site.register(Event, EventAdmin)
+
+
+class HolidayAdmin(admin.ModelAdmin):
+    list_display = ("title", "date_start", "date_end")
+
+
+admin.site.register(Holiday, HolidayAdmin)
+
+
+class LessonPeriodInline(admin.TabularInline):
+    model = LessonPeriod
+
+
+class LessonSubstitutionAdmin(admin.ModelAdmin):
+    list_display = ("lesson_period", "week", "date")
+    list_display_links = ("lesson_period", "week", "date")
+    filter_horizontal = ("teachers",)
+
+
+admin.site.register(LessonSubstitution, LessonSubstitutionAdmin)
+
+
+class LessonAdmin(admin.ModelAdmin):
+    def _groups(self, obj):
+        return format_m2m(obj.groups)
+
+    def _teachers(self, obj):
+        return format_m2m(obj.teachers)
+
+    filter_horizontal = ["teachers", "groups"]
+    inlines = [LessonPeriodInline]
+    list_filter = ("subject", "groups", "groups__parent_groups", "teachers")
+    list_display = ("_groups", "subject", "_teachers")
+
+
+admin.site.register(Lesson, LessonAdmin)
+
+
+class RoomAdmin(admin.ModelAdmin):
+    list_display = ("short_name", "name")
+    list_display_links = ("short_name", "name")
+
+
+admin.site.register(Room, RoomAdmin)
+
+
+class SubjectAdmin(admin.ModelAdmin):
+    def _colour(self, obj):
+        return colour_badge(obj.colour_fg, obj.colour_bg, obj.abbrev,)
+
+    list_display = ("abbrev", "name", "_colour")
+    list_display_links = ("abbrev", "name")
+
+
+admin.site.register(Subject, SubjectAdmin)
+
+
+class SupervisionAreaAdmin(admin.ModelAdmin):
+    def _colour(self, obj):
+        return colour_badge(obj.colour_fg, obj.colour_bg, obj.short_name,)
+
+    list_display = ("short_name", "name", "_colour")
+    list_display_links = ("short_name", "name")
+    inlines = [SupervisionInline]
+
+
+admin.site.register(SupervisionArea, SupervisionAreaAdmin)
+
+
+class SupervisionSubstitutionAdmin(admin.ModelAdmin):
+    list_display = ("supervision", "date")
+
+
+admin.site.register(SupervisionSubstitution, SupervisionSubstitutionAdmin)
+
+
+class SupervisionAdmin(admin.ModelAdmin):
+    list_display = ("break_item", "area", "teacher")
+
+
+admin.site.register(Supervision, SupervisionAdmin)
+
+
+class TimePeriodAdmin(admin.ModelAdmin):
+    list_display = ("weekday", "period", "time_start", "time_end")
+    list_display_links = ("weekday", "period")
+
+
+admin.site.register(TimePeriod, TimePeriodAdmin)
+
+
+class TimetableWidgetAdmin(admin.ModelAdmin):
+    list_display = ("title", "active")
+
+
+admin.site.register(TimetableWidget, TimetableWidgetAdmin)
diff --git a/aleksis/apps/chronos/util/format.py b/aleksis/apps/chronos/util/format.py
new file mode 100644
index 00000000..c85668bf
--- /dev/null
+++ b/aleksis/apps/chronos/util/format.py
@@ -0,0 +1,9 @@
+from django.utils.formats import date_format
+
+
+def format_m2m(f, attr: str = "short_name"):
+    return ", ".join([getattr(x, attr) for x in f.all()])
+
+
+def format_date_period(date, period):
+    return "{}, {}.".format(date_format(date), period.period)
-- 
GitLab