Commit fd78dcd1 authored by Dominik George's avatar Dominik George 🍳

Merge branch 'feature/smart-plan-dashboard-widget' into 'master'

Dashboard widget for SMART PLAN

See merge request !36
parents c4a69802 c5b23ddd
from django.contrib import admin
from .models import TimetableWidget
admin.site.register(TimetableWidget)
......@@ -4,7 +4,7 @@ import django.core.validators
import django.db.models.deletion
from django.db import migrations, models
import aleksis.apps.chronos.util.weeks
import aleksis.apps.chronos.util.date
import aleksis.core.util.core_helpers
......@@ -229,7 +229,7 @@ class Migration(migrations.Migration):
(
"week",
models.IntegerField(
default=aleksis.apps.chronos.util.weeks.CalendarWeek.current_week,
default=aleksis.apps.chronos.util.date.CalendarWeek.current_week,
verbose_name="Week",
),
),
......
from aleksis.core.models import Person
from typing import Optional, Union
from aleksis.core.models import Person, Group
from .models import Lesson, LessonPeriod
......@@ -10,6 +12,32 @@ def is_teacher(self):
return self.lesson_periods_as_teacher.exists()
@Person.property
def timetable_type(self) -> Optional[str]:
""" Return which type of timetable this user has """
if self.is_teacher:
return "teacher"
elif self.primary_group:
return "group"
else:
return None
@Person.property
def timetable_object(self) -> Optional[Union[Group, Person]]:
""" Return the object which has the user's timetable """
type_ = self.timetable_type
if type_ == "teacher":
return self
elif type_ == "group":
return self.primary_group
else:
return None
@Person.property
def lessons_as_participant(self):
""" Return a `QuerySet` containing all `Lesson`s this person
......
from __future__ import annotations
from collections import OrderedDict
from datetime import date, datetime, timedelta, time
from typing import Dict, Optional, Tuple, Union
......@@ -8,6 +9,7 @@ from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import F, Max, Min, Q
from django.db.models.functions import Coalesce
from django.forms import Media
from django.http.request import QueryDict
from django.urls import reverse
from django.utils import timezone
......@@ -15,11 +17,13 @@ from django.utils.decorators import classproperty
from django.utils.translation import ugettext_lazy as _
from calendarweek.django import CalendarWeek, i18n_day_names_lazy, i18n_day_abbrs_lazy
from django_global_request.middleware import get_request
from aleksis.core.mixins import ExtensibleModel
from aleksis.core.models import Group, Person
from aleksis.core.models import Group, Person, DashboardWidget
from aleksis.apps.chronos.util.weeks import week_weekday_from_date
from aleksis.apps.chronos.util.date import week_weekday_from_date
from aleksis.core.util.core_helpers import has_person
class LessonPeriodManager(models.Manager):
......@@ -202,6 +206,41 @@ class LessonPeriodQuerySet(LessonDataQuerySet):
else:
return None
def filter_from_person(self, person: Person) -> Optional[models.QuerySet]:
type_ = person.timetable_type
if type_ == "teacher":
# Teacher
return person.lesson_periods_as_teacher
elif type_ == "group":
# Student
return person.lesson_periods_as_participant
else:
# If no student or teacher
return None
def daily_lessons_for_person(self, person: Person, wanted_day: date) -> Optional[models.QuerySet]:
lesson_periods = LessonPeriod.objects.filter_from_person(person)
if lesson_periods is None:
return None
return lesson_periods.on_day(wanted_day)
def per_period_one_day(self) -> OrderedDict:
""" Group selected lessons per period for one day """
per_period = {}
for lesson_period in self:
if lesson_period.period.period in per_period:
per_period[lesson_period.period.period].append(lesson_period)
else:
per_period[lesson_period.period.period] = [lesson_period]
return OrderedDict(sorted(per_period.items()))
class LessonSubstitutionQuerySet(LessonDataQuerySet):
_period_path = "lesson_period__"
......@@ -505,3 +544,40 @@ class LessonPeriod(ExtensibleModel):
class Meta:
ordering = ["lesson__date_start", "period__weekday", "period__period"]
indexes = [models.Index(fields=["lesson", "period"])]
class TimetableWidget(DashboardWidget):
template = "chronos/widget.html"
def get_context(self):
request = get_request()
context = {"has_plan": True}
wanted_day = TimePeriod.get_next_relevant_day(timezone.now().date(), datetime.now().time())
if has_person(request.user):
person = request.user.person
lesson_periods = LessonPeriod.objects.daily_lessons_for_person(person, wanted_day)
type_ = person.timetable_type
if type_ is None:
# If no student or teacher, redirect to all timetables
context["has_plan"] = False
else:
context["lesson_periods"] = lesson_periods.per_period_one_day()
context["type"] = type_
context["day"] = wanted_day
context["periods"] = TimePeriod.get_times_dict()
context["smart"] = True
else:
context["has_plan"] = False
return context
media = Media(css={
"all": ("css/chronos/timetable.css",)
})
class Meta:
proxy = True
verbose_name = _("Timetable widget")
......@@ -50,18 +50,9 @@
</div>
</div>
{# Lessons #}
{% for period, lessons in lesson_periods.items %}
<div class="row">
<div class="col s4">
{% include "chronos/partials/period_time.html" with period=period periods=periods %}
</div>
<div class="col s8">
{# A lesson #}
{% include "chronos/partials/lesson.html" with lessons=lessons %}
</div>
</div>
{% endfor %}
{% include "chronos/partials/lessons_col.html" with lesson_periods=lesson_periods %}
</div>
</div>
......
{% for period, lessons in lesson_periods.items %}
<div class="row">
<div class="col s4">
{% include "chronos/partials/period_time.html" with period=period periods=periods %}
</div>
<div class="col s8">
{% include "chronos/partials/lesson.html" with lessons=lessons %}
</div>
</div>
{% endfor %}
{# -*- engine:django -*- #}
{% load i18n static humanize %}
<div class="card">
<div class="card-content">
<span class="card-title">
{% blocktrans with day=day|naturalday:"l" %}
My timetable for {{ day }}
{% endblocktrans %}
</span>
<div class="timetable-plan">
{% if has_plan %}
{% include "chronos/partials/lessons_col.html" with lesson_periods=lesson_periods %}
{% else %}
<div class="alert warning">
<p>
<i class="material-icons left">info</i>
{% blocktrans %}
There is no timetable linked to your person.
{% endblocktrans %}
</p>
</div>
{% endif %}
</div>
</div>
{% if has_plan %}
<div class="card-action">
<a href="{% url "my_timetable" %}">{% trans "Go to smart plan" %}</a>
</div>
{% endif %}
</div>
......@@ -4,7 +4,7 @@ from typing import Optional, Union
from django import template
from django.db.models.query import QuerySet
from aleksis.apps.chronos.util.weeks import CalendarWeek, week_period_to_date, week_weekday_to_date
from aleksis.apps.chronos.util.date import CalendarWeek, week_period_to_date, week_weekday_to_date
register = template.Library()
......
......@@ -2,6 +2,9 @@ from datetime import date
from typing import Tuple, List, Union
from calendarweek import CalendarWeek
from calendarweek.django import i18n_day_names
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
def week_weekday_from_date(when: date) -> Tuple[CalendarWeek, int]:
......
......@@ -19,7 +19,7 @@ from .forms import LessonSubstitutionForm
from .models import LessonPeriod, LessonSubstitution, TimePeriod, Room
from .tables import LessonsTable
from .util.js import date_unix
from .util.weeks import CalendarWeek, get_weeks_for_year
from .util.date import CalendarWeek, get_weeks_for_year
from aleksis.core.util.core_helpers import has_person
......@@ -62,46 +62,29 @@ def my_timetable(
if has_person(request.user):
person = request.user.person
if person.is_teacher:
# Teacher
lesson_periods = LessonPeriod.objects.daily_lessons_for_person(person, wanted_day)
type_ = "teacher"
super_el = person
lesson_periods_person = person.lesson_periods_as_teacher
elif person.primary_group:
# Student
type_ = "group"
super_el = person.primary_group
lesson_periods_person = person.lesson_periods_as_participant
else:
if lesson_periods is None:
# If no student or teacher, redirect to all timetables
return redirect("all_timetables")
lesson_periods = lesson_periods_person.on_day(wanted_day)
# Build dictionary with lessons
per_period = {}
for lesson_period in lesson_periods:
if lesson_period.period.period in per_period:
per_period[lesson_period.period.period].append(lesson_period)
else:
per_period[lesson_period.period.period] = [lesson_period]
type_ = person.timetable_type
super_el = person.timetable_object
context["lesson_periods"] = OrderedDict(sorted(per_period.items()))
context["super"] = {"type": type_, "el": super_el}
context["type"] = type_
context["day"] = wanted_day
context["periods"] = TimePeriod.get_times_dict()
context["smart"] = True
context["lesson_periods"] = lesson_periods.per_period_one_day()
context["super"] = {"type": type_, "el": super_el}
context["type"] = type_
context["day"] = wanted_day
context["periods"] = TimePeriod.get_times_dict()
context["smart"] = True
context["url_prev"], context["url_next"] = TimePeriod.get_prev_next_by_day(
wanted_day, "my_timetable_by_date"
)
context["url_prev"], context["url_next"] = TimePeriod.get_prev_next_by_day(
wanted_day, "my_timetable_by_date"
)
return render(request, "chronos/my_timetable.html", context)
return render(request, "chronos/my_timetable.html", context)
else:
return redirect("all_timetables")
@login_required
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment