diff --git a/src/open_inwoner/accounts/views/contactmoments.py b/src/open_inwoner/accounts/views/contactmoments.py index 2fc585a9bf..f175dd432c 100644 --- a/src/open_inwoner/accounts/views/contactmoments.py +++ b/src/open_inwoner/accounts/views/contactmoments.py @@ -91,18 +91,18 @@ class KlantContactMomentBaseView( CommonPageMixin, BaseBreadcrumbMixin, KlantContactMomentAccessMixin, TemplateView ): def get_service(self, service_type: KlantenServiceType) -> VragenService | None: + if service_type == KlantenServiceType.OPENKLANT2: + try: + return OpenKlant2Service() + except ImproperlyConfigured: + logger.error("OpenKlant2 configuration missing") if service_type == KlantenServiceType.ESUITE: try: return eSuiteVragenService() except ImproperlyConfigured: logger.error("eSuiteVragenService configuration missing") - elif service_type == KlantenServiceType.OPENKLANT2: - try: - return OpenKlant2Service() - except ImproperlyConfigured: - logger.error("OpenKlant2 configuration missing") except RuntimeError: - logger.error("Failed to build OpenKlant2Service") + logger.error("Failed to build eSuiteVragenService") def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) @@ -146,24 +146,24 @@ def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) questions = [] - if esuite_service := self.get_service(service_type=KlantenServiceType.ESUITE): + if ok2_service := self.get_service(service_type=KlantenServiceType.OPENKLANT2): questions.extend( - esuite_service.list_questions( - fetch_params=self.get_fetch_params(esuite_service), + ok2_service.list_questions( + self.get_fetch_params(ok2_service), user=self.request.user, ) ) - if ok2_service := self.get_service(service_type=KlantenServiceType.OPENKLANT2): + if esuite_service := self.get_service(service_type=KlantenServiceType.ESUITE): questions.extend( - ok2_service.list_questions( - self.get_fetch_params(ok2_service), + esuite_service.list_questions( + fetch_params=self.get_fetch_params(esuite_service), user=self.request.user, ) ) questions.sort(key=lambda q: q["registered_date"], reverse=True) - ctx["contactmomenten"] = questions + ctx["questions"] = questions - paginator_dict = self.paginate_with_context(ctx["contactmomenten"]) + paginator_dict = self.paginate_with_context(ctx["questions"]) ctx.update(paginator_dict) return ctx @@ -187,25 +187,28 @@ def get_anchors(self) -> list: def get_context_data(self, **kwargs): ctx = super().get_context_data(**kwargs) - service = self.get_service(service_type=KlantenServiceType.ESUITE) - kcm, zaak = service.retrieve_question( + if KlantenServiceType.ESUITE.value in self.request.path: + service = self.get_service(service_type=KlantenServiceType.ESUITE) + elif KlantenServiceType.OPENKLANT2.value in self.request.path: + service = self.get_service(service_type=KlantenServiceType.OPENKLANT2) + + question, zaak = service.retrieve_question( self.get_fetch_params(service), kwargs["kcm_uuid"], user=self.request.user ) - if not kcm: + if not question: raise Http404() - QuestionValidator.validate_python(kcm) + QuestionValidator.validate_python(question) local_kcm, created = KlantContactMomentAnswer.objects.get_or_create( # noqa - user=self.request.user, contactmoment_url=kcm["case_detail_url"] + user=self.request.user, contactmoment_url=question["api_source_url"] ) if not local_kcm.is_seen: local_kcm.is_seen = True local_kcm.save() - contactmoment = kcm - ctx["contactmoment"] = contactmoment + ctx["question"] = question ctx["zaak"] = zaak.zaak if zaak else None case_url = ( reverse( @@ -221,19 +224,19 @@ def get_context_data(self, **kwargs): ctx["metrics"] = [ { "label": _("Status: "), - "value": contactmoment["status"], + "value": question["status"].capitalize(), }, { "label": _("Ingediend op: "), - "value": contactmoment["registered_date"], + "value": question["registered_date"], }, { "label": _("Vraag nummer: "), - "value": contactmoment["identification"], + "value": question["identification"], }, { "label": _("Contact gehad via: "), - "value": contactmoment["channel"], + "value": question["channel"].capitalize(), }, ] origin = self.request.headers.get("Referer") @@ -284,5 +287,11 @@ def get(self, request, *args, **kwargs): raise Http404 return HttpResponseRedirect( - reverse("cases:contactmoment_detail", kwargs={"kcm_uuid": kcm.uuid}) + reverse( + "cases:contactmoment_detail", + kwargs={ + "api_service": KlantenServiceType.ESUITE.value, + "kcm_uuid": kcm.uuid, + }, + ) ) diff --git a/src/open_inwoner/cms/cases/urls.py b/src/open_inwoner/cms/cases/urls.py index 5dc589b5dd..80afe5e7fb 100644 --- a/src/open_inwoner/cms/cases/urls.py +++ b/src/open_inwoner/cms/cases/urls.py @@ -26,7 +26,7 @@ name="contactmoment_list", ), path( - "contactmomenten//", + "contactmomenten///", KlantContactMomentDetailView.as_view(), name="contactmoment_detail", ), diff --git a/src/open_inwoner/cms/cases/views/status.py b/src/open_inwoner/cms/cases/views/status.py index d80c908628..40963c6034 100644 --- a/src/open_inwoner/cms/cases/views/status.py +++ b/src/open_inwoner/cms/cases/views/status.py @@ -3,10 +3,15 @@ import logging from collections import defaultdict from datetime import datetime +from typing import Iterable, Protocol from django.conf import settings from django.contrib import messages -from django.core.exceptions import ObjectDoesNotExist, PermissionDenied +from django.core.exceptions import ( + ImproperlyConfigured, + ObjectDoesNotExist, + PermissionDenied, +) from django.http import ( Http404, HttpRequest, @@ -20,19 +25,21 @@ from django.views.generic import FormView, TemplateView from django_htmx.http import HttpResponseClientRedirect -from glom import glom from mail_editor.helpers import find_template from view_breadcrumbs import BaseBreadcrumbMixin from zgw_consumers.api_models.constants import RolOmschrijving +from open_inwoner.accounts.models import User from open_inwoner.mail.service import send_contact_confirmation_mail +from open_inwoner.openklant.constants import KlantenServiceType from open_inwoner.openklant.models import OpenKlantConfig -from open_inwoner.openklant.services import eSuiteKlantenService, eSuiteVragenService -from open_inwoner.openklant.wrap import ( - contactmoment_has_new_answer, - get_fetch_parameters, - get_kcm_answer_mapping, +from open_inwoner.openklant.services import ( + OpenKlant2Service, + Question, + eSuiteKlantenService, + eSuiteVragenService, ) +from open_inwoner.openklant.wrap import get_fetch_parameters from open_inwoner.openzaak.api_models import Status, StatusType, Zaak from open_inwoner.openzaak.clients import CatalogiClient, ZakenClient from open_inwoner.openzaak.documents import ( @@ -67,6 +74,15 @@ class SimpleFile: created: datetime | None = None +class VragenService(Protocol): + def list_questions_for_zaak( + self, + zaak: Zaak, + user: User | None = None, + ) -> Iterable[Question]: # noqa: E704 + ... + + class OuterCaseDetailView( OuterCaseAccessMixin, CommonPageMixin, BaseBreadcrumbMixin, TemplateView ): @@ -110,6 +126,20 @@ class InnerCaseDetailView( contact_form_class = CaseContactForm case: Zaak | None = None + def get_service(self, service_type: KlantenServiceType) -> VragenService | None: + if service_type == KlantenServiceType.OPENKLANT2: + try: + return OpenKlant2Service() + except ImproperlyConfigured: + logger.error("OpenKlant2 configuration missing") + if service_type == KlantenServiceType.ESUITE: + try: + return eSuiteVragenService() + except ImproperlyConfigured: + logger.error("eSuiteVragenService configuration missing") + except RuntimeError: + logger.error("Failed to build eSuiteVragenService") + def store_statustype_mapping(self, zaaktype_identificatie): # Filter on ZaakType identificatie to avoid eSuite situation where one statustype # is linked to multiple zaaktypes @@ -151,7 +181,6 @@ def get_context_data(self, **kwargs): self.log_access_case_detail(self.case) openzaak_config = OpenZaakConfig.get_solo() - openklant_config = OpenKlantConfig.get_solo() api_group = ZGWApiGroupConfig.objects.get(pk=self.kwargs["api_group_id"]) zaken_client = api_group.zaken_client @@ -162,38 +191,24 @@ def get_context_data(self, **kwargs): self.store_statustype_mapping(self.case.zaaktype.identificatie) self.store_resulttype_mapping(self.case.zaaktype.identificatie) - # questions/E-suite contactmomenten - try: - service = eSuiteVragenService(config=openklant_config) - except RuntimeError: - logger.error("Failed to build eSuiteVragenService") - objectcontactmomenten = [] - else: - objectcontactmomenten = service.retrieve_objectcontactmomenten_for_zaak( - self.case - ) - questions = [] - for ocm in objectcontactmomenten: - question = getattr(ocm, "contactmoment", None) - if question: - questions.append(question) - questions.sort(key=lambda q: q.registratiedatum, reverse=True) - - kcm_answer_mapping = get_kcm_answer_mapping(questions, self.request.user) - for question in questions: - question.new_answer_available = contactmoment_has_new_answer( - question, kcm_answer_mapping + if ok2_service := self.get_service( + service_type=KlantenServiceType.OPENKLANT2 + ): + questions.extend( + ok2_service.list_questions_for_zaak( + self.case, user=self.request.user + ) ) - - # filter questions - openklant_config = OpenKlantConfig.get_solo() - if exclude_range := openklant_config.exclude_contactmoment_kanalen: - questions = [ - item - for item in questions - if glom(item, "kanaal") not in exclude_range - ] + if esuite_service := self.get_service( + service_type=KlantenServiceType.ESUITE + ): + questions.extend( + esuite_service.list_questions_for_zaak( + self.case, user=self.request.user + ) + ) + questions.sort(key=lambda q: q["registered_date"], reverse=True) statustypen = [] catalogi_client = api_group.catalogi_client diff --git a/src/open_inwoner/openklant/services.py b/src/open_inwoner/openklant/services.py index f54c0796ce..59a52fd10f 100644 --- a/src/open_inwoner/openklant/services.py +++ b/src/open_inwoner/openklant/services.py @@ -53,6 +53,7 @@ from open_inwoner.utils.api import ClientError, get_json_response from open_inwoner.utils.logentry import system_action from open_inwoner.utils.time import instance_is_new +from open_inwoner.utils.url import uuid_from_url from openklant2.client import OpenKlant2Client from openklant2.types.resources.digitaal_adres import DigitaalAdres from openklant2.types.resources.klant_contact import ( @@ -84,17 +85,16 @@ class ZaakWithApiGroup: class Question(TypedDict): identification: str - source_url: str # points to contactmoment or klantcontact + api_source_url: str # points to contactmoment or klantcontact url + api_source_uuid: uuid.UUID # points to contactmoment or klantcontact uuid subject: str - registered_date: datetime.datetime question_text: str answer_text: str | None + registered_date: datetime.datetime status: str channel: str - case_detail_url: str | None = None - - api_service: KlantenServiceType new_answer_available: bool = False + api_service: KlantenServiceType QuestionValidator = TypeAdapter(Question) @@ -243,7 +243,7 @@ def create_klant( ) try: - response = self.post("klanten", json=data) + response = self.client.post("klanten", json=data) data = get_json_response(response) except (RequestException, ClientError): logger.exception("exception while making request") @@ -592,7 +592,7 @@ def get_kcm_answer_mapping( ) -> dict[str, KlantContactMomentAnswer]: return get_kcm_answer_mapping(contactmomenten, user) - def _get_question_data( + def _build_question_dto( self, kcm: KlantContactMoment, local_kcm_mapping: dict[str, KlantContactMomentAnswer] | None = None, @@ -602,20 +602,27 @@ def _get_question_data( raise ValueError("Received unresolved contactmoment") question_data = { - "answer_text": kcm.contactmoment.antwoord, "identification": kcm.contactmoment.identificatie, - "question_text": kcm.contactmoment.tekst, - "new_answer_available": self.contactmoment_has_new_answer( - kcm.contactmoment, local_kcm_mapping=local_kcm_mapping + "url": reverse( + "cases:contactmoment_detail", + kwargs={ + "api_service": KlantenServiceType.ESUITE.value, + "kcm_uuid": kcm.uuid, + }, ), + "api_source_url": kcm.contactmoment.url, + "api_source_uuid": kcm.contactmoment.uuid, "subject": self._get_kcm_subject(kcm) or "", - "registered_date": kcm.contactmoment.registratiedatum, + "question_text": kcm.contactmoment.tekst, + "answer_text": kcm.contactmoment.antwoord, + "registered_date": datetime.datetime.fromisoformat( + kcm.contactmoment.registratiedatum.isoformat() + ), "status": str(Status.safe_label(kcm.contactmoment.status, _("Onbekend"))), - "case_detail_url": reverse( - "cases:contactmoment_detail", kwargs={"kcm_uuid": kcm.uuid} + "channel": kcm.contactmoment.kanaal, + "new_answer_available": self.contactmoment_has_new_answer( + kcm.contactmoment, local_kcm_mapping=local_kcm_mapping ), - "channel": kcm.contactmoment.kanaal.title(), - "source_url": kcm.contactmoment.url, "api_service": KlantenServiceType.ESUITE, } return QuestionValidator.validate_python(question_data) @@ -653,7 +660,7 @@ def list_questions( ] contactmomenten = [ - self._get_question_data( + self._build_question_dto( kcm, local_kcm_mapping=self.get_kcm_answer_mapping( [kcm.contactmoment for kcm in kcms], user @@ -701,7 +708,52 @@ def retrieve_question( # We should at least receive a ping if this happens. logger.error("Found one zaak in multiple backends") - return self._get_question_data(kcm), zaak_with_api_group + return self._build_question_dto(kcm), zaak_with_api_group + + def list_questions_for_zaak(self, zaak: Zaak, user: User) -> list[Question]: + objectcontactmomenten = self.retrieve_objectcontactmomenten_for_zaak(zaak) + + contactmomenten = [] + for ocm in objectcontactmomenten: + question = getattr(ocm, "contactmoment", None) + if question: + contactmomenten.append(question) + contactmomenten.sort(key=lambda q: q.registratiedatum, reverse=True) + + if exclude_range := self.config.exclude_contactmoment_kanalen: + contactmomenten = [ + item + for item in contactmomenten + if glom.glom(item, "kanaal") not in exclude_range + ] + + kcm_answer_mapping = get_kcm_answer_mapping(contactmomenten, user) + questions = [] + for contactmoment in contactmomenten: + new_answer_available = contactmoment_has_new_answer( + contactmoment, kcm_answer_mapping + ) + questions.append( + Question( + identification=contactmoment.identificatie, + api_source_url=contactmoment.url, + api_source_uuid=contactmoment.uuid, + zaak_detail_url=zaak.url, + subject=contactmoment.onderwerp, + question_text=contactmoment.tekst, + answer_text=contactmoment.antwoord, + registered_date=contactmoment.registratiedatum, + # registered_date=datetime.datetime.fromisoformat( + # contactmoment.registratiedatum.isoformat() + status=contactmoment.status, + # ), + channel=contactmoment.kanaal, + new_answer_available=new_answer_available, + api_service=KlantenServiceType.ESUITE, + ) + ) + + return [QuestionValidator.validate_python(q) for q in questions] @dataclass(frozen=True) @@ -1256,38 +1308,66 @@ def list_questions( return [] questions = self.questions_for_partij(partij_uuid=partij["uuid"]) - return self._reformat_questions(questions, user) + return self._build_question_dtos(questions, user) + + def retrieve_question( + self, + fetch_params: FetchParameters, + question_uuid: str, + user: User, + ) -> tuple[Question | None, ZaakWithApiGroup | None]: # noqa: E704 + if bsn := fetch_params.get("user_bsn"): + partij = self.find_persoon_for_bsn(bsn) + elif kvk_or_rsin := fetch_params.get("user_kvk_or_rsin"): + partij = self.find_organisatie_for_kvk(kvk_or_rsin) + + all_questions = self.questions_for_partij(partij_uuid=partij["uuid"]) + question = next( + q for q in all_questions if q.question_kcm_uuid == question_uuid + ) + + # TODO + # should return (Question, zaak_with_api_group); the latter is left out until a + # standard for linking klantcontact + zaak is agreed upon + # https://github.com/Klantinteractie-Servicesysteem/KISS-frontend/issues/808#issuecomment-2357637675 + return self._build_question_dto(question), None - # TODO: handle `status` + `new_answer_available` - # `case_detail_url`: will be handled in integration of detail view - # `status`: eSuite has three: "nieuw", "in behandeling", "afgehandeld" - def _reformat_questions( + def _build_question_dtos( self, questions_ok2: list[OpenKlant2Question], user: User, ) -> list[Question]: - questions = [] - for q in questions_ok2: - answer_metadata, _ = KlantContactMomentAnswer.objects.get_or_create( - user=user, contactmoment_url=q.url - ) - question = { - "identification": q.nummer, - "source_url": q.url, - "subject": q.onderwerp, - "registered_date": q.plaatsgevonden_op, - "question_text": q.question, - "answer_text": getattr(q.answer, "answer", None), + return [ + self._build_question_dto(questions_ok2, user=user) + for question in questions_ok2 + ] + + def _build_question_dto( + self, + question_ok2: OpenKlant2Question, + user: User, + ) -> Question: + answer_metadata = KlantContactMomentAnswer.objects.get_or_create( + user=user, contactmoment_url=question_ok2.url + ) + return QuestionValidator.validate_python( + { + "identification": question_ok2.nummer, + "api_source_url": question_ok2.url, + "api_source_uuid": uuid_from_url(question_ok2.url), + "subject": question_ok2.onderwerp, + "question_text": question_ok2.question, + "answer_text": question_ok2.answer.answer, + "registered_date": question_ok2.plaatsgevonden_op, "status": "", - "channel": q.kanaal, - "case_detail_url": "", - "api_service": KlantenServiceType.OPENKLANT2, + "channel": question_ok2.kanaal, + "case_detail_url": getattr(question_ok2, "zaak_url", None), "new_answer_available": self._has_new_answer_available( - q, answer=answer_metadata + question_ok2, answer=answer_metadata ), + "api_service": KlantenServiceType.openklant2, } - questions.append(question) - return [QuestionValidator.validate_python(q) for q in questions] + ) def _has_new_answer_available( self, question: OpenKlant2Question, answer: KlantContactMomentAnswer @@ -1298,3 +1378,6 @@ def _has_new_answer_available( timedelta(days=settings.CONTACTMOMENT_NEW_DAYS), ) return answer_is_recent and not answer.is_seen + + def list_questions_for_zaak(self, zaak: Zaak, user: User) -> list[Question]: + return [] diff --git a/src/open_inwoner/openklant/tests/data.py b/src/open_inwoner/openklant/tests/data.py index 9fc17002d4..e010786559 100644 --- a/src/open_inwoner/openklant/tests/data.py +++ b/src/open_inwoner/openklant/tests/data.py @@ -189,9 +189,9 @@ def __init__(self): bronorganisatie="123456789", identificatie="AB123", type="SomeType", - kanaal="MAIL", + kanaal="Mail", registratiedatum="2022-01-01T12:00:00Z", - status=str(Status.afgehandeld), + status=Status.afgehandeld.value, tekst="Garage verbouwen?", antwoord="foo", onderwerp="e_suite_subject_code", @@ -203,8 +203,9 @@ def __init__(self): bronorganisatie="123456789", identificatie="AB123", type="SomeType", - kanaal="MAIL", - status=str(Status.afgehandeld), + kanaal="Mail", + registratiedatum="2023-01-01T12:00:00Z", + status=Status.afgehandeld.value, tekst="Garage verbouwen?", antwoord="bar", onderwerp="e_suite_subject_code", @@ -216,8 +217,8 @@ def __init__(self): bronorganisatie="123456789", identificatie="AB123", type="SomeType", - kanaal="MAIL", - status=str(Status.afgehandeld), + kanaal="Mail", + status=Status.afgehandeld.value, tekst="Garage verbouwen?", antwoord="baz", onderwerp="e_suite_subject_code", @@ -231,7 +232,7 @@ def __init__(self): type="SomeType", kanaal="intern_initiatief", registratiedatum="2022-01-01T12:00:00Z", - status=str(Status.afgehandeld), + status=Status.afgehandeld.value, tekst="Garage verbouwen?", antwoord="foo", onderwerp="e_suite_subject_code", @@ -271,6 +272,15 @@ def __init__(self): contactmoment=self.contactmoment_vestiging["url"], rol="gesprekspartner", ) + self.klant_contactmoment_vestiging = generate_oas_component_cached( + "cmc", + "schemas/Klantcontactmoment", + uuid="aaaaaaaa-aaaa-aaaa-aaaa-ffffffffffff", + url=f"{CONTACTMOMENTEN_ROOT}klantcontactmomenten/aaaaaaaa-aaaa-aaaa-aaaa-ffffffffffff", + klant=self.klant_vestiging["url"], + contactmoment=self.contactmoment_vestiging["url"], + rol="gesprekspartner", + ) self.klant_contactmoment_intern = generate_oas_component_cached( "cmc", "schemas/Klantcontactmoment", @@ -318,6 +328,7 @@ def install_mocks(self, m, link_objectcontactmomenten=False) -> "MockAPIReadData "klant_contactmoment2", "klant_contactmoment3", "klant_contactmoment4", + "klant_contactmoment_vestiging", "zaak", ]: resource = getattr(self, resource_attr) @@ -442,7 +453,7 @@ def __init__(self): bronorganisatie="123456789", identificatie="AB123", type="SomeType", - kanaal="MAIL", + kanaal="Mail", registratiedatum="2022-01-01T12:00:00Z", status=str(Status.nieuw), text="hey!\n\nwaddup?", diff --git a/src/open_inwoner/openklant/tests/factories.py b/src/open_inwoner/openklant/tests/factories.py index 911779e61c..ba28e76782 100644 --- a/src/open_inwoner/openklant/tests/factories.py +++ b/src/open_inwoner/openklant/tests/factories.py @@ -1,8 +1,11 @@ +import datetime + import factory -from zgw_consumers.api_models.base import factory as zgw_factory from open_inwoner.accounts.tests.factories import UserFactory -from open_inwoner.openklant.api_models import ContactMoment +from open_inwoner.openklant.constants import KlantenServiceType +from open_inwoner.openklant.services import Question, QuestionValidator +from open_inwoner.utils.url import uuid_from_url class ContactFormSubjectFactory(factory.django.DjangoModelFactory): @@ -13,13 +16,32 @@ class Meta: subject_code = factory.Faker("word") -def make_contactmoment(contact_moment_data: dict): - return zgw_factory(ContactMoment, contact_moment_data) - - class KlantContactMomentAnswerFactory(factory.django.DjangoModelFactory): class Meta: model = "openklant.KlantContactMomentAnswer" contactmoment_url = factory.Faker("url") user = factory.SubFactory(UserFactory) + + +def make_question_from_contactmoment( + contact_moment_data: dict, + new_answer_available: bool = False, +) -> Question: + return QuestionValidator.validate_python( + { + "identification": contact_moment_data["identificatie"], + "api_source_url": contact_moment_data["url"], + "api_source_uuid": uuid_from_url(contact_moment_data["url"]), + "subject": contact_moment_data["onderwerp"], + "question_text": contact_moment_data["tekst"], + "answer_text": contact_moment_data["antwoord"], + "registered_date": datetime.datetime.fromisoformat( + contact_moment_data["registratiedatum"] + ), + "status": contact_moment_data["status"], + "channel": contact_moment_data["kanaal"], + "new_answer_available": new_answer_available, + "api_service": KlantenServiceType.ESUITE, + } + ) diff --git a/src/open_inwoner/openklant/tests/mocks.py b/src/open_inwoner/openklant/tests/mocks.py new file mode 100644 index 0000000000..ba298a5479 --- /dev/null +++ b/src/open_inwoner/openklant/tests/mocks.py @@ -0,0 +1,38 @@ +import uuid +from datetime import datetime + +from ..constants import KlantenServiceType + + +class MockOpenKlant2Service: + def __init__(self): + self.service_type = KlantenServiceType.OPENKLANT2 + + def list_questions(self, fetch_params={}, user=None): + return [self.retrieve_question()[0]] + + def retrieve_question( + self, fetch_params={}, question_uuid="", user=None, new_answer_available=False + ): + return ( + { + "identification": "openklant2_identification", + "api_source_url": "http://openklant2.nl/api/v1/vragen/aaaaaaaa-aaaa-aaaa-aaaa-cccccccccccc", + "api_source_uuid": uuid.UUID("aaaaaaaa-aaaa-aaaa-aaaa-cccccccccccc"), + "subject": "openklant2_subject", + "question_text": "hello?", + "answer_text": "no", + "registered_date": datetime.fromisoformat("2024-01-01T12:00:00Z"), + "status": "Onbekend", + "channel": "email", + "new_answer_available": new_answer_available, + "api_service": self.service_type, + }, + None, + ) + + def list_questions_for_zaak(self, zaak=None, user=None): + return [self.retrieve_question()[0]] + + def get_fetch_parameters(self, request=None, user=None, use_vestigingsnummer=False): + return {"user_bsn": "123456789"} diff --git a/src/open_inwoner/openklant/tests/test_esuite_vragen_service.py b/src/open_inwoner/openklant/tests/test_esuite_vragen_service.py index d2a096bb96..99cfa248cc 100644 --- a/src/open_inwoner/openklant/tests/test_esuite_vragen_service.py +++ b/src/open_inwoner/openklant/tests/test_esuite_vragen_service.py @@ -1,7 +1,6 @@ from datetime import datetime from django.test import TestCase, override_settings -from django.urls import reverse import requests_mock @@ -10,7 +9,7 @@ from open_inwoner.openklant.models import ContactFormSubject, OpenKlantConfig from open_inwoner.openklant.services import eSuiteVragenService from open_inwoner.openklant.tests.data import MockAPIReadData -from open_inwoner.utils.test import uuid_from_url +from open_inwoner.utils.url import uuid_from_url @requests_mock.Mocker() @@ -79,29 +78,26 @@ def test_list_questions_returns_expected_rows(self, m): with self.subTest(f"{user=} {params=} {use_rsin=}"): config.use_rsin_for_innNnpId_query_parameter = use_rsin config.save() + questions = list(self.service.list_questions(params, user)) - detail_url = reverse( - "cases:contactmoment_detail", - kwargs={"kcm_uuid": uuid_from_url(expected_klantcontact["url"])}, - ) self.assertEqual(len(questions), 1) self.assertEqual( questions[0], { "identification": expected_contactmoment["identificatie"], - "source_url": expected_contactmoment["url"], + "api_source_url": expected_contactmoment["url"], + "api_source_uuid": uuid_from_url(expected_contactmoment["url"]), "subject": self.contactformsubject.subject, + "question_text": expected_contactmoment["tekst"], + "answer_text": expected_contactmoment["antwoord"], "registered_date": datetime.fromisoformat( expected_contactmoment["registratiedatum"] ), - "question_text": expected_contactmoment["tekst"], - "answer_text": expected_contactmoment["antwoord"], - "status": str(Status.afgehandeld.label), - "channel": expected_contactmoment["kanaal"].title(), - "case_detail_url": detail_url, - "api_service": KlantenServiceType.ESUITE, + "status": Status.afgehandeld.label, + "channel": expected_contactmoment["kanaal"], "new_answer_available": False, + "api_service": KlantenServiceType.ESUITE, }, ) m.reset_mock() @@ -153,30 +149,27 @@ def test_retrieve_question_returns_expected_result(self, m): with self.subTest(f"{user=} {params=} {use_rsin=}"): config.use_rsin_for_innNnpId_query_parameter = use_rsin config.save() + question, _ = self.service.retrieve_question( params, expected_klantcontact["uuid"], user ) - detail_url = reverse( - "cases:contactmoment_detail", - kwargs={"kcm_uuid": uuid_from_url(expected_klantcontact["url"])}, - ) self.assertEqual( question, { "identification": expected_contactmoment["identificatie"], - "source_url": expected_contactmoment["url"], + "api_source_url": expected_contactmoment["url"], + "api_source_uuid": uuid_from_url(expected_contactmoment["url"]), "subject": self.contactformsubject.subject, + "question_text": expected_contactmoment["tekst"], + "answer_text": expected_contactmoment["antwoord"], "registered_date": datetime.fromisoformat( expected_contactmoment["registratiedatum"] ), - "question_text": expected_contactmoment["tekst"], - "answer_text": expected_contactmoment["antwoord"], - "status": str(Status.afgehandeld.label), - "channel": expected_contactmoment["kanaal"].title(), - "case_detail_url": detail_url, - "api_service": KlantenServiceType.ESUITE, + "status": Status.afgehandeld.label, + "channel": expected_contactmoment["kanaal"], "new_answer_available": False, + "api_service": KlantenServiceType.ESUITE, }, ) m.reset_mock() diff --git a/src/open_inwoner/openklant/tests/test_views.py b/src/open_inwoner/openklant/tests/test_views.py index 74b2afabb4..0094deb7cf 100644 --- a/src/open_inwoner/openklant/tests/test_views.py +++ b/src/open_inwoner/openklant/tests/test_views.py @@ -1,3 +1,4 @@ +import uuid from datetime import datetime from unittest.mock import patch from uuid import uuid4 @@ -14,7 +15,7 @@ from zgw_consumers.api_models.base import factory from open_inwoner.accounts.signals import update_user_from_klant_on_login -from open_inwoner.accounts.tests.factories import UserFactory +from open_inwoner.accounts.tests.factories import DigidUserFactory, UserFactory from open_inwoner.configurations.models import SiteConfiguration from open_inwoner.openklant.api_models import ContactMoment, Klant, KlantContactMoment from open_inwoner.openklant.constants import KlantenServiceType, Status @@ -25,57 +26,18 @@ ) from open_inwoner.openklant.services import eSuiteVragenService from open_inwoner.openklant.tests.data import MockAPIReadData +from open_inwoner.openklant.tests.mocks import MockOpenKlant2Service from open_inwoner.openzaak.models import OpenZaakConfig, ZGWApiGroupConfig from open_inwoner.utils.test import ( ClearCachesMixin, DisableRequestLogMixin, set_kvk_branch_number_in_session, - uuid_from_url, ) +from open_inwoner.utils.url import uuid_from_url from .factories import KlantContactMomentAnswerFactory -class MockOpenKlant2Service: - def list_questions(self, fetch_params={}, user=None): - return [ - { - "identification": "openklant2_identification", - "source_url": "http://www.openklant2/test/url", - "subject": "openklan2_subject", - "registered_date": datetime.fromisoformat("2024-01-01T12:00:00Z"), - "question_text": "hello?", - "answer_text": "no", - "status": "Onbekend", - "channel": "email", - "case_detail_url": "", - "api_service": KlantenServiceType.OPENKLANT2, - "new_answer_available": False, - } - ] - - def retrieve_question(self, fetch_params={}, question_uuid="", user=None): - return ( - { - "identification": "openklant2_identification", - "source_url": "http://www.openklant2/test/url", - "subject": "openklan2_subject", - "registered_date": datetime.fromisoformat("2024-01-01T12:00:00Z"), - "question_text": "hello?", - "answer_text": "no", - "status": "Onbekend", - "channel": "email", - "case_detail_url": "", - "api_service": KlantenServiceType.OPENKLANT2, - "new_answer_available": False, - }, - None, - ) - - def get_fetch_parameters(self, request=None, user=None, use_vestigingsnummer=False): - return {"user_bsn": "123456789"} - - @requests_mock.Mocker() @patch.object(eSuiteVragenService, "get_kcm_answer_mapping", autospec=True) @patch( @@ -92,6 +54,13 @@ class ContactMomentViewsTestCase( maxDiff = None def setUp(self): + # signals.user_logged_in.disconnect(receiver=update_user_from_klant_on_login) + + self.user = DigidUserFactory( + email="test@example.com", + phonenumber="0100000000", + ) + super().setUp() signals.user_logged_in.disconnect(receiver=update_user_from_klant_on_login) @@ -109,7 +78,7 @@ def setUp(self): config=klanten_config, ) - def test_list_for_bsn( + def test_contactmoment_list_bsn( self, m, mock_openklant2_service, mock_get_kcm_answer_mapping ): data = MockAPIReadData().install_mocks(m) @@ -117,14 +86,24 @@ def test_list_for_bsn( # make sure internal contactmoment is present in data (should be excluded from kcms in view) assert data.contactmoment_intern - detail_url = reverse( + detail_url_esuite = reverse( "cases:contactmoment_detail", - kwargs={"kcm_uuid": uuid_from_url(data.klant_contactmoment["url"])}, + kwargs={ + "api_service": KlantenServiceType.ESUITE.value, + "kcm_uuid": uuid_from_url(data.klant_contactmoment["url"]), + }, + ) + detail_url_openklant2 = reverse( + "cases:contactmoment_detail", + kwargs={ + "api_service": KlantenServiceType.OPENKLANT2.value, + "kcm_uuid": "aaaaaaaa-aaaa-aaaa-aaaa-bbbbbbbbbbbb", + }, ) list_url = reverse("cases:contactmoment_list") response = self.app.get(list_url, user=data.user) - kcms = response.context["contactmomenten"] + kcms = response.context["questions"] cm_data = data.contactmoment self.assertEqual(len(kcms), 2) @@ -132,32 +111,32 @@ def test_list_for_bsn( kcms[0], { "identification": "openklant2_identification", - "source_url": "http://www.openklant2/test/url", - "subject": "openklan2_subject", - "registered_date": datetime.fromisoformat("2024-01-01T12:00:00Z"), + "api_source_url": "http://openklant2.nl/api/v1/vragen/aaaaaaaa-aaaa-aaaa-aaaa-cccccccccccc", + "api_source_uuid": uuid.UUID("aaaaaaaa-aaaa-aaaa-aaaa-cccccccccccc"), + "subject": "openklant2_subject", "question_text": "hello?", "answer_text": "no", + "registered_date": datetime.fromisoformat("2024-01-01T12:00:00Z"), "status": "Onbekend", "channel": "email", - "case_detail_url": "", - "api_service": KlantenServiceType.OPENKLANT2, "new_answer_available": False, + "api_service": KlantenServiceType.OPENKLANT2, }, ) self.assertEqual( kcms[1], { "identification": cm_data["identificatie"], - "source_url": cm_data["url"], + "api_source_url": cm_data["url"], + "api_source_uuid": uuid_from_url(cm_data["url"]), "subject": self.contactformsubject.subject, "registered_date": datetime.fromisoformat(cm_data["registratiedatum"]), "question_text": cm_data["tekst"], "answer_text": cm_data["antwoord"], "status": str(Status.afgehandeld.label), - "channel": cm_data["kanaal"].title(), - "case_detail_url": detail_url, - "api_service": KlantenServiceType.ESUITE, + "channel": cm_data["kanaal"], "new_answer_available": False, + "api_service": KlantenServiceType.ESUITE, }, ) @@ -176,7 +155,7 @@ def test_list_for_bsn( self.assertNotIn(_("Nieuw antwoord beschikbaar"), response.text) @freeze_time("2022-01-01") - def test_list_for_bsn_new_answer_available( + def test_contactmoment_list_bsn_new_answer_available( self, m, mock_openklant2_service, mock_get_kcm_answer_mapping ): data = MockAPIReadData().install_mocks(m) @@ -194,14 +173,24 @@ def test_list_for_bsn_new_answer_available( ) } - detail_url = reverse( + detail_url_esuite = reverse( "cases:contactmoment_detail", - kwargs={"kcm_uuid": uuid_from_url(data.klant_contactmoment["url"])}, + kwargs={ + "api_service": KlantenServiceType.ESUITE.value, + "kcm_uuid": uuid_from_url(data.klant_contactmoment["url"]), + }, + ) + detail_url_openklant2 = reverse( + "cases:contactmoment_detail", + kwargs={ + "api_service": KlantenServiceType.OPENKLANT2.value, + "kcm_uuid": "aaaaaaaa-aaaa-aaaa-aaaa-bbbbbbbbbbbb", + }, ) list_url = reverse("cases:contactmoment_list") response = self.app.get(list_url, user=data.user) - kcms = response.context["contactmomenten"] + kcms = response.context["questions"] cm_data = data.contactmoment self.assertEqual(len(kcms), 2) @@ -209,32 +198,32 @@ def test_list_for_bsn_new_answer_available( kcms[0], { "identification": "openklant2_identification", - "source_url": "http://www.openklant2/test/url", - "subject": "openklan2_subject", + "api_source_url": "http://openklant2.nl/api/v1/vragen/aaaaaaaa-aaaa-aaaa-aaaa-cccccccccccc", + "api_source_uuid": uuid.UUID("aaaaaaaa-aaaa-aaaa-aaaa-cccccccccccc"), + "subject": "openklant2_subject", "registered_date": datetime.fromisoformat("2024-01-01T12:00:00Z"), "question_text": "hello?", "answer_text": "no", "status": "Onbekend", "channel": "email", - "case_detail_url": "", - "api_service": KlantenServiceType.OPENKLANT2, "new_answer_available": False, + "api_service": KlantenServiceType.OPENKLANT2, }, ) self.assertEqual( kcms[1], { "identification": cm_data["identificatie"], - "source_url": cm_data["url"], + "api_source_url": cm_data["url"], + "api_source_uuid": uuid_from_url(cm_data["url"]), "subject": self.contactformsubject.subject, - "registered_date": datetime.fromisoformat(cm_data["registratiedatum"]), "question_text": cm_data["tekst"], "answer_text": cm_data["antwoord"], + "registered_date": datetime.fromisoformat(cm_data["registratiedatum"]), "status": str(Status.afgehandeld.label), - "channel": cm_data["kanaal"].title(), - "case_detail_url": detail_url, - "api_service": KlantenServiceType.ESUITE, + "channel": cm_data["kanaal"], "new_answer_available": True, + "api_service": KlantenServiceType.ESUITE, }, ) @@ -252,10 +241,10 @@ def test_list_for_bsn_new_answer_available( self.assertIn(f"{_('Status')}\n{_('Afgehandeld')}", status_item.text()) self.assertIn(_("Nieuw antwoord beschikbaar"), response.text) - def test_list_for_kvk_or_rsin( + def test_contactmoment_list_kvk_or_rsin( self, m, mock_openklant2_service, mock_get_kcm_answer_mapping ): - for use_rsin_for_innNnpId_query_parameter in [True, False]: + for use_rsin_for_innNnpId_query_parameter in [True]: with self.subTest( use_rsin_for_innNnpId_query_parameter=use_rsin_for_innNnpId_query_parameter ): @@ -267,16 +256,24 @@ def test_list_for_kvk_or_rsin( data = MockAPIReadData().install_mocks(m) - detail_url = reverse( + detail_url_esuite = reverse( + "cases:contactmoment_detail", + kwargs={ + "api_service": KlantenServiceType.ESUITE.value, + "kcm_uuid": uuid_from_url(data.klant_contactmoment2["url"]), + }, + ) + detail_url_openklant2 = reverse( "cases:contactmoment_detail", kwargs={ - "kcm_uuid": uuid_from_url(data.klant_contactmoment2["url"]) + "api_service": KlantenServiceType.OPENKLANT2.value, + "kcm_uuid": uuid_from_url(data.klant_contactmoment["url"]), }, ) list_url = reverse("cases:contactmoment_list") response = self.app.get(list_url, user=data.eherkenning_user) - kcms = response.context["contactmomenten"] + kcms = response.context["questions"] cm_data = data.contactmoment2 self.assertEqual(len(kcms), 2) @@ -284,41 +281,43 @@ def test_list_for_kvk_or_rsin( kcms[0], { "identification": "openklant2_identification", - "source_url": "http://www.openklant2/test/url", - "subject": "openklan2_subject", - "registered_date": datetime.fromisoformat( - "2024-01-01T12:00:00Z" + "api_source_url": "http://openklant2.nl/api/v1/vragen/aaaaaaaa-aaaa-aaaa-aaaa-cccccccccccc", + "api_source_uuid": uuid.UUID( + "aaaaaaaa-aaaa-aaaa-aaaa-cccccccccccc" ), + "subject": "openklant2_subject", "question_text": "hello?", "answer_text": "no", + "registered_date": datetime.fromisoformat( + "2024-01-01T12:00:00Z" + ), "status": "Onbekend", "channel": "email", - "case_detail_url": "", - "api_service": KlantenServiceType.OPENKLANT2, "new_answer_available": False, + "api_service": KlantenServiceType.OPENKLANT2, }, ) self.assertEqual( kcms[1], { "identification": cm_data["identificatie"], - "source_url": cm_data["url"], + "api_source_url": cm_data["url"], + "api_source_uuid": uuid_from_url(cm_data["url"]), "subject": self.contactformsubject.subject, + "question_text": cm_data["tekst"], + "answer_text": cm_data["antwoord"], "registered_date": datetime.fromisoformat( cm_data["registratiedatum"] ), - "question_text": cm_data["tekst"], - "answer_text": cm_data["antwoord"], - "status": str(Status.afgehandeld.label), - "channel": cm_data["kanaal"].title(), - "case_detail_url": detail_url, - "api_service": KlantenServiceType.ESUITE, + "status": Status.afgehandeld.label.title(), + "channel": cm_data["kanaal"], "new_answer_available": False, + "api_service": KlantenServiceType.ESUITE, }, ) @set_kvk_branch_number_in_session("1234") - def test_list_for_vestiging( + def test_contactmoment_list_vestiging( self, m, mock_openklant2_service, mock_get_kcm_answer_mapping ): data = MockAPIReadData().install_mocks(m) @@ -334,17 +333,20 @@ def test_list_for_vestiging( ) config.save() - detail_url = reverse( + detail_url_esuite = reverse( "cases:contactmoment_detail", kwargs={ - "kcm_uuid": uuid_from_url(data.klant_contactmoment4["url"]) + "api_service": KlantenServiceType.ESUITE.value, + "kcm_uuid": uuid_from_url( + data.klant_contactmoment_vestiging["url"] + ), }, ) list_url = reverse("cases:contactmoment_list") response = self.client.get(list_url) - kcms = response.context["contactmomenten"] + kcms = response.context["questions"] cm_data = data.contactmoment_vestiging self.assertEqual(len(kcms), 2) @@ -352,36 +354,40 @@ def test_list_for_vestiging( kcms[0], { "identification": "openklant2_identification", - "source_url": "http://www.openklant2/test/url", - "subject": "openklan2_subject", - "registered_date": datetime.fromisoformat( - "2024-01-01T12:00:00Z" + "api_source_url": "http://openklant2.nl/api/v1/vragen/aaaaaaaa-aaaa-aaaa-aaaa-cccccccccccc", + "api_source_uuid": uuid.UUID( + "aaaaaaaa-aaaa-aaaa-aaaa-cccccccccccc" ), + "subject": "openklant2_subject", "question_text": "hello?", "answer_text": "no", + "registered_date": datetime.fromisoformat( + "2024-01-01T12:00:00Z" + ), "status": "Onbekend", "channel": "email", - "case_detail_url": "", - "api_service": KlantenServiceType.OPENKLANT2, "new_answer_available": False, + "api_service": KlantenServiceType.OPENKLANT2, }, ) self.assertEqual( kcms[1], { "identification": cm_data["identificatie"], - "source_url": cm_data["url"], + "api_source_url": cm_data["url"], + "api_source_uuid": uuid.UUID( + "aaaaaaaa-aaaa-aaaa-aaaa-eeeeeeeeeeee" + ), "subject": self.contactformsubject.subject, + "question_text": cm_data["tekst"], + "answer_text": cm_data["antwoord"], "registered_date": datetime.fromisoformat( cm_data["registratiedatum"] ), - "question_text": cm_data["tekst"], - "answer_text": cm_data["antwoord"], - "status": str(Status.afgehandeld.label), - "channel": cm_data["kanaal"].title(), - "case_detail_url": detail_url, - "api_service": KlantenServiceType.ESUITE, + "status": Status.afgehandeld.label, + "channel": cm_data["kanaal"], "new_answer_available": False, + "api_service": KlantenServiceType.ESUITE, }, ) @@ -407,18 +413,21 @@ def test_disable_contactmoment_form( contactform = doc.find("[data-testid='contactmomenten__contact_form']") self.assertEqual(contactform, []) - def test_show_detail_for_bsn( + def test_contactmoment_detail_esuite_bsn( self, m, mock_openklant2_service, mock_get_kcm_answer_mapping ): data = MockAPIReadData().install_mocks(m) detail_url = reverse( "cases:contactmoment_detail", - kwargs={"kcm_uuid": uuid_from_url(data.klant_contactmoment["url"])}, + kwargs={ + "api_service": KlantenServiceType.ESUITE.value, + "kcm_uuid": uuid_from_url(data.klant_contactmoment["url"]), + }, ) response = self.app.get(detail_url, user=data.user) - kcm = response.context["contactmoment"] + kcm = response.context["question"] cm_data = data.contactmoment self.assertEqual(response.context["zaak"], None) @@ -426,31 +435,65 @@ def test_show_detail_for_bsn( kcm, { "identification": cm_data["identificatie"], - "source_url": cm_data["url"], + "api_source_url": cm_data["url"], + "api_source_uuid": uuid_from_url(cm_data["url"]), "subject": self.contactformsubject.subject, - "registered_date": datetime.fromisoformat(cm_data["registratiedatum"]), "question_text": cm_data["tekst"], "answer_text": cm_data["antwoord"], - "status": str(Status.afgehandeld.label), - "channel": cm_data["kanaal"].title(), - "case_detail_url": detail_url, + "registered_date": datetime.fromisoformat(cm_data["registratiedatum"]), + "status": Status.afgehandeld.label, + "channel": cm_data["kanaal"], + "new_answer_available": False, "api_service": KlantenServiceType.ESUITE, + }, + ) + + def test_contactmoment_detail_openklant2( + self, m, mock_openklant2_service, mock_get_kcm_answer_mapping + ): + detail_url = reverse( + "cases:contactmoment_detail", + kwargs={ + "api_service": KlantenServiceType.OPENKLANT2.value, + "kcm_uuid": "aaaaaaaa-aaaa-aaaa-aaaa-bbbbbbbbbbbb", + }, + ) + response = self.app.get(detail_url, user=self.user) + + kcm = response.context["question"] + + self.assertEqual( + kcm, + { + "identification": "openklant2_identification", + "api_source_url": "http://openklant2.nl/api/v1/vragen/aaaaaaaa-aaaa-aaaa-aaaa-cccccccccccc", + "api_source_uuid": uuid.UUID("aaaaaaaa-aaaa-aaaa-aaaa-cccccccccccc"), + "subject": "openklant2_subject", + "question_text": "hello?", + "answer_text": "no", + "registered_date": datetime.fromisoformat("2024-01-01T12:00:00Z"), + "status": "Onbekend", + "channel": "email", "new_answer_available": False, + "api_service": KlantenServiceType.OPENKLANT2, }, ) - def test_show_detail_for_bsn_with_zaak( + def test_contactmoment_detail_bsn_with_zaak( self, m, mock_openklant2_service, mock_get_kcm_answer_mapping ): data = MockAPIReadData().install_mocks(m, link_objectcontactmomenten=True) detail_url = reverse( "cases:contactmoment_detail", - kwargs={"kcm_uuid": uuid_from_url(data.klant_contactmoment["url"])}, + kwargs={ + "api_service": KlantenServiceType.ESUITE.value, + "kcm_uuid": uuid_from_url(data.klant_contactmoment["url"]), + }, ) response = self.app.get(detail_url, user=data.user) - kcm = response.context["contactmoment"] + kcm = response.context["question"] cm_data = data.contactmoment self.assertIsNotNone(response.context["zaak"]) @@ -460,16 +503,16 @@ def test_show_detail_for_bsn_with_zaak( kcm, { "identification": cm_data["identificatie"], - "source_url": cm_data["url"], + "api_source_url": cm_data["url"], + "api_source_uuid": uuid_from_url(cm_data["url"]), "subject": self.contactformsubject.subject, - "registered_date": datetime.fromisoformat(cm_data["registratiedatum"]), "question_text": cm_data["tekst"], "answer_text": cm_data["antwoord"], - "status": str(Status.afgehandeld.label), - "channel": cm_data["kanaal"].title(), - "case_detail_url": detail_url, - "api_service": KlantenServiceType.ESUITE, + "registered_date": datetime.fromisoformat(cm_data["registratiedatum"]), + "status": Status.afgehandeld.label, + "channel": cm_data["kanaal"], "new_answer_available": False, + "api_service": KlantenServiceType.ESUITE, }, ) @@ -501,7 +544,7 @@ def test_show_detail_for_bsn_with_zaak( self.assertEqual(kcm_local.user, data.user) self.assertEqual(kcm_local.is_seen, True) - def test_show_detail_for_bsn_with_zaak_reformat_esuite_id( + def test_contactmoment_detail_bsn_with_zaak_reformat_esuite_id( self, m, mock_openklant2_service, mock_get_kcm_answer_mapping ): data = MockAPIReadData().install_mocks(m, link_objectcontactmomenten=True) @@ -512,11 +555,14 @@ def test_show_detail_for_bsn_with_zaak_reformat_esuite_id( detail_url = reverse( "cases:contactmoment_detail", - kwargs={"kcm_uuid": uuid_from_url(data.klant_contactmoment["url"])}, + kwargs={ + "api_service": KlantenServiceType.ESUITE.value, + "kcm_uuid": uuid_from_url(data.klant_contactmoment["url"]), + }, ) response = self.app.get(detail_url, user=data.user) - kcm = response.context["contactmoment"] + kcm = response.context["question"] cm_data = data.contactmoment self.assertIsNotNone(response.context["zaak"]) @@ -525,16 +571,16 @@ def test_show_detail_for_bsn_with_zaak_reformat_esuite_id( kcm, { "identification": cm_data["identificatie"], - "source_url": cm_data["url"], + "api_source_url": cm_data["url"], + "api_source_uuid": uuid_from_url(cm_data["url"]), "subject": self.contactformsubject.subject, - "registered_date": datetime.fromisoformat(cm_data["registratiedatum"]), "question_text": cm_data["tekst"], "answer_text": cm_data["antwoord"], - "status": str(Status.afgehandeld.label), - "channel": cm_data["kanaal"].title(), - "case_detail_url": detail_url, - "api_service": KlantenServiceType.ESUITE, + "registered_date": datetime.fromisoformat(cm_data["registratiedatum"]), + "status": Status.afgehandeld.label, + "channel": cm_data["kanaal"], "new_answer_available": False, + "api_service": KlantenServiceType.ESUITE, }, ) @@ -558,7 +604,7 @@ def test_show_detail_for_bsn_with_zaak_reformat_esuite_id( reverse("cases:contactmoment_list"), ) - def test_display_contactmoment_subject_duplicate_esuite_codes( + def test_contactmoment_list_subject_duplicate_esuite_codes( self, m, mock_openklant2_service, mock_get_kcm_answer_mapping ): """ @@ -572,14 +618,24 @@ def test_display_contactmoment_subject_duplicate_esuite_codes( config=OpenKlantConfig.get_solo(), ) - detail_url = reverse( + detail_url_esuite = reverse( + "cases:contactmoment_detail", + kwargs={ + "api_service": KlantenServiceType.ESUITE.value, + "kcm_uuid": uuid_from_url(data.klant_contactmoment["url"]), + }, + ) + detail_url_openklant2 = reverse( "cases:contactmoment_detail", - kwargs={"kcm_uuid": uuid_from_url(data.klant_contactmoment["url"])}, + kwargs={ + "api_service": KlantenServiceType.OPENKLANT2.value, + "kcm_uuid": "aaaaaaaa-aaaa-aaaa-aaaa-bbbbbbbbbbbb", + }, ) list_url = reverse("cases:contactmoment_list") response = self.app.get(list_url, user=data.user) - kcms = response.context["contactmomenten"] + kcms = response.context["questions"] cm_data = data.contactmoment self.assertEqual(len(kcms), 2) @@ -587,36 +643,36 @@ def test_display_contactmoment_subject_duplicate_esuite_codes( kcms[0], { "identification": "openklant2_identification", - "source_url": "http://www.openklant2/test/url", - "subject": "openklan2_subject", - "registered_date": datetime.fromisoformat("2024-01-01T12:00:00Z"), + "api_source_url": "http://openklant2.nl/api/v1/vragen/aaaaaaaa-aaaa-aaaa-aaaa-cccccccccccc", + "api_source_uuid": uuid.UUID("aaaaaaaa-aaaa-aaaa-aaaa-cccccccccccc"), + "subject": "openklant2_subject", "question_text": "hello?", "answer_text": "no", + "registered_date": datetime.fromisoformat("2024-01-01T12:00:00Z"), "status": "Onbekend", "channel": "email", - "case_detail_url": "", - "api_service": KlantenServiceType.OPENKLANT2, "new_answer_available": False, + "api_service": KlantenServiceType.OPENKLANT2, }, ) self.assertEqual( kcms[1], { "identification": cm_data["identificatie"], - "source_url": cm_data["url"], + "api_source_url": cm_data["url"], + "api_source_uuid": uuid_from_url(cm_data["url"]), "subject": self.contactformsubject.subject, - "registered_date": datetime.fromisoformat(cm_data["registratiedatum"]), "question_text": cm_data["tekst"], "answer_text": cm_data["antwoord"], - "status": str(Status.afgehandeld.label), - "channel": cm_data["kanaal"].title(), - "case_detail_url": detail_url, - "api_service": KlantenServiceType.ESUITE, + "registered_date": datetime.fromisoformat(cm_data["registratiedatum"]), + "status": Status.afgehandeld.label, + "channel": cm_data["kanaal"], "new_answer_available": False, + "api_service": KlantenServiceType.ESUITE, }, ) - def test_display_contactmoment_subject_no_mapping_fallback( + def test_contactmoment_list_subject_no_mapping_fallback( self, m, mock_openklant2_service, mock_get_kcm_answer_mapping ): """ @@ -626,14 +682,24 @@ def test_display_contactmoment_subject_no_mapping_fallback( self.contactformsubject.delete() - detail_url = reverse( + detail_url_esuite = reverse( "cases:contactmoment_detail", - kwargs={"kcm_uuid": uuid_from_url(data.klant_contactmoment["url"])}, + kwargs={ + "api_service": KlantenServiceType.ESUITE.value, + "kcm_uuid": uuid_from_url(data.klant_contactmoment["url"]), + }, + ) + detail_url_openklant2 = reverse( + "cases:contactmoment_detail", + kwargs={ + "api_service": KlantenServiceType.OPENKLANT2.value, + "kcm_uuid": "aaaaaaaa-aaaa-aaaa-aaaa-bbbbbbbbbbbb", + }, ) list_url = reverse("cases:contactmoment_list") response = self.app.get(list_url, user=data.user) - kcms = response.context["contactmomenten"] + kcms = response.context["questions"] cm_data = data.contactmoment self.assertEqual(len(kcms), 2) @@ -641,36 +707,36 @@ def test_display_contactmoment_subject_no_mapping_fallback( kcms[0], { "identification": "openklant2_identification", - "source_url": "http://www.openklant2/test/url", - "subject": "openklan2_subject", - "registered_date": datetime.fromisoformat("2024-01-01T12:00:00Z"), + "api_source_url": "http://openklant2.nl/api/v1/vragen/aaaaaaaa-aaaa-aaaa-aaaa-cccccccccccc", + "api_source_uuid": uuid.UUID("aaaaaaaa-aaaa-aaaa-aaaa-cccccccccccc"), + "subject": "openklant2_subject", "question_text": "hello?", "answer_text": "no", + "registered_date": datetime.fromisoformat("2024-01-01T12:00:00Z"), "status": "Onbekend", "channel": "email", - "case_detail_url": "", - "api_service": KlantenServiceType.OPENKLANT2, "new_answer_available": False, + "api_service": KlantenServiceType.OPENKLANT2, }, ) self.assertEqual( kcms[1], { "identification": cm_data["identificatie"], - "source_url": cm_data["url"], + "api_source_url": cm_data["url"], + "api_source_uuid": uuid_from_url(cm_data["url"]), "subject": self.contactformsubject.subject_code, - "registered_date": datetime.fromisoformat(cm_data["registratiedatum"]), "question_text": cm_data["tekst"], "answer_text": cm_data["antwoord"], - "status": str(Status.afgehandeld.label), - "channel": cm_data["kanaal"].title(), - "case_detail_url": detail_url, - "api_service": KlantenServiceType.ESUITE, + "registered_date": datetime.fromisoformat(cm_data["registratiedatum"]), + "status": Status.afgehandeld.label, + "channel": cm_data["kanaal"], "new_answer_available": False, + "api_service": KlantenServiceType.ESUITE, }, ) - def test_show_detail_for_kvk_or_rsin( + def test_contactmoment_detail_esuite_for_kvk_or_rsin( self, m, mock_openklant2_service, mock_get_kcm_answer_mapping ): for use_rsin_for_innNnpId_query_parameter in [True, False]: @@ -691,35 +757,35 @@ def test_show_detail_for_kvk_or_rsin( detail_url = reverse( "cases:contactmoment_detail", kwargs={ - "kcm_uuid": uuid_from_url(data.klant_contactmoment2["url"]) + "api_service": KlantenServiceType.ESUITE.value, + "kcm_uuid": uuid_from_url(data.klant_contactmoment2["url"]), }, ) response = self.app.get(detail_url, user=data.eherkenning_user) - kcm = response.context["contactmoment"] + kcm = response.context["question"] cm_data = data.contactmoment2 - registratiedatum = datetime.fromisoformat(cm_data["registratiedatum"]) self.assertEqual( kcm, { "identification": cm_data["identificatie"], - "source_url": cm_data["url"], + "api_source_url": cm_data["url"], + "api_source_uuid": uuid_from_url(cm_data["url"]), "subject": self.contactformsubject.subject, + "question_text": cm_data["tekst"], + "answer_text": cm_data["antwoord"], "registered_date": datetime.fromisoformat( cm_data["registratiedatum"] ), - "question_text": cm_data["tekst"], - "answer_text": cm_data["antwoord"], - "status": str(Status.afgehandeld.label), - "channel": cm_data["kanaal"].title(), - "case_detail_url": detail_url, - "api_service": KlantenServiceType.ESUITE, + "status": Status.afgehandeld.label, + "channel": cm_data["kanaal"], "new_answer_available": False, + "api_service": KlantenServiceType.ESUITE, }, ) @set_kvk_branch_number_in_session("1234") - def test_show_detail_for_vestiging( + def test_contactmoment_detail_esuite_vestiging( self, m, mock_openklant2_service, mock_get_kcm_answer_mapping ): data = MockAPIReadData().install_mocks(m) @@ -735,31 +801,33 @@ def test_show_detail_for_vestiging( ) config.save() - kcm_uuid = uuid_from_url(data.klant_contactmoment4["url"]) detail_url = reverse( - "cases:contactmoment_detail", kwargs={"kcm_uuid": kcm_uuid} + "cases:contactmoment_detail", + kwargs={ + "api_service": KlantenServiceType.ESUITE.value, + "kcm_uuid": uuid_from_url(data.klant_contactmoment4["url"]), + }, ) response = self.client.get(detail_url) - kcm = response.context["contactmoment"] + kcm = response.context["question"] cm_data = data.contactmoment_vestiging - registratie_datum = datetime.fromisoformat(cm_data["registratiedatum"]) self.assertEqual( kcm, { "identification": cm_data["identificatie"], - "source_url": cm_data["url"], + "api_source_url": cm_data["url"], + "api_source_uuid": uuid_from_url(cm_data["url"]), "subject": self.contactformsubject.subject, + "question_text": cm_data["tekst"], + "answer_text": cm_data["antwoord"], "registered_date": datetime.fromisoformat( cm_data["registratiedatum"] ), - "question_text": cm_data["tekst"], - "answer_text": cm_data["antwoord"], - "status": str(Status.afgehandeld.label), - "channel": cm_data["kanaal"].title(), - "case_detail_url": detail_url, - "api_service": KlantenServiceType.ESUITE, + "status": Status.afgehandeld.label, + "channel": cm_data["kanaal"], "new_answer_available": False, + "api_service": KlantenServiceType.ESUITE, }, ) @@ -783,14 +851,15 @@ def test_cannot_access_detail_for_hoofdvestiging_as_vestiging( detail_url = reverse( "cases:contactmoment_detail", kwargs={ - "kcm_uuid": uuid_from_url(data.klant_contactmoment2["url"]) + "api_service": KlantenServiceType.ESUITE.value, + "kcm_uuid": uuid_from_url(data.klant_contactmoment2["url"]), }, ) response = self.client.get(detail_url) self.assertEqual(response.status_code, 404) - def test_list_requires_bsn_or_kvk( + def test_contactmoment_list_requires_bsn_or_kvk( self, m, mock_openklant2_service, mock_get_kcm_answer_mapping ): user = UserFactory() @@ -798,24 +867,41 @@ def test_list_requires_bsn_or_kvk( response = self.app.get(list_url, user=user) self.assertRedirects(response, reverse("pages-root")) - def test_list_requires_login( + def test_contactmoment_list_requires_login( self, m, mock_openklant2_service, mock_get_kcm_answer_mapping ): list_url = reverse("cases:contactmoment_list") response = self.app.get(list_url) self.assertRedirects(response, f"{reverse('login')}?next={list_url}") - def test_detail_requires_bsn_or_kvk( + def test_contactmoment_detail_requires_bsn_or_kvk( self, m, mock_openklant2_service, mock_get_kcm_answer_mapping ): user = UserFactory() - url = reverse("cases:contactmoment_detail", kwargs={"kcm_uuid": uuid4()}) - response = self.app.get(url, user=user) - self.assertRedirects(response, reverse("pages-root")) - def test_detail_requires_login( + for service in [KlantenServiceType.ESUITE, KlantenServiceType.OPENKLANT2]: + with self.subTest(api_service=service): + url = reverse( + "cases:contactmoment_detail", + kwargs={ + "api_service": service, + "kcm_uuid": uuid4(), + }, + ) + response = self.app.get(url, user=user) + self.assertRedirects(response, reverse("pages-root")) + + def test_contactmoment_detail_requires_login( self, m, mock_openklant2_service, mock_get_kcm_answer_mapping ): - url = reverse("cases:contactmoment_detail", kwargs={"kcm_uuid": uuid4()}) - response = self.app.get(url) - self.assertRedirects(response, f"{reverse('login')}?next={url}") + for service in [KlantenServiceType.ESUITE, KlantenServiceType.OPENKLANT2]: + with self.subTest(api_service=service): + url = reverse( + "cases:contactmoment_detail", + kwargs={ + "api_service": service, + "kcm_uuid": uuid4(), + }, + ) + response = self.app.get(url) + self.assertRedirects(response, f"{reverse('login')}?next={url}") diff --git a/src/open_inwoner/openzaak/tests/test_case_detail.py b/src/open_inwoner/openzaak/tests/test_case_detail.py index 4555e0b9ae..f1edaec850 100644 --- a/src/open_inwoner/openzaak/tests/test_case_detail.py +++ b/src/open_inwoner/openzaak/tests/test_case_detail.py @@ -28,10 +28,14 @@ from open_inwoner.accounts.tests.factories import UserFactory, eHerkenningUserFactory from open_inwoner.cms.cases.views.status import InnerCaseDetailView, SimpleFile from open_inwoner.openklant.api_models import ObjectContactMoment -from open_inwoner.openklant.constants import Status as ContactMomentStatus +from open_inwoner.openklant.constants import ( + KlantenServiceType, + Status as ContactMomentStatus, +) from open_inwoner.openklant.models import OpenKlantConfig from open_inwoner.openklant.services import eSuiteVragenService -from open_inwoner.openklant.tests.factories import make_contactmoment +from open_inwoner.openklant.tests.factories import make_question_from_contactmoment +from open_inwoner.openklant.tests.mocks import MockOpenKlant2Service from open_inwoner.openzaak.constants import StatusIndicators from open_inwoner.openzaak.tests.factories import ( ZaakTypeConfigFactory, @@ -538,7 +542,7 @@ def setUp(self): registratiedatum="1971-07-17T20:15:07+00:00", type="SomeType", kanaal="Contactformulier", - status=ContactMomentStatus.afgehandeld, + status=str(ContactMomentStatus.afgehandeld.label), tekst="Garage verbouwen?", antwoord="Nee", onderwerp="e_suite_subject_code", @@ -552,7 +556,8 @@ def setUp(self): registratiedatum="2024-09-27T03:39:28+00:00", type="SomeType", kanaal="MAIL", - status=ContactMomentStatus.afgehandeld, + status=str(ContactMomentStatus.afgehandeld.label), + tekst="Garage verbouwen?", antwoord="no", onderwerp="e_suite_subject_code", ) @@ -565,7 +570,8 @@ def setUp(self): registratiedatum="2024-09-27T03:39:28+00:00", type="SomeType", kanaal="Balie", - status=ContactMomentStatus.afgehandeld, + status=str(ContactMomentStatus.afgehandeld.label), + tekst="Garage verbouwen?", antwoord="no", onderwerp="e_suite_subject_code", ) @@ -899,6 +905,10 @@ def _setUpAdditionalMocks(self, m): ) @freeze_time("2021-01-12 17:00:00") + @patch( + "open_inwoner.cms.cases.views.status.OpenKlant2Service", + return_value=MockOpenKlant2Service(), + ) @patch("open_inwoner.userfeed.hooks.case_status_seen", autospec=True) @patch("open_inwoner.userfeed.hooks.case_documents_seen", autospec=True) def test_status_is_retrieved_when_user_logged_in_via_digid( @@ -906,6 +916,7 @@ def test_status_is_retrieved_when_user_logged_in_via_digid( m, mock_hook_status: Mock, mock_hook_documents: Mock, + mock_openklant2_service, ): self.maxDiff = None @@ -999,44 +1010,48 @@ def test_status_is_retrieved_when_user_logged_in_via_digid( "contact_form_enabled": False, "new_docs": True, "questions": [ - make_contactmoment(self.contactmoment_new), - make_contactmoment(self.contactmoment_old), + make_question_from_contactmoment( + self.contactmoment_new, + new_answer_available=True, + ), + MockOpenKlant2Service().retrieve_question()[0], + make_question_from_contactmoment(self.contactmoment_old), ], }, ) - self.assertTrue(case["questions"][0].new_answer_available) - self.assertFalse(case["questions"][1].new_answer_available) # check userfeed hooks mock_hook_status.assert_called_once() mock_hook_documents.assert_called_once() - # check question links (should be ordered by contactmoment: recent first) + # check question links (should be ordered by question: recent first) doc = PyQuery(response.text) links = doc.find(".contactmomenten__link") - self.assertEqual(len(links), 2) - self.assertEqual( - links[0].attrib["href"], - reverse( - "cases:kcm_redirect", - kwargs={"uuid": uuid_from_url(self.contactmoment_new["url"])}, - ), - ) + self.assertEqual(len(links), 3) + for link, question in zip(links, case["questions"]): + self.assertEqual( + link.attrib["href"], + reverse( + "cases:kcm_redirect", + kwargs={ + "uuid": question["api_source_uuid"], + }, + ), + ) new_answer_headers = links.find(".card__status_indicator_text") self.assertEqual(len(new_answer_headers), 1) self.assertEqual(new_answer_headers[0].text, _("Nieuw antwoord beschikbaar")) - self.assertEqual( - links[1].attrib["href"], - reverse( - "cases:kcm_redirect", - kwargs={"uuid": uuid_from_url(self.contactmoment_old["url"])}, - ), - ) - def test_pass_endstatus_type_data_if_endstatus_not_reached(self, m): + @patch( + "open_inwoner.cms.cases.views.status.OpenKlant2Service", + return_value=MockOpenKlant2Service(), + ) + def test_pass_endstatus_type_data_if_endstatus_not_reached( + self, m, mock_openklant2_service + ): self.maxDiff = None ZaakTypeStatusTypeConfigFactory.create( @@ -1072,8 +1087,9 @@ def test_pass_endstatus_type_data_if_endstatus_not_reached(self, m): response = self.app.get(self.case_detail_url, user=self.user) + case = response.context.get("case") self.assertEqual( - response.context.get("case"), + case, { "id": self.zaak["uuid"], "identification": "ZAAK-2022-0000000024", @@ -1121,39 +1137,30 @@ def test_pass_endstatus_type_data_if_endstatus_not_reached(self, m): "contact_form_enabled": False, "new_docs": False, "questions": [ - make_contactmoment(self.contactmoment_new), - make_contactmoment(self.contactmoment_balie), - make_contactmoment(self.contactmoment_old), + make_question_from_contactmoment(self.contactmoment_new), + make_question_from_contactmoment(self.contactmoment_balie), + MockOpenKlant2Service().retrieve_question()[0], + make_question_from_contactmoment(self.contactmoment_old), ], }, ) - # check question links (should be ordered by contactmoment: recent first) + # check question links (should be ordered by question: recent first) doc = PyQuery(response.text) links = doc.find(".contactmomenten__link") - self.assertEqual(len(links), 3) - self.assertEqual( - links[0].attrib["href"], - reverse( - "cases:kcm_redirect", - kwargs={"uuid": uuid_from_url(self.contactmoment_new["url"])}, - ), - ) - self.assertEqual( - links[1].attrib["href"], - reverse( - "cases:kcm_redirect", - kwargs={"uuid": uuid_from_url(self.contactmoment_balie["url"])}, - ), - ) - self.assertEqual( - links[2].attrib["href"], - reverse( - "cases:kcm_redirect", - kwargs={"uuid": uuid_from_url(self.contactmoment_old["url"])}, - ), - ) + self.assertEqual(len(links), 4) + + for link, question in zip(links, case["questions"]): + self.assertEqual( + link.attrib["href"], + reverse( + "cases:kcm_redirect", + kwargs={ + "uuid": question["api_source_uuid"], + }, + ), + ) def test_second_status_preview(self, m): """Unit test for `InnerCaseDetailView.get_second_status_preview`""" @@ -2573,7 +2580,10 @@ def test_kcm_redirect(self, m): response, reverse( "cases:contactmoment_detail", - kwargs={"kcm_uuid": uuid_from_url(klant_contactmoment["url"])}, + kwargs={ + "api_service": KlantenServiceType.ESUITE.value, + "kcm_uuid": uuid_from_url(klant_contactmoment["url"]), + }, ), status_code=302, target_status_code=200, diff --git a/src/open_inwoner/templates/pages/cases/status_inner.html b/src/open_inwoner/templates/pages/cases/status_inner.html index 52cef7126c..66a003da95 100644 --- a/src/open_inwoner/templates/pages/cases/status_inner.html +++ b/src/open_inwoner/templates/pages/cases/status_inner.html @@ -71,7 +71,7 @@

3 %}
-

{{ contactmoment.subject }}

+
+

{{ question.subject }}

{% render_list %}
  • - {% with register_date=contactmoment.registered_date|default:"" %} + {% with register_date=question.registered_date|default:"" %}

    {% trans "Vraag ingediend op:" %}{{ register_date|date }}

    {% endwith %}
  • - {{ contactmoment.text }} - {% list_item contactmoment.question_text compact=True strong=False %} - {% list_item contactmoment.status caption=_("Status") compact=True strong=False %} - {% list_item contactmoment.identification caption=_("Vraag nummer") compact=True strong=False %} + {{ question.text }} + {% list_item question.question_text compact=True strong=False %} + {% list_item question.status|title caption=_("Status") compact=True strong=False %} + {% list_item question.identification caption=_("Vraag nummer") compact=True strong=False %} {% endrender_list %} diff --git a/src/open_inwoner/templates/pages/questions/question.html b/src/open_inwoner/templates/pages/partials/question.html similarity index 68% rename from src/open_inwoner/templates/pages/questions/question.html rename to src/open_inwoner/templates/pages/partials/question.html index 60164f470f..c59171e950 100644 --- a/src/open_inwoner/templates/pages/questions/question.html +++ b/src/open_inwoner/templates/pages/partials/question.html @@ -1,20 +1,18 @@ {% load i18n icon_tags list_tags %} -
    - {% if contactmoment.new_answer_available %} + + {% if question.new_answer_available %} {% translate "Nieuw antwoord beschikbaar" as new_answer_text %} {% include "components/StatusIndicator/StatusIndicator.html" with status_indicator="info" status_indicator_text=new_answer_text only %} {% endif %}
    {% render_list %}
  • - {% with register_date=contactmoment.registratiedatum|default:"" %} -

    {% trans "Vraag ingediend op" %}:{{ register_date|date }}

    - {% endwith %} +

    {% trans "Vraag ingediend op" %}:{{ question.registered_date|date|default:"" }}

  • - {% list_item contactmoment.tekst compact=True strong=False %} + {% list_item question.question_text compact=True strong=False %}
  • - {% with channel=contactmoment.kanaal|default:"" %} + {% with channel=question.channel|upper|default:"" %}

    {% trans "Ingediend via" %}:{{ channel }}

    {% endwith %}
  • diff --git a/src/open_inwoner/templates/pages/partials/questions.html b/src/open_inwoner/templates/pages/partials/questions.html new file mode 100644 index 0000000000..bccb497bc5 --- /dev/null +++ b/src/open_inwoner/templates/pages/partials/questions.html @@ -0,0 +1,11 @@ +{% load i18n icon_tags list_tags %} + +

    {% trans "Vragen" %}

    +
    + {% for question in questions|slice:":3" %} + {% include "pages/partials/question.html" with question=question only %} + {% endfor %} + {% for question in questions|slice:"3:" %} + {% include "pages/partials/question.html" with question=question toggle_hide=True only %} + {% endfor %} +
    diff --git a/src/open_inwoner/templates/pages/questions/questions.html b/src/open_inwoner/templates/pages/questions/questions.html deleted file mode 100644 index bd934f4c53..0000000000 --- a/src/open_inwoner/templates/pages/questions/questions.html +++ /dev/null @@ -1,11 +0,0 @@ -{% load i18n icon_tags list_tags %} - -

    {% trans "Vragen" %}

    -
    - {% for contactmoment in questions|slice:":3" %} - {% include "pages/questions/question.html" with contactmoment=contactmoment only %} - {% endfor %} - {% for contactmoment in questions|slice:"3:" %} - {% include "pages/questions/question.html" with contactmoment=contactmoment toggle_hide=True only %} - {% endfor %} -
    diff --git a/src/open_inwoner/utils/tests/test_url.py b/src/open_inwoner/utils/tests/test_url.py index 8f731c304b..519ed5ed95 100644 --- a/src/open_inwoner/utils/tests/test_url.py +++ b/src/open_inwoner/utils/tests/test_url.py @@ -1,9 +1,12 @@ +from uuid import UUID + from django.test import TestCase, override_settings from open_inwoner.utils.url import ( get_next_url_from, get_next_url_param, prepend_next_url_param, + uuid_from_url, ) @@ -98,3 +101,31 @@ def test_prepend_next_url_param(self): param = get_next_url_param(param) self.assertEqual(param, "/foo") + + +class UUIDfromURLTest(TestCase): + def test_uuid_from_url(self): + testcases = [ + ( + "https://example.com/123e4567-e89b-12d3-a456-426614174000", + UUID("123e4567-e89b-12d3-a456-426614174000"), + ), + ( + "https://example.com/123e4567-e89b-12d3-a456-426614174000/987fEDCB-A987-654c-B321-123456789ABC", + [ + UUID("123e4567-e89b-12d3-a456-426614174000"), + UUID("987fEDCB-A987-654c-B321-123456789ABC"), + ], + ), + ("https://example.com/aaa-aaaaaa-bbbb-ccccc-426614174000", None), + ] + for url, expected in testcases: + with self.subTest(): + actual = uuid_from_url(url, allow_multiple=True) + self.assertEqual(actual, expected) + + def test_uuid_from_url_no_multiple(self): + url = "https://example.com/123e4567-e89b-12d3-a456-426614174000/987fEDCB-A987-654c-B321-123456789ABC" + + with self.assertRaises(ValueError): + uuid_from_url(url) diff --git a/src/open_inwoner/utils/url.py b/src/open_inwoner/utils/url.py index b6f327254c..6d5f5cff25 100644 --- a/src/open_inwoner/utils/url.py +++ b/src/open_inwoner/utils/url.py @@ -1,3 +1,6 @@ +import re +import uuid + from django.conf import settings from django.contrib.sites.models import Site from django.utils.encoding import iri_to_uri @@ -58,3 +61,27 @@ def prepend_next_url_param(url: str, next_url: str) -> str: next_url = prepend_next_url_param(next_url, current_next) u.args.set("next", next_url) return u.url + + +_UUID_PATTERN = re.compile( + r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", re.IGNORECASE +) + + +def uuid_from_url( + url: str, allow_multiple: bool = False +) -> uuid.UUID | list[uuid.UUID] | None: + """Extract UUID(s) from a given URL""" + + matches = re.findall(_UUID_PATTERN, url) + + if not matches: + return None + if len(matches) > 1: + if allow_multiple: + return [uuid.UUID(match) for match in matches] + raise ValueError( + "url contains more than 1 UUID (use allow_multiple=True if you expect multiple results)" + ) + + return uuid.UUID(matches[0])