You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

379 lines
11 KiB
Python

from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.utils import timezone
from django.db.models.fields import DateTimeField
from django.db.models import F, Count, Q, ExpressionWrapper, Case, When
from .models import (
ShiftRegistration,
Room,
Shift,
Helper,
Message,
IncomingMessage,
RoomViewToken,
)
from django.views.generic import DetailView, ListView
from django.views.generic.edit import FormMixin
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib import messages
from django.db import models, transaction
from django.core.paginator import Paginator
from .forms import BulkMessage, HelperShift, HelperMessage
from datetime import timedelta
# Create your views here.
def index(request):
return redirect("team:shift_overview")
@login_required
def shift_overview(request):
checkin_count = Count(
Case(
When(
shiftregistration__state=ShiftRegistration.RegState.CHECKED_IN, then=1
),
output_field=models.IntegerField(),
)
)
context = {}
context["running_shifts"] = (
Shift.with_reg_count()
.annotate(
checkin_count=Count(
Case(
When(
shiftregistration__state=ShiftRegistration.RegState.CHECKED_IN,
then=1,
),
output_field=models.IntegerField(),
)
),
end_at=ExpressionWrapper(
F("start_at") + F("duration"), output_field=DateTimeField()
),
)
.filter(start_at__lte=timezone.now(), end_at__gte=timezone.now(), deleted=False)
.order_by("start_at")
)
context["next_shifts"] = (
Shift.with_reg_count()
.annotate(checkin_count=checkin_count)
.filter(
start_at__gt=timezone.now(),
start_at__lte=timezone.now() + timedelta(minutes=30),
deleted=False,
)
.order_by("start_at")
)
# only Postgres supports DISTINCT on specific columns, SQLite does not support aggregates on datetime fields
context["next_shifts_per_room"] = filter(
lambda x: x is not None,
(
Shift.with_reg_count()
.filter(room=room, start_at__gt=timezone.now(), deleted=False)
.order_by("start_at")
.first()
for room in Room.objects.all().order_by("name")
),
)
return render(request, "shift_overview.html", context)
def add_helper_shift(self):
pass
@login_required
def shift_detail(request, pk):
shift = get_object_or_404(
Shift.with_reg_count().prefetch_related("shiftregistration_set__helper"), pk=pk
)
form = HelperShift()
if request.method == "POST":
form = HelperShift(request.POST)
if form.is_valid():
(reg, created) = ShiftRegistration.objects.get_or_create(
helper=form.cleaned_data["helper"], shift=shift
)
if created:
messages.add_message(
request,
messages.SUCCESS,
"Helfer erfolgreich zur Schicht hinzugefügt",
)
else:
messages.add_message(
request,
messages.WARNING,
"Helfer ist bereits für diese Schicht angemeldet",
)
return redirect("team:shift", pk=shift.pk)
context = {
"shift": shift,
"add_helper_form": form,
}
return render(request, "shift_detail.html", context)
@login_required
def bulk_message(request):
form = BulkMessage()
if request.method == "POST":
form = BulkMessage(request.POST)
if form.is_valid():
helpers = Helper.objects.filter(number_validated=True)
if form.cleaned_data["checked_in_only"]:
helpers = Helper.objects.annotate(
shift_count=Count(
Case(
When(
shiftregistration__state__in=[
ShiftRegistration.RegState.CHECKED_IN,
],
then=1,
),
output_field=models.IntegerField(),
)
)
).filter(number_validated=True, shift_count__gte=1)
try:
outbox = []
for helper in helpers:
text = form.cleaned_data["message"].replace(
"$token",
f"https://helfen.kntkt.de{helper.logintoken_set.first().get_absolute_url()}",
)
text = text.replace(
"$fb",
f"https://helfen.kntkt.de/f/{helper.logintoken_set.first().id}",
)
outbox.append(Message(text=text, to=helper))
Message.objects.bulk_create(outbox)
messages.add_message(
request, messages.SUCCESS, "Massen-Nachricht erfolgreich versendet"
)
except:
messages.add_message(
request,
messages.ERROR,
"Fehler beim Versenden der Massen-Nachricht",
)
context = {
"form": form,
}
return render(request, "bulk_message.html", context)
class HelperDetail(FormMixin, LoginRequiredMixin, DetailView):
template_name = "helper_detail.html"
model = Helper
form_class = HelperMessage
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["history"] = (
IncomingMessage.objects.filter(sender=self.object.phone)
.annotate(incoming=models.Value(True))
.values_list("content", "created_at", "read", "incoming")
.union(
self.object.message_set.annotate(
read=models.Value(True), incoming=models.Value(False)
).values_list("text", "sent_at", "read", "incoming")
)
.order_by(F("created_at").asc(nulls_last=True))
)
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if form.is_valid():
IncomingMessage.objects.filter(sender=self.object.phone).update(read=True)
Message(
text=form.cleaned_data["message"].replace(
"$token",
f"https://helfen.kntkt.de{self.object.logintoken_set.first().get_absolute_url()}",
),
to=self.object,
).save()
return self.render_to_response(self.get_context_data(form=form))
class ShiftList(LoginRequiredMixin, ListView):
template_name = "shift_list.html"
model = Shift
title = "Alle Schichten"
def get_queryset(self):
return Shift.with_reg_count()
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = self.title
return context
def get_ordering(self):
return ("start_at", "room__name")
class FreeShiftList(ShiftList):
title = "Freie Schichten"
def get_queryset(self):
help_wanted = Q(required_helpers__gt=F("reg_count")) | Q(
required_helpers=0
) & Q(room__required_helpers__gt=F("reg_count"))
return (
Shift.with_reg_count()
.annotate(
end_at=ExpressionWrapper(
F("start_at") + F("duration"),
output_field=DateTimeField(),
)
)
.filter(
help_wanted,
end_at__gte=timezone.now(),
deleted=False,
)
.order_by("start_at", "room__name")
)
class RoomShiftList(ShiftList):
def get_context_data(self, **kwargs):
room = get_object_or_404(Room, pk=self.kwargs["pk"])
context = super().get_context_data(**kwargs)
context["title"] = f"Schichten für {room.name}"
return context
def get_queryset(self):
room = get_object_or_404(Room, pk=self.kwargs["pk"])
help_wanted = Q(required_helpers__gt=F("reg_count")) | Q(
required_helpers=0
) & Q(room__required_helpers__gt=F("reg_count"))
return (
Shift.with_reg_count()
.filter(
deleted=False,
room=room,
)
.order_by("start_at", "room__name")
)
class CheckinList(LoginRequiredMixin, ListView):
template_name = "checkin_list.html"
model = Shift
title = "Ankommende Helfer:innen"
def get_queryset(self):
return ShiftRegistration.objects.filter(
shift__deleted=False,
state=ShiftRegistration.RegState.REGISTERED,
).order_by("shift__start_at")[:30]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = self.title
return context
def get_ordering(self):
return ("start_at", "room__name")
@login_required
def checkin(request, pk):
reg = get_object_or_404(ShiftRegistration, pk=pk)
reg.state = reg.RegState.CHECKED_IN
reg.save()
return redirect("team:shift", pk=reg.shift.pk)
@login_required
def mark_as_failed(request, pk):
reg = get_object_or_404(ShiftRegistration, pk=pk)
if request.method == "POST":
with transaction.atomic():
reg.state = reg.RegState.FAILED
reg.save()
# TODO: Mark helper as barred from further shift registrations (and delete pending existing ones)
return redirect("team:shift", pk=reg.shift.pk)
return render(request, "molly_guard.html", {"reg": reg})
@login_required
def delete_shiftregistration(request, pk):
reg = get_object_or_404(ShiftRegistration, pk=pk)
spk = reg.shift.pk
reg.delete()
return redirect("team:shift", pk=spk)
@login_required
def incoming_messages(request):
p = Paginator(IncomingMessage.objects.order_by("read", "-created_at"), 10)
page = p.get_page(request.GET.get("page"))
return render(
request,
"incoming_messages.html",
{
"num_unread": IncomingMessage.objects.filter(read=False).count(),
"page": page,
"page_range": p.get_elided_page_range(page.number),
},
)
@login_required
def incoming_message(request, pk):
message = get_object_or_404(IncomingMessage, pk=pk)
if request.method == "POST":
message.read = True
message.save()
return render(request, "incoming_message.html", {"message": message})
@login_required
def mark_as_read(request, pk):
helper = get_object_or_404(Helper, pk=pk)
if request.method == "POST":
IncomingMessage.objects.filter(sender=helper.phone).update(read=True)
return redirect("team:helper", pk=pk)
def room_view_token(request, token):
return render(
request,
"room_registrations.html",
{
"room": get_object_or_404(RoomViewToken, pk=token).room,
},
)