Skip to content

Commit

Permalink
Merge pull request #1410 from maykinmedia/tasks/2779-filter-cases-by-…
Browse files Browse the repository at this point in the history
…open-closed

[#2779] Only filter case list by open and closed status
  • Loading branch information
swrichards authored Sep 26, 2024
2 parents 9089716 + 0699bc6 commit 2835a28
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 116 deletions.
23 changes: 0 additions & 23 deletions src/open_inwoner/cms/cases/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,26 +79,3 @@ class CaseContactForm(forms.Form):
widget=forms.Textarea(attrs={"rows": "5"}),
required=True,
)


class CaseFilterForm(forms.Form):
def __init__(
self,
status_freqs: dict[str, int],
status_initial: list[str] | None = None,
*args,
**kwargs,
):
super().__init__(*args, **kwargs)

self.fields["status"].choices = (
(status, f"{status} ({frequency})")
for status, frequency in status_freqs.items()
)
self.fields["status"].initial = status_initial or []

status = forms.MultipleChoiceField(
label=_("Filter by status"),
widget=forms.CheckboxSelectMultiple,
choices=dict(),
)
87 changes: 46 additions & 41 deletions src/open_inwoner/cms/cases/views/cases.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import concurrent.futures
import enum
import logging
from dataclasses import dataclass

Expand All @@ -22,13 +23,15 @@
from open_inwoner.utils.mixins import PaginationMixin
from open_inwoner.utils.views import CommonPageMixin

from ..forms import CaseFilterForm
from .mixins import CaseAccessMixin, CaseLogMixin, OuterCaseAccessMixin

logger = logging.getLogger(__name__)


SUBMISSION_STATUS_OPEN = _("Openstaande aanvraag")
class CaseFilterFormOption(enum.Enum):
OPEN_SUBMISSION = _("Openstaande formulieren")
OPEN_CASE = _("Lopende aanvragen")
CLOSED_CASE = _("Afgeronde aanvragen")


@dataclass(frozen=True)
Expand Down Expand Up @@ -86,16 +89,25 @@ def get_submissions(self):

return subs

def get_case_status_frequencies(self):
@staticmethod
def get_case_filter_status(zaak: Zaak) -> CaseFilterFormOption:
if zaak.einddatum:
return CaseFilterFormOption.CLOSED_CASE

return CaseFilterFormOption.OPEN_CASE

def get_case_status_frequencies(self) -> dict[CaseFilterFormOption, int]:
cases = self.get_cases()
submissions = self.get_submissions()

case_statuses = [case.zaak.status_text for case in cases]
case_statuses = [self.get_case_filter_status(case.zaak) for case in cases]

# add static text for open submissions
case_statuses += [SUBMISSION_STATUS_OPEN for submission in submissions]
case_statuses += [CaseFilterFormOption.OPEN_SUBMISSION for _ in submissions]

return {status: case_statuses.count(status) for status in case_statuses}
return {
status: case_statuses.count(status) for status in list(CaseFilterFormOption)
}


class OuterCaseListView(
Expand Down Expand Up @@ -144,38 +156,43 @@ def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
config = OpenZaakConfig.get_solo()
case_service = CaseListService(self.request)
form = None
context["filter_form_enabled"] = config.zaken_filter_enabled

# update ctx with open submissions, cases, form for filtering
# update ctx with open submissions and cases (possibly fitered)
open_submissions: list[UniformCase] = case_service.get_submissions()
preprocessed_cases: list[UniformCase] = case_service.get_cases()

statuses = self.request.GET.getlist("status")
# GET.getlist returns [''] if no query params are passed
if config.zaken_filter_enabled and any(statuses):
form = CaseFilterForm(
status_freqs=case_service.get_case_status_frequencies(),
status_initial=statuses,
data={"status": statuses},
)

# error message to user would be unhelpful;
# we pass silently over invalid input and send a ping to Sentry
if not form.is_valid():
form.errors["status"] = []
logger.error(
"Invalid data (%s) for case filtering by %s",
self.request.GET,
self.request.user,
)
else:
if config.zaken_filter_enabled:
case_status_frequencies = case_service.get_case_status_frequencies()
# Separate frequency data from statusname
context["status_freqs"] = [
(status.value, frequency)
for status, frequency in case_status_frequencies.items()
]

# Validate statuses are valid according to the options enum
statuses: list[CaseFilterFormOption] = []
for status in self.request.GET.getlist("status"):
try:
statuses.append(CaseFilterFormOption(status))
except ValueError:
logger.error(
"Invalid data (%s) for case filtering by %s",
self.request.GET,
self.request.user,
)

# Actually filter the submissions
if statuses:
open_submissions = (
open_submissions if SUBMISSION_STATUS_OPEN in statuses else []
open_submissions
if CaseFilterFormOption.OPEN_SUBMISSION in statuses
else []
)
preprocessed_cases = [
case
for case in preprocessed_cases
if case.zaak.status_text in statuses
if case_service.get_case_filter_status(case.zaak) in statuses
]

paginator_dict = self.paginate_with_context(
Expand All @@ -188,18 +205,6 @@ def get_context_data(self, **kwargs):

self.log_access_cases(case_dicts)

if config.zaken_filter_enabled:
context["filter_form"] = form or CaseFilterForm(
status_freqs=case_service.get_case_status_frequencies(),
status_initial=statuses,
)

# Separate frequency data from statusname
context["status_freqs"] = [
(status, frequency)
for status, frequency in case_service.get_case_status_frequencies().items()
]

# other data
context["hxget"] = reverse("cases:cases_content")
context["title_text"] = config.title_text
Expand Down
91 changes: 40 additions & 51 deletions src/open_inwoner/openzaak/tests/test_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,24 @@
import random
import uuid
from unittest.mock import patch
from urllib.parse import urlencode

from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from django.test import TestCase, TransactionTestCase
from django.test import TransactionTestCase
from django.test.utils import override_settings
from django.urls import reverse_lazy
from django.utils.translation import gettext as _

import requests_mock
from django_webtest import TransactionWebTest
from furl import furl
from pyquery import PyQuery as PQ
from timeline_logger.models import TimelineLog
from zgw_consumers.api_models.constants import VertrouwelijkheidsAanduidingen

from open_inwoner.accounts.choices import LoginTypeChoices
from open_inwoner.accounts.tests.factories import UserFactory, eHerkenningUserFactory
from open_inwoner.cms.cases.forms import CaseFilterForm
from open_inwoner.cms.cases.views.cases import InnerCaseListView
from open_inwoner.cms.cases.views.cases import CaseFilterFormOption, InnerCaseListView
from open_inwoner.openzaak.tests.shared import FORMS_ROOT
from open_inwoner.utils.test import (
ClearCachesMixin,
Expand Down Expand Up @@ -606,7 +606,34 @@ def test_list_cases(self, m):
)

# case filter form is disabled by default
self.assertIsNone(response.context.get("filter_form"))
self.assertFalse(response.context.get("filter_form_enabled"))

def test_filter_widget_is_controlled_by_zaken_filter_enabled(self, m):
self.client.force_login(user=self.user)

for flag in True, False:
with self.subTest(f"zaken_filter_enabled={flag}"):
self.config.zaken_filter_enabled = flag
self.config.save()

response = self.client.get(self.inner_url, HTTP_HX_REQUEST="true")

self.assertEqual(response.context.get("filter_form_enabled"), flag)

doc = PQ(response.rendered_content)
self.assertEqual(len(doc.find("#filterBar")), 1 if flag else 0)

@staticmethod
def _encode_statuses(
status_or_statuses: CaseFilterFormOption | list[CaseFilterFormOption],
):
statuses = (
[status_or_statuses]
if isinstance(status_or_statuses, CaseFilterFormOption)
else status_or_statuses
)
parts = [urlencode({"status": status.value}) for status in statuses]
return "&".join(parts)

def test_filter_cases_simple(self, m):
for mock in self.mocks:
Expand All @@ -616,21 +643,16 @@ def test_filter_cases_simple(self, m):
self.config.save()

self.client.force_login(user=self.user)
inner_url = f"{reverse_lazy('cases:cases_content')}?status=Initial+request"
inner_url = f"{reverse_lazy('cases:cases_content')}?{self._encode_statuses(CaseFilterFormOption.OPEN_CASE)}"

response = self.client.get(inner_url, HTTP_HX_REQUEST="true")

# check filter form
filter_form = response.context["filter_form"]
self.assertTrue(filter_form.is_valid())
self.assertEqual(filter_form.cleaned_data.get("status"), ["Initial request"])

# check cases
cases = response.context["cases"]

self.assertEqual(len(cases), 4)
for case in cases:
self.assertEqual(case["current_status"], "Initial request")
self.assertIsNone(case["end_date"])

def test_filter_cases_multiple(self, m):
for mock in self.mocks:
Expand All @@ -640,26 +662,19 @@ def test_filter_cases_multiple(self, m):
self.config.save()

self.client.force_login(user=self.user)
inner_url = f"{reverse_lazy('cases:cases_content')}?status=Initial+request&status=Statustekst+finish"
filter_param = self._encode_statuses(
[CaseFilterFormOption.CLOSED_CASE, CaseFilterFormOption.OPEN_SUBMISSION]
)
inner_url = f"{reverse_lazy('cases:cases_content')}?{filter_param}"

response = self.client.get(inner_url, HTTP_HX_REQUEST="true")

# check filter form
filter_form = response.context["filter_form"]
self.assertTrue(filter_form.is_valid())
self.assertEqual(
filter_form.cleaned_data.get("status"),
["Initial request", "Statustekst finish"],
)

# check cases
cases = response.context["cases"]

self.assertEqual(len(cases), 6)
self.assertEqual(len(cases), 4)
for case in cases:
self.assertIn(
case["current_status"], ["Initial request", "Statustekst finish"]
)
self.assertIsNotNone(case["end_date"])

@set_kvk_branch_number_in_session(None)
def test_list_cases_for_eherkenning_user(self, m):
Expand Down Expand Up @@ -1125,29 +1140,3 @@ def test_case_submission(self, m):
cases[0]["datum_laatste_wijziging"].strftime("%Y-%m-%dT%H:%M:%S.%f%z"),
data.submission_2["datumLaatsteWijziging"],
)


class CaseFilterFormTest(TestCase):
def test_case_filter_form_valid(self):
form = CaseFilterForm(
status_freqs={"Start": 2, "Intermediate": 2, "Finish": 3},
data={"status": ["Start", "Intermediate"]},
)

self.assertTrue(form.is_valid())

def test_case_filter_form_errors(self):
form = CaseFilterForm(
status_freqs={"Start": 2, "Intermediate": 2, "Finish": 3},
data={"status": ["Start", "Bogus"]},
)

self.assertFalse(form.is_valid())
self.assertEqual(
form.errors,
{
"status": [
_("Selecteer een geldige keuze. Bogus is geen beschikbare keuze.")
]
},
)
2 changes: 1 addition & 1 deletion src/open_inwoner/templates/pages/cases/list_inner.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<h1 class="utrecht-heading-1" id="cases">{{ page_title }} ({{ paginator.count }})</h1>
<p class="utrecht-paragraph utrecht-paragraph--oip utrecht-paragraph--oip-title-text">{{ title_text }}</p>

{% if filter_form %}
{% if filter_form_enabled %}
<div class="filter-bar" id="filterBar">
<form class="form filter-bar__form" method="GET" id="filter-form" novalidate>
<span>{% trans 'Filter op' %}:</span>
Expand Down

0 comments on commit 2835a28

Please sign in to comment.