From 5429055f0d886bf6b2aaaeaea0e482097871e425 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Sun, 25 Jan 2015 22:07:54 +0100 Subject: [PATCH] implement mail address target editing - extract common code into managemails.forms.MailAddressFieldMixin - move code from forms into managemails.models.MailAddress - implement managemails.models.MailboxManager.unused and unused_or_own - implement managemails.forms.EditMailAddressForm - add managemails.views.EditMailAddress - add URL pattern 'edit_mailaddress' to managemails.urls - add template managemails/mailaddress_edit.html - add changelog entry --- docs/changelog.rst | 1 + gnuviechadmin/managemails/forms.py | 121 ++++++++++++++---- gnuviechadmin/managemails/models.py | 88 +++++++++++++ gnuviechadmin/managemails/urls.py | 4 + gnuviechadmin/managemails/views.py | 55 ++++++++ .../managemails/mailaddress_edit.html | 26 ++++ 6 files changed, 269 insertions(+), 26 deletions(-) create mode 100644 gnuviechadmin/templates/managemails/mailaddress_edit.html diff --git a/docs/changelog.rst b/docs/changelog.rst index dd5830c..9b26bae 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,7 @@ Changelog ========= +* :feature:`-` implement mail address target editing * :feature:`-` implement mail address deletion * :feature:`-` implement adding mail address to mail domains * :feature:`-` implement adding options to hosting packages diff --git a/gnuviechadmin/managemails/forms.py b/gnuviechadmin/managemails/forms.py index ba723e7..baa6273 100644 --- a/gnuviechadmin/managemails/forms.py +++ b/gnuviechadmin/managemails/forms.py @@ -19,8 +19,6 @@ from crispy_forms.layout import ( from .models import ( MailAddress, - MailAddressForward, - MailAddressMailbox, Mailbox, ) from gvawebcore.forms import PasswordModelFormMixin @@ -106,9 +104,9 @@ def multiple_email_validator(value): return value -class AddMailAddressForm(forms.ModelForm): +class MailAddressFieldMixin(forms.Form): """ - This form is used to add a new mail address. + This mixin defines form fields common to mail address forms. """ mailbox_or_forwards = forms.TypedChoiceField( @@ -133,6 +131,12 @@ class AddMailAddressForm(forms.ModelForm): validators=[multiple_email_validator], ) + +class AddMailAddressForm(forms.ModelForm, MailAddressFieldMixin): + """ + This form is used to add a new mail address. + + """ class Meta: model = MailAddress fields = ['localpart'] @@ -141,8 +145,8 @@ class AddMailAddressForm(forms.ModelForm): self.maildomain = kwargs.pop('maildomain') self.hosting_package = kwargs.pop('hostingpackage') super(AddMailAddressForm, self).__init__(*args, **kwargs) - self.fields['mailbox'].queryset = Mailbox.objects.filter( - active=True, osuser=self.hosting_package.osuser, + self.fields['mailbox'].queryset = Mailbox.objects.unused( + osuser=self.hosting_package.osuser, ) self.helper = FormHelper() @@ -168,7 +172,7 @@ class AddMailAddressForm(forms.ModelForm): ), css_class='row', ), - Submit('submit', _('Add mailaddress')), + Submit('submit', _('Add mail address')), ) def clean_localpart(self): @@ -186,11 +190,10 @@ class AddMailAddressForm(forms.ModelForm): data = self.cleaned_data if data['mailbox_or_forwards'] == MAILBOX_OR_FORWARDS.mailbox: if not data['mailbox']: - raise forms.ValidationError(_('No mailbox selected')) + self.add_error('mailbox', _('No mailbox selected')) elif data['mailbox_or_forwards'] == MAILBOX_OR_FORWARDS.forwards: - if 'forwards' not in data: - raise forms.ValidationError( - _('No forward addresses selected')) + if 'forwards' not in data or not data['forwards']: + self.add_error('forwards', _('No forward addresses selected')) else: raise forms.ValidationError( _('Illegal choice for target of the mail address')) @@ -205,21 +208,87 @@ class AddMailAddressForm(forms.ModelForm): """ self.instance.domain = self.maildomain - target_choice = self.cleaned_data['mailbox_or_forwards'] - mailaddress = super(AddMailAddressForm, self).save(commit) + data = self.cleaned_data + target_choice = data['mailbox_or_forwards'] if target_choice == MAILBOX_OR_FORWARDS.mailbox: - mailbox = MailAddressMailbox( - mailaddress=mailaddress, - mailbox=self.cleaned_data['mailbox']) - if commit: - mailbox.save() + mabox = self.instance.set_mailbox(data['mailbox'], commit=False) elif target_choice == MAILBOX_OR_FORWARDS.forwards: - for address in [ - part.strip() for part in - self.cleaned_data['forwards'].split(',') - ]: - forward = MailAddressForward( - mailaddress=mailaddress, target=part) - if commit: - forward.save() + targets = [part.strip() for part in data['forwards'].split()] + fwds = self.instance.set_forward_addresses(targets, commit=False) + mailaddress = super(AddMailAddressForm, self).save(commit) + if commit: + if target_choice == MAILBOX_OR_FORWARDS.mailbox: + mabox.mailaddress = mailaddress + mabox.save() + elif target_choice == MAILBOX_OR_FORWARDS.forwards: + for fwd in fwds: + fwd.mailaddress = mailaddress + fwd.save() return mailaddress + + +class EditMailAddressForm(forms.ModelForm, MailAddressFieldMixin): + """ + This form is used to edit the targets for a mail address. + + """ + class Meta: + model = MailAddress + fields = [] + + def __init__(self, *args, **kwargs): + self.hosting_package = kwargs.pop('hostingpackage') + self.maildomain = kwargs.pop('maildomain') + super(EditMailAddressForm, self).__init__(*args, **kwargs) + self.fields['mailbox'].queryset = Mailbox.objects.unused_or_own( + self.instance, self.hosting_package.osuser + ) + + self.helper = FormHelper() + self.helper.form_action = reverse( + 'edit_mailaddress', kwargs={ + 'package': self.hosting_package.id, + 'domain': self.maildomain.domain, + 'pk': self.instance.id, + }) + self.helper.layout = Layout( + Div( + Div( + 'mailbox_or_forwards', + css_class='col-log-2 col-md-2 col-xs-12', + ), + Div( + 'mailbox', + 'forwards', + css_class='col-lg-10 col-md-10 col-xs-12', + ), + css_class='row', + ), + Submit('submit', _('Change mail address targets')), + ) + + def clean(self): + data = self.cleaned_data + if data['mailbox_or_forwards'] == MAILBOX_OR_FORWARDS.mailbox: + if not data['mailbox']: + self.add_error('mailbox', _('No mailbox selected')) + elif data['mailbox_or_forwards'] == MAILBOX_OR_FORWARDS.forwards: + if 'forwards' not in data or not data['forwards']: + self.add_error('forwards', _('No forward addresses selected')) + else: + raise forms.ValidationError( + _('Illegal choice for target of the mail address')) + + def save(self, commit=True): + """ + Save the mail address. + + :param boolean commit: + """ + data = self.cleaned_data + if data['mailbox_or_forwards'] == MAILBOX_OR_FORWARDS.mailbox: + self.instance.set_mailbox(data['mailbox'], commit) + elif data['mailbox_or_forwards'] == MAILBOX_OR_FORWARDS.forwards: + targets = [part.strip() for part in data['forwards'].split(',')] + self.instance.set_foward_addresses(targets, commit) + return self.instance diff --git a/gnuviechadmin/managemails/models.py b/gnuviechadmin/managemails/models.py index 6e612db..f818b87 100644 --- a/gnuviechadmin/managemails/models.py +++ b/gnuviechadmin/managemails/models.py @@ -61,6 +61,30 @@ class MailboxManager(models.Manager): break return mailboxname + def unused_or_own(self, mailaddress, osuser): + """ + Returns a queryset that fetches those mailboxes that belong to the + given operating system user and are either not used by any mail address + or used by the given mailaddress itself. + + """ + return self.filter( + models.Q(mailaddressmailbox__isnull=True) | + models.Q(mailaddressmailbox__mailaddress=mailaddress), + active=True, osuser=osuser, + ) + + def unused(self, osuser): + """ + Returns a queryset that fetches unused mailboxes belonging to the + given operating system user. + + """ + return self.filter( + mailaddressmailbox__isnull=True, + active=True, osuser=osuser, + ) + @python_2_unicode_compatible class Mailbox(ActivateAbleMixin, TimeStampedModel): @@ -130,6 +154,70 @@ class MailAddress(ActivateAbleMixin, TimeStampedModel, models.Model): def __str__(self): return "{0}@{1}".format(self.localpart, self.domain) + def set_mailbox(self, mailbox, commit=True): + """ + Set the target for this mail address to the given mailbox. This method + takes care of removing forwarding addresses or changing existing + mailbox relations. + + :param mailbox: :py:class:`Mailbox` + :param boolean commit: whether to persist changes or not + :return: :py:class:`MailAddressMailbox` instance + + """ + if self.pk is not None: + if MailAddressMailbox.objects.filter(mailaddress=self).exists(): + mabox = MailAddressMailbox.objects.get(mailaddress=self) + mabox.mailbox = mailbox + else: + mabox = MailAddressMailbox(mailaddress=self, mailbox=mailbox) + if commit: + mabox.save() + for mafwd in MailAddressForward.objects.filter(mailaddress=self): + mafwd.delete() + else: + mabox = MailAddressMailbox(mailaddress=self, mailbox=mailbox) + if commit: + mabox.save() + return mabox + + def set_forward_addresses(self, addresses, commit=True): + """ + Set the forwarding addresses for this mail address to the given + addresses. This method takes care of removing other mail forwards and + mailboxes. + + :param addresses: list of email address strings + :param boolean commit: whether to persist changes or not + :return: list of :py:class:`MailAddressForward` instances + + """ + retval = [] + if self.pk is not None: + if MailAddressMailbox.objects.filter(mailaddress=self).exists(): + mabox = MailAddressMailbox.objects.get(mailaddress=self) + mabox.delete() + forwards = MailAddressForward.objects.filter( + mailaddress=self).all() + for item in forwards: + if not item.target in addresses: + item.delete() + else: + retval.append(item) + for target in addresses: + if not target in [item.target for item in forwards]: + mafwd = MailAddressForward(mailaddress=self, target=target) + if commit: + mafwd.save() + retval.append(item) + else: + for target in addresses: + mafwd = MailAddressForward(mailaddress=self, target=target) + if commit: + mafwd.save() + retval.append(item) + return retval + @python_2_unicode_compatible class MailAddressMailbox(TimeStampedModel, models.Model): diff --git a/gnuviechadmin/managemails/urls.py b/gnuviechadmin/managemails/urls.py index 405c6bd..c37a83e 100644 --- a/gnuviechadmin/managemails/urls.py +++ b/gnuviechadmin/managemails/urls.py @@ -12,6 +12,7 @@ from .views import ( ChangeMailboxPassword, CreateMailbox, DeleteMailAddress, + EditMailAddress, ) urlpatterns = patterns( @@ -22,6 +23,9 @@ urlpatterns = patterns( ChangeMailboxPassword.as_view(), name='change_mailbox_password'), url(r'^(?P\d+)/mailaddress/(?P[\w0-9-.]+)/create$', AddMailAddress.as_view(), name='add_mailaddress'), + url(r'^(?P\d+)/mailaddress/(?P[\w0-9-.]+)/(?P\d+)' + r'/edit$', + EditMailAddress.as_view(), name='edit_mailaddress'), url(r'^(?P\d+)/mailaddress/(?P[\w0-9-.]+)/(?P\d+)' r'/delete$', DeleteMailAddress.as_view(), name='delete_mailaddress'), diff --git a/gnuviechadmin/managemails/views.py b/gnuviechadmin/managemails/views.py index f807c5d..8fed77f 100644 --- a/gnuviechadmin/managemails/views.py +++ b/gnuviechadmin/managemails/views.py @@ -23,9 +23,12 @@ from .forms import ( AddMailAddressForm, ChangeMailboxPasswordForm, CreateMailboxForm, + EditMailAddressForm, + MAILBOX_OR_FORWARDS, ) from .models import ( MailAddress, + MailAddressMailbox, Mailbox, ) @@ -188,3 +191,55 @@ class DeleteMailAddress( def get_success_url(self): return self.get_hosting_package().get_absolute_url() + + +class EditMailAddress( + HostingPackageAndCustomerMixin, StaffOrSelfLoginRequiredMixin, UpdateView +): + """ + This view is used to edit a mail address' target mailbox or forwarding + addresses. + + """ + context_object_name = 'mailaddress' + form_class = EditMailAddressForm + model = MailAddress + template_name_suffix = '_edit' + + def get_maildomain(self): + return get_object_or_404(MailDomain, domain=self.kwargs['domain']) + + def get_context_data(self, **kwargs): + context = super(EditMailAddress, self).get_context_data(**kwargs) + context['customer'] = self.get_customer_object() + return context + + def get_form_kwargs(self): + kwargs = super(EditMailAddress, self).get_form_kwargs() + kwargs.update({ + 'hostingpackage': self.get_hosting_package(), + 'maildomain': self.get_maildomain(), + }) + return kwargs + + def get_initial(self): + initial = super(EditMailAddress, self).get_initial() + mailaddress = self.get_object() + if MailAddressMailbox.objects.filter(mailaddress=mailaddress).exists(): + initial['mailbox'] = mailaddress.mailaddressmailbox.mailbox + initial['mailbox_or_forwards'] = MAILBOX_OR_FORWARDS.mailbox + elif mailaddress.mailaddressforward_set.exists(): + initial['forwards'] = ", ".join( + fwd.target for fwd in mailaddress.mailaddressforward_set.all() + ) + initial['mailbox_or_forwards'] = MAILBOX_OR_FORWARDS.forwards + return initial + + def form_valid(self, form): + mailaddress = form.save() + messages.success( + self.request, + _('Successfully updated mail address {mailaddress} ' + 'targets.').format(mailaddress=mailaddress) + ) + return redirect(self.get_hosting_package()) diff --git a/gnuviechadmin/templates/managemails/mailaddress_edit.html b/gnuviechadmin/templates/managemails/mailaddress_edit.html new file mode 100644 index 0000000..8102a5e --- /dev/null +++ b/gnuviechadmin/templates/managemails/mailaddress_edit.html @@ -0,0 +1,26 @@ +{% extends "managemails/base.html" %} +{% load i18n crispy_forms_tags %} + +{% block title %}{{ block.super }} - {% spaceless %} +{% if user == customer %} +{% blocktrans %}Change target of Mail Address{% endblocktrans %} +{% else %} +{% blocktrans with full_name=customer.get_full_name %} +Change target of Mail Address for Customer {{ full_name }} +{% endblocktrans %} +{% endif %} +{% endspaceless %}{% endblock title %} + +{% block page_title %}{% spaceless %} +{% if user == customer %} +{% blocktrans %}Change target of Mail Address{% endblocktrans %} +{% else %} +{% blocktrans with full_name=customer.get_full_name %} +Change target of Mail Address for Customer {{ full_name }} +{% endblocktrans %} +{% endif %} +{% endspaceless %}{% endblock page_title %} + +{% block content %} +{% crispy form %} +{% endblock content %}