callback sur les réservations lorsqu'un événement est marqué pointé (#75896) #63
|
@ -0,0 +1,22 @@
|
|||
# Generated by Django 3.2.16 on 2023-03-31 06:34
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('agendas', '0151_event_invoiced'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='booking',
|
||||
name='absence_callback_url',
|
||||
field=models.URLField(blank=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='booking',
|
||||
name='presence_callback_url',
|
||||
field=models.URLField(blank=True),
|
||||
),
|
||||
]
|
|
@ -20,6 +20,7 @@ import dataclasses
|
|||
import datetime
|
||||
import functools
|
||||
import itertools
|
||||
import logging
|
||||
import math
|
||||
import sys
|
||||
import uuid
|
||||
|
@ -1595,6 +1596,45 @@ class Event(models.Model):
|
|||
return
|
||||
self.checked = True
|
||||
self.save(update_fields=['checked'])
|
||||
self.async_notify_checked()
|
||||
|
||||
def async_notify_checked(self):
|
||||
if 'uwsgi' in sys.modules:
|
||||
from chrono.utils.spooler import event_notify_checked
|
||||
|
||||
tenant = getattr(connection, 'tenant', None)
|
||||
transaction.on_commit(
|
||||
lambda: event_notify_checked.spool(
|
||||
event_id=str(self.pk), domain=getattr(tenant, 'domain_url', None)
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
self.notify_checked()
|
||||
|
||||
def notify_checked(self):
|
||||
for booking in self.booking_set.filter(user_was_present__isnull=False):
|
||||
if booking.user_was_present is True and booking.presence_callback_url:
|
||||
url = booking.presence_callback_url
|
||||
elif booking.user_was_present is False and booking.absence_callback_url:
|
||||
url = booking.absence_callback_url
|
||||
else:
|
||||
continue
|
||||
payload = {
|
||||
'user_was_present': booking.user_was_present,
|
||||
'user_check_type_slug': booking.user_check_type_slug,
|
||||
'user_check_type_label': booking.user_check_type_label,
|
||||
}
|
||||
try:
|
||||
response = requests_wrapper.post(url, json=payload, remote_service='auto', timeout=15)
|
||||
if response and not response.ok:
|
||||
logging.error(
|
||||
'error (HTTP %s) notifying checked booking (%s)', response.status_code, booking.id
|
||||
)
|
||||
except requests.Timeout:
|
||||
logging.error('error (timeout) notifying checked booking (%s)', booking.id)
|
||||
except Exception as e: # noqa pylint: disable=broad-except
|
||||
logging.error('error (%s) notifying checked booking (%s)', e, booking.id)
|
||||
|
||||
def in_bookable_period(self, bypass_delays=False):
|
||||
if self.publication_datetime and now() < self.publication_datetime:
|
||||
|
@ -2163,6 +2203,8 @@ class Booking(models.Model):
|
|||
form_url = models.URLField(blank=True)
|
||||
backoffice_url = models.URLField(blank=True)
|
||||
cancel_callback_url = models.URLField(blank=True)
|
||||
presence_callback_url = models.URLField(blank=True)
|
||||
absence_callback_url = models.URLField(blank=True)
|
||||
color = models.ForeignKey(BookingColor, null=True, on_delete=models.SET_NULL, related_name='bookings')
|
||||
|
||||
@property
|
||||
|
|
|
@ -82,6 +82,8 @@ class FillSlotSerializer(serializers.Serializer):
|
|||
form_url = serializers.CharField(max_length=250, allow_blank=True)
|
||||
backoffice_url = serializers.URLField(allow_blank=True)
|
||||
cancel_callback_url = serializers.URLField(allow_blank=True)
|
||||
presence_callback_url = serializers.URLField(allow_blank=True)
|
||||
absence_callback_url = serializers.URLField(allow_blank=True)
|
||||
count = serializers.IntegerField(min_value=1)
|
||||
cancel_booking_id = serializers.CharField(max_length=250, allow_blank=True, allow_null=True)
|
||||
force_waiting_list = serializers.BooleanField(default=False)
|
||||
|
|
|
@ -727,6 +727,8 @@ def make_booking(event, payload, extra_data, primary_booking=None, in_waiting_li
|
|||
form_url=translate_to_publik_url(payload.get('form_url', '')),
|
||||
backoffice_url=translate_to_publik_url(payload.get('backoffice_url', '')),
|
||||
cancel_callback_url=translate_to_publik_url(payload.get('cancel_callback_url', '')),
|
||||
presence_callback_url=translate_to_publik_url(payload.get('presence_callback_url', '')),
|
||||
absence_callback_url=translate_to_publik_url(payload.get('absence_callback_url', '')),
|
||||
user_display_label=payload.get('user_display_label', ''),
|
||||
extra_emails=payload.get('extra_emails', []),
|
||||
extra_phone_numbers=payload.get('extra_phone_numbers', []),
|
||||
|
@ -3064,6 +3066,7 @@ class EventCheck(APIView):
|
|||
if not event.checked:
|
||||
event.checked = True
|
||||
event.save(update_fields=['checked'])
|
||||
event.async_notify_checked()
|
||||
response = {
|
||||
'err': 0,
|
||||
}
|
||||
|
|
|
@ -2749,6 +2749,7 @@ class EventCheckedView(EventCheckMixin, ViewableAgendaMixin, View):
|
|||
if not self.event.checked:
|
||||
self.event.checked = True
|
||||
self.event.save(update_fields=['checked'])
|
||||
lguerin marked this conversation as resolved
Outdated
|
||||
self.event.async_notify_checked()
|
||||
return self.response(request)
|
||||
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
from django.db import connection
|
||||
from uwsgidecorators import spool # pylint: disable=import-error
|
||||
|
||||
from chrono.agendas.models import ICSError, TimePeriodExceptionSource
|
||||
from chrono.agendas.models import Event, ICSError, TimePeriodExceptionSource
|
||||
|
||||
|
||||
def set_connection(domain):
|
||||
|
@ -55,3 +55,17 @@ def refresh_exceptions_from_settings(args):
|
|||
return
|
||||
|
||||
source.refresh_from_settings()
|
||||
|
||||
|
||||
@spool
|
||||
def event_notify_checked(args):
|
||||
if args.get('domain'):
|
||||
# multitenant installation
|
||||
set_connection(args['domain'])
|
||||
|
||||
try:
|
||||
event = Event.objects.get(pk=args['event_id'])
|
||||
except Event.DoesNotExist:
|
||||
return
|
||||
|
||||
event.notify_checked()
|
||||
|
|
|
@ -80,6 +80,8 @@ def test_booking_api(app, user):
|
|||
'form_url': 'http://example.net/',
|
||||
'extra_emails': ['baz@baz.com', 'hop@hop.com'],
|
||||
'extra_phone_numbers': ['+33123456789', '+33123456789'],
|
||||
'presence_callback_url': 'http://example.net/jump/trigger2/',
|
||||
'absence_callback_url': 'http://example.net/jump/trigger3/',
|
||||
},
|
||||
)
|
||||
booking = Booking.objects.get(id=resp.json['booking_id'])
|
||||
|
@ -88,6 +90,8 @@ def test_booking_api(app, user):
|
|||
assert booking.user_last_name == 'bar'
|
||||
assert booking.backoffice_url == 'http://example.net/'
|
||||
assert booking.cancel_callback_url == 'http://example.net/jump/trigger/'
|
||||
assert booking.presence_callback_url == 'http://example.net/jump/trigger2/'
|
||||
assert booking.absence_callback_url == 'http://example.net/jump/trigger3/'
|
||||
assert booking.user_email == 'bar@bar.com'
|
||||
assert booking.user_phone_number == '+33 (0) 6 12 34 56 78'
|
||||
assert booking.form_url == 'http://example.net/'
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import datetime
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
from django.db import connection
|
||||
|
@ -189,6 +190,49 @@ def test_event_checked(app, user):
|
|||
app.post('/api/agenda/%s/check/%s/' % (agenda.slug, event.slug), status=404)
|
||||
|
||||
|
||||
def test_event_notify_checked(app, user):
|
||||
agenda = Agenda.objects.create(label='Events', kind='events')
|
||||
event = Event.objects.create(
|
||||
label='xyz',
|
||||
start_datetime=now() - datetime.timedelta(days=1),
|
||||
places=10,
|
||||
agenda=agenda,
|
||||
)
|
||||
assert event.checked is False
|
||||
|
||||
for i in range(8):
|
||||
user_was_present = None
|
||||
if i < 3:
|
||||
user_was_present = True
|
||||
elif i < 7:
|
||||
user_was_present = False
|
||||
Booking.objects.create(
|
||||
event=event,
|
||||
user_was_present=user_was_present,
|
||||
presence_callback_url='https://example.invalid/presence/%s' % i,
|
||||
absence_callback_url='https://example.invalid/absence/%s' % i,
|
||||
)
|
||||
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
|
||||
with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send:
|
||||
mock_response = mock.Mock(status_code=200)
|
||||
mock_send.return_value = mock_response
|
||||
app.post('/api/agenda/%s/check/%s/' % (agenda.slug, event.slug))
|
||||
event.refresh_from_db()
|
||||
assert event.checked is True
|
||||
|
||||
assert {x[0][0].url for x in mock_send.call_args_list} == {
|
||||
'https://example.invalid/presence/0',
|
||||
'https://example.invalid/presence/1',
|
||||
'https://example.invalid/presence/2',
|
||||
'https://example.invalid/absence/3',
|
||||
'https://example.invalid/absence/4',
|
||||
'https://example.invalid/absence/5',
|
||||
'https://example.invalid/absence/6',
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'days_in, days_out, err_msg',
|
||||
[
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import codecs
|
||||
import datetime
|
||||
import json
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
@ -1798,6 +1799,53 @@ def test_event_checked(app, admin_user):
|
|||
assert '<span class="invoiced tag">Invoiced</span>' in resp
|
||||
|
||||
|
||||
def test_event_notify_checked(app, admin_user):
|
||||
agenda = Agenda.objects.create(label='Events', kind='events', booking_check_filters='foo,bar')
|
||||
event = Event.objects.create(
|
||||
label='xyz',
|
||||
start_datetime=now() - datetime.timedelta(days=1),
|
||||
places=10,
|
||||
agenda=agenda,
|
||||
)
|
||||
login(app)
|
||||
|
||||
for i in range(8):
|
||||
user_was_present = None
|
||||
if i < 3:
|
||||
user_was_present = True
|
||||
elif i < 7:
|
||||
user_was_present = False
|
||||
Booking.objects.create(
|
||||
event=event,
|
||||
user_was_present=user_was_present,
|
||||
presence_callback_url='https://example.invalid/presence/%s' % i,
|
||||
absence_callback_url='https://example.invalid/absence/%s' % i,
|
||||
)
|
||||
resp = app.get('/manage/agendas/%s/events/%s/check' % (agenda.pk, event.pk))
|
||||
|
||||
with mock.patch('chrono.utils.requests_wrapper.RequestsSession.send') as mock_send:
|
||||
mock_response = mock.Mock(status_code=200)
|
||||
mock_send.return_value = mock_response
|
||||
resp = app.post(
|
||||
'/manage/agendas/%s/events/%s/checked' % (agenda.pk, event.pk),
|
||||
params={'csrfmiddlewaretoken': resp.context['csrf_token']},
|
||||
)
|
||||
assert {x[0][0].url for x in mock_send.call_args_list} == {
|
||||
'https://example.invalid/presence/0',
|
||||
'https://example.invalid/presence/1',
|
||||
'https://example.invalid/presence/2',
|
||||
'https://example.invalid/absence/3',
|
||||
'https://example.invalid/absence/4',
|
||||
'https://example.invalid/absence/5',
|
||||
'https://example.invalid/absence/6',
|
||||
}
|
||||
assert set(json.loads(mock_send.call_args_list[0][0][0].body).keys()) == {
|
||||
'user_check_type_label',
|
||||
'user_check_type_slug',
|
||||
'user_was_present',
|
||||
}
|
||||
|
||||
|
||||
@mock.patch('chrono.manager.forms.get_agenda_check_types')
|
||||
def test_event_check_filters(check_types, app, admin_user):
|
||||
check_types.return_value = [
|
||||
|
|
Loading…
Reference in New Issue
on a aussi un endpoint pour marquer un event comme pointé, EventCheck
J'étais passé à côté, voilà il est traité, avec un test dédié.
(et c'est rebasé pour la migration)