!! VERY UNSTABLE CHANGESET !!

Many updates in this commit:
 * first work on the webmail plugin, for now only emails listing and folders browsing is supported...
 * New login form,
 * New graphical layout (still needing improvments)
 * code factoring between webmail and quarantine plugins
 * etc.
This commit is contained in:
Antoine Nguyen
2010-01-15 12:15:15 +01:00
parent 6074497a80
commit 6c7a64e947
59 changed files with 1813 additions and 484 deletions
+14
View File
@@ -67,6 +67,20 @@ def settings_menu(selection, perms):
{"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):
menu = events.raiseQueryEvent("AdminMenuDisplay", target="admin_menu_box",
+1 -1
View File
@@ -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:
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
+32
View File
@@ -0,0 +1,32 @@
# -*- 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 _
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)
request.session["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
})
+34 -63
View File
@@ -2,15 +2,19 @@
"""
"""
from datetime import datetime
import time
import email
import re
import os
import lxml.html
from lxml.html.clean import Cleaner
from django.http import HttpResponseRedirect, HttpResponse
from django.shortcuts import get_object_or_404, render_to_response
from django.template.loader import render_to_string
from django.template import Template
from django.utils import simplejson
from django.conf.urls.defaults import *
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
from django.core.paginator import Paginator
from django.contrib.auth.decorators \
import login_required
from mailng.lib import events, parameters
@@ -18,12 +22,7 @@ from mailng.lib import _render, _ctx_ok, _ctx_ko, decode
from mailng.lib import db
from mailng.admin.models import Mailbox
from lib import AMrelease
from datetime import datetime
import email
import re
import os
import lxml.html
from lxml.html.clean import Cleaner
from sql_listing import *
attached_map = {}
@@ -53,62 +52,22 @@ def menu(**kwargs):
]
return []
def lookup(*args):
filter = ""
for a in args:
if not a:
continue
if a[0] == "&":
filter += " AND "
else:
filter += " OR "
filter += a[1:]
conn = db.getconnection("amavis_quarantine")
status, cursor = db.execute(conn, """
SELECT msgs.from_addr, maddr.email, msgs.subject, msgs.content, quarantine.mail_id,
msgs.time_num, msgs.content
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
""" % filter)
return cursor
def render_listing(request, cursor):
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], "time" : datetime.fromtimestamp(row[5]),
"type" : row[6]})
paginator = Paginator(emails, 10)
try:
pagenum = int(request.GET.get('page', '1'))
except ValueError:
pagenum = 1
page = paginator.page(pagenum)
return render_to_string("amavis_quarantine/listing.html", {
"count" : len(rows),
"emails" : page, "current_page" : pagenum,
"last_page" : paginator._get_num_pages()
})
@login_required
def index(request, message=None):
start = time.time()
if not request.user.is_superuser:
mb = Mailbox.objects.get(user=request.user.id)
filter = "&maddr.email='%s'" % mb.full_address
filter = ["&maddr.email='%s'" % mb.full_address]
else:
filter = None
cursor = lookup(filter)
return _render(request, 'amavis_quarantine/index.html', {
"content" : render_listing(request, cursor)
})
try:
pageid = request.GET["page"]
if pageid == "":
pageid = 1
except KeyError:
pageid = 1
lst = SQLlisting(filter)
return lst.render(request, pageid=int(pageid), start=start)
@login_required
def viewmail_plain(request, content):
@@ -311,7 +270,19 @@ def search(request):
filter += " OR "
filter += "msgs.%s LIKE '%%%s%%'" % (c, pattern)
filter = "&(%s)" % filter
cursor = lookup(filter)
ctx = {"status" : "ok", "content" : render_listing(request, cursor)}
return HttpResponse(simplejson.dumps(ctx),
mimetype="application/json")
lst = SQLlisting([filter])
try:
pageid = request.GET["page"]
if pageid == "":
pageid = 1
except KeyError:
pageid = 1
page = lst.paginator.getpage(pageid)
if page:
content = lst.fetch(page.id_start, page.id_stop).render(request)
navbar = lst.render_navbar(page)
else:
content = "Empty quarantine"
navbar = ""
ctx = {"status" : "ok", "listing" : content, "navbar" : navbar}
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
@@ -0,0 +1,85 @@
# -*- 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)
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"
def __init__(self, filter, **kwargs):
self.mbc = SQLconnector(filter)
EmailListing.__init__(self, **kwargs)
def fetch(self, id_start, id_stop):
return Qtable(self.mbc.fetch(start=id_start, stop=id_stop))
+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'),
View File
+124
View File
@@ -0,0 +1,124 @@
# -*- coding: utf-8 -*-
import re
import time
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
from mailng.lib.email_listing import MBconnector, EmailListing
from mailng.lib import tables, imap_utf7, parameters
class WMtable(tables.Table):
idkey = "imapid"
selection = tables.SelectionColumn("selection", first=True)
subject = tables.Column("subject", label=_("Subject"))
from_ = tables.Column("from", label=_("From"))
date = tables.Column("date", label=_("Date"))
from_exp = re.compile("([^<]+)<([^>]+)>")
name_exp = re.compile("\"(.+)\"")
def parse_from(self, value):
m = self.from_exp.match(value)
if m:
name = m.group(1)
name = name.strip()
m2 = self.name_exp.match(name)
if m2:
return m2.group(1)
return m.group(1)
return value
def parse_date(self, value):
ndate = datetime(*(email.utils.parsedate_tz(value))[:7])
now = datetime.now()
if now - ndate > timedelta(7):
return ndate.strftime("%d.%m.%Y %H:%M")
return ndate.strftime("%a %H:%M")
def parse_subject(self, value):
res = ""
dcd = decode_header(value)
for part in dcd:
if res != "":
res += " "
res += part[0].strip(" ")
return decode(res)
class IMAPconnector(MBconnector):
def login(self, user, passwd):
import imaplib
try:
self.m = imaplib.IMAP4_SSL(self.address)
self.m.login(user, passwd)
except imaplib.IMAP4.error, test:
return False, test
return True, None
def _encodefolder(self, folder):
if not folder:
return "INBOX"
return folder.encode("imap4-utf-7")
def messages_count(self, folder=None):
(status, data) = self.m.select(self._encodefolder(folder))
return int(data[0])
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 fetch(self, start=None, stop=None, folder=None):
if not start or not stop:
return []
result = []
self.m.select(self._encodefolder(folder))
range = "%d:%d" % (start, stop)
typ, data = self.m.fetch(range, '(BODY[HEADER.FIELDS (DATE FROM SUBJECT)])')
imapid = start
for response_part in data:
if isinstance(response_part, tuple):
msg = email.message_from_string(response_part[1])
msg["imapid"] = imapid
result += [msg]
imapid += 1
return result
class ImapListing(EmailListing):
tpl = "webmail/index.html"
def __init__(self, user, password, **kwargs):
self.mbc = IMAPconnector(parameters.get("webmail", "SERVER_ADDRESS"), 143)
status, text = self.mbc.login(user, password)
if not status:
print "Login error: %s" % text
EmailListing.__init__(self, **kwargs)
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
def fetch(self, id_start, id_stop):
return WMtable(self.mbc.fetch(start=id_start, stop=id_stop,
folder=self.folder))
+57
View File
@@ -0,0 +1,57 @@
# -*- coding: utf-8 -*-
import time
from django.http import HttpResponse
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
from mailng.lib import _render
from imap_listing import *
def init():
events.register("UserMenuDisplay", menu)
parameters.register("webmail", "SERVER_ADDRESS", "string", "127.0.0.1",
help=_("Mailboxes server address"))
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"}
]
@login_required
def index(request):
start = time.time()
lst = ImapListing(request.user.username, request.session["password"])
try:
pageid = request.GET["page"]
except KeyError:
pageid = 1
return lst.render(request, pageid=int(pageid), start=start)
@login_required
def folder(request, folder):
lst = ImapListing(folder=folder)
try:
pageid = request.GET["page"]
except KeyError:
pageid = 1
page = lst.paginator.getpage(pageid)
if page:
content = lst.fetch(page.id_start, page.id_stop).render(request)
navbar = lst.render_navbar(page)
else:
content = "Empty folder"
navbar = ""
ctx = {"status" : "ok", "listing" : content, "navbar" : navbar}
return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
+85
View File
@@ -0,0 +1,85 @@
{% extends "common/email_listing.html" %}
{% load i18n %}
{% block css %}
#leftcol {
float: left;
width: 150px;
}
.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;
}
{% endblock %}
{% block js %}
$$("a[name=loadfolder]").addEvent("click", function(event) {
event.stop();
$$("a[name=loadfolder]").each(function(item) {
item.setStyle("background", "white");
});
event.target.setStyle("background", "#FFFFCC");
new Request.JSON({url: event.target.get("href"), onSuccess: function(resp) {
$("listing").set("html", resp.listing);
$("navbar").set("html", resp.navbar);
}}).get();
});
{% endblock %}
{% block middletitle %}{% trans "Webmail" %}{% endblock %}
{% block menu %}
<ul>
<a href="">
<li><img src="/static/pics/edit.png" />{% trans "New message" %}</li>
</a>
</ul>
{% 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 %}
<a href="{{ fdurl }}" name="loadfolder">
<li {% ifequal selection fd.name %}class="selected"{% endifequal %}>
{% if fd.icon %}<img src="/static/pics/{{ fd.icon }}" />
{% else %}<img src="/static/pics/folder.png" />
{% endif %}
{{ fd.name }}</li>
</a>
{% endfor %}
</ul>
</div>
{% endblock %}
+6
View File
@@ -0,0 +1,6 @@
from django.conf.urls.defaults import *
urlpatterns = patterns('mailng.extensions.webmail.main',
(r'^$', "index"),
(r'^(?P<folder>.+)/$', "folder"),
)
+1 -1
View File
@@ -63,7 +63,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)
+95
View File
@@ -0,0 +1,95 @@
# -*- coding: utf-8 -*-
import time
from django.template.loader import render_to_string
from mailng.lib import _render
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):
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
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
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
return Page(page, id_start, id_stop, self.total,
self.elems_per_page, has_previous, has_next)
class EmailListing:
def __init__(self, folder=None, elems_per_page=40, **kwargs):
self.folder = folder
self.elems_per_page = elems_per_page
self.paginator = Paginator(self.mbc.messages_count(self.folder),
elems_per_page)
def render_navbar(self, page):
return render_to_string("common/navbar.html", {
"page" : page
})
def getfolders(self):
return None
def render(self, request, pageid=1, **kwargs):
page = self.paginator.getpage(pageid)
if not page:
listing = "Empty folder"
else:
listing = self.fetch(page.id_start, page.id_stop).render(request)
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,
"current_page" : pageid
})
+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')
+3 -3
View File
@@ -24,14 +24,14 @@ class NotDefined(Exception):
return "Application '%s' and/or parameter '%s' not defined" % (self.app,
self.name)
def register(app, name, type="string", default=None, help=None):
def register(app, name, type="string", deflt=None, help=None):
"""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" : default, "help" : help}
_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():
@@ -53,5 +53,5 @@ def get(app, name):
try:
p = Parameter.objects.get(name="%s.%s" % (app, name))
except Parameter.DoesNotExist:
return _params[app][name]["defaut"]
return _params[app][name]["default"]
return p.value
+56
View File
@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*-
import inspect
import lxml
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 Table(object):
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 row.has_key(c.name):
try:
nrow["cols"] += [{"value" : getattr(self, "parse_%s" % c.name)(row[c.name])}]
except AttributeError:
nrow["cols"] += [{"value" : row[c.name]}]
self.rows += [nrow]
def render(self, request):
return render_to_string("common/tables.html", {
"table" : self
})
+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)
+46 -6
View File
@@ -1,6 +1,14 @@
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',
});
});
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 +23,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 +70,38 @@ confirmation = function(question, action, callback) {
});
SqueezeBox.open("/mailng/main/confirm/?question=" + question);
}
setContentSize = function(cellsize) {
var contentsize = $(document.body).getSize().y - $("topmenubar").getSize().y
- $("header").getSize().y;
contentsize -= $("navbar").getSize().y * 2;
contentsize -= contentsize % cellsize;
$("listing").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);
}
+2 -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');
+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
});
});
+239 -152
View File
@@ -182,6 +182,101 @@ Element.implement({
});
/*
---
script: Scroller.js
description: Class which scrolls the contents of any Element (including the window) when the mouse reaches the Element's boundaries.
license: MIT-style license
authors:
- Valerio Proietti
requires:
- core:1.2.4/Events
- core:1.2.4/Options
- core:1.2.4/Element.Event
- core:1.2.4/Element.Dimensions
provides: [Scroller]
...
*/
var Scroller = new Class({
Implements: [Events, Options],
options: {
area: 20,
velocity: 1,
onChange: function(x, y){
this.element.scrollTo(x, y);
},
fps: 50
},
initialize: function(element, options){
this.setOptions(options);
this.element = document.id(element);
this.listener = ($type(this.element) != 'element') ? document.id(this.element.getDocument().body) : this.element;
this.timer = null;
this.bound = {
attach: this.attach.bind(this),
detach: this.detach.bind(this),
getCoords: this.getCoords.bind(this)
};
},
start: function(){
this.listener.addEvents({
mouseover: this.bound.attach,
mouseout: this.bound.detach
});
},
stop: function(){
this.listener.removeEvents({
mouseover: this.bound.attach,
mouseout: this.bound.detach
});
this.detach();
this.timer = $clear(this.timer);
},
attach: function(){
this.listener.addEvent('mousemove', this.bound.getCoords);
},
detach: function(){
this.listener.removeEvent('mousemove', this.bound.getCoords);
this.timer = $clear(this.timer);
},
getCoords: function(event){
this.page = (this.listener.get('tag') == 'body') ? event.client : event.page;
if (!this.timer) this.timer = this.scroll.periodical(Math.round(1000 / this.options.fps), this);
},
scroll: function(){
var size = this.element.getSize(),
scroll = this.element.getScroll(),
pos = this.element.getOffsets(),
scrollSize = this.element.getScrollSize(),
change = {x: 0, y: 0};
for (var z in this.page){
if (this.page[z] < (this.options.area + pos[z]) && scroll[z] != 0)
change[z] = (this.page[z] - this.options.area - pos[z]) * this.options.velocity;
else if (this.page[z] + this.options.area > (size[z] + pos[z]) && scroll[z] + size[z] != scrollSize[z])
change[z] = (this.page[z] - size[z] + this.options.area - pos[z]) * this.options.velocity;
}
if (change.y || change.x) this.fireEvent('change', [scroll.x + change.x, scroll.y + change.y]);
}
});
/*
---
@@ -211,167 +306,159 @@ provides: [Tips]
(function(){
var read = function(option, element){
return (option) ? ($type(option) == 'function' ? option(element) : element.get(option)) : '';
return (option) ? ($type(option) == 'function' ? option(element) : element.get(option)) : '';
};
this.Tips = new Class({
Implements: [Events, Options],
Implements: [Events, Options],
options: {
/*
onAttach: $empty(element),
onDetach: $empty(element),
*/
onShow: function(){
this.tip.setStyle('display', 'block');
},
onHide: function(){
this.tip.setStyle('display', 'none');
},
title: 'title',
text: function(element){
return element.get('rel') || element.get('href');
},
showDelay: 100,
hideDelay: 100,
className: 'tip-wrap',
offset: {x: 16, y: 16},
fixed: false
},
options: {
/*
onAttach: $empty(element),
onDetach: $empty(element),
*/
onShow: function(){
this.tip.setStyle('display', 'block');
},
onHide: function(){
this.tip.setStyle('display', 'none');
},
title: 'title',
text: function(element){
return element.get('rel') || element.get('href');
},
showDelay: 100,
hideDelay: 100,
className: 'tip-wrap',
offset: {x: 16, y: 16},
fixed: false
},
initialize: function(){
var params = Array.link(arguments, {options: Object.type, elements: $defined});
this.setOptions(params.options);
document.id(this);
if (params.elements) this.attach(params.elements);
},
initialize: function(){
var params = Array.link(arguments, {options: Object.type, elements: $defined});
this.setOptions(params.options);
document.id(this);
if (params.elements) this.attach(params.elements);
},
toElement: function(){
if (this.tip) return this.tip;
this.container = new Element('div', {'class': 'tip'});
return this.tip = new Element('div', {
'class': this.options.className,
styles: {
position: 'absolute',
top: 0,
left: 0,
visibility: 'hidden',
display: 'none'
}
}).adopt(
new Element('div', {'class': 'tip-top'}),
this.container,
new Element('div', {'class': 'tip-bottom'})
).inject(document.body);
},
attach: function(elements){
$$(elements).each(function(element){
var title = read(this.options.title, element),
text = read(this.options.text, element);
element.erase('title').store('tip:native', title).retrieve('tip:title', title);
element.retrieve('tip:text', text);
this.fireEvent('attach', [element]);
var events = ['enter', 'leave'];
if (!this.options.fixed) events.push('move');
toElement: function(){
if (this.tip) return this.tip;
this.container = new Element('div', {'class': 'tip'});
return this.tip = new Element('div', {
'class': this.options.className,
styles: {
position: 'absolute',
top: 0,
left: 0
}
}).adopt(
new Element('div', {'class': 'tip-top'}),
this.container,
new Element('div', {'class': 'tip-bottom'})
).inject(document.body);
},
events.each(function(value){
var event = element.retrieve('tip:' + value);
if (!event) event = this['element' + value.capitalize()].bindWithEvent(this, element);
element.store('tip:' + value, event).addEvent('mouse' + value, event);
}, this);
element.addEvent('mouseout', this.hide.bind(this, element)); // firefox doesn't handle mouseleave correct
}, this);
return this;
},
detach: function(elements){
$$(elements).each(function(element){
['enter', 'leave', 'move'].each(function(value){
element.removeEvent('mouse' + value, element.retrieve('tip:' + value)).eliminate('tip:' + value);
});
this.fireEvent('detach', [element]);
if (this.options.title == 'title'){ // This is necessary to check if we can revert the title
var original = element.retrieve('tip:native');
if (original) element.set('title', original);
}
}, this);
return this;
},
elementEnter: function(event, element){
this.container.empty();
['title', 'text'].each(function(value){
var content = element.retrieve('tip:' + value);
if (content) this.fill(new Element('div', {'class': 'tip-' + value}).inject(this.container), content);
}, this);
$clear(this.timer);
this.timer = this.show.delay(this.options.showDelay, this, [element, event]);
},
elementLeave: function(event, element){
this.tip.setStyle('visibility', 'hidden');
$clear(this.timer);
this.timer = this.hide.delay(this.options.hideDelay, this, element);
this.fireForParent(event, element);
},
fireForParent: function(event, element){
var element = $(element);
if (!element) return;
parentNode = element.getParent();
if (parentNode == document.body) return;
if (parentNode.retrieve('tip:enter')) parentNode.fireEvent('mouseenter', event);
else this.fireForParent(event, parentNode);
},
elementMove: function(event, element) {
this.position(event);
},
attach: function(elements){
$$(elements).each(function(element){
var title = read(this.options.title, element),
text = read(this.options.text, element);
element.erase('title').store('tip:native', title).retrieve('tip:title', title);
element.retrieve('tip:text', text);
this.fireEvent('attach', [element]);
var events = ['enter', 'leave'];
if (!this.options.fixed) events.push('move');
events.each(function(value){
var event = element.retrieve('tip:' + value);
if (!event) event = this['element' + value.capitalize()].bindWithEvent(this, element);
element.store('tip:' + value, event).addEvent('mouse' + value, event);
}, this);
}, this);
return this;
},
detach: function(elements){
$$(elements).each(function(element){
['enter', 'leave', 'move'].each(function(value){
element.removeEvent('mouse' + value, element.retrieve('tip:' + value)).eliminate('tip:' + value);
});
this.fireEvent('detach', [element]);
if (this.options.title == 'title'){ // This is necessary to check if we can revert the title
var original = element.retrieve('tip:native');
if (original) element.set('title', original);
}
}, this);
return this;
},
elementEnter: function(event, element){
this.container.empty();
['title', 'text'].each(function(value){
var content = element.retrieve('tip:' + value);
if (content) this.fill(new Element('div', {'class': 'tip-' + value}).inject(this.container), content);
}, this);
$clear(this.timer);
this.timer = this.show.delay(this.options.showDelay, this, element);
this.position((this.options.fixed) ? {page: element.getPosition()} : event);
},
elementLeave: function(event, element){
$clear(this.timer);
this.timer = this.hide.delay(this.options.hideDelay, this, element);
this.fireForParent(event, element);
},
fireForParent: function(event, element){
if (!element) return;
parentNode = element.getParent();
if (parentNode == document.body) return;
if (parentNode.retrieve('tip:enter')) parentNode.fireEvent('mouseenter', event);
else this.fireForParent(parentNode, event);
},
elementMove: function(event, element){
this.position(event);
},
position: function(event){
var size = window.getSize(), scroll = window.getScroll(),
tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight},
props = {x: 'left', y: 'top'},
obj = {};
for (var z in props){
obj[props[z]] = event.page[z] + this.options.offset[z];
if ((obj[props[z]] + tip[z] - scroll[z]) > size[z]) obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z];
}
this.tip.setStyles(obj);
},
fill: function(element, contents){
if(typeof contents == 'string') element.set('html', contents);
else element.adopt(contents);
},
show: function(element){
this.fireEvent('show', [this.tip, element]);
},
hide: function(element){
this.fireEvent('hide', [this.tip, element]);
}
position: function(event){
if (this.tip.getStyle('display') != 'block') return;
var size = window.getSize(), scroll = window.getScroll(),
tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight},
props = {x: 'left', y: 'top'},
obj = {};
for (var z in props){
obj[props[z]] = event.page[z] + this.options.offset[z];
if ((obj[props[z]] + tip[z] - scroll[z]) > size[z]) obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z];
}
this.tip.setStyles(obj);
this.tip.setStyle('visibility', 'visible');
},
fill: function(element, contents){
if(typeof contents == 'string') element.set('html', contents);
else element.adopt(contents);
},
show: function(element, event) {
this.fireEvent('show', [this.tip, element]);
this.position((this.options.fixed) ? {page: element.getPosition()} : event);
},
hide: function(element){
this.fireEvent('hide', [this.tip, element]);
}
});
})();
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: 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;
}
+254
View File
@@ -0,0 +1,254 @@
html, body {
font-family: arial, helvetica, verdana, tahoma, sans-serif;
font-size: small;
background: #153F65;
margin: 0;
padding: 0;
}
#topmenubar {
width: 100%;
margin: 0 auto;
padding: 0;
background: #072342;
border-bottom: 2px solid #333;
height: 30px;
}
#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 {
border: 2px solid #333;
background: white;
min-height: 92%;
margin: 0 15px;
}
#content {
overflow: auto;
padding: 5px;
}
#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;
}
.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 {
text-align: 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;
/*border-spacing: 20px;*/
}
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;
}
.hr td.odd {
background: #F7F9Fb;
}
.hr td.even {
}
+70
View File
@@ -0,0 +1,70 @@
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;
border: 5px 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;
}
.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
@@ -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">
+1 -5
View File
@@ -1,8 +1,4 @@
{% extends "admin/settings_header.html" %}
{% block html_head %}
<script src="/static/js/settings.js" type="text/javascript"></script>
<link rel="stylesheet" type="text/css" href="/static/stylesheets/box.css" />
{% endblock %}
{% block menu %}
{% load admin_extras %}
{% settings_menu "parameters" perms %}
@@ -11,7 +7,7 @@
{% load i18n %}
{% url admin.views.saveparameters as action %}
<form method="POST" action="{{ action }}">
<table>
<table class="hr">
<tr>
<th width="50%">{% trans "Name" %}</th>
<th>{% trans "Value" %}</th>
+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>
+57
View File
@@ -0,0 +1,57 @@
{% extends "nlayout.html" %}
{% load i18n %}
{% block html_head %}
<style type="text/css">
#listing {
overflow: auto;
border-top: 1px solid #C8D6E8;
}
#navbar {
padding: 0;
margin: 0;
background: white;
height: 25px;
width: 100%;
text-align: right;
border-top: 1px solid #C8D6E8;
}
#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 %}
setContentSize(20);
var scroller = new Scroller("listing", {area: 150, velocity: 1});
});
</script>
{% endblock %}
{% block content %}
{% block leftcol %}{% endblock %}
<form method="POST" action="{{ actionurl }}" id="listingform">
<input type="hidden" name="pagenum" id="pagenum" value="{{ current_page }}" />
<div id="listing">{{ listing }}</div>
</form>
<div class="spacer"></div>
<div id="navbar">{{ navbar }}</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 %}
+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=1">
<img src="/static/pics/first.png" border="0" title="{% trans "First" %}" />
</a>
<a href="?page={{ page.previous_page }}">
<img src="/static/pics/back.png" border="0" title="{% trans "Previous" %}" />
</a>
{% endif %}
{% if page.next_page %}
<a href="?page={{ page.next_page }}">
<img src="/static/pics/next.png" border="0" title="{% trans "Next" %}" />
</a>
<a href="?page={{ page.last_page }}">
<img src="/static/pics/last.png" border="0" title="{% trans "Last" %}" />
</a>
{% endif %}
+18
View File
@@ -0,0 +1,18 @@
<table>
<tr>
{% for c in table.columns %}
<th>{{ c|safe }}</th>
{% endfor %}
</tr>
{% for row in table.rows %}
<tr id="{{ row.id }}" class="{% cycle 'odd' 'even' %}">
{% for cell in row.cols %}
{% if cell.safe %}
<td>{{ cell.value|safe }}</td>
{% else %}
<td>{{ cell.value }}</td>
{% endif %}
{% endfor %}
</tr>
{% endfor %}
</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 %}
+2
View File
@@ -1,7 +1,9 @@
{% extends "layout.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/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="topmenubar" class="menubar">
<div class="left">
<ul>
{% loadextmenu perms %}
{% if user.is_superuser or perms.admin.view_domain %}
<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 class="spacer"></div>
<div id="content">
{% block content %}{% endblock %}
</div>
</div>
<!-- <div id="header">
<img class="logo" src="/static/stylesheets/mailng.png" />
<div id="infos">
{% if messages %}
{% for m in messages %}{{ m }}{% endfor %}
{% endif %}
</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>Antoine Nguyen - 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>
+1 -1
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',),})