1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2017-2022 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)
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
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
):
36 Form handling the user's global, address and subscription based preferences
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
)
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 {}
55 def initial(self
, value
):
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
),
65 label
=_('Receive own postings'),
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 '
71 acknowledge_posts
= forms
.NullBooleanField(
72 widget
=NullBooleanRadioSelect(choices
=choices
),
74 label
=_('Acknowledge posts'),
76 'Receive acknowledgement mail when you send mail to the list?'))
77 hide_address
= forms
.NullBooleanField(
78 widget
=NullBooleanRadioSelect(choices
=choices
),
80 label
=_('Hide address'),
82 'When someone views the list membership, your email address is '
83 'normally shown (in an obscured fashion to thwart spam '
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
),
90 label
=_('Receive list copies (possible duplicates)'),
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
),
101 label
=_('Preferred language'),
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.'))
111 Class to define the name of the fieldsets and what should be
114 layout
= [["User Preferences", "acknowledge_posts", "hide_address",
115 "receive_list_copy", "receive_own_postings",
116 "delivery_mode", "delivery_status", "preferred_language"]]
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
:
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"
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
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.
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.
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
)
169 for form
in self
.forms
:
173 class ManageMemberForm(forms
.Form
):
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
)
188 return {'moderation_action': self
.member
.moderation_action
,
189 'delivery_mode': self
.member
.delivery_mode
}
192 def initial(self
, value
):
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
:
202 for each
in self
.changed_data
:
203 updated
= self
.cleaned_data
.get(each
)
204 setattr(self
.member
, each
, updated
)
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
]
221 def _construct_form(self
, i
, **kwargs
):
222 form
= super()._construct
_form
(i
, **kwargs
)
223 form
.member
= self
._members
[i
]
227 """Save and return the lists for which subs were updated"""
229 for form
in self
.forms
:
230 was_updated
= form
.save()
232 updated
.append(form
.member
.list_id
)
236 class ManageAddressForm(forms
.Form
):
238 verified
= forms
.BooleanField(
239 widget
=forms
.CheckboxInput(),
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
)
254 return {'verified': self
.address
.verified
}
257 def initial(self
, value
):
261 """Save the data and return if there was anything changed."""
262 if not self
.changed_data
:
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']
269 self
.address
.verify()
270 self
._update
_django
_address
(True)
272 self
.address
.unverify()
273 self
._update
_django
_address
(False)
276 def _update_django_address(self
, value
):
277 """Verify/Unverify the EmailAddress model in Allauth."""
279 email
= EmailAddress
.objects
.get(email
=self
.address
.email
)
280 except EmailAddress
.DoesNotExist
:
282 email
.verified
= value
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
]
298 def _construct_form(self
, i
, **kwargs
):
299 form
= super()._construct
_form
(i
, **kwargs
)
300 form
.address
= self
._addresses
[i
]
304 """Save and return which addresses were updated."""
306 for form
in self
.forms
:
307 was_updated
= form
.save()
309 updated
.append(form
.address
.email
)
313 class ManageUserForm(forms
.Form
):
315 display_name
= forms
.CharField(
316 label
=_('Display Name'),
319 def __init__(self
, *args
, **kw
):
320 self
.user
= kw
.pop('user')
321 super().__init
__(*args
, **kw
)
325 return {'display_name': self
.user
.display_name
}
328 def initial(self
, value
):
332 if not self
.changed_data
:
334 new_name
= self
.cleaned_data
.get('display_name')
335 self
.user
.display_name
= new_name
337 for addr
in self
.user
.addresses
:
338 addr
.display_name
= new_name