Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ [#2242] Add CMS plugin for mijn afspraken #1121

Merged
merged 4 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 17 additions & 77 deletions src/open_inwoner/accounts/tests/test_profile_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,14 @@
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
from cms import api
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
Expand All @@ -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
Expand Down Expand Up @@ -1149,103 +1146,46 @@ 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):
class UserAppointmentsTests(ClearCachesMixin, WebTest):
appointments_url = reverse_lazy("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)

Expand All @@ -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()
Expand All @@ -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}",
)
4 changes: 2 additions & 2 deletions src/open_inwoner/accounts/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -81,7 +81,7 @@
"MyNotificationsView",
"MyProfileView",
"NewsletterSubscribeView",
"MyAppointmentsView",
"UserAppointmentsView",
"CustomRegistrationView",
"NecessaryFieldsUserView",
"CustomEHerkenningOIDCAuthenticationCallbackView",
Expand Down
2 changes: 1 addition & 1 deletion src/open_inwoner/accounts/views/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 2 additions & 0 deletions src/open_inwoner/cms/plugins/cms_plugins/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from .appointments import UserAppointmentsPlugin
from .userfeed import UserFeedPlugin
from .videoplayer import VideoPlayerPlugin

__all__ = [
"UserAppointmentsPlugin",
"UserFeedPlugin",
"VideoPlayerPlugin",
]
41 changes: 41 additions & 0 deletions src/open_inwoner/cms/plugins/cms_plugins/appointments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
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 UserAppointments
from open_inwoner.qmatic.client import NoServiceConfigured, QmaticClient

logger = logging.getLogger(__name__)


@plugin_pool.register_plugin
class UserAppointmentsPlugin(CMSPluginBase):
model = UserAppointments
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
if not request.user.is_authenticated:
return context

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
45 changes: 45 additions & 0 deletions src/open_inwoner/cms/plugins/migrations/0005_userappointments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Generated by Django 4.2.10 on 2024-03-28 10:07

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="UserAppointments",
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",),
),
]
2 changes: 2 additions & 0 deletions src/open_inwoner/cms/plugins/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from .appointments import UserAppointments
from .userfeed import UserFeed
from .videoplayer import VideoPlayer

__all__ = [
"UserAppointments",
"UserFeed",
"VideoPlayer",
]
16 changes: 16 additions & 0 deletions src/open_inwoner/cms/plugins/models/appointments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from django.db import models
from django.utils.translation import gettext_lazy as _

from cms.models import CMSPlugin


class UserAppointments(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__()
48 changes: 48 additions & 0 deletions src/open_inwoner/cms/plugins/tests/test_appointments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from django.test import TestCase, override_settings
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 UserAppointmentsPlugin


@requests_mock.Mocker()
@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(
UserAppointmentsPlugin, 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(".plugin-card__heading")[0]).text()
aanvraag_id_kaart_date = PQ(items.find("p.tabled__value")[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")
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"))
2 changes: 1 addition & 1 deletion src/open_inwoner/cms/plugins/tests/test_userfeed.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
4 changes: 2 additions & 2 deletions src/open_inwoner/cms/profile/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@
DocumentPrivateMediaView,
EditProfileView,
InviteAcceptView,
MyAppointmentsView,
MyCategoriesView,
MyDataView,
MyNotificationsView,
MyProfileView,
NecessaryFieldsUserView,
NewsletterSubscribeView,
UserAppointmentsView,
)
from open_inwoner.accounts.views.actions import ActionDeleteView

Expand Down Expand Up @@ -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"),
]
Loading
Loading