Quarantine plugin update:

* Using django's orm to access amavis database
* Many UI improvments
* Better use of the RS field

fixed #88 & #72
see #155
This commit is contained in:
Antoine Nguyen
2011-08-23 16:36:57 +02:00
parent 893dcfdb3f
commit 968f91be1a
19 changed files with 505 additions and 305 deletions
+34 -18
View File
@@ -1,33 +1,49 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from modoboa.lib import db, parameters
import time
from modoboa.extensions.amavis_quarantine.models import *
if __name__ == "__main__":
from modoboa.extensions.amavis_quarantine import main
from modoboa.lib import parameters
from optparse import OptionParser
parser = OptionParser()
parser.add_option("--debug", action="store_true",
help="Activate debug output")
options, args = parser.parse_args()
if options.debug:
import logging
l = logging.getLogger("django.db.backends")
l.setLevel(logging.DEBUG)
l.addHandler(logging.StreamHandler())
main.init()
conn = db.getconnection("amavis_quarantine")
max_messages_age = int(parameters.get_admin("MAX_MESSAGES_AGE",
app="amavis_quarantine"))
flags = ['D']
if parameters.get_admin("RELEASED_MSGS_CLEANUP",
app="amavis_quarantine") == "yes":
flags += ['R']
print "Deleting marked messages..."
ids = Msgrcpt.objects.filter(rs__in=flags).values("mail_id")
for msg in Msgs.objects.filter(mail_id__in=ids):
if not msg.msgrcpt_set.exclude(rs__in=flags).count():
msg.delete()
print "Deleting messages older than %d days..." % max_messages_age
db.execute(conn, """
DELETE FROM msgs WHERE time_num < UNIX_TIMESTAMP()-%d*24*3600;
""" % max_messages_age)
limit = int(time.time()) - (max_messages_age * 24 * 3600)
Msgs.objects.filter(time_num__lt=limit).delete()
print "Deleting unreferenced e-mail addresses..."
db.execute(conn, """
DELETE FROM maddr
WHERE NOT EXISTS (SELECT 1 FROM msgs WHERE sid=id)
AND NOT EXISTS (SELECT 1 FROM msgrcpt WHERE rid=id);
""")
print "Optimizing tables..."
db.execute(conn, """
OPTIMIZE TABLE msgs, msgrcpt, quarantine, maddr;
""")
db.close(conn)
for maddr in Maddr.objects.all():
if not len(maddr.msgs_set.all()) and not len(maddr.msgrcpt_set.all()):
maddr.delete()
print "Done."
+30
View File
@@ -0,0 +1,30 @@
class AmavisRouter(object):
"""A router to control all database operations on models in
the amavis_quarantine application"""
def db_for_read(self, model, **hints):
"Point all operations on amavis_quarantine models to 'amavis'"
if model._meta.app_label == 'amavis_quarantine':
return 'amavis'
return None
def db_for_write(self, model, **hints):
"Point all operations on amavis_quarantine models to 'amavis'"
if model._meta.app_label == 'amavis_quarantine':
return 'amavis'
return None
def allow_relation(self, obj1, obj2, **hints):
"Allow any relation if a model in amavis_quarantine is involved"
if obj1._meta.app_label == 'amavis_quarantine' \
or obj2._meta.app_label == 'amavis_quarantine':
return True
return None
def allow_syncdb(self, db, model):
"Make sure the amavis_quarantine app only appears on the 'amavis' db"
if db == 'amavis':
return model._meta.app_label == 'amavis_quarantine'
elif model._meta.app_label == 'amavis_quarantine':
return False
return None
+2
View File
@@ -19,6 +19,8 @@ def init():
parameters.register_admin("MAX_MESSAGES_AGE", type="int",
deflt=14,
help=_("Quarantine messages maximum age (in days) before deletion"))
parameters.register_admin("RELEASED_MSGS_CLEANUP", type="list_yesno", deflt="no",
help=_("Remove messages marked as released while cleaning up the database"))
parameters.register_admin("AM_PDP_MODE", type="list",
deflt="unix",
values=[("inet", "inet"), ("unix", "unix")],
+141
View File
@@ -0,0 +1,141 @@
# This is an auto-generated Django model module.
# You'll have to do the following manually to clean this up:
# * Rearrange models' order
# * Make sure each model has one field with primary_key=True
# Feel free to rename the models, but don't rename db_table values or field names.
#
# Also note: You'll have to insert the output of 'django-admin.py sqlcustom [appname]'
# into your database.
#
# Original Amavis version : 2.6.2
from django.db import models
class Maddr(models.Model):
partition_tag = models.IntegerField(unique=True, null=True, blank=True)
id = models.BigIntegerField(primary_key=True)
email = models.CharField(unique=True, max_length=255)
domain = models.CharField(max_length=765)
class Meta:
db_table = u'maddr'
managed = False
class Mailaddr(models.Model):
id = models.IntegerField(primary_key=True)
priority = models.IntegerField()
email = models.CharField(unique=True, max_length=255)
class Meta:
db_table = u'mailaddr'
managed = False
class Msgs(models.Model):
partition_tag = models.IntegerField(null=True, blank=True)
mail_id = models.CharField(max_length=12, primary_key=True)
secret_id = models.CharField(max_length=12, blank=True)
am_id = models.CharField(max_length=60)
time_num = models.IntegerField()
time_iso = models.CharField(max_length=48)
sid = models.ForeignKey(Maddr, db_column='sid')
policy = models.CharField(max_length=765, blank=True)
client_addr = models.CharField(max_length=765, blank=True)
size = models.IntegerField()
content = models.CharField(max_length=1, blank=True)
quar_type = models.CharField(max_length=1, blank=True)
quar_loc = models.CharField(max_length=255, blank=True)
dsn_sent = models.CharField(max_length=3, blank=True)
spam_level = models.FloatField(null=True, blank=True)
message_id = models.CharField(max_length=765, blank=True)
from_addr = models.CharField(max_length=765, blank=True)
subject = models.CharField(max_length=765, blank=True)
host = models.CharField(max_length=765)
class Meta:
db_table = u'msgs'
managed = False
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)
ds = models.CharField(max_length=3)
rs = models.CharField(max_length=3)
bl = models.CharField(max_length=3, blank=True)
wl = models.CharField(max_length=3, blank=True)
bspam_level = models.FloatField(null=True, blank=True)
smtp_resp = models.CharField(max_length=765, blank=True)
class Meta:
db_table = u'msgrcpt'
managed = False
unique_together = ("mail", "rid")
class Policy(models.Model):
id = models.IntegerField(primary_key=True)
policy_name = models.CharField(max_length=96, blank=True)
virus_lover = models.CharField(max_length=3, blank=True)
spam_lover = models.CharField(max_length=3, blank=True)
banned_files_lover = models.CharField(max_length=3, blank=True)
bad_header_lover = models.CharField(max_length=3, blank=True)
bypass_virus_checks = models.CharField(max_length=3, blank=True)
bypass_spam_checks = models.CharField(max_length=3, blank=True)
bypass_banned_checks = models.CharField(max_length=3, blank=True)
bypass_header_checks = models.CharField(max_length=3, blank=True)
spam_modifies_subj = models.CharField(max_length=3, blank=True)
virus_quarantine_to = models.CharField(max_length=192, blank=True)
spam_quarantine_to = models.CharField(max_length=192, blank=True)
banned_quarantine_to = models.CharField(max_length=192, blank=True)
bad_header_quarantine_to = models.CharField(max_length=192, blank=True)
clean_quarantine_to = models.CharField(max_length=192, blank=True)
other_quarantine_to = models.CharField(max_length=192, blank=True)
spam_tag_level = models.FloatField(null=True, blank=True)
spam_tag2_level = models.FloatField(null=True, blank=True)
spam_kill_level = models.FloatField(null=True, blank=True)
spam_dsn_cutoff_level = models.FloatField(null=True, blank=True)
spam_quarantine_cutoff_level = models.FloatField(null=True, blank=True)
addr_extension_virus = models.CharField(max_length=192, blank=True)
addr_extension_spam = models.CharField(max_length=192, blank=True)
addr_extension_banned = models.CharField(max_length=192, blank=True)
addr_extension_bad_header = models.CharField(max_length=192, blank=True)
warnvirusrecip = models.CharField(max_length=3, blank=True)
warnbannedrecip = models.CharField(max_length=3, blank=True)
warnbadhrecip = models.CharField(max_length=3, blank=True)
newvirus_admin = models.CharField(max_length=192, blank=True)
virus_admin = models.CharField(max_length=192, blank=True)
banned_admin = models.CharField(max_length=192, blank=True)
bad_header_admin = models.CharField(max_length=192, blank=True)
spam_admin = models.CharField(max_length=192, blank=True)
spam_subject_tag = models.CharField(max_length=192, blank=True)
spam_subject_tag2 = models.CharField(max_length=192, blank=True)
message_size_limit = models.IntegerField(null=True, blank=True)
banned_rulenames = models.CharField(max_length=192, blank=True)
class Meta:
db_table = u'policy'
managed = False
class Quarantine(models.Model):
partition_tag = models.IntegerField(null=True, blank=True)
mail = models.ForeignKey(Msgs, primary_key=True)
chunk_ind = models.IntegerField()
mail_text = models.TextField()
class Meta:
db_table = u'quarantine'
managed = False
ordering = ["-mail__time_num"]
unique_together = ("mail", "chunk_ind")
class Users(models.Model):
id = models.IntegerField(primary_key=True)
priority = models.IntegerField()
policy_id = models.IntegerField()
email = models.CharField(unique=True, max_length=255)
fullname = models.CharField(max_length=765, blank=True)
local = models.CharField(max_length=3, blank=True)
class Meta:
db_table = u'users'
managed = False
class Wblist(models.Model):
rid = models.IntegerField(primary_key=True)
sid = models.IntegerField(primary_key=True)
wb = models.CharField(max_length=30)
class Meta:
db_table = u'wblist'
managed = False
+49 -59
View File
@@ -2,9 +2,11 @@
from datetime import datetime
from django.utils.translation import ugettext as _
from modoboa.lib import tables, db, static_url
from django.db.models import Q
from modoboa.lib import tables, static_url
from modoboa.lib.email_listing import MBconnector, EmailListing
from modoboa.lib.emailutils import *
from models import *
class Qtable(tables.Table):
tableid = "emails"
@@ -16,7 +18,7 @@ class Qtable(tables.Table):
from_ = tables.Column("from", label=_("From"), limit=30)
subject = tables.Column("subject", label=_("Subject"), limit=40)
time = tables.Column("date", label=_("Date"))
to = tables.Column("to", label=_("To"), limit=30)
to = tables.Column("to", label=_("To"), sortable=False)
cols_order = ['type', 'rstatus', 'to', 'from_', 'subject', 'time']
@@ -24,64 +26,51 @@ class Qtable(tables.Table):
return datetime.fromtimestamp(value)
class SQLconnector(MBconnector):
def __init__(self, filter=None):
self.conn = db.getconnection("amavis_quarantine")
self.filter = ""
if filter:
for a in filter:
if not a:
continue
if a[0] == "&":
self.filter += " AND "
else:
self.filter += " OR "
self.filter += a[1:]
query = self._get_query()
status, cursor = db.execute(self.conn, """
SELECT count(quarantine.mail_id) AS total
%s
""" % query)
if not status:
print cursor
self.count = 0
else:
self.count = int(cursor.fetchone()[0])
def _get_query(self):
return """
FROM quarantine, maddr, msgrcpt, msgs
WHERE quarantine.mail_id=msgrcpt.mail_id
AND msgrcpt.rid=maddr.id
AND msgrcpt.mail_id=msgs.mail_id
AND quarantine.chunk_ind=1
%s
ORDER BY msgs.time_num DESC
""" % self.filter
orders = {
"from" : "mail__from_addr",
"subject" : "mail__subject",
"date" : "mail__time_num"
}
def __init__(self, mail_ids=None, filter=None):
self.count = None
self.mail_ids = mail_ids
self.filter = filter
def messages_count(self, **kwargs):
if self.count is None:
filter = Q(chunk_ind=1)
if self.mail_ids is not None:
filter &= Q(mail__in=self.mail_ids)
if self.filter:
filter &= self.filter
self.messages = Quarantine.objects.filter(filter)
if kwargs.has_key("order"):
totranslate = kwargs["order"][1:]
sign = kwargs["order"][:1]
if sign == " ":
sign = ""
order = sign + self.orders[totranslate]
self.messages = self.messages.order_by(order)
self.count = self.messages.count()
return self.count
def fetch(self, start=None, stop=None, **kwargs):
query = self._get_query()
status, cursor = db.execute(self.conn, """
SELECT msgs.from_addr, maddr.email, msgs.subject, msgs.content, quarantine.mail_id,
msgs.time_num, msgs.content, msgrcpt.rs
%s
LIMIT %d,%d
""" % (query, start - 1, kwargs["nbelems"]))
if not status:
print cursor
return []
messages = self.messages[start - 1:stop]
emails = []
rows = cursor.fetchall()
for row in rows:
m = {"from" : row[0], "to" : row[1],
"subject" : row[2], "content" : row[3],
"mailid" : row[4], "date" : row[5],
"type" : row[6]}
if row[7] == "R":
m["img_rstatus"] = static_url("pics/release.png");
emails.append(m)
for qm in messages:
for rcpt in qm.mail.msgrcpt_set.all():
m = {"from" : qm.mail.from_addr,
"to" : rcpt.rid.email,
"subject" : qm.mail.subject,
"mailid" : qm.mail_id,
"date" : qm.mail.time_num,
"type" : qm.mail.content}
if rcpt.rs == '':
m["class"] = "unseen"
elif rcpt.rs == 'R':
m["img_rstatus"] = static_url("pics/release.png")
emails.append(m)
return emails
class SQLlisting(EmailListing):
@@ -91,16 +80,17 @@ class SQLlisting(EmailListing):
defcallback = "updatelisting"
reset_wm_url = True
def __init__(self, user, filter, **kwargs):
def __init__(self, user, msgs, filter, **kwargs):
if not user.is_superuser:
Qtable.cols_order.remove('to')
self.mbc = SQLconnector(filter)
EmailListing.__init__(self, **kwargs)
self.mbc = SQLconnector(msgs, filter)
super(SQLlisting, self).__init__(**kwargs)
class SQLemail(Email):
def __init__(self, msg, *args, **kwargs):
Email.__init__(self, msg, *args, **kwargs)
fields = ["X-Amavis-Alert", "Subject", "From", "To", "Date"]
super(SQLemail, self).__init__(msg, *args, **kwargs)
fields = ["X-Amavis-Alert", "Subject", "From", "To", "Cc", "Date"]
for f in fields:
label = f
if not msg.has_key(f):
@@ -1,6 +1,7 @@
function updatehash(evt) {
evt.stop();
current_anchor.parse_string(evt.target.get("href"), true).setparams(navparams);
var url = $defined(this.get("href")) ? this.get("href") : "";
current_anchor.parse_string(url, true).setparams(navparams);
current_anchor.update();
}
@@ -20,13 +21,12 @@ function actioncallback(resp) {
$("menubar").set("html", "");
$("navbar").set("html", "");
if (resp.status == "ok") {
infobox.info(resp.message);
infobox.info(resp.respmsg);
current_anchor.parse_string(resp.url, true).setparams(navparams);
current_anchor.update(1);
} else {
infobox.error(resp.message);
infobox.error(resp.respmsg);
}
infobox.hide(1);
current_anchor.parse_string(resp.url, true).setparams(navparams);
current_anchor.update(1);
}
function actionclick(evt) {
@@ -37,6 +37,17 @@ function actionclick(evt) {
}).get();
}
function viewmail_cs(evt) {
evt.stop();
var to = this.getFirst("td[name=to]");
current_anchor.baseurl(this.get("id"));
if ($defined(to)) {
current_anchor.setparam("rcpt", to.get("html").trim());
}
current_anchor.update();
}
/*
* Callback of the 'listing' action (the main screen)
*/
@@ -92,7 +103,9 @@ function listing_cb(resp) {
mysubmit(event, "delete");
});
searchbox_init();
$$("tr").addEvent("dblclick", viewmail);
init_sortable_columns();
update_sortable_column();
$$("tbody>tr").addEvent("dblclick", viewmail_cs);
}
/*
@@ -9,7 +9,13 @@
event.stop();
var selection = new Array();
$$("tr[class*=table-tr-selected]").each(function(item) {
selection.push(item.get("id"));
var content = "";
var to = item.getFirst("td[name=to]");
if ($defined(to)) {
content = to.get("html").trim() + " ";
}
selection.push(content + item.get("id"));
});
new Element("input", {
"type" : "hidden", "name" : "selection", "value" : selection
@@ -9,7 +9,7 @@ from modoboa.extensions import amavis_quarantine
register = template.Library()
@register.simple_tag
def viewm_menu(selection, backurl, mail_id, perms):
def viewm_menu(selection, backurl, mail_id, rcpt, perms):
options_menu = [
{"name" : "viewmode",
"label" : _("View as plain text"),
@@ -34,12 +34,14 @@ def viewm_menu(selection, backurl, mail_id, perms):
"class" : "boxed",
"rel" : "{handler:'iframe',size:{x:600,y:500}}"},
{"name" : "release",
"url" : reverse(amavis_quarantine.views.release, args=[mail_id]),
"url" : reverse(amavis_quarantine.views.release, args=[mail_id]) \
+ "?rcpt=%s" % rcpt,
"img" : static_url("pics/release.png"),
"label" : _("Release"),
"confirm" : _("Release this message?")},
{"name" : "delete",
"url" : reverse(amavis_quarantine.views.delete, args=[mail_id]),
"url" : reverse(amavis_quarantine.views.delete, args=[mail_id]) \
+ "?rcpt=%s" % rcpt,
"img" : static_url("pics/remove.png"),
"label" : _("Delete"),
"confirm" : _("Delete this message?")},
@@ -55,7 +57,7 @@ def viewm_menu(selection, backurl, mail_id, perms):
"perms" : perms})
@register.simple_tag
def quar_menu(selection, perms):
def quar_menu(selection, user):
entries = [
{"name" : "release",
"url" : "",
@@ -88,9 +90,13 @@ def quar_menu(selection, perms):
]
menu = render_to_string('common/menu.html',
{"selection" : selection, "entries" : entries,
"perms" : perms})
{"selection" : selection, "entries" : entries})
if user.is_superuser or is_domain_admin(user):
extraopts = [{"name" : "to", "label" : _("To")}]
else:
extraopts = []
searchbar = render_to_string('common/email_searchbar.html', {
"MEDIA_URL" : settings.MEDIA_URL
"MEDIA_URL" : settings.MEDIA_URL,
"extraopts" : extraopts
})
return menu + searchbar
+130 -107
View File
@@ -1,10 +1,6 @@
# -*- coding: utf-8 -*-
from datetime import datetime
import time
import email
import re
import os
from django.http import HttpResponseRedirect, HttpResponse
from django.template import Template, Context
from django.utils import simplejson
@@ -12,51 +8,78 @@ from django.utils.translation import ugettext as _, ungettext
from django.core.urlresolvers import reverse
from django.contrib.auth.decorators \
import login_required
from modoboa.lib import _render, _ctx_ok, _ctx_ko, decode, getctx
from modoboa.lib import db, parameters
from django.db.models import Q
from modoboa.lib import parameters, _render, getctx, ajax_response
from modoboa.admin.models import Mailbox
from lib import AMrelease
from templatetags.amextras import *
from modoboa.lib.email_listing import parse_search_parameters
from sql_listing import *
from admin.lib import is_domain_admin
def __get_current_url(request):
res = "?page=%s" % request.session["page"]
for p in ["criteria", "pattern"]:
if p in request.session.keys():
res += "&%s=%s" % (p, request.session[p])
if request.session.has_key("page"):
res = "?page=%s" % request.session["page"]
else:
res = ""
params = "&".join(map(lambda p: "%s=%s" % (p, request.session[p]),
filter(request.session.has_key, ["criteria", "pattern"])))
if params != "":
res += "?%s" % (params)
return res
@login_required
def _listing(request, internal=False, filter=None):
#start = time.time()
if not request.user.is_superuser:
mb = Mailbox.objects.get(user=request.user.id)
if filter is None:
filter = ["&maddr.email='%s'" % mb.full_address]
else:
filter += ["&maddr.email='%s'" % mb.full_address]
def _listing(request):
filter = None
rcptfilter = None
msgs = None
order = request.GET.has_key("order") and request.GET["order"] or "-date"
if not request.session.has_key("navparams"):
request.session["navparams"] = {}
request.session["navparams"]["order"] = order
parse_search_parameters(request)
if request.session.has_key("pattern"):
tmp = ""
for c in request.session["criteria"].split(','):
if tmp != "":
tmp += " OR "
tmp += "msgs.%s LIKE '%%%s%%'" % (c, request.session["pattern"])
tmp = "&(%s)" % tmp
if filter is None:
filter = [tmp]
else:
filter += [tmp]
criteria = request.session["criteria"]
if criteria == "both":
criteria = "from_addr,subject,to"
for c in criteria.split(","):
if c == "from_addr":
nfilter = Q(mail__from_addr__contains=request.session["pattern"])
elif c == "subject":
nfilter = Q(mail__subject__contains=request.session["pattern"])
elif c == "to":
rcptfilter = request.session["pattern"]
continue
else:
raise Exception("unsupported search criteria %s" % c)
filter = nfilter if filter is None else filter | nfilter
pageid = request.GET.has_key("page") and int(request.GET["page"]) or 1
request.session["page"] = pageid # ?? nécessaire
lst = SQLlisting(request.user, filter, baseurl="listing/", empty=internal,
q = ~Q(rs='D')
if not request.user.is_superuser:
mb = Mailbox.objects.get(user=request.user.id)
if is_domain_admin(request.user):
q &= Q(rid__email__contains=mb.domain.name)
else:
q &= Q(rid__email=mb.full_address)
if (request.user.is_superuser or is_domain_admin(request.user)) \
and rcptfilter is not None:
q &= Q(rid__email__contains=rcptfilter)
msgs = Msgrcpt.objects.filter(q).values("mail_id")
if request.GET.has_key("page"):
request.session["page"] = request.GET["page"]
pageid = int(request.session["page"])
else:
if request.session.has_key("page"):
del request.session["page"]
pageid = 1
lst = SQLlisting(request.user, msgs, filter, baseurl="listing/",
navparams=request.session["navparams"],
elems_per_page=int(parameters.get_user(request.user,
"MESSAGES_PER_PAGE")))
if internal:
return lst.render(request, pageid=int(pageid))
page = lst.paginator.getpage(pageid)
if page:
content = lst.fetch(request, page.id_start, page.id_stop)
@@ -65,26 +88,21 @@ def _listing(request, internal=False, filter=None):
content = "<div class='info'>%s</div>" % _("Empty quarantine")
navbar = ""
ctx = getctx("ok", listing=content, navbar=navbar,
menu=quar_menu("", request.user.get_all_permissions()))
menu=quar_menu("", request.user))
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
@login_required
def index(request, message=None):
return _listing(request, True)
def index(request):
return SQLlisting(request.user, None, None, empty=True).render(request)
@login_required
def getmailcontent(request, mail_id):
from sql_listing import SQLemail
conn = db.getconnection("amavis_quarantine")
status, cursor = db.execute(conn, """
SELECT mail_text
FROM quarantine
WHERE quarantine.mail_id='%s'
""" % mail_id)
qmails = Quarantine.objects.filter(mail=mail_id)
content = ""
for part in cursor.fetchall():
content += part[0]
for qm in qmails:
content += qm.mail_text
msg = email.message_from_string(content)
links = request.GET.has_key("links") and request.GET["links"] or "0"
mode = request.GET.has_key("mode") and request.GET["mode"] or "plain"
@@ -96,102 +114,107 @@ WHERE quarantine.mail_id='%s'
@login_required
def viewmail(request, mail_id):
args = ""
if request.user.is_superuser or is_domain_admin(request.user):
rcpt = request.GET["rcpt"]
else:
mb = Mailbox.objects.get(user=request.user)
rcpt = mb.full_address
msgrcpt = Msgrcpt.objects.get(mail=mail_id, rid__email=rcpt)
msgrcpt.rs = 'V'
msgrcpt.save()
args = []
for kw in ["mode", "links"]:
if kw in request.GET.keys():
args += args != "" and "&" or "?"
args += "%s=%s" % (kw, request.GET[kw])
if request.GET.has_key(kw):
args += ["%s=%s" % (kw, request.GET[kw])]
content = Template("""
<iframe width="100%" frameBorder="0" src="{{ url }}" id="mailcontent"></iframe>
""").render(Context({"url" : reverse(getmailcontent, args=[mail_id]) + args}))
menu = viewm_menu("", __get_current_url(request), mail_id,
""").render(Context({"url" : reverse(getmailcontent, args=[mail_id]) \
+ "?%s" % "&".join(args)}))
menu = viewm_menu("", __get_current_url(request), mail_id, rcpt,
request.user.get_all_permissions())
ctx = getctx("ok", menu=menu, listing=content)
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
@login_required
def viewheaders(request, mail_id):
conn = db.getconnection("amavis_quarantine")
status, cursor = db.execute(conn, """
SELECT mail_text
FROM quarantine
WHERE quarantine.mail_id='%s'
""" % mail_id)
content = ""
for part in cursor.fetchall():
content += part[0]
for qm in Quarantine.objects.filter(mail=mail_id):
content += qm.mail_text
msg = email.message_from_string(content)
return _render(request, 'amavis_quarantine/viewheader.html', {
"headers" : msg.items()
})
@login_required
def delete(request, mail_id, count=1):
conn = db.getconnection("amavis_quarantine")
if mail_id[0] != "'":
mail_id = "'%s'" % mail_id
status, error = db.execute(conn,
"DELETE FROM msgs WHERE mail_id IN (%s)" % mail_id)
if status:
message = ungettext("%(count)d message deleted successfully",
"%(count)d messages deleted successfully",
count) % {"count" : count}
else:
message = error
ctx = getctx("ok", url=__get_current_url(request), message=message)
return HttpResponse(simplejson.dumps(ctx),
mimetype="application/json")
def check_mail_id(request, mail_id):
if type(mail_id) in [str, unicode]:
if request.GET.has_key("rcpt"):
mail_id = ["%s %s" % (request.GET["rcpt"], mail_id)]
else:
mail_id = [mail_id]
return mail_id
@login_required
def release(request, mail_id, count=1):
conn = db.getconnection("amavis_quarantine")
if mail_id[0] != "'":
mail_id = "'%s'" % mail_id
status, cursor = db.execute(conn, """
SELECT msgs.mail_id,secret_id,quar_type,maddr.email FROM msgs, maddr, msgrcpt
WHERE msgrcpt.mail_id=msgs.mail_id AND msgrcpt.rid=maddr.id AND msgs.mail_id IN (%s)
""" % (mail_id))
emails = {}
def delete(request, mail_id):
mail_id = check_mail_id(request, mail_id)
if not request.user.is_superuser and not is_domain_admin(request.user):
mb = Mailbox.objects.get(user=request.user)
msgrcpts = Msgrcpt.objects.filter(mail__in=mail_id, rid__email=mb.full_address)
msgrcpts.update(rs='D')
else:
for mid in mail_id:
r, i = mid.split()
msgrcpt = Msgrcpt.objects.get(mail=i, rid__email=r)
msgrcpt.rs = 'D'
msgrcpt.save()
message = ungettext("%(count)d message deleted successfully",
"%(count)d messages deleted successfully",
len(mail_id)) % {"count" : len(mail_id)}
return ajax_response(request, respmsg=message,
url=__get_current_url(request))
@login_required
def release(request, mail_id):
mail_id = check_mail_id(request, mail_id)
if not request.user.is_superuser and not is_domain_admin(request.user):
mb = Mailbox.objects.get(user=request.user)
msgrcpts = Msgrcpt.objects.filter(mail__in=mail_id, rid__email=mb.full_address)
else:
msgrcpts = []
for mid in mail_id:
r, i = mid.split()
msgrcpts += [Msgrcpt.objects.get(mail=i, rid__email=r)]
amr = AMrelease()
for row in cursor.fetchall():
if not emails.has_key(row[0]):
emails[row[0]] = {}
emails[row[0]]["rcpts"] = []
emails[row[0]]["secret"] = row[1]
emails[row[0]]["rcpts"] += [row[3]]
count = 0
error = None
for k, values in emails.iteritems():
result = amr.sendreq(k, values["secret"], *values["rcpts"])
for rcpt in msgrcpts:
result = amr.sendreq(rcpt.mail.mail_id, rcpt.mail.secret_id, rcpt.rid.email)
if result:
count += 1
db.execute(conn, "UPDATE msgrcpt SET rs='R' WHERE mail_id='%s'" % k)
rcpt.rs = 'R'
rcpt.save()
else:
error = result
break
if not error:
message = ungettext("%(count)d message released successfully",
"%(count)d messages released successfully",
count) % {"count" : count}
len(mail_id)) % {"count" : len(mail_id)}
else:
message = error
ctx = getctx("ok", url=__get_current_url(request), message=message)
return HttpResponse(simplejson.dumps(ctx),
mimetype="application/json")
return ajax_response(request, "ko" if error else "ok", respmsg=message,
url=__get_current_url(request))
@login_required
def process(request):
ids = ""
count = len(request.POST["selection"].split(","))
for id in request.POST["selection"].split(","):
if ids != "":
ids += ","
ids += "'%s'" % id
if ids == "":
ids = request.POST.get("selection", "")
ids = ids.split(",")
if not len(ids):
return HttpResponseRedirect(reverse(index))
if request.POST["action"] == "release":
return release(request, ids, count)
return release(request, ids)
if request.POST["action"] == "delete":
return delete(request, ids, count)
return delete(request, ids)
-10
View File
@@ -45,16 +45,6 @@ html > body #listing {
-khtml-user-select: none; /* Webkit (Safari, Chrome) */
}
th[class*=sortable] {
cursor: pointer;
/*-moz-box-shadow: 0px -15px 10px -10px rgba(0,0,0,0.6) inset;
-webkit-box-shadow: 0px -15px 10px -10px rgba(0,0,0,0.6) inset;*/
}
th.selected {
background: #EEEEEE;
}
.draggable {
cursor: move;
/*position: relative;*/
@@ -75,13 +75,7 @@ function wm_updatelisting(resp) {
wm_updatelisting(resp);
rtimer = refreshFolder.periodical({{ refreshrate }});
searchbox_init();
if (navparams.has("order")) {
src = navparams.get("order").charAt(0) == "-" ? "down_arrow.png" : "up_arrow.png";
th = $$("th[id=" + navparams.get("order").substring(1) + "]");
th.getElement("img").set("src", static_url("pics/" + src));
th.getElement("img").setStyle("display", "block");
th.addClass("selected");
}
update_sortable_column();
var emails_table = new HtmlTable($("emails"), {
selectable: true,
@@ -211,24 +205,8 @@ function wm_updatelisting(resp) {
}
});
}
init_sortable_columns();
$$("tbody>tr").addEvent("dblclick", viewmail);
$$("th[class*=sortable]").addEvent("click", function(evt) {
var tmp = "?";
var order = (navparams.has("order")) ? navparams.get("order") : "";
if (order == "") {
order = current_anchor.getparam("order");
}
if (order.substring(1) == evt.target.id) {
var sign = (order.charAt(0) == '+') ? '-' : '+';
order = sign + evt.target.id;
} else {
order = "-" + evt.target.id;
}
navparams.set("order", order);
current_anchor.setparams(navparams).update();
});
});
{% endblock %}
{% block middletitle %}{% trans "Webmail" %}{% endblock %}
-44
View File
@@ -1,44 +0,0 @@
# -*- coding: utf-8 -*-
"""
"""
import re
import MySQLdb
connections = {}
def mysql_newconnection(host, dbname, login, password):
import MySQLdb
return MySQLdb.connect(host=host, db=dbname, user=login, passwd=password)
def getconnection(name):
# if not name in connections.keys():
if True:
from django.conf import settings
cfg = settings.DB_CONNECTIONS[name]
if cfg["driver"] == "mysql":
conn = mysql_newconnection(cfg["host"], cfg["dbname"], cfg["login"],
cfg["password"])
if connections.has_key(name):
close(connections[name])
connections[name] = conn
return connections[name]
def execute(conn, query):
try:
cursor = conn.cursor()
cursor.execute(query)
if re.search("INSERT|DELETE|UPDATE", query):
conn.commit()
return (True, None)
return (True, cursor)
except MySQLdb.Error, e:
return (False, "Error %d: %s" % (e.args[0], e.args[1]))
def close(conn):
conn.close()
+7 -2
View File
@@ -83,16 +83,21 @@ class Paginator(object):
p.baseurl = self.baseurl
return p
class EmailListing:
class EmailListing(object):
def __init__(self, baseurl=None, folder=None, elems_per_page=40,
navparams={}, **kwargs):
self.folder = folder
self.elems_per_page = elems_per_page
self.navparams = navparams
self.extravars = {}
order = "order" in kwargs.keys() and kwargs["order"] or None
self.empty = "empty" in kwargs.keys() and kwargs["empty"] or False
if not self.empty:
if kwargs.has_key("order"):
order = kwargs["order"]
elif self.navparams.has_key("order"):
order = self.navparams["order"]
else:
order = None
self.paginator = Paginator(self.mbc.messages_count(folder=self.folder,
order=order),
elems_per_page)
+2 -4
View File
@@ -17,10 +17,9 @@ from django.template.loader import render_to_string
class Column:
"""Simple column representation
"""
sortable = True
def __init__(self, name, **kwargs):
self.name = name
self.sortable = True
for k, v in kwargs.iteritems():
setattr(self, k, v)
@@ -50,9 +49,8 @@ class ImgColumn(Column):
This kind of column only contains images tags (<img>).
"""
sortable = False
def __str__(self):
self.sortable = False
try:
return getattr(self, "header")
except AttributeError:
+9 -11
View File
@@ -27,9 +27,18 @@ DATABASES = {
# "NAME" : "",
# "USER" : "",
# "PASSWORD" : ""
# },
# "amavis": {
# "ENGINE" : "",
# "HOST" : "",
# "NAME" : "",
# "USER" : "",
# "PASSWORD" : ""
# }
}
DATABASE_ROUTERS = ["extensions.amavis_quarantine.lib.AmavisRouter"]
# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
@@ -120,16 +129,5 @@ AUTHENTICATION_BACKENDS = (
'modoboa.lib.authbackends.SimpleBackend'
)
# Amavis quarantine
# DB_CONNECTIONS = (
# {"amavis_quarantine" : {"driver" : "",
# "host" : "",
# "dbname" : "",
# "login" : "",
# "password" : ""}
# }
# )
#SESSION_COOKIE_AGE = 300
#CACHE_BACKEND = 'file://'
+10
View File
@@ -318,6 +318,16 @@ th.center, td.center {
text-align: center;
}
th[class*=sortable] {
cursor: pointer;
/*-moz-box-shadow: 0px -15px 10px -10px rgba(0,0,0,0.6) inset;
-webkit-box-shadow: 0px -15px 10px -10px rgba(0,0,0,0.6) inset;*/
}
th.selected {
background: #EEEEEE;
}
td {
border-bottom: 1px solid #C8D6E8;
}
+15 -11
View File
@@ -10,16 +10,20 @@ function navbarcallback(event) {
}
function updatelisting(resp) {
if ($defined(resp.menu)) {
$("menubar").set("html", resp.menu);
parse_menubar("menubar");
}
if ($defined(resp.listing)) {
$("listing").set("html", resp.listing);
}
if ($defined(resp.navbar)) {
$("navbar").set("html", resp.navbar);
$("navbar").getChildren("a").addEvent("click", navbarcallback);
}
if ($defined(resp.url)) {
current_anchor.baseurl(resp.url).update();
return;
}
if ($defined(resp.menu)) {
$("menubar").set("html", resp.menu);
parse_menubar("menubar");
}
if ($defined(resp.listing)) {
$("listing").set("html", resp.listing);
}
if ($defined(resp.navbar)) {
$("navbar").set("html", resp.navbar);
$("navbar").getChildren("a").addEvent("click", navbarcallback);
}
}
+31
View File
@@ -23,6 +23,37 @@ function viewmail(event) {
}
}
function init_sortable_columns() {
$$("th[class*=sortable]").addEvent("click", function(evt) {
var tmp = "?";
var order = (navparams.has("order")) ? navparams.get("order") : "";
if (order == "") {
order = current_anchor.getparam("order");
}
if (order.substring(1) == evt.target.id) {
var sign = (order.charAt(0) == '+') ? '-' : '+';
order = sign + evt.target.id;
} else {
order = "-" + evt.target.id;
}
navparams.set("order", order);
current_anchor.setparams(navparams).update();
});
}
function update_sortable_column() {
if (!navparams.has("order")) {
return;
}
var src = navparams.get("order").charAt(0) == "-" ? "down_arrow.png" : "up_arrow.png";
var th = $$("th[id=" + navparams.get("order").substring(1) + "]");
th.getElement("img").set("src", static_url("pics/" + src));
th.getElement("img").setStyle("display", "block");
th.addClass("selected");
}
window.addEvent("domready", function() {
navparams = new Hash({
{% join navparams %}
+3
View File
@@ -5,6 +5,9 @@
<div id="searchparams">
<input type="radio" id="crit_from_addr" name="scriteria" value="from_addr" checked>{% trans "From address" %}<br/>
<input type="radio" id="crit_subject" name="scriteria" value="subject">{% trans "Subject" %}<br/>
{% for opt in extraopts %}
<input type="radio" id="crit_{{ opt.name }}" name="scriteria" value="{{ opt.name }}">{{ opt.label }}</br>
{% endfor %}
<input type="radio" id="crit_both" name="scriteria" value="both">{% trans "Both" %}<br/>
</div>
</div>