Skip to content

Commit 0290024

Browse files
author
Bart van der Schoor
committed
[#1760] Implemented User Feed app & CMS plugin
1 parent e2f4b64 commit 0290024

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1655
-66
lines changed

src/open_inwoner/cms/cases/views/status.py

+6
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555
ZaakTypeStatusTypeConfig,
5656
)
5757
from open_inwoner.openzaak.utils import get_role_name_display, is_info_object_visible
58+
from open_inwoner.userfeed.hooks.case_document import case_documents_seen
59+
from open_inwoner.userfeed.hooks.case_status import case_status_seen
5860
from open_inwoner.utils.time import has_new_elements
5961
from open_inwoner.utils.translate import TranslationLookup
6062
from open_inwoner.utils.views import CommonPageMixin, LogMixin
@@ -218,6 +220,10 @@ def get_context_data(self, **kwargs):
218220
self.case, self.resulttype_config_mapping
219221
)
220222

223+
# flag case seen in user feed
224+
case_status_seen(self.request.user, self.case)
225+
case_documents_seen(self.request.user, self.case)
226+
221227
context["case"] = {
222228
"id": str(self.case.uuid),
223229
"identification": self.case.identification,
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
from .userfeed import UserFeedPlugin
12
from .videoplayer import VideoPlayerPlugin
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
from django.utils.translation import gettext as _
2+
3+
from cms.plugin_base import CMSPluginBase
4+
from cms.plugin_pool import plugin_pool
5+
6+
from open_inwoner.cms.plugins.models.userfeed import UserFeed
7+
from open_inwoner.userfeed.feed import get_feed
8+
9+
10+
@plugin_pool.register_plugin
11+
class UserFeedPlugin(CMSPluginBase):
12+
model = UserFeed
13+
module = _("General")
14+
name = _("User Feed")
15+
render_template = "cms/plugins/userfeed/userfeed.html"
16+
17+
def render(self, context, instance, placeholder):
18+
request = context["request"]
19+
feed = get_feed(request.user)
20+
context.update(
21+
{
22+
"instance": instance,
23+
"userfeed": feed,
24+
}
25+
)
26+
return context
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Generated by Django 3.2.23 on 2024-01-05 08:26
2+
3+
import django.db.models.deletion
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("cms", "0022_auto_20180620_1551"),
11+
("plugins", "0001_initial"),
12+
]
13+
14+
operations = [
15+
migrations.CreateModel(
16+
name="UserFeed",
17+
fields=[
18+
(
19+
"cmsplugin_ptr",
20+
models.OneToOneField(
21+
auto_created=True,
22+
on_delete=django.db.models.deletion.CASCADE,
23+
parent_link=True,
24+
primary_key=True,
25+
related_name="plugins_userfeed",
26+
serialize=False,
27+
to="cms.cmsplugin",
28+
),
29+
),
30+
],
31+
options={
32+
"abstract": False,
33+
},
34+
bases=("cms.cmsplugin",),
35+
),
36+
]
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
from .userfeed import UserFeed
12
from .videoplayer import VideoPlayer
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from cms.models import CMSPlugin
2+
3+
4+
class UserFeed(CMSPlugin):
5+
# TODO add options
6+
pass
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from django.test import TestCase
2+
from django.utils.html import strip_tags
3+
from django.utils.translation import ugettext as _
4+
5+
from pyquery import PyQuery as PQ
6+
7+
from open_inwoner.accounts.tests.factories import UserFactory
8+
from open_inwoner.cms.tests import cms_tools
9+
from open_inwoner.userfeed.hooks.common import simple_message
10+
11+
from ..cms_plugins import UserFeedPlugin
12+
13+
14+
class TestUserFeedPlugin(TestCase):
15+
def test_plugin(self):
16+
user = UserFactory()
17+
simple_message(user, "Hello", title="Test message", url="http://foo.bar")
18+
19+
html, context = cms_tools.render_plugin(
20+
UserFeedPlugin, plugin_data={}, user=user
21+
)
22+
23+
feed = context["userfeed"]
24+
self.assertEqual(feed.total_items, 1)
25+
26+
self.assertIn("Test message", html)
27+
self.assertIn("Hello", html)
28+
29+
pyquery = PQ(html)
30+
31+
# test summary
32+
summaries = pyquery.find(".userfeed__summary .userfeed__list-item")
33+
self.assertEqual(len(summaries), 1)
34+
35+
summary = summaries.text()
36+
expected = _("There is {count} message").format(count=1)
37+
self.assertEqual(strip_tags(summary), expected)
38+
39+
# test item
40+
items = pyquery.find(".card-container .card")
41+
self.assertEqual(len(items), 1)
42+
43+
title = items.find("p.tabled__value").text()
44+
self.assertEqual(title, "Test message")
45+
46+
message = items.find(".userfeed__heading").text()
47+
self.assertEqual(message, "Hello")
48+
49+
action_url = items[0].attrib["href"]
50+
self.assertEqual(action_url, "http://foo.bar")

src/open_inwoner/cms/utils/page_display.py

+14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
"""Utilities for determining whether CMS pages are published"""
22

33

4+
from django.db.models import Q
5+
46
from cms.models import Page
57

68
from open_inwoner.cms.benefits.cms_apps import SSDApphook
@@ -52,3 +54,15 @@ def benefits_page_is_published() -> bool:
5254
:returns: True if the social benefits page published, False otherwise
5355
"""
5456
return _is_published("ssd")
57+
58+
59+
def get_active_app_names() -> list[str]:
60+
return list(
61+
Page.objects.published()
62+
.exclude(
63+
Q(application_urls="")
64+
| Q(application_urls__isnull=True)
65+
| Q(application_namespace="")
66+
)
67+
.values_list("application_namespace", flat=True)
68+
)

src/open_inwoner/components/templates/components/UserFeed/UserFeed.html

-58
This file was deleted.

src/open_inwoner/conf/base.py

+2
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@
216216
"open_inwoner.extended_sessions",
217217
"open_inwoner.custom_csp",
218218
"open_inwoner.media",
219+
"open_inwoner.userfeed",
219220
"open_inwoner.cms.profile",
220221
"open_inwoner.cms.cases",
221222
"open_inwoner.cms.inbox",
@@ -553,6 +554,7 @@
553554
"QuestionnairePlugin",
554555
"ProductFinderPlugin",
555556
"ProductLocationPlugin",
557+
"UserFeedPlugin",
556558
],
557559
"text_only_plugins": ["LinkPlugin"],
558560
"name": _("Content"),

src/open_inwoner/openzaak/notifications.py

+9
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
from open_inwoner.utils.logentry import system_action as log_system_action
4545
from open_inwoner.utils.url import build_absolute_url
4646

47+
from ..userfeed.hooks.case_document import case_document_added_notification_received
48+
from ..userfeed.hooks.case_status import case_status_notification_received
4749
from .models import ZaakTypeStatusTypeConfig
4850

4951
logger = logging.getLogger(__name__)
@@ -215,6 +217,9 @@ def _handle_zaakinformatieobject_notification(
215217
def handle_zaakinformatieobject_update(
216218
user: User, case: Zaak, zaak_info_object: ZaakInformatieObject
217219
):
220+
# hook into userfeed
221+
case_document_added_notification_received(user, case, zaak_info_object)
222+
218223
note = UserCaseInfoObjectNotification.objects.record_if_unique_notification(
219224
user,
220225
case.uuid,
@@ -344,6 +349,10 @@ def _handle_status_notification(notification: Notification, case: Zaak, inform_u
344349

345350

346351
def handle_status_update(user: User, case: Zaak, status: Status):
352+
# hook into userfeed
353+
case_status_notification_received(user, case, status)
354+
355+
# email notification
347356
note = UserCaseStatusNotification.objects.record_if_unique_notification(
348357
user,
349358
case.uuid,

src/open_inwoner/openzaak/tests/test_case_detail.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import datetime
2-
from unittest.mock import patch
2+
from unittest.mock import Mock, patch
33

44
from django.conf import settings
55
from django.contrib.auth.models import AnonymousUser
@@ -585,7 +585,11 @@ def _setUpMocks(self, m, use_eindstatus=True):
585585
),
586586
)
587587

588-
def test_status_is_retrieved_when_user_logged_in_via_digid(self, m):
588+
@patch("open_inwoner.cms.cases.views.status.case_status_seen")
589+
@patch("open_inwoner.cms.cases.views.status.case_documents_seen")
590+
def test_status_is_retrieved_when_user_logged_in_via_digid(
591+
self, m, mock_hook_status: Mock, mock_hook_documents: Mock
592+
):
589593
self.maxDiff = None
590594

591595
ZaakTypeStatusTypeConfigFactory.create(
@@ -663,6 +667,9 @@ def test_status_is_retrieved_when_user_logged_in_via_digid(self, m):
663667
"new_docs": False,
664668
},
665669
)
670+
# check userfeed hooks
671+
mock_hook_status.assert_called_once()
672+
mock_hook_documents.assert_called_once()
666673

667674
def test_pass_endstatus_type_data_if_endstatus_not_reached(self, m):
668675
self.maxDiff = None

src/open_inwoner/openzaak/tests/test_notification_data.py

+18
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,22 @@ def __init__(self):
124124
zaak=self.zaak["url"],
125125
)
126126

127+
self.informatie_object_extra = generate_oas_component(
128+
"drc",
129+
"schemas/EnkelvoudigInformatieObject",
130+
url=f"{DOCUMENTEN_ROOT}enkelvoudiginformatieobjecten/aaaaaaaa-0002-bbbb-aaaa-aaaaaaaaaaaa",
131+
informatieobjecttype=f"{CATALOGI_ROOT}informatieobjecttypen/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
132+
status="definitief",
133+
vertrouwelijkheidaanduiding=VertrouwelijkheidsAanduidingen.openbaar,
134+
)
135+
self.zaak_informatie_object_extra = generate_oas_component(
136+
"zrc",
137+
"schemas/ZaakInformatieObject",
138+
url=f"{ZAKEN_ROOT}zaakinformatieobjecten/aaaaaaaa-0002-aaaa-aaaa-aaaaaaaaaaaa",
139+
informatieobject=self.informatie_object_extra["url"],
140+
zaak=self.zaak["url"],
141+
)
142+
127143
self.role_initiator = generate_oas_component(
128144
"zrc",
129145
"schemas/Rol",
@@ -190,6 +206,8 @@ def install_mocks(self, m, *, res404: Optional[List[str]] = None) -> "MockAPIDat
190206
"status_type_final",
191207
"informatie_object",
192208
"zaak_informatie_object",
209+
"informatie_object_extra",
210+
"zaak_informatie_object_extra",
193211
]:
194212
resource = getattr(self, resource_attr)
195213
if resource_attr in res404:

src/open_inwoner/openzaak/tests/test_notification_zaak_infoobject.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -301,9 +301,14 @@ def test_zio_bails_when_zaak_type_info_object_type_config_is_found_not_marked_fo
301301

302302
@override_settings(ZGW_LIMIT_NOTIFICATIONS_FREQUENCY=3600)
303303
@freeze_time("2023-01-01 01:00:00")
304-
class NotificationHandlerEmailTestCase(AssertTimelineLogMixin, TestCase):
304+
class NotificationHandlerUserMessageTestCase(AssertTimelineLogMixin, TestCase):
305+
@patch(
306+
"open_inwoner.openzaak.notifications.case_document_added_notification_received"
307+
)
305308
@patch("open_inwoner.openzaak.notifications.send_case_update_email")
306-
def test_handle_zaak_info_object_update(self, mock_send: Mock):
309+
def test_handle_zaak_info_object_update(
310+
self, mock_send: Mock, mock_feed_hook: Mock
311+
):
307312
"""
308313
note this test matches with a similar test from `test_notification_zaak_status.py`
309314
"""
@@ -321,6 +326,9 @@ def test_handle_zaak_info_object_update(self, mock_send: Mock):
321326

322327
mock_send.assert_called_once()
323328

329+
# check if userfeed hook was called
330+
mock_feed_hook.assert_called_once()
331+
324332
# check call arguments
325333
args = mock_send.call_args.args
326334
self.assertEqual(args[0], user)

src/open_inwoner/openzaak/tests/test_notification_zaak_status.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -472,9 +472,10 @@ def test_status_bails_when_skip_informeren_is_set_and_zaaktypeconfig_is_not_foun
472472

473473
@override_settings(ZGW_LIMIT_NOTIFICATIONS_FREQUENCY=3600)
474474
@freeze_time("2023-01-01 01:00:00")
475-
class NotificationHandlerEmailTestCase(AssertTimelineLogMixin, TestCase):
475+
class NotificationHandlerUserMessageTestCase(AssertTimelineLogMixin, TestCase):
476+
@patch("open_inwoner.openzaak.notifications.case_status_notification_received")
476477
@patch("open_inwoner.openzaak.notifications.send_case_update_email")
477-
def test_handle_status_update(self, mock_send: Mock):
478+
def test_handle_status_update(self, mock_send: Mock, mock_feed_hook: Mock):
478479
"""
479480
note this test matches with a similar test from `test_notification_zaak_infoobject.py`
480481
"""
@@ -492,6 +493,9 @@ def test_handle_status_update(self, mock_send: Mock):
492493

493494
mock_send.assert_called_once()
494495

496+
# check if userfeed hook was called
497+
mock_feed_hook.assert_called_once()
498+
495499
# check call arguments
496500
args = mock_send.call_args.args
497501
self.assertEqual(args[0], user)

0 commit comments

Comments
 (0)