mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
@@ -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():
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"):
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
]
|
||||
@@ -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')
|
||||
|
||||
@@ -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.")
|
||||
})
|
||||
))
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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',)
|
||||
|
||||
Reference in New Issue
Block a user