diff options
Diffstat (limited to 'models.py')
-rw-r--r-- | models.py | 144 |
1 files changed, 101 insertions, 43 deletions
@@ -1,4 +1,4 @@ -# Copyright (C) 2017 Alban Gruin +# 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 @@ -16,7 +16,7 @@ from functools import reduce from django.db import models -from django.db.models import Count, Manager, Q +from django.db.models import Count, Manager, OuterRef, Q, Subquery from django.db.models.functions import ExtractWeek, ExtractYear from django.utils import timezone from django.utils.text import slugify @@ -25,12 +25,11 @@ from .utils import parse_group class SlugModel(models.Model): - def save(self): + def save(self, *args, **kwargs): if not self.slug: self.slug = slugify(self.name) - super(SlugModel, self).save() - + super(SlugModel, self).save(*args, **kwargs) class Meta: abstract = True @@ -43,26 +42,49 @@ class Year(SlugModel): 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.all()]) + + 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") - url = models.URLField(max_length=255, verbose_name="URL") slug = models.SlugField(max_length=64, default="") - - last_update_date = models.DateTimeField(verbose_name="dernière mise à jour Celcat", - null=True, blank=True) + 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" @@ -71,31 +93,40 @@ class Timetable(SlugModel): class GroupManager(Manager): def get_parents(self, group): - groups_criteria = Q(subgroup="") - - if len(group.subgroup) != 0: - groups_criteria |= reduce(lambda x, y: x | y, - [Q(subgroup=group.subgroup[:i]) - for i in range(1, len(group.subgroup) + 1)]) + 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), + Q(semester=None) | + Q(semester=group.semester), mention=group.mention, - timetable=group.timetable) + source=group.source) + def get_relevant_groups(self, start, **criteria): + courses = Course.objects.filter(groups=OuterRef("pk"), + begin__gte=start) \ + .only("pk")[:1] + return self.get_queryset() \ + .annotate(c=Subquery(courses, + output_field=models.IntegerField())) \ + .filter(c__isnull=False, **criteria).order_by("name") -class Group(models.Model): + +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") - timetable = models.ForeignKey(Timetable, on_delete=models.CASCADE, - verbose_name="emploi du temps") + 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="") + subgroup = models.CharField(max_length=16, verbose_name="sous-groupe", + default="") slug = models.SlugField(max_length=64, default="") @@ -106,10 +137,10 @@ class Group(models.Model): if self.subgroup is not None and subgroup is not None: subgroup_corresponds = self.subgroup.startswith(subgroup) - return (self.mention.startswith(mention) or \ + return (self.mention.startswith(mention) or mention.startswith(self.mention)) and \ - (self.semester == semester or semester is None) and \ - subgroup_corresponds + (self.semester == semester or semester is None) and \ + subgroup_corresponds @property def group_info(self): @@ -121,42 +152,68 @@ class Group(models.Model): def save(self, *args, **kwargs): if self.name == "": self.name = self.celcat_name - self.slug = slugify(self.name) self.mention, self.semester, self.subgroup = parse_group(self.name) if self.subgroup is None: self.subgroup = "" - super(Group, self).save() - + super(Group, self).save(*args, **kwargs) class Meta: index_together = ("mention", "semester", "subgroup",) - unique_together = (("name", "timetable",), - ("celcat_name", "timetable",), - ("slug", "timetable",),) + unique_together = (("name", "source",), + ("celcat_name", "source",), + ("slug", "source",),) verbose_name = "groupe" verbose_name_plural = "groupes" -class Room(models.Model): +class RoomManager(Manager): + def qsjps(self, begin, end): + # On récupère la liste des cours qui commencent avant la fin + # de l’intervalle sélectionné et qui terminent après le début + # de l’intervalle, c’est-à-dire qu’au moins une partie du + # cours se déroule pendant l’intervalle. On récupère ensuite + # la liste des salles dans lesquelles se déroulent ces + # cours. On exclu les cours n’ayant aucune salle assignée. + courses = Course.objects.filter(begin__lt=end, end__gt=begin, + rooms__isnull=False) \ + .values_list("rooms", flat=True) + + # On sélectionne ensuite les salles qui ne sont pas dans la + # liste récupérée plus haut, et on les trie par leur nom. + return self.get_queryset().exclude(pk__in=courses).order_by("name") + + +class Room(SlugModel): + objects = RoomManager() + 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_for_group(self, group, **criteria): - return self.get_queryset() \ - .filter(groups__in=Group.objects.get_parents(group), **criteria) \ - .order_by("begin").prefetch_related("rooms") + 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=obj, **criteria) \ + .prefetch_related("rooms", "groups") + 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() \ @@ -165,17 +222,19 @@ class CourseManager(Manager): .annotate(year=ExtractYear("begin"), week=ExtractWeek("begin")) \ .values("groups__mention", "groups__semester", - "groups__subgroup", "year", "week") + "groups__subgroup", "year", "week") \ + .annotate(c=Count("*")) class Course(models.Model): objects = CourseManager() - name = models.CharField(max_length=255, verbose_name="nom", default="Sans nom") + 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) - timetable = models.ForeignKey(Timetable, on_delete=models.CASCADE, - verbose_name="emploi du temps") + 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") @@ -199,7 +258,6 @@ class Course(models.Model): super(Course, self).save(*args, **kwargs) - class Meta: verbose_name = "cours" verbose_name_plural = "cours" |