Compare commits

...

2 Commits

Author SHA1 Message Date
xAndy f55b653ccd first shift trade draft. without pin for now
continuous-integration/drone/push Build is passing Details
2025-05-14 13:49:07 +02:00
xAndy fd86c2fcc0 isort 2025-05-14 13:38:41 +02:00
9 changed files with 128 additions and 22 deletions

View File

@ -1,5 +1,6 @@
from django.test import TestCase
from datetime import timedelta from datetime import timedelta
from django.test import TestCase
from django.utils import timezone from django.utils import timezone
from .models import Helper, Room, Shift, ShiftRegistration from .models import Helper, Room, Shift, ShiftRegistration

View File

@ -25,6 +25,7 @@ class FallbackAssignmentInline(admin.TabularInline):
model = FallbackAssignment model = FallbackAssignment
ordering = ("shift__start_at",) ordering = ("shift__start_at",)
readonly_fields = ("shift",) readonly_fields = ("shift",)
fk_name = "team_member"
@admin.register(TeamMember) @admin.register(TeamMember)

View File

@ -0,0 +1,25 @@
# Generated by Django 5.0.4 on 2025-05-14 11:39
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("fallback", "0008_alter_teammember_comment"),
]
operations = [
migrations.AddField(
model_name="fallbackassignment",
name="traded_to",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="received_trades",
to="fallback.teammember",
),
),
]

View File

@ -21,18 +21,19 @@ class TeamMember(models.Model):
id = models.IntegerField(default=generate_id, editable=False, primary_key=True) id = models.IntegerField(default=generate_id, editable=False, primary_key=True)
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
comment = models.CharField(max_length=100, blank=True, default="") comment = models.CharField(max_length=100, blank=True, default="")
fallback_shifts = models.ManyToManyField(Shift, through="FallbackAssignment") fallback_shifts = models.ManyToManyField(
Shift, through="FallbackAssignment", through_fields=("team_member", "shift")
)
def url(self): def url(self):
return "https://helfen.kntkt.de" + reverse( return "https://helfen.kntkt.de" + reverse(
"my_fallback_shifts", "my_fallback_shifts",
kwargs={ kwargs={"team_member_id": self.url_id()},
"team_member_id": urlsafe_b64encode(
self.id.to_bytes(3, byteorder="big")
).decode("utf-8")
},
) )
def url_id(self):
return urlsafe_b64encode(self.id.to_bytes(3, byteorder="big")).decode("utf-8")
def assign_random_shifts(self): def assign_random_shifts(self):
needs_fallback = Q(deleted=False, calendar__needs_fallback=True) needs_fallback = Q(deleted=False, calendar__needs_fallback=True)
@ -199,6 +200,15 @@ class FallbackAssignment(models.Model):
shift = models.ForeignKey(Shift, on_delete=models.CASCADE) shift = models.ForeignKey(Shift, on_delete=models.CASCADE)
team_member = models.ForeignKey(TeamMember, on_delete=models.CASCADE) team_member = models.ForeignKey(TeamMember, on_delete=models.CASCADE)
was_full = models.BooleanField(default=False) was_full = models.BooleanField(default=False)
traded_to = models.ForeignKey(
TeamMember,
on_delete=models.CASCADE,
null=True,
blank=True,
related_name="received_trades",
)
def __str__(self): def __str__(self):
if self.traded_to:
return f"{self.shift} {self.team_member.name} -> {self.traded_to.name}"
return f"{self.shift} {self.team_member.name}" return f"{self.shift} {self.team_member.name}"

View File

@ -9,6 +9,22 @@
<div class="content"> <div class="content">
<a href="{% url 'pages:view' 'team_faq' %}">Häufig gestellte Fragen zu Teamschichten</a> <a href="{% url 'pages:view' 'team_faq' %}">Häufig gestellte Fragen zu Teamschichten</a>
</div> </div>
<div class="box">
<h4 class="subtitle">Schicht übernehmen</h4>
<form method="POST">
{% csrf_token %}
<div class="field has-addons">
<div class="control">
<input class="input" type="number" name="assignment_id" placeholder="Assignment ID" required>
</div>
<div class="control">
<button type="submit" name="take_shift" class="button is-info">Übernehmen/Entfernen</button>
</div>
</div>
</form>
</div>
{% if assignments %} {% if assignments %}
{% if is_draw %} {% if is_draw %}
<pre class="mb-5 select_all">Hallo {{ team_member.name }}, hier deine Teamschichten für das Festival: <pre class="mb-5 select_all">Hallo {{ team_member.name }}, hier deine Teamschichten für das Festival:
@ -26,6 +42,7 @@ Diese Schichtzuteilung wurde maschinell erstellt und ist auch ohne Unterschrift
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th>Tausch-ID</th>
<th>Wann</th> <th>Wann</th>
<th>Wie lange</th> <th>Wie lange</th>
<th>Wo</th> <th>Wo</th>
@ -38,13 +55,19 @@ Diese Schichtzuteilung wurde maschinell erstellt und ist auch ohne Unterschrift
{% for assignment in assignments %} {% for assignment in assignments %}
{% with assignment.shift as shift %} {% with assignment.shift as shift %}
<tr{% if shift.registration_count == shift.required_helpers|default:shift.room.required_helpers or assignment.was_full %} class="has-text-grey" style="text-decoration: line-through;"{% endif %}> <tr{% if shift.registration_count == shift.required_helpers|default:shift.room.required_helpers or assignment.was_full %} class="has-text-grey" style="text-decoration: line-through;"{% endif %}>
<td>{{ assignment.id }} {% if assignment.traded_to %}*{% endif %}</td>
<td>{{ shift.start_at }}</td> <td>{{ shift.start_at }}</td>
<td>{{ shift.duration|duration }}</td> <td>{{ shift.duration|duration }}</td>
<td>{{ shift.room.name }} </td> <td>{{ shift.room.name }} </td>
<td>{{ shift.registration_count }}/{{ shift.required_helpers|default:shift.room.required_helpers }}</td> <td>{{ shift.registration_count }}/{{ shift.required_helpers|default:shift.room.required_helpers }}</td>
<td> <td>
{% for assignment in shift.fallbackassignment_set.all %} {% for fa in shift.fallbackassignment_set.all %}
{{ assignment.team_member.name }}{% if not forloop.last %}, {% endif %} {% if fa.traded_to %}
{{ fa.traded_to.name }}
{% else %}
{{ fa.team_member.name }}
{% endif %}
{% if not forloop.last %}, {% endif %}
{% endfor %} {% endfor %}
</td> </td>
<td> <td>
@ -61,7 +84,7 @@ Diese Schichtzuteilung wurde maschinell erstellt und ist auch ohne Unterschrift
{% if user.is_authenticated %} {% if user.is_authenticated %}
<form method="POST"> <form method="POST">
{% csrf_token %} {% csrf_token %}
<button class="button is-success" type="submit">Schichten zulosen</button> <button class="button is-success" type="submit" name="draw_shifts">Schichten zulosen</button>
</form> </form>
{% else %} {% else %}
Noch keine Schichten zugewiesen, bitte wende dich an den Infopoint. Noch keine Schichten zugewiesen, bitte wende dich an den Infopoint.

View File

@ -1,11 +1,13 @@
from base64 import urlsafe_b64decode from base64 import urlsafe_b64decode
from django.contrib import messages
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.db.models import Count from django.db.models import Count, Q
from django.http import HttpResponse from django.http import HttpResponse
from django.shortcuts import get_object_or_404, render from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from shiftregister.fallback.models import TeamMember from shiftregister.fallback.models import FallbackAssignment, TeamMember
# Create your views here. # Create your views here.
@ -22,15 +24,44 @@ def my_fallback_shifts(request, team_member_id):
is_draw = False is_draw = False
if request.method == "POST": if request.method == "POST":
team_member.assign_random_shifts() if "draw_shifts" in request.POST:
is_draw = True team_member.assign_random_shifts()
is_draw = True
elif "take_shift" in request.POST:
assignment_id = request.POST.get("assignment_id")
try:
assignment = FallbackAssignment.objects.get(pk=assignment_id)
if assignment.team_member == team_member:
assignment.traded_to = None
messages.success(request, f"Schicht erfolgreich zurückgenommen")
elif assignment.traded_to == team_member:
assignment.traded_to = None
messages.success(request, f"Schicht erfolgreich zurückgegeben")
else:
assignment.traded_to = team_member
messages.success(request, f"Schicht erfolgreich übernommen")
assignment.save()
return redirect(
reverse(
"my_fallback_shifts",
kwargs={"team_member_id": team_member.url_id()},
)
)
except FallbackAssignment.DoesNotExist:
messages.error(request, "Ungültige Schicht-ID")
assignments = (
FallbackAssignment.objects.filter(
Q(team_member=team_member, traded_to__isnull=True)
| Q(traded_to=team_member)
)
.prefetch_related("shift", "traded_to", "team_member")
.order_by("shift__start_at")
)
context = { context = {
"team_member": team_member, "team_member": team_member,
"assignments": team_member.fallbackassignment_set.order_by( "assignments": assignments,
"shift__start_at"
).all(),
# "shifts": team_member.fallback_shifts.order_by("start_at").all(),
"is_draw": is_draw, "is_draw": is_draw,
} }
return render(request, "my_fallback_shifts.html", context) return render(request, "my_fallback_shifts.html", context)

View File

@ -18,7 +18,18 @@
<tr> <tr>
<td>{{ shift.room.name }}</td> <td>{{ shift.room.name }}</td>
<td>{{ shift.start_at }}</td> <td>{{ shift.start_at }}</td>
<td>{% for fa in shift.fallbackassignment_set.all %}{% if not fa.was_full %}{{ fa.team_member.name }}{% if not forloop.last %}, {% endif %}{% endif %}{% endfor %}</td> <td>
{% for fa in shift.fallbackassignment_set.all %}
{% if not fa.was_full %}
{% if fa.traded_to %}
{{ fa.traded_to.name }} ({{ fa.id }})
{% else %}
{{ fa.team_member.name }} ({{ fa.id }})
{% endif %}
{% if not forloop.last %}, {% endif %}
{% endif %}
{% endfor %}
</td>
</tr> </tr>
{% empty %} {% empty %}
<tr> <tr>

View File

@ -59,7 +59,11 @@
{% for fallback in shift.event.fallbackassignment_set.all %} {% for fallback in shift.event.fallbackassignment_set.all %}
<div class="column is-one-quarter"> <div class="column is-one-quarter">
<div class="box{% if fallback.was_full %} has-text-grey" style="text-decoration: line-through;{% endif %}"> <div class="box{% if fallback.was_full %} has-text-grey" style="text-decoration: line-through;{% endif %}">
{{ fallback.team_member.name }} {% if fallback.traded_to %}
{{ fallback.traded_to.name }}
{% else %}
{{ fallback.team_member.name }}
{% endif %}
</div> </div>
</div> </div>
{% endfor %} {% endfor %}

View File

@ -14,9 +14,9 @@ Including another URLconf
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
from django.conf import settings
from django.contrib import admin from django.contrib import admin
from django.urls import include, path from django.urls import include, path
from django.conf import settings
urlpatterns = [ urlpatterns = [
path("", include("shiftregister.metrics.urls")), path("", include("shiftregister.metrics.urls")),