Skip to content
Snippets Groups Projects
models.py 6.81 KiB
Newer Older
from datetime import datetime
from typing import Dict, Optional, Tuple
from django.core import validators
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import Q
from django.utils.translation import ugettext_lazy as _

from biscuit.core.mixins import SchoolRelated

from .util import current_week

class TimePeriod(SchoolRelated):
    WEEKDAY_CHOICES = [
        (0, _('Sunday')),
        (1, _('Monday')),
        (2, _('Tuesday')),
        (3, _('Wednesday')),
        (4, _('Thursday')),
        (5, _('Friday')),
        (6, _('Saturday'))
    ]

    weekday = models.PositiveSmallIntegerField(verbose_name=_(
        'Week day'), choices=WEEKDAY_CHOICES)
    period = models.PositiveSmallIntegerField(
        verbose_name=_('Number of period'))
    time_start = models.TimeField(verbose_name=_('Time the period starts'))
    time_end = models.TimeField(verbose_name=_('Time the period ends'))

Nik | Klampfradler's avatar
Nik | Klampfradler committed
    def __str__(self) -> str:
        return '%s, %d. period (%s - %s)' % (self.weekday, self.period, self.time_start, self.time_end)
    def get_times_dict(cls) -> Dict[int, Tuple[datetime, datetime]]:
        periods = {}
        for period in cls.objects.all():
            periods[period.period] = (period.time_start, period.time_end)

        return periods

    class Meta:
        unique_together = [['school', 'weekday', 'period']]
        ordering = ['weekday', 'period']
        indexes = [models.Index(fields=['time_start', 'time_end'])]
class Subject(SchoolRelated):
    abbrev = models.CharField(verbose_name=_(
        'Abbreviation of subject in timetable'), max_length=10)
    name = models.CharField(verbose_name=_(
        'Long name of subject'), max_length=30)

    colour_fg = models.CharField(verbose_name=_('Foreground colour in timetable'), blank=True, validators=[
                                 validators.RegexValidator(r'#[0-9A-F]{6}')], max_length=7)
    colour_bg = models.CharField(verbose_name=_('Background colour in timetable'), blank=True, validators=[
                                 validators.RegexValidator(r'#[0-9A-F]{6}')], max_length=7)
    def __str__(self) -> str:
        return '%s - %s' % (self.abbrev, self.name)
        ordering = ['name', 'abbrev']
        unique_together = [['school', 'abbrev'], ['school', 'name']]
class Room(SchoolRelated):
    short_name = models.CharField(verbose_name=_(
        'Short name, e.g. room number'), max_length=10)
    name = models.CharField(verbose_name=_('Long name'),
    def __str__(self) -> str:
        return '%s (%s)' % (self.name, self.short_name)

    class Meta:
        ordering = ['name', 'short_name']
        unique_together = [['school', 'short_name'], ['school', 'name']]
class Lesson(SchoolRelated):
    subject = models.ForeignKey(
        'Subject', on_delete=models.CASCADE, related_name='lessons')
    teachers = models.ManyToManyField('core.Person', related_name='lessons')
    periods = models.ManyToManyField(
        'TimePeriod', related_name='lessons', through='LessonPeriod')
    groups = models.ManyToManyField('core.Group', related_name='lessons')

    date_start = models.DateField(verbose_name=_(
        'Effective start date of lesson'), null=True)
    date_end = models.DateField(verbose_name=_(
        'Effective end date of lesson'), null=True)
    @property
    def teacher_names(self, sep: Optional[str] = ', ') -> str:
        return sep.join([teacher.full_name for teacher in self.teachers.all()])

    @property
    def group_names(self, sep: Optional[str] = ', ') -> str:
        return sep.join([group.short_name for group in self.groups.all()])
    class Meta:
        ordering = ['date_start']
        indexes = [models.Index(fields=['date_start', 'date_end'])]
class LessonSubstitution(SchoolRelated):
    week = models.IntegerField(verbose_name=_('Week'),
Nik | Klampfradler's avatar
Nik | Klampfradler committed
                               default=current_week)

    lesson_period = models.ForeignKey(
        'LessonPeriod', models.CASCADE, 'substitutions')

    subject = models.ForeignKey(
        'Subject', on_delete=models.CASCADE,
        related_name='lesson_substitutions', null=True, blank=True)
Nik | Klampfradler's avatar
Nik | Klampfradler committed
    teachers = models.ManyToManyField('core.Person',
                                      related_name='lesson_substitutions', blank=True, null=True)
    room = models.ForeignKey('Room', models.CASCADE, null=True, blank=True)
    cancelled = models.BooleanField(default=False)
    def clean(self) -> None:
        if self.subject and self.cancelled:
            raise ValidationError(_('Lessons can only be either substituted or cancelled.'))

        unique_together = [['school', 'lesson_period', 'week']]
        ordering = ['lesson_period__lesson__date_start', 'week',
                    'lesson_period__period__weekday', 'lesson_period__period__period']
        constraints = [
            models.CheckConstraint(
                check=~Q(cancelled=True, subject__isnull=False),
                name='either_substituted_or_cancelled'
            )
        ]
class LessonPeriod(SchoolRelated):
    lesson = models.ForeignKey('Lesson', models.CASCADE, related_name='lesson_periods')
    period = models.ForeignKey('TimePeriod', models.CASCADE, related_name='lesson_periods')
    room = models.ForeignKey('Room', models.CASCADE, null=True, related_name='lesson_periods')
    def get_substitution(self, week: Optional[int] = None) -> LessonSubstitution:
        wanted_week = week or getattr(self, '_week', None) or current_week()

        # We iterate over all substitutions because this can make use of
        # prefetching when this model is loaded from outside, in contrast
        # to .filter()
        for substitution in self.substitutions.all():
mirabilos's avatar
mirabilos committed
            if substitution.week == wanted_week:
    def get_subject(self) -> Optional[Subject]:
        if self.get_substitution():
            return self.get_substitution().subject
    def get_teachers(self) -> models.query.QuerySet:
        if self.get_substitution():
            return self.get_substitution().teachers
    def get_room(self) -> Optional[Room]:
        if self.get_substitution():
            return self.get_substitution().room
    def get_groups(self) -> models.query.QuerySet:
    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)
    class Meta:
        ordering = ['lesson__date_start', 'period__weekday', 'period__period']
        indexes = [models.Index(fields=['lesson', 'period'])]