Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • AlekSIS/official/AlekSIS-App-Chronos
  • sunweaver/AlekSIS-App-Chronos
  • sggua/AlekSIS-App-Chronos
  • tincmeKdenka/AlekSIS-App-Chronos
  • ligquamacti/AlekSIS-App-Chronos
  • 1crotatilhe/AlekSIS-App-Chronos
  • 1compluningi/AlekSIS-App-Chronos
  • starwardcarfi/AlekSIS-App-Chronos
  • ceohecholeg/AlekSIS-App-Chronos
  • 7quecontranchi/AlekSIS-App-Chronos
  • 8evsubcesza/AlekSIS-App-Chronos
  • unscinKibdzu/AlekSIS-App-Chronos
  • delucPchondmu/AlekSIS-App-Chronos
13 results
Show changes
Commits on Source (1)
Showing
with 680 additions and 311 deletions
import pkg_resources
try:
__version__ = pkg_resources.get_distribution('BiscuIT-App-Chronos').version
__version__ = pkg_resources.get_distribution("BiscuIT-App-Chronos").version
except Exception:
__version__ = 'unknown'
__version__ = "unknown"
default_app_config = 'biscuit.apps.chronos.apps.ChronosConfig'
default_app_config = "biscuit.apps.chronos.apps.ChronosConfig"
......@@ -2,5 +2,5 @@ from biscuit.core.util.apps import AppConfig
class ChronosConfig(AppConfig):
name = 'biscuit.apps.chronos'
verbose_name = 'BiscuIT - Chronos (Timetables)'
name = "biscuit.apps.chronos"
verbose_name = "BiscuIT - Chronos (Timetables)"
from django import forms
from django.db.models import Count
from django.utils.translation import ugettext_lazy as _
from django_select2.forms import ModelSelect2MultipleWidget, Select2Widget
from biscuit.core.models import Person, Group
from biscuit.core.models import Group, Person
from .models import Room, LessonSubstitution, Subject, LessonPeriod
from .models import LessonPeriod, LessonSubstitution, Room, Subject
class SelectForm(forms.Form):
group = forms.ModelChoiceField(
queryset=Group.objects.annotate(lessons_count=Count('lessons')).filter(lessons_count__gt=0),
label=_('Group'), required=False, widget=Select2Widget)
queryset=Group.objects.annotate(lessons_count=Count("lessons")).filter(
lessons_count__gt=0
),
label=_("Group"),
required=False,
widget=Select2Widget,
)
teacher = forms.ModelChoiceField(
queryset=Person.objects.annotate(lessons_count=Count(
'lessons_as_teacher')).filter(lessons_count__gt=0),
label=_('Teacher'), required=False, widget=Select2Widget)
queryset=Person.objects.annotate(
lessons_count=Count("lessons_as_teacher")
).filter(lessons_count__gt=0),
label=_("Teacher"),
required=False,
widget=Select2Widget,
)
room = forms.ModelChoiceField(
queryset=Room.objects.annotate(lessons_count=Count(
'lesson_periods')).filter(lessons_count__gt=0),
label=_('Room'), required=False, widget=Select2Widget)
queryset=Room.objects.annotate(lessons_count=Count("lesson_periods")).filter(
lessons_count__gt=0
),
label=_("Room"),
required=False,
widget=Select2Widget,
)
class LessonSubstitutionForm(forms.ModelForm):
class Meta:
model = LessonSubstitution
fields = ['week', 'lesson_period', 'subject', 'teachers', 'room', 'cancelled']
fields = ["week", "lesson_period", "subject", "teachers", "room", "cancelled"]
widgets = {
'teachers': ModelSelect2MultipleWidget(search_fields=['first_name__icontains', 'last_name__icontains', 'short_name__icontains'])
"teachers": ModelSelect2MultipleWidget(
search_fields=[
"first_name__icontains",
"last_name__icontains",
"short_name__icontains",
]
)
}
from django.utils.translation import ugettext_lazy as _
MENUS = {
'NAV_MENU_CORE': [
"NAV_MENU_CORE": [
{
'name': _('Timetables'),
'url': '#',
'root': True,
'validators': ['menu_generator.validators.is_authenticated', 'biscuit.core.util.core_helpers.has_person'],
'submenu': [
"name": _("Timetables"),
"url": "#",
"root": True,
"validators": [
"menu_generator.validators.is_authenticated",
"biscuit.core.util.core_helpers.has_person",
],
"submenu": [
{
'name': _('Timetable'),
'url': 'timetable',
'validators': ['menu_generator.validators.is_authenticated']
"name": _("Timetable"),
"url": "timetable",
"validators": ["menu_generator.validators.is_authenticated"],
},
{
'name': _('Daily lessons'),
'url': 'lessons_day',
'validators': ['menu_generator.validators.is_authenticated']
"name": _("Daily lessons"),
"url": "lessons_day",
"validators": ["menu_generator.validators.is_authenticated"],
},
{
'name': _('Substitutions'),
'url': 'substitutions',
'validators': ['menu_generator.validators.is_authenticated']
}
]
"name": _("Substitutions"),
"url": "substitutions",
"validators": ["menu_generator.validators.is_authenticated"],
},
],
}
]
}
# Generated by Django 2.2.5 on 2019-09-03 18:30
import biscuit.apps.chronos.util
import biscuit.core.util.core_helpers
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
from django.db import migrations, models
import biscuit.apps.chronos.util
import biscuit.core.util.core_helpers
class Migration(migrations.Migration):
......@@ -12,123 +13,335 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('core', '0001_initial'),
("core", "0001_initial"),
]
operations = [
migrations.CreateModel(
name='Lesson',
name="Lesson",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date_start', models.DateField(null=True, verbose_name='Effective start date of lesson')),
('date_end', models.DateField(null=True, verbose_name='Effective end date of lesson')),
('groups', models.ManyToManyField(related_name='lessons', to='core.Group')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"date_start",
models.DateField(
null=True, verbose_name="Effective start date of lesson"
),
),
(
"date_end",
models.DateField(
null=True, verbose_name="Effective end date of lesson"
),
),
(
"groups",
models.ManyToManyField(related_name="lessons", to="core.Group"),
),
],
options={
'ordering': ['date_start'],
},
options={"ordering": ["date_start"],},
),
migrations.CreateModel(
name='LessonPeriod',
name="LessonPeriod",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('lesson', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lesson_periods', to='chronos.Lesson')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"lesson",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="lesson_periods",
to="chronos.Lesson",
),
),
],
options={
'ordering': ['lesson__date_start', 'period__weekday', 'period__period'],
"ordering": ["lesson__date_start", "period__weekday", "period__period"],
},
),
migrations.CreateModel(
name='TimePeriod',
name="TimePeriod",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('weekday', models.PositiveSmallIntegerField(choices=[(0, 'Sunday'), (1, 'Monday'), (2, 'Tuesday'), (3, 'Wednesday'), (4, 'Thursday'), (5, 'Friday'), (6, 'Saturday')], verbose_name='Week day')),
('period', models.PositiveSmallIntegerField(verbose_name='Number of period')),
('time_start', models.TimeField(verbose_name='Time the period starts')),
('time_end', models.TimeField(verbose_name='Time the period ends')),
('school', models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='core.School')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"weekday",
models.PositiveSmallIntegerField(
choices=[
(0, "Sunday"),
(1, "Monday"),
(2, "Tuesday"),
(3, "Wednesday"),
(4, "Thursday"),
(5, "Friday"),
(6, "Saturday"),
],
verbose_name="Week day",
),
),
(
"period",
models.PositiveSmallIntegerField(verbose_name="Number of period"),
),
("time_start", models.TimeField(verbose_name="Time the period starts")),
("time_end", models.TimeField(verbose_name="Time the period ends")),
(
"school",
models.ForeignKey(
default=1,
on_delete=django.db.models.deletion.CASCADE,
to="core.School",
),
),
],
options={
'ordering': ['weekday', 'period'],
'unique_together': {('school', 'weekday', 'period')},
"ordering": ["weekday", "period"],
"unique_together": {("school", "weekday", "period")},
},
),
migrations.CreateModel(
name='Subject',
name="Subject",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('abbrev', models.CharField(max_length=10, verbose_name='Abbreviation of subject in timetable')),
('name', models.CharField(max_length=30, verbose_name='Long name of subject')),
('colour_fg', models.CharField(blank=True, max_length=7, validators=[django.core.validators.RegexValidator('#[0-9A-F]{6}')], verbose_name='Foreground colour in timetable')),
('colour_bg', models.CharField(blank=True, max_length=7, validators=[django.core.validators.RegexValidator('#[0-9A-F]{6}')], verbose_name='Background colour in timetable')),
('school', models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='core.School')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"abbrev",
models.CharField(
max_length=10,
verbose_name="Abbreviation of subject in timetable",
),
),
(
"name",
models.CharField(
max_length=30, verbose_name="Long name of subject"
),
),
(
"colour_fg",
models.CharField(
blank=True,
max_length=7,
validators=[
django.core.validators.RegexValidator("#[0-9A-F]{6}")
],
verbose_name="Foreground colour in timetable",
),
),
(
"colour_bg",
models.CharField(
blank=True,
max_length=7,
validators=[
django.core.validators.RegexValidator("#[0-9A-F]{6}")
],
verbose_name="Background colour in timetable",
),
),
(
"school",
models.ForeignKey(
default=1,
on_delete=django.db.models.deletion.CASCADE,
to="core.School",
),
),
],
options={
'ordering': ['name', 'abbrev'],
'unique_together': {('school', 'abbrev'), ('school', 'name')},
"ordering": ["name", "abbrev"],
"unique_together": {("school", "abbrev"), ("school", "name")},
},
),
migrations.CreateModel(
name='Room',
name="Room",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('short_name', models.CharField(max_length=10, verbose_name='Short name, e.g. room number')),
('name', models.CharField(max_length=30, verbose_name='Long name')),
('school', models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='core.School')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"short_name",
models.CharField(
max_length=10, verbose_name="Short name, e.g. room number"
),
),
("name", models.CharField(max_length=30, verbose_name="Long name")),
(
"school",
models.ForeignKey(
default=1,
on_delete=django.db.models.deletion.CASCADE,
to="core.School",
),
),
],
options={
'ordering': ['name', 'short_name'],
'unique_together': {('school', 'name'), ('school', 'short_name')},
"ordering": ["name", "short_name"],
"unique_together": {("school", "name"), ("school", "short_name")},
},
),
migrations.CreateModel(
name='LessonSubstitution',
name="LessonSubstitution",
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('week', models.IntegerField(default=biscuit.apps.chronos.util.CalendarWeek.current_week, verbose_name='Week')),
('lesson_period', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='substitutions', to='chronos.LessonPeriod')),
('room', models.ForeignKey(null=True, blank=True, on_delete=django.db.models.deletion.CASCADE, to='chronos.Room', verbose_name='Room')),
('school', models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='core.School')),
('subject', models.ForeignKey(null=True, blank=True, on_delete=django.db.models.deletion.CASCADE, related_name='lesson_substitutions', to='chronos.Subject', verbose_name='Subject')),
('teachers', models.ManyToManyField(blank=True, related_name='lesson_substitutions', to='core.Person')),
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"week",
models.IntegerField(
default=biscuit.apps.chronos.util.CalendarWeek.current_week,
verbose_name="Week",
),
),
(
"lesson_period",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="substitutions",
to="chronos.LessonPeriod",
),
),
(
"room",
models.ForeignKey(
null=True,
blank=True,
on_delete=django.db.models.deletion.CASCADE,
to="chronos.Room",
verbose_name="Room",
),
),
(
"school",
models.ForeignKey(
default=1,
on_delete=django.db.models.deletion.CASCADE,
to="core.School",
),
),
(
"subject",
models.ForeignKey(
null=True,
blank=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="lesson_substitutions",
to="chronos.Subject",
verbose_name="Subject",
),
),
(
"teachers",
models.ManyToManyField(
blank=True,
related_name="lesson_substitutions",
to="core.Person",
),
),
],
options={
'ordering': ['lesson_period__lesson__date_start', 'week', 'lesson_period__period__weekday', 'lesson_period__period__period'],
"ordering": [
"lesson_period__lesson__date_start",
"week",
"lesson_period__period__weekday",
"lesson_period__period__period",
],
},
),
migrations.AddField(
model_name='lessonperiod',
name='period',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lesson_periods', to='chronos.TimePeriod'),
model_name="lessonperiod",
name="period",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="lesson_periods",
to="chronos.TimePeriod",
),
),
migrations.AddField(
model_name='lessonperiod',
name='room',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='lesson_periods', to='chronos.Room'),
model_name="lessonperiod",
name="room",
field=models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="lesson_periods",
to="chronos.Room",
),
),
migrations.AddField(
model_name='lessonperiod',
name='school',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='core.School'),
model_name="lessonperiod",
name="school",
field=models.ForeignKey(
default=1, on_delete=django.db.models.deletion.CASCADE, to="core.School"
),
),
migrations.AddField(
model_name='lesson',
name='periods',
field=models.ManyToManyField(related_name='lessons', through='chronos.LessonPeriod', to='chronos.TimePeriod'),
model_name="lesson",
name="periods",
field=models.ManyToManyField(
related_name="lessons",
through="chronos.LessonPeriod",
to="chronos.TimePeriod",
),
),
migrations.AddField(
model_name='lesson',
name='school',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='core.School'),
model_name="lesson",
name="school",
field=models.ForeignKey(
default=1, on_delete=django.db.models.deletion.CASCADE, to="core.School"
),
),
migrations.AddField(
model_name='lesson',
name='subject',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lessons', to='chronos.Subject'),
model_name="lesson",
name="subject",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="lessons",
to="chronos.Subject",
),
),
migrations.AddField(
model_name='lesson',
name='teachers',
field=models.ManyToManyField(related_name='lessons', to='core.Person'),
model_name="lesson",
name="teachers",
field=models.ManyToManyField(related_name="lessons", to="core.Person"),
),
]
......@@ -6,25 +6,31 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0001_initial'),
('chronos', '0001_initial'),
("core", "0001_initial"),
("chronos", "0001_initial"),
]
operations = [
migrations.AlterUniqueTogether(
name='lessonsubstitution',
unique_together={('school', 'lesson_period', 'week')},
name="lessonsubstitution",
unique_together={("school", "lesson_period", "week")},
),
migrations.AddIndex(
model_name='lesson',
index=models.Index(fields=['date_start', 'date_end'], name='chronos_les_date_st_5ecc62_idx'),
model_name="lesson",
index=models.Index(
fields=["date_start", "date_end"], name="chronos_les_date_st_5ecc62_idx"
),
),
migrations.AddIndex(
model_name='lessonperiod',
index=models.Index(fields=['lesson', 'period'], name='chronos_les_lesson__05250e_idx'),
model_name="lessonperiod",
index=models.Index(
fields=["lesson", "period"], name="chronos_les_lesson__05250e_idx"
),
),
migrations.AddIndex(
model_name='timeperiod',
index=models.Index(fields=['time_start', 'time_end'], name='chronos_tim_time_st_491e4c_idx'),
model_name="timeperiod",
index=models.Index(
fields=["time_start", "time_end"], name="chronos_tim_time_st_491e4c_idx"
),
),
]
......@@ -6,17 +6,22 @@ from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('chronos', '0002_db_indexes'),
("chronos", "0002_db_indexes"),
]
operations = [
migrations.AddField(
model_name='lessonsubstitution',
name='cancelled',
model_name="lessonsubstitution",
name="cancelled",
field=models.BooleanField(default=False),
),
migrations.AddConstraint(
model_name='lessonsubstitution',
constraint=models.CheckConstraint(check=models.Q(('cancelled', True), ('subject__isnull', False), _negated=True), name='either_substituted_or_cancelled'),
model_name="lessonsubstitution",
constraint=models.CheckConstraint(
check=models.Q(
("cancelled", True), ("subject__isnull", False), _negated=True
),
name="either_substituted_or_cancelled",
),
),
]
......@@ -6,12 +6,11 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('chronos', '0003_substitution_cancelled_or_subject'),
("chronos", "0003_substitution_cancelled_or_subject"),
]
operations = [
migrations.AlterUniqueTogether(
name='room',
unique_together={('school', 'short_name')},
name="room", unique_together={("school", "short_name")},
),
]
......@@ -22,10 +22,11 @@ class LessonPeriodManager(models.Manager):
def get_queryset(self):
""" Ensures all related lesson data is loaded as well. """
return super().get_queryset().select_related(
'lesson', 'lesson__subject', 'period', 'room'
).prefetch_related(
'lesson__groups', 'lesson__teachers', 'substitutions'
return (
super()
.get_queryset()
.select_related("lesson", "lesson__subject", "period", "room")
.prefetch_related("lesson__groups", "lesson__teachers", "substitutions")
)
......@@ -41,8 +42,8 @@ class LessonPeriodQuerySet(models.QuerySet):
""" Filter for all lessons within a calendar week. """
return self.within_dates(
wanted_week[0] + timedelta(days=1) * (models.F('period__weekday') - 1),
wanted_week[0] + timedelta(days=1) * (models.F('period__weekday') - 1)
wanted_week[0] + timedelta(days=1) * (models.F("period__weekday") - 1),
wanted_week[0] + timedelta(days=1) * (models.F("period__weekday") - 1),
).annotate_week(wanted_week)
def on_day(self, day: date):
......@@ -50,9 +51,11 @@ class LessonPeriodQuerySet(models.QuerySet):
week, weekday = week_weekday_from_date(day)
return self.within_dates(day, day).filter(
period__weekday=weekday
).annotate_week(week)
return (
self.within_dates(day, day)
.filter(period__weekday=weekday)
.annotate_week(week)
)
def at_time(self, when: Optional[datetime] = None):
""" Filter for the lessons taking place at a certain point in time. """
......@@ -65,32 +68,39 @@ class LessonPeriodQuerySet(models.QuerySet):
lesson__date_end__gte=now.date(),
period__weekday=now.isoweekday(),
period__time_start__lte=now.time(),
period__time_end__gte=now.time()
period__time_end__gte=now.time(),
).annotate_week(week)
def filter_participant(self, person: Union[Person, int]):
""" Filter for all lessons a participant (student) attends. """
return self.filter(
Q(lesson__groups__members=person) | Q(lesson__groups__parent_groups__members=person))
Q(lesson__groups__members=person)
| Q(lesson__groups__parent_groups__members=person)
)
def filter_group(self, group: Union[Group, int]):
""" Filter for all lessons a group (class) regularly attends. """
return self.filter(
Q(lesson__groups=group) | Q(lesson__groups__parent_groups=group))
Q(lesson__groups=group) | Q(lesson__groups__parent_groups=group)
)
def filter_teacher(self, teacher: Union[Person, int]):
""" Filter for all lessons given by a certain teacher. """
return self.filter(
Q(substitutions__teachers=teacher, substitutions__week=models.F('_week')) | Q(lesson__teachers=teacher))
Q(substitutions__teachers=teacher, substitutions__week=models.F("_week"))
| Q(lesson__teachers=teacher)
)
def filter_room(self, room: Union[Room, int]):
""" Filter for all lessons taking part in a certain room. """
return self.filter(
Q(substitutions__room=room, substitutions__week=models.F('_week')) | Q(room=room))
Q(substitutions__room=room, substitutions__week=models.F("_week"))
| Q(room=room)
)
def annotate_week(self, week: Union[CalendarWeek, int]):
""" Annotate all lessons in the QuerySet with the number of the provided calendar week. """
......@@ -100,9 +110,7 @@ class LessonPeriodQuerySet(models.QuerySet):
else:
week_num = week
return self.annotate(
_week=models.Value(week_num, models.IntegerField())
)
return self.annotate(_week=models.Value(week_num, models.IntegerField()))
def next(self, reference: LessonPeriod, offset: Optional[int] = 1) -> LessonPeriod:
""" Get another lesson in an ordered set of lessons.
......@@ -112,7 +120,7 @@ class LessonPeriodQuerySet(models.QuerySet):
previous lesson can be selected.
"""
index = list(self.values_list('id', flat=True)).index(reference.id)
index = list(self.values_list("id", flat=True)).index(reference.id)
next_index = index + offset
if next_index > self.count() - 1:
......@@ -132,35 +140,40 @@ class LessonPeriodQuerySet(models.QuerySet):
All three fields are filtered, in order.
"""
if query_data.get('group', None):
return self.filter_group(int(query_data['group']))
if query_data.get('teacher', None):
return self.filter_teacher(int(query_data['teacher']))
if query_data.get('room', None):
return self.filter_room(int(query_data['room']))
if query_data.get("group", None):
return self.filter_group(int(query_data["group"]))
if query_data.get("teacher", None):
return self.filter_teacher(int(query_data["teacher"]))
if query_data.get("room", None):
return self.filter_room(int(query_data["room"]))
class TimePeriod(models.Model):
WEEKDAY_CHOICES = [
(0, _('Sunday')),
(1, _('Monday')),
(2, _('Tuesday')),
(3, _('Wednesday')),
(4, _('Thursday')),
(5, _('Friday')),
(6, _('Saturday'))
(0, _("Sunday")),
(1, _("Monday")),
(2, _("Tuesday")),
(3, _("Wednesday")),
(4, _("Thursday")),
(5, _("Friday")),
(6, _("Saturday")),
]
weekday = models.PositiveSmallIntegerField(verbose_name=_(
'Week day'), choices=WEEKDAY_CHOICES)
period = models.PositiveSmallIntegerField(
verbose_name=_('Number of period'))
weekday = models.PositiveSmallIntegerField(
verbose_name=_("Week day"), choices=WEEKDAY_CHOICES
)
period = models.PositiveSmallIntegerField(verbose_name=_("Number of period"))
time_start = models.TimeField(verbose_name=_('Time the period starts'))
time_end = models.TimeField(verbose_name=_('Time the period ends'))
time_start = models.TimeField(verbose_name=_("Time the period starts"))
time_end = models.TimeField(verbose_name=_("Time the period ends"))
def __str__(self) -> str:
return '%s, %d. period (%s - %s)' % (self.weekday, self.period, self.time_start, self.time_end)
return "%s, %d. period (%s - %s)" % (
self.weekday,
self.period,
self.time_start,
self.time_end,
)
@classmethod
def get_times_dict(cls) -> Dict[int, Tuple[datetime, datetime]]:
......@@ -175,113 +188,143 @@ class TimePeriod(models.Model):
wanted_week = week
else:
year = date.today().year
week_number = week or getattr(self, '_week', None) or CalendarWeek().week
week_number = week or getattr(self, "_week", None) or CalendarWeek().week
if week_number < self.school.current_term.date_start.isocalendar()[1]:
year += 1
wanted_week = CalendarWeek(year=year, week=week_number)
return wanted_week[self.weekday-1]
return wanted_week[self.weekday - 1]
class Meta:
unique_together = [['weekday', 'period']]
ordering = ['weekday', 'period']
indexes = [models.Index(fields=['time_start', 'time_end'])]
unique_together = [["weekday", "period"]]
ordering = ["weekday", "period"]
indexes = [models.Index(fields=["time_start", "time_end"])]
class Subject(models.Model):
abbrev = models.CharField(verbose_name=_(
'Abbreviation of subject in timetable'), max_length=10, unique=True)
name = models.CharField(verbose_name=_(
'Long name of subject'), max_length=30, unique=True)
colour_fg = models.CharField(verbose_name=_('Foreground colour in timetable'), blank=True, validators=[
validators.RegexValidator(r'#[0-9A-F]{6}')], max_length=7)
colour_bg = models.CharField(verbose_name=_('Background colour in timetable'), blank=True, validators=[
validators.RegexValidator(r'#[0-9A-F]{6}')], max_length=7)
abbrev = models.CharField(
verbose_name=_("Abbreviation of subject in timetable"),
max_length=10,
unique=True,
)
name = models.CharField(
verbose_name=_("Long name of subject"), max_length=30, unique=True
)
colour_fg = models.CharField(
verbose_name=_("Foreground colour in timetable"),
blank=True,
validators=[validators.RegexValidator(r"#[0-9A-F]{6}")],
max_length=7,
)
colour_bg = models.CharField(
verbose_name=_("Background colour in timetable"),
blank=True,
validators=[validators.RegexValidator(r"#[0-9A-F]{6}")],
max_length=7,
)
def __str__(self) -> str:
return '%s - %s' % (self.abbrev, self.name)
return "%s - %s" % (self.abbrev, self.name)
class Meta:
ordering = ['name', 'abbrev']
ordering = ["name", "abbrev"]
class Room(models.Model):
short_name = models.CharField(verbose_name=_(
'Short name, e.g. room number'), max_length=10, unique=True)
name = models.CharField(verbose_name=_('Long name'),
max_length=30)
short_name = models.CharField(
verbose_name=_("Short name, e.g. room number"), max_length=10, unique=True
)
name = models.CharField(verbose_name=_("Long name"), max_length=30)
def __str__(self) -> str:
return '%s (%s)' % (self.name, self.short_name)
return "%s (%s)" % (self.name, self.short_name)
class Meta:
ordering = ['name', 'short_name']
ordering = ["name", "short_name"]
class Lesson(models.Model):
subject = models.ForeignKey(
'Subject', on_delete=models.CASCADE, related_name='lessons')
teachers = models.ManyToManyField('core.Person', related_name='lessons_as_teacher')
"Subject", on_delete=models.CASCADE, related_name="lessons"
)
teachers = models.ManyToManyField("core.Person", related_name="lessons_as_teacher")
periods = models.ManyToManyField(
'TimePeriod', related_name='lessons', through='LessonPeriod')
groups = models.ManyToManyField('core.Group', related_name='lessons')
"TimePeriod", related_name="lessons", through="LessonPeriod"
)
groups = models.ManyToManyField("core.Group", related_name="lessons")
date_start = models.DateField(verbose_name=_(
'Effective start date of lesson'), null=True)
date_end = models.DateField(verbose_name=_(
'Effective end date of lesson'), null=True)
date_start = models.DateField(
verbose_name=_("Effective start date of lesson"), null=True
)
date_end = models.DateField(
verbose_name=_("Effective end date of lesson"), null=True
)
@property
def teacher_names(self, sep: Optional[str] = ', ') -> str:
def teacher_names(self, sep: Optional[str] = ", ") -> str:
return sep.join([teacher.full_name for teacher in self.teachers.all()])
@property
def group_names(self, sep: Optional[str] = ', ') -> str:
def group_names(self, sep: Optional[str] = ", ") -> str:
return sep.join([group.short_name for group in self.groups.all()])
def get_calendar_week(self, week: int):
year = self.date_start.year
if week < int(self.date_start.strftime('%V')):
if week < int(self.date_start.strftime("%V")):
year += 1
return CalendarWeek(year=year, week=week)
class Meta:
ordering = ['date_start']
indexes = [models.Index(fields=['date_start', 'date_end'])]
ordering = ["date_start"]
indexes = [models.Index(fields=["date_start", "date_end"])]
class LessonSubstitution(models.Model):
week = models.IntegerField(verbose_name=_('Week'),
default=CalendarWeek.current_week)
week = models.IntegerField(
verbose_name=_("Week"), default=CalendarWeek.current_week
)
lesson_period = models.ForeignKey(
'LessonPeriod', models.CASCADE, 'substitutions')
lesson_period = models.ForeignKey("LessonPeriod", models.CASCADE, "substitutions")
subject = models.ForeignKey(
'Subject', on_delete=models.CASCADE,
related_name='lesson_substitutions', null=True, blank=True, verbose_name=_('Subject'))
teachers = models.ManyToManyField('core.Person',
related_name='lesson_substitutions', blank=True)
room = models.ForeignKey('Room', models.CASCADE, null=True, blank=True, verbose_name=_('Room'))
"Subject",
on_delete=models.CASCADE,
related_name="lesson_substitutions",
null=True,
blank=True,
verbose_name=_("Subject"),
)
teachers = models.ManyToManyField(
"core.Person", related_name="lesson_substitutions", blank=True
)
room = models.ForeignKey(
"Room", models.CASCADE, null=True, blank=True, verbose_name=_("Room")
)
cancelled = models.BooleanField(default=False)
def clean(self) -> None:
if self.subject and self.cancelled:
raise ValidationError(_('Lessons can only be either substituted or cancelled.'))
raise ValidationError(
_("Lessons can only be either substituted or cancelled.")
)
class Meta:
unique_together = [['lesson_period', 'week']]
ordering = ['lesson_period__lesson__date_start', 'week',
'lesson_period__period__weekday', 'lesson_period__period__period']
unique_together = [["lesson_period", "week"]]
ordering = [
"lesson_period__lesson__date_start",
"week",
"lesson_period__period__weekday",
"lesson_period__period__period",
]
constraints = [
models.CheckConstraint(
check=~Q(cancelled=True, subject__isnull=False),
name='either_substituted_or_cancelled'
name="either_substituted_or_cancelled",
)
]
......@@ -289,13 +332,17 @@ class LessonSubstitution(models.Model):
class LessonPeriod(models.Model, ExtensibleModel):
objects = LessonPeriodManager.from_queryset(LessonPeriodQuerySet)()
lesson = models.ForeignKey('Lesson', models.CASCADE, related_name='lesson_periods')
period = models.ForeignKey('TimePeriod', models.CASCADE, related_name='lesson_periods')
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')
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 getattr(self, '_week', None) or CalendarWeek().week
wanted_week = week or getattr(self, "_week", None) or CalendarWeek().week
# We iterate over all substitutions because this can make use of
# prefetching when this model is loaded from outside, in contrast
......@@ -323,17 +370,20 @@ class LessonPeriod(models.Model, ExtensibleModel):
else:
return self.room
def get_teacher_names(self, sep: Optional[str] = ', ') -> str:
def get_teacher_names(self, sep: Optional[str] = ", ") -> str:
return sep.join([teacher.full_name for teacher in self.get_teachers().all()])
def get_groups(self) -> models.query.QuerySet:
return self.lesson.groups
def __str__(self) -> str:
return '%s, %d., %s, %s' % (self.period.get_weekday_display(), self.period.period,
', '.join(list(self.lesson.groups.values_list('short_name', flat=True))),
self.lesson.subject.name)
return "%s, %d., %s, %s" % (
self.period.get_weekday_display(),
self.period.period,
", ".join(list(self.lesson.groups.values_list("short_name", flat=True))),
self.lesson.subject.name,
)
class Meta:
ordering = ['lesson__date_start', 'period__weekday', 'period__period']
indexes = [models.Index(fields=['lesson', 'period'])]
ordering = ["lesson__date_start", "period__weekday", "period__period"]
indexes = [models.Index(fields=["lesson", "period"])]
......@@ -3,43 +3,59 @@ from __future__ import annotations
from typing import Optional
from django.utils.translation import ugettext_lazy as _
import django_tables2 as tables
from django_tables2.utils import A
from .models import LessonPeriod
def _css_class_from_lesson_state(record: Optional[LessonPeriod] = None, table: Optional[LessonsTable] = None) -> str:
def _css_class_from_lesson_state(
record: Optional[LessonPeriod] = None, table: Optional[LessonsTable] = None
) -> str:
if record.get_substitution(record._week):
if record.get_substitution(record._week).cancelled:
return 'table-danger'
return "table-danger"
else:
return 'table-warning'
return "table-warning"
else:
return ''
return ""
class LessonsTable(tables.Table):
class Meta:
attrs = {'class': 'table table-striped table-bordered table-hover table-responsive-xl'}
row_attrs = {'class': _css_class_from_lesson_state}
period__period = tables.Column(accessor='period.period')
lesson__groups = tables.Column(accessor='lesson.group_names', verbose_name=_('Groups'))
lesson__teachers = tables.Column(accessor='lesson.teacher_names', verbose_name=_('Teachers'))
lesson__subject = tables.Column(accessor='lesson.subject')
room = tables.Column(accessor='room')
attrs = {
"class": "table table-striped table-bordered table-hover table-responsive-xl"
}
row_attrs = {"class": _css_class_from_lesson_state}
period__period = tables.Column(accessor="period.period")
lesson__groups = tables.Column(
accessor="lesson.group_names", verbose_name=_("Groups")
)
lesson__teachers = tables.Column(
accessor="lesson.teacher_names", verbose_name=_("Teachers")
)
lesson__subject = tables.Column(accessor="lesson.subject")
room = tables.Column(accessor="room")
edit_substitution = tables.LinkColumn(
'edit_substitution', args=[A('id'), A('_week')], text=_('Substitution'))
"edit_substitution", args=[A("id"), A("_week")], text=_("Substitution")
)
class SubstitutionsTable(tables.Table):
class Meta:
attrs = {'class': 'table table-striped table-bordered table-hover table-responsive-xl'}
lesson_period = tables.Column(verbose_name=_('Lesson'))
lesson__groups = tables.Column(accessor='lesson_period.lesson.group_names', verbose_name=_('Groups'))
lesson__teachers = tables.Column(accessor='lesson_period.get_teacher_names', verbose_name=_('Teachers'))
lesson__subject = tables.Column(accessor='subject')
room = tables.Column(accessor='room')
cancelled = tables.BooleanColumn(accessor='cancelled', verbose_name=_('Cancelled'))
attrs = {
"class": "table table-striped table-bordered table-hover table-responsive-xl"
}
lesson_period = tables.Column(verbose_name=_("Lesson"))
lesson__groups = tables.Column(
accessor="lesson_period.lesson.group_names", verbose_name=_("Groups")
)
lesson__teachers = tables.Column(
accessor="lesson_period.get_teacher_names", verbose_name=_("Teachers")
)
lesson__subject = tables.Column(accessor="subject")
room = tables.Column(accessor="room")
cancelled = tables.BooleanColumn(accessor="cancelled", verbose_name=_("Cancelled"))
......@@ -4,8 +4,7 @@ from typing import Optional, Union
from django import template
from django.db.models.query import QuerySet
from ..util import CalendarWeek, week_weekday_to_date, week_period_to_date
from ..util import CalendarWeek, week_period_to_date, week_weekday_to_date
register = template.Library()
......
......@@ -2,14 +2,25 @@ from django.urls import path
from . import views
urlpatterns = [
path('timetable', views.timetable, name='timetable'),
path('timetable/<int:year>/<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'),
path('lessons/<int:id_>/<int:week>/substition/delete', views.delete_substitution, name='delete_substitution'),
path('substitutions', views.substitutions, name='substitutions'),
path('substitutions/<int:year>/<int:week>', views.substitutions, name='substitutions_by_week')
path("timetable", views.timetable, name="timetable"),
path("timetable/<int:year>/<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",
),
path(
"lessons/<int:id_>/<int:week>/substition/delete",
views.delete_substitution,
name="delete_substitution",
),
path("substitutions", views.substitutions, name="substitutions"),
path(
"substitutions/<int:year>/<int:week>",
views.substitutions,
name="substitutions_by_week",
),
]
......@@ -20,7 +20,7 @@ class CalendarWeek:
def from_date(cls, when: date):
""" Get the calendar week by a date object (the week this date is in). """
return cls(year=when.year, week=int(when.strftime('%V')))
return cls(year=when.year, week=int(when.strftime("%V")))
@classmethod
def current_week(cls) -> int:
......@@ -33,7 +33,7 @@ class CalendarWeek:
""" Get all calendar weeks within a date range. """
if start > end:
raise ValueError('End date must be after start date.')
raise ValueError("End date must be after start date.")
current = start
weeks = []
......@@ -49,22 +49,30 @@ class CalendarWeek:
if not self.year:
self.year = today.year
if not self.week:
self.week = int(today.strftime('%V'))
self.week = int(today.strftime("%V"))
def __str__(self) -> str:
return '%s %d (%s %s %s)' % (_('Calendar Week'), self.week, self[0], _('to'), self[-1])
return "%s %d (%s %s %s)" % (
_("Calendar Week"),
self.week,
self[0],
_("to"),
self[-1],
)
def __len__(self) -> int:
return 7
def __getitem__(self, n: int) -> date:
if n < -7 or n > 6:
raise IndexError('Week day %d is out of range.' % n)
raise IndexError("Week day %d is out of range." % n)
if n < 0:
n += 7
return datetime.strptime('%d-%d-%d' % (self.year, self.week, n + 1), '%G-%V-%u').date()
return datetime.strptime(
"%d-%d-%d" % (self.year, self.week, n + 1), "%G-%V-%u"
).date()
def __contains__(self, day: date) -> bool:
return self.__class__.form_date(day) == self
......
from datetime import date, datetime, timedelta
from collections import OrderedDict
from datetime import date, datetime, timedelta
from typing import Optional
from django.contrib.auth.decorators import login_required
......@@ -14,14 +14,16 @@ from django_tables2 import RequestConfig
from biscuit.core.decorators import admin_required
from biscuit.core.util import messages
from .forms import SelectForm, LessonSubstitutionForm
from .models import LessonPeriod, TimePeriod, LessonSubstitution
from .util import CalendarWeek
from .forms import LessonSubstitutionForm, SelectForm
from .models import LessonPeriod, LessonSubstitution, TimePeriod
from .tables import LessonsTable, SubstitutionsTable
from .util import CalendarWeek
@login_required
def timetable(request: HttpRequest, year: Optional[int] = None, week: Optional[int] = None) -> HttpResponse:
def timetable(
request: HttpRequest, year: Optional[int] = None, week: Optional[int] = None
) -> HttpResponse:
context = {}
if year and week:
......@@ -32,60 +34,77 @@ def timetable(request: HttpRequest, year: Optional[int] = None, week: Optional[i
lesson_periods = LessonPeriod.objects.in_week(wanted_week)
# Incrementally filter lesson periods by GET parameters
if request.GET.get('group', None) or request.GET.get('teacher', None) or request.GET.get('room', None):
if (
request.GET.get("group", None)
or request.GET.get("teacher", None)
or request.GET.get("room", None)
):
lesson_periods = lesson_periods.filter_from_query(request.GET)
else:
# Redirect to a selected view if no filter provided
if request.user.person:
if request.user.person.primary_group:
return redirect(reverse('timetable') + '?group=%d' % request.user.person.primary_group.pk)
return redirect(
reverse("timetable")
+ "?group=%d" % request.user.person.primary_group.pk
)
elif lesson_periods.filter(lesson__teachers=request.user.person).exists():
return redirect(reverse('timetable') + '?teacher=%d' % request.user.person.pk)
return redirect(
reverse("timetable") + "?teacher=%d" % request.user.person.pk
)
# Regroup lesson periods per weekday
per_day = {}
for lesson_period in lesson_periods:
per_day.setdefault(lesson_period.period.weekday,
{})[lesson_period.period.period] = lesson_period
per_day.setdefault(lesson_period.period.weekday, {})[
lesson_period.period.period
] = lesson_period
# Determine overall first and last day and period
min_max = TimePeriod.objects.aggregate(
Min('period'), Max('period'),
Min('weekday'), Max('weekday'))
Min("period"), Max("period"), Min("weekday"), Max("weekday")
)
# Fill in empty lessons
for weekday_num in range(min_max.get('weekday__min', 0),
min_max.get('weekday__max', 6) + 1):
for weekday_num in range(
min_max.get("weekday__min", 0), min_max.get("weekday__max", 6) + 1
):
# Fill in empty weekdays
if weekday_num not in per_day.keys():
per_day[weekday_num] = {}
# Fill in empty lessons on this workday
for period_num in range(min_max.get('period__min', 1),
min_max.get('period__max', 7) + 1):
for period_num in range(
min_max.get("period__min", 1), min_max.get("period__max", 7) + 1
):
if period_num not in per_day[weekday_num].keys():
per_day[weekday_num][period_num] = None
# Order this weekday by periods
per_day[weekday_num] = OrderedDict(
sorted(per_day[weekday_num].items()))
per_day[weekday_num] = OrderedDict(sorted(per_day[weekday_num].items()))
# Add a form to filter the view
select_form = SelectForm(request.GET or None)
context['current_head'] = _('Timetable')
context['lesson_periods'] = OrderedDict(sorted(per_day.items()))
context['periods'] = TimePeriod.get_times_dict()
context['weekdays'] = dict(TimePeriod.WEEKDAY_CHOICES)
context['week'] = wanted_week
context['select_form'] = select_form
context["current_head"] = _("Timetable")
context["lesson_periods"] = OrderedDict(sorted(per_day.items()))
context["periods"] = TimePeriod.get_times_dict()
context["weekdays"] = dict(TimePeriod.WEEKDAY_CHOICES)
context["week"] = wanted_week
context["select_form"] = select_form
week_prev = wanted_week - 1
week_next = wanted_week + 1
context['url_prev'] = '%s?%s' % (reverse('timetable_by_week', args=[week_prev.year, week_prev.week]), request.GET.urlencode())
context['url_next'] = '%s?%s' % (reverse('timetable_by_week', args=[week_next.year, week_next.week]), request.GET.urlencode())
context["url_prev"] = "%s?%s" % (
reverse("timetable_by_week", args=[week_prev.year, week_prev.week]),
request.GET.urlencode(),
)
context["url_next"] = "%s?%s" % (
reverse("timetable_by_week", args=[week_next.year, week_next.week]),
request.GET.urlencode(),
)
return render(request, 'chronos/tt_week.html', context)
return render(request, "chronos/tt_week.html", context)
@login_required
......@@ -93,7 +112,7 @@ def lessons_day(request: HttpRequest, when: Optional[str] = None) -> HttpRespons
context = {}
if when:
day = datetime.strptime(when, '%Y-%m-%d').date()
day = datetime.strptime(when, "%Y-%m-%d").date()
else:
day = date.today()
......@@ -104,17 +123,21 @@ def lessons_day(request: HttpRequest, when: Optional[str] = None) -> HttpRespons
lessons_table = LessonsTable(lesson_periods.all())
RequestConfig(request).configure(lessons_table)
context['current_head'] = _('Lessons %s') % (day)
context['lessons_table'] = lessons_table
context['day'] = day
context['lesson_periods'] = lesson_periods
context["current_head"] = _("Lessons %s") % (day)
context["lessons_table"] = lessons_table
context["day"] = day
context["lesson_periods"] = lesson_periods
day_prev = day - timedelta(days=1)
day_next = day + timedelta(days=1)
context['url_prev'] = reverse('lessons_day_by_date', args=[day_prev.strftime('%Y-%m-%d')])
context['url_next'] = reverse('lessons_day_by_date', args=[day_next.strftime('%Y-%m-%d')])
context["url_prev"] = reverse(
"lessons_day_by_date", args=[day_prev.strftime("%Y-%m-%d")]
)
context["url_next"] = reverse(
"lessons_day_by_date", args=[day_next.strftime("%Y-%m-%d")]
)
return render(request, 'chronos/lessons_day.html', context)
return render(request, "chronos/lessons_day.html", context)
@admin_required
......@@ -125,26 +148,33 @@ def edit_substitution(request: HttpRequest, id_: int, week: int) -> HttpResponse
wanted_week = lesson_period.lesson.get_calendar_week(week)
lesson_substitution = LessonSubstitution.objects.filter(
week=wanted_week.week, lesson_period=lesson_period).first()
week=wanted_week.week, lesson_period=lesson_period
).first()
if lesson_substitution:
edit_substitution_form = LessonSubstitutionForm(
request.POST or None, instance=lesson_substitution)
request.POST or None, instance=lesson_substitution
)
else:
edit_substitution_form = LessonSubstitutionForm(
request.POST or None, initial={'week': wanted_week.week, 'lesson_period': lesson_period})
request.POST or None,
initial={"week": wanted_week.week, "lesson_period": lesson_period},
)
context['substitution'] = lesson_substitution
context["substitution"] = lesson_substitution
if request.method == 'POST':
if request.method == "POST":
if edit_substitution_form.is_valid():
edit_substitution_form.save(commit=True)
messages.success(request, _('The substitution has been saved.'))
return redirect('lessons_day_by_date', when=wanted_week[lesson_period.period.weekday - 1].strftime('%Y-%m-%d'))
messages.success(request, _("The substitution has been saved."))
return redirect(
"lessons_day_by_date",
when=wanted_week[lesson_period.period.weekday - 1].strftime("%Y-%m-%d"),
)
context['edit_substitution_form'] = edit_substitution_form
context["edit_substitution_form"] = edit_substitution_form
return render(request, 'chronos/edit_substitution.html', context)
return render(request, "chronos/edit_substitution.html", context)
@admin_required
......@@ -158,11 +188,16 @@ def delete_substitution(request: HttpRequest, id_: int, week: int) -> HttpRespon
week=wanted_week.week, lesson_period=lesson_period
).delete()
messages.success(request, _('The substitution has been deleted.'))
return redirect('lessons_day_by_date', when=wanted_week[lesson_period.period.weekday - 1].strftime('%Y-%m-%d'))
messages.success(request, _("The substitution has been deleted."))
return redirect(
"lessons_day_by_date",
when=wanted_week[lesson_period.period.weekday - 1].strftime("%Y-%m-%d"),
)
def substitutions(request: HttpRequest, year: Optional[int] = None, week: Optional[int] = None) -> HttpResponse:
def substitutions(
request: HttpRequest, year: Optional[int] = None, week: Optional[int] = None
) -> HttpResponse:
context = {}
if week:
......@@ -176,11 +211,15 @@ def substitutions(request: HttpRequest, year: Optional[int] = None, week: Option
substitutions_table = SubstitutionsTable(substitutions)
RequestConfig(request).configure(substitutions_table)
context['current_head'] = str(wanted_week)
context['substitutions_table'] = substitutions_table
context["current_head"] = str(wanted_week)
context["substitutions_table"] = substitutions_table
week_prev = wanted_week - 1
week_next = wanted_week + 1
context['url_prev'] = '%s' % (reverse('substitutions_by_week', args=[week_prev.year, week_prev.week]))
context['url_next'] = '%s' % (reverse('substitutions_by_week', args=[week_next.year, week_next.week]))
return render(request, 'chronos/substitutions.html', context)
context["url_prev"] = "%s" % (
reverse("substitutions_by_week", args=[week_prev.year, week_prev.week])
)
context["url_next"] = "%s" % (
reverse("substitutions_by_week", args=[week_next.year, week_next.week])
)
return render(request, "chronos/substitutions.html", context)