From 0b5d2ede0a7027c26f117bf85636fd0c002f772c Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 26 Nov 2017 23:20:07 +0100 Subject: Suppression des imports inutiles --- models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'models.py') diff --git a/models.py b/models.py index 25ddd1b..035bd6d 100644 --- a/models.py +++ b/models.py @@ -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 Manager, Q from django.db.models.functions import ExtractWeek, ExtractYear from django.utils import timezone from django.utils.text import slugify -- cgit v1.2.1 From 1b0fbf29a484b16de31ac5df1b3fded39be95e97 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Fri, 17 Nov 2017 21:20:37 +0100 Subject: Ajout d’un champ slug au modèle des salles --- models.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'models.py') diff --git a/models.py b/models.py index 035bd6d..547c0e1 100644 --- a/models.py +++ b/models.py @@ -142,10 +142,14 @@ class Group(models.Model): class Room(models.Model): 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 + def save(self, *args, **kwargs): + self.slug = slugify(self.name) + super(Room, self).save() class Meta: verbose_name = "salle" -- cgit v1.2.1 From 3148aaf7c43866bd672d54ac54a0e70bc71f1020 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 18 Nov 2017 17:14:26 +0100 Subject: Mise en commun du traitement des données avant rendu de l’emploi du temps --- models.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'models.py') diff --git a/models.py b/models.py index 547c0e1..369a4a6 100644 --- a/models.py +++ b/models.py @@ -157,10 +157,15 @@ class Room(models.Model): 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__in=(obj,), **criteria) + + return qs.order_by("begin") def get_weeks(self, **criteria): return self.get_queryset() \ -- cgit v1.2.1 From 700d4218b84c45bb37a150f0ec1c9bf6866b3bf3 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 19 Nov 2017 00:19:21 +0100 Subject: À QUOI ÇA SERT DE FAIRE DES SUPERS MODÈLES ABSTRAITS SI ON S’EN SERT PAS APRÈS AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA --- models.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) (limited to 'models.py') diff --git a/models.py b/models.py index 369a4a6..66d5ae5 100644 --- a/models.py +++ b/models.py @@ -84,7 +84,7 @@ class GroupManager(Manager): timetable=group.timetable) -class Group(models.Model): +class Group(SlugModel): objects = GroupManager() name = models.CharField(max_length=255, verbose_name="nom") @@ -121,7 +121,6 @@ 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: @@ -140,17 +139,13 @@ class Group(models.Model): verbose_name_plural = "groupes" -class Room(models.Model): +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 - def save(self, *args, **kwargs): - self.slug = slugify(self.name) - super(Room, self).save() - class Meta: verbose_name = "salle" verbose_name_plural = "salles" -- cgit v1.2.1 From 582b1d2be865cc0fba9aa4726404f4370d0b80c5 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 19 Nov 2017 01:10:29 +0100 Subject: Séparation en deux modèles des emplois du temps : un pour l’affichage, l’autre pour les sources --- models.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) (limited to 'models.py') diff --git a/models.py b/models.py index 66d5ae5..dc9732b 100644 --- a/models.py +++ b/models.py @@ -49,15 +49,27 @@ class Year(SlugModel): verbose_name_plural = "années" -class Timetable(SlugModel): +class Timetable(models.Model): + url = models.URLField(max_length=255, verbose_name="URL", unique=True) + last_update_date = models.DateTimeField(verbose_name="dernière mise à jour Celcat", + null=True, blank=True) + + def __str__(self): + return self.url + + + class Meta: + verbose_name = "source d’emploi du temps" + verbose_name_plural = "sources d’emploi du temps" + + +class TimetableFront(SlugModel): 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(Timetable, on_delete=models.CASCADE, + verbose_name="source") def __str__(self): return self.year.name + " " + self.name -- cgit v1.2.1 From f729db8ed67f4540eb265e848bcd71add3ff074e Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Mon, 27 Nov 2017 15:25:42 +0100 Subject: Renommage de Timetable en Source et de TimetableFront en Timetable. C’est à partir de ce commit que la migration fournie sur la ML est utilisable. --- models.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'models.py') diff --git a/models.py b/models.py index dc9732b..c55bbae 100644 --- a/models.py +++ b/models.py @@ -31,7 +31,6 @@ class SlugModel(models.Model): super(SlugModel, self).save() - class Meta: abstract = True @@ -49,7 +48,7 @@ class Year(SlugModel): verbose_name_plural = "années" -class Timetable(models.Model): +class Source(models.Model): url = models.URLField(max_length=255, verbose_name="URL", unique=True) last_update_date = models.DateTimeField(verbose_name="dernière mise à jour Celcat", null=True, blank=True) @@ -63,12 +62,12 @@ class Timetable(models.Model): verbose_name_plural = "sources d’emploi du temps" -class TimetableFront(SlugModel): +class Timetable(SlugModel): 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(Timetable, on_delete=models.CASCADE, + source = models.ForeignKey(Source, on_delete=models.CASCADE, verbose_name="source") def __str__(self): @@ -102,7 +101,7 @@ class Group(SlugModel): 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, + timetable = models.ForeignKey(Source, on_delete=models.CASCADE, verbose_name="emploi du temps") mention = models.CharField(max_length=128) @@ -190,7 +189,7 @@ class Course(models.Model): 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, + timetable = models.ForeignKey(Source, on_delete=models.CASCADE, verbose_name="emploi du temps") notes = models.TextField(verbose_name="remarques", blank=True, null=True) -- cgit v1.2.1 From 703cec5da11e7b859ef0172f5cc656bd8040a846 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Mon, 27 Nov 2017 15:42:33 +0100 Subject: Relation source inverse --- models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'models.py') diff --git a/models.py b/models.py index c55bbae..eba8f1c 100644 --- a/models.py +++ b/models.py @@ -68,7 +68,7 @@ class Timetable(SlugModel): 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") + verbose_name="source", related_name="timetables") def __str__(self): return self.year.name + " " + self.name -- cgit v1.2.1 From d0c69d3095d14f5509190d6b637cc0c018e53a19 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Mon, 27 Nov 2017 16:25:25 +0100 Subject: Changement des champs timetable en source pour plus de clareté --- models.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'models.py') diff --git a/models.py b/models.py index eba8f1c..3c9fa27 100644 --- a/models.py +++ b/models.py @@ -101,8 +101,8 @@ class Group(SlugModel): name = models.CharField(max_length=255, verbose_name="nom") celcat_name = models.CharField(max_length=255, verbose_name="nom dans Celcat") - timetable = models.ForeignKey(Source, 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) @@ -142,9 +142,9 @@ class Group(SlugModel): 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" @@ -189,8 +189,8 @@ class Course(models.Model): 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(Source, 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") -- cgit v1.2.1 From 5d4d7530e1aba199c2604b311b17bef253f0a008 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Mon, 27 Nov 2017 16:27:50 +0100 Subject: Remplacement des références aux champs timetable vers source --- models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'models.py') diff --git a/models.py b/models.py index 3c9fa27..6124b59 100644 --- a/models.py +++ b/models.py @@ -92,7 +92,7 @@ class GroupManager(Manager): return self.get_queryset().filter(groups_criteria, Q(semester=None) | Q(semester=group.semester), mention=group.mention, - timetable=group.timetable) + source=group.source) class Group(SlugModel): -- cgit v1.2.1 From fb6ea65e7d32dcf3d697e6c63985a018ada4d6b1 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Mon, 27 Nov 2017 16:37:31 +0100 Subject: Ajout d’un gestionnaire pour Timetable récupérant automatiquement les années Réduit considérablement le nombre d’appels effectués dans l’interface d’administration --- models.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'models.py') diff --git a/models.py b/models.py index 6124b59..b8c67d6 100644 --- a/models.py +++ b/models.py @@ -62,7 +62,14 @@ class Source(models.Model): 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") -- cgit v1.2.1 From 8a71b0a55847e8783006b5a9514f64e0129578c9 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Mon, 27 Nov 2017 17:23:11 +0100 Subject: Fonction pour formater les emplois du temps à partir d’une source --- models.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'models.py') diff --git a/models.py b/models.py index b8c67d6..363cf33 100644 --- a/models.py +++ b/models.py @@ -56,6 +56,10 @@ class Source(models.Model): 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" -- cgit v1.2.1 From 8f9c5eb32c6cf27aa84d1cbf14b7aaaa00acc0b9 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Tue, 16 Jan 2018 20:31:20 +0100 Subject: Utilisation des valeurs par défaut de reduce() --- models.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) (limited to 'models.py') diff --git a/models.py b/models.py index 363cf33..56c1845 100644 --- a/models.py +++ b/models.py @@ -93,12 +93,10 @@ 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), -- cgit v1.2.1 From bc7cac9459ac3ae55a31e9dd215cfc8f054e5fb1 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Thu, 18 Jan 2018 13:35:29 +0100 Subject: get_courses() émet une exception si l’objet passé n’est ni un groupe ni une salle --- models.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'models.py') diff --git a/models.py b/models.py index 56c1845..5de1df2 100644 --- a/models.py +++ b/models.py @@ -179,6 +179,8 @@ class CourseManager(Manager): .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") -- cgit v1.2.1 From 76fef8f3e0b3ad77f632a9e4d2c048607a1cf21b Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Thu, 18 Jan 2018 13:42:49 +0100 Subject: Mise à jour des copyrights --- models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'models.py') diff --git a/models.py b/models.py index 5de1df2..4903b11 100644 --- a/models.py +++ b/models.py @@ -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 -- cgit v1.2.1 From 8b0626139036e50396f14cf9ae39b12e2540af85 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Fri, 19 Jan 2018 20:48:58 +0100 Subject: Préchargement des groupes et des salles lorsqu’on demande la liste des salles. Réduit le nombre de requêtes à effectuer ainsi que le temps de traitement. --- models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'models.py') diff --git a/models.py b/models.py index 4903b11..ae04fb8 100644 --- a/models.py +++ b/models.py @@ -178,7 +178,8 @@ class CourseManager(Manager): 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) + qs = qs.filter(rooms__in=(obj,), **criteria) \ + .prefetch_related("groups", "rooms") else: raise(TypeError, "obj must be a Group or a Room") -- cgit v1.2.1 From 046e92137ace30cd645ba0f42421c283a60ba0cd Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Fri, 19 Jan 2018 21:31:35 +0100 Subject: N’affiche plus la liste des groupes dans l’emploi du temps des salles si un cours n’en a pas, au lieu de se baser sur le nombre de salles d’un cours pour faire ce choix. Suppression du préchargement des salles lorsqu’on demande les cours d’une salle. Cela permet de réduire le nombre de requêtes effectuées. --- models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'models.py') diff --git a/models.py b/models.py index ae04fb8..15f378a 100644 --- a/models.py +++ b/models.py @@ -31,6 +31,7 @@ class SlugModel(models.Model): super(SlugModel, self).save() + class Meta: abstract = True @@ -179,7 +180,7 @@ class CourseManager(Manager): .prefetch_related("rooms") elif isinstance(obj, Room): qs = qs.filter(rooms__in=(obj,), **criteria) \ - .prefetch_related("groups", "rooms") + .prefetch_related("groups") else: raise(TypeError, "obj must be a Group or a Room") -- cgit v1.2.1 From a0101b9a7566b5296c3490ce3592984976553807 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 20 Jan 2018 22:28:21 +0100 Subject: On cache les groupes qui n’ont plus de cours La requête est assez longue à s’effectuer sur SQLite, mais pas sur PostgreSQL --- models.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'models.py') diff --git a/models.py b/models.py index 15f378a..c122bfd 100644 --- a/models.py +++ b/models.py @@ -16,7 +16,7 @@ from functools import reduce from django.db import models -from django.db.models import 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 @@ -104,6 +104,13 @@ class GroupManager(Manager): mention=group.mention, 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(SlugModel): objects = GroupManager() -- cgit v1.2.1 From 8ad4a03f9d6ab6d218ddcbe277ca3766f05f6d79 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 20 Jan 2018 17:24:18 +0100 Subject: Légère optimisation de la page des groupes --- models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'models.py') diff --git a/models.py b/models.py index c122bfd..b809a53 100644 --- a/models.py +++ b/models.py @@ -200,7 +200,8 @@ 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): -- cgit v1.2.1 From c9388e29b2f9ee18a9e190683a8a33fb710684c5 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 27 Jan 2018 17:56:45 +0100 Subject: PEP8 --- models.py | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) (limited to 'models.py') diff --git a/models.py b/models.py index 4903b11..60be95c 100644 --- a/models.py +++ b/models.py @@ -42,7 +42,6 @@ class Year(SlugModel): def __str__(self): return self.name - class Meta: verbose_name = "année" verbose_name_plural = "années" @@ -50,16 +49,17 @@ class Year(SlugModel): class Source(models.Model): url = models.URLField(max_length=255, verbose_name="URL", unique=True) - last_update_date = models.DateTimeField(verbose_name="dernière mise à jour Celcat", - null=True, blank=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()]) - + return ", ".join([str(timetable) for timetable in + self.timetables.iterator()]) class Meta: verbose_name = "source d’emploi du temps" @@ -79,12 +79,12 @@ class Timetable(SlugModel): 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") + 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" @@ -95,11 +95,12 @@ 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)], + 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, source=group.source) @@ -115,7 +116,8 @@ class Group(SlugModel): 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="") @@ -126,10 +128,10 @@ class Group(SlugModel): 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): @@ -148,7 +150,6 @@ class Group(SlugModel): super(Group, self).save() - class Meta: index_together = ("mention", "semester", "subgroup",) unique_together = (("name", "source",), @@ -175,8 +176,8 @@ 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") + 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: @@ -197,7 +198,8 @@ class CourseManager(Manager): 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) source = models.ForeignKey(Source, on_delete=models.CASCADE, @@ -225,7 +227,6 @@ class Course(models.Model): super(Course, self).save(*args, **kwargs) - class Meta: verbose_name = "cours" verbose_name_plural = "cours" -- cgit v1.2.1 From 0838f8a7ab0863c8e1cd277a4dd7de62ba61b5f4 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 27 Jan 2018 19:11:45 +0100 Subject: SlugModel.save() transfère tous les arguments reçus à Model.save() --- models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'models.py') diff --git a/models.py b/models.py index 60be95c..9f36a6f 100644 --- a/models.py +++ b/models.py @@ -25,11 +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 @@ -148,7 +148,7 @@ class Group(SlugModel): if self.subgroup is None: self.subgroup = "" - super(Group, self).save() + super(Group, self).save(*args, **kwargs) class Meta: index_together = ("mention", "semester", "subgroup",) -- cgit v1.2.1 From 661fbb63d8f6e3607e7449b25f45613a08a1c6bb Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 27 Jan 2018 19:28:37 +0100 Subject: Création des tests de la requête de QSJPS On y créée sept salles, avec différents agencements de cours : 0. Le cours se finit dans l’intervalle sélectionné 1. Le cours se commence dans l’intervalle 2. Combinaison de 0. et de 1. 3. Le cours commence avant et fini après l’intervalle 4. Le cours commence et fini pendant l’intervalle 5. Un cours se finit avant et un autre commence après 6. Aucun cours liste des salles. Normalement, seules les salles des cas cinq et six doivent se retrouver dans la liste des salles. --- models.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'models.py') diff --git a/models.py b/models.py index 9f36a6f..1a638ac 100644 --- a/models.py +++ b/models.py @@ -160,7 +160,14 @@ class Group(SlugModel): verbose_name_plural = "groupes" +class RoomManager(Manager): + def qsjps(self, begin, end): + return None + + 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) -- cgit v1.2.1 From ee94b9e48dc2b632f876702df57136f394ee5574 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 27 Jan 2018 19:57:02 +0100 Subject: Requête de QSJPS Pour chaque salle, on compte tous les cours commençant avant la fin de l’intervalle entré par l’utilisateur et finissant après le début de cet intervalle. Tous les cours correspondant à cette requête se trouvent au moins en partie sur l’intervalle. On sélectionne ensuite les salles n’ayant pas de cours correspondant à la requête précédente. --- models.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) (limited to 'models.py') diff --git a/models.py b/models.py index 1a638ac..e907944 100644 --- a/models.py +++ b/models.py @@ -16,7 +16,7 @@ from functools import reduce from django.db import models -from django.db.models import Manager, Q +from django.db.models import Manager, OuterRef, Q, Subquery, Value from django.db.models.functions import ExtractWeek, ExtractYear from django.utils import timezone from django.utils.text import slugify @@ -162,7 +162,26 @@ class Group(SlugModel): class RoomManager(Manager): def qsjps(self, begin, end): - return None + # On compte tous les cours correspondant à chaque salle + # commençant avant la fin de l’intervalle et finissant après + # le début de l’intervalle. Ces cours se trouvent à un moment + # dans l’intervalle, et la salle assignée à ce cours ne peut + # donc pas être sélectionnée. Pour accélérer la requête, on + # s’arrête au premier cours trouvé. + courses = Course.objects.filter(begin__lt=end, end__gt=begin, + rooms=OuterRef("pk")) \ + .annotate(c=Value(1, + output_field=models.IntegerField())) \ + .values_list("c", flat=True)[:1] + + # On sélectionne toutes les salles qui n’ont aucun de cours se + # trouvant au moins en partie dans l’intervalle entré par + # l’utilisateur. + rooms = self.get_queryset().annotate( + c=Subquery(courses, output_field=models.IntegerField())) \ + .filter(c__isnull=True).order_by("name") + + return rooms class Room(SlugModel): -- cgit v1.2.1 From 0aceecf04cf720525772a9801d7799f19e5a3cd1 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 27 Jan 2018 21:59:42 +0100 Subject: Remplacement de la requête de QSJPS par une autre, plus simple et plus rapide à exécuter sur de gros volumes de données. On aura peut-être besoin d’utiliser un double index pour augmenter encore plus les performances. La requête liste tous les cours commençant avant la fin de l’intervalle et finissant après le début de l’intervalle, en excluant les cours n’ayant pas de salle assignée. On récupère ensuite la liste des salles de ces cours, et on inverse le contenu de la liste. On trie ensuite les cours par leur nom. --- models.py | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) (limited to 'models.py') diff --git a/models.py b/models.py index e907944..8ff9359 100644 --- a/models.py +++ b/models.py @@ -16,7 +16,7 @@ from functools import reduce from django.db import models -from django.db.models import Manager, OuterRef, Q, Subquery, Value +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 @@ -162,26 +162,19 @@ class Group(SlugModel): class RoomManager(Manager): def qsjps(self, begin, end): - # On compte tous les cours correspondant à chaque salle - # commençant avant la fin de l’intervalle et finissant après - # le début de l’intervalle. Ces cours se trouvent à un moment - # dans l’intervalle, et la salle assignée à ce cours ne peut - # donc pas être sélectionnée. Pour accélérer la requête, on - # s’arrête au premier cours trouvé. + # 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=OuterRef("pk")) \ - .annotate(c=Value(1, - output_field=models.IntegerField())) \ - .values_list("c", flat=True)[:1] - - # On sélectionne toutes les salles qui n’ont aucun de cours se - # trouvant au moins en partie dans l’intervalle entré par - # l’utilisateur. - rooms = self.get_queryset().annotate( - c=Subquery(courses, output_field=models.IntegerField())) \ - .filter(c__isnull=True).order_by("name") - - return rooms + 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): -- cgit v1.2.1 From 1ba5cda81bfeb19c98b80f172dca5ddc0b64ab4c Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 27 Jan 2018 22:02:32 +0100 Subject: Préchargement des salles et des groupes des cours lorsqu’on liste les cours d’une salle pour économiser les requêtes et augmenter les performances --- models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'models.py') diff --git a/models.py b/models.py index 8ff9359..5d2fe17 100644 --- a/models.py +++ b/models.py @@ -198,7 +198,8 @@ class CourseManager(Manager): 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) + qs = qs.filter(rooms=obj, **criteria).prefetch_related("rooms", + "groups") else: raise(TypeError, "obj must be a Group or a Room") -- cgit v1.2.1 From 8ffb95ff5e9ea571baafd30249caa9c0a11b8d98 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 27 Jan 2018 22:08:18 +0100 Subject: Petit oubli --- models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'models.py') diff --git a/models.py b/models.py index 5d2fe17..ba4ebaf 100644 --- a/models.py +++ b/models.py @@ -51,7 +51,7 @@ 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") + " Celcat") def __str__(self): return self.url -- cgit v1.2.1 From 77a4b75431cfab7348db73b563dd005ce64be14a Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 28 Jan 2018 11:14:34 +0100 Subject: Changements dans le formatage du code pour le rendre plus lisible --- models.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'models.py') diff --git a/models.py b/models.py index ba4ebaf..b762215 100644 --- a/models.py +++ b/models.py @@ -195,11 +195,12 @@ 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") + 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") + qs = qs.filter(rooms=obj, **criteria) \ + .prefetch_related("rooms", "groups") else: raise(TypeError, "obj must be a Group or a Room") -- cgit v1.2.1 From abaa63de655d8d0a0c62224c7cca9318f39d436f Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sat, 10 Feb 2018 15:14:15 +0100 Subject: Plus d’appel à iterator() --- models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'models.py') diff --git a/models.py b/models.py index b762215..538d64c 100644 --- a/models.py +++ b/models.py @@ -59,7 +59,7 @@ class Source(models.Model): @property def formatted_timetables(self): return ", ".join([str(timetable) for timetable in - self.timetables.iterator()]) + self.timetables.all()]) class Meta: verbose_name = "source d’emploi du temps" -- cgit v1.2.1 From 1934cdb6093a96269dc3ea911776825768fe5ae7 Mon Sep 17 00:00:00 2001 From: Alban Gruin Date: Sun, 15 Apr 2018 15:25:16 +0200 Subject: Légères corrections PEP8 --- models.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'models.py') diff --git a/models.py b/models.py index f7450dc..e56d33d 100644 --- a/models.py +++ b/models.py @@ -31,7 +31,6 @@ class SlugModel(models.Model): super(SlugModel, self).save(*args, **kwargs) - class Meta: abstract = True @@ -106,11 +105,13 @@ class GroupManager(Manager): source=group.source) def get_relevant_groups(self, start, **criteria): - courses = Course.objects.filter(groups=OuterRef("pk"), begin__gte=start) \ + 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") + return self.get_queryset() \ + .annotate(c=Subquery(courses, + output_field=models.IntegerField())) \ + .filter(c__isnull=False, **criteria).order_by("name") class Group(SlugModel): -- cgit v1.2.1