Skip to content

Commit

Permalink
[#2840] Contactmoment views should separate view from fetching logic
Browse files Browse the repository at this point in the history
  • Loading branch information
swrichards committed Oct 31, 2024
1 parent a3a9c6b commit 9775d03
Show file tree
Hide file tree
Showing 7 changed files with 543 additions and 247 deletions.
226 changes: 73 additions & 153 deletions src/open_inwoner/accounts/views/contactmoments.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import logging
from datetime import datetime
from typing import TypedDict
from typing import Iterable, Protocol

from django.contrib.auth.mixins import AccessMixin
from django.http import Http404, HttpResponseRedirect
Expand All @@ -11,30 +10,20 @@
from django.views import View
from django.views.generic import TemplateView

from glom import glom
from view_breadcrumbs import BaseBreadcrumbMixin

from open_inwoner.openklant.api_models import KlantContactMoment
from open_inwoner.openklant.clients import (
build_contactmomenten_client,
build_klanten_client,
)
from open_inwoner.openklant.constants import Status
from open_inwoner.openklant.models import (
ContactFormSubject,
KlantContactMomentAnswer,
OpenKlantConfig,
from open_inwoner.accounts.models import User
from open_inwoner.openklant.models import KlantContactMomentAnswer
from open_inwoner.openklant.services import (
KlantenService,
Question,
QuestionValidator,
ZaakWithApiGroup,
eSuiteKlantenService,
eSuiteVragenService,
)
from open_inwoner.openklant.views.contactform import ContactFormView
from open_inwoner.openklant.wrap import (
contactmoment_has_new_answer,
fetch_klantcontactmoment,
fetch_klantcontactmomenten,
get_fetch_parameters,
get_kcm_answer_mapping,
)
from open_inwoner.openzaak.clients import MultiZgwClientProxy
from open_inwoner.openzaak.models import ZGWApiGroupConfig
from open_inwoner.openklant.wrap import FetchParameters
from open_inwoner.utils.mixins import PaginationMixin
from open_inwoner.utils.views import CommonPageMixin

Expand Down Expand Up @@ -70,87 +59,52 @@ def handle_no_permission(self):
return super().handle_no_permission()


class KCMDict(TypedDict):
registered_date: datetime
channel: str
text: str
url: str
identificatie: str
type: str
onderwerp: str
antwoord: str
new_answer_available: bool
status: str
class VragenService(Protocol):
def list_questions(
self,
fetch_params: FetchParameters,
user: User,
) -> Iterable[Question]: # noqa: E704
...

def retrieve_question(
self,
fetch_params: FetchParameters,
question_uuid: str,
user: User,
) -> tuple[Question | None, ZaakWithApiGroup | None]: # noqa: E704
...

QuestionValidator = TypeAdapter(Question)
def get_fetch_parameters(
self,
request=None,
user: User | None = None,
use_vestigingsnummer: bool = False,
) -> FetchParameters | None: # noqa: E704
...


class KlantContactMomentBaseView(
CommonPageMixin, BaseBreadcrumbMixin, KlantContactMomentAccessMixin, TemplateView
):
def get_kcm_data(
self,
kcm: KlantContactMoment,
local_kcm_mapping: dict[str, KlantContactMomentAnswer] | None = None,
) -> KCMDict:
data = {
"registered_date": kcm.contactmoment.registratiedatum,
"channel": kcm.contactmoment.kanaal.title(),
"text": kcm.contactmoment.tekst,
"url": reverse("cases:contactmoment_detail", kwargs={"kcm_uuid": kcm.uuid}),
# eSuite extra
"identificatie": kcm.contactmoment.identificatie,
"type": kcm.contactmoment.type,
"status": Status.safe_label(kcm.contactmoment.status, _("Onbekend")),
"antwoord": kcm.contactmoment.antwoord,
"new_answer_available": contactmoment_has_new_answer(
kcm.contactmoment, local_kcm_mapping=local_kcm_mapping
),
}

data["onderwerp"] = self.get_kcm_subject(kcm)

return data
def get_service(self) -> VragenService:
# TODO: Refactor to support both OpenKlant2 and eSuite services at once
return eSuiteVragenService()

def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx["anchors"] = self.get_anchors()
return ctx

def get_kcm_subject(
self,
kcm: KlantContactMoment,
) -> str | None:
"""
Determine the subject (`onderwerp`) of a `KlantContactMoment.contactmoment`:
1. replace e-suite subject code with corresponding OIP configured subject or
2. return the first OIP subject if multiple subjects are mapped to the same
e-suite code or
3. return the the e-suite subject code if no mapping exists
"""
e_suite_subject_code = getattr(kcm.contactmoment, "onderwerp", "")

try:
subject = ContactFormSubject.objects.get(subject_code=e_suite_subject_code)
except ContactFormSubject.MultipleObjectsReturned as exc:
logger.warning(
"Multiple OIP subjects mapped to the same e-suite subject code for ",
"contactmoment %s; using the first one",
kcm.contactmoment.url,
exc_info=exc,
def get_fetch_params(self, service: VragenService):
if not (
fetch_params := service.get_fetch_parameters(
self.request, use_vestigingsnummer=True
)
return ContactFormSubject.objects.first().subject
except ContactFormSubject.DoesNotExist as exc:
logger.warning(
"Could not determine OIP subject for contactmoment %s; "
"falling back on e-suite subject code ('onderwerp')",
kcm.contactmoment.url,
exc_info=exc,
)
return e_suite_subject_code
):
raise ValueError("User has no bsn or kvk attributes")

return subject.subject
return fetch_params


class KlantContactMomentListView(
Expand All @@ -177,30 +131,16 @@ def get_anchors(self) -> list:

def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)

kcms = fetch_klantcontactmomenten(
**get_fetch_parameters(self.request, use_vestigingsnummer=True)
service = self.get_service()
questions = service.list_questions(
self.get_fetch_params(service), user=self.request.user
)

klant_config = OpenKlantConfig.get_solo()
if exclude_range := klant_config.exclude_contactmoment_kanalen:
kcms = [
item
for item in kcms
if glom(item, "contactmoment.kanaal") not in exclude_range
]

ctx["contactmomenten"] = [
self.get_kcm_data(
kcm,
local_kcm_mapping=get_kcm_answer_mapping(
[kcm.contactmoment for kcm in kcms], self.request.user
),
)
for kcm in kcms
QuestionValidator.validate_python(q) for q in questions
]
paginator_dict = self.paginate_with_context(ctx["contactmomenten"])
ctx.update(paginator_dict)

return ctx


Expand All @@ -222,58 +162,37 @@ def get_anchors(self) -> list:

def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
service = self.get_service()

kcm = fetch_klantcontactmoment(
kwargs["kcm_uuid"],
**get_fetch_parameters(self.request, use_vestigingsnummer=True),
kcm, zaak = service.retrieve_question(
self.get_fetch_params(service), kwargs["kcm_uuid"], user=self.request.user
)

if not kcm:
raise Http404()

local_kcm, is_created = KlantContactMomentAnswer.objects.get_or_create( # noqa
user=self.request.user, contactmoment_url=kcm.contactmoment.url
QuestionValidator.validate_python(kcm)

local_kcm, created = KlantContactMomentAnswer.objects.get_or_create( # noqa
user=self.request.user, contactmoment_url=kcm["case_detail_url"]
)
if not local_kcm.is_seen:
local_kcm.is_seen = True
local_kcm.save()

zaak = None
case_url = None
ctx["zaak"] = None
if client := build_contactmomenten_client():
ocm = client.retrieve_objectcontactmoment(kcm.contactmoment, "zaak")
if ocm and ocm.object_type == "zaak":
zaak_url = ocm.object
groups = list(ZGWApiGroupConfig.objects.all())
proxy = MultiZgwClientProxy([group.zaken_client for group in groups])
proxy_response = proxy.fetch_case_by_url_no_cache(zaak_url)
cases_found = proxy_response.truthy_responses
if (case_count := len(cases_found)) == 0:
logger.error(
"Unable to find matched contactmomenten zaak in any zgw backend"
)
else:
zaak, client = cases_found[0].result, cases_found[0].client
group = ZGWApiGroupConfig.objects.resolve_group_from_hints(
client=client
)
case_url = reverse(
"cases:case_detail",
kwargs={
"object_id": str(zaak.uuid),
"api_group_id": group.id,
},
)
ctx["zaak"] = zaak
if case_count > 1:
# In principle this should not happen, a zaak should be stored in
# exactly one backend. But: https://www.xkcd.com/2200/
# We should at least receive a ping if this happens.
logger.error("Found one zaak in multiple backends")

contactmoment: KCMDict = self.get_kcm_data(kcm)
contactmoment = kcm
ctx["contactmoment"] = contactmoment
ctx["zaak"] = zaak.zaak if zaak else None
case_url = (
reverse(
"cases:case_detail",
kwargs={
"object_id": str(zaak.zaak.uuid),
"api_group_id": zaak.api_group.id,
},
)
if zaak
else None
)
ctx["metrics"] = [
{
"label": _("Status: "),
Expand All @@ -285,7 +204,7 @@ def get_context_data(self, **kwargs):
},
{
"label": _("Vraag nummer: "),
"value": contactmoment["identificatie"],
"value": contactmoment["identification"],
},
{
"label": _("Contact gehad via: "),
Expand Down Expand Up @@ -321,11 +240,12 @@ class KlantContactMomentRedirectView(KlantContactMomentAccessMixin, View):
"""

def get(self, request, *args, **kwargs):
klanten_client = build_klanten_client()
contactmoment_client = build_contactmomenten_client()
vragen_service: VragenService = eSuiteVragenService()
klanten_service: KlantenService = eSuiteKlantenService()
fetch_params = klanten_service.get_fetch_parameters(self.request)

klant = klanten_client.retrieve_klant(**get_fetch_parameters(self.request))
kcms = contactmoment_client.retrieve_klantcontactmomenten_for_klant(klant)
klant = klanten_service.retrieve_klant(**fetch_params)
kcms = vragen_service.retrieve_klantcontactmomenten_for_klant(klant)

if not kcms:
raise Http404
Expand Down
Loading

0 comments on commit 9775d03

Please sign in to comment.