diff --git a/src/open_inwoner/accounts/templates/accounts/registration_necessary.html b/src/open_inwoner/accounts/templates/accounts/registration_necessary.html index e012d9f8e1..856b5246d9 100644 --- a/src/open_inwoner/accounts/templates/accounts/registration_necessary.html +++ b/src/open_inwoner/accounts/templates/accounts/registration_necessary.html @@ -69,8 +69,17 @@

{% trans "Notification preferences" %}

{% trans "E-mailnotificaties wanneer er actie nodig is voor een zaak (kan niet uitgeschakeld worden)" %}

+ {# Choice of zaken notification channel #}

{% trans "How do you want to receive notifications about cases?" %}

- {% choice_radio_multiple form.case_notification_channel %} +
+ {% with form.case_notification_channel as field %} + {% for choice in field.field.choices %} +
+ {% choice_radio_stacked choice=choice name=field.name data=field.value index=forloop.counter initial=field.form.initial icon_class=choice.1|get_icon_class %} +
+ {% endfor %} + {% endwith %} +
{% form_actions primary_icon='east' primary_text="Voltooi registratie" fullwidth=True %} diff --git a/src/open_inwoner/accounts/tests/test_profile_views.py b/src/open_inwoner/accounts/tests/test_profile_views.py index 1267a46d44..043c9299bd 100644 --- a/src/open_inwoner/accounts/tests/test_profile_views.py +++ b/src/open_inwoner/accounts/tests/test_profile_views.py @@ -990,6 +990,10 @@ def test_disabling_notification_is_saved(self, mock_page_display): self.assertTrue(self.user.cases_notifications) self.assertFalse(self.user.messages_notifications) self.assertTrue(self.user.plans_notifications) + self.assertEqual( + self.user.case_notification_channel, + NotificationChannelChoice.digital_and_post, + ) def test_cases_notifications_is_accessible_when_digid_user(self, mock_page_display): self.user.login_type = LoginTypeChoices.digid diff --git a/src/open_inwoner/accounts/views/auth.py b/src/open_inwoner/accounts/views/auth.py index 2d8c3d7c01..3051c01457 100644 --- a/src/open_inwoner/accounts/views/auth.py +++ b/src/open_inwoner/accounts/views/auth.py @@ -18,7 +18,6 @@ from digid_eherkenning.views.base import get_redirect_url from digid_eherkenning.views.digid import DigiDAssertionConsumerServiceView from digid_eherkenning.views.eherkenning import eHerkenningAssertionConsumerServiceView -from onelogin.saml2.utils import OneLogin_Saml2_ValidationError from digid_eherkenning_oidc_generics.views import ( eHerkenningOIDCAuthenticationCallbackView, @@ -27,7 +26,6 @@ from eherkenning.mock.views.eherkenning import ( eHerkenningAssertionConsumerServiceMockView, ) -from open_inwoner.openklant.clients import build_klanten_client from open_inwoner.openklant.models import OpenKlantConfig from open_inwoner.openzaak.models import OpenZaakConfig from open_inwoner.utils.views import LogMixin @@ -109,34 +107,6 @@ def get_success_url(self): class CustomDigiDAssertionConsumerServiceView(DigiDAssertionConsumerServiceView): - def get(self, request): - errors = [] - user = auth.authenticate( - request=request, - digid=True, - saml_art=request.GET.get("SAMLart"), - errors=errors, - ) - if user is None: - error_code = getattr(errors[0], "code", "") if errors else "" - error_type = ( - "cancelled" - if error_code == OneLogin_Saml2_ValidationError.STATUS_CODE_AUTHNFAILED - else "default" - ) - messages.error(request, self.error_messages[error_type]) - login_url = self.get_login_url(error_type=error_type) - return HttpResponseRedirect(login_url) - - if (client := build_klanten_client()) and ( - klant := client.create_klant(user_bsn=user.bsn) - ): - logger.info("Created klant %s for new user %s", klant, user) - - auth.login(request, user) - - return HttpResponseRedirect(self.get_success_url()) - def get_login_url(self, **kwargs): invite_url = self.request.session.get("invite_url") next_url = self.request.GET.get("RelayState") diff --git a/src/open_inwoner/accounts/views/mixins.py b/src/open_inwoner/accounts/views/mixins.py new file mode 100644 index 0000000000..539f3670e3 --- /dev/null +++ b/src/open_inwoner/accounts/views/mixins.py @@ -0,0 +1,22 @@ +import logging + +from open_inwoner.openklant.clients import build_klanten_client +from open_inwoner.openklant.wrap import get_fetch_parameters + +logger = logging.getLogger(__name__) + + +class KlantenAPIMixin: + def update_klanten_api(self, update_data: dict): + if update_data and (client := build_klanten_client()): + klant = client.retrieve_klant(**get_fetch_parameters(self.request)) + if not klant: + logger.error("Failed to retrieve klant for user %s", self.request.user) + return + + self.log_system_action("retrieved klant for user", user=self.request.user) + client.partial_update_klant(klant, update_data) + self.log_system_action( + f"patched klant from user profile edit with fields: {', '.join(sorted(update_data.keys()))}", + user=self.request.user, + ) diff --git a/src/open_inwoner/accounts/views/profile.py b/src/open_inwoner/accounts/views/profile.py index 19f6a8766c..832e38b2a4 100644 --- a/src/open_inwoner/accounts/views/profile.py +++ b/src/open_inwoner/accounts/views/profile.py @@ -28,8 +28,6 @@ from open_inwoner.haalcentraal.utils import fetch_brp from open_inwoner.laposta.forms import NewsletterSubscriptionForm from open_inwoner.laposta.models import LapostaConfig -from open_inwoner.openklant.clients import build_klanten_client -from open_inwoner.openklant.wrap import get_fetch_parameters from open_inwoner.plans.models import Plan from open_inwoner.qmatic.client import NoServiceConfigured, QmaticClient from open_inwoner.questionnaire.models import QuestionnaireStep @@ -37,6 +35,7 @@ from ..forms import BrpUserForm, CategoriesForm, UserForm, UserNotificationsForm from ..models import Action, User +from .mixins import KlantenAPIMixin logger = logging.getLogger(__name__) @@ -202,6 +201,7 @@ class EditProfileView( LogMixin, LoginRequiredMixin, CommonPageMixin, + KlantenAPIMixin, BaseBreadcrumbMixin, UpdateView, ): @@ -223,17 +223,13 @@ def get_object(self): def form_valid(self, form): form.save() - self.update_klant_api({k: form.cleaned_data[k] for k in form.changed_data}) + self.update_klanten_api({k: form.cleaned_data[k] for k in form.changed_data}) messages.success(self.request, _("Uw wijzigingen zijn opgeslagen")) self.log_change(self.get_object(), _("profile was modified")) return HttpResponseRedirect(self.get_success_url()) - def update_klant_api(self, user_form_data: dict): - user: User = self.request.user - if not user.bsn and not user.kvk: - return - + def update_klanten_api(self, user_form_data: dict): field_mapping = { "emailadres": "email", "telefoonnummer": "phonenumber", @@ -243,20 +239,7 @@ def update_klant_api(self, user_form_data: dict): for api_name, local_name in field_mapping.items() if user_form_data.get(local_name) } - if update_data: - if client := build_klanten_client(): - klant = client.retrieve_klant(**get_fetch_parameters(self.request)) - - if klant: - self.log_system_action( - "retrieved klant for user", user=self.request.user - ) - client.partial_update_klant(klant, update_data) - if klant: - self.log_system_action( - f"patched klant from user profile edit with fields: {', '.join(sorted(update_data.keys()))}", - user=self.request.user, - ) + super().update_klanten_api(update_data) def get_form_class(self): user = self.request.user @@ -319,7 +302,12 @@ def get_brp_data(self): class MyNotificationsView( - LogMixin, LoginRequiredMixin, CommonPageMixin, BaseBreadcrumbMixin, UpdateView + LogMixin, + LoginRequiredMixin, + CommonPageMixin, + KlantenAPIMixin, + BaseBreadcrumbMixin, + UpdateView, ): template_name = "pages/profile/notifications.html" model = User @@ -344,35 +332,21 @@ def get_form_kwargs(self): def form_valid(self, form): form.save() - self.update_klant_api({k: form.cleaned_data[k] for k in form.changed_data}) + self.sync_notifications_with_klanten_api( + user_form_data={k: form.cleaned_data[k] for k in form.changed_data} + ) messages.success(self.request, _("Uw wijzigingen zijn opgeslagen")) self.log_change(self.object, _("users notifications were modified")) return HttpResponseRedirect(self.get_success_url()) - def update_klant_api(self, user_form_data: dict): - user: User = self.request.user - if not user.bsn and not user.kvk: - return - - update_data = {} + def sync_notifications_with_klanten_api(self, user_form_data: dict): if notification_channel := user_form_data.get("case_notification_channel"): update_data = { "toestemmingZaakNotificatiesAlleenDigitaal": notification_channel == NotificationChannelChoice.digital_only } - - if update_data and (client := build_klanten_client()): - klant = client.retrieve_klant(**get_fetch_parameters(self.request)) - if not klant: - return - - self.log_system_action("retrieved klant for user", user=self.request.user) - client.partial_update_klant(klant, update_data) - self.log_system_action( - f"patched klant from user profile edit with fields: {', '.join(sorted(update_data.keys()))}", - user=self.request.user, - ) + super().update_klanten_api(update_data) class UserAppointmentsView( diff --git a/src/open_inwoner/accounts/views/registration.py b/src/open_inwoner/accounts/views/registration.py index fb3344e33c..4c932e4d92 100644 --- a/src/open_inwoner/accounts/views/registration.py +++ b/src/open_inwoner/accounts/views/registration.py @@ -16,8 +16,7 @@ OpenIDConnectEHerkenningConfig, ) from open_inwoner.accounts.choices import NotificationChannelChoice -from open_inwoner.openklant.clients import build_klanten_client -from open_inwoner.openklant.wrap import get_fetch_parameters +from open_inwoner.accounts.views.mixins import KlantenAPIMixin from open_inwoner.utils.hash import generate_email_from_string from open_inwoner.utils.views import CommonPageMixin, LogMixin @@ -138,7 +137,13 @@ def get(self, request, *args, **kwargs): return super().get(self, request, *args, **kwargs) -class NecessaryFieldsUserView(LogMixin, LoginRequiredMixin, InviteMixin, UpdateView): +class NecessaryFieldsUserView( + LogMixin, + LoginRequiredMixin, + KlantenAPIMixin, + InviteMixin, + UpdateView, +): model = User form_class = NecessaryUserForm template_name = "accounts/registration_necessary.html" @@ -163,7 +168,9 @@ def get_form_kwargs(self): def form_valid(self, form): user = form.save() - self.update_klant_api({k: form.cleaned_data[k] for k in form.changed_data}) + self.sync_notifications_with_klanten_api( + {k: form.cleaned_data[k] for k in form.changed_data} + ) invite = form.cleaned_data["invite"] if invite: @@ -187,29 +194,13 @@ def get_initial(self): return initial - def update_klant_api(self, user_form_data: dict): - user: User = self.request.user - if not user.bsn and not user.kvk: - return - - update_data = {} + def sync_notifications_with_klanten_api(self, user_form_data: dict): if notification_channel := user_form_data.get("case_notification_channel"): update_data = { "toestemmingZaakNotificatiesAlleenDigitaal": notification_channel == NotificationChannelChoice.digital_only } - - if update_data and (client := build_klanten_client()): - klant = client.retrieve_klant(**get_fetch_parameters(self.request)) - if not klant: - return - - self.log_system_action("retrieved klant for user", user=self.request.user) - client.partial_update_klant(klant, update_data) - self.log_system_action( - f"patched klant from user profile edit with fields: {', '.join(sorted(update_data.keys()))}", - user=self.request.user, - ) + super().update_klanten_api(update_data) class EmailVerificationUserView(LogMixin, LoginRequiredMixin, TemplateView): diff --git a/src/open_inwoner/accounts/views/signals.py b/src/open_inwoner/accounts/views/signals.py new file mode 100644 index 0000000000..473675daf4 --- /dev/null +++ b/src/open_inwoner/accounts/views/signals.py @@ -0,0 +1,26 @@ +import logging + +from django.db.models.signals import post_save +from django.dispatch import receiver + +from open_inwoner.accounts.models import User +from open_inwoner.openklant.clients import build_klanten_client + +logger = logging.getLogger(__name__) + + +@receiver(post_save, sender=User) +def create_klant_for_new_user( + sender: type, instance: User, created: bool, **kwargs +) -> None: + if not created: + return + + user = instance + + if (client := build_klanten_client()) and ( + klant := client.create_klant(user_bsn=user.bsn) + ): + logger.info("Created klant %s for new user %s", klant, user) + else: + logger.error("Failed to create klant for new user %s", user) diff --git a/src/open_inwoner/components/templatetags/form_tags.py b/src/open_inwoner/components/templatetags/form_tags.py index df59c97404..91541cc9e9 100644 --- a/src/open_inwoner/components/templatetags/form_tags.py +++ b/src/open_inwoner/components/templatetags/form_tags.py @@ -266,29 +266,21 @@ def choice_radio(choice, **kwargs): @register.inclusion_tag("components/Form/ChoiceRadioStacked.html") def choice_radio_stacked(choice, **kwargs): """ - Displaying a radio input that is rendered from a choice field. - - Usage: - {% choice_radio form.radio_field %} + Display radio input rendered from a choice field. - Variables: - + choice: The choice that needs to be rendered. - """ - return {**kwargs, "choice": choice} - - -@register.inclusion_tag(WIDGET_TEMPLATES["RADIO"]) -def choice_radio_multiple(field, **kwargs): - """ - Display multiple radio inputs that are rendered from a choice field. + Args: + choice: the choice to be rendered + name: the name of the form field + data: the value of a form field field + index: the index of a for-loop when looping over choices + initial: the initial value of the field + icon_class: the icon to be displayed at the top of the + radio stack Usage: - {% choice_radio_multiple form.radio_field %} - - Variables: - + field: The field that needs to be rendered. + {% choice_radio_stacked choice=choice name=field.name ... icon_class=choice.1|get_icon_class %} """ - return {**kwargs, "field": field} + return {**kwargs, "choice": choice} @register.inclusion_tag("components/Form/Input.html") diff --git a/src/open_inwoner/openklant/api_models.py b/src/open_inwoner/openklant/api_models.py index 33161dc39f..c4411cd10c 100644 --- a/src/open_inwoner/openklant/api_models.py +++ b/src/open_inwoner/openklant/api_models.py @@ -31,7 +31,7 @@ class Klant(ZGWModel): achternaam: str = "" telefoonnummer: str = "" emailadres: str = "" - toestemmingZaakNotificatiesAlleenDigitaal: bool = False + toestemmingZaakNotificatiesAlleenDigitaal: bool | None = None def get_name_display(self): return " ".join(