mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-18 05:51:26 +02:00
Merge branch 'master' into d3js
Conflicts: modoboa/extensions/stats/grapher.py
This commit is contained in:
+1
-1
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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")
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
@@ -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",
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user