diff options
| -rw-r--r-- | admin.py | 2 | ||||
| -rw-r--r-- | db.py | 76 | ||||
| -rw-r--r-- | management/commands/sendmails.py | 19 | ||||
| -rw-r--r-- | models.py | 27 | ||||
| -rw-r--r-- | templates/index.html | 2 | ||||
| -rw-r--r-- | tests.py | 116 | ||||
| -rw-r--r-- | views.py | 10 | 
7 files changed, 145 insertions, 107 deletions
@@ -15,7 +15,7 @@  #    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.  from django.contrib import admin -from edt.models import Timetable, LastUpdate, Group, Subscription, Room, Course +from .models import Timetable, LastUpdate, Group, Subscription, Room, Course  @admin.register(Timetable) @@ -1,76 +0,0 @@ -#    Copyright (C) 2017  Alban Gruin -# -#    celcatsanitizer is free software; you can redistribute it and/or modify -#    it under the terms of the GNU General Public License as published by -#    the Free Software Foundation; either version 2 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 General Public License for more details. -# -#    You should have received a copy of the GNU General Public License along -#    with celcatsanitizer; if not, write to the Free Software Foundation, Inc., -#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -from django.db import connections -from django.db.models import Manager -from django.db.models.query import QuerySet -from django.db.models.sql.compiler import SQLCompiler -from django.db.models.sql.query import Query -from django.db.models.sql.where import WhereNode - - -class GroupedCompiler(SQLCompiler): -    def get_group_by(self, select, order_by): -        result = super(GroupedCompiler, self).get_group_by(select, order_by) -        expressions = [] -        for expr in self.query.real_group_by: -            ref = expr if hasattr(expr, "as_sql") else self.query.resolve_ref(expr) -            sql, params = self.compile(ref) -            result.append((sql, params)) - -        return result - - -class GroupedQuery(Query): -    def __init__(self, model, where=WhereNode): -        super(GroupedQuery, self).__init__(model, where) -        self.real_group_by = [] - -    def clone(self, klass=None, memo=None, **kwargs): -        obj = super(GroupedQuery, self).clone(klass, memo, **kwargs) -        obj.real_group_by = self.real_group_by[:] -        return obj - -    def add_grouping(self, *grouping): -        self.real_group_by.extend(grouping) - -    def clear_grouping(self): -        self.real_group_by = [] - -    def get_compiler(self, using=None, connection=None): -        if using is None and connection is None: -            raise ValueError("Need either using or connection") -        if using: -            connection = connections[using] -        return GroupedCompiler(self, connection, using) - - -class GroupedQuerySet(QuerySet): -    def __init__(self, model=None, query=None, using=None, hints=None): -        super(GroupedQuerySet, self).__init__(model, query, using, hints) -        self.query = query or GroupedQuery(self.model) - -    def group_by(self, *field_names): -        obj = self._clone() -        obj.query.clear_grouping() -        obj.query.add_grouping(*field_names) -        return obj - - -class GroupedManager(Manager): -    def __init__(self): -        super(GroupedManager, self).__init__() -        self._queryset_class = GroupedQuerySet diff --git a/management/commands/sendmails.py b/management/commands/sendmails.py index 3126243..5a2054f 100644 --- a/management/commands/sendmails.py +++ b/management/commands/sendmails.py @@ -38,22 +38,25 @@ class Command(BaseCommand):          subscriptions = Subscription.objects.filter(active=True)          content = {}          mails = [] +        footer = loader.get_template("mail/mail_footer.txt")          print("Generating messages...")          for subscription in subscriptions:              if subscription.group.id not in content:                  courses = Course.objects.get_courses_for_group(subscription.group, begin__gte=start, begin__lt=end) -                grouped_courses = group_courses(courses) -                template = loader.get_template("mail/mail_timetable.txt") -                context = Context({"subscription": subscription, "courses": grouped_courses, "week": week}) -                content[subscription.group.id] = template.render(context) +                if len(courses) > 0: +                    grouped_courses = group_courses(courses) -            footer = loader.get_template("mail/mail_footer.txt") -            context = Context({"admins": settings.ADMINS, "token": subscription.token, "domain": settings.DEFAULT_DOMAIN}) -            mail_content = content[subscription.group.id] + footer.render(context) +                    template = loader.get_template("mail/mail_timetable.txt") +                    context = Context({"subscription": subscription, "courses": grouped_courses, "week": week}) +                    content[subscription.group.id] = template.render(context) -            mails.append(("{0} - {1} - Semaine {2}".format(subscription.group.timetable.name, subscription.group.name, week), mail_content, settings.DEFAULT_FROM_EMAIL, [subscription.email],)) +            if subscription.group.id in content: +                context = Context({"admins": settings.ADMINS, "token": subscription.token, "domain": settings.DEFAULT_DOMAIN}) +                mail_content = content[subscription.group.id] + footer.render(context) + +                mails.append(("{0} - {1} - Semaine {2}".format(subscription.group.timetable.name, subscription.group.name, week), mail_content, settings.DEFAULT_FROM_EMAIL, [subscription.email],))          print("Sending mails...")          send_mass_mail(mails) @@ -15,13 +15,11 @@  #    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.  from django.db import connection, models -from django.db.models import Q +from django.db.models import Count, Manager, Q  from django.db.models.expressions import RawSQL  from django.db.models.functions import Extract, ExtractYear  from django.utils.text import slugify -from .db import GroupedManager -  import hashlib  import os @@ -75,6 +73,10 @@ class Group(models.Model):      def corresponds_to(self, timetable_id, mention, subgroup, td, tp):          return self.timetable.id == timetable_id and self.mention == mention and self.subgroup == subgroup 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) +    @property +    def group_info(self): +        return self.timetable.id, self.mention, self.subgroup, self.td, self.tp +      def __str__(self):          return self.name @@ -141,22 +143,17 @@ class Room(models.Model):          verbose_name_plural = "salles" -class CourseManager(GroupedManager): -    def __get_weeks(self, qs): -        extractYear = ExtractYear("begin") -        qs = qs.group_by("groups", "year", "week", "groups__timetable", "groups__mention", "groups__subgroup", "groups__td", "groups__tp", "begin", "groups__name").order_by("groups__name", "year", "week") - -        if connection.vendor == "postgresql": -            return qs.annotate(week=ExtractWeek("begin"), year=extractYear) -        else: -            return qs.annotate(week=RawSQL("""cast(strftime("%%W", "begin") as integer)""", []), year=extractYear) - +class CourseManager(Manager):      def get_courses_for_group(self, group, **filters):          return self.get_queryset().filter(Q(groups__td__isnull=True) | Q(groups__td=group.td), Q(groups__tp__isnull=True) | Q(groups__tp=group.tp), groups__mention=group.mention, groups__subgroup=group.subgroup, timetable=group.timetable, **filters).order_by("begin")      def get_weeks(self, **criteria): -        qs = self.get_queryset().filter(**criteria) -        return self.__get_weeks(qs) +        qs = self.get_queryset().filter(**criteria).order_by("groups__name", "year", "week").annotate(_=Count(("groups", "year", "week", "begin")), year=ExtractYear("begin")) + +        if connection.vendor == "postgresql": +            return qs.annotate(week=ExtractWeek("begin")) +        else: +            return qs.annotate(week=RawSQL("""cast(strftime("%%W", "begin") as integer)""", []))  class Course(models.Model): diff --git a/templates/index.html b/templates/index.html index cca2eaf..4db748d 100644 --- a/templates/index.html +++ b/templates/index.html @@ -62,7 +62,7 @@ li.course {        <section id="{{ timetable.slug }}">          <h3><a href="{{ timetable.url }}">{{ timetable.name }}</a></h3>          <ul>{% for group in groups %}{% if group.timetable.id == timetable.id %} -          <li><a class="text" href="{% url "timetable" timetable.slug group.slug %}">{{ group.name }}</a> – {% for week in group.weeks %}<a href="{% url "timetable" timetable.slug group.slug week.year week|dt_week %}">{{ week|dt_prettyprint }}</a> {% endfor %}</li>{% endif %}{% endfor %} +          <li><a class="text"{% if group.weeks is not None %} href="{% url "timetable" timetable.slug group.slug %}"{% endif %}>{{ group.name }}</a> – {% for week in group.weeks %}<a href="{% url "timetable" timetable.slug group.slug week.year week|dt_week %}">{{ week|dt_prettyprint }}</a> {% empty %}<em>aucun cours</em>{% endfor %}</li>{% endif %}{% endfor %}          </ul>        </section>{% endfor %}{% endblock %}      </div> @@ -15,5 +15,119 @@  #    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.  from django.test import TestCase +from django.utils import timezone -# Create your tests here. +from .models import Course, Group, Timetable + + +class CourseTestCase(TestCase): +    def setUp(self): +        dt = timezone.now() + +        self.timetable = Timetable(name="Test timetable 2", url="http://example.org/", slug="test-timetable2") +        self.timetable.save() + +        cma = Group.objects.create(celcat_name="L1 info s2 - CMA", timetable=self.timetable) +        tda2 = Group.objects.create(celcat_name="L1 info s2 - TDA2", timetable=self.timetable) +        self.tpa21 = Group.objects.create(celcat_name="L1 info s2 - TPA21", timetable=self.timetable) + +        cmb = Group.objects.create(celcat_name="L1 info s2 - CMB", timetable=self.timetable) +        tdb2 = Group.objects.create(celcat_name="L1 info s2 - TDB2", timetable=self.timetable) +        self.tpb21 = Group.objects.create(celcat_name="L1 info s2 - TPB21", timetable=self.timetable) + +        for group in (cma, tda2, self.tpa21, cmb, tdb2, self.tpb21,): +            course = Course.objects.create(name="{0} course".format(group.name), type="cours", timetable=self.timetable, begin=dt, end=dt) +            course.groups.add(group) + +    def test_get_courses_for_group(self): +        tpa21_courses = Course.objects.get_courses_for_group(self.tpa21) +        tpb21_courses = Course.objects.get_courses_for_group(self.tpb21) + +        tpa21_course_names = ["L1 info s2 - CMA course", "L1 info s2 - TDA2 course", "L1 info s2 - TPA21 course"] +        tpb21_course_names = ["L1 info s2 - CMB course", "L1 info s2 - TDB2 course", "L1 info s2 - TPB21 course"] + +        for courses, names in ((tpa21_courses, tpa21_course_names,), (tpb21_courses, tpb21_course_names,),): +            for course in courses: +                self.assertIn(course.name, names) +                names.remove(course.name) + + +class GroupTestCase(TestCase): +    def setUp(self): +        self.timetable = Timetable(name="Test timetable", url="http://example.com/", slug="test-timetable") +        self.timetable.save() + +        Group.objects.create(celcat_name="L1 info s2 - CMA", timetable=self.timetable) +        Group.objects.create(celcat_name="L1 info s2 - TDA2", timetable=self.timetable) +        Group.objects.create(celcat_name="L1 info s2 - TPA21", timetable=self.timetable) + +        Group.objects.create(celcat_name="L1 info s2 - CMB", timetable=self.timetable) +        Group.objects.create(celcat_name="L1 info s2 - TDB2", timetable=self.timetable) +        Group.objects.create(celcat_name="L1 info s2 - TPB21", timetable=self.timetable) + +    def test_corresponds(self): +        cma = Group.objects.get(celcat_name="L1 info s2 - CMA", timetable=self.timetable) +        tda2 = Group.objects.get(celcat_name="L1 info s2 - TDA2", timetable=self.timetable) +        tpa21 = Group.objects.get(celcat_name="L1 info s2 - TPA21", timetable=self.timetable) + +        cmb = Group.objects.get(celcat_name="L1 info s2 - CMB", timetable=self.timetable) +        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.assertTrue(cma.corresponds_to(*tda2.group_info)) # CMA corresponds to TDA2 +        self.assertTrue(cma.corresponds_to(*tpa21.group_info)) # CMA corresponds to TPA21 +        self.assertTrue(tda2.corresponds_to(*tpa21.group_info)) # TDA2 corresponds to TPA21 + +        self.assertTrue(cmb.corresponds_to(*tdb2.group_info)) # CMB corresponds to TDB2 +        self.assertTrue(cmb.corresponds_to(*tpb21.group_info)) # CMB corresponds to TPB21 +        self.assertTrue(tdb2.corresponds_to(*tpb21.group_info)) # TDB2 corresponds to TPB21 + +        self.assertFalse(cmb.corresponds_to(*tda2.group_info)) # CMB does not corresponds to TDA2 +        self.assertFalse(cmb.corresponds_to(*tpa21.group_info)) # CMB does not corresponds to TPA21 +        self.assertFalse(tdb2.corresponds_to(*tpa21.group_info)) # TDB2 does not corresponds to TPA21 + +        self.assertTrue(tda2.corresponds_to(*cma.group_info)) # TDA2 corresponds to CMA +        self.assertTrue(tpa21.corresponds_to(*cma.group_info)) # TPA21 corresponds to CMA +        self.assertTrue(tpa21.corresponds_to(*tda2.group_info)) # TPA21 corresponds to TDA2 + +        self.assertTrue(tdb2.corresponds_to(*cmb.group_info)) # TDB2 corresponds to CMB +        self.assertTrue(tpb21.corresponds_to(*cmb.group_info)) # TPB21 corresponds to CMB +        self.assertTrue(tpb21.corresponds_to(*tdb2.group_info)) # TPB21 corresponds to TDB2 + +        self.assertFalse(tda2.corresponds_to(*cmb.group_info)) # TDA2 does not corresponds to CMB +        self.assertFalse(tpa21.corresponds_to(*cmb.group_info)) # TPA21 does not corresponds to CMB +        self.assertFalse(tpa21.corresponds_to(*tdb2.group_info)) # TPA21 does not corresponds to TDB2 + +    def test_get(self): +        cma = Group.objects.get(name="L1 info s2 - CMA", timetable=self.timetable) +        tda2 = Group.objects.get(name="L1 info s2 - TDA2", timetable=self.timetable) +        tpa21 = Group.objects.get(name="L1 info s2 - TPA21", timetable=self.timetable) + +        cmb = Group.objects.get(name="L1 info s2 - CMB", timetable=self.timetable) +        tdb2 = Group.objects.get(name="L1 info s2 - TDB2", timetable=self.timetable) +        tpb21 = Group.objects.get(name="L1 info s2 - TPB21", timetable=self.timetable) + +        self.assertEqual(cma.celcat_name, "L1 info s2 - CMA") +        self.assertEqual(tda2.celcat_name, "L1 info s2 - TDA2") +        self.assertEqual(tpa21.celcat_name, "L1 info s2 - TPA21") + +        self.assertEqual(cmb.celcat_name, "L1 info s2 - CMB") +        self.assertEqual(tdb2.celcat_name, "L1 info s2 - TDB2") +        self.assertEqual(tpb21.celcat_name, "L1 info s2 - TPB21") + +    def test_parse(self): +        cma = Group.objects.get(celcat_name="L1 info s2 - CMA", timetable=self.timetable) +        tda2 = Group.objects.get(celcat_name="L1 info s2 - TDA2", timetable=self.timetable) +        tpa21 = Group.objects.get(celcat_name="L1 info s2 - TPA21", timetable=self.timetable) + +        cmb = Group.objects.get(celcat_name="L1 info s2 - CMB", timetable=self.timetable) +        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(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)) @@ -22,14 +22,14 @@ from django.core.mail import send_mail  from django.conf import settings  from django.template import Context, loader -from edt.forms import SubscribeForm -from edt.models import Timetable, LastUpdate, Group, Subscription, Course -from edt.utils import get_current_week, get_week, group_courses +from .forms import SubscribeForm +from .models import Timetable, LastUpdate, Group, Subscription, Course +from .utils import get_current_week, get_week, group_courses  import datetime  def index(request): -    timetables = Timetable.objects.all() +    timetables = Timetable.objects.all().order_by("name")      groups = Group.objects.filter(tp__isnull=False).order_by("name")      year, week = get_current_week() @@ -59,8 +59,8 @@ def timetable(request, timetable_slug, group_slug, year=None, week=None):      timetable = get_object_or_404(Timetable, slug=timetable_slug)      group = get_object_or_404(Group, slug=group_slug, timetable=timetable) +    last_update = get_object_or_404(LastUpdate, timetable=timetable, week=week, year=year)      courses = Course.objects.get_courses_for_group(group, begin__gte=start, begin__lt=end) -    last_update = LastUpdate.objects.get(timetable=timetable, week=week, year=year)      grouped_courses = group_courses(courses)  | 
