see #799
This commit is contained in:
Antoine Nguyen
2016-04-16 15:27:33 +02:00
parent cdc61618a8
commit 49e5362f41
21 changed files with 375 additions and 101 deletions
+17 -10
View File
@@ -13,7 +13,7 @@ from modoboa.core.models import User
from modoboa.core import signals as core_signals
from modoboa.lib import events, parameters
from modoboa.lib.email_utils import split_mailbox
from modoboa.lib.exceptions import PermDeniedException
from modoboa.lib import exceptions as lib_exceptions
from modoboa.lib import fields as lib_fields
from modoboa.lib.form_utils import (
DynamicForm, TabForms, WizardForm, WizardStep
@@ -161,7 +161,8 @@ class AccountFormGeneral(forms.ModelForm):
def save(self, commit=True):
account = super(AccountFormGeneral, self).save(commit=False)
if self.user == account and not self.cleaned_data["is_active"]:
raise PermDeniedException(_("You can't disable your own account"))
raise lib_exceptions.PermDeniedException(
_("You can't disable your own account"))
if commit:
if "password1" in self.cleaned_data \
and self.cleaned_data["password1"] != "":
@@ -198,11 +199,7 @@ class AccountFormMail(forms.Form, DynamicForm):
)
def __init__(self, *args, **kwargs):
if "instance" in kwargs:
self.mb = kwargs["instance"]
del kwargs["instance"]
else:
self.mb = None
self.mb = kwargs.pop("instance", None)
super(AccountFormMail, self).__init__(*args, **kwargs)
self.field_widths = {
"quota": 3
@@ -245,6 +242,13 @@ class AccountFormMail(forms.Form, DynamicForm):
self.domain = Domain.objects.get(name=domname)
except Domain.DoesNotExist:
raise forms.ValidationError(_("Domain does not exist"))
if not self.mb:
try:
core_signals.can_create_object.send(
sender=self.__class__, context=self.domain,
object_type="mailboxes")
except lib_exceptions.ModoboaException as inst:
raise forms.ValidationError(inst)
return email
def clean(self):
@@ -262,9 +266,9 @@ class AccountFormMail(forms.Form, DynamicForm):
def create_mailbox(self, user, account):
"""Create a mailbox associated to :kw:`account`."""
if not user.can_access(self.domain):
raise PermDeniedException
raise lib_exceptions.PermDeniedException
core_signals.can_create_object.send(
self.__class__, user=user, object_type="mailboxes")
self.__class__, context=user, object_type="mailboxes")
self.mb = Mailbox(
address=self.locpart, domain=self.domain, user=account,
use_domain_quota=self.cleaned_data["quota_act"])
@@ -296,8 +300,11 @@ class AccountFormMail(forms.Form, DynamicForm):
if not aliases:
return
core_signals.can_create_object.send(
self.__class__, user=user, object_type="mailbox_aliases",
self.__class__, context=user, object_type="mailbox_aliases",
count=len(aliases))
core_signals.can_create_object.send(
self.__class__, context=self.mb.domain,
object_type="mailbox_aliases", count=len(aliases))
for alias in aliases:
if self.mb.aliasrecipient_set.select_related("alias").filter(
alias__address=alias).exists():
+9
View File
@@ -6,6 +6,8 @@ from django import forms
from django.http import QueryDict
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.core import signals as core_signals
from modoboa.lib import exceptions as lib_exceptions
from modoboa.lib.email_utils import split_mailbox
from modoboa.lib import fields as lib_fields
from modoboa.lib.form_utils import (
@@ -74,6 +76,13 @@ class AliasForm(forms.ModelForm, DynamicForm):
raise forms.ValidationError(
_("You don't have access to this domain")
)
if self.instance.pk:
try:
core_signals.can_create_object.send(
sender=self.__class__, context=domain,
object_type="mailbox_aliases")
except lib_exceptions.ModoboaException as inst:
raise forms.ValidationError(inst)
return self.cleaned_data["address"].lower()
def clean(self):
+15 -21
View File
@@ -101,6 +101,7 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
"name", _("A %s with this name already exists")
% unicode(label)
)
self.aliases = []
for k in cleaned_data.keys():
if not k.startswith("aliases"):
continue
@@ -120,6 +121,7 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
k, _("A %s with this name already exists")
% unicode(label)
)
self.aliases.append(cleaned_data[k])
return cleaned_data
def save(self, user, commit=True, domalias_post_create=False):
@@ -134,28 +136,20 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
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:
if dalias.name not in self.aliases:
dalias.delete()
else:
aliases.remove(dalias.name)
if aliases:
self.aliases.remove(dalias.name)
if self.aliases:
core_signals.can_create_object.send(
self.__class__, user=user, object_type="domain_aliases",
count=len(aliases))
for alias in aliases:
try:
d.domainalias_set.get(name=alias)
except DomainAlias.DoesNotExist:
pass
else:
self.__class__, context=user, object_type="domain_aliases",
count=len(self.aliases))
core_signals.can_create_object.send(
self.__class__, context=d, object_type="domain_aliases",
count=len(self.aliases))
for alias in self.aliases:
if d.domainalias_set.filter(name=alias).exists():
continue
options = {"creator": user} if domalias_post_create else {}
DomainAlias(name=alias, target=d, enabled=d.enabled).save(
@@ -225,9 +219,9 @@ class DomainFormOptions(forms.Form):
except User.DoesNotExist:
pass
else:
raise Conflict(_("User '%s' already exists" % username))
raise Conflict(_("User '%s' already exists") % username)
core_signals.can_create_object.send(
self.__class__, user=user, object_type="mailboxes")
self.__class__, context=user, object_type="mailboxes")
da = User(username=username, email=username, is_active=True)
da.set_password("password")
da.save()
@@ -245,7 +239,7 @@ class DomainFormOptions(forms.Form):
if domain.type == "domain" and \
self.cleaned_data["create_aliases"] == "yes":
core_signals.can_create_object.send(
self.__class__, user=user, object_type="mailbox_aliases")
self.__class__, context=user, object_type="mailbox_aliases")
alias = Alias(
address=u"postmaster@{}".format(domain.name),
domain=domain, enabled=True
+1
View File
@@ -101,6 +101,7 @@ class Domain(AdminObject):
:param User account: the administrotor to add
"""
from modoboa.lib.permissions import grant_access_to_object
grant_access_to_object(account, self)
for mb in self.mailbox_set.all():
if mb.user.has_perm("admin.add_domain"):
+2 -2
View File
@@ -161,9 +161,9 @@ class PermissionsTestCase(ModoTestCase):
from modoboa.limits import utils as limits_utils
super(PermissionsTestCase, cls).setUpTestData()
for name, tpl in limits_utils.get_limit_templates():
for name, tpl in limits_utils.get_user_limit_templates():
parameters.save_admin(
"DEFLT_{0}_LIMIT".format(name.upper()), 2,
"DEFLT_USER_{0}_LIMIT".format(name.upper()), 2,
app="limits"
)
factories.populate_database()
+1 -1
View File
@@ -37,7 +37,7 @@ def _validate_alias(request, form, successmsg, callback=None):
def _new_alias(request, title, action, successmsg,
tplname="admin/aliasform.html"):
core_signals.can_create_object.send(
"new_alias", user=request.user, object_type="mailbox_aliases")
"new_alias", context=request.user, object_type="mailbox_aliases")
if request.method == "POST":
def callback(user, alias):
alias.post_create(user)
+1 -1
View File
@@ -100,7 +100,7 @@ def domains_list(request):
@reversion.create_revision()
def newdomain(request):
core_signals.can_create_object.send(
"newdomain", user=request.user, object_type="domains")
"newdomain", context=request.user, object_type="domains")
return DomainWizard(request).process()
+1 -1
View File
@@ -3,4 +3,4 @@
import django.dispatch
can_create_object = django.dispatch.Signal(
providing_args=["user", "object_type", "count"])
providing_args=["context", "object_type", "count"])
@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations
def rename_limits_parameters(apps, schema_editor):
"""Rename parameters."""
Parameter = apps.get_model("lib", "Parameter")
for param in Parameter.objects.filter(name__startswith="DEFLT_"):
param.name = param.name.replace("DEFLT_", "DEFLT_USER_")
param.save()
class Migration(migrations.Migration):
dependencies = [
('lib', '0004_auto_20151114_1409'),
]
operations = [
migrations.RunPython(rename_limits_parameters)
]
+51 -6
View File
@@ -10,9 +10,9 @@ from modoboa.lib.parameters import AdminParametersForm
class ParametersForm(AdminParametersForm):
app = "limits"
defv_sep = SeparatorField(label=_("Default limits"))
defv_sep = SeparatorField(label=_("Default per-admin limits"))
deflt_domain_admins_limit = forms.IntegerField(
deflt_user_domain_admins_limit = forms.IntegerField(
label=_("Domain admins"),
initial=0,
help_text=_(
@@ -23,7 +23,7 @@ class ParametersForm(AdminParametersForm):
widget=forms.widgets.TextInput(
attrs={"class": "col-md-1 form-control"})
)
deflt_domains_limit = forms.IntegerField(
deflt_user_domains_limit = forms.IntegerField(
label=_("Domains"),
initial=0,
help_text=_(
@@ -33,7 +33,7 @@ class ParametersForm(AdminParametersForm):
widget=forms.widgets.TextInput(
attrs={"class": "col-md-1 form-control"})
)
deflt_domain_aliases_limit = forms.IntegerField(
deflt_user_domain_aliases_limit = forms.IntegerField(
label=_("Domain aliases"),
initial=0,
help_text=_(
@@ -44,7 +44,7 @@ class ParametersForm(AdminParametersForm):
widget=forms.widgets.TextInput(
attrs={"class": "col-md-1 form-control"})
)
deflt_mailboxes_limit = forms.IntegerField(
deflt_user_mailboxes_limit = forms.IntegerField(
label=_("Mailboxes"),
initial=0,
help_text=_(
@@ -54,7 +54,7 @@ class ParametersForm(AdminParametersForm):
widget=forms.widgets.TextInput(
attrs={"class": "col-md-1 form-control"})
)
deflt_mailbox_aliases_limit = forms.IntegerField(
deflt_user_mailbox_aliases_limit = forms.IntegerField(
label=_("Mailbox aliases"),
initial=0,
help_text=_(
@@ -65,6 +65,51 @@ class ParametersForm(AdminParametersForm):
attrs={"class": "col-md-1 form-control"})
)
domain_limits_sep = SeparatorField(label=_("Default per-domain limits"))
# deflt_domain_domain_admins_limit = forms.IntegerField(
# label=_("Domain admins"),
# initial=0,
# help_text=_(
# "Maximum number of allowed domain administrators for a new "
# "domain. (0 to deny any creation, -1 to allow unlimited "
# "creations)"
# ),
# widget=forms.widgets.TextInput(
# attrs={"class": "col-md-1 form-control"})
# )
deflt_domain_domain_aliases_limit = forms.IntegerField(
label=_("Domain aliases"),
initial=0,
help_text=_(
"Maximum number of allowed domain aliases for a new "
"domain. (0 to deny any creation, -1 to allow "
"unlimited creations)"
),
widget=forms.widgets.TextInput(
attrs={"class": "col-md-1 form-control"})
)
deflt_domain_mailboxes_limit = forms.IntegerField(
label=_("Mailboxes"),
initial=0,
help_text=_(
"Maximum number of allowed mailboxes for a new domain. "
"(0 to deny any creation, -1 to allow unlimited creations)"
),
widget=forms.widgets.TextInput(
attrs={"class": "col-md-1 form-control"})
)
deflt_domain_mailbox_aliases_limit = forms.IntegerField(
label=_("Mailbox aliases"),
initial=0,
help_text=_(
"Maximum number of allowed aliases for a new domain. "
"(0 to deny any creation, -1 to allow unlimited creations)"
),
widget=forms.widgets.TextInput(
attrs={"class": "col-md-1 form-control"})
)
def __init__(self, *args, **kwargs):
super(AdminParametersForm, self).__init__(*args, **kwargs)
self._load_extra_parameters('A')
+16 -1
View File
@@ -5,7 +5,7 @@ import collections
from django.utils.translation import ugettext_lazy as _
DEFAULT_LIMITS = collections.OrderedDict((
DEFAULT_USER_LIMITS = collections.OrderedDict((
("domains", {
"content_type": "admin.domain", "label": _("Domains"),
"help": _("Maximum number of domains this user can create"),
@@ -28,3 +28,18 @@ DEFAULT_LIMITS = collections.OrderedDict((
"required_role": "Resellers",
"extra_filters": {"groups__name": "DomainAdmins"}})
))
DEFAULT_DOMAIN_LIMITS = collections.OrderedDict((
("domain_aliases", {
"relation": "domainalias_set", "label": _("Domain aliases"),
"help": _("Maximum number of domain aliases allowed for this domain.")
}),
("mailboxes", {
"relation": "mailbox_set", "label": _("Mailboxes"),
"help": _("Maximum number of mailboxes allowed for this domain.")}),
("mailbox_aliases", {
"relation": "alias_set", "label": _("Mailbox aliases"),
"help": _("Maximum number of mailbox aliases allowed for this domain.")
})
))
+4 -4
View File
@@ -18,11 +18,11 @@ def move_pool_resource(owner, user):
associated resource to the pool of its owner.
"""
if not owner.is_superuser:
for name, ltpl in utils.get_limit_templates():
l = user.objectlimit_set.get(name=name)
for name, ltpl in utils.get_user_limit_templates():
l = user.userobjectlimit_set.get(name=name)
if l.max_value < 0:
continue
ol = owner.objectlimit_set.get(name=name)
ol = owner.userobjectlimit_set.get(name=name)
ol.max_value += l.max_value
ol.save()
@@ -44,7 +44,7 @@ def user_can_set_role(user, role, account=None):
lname = "domain_admins"
condition = (
user.is_superuser or
not user.objectlimit_set.get(name=lname).is_exceeded()
not user.userobjectlimit_set.get(name=lname).is_exceeded()
)
if condition:
return [True]
+46 -11
View File
@@ -5,7 +5,6 @@
from django import forms
from django.utils.translation import ugettext as _
from modoboa.lib import parameters
from modoboa.lib.permissions import get_object_owner
from .lib import BadLimitValue, UnsufficientResource
@@ -16,12 +15,9 @@ class ResourcePoolForm(forms.Form):
"""Dynamic form to display user limits."""
def __init__(self, *args, **kwargs):
self.account = None
if "instance" in kwargs:
self.account = kwargs["instance"]
del kwargs["instance"]
self.account = kwargs.pop("instance", None)
super(ResourcePoolForm, self).__init__(*args, **kwargs)
for name, tpl in utils.get_limit_templates():
for name, tpl in utils.get_user_limit_templates():
if "required_role" in tpl:
condition = (
self.account is not None and
@@ -32,7 +28,7 @@ class ResourcePoolForm(forms.Form):
self.fields["{}_limit".format(name)] = forms.IntegerField(
label=tpl["label"], help_text=tpl["help"]
)
if hasattr(self, "account"):
if self.account:
self.load_from_user(self.account)
def clean(self):
@@ -50,7 +46,7 @@ class ResourcePoolForm(forms.Form):
for fieldname in self.fields.keys():
lname = fieldname.replace("_limit", "")
self.fields[fieldname].initial = (
user.objectlimit_set.get(name=lname).max_value)
user.userobjectlimit_set.get(name=lname).max_value)
def allocate_from_user(self, limit, user):
"""Allocate resource using an existing user.
@@ -60,7 +56,7 @@ class ResourcePoolForm(forms.Form):
also be limited, the resource he gives is taken from its own
pool.
"""
ol = user.objectlimit_set.get(name=limit.name)
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:
@@ -80,12 +76,51 @@ class ResourcePoolForm(forms.Form):
def save(self):
owner = get_object_owner(self.account)
for name, ltpl in utils.get_limit_templates():
for name, ltpl in utils.get_user_limit_templates():
fieldname = "{}_limit".format(name)
if fieldname not in self.cleaned_data:
continue
l = self.account.objectlimit_set.get(name=name)
l = self.account.userobjectlimit_set.get(name=name)
if not owner.is_superuser:
self.allocate_from_user(l, owner)
l.max_value = self.cleaned_data[fieldname]
l.save()
class DomainLimitsForm(forms.Form):
"""Per-domain limits form."""
def __init__(self, *args, **kwargs):
"""Define limits as fields."""
self.domain = kwargs.pop("instance", None)
super(DomainLimitsForm, self).__init__(*args, **kwargs)
for name, tpl in utils.get_domain_limit_templates():
self.fields["{}_limit".format(name)] = forms.IntegerField(
label=tpl["label"], help_text=tpl["help"]
)
if not self.domain:
return
for fieldname in self.fields.keys():
lname = fieldname.replace("_limit", "")
self.fields[fieldname].initial = (
self.domain.domainobjectlimit_set.get(name=lname).max_value)
def clean(self):
"""Ensure limit values are correct."""
cleaned_data = super(DomainLimitsForm, self).clean()
if self.errors:
return cleaned_data
for lname in self.fields.keys():
if cleaned_data[lname] < -1:
self.add_error(lname, _("Invalid limit"))
return cleaned_data
def save(self, user):
"""Set limits."""
for name, ltpl in utils.get_domain_limit_templates():
fieldname = "{}_limit".format(name)
if fieldname not in self.cleaned_data:
continue
l = self.domain.domainobjectlimit_set.get(name=name)
l.max_value = self.cleaned_data[fieldname]
l.save()
+26 -8
View File
@@ -4,7 +4,7 @@ from django.utils.translation import ugettext as _
from modoboa.lib import events
from .forms import ResourcePoolForm
from . import forms
from . import utils
@@ -21,13 +21,13 @@ def display_pool_usage(user, target, currentpage):
else:
exceptions = ["domain_admins", "mailboxes", "mailbox_aliases"]
names = [
name for name, tpl in utils.get_limit_templates()
name for name, tpl in utils.get_user_limit_templates()
if name not in exceptions and
("required_role" not in tpl or
tpl["required_role"] == user.group)
]
limits = user.objectlimit_set.filter(name__in=names, max_value__gt=0)
limits = user.userobjectlimit_set.filter(name__in=names, max_value__gt=0)
if len(limits) == 0:
return []
return [
@@ -44,11 +44,29 @@ def extra_account_form(user, account=None):
account.group not in ["Resellers", "DomainAdmins"]:
return []
return [
dict(
id="resources", title=_("Resources"), cls=ResourcePoolForm
)
]
return [{
"id": "resources", "title": _("Resources"),
"cls": forms.ResourcePoolForm
}]
@events.observe("ExtraDomainForm")
def extra_domain_form(user, domain):
"""Include domain limits form."""
if not user.has_perm("admin.change_domain"):
return []
return [{
"id": "resources", "title": _("Resources"),
"cls": forms.DomainLimitsForm
}]
@events.observe("FillDomainInstances")
def fill_domain_instances(user, domain, instances):
"""Set domain instance for resources form."""
if not user.has_perm("admin.change_domain"):
return
instances["resources"] = domain
@events.observe("CheckExtraAccountForm")
+27 -7
View File
@@ -5,6 +5,7 @@ from django.dispatch import receiver
from django.contrib.contenttypes.models import ContentType
from modoboa.admin import models as admin_models
from modoboa.core import models as core_models
from modoboa.core import signals as core_signals
from modoboa.lib import signals as lib_signals
@@ -16,11 +17,16 @@ from . import utils
@receiver(core_signals.can_create_object)
def check_object_limit(sender, user, object_type, **kwargs):
def check_object_limit(sender, context, object_type, **kwargs):
"""Check if user can create a new object."""
if user.is_superuser:
return True
limit = user.objectlimit_set.get(name=object_type)
if context.__class__.__name__ == "User":
if context.is_superuser:
return True
limit = context.userobjectlimit_set.get(name=object_type)
elif context.__class__.__name__ == "Domain":
limit = context.domainobjectlimit_set.get(name=object_type)
else:
raise NotImplementedError
count = kwargs.get("count", 1)
if limit.is_exceeded(count):
raise lib.LimitReached(limit)
@@ -33,13 +39,27 @@ def create_user_limits(sender, instance, **kwargs):
return
request = lib_signals.get_request()
creator = request.user if request else None
for name, definition in utils.get_limit_templates():
for name, definition in utils.get_user_limit_templates():
ct = ContentType.objects.get_by_natural_key(
*definition["content_type"].split("."))
max_value = 0
# creator can be None if user was created by a factory
if not creator or creator.is_superuser:
max_value = int(
parameters.get_admin("DEFLT_{}_LIMIT".format(name.upper())))
models.ObjectLimit.objects.create(
parameters.get_admin(
"DEFLT_USER_{}_LIMIT".format(name.upper())))
models.UserObjectLimit.objects.create(
user=instance, name=name, content_type=ct, max_value=max_value)
@receiver(signals.post_save, sender=admin_models.Domain)
def create_domain_limits(sender, instance, **kwargs):
"""Create limits for new domain."""
if not kwargs.get("created"):
return
for name, definition in utils.get_domain_limit_templates():
max_value = 0
max_value = int(
parameters.get_admin("DEFLT_DOMAIN_{}_LIMIT".format(name.upper())))
models.DomainObjectLimit.objects.create(
domain=instance, name=name, max_value=max_value)
@@ -8,7 +8,7 @@ from django.conf import settings
def move_limits_to_user(apps, schema_editor):
"""Move limits from Pool to User."""
LimitsPool = apps.get_model("limits", "LimitsPool")
ObjectLimit = apps.get_model("limits", "ObjectLimit")
UserObjectLimit = apps.get_model("limits", "UserObjectLimit")
ContentType = apps.get_model("contenttypes", "ContentType")
if not ContentType.objects.exists():
@@ -34,12 +34,12 @@ def move_limits_to_user(apps, schema_editor):
.prefetch_related("limit_set"))
for pool in qset:
for limit in pool.limit_set.all():
objlimit = ObjectLimit(
objlimit = UserObjectLimit(
user=pool.user, max_value=limit.maxvalue,
name=limit.name.replace("_limit", ""),
content_type=name_to_content_type[limit.name])
to_create.append(objlimit)
ObjectLimit.objects.bulk_create(to_create)
UserObjectLimit.objects.bulk_create(to_create)
class Migration(migrations.Migration):
@@ -53,7 +53,7 @@ class Migration(migrations.Migration):
operations = [
migrations.CreateModel(
name='ObjectLimit',
name='UserObjectLimit',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=254)),
@@ -63,7 +63,7 @@ class Migration(migrations.Migration):
],
),
migrations.AlterUniqueTogether(
name='objectlimit',
name='userobjectlimit',
unique_together=set([('user', 'name')]),
),
migrations.RunPython(move_limits_to_user)
@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import modoboa.limits.models
from .. import utils
def create_limits(apps, schema_editor):
"""Create limits for existing domains."""
Domain = apps.get_model("admin", "Domain")
DomainObjectLimit = apps.get_model("limits", "DomainObjectLimit")
to_create = []
for domain in Domain.objects.all():
for name, tpl in utils.get_domain_limit_templates():
to_create.append(
DomainObjectLimit(domain=domain, name=name, max_value=-1))
DomainObjectLimit.objects.bulk_create(to_create)
class Migration(migrations.Migration):
dependencies = [
('admin', '0003_auto_20151118_1215'),
('limits', '0004_auto_20160413_1312'),
]
operations = [
migrations.CreateModel(
name='DomainObjectLimit',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=254)),
('max_value', models.IntegerField(default=0)),
('domain', models.ForeignKey(to='admin.Domain')),
],
bases=(modoboa.limits.models.ObjectLimitMixin, models.Model),
),
migrations.AlterField(
model_name='userobjectlimit',
name='max_value',
field=models.IntegerField(default=0),
),
migrations.AlterUniqueTogether(
name='domainobjectlimit',
unique_together=set([('domain', 'name')]),
),
migrations.RunPython(create_limits)
]
+63 -11
View File
@@ -12,14 +12,42 @@ from modoboa.core import models as core_models
from . import utils
class ObjectLimitMixin(object):
"""Common methods."""
@property
def label(self):
"""Return display name."""
return self.definition["label"]
@property
def usage(self):
"""Return current limit usage in %."""
if self.max_value < 0:
return -1
if self.max_value == 0:
return 100
return int(float(self.current_value) / self.max_value * 100)
def is_exceeded(self, count=1):
"""Check if limit will be reached if we add count object(s)."""
return self.current_value + 1 > self.max_value
def __str__(self):
"""Display current usage."""
if self.max_value == -1:
return _("unlimited")
return "{}%".format(self.usage)
@python_2_unicode_compatible
class ObjectLimit(models.Model):
"""Per-user limits on object creation."""
class UserObjectLimit(ObjectLimitMixin, models.Model):
"""Object level limit."""
user = models.ForeignKey(settings.AUTH_USER_MODEL)
name = models.CharField(max_length=254)
content_type = models.ForeignKey("contenttypes.ContentType")
max_value = models.IntegerField(default=-2)
max_value = models.IntegerField(default=0)
class Meta:
unique_together = (("user", "name"), )
@@ -27,7 +55,7 @@ class ObjectLimit(models.Model):
@property
def definition(self):
"""Return the definition of this limit."""
for name, tpl in utils.get_limit_templates():
for name, tpl in utils.get_user_limit_templates():
if name == self.name:
return tpl
return None
@@ -46,6 +74,11 @@ class ObjectLimit(models.Model):
return model_class.objects.filter(
pk__in=id_list, **self.definition["extra_filters"]).count()
@property
def label(self):
"""Return display name."""
return self.definition["label"]
@property
def usage(self):
"""Return current limit usage in %."""
@@ -55,19 +88,38 @@ class ObjectLimit(models.Model):
return 100
return int(float(self.current_value) / self.max_value * 100)
@property
def label(self):
"""Return display name."""
return self.definition["label"]
def is_exceeded(self, count=1):
"""Check if limit will be reached if we add count object(s)."""
return self.current_value + 1 > self.max_value
def __str__(self):
"""Display current usage."""
if self.max_value == -2:
return _("undefined")
if self.max_value == -1:
return _("unlimited")
return "{}%".format(self.usage)
class DomainObjectLimit(ObjectLimitMixin, models.Model):
"""Per-domain limits on object creation."""
domain = models.ForeignKey("admin.Domain")
name = models.CharField(max_length=254)
max_value = models.IntegerField(default=0)
class Meta:
unique_together = (("domain", "name"), )
@property
def definition(self):
"""Return the definition of this limit."""
for name, tpl in utils.get_domain_limit_templates():
if name == self.name:
return tpl
return None
@property
def current_value(self):
"""Return the current number of objects."""
if not self.definition:
raise RuntimeError("Bad limit {}".format(self.name))
return getattr(self.domain, self.definition["relation"]).count()
+7 -7
View File
@@ -47,9 +47,9 @@ class ResourceTestCase(ModoTestCase):
def setUpTestData(cls):
"""Custom setUpTestData method."""
super(ResourceTestCase, cls).setUpTestData()
for name, tpl in utils.get_limit_templates():
for name, tpl in utils.get_user_limit_templates():
parameters.save_admin(
"DEFLT_{0}_LIMIT".format(name.upper()), 2,
"DEFLT_USER_{0}_LIMIT".format(name.upper()), 2,
app="limits"
)
populate_database()
@@ -107,7 +107,7 @@ class ResourceTestCase(ModoTestCase):
)
def _check_limit(self, name, curvalue, maxvalue):
l = self.user.objectlimit_set.get(name=name)
l = self.user.userobjectlimit_set.get(name=name)
self.assertEqual(l.current_value, curvalue)
self.assertEqual(l.max_value, maxvalue)
@@ -119,7 +119,7 @@ class DomainAdminTestCase(ResourceTestCase):
"""Create test data."""
super(DomainAdminTestCase, cls).setUpTestData()
cls.user = User.objects.get(username='admin@test.com')
cls.user.objectlimit_set.filter(
cls.user.userobjectlimit_set.filter(
name__in=["mailboxes", "mailbox_aliases"]).update(max_value=2)
def setUp(self):
@@ -240,7 +240,7 @@ class ResellerTestCase(ResourceTestCase):
)
self._check_limit('domain_admins', 2, 2)
self.user.objectlimit_set.filter(name="mailboxes").update(max_value=3)
self.user.userobjectlimit_set.filter(name="mailboxes").update(max_value=3)
self._create_account('user1@domain.tld')
user = User.objects.get(username='user1@domain.tld')
values = {
@@ -265,11 +265,11 @@ class ResellerTestCase(ResourceTestCase):
domadmin = User.objects.get(username='admin1@domain.tld')
for l in ['mailboxes', 'mailbox_aliases']:
self.assertEqual(
domadmin.objectlimit_set.get(name=l).max_value, 0
domadmin.userobjectlimit_set.get(name=l).max_value, 0
)
def test_domain_admins_limit_from_domain_tpl(self):
self.user.objectlimit_set.filter(name="domains").update(max_value=3)
self.user.userobjectlimit_set.filter(name="domains").update(max_value=3)
self._create_domain('domain1.tld', withtpl=True)
self._create_domain('domain2.tld', withtpl=True)
self._check_limit('domain_admins', 2, 2)
+7 -2
View File
@@ -3,7 +3,12 @@
from . import constants
def get_limit_templates():
def get_user_limit_templates():
"""Return defined templates."""
# FIXME: change GetExtraLimitTemplates
return constants.DEFAULT_LIMITS.items()
return constants.DEFAULT_USER_LIMITS.items()
def get_domain_limit_templates():
"""Return defined templates."""
return constants.DEFAULT_DOMAIN_LIMITS.items()
+3 -3
View File
@@ -50,7 +50,7 @@ class Operations(object):
)
def _check_limit(self, name, curvalue, maxvalue):
l = self.user.objectlimit_set.get(name=name)
l = self.user.userobjectlimit_set.get(name=name)
self.assertEqual(l.current_value, curvalue)
self.assertEqual(l.max_value, maxvalue)
@@ -233,9 +233,9 @@ class LimitsTestCase(ModoTestCase, Operations):
"""Create test data."""
super(LimitsTestCase, cls).setUpTestData()
for name, tpl in limits_utils.get_limit_templates():
for name, tpl in limits_utils.get_user_limit_templates():
parameters.save_admin(
"DEFLT_{0}_LIMIT".format(name.upper()), 2, app="limits"
"DEFLT_USER_{0}_LIMIT".format(name.upper()), 2, app="limits"
)
cls.user = UserFactory.create(
username='reseller', groups=('Resellers',)