diff --git a/modoboa/extensions/limits/__init__.py b/modoboa/extensions/limits/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/modoboa/extensions/limits/app_settings.py b/modoboa/extensions/limits/app_settings.py deleted file mode 100644 index 2d759a3cd..000000000 --- a/modoboa/extensions/limits/app_settings.py +++ /dev/null @@ -1,55 +0,0 @@ -"""Custom settings.""" - -from django import forms -from django.utils.translation import ugettext_lazy as _ - -from modoboa.lib.form_utils import SeparatorField -from modoboa.lib.parameters import AdminParametersForm - - -class ParametersForm(AdminParametersForm): - app = "limits" - - defv_sep = SeparatorField(label=_("Default limits")) - - deflt_domain_admins_limit = forms.IntegerField( - label=_("Domain admins"), - initial=0, - help_text=_( - "Maximum number of allowed domain administrators for a new " - "administrator" - ), - widget=forms.widgets.TextInput(attrs={"class": "col-md-1 form-control"}) - ) - deflt_domains_limit = forms.IntegerField( - label=_("Domains"), - initial=0, - help_text=_( - "Maximum number of allowed domains for a new administrator"), - widget=forms.widgets.TextInput(attrs={"class": "col-md-1 form-control"}) - ) - deflt_domain_aliases_limit = forms.IntegerField( - label=_("Domain aliases"), - initial=0, - help_text=_( - "Maximum number of allowed domain aliases for a new administrator"), - widget=forms.widgets.TextInput(attrs={"class": "col-md-1 form-control"}) - ) - deflt_mailboxes_limit = forms.IntegerField( - label=_("Mailboxes"), - initial=0, - help_text=_( - "Maximum number of allowed mailboxes for a new administrator"), - widget=forms.widgets.TextInput(attrs={"class": "col-md-1 form-control"}) - ) - deflt_mailbox_aliases_limit = forms.IntegerField( - label=_("Mailbox aliases"), - initial=0, - help_text=_( - "Maximum number of allowed aliases for a new administrator"), - 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') diff --git a/modoboa/extensions/limits/controls.py b/modoboa/extensions/limits/controls.py deleted file mode 100644 index f5d00b4ac..000000000 --- a/modoboa/extensions/limits/controls.py +++ /dev/null @@ -1,219 +0,0 @@ -# coding: utf-8 -""" -:mod:`controls` --- provides event handlers that check if limits are reached ----------------------------------------------------------------------------- - -""" -from .lib import LimitReached, inc_limit_usage, dec_limit_usage -from .models import LimitTemplates, LimitsPool -from modoboa.lib import events -from modoboa.lib.permissions import get_object_owner - - -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 - - -def move_pool_resource(owner, user): - """Move resource from one pool to another - - When an account doesn't need a pool anymore, we move the - associated resource to the pool of its owner. - """ - try: - pool = user.limitspool - except LimitsPool.DoesNotExist: - return - if not owner.is_superuser: - for ltpl in LimitTemplates().templates: - l = user.limitspool.get_limit(ltpl[0]) - if l.maxvalue < 0: - continue - ol = owner.limitspool.get_limit(ltpl[0]) - ol.curvalue += l.curvalue - ol.maxvalue += l.maxvalue - 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): - from modoboa.extensions.admin.models import DomainAlias - - if isinstance(domainaliases, 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): - from modoboa.extensions.admin.models import Mailbox - - if isinstance(mailboxes, 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): - from modoboa.extensions.admin.models import Alias - - if isinstance(mailboxaliases, 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): - """Check if the user can still set this role. - - The only interesting case concerns resellers defining new domain - administrators. We want to check if they are allowed to do this - operation before any modification is made to :keyword:`account`. - - :param ``User`` user: connected user - :param ``User`` account: account modified (None on creation) - :param str newrole: role to check - """ - if role == 'DomainAdmins': - lname = 'domain_admins_limit' - else: - return [True] - if user.is_superuser or not user.limitspool.will_be_reached(lname): - return [True] - if account is not None and account.group == role: - return [True] - return [False] - - -@events.observe("AccountModified") -def on_account_modified(old, new): - """Update limits when roles are updated""" - owner = get_object_owner(old) - if owner.group not in ["SuperAdmins", "Resellers"]: - # 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) - try: - pool = new.limitspool - except LimitsPool.DoesNotExist: - 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): - owner = get_object_owner(account) - if owner.group not in ["SuperAdmins", "Resellers"]: - 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') diff --git a/modoboa/extensions/limits/fixtures/demo.json b/modoboa/extensions/limits/fixtures/demo.json deleted file mode 100644 index 52a9bc1a1..000000000 --- a/modoboa/extensions/limits/fixtures/demo.json +++ /dev/null @@ -1 +0,0 @@ -[{"pk": 1, "model": "limits.limitspool", "fields": {"user": 2}}, {"pk": 1, "model": "limits.limit", "fields": {"curvalue": 0, "name": "domain_admins_limit", "pool": 1, "maxvalue": -2}}, {"pk": 2, "model": "limits.limit", "fields": {"curvalue": 0, "name": "domains_limit", "pool": 1, "maxvalue": -2}}, {"pk": 3, "model": "limits.limit", "fields": {"curvalue": 0, "name": "domain_aliases_limit", "pool": 1, "maxvalue": -2}}, {"pk": 4, "model": "limits.limit", "fields": {"curvalue": 0, "name": "mailboxes_limit", "pool": 1, "maxvalue": 5}}, {"pk": 5, "model": "limits.limit", "fields": {"curvalue": 0, "name": "mailbox_aliases_limit", "pool": 1, "maxvalue": 5}},{"pk": 3, "model": "auth.group", "fields": {"name": "Resellers", "permissions": [["add_user", "core", "user"], ["change_user", "core", "user"], ["delete_user", "core", "user"], ["view_domains", "admin", "domain"], ["add_mailbox", "admin", "mailbox"], ["change_mailbox", "admin", "mailbox"], ["delete_mailbox", "admin", "mailbox"], ["add_alias", "admin", "alias"], ["change_alias", "admin", "alias"], ["delete_alias", "admin", "alias"], ["view_mailboxes", "admin", "mailbox"], ["view_aliases", "admin", "alias"], ["view_domaliases", "admin", "domainalias"], ["add_domainalias", "admin", "domainalias"], ["change_domainalias", "admin", "domainalias"], ["delete_domainalias", "admin", "domainalias"], ["view_domains", "admin", "domain"], ["add_domain", "admin", "domain"], ["change_domain", "admin", "domain"], ["delete_domain", "admin", "domain"]]}}] \ No newline at end of file diff --git a/modoboa/extensions/limits/forms.py b/modoboa/extensions/limits/forms.py deleted file mode 100644 index b96d10ef3..000000000 --- a/modoboa/extensions/limits/forms.py +++ /dev/null @@ -1,91 +0,0 @@ -# coding: utf-8 - -"""Custom forms.""" - -from django import forms -from django.utils.translation import ugettext as _ - -from .lib import BadLimitValue, UnsufficientResource -from .models import LimitTemplates -from modoboa.lib import parameters - - -class ResourcePoolForm(forms.Form): - - def __init__(self, *args, **kwargs): - self.account = None - if "instance" in kwargs: - 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]: - continue - self.fields[tpl[0]] = forms.IntegerField( - label=tpl[1], help_text=tpl[2] - ) - if hasattr(self, "account"): - self.load_from_user(self.account) - - def check_limit_value(self, lname): - if self.cleaned_data[lname] < -1: - raise forms.ValidationError(_("Invalid limit")) - return self.cleaned_data[lname] - - def clean(self): - cleaned_data = super(ResourcePoolForm, self).clean() - for lname in self.fields.keys(): - if lname not in self._errors and cleaned_data[lname] < -1: - self.add_error(lname, _("Invalid limit")) - return cleaned_data - - def load_from_user(self, user): - for lname in self.fields.keys(): - self.fields[lname].initial = user.limitspool.getmaxvalue(lname) - # 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()) - - def allocate_from_pool(self, limit, pool): - """Allocate resource using an existing pool. - - 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: - raise BadLimitValue(_("Your resources are not initialized yet")) - newvalue = self.cleaned_data[limit.name] - if newvalue == -1 and ol.maxvalue != -1: - raise BadLimitValue( - _("You're not allowed to define unlimited values") - ) - - if limit.maxvalue > -1: - newvalue -= limit.maxvalue - if newvalue == 0: - return - remain = ol.maxvalue - ol.curvalue - if newvalue > remain: - raise UnsufficientResource(ol) - ol.maxvalue -= newvalue - ol.save() - - def save(self): - from modoboa.lib.permissions import get_object_owner - - owner = get_object_owner(self.account) - for ltpl in LimitTemplates().templates: - if ltpl[0] not in self.cleaned_data: - continue - l = self.account.limitspool.limit_set.get(name=ltpl[0]) - if not owner.is_superuser: - self.allocate_from_pool(l, owner.limitspool) - l.maxvalue = self.cleaned_data[ltpl[0]] - l.save() diff --git a/modoboa/extensions/limits/general_callbacks.py b/modoboa/extensions/limits/general_callbacks.py deleted file mode 100644 index 3f207eebc..000000000 --- a/modoboa/extensions/limits/general_callbacks.py +++ /dev/null @@ -1,123 +0,0 @@ -"""General event callbacks.""" - -from django.utils.translation import ugettext as _ - -from modoboa.core import PERMISSIONS as CORE_PERMS -from modoboa.extensions.admin.modo_extension import PERMISSIONS as ADMIN_PERMS -from modoboa.extensions.limits.forms import ResourcePoolForm -from modoboa.extensions.limits.models import LimitTemplates -from modoboa.lib import events - - -PERMISSIONS = { - "Resellers": ( - CORE_PERMS.get("DomainAdmins") + - ADMIN_PERMS.get("DomainAdmins") + - [["admin", "domain", "view_domains"], - ["admin", "domain", "add_domain"], - ["admin", "domain", "change_domain"], - ["admin", "domain", "delete_domain"]] - ) -} - - -@events.observe("GetExtraRoles") -def get_extra_roles(user): - if user.is_superuser: - return [("Resellers", _("Reseller")), ] - return [] - - -@events.observe("GetExtraRolePermissions") -def extra_permissions(rolename): - """Return extra permissions for Resellers.""" - return PERMISSIONS.get(rolename, []) - - -@events.observe("ExtraAdminContent") -def display_pool_usage(user, target, currentpage): - from django.template.loader import render_to_string - - if target != "leftcol" or user.is_superuser: - return [] - if currentpage == "identities": - names = ["mailboxes_limit", "mailbox_aliases_limit"] - if user.has_perm("admin.add_domain"): - names += ["domain_admins_limit"] - else: - 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) - ] - - limits = user.limitspool.limit_set.filter(name__in=names, maxvalue__gt=0) - if len(limits) == 0: - return [] - return [render_to_string("limits/poolusage.html", dict(limits=limits))] - - -@events.observe("ExtraAccountForm") -def extra_account_form(user, account=None): - if user.group not in ["SuperAdmins", "Resellers"]: - return [] - if account is not None and \ - account.group not in ["Resellers", "DomainAdmins"]: - return [] - - return [ - dict( - id="resources", title=_("Resources"), cls=ResourcePoolForm - ) - ] - - -@events.observe("CheckExtraAccountForm") -def check_form_access(account, form): - if form["id"] != "resources": - return [True] - if not account.belongs_to_group("Resellers") and \ - not account.belongs_to_group("DomainAdmins"): - return [False] - return [True] - - -@events.observe("FillAccountInstances") -def fill_account_instances(user, account, instances): - if not user.is_superuser and not user.belongs_to_group("Resellers"): - return - if not account.belongs_to_group("Resellers") and \ - not account.belongs_to_group("DomainAdmins"): - return - instances["resources"] = account - - -@events.observe("GetStaticContent") -def get_static_content(caller, st_type, user): - if caller not in ['domains', 'identities']: - return [] - if user.group == "SimpleUsers": - return [] - if st_type == "css": - return [""" -"""] - return [""" - -"""] diff --git a/modoboa/extensions/limits/lib.py b/modoboa/extensions/limits/lib.py deleted file mode 100644 index c0135a234..000000000 --- a/modoboa/extensions/limits/lib.py +++ /dev/null @@ -1,53 +0,0 @@ -# coding: utf-8 -""" -:mod:`lib` --- public functions -------------------------------- - -""" -from django.utils.translation import ugettext as _ - -from modoboa.extensions.limits.models import LimitsPool -from modoboa.lib.exceptions import ModoboaException - - -class LimitReached(ModoboaException): - http_code = 403 - - def __init__(self, limit): - self.limit = limit - - def __str__(self): - return _("%s: limit reached") % self.limit.label - - -class UnsufficientResource(ModoboaException): - http_code = 424 - - def __init__(self, limit): - self.limit = limit - - def __str__(self): - return _("Not enough resources") - - -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 diff --git a/modoboa/extensions/limits/migrations/0001_initial.py b/modoboa/extensions/limits/migrations/0001_initial.py deleted file mode 100644 index 82ed516e3..000000000 --- a/modoboa/extensions/limits/migrations/0001_initial.py +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import models, migrations -from django.conf import settings - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='Limit', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('name', models.CharField(max_length=255)), - ('curvalue', models.IntegerField(default=0)), - ('maxvalue', models.IntegerField(default=-2)), - ], - options={ - }, - bases=(models.Model,), - ), - migrations.CreateModel( - name='LimitsPool', - fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL)), - ], - options={ - }, - bases=(models.Model,), - ), - migrations.AddField( - model_name='limit', - name='pool', - field=models.ForeignKey(to='limits.LimitsPool'), - preserve_default=True, - ), - migrations.AlterUniqueTogether( - name='limit', - unique_together=set([('name', 'pool')]), - ), - ] diff --git a/modoboa/extensions/limits/migrations/__init__.py b/modoboa/extensions/limits/migrations/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/modoboa/extensions/limits/models.py b/modoboa/extensions/limits/models.py deleted file mode 100644 index 60ced9325..000000000 --- a/modoboa/extensions/limits/models.py +++ /dev/null @@ -1,148 +0,0 @@ -# coding: utf-8 - -"""Models for the limits extensions.""" - -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 - - -class LimitTemplates(Singleton): - - 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) - - class Meta: - unique_together = (("name", "pool"),) - - @property - def usage(self): - if self.maxvalue < 0: - return -1 - if self.maxvalue == 0: - return 0 - return int(float(self.curvalue) / self.maxvalue * 100) - - @property - def label(self): - for l in LimitTemplates().templates: - if l[0] == self.name: - return l[1] - return "" - - def __str__(self): - if self.maxvalue == -2: - return _("undefined") - if self.maxvalue == -1: - return _("unlimited") - return "%d%%" % self.usage diff --git a/modoboa/extensions/limits/modo_extension.py b/modoboa/extensions/limits/modo_extension.py deleted file mode 100644 index 45a8659a6..000000000 --- a/modoboa/extensions/limits/modo_extension.py +++ /dev/null @@ -1,44 +0,0 @@ -# coding: utf-8 - -""" -The *limits* extension ----------------------- -""" -from django.utils.translation import ugettext_lazy - -from modoboa.core.extensions import ModoExtension, exts_pool -from modoboa.core.models import User -from modoboa.lib import events, parameters -from .models import LimitTemplates, LimitsPool, Limit - - -EVENTS = [ - 'GetExtraLimitTemplates' -] - - -class Limits(ModoExtension): - name = "limits" - label = "Limits" - version = "1.0" - description = ugettext_lazy( - "Per administrator resources to limit the number of objects " - "they can create" - ) - - def load(self): - from modoboa.extensions.limits.app_settings import ParametersForm - from modoboa.extensions.limits import controls - - parameters.register(ParametersForm, ugettext_lazy("Limits")) - events.declare(EVENTS) - from modoboa.extensions.limits import general_callbacks - - def load_initial_data(self): - """Create pools for existing accounts.""" - for user in User.objects.filter(groups__name="DomainAdmins"): - pool, created = LimitsPool.objects.get_or_create(user=user) - for tpl in LimitTemplates().templates: - Limit.objects.get_or_create(name=tpl[0], pool=pool, maxvalue=0) - -exts_pool.register_extension(Limits) diff --git a/modoboa/extensions/limits/templates/limits/poolusage.html b/modoboa/extensions/limits/templates/limits/poolusage.html deleted file mode 100644 index 1665b6d31..000000000 --- a/modoboa/extensions/limits/templates/limits/poolusage.html +++ /dev/null @@ -1,12 +0,0 @@ -{% load i18n lib_tags %} -
diff --git a/modoboa/extensions/limits/tests.py b/modoboa/extensions/limits/tests.py deleted file mode 100644 index 69b737714..000000000 --- a/modoboa/extensions/limits/tests.py +++ /dev/null @@ -1,394 +0,0 @@ -"""Test cases for the limits extension.""" - -from django.core.urlresolvers import reverse - -from modoboa.core.factories import UserFactory -from modoboa.core.models import User -from modoboa.extensions.admin.factories import ( - populate_database -) -from modoboa.extensions.admin.models import Alias, Domain -from modoboa.extensions.limits.models import LimitTemplates -from modoboa.lib import parameters -from modoboa.lib.tests import ModoTestCase - - -class PermissionsTestCase(ModoTestCase): - fixtures = ["initial_users.json"] - - def setUp(self): - super(PermissionsTestCase, self).setUp() - populate_database() - - def test_domainadmin_deletes_reseller(self): - """Check if a domain admin can delete a reseller. - - Expected result: no. - """ - values = dict( - username="reseller@test.com", first_name="Reseller", last_name="", - password1="toto", password2="toto", role="Resellers", - is_active=True, email="reseller@test.com", stepid='step2' - ) - self.ajax_post(reverse("admin:account_add"), values) - account = User.objects.get(username="reseller@test.com") - self.clt.logout() - self.clt.login(username="admin@test.com", password="toto") - resp = self.ajax_post( - reverse("admin:account_delete", args=[account.id]), - {}, status=403 - ) - self.assertEqual(resp, "Permission denied") - - -class ResourceTestCase(ModoTestCase): - fixtures = ["initial_users.json"] - - def setUp(self): - """Custom setUp method. - - The 'limits' is manually loaded to ensure extra parameters - provided by 'postfix_relay_domains' are properly received. - """ - super(ResourceTestCase, self).setUp() - for tpl in LimitTemplates().templates: - parameters.save_admin('DEFLT_%s' % tpl[0].upper(), 2, app='limits') - populate_database() - - def _create_account(self, username, role='SimpleUsers', status=200): - values = dict( - username=username, first_name="Tester", last_name="Toto", - password1="toto", password2="toto", role=role, - quota_act=True, - is_active=True, email=username, stepid='step2', - ) - return self.ajax_post( - reverse("admin:account_add"), values, status - ) - - def _create_alias(self, email, rcpt='user@test.com', status=200): - values = dict( - email=email, recipients=rcpt, enabled=True - ) - return self.ajax_post( - reverse("admin:alias_add"), values, status - ) - - def _create_domain(self, name, status=200, withtpl=False): - values = { - "name": name, "quota": 100, "create_dom_admin": "no", - "create_aliases": "no", "stepid": 'step2' - } - if withtpl: - values['create_dom_admin'] = 'yes' - values['dom_admin_username'] = 'admin' - values['create_aliases'] = 'yes' - return self.ajax_post( - reverse("admin:domain_add"), values, status - ) - - def _domain_alias_operation(self, optype, domain, name, status=200): - dom = Domain.objects.get(name=domain) - values = { - 'name': dom.name, 'quota': dom.quota, 'enabled': dom.enabled, - } - aliases = [alias.name for alias in dom.domainalias_set.all()] - if optype == 'add': - aliases.append(name) - else: - aliases.remove(name) - for cpt, alias in enumerate(aliases): - fname = 'aliases' if not cpt else 'aliases_%d' % cpt - values[fname] = alias - self.ajax_post( - reverse("admin:domain_change", args=[dom.id]), - values, status - ) - - 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) - - -class DomainAdminTestCase(ResourceTestCase): - - def setUp(self): - super(DomainAdminTestCase, self).setUp() - self.user = User.objects.get(username='admin@test.com') - self.user.limitspool.set_maxvalue('mailboxes_limit', 2) - self.user.limitspool.set_maxvalue('mailbox_aliases_limit', 2) - self.clt.logout() - self.clt.login(username='admin@test.com', password='toto') - - def test_mailboxes_limit(self): - self._create_account('tester1@test.com') - self._check_limit('mailboxes', 1, 2) - self._create_account('tester2@test.com') - self._check_limit('mailboxes', 2, 2) - resp = self._create_account('tester3@test.com', status=403) - self._check_limit('mailboxes', 2, 2) - self.ajax_post( - reverse('admin:account_delete', - args=[User.objects.get(username='tester2@test.com').id]), - {} - ) - self._check_limit('mailboxes', 1, 2) - - def test_aliases_limit(self): - self._create_alias('alias1@test.com') - self._check_limit('mailbox_aliases', 1, 2) - self._create_alias('alias2@test.com') - self._check_limit('mailbox_aliases', 2, 2) - resp = self._create_alias('alias3@test.com', status=403) - self._check_limit('mailbox_aliases', 2, 2) - self.ajax_post( - reverse('admin:alias_delete') + '?selection=%d' - % Alias.objects.get(address='alias2', domain__name='test.com').id, - {} - ) - self._check_limit('mailbox_aliases', 1, 2) - - def test_aliases_limit_through_account_form(self): - user = User.objects.get(username='user@test.com') - values = dict( - username=user.username, role=user.group, - is_active=user.is_active, email=user.email, quota_act=True, - aliases="alias1@test.com", aliases_1="alias2@test.com" - ) - self.ajax_post( - reverse("admin:account_change", args=[user.id]), - values - ) - Alias.objects.get(address='alias1', domain__name='test.com') - self._check_limit('mailbox_aliases', 2, 2) - - -class ResellerTestCase(ResourceTestCase): - - def setUp(self): - super(ResellerTestCase, self).setUp() - self.user = UserFactory.create( - username='reseller', groups=('Resellers',) - ) - self.clt.logout() - self.clt.login(username='reseller', password='toto') - - def test_domains_limit(self): - self._create_domain('domain1.tld') - self._check_limit('domains', 1, 2) - self._create_domain('domain2.tld') - self._check_limit('domains', 2, 2) - resp = self._create_domain('domain3.tld', 403) - self._check_limit('domains', 2, 2) - self.ajax_post( - reverse('admin:domain_delete', - args=[Domain.objects.get(name='domain2.tld').id]), - {} - ) - self._check_limit('domains', 1, 2) - - def test_domain_aliases_limit(self): - self._create_domain('pouet.com') - self._domain_alias_operation('add', 'pouet.com', 'domain_alias1.tld') - self._check_limit('domain_aliases', 1, 2) - self._domain_alias_operation('add', 'pouet.com', 'domain_alias2.tld') - self._check_limit('domain_aliases', 2, 2) - resp = self._domain_alias_operation( - 'add', 'pouet.com', 'domain_alias3.tld', 403 - ) - self._check_limit('domain_aliases', 2, 2) - self._domain_alias_operation('delete', 'pouet.com', 'domain_alias2.tld') - self._check_limit('domain_aliases', 1, 2) - - def test_domain_admins_limit(self): - self._create_domain('domain.tld') - self._create_account('admin1@domain.tld', role='DomainAdmins') - self._check_limit('domain_admins', 1, 2) - self._create_account('admin2@domain.tld', role='DomainAdmins') - self._check_limit('domain_admins', 2, 2) - resp = self._create_account( - 'admin3@domain.tld', - role='DomainAdmins', - status=400) - self.assertEqual( - resp['form_errors']['role'][0], - 'Select a valid choice. DomainAdmins is not one of the available ' - 'choices.' - ) - self._check_limit('domain_admins', 2, 2) - - self.user.limitspool.set_maxvalue('mailboxes_limit', 3) - self._create_account('user1@domain.tld') - user = User.objects.get(username='user1@domain.tld') - values = { - 'username': user.username, 'role': 'DomainAdmins', - 'quota_act': True, 'is_active': user.is_active, - 'email': user.email - } - resp = self.ajax_post( - reverse("admin:account_change", args=[user.id]), - values, status=400 - ) - self.assertEqual( - resp['form_errors']['role'][0], - 'Select a valid choice. DomainAdmins is not one of the available ' - 'choices.' - ) - self._check_limit('domain_admins', 2, 2) - - def test_domain_admin_resource_are_empty(self): - self._create_domain('domain.tld') - self._create_account('admin1@domain.tld', role='DomainAdmins') - 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 - ) - - def test_domain_admins_limit_from_domain_tpl(self): - self.user.limitspool.set_maxvalue('domains_limit', 3) - self._create_domain('domain1.tld', withtpl=True) - self._create_domain('domain2.tld', withtpl=True) - self._check_limit('domain_admins', 2, 2) - self._check_limit('domains', 2, 3) - resp = self._create_domain('domain3.tld', status=200, withtpl=True) - self._check_limit('domain_admins', 2, 2) - self._check_limit('domains', 3, 3) - - def test_reseller_deletes_domain(self): - """Check if all resources are restored after the deletion. - """ - self._create_domain('domain.tld', withtpl=True) - dom = Domain.objects.get(name="domain.tld") - self.ajax_post( - reverse("admin:domain_delete", args=[dom.id]), - {} - ) - self._check_limit('domains', 0, 2) - self._check_limit('domain_admins', 1, 2) - self._check_limit('mailboxes', 0, 2) - self._check_limit('mailbox_aliases', 0, 2) - - def test_sadmin_removes_ownership(self): - self._create_domain('domain.tld', withtpl=True) - dom = Domain.objects.get(name="domain.tld") - self.clt.logout() - self.clt.login(username='admin', password='password') - self.ajax_get( - "{0}?domid={1}&daid={2}".format( - reverse('admin:permission_remove'), dom.id, self.user.id - ), {} - ) - self._check_limit('domains', 0, 2) - self._check_limit('domain_admins', 0, 2) - self._check_limit('mailboxes', 0, 2) - self._check_limit('mailbox_aliases', 0, 2) - - def test_allocate_from_pool(self): - self._create_domain('domain.tld') - self._create_account('admin1@domain.tld', role='DomainAdmins') - user = User.objects.get(username='admin1@domain.tld') - - # Give 1 mailbox and 2 aliases to the admin -> should work - values = { - 'username': user.username, 'role': user.group, 'quota_act': True, - 'is_active': user.is_active, 'email': user.email, - 'mailboxes_limit': 1, 'mailbox_aliases_limit': 2 - } - self.ajax_post( - reverse("admin:account_change", args=[user.id]), - values - ) - self._check_limit('mailboxes', 1, 1) - self._check_limit('mailbox_aliases', 0, 0) - - # Delete the admin -> resources should go back to the - # reseller's pool - self.ajax_post( - reverse("admin:account_delete", args=[user.id]), - {} - ) - self._check_limit('mailboxes', 0, 2) - self._check_limit('mailbox_aliases', 0, 2) - - def test_restore_resources(self): - self._create_domain('domain.tld') - dom = Domain.objects.get(name='domain.tld') - self._create_account('admin1@domain.tld', role='DomainAdmins') - user = User.objects.get(username='admin1@domain.tld') - values = { - 'username': user.username, 'role': user.group, 'quota_act': True, - 'is_active': user.is_active, 'email': user.email, - 'mailboxes_limit': 1, 'mailbox_aliases_limit': 2 - } - self.ajax_post( - reverse("admin:account_change", args=[user.id]), - values - ) - dom.add_admin(user) - self.clt.logout() - self.clt.login(username='admin1@domain.tld', password='toto') - self._create_account('user1@domain.tld') - self._create_alias('alias1@domain.tld', 'user1@domain.tld') - self._create_alias('alias2@domain.tld', 'user1@domain.tld') - self.clt.logout() - self.clt.login(username='reseller', password='toto') - # Delete the admin -> resources should go back to the - # reseller's pool - self.ajax_post( - reverse("admin:account_delete", args=[user.id]), - {} - ) - self._check_limit('mailboxes', 1, 2) - self._check_limit('mailbox_aliases', 2, 2) - - def test_change_role(self): - self._create_domain('domain.tld') - self._create_account('admin1@domain.tld', role='DomainAdmins') - user = User.objects.get(username='admin1@domain.tld') - - # Give 1 mailbox and 2 aliases to the admin -> should work - values = { - 'username': user.username, 'role': user.group, 'quota_act': True, - 'is_active': user.is_active, 'email': user.email, - 'mailboxes_limit': 1, 'mailbox_aliases_limit': 2 - } - self.ajax_post( - reverse("admin:account_change", args=[user.id]), - values - ) - self._check_limit('mailboxes', 1, 1) - self._check_limit('mailbox_aliases', 0, 0) - - # Change admin role to SimpleUser -> resources should go back - # to the reseller. - values = { - 'username': user.username, 'role': 'SimpleUsers', 'quota_act': True, - 'is_active': user.is_active, 'email': user.email, - } - self.ajax_post( - reverse("admin:account_change", args=[user.id]), - values - ) - self._check_limit('mailboxes', 1, 2) - self._check_limit('mailbox_aliases', 0, 2) - - def test_allocate_too_much(self): - self._create_domain('domain.tld') - self._create_account('admin1@domain.tld', role='DomainAdmins') - user = User.objects.get(username='admin1@domain.tld') - - # Give 2 mailboxes and 3 aliases to the admin -> should fail. - values = { - 'username': user.username, 'role': user.group, 'quota_act': True, - 'is_active': user.is_active, 'email': user.email, - 'mailboxes_limit': 2, 'mailbox_aliases_limit': 3 - } - resp = self.ajax_post( - reverse("admin:account_change", args=[user.id]), - values, 424 - ) - self.assertEqual(resp, 'Not enough resources') - self._check_limit('mailboxes', 1, 2) - self._check_limit('mailbox_aliases', 0, 2) diff --git a/modoboa/extensions/limits/urls.py b/modoboa/extensions/limits/urls.py deleted file mode 100644 index 9adc5e04a..000000000 --- a/modoboa/extensions/limits/urls.py +++ /dev/null @@ -1,6 +0,0 @@ -from django.conf.urls import patterns - -urlpatterns = patterns( - 'modoboa.extensions.limits.views', - -) diff --git a/modoboa/extensions/limits/views.py b/modoboa/extensions/limits/views.py deleted file mode 100644 index 57d631c3f..000000000 --- a/modoboa/extensions/limits/views.py +++ /dev/null @@ -1 +0,0 @@ -# coding: utf-8 diff --git a/modoboa/extensions/stats/__init__.py b/modoboa/extensions/stats/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/modoboa/extensions/stats/app_settings.py b/modoboa/extensions/stats/app_settings.py deleted file mode 100644 index 9c48d156e..000000000 --- a/modoboa/extensions/stats/app_settings.py +++ /dev/null @@ -1,25 +0,0 @@ -# coding: utf-8 -from django.utils.translation import ugettext_lazy -from django import forms -from modoboa.lib.parameters import AdminParametersForm -from modoboa.lib.form_utils import SeparatorField - - -class ParametersForm(AdminParametersForm): - app = "stats" - - general_sep = SeparatorField(label=ugettext_lazy("General")) - - logfile = forms.CharField( - label=ugettext_lazy("Path to the log file"), - initial="/var/log/mail.log", - help_text=ugettext_lazy("Path to log file used to collect statistics"), - widget=forms.TextInput(attrs={"class": "form-control"}) - ) - - rrd_rootdir = forms.CharField( - label=ugettext_lazy("Directory to store RRD files"), - initial="/tmp/modoboa", - help_text=ugettext_lazy("Path to directory where RRD files are stored"), - widget=forms.TextInput(attrs={"class": "form-control"}) - ) diff --git a/modoboa/extensions/stats/general_callbacks.py b/modoboa/extensions/stats/general_callbacks.py deleted file mode 100644 index e6112736d..000000000 --- a/modoboa/extensions/stats/general_callbacks.py +++ /dev/null @@ -1,22 +0,0 @@ -from django.core.urlresolvers import reverse -from django.utils.translation import ugettext as _ -from modoboa.lib import events - - -@events.observe("UserMenuDisplay") -def menu(target, user): - if target != "top_menu" or user.group == "SimpleUsers": - return [] - return [ - {"name": "stats", - "label": _("Statistics"), - "url": reverse('stats:fullindex')} - ] - - -@events.observe("GetGraphSets") -def get_default_graphic_sets(): - from modoboa.extensions.stats.graphics import MailTraffic - - gset = MailTraffic() - return {gset.html_id: gset} diff --git a/modoboa/extensions/stats/graphics.py b/modoboa/extensions/stats/graphics.py deleted file mode 100644 index 80ca0af88..000000000 --- a/modoboa/extensions/stats/graphics.py +++ /dev/null @@ -1,161 +0,0 @@ -import os -import inspect -from lxml import etree -from django.utils.translation import ugettext_lazy -from modoboa.lib import parameters -from modoboa.lib.sysutils import exec_cmd - - -class Curve(object): - - """Graphic curve. - - Simple way to represent a graphic curve. - """ - - def __init__(self, dsname, color, legend, cfunc="AVERAGE"): - """Constructor. - """ - self.dsname = dsname - self.color = color - self.legend = legend.encode("utf-8") - self.cfunc = cfunc - - def to_rrd_command_args(self, rrdfile): - """Convert this curve to the approriate RRDtool command. - - :param str rrdfile: RRD file name - :return: a list - """ - rrdfile = os.path.join( - parameters.get_admin("RRD_ROOTDIR"), "%s.rrd" % rrdfile - ) - return [ - 'DEF:%s=%s:%s:%s' % - (self.dsname, rrdfile, self.dsname, self.cfunc), - 'CDEF:%(ds)spm=%(ds)s,60,*' % {"ds": self.dsname}, - 'XPORT:%spm:"%s"' % (self.dsname, self.legend.decode("utf-8")) - ] - - -class Graphic(object): - - """Graphic. - - """ - - def __init__(self): - """Constructor. - """ - self._curves = [] - try: - order = getattr(self, "order") - except AttributeError: - for member in inspect.getmembers(self): - if isinstance(member[1], Curve): - self._curves += [member[1]] - else: - for name in order: - try: - curve = getattr(self, name) - except AttributeError: - continue - if not isinstance(curve, Curve): - continue - self._curves += [curve] - - @property - def display_name(self): - return self.__class__.__name__.lower() - - def export(self, rrdfile, start, end): - """ - """ - result = [] - cmdargs = [] - for curve in self._curves: - result += [{ - "name": curve.legend, "color": curve.color, "data": [] - }] - cmdargs += curve.to_rrd_command_args(rrdfile) - cmd = "rrdtool xport --start %s --end %s " % (str(start), str(end)) - cmd += " ".join(cmdargs) - if isinstance(cmd, unicode): - cmd = cmd.encode("utf-8") - code, output = exec_cmd(cmd) - if code: - return [] - tree = etree.fromstring(output) - for row in tree.xpath('/xport/data/row'): - timestamp = int(row.find('t').text) - for vindex, value in enumerate(row.findall('v')): - if value.text == 'NaN': - result[vindex]['data'].append({'x': timestamp, 'y': 0}) - else: - result[vindex]['data'].append( - {'x': timestamp, 'y': float(value.text)} - ) - return result - - -class AverageTraffic(Graphic): - - """Average traffic. - """ - title = ugettext_lazy('Average traffic (msgs/min)') - - # Curve definitions - sent = Curve("sent", "lawngreen", ugettext_lazy("sent messages")) - recv = Curve("recv", "steelblue", ugettext_lazy("received messages")) - bounced = Curve("bounced", "yellow", ugettext_lazy("bounced messages")) - reject = Curve("reject", "tomato", ugettext_lazy("rejected messages")) - - order = ['reject', 'bounced', 'recv', 'sent'] - - -class AverageTrafficSize(Graphic): - - """Average traffic size. - """ - title = ugettext_lazy('Average normal traffic size (bytes/min)') - - # Curve definitions - size_recv = Curve("size_recv", "orange", ugettext_lazy("received size")) - size_sent = Curve( - "size_sent", "mediumturquoise", ugettext_lazy("sent size") - ) - - -class GraphicSet(object): - title = None - _graphics = [] - - def __init__(self): - self.__ginstances = [] - - @property - def html_id(self): - return self.__class__.__name__.lower() - - @property - def graphics(self): - if not self.__ginstances: - self.__ginstances = [graphic() for graphic in self._graphics] - return self.__ginstances - - def get_graphic_names(self): - return [graphic.display_name for graphic in self._graphics] - - def export(self, rrdfile, start, end): - result = {} - for graph in self.graphics: - result[graph.display_name] = { - "title": graph.title.encode("utf-8"), - "curves": graph.export(rrdfile, start, end) - } - return result - - -class MailTraffic(GraphicSet): - title = ugettext_lazy('Mail traffic') - _graphics = [AverageTraffic, AverageTrafficSize] diff --git a/modoboa/extensions/stats/lib.py b/modoboa/extensions/stats/lib.py deleted file mode 100644 index 9d78c039f..000000000 --- a/modoboa/extensions/stats/lib.py +++ /dev/null @@ -1,24 +0,0 @@ -# coding: utf-8 -import sys -import time - - -def date_to_timestamp(timetuple): - """Date conversion. - - Returns a date and a time in seconds from the epoch. - - :param list timetuple: list containing date - :return: an integer - """ - date = " ".join( - [("%d" % elem) if isinstance(elem, int) else elem for elem in timetuple] - ) - fmt = "%Y %m %d %H %M %S" \ - if timetuple[1].isdigit() else "%Y %b %d %H %M %S" - try: - local = time.strptime(date, fmt) - except ValueError: - print >> sys.stderr, "Error: failed to convert date and time" - return 0 - return int(time.mktime(local)) diff --git a/modoboa/extensions/stats/management/__init__.py b/modoboa/extensions/stats/management/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/modoboa/extensions/stats/management/commands/__init__.py b/modoboa/extensions/stats/management/commands/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/modoboa/extensions/stats/management/commands/logparser.py b/modoboa/extensions/stats/management/commands/logparser.py deleted file mode 100644 index 1f38ff6fa..000000000 --- a/modoboa/extensions/stats/management/commands/logparser.py +++ /dev/null @@ -1,378 +0,0 @@ -#!/usr/bin/env python -# coding: utf-8 -""" -Postfix log parser. - -This scripts parses a log file produced by postfix (or using the same -format). It looks for predefined events and build statistics about -their occurence rate. - -At the, somes default graphics are generated using the grapher module. -(see grapher.py) - -Predefined events are: - * Per domain sent/received messages, - * Per domain received bad messages (bounced, reject for now), - * Per domain sent/received traffics size, - * Global consolidation of all previous events. - -""" -import time -import sys -import os -import re -import string -from optparse import make_option - -import rrdtool - -from django.core.management.base import BaseCommand - -from modoboa.core.extensions import exts_pool -from modoboa.core.management.commands import CloseConnectionMixin -from modoboa.extensions.admin.models import Domain -from modoboa.extensions.stats.lib import date_to_timestamp -from modoboa.lib import parameters - -rrdstep = 60 -xpoints = 540 -points_per_sample = 3 -variables = ["sent", "recv", "bounced", "reject", "spam", "virus", - "size_sent", "size_recv"] - - -class LogParser(object): - - def __init__(self, options, workdir, year=None): - """Constructor - """ - self.logfile = options["logfile"] - self.debug = options["debug"] - self.verbose = options["verbose"] - try: - self.f = open(self.logfile) - except IOError as errno: - self._dprint("%s" % errno) - sys.exit(1) - self.workdir = workdir - self.__year = year - self.cfs = ['AVERAGE', 'MAX'] - - curtime = time.localtime() - if not self.__year: - self.__year = curtime.tm_year - self.curmonth = curtime.tm_mon - - self.data = {} - self.domains = [] - self._load_domain_list() - self.data["global"] = {} - - self.workdict = {} - self.lupdates = {} - self._s_date_expr = \ - re.compile( - r"(?P" + d.name + ": " + point.y.toFixed(3) + "
"; - - var tracker_x = x(point.x * 1000), - tracker_y = y(point.y); - - focus.selectAll(".tracker-" + cid) - .style("fill", d.color) - .attr("cx", tracker_x) - .attr("cy", tracker_y); - }); - if (mousex + tooltip[0][0].clientWidth >= width - 2 * margin.left) { - var left = mousex - (tooltip[0][0].clientWidth - (margin.left - 20)); - tooltip.style("left", left + "px"); - } else { - tooltip.style("left", (mousex + margin.left + 20) + "px"); - } - tooltip.html("" + date + "
" + text); - }).on("mouseout", function(d) { - tooltip.style("display", "none"); - line.style("display", "none"); - focus.selectAll(".tracker").style("display", "none"); - }); - - var legend = focus.append("g") - .attr("class", "legend") - .attr("transform", "translate(50,10)") - .style("font-size", "0.8em") - .call(d3.legend); - - curve2.append("path") - .attr("class", "area2") - .attr("d", function(d) { return area2(d.data); }) - .attr("stroke", function(d) { return d.color; }) - .attr("stroke-width", "2px") - .attr("stroke-opacity", 0.8) - .attr("data-legend", function(d) { return d.name; }) - .style("fill", function(d) { return d.color; }); - - context.append("g") - .attr("class", "x axis") - .attr("transform", "translate(0," + preview_height + ")") - .call(xAxis2); - - context.append("g") - .attr("class", "grid") - .attr("transform", "translate(0," + preview_height + ")") - .call(make_preview_x_axis); - - context.append("g") - .attr("class", "x brush") - .call(brush) - .selectAll("rect") - .attr("y", -6) - .attr("height", preview_height + brush_height); - - function make_x_axis() { - return d3.svg.axis() - .scale(x) - .orient("bottom") - .ticks(6) - .tickSize(-height, 0, 0) - .tickFormat(""); - } - - function make_y_axis() { - return d3.svg.axis() - .scale(y) - .orient("left") - .ticks(6) - .tickSize(-(width - 2 * margin.left), 0, 0) - .tickFormat(""); - } - - function make_preview_x_axis() { - return d3.svg.axis() - .scale(x2) - .orient("bottom") - .ticks(6) - .tickSize(-preview_height, 0, 0) - .tickFormat(""); - } - - function brushed() { - x.domain(brush.empty() ? x2.domain() : brush.extent()); - focus.selectAll(".area") - .attr("d", function(d) { return area(d.data); }); - focus.select(".x.axis").call(xAxis); - focus.select(".grid").call(xrules); - } - } - - /** - * Set or get chart's width. - * - * @this {ModoChart} - * @param {integer} value new width. - * @return {number} current width. - */ - my.width = function(value) { - if (!arguments.length) return width; - width = value; - return my; - }; - - /** - * Set or get chart's height. - * - * @this {ModoChart} - * @param {integer} value new height. - * @return {number} current height. - */ - my.height = function(value) { - if (!arguments.length) return height; - height = value; - return my; - }; - - /** - * Set or get chart's margin. - * - * @this {ModoChart} - * @param {Object} value new margin. - * @return {Object} current margin. - */ - my.margin = function(value) { - if (!arguments.length) return margin; - margin = value; - return my; - }; - - /** - * Compute and return chart's total height. - * - * @this {ModoChart} - * @return {integer} total height. - */ - my.totalHeight = function() { - return height + 2 * margin.top - + 2 * margin.bottom + preview_height - + brush_height; - }; - - /** - * Update the chart with new data. - * - * @this {ModoChart} - * @param {Object} data new data - */ - my.update = function(data) { - container.select("svg").remove(); - $(container[0][0]).empty(); - my(data); - }; - - return my; -} diff --git a/modoboa/extensions/stats/static/stats/js/stats.js b/modoboa/extensions/stats/static/stats/js/stats.js deleted file mode 100644 index 5258aecd1..000000000 --- a/modoboa/extensions/stats/static/stats/js/stats.js +++ /dev/null @@ -1,176 +0,0 @@ -/** - * Create an instance of Stats. - * - * @constructor - */ -var Stats = function(options) { - this.initialize(options); -}; - -Stats.prototype = { - constructor: Stats, - - defaults: { - deflocation: "graphs/", - language: "en", - domain_list_url: null - }, - - initialize: function(options) { - this.options = $.extend({}, this.defaults, options); - this.options.defcallback = $.proxy(this.charts_cb, this); - - var navobj = new History(this.options); - this.navobj = navobj; - - if (navobj.params.searchquery !== undefined) { - $("#searchquery").val(navobj.params.searchquery); - } - $("#searchquery").focus(function() { - var $this = $(this); - if ($this.val() === undefined) { - return; - } - $this.data("oldvalue", $this.val()); - $this.val(""); - }).blur(function() { - var $this = $(this); - if ($this.val() === "") { - if ($this.data("oldvalue")) { - $this.val($this.data('oldvalue')); - $this.data("oldvalue", null); - } else { - $this.val(gettext("Search a domain")); - } - } - }); - if (navobj.params.start) { - $("#id_from").val(navobj.params.start); - } - if (navobj.params.end) { - $("#id_to").val(navobj.params.end); - } - if (navobj.params.period) { - $("input[data-period={0}]".format(navobj.params.period)) - .attr("checked", true) - .parent().addClass("active"); - if (navobj.params.period == "custom") { - $("#custom_period").removeClass("hidden"); - } - } - $("#custom-period .datetime_picker").datetimepicker({ - format: 'YYYY-MM-DD hh:mm:ss', - language: this.options.language - }); - $("#searchquery").autocompleter({ - choices: $.proxy(this.get_domain_list, this), - choice_selected: $.proxy(this.search_domain, this), - empty_choice: $.proxy(this.reset_search, this) - }); - - this.register_nav_callbacks(); - this.listen(); - }, - - listen: function() { - $(".period_selector").click($.proxy(this.change_period, this)); - $("#customsend").on("click", $.proxy(this.custom_period, this)); - $(window).resize($.proxy(this.resize_charts, this)); - }, - - register_nav_callbacks: function() { - this.navobj.register_callback("graphs", - $.proxy(this.charts_cb, this)); - }, - - /** - * Retrieve a list of domain from the server. - */ - get_domain_list: function() { - var result; - - $.ajax({ - url: this.options.domain_list_url, - dataType: "json", - async: false - }).done(function(data) { - result = data; - }); - return result; - }, - - change_period: function(e) { - e.preventDefault(); - var $link = $(e.target).children("input"); - var period = $link.attr("data-period"); - - if (period != "custom") { - $("#custom_period").addClass("hidden"); - this.navobj.delparam("start").delparam("end"); - this.navobj.setparam("period", $link.attr("data-period")).update(); - } else { - $("#custom_period").removeClass("hidden"); - } - }, - - /** - * Update all charts on resize event. - * - * @this {Stats} - */ - resize_charts: function() { - var data = this.data; - $.each(this.charts, function(id, mychart) { - mychart.update(data.graphs[id]); - }); - }, - - /** - * Create or update charts. - * - * @this {Stats} - * @param {Object} data - */ - charts_cb: function(data) { - var menuid = "menu_" + this.navobj.getparam("gset"); - - $("#" + menuid).addClass("active"); - this.data = data; - if (this.charts !== undefined) { - $.each(this.charts, function(id, mychart) { - mychart.update(data.graphs[id]); - }); - } else { - this.charts = {}; - $.each(data.graphs, $.proxy(function(id, graphdef) { - var mychart = ModoChart("#gset"); - this.charts[id] = mychart; - mychart(graphdef); - }, this)); - } - }, - - search_domain: function(value) { - this.navobj.setparam("searchquery", value).update(); - }, - - reset_search: function() { - $("#searchquery").data("oldvalue", null); - this.navobj.delparam("searchquery").update(); - }, - - custom_period: function(e) { - e.preventDefault(); - var $fromdate = $("#id_from"); - var $todate = $("#id_to"); - - if ($fromdate.val() === "" || $todate.val() === "") { - return; - } - this.navobj.setparams({ - period: "custom", - start: $fromdate.val(), - end: $todate.val() - }).update(); - } -}; diff --git a/modoboa/extensions/stats/templates/stats/index.html b/modoboa/extensions/stats/templates/stats/index.html deleted file mode 100644 index 08e583a8b..000000000 --- a/modoboa/extensions/stats/templates/stats/index.html +++ /dev/null @@ -1,89 +0,0 @@ -{% extends "twocols.html" %}{% load i18n %}{% load url from future %} -{% load static %} - -{% block pagetitle %}{% trans "Graphical statistics" %}{% endblock %} - -{% block extra_css %} - -{% endblock %} - -{% block extra_js %} - - - - - - - -{% endblock %} - -{% block leftcol %} - -{% endblock %} - -{% block apparea %} - - - - -