Skip to content
Snippets Groups Projects
Verified Commit 9cf9fa33 authored by Jonathan Weth's avatar Jonathan Weth :keyboard:
Browse files

Add mode for sending notifications for the next day

parent d0b7fcaa
No related branches found
No related tags found
1 merge request!242Resolve "Notification for changes in the substitution plan"
from typing import Any, Optional
import django.apps
from django.db import transaction
from reversion.signals import post_revision_commit
......@@ -33,3 +36,74 @@ class ChronosConfig(AppConfig):
transaction.on_commit(lambda: handle_new_revision.delay(revision.pk))
post_revision_commit.connect(_handle_post_revision_commit, weak=False)
def _ensure_notification_task(self):
from django.conf import settings # noqa
from celery import schedules
from django_celery_beat.models import CrontabSchedule, PeriodicTask
from aleksis.core.util.core_helpers import get_site_preferences
time_for_sending = get_site_preferences()["chronos__time_for_sending_notifications"]
active = get_site_preferences()["chronos__send_notifications_site"]
if active:
schedule = schedules.crontab(
minute=str(time_for_sending.minute), hour=str(time_for_sending.hour)
)
schedule = CrontabSchedule.from_schedule(schedule)
schedule.timezone = settings.TIME_ZONE
schedule.save()
possible_periodic_tasks = PeriodicTask.objects.filter(
task="chronos_send_notifications_for_next_day"
)
if not active:
possible_periodic_tasks.delete()
elif possible_periodic_tasks.exists():
task = possible_periodic_tasks[0]
for d_task in possible_periodic_tasks:
if d_task != task:
d_task.delete()
if task.crontab != schedule:
task.interval, task.solar, task.clocked = None, None, None
task.crontab = schedule
task.save()
elif active:
PeriodicTask.objects.get_or_create(
task="chronos_send_notifications_for_next_day",
crontab=schedule,
defaults=dict(name="Send notifications for next day (automatic schedule)"),
)
def preference_updated(
self,
sender: Any,
section: Optional[str] = None,
name: Optional[str] = None,
old_value: Optional[Any] = None,
new_value: Optional[Any] = None,
**kwargs,
) -> None:
if section == "chronos" and name in (
"send_notifications_site",
"time_for_sending_notifications",
):
self._ensure_notification_task()
def post_migrate(
self,
app_config: django.apps.AppConfig,
verbosity: int,
interactive: bool,
using: str,
**kwargs,
) -> None:
super().post_migrate(app_config, verbosity, interactive, using, **kwargs)
self._ensure_notification_task()
import zoneinfo
from datetime import date, timedelta
from typing import Optional, Union
from urllib.parse import urljoin
from django.conf import settings
from django.dispatch import receiver
from django.urls import reverse
from django.utils import timezone
from django.utils.formats import date_format
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext
from jsonstore import BooleanField
from reversion.models import Revision
from aleksis.apps.chronos.models import (
Event,
ExtraLesson,
LessonSubstitution,
SupervisionSubstitution,
)
from aleksis.core.models import Announcement, Group, Notification, Person
from aleksis.core.models import Announcement, Group, Person
from aleksis.core.util.core_helpers import get_site_preferences
from .managers import TimetableType
from .models import Lesson, LessonPeriod, Subject
from .util.change_tracker import timetable_data_changed
from .util.notifications import send_notifications_for_object
@Person.property_
......@@ -171,7 +162,7 @@ Person.add_permission(
@receiver(timetable_data_changed)
def send_notifications(sender: Revision, **kwargs):
"""Send notifications to users about the changes."""
if not get_site_preferences()["chronos__send_notifications"]:
if not get_site_preferences()["chronos__send_notifications_site"]:
return
for change in sender.changes.values():
......@@ -193,182 +184,4 @@ def send_notifications(sender: Revision, **kwargs):
# Skip this because it's not in the current range for notifications
continue
recipients = []
if isinstance(change.instance, LessonSubstitution):
recipients += change.instance.lesson_period.lesson.teachers.all()
recipients += change.instance.teachers.all()
recipients += Person.objects.filter(
member_of__in=change.instance.lesson_period.lesson.groups.all()
)
elif isinstance(change.instance, Event):
recipients += change.instance.teachers.all()
recipients += Person.objects.filter(member_of__in=change.instance.groups.all())
elif isinstance(change.instance, ExtraLesson):
recipients += change.instance.teachers.all()
recipients += Person.objects.filter(member_of__in=change.instance.groups.all())
elif isinstance(change.instance, SupervisionSubstitution):
recipients.append(change.instance.teacher)
recipients.append(change.instance.supervision.teacher)
print(change.instance, recipients)
description = ""
if isinstance(change.instance, LessonSubstitution):
# Date, lesson, subject
subject = change.instance.lesson_period.lesson.subject
day = change.instance.date
period = change.instance.lesson_period.period
if change.instance.cancelled:
description += (
_(
"The {subject} lesson in the {period}. period on {day} has been cancelled."
).format(subject=subject.name, period=period.period, day=date_format(day))
+ " "
)
else:
description += (
_(
"The {subject} lesson in the {period}. period "
"on {day} has some current changes."
).format(subject=subject.name, period=period.period, day=date_format(day))
+ " "
)
if change.instance.teachers.all():
description += (
ngettext(
"The teacher {old} is substituted by {new}.",
"The teachers {old} are substituted by {new}.",
change.instance.teachers.count(),
).format(
old=change.instance.lesson_period.lesson.teacher_names,
new=change.instance.teacher_names,
)
+ " "
)
if change.instance.subject:
description += (
_("The subject is changed to {subject}.").format(
subject=change.instance.subject.name
)
+ " "
)
if change.instance.room:
description += (
_("The lesson is moved from {old} to {new}.").format(
old=change.instance.lesson_period.room.name,
new=change.instance.room.name,
)
+ " "
)
if change.instance.comment:
description += (
_("There is an additional comment: {comment}.").format(
comment=change.instance.comment
)
+ " "
)
elif isinstance(change.instance, Event):
if change.instance.date_start != change.instance.date_end:
description += (
_(
"There is an event that starts on {date_start}, {period_from}. period "
"and ends on {date_end}, {period_to}. period:"
).format(
date_start=date_format(change.instance.date_start),
date_end=date_format(change.instance.date_end),
period_from=change.instance.period_from.period,
period_to=change.instance.period_to.period,
)
+ "\n"
)
else:
description += (
_(
"There is an event on {date} from the "
"{period_from}. period to the {period_to}. period:"
).format(
date=date_format(change.instance.date_start),
period_from=change.instance.period_from.period,
period_to=change.instance.period_to.period,
)
+ "\n"
)
if change.instance.groups.all():
description += (
_("Groups: {groups}").format(groups=change.instance.group_names) + "\n"
)
if change.instance.teachers.all():
description += (
_("Teachers: {teachers}").format(teachers=change.instance.teacher_names) + "\n"
)
if change.instance.rooms.all():
description += (
_("Rooms: {rooms}").format(
rooms=", ".join([room.name for room in change.instance.rooms.all()])
)
+ "\n"
)
elif isinstance(change.instance, ExtraLesson):
description += (
_("There is an extra lesson on {date} in the {period}. period:").format(
date=date_format(change.instance.date),
period=change.instance.period.period,
)
+ "\n"
)
if change.instance.groups.all():
description += (
_("Groups: {groups}").format(groups=change.instance.group_names) + "\n"
)
if change.instance.room:
description += (
_("Subject: {subject}").format(subject=change.instance.subject.name) + "\n"
)
if change.instance.teachers.all():
description += (
_("Teachers: {teachers}").format(teachers=change.instance.teacher_names) + "\n"
)
if change.instance.room:
description += _("Room: {room}").format(room=change.instance.room.name) + "\n"
if change.instance.comment:
description += (
_("Comment: {comment}.").format(comment=change.instance.comment) + "\n"
)
elif isinstance(change.instance, SupervisionSubstitution):
description += _(
"The supervision of {old} on {date} between the {period_from}. period "
"and the {period_to}. period in the area {area} is substituted by {new}."
).format(
old=change.instance.supervision.teacher.full_name,
date=date_format(change.instance.date),
period_from=change.instance.supervision.break_item.after_period_number,
period_to=change.instance.supervision.break_item.before_period_number,
area=change.instance.supervision.area.name,
new=change.instance.teacher.full_name,
)
url = urljoin(
settings.BASE_URL,
reverse(
"my_timetable_by_date",
args=[dt_start.date().year, dt_start.date().month, dt_start.date().day],
),
)
for recipient in recipients:
if recipient.preferences["chronos__send_notifications"]:
n = Notification(
recipient=recipient,
sender=_("Timetable"),
title=_("There are current changes to your timetable."),
description=description,
link=url,
)
n.save()
send_notifications_for_object(change.instance)
......@@ -83,7 +83,7 @@ class TimeForSendingNotifications(TimePreference):
@site_preferences_registry.register
class SendNotifications(BooleanPreference):
section = chronos
name = "send_notifications"
name = "send_notifications_site"
default = True
verbose_name = _("Send notifications for current timetable changes")
......
from django.utils import timezone
from aleksis.apps.chronos.models import (
Event,
ExtraLesson,
LessonSubstitution,
SupervisionSubstitution,
)
from aleksis.apps.chronos.util.notifications import send_notifications_for_object
from aleksis.core.celery import app
@app.task(name="chronos_send_notifications_for_next_day")
def send_notifications_for_next_day():
"""Send notifications for next day."""
next_day = timezone.now().date() + timezone.timedelta(days=1)
relevant_objects = []
relevant_objects += LessonSubstitution.objects.on_day(next_day)
relevant_objects += ExtraLesson.objects.on_day(next_day)
relevant_objects += Event.objects.on_day(next_day)
relevant_objects += SupervisionSubstitution.objects.filter(date=next_day)
for instance in relevant_objects:
send_notifications_for_object(instance)
from typing import Union
from urllib.parse import urljoin
from django.conf import settings
from django.urls import reverse
from django.utils.formats import date_format
from django.utils.translation import gettext_lazy as _
from django.utils.translation import ngettext
from aleksis.core.models import Notification, Person
from ..models import Event, ExtraLesson, LessonSubstitution, SupervisionSubstitution
def send_notifications_for_object(
instance: Union[ExtraLesson, LessonSubstitution, Event, SupervisionSubstitution]
):
"""Send notifications for a change object."""
recipients = []
if isinstance(instance, LessonSubstitution):
recipients += instance.lesson_period.lesson.teachers.all()
recipients += instance.teachers.all()
recipients += Person.objects.filter(
member_of__in=instance.lesson_period.lesson.groups.all()
)
elif isinstance(instance, Event):
recipients += instance.teachers.all()
recipients += Person.objects.filter(member_of__in=instance.groups.all())
elif isinstance(instance, ExtraLesson):
recipients += instance.teachers.all()
recipients += Person.objects.filter(member_of__in=instance.groups.all())
elif isinstance(instance, SupervisionSubstitution):
recipients.append(instance.teacher)
recipients.append(instance.supervision.teacher)
description = ""
if isinstance(instance, LessonSubstitution):
# Date, lesson, subject
subject = instance.lesson_period.lesson.subject
day = instance.date
period = instance.lesson_period.period
if instance.cancelled:
description += (
_(
"The {subject} lesson in the {period}. period on {day} has been cancelled."
).format(subject=subject.name, period=period.period, day=date_format(day))
+ " "
)
else:
description += (
_(
"The {subject} lesson in the {period}. period "
"on {day} has some current changes."
).format(subject=subject.name, period=period.period, day=date_format(day))
+ " "
)
if instance.teachers.all():
description += (
ngettext(
"The teacher {old} is substituted by {new}.",
"The teachers {old} are substituted by {new}.",
instance.teachers.count(),
).format(
old=instance.lesson_period.lesson.teacher_names,
new=instance.teacher_names,
)
+ " "
)
if instance.subject:
description += (
_("The subject is changed to {subject}.").format(subject=instance.subject.name)
+ " "
)
if instance.room:
description += (
_("The lesson is moved from {old} to {new}.").format(
old=instance.lesson_period.room.name,
new=instance.room.name,
)
+ " "
)
if instance.comment:
description += (
_("There is an additional comment: {comment}.").format(comment=instance.comment)
+ " "
)
elif isinstance(instance, Event):
if instance.date_start != instance.date_end:
description += (
_(
"There is an event that starts on {date_start}, {period_from}. period "
"and ends on {date_end}, {period_to}. period:"
).format(
date_start=date_format(instance.date_start),
date_end=date_format(instance.date_end),
period_from=instance.period_from.period,
period_to=instance.period_to.period,
)
+ "\n"
)
else:
description += (
_(
"There is an event on {date} from the "
"{period_from}. period to the {period_to}. period:"
).format(
date=date_format(instance.date_start),
period_from=instance.period_from.period,
period_to=instance.period_to.period,
)
+ "\n"
)
if instance.groups.all():
description += _("Groups: {groups}").format(groups=instance.group_names) + "\n"
if instance.teachers.all():
description += _("Teachers: {teachers}").format(teachers=instance.teacher_names) + "\n"
if instance.rooms.all():
description += (
_("Rooms: {rooms}").format(
rooms=", ".join([room.name for room in instance.rooms.all()])
)
+ "\n"
)
elif isinstance(instance, ExtraLesson):
description += (
_("There is an extra lesson on {date} in the {period}. period:").format(
date=date_format(instance.date),
period=instance.period.period,
)
+ "\n"
)
if instance.groups.all():
description += _("Groups: {groups}").format(groups=instance.group_names) + "\n"
if instance.room:
description += _("Subject: {subject}").format(subject=instance.subject.name) + "\n"
if instance.teachers.all():
description += _("Teachers: {teachers}").format(teachers=instance.teacher_names) + "\n"
if instance.room:
description += _("Room: {room}").format(room=instance.room.name) + "\n"
if instance.comment:
description += _("Comment: {comment}.").format(comment=instance.comment) + "\n"
elif isinstance(instance, SupervisionSubstitution):
description += _(
"The supervision of {old} on {date} between the {period_from}. period "
"and the {period_to}. period in the area {area} is substituted by {new}."
).format(
old=instance.supervision.teacher.full_name,
date=date_format(instance.date),
period_from=instance.supervision.break_item.after_period_number,
period_to=instance.supervision.break_item.before_period_number,
area=instance.supervision.area.name,
new=instance.teacher.full_name,
)
day = instance.date if hasattr(instance, "date") else instance.date_start
url = urljoin(
settings.BASE_URL,
reverse(
"my_timetable_by_date",
args=[day.year, day.month, day.day],
),
)
for recipient in recipients:
if recipient.preferences["chronos__send_notifications"]:
n = Notification(
recipient=recipient,
sender=_("Timetable"),
title=_("There are current changes to your timetable."),
description=description,
link=url,
)
n.save()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment