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 (54)
Showing
with 1397 additions and 565 deletions
<!-- AlekSIS is developed on EduGit. GitHub only serves as
backup mirror and to help people find the project. If
possible, please submit your merge request on EduGit!
EduGit accepts logins with GitHub accounts.
-->
[ ] I have read the above and have no way to contribute on EduGit
[ ] I understand that GitHub's terms of service exclude young and
learning contributors, but still cannot contribute on EduGit
instead.
include:
- project: "AlekSIS/official/AlekSIS"
file: /ci/general.yml
# - project: "AlekSIS/official/AlekSIS"
# file: /ci/test/test.yml
- project: "AlekSIS/official/AlekSIS"
file: /ci/test.yml
file: /ci/test/lint.yml
- project: "AlekSIS/official/AlekSIS"
file: /ci/build_dist.yml
file: /ci/test/security.yml
- project: "AlekSIS/official/AlekSIS"
file: /ci/deploy_pypi.yml
file: /ci/build/dist.yml
- project: "AlekSIS/official/AlekSIS"
file: "/ci/deploy/trigger_dist.yml"
- project: "AlekSIS/official/AlekSIS"
file: /ci/publish/pypi.yml
from datetime import date, datetime, timedelta
from enum import Enum
from typing import Optional, Union
from typing import Iterable, List, Optional, Union
from django.contrib.sites.managers import CurrentSiteManager as _CurrentSiteManager
from django.db import models
from django.db.models import Count, F, Q, QuerySet
from django.db.models import Count, ExpressionWrapper, F, Func, Q, QuerySet, Value
from django.db.models.fields import DateField
from django.db.models.functions import Concat
from calendarweek import CalendarWeek
......@@ -209,7 +211,7 @@ class WeekQuerySetMixin:
class GroupByPeriodsMixin:
def group_by_periods(self, is_person: bool = False) -> dict:
def group_by_periods(self, is_week: bool = False) -> dict:
"""Group a QuerySet of objects with attribute period by period numbers and weekdays."""
per_period = {}
for obj in self:
......@@ -217,12 +219,12 @@ class GroupByPeriodsMixin:
weekday = obj.period.weekday
if period not in per_period:
per_period[period] = [] if is_person else {}
per_period[period] = [] if not is_week else {}
if not is_person and weekday not in per_period[period]:
if is_week and weekday not in per_period[period]:
per_period[period][weekday] = []
if is_person:
if not is_week:
per_period[period].append(obj)
else:
per_period[period][weekday].append(obj)
......@@ -298,6 +300,13 @@ class LessonDataQuerySet(models.QuerySet, WeekQuerySetMixin):
| Q(**{self._period_path + "lesson__groups__parent_groups": group})
)
def filter_groups(self, groups: Iterable[Group]) -> QuerySet:
"""Filter for all lessons one of the groups regularly attends."""
return self.filter(
Q(**{self._period_path + "lesson__groups__in": groups})
| Q(**{self._period_path + "lesson__groups__parent_groups__in": groups})
)
def filter_teacher(self, teacher: Union[Person, int]):
"""Filter for all lessons given by a certain teacher."""
qs1 = self.filter(**{self._period_path + "lesson__teachers": teacher})
......@@ -503,6 +512,13 @@ class DateRangeQuerySetMixin:
period_from__time_start__lte=now.time(), period_to__time_end__gte=now.time()
)
def exclude_holidays(self, holidays: Iterable["Holiday"]) -> QuerySet:
"""Exclude all objects which are in the provided holidays."""
q = Q()
for holiday in holidays:
q = q | Q(date_start__lte=holiday.date_end, date_end__gte=holiday.date_start)
return self.exclude(q)
class AbsenceQuerySet(DateRangeQuerySetMixin, SchoolTermRelatedQuerySet):
"""QuerySet with custom query methods for absences."""
......@@ -532,7 +548,12 @@ class AbsenceQuerySet(DateRangeQuerySetMixin, SchoolTermRelatedQuerySet):
class HolidayQuerySet(QuerySet, DateRangeQuerySetMixin):
"""QuerySet with custom query methods for holidays."""
pass
def get_all_days(self) -> List[date]:
"""Get all days included in the selected holidays."""
holiday_days = []
for holiday in self:
holiday_days += list(holiday.get_days())
return holiday_days
class SupervisionQuerySet(ValidityRangeRelatedQuerySet, WeekQuerySetMixin):
......@@ -589,6 +610,10 @@ class TimetableQuerySet(models.QuerySet):
else:
return self.filter(Q(groups=group) | Q(groups__parent_groups=group))
def filter_groups(self, groups: Iterable[Group]) -> QuerySet:
"""Filter for all objects one of the groups attends."""
return self.filter(Q(groups__in=groups) | Q(groups__parent_groups__in=groups)).distinct()
def filter_teacher(self, teacher: Union[Person, int]):
"""Filter for all lessons given by a certain teacher."""
return self.filter(teachers=teacher)
......@@ -647,22 +672,32 @@ class ExtraLessonQuerySet(TimetableQuerySet, SchoolTermRelatedQuerySet, GroupByP
def within_dates(self, start: date, end: date):
"""Filter all extra lessons within a specific time range."""
week_start = CalendarWeek.from_date(start)
week_end = CalendarWeek.from_date(end)
return self.filter(
week__gte=week_start.week,
week__lte=week_end.week,
year__gte=week_start.year,
year__lte=week_end.year,
period__weekday__gte=start.weekday(),
period__weekday__lte=end.weekday(),
)
return self.annotate_day().filter(day__gte=start, day__lte=end)
def on_day(self, day: date):
"""Filter all extra lessons on a day."""
return self.within_dates(day, day)
def annotate_day(self):
weekday_to_date = ExpressionWrapper(
Func(
Concat(F("year"), F("week")),
Value("IYYYIW"),
output_field=DateField(),
function="TO_DATE",
)
+ F("period__weekday"),
output_field=DateField(),
)
return self.annotate(day=weekday_to_date)
def exclude_holidays(self, holidays: Iterable["Holiday"]) -> QuerySet:
"""Exclude all extra lessons which are in the provided holidays."""
q = Q()
for holiday in holidays:
q = q | Q(day__lte=holiday.date_end, day__gte=holiday.date_start)
return self.annotate_day().exclude(q)
class GroupPropertiesMixin:
"""Mixin for common group properties.
......@@ -699,4 +734,8 @@ class TeacherPropertiesMixin:
@property
def teacher_names(self, sep: Optional[str] = ", ") -> str:
return sep.join([teacher.full_name for teacher in self.teachers.all()])
return sep.join([teacher.full_name for teacher in self.get_teachers().all()])
@property
def teacher_short_names(self, sep: str = ", ") -> str:
return sep.join([teacher.short_name for teacher in self.get_teachers().all()])
......@@ -34,7 +34,8 @@ class ValidityRangeRelatedExtensibleModel(ExtensibleModel):
class WeekRelatedMixin:
@property
def date(self) -> date:
return week_weekday_to_date(self.calendar_week, self.lesson_period.period.weekday)
period = self.lesson_period.period if hasattr(self, "lesson_period") else self.period
return week_weekday_to_date(self.calendar_week, period.weekday)
@property
def calendar_week(self) -> CalendarWeek:
......@@ -42,6 +43,11 @@ class WeekRelatedMixin:
class WeekAnnotationMixin:
def annotate_week(self, week: CalendarWeek):
"""Annotate this lesson with the number of the provided calendar week."""
self._week = week.week
self._year = week.year
@property
def week(self) -> Union[CalendarWeek, None]:
"""Get annotated week as `CalendarWeek`.
......
......@@ -83,7 +83,7 @@ def lesson_periods_as_teacher(self):
@Person.method
def lessons_on_day(self, day: date):
"""Get all lessons of this person (either as participant or teacher) on the given day."""
return LessonPeriod.objects.on_day(day).filter_from_person(self).order_by("period__period")
return LessonPeriod.objects.order_by("period__period").on_day(day).filter_from_person(self)
@Person.method
......@@ -92,7 +92,17 @@ def _adjacent_lesson(
) -> Union["LessonPeriod", None]:
"""Get next/previous lesson of the person (either as participant or teacher) on the same day."""
daily_lessons = self.lessons_on_day(day)
if not daily_lessons:
return None
ids = list(daily_lessons.values_list("id", flat=True))
# Check if the lesson period is one of the person's lesson periods on this day
# and return None if it's not so
if lesson_period.pk not in ids:
return None
index = ids.index(lesson_period.pk)
if (offset > 0 and index + offset < len(ids)) or (offset < 0 and index >= -offset):
......
......@@ -3,7 +3,7 @@
from __future__ import annotations
from datetime import date, datetime, time, timedelta
from typing import Dict, List, Optional, Tuple, Union
from typing import Dict, Iterator, List, Optional, Tuple, Union
from django.core.exceptions import ValidationError
from django.db import models
......@@ -19,7 +19,6 @@ from django.utils.translation import gettext_lazy as _
from cache_memoize import cache_memoize
from calendarweek.django import CalendarWeek, i18n_day_abbr_choices_lazy, i18n_day_name_choices_lazy
from colorfield.fields import ColorField
from django_global_request.middleware import get_request
from aleksis.apps.chronos.managers import (
AbsenceQuerySet,
......@@ -218,6 +217,14 @@ class TimePeriod(ValidityRangeRelatedExtensibleModel):
return url_prev, url_next
@classmethod
def from_period(cls, period: int, day: date) -> "TimePeriod":
"""Get `TimePeriod` object for a period on a specific date.
This will respect the relation to validity ranges.
"""
return cls.objects.on_day(day).filter(period=period, weekday=day.weekday()).first()
@classproperty
@cache_memoize(3600)
def period_min(cls) -> int:
......@@ -345,6 +352,10 @@ class Lesson(ValidityRangeRelatedExtensibleModel, GroupPropertiesMixin, TeacherP
return CalendarWeek(year=year, week=week)
def get_teachers(self) -> models.query.QuerySet:
"""Get teachers relation."""
return self.teachers
def __str__(self):
return f"{format_m2m(self.groups)}, {self.subject.short_name}, {format_m2m(self.teachers)}"
......@@ -414,7 +425,7 @@ class LessonSubstitution(ExtensibleModel, WeekRelatedMixin):
verbose_name_plural = _("Lesson substitutions")
class LessonPeriod(ExtensibleModel, WeekAnnotationMixin):
class LessonPeriod(WeekAnnotationMixin, TeacherPropertiesMixin, ExtensibleModel):
label_ = "lesson_period"
objects = LessonPeriodManager.from_queryset(LessonPeriodQuerySet)()
......@@ -459,15 +470,26 @@ class LessonPeriod(ExtensibleModel, WeekAnnotationMixin):
else:
return self.room
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 f"{self.period}, {self.lesson}"
@property
def _equal_lessons(self):
"""Get all lesson periods with equal lessons in the whole school term."""
qs = LessonPeriod.objects.filter(
lesson__subject=self.lesson.subject,
lesson__validity__school_term=self.lesson.validity.school_term,
)
for group in self.lesson.groups.all():
qs = qs.filter(lesson__groups=group)
for teacher in self.lesson.teachers.all():
qs = qs.filter(lesson__teachers=teacher)
return qs
@property
def next(self) -> "LessonPeriod":
"""Get next lesson period of this lesson.
......@@ -475,7 +497,7 @@ class LessonPeriod(ExtensibleModel, WeekAnnotationMixin):
.. warning::
To use this property, the provided lesson period must be annotated with a week.
"""
return LessonPeriod.objects.filter(lesson=self.lesson).next_lesson(self)
return self._equal_lessons.next_lesson(self)
@property
def prev(self) -> "LessonPeriod":
......@@ -484,7 +506,7 @@ class LessonPeriod(ExtensibleModel, WeekAnnotationMixin):
.. warning::
To use this property, the provided lesson period must be annotated with a week.
"""
return LessonPeriod.objects.filter(lesson=self.lesson).next_lesson(self, -1)
return self._equal_lessons.next_lesson(self, -1)
class Meta:
ordering = [
......@@ -501,10 +523,9 @@ class LessonPeriod(ExtensibleModel, WeekAnnotationMixin):
class TimetableWidget(DashboardWidget):
template = "chronos/widget.html"
def get_context(self):
def get_context(self, request):
from aleksis.apps.chronos.util.build import build_timetable # noqa
request = get_request()
context = {"has_plan": True}
wanted_day = TimePeriod.get_next_relevant_day(timezone.now().date(), datetime.now().time())
......@@ -664,6 +685,11 @@ class Holiday(ExtensibleModel):
date_end = models.DateField(verbose_name=_("End date"), null=True)
comments = models.TextField(verbose_name=_("Comments"), blank=True, null=True)
def get_days(self) -> Iterator[date]:
delta = self.date_end - self.date_start
for i in range(delta.days + 1):
yield self.date_start + timedelta(days=i)
@classmethod
def on_day(cls, day: date) -> Optional["Holiday"]:
holidays = cls.objects.on_day(day)
......@@ -679,13 +705,13 @@ class Holiday(ExtensibleModel):
for weekday in range(TimePeriod.weekday_min, TimePeriod.weekday_max + 1):
holiday_date = week[weekday]
holidays = list(
filtered_holidays = list(
filter(
lambda h: holiday_date >= h.date_start and holiday_date <= h.date_end, holidays,
)
)
if holidays:
per_weekday[weekday] = holidays[0]
if filtered_holidays:
per_weekday[weekday] = filtered_holidays[0]
return per_weekday
......@@ -883,20 +909,70 @@ class Event(SchoolTermRelatedExtensibleModel, GroupPropertiesMixin, TeacherPrope
return _(f"Event {self.pk}")
@property
def period_from_on_day(self) -> int:
def raw_period_from_on_day(self) -> TimePeriod:
"""Get start period on the annotated day (as TimePeriod object).
If there is no date annotated, it will use the current date.
"""
day = getattr(self, "_date", timezone.now().date())
if day != self.date_start:
return TimePeriod.period_min
return TimePeriod.from_period(TimePeriod.period_min, day)
else:
return self.period_from.period
return self.period_from
@property
def period_to_on_day(self) -> int:
def raw_period_to_on_day(self) -> TimePeriod:
"""Get end period on the annotated day (as TimePeriod object).
If there is no date annotated, it will use the current date.
"""
day = getattr(self, "_date", timezone.now().date())
if day != self.date_end:
return TimePeriod.period_max
return TimePeriod.from_period(TimePeriod.period_max, day)
else:
return self.period_to
@property
def period_from_on_day(self) -> int:
"""Get start period on the annotated day (as period number).
If there is no date annotated, it will use the current date.
"""
return self.raw_period_from_on_day.period
@property
def period_to_on_day(self) -> int:
"""Get end period on the annotated day (as period number).
If there is no date annotated, it will use the current date.
"""
return self.raw_period_to_on_day.period
def get_start_weekday(self, week: CalendarWeek) -> int:
"""Get start date of an event in a specific week."""
if self.date_start < week[TimePeriod.weekday_min]:
return TimePeriod.weekday_min
else:
return self.date_start.weekday()
def get_end_weekday(self, week: CalendarWeek) -> int:
"""Get end date of an event in a specific week."""
if self.date_end > week[TimePeriod.weekday_max]:
return TimePeriod.weekday_max
else:
return self.period_to.period
return self.date_end.weekday()
def annotate_day(self, day: date):
"""Annotate event with the provided date."""
self._date = day
def get_groups(self) -> models.query.QuerySet:
"""Get groups relation."""
return self.groups
def get_teachers(self) -> models.query.QuerySet:
"""Get teachers relation."""
return self.teachers
class Meta:
ordering = ["date_start"]
......@@ -905,7 +981,9 @@ class Event(SchoolTermRelatedExtensibleModel, GroupPropertiesMixin, TeacherPrope
verbose_name_plural = _("Events")
class ExtraLesson(SchoolTermRelatedExtensibleModel, GroupPropertiesMixin, WeekRelatedMixin):
class ExtraLesson(
GroupPropertiesMixin, TeacherPropertiesMixin, WeekRelatedMixin, SchoolTermRelatedExtensibleModel
):
label_ = "extra_lesson"
objects = ExtraLessonManager.from_queryset(ExtraLessonQuerySet)()
......@@ -937,6 +1015,18 @@ class ExtraLesson(SchoolTermRelatedExtensibleModel, GroupPropertiesMixin, WeekRe
def __str__(self):
return f"{self.week}, {self.period}, {self.subject}"
def get_groups(self) -> models.query.QuerySet:
"""Get groups relation."""
return self.groups
def get_teachers(self) -> models.query.QuerySet:
"""Get teachers relation."""
return self.teachers
def get_subject(self) -> Subject:
"""Get subject."""
return self.subject
class Meta:
verbose_name = _("Extra lesson")
verbose_name_plural = _("Extra lessons")
......
......@@ -13,7 +13,7 @@
{% block content %}
<div class="row no-margin">
<div class="col m12 s12 l6 xl4">
<div class="col s12">
<h4>
{% trans "My timetable" %} <i>{{ el }}</i>
<span class="badge new primary-color ">{% trans "SMART PLAN" %}</span>
......@@ -24,36 +24,43 @@
</div>
</div>
<div class="row nomargin">
<div class="row nomargin hide-on-large-only">
<div class="col m12 s12 l6 xl4">
{% include "core/partials/announcements.html" with announcements=announcements %}
</div>
</div>
<div class="row">
<div class="timetable-plan col s12 m12 xl4">
{# Date #}
<div class="row">
<div class="col s12">
<div class="card timetable-title-card">
<div class="card-content">
<span class="card-title">
{% include "chronos/partials/datepicker.html" with display_date_only=1 %}
{# {% if holiday %}#}
{# <span class="badge new blue center-align holiday-badge">{{ holiday.0 }}</span>#}
{# {% endif %}#}
</span>
</div>
</div>
<div class="row nomargin hide-on-large-med-and-down">
<div class="col m12 s12 l6 xl4">
{% include "core/partials/announcements.html" with announcements=week_announcements %}
</div>
</div>
<div class="row">
<div class="col s12">
<div class="card timetable-title-card">
<div class="card-content">
<span class="card-title">
{% include "chronos/partials/datepicker.html" with display_date_only=1 %}
<span class="show-on-medium-and-down hide-on-large-only">
{% if weekday.date == today %}
<br/> {% include "chronos/partials/today.html" %}
{% endif %}
</span>
</span>
</div>
</div>
</div>
</div>
<div class="row hide-on-large-only">
<div class="timetable-plan col s12 m12 xl4">
{# Lessons #}
{% include "chronos/partials/lessons_col.html" with lesson_periods=lesson_periods %}
</div>
</div>
<div class="row timetable-plan hide-on-med-and-down">
{% include "chronos/partials/week_timetable.html" with timetable=week_timetable active_day=day today=today %}
</div>
{% endblock %}
<div class="card lesson-card">
<div class="card lesson-card {% if active_day and week_day == active_day %} z-depth-5 active {% endif %}">
<div class="card-content">
{% for element in elements %}
{% if element.label_ == "lesson_period" %}
{% include "chronos/partials/lesson.html" with lesson_period=element %}
{% elif element.label_ == "extra_lesson" %}
{% elif element.label_ == "extra_lesson" and smart %}
{% include "chronos/partials/extra_lesson.html" with extra_lesson=element %}
{% elif element.label_ == "event" %}
{% elif element.label_ == "event" and smart %}
{% include "chronos/partials/event.html" with event=element %}
{% endif %}
{% endfor %}
......
......@@ -12,9 +12,7 @@
class="{% if lesson_period.get_substitution and smart %}lesson-with-sub{% endif %}"
>
<p>
{% if lesson_period.is_hol and smart %}
{# Do nothing #}
{% elif lesson_period.get_substitution and smart %}
{% if lesson_period.get_substitution and smart %}
{% with sub=lesson_period.get_substitution %}
{# SUBSTITUTION #}
{% if type.value == "room" and lesson_period.room != lesson_period.get_room and lesson_period.get_room != el %}
......
{% load i18n %}
<div class="card lesson-card supervision-card">
<div class="card lesson-card supervision-card {% if active_day and week_day == active_day %} z-depth-5 active {% endif %}">
<div class="card-content">
{% if supervision %}
<div style="
......
{% load i18n %}
<span class="badge new orange center-align holiday-badge">{% trans "Today" %}</span>
{# Week days #}
<div class="row">
<div class="col {% if active_day %}s1{% else %}s2{% endif %}">
</div>
{# Show short weekdays on tablets #}
{% for weekday in weekdays_short %}
<div class="col s2 hide-on-large-only">
<div class="card timetable-title-card {% if active_day and weekday.date == active_day %} z-depth-5 {% endif %}">
<div class="card-content">
<span class="card-title">
{{ weekday.name }}
</span>
{% if smart %}
{{ weekday.date }}
{% if weekday.holiday %}
<br/>{% include "chronos/partials/holiday.html" with holiday=weekday.holiday %}
{% endif %}
{% if weekday.date == today %}
<br/> {% include "chronos/partials/today.html" %}
{% endif %}
{% endif %}
</div>
</div>
</div>
{% endfor %}
{# Show long weekdays elsewere #}
{% for weekday in weekdays %}
<div class="col {% if weekday.date == active_day %} s3 {% else %} s2 {% endif %} hide-on-med-only">
<div class="card timetable-title-card {% if active_day and weekday.date == active_day %} z-depth-5 {% endif %}">
<div class="card-content">
<span class="card-title">
{{ weekday.name }}
</span>
{% if smart %}
{{ weekday.date }}
{% if weekday.holiday %}
<br/>{% include "chronos/partials/holiday.html" with holiday=weekday.holiday %}
{% endif %}
{% if weekday.date == today %}
<br/> {% include "chronos/partials/today.html" %}
{% endif %}
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
{# Lessons #}
{% for row in timetable %}
<div class="row">
<div class="col {% if active_day %}s1{% else %}s2{% endif %}">
{% if row.type == "period" %}
{% include "chronos/partials/period_time.html" with period=row.period periods=periods %}
{% endif %}
</div>
{% for col in row.cols %}
{# A lesson #}
<div class="col {% if forloop.counter0 == active_day.weekday %} s3 {% else %} s2 {% endif %}">
{% if row.type == "period" %}
{% include "chronos/partials/elements.html" with elements=col week_day=forloop.counter0 active_day=active_day.weekday %}
{% else %}
{% include "chronos/partials/supervision.html" with supervision=col %}
{% endif %}
</div>
{% endfor %}
</div>
{% endfor %}
\ No newline at end of file
......@@ -73,72 +73,7 @@
{# show full timetable on tablets, laptops and pcs #}
<div class="timetable-plan hide-on-small-and-down">
{# Week days #}
<div class="row">
<div class="col s2">
</div>
{# Show short weekdays on tablets #}
{% for weekday in weekdays_short %}
<div class="col s2 hide-on-large-only">
<div class="card timetable-title-card">
<div class="card-content">
<span class="card-title">
{{ weekday.name }}
</span>
{% if smart %}
{{ weekday.date }}
{% if weekday.holiday %}
<br/>{% include "chronos/partials/holiday.html" with holiday=weekday.holiday %}
{% endif %}
{% endif %}
</div>
</div>
</div>
{% endfor %}
{# Show long weekdays elsewere #}
{% for weekday in weekdays %}
<div class="col s2 hide-on-med-only">
<div class="card timetable-title-card">
<div class="card-content">
<span class="card-title">
{{ weekday.name }}
</span>
{% if smart %}
{{ weekday.date }}
{% if weekday.holiday %}
<br/>{% include "chronos/partials/holiday.html" with holiday=weekday.holiday %}
{% endif %}
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
{# Lessons #}
{% for row in timetable %}
<div class="row">
<div class="col s2">
{% if row.type == "period" %}
{% include "chronos/partials/period_time.html" with period=row.period periods=periods %}
{% endif %}
</div>
{% for col in row.cols %}
{# A lesson #}
<div class="col s2">
{% if row.type == "period" %}
{% include "chronos/partials/elements.html" with elements=col %}
{% else %}
{% include "chronos/partials/supervision.html" with supervision=col %}
{% endif %}
</div>
{% endfor %}
</div>
{% endfor %}
{% include "chronos/partials/week_timetable.html" %}
</div>
{# show 5 seperate ones on mobiles #}
......
......@@ -33,45 +33,64 @@ def build_timetable(
is_person = True
type_ = obj.timetable_type
is_week = False
if type(date_ref) == CalendarWeek:
is_week = True
if type_ is None:
return None
# Get matching holidays
if is_person:
holiday = Holiday.on_day(date_ref)
else:
if is_week:
holidays_per_weekday = Holiday.in_week(date_ref)
else:
holiday = Holiday.on_day(date_ref)
# Get matching lesson periods
lesson_periods = LessonPeriod.objects
if is_week:
lesson_periods = lesson_periods.in_week(date_ref)
else:
lesson_periods = lesson_periods.on_day(date_ref)
if is_person:
lesson_periods = LessonPeriod.objects.daily_lessons_for_person(obj, date_ref)
lesson_periods = lesson_periods.filter_from_person(obj)
else:
lesson_periods = LessonPeriod.objects.in_week(date_ref).filter_from_type(type_, obj)
lesson_periods = lesson_periods.filter_from_type(type_, obj)
# Sort lesson periods in a dict
lesson_periods_per_period = lesson_periods.group_by_periods(is_person=is_person)
lesson_periods_per_period = lesson_periods.group_by_periods(is_week=is_week)
# Get events
extra_lessons = ExtraLesson.objects
if is_week:
extra_lessons = extra_lessons.filter(week=date_ref.week, year=date_ref.year)
else:
extra_lessons = extra_lessons.on_day(date_ref)
if is_person:
extra_lessons = ExtraLesson.objects.on_day(date_ref).filter_from_person(obj)
extra_lessons = extra_lessons.filter_from_person(obj)
else:
extra_lessons = ExtraLesson.objects.filter(
week=date_ref.week, year=date_ref.year
).filter_from_type(type_, obj)
extra_lessons = extra_lessons.filter_from_type(type_, obj)
# Sort lesson periods in a dict
extra_lessons_per_period = extra_lessons.group_by_periods(is_person=is_person)
extra_lessons_per_period = extra_lessons.group_by_periods(is_week=is_week)
# Get events
events = Event.objects
if is_week:
events = events.in_week(date_ref)
else:
events = events.on_day(date_ref)
if is_person:
events = Event.objects.on_day(date_ref).filter_from_person(obj)
events = events.filter_from_person(obj)
else:
events = Event.objects.in_week(date_ref).filter_from_type(type_, obj)
events = events.filter_from_type(type_, obj)
# Sort events in a dict
events_per_period = {}
for event in events:
if not is_person and event.date_start < date_ref[TimePeriod.weekday_min]:
if is_week and event.date_start < date_ref[TimePeriod.weekday_min]:
# If start date not in current week, set weekday and period to min
weekday_from = TimePeriod.weekday_min
period_from_first_weekday = TimePeriod.period_min
......@@ -79,7 +98,7 @@ def build_timetable(
weekday_from = event.date_start.weekday()
period_from_first_weekday = event.period_from.period
if not is_person and event.date_end > date_ref[TimePeriod.weekday_max]:
if is_week and event.date_end > date_ref[TimePeriod.weekday_max]:
# If end date not in current week, set weekday and period to max
weekday_to = TimePeriod.weekday_max
period_to_last_weekday = TimePeriod.period_max
......@@ -88,7 +107,7 @@ def build_timetable(
period_to_last_weekday = event.period_to.period
for weekday in range(weekday_from, weekday_to + 1):
if is_person and weekday != date_ref.weekday():
if not is_week and weekday != date_ref.weekday():
# If daily timetable for person, skip other weekdays
continue
......@@ -110,17 +129,17 @@ def build_timetable(
if period not in events_per_period:
events_per_period[period] = [] if is_person else {}
if not is_person and weekday not in events_per_period[period]:
if is_week and weekday not in events_per_period[period]:
events_per_period[period][weekday] = []
if is_person:
if not is_week:
events_per_period[period].append(event)
else:
events_per_period[period][weekday].append(event)
if type_ == TimetableType.TEACHER:
# Get matching supervisions
if is_person:
if not is_week:
week = CalendarWeek.from_date(date_ref)
else:
week = date_ref
......@@ -128,22 +147,21 @@ def build_timetable(
Supervision.objects.in_week(week).all().annotate_week(week).filter_by_teacher(obj)
)
if is_person:
if not is_week:
supervisions = supervisions.filter_by_weekday(date_ref.weekday())
supervisions_per_period_after = {}
for supervision in supervisions:
weekday = supervision.break_item.weekday
period_after_break = supervision.break_item.before_period_number
print(supervision, weekday, period_after_break)
if period_after_break not in needed_breaks:
needed_breaks.append(period_after_break)
if not is_person and period_after_break not in supervisions_per_period_after:
if is_week and period_after_break not in supervisions_per_period_after:
supervisions_per_period_after[period_after_break] = {}
if is_person:
if not is_week:
supervisions_per_period_after[period_after_break] = supervision
else:
supervisions_per_period_after[period_after_break][weekday] = supervision
......@@ -163,7 +181,7 @@ def build_timetable(
"time_end": break_.time_end,
}
if not is_person:
if is_week:
cols = []
for weekday in range(TimePeriod.weekday_min, TimePeriod.weekday_max + 1):
......@@ -193,7 +211,7 @@ def build_timetable(
"time_end": break_.before_period.time_end,
}
if not is_person:
if is_week:
cols = []
for weekday in range(TimePeriod.weekday_min, TimePeriod.weekday_max + 1):
col = []
......
......@@ -7,6 +7,7 @@ from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils import timezone
from django.utils.translation import ugettext as _
from django.views.decorators.cache import never_cache
from django_tables2 import RequestConfig
from rules.contrib.views import permission_required
......@@ -70,12 +71,15 @@ def my_timetable(
else:
wanted_day = TimePeriod.get_next_relevant_day(timezone.now().date(), datetime.now().time())
wanted_week = CalendarWeek.from_date(wanted_day)
if has_person(request.user):
person = request.user.person
type_ = person.timetable_type
# Build timetable
timetable = build_timetable("person", person, wanted_day)
week_timetable = build_timetable("person", person, wanted_week)
if type_ is None:
# If no student or teacher, redirect to all timetables
......@@ -84,16 +88,25 @@ def my_timetable(
super_el = person.timetable_object
context["timetable"] = timetable
context["week_timetable"] = week_timetable
context["holiday"] = Holiday.on_day(wanted_day)
context["super"] = {"type": type_, "el": super_el}
context["type"] = type_
context["day"] = wanted_day
context["today"] = timezone.now().date()
context["week"] = wanted_week
context["periods"] = TimePeriod.get_times_dict()
context["smart"] = True
context["announcements"] = (
Announcement.for_timetables().on_date(wanted_day).for_person(person)
)
context["week_announcements"] = (
Announcement.for_timetables()
.within_days(wanted_week[0], wanted_week[6])
.for_person(person)
)
context["weekdays"] = build_weekdays(TimePeriod.WEEKDAY_CHOICES, wanted_week)
context["weekdays_short"] = build_weekdays(TimePeriod.WEEKDAY_CHOICES_SHORT, wanted_week)
context["url_prev"], context["url_next"] = TimePeriod.get_prev_next_by_day(
wanted_day, "my_timetable_by_date"
)
......@@ -214,6 +227,7 @@ def lessons_day(
return render(request, "chronos/lessons_day.html", context)
@never_cache
@permission_required("chronos.edit_substitution", fn=get_substitution_by_id)
def edit_substitution(request: HttpRequest, id_: int, week: int) -> HttpResponse:
"""View a form to edit a substitution lessen."""
......
This diff is collapsed.
[tool.poetry]
name = "AlekSIS-App-Chronos"
version = "2.0a3.dev0"
version = "2.0a4.dev0"
packages = [
{ include = "aleksis" }
]
......
......@@ -9,9 +9,11 @@ whitelist_externals = poetry
skip_install = true
envdir = {toxworkdir}/globalenv
commands_pre =
- poetry install
poetry install
poetry run aleksis-admin yarn install
poetry run aleksis-admin collectstatic --no-input
commands =
- poetry run pytest --cov=. {posargs} aleksis/
poetry run pytest --cov=. {posargs} aleksis/
[testenv:selenium]
setenv =
......@@ -33,6 +35,7 @@ commands =
[testenv:build]
commands_pre =
poetry run sh -c "cd aleksis; aleksis-admin compilemessages"
commands = poetry build
[testenv:docs]
......