diff --git a/modoboa/admin/forms/alias.py b/modoboa/admin/forms/alias.py index 8195d5613..75f9d2745 100644 --- a/modoboa/admin/forms/alias.py +++ b/modoboa/admin/forms/alias.py @@ -20,6 +20,7 @@ from ..models import Domain, Alias class AliasForm(forms.ModelForm, DynamicForm): """A form to create/modify an alias.""" + random_address = forms.BooleanField(required=False) address = lib_fields.UTF8AndEmptyUserEmailField( label=ugettext_lazy("Email address"), help_text=ugettext_lazy( @@ -39,16 +40,27 @@ class AliasForm(forms.ModelForm, DynamicForm): class Meta: model = Alias - fields = ("address", "enabled", ) + fields = ("address", "domain", "enabled", "expire_at", "description") + widgets = { + "domain": forms.widgets.Select( + attrs={"class": "selectpicker", "data-live-search": "true"}) + } def __init__(self, user, *args, **kwargs): self.user = user super(AliasForm, self).__init__(*args, **kwargs) self.fields = OrderedDict( (key, self.fields[key]) for key in - ['address', 'recipients', 'enabled'] + ["random_address", "address", "domain", "recipients", "enabled", + "expire_at", "description"] ) - + if self.instance.pk: + del self.fields["random_address"] + del self.fields["domain"] + else: + self.fields["address"].required = False + self.fields["domain"].required = False + self.fields["domain"].queryset = Domain.objects.get_for_admin(user) if len(args) and isinstance(args[0], QueryDict): if "instance" in kwargs: if not kwargs["instance"].domain.enabled: @@ -66,6 +78,8 @@ class AliasForm(forms.ModelForm, DynamicForm): def clean_address(self): """Check if address points to a local domain.""" + if not self.cleaned_data["address"]: + return self.cleaned_data["address"] localpart, domname = split_mailbox(self.cleaned_data["address"]) try: domain = Domain.objects.get(name=domname) @@ -87,6 +101,12 @@ class AliasForm(forms.ModelForm, DynamicForm): def clean(self): """Check it there is at least one recipient.""" super(AliasForm, self).clean() + random_address = self.cleaned_data.get("random_address") + if not random_address: + if not self.cleaned_data.get("address"): + self.add_error("address", _("This field is required")) + elif not self.cleaned_data.get("domain"): + self.add_error("domain", _("This field is required")) for field, value in self.cleaned_data.items(): if field.startswith("recipients") and value: return self.cleaned_data @@ -96,8 +116,12 @@ class AliasForm(forms.ModelForm, DynamicForm): def save(self, commit=True): """Custom save method.""" alias = super(AliasForm, self).save(commit=False) - local_part, domname = split_mailbox(self.cleaned_data["address"]) - alias.domain = Domain.objects.get(name=domname) + if self.cleaned_data.get("random_address"): + alias.address = "{}@{}".format( + Alias.generate_random_address(), alias.domain) + else: + local_part, domname = split_mailbox(self.cleaned_data["address"]) + alias.domain = Domain.objects.get(name=domname) if commit: alias.save() address_list = [ diff --git a/modoboa/admin/migrations/0005_auto_20160719_0903.py b/modoboa/admin/migrations/0005_auto_20160719_0903.py new file mode 100644 index 000000000..343ac82b1 --- /dev/null +++ b/modoboa/admin/migrations/0005_auto_20160719_0903.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.5 on 2016-07-19 07:03 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('admin', '0004_auto_20160608_1423'), + ] + + operations = [ + migrations.AddField( + model_name='alias', + name='description', + field=models.TextField(blank=True), + ), + migrations.AddField( + model_name='alias', + name='expire_at', + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/modoboa/admin/models/alias.py b/modoboa/admin/models/alias.py index c31147e56..c277f97a8 100644 --- a/modoboa/admin/models/alias.py +++ b/modoboa/admin/models/alias.py @@ -1,5 +1,8 @@ """Models related to aliases management.""" +import hashlib +import random + from django.core.urlresolvers import reverse from django.db import models from django.utils.encoding import python_2_unicode_compatible, smart_text @@ -37,6 +40,8 @@ class Alias(AdminObject): default=True ) internal = models.BooleanField(default=False) + description = models.TextField(blank=True) + expire_at = models.DateTimeField(blank=True, null=True) _objectname = 'MailboxAlias' class Meta: @@ -50,6 +55,14 @@ class Alias(AdminObject): def __str__(self): return smart_text(self.address) + @classmethod + def generate_random_address(self): + """Generate a random address (local part).""" + m = hashlib.md5() + for x in random.sample(xrange(10000000), 60): + m.update(str(x)) + return m.hexdigest()[:20] + @property def identity(self): return self.address diff --git a/modoboa/admin/postfix_maps.py b/modoboa/admin/postfix_maps.py index 72cf8df24..a0640a131 100644 --- a/modoboa/admin/postfix_maps.py +++ b/modoboa/admin/postfix_maps.py @@ -52,17 +52,17 @@ class AliasesMap(object): mysql = ( "SELECT alr.address FROM modoboa_admin_aliasrecipient AS alr " "INNER JOIN admin_alias AS al ON alr.alias_id=al.id " - "WHERE al.enabled=1 AND al.address='%s'" + "WHERE al.enabled=1 AND al.address='%s' AND al.expire_at>now()" ) postgres = ( "SELECT alr.address FROM modoboa_admin_aliasrecipient AS alr " "INNER JOIN admin_alias AS al ON alr.alias_id=al.id " - "WHERE al.enabled AND al.address='%s'" + "WHERE al.enabled AND al.address='%s' AND al.expire_at>now()" ) sqlite = ( "SELECT alr.address FROM modoboa_admin_aliasrecipient AS alr " "INNER JOIN admin_alias AS al ON alr.alias_id=al.id " - "WHERE al.enabled=1 AND al.address='%s'" + "WHERE al.enabled=1 AND al.address='%s' AND al.expire_at>now()" ) diff --git a/modoboa/admin/static/admin/js/admin.js b/modoboa/admin/static/admin/js/admin.js index ca52954da..bb3a64c51 100644 --- a/modoboa/admin/static/admin/js/admin.js +++ b/modoboa/admin/static/admin/js/admin.js @@ -552,6 +552,15 @@ Identities.prototype = { }, aliasform_cb: function() { + $("#id_random_address").click(function(evt) { + if ($(this).prop("checked")) { + $("#id_address").parents(".form-group").hide(); + $("#id_domain").parents(".form-group").show(); + } else { + $("#id_address").parents(".form-group").show(); + $("#id_domain").parents(".form-group").hide(); + } + }); $("#id_address").autocompleter({ from_character: "@", choices: $.proxy(this.get_domain_list, this) @@ -565,6 +574,9 @@ Identities.prototype = { return true; } }); + $("#id_expire_at").datetimepicker({ + format: 'YYYY-MM-DD HH:mm:ss' + }); $(".submit").on('click', $.proxy(function(e) { simple_ajax_form_post(e, { formid: "aliasform", diff --git a/modoboa/admin/templates/admin/alias_detail.html b/modoboa/admin/templates/admin/alias_detail.html index 01d49a6dc..6e00125ec 100644 --- a/modoboa/admin/templates/admin/alias_detail.html +++ b/modoboa/admin/templates/admin/alias_detail.html @@ -25,6 +25,12 @@