Bump version to 1.3.6 and update NEWS.rst for 1.3.5
[mailman-postorious.git] / src / postorius / forms / user_forms.py
blob40b99024d87fc6d8b04517d2cfb76b7a228d12d8
1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2017-2021 by the Free Software Foundation, Inc.
4 # This file is part of Postorius.
6 # Postorius is free software: you can redistribute it and/or modify it under
7 # the terms of the GNU General Public License as published by the Free
8 # Software Foundation, either version 3 of the License, or (at your option)
9 # any later version.
11 # Postorius is distributed in the hope that it will be useful, but WITHOUT
12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14 # more details.
16 # You should have received a copy of the GNU General Public License along with
17 # Postorius. If not, see <http://www.gnu.org/licenses/>.
21 from django import forms
22 from django.core.exceptions import ValidationError
23 from django.utils.translation import gettext_lazy as _
25 from allauth.account.models import EmailAddress
27 from postorius.forms.fields import (
28 NullBooleanRadioSelect, delivery_mode_field, delivery_status_field,
29 moderation_action_field)
30 from postorius.utils import LANGUAGES, with_empty_choice
33 class UserPreferences(forms.Form):
35 """
36 Form handling the user's global, address and subscription based preferences
37 """
39 def __init__(self, *args, **kwargs):
40 self._preferences = kwargs.pop('preferences', None)
41 self.disabled_delivery_choices = kwargs.pop(
42 'disabled_delivery_choices', [])
43 super().__init__(*args, **kwargs)
44 # Disable some options to be set.
45 self.fields['delivery_status'].widget.disabled_choices = (
46 self.disabled_delivery_choices)
48 @property
49 def initial(self):
50 # Redirect to the preferences, this allows setting the preferences
51 # after instanciation and it will also set the initial data.
52 return self._preferences or {}
54 @initial.setter
55 def initial(self, value):
56 pass
58 choices = ((True, _('Yes')), (False, _('No')))
60 delivery_status = delivery_status_field()
61 delivery_mode = delivery_mode_field()
62 receive_own_postings = forms.NullBooleanField(
63 widget=NullBooleanRadioSelect(choices=choices),
64 required=False,
65 label=_('Receive own postings'),
66 help_text=_(
67 'Ordinarily, you will get a copy of every message you post to the '
68 'list. If you don\'t want to receive this copy, set this option '
69 'to No.'
71 acknowledge_posts = forms.NullBooleanField(
72 widget=NullBooleanRadioSelect(choices=choices),
73 required=False,
74 label=_('Acknowledge posts'),
75 help_text=_(
76 'Receive acknowledgement mail when you send mail to the list?'))
77 hide_address = forms.NullBooleanField(
78 widget=NullBooleanRadioSelect(choices=choices),
79 required=False,
80 label=_('Hide address'),
81 help_text=_(
82 'When someone views the list membership, your email address is '
83 'normally shown (in an obscured fashion to thwart spam '
84 'harvesters). '
85 'If you do not want your email address to show up on this '
86 'membership roster at all, select Yes for this option.'))
87 receive_list_copy = forms.NullBooleanField(
88 widget=NullBooleanRadioSelect(choices=choices),
89 required=False,
90 label=_('Receive list copies (possible duplicates)'),
91 help_text=_(
92 'When you are listed explicitly in the To: or Cc: headers of a '
93 'list message, you can opt to not receive another copy from the '
94 'mailing list. Select Yes to receive copies. '
95 'Select No to avoid receiving copies from the mailing list'))
97 preferred_language = forms.ChoiceField(
98 widget=forms.Select(),
99 choices=with_empty_choice(LANGUAGES),
100 required=False,
101 label=_('Preferred language'),
102 help_text=_(
103 'Preferred language for your interactions with Mailman. When '
104 'this is set, it will override the MailingList\'s preferred '
105 'language. This affects which language is used for your '
106 'email notifications and such.'))
108 class Meta:
111 Class to define the name of the fieldsets and what should be
112 included in each.
114 layout = [["User Preferences", "acknowledge_posts", "hide_address",
115 "receive_list_copy", "receive_own_postings",
116 "delivery_mode", "delivery_status", "preferred_language"]]
118 def save(self):
119 # Note (maxking): It is possible that delivery_status field will always
120 # be a part of changed_data because of how the SelectWidget() works.
121 if not self.changed_data:
122 return
123 for key in self.changed_data:
124 if self.cleaned_data[key] is not None:
125 # None: nothing set yet. Remember to remove this test
126 # when Mailman accepts None as a "reset to default"
127 # value.
128 self._preferences[key] = self.cleaned_data[key]
129 self._preferences.save()
131 def clean_delivery_status(self):
132 """Check that someone didn't pass the disabled value.
134 This is meant to enforce that certain values are RO and can be seen but
135 not set.
137 val = self.cleaned_data.get('delivery_status')
138 # When the options are disabled in the widget, the values returned are
139 # empty. Consider that as unchanged values and just return the initial
140 # value of the field.
141 if not val:
142 return self.initial.get('delivery_status')
143 # This means the value was changed, check if the change was allowed. If
144 # not, just raise a ValidationError.
145 if val in self.disabled_delivery_choices:
146 raise ValidationError(
147 _('Cannot set delivery_status to {}').format(val))
148 # The change seems correct, just return the value.
149 return val
152 class UserPreferencesFormset(forms.BaseFormSet):
154 def __init__(self, *args, **kwargs):
155 self._preferences = kwargs.pop('preferences')
156 self._disabled_delivery_choices = kwargs.pop(
157 'disabled_delivery_choices', [])
158 kwargs["initial"] = self._preferences
159 super(UserPreferencesFormset, self).__init__(*args, **kwargs)
161 def _construct_form(self, i, **kwargs):
162 form = super(UserPreferencesFormset, self)._construct_form(i, **kwargs)
163 form._preferences = self._preferences[i]
164 form.fields['delivery_status'].widget.disabled_choices = (
165 self._disabled_delivery_choices)
166 return form
168 def save(self):
169 for form in self.forms:
170 form.save()
173 class ManageMemberForm(forms.Form):
175 # RW fields.
176 moderation_action = moderation_action_field()
177 delivery_mode = delivery_mode_field()
179 # TODO: Maybe add Member's preferences here to set other things like
180 # delivery_mode and such?
182 def __init__(self, *args, **kw):
183 self.member = kw.pop('member')
184 super().__init__(*args, **kw)
186 @property
187 def initial(self):
188 return {'moderation_action': self.member.moderation_action,
189 'delivery_mode': self.member.delivery_mode}
191 @initial.setter
192 def initial(self, value):
193 pass
195 def save(self):
196 """Save the data to the Member object by calling into REST API.
198 Also, return True/False to determine if anything was updated or not.
200 if not self.changed_data:
201 return False
202 for each in self.changed_data:
203 updated = self.cleaned_data.get(each)
204 setattr(self.member, each, updated)
205 self.member.save()
206 return True
209 class ManageMemberFormSet(forms.BaseFormSet):
211 def __init__(self, *args, **kw):
212 self._members = kw.pop('members')
213 kw['initial'] = self._members
214 super().__init__(*args, **kw)
216 def get_form_kwargs(self, index):
217 kwargs = super().get_form_kwargs(index)
218 kwargs['member'] = self._members[index]
219 return kwargs
221 def _construct_form(self, i, **kwargs):
222 form = super()._construct_form(i, **kwargs)
223 form.member = self._members[i]
224 return form
226 def save(self):
227 """Save and return the lists for which subs were updated"""
228 updated = []
229 for form in self.forms:
230 was_updated = form.save()
231 if was_updated:
232 updated.append(form.member.list_id)
233 return updated
236 class ManageAddressForm(forms.Form):
237 # RW fields.
238 verified = forms.BooleanField(
239 widget=forms.CheckboxInput(),
240 required=False,
241 label=_('Verified'),
242 help_text=_(
243 'Specifies whether or not this email address is verified'))
245 # TODO: Primary/Preferred Address. Needs integration with EmailAddress
246 # model from Django/Allauth.
248 def __init__(self, *args, **kw):
249 self.address = kw.pop('address')
250 super().__init__(*args, **kw)
252 @property
253 def initial(self):
254 return {'verified': self.address.verified}
256 @initial.setter
257 def initial(self, value):
258 pass
260 def save(self):
261 """Save the data and return if there was anything changed."""
262 if not self.changed_data:
263 return False
264 # Since there is a single field, the below shouldn't raise KeyError. In
265 # future when more fields are added, it can raise KeyError so make sure
266 # to use .get() and not do anything if value is None.
267 verified = self.cleaned_data['verified']
268 if verified:
269 self.address.verify()
270 self._update_django_address(True)
271 return True
272 self.address.unverify()
273 self._update_django_address(False)
274 return True
276 def _update_django_address(self, value):
277 """Verify/Unverify the EmailAddress model in Allauth."""
278 try:
279 email = EmailAddress.objects.get(email=self.address.email)
280 except EmailAddress.DoesNotExist:
281 return
282 email.verified = value
283 email.save()
286 class ManageAddressFormSet(forms.BaseFormSet):
288 def __init__(self, *args, **kw):
289 self._addresses = kw.pop('addresses')
290 kw['initial'] = self._addresses
291 super().__init__(*args, **kw)
293 def get_form_kwargs(self, index):
294 kwargs = super().get_form_kwargs(index)
295 kwargs['address'] = self._addresses[index]
296 return kwargs
298 def _construct_form(self, i, **kwargs):
299 form = super()._construct_form(i, **kwargs)
300 form.address = self._addresses[i]
301 return form
303 def save(self):
304 """Save and return which addresses were updated."""
305 updated = []
306 for form in self.forms:
307 was_updated = form.save()
308 if was_updated:
309 updated.append(form.address.email)
310 return updated
313 class ManageUserForm(forms.Form):
315 display_name = forms.CharField(
316 label=_('Display Name'),
317 required=True)
319 def __init__(self, *args, **kw):
320 self.user = kw.pop('user')
321 super().__init__(*args, **kw)
323 @property
324 def initial(self):
325 return {'display_name': self.user.display_name}
327 @initial.setter
328 def initial(self, value):
329 pass
331 def save(self):
332 if not self.changed_data:
333 return
334 new_name = self.cleaned_data.get('display_name')
335 self.user.display_name = new_name
336 self.user.save()
337 for addr in self.user.addresses:
338 addr.display_name = new_name
339 addr.save()