feat: implement restricted shifts
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
701caae254
commit
c5bb532749
|
@ -28,18 +28,11 @@ class Room(models.Model):
|
|||
return self.shift_set.filter(deleted=False)
|
||||
|
||||
|
||||
class Shift(models.Model):
|
||||
room = models.ForeignKey(Room, on_delete=models.RESTRICT)
|
||||
start_at = models.DateTimeField(db_index=True)
|
||||
duration = models.DurationField()
|
||||
required_helpers = models.IntegerField(
|
||||
default=0, help_text="When this is set to zero, the room value is used instead."
|
||||
)
|
||||
description = models.TextField(blank=True, default="")
|
||||
deleted = models.BooleanField(default=False, db_index=True)
|
||||
|
||||
def with_reg_count():
|
||||
return Shift.objects.annotate(
|
||||
class ShiftManager(models.Manager):
|
||||
def with_reg_count(self):
|
||||
return (
|
||||
self.get_queryset()
|
||||
.annotate(
|
||||
reg_count=Count(
|
||||
"shiftregistration",
|
||||
distinct=True,
|
||||
|
@ -50,7 +43,36 @@ class Shift(models.Model):
|
|||
)
|
||||
),
|
||||
)
|
||||
).select_related("room")
|
||||
)
|
||||
.select_related("room")
|
||||
)
|
||||
|
||||
|
||||
class DisplayShiftManager(ShiftManager):
|
||||
def get_queryset(self):
|
||||
return (
|
||||
super()
|
||||
.get_queryset()
|
||||
.select_related("event__calendar")
|
||||
.filter(
|
||||
Q(event__isnull=True) | Q(event__calendar__restricted=False),
|
||||
deleted=False,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class Shift(models.Model):
|
||||
all_objects = ShiftManager()
|
||||
objects = DisplayShiftManager()
|
||||
|
||||
room = models.ForeignKey(Room, on_delete=models.RESTRICT)
|
||||
start_at = models.DateTimeField(db_index=True)
|
||||
duration = models.DurationField()
|
||||
required_helpers = models.IntegerField(
|
||||
default=0, help_text="When this is set to zero, the room value is used instead."
|
||||
)
|
||||
description = models.TextField(blank=True, default="")
|
||||
deleted = models.BooleanField(default=False, db_index=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.room.name}: {self.start_at}"
|
||||
|
|
|
@ -13,7 +13,7 @@ def notify_shift_changed(sender, **kwargs):
|
|||
if issubclass(sender, Shift):
|
||||
instance = kwargs["instance"]
|
||||
try:
|
||||
prev = Shift.objects.get(pk=instance.id)
|
||||
prev = Shift.all_objects.get(pk=instance.id)
|
||||
except Shift.DoesNotExist:
|
||||
return
|
||||
|
||||
|
|
|
@ -13,11 +13,13 @@
|
|||
<div class="notification">Diese Schicht wurde gelöscht.</div>
|
||||
{% endif %}
|
||||
{% if not can_register and not is_registered %}
|
||||
{% if has_overlap %}
|
||||
{% if shift.restricted %}
|
||||
<div class="notification is-warning">Diese Schicht kann nur von Teamis besetzt werden, daher kannst du dich nicht anmelden.</div>
|
||||
{% elif has_overlap %}
|
||||
<div class="notification is-warning">Du hast bereits eine überlappende Schicht zu dieser Zeit: <a href="{% url 'shift' overlapping_shift.id %}">{{ overlapping_shift.room.name }} ({{ overlapping_shift.start_at }})</a></div>
|
||||
{% else %}
|
||||
{% else %}
|
||||
<div class="notification">Diese Schicht ist bereits besetzt.</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<div class="content">
|
||||
<p>
|
||||
|
|
|
@ -8,6 +8,7 @@ from django.core.cache import cache
|
|||
from django.db import transaction
|
||||
from django.db.models import Count, ExpressionWrapper, F, Q
|
||||
from django.db.models.fields import DateTimeField
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.utils import timezone
|
||||
from dynamic_preferences.registries import global_preferences_registry
|
||||
|
@ -30,7 +31,7 @@ def index(request):
|
|||
event_end_at = global_preferences["helper__event_end_at"]
|
||||
days = (
|
||||
Shift.objects.filter(
|
||||
deleted=False, start_at__gte=event_start_at, start_at__lte=event_end_at
|
||||
start_at__gte=event_start_at, start_at__lte=event_end_at
|
||||
)
|
||||
.datetimes("start_at", "day")
|
||||
.all()
|
||||
|
@ -65,13 +66,12 @@ def index(request):
|
|||
# dont show shifts starting in <60 minutes?
|
||||
# currently only sorts by date
|
||||
free_shifts = (
|
||||
Shift.with_reg_count()
|
||||
Shift.objects.with_reg_count()
|
||||
.filter(
|
||||
help_wanted,
|
||||
start_at__gte=day + timedelta(hours=6),
|
||||
start_at__lte=day + timedelta(hours=30),
|
||||
start_at__gt=timezone.now(),
|
||||
deleted=False,
|
||||
)
|
||||
.order_by("start_at")
|
||||
for day in days
|
||||
|
@ -191,7 +191,12 @@ def register(request):
|
|||
|
||||
@event_state
|
||||
def shift(request, shiftid):
|
||||
shift = get_object_or_404(Shift.with_reg_count(), pk=shiftid)
|
||||
shift = get_object_or_404(
|
||||
Shift.all_objects.with_reg_count()
|
||||
.select_related("event__calendar")
|
||||
.annotate(restricted=Coalesce("event__calendar__restricted", False)),
|
||||
pk=shiftid,
|
||||
)
|
||||
helper = request.helper
|
||||
context = {
|
||||
"enable_asta": global_preferences["helper__enable_asta"],
|
||||
|
@ -201,15 +206,14 @@ def shift(request, shiftid):
|
|||
"shift": shift,
|
||||
"shift_form": EmptyForm,
|
||||
}
|
||||
|
||||
# this currently ignores date/time
|
||||
request.session["last_seen_shift"] = shiftid
|
||||
if (
|
||||
context["can_register"] = (
|
||||
shift.required_helpers > shift.registration_count()
|
||||
or shift.required_helpers == 0
|
||||
and shift.room.required_helpers > shift.registration_count()
|
||||
):
|
||||
context["can_register"] = True
|
||||
) and not shift.restricted
|
||||
|
||||
# this currently ignores date/time
|
||||
request.session["last_seen_shift"] = shiftid
|
||||
|
||||
if helper:
|
||||
context["helper"] = helper
|
||||
|
|
|
@ -10,5 +10,5 @@ def update_calendar(modeladmin, request, queryset):
|
|||
|
||||
@admin.register(Calendar)
|
||||
class CalendarAdmin(admin.ModelAdmin):
|
||||
list_display = ("name", "url", "needs_fallback", "has_errors")
|
||||
list_display = ("name", "url", "needs_fallback", "restricted", "has_errors")
|
||||
actions = (update_calendar,)
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 5.0.4 on 2025-05-16 22:17
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("importer", "0004_calendar_name"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="calendar",
|
||||
name="restricted",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
]
|
|
@ -8,6 +8,7 @@ class Calendar(models.Model):
|
|||
needs_fallback = models.BooleanField(default=False, editable=True)
|
||||
has_errors = models.BooleanField(default=False, editable=False)
|
||||
name = models.CharField(max_length=255, null=True, blank=True)
|
||||
restricted = models.BooleanField(default=False)
|
||||
|
||||
def update(self):
|
||||
# break circular import
|
||||
|
@ -18,5 +19,7 @@ class Calendar(models.Model):
|
|||
|
||||
|
||||
class Event(Shift):
|
||||
objects = models.Manager()
|
||||
|
||||
uuid = models.UUIDField(primary_key=True, editable=False)
|
||||
calendar = models.ForeignKey(Calendar, on_delete=models.CASCADE)
|
||||
|
|
|
@ -35,7 +35,7 @@ def metrics(request):
|
|||
),
|
||||
(
|
||||
"fallback_shifts_full",
|
||||
Shift.with_reg_count()
|
||||
Shift.objects.with_reg_count()
|
||||
.annotate(
|
||||
real_required_helpers=Case(
|
||||
When(
|
||||
|
@ -46,7 +46,6 @@ def metrics(request):
|
|||
fallbackassignment_count=Count("fallbackassignment"),
|
||||
)
|
||||
.filter(
|
||||
deleted=False,
|
||||
reg_count__gte=F("real_required_helpers"),
|
||||
fallbackassignment_count__gt=0,
|
||||
)
|
||||
|
@ -96,16 +95,14 @@ def metrics(request):
|
|||
),
|
||||
(
|
||||
"helpers_required",
|
||||
Shift.objects.filter(deleted=False)
|
||||
.annotate(
|
||||
Shift.objects.annotate(
|
||||
real_required_helpers=Case(
|
||||
When(
|
||||
required_helpers=0, then=F("room__required_helpers")
|
||||
),
|
||||
default=F("required_helpers"),
|
||||
)
|
||||
)
|
||||
.aggregate(sum=Sum("real_required_helpers"))["sum"]
|
||||
).aggregate(sum=Sum("real_required_helpers"))["sum"]
|
||||
or 0,
|
||||
),
|
||||
(
|
||||
|
@ -124,15 +121,15 @@ def metrics(request):
|
|||
*(
|
||||
(
|
||||
f'shifts{{room="{room.name}"}}',
|
||||
Shift.objects.filter(deleted=False, room=room).count(),
|
||||
Shift.objects.filter(room=room).count(),
|
||||
)
|
||||
for room in Room.objects.all()
|
||||
),
|
||||
*(
|
||||
(
|
||||
f'shifts_occupied{{room="{room.name}"}}',
|
||||
Shift.with_reg_count()
|
||||
.filter(deleted=False, reg_count__gte=1, room=room)
|
||||
Shift.objects.with_reg_count()
|
||||
.filter(reg_count__gte=1, room=room)
|
||||
.count(),
|
||||
)
|
||||
for room in Room.objects.all()
|
||||
|
@ -140,7 +137,7 @@ def metrics(request):
|
|||
*(
|
||||
(
|
||||
f'shifts_full{{room="{room.name}"}}',
|
||||
Shift.with_reg_count()
|
||||
Shift.objects.with_reg_count()
|
||||
.annotate(
|
||||
real_required_helpers=Case(
|
||||
When(
|
||||
|
@ -151,7 +148,7 @@ def metrics(request):
|
|||
)
|
||||
)
|
||||
.filter(
|
||||
deleted=False,
|
||||
reg_count__gt=0,
|
||||
reg_count__gte=F("real_required_helpers"),
|
||||
room=room,
|
||||
)
|
||||
|
|
|
@ -33,16 +33,16 @@ def public_dashboard(request):
|
|||
)
|
||||
|
||||
num_free_shifts = (
|
||||
Shift.with_reg_count()
|
||||
.filter(help_wanted, deleted=False, start_at__gte=timezone.now())
|
||||
Shift.objects.with_reg_count()
|
||||
.filter(help_wanted, start_at__gte=timezone.now())
|
||||
.count()
|
||||
)
|
||||
if num_free_shifts > 0:
|
||||
facts.append(("Zu übernehmende Schichten", num_free_shifts))
|
||||
|
||||
next_free_shifts = (
|
||||
Shift.with_reg_count()
|
||||
.filter(help_wanted, start_at__gt=timezone.now(), deleted=False)
|
||||
Shift.objects.with_reg_count()
|
||||
.filter(help_wanted, start_at__gt=timezone.now())
|
||||
.order_by("start_at")[:4]
|
||||
)
|
||||
|
||||
|
@ -79,7 +79,7 @@ def team_dashboard(request):
|
|||
day = today
|
||||
|
||||
team_shifts = (
|
||||
Shift.with_reg_count()
|
||||
Shift.all_objects.with_reg_count()
|
||||
.annotate(
|
||||
end_at=ExpressionWrapper(
|
||||
F("start_at") + F("duration"), output_field=models.DateTimeField()
|
||||
|
|
|
@ -44,7 +44,7 @@ def shift_overview(request):
|
|||
|
||||
context = {}
|
||||
context["running_shifts"] = (
|
||||
Shift.with_reg_count()
|
||||
Shift.all_objects.with_reg_count()
|
||||
.prefetch_related("event__calendar")
|
||||
.annotate(
|
||||
checkin_count=Count(
|
||||
|
@ -65,7 +65,7 @@ def shift_overview(request):
|
|||
)
|
||||
|
||||
context["next_shifts"] = (
|
||||
Shift.with_reg_count()
|
||||
Shift.all_objects.with_reg_count()
|
||||
.prefetch_related("event__calendar")
|
||||
.annotate(checkin_count=checkin_count)
|
||||
.filter(
|
||||
|
@ -80,7 +80,7 @@ def shift_overview(request):
|
|||
context["next_shifts_per_room"] = filter(
|
||||
lambda x: x is not None,
|
||||
(
|
||||
Shift.with_reg_count()
|
||||
Shift.all_objects.with_reg_count()
|
||||
.prefetch_related("event__calendar")
|
||||
.filter(room=room, start_at__gt=timezone.now(), deleted=False)
|
||||
.order_by("start_at")
|
||||
|
@ -99,7 +99,10 @@ def add_helper_shift(self):
|
|||
@login_required
|
||||
def shift_detail(request, pk):
|
||||
shift = get_object_or_404(
|
||||
Shift.with_reg_count().prefetch_related("shiftregistration_set__helper"), pk=pk
|
||||
Shift.all_objects.with_reg_count().prefetch_related(
|
||||
"shiftregistration_set__helper"
|
||||
),
|
||||
pk=pk,
|
||||
)
|
||||
form = HelperShift()
|
||||
if request.method == "POST":
|
||||
|
@ -217,7 +220,7 @@ class ShiftList(LoginRequiredMixin, ListView):
|
|||
title = "Alle Schichten"
|
||||
|
||||
def get_queryset(self):
|
||||
return Shift.with_reg_count()
|
||||
return Shift.all_objects.with_reg_count()
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
@ -236,7 +239,7 @@ class FreeShiftList(ShiftList):
|
|||
required_helpers=0
|
||||
) & Q(room__required_helpers__gt=F("reg_count"))
|
||||
return (
|
||||
Shift.with_reg_count()
|
||||
Shift.all_objects.with_reg_count()
|
||||
.annotate(
|
||||
end_at=ExpressionWrapper(
|
||||
F("start_at") + F("duration"),
|
||||
|
@ -265,7 +268,7 @@ class RoomShiftList(ShiftList):
|
|||
required_helpers=0
|
||||
) & Q(room__required_helpers__gt=F("reg_count"))
|
||||
return (
|
||||
Shift.with_reg_count()
|
||||
Shift.all_objects.with_reg_count()
|
||||
.filter(
|
||||
deleted=False,
|
||||
room=room,
|
||||
|
|
Loading…
Reference in New Issue