Merge pull request #1108 from modoboa/feature/limits_api

New resources endpoints.
This commit is contained in:
Antoine Nguyen
2017-04-24 14:04:12 +02:00
committed by GitHub
7 changed files with 195 additions and 30 deletions
+4 -29
View File
@@ -7,7 +7,7 @@ from django.utils.translation import ugettext as _
from modoboa.lib.permissions import get_object_owner
from .lib import BadLimitValue, UnsufficientResource
from .lib import allocate_resources_from_user
from . import utils
@@ -48,32 +48,6 @@ class ResourcePoolForm(forms.Form):
self.fields[fieldname].initial = (
user.userobjectlimit_set.get(name=lname).max_value)
def allocate_from_user(self, limit, user):
"""Allocate resource using an existing user.
When a reseller creates a domain administrator, he generally
assigns him resource to create new objetcs. As a reseller may
also be limited, the resource he gives is taken from its own
pool.
"""
ol = user.userobjectlimit_set.get(name=limit.name)
fieldname = "{}_limit".format(limit.name)
newvalue = self.cleaned_data[fieldname]
if newvalue == -1 and ol.max_value != -1:
raise BadLimitValue(
_("You're not allowed to define unlimited values")
)
if limit.max_value > -1:
newvalue -= limit.max_value
if newvalue == 0:
return
remain = ol.max_value - ol.current_value
if newvalue > remain:
raise UnsufficientResource(ol)
ol.max_value -= newvalue
ol.save()
def save(self):
owner = get_object_owner(self.account)
for name, ltpl in utils.get_user_limit_templates():
@@ -82,9 +56,10 @@ class ResourcePoolForm(forms.Form):
continue
l = self.account.userobjectlimit_set.get(name=name)
if not owner.is_superuser:
self.allocate_from_user(l, owner)
allocate_resources_from_user(
l, owner, self.cleaned_data[fieldname])
l.max_value = self.cleaned_data[fieldname]
l.save()
l.save(update_fields=["max_value"])
class DomainLimitsForm(forms.Form):
+25
View File
@@ -31,3 +31,28 @@ class UnsufficientResource(ModoboaException):
class BadLimitValue(ModoboaException):
http_code = 400
def allocate_resources_from_user(limit, user, value):
"""Allocate resource using an existing user.
When a reseller creates a domain administrator, he generally
assigns him resource to create new objetcs. As a reseller may
also be limited, the resource he gives is taken from its own
pool.
"""
ol = user.userobjectlimit_set.get(name=limit.name)
if value == -1 and ol.max_value != -1:
raise BadLimitValue(
_("You're not allowed to define unlimited values")
)
if limit.max_value > -1:
value -= limit.max_value
if value == 0:
return
remain = ol.max_value - ol.current_value
if value > remain:
raise UnsufficientResource(ol)
ol.max_value -= value
ol.save()
+33
View File
@@ -0,0 +1,33 @@
"""Limits serializers."""
from rest_framework import serializers
from . import lib
class ResourcesSerializer(serializers.Serializer):
"""Resources serializer."""
domains = serializers.IntegerField(min_value=-1)
domain_aliases = serializers.IntegerField(min_value=-1)
mailboxes = serializers.IntegerField(min_value=-1)
mailbox_aliases = serializers.IntegerField(min_value=-1)
domain_admins = serializers.IntegerField(min_value=-1)
quota = serializers.IntegerField(min_value=0)
def to_representation(self, instance):
"""Return limits."""
return dict(
(limit.name, limit.max_value)
for limit in instance.userobjectlimit_set.all())
def update(self, instance, validated_data):
"""Update limits."""
user = self.context["request"].user
for name, max_value in validated_data.items():
limit = instance.userobjectlimit_set.get(name=name)
if not user.is_superuser:
lib.allocate_resources_from_user(limit, user, max_value)
limit.max_value = max_value
limit.save(update_fields=["max_value"])
return instance
+88
View File
@@ -1,6 +1,8 @@
# coding: utf-8
"""Test cases for the limits extension."""
from testfixtures import compare
from django.core.urlresolvers import reverse
from rest_framework.authtoken.models import Token
@@ -9,6 +11,7 @@ from modoboa.admin.factories import populate_database
from modoboa.admin.models import Domain
from modoboa.core import factories as core_factories
from modoboa.core.models import User
from modoboa.lib import permissions
from modoboa.lib import tests as lib_tests
from .. import utils
@@ -253,3 +256,88 @@ class APIDomainLimitsTestCase(lib_tests.ModoAPITestCase):
}
response = self.client.post(url, data, format="json")
self.assertEqual(response.status_code, 400)
class ResourcesAPITestCase(lib_tests.ModoAPITestCase):
"""Check resources API."""
@classmethod
def setUpTestData(cls):
"""Create test data."""
super(ResourcesAPITestCase, cls).setUpTestData()
for name, tpl in utils.get_user_limit_templates():
cls.localconfig.parameters.set_value(
"deflt_user_{0}_limit".format(name), 2)
cls.localconfig.save()
populate_database()
cls.user = User.objects.get(username="admin@test.com")
cls.da_token = Token.objects.create(user=cls.user)
cls.reseller = core_factories.UserFactory(
username="reseller", groups=("Resellers", ),
)
cls.r_token = Token.objects.create(user=cls.reseller)
def test_get_admin_resources(self):
"""Retrieve admin resources."""
url = reverse("api:resources-detail", args=[self.user.pk])
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
expected = {
"quota": 2,
"mailboxes": 2,
"domain_admins": 2,
"domain_aliases": 2,
"domains": 2,
"mailbox_aliases": 2
}
compare(expected, response.data)
# As reseller => fails
self.client.credentials(
HTTP_AUTHORIZATION="Token {}".format(self.r_token.key))
response = self.client.get(url)
self.assertEqual(response.status_code, 404)
# As domain admin => fails
self.client.credentials(
HTTP_AUTHORIZATION="Token {}".format(self.da_token.key))
response = self.client.get(url)
self.assertEqual(response.status_code, 404)
def test_update_resources(self):
"""Update resources."""
url = reverse("api:resources-detail", args=[self.reseller.pk])
response = self.client.get(url)
resources = response.data
resources.update({"domains": 1000, "mailboxes": 1000})
response = self.client.put(url, resources)
self.assertEqual(response.status_code, 200)
self.assertEqual(
self.reseller.userobjectlimit_set.get(name="domains").max_value,
1000)
# As domain admin => fails
self.client.credentials(
HTTP_AUTHORIZATION="Token {}".format(self.da_token.key))
resources.update({"domains": 2, "mailboxes": 2})
url = reverse("api:resources-detail", args=[self.user.pk])
response = self.client.put(url, resources)
self.assertEqual(response.status_code, 404)
# As reseller => ok
permissions.grant_access_to_object(self.reseller, self.user, True)
self.client.credentials(
HTTP_AUTHORIZATION="Token {}".format(self.r_token.key))
resources.update({"domains": 500, "mailboxes": 500})
url = reverse("api:resources-detail", args=[self.user.pk])
response = self.client.put(url, resources)
self.assertEqual(response.status_code, 200)
self.assertEqual(
self.user.userobjectlimit_set.get(name="domains").max_value,
500)
self.assertEqual(
self.reseller.userobjectlimit_set.get(name="domains").max_value,
502)
resources.update({"domains": 1003})
response = self.client.put(url, resources)
self.assertEqual(response.status_code, 424)
+12
View File
@@ -0,0 +1,12 @@
"""Limits API urls."""
from rest_framework import routers
from . import viewsets
router = routers.SimpleRouter()
router.register(
r"resources", viewsets.ResourcesViewSet, base_name="resources")
urlpatterns = router.urls
+31
View File
@@ -0,0 +1,31 @@
"""Limits API."""
from django.contrib.contenttypes.models import ContentType
from rest_framework import mixins, viewsets
from rest_framework.permissions import IsAuthenticated, DjangoModelPermissions
from modoboa.core import models as core_models
from . import serializers
class ResourcesViewSet(
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
viewsets.GenericViewSet):
"""Resources viewset."""
permission_classes = [IsAuthenticated, DjangoModelPermissions]
serializer_class = serializers.ResourcesSerializer
def get_queryset(self):
"""Filter queryset based on current user."""
user = self.request.user
ids = list(
user.objectaccess_set.filter(
content_type=ContentType.objects.get_for_model(user))
.values_list("object_id", flat=True))
if not user.is_superuser and user.pk in ids:
ids.remove(user.pk)
return core_models.User.objects.filter(pk__in=ids)
+2 -1
View File
@@ -6,6 +6,7 @@ from modoboa.core.extensions import exts_pool
urlpatterns = [
url("", include("modoboa.admin.urls_api"))
url("", include("modoboa.admin.urls_api")),
url("", include("modoboa.limits.urls_api")),
]
urlpatterns += exts_pool.get_urls(category="api")