From d4540149fcd5c529afd9944e66a211a1a4854fe5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Mon, 23 May 2022 15:52:54 +0200
Subject: [PATCH 3/3] pricing: import/export check types (#65459)

---
 .../migrations/0003_check_type_group.py       | 23 +++++
 lingo/agendas/models.py                       | 16 ++++
 lingo/pricing/forms.py                        |  1 +
 .../lingo/pricing/manager_agenda_detail.html  | 36 ++++++++
 .../lingo/pricing/manager_agenda_form.html    | 22 +++++
 lingo/pricing/urls.py                         |  5 ++
 lingo/pricing/utils.py                        | 13 ++-
 lingo/pricing/views.py                        | 53 +++++++++---
 tests/pricing/manager/test_agenda.py          | 17 +++-
 tests/pricing/manager/test_import_export.py   | 72 +++++++++++++++-
 tests/pricing/test_import_export.py           | 86 ++++++++++++++++++-
 11 files changed, 325 insertions(+), 19 deletions(-)
 create mode 100644 lingo/agendas/migrations/0003_check_type_group.py
 create mode 100644 lingo/pricing/templates/lingo/pricing/manager_agenda_form.html

diff --git a/lingo/agendas/migrations/0003_check_type_group.py b/lingo/agendas/migrations/0003_check_type_group.py
new file mode 100644
index 0000000..eda655b
--- /dev/null
+++ b/lingo/agendas/migrations/0003_check_type_group.py
@@ -0,0 +1,23 @@
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('agendas', '0002_agenda'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='agenda',
+            name='check_type_group',
+            field=models.ForeignKey(
+                blank=True,
+                null=True,
+                on_delete=django.db.models.deletion.SET_NULL,
+                to='agendas.CheckTypeGroup',
+                verbose_name='Check type group',
+            ),
+        ),
+    ]
diff --git a/lingo/agendas/models.py b/lingo/agendas/models.py
index 680c043..a4b01b3 100644
--- a/lingo/agendas/models.py
+++ b/lingo/agendas/models.py
@@ -28,6 +28,13 @@ class Agenda(models.Model):
     slug = models.SlugField(_('Identifier'), max_length=160, unique=True)
     category_label = models.CharField(_('Category label'), max_length=150, null=True)
     category_slug = models.SlugField(_('Category identifier'), max_length=160, null=True)
+    check_type_group = models.ForeignKey(
+        'CheckTypeGroup',
+        verbose_name=_('Check type group'),
+        blank=True,
+        null=True,
+        on_delete=models.SET_NULL,
+    )
 
     def __str__(self):
         return self.label
@@ -45,6 +52,7 @@ class Agenda(models.Model):
         return {
             'slug': self.slug,
             'pricings': [x.export_json() for x in self.agendapricing_set.all()],
+            'check_type_group': self.check_type_group.slug if self.check_type_group else None,
         }
 
     @classmethod
@@ -62,6 +70,14 @@ class Agenda(models.Model):
                 pricing_data['pricing'] = Pricing.objects.get(slug=pricing_data['pricing'])
             except Pricing.DoesNotExist:
                 raise AgendaImportError(_('Missing "%s" pricing model') % pricing_data['pricing'])
+        if data.get('check_type_group'):
+            try:
+                data['check_type_group'] = CheckTypeGroup.objects.get(slug=data['check_type_group'])
+            except CheckTypeGroup.DoesNotExist:
+                raise AgendaImportError(_('Missing "%s" check type group') % data['check_type_group'])
+
+        agenda.check_type_group = data.get('check_type_group')
+        agenda.save()
 
         for pricing_data in pricings:
             pricing_data['agenda'] = agenda
diff --git a/lingo/pricing/forms.py b/lingo/pricing/forms.py
index 3f1a750..646ed75 100644
--- a/lingo/pricing/forms.py
+++ b/lingo/pricing/forms.py
@@ -25,6 +25,7 @@ from lingo.pricing.models import AgendaPricing, Criteria, CriteriaCategory
 
 class ExportForm(forms.Form):
     agendas = forms.BooleanField(label=_('Agendas'), required=False, initial=True)
+    check_type_groups = forms.BooleanField(label=_('Check type groups'), required=False, initial=True)
     pricing_categories = forms.BooleanField(
         label=_('Pricing criteria categories'), required=False, initial=True
     )
diff --git a/lingo/pricing/templates/lingo/pricing/manager_agenda_detail.html b/lingo/pricing/templates/lingo/pricing/manager_agenda_detail.html
index 946d32c..51a6081 100644
--- a/lingo/pricing/templates/lingo/pricing/manager_agenda_detail.html
+++ b/lingo/pricing/templates/lingo/pricing/manager_agenda_detail.html
@@ -36,4 +36,40 @@
   {% endif %}
 </div>
 </div>
+
+<div class="section">
+<h3>{% trans "Booking check options" %}
+    <a rel="popup" class="button" href="{% url 'lingo-manager-agenda-booking-check-settings' pk=object.pk %}">{% trans 'Configure' %}</a>
+</h3>
+<div>
+    <ul>
+    {% if agenda.check_type_group %}
+    <li>{% trans "Check type group:" %} {{ agenda.check_type_group }}
+        <ul>
+            <li>{% trans "Absences:" %}
+                <ul>
+                  {% for check_type in agenda.check_type_group.check_types.absences %}
+                  <li>{{ check_type }}</li>
+                  {% empty %}
+                  <li>({% trans "No absence check type defined" %})</li>
+                  {% endfor %}
+                </ul>
+            </li>
+            <li>{% trans "Presences:" %}
+                <ul>
+                  {% for check_type in agenda.check_type_group.check_types.presences %}
+                  <li>{{ check_type }}</li>
+                  {% empty %}
+                  <li>({% trans "No presence check type defined" %})</li>
+                  {% endfor %}
+                </ul>
+            </li>
+        </ul>
+    </li>
+    {% else %}
+    <li>{% trans "No check types configured for this agenda." %}</li>
+    {% endif %}
+    </ul>
+</div>
+</div>
 {% endblock %}
diff --git a/lingo/pricing/templates/lingo/pricing/manager_agenda_form.html b/lingo/pricing/templates/lingo/pricing/manager_agenda_form.html
new file mode 100644
index 0000000..336b21f
--- /dev/null
+++ b/lingo/pricing/templates/lingo/pricing/manager_agenda_form.html
@@ -0,0 +1,22 @@
+{% extends "lingo/pricing/manager_agenda_detail.html" %}
+{% load i18n %}
+
+{% block breadcrumb %}
+{{ block.super }}
+<a href="{{ form_url }}">{{ title }}</a>
+{% endblock %}
+
+{% block appbar %}
+<h2>{{ title }}</h2>
+{% endblock %}
+
+{% block content %}
+<form method="post" enctype="multipart/form-data">
+  {% csrf_token %}
+  {{ form.as_p }}
+  <div class="buttons">
+    <button class="submit-button">{% trans "Save" %}</button>
+    <a class="cancel" href="{% url 'lingo-manager-agenda-detail' agenda.pk %}">{% trans 'Cancel' %}</a>
+  </div>
+</form>
+{% endblock %}
diff --git a/lingo/pricing/urls.py b/lingo/pricing/urls.py
index a8a6b39..b6fad90 100644
--- a/lingo/pricing/urls.py
+++ b/lingo/pricing/urls.py
@@ -143,6 +143,11 @@ urlpatterns = [
         staff_member_required(views.agenda_export),
         name='lingo-manager-agenda-export',
     ),
+    url(
+        r'^agenda/(?P<pk>\d+)/check-options/$',
+        staff_member_required(views.agenda_booking_check_settings),
+        name='lingo-manager-agenda-booking-check-settings',
+    ),
     url(
         r'^agenda/(?P<pk>\d+)/pricing/add/$',
         staff_member_required(views.agenda_pricing_add),
diff --git a/lingo/pricing/utils.py b/lingo/pricing/utils.py
index a786d93..6288feb 100644
--- a/lingo/pricing/utils.py
+++ b/lingo/pricing/utils.py
@@ -18,12 +18,13 @@ import collections
 
 from django.db import transaction
 
-from lingo.agendas.models import Agenda
+from lingo.agendas.models import Agenda, CheckTypeGroup
 from lingo.pricing.models import AgendaPricing, CriteriaCategory, Pricing
 
 
 def export_site(
     agendas=True,
+    check_type_groups=True,
     pricing_categories=True,
     pricing_models=True,
 ):
@@ -33,6 +34,8 @@ def export_site(
         data['pricing_models'] = [x.export_json() for x in Pricing.objects.all()]
     if pricing_categories:
         data['pricing_categories'] = [x.export_json() for x in CriteriaCategory.objects.all()]
+    if check_type_groups:
+        data['check_type_groups'] = [x.export_json() for x in CheckTypeGroup.objects.all()]
     if agendas:
         data['agendas'] = [x.export_json() for x in Agenda.objects.all()]
     return data
@@ -40,7 +43,10 @@ def export_site(
 
 def import_site(data, if_empty=False, clean=False, overwrite=False):
     if if_empty and (
-        AgendaPricing.objects.exists() or CriteriaCategory.objects.exists() or Pricing.objects.exists()
+        AgendaPricing.objects.exists()
+        or CheckTypeGroup.objects.exists()
+        or CriteriaCategory.objects.exists()
+        or Pricing.objects.exists()
     ):
         return
 
@@ -48,11 +54,13 @@ def import_site(data, if_empty=False, clean=False, overwrite=False):
         AgendaPricing.objects.all().delete()
         CriteriaCategory.objects.all().delete()
         Pricing.objects.all().delete()
+        CheckTypeGroup.objects.all().delete()
 
     results = {
         key: collections.defaultdict(list)
         for key in [
             'agendas',
+            'check_type_groups',
             'pricing_categories',
             'pricing_models',
         ]
@@ -62,6 +70,7 @@ def import_site(data, if_empty=False, clean=False, overwrite=False):
         for cls, key in (
             (CriteriaCategory, 'pricing_categories'),
             (Pricing, 'pricing_models'),
+            (CheckTypeGroup, 'check_type_groups'),
             (Agenda, 'agendas'),
         ):
             objs = data.get(key, [])
diff --git a/lingo/pricing/views.py b/lingo/pricing/views.py
index 7c175b6..dcd0e8f 100644
--- a/lingo/pricing/views.py
+++ b/lingo/pricing/views.py
@@ -108,6 +108,20 @@ class ConfigImportView(FormView):
                     x,
                 ),
             },
+            'check_type_groups': {
+                'create_noop': _('No check type group created.'),
+                'create': lambda x: ungettext(
+                    'A check type group has been created.',
+                    '%(count)d check type groups have been created.',
+                    x,
+                ),
+                'update_noop': _('No check type group updated.'),
+                'update': lambda x: ungettext(
+                    'A check type group has been updated.',
+                    '%(count)d check type groups have been updated.',
+                    x,
+                ),
+            },
             'pricing_categories': {
                 'create_noop': _('No pricing criteria category created.'),
                 'create': lambda x: ungettext(
@@ -156,12 +170,13 @@ class ConfigImportView(FormView):
 
                 obj_results['messages'] = "%s %s" % (message1, message2)
 
-        a_count, pc_count, pm_count = (
+        a_count, ct_count, pc_count, pm_count = (
             len(results['agendas']['all']),
+            len(results['check_type_groups']['all']),
             len(results['pricing_categories']['all']),
             len(results['pricing_models']['all']),
         )
-        if (a_count, pc_count, pm_count) == (1, 0, 0):
+        if (a_count, ct_count, pc_count, pm_count) == (1, 0, 0, 0):
             # only one agenda imported, redirect to agenda page
             return HttpResponseRedirect(
                 reverse(
@@ -169,10 +184,13 @@ class ConfigImportView(FormView):
                     kwargs={'pk': results['agendas']['all'][0].pk},
                 )
             )
-        if (a_count, pc_count, pm_count) == (0, 1, 0):
+        if (a_count, ct_count, pc_count, pm_count) == (0, 1, 0, 0):
+            # only one check type group imported, redirect to check type page
+            return HttpResponseRedirect(reverse('lingo-manager-check-type-list'))
+        if (a_count, ct_count, pc_count, pm_count) == (0, 0, 1, 0):
             # only one criteria category imported, redirect to criteria page
             return HttpResponseRedirect(reverse('lingo-manager-pricing-criteria-list'))
-        if (a_count, pc_count, pm_count) == (0, 0, 1):
+        if (a_count, ct_count, pc_count, pm_count) == (0, 0, 0, 1):
             # only one pricing imported, redirect to pricing page
             return HttpResponseRedirect(
                 reverse(
@@ -185,6 +203,7 @@ class ConfigImportView(FormView):
             messages.info(self.request, _('No data found.'))
         else:
             messages.info(self.request, results['agendas']['messages'])
+            messages.info(self.request, results['check_type_groups']['messages'])
             messages.info(self.request, results['pricing_categories']['messages'])
             messages.info(self.request, results['pricing_models']['messages'])
 
@@ -646,6 +665,21 @@ class AgendaExport(AgendaMixin, DetailView):
 agenda_export = AgendaExport.as_view()
 
 
+class AgendaBookingCheckSettingsView(AgendaMixin, UpdateView):
+    template_name = 'lingo/pricing/manager_agenda_form.html'
+    model = Agenda
+    fields = ['check_type_group']
+
+    def get_context_data(self, **kwargs):
+        context = super().get_context_data(**kwargs)
+        context['form_url'] = reverse('lingo-manager-agenda-booking-check-settings', args=[self.agenda.pk])
+        context['title'] = _("Configure booking check options")
+        return context
+
+
+agenda_booking_check_settings = AgendaBookingCheckSettingsView.as_view()
+
+
 class AgendaPricingAddView(AgendaMixin, CreateView):
     template_name = 'lingo/pricing/manager_agenda_pricing_form.html'
     model = AgendaPricing
@@ -663,9 +697,6 @@ class AgendaPricingDetailView(AgendaMixin, DetailView):
     pk_url_kwarg = 'pricing_pk'
     template_name = 'lingo/pricing/manager_agenda_pricing_detail.html'
 
-    def set_agenda(self, **kwargs):
-        self.agenda = get_object_or_404(Agenda, pk=kwargs.get('pk'))
-
     def get_queryset(self):
         return AgendaPricing.objects.filter(agenda=self.agenda).prefetch_related(
             'pricing__criterias__category'
@@ -681,9 +712,6 @@ class AgendaPricingEditView(AgendaMixin, UpdateView):
     pk_url_kwarg = 'pricing_pk'
     form_class = AgendaPricingForm
 
-    def set_agenda(self, **kwargs):
-        self.agenda = get_object_or_404(Agenda, pk=kwargs.get('pk'))
-
     def get_queryset(self):
         return AgendaPricing.objects.filter(agenda=self.agenda)
 
@@ -699,9 +727,6 @@ class AgendaPricingDeleteView(AgendaMixin, DeleteView):
     model = AgendaPricing
     pk_url_kwarg = 'pricing_pk'
 
-    def set_agenda(self, **kwargs):
-        self.agenda = get_object_or_404(Agenda, pk=kwargs.get('pk'))
-
     def get_queryset(self):
         return AgendaPricing.objects.filter(agenda=self.agenda)
 
@@ -713,7 +738,7 @@ class AgendaPricingMatrixEdit(AgendaMixin, FormView):
     template_name = 'lingo/pricing/manager_agenda_pricing_matrix_form.html'
 
     def set_agenda(self, **kwargs):
-        self.agenda = get_object_or_404(Agenda, pk=kwargs.get('pk'))
+        super().set_agenda(**kwargs)
         self.object = get_object_or_404(
             AgendaPricing.objects.filter(agenda=self.agenda), pk=kwargs['pricing_pk']
         )
diff --git a/tests/pricing/manager/test_agenda.py b/tests/pricing/manager/test_agenda.py
index 85a29c3..6235986 100644
--- a/tests/pricing/manager/test_agenda.py
+++ b/tests/pricing/manager/test_agenda.py
@@ -3,7 +3,7 @@ from unittest import mock
 
 import pytest
 
-from lingo.agendas.models import Agenda
+from lingo.agendas.models import Agenda, CheckTypeGroup
 from lingo.pricing.models import AgendaPricing, Criteria, CriteriaCategory, Pricing
 from tests.utils import login
 
@@ -698,3 +698,18 @@ def test_edit_agenda_pricing_matrix_empty(app, admin_user):
 
     app = login(app)
     app.get('/manage/pricing/agenda/%s/pricing/%s/matrix/edit/' % (agenda.pk, agenda_pricing.pk), status=404)
+
+
+def test_edit_agenda_check_type_group(app, admin_user):
+    agenda = Agenda.objects.create(label='Foo bar')
+    group = CheckTypeGroup.objects.create(label='Foo bar')
+
+    app = login(app)
+    resp = app.get('/manage/pricing/agenda/%s/' % agenda.pk)
+    assert 'No check types configured for this agenda.' in resp
+    resp = resp.click(href='/manage/pricing/agenda/%s/check-options' % agenda.pk)
+    resp.form['check_type_group'] = group.pk
+    resp = resp.form.submit().follow()
+    agenda.refresh_from_db()
+    assert agenda.check_type_group == group
+    assert 'Check type group: Foo bar' in resp
diff --git a/tests/pricing/manager/test_import_export.py b/tests/pricing/manager/test_import_export.py
index c631cbf..2a06971 100644
--- a/tests/pricing/manager/test_import_export.py
+++ b/tests/pricing/manager/test_import_export.py
@@ -4,7 +4,7 @@ import json
 import pytest
 from webtest import Upload
 
-from lingo.agendas.models import Agenda
+from lingo.agendas.models import Agenda, CheckType, CheckTypeGroup
 from lingo.pricing.models import Criteria, CriteriaCategory, Pricing
 from tests.utils import login
 
@@ -24,6 +24,7 @@ def test_export_site(freezer, app, admin_user):
     site_json = json.loads(resp.text)
     assert site_json == {
         'agendas': [],
+        'check_type_groups': [],
         'pricing_categories': [],
         'pricing_models': [],
     }
@@ -37,12 +38,14 @@ def test_export_site(freezer, app, admin_user):
 
     resp = app.get('/manage/pricing/export/')
     resp.form['agendas'] = False
+    resp.form['check_type_groups'] = False
     resp.form['pricing_categories'] = False
     resp.form['pricing_models'] = False
     resp = resp.form.submit()
 
     site_json = json.loads(resp.text)
     assert 'agendas' not in site_json
+    assert 'check_type_groups' not in site_json
     assert 'pricing_categories' not in site_json
     assert 'pricing_models' not in site_json
 
@@ -233,3 +236,70 @@ def test_import_agenda(app, admin_user):
     resp = resp.follow()
     assert '2 agendas have been updated.' in resp.text
     assert Agenda.objects.count() == 2
+
+
+@pytest.mark.freeze_time('2021-07-08')
+def test_import_check_type_group(app, admin_user):
+    group = CheckTypeGroup.objects.create(label='Foo bar')
+    CheckType.objects.create(label='Foo reason', group=group)
+    CheckType.objects.create(label='Baz', group=group)
+
+    app = login(app)
+    resp = app.get('/manage/pricing/check-type/group/%s/export/' % group.id)
+    assert resp.headers['content-type'] == 'application/json'
+    assert (
+        resp.headers['content-disposition']
+        == 'attachment; filename="export_check_type_group_foo-bar_20210708.json"'
+    )
+    group_export = resp.text
+
+    # existing group
+    resp = app.get('/manage/pricing/', status=200)
+    resp = resp.click('Import')
+    resp.form['config_json'] = Upload('export.json', group_export.encode('utf-8'), 'application/json')
+    resp = resp.form.submit()
+    assert resp.location.endswith('/manage/pricing/check-types/')
+    resp = resp.follow()
+    assert 'No check type group created. A check type group has been updated.' not in resp.text
+    assert CheckTypeGroup.objects.count() == 1
+    assert CheckType.objects.count() == 2
+
+    # new group
+    CheckTypeGroup.objects.all().delete()
+    resp = app.get('/manage/pricing/', status=200)
+    resp = resp.click('Import')
+    resp.form['config_json'] = Upload('export.json', group_export.encode('utf-8'), 'application/json')
+    resp = resp.form.submit()
+    assert resp.location.endswith('/manage/pricing/check-types/')
+    resp = resp.follow()
+    assert 'A check type group has been created. No check type group updated.' not in resp.text
+    assert CheckTypeGroup.objects.count() == 1
+    assert CheckType.objects.count() == 2
+
+    # multiple groups
+    groups = json.loads(group_export)
+    groups['check_type_groups'].append(copy.copy(groups['check_type_groups'][0]))
+    groups['check_type_groups'].append(copy.copy(groups['check_type_groups'][0]))
+    groups['check_type_groups'][1]['label'] = 'Foo bar 2'
+    groups['check_type_groups'][1]['slug'] = 'foo-bar-2'
+    groups['check_type_groups'][2]['label'] = 'Foo bar 3'
+    groups['check_type_groups'][2]['slug'] = 'foo-bar-3'
+
+    resp = app.get('/manage/pricing/', status=200)
+    resp = resp.click('Import')
+    resp.form['config_json'] = Upload('export.json', json.dumps(groups).encode('utf-8'), 'application/json')
+    resp = resp.form.submit()
+    assert resp.location.endswith('/manage/pricing/')
+    resp = resp.follow()
+    assert '2 check type groups have been created. A check type group has been updated.' in resp.text
+    assert CheckTypeGroup.objects.count() == 3
+    assert CheckType.objects.count() == 6
+
+    CheckTypeGroup.objects.all().delete()
+    resp = app.get('/manage/pricing/', status=200)
+    resp = resp.click('Import')
+    resp.form['config_json'] = Upload('export.json', json.dumps(groups).encode('utf-8'), 'application/json')
+    resp = resp.form.submit().follow()
+    assert '3 check type groups have been created. No check type group updated.' in resp.text
+    assert CheckTypeGroup.objects.count() == 3
+    assert CheckType.objects.count() == 6
diff --git a/tests/pricing/test_import_export.py b/tests/pricing/test_import_export.py
index 69e4289..9b4fd40 100644
--- a/tests/pricing/test_import_export.py
+++ b/tests/pricing/test_import_export.py
@@ -11,7 +11,7 @@ import pytest
 from django.core.management import call_command
 from django.utils.encoding import force_bytes
 
-from lingo.agendas.models import Agenda
+from lingo.agendas.models import Agenda, CheckType, CheckTypeGroup
 from lingo.pricing.models import AgendaPricing, Criteria, CriteriaCategory, Pricing, PricingCriteriaCategory
 from lingo.pricing.utils import import_site
 from lingo.utils.misc import AgendaImportError
@@ -159,6 +159,33 @@ def test_import_export_agenda_with_pricing(app):
     assert agenda_pricing.date_end == datetime.date(year=2022, month=10, day=1)
 
 
+def test_import_export_agenda_with_check_types(app):
+    group = CheckTypeGroup.objects.create(label='foo')
+    agenda = Agenda.objects.create(label='Foo Bar', check_type_group=group)
+    output = get_output_of_command('export_site')
+
+    import_site(data={}, clean=True)
+    assert CheckTypeGroup.objects.count() == 0
+    data = json.loads(output)
+    del data['check_type_groups']
+    agenda.check_type_group = None
+    agenda.save()
+
+    with pytest.raises(AgendaImportError) as excinfo:
+        import_site(data, overwrite=True)
+    assert str(excinfo.value) == 'Missing "foo" check type group'
+
+    CheckTypeGroup.objects.create(label='foobar')
+    with pytest.raises(AgendaImportError) as excinfo:
+        import_site(data, overwrite=True)
+    assert str(excinfo.value) == 'Missing "foo" check type group'
+
+    group = CheckTypeGroup.objects.create(label='foo')
+    import_site(data, overwrite=True)
+    agenda.refresh_from_db()
+    assert agenda.check_type_group == group
+
+
 def test_import_export_pricing_criteria_category(app):
     output = get_output_of_command('export_site')
     payload = json.loads(output)
@@ -372,3 +399,60 @@ def test_import_export_pricing_with_categories(app):
         35,
     ]
     assert set(pricing.criterias.all()) == {criteria1, criteria3}
+
+
+def test_import_export_check_type_group(app):
+    output = get_output_of_command('export_site')
+    payload = json.loads(output)
+    assert len(payload['check_type_groups']) == 0
+
+    group = CheckTypeGroup.objects.create(label='Foo bar')
+    CheckType.objects.create(label='Foo reason', group=group)
+    CheckType.objects.create(label='Baz', group=group)
+
+    output = get_output_of_command('export_site')
+    payload = json.loads(output)
+    assert len(payload['check_type_groups']) == 1
+
+    group.delete()
+    assert not CheckTypeGroup.objects.exists()
+    assert not CheckType.objects.exists()
+
+    import_site(copy.deepcopy(payload))
+    assert CheckTypeGroup.objects.count() == 1
+    group = CheckTypeGroup.objects.first()
+    assert group.label == 'Foo bar'
+    assert group.slug == 'foo-bar'
+    assert group.check_types.count() == 2
+    assert CheckType.objects.get(group=group, label='Foo reason', slug='foo-reason')
+    assert CheckType.objects.get(group=group, label='Baz', slug='baz')
+
+    # update
+    update_payload = copy.deepcopy(payload)
+    update_payload['check_type_groups'][0]['label'] = 'Foo bar Updated'
+    import_site(update_payload)
+    group.refresh_from_db()
+    assert group.label == 'Foo bar Updated'
+
+    # insert another group
+    group.slug = 'foo-bar-updated'
+    group.save()
+    import_site(copy.deepcopy(payload))
+    assert CheckTypeGroup.objects.count() == 2
+    group = CheckTypeGroup.objects.latest('pk')
+    assert group.label == 'Foo bar'
+    assert group.slug == 'foo-bar'
+    assert group.check_types.count() == 2
+    assert CheckType.objects.get(group=group, label='Foo reason', slug='foo-reason')
+    assert CheckType.objects.get(group=group, label='Baz', slug='baz')
+
+    # with overwrite
+    CheckType.objects.create(group=group, label='Baz2')
+    import_site(copy.deepcopy(payload), overwrite=True)
+    assert CheckTypeGroup.objects.count() == 2
+    group = CheckTypeGroup.objects.latest('pk')
+    assert group.label == 'Foo bar'
+    assert group.slug == 'foo-bar'
+    assert group.check_types.count() == 2
+    assert CheckType.objects.get(group=group, label='Foo reason', slug='foo-reason')
+    assert CheckType.objects.get(group=group, label='Baz', slug='baz')
-- 
2.35.1

