diff --git a/src/core/locale/de/LC_MESSAGES/django.po b/src/core/locale/de/LC_MESSAGES/django.po index 54605f17f955c3b137a75835b2e6c296b318595e..d2e9c7ad8ff6fb8090a1305fe2e38b5a4b2c6a7e 100644 --- a/src/core/locale/de/LC_MESSAGES/django.po +++ b/src/core/locale/de/LC_MESSAGES/django.po @@ -2339,6 +2339,9 @@ msgstr "Adresse öffentlich anzeigen" msgid "UserCommunicationChannel__show_public" msgstr "öffentlich" +msgid "UserCommunicationChannel__cannot_notify__dect" +msgstr "Dieser Kanal kann nicht für Benachrichtigungen benutzt werden." + msgid "UserCommunicationChannel__cannot_notify__phone" msgstr "Eine Benachrichtigung per Telefon wird nicht unterstützt." diff --git a/src/core/locale/en/LC_MESSAGES/django.po b/src/core/locale/en/LC_MESSAGES/django.po index d5c6b5e3d29d407e90a308af459a86bdf22ae8f1..00a10f3dc443e5a829aeab5541f55391c86e5244 100644 --- a/src/core/locale/en/LC_MESSAGES/django.po +++ b/src/core/locale/en/LC_MESSAGES/django.po @@ -2337,6 +2337,9 @@ msgstr "show this address publicly" msgid "UserCommunicationChannel__show_public" msgstr "public" +msgid "UserCommunicationChannel__cannot_notify__dect" +msgstr "cannot use this channel for notifications" + msgid "UserCommunicationChannel__cannot_notify__phone" msgstr "notifications via phone/mobile are not supported" diff --git a/src/core/models/users.py b/src/core/models/users.py index 0ab6c716e25656a7d98b1e212303066f19a94872..9b73e9633d196cfb91c0430909d832796d49793e 100644 --- a/src/core/models/users.py +++ b/src/core/models/users.py @@ -563,6 +563,17 @@ class UserCommunicationChannel(models.Model): if url.host is None or url.path is None: raise ValidationError({'address': 'Expected valid URL.'}) + @staticmethod + def validate_dect(address): + try: + if len(str(address)) > 10: + raise ValidationError({'address': 'Dect may not be longer than 10 chars.'}) + if address[0] == '0': + raise ValidationError({'address': 'Dect must not start with a 0.'}) + int(address) + except ValueError: + raise ValidationError({'address': 'Dect must be a number.'}) + @property def can_notify(self): """Signals whether this channel can be used for notifications.""" @@ -571,19 +582,21 @@ class UserCommunicationChannel(models.Model): def clean(self): super().clean() - if self.channel == self.Channel.MAIL: - self.validate_email(self.address) - - elif self.channel == self.Channel.MATRIX: - self.validate_matrix_room_url(self.address) - - elif self.channel == self.Channel.PHONE: - self.validate_phone_number(self.address) - if self.use_for_notifications: - raise ValidationError({'use_for_notifications': _('UserCommunicationChannel__cannot_notify__phone')}) - - elif self.channel == self.Channel.ACTIVITYPUB: - self.validate_activitypub_url(self.address) + match self.channel: + case self.Channel.MAIL: + self.validate_email(self.address) + case self.Channel.MATRIX: + self.validate_matrix_room_url(self.address) + case self.Channel.DECT: + self.validate_dect(self.address) + if self.use_for_notifications: + raise ValidationError({'use_for_notifications': _('UserCommunicationChannel__cannot_notify__dect')}) + case self.Channel.PHONE: + self.validate_phone_number(self.address) + if self.use_for_notifications: + raise ValidationError({'use_for_notifications': _('UserCommunicationChannel__cannot_notify__phone')}) + case self.Channel.ACTIVITYPUB: + self.validate_activitypub_url(self.address) # TODO: verify the other channel types for correct syntax as well diff --git a/src/plainui/forms.py b/src/plainui/forms.py index 2f62412aabd617da3e5de06bbd872d2510183a48..bb7c18cc802f79fb1564501792e6dc24b9a7629c 100644 --- a/src/plainui/forms.py +++ b/src/plainui/forms.py @@ -92,6 +92,8 @@ class ExampleForm(forms.Form): class ProfileEditForm(TranslatedFieldsForm): + dect = forms.IntegerField(required=False) + class Meta: model = PlatformUser fields = [ @@ -99,6 +101,29 @@ class ProfileEditForm(TranslatedFieldsForm): 'timezone', ] + def __init__(self, *args, instance, **kwargs): + super().__init__(*args, instance=instance, **kwargs) + + dect_channel = UserCommunicationChannel.objects.filter( + user=instance, + channel=UserCommunicationChannel.Channel.DECT, + ).first() + if dect_channel: + self.fields['dect'].initial = dect_channel.address + + def clean_dect(self): + dect = self.cleaned_data['dect'] + # TODO: De-Duplicate with UserCommunicationChannel + try: + if len(str(dect)) > 10: + raise ValidationError('Dect may not be longer than 10 chars.') + if str(dect)[0] == '0': + raise ValidationError('Dect must not start with a 0.') + int(dect) + except ValueError: + raise ValidationError('Dect must be a number.') + return dect + class ProfileDescriptionEditForm(TranslatedFieldsForm): class Meta: diff --git a/src/plainui/jinja2/plainui/components/form_elements.html.j2 b/src/plainui/jinja2/plainui/components/form_elements.html.j2 index 6da3b51b269bedaed4a4c2530e6c9b94e09292d1..facb377b6000a23112675155a29cc4dd31ee6a6f 100644 --- a/src/plainui/jinja2/plainui/components/form_elements.html.j2 +++ b/src/plainui/jinja2/plainui/components/form_elements.html.j2 @@ -37,6 +37,9 @@ {% macro password(form, name) -%} {{ input(form, name, 'password') }} {%- endmacro %} +{% macro number(form, name) -%} + {{ input(form, name, 'number') }} +{%- endmacro %} {% macro textarea(form, name) -%} {% set el = form[name] -%} {% set my_id = unique_id() -%} @@ -132,6 +135,8 @@ {{ text(form, field_name) }} {% elif field.widget_type == 'password' -%} {{ password(form, field_name) }} + {% elif field.widget_type == 'number' -%} + {{ number(form, field_name) }} {% elif field.widget_type == 'checkbox' -%} {{ checkbox(form, field_name) }} {% elif field.widget_type == 'select' -%} diff --git a/src/plainui/tests/test_views.py b/src/plainui/tests/test_views.py index 373612288a64e6f8ff33971397c0008aeffa76c6..daef90c5d063f83c0ba48c23a374ea360c708c6f 100644 --- a/src/plainui/tests/test_views.py +++ b/src/plainui/tests/test_views.py @@ -47,6 +47,7 @@ from core.models import ( StaticPageRevision, TagItem, UserBadge, + UserCommunicationChannel, UserDereferrerAllowlist, ) from core.templatetags.hub_absolute import hub_absolute @@ -1297,6 +1298,7 @@ class ViewsTest(ViewsTestBase): 'default_badge_visibility': 'private', 'pronouns': 'they', 'timezone': 'Europe/Berlin', + 'dect': '1337', }, ) self.assertRedirects(resp, reverse('plainui:userprofile')) @@ -1307,6 +1309,7 @@ class ViewsTest(ViewsTestBase): self.assertEqual(self.user.pronouns, 'they') self.assertEqual(self.user.timezone.key, 'Europe/Berlin') self.assertSetsMessage(resp, 'Updated Profile') + self.assertTrue(UserCommunicationChannel.objects.filter(user=self.user, channel=UserCommunicationChannel.Channel.DECT, address='1337').exists()) @override_locale('en') def test_ModifyThemeView_get(self): diff --git a/src/plainui/views/user_profile.py b/src/plainui/views/user_profile.py index 2ba4f6639f95afdd467d5096e9c27499613882b2..cbf955451a7ed6d18c2e22f6fce1f5fe6e397992 100644 --- a/src/plainui/views/user_profile.py +++ b/src/plainui/views/user_profile.py @@ -24,6 +24,7 @@ from core.models import ( Event, Project, UserBadge, + UserCommunicationChannel, UserDereferrerAllowlist, ) from core.sso import SSO @@ -120,6 +121,20 @@ class ProfileView(ConferenceRequiredMixin, UpdateView): form1.instance.timezone = form1.cleaned_data['timezone'] form1.instance.save() + if form1.cleaned_data['dect']: + UserCommunicationChannel.objects.update_or_create( + user=self.request.user, + channel=UserCommunicationChannel.Channel.DECT, + defaults={'address': form1.cleaned_data['dect']}, + ) + else: + dect_channel = UserCommunicationChannel.objects.filter( + user=self.request.user, + channel=UserCommunicationChannel.Channel.DECT, + ) + if dect_channel.exists(): + dect_channel.delete() + # TODO: Update after deciding oh one or more conferences in #648 cm = form1.instance.conferences.filter(conference=self.conf).first() if cm: