@@ -121,12 +150,14 @@ import { bus } from '@/main'
import DomainDNSForm from './DomainDNSForm'
import DomainGeneralForm from './DomainGeneralForm'
import DomainLimitationsForm from './DomainLimitationsForm'
+import DomainTransportForm from './DomainTransportForm'
export default {
components: {
'domain-dns-form': DomainDNSForm,
DomainGeneralForm,
- DomainLimitationsForm
+ DomainLimitationsForm,
+ DomainTransportForm
},
props: ['domain'],
data () {
@@ -143,6 +174,9 @@ export default {
return
}
}
+ if (this.editedDomain.type === 'relaydomain') {
+ this.$refs.transportForm.checkSettingTypes(this.editedDomain)
+ }
this.$store.dispatch('domains/updateDomain', this.editedDomain).then(resp => {
bus.$emit('notification', { msg: this.$gettext('Domain updated') })
})
@@ -156,6 +190,11 @@ export default {
}
},
immediate: true
+ },
+ 'editedDomain.type' (val) {
+ if (val === 'relaydomain' && this.editedDomain.transport === null) {
+ this.$set(this.editedDomain, 'transport', {})
+ }
}
}
}
diff --git a/frontend/src/components/domains/DomainList.vue b/frontend/src/components/domains/DomainList.vue
index 8890ecaf4..be5958cbd 100644
--- a/frontend/src/components/domains/DomainList.vue
+++ b/frontend/src/components/domains/DomainList.vue
@@ -42,6 +42,14 @@
{{ item.name }}
+
+ Relay
+
(disabled)
diff --git a/frontend/src/components/domains/DomainTransportForm.vue b/frontend/src/components/domains/DomainTransportForm.vue
new file mode 100644
index 000000000..7557996ac
--- /dev/null
+++ b/frontend/src/components/domains/DomainTransportForm.vue
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/domains/TransportForm.vue b/frontend/src/components/domains/TransportForm.vue
new file mode 100644
index 000000000..e69de29bb
diff --git a/modoboa/admin/api/v2/serializers.py b/modoboa/admin/api/v2/serializers.py
index 9c2d77829..3e15e5151 100644
--- a/modoboa/admin/api/v2/serializers.py
+++ b/modoboa/admin/api/v2/serializers.py
@@ -18,6 +18,8 @@ from modoboa.lib import fields as lib_fields
from modoboa.lib import validators, web_utils
from modoboa.lib.sysutils import exec_cmd
from modoboa.parameters import tools as param_tools
+from modoboa.transport import models as transport_models
+from modoboa.transport.api.v2 import serializers as transport_serializers
from ... import constants, models
@@ -34,17 +36,33 @@ class CreateDomainAdminSerializer(serializers.Serializer):
class DomainSerializer(v1_serializers.DomainSerializer):
"""Domain serializer for v2 API."""
- domain_admin = CreateDomainAdminSerializer(required=False)
+ domain_admin = CreateDomainAdminSerializer(required=False, write_only=True)
+ transport = transport_serializers.TransportSerializer(required=False)
class Meta(v1_serializers.DomainSerializer.Meta):
fields = v1_serializers.DomainSerializer.Meta.fields + (
- "domain_admin",
+ "domain_admin", "transport"
)
+ def validate(self, data):
+ result = super().validate(data)
+ dtype = data.get("type", "domain")
+ if dtype == "relaydomain" and not data.get("transport"):
+ raise serializers.ValidationError({"transport": _("This field is required")})
+ return result
+
def create(self, validated_data):
"""Create administrator and other stuff if needed."""
domain_admin = validated_data.pop("domain_admin", None)
+ transport_def = validated_data.pop("transport", None)
domain = super().create(validated_data)
+ if transport_def:
+ transport = transport_models.Transport(**transport_def)
+ transport.pattern = domain.name
+ transport.save()
+ domain.transport = transport
+ domain.save()
+
if not domain_admin:
return domain
@@ -99,6 +117,27 @@ class DomainSerializer(v1_serializers.DomainSerializer):
domain.add_admin(da)
return domain
+ def update(self, instance, validated_data):
+ """Update domain and create/update transport."""
+ transport_def = validated_data.pop("transport", None)
+ super().update(instance, validated_data)
+ if transport_def and instance.type == "relaydomain":
+ transport = getattr(instance, "transport", None)
+ created = False
+ if transport:
+ for key, value in transport_def.items():
+ setattr(instance, key, value)
+ transport._settings = transport_def["_settings"]
+ else:
+ transport = transport_models.Transport(**transport_def)
+ created = True
+ transport.pattern = instance.name
+ transport.save()
+ if created:
+ instance.transport = transport
+ instance.save()
+ return instance
+
class DeleteDomainSerializer(serializers.Serializer):
"""Serializer used with delete operation."""
diff --git a/modoboa/admin/api/v2/tests.py b/modoboa/admin/api/v2/tests.py
index 09b83a756..82c87bdd7 100644
--- a/modoboa/admin/api/v2/tests.py
+++ b/modoboa/admin/api/v2/tests.py
@@ -41,6 +41,33 @@ class DomainViewSetTestCase(ModoAPITestCase):
address="postmaster@domain.tld").exists()
)
+ def test_update(self):
+ domain = models.Domain.objects.get(name="test2.com")
+ data = {
+ "name": "test2.com",
+ "type": "relaydomain",
+ "transport": {
+ "service": "relay",
+ "settings": {
+ "relay_target_port": 25,
+ "relay_target_host": "localhost"
+ }
+ }
+ }
+ url = reverse("v2:domain-detail", args=[domain.pk])
+ resp = self.client.put(url, data, format="json")
+ self.assertEqual(resp.status_code, 200)
+ domain.refresh_from_db()
+ self.assertEqual(domain.transport.service, data["transport"]["service"])
+ self.assertEqual(
+ domain.transport._settings["relay_target_host"],
+ data["transport"]["settings"]["relay_target_host"]
+ )
+
+ data["transport"]["relay_verify_recipients"] = True
+ resp = self.client.put(url, data, format="json")
+ self.assertEqual(resp.status_code, 200)
+
def test_delete(self):
self.client.credentials(
HTTP_AUTHORIZATION="Token " + self.da_token.key)
diff --git a/modoboa/admin/api/v2/viewsets.py b/modoboa/admin/api/v2/viewsets.py
index bacdcf5b5..7527d7d27 100644
--- a/modoboa/admin/api/v2/viewsets.py
+++ b/modoboa/admin/api/v2/viewsets.py
@@ -58,15 +58,13 @@ class DomainViewSet(lib_viewsets.RevisionModelMixin,
return models.Domain.objects.get_for_admin(self.request.user)
def get_serializer_class(self, *args, **kwargs):
- if self.action == "create":
- return serializers.DomainSerializer
if self.action == "delete":
return serializers.DeleteDomainSerializer
if self.action == "administrators":
return serializers.DomainAdminSerializer
if self.action in ["add_administrator", "remove_administrator"]:
return serializers.SimpleDomainAdminSerializer
- return v1_serializers.DomainSerializer
+ return serializers.DomainSerializer
@action(methods=["post"], detail=True)
def delete(self, request, **kwargs):
diff --git a/modoboa/transport/api/__init__.py b/modoboa/transport/api/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/modoboa/transport/api/v2/__init__.py b/modoboa/transport/api/v2/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/modoboa/transport/api/v2/serializers.py b/modoboa/transport/api/v2/serializers.py
new file mode 100644
index 000000000..7ce8aa984
--- /dev/null
+++ b/modoboa/transport/api/v2/serializers.py
@@ -0,0 +1,42 @@
+"""Transport related serializers."""
+
+from rest_framework import serializers
+
+from ... import backends
+from ... import models
+
+
+class TransportBackendSettingSerializer(serializers.Serializer):
+ """Serializer for transport backend setting."""
+
+ name = serializers.CharField()
+ label = serializers.CharField()
+ type = serializers.CharField(default="str")
+ required = serializers.BooleanField(default=True)
+ default = serializers.CharField(default=None)
+
+
+class TransportBackendSerializer(serializers.Serializer):
+ """Serializer for transport backend."""
+
+ name = serializers.CharField()
+ settings = TransportBackendSettingSerializer(many=True)
+
+
+class TransportSerializer(serializers.ModelSerializer):
+ """Serializer for Transport model."""
+
+ settings = serializers.DictField(source="_settings")
+
+ class Meta:
+ fields = ("service", "settings")
+ model = models.Transport
+
+ def validate(self, data):
+ self.backend = backends.manager.get_backend(data["service"])
+ errors = {}
+ for field, error in self.backend.clean_fields(data["_settings"]):
+ errors[field] = error
+ if errors:
+ raise serializers.ValidationError(errors)
+ return data
diff --git a/modoboa/transport/api/v2/tests.py b/modoboa/transport/api/v2/tests.py
new file mode 100644
index 000000000..790ba2ea6
--- /dev/null
+++ b/modoboa/transport/api/v2/tests.py
@@ -0,0 +1,16 @@
+"""API v2 tests."""
+
+from django.urls import reverse
+
+from modoboa.lib.tests import ModoAPITestCase
+
+
+class TransportViewSetTestCase(ModoAPITestCase):
+
+ def test_list(self):
+ url = reverse("v2:transport-list")
+ resp = self.client.get(url)
+ self.assertEqual(resp.status_code, 200)
+ backends = resp.json()
+ self.assertEqual(len(backends), 1)
+ self.assertEqual(backends[0]["name"], "relay")
diff --git a/modoboa/transport/api/v2/urls.py b/modoboa/transport/api/v2/urls.py
new file mode 100644
index 000000000..816410035
--- /dev/null
+++ b/modoboa/transport/api/v2/urls.py
@@ -0,0 +1,12 @@
+"""App related urls."""
+
+from rest_framework import routers
+
+from . import viewsets
+
+
+router = routers.SimpleRouter()
+router.register(
+ r"transports", viewsets.TransportViewSet, basename="transport")
+
+urlpatterns = router.urls
diff --git a/modoboa/transport/api/v2/viewsets.py b/modoboa/transport/api/v2/viewsets.py
new file mode 100644
index 000000000..28d2b005f
--- /dev/null
+++ b/modoboa/transport/api/v2/viewsets.py
@@ -0,0 +1,27 @@
+"""Transport related viewsets."""
+
+from drf_spectacular.utils import extend_schema
+from rest_framework import permissions, response, viewsets
+
+from . import serializers
+from ... import backends
+
+
+class TransportViewSet(viewsets.ViewSet):
+ """Viewset for Transport."""
+
+ permissions = (permissions.IsAuthenticated, )
+
+ @extend_schema(
+ responses={200: serializers.TransportBackendSerializer}
+ )
+ def list(self, request, *args, **kwargs):
+ """Return the list of all defined transport backends."""
+ all_settings = backends.manager.get_all_backend_settings()
+ result = []
+ for name, settings in all_settings.items():
+ serializer = serializers.TransportBackendSerializer(
+ {"name": name, "settings": settings}
+ )
+ result.append(serializer.data)
+ return response.Response(result)
diff --git a/modoboa/urls_api_v2.py b/modoboa/urls_api_v2.py
index 665cd5155..b6376b088 100644
--- a/modoboa/urls_api_v2.py
+++ b/modoboa/urls_api_v2.py
@@ -14,6 +14,7 @@ urlpatterns = [
path('', include("modoboa.relaydomains.api.v1.urls")),
path('', include("modoboa.dnstools.api.v2.urls")),
path('', include("modoboa.maillog.api.v2.urls")),
+ path('', include("modoboa.transport.api.v2.urls")),
]
urlpatterns += exts_pool.get_urls(category="api")
|