From 139b233a901b126cec56a92dfb66fe8dd9324984 Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Thu, 25 Nov 2021 14:07:16 +0100 Subject: [PATCH] agendas: add subscription model (#58444) --- .../agendas/migrations/0104_subscription.py | 34 +++++++++++++++++++ chrono/agendas/models.py | 7 ++++ chrono/api/serializers.py | 14 +++++++- chrono/api/urls.py | 5 +++ chrono/api/views.py | 19 +++++++++++ tests/api/test_all.py | 33 ++++++++++++++++++ 6 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 chrono/agendas/migrations/0104_subscription.py diff --git a/chrono/agendas/migrations/0104_subscription.py b/chrono/agendas/migrations/0104_subscription.py new file mode 100644 index 00000000..6d152e1a --- /dev/null +++ b/chrono/agendas/migrations/0104_subscription.py @@ -0,0 +1,34 @@ +# Generated by Django 2.2.19 on 2021-11-25 13:21 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('agendas', '0103_publication_datetime'), + ] + + operations = [ + migrations.CreateModel( + name='Subscription', + fields=[ + ( + 'id', + models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'), + ), + ('user_external_id', models.CharField(max_length=250)), + ('date_start', models.DateField()), + ('date_end', models.DateField()), + ( + 'agenda', + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name='subscriptions', + to='agendas.Agenda', + ), + ), + ], + ), + ] diff --git a/chrono/agendas/models.py b/chrono/agendas/models.py index f8e5d085..184b600d 100644 --- a/chrono/agendas/models.py +++ b/chrono/agendas/models.py @@ -2900,3 +2900,10 @@ class AbsenceReason(models.Model): 'label': self.label, 'slug': self.slug, } + + +class Subscription(models.Model): + agenda = models.ForeignKey(Agenda, on_delete=models.CASCADE, related_name='subscriptions') + user_external_id = models.CharField(max_length=250) + date_start = models.DateField() + date_end = models.DateField() diff --git a/chrono/api/serializers.py b/chrono/api/serializers.py index c532a553..294d7352 100644 --- a/chrono/api/serializers.py +++ b/chrono/api/serializers.py @@ -5,7 +5,7 @@ from django.utils.translation import ugettext_lazy as _ from rest_framework import serializers from rest_framework.exceptions import ValidationError -from chrono.agendas.models import AbsenceReason, Agenda, Booking, Category, Event +from chrono.agendas.models import AbsenceReason, Agenda, Booking, Category, Event, Subscription class StringOrListField(serializers.ListField): @@ -274,3 +274,15 @@ class AgendaSerializer(serializers.ModelSerializer): } ) return attrs + + +class SubscriptionSerializer(serializers.ModelSerializer): + class Meta: + model = Subscription + fields = ['user_external_id', 'date_start', 'date_end'] + + def validate(self, attrs): + super().validate(attrs) + if attrs['date_start'] > attrs['date_end']: + raise ValidationError(_('start_datetime must be before end_datetime')) + return attrs diff --git a/chrono/api/urls.py b/chrono/api/urls.py index 82b87c3d..424c5f7c 100644 --- a/chrono/api/urls.py +++ b/chrono/api/urls.py @@ -88,6 +88,11 @@ urlpatterns = [ views.meeting_datetimes, name='api-agenda-meeting-datetimes', ), + url( + r'^agenda/(?P[\w-]+)/subscription/$', + views.subscription, + name='api-agenda-subscription', + ), url(r'^bookings/$', views.bookings, name='api-bookings'), url(r'^booking/(?P\d+)/$', views.booking, name='api-booking'), url(r'^booking/(?P\d+)/cancel/$', views.cancel_booking, name='api-cancel-booking'), diff --git a/chrono/api/views.py b/chrono/api/views.py index 83608fd9..65d29b46 100644 --- a/chrono/api/views.py +++ b/chrono/api/views.py @@ -49,6 +49,7 @@ from chrono.agendas.models import ( Desk, Event, MeetingType, + Subscription, TimePeriodException, ) from chrono.api import serializers @@ -1722,6 +1723,24 @@ class MultipleAgendasEventsFillslots(EventsFillslots): agendas_events_fillslots = MultipleAgendasEventsFillslots.as_view() +class SubscriptionAPI(APIView): + serializer_class = serializers.SubscriptionSerializer + permission_classes = (permissions.IsAuthenticated,) + + def post(self, request, agenda_identifier): + agenda = get_object_or_404(Agenda, slug=agenda_identifier, kind='events') + + serializer = self.serializer_class(data=request.data) + if not serializer.is_valid(): + raise APIErrorBadRequest(N_('invalid payload'), errors=serializer.errors) + + subscription = Subscription.objects.create(agenda=agenda, **serializer.validated_data) + return Response({'err': 0, 'id': subscription.pk}) + + +subscription = SubscriptionAPI.as_view() + + class BookingFilter(filters.FilterSet): agenda = filters.CharFilter(field_name='event__agenda__slug', lookup_expr='exact') category = filters.CharFilter(field_name='event__agenda__category__slug', lookup_expr='exact') diff --git a/tests/api/test_all.py b/tests/api/test_all.py index b7156046..34861590 100644 --- a/tests/api/test_all.py +++ b/tests/api/test_all.py @@ -15,6 +15,7 @@ from chrono.agendas.models import ( Event, MeetingType, Resource, + Subscription, TimePeriodException, VirtualMember, ) @@ -729,3 +730,35 @@ def test_add_agenda(app, user, settings): assert agenda.view_role == view_group assert agenda.min_booking_datetime.date() == datetime.date(2021, 7, 12) assert agenda.category == category_a + + +def test_api_create_subscription(app, user): + agenda = Agenda.objects.create(label='Foo bar', kind='events') + + resp = app.post('/api/agenda/%s/subscription/' % agenda.slug, status=401) + + app.authorization = ('Basic', ('john.doe', 'password')) + + params = {'user_external_id': 'xxx', 'date_start': '2021-09-01', 'date_end': '2021-10-01'} + resp = app.post('/api/agenda/%s/subscription/' % agenda.slug, params=params) + subscription = Subscription.objects.get(pk=resp.json['id']) + assert subscription.agenda == agenda + assert subscription.user_external_id == 'xxx' + assert subscription.date_start == datetime.date(year=2021, month=9, day=1) + assert subscription.date_end == datetime.date(year=2021, month=10, day=1) + + # check errors + resp = app.post('/api/agenda/%s/subscription/' % agenda.slug, params={}, status=400) + assert resp.json['err_class'] == 'invalid payload' + for field in ('user_external_id', 'date_start', 'date_end'): + assert 'required' in resp.json['errors'][field][0] + + params = {'user_external_id': 'xxx', 'date_start': 'wrong-format', 'date_end': 'wrong-format'} + resp = app.post('/api/agenda/%s/subscription/' % agenda.slug, params=params, status=400) + assert resp.json['err_class'] == 'invalid payload' + assert 'wrong format' in resp.json['errors']['date_start'][0] + assert 'wrong format' in resp.json['errors']['date_end'][0] + + params = {'user_external_id': 'xxx', 'date_start': '2021-10-01', 'date_end': '2021-09-01'} + resp = app.post('/api/agenda/%s/subscription/' % agenda.slug, params=params, status=400) + assert resp.json['errors']['non_field_errors'][0] == 'start_datetime must be before end_datetime' -- 2.30.2