diff options
| -rw-r--r-- | admin.py | 4 | ||||
| -rw-r--r-- | management/commands/_private.py | 40 | ||||
| -rw-r--r-- | management/commands/timetables.py | 1 | ||||
| -rw-r--r-- | models.py | 60 | ||||
| -rw-r--r-- | tests.py | 14 | ||||
| -rw-r--r-- | utils.py | 36 | ||||
| -rw-r--r-- | views.py | 14 | 
7 files changed, 58 insertions, 111 deletions
| @@ -44,12 +44,12 @@ class TimetableAdmin(admin.ModelAdmin):  class GroupAdmin(admin.ModelAdmin):      fieldsets = (          (None, {"fields": ("name", "celcat_name", "timetable", "hidden",)}), -        ("Groupes", {"fields": ("mention", "subgroup", "td", "tp", "parent",)}),) +        ("Groupes", {"fields": ("mention", "subgroup",)}),)      list_display = ("name", "timetable", "hidden",)      list_editable = ("hidden",)      list_filter = ("timetable",)      ordering = ("timetable",) -    readonly_fields = ("celcat_name", "mention", "subgroup", "td", "tp",) +    readonly_fields = ("celcat_name", "mention",)      actions = (make_hidden, make_visible,) diff --git a/management/commands/_private.py b/management/commands/_private.py index a9d283d..b663454 100644 --- a/management/commands/_private.py +++ b/management/commands/_private.py @@ -30,42 +30,6 @@ def add_time(date, time):      delta = datetime.timedelta(hours=ptime.hour, minutes=ptime.minute)      return date + delta -def consolidate_group(group): -    group_content_key = ("mention", "subgroup", "td", "tp") -    group_content_list = group.group_info[1:] - -    if group.subgroup is not None: -        group_content = dict(zip(group_content_key, group_content_list)) - -        for i in range(len(group_content_list))[::-1]: -            del group_content[group_content_key[i]] -            group_content[group_content_key[i] + "__isnull"] = True - -            if group_content_list[i] is not None: -                break - -        group.parent = Group.objects.filter(timetable=group.timetable, -                                            **group_content).first() -        group.save() - -    if group.tp is None: -        group_content = dict(zip(group_content_key, group_content_list)) -        last_is_none = False - -        for i, key in enumerate(group_content_key): -            if group_content_list[i] is None or last_is_none: -                del group_content[key] -                group_content[key + "__isnull"] = last_is_none -                last_is_none = True - -        Group.objects.filter(timetable=group.timetable, parent__isnull=True, -                             **group_content).update(parent=group) - -def consolidate_groups(groups): -    for group in groups: -        if group.parent is None: -            consolidate_group(group) -  def delete_courses_in_week(timetable, year, week, today):      start, end = get_week(year, week)      Course.objects.filter(begin__gte=max(start, today), begin__lt=end, @@ -96,12 +60,10 @@ def get_event(timetable, event, event_week, today):      # Création de l’objet cours      course = Course.objects.create(timetable=timetable, begin=begin, end=end) -    # On récupère les groupes concernés par les cours, on les -    # « consolide », puis on les insère dans l’objet cours. +    # On récupère les groupes concernés par les cours      groups = [get_from_db_or_create(Group, timetable=timetable,                                      celcat_name=item.text)                for item in event.resources.group.find_all("item")] -    consolidate_groups(groups)      course.groups.add(*groups)      # On récupère le champ « remarque » diff --git a/management/commands/timetables.py b/management/commands/timetables.py index 35fb26e..2d8a17e 100644 --- a/management/commands/timetables.py +++ b/management/commands/timetables.py @@ -18,7 +18,6 @@ import datetime  from django.core.management.base import BaseCommand  from django.db import transaction  from django.db.models import Min -from django.utils import timezone  from edt.models import Course, Timetable  from edt.utils import get_week, tz_now @@ -13,8 +13,11 @@  #    You should have received a copy of the GNU Affero General Public License  #    along with celcatsanitizer.  If not, see <http://www.gnu.org/licenses/>. +from functools import reduce +  from django.db import models -from django.db.models import Count, Manager, Q +from django.db.models import Count, Manager, Q, Subquery +from django.db.models.expressions import OuterRef  from django.db.models.functions import ExtractWeek, ExtractYear  from django.utils import timezone  from django.utils.text import slugify @@ -67,17 +70,13 @@ class Timetable(SlugModel):  class GroupManager(Manager): -    def get_relevant_children(self, group): -        parent_in = self.get_queryset().filter(parent=group) -        return self.get_queryset().filter(Q(parent=group) | Q(parent__in=parent_in)) \ -                                  .annotate(children_count=Count("children")) \ -                                  .filter(children_count=0, hidden=False) \ -                                  .order_by("name") - -    def get_relevant_groups(self, *args, **criteria): -        return self.get_queryset().filter(*args, **criteria) \ -                                  .annotate(children_count=Count("children")) \ -                                  .filter(children_count=0, hidden=False) +    def get_relevant_groups(self, timetable, *args, **criteria): +        sub = Group.objects.filter(timetable=timetable,mention=OuterRef("mention"), +                                   subgroup__startswith=OuterRef("subgroup")) \ +                           .order_by().values("mention").annotate(c=Count("*")).values("c") +        return Group.objects.filter(*args, timetable=timetable, hidden=False, **criteria) \ +                            .annotate(nbsub=Subquery(sub, output_field=models.IntegerField())) \ +                            .filter(nbsub=1).order_by("name")  class Group(models.Model): @@ -90,27 +89,26 @@ class Group(models.Model):                                    verbose_name="emploi du temps")      mention = models.CharField(max_length=128) -    subgroup = models.CharField(max_length=1, verbose_name="sous-groupe", +    subgroup = models.CharField(max_length=16, verbose_name="sous-groupe",                                  null=True) -    td = models.IntegerField(verbose_name="groupe de TD", null=True) -    tp = models.IntegerField(verbose_name="groupe de TP", null=True) -    parent = models.ForeignKey("self", verbose_name="groupe parent", null=True, -                               default=None, related_name="children")      slug = models.SlugField(max_length=64, default="")      hidden = models.BooleanField(verbose_name="caché", default=False) -    def corresponds_to(self, timetable_id, mention, subgroup, td, tp): +    def corresponds_to(self, timetable_id, mention, subgroup): +        subgroup_corresponds = True +        if self.subgroup is not None and subgroup is not None: +            subgroup_corresponds = subgroup.startswith(self.subgroup) or \ +                                   self.subgroup.startswith(subgroup) +          return self.timetable.id == timetable_id and \              self.mention.startswith(mention) and \ -            (self.subgroup == subgroup or self.subgroup is None) and \ -            (self.td == td or self.td is None or td is None) and \ -            (self.tp == tp or self.tp is None or tp is None) +            subgroup_corresponds      @property      def group_info(self): -        return self.timetable.id, self.mention, self.subgroup, self.td, self.tp +        return self.timetable.id, self.mention, self.subgroup      def __str__(self):          return self.name @@ -120,12 +118,12 @@ class Group(models.Model):              self.name = self.celcat_name              self.slug = slugify(self.name) -        self.mention, self.subgroup, self.td, self.tp = parse_group(self.name) +        self.mention, self.subgroup = parse_group(self.name)          super(Group, self).save()      class Meta: -        index_together = ("mention", "subgroup", "td", "tp",) +        index_together = ("mention", "subgroup",)          unique_together = (("name", "timetable",),                             ("celcat_name", "timetable",),                             ("slug", "timetable",),) @@ -148,17 +146,13 @@ class Room(models.Model):  class CourseManager(Manager):      def get_courses_for_group(self, group, **criteria): -        groups_criteria = [] -        if group.subgroup is not None: -            groups_criteria.append(Q(groups__subgroup__isnull=True) | \ -                                   Q(groups__subgroup=group.subgroup)) -        if group.td is not None: -            groups_criteria.append(Q(groups__td__isnull=True) | Q(groups__td=group.td)) -        if group.tp is not None: -            groups_criteria.append(Q(groups__tp__isnull=True) | Q(groups__tp=group.tp)) +        groups_criteria = reduce(lambda x, y: x | y, +                                 [Q(groups__subgroup=group.subgroup[:i]) +                                  for i in range(1, len(group.subgroup) + 1)]) | \ +                                      Q(groups__subgroup__isnull=True)          return self.get_queryset() \ -                   .filter(*groups_criteria, +                   .filter(groups_criteria,                             groups__mention=group.mention,                             timetable=group.timetable, **criteria) \                     .order_by("begin") @@ -14,8 +14,6 @@  #    along with celcatsanitizer.  If not, see <http://www.gnu.org/licenses/>.  from django.test import TestCase -from django.utils import timezone -  from .models import Course, Group, Timetable, Year  from .utils import tz_now @@ -130,10 +128,10 @@ class GroupTestCase(TestCase):          tdb2 = Group.objects.get(celcat_name="L1 info s2 TDB2", timetable=self.timetable)          tpb21 = Group.objects.get(celcat_name="L1 info s2 TPB21", timetable=self.timetable) -        self.assertEqual(cma.group_info, (self.timetable.id, "L1 info s2", "A", None, None)) -        self.assertEqual(tda2.group_info, (self.timetable.id, "L1 info s2", "A", 2, None)) -        self.assertEqual(tpa21.group_info, (self.timetable.id, "L1 info s2", "A", 2, 1)) +        self.assertEqual(cma.group_info, (self.timetable.id, "L1 info", "A")) +        self.assertEqual(tda2.group_info, (self.timetable.id, "L1 info", "A2")) +        self.assertEqual(tpa21.group_info, (self.timetable.id, "L1 info", "A21")) -        self.assertEqual(cmb.group_info, (self.timetable.id, "L1 info s2", "B", None, None)) -        self.assertEqual(tdb2.group_info, (self.timetable.id, "L1 info s2", "B", 2, None)) -        self.assertEqual(tpb21.group_info, (self.timetable.id, "L1 info s2", "B", 2, 1)) +        self.assertEqual(cmb.group_info, (self.timetable.id, "L1 info", "B")) +        self.assertEqual(tdb2.group_info, (self.timetable.id, "L1 info", "B2")) +        self.assertEqual(tpb21.group_info, (self.timetable.id, "L1 info", "B21")) @@ -48,32 +48,24 @@ def group_courses(courses):  def parse_group(name):      # Explication de la regex      # -    # ^(.+?)\s+((CM(\w))|(TD(\w)(\d))|(TP(\w)(\d)(\d)))?(\s+\(.+\))?$ -    # ^                                                               début de la ligne -    #  (.+?)                                                          correspond à au moins un caractère -    #       \s+                                                       un ou plusieurs espaces -    #          ((CM(\w))|                                             correspond à CM suivi d'une lettre ou… -    #                    (TD(\w)(\d))|                                … à TD suivi d’une lettre et d'un chiffre ou… -    #                                 (TP(\w)(\d)(\d))                … à TP suivi d’une lettre et de deux chiffres -    #                                                 )?              groupe optionnel -    #                                                   (\s+          un ou plusieurs espaces -    #                                                       \(.+\))   un ou plusieurs caractères quelconques entre parenthèses -    #                                                              ?  groupe optionnel -    #                                                               $ fin de la ligne -    group_regex = re.compile(r"^(.+?)\s+((CM(\w))|(TD(\w)(\d))|(TP(\w)(\d)(\d)))?(\s+\(.+\))?$") +    # ^(.+?)\s*(s\d\s+)?((CM|TD|TP|G)(\w\d{0,3}))?(\s+\(.+\))?$ +    # ^                                                         début de la ligne +    #  (.+?)                                                    correspond à au moins un caractère +    #       \s*                                                 éventuellement un ou plusieurs espaces +    #          (s\d\s+)?                                        éventuellement un s suivi d’un nombre et d’un ou plusieurs espaces +    #                   ((CM|TD|TP|G)                           « CM » ou « TD » ou « TP » ou « G » +    #                                (\w\d{0,3})                suivi d’un caractère puis entre 0 et 3 chiffres +    #                                           )?              groupe optionnel +    #                                             (\s+          un ou plusieurs espaces +    #                                                 \(.+\))?  un ou pliseurs caractères entre parenthèses +    #                                                         $ fin de la ligne +    group_regex = re.compile(r"^(.+?)\s*(s\d\s+)?((CM|TD|TP|G)(\w\d{0,3}))?(\s+\(.+\))?$")      search = group_regex.search(name)      if search is None: -        return name, None, None, None +        return name, None      parts = search.groups() -    if parts[1] is None:       # Pas de groupe précis indiqué -        return parts[0], None, None, None -    elif parts[2] is not None: # Groupe de CM -        return parts[0], parts[3], None, None -    elif parts[4] is not None: # Groupe de TD -        return parts[0], parts[5], parts[6], None -    elif parts[7] is not None: # Groupe de TP -        return parts[0], parts[8], parts[9], parts[10] +    return parts[0], parts[4]  def tz_now():      """Retourne la date et l’heure avec le bon fuseau horaire""" @@ -39,13 +39,12 @@ def group_list_common(request, timetable, groups):      groups_weeks = Course.objects.get_weeks(begin__gte=start, begin__lt=end, timetable=timetable) \                                   .values("groups__mention", "groups__subgroup", -                                         "groups__td", "groups__tp", "year", "week") +                                         "year", "week")      for group in groups:          for group_week in groups_weeks:              if group.corresponds_to(timetable.id, group_week["groups__mention"], -                                    group_week["groups__subgroup"], group_week["groups__td"], -                                    group_week["groups__tp"]): +                                    group_week["groups__subgroup"]):                  if not hasattr(group, "weeks"):                      group.weeks = [] @@ -60,7 +59,7 @@ def group_list_common(request, timetable, groups):  def group_list(request, year_slug, timetable_slug):      timetable = get_object_or_404(Timetable, year__slug=year_slug, slug=timetable_slug) -    groups = Group.objects.get_relevant_groups(timetable=timetable).order_by("name") +    groups = Group.objects.get_relevant_groups(timetable)      return group_list_common(request, timetable, groups)  def timetable(request, year_slug, timetable_slug, group_slug, year=None, week=None): @@ -78,8 +77,11 @@ def timetable(request, year_slug, timetable_slug, group_slug, year=None, week=No      timetable = get_object_or_404(Timetable, year__slug=year_slug, slug=timetable_slug)      group = get_object_or_404(Group, slug=group_slug, timetable=timetable) -    if group.children.count(): -        return group_list_common(request, timetable, Group.objects.get_relevant_children(group)) +    if Group.objects.filter(timetable=timetable, mention=group.mention, +                            subgroup__startswith=group.subgroup).count() > 1: +        subgroups = Group.objects.get_relevant_groups(timetable, mention=group.mention, +                                                      subgroup__startswith=group.subgroup) +        return group_list_common(request, timetable, subgroups)      courses = Course.objects.get_courses_for_group(group, begin__gte=start, begin__lt=end)      if courses.count() == 0 and provided_week: | 
