Skip to content

Commit

Permalink
add conflicting_shifts signup checker (#361)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeriox committed Mar 11, 2021
1 parent 40ebb0b commit 7d04d32
Show file tree
Hide file tree
Showing 9 changed files with 92 additions and 2 deletions.
2 changes: 1 addition & 1 deletion ephios/core/signup/disposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def post(self, request, *args, **kwargs):
start_index=form.cleaned_data["new_index"],
)
form = next(filter(lambda form: form.instance.id == instance.id, formset))
return self.render_to_response({"form": form})
return self.render_to_response({"form": form, "shift": shift})
raise Http404()


Expand Down
26 changes: 25 additions & 1 deletion ephios/core/signup/methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from django.contrib.auth import get_user_model
from django.core.exceptions import PermissionDenied, ValidationError
from django.db import transaction
from django.db.models import QuerySet
from django.db.models import Q, QuerySet
from django.shortcuts import redirect
from django.template import Context, Template
from django.template.defaultfilters import yesno
Expand Down Expand Up @@ -65,6 +65,10 @@ def participation_for(self, shift):
"""Return the participation object for a shift. Return None if it does not exist."""
raise NotImplementedError

def all_participations(self):
"""Return all participations for this participant"""
raise NotImplementedError

@functools.lru_cache
def collect_all_qualifications(self) -> set:
"""We collect using breadth first search with one query for every layer of inclusion."""
Expand Down Expand Up @@ -107,6 +111,9 @@ def participation_for(self, shift):
except LocalParticipation.DoesNotExist:
return None

def all_participations(self):
return LocalParticipation.objects.filter(user=self.user)

def reverse_signup_action(self, shift):
return reverse("core:signup_action", kwargs=dict(pk=shift.pk))

Expand Down Expand Up @@ -249,6 +256,22 @@ def check_participant_age(method, participant):
)


def get_conflicting_shifts(shift, participant):
return participant.all_participations().filter(
~Q(shift=shift)
& Q(state=AbstractParticipation.States.CONFIRMED)
& (
Q(shift__start_time__lte=shift.start_time, shift__end_time__gte=shift.start_time)
| Q(shift__start_time__gte=shift.start_time, shift__start_time__lt=shift.end_time)
)
)


def check_conflicting_shifts(method, participant):
if get_conflicting_shifts(method.shift, participant).exists():
return ParticipationError(_("You are already participating at another shift at this time."))


class BaseSignupMethod:
@property
def slug(self):
Expand Down Expand Up @@ -296,6 +319,7 @@ def signup_checkers(self):
check_participation_state_for_signup,
check_inside_signup_timeframe,
check_participant_age,
check_conflicting_shifts,
]

@property
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ <h6 class="mb-0">
<div class="col-10 pr-3">
{{ form.instance.participant.icon }}
{{ form.instance.participant }}
{% with form.instance.participant|conflicting_shifts:shift as conflicting_shifts %}
{% if conflicting_shifts %}
<span class="fa fa-exclamation-triangle text-danger"
data-toggle="tooltip"
title="{{ form.instance.participant }} already participates in {{ conflicting_shifts|join:", " }} at the same time."></span>
{% endif %}
{% endwith %}
{% for qualification in form.instance.participant.qualifications|get_relevant_qualifications %}
<span class="badge badge-secondary">{{ qualification }}</span>
{% endfor %}
Expand Down
6 changes: 6 additions & 0 deletions ephios/core/templatetags/user_extras.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from dynamic_preferences.registries import global_preferences_registry

from ephios.core.consequences import editable_consequences
from ephios.core.signup import get_conflicting_shifts

register = template.Library()

Expand Down Expand Up @@ -36,3 +37,8 @@ def get_relevant_qualifications(qualification_queryset):
category__in=global_preferences["general__relevant_qualification_categories"]
)
return qs.values_list("abbreviation", flat=True)


@register.filter(name="conflicting_shifts")
def participant_conflicting_shifts(participant, shift):
return get_conflicting_shifts(shift, participant).values_list("shift__event__title", flat=True)
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ <h6 class="mb-0">
{% endif %}
</div>
<div>
{% with form.instance.participant|conflicting_shifts:shift as conflicting_shifts %}
{% if conflicting_shifts %}
<span class="fa fa-exclamation-triangle text-danger"
data-toggle="tooltip"
title="{{ form.instance.participant }} already participates in {{ conflicting_shifts|join:", " }} at the same time."></span>
{% endif %}
{% endwith %}
{% for qualification in form.instance.participant.qualifications|get_relevant_qualifications %}
<span class="badge badge-secondary">{{ qualification }}</span>
{% endfor %}
Expand Down
3 changes: 3 additions & 0 deletions ephios/plugins/guests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ def participation_for(self, shift):
except GuestParticipation.DoesNotExist:
return None

def all_participations(self):
return GuestParticipation.objects.filter(guest_user=self.guest_user)

def reverse_signup_action(self, shift):
return reverse(
"guests:signup_action",
Expand Down
1 change: 1 addition & 0 deletions ephios/static/plugins/basesignup/js/disposition.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ $(document).ready(function () {
handleDispositionForm($newFormFragment, false, true);
$spinner.fadeOut("fast", function () {
$(this).replaceWith($newFormFragment);
handleForms($newFormFragment);
$newFormFragment.fadeIn("fast");
});

Expand Down
35 changes: 35 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@

from ephios.core.consequences import QualificationConsequenceHandler, WorkingHoursConsequenceHandler
from ephios.core.models import (
AbstractParticipation,
Event,
EventType,
LocalParticipation,
Qualification,
QualificationCategory,
QualificationGrant,
Expand Down Expand Up @@ -177,6 +179,39 @@ def event(groups, service_event_type, planner, tz):
return event


@pytest.fixture
def conflicting_event(event, service_event_type, volunteer, groups):
managers, planners, volunteers = groups
conflicting_event = Event.objects.create(
title="Conflicting event",
description="clashes",
location="Berlin",
type=service_event_type,
mail_updates=True,
active=True,
)

assign_perm("view_event", [volunteers, planners], conflicting_event)
assign_perm("change_event", planners, conflicting_event)

Shift.objects.create(
event=conflicting_event,
meeting_time=event.shifts.first().meeting_time,
start_time=event.shifts.first().start_time,
end_time=event.shifts.first().end_time,
signup_method_slug=RequestConfirmSignupMethod.slug,
signup_configuration={},
)

LocalParticipation.objects.create(
shift=event.shifts.first(),
user=volunteer,
state=AbstractParticipation.States.CONFIRMED,
)

return conflicting_event


@pytest.fixture
def event_to_next_day(groups, service_event_type, planner, tz):
managers, planners, volunteers = groups
Expand Down
7 changes: 7 additions & 0 deletions tests/core/test_signup_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,10 @@ def test_signup_stats_addition(django_app):
c = SignupStats(3, 2, None, 2)
assert a + b == SignupStats(9, 4, 6, None)
assert b + c == SignupStats(8, 4, 3, 7)


@pytest.mark.django_db
def test_signup_conflicting_shifts(django_app, volunteer, event, conflicting_event):
assert not conflicting_event.shifts.first().signup_method.can_sign_up(
volunteer.as_participant()
)

0 comments on commit 7d04d32

Please sign in to comment.