From 22c811f3da1de8a56f949785b359b41824e845a5 Mon Sep 17 00:00:00 2001 From: Antoine Nguyen Date: Fri, 1 Nov 2013 16:48:04 +0100 Subject: [PATCH] wip: relay domains support. * Integration with the amavis frontend * Per relay domain policies * One policy shared between a relay domain and its aliases * One 'users' record per object * Extra: better event handlers management see #491 --- modoboa/extensions/amavis/__init__.py | 133 +--------- modoboa/extensions/amavis/forms.py | 23 +- .../extensions/amavis/general_callbacks.py | 133 ++++++++++ modoboa/extensions/amavis/lib.py | 69 +++++ .../postfix_relay_domains/__init__.py | 241 +++++------------- .../postfix_relay_domains/amavis_callbacks.py | 30 +++ .../extensions/postfix_relay_domains/forms.py | 42 ++- .../general_callbacks.py | 176 +++++++++++++ ...limits_controls.py => limits_callbacks.py} | 0 .../postfix_relay_domains/models.py | 3 + .../postfix_relay_domains/js/relay_domains.js | 1 + .../new_relaydomain_form.html | 3 + .../relaydomain_form.html | 3 +- .../extensions/postfix_relay_domains/views.py | 17 +- modoboa/lib/events.py | 15 ++ modoboa/lib/parameters.py | 31 +-- modoboa/lib/sysutils.py | 14 + 17 files changed, 576 insertions(+), 358 deletions(-) create mode 100644 modoboa/extensions/amavis/general_callbacks.py create mode 100644 modoboa/extensions/postfix_relay_domains/amavis_callbacks.py create mode 100644 modoboa/extensions/postfix_relay_domains/general_callbacks.py rename modoboa/extensions/postfix_relay_domains/{limits_controls.py => limits_callbacks.py} (100%) create mode 100644 modoboa/extensions/postfix_relay_domains/templates/postfix_relay_domains/new_relaydomain_form.html diff --git a/modoboa/extensions/amavis/__init__.py b/modoboa/extensions/amavis/__init__.py index 69e63e056..5e47ac577 100644 --- a/modoboa/extensions/amavis/__init__.py +++ b/modoboa/extensions/amavis/__init__.py @@ -8,9 +8,7 @@ Provides: * Per-domain settings """ -from django.utils.translation import ugettext as _, ugettext_lazy -from django.core.urlresolvers import reverse -from django.template import Template, Context +from django.utils.translation import ugettext_lazy from modoboa.lib import events, parameters from modoboa.core.extensions import ModoExtension, exts_pool @@ -41,136 +39,13 @@ class Amavis(ModoExtension): def load(self): from .app_settings import ParametersForm, UserSettings + parameters.register(ParametersForm, "Amavis") parameters.register(UserSettings, ugettext_lazy("Quarantine")) + from modoboa.extensions.amavis import general_callbacks def destroy(self): - events.unregister("UserMenuDisplay", menu) + events.unregister_extension() parameters.unregister() exts_pool.register_extension(Amavis) - - -@events.observe("UserMenuDisplay") -def menu(target, user): - if target == "top_menu": - return [ - {"name": "quarantine", - "label": _("Quarantine"), - "url": reverse('modoboa.extensions.amavis.views.index')} - ] - return [] - - -@events.observe("DomainCreated") -def on_domain_created(user, domain): - from .models import Users, Policy - p = Policy.objects.create(policy_name=domain.name) - Users.objects.create(email="@%s" % domain.name, fullname=domain.name, - priority=7, policy=p) - - -@events.observe("DomainModified") -def on_domain_modified(domain): - if domain.oldname != domain.name: - from .models import Users - u = Users.objects.get(email="@%s" % domain.oldname) - u.email = "@%s" % domain.name - u.fullname = domain.name - u.policy.policy_name = domain.name - u.policy.save() - u.save() - - -@events.observe("DomainDeleted") -def on_domain_deleted(domain): - from .models import Users - - try: - u = Users.objects.get(email="@%s" % domain.name) - except Users.DoesNotExist: - return - u.policy.delete() - u.delete() - - -@events.observe("GetStaticContent") -def extra_static_content(caller, user): - if user.group == "SimpleUsers": - return [] - - if caller == 'top' and parameters.get_admin("USER_CAN_RELEASE") == 'no': - tpl = Template(""" -""") - return [tpl.render( - Context({ - 'url': reverse("modoboa.extensions.amavis.views.nbrequests"), - 'interval': int(parameters.get_admin("CHECK_REQUESTS_INTERVAL")) * 1000, - 'text': _("pending requests"), - }) - )] - - if caller == 'domains': - tpl = Template(""" -""") - - return [tpl.render(Context({}))] - return [] - - -@events.observe("TopNotifications") -def display_requests(user): - from .sql_listing import get_wrapper - - if parameters.get_admin("USER_CAN_RELEASE") == "yes" \ - or user.group == "SimpleUsers": - return [] - nbrequests = get_wrapper().get_pending_requests(user) - - url = reverse("modoboa.extensions.amavis.views.index") - url += "#listing/?viewrequests=1" - tpl = Template('
{{ label }}
') - css = "hidden" if nbrequests == 0 else "" - return [tpl.render(Context(dict( - label=_("%d pending requests" % nbrequests), url=url, css=css - )))] - - -@events.observe("ExtraDomainForm") -def extra_domain_form(user, domain): - from .forms import DomainPolicyForm - - if not user.has_perm("admin.view_domains"): - return [] - return [ - dict( - id="amavis", title=_("Content filter"), cls=DomainPolicyForm, - formtpl="amavis/domain_content_filter.html" - ) - ] - - -@events.observe("FillDomainInstances") -def fill_domain_instances(user, domain, instances): - if not user.has_perm("admin.view_domains"): - return - instances["amavis"] = domain diff --git a/modoboa/extensions/amavis/forms.py b/modoboa/extensions/amavis/forms.py index 721b3c5a1..1f9a270ad 100644 --- a/modoboa/extensions/amavis/forms.py +++ b/modoboa/extensions/amavis/forms.py @@ -1,7 +1,7 @@ # coding: utf-8 from django import forms -from models import Policy, Users from modoboa.lib.formutils import InlineRadioSelect +from modoboa.extensions.amavis.models import Policy, Users class DomainPolicyForm(forms.ModelForm): @@ -34,20 +34,21 @@ class DomainPolicyForm(forms.ModelForm): self.fields[f].required = False def save(self, user, commit=True): - p = super(DomainPolicyForm, self).save(commit=False) - for f in ['bypass_spam_checks', 'bypass_virus_checks', 'bypass_banned_checks']: - if getattr(p, f) == '': - setattr(p, f, None) + policy = super(DomainPolicyForm, self).save(commit=False) + for field in ['bypass_spam_checks', 'bypass_virus_checks', + 'bypass_banned_checks']: + if getattr(policy, field) == '': + setattr(policy, field, None) if self.cleaned_data['spam_subject_tag2_act']: - p.spam_subject_tag2 = None + policy.spam_subject_tag2 = None if commit: - p.save() + policy.save() try: - u = Users.objects.get(policy=p) + u = Users.objects.get(fullname=policy.policy_name) except Users.DoesNotExist: u = Users.objects.get(email="@%s" % self.domain.name) - u.policy = p - p.save() - return p + u.policy = policy + policy.save() + return policy diff --git a/modoboa/extensions/amavis/general_callbacks.py b/modoboa/extensions/amavis/general_callbacks.py new file mode 100644 index 000000000..29366aa3a --- /dev/null +++ b/modoboa/extensions/amavis/general_callbacks.py @@ -0,0 +1,133 @@ +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext as _ +from django.template import Template, Context + +from modoboa.lib import events, parameters +from modoboa.extensions.amavis.lib import ( + create_user_and_policy, update_user_and_policy, delete_user_and_policy +) + + +@events.observe("UserMenuDisplay") +def menu(target, user): + if target == "top_menu": + return [ + {"name": "quarantine", + "label": _("Quarantine"), + "url": reverse('modoboa.extensions.amavis.views.index')} + ] + return [] + + +@events.observe("DomainCreated") +def on_domain_created(user, domain): + create_user_and_policy(domain.name) + + +@events.observe("DomainModified") +def on_domain_modified(domain): + update_user_and_policy(domain.oldname, domain.name) + + +@events.observe("DomainDeleted") +def on_domain_deleted(domain): + delete_user_and_policy(domain.name) + + +@events.observe("GetStaticContent") +def extra_static_content(caller, user): + if user.group == "SimpleUsers": + return [] + + if caller == 'top' and parameters.get_admin("USER_CAN_RELEASE") == 'no': + tpl = Template(""" +""") + return [tpl.render( + Context({ + 'url': reverse("modoboa.extensions.amavis.views.nbrequests"), + 'interval': int(parameters.get_admin("CHECK_REQUESTS_INTERVAL")) * 1000, + 'text': _("pending requests"), + }) + )] + + if caller == 'domains': + tpl = Template(""" +""") + + return [tpl.render(Context({}))] + return [] + + +@events.observe("TopNotifications") +def display_requests(user): + from .sql_listing import get_wrapper + + if parameters.get_admin("USER_CAN_RELEASE") == "yes" \ + or user.group == "SimpleUsers": + return [] + nbrequests = get_wrapper().get_pending_requests(user) + + url = reverse("modoboa.extensions.amavis.views.index") + url += "#listing/?viewrequests=1" + tpl = Template('
{{ label }}
') + css = "hidden" if nbrequests == 0 else "" + return [tpl.render(Context(dict( + label=_("%d pending requests" % nbrequests), url=url, css=css + )))] + + +def send_amavis_form(): + """ + """ + from .forms import DomainPolicyForm + return [{ + 'id': 'amavis', 'title': _("Content filter"), 'cls': DomainPolicyForm, + 'formtpl': 'amavis/domain_content_filter.html' + }] + + +@events.observe("ExtraDomainForm") +def extra_domain_form(user, domain): + if not user.has_perm("admin.view_domains"): + return [] + return send_amavis_form() + + +@events.observe('ExtraRelayDomainForm') +def extra_relaydomain_form(user, rdomain): + if not user.has_perm("postfix_relay_domains.add_relaydomain"): + return [] + return send_amavis_form() + + +@events.observe("FillDomainInstances") +def fill_domain_instances(user, domain, instances): + if not user.has_perm("admin.view_domains"): + return + instances["amavis"] = domain + + +@events.observe("FillRelayDomainInstances") +def fill_relaydomain_instances(user, rdomain, instances): + if not user.has_perm("postfix_relay_domains.add_relaydomain"): + return + instances["amavis"] = rdomain diff --git a/modoboa/extensions/amavis/lib.py b/modoboa/extensions/amavis/lib.py index 56d8312df..f33e77249 100644 --- a/modoboa/extensions/amavis/lib.py +++ b/modoboa/extensions/amavis/lib.py @@ -8,6 +8,7 @@ from django.core.urlresolvers import reverse from django.utils.translation import ugettext as _ from modoboa.lib import parameters from modoboa.lib.exceptions import InternalError +from modoboa.extensions.amavis.models import Users, Policy def selfservice(ssfunc=None): @@ -76,3 +77,71 @@ recipient=%s if re.search("250 [\d\.]+ Ok", answer): return True return False + + +def create_user_and_policy(name): + """Create records. + + Create two records (a user and a policy) using :keyword:`name` as + an identifier. + + :param str name: name + """ + policy = Policy.objects.create(policy_name=name) + Users.objects.create( + email="@%s" % name, fullname=name, + priority=7, policy=policy + ) + + +def create_user_and_use_policy(name, policy_name): + """Create a *users* record and use an existing policy. + + :param str name: user record name + :param str policy_name: policy name + """ + policy = Policy.objects.get(policy_name=policy_name) + Users.objects.create( + email="@%s" % name, fullname=name, + priority=7, policy=policy + ) + + +def update_user_and_policy(oldname, newname): + """Update records. + + :param str oldname: old name + :param str newname: new name + """ + if oldname == newname: + return + u = Users.objects.get(email="@%s" % oldname) + u.email = "@%s" % newname + u.fullname = newname + u.policy.policy_name = newname + u.policy.save() + u.save() + + +def delete_user_and_policy(name): + """Delete records. + + :param str name: identifier + """ + try: + u = Users.objects.get(email="@%s" % name) + except Users.DoesNotExist: + return + u.policy.delete() + u.delete() + + +def delete_user(name): + """Delete a *users* record. + + :param str name: user record name + """ + try: + Users.objects.get(email="@%s" % name).delete() + except Users.DoesNotExist: + pass diff --git a/modoboa/extensions/postfix_relay_domains/__init__.py b/modoboa/extensions/postfix_relay_domains/__init__.py index da9bf2ea9..10aba023a 100644 --- a/modoboa/extensions/postfix_relay_domains/__init__.py +++ b/modoboa/extensions/postfix_relay_domains/__init__.py @@ -1,11 +1,7 @@ # coding: utf-8 -from django.core.urlresolvers import reverse from django.contrib.auth.models import Group, Permission from django.contrib.contenttypes.models import ContentType -from django.template import Template, Context -from django.conf import settings from django.utils.translation import ugettext_lazy -from django.db.models import Q from modoboa.lib import events, parameters from modoboa.core.extensions import ModoExtension, exts_pool from .models import RelayDomain, RelayDomainAlias, Service @@ -14,16 +10,64 @@ extension_events = [ "RelayDomainCreated", "RelayDomainDeleted", "RelayDomainAliasCreated", - "RelayDomainAliasDeleted" + "RelayDomainAliasDeleted", + "ExtraRelayDomainForm", + "FillRelayDomainInstances" ] +def init_limits_dependant_features(): + """Populate limits tables. + + Update the pool of every defined user (to add new limits) and + update the *Resellers* group with new permissions. + """ + from modoboa.core.models import User + from modoboa.extensions.limits import controls + + for u in User.objects.all(): + controls.create_pool(u) + grp = Group.objects.get(name='Resellers') + for model in [RelayDomain, RelayDomainAlias, Service]: + ct = ContentType.objects.get_for_model(model) + name = model.__name__.lower() + for action in ['add', 'change', 'delete']: + grp.permissions.add( + Permission.objects.get( + content_type=ct, codename='%s_%s' % (action, name) + ) + ) + grp.save() + + +def init_amavis_dependant_features(): + """Populate amavis database. + + We create records for *users* and *policy* tables for each defined + relay domain or relay domain alias. + """ + from modoboa.extensions.amavis.models import Users + from modoboa.extensions.amavis.lib import ( + create_user_and_policy, create_user_and_use_policy + ) + + for rdom in RelayDomain.objects.all(): + try: + Users.objects.get(email="@%s" % rdom.name) + except Users.DoesNotExist: + create_user_and_policy(rdom.name) + for rdomalias in rdom.relaydomainalias_set.all(): + try: + Users.objects.get(email='@%s' % rdomalias.name) + except Users.DoesNotExist: + create_user_and_use_policy(rdomalias.name, rdom.name) + + class PostfixRelayDomains(ModoExtension): name = "postfix_relay_domains" label = "Postfix relay domains" version = "1.0" description = ugettext_lazy("Relay domains support for Postfix") - depends_on = ['limits'] def init(self): """Initialisation method. @@ -33,26 +77,8 @@ class PostfixRelayDomains(ModoExtension): """ for service_name in ['relay', 'smtp']: Service.objects.get_or_create(name=service_name) - if not exts_pool.is_extension_enabled('limits'): - return - - from modoboa.core.models import User - from modoboa.extensions.limits import controls - - for u in User.objects.all(): - controls.create_pool(u) - - grp = Group.objects.get(name='Resellers') - for model in [RelayDomain, RelayDomainAlias, Service]: - ct = ContentType.objects.get_for_model(model) - name = model.__name__.lower() - for action in ['add', 'change', 'delete']: - grp.permissions.add( - Permission.objects.get( - content_type=ct, codename='%s_%s' % (action, name) - ) - ) - grp.save() + if exts_pool.is_extension_enabled('limits'): + init_limits_dependant_features() def load(self): from .app_settings import AdminParametersForm @@ -61,163 +87,14 @@ class PostfixRelayDomains(ModoExtension): AdminParametersForm, ugettext_lazy("Relay domains") ) events.declare(extension_events) - if not exts_pool.is_extension_enabled('limits'): - return - import limits_controls + from modoboa.extensions.postfix_relay_domains import general_callbacks + if exts_pool.is_extension_enabled('limits'): + import limits_callbacks + if exts_pool.is_extension_enabled('amavis'): + import amavis_callbacks def destroy(self): - events.unregister('GetExtraDomainEntries', extra_domain_entries) - events.unregister('GetExtraParameters', extra_parameters) + events.unregister_extension() + parameters.unregister() exts_pool.register_extension(PostfixRelayDomains) - - -@events.observe('GetStaticContent') -def static_content(caller, user): - if caller != 'domains': - return [] - - t = Template(""" - -""") - return [t.render(Context({'STATIC_URL': settings.STATIC_URL}))] - - -@events.observe('ExtraDomainFilters') -def extra_domain_filters(): - return ['srvfilter'] - - -@events.observe('ExtraDomainMenuEntries') -def extra_domain_menu_entries(user): - return [ - {"name": "newrelaydomain", - "label": ugettext_lazy("Add relay domain"), - "img": "icon-plus", - "modal": True, - "modalcb": "rdomain.domainform_cb", - "url": reverse( - "modoboa.extensions.postfix_relay_domains.views.create" - )}, - ] - - -@events.observe('ExtraDomainEntries') -def extra_domain_entries(user, domfilter, searchquery, **extrafilters): - if domfilter is not None and domfilter and domfilter != 'relaydomain': - return [] - relay_domains = RelayDomain.objects.get_for_admin(user) - if searchquery is not None: - q = Q(name__contains=searchquery) - q |= Q(relaydomainalias__name__contains=searchquery) - relay_domains = relay_domains.filter(q).distinct() - if 'srvfilter' in extrafilters and extrafilters['srvfilter']: - relay_domains = relay_domains.filter( - Q(service__name=extrafilters['srvfilter']) - ) - return relay_domains - - -@events.observe('GetDomainModifyLink') -def rdomain_modify_link(domain): - if not isinstance(domain, RelayDomain): - return {} - return { - 'url': reverse( - 'modoboa.extensions.postfix_relay_domains.views.edit', - args=[domain.id] - ), - 'modalcb': 'rdomain.domainform_cb' - } - - -@events.observe('GetDomainAliasQuerySet') -def get_da_query_set(domain): - if domain.__class__.__name__ != 'RelayDomain': - return [] - return [domain.relaydomainalias_set] - - -@events.observe('GetDomainActions') -def rdomain_actions(user, domain): - if not isinstance(domain, RelayDomain): - return [] - if not user.has_perm("postfix_relay_domains.delete_relaydomain"): - return [] - return [{ - "name": "delrelaydomain", - "url": reverse("modoboa.extensions.postfix_relay_domains.views.delete", - args=[domain.id]), - "title": ugettext_lazy("Delete %s?" % domain.name), - "img": "icon-trash" - }] - - -@events.observe('CheckDomainName') -def check_domain_name(): - return [ - (RelayDomain, ugettext_lazy('relay domain')), - (RelayDomainAlias, ugettext_lazy('relay domain alias')) - ] - - -@events.observe('GetExtraLimitTemplates') -def extra_limit_templates(): - return [ - ('relay_domains_limit', ugettext_lazy('Relay domains'), - ugettext_lazy('Maximum number of relay domains this user can create'), - 'Resellers'), - ("relay_domain_aliases_limit", ugettext_lazy("Relay domain aliases"), - ugettext_lazy('Maximum number of relay domain aliases this user can create'), - 'Resellers'), - ] - - -@events.observe('GetExtraParameters') -def extra_parameters(app, level): - from django import forms - - if app != 'limits' or level != 'A': - return {} - return { - 'deflt_relay_domains_limit': forms.IntegerField( - label=ugettext_lazy("Relay domains"), - initial=0, - help_text=ugettext_lazy( - "Maximum number of allowed relay domains for a new administrator" - ), - widget=forms.widgets.TextInput(attrs={"class": "span1"}) - ), - 'deflt_relay_domain_aliases_limit': forms.IntegerField( - label=ugettext_lazy("Relay domain aliases"), - initial=0, - help_text=ugettext_lazy( - "Maximum number of allowed relay domain aliases for a new administrator" - ), - widget=forms.widgets.TextInput(attrs={"class": "span1"}) - ) - } - - -@events.observe('ExtraDomainImportHelp') -def extra_domain_import_help(): - return [ugettext_lazy(""" -
  • relaydomain; name; target host; service; enabled; verify recipients
  • -
  • relaydomainalias; name; target; enabled
  • -""")] - - -@events.observe('ImportObject') -def get_import_func(objtype): - from .lib import import_relaydomain, import_relaydomainalias - - if objtype == 'relaydomain': - return [import_relaydomain] - if objtype == 'relaydomainalias': - return [import_relaydomainalias] - return [] diff --git a/modoboa/extensions/postfix_relay_domains/amavis_callbacks.py b/modoboa/extensions/postfix_relay_domains/amavis_callbacks.py new file mode 100644 index 000000000..c43052af3 --- /dev/null +++ b/modoboa/extensions/postfix_relay_domains/amavis_callbacks.py @@ -0,0 +1,30 @@ +from modoboa.lib import events +from modoboa.extensions.amavis.lib import ( + create_user_and_policy, update_user_and_policy, delete_user_and_policy, + create_user_and_use_policy, delete_user +) + + +@events.observe("RelayDomainCreated") +def on_relay_domain_created(user, rdomain): + create_user_and_policy(rdomain.name) + + +@events.observe("RelayDomainModified") +def on_relay_domain_modified(rdomain): + update_user_and_policy(rdomain.oldname, rdomain.name) + + +@events.observe("RelayDomainDeleted") +def on_relay_domain_deleted(rdomain): + delete_user_and_policy(rdomain.name) + + +@events.observe("RelayDomainAliasCreated") +def on_relay_domain_alias_created(user, rdomainalias): + create_user_and_use_policy(rdomainalias.name, rdomainalias.target.name) + + +@events.observe("RelayDomainAliasDeleted") +def on_relay_domain_alias_deleted(rdomainalias): + delete_user(rdomainalias.name) diff --git a/modoboa/extensions/postfix_relay_domains/forms.py b/modoboa/extensions/postfix_relay_domains/forms.py index 767caeb3a..37a49d646 100644 --- a/modoboa/extensions/postfix_relay_domains/forms.py +++ b/modoboa/extensions/postfix_relay_domains/forms.py @@ -2,12 +2,12 @@ from django import forms from django.http import QueryDict from django.utils.translation import ugettext as _, ugettext_lazy from modoboa.lib import events -from modoboa.lib.formutils import DynamicForm, DomainNameField +from modoboa.lib.formutils import DynamicForm, DomainNameField, TabForms from modoboa.extensions.admin.models import Domain, DomainAlias from .models import RelayDomain, RelayDomainAlias -class RelayDomainForm(forms.ModelForm, DynamicForm): +class RelayDomainFormGeneral(forms.ModelForm, DynamicForm): aliases = DomainNameField( label=ugettext_lazy("Alias(es)"), required=False, @@ -25,7 +25,7 @@ class RelayDomainForm(forms.ModelForm, DynamicForm): } def __init__(self, *args, **kwargs): - super(RelayDomainForm, self).__init__(*args, **kwargs) + super(RelayDomainFormGeneral, self).__init__(*args, **kwargs) if args and isinstance(args[0], QueryDict): self._load_from_qdict(args[0], "aliases", DomainNameField) elif 'instance' in kwargs: @@ -42,7 +42,7 @@ class RelayDomainForm(forms.ModelForm, DynamicForm): The validation way is not very smart... """ - super(RelayDomainForm, self).clean() + super(RelayDomainFormGeneral, self).clean() if self._errors: raise forms.ValidationError(self._errors) cleaned_data = self.cleaned_data @@ -90,7 +90,7 @@ class RelayDomainForm(forms.ModelForm, DynamicForm): :param ``User`` user: connected user """ - rd = super(RelayDomainForm, self).save(commit=False) + rd = super(RelayDomainFormGeneral, self).save(commit=False) if commit: rd.save() aliases = [] @@ -121,3 +121,35 @@ class RelayDomainForm(forms.ModelForm, DynamicForm): ) al.save(creator=user) return rd + + +class RelayDomainForm(TabForms): + """Specific edition form for relay domains. + + We use a *tabs* compatible form because extensions can add their + own tab. (ex: amavis) + """ + def __init__(self, user, *args, **kwargs): + self.user = user + self.forms = [] + if user.has_perm("postfix_relay_domains.change_relaydomain"): + self.forms.append({ + 'id': 'general', 'title': _("General"), + 'formtpl': 'postfix_relay_domains/relaydomain_form.html', + 'cls': RelayDomainFormGeneral, 'mandatory': True + }) + + cbargs = [user] + if "instances" in kwargs: + cbargs += [kwargs["instances"]["general"]] + self.forms += events.raiseQueryEvent("ExtraRelayDomainForm", *cbargs) + super(RelayDomainForm, self).__init__(*args, **kwargs) + + def save(self, user): + """Custom save method + + As forms interact with each other, it is easier to make custom + code to save them. + """ + for f in self.forms: + f["instance"].save(user) diff --git a/modoboa/extensions/postfix_relay_domains/general_callbacks.py b/modoboa/extensions/postfix_relay_domains/general_callbacks.py new file mode 100644 index 000000000..d7fa11bc6 --- /dev/null +++ b/modoboa/extensions/postfix_relay_domains/general_callbacks.py @@ -0,0 +1,176 @@ +from django.core.urlresolvers import reverse +from django.template import Template, Context +from django.conf import settings +from django.utils.translation import ugettext_lazy +from django.db.models import Q +from modoboa.lib import events +from .models import RelayDomain, RelayDomainAlias + + +@events.observe('GetStaticContent') +def static_content(caller, user): + if caller != 'domains': + return [] + + t = Template(""" + +""") + return [t.render(Context({'STATIC_URL': settings.STATIC_URL}))] + + +@events.observe('ExtraDomainFilters') +def extra_domain_filters(): + return ['srvfilter'] + + +@events.observe('ExtraDomainMenuEntries') +def extra_domain_menu_entries(user): + return [ + {"name": "newrelaydomain", + "label": ugettext_lazy("Add relay domain"), + "img": "icon-plus", + "modal": True, + "modalcb": "rdomain.domainform_cb", + "url": reverse( + "modoboa.extensions.postfix_relay_domains.views.create" + )}, + ] + + +@events.observe('ExtraDomainEntries') +def extra_domain_entries(user, domfilter, searchquery, **extrafilters): + if domfilter is not None and domfilter and domfilter != 'relaydomain': + return [] + relay_domains = RelayDomain.objects.get_for_admin(user) + if searchquery is not None: + q = Q(name__contains=searchquery) + q |= Q(relaydomainalias__name__contains=searchquery) + relay_domains = relay_domains.filter(q).distinct() + if 'srvfilter' in extrafilters and extrafilters['srvfilter']: + relay_domains = relay_domains.filter( + Q(service__name=extrafilters['srvfilter']) + ) + return relay_domains + + +@events.observe('GetDomainModifyLink') +def rdomain_modify_link(domain): + if not isinstance(domain, RelayDomain): + return {} + return { + 'url': reverse( + 'modoboa.extensions.postfix_relay_domains.views.edit', + args=[domain.id] + ), + 'modalcb': 'rdomain.domainform_cb' + } + + +@events.observe('GetDomainAliasQuerySet') +def get_da_query_set(domain): + if domain.__class__.__name__ != 'RelayDomain': + return [] + return [domain.relaydomainalias_set] + + +@events.observe('GetDomainActions') +def rdomain_actions(user, domain): + if not isinstance(domain, RelayDomain): + return [] + if not user.has_perm("postfix_relay_domains.delete_relaydomain"): + return [] + return [{ + "name": "delrelaydomain", + "url": reverse("modoboa.extensions.postfix_relay_domains.views.delete", + args=[domain.id]), + "title": ugettext_lazy("Delete %s?" % domain.name), + "img": "icon-trash" + }] + + +@events.observe('CheckDomainName') +def check_domain_name(): + return [ + (RelayDomain, ugettext_lazy('relay domain')), + (RelayDomainAlias, ugettext_lazy('relay domain alias')) + ] + + +@events.observe('GetExtraLimitTemplates') +def extra_limit_templates(): + return [ + ('relay_domains_limit', ugettext_lazy('Relay domains'), + ugettext_lazy('Maximum number of relay domains this user can create'), + 'Resellers'), + ("relay_domain_aliases_limit", ugettext_lazy("Relay domain aliases"), + ugettext_lazy('Maximum number of relay domain aliases this user can create'), + 'Resellers'), + ] + + +@events.observe('GetExtraParameters') +def extra_parameters(app, level): + from django import forms + + if app != 'limits' or level != 'A': + return {} + return { + 'deflt_relay_domains_limit': forms.IntegerField( + label=ugettext_lazy("Relay domains"), + initial=0, + help_text=ugettext_lazy( + "Maximum number of allowed relay domains for a new administrator" + ), + widget=forms.widgets.TextInput(attrs={"class": "span1"}) + ), + 'deflt_relay_domain_aliases_limit': forms.IntegerField( + label=ugettext_lazy("Relay domain aliases"), + initial=0, + help_text=ugettext_lazy( + "Maximum number of allowed relay domain aliases for a new administrator" + ), + widget=forms.widgets.TextInput(attrs={"class": "span1"}) + ) + } + + +@events.observe('ExtraDomainImportHelp') +def extra_domain_import_help(): + return [ugettext_lazy(""" +
  • relaydomain; name; target host; service; enabled; verify recipients
  • +
  • relaydomainalias; name; target; enabled
  • +""")] + + +@events.observe('ImportObject') +def get_import_func(objtype): + from .lib import import_relaydomain, import_relaydomainalias + + if objtype == 'relaydomain': + return [import_relaydomain] + if objtype == 'relaydomainalias': + return [import_relaydomainalias] + return [] + + +@events.observe('ExtEnabled') +def extension_enabled(extension): + """ExtEnabled event listener. + + Usefull when *limits* or *amavis* extensions are activated after + *postfix_relay_domains*. + + :param extension: enabled extension + """ + from modoboa.extensions.postfix_relay_domains import ( + init_limits_dependant_features, init_amavis_dependant_features + ) + if extension.name == 'limits': + init_limits_dependant_features() + if extension.name == 'amavis': + init_amavis_dependant_features() diff --git a/modoboa/extensions/postfix_relay_domains/limits_controls.py b/modoboa/extensions/postfix_relay_domains/limits_callbacks.py similarity index 100% rename from modoboa/extensions/postfix_relay_domains/limits_controls.py rename to modoboa/extensions/postfix_relay_domains/limits_callbacks.py diff --git a/modoboa/extensions/postfix_relay_domains/models.py b/modoboa/extensions/postfix_relay_domains/models.py index 560dfb0c5..1c45cbffb 100644 --- a/modoboa/extensions/postfix_relay_domains/models.py +++ b/modoboa/extensions/postfix_relay_domains/models.py @@ -139,6 +139,9 @@ reversion.register(RelayDomain) class RelayDomainAlias(AdminObject): + """Relay domain alias. + + """ name = models.CharField( ugettext_lazy("name"), max_length=100, unique=True, help_text=ugettext_lazy("The alias name") diff --git a/modoboa/extensions/postfix_relay_domains/static/postfix_relay_domains/js/relay_domains.js b/modoboa/extensions/postfix_relay_domains/static/postfix_relay_domains/js/relay_domains.js index fc454ab39..d30e10f4b 100644 --- a/modoboa/extensions/postfix_relay_domains/static/postfix_relay_domains/js/relay_domains.js +++ b/modoboa/extensions/postfix_relay_domains/static/postfix_relay_domains/js/relay_domains.js @@ -67,6 +67,7 @@ RelayDomains.prototype = { success_cb: $.proxy(admin.reload_listing, admin) }); }, this)); + $(document).trigger('domform_init'); }, srv_tag_handler: function(tag, $link) { diff --git a/modoboa/extensions/postfix_relay_domains/templates/postfix_relay_domains/new_relaydomain_form.html b/modoboa/extensions/postfix_relay_domains/templates/postfix_relay_domains/new_relaydomain_form.html new file mode 100644 index 000000000..8a349d160 --- /dev/null +++ b/modoboa/extensions/postfix_relay_domains/templates/postfix_relay_domains/new_relaydomain_form.html @@ -0,0 +1,3 @@ +{% extends "common/generic_modal_form.html" %}{% block formbody %} +{% include "postfix_relay_domains/relaydomain_form.html" %} +{% endblock %} diff --git a/modoboa/extensions/postfix_relay_domains/templates/postfix_relay_domains/relaydomain_form.html b/modoboa/extensions/postfix_relay_domains/templates/postfix_relay_domains/relaydomain_form.html index 99edce36b..2c0e8d150 100644 --- a/modoboa/extensions/postfix_relay_domains/templates/postfix_relay_domains/relaydomain_form.html +++ b/modoboa/extensions/postfix_relay_domains/templates/postfix_relay_domains/relaydomain_form.html @@ -1,8 +1,7 @@ -{% extends "common/generic_modal_form.html" %}{% load lib_tags %}{% block formbody %} +{% load lib_tags %} {% render_field form.name %} {% render_field form.target_host %} {% include "postfix_relay_domains/service_field.html" with field=form.service %} {% render_fields_group form "aliases" %} {% render_field form.enabled %} {% render_field form.verify_recipients %} -{% endblock %} diff --git a/modoboa/extensions/postfix_relay_domains/views.py b/modoboa/extensions/postfix_relay_domains/views.py index 5eded6ca3..e59657891 100644 --- a/modoboa/extensions/postfix_relay_domains/views.py +++ b/modoboa/extensions/postfix_relay_domains/views.py @@ -10,16 +10,16 @@ from modoboa.lib import events from modoboa.lib.exceptions import PermDeniedException from modoboa.lib.webutils import render_to_json_response from .models import RelayDomain, Service -from .forms import RelayDomainForm +from .forms import RelayDomainForm, RelayDomainFormGeneral @login_required @permission_required("postfix_relay_domains.add_relaydomain") @transaction.commit_on_success -def create(request, tplname="postfix_relay_domains/relaydomain_form.html"): +def create(request, tplname="postfix_relay_domains/new_relaydomain_form.html"): events.raiseEvent("CanCreate", request.user, "relay_domains") if request.method == 'POST': - form = RelayDomainForm(request.POST) + form = RelayDomainFormGeneral(request.POST) if form.is_valid(): rdom = form.save(request.user) rdom.post_create(request.user) @@ -35,18 +35,21 @@ def create(request, tplname="postfix_relay_domains/relaydomain_form.html"): "action_classes": "submit", "action": reverse(create), "formid": "rdomform", - "form": RelayDomainForm()} + "form": RelayDomainFormGeneral()} return render(request, tplname, ctx) @login_required @permission_required("postfix_relay_domains.change_relaydomain") -def edit(request, rdom_id, tplname='postfix_relay_domains/relaydomain_form.html'): +def edit(request, rdom_id, tplname='common/tabforms.html'): rdom = RelayDomain.objects.get(pk=rdom_id) if not request.user.can_access(rdom): raise PermDeniedException + instances = {'general': rdom} + events.raiseEvent("FillRelayDomainInstances", request.user, rdom, instances) if request.method == 'POST': - form = RelayDomainForm(request.POST, instance=rdom) + rdom.oldname = rdom.name + form = RelayDomainForm(request.user, request.POST, instances=instances) if form.is_valid(): form.save(request.user) events.raiseEvent('RelayDomainModified', rdom) @@ -61,7 +64,7 @@ def edit(request, rdom_id, tplname='postfix_relay_domains/relaydomain_form.html' 'title': rdom.name, 'action_label': _("Update"), 'action_classes': "submit", - 'form': RelayDomainForm(instance=rdom) + 'tabs': RelayDomainForm(request.user, instances=instances) } return render(request, tplname, ctx) diff --git a/modoboa/lib/events.py b/modoboa/lib/events.py index b2e778bbb..cf565e370 100644 --- a/modoboa/lib/events.py +++ b/modoboa/lib/events.py @@ -12,6 +12,7 @@ from functools import wraps import inspect import re from django.conf import settings +from modoboa.lib.sysutils import guess_extension_name events = [] callbacks = {} @@ -117,6 +118,20 @@ def unregister(event, callback): pass +def unregister_extension(extension=None): + """Unregister all callbacks declared by an extension. + + :param str extension: extension full name (ie. module name) + """ + if extension is None: + extension = guess_extension_name() + print extension + for evt, values in callbacks.items(): + for name in values.keys(): + if extension in name: + del callbacks[evt][name] + + def raiseEvent(event, *args, **kwargs): """Raise a specific event diff --git a/modoboa/lib/parameters.py b/modoboa/lib/parameters.py index 61d3372d7..a5c7dfa89 100644 --- a/modoboa/lib/parameters.py +++ b/modoboa/lib/parameters.py @@ -8,11 +8,10 @@ will be available and modifiable directly from the web interface. Only super users will be able to access this part of the web interface. """ -import inspect -import re from django import forms -from . import events -from exceptions import ModoboaException +from modoboa.lib import events +from modoboa.lib.sysutils import guess_extension_name +from modoboa.lib.exceptions import ModoboaException _params = {'A': {}, 'U': {}} @@ -185,7 +184,7 @@ def unregister(app=None): :param app: the application's name (string) """ if app is None: - app = __guess_extension() + app = guess_extension_name() for lvlparams in _params.values(): if app in lvlparams: del lvlparams[app] @@ -198,23 +197,11 @@ def __is_defined(app, level, name): raise NotDefined(app, name) -def __guess_extension(): - """Tries to guess the application's name by inspecting the stack - - :return: a string or None - """ - modname = inspect.getmodule(inspect.stack()[2][0]).__name__ - m = re.match("(?:modoboa\.)?(?:extensions\.)?([^\.$]+)", modname) - if m: - return m.group(1) - return None - - def save_admin(name, value, app=None): from .models import Parameter if app is None: - app = __guess_extension() + app = guess_extension_name() __is_defined(app, 'A', name) fullname = "%s.%s" % (app, name) try: @@ -231,7 +218,7 @@ def save_user(user, name, value, app=None): from .models import UserParameter if app is None: - app = __guess_extension() + app = guess_extension_name() __is_defined(app, 'U', name) fullname = "%s.%s" % (app, name) try: @@ -256,7 +243,7 @@ def get_admin(name, app=None, raise_error=True): from .models import Parameter if app is None: - app = __guess_extension() + app = guess_extension_name() try: __is_defined(app, "A", name) except NotDefined: @@ -283,7 +270,7 @@ def get_user(user, name, app=None, raise_error=True): from .models import UserParameter if app is None: - app = __guess_extension() + app = guess_extension_name() try: __is_defined(app, "U", name) except NotDefined: @@ -349,7 +336,7 @@ def get_parameter_form(level, name, app=None): :return: a form class """ if app is None: - app = __guess_extension() + app = guess_extension_name() __is_defined(app, level, name) return _params[level][app]["form"] diff --git a/modoboa/lib/sysutils.py b/modoboa/lib/sysutils.py index 237f9ea64..02af77148 100644 --- a/modoboa/lib/sysutils.py +++ b/modoboa/lib/sysutils.py @@ -5,6 +5,8 @@ This module extra functions/shortcuts to communicate with the system (executing commands, etc.) """ import subprocess +import inspect +import re def exec_cmd(cmd, sudo_user=None, **kwargs): @@ -24,3 +26,15 @@ def exec_cmd(cmd, sudo_user=None, **kwargs): stderr=subprocess.STDOUT, **kwargs) output = p.communicate()[0] return p.returncode, output + + +def guess_extension_name(): + """Tries to guess the application's name by inspecting the stack. + + :return: a string or None + """ + modname = inspect.getmodule(inspect.stack()[2][0]).__name__ + match = re.match(r"(?:modoboa\.)?(?:extensions\.)?([^\.$]+)", modname) + if match is not None: + return match.group(1) + return None