mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-18 05:51:26 +02:00
!! 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:
@@ -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
@@ -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:
|
||||
|
||||
Executable
@@ -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
@@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
Executable
+23
@@ -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
@@ -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
|
||||
})
|
||||
@@ -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))
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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))
|
||||
@@ -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")
|
||||
@@ -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 %}
|
||||
@@ -0,0 +1,6 @@
|
||||
from django.conf.urls.defaults import *
|
||||
|
||||
urlpatterns = patterns('mailng.extensions.webmail.main',
|
||||
(r'^$', "index"),
|
||||
(r'^(?P<folder>.+)/$', "folder"),
|
||||
)
|
||||
+1
-1
@@ -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)
|
||||
|
||||
@@ -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
|
||||
})
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
@@ -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');
|
||||
|
||||
@@ -2,4 +2,9 @@ window.addEvent('domready', function() {
|
||||
SqueezeBox.assign($$('a.boxed'), {
|
||||
parse: 'rel'
|
||||
});
|
||||
|
||||
new DatePicker("#untildate", {
|
||||
pickerClass: "datepicker_vista",
|
||||
allowEmpty: true
|
||||
});
|
||||
});
|
||||
|
||||
+239
-152
@@ -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 |
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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 |
@@ -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,4 +1,4 @@
|
||||
{% extends "layout.html" %}
|
||||
{% extends "nlayout.html" %}
|
||||
{% load i18n %}
|
||||
{% block html_head %}
|
||||
<script src="/static/js/domains.js" type="text/javascript"></script>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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' %}" />
|
||||
|
||||
@@ -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' %}" />
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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' %}" />
|
||||
|
||||
@@ -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' %}" />
|
||||
|
||||
@@ -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,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,4 +1,4 @@
|
||||
{% extends "layout.html" %}
|
||||
{% extends "nlayout.html" %}
|
||||
{% load i18n %}
|
||||
{% block html_head %}
|
||||
<script src="/static/js/settings.js" type="text/javascript"></script>
|
||||
|
||||
@@ -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 %}
|
||||
@@ -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 %}
|
||||
@@ -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>
|
||||
@@ -1,5 +0,0 @@
|
||||
{% for field in form %}
|
||||
<div class="row">
|
||||
{{ field.label_tag }} {{ field }} {% ifequal noerrors False %}{{ field.errors }}{% endifequal %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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',),})
|
||||
|
||||
Reference in New Issue
Block a user