Skip to content

Commit

Permalink
Merge pull request #4888 from open-formulieren/chore/4320-refactor-co…
Browse files Browse the repository at this point in the history
…sign-state

Refactor (accessing) the cosign state
  • Loading branch information
sergei-maertens authored Dec 9, 2024
2 parents 48773c8 + efa8791 commit 53d6f8d
Show file tree
Hide file tree
Showing 23 changed files with 545 additions and 146 deletions.
7 changes: 7 additions & 0 deletions pyright.pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ include = [
"src/openforms/pre_requests/base.py",
"src/openforms/pre_requests/registry.py",
"src/openforms/prefill/base.py",
"src/openforms/prefill/co_sign.py",
"src/openforms/prefill/registry.py",
"src/openforms/registrations/base.py",
"src/openforms/registrations/registry.py",
Expand All @@ -31,6 +32,8 @@ include = [
# Interaction with the outside world
"src/openforms/contrib/zgw/service.py",
"src/openforms/contrib/objects_api/",
# Emails
"src/openforms/emails/templatetags/cosign_information.py",
# Registrations
"src/openforms/registrations/tasks.py",
"src/openforms/registrations/contrib/email/config.py",
Expand All @@ -40,9 +43,13 @@ include = [
"src/openforms/registrations/contrib/stuf_zds/typing.py",
"src/openforms/registrations/contrib/objects_api/handlers/",
"src/openforms/registrations/contrib/objects_api/plugin.py",
"src/openforms/registrations/contrib/objects_api/registration_variables.py",
"src/openforms/registrations/contrib/objects_api/submission_registration.py",
"src/openforms/registrations/contrib/objects_api/typing.py",
"src/openforms/registrations/contrib/zgw_apis/",
# core submissions app
"src/openforms/submissions/cosigning.py",
"src/openforms/submissions/report.py",
# our own template app/package on top of Django
"src/openforms/template",
# generic typing helpers
Expand Down
2 changes: 1 addition & 1 deletion src/openforms/authentication/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ class LoginOptionSerializer(serializers.Serializer):

class CosignLoginInfoSerializer(LoginOptionSerializer):
def get_attribute(self, form):
if not form.cosign_component:
if not form.has_cosign_enabled:
return None

# cosign component but no auth backends is an invalid config that should
Expand Down
9 changes: 6 additions & 3 deletions src/openforms/authentication/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,12 @@ def get_options(
form: Form | None = None,
is_for_cosign: bool = False,
) -> list[LoginInfo]:
options = list()
if is_for_cosign and (not form or not form.cosign_component):
return options
options: list[LoginInfo] = []

# return empty list for forms without cosign
if is_for_cosign:
if not form or not form.has_cosign_enabled:
return []

for plugin_id in _iter_plugin_ids(form, self):
if plugin_id not in self._registry:
Expand Down
5 changes: 3 additions & 2 deletions src/openforms/emails/confirmation_emails.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ def get_confirmation_email_templates(submission: Submission) -> tuple[str, str]:
with translation.override(submission.language_code):
config = GlobalConfiguration.get_solo()
custom_templates = getattr(submission.form, "confirmation_email_template", None)
cosign = submission.cosign_state

match (submission.requires_cosign, custom_templates):
match (cosign.is_required, custom_templates):
# no cosign, definitely no custom templates exist
case (False, None):
return (
Expand Down Expand Up @@ -72,7 +73,7 @@ def get_confirmation_email_context_data(submission: Submission) -> dict[str, Any
**get_variables_for_context(submission),
"public_reference": submission.public_registration_reference,
"registration_completed": submission.is_registered,
"waiting_on_cosign": submission.waiting_on_cosign,
"waiting_on_cosign": submission.cosign_state.is_waiting,
}

# use the ``|date`` filter so that the timestamp is first localized to the correct
Expand Down
19 changes: 15 additions & 4 deletions src/openforms/emails/templatetags/cosign_information.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
from typing import NotRequired, TypedDict

from django import template
from django.template.loader import render_to_string

from openforms.submissions.models import Submission

register = template.Library()


class CosignInformationContext(TypedDict):
_submission: Submission
rendering_text: NotRequired[bool]


@register.simple_tag(takes_context=True)
def cosign_information(context):
def cosign_information(context: CosignInformationContext) -> str:
submission = context["_submission"]
if not (cosign := submission.cosign_state).is_required:
return ""

if context.get("rendering_text"):
template_name = "emails/templatetags/cosign_information.txt"
else:
template_name = "emails/templatetags/cosign_information.html"

tag_context = {
"cosign_complete": submission.cosign_complete,
"waiting_on_cosign": submission.waiting_on_cosign,
"cosigner_email": submission.cosigner_email,
"cosign_complete": cosign.is_signed,
"waiting_on_cosign": cosign.is_waiting,
"cosigner_email": cosign.email,
}
return render_to_string(template_name, tag_context)
28 changes: 11 additions & 17 deletions src/openforms/forms/models/form.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import uuid as _uuid
from contextlib import suppress
from copy import deepcopy
from functools import cached_property
from typing import Literal

from django.conf import settings
Expand All @@ -24,7 +25,6 @@
from openforms.authentication.registry import register as authentication_register
from openforms.config.models import GlobalConfiguration
from openforms.data_removal.constants import RemovalMethods
from openforms.formio.typing import Component
from openforms.formio.validators import variable_key_validator
from openforms.payments.fields import PaymentBackendChoiceField
from openforms.payments.registry import register as payment_register
Expand All @@ -40,8 +40,6 @@
User = get_user_model()
logger = logging.getLogger(__name__)

_sentinel = object()


class FormQuerySet(models.QuerySet):
def live(self):
Expand Down Expand Up @@ -438,23 +436,19 @@ def get_authentication_backends_display(self):
"authentication backend(s)"
)

_cosign_component = _sentinel
@cached_property
def has_cosign_enabled(self) -> bool:
"""
Check if cosign is enabled by checking the presence of a cosign (v2) component.
def get_cosign_component(self) -> Component | None:
We don't return the component itself, as you should use
:class:`openforms.submissions.cosigning.CosignState` to check the state, which
can take dynamic logic rules into account.
"""
for component in self.iter_components():
if component["type"] == "cosign":
return component

@property
def cosign_component(self) -> Component | None:
if self._cosign_component is _sentinel:
self._cosign_component = self.get_cosign_component()
return self._cosign_component

@property
def cosigning_required(self) -> bool:
cosign_component = self.get_cosign_component()
return cosign_component and cosign_component.get("validate", {}).get("required")
return True
return False

@property
def login_required(self) -> bool:
Expand Down
15 changes: 11 additions & 4 deletions src/openforms/prefill/co_sign.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import logging

from openforms.authentication.service import AuthAttribute
from openforms.submissions.cosigning import CosignV1Data
from openforms.submissions.models import Submission

from .models import PrefillConfig
Expand Down Expand Up @@ -57,10 +58,16 @@ def add_co_sign_representation(
plugin,
)

_cosign_data: CosignV1Data = submission.co_sign_data.copy()

values, representation = plugin.get_co_sign_values(
submission,
submission.co_sign_data["identifier"],
submission, _cosign_data["identifier"]
)
_cosign_data.update(
{
"fields": values,
"representation": representation,
}
)
submission.co_sign_data["fields"] = values
submission.co_sign_data["representation"] = representation
submission.co_sign_data.update(_cosign_data)
submission.save(update_fields=["co_sign_data"])
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
from __future__ import annotations

from datetime import datetime
from typing import TYPE_CHECKING

from django.utils.translation import gettext_lazy as _

from openforms.authentication.service import AuthAttribute, BaseAuth
from openforms.authentication.service import AuthAttribute
from openforms.plugins.registry import BaseRegistry
from openforms.submissions.cosigning import CosignV2Data
from openforms.submissions.models import Submission
from openforms.variables.base import BaseStaticVariable
from openforms.variables.constants import FormVariableDataTypes

from .models import ObjectsAPISubmissionAttachment

if TYPE_CHECKING:
from openforms.submissions.models import Submission
from .models import ObjectsAPIRegistrationData, ObjectsAPISubmissionAttachment


class Registry(BaseRegistry[BaseStaticVariable]):
Expand Down Expand Up @@ -47,7 +45,10 @@ class PdfUrl(BaseStaticVariable):
def get_initial_value(self, submission: Submission | None = None):
if submission is None:
return None
return submission.objects_api_registration_data.pdf_url
_data: ObjectsAPIRegistrationData = (
submission.objects_api_registration_data # pyright: ignore[reportAttributeAccessIssue]
)
return _data.pdf_url


@register("csv_url")
Expand All @@ -58,7 +59,10 @@ class CsvUrl(BaseStaticVariable):
def get_initial_value(self, submission: Submission | None = None):
if submission is None:
return None
return submission.objects_api_registration_data.csv_url
_data: ObjectsAPIRegistrationData = (
submission.objects_api_registration_data # pyright: ignore[reportAttributeAccessIssue]
)
return _data.csv_url


@register("attachment_urls")
Expand Down Expand Up @@ -127,19 +131,20 @@ class Cosign(BaseStaticVariable):

def get_initial_value(
self, submission: Submission | None = None
) -> BaseAuth | None:
if not submission or not submission.cosign_complete:
) -> CosignV2Data | None:
if not submission or not (cosign := submission.cosign_state).is_signed:
return None

return submission.co_sign_data
return cosign.signing_details


def get_cosign_value(submission: Submission | None, attribute: AuthAttribute) -> str:
if not submission or not submission.cosign_complete:
if not submission or not (cosign := submission.cosign_state).is_signed:
return ""

if submission.co_sign_data["attribute"] == attribute:
return submission.co_sign_data["value"]
details = cosign.signing_details
if details["attribute"] == attribute:
return details["value"]

return ""

Expand All @@ -152,15 +157,10 @@ class CosignDate(BaseStaticVariable):
def get_initial_value(
self, submission: Submission | None = None
) -> datetime | None:
if not submission or not submission.cosign_complete:
if not submission or not (cosign := submission.cosign_state).is_signed:
return None

if (cosign_date := submission.co_sign_data.get("cosign_date")) is None:
# Can be the case on existing submissions, at some point we can switch back to
# `__getitem__` ([...]).
return None

return datetime.fromisoformat(cosign_date)
cosign_date = cosign.signing_details.get("cosign_date")
return datetime.fromisoformat(cosign_date) if cosign_date else None


@register("cosign_bsn")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,16 @@
from contextlib import contextmanager
from datetime import date, datetime
from decimal import Decimal
from typing import Any, Generic, Iterator, Literal, TypeVar, cast, override
from typing import (
Any,
Generic,
Iterator,
Literal,
TypeVar,
assert_never,
cast,
override,
)

from django.db.models import F

Expand Down Expand Up @@ -91,7 +100,7 @@ def _resolve_documenttype(
description = options["iot_attachment"]
url_ref = options.get("informatieobjecttype_attachment", "")
case _: # pragma: no cover
raise RuntimeError(f"Unhandled field '{field}'.")
assert_never(field)

# descriptions only work if a catalogue is provided to look up the document type
# inside it
Expand Down Expand Up @@ -367,21 +376,16 @@ def get_payment_context_data(submission: Submission) -> dict[str, Any]:
def get_cosign_context_data(
submission: Submission,
) -> dict[str, str | datetime] | None:
if not submission.cosign_complete:
if not (cosign := submission.cosign_state).is_signed:
return None
else:
# date can be missing on existing submissions, so fallback to an empty string
date = (
datetime.fromisoformat(submission.co_sign_data["cosign_date"])
if "cosign_date" in submission.co_sign_data
else ""
)
return {
"bsn": get_cosign_value(submission, AuthAttribute.bsn),
"kvk": get_cosign_value(submission, AuthAttribute.kvk),
"pseudo": get_cosign_value(submission, AuthAttribute.pseudo),
"date": date,
}

cosign_date = cosign.signing_details.get("cosign_date")
return {
"bsn": get_cosign_value(submission, AuthAttribute.bsn),
"kvk": get_cosign_value(submission, AuthAttribute.kvk),
"pseudo": get_cosign_value(submission, AuthAttribute.pseudo),
"date": datetime.fromisoformat(cosign_date) if cosign_date else "",
}

@override
def get_record_data(
Expand Down
6 changes: 5 additions & 1 deletion src/openforms/registrations/contrib/stuf_zds/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,11 @@ def register_submission(
zaak_options: ZaakOptions = {
**options,
"omschrijving": submission.form.admin_name,
"co_sign_data": submission.co_sign_data if submission.co_sign_data else {},
"cosigner": (
cosign.signing_details["value"]
if (cosign := submission.cosign_state).is_signed
else ""
),
}

with get_client(options=zaak_options) as client:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ def test_plugin(self, m, mock_task):
{
"key": "language_code",
},
{"key": "cosignerEmail", "type": "cosign"},
],
form__name="my-form",
bsn="111222333",
Expand All @@ -214,7 +215,8 @@ def test_plugin(self, m, mock_task):
"language_code": "Dothraki", # some form widget defined by form designer
},
language_code="en",
co_sign_data={"value": "123456782"},
cosigned=True,
co_sign_data__value="123456782",
)

attachment = SubmissionFileAttachmentFactory.create(
Expand Down
2 changes: 1 addition & 1 deletion src/openforms/registrations/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ def register_submission(submission_id: int, event: PostSubmissionEvents | str) -
)
return

if submission.waiting_on_cosign:
if submission.cosign_state.is_waiting:
logger.debug(
"Skipping registration for submission '%s' as it hasn't been co-signed yet.",
submission,
Expand Down
Loading

0 comments on commit 53d6f8d

Please sign in to comment.