1 # -*- coding: utf-8 -*-
2 # Copyright (C) 1998-2018 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/>.
24 from allauth
.account
.models
import EmailAddress
25 from django
.http
import HttpResponse
, HttpResponseNotAllowed
, Http404
26 from django
.conf
import settings
27 from django
.contrib
import messages
28 from django
.contrib
.auth
.decorators
import login_required
29 from django
.core
.validators
import validate_email
30 from django
.forms
import formset_factory
31 from django
.shortcuts
import render
, redirect
32 from django
.core
.exceptions
import ValidationError
33 from django
.utils
.decorators
import method_decorator
34 from django
.utils
.translation
import gettext
as _
35 from django_mailman3
.lib
.mailman
import get_mailman_client
36 from django_mailman3
.lib
.paginator
import paginate
, MailmanPaginator
37 from django
.utils
.six
.moves
.urllib
.error
import HTTPError
39 from postorius
.forms
import (
40 ListNew
, MemberForm
, ListSubscribe
, MultipleChoiceForm
, UserPreferences
,
41 ListSubscriptionPolicyForm
, ArchiveSettingsForm
, MessageAcceptanceForm
,
42 DigestSettingsForm
, AlterMessagesForm
, ListAutomaticResponsesForm
,
43 ListIdentityForm
, ListMassSubscription
, ListMassRemoval
, ListAddBanForm
,
44 ListHeaderMatchForm
, ListHeaderMatchFormset
, MemberModeration
,
45 DMARCMitigationsForm
, ListAnonymousSubscribe
)
46 from postorius
.models
import Domain
, List
, Mailman404Error
, Style
47 from postorius
.auth
.decorators
import (
48 list_owner_required
, list_moderator_required
, superuser_required
)
49 from postorius
.views
.generic
import MailingListView
52 from django
.core
.urlresolvers
import reverse
54 from django
.urls
import reverse
57 logger
= logging
.getLogger(__name__
)
62 def list_members_view(request
, list_id
, role
=None):
63 """Display all members of a given list."""
64 if role
not in ['owner', 'moderator', 'subscriber']:
65 return redirect('list_members', list_id
, 'subscriber')
66 mailing_list
= List
.objects
.get_or_404(fqdn_listname
=list_id
)
67 if request
.method
== 'POST':
68 if role
== 'subscriber':
69 form
= MultipleChoiceForm(request
.POST
)
71 members
= form
.cleaned_data
['choices']
72 for member
in members
:
73 mailing_list
.unsubscribe(member
)
74 messages
.success(request
, _('The selected members'
75 ' have been unsubscribed'))
76 return redirect('list_members', list_id
, role
)
78 member_form
= MemberForm(request
.POST
)
79 if member_form
.is_valid():
81 getattr(mailing_list
, 'add_%s' % role
)(
82 member_form
.cleaned_data
['email'])
84 request
, _('%(email)s has been added'
85 ' with the role %(role)s')
86 % {'email': member_form
.cleaned_data
['email'],
88 return redirect('list_members', list_id
, role
)
89 except HTTPError
as e
:
90 messages
.error(request
, _(e
.msg
))
92 form
= MultipleChoiceForm()
93 member_form
= MemberForm()
98 if role
== 'subscriber':
99 context
['page_title'] = _('List subscribers')
100 if request
.GET
.get('q'):
101 query
= context
['query'] = request
.GET
['q']
103 query
= '*{}*'.format(query
)
105 # Proxy the find_members method to insert the query
106 def find_method(count
, page
):
107 return mailing_list
.find_members(query
, count
=count
, page
=page
)
109 find_method
= mailing_list
.get_member_page
110 context
['members'] = paginate(
112 request
.GET
.get('page'),
113 request
.GET
.get('count', 25),
114 paginator_class
=MailmanPaginator
)
115 if mailing_list
.member_count
== 0:
116 context
['empty_error'] = _('List has no Subscribers')
118 context
['empty_error'] =\
119 _('No member was found matching the search')
120 context
['form'] = form
122 context
['member_form'] = member_form
124 context
['page_title'] = _('List owners')
125 context
['members'] = mailing_list
.owners
126 context
['form_action'] = _('Add owner')
127 elif role
== 'moderator':
128 context
['page_title'] = _('List moderators')
129 context
['members'] = mailing_list
.moderators
130 context
['empty_error'] = _('List has no moderators')
131 context
['form_action'] = _('Add moderator')
132 return render(request
, 'postorius/lists/members.html', context
)
137 def list_member_options(request
, list_id
, email
):
138 template_name
= 'postorius/lists/memberoptions.html'
139 client
= get_mailman_client()
140 mm_list
= List
.objects
.get_or_404(fqdn_listname
=list_id
)
142 mm_member
= client
.get_member(list_id
, email
)
143 member_prefs
= mm_member
.preferences
145 raise Http404(_('Member does not exist'))
146 except Mailman404Error
:
147 return render(request
, template_name
, {'nolists': 'true'})
148 initial_moderation
= dict([
149 (key
, getattr(mm_member
, key
)) for key
in MemberModeration
.base_fields
151 if request
.method
== 'POST':
152 if request
.POST
.get("formname") == 'preferences':
153 moderation_form
= MemberModeration(initial
=initial_moderation
)
154 preferences_form
= UserPreferences(
155 request
.POST
, initial
=member_prefs
)
156 if preferences_form
.is_valid():
157 if not preferences_form
.has_changed():
158 messages
.info(request
,
159 _("No change to the member's preferences."))
160 return redirect('list_member_options', list_id
, email
)
161 for key
in list(preferences_form
.fields
.keys()):
162 member_prefs
[key
] = preferences_form
.cleaned_data
[key
]
165 except HTTPError
as e
:
166 messages
.error(request
, e
.msg
)
168 messages
.success(request
, _("The member's preferences have"
170 return redirect('list_member_options', list_id
, email
)
171 elif request
.POST
.get("formname") == 'moderation':
172 preferences_form
= UserPreferences(initial
=member_prefs
)
173 moderation_form
= MemberModeration(
174 request
.POST
, initial
=initial_moderation
)
175 if moderation_form
.is_valid():
176 if not moderation_form
.has_changed():
177 messages
.info(request
,
178 _("No change to the member's moderation."))
179 return redirect('list_member_options', list_id
, email
)
180 for key
in list(moderation_form
.fields
.keys()):
181 setattr(mm_member
, key
, moderation_form
.cleaned_data
[key
])
184 except HTTPError
as e
:
185 messages
.error(request
, e
.msg
)
187 messages
.success(request
, _("The member's moderation "
188 "settings have been updated."))
189 return redirect('list_member_options', list_id
, email
)
191 preferences_form
= UserPreferences(initial
=member_prefs
)
192 moderation_form
= MemberModeration(initial
=initial_moderation
)
193 return render(request
, template_name
, {
194 'mm_member': mm_member
,
196 'preferences_form': preferences_form
,
197 'moderation_form': moderation_form
,
201 class ListSummaryView(MailingListView
):
202 """Shows common list metrics.
205 def get(self
, request
, list_id
):
206 data
= {'list': self
.mailing_list
,
207 'user_subscribed': False,
208 'subscribed_address': None,
209 'public_archive': False,
210 'hyperkitty_enabled': False}
211 if self
.mailing_list
.settings
['archive_policy'] == 'public':
212 data
['public_archive'] = True
213 if getattr(settings
, 'TESTING', False) and \
214 'hyperkitty' not in settings
.INSTALLED_APPS
:
215 # avoid systematic test failure when HyperKitty is installed
216 # (missing VCR request, see below).
217 list(self
.mailing_list
.archivers
)
218 if ('hyperkitty' in settings
.INSTALLED_APPS
and
219 'hyperkitty' in self
.mailing_list
.archivers
and
220 self
.mailing_list
.archivers
['hyperkitty']):
221 data
['hyperkitty_enabled'] = True
222 if request
.user
.is_authenticated
:
223 user_emails
= EmailAddress
.objects
.filter(
224 user
=request
.user
, verified
=True).order_by(
225 "email").values_list("email", flat
=True)
226 pending_requests
= [r
['email'] for r
in self
.mailing_list
.requests
]
227 for address
in user_emails
:
228 if address
in pending_requests
:
229 data
['user_request_pending'] = True
232 self
.mailing_list
.get_member(address
)
236 data
['user_subscribed'] = True
237 data
['subscribed_address'] = address
238 break # no need to test more addresses
239 data
['subscribe_form'] = ListSubscribe(user_emails
)
242 data
['anonymous_subscription_form'] = ListAnonymousSubscribe()
243 return render(request
, 'postorius/lists/summary.html', data
)
246 class ChangeSubscriptionView(MailingListView
):
247 """Change mailing list subscription
250 @method_decorator(login_required
)
251 def post(self
, request
, list_id
):
253 user_emails
= EmailAddress
.objects
.filter(
254 user
=request
.user
, verified
=True).order_by(
255 "email").values_list("email", flat
=True)
256 form
= ListSubscribe(user_emails
, request
.POST
)
257 # Find the currently subscribed email
259 for address
in user_emails
:
261 self
.mailing_list
.get_member(address
)
266 break # no need to test more addresses
267 assert old_email
is not None
269 email
= form
.cleaned_data
['email']
270 if old_email
== email
:
271 messages
.error(request
, _('You are already subscribed'))
273 self
.mailing_list
.unsubscribe(old_email
)
274 # Since the action is done via the web UI, no email
275 # confirmation is needed.
276 response
= self
.mailing_list
.subscribe(
277 email
, pre_confirmed
=True)
278 if (type(response
) == dict and
279 response
.get('token_owner') == 'moderator'):
281 request
, _('Your request to change the email for'
282 ' this subscription was submitted and'
283 ' is waiting for moderator approval.'))
285 messages
.success(request
,
286 _('Subscription changed to %s') %
289 messages
.error(request
,
290 _('Something went wrong. Please try again.'))
291 except HTTPError
as e
:
292 messages
.error(request
, e
.msg
)
293 return redirect('list_summary', self
.mailing_list
.list_id
)
296 class ListSubscribeView(MailingListView
):
298 view name: `list_subscribe`
301 @method_decorator(login_required
)
302 def post(self
, request
, list_id
):
304 Subscribes an email address to a mailing list via POST and
305 redirects to the `list_summary` view.
308 user_emails
= EmailAddress
.objects
.filter(
309 user
=request
.user
, verified
=True).order_by(
310 "email").values_list("email", flat
=True)
311 form
= ListSubscribe(user_emails
, request
.POST
)
313 email
= request
.POST
.get('email')
314 response
= self
.mailing_list
.subscribe(
315 email
, pre_verified
=True, pre_confirmed
=True)
316 if (type(response
) == dict and
317 response
.get('token_owner') == 'moderator'):
319 request
, _('Your subscription request has been'
320 ' submitted and is waiting for moderator'
323 messages
.success(request
,
324 _('You are subscribed to %s.') %
325 self
.mailing_list
.fqdn_listname
)
327 messages
.error(request
,
328 _('Something went wrong. Please try again.'))
329 except HTTPError
as e
:
330 messages
.error(request
, e
.msg
)
331 return redirect('list_summary', self
.mailing_list
.list_id
)
334 class ListAnonymousSubscribeView(MailingListView
):
336 view name: `list_anonymous_subscribe`
339 def post(self
, request
, list_id
):
341 Subscribes an email address to a mailing list via POST and
342 redirects to the `list_summary` view.
343 This view is used for unauthenticated users and asks Mailman core to
344 verify the supplied email address.
347 form
= ListAnonymousSubscribe(request
.POST
)
349 email
= form
.cleaned_data
.get('email')
350 self
.mailing_list
.subscribe(email
, pre_verified
=False,
352 messages
.success(request
, _('Please check your inbox for '
353 'further instructions'))
355 messages
.error(request
,
356 _('Something went wrong. Please try again.'))
357 except HTTPError
as e
:
358 messages
.error(request
, e
.msg
)
359 return redirect('list_summary', self
.mailing_list
.list_id
)
362 class ListUnsubscribeView(MailingListView
):
364 """Unsubscribe from a mailing list."""
366 @method_decorator(login_required
)
367 def post(self
, request
, *args
, **kwargs
):
368 email
= request
.POST
['email']
370 self
.mailing_list
.unsubscribe(email
)
371 messages
.success(request
, _('%s has been unsubscribed'
372 ' from this list.') % email
)
373 except ValueError as e
:
374 messages
.error(request
, e
)
375 return redirect('list_summary', self
.mailing_list
.list_id
)
380 def list_mass_subscribe(request
, list_id
):
381 mailing_list
= List
.objects
.get_or_404(fqdn_listname
=list_id
)
382 if request
.method
== 'POST':
383 form
= ListMassSubscription(request
.POST
)
385 for data
in form
.cleaned_data
['emails']:
387 # Parse the data to get the address and the display name
388 display_name
, address
= email
.utils
.parseaddr(data
)
389 validate_email(address
)
390 mailing_list
.subscribe(address
=address
,
391 display_name
=display_name
,
396 request
, _('The address %(address)s has been'
397 ' subscribed to %(list)s.') %
399 'list': mailing_list
.fqdn_listname
})
400 except HTTPError
as e
:
401 messages
.error(request
, e
)
402 except ValidationError
:
403 messages
.error(request
, _('The email address %s'
404 ' is not valid.') % address
)
406 form
= ListMassSubscription()
407 return render(request
, 'postorius/lists/mass_subscribe.html',
408 {'form': form
, 'list': mailing_list
})
411 class ListMassRemovalView(MailingListView
):
413 """Class For Mass Removal"""
415 @method_decorator(login_required
)
416 @method_decorator(list_owner_required
)
417 def get(self
, request
, *args
, **kwargs
):
418 form
= ListMassRemoval()
419 return render(request
, 'postorius/lists/mass_removal.html',
420 {'form': form
, 'list': self
.mailing_list
})
422 @method_decorator(list_owner_required
)
423 def post(self
, request
, *args
, **kwargs
):
424 form
= ListMassRemoval(request
.POST
)
425 if not form
.is_valid():
426 messages
.error(request
, _('Please fill out the form correctly.'))
428 for address
in form
.cleaned_data
['emails']:
430 validate_email(address
)
431 self
.mailing_list
.unsubscribe(address
.lower())
433 request
, _('The address %(address)s has been'
434 ' unsubscribed from %(list)s.') %
436 'list': self
.mailing_list
.fqdn_listname
})
437 except (HTTPError
, ValueError) as e
:
438 messages
.error(request
, e
)
439 except ValidationError
:
440 messages
.error(request
, _('The email address %s'
441 ' is not valid.') % address
)
442 return redirect('mass_removal', self
.mailing_list
.list_id
)
445 def _perform_action(message_ids
, action
):
446 for message_id
in message_ids
:
451 @list_moderator_required
452 def list_moderation(request
, list_id
, held_id
=-1):
453 mailing_list
= List
.objects
.get_or_404(fqdn_listname
=list_id
)
454 if request
.method
== 'POST':
455 form
= MultipleChoiceForm(request
.POST
)
457 message_ids
= form
.cleaned_data
['choices']
459 if 'accept' in request
.POST
:
460 _perform_action(message_ids
, mailing_list
.accept_message
)
461 messages
.success(request
,
462 _('The selected messages were accepted'))
463 elif 'reject' in request
.POST
:
464 _perform_action(message_ids
, mailing_list
.reject_message
)
465 messages
.success(request
,
466 _('The selected messages were rejected'))
467 elif 'discard' in request
.POST
:
468 _perform_action(message_ids
, mailing_list
.discard_message
)
469 messages
.success(request
,
470 _('The selected messages were discarded'))
472 messages
.error(request
, _('Message could not be found'))
474 form
= MultipleChoiceForm()
475 held_messages
= paginate(
476 mailing_list
.get_held_page
,
477 request
.GET
.get('page'), request
.GET
.get('count'),
478 paginator_class
=MailmanPaginator
)
480 'list': mailing_list
,
481 'held_messages': held_messages
,
484 return render(request
, 'postorius/lists/held_messages.html', context
)
488 @list_moderator_required
489 def moderate_held_message(request
, list_id
):
490 if request
.method
!= 'POST':
491 return HttpResponseNotAllowed(['POST'])
492 msg_id
= request
.POST
['msgid']
493 mailing_list
= List
.objects
.get_or_404(fqdn_listname
=list_id
)
494 if 'accept' in request
.POST
:
495 mailing_list
.accept_message(msg_id
)
496 messages
.success(request
, _('The message was accepted'))
497 elif 'reject' in request
.POST
:
498 mailing_list
.reject_message(msg_id
)
499 messages
.success(request
, _('The message was rejected'))
500 elif 'discard' in request
.POST
:
501 mailing_list
.discard_message(msg_id
)
502 messages
.success(request
, _('The message was discarded'))
503 return redirect('list_held_messages', list_id
)
508 def csv_view(request
, list_id
):
509 """Export all the subscriber in csv
511 mm_lists
= List
.objects
.get_or_404(fqdn_listname
=list_id
)
513 response
= HttpResponse(content_type
='text/csv')
514 response
['Content-Disposition'] = (
515 'attachment; filename="Subscribers.csv"')
517 writer
= csv
.writer(response
)
519 for i
in mm_lists
.members
:
520 writer
.writerow([i
.email
])
525 def _get_choosable_domains(request
):
526 domains
= Domain
.objects
.all()
527 return [(d
.mail_host
, d
.mail_host
) for d
in domains
]
530 def _get_choosable_styles(request
):
531 styles
= Style
.objects
.all()
532 options
= [(style
['name'], style
['description'])
533 for style
in styles
['styles']]
534 # Reorder to put the default at the beginning
535 for style_option
in options
:
536 if style_option
[0] == styles
['default']:
537 options
.remove(style_option
)
538 options
.insert(0, style_option
)
545 def list_new(request
, template
='postorius/lists/new.html'):
547 Add a new mailing list.
548 If the request to the function is a GET request an empty form for
549 creating a new list will be displayed. If the request method is
550 POST the form will be evaluated. If the form is considered
551 correct the list gets created and otherwise the form with the data
552 filled in before the last POST request is returned. The user must
553 be logged in to create a new list.
556 choosable_domains
= [('', _('Choose a Domain'))]
557 choosable_domains
+= _get_choosable_domains(request
)
558 choosable_styles
= _get_choosable_styles(request
)
559 if request
.method
== 'POST':
560 form
= ListNew(choosable_domains
, choosable_styles
, request
.POST
)
563 domain
= Domain
.objects
.get_or_404(
564 mail_host
=form
.cleaned_data
['mail_host'])
567 mailing_list
= domain
.create_list(
568 form
.cleaned_data
['listname'],
569 style_name
=form
.cleaned_data
['list_style'])
570 mailing_list
.add_owner(form
.cleaned_data
['list_owner'])
571 list_settings
= mailing_list
.settings
572 if form
.cleaned_data
['description']:
573 list_settings
["description"] = \
574 form
.cleaned_data
['description']
575 list_settings
["advertised"] = form
.cleaned_data
['advertised']
577 messages
.success(request
, _("List created"))
578 return redirect("list_summary",
579 list_id
=mailing_list
.list_id
)
580 # TODO catch correct Error class:
581 except HTTPError
as e
:
582 # Right now, there is no good way to detect that this is a
583 # duplicate mailing list request other than checking the
584 # reason for 400 error.
585 if e
.reason
== b
'Mailing list exists':
587 'listname', _('Mailing List already exists.'))
588 return render(request
, template
, {'form': form
})
589 # Otherwise just render the generic error page.
590 return render(request
, 'postorius/errors/generic.html',
593 messages
.error(request
, _('Please check the errors below'))
595 form
= ListNew(choosable_domains
, choosable_styles
, initial
={
596 'list_owner': request
.user
.email
,
599 return render(request
, template
, {'form': form
})
602 def list_index(request
, template
='postorius/index.html'):
603 """Show a table of all public mailing lists.
605 # TODO maxking: Figure out why does this view accept POST request and why
606 # can't it be just a GET with list parameter.
607 if request
.method
== 'POST':
608 return redirect("list_summary", list_id
=request
.POST
["list"])
610 def _get_list_page(count
, page
):
611 client
= get_mailman_client()
612 advertised
= not request
.user
.is_superuser
613 return client
.get_list_page(
614 advertised
=advertised
, count
=count
, page
=page
)
616 _get_list_page
, request
.GET
.get('page'), request
.GET
.get('count'),
617 paginator_class
=MailmanPaginator
)
618 choosable_domains
= _get_choosable_domains(request
)
619 return render(request
, template
,
621 'domain_count': len(choosable_domains
)})
626 def list_delete(request
, list_id
):
627 """Deletes a list but asks for confirmation first.
629 the_list
= List
.objects
.get_or_404(fqdn_listname
=list_id
)
630 if request
.method
== 'POST':
632 return redirect("list_index")
634 submit_url
= reverse('list_delete',
635 kwargs
={'list_id': list_id
})
636 cancel_url
= reverse('list_index',)
637 return render(request
, 'postorius/lists/confirm_delete.html',
638 {'submit_url': submit_url
, 'cancel_url': cancel_url
,
643 @list_moderator_required
644 def list_subscription_requests(request
, list_id
):
645 """Shows a list of subscription requests.
647 m_list
= List
.objects
.get_or_404(fqdn_listname
=list_id
)
648 return render(request
, 'postorius/lists/subscription_requests.html',
653 @list_moderator_required
654 def handle_subscription_request(request
, list_id
, request_id
, action
):
656 Handle a subscription request. Possible actions:
662 confirmation_messages
= {
663 'accept': _('The request has been accepted.'),
664 'reject': _('The request has been rejected.'),
665 'discard': _('The request has been discarded.'),
666 'defer': _('The request has been defered.'),
668 assert action
in confirmation_messages
670 m_list
= List
.objects
.get_or_404(fqdn_listname
=list_id
)
671 # Moderate request and add feedback message to session.
672 m_list
.moderate_request(request_id
, action
)
673 messages
.success(request
, confirmation_messages
[action
])
674 except HTTPError
as e
:
676 messages
.success(request
,
677 _('The request was already moderated: %s')
680 messages
.error(request
, _('The request could not be moderated: %s')
682 return redirect('list_subscription_requests', m_list
.list_id
)
685 SETTINGS_SECTION_NAMES
= (
686 ('list_identity', _('List Identity')),
687 ('automatic_responses', _('Automatic Responses')),
688 ('alter_messages', _('Alter Messages')),
689 ('dmarc_mitigations', _('DMARC Mitigations')),
690 ('digest', _('Digest')),
691 ('message_acceptance', _('Message Acceptance')),
692 ('archiving', _('Archiving')),
693 ('subscription_policy', _('Subscription Policy')),
697 'list_identity': ListIdentityForm
,
698 'automatic_responses': ListAutomaticResponsesForm
,
699 'alter_messages': AlterMessagesForm
,
700 'dmarc_mitigations': DMARCMitigationsForm
,
701 'digest': DigestSettingsForm
,
702 'message_acceptance': MessageAcceptanceForm
,
703 'archiving': ArchiveSettingsForm
,
704 'subscription_policy': ListSubscriptionPolicyForm
,
710 def list_settings(request
, list_id
=None, visible_section
=None,
711 template
='postorius/lists/settings.html'):
713 View and edit the settings of a list.
714 The function requires the user to be logged in and have the
715 permissions necessary to perform the action.
717 Use /<NAMEOFTHESECTION>/<NAMEOFTHEOPTION>
718 to show only parts of the settings
719 <param> is optional / is used to differ in between section and option might
720 result in using //option
722 if visible_section
is None:
723 visible_section
= 'list_identity'
725 form_class
= SETTINGS_FORMS
[visible_section
]
727 raise Http404('No such settings section')
728 m_list
= List
.objects
.get_or_404(fqdn_listname
=list_id
)
729 list_settings
= m_list
.settings
731 (key
, str(value
)) for key
, value
in list(list_settings
.items()))
732 # List settings are grouped an processed in different forms.
733 if request
.method
== 'POST':
734 form
= form_class(request
.POST
, mlist
=m_list
, initial
=initial_data
)
737 for key
in form
.changed_data
:
738 if key
in form_class
.mlist_properties
:
739 setattr(m_list
, key
, form
.cleaned_data
[key
])
741 list_settings
[key
] = form
.cleaned_data
[key
]
743 messages
.success(request
, _('The settings have been updated.'))
744 except HTTPError
as e
:
745 messages
.error(request
, _('An error occured: %s') % e
.reason
)
746 return redirect('list_settings', m_list
.list_id
, visible_section
)
748 form
= form_class(initial
=initial_data
, mlist
=m_list
)
750 return render(request
, template
, {
752 'section_names': SETTINGS_SECTION_NAMES
,
754 'visible_section': visible_section
,
760 def remove_role(request
, list_id
=None, role
=None, address
=None,
761 template
='postorius/lists/confirm_remove_role.html'):
762 """Removes a list moderator or owner."""
763 the_list
= List
.objects
.get_or_404(fqdn_listname
=list_id
)
765 redirect_on_success
= redirect('list_members', the_list
.list_id
, role
)
767 roster
= getattr(the_list
, '{}s'.format(role
))
768 if address
not in roster
:
769 messages
.error(request
,
770 _('The user %(email)s is not in the %(role)s group')
771 % {'email': address
, 'role': role
})
772 return redirect('list_members', the_list
.list_id
, role
)
776 messages
.error(request
, _('Removing the last owner is impossible'))
777 return redirect('list_members', the_list
.list_id
, role
)
778 user_emails
= EmailAddress
.objects
.filter(
779 user
=request
.user
, verified
=True).order_by(
780 "email").values_list("email", flat
=True)
781 if address
in user_emails
:
782 # The user is removing themselves, redirect to the list info page
783 # because they won't have access to the members page anyway.
784 redirect_on_success
= redirect('list_summary', the_list
.list_id
)
786 if request
.method
== 'POST':
788 the_list
.remove_role(role
, address
)
789 except HTTPError
as e
:
790 messages
.error(request
, _('The user could not be removed: %(msg)s')
792 return redirect('list_members', the_list
.list_id
, role
)
793 messages
.success(request
, _('The user %(address)s has been removed'
794 ' from the %(role)s group.')
795 % {'address': address
, 'role': role
})
796 return redirect_on_success
797 return render(request
, template
,
798 {'role': role
, 'address': address
,
799 'list_id': the_list
.list_id
})
804 def remove_all_subscribers(request
, list_id
):
806 """Empty the list by unsubscribing all members."""
808 mlist
= List
.objects
.get_or_404(fqdn_listname
=list_id
)
809 if len(mlist
.members
) == 0:
810 messages
.error(request
,
811 _('No member is subscribed to the list currently.'))
812 return redirect('mass_removal', mlist
.list_id
)
813 if request
.method
== 'POST':
815 # TODO maxking: This doesn't scale. Either the Core should provide
816 # an endpoint to remove all subscribers or there should be some
817 # better way to do this. Maybe, Core can take a list of email
818 # addresses in batches of 50 and unsubscribe all of them.
819 for names
in mlist
.members
:
820 mlist
.unsubscribe(names
.email
)
821 messages
.success(request
, _('All members have been'
822 ' unsubscribed from the list.'))
823 return redirect('list_members', mlist
.list_id
)
824 except Exception as e
:
825 messages
.error(request
, e
)
826 return render(request
,
827 'postorius/lists/confirm_removeall_subscribers.html',
833 def list_bans(request
, list_id
):
835 Ban or unban email addresses.
837 # Get the list and cache the archivers property.
838 m_list
= List
.objects
.get_or_404(fqdn_listname
=list_id
)
839 ban_list
= m_list
.bans
841 # Process form submission.
842 if request
.method
== 'POST':
843 if 'add' in request
.POST
:
844 addban_form
= ListAddBanForm(request
.POST
)
845 if addban_form
.is_valid():
847 ban_list
.add(addban_form
.cleaned_data
['email'])
848 messages
.success(request
, _(
849 'The email {} has been banned.'.format(
850 addban_form
.cleaned_data
['email'])))
851 except HTTPError
as e
:
853 request
, _('An error occured: %s') % e
.reason
)
854 except ValueError as e
:
855 messages
.error(request
, _('Invalid data: %s') % e
)
856 return redirect('list_bans', list_id
)
857 elif 'del' in request
.POST
:
859 ban_list
.remove(request
.POST
['email'])
860 messages
.success(request
, _(
861 'The email {} has been un-banned'.format(
862 request
.POST
['email'])))
863 except HTTPError
as e
:
864 messages
.error(request
, _('An error occured: %s') % e
.reason
)
865 except ValueError as e
:
866 messages
.error(request
, _('Invalid data: %s') % e
)
867 return redirect('list_bans', list_id
)
869 addban_form
= ListAddBanForm()
870 banned_addresses
= paginate(
871 list(ban_list
), request
.GET
.get('page'), request
.GET
.get('count'))
872 return render(request
, 'postorius/lists/bans.html',
874 'addban_form': addban_form
,
875 'banned_addresses': banned_addresses
,
881 def list_header_matches(request
, list_id
):
883 View and edit the list's header matches.
885 m_list
= List
.objects
.get_or_404(fqdn_listname
=list_id
)
886 header_matches
= m_list
.header_matches
887 HeaderMatchFormset
= formset_factory(
888 ListHeaderMatchForm
, extra
=1, can_delete
=True, can_order
=True,
889 formset
=ListHeaderMatchFormset
)
892 (key
, getattr(hm
, key
)) for key
in ListHeaderMatchForm
.base_fields
893 ]) for hm
in header_matches
]
895 # Process form submission.
896 if request
.method
== 'POST':
897 formset
= HeaderMatchFormset(request
.POST
, initial
=initial_data
)
898 if formset
.is_valid():
899 if not formset
.has_changed():
900 return redirect('list_header_matches', list_id
)
901 # Purge the existing header_matches
902 header_matches
.clear()
903 # Add the ones in the form
906 # If ORDER is None (new header match), add it last.
907 return f
.cleaned_data
.get('ORDER') or len(formset
.forms
)
909 for form
in sorted(formset
, key
=form_order
):
910 if 'header' not in form
.cleaned_data
:
911 # The new header match form was not filled
913 if form
.cleaned_data
.get('DELETE'):
917 header
=form
.cleaned_data
['header'],
918 pattern
=form
.cleaned_data
['pattern'],
919 action
=form
.cleaned_data
['action'],
921 except HTTPError
as e
:
925 request
, _('An error occured: %s') % e
.reason
)
927 messages
.success(request
, _('The header matches were'
928 ' successfully modified.'))
929 return redirect('list_header_matches', list_id
)
931 formset
= HeaderMatchFormset(initial
=initial_data
)
932 # Adapt the last form to create new matches
933 form_new
= formset
.forms
[-1]
934 form_new
.fields
['header'].widget
.attrs
['placeholder'] = _('New header')
935 form_new
.fields
['pattern'].widget
.attrs
['placeholder'] = _('New pattern')
936 del form_new
.fields
['ORDER']
937 del form_new
.fields
['DELETE']
939 return render(request
, 'postorius/lists/header_matches.html', {