diff --git a/modoboa/relaydomains/factories.py b/modoboa/relaydomains/factories.py deleted file mode 100644 index ad5182e23..000000000 --- a/modoboa/relaydomains/factories.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Factories.""" - -import factory - - -from modoboa.admin.factories import DomainFactory -from modoboa.core.factories import PermissionFactory - -from . import models - - -class ServiceFactory(factory.DjangoModelFactory): - - """Factory to create Service instances.""" - - class Meta: - model = models.Service - django_get_or_create = ('name', ) - - name = "dummy" - - -class RelayDomainFactory(PermissionFactory): - - """Factory to create RelayDomain instances.""" - - class Meta: - model = models.RelayDomain - django_get_or_create = ("domain", ) - - target_host = "external.host.tld" - domain = factory.SubFactory(DomainFactory) - verify_recipients = True - service = factory.SubFactory(ServiceFactory) diff --git a/modoboa/relaydomains/forms.py b/modoboa/relaydomains/forms.py index 7312cc329..abd3652b1 100644 --- a/modoboa/relaydomains/forms.py +++ b/modoboa/relaydomains/forms.py @@ -1,11 +1,11 @@ -""" -Postfix relay domains extension forms. -""" -from django import forms -from django.utils.translation import ugettext_lazy +"""Postfix relay domains extension forms.""" + +from django import forms +from django.core import validators +from django.utils.translation import ugettext as _ -from modoboa.lib.fields import DomainNameField from modoboa.lib.form_utils import WizardStep +from modoboa.lib import validators as lib_validators from .models import RelayDomain @@ -20,14 +20,8 @@ class RelayDomainWizardStep(WizardStep): class RelayDomainFormGeneral(forms.ModelForm): - """RelayDomain form.""" - name = DomainNameField( - label=ugettext_lazy("Name"), - help_text=ugettext_lazy("The relay domain name") - ) - class Meta: model = RelayDomain exclude = ["domain", "creation", "last_modification"] @@ -41,6 +35,22 @@ class RelayDomainFormGeneral(forms.ModelForm): "service": 3 } + def clean_target_host(self): + """Check that target host is valid.""" + validator_list = [ + lib_validators.validate_hostname, + validators.validate_ipv46_address + ] + value = self.cleaned_data.get("target_host") + for validator in validator_list: + try: + validator(value) + except forms.ValidationError: + pass + else: + return value + raise forms.ValidationError(_("Invalid value"), code="invalid") + def save(self, *args, **kwargs): """Custom save method.""" domain = kwargs.get("domain") @@ -50,5 +60,6 @@ class RelayDomainFormGeneral(forms.ModelForm): return None if domain: instance.domain = domain - instance.save() + if instance.domain.type == "relaydomain": + instance.save() return instance diff --git a/modoboa/relaydomains/handlers.py b/modoboa/relaydomains/handlers.py index 4a3032c9d..87b8e36e3 100644 --- a/modoboa/relaydomains/handlers.py +++ b/modoboa/relaydomains/handlers.py @@ -39,14 +39,18 @@ def clean_domain(sender, instance, **kwargs): """Remove or create RelayDomain record if needed.""" if kwargs.get("created"): return - has_relaydom = hasattr(instance, "relaydomain") - if instance.type == "domain" and has_relaydom: - instance.relaydomain.delete() - elif instance.type == "relaydomain" and not has_relaydom: + if instance.type == "domain": + try: + instance.relaydomain.delete() + except models.RelayDomain.DoesNotExist: + pass + else: # Make sure to create a RelayDomain instance since we can't do it # at form level... - models.RelayDomain.objects.create( - domain=instance, service=models.Service.objects.first()) + models.RelayDomain.objects.get_or_create( + domain=instance, + defaults={"service": models.Service.objects.first()} + ) @receiver(core_signals.register_postfix_maps) diff --git a/modoboa/relaydomains/migrations/0003_auto_20170116_1508.py b/modoboa/relaydomains/migrations/0003_auto_20170116_1508.py new file mode 100644 index 000000000..19c94ce5b --- /dev/null +++ b/modoboa/relaydomains/migrations/0003_auto_20170116_1508.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.12 on 2017-01-16 14:08 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('relaydomains', '0002_migrate_from_modoboa_admin_relaydomains'), + ] + + operations = [ + migrations.AddField( + model_name='relaydomain', + name='target_port', + field=models.IntegerField(default=25, help_text='Remote port of this domain', verbose_name='target host port'), + ), + migrations.AlterField( + model_name='relaydomain', + name='target_host', + field=models.CharField(help_text='Remote address (hostname or IP) of this domain', max_length=255, verbose_name='target host address'), + ), + ] diff --git a/modoboa/relaydomains/models.py b/modoboa/relaydomains/models.py index b72f17829..7b4d20ddb 100644 --- a/modoboa/relaydomains/models.py +++ b/modoboa/relaydomains/models.py @@ -67,8 +67,15 @@ class RelayDomain(admin_models.AdminObject): domain = models.OneToOneField("admin.Domain", null=True) target_host = models.CharField( - ugettext_lazy('target host'), max_length=255, - help_text=ugettext_lazy('Remote destination of this domain') + ugettext_lazy("target host address"), max_length=255, + help_text=ugettext_lazy( + "Remote address (hostname or IP) of this domain") + ) + target_port = models.IntegerField( + ugettext_lazy("target host port"), + help_text=ugettext_lazy( + "Remote port of this domain"), + default=25 ) service = models.ForeignKey(Service) verify_recipients = models.BooleanField( diff --git a/modoboa/relaydomains/postfix_maps.py b/modoboa/relaydomains/postfix_maps.py index 1ca969379..e91444e7f 100644 --- a/modoboa/relaydomains/postfix_maps.py +++ b/modoboa/relaydomains/postfix_maps.py @@ -26,7 +26,8 @@ class RelayDomainsTransportMap(object): filename = "sql-relaydomains-transport.cf" mysql = ( - "SELECT CONCAT(srv.name, ':[', rdom.target_host, ']') " + "SELECT CONCAT(srv.name, ':[', rdom.target_host, ']:'" + ", rdom.target_port) " "FROM postfix_relay_domains_service AS srv " "INNER JOIN postfix_relay_domains_relaydomain AS rdom " "ON rdom.service_id=srv.id " @@ -34,7 +35,8 @@ class RelayDomainsTransportMap(object): "WHERE dom.enabled=1 AND dom.name='%s'" ) postgres = ( - "SELECT srv.name || ':[' || rdom.target_host || ']' " + "SELECT srv.name || ':[' || rdom.target_host || ']:' || " + "rdom.target_port " "FROM postfix_relay_domains_service AS srv " "INNER JOIN postfix_relay_domains_relaydomain AS rdom " "ON rdom.service_id=srv.id " @@ -42,7 +44,8 @@ class RelayDomainsTransportMap(object): "WHERE dom.enabled AND dom.name='%s'" ) sqlite = ( - "SELECT srv.name || ':[' || rdom.target_host || ']' " + "SELECT srv.name || ':[' || rdom.target_host || ']:' || " + "rdom.target_host " "FROM postfix_relay_domains_service AS srv " "INNER JOIN postfix_relay_domains_relaydomain AS rdom " "ON rdom.service_id=srv.id " diff --git a/modoboa/relaydomains/templates/relaydomains/relaydomain_form.html b/modoboa/relaydomains/templates/relaydomains/relaydomain_form.html index 3d078f556..4696fac0e 100644 --- a/modoboa/relaydomains/templates/relaydomains/relaydomain_form.html +++ b/modoboa/relaydomains/templates/relaydomains/relaydomain_form.html @@ -1,5 +1,6 @@ {% load form_tags %} {% render_field form.target_host %} +{% render_field form.target_port %} {% include "relaydomains/service_field.html" with field=form.service label_width="col-sm-4" %} {% render_field form.verify_recipients %} diff --git a/modoboa/relaydomains/tests.py b/modoboa/relaydomains/tests.py index 5805299cf..0287c0bad 100644 --- a/modoboa/relaydomains/tests.py +++ b/modoboa/relaydomains/tests.py @@ -13,7 +13,6 @@ from modoboa.lib.tests import ModoTestCase from modoboa.lib.test_utils import MapFilesTestCaseMixin from modoboa.limits import utils as limits_utils -from .factories import RelayDomainFactory from . import models @@ -23,8 +22,8 @@ class Operations(object): srv, created = models.Service.objects.get_or_create(name='dummy') values = { "name": name, "create_dom_admin": False, "type": "relaydomain", - "target_host": "external.host.tld", "service": srv.id, - "enabled": True, "stepid": "step3" + "target_host": "external.host.tld", "target_port": 25, + "service": srv.id, "enabled": True, "stepid": "step3" } return self.ajax_post( reverse("admin:domain_add"), @@ -35,6 +34,7 @@ class Operations(object): rdom = models.RelayDomain.objects.get(domain__name=rdomain) values = { "name": rdom.domain.name, "target_host": rdom.target_host, + "target_port": rdom.target_port, "type": "relaydomain", "service": rdom.service.id } aliases = [alias.name for alias in rdom.domain.domainalias_set.all()] @@ -64,9 +64,11 @@ class RelayDomainsTestCase(ModoTestCase, Operations): """Create test data.""" super(RelayDomainsTestCase, cls).setUpTestData() admin_factories.populate_database() - cls.rdom = RelayDomainFactory(domain__name="relaydomain.tld") - cls.rdom.domain.type = "relaydomain" - cls.rdom.domain.save() + dom = admin_factories.DomainFactory( + name="relaydomain.tld", type="relaydomain") + dom.relaydomain.target_host = "external.host.tld" + dom.relaydomain.save() + cls.rdom = dom.relaydomain admin_factories.DomainAliasFactory( name="relaydomainalias.tld", target=cls.rdom.domain) admin_factories.MailboxFactory( @@ -141,28 +143,29 @@ class RelayDomainsTestCase(ModoTestCase, Operations): """ values = { "name": "relaydomain.org", "target_host": self.rdom.target_host, - "type": "relaydomain", "enabled": True, + "target_port": 4040, "type": "relaydomain", "enabled": True, "service": self.rdom.service.id } self.ajax_post( - reverse("admin:domain_change", - args=[self.rdom.domain.id]), + reverse("admin:domain_change", args=[self.rdom.domain.id]), values ) - models.RelayDomain.objects.get(domain__name='relaydomain.org') + self.rdom.refresh_from_db() + self.assertEqual(self.rdom.target_port, 4040) def test_relaydomain_domain_switch(self): """Check domain <-> relaydomain transitions.""" - domain = self.rdom.domain + domain_pk = self.rdom.domain.pk values = { "name": "relaydomain.tld", "type": "domain", "enabled": True, "target_host": self.rdom.target_host, + "target_port": self.rdom.target_port, "service": self.rdom.service.pk } self.ajax_post( - reverse("admin:domain_change", args=[domain.pk]), values) + reverse("admin:domain_change", args=[domain_pk]), values) self.assertFalse( models.RelayDomain.objects.filter(domain=domain).exists()) self.assertEqual( @@ -174,7 +177,7 @@ class RelayDomainsTestCase(ModoTestCase, Operations): "enabled": True, } self.ajax_post( - reverse("admin:domain_change", args=[domain.pk]), values) + reverse("admin:domain_change", args=[domain_pk]), values) self.assertEqual( admin_models.Domain.objects.get(name="relaydomain.tld").type, "relaydomain") @@ -189,6 +192,7 @@ class RelayDomainsTestCase(ModoTestCase, Operations): """ values = { "name": "relaydomain.org", "target_host": self.rdom.target_host, + "target_port": self.rdom.target_port, "type": "relaydomain", "service": self.rdom.service.id, "aliases": "relaydomainalias.net" } @@ -246,7 +250,7 @@ class ImportTestCase(ModoTestCase): def test_webui_import(self): """Check if import from webui works.""" - f = ContentFile("relaydomain;relay.com;127.0.0.1;relay;True;True", + f = ContentFile("relaydomain;relay.com;127.0.0.1;25;relay;True;True", name="domains.csv") self.client.post( reverse("admin:domain_import"), {