From 18ad148479c5f3b659861b82a4e5c8cd7b4e7302 Mon Sep 17 00:00:00 2001
From: tonio
Date: Tue, 24 Mar 2009 16:59:18 +0100
Subject: [PATCH] Initial import
---
.hgignore | 2 +
COPYING | 13 +
INSTALL | 29 +
README | 7 +
__init__.py | 0
admin/__init__.py | 0
admin/forms.py | 34 +
admin/models.py | 30 +
admin/templatetags/__init__.py | 0
admin/templatetags/admin_extras.py | 36 +
admin/urls.py | 18 +
admin/views.py | 222 ++
manage.py | 11 +
settings_sample.py | 82 +
static/js/SqueezeBox.js | 384 +++
static/js/domains.js | 92 +
static/js/form.js | 40 +
static/js/mootools.js | 3946 ++++++++++++++++++++++++++++
static/pics/add.png | Bin 0 -> 323 bytes
static/pics/admin.png | Bin 0 -> 611 bytes
static/pics/alias.png | Bin 0 -> 681 bytes
static/pics/domains.png | Bin 0 -> 858 bytes
static/pics/edit.png | Bin 0 -> 574 bytes
static/pics/logout.png | Bin 0 -> 799 bytes
static/pics/mailbox.png | Bin 0 -> 619 bytes
static/pics/remove.png | Bin 0 -> 518 bytes
static/pics/settings.png | Bin 0 -> 611 bytes
static/pics/simplemail.png | Bin 0 -> 8186 bytes
static/pics/trash.png | Bin 0 -> 655 bytes
static/pics/undo.png | Bin 0 -> 650 bytes
static/stylesheets/SqueezeBox.css | 78 +
static/stylesheets/basic.css | 275 ++
static/stylesheets/box.css | 74 +
static/stylesheets/closebox.png | Bin 0 -> 1910 bytes
static/stylesheets/corner-tl.png | Bin 0 -> 495 bytes
static/stylesheets/logo.png | Bin 0 -> 6799 bytes
static/stylesheets/spinner.gif | Bin 0 -> 1569 bytes
templates/admin/aliases.html | 38 +
templates/admin/domain_header.html | 17 +
templates/admin/domain_menu.html | 27 +
templates/admin/domains.html | 45 +
templates/admin/editalias.html | 15 +
templates/admin/editdomain.html | 16 +
templates/admin/editmailbox.html | 17 +
templates/admin/genericform.html | 5 +
templates/admin/index.html | 12 +
templates/admin/layout.html | 48 +
templates/admin/layout_simple.html | 18 +
templates/admin/mailbox.html | 22 +
templates/admin/mailboxes.html | 50 +
templates/admin/mbaliases.html | 14 +
templates/admin/newalias.html | 15 +
templates/admin/newdomain.html | 18 +
templates/admin/newmailbox.html | 18 +
templates/base.html | 36 +
templates/layout.html | 29 +
templates/registration/login.html | 25 +
urls.py | 29 +
58 files changed, 5887 insertions(+)
create mode 100644 .hgignore
create mode 100644 COPYING
create mode 100644 INSTALL
create mode 100644 README
create mode 100644 __init__.py
create mode 100644 admin/__init__.py
create mode 100644 admin/forms.py
create mode 100644 admin/models.py
create mode 100644 admin/templatetags/__init__.py
create mode 100644 admin/templatetags/admin_extras.py
create mode 100644 admin/urls.py
create mode 100644 admin/views.py
create mode 100755 manage.py
create mode 100644 settings_sample.py
create mode 100644 static/js/SqueezeBox.js
create mode 100644 static/js/domains.js
create mode 100644 static/js/form.js
create mode 100644 static/js/mootools.js
create mode 100644 static/pics/add.png
create mode 100644 static/pics/admin.png
create mode 100644 static/pics/alias.png
create mode 100644 static/pics/domains.png
create mode 100644 static/pics/edit.png
create mode 100644 static/pics/logout.png
create mode 100644 static/pics/mailbox.png
create mode 100644 static/pics/remove.png
create mode 100644 static/pics/settings.png
create mode 100644 static/pics/simplemail.png
create mode 100644 static/pics/trash.png
create mode 100644 static/pics/undo.png
create mode 100644 static/stylesheets/SqueezeBox.css
create mode 100644 static/stylesheets/basic.css
create mode 100644 static/stylesheets/box.css
create mode 100644 static/stylesheets/closebox.png
create mode 100644 static/stylesheets/corner-tl.png
create mode 100644 static/stylesheets/logo.png
create mode 100644 static/stylesheets/spinner.gif
create mode 100644 templates/admin/aliases.html
create mode 100644 templates/admin/domain_header.html
create mode 100644 templates/admin/domain_menu.html
create mode 100644 templates/admin/domains.html
create mode 100644 templates/admin/editalias.html
create mode 100644 templates/admin/editdomain.html
create mode 100644 templates/admin/editmailbox.html
create mode 100644 templates/admin/genericform.html
create mode 100644 templates/admin/index.html
create mode 100644 templates/admin/layout.html
create mode 100644 templates/admin/layout_simple.html
create mode 100644 templates/admin/mailbox.html
create mode 100644 templates/admin/mailboxes.html
create mode 100644 templates/admin/mbaliases.html
create mode 100644 templates/admin/newalias.html
create mode 100644 templates/admin/newdomain.html
create mode 100644 templates/admin/newmailbox.html
create mode 100644 templates/base.html
create mode 100644 templates/layout.html
create mode 100644 templates/registration/login.html
create mode 100644 urls.py
diff --git a/.hgignore b/.hgignore
new file mode 100644
index 000000000..178309130
--- /dev/null
+++ b/.hgignore
@@ -0,0 +1,2 @@
+.*~$
+^[^\.]+\.pyc
diff --git a/COPYING b/COPYING
new file mode 100644
index 000000000..9ef7ecc00
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,13 @@
+Copyright (c) 2009, Antoine Nguyen
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 000000000..0d4b5a0b8
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,29 @@
+Installation
+============
+
+Requirements
+------------
+
+- Python version 2.5+
+- Django version 1.0+ (http://docs.djangoproject.com/en/dev/intro/install/#intro-install)
+
+How to install
+--------------
+
+Download Mailng's tarball and extract it::
+ tar xzf mailng-.tar.gz
+
+Enter the mailng repository, create a settings.py file (you can use settings-sample.py as a model). Specify database relative informations. You must choose between mysql and postgresql, other types are not supported by major email projects like postfix or dovecot.
+
+Initialise the database by running the following command::
+ python manage.py syncdb
+
+Create the super admin account (just follow the procedure).
+
+Testing installation
+--------------------
+
+As a typical Django application, Mailng can be tested using a simple web server. Just run this command to start it::
+ python manage.py runserver
+
+You will be able to play with mailng by looking at http://:8000/mailng/admin/
diff --git a/README b/README
new file mode 100644
index 000000000..89bb35293
--- /dev/null
+++ b/README
@@ -0,0 +1,7 @@
+Mailng is a light web messaging solution. It provides different tools that facilitates virtual domains hosting. It is designed to be integrated with email servers like postfix (http://www.postfix.org/) and/or dovecot (http://www.dovecot.org/).
+
+For now, only the administration component is available. Additional tools will come in a near future.
+
+Django is needed to use Mailng (http://www.djangoproject.com/)
+
+Mootools version 1.2 (http://www.mootools.net/) and SqueezeBox version 1.1 (http://digitarald.de/project/squeezebox/) are provided with Mailng.
diff --git a/__init__.py b/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/admin/__init__.py b/admin/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/admin/forms.py b/admin/forms.py
new file mode 100644
index 000000000..d24370960
--- /dev/null
+++ b/admin/forms.py
@@ -0,0 +1,34 @@
+from django import forms
+from mailng.admin.models import Domain, Mailbox, Alias
+
+class DomainForm(forms.ModelForm):
+ class Meta:
+ model = Domain
+
+class MailboxForm(forms.ModelForm):
+ password1 = forms.CharField(label="Password", widget=forms.PasswordInput)
+ password2 = forms.CharField(label="Confirmation", widget=forms.PasswordInput)
+ enabled = forms.BooleanField(label="Enabled", required=False)
+
+ class Meta:
+ model = Mailbox
+ fields = ('name', 'address', 'quota')
+
+ def clean_password2(self):
+ if self.cleaned_data["password1"] != self.cleaned_data["password2"]:
+ raise forms.ValidationError("Passwords mismatch")
+ return self.cleaned_data["password2"]
+
+class AliasForm(forms.ModelForm):
+ def __init__(self, *args, **kwargs):
+ domain = None
+ if kwargs.has_key("domain"):
+ domain = kwargs["domain"]
+ del kwargs["domain"]
+ super(AliasForm, self).__init__(*args, **kwargs)
+ if domain:
+ self.fields['mbox'].queryset = Mailbox.objects.filter(domain=domain.id)
+
+ class Meta:
+ model = Alias
+
diff --git a/admin/models.py b/admin/models.py
new file mode 100644
index 000000000..1ab12639a
--- /dev/null
+++ b/admin/models.py
@@ -0,0 +1,30 @@
+from django.db import models
+from django.contrib.auth.models import User
+
+# Create your models here.
+
+class Domain(models.Model):
+ name = models.CharField(max_length=100)
+ quota = models.IntegerField()
+ enabled = models.BooleanField()
+
+class Mailbox(models.Model):
+ name = models.CharField(max_length=100)
+ address = models.CharField(max_length=100)
+# password = models.CharField(max_length=100)
+ quota = models.IntegerField()
+# enabled = models.BooleanField()
+ uid = models.IntegerField()
+ gid = models.IntegerField()
+ path = models.CharField(max_length=200)
+ domain = models.ForeignKey(Domain)
+ user = models.ForeignKey(User)
+
+ def __str__(self):
+ return "%s" % (self.address)
+
+
+class Alias(models.Model):
+ address = models.CharField(max_length=100)
+ mbox = models.ForeignKey(Mailbox)
+ enabled = models.BooleanField()
diff --git a/admin/templatetags/__init__.py b/admin/templatetags/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/admin/templatetags/admin_extras.py b/admin/templatetags/admin_extras.py
new file mode 100644
index 000000000..fcc4ae1f9
--- /dev/null
+++ b/admin/templatetags/admin_extras.py
@@ -0,0 +1,36 @@
+from django import template
+from django.template.loader import render_to_string
+
+register = template.Library()
+
+@register.simple_tag
+def domain_menu(domain_id, selection):
+ return render_to_string('admin/domain_menu.html',
+ {"selection" : selection,
+ "domain_id" : domain_id})
+
+
+# class MyVar(template.Node):
+# def __init__(self, value, var_name):
+# self.var_name = var_name
+# self.value = value
+
+# def render(self, context):
+# context[self.var_name] = self.value
+# return ""
+
+# import re
+
+# @register.tag(name="var")
+# def do_my_var(parser, token):
+# try:
+# tag_name, arg = token.contents.split(None, 1)
+# except ValueError:
+# raise template.TemplateSyntaxError, '%r tag requires arguments' \
+# % token.contents.split()[0]
+# m = re.search('(.*?) as (\w+)', arg)
+# if not m:
+# raise template.TemplateSyntaxError, "%r tag had invalid arguments" \
+# % tag_name
+# value, var_name = m.groups()
+# return MyVar(value, var_name)
diff --git a/admin/urls.py b/admin/urls.py
new file mode 100644
index 000000000..466d752d3
--- /dev/null
+++ b/admin/urls.py
@@ -0,0 +1,18 @@
+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('mailng.admin.views',
+ (r'^$', 'domains'),
+ (r'^domains/$', 'domains'),
+ (r'^domains/new/', 'newdomain'),
+ (r'^domains/(?P\d+)/edit/$', 'editdomain'),
+ (r'^domains/(?P\d+)/delete/$', 'deldomain'),
+ (r'^domains/(?P\d+)/newmailbox/$', 'newmailbox'),
+ (r'^domains/(?P\d+)/editmailbox/(?P\d+)/$', 'editmailbox'),
+ (r'^domains/(?P\d+)/deletemailbox/(?P\d+)/$', 'delmailbox'),
+ (r'^domains/(?P\d+)/newalias/(?P\d+)/$', 'newalias'),
+ (r'^domains/(?P\d+)/editalias/(?P\d+)/$', 'editalias'),
+ (r'^domains/(?P\d+)/$', 'mailboxes'),
+ url(r'^domains/(?P\d+)/aliases/$', "aliases", name="full-aliases"),
+ url(r'^domains/(?P\d+)/aliases/(?P\d+)/$', "aliases", name="mbox-aliases"),
+ (r'^domains/(?P\d+)/delalias/(?P\d+)/$', 'delalias'),
+ )
diff --git a/admin/views.py b/admin/views.py
new file mode 100644
index 000000000..6de6feec5
--- /dev/null
+++ b/admin/views.py
@@ -0,0 +1,222 @@
+from django.shortcuts import get_object_or_404, render_to_response
+from django.template.loader import render_to_string
+from django.template import RequestContext
+from django.http import HttpResponse, HttpResponseRedirect
+from django.utils import simplejson
+from django.core import serializers
+from django.core.urlresolvers import reverse
+from django.contrib.auth.decorators import login_required
+from django.contrib.auth.models import User
+from mailng import admin
+from mailng.admin.models import Domain, Mailbox, Alias
+from forms import MailboxForm, DomainForm, AliasForm
+
+def _ctx_ok(url):
+ return {"status" : "ok", "url" : url}
+
+def _ctx_ko(tpl, ctx):
+ return {"status" : "ko", "content" : render_to_string(tpl, ctx)}
+
+def _render(request, tpl, user_context):
+ return render_to_response(tpl, user_context,
+ context_instance=RequestContext(request))
+
+@login_required
+def domains(request):
+ domains = Domain.objects.all()
+ counters = {}
+ for dom in domains:
+ dom.mboxcounter = len(dom.mailbox_set.all())
+ return _render(request, 'admin/domains.html', {
+ "domains" : domains, "counters" : counters
+ })
+
+@login_required
+def newdomain(request):
+ if request.method == "POST":
+ form = DomainForm(request.POST)
+ error = None
+ if form.is_valid():
+ if Domain.objects.filter(name=request.POST["name"]):
+ error = "Domain with this name already defined"
+ else:
+ form.save()
+ ctx = _ctx_ok(reverse(admin.views.domains))
+ return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
+ ctx = _ctx_ko("admin/newdomain.html", {
+ "form" : form, "error" : error
+ })
+ return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
+
+ form = DomainForm()
+ return render_to_response('admin/newdomain.html', {
+ "form" : form
+ })
+@login_required
+def editdomain(request, dom_id):
+ domain = Domain.objects.get(pk=dom_id)
+ if request.method == "POST":
+ form = DomainForm(request.POST, instance=domain)
+ if form.is_valid():
+ form.save()
+ ctx = _ctx_ok(reverse(admin.views.domains))
+ return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
+ ctx = _ctx_ko("admin/editdomain.html", {
+ "domain" : domain, "form" : form
+ })
+ return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
+
+ form = DomainForm(instance=domain)
+ return render_to_response('admin/editdomain.html', {
+ "domain" : domain, "form" : form
+ })
+
+@login_required
+def deldomain(request, dom_id):
+ domain = Domain.objects.get(pk=dom_id)
+ domain.delete()
+ return HttpResponseRedirect('/mailng/admin/')
+
+@login_required
+def mailboxes(request, dom_id=None):
+ domain = Domain.objects.get(pk=dom_id)
+ mailboxes = Mailbox.objects.filter(domain=dom_id)
+ return _render(request, 'admin/mailboxes.html', {
+ "mailboxes" : mailboxes, "domain" : domain
+ })
+
+@login_required
+def newmailbox(request, dom_id=None):
+ domain = Domain.objects.get(pk=dom_id)
+ if request.method == "POST":
+ form = MailboxForm(request.POST)
+ error = None
+ if form.is_valid():
+ if Mailbox.objects.filter(address=request.POST["address"],
+ domain=dom_id):
+ error = "Mailbox with this address already exists"
+ else:
+ mb = form.save(commit=False)
+ user = User()
+ user.username = user.email = "%s@%s" % (mb.address, domain.name)
+ user.set_password(request.POST["password1"])
+ user.is_active = request.POST.has_key("enabled") \
+ and True or False
+ fname, lname = mb.name.split()
+ user.first_name = fname
+
+ user.last_name = lname
+ user.save()
+ mb.user = user
+
+ mb.uid = mb.gid = 500
+ mb.domain = domain
+ mb.save()
+ ctx = _ctx_ok(reverse(admin.views.mailboxes, args=[domain.id]))
+ return HttpResponse(simplejson.dumps(ctx),
+ mimetype="application/json")
+ ctx = _ctx_ko("admin/newmailbox.html", {
+ "form" : form, "domain" : domain, "error" : error
+ })
+ return HttpResponse(simplejson.dumps(ctx), mimetype="application/json")
+
+ form = MailboxForm()
+ return render_to_response('admin/newmailbox.html', {
+ "domain" : domain, "form" : form
+ })
+
+@login_required
+def editmailbox(request, dom_id, mbox_id=None):
+ mb = Mailbox.objects.get(pk=mbox_id)
+ if request.method == "POST":
+ form = MailboxForm(request.POST, instance=mb)
+ if form.is_valid():
+ if request.POST["enabled"] != mb.user.is_active \
+ or request.POST["password1"] != mb.user.password:
+ mb.user.is_active = request.POST["enabled"]
+ mb.user.password = request.POST["password1"]
+ mb.user.save()
+ form.save()
+ ctx = _ctx_ok(reverse(admin.views.mailboxes, args=[dom_id]))
+ else:
+ ctx = _ctx_ko("admin/editmailbox.html", {
+ "mbox" : mb, "form" : form
+ })
+ json = simplejson.dumps(ctx)
+ return HttpResponse(json, mimetype="application/json")
+
+ form = MailboxForm(instance=mb)
+ form.fields['enabled'].initial = mb.user.is_active
+ form.fields['password1'].initial = mb.user.password
+ form.fields['password2'].initial = mb.user.password
+ return _render(request, 'admin/editmailbox.html', {
+ "form" : form, "mbox" : mb
+ })
+
+@login_required
+def delmailbox(request, dom_id, mbox_id=None):
+ mb = Mailbox.objects.get(pk=mbox_id)
+ mb.delete()
+ return HttpResponseRedirect(reverse(admin.views.mailboxes,
+ args=[dom_id]))
+
+@login_required
+def aliases(request, dom_id=None, mbox_id=None):
+ domain = Domain.objects.get(pk=dom_id)
+ if not mbox_id:
+ mboxes = Mailbox.objects.filter(domain=dom_id)
+ aliases = []
+ for mb in mboxes:
+ aliases += Alias.objects.filter(mbox=mb.id)
+ else:
+ aliases = Alias.objects.filter(mbox=mbox_id)
+ return _render(request, 'admin/aliases.html', {
+ "aliases" : aliases, "domain" : domain
+ })
+
+@login_required
+def newalias(request, dom_id, mbox_id):
+ mbox = Mailbox.objects.get(pk=mbox_id)
+ if request.method == "POST":
+ form = AliasForm(request.POST)
+ if form.is_valid():
+ form.save()
+ ctx = _ctx_ok(reverse(admin.views.aliases, args=[mbox.domain.id]))
+ else:
+ ctx = _ctx_ko("admin/newalias.html", {
+ "mbox" : mbox, "form" : form
+ })
+ json = simplejson.dumps(ctx)
+ return HttpResponse(json, mimetype="application/json")
+
+ form = AliasForm({"mbox" : mbox_id}, domain=mbox.domain)
+ return render_to_response('admin/newalias.html', {
+ "mbox" : mbox, "form" : form, "noerrors" : True
+ })
+
+@login_required
+def editalias(request, dom_id, alias_id):
+ alias = Alias.objects.get(pk=alias_id)
+ if request.method == "POST":
+ form = AliasForm(request.POST, instance=alias)
+ if form.is_valid():
+ form.save()
+ ctx = _ctx_ok(reverse(admin.views.aliases, args=[dom_id]))
+ else:
+ ctx = _ctx_ko("admin/editalias.html", {
+ "alias" : alias, "form" : form
+ })
+ json = simplejson.dumps(ctx)
+ return HttpResponse(json, mimetype="application/json")
+ form = AliasForm(instance=alias)
+ return _render(request, 'admin/editalias.html', {
+ "form" : form, "alias" : alias
+ })
+
+@login_required
+def delalias(request, dom_id, alias_id):
+ alias = Alias.objects.get(pk=alias_id)
+ alias.delete()
+ return HttpResponseRedirect(reverse(admin.views.aliases,
+ args=[dom_id]))
+
diff --git a/manage.py b/manage.py
new file mode 100755
index 000000000..5e78ea979
--- /dev/null
+++ b/manage.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python
+from django.core.management import execute_manager
+try:
+ import settings # Assumed to be in the same directory.
+except ImportError:
+ import sys
+ sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
+ sys.exit(1)
+
+if __name__ == "__main__":
+ execute_manager(settings)
diff --git a/settings_sample.py b/settings_sample.py
new file mode 100644
index 000000000..049d462dd
--- /dev/null
+++ b/settings_sample.py
@@ -0,0 +1,82 @@
+# Django settings for mailng project.
+
+DEBUG = False
+TEMPLATE_DEBUG = DEBUG
+
+ADMINS = (
+ # ('Your Name', 'your_email@domain.com'),
+)
+
+MANAGERS = ADMINS
+
+DATABASE_ENGINE = '' # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
+DATABASE_NAME = 'mailng' # Or path to database file if using sqlite3.
+DATABASE_USER = '' # Not used with sqlite3.
+DATABASE_PASSWORD = '' # Not used with sqlite3.
+DATABASE_HOST = '' # Set to empty string for localhost. Not used with sqlite3.
+DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3.
+
+# Local time zone for this installation. Choices can be found here:
+# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
+# although not all choices may be available on all operating systems.
+# If running in a Windows environment this must be set to the same as your
+# system time zone.
+TIME_ZONE = 'America/Chicago'
+
+# Language code for this installation. All choices can be found here:
+# http://www.i18nguy.com/unicode/language-identifiers.html
+LANGUAGE_CODE = 'en-us'
+
+SITE_ID = 1
+
+# If you set this to False, Django will make some optimizations so as not
+# to load the internationalization machinery.
+USE_I18N = True
+
+LOGIN_REDIRECT_URL = '/mailng/admin/'
+
+# Absolute path to the directory that holds media.
+# Example: "/home/media/media.lawrence.com/"
+MEDIA_ROOT = ''
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash if there is a path component (optional in other cases).
+# Examples: "http://media.lawrence.com", "http://example.com/media/"
+MEDIA_URL = ''
+
+# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
+# trailing slash.
+# Examples: "http://foo.com/media/", "/media/".
+ADMIN_MEDIA_PREFIX = '/media/'
+
+# Make this unique, and don't share it with anybody.
+SECRET_KEY = '2)e+la#y$1b&v!p)s^&4lwpc92o59fye)mj^3^7$jytmvx379d'
+
+# List of callables that know how to import templates from various sources.
+TEMPLATE_LOADERS = (
+ 'django.template.loaders.filesystem.load_template_source',
+ 'django.template.loaders.app_directories.load_template_source',
+# 'django.template.loaders.eggs.load_template_source',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.middleware.common.CommonMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+)
+
+ROOT_URLCONF = 'mailng.urls'
+
+TEMPLATE_DIRS = (
+ # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
+ # Always use forward slashes, even on Windows.
+ # Don't forget to use absolute paths, not relative paths.
+)
+
+INSTALLED_APPS = (
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.sites',
+ 'mailng.admin'
+)
diff --git a/static/js/SqueezeBox.js b/static/js/SqueezeBox.js
new file mode 100644
index 000000000..980ba8778
--- /dev/null
+++ b/static/js/SqueezeBox.js
@@ -0,0 +1,384 @@
+/**
+ * SqueezeBox - Expandable Lightbox
+ *
+ * Allows to open various content as modal,
+ * centered and animated box.
+ *
+ * Dependencies: MooTools 1.2 trunk (04/2008)
+ *
+ * Inspired by
+ * ... Lokesh Dhakar - The original Lightbox v2
+ *
+ * @version 1.1 rc2
+ *
+ * @license MIT-style license
+ * @author Harald Kirschner
+ * @copyright Author
+ */
+var SqueezeBox = {
+
+ presets: {
+ size: {x: 600, y: 450},
+ sizeLoading: {x: 200, y: 150},
+ marginInner: {x: 20, y: 20},
+ marginImage: {x: 50, y: 75},
+ handler: false,
+ target: null,
+ closable: true,
+ closeBtn: true,
+ zIndex: 65555,
+ overlayOpacity: 0.7,
+ classWindow: '',
+ classOverlay: '',
+ overlayFx: {},
+ resizeFx: {},
+ contentFx: {},
+ parse: false, // 'rel'
+ parseSecure: false,
+ ajaxOptions: {},
+ onOpen: $empty,
+ onClose: $empty,
+ onUpdate: $empty,
+ onResize: $empty,
+ onMove: $empty,
+ onShow: $empty,
+ onHide: $empty
+ },
+
+ initialize: function(presets) {
+ if (this.options) return this;
+ this.presets = $merge(this.presets, presets);
+ this.options = {};
+ this.setOptions(this.presets).build();
+ this.bound = {
+ window: this.reposition.bind(this, [null]),
+ scroll: this.checkTarget.bind(this),
+ close: this.close.bind(this),
+ key: this.onKey.bind(this)
+ };
+ this.isOpen = this.isLoading = false;
+ return this;
+ },
+
+ build: function() {
+ this.overlay = new Element('div', {
+ id: 'sbox-overlay',
+ styles: {display: 'none', zIndex: this.options.zIndex}
+ });
+ this.content = new Element('div', {id: 'sbox-content'});
+ this.closeBtn = new Element('a', {id: 'sbox-btn-close', href: '#'});
+ this.win = new Element('div', {
+ id: 'sbox-window',
+ styles: {display: 'none', zIndex: this.options.zIndex + 2}
+ }).adopt(this.closeBtn, this.content);
+ this.fx = {
+ overlay: new Fx.Tween(this.overlay, $merge({
+ property: 'opacity',
+ onStart: Events.prototype.clearChain,
+ duration: 250,
+ link: 'cancel'
+ }, this.options.overlayFx)).set(0),
+ win: new Fx.Morph(this.win, $merge({
+ onStart: Events.prototype.clearChain,
+ unit: 'px',
+ duration: 750,
+ transition: Fx.Transitions.Quint.easeOut,
+ link: 'cancel',
+ unit: 'px'
+ }, this.options.resizeFx)),
+ content: new Fx.Tween(this.content, $merge({
+ property: 'opacity',
+ duration: 250,
+ link: 'cancel'
+ }, this.options.contentFx)).set(0)
+ };
+ $(document.body).adopt(this.overlay, this.win);
+ },
+
+ assign: function(to, options) {
+ return to.addEvent('click', function() {
+ return !SqueezeBox.fromElement(this, options);
+ });
+ },
+
+ fromElement: function(from, options) {
+ this.initialize();
+ if (this.element) this.trash();
+ this.element = $(from);
+ this.setOptions($merge(this.presets, options || {}));
+ if (this.element && this.options.parse) {
+ var obj = this.element.getProperty(this.options.parse);
+ if (obj && (obj = JSON.decode(obj, this.options.parseSecure))) this.setOptions(obj);
+ }
+ this.assignOptions();
+ this.url = ((this.element) ? (this.options.url || this.element.get('href')) : from) || '';
+ var handler = this.options.handler;
+ if (handler) return this.setContent(handler, this.parsers[handler].call(this, true));
+ var ret = false;
+ this.parsers.some(function(parser, key) {
+ var content = parser.call(this);
+ if (content) {
+ ret = this.setContent(key, content);
+ return true;
+ }
+ return false;
+ }, this);
+ return ret;
+ },
+
+ assignOptions: function() {
+ this.overlay.set('class', this.options.classOverlay);
+ this.win.set('class', this.options.classWindow);
+ if (Browser.Engine.trident4) this.win.addClass('sbox-window-ie6');
+ },
+
+ close: function(e) {
+ var stoppable = ($type(e) == 'event');
+ if (stoppable) e.stop();
+ if (!this.isOpen || (stoppable && !$lambda(this.options.closable).call(this, e))) return this;
+ this.fx.overlay.start(0).chain(this.toggleOverlay.bind(this));
+ this.win.setStyle('display', 'none');
+ this.toggleListeners();
+ this.isOpen = false;
+ this.fireEvent('onClose', [this.content]);
+ this.trash();
+ return this;
+ },
+
+ trash: function() {
+ this.element = this.asset = null;
+ this.options = {};
+ this.removeEvents().setOptions(this.presets).callChain();
+ },
+
+ onError: function() {
+ this.asset = null;
+ this.setContent('string', 'Error during loading');
+ },
+
+ setContent: function(handler, content) {
+ if (!this.handlers[handler]) return false;
+ this.content.className = 'sbox-content-' + handler;
+ this.applyTimer = this.applyContent.delay(this.fx.overlay.options.duration, this, this.handlers[handler].call(this, content));
+ if (this.overlay.retrieve('opacity')) return this;
+ this.toggleOverlay(true);
+ this.fx.overlay.start(this.options.overlayOpacity);
+ return this.reposition();
+ },
+
+ applyContent: function(content, size) {
+ this.applyTimer = $clear(this.applyTimer);
+ this.hideContent();
+ if (!content) {
+ this.toggleLoading(true);
+ } else {
+ if (this.isLoading) this.toggleLoading(false);
+ this.fireEvent('onUpdate', [this.content], 20);
+ }
+ this.content.empty();
+ if (['string', 'array', false].contains($type(content))) this.content.set('html', content || '');
+ else this.content.adopt(content);
+ this.callChain();
+ if (!this.isOpen) {
+ this.toggleListeners(true);
+ this.resize(size, true);
+ this.isOpen = true;
+ this.fireEvent('onOpen', [this.content]);
+ } else {
+ this.resize(size);
+ }
+ },
+
+ resize: function(size, instantly) {
+ var box = document.getSize(), scroll = document.getScroll();
+ this.size = $merge((this.isLoading) ? this.options.sizeLoading : this.options.size, size);
+ var to = {
+ width: this.size.x,
+ height: this.size.y,
+ left: (scroll.x + (box.x - this.size.x - this.options.marginInner.x) / 2).toInt(),
+ top: (scroll.y + (box.y - this.size.y - this.options.marginInner.y) / 2).toInt()
+ };
+ $clear(this.showTimer || null);
+ this.hideContent();
+ if (!instantly) {
+ this.fx.win.start(to).chain(this.showContent.bind(this));
+ } else {
+ this.win.setStyles(to).setStyle('display', '');
+ this.showTimer = this.showContent.delay(50, this);
+ }
+ return this.reposition();
+ },
+
+ toggleListeners: function(state) {
+ var fn = (state) ? 'addEvent' : 'removeEvent';
+ this.closeBtn[fn]('click', this.bound.close);
+ this.overlay[fn]('click', this.bound.close);
+ document[fn]('keydown', this.bound.key)[fn]('mousewheel', this.bound.scroll);
+ window[fn]('resize', this.bound.window)[fn]('scroll', this.bound.window);
+ },
+
+ toggleLoading: function(state) {
+ this.isLoading = state;
+ this.win[(state) ? 'addClass' : 'removeClass']('sbox-loading');
+ if (state) this.fireEvent('onLoading', [this.win]);
+ },
+
+ toggleOverlay: function(state) {
+ this.overlay.setStyle('display', (state) ? '' : 'none');
+ $(document.body)[(state) ? 'addClass' : 'removeClass']('body-overlayed');
+ },
+
+ showContent: function() {
+ if (this.content.get('opacity')) this.fireEvent('onShow', [this.win]);
+ this.fx.content.start(1);
+ },
+
+ hideContent: function() {
+ if (!this.content.get('opacity')) this.fireEvent('onHide', [this.win]);
+ this.fx.content.set(0);
+ },
+
+ onKey: function(e) {
+ switch (e.key) {
+ case 'esc': this.close(e);
+ case 'up': case 'down': return false;
+ }
+ },
+
+ checkTarget: function(e) {
+ return this.content.hasChild(e.target);
+ },
+
+ reposition: function() {
+ var size = document.getSize(), scroll = document.getScroll();
+ this.overlay.setStyles({
+ left: scroll.x + 'px',
+ top: scroll.y + 'px',
+ width: size.x + 'px',
+ height: size.y + 'px'
+ });
+ this.win.setStyles({
+ left: (scroll.x + (size.x - this.win.offsetWidth) / 2).toInt() + 'px',
+ top: (scroll.y + (size.y - this.win.offsetHeight) / 2).toInt() + 'px'
+ });
+ return this.fireEvent('onMove', [this.overlay, this.win]);
+ },
+
+ removeEvents: function(type){
+ if (!this.$events) return this;
+ if (!type) this.$events = null;
+ else if (this.$events[type]) this.$events[type] = null;
+ return this;
+ },
+
+ extend: function(properties) {
+ return $extend(this, properties);
+ },
+
+ handlers: new Hash(),
+
+ parsers: new Hash()
+
+};
+
+SqueezeBox.extend(new Events($empty)).extend(new Options($empty)).extend(new Chain($empty));
+
+SqueezeBox.parsers.extend({
+
+ image: function(preset) {
+ return (preset || (/\.(?:jpg|png|gif)$/i).test(this.url)) ? this.url : false;
+ },
+
+ clone: function(preset) {
+ if ($(this.options.target)) return $(this.options.target);
+ if (this.element && !this.element.parentNode) return this.element;
+ var bits = this.url.match(/#([\w-]+)$/);
+ return (bits) ? $(bits[1]) : (preset ? this.element : false);
+ },
+
+ ajax: function(preset) {
+ return (preset || (this.url && !(/^(?:javascript|#)/i).test(this.url))) ? this.url : false;
+ },
+
+ iframe: function(preset) {
+ return (preset || this.url) ? this.url : false;
+ },
+
+ string: function(preset) {
+ return true;
+ }
+});
+
+SqueezeBox.handlers.extend({
+
+ image: function(url) {
+ var size, tmp = new Image();
+ this.asset = null;
+ tmp.onload = tmp.onabort = tmp.onerror = (function() {
+ tmp.onload = tmp.onabort = tmp.onerror = null;
+ if (!tmp.width) {
+ this.onError.delay(10, this);
+ return;
+ }
+ var box = document.getSize();
+ box.x -= this.options.marginImage.x;
+ box.y -= this.options.marginImage.y;
+ size = {x: tmp.width, y: tmp.height};
+ for (var i = 2; i--;) {
+ if (size.x > box.x) {
+ size.y *= box.x / size.x;
+ size.x = box.x;
+ } else if (size.y > box.y) {
+ size.x *= box.y / size.y;
+ size.y = box.y;
+ }
+ }
+ size.x = size.x.toInt();
+ size.y = size.y.toInt();
+ this.asset = $(tmp);
+ tmp = null;
+ this.asset.setProperties({width: size.x, height: size.y});
+ if (this.isOpen) this.applyContent(this.asset, size);
+ }).bind(this);
+ tmp.src = url;
+ if (tmp && tmp.onload && tmp.complete) tmp.onload();
+ return (this.asset) ? [this.asset, size] : null;
+ },
+
+ clone: function(el) {
+ return el.clone();
+ },
+
+ adopt: $arguments(0),
+
+ ajax: function(url) {
+ this.asset = new Request.HTML($merge({
+ method: 'get'
+ }, this.options.ajaxOptions)).addEvents({
+ onSuccess: function(resp) {
+ this.applyContent(resp);
+ this.asset = null;
+ }.bind(this),
+ onFailure: this.onError.bind(this)
+ });
+ this.asset.send.delay(10, this.asset, [{url: url}]);
+ },
+
+ iframe: function(url) {
+ return new Element('iframe', $merge({
+ src: url,
+ frameBorder: 0,
+ width: this.options.size.x,
+ height: this.options.size.y
+ }, this.options.iframeOptions));
+ },
+
+ string: function(str) {
+ return str;
+ }
+
+});
+
+SqueezeBox.handlers.url = SqueezeBox.handlers.ajax;
+SqueezeBox.parsers.url = SqueezeBox.parsers.ajax;
+SqueezeBox.parsers.adopt = SqueezeBox.parsers.clone;
\ No newline at end of file
diff --git a/static/js/domains.js b/static/js/domains.js
new file mode 100644
index 000000000..b0f603c53
--- /dev/null
+++ b/static/js/domains.js
@@ -0,0 +1,92 @@
+
+window.addEvent('domready', function() {
+ var handlers = new Array();
+
+// handlers['loadDom'] = function(obj, target) {
+/* $$('a[name=loadDom]').setStyles({'color' : '#999'});*/
+/* obj.setStyles({"color": "black"});*/
+// loadPage(target, "content", null);
+// };
+
+// handlers['loadContent'] = function(obj, target) {
+// loadPage(target, "content", null);
+// };
+
+ if ($$("a[name=deleteDom]"))
+ $$("a[name=deleteDom]").addEvent('click', function(event) {
+ if (!confirm("Delete this domain?"))
+ event.stop();
+ });
+
+ if ($$("a[name=deleteMb]"))
+ $$("a[name=deleteMb]").addEvent('click', function(event) {
+ if (!confirm("Delete this mailbox?"))
+ event.stop();
+ });
+
+ if ($$("a[name=deleteAlias]"))
+ $$("a[name=deleteAlias]").addEvent('click', function(event) {
+ if (!confirm("Delete this alias?"))
+ event.stop();
+ });
+
+ handlers['deleteDom'] = function(obj, target) {
+ return confirm("Supprimer ce domaine?");
+ }
+
+ $('body').addEvent('click', function(event) {
+ target = event.target;
+ if (target.get('tag') == 'a') {
+ name = target.get('name');
+ if (name == 'undefined')
+ return;
+ if (typeof(handlers[name]) != 'undefined') {
+ if (!handlers[name](target, target.get('href'))) {
+ new Event(event).stop();
+ }
+ }
+ }
+ });
+
+ $('body').addEvent('submit', function(event) {
+ alert("newodm submit");
+ target = event.target;
+ if (target.get('tag') == 'form') {
+ name = target.get('name');
+ if (name == 'undefined')
+ return;
+ if (typeof(handlers[name]) != 'undefined') {
+ new Event(event).stop();
+ handlers[name](target);
+ }
+ }
+ });
+
+ SqueezeBox.assign($$('a.boxed'), {
+ //size: {x:300, y:180}
+ parse: 'rel',
+
+ });
+});
+
+newdom = function() {
+ alert("pouet");
+ form = $("form[name=newdom]");
+ var params = obj.toQueryString();
+ form.set('send', {
+ onSuccess: function(response) {
+ SqueezeBox.loadModal(obj.action + '?' + params);
+ }
+ }).send();
+};
+
+loadPage = function(url, destination, callback) {
+ var destination = (destination == null) ? "page" : destination;
+
+ if (callback)
+ new Request({url: url, method: "get", onSuccess: callback}).send();
+ else
+ new Request({url: url, method: "get", onSuccess: function(data) {
+ $(destination).set('html', data);
+ }}).send();
+};
\ No newline at end of file
diff --git a/static/js/form.js b/static/js/form.js
new file mode 100644
index 000000000..2d9f2b43b
--- /dev/null
+++ b/static/js/form.js
@@ -0,0 +1,40 @@
+window.addEvent("domready", function() {
+ sqbsubmit = function(obj, dest, id, callback) {
+ obj.set('send', {
+ onSuccess: function(responseText) {
+ response = JSON.decode(responseText);
+ if (response.status == "ko") {
+ $(dest).set("html", response.content);
+ $(id).addEvent("submit", callback);
+ } else {
+ parent.SqueezeBox.close();
+ parent.location.href = response.url;
+ }
+ }
+ });
+ obj.send();
+ }
+
+ generic_submit = function(obj, event, id, callback) {
+ event.stop();
+ sqbsubmit(obj, "content", id, callback);
+ }
+ domain_submit = function(event) {
+ generic_submit(this, event, "domform", domain_submit);
+ }
+ mailbox_submit = function(event) {
+ generic_submit(this, event, "mboxform", mailbox_submit);
+ }
+ alias_submit = function(event) {
+ generic_submit(this, event, "aliasform", alias_submit);
+ }
+
+ if ($("domform"))
+ $("domform").addEvent("submit", domain_submit);
+
+ if ($("mboxform"))
+ $("mboxform").addEvent("submit", mailbox_submit);
+
+ if ($("aliasform"))
+ $("aliasform").addEvent("submit", alias_submit);
+});
diff --git a/static/js/mootools.js b/static/js/mootools.js
new file mode 100644
index 000000000..7e1e482bf
--- /dev/null
+++ b/static/js/mootools.js
@@ -0,0 +1,3946 @@
+/*
+Script: Core.js
+ MooTools - My Object Oriented JavaScript Tools.
+
+License:
+ MIT-style license.
+
+Copyright:
+ Copyright (c) 2006-2008 [Valerio Proietti](http://mad4milk.net/).
+
+Code & Documentation:
+ [The MooTools production team](http://mootools.net/developers/).
+
+Inspiration:
+ - Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)
+ - Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php)
+*/
+
+var MooTools = {
+ 'version': '1.2.1',
+ 'build': '0d4845aab3d9a4fdee2f0d4a6dd59210e4b697cf'
+};
+
+var Native = function(options){
+ options = options || {};
+ var name = options.name;
+ var legacy = options.legacy;
+ var protect = options.protect;
+ var methods = options.implement;
+ var generics = options.generics;
+ var initialize = options.initialize;
+ var afterImplement = options.afterImplement || function(){};
+ var object = initialize || legacy;
+ generics = generics !== false;
+
+ object.constructor = Native;
+ object.$family = {name: 'native'};
+ if (legacy && initialize) object.prototype = legacy.prototype;
+ object.prototype.constructor = object;
+
+ if (name){
+ var family = name.toLowerCase();
+ object.prototype.$family = {name: family};
+ Native.typize(object, family);
+ }
+
+ var add = function(obj, name, method, force){
+ if (!protect || force || !obj.prototype[name]) obj.prototype[name] = method;
+ if (generics) Native.genericize(obj, name, protect);
+ afterImplement.call(obj, name, method);
+ return obj;
+ };
+
+ object.alias = function(a1, a2, a3){
+ if (typeof a1 == 'string'){
+ if ((a1 = this.prototype[a1])) return add(this, a2, a1, a3);
+ }
+ for (var a in a1) this.alias(a, a1[a], a2);
+ return this;
+ };
+
+ object.implement = function(a1, a2, a3){
+ if (typeof a1 == 'string') return add(this, a1, a2, a3);
+ for (var p in a1) add(this, p, a1[p], a2);
+ return this;
+ };
+
+ if (methods) object.implement(methods);
+
+ return object;
+};
+
+Native.genericize = function(object, property, check){
+ if ((!check || !object[property]) && typeof object.prototype[property] == 'function') object[property] = function(){
+ var args = Array.prototype.slice.call(arguments);
+ return object.prototype[property].apply(args.shift(), args);
+ };
+};
+
+Native.implement = function(objects, properties){
+ for (var i = 0, l = objects.length; i < l; i++) objects[i].implement(properties);
+};
+
+Native.typize = function(object, family){
+ if (!object.type) object.type = function(item){
+ return ($type(item) === family);
+ };
+};
+
+(function(){
+ var natives = {'Array': Array, 'Date': Date, 'Function': Function, 'Number': Number, 'RegExp': RegExp, 'String': String};
+ for (var n in natives) new Native({name: n, initialize: natives[n], protect: true});
+
+ var types = {'boolean': Boolean, 'native': Native, 'object': Object};
+ for (var t in types) Native.typize(types[t], t);
+
+ var generics = {
+ 'Array': ["concat", "indexOf", "join", "lastIndexOf", "pop", "push", "reverse", "shift", "slice", "sort", "splice", "toString", "unshift", "valueOf"],
+ 'String': ["charAt", "charCodeAt", "concat", "indexOf", "lastIndexOf", "match", "replace", "search", "slice", "split", "substr", "substring", "toLowerCase", "toUpperCase", "valueOf"]
+ };
+ for (var g in generics){
+ for (var i = generics[g].length; i--;) Native.genericize(window[g], generics[g][i], true);
+ };
+})();
+
+var Hash = new Native({
+
+ name: 'Hash',
+
+ initialize: function(object){
+ if ($type(object) == 'hash') object = $unlink(object.getClean());
+ for (var key in object) this[key] = object[key];
+ return this;
+ }
+
+});
+
+Hash.implement({
+
+ forEach: function(fn, bind){
+ for (var key in this){
+ if (this.hasOwnProperty(key)) fn.call(bind, this[key], key, this);
+ }
+ },
+
+ getClean: function(){
+ var clean = {};
+ for (var key in this){
+ if (this.hasOwnProperty(key)) clean[key] = this[key];
+ }
+ return clean;
+ },
+
+ getLength: function(){
+ var length = 0;
+ for (var key in this){
+ if (this.hasOwnProperty(key)) length++;
+ }
+ return length;
+ }
+
+});
+
+Hash.alias('forEach', 'each');
+
+Array.implement({
+
+ forEach: function(fn, bind){
+ for (var i = 0, l = this.length; i < l; i++) fn.call(bind, this[i], i, this);
+ }
+
+});
+
+Array.alias('forEach', 'each');
+
+function $A(iterable){
+ if (iterable.item){
+ var array = [];
+ for (var i = 0, l = iterable.length; i < l; i++) array[i] = iterable[i];
+ return array;
+ }
+ return Array.prototype.slice.call(iterable);
+};
+
+function $arguments(i){
+ return function(){
+ return arguments[i];
+ };
+};
+
+function $chk(obj){
+ return !!(obj || obj === 0);
+};
+
+function $clear(timer){
+ clearTimeout(timer);
+ clearInterval(timer);
+ return null;
+};
+
+function $defined(obj){
+ return (obj != undefined);
+};
+
+function $each(iterable, fn, bind){
+ var type = $type(iterable);
+ ((type == 'arguments' || type == 'collection' || type == 'array') ? Array : Hash).each(iterable, fn, bind);
+};
+
+function $empty(){};
+
+function $extend(original, extended){
+ for (var key in (extended || {})) original[key] = extended[key];
+ return original;
+};
+
+function $H(object){
+ return new Hash(object);
+};
+
+function $lambda(value){
+ return (typeof value == 'function') ? value : function(){
+ return value;
+ };
+};
+
+function $merge(){
+ var mix = {};
+ for (var i = 0, l = arguments.length; i < l; i++){
+ var object = arguments[i];
+ if ($type(object) != 'object') continue;
+ for (var key in object){
+ var op = object[key], mp = mix[key];
+ mix[key] = (mp && $type(op) == 'object' && $type(mp) == 'object') ? $merge(mp, op) : $unlink(op);
+ }
+ }
+ return mix;
+};
+
+function $pick(){
+ for (var i = 0, l = arguments.length; i < l; i++){
+ if (arguments[i] != undefined) return arguments[i];
+ }
+ return null;
+};
+
+function $random(min, max){
+ return Math.floor(Math.random() * (max - min + 1) + min);
+};
+
+function $splat(obj){
+ var type = $type(obj);
+ return (type) ? ((type != 'array' && type != 'arguments') ? [obj] : obj) : [];
+};
+
+var $time = Date.now || function(){
+ return +new Date;
+};
+
+function $try(){
+ for (var i = 0, l = arguments.length; i < l; i++){
+ try {
+ return arguments[i]();
+ } catch(e){}
+ }
+ return null;
+};
+
+function $type(obj){
+ if (obj == undefined) return false;
+ if (obj.$family) return (obj.$family.name == 'number' && !isFinite(obj)) ? false : obj.$family.name;
+ if (obj.nodeName){
+ switch (obj.nodeType){
+ case 1: return 'element';
+ case 3: return (/\S/).test(obj.nodeValue) ? 'textnode' : 'whitespace';
+ }
+ } else if (typeof obj.length == 'number'){
+ if (obj.callee) return 'arguments';
+ else if (obj.item) return 'collection';
+ }
+ return typeof obj;
+};
+
+function $unlink(object){
+ var unlinked;
+ switch ($type(object)){
+ case 'object':
+ unlinked = {};
+ for (var p in object) unlinked[p] = $unlink(object[p]);
+ break;
+ case 'hash':
+ unlinked = new Hash(object);
+ break;
+ case 'array':
+ unlinked = [];
+ for (var i = 0, l = object.length; i < l; i++) unlinked[i] = $unlink(object[i]);
+ break;
+ default: return object;
+ }
+ return unlinked;
+};
+
+
+/*
+Script: Browser.js
+ The Browser Core. Contains Browser initialization, Window and Document, and the Browser Hash.
+
+License:
+ MIT-style license.
+*/
+
+var Browser = $merge({
+
+ Engine: {name: 'unknown', version: 0},
+
+ Platform: {name: (window.orientation != undefined) ? 'ipod' : (navigator.platform.match(/mac|win|linux/i) || ['other'])[0].toLowerCase()},
+
+ Features: {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)},
+
+ Plugins: {},
+
+ Engines: {
+
+ presto: function(){
+ return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925));
+ },
+
+ trident: function(){
+ return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? 5 : 4);
+ },
+
+ webkit: function(){
+ return (navigator.taintEnabled) ? false : ((Browser.Features.xpath) ? ((Browser.Features.query) ? 525 : 420) : 419);
+ },
+
+ gecko: function(){
+ return (document.getBoxObjectFor == undefined) ? false : ((document.getElementsByClassName) ? 19 : 18);
+ }
+
+ }
+
+}, Browser || {});
+
+Browser.Platform[Browser.Platform.name] = true;
+
+Browser.detect = function(){
+
+ for (var engine in this.Engines){
+ var version = this.Engines[engine]();
+ if (version){
+ this.Engine = {name: engine, version: version};
+ this.Engine[engine] = this.Engine[engine + version] = true;
+ break;
+ }
+ }
+
+ return {name: engine, version: version};
+
+};
+
+Browser.detect();
+
+Browser.Request = function(){
+ return $try(function(){
+ return new XMLHttpRequest();
+ }, function(){
+ return new ActiveXObject('MSXML2.XMLHTTP');
+ });
+};
+
+Browser.Features.xhr = !!(Browser.Request());
+
+Browser.Plugins.Flash = (function(){
+ var version = ($try(function(){
+ return navigator.plugins['Shockwave Flash'].description;
+ }, function(){
+ return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
+ }) || '0 r0').match(/\d+/g);
+ return {version: parseInt(version[0] || 0 + '.' + version[1] || 0), build: parseInt(version[2] || 0)};
+})();
+
+function $exec(text){
+ if (!text) return text;
+ if (window.execScript){
+ window.execScript(text);
+ } else {
+ var script = document.createElement('script');
+ script.setAttribute('type', 'text/javascript');
+ script[(Browser.Engine.webkit && Browser.Engine.version < 420) ? 'innerText' : 'text'] = text;
+ document.head.appendChild(script);
+ document.head.removeChild(script);
+ }
+ return text;
+};
+
+Native.UID = 1;
+
+var $uid = (Browser.Engine.trident) ? function(item){
+ return (item.uid || (item.uid = [Native.UID++]))[0];
+} : function(item){
+ return item.uid || (item.uid = Native.UID++);
+};
+
+var Window = new Native({
+
+ name: 'Window',
+
+ legacy: (Browser.Engine.trident) ? null: window.Window,
+
+ initialize: function(win){
+ $uid(win);
+ if (!win.Element){
+ win.Element = $empty;
+ if (Browser.Engine.webkit) win.document.createElement("iframe"); //fixes safari 2
+ win.Element.prototype = (Browser.Engine.webkit) ? window["[[DOMElement.prototype]]"] : {};
+ }
+ win.document.window = win;
+ return $extend(win, Window.Prototype);
+ },
+
+ afterImplement: function(property, value){
+ window[property] = Window.Prototype[property] = value;
+ }
+
+});
+
+Window.Prototype = {$family: {name: 'window'}};
+
+new Window(window);
+
+var Document = new Native({
+
+ name: 'Document',
+
+ legacy: (Browser.Engine.trident) ? null: window.Document,
+
+ initialize: function(doc){
+ $uid(doc);
+ doc.head = doc.getElementsByTagName('head')[0];
+ doc.html = doc.getElementsByTagName('html')[0];
+ if (Browser.Engine.trident && Browser.Engine.version <= 4) $try(function(){
+ doc.execCommand("BackgroundImageCache", false, true);
+ });
+ if (Browser.Engine.trident) doc.window.attachEvent('onunload', function() {
+ doc.window.detachEvent('onunload', arguments.callee);
+ doc.head = doc.html = doc.window = null;
+ });
+ return $extend(doc, Document.Prototype);
+ },
+
+ afterImplement: function(property, value){
+ document[property] = Document.Prototype[property] = value;
+ }
+
+});
+
+Document.Prototype = {$family: {name: 'document'}};
+
+new Document(document);
+
+
+/*
+Script: Array.js
+ Contains Array Prototypes like each, contains, and erase.
+
+License:
+ MIT-style license.
+*/
+
+Array.implement({
+
+ every: function(fn, bind){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (!fn.call(bind, this[i], i, this)) return false;
+ }
+ return true;
+ },
+
+ filter: function(fn, bind){
+ var results = [];
+ for (var i = 0, l = this.length; i < l; i++){
+ if (fn.call(bind, this[i], i, this)) results.push(this[i]);
+ }
+ return results;
+ },
+
+ clean: function() {
+ return this.filter($defined);
+ },
+
+ indexOf: function(item, from){
+ var len = this.length;
+ for (var i = (from < 0) ? Math.max(0, len + from) : from || 0; i < len; i++){
+ if (this[i] === item) return i;
+ }
+ return -1;
+ },
+
+ map: function(fn, bind){
+ var results = [];
+ for (var i = 0, l = this.length; i < l; i++) results[i] = fn.call(bind, this[i], i, this);
+ return results;
+ },
+
+ some: function(fn, bind){
+ for (var i = 0, l = this.length; i < l; i++){
+ if (fn.call(bind, this[i], i, this)) return true;
+ }
+ return false;
+ },
+
+ associate: function(keys){
+ var obj = {}, length = Math.min(this.length, keys.length);
+ for (var i = 0; i < length; i++) obj[keys[i]] = this[i];
+ return obj;
+ },
+
+ link: function(object){
+ var result = {};
+ for (var i = 0, l = this.length; i < l; i++){
+ for (var key in object){
+ if (object[key](this[i])){
+ result[key] = this[i];
+ delete object[key];
+ break;
+ }
+ }
+ }
+ return result;
+ },
+
+ contains: function(item, from){
+ return this.indexOf(item, from) != -1;
+ },
+
+ extend: function(array){
+ for (var i = 0, j = array.length; i < j; i++) this.push(array[i]);
+ return this;
+ },
+
+ getLast: function(){
+ return (this.length) ? this[this.length - 1] : null;
+ },
+
+ getRandom: function(){
+ return (this.length) ? this[$random(0, this.length - 1)] : null;
+ },
+
+ include: function(item){
+ if (!this.contains(item)) this.push(item);
+ return this;
+ },
+
+ combine: function(array){
+ for (var i = 0, l = array.length; i < l; i++) this.include(array[i]);
+ return this;
+ },
+
+ erase: function(item){
+ for (var i = this.length; i--; i){
+ if (this[i] === item) this.splice(i, 1);
+ }
+ return this;
+ },
+
+ empty: function(){
+ this.length = 0;
+ return this;
+ },
+
+ flatten: function(){
+ var array = [];
+ for (var i = 0, l = this.length; i < l; i++){
+ var type = $type(this[i]);
+ if (!type) continue;
+ array = array.concat((type == 'array' || type == 'collection' || type == 'arguments') ? Array.flatten(this[i]) : this[i]);
+ }
+ return array;
+ },
+
+ hexToRgb: function(array){
+ if (this.length != 3) return null;
+ var rgb = this.map(function(value){
+ if (value.length == 1) value += value;
+ return value.toInt(16);
+ });
+ return (array) ? rgb : 'rgb(' + rgb + ')';
+ },
+
+ rgbToHex: function(array){
+ if (this.length < 3) return null;
+ if (this.length == 4 && this[3] == 0 && !array) return 'transparent';
+ var hex = [];
+ for (var i = 0; i < 3; i++){
+ var bit = (this[i] - 0).toString(16);
+ hex.push((bit.length == 1) ? '0' + bit : bit);
+ }
+ return (array) ? hex : '#' + hex.join('');
+ }
+
+});
+
+
+/*
+Script: Function.js
+ Contains Function Prototypes like create, bind, pass, and delay.
+
+License:
+ MIT-style license.
+*/
+
+Function.implement({
+
+ extend: function(properties){
+ for (var property in properties) this[property] = properties[property];
+ return this;
+ },
+
+ create: function(options){
+ var self = this;
+ options = options || {};
+ return function(event){
+ var args = options.arguments;
+ args = (args != undefined) ? $splat(args) : Array.slice(arguments, (options.event) ? 1 : 0);
+ if (options.event) args = [event || window.event].extend(args);
+ var returns = function(){
+ return self.apply(options.bind || null, args);
+ };
+ if (options.delay) return setTimeout(returns, options.delay);
+ if (options.periodical) return setInterval(returns, options.periodical);
+ if (options.attempt) return $try(returns);
+ return returns();
+ };
+ },
+
+ run: function(args, bind){
+ return this.apply(bind, $splat(args));
+ },
+
+ pass: function(args, bind){
+ return this.create({bind: bind, arguments: args});
+ },
+
+ bind: function(bind, args){
+ return this.create({bind: bind, arguments: args});
+ },
+
+ bindWithEvent: function(bind, args){
+ return this.create({bind: bind, arguments: args, event: true});
+ },
+
+ attempt: function(args, bind){
+ return this.create({bind: bind, arguments: args, attempt: true})();
+ },
+
+ delay: function(delay, bind, args){
+ return this.create({bind: bind, arguments: args, delay: delay})();
+ },
+
+ periodical: function(periodical, bind, args){
+ return this.create({bind: bind, arguments: args, periodical: periodical})();
+ }
+
+});
+
+
+/*
+Script: Number.js
+ Contains Number Prototypes like limit, round, times, and ceil.
+
+License:
+ MIT-style license.
+*/
+
+Number.implement({
+
+ limit: function(min, max){
+ return Math.min(max, Math.max(min, this));
+ },
+
+ round: function(precision){
+ precision = Math.pow(10, precision || 0);
+ return Math.round(this * precision) / precision;
+ },
+
+ times: function(fn, bind){
+ for (var i = 0; i < this; i++) fn.call(bind, i, this);
+ },
+
+ toFloat: function(){
+ return parseFloat(this);
+ },
+
+ toInt: function(base){
+ return parseInt(this, base || 10);
+ }
+
+});
+
+Number.alias('times', 'each');
+
+(function(math){
+ var methods = {};
+ math.each(function(name){
+ if (!Number[name]) methods[name] = function(){
+ return Math[name].apply(null, [this].concat($A(arguments)));
+ };
+ });
+ Number.implement(methods);
+})(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']);
+
+
+/*
+Script: String.js
+ Contains String Prototypes like camelCase, capitalize, test, and toInt.
+
+License:
+ MIT-style license.
+*/
+
+String.implement({
+
+ test: function(regex, params){
+ return ((typeof regex == 'string') ? new RegExp(regex, params) : regex).test(this);
+ },
+
+ contains: function(string, separator){
+ return (separator) ? (separator + this + separator).indexOf(separator + string + separator) > -1 : this.indexOf(string) > -1;
+ },
+
+ trim: function(){
+ return this.replace(/^\s+|\s+$/g, '');
+ },
+
+ clean: function(){
+ return this.replace(/\s+/g, ' ').trim();
+ },
+
+ camelCase: function(){
+ return this.replace(/-\D/g, function(match){
+ return match.charAt(1).toUpperCase();
+ });
+ },
+
+ hyphenate: function(){
+ return this.replace(/[A-Z]/g, function(match){
+ return ('-' + match.charAt(0).toLowerCase());
+ });
+ },
+
+ capitalize: function(){
+ return this.replace(/\b[a-z]/g, function(match){
+ return match.toUpperCase();
+ });
+ },
+
+ escapeRegExp: function(){
+ return this.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
+ },
+
+ toInt: function(base){
+ return parseInt(this, base || 10);
+ },
+
+ toFloat: function(){
+ return parseFloat(this);
+ },
+
+ hexToRgb: function(array){
+ var hex = this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
+ return (hex) ? hex.slice(1).hexToRgb(array) : null;
+ },
+
+ rgbToHex: function(array){
+ var rgb = this.match(/\d{1,3}/g);
+ return (rgb) ? rgb.rgbToHex(array) : null;
+ },
+
+ stripScripts: function(option){
+ var scripts = '';
+ var text = this.replace(/
+{% endblock %}
+{% block mainframe %}
+
+
+
+
+
+{% block domcontent %}{% endblock %}
+
+{% endblock %}
diff --git a/templates/admin/domain_menu.html b/templates/admin/domain_menu.html
new file mode 100644
index 000000000..669d53394
--- /dev/null
+++ b/templates/admin/domain_menu.html
@@ -0,0 +1,27 @@
+{% url admin.views.editdomain domain_id as edit_url %}
+{% url admin.views.newmailbox domain_id as new_url %}
+{% url admin.views.mailboxes domain_id as mboxes_url %}
+{% url full-aliases domain_id as aliases_url %}
+
diff --git a/templates/admin/domains.html b/templates/admin/domains.html
new file mode 100644
index 000000000..e845ff9fe
--- /dev/null
+++ b/templates/admin/domains.html
@@ -0,0 +1,45 @@
+{% extends "admin/layout.html" %}
+{% block html_head %}
+
+{% endblock %}
+{% block mainframe %}
+
+
+
+ {% if not domains %}
+ No domain defined.
+ {% else %}
+
+
+ | Actions |
+ Name |
+ Mailboxes |
+ Quota |
+ Enabled |
+
+ {% for dom in domains %}
+
+
+ {% url admin.views.editdomain dom.id as edit_url %}
+
+
+
+ {% url admin.views.deldomain dom.id as del_url %}
+
+
+
+ |
+ {% url admin.views.mailboxes dom.id as domurl %}
+ {{ dom.name }} |
+ {{ dom.mboxcounter }} |
+ {{ dom.quota }} MB |
+ {{ dom.enabled|yesno }} |
+
+ {% endfor %}
+
+ {% endif %}
+
+{% endblock %}
diff --git a/templates/admin/editalias.html b/templates/admin/editalias.html
new file mode 100644
index 000000000..da5299440
--- /dev/null
+++ b/templates/admin/editalias.html
@@ -0,0 +1,15 @@
+{% extends "admin/layout_simple.html" %}
+{% block html_head %}
+
+{% endblock %}
+{% block content %}
+Alias {{ alias.address }}
+{% url admin.views.editalias alias.mbox.domain.id alias.id as edit_url %}
+
+{% endblock %}
diff --git a/templates/admin/editdomain.html b/templates/admin/editdomain.html
new file mode 100644
index 000000000..69ac115de
--- /dev/null
+++ b/templates/admin/editdomain.html
@@ -0,0 +1,16 @@
+{% extends "admin/layout_simple.html" %}
+{% block html_head %}
+
+{% endblock %}
+{% block content %}
+Domain {{ domain.name }}
+
+{% url admin.views.editdomain domain.id as dom_url %}
+
+{% endblock %}
diff --git a/templates/admin/editmailbox.html b/templates/admin/editmailbox.html
new file mode 100644
index 000000000..fea5ccc50
--- /dev/null
+++ b/templates/admin/editmailbox.html
@@ -0,0 +1,17 @@
+{% extends "admin/layout_simple.html" %}
+{% block html_head %}
+
+{% endblock %}
+{% block content %}
+Mailbox {{ mbox.name }}
+
+{% url admin.views.editmailbox mbox.domain_id mbox.id as box_url %}
+
+{% endblock %}
diff --git a/templates/admin/genericform.html b/templates/admin/genericform.html
new file mode 100644
index 000000000..728f1c91a
--- /dev/null
+++ b/templates/admin/genericform.html
@@ -0,0 +1,5 @@
+{% for field in form %}
+
+ {{ field.label_tag }} {{ field }} {% ifequal noerrors False %}{{ field.errors }}{% endifequal %}
+
+{% endfor %}
diff --git a/templates/admin/index.html b/templates/admin/index.html
new file mode 100644
index 000000000..eecfc9cd3
--- /dev/null
+++ b/templates/admin/index.html
@@ -0,0 +1,12 @@
+{% extends "admin/layout.html" %}
+
+{%block pagetitle %}Administration{%endblock %}
+{%block breadcrumb %}Domains{%endblock %}
+{% block html_head %}
+
+{% endblock %}
+
+
+{% block content %}
+{% include default %}
+{% endblock %}
diff --git a/templates/admin/layout.html b/templates/admin/layout.html
new file mode 100644
index 000000000..522ea9094
--- /dev/null
+++ b/templates/admin/layout.html
@@ -0,0 +1,48 @@
+{% extends "layout.html" %}
+{% block content %}
+{% url admin.views.domains as dom_url %}
+
+
+
+ {% if user.first_name %}
+
{{ user.first_name }} {{ user.last_name }}
+ {% else %}
+
{{ user.username }}
+ {% endif %}
+
+ -
+ {% url django.contrib.auth.views.logout_then_login as logout_url %}
+
+
Logout
+
+
+
+
+
+
+
+ {%block mainframe %}{% endblock %}
+
+
+{% endblock %}
diff --git a/templates/admin/layout_simple.html b/templates/admin/layout_simple.html
new file mode 100644
index 000000000..47431d742
--- /dev/null
+++ b/templates/admin/layout_simple.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+ {%block html_head %}{% endblock %}
+
+
+
+
+ {%block content %}{% endblock %}
+
+
+
diff --git a/templates/admin/mailbox.html b/templates/admin/mailbox.html
new file mode 100644
index 000000000..5427ecfbe
--- /dev/null
+++ b/templates/admin/mailbox.html
@@ -0,0 +1,22 @@
+{% extends "admin/layout.html" %}
+{% block html_head %}{% endblock %}
+{% block content %}
+
+
+
+
+
+
+
+
+
+
Aliases
+ {% for alias in aliases %}
+ {{ alias.address }}
+ {% endfor %}
+
+{% endblock %}
diff --git a/templates/admin/mailboxes.html b/templates/admin/mailboxes.html
new file mode 100644
index 000000000..412de23c9
--- /dev/null
+++ b/templates/admin/mailboxes.html
@@ -0,0 +1,50 @@
+{% extends "admin/domain_header.html" %}
+{% block menu %}
+{% load admin_extras %}
+{% domain_menu domain.id "mailboxes" %}
+{% endblock %}
+
+{% block domcontent %}
+{% if not mailboxes %}
+No mailbox defined.
+{% else %}
+
+
+ | Actions |
+ Name |
+ Address |
+ Quota |
+ Enabled |
+
+ {% for mb in mailboxes %}
+
+
+ {% url admin.views.editmailbox domain.id mb.id as edit_url %}
+
+
+
+ {% url admin.views.delmailbox domain.id mb.id as del_url %}
+
+
+
+ {% url admin.views.newalias domain.id mb.id as alias_url %}
+
+
+
+ {% url mbox-aliases domain.id mb.id as aliases_url %}
+
+
+
+ |
+ {{ mb.name }} |
+ {{ mb.address }}@{{ mb.domain.name }} |
+ {{ mb.quota }} MB |
+ {{ mb.user.is_active|yesno }} |
+
+ {% endfor %}
+
+{% endif %}
+{% endblock %}
diff --git a/templates/admin/mbaliases.html b/templates/admin/mbaliases.html
new file mode 100644
index 000000000..7aefb2796
--- /dev/null
+++ b/templates/admin/mbaliases.html
@@ -0,0 +1,14 @@
+{% extends "admin/layout_simple.html" %}
+{% block html_head %}
+
+{% endblock %}
+{% block content %}
+Aliases for {{ mbox.address }}
+
+
+ {% for alias in aliases %}
+ - {{alias.address}}
+ {% endfor %}
+
+
+{% endblock %}
diff --git a/templates/admin/newalias.html b/templates/admin/newalias.html
new file mode 100644
index 000000000..b7ca008e5
--- /dev/null
+++ b/templates/admin/newalias.html
@@ -0,0 +1,15 @@
+{% extends "admin/layout_simple.html" %}
+{% block html_head %}
+
+{% endblock %}
+{% block content %}
+New alias
+{% url admin.views.newalias mbox.domain_id mbox.id as box_url %}
+
+{% endblock %}
diff --git a/templates/admin/newdomain.html b/templates/admin/newdomain.html
new file mode 100644
index 000000000..8a55e0622
--- /dev/null
+++ b/templates/admin/newdomain.html
@@ -0,0 +1,18 @@
+{% extends "admin/layout_simple.html" %}
+{% block html_head %}
+
+{% endblock %}
+{% block content %}
+New domain
+
+{% if error %}{{ error }}
{% endif %}
+{% url admin.views.newdomain as dom_url %}
+
+{% endblock %}
+²
diff --git a/templates/admin/newmailbox.html b/templates/admin/newmailbox.html
new file mode 100644
index 000000000..f65867340
--- /dev/null
+++ b/templates/admin/newmailbox.html
@@ -0,0 +1,18 @@
+{% extends "admin/layout_simple.html" %}
+{% block html_head %}
+
+{% endblock %}
+{% block content %}
+New mailbox
+
+{% if error %}{{ error }}
{% endif %}
+{% url admin.views.newmailbox domain.id as box_url %}
+
+{% endblock %}
diff --git a/templates/base.html b/templates/base.html
new file mode 100644
index 000000000..39353db47
--- /dev/null
+++ b/templates/base.html
@@ -0,0 +1,36 @@
+
+
+
+
+ MailNG - {%block pagetitle %}{% endblock %}
+
+
+
+
+ {%block html_head %}{% endblock %}
+
+
+
+
+
+
+ {%block content %}{% endblock %}
+
+
+
+
+
+
+
+
diff --git a/templates/layout.html b/templates/layout.html
new file mode 100644
index 000000000..29fd6ce54
--- /dev/null
+++ b/templates/layout.html
@@ -0,0 +1,29 @@
+
+
+
+
+ MailNG - {%block pagetitle %}{% endblock %}
+
+
+
+
+ {%block html_head %}{% endblock %}
+
+
+
+
+ {% block content %}{% endblock %}
+
+
+
+
+
diff --git a/templates/registration/login.html b/templates/registration/login.html
new file mode 100644
index 000000000..041eb2094
--- /dev/null
+++ b/templates/registration/login.html
@@ -0,0 +1,25 @@
+{% extends "layout.html" %}
+{% block html_head %}
+
+{% endblock %}
+{% block content %}
+
+
Welcome
+{% if form.errors %}
+
+
Your username and password didn't match. Please try again.
+
+{% endif %}
+
+
+{% endblock %}
diff --git a/urls.py b/urls.py
new file mode 100644
index 000000000..28ebd211b
--- /dev/null
+++ b/urls.py
@@ -0,0 +1,29 @@
+from django.conf.urls.defaults import *
+from django.conf import settings
+
+# Uncomment the next two lines to enable the admin:
+# from django.contrib import admin
+# admin.autodiscover()
+
+urlpatterns = patterns('',
+ # Example:
+ # (r'^mailng/', include('mailng.foo.urls')),
+
+ # Uncomment the admin/doc line below and add 'django.contrib.admindocs'
+ # to INSTALLED_APPS to enable admin documentation:
+ # (r'^admin/doc/', include('django.contrib.admindocs.urls')),
+
+ # Uncomment the next line to enable the admin:
+ # (r'^admin/(.*)', admin.site.root),
+
+ (r'^mailng/admin/', include('mailng.admin.urls')),
+ (r'^accounts/login/$', 'django.contrib.auth.views.login'),
+ (r'^accounts/logout/$', 'django.contrib.auth.views.logout_then_login')
+)
+
+if settings.DEBUG:
+ urlpatterns += patterns(
+ '',
+ (r'^static/(?P.*)$', 'django.views.static.serve', {'document_root': '/home/tonio/projets/mailng/static'})
+ )
+