Remove extensions.

This commit is contained in:
Antoine Nguyen
2015-02-11 14:48:50 +01:00
parent a434286e20
commit b0f88c3988
34 changed files with 0 additions and 1986 deletions
View File
@@ -1,63 +0,0 @@
# coding: utf-8
"""Custom settings."""
from django import forms
from django.utils.translation import ugettext_lazy
from modoboa.lib.form_utils import SeparatorField
from modoboa.lib.parameters import AdminParametersForm
class ParametersForm(AdminParametersForm):
app = "postfix_autoreply"
general_sep = SeparatorField(label=ugettext_lazy("General"))
autoreplies_timeout = forms.IntegerField(
label=ugettext_lazy("Automatic reply timeout"),
initial=86400,
help_text=ugettext_lazy(
"Timeout in seconds between two auto-replies to the same recipient"
),
widget=forms.TextInput(attrs={"class": "form-control"})
)
default_subject = forms.CharField(
label=ugettext_lazy("Default subject"),
initial=ugettext_lazy("I'm off"),
help_text=ugettext_lazy(
"Default subject used when an auto-reply message is created "
"automatically"
),
widget=forms.TextInput(attrs={"class": "form-control"})
)
default_content = forms.CharField(
label=ugettext_lazy("Default content"),
initial=ugettext_lazy(
"""I'm currently off. I'll answer as soon as I come back.
Best regards,
%(name)s
"""),
help_text=ugettext_lazy(
"Default content used when an auto-reply message is created "
"automatically. The '%(name)s' macro will be replaced by the "
"user's full name."
),
widget=forms.widgets.Textarea(attrs={"class": "form-control"})
)
def clean_default_content(self):
"""Check if the provided value is valid.
Must be a valid format string which will be used with the %
operator.
"""
tpl = self.cleaned_data["default_content"]
try:
test = tpl % {"name": "Antoine Nguyen"}
except (KeyError, ValueError):
raise forms.ValidationError(ugettext_lazy("Invalid syntax"))
return tpl
@@ -1,81 +0,0 @@
# coding: utf-8
"""Custom forms."""
from collections import OrderedDict
from django import forms
from django.utils import timezone
from django.utils.translation import ugettext as _, ugettext_lazy
from .models import ARmessage
class ARmessageForm(forms.ModelForm):
fromdate = forms.DateTimeField(
label=ugettext_lazy('From'),
required=False,
help_text=ugettext_lazy("Activate your auto reply from this date"),
widget=forms.TextInput(
attrs={'class': 'datefield form-control', 'readonly': 'readonly'}
)
)
untildate = forms.DateTimeField(
label=ugettext_lazy('Until'),
required=False,
help_text=ugettext_lazy("Activate your auto reply until this date"),
widget=forms.TextInput(
attrs={'class': 'datefield form-control', 'readonly': 'readonly'}
)
)
subject = forms.CharField(
widget=forms.TextInput(
attrs={'class': 'form-control'}
)
)
content = forms.CharField(
widget=forms.Textarea(
attrs={'class': 'form-control'}
)
)
class Meta:
model = ARmessage
fields = ('subject', 'content', 'enabled')
def __init__(self, *args, **kwargs):
super(ARmessageForm, self).__init__(*args, **kwargs)
self.fields = OrderedDict(
(key, self.fields[key]) for key in
['subject', 'content', 'fromdate', 'untildate', 'enabled']
)
if 'instance' in kwargs and kwargs['instance'] is not None:
self.fields['fromdate'].initial = \
kwargs['instance'].fromdate.replace(second=0, microsecond=0)
self.fields['untildate'].initial = kwargs['instance'].untildate
def clean(self):
"""Custom fields validaton.
We want to be sure that fromdate < untildate and that they are
both in the future ONLY IF the autoreply is beeing activated.
"""
super(ARmessageForm, self).clean()
if not self.cleaned_data["enabled"]:
return self.cleaned_data
if self.cleaned_data["fromdate"] is not None:
if self.cleaned_data["fromdate"] < timezone.now():
self.add_error("fromdate", _("This date is over"))
else:
self.cleaned_data['fromdate'] = timezone.now()
if self.cleaned_data["untildate"] is not None:
untildate = self.cleaned_data["untildate"]
if untildate < timezone.now():
self.add_error("untildate", _("This date is over"))
elif "fromdate" in self.cleaned_data and \
untildate < self.cleaned_data["fromdate"]:
self.add_error(
"untildate", _("Must be greater than start date"))
return self.cleaned_data
@@ -1,151 +0,0 @@
"""Event callbacks."""
from django.utils import timezone
from django.utils.translation import ugettext_lazy
from .models import ARmessage
from modoboa.extensions.postfix_autoreply.models import Transport, Alias
from modoboa.lib import events, parameters
@events.observe("ExtraUprefsJS")
def extra_js(user):
return ["""function autoreply_cb() {
$('.datefield').datetimepicker({
format: 'YYYY-MM-DD hh:mm:ss',
language: '%(lang)s'
});
}
""" % {'lang': parameters.get_user(user, "LANG", app="core")}
]
@events.observe("UserMenuDisplay")
def menu(target, user):
if target != "uprefs_menu":
return []
if not user.mailbox_set.count():
return []
return [
{"name": "autoreply",
"class": "ajaxnav",
"url": "autoreply/",
"label": ugettext_lazy("Auto-reply message")}
]
@events.observe("DomainCreated")
def onDomainCreated(user, domain):
transport = Transport()
transport.domain = "autoreply.%s" % domain.name
transport.method = "autoreply:"
transport.save()
@events.observe("DomainModified")
def onDomainModified(domain):
if domain.oldname == domain.name:
return
Transport.objects.filter(domain="autoreply.%s" % domain.oldname) \
.update(domain="autoreply.%s" % domain.name)
for al in Alias.objects.filter(
full_address__contains="@%s" % domain.oldname):
new_address = al.full_address.replace(
"@%s" % domain.oldname,
"@%s" % domain.name)
al.full_address = new_address
al.autoreply_address = "%s@autoreply.%s" % (new_address, domain.name)
al.save()
@events.observe("DomainDeleted")
def onDomainDeleted(domain):
Transport.objects.filter(domain="autoreply.%s" % domain.name).delete()
@events.observe("MailboxCreated")
def onMailboxCreated(user, mailbox):
alias = Alias()
alias.full_address = mailbox.full_address
alias.autoreply_address = \
"%s@autoreply.%s" % (mailbox.full_address, mailbox.domain.name)
alias.save()
@events.observe("MailboxDeleted")
def onMailboxDeleted(mailboxes):
from modoboa.extensions.admin.models import Mailbox
if isinstance(mailboxes, Mailbox):
mailboxes = [mailboxes]
for mailbox in mailboxes:
try:
alias = Alias.objects.get(full_address=mailbox.full_address)
except Alias.DoesNotExist:
pass
else:
alias.delete()
@events.observe("MailboxModified")
def onMailboxModified(mailbox):
if not hasattr(mailbox, 'old_full_address'):
return
if mailbox.full_address == mailbox.old_full_address:
return
alias = Alias.objects.get(full_address=mailbox.old_full_address)
alias.full_address = mailbox.full_address
alias.autoreply_address = \
"%s@autoreply.%s" % (mailbox.full_address, mailbox.domain.name)
alias.save()
@events.observe("ExtraFormFields")
def extra_mailform_fields(form_name, mailbox=None):
"""Define extra fields to include in mail forms.
For now, only the auto-reply state can be modified.
:param str form_name: form name (must be 'mailform')
:param Mailbox mailbox: mailbox
"""
from modoboa.lib.form_utils import YesNoField
if form_name != "mailform":
return []
status = False
if mailbox is not None and mailbox.armessage_set.count():
status = mailbox.armessage_set.all()[0].enabled
return [
('autoreply', YesNoField(
label=ugettext_lazy("Enable auto-reply"),
initial="yes" if status else "no",
required=False,
help_text=ugettext_lazy("Enable or disable Postfix auto-reply")
))
]
@events.observe("SaveExtraFormFields")
def save_extra_mailform_fields(form_name, mailbox, values):
"""Set the auto-reply status for a mailbox.
If a corresponding auto-reply message exists, we update its
status. Otherwise, we create a message using default values.
:param str form_name: form name (must be 'mailform')
:param Mailbox mailbox: mailbox
:param dict values: form values
"""
if form_name != 'mailform':
return
if mailbox.armessage_set.count():
arm = mailbox.armessage_set.all()[0]
else:
arm = ARmessage(mbox=mailbox)
arm.subject = parameters.get_admin("DEFAULT_SUBJECT")
arm.content = parameters.get_admin("DEFAULT_CONTENT") \
% {'name': mailbox.user.fullname}
arm.fromdate = timezone.now()
arm.enabled = True if values['autoreply'] == 'yes' else False
arm.save()
@@ -1,2 +0,0 @@
#!/usr/bin/env python
# coding: utf-8
@@ -1,2 +0,0 @@
#!/usr/bin/env python
# coding: utf-8
@@ -1,68 +0,0 @@
#!/usr/bin/env python
# coding: utf-8
import sys
import datetime
from django.core.management.base import BaseCommand, CommandError
from django.utils import timezone
from modoboa.core.management.commands import CloseConnectionMixin
from modoboa.lib import parameters
from modoboa.lib.email_utils import split_mailbox, sendmail_simple
from modoboa.extensions.admin.models import Mailbox
from modoboa.extensions.postfix_autoreply.models import ARmessage, ARhistoric
def send_autoreply(sender, mailbox, armessage):
if armessage.fromdate > timezone.now():
return
if armessage.untildate is not None \
and armessage.untildate < timezone.now():
armessage.enabled = False
armessage.save()
return
try:
lastar = ARhistoric.objects.get(armessage=armessage.id, sender=sender)
timeout = parameters.get_admin("AUTOREPLIES_TIMEOUT",
app="postfix_autoreply")
delta = datetime.timedelta(seconds=int(timeout))
now = timezone.make_aware(datetime.datetime.now(),
timezone.get_default_timezone())
if lastar.last_sent + delta > now:
sys.exit(0)
except ARhistoric.DoesNotExist:
lastar = ARhistoric()
lastar.armessage = armessage
lastar.sender = sender
sendmail_simple(mailbox.user.encoded_address, sender, armessage.subject,
armessage.content.encode('utf-8'))
lastar.last_sent = datetime.datetime.now()
lastar.save()
class Command(BaseCommand, CloseConnectionMixin):
args = '<sender> <recipient ...>'
help = 'Send autoreply emails'
def handle(self, *args, **options):
if len(args) < 2:
raise CommandError(
"usage: ./manage.py autoreply <sender> <recipient ...>")
sender = args[0]
for fulladdress in args[1:]:
address, domain = split_mailbox(fulladdress)
try:
mbox = Mailbox.objects.get(address=address, domain__name=domain)
except Mailbox.DoesNotExist:
print "Unknown recipient %s" % (fulladdress)
continue
try:
armessage = ARmessage.objects.get(mbox=mbox.id, enabled=True)
except ARmessage.DoesNotExist:
continue
send_autoreply(sender, mbox, armessage)
@@ -1,73 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('admin', '__first__'),
]
operations = [
migrations.CreateModel(
name='Alias',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('full_address', models.CharField(max_length=255)),
('autoreply_address', models.CharField(max_length=255)),
],
options={
},
bases=(models.Model,),
),
migrations.CreateModel(
name='ARhistoric',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('last_sent', models.DateTimeField(auto_now=True)),
('sender', models.CharField(max_length=254)),
],
options={
},
bases=(models.Model,),
),
migrations.CreateModel(
name='ARmessage',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('subject', models.CharField(help_text='The subject that will appear in sent emails', max_length=255, verbose_name='subject')),
('content', models.TextField(help_text='The content that will appear in sent emails', verbose_name='content')),
('enabled', models.BooleanField(default=False, help_text='Activate/Deactivate your auto reply', verbose_name='enabled')),
('fromdate', models.DateTimeField(default=django.utils.timezone.now)),
('untildate', models.DateTimeField(null=True, blank=True)),
('mbox', models.ForeignKey(to='admin.Mailbox')),
],
options={
},
bases=(models.Model,),
),
migrations.CreateModel(
name='Transport',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('domain', models.CharField(max_length=300)),
('method', models.CharField(max_length=255)),
],
options={
},
bases=(models.Model,),
),
migrations.AddField(
model_name='arhistoric',
name='armessage',
field=models.ForeignKey(to='postfix_autoreply.ARmessage'),
preserve_default=True,
),
migrations.AlterUniqueTogether(
name='arhistoric',
unique_together=set([('armessage', 'sender')]),
),
]
@@ -1,43 +0,0 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from django.utils import timezone
from modoboa.extensions.admin.models import Mailbox
class Transport(models.Model):
domain = models.CharField(max_length=300)
method = models.CharField(max_length=255)
class Alias(models.Model):
full_address = models.CharField(max_length=255)
autoreply_address = models.CharField(max_length=255)
class ARmessage(models.Model):
mbox = models.ForeignKey(Mailbox)
subject = models.CharField(
_('subject'), max_length=255,
help_text=_("The subject that will appear in sent emails")
)
content = models.TextField(
_('content'),
help_text=_("The content that will appear in sent emails")
)
enabled = models.BooleanField(
_('enabled'),
help_text=_("Activate/Deactivate your auto reply"),
default=False
)
fromdate = models.DateTimeField(default=timezone.now)
untildate = models.DateTimeField(null=True, blank=True)
class ARhistoric(models.Model):
armessage = models.ForeignKey(ARmessage)
last_sent = models.DateTimeField(auto_now=True)
sender = models.CharField(max_length=254)
class Meta:
unique_together = ("armessage", "sender")
@@ -1,65 +0,0 @@
# coding: utf-8
"""
Postfix auto-replies plugin.
This module provides a way to integrate Modoboa auto-reply
functionality into Postfix.
"""
from django.utils.translation import ugettext_lazy
from modoboa.core.extensions import ModoExtension, exts_pool
from modoboa.extensions.admin.models import Domain
from modoboa.lib import events, parameters
from . import general_callbacks
from .models import Transport, Alias
class PostfixAutoreply(ModoExtension):
"""
Auto-reply (vacation) functionality using Postfix.
"""
name = "postfix_autoreply"
label = "Postfix autoreply"
version = "1.0"
description = ugettext_lazy(
"Auto-reply (vacation) functionality using Postfix")
def load(self):
from modoboa.extensions.postfix_autoreply.app_settings import (
ParametersForm
)
parameters.register(
ParametersForm, ugettext_lazy("Automatic replies"))
def load_initial_data(self):
"""Create records for existing domains."""
for dom in Domain.objects.all():
try:
Transport.objects.get(domain="autoreply.%s" % dom.name)
except Transport.DoesNotExist:
general_callbacks.onDomainCreated(None, dom)
else:
continue
for mb in dom.mailbox_set.all():
try:
Alias.objects.get(full_address=mb.full_address)
except Alias.DoesNotExist:
general_callbacks.onMailboxCreated(None, mb)
exts_pool.register_extension(PostfixAutoreply)
@events.observe("ExtraUprefsRoutes")
def extra_routes():
from django.conf.urls import url
return [
url(r'^user/autoreply/$',
'modoboa.extensions.postfix_autoreply.views.autoreply',
name="autoreply")
]
@@ -1,11 +0,0 @@
{% load i18n form_tags %}{% load url from future %}
<h2>{% trans "Auto-reply message" %} <small>{% trans "Define a message to automatically send when you are off" %}</small></h2>
<hr>
<form class="form-horizontal" method="POST"
action="{% url 'autoreply' %}"
id="uprefs_form">{% csrf_token %}
{% render_form form %}
<div class="form-actions">
<button type="submit" class="btn btn-primary col-lg-offset-8 col-md-offset-8 col-sm-offset-7 col-lg-1 col-md-1 col-sm-2" id="update" name="update">{% trans 'Update' %}</button>
</div>
</form>
@@ -1,125 +0,0 @@
from django.core.urlresolvers import reverse
from modoboa.core.models import User
from modoboa.lib.tests import ModoTestCase
from modoboa.extensions.admin import factories
from modoboa.extensions.admin.models import (
Domain
)
from modoboa.extensions.postfix_autoreply.models import (
Transport, Alias, ARmessage
)
class EventsTestCase(ModoTestCase):
fixtures = ['initial_users.json']
def setUp(self):
super(EventsTestCase, self).setUp()
factories.populate_database()
def test_domain_created_event(self):
values = {
"name": "domain.tld", "quota": 100, "create_dom_admin": "no",
"stepid": 'step2'
}
self.ajax_post(
reverse("admin:domain_add"), values
)
trans = Transport.objects.get(domain='autoreply.domain.tld')
def test_domain_deleted_event(self):
dom = Domain.objects.get(name="test.com")
trans = Transport.objects.get(domain='autoreply.test.com')
self.ajax_post(
reverse("admin:domain_delete", args=[dom.id]),
{}
)
with self.assertRaises(Transport.DoesNotExist):
Transport.objects.get(domain='autoreply.test.com')
def test_domain_modified_event(self):
values = {
"name": "test.fr", "quota": 100, "enabled": True
}
dom = Domain.objects.get(name="test.com")
self.ajax_post(
reverse("admin:domain_change", args=[dom.id]),
values
)
trans = Transport.objects.get(domain='autoreply.test.fr')
self.assertEqual(
Alias.objects.filter(full_address__contains='@test.fr').count(), 2
)
for al in Alias.objects.filter(full_address__contains='@test.fr'):
self.assertIn('autoreply.test.fr', al.autoreply_address)
def test_mailbox_created_event(self):
values = {
'username': "tester@test.com",
'first_name': 'Tester', 'last_name': 'Toto',
'password1': 'toto', 'password2': 'toto', 'role': 'SimpleUsers',
'quota_act': True, 'is_active': True, 'email': 'tester@test.com',
'stepid': 'step2', 'autoreply': 'no'
}
self.ajax_post(
reverse("admin:account_add"), values
)
al = Alias.objects.get(full_address='tester@test.com')
self.assertEqual(
al.autoreply_address, 'tester@test.com@autoreply.test.com')
arm = ARmessage.objects.get(mbox__address='tester')
def test_mailbox_deleted_event(self):
account = User.objects.get(username="user@test.com")
self.ajax_post(
reverse("admin:account_delete", args=[account.id]),
{}
)
with self.assertRaises(Alias.DoesNotExist):
Alias.objects.get(full_address='user@test.com')
with self.assertRaises(ARmessage.DoesNotExist):
ARmessage.objects.get(
mbox__address='user', mbox__domain__name='test.com')
def test_modify_mailbox_event(self):
values = {
'username': "leon@test.com",
'first_name': 'Tester', 'last_name': 'Toto',
'role': 'SimpleUsers', 'quota_act': True,
'is_active': True, 'email': 'leon@test.com',
'autoreply': 'no'
}
account = User.objects.get(username="user@test.com")
self.ajax_post(
reverse("admin:account_change", args=[account.id]),
values
)
with self.assertRaises(Alias.DoesNotExist):
Alias.objects.get(full_address='user@test.com')
al = Alias.objects.get(full_address='leon@test.com')
class FormTestCase(ModoTestCase):
fixtures = ['initial_users.json']
def setUp(self):
super(FormTestCase, self).setUp()
factories.populate_database()
# def test_set_autoreply(self):
# self.clt.get(
# reverse('modoboa.core.views.user.index')
# )
# values = {
# 'subject': 'test', 'content': "I'm off", "enabled": True
# }
# self.ajax_post(
# reverse('modoboa.extensions.postfix_autoreply.views.autoreply'),
# values
# )
# account = User.objects.get(username="user@test.com")
# arm = ARmessage.objects.get(mbox=account.mailbox_set.all()[0])
# self.assertEqual(arm.subject, 'test')
# self.assertTrue(arm.enabled)
# self.assertFalse(arm.untildate)
# self.assertTrue(arm.fromdate)
@@ -1,45 +0,0 @@
# coding: utf-8
from datetime import date
from django.contrib.auth.decorators import login_required
from django.utils.translation import ugettext as _
from modoboa.lib.web_utils import (
render_to_json_response, _render_to_string
)
from modoboa.extensions.admin.lib import needs_mailbox
from modoboa.extensions.admin.models import Mailbox
from .forms import ARmessageForm
from .models import ARmessage
@login_required
@needs_mailbox()
def autoreply(request, tplname="postfix_autoreply/autoreply.html"):
mb = Mailbox.objects.get(user=request.user.id)
try:
arm = ARmessage.objects.get(mbox=mb.id)
except ARmessage.DoesNotExist:
arm = None
if request.method == "POST":
if arm:
form = ARmessageForm(request.POST, instance=arm)
else:
form = ARmessageForm(request.POST)
if form.is_valid():
arm = form.save(commit=False)
arm.fromdate = form.cleaned_data["fromdate"]
arm.untildate = form.cleaned_data["untildate"]
arm.mbox = mb
arm.save()
return render_to_json_response(
_("Auto reply message updated successfully.")
)
return render_to_json_response(
{"form_errors": form.errors}, status=400
)
form = ARmessageForm(instance=arm)
return render_to_json_response({
"content": _render_to_string(request, tplname, {"form": form}),
"onload_cb": "autoreply_cb"
})
@@ -1,35 +0,0 @@
from modoboa.lib import events
from modoboa.extensions.amavis.lib import (
create_user_and_policy, update_user_and_policy, delete_user_and_policy,
create_user_and_use_policy, delete_user
)
@events.observe("RelayDomainCreated")
def on_relay_domain_created(user, rdomain):
create_user_and_policy("@{0}".format(rdomain.name))
@events.observe("RelayDomainModified")
def on_relay_domain_modified(rdomain):
update_user_and_policy(
"@{0}".format(rdomain.oldname), "@{0}".format(rdomain.name)
)
@events.observe("RelayDomainDeleted")
def on_relay_domain_deleted(rdomain):
delete_user_and_policy("@{0}".format(rdomain.name))
@events.observe("RelayDomainAliasCreated")
def on_relay_domain_alias_created(user, rdomainalias):
create_user_and_use_policy(
"@{0}".format(rdomainalias.name),
"@{0}".format(rdomainalias.target.name)
)
@events.observe("RelayDomainAliasDeleted")
def on_relay_domain_alias_deleted(rdomainalias):
delete_user("@{0}".format(rdomainalias.name))
@@ -1,25 +0,0 @@
"""
Relay domains settings definition.
"""
from django import forms
from django.utils.translation import ugettext_lazy
from modoboa.lib import parameters
from modoboa.lib.form_utils import SeparatorField
class AdminParametersForm(parameters.AdminParametersForm):
"""Admin parameters definition."""
app = "postfix_relay_domains"
general_sep = SeparatorField(label=ugettext_lazy("General"))
master_cf_path = forms.CharField(
label=ugettext_lazy("Postfix's master.cf path"),
initial="/etc/postfix/master.cf",
help_text=ugettext_lazy('Path to the master.cf configuration file'),
widget=forms.TextInput(attrs={"class": "form-control"})
)
@@ -1,25 +0,0 @@
import factory
from modoboa.core.factories import PermissionFactory
from . import models
class ServiceFactory(factory.DjangoModelFactory):
FACTORY_FOR = models.Service
FACTORY_DJANGO_GET_OR_CREATE = ('name', )
name = 'dummy'
class RelayDomainFactory(PermissionFactory):
FACTORY_FOR = models.RelayDomain
target_host = 'external.host.tld'
enabled = True
verify_recipients = True
service = factory.SubFactory(ServiceFactory)
class RelayDomainAliasFactory(PermissionFactory):
FACTORY_FOR = models.RelayDomainAlias
enabled = True
@@ -1,184 +0,0 @@
"""
Postfix relay domains extension forms.
"""
from django import forms
from django.core.urlresolvers import reverse
from django.http import QueryDict
from django.utils.translation import ugettext as _, ugettext_lazy
from .models import RelayDomain, RelayDomainAlias
from modoboa.extensions.admin.models import Domain, DomainAlias
from modoboa.lib import events
from modoboa.lib.form_utils import DynamicForm, DomainNameField, TabForms
from modoboa.lib.web_utils import render_to_json_response
class RelayDomainFormGeneral(forms.ModelForm, DynamicForm):
aliases = DomainNameField(
label=ugettext_lazy("Alias(es)"),
required=False,
help_text=ugettext_lazy(
"Alias(es) of this relay domain. Indicate only one name per input"
", press ENTER to add a new input."
)
)
class Meta:
model = RelayDomain
exclude = ['dates']
widgets = {
"service": forms.Select(attrs={"class": "form-control"})
}
def __init__(self, *args, **kwargs):
self.oldname = None
if "instance" in kwargs:
self.oldname = kwargs["instance"].name
super(RelayDomainFormGeneral, self).__init__(*args, **kwargs)
self.field_widths = {
"service": 3
}
if args and isinstance(args[0], QueryDict):
self._load_from_qdict(args[0], "aliases", DomainNameField)
elif 'instance' in kwargs:
rd = kwargs['instance']
for pos, rdalias in enumerate(rd.relaydomainalias_set.all()):
name = "aliases_%d" % (pos + 1)
self._create_field(forms.CharField, name, rdalias.name, 3)
def clean(self):
"""Custom fields validaton.
We want to prevent duplicate names between domains, relay
domains, domain aliases and relay domain aliases.
The validation way is not very smart...
"""
super(RelayDomainFormGeneral, self).clean()
cleaned_data = self.cleaned_data
for dtype, label in [(Domain, _('domain')),
(DomainAlias, _('domain alias')),
(RelayDomainAlias, _('relay domain alias'))]:
try:
dtype.objects.get(name=cleaned_data['name'])
except dtype.DoesNotExist:
pass
else:
self.add_error(
"name", _("A %s with this name already exists") % label
)
break
for k in cleaned_data.keys():
if not k.startswith("aliases"):
continue
if not cleaned_data[k]:
del cleaned_data[k]
continue
for dtype, name in [(RelayDomain, _('relay domain')),
(DomainAlias, _('domain alias')),
(Domain, _('domain'))]:
try:
dtype.objects.get(name=cleaned_data[k])
except dtype.DoesNotExist:
pass
else:
self.add_error(
k, _("A %s with this name already exists") % name)
break
return cleaned_data
def save(self, user, commit=True, rdomalias_post_create=False):
"""Custom save method.
As relay domain aliases are defined using the same form as
relay domains, we need to save them manually.
:param ``User`` user: connected user
"""
rd = super(RelayDomainFormGeneral, self).save(commit=False)
if commit:
rd.oldname = self.oldname
rd.save()
aliases = []
for k, v in self.cleaned_data.iteritems():
if not k.startswith("aliases"):
continue
if v in ["", None]:
continue
aliases.append(v)
for rdalias in rd.relaydomainalias_set.all():
if rdalias.name not in aliases:
rdalias.delete()
else:
aliases.remove(rdalias.name)
if aliases:
events.raiseEvent(
"CanCreate", user, "relay_domain_aliases", len(aliases)
)
for alias in aliases:
try:
rd.relaydomainalias_set.get(name=alias)
except RelayDomainAlias.DoesNotExist:
pass
else:
continue
al = RelayDomainAlias(
name=alias, target=rd, enabled=rd.enabled
)
al.save(creator=user) \
if rdomalias_post_create else al.save()
return rd
class RelayDomainForm(TabForms):
"""Specific edition form for relay domains.
We use a *tabs* compatible form because extensions can add their
own tab. (ex: amavis)
"""
def __init__(self, request, *args, **kwargs):
self.user = request.user
self.forms = []
if self.user.has_perm("postfix_relay_domains.change_relaydomain"):
self.forms.append({
'id': 'general', 'title': _("General"),
'formtpl': 'postfix_relay_domains/relaydomain_form.html',
'cls': RelayDomainFormGeneral, 'mandatory': True
})
cbargs = [self.user]
if "instances" in kwargs:
cbargs += [kwargs["instances"]["general"]]
self.forms += events.raiseQueryEvent("ExtraRelayDomainForm", *cbargs)
super(RelayDomainForm, self).__init__(request, *args, **kwargs)
def extra_context(self, context):
"""Additional content.
"""
rdom = self.instances["general"]
context.update({
'action': reverse(
"postfix_relay_domains:relaydomain_change", args=[rdom.id]),
'formid': 'rdomform',
'title': rdom.name
})
def save(self):
"""Custom save method
As forms interact with each other, it is easier to make custom
code to save them.
"""
self.forms[0]['instance'].save(
self.request.user, rdomalias_post_create=True
)
for f in self.forms[1:]:
f["instance"].save(self.request.user)
def done(self):
events.raiseEvent('RelayDomainModified', self.instances["general"])
return render_to_json_response(_('Relay domain modified'))
@@ -1,154 +0,0 @@
"""General event callbacks."""
from django.conf import settings
from django.core.urlresolvers import reverse
from django.db.models import Q
from django.template import Template, Context
from django.utils.translation import ugettext_lazy
from modoboa.core.extensions import exts_pool
from modoboa.lib import events
from .models import RelayDomain, RelayDomainAlias
PERMISSIONS = {
"Resellers": [
("postfix_relay_domains", "relaydomain", "add_relaydomain"),
("postfix_relay_domains", "relaydomain", "change_relaydomain"),
("postfix_relay_domains", "relaydomain", "delete_relaydomain"),
("postfix_relay_domains", "relaydomainalias", "add_relaydomainalias"),
("postfix_relay_domains", "relaydomainalias",
"change_relaydomainalias"),
("postfix_relay_domains", "relaydomainalias",
"delete_relaydomainalias"),
("postfix_relay_domains", "service", "add_service"),
("postfix_relay_domains", "service", "change_service"),
("postfix_relay_domains", "service", "delete_service")
]
}
@events.observe("GetExtraRolePermissions")
def extra_permissions(rolename):
"""Return extra permissions for Resellers."""
if not exts_pool.is_extension_installed("modoboa.extensions.limits"):
return []
return PERMISSIONS.get(rolename, [])
@events.observe('GetStaticContent')
def static_content(caller, st_type, user):
if caller != 'domains' or st_type != 'js':
return []
t = Template("""<script src="{{ STATIC_URL }}postfix_relay_domains/js/relay_domains.js" type="text/javascript"></script>
<script type="text/javascript">
var rdomain;
$(document).ready(function() {
rdomain = new RelayDomains({});
});
</script>
""")
return [t.render(Context({'STATIC_URL': settings.STATIC_URL}))]
@events.observe('ExtraDomainFilters')
def extra_domain_filters():
return ['srvfilter']
@events.observe('ExtraDomainMenuEntries')
def extra_domain_menu_entries(user):
return [{
"name": "newrelaydomain",
"label": ugettext_lazy("Add relay domain"),
"img": "fa fa-plus",
"modal": True,
"modalcb": "rdomain.domainform_cb",
"url": reverse("postfix_relay_domains:relaydomain_add")
}]
@events.observe('ExtraDomainEntries')
def extra_domain_entries(user, domfilter, searchquery, **extrafilters):
if domfilter is not None and domfilter and domfilter != 'relaydomain':
return []
relay_domains = RelayDomain.objects.get_for_admin(user)
if searchquery is not None:
q = Q(name__contains=searchquery)
q |= Q(relaydomainalias__name__contains=searchquery)
relay_domains = relay_domains.filter(q).distinct()
if 'srvfilter' in extrafilters and extrafilters['srvfilter']:
relay_domains = relay_domains.filter(
Q(service__name=extrafilters['srvfilter'])
)
return relay_domains
@events.observe('GetDomainModifyLink')
def rdomain_modify_link(domain):
if not isinstance(domain, RelayDomain):
return {}
return {
'url': reverse(
"postfix_relay_domains:relaydomain_change",
args=[domain.id]
),
'modalcb': 'rdomain.editdomain_form_cb'
}
@events.observe('GetDomainActions')
def rdomain_actions(user, domain):
if not isinstance(domain, RelayDomain):
return []
if not user.has_perm("postfix_relay_domains.delete_relaydomain"):
return []
return [{
"name": "delrelaydomain",
"url": reverse(
"postfix_relay_domains:relaydomain_delete",
args=[domain.id]
),
"title": ugettext_lazy("Delete %s?" % domain.name),
"img": "fa fa-trash"
}]
@events.observe('CheckDomainName')
def check_domain_name():
return [
(RelayDomain, ugettext_lazy('relay domain')),
(RelayDomainAlias, ugettext_lazy('relay domain alias'))
]
@events.observe('GetExtraLimitTemplates')
def extra_limit_templates():
return [
('relay_domains_limit', ugettext_lazy('Relay domains'),
ugettext_lazy('Maximum number of relay domains this user can create'),
'Resellers'),
("relay_domain_aliases_limit", ugettext_lazy("Relay domain aliases"),
ugettext_lazy(
'Maximum number of relay domain aliases this user can create'),
'Resellers'),
]
@events.observe('ExtraDomainImportHelp')
def extra_domain_import_help():
return [ugettext_lazy("""
<li><em>relaydomain; name; target host; service; enabled; verify recipients</em></li>
<li><em>relaydomainalias; name; target; enabled</em></li>
""")]
@events.observe('ImportObject')
def get_import_func(objtype):
from .lib import import_relaydomain, import_relaydomainalias
if objtype == 'relaydomain':
return [import_relaydomain]
if objtype == 'relaydomainalias':
return [import_relaydomainalias]
return []
@@ -1,14 +0,0 @@
from django.db import transaction
from .models import RelayDomain, RelayDomainAlias
@transaction.commit_on_success
def import_relaydomain(user, row, formopts):
"""Specific code for relay domains import"""
RelayDomain().from_csv(user, row)
@transaction.commit_on_success
def import_relaydomainalias(user, row, formopts):
"""Specific code for relay domain aliases import"""
RelayDomainAlias().from_csv(user, row)
@@ -1,85 +0,0 @@
"""Callbacks related to limits extension."""
from django.utils.translation import ugettext_lazy
from modoboa.extensions.limits.lib import inc_limit_usage, dec_limit_usage
from modoboa.extensions.limits.models import LimitsPool, Limit
from modoboa.lib import events
from modoboa.lib.permissions import get_object_owner
from .models import RelayDomainAlias
def create_new_limits():
"""Create relay domains limits."""
new_limits = ["relay_domains_limit", "relay_domain_aliases_limit"]
for pool in LimitsPool.objects.all():
for lname in new_limits:
Limit.objects.get_or_create(name=lname, pool=pool, maxvalue=0)
@events.observe("InitialDataLoaded")
def load_limits_dependant_data(name):
"""Complete existing pools with new limits."""
if name != "limits":
return
create_new_limits()
@events.observe('GetExtraParameters')
def extra_parameters(app, level):
from django import forms
if app != 'limits' or level != 'A':
return {}
return {
'deflt_relay_domains_limit': forms.IntegerField(
label=ugettext_lazy("Relay domains"),
initial=0,
help_text=ugettext_lazy(
"Maximum number of allowed relay domains for a new "
"administrator"
),
widget=forms.widgets.TextInput(
attrs={
"class": "col-md-1 form-control"})
),
'deflt_relay_domain_aliases_limit': forms.IntegerField(
label=ugettext_lazy("Relay domain aliases"),
initial=0,
help_text=ugettext_lazy(
"Maximum number of allowed relay domain aliases for a new "
"administrator"
),
widget=forms.widgets.TextInput(
attrs={
"class": "col-md-1 form-control"})
)
}
@events.observe('RelayDomainCreated')
def inc_relaydomains_count(user, rdomain):
inc_limit_usage(user, 'relay_domains_limit')
@events.observe('RelayDomainDeleted')
def dec_relaydomains_count(rdomain):
owner = get_object_owner(rdomain)
dec_limit_usage(owner, 'relay_domains_limit')
for rdomalias in rdomain.relaydomainalias_set.all():
dec_rdomaliases_count(rdomalias)
@events.observe('RelayDomainAliasCreated')
def inc_rdomaliases_count(user, rdomalias):
inc_limit_usage(user, 'relay_domain_aliases_limit')
@events.observe('RelayDomainAliasDeleted')
def dec_rdomaliases_count(rdomainaliases):
if isinstance(rdomainaliases, RelayDomainAlias):
rdomainaliases = [rdomainaliases]
for rdomainalias in rdomainaliases:
owner = get_object_owner(rdomainalias)
dec_limit_usage(owner, 'relay_domain_aliases_limit')
@@ -1,59 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('admin', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='RelayDomain',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(help_text='The domain name', unique=True, max_length=100, verbose_name='name')),
('target_host', models.CharField(help_text='Remote destination of this domain', max_length=255, verbose_name='target host')),
('enabled', models.BooleanField(default=True, help_text='Check to activate this domain', verbose_name='enabled')),
('verify_recipients', models.BooleanField(default=False, help_text='Check for valid recipients', verbose_name='verify recipients')),
('dates', models.ForeignKey(to='admin.ObjectDates')),
],
options={
'ordering': ['name'],
},
bases=(models.Model,),
),
migrations.CreateModel(
name='RelayDomainAlias',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(help_text='The alias name', unique=True, max_length=100, verbose_name='name')),
('enabled', models.BooleanField(default=True, help_text='Check to activate this alias', verbose_name='enabled')),
('dates', models.ForeignKey(to='admin.ObjectDates')),
('target', models.ForeignKey(verbose_name='target', to='postfix_relay_domains.RelayDomain', help_text='The relay domain this alias points to')),
],
options={
'abstract': False,
},
bases=(models.Model,),
),
migrations.CreateModel(
name='Service',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(help_text='The service name', unique=True, max_length=100, verbose_name='name')),
],
options={
},
bases=(models.Model,),
),
migrations.AddField(
model_name='relaydomain',
name='service',
field=models.ForeignKey(to='postfix_relay_domains.Service'),
preserve_default=True,
),
]
@@ -1,210 +0,0 @@
"""Relay domain related models."""
from django.contrib.contenttypes import generic
from django.db import models
from django.db.models.manager import Manager
from django.utils.translation import ugettext as _, ugettext_lazy
import reversion
from modoboa.core.models import ObjectAccess
from modoboa.extensions.admin.models import AdminObject
from modoboa.lib import parameters
from modoboa.lib.exceptions import BadRequest, NotFound
class RelayDomainManager(Manager):
def get_for_admin(self, admin):
"""Return the relay domains belonging to this admin.
The result is a ``QuerySet`` object, so this function can be used
to fill ``ModelChoiceField`` objects.
"""
if admin.is_superuser:
return self.get_query_set()
return self.get_query_set().filter(owners__user=admin)
class ServiceManager(Manager):
def load_from_master_cf(self):
"""Load services from the master.cf file.
Parse the configuration file to update the service table. New
entries are saved and outdated ones (ie. present in the
database but not in the file) are removed.
"""
with open(parameters.get_admin('MASTER_CF_PATH')) as fp:
content = fp.read()
services = []
for line in content.split('\n'):
if not line or line.startswith('#'):
continue
parts = line.strip().split()
if len(parts) != 8:
continue
if parts[7] != 'smtp':
continue
srv, created = self.get_or_create(name=parts[0])
services.append(parts[0])
to_delete = []
for service in self.all():
if service.name not in services:
to_delete.append(service.name)
Service.objects.filter(name__in=to_delete).delete()
class Service(models.Model):
"""Postfix service.
"""
name = models.CharField(
ugettext_lazy('name'), max_length=100, unique=True,
help_text=ugettext_lazy('The service name')
)
objects = ServiceManager()
def __str__(self):
return self.name
class RelayDomain(AdminObject):
"""Relay domain.
A relay domain differs from a usual domaine because its final
destination is not reached yet. It must be accepted by the MTA but
it will then be transfered to another one.
"""
name = models.CharField(
ugettext_lazy('name'), max_length=100, unique=True,
help_text=ugettext_lazy('The domain name')
)
target_host = models.CharField(
ugettext_lazy('target host'), max_length=255,
help_text=ugettext_lazy('Remote destination of this domain')
)
service = models.ForeignKey(Service)
enabled = models.BooleanField(
ugettext_lazy('enabled'),
help_text=ugettext_lazy('Check to activate this domain'),
default=True
)
verify_recipients = models.BooleanField(
ugettext_lazy('verify recipients'),
help_text=ugettext_lazy('Check for valid recipients'),
default=False
)
owners = generic.GenericRelation(ObjectAccess)
objects = RelayDomainManager()
class Meta:
ordering = ['name']
@property
def tags(self):
return [
{"name": "relaydomain", "label": _("Relay Domain"), "type": "dom"},
{"name": self.service.name, "label": "%s:" % self.service.name,
"type": "srv", "color": "info"}
]
@property
def aliases(self):
return self.relaydomainalias_set
def __str__(self):
return self.name
def to_csv(self, csvwriter):
"""Export this relay domain to CSV.
:param csvwriter:
"""
csvwriter.writerow(
["relaydomain", self.name, self.target_host,
self.service.name, self.enabled, self.verify_recipients]
)
for rdalias in self.relaydomainalias_set.all():
rdalias.to_csv(csvwriter)
def from_csv(self, user, row):
"""Import a relay domain from CSV.
:param user: user importing the relay domain
:param str row: relay domain definition
"""
if len(row) != 6:
raise BadRequest(_("Invalid line"))
self.name = row[1].strip()
self.target_host = row[2].strip()
self.service, created = Service.objects.get_or_create(
name=row[3].strip())
self.enabled = (row[4].strip() == 'True')
self.verify_recipients = (row[5].strip() == 'True')
self.save(creator=user)
def post_create(self, creator):
"""Post creation actions.
:param ``User`` creator: user whos created this relay domain
"""
super(RelayDomain, self).post_create(creator)
for rdomalias in self.relaydomainalias_set.all():
rdomalias.post_create(creator)
reversion.register(RelayDomain)
class RelayDomainAlias(AdminObject):
"""Relay domain alias.
"""
name = models.CharField(
ugettext_lazy("name"), max_length=100, unique=True,
help_text=ugettext_lazy("The alias name")
)
target = models.ForeignKey(
RelayDomain, verbose_name=ugettext_lazy('target'),
help_text=ugettext_lazy("The relay domain this alias points to")
)
enabled = models.BooleanField(
ugettext_lazy('enabled'),
help_text=ugettext_lazy("Check to activate this alias"),
default=True
)
def __str__(self):
return self.name
def to_csv(self, csvwriter):
"""Export this relay domain alias to CSV.
:param csvwriter:
"""
csvwriter.writerow(
['relaydomainalias', self.name, self.target.name, self.enabled]
)
def from_csv(self, user, row):
"""Import a relay domain alias from CSV.
:param user: user importing the relay domain alias
:param str row: relay domain alias definition
"""
if len(row) != 4:
raise BadRequest(_("Invalid line"))
self.name = row[1].strip()
try:
self.target = RelayDomain.objects.get(name=row[2].strip())
except RelayDomain.DoesNotExist:
raise NotFound(_("Relay domain %s does not exist" % row[2].strip()))
self.enabled = (row[3].strip() == 'True')
self.save(creator=user)
reversion.register(RelayDomainAlias)
@@ -1,55 +0,0 @@
# coding: utf-8
"""Declare postfix_relay_domains extension and register it."""
from django.utils.translation import ugettext_lazy
from modoboa.core.extensions import ModoExtension, exts_pool
from modoboa.lib import events, parameters
EXTENSION_EVENTS = [
"RelayDomainCreated",
"RelayDomainDeleted",
"RelayDomainModified",
"RelayDomainAliasCreated",
"RelayDomainAliasDeleted",
"ExtraRelayDomainForm",
"FillRelayDomainInstances"
]
class PostfixRelayDomains(ModoExtension):
"""Extension declaration."""
name = "postfix_relay_domains"
label = "Postfix relay domains"
version = "1.0"
description = ugettext_lazy("Relay domains support for Postfix")
def load(self):
from .app_settings import AdminParametersForm
parameters.register(
AdminParametersForm, ugettext_lazy("Relay domains")
)
events.declare(EXTENSION_EVENTS)
from modoboa.extensions.postfix_relay_domains import general_callbacks
if exts_pool.is_extension_installed("modoboa.extensions.limits"):
import limits_callbacks
if exts_pool.is_extension_installed("modoboa.extensions.amavis"):
import amavis_callbacks
def load_initial_data(self):
"""Create extension data."""
from .models import Service
for service_name in ['relay', 'smtp']:
Service.objects.get_or_create(name=service_name)
if not exts_pool.is_extension_installed("modoboa.extensions.limits"):
return
from .limits_callbacks import create_new_limits
create_new_limits()
exts_pool.register_extension(PostfixRelayDomains)
@@ -1,100 +0,0 @@
/**
* @module relay_domains
* @desc Utility class to manage relay domains.
*/
var RelayDomains = function(options) {
this.initialize(options);
};
RelayDomains.prototype = {
constructor: RelayDomains,
defaults: {
},
initialize: function(options) {
this.options = $.extend({}, this.defaults, options);
admin.register_tag_handler("srv", this.srv_tag_handler);
$(document).bind('domain_listing_refresh', this.listing_refresh_cb);
},
listing_refresh_cb: function() {
$("a[name=delrelaydomain]").confirm({
question: function() { return this.$element.attr('title'); },
method: "DELETE",
success_cb: $.proxy(admin.reload_listing, admin)
});
},
launch_scan: function(e) {
var $link = get_target(e);
e.preventDefault();
$.ajax({
type: "POST",
url: $link.attr("href"),
global: false
}).done(function(data) {
var $widget = $('#id_service');
$widget.empty();
$.each(data, function(label, id) {
$widget.append($('<option />', {
value: id,
html: label
}));
});
$('.modal-body').prepend(
build_success_alert(gettext('Services updated'))
);
}).fail(function(jqxhr) {
var result = $.parseJSON(jqxhr.responseText);
$('.modal-body').prepend(
build_error_alert(result[0])
);
});
},
domainform_cb: function() {
$('#btnscan').click(this.launch_scan);
$('input:text:visible:first').focus();
$("#original_aliases").dynamic_input({
input_added: function($row) {
$row.find("label").html("");
},
input_removed: function($input) {
$input.parents(".form-group").remove();
return true;
}
});
$(".submit").on('click', $.proxy(function(e) {
simple_ajax_form_post(e, {
formid: "rdomform",
reload_on_success: false,
success_cb: $.proxy(admin.reload_listing, admin)
});
}, this));
},
editdomain_form_cb: function() {
this.domainform_cb();
$(document).trigger('domform_init');
},
srv_tag_handler: function(tag, $link) {
if (this.navobj.getparam(tag + "filter") === undefined && $link.hasClass(tag)) {
var text = $link.attr("name");
this.navobj
.setparam("domfilter", "relaydomain")
.setparam(tag + "filter", text)
.update();
if ($("a[name=dom]").length === 0) {
$("#taglist").append(this.make_tag("relaydomain", "dom"));
}
$("#taglist").append(this.make_tag(text, tag));
return true;
}
return false;
}
};
@@ -1,3 +0,0 @@
{% extends "common/generic_modal_form.html" %}{% block formbody %}
{% include "postfix_relay_domains/relaydomain_form.html" %}
{% endblock %}
@@ -1,7 +0,0 @@
{% load form_tags %}
{% render_field form.name %}
{% render_field form.target_host %}
{% include "postfix_relay_domains/service_field.html" with field=form.service label_width="col-sm-4" %}
{% render_fields_group form "aliases" %}
{% render_field form.enabled %}
{% render_field form.verify_recipients %}
@@ -1,4 +0,0 @@
{% extends "common/generic_field.html" %}{% load i18n %}
{% block extra_controls %}
<a id="btnscan" class="btn btn-success" style="margin-top: 2px" href="{% url 'postfix_relay_domains:service_scan' %}">{% trans "Scan" %}</a>
{% endblock %}
@@ -1,209 +0,0 @@
from django.core.urlresolvers import reverse
from modoboa.core.factories import UserFactory
from modoboa.extensions.admin import factories
from modoboa.lib import parameters
from modoboa.lib.tests import ModoTestCase
from .factories import RelayDomainFactory, RelayDomainAliasFactory
from .models import RelayDomain, RelayDomainAlias, Service
class Operations(object):
def _create_relay_domain(self, name, status=200):
srv, created = Service.objects.get_or_create(name='dummy')
values = {
'name': name, 'target_host': 'external.host.tld',
'service': srv.id, 'enabled': True
}
return self.ajax_post(
reverse("postfix_relay_domains:relaydomain_add"),
values, status
)
def _relay_domain_alias_operation(self, optype, rdomain, name, status=200):
rdom = RelayDomain.objects.get(name=rdomain)
values = {
'name': rdom.name, 'target_host': rdom.target_host,
'service': rdom.service.id
}
aliases = [alias.name for alias in rdom.relaydomainalias_set.all()]
if optype == 'add':
aliases.append(name)
else:
aliases.remove(name)
for cpt, alias in enumerate(aliases):
fname = 'aliases' if not cpt else 'aliases_%d' % cpt
values[fname] = alias
return self.ajax_post(
reverse("postfix_relay_domains:relaydomain_change",
args=[rdom.id]),
values, status
)
def _check_limit(self, name, curvalue, maxvalue):
l = self.user.limitspool.get_limit('%s_limit' % name)
self.assertEqual(l.curvalue, curvalue)
self.assertEqual(l.maxvalue, maxvalue)
class RelayDomainsTestCase(ModoTestCase, Operations):
fixtures = ['initial_users.json']
def setUp(self):
super(RelayDomainsTestCase, self).setUp()
factories.populate_database()
self.rdom = RelayDomainFactory(name='relaydomain.tld')
RelayDomainAliasFactory(name='relaydomainalias.tld', target=self.rdom)
def test_create_relaydomain(self):
"""Test the creation of a relay domain.
We also check that unique constraints are respected: domain,
relay domain alias.
FIXME: add a check for domain alias.
"""
self._create_relay_domain('relaydomain1.tld')
rdom = RelayDomain.objects.get(name='relaydomain1.tld')
self.assertEqual(rdom.target_host, 'external.host.tld')
self.assertEqual(rdom.service.name, 'dummy')
self.assertEqual(rdom.enabled, True)
self.assertEqual(rdom.verify_recipients, False)
resp = self._create_relay_domain('test.com', 400)
self.assertEqual(resp['form_errors']['name'][0],
'A domain with this name already exists')
resp = self._create_relay_domain('relaydomainalias.tld', 400)
self.assertEqual(
resp['form_errors']['name'][0],
'A relay domain alias with this name already exists'
)
def test_create_relaydomainalias(self):
"""Test the creation of a relay domain alias.
We also check that unique constraints are respected: domain,
relay domain.
FIXME: add a check for domain alias.
"""
self._relay_domain_alias_operation(
'add', self.rdom.name, 'relaydomainalias1.tld'
)
resp = self._relay_domain_alias_operation(
'add', self.rdom.name, 'test.com', 400
)
self.assertEqual(
resp['form_errors']['aliases_2'][0],
'A domain with this name already exists'
)
resp = self._relay_domain_alias_operation(
'add', self.rdom.name, self.rdom.name, 400
)
self.assertEqual(
resp['form_errors']['aliases_2'][0],
'A relay domain with this name already exists'
)
def test_edit_relaydomain(self):
"""Test the modification of a relay domain.
Rename 'relaydomain.tld' domain to 'relaydomain.org'
"""
values = {
'name': 'relaydomain.org', 'target_host': self.rdom.target_host,
'service': self.rdom.service.id
}
self.ajax_post(
reverse('postfix_relay_domains:relaydomain_change',
args=[self.rdom.id]),
values
)
RelayDomain.objects.get(name='relaydomain.org')
def test_edit_relaydomainalias(self):
"""Test the modification of a relay domain alias.
Rename 'relaydomainalias.tld' domain to 'relaydomainalias.net'
"""
values = {
'name': 'relaydomain.org', 'target_host': self.rdom.target_host,
'service': self.rdom.service.id, 'aliases': 'relaydomainalias.net'
}
self.ajax_post(
reverse('postfix_relay_domains:relaydomain_change',
args=[self.rdom.id]),
values
)
RelayDomainAlias.objects.get(name='relaydomainalias.net')
with self.assertRaises(RelayDomainAlias.DoesNotExist):
RelayDomainAlias.objects.get(name='relaydomainalias.tld')
def test_delete_relaydomain(self):
"""Test the removal of a relay domain
"""
self.ajax_post(
reverse("postfix_relay_domains:relaydomain_delete",
args=[self.rdom.id]),
{}
)
with self.assertRaises(RelayDomain.DoesNotExist):
RelayDomain.objects.get(name='relaydomain.tld')
def test_delete_relaydomainalias(self):
"""Test the remove of a relay domain alias
"""
self._relay_domain_alias_operation(
'del', self.rdom.name, 'relaydomainalias.tld'
)
with self.assertRaises(RelayDomain.DoesNotExist):
RelayDomain.objects.get(name='relaydomainalias.tld')
class LimitsTestCase(ModoTestCase, Operations):
def setUp(self):
super(LimitsTestCase, self).setUp()
from modoboa.extensions.limits.models import LimitTemplates
for tpl in LimitTemplates().templates:
parameters.save_admin('DEFLT_%s' % tpl[0].upper(), 2, app='limits')
self.user = UserFactory.create(
username='reseller', groups=('Resellers',)
)
self.clt.logout()
self.clt.login(username='reseller', password='toto')
def test_relay_domains_limit(self):
self._create_relay_domain('relaydomain1.tld')
self._check_limit('relay_domains', 1, 2)
self._create_relay_domain('relaydomain2.tld')
self._check_limit('relay_domains', 2, 2)
resp = self._create_relay_domain('relaydomain3.tld', 403)
self._check_limit('relay_domains', 2, 2)
self.ajax_post(
reverse('postfix_relay_domains:relaydomain_delete',
args=[RelayDomain.objects.get(name='relaydomain2.tld').id]),
{}
)
self._check_limit('relay_domains', 1, 2)
def test_relay_domain_aliases_limit(self):
self._create_relay_domain('relaydomain1.tld')
self._relay_domain_alias_operation(
'add', 'relaydomain1.tld', 'relay_domain_alias1.tld'
)
self._check_limit('relay_domain_aliases', 1, 2)
self._relay_domain_alias_operation(
'add', 'relaydomain1.tld', 'relay_domain_alias2.tld'
)
self._check_limit('relay_domain_aliases', 2, 2)
self._relay_domain_alias_operation(
'add', 'relaydomain1.tld', 'relay_domain_alias3.tld', 403
)
self._check_limit('relay_domain_aliases', 2, 2)
self._relay_domain_alias_operation(
'delete', 'relaydomain1.tld', 'relay_domain_alias2.tld'
)
self._check_limit('relay_domain_aliases', 1, 2)
@@ -1,13 +0,0 @@
from django.conf.urls import patterns, url
urlpatterns = patterns(
'modoboa.extensions.postfix_relay_domains.views',
url(r'^relaydomains/new/', 'create', name="relaydomain_add"),
url(r'^relaydomains/(?P<rdom_id>\d+)/edit/$', 'edit',
name='relaydomain_change'),
url(r'^relaydomains/(?P<rdom_id>\d+)/delete/$', 'delete',
name="relaydomain_delete"),
url(r'^relaydomains/scan_services/$', 'scan_for_services',
name="service_scan"),
)
@@ -1,75 +0,0 @@
import reversion
from django.shortcuts import render
from django.db import transaction
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from django.views.decorators.http import require_http_methods
from django.contrib.auth.decorators import (
login_required, permission_required
)
from modoboa.lib import events
from modoboa.lib.exceptions import PermDeniedException
from modoboa.lib.web_utils import render_to_json_response
from .models import RelayDomain, Service
from .forms import RelayDomainForm, RelayDomainFormGeneral
@login_required
@permission_required("postfix_relay_domains.add_relaydomain")
@transaction.commit_on_success
@reversion.create_revision()
def create(request, tplname="postfix_relay_domains/new_relaydomain_form.html"):
events.raiseEvent("CanCreate", request.user, "relay_domains")
if request.method == 'POST':
form = RelayDomainFormGeneral(request.POST)
if form.is_valid():
rdom = form.save(request.user)
rdom.post_create(request.user)
return render_to_json_response(_("Relay domain created"))
return render_to_json_response(
{'form_errors': form.errors}, status=400
)
ctx = {"title": _("New relay domain"),
"action_label": _("Create"),
"action_classes": "submit",
"action": reverse("postfix_relay_domains:relaydomain_add"),
"formid": "rdomform",
"form": RelayDomainFormGeneral()}
return render(request, tplname, ctx)
@login_required
@permission_required("postfix_relay_domains.change_relaydomain")
@reversion.create_revision()
def edit(request, rdom_id, tplname='common/tabforms.html'):
rdom = RelayDomain.objects.get(pk=rdom_id)
if not request.user.can_access(rdom):
raise PermDeniedException
instances = {'general': rdom}
events.raiseEvent("FillRelayDomainInstances", request.user, rdom, instances)
return RelayDomainForm(request, instances=instances).process()
@login_required
@permission_required("postfix_relay_domains.delete_relaydomain")
def delete(request, rdom_id):
rdom = RelayDomain.objects.get(pk=rdom_id)
if not request.user.can_access(rdom):
raise PermDeniedException
rdom.delete()
return render_to_json_response(_('Relay domain deleted'))
@login_required
@permission_required("postfix_relay_domains.add_service")
@require_http_methods(["POST"])
def scan_for_services(request):
try:
Service.objects.load_from_master_cf()
except IOError as e:
return render_to_json_response([str(e)], status=500)
return render_to_json_response(
dict((srv.name, srv.id) for srv in Service.objects.all())
)