shiftregister/shiftregister/app/models.py

136 lines
4.4 KiB
Python

from django.db import models
import secrets
from django.shortcuts import reverse
from datetime import timedelta
from django.utils import timezone
from django.db.models import F, Count, Q, ExpressionWrapper
from phonenumber_field.modelfields import PhoneNumberField
from dynamic_preferences.registries import global_preferences_registry
global_preferences = global_preferences_registry.manager()
class Room(models.Model):
name = models.CharField(max_length=200, primary_key=True)
required_helpers = models.IntegerField()
def __str__(self):
return self.name
class Shift(models.Model):
room = models.ForeignKey(Room, on_delete=models.RESTRICT)
start_at = models.DateTimeField()
duration = models.DurationField()
required_helpers = models.IntegerField(
default=0, help_text="When this is set to zero, the room value is used instead."
)
deleted = models.BooleanField(default=False)
def __str__(self):
return f"{self.room.name}: {self.start_at}"
def has_ended(self):
return (self.start_at + self.duration) < timezone.now()
def is_running(self):
return (self.start_at <= timezone.now()) and (not self.has_ended())
class Helper(models.Model):
phone = PhoneNumberField(unique=True, editable=False)
name = models.CharField(max_length=200)
# change this to a generic state variable to allow for number blocking/account deactivation?
number_validated = models.BooleanField(default=False)
def __str__(self):
return self.name
def send_confirmation(self):
(token, created) = LoginToken.objects.get_or_create(helper=self)
token.send()
return token
# current or next shift
def important_shift(self):
ret = (
ShiftRegistration.objects.annotate(
shift_end=ExpressionWrapper(
F("shift__start_at") + F("shift__duration"),
output_field=models.DateTimeField(),
)
)
.filter(helper=self, shift_end__gte=timezone.now(), shift__deleted=False)
.order_by("shift__start_at")
.first()
)
if ret:
return ret.shift
class ShiftRegistration(models.Model):
class Meta:
unique_together = (("shift", "helper"),)
# use restrict for now as Model.delete is not called
shift = models.ForeignKey(Shift, on_delete=models.RESTRICT)
helper = models.ForeignKey(Helper, on_delete=models.CASCADE)
reminder_sent = models.BooleanField(default=False)
def can_cancel(self):
return self.shift.start_at > (
timezone.now()
+ global_preferences_registry.manager()["helper__min_cancel_time"]
)
def send_reminder(self):
text = f"Deine kontakt-Schicht beginnt um {self.shift.start_at.strftime('%H:%M')}, bitte komm eine halbe Stunde vorher an den Infopoint."
msg = Message(to=self.helper, text=text)
msg.save()
self.reminder_sent = True
self.save()
def __str__(self):
return f"{self.helper.name}: {self.shift}"
class Message(models.Model):
# remove limit and send long messages in multiple messages?
text = models.CharField(max_length=160)
to = models.ForeignKey(Helper, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
sent_at = models.DateTimeField(null=True)
def __str__(self):
return f"{self.to.name}({self.created_at}): {self.text}"
def gen_token():
return secrets.token_urlsafe(
15
) # returns 15 bytes Base64-encoded (times 1.333...) = 20 characters
class LoginToken(models.Model):
id = models.CharField(
max_length=20, primary_key=True, default=gen_token, editable=False
)
helper = models.ForeignKey(Helper, on_delete=models.CASCADE)
sent_at = models.DateTimeField(auto_now_add=True)
send_count = models.IntegerField(default=0)
def send(self):
text = f"Dein Registrierungslink zum Helfer*innensystem: https://kontakt.rocks{self.get_absolute_url()}\nWenn du dich nicht registriert hast, ignoriere diese SMS."
msg = Message(to=self.helper, text=text)
msg.save()
self.sent_at = timezone.now()
self.send_count += 1
self.save()
# import here to break import cycle
from .tasks import send_message
send_message.delay(msg.pk)
def get_absolute_url(self):
return reverse("token_login", kwargs={"token": self.id})