mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
Merge pull request #1108 from modoboa/feature/limits_api
New resources endpoints.
This commit is contained in:
+4
-29
@@ -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):
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user