Fusion tip

This commit is contained in:
tonio
2010-03-02 16:37:06 +01:00
95 changed files with 6249 additions and 529 deletions
+1
View File
@@ -1,3 +1,4 @@
.*~$
^[^\.]+\.pyc
static/tmp
static/ckeditor
+4
View File
@@ -115,3 +115,7 @@ class Alias(models.Model):
permissions = (
("view_aliases", "View aliases"),
)
class Parameter(models.Model):
name = models.CharField(max_length=255)
value = models.CharField(max_length=255)
+74 -3
View File
@@ -46,9 +46,40 @@ def domain_menu(domain_id, selection, perms):
"perms" : perms})
@register.simple_tag
def settings_menu(selection):
return render_to_string('admin/settings_menu.html',
{"selection" : selection})
def settings_menu(selection, perms):
entries = [
{"name" : "permissions",
"url" : reverse(admin.views.settings),
"label" : _("Permissions"),
"img" : "/static/pics/permissions.png"},
{"name" : "addperm",
"url" : reverse(admin.views.addpermission),
"img" : "/static/pics/add.png",
"label" : _("Add permission"),
"class" : "boxed",
"rel" : "{handler:'iframe',size:{x:320,y:210}}"},
{"name" : "parameters",
"url" : reverse(admin.views.viewparameters),
"img" : "/static/pics/domains.png",
"label" : _("Parameters")},
]
return render_to_string('main/menu.html',
{"selection" : selection, "entries" : entries,
"perms" : perms})
@register.simple_tag
def domains_menu(selection, perms):
entries = [
{"name" : "domains",
"url" : reverse(admin.views.domains),
"label" : _("Domains"),
"img" : "/static/pics/domains.png"}
]
entries += events.raiseQueryEvent("AdminMenuDisplay", target="admin_menu_box",
perms=perms)
return render_to_string('main/menulist.html',
{"menu" : entries, "selection" : selection,
"perms" : perms})
@register.simple_tag
def loadadminextmenu(perms):
@@ -57,6 +88,46 @@ def loadadminextmenu(perms):
return render_to_string('main/menulist.html',
{"menu" : menu, "perms" : perms})
@register.simple_tag
def param(app, definition):
result = """<div class='row'>
<label>%s</label>""" % definition["name"]
name = "%s.%s" % (app, definition["name"])
if definition["type"] in ["string", "int"]:
value = definition.has_key("value") \
and definition["value"] or definition["default"]
result += """
<input type='text' name='%s' id='%s' value='%s' />""" % (name, name, value)
if definition["type"] in ["list", "list_yesno"]:
result += """
<select name='%s' id='%s'>""" % (name, name)
values = []
if definition["type"] == "list_yesno":
values = [("yes", _("Yes")), ("no", _("No"))]
else:
if definition.has_key("values"):
values = definition["values"]
value = definition.has_key("value") \
and definition["value"] or definition["default"]
for v in values:
selected = ""
if value == v[0]:
selected = " selected='selected'"
result += "<option value='%s'%s>%s</option>\n" % (v[0], selected, v[1])
result += """
</select>
"""
if definition.has_key("help"):
result += """
<a href='%s' onclick='return false;' class='Tips' title='%s'>
<img src='/static/pics/info.png' border='0' />
</a>""" % (definition["help"], _("Help"))
result += """
</div>
"""
return result
@register.filter
def gender(value, target):
if genders.has_key(value):
+3 -1
View File
@@ -18,5 +18,7 @@ urlpatterns = patterns('mailng.admin.views',
(r'^domains/(?P<dom_id>\d+)/delalias/(?P<alias_id>\d+)/$', 'delalias'),
(r'^settings/$', 'settings'),
(r'^settings/addperm/$', 'addpermission'),
(r'^settings/delperm/(?P<mbox_id>\d+)/(?P<group>\w+)/$', 'deletepermission')
(r'^settings/delperm/(?P<mbox_id>\d+)/(?P<group>\w+)/$', 'deletepermission'),
(r'^settings/parameters/$', 'viewparameters'),
(r'^settings/parameters/save/$', 'saveparameters'),
)
+33 -3
View File
@@ -11,11 +11,11 @@ from django.contrib.auth.decorators \
import login_required, permission_required, user_passes_test
from django.contrib.auth.models import User, Group
from mailng import admin, main
from mailng.admin.models import Domain, Mailbox, Alias
from mailng.admin.models import Domain, Mailbox, Alias, Parameter
from forms import MailboxForm, DomainForm, AliasForm, PermissionForm
from mailng.lib.authbackends import crypt_password
from mailng.lib import _render, _ctx_ok, _ctx_ko
from mailng.lib import events
from mailng.lib import events, parameters
import string
import copy
@@ -49,7 +49,7 @@ def domains(request):
return mailboxes(request, dom_id=mb.domain.id)
return HttpResponseRedirect(reverse(main.views.index))
domains = Domain.objects.all()
counters = {}
for dom in domains:
@@ -404,3 +404,33 @@ def deletepermission(request, mbox_id, group):
mbox.user.groups.remove(grp)
mbox.user.save()
return HttpResponseRedirect(reverse(admin.views.settings))
@login_required
@user_passes_test(lambda u: u.is_superuser)
def viewparameters(request):
apps = sorted(parameters._params.keys())
gparams = []
for app in apps:
tmp = {"name" : app, "params" : []}
for p in sorted(parameters._params[app]):
tmp["params"] += [{"name" : p,
"value" : parameters.get(app, p),
"help" : parameters._params[app][p]["help"],
"default" : parameters._params[app][p]["default"],
"type" : parameters._params[app][p]["type"]}]
gparams += [tmp]
return _render(request, 'admin/parameters.html', {
"gparams" : gparams
})
@login_required
@user_passes_test(lambda u: u.is_superuser)
def saveparameters(request):
for pname, v in request.POST.iteritems():
if pname == "update":
continue
app, name = pname.split('.')
parameters.save(app, name, v)
request.user.message_set.create(message=_("Configuration saved."))
return HttpResponseRedirect(reverse(admin.views.viewparameters))
View File
+7
View File
@@ -0,0 +1,7 @@
from django import forms
from django.utils.translation import ugettext as _
class LoginForm(forms.Form):
username = forms.CharField(label=_("Username"))
password = forms.CharField(label=_("Password"), widget=forms.PasswordInput,
help_text=_("Password used to authenticate"))
Executable
+3
View File
@@ -0,0 +1,3 @@
from django.db import models
# Create your models here.
Executable
+23
View File
@@ -0,0 +1,23 @@
"""
This file demonstrates two different styles of tests (one doctest and one
unittest). These will both pass when you run "manage.py test".
Replace these with more appropriate tests for your application.
"""
from django.test import TestCase
class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.failUnlessEqual(1 + 1, 2)
__test__ = {"doctest": """
Another way to test that 1 + 1 is equal to 2.
>>> 1 + 1 == 2
True
"""}
Executable
+37
View File
@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
from django.contrib.auth import authenticate, login
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from mailng.lib import events
import mailng.admin
from forms import LoginForm
def dologin(request):
error = None
if request.method == "POST":
form = LoginForm(request.POST)
user = authenticate(username=request.POST["username"],
password=request.POST["password"])
if user:
if user.is_active:
login(request, user)
events.raiseEvent("UserLogin", request=request,
username=request.POST["username"],
password=request.POST["password"])
next = request.POST["next"]
if next == "None":
next = reverse(mailng.admin.views.domains)
return HttpResponseRedirect(next)
else:
error = _("Account is disabled.")
error = _("Your username and password didn't match. Please try again.")
else:
form = LoginForm()
next = request.GET.has_key("next") and request.GET["next"] or None
return render_to_response("registration/login.html", {
"form" : form, "error" : error, "next" : next
})
@@ -0,0 +1,89 @@
# -*- coding: utf-8 -*-
from datetime import datetime
from django.utils.translation import ugettext as _
from mailng.lib import tables, db
from mailng.lib.email_listing import MBconnector, EmailListing
class Qtable(tables.Table):
idkey = "mailid"
selection = tables.SelectionColumn("selection", first=True)
type = tables.Column("type", label=_("Type"))
from_ = tables.Column("from", label=_("From"))
subject = tables.Column("subject", label=_("Subject"))
time = tables.Column("date", label=_("Date"))
def parse_date(self, value):
return datetime.fromtimestamp(value)
def parse(self, header, value):
try:
return getattr(self, "parse_%s" % header)(value)
except AttributeError:
return 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
def messages_count(self, folder=None):
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
%s
LIMIT %d,%d
""" % (query, start - 1, stop - 1))
if not status:
print cursor
return []
emails = []
rows = cursor.fetchall()
for row in rows:
emails.append({"from" : row[0], "to" : row[1],
"subject" : row[2], "content" : row[3],
"mailid" : row[4], "date" : row[5],
"type" : row[6]})
return emails
class SQLlisting(EmailListing):
tpl = "amavis_quarantine/index.html"
tbltype = Qtable
def __init__(self, filter, **kwargs):
self.mbc = SQLconnector(filter)
EmailListing.__init__(self, **kwargs)
+68 -141
View File
@@ -1,49 +1,11 @@
{% extends "layout.html" %}
{% extends "common/email_listing.html" %}
{% load i18n %}
{% load amextras %}
{% block html_head %}
<link rel="stylesheet" type="text/css" href="/static/stylesheets/box.css" />
<style type="text/css">
form {
margin: 0 auto;
width: 98%;
}
#quarmenu {
float: left;
width: 100%;
margin: 5px 0;
}
#listing {
float: left;
width: 100%;
padding-bottom: 10px;
}
#listheader {
float: left;
width: 100%;
margin-bottom: 10px;
}
#navbar {
float: right;
}
#counter {
float: left;
font-weight: bold;
}
td {
font-size: smaller;
height: 40px;
}
{% block css %}
#searchbox {
float: right;
float: left;
margin-right: 10px;
width: 250px;
}
#searchbox img {
@@ -55,99 +17,76 @@ td {
#searchparams {
background: white;
padding: 5px;
width: 150px;
}
</style>
<script type="text/javascript">
window.addEvent("domready", function() {
mysubmit = function(event, name) {
event.stop();
new Element('input', {
'type' : 'hidden', 'name' : "action", style: "display:none", 'value' : name
}).inject($("quarlistingform"), "top");
$("quarlistingform").set("send", {
{% endblock %}
{% block js %}
mysubmit = function(event, name) {
event.stop();
new Element('input', {
'type' : 'hidden', 'name' : "action", style: "display:none", 'value' : name
}).inject($("listingform"), "top");
{% url extensions.amavis_quarantine.main.process as actionurl %}
$("listingform").set("action", "{{ actionurl }}");
$("listingform").set("send", {
onSuccess : function(res) {
response = JSON.decode(res);
location.href = response.url;
}
});
$("quarlistingform").send();
}
checkSelection = function(text) {
var getout = $$("input[name=selection]").every(function(item, index) {
return item.checked == false;
});
if (getout) {
return false;
}
return confirm(text);
}
toggleSelection = function(value) {
$$("input[name=selection]").each(function(obj) {
obj.checked = value;
});
}
$$("a[name=delete]").addEvent("click", function(event) {
if (!checkSelection("{% trans "Delete this selection?" %}")) {
event.stop();
return;
}
mysubmit(event, "delete");
});
$$("a[name=release]").addEvent("click", function(event) {
if (!checkSelection("{% trans "Release this selection?" %}")) {
event.stop();
return;
}
mysubmit(event, "release");
});
if ($("selectall")) {
$("selectall").addEvent("click", function(event) {
if ($("selectall").checked) {
toggleSelection(true);
} else {
toggleSelection(false);
}
});
}
{% url extensions.amavis_quarantine.main.search as s_url %}
$("searchfield").addEvent("change", function(event) {
var criteria = "";
$$("input[name=scriteria]:checked").each(function(obj) {
criteria = obj.value;
});
new Request.JSON({url: "{{ s_url }}", onSuccess: function(resp) {
$("listing").set("html", resp.content);
}}).get({"pattern" : $("searchfield").value, "criteria" : criteria});
});
$("searchfield").addEvent("click", function(event) {
$("searchfield").select();
});
$("searchfield").addEvent("blur", function(event) {
if ($("searchfield").value == "") {
$("searchfield").set("value", "{% trans "Search..." %}");
response = JSON.decode(res);
location.href = response.url;
}
});
$("listingform").send();
}
var myFx = new Fx.Slide($("searchparams"));
myFx.hide();
$("searchopts").addEvent("click", function(event) {
myFx.cancel();
myFx.toggle();
});
$$("input[name=scriteria]").addEvent("click", function(event) {
myFx.cancel();
myFx.toggle();
});
$$("a[name=delete]").addEvent("click", function(event) {
if (!checkSelection("selection", "{% trans "Delete this selection?" %}")) {
event.stop();
return;
}
mysubmit(event, "delete");
});
</script>
$$("a[name=release]").addEvent("click", function(event) {
if (!checkSelection("selection", "{% trans "Release this selection?" %}")) {
event.stop();
return;
}
mysubmit(event, "release");
});
var myFx = new Fx.Slide($("searchparams"));
myFx.hide();
$("searchopts").addEvent("click", function(event) {
myFx.cancel();
myFx.toggle();
});
$$("input[name=scriteria]").addEvent("click", function(event) {
myFx.cancel();
myFx.toggle();
});
$("searchfield").addEvent("change", function(event) {
var criteria = "";
$$("input[name=scriteria]:checked").each(function(obj) {
criteria = obj.value;
});
{% url extensions.amavis_quarantine.main.search as s_url %}
new Request.JSON({url: "{{ s_url }}", onSuccess: function(resp) {
$("listing").set("html", resp.listing);
$("navbar").set("html", resp.navbar);
{% url extensions.amavis_quarantine.main.viewmail as view_url %}
bindRows("{{ view_url }}", "{{ current_page }}");
}}).get({"pattern" : $("searchfield").value, "criteria" : criteria});
});
$("searchfield").addEvent("click", function(event) {
$("searchfield").select();
});
$("searchfield").addEvent("blur", function(event) {
if ($("searchfield").value == "") {
$("searchfield").set("value", "{% trans "Search..." %}");
}
});
{% url extensions.amavis_quarantine.main.viewmail as view_url %}
bindRows("{{ view_url }}", "{{ current_page }}");
{% endblock %}
{% block middletitle %}{% trans "Quarantine" %}{% endblock %}
{% block menu %}
@@ -162,15 +101,3 @@ window.addEvent("domready", function() {
</div>
</div>
{% endblock %}
{% block content %}
<div id="form-container">
{% url extensions.amavis_quarantine.main.process as actionurl %}
<form method="POST" action="{{ actionurl }}" id="quarlistingform">
<input type="hidden" name="pagenum" id="pagenum" value="{{ current_page }}" />
<div id="listing">
{{ content }}
</div>
</form>
</div>
{% endblock %}
@@ -1,51 +0,0 @@
{% load i18n %}
{% if not count %}
<div id="notifybox">{% trans "No messages in quarantine" %}</div>
{% else %}
<div id="listheader">
<div id="counter">
Messages {{ emails.start_index }} à {{ emails.end_index }} sur {{ count }}
</div>
<div id="navbar">
{% if emails.has_previous %}
<a href="?page=1">
<img src="/static/pics/first.png" border="0" title="{% trans "First" %}" />
</a>
<a href="?page={{ emails.previous_page_number }}">
<img src="/static/pics/back.png" border="0" title="{% trans "Previous" %}" />
</a>
{% endif %}
{% if emails.has_next %}
<a href="?page={{ emails.next_page_number }}">
<img src="/static/pics/next.png" border="0" title="{% trans "Next" %}" />
</a>
<a href="?page={{ last_page }}">
<img src="/static/pics/last.png" border="0" title="{% trans "Last" %}" />
</a>
{% endif %}
</div>
</div>
<table>
<tr>
<th><input type="checkbox" name="selectall" id="selectall" /></th>
<th>{% trans "Type" %}</th>
<th width="150px">{% trans "From" %}</th>
<th>{% trans "Subject" %}</th>
<th width="100px">{% trans "Date" %}</th>
</tr>
{% for m in emails.object_list %}
<tr class="{% cycle 'odd' 'even' %}">
<td><input type="checkbox" name="selection" id="selection"
value="{{ m.mailid }}" /></td>
<td>{{ m.type }}</td>
<td>{{ m.from }}</td>
{% url extensions.amavis_quarantine.main.viewmail m.mailid as view_url %}
<td><a href="{{ view_url }}?page={{ current_page }}">{{ m.subject }}</a></td>
<td>{{ m.time }}</td>
</tr>
{% endfor %}
</table>
{% endif %}
@@ -1,11 +1,11 @@
{% extends "layout.html" %}
{% extends "nlayout.html" %}
{% load i18n %}
{% block html_head %}
<link rel="stylesheet" type="text/css" href="/static/stylesheets/box.css" />
<style type="text/css">
td {
font-size: smaller;
font-size: 1em;
height: 20px;
padding-left: 5px;
}
td.left {
@@ -14,10 +14,21 @@ td.left {
#mailcontent {
width: 100%;
height: 400px;
border: 0;
}
</style>
<script type="text/javascript">
window.addEvent("domready", function() {
var spage = $("page").getSize();
var stable = $("table-container").getSize();
var slogo = $("header").getSize();
var iheight = spage.y - stable.y - slogo.y;
$("mailcontent").setStyle("height", iheight + "px");
});
</script>
{% endblock %}
{% block middletitle %}{% trans "Mail content" %}{% endblock %}
{% block menu %}
+3 -1
View File
@@ -2,7 +2,9 @@ from django.conf.urls.defaults import *
urlpatterns = patterns('mailng.extensions.amavis_quarantine.main',
(r'^$', 'index'),
(r'^viewmail/(?P<mail_id>[\w\-\+]+)/$', 'viewmail'),
url(r'^viewmail/(?P<mail_id>[\w\-\+]+)/$', 'viewmail',
name="viewmail_std"),
url(r'^viewmail/$', 'viewmail', name="viewmail_null"),
(r'^getmailcontent/(?P<mail_id>[\w\-\+]+)/$', 'getmailcontent'),
(r'^viewheaders/(?P<mail_id>[\w\-\+]+)/$', 'viewheaders'),
(r'^process/$', 'process'),
+13 -3
View File
@@ -49,6 +49,10 @@ size_avg_template = {
}
}
tpl = {'traffic':traffic_avg_template,
'badtraffic':badtraffic_avg_template,
'size':size_avg_template}
class Grapher(object):
def __init__(self):
self.rrd_rootdir = getoption("RRD_ROOTDIR", "/tmp")
@@ -57,9 +61,10 @@ class Grapher(object):
def process(self, target, suffix, start, end, tpl=traffic_avg_template):
rrdfile = "%s/%s.rrd" % (self.rrd_rootdir, target)
if not os.path.exists(rrdfile):
print "[graph] no rrd file %s detected" %rrdfile
return
ext = "png"
path = "%s/%s_%s_%s_%s.%s" % (self.img_rootdir, tpl['name'],
path = "%s/%s_%s_%s_%s.%s" % (self.img_rootdir, tpl['name'],
target, tpl['cf'], suffix, ext)
start = str(start)
end = str(end)
@@ -69,10 +74,10 @@ class Grapher(object):
defs += ['DEF:%s=%s:%s:%s' % (v, rrdfile, v, tpl['cf']),
'CDEF:%spm=%s,60,*' % (v, v)]
type = d.has_key("type") and d["type"] or "LINE"
lines += ["%s:%spm%s:%s" % (type, v, d["color"],
lines += ["%s:%spm%s:%s" % (type, v, d["color"],
d["legend"].encode("utf-8"))]
params = defs + lines
rrdtool.graph(path,
rrdtool.graph(str(path),
"--imgformat", "PNG",
"--width", tpl["width"],
"--height", tpl["height"],
@@ -84,6 +89,11 @@ class Grapher(object):
"--units-exponent", "0",
*params)
if not os.path.exists(path):
print "[graph] Impossible to create %s graph" %path
else:
print "[graph] %s graph created" %path
def make_defaults(self, target, tpl=traffic_avg_template):
end = "%d" % int(time.mktime(time.localtime()))
self.process(target, "day", "-1day", end, tpl)
+86 -60
View File
@@ -5,7 +5,7 @@ import sys
import os
import re
import rrdtool
import string
import string, pdb
from optparse import OptionParser
from mailng.lib import getoption
from mailng.admin.models import Domain
@@ -35,6 +35,25 @@ points_per_sample = 3
variables = ["sent", "recv", "bounced", "reject", "spam", "virus",
"size_sent", "size_recv"]
def str2Time(y, M, d, h ="0", m ="0", s="0"):
"""str2Time
return epoch time from Year Month Day Hour:Minute:Second time format
"""
try:
local = time.strptime("%s %s %s %s:%s:%s" %(y, M, d, h, m, s), \
"%Y %b %d %H:%M:%S")
except:
# try with 01-12 month format
try:
local = time.strptime("%s %s %s %s:%s:%s" %(y, M, d, h, m, s), \
"%Y %m %d %H:%M:%S")
except:
print "[rrd] ERROR unrecognized %s time format" %(y, M, d, h, m, s)
return 0
return int(time.mktime(local))
class LogParser(object):
def __init__(self, logfile, workdir,
year=None, debug=False, verbose=False):
@@ -42,17 +61,20 @@ class LogParser(object):
try:
self.f = open(logfile)
except IOError, errno:
print "%s" % errno
if self.debug:
print "%s" % errno
sys.exit(1)
self.workdir = workdir
self.year = year
self.debug = debug
self.verbose = verbose
self.cfs = ['AVERAGE', 'MAX']
self.last_month = None
if not self.year:
self.year = time.localtime().tm_year
if self.debug:
print "[rrd] Dealing with year %s" %self.year
self.data = {}
domains = Domain.objects.all()
self.domains = []
@@ -65,19 +87,6 @@ class LogParser(object):
self.lupdates = {}
self.line_expr = re.compile("(\w+)\s+(\d+)\s+(\d+):(\d+):(\d+)\s+(\w+)\s+(\w+)/?\w*[[](\d+)[]]:\s+(.*)")
def str2Time(self, y, M, d, h, m, s):
"""str2Time
return epoch time from Year Month Day Hour:Minute:Second time format
"""
try:
local = time.strptime("%s %s %s %s:%s:%s" %(y, M, d, h, m, s), \
"%Y %b %d %H:%M:%S")
except:
print "[rrd] ERROR unrecognized %s time format" %(y, M, d, h, m, s)
return 0
return int(time.mktime(local))
def init_rrd(self, fname, m):
"""init_rrd
@@ -128,7 +137,8 @@ class LogParser(object):
m = t - (t % rrdstep)
if not os.path.exists(fname):
self.lupdates[fname] = self.init_rrd(fname, m)
print "[rrd] create new RRD file"
if self.debug:
print "[rrd] create new RRD file %s" %fname
else:
if not self.lupdates.has_key(fname):
self.lupdates[fname] = rrdtool.last(fname)
@@ -186,60 +196,76 @@ class LogParser(object):
self.data["global"][cur_t][counter] += val
def process(self):
id_expr = re.compile("(\w{9,18}): (.*)")
prev_se = -1
prev_mi = -1
prev_ho = -1
for line in self.f.readlines():
m = self.line_expr.match(line)
if not m:
continue
(mo, da, ho, mi, se, host, prog, pid, log) = m.groups()
se = int(int(se) / rrdstep) # rrd step is one-minute => se = 0
cur_t = self.str2Time(self.year, mo, da, ho, mi, se)
cur_t = cur_t - cur_t % rrdstep
m = re.search("(\w{10}): message-id=<([^>]*)>", log)
if prev_se != se or prev_mi != mi or prev_ho != ho:
cur_t = str2Time(self.year, mo, da, ho, mi, se)
cur_t = cur_t - cur_t % rrdstep
prev_mi = mi
prev_ho = ho
prev_se = se
m = id_expr.search(log)
if m:
self.workdict[m.group(1)] = {'from' : m.group(2), 'size' : 0}
continue
(line_id, line_log) = m.groups()
m = re.search("(\w{10}): from=<([^>]*)>, size=(\d+)", log)
if m:
self.workdict[m.group(1)] = {'from' : m.group(2),
'size' : string.atoi(m.group(3))}
continue
m = re.search("(\w{10}): to=<([^>]*)>.*status=(\S+)", log)
if m:
if not self.workdict.has_key(m.group(1)):
print "Inconsistent mail (%s: %s), skipping" % (m.group(1), m.group(2))
m = re.search("message-id=<([^>]*)>", line_log)
if m:
self.workdict[line_id] = {'from' : m.group(1), 'size' : 0}
continue
if not m.group(3) in variables:
print "Unsupported status %s, skipping" % m.group(3)
m = re.search("from=<([^>]*)>, size=(\d+)", line_log)
if m:
self.workdict[line_id] = {'from' : m.group(1),
'size' : string.atoi(m.group(2))}
continue
addrfrom = re.match("([^@]+)@(.+)", self.workdict[m.group(1)]['from'])
if addrfrom and addrfrom.group(2) in self.domains:
self.inc_counter(addrfrom.group(2), cur_t, 'sent')
self.inc_counter(addrfrom.group(2), cur_t, 'size_sent',
self.workdict[m.group(1)]['size'])
addrto = re.match("([^@]+)@(.+)", m.group(2))
if addrto.group(2) in self.domains:
if m.group(3) == "sent":
self.inc_counter(addrto.group(2), cur_t, 'recv')
self.inc_counter(addrto.group(2), cur_t, 'size_recv',
self.workdict[m.group(1)]['size'])
else:
self.inc_counter(addrto.group(2), cur_t, m.group(3))
continue
m = re.search("NOQUEUE: reject: .*from=<(.*)> to=<([^>]*)>", log)
if m:
addrto = re.match("([^@]+)@(.+)", m.group(2))
if addrto and addrto.group(2) in self.domains:
self.inc_counter(addrto.group(2), cur_t, 'reject')
continue
m = re.search("to=<([^>]*)>.*status=(\S+)", line_log)
if m:
if not self.workdict.has_key(line_id):
if self.debug:
print "Inconsistent mail (%s: %s), skipping" % (line_id, m.group(1))
continue
if not m.group(2) in variables:
if self.debug:
print "Unsupported status %s, skipping" % m.group(2)
continue
addrfrom = re.match("([^@]+)@(.+)", self.workdict[line_id]['from'])
if addrfrom and addrfrom.group(2) in self.domains:
self.inc_counter(addrfrom.group(2), cur_t, 'sent')
self.inc_counter(addrfrom.group(2), cur_t, 'size_sent',
self.workdict[line_id]['size'])
addrto = re.match("([^@]+)@(.+)", m.group(1))
if addrto.group(2) in self.domains:
if m.group(2) == "sent":
self.inc_counter(addrto.group(2), cur_t, 'recv')
self.inc_counter(addrto.group(2), cur_t, 'size_recv',
self.workdict[line_id]['size'])
else:
self.inc_counter(addrto.group(2), cur_t, m.group(2))
continue
else:
m = re.search("NOQUEUE: reject: .*from=<(.*)> to=<([^>]*)>", log)
if m:
addrto = re.match("([^@]+)@(.+)", m.group(2))
if addrto and addrto.group(2) in self.domains:
self.inc_counter(addrto.group(2), cur_t, 'reject')
continue
# Sort everything by time
G = grapher.Grapher()
for dom, data in self.data.iteritems():
if self.debug:
print "[rrd] dealing with domain %s" %dom
sortedData = {}
sortedData = [ (i, data[i]) for i in sorted(data.keys()) ]
for t, dict in sortedData:
@@ -256,12 +282,12 @@ if __name__ == "__main__":
parser = OptionParser()
parser.add_option("-l","--logFile", default=log_file,
help="postfix log in syslog format", metavar="FILE")
parser.add_option("-v","--verbose", default=False, action="store_true",
parser.add_option("-v","--verbose", default=False, action="store_true",
dest="verbose", help="set verbose mode")
parser.add_option("-d","--debug", default=False, action="store_true",
parser.add_option("-d","--debug", default=False, action="store_true",
dest="debug", help="set debug mode")
(options, args) = parser.parse_args()
P = LogParser(options.logFile, rrd_rootdir,
debug=options.debug, verbose=options.verbose)
P.process()
+89 -16
View File
@@ -7,19 +7,33 @@ This module provides rrdtool support to retreive statistics
from postfix log : sent, received, bounced, rejected
"""
import calendar
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from django.conf.urls.defaults import *
from mailng.lib import events, _render
from mailng.lib import events, parameters, _render
from mailng.admin.views import good_domain
from mailng.admin.models import Domain, Mailbox
from django.contrib.auth.decorators \
import login_required, user_passes_test, permission_required
from extensions.stats.grapher import Grapher, tpl
from extensions.stats.logparser import str2Time
from mailng.lib import getoption
graph_types = ['AVERAGE', 'MAX']
graph_list = [{"name" : "traffic", "label" : _("Average normal traffic")},
{"name" : "badtraffic", "label" : _("Average bad traffic")},
{"name" : "size", "label" : _("Average normal traffic size")}]
def init():
events.register("AdminMenuDisplay", menu)
parameters.register("stats", "LOGFILE", "string", "/var/log/mail.log",
help=_("Path to log file used to collect statistics"))
parameters.register("stats", "RRD_ROOTDIR", "string", "/tmp/mailng",
help=_("Path to directory where RRD files are stored"))
parameters.register("stats", "IMG_ROOTDIR", "string", "/tmp/mailng",
help=_("Path to directory where PNG files are stored"))
def urls():
return (r'^mailng/stats/',
@@ -77,26 +91,87 @@ def graph_display(request,dom_id,graph_t=graph_types):
return _render(request, 'stats/index.html', {
"page" : "Domain statistics", "graph" : graph_nature,
"domain":domain,"domains" : domains, "messages" : errors ,
"types" : graph_type, "tmp_path" : tmp_path})
"domain": domain, "domains" : domains, "messages" : errors,
"types" : graph_type, "tmp_path" : tmp_path,
"img_rootdir" : getoption("IMG_ROOTDIR", "/tmp")})
@login_required
@user_passes_test(lambda u: u.is_superuser)
def adminindex(request):
CH = calendar.HTMLCalendar(calendar.MONDAY)
CH_print = CH.formatmonth(2009,11)
period = request.GET.has_key("period") and request.GET["period"] or "day"
domain = request.GET.has_key("domain") and request.GET["domain"] or "global"
if domain != "global":
domain = Domain.objects.get(pk=domain)
domain = domain.name
domains = Domain.objects.all()
G = Grapher()
for name, t in tpl.iteritems():
G.make_defaults(domain, t)
return _render(request, 'stats/adminindex.html', {
"domains" : domains, "domain" : domain,
"graphs" : graph_list,
"periods" : ["day", "week", "month", "year","Custom"],
"period" : period, "cal" : CH_print,
"img_rootdir" : getoption("IMG_ROOTDIR", "/tmp")})
@login_required
@user_passes_test(lambda u: u.is_superuser)
def custom_period(request):
start = None
end = None
start_ref = None
end_ref = None
domains = Domain.objects.all()
domain = request.GET.has_key("domain") and request.GET["domain"] or "global"
period = request.GET.has_key("period") and request.GET["period"] or "Custom"
if domain != "global":
domain = Domain.objects.get(pk=domain)
domain = domain.name
try:
start_ref = str(request.POST.get('start'))
start = start_ref.split(', ')[1]
end_ref = str(request.POST.get('end'))
end = end_ref.split(', ')[1]
except:
pass
if not end or not start:
return _render(request, 'stats/adminindex.html', {
"domains" : domains, "domain" : domain,
"graphs" : [{"name" : "traffic", "label" : _("Average normal traffic")},
{"name" : "badtraffic", "label" : _("Average bad traffic")},
{"name" : "size", "label" : _("Average normal traffic size")}],
"periods" : ["day", "week", "month", "year"],
"period" : period
})
"graphs" : graph_list,
"period" : period,
"periods" : ["day", "week", "month", "year","Custom"],
"period_name":None,
"start" : start_ref,
"end" : end_ref,
"messages" : ["Custom period not selected"],
"img_rootdir" : getoption("IMG_ROOTDIR", "/tmp")})
period_name = "%s_%s" %(start.replace('/',''),end.replace('/',''))
G = Grapher()
for tpl_name in graph_list:
G.process(domain,
period_name,
str2Time(*start.split('/')),
str2Time(*end.split('/')),
tpl[tpl_name['name']])
print "[stats] ", period
return _render(request, 'stats/adminindex.html', {
"domains" : domains, "domain" : domain,
"graphs" : graph_list,
"period" : period,
"periods" : ["day", "week", "month", "year","Custom"],
"period_name": period_name,
"start" : start_ref,
"end" : end_ref,
"messages" : None,
"img_rootdir" : getoption("IMG_ROOTDIR", "/tmp")})
@login_required
@good_domain
@@ -108,10 +183,8 @@ def index(request, dom_id=None):
domain = Domain.objects.get(pk=dom_id)
return _render(request, 'stats/index.html', {
"domain" : domain,
"graphs" : [{"name" : "traffic", "label" : _("Average normal traffic")},
{"name" : "badtraffic", "label" : _("Average bad traffic")},
{"name" : "size", "label" : _("Average normal traffic size")}],
"periods" : ["day", "week", "month", "year"],
"period" : period
})
"domain" : domain,
"graphs" : graph_list,
"periods" : ["day", "week", "month", "year","Custom"],
"period" : period,
"img_rootdir" : getoption("IMG_ROOTDIR", "/tmp")})
+12 -3
View File
@@ -1,9 +1,9 @@
{% extends "layout.html" %}
{% extends "nlayout.html" %}
{% load i18n %}
{% load stats_extra %}
{% block html_head %}
<script src="/static/js/mootools-more.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="/static/stylesheets/box.css" />
<link rel="stylesheet" type="text/css" href="/static/stylesheets/calendar.css" />
<style type="text/css">
#graphcontent {
margin: 25px auto 10px;
@@ -11,7 +11,7 @@
}
#selectors {
float: right;
float: none;
}
</style>
@@ -27,6 +27,7 @@ window.addEvent("domready", function() {
});
});
</script>
<script type="text/javascript" src="/static/js/calendar.js"></script>
{% endblock %}
{% block middletitle %}{% trans "Global statistics" %}{% endblock %}
{% block content %}
@@ -46,7 +47,15 @@ window.addEvent("domready", function() {
<option value="{{ p }}" {% ifequal p period %}selected{% endifequal %}>{% getLabel p %}</option>
{% endfor %}
</select>
{% ifequal period "Custom" %}
{% include "stats/calendar.html" %}
{% endifequal %}
</div>
{% if start or end %}
<h3>{% trans "Custom period : from" %} {{ start }} {% trans "to" %} {{ end }}</h3>
{% endif %}
<div style="float:None;">
{% include "stats/graphs.html" %}
</div>
</div>
{% endblock %}
+29
View File
@@ -0,0 +1,29 @@
{% load i18n %}
<div style="border:1px solid #dddddd;float:right;padding:3px;margin-top:-3px;">
{% url extensions.stats.main.custom_period as c_stats_url %}
<form class="calendarForm" action="{{ c_stats_url }}" method="POST">
{% trans "Start" %}: <input id="dateTravelStart" name="start" value="{{ start }}" type="text"/>
{% trans "End" %}: <input id="dateTravelEnd" name="end" value="{{ end }}" type="text" />
<input TYPE=SUBMIT VALUE="ok">
</form>
</div>
<script type="text/javascript">
myCal = new Calendar(
{
dateTravelStart: 'D, Y/m/d',
dateTravelEnd : 'D, Y/m/d'
},
{
direction: 0, // -1 past, 0 past + future, 1 future
pad:1,
offset: 1, // first day of the week: 0 = sunday, 1 = monday, etc..
tweak: {x: -147, y: 18} // tweak calendar positioning
});
</script>
<!--
<form name="calendar">
<textarea type="text" rows="0" cols="5">{{ sdate }}</textarea>
<textarea type="text" rows="0" cols="5">{{ edate }}</textarea>
</select>
-->
+1 -1
View File
@@ -1,6 +1,6 @@
{% load i18n %}
{% for elems in graphs %}
<h3>{{ elems.label }}</h3>
<p><img src="/static/graphs/{{ elems.name }}_{{ domain }}_AVERAGE_{{ period }}.png"
<p><img src="/img/stats/{{ elems.name }}_{{ domain }}_AVERAGE_{% ifequal period 'Custom' %}{{ period_name }}{% else %}{{ period }}{% endifequal %}.png"
alt="{% trans 'No statistics available' %}" /></p>
{% endfor %}
+11 -2
View File
@@ -2,10 +2,10 @@
{% load i18n %}
{% block html_head %}
<script src="/static/js/mootools-more.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="/static/stylesheets/box.css" />
<link rel="stylesheet" type="text/css" href="/static/stylesheets/calendar.css" />
<style type="text/css">
#selectors {
float: right;
float: none;
}
</style>
@@ -17,6 +17,7 @@ window.addEvent("domready", function() {
});
});
</script>
<script type="text/javascript" src="/static/js/calendar.js"></script>
{% endblock %}
{% block menu %}
@@ -33,7 +34,15 @@ window.addEvent("domready", function() {
<option value="{{ p }}" {% ifequal p period %}selected{% endifequal %}>{{ p }}</option>
{% endfor %}
</select>
{% ifequal period "Custom" %}
{% include "stats/calendar.html" %}
{% endifequal %}
</div>
{% if start or end %}
<h3>{% trans "Custom period : from" %} {{ start }} {% trans "to" %} {{ end }}</h3>
{% endif %}
<div style="float:None;">
{% include "stats/graphs.html" %}
</div>
</div>
{% endblock %}
+1
View File
@@ -2,6 +2,7 @@ from django.conf.urls.defaults import *
urlpatterns = patterns('mailng.extensions.stats.main',
url(r'^$', 'adminindex', name='fullindex'),
url(r'^custom_period$', 'custom_period'),
url(r'^(?P<dom_id>\d+)/$', 'index', name='domindex'),
(r'^display/(?P<dom_id>\d+)/(?P<graph_t>\w+)/$', 'graph_display'),
(r'^domain/(?P<dom_id>\d+)/$', 'domain'),
View File
+8
View File
@@ -0,0 +1,8 @@
from django import forms
from django.utils.translation import ugettext as _
class ComposeMailForm(forms.Form):
from_ = forms.CharField(label=_("From"))
to = forms.CharField(label=_("To"), widget=forms.Textarea())
subject = forms.CharField(label=_("Subject"), max_length=255)
+228
View File
@@ -0,0 +1,228 @@
# -*- coding: utf-8 -*-
import re
import time
import imaplib
import ssl
from datetime import datetime, timedelta
from email.header import decode_header
import email.utils
from django.utils.translation import ugettext as _
from mailng.lib import decode, tables, imap_utf7, Singleton
from mailng.lib.email_listing import MBconnector, EmailListing
from mailng.lib import tables, imap_utf7, parameters
class WMtable(tables.Table):
tableid = "emails"
idkey = "imapid"
# selection = tables.SelectionColumn("selection", width="20px", first=True)
_1_flags = tables.ImgColumn("flags", width="6%")
_2_subject = tables.Column("subject", label=_("Subject"),
cssclass="draggable", width="50%")
_3_from_ = tables.Column("from", width="20%", label=_("From"))
_4_date = tables.Column("date", width="20%", label=_("Date"))
def parse(self, header, value):
try:
return getattr(IMAPheader, "parse_%s" % header)(value)
except AttributeError:
return value
class IMAPheader(object):
addr_exp = re.compile("([^<\(]+)<|\(([^>\)]+)>|\)")
name_exp = re.compile("\"(.+)\"")
@staticmethod
def parse_address(value):
m = IMAPheader.addr_exp.match(value)
if m:
name = m.group(1)
name = name.strip()
m2 = IMAPheader.name_exp.match(name)
if m2:
return IMAPheader.parse_subject(m2.group(1))
return IMAPheader.parse_subject(name)
return value
@staticmethod
def parse_address_list(values):
lst = values.split(",")
result = ""
for addr in lst:
if result != "":
result += ", "
result += IMAPheader.parse_address(addr)
return result
@staticmethod
def parse_from(value):
return IMAPheader.parse_address(value)
@staticmethod
def parse_to(value):
return IMAPheader.parse_address_list(value)
@staticmethod
def parse_cc(value):
return IMAPheader.parse_address_list(value)
@staticmethod
def parse_date(value):
tmp = email.utils.parsedate_tz(value)
if not tmp:
return value
ndate = datetime(*(tmp)[:7])
now = datetime.now()
if now - ndate > timedelta(7):
return ndate.strftime("%d.%m.%Y %H:%M")
return ndate.strftime("%a %H:%M")
@staticmethod
def parse_subject(value):
res = ""
dcd = decode_header(value)
for part in dcd:
if res != "":
res += " "
res += part[0].strip(" ")
return decode(res)
class IMAPconnector(object):
__metaclass__ = Singleton
def __init__(self, request=None, user=None, password=None):
self.address = parameters.get("webmail", "IMAP_SERVER")
self.port = int(parameters.get("webmail", "IMAP_PORT"))
if request:
status, msg = self.login(request.user.username,
request.session["password"])
else:
status, msg = self.login(user, password)
if not status:
raise Exception(msg)
def login(self, user, passwd):
import imaplib
try:
secured = parameters.get("webmail", "IMAP_SECURED")
if secured == "yes":
self.m = imaplib.IMAP4_SSL(self.address, self.port)
else:
self.m = imaplib.IMAP4(self.address, self.port)
except (imaplib.IMAP4.error, ssl.SSLError), error:
return False, _("Connection to IMAP server failed, check your configuration")
try:
self.m.login(user, passwd)
except (imaplib.IMAP4.error, ssl.SSLError), error:
return False, _("Authentication failed, check your configuration")
return True, None
def _encodefolder(self, folder):
if not folder:
return "INBOX"
return folder.encode("imap4-utf-7")
def messages_count(self, folder=None, order=None):
if order:
sign = order[:1]
criterion = order[1:].upper()
if sign == '-':
criterion = "REVERSE %s" % criterion
else:
criterion = "REVERSE DATE"
print criterion
(status, data) = self.m.select(self._encodefolder(folder))
(status, data) = self.m.sort("(%s)" % criterion, "UTF-8", "(NOT DELETED)")
self.messages = data[0].split()
return len(self.messages)
def listfolders(self, topfolder='INBOX', md_folders=[]):
(status, data) = self.m.list()
result = []
for mb in data:
attrs, truc, name = mb.split()
name = name.strip('"').decode("imap4-utf-7")
present = False
for mdf in md_folders:
if mdf["name"] == name:
present = True
break
if not present:
result += [name]
return sorted(result)
def msgseen(self, imapid):
folder, id = imapid.split("/")
self.m.store(id, "+FLAGS", "\\Seen")
def move(self, msgset, oldfolder, newfolder):
self.m.select(self._encodefolder(oldfolder), True)
self.m.copy(msgset, newfolder)
self.m.store(msgset, "+FLAGS", "\\Deleted")
def empty(self, folder):
self.m.select(self._encodefolder(folder))
typ, data = self.m.search(None, 'ALL')
for num in data[0].split():
self.m.store(num, "+FLAGS", "\\Deleted")
self.m.expunge()
def fetch(self, start=None, stop=None, folder=None, all=False):
if not start and not stop:
return []
result = []
self.m.select(self._encodefolder(folder), True)
if start and stop:
submessages = self.messages[start - 1:stop]
range = ",".join(submessages)
else:
submessages = [start]
range = start
if not all:
query = '(FLAGS BODY[HEADER.FIELDS (DATE FROM TO CC SUBJECT)])'
else:
query = '(RFC822)'
typ, data = self.m.fetch(range, query)
if not folder:
folder = "INBOX"
tmpdict = {}
for response_part in data:
if isinstance(response_part, tuple):
imapid = response_part[0].split()[0]
flags = imaplib.ParseFlags(response_part[0])
msg = email.message_from_string(response_part[1])
msg["imapid"] = "%s/%s" % (folder, imapid)
if not "\\Seen" in flags:
msg["class"] = "unseen"
if "\\Answered" in flags:
msg["img_flags"] = "/static/pics/answered.png"
tmpdict[imapid] = msg
for id in submessages:
result += [tmpdict[id]]
return result
class ImapListing(EmailListing):
tpl = "webmail/index.html"
tbltype = WMtable
def __init__(self, user, password, baseurl=None, navparams={}, **kwargs):
self.mbc = IMAPconnector(user=user, password=password)
self.navparams = navparams
EmailListing.__init__(self, **kwargs)
if baseurl:
self.paginator.baseurl = baseurl
def getfolders(self):
md_folders = [{"name" : "INBOX", "icon" : "overview.png"},
{"name" : 'Drafts'},
{"name" : 'Junk'},
{"name" : 'Sent'},
{"name" : 'Trash', "icon" : "trash.png"}]
folders = self.mbc.listfolders(md_folders=md_folders)
for fd in folders:
md_folders += [{"name" : fd}]
return md_folders
+195
View File
@@ -0,0 +1,195 @@
# -*- coding: utf-8 -*-
import time
import sys
from django.http import HttpResponse
from django.template.loader import render_to_string
from django.utils import simplejson
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
from django.conf.urls.defaults import include
from django.contrib.auth.decorators import login_required
from mailng.admin.models import Mailbox
from mailng.lib import events, parameters, _render, _render_error
from mailng.lib.email_listing import Email
from imap_listing import *
from forms import *
from templatetags.webextras import *
def init():
events.register("UserMenuDisplay", menu)
events.register("UserLogin", userlogin)
parameters.register("webmail", "IMAP_SERVER", "string", "127.0.0.1",
help=_("Address of your IMAP server"))
parameters.register("webmail", "IMAP_SECURED", "list_yesno", "no",
help=_("Use a secure connexion to access IMAP server"))
parameters.register("webmail", "IMAP_PORT", "int", "143",
help=_("Listening port of your IMAP server"))
parameters.register("webmail", "SMTP_SERVER", "string", "127.0.0.1",
help=_("Address of your SMTP server"))
parameters.register("webmail", "SMTP_PORT", "int", "25",
help=_("Listening port of your SMTP server"))
def urls():
return (r'^mailng/webmail/',
include('mailng.extensions.webmail.urls'))
def menu(**kwargs):
if kwargs["target"] != "user_menu_box":
return []
return [
{"name" : _("Webmail"),
"url" : reverse(index),
"img" : "/static/pics/webmail.png"}
]
def userlogin(**kwargs):
kwargs["request"].session["password"] = kwargs["password"]
def getctx(status, level=1, **kwargs):
callername = sys._getframe(level).f_code.co_name
ctx = {"status" : status, "callback" : callername}
for kw, v in kwargs.iteritems():
ctx[kw] = v
return ctx
@login_required
def folder(request, name, updatenav=True):
if not name:
name = "INBOX"
order = request.GET.has_key("order") and request.GET["order"] or None
if updatenav:
pageid = request.GET.has_key("page") and int(request.GET["page"]) or 1
if not "navparams" in request.session.keys():
request.session["navparams"] = {}
request.session["folder"] = name
request.session["page"] = pageid
if order:
request.session["navparams"]["order"] = order
lst = ImapListing(request.user.username, request.session["password"],
name, folder=name, order=order)
page = lst.paginator.getpage(request.session["page"])
if page:
content = lst.fetch(request, page.id_start, page.id_stop)
navbar = lst.render_navbar(page)
else:
content = "Empty folder"
navbar = ""
ctx = getctx("ok", listing=content, navbar=navbar,
menu=listing_menu("", name, request.user.get_all_permissions()))
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
@login_required
def index(request):
try:
navp = request.session.has_key("navparams") \
and request.session["navparams"] or {}
lst = ImapListing(request.user.username, request.session["password"],
"INBOX", navparams=navp, folder="INBOX")
except Exception as exp:
return _render_error(request, {"error" : exp})
return lst.render(request, empty=True)
def fetchmail(request, folder, mail_id, all=False):
res = IMAPconnector(request).fetch(start=mail_id, folder=folder, all=all)
if len(res):
return res[0]
return None
@login_required
def viewmail(request, folder, mail_id=None):
header = fetchmail(request, folder, mail_id)
if header:
from templatetags.webextras import viewm_menu
try:
pageid = request.session["page"]
except KeyError:
pageid = "1"
menu = viewm_menu("", request.session, mail_id,
request.user.get_all_permissions())
folder, imapid = header["imapid"].split("/")
mailcontent = render_to_string("webmail/viewmail.html", {
"header" : header, "folder" : folder, "imapid" : imapid
})
ctx = getctx("ok", menu=menu, listing=mailcontent)
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
@login_required
def getmailcontent(request, folder, mail_id):
from mailng.lib.email_listing import Email
msg = fetchmail(request, folder, mail_id, True)
if "class" in msg.keys() and msg["class"] == "unseen":
IMAPconnector(request).msgseen(msg["imapid"])
email = Email(msg, mode="html", links="1")
return email.render(request)
@login_required
def move(request):
for arg in ["msgset", "to"]:
if not request.GET.has_key(arg):
return
folder, id = request.GET["msgset"].split("/")
mbc = IMAPconnector(request)
mbc.move(id, folder, request.GET["to"])
return HttpResponse(simplejson.dumps({"status" : "ok"}),
mimetype="application/json")
@login_required
def empty(request, name):
mbc = IMAPconnector(request)
mbc.empty(name)
return folder(request, name, False)
def render_compose(request, form, bodyheader=None, body=None):
menu = compose_menu("", request.session, request.user.get_all_permissions())
content = render_to_string("webmail/compose.html", {
"form" : form, "bodyheader" : bodyheader, "body" : body
})
ctx = getctx("ok", level=2, menu=menu, listing=content)
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
@login_required
def compose(request):
if request.method == "POST":
form = ComposeMailForm(request.POST)
if form.is_valid():
from email.mime.text import MIMEText
import smtplib
msg = MIMEText(request.POST["id_body"].encode("utf-8"),
_charset="utf-8")
msg["Subject"] = request.POST["subject"]
msg["From"] = request.POST["from_"]
msg["to"] = request.POST["to"]
s = smtplib.SMTP(parameters.get("webmail", "SMTP_SERVER"))
s.sendmail(msg['From'], [msg['To']], msg.as_string())
s.quit()
return folder(request, request.session["navparams"]["folder"], False)
form = ComposeMailForm()
form.fields["from_"].initial = request.user.username
return render_compose(request, form)
@login_required
def reply(request, folder, mail_id):
if request.method == "POST":
return
msg = fetchmail(request, folder, mail_id, True)
email = Email(msg)
lines = email.body.split('\n')
body = ""
for l in lines:
if body != "":
body += "\n"
body += ">%s" % l
form = ComposeMailForm()
form.fields["from_"].initial = request.user.username
form.fields["to"].initial = msg["From"]
m = re.match("re\s*:\s*.+", msg["Subject"].lower())
if m:
form.fields["subject"].initial = msg["Subject"]
else:
form.fields["subject"].initial = "Re: %s" % msg["Subject"]
textheader = msg["From"] + " " + _("wrote:")
return render_compose(request, form, textheader, body)
+50
View File
@@ -0,0 +1,50 @@
{% load i18n %}
<style type="text/css">
#compose-container {
margin: 0 auto;
width: 95%;
}
div.row {
clear: both;
padding-top: 5px;
}
div.row label {
float: left;
width: 120px;
font-size: small;
padding: 3px 0;
}
textarea {
border: 1px solid #cccccc;
background-color: #eee;
width: 80%;
height: 40px;
font-size: medium;
}
textarea:hover {
background-color: #ffffcc;
}
#id_body {
background: white;
width: 100%;
}
input {
width: 80%;
padding: 0;
}
#mailheader {
padding-bottom: 10px;
}
</style>
{% url extensions.webmail.main.compose as compurl %}
<form method="POST" action="{{ compurl }}" id="composemail">
<div id="compose-container">
<div id="mailheader">
{% include "common/genericform.html" %}
</div>
<textarea id="id_body" name="id_body">
{% if bodyheader %}{{ bodyheader }}{% endif %}
{% if body %}{{ body }}{% endif %}</textarea>
</div>
</form>
@@ -0,0 +1,5 @@
<table id="headers">
{% for hdr in headers %}
<tr><th width="150px">{{ hdr.name }}</th><td>{{ hdr.value }}</td></tr>
{% endfor %}
</table>
+297
View File
@@ -0,0 +1,297 @@
{% extends "common/email_listing.html" %}
{% load i18n %}
{% block css %}
#leftcol {
float: left;
width: 150px;
}
thead tr {
position: relative;
top: expression(offsetParent.scrollTop); /*IE5+ only*/
z-index: 1;
}
table#emails > tbody {
position: fixed;
bottom: 35px;
top: 155px;
left: 155px;
right: 0px;
overflow-x: hidden;
overflow-y: auto;
}
#emails th {
text-align: center;
}
#emails tr {
width: 100%;
}
.box h3 {
font-size: small;
margin: 2px 0px 2px 0px;
text-align: center;
}
.box ul {
list-style: none;
padding: 5px 5px;
margin: 0;
}
.box li {
clear: both;
margin-bottom: 4px;
}
.box li.selected {
background: #ffffcc;
}
.box a:link, .box a:visited {
background: transparent;
color: black;
font-weight: bold;
text-decoration: none;
}
.box a:hover {
color: #6AB0C5;
text-decoration: none;
}
.box img {
float: left;
margin-right: 2px;
border: 0;
}
td {
position: relative;
}
th {
-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;
}
.dragging {
background: #6AB0C5;
}
tr {
cursor: pointer;
}
{% endblock %}
{% block js %}
navparams = new Hash({
{% for k, v in navparams.items %}
{{ k }} : "{{ v }}",
{% endfor %}
});
updatelisting = function(resp, overflow) {
/*if (!$defined(overflow)) {
overflow = "auto";
}*/
if ($defined(resp.menu)) {
$("menubar").set("html", resp.menu);
}
$("listing").set("html", resp.listing);
/*if ($defined(resp.navbar)) {
$("navbar").set("html", resp.navbar);
$("navbar").setStyle("display", "block");
$("navbar").getChildren("a").addEvent("click", navbarcallback);
}*/
$$("a[name=compose]").addEvent("click", loadCompose);
$$("a[name=empty]").addEvent("click", empty);
}
empty = function(event) {
event.stop();
new Request.JSON({url : event.target.get("href"), onSuccess : function(resp) {
updatelisting(resp);
}}).get();
}
select = function(obj) {
$$("a[name=loadfolder]").each(function(item) {
item.getParent().removeClass("selected");
item.getParent().addClass("droppable");
});
obj.getParent().removeClass("droppable");
obj.getParent().addClass("selected");
}
{% url extensions.webmail.main.folder as view_url %}
viewmail = function(event) {
event.stop();
if (event.target.parentNode.id) {
var url = event.target.parentNode.id + "/";
event.target.parentNode.setStyle("background", "#ffffcc");
current_anchor.baseurl(url).update();
}
}
register_callback("viewmail", function(resp) {
updatelisting(resp, "hidden");
$$("a[name=back]").addEvent("click", loadFolder);
$$("a[name=reply]").addEvent("click", reply);
setDivHeight("mailcontent", $("table-container").getSize().y, 0);
});
compose_callback = function(resp) {
updatelisting(resp, "hidden");
$$("a[name=back]").addEvent("click", loadFolder);
$$("a[name=sendmail]").addEvent("click", function(evt) {
evt.stop();
$("composemail").set("send", {
onSuccess: function(res) {
updatelisting(JSON.decode(res));
$$("tr").addEvent("click", viewmail);
}
});
$("composemail").send();
});
setDivHeight("id_body", $("mailheader").getSize().y + 20, 0);
}
loadCompose = function(event) {
event.stop();
current_anchor.baseurl("compose/").update();
}
register_callback("compose", compose_callback);
reply = function(event) {
event.stop();
location.hash += event.target.get("href");
}
register_callback("reply", compose_callback);
loadFolder = function(event, obj) {
if (!$defined(obj)) {
obj = event.target;
}
event.stop();
current_anchor.parse_string(obj.get("href"), true).setparams(navparams);
current_anchor.update();
}
register_callback("folder", function(resp) {
updatelisting(resp, "auto");
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/pics/" + src);
th.addClass("selected");
}
/*$$("td[class*=draggable]").each(function(item) {
item.makeDraggable({
droppables: ".droppable",
}).addEvents({
"enter" : function(item, droppable) {
droppable.addClass("dragging");
},
"leave" : function(item, droppable) {
droppable.removeClass("dragging");
},
"drop" : function(item, droppable, event) {
item.dispose();
delete(item);
if (!droppable) {
this.element = this.oldelement;
return;
}
droppable.removeClass("dragging");
this.oldelement.getParent().dispose();
to = droppable.getFirst("a").get("href");
{% url extensions.webmail.main.move as moveurl %}
new Request.JSON({url: "{{ moveurl }}", onSuccess: function(resp) {
}}).get({msgset : this.oldelement.getParent().get("id"),
to : to});
},
"beforeStart": function(item) {
var clone = item.clone()
.setStyles({"position" : "absolute",
"opacity" : 0.6,
"width" : "300px",
"border" : "1px solid #333",
"background" : "white"})
.setStyles(item.getCoordinates())
.inject(document.body);
this.oldelement = this.element;
this.element = clone;
},
"cancel": function(item) {
if ($defined(this.oldelement)) {
item.dispose();
delete(item);
this.element = this.oldelement;
}
}
});
});*/
$$("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();
});
});
/*navbarcallback = function(event) {
event.stop();
current_anchor.setparams(navparams);
current_anchor.parse_string(event.target.getParent().get("href")).update();
}*/
foldercallback = function(event) {
select(event.target);
loadFolder(event, event.target);
}
ajaxListener("INBOX/", updatelisting);
$$("a[name=loadfolder]").addEvent("click", foldercallback);
/*$("navbar").getChildren("a").addEvent("click", navbarcallback);*/
{% endblock %}
{% block middletitle %}{% trans "Webmail" %}{% endblock %}
{% block leftcol %}
<div id="leftcol" class="box">
<h3>{% trans "Folders" %}</h3>
<ul>
{% for fd in folders %}
{% url extensions.webmail.main.folder fd.name as fdurl %}
<li class="droppable">
<a href="{{ fd.name }}" name="loadfolder">
{% if fd.icon %}<img src="/static/pics/{{ fd.icon }}" />
{% else %}<img src="/static/pics/folder.png" />
{% endif %}
{{ fd.name }}
</a>
</li>
{% endfor %}
</ul>
</div>
{% endblock %}
@@ -0,0 +1,25 @@
{% load i18n %}
{% load webextras %}
<style type="text/css">
#mailcontent {
width: 100%;
border: 0;
margin: 0;
}
td {
font-size: 1em;
height: 20px;
padding-left: 5px;
}
th {
background: #F7F9Fb;
}
</style>
<div id="table-container">
{% render_headers header %}
</div>
{% url extensions.webmail.main.getmailcontent folder imapid as geturl %}
<iframe id="mailcontent" src="{{ geturl }}"></iframe>
@@ -0,0 +1,101 @@
from django import template
from django.template.loader import render_to_string
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
from mailng.extensions import webmail
from mailng.extensions.webmail.imap_listing import IMAPheader
register = template.Library()
def backurl(session, **kwargs):
res = session["folder"]
params = ""
for key in ["page"]:
if key in session.keys():
if params != "":
params += "&"
params += "%s=%s" % (key, session[key])
for k, v in kwargs.iteritems():
if params != "":
params += "&"
params += "%s=%s" % (k, v)
if params != "":
res += "?%s" % params
return res
@register.simple_tag
def viewm_menu(selection, session, mail_id, perms):
entries = [
{"name" : "back",
"url" : "%s?page=%s" % (session["folder"], session["page"]),
"img" : "/static/pics/back.png",
"label" : _("Back")},
{"name" : "reply",
"url" : "reply/",
"img" : "/static/pics/reply.png",
"label" : _("Reply")},
{"name" : "reply-all",
"url" : "",
"img" : "/static/pics/reply-all.png",
"label" : _("Reply all")},
{"name" : "forward",
"url" : "",
"img" : "/static/pics/alias.png",
"label" : _("Forward")},
]
return render_to_string('main/menu.html',
{"selection" : selection, "entries" : entries,
"perms" : perms})
@register.simple_tag
def compose_menu(selection, session, perms):
entries = [
{"name" : "back",
"url" : "%s?page=%s" % (session["folder"], session["page"]),
"img" : "/static/pics/back.png",
"label" : _("Back")},
{"name" : "sendmail",
"url" : "",
"img" : "/static/pics/send-receive.png",
"label" : _("Send")},
]
return render_to_string('main/menu.html',
{"selection" : selection, "entries" : entries,
"perms" : perms})
@register.simple_tag
def listing_menu(selection, folder, perms):
entries = [
{"name" : "compose",
"url" : reverse(webmail.main.compose),
"img" : "/static/pics/edit.png",
"label" : _("New message")}
]
if folder in ["Trash"]:
entries += [
{"name" : "empty",
"url" : reverse(webmail.main.empty, args=[folder]),
"img" : "/static/pics/clear.png",
"label" : _("Empty trash")}
]
return render_to_string("main/menu.html",
{"selection" : selection, "entries" : entries,
"perms" : perms})
@register.simple_tag
def render_headers(headers):
fields = ["Subject", "From", "To", "Cc", "Date"]
result = []
for f in fields:
if not f in headers.keys():
continue
try:
result += [{"name" : f, "value" : getattr(IMAPheader, "parse_%s" % f.lower())(headers[f])}]
except AttributeError:
result += [{"name" : f, "value" : headers[f]}]
return render_to_string("webmail/headers.html", {
"headers" : result
})
+12
View File
@@ -0,0 +1,12 @@
from django.conf.urls.defaults import *
urlpatterns = patterns('mailng.extensions.webmail.main',
(r'^$', "index"),
(r'^compose/$', "compose"),
(r'^move/$', "move"),
(r'^empty/(?P<name>.+)/', "empty"),
(r'^(?P<folder>.+)/(?P<mail_id>[\w\-\+]+)/content/$', 'getmailcontent'),
(r'^(?P<folder>.+)/(?P<mail_id>[\w\-\+]+)/reply/$', 'reply'),
(r'^(?P<folder>.+)/(?P<mail_id>[\w\-\+]+)/$', 'viewmail'),
(r'^(?P<name>.+)/$', "folder"),
)
+16 -1
View File
@@ -50,6 +50,10 @@ def _render(request, tpl, user_context):
return render_to_response(tpl, user_context,
context_instance=RequestContext(request))
def _render_error(request, user_context):
return render_to_response("common/error.html", user_context,
context_instance=RequestContext(request))
def exec_as_vuser(cmd):
code, output = exec_pipe("sudo -u %s %s" % (settings.VIRTUAL_UID, cmd))
if code:
@@ -63,7 +67,7 @@ def _ctx_ok(url):
def _ctx_ko(tpl, ctx):
return {"status" : "ko", "content" : render_to_string(tpl, ctx)}
def decode(s, encodings=('ascii', 'utf8', 'latin1')):
def decode(s, encodings=('utf8', 'latin1', 'windows-1252', 'ascii')):
for encoding in encodings:
try:
return s.decode(encoding)
@@ -78,3 +82,14 @@ def getoption(name, default=None):
except AttributeError:
res = default
return res
class Singleton(type):
def __init__(cls, name, bases, dict):
super(Singleton, cls).__init__(name, bases, dict)
cls.instance = None
def __call__(cls, *args, **kwargs):
if cls.instance is None:
cls.instance = super(Singleton, cls).__call__(*args, **kwargs)
return cls.instance
+187
View File
@@ -0,0 +1,187 @@
# -*- coding: utf-8 -*-
import time
import re
import lxml
import lxml.html
from django.template.loader import render_to_string
from django.template import Template, Context
from mailng.lib import _render, decode
attached_map = {}
class MBconnector(object):
def __init__(self, address, port):
self.address = address
self.port = port
class Page(object):
def __init__(self, pageid, id_start, id_stop, items,
items_per_page, has_previous, has_next,
baseurl=None):
self.pageid = pageid
self.id_start = id_start
self.id_stop = id_stop
self.items = items
self.items_per_page = items_per_page
self.has_previous = has_previous
self.has_next = has_next
self.baseurl = baseurl
def previous_page(self):
if not self.has_previous:
return False
return self.pageid - 1
def next_page(self):
if not self.has_next:
return False
return self.pageid + 1
def last_page(self):
lid = self.items / self.items_per_page
if not lid:
return 1
if self.items % self.items_per_page:
lid += 1
return lid
class Paginator(object):
def __init__(self, total, elems_per_page):
self.total = total
self.elems_per_page = elems_per_page
self.baseurl = None
def _indexes(self, page):
id_start = self.elems_per_page * page + 1
id_stop = id_start + self.elems_per_page - 1
return (id_start, id_stop)
def getpage(self, page):
if page < 1:
return None
id_start, id_stop = self._indexes(page - 1)
has_previous = has_next = False
if id_start < self.total:
if page > 1:
has_previous = True
if id_stop < self.total:
has_next = True
else:
id_stop = self.total
else:
return None
p = Page(page, id_start, id_stop, self.total,
self.elems_per_page, has_previous, has_next)
if self.baseurl:
p.baseurl = self.baseurl
return p
class EmailListing:
def __init__(self, folder=None, elems_per_page=40, **kwargs):
self.folder = folder
self.elems_per_page = elems_per_page
order = "order" in kwargs.keys() and kwargs["order"] or None
self.paginator = Paginator(self.mbc.messages_count(self.folder, order),
elems_per_page)
def render_navbar(self, page):
return render_to_string("common/navbar.html", {
"page" : page
})
def getfolders(self):
return None
def fetch(self, request, id_start, id_stop):
table = self.tbltype(self.mbc.fetch(start=id_start, stop=id_stop,
folder=self.folder))
tpl = Template("""
<form method="POST" id="listingform">
{{ table }}
</form>""")
return tpl.render(Context({"table" : table.render(request)}))
def render(self, request, pageid=1, **kwargs):
page = self.paginator.getpage(pageid)
thead = listing = ""
if "empty" in kwargs.keys() and not kwargs["empty"]:
if not page:
listing = "Empty folder"
else:
listing = self.fetch(request, page.id_start, page.id_stop)
elapsed = kwargs.has_key("start") and time.time() - kwargs["start"] or 0
return _render(request, self.tpl, {
"listing" : listing,
"elapsed" : elapsed,
"navbar" : self.render_navbar(page),
"folders" : self.getfolders(),
"selection" : self.folder,
"navparams" : self.navparams,
"current_page" : pageid
})
class Email(object):
def __init__(self, msg, mode="plain", links="0"):
self.attached_map = {}
contents = {"html" : "", "plain" : ""}
for part in msg.walk():
if part.get_content_maintype() == 'multipart':
continue
if part.get_content_type() in ("text/html", "text/plain"):
contents[part.get_content_subtype()] += part.get_payload(decode=True)
if mode != "html" or links == "0":
continue
if part.get_content_maintype() == "image":
if part.has_key("Content-Location"):
fname = part["Content-Location"]
if re.match("^http:", fname):
path = fname
else:
path = "/%s" % os.path.join("static/tmp", fname)
fp = open(path, "wb")
fp.write(part.get_payload(decode=True))
fp.close()
m = re.match("<(.+)>", part["Content-ID"])
if m:
self.attached_map[m.group(1)] = path
else:
self.attached_map[part["Content-ID"]] = path
if not contents.has_key(mode) or contents[mode] == "":
if mode == "html":
mode = "plain"
else:
mode = "html"
self.pre, self.body = \
getattr(self, "viewmail_%s" % mode)(contents[mode], links=links)
def map_cid(self, url):
import re
map = globals()["attached_map"]
m = re.match(".*cid:([^\"]+)", url)
if m:
if map.has_key(m.group(1)):
return map[m.group(1)]
return url
def render(self, request):
return _render(request, 'common/getmailcontent.html', {
"body" : self.body, "pre" : self.pre
})
def viewmail_plain(self, content, **kwargs):
body = decode(content)
return (True, body)
def viewmail_html(self, content, **kwargs):
links = kwargs.has_key("links") and kwargs["links"] or "0"
html = lxml.html.fromstring(content)
if links == "0":
html.rewrite_links(lambda x: None)
else:
html.rewrite_links(self.map_cid)
body = lxml.html.tostring(html)
body = Template(decode(body)).render({})
return (False, body)
+3 -1
View File
@@ -13,7 +13,9 @@ events = ["CreateDomain",
"ModifyMailbox",
"UserMenuDisplay",
"AdminMenuDisplay"]
"AdminMenuDisplay",
"UserLogin"]
callbacks = {}
+210
View File
@@ -0,0 +1,210 @@
# -*- coding: latin-1 -*-
"""
Imap folder names are encoded using a special version of utf-7 as defined in RFC
2060 section 5.1.3.
5.1.3. Mailbox International Naming Convention
By convention, international mailbox names are specified using a
modified version of the UTF-7 encoding described in [UTF-7]. The
purpose of these modifications is to correct the following problems
with UTF-7:
1) UTF-7 uses the "+" character for shifting; this conflicts with
the common use of "+" in mailbox names, in particular USENET
newsgroup names.
2) UTF-7's encoding is BASE64 which uses the "/" character; this
conflicts with the use of "/" as a popular hierarchy delimiter.
3) UTF-7 prohibits the unencoded usage of "\"; this conflicts with
the use of "\" as a popular hierarchy delimiter.
4) UTF-7 prohibits the unencoded usage of "~"; this conflicts with
the use of "~" in some servers as a home directory indicator.
5) UTF-7 permits multiple alternate forms to represent the same
string; in particular, printable US-ASCII chararacters can be
represented in encoded form.
In modified UTF-7, printable US-ASCII characters except for "&"
represent themselves; that is, characters with octet values 0x20-0x25
and 0x27-0x7e. The character "&" (0x26) is represented by the two-
octet sequence "&-".
All other characters (octet values 0x00-0x1f, 0x7f-0xff, and all
Unicode 16-bit octets) are represented in modified BASE64, with a
further modification from [UTF-7] that "," is used instead of "/".
Modified BASE64 MUST NOT be used to represent any printing US-ASCII
character which can represent itself.
"&" is used to shift to modified BASE64 and "-" to shift back to US-
ASCII. All names start in US-ASCII, and MUST end in US-ASCII (that
is, a name that ends with a Unicode 16-bit octet MUST end with a "-
").
For example, here is a mailbox name which mixes English, Japanese,
and Chinese text: ~peter/mail/&ZeVnLIqe-/&U,BTFw-
Found here:
http://svn.plone.org/svn/collective/mxmImapClient/trunk/imapUTF7.py
"""
import binascii
import codecs
# encoding
def modified_base64(s):
s = s.encode('utf-16be')
return binascii.b2a_base64(s).rstrip('\n=').replace('/', ',')
def doB64(_in, r):
if _in:
r.append('&%s-' % modified_base64(''.join(_in)))
del _in[:]
def encoder(s):
r = []
_in = []
for c in s:
ordC = ord(c)
if 0x20 <= ordC <= 0x25 or 0x27 <= ordC <= 0x7e:
doB64(_in, r)
r.append(c)
elif c == '&':
doB64(_in, r)
r.append('&-')
else:
_in.append(c)
doB64(_in, r)
return (str(''.join(r)), len(s))
# decoding
def modified_unbase64(s):
b = binascii.a2b_base64(s.replace(',', '/') + '===')
return unicode(b, 'utf-16be')
def decoder(s):
r = []
decode = []
for c in s:
if c == '&' and not decode:
decode.append('&')
elif c == '-' and decode:
if len(decode) == 1:
r.append('&')
else:
r.append(modified_unbase64(''.join(decode[1:])))
decode = []
elif decode:
decode.append(c)
else:
r.append(c)
if decode:
r.append(modified_unbase64(''.join(decode[1:])))
bin_str = ''.join(r)
return (bin_str, len(s))
class StreamReader(codecs.StreamReader):
def decode(self, s, errors='strict'):
return decoder(s)
class StreamWriter(codecs.StreamWriter):
def decode(self, s, errors='strict'):
return encoder(s)
def imap4_utf_7(name):
if name == 'imap4-utf-7':
return (encoder, decoder, StreamReader, StreamWriter)
codecs.register(imap4_utf_7)
## testing methods
def imapUTF7Encode(ust):
"Returns imap utf-7 encoded version of string"
return ust.encode('imap4-utf-7')
def imapUTF7EncodeSequence(seq):
"Returns imap utf-7 encoded version of strings in sequence"
return [imapUTF7Encode(itm) for itm in seq]
def imapUTF7Decode(st):
"Returns utf7 encoded version of imap utf-7 string"
return st.decode('imap4-utf-7')
def imapUTF7DecodeSequence(seq):
"Returns utf7 encoded version of imap utf-7 strings in sequence"
return [imapUTF7Decode(itm) for itm in seq]
def utf8Decode(st):
"Returns utf7 encoded version of imap utf-7 string"
return st.decode('utf-8')
def utf7SequenceToUTF8(seq):
"Returns utf7 encoded version of imap utf-7 strings in sequence"
return [itm.decode('imap4-utf-7').encode('utf-8') for itm in seq]
__all__ = ['imapUTF7Encode', 'imapUTF7Decode', ]
if __name__=='__main__':
## print u'bøx'.encode('imap4-utf-7')
## print 'expected b&APg-x'
##
## print u'båx'.encode('imap4-utf-7')
## print 'expected b&AOU-x'
##
print '#######'
print 'bøx'
e = imapUTF7Encode(u'bøx')
print e
print imapUTF7Decode(e).encode('latin-1')
print '#######'
print 'båx'
e = imapUTF7Encode(u'båx')
print e
print imapUTF7Decode(e).encode('latin-1')
print '#######'
print '~/bågø'
e = imapUTF7Encode(u'~/bågø')
print e
print imapUTF7Decode(e).encode('latin-1')
print '#######'
print 'Ting & Såger'
e = imapUTF7Encode(u'Ting & Såger')
print e
print imapUTF7Decode(e).encode('latin-1')
## e = imapUTF7Decode('b&AOU-x')
## print e.encode('latin-1')
##
## e = imapUTF7Decode('b&APg-x')
## print e.encode('latin-1')
##
print '#######'
print '~/Følder/mailbåx & stuff + more'
n = u'~/Følder/mailbåx & stuff + more'
e = imapUTF7Encode(n)
print e
print imapUTF7Decode(e).encode('latin-1')
print '#######'
print '~peter/mail/&ZeVnLIqe-/&U,BTFw-'
print imapUTF7Decode('~peter/mail/&ZeVnLIqe-/&U,BTFw-').encode('utf-8')
+57
View File
@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
import string
from mailng.admin.models import Parameter
"""
This interface provides a simple way to declare and store parameters
in MailNG's database.
Core components or extensions can register their own parameters, which
will be available and modifiable directly from the web interface.
Only super users will be able to acces this part of the web interface.
"""
_params = {}
class NotDefined(Exception):
def __init__(self, app, name):
self.app = app
self.name = name
def __str__(self):
return "Application '%s' and/or parameter '%s' not defined" % (self.app,
self.name)
def register(app, name, type="string", deflt=None, help=None, **kwargs):
"""Register a new parameter.
app corresponds to a core component (admin, main) or an extension.
"""
if not app in _params.keys():
_params[app] = {}
_params[app][name] = {"type" : type, "default" : deflt, "help" : help}
def save(app, name, value):
if not app in _params.keys() or not name in _params[app].keys():
raise NotDefined(app, name)
fullname = "%s.%s" % (app, name)
try:
p = Parameter.objects.get(name=fullname)
except Parameter.DoesNotExist:
p = Parameter()
p.name = fullname
if p.value != value:
p.value = value
p.save()
return True
def get(app, name):
if not app in _params.keys() or not name in _params[app].keys():
raise NotDefined(app, name)
try:
p = Parameter.objects.get(name="%s.%s" % (app, name))
except Parameter.DoesNotExist:
return _params[app][name]["default"]
return p.value
+104
View File
@@ -0,0 +1,104 @@
# -*- coding: utf-8 -*-
import inspect
import lxml
from django.template import Template, Context
from django.template.loader import render_to_string
class Column:
def __init__(self, name, **kwargs):
self.name = name
for k, v in kwargs.iteritems():
setattr(self, k, v)
def __str__(self):
try:
label = getattr(self, "label")
except KeyError:
label = getattr(self, "name")
return label
class SelectionColumn(Column):
def __str__(self):
return "<input type='checkbox' name='selectall' id='selectall' />"
def render(self, value):
return "<input type='checkbox' name='%s' value='%s' />" \
% (self.name, value)
class ImgColumn(Column):
def __str__(self):
return ""
def render(self, value):
return "<img src='%s' />" % value
class Table(object):
tableid = ""
def __init__(self, rows={}):
self.columns = []
for m in inspect.getmembers(self):
if isinstance(m[1], Column):
try:
if getattr(m[1], "first"):
self.columns = [m[1]] + self.columns
except AttributeError:
self.columns += [m[1]]
self.rows = []
for row in rows:
nrow = {"id" : row[self.idkey], "cols" : []}
for c in self.columns:
if isinstance(c, SelectionColumn):
nrow["cols"] += [{"value" : c.render(row[self.idkey]), "safe" : True}]
continue
if isinstance(c, ImgColumn):
key = "img_%s" % c.name
if key in row.keys():
nrow["cols"] += [{"value" : c.render(row[key]), "safe" : True, "width" : c.width}]
else:
nrow["cols"] += [{"width" : c.width}]
continue
if row.has_key(c.name):
try:
cssclass = getattr(c, "cssclass")
except AttributeError:
cssclass = ""
if "class" in row.keys():
cssclass += (cssclass != "") and ", %s" % row["class"] or row["class"]
nrow["cols"] += [{"value" : self.parse(c.name, row[c.name]),
"class" : cssclass,
"width" : c.width}]
self.rows += [nrow]
def render(self, request):
t = Template("""
<table id="{{ tableid }}">
{% include "common/table_head.html" %}
{% include "common/table_body.html" %}
</table>
""")
return t.render(Context({
"table" : self, "tableid" : self.tableid
}))
def render_head(self, request):
t = Template("""
<table id="{{ tableid }}">
{% include "common/table_head.html" %}
</table>
""")
return t.render(Context({
"table" : self, "tableid" : "%s_head" % self.tableid
}))
def render_body(self, request):
t = Template("""
<table id="{{ tableid }}">
{% include "common/table_body.html" %}
</table>
""")
return t.render(Context({
"table" : self, "tableid" : "%s_body" % self.tableid
}))
+1 -1
View File
@@ -478,7 +478,7 @@ msgstr "Domaines"
#: templates/layout.html:87 templates/admin/settings_header.html:6
msgid "Settings"
msgstr "Paramètres"
msgstr "Configuration"
#: templates/admin/addpermission.html:24 templates/admin/settings_menu.html:15
msgid "Add permission"
+1 -1
View File
@@ -6,7 +6,7 @@ from django.utils.translation import ugettext as _
class ARmessageForm(forms.ModelForm):
class Meta:
model = ARmessage
fields = ('subject', 'content', 'enabled')
fields = ('subject', 'content', 'enabled', 'untildate')
def __init__(self, *args, **kwargs):
super(ARmessageForm, self).__init__(*args, **kwargs)
+1
View File
@@ -7,6 +7,7 @@ class ARmessage(models.Model):
subject = models.CharField(_('subject'), max_length=255)
content = models.TextField(_('content'))
enabled = models.BooleanField(_('enabled'))
# untildate = models.DateTimeField(_('until'))
class ARhistoric(models.Model):
armessage = models.ForeignKey(ARmessage)
File diff suppressed because it is too large Load Diff
+181 -6
View File
@@ -1,6 +1,140 @@
window.addEvent('domready', function(){
if ($('menubar')) {
$('menubar').getElements('li.dropdown').each( function( elem ){
parse_menubar('topmenubar');
parse_menubar('menubar');
/*SqueezeBox.assign($$('a.boxed'), {
parse: 'rel',
});*/
});
callbacks = {};
register_callback = function(name, callback) {
callbacks[name] = callback;
}
current_anchor = null;
check_anchor = function() {
if (current_anchor.serialized == location.hash) {
return;
}
current_anchor.from_string(location.hash);
if (!current_anchor.serialized) {
query = current_anchor.deflocation;
} else {
query = current_anchor.serialized.substring(1);
}
new Request.JSON({url: query, onSuccess: function(resp) {
callback = ($defined(resp.callback)) ? resp.callback : "default";
callbacks[callback](resp);
}}).get();
}
ajaxListener = function(deflocation, defcallback) {
current_anchor = new HashWrapper(deflocation);
register_callback("default", defcallback);
check_anchor.periodical(300);
}
/*
*
*/
function HashWrapper(deflocation) {
this.deflocation = deflocation;
this.params = $H();
this.base = '';
this.serialized = null;
this.reset = function() {
this.base = null;
this.params.empty();
return this;
};
this.parse_string = function(value, reset) {
var splits = (value.charAt(0) == '#')
? value.substring(1).split('?') : value.split('?');
if (splits.length == 0) {
return this;
}
if (!$defined(reset)) {
reset = false;
}
if (reset) {
this.reset();
}
this.base = (splits[0] == "") ? this.deflocation : splits[0];
if (this.base[this.base.length - 1] != '/') {
this.base += '/';
}
if (splits.length > 1) {
params = splits[1].split('&');
for (var i = 0; i < params.length; i++) {
tmp = params[i].split('=');
this.setparam(tmp[0], tmp[1]);
}
}
return this;
};
this.from_string = function(value, reset) {
this.parse_string(value);
this.serialized = value;
return this;
};
this.serialize = function() {
var res = this.base;
if (this.params.getLength() != 0) {
res += "?" + this.params.toQueryString();
}
return res;
};
this.update = function() {
location.hash = this.serialize();
};
this.setparam = function(name, value) {
this.params.set(name, value);
return this;
};
this.setparams = function(params) {
this.params.extend(params);
return this;
};
this.getparam = function(name) {
if (!this.params.has(name)) {
return "";
}
return this.params.get(name);
}
this.baseurl = function(value) {
this.reset();
this.base = value;
if (this.base[this.base.length - 1] != '/') {
this.base += '/';
}
return this;
};
this.delparam = function(name) {
if (!this.params.has(name)) {
return false;
}
this.params.erase(name);
return this;
};
};
parse_menubar = function(id) {
if ($(id)) {
$(id).getElements('li.dropdown').each(function( elem ){
var list = elem.getElement('ul.links');
var myFx = new Fx.Slide(list).hide();
elem.addEvents({
@@ -15,10 +149,7 @@ window.addEvent('domready', function(){
});
});
}
SqueezeBox.assign($$('a.boxed'), {
parse: 'rel',
});
});
}
get_iframe_body = function(id) {
var saf = navigator.userAgent.match(/Safari/i);
@@ -65,3 +196,47 @@ confirmation = function(question, action, callback) {
});
SqueezeBox.open("/mailng/main/confirm/?question=" + question);
}
setDivHeight = function(id, extrah, modulo) {
var contentsize = $(document.body).getSize().y
- $("topmenubar").getSize().y
- $("header").getSize().y
- extrah;
if ($("navbar")) {
contentsize -= $("navbar").getSize().y;
}
if ($("thead")) {
contentsize -= $("thead").getSize().y;
}
if (modulo) {
contentsize -= contentsize % modulo;
}
$(id).setStyle("height", contentsize + "px");
}
bindRows = function(rooturl, page) {
$$("tr").addEvent("click", function(event) {
if (event.target.parentNode.id) {
var url = rooturl + event.target.parentNode.id + "?page=" + page;
event.target.parentNode.setStyle("background", "#ffffcc");
location.href = url;
}
});
}
toggleSelection = function(id, value) {
$$("input[name=" + id + "]").each(function(obj) {
obj.checked = value;
});
}
checkSelection = function(id, text) {
var getout = $$("input[name=" + id + "]").every(function(item, index) {
return item.checked == false;
});
if (getout) {
return false;
}
return confirm(text);
}
+3 -2
View File
@@ -40,7 +40,7 @@ window.addEvent('domready', function() {
});
$('body').addEvent('click', function(event) {
$(document.body).addEvent('click', function(event) {
target = event.target;
if (target.get('tag') == 'a') {
name = target.get('name');
@@ -54,7 +54,7 @@ window.addEvent('domready', function() {
}
});
$('body').addEvent('submit', function(event) {
$(document.body).addEvent('submit', function(event) {
target = event.target;
if (target.get('tag') == 'form') {
name = target.get('name');
@@ -66,6 +66,7 @@ window.addEvent('domready', function() {
}
}
});
setDivHeight("content", 20, 0);
});
newdom = function() {
+5
View File
@@ -2,4 +2,9 @@ window.addEvent('domready', function() {
SqueezeBox.assign($$('a.boxed'), {
parse: 'rel'
});
new DatePicker("#untildate", {
pickerClass: "datepicker_vista",
allowEmpty: true
});
});
+1745 -152
View File
File diff suppressed because it is too large Load Diff
+15
View File
@@ -3,10 +3,25 @@ window.addEvent('domready', function() {
parse: 'rel',
});
setDivHeight("content", 20, 0);
if ($$("a[name=deletePerm]"))
$$("a[name=deletePerm]").addEvent('click', function(event) {
if (!confirm(gettext("Remove this permission?")))
event.stop();
});
var tip = new Tips($$(".Tips"));
var accordion = new Accordion('h3.atStart', 'div.atStart', {
opacity: false,
onActive: function(toggler, element){
toggler.setStyle('color', '#ff3300');
},
onBackground: function(toggler, element){
toggler.setStyle('color', '#222');
}
}, $('accordion'));
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 641 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 773 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 580 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 868 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 693 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 B

+11
View File
@@ -2,6 +2,7 @@ body {
font-family: arial, helvetica, verdana, tahoma, sans-serif;
font-size: small;
background: #153F65;
margin: 0;
}
#first-container {
@@ -342,3 +343,13 @@ body {
margin: 20px;
font-weight: bold;
}
/* nouveau style */
#topmenubar {
width: 100%;
margin: 0 auto;
padding: 0;
background: #072342;
border-bottom: 2px solid #333;
height: 30px;
}
+8
View File
@@ -31,6 +31,14 @@ input:hover {
background-color: #ffffcc;
}
td.odd {
background: #F7F9Fb;
}
td.even {
}
.title {
float: left;
width: 100%;
+91
View File
@@ -0,0 +1,91 @@
/*
Document : calendar.css
Created on : 29.07.2009
Author : jens klose
Description:
example styles for calendar
*/
.calendarForm{
float:right;
}
.calendar{
font-family:verdana,sans-serif;
font-size:10px;
float:None;
}
button.calendar{
position:relative;
top:-3px;
left:1px;
background-color:transparent;
background-image:url(../pics/calendar.gif);
border:none;
margin: 0px;
padding:0px;
margin-right:3px;
cursor:pointer;
width:16px;
height:16px;
}
input.calendar{
cursor:pointer;
border: 1px solid #87847F;
height:12px;
width:100px;
color: #87847F;
}
div.calendar{
background:white;
border: 1px solid #87847F;
width:180px
}
a.footer{
color:#474747;
padding:2px;
}
.calendar table{
width:100%;
}
.calendar th,
.calendar td{
text-align:center;
font-size:10px;
padding: 0.1em 0.25em 0.1em 0.3em;
}
.calendar tbody td,
.calendar .invalid{
color:silver;
}
.calendar .valid{
color:blue;
font-weight:bold;
cursor:pointer;
}
.calendar .valid.hover{
background:#FFFF77;
font-weight:bold;
}
.calendar caption a,
.calendar .next{
color:blue;
font-weight:bold;
padding:2px;
cursor:pointer;
}
.calendar caption a,
.calendar .prev{
color:blue;
font-weight:bold;
padding:2px;
}
.calendar .active,
.calendar .hilite,
.calendar .inactive{
color:white;
background-color:#BE3916;
font-weight:bold;
}
.calendar .active,
.calendar .inactive{
background-color:red;
}
+329
View File
@@ -0,0 +1,329 @@
html, body {
font-family: arial, helvetica, verdana, tahoma, sans-serif;
font-size: small;
background: #153F65;
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
}
div#container {
height: auto !important;
height: 100%;
min-height: 100%;
position: relative;
background: white;
}
@media screen {
body > div#footer {
position: fixed;
}
}
div#topmenubar {
position: fixed;
border-bottom: 5px solid #333;
height: 30px;
bottom: auto;
}
div#footer {
position: absolute;
height: 30px;
bottom: 0px;
border-top: 5px solid #333;
}
#menubar {
margin: 5px;
display: table;
width: 700px;
margin-top: 35px;
/*position: absolute;*/
}
#topmenubar a, #topmenubar a:visited {
color: white;
font-weight: bold;
}
#topmenubar label {
color: white;
font-weight: bold;
}
#menubar a, #menubar a:visited {
color: black;
font-weight: bold;
}
#menubar label {
color: black;
font-weight: bold;
}
#page {
width: 100%;
top: 35px;
bottom: 35px;
position: absolute;
}
#content {
position: fixed;
overflow: auto;
padding: 5px;
top: 130px;
bottom: 35px;
width: 100%;
}
#header {
width: 100%;
padding: 5px 0;
}
#header h1 {
font-size: medium;
width: 50%;
margin: 0 0 0 5px;
}
#loginbox {
margin: 100px auto;
width: 610px;
padding-bottom: 15px;
border: 2px solid #333;
background: white;
}
#loginbox h2 {
margin: 30px 10px 0 10px;
font-size: small;
}
#loginbox h3 {
font-size: small;
text-align: center;
}
#logo {
float: left;
text-align: center;
width: 300px;
margin-top: 10px;
border-right: 1px solid #ccc;
}
#logo img {
height: 90px;
}
#test {
float: right;
width: 300px;
}
.uibar {
background: #153F65;
padding: 0;
margin: 0 auto;
width: 100%;
}
.menubar a, .menubar a:visited {
text-decoration: none;
}
.menubar a:hover {
text-decoration: underline;
}
.menubar ul {
margin: 0;
padding: 0;
list-style: none;
width: 100%;
color: #2a4069;
}
.menubar li {
float: left;
display: inline;
margin: 5px 5px;
}
.menubar li.selected {
background: #ffff77;
}
.menubar img {
float: left;
margin: 0px 3px;
border: 0;
}
.menubar .title {
color: white;
}
.left {
float: left;
}
.right {
float: right;
}
.logo {
float: right;
/*margin: 5px 0 0 5px;*/
height: 90px;
}
.dropdown {
position: absolute;
}
.dropdown ul {
display: block;
background: #ffffcc;
width: 150px;
height: 90px;
margin: 0px;
padding: 0px;
padding-top: 10px;
}
.dropdown a, .dropdown a:visited {
color: black !important;
}
.spacer {
clear: both;
}
/* Mootools tooltips */
.tip {
color: #fff;
width: 139px;
z-index: 13000;
border: 1px solid #cccccc;
}
.tip-title {
font-weight: bold;
font-size: 11px;
margin: 0;
color: #9FD4FF;
padding: 8px 8px 4px;
background: #eee;
}
.tip-text {
font-size: 11px;
padding: 4px 8px 8px;
background: #eee;
color: black;
}
table {
/*margin: 0 auto;*/
width: 100%;
border-collapse: collapse;
}
tr.odd {
background: white;
}
tr.even {
background: #F4F6F8;
}
th, td {
height: 20px;
}
table th {
background: #CEDBEC;
text-align: left;
padding: 5px;
}
table th.right {
text-align: right;
}
table td {
border-bottom: 1px solid #C8D6E8;
/*padding: 5px;*/
}
input {
border: 1px solid #cccccc;
color: inherit;
/*background-color: #e9e9e9;*/
background-color: #eee;
padding: 2px;
}
input:hover {
color: inherit;
background-color: #ffffcc;
}
table.hr {
width: 80%;
margin: 10px auto;
}
.hr td.odd {
background: #F7F9Fb;
}
.hr td.even {
}
.unseen {
font-weight: bold;
}
.toggler {
margin: 5px auto;
padding: 2px 5px;
background: #CEDBEC;
-moz-border-radius: 5px;
-webkit-border-radius: 5px;
/*border: 1px solid #CCC;*/
font-size: 1.1em;
}
.element {
padding-left: 10px;
}
div.row {
margin: 10px;
}
div.row label {
float: left;
width: 200px;
padding: 3px 0;
}
.error {
margin: 0 auto;
padding: 5px;
background: red;
width: 500px;
text-align: center;
background-color: #F9D2D2;
border: 3px solid #FF2A2A;
color: #FF2A00;
}
+75
View File
@@ -0,0 +1,75 @@
html, body {
font-family: arial, helvetica, verdana, tahoma, sans-serif;
font-size: small;
background: #153F65;
margin: 0;
padding: 0;
}
#loginbox {
margin: 100px auto;
width: 610px;
padding-bottom: 15px;
background: white;
border: 5px solid #333;
/*-moz-box-shadow: 0 10px 30px #222;*/
-webkit-box-shadow: 0 10px 30px #222;
-moz-border-radius: 10px;
-webkit-border-radius: 5px;
}
#loginbox h2 {
margin: 30px 10px 0 10px;
font-size: small;
}
#loginbox h3 {
font-size: small;
text-align: center;
}
#logo {
float: left;
text-align: center;
width: 300px;
margin-top: 10px;
border-right: 1px solid #ccc;
}
#logo img {
height: 90px;
}
#test {
float: right;
width: 300px;
}
.spacer {
clear: both;
}
/* Mootools tooltips */
.tip {
color: #fff;
width: 139px;
z-index: 13000;
border: 1px solid #cccccc;
}
.tip-title {
font-weight: bold;
font-size: 11px;
margin: 0;
color: #9FD4FF;
padding: 8px 8px 4px;
background: #eee;
}
.tip-text {
font-size: 11px;
padding: 4px 8px 8px;
background: #eee;
color: black;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

+1 -1
View File
@@ -26,7 +26,7 @@ window.addEvent("domready", function() {
{% if error %}<div class="error">{{ error }}</div>{% endif %}
{% url admin.views.addpermission as addperm_url %}
<form action="{{ addperm_url }}" method="POST" id="permform" name="permform">
{% include "admin/genericform.html" %}
{% include "common/genericform.html" %}
<div class="spacer"></div>
<div class="lastrow" align="center">
+1 -1
View File
@@ -1,4 +1,4 @@
{% extends "layout.html" %}
{% extends "nlayout.html" %}
{% load i18n %}
{% block html_head %}
<script src="/static/js/domains.js" type="text/javascript"></script>
+13 -5
View File
@@ -1,9 +1,17 @@
{% extends "layout.html" %}
{% extends "nlayout.html" %}
{% load i18n %}
{% load admin_extras %}
{% block html_head %}
<script src="/static/js/domains.js" type="text/javascript"></script>
<script type="text/javascript">
window.addEvent('domready', function() {
{% url admin.views.domains as view_url %}
bindRows("{{ view_url }}", "{{ current_page }}");
});
</script>
{% endblock %}
{% block middletitle %}{% trans "Available domains" %}{% endblock %}
{% block menu %}{% domains_menu "domains" perms %}{% endblock %}
{% block content %}
<div id="domcontent">
{% if not domains %}
@@ -12,13 +20,13 @@
<table>
<tr>
<th width="50px">{% trans "Actions" %}</th>
<th width="80px">{% trans "Name" %}</th>
<th width="50px">{% trans "Mailboxes" %}</th>
<th>{% trans "Name" %}</th>
<th width="150px">{% trans "Mailboxes" %}</th>
<th width="50px">{% trans "Quota" %}</th>
<th width="50px">{% trans "Enabled" %}</th>
</tr>
{% for dom in domains %}
<tr class="{% cycle 'odd' 'even' %}">
<tr class="{% cycle 'odd' 'even' %}" id="{{ dom.id }}">
<td>
{% url admin.views.editdomain dom.id as edit_url %}
<a href="{{ edit_url }}" class="boxed"
@@ -31,7 +39,7 @@
</a>
</td>
{% url admin.views.mailboxes dom.id as domurl %}
<td><a name="loadDom" href="{{ domurl }}">{{ dom.name }}</a></td>
<td>{{ dom.name }}</td>
<td>{{ dom.mboxcounter }}</td>
<td>{{ dom.quota }} {% trans "MB" %}</td>
<td>{{ dom.enabled|yesno:_("yes,no") }}</td>
+1 -1
View File
@@ -8,7 +8,7 @@
{% if error %}<div class="error">{{ error }}</div>{% endif %}
{% url admin.views.editalias alias.mbox.domain.id alias.id as edit_url %}
<form action="{{ edit_url }}" method="POST" id="aliasform" name="aliasform">
{% include "admin/genericform.html" %}
{% include "common/genericform.html" %}
<div class="spacer"></div>
<div class="lastrow" align="center">
<input type="submit" name="submit" value="{% trans 'Update' %}" />
+1 -1
View File
@@ -9,7 +9,7 @@
{% if error %}<div class="error">{{ error }}</div>{% endif %}
{% url admin.views.editdomain domain.id as dom_url %}
<form action="{{ dom_url }}" method="post" name="domform" id="domform">
{% include "admin/genericform.html" %}
{% include "common/genericform.html" %}
<div class="lastrow" align="center">
<input type="submit" class="button" name="submit" value="{% trans 'Update' %}" />
+1 -1
View File
@@ -9,7 +9,7 @@
{% if error %}<div class="error">{{ error }}</div>{% endif %}
{% url admin.views.editmailbox mbox.domain_id mbox.id as box_url %}
<form action="{{ box_url }}" method="POST" id="mboxform" name="mboxform">
{% include "admin/genericform.html" %}
{% include "common/genericform.html" %}
<div class="spacer"></div>
<div class="lastrow" align="center">
+1 -1
View File
@@ -1,4 +1,4 @@
{% extends "layout.html" %}
{% extends "nlayout.html" %}
{% block content %}
{% load i18n %}
<div id="body">
+1 -1
View File
@@ -8,7 +8,7 @@
{% if error %}<div class="error">{{ error }}</div>{% endif %}
{% url admin.views.newalias mbox.domain_id mbox.id as box_url %}
<form action="{{ box_url }}" method="POST" id="aliasform" name="aliasform">
{% include "admin/genericform.html" %}
{% include "common/genericform.html" %}
<div class="spacer"></div>
<div class="lastrow" align="center">
<input type="submit" name="submit" value="{% trans 'Create' %}" />
+1 -1
View File
@@ -9,7 +9,7 @@
{% if error %}<div class="error">{{ error }}</div>{% endif %}
{% url admin.views.newdomain as dom_url %}
<form action="{{ dom_url }}" method="post" name="domform" id="domform">
{% include "admin/genericform.html" %}
{% include "common/genericform.html" %}
<div class="lastrow" align="center">
<input type="submit" class="button" name="submit" value="{% trans 'Create' %}" />
+1 -1
View File
@@ -9,7 +9,7 @@
{% if error %}<div class="error">{{ error }}</div>{% endif %}
{% url admin.views.newmailbox domain.id as box_url %}
<form action="{{ box_url }}" method="POST" id="mboxform" name="mboxform">
{% include "admin/genericform.html" %}
{% include "common/genericform.html" %}
<div class="spacer"></div>
<div class="lastrow" align="center">
+22
View File
@@ -0,0 +1,22 @@
{% extends "admin/settings_header.html" %}
{% block menu %}
{% load admin_extras %}
{% settings_menu "parameters" perms %}
{% endblock %}
{% block setcontent %}
{% load i18n %}
{% url admin.views.saveparameters as action %}
<form method="POST" action="{{ action }}">
<div id="accordion">
{% for app in gparams %}
<h3 class="toggler atStart">{{ app.name }}</h3>
<div class="element atStart">
{% for p in app.params %}
{% param app.name p %}
{% endfor %}
</div>
{% endfor %}
</div>
<p><input type="submit" name="update" value="{% trans 'Save' %}" /></p>
</form>
{% endblock %}
+1 -1
View File
@@ -1,7 +1,7 @@
{% extends "admin/settings_header.html" %}
{% block menu %}
{% load admin_extras %}
{% settings_menu "permissions" %}
{% settings_menu "permissions" perms %}
{% endblock %}
{% block setcontent %}
{% load i18n %}
+1 -1
View File
@@ -1,4 +1,4 @@
{% extends "layout.html" %}
{% extends "nlayout.html" %}
{% load i18n %}
{% block html_head %}
<script src="/static/js/settings.js" type="text/javascript"></script>
-18
View File
@@ -1,18 +0,0 @@
{% load i18n %}
<ul>
{% url admin.views.permissions as perm_url %}
<li {% ifequal selection "permissions" %}class="selected"{% endifequal %}>
<a href="{{ perm_url }}">
<img src="/static/pics/permissions.png" border="0" />
{% trans "Permissions" %}
</a>
</li>
{% url admin.views.addpermission as addperm_url %}
<li>
<a href="{{ addperm_url }}" class="boxed"
rel="{handler:'iframe',size:{x:320,y:210}}">
<img src="/static/pics/add.png" border="0" />
{% trans "Add permission" %}
</a>
</li>
</ul>
+61
View File
@@ -0,0 +1,61 @@
{% extends "nlayout.html" %}
{% load i18n %}
{% block html_head %}
<style type="text/css">
#listing {
position: absolute;
left: 155px;
right: 0px;
top: 130px;
height: expression(document.body.clientHeight - 35 + "px");
overflow: auto;
}
html > body #listing {
position: fixed;
bottom: 35px;
}
#navbar {
padding: 0;
margin: 0;
background: white;
height: 40px;
width: 100%;
text-align: right;
/*border-top: 1px solid #C8D6E8;*/
display: none;
}
#navbar label {
margin-right: 5px;
font-weight: bold;
}
#navbar img {
margin-top: 5px;
margin-right: 5px;
}
{% block css %}{% endblock %}
</style>
<script type="text/javascript">
window.addEvent("domready", function() {
if ($("selectall")) {
$("selectall").addEvent("click", function(event) {
if ($("selectall").checked) {
toggleSelection("selection", true);
} else {
toggleSelection("selection", false);
}
});
}
{% block js %}{% endblock %}
});
</script>
{% endblock %}
{% block content %}
{% block leftcol %}{% endblock %}
<div id="listing">{{ listing }}</div>
<div class="spacer"></div>
<div id="navbar"></div>
{% endblock %}
+6
View File
@@ -0,0 +1,6 @@
{% extends "nlayout.html" %}
{% load i18n %}
{% block middletitle %}{% trans "Error" %}{% endblock %}
{% block content %}
<div class="error"><p>{{ error }}</p></div>
{% endblock %}
@@ -1,7 +1,8 @@
{% for field in form %}
<div class="row">
{% if field.help_text %}
<div id="{{ field.name }}_helptext" style="display:none">{{ field.help_text}}</div>{% endif %}
<div id="{{ field.name }}_helptext" style="display:none">{{ field.help_text}}</div>
{% endif %}
{{ field.label_tag }} {{ field }} {% ifequal noerrors False %}{{ field.errors }}{% endifequal %}
</div>
{% endfor %}
+1
View File
@@ -0,0 +1 @@
{% if pre %}<pre>{% endif %}{{ body }}{% if pre %}</pre>{% endif %}
+19
View File
@@ -0,0 +1,19 @@
{% load i18n %}
<label>Messages {{ page.id_start }} {% trans "to" %} {{ page.id_stop }} {% trans "of" %} {{ page.items }}</label>
{% if page.previous_page %}
<a href="{{ page.baseurl }}?page=1">
<img src="/static/pics/first.png" border="0" title="{% trans "First" %}" />
</a>
<a href="{{ page.baseurl }}?page={{ page.previous_page }}">
<img src="/static/pics/back.png" border="0" title="{% trans "Previous" %}" />
</a>
{% endif %}
{% if page.next_page %}
<a href="{{ page.baseurl }}?page={{ page.next_page }}">
<img src="/static/pics/next.png" border="0" title="{% trans "Next" %}" />
</a>
<a href="{{ page.baseurl }}?page={{ page.last_page }}">
<img src="/static/pics/last.png" border="0" title="{% trans "Last" %}" />
</a>
{% endif %}
+13
View File
@@ -0,0 +1,13 @@
<tbody>
{% for row in table.rows %}
<tr id="{{ row.id }}" class="{% cycle 'odd' 'even' %}">
{% for cell in row.cols %}
{% if cell.safe %}
<td width="{{ cell.width }}">{{ cell.value|safe }}</td>
{% else %}
<td width="{{ cell.width }}" class="{{ cell.class }}">{{ cell.value }}</td>
{% endif %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
+10
View File
@@ -0,0 +1,10 @@
<thead>
<tr>
{% for c in table.columns %}
<th id="{{ c.name }}" class="sortable" width="{{ c.width }}">
{{ c|safe }}
<span style="float: right"><img src="" style="display:none" /></span>
</th>
{% endfor %}
</tr>
</thead>
+26
View File
@@ -0,0 +1,26 @@
<table id="{{ tableid }}">
<thead>
<tr>
{% for c in table.columns %}
<th id="{{ c.name }}" class="sortable" width="{{ c.width }}">
{{ c|safe }}
<span style="float: right"><img src="" /></span>
</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for row in table.rows %}
<tr id="{{ row.id }}" class="{% cycle 'odd' 'even' %}">
{% for cell in row.cols %}
{% if cell.safe %}
<td width="{{ c.width }}">{{ cell.value|safe }}</td>
{% else %}
<td width="{{ c.width }}" class="{{ cell.class }}">{{ cell.value }}</td>
{% endif %}
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
-5
View File
@@ -1,5 +0,0 @@
{% for field in form %}
<div class="row">
{{ field.label_tag }} {{ field }} {% ifequal noerrors False %}{{ field.errors }}{% endifequal %}
</div>
{% endfor %}
+6 -4
View File
@@ -1,8 +1,10 @@
{% extends "main/header.html" %}
{% load i18n %}
{% block html_head %}
<script src="/static/js/main.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="/static/stylesheets/box.css" />
<link rel="stylesheet" type="text/css" href="/static/stylesheets/datepicker/vista.css" />
<script src="/static/js/datepicker.js" type="text/javascript"></script>
<script src="/static/js/main.js" type="text/javascript"></script>
{% endblock %}
{% block menu %}
{% load main_extras %}
@@ -10,11 +12,11 @@
{% endblock %}
{% block subcontent %}
<form action="" method="POST" id="armform" name="armform" class="simple">
{% include "genericform.html" %}
{% include "common/genericform.html" %}
<div class="spacer"></div>
<div class="lastrow" align="center">
<input type="submit" name="submit" value="{% trans 'Update' %}" />
</div>
</form>
{% endblock %}
{% endblock %}
+2 -2
View File
@@ -9,10 +9,10 @@
{% if error %}<div class="error">{{ error }}</div>{% endif %}
{% url main.views.changepassword as action_url %}
<form action="{{ action_url }}" method="post" name="chpwdform" id="chpwdform">
{% include "genericform.html" %}
{% include "common/genericform.html" %}
<div class="lastrow" align="center">
<input type="submit" class="button" name="submit" value="{% trans 'Update' %}" />
</div>
</form>
{% endblock %}
{% endblock %}
+3 -1
View File
@@ -1,7 +1,9 @@
{% extends "layout.html" %}
{% extends "nlayout.html" %}
{% load i18n %}
{% block html_head %}
<script src="/static/js/calendar.js" type="text/javascript"></script>
<script src="/static/js/main.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="/static/stylesheets/calendar.css" />
{% endblock %}
{% block middletitle %}{% trans "General" %}{% endblock %}
{% block content %}
+2 -2
View File
@@ -4,7 +4,7 @@
{% if entry.menu %}
<li class="dropdown">
<img src="{{ entry.img }}" border="0" />
{{ entry.label }}
<label>{{ entry.label }}</label>
<ul class="links">
{% for sentry in entry.menu %}
<a href="{{ sentry.url }}" name="{{ sentry.name }}">
@@ -26,4 +26,4 @@
</li>
{% endif %}
{% endfor %}
</ul>
</ul>
+92
View File
@@ -0,0 +1,92 @@
{% load i18n %}
{% load main_extras %}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head profile="http://www.w3.org/2005/10/profile">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>MailNG - {%block pagetitle %}{% endblock %}</title>
<link rel="icon" type="image/png" href="/static/stylesheets/logo-icon.png" />
<link rel="stylesheet" type="text/css" href="/static/stylesheets/default.css" />
<link rel="stylesheet" type="text/css" href="/static/stylesheets/SqueezeBox.css" />
<script src="/static/js/mootools.js" type="text/javascript"></script>
<script src="/static/js/mootools-more.js" type="text/javascript"></script>
<script src="/static/js/drag.group.js" type="text/javascript"></script>
<script src="/static/js/SqueezeBox.js" type="text/javascript"></script>
<script src="/static/js/common.js" type="text/javascript"></script>
{% url django.views.i18n.javascript_catalog as catalog_url %}
<script type="text/javascript" src="{{ catalog_url }}"></script>
{%block html_head %}{% endblock %}
</head>
<body>
<div id="container">
<div id="topmenubar" class="uibar menubar">
<div class="left" style="width: 50%">
<ul>
<li>
{% url main.views.index as index_url %}
<a href="{{ index_url }}">
<img src="/static/pics/user.png" border="0" />{% trans "Preferences" %}</a>
</li>
{% loadextmenu perms %}
{% if user.is_superuser or perms.admin.view_domains %}
<li class="dropdown">
<img src="/static/pics/settings.png" border="0" />
<label>{% trans "Admin" %}</label>
<ul class="links">
{% url admin.views.domains as dom_url %}
<a href="{{ dom_url }}">
<li><img src="/static/pics/domains.png" />{% trans "Domains" %}</li>
</a>
{% url admin.views.settings as settings_url %}
<a href="{{ settings_url }}">
<li>
<img src="/static/pics/settings.png" border="0"/>
{% trans "Settings" %}
</li>
</a>
</ul>
</li>
{% endif %}
</ul>
</div>
<div class="right">
<ul>
<li class="title">
{% if user.first_name %}
{{ user.first_name }} {{ user.last_name }}
{% else %}
{{ user.username }}
{% endif %}
/
</li>
<li>
{% url django.contrib.auth.views.logout_then_login as logout_url %}
<a href="{{ logout_url }}">
<img src="/static/pics/logout.png" border="0" />{% trans "Logout" %}</a>
</li>
</ul>
</div>
</div>
<div id="page">
<div id="header">
<img class="logo" src="/static/stylesheets/mailng.png" />
<h1>{% block middletitle %}{% endblock %}</h1>
<div id="menubar" class="menubar">
{% block menu %}{% endblock %}
</div>
</div>
<div id="content">
{% block content %}{% endblock %}
</div>
</div>
<div id="footer" class="uibar menubar">
&nbsp;
</div>
</div>
</body>
</html>
+38 -24
View File
@@ -1,26 +1,40 @@
{% extends "layout.html" %}
{% load i18n %}
{% block html_head %}
<link rel="stylesheet" type="text/css" href="/static/stylesheets/box.css" />
{% endblock %}
{% block content %}
<div id="loginbox">
<h3>{% trans "Welcome" %}</h3>
{% if form.errors %}
<div class="error">
<p>{% trans "Your username and password didn't match. Please try again." %}</p>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head profile="http://www.w3.org/2005/10/profile">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>{% trans "Welcome to MailNG" %}</title>
<link rel="icon" type="image/png" href="/static/stylesheets/logo-icon.png" />
<link rel="stylesheet" type="text/css" href="/static/stylesheets/login.css" />
<link rel="stylesheet" type="text/css" href="/static/stylesheets/box.css" />
<script src="/static/js/mootools.js" type="text/javascript"></script>
<script src="/static/js/mootools-more.js" type="text/javascript"></script>
{% url django.views.i18n.javascript_catalog as catalog_url %}
<script type="text/javascript" src="{{ catalog_url }}"></script>
</head>
<body>
<div id="loginbox">
<div id="logo">
<img src="/static/stylesheets/mailng.png" />
{% if error %}
<div class="error"><p>{{ error }}</p></div>
{% endif %}
<h2>2010 - Version 0.7-dev</h2>
</div>
<div id="test">
<h3>{% trans "Welcome" %}</h3>
<form method="post" action="">
{% include "common/genericform.html" %}
<div class="lastrow" align="center">
<input type="submit" value="login" />
</div>
<input type="hidden" name="next" value="{{ next }}" />
</form>
</div>
<div class="spacer"></div>
</div>
{% endif %}
<form method="post" action=".">
{% for field in form %}
<div class="row">
{{ field.label_tag }}{{ field }}
</div>
{% endfor %}
<div class="lastrow" align="center">
<input type="submit" value="login" />
</div>
<input type="hidden" name="next" value="{{ next }}" />
</form>
</div>
{% endblock %}
</body>
</html>
+12 -3
View File
@@ -21,7 +21,7 @@ urlpatterns = patterns('',
(r'^mailng/admin/', include('mailng.admin.urls')),
(r'^mailng/main/', include('mailng.main.urls')),
(r'^accounts/login/$', 'django.contrib.auth.views.login'),
(r'^accounts/login/$', 'mailng.auth.views.dologin'),
(r'^accounts/logout/$', 'django.contrib.auth.views.logout_then_login'),
(r'^jsi18n/$', 'django.views.i18n.javascript_catalog',
{'packages': ('mailng',),})
@@ -33,6 +33,15 @@ if menus != ():
if settings.DEBUG:
urlpatterns += patterns(
'',
(r'^static/(?P<path>.*)$', 'django.views.static.serve', {'document_root': '/home/tonio/projets/mailng/static'})
(r'^static/(?P<path>.*)$', 'django.views.static.serve',
{'document_root': settings.STATIC_ROOTDIR})
)
# Stats
if 'mailng.extensions.stats' in settings.INSTALLED_APPS:
urlpatterns += patterns(
'',
(r'^img/stats/(?P<path>.*)/$', 'django.views.static.serve',
{'document_root': settings.IMG_ROOTDIR})
)