diff --git a/biscuit/apps/chronos/menus.py b/biscuit/apps/chronos/menus.py index a2709a213571c5f1d4f7de894875485ca16421c6..109bbb85d9fe5d431d5ca2ebf40d6a85dcacae47 100644 --- a/biscuit/apps/chronos/menus.py +++ b/biscuit/apps/chronos/menus.py @@ -6,7 +6,7 @@ MENUS = { 'name': _('Timetables'), 'url': '#', 'root': True, - 'validators': ['menu_generator.validators.is_authenticated'], + 'validators': ['menu_generator.validators.is_authenticated', 'biscuit.core.util.core_helpers.has_person'], 'submenu': [ { 'name': _('Timetable'), diff --git a/biscuit/apps/chronos/migrations/0005_school_related.py b/biscuit/apps/chronos/migrations/0005_school_related.py new file mode 100644 index 0000000000000000000000000000000000000000..d30597a55d45f3f8998675d4a0853c7309efaeac --- /dev/null +++ b/biscuit/apps/chronos/migrations/0005_school_related.py @@ -0,0 +1,78 @@ +# Generated by Django 2.2.4 on 2019-09-03 14:10 + +import biscuit.apps.chronos.util +import biscuit.core.util.core_helpers +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('chronos', '0004_auto_20190821_1550'), + ] + + operations = [ + migrations.AddField( + model_name='lesson', + name='school', + field=models.ForeignKey(default=biscuit.core.util.core_helpers.get_current_school, on_delete=django.db.models.deletion.CASCADE, to='core.School'), + ), + migrations.AddField( + model_name='lessonperiod', + name='school', + field=models.ForeignKey(default=biscuit.core.util.core_helpers.get_current_school, on_delete=django.db.models.deletion.CASCADE, to='core.School'), + ), + migrations.AddField( + model_name='lessonsubstitution', + name='school', + field=models.ForeignKey(default=biscuit.core.util.core_helpers.get_current_school, on_delete=django.db.models.deletion.CASCADE, to='core.School'), + ), + migrations.AddField( + model_name='room', + name='school', + field=models.ForeignKey(default=biscuit.core.util.core_helpers.get_current_school, on_delete=django.db.models.deletion.CASCADE, to='core.School'), + ), + migrations.AddField( + model_name='subject', + name='school', + field=models.ForeignKey(default=biscuit.core.util.core_helpers.get_current_school, on_delete=django.db.models.deletion.CASCADE, to='core.School'), + ), + migrations.AddField( + model_name='timeperiod', + name='school', + field=models.ForeignKey(default=biscuit.core.util.core_helpers.get_current_school, on_delete=django.db.models.deletion.CASCADE, to='core.School'), + ), + migrations.AlterField( + model_name='room', + name='name', + field=models.CharField(max_length=30, verbose_name='Long name'), + ), + migrations.AlterField( + model_name='room', + name='short_name', + field=models.CharField(max_length=10, verbose_name='Short name, e.g. room number'), + ), + migrations.AlterField( + model_name='subject', + name='abbrev', + field=models.CharField(max_length=10, verbose_name='Abbreviation of subject in timetable'), + ), + migrations.AlterField( + model_name='subject', + name='name', + field=models.CharField(max_length=30, verbose_name='Long name of subject'), + ), + migrations.AlterUniqueTogether( + name='room', + unique_together={('school', 'name'), ('school', 'short_name')}, + ), + migrations.AlterUniqueTogether( + name='subject', + unique_together={('school', 'abbrev'), ('school', 'name')}, + ), + migrations.AlterUniqueTogether( + name='timeperiod', + unique_together={('school', 'weekday', 'period')}, + ), + ] diff --git a/biscuit/apps/chronos/models.py b/biscuit/apps/chronos/models.py index 0d4eec2df40dc0d63c1761b13605d34cdba8f26a..0242663d63070a238b4f3b253a321d051a14c637 100644 --- a/biscuit/apps/chronos/models.py +++ b/biscuit/apps/chronos/models.py @@ -5,10 +5,12 @@ from django.core import validators from django.db import models from django.utils.translation import ugettext_lazy as _ +from biscuit.core.mixins import SchoolRelated + from .util import current_week -class TimePeriod(models.Model): +class TimePeriod(SchoolRelated): WEEKDAY_CHOICES = [ (0, _('Sunday')), (1, _('Monday')), @@ -39,15 +41,15 @@ class TimePeriod(models.Model): return periods class Meta: - unique_together = [['weekday', 'period']] + unique_together = [['school', 'weekday', 'period']] ordering = ['weekday', 'period'] -class Subject(models.Model): +class Subject(SchoolRelated): abbrev = models.CharField(verbose_name=_( - 'Abbreviation of subject in timetable'), max_length=10, unique=True) + 'Abbreviation of subject in timetable'), max_length=10) name = models.CharField(verbose_name=_( - 'Long name of subject'), max_length=30, unique=True) + 'Long name of subject'), max_length=30) colour_fg = models.CharField(verbose_name=_('Foreground colour in timetable'), blank=True, validators=[ validators.RegexValidator(r'#[0-9A-F]{6}')], max_length=7) @@ -59,22 +61,24 @@ class Subject(models.Model): class Meta: ordering = ['name', 'abbrev'] + unique_together = [['school', 'abbrev'], ['school', 'name']] -class Room(models.Model): +class Room(SchoolRelated): short_name = models.CharField(verbose_name=_( - 'Short name, e.g. room number'), max_length=10, unique=True) + 'Short name, e.g. room number'), max_length=10) name = models.CharField(verbose_name=_('Long name'), - max_length=30, unique=True) + max_length=30) def __str__(self) -> str: return '%s (%s)' % (self.name, self.short_name) class Meta: ordering = ['name', 'short_name'] + unique_together = [['school', 'short_name'], ['school', 'name']] -class Lesson(models.Model): +class Lesson(SchoolRelated): subject = models.ForeignKey( 'Subject', on_delete=models.CASCADE, related_name='lessons') teachers = models.ManyToManyField('core.Person', related_name='lessons') @@ -99,7 +103,7 @@ class Lesson(models.Model): ordering = ['date_start'] -class LessonSubstitution(models.Model): +class LessonSubstitution(SchoolRelated): week = models.IntegerField(verbose_name=_('Week'), default=current_week) @@ -118,14 +122,14 @@ class LessonSubstitution(models.Model): 'lesson_period__period__weekday', 'lesson_period__period__period'] -class LessonPeriod(models.Model): +class LessonPeriod(SchoolRelated): lesson = models.ForeignKey('Lesson', models.CASCADE, related_name='lesson_periods') period = models.ForeignKey('TimePeriod', models.CASCADE, related_name='lesson_periods') room = models.ForeignKey('Room', models.CASCADE, null=True, related_name='lesson_periods') def get_substitution(self, week: Optional[int] = None) -> LessonSubstitution: - wanted_week = week or current_week() + wanted_week = week or getattr(self, '_week', None) or current_week() return self.substitutions.filter(week=wanted_week).first() def get_subject(self) -> Optional[Subject]: diff --git a/biscuit/apps/chronos/templates/chronos/tt_week.html b/biscuit/apps/chronos/templates/chronos/tt_week.html index 8cbf5cc9a36fa872ed641f09057b7037611e047a..973eb483ce7751baa89e805f462927fc7453712b 100644 --- a/biscuit/apps/chronos/templates/chronos/tt_week.html +++ b/biscuit/apps/chronos/templates/chronos/tt_week.html @@ -11,8 +11,21 @@ {% block page_title %}{% blocktrans %}Timetable{% endblocktrans %}{% endblock %} {% block content %} +<div class="d-flex justify-content-between"> + <div> + <h1>{{ day }}</h1> + </div> + <div class="btn-group" role="group" aria-label="Day actions"> + <a href="{% url 'timetable_by_week' week_prev %}" class="btn btn-secondary"> + {% fa 'arrow-left' %} + </a> + <a href="{% url 'timetable_by_week' week_next %}" class="btn btn-secondary"> + {% fa 'arrow-right' %} + </a> + </div> +</div> + <form method="get"> - {% csrf_token %} <ul id="timetable_select_form"> {{ select_form.as_ul }} </ul> diff --git a/biscuit/apps/chronos/urls.py b/biscuit/apps/chronos/urls.py index 87e879b7a84ada0d14688c8e9c2f89ff0619f5cd..e3a9751a63ef7207abe49fcfb5277dde07143fb9 100644 --- a/biscuit/apps/chronos/urls.py +++ b/biscuit/apps/chronos/urls.py @@ -5,6 +5,7 @@ from . import views urlpatterns = [ path('timetable', views.timetable, name='timetable'), + path('timetable/<int:week>', views.timetable, name='timetable_by_week'), path('lessons', views.lessons_day, name='lessons_day'), path('lessons/<when>', views.lessons_day, name='lessons_day_by_date'), path('lessons/<int:id_>/<int:week>/substition', views.edit_substitution, name='edit_substitution') diff --git a/biscuit/apps/chronos/views.py b/biscuit/apps/chronos/views.py index 079f295c1891da45e62eddaf687b0139f1c070c6..6fa61d1227b1203e3337aa3c9ad48000ace91ff4 100644 --- a/biscuit/apps/chronos/views.py +++ b/biscuit/apps/chronos/views.py @@ -1,13 +1,15 @@ -from collections import OrderedDict from datetime import date, datetime, timedelta - +from collections import OrderedDict from typing import Optional + from django.contrib.auth.decorators import login_required -from django.db.models import Max, Min +from django.db.models import Max, Min, Q from django.http import HttpRequest, HttpResponse from django.shortcuts import get_object_or_404, redirect, render +from django.views.decorators.cache import cache_page from django.urls import reverse from django.utils.translation import ugettext as _ + from django_tables2 import RequestConfig from biscuit.core.decorators import admin_required @@ -20,16 +22,22 @@ from .tables import LessonsTable @login_required -def timetable(request: HttpRequest) -> HttpResponse: +@cache_page(60 * 60 * 12) +def timetable(request: HttpRequest, week: Optional[int] = None) -> HttpResponse: context = {} - lesson_periods = LessonPeriod.objects.all() + wanted_week = week or current_week() + + lesson_periods = LessonPeriod.objects.filter( + lesson__date_start__gte=week_days(wanted_week)[0], + lesson__date_end__lte=week_days(wanted_week)[-1] + ).extra(select={'_week': wanted_week}) if request.GET.get('group', None) or request.GET.get('teacher', None) or request.GET.get('room', None): # Incrementally filter lesson periods by GET parameters if 'group' in request.GET and request.GET['group']: lesson_periods = lesson_periods.filter( - lesson__groups__pk=int(request.GET['group'])) + Q(lesson__groups__pk=int(request.GET['group'])) | Q(lesson__groups__parent_groups__pk=int(request.GET['group']))) if 'teacher' in request.GET and request.GET['teacher']: lesson_periods = lesson_periods.filter( lesson__teachers__pk=int(request.GET['teacher'])) @@ -78,7 +86,9 @@ def timetable(request: HttpRequest) -> HttpResponse: context['lesson_periods'] = OrderedDict(sorted(per_day.items())) context['periods'] = TimePeriod.get_times_dict() context['weekdays'] = dict(TimePeriod.WEEKDAY_CHOICES) - context['current_week'] = current_week() + context['week'] = wanted_week + context['week_prev'] = wanted_week - 1 + context['week_next'] = wanted_week + 1 context['select_form'] = select_form return render(request, 'chronos/tt_week.html', context)