Skip to content
Snippets Groups Projects
Verified Commit 334cb21e authored by Andreas Ellewsen's avatar Andreas Ellewsen
Browse files

Remove task scheduler

We have switched to using Django Q for queuing tasks so the task
scheduler can be removed. A future commit will reintroduce queing of
notifications on the end date of roles.
parent 2c436668
No related branches found
No related tags found
1 merge request!137Greg 65 notification on start
import logging
import logging.config
import time
from django.conf import settings
from django.core.management.base import BaseCommand
from greg.schedule import ExpiringRolesNotification
logging.config.dictConfig(settings.LOGGING)
logger = logging.getLogger(__name__)
class Command(BaseCommand):
"""
This command starts a basic task runner. All tasks it is supposed to
run are given explicitly in code
"""
help = "Start schedule task runner"
def handle(self, *args, **options):
logger.info("Task scheduler started")
# For now there is just one task, but the idea is that more can
# be added
expiring_role_notification_task = ExpiringRolesNotification()
while True:
expiring_role_notification_task.run()
# The single task is set up for far only needs to run once
# a day, but running every 6 hours just in case a run fails,
# it is up the task it self to figure out if needs to do
# something every time it is called
time.sleep(60 * 60 * 6)
# Generated by Django 3.2.8 on 2021-11-04 10:02
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("greg", "0012_ou_identifiers"),
]
operations = [
migrations.DeleteModel(
name="ScheduleTask",
),
]
......@@ -469,20 +469,6 @@ class SponsorOrganizationalUnit(BaseModel):
)
class ScheduleTask(models.Model):
"""
Stores information about a task
"""
name = models.CharField(max_length=32)
last_completed = models.DateTimeField(null=True)
def __repr__(self):
return "{}(id={!r}, name={!r}, last_completed={!r})".format(
self.__class__.__name__, self.pk, self.name, self.last_completed
)
class InvitationLink(BaseModel):
"""
Link to an invitation.
......
import time
from datetime import date, datetime, timedelta
from typing import Optional
from abc import ABC, abstractmethod
from django.utils import timezone
from greg.models import Role, Notification, ScheduleTask
class BaseSchedule(ABC):
"""
Provides common methods for tasks
"""
task_object: ScheduleTask
def __init__(self, name: str):
self.task_object = ScheduleTask.objects.get(name=name)
def run(self):
self._run_internal()
self.task_object.last_completed = timezone.now()
self.task_object.save()
@abstractmethod
def _run_internal(self):
pass
def get_last_run(self) -> Optional[datetime]:
self.task_object.refresh_from_db()
return self.task_object.last_completed
def _store_notification(
self, identifier, object_type, operation, **kwargs
) -> Notification:
return Notification.objects.create(
identifier=identifier,
object_type=object_type,
operation=operation,
issued_at=int(time.time()),
meta=kwargs,
)
class ExpiringRolesNotification(BaseSchedule):
"""
This task does a simple check for roles that will expire in 30 days
and creates entries in the notification table for them.
There should be an entry in the ScheduleTask-table with name role_expiration
before this task is run.
Some assumptions that are made:
- The task will be run every day, it does
not keep track of which roles it has or has not notified, it only
looks at which roles will expire in exactly 30 days
- If there are roles created that expire in less than 30 days, no
notification is necessary
"""
EXPIRATION_THRESHOLD_DAYS = 30
def __init__(self):
# Will raise exception if there is not exactly one result
super().__init__("role_expiration")
def _run_internal(self):
last_run = self.get_last_run()
# Only run once per day
if last_run is None or last_run.date() != date.today():
check_date = datetime.today() + timedelta(
days=self.EXPIRATION_THRESHOLD_DAYS
)
self.__get_roles_about_to_expire(check_date)
def __get_roles_about_to_expire(self, end_date: date):
roles_about_to_expire = Role.objects.filter(end_date=end_date)
for role in roles_about_to_expire:
meta = {"person_id": role.person.id, "type_id": role.type.id}
self._store_notification(
identifier=role.id,
object_type="PersonRole",
operation="expire_reminder",
**meta
)
import pytest
from greg.models import ScheduleTask
@pytest.fixture
def scheduletask():
ScheduleTask.objects.create(name="foo", last_completed="2020-10-15T23:04Z")
return ScheduleTask.objects.get(id=1)
@pytest.mark.django_db
def test_scheduletask_repr(scheduletask):
assert (
repr(scheduletask)
== "ScheduleTask(id=1, name='foo', last_completed=datetime.datetime(2020, 10, 15, 23, 4, tzinfo=<UTC>))"
)
from datetime import datetime, timedelta
import pytest
from django.db.models import Q
from django.utils import timezone
from greg.models import (
ScheduleTask,
RoleType,
Person,
OrganizationalUnit,
Role,
Notification,
Sponsor,
)
from greg.schedule import ExpiringRolesNotification
@pytest.fixture
def role_task():
ScheduleTask.objects.create(
name="role_expiration", last_completed=timezone.now() - timedelta(days=1)
)
@pytest.fixture
def role(
person: Person,
role_type_bar: RoleType,
unit_foo: OrganizationalUnit,
sponsor_guy: Sponsor,
) -> Role:
return Role.objects.create(
person=person,
type=role_type_bar,
orgunit=unit_foo,
start_date="2020-03-05",
end_date=datetime.today() + timedelta(days=30),
contact_person_unit="Contact Person",
available_in_search=True,
sponsor=sponsor_guy,
)
@pytest.mark.django_db
def test_role_picked_up(role_task: ScheduleTask, role: Role):
role_notification = ExpiringRolesNotification()
assert len(Notification.objects.filter(~Q(operation="add"))) == 0
role_notification.run()
notification = Notification.objects.get(operation="expire_reminder")
assert notification.identifier == role.id
role_notification.run()
notifications = Notification.objects.filter(operation="expire_reminder")
assert len(notifications) == 1
@pytest.mark.django_db
def test_no_notification_for_role_not_about_to_expire(
role_task: ScheduleTask, role: Role
):
role.end_date = datetime.today() + timedelta(days=31)
role.save()
role_notification = ExpiringRolesNotification()
assert len(Notification.objects.filter(operation="expire_reminder")) == 0
# Role should not be picked up since it expires in 31 days
role_notification.run()
notifications = Notification.objects.filter(operation="expire_reminder")
assert len(notifications) == 0
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