From d501c7b5e971e272193548a1c3bc0c29e79d4063 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Tue, 7 Jun 2022 10:41:13 +0200
Subject: [PATCH 1/2] api: list agenda's check types (#66008)

---
 lingo/api/__init__.py |  0
 lingo/api/urls.py     | 27 +++++++++++++++++++++++
 lingo/api/utils.py    | 51 +++++++++++++++++++++++++++++++++++++++++++
 lingo/api/views.py    | 39 +++++++++++++++++++++++++++++++++
 lingo/settings.py     |  2 ++
 lingo/urls.py         |  2 ++
 setup.py              |  1 +
 tests/test_api.py     | 36 ++++++++++++++++++++++++++++++
 8 files changed, 158 insertions(+)
 create mode 100644 lingo/api/__init__.py
 create mode 100644 lingo/api/urls.py
 create mode 100644 lingo/api/utils.py
 create mode 100644 lingo/api/views.py
 create mode 100644 tests/test_api.py

diff --git a/lingo/api/__init__.py b/lingo/api/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/lingo/api/urls.py b/lingo/api/urls.py
new file mode 100644
index 0000000..d2d6893
--- /dev/null
+++ b/lingo/api/urls.py
@@ -0,0 +1,27 @@
+# lingo - payment and billing system
+# Copyright (C) 2022  Entr'ouvert
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from django.conf.urls import url
+
+from . import views
+
+urlpatterns = [
+    url(
+        r'^agenda/(?P<agenda_identifier>[\w-]+)/check-types/$',
+        views.agenda_check_type_list,
+        name='api-agenda-check-types',
+    ),
+]
diff --git a/lingo/api/utils.py b/lingo/api/utils.py
new file mode 100644
index 0000000..77450e9
--- /dev/null
+++ b/lingo/api/utils.py
@@ -0,0 +1,51 @@
+# lingo - payment and billing system
+# Copyright (C) 2022  Entr'ouvert
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from django.utils.translation import gettext_lazy as _
+from rest_framework.response import Response
+from rest_framework.views import exception_handler as DRF_exception_handler
+
+
+class APIError(Exception):
+    http_status = 200
+
+    def __init__(self, message, *args, err=1, err_class=None, errors=None):
+        self.err_desc = _(message) % args
+        self.err = err
+        self.err_class = err_class or message % args
+        self.errors = errors
+        super().__init__(self.err_desc)
+
+    def to_response(self):
+        data = {
+            'err': self.err,
+            'err_class': self.err_class,
+            'err_desc': self.err_desc,
+        }
+        if self.errors:
+            data['errors'] = self.errors
+        return Response(data, status=self.http_status)
+
+
+class APIErrorBadRequest(APIError):
+    http_status = 400
+
+
+def exception_handler(exc, context):
+    if isinstance(exc, APIError):
+        return exc.to_response()
+
+    return DRF_exception_handler(exc, context)
diff --git a/lingo/api/views.py b/lingo/api/views.py
new file mode 100644
index 0000000..a2410c5
--- /dev/null
+++ b/lingo/api/views.py
@@ -0,0 +1,39 @@
+# lingo - payment and billing system
+# Copyright (C) 2022  Entr'ouvert
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from django.shortcuts import get_object_or_404
+from rest_framework.views import APIView
+
+from lingo.agendas.models import Agenda
+from lingo.api.utils import Response
+
+
+class AgendaCheckTypeList(APIView):
+    permission_classes = ()
+
+    def get(self, request, agenda_identifier=None, format=None):
+        agenda = get_object_or_404(Agenda, slug=agenda_identifier)
+
+        check_types = []
+        if agenda.check_type_group:
+            check_types = [
+                {'id': x.slug, 'text': x.label, 'kind': x.kind, 'disabled': x.disabled}
+                for x in agenda.check_type_group.check_types.all()
+            ]
+        return Response({'data': check_types})
+
+
+agenda_check_type_list = AgendaCheckTypeList.as_view()
diff --git a/lingo/settings.py b/lingo/settings.py
index 8a1209f..5612f76 100644
--- a/lingo/settings.py
+++ b/lingo/settings.py
@@ -57,6 +57,7 @@ INSTALLED_APPS = (
     'lingo.agendas',
     'lingo.manager',
     'lingo.pricing',
+    'lingo.api',
 )
 
 MIDDLEWARE = (
@@ -194,6 +195,7 @@ def debug_show_toolbar(request):
 
 DEBUG_TOOLBAR_CONFIG = {'SHOW_TOOLBAR_CALLBACK': debug_show_toolbar}
 
+REST_FRAMEWORK = {'EXCEPTION_HANDLER': 'lingo.api.utils.exception_handler'}
 
 local_settings_file = os.environ.get(
     'LINGO_SETTINGS_FILE', os.path.join(os.path.dirname(__file__), 'local_settings.py')
diff --git a/lingo/urls.py b/lingo/urls.py
index a822257..1e18323 100644
--- a/lingo/urls.py
+++ b/lingo/urls.py
@@ -19,6 +19,7 @@ from django.conf.urls import include, url
 from django.conf.urls.static import static
 from django.contrib.staticfiles.urls import staticfiles_urlpatterns
 
+from .api.urls import urlpatterns as lingo_api_urls
 from .manager.urls import urlpatterns as lingo_manager_urls
 from .pricing.urls import urlpatterns as lingo_pricing_urls
 from .urls_utils import decorated_includes, manager_required
@@ -28,6 +29,7 @@ urlpatterns = [
     url(r'^$', homepage, name='homepage'),
     url(r'^manage/', decorated_includes(manager_required, include(lingo_manager_urls))),
     url(r'^manage/pricing/', decorated_includes(manager_required, include(lingo_pricing_urls))),
+    url(r'^api/', include(lingo_api_urls)),
     url(r'^login/$', login, name='auth_login'),
     url(r'^logout/$', logout, name='auth_logout'),
 ]
diff --git a/setup.py b/setup.py
index 860413b..17c3688 100644
--- a/setup.py
+++ b/setup.py
@@ -162,6 +162,7 @@ setup(
     install_requires=[
         'django>=2.2, <2.3',
         'gadjo>=0.53',
+        'djangorestframework>=3.4',
         'requests',
         'eopayment>=1.60',
         'djangorestframework>=3.3, <3.10',
diff --git a/tests/test_api.py b/tests/test_api.py
new file mode 100644
index 0000000..a9e7acc
--- /dev/null
+++ b/tests/test_api.py
@@ -0,0 +1,36 @@
+import pytest
+
+from lingo.agendas.models import Agenda, CheckType, CheckTypeGroup
+
+pytestmark = pytest.mark.django_db
+
+
+def test_agendas_check_types_api(app):
+    agenda = Agenda.objects.create(label='Foo bar')
+    group = CheckTypeGroup.objects.create(label='Foo bar')
+    CheckType.objects.create(label='Foo reason', group=group, kind='absence')
+    CheckType.objects.create(label='Bar reason', group=group, kind='presence')
+    CheckType.objects.create(label='Baz reason', group=group, kind='presence', disabled=True)
+    group2 = CheckTypeGroup.objects.create(label='Foo bar 2')
+
+    resp = app.get('/api/agenda/%s/check-types/' % agenda.slug)
+    assert resp.json == {'data': []}
+
+    agenda.check_type_group = group2
+    agenda.save()
+    resp = app.get('/api/agenda/%s/check-types/' % agenda.slug)
+    assert resp.json == {'data': []}
+
+    agenda.check_type_group = group
+    agenda.save()
+    resp = app.get('/api/agenda/%s/check-types/' % agenda.slug)
+    assert resp.json == {
+        'data': [
+            {'id': 'bar-reason', 'kind': 'presence', 'text': 'Bar reason', 'disabled': False},
+            {'id': 'baz-reason', 'kind': 'presence', 'text': 'Baz reason', 'disabled': True},
+            {'id': 'foo-reason', 'kind': 'absence', 'text': 'Foo reason', 'disabled': False},
+        ]
+    }
+
+    # unknown
+    resp = app.get('/api/agenda/xxxx/resources/', status=404)
-- 
2.35.1

