# Copyright (C) 2017-2018 Alban Gruin # # celcatsanitizer is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # celcatsanitizer is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with celcatsanitizer. If not, see . from functools import reduce from django.db import models from django.db.models import Manager, Q from django.db.models.functions import ExtractWeek, ExtractYear from django.utils import timezone from django.utils.text import slugify from .utils import parse_group class SlugModel(models.Model): def save(self): if not self.slug: self.slug = slugify(self.name) super(SlugModel, self).save() class Meta: abstract = True class Year(SlugModel): name = models.CharField(max_length=16, verbose_name="année") slug = models.SlugField(max_length=16, unique=True, default="") def __str__(self): return self.name class Meta: verbose_name = "année" verbose_name_plural = "années" class Source(models.Model): url = models.URLField(max_length=255, verbose_name="URL", unique=True) last_update_date = models.DateTimeField(null=True, blank=True, verbose_name="dernière mise à jour" "Celcat") def __str__(self): return self.url @property def formatted_timetables(self): return ", ".join([str(timetable) for timetable in self.timetables.iterator()]) class Meta: verbose_name = "source d’emploi du temps" verbose_name_plural = "sources d’emploi du temps" class TimetableManager(Manager): def get_queryset(self): return super(Manager, self).get_queryset().select_related("year") class Timetable(SlugModel): objects = TimetableManager() year = models.ForeignKey(Year, on_delete=models.CASCADE, verbose_name="année") name = models.CharField(max_length=64, verbose_name="nom") slug = models.SlugField(max_length=64, default="") source = models.ForeignKey(Source, on_delete=models.CASCADE, verbose_name="source", related_name="timetables") def __str__(self): return self.year.name + " " + self.name class Meta: unique_together = (("year", "name"), ("year", "slug"),) verbose_name = "emploi du temps" verbose_name_plural = "emplois du temps" class GroupManager(Manager): def get_parents(self, group): groups_criteria = reduce(lambda x, y: x | y, [Q(subgroup=group.subgroup[:i]) for i in range(1, len(group.subgroup) + 1)], Q(subgroup="")) return self.get_queryset().filter(groups_criteria, Q(semester=None) | Q(semester=group.semester), mention=group.mention, source=group.source) class Group(SlugModel): objects = GroupManager() name = models.CharField(max_length=255, verbose_name="nom") celcat_name = models.CharField(max_length=255, verbose_name="nom dans Celcat") source = models.ForeignKey(Source, on_delete=models.CASCADE, verbose_name="source d’emploi du temps") mention = models.CharField(max_length=128) semester = models.IntegerField(verbose_name="semestre", null=True) subgroup = models.CharField(max_length=16, verbose_name="sous-groupe", default="") slug = models.SlugField(max_length=64, default="") hidden = models.BooleanField(verbose_name="caché", default=False) def corresponds_to(self, mention, semester, subgroup): subgroup_corresponds = True if self.subgroup is not None and subgroup is not None: subgroup_corresponds = self.subgroup.startswith(subgroup) return (self.mention.startswith(mention) or mention.startswith(self.mention)) and \ (self.semester == semester or semester is None) and \ subgroup_corresponds @property def group_info(self): return self.mention, self.semester, self.subgroup def __str__(self): return self.name def save(self, *args, **kwargs): if self.name == "": self.name = self.celcat_name self.mention, self.semester, self.subgroup = parse_group(self.name) if self.subgroup is None: self.subgroup = "" super(Group, self).save() class Meta: index_together = ("mention", "semester", "subgroup",) unique_together = (("name", "source",), ("celcat_name", "source",), ("slug", "source",),) verbose_name = "groupe" verbose_name_plural = "groupes" class Room(SlugModel): name = models.CharField(max_length=255, unique=True, verbose_name="nom") slug = models.SlugField(max_length=64, default="", unique=True) def __str__(self): return self.name class Meta: verbose_name = "salle" verbose_name_plural = "salles" class CourseManager(Manager): def get_courses(self, obj, **criteria): qs = self.get_queryset() if isinstance(obj, Group): qs = qs.filter(groups__in=Group.objects.get_parents(obj), **criteria).prefetch_related("rooms") elif isinstance(obj, Room): qs = qs.filter(rooms__in=(obj,), **criteria) else: raise(TypeError, "obj must be a Group or a Room") return qs.order_by("begin") def get_weeks(self, **criteria): return self.get_queryset() \ .filter(**criteria) \ .order_by("groups__name", "year", "week") \ .annotate(year=ExtractYear("begin"), week=ExtractWeek("begin")) \ .values("groups__mention", "groups__semester", "groups__subgroup", "year", "week") class Course(models.Model): objects = CourseManager() name = models.CharField(max_length=255, verbose_name="nom", default="Sans nom") type_ = models.CharField(name="type", max_length=255, verbose_name="type de cours", null=True) source = models.ForeignKey(Source, on_delete=models.CASCADE, verbose_name="emploi du temps") notes = models.TextField(verbose_name="remarques", blank=True, null=True) groups = models.ManyToManyField(Group, verbose_name="groupes") rooms = models.ManyToManyField(Room, verbose_name="salles") begin = models.DateTimeField(verbose_name="début du cours", db_index=True) end = models.DateTimeField(verbose_name="fin du cours") last_update = models.DateTimeField(verbose_name="dernière mise à jour", default=timezone.now) def __str__(self): return self.name def save(self, *args, **kwargs): if self.type is not None: self.type = self.type.replace("COURS", "cours") self.type = self.type.replace("REUNION", "réunion") if self.name is not None: self.name = self.name.split("(")[0].strip() super(Course, self).save(*args, **kwargs) class Meta: verbose_name = "cours" verbose_name_plural = "cours"