manager: allow check of subscription with no booking (#62235)
gitea/chrono/pipeline/head This commit looks good Details

This commit is contained in:
Lauréline Guérin 2023-03-13 15:50:29 +01:00
parent 45b72d53de
commit 6c134bfa3f
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
5 changed files with 360 additions and 49 deletions

View File

@ -74,15 +74,7 @@
{% endif %}
{% for result in results %}
<tr class="booking">
{% if result.kind == 'booking' %}
{% include "chrono/manager_event_check_booking_fragment.html" with booking=result %}
{% elif result.kind == 'subscription' %}
<td class="booking-username main-list">{{ result.get_user_block }}</td>
<td class="booking-status">({% trans "Not booked" %})</td>
{% if not event.checked or not agenda.disable_check_update %}
<td class="booking-actions"></td>
{% endif %}
{% endif %}
{% include "chrono/manager_event_check_booking_fragment.html" with booking=result %}
</tr>
{% endfor %}
</tbody>

View File

@ -1,8 +1,10 @@
{% load i18n %}
<td class="booking-username main-list">{{ booking.get_user_block }}{% if booking.places_count > 1 %} ({{ booking.places_count }} {% trans "places" %}){% endif %}</td>
<td class="booking-status {% if booking.cancellation_datetime is None and booking.user_was_present is None %}without-status{% endif %}">
{% if booking.cancellation_datetime is None %}
<td class="booking-status {% if booking.kind != "subscription" and booking.cancellation_datetime is None and booking.user_was_present is None %}without-status{% endif %}">
{% if booking.kind == "subscription" %}
({% trans "Not booked" %})
{% elif booking.cancellation_datetime is None %}
{{ booking.user_was_present|yesno:_('Present,Absent,-') }}
{% if booking.user_was_present is not None and booking.user_check_type_label %}
({{ booking.user_check_type_label }})
@ -12,36 +14,44 @@
{% endif %}
</td>
{% if not event.checked or not agenda.disable_check_update %}
<td class="booking-actions" data-booking-id="{{ booking.id }}">
<form method="post" action="{% url 'chrono-manager-booking-presence' pk=agenda.pk booking_pk=booking.pk %}" class="with-ajax presence">
{% csrf_token %}
<button class="submit-button"
{% if booking.user_was_present is True %}disabled{% endif %}
>{% trans "Presence" %}</button>
{% if booking.presence_form.check_type.field.choices.1 %}{{ booking.presence_form.check_type }}{% endif %}
<script>
$(function() {
$('td[data-booking-id="{{ booking.id }}"] form.presence select').on('change',
function() {
$(this).parents('form.presence').submit();
});
});
</script>
</form>
<td class="booking-actions" data-{{ booking.kind }}-id="{{ booking.id }}">
{% if booking.kind == "subscription" %}
<form method="post" action="{% url 'chrono-manager-subscription-presence' pk=agenda.pk subscription_pk=booking.pk event_pk=event.pk %}" class="with-ajax presence">
{% else %}
<form method="post" action="{% url 'chrono-manager-booking-presence' pk=agenda.pk booking_pk=booking.pk %}" class="with-ajax presence">
{% endif %}
{% csrf_token %}
<button class="submit-button"
{% if booking.user_was_present is True %}disabled{% endif %}
>{% trans "Presence" %}</button>
{% if booking.presence_form.check_type.field.choices.1 %}{{ booking.presence_form.check_type }}{% endif %}
<script>
$(function() {
$('td[data-{{ booking.kind }}-id="{{ booking.id }}"] form.presence select').on('change',
function() {
$(this).parents('form.presence').submit();
});
});
</script>
</form>
{% if booking.kind == "subscription" %}
<form method="post" action="{% url 'chrono-manager-subscription-absence' pk=agenda.pk subscription_pk=booking.pk event_pk=event.pk %}" class="with-ajax absence">
{% else %}
<form method="post" action="{% url 'chrono-manager-booking-absence' pk=agenda.pk booking_pk=booking.pk %}" class="with-ajax absence">
{% csrf_token %}
<button class="submit-button"
{% if booking.user_was_present is False %}disabled{% endif %}
>{% trans "Absence" %}</button>
{% if booking.absence_form.check_type.field.choices.1 %}{{ booking.absence_form.check_type }}{% endif %}
<script>
$(function() {
$('td[data-booking-id="{{ booking.id }}"] form.absence select').on('change',
function() {
$(this).parents('form.absence').submit();
});
{% endif %}
{% csrf_token %}
<button class="submit-button"
{% if booking.user_was_present is False %}disabled{% endif %}
>{% trans "Absence" %}</button>
{% if booking.absence_form.check_type.field.choices.1 %}{{ booking.absence_form.check_type }}{% endif %}
<script>
$(function() {
$('td[data-{{ booking.kind }}-id="{{ booking.id }}"] form.absence select').on('change',
function() {
$(this).parents('form.absence').submit();
});
</script>
</form>
});
</script>
</form>
</td>
{% endif %}

View File

@ -409,6 +409,16 @@ urlpatterns = [
views.booking_absence,
name='chrono-manager-booking-absence',
),
path(
'agendas/<int:pk>/subscriptions/<int:subscription_pk>/presence/<int:event_pk>',
views.subscription_presence,
name='chrono-manager-subscription-presence',
),
path(
'agendas/<int:pk>/subscriptions/<int:subscription_pk>/absence/<int:event_pk>',
views.subscription_absence,
name='chrono-manager-subscription-absence',
),
re_path(
r'^agendas/events.csv$',
views.agenda_import_events_sample_csv,

View File

@ -80,6 +80,7 @@ from chrono.agendas.models import (
SharedCustodyPeriod,
SharedCustodyRule,
SharedCustodySettings,
Subscription,
TimePeriod,
TimePeriodException,
TimePeriodExceptionSource,
@ -2623,6 +2624,12 @@ class EventCheckView(ViewableAgendaMixin, DetailView):
booking.kind = 'booking'
results.append(booking)
for subscription in subscription_filterset.qs:
subscription.absence_form = BookingCheckAbsenceForm(
agenda=self.agenda,
)
subscription.presence_form = BookingCheckPresenceForm(
agenda=self.agenda,
)
subscription.kind = 'subscription'
results.append(subscription)
# sort results
@ -3510,6 +3517,7 @@ class BookingCheckMixin:
booking.presence_form = BookingCheckPresenceForm(
agenda=self.agenda, initial={'check_type': booking.user_check_type_slug}
)
booking.kind = 'booking'
return render(
request,
'chrono/manager_event_check_booking_fragment.html',
@ -3523,9 +3531,7 @@ class BookingCheckMixin:
)
class BookingPresenceView(ViewableAgendaMixin, BookingCheckMixin, FormView):
form_class = BookingCheckPresenceForm
class PresenceViewMixin:
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['agenda'] = self.agenda
@ -3541,12 +3547,7 @@ class BookingPresenceView(ViewableAgendaMixin, BookingCheckMixin, FormView):
return self.response(request, booking)
booking_presence = BookingPresenceView.as_view()
class BookingAbsenceView(ViewableAgendaMixin, BookingCheckMixin, FormView):
form_class = BookingCheckAbsenceForm
class AbsenceViewMixin:
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['agenda'] = self.agenda
@ -3562,9 +3563,71 @@ class BookingAbsenceView(ViewableAgendaMixin, BookingCheckMixin, FormView):
return self.response(request, booking)
class BookingPresenceView(ViewableAgendaMixin, BookingCheckMixin, PresenceViewMixin, FormView):
form_class = BookingCheckPresenceForm
booking_presence = BookingPresenceView.as_view()
class BookingAbsenceView(ViewableAgendaMixin, BookingCheckMixin, AbsenceViewMixin, FormView):
form_class = BookingCheckAbsenceForm
booking_absence = BookingAbsenceView.as_view()
class SubscriptionCheckMixin(BookingCheckMixin):
def get_booking(self, **kwargs):
event = get_object_or_404(
Event,
Q(checked=False) | Q(agenda__disable_check_update=False),
agenda=self.agenda,
start_datetime__date__lte=now().date(),
cancelled=False,
pk=kwargs['event_pk'],
)
subscription = get_object_or_404(
Subscription,
agenda=self.agenda,
pk=kwargs['subscription_pk'],
date_start__lte=event.start_datetime,
date_end__gt=event.start_datetime,
)
try:
booking = event.booking_set.get(user_external_id=subscription.user_external_id)
raise Http404
except Booking.MultipleObjectsReturned:
raise Http404
except Booking.DoesNotExist:
pass
booking = event.booking_set.create(
user_external_id=subscription.user_external_id,
user_last_name=subscription.user_last_name,
user_first_name=subscription.user_first_name,
user_email=subscription.user_email,
user_phone_number=subscription.user_phone_number,
extra_data=subscription.extra_data,
)
# create booking
return booking
class SubscriptionPresenceView(ViewableAgendaMixin, SubscriptionCheckMixin, PresenceViewMixin, FormView):
form_class = BookingCheckPresenceForm
subscription_presence = SubscriptionPresenceView.as_view()
class SubscriptionAbsenceView(ViewableAgendaMixin, SubscriptionCheckMixin, AbsenceViewMixin, FormView):
form_class = BookingCheckAbsenceForm
subscription_absence = SubscriptionAbsenceView.as_view()
class EventCancelView(ViewableAgendaMixin, UpdateView):
template_name = 'chrono/manager_confirm_event_cancellation.html'
model = Event

View File

@ -2446,6 +2446,242 @@ def test_event_check_booking_ajax(check_types, app, admin_user):
assert '<option value="foo-reason" selected>Foo reason</option>' in resp
@mock.patch('chrono.manager.forms.get_agenda_check_types')
def test_event_check_subscription(check_types, app, admin_user):
check_types.return_value = [
CheckType(slug='foo-reason', label='Foo reason', kind='absence'),
CheckType(slug='bar-reason', label='Bar reason', kind='presence'),
]
agenda = Agenda.objects.create(label='Events', kind='events')
event = Event.objects.create(
label='xyz',
start_datetime=now() - datetime.timedelta(days=1),
places=10,
waiting_list_places=5,
agenda=agenda,
)
subscription = Subscription.objects.create(
agenda=agenda,
user_external_id='user:1',
user_first_name='Subscription',
user_last_name='42',
user_email='foo@bar.com',
user_phone_number='06',
extra_data={'foo': 'bar'},
date_start=now() - datetime.timedelta(days=1),
date_end=now(),
)
# existing booking: no check for subscription
booking = Booking.objects.create(
event=event, user_first_name='User', user_last_name='42', user_external_id='user:1'
)
booking2 = Booking.objects.create(
event=event, user_first_name='User', user_last_name='42', user_external_id='user:1'
)
login(app)
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
assert (
'/manage/agendas/%s/subscriptions/%s/presence/%s' % (agenda.pk, subscription.pk, event.pk) not in resp
)
assert (
'/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk) not in resp
)
token = resp.context['csrf_token']
app.post(
'/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk),
params={'csrfmiddlewaretoken': token},
status=404,
)
app.post(
'/manage/agendas/%s/subscriptions/%s/presence/%s' % (agenda.pk, subscription.pk, event.pk),
params={'csrfmiddlewaretoken': token},
status=404,
)
booking2.delete()
booking.cancel()
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
assert (
'/manage/agendas/%s/subscriptions/%s/presence/%s' % (agenda.pk, subscription.pk, event.pk) not in resp
)
assert (
'/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk) not in resp
)
app.post(
'/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk),
params={'csrfmiddlewaretoken': token},
status=404,
)
app.post(
'/manage/agendas/%s/subscriptions/%s/presence/%s' % (agenda.pk, subscription.pk, event.pk),
params={'csrfmiddlewaretoken': token},
status=404,
)
booking.delete()
# set as present
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
assert '/manage/agendas/%s/subscriptions/%s/presence/%s' % (agenda.pk, subscription.pk, event.pk) in resp
assert '/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk) in resp
app.post(
'/manage/agendas/%s/subscriptions/%s/presence/%s' % (agenda.pk, subscription.pk, event.pk),
params={'csrfmiddlewaretoken': token, 'check_type': 'bar-reason'},
)
booking = Booking.objects.latest('pk')
assert booking.user_was_present is True
assert booking.user_check_type_slug == 'bar-reason'
assert booking.user_check_type_label == 'Bar reason'
assert booking.event == event
assert booking.user_external_id == subscription.user_external_id
assert booking.user_first_name == subscription.user_first_name
assert booking.user_last_name == subscription.user_last_name
assert booking.user_email == subscription.user_email
assert booking.user_phone_number == subscription.user_phone_number
assert booking.extra_data == subscription.extra_data
booking.delete()
# set as absent
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
assert '/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk) in resp
assert '/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk) in resp
app.post(
'/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk),
params={'csrfmiddlewaretoken': token, 'check_type': 'foo-reason'},
)
booking = Booking.objects.latest('pk')
assert booking.user_was_present is False
assert booking.user_check_type_slug == 'foo-reason'
assert booking.user_check_type_label == 'Foo reason'
assert booking.event == event
assert booking.user_external_id == subscription.user_external_id
assert booking.user_first_name == subscription.user_first_name
assert booking.user_last_name == subscription.user_last_name
assert booking.user_email == subscription.user_email
assert booking.user_phone_number == subscription.user_phone_number
assert booking.extra_data == subscription.extra_data
booking.delete()
# mark the event as checked
assert agenda.disable_check_update is False
event.checked = True
event.save()
app.post(
'/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk),
params={'csrfmiddlewaretoken': token},
status=302,
)
Booking.objects.all().delete()
app.post(
'/manage/agendas/%s/subscriptions/%s/presence/%s' % (agenda.pk, subscription.pk, event.pk),
params={'csrfmiddlewaretoken': token},
status=302,
)
Booking.objects.all().delete()
agenda.disable_check_update = True
agenda.save()
app.post(
'/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk),
params={'csrfmiddlewaretoken': token},
status=404,
)
app.post(
'/manage/agendas/%s/subscriptions/%s/presence/%s' % (agenda.pk, subscription.pk, event.pk),
params={'csrfmiddlewaretoken': token},
status=404,
)
# check subscription dates
agenda.disable_check_update = False
agenda.save()
subscription.date_start = now() - datetime.timedelta(days=2)
subscription.date_end = now() - datetime.timedelta(days=1)
subscription.save()
app.post(
'/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk),
params={'csrfmiddlewaretoken': token},
status=404,
)
app.post(
'/manage/agendas/%s/subscriptions/%s/presence/%s' % (agenda.pk, subscription.pk, event.pk),
params={'csrfmiddlewaretoken': token},
status=404,
)
subscription.date_start = now()
subscription.date_end = now() + datetime.timedelta(days=1)
subscription.save()
app.post(
'/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk),
params={'csrfmiddlewaretoken': token},
status=404,
)
app.post(
'/manage/agendas/%s/subscriptions/%s/presence/%s' % (agenda.pk, subscription.pk, event.pk),
params={'csrfmiddlewaretoken': token},
status=404,
)
@mock.patch('chrono.manager.forms.get_agenda_check_types')
def test_event_check_subscription_ajax(check_types, app, admin_user):
check_types.return_value = [
CheckType(slug='foo-reason', label='Foo reason', kind='absence'),
CheckType(slug='bar-reason', label='Bar reason', kind='presence'),
]
agenda = Agenda.objects.create(label='Events', kind='events')
event = Event.objects.create(
label='xyz',
start_datetime=now() - datetime.timedelta(days=1),
places=10,
waiting_list_places=5,
agenda=agenda,
)
subscription = Subscription.objects.create(
agenda=agenda,
user_external_id='user:1',
user_first_name='Subscription',
user_last_name='42',
date_start=now() - datetime.timedelta(days=1),
date_end=now(),
)
login(app)
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
token = resp.context['csrf_token']
# set as present
resp = app.post(
'/manage/agendas/%s/subscriptions/%s/presence/%s' % (agenda.pk, subscription.pk, event.pk),
params={'csrfmiddlewaretoken': token, 'check_type': 'bar-reason'},
extra_environ={'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'},
)
assert '<tr>' not in resp # because this is a fragment
assert resp.pyquery.find('td.booking-status')[0].text.strip() == 'Present\n \n (Bar reason)'
assert len(resp.pyquery.find('td.booking-actions button[disabled]')) == 1
assert resp.pyquery.find('td.booking-actions button[disabled]')[0].text == 'Presence'
assert '<option value="bar-reason" selected>Bar reason</option>' in resp
Booking.objects.all().delete()
# set as absent
resp = app.post(
'/manage/agendas/%s/subscriptions/%s/absence/%s' % (agenda.pk, subscription.pk, event.pk),
params={'csrfmiddlewaretoken': token, 'check_type': 'foo-reason'},
extra_environ={'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'},
)
assert '<tr>' not in resp # because this is a fragment
assert resp.pyquery.find('td.booking-status')[0].text.strip() == 'Absent\n \n (Foo reason)'
assert len(resp.pyquery.find('td.booking-actions button[disabled]')) == 1
assert resp.pyquery.find('td.booking-actions button[disabled]')[0].text.startswith('Absence')
assert '<option value="foo-reason" selected>Foo reason</option>' in resp
@mock.patch('chrono.manager.forms.get_agenda_check_types')
def test_event_check_all_bookings(check_types, app, admin_user):
check_types.return_value = [