mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-19 05:58:34 +02:00
Reintroduced relay domain concept. (#1334)
* Reintroduced relay domain concept. * Removed wrong url include. * Removed deprecated tests. * Updated requirement.
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.7 on 2017-12-20 14:17
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('transport', '0001_initial'),
|
||||
('admin', '0010_auto_20170215_1328'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='domain',
|
||||
name='transport',
|
||||
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, to='transport.Transport'),
|
||||
),
|
||||
]
|
||||
@@ -55,6 +55,9 @@ class Domain(AdminObject):
|
||||
help_text=ugettext_lazy("Check to enable DNS checks for this domain")
|
||||
)
|
||||
|
||||
transport = models.OneToOneField(
|
||||
"transport.Transport", null=True, on_delete=models.SET_NULL)
|
||||
|
||||
class Meta:
|
||||
permissions = (
|
||||
("view_domain", "View domain"),
|
||||
@@ -94,7 +97,15 @@ class Domain(AdminObject):
|
||||
for dt in constants.DOMAIN_TYPES:
|
||||
if self.type == dt[0]:
|
||||
label = dt[1]
|
||||
return [{"name": self.type, "label": label, "type": "dom"}]
|
||||
result = [{"name": self.type, "label": label, "type": "dom"}]
|
||||
if self.transport:
|
||||
result.append({
|
||||
"name": self.transport.service,
|
||||
"label": self.transport.service,
|
||||
"type": "srv",
|
||||
"color": "info"
|
||||
})
|
||||
return result
|
||||
|
||||
@property
|
||||
def admins(self):
|
||||
|
||||
@@ -128,6 +128,7 @@ Domains.prototype = {
|
||||
this.options.navigation_params.push("domfilter", "srvfilter");
|
||||
this.options.eor_message = gettext("No more domain to show");
|
||||
this.register_tag_handler("dom");
|
||||
this.register_tag_handler("srv", this.srv_tag_handler);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -178,6 +179,22 @@ Domains.prototype = {
|
||||
this.init_domain_links(data);
|
||||
},
|
||||
|
||||
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;
|
||||
},
|
||||
|
||||
/**
|
||||
* A new page has been received, inject it.
|
||||
*
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{% extends "admin/listing.html" %}
|
||||
{% load i18n admin_tags lib_tags %}
|
||||
|
||||
{% load i18n admin_tags lib_tags static%}
|
||||
|
||||
{% block pagetitle %}{% trans "Domains" %}{% endblock %}
|
||||
|
||||
@@ -9,14 +10,15 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
{{ block.super }}
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
admin = new Domains({
|
||||
load_page_url: "{% url 'admin:_domain_list' %}"
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{{ block.super }}
|
||||
<script src="{% static 'transport/js/transport.js' %}" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
admin = new Domains({
|
||||
load_page_url: "{% url 'admin:_domain_list' %}"
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% extra_static_content "domains" "js" user %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -119,7 +119,15 @@ class U2UTestCase(TestCase):
|
||||
"votre annonce",
|
||||
"Accusé de réception de votre annonce"),
|
||||
("Sm=?ISO-8859-1?B?9g==?=rg=?ISO-8859-1?B?5Q==?=sbord",
|
||||
"Sm\xf6rg\xe5sbord")
|
||||
"Sm\xf6rg\xe5sbord"),
|
||||
# The following case currently fails because of the way we split
|
||||
# encoded words to parse them separately, which can lead to
|
||||
# unexpected unicode decode errors... I think it will work fine on
|
||||
# Python3
|
||||
# ("=?utf-8?B?VMOpbMOpcMOpYWdlIFZJTkNJIEF1dG9yb3V0ZXMgLSBFeHDD?=\n"
|
||||
# "=?utf-8?B?qWRpdGlvbiBkZSB2b3RyZSBjb21tYW5kZSBOwrAgMjAxNzEyMDcw"
|
||||
# "MDA1?=\n=?utf-8?B?MyBkdSAwNy8xMi8yMDE3IDE0OjQ5OjQx?=",
|
||||
# "")
|
||||
]
|
||||
for sample in samples:
|
||||
self.assertEqual(u2u_decode.u2u_decode(sample[0]), sample[1])
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
# coding: utf-8
|
||||
|
||||
"""Unstructured rfc2047 header to unicode."""
|
||||
"""
|
||||
Unstructured rfc2047 header to unicode.
|
||||
|
||||
A stupid (and not accurate) answer to https://bugs.python.org/issue1079.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django import forms
|
||||
|
||||
from modoboa.lib.form_utils import WizardStep
|
||||
from modoboa.transport import backends as tr_backends
|
||||
from modoboa.transport import forms as tr_forms
|
||||
from modoboa.transport import models as tr_models
|
||||
|
||||
@@ -19,29 +16,16 @@ class RelayDomainWizardStep(WizardStep):
|
||||
return wizard.steps[0].form.cleaned_data["type"] == "relaydomain"
|
||||
|
||||
|
||||
class RelayDomainFormGeneral(tr_forms.BackendSettingsMixin, forms.ModelForm):
|
||||
"""A form to display transport of type relay."""
|
||||
class RelayDomainFormGeneral(tr_forms.TransportForm):
|
||||
"""A form to display transport."""
|
||||
|
||||
class Meta(object):
|
||||
class Meta:
|
||||
model = tr_models.Transport
|
||||
exclude = [
|
||||
"creation", "pattern", "service", "next_hop", "enabled",
|
||||
"creation", "pattern", "next_hop", "enabled",
|
||||
"_settings"
|
||||
]
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(RelayDomainFormGeneral, self).__init__(*args, **kwargs)
|
||||
settings = tr_backends.manager.get_backend_settings("relay")
|
||||
self.inject_backend_settings("relay", settings)
|
||||
|
||||
def clean(self):
|
||||
"""Check values."""
|
||||
cleaned_data = super(RelayDomainFormGeneral, self).clean()
|
||||
if self.errors:
|
||||
return cleaned_data
|
||||
self.clean_backend_fields("relay")
|
||||
return cleaned_data
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""Custom save method."""
|
||||
domain = kwargs.pop("domain", None)
|
||||
@@ -52,7 +36,7 @@ class RelayDomainFormGeneral(tr_forms.BackendSettingsMixin, forms.ModelForm):
|
||||
instance = super(RelayDomainFormGeneral, self).save(commit=False)
|
||||
instance.pattern = domain.name
|
||||
instance.service = "relay"
|
||||
instance.enabled = domain.enabled
|
||||
self.backend.serialize(instance)
|
||||
instance.save()
|
||||
domain.transport = instance
|
||||
domain.save(update_fields=["transport"])
|
||||
return instance
|
||||
|
||||
@@ -40,7 +40,7 @@ def clean_domain(sender, instance, **kwargs):
|
||||
tr_models.Transport.objects.filter(pattern=instance.name).delete()
|
||||
|
||||
|
||||
@receiver(signals.pre_delete, sender=admin_models.Domain)
|
||||
@receiver(signals.post_delete, sender=admin_models.Domain)
|
||||
def delete_transport(sender, instance, **kwargs):
|
||||
"""Delete Transport instance if any."""
|
||||
tr_models.Transport.objects.filter(pattern=instance.name).delete()
|
||||
@@ -65,9 +65,9 @@ def extra_domain_form(sender, user, **kwargs):
|
||||
if not domain or domain.type != "relaydomain":
|
||||
return []
|
||||
return [{
|
||||
"id": "relaydomain", "title": _("Relay settings"),
|
||||
"id": "relaydomain", "title": _("Transport settings"),
|
||||
"cls": forms.RelayDomainFormGeneral,
|
||||
"formtpl": "relaydomains/relaydomain_form.html"
|
||||
"formtpl": "transport/_transport_form.html"
|
||||
}]
|
||||
|
||||
|
||||
@@ -81,8 +81,7 @@ def fill_domain_instances(sender, user, domain, **kwargs):
|
||||
if condition:
|
||||
return {}
|
||||
return {
|
||||
"relaydomain": (
|
||||
tr_models.Transport.objects.filter(pattern=domain.name).first())
|
||||
"relaydomain": domain.transport
|
||||
}
|
||||
|
||||
|
||||
@@ -90,8 +89,8 @@ def fill_domain_instances(sender, user, domain, **kwargs):
|
||||
def extra_wizard_step(sender, **kwargs):
|
||||
"""Return a step to configure the relay settings."""
|
||||
return [forms.RelayDomainWizardStep(
|
||||
"relay", forms.RelayDomainFormGeneral, _("Relay domain"),
|
||||
"relaydomains/relaydomain_form.html"
|
||||
"relay", forms.RelayDomainFormGeneral, _("Transport"),
|
||||
"transport/_transport_form.html"
|
||||
)]
|
||||
|
||||
|
||||
|
||||
@@ -28,4 +28,4 @@ def import_relaydomain(user, row, formopts):
|
||||
transport = tr_models.Transport(
|
||||
pattern=domain.name, service="relay", _settings=settings)
|
||||
tr_backends.manager.get_backend("relay").serialize(transport)
|
||||
transport.save(creator=user)
|
||||
transport.save()
|
||||
|
||||
@@ -10,14 +10,12 @@ def move_relaydomain_to_transport(apps, schema_editor):
|
||||
RelayDomain = apps.get_model("relaydomains", "RelayDomain")
|
||||
RecipientAccess = apps.get_model("relaydomains", "RecipientAccess")
|
||||
Transport = apps.get_model("transport", "Transport")
|
||||
tr_to_create = []
|
||||
ra_to_create = []
|
||||
for rd in RelayDomain.objects.select_related("domain", "service"):
|
||||
next_hop = "[{}]:{}".format(rd.target_host, rd.target_port)
|
||||
tr = Transport(
|
||||
tr = Transport.objects.create(
|
||||
pattern=rd.domain.name,
|
||||
service="relay",
|
||||
enabled=rd.domain.enabled,
|
||||
next_hop=next_hop,
|
||||
_settings={
|
||||
"relay_target_host": rd.target_host,
|
||||
@@ -25,13 +23,13 @@ def move_relaydomain_to_transport(apps, schema_editor):
|
||||
"relay_verify_recipients": rd.verify_recipients
|
||||
}
|
||||
)
|
||||
tr_to_create.append(tr)
|
||||
rd.domain.transport = tr
|
||||
rd.domain.save(update_fields=["transport"])
|
||||
if not rd.verify_recipients:
|
||||
continue
|
||||
ra_to_create.append(
|
||||
RecipientAccess(
|
||||
pattern=rd.domain.name, action="reject_unverified_recipient"))
|
||||
Transport.objects.bulk_create(tr_to_create)
|
||||
RecipientAccess.objects.bulk_create(ra_to_create)
|
||||
|
||||
|
||||
@@ -45,6 +43,7 @@ class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('relaydomains', '0007_recipientaccess'),
|
||||
('transport', '0001_initial'),
|
||||
('admin', '0011_domain_transport'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
"""RelayDomain serializers."""
|
||||
|
||||
import json
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from modoboa.admin import models as admin_models
|
||||
from modoboa.transport import backends as tr_backends
|
||||
from modoboa.transport import models as tr_models
|
||||
|
||||
|
||||
class TransportSerializer(serializers.ModelSerializer):
|
||||
"""Transport serializer."""
|
||||
|
||||
class Meta:
|
||||
model = tr_models.Transport
|
||||
fields = ("pk", "service", "_settings")
|
||||
|
||||
def validate(self, data):
|
||||
"""Check fields based on backend."""
|
||||
self.backend = tr_backends.manager.get_backend(data["service"])
|
||||
if not self.backend:
|
||||
raise serializers.ValidationError({
|
||||
"service": _("Unsupported service")
|
||||
})
|
||||
data["_settings"] = json.loads(data["_settings"])
|
||||
errors = self.backend.clean_fields(data["_settings"])
|
||||
if errors:
|
||||
raise serializers.ValidationError({
|
||||
"_settings": ",".join(
|
||||
["{}: {}".format(error[0], error[1]) for error in errors])
|
||||
})
|
||||
return data
|
||||
|
||||
|
||||
class RelayDomainSerializer(serializers.ModelSerializer):
|
||||
"""RelayDomain serializer class."""
|
||||
|
||||
transport = TransportSerializer()
|
||||
|
||||
class Meta:
|
||||
model = admin_models.Domain
|
||||
fields = ("pk", "name", "enabled", "transport")
|
||||
|
||||
def create(self, validated_data):
|
||||
"""Use backend to serialize data."""
|
||||
transport = tr_models.Transport(**validated_data.pop("transport"))
|
||||
transport.pattern = validated_data["name"]
|
||||
transport.save()
|
||||
domain = admin_models.Domain(**validated_data)
|
||||
domain.type = "relaydomain"
|
||||
domain.transport = transport
|
||||
domain.save(creator=self.context["request"].user)
|
||||
return domain
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
"""Use backend to serialize data."""
|
||||
transport_data = validated_data.pop("transport")
|
||||
for key, value in validated_data.items():
|
||||
setattr(instance, key, value)
|
||||
instance.save()
|
||||
for key, value in transport_data.items():
|
||||
setattr(instance.transport, key, value)
|
||||
instance.transport.pattern = instance.name
|
||||
instance.transport.save()
|
||||
return instance
|
||||
@@ -1,5 +0,0 @@
|
||||
{% extends "common/generic_modal_form.html" %}
|
||||
|
||||
{% block formbody %}
|
||||
{% include "relaydomains/relaydomain_form.html" %}
|
||||
{% endblock %}
|
||||
@@ -1,5 +0,0 @@
|
||||
{% load form_tags %}
|
||||
|
||||
{% render_field form.relay_target_host %}
|
||||
{% render_field form.relay_target_port %}
|
||||
{% render_field form.relay_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 'relaydomains:service_scan' %}">{% trans "Scan" %}</a>
|
||||
{% endblock %}
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import json
|
||||
|
||||
from django.core.files.base import ContentFile
|
||||
from django.urls import reverse
|
||||
from django.test import TestCase
|
||||
@@ -9,7 +11,7 @@ from django.test import TestCase
|
||||
from modoboa.admin import factories as admin_factories
|
||||
from modoboa.admin import models as admin_models
|
||||
from modoboa.core.factories import UserFactory
|
||||
from modoboa.lib.tests import ModoTestCase
|
||||
from modoboa.lib.tests import ModoAPITestCase, ModoTestCase
|
||||
from modoboa.lib.test_utils import MapFilesTestCaseMixin
|
||||
from modoboa.limits import utils as limits_utils
|
||||
from modoboa.transport import factories as tr_factories
|
||||
@@ -25,6 +27,7 @@ class Operations(object):
|
||||
"name": name,
|
||||
"create_dom_admin": False,
|
||||
"type": "relaydomain",
|
||||
"service": "relay",
|
||||
"relay_target_host": "external.host.tld",
|
||||
"relay_target_port": 25,
|
||||
"enabled": True,
|
||||
@@ -42,6 +45,7 @@ class Operations(object):
|
||||
transport = tr_models.Transport.objects.get(pattern=domain.name)
|
||||
values = {
|
||||
"name": domain.name,
|
||||
"service": "relay",
|
||||
"relay_target_host": transport._settings["relay_target_host"],
|
||||
"relay_target_port": transport._settings["relay_target_port"],
|
||||
"type": "relaydomain",
|
||||
@@ -75,15 +79,17 @@ class RelayDomainsTestCase(ModoTestCase, Operations):
|
||||
"""Create test data."""
|
||||
super(RelayDomainsTestCase, cls).setUpTestData()
|
||||
admin_factories.populate_database()
|
||||
cls.dom = admin_factories.DomainFactory(
|
||||
name="relaydomain.tld", type="relaydomain")
|
||||
cls.transport = tr_factories.TransportFactory(
|
||||
pattern=cls.dom.name, _settings={
|
||||
pattern="relaydomain.tld", service="relay",
|
||||
_settings={
|
||||
"relay_target_host": "external.host.tld",
|
||||
"relay_target_port": "25",
|
||||
"relay_verify_recipients": False
|
||||
}
|
||||
)
|
||||
cls.dom = admin_factories.DomainFactory(
|
||||
name="relaydomain.tld", type="relaydomain",
|
||||
transport=cls.transport)
|
||||
admin_factories.DomainAliasFactory(
|
||||
name="relaydomainalias.tld", target=cls.dom)
|
||||
admin_factories.MailboxFactory(
|
||||
@@ -158,6 +164,7 @@ class RelayDomainsTestCase(ModoTestCase, Operations):
|
||||
"""
|
||||
values = {
|
||||
"name": "relaydomain.org",
|
||||
"service": "relay",
|
||||
"relay_target_host": self.transport._settings["relay_target_host"],
|
||||
"relay_target_port": 4040,
|
||||
"relay_verify_recipients": True,
|
||||
@@ -193,6 +200,7 @@ class RelayDomainsTestCase(ModoTestCase, Operations):
|
||||
"quota": 0,
|
||||
"default_mailbox_quota": 0,
|
||||
"enabled": True,
|
||||
"service": "relay",
|
||||
"relay_target_host": self.transport._settings["relay_target_host"],
|
||||
"relay_target_port": self.transport._settings["relay_target_port"]
|
||||
}
|
||||
@@ -200,9 +208,8 @@ class RelayDomainsTestCase(ModoTestCase, Operations):
|
||||
reverse("admin:domain_change", args=[domain_pk]), values)
|
||||
with self.assertRaises(tr_models.Transport.DoesNotExist):
|
||||
self.transport.refresh_from_db()
|
||||
self.assertEqual(
|
||||
admin_models.Domain.objects.get(name="relaydomain.tld").type,
|
||||
"domain")
|
||||
self.dom.refresh_from_db()
|
||||
self.assertEqual(self.dom.type, "domain")
|
||||
values = {
|
||||
"name": "relaydomain.tld",
|
||||
"type": "relaydomain",
|
||||
@@ -223,6 +230,7 @@ class RelayDomainsTestCase(ModoTestCase, Operations):
|
||||
"""
|
||||
values = {
|
||||
"name": "relaydomain.org",
|
||||
"service": "relay",
|
||||
"relay_target_host": self.transport._settings["relay_target_host"],
|
||||
"relay_target_port": self.transport._settings["relay_target_port"],
|
||||
"type": "relaydomain",
|
||||
@@ -360,3 +368,102 @@ class MapFilesTestCase(MapFilesTestCaseMixin, TestCase):
|
||||
"sql-relaydomains.cf",
|
||||
"sql-relay-recipient-verification.cf"
|
||||
]
|
||||
|
||||
|
||||
class DataMixin(object):
|
||||
"""A mixin to provide test data."""
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
super(DataMixin, cls).setUpTestData()
|
||||
transport = tr_factories.TransportFactory(
|
||||
pattern="test.com", _settings={
|
||||
"relay_target_host": "external.host.tld",
|
||||
"relay_target_port": 25,
|
||||
"relay_verify_recipients": False
|
||||
}
|
||||
)
|
||||
cls.domain1 = admin_factories.DomainFactory(
|
||||
name="test.com", type="relaydomain", transport=transport)
|
||||
transport = tr_factories.TransportFactory(
|
||||
pattern="domain2.test", _settings={
|
||||
"relay_target_host": "external.host.tld",
|
||||
"relay_target_port": 25,
|
||||
"relay_verify_recipients": True
|
||||
}
|
||||
)
|
||||
cls.domain2 = admin_factories.DomainFactory(
|
||||
name="test2.com", type="relaydomain", transport=transport)
|
||||
|
||||
|
||||
class RelayDomainAPITestCase(DataMixin, ModoAPITestCase):
|
||||
"""API test cases."""
|
||||
|
||||
def test_list(self):
|
||||
"""Test list service."""
|
||||
url = reverse("api:relaydomain-list")
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(len(response.data), 2)
|
||||
|
||||
def test_create(self):
|
||||
"""Test create service."""
|
||||
url = reverse("api:relaydomain-list")
|
||||
settings = {
|
||||
"relay_target_host": "1.2.3.4"
|
||||
}
|
||||
data = {
|
||||
"name": "test3.com",
|
||||
"transport": {
|
||||
"service": "relay",
|
||||
"_settings": json.dumps(settings)
|
||||
}
|
||||
}
|
||||
response = self.client.post(url, data, format="json")
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertEqual(
|
||||
response.data["transport"]["_settings"],
|
||||
["relay_target_port: This field is required"]
|
||||
)
|
||||
|
||||
settings.update({"relay_target_port": 25})
|
||||
data["transport"]["_settings"] = json.dumps(settings)
|
||||
response = self.client.post(url, data, format="json")
|
||||
self.assertEqual(response.status_code, 201)
|
||||
domain = admin_models.Domain.objects.get(name="test3.com")
|
||||
self.assertEqual(
|
||||
domain.transport.next_hop, "[{}]:{}".format(
|
||||
settings["relay_target_host"], settings["relay_target_port"])
|
||||
)
|
||||
|
||||
def test_update(self):
|
||||
"""Test update service."""
|
||||
url = reverse("api:relaydomain-detail", args=[self.domain1.pk])
|
||||
settings = self.domain1.transport._settings.copy()
|
||||
settings.update({"relay_target_port": 1000})
|
||||
data = {
|
||||
"name": "test3.com",
|
||||
"transport": {
|
||||
"service": "relay",
|
||||
"_settings": json.dumps(settings)
|
||||
}
|
||||
}
|
||||
response = self.client.put(url, data, format="json")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.domain1.refresh_from_db()
|
||||
self.domain1.transport.refresh_from_db()
|
||||
self.assertEqual(self.domain1.name, data["name"])
|
||||
self.assertEqual(
|
||||
self.domain1.transport.next_hop, "[{}]:{}".format(
|
||||
settings["relay_target_host"], settings["relay_target_port"])
|
||||
)
|
||||
|
||||
def test_delete(self):
|
||||
"""Test delete service."""
|
||||
url = reverse("api:relaydomain-detail", args=[self.domain1.pk])
|
||||
response = self.client.delete(url)
|
||||
self.assertEqual(response.status_code, 204)
|
||||
with self.assertRaises(admin_models.Domain.DoesNotExist):
|
||||
self.domain1.refresh_from_db()
|
||||
self.assertFalse(
|
||||
tr_models.Transport.objects.filter(pattern="test.com").exists())
|
||||
|
||||
@@ -7,5 +7,5 @@ from . import viewsets
|
||||
|
||||
router = routers.SimpleRouter()
|
||||
router.register(
|
||||
r"transports", viewsets.TransportViewSet, base_name="transport")
|
||||
r"relaydomains", viewsets.RelayDomainViewSet, base_name="relaydomain")
|
||||
urlpatterns = router.urls
|
||||
@@ -0,0 +1,26 @@
|
||||
"""RelayDomain viewsets."""
|
||||
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.permissions import IsAuthenticated, DjangoModelPermissions
|
||||
|
||||
from modoboa.admin import models as admin_models
|
||||
|
||||
from . import serializers
|
||||
|
||||
|
||||
class RelayDomainViewSet(viewsets.ModelViewSet):
|
||||
"""RelayDomain viewset."""
|
||||
|
||||
permission_classes = [IsAuthenticated, DjangoModelPermissions, ]
|
||||
serializer_class = serializers.RelayDomainSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
"""Filter queryset based on current user."""
|
||||
return (
|
||||
admin_models.Domain.objects.get_for_admin(self.request.user)
|
||||
.filter(type="relaydomain")
|
||||
)
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
"""Add custom args to delete call."""
|
||||
instance.delete(self.request.user)
|
||||
@@ -1,3 +0,0 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@@ -2,12 +2,10 @@
|
||||
|
||||
import factory
|
||||
|
||||
from modoboa.core.factories import PermissionFactory
|
||||
|
||||
from . import models
|
||||
|
||||
|
||||
class TransportFactory(PermissionFactory):
|
||||
class TransportFactory(factory.django.DjangoModelFactory):
|
||||
"""Factory for Transport."""
|
||||
|
||||
class Meta:
|
||||
@@ -17,4 +15,3 @@ class TransportFactory(PermissionFactory):
|
||||
pattern = factory.Sequence(lambda n: "transport{}".format(n))
|
||||
service = "relay"
|
||||
next_hop = "[external.host.tld]:25"
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ class BackendSettingsMixin(object):
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Constructor."""
|
||||
super(BackendSettingsMixin, self).__init__(*args, **kwargs)
|
||||
self.setting_fields = []
|
||||
self.setting_field_names = []
|
||||
|
||||
def inject_backend_settings(self, name, settings):
|
||||
"""Inject backend settings to form."""
|
||||
@@ -35,7 +35,7 @@ class BackendSettingsMixin(object):
|
||||
options["widget"] = setting["widget"]
|
||||
self.fields[fullname] = TYPE_TO_FIELD_MAP[ftype](
|
||||
label=setting["label"], required=False, **options)
|
||||
self.setting_fields.append(fullname)
|
||||
self.setting_field_names.append(fullname)
|
||||
|
||||
def clean_backend_fields(self, name):
|
||||
"""Clean backend fields."""
|
||||
@@ -47,7 +47,8 @@ class BackendSettingsMixin(object):
|
||||
"""Set settings to JSON field."""
|
||||
transport = super(BackendSettingsMixin, self).save(commit=False)
|
||||
transport._settings = {
|
||||
name: self.cleaned_data[name] for name in self.setting_fields
|
||||
name: self.cleaned_data[name]
|
||||
for name in self.setting_field_names
|
||||
}
|
||||
if commit:
|
||||
transport.save()
|
||||
@@ -60,7 +61,7 @@ class TransportForm(BackendSettingsMixin, forms.ModelForm):
|
||||
service = forms.ChoiceField(choices=[])
|
||||
|
||||
class Meta:
|
||||
fields = ("pattern", "service", "enabled")
|
||||
fields = ("pattern", "service")
|
||||
model = models.Transport
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -71,6 +72,10 @@ class TransportForm(BackendSettingsMixin, forms.ModelForm):
|
||||
for name, backend_settings in settings.items():
|
||||
self.inject_backend_settings(name, backend_settings)
|
||||
|
||||
@property
|
||||
def setting_fields(self):
|
||||
return [self[name] for name in self.setting_field_names]
|
||||
|
||||
def _clean_fields(self):
|
||||
"""Make backend settings required."""
|
||||
backend_name = self.data.get("service")
|
||||
|
||||
@@ -2,30 +2,26 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.urls import reverse
|
||||
from django.db.models import signals
|
||||
from django.dispatch import receiver
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from modoboa.admin import signals as admin_signals
|
||||
from modoboa.core import signals as core_signals
|
||||
|
||||
from . import backends
|
||||
from . import models
|
||||
from . import postfix_maps
|
||||
|
||||
|
||||
@receiver(admin_signals.extra_domain_menu_entries)
|
||||
def extra_domain_menu_entries(sender, user, **kwargs):
|
||||
"""Add extra menu entries."""
|
||||
return [{
|
||||
"name": "transports",
|
||||
"label": _("Transports"),
|
||||
"url": reverse("transport:transport_list"),
|
||||
"img": "fa fa-bus",
|
||||
}]
|
||||
|
||||
|
||||
@receiver(core_signals.register_postfix_maps)
|
||||
def register_postfix_maps(sender, **kwargs):
|
||||
"""Register postfix maps."""
|
||||
return [
|
||||
postfix_maps.TransportMap,
|
||||
]
|
||||
|
||||
|
||||
@receiver(signals.pre_save, sender=models.Transport)
|
||||
def serialize_transport_settings(sender, instance, **kwargs):
|
||||
"""Call backend serialize method on transport."""
|
||||
backend = backends.manager.get_backend(instance.service)
|
||||
backend.serialize(instance)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.utils.timezone
|
||||
|
||||
import jsonfield.fields
|
||||
|
||||
|
||||
@@ -19,12 +19,9 @@ class Migration(migrations.Migration):
|
||||
name='Transport',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('creation', models.DateTimeField(default=django.utils.timezone.now)),
|
||||
('last_modification', models.DateTimeField(auto_now=True)),
|
||||
('pattern', models.CharField(max_length=254, unique=True, verbose_name='pattern')),
|
||||
('service', models.CharField(max_length=30, verbose_name='service')),
|
||||
('next_hop', models.CharField(blank=True, max_length=100, verbose_name='next hop')),
|
||||
('enabled', models.BooleanField(default=True, verbose_name='enabled')),
|
||||
('_settings', jsonfield.fields.JSONField(default={})),
|
||||
],
|
||||
options={
|
||||
|
||||
@@ -9,16 +9,15 @@ from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import jsonfield
|
||||
|
||||
from modoboa.admin import models as admin_models
|
||||
from . import backends
|
||||
|
||||
|
||||
class Transport(admin_models.AdminObject):
|
||||
class Transport(models.Model):
|
||||
"""Transport table."""
|
||||
|
||||
pattern = models.CharField(_("pattern"), unique=True, max_length=254)
|
||||
service = models.CharField(_("service"), max_length=30)
|
||||
next_hop = models.CharField(_("next hop"), max_length=100, blank=True)
|
||||
enabled = models.BooleanField("enabled", default=True)
|
||||
|
||||
_settings = jsonfield.JSONField(default={})
|
||||
|
||||
@@ -28,5 +27,12 @@ class Transport(admin_models.AdminObject):
|
||||
def __str__(self):
|
||||
return self.pattern
|
||||
|
||||
@property
|
||||
def backend(self):
|
||||
"""Shortcut to access backend."""
|
||||
if not self.service:
|
||||
return None
|
||||
return backends.manager.get_backend(self.service)
|
||||
|
||||
|
||||
reversion.register(Transport)
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
"""Transport serializers."""
|
||||
|
||||
import json
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from . import backends
|
||||
from . import models
|
||||
|
||||
|
||||
class TransportSerializer(serializers.ModelSerializer):
|
||||
"""Transport serializer class."""
|
||||
|
||||
class Meta:
|
||||
model = models.Transport
|
||||
fields = ("pk", "pattern", "service", "enabled", "_settings")
|
||||
|
||||
def validate(self, data):
|
||||
"""Check fields based on backend."""
|
||||
self.backend = backends.manager.get_backend(data["service"])
|
||||
if not self.backend:
|
||||
raise serializers.ValidationError({
|
||||
"service": _("Unsupported service")
|
||||
})
|
||||
data["_settings"] = json.loads(data["_settings"])
|
||||
errors = self.backend.clean_fields(data["_settings"])
|
||||
if errors:
|
||||
raise serializers.ValidationError({
|
||||
"_settings": ",".join(
|
||||
["{}: {}".format(error[0], error[1]) for error in errors])
|
||||
})
|
||||
return data
|
||||
|
||||
def create(self, validated_data):
|
||||
"""Use backend to serialize data."""
|
||||
transport = models.Transport(**validated_data)
|
||||
self.backend.serialize(transport)
|
||||
transport.save(creator=self.context["request"].user)
|
||||
return transport
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
"""Use backend to serialize data."""
|
||||
for key, value in validated_data.items():
|
||||
setattr(instance, key, value)
|
||||
self.backend.serialize(instance)
|
||||
instance.save()
|
||||
return instance
|
||||
@@ -18,26 +18,8 @@ function transportFormCallback () {
|
||||
displayBackendSettings($(this).val());
|
||||
});
|
||||
displayBackendSettings($service_field.val());
|
||||
|
||||
$('.submit').one('click', $.proxy(function(e) {
|
||||
simple_ajax_form_post(e, {
|
||||
formid: 'transport_form',
|
||||
error_cb: transportFormCallback
|
||||
});
|
||||
}, this));
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$('#searchquery').focus(function () {
|
||||
$(this).val('');
|
||||
}).blur(function (e) {
|
||||
var $this = $(this);
|
||||
if ($this.val() === '') {
|
||||
$this.val(gettext('Search'));
|
||||
}
|
||||
});
|
||||
$('a[name=deltransport]').confirm({
|
||||
question: function() { return this.$element.attr('title'); },
|
||||
method: 'DELETE'
|
||||
});
|
||||
$(this).bind('domform_init', transportFormCallback);
|
||||
});
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
{% extends "common/generic_modal_form.html" %}
|
||||
|
||||
{% load form_tags %}
|
||||
|
||||
{% block formbody %}
|
||||
{% render_field form.pattern %}
|
||||
{% render_field form.service %}
|
||||
{% render_field form.enabled %}
|
||||
<div id="backend_settings">
|
||||
{% for field in setting_fields %}
|
||||
{% render_field field hidden=True %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% render_field form.service %}
|
||||
<div id="backend_settings">
|
||||
{% for field in form.setting_fields %}
|
||||
{% render_field field hidden=True %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
{% load i18n %}
|
||||
|
||||
<th id="name" class="sortable" data-sort_order="name" width="">{% trans "Name" %}</th>
|
||||
<th id="tags" class="sortable" data-sort_order="tags" width="">{% trans "Tags" %}</th>
|
||||
<th id="actions" width="">{% trans "Actions" %}</th>
|
||||
@@ -1,66 +0,0 @@
|
||||
{% extends "twocols.html" %}
|
||||
|
||||
{% load i18n admin_tags lib_tags static %}
|
||||
|
||||
{% block pagetitle %}{% trans "Transports" %}{% endblock %}
|
||||
|
||||
{% block leftcol %}
|
||||
<ul class="nav nav-sidebar">
|
||||
{% domains_menu "transports" user False %}
|
||||
</ul>
|
||||
{% endblock %}
|
||||
|
||||
{% block apparea %}
|
||||
<div class="row">
|
||||
<div class="col-sm-5">
|
||||
{% include "admin/searchform.html" %}
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
{% if perms.transport.add_transport %}
|
||||
<a href="{% url 'transport:transport_create' %}" class="btn btn-primary" data-toggle="ajaxmodal" modalcb="transportFormCallback">
|
||||
<span class="fa fa-plus"></span> {% trans "Add" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div id="taglist" class="col-sm-4"> </div>
|
||||
</div>
|
||||
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th id="pattern" class="sortable" data-sort_order="pattern" width="">{% trans "Pattern" %}</th>
|
||||
<th id="service" class="sortable" data-sort_order="service" width="">{% trans "Service" %}</th>
|
||||
<th id="actions" width="">{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for transport in object_list %}
|
||||
<tr class="{% if not transport.enabled %}text-muted{% endif %}">
|
||||
<td>{{ transport.pattern }}{% if not transport.enabled %} ({% trans "disabled" %}){% endif %}</td>
|
||||
<td><span class="label label-default">
|
||||
<a href="#" class="filter transport">{{ transport.service }}</a>
|
||||
</span></td>
|
||||
<td name="actions">
|
||||
<a href="{% url 'transport:transport_update' transport.pk %}"
|
||||
data-toggle="ajaxmodal" modalcb="transportFormCallback">
|
||||
<span class="fa fa-edit"></span>
|
||||
</a>
|
||||
<a href="{% url 'transport:transport_delete' transport.pk %}"
|
||||
name="deltransport" title="{% blocktrans with pattern=transport.pattern %}Delete {{ pattern }}? {% endblocktrans %}">
|
||||
<span class="fa fa-trash"></span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="3">
|
||||
<div class="alert alert-info">{% trans "No transport defined yet." %}</div>
|
||||
</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
{{ block.super }}
|
||||
<script type="text/javascript" src="{% static 'transport/js/transport.js' %}"></script>
|
||||
{% endblock %}
|
||||
@@ -1,163 +0,0 @@
|
||||
"""Transport tests."""
|
||||
|
||||
import json
|
||||
|
||||
from django.urls import reverse
|
||||
|
||||
from modoboa.lib.tests import ModoAPITestCase, ModoTestCase
|
||||
|
||||
from . import factories
|
||||
from . import models
|
||||
|
||||
|
||||
class DataMixin(object):
|
||||
"""A mixin to provide test data."""
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
super(DataMixin, cls).setUpTestData()
|
||||
cls.transport1 = factories.TransportFactory(
|
||||
pattern="domain1.test", _settings={
|
||||
"relay_target_host": "external.host.tld",
|
||||
"relay_target_port": 25,
|
||||
"relay_verify_recipients": False
|
||||
}
|
||||
)
|
||||
cls.transport2 = factories.TransportFactory(
|
||||
pattern="domain2.test", _settings={
|
||||
"relay_target_host": "external.host.tld",
|
||||
"relay_target_port": 25,
|
||||
"relay_verify_recipients": True
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class TransportViewsTestCase(DataMixin, ModoTestCase):
|
||||
"""Test transport views."""
|
||||
|
||||
def test_transport_list(self):
|
||||
"""Test list view."""
|
||||
url = reverse("transport:transport_list")
|
||||
response = self.client.get(url)
|
||||
self.assertContains(response, self.transport1.pattern)
|
||||
self.assertContains(response, self.transport2.pattern)
|
||||
|
||||
url = "{}?searchquery=domain1".format(url)
|
||||
response = self.client.get(url)
|
||||
self.assertContains(response, self.transport1.pattern)
|
||||
self.assertNotContains(response, self.transport2.pattern)
|
||||
|
||||
def test_create_transport(self):
|
||||
"""Test create view."""
|
||||
url = reverse("transport:transport_create")
|
||||
response = self.client.get(url)
|
||||
self.assertContains(response, "New transport")
|
||||
data = {
|
||||
"pattern": "domain3.test",
|
||||
"service": "relay",
|
||||
"relay_target_host": "1.2.3.4",
|
||||
}
|
||||
response = self.ajax_post(url, data, 400)
|
||||
|
||||
data.update({"relay_target_port": 25})
|
||||
self.ajax_post(url, data)
|
||||
transport = models.Transport.objects.get(pattern="domain3.test")
|
||||
self.assertEqual(
|
||||
transport.next_hop, "[{}]:{}".format(
|
||||
data["relay_target_host"], data["relay_target_port"])
|
||||
)
|
||||
|
||||
def test_update_transport(self):
|
||||
"""Test update view."""
|
||||
url = reverse("transport:transport_update", args=[self.transport1.pk])
|
||||
response = self.client.get(url)
|
||||
self.assertContains(response, "Edit transport")
|
||||
data = {
|
||||
"pattern": self.transport1.pattern,
|
||||
"service": self.transport1.service,
|
||||
"relay_target_host": "1.2.3.5",
|
||||
"relay_target_port": 234,
|
||||
"relay_verify_recipients": True
|
||||
}
|
||||
self.ajax_post(url, data)
|
||||
self.transport1.refresh_from_db()
|
||||
self.assertTrue(
|
||||
self.transport1._settings["relay_verify_recipients"])
|
||||
self.assertEqual(
|
||||
self.transport1.next_hop, "[{}]:{}".format(
|
||||
data["relay_target_host"], data["relay_target_port"])
|
||||
)
|
||||
|
||||
def test_delete_transport(self):
|
||||
"""Test delete view."""
|
||||
url = reverse("transport:transport_delete", args=[self.transport1.pk])
|
||||
response = self.client.delete(url)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
with self.assertRaises(models.Transport.DoesNotExist):
|
||||
self.transport1.refresh_from_db()
|
||||
|
||||
|
||||
class TransportAPITestCase(DataMixin, ModoAPITestCase):
|
||||
"""API test cases."""
|
||||
|
||||
def test_list(self):
|
||||
"""Test list service."""
|
||||
url = reverse("api:transport-list")
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(len(response.data), 2)
|
||||
|
||||
def test_create(self):
|
||||
"""Test create service."""
|
||||
url = reverse("api:transport-list")
|
||||
settings = {
|
||||
"relay_target_host": "1.2.3.4"
|
||||
}
|
||||
data = {
|
||||
"pattern": "domain3.test",
|
||||
"service": "relay",
|
||||
"_settings": json.dumps(settings)
|
||||
}
|
||||
response = self.client.post(url, data, format="json")
|
||||
self.assertEqual(response.status_code, 400)
|
||||
self.assertEqual(
|
||||
response.data["_settings"],
|
||||
["relay_target_port: This field is required"]
|
||||
)
|
||||
|
||||
settings.update({"relay_target_port": 25})
|
||||
data["_settings"] = json.dumps(settings)
|
||||
response = self.client.post(url, data, format="json")
|
||||
self.assertEqual(response.status_code, 201)
|
||||
transport = models.Transport.objects.get(pattern="domain3.test")
|
||||
self.assertEqual(
|
||||
transport.next_hop, "[{}]:{}".format(
|
||||
settings["relay_target_host"], settings["relay_target_port"])
|
||||
)
|
||||
|
||||
def test_update(self):
|
||||
"""Test update service."""
|
||||
url = reverse("api:transport-detail", args=[self.transport1.pk])
|
||||
settings = self.transport1._settings.copy()
|
||||
settings.update({"relay_target_port": 1000})
|
||||
data = {
|
||||
"pattern": "domain3.test",
|
||||
"service": "relay",
|
||||
"_settings": json.dumps(settings)
|
||||
}
|
||||
response = self.client.put(url, data, format="json")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.transport1.refresh_from_db()
|
||||
self.assertEqual(self.transport1.pattern, data["pattern"])
|
||||
self.assertEqual(
|
||||
self.transport1.next_hop, "[{}]:{}".format(
|
||||
settings["relay_target_host"], settings["relay_target_port"])
|
||||
)
|
||||
|
||||
def test_delete(self):
|
||||
"""Test delete service."""
|
||||
url = reverse("api:transport-detail", args=[self.transport1.pk])
|
||||
response = self.client.delete(url)
|
||||
self.assertEqual(response.status_code, 204)
|
||||
with self.assertRaises(models.Transport.DoesNotExist):
|
||||
self.transport1.refresh_from_db()
|
||||
@@ -1,17 +0,0 @@
|
||||
"""Transport urls."""
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from . import views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r"^$", views.TransportListView.as_view(),
|
||||
name="transport_list"),
|
||||
url(r"^create/$", views.TransportCreateView.as_view(),
|
||||
name="transport_create"),
|
||||
url(r"^update/(?P<pk>\d+)/$", views.TransportUpdateView.as_view(),
|
||||
name="transport_update"),
|
||||
url(r"^delete/(?P<pk>\d+)/$", views.TransportDeleteView.as_view(),
|
||||
name="transport_delete"),
|
||||
]
|
||||
@@ -1,142 +0,0 @@
|
||||
"""Transport views."""
|
||||
|
||||
from django import http
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
from django.views import generic
|
||||
|
||||
from django.contrib.auth import mixins as auth_mixins
|
||||
from django.contrib import messages
|
||||
|
||||
from . import backends
|
||||
from . import forms
|
||||
from . import models
|
||||
|
||||
|
||||
class TransportListView(
|
||||
auth_mixins.PermissionRequiredMixin, generic.ListView):
|
||||
"""ListView for Transport."""
|
||||
|
||||
model = models.Transport
|
||||
permission_required = "transport.add_transport"
|
||||
template_name = "transport/transport_list.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""Inject search query."""
|
||||
context = super(TransportListView, self).get_context_data(**kwargs)
|
||||
context.update({
|
||||
"selection": "domains",
|
||||
"squery": self.request.GET.get("searchquery")
|
||||
})
|
||||
return context
|
||||
|
||||
def get_queryset(self):
|
||||
"""Filter."""
|
||||
qs = super(TransportListView, self).get_queryset()
|
||||
searchquery = self.request.GET.get("searchquery")
|
||||
if searchquery:
|
||||
qs = qs.filter(pattern__icontains=searchquery)
|
||||
return qs
|
||||
|
||||
|
||||
class TransportMixin(object):
|
||||
"""Mixin for Transport creation/update."""
|
||||
|
||||
form_class = forms.TransportForm
|
||||
model = models.Transport
|
||||
success_message = ugettext_lazy("Transport created")
|
||||
success_url = reverse_lazy("transport:transport_list")
|
||||
template_name = "transport/_transport_form.html"
|
||||
|
||||
def form_invalid(self, form):
|
||||
"""Return JSON response."""
|
||||
return http.JsonResponse({
|
||||
"form_errors": form.errors,
|
||||
"prefix": form.prefix
|
||||
}, status=400)
|
||||
|
||||
def form_valid(self, form):
|
||||
"""Save transport."""
|
||||
transport = form.save(commit=False)
|
||||
backend = backends.manager.get_backend(transport.service)
|
||||
backend.serialize(transport)
|
||||
options = {}
|
||||
if not transport.pk:
|
||||
options.update({"creator": self.request.user})
|
||||
transport.save(**options)
|
||||
messages.success(self.request, self.success_message)
|
||||
return http.JsonResponse({})
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""Include extra data."""
|
||||
context = super(TransportMixin, self).get_context_data(
|
||||
**kwargs)
|
||||
setting_fields = []
|
||||
for fname in context["form"].setting_fields:
|
||||
setting_fields.append(context["form"][fname])
|
||||
context.update({
|
||||
"action_classes": "submit",
|
||||
"formid": "transport_form",
|
||||
"setting_fields": setting_fields
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
class TransportCreateView(
|
||||
auth_mixins.PermissionRequiredMixin,
|
||||
TransportMixin,
|
||||
generic.CreateView):
|
||||
"""Transport create view."""
|
||||
|
||||
permission_required = "transport.add_transport"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""Include extra data."""
|
||||
context = super(TransportCreateView, self).get_context_data(
|
||||
**kwargs)
|
||||
context.update({
|
||||
"action": reverse("transport:transport_create"),
|
||||
"action_label": _("Create"),
|
||||
"title": _("New transport"),
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
class TransportUpdateView(
|
||||
auth_mixins.PermissionRequiredMixin,
|
||||
TransportMixin,
|
||||
generic.UpdateView):
|
||||
"""Transport update view."""
|
||||
|
||||
permission_required = "transport.change_transport"
|
||||
success_message = ugettext_lazy("Transport updated")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""Include extra data."""
|
||||
context = super(TransportUpdateView, self).get_context_data(
|
||||
**kwargs)
|
||||
context.update({
|
||||
"action": reverse(
|
||||
"transport:transport_update", args=[self.object.pk]),
|
||||
"action_label": _("Update"),
|
||||
"title": _("Edit transport"),
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
class TransportDeleteView(
|
||||
auth_mixins.PermissionRequiredMixin,
|
||||
TransportMixin,
|
||||
generic.DeleteView):
|
||||
"""Transport delete view."""
|
||||
|
||||
model = models.Transport
|
||||
permission_required = "transport.del_transport"
|
||||
success_url = reverse_lazy("transport:transport_list")
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
"""Add message."""
|
||||
response = super(TransportDeleteView, self).delete(
|
||||
request, *args, **kwargs)
|
||||
messages.success(request, _("Transport deleted"))
|
||||
return response
|
||||
@@ -1,18 +0,0 @@
|
||||
"""Transport viewsets."""
|
||||
|
||||
from rest_framework import viewsets
|
||||
from rest_framework.permissions import IsAuthenticated, DjangoModelPermissions
|
||||
|
||||
from . import models
|
||||
from . import serializers
|
||||
|
||||
|
||||
class TransportViewSet(viewsets.ModelViewSet):
|
||||
"""Transport viewset."""
|
||||
|
||||
permission_classes = [IsAuthenticated, DjangoModelPermissions, ]
|
||||
serializer_class = serializers.TransportSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
"""Filter queryset based on current user."""
|
||||
return models.Transport.objects.get_for_admin(self.request.user)
|
||||
@@ -28,8 +28,6 @@ urlpatterns = [
|
||||
url('', include('modoboa.core.urls', namespace="core")),
|
||||
url('^user/forward/', forward, name="user_forward"),
|
||||
url('admin/', include('modoboa.admin.urls', namespace="admin")),
|
||||
url(r'^transports/',
|
||||
include("modoboa.transport.urls", namespace="transport")),
|
||||
# No namespace
|
||||
url(r'^accounts/password_reset/$', core_views.password_reset,
|
||||
name="password_reset"),
|
||||
|
||||
+1
-1
@@ -10,6 +10,6 @@ from modoboa.core.extensions import exts_pool
|
||||
urlpatterns = [
|
||||
url("", include("modoboa.admin.urls_api")),
|
||||
url("", include("modoboa.limits.urls_api")),
|
||||
url("", include("modoboa.transport.urls_api")),
|
||||
url("", include("modoboa.relaydomains.urls_api")),
|
||||
]
|
||||
urlpatterns += exts_pool.get_urls(category="api")
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ django-ckeditor==5.2.2
|
||||
django-reversion==2.0.12
|
||||
django-subcommand2==0.1.1
|
||||
django-versionfield2>=0.5.0
|
||||
django-xforwardedfor-middleware
|
||||
django-xforwardedfor-middleware==2.0
|
||||
|
||||
dj-database-url
|
||||
|
||||
|
||||
Reference in New Issue
Block a user