From 308702ec100f24ae965e27e76d126e0e20cb848a Mon Sep 17 00:00:00 2001 From: tonio Date: Tue, 7 Dec 2010 23:44:28 +0100 Subject: [PATCH] see #103 Work in progress: * New domains admin page, all menus are now at the same level, * Views, models and forms partial refactoring (better code organization). --- admin/forms.py | 17 +- .../0005_increase_columns_length.py | 2 +- .../0007_auto__alter_full_address.py | 98 ++++++++ admin/models.py | 87 +++++-- admin/templatetags/admin_extras.py | 96 +++++--- admin/urls.py | 21 +- admin/views.py | 228 ++++++++---------- extensions/stats/views.py | 2 +- extensions/webmail/static/css/webmail.css | 1 + lib/__init__.py | 19 ++ lib/authbackends.py | 22 +- static/css/default.css | 5 + static/js/common.js | 5 +- templates/admin/domains.html | 14 +- templates/admin/domains_header.html | 17 ++ templates/admin/domaliases.html | 17 +- templates/admin/editmailbox.html | 2 +- .../{editalias.html => editmbalias.html} | 4 +- templates/admin/mailboxes.html | 16 +- templates/admin/mbaliases.html | 29 +++ templates/admin/newmailbox.html | 2 +- .../admin/{newalias.html => newmbalias.html} | 4 +- templates/common/menulist.html | 6 +- userprefs/forms.py | 2 +- userprefs/views.py | 2 +- 25 files changed, 462 insertions(+), 256 deletions(-) create mode 100644 admin/migrations/0007_auto__alter_full_address.py create mode 100644 templates/admin/domains_header.html rename templates/admin/{editalias.html => editmbalias.html} (81%) create mode 100644 templates/admin/mbaliases.html rename templates/admin/{newalias.html => newmbalias.html} (82%) diff --git a/admin/forms.py b/admin/forms.py index 5865bc9be..7090109dc 100644 --- a/admin/forms.py +++ b/admin/forms.py @@ -32,7 +32,7 @@ class MailboxForm(forms.ModelForm): class Meta: model = Mailbox - fields = ('name', 'address') + fields = ('domain', 'name', 'address') def __init__(self, *args, **kwargs): super(MailboxForm, self).__init__(*args, **kwargs) @@ -49,6 +49,13 @@ class MailboxForm(forms.ModelForm): raise forms.ValidationError(_("Passwords mismatch")) return self.cleaned_data["password2"] + def save(self, force_insert=False, force_update=False, commit=True): + m = super(MailboxForm, self).save(commit=False) + if commit: + m.save(enabled=self.cleaned_data["enabled"], + password=self.cleaned_data["password1"]) + return m + class AliasForm(forms.ModelForm): def __init__(self, *args, **kwargs): domain = None @@ -70,6 +77,14 @@ class AliasForm(forms.ModelForm): return self.cleaned_data["address"].rsplit("@", 1)[0] return self.cleaned_data["address"] + def save(self, force_insert=False, force_update=False, commit=True): + a = super(AliasForm, self).save(commit=False) + if commit: + domain = self.cleaned_data["mboxes"][0].domain + a.full_address = "%s@%s" % (a.address, domain.name) + a.save() + return a + class SuperAdminForm(forms.Form): user = forms.ModelChoiceField([], label=_("User"), required=True, empty_label=_("Select a user"), diff --git a/admin/migrations/0005_increase_columns_length.py b/admin/migrations/0005_increase_columns_length.py index 5d7a3f5ad..4c0b08463 100644 --- a/admin/migrations/0005_increase_columns_length.py +++ b/admin/migrations/0005_increase_columns_length.py @@ -7,7 +7,7 @@ from django.db import models class Migration(SchemaMigration): def forwards(self, orm): - db.alter_column("auth_user", "username", models.CharField(max_length=254)) + db.alter_column("auth_user", "username", models.CharField(unique=True, max_length=254)) db.alter_column("auth_user", "email", models.CharField(max_length=254)) def backwards(self, orm): diff --git a/admin/migrations/0007_auto__alter_full_address.py b/admin/migrations/0007_auto__alter_full_address.py new file mode 100644 index 000000000..04232559c --- /dev/null +++ b/admin/migrations/0007_auto__alter_full_address.py @@ -0,0 +1,98 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + db.alter_column("admin_alias", "full_address", + models.CharField(unique=True, max_length=254)) + + def backwards(self, orm): + db.alter_column("admin_alias", "full_address", + models.CharField(max_length=150)) + + models = { + 'admin.alias': { + 'Meta': {'object_name': 'Alias'}, + 'address': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'full_address': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '150'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'mboxes': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['admin.Mailbox']", 'symmetrical': 'False'}) + }, + 'admin.domain': { + 'Meta': {'object_name': 'Domain'}, + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'quota': ('django.db.models.fields.IntegerField', [], {}) + }, + 'admin.domainalias': { + 'Meta': {'object_name': 'DomainAlias'}, + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}), + 'target': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.Domain']"}) + }, + 'admin.extension': { + 'Meta': {'object_name': 'Extension'}, + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '150'}) + }, + 'admin.mailbox': { + 'Meta': {'object_name': 'Mailbox'}, + 'address': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'domain': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['admin.Domain']"}), + 'full_address': ('django.db.models.fields.CharField', [], {'max_length': '150'}), + 'gid': ('django.db.models.fields.IntegerField', [], {}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'path': ('django.db.models.fields.CharField', [], {'max_length': '255'}), + 'quota': ('django.db.models.fields.IntegerField', [], {}), + 'uid': ('django.db.models.fields.IntegerField', [], {}), + 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) + }, + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['admin'] diff --git a/admin/models.py b/admin/models.py index 0f184f692..c07383ab7 100644 --- a/admin/models.py +++ b/admin/models.py @@ -1,10 +1,28 @@ -from django.db import models +# coding: utf8 +from django.db import models, IntegrityError from django.contrib.auth.models import User -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext as _ from django.conf import settings from modoboa.lib import parameters -from modoboa.lib import exec_cmd, exec_as_vuser +from modoboa.lib import exec_cmd, exec_as_vuser, crypt_password import os +import pwd + +class AdminError(Exception): + """Custom exception + + + """ + def __init__(self, value): + """Constructor + + :param value: the information contained in this exception. + """ + self.value = str(value) + + def __str__(self): + """String representation""" + return self.value class Domain(models.Model): name = models.CharField(_('name'), max_length=100, @@ -70,28 +88,29 @@ class Mailbox(models.Model): self.mdirroot = parameters.get_admin("MAILDIR_ROOT") def __str__(self): - return "%s" % (self.address) + return "%s" % (self.full_address) - def create_dir(self, domain): - path = "%s/%s/%s" % (parameters.get_admin("STORAGE_PATH"), - domain.name, self.address) - if os.path.exists(path): + def create_dir(self): + relpath = "%s/%s" % (self.domain.name, self.address) + abspath = os.path.join(parameters.get_admin("STORAGE_PATH"), relpath) + if self.mbtype == "mbox": + self.path = "%s/Inbox" % relpath + else: + self.path = "%s/%s" % (relpath, self.mdirroot) + if os.path.exists(abspath): return True - if not exec_as_vuser("mkdir -p %s" % path): + if not exec_as_vuser("mkdir -p %s" % abspath): return False if self.mbtype == "mbox": template = ["Inbox", "Drafts", "Sent", "Trash", "Junk"] - for dir in template: - exec_as_vuser("touch %s/%s" % (path, dir)) - - self.path = "%s/%s/Inbox" % (domain.name, self.address) + for f in template: + exec_as_vuser("touch %s/%s" % (abspath, f)) else: template = [".Drafts/", ".Sent/", ".Trash/", ".Junk/"] for dir in template: for sdir in ["cur", "new", "tmp"]: exec_as_vuser("mkdir -p %s/%s/%s/%s" \ - % (path, self.mdirroot, dir, sdir)) - self.path = "%s/%s/%s/" % (domain.name, self.address, self.mdirroot) + % (abspath, self.mdirroot, dir, sdir)) return True def rename_dir(self, domain, newaddress): @@ -109,6 +128,42 @@ class Mailbox(models.Model): % (parameters.get_admin("STORAGE_PATH"), self.domain.name, self.address)) + def save(self, *args, **kwargs): + if not self.create_dir(): + raise AdminError(_("Failed to initialise mailbox, check permissions")) + try: + user = getattr(self, "user") + except User.DoesNotExist: + user = User() + user.username = user.email = "%s@%s" % (self.address, self.domain.name) + user.set_unusable_password() + user.is_active = kwargs["enabled"] + try: + fname, lname = self.name.split() + except ValueError: + fname = self.name + lname = "" + user.first_name = fname + user.last_name = lname + try: + user.save() + except IntegrityError: + raise AdminError(_("Mailbox with this address already exists")) + self.user = user + if kwargs["password"] != u"é": + self.password = crypt_password(kwargs["password"]) + self.uid = pwd.getpwnam(parameters.get_admin("VIRTUAL_UID")).pw_uid + self.gid = pwd.getpwnam(parameters.get_admin("VIRTUAL_GID")).pw_gid + if not self.quota: + self.quota = self.domain.quota + self.full_address = self.user.email + try: + del kwargs["enabled"] + del kwargs["password"] + except KeyError: + pass + super(Mailbox, self).save(*args, **kwargs) + def tohash(self): return { "id" : self.id, @@ -120,7 +175,7 @@ class Mailbox(models.Model): class Alias(models.Model): address = models.CharField(_('address'), max_length=100, help_text=_("The alias address (without the domain part)")) - full_address = models.CharField(max_length=150) + full_address = models.CharField(max_length=254, unique=True) mboxes = models.ManyToManyField(Mailbox, verbose_name=_('mailboxes'), help_text=_("The mailboxes this alias points to")) enabled = models.BooleanField(_('enabled'), diff --git a/admin/templatetags/admin_extras.py b/admin/templatetags/admin_extras.py index c0d8a6e08..be71aff28 100644 --- a/admin/templatetags/admin_extras.py +++ b/admin/templatetags/admin_extras.py @@ -62,17 +62,17 @@ def admin_menu(user): def domain_menu(domain_id, selection, user): entries = [ {"name" : "", - "url" : reverse(admin.views.newmailbox, args=[domain_id]), + "url" : reverse(admin.views.newmailbox), "label" : _("New mailbox"), "img" : static_url("pics/add.png"), "class" : "boxed", "rel" : "{handler:'iframe',size:{x:300,y:280}}"}, {"name" : "mailboxes", - "url" : reverse(admin.views.mailboxes, args=[domain_id]), + "url" : reverse(admin.views.mailboxes), "img" : static_url("pics/mailbox.png"), "label" : _("Mailboxes")}, {"name" : "aliases", - "url" : reverse("full-aliases", args=[domain_id]), + "url" : reverse("full-aliases"), "img" : static_url("/pics/alias.png"), "label" : _("Aliases")}, ] @@ -108,34 +108,47 @@ def settings_menu(selection, user): {"selection" : selection, "entries" : entries, "user" : user}) +def dommenu_entry(name, label, img, listurl, newurl): + result = {"name" : name, + "label" : label, + "img" : img, + "url" : listurl, + "class" : "menubardropdown", + "menu" : [{"name" : "%s_new" % name, + "url" : newurl, + "label" : _("New"), + "img" : static_url("pics/add.png"), + "class" : "boxed", + "rel" : "{handler:'iframe',size:{x:300,y:200}}"} + ] + } + return result + @register.simple_tag -def domains_menu(selection, user): - entries = [ - {"name" : "newdomain", - "url" : reverse(admin.views.newdomain), - "label" : _("New domain"), - "img" : static_url("pics/add.png"), - "class" : "boxed", - "rel" : "{handler:'iframe',size:{x:300,y:200}}"}, - {"name" : "domains", - "url" : reverse(admin.views.domains), - "label" : _("Domains"), - "img" : static_url("pics/domains.png")}, - {"name" : "newdomalias", - "url" : reverse(admin.views.newdomalias), - "label" : _("New domain alias"), - "img" : static_url("pics/add.png"), - "class" : "boxed", - "rel" : "{handler:'iframe',size:{x:300,y:200}}"}, - {"name" : "domaliases", - "url" : reverse(admin.views.domaliases), - "label" : _("Domain aliases"), - "img" : static_url("pics/alias.png")} +def domains_menu(selection, user, domid): + args = domid != "" and ("?domid=%s" % domid) or "" + entries = [] + if user.has_perm("admin.view_domains"): + entries += dommenu_entry("domains", _("Domains"), + static_url("pics/domains.png"), + reverse(admin.views.domains), + reverse(admin.views.newdomain)), + entries += [ + dommenu_entry("domaliases", _("Domain aliases"), static_url("pics/alias.png"), + reverse(admin.views.domaliases) + args, + reverse(admin.views.newdomalias) + args), + dommenu_entry("mailboxes", _("Mailboxes"), static_url("pics/mailbox.png"), + reverse(admin.views.mailboxes) + args, + reverse(admin.views.newmailbox) + args), + dommenu_entry("mbaliases", _("Mailbox aliases"), static_url("pics/alias.png"), + reverse(admin.views.mbaliases) + args, + reverse(admin.views.newmbalias) + args), ] entries += events.raiseQueryEvent("AdminMenuDisplay", target="admin_menu_box", user=user) return render_to_string('common/menu.html', - {"entries" : entries, "selection" : selection, + {"entries" : entries, + "selection" : selection, "user" : user}) @register.simple_tag @@ -156,7 +169,8 @@ def permissions_menu(user): def render_actions(actions): t = template.Template(""" {% for a in actions %} - + {% endfor %} @@ -177,7 +191,8 @@ def domain_actions(user, domid): {"name" : "deldomain", "url" : reverse(admin.views.deldomain, args=[domid]), "img" : static_url("pics/remove.png"), - "title" : _("Delete domain")}, + "title" : _("Delete domain"), + "confirm" : _("Delete this domain?")}, {"name" : "domaliases", "url" : reverse(admin.views.domaliases) + "?domid=%s" % domid, "img" : static_url("pics/alias.png"), @@ -198,25 +213,42 @@ def domalias_actions(user, aid): return render_actions(actions) @register.simple_tag -def mailbox_actions(user, domid, mid): +def mailbox_actions(user, mbox): actions = [ {"name" : "editmailbox", - "url" : reverse(admin.views.editmailbox, args=[domid, mid]), + "url" : reverse(admin.views.editmailbox, args=[mbox.id]), "img" : static_url("pics/edit.png"), "title" : _("Edit mailbox"), "class" : "boxed", "rel" : "{handler:'iframe',size:{x:300,y:280}}"}, {"name" : "deleteMb", - "url" : reverse(admin.views.delmailbox, args=[domid, mid]), + "url" : reverse(admin.views.delmailbox, args=[mbox.id]), "img" : static_url("pics/remove.png"), "title" : _("Delete mailbox")}, {"name" : "aliases", - "url" : reverse("mbox-aliases", args=[domid, mid]), + "url" : reverse(admin.views.mbaliases) + "?mbid=%d" % mbox.id, "img" : static_url("pics/alias.png"), "title" : _("View this mailbox aliases")}, ] return render_actions(actions) +@register.simple_tag +def mbalias_actions(user, alias): + actions = [ + {"name" : "editmbalias", + "url" : reverse(admin.views.editmbalias, args=[alias.id]), + "img" : static_url("pics/edit.png"), + "title" : _("Edit mailbox alias"), + "class" : "boxed", + "rel" : "{handler:'iframe',size:{x:300,y:250}}"}, + {"name" : "dembalias", + "url" : reverse(admin.views.delmbalias, args=[alias.id]), + "img" : static_url("pics/remove.png"), + "title" : _("Delete mailbox alias"), + "confirm" : _("Delete this alias?")}, + ] + return render_actions(actions) + @register.simple_tag def loadadminextmenu(user): menu = events.raiseQueryEvent("AdminMenuDisplay", target="admin_menu_box", diff --git a/admin/urls.py b/admin/urls.py index 046b44176..6ea81b344 100644 --- a/admin/urls.py +++ b/admin/urls.py @@ -11,16 +11,19 @@ urlpatterns = patterns('modoboa.admin.views', (r'^domaliases/new/', 'newdomalias'), (r'^domaliases/edit/(?P\d+)/', 'editdomalias'), - (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/$', 'newalias'), - (r'^domains/(?P\d+)/editalias/(?P\d+)/$', 'editalias'), - (r'^domains/(?P\d+)/$', 'mailboxes'), + (r'^mailboxes/$', 'mailboxes'), + (r'^mailboxes/new/$', 'newmailbox'), + (r'^mailboxes/edit/(?P\d+)/$', 'editmailbox'), + (r'^mailboxes/delete/(?P\d+)/$', 'delmailbox'), + + (r'^mbaliases/$', 'mbaliases'), + (r'^mbaliases/new/$', 'newmbalias'), + (r'^mbaliases/edit/(?P\d+)/$', 'editmbalias'), + (r'^mbaliases/delete/(?P\d+)/$', 'delmbalias'), + (r'^domains/(?P\d+)/raw/$', 'mailboxes_raw'), - 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'), + #url(r'^domains/(?P\d+)/aliases/$', "aliases", name="full-aliases"), + #url(r'^domains/(?P\d+)/aliases/(?P\d+)/$', "aliases", name="mbox-aliases"), (r'^settings/$', 'viewparameters'), (r'^settings/parameters/$', 'viewparameters'), diff --git a/admin/views.py b/admin/views.py index abaab4705..db9a50a04 100644 --- a/admin/views.py +++ b/admin/views.py @@ -13,22 +13,28 @@ from django.db import transaction, IntegrityError from modoboa import admin, userprefs from models import * from admin.permissions import * -from modoboa.lib.authbackends import crypt_password +from modoboa.lib import crypt_password from modoboa.lib import _render, _render_to_string, _ctx_ok, \ getctx, events, parameters from modoboa.lib.models import Parameter import copy -import pwd def good_domain(f): - def dec(request, dom_id, **kwargs): + def dec(request, **kwargs): if request.user.is_superuser: - return f(request, dom_id, **kwargs) + return f(request, **kwargs) mb = Mailbox.objects.get(user=request.user.id) - if isinstance(dom_id, str) or isinstance(dom_id, unicode): - dom_id = int(dom_id) - if dom_id == mb.domain.id: - return f(request, dom_id, **kwargs) + access = True + if request.GET.has_key("domid"): + dom_id = int(request.GET["domid"]) + if dom_id != mb.domain.id: + access = False + else: + q = request.GET.copy() + q["domid"] = mb.domain.id + request.GET = q + if access: + return f(request, **kwargs) from django.conf import settings path = urlquote(request.get_full_path()) @@ -36,12 +42,20 @@ def good_domain(f): return HttpResponseRedirect("%s?next=%s" % (login_url, path)) return dec +def render_domains_page(request, page, **kwargs): + template = "admin/%s.html" % page + if request.GET.has_key("domid"): + kwargs["domid"] = request.GET["domid"] + else: + kwargs["domid"] = "" + kwargs["selection"] = page + return _render(request, template, kwargs) + @login_required def domains(request): if not request.user.has_perm("admin.view_domains"): if request.user.has_perm("admin.view_mailboxes"): - mb = Mailbox.objects.get(user=request.user.id) - return mailboxes(request, dom_id=mb.domain.id) + return HttpResponseRedirect(reverse(mailboxes)) return HttpResponseRedirect(reverse(userprefs.views.preferences)) @@ -49,9 +63,8 @@ def domains(request): counters = {} for dom in domains: dom.mboxcounter = len(dom.mailbox_set.all()) - return _render(request, 'admin/domains.html', { - "domains" : domains, "counters" : counters - }) + return render_domains_page(request, "domains", + domains=domains, counter=counters) @login_required def domaliases(request): @@ -61,9 +74,8 @@ def domaliases(request): else: domain = None domaliases = DomainAlias.objects.all() - return _render(request, 'admin/domaliases.html', { - "domaliases" : domaliases, "domain" : domain - }) + return render_domains_page(request, "domaliases", + domaliases=domaliases, domain=domain) @login_required def newdomalias(request): @@ -199,12 +211,15 @@ def deldomain(request, dom_id): @login_required @good_domain @permission_required("admin.view_mailboxes") -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 - }) +def mailboxes(request): + if request.GET.has_key("domid"): + domain = Domain.objects.get(pk=request.GET["domid"]) + mailboxes = Mailbox.objects.filter(domain=domain.id) + else: + mailboxes = Mailbox.objects.all() + domain = None + return render_domains_page(request, "mailboxes", + mailboxes=mailboxes, domain=domain) @login_required @good_domain @@ -218,102 +233,60 @@ def mailboxes_raw(request, dom_id=None): @login_required @good_domain @permission_required("admin.add_mailbox") -def newmailbox(request, dom_id=None): - domain = Domain.objects.get(pk=dom_id) +def newmailbox(request): if request.method == "POST": form = MailboxForm(request.POST) error = None if form.is_valid(): - mb = form.save(commit=False) - if mb.create_dir(domain): - from django.conf import settings - - user = User() - user.username = user.email = "%s@%s" % (mb.address, domain.name) - user.set_unusable_password() - user.is_active = request.POST.has_key("enabled") \ - and True or False - try: - fname, lname = mb.name.split() - except ValueError: - fname = mb.name - lname = "" - user.first_name = fname - user.last_name = lname - try: - user.save() - except IntegrityError: - error = _("Mailbox with this address already exists") - else: - mb.user = user - mb.password = crypt_password(request.POST["password1"]) - mb.uid = pwd.getpwnam(parameters.get_admin("VIRTUAL_UID")).pw_uid - mb.gid = pwd.getpwnam(parameters.get_admin("VIRTUAL_GID")).pw_gid - mb.domain = domain - mb.quota = request.POST["quota"] - if not mb.quota: - mb.quota = domain.quota - mb.full_address = user.email - mb.save() - - events.raiseEvent("CreateMailbox", mbox=mb) - - messages.info(request, _("Mailbox created."), - fail_silently=True) - ctx = _ctx_ok(reverse(admin.views.mailboxes, args=[domain.id])) - return HttpResponse(simplejson.dumps(ctx), - mimetype="application/json") + try: + mb = form.save() + except AdminError as e: + error = str(e) else: - error = _("Failed to initialise mailbox, check permissions") - content = _render_to_string("ko", "admin/newmailbox.html", { - "form" : form, "domain" : domain, "error" : error + events.raiseEvent("CreateMailbox", mbox=mb) + messages.info(request, _("Mailbox created"), + fail_silently=True) + ctx = _ctx_ok(reverse(admin.views.mailboxes) + "?domid=%d" % mb.domain.id) + return HttpResponse(simplejson.dumps(ctx), + mimetype="application/json") + content = _render_to_string(request, "admin/newmailbox.html", { + "form" : form, "error" : error }) ctx = getctx("ko", content=content) return HttpResponse(simplejson.dumps(ctx), mimetype="application/json") form = MailboxForm() + if request.GET.has_key("domid"): + form.fields["domain"].initial = request.GET["domid"] return _render(request, "admin/newmailbox.html", { - "domain" : domain, "form" : form + "form" : form }) @login_required @good_domain @permission_required("admin.change_mailbox") @transaction.commit_manually -def editmailbox(request, dom_id, mbox_id=None): +def editmailbox(request, mbox_id=None): mb = Mailbox.objects.get(pk=mbox_id) if request.method == "POST": oldmb = copy.deepcopy(mb) form = MailboxForm(request.POST, instance=mb) error = None if form.is_valid(): - mb = form.save(commit=False) - mb.user.is_active = request.POST.has_key("enabled") \ - and request.POST["enabled"] or False - mb.user.username = mb.user.email = \ - "%s@%s" % (mb.address, mb.domain.name) try: - mb.user.save() - except IntegrityError: - error = _("Mailbox with this address already exists") + mb = form.save() + except AdminError, inst: + error = str(inst) else: - if not oldmb.rename_dir(mb.domain.name, mb.address): - error = _("Failed to rename mailbox, check permissions") - else: - if request.POST["password1"] != u"é": - mb.password = crypt_password(request.POST["password1"]) - mb.quota = request.POST["quota"] - if not mb.quota: - mb.quota = mb.domain.quota - mb.full_address = mb.user.email - mb.save() + if oldmb.rename_dir(mb.domain.name, mb.address): transaction.commit() events.raiseEvent("ModifyMailbox", mbox=mb, oldmbox=oldmb) messages.info(request, _("Mailbox modified"), fail_silently=True) - ctx = _ctx_ok(reverse(admin.views.mailboxes, args=[dom_id])) + ctx = _ctx_ok(reverse(admin.views.mailboxes) + "?domid=%d" % mb.domain.id) return HttpResponse(simplejson.dumps(ctx), mimetype="application/json") + error = _("Failed to rename mailbox, check permissions") if error is not None: transaction.rollback() ctx = getctx("ko", content=_render_to_string(request, "admin/editmailbox.html", { @@ -333,7 +306,7 @@ def editmailbox(request, dom_id, mbox_id=None): @login_required @good_domain @permission_required("admin.delete_mailbox") -def delmailbox(request, dom_id, mbox_id=None): +def delmailbox(request, mbox_id=None): mb = Mailbox.objects.get(pk=mbox_id) if mb.user.id != request.user.id: events.raiseEvent("DeleteMailbox", mbox=mb) @@ -351,90 +324,85 @@ def delmailbox(request, dom_id, mbox_id=None): @login_required @good_domain @permission_required("admin.view_aliases") -def aliases(request, dom_id=None, mbox_id=None): - domain = Domain.objects.get(pk=dom_id) - if not mbox_id: - aliases = Alias.objects.filter(mboxes__domain__id=dom_id).distinct() +def mbaliases(request): + if request.GET.has_key("domid"): + aliases = Alias.objects.filter(mboxes__domain__id=request.GET["domid"]).distinct() + if request.GET.has_key("mbid"): + aliases = Alias.objects.filter(mboxes__id=request.GET["mbid"]).distinct() else: - aliases = Alias.objects.filter(mboxes__id=mbox_id) - return _render(request, 'admin/aliases.html', { - "aliases" : aliases, "domain" : domain - }) + aliases = Alias.objects.all() + return render_domains_page(request, "mbaliases", aliases=aliases) @login_required @good_domain @permission_required("admin.add_alias") -def newalias(request, dom_id): - domain = Domain.objects.get(pk=dom_id) +def newmbalias(request): if request.method == "POST": - form = AliasForm(request.POST, domain=domain) + form = AliasForm(request.POST) error = None if form.is_valid(): - if Alias.objects.filter(address=request.POST["address"]): + try: + alias = form.save() + except IntegrityError: error = _("Alias with this name already exists") else: - alias = form.save(commit=False) - alias.full_address = "%s@%s" % (alias.address, domain.name) - alias.save() form.save_m2m() - ctx = _ctx_ok(reverse(admin.views.aliases, args=[dom_id])) - messages.info(request, _("Alias created"), fail_silently=True) + ctx = _ctx_ok(reverse(admin.views.mbaliases) ) + messages.info(request, _("Mailbox alias created"), fail_silently=True) return HttpResponse(simplejson.dumps(ctx), mimetype="application/json") - content = _render_to_string(request, "admin/newalias.html", { - "domain" : dom_id, "form" : form, "error" : error + content = _render_to_string(request, "admin/newmbalias.html", { + "form" : form, "error" : error }) ctx = getctx("ko", content=content) return HttpResponse(simplejson.dumps(ctx), mimetype="application/json") - form = AliasForm(domain=domain) - return _render(request, 'admin/newalias.html', { - "domain" : dom_id, "form" : form, "noerrors" : True + if request.GET.has_key("domid"): + form = AliasForm(domain=Domain.objects.get(pk=request.GET["domid"])) + else: + form = AliasForm() + return _render(request, 'admin/newmbalias.html', { + "form" : form, "noerrors" : True }) @login_required @good_domain @permission_required("admin.change_alias") -def editalias(request, dom_id, alias_id): +def editmbalias(request, alias_id): alias = Alias.objects.get(pk=alias_id) if request.method == "POST": form = AliasForm(request.POST, instance=alias) error = None if form.is_valid(): - if alias.address != request.POST["address"] \ - and Alias.objects.filter(address=request.POST["address"]): + try: + alias = form.save() + except IntegrityError: error = _("Alias with this name already exists") else: - domain = Domain.objects.get(pk=dom_id) - alias = form.save(commit=False) - alias.full_address = "%s@%s" % (alias.address, - domain.name) - alias.save() form.save_m2m() - ctx = _ctx_ok(reverse(admin.views.aliases, args=[dom_id])) - messages.info(request, _("Alias modified"), + ctx = _ctx_ok(reverse(admin.views.mbaliases)) + messages.info(request, _("Mailbox alias modified"), fail_silently=True) return HttpResponse(simplejson.dumps(ctx), mimetype="application/json") - content = _render_to_string(request, "admin/editalias.html", { - "domain" : dom_id, "alias" : alias, "form" : form, "error" : error + content = _render_to_string(request, "admin/editmbalias.html", { + "alias" : alias, "form" : form, "error" : error }) ctx = getctx("ko", content=content) return HttpResponse(simplejson.dumps(ctx), mimetype="application/json") form = AliasForm(instance=alias) - return _render(request, 'admin/editalias.html', { - "form" : form, "alias" : alias, "domain" : dom_id + return _render(request, 'admin/editmbalias.html', { + "form" : form, "alias" : alias }) @login_required @good_domain @permission_required("admin.delete_alias") -def delalias(request, dom_id, alias_id): +def delmbalias(request, alias_id): alias = Alias.objects.get(pk=alias_id) alias.delete() messages.info(request, _("Alias deleted"), fail_silently=True) - return HttpResponseRedirect(reverse(admin.views.aliases, - args=[dom_id])) + return HttpResponseRedirect(reverse(admin.views.mbaliases)) @login_required @permission_required("auth.view_permissions") @@ -530,8 +498,8 @@ def saveparameters(request): @login_required @user_passes_test(lambda u: u.is_superuser) def viewextensions(request): - from extensions import list_extensions - from lib import tables + from modoboa.extensions import list_extensions + from modoboa.lib import tables class ExtensionsTable(tables.Table): idkey = "id" @@ -560,7 +528,7 @@ def viewextensions(request): @login_required @user_passes_test(lambda u: u.is_superuser) def saveextensions(request): - import extensions + from modoboa import extensions actived_exts = Extension.objects.filter(enabled=True) found = [] diff --git a/extensions/stats/views.py b/extensions/stats/views.py index c656a73d6..b73976e3f 100644 --- a/extensions/stats/views.py +++ b/extensions/stats/views.py @@ -9,7 +9,7 @@ from modoboa.admin.views import good_domain from modoboa.admin.models import Domain, Mailbox from django.contrib.auth.decorators \ import login_required, user_passes_test, permission_required -from extensions.stats.grapher import * +from modoboa.extensions.stats.grapher import * graph_types = ['AVERAGE', 'MAX'] graph_list = [{"name" : "traffic", "label" : _("Average normal traffic")}, diff --git a/extensions/webmail/static/css/webmail.css b/extensions/webmail/static/css/webmail.css index 84553db93..a6359cc11 100644 --- a/extensions/webmail/static/css/webmail.css +++ b/extensions/webmail/static/css/webmail.css @@ -156,6 +156,7 @@ textarea { background-color: #eee; width: 80%; height: 30px; + word-wrap: break-word; } textarea:hover { diff --git a/lib/__init__.py b/lib/__init__.py index a5da010d2..5d9aac7bf 100644 --- a/lib/__init__.py +++ b/lib/__init__.py @@ -2,6 +2,8 @@ import os import sys import time +import hashlib, crypt, string +from random import Random from django.conf import settings from django.template import RequestContext from django.shortcuts import render_to_response @@ -118,3 +120,20 @@ class Singleton(type): cls.instance = super(Singleton, cls).__call__(*args, **kwargs) return cls.instance + +def _check_password(password, crypted): + scheme = parameters.get_admin("PASSWORD_SCHEME", app="admin") + if scheme == "crypt": + return crypt.crypt(password, crypted) == crypted + if scheme == "md5": + return hashlib.md5(password).hexdigest() == crypted + return password + +def crypt_password(password): + scheme = parameters.get_admin("PASSWORD_SCHEME", app="admin") + if scheme == "crypt": + salt = ''.join(Random().sample(string.letters + string.digits, 2)) + return crypt.crypt(password, salt) + if scheme == "md5": + return hashlib.md5(password).hexdigest() + return password diff --git a/lib/authbackends.py b/lib/authbackends.py index b63c9f943..76cd8b022 100644 --- a/lib/authbackends.py +++ b/lib/authbackends.py @@ -1,10 +1,6 @@ from django.contrib.auth.models import User from modoboa.admin.models import Mailbox -import hashlib -import crypt -import string -from random import Random -from modoboa.lib import parameters +from modoboa.lib import _check_password class SimpleBackend: def authenticate(self, username=None, password=None): @@ -26,19 +22,3 @@ class SimpleBackend: except User.DoesNotExist: return None -def _check_password(password, crypted): - scheme = parameters.get_admin("PASSWORD_SCHEME", app="admin") - if scheme == "crypt": - return crypt.crypt(password, crypted) == crypted - if scheme == "md5": - return hashlib.md5(password).hexdigest() == crypted - return password - -def crypt_password(password): - scheme = parameters.get_admin("PASSWORD_SCHEME", app="admin") - if scheme == "crypt": - salt = ''.join(Random().sample(string.letters + string.digits, 2)) - return crypt.crypt(password, salt) - if scheme == "md5": - return hashlib.md5(password).hexdigest() - return password diff --git a/static/css/default.css b/static/css/default.css index d086af317..98769bc9d 100644 --- a/static/css/default.css +++ b/static/css/default.css @@ -177,6 +177,11 @@ div#footer { border-left: 5px solid #ccc; position: relative; z-index: 2; + +} + +.menubardropdown label { + cursor: pointer; } div.left { diff --git a/static/js/common.js b/static/js/common.js index 75cf58992..95a5e4eb3 100644 --- a/static/js/common.js +++ b/static/js/common.js @@ -288,7 +288,10 @@ setDivHeight = function(id, extrah, modulo) { bindRows = function(rooturl, page) { $$("tr").addEvent("click", function(event) { if (event.target.parentNode.id) { - var url = rooturl + event.target.parentNode.id + "?page=" + page; + var url = rooturl + "?domid=" + event.target.parentNode.id; + if (page != "") { + url += "?page=" + page; + } event.target.parentNode.setStyle("background", "#ffffcc"); location.href = url; } diff --git a/templates/admin/domains.html b/templates/admin/domains.html index 6b298cf35..eb716b7de 100644 --- a/templates/admin/domains.html +++ b/templates/admin/domains.html @@ -1,21 +1,16 @@ -{% extends "nlayout.html" %} +{% extends "admin/domains_header.html" %} {% load i18n admin_extras libextras %} {% block html_head %} - + {{ block.super }} {% endblock %} {% block middletitle %}{% trans "Available domains" %}{% endblock %} -{% block menu %}{% domains_menu "domains" user %}{% endblock %} -{% block content %} -{% if messages %} -{% display_messages messages %} -{% endif %} -
+{% block subcontent %} {% if not domains %}
{% trans "No domain defined." %}
{% else %} @@ -37,5 +32,4 @@ {% endfor %}
{% endif %} -
{% endblock %} diff --git a/templates/admin/domains_header.html b/templates/admin/domains_header.html new file mode 100644 index 000000000..d797f377c --- /dev/null +++ b/templates/admin/domains_header.html @@ -0,0 +1,17 @@ +{% extends "nlayout.html" %} +{% load i18n admin_extras libextras %} +{% block html_head %} + +{% endblock %} +{% block middletitle %} +{% block pagetitle %}{% endblock %}{% if domain %} ({% trans "for" %} {{ domain.name }}){% endif %} +{% endblock %} +{% block menu %}{% domains_menu selection user domid %}{% endblock %} +{% block content %} +{% if messages %} +{% display_messages messages %} +{% endif %} +
+{% block subcontent %}{% endblock %} +
+{% endblock %} diff --git a/templates/admin/domaliases.html b/templates/admin/domaliases.html index a50611fc3..e3dcef78a 100644 --- a/templates/admin/domaliases.html +++ b/templates/admin/domaliases.html @@ -1,17 +1,7 @@ -{% extends "nlayout.html" %} +{% extends "admin/domains_header.html" %} {% load i18n admin_extras libextras %} -{% block html_head %} - - -{% endblock %} -{% block middletitle %} -{% trans "Domain aliases" %}{% if domain %} ({% trans "for" %} {{ domain.name }}){% endif %} -{% endblock %} -{% block menu %}{% domains_menu "domaliases" user %}{% endblock %} -{% block content %} -{% if messages %}{% display_messages messages %}{% endif %} -
+{% block pagetitle %}{% trans "Domain aliases" %}{% endblock %} +{% block subcontent %} {% if not domaliases %}
{% trans "No domain alias defined." %}
{% else %} @@ -32,5 +22,4 @@ {% endfor %} {% endif %} -
{% endblock %} diff --git a/templates/admin/editmailbox.html b/templates/admin/editmailbox.html index 02d0d6b5c..97da28461 100644 --- a/templates/admin/editmailbox.html +++ b/templates/admin/editmailbox.html @@ -6,7 +6,7 @@ {% block content %}

{% trans "Mailbox" %} {{ mbox.name }}

{% if error %}
{{ error }}
{% endif %} -{% url admin.views.editmailbox mbox.domain_id mbox.id as box_url %} +{% url admin.views.editmailbox mbox.id as box_url %}
{% include "common/genericform.html" %}
diff --git a/templates/admin/editalias.html b/templates/admin/editmbalias.html similarity index 81% rename from templates/admin/editalias.html rename to templates/admin/editmbalias.html index f88e83cf8..b6fae716b 100644 --- a/templates/admin/editalias.html +++ b/templates/admin/editmbalias.html @@ -4,9 +4,9 @@ {% endblock %} {% block content %} {% load i18n %} -

Alias {{ alias.address }}

+

Mailbox alias {{ alias.address }}

{% if error %}
{{ error }}
{% endif %} -{% url admin.views.editalias domain alias.id as edit_url %} +{% url admin.views.editmbalias alias.id as edit_url %} {% include "common/genericform.html" %}
diff --git a/templates/admin/mailboxes.html b/templates/admin/mailboxes.html index d3bfcc412..02699541c 100644 --- a/templates/admin/mailboxes.html +++ b/templates/admin/mailboxes.html @@ -1,13 +1,9 @@ -{% extends "admin/domain_header.html" %} -{% load i18n %} -{% block menu %} -{% load admin_extras %} -{% domain_menu domain.id "mailboxes" user %} -{% endblock %} -{% block domcontent %} -{% load i18n %} +{% extends "admin/domains_header.html" %} +{% load i18n admin_extras %} +{% block pagetitle %}{% trans "Available mailboxes" %}{% endblock %} +{% block subcontent %} {% if not mailboxes %} -

{% trans "No mailbox defined." %}

+
{% trans "No mailbox defined." %}
{% else %} @@ -19,7 +15,7 @@ {% for mb in mailboxes %} - + diff --git a/templates/admin/mbaliases.html b/templates/admin/mbaliases.html new file mode 100644 index 000000000..637cdfd8f --- /dev/null +++ b/templates/admin/mbaliases.html @@ -0,0 +1,29 @@ +{% extends "admin/domains_header.html" %} +{% load i18n admin_extras %} +{% block pagetitle %}{% trans "Mailbox aliases" %}{% endblock %} +{% block subcontent %} +{% if not aliases %} +
{% trans "No mailbox alias defined." %}
+{% else %} +
{% mailbox_actions user domain.id mb.id %}{% mailbox_actions user mb %} {{ mb.name }} {{ mb.address }}@{{ mb.domain.name }} {{ mb.quota }} {% trans "MB" %}
+ + + + + + +{% for alias in aliases %} + + + + +{% endfor %} +
{% trans "Actions" %}{% trans "Address" %}{% trans "Mailbox" %}{% trans "Enabled" %}
{% mbalias_actions user alias %} + {{ alias.full_address }} + {% for mb in alias.mboxes.all %} + {{ mb.full_address }}
+ {% endfor %} +
{{ alias.enabled|yesno }}
+{% endif %} +{% endblock %} diff --git a/templates/admin/newmailbox.html b/templates/admin/newmailbox.html index ae894a060..8c5f5f487 100644 --- a/templates/admin/newmailbox.html +++ b/templates/admin/newmailbox.html @@ -7,7 +7,7 @@

{% trans "New mailbox" %}

{% if error %}
{{ error }}
{% endif %} -{% url admin.views.newmailbox domain.id as box_url %} +{% url admin.views.newmailbox as box_url %} {% include "common/genericform.html" %}
diff --git a/templates/admin/newalias.html b/templates/admin/newmbalias.html similarity index 82% rename from templates/admin/newalias.html rename to templates/admin/newmbalias.html index 77200084b..31c2fec03 100644 --- a/templates/admin/newalias.html +++ b/templates/admin/newmbalias.html @@ -4,9 +4,9 @@ {% endblock %} {% block content %} -

{% trans "New alias" %}

+

{% trans "New mailbox alias" %}

{% if error %}
{{ error }}
{% endif %} -{% url admin.views.newalias domain as box_url %} +{% url admin.views.newmbalias as box_url %} {% include "common/genericform.html" %}
diff --git a/templates/common/menulist.html b/templates/common/menulist.html index 3330f4c4e..b54140cee 100644 --- a/templates/common/menulist.html +++ b/templates/common/menulist.html @@ -1,9 +1,11 @@ {% load i18n %} {% for entry in entries %} {% if entry.menu %} - {% else %} -
  • +