Merge pull request #833 from tonioo/feature/domain_api_831

Feature/domain api 831
This commit is contained in:
Antoine Nguyen
2016-02-04 08:48:37 +01:00
12 changed files with 203 additions and 46 deletions
+1
View File
@@ -9,6 +9,7 @@ before_install:
install:
- python setup.py -q develop
- pip install -q psycopg2
- pip install -q -r test-requirements.txt
before_script:
- psql -c 'create database modoboa_test;' -U postgres
+22
View File
@@ -0,0 +1,22 @@
"""Admin API."""
from rest_framework import viewsets
from rest_framework.permissions import DjangoModelPermissions
from . import models
from . import serializers
class DomainViewSet(viewsets.ModelViewSet):
"""ViewSet for Domain."""
permission_classes = [DjangoModelPermissions, ]
serializer_class = serializers.DomainSerializer
def get_queryset(self):
"""Filter queryset based on current user."""
return models.Domain.objects.get_for_admin(self.request.user)
def perform_destroy(self, instance):
"""Add custom args to delete call."""
instance.delete(self.request.user)
+32 -42
View File
@@ -22,7 +22,6 @@ from ..models import (
DOMAIN_TYPES = [
("domain", _("Domain")),
]
DOMAIN_TYPES += events.raiseQueryEvent("ExtraDomainTypes")
class DomainFormGeneral(forms.ModelForm, DynamicForm):
@@ -30,7 +29,7 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
"""A form to create/edit a domain."""
type = forms.ChoiceField(
label=ugettext_lazy("Type"), choices=DOMAIN_TYPES
label=ugettext_lazy("Type"),
)
quota = forms.IntegerField(
label=ugettext_lazy("Quota"),
@@ -59,6 +58,9 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
self.oldname = kwargs["instance"].name
super(DomainFormGeneral, self).__init__(*args, **kwargs)
self.fields["type"].choices = (
DOMAIN_TYPES + events.raiseQueryEvent("ExtraDomainTypes"))
self.field_widths = {
"quota": 3
}
@@ -142,7 +144,7 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
q.delete()
def save(self, user, commit=True, domalias_post_create=False):
"""Custom save method
"""Custom save method.
Updating a domain may have consequences on other objects
(domain alias, mailbox, quota). The most tricky part concerns
@@ -150,46 +152,34 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
"""
d = super(DomainFormGeneral, self).save(commit=False)
if commit:
old_mail_homes = None
if self.oldname is not None and d.name != self.oldname:
d.name = self.oldname
old_mail_homes = \
dict((mb.id, mb.mail_home) for mb in d.mailbox_set.all())
d.name = self.cleaned_data['name']
d.save()
Mailbox.objects.filter(domain=d, use_domain_quota=True) \
.update(quota=d.quota)
aliases = []
for k, v in self.cleaned_data.iteritems():
if not k.startswith("aliases"):
continue
if v in ["", None]:
continue
aliases.append(v)
for dalias in d.domainalias_set.all():
if dalias.name not in aliases:
dalias.delete()
if not commit:
return d
d.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 dalias in d.domainalias_set.all():
if dalias.name not in aliases:
dalias.delete()
else:
aliases.remove(dalias.name)
if aliases:
events.raiseEvent(
"CanCreate", user, "domain_aliases", len(aliases))
for alias in aliases:
try:
d.domainalias_set.get(name=alias)
except DomainAlias.DoesNotExist:
pass
else:
aliases.remove(dalias.name)
if aliases:
events.raiseEvent(
"CanCreate", user, "domain_aliases", len(aliases))
for alias in aliases:
try:
d.domainalias_set.get(name=alias)
except DomainAlias.DoesNotExist:
pass
else:
continue
al = DomainAlias(name=alias, target=d, enabled=d.enabled)
al.save(creator=user) if domalias_post_create else al.save()
if old_mail_homes is not None:
self.update_mailbox_quotas(d)
for mb in d.mailbox_set.all():
mb.rename_dir(old_mail_homes[mb.id])
continue
options = {"creator": user} if domalias_post_create else {}
DomainAlias(name=alias, target=d, enabled=d.enabled).save(
**options)
return d
+23
View File
@@ -6,6 +6,29 @@ from django.dispatch import receiver
from . import models
@receiver(signals.post_save, sender=models.Domain)
def update_domain_mailboxes(sender, instance, **kwargs):
"""Update associated mailboxes."""
if kwargs.get("created"):
return
instance.mailbox_set.filter(use_domain_quota=True).update(
quota=instance.quota)
if instance.old_mail_homes is None:
return
qset = (
models.Quota.objects.filter(
username__contains="@{}".format(instance.oldname))
)
for q in qset:
username = q.username.replace(
"@{}".format(instance.oldname), "@{}".format(instance.name))
models.Quota.objects.create(
username=username, bytes=q.bytes, messages=q.messages)
q.delete()
for mb in instance.mailbox_set.all():
mb.rename_dir(instance.old_mail_homes[mb.pk])
@receiver(signals.post_save, sender=models.DomainAlias)
def create_alias_for_domainalias(sender, instance, **kwargs):
"""Create a dedicated alias for domain alias."""
+15 -3
View File
@@ -29,7 +29,6 @@ class DomainManager(Manager):
@python_2_unicode_compatible
class Domain(AdminObject):
"""Mail domain."""
name = models.CharField(ugettext_lazy('name'), max_length=100, unique=True,
@@ -53,6 +52,12 @@ class Domain(AdminObject):
ordering = ["name"]
app_label = "admin"
def __init__(self, *args, **kwargs):
"""Save name for further use."""
super(Domain, self).__init__(*args, **kwargs)
self.old_mail_homes = None
self.oldname = self.name
@property
def domainalias_count(self):
return self.domainalias_set.count()
@@ -124,9 +129,16 @@ class Domain(AdminObject):
for al in self.alias_set.all():
ungrant_access_to_object(al, account)
def save(self, *args, **kwargs):
"""Store current data if domain is renamed."""
if self.oldname != self.name:
self.old_mail_homes = (
dict((mb.id, mb.mail_home) for mb in self.mailbox_set.all())
)
super(Domain, self).save(*args, **kwargs)
def delete(self, fromuser, keepdir=False):
"""Custom delete method.
"""
"""Custom delete method."""
from modoboa.lib.permissions import ungrant_access_to_objects
from .mailbox import Quota
+14
View File
@@ -0,0 +1,14 @@
"""Admin serializers."""
from rest_framework import serializers
from . import models
class DomainSerializer(serializers.ModelSerializer):
"""Base Domain serializer."""
class Meta:
model = models.Domain
fields = ("pk", "name", "quota", "enabled", "type", )
+2
View File
@@ -2,6 +2,7 @@
from .account import AccountTestCase, PermissionsTestCase
from .alias import AliasTestCase
from .api import APITestCase
from .domain import DomainTestCase
from .domain_alias import DomainAliasTestCase
from .export import ExportTestCase
@@ -13,6 +14,7 @@ from .user import ForwardTestCase
__all__ = [
'AccountTestCase',
'AliasTestCase',
'APITestCase',
'DomainTestCase',
'DomainAliasTestCase',
'ExportTestCase',
+79
View File
@@ -0,0 +1,79 @@
"""Admin API related tests."""
from django.core.urlresolvers import reverse
from rest_framework.authtoken.models import Token
from modoboa.core import models as core_models
from modoboa.lib.tests import ModoAPITestCase
from .. import factories
from .. import models
class APITestCase(ModoAPITestCase):
"""Check API."""
@classmethod
def setUpTestData(cls):
"""Create test data."""
super(APITestCase, cls).setUpTestData()
factories.populate_database()
cls.da_token = Token.objects.create(
user=core_models.User.objects.get(username="admin@test.com"))
def test_get_domains(self):
"""Retrieve a list of domains."""
url = reverse("external_api:domain-list")
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data), 2)
url = reverse(
"external_api:domain-detail", args=[response.data[0]["pk"]])
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data["name"], "test2.com")
def test_create_domain(self):
"""Check domain creation."""
url = reverse("external_api:domain-list")
response = self.client.post(url, {"name": "test3.com", "quota": 10})
self.assertEqual(response.status_code, 201)
self.assertTrue(
models.Domain.objects.filter(name="test3.com").exists())
response = self.client.post(url, {})
self.assertEqual(response.status_code, 400)
self.assertIn("name", response.data)
self.assertIn("quota", response.data)
self.client.credentials(
HTTP_AUTHORIZATION='Token ' + self.da_token.key)
response = self.client.post(url, {"name": "test4.com", "quota": 10})
self.assertEqual(response.status_code, 403)
def test_update_domain(self):
"""Check domain update."""
domain = models.Domain.objects.get(name="test.com")
models.Mailbox.objects.filter(
domain__name="test.com", address="user").update(
use_domain_quota=True)
url = reverse("external_api:domain-detail", args=[domain.pk])
response = self.client.put(url, {"name": "test.com", "quota": 1000})
self.assertEqual(response.status_code, 200)
domain.refresh_from_db()
self.assertEqual(domain.quota, 1000)
mb = models.Mailbox.objects.get(
domain__name="test.com", address="user")
self.assertEqual(mb.quota, 1000)
response = self.client.put(url, {"name": "test42.com", "quota": 1000})
self.assertEqual(response.status_code, 200)
def test_delete_domain(self):
"""Try to delete a domain."""
domain = models.Domain.objects.get(name="test.com")
url = reverse("external_api:domain-detail", args=[domain.pk])
response = self.client.delete(url)
self.assertEqual(response.status_code, 204)
self.assertFalse(models.Domain.objects.filter(pk=domain.pk).exists())
+11
View File
@@ -0,0 +1,11 @@
"""Admin API urls."""
from rest_framework import routers
from . import api
router = routers.SimpleRouter()
router.register(r"domains", api.DomainViewSet, base_name="domain")
urlpatterns = router.urls
+1
View File
@@ -7,4 +7,5 @@ urlpatterns = patterns(
"",
("", include("modoboa.core.urls_api")),
("", include("modoboa.admin.urls_api")),
)
-1
View File
@@ -3,7 +3,6 @@ django-reversion==1.9.3
dj-database-url
pycrypto
argparse
factory-boy>=2.4
passlib
django-versionfield2>=0.3.3
requests
+3
View File
@@ -0,0 +1,3 @@
factory-boy>=2.4
testfixtures==4.7.0