misc: notify bookings when event is marked as checked (#75896)
gitea/chrono/pipeline/head This commit looks good Details

This commit is contained in:
Frédéric Péters 2023-03-31 08:39:15 +02:00
parent 5265839e69
commit ad2b04f89f
6 changed files with 149 additions and 1 deletions

View File

@ -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:

View File

@ -3066,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,
}

View File

@ -2749,6 +2749,7 @@ class EventCheckedView(EventCheckMixin, ViewableAgendaMixin, View):
if not self.event.checked:
self.event.checked = True
self.event.save(update_fields=['checked'])
self.event.async_notify_checked()
return self.response(request)

View File

@ -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()

View File

@ -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',
[

View File

@ -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 = [