mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-18 05:51:26 +02:00
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:
@@ -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."
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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")],
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
@@ -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
@@ -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://'
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user