Merge branch 'master' into d3js

Conflicts:
	modoboa/extensions/stats/grapher.py
This commit is contained in:
Antoine Nguyen
2014-04-07 16:45:44 +02:00
28 changed files with 188 additions and 78 deletions
+1 -1
View File
@@ -50,7 +50,7 @@ copyright = u'2014, Antoine Nguyen'
# The short X.Y version.
version = '1.1'
# The full version, including alpha/beta/rc tags.
release = '1.1.0'
release = '1.1.1'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
+1 -1
View File
@@ -41,7 +41,7 @@ Then, you need to register this application using the provided
API. Just copy/paste the following example into the :file:`__init__.py` file
of the future extension::
from modoboa.extensions import ModoExtension, exts_pool
from modoboa.core.extensions import ModoExtension, exts_pool
class MyExtension(ModoExtension):
name = "myext"
+15 -10
View File
@@ -58,16 +58,16 @@ Replace values between ``<>`` by yours.
Edit the :file:`/etc/postfix/main.cf` file and copy the following
lines inside::
relay_domains = <driver>:/etc/postfix/maps/sql-relaydomains.cf
relay_domains = <driver>:/etc/postfix/sql-relaydomains.cf
transport_maps =
<driver>:/etc/postfix/maps/sql-relaydomains-transport.cf
<driver>:/etc/postfix/maps/sql-relaydomain-aliases-transport.cf
<driver>:/etc/postfix/sql-relaydomains-transport.cf
<driver>:/etc/postfix/sql-relaydomain-aliases-transport.cf
smtpd_recipient_restrictions =
permit_mynetworks
reject_unauth_destination
check_recipient_access
<driver>:/etc/postfix/maps/sql-relay-recipient-verification.cf
<driver>:/etc/postfix/sql-relay-recipient-verification.cf
Replace ``<driver>`` by the name of the database you use.
@@ -126,6 +126,11 @@ Replace values between ``<>`` with yours.
following the `official documentation
<http://www.amavis.org/#doc>`_.
.. note::
Amavis configuration allows for separate lookup and storage
databases but Modoboa doesn't support it yet.
Cleanup
-------
@@ -327,11 +332,11 @@ configuration files as follows:
``/etc/postfix/main.cf``::
transport_maps = mysql:/etc/postfix/maps/sql-autoreplies-transport.cf
virtual_alias_maps = <driver>:/etc/postfix/maps/sql-aliases.cf
<driver>:/etc/postfix/mapfiles/sql-domain-aliases-mailboxes.cf,
<driver>:/etc/postfix/maps/sql-autoreplies.cf,
<driver>:/etc/postfix/mapfiles/sql-catchall-aliases.cf
transport_maps = <driver>:/etc/postfix/sql-autoreplies-transport.cf
virtual_alias_maps = <driver>:/etc/postfix/sql-aliases.cf
<driver>:/etc/postfix/sql-domain-aliases-mailboxes.cf,
<driver>:/etc/postfix/sql-autoreplies.cf,
<driver>:/etc/postfix/sql-catchall-aliases.cf
.. note::
@@ -340,7 +345,7 @@ configuration files as follows:
``/etc/postfix/master.cf``::
autoreply unix - n n - - pipe
flags= user=vmail:<group> argv=<modoboa_site>/manage.py autoreply $sender $mailbox
flags= user=vmail:<group> argv=python <modoboa_site>/manage.py autoreply $sender $mailbox
Replace ``<driver>`` by the name of the database you
use. ``<modoboa_site>`` is the path of your Modoboa instance.
+21
View File
@@ -48,6 +48,27 @@ Finally, follow the common procedure::
$ python manage.py syncdb --migrate
$ python manage.py collectstatic
1.1.2: Audit trail issues
=========================
Update the :file:`settings.py` file as follows:
#. Remove the ``'reversion.middleware.RevisionMiddleware'``
middleware from the ``MIDDLEWARE_CLASSES`` variable
#. Add the new ``'modoboa.lib.middleware.RequestCatcherMiddleware'``
middleware at the end of the ``MIDDLEWARE_CLASSES`` variable
1.1.1: Few bugfixes
===================
For those who installed Dovecot in a non-standard location, it is now
possible to tell Modoboa where to find it. Just define a variable
named ``DOVECOT_LOOKUP_PATH`` in the :file:`settings.py` file and
include the appropriate lookup path inside::
DOVECOT_LOOKUP_PATH = ("/usr/sbin/dovecot", "/usr/local/sbin/dovecot")
.. _1.1.0:
1.1.0: relay domains and better passwords encryption
+7 -5
View File
@@ -28,19 +28,19 @@ Then, edit the ``inbox`` namespace and add the following lines::
inbox = yes
mailbox Drafts {
auto = create
auto = subscribe
special_use = \Drafts
}
mailbox Junk {
auto = create
auto = subscribe
special_use = \Junk
}
mailbox Sent {
auto = create
auto = subscribe
special_use = \Sent
}
mailbox Trash {
auto = create
auto = subscribe
special_use = \Trash
}
@@ -357,8 +357,10 @@ Copy this example into a file (for example: :file:`quota-trigger.sql`) on
server running postgres and execute the following commands::
$ su - postgres
$ psql < /path/to/quota-trigger.sql
$ psql [modoboa database] < /path/to/quota-trigger.sql
$ exit
Replace ``[modoboa database]`` by the appropriate value.
Forcing recalculation
---------------------
@@ -113,11 +113,11 @@ MIDDLEWARE_CLASSES = (
'django.middleware.locale.LocaleMiddleware',
# Uncomment the next line for simple clickjacking protection:
# 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'reversion.middleware.RevisionMiddleware',
'modoboa.lib.middleware.AjaxLoginRedirect',
'modoboa.lib.middleware.CommonExceptionCatcher',
'modoboa.lib.middleware.ExtControlMiddleware',
'modoboa.lib.middleware.RequestCatcherMiddleware',
)
ROOT_URLCONF = '{{ name }}.urls'
+16 -5
View File
@@ -472,10 +472,20 @@ class Log(models.Model):
@receiver(reversion.post_revision_commit)
def post_revision_commit(sender, **kwargs):
if kwargs["revision"].user is None:
return
"""Custom post-revision hook.
We want to track all creations and modifications of admin. objects
(alias, mailbox, user, domain, domain alias, etc.) so we use
django-reversion for that.
"""
from modoboa.lib.signals import get_request
current_user = get_request().user.username
logger = logging.getLogger("modoboa.admin")
for version in kwargs["versions"]:
if version.object is None:
continue
prev_revisions = reversion.get_for_object(version.object)
if prev_revisions.count() == 1:
action = _("added")
@@ -486,18 +496,19 @@ def post_revision_commit(sender, **kwargs):
message = _("%(object)s '%(name)s' %(action)s by user %(user)s") % {
"object": unicode(version.content_type).capitalize(),
"name": version.object_repr, "action": action,
"user": kwargs["revision"].user.username
"user": current_user
}
getattr(logger, level)(message)
@receiver(post_delete)
def post_delete_hook(sender, instance, **kwargs):
def log_object_removal(sender, instance, **kwargs):
"""Custom post-delete hook.
We want to know who was responsible for an object deletion.
"""
from reversion.models import Version
from modoboa.lib.signals import get_request
if not reversion.is_registered(sender):
return
@@ -510,6 +521,6 @@ def post_delete_hook(sender, instance, **kwargs):
msg = _("%(object)s '%(name)s' %(action)s by user %(user)s") % {
"object": unicode(version.content_type).capitalize(),
"name": version.object_repr, "action": _("deleted"),
"user": version.revision.user.username
"user": get_request().user.username
}
logger.critical(msg)
+7 -1
View File
@@ -3,7 +3,7 @@ from django.utils.translation import ugettext as _, ugettext_lazy
from django.core.urlresolvers import reverse
from modoboa.core.extensions import ModoExtension, exts_pool
from modoboa.lib import parameters, events
from modoboa.lib.exceptions import PermDeniedException, BadRequest
from modoboa.lib.exceptions import PermDeniedException, BadRequest, Conflict
from modoboa.lib.emailutils import split_mailbox
from modoboa.extensions.admin.models import (
Domain, DomainAlias, Mailbox, Alias
@@ -164,6 +164,12 @@ def import_account_mailbox(user, account, row):
)
if not user.can_access(domain):
raise PermDeniedException
try:
mb = Mailbox.objects.get(address=mailbox, domain=domain)
except Mailbox.DoesNotExist:
pass
else:
raise Conflict(_("Mailbox %s already exists" % account.email))
quota = int(row[1].strip())
use_domain_quota = True if not quota else False
mb = Mailbox(address=mailbox, domain=domain,
+5
View File
@@ -1,3 +1,4 @@
import reversion
from django.db import transaction, IntegrityError
from django.utils.translation import ugettext as _, ungettext
from django.shortcuts import render
@@ -56,6 +57,7 @@ def _new_alias(request, title, action, successmsg,
@login_required
@permission_required("admin.add_alias")
@transaction.commit_on_success
@reversion.create_revision()
def newdlist(request):
return _new_alias(
request, _("New distribution list"), reverse(newdlist),
@@ -66,6 +68,7 @@ def newdlist(request):
@login_required
@permission_required("admin.add_alias")
@transaction.commit_on_success
@reversion.create_revision()
def newalias(request):
return _new_alias(
request, _("New alias"), reverse(newalias),
@@ -76,6 +79,7 @@ def newalias(request):
@login_required
@permission_required("admin.add_alias")
@transaction.commit_on_success
@reversion.create_revision()
def newforward(request):
return _new_alias(
request, _("New forward"), reverse(newforward),
@@ -86,6 +90,7 @@ def newforward(request):
@login_required
@permission_required("admin.change_alias")
@transaction.commit_on_success
@reversion.create_revision()
def editalias(request, alid, tplname="admin/aliasform.html"):
alias = Alias.objects.get(pk=alid)
if not request.user.can_access(alias):
+3
View File
@@ -1,3 +1,4 @@
import reversion
from django.http import HttpResponseRedirect
from django.utils.translation import ugettext as _, ungettext
from django.core.urlresolvers import reverse
@@ -88,6 +89,7 @@ def domains_list(request):
@login_required
@permission_required("admin.add_domain")
@transaction.commit_on_success
@reversion.create_revision()
def newdomain(request, tplname="common/wizard_forms.html"):
events.raiseEvent("CanCreate", request.user, "domains")
@@ -139,6 +141,7 @@ def newdomain(request, tplname="common/wizard_forms.html"):
@login_required
@permission_required("admin.view_domains")
@transaction.commit_on_success
@reversion.create_revision()
def editdomain(request, dom_id, tplname="admin/editdomainform.html"):
domain = Domain.objects.get(pk=dom_id)
if not request.user.can_access(domain):
+4 -1
View File
@@ -1,3 +1,4 @@
import reversion
from django.shortcuts import render
from django.db import transaction
from django.utils.translation import ugettext as _, ungettext
@@ -8,7 +9,7 @@ from django.contrib.auth.decorators import (
from django.views.decorators.csrf import ensure_csrf_cookie
from modoboa.lib import parameters, events
from modoboa.lib.exceptions import (
ModoboaException, PermDeniedException, BadRequest
PermDeniedException, BadRequest
)
from modoboa.lib.webutils import (
_render_to_string, render_to_json_response
@@ -114,6 +115,7 @@ def list_quotas(request, tplname="admin/quotas.html"):
@login_required
@permission_required("core.add_user")
@transaction.commit_on_success
@reversion.create_revision()
def newaccount(request, tplname='common/wizard_forms.html'):
"""Create a new account.
@@ -166,6 +168,7 @@ def newaccount(request, tplname='common/wizard_forms.html'):
@login_required
@permission_required("core.change_user")
@transaction.commit_on_success
@reversion.create_revision()
def editaccount(request, accountid, tplname="common/tabforms.html"):
account = User.objects.get(pk=accountid)
if not request.user.can_access(account):
+3 -1
View File
@@ -1,8 +1,9 @@
import csv
import reversion
from django.utils.translation import ugettext as _
from django.shortcuts import render
from django.core.urlresolvers import reverse
from django.db import transaction, IntegrityError
from django.db import transaction
from django.contrib.auth.decorators import (
login_required, permission_required, user_passes_test
)
@@ -86,6 +87,7 @@ def import_dlist(user, row, formopts):
_import_alias(user, row)
@reversion.create_revision()
def importdata(request, formclass=ImportDataForm):
"""Generic import function
+2 -1
View File
@@ -1,6 +1,6 @@
import reversion
from django.contrib.auth.decorators import login_required
from django.utils.translation import ugettext as _
from modoboa.lib.exceptions import ModoboaException
from modoboa.lib.webutils import (
render_to_json_response, _render_to_string
)
@@ -11,6 +11,7 @@ from modoboa.extensions.admin.forms import ForwardForm
@login_required
@needs_mailbox()
@reversion.create_revision()
def forward(request, tplname='admin/forward.html'):
mb = request.user.mailbox_set.all()[0]
try:
@@ -50,7 +50,7 @@ class Command(BaseCommand):
flags += ['R']
self.__vprint("Deleting marked messages...")
ids = Msgrcpt.objects.filter(rs__in=flags).values("mail_id")
ids = Msgrcpt.objects.filter(rs__in=flags).values("mail_id").distinct()
for msg in Msgs.objects.filter(mail_id__in=ids):
if not msg.msgrcpt_set.exclude(rs__in=flags).count():
msg.delete()
+1 -1
View File
@@ -65,7 +65,7 @@ class Msgs(models.Model):
class Msgrcpt(models.Model):
partition_tag = models.IntegerField(null=True, blank=True)
mail = models.ForeignKey(Msgs, primary_key=True)
rid = models.ForeignKey(Maddr, db_column='rid', primary_key=True)
rid = models.ForeignKey(Maddr, db_column='rid')
rseqnum = models.IntegerField(default=0)
is_local = models.CharField(max_length=3)
content = models.CharField(max_length=3)
+33 -5
View File
@@ -9,7 +9,7 @@ from modoboa.lib.webutils import static_url
from modoboa.lib.email_listing import MBconnector, EmailListing
from modoboa.lib.emailutils import Email
from modoboa.lib.dbutils import db_type
from modoboa.extensions.admin.models import Domain, Alias
from modoboa.extensions.admin.models import Domain
from .models import Quarantine, Msgrcpt
@@ -46,9 +46,10 @@ class Qtable(tables.Table):
"date", label=ugettext_lazy("Date"), sort_order="date",
cssclass="openable"
)
cols_order = [
'selection', 'type', 'rstatus', 'score', 'to', 'from_', 'subject', 'time'
'selection', 'type', 'rstatus', 'score', 'to', 'from_', 'subject',
'time'
]
def parse_type(self, value):
@@ -101,7 +102,7 @@ class SQLconnector(MBconnector):
order = self.order_translation_table[order]
self.messages = self.messages.order_by(sign + order)
self.count = len(self.messages)
self.count = self.messages.count()
return self.count
def fetch(self, start=None, stop=None, **kwargs):
@@ -115,7 +116,9 @@ class SQLconnector(MBconnector):
"type": qm["mail__msgrcpt__content"],
"score": qm["mail__msgrcpt__bspam_level"]}
rs = qm["mail__msgrcpt__rs"]
if rs == '':
if rs == 'D':
continue
elif rs == '':
m["class"] = "unseen"
elif rs == 'R':
m["img_rstatus"] = static_url("pics/release.png")
@@ -138,6 +141,18 @@ class SQLWrapper(object):
See ``PgWrapper`` for the real mess...
"""
def _exec(self, query, args):
"""Execute a raw SQL query.
:param string query: query to execute
:param list args: a list of arguments to replace in :kw:`query`
"""
from django.db import connections, transaction
cursor = connections['amavis'].cursor()
cursor.execute(query, args)
transaction.commit_unless_managed(using='amavis')
def get_mails(self, request, rcptfilter=None):
"""Retrieve all messages visible by a user.
@@ -163,6 +178,19 @@ class SQLWrapper(object):
def get_recipient_message(self, address, mailid):
return Msgrcpt.objects.get(mail=mailid, rid__email=address)
def set_msgrcpt_status(self, address, mailid, status):
"""Change the status (rs field) of a message recipient.
:param string status: status
"""
from modoboa.extensions.amavis.models import Maddr
addr = Maddr.objects.get(email=address)
self._exec(
"UPDATE msgrcpt SET rs=%s WHERE mail_id=%s AND rid=%s",
[status, mailid, addr.id]
)
def get_recipient_messages(self, address, mailids):
return Msgrcpt.objects.filter(mail__in=mailids, rid__email=address)
+14 -24
View File
@@ -57,7 +57,6 @@ def _listing(request):
else:
raise BadRequest("unsupported search criteria %s" % c)
flt = nfilter if flt is None else flt | nfilter
msgtype = navparams.get('msgtype', None)
if msgtype is not None:
nfilter = Q(mail__msgrcpt__content=msgtype)
@@ -140,9 +139,7 @@ def viewmail(request, mail_id):
if request.user.mailbox_set.count():
mb = Mailbox.objects.get(user=request.user)
if rcpt in mb.alias_addresses:
msgrcpt = get_wrapper().get_recipient_message(rcpt, mail_id)
msgrcpt.rs = 'V'
msgrcpt.save()
get_wrapper().set_msgrcpt_status(rcpt, mail_id, 'V')
content = Template("""
<iframe src="{{ url }}" id="mailcontent"></iframe>
@@ -178,9 +175,7 @@ def delete_selfservice(request, mail_id):
if rcpt is None:
raise BadRequest(_("Invalid request"))
try:
msgrcpt = get_wrapper().get_recipient_message(rcpt, mail_id)
msgrcpt.rs = 'D'
msgrcpt.save()
get_wrapper().set_msgrcpt_status(rcpt, mail_id, 'D')
except Msgrcpt.DoesNotExist:
raise BadRequest(_("Invalid request"))
return render_to_json_response(_("Message deleted"))
@@ -201,9 +196,7 @@ def delete(request, mail_id):
if mb is not None and r != mb.full_address \
and not r in mb.alias_addresses:
continue
msgrcpt = wrapper.get_recipient_message(r, i)
msgrcpt.rs = 'D'
msgrcpt.save()
wrapper.set_msgrcpt_status(r, i, 'D')
message = ungettext("%(count)d message deleted successfully",
"%(count)d messages deleted successfully",
len(mail_id)) % {"count": len(mail_id)}
@@ -218,24 +211,24 @@ def release_selfservice(request, mail_id):
secret_id = request.GET.get("secret_id", None)
if rcpt is None or secret_id is None:
raise BadRequest(_("Invalid request"))
wrapper = get_wrapper()
try:
msgrcpt = get_wrapper().get_recipient_message(rcpt, mail_id)
msgrcpt = wrapper.get_recipient_message(rcpt, mail_id)
except Msgrcpt.DoesNotExist:
raise BadRequest(_("Invalid request"))
if secret_id != msgrcpt.mail.secret_id:
raise BadRequest(_("Invalid request"))
if parameters.get_admin("USER_CAN_RELEASE") == "no":
msgrcpt.rs = 'p'
wrapper.set_msgrcpt_status(rcpt, mail_id, 'p')
msg = _("Request sent")
else:
amr = AMrelease()
result = amr.sendreq(mail_id, secret_id, rcpt)
if result:
rcpt.rs = 'R'
wrapper.set_msgrcpt_status(rcpt, mail_id, 'R')
msg = _("Message released")
else:
raise BadRequest(result)
msgrcpt.save()
return render_to_json_response(msg)
@@ -257,14 +250,10 @@ def release(request, mail_id):
continue
msgrcpts += [wrapper.get_recipient_message(r, i)]
if mb is not None and parameters.get_admin("USER_CAN_RELEASE") == "no":
# FIXME : can't use this syntax because extra SQL (using
# .extra() for postgres) is not propagated (the 'tables'
# parameter is lost somewhere...)
#
# msgrcpts.update(rs='p')
for msgrcpt in msgrcpts:
msgrcpt.rs = 'p'
msgrcpt.save()
wrapper.set_msgrcpt_status(
msgrcpt.rid.email, msgrcpt.mail.mail_id, 'p'
)
message = ungettext("%(count)d request sent",
"%(count)d requests sent",
len(mail_id)) % {"count": len(mail_id)}
@@ -276,10 +265,11 @@ def release(request, mail_id):
amr = AMrelease()
error = None
for rcpt in msgrcpts:
result = amr.sendreq(rcpt.mail.mail_id, rcpt.mail.secret_id, rcpt.rid.email)
result = amr.sendreq(
rcpt.mail.mail_id, rcpt.mail.secret_id, rcpt.rid.email
)
if result:
rcpt.rs = 'R'
rcpt.save()
wrapper.set_msgrcpt_status(rcpt.rid.email, rcpt.mail.mail_id, 'R')
else:
error = result
break
@@ -1,3 +1,4 @@
import reversion
from django.shortcuts import render
from django.db import transaction
from django.core.urlresolvers import reverse
@@ -16,6 +17,7 @@ from .forms import RelayDomainForm, RelayDomainFormGeneral
@login_required
@permission_required("postfix_relay_domains.add_relaydomain")
@transaction.commit_on_success
@reversion.create_revision()
def create(request, tplname="postfix_relay_domains/new_relaydomain_form.html"):
events.raiseEvent("CanCreate", request.user, "relay_domains")
if request.method == 'POST':
@@ -39,6 +41,7 @@ def create(request, tplname="postfix_relay_domains/new_relaydomain_form.html"):
@login_required
@permission_required("postfix_relay_domains.change_relaydomain")
@reversion.create_revision()
def edit(request, rdom_id, tplname='common/tabforms.html'):
rdom = RelayDomain.objects.get(pk=rdom_id)
if not request.user.can_access(rdom):
-6
View File
@@ -21,9 +21,3 @@ class ParametersForm(AdminParametersForm):
initial="/tmp/modoboa",
help_text=ugettext_lazy("Path to directory where RRD files are stored")
)
img_rootdir = forms.CharField(
label=ugettext_lazy("Directory to store PNG files"),
initial="/tmp/modoboa",
help_text=ugettext_lazy("Path to directory where PNG files are stored")
)
+7 -1
View File
@@ -415,6 +415,9 @@ class IMAPconnector(object):
@capability('LIST-EXTENDED', '_listmboxes_simple')
def _listmboxes(self, topmailbox, mailboxes, until_mailbox=None):
"""Retrieve mailboxes list.
"""
pattern = "%s%s%%" % (topmailbox.encode("imap4-utf-7"), self.hdelimiter) \
if len(topmailbox) else "%"
resp = self._cmd("LIST", "", pattern, "RETURN", "(CHILDREN)")
@@ -443,6 +446,8 @@ class IMAPconnector(object):
if r'\Marked' in flags or not r'\UnMarked' in flags:
descr["send_status"] = True
if r'\HasChildren' in flags:
if r'\NonExistent' in flags:
descr["removed"] = True
descr["path"] = name
descr["sub"] = []
if until_mailbox and until_mailbox.startswith(name):
@@ -489,7 +494,8 @@ class IMAPconnector(object):
continue
del mb["send_status"]
key = "path" if "path" in mb else "name"
count = self.unseen_messages(mb[key])
if not mb.get("removed", False):
count = self.unseen_messages(mb[key])
if count == 0:
continue
mb["unseen"] = count
+1 -1
View File
@@ -3,7 +3,7 @@ import os
import re
from datetime import datetime, timedelta
import email
import lxml
import lxml.html
import chardet
from rfc6266 import build_header, parse_headers
from django.core.files.uploadhandler import FileUploadHandler, SkipFile
@@ -693,8 +693,14 @@ Webmail.prototype = {
},
listmailbox_loader: function(event) {
var $target = get_target(event, "a");
if ($target.parent().hasClass("disabled")) {
event.preventDefault();
return;
}
this.select_mailbox(event.target);
this._listmailbox_loader(event, get_target(event, "a"));
this._listmailbox_loader(event, $target);
},
/*
@@ -132,7 +132,9 @@ def print_mailboxes(tree, selected=None, withunseen=False, selectonly=False, hde
cssclass = ""
name = mbox["path"] if "sub" in mbox else mbox["name"]
label = separate_mailbox(mbox["name"], hdelimiter)[0]
if selected == name:
if mbox.get("removed", False):
cssclass = "disabled"
elif selected == name:
cssclass = "active"
result += "<li name='%s' class='droppable %s'>\n" % (name, cssclass)
if "sub" in mbox:
-8
View File
@@ -39,14 +39,6 @@
"value": "vmail"
}
},
{
"pk": 12,
"model": "lib.parameter",
"fields": {
"name": "stats.IMG_ROOTDIR",
"value": "/srv/www/modoboa/media/stats"
}
},
{
"pk": 13,
"model": "lib.parameter",
+20
View File
@@ -1,4 +1,7 @@
# coding: utf-8
"""
Custom middlewares.
"""
import re
from django.http import Http404, HttpResponseRedirect
from modoboa.core.models import Extension
@@ -7,6 +10,7 @@ from modoboa.lib.webutils import (
_render_error, ajax_response, render_to_json_response
)
from modoboa.lib.exceptions import ModoboaException
from modoboa.lib.signals import request_accessor
class ExtControlMiddleware(object):
@@ -50,3 +54,19 @@ class CommonExceptionCatcher(object):
return render_to_json_response(
unicode(exception), status=exception.http_code
)
class RequestCatcherMiddleware(object):
"""
Simple middleware to store the current request.
"""
def __init__(self):
self._request = None
request_accessor.connect(self)
def process_request(self, request):
self._request = request
def __call__(self, **kwargs):
return self._request
+11
View File
@@ -0,0 +1,11 @@
"""
Custom signals.
"""
from django.dispatch import Signal
request_accessor = Signal()
def get_request():
"""Get the current request from anywhere.
"""
return request_accessor.send(None)[0][1]
+1 -1
View File
@@ -54,7 +54,7 @@ def read(fname):
setup(
name="modoboa",
version='1.1.0',
version='1.1.1',
url='http://modoboa.org/',
license='MIT',
description="Mail hosting made simple",
-1
View File
@@ -21,7 +21,6 @@ class DeployTest(unittest.TestCase):
def tearDown(self):
path = os.path.join(self.workdir, self.projname)
code, output = exec_cmd("python manage.py test core lib admin limits postfix_relay_domains", cwd=path)
print output
self.assertEqual(code, 0)
def test_standard(self):