From e74164b909a7bc60ca5892738cf24dcaa1e27e37 Mon Sep 17 00:00:00 2001 From: Steven Bal Date: Tue, 26 Mar 2024 15:36:52 +0100 Subject: [PATCH 01/12] :sparkles: [#2242] Add CMS plugin for mijn afspraken task: https://taiga.maykinmedia.nl/project/open-inwoner/task/2242 --- .../cms/plugins/cms_plugins/__init__.py | 2 + .../cms/plugins/cms_plugins/appointments.py | 38 +++++++++++++ .../plugins/migrations/0005_myappointments.py | 45 ++++++++++++++++ .../cms/plugins/models/__init__.py | 2 + .../cms/plugins/models/appointments.py | 16 ++++++ src/open_inwoner/conf/base.py | 1 + .../components/Appointments/Appointments.scss | 7 +++ .../components/PluginCard/PluginCard.scss | 54 +++++++++++++++++++ .../scss/components/UserFeed/UserFeed.scss | 53 ------------------ src/open_inwoner/scss/components/_index.scss | 2 + .../plugins/appointments/appointments.html | 25 +++++++++ .../cms/plugins/userfeed/userfeed.html | 4 +- 12 files changed, 194 insertions(+), 55 deletions(-) create mode 100644 src/open_inwoner/cms/plugins/cms_plugins/appointments.py create mode 100644 src/open_inwoner/cms/plugins/migrations/0005_myappointments.py create mode 100644 src/open_inwoner/cms/plugins/models/appointments.py create mode 100644 src/open_inwoner/scss/components/Appointments/Appointments.scss create mode 100644 src/open_inwoner/scss/components/PluginCard/PluginCard.scss create mode 100644 src/open_inwoner/templates/cms/plugins/appointments/appointments.html diff --git a/src/open_inwoner/cms/plugins/cms_plugins/__init__.py b/src/open_inwoner/cms/plugins/cms_plugins/__init__.py index ff68bb59e0..1ee8e4713d 100644 --- a/src/open_inwoner/cms/plugins/cms_plugins/__init__.py +++ b/src/open_inwoner/cms/plugins/cms_plugins/__init__.py @@ -1,7 +1,9 @@ +from .appointments import MyAppointmentsPlugin from .userfeed import UserFeedPlugin from .videoplayer import VideoPlayerPlugin __all__ = [ + "MyAppointmentsPlugin", "UserFeedPlugin", "VideoPlayerPlugin", ] diff --git a/src/open_inwoner/cms/plugins/cms_plugins/appointments.py b/src/open_inwoner/cms/plugins/cms_plugins/appointments.py new file mode 100644 index 0000000000..e8ab65abd4 --- /dev/null +++ b/src/open_inwoner/cms/plugins/cms_plugins/appointments.py @@ -0,0 +1,38 @@ +import logging + +from django.utils.translation import gettext as _ + +from cms.plugin_base import CMSPluginBase +from cms.plugin_pool import plugin_pool + +from open_inwoner.cms.plugins.models.appointments import MyAppointments +from open_inwoner.qmatic.client import NoServiceConfigured, QmaticClient + +logger = logging.getLogger(__name__) + + +@plugin_pool.register_plugin +class MyAppointmentsPlugin(CMSPluginBase): + model = MyAppointments + module = _("General") + name = _("My appointments") + render_template = "cms/plugins/appointments/appointments.html" + + def render(self, context, instance, placeholder): + request = context["request"] + # TODO email should be verified + try: + client = QmaticClient() + except NoServiceConfigured: + logger.exception("Error occurred while creating Qmatic client") + appointments = [] + else: + appointments = client.list_appointments_for_customer(request.user.email) + + context.update( + { + "instance": instance, + "appointments": appointments, + } + ) + return context diff --git a/src/open_inwoner/cms/plugins/migrations/0005_myappointments.py b/src/open_inwoner/cms/plugins/migrations/0005_myappointments.py new file mode 100644 index 0000000000..7734682544 --- /dev/null +++ b/src/open_inwoner/cms/plugins/migrations/0005_myappointments.py @@ -0,0 +1,45 @@ +# Generated by Django 4.2.10 on 2024-03-26 14:04 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("cms", "0022_auto_20180620_1551"), + ("plugins", "0004_alter_userfeed_cmsplugin_ptr_and_more"), + ] + + operations = [ + migrations.CreateModel( + name="MyAppointments", + fields=[ + ( + "cmsplugin_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + related_name="%(app_label)s_%(class)s", + serialize=False, + to="cms.cmsplugin", + ), + ), + ( + "title", + models.CharField( + default="Geplande balie-afspraken", + help_text="The title of the plugin block", + max_length=250, + verbose_name="Title", + ), + ), + ], + options={ + "abstract": False, + }, + bases=("cms.cmsplugin",), + ), + ] diff --git a/src/open_inwoner/cms/plugins/models/__init__.py b/src/open_inwoner/cms/plugins/models/__init__.py index 71f41ff6d5..0f5472ab27 100644 --- a/src/open_inwoner/cms/plugins/models/__init__.py +++ b/src/open_inwoner/cms/plugins/models/__init__.py @@ -1,7 +1,9 @@ +from .appointments import MyAppointments from .userfeed import UserFeed from .videoplayer import VideoPlayer __all__ = [ + "MyAppointments", "UserFeed", "VideoPlayer", ] diff --git a/src/open_inwoner/cms/plugins/models/appointments.py b/src/open_inwoner/cms/plugins/models/appointments.py new file mode 100644 index 0000000000..3ef53beae5 --- /dev/null +++ b/src/open_inwoner/cms/plugins/models/appointments.py @@ -0,0 +1,16 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from cms.models import CMSPlugin + + +class MyAppointments(CMSPlugin): + title = models.CharField( + _("Title"), + max_length=250, + help_text=_("The title of the plugin block"), + default=_("Geplande balie-afspraken"), + ) + + def __str__(self): + return self.title or super().__str__() diff --git a/src/open_inwoner/conf/base.py b/src/open_inwoner/conf/base.py index d6d5480f64..2297220fb6 100644 --- a/src/open_inwoner/conf/base.py +++ b/src/open_inwoner/conf/base.py @@ -562,6 +562,7 @@ "ProductFinderPlugin", "ProductLocationPlugin", "UserFeedPlugin", + "MyAppointmentsPlugin", ], "text_only_plugins": ["LinkPlugin"], "name": _("Content"), diff --git a/src/open_inwoner/scss/components/Appointments/Appointments.scss b/src/open_inwoner/scss/components/Appointments/Appointments.scss new file mode 100644 index 0000000000..cc98936dbd --- /dev/null +++ b/src/open_inwoner/scss/components/Appointments/Appointments.scss @@ -0,0 +1,7 @@ +.appointments { + .card__body { + span.button:last-child { + bottom: var(--spacing-large) !important; + } + } +} diff --git a/src/open_inwoner/scss/components/PluginCard/PluginCard.scss b/src/open_inwoner/scss/components/PluginCard/PluginCard.scss new file mode 100644 index 0000000000..e3163c4184 --- /dev/null +++ b/src/open_inwoner/scss/components/PluginCard/PluginCard.scss @@ -0,0 +1,54 @@ +.plugin-card { + position: relative; + + &__heading { + color: var(--font-color-heading); + font-family: var(--font-family-heading); + font-size: var(--font-size-body); + line-height: var(--font-line-height-body); + padding: var(--spacing-large) var(--spacing-giant) 0 0; + margin: 0; + + @media (min-width: 768px) { + padding: var(--spacing-large) 0 0 0; + } + } + + .card { + justify-content: space-between; + } + + .card-container { + grid-row-gap: var(--spacing-medium); + + @media (max-width: 380px) { + grid-template-columns: repeat( + auto-fit, + calc(320px - var(--spacing-small)) + ); + } + } + + .card__body { + background-color: #fff; + border-bottom-left-radius: var(--border-radius); + border-bottom-right-radius: var(--border-radius); + padding: var(--card-spacing) var(--spacing-giant) var(--card-spacing) + var(--card-spacing); + + @media (min-width: 768px) { + padding: var(--card-spacing); + } + + .tabled__value { + color: var(--color-gray-dark); + font-size: var(--font-size-body-small); + margin: 0; + padding: 0; + } + + span.button:last-child { + bottom: var(--spacing-medium); + } + } +} diff --git a/src/open_inwoner/scss/components/UserFeed/UserFeed.scss b/src/open_inwoner/scss/components/UserFeed/UserFeed.scss index 0a4eae8987..71549576f4 100644 --- a/src/open_inwoner/scss/components/UserFeed/UserFeed.scss +++ b/src/open_inwoner/scss/components/UserFeed/UserFeed.scss @@ -1,6 +1,4 @@ .userfeed { - position: relative; - &__summary { background-color: var(--color-white); color: var(--color-red-notification); @@ -54,34 +52,6 @@ } } - &__heading { - color: var(--font-color-heading); - font-family: var(--font-family-heading); - font-size: var(--font-size-body); - line-height: var(--font-line-height-body); - padding: var(--spacing-large) var(--spacing-giant) 0 0; - margin: 0; - - @media (min-width: 768px) { - padding: var(--spacing-large) 0 0 0; - } - } - - .card { - justify-content: space-between; - } - - .card-container { - grid-row-gap: var(--spacing-medium); - - @media (max-width: 380px) { - grid-template-columns: repeat( - auto-fit, - calc(320px - var(--spacing-small)) - ); - } - } - .card--status { // Default color padding: 0; @@ -148,27 +118,4 @@ } } } - - .card__body { - background-color: #fff; - border-bottom-left-radius: var(--border-radius); - border-bottom-right-radius: var(--border-radius); - padding: var(--card-spacing) var(--spacing-giant) var(--card-spacing) - var(--card-spacing); - - @media (min-width: 768px) { - padding: var(--card-spacing); - } - - .tabled__value { - color: var(--color-gray-dark); - font-size: var(--font-size-body-small); - margin: 0; - padding: 0; - } - - span.button:last-child { - bottom: var(--spacing-medium); - } - } } diff --git a/src/open_inwoner/scss/components/_index.scss b/src/open_inwoner/scss/components/_index.scss index 12ac209d91..9e52b8f27c 100644 --- a/src/open_inwoner/scss/components/_index.scss +++ b/src/open_inwoner/scss/components/_index.scss @@ -1,5 +1,6 @@ @import './AccessibilityHeader/AccessibilityHeader.scss'; @import './Actions/Actions.scss'; +@import './Appointments/Appointments.scss'; @import './Autocomplete/Autocomplete.scss'; @import './Button/Button.scss'; @import './Button/ButtonRow.scss'; @@ -78,6 +79,7 @@ @import './Product/product-finder.scss'; @import './Product/product-location.scss'; @import './PlanTemplate/PlanTemplate.scss'; +@import './PluginCard/PluginCard.scss'; @import './Preview/Preview.scss'; @import './ProgressIndicator/ProgressIndicator.scss'; @import './Questionnaire/Questionnaire.scss'; diff --git a/src/open_inwoner/templates/cms/plugins/appointments/appointments.html b/src/open_inwoner/templates/cms/plugins/appointments/appointments.html new file mode 100644 index 0000000000..c030e2525a --- /dev/null +++ b/src/open_inwoner/templates/cms/plugins/appointments/appointments.html @@ -0,0 +1,25 @@ +{% load i18n tz list_tags icon_tags grid_tags utils %} + +{% if appointments %} +
+

{{ instance.title }}

+ +
+{% endif %} diff --git a/src/open_inwoner/templates/cms/plugins/userfeed/userfeed.html b/src/open_inwoner/templates/cms/plugins/userfeed/userfeed.html index 203cccc1b2..453a1af3b1 100644 --- a/src/open_inwoner/templates/cms/plugins/userfeed/userfeed.html +++ b/src/open_inwoner/templates/cms/plugins/userfeed/userfeed.html @@ -14,7 +14,7 @@

{% endfor %} -
+
{% for item in userfeed.items %}
@@ -25,7 +25,7 @@

{{ item.title }}

-

+

{{ item.message }}

From 82f9fa0d290a84dd1f19f2c46d654070ea0ab47d Mon Sep 17 00:00:00 2001 From: Steven Bal Date: Tue, 26 Mar 2024 16:20:44 +0100 Subject: [PATCH 02/12] :white_check_mark: [#2242] Add test for mijn afspraken plugin task: https://taiga.maykinmedia.nl/project/open-inwoner/task/2242 --- .../accounts/tests/test_profile_views.py | 90 ++++--------------- .../cms/plugins/tests/test_appointments.py | 47 ++++++++++ src/open_inwoner/qmatic/tests/data.py | 72 +++++++++++++++ 3 files changed, 134 insertions(+), 75 deletions(-) create mode 100644 src/open_inwoner/cms/plugins/tests/test_appointments.py create mode 100644 src/open_inwoner/qmatic/tests/data.py diff --git a/src/open_inwoner/accounts/tests/test_profile_views.py b/src/open_inwoner/accounts/tests/test_profile_views.py index bb916771d1..b93cf79e58 100644 --- a/src/open_inwoner/accounts/tests/test_profile_views.py +++ b/src/open_inwoner/accounts/tests/test_profile_views.py @@ -13,8 +13,6 @@ from django_webtest import WebTest from pyquery import PyQuery as PQ from webtest import Upload -from zgw_consumers.constants import APITypes -from zgw_consumers.test.factories import ServiceFactory from open_inwoner.accounts.choices import StatusChoices from open_inwoner.cms.profile.cms_appconfig import ProfileConfig @@ -24,8 +22,7 @@ from open_inwoner.openklant.models import OpenKlantConfig from open_inwoner.pdc.tests.factories import CategoryFactory from open_inwoner.plans.tests.factories import PlanFactory -from open_inwoner.qmatic.models import QmaticConfig -from open_inwoner.qmatic.tests.factories import AppointmentFactory, BranchDetailFactory +from open_inwoner.qmatic.tests.data import QmaticMockData from open_inwoner.utils.logentry import LOG_ACTIONS from open_inwoner.utils.test import ClearCachesMixin from open_inwoner.utils.tests.helpers import AssertTimelineLogMixin, create_image_bytes @@ -1150,102 +1147,45 @@ def test_render_form_limit_newsletters_to_admin_selection(self, m): ROOT_URLCONF="open_inwoner.cms.tests.urls", MIDDLEWARE=PATCHED_MIDDLEWARE ) class MyAppointmentsTests(ClearCachesMixin, WebTest): + appointments_url = reverse("profile:appointments") + def setUp(self): super().setUp() - self.appointments_url = reverse("profile:appointments") - self.user = DigidUserFactory() - - self.config = QmaticConfig.get_solo() - self.config.booking_base_url = "https://qmatic.local/" - self.api_root = "https://qmatic.local/api/" - self.service = ServiceFactory.create( - api_root=self.api_root, api_type=APITypes.orc - ) - self.config.service = self.service - self.config.save() - - self.appointment_passport = AppointmentFactory.build( - title="Aanvraag paspoort", - start="2020-01-01T12:00:00+00:00", - notes="foo", - branch=BranchDetailFactory.build( - name="Hoofdkantoor", - timeZone="Europe/Amsterdam", - addressCity="Amsterdam", - addressLine1="Hoofdkantoor", - addressLine2="Dam 1", - addressZip="1234 ZZ", - ), - ) - self.appointment_idcard = AppointmentFactory.build( - title="Aanvraag ID kaart", - start="2020-03-06T16:30:00+00:00", - notes="bar", - branch=BranchDetailFactory.build( - name="Hoofdkantoor", - timeZone="America/New_York", - addressCity="New York", - addressLine1="Hoofdkantoor", - addressLine2="Wall Street 1", - addressZip="1111 AA", - ), - ) - - def setUpMocks(self, m): - data = { - "notifications": [], - "meta": { - "start": "", - "end": "", - "totalResults": 1, - "offset": None, - "limit": None, - "fields": "", - "arguments": [], - }, - "appointmentList": [ - self.appointment_passport.dict(), - self.appointment_idcard.dict(), - ], - } - m.get( - f"{self.api_root}v1/customers/externalId/{self.user.email}/appointments", - json=data, - ) + self.data = QmaticMockData() def test_do_not_render_list_if_config_is_missing(self, m): - self.config.service = None - self.config.save() + self.data.config.service = None + self.data.config.save() - response = self.app.get(self.appointments_url, user=self.user) + response = self.app.get(self.appointments_url, user=self.data.user) self.assertIn(_("Geen afspraken beschikbaar"), response.text) def test_do_not_render_list_if_no_appointments_are_found(self, m): m.get( - f"{self.api_root}v1/customers/externalId/{self.user.email}/appointments", + f"{self.data.api_root}v1/customers/externalId/{self.data.user.email}/appointments", status_code=404, ) - response = self.app.get(self.appointments_url, user=self.user) + response = self.app.get(self.appointments_url, user=self.data.user) self.assertIn(_("Geen afspraken beschikbaar"), response.text) def test_do_not_render_list_if_validation_error(self, m): m.get( - f"{self.api_root}v1/customers/externalId/{self.user.email}/appointments", + f"{self.data.api_root}v1/customers/externalId/{self.data.user.email}/appointments", json={"appointmentList": [{"invalid": "data"}]}, ) - response = self.app.get(self.appointments_url, user=self.user) + response = self.app.get(self.appointments_url, user=self.data.user) self.assertIn(_("Geen afspraken beschikbaar"), response.text) def test_render_list_if_appointments_are_found(self, m): - self.setUpMocks(m) + self.data.setUpMocks(m) - response = self.app.get(self.appointments_url, user=self.user) + response = self.app.get(self.appointments_url, user=self.data.user) self.assertIn(_("Een overzicht van uw afspraken"), response.text) @@ -1263,7 +1203,7 @@ def test_render_list_if_appointments_are_found(self, m): self.assertEqual(PQ(passport_appointment[5]).text(), "1234 ZZ Amsterdam") self.assertEqual( PQ(cards[0]).find("a").attr("href"), - f"{self.config.booking_base_url}{self.appointment_passport.publicId}", + f"{self.data.config.booking_base_url}{self.data.appointment_passport.publicId}", ) id_card_appointment = PQ(cards[1]).find("ul").children() @@ -1276,5 +1216,5 @@ def test_render_list_if_appointments_are_found(self, m): self.assertEqual(PQ(id_card_appointment[5]).text(), "1111 AA New York") self.assertEqual( PQ(cards[1]).find("a").attr("href"), - f"{self.config.booking_base_url}{self.appointment_idcard.publicId}", + f"{self.data.config.booking_base_url}{self.data.appointment_idcard.publicId}", ) diff --git a/src/open_inwoner/cms/plugins/tests/test_appointments.py b/src/open_inwoner/cms/plugins/tests/test_appointments.py new file mode 100644 index 0000000000..556c6fe5c9 --- /dev/null +++ b/src/open_inwoner/cms/plugins/tests/test_appointments.py @@ -0,0 +1,47 @@ +from django.test import TestCase +from django.urls import reverse + +import requests_mock +from pyquery import PyQuery as PQ + +from open_inwoner.cms.tests import cms_tools +from open_inwoner.qmatic.tests.data import QmaticMockData + +from ..cms_plugins import MyAppointmentsPlugin + + +@requests_mock.Mocker() +class TestMyAppointmentsPlugin(TestCase): + def test_plugin(self, m): + data = QmaticMockData() + data.setUpMocks(m) + + html, context = cms_tools.render_plugin( + MyAppointmentsPlugin, plugin_data={}, user=data.user + ) + + appointments = context["appointments"] + + self.assertEqual(len(appointments), 2) + + self.assertIn("Aanvraag paspoort", html) + self.assertIn("Aanvraag ID kaart", html) + + pyquery = PQ(html) + + # test item + items = pyquery.find(".card-container .card") + self.assertEqual(len(items), 2) + + aanvraag_paspoort_date = PQ(items.find("p.tabled__value")[0]).text() + aanvraag_paspoort_title = PQ(items.find(".appointments__heading")[0]).text() + aanvraag_id_kaart_date = PQ(items.find("p.tabled__value")[1]).text() + aanvraag_id_kaart_title = PQ(items.find(".appointments__heading")[1]).text() + + self.assertEqual(aanvraag_paspoort_date, "1 januari 2020 om 13:00 uur") + self.assertEqual(aanvraag_paspoort_title, "Aanvraag paspoort") + self.assertEqual(aanvraag_id_kaart_date, "6 maart 2020 om 11:30 uur") + self.assertEqual(aanvraag_id_kaart_title, "Aanvraag ID kaart") + + action_url = items[0].attrib["href"] + self.assertEqual(action_url, reverse("profile:appointments")) diff --git a/src/open_inwoner/qmatic/tests/data.py b/src/open_inwoner/qmatic/tests/data.py new file mode 100644 index 0000000000..f65147c1a4 --- /dev/null +++ b/src/open_inwoner/qmatic/tests/data.py @@ -0,0 +1,72 @@ +from django.urls import reverse + +from zgw_consumers.constants import APITypes +from zgw_consumers.test.factories import ServiceFactory + +from open_inwoner.accounts.tests.factories import DigidUserFactory +from open_inwoner.qmatic.models import QmaticConfig +from open_inwoner.qmatic.tests.factories import AppointmentFactory, BranchDetailFactory + + +class QmaticMockData: + def __init__(self): + self.appointments_url = reverse("profile:appointments") + self.user = DigidUserFactory() + + self.config = QmaticConfig.get_solo() + self.config.booking_base_url = "https://qmatic.local/" + self.api_root = "https://qmatic.local/api/" + self.service = ServiceFactory.create( + api_root=self.api_root, api_type=APITypes.orc + ) + self.config.service = self.service + self.config.save() + + self.appointment_passport = AppointmentFactory.build( + title="Aanvraag paspoort", + start="2020-01-01T12:00:00+00:00", + notes="foo", + branch=BranchDetailFactory.build( + name="Hoofdkantoor", + timeZone="Europe/Amsterdam", + addressCity="Amsterdam", + addressLine1="Hoofdkantoor", + addressLine2="Dam 1", + addressZip="1234 ZZ", + ), + ) + self.appointment_idcard = AppointmentFactory.build( + title="Aanvraag ID kaart", + start="2020-03-06T16:30:00+00:00", + notes="bar", + branch=BranchDetailFactory.build( + name="Hoofdkantoor", + timeZone="America/New_York", + addressCity="New York", + addressLine1="Hoofdkantoor", + addressLine2="Wall Street 1", + addressZip="1111 AA", + ), + ) + + def setUpMocks(self, m): + data = { + "notifications": [], + "meta": { + "start": "", + "end": "", + "totalResults": 1, + "offset": None, + "limit": None, + "fields": "", + "arguments": [], + }, + "appointmentList": [ + self.appointment_passport.dict(), + self.appointment_idcard.dict(), + ], + } + m.get( + f"{self.api_root}v1/customers/externalId/{self.user.email}/appointments", + json=data, + ) From 91b2ef086f2a8e8ada6877f9c59817c3115b42eb Mon Sep 17 00:00:00 2001 From: Steven Bal Date: Thu, 28 Mar 2024 11:09:32 +0100 Subject: [PATCH 03/12] :ok_hand: [#2242] PR feedback --- .../accounts/tests/test_profile_views.py | 6 +++--- src/open_inwoner/accounts/views/__init__.py | 4 ++-- src/open_inwoner/accounts/views/profile.py | 2 +- .../cms/plugins/cms_plugins/__init__.py | 4 ++-- .../cms/plugins/cms_plugins/appointments.py | 6 +++--- ...5_myappointments.py => 0005_userappointments.py} | 4 ++-- src/open_inwoner/cms/plugins/models/__init__.py | 4 ++-- src/open_inwoner/cms/plugins/models/appointments.py | 2 +- .../cms/plugins/tests/test_appointments.py | 13 +++++++------ src/open_inwoner/cms/plugins/tests/test_userfeed.py | 2 +- src/open_inwoner/cms/profile/urls.py | 4 ++-- src/open_inwoner/conf/base.py | 2 +- .../scss/components/Appointments/Appointments.scss | 8 ++------ 13 files changed, 29 insertions(+), 32 deletions(-) rename src/open_inwoner/cms/plugins/migrations/{0005_myappointments.py => 0005_userappointments.py} (93%) diff --git a/src/open_inwoner/accounts/tests/test_profile_views.py b/src/open_inwoner/accounts/tests/test_profile_views.py index b93cf79e58..9263757594 100644 --- a/src/open_inwoner/accounts/tests/test_profile_views.py +++ b/src/open_inwoner/accounts/tests/test_profile_views.py @@ -5,7 +5,7 @@ from django.conf import settings from django.template.defaultfilters import date as django_date from django.test import override_settings -from django.urls import reverse +from django.urls import reverse, reverse_lazy from django.utils.translation import gettext as _ import requests_mock @@ -1146,8 +1146,8 @@ def test_render_form_limit_newsletters_to_admin_selection(self, m): @override_settings( ROOT_URLCONF="open_inwoner.cms.tests.urls", MIDDLEWARE=PATCHED_MIDDLEWARE ) -class MyAppointmentsTests(ClearCachesMixin, WebTest): - appointments_url = reverse("profile:appointments") +class UserAppointmentsTests(ClearCachesMixin, WebTest): + appointments_url = reverse_lazy("profile:appointments") def setUp(self): super().setUp() diff --git a/src/open_inwoner/accounts/views/__init__.py b/src/open_inwoner/accounts/views/__init__.py index 58d1826514..24428ecabd 100644 --- a/src/open_inwoner/accounts/views/__init__.py +++ b/src/open_inwoner/accounts/views/__init__.py @@ -37,12 +37,12 @@ from .password_reset import PasswordResetView from .profile import ( EditProfileView, - MyAppointmentsView, MyCategoriesView, MyDataView, MyNotificationsView, MyProfileView, NewsletterSubscribeView, + UserAppointmentsView, ) from .registration import CustomRegistrationView, NecessaryFieldsUserView @@ -81,7 +81,7 @@ "MyNotificationsView", "MyProfileView", "NewsletterSubscribeView", - "MyAppointmentsView", + "UserAppointmentsView", "CustomRegistrationView", "NecessaryFieldsUserView", "CustomEHerkenningOIDCAuthenticationCallbackView", diff --git a/src/open_inwoner/accounts/views/profile.py b/src/open_inwoner/accounts/views/profile.py index 708d6dba4f..f626243529 100644 --- a/src/open_inwoner/accounts/views/profile.py +++ b/src/open_inwoner/accounts/views/profile.py @@ -349,7 +349,7 @@ def form_valid(self, form): return HttpResponseRedirect(self.get_success_url()) -class MyAppointmentsView( +class UserAppointmentsView( LogMixin, LoginRequiredMixin, CommonPageMixin, BaseBreadcrumbMixin, TemplateView ): template_name = "pages/profile/appointments.html" diff --git a/src/open_inwoner/cms/plugins/cms_plugins/__init__.py b/src/open_inwoner/cms/plugins/cms_plugins/__init__.py index 1ee8e4713d..56da72f1c1 100644 --- a/src/open_inwoner/cms/plugins/cms_plugins/__init__.py +++ b/src/open_inwoner/cms/plugins/cms_plugins/__init__.py @@ -1,9 +1,9 @@ -from .appointments import MyAppointmentsPlugin +from .appointments import UserAppointmentsPlugin from .userfeed import UserFeedPlugin from .videoplayer import VideoPlayerPlugin __all__ = [ - "MyAppointmentsPlugin", + "UserAppointmentsPlugin", "UserFeedPlugin", "VideoPlayerPlugin", ] diff --git a/src/open_inwoner/cms/plugins/cms_plugins/appointments.py b/src/open_inwoner/cms/plugins/cms_plugins/appointments.py index e8ab65abd4..8d7570bb86 100644 --- a/src/open_inwoner/cms/plugins/cms_plugins/appointments.py +++ b/src/open_inwoner/cms/plugins/cms_plugins/appointments.py @@ -5,15 +5,15 @@ from cms.plugin_base import CMSPluginBase from cms.plugin_pool import plugin_pool -from open_inwoner.cms.plugins.models.appointments import MyAppointments +from open_inwoner.cms.plugins.models.appointments import UserAppointments from open_inwoner.qmatic.client import NoServiceConfigured, QmaticClient logger = logging.getLogger(__name__) @plugin_pool.register_plugin -class MyAppointmentsPlugin(CMSPluginBase): - model = MyAppointments +class UserAppointmentsPlugin(CMSPluginBase): + model = UserAppointments module = _("General") name = _("My appointments") render_template = "cms/plugins/appointments/appointments.html" diff --git a/src/open_inwoner/cms/plugins/migrations/0005_myappointments.py b/src/open_inwoner/cms/plugins/migrations/0005_userappointments.py similarity index 93% rename from src/open_inwoner/cms/plugins/migrations/0005_myappointments.py rename to src/open_inwoner/cms/plugins/migrations/0005_userappointments.py index 7734682544..d327a8c141 100644 --- a/src/open_inwoner/cms/plugins/migrations/0005_myappointments.py +++ b/src/open_inwoner/cms/plugins/migrations/0005_userappointments.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.10 on 2024-03-26 14:04 +# Generated by Django 4.2.10 on 2024-03-28 10:07 from django.db import migrations, models import django.db.models.deletion @@ -13,7 +13,7 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name="MyAppointments", + name="UserAppointments", fields=[ ( "cmsplugin_ptr", diff --git a/src/open_inwoner/cms/plugins/models/__init__.py b/src/open_inwoner/cms/plugins/models/__init__.py index 0f5472ab27..963a7fdf69 100644 --- a/src/open_inwoner/cms/plugins/models/__init__.py +++ b/src/open_inwoner/cms/plugins/models/__init__.py @@ -1,9 +1,9 @@ -from .appointments import MyAppointments +from .appointments import UserAppointments from .userfeed import UserFeed from .videoplayer import VideoPlayer __all__ = [ - "MyAppointments", + "UserAppointments", "UserFeed", "VideoPlayer", ] diff --git a/src/open_inwoner/cms/plugins/models/appointments.py b/src/open_inwoner/cms/plugins/models/appointments.py index 3ef53beae5..20a1680c21 100644 --- a/src/open_inwoner/cms/plugins/models/appointments.py +++ b/src/open_inwoner/cms/plugins/models/appointments.py @@ -4,7 +4,7 @@ from cms.models import CMSPlugin -class MyAppointments(CMSPlugin): +class UserAppointments(CMSPlugin): title = models.CharField( _("Title"), max_length=250, diff --git a/src/open_inwoner/cms/plugins/tests/test_appointments.py b/src/open_inwoner/cms/plugins/tests/test_appointments.py index 556c6fe5c9..1752887b44 100644 --- a/src/open_inwoner/cms/plugins/tests/test_appointments.py +++ b/src/open_inwoner/cms/plugins/tests/test_appointments.py @@ -1,4 +1,4 @@ -from django.test import TestCase +from django.test import TestCase, override_settings from django.urls import reverse import requests_mock @@ -7,17 +7,18 @@ from open_inwoner.cms.tests import cms_tools from open_inwoner.qmatic.tests.data import QmaticMockData -from ..cms_plugins import MyAppointmentsPlugin +from ..cms_plugins import UserAppointmentsPlugin @requests_mock.Mocker() -class TestMyAppointmentsPlugin(TestCase): +@override_settings(ROOT_URLCONF="open_inwoner.cms.tests.urls") +class TestUserAppointmentsPlugin(TestCase): def test_plugin(self, m): data = QmaticMockData() data.setUpMocks(m) html, context = cms_tools.render_plugin( - MyAppointmentsPlugin, plugin_data={}, user=data.user + UserAppointmentsPlugin, plugin_data={}, user=data.user ) appointments = context["appointments"] @@ -34,9 +35,9 @@ def test_plugin(self, m): self.assertEqual(len(items), 2) aanvraag_paspoort_date = PQ(items.find("p.tabled__value")[0]).text() - aanvraag_paspoort_title = PQ(items.find(".appointments__heading")[0]).text() + aanvraag_paspoort_title = PQ(items.find(".plugin-card__heading")[0]).text() aanvraag_id_kaart_date = PQ(items.find("p.tabled__value")[1]).text() - aanvraag_id_kaart_title = PQ(items.find(".appointments__heading")[1]).text() + aanvraag_id_kaart_title = PQ(items.find(".plugin-card__heading")[1]).text() self.assertEqual(aanvraag_paspoort_date, "1 januari 2020 om 13:00 uur") self.assertEqual(aanvraag_paspoort_title, "Aanvraag paspoort") diff --git a/src/open_inwoner/cms/plugins/tests/test_userfeed.py b/src/open_inwoner/cms/plugins/tests/test_userfeed.py index c38d6c704b..855291453c 100644 --- a/src/open_inwoner/cms/plugins/tests/test_userfeed.py +++ b/src/open_inwoner/cms/plugins/tests/test_userfeed.py @@ -47,7 +47,7 @@ def test_plugin(self): title = items.find("p.tabled__value").text() self.assertEqual(title, "Test message") - message = items.find(".userfeed__heading").text() + message = items.find(".plugin-card__heading").text() self.assertEqual(message, "Hello") action_url = items[0].attrib["href"] diff --git a/src/open_inwoner/cms/profile/urls.py b/src/open_inwoner/cms/profile/urls.py index ea5f33cfa4..ef5d375b7b 100644 --- a/src/open_inwoner/cms/profile/urls.py +++ b/src/open_inwoner/cms/profile/urls.py @@ -17,13 +17,13 @@ DocumentPrivateMediaView, EditProfileView, InviteAcceptView, - MyAppointmentsView, MyCategoriesView, MyDataView, MyNotificationsView, MyProfileView, NecessaryFieldsUserView, NewsletterSubscribeView, + UserAppointmentsView, ) from open_inwoner.accounts.views.actions import ActionDeleteView @@ -108,6 +108,6 @@ NewsletterSubscribeView.as_view(), name="newsletters", ), - path("appointments", MyAppointmentsView.as_view(), name="appointments"), + path("appointments", UserAppointmentsView.as_view(), name="appointments"), path("", MyProfileView.as_view(), name="detail"), ] diff --git a/src/open_inwoner/conf/base.py b/src/open_inwoner/conf/base.py index 2297220fb6..3c725f8667 100644 --- a/src/open_inwoner/conf/base.py +++ b/src/open_inwoner/conf/base.py @@ -562,7 +562,7 @@ "ProductFinderPlugin", "ProductLocationPlugin", "UserFeedPlugin", - "MyAppointmentsPlugin", + "UserAppointmentsPlugin", ], "text_only_plugins": ["LinkPlugin"], "name": _("Content"), diff --git a/src/open_inwoner/scss/components/Appointments/Appointments.scss b/src/open_inwoner/scss/components/Appointments/Appointments.scss index cc98936dbd..4f5885d698 100644 --- a/src/open_inwoner/scss/components/Appointments/Appointments.scss +++ b/src/open_inwoner/scss/components/Appointments/Appointments.scss @@ -1,7 +1,3 @@ -.appointments { - .card__body { - span.button:last-child { - bottom: var(--spacing-large) !important; - } - } +.appointments > .plugin-card > .card > .card__body > span.button:last-child { + bottom: var(--spacing-large); } From 5ed758e1513e76723441abfa2fc161356d877b2b Mon Sep 17 00:00:00 2001 From: Steven Bal Date: Fri, 29 Mar 2024 09:52:25 +0100 Subject: [PATCH 04/12] :bug: [#2242] Only try fetching appointments if user is authenticated --- src/open_inwoner/cms/plugins/cms_plugins/appointments.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/open_inwoner/cms/plugins/cms_plugins/appointments.py b/src/open_inwoner/cms/plugins/cms_plugins/appointments.py index 8d7570bb86..9dc30457d5 100644 --- a/src/open_inwoner/cms/plugins/cms_plugins/appointments.py +++ b/src/open_inwoner/cms/plugins/cms_plugins/appointments.py @@ -21,6 +21,9 @@ class UserAppointmentsPlugin(CMSPluginBase): def render(self, context, instance, placeholder): request = context["request"] # TODO email should be verified + if not request.user.is_authenticated: + return context + try: client = QmaticClient() except NoServiceConfigured: From b92e5a62bf68a4623fc86174f8b893e6482aa9df Mon Sep 17 00:00:00 2001 From: Jiro Ghianni Date: Wed, 7 Feb 2024 21:48:04 +0100 Subject: [PATCH 05/12] :lipstick: [#2253] Added modal props and variations --- .../templates/components/Action/Actions.html | 2 +- .../templates/components/Modal/Modal.html | 15 ++++ .../js/components/accessibility/help_modal.js | 15 +++- .../js/components/confirmation/index.js | 19 ++-- src/open_inwoner/js/components/index.js | 2 +- src/open_inwoner/js/components/modal/index.js | 65 ++++++++++++-- .../js/components/plan-preview/index.js | 28 ++++++ .../js/components/preview/index.js | 21 ----- .../js/components/session/index.js | 6 ++ .../scss/components/Button/Button.scss | 11 +++ .../scss/components/Plan/PlanCreate.scss | 19 ++++ .../components/PlanTemplate/PlanTemplate.scss | 4 +- .../Profile/_personal-overview.scss | 1 - .../scss/components/modal/_modal.scss | 90 +++++++++++++++++-- src/open_inwoner/templates/master.html | 13 +-- .../templates/pages/plans/create.html | 8 +- .../pages/profile/contacts/list.html | 4 +- .../templates/pages/profile/me.html | 3 +- 18 files changed, 257 insertions(+), 69 deletions(-) create mode 100644 src/open_inwoner/components/templates/components/Modal/Modal.html create mode 100644 src/open_inwoner/js/components/plan-preview/index.js delete mode 100644 src/open_inwoner/js/components/preview/index.js diff --git a/src/open_inwoner/components/templates/components/Action/Actions.html b/src/open_inwoner/components/templates/components/Action/Actions.html index 8e8fc7344c..31eefdb828 100644 --- a/src/open_inwoner/components/templates/components/Action/Actions.html +++ b/src/open_inwoner/components/templates/components/Action/Actions.html @@ -35,7 +35,7 @@
{% endif %}

+ + {% include "components/Modal/Modal.html" with cancel_icon=False modal_icons=True confirm_button=False %} {% endblock %} diff --git a/src/open_inwoner/templates/pages/profile/contacts/list.html b/src/open_inwoner/templates/pages/profile/contacts/list.html index eade812029..e86f345098 100644 --- a/src/open_inwoner/templates/pages/profile/contacts/list.html +++ b/src/open_inwoner/templates/pages/profile/contacts/list.html @@ -141,9 +141,9 @@

{% trans "U bent toegevoegd als contactpersoon" %}

{% dropdown icon="settings" disabled=contact.is_not_active %} {% enddropdown %} diff --git a/src/open_inwoner/templates/pages/profile/me.html b/src/open_inwoner/templates/pages/profile/me.html index 95c2e55c38..1e46008fae 100644 --- a/src/open_inwoner/templates/pages/profile/me.html +++ b/src/open_inwoner/templates/pages/profile/me.html @@ -294,13 +294,12 @@

{% trans "Profiel verwijderen" %}

< {% trans "Hiermee worden alleen uw persoonlijke voorkeuren verwijderd. U krijgt dan bijvoorbeeld geen e-mail meer van ons over wijzigingen van uw lopende zaken. Uw persoonsgegevens en uw lopende zaken zelf worden hiermee niet verwijderd." %}

- {% render_form form=form method="POST" id="delete-form" extra_classes="confirm" spaceless=True data_confirm_title=_("Weet u zeker dat u uw account wilt verwijderen?") data_confirm_text=_("Hiermee worden alleen uw persoonlijke voorkeuren verwijderd. U krijgt dan bijvoorbeeld geen e-mail meer van ons over wijzigingen van uw lopende zaken. Uw persoonsgegevens en uw lopende zaken zelf worden hiermee niet verwijderd.") data_confirm_cancel=_("Nee, annuleren") data_confirm_default=_("Ja, verwijderen") %} + {% render_form form=form method="POST" id="delete-form" extra_classes="confirm" spaceless=True data_confirm_title=_("Weet je zeker dat je jouw profiel wilt verwijderen?") data_confirm_text=_("Hiermee worden alleen uw persoonlijke voorkeuren verwijderd. U krijgt dan bijvoorbeeld geen e-mail meer van ons over wijzigingen van uw lopende zaken. Uw persoonsgegevens en uw lopende zaken zelf worden hiermee niet verwijderd.") data_confirm_cancel=_("Nee, annuleren") data_confirm_default=_("Ja, verwijderen") %} {% csrf_token %} {% button_row %} {% button text=_("Profiel verwijderen") icon="delete" icon_position="before" icon_outlined=True transparent=True extra_classes="button--spaceless button--error" %} {% endbutton_row %} {% endrender_form %} - {% endwith %} From 9685a2b31eb2dca640e0c29d93679549c6237c71 Mon Sep 17 00:00:00 2001 From: Jiro Ghianni Date: Tue, 26 Mar 2024 15:24:20 +0100 Subject: [PATCH 06/12] [#2253] Improved modals after PR feedback --- .../templates/components/Modal/Modal.html | 2 +- src/open_inwoner/js/components/modal/index.js | 12 ++++++++++++ .../scss/components/Plan/PlanCreate.scss | 12 ++++++++++-- src/open_inwoner/scss/components/modal/_modal.scss | 14 +++++++++++++- src/open_inwoner/templates/pages/plans/create.html | 6 +++--- 5 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/open_inwoner/components/templates/components/Modal/Modal.html b/src/open_inwoner/components/templates/components/Modal/Modal.html index fe1cb9d8ac..ebc2070b23 100644 --- a/src/open_inwoner/components/templates/components/Modal/Modal.html +++ b/src/open_inwoner/components/templates/components/Modal/Modal.html @@ -2,7 +2,7 @@