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
This commit is contained in:
Jan Dittberner 2015-01-25 22:07:54 +01:00
parent bebcad8c86
commit 5429055f0d
6 changed files with 269 additions and 26 deletions

View file

@ -1,6 +1,7 @@
Changelog Changelog
========= =========
* :feature:`-` implement mail address target editing
* :feature:`-` implement mail address deletion * :feature:`-` implement mail address deletion
* :feature:`-` implement adding mail address to mail domains * :feature:`-` implement adding mail address to mail domains
* :feature:`-` implement adding options to hosting packages * :feature:`-` implement adding options to hosting packages

View file

@ -19,8 +19,6 @@ from crispy_forms.layout import (
from .models import ( from .models import (
MailAddress, MailAddress,
MailAddressForward,
MailAddressMailbox,
Mailbox, Mailbox,
) )
from gvawebcore.forms import PasswordModelFormMixin from gvawebcore.forms import PasswordModelFormMixin
@ -106,9 +104,9 @@ def multiple_email_validator(value):
return 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( mailbox_or_forwards = forms.TypedChoiceField(
@ -133,6 +131,12 @@ class AddMailAddressForm(forms.ModelForm):
validators=[multiple_email_validator], validators=[multiple_email_validator],
) )
class AddMailAddressForm(forms.ModelForm, MailAddressFieldMixin):
"""
This form is used to add a new mail address.
"""
class Meta: class Meta:
model = MailAddress model = MailAddress
fields = ['localpart'] fields = ['localpart']
@ -141,8 +145,8 @@ class AddMailAddressForm(forms.ModelForm):
self.maildomain = kwargs.pop('maildomain') self.maildomain = kwargs.pop('maildomain')
self.hosting_package = kwargs.pop('hostingpackage') self.hosting_package = kwargs.pop('hostingpackage')
super(AddMailAddressForm, self).__init__(*args, **kwargs) super(AddMailAddressForm, self).__init__(*args, **kwargs)
self.fields['mailbox'].queryset = Mailbox.objects.filter( self.fields['mailbox'].queryset = Mailbox.objects.unused(
active=True, osuser=self.hosting_package.osuser, osuser=self.hosting_package.osuser,
) )
self.helper = FormHelper() self.helper = FormHelper()
@ -168,7 +172,7 @@ class AddMailAddressForm(forms.ModelForm):
), ),
css_class='row', css_class='row',
), ),
Submit('submit', _('Add mailaddress')), Submit('submit', _('Add mail address')),
) )
def clean_localpart(self): def clean_localpart(self):
@ -186,11 +190,10 @@ class AddMailAddressForm(forms.ModelForm):
data = self.cleaned_data data = self.cleaned_data
if data['mailbox_or_forwards'] == MAILBOX_OR_FORWARDS.mailbox: if data['mailbox_or_forwards'] == MAILBOX_OR_FORWARDS.mailbox:
if not data['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: elif data['mailbox_or_forwards'] == MAILBOX_OR_FORWARDS.forwards:
if 'forwards' not in data: if 'forwards' not in data or not data['forwards']:
raise forms.ValidationError( self.add_error('forwards', _('No forward addresses selected'))
_('No forward addresses selected'))
else: else:
raise forms.ValidationError( raise forms.ValidationError(
_('Illegal choice for target of the mail address')) _('Illegal choice for target of the mail address'))
@ -205,21 +208,87 @@ class AddMailAddressForm(forms.ModelForm):
""" """
self.instance.domain = self.maildomain self.instance.domain = self.maildomain
target_choice = self.cleaned_data['mailbox_or_forwards'] data = self.cleaned_data
mailaddress = super(AddMailAddressForm, self).save(commit) target_choice = data['mailbox_or_forwards']
if target_choice == MAILBOX_OR_FORWARDS.mailbox: if target_choice == MAILBOX_OR_FORWARDS.mailbox:
mailbox = MailAddressMailbox( mabox = self.instance.set_mailbox(data['mailbox'], commit=False)
mailaddress=mailaddress,
mailbox=self.cleaned_data['mailbox'])
if commit:
mailbox.save()
elif target_choice == MAILBOX_OR_FORWARDS.forwards: elif target_choice == MAILBOX_OR_FORWARDS.forwards:
for address in [ targets = [part.strip() for part in data['forwards'].split()]
part.strip() for part in fwds = self.instance.set_forward_addresses(targets, commit=False)
self.cleaned_data['forwards'].split(',') mailaddress = super(AddMailAddressForm, self).save(commit)
]: if commit:
forward = MailAddressForward( if target_choice == MAILBOX_OR_FORWARDS.mailbox:
mailaddress=mailaddress, target=part) mabox.mailaddress = mailaddress
if commit: mabox.save()
forward.save() elif target_choice == MAILBOX_OR_FORWARDS.forwards:
for fwd in fwds:
fwd.mailaddress = mailaddress
fwd.save()
return mailaddress 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

View file

@ -61,6 +61,30 @@ class MailboxManager(models.Manager):
break break
return mailboxname 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 @python_2_unicode_compatible
class Mailbox(ActivateAbleMixin, TimeStampedModel): class Mailbox(ActivateAbleMixin, TimeStampedModel):
@ -130,6 +154,70 @@ class MailAddress(ActivateAbleMixin, TimeStampedModel, models.Model):
def __str__(self): def __str__(self):
return "{0}@{1}".format(self.localpart, self.domain) 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 @python_2_unicode_compatible
class MailAddressMailbox(TimeStampedModel, models.Model): class MailAddressMailbox(TimeStampedModel, models.Model):

View file

@ -12,6 +12,7 @@ from .views import (
ChangeMailboxPassword, ChangeMailboxPassword,
CreateMailbox, CreateMailbox,
DeleteMailAddress, DeleteMailAddress,
EditMailAddress,
) )
urlpatterns = patterns( urlpatterns = patterns(
@ -22,6 +23,9 @@ urlpatterns = patterns(
ChangeMailboxPassword.as_view(), name='change_mailbox_password'), ChangeMailboxPassword.as_view(), name='change_mailbox_password'),
url(r'^(?P<package>\d+)/mailaddress/(?P<domain>[\w0-9-.]+)/create$', url(r'^(?P<package>\d+)/mailaddress/(?P<domain>[\w0-9-.]+)/create$',
AddMailAddress.as_view(), name='add_mailaddress'), AddMailAddress.as_view(), name='add_mailaddress'),
url(r'^(?P<package>\d+)/mailaddress/(?P<domain>[\w0-9-.]+)/(?P<pk>\d+)'
r'/edit$',
EditMailAddress.as_view(), name='edit_mailaddress'),
url(r'^(?P<package>\d+)/mailaddress/(?P<domain>[\w0-9-.]+)/(?P<pk>\d+)' url(r'^(?P<package>\d+)/mailaddress/(?P<domain>[\w0-9-.]+)/(?P<pk>\d+)'
r'/delete$', r'/delete$',
DeleteMailAddress.as_view(), name='delete_mailaddress'), DeleteMailAddress.as_view(), name='delete_mailaddress'),

View file

@ -23,9 +23,12 @@ from .forms import (
AddMailAddressForm, AddMailAddressForm,
ChangeMailboxPasswordForm, ChangeMailboxPasswordForm,
CreateMailboxForm, CreateMailboxForm,
EditMailAddressForm,
MAILBOX_OR_FORWARDS,
) )
from .models import ( from .models import (
MailAddress, MailAddress,
MailAddressMailbox,
Mailbox, Mailbox,
) )
@ -188,3 +191,55 @@ class DeleteMailAddress(
def get_success_url(self): def get_success_url(self):
return self.get_hosting_package().get_absolute_url() 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())

View file

@ -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 %}