From 8dd717a3344b58711b79904240aed308a31fc7a2 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Wed, 25 Jan 2012 11:57:37 +0100 Subject: [PATCH] wip: Resellers and limits * We now make a difference between users that own a specific object and users that have access to a specific object * Limits are not moved anymore (from one reseller to a domain admin) when we grant access to a domain see #105 --- admin/forms.py | 14 +++ admin/lib.py | 105 ++++++++++++------ ...ue_objectaccess_user_content_type_obje.py} | 32 ++++-- admin/models.py | 7 +- admin/permissions.py | 12 +- admin/tables.py | 2 +- admin/templatetags/admin_extras.py | 6 + admin/urls.py | 1 + admin/views.py | 73 +++++++++--- extensions/limits/controls.py | 27 ++--- extensions/limits/models.py | 14 +++ extensions/limits/permissions.py | 6 +- extensions/stats/grapher.py | 2 +- lib/events.py | 1 + static/css/box.css | 8 ++ 15 files changed, 223 insertions(+), 87 deletions(-) rename admin/migrations/{0015_auto__add_objectowner.py => 0015_auto__add_objectaccess__add_unique_objectaccess_user_content_type_obje.py} (89%) diff --git a/admin/forms.py b/admin/forms.py index dca068fb9..6cbfb8131 100644 --- a/admin/forms.py +++ b/admin/forms.py @@ -264,6 +264,20 @@ class DomainAdminPromotionForm(forms.Form): raise forms.ValidationError(_("Unknown user")) return self.cleaned_data["name"] +class AssignDomainsForm(forms.Form): + domains = forms.ModelMultipleChoiceField( + queryset=None, + label=ugettext_noop("Domains"), + required=True, + help_text=ugettext_noop("Select one or more domains in the list") + ) + + def __init__(self, user, domadmin, *args, **kwargs): + super(AssignDomainsForm, self).__init__(*args, **kwargs) + self.fields["domains"].queryset = get_user_domains_qs(user) + self.fields["domains"].initial = get_user_domains_qs(domadmin) + + class ImportDataForm(forms.Form): sourcefile = forms.FileField(label=_("Select a file")) sepcar = forms.CharField(label=_("Separator"), max_length=1, required=False) diff --git a/admin/lib.py b/admin/lib.py index 8d4296526..a42d989ed 100644 --- a/admin/lib.py +++ b/admin/lib.py @@ -115,35 +115,59 @@ def render_listing(request, objtype, tplname="admin/listing.html", return _render(request, tplname, kwargs) -def set_object_ownership(user, obj): - """Simple shorcut to associate an object to a user +def grant_access_to_object(user, obj, is_owner=False): + """Grant access to an object for a given user + + There are two different cases where we want to grant access to an + object for a specific user: + + * He is the owner (he's just created the object) + * He is going to administrate the object (but he is not the owner) :param user: a ``User`` object :param obj: an admin. object (Domain, Mailbox, ...) + :param is_owner: the user is the unique object's owner """ try: ct = ContentType.objects.get_for_model(obj) - obj_owner = user.objectowner_set.get(content_type=ct, object_id=obj.id) - except ObjectOwner.DoesNotExist: - obj_owner = ObjectOwner(user=user, content_object=obj) - obj_owner.save() + entry = user.objectaccess_set.get(content_type=ct, object_id=obj.id) + entry.is_owner = is_owner + entry.save() + except ObjectAccess.DoesNotExist: + pass + else: + return -def unset_object_ownership(obj): - """Remove the ownership of the given object + try: + objaccess = ObjectAccess(user=user, content_object=obj, is_owner=is_owner) + objaccess.save() + except IntegrityError, e: + raise AdminError(_("Failed to grant access (%s)" % str(e))) + +def ungrant_access_to_object(obj, user=None): + """Ungrant access to an object for a specific user + + If no user is provided, all entries referencing this object are + deleted from the database. :param obj: an object inheriting from ``models.Model`` + :param user: a ``User`` object """ ct = ContentType.objects.get_for_model(obj) - ObjectOwner.objects.get(content_type=ct, object_id=obj.id).delete() + if user: + try: + ObjectAccess.objects.get(user=user, content_type=ct, object_id=obj.id).delete() + except ObjectAccess.DoesNotExist: + pass + else: + ObjectAccess.objects.filter(content_type=ct, object_id=obj.id).delete() -def is_object_owner(user, obj): - """Check if a user is the object's owner +def can_access_object(user, obj): + """Check if a user can access a specific object - An object can only belongs to one user. - - This function is recursive : if the given user is not the owner - and if he owns other ``User`` objects, we also check if one of - those users owns the object. + This function is recursive : if the given user hasn't got direct + access to this object and if he has got access other ``User`` + objects, we check if one of those users owns the object. :param user: a ``User`` object :param obj: a admin object @@ -154,35 +178,52 @@ def is_object_owner(user, obj): ct = ContentType.objects.get_for_model(obj) try: - ooentry = user.objectowner_set.get(content_type=ct, object_id=obj.id) - except ObjectOwner.DoesNotExist: + ooentry = user.objectaccess_set.get(content_type=ct, object_id=obj.id) + except ObjectAccess.DoesNotExist: pass else: return True if ct.model == "user": return False + if directowner: + return False ct = ContentType.objects.get_for_model(User) - qs = user.objectowner_set.filter(content_type=ct) + qs = user.objectaccess_set.filter(content_type=ct) for ooentry in qs.all(): if is_object_owner(ooentry.content_object, obj): return True return False +def is_object_owner(user, obj): + """Tell is the user is the unique owner of this object + + :param user: a ``User`` object + :param obj: an object inheriting from ``models.Model`` + :return: a boolean + """ + ct = ContentType.objects.get_for_model(obj) + try: + ooentry = user.objectaccess_set.get(content_type=ct, object_id=obj.id) + except ObjectAccess.DoesNotExist: + return False + return ooentry.is_owner + def get_object_owner(obj): - """Return this object's owner + """Return the unique owner of this object :param obj: an object inheriting from ``model.Model`` :return: a ``User`` object """ ct = ContentType.objects.get_for_model(obj) - res = ObjectOwner.objects.filter(content_type=ct, object_id=obj.id) - if not len(res): + try: + entry = ObjectAccess.objects.get(content_type=ct, object_id=obj.id, is_owner=True) + except ObjectAccess.DoesNotExist: return None - return res[0].user + return entry.user -def check_domain_ownership(f): - """Decorator to check if the current user can access to a domain +def check_domain_access(f): + """Decorator to check if the current user can access a domain This decorator only applies to url that contain a 'domid' or 'mbid' parameter. (for a direct access) @@ -201,7 +242,7 @@ def check_domain_ownership(f): mbid = request.GET.get("mbid", None) if mbid: domain = Mailbox.objects.get(pk=mbid).domain - if domain and not is_object_owner(request.user, domain): + if domain and not can_access_object(request.user, domain): raise PermDeniedError(_("You can't access this domain")) return f(request, **kwargs) @@ -212,13 +253,13 @@ def get_user_domains(user): return Domain.objects.all() domct = ContentType.objects.get_for_model(Domain) - qs = user.objectowner_set.filter(content_type=domct) + qs = user.objectaccess_set.filter(content_type=domct) domains = [] for entry in qs.all(): domains += [entry.content_object] userct = ContentType.objects.get_for_model(User) - qs = user.objectowner_set.filter(content_type=userct) + qs = user.objectaccess_set.filter(content_type=userct) for entry in qs: if not is_domain_admin(entry.content_object): continue @@ -235,13 +276,13 @@ def get_user_domains_qs(user): """ if user.is_superuser: return Domain.objects.all() - result = Domain.objects.filter(owners__user=user) - + result = Domain.objects.filter(owners__user=user).distinct() + userct = ContentType.objects.get_for_model(User) - for entry in user.objectowner_set.filter(content_type=userct): + for entry in user.objectaccess_set.filter(content_type=userct): if not is_domain_admin(entry.content_object): continue - result |= get_user_domains_qs(entry.content_object) + result |= get_user_domains_qs(entry.content_object).distinct() return result def get_user_domaliases(user): diff --git a/admin/migrations/0015_auto__add_objectowner.py b/admin/migrations/0015_auto__add_objectaccess__add_unique_objectaccess_user_content_type_obje.py similarity index 89% rename from admin/migrations/0015_auto__add_objectowner.py rename to admin/migrations/0015_auto__add_objectaccess__add_unique_objectaccess_user_content_type_obje.py index cc5fa4ad1..de6bcfecc 100644 --- a/admin/migrations/0015_auto__add_objectowner.py +++ b/admin/migrations/0015_auto__add_objectaccess__add_unique_objectaccess_user_content_type_obje.py @@ -8,20 +8,27 @@ class Migration(SchemaMigration): def forwards(self, orm): - # Adding model 'ObjectOwner' - db.create_table('admin_objectowner', ( + # Adding model 'ObjectAccess' + db.create_table('admin_objectaccess', ( ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), ('content_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['contenttypes.ContentType'])), ('object_id', self.gf('django.db.models.fields.PositiveIntegerField')()), + ('is_owner', self.gf('django.db.models.fields.BooleanField')(default=False)), )) - db.send_create_signal('admin', ['ObjectOwner']) + db.send_create_signal('admin', ['ObjectAccess']) + + # Adding unique constraint on 'ObjectAccess', fields ['user', 'content_type', 'object_id'] + db.create_unique('admin_objectaccess', ['user_id', 'content_type_id', 'object_id']) def backwards(self, orm): - # Deleting model 'ObjectOwner' - db.delete_table('admin_objectowner') + # Removing unique constraint on 'ObjectAccess', fields ['user', 'content_type', 'object_id'] + db.delete_unique('admin_objectaccess', ['user_id', 'content_type_id', 'object_id']) + + # Deleting model 'ObjectAccess' + db.delete_table('admin_objectaccess') models = { @@ -71,19 +78,20 @@ class Migration(SchemaMigration): 'uid': ('django.db.models.fields.IntegerField', [], {}), 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) }, + 'admin.objectaccess': { + 'Meta': {'unique_together': "(('user', 'content_type', 'object_id'),)", 'object_name': 'ObjectAccess'}, + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_owner': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, 'admin.objectdates': { 'Meta': {'object_name': 'ObjectDates'}, 'creation': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'last_modification': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) }, - 'admin.objectowner': { - 'Meta': {'object_name': 'ObjectOwner'}, - 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), - 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), - 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), - 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) - }, 'auth.group': { 'Meta': {'object_name': 'Group'}, 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), diff --git a/admin/models.py b/admin/models.py index 82834409a..b0a246c45 100644 --- a/admin/models.py +++ b/admin/models.py @@ -18,14 +18,15 @@ try: except ImportError: ldap_available = False -class ObjectOwner(models.Model): +class ObjectAccess(models.Model): user = models.ForeignKey(User) content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField() content_object = generic.GenericForeignKey('content_type', 'object_id') + is_owner = models.BooleanField(default=False) class Meta: - unique_together = (("content_type", "object_id"),) + unique_together = (("user", "content_type", "object_id"),) class ObjectDates(models.Model): """Dates recording for admin objects @@ -80,7 +81,7 @@ class Domain(DatesAware): quota = models.IntegerField(help_text=ugettext_noop("Default quota in MB applied to mailboxes")) enabled = models.BooleanField(ugettext_noop('enabled'), help_text=ugettext_noop("Check to activate this domain")) - owners = generic.GenericRelation(ObjectOwner) + owners = generic.GenericRelation(ObjectAccess) class Meta: permissions = ( diff --git a/admin/permissions.py b/admin/permissions.py index 8c1b65e4e..ac212d49f 100644 --- a/admin/permissions.py +++ b/admin/permissions.py @@ -7,7 +7,7 @@ from modoboa.lib.webutils import _render, getctx, _render_to_string from modoboa.lib import events from forms import * from tables import SuperAdminsTable, DomainAdminsTable -from lib import set_object_ownership +from lib import grant_access_to_object, ungrant_access_to_object class Permissions(object): submit_label = ugettext_noop("Add") @@ -101,20 +101,24 @@ class DomainAdminsPerms(Permissions): mb.user.date_joined = datetime.datetime.now() mb.user.groups.add(Group.objects.get(name="DomainAdmins")) mb.user.save() - set_object_ownership(mb.user, mb.domain) + #set_object_ownership(mb.user, mb.domain) return True, None content = self._render_form(request, form, True) return False, getctx("ko", content=content) def delete(self, selection): - grp = Group.objects.get(name="DomainAdmins") + dagrp = Group.objects.get(name="DomainAdmins") + sugrp = Group.objects.get(name="SimpleUsers") for uid in selection: u = User.objects.get(pk=uid) - u.groups.remove(grp) + u.groups.remove(dagrp) u.save() events.raiseEvent("DomainAdminDeleted", u) if not len(u.mailbox_set.all()): + ungrant_access_to_object(u) u.delete() + else: + u.groups.add(sugrp) def get(self, request): #domadmins = Mailbox.objects.filter(user__groups__name="DomainAdmins") diff --git a/admin/tables.py b/admin/tables.py index 0648f94cd..d5523435c 100644 --- a/admin/tables.py +++ b/admin/tables.py @@ -149,7 +149,7 @@ class DomainAdminsTable(tables.Table): date_joined = tables.Column("date_joined", label=ugettext_noop("Defined")) enabled = tables.Column("is_active", label=gender("Enabled", "m"), width="10%") actions = tables.ActionColumn("actions", label=ugettext_noop("Actions"), - width="50px", align="center", + width="70px", align="center", defvalue=domain_admin_actions) cols_order = ["selection", "username", "first_name", diff --git a/admin/templatetags/admin_extras.py b/admin/templatetags/admin_extras.py index 9b0976d8e..0609c3bd9 100644 --- a/admin/templatetags/admin_extras.py +++ b/admin/templatetags/admin_extras.py @@ -209,6 +209,12 @@ def domain_admin_actions(user, daid): "title" : _("Edit domain admin"), "class" : "boxed", "rel" : "{handler:'iframe',size:{x:350,y:320}}"}, + {"name" : "assigndomains", + "url" : reverse(admin.views.assign_domains_to_admin, args=[daid]), + "img" : static_url("pics/domains.png"), + "title" : _("Assign domain(s) to this administrator"), + "class" : "boxed", + "rel" : "{handler:'iframe',size:{x:350,y:320}}"}, ] actions += events.raiseQueryEvent("DomainAdminActions", User.objects.get(pk=daid)) return render_actions(actions) diff --git a/admin/urls.py b/admin/urls.py index ba5351fba..4a1de7f50 100644 --- a/admin/urls.py +++ b/admin/urls.py @@ -38,6 +38,7 @@ urlpatterns = patterns( (r'^permissions/domainadmin/new/$', 'create_domain_admin'), (r'^permissions/domainadmin/edit/(?P\d+)/$', 'edit_domain_admin'), (r'^permissions/domainadmin/promote/$', 'domain_admin_promotion'), + (r'^permissions/domainadmin/assign/(?P\d+)/$', 'assign_domains_to_admin'), (r'^permissions/searchaccount/$', 'search_account'), (r'^permissions/delete/$', 'delete_permissions'), ) diff --git a/admin/views.py b/admin/views.py index 49d545027..88d13755e 100644 --- a/admin/views.py +++ b/admin/views.py @@ -32,7 +32,7 @@ def domains(request): return HttpResponseRedirect(reverse(userprefs.views.preferences)) - domains = get_user_domains(request.user) + domains = get_user_domains_qs(request.user) for dom in domains: dom.mbalias_counter = len(Alias.objects.filter(domain=dom.id)) deloptions = {"keepdir" : _("Do not delete domain directory")} @@ -73,7 +73,7 @@ def _validate_domain(request, form, successmsg, commonctx, def newdomain(request, tplname="admin/adminform.html"): events.raiseEvent("CanCreateDomain", request.user) def newdomain_cb(user, domain): - set_object_ownership(user, domain) + grant_access_to_object(user, domain, is_owner=True) events.raiseEvent("CreateDomain", user, domain) commonctx = {"title" : _("New domain"), @@ -127,7 +127,7 @@ def deldomain(request): raise AdminError(_("You can't delete your own domain")) events.raiseEvent("DeleteDomain", dom) - unset_object_ownership(dom) + ungrant_access_to_object(dom) dom.delete(keepdir=keepdir) msg = ungettext("Domain deleted", "Domains deleted", len(selection)) @@ -136,7 +136,7 @@ def deldomain(request): @login_required @permission_required("admin.view_domaliases") -@check_domain_ownership +@check_domain_access def domaliases(request): if request.GET.has_key("domid"): domain = Domain.objects.get(pk=request.GET["domid"]) @@ -171,7 +171,7 @@ def newdomalias(request, tplname="admin/adminform.html"): except IntegrityError: error = _("Alias with this name already exists") else: - set_object_ownership(request.user, domalias) + grant_access_to_object(request.user, domalias, is_owner=True) events.raiseEvent("DomainAliasCreated", request.user, domalias) messages.info(request, _("Domain alias created"), fail_silently=True) return ajax_response(request, url=reverse(admin.views.domaliases)) @@ -230,7 +230,7 @@ def deldomalias(request): if not is_object_owner(request.user, domalias.target): raise PermDeniedException(_("You don't have acces to this domain")) events.raiseEvent("DomainAliasDeleted", domalias) - unset_object_ownership(domalias) + ungrant_access_to_object(domalias) domalias.delete() msg = ungettext("Domain alias deleted", "Domain aliases deleted", len(selection)) messages.info(request, msg, fail_silently=True) @@ -238,7 +238,7 @@ def deldomalias(request): @login_required @permission_required("admin.view_mailboxes") -@check_domain_ownership +@check_domain_access def mailboxes(request): title = _("Available mailboxes") if request.GET.has_key("domid"): @@ -270,7 +270,7 @@ def mailboxes_raw(request, dom_id=None): @login_required @permission_required("admin.view_mailboxes") -@check_domain_ownership +@check_domain_access def mailboxes_search(request): if request.method != "POST": return @@ -300,7 +300,7 @@ def newmailbox(request, tplname="admin/adminform.html"): error = None if form.is_valid(): if not request.user.is_superuser: - if not is_object_owner(request.user, form.cleaned_data["domain"]): + if not can_access_object(request.user, form.cleaned_data["domain"]): raise PermDeniedException(_("You do not have access to this domain")) try: @@ -308,7 +308,8 @@ def newmailbox(request, tplname="admin/adminform.html"): except AdminError as e: error = str(e) else: - set_object_ownership(request.user, mb) + grant_access_to_object(request.user, mb, is_owner=True) + grant_access_to_object(request.user, mb.user, is_owner=True) events.raiseEvent("CreateMailbox", request.user, mb) messages.info(request, _("Mailbox created"), fail_silently=True) @@ -370,12 +371,13 @@ def delmailbox(request): if len(request.user.mailbox_set.all()) else None for mb in Mailbox.objects.filter(id__in=selection): - if not is_object_owner(request.user, mb.domain): + if not can_access_object(request.user, mb.domain): raise PermDeniedException(_("You don't have access to this domain")) if usermb and usermb == mb: raise AdminError(_("You can't delete your own mailbox")) events.raiseEvent("DeleteMailbox", mb) - unset_object_ownership(mb) + ungrant_access_to_object(mb) + ungrant_access_to_object(mb.user) mb.delete(keepdir=keepdir) msg = ungettext("Mailbox deleted", "Mailboxes deleted", len(selection)) @@ -384,7 +386,7 @@ def delmailbox(request): @login_required @permission_required("admin.view_aliases") -@check_domain_ownership +@check_domain_access def mbaliases(request): if request.GET.has_key("domid"): aliases = Alias.objects.filter(domain=request.GET["domid"]) @@ -438,7 +440,7 @@ def newmbalias(request, tplname="admin/mbaliasform.html"): "formid" : "mbaliasform"} if request.method == "POST": def callback(user, mbalias): - set_object_ownership(user, mbalias) + grant_access_to_object(user, mbalias, is_owner=True) events.raiseEvent("MailboxAliasCreated", user, mbalias) form = AliasForm(request.user, request.POST) @@ -480,7 +482,7 @@ def delmbalias(request): if not is_object_owner(request.user, mbalias.domain): raise PermDeniedException(_("You do not have access to this domain")) events.raiseEvent("MailboxAliasDeleted", mbalias) - unset_object_ownership(mbalias) + ungrant_access_to_object(mbalias) mbalias.delete() msg = ungettext("Alias deleted", "Aliases deleted", len(selection)) @@ -541,7 +543,7 @@ def create_domain_admin(request, tplname="admin/create_domain_admin.html"): form = UserWithPasswordForm(request.POST) if form.is_valid(): user = form.save(group="DomainAdmins") - set_object_ownership(request.user, user) + grant_access_to_object(request.user, user, is_owner=True) events.raiseEvent("DomainAdminCreated", user) messages.info(request, _("Domain admin created"), fail_silently=True) return ajax_response(request, url=reverse(admin.views.permissions)) @@ -588,7 +590,11 @@ def domain_admin_promotion(request): u = User.objects.get(username=form.cleaned_data["name"]) u.groups.add(Group.objects.get(name="DomainAdmins")) u.save() - set_object_ownership(request.user, u) + if not can_access_object(request.user, u): + # The only case where we need to grant access to the + # object is when ``u`` has been create by a super + # admin + grant_access_to_object(request.user, u, is_owner=True) events.raiseEvent("DomainAdminCreated", u) messages.info(request, _("Domain admin added"), fail_silently=True) return ajax_response(request, url=reverse(admin.views.permissions)) @@ -599,7 +605,38 @@ def domain_admin_promotion(request): @login_required @permission_required("auth.view_permissions") -@check_domain_ownership +@transaction.commit_on_success +def assign_domains_to_admin(request, da_id, tplname="admin/adminform.html"): + da = User.objects.get(pk=da_id) + ctx = dict(title=_("Assign domains to %s" % da.username), + action=reverse(assign_domains_to_admin, args=[da_id]), + formid="assignform", + submit_label=_("Assign")) + if request.method == "POST": + form = AssignDomainsForm(request.user, da, request.POST) + if form.is_valid(): + current_domains = get_user_domains(da) + for domain in form.cleaned_data["domains"]: + if not domain in current_domains: + grant_access_to_object(da, domain) + else: + current_domains.remove(domain) + for domain in current_domains: + ungrant_access_to_object(domain, da) + msg = ungettext("Domain assigned", "Domains assigned", + len(form.cleaned_data["domains"])) + messages.info(request, msg) + return ajax_response(request, url=reverse(admin.views.permissions)) + ctx.update(form=form) + return ajax_response(request, status="ko", template=tplname, **ctx) + + form = AssignDomainsForm(request.user, da) + ctx.update(form=form) + return _render(request, tplname, ctx) + +@login_required +@permission_required("auth.view_permissions") +@check_domain_access def search_account(request): search = request.POST.get("search", None) if search is None: diff --git a/extensions/limits/controls.py b/extensions/limits/controls.py index 610c9d3d5..ff49f2b2f 100644 --- a/extensions/limits/controls.py +++ b/extensions/limits/controls.py @@ -5,7 +5,9 @@ """ from modoboa.admin.models import Mailbox -from modoboa.admin.lib import get_object_owner, set_object_ownership +from modoboa.admin.lib import \ + get_object_owner, grant_access_to_object, is_object_owner, \ + ungrant_access_to_object from modoboa.lib import events from lib import * from models import * @@ -20,8 +22,6 @@ def check_limit(user, lname): :param user: a ``User`` object """ - if not is_reseller(user): - return try: if user.limitspool.will_be_reached(lname): raise LimitReached(user.limitspool.get_limit(lname)) @@ -107,16 +107,17 @@ def create_pool(user): @events.observe("DomainAdminDeleted") def move_pool_resource(domadmin): owner = get_object_owner(domadmin) - for ooentry in domadmin.objectowner_set.all(): - obj = ooentry.content_object - ooentry.delete() - set_object_ownership(owner, obj) + if not owner.is_superuser: + for ooentry in domadmin.objectaccess_set.all(): + if ooentry.is_owner: + grant_access_to_object(owner, ooentry.content_object, True) + ooentry.delete() - for lname in reseller_limits_tpl: - l = domadmin.limitspool.get_limit(lname) - ol = owner.limitspool.get_limit(lname) - ol.curvalue += l.curvalue - ol.maxvalue += l.maxvalue - ol.save() + for lname in reseller_limits_tpl: + l = domadmin.limitspool.get_limit(lname) + ol = owner.limitspool.get_limit(lname) + ol.curvalue += l.curvalue + ol.maxvalue += l.maxvalue + ol.save() domadmin.limitspool.delete() diff --git a/extensions/limits/models.py b/extensions/limits/models.py index bdf8f3f30..7c6d5e85a 100644 --- a/extensions/limits/models.py +++ b/extensions/limits/models.py @@ -43,6 +43,20 @@ class LimitsPool(models.Model): 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: diff --git a/extensions/limits/permissions.py b/extensions/limits/permissions.py index 9c647947d..52349171f 100644 --- a/extensions/limits/permissions.py +++ b/extensions/limits/permissions.py @@ -3,8 +3,8 @@ from django.utils.translation import ugettext as _, ugettext_noop from django.contrib.auth.models import User from modoboa.admin.permissions import Permissions -from modoboa.admin.lib import set_object_ownership -from modoboa.admin.models import ObjectOwner +from modoboa.admin.lib import grant_access_to_object +from modoboa.admin.models import ObjectAccess from modoboa.lib import events from modoboa.lib.webutils import _render, _render_to_string from tables import * @@ -24,7 +24,7 @@ class ResellersPerms(Permissions): error = None if form.is_valid(): r = form.save(group="Resellers") - set_object_ownership(request.user, r) + grant_access_to_object(request.user, r, True) return True, None content = self._render_form(request, form, True) return False, dict(status="ko", content=content) diff --git a/extensions/stats/grapher.py b/extensions/stats/grapher.py index ad52da760..2dfd5d3e7 100644 --- a/extensions/stats/grapher.py +++ b/extensions/stats/grapher.py @@ -3,7 +3,7 @@ import os import time import datetime -import rrdtool +#import rrdtool from django.utils.translation import ugettext as _ from modoboa.lib import parameters diff --git a/lib/events.py b/lib/events.py index ce8105f78..c2de6f32c 100644 --- a/lib/events.py +++ b/lib/events.py @@ -16,6 +16,7 @@ events = [ "CanCreateDomain", "CreateDomain", "DeleteDomain", + "DomainAssigned", "CanCreateDomainAlias", "DomainAliasCreated", diff --git a/static/css/box.css b/static/css/box.css index 6c7c16e84..79327c7fd 100644 --- a/static/css/box.css +++ b/static/css/box.css @@ -188,4 +188,12 @@ html[xmlns] .clearfix { #permform > #id_name { width: 180px; +} + +/* + * Assign domains form + */ +#assignform #id_domains { + width: 310px; + height: 200px; } \ No newline at end of file