see #860
This commit is contained in:
Antoine Nguyen
2016-04-14 12:03:05 +02:00
parent fa2cabdbfe
commit 0210275499
20 changed files with 333 additions and 381 deletions
-14
View File
@@ -147,20 +147,6 @@ displayed. Possible values are:
See :ref:`usermenudisplay` for a description of what callbacks that
listen to this event must return.
CanCreate
=========
Raised just before a user tries to create a new object.
*Callback prototype*::
def callback(user): pass
* ``user`` is a ``User`` instance
Return ``True`` or ``False`` to indicate if this user can respectively
create or not create a new ``Domain`` object.
CheckDomainName
===============
+6 -4
View File
@@ -10,6 +10,7 @@ from django.utils.translation import ugettext as _, ugettext_lazy
from passwords.fields import PasswordField
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
@@ -262,7 +263,8 @@ class AccountFormMail(forms.Form, DynamicForm):
"""Create a mailbox associated to :kw:`account`."""
if not user.can_access(self.domain):
raise PermDeniedException
events.raiseEvent("CanCreate", user, "mailboxes")
core_signals.can_create_object.send(
self.__class__, user=user, object_type="mailboxes")
self.mb = Mailbox(
address=self.locpart, domain=self.domain, user=account,
use_domain_quota=self.cleaned_data["quota_act"])
@@ -293,9 +295,9 @@ class AccountFormMail(forms.Form, DynamicForm):
aliases.remove(ralias.alias.address)
if not aliases:
return
events.raiseEvent(
"CanCreate", user, "mailbox_aliases", len(aliases)
)
core_signals.can_create_object.send(
self.__class__, user=user, object_type="mailbox_aliases",
count=len(aliases))
for alias in aliases:
if self.mb.aliasrecipient_set.select_related("alias").filter(
alias__address=alias).exists():
+8 -4
View File
@@ -6,6 +6,7 @@ from django.http import QueryDict
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.core.models import User
from modoboa.core import signals as core_signals
from modoboa.lib import events, parameters
from modoboa.lib.exceptions import ModoboaException, Conflict
from modoboa.lib.fields import DomainNameField
@@ -146,8 +147,9 @@ class DomainFormGeneral(forms.ModelForm, DynamicForm):
else:
aliases.remove(dalias.name)
if aliases:
events.raiseEvent(
"CanCreate", user, "domain_aliases", len(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)
@@ -224,7 +226,8 @@ class DomainFormOptions(forms.Form):
pass
else:
raise Conflict(_("User '%s' already exists" % username))
events.raiseEvent("CanCreate", user, "mailboxes")
core_signals.can_create_object.send(
self.__class__, user=user, object_type="mailboxes")
da = User(username=username, email=username, is_active=True)
da.set_password("password")
da.save()
@@ -241,7 +244,8 @@ class DomainFormOptions(forms.Form):
if domain.type == "domain" and \
self.cleaned_data["create_aliases"] == "yes":
events.raiseEvent("CanCreate", user, "mailbox_aliases")
core_signals.can_create_object.send(
self.__class__, user=user, object_type="mailbox_aliases")
alias = Alias(
address=u"postmaster@{}".format(domain.name),
domain=domain, enabled=True
+3 -2
View File
@@ -9,7 +9,7 @@ from django.utils.translation import ugettext as _, ungettext
import reversion
from modoboa.lib import events
from modoboa.core import signals as core_signals
from modoboa.lib.exceptions import PermDeniedException, Conflict
from modoboa.lib.web_utils import render_to_json_response
@@ -36,7 +36,8 @@ def _validate_alias(request, form, successmsg, callback=None):
def _new_alias(request, title, action, successmsg,
tplname="admin/aliasform.html"):
events.raiseEvent("CanCreate", request.user, "mailbox_aliases")
core_signals.can_create_object.send(
"new_alias", user=request.user, object_type="mailbox_aliases")
if request.method == "POST":
def callback(user, alias):
alias.post_create(user)
+3 -1
View File
@@ -14,6 +14,7 @@ from django.contrib.auth.decorators import (
import reversion
from modoboa.core.models import User
from modoboa.core import signals as core_signals
from modoboa.lib import parameters, events
from modoboa.lib.web_utils import (
_render_to_string, render_to_json_response
@@ -98,7 +99,8 @@ def domains_list(request):
@permission_required("admin.add_domain")
@reversion.create_revision()
def newdomain(request):
events.raiseEvent("CanCreate", request.user, "domains")
core_signals.can_create_object.send(
"newdomain", user=request.user, object_type="domains")
return DomainWizard(request).process()
-2
View File
@@ -4,8 +4,6 @@ from django.apps import AppConfig
BASE_EVENTS = [
"CanCreate",
"AccountCreated",
"AccountAutoCreated",
"AccountModified",
+6
View File
@@ -0,0 +1,6 @@
"""Modoboa core signals."""
import django.dispatch
can_create_object = django.dispatch.Signal(
providing_args=["user", "object_type", "count"])
+6 -3
View File
@@ -1,10 +1,13 @@
"""Custom signals."""
from django.dispatch import Signal
import django.dispatch
request_accessor = Signal()
request_accessor = django.dispatch.Signal()
def get_request():
"""Get the current request from anywhere."""
return request_accessor.send(None)[0][1]
request = request_accessor.send(None)
if request:
return request[0][1]
return None
+2
View File
@@ -29,3 +29,5 @@ class LimitsConfig(AppConfig):
def ready(self):
load_limits_settings()
from . import handlers
+30
View File
@@ -0,0 +1,30 @@
"""Modoboa limits constants."""
import collections
from django.utils.translation import ugettext_lazy as _
DEFAULT_LIMITS = collections.OrderedDict((
("domains", {
"content_type": "admin.domain", "label": _("Domains"),
"help": _("Maximum number of domains this user can create"),
"required_role": "Reselles"}),
("domain_aliases", {
"content_type": "admin.domainalias", "label": _("Domain aliases"),
"help": _("Maximum number of domain aliases this user can create"),
"required_role": "Resellers"}),
("mailboxes", {
"content_type": "admin.mailbox", "label": _("Mailboxes"),
"help": _("Maximum number of mailboxes this user can create")}),
("mailbox_aliases", {
"content_type": "admin.alias", "label": _("Mailbox aliases"),
"help": _("Maximum number of mailbox aliases this user "
"can create")}),
("domain_admins", {
"content_type": "core.user", "label": _("Domain admins"),
"help": _("Maximum number of domain administrators this user "
"can create"),
"required_role": "Resellers",
"extra_filters": {"groups__name": "DomainAdmins"}})
))
+13 -151
View File
@@ -8,27 +8,7 @@
from modoboa.lib import events
from modoboa.lib.permissions import get_object_owner
import modoboa.admin.models as admin_models
from .lib import LimitReached, inc_limit_usage, dec_limit_usage
from .models import LimitTemplates, LimitsPool
def check_limit(user, lname, count=1):
"""Check if a user has reached a defined limit
We check if the user will not reach the given limit after this
creation.
:param user: a ``User`` object
:param int count: the number of objects the user tries to create
:raises: ``LimitReached``
"""
try:
if user.limitspool.will_be_reached(lname, count):
raise LimitReached(user.limitspool.get_limit(lname))
except LimitsPool.DoesNotExist:
pass
from . import utils
def move_pool_resource(owner, user):
@@ -37,99 +17,15 @@ def move_pool_resource(owner, user):
When an account doesn't need a pool anymore, we move the
associated resource to the pool of its owner.
"""
if not hasattr(user, "limitspool"):
return
if not owner.is_superuser:
for ltpl in LimitTemplates().templates:
l = user.limitspool.get_limit(ltpl[0])
if l.maxvalue < 0:
for name, ltpl in utils.get_limit_templates():
l = user.objectlimit_set.get(name=name)
if l.max_value < 0:
continue
ol = owner.limitspool.get_limit(ltpl[0])
ol.curvalue += l.curvalue
ol.maxvalue += l.maxvalue
ol = owner.objectlimit_set.get(name=name)
ol.max_value += l.max_value
ol.save()
user.limitspool.delete()
@events.observe('DomainCreated')
def inc_nb_domains(user, domain):
inc_limit_usage(user, 'domains_limit')
@events.observe('DomainDeleted')
def dec_nb_domains(domain, owner=None):
if owner is None:
owner = get_object_owner(domain)
dec_limit_usage(owner, 'domains_limit')
for domalias in domain.domainalias_set.all():
dec_nb_domaliases(domalias)
for mailbox in domain.mailbox_set.all():
dec_nb_mailboxes(mailbox)
for mbalias in domain.alias_set.all():
dec_nb_mbaliases(mbalias)
@events.observe('DomainAliasCreated')
def inc_nb_domaliases(user, domalias):
inc_limit_usage(user, 'domain_aliases_limit')
@events.observe('DomainAliasDeleted')
def dec_nb_domaliases(domainaliases):
if isinstance(domainaliases, admin_models.DomainAlias):
domainaliases = [domainaliases]
for domainalias in domainaliases:
owner = get_object_owner(domainalias)
dec_limit_usage(owner, 'domain_aliases_limit')
@events.observe('MailboxCreated')
def inc_nb_mailboxes(user, mailbox):
inc_limit_usage(user, 'mailboxes_limit')
@events.observe('MailboxDeleted')
def dec_nb_mailboxes(mailboxes):
if isinstance(mailboxes, admin_models.Mailbox):
mailboxes = [mailboxes]
for mailbox in mailboxes:
owner = get_object_owner(mailbox)
dec_limit_usage(owner, 'mailboxes_limit')
@events.observe('MailboxAliasCreated')
def inc_nb_mbaliases(user, mailboxalias):
inc_limit_usage(user, 'mailbox_aliases_limit')
@events.observe('MailboxAliasDeleted')
def dec_nb_mbaliases(mailboxaliases):
if isinstance(mailboxaliases, admin_models.Alias):
mailboxaliases = [mailboxaliases]
for alias in mailboxaliases:
owner = get_object_owner(alias)
dec_limit_usage(owner, 'mailbox_aliases_limit')
@events.observe('CanCreate')
def can_create_new_object(user, objtype, count=1):
check_limit(user, '%s_limit' % objtype, count)
@events.observe("AccountCreated")
def create_pool(user):
owner = get_object_owner(user)
if owner.group not in ['SuperAdmins', 'Resellers']:
return
if user.group == 'DomainAdmins':
check_limit(owner, 'domain_admins_limit')
inc_limit_usage(owner, 'domain_admins_limit')
if user.group in ['DomainAdmins', 'Resellers']:
p, created = LimitsPool.objects.get_or_create(user=user)
p.create_limits(owner)
@events.observe("UserCanSetRole")
def user_can_set_role(user, role, account=None):
@@ -143,11 +39,14 @@ def user_can_set_role(user, role, account=None):
:param ``User`` account: account modified (None on creation)
:param str newrole: role to check
"""
if role == 'DomainAdmins':
lname = 'domain_admins_limit'
else:
if role != "DomainAdmins":
return [True]
if user.is_superuser or not user.limitspool.will_be_reached(lname):
lname = "domain_admins"
condition = (
user.is_superuser or
not user.objectlimit_set.get(name=lname).is_exceeded()
)
if condition:
return [True]
if account is not None and account.group == role:
return [True]
@@ -162,26 +61,9 @@ def on_account_modified(old, new):
# Domain admins can't change the role so nothing to check.
return
if new.group != "SuperAdmins":
# Check if account needs a pool (case: a superadmin is
# downgraded)
if not hasattr(new, "limitspool"):
p = LimitsPool(user=new)
p.save()
p.create_limits(owner)
if new.group not in ["DomainAdmins", "Resellers"]:
move_pool_resource(owner, new)
if old.oldgroup == "DomainAdmins":
if new.group != "DomainAdmins":
dec_limit_usage(owner, 'domain_admins_limit')
return
if new.group == "DomainAdmins":
check_limit(owner, 'domain_admins_limit')
inc_limit_usage(owner, 'domain_admins_limit')
@events.observe("AccountDeleted")
def on_account_deleted(account, byuser, **kwargs):
@@ -190,23 +72,3 @@ def on_account_deleted(account, byuser, **kwargs):
return
move_pool_resource(owner, account)
if account.group == "DomainAdmins":
dec_limit_usage(owner, 'domain_admins_limit')
@events.observe('DomainOwnershipRemoved')
def domain_ownership_removed(reseller, domain):
"""DomainOwnershipRemoved listener.
The access :keyword:`reseller` had to :keyword:`domain` has been
removed by a super adminstrator. We must decrement all limit
usages.
:param ``User`` reseller: reseller that created :keyword:`domain`
:param ``Domain`` domain: domain
"""
dec_nb_domains(domain, reseller)
for dadmin in domain.admins:
if reseller.is_owner(dadmin):
dec_limit_usage(reseller, 'domain_admins_limit')
+38 -29
View File
@@ -9,10 +9,11 @@ from modoboa.lib import parameters
from modoboa.lib.permissions import get_object_owner
from .lib import BadLimitValue, UnsufficientResource
from .models import LimitTemplates
from . import utils
class ResourcePoolForm(forms.Form):
"""Dynamic form to display user limits."""
def __init__(self, *args, **kwargs):
self.account = None
@@ -20,20 +21,25 @@ class ResourcePoolForm(forms.Form):
self.account = kwargs["instance"]
del kwargs["instance"]
super(ResourcePoolForm, self).__init__(*args, **kwargs)
for tpl in LimitTemplates().templates:
if len(tpl) > 3:
if self.account is not None and self.account.group != tpl[3]:
for name, tpl in utils.get_limit_templates():
if "required_role" in tpl:
condition = (
self.account is not None and
self.account.group != tpl["required_role"]
)
if condition:
continue
self.fields[tpl[0]] = forms.IntegerField(
label=tpl[1], help_text=tpl[2]
self.fields["{}_limit".format(name)] = forms.IntegerField(
label=tpl["label"], help_text=tpl["help"]
)
if hasattr(self, "account"):
self.load_from_user(self.account)
def check_limit_value(self, lname):
if self.cleaned_data[lname] < -1:
fieldname = "{}_limit".format(lname)
if self.cleaned_data[fieldname] < -1:
raise forms.ValidationError(_("Invalid limit"))
return self.cleaned_data[lname]
return self.cleaned_data[fieldname]
def clean(self):
cleaned_data = super(ResourcePoolForm, self).clean()
@@ -43,49 +49,52 @@ class ResourcePoolForm(forms.Form):
return cleaned_data
def load_from_user(self, user):
for lname in self.fields.keys():
self.fields[lname].initial = user.limitspool.getmaxvalue(lname)
for fieldname in self.fields.keys():
lname = fieldname.replace("_limit", "")
self.fields[fieldname].initial = (
user.objectlimit_set.get(name=lname).max_value)
# The following lines will become useless in a near
# future.
if self.fields[lname].initial == -2:
self.fields[lname].initial = parameters.get_admin(
"DEFLT_%s" %
lname.upper())
if self.fields[fieldname].initial == -2:
self.fields[fieldname].initial = parameters.get_admin(
"DEFLT_{}_LIMIT".format(lname.upper()))
def allocate_from_pool(self, limit, pool):
"""Allocate resource using an existing pool.
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 = pool.get_limit(limit.name)
if ol.maxvalue == -2:
ol = user.objectlimit_set.get(name=limit.name)
if ol.max_value == -2:
raise BadLimitValue(_("Your resources are not initialized yet"))
newvalue = self.cleaned_data[limit.name]
if newvalue == -1 and ol.maxvalue != -1:
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.maxvalue > -1:
newvalue -= limit.maxvalue
if limit.max_value > -1:
newvalue -= limit.max_value
if newvalue == 0:
return
remain = ol.maxvalue - ol.curvalue
remain = ol.max_value - ol.current_value
if newvalue > remain:
raise UnsufficientResource(ol)
ol.maxvalue -= newvalue
ol.max_value -= newvalue
ol.save()
def save(self):
owner = get_object_owner(self.account)
for ltpl in LimitTemplates().templates:
if ltpl[0] not in self.cleaned_data:
for name, ltpl in utils.get_limit_templates():
fieldname = "{}_limit".format(name)
if fieldname not in self.cleaned_data:
continue
l = self.account.limitspool.limit_set.get(name=ltpl[0])
l = self.account.objectlimit_set.get(name=name)
if not owner.is_superuser:
self.allocate_from_pool(l, owner.limitspool)
l.maxvalue = self.cleaned_data[ltpl[0]]
self.allocate_from_user(l, owner)
l.max_value = self.cleaned_data[fieldname]
l.save()
+9 -8
View File
@@ -5,7 +5,7 @@ from django.utils.translation import ugettext as _
from modoboa.lib import events
from .forms import ResourcePoolForm
from .models import LimitTemplates
from . import utils
@events.observe("ExtraAdminContent")
@@ -15,18 +15,19 @@ def display_pool_usage(user, target, currentpage):
if target != "leftcol" or user.is_superuser:
return []
if currentpage == "identities":
names = ["mailboxes_limit", "mailbox_aliases_limit"]
names = ["mailboxes", "mailbox_aliases"]
if user.has_perm("admin.add_domain"):
names += ["domain_admins_limit"]
names += ["domain_admins"]
else:
exceptions = ["domain_admins", "mailboxes", "mailbox_aliases"]
names = [
tpl[0] for tpl in LimitTemplates().templates
if tpl[0] not in ["domain_admins_limit", "mailboxes_limit",
"mailbox_aliases_limit"] and
(len(tpl) == 3 or tpl[3] == user.group)
name for name, tpl in utils.get_limit_templates()
if name not in exceptions and
("required_role" not in tpl or
tpl["required_role"] == user.group)
]
limits = user.limitspool.limit_set.filter(name__in=names, maxvalue__gt=0)
limits = user.objectlimit_set.filter(name__in=names, max_value__gt=0)
if len(limits) == 0:
return []
return [
+44
View File
@@ -0,0 +1,44 @@
"""Django signal handlers for limits."""
from django.db.models import signals
from django.dispatch import receiver
from django.contrib.contenttypes.models import ContentType
from modoboa.core import models as core_models
from modoboa.core import signals as core_signals
from modoboa.lib import signals as lib_signals
from modoboa.lib import parameters
from . import lib
from . import models
from . import utils
@receiver(core_signals.can_create_object)
def check_object_limit(sender, user, 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)
count = kwargs.get("count", 1)
if limit.is_exceeded(count):
raise lib.LimitReached(limit)
@receiver(signals.post_save, sender=core_models.User)
def create_user_limits(sender, instance, **kwargs):
"""Create limits for new user."""
if not kwargs.get("created"):
return
request = lib_signals.get_request()
creator = request.user if request else None
for name, definition in utils.get_limit_templates():
ct = ContentType.objects.get_by_natural_key(
*definition["content_type"].split("."))
max_value = 0
if not creator or creator.is_superuser:
max_value = int(
parameters.get_admin("DEFLT_{}_LIMIT".format(name.upper())))
models.ObjectLimit.objects.create(
user=instance, name=name, content_type=ct, max_value=max_value)
-19
View File
@@ -8,8 +8,6 @@ from django.utils.translation import ugettext as _
from modoboa.lib.exceptions import ModoboaException
from .models import LimitsPool
class LimitReached(ModoboaException):
http_code = 403
@@ -33,20 +31,3 @@ class UnsufficientResource(ModoboaException):
class BadLimitValue(ModoboaException):
http_code = 400
def inc_limit_usage(user, lname):
"""Increase a given limit usage."""
try:
user.limitspool.inc_curvalue(lname)
except LimitsPool.DoesNotExist:
pass
def dec_limit_usage(user, lname):
if user is None:
return
try:
user.limitspool.dec_curvalue(lname)
except LimitsPool.DoesNotExist:
pass
@@ -0,0 +1,70 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
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")
ContentType = apps.get_model("contenttypes", "ContentType")
if not ContentType.objects.exists():
# No ContentType means this is a fresh install
return
name_to_content_type = {
"domains_limit": (
ContentType.objects.get(app_label="admin", model="domain")),
"domain_aliases_limit": (
ContentType.objects.get(app_label="admin", model="domainalias")),
"mailboxes_limit": (
ContentType.objects.get(app_label="admin", model="mailbox")),
"mailbox_aliases_limit": (
ContentType.objects.get(app_label="admin", model="alias")),
"domain_admins_limit": (
ContentType.objects.get(app_label="core", model="user")),
}
to_create = []
qset = (
LimitsPool.objects.select_related("user")
.prefetch_related("limit_set"))
for pool in qset:
for limit in pool.limit_set.all():
objlimit = ObjectLimit(
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)
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('limits', '0002_auto_20151114_1518'),
('admin', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='ObjectLimit',
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=-2)),
('content_type', models.ForeignKey(to='contenttypes.ContentType')),
('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
],
),
migrations.AlterUniqueTogether(
name='objectlimit',
unique_together=set([('user', 'name')]),
),
migrations.RunPython(move_limits_to_user)
]
@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('limits', '0003_auto_20160413_1046'),
]
operations = [
migrations.AlterUniqueTogether(
name='limit',
unique_together=set([]),
),
migrations.RemoveField(
model_name='limit',
name='pool',
),
migrations.RemoveField(
model_name='limitspool',
name='user',
),
migrations.DeleteModel(
name='Limit',
),
migrations.DeleteModel(
name='LimitsPool',
),
]
+41 -132
View File
@@ -4,150 +4,59 @@
from django.conf import settings
from django.db import models
from django.utils.translation import ugettext as _, ugettext_lazy
from modoboa.lib import parameters, events
from modoboa.lib.singleton import Singleton
from modoboa.core import models as core_models
from . import utils
class LimitTemplates(Singleton):
class ObjectLimit(models.Model):
"""Per-user limits on object creation."""
def __init__(self):
self.__templates = [
("domain_admins_limit", ugettext_lazy("Domain admins"),
ugettext_lazy("Maximum number of domain administrators this user "
"can create"),
'Resellers'),
("domains_limit", ugettext_lazy("Domains"),
ugettext_lazy("Maximum number of domains this user can create"),
'Resellers'),
("domain_aliases_limit", ugettext_lazy("Domain aliases"),
ugettext_lazy(
"Maximum number of domain aliases this user can create"),
'Resellers'),
("mailboxes_limit", ugettext_lazy("Mailboxes"),
ugettext_lazy("Maximum number of mailboxes this user can create")),
("mailbox_aliases_limit", ugettext_lazy("Mailbox aliases"),
ugettext_lazy("Maximum number of mailbox aliases this user "
"can create"))
]
@property
def templates(self):
return self.__templates + \
events.raiseQueryEvent('GetExtraLimitTemplates')
class LimitsPool(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL)
def create_limits(self, creator):
"""Create limits for this pool.
All limits defined into ``LimitTemplates`` will be created. If
the creator is a super administrator, the default maximum values
will be used.
:param ``User`` creator: user creating this pool
"""
for ltpl in LimitTemplates().templates:
try:
Limit.objects.get(name=ltpl[0], pool=self)
except Limit.DoesNotExist:
maxvalue = (
int(parameters.get_admin("DEFLT_%s" % ltpl[0].upper()))
if creator.is_superuser else 0
)
Limit.objects.create(
name=ltpl[0], pool=self, maxvalue=maxvalue
)
def getcurvalue(self, lname):
l = self.limit_set.get(name=lname)
return l.curvalue
def getmaxvalue(self, lname):
l = self.limit_set.get(name=lname)
return l.maxvalue
def set_maxvalue(self, lname, value):
l = self.limit_set.get(name=lname)
l.maxvalue = value
l.save()
def inc_curvalue(self, lname, nb=1):
l = self.limit_set.get(name=lname)
l.curvalue += nb
l.save()
def dec_curvalue(self, lname, nb=1):
l = self.limit_set.get(name=lname)
if not l.curvalue:
return
l.curvalue -= nb
l.save()
def dec_limit(self, lname, nb=1):
l = self.limit_set.get(name=lname)
l.curvalue -= nb
if l.maxvalue > -1:
l.maxvalue -= nb
l.save()
def inc_limit(self, lname, nb=1):
l = self.limit_set.get(name=lname)
l.curvalue += nb
if l.maxvalue > -1:
l.maxvalue += nb
l.save()
def will_be_reached(self, lname, nb=1):
l = self.limit_set.get(name=lname)
if l.maxvalue <= -1:
return False
if l.curvalue + nb > l.maxvalue:
return True
return False
def get_limit(self, lname):
try:
return self.limit_set.get(name=lname)
except Limit.DoesNotExist:
return None
class Limit(models.Model):
name = models.CharField(max_length=255)
curvalue = models.IntegerField(default=0)
maxvalue = models.IntegerField(default=-2)
pool = models.ForeignKey(LimitsPool)
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)
class Meta:
unique_together = (("name", "pool"),)
unique_together = (("user", "name"), )
@property
def definition(self):
"""Return the definition of this limit."""
for name, tpl in utils.get_limit_templates():
if name == self.name:
return tpl
return None
@property
def current_value(self):
"""Return the current number of objects."""
if "extra_filters" not in self.definition:
return core_models.ObjectAccess.objects.filter(
user=self.user, is_owner=True,
content_type=self.content_type).count()
id_list = core_models.ObjectAccess.objects.filter(
user=self.user, is_owner=True,
content_type=self.content_type).values_list("object_id", flat=True)
model_class = self.content_type.model_class()
return model_class.objects.filter(
pk__in=id_list, **self.definition["extra_filters"]).count()
@property
def usage(self):
if self.maxvalue < 0:
"""Return current limit usage in %."""
if self.max_value < 0:
return -1
if self.maxvalue == 0:
if self.max_value == 0:
return 0
return int(float(self.curvalue) / self.maxvalue * 100)
return int(float(self.current_value) / self.max_value * 100)
@property
def label(self):
for l in LimitTemplates().templates:
if l[0] == self.name:
return l[1]
return ""
"""Return display name."""
return self.definition["label"]
def __str__(self):
if self.maxvalue == -2:
return _("undefined")
if self.maxvalue == -1:
return _("unlimited")
if self.maxvalue == 0:
return "100%"
return "%d%%" % self.usage
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
+13 -12
View File
@@ -9,7 +9,7 @@ from modoboa.core.models import User
from modoboa.lib import parameters
from modoboa.lib.tests import ModoTestCase
from .models import LimitTemplates
from . import utils
class PermissionsTestCase(ModoTestCase):
@@ -47,9 +47,9 @@ class ResourceTestCase(ModoTestCase):
def setUpTestData(cls):
"""Custom setUpTestData method."""
super(ResourceTestCase, cls).setUpTestData()
for tpl in LimitTemplates().templates:
for name, tpl in utils.get_limit_templates():
parameters.save_admin(
"DEFLT_{0}".format(tpl[0].upper()), 2,
"DEFLT_{0}_LIMIT".format(name.upper()), 2,
app="limits"
)
populate_database()
@@ -107,9 +107,9 @@ class ResourceTestCase(ModoTestCase):
)
def _check_limit(self, name, curvalue, maxvalue):
l = self.user.limitspool.get_limit('%s_limit' % name)
self.assertEqual(l.curvalue, curvalue)
self.assertEqual(l.maxvalue, maxvalue)
l = self.user.objectlimit_set.get(name=name)
self.assertEqual(l.current_value, curvalue)
self.assertEqual(l.max_value, maxvalue)
class DomainAdminTestCase(ResourceTestCase):
@@ -119,8 +119,8 @@ class DomainAdminTestCase(ResourceTestCase):
"""Create test data."""
super(DomainAdminTestCase, cls).setUpTestData()
cls.user = User.objects.get(username='admin@test.com')
cls.user.limitspool.set_maxvalue('mailboxes_limit', 2)
cls.user.limitspool.set_maxvalue('mailbox_aliases_limit', 2)
cls.user.objectlimit_set.filter(
name__in=["mailboxes", "mailbox_aliases"]).update(max_value=2)
def setUp(self):
"""Test initialization."""
@@ -177,9 +177,10 @@ class ResellerTestCase(ResourceTestCase):
def setUpTestData(cls):
"""Create test data."""
super(ResellerTestCase, cls).setUpTestData()
cls.user = UserFactory.create(
cls.user = UserFactory(
username='reseller', groups=('Resellers',)
)
cls.user.objectlimit_set.update(max_value=2)
def setUp(self):
"""Test initialization."""
@@ -232,7 +233,7 @@ class ResellerTestCase(ResourceTestCase):
)
self._check_limit('domain_admins', 2, 2)
self.user.limitspool.set_maxvalue('mailboxes_limit', 3)
self.user.objectlimit_set.filter(name="mailboxes").update(max_value=3)
self._create_account('user1@domain.tld')
user = User.objects.get(username='user1@domain.tld')
values = {
@@ -257,11 +258,11 @@ class ResellerTestCase(ResourceTestCase):
domadmin = User.objects.get(username='admin1@domain.tld')
for l in ['mailboxes', 'mailbox_aliases']:
self.assertEqual(
domadmin.limitspool.get_limit('%s_limit' % l).maxvalue, 0
domadmin.objectlimit_set.get(name=l).max_value, 0
)
def test_domain_admins_limit_from_domain_tpl(self):
self.user.limitspool.set_maxvalue('domains_limit', 3)
self.user.objectlimit_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)
+9
View File
@@ -0,0 +1,9 @@
"""Modoboa limits utilities."""
from . import constants
def get_limit_templates():
"""Return defined templates."""
# FIXME: change GetExtraLimitTemplates
return constants.DEFAULT_LIMITS.items()