api: set presence reason on bookings (#63810)
This commit is contained in:
parent
30afa66e3f
commit
3a1655adff
|
@ -139,39 +139,70 @@ class RecurringFillslotsSerializer(MultipleAgendasEventsSlotsSerializer):
|
|||
|
||||
class BookingSerializer(serializers.ModelSerializer):
|
||||
user_absence_reason = serializers.CharField(required=False, allow_blank=True, allow_null=True)
|
||||
user_presence_reason = serializers.CharField(required=False, allow_blank=True, allow_null=True)
|
||||
|
||||
class Meta:
|
||||
model = Booking
|
||||
fields = ['id', 'in_waiting_list', 'user_was_present', 'user_absence_reason', 'extra_data']
|
||||
fields = [
|
||||
'id',
|
||||
'in_waiting_list',
|
||||
'user_was_present',
|
||||
'user_absence_reason',
|
||||
'user_presence_reason',
|
||||
'extra_data',
|
||||
]
|
||||
read_only_fields = ['id', 'in_waiting_list', 'extra_data']
|
||||
|
||||
def to_representation(self, instance):
|
||||
ret = super().to_representation(instance)
|
||||
ret['user_absence_reason'] = self.instance.user_check_type
|
||||
ret['user_absence_reason'] = (
|
||||
self.instance.user_check_type if self.instance.user_was_present is False else ''
|
||||
)
|
||||
ret['user_presence_reason'] = (
|
||||
self.instance.user_check_type if self.instance.user_was_present is True else ''
|
||||
)
|
||||
return ret
|
||||
|
||||
def validate_user_absence_reason(self, value):
|
||||
def _validate_check_type(self, kind, value):
|
||||
if not value:
|
||||
return ''
|
||||
|
||||
if not self.instance.event.agenda.check_type_group:
|
||||
raise serializers.ValidationError(_('unknown absence reason'))
|
||||
error_messages = {
|
||||
'absence': _('unknown absence reason'),
|
||||
'presence': _('unknown presence reason'),
|
||||
}
|
||||
|
||||
check_types_qs = self.instance.event.agenda.check_type_group.check_types.absences()
|
||||
if not self.instance.event.agenda.check_type_group:
|
||||
raise serializers.ValidationError(error_messages[kind])
|
||||
|
||||
check_types_qs = self.instance.event.agenda.check_type_group.check_types.filter(kind=kind)
|
||||
try:
|
||||
check_type = check_types_qs.get(slug=value)
|
||||
value = check_type.label
|
||||
except CheckType.DoesNotExist:
|
||||
if not check_types_qs.filter(label=value).exists():
|
||||
raise serializers.ValidationError(_('unknown absence'))
|
||||
raise serializers.ValidationError(error_messages[kind])
|
||||
|
||||
return value
|
||||
|
||||
def validate_user_absence_reason(self, value):
|
||||
return self._validate_check_type('absence', value)
|
||||
|
||||
def validate_user_presence_reason(self, value):
|
||||
return self._validate_check_type('presence', value)
|
||||
|
||||
def validate(self, attrs):
|
||||
super().validate(attrs)
|
||||
if 'user_absence_reason' in attrs and 'user_presence_reason' in attrs:
|
||||
raise ValidationError(
|
||||
{'user_absence_reason': _('can not set user_absence_reason and user_presence_reason')}
|
||||
)
|
||||
if 'user_absence_reason' in attrs:
|
||||
attrs['user_check_type'] = attrs['user_absence_reason']
|
||||
del attrs['user_absence_reason']
|
||||
elif 'user_presence_reason' in attrs:
|
||||
attrs['user_check_type'] = attrs['user_presence_reason']
|
||||
del attrs['user_presence_reason']
|
||||
return attrs
|
||||
|
||||
|
||||
|
|
|
@ -385,6 +385,11 @@ def get_agenda_detail(request, agenda, check_events=False):
|
|||
for r in agenda.check_type_group.check_types.all()
|
||||
if r.kind == 'absence'
|
||||
]
|
||||
agenda_detail['presence_reasons'] = [
|
||||
{'id': r.slug, 'slug': r.slug, 'text': r.label, 'label': r.label}
|
||||
for r in agenda.check_type_group.check_types.all()
|
||||
if r.kind == 'presence'
|
||||
]
|
||||
elif agenda.accept_meetings():
|
||||
agenda_detail['api'] = {
|
||||
'meetings_url': request.build_absolute_uri(
|
||||
|
@ -2227,13 +2232,17 @@ class BookingFilter(filters.FilterSet):
|
|||
date_start = filters.DateFilter(field_name='event__start_datetime', lookup_expr='gte')
|
||||
date_end = filters.DateFilter(field_name='event__start_datetime', lookup_expr='lt')
|
||||
user_absence_reason = filters.CharFilter(method='filter_user_absence_reason')
|
||||
user_presence_reason = filters.CharFilter(method='filter_user_presence_reason')
|
||||
|
||||
def filter_event(self, queryset, name, value):
|
||||
# we want to include bookings of event recurrences
|
||||
return queryset.filter(Q(event__slug=value) | Q(event__primary_event__slug=value))
|
||||
|
||||
def filter_user_absence_reason(self, queryset, name, value):
|
||||
return queryset.filter(user_check_type=value)
|
||||
return queryset.filter(user_check_type=value, user_was_present=False)
|
||||
|
||||
def filter_user_presence_reason(self, queryset, name, value):
|
||||
return queryset.filter(user_check_type=value, user_was_present=True)
|
||||
|
||||
class Meta:
|
||||
model = Booking
|
||||
|
@ -2245,6 +2254,7 @@ class BookingFilter(filters.FilterSet):
|
|||
'date_end',
|
||||
'user_was_present',
|
||||
'user_absence_reason',
|
||||
'user_presence_reason',
|
||||
'in_waiting_list',
|
||||
]
|
||||
|
||||
|
@ -2325,13 +2335,19 @@ class BookingAPI(APIView):
|
|||
if (
|
||||
self.booking.event.checked
|
||||
and self.booking.event.agenda.disable_check_update
|
||||
and ('user_was_present' in request.data or 'user_absence_reason' in request.data)
|
||||
and (
|
||||
'user_was_present' in request.data
|
||||
or 'user_absence_reason' in request.data
|
||||
or 'user_presence_reason' in request.data
|
||||
)
|
||||
):
|
||||
raise APIErrorBadRequest(N_('event is marked as checked'), err=5)
|
||||
|
||||
user_was_present = serializer.validated_data.get('user_was_present', self.booking.user_was_present)
|
||||
if user_was_present is True and 'user_absence_reason' in request.data:
|
||||
raise APIErrorBadRequest(N_('user is marked as present, can not set absence reason'), err=6)
|
||||
if user_was_present is False and 'user_presence_reason' in request.data:
|
||||
raise APIErrorBadRequest(N_('user is marked as absent, can not set presence reason'), err=6)
|
||||
|
||||
serializer.save()
|
||||
extra_data = {k: v for k, v in request.data.items() if k not in serializer.validated_data}
|
||||
|
@ -2343,7 +2359,7 @@ class BookingAPI(APIView):
|
|||
if 'user_was_present' in request.data:
|
||||
self.booking.secondary_booking_set.update(user_was_present=self.booking.user_was_present)
|
||||
self.booking.event.set_is_checked()
|
||||
if 'user_absence_reason' in request.data:
|
||||
if 'user_absence_reason' in request.data or 'user_presence_reason' in request.data:
|
||||
self.booking.secondary_booking_set.update(user_check_type=self.booking.user_check_type)
|
||||
if extra_data:
|
||||
self.booking.secondary_booking_set.update(extra_data=self.booking.extra_data)
|
||||
|
|
|
@ -30,6 +30,7 @@ def test_agendas_api(app):
|
|||
group = CheckTypeGroup.objects.create(label='Foo')
|
||||
reason = CheckType.objects.create(group=group, label='Foo bar')
|
||||
reason2 = CheckType.objects.create(group=group, label='Foo bar baz')
|
||||
reason3 = CheckType.objects.create(group=group, label='Foo bar baz presence', kind='presence')
|
||||
events_type = EventsType.objects.create(label='Type A')
|
||||
events_type2 = EventsType.objects.create(label='Type B')
|
||||
event_agenda = Agenda.objects.create(
|
||||
|
@ -80,6 +81,9 @@ def test_agendas_api(app):
|
|||
{'id': reason.slug, 'slug': reason.slug, 'text': reason.label, 'label': reason.label},
|
||||
{'id': reason2.slug, 'slug': reason2.slug, 'text': reason2.label, 'label': reason2.label},
|
||||
],
|
||||
'presence_reasons': [
|
||||
{'id': reason3.slug, 'slug': reason3.slug, 'text': reason3.label, 'label': reason3.label},
|
||||
],
|
||||
'api': {
|
||||
'datetimes_url': 'http://testserver/api/agenda/foo-bar/datetimes/',
|
||||
'fillslots_url': 'http://testserver/api/agenda/foo-bar/fillslots/',
|
||||
|
@ -120,6 +124,9 @@ def test_agendas_api(app):
|
|||
{'id': reason.slug, 'slug': reason.slug, 'text': reason.label, 'label': reason.label},
|
||||
{'id': reason2.slug, 'slug': reason2.slug, 'text': reason2.label, 'label': reason2.label},
|
||||
],
|
||||
'presence_reasons': [
|
||||
{'id': reason3.slug, 'slug': reason3.slug, 'text': reason3.label, 'label': reason3.label},
|
||||
],
|
||||
'api': {
|
||||
'datetimes_url': 'http://testserver/api/agenda/foo-bar-3/datetimes/',
|
||||
'fillslots_url': 'http://testserver/api/agenda/foo-bar-3/fillslots/',
|
||||
|
|
|
@ -147,6 +147,7 @@ def test_bookings_api(app, user):
|
|||
'in_waiting_list': False,
|
||||
'user_was_present': None,
|
||||
'user_absence_reason': '',
|
||||
'user_presence_reason': '',
|
||||
'extra_data': None,
|
||||
},
|
||||
{
|
||||
|
@ -154,6 +155,7 @@ def test_bookings_api(app, user):
|
|||
'in_waiting_list': False,
|
||||
'user_was_present': None,
|
||||
'user_absence_reason': '',
|
||||
'user_presence_reason': '',
|
||||
'extra_data': None,
|
||||
'event': resp.json['data'][1]['event'],
|
||||
},
|
||||
|
@ -162,6 +164,7 @@ def test_bookings_api(app, user):
|
|||
'in_waiting_list': False,
|
||||
'user_was_present': None,
|
||||
'user_absence_reason': '',
|
||||
'user_presence_reason': '',
|
||||
'extra_data': None,
|
||||
'event': resp.json['data'][1]['event'],
|
||||
},
|
||||
|
@ -303,8 +306,10 @@ def test_bookings_api_filter_user_absence_reason(app, user):
|
|||
event = Event.objects.create(
|
||||
agenda=agenda, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 0, 0)), places=10
|
||||
)
|
||||
Booking.objects.create(event=event, user_external_id='42')
|
||||
booking2 = Booking.objects.create(event=event, user_external_id='42', user_check_type='foo-bar')
|
||||
Booking.objects.create(event=event, user_external_id='42', user_was_present=False)
|
||||
booking2 = Booking.objects.create(
|
||||
event=event, user_external_id='42', user_was_present=False, user_check_type='foo-bar'
|
||||
)
|
||||
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
resp = app.get('/api/bookings/', params={'user_external_id': '42', 'user_absence_reason': 'foo'})
|
||||
|
@ -313,6 +318,33 @@ def test_bookings_api_filter_user_absence_reason(app, user):
|
|||
resp = app.get('/api/bookings/', params={'user_external_id': '42', 'user_absence_reason': 'foo-bar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert [b['id'] for b in resp.json['data']] == [booking2.pk]
|
||||
Booking.objects.update(user_was_present=True)
|
||||
resp = app.get('/api/bookings/', params={'user_external_id': '42', 'user_absence_reason': 'foo-bar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert [b['id'] for b in resp.json['data']] == []
|
||||
|
||||
|
||||
def test_bookings_api_filter_user_presence_reason(app, user):
|
||||
agenda = Agenda.objects.create(label='Foo bar')
|
||||
event = Event.objects.create(
|
||||
agenda=agenda, start_datetime=make_aware(datetime.datetime(2017, 5, 22, 0, 0)), places=10
|
||||
)
|
||||
Booking.objects.create(event=event, user_external_id='42', user_was_present=True)
|
||||
booking2 = Booking.objects.create(
|
||||
event=event, user_external_id='42', user_was_present=True, user_check_type='foo-bar'
|
||||
)
|
||||
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
resp = app.get('/api/bookings/', params={'user_external_id': '42', 'user_presence_reason': 'foo'})
|
||||
assert resp.json['err'] == 0
|
||||
assert [b['id'] for b in resp.json['data']] == []
|
||||
resp = app.get('/api/bookings/', params={'user_external_id': '42', 'user_presence_reason': 'foo-bar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert [b['id'] for b in resp.json['data']] == [booking2.pk]
|
||||
Booking.objects.update(user_was_present=False)
|
||||
resp = app.get('/api/bookings/', params={'user_external_id': '42', 'user_presence_reason': 'foo-bar'})
|
||||
assert resp.json['err'] == 0
|
||||
assert [b['id'] for b in resp.json['data']] == []
|
||||
|
||||
|
||||
def test_bookings_api_filter_in_waiting_list(app, user):
|
||||
|
@ -391,7 +423,8 @@ def test_booking_api_present(app, user, flag):
|
|||
resp = app.get('/api/booking/%s/' % booking.pk)
|
||||
assert resp.json['booking_id'] == booking.pk
|
||||
assert resp.json['user_was_present'] == flag
|
||||
assert resp.json['user_absence_reason'] == 'foobar'
|
||||
assert resp.json['user_absence_reason'] == ('foobar' if flag is False else '')
|
||||
assert resp.json['user_presence_reason'] == ('foobar' if flag is True else '')
|
||||
|
||||
|
||||
@pytest.mark.parametrize('flag', [True, False])
|
||||
|
@ -627,6 +660,132 @@ def test_booking_patch_api_absence_reason(app, user):
|
|||
assert resp.json['err'] == 0
|
||||
|
||||
|
||||
def test_booking_patch_api_presence_reason(app, user):
|
||||
agenda = Agenda.objects.create(kind='events')
|
||||
event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10)
|
||||
booking = Booking.objects.create(event=event, user_was_present=True)
|
||||
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
|
||||
# reasons not defined on agenda
|
||||
resp = app.patch_json(
|
||||
'/api/booking/%s/' % booking.pk, params={'user_presence_reason': 'foobar'}, status=400
|
||||
)
|
||||
assert resp.json['err'] == 4
|
||||
assert resp.json['err_desc'] == 'invalid payload'
|
||||
|
||||
group = CheckTypeGroup.objects.create(label='Foo')
|
||||
check_type_presence = CheckType.objects.create(group=group, label='Foo bar', kind='presence')
|
||||
check_type_absence = CheckType.objects.create(group=group, label='Foo baz', kind='absence')
|
||||
|
||||
resp = app.patch_json(
|
||||
'/api/booking/%s/' % booking.pk, params={'user_presence_reason': 'foobar'}, status=400
|
||||
)
|
||||
assert resp.json['err'] == 4
|
||||
assert resp.json['err_desc'] == 'invalid payload'
|
||||
|
||||
# wrong kind
|
||||
resp = app.patch_json(
|
||||
'/api/booking/%s/' % booking.pk, params={'user_presence_reason': 'Foo baz'}, status=400
|
||||
)
|
||||
assert resp.json['err'] == 4
|
||||
assert resp.json['err_desc'] == 'invalid payload'
|
||||
resp = app.patch_json(
|
||||
'/api/booking/%s/' % booking.pk, params={'user_presence_reason': check_type_absence.slug}, status=400
|
||||
)
|
||||
assert resp.json['err'] == 4
|
||||
assert resp.json['err_desc'] == 'invalid payload'
|
||||
|
||||
# set check_type
|
||||
agenda.check_type_group = group
|
||||
agenda.save()
|
||||
# it works with label
|
||||
app.patch_json('/api/booking/%s/' % booking.pk, params={'user_presence_reason': 'Foo bar'})
|
||||
booking.refresh_from_db()
|
||||
assert booking.user_check_type == 'Foo bar'
|
||||
|
||||
# reset
|
||||
app.patch_json('/api/booking/%s/' % booking.pk, params={'user_presence_reason': ''})
|
||||
booking.refresh_from_db()
|
||||
assert booking.user_check_type == ''
|
||||
app.patch_json('/api/booking/%s/' % booking.pk, params={'user_presence_reason': None})
|
||||
booking.refresh_from_db()
|
||||
assert booking.user_check_type == ''
|
||||
|
||||
# make secondary bookings
|
||||
Booking.objects.create(event=event, primary_booking=booking, user_was_present=True)
|
||||
Booking.objects.create(event=event, primary_booking=booking, user_was_present=True)
|
||||
# and other booking
|
||||
other_booking = Booking.objects.create(event=event)
|
||||
|
||||
# it works also with slug
|
||||
app.patch_json('/api/booking/%s/' % booking.pk, params={'user_presence_reason': check_type_presence.slug})
|
||||
booking.refresh_from_db()
|
||||
assert booking.user_check_type == 'Foo bar'
|
||||
# all secondary bookings are updated
|
||||
assert list(booking.secondary_booking_set.values_list('user_check_type', flat=True)) == [
|
||||
'Foo bar',
|
||||
'Foo bar',
|
||||
]
|
||||
other_booking.refresh_from_db()
|
||||
assert other_booking.user_check_type == '' # not changed
|
||||
|
||||
# user_was_present is False, can not set user_presence_reason
|
||||
Booking.objects.update(user_was_present=False)
|
||||
resp = app.patch_json(
|
||||
'/api/booking/%s/' % booking.pk, params={'user_presence_reason': check_type_presence.slug}, status=400
|
||||
)
|
||||
assert resp.json['err'] == 6
|
||||
assert resp.json['err_desc'] == 'user is marked as absent, can not set presence reason'
|
||||
|
||||
# but it's ok if user_was_present is set to True
|
||||
resp = app.patch_json(
|
||||
'/api/booking/%s/' % booking.pk,
|
||||
params={'user_presence_reason': check_type_presence.slug, 'user_was_present': True},
|
||||
)
|
||||
assert resp.json['err'] == 0
|
||||
booking.refresh_from_db()
|
||||
assert booking.user_was_present is True
|
||||
assert booking.user_check_type == 'Foo bar'
|
||||
|
||||
# mark the event as checked
|
||||
event.checked = True
|
||||
event.save()
|
||||
resp = app.patch_json(
|
||||
'/api/booking/%s/' % booking.pk, params={'user_presence_reason': check_type_presence.slug}
|
||||
)
|
||||
assert resp.json['err'] == 0
|
||||
|
||||
# now disable check update
|
||||
agenda.disable_check_update = True
|
||||
agenda.save()
|
||||
resp = app.patch_json(
|
||||
'/api/booking/%s/' % booking.pk, params={'user_presence_reason': check_type_presence.slug}, status=400
|
||||
)
|
||||
assert resp.json['err'] == 5
|
||||
assert resp.json['err_desc'] == 'event is marked as checked'
|
||||
resp = app.patch_json('/api/booking/%s/' % booking.pk)
|
||||
assert resp.json['err'] == 0
|
||||
|
||||
|
||||
def test_booking_patch_api_both_reasons(app, user):
|
||||
group = CheckTypeGroup.objects.create(label='Foo')
|
||||
CheckType.objects.create(group=group, label='Foo bar', kind='presence')
|
||||
CheckType.objects.create(group=group, label='Foo baz', kind='absence')
|
||||
agenda = Agenda.objects.create(kind='events', check_type_group=group)
|
||||
event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10)
|
||||
booking = Booking.objects.create(event=event)
|
||||
|
||||
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||
resp = app.patch_json(
|
||||
'/api/booking/%s/' % booking.pk,
|
||||
params={'user_presence_reason': 'foo-bar', 'user_absence_reason': 'foo-baz'},
|
||||
status=400,
|
||||
)
|
||||
assert resp.json['err'] == 4
|
||||
assert resp.json['err_desc'] == 'invalid payload'
|
||||
|
||||
|
||||
def test_booking_patch_api_extra_data(app, user):
|
||||
agenda = Agenda.objects.create(kind='events')
|
||||
event = Event.objects.create(agenda=agenda, start_datetime=now(), places=10)
|
||||
|
|
Loading…
Reference in New Issue