Implement periodic import of shifts
parent
38a72a97be
commit
3dd3c028e1
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.0.4 on 2022-04-23 00:40
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("app", "0005_alter_helper_phone"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="shift",
|
||||||
|
name="deleted",
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,7 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from .models import Calendar
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Calendar)
|
||||||
|
class CalendarAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ("url", "has_errors")
|
@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ImporterConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "shiftregister.importer"
|
@ -0,0 +1,83 @@
|
|||||||
|
from datetime import timezone
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import transaction
|
||||||
|
from icalendar import Calendar
|
||||||
|
from .models import Event, Room, Shift
|
||||||
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
def import_calendar(calendar):
|
||||||
|
try:
|
||||||
|
r = requests.get(calendar.url)
|
||||||
|
r.raise_for_status()
|
||||||
|
except:
|
||||||
|
if settings.DEBUG:
|
||||||
|
raise
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not r.headers["content-type"].startswith("text/calendar"):
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
cal = Calendar.from_ical(r.text)
|
||||||
|
|
||||||
|
rooms = {}
|
||||||
|
events = {}
|
||||||
|
for event in cal.walk("vevent"):
|
||||||
|
uid = event.decoded("uid").decode()
|
||||||
|
room = (
|
||||||
|
event.decoded("location", None) or event.decoded("summary")
|
||||||
|
).decode()
|
||||||
|
start = event.decoded("dtstart").astimezone(timezone.utc)
|
||||||
|
end = event.decoded("dtend").astimezone(timezone.utc)
|
||||||
|
|
||||||
|
if not uid or not room:
|
||||||
|
return False
|
||||||
|
|
||||||
|
rooms[room] = None
|
||||||
|
events[uid] = (
|
||||||
|
room,
|
||||||
|
{
|
||||||
|
"start_at": start,
|
||||||
|
"duration": end - start,
|
||||||
|
"uuid": uid,
|
||||||
|
"calendar": calendar,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
with transaction.atomic():
|
||||||
|
for r in Room.objects.filter(name__in=rooms):
|
||||||
|
rooms[r.name] = r
|
||||||
|
for room, r in rooms.items():
|
||||||
|
if r == None:
|
||||||
|
rooms[room] = Room(
|
||||||
|
name=room, required_helpers=0
|
||||||
|
) # required_helpers=0 ensures a shift in a new room is not displayed until the correct number of required helpers is set
|
||||||
|
rooms[room].save()
|
||||||
|
|
||||||
|
for e in Event.objects.filter(calendar=calendar, uuid__in=events):
|
||||||
|
uuid = str(e.uuid)
|
||||||
|
room, event = events[uuid]
|
||||||
|
|
||||||
|
e.room = rooms[room]
|
||||||
|
e.start_at = event["start_at"]
|
||||||
|
e.duration = event["duration"]
|
||||||
|
e.save()
|
||||||
|
|
||||||
|
events[uuid] = (room, e)
|
||||||
|
for event in events:
|
||||||
|
room, e = events[event]
|
||||||
|
if not isinstance(e, Event):
|
||||||
|
Event(room=rooms[room], **e).save()
|
||||||
|
|
||||||
|
for event in Event.objects.filter(calendar=calendar).exclude(
|
||||||
|
uuid__in=events
|
||||||
|
):
|
||||||
|
event.deleted = True
|
||||||
|
event.save()
|
||||||
|
except:
|
||||||
|
if settings.DEBUG:
|
||||||
|
raise
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
@ -0,0 +1,49 @@
|
|||||||
|
# Generated by Django 4.0.4 on 2022-04-23 00:42
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("app", "0006_shift_deleted"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Calendar",
|
||||||
|
fields=[
|
||||||
|
("url", models.URLField(primary_key=True, serialize=False)),
|
||||||
|
("has_errors", models.BooleanField(default=False, editable=False)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="Event",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"shift_ptr",
|
||||||
|
models.OneToOneField(
|
||||||
|
auto_created=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
parent_link=True,
|
||||||
|
to="app.shift",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"uuid",
|
||||||
|
models.UUIDField(editable=False, primary_key=True, serialize=False),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"calendar",
|
||||||
|
models.ForeignKey(
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
to="importer.calendar",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
bases=("app.shift",),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,12 @@
|
|||||||
|
from django.db import models
|
||||||
|
from shiftregister.app.models import *
|
||||||
|
|
||||||
|
|
||||||
|
class Calendar(models.Model):
|
||||||
|
url = models.URLField(primary_key=True)
|
||||||
|
has_errors = models.BooleanField(default=False, editable=False)
|
||||||
|
|
||||||
|
|
||||||
|
class Event(Shift):
|
||||||
|
uuid = models.UUIDField(primary_key=True, editable=False)
|
||||||
|
calendar = models.ForeignKey(Calendar, on_delete=models.CASCADE)
|
@ -0,0 +1,10 @@
|
|||||||
|
from celery import shared_task
|
||||||
|
from .importer import import_calendar
|
||||||
|
from .models import Calendar
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def import_shifts():
|
||||||
|
for calendar in Calendar.objects.all():
|
||||||
|
calendar.has_errors = not import_calendar(calendar)
|
||||||
|
calendar.save()
|
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
@ -0,0 +1,3 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
Loading…
Reference in New Issue