Skip to content

Commit

Permalink
[#1799] Updated cases CMS plugin to run async with htmx
Browse files Browse the repository at this point in the history
  • Loading branch information
Bart van der Schoor committed Oct 31, 2023
1 parent 00979ac commit 24fc50b
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 28 deletions.
30 changes: 26 additions & 4 deletions src/open_inwoner/cms/cases/cms_plugins.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from django.core.exceptions import BadRequest
from django.shortcuts import render
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _

from cms.plugin_base import CMSPluginBase
Expand Down Expand Up @@ -25,27 +28,46 @@ class CasesPlugin(CMSActiveAppMixin, CMSPluginBase):
def render(self, context, instance, placeholder):
request = context["request"]
user = request.user
if check_user_auth(user, digid_required=True):
context["hxget"] = reverse("cases:cases_plugin_content")
return context

@classmethod
def render_htmx_content(cls, request):
user = request.user
context = dict()
context["page_title"] = "Page title"
context["title_text"] = "Title text"

if not check_user_auth(user, digid_required=True):
context["cases"] = None
return context

raw_cases = [case for case in fetch_cases(user.bsn) if not case.einddatum]

if not all(check_user_access_rights(user, case.url) for case in raw_cases):
context["cases"] = None
return context

# TODO
# preprocessed_cases = preprocess_data(raw_cases)

subs = fetch_open_submissions(request.user.bsn)

# replce raw_cases with preprocessed_cases
# replace raw_cases with preprocessed_cases
all_cases = raw_cases + subs

# processed_cases = [case.preprocess_data() for case in all_cases]

context["cases"] = all_cases[: self.limit]
context["cases"] = all_cases[: cls.limit]

return context

@classmethod
def as_htmx_view(cls):
# this could be generalized to a mixin
def _view(request):
if not request.htmx:
raise BadRequest("requires htmx")
context = cls.render_htmx_content(request)
return render(request, cls.render_template, context)

return _view
76 changes: 58 additions & 18 deletions src/open_inwoner/cms/cases/tests/test_plugin_cases.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from unittest.mock import patch

from django.template.loader import render_to_string
from django.test import TestCase, override_settings
from django.urls import reverse

import lxml
import requests_mock
from pyquery import PyQuery as pq

Expand All @@ -12,10 +13,20 @@
from open_inwoner.utils.test import ClearCachesMixin

from ...tests import cms_tools
from ...tests.cms_tools import get_request
from ..cms_apps import CasesApphook
from ..cms_plugins import CasesPlugin


def render_htmx_plugin(plugin_class, *, user=None) -> tuple[str, dict]:
# this could be moved to cmstools when re-used
request = get_request(user=user, htmx=True)
context = plugin_class.render_content(request)
html = render_to_string(plugin_class.render_template, context, request=request)
html = html.strip()
return html, context


@requests_mock.Mocker()
@patch.object(CasesPlugin, "limit", 2)
@override_settings(ROOT_URLCONF="open_inwoner.cms.tests.urls")
Expand All @@ -31,13 +42,8 @@ def test_cms_plugin_cases_not_rendered_for_anonymous_user(self, m):
# anonymous user
html, context = cms_tools.render_plugin(CasesPlugin)

self.assertIsNone(context["cases"])

# check that html empty
with self.assertRaises(lxml.etree.ParserError) as ctx:
pq(html)

self.assertEqual(str(ctx.exception), "Document is empty")
self.assertIsNone(context)
self.assertEqual(html, "")

def test_cms_plugin_cases_not_rendered_for_non_digid_user(self, m):
self.setUpMocks(m)
Expand All @@ -48,20 +54,25 @@ def test_cms_plugin_cases_not_rendered_for_non_digid_user(self, m):

html, context = cms_tools.render_plugin(CasesPlugin, user=user)

self.assertIsNone(context["cases"])
self.assertIsNone(context)
self.assertEqual(html, "")

# check that html empty
with self.assertRaises(lxml.etree.ParserError) as ctx:
pq(html)
def test_cms_plugin_renders_htmx_trigger(self, m):
self.setUpMocks(m)
html, context = cms_tools.render_plugin(CasesPlugin, user=self.user)

self.assertEqual(str(ctx.exception), "Document is empty")
self.assertEqual(context["hxget"], reverse("cases:cases_plugin_content"))

def test_cms_plugin_cases_rendered(self, m):
doc = pq(html)
trigger = doc.find("#spinner")
self.assertEqual(trigger.attr["hx-get"], reverse("cases:cases_plugin_content"))
self.assertEqual(trigger.attr["hx-trigger"], "load")

def test_cms_plugin_view_renders_cases(self, m):
self.setUpMocks(m)
self.setUpMocksExtra(m) # create additional zaken
self.setUpMocksExtra(m)

# the ZakenTestMixin user is a digid user
html, context = cms_tools.render_plugin(CasesPlugin, user=self.user)
html, context = render_htmx_plugin(CasesPlugin, user=self.user)

cases = context["cases"]

Expand All @@ -83,4 +94,33 @@ def test_cms_plugin_cases_rendered(self, m):
html_case_links = doc.find("a")

for html_link, path in zip(html_case_links, case_link_paths):
self.assertEqual(html_link.attrib["href"], f"/cases/{path}/status/")
# TODO reverse
self.assertEqual(html_link.attrib["href"], rf"/cases/{path}/status/")

def test_cms_plugin_view_requires_digid_and_htmx(self, m):
self.setUpMocks(m)
self.setUpMocksExtra(m)

url = reverse("cases:cases_plugin_content")

with self.subTest("anonymous"):
response = self.client.get(url)
self.assertEqual(response.status_code, 400)

with self.subTest("not bsn"):
user = UserFactory()
user.login_type = LoginTypeChoices.default
user.save()
self.client.force_login(user)
response = self.client.get(url)
self.assertEqual(response.status_code, 400)

with self.subTest("digid"):
self.client.force_login(self.user)
response = self.client.get(url)
self.assertEqual(response.status_code, 400)

with self.subTest("digid and htmx"):
self.client.force_login(self.user)
response = self.client.get(url, HTTP_HX_REQUEST="true")
self.assertEqual(response.status_code, 200)
6 changes: 6 additions & 0 deletions src/open_inwoner/cms/cases/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
KlantContactMomentListView,
)

from .cms_plugins import CasesPlugin
from .views import (
CaseContactFormView,
CaseDocumentDownloadView,
Expand All @@ -23,6 +24,11 @@
app_name = "cases"

urlpatterns = [
path(
"plugins/cases/",
CasesPlugin.as_htmx_view(),
name="cases_plugin_content",
),
path(
"closed/content/",
InnerClosedCaseListView.as_view(),
Expand Down
15 changes: 13 additions & 2 deletions src/open_inwoner/cms/tests/cms_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from cms.app_base import CMSApp
from cms.models import Placeholder
from cms.plugin_rendering import ContentRenderer
from django_htmx.middleware import HtmxDetails

from open_inwoner.cms.extensions.models import CommonExtension

Expand Down Expand Up @@ -43,12 +44,19 @@ def _init_plugin(plugin_class, plugin_data=None) -> Tuple[dict, str]:
return model_instance


def get_request(*, user=None):
request = RequestFactory()
def get_request(*, user=None, htmx=False):
headers = {}
if htmx:
headers["HX-Request"] = "true"
request = RequestFactory().get("/", headers=headers)
if user:
request.user = user
else:
request.user = AnonymousUser()
if htmx:
request.htmx = HtmxDetails(request)
else:
request.htmx = None
return request


Expand All @@ -63,6 +71,9 @@ def render_plugin(plugin_class, plugin_data=None, *, user=None) -> Tuple[str, di
renderer = ContentRenderer(request=request)
html = renderer.render_plugin(model_instance, context)

# clean up newlines from template that decides to render nothing
html = html.strip()

# let's check for output
if html:
plugin_instance = model_instance.get_plugin_class_instance()
Expand Down
2 changes: 1 addition & 1 deletion src/open_inwoner/cms/utils/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,6 @@ def check_user_auth(user, digid_required: bool = False) -> bool:

def check_user_access_rights(user, case_url) -> bool:
if not fetch_roles_for_case_and_bsn(case_url, user.bsn):
f"Permission denied: no role for the case {case_url}"
logger.debug(f"Permission denied: no role for the case {case_url}")
return False
return True
9 changes: 6 additions & 3 deletions src/open_inwoner/templates/cms/cases/cases_plugin.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
{% load i18n grid_tags icon_tags list_tags pagination_tags utils %}

{% if cases %}
{% if hxget %}
<div class="spinner" id="spinner-id" hx-get="{{ hxget }}" hx-trigger="load">
{% icon icon="rotate_right" extra_classes="spinner-icon rotate" %}
<div class="spinner__content">{% trans "Gegevens laden..." %}</div>
</div>
{% elif cases %}
<h2 class="h2" id="cases">{{ page_title }} ({{ paginator.count }})</h2>
<p class="cases__title_text">{{ title_text }}</p>
{% render_grid %}

{% for case in cases %}
{% render_column start=forloop.counter_0|multiply:4 span=4 %}
<div class="card card--compact card--stretch">
Expand Down

0 comments on commit 24fc50b

Please sign in to comment.