diff --git a/modoboa/admin/lib.py b/modoboa/admin/lib.py index 215cc4e76..7356a68d7 100644 --- a/modoboa/admin/lib.py +++ b/modoboa/admin/lib.py @@ -26,6 +26,6 @@ def get_listing_page(objects, pagenum): paginator = Paginator(objects, int(parameters.get_admin("ITEMS_PER_PAGE"))) try: page = paginator.page(int(pagenum)) - except (EmptyPage, PageNotAnInteger): + except (EmptyPage, PageNotAnInteger, ValueError): page = paginator.page(paginator.num_pages) return page diff --git a/modoboa/admin/models.py b/modoboa/admin/models.py index 87c546c50..a7564d990 100644 --- a/modoboa/admin/models.py +++ b/modoboa/admin/models.py @@ -1,6 +1,5 @@ # coding: utf-8 -from django.core import validators -from django.db import models, IntegrityError +from django.db import models from django.db.models import Q from django.db.models.signals import pre_delete from django.dispatch import receiver @@ -15,10 +14,11 @@ from django.utils import timezone from django.conf import settings from modoboa.lib import md5crypt, parameters, events from modoboa.lib.exceptions import PermDeniedException -from modoboa.lib.sysutils import exec_cmd, exec_as_vuser +from modoboa.lib.sysutils import exec_cmd from modoboa.lib.emailutils import split_mailbox from modoboa.extensions import exts_pool from exceptions import AdminError +import reversion import os import sys import pwd @@ -182,18 +182,18 @@ class User(AbstractBaseUser, PermissionsMixin): val1 = hashlib.md5(raw_value).hexdigest() elif scheme == u"sha256": val1 = base64.b64encode(hashlib.sha256(raw_value).digest()) - elif scheme == u"$1$": # md5crypt + elif scheme == u"$1$": # md5crypt salt, hashed = val2.split('$') val1 = md5crypt(raw_value, str(salt)) - val2 = self.password # re-add scheme for comparison below + val2 = self.password # re-add scheme for comparison below else: val1 = raw_value return constant_time_compare(val1, val2) @property def tags(self): - return [{"name" : "account", "label" : _("account"), "type" : "idt"}, - {"name" : self.group, "label" : self.group, "type" : "grp", "color" : "info"}] + return [{"name": "account", "label": _("account"), "type": "idt"}, + {"name": self.group, "label": self.group, "type": "grp", "color": "info"}] @property def has_mailbox(self): @@ -541,6 +541,8 @@ class User(AbstractBaseUser, PermissionsMixin): row += [dom.name for dom in self.get_domains()] csvwriter.writerow(row) +reversion.register(User) + class ObjectAccess(models.Model): user = models.ForeignKey(User) @@ -674,7 +676,8 @@ class Domain(DatesAware): ungrant_access_to_object(al, account) def delete(self, fromuser, keepdir=False): - from modoboa.lib.permissions import ungrant_access_to_object, ungrant_access_to_objects + from modoboa.lib.permissions import \ + ungrant_access_to_object, ungrant_access_to_objects if self.domainalias_set.count(): events.raiseEvent("DomainAliasDeleted", self.domainalias_set.all()) @@ -683,7 +686,7 @@ class Domain(DatesAware): Quota.objects.filter(username__contains='@%s' % self.name).delete() events.raiseEvent("DeleteMailbox", self.mailbox_set.all()) ungrant_access_to_objects(self.mailbox_set.all()) - hm = parameters.get_admin("HANDLE_MAILBOXES", raise_error=False) + hm = parameters.get_admin("HANDLE_MAILBOXES", raise_error=False) if hm == "yes" and not keepdir: for mb in self.mailbox_set.all(): mb.delete_dir() @@ -726,6 +729,8 @@ class Domain(DatesAware): def to_csv(self, csvwriter): csvwriter.writerow(["domain", self.name, self.quota, self.enabled]) +reversion.register(Domain) + class DomainAlias(DatesAware): name = models.CharField(ugettext_lazy("name"), max_length=100, unique=True, @@ -792,7 +797,10 @@ class DomainAlias(DatesAware): :param csvwriter: a ``csv.writer`` object """ - csvwriter.writerow(["domainalias", self.name, self.target.name, self.enabled]) + csvwriter.writerow(["domainalias", self.name, + self.target.name, self.enabled]) + +reversion.register(DomainAlias) class Mailbox(DatesAware): @@ -855,8 +863,10 @@ class Mailbox(DatesAware): self.__mail_home = None if not os.path.exists(old_mail_home): return - code, output = exec_cmd("mv %s %s" % (old_mail_home, self.mail_home), - sudo_user=parameters.get_admin("MAILBOXES_OWNER")) + code, output = exec_cmd( + "mv %s %s" % (old_mail_home, self.mail_home), + sudo_user=parameters.get_admin("MAILBOXES_OWNER") + ) if code: raise AdminError(_("Failed to rename mailbox: %s" % output)) @@ -879,8 +889,10 @@ class Mailbox(DatesAware): return if not os.path.exists(self.mail_home): return - code, output = exec_cmd("rm -r %s" % self.mail_home, - sudo_user=parameters.get_admin("MAILBOXES_OWNER")) + code, output = exec_cmd( + "rm -r %s" % self.mail_home, + sudo_user=parameters.get_admin("MAILBOXES_OWNER") + ) if code: raise AdminError(_("Failed to remove mailbox: %s" % output)) @@ -960,6 +972,8 @@ class Mailbox(DatesAware): self.delete_dir() super(Mailbox, self).delete() +reversion.register(Mailbox) + @receiver(pre_delete, sender=Mailbox) def mailbox_deleted_handler(sender, **kwargs): @@ -1017,6 +1031,9 @@ class Alias(DatesAware): unique_together = (("address", "domain"),) ordering = ["domain__name", "address"] + def __unicode__(self): + return self.full_address + @property def full_address(self): return "%s@%s" % (self.address, self.domain.name) @@ -1113,7 +1130,7 @@ class Alias(DatesAware): total = 0 if self.extmboxes != "": total += len(self.extmboxes.split(',')) - return total + self.aliases.count() + self.mboxes.count() + return total + self.aliases.count() + self.mboxes.count() def ui_disabled(self, user): if user.is_superuser: @@ -1158,6 +1175,8 @@ class Alias(DatesAware): row += self.get_recipients() csvwriter.writerow(row) +reversion.register(Alias) + class Extension(models.Model): name = models.CharField(max_length=150) @@ -1215,6 +1234,8 @@ class Extension(models.Model): events.raiseEvent("ExtDisabled", self) +reversion.register(Extension) + def populate_callback(user): """Populate callback @@ -1250,3 +1271,27 @@ def populate_callback(user): mb.save(creator=sadmins[0]) for su in sadmins[1:]: grant_access_to_object(su, mb) + + +@receiver(reversion.post_revision_commit) +def post_revision_commit(sender, **kwargs): + import logging + + if kwargs["revision"].user is None: + return + logger = logging.getLogger("modoboa.admin") + for version in kwargs["versions"]: + if version.type == reversion.models.VERSION_ADD: + action = _("added") + level = "info" + elif version.type == reversion.models.VERSION_CHANGE: + action = _("modified") + level = "warning" + else: + action = _("deleted") + level = "critical" + message = _("%s '%s' %s by user %s") % ( + unicode(version.content_type).capitalize(), version.object_repr, action, + kwargs["revision"].user.username + ) + getattr(logger, level)(message) diff --git a/modoboa/admin/static/admin/js/admin.js b/modoboa/admin/static/admin/js/admin.js index 3699c58da..a478fdbe5 100644 --- a/modoboa/admin/static/admin/js/admin.js +++ b/modoboa/admin/static/admin/js/admin.js @@ -1,35 +1,34 @@ var Admin = function(options) { - this.initialize(options); + Listing.call(this, options); }; Admin.prototype = { - constructor: Admin, - defaults: { deflocation: "list/", squery: null }, initialize: function(options) { - this.options = $.extend({}, this.defaults, options); + Listing.prototype.initialize.call(this, options); + this.options = $.extend({}, this.defaults, this.options); this.options.defcallback = $.proxy(this.list_cb, this); this.navobj = new History(this.options); $("#searchquery").focus(function() { - $(this).attr("value", ""); + $(this).val(""); }).blur($.proxy(function(e) { var $this = $(e.target); - if ($this.attr("value") == "") { - if (this.options.squery && this.options.squery == "") { - $this.attr("value", this.options.squery); + if ($this.val() == "") { + if (this.navobj.getparam("searchquery")) { + $this.val(this.navobj.getparam("searchquery")); } else { - $this.attr("value", gettext("Search")); + $this.val(gettext("Search")); } } }, this)); if (this.navobj.getparam("searchquery") != undefined) { - $("#searchquery").attr("value", this.navobj.getparam("searchquery")); + $("#searchquery").val(this.navobj.getparam("searchquery")); } this.listen(); @@ -37,51 +36,16 @@ Admin.prototype = { list_cb: function(data) { $("#listing").html(data.table); - $(".sortable").sortable({ - onSortOrderChange: $.proxy(this.change_sort_order, this) - }); - this.set_sort_order(); - $("#bottom-bar-right").html(data.paginbar); - $("#pagination_bar").find(".disabled a").click(function(e) { - e.preventDefault(); - }); - if (data.page != this.navobj.getparam("page")) { - this.navobj.setparam("page", data.page).update(false, true); - } + this.update_listing(data); }, listen: function() { - $(document).on("click", "#bottom-bar-right a", - $.proxy(this.getpage_loader, this)); $("#searchform").submit($.proxy(this.do_search, this)); }, - set_sort_order: function() { - var sort_order = this.navobj.getparam("sort_order"); - var sort_dir; - - if (!sort_order) { - return; - } - if (sort_order[0] == '-') { - sort_dir = "desc"; - sort_order = sort_order.substr(1); - } else { - sort_dir = 'asc'; - } - $("th[data-sort_order=" + sort_order + "]").sortable('select', sort_dir); - }, - - change_sort_order: function(sort_order, dir) { - if (dir == "desc") { - sort_order = "-" + sort_order; - } - this.navobj.setparam("sort_order", sort_order).update(); - }, - do_search: function(e) { e.preventDefault(); - var squery = $("#searchquery").attr("value"); + var squery = $("#searchquery").val(); if (squery != "") { this.navobj.setparam("searchquery", squery); } else { @@ -90,16 +54,10 @@ Admin.prototype = { this.navobj.update(); }, - getpage_loader: function(e) { - var $link = $(e.target).parent(); - e.preventDefault(); - this.navobj.updateparams($link.attr("href")).update(); - }, - importform_cb: function() { $(".submit").one('click', function(e) { e.preventDefault(); - if ($("#id_sourcefile").attr("value") == "") { + if ($("#id_sourcefile").val() == "") { return; } $("#import_status").css("display", "block"); @@ -136,6 +94,8 @@ Admin.prototype = { } }; +Admin.prototype = $.extend({}, Listing.prototype, Admin.prototype); + /* * Domains */ @@ -169,7 +129,7 @@ Domains.prototype = { create_dom_admin_changed: function(e) { var $target = $(e.target); - this.change_inputs_state(($target.attr("value") == "yes") ? false : true); + this.change_inputs_state(($target.val() == "yes") ? false : true); }, generalform_init: function() { @@ -180,14 +140,14 @@ Domains.prototype = { optionsform_init: function() { $("input[name=create_dom_admin]").click($.proxy(this.create_dom_admin_changed, this)); this.change_inputs_state( - $("input[name=create_dom_admin]:checked").attr("value") == "yes" ? false : true + $("input[name=create_dom_admin]:checked").val() == "yes" ? false : true ); this.optionsform_prefill(); }, optionsform_prefill: function() { var $span = $("#id_dom_admin_username").next("span"); - $span.html("@" + $("#id_name").attr("value")); + $span.html("@" + $("#id_name").val()); }, domadminsform_init: function() { @@ -249,7 +209,6 @@ var Identities = function(options) { Identities.prototype = { initialize: function(options) { Admin.prototype.initialize.call(this, options); - if (this.navobj.getparam("idtfilter") != undefined) { var text = this.navobj.getparam("idtfilter"); $("#searchform").parent().after(this.make_tag(text, "idt")); @@ -364,7 +323,7 @@ Identities.prototype = { generalform_init: function() { $("#id_role").change($.proxy(function(e) { var $this = $(e.target); - var value = $this.attr("value"); + var value = $this.val(); if (value == "SimpleUsers" || value == "") { this.simpleuser_mode(); } else { @@ -402,8 +361,8 @@ Identities.prototype = { mailform_prefill: function() { var $role = $("#id_role"); - if (!$role.length || $role.attr("value") == "" || $role.attr("value") == "SimpleUsers") { - $("#id_email").attr("value", $("#id_username").attr("value")); + if (!$role.length || $role.val() == "" || $role.val() == "SimpleUsers") { + $("#id_email").val($("#id_username").val()); } }, diff --git a/modoboa/admin/templates/admin/header.html b/modoboa/admin/templates/admin/header.html index 8b749dcff..c8883b7ad 100644 --- a/modoboa/admin/templates/admin/header.html +++ b/modoboa/admin/templates/admin/header.html @@ -2,5 +2,6 @@ {% block html_head %} + {% endblock %} diff --git a/modoboa/admin/templates/admin/logs.html b/modoboa/admin/templates/admin/logs.html new file mode 100644 index 000000000..6e3a2487e --- /dev/null +++ b/modoboa/admin/templates/admin/logs.html @@ -0,0 +1,20 @@ +{% load i18n admin_extras %} +

{% trans "Logs" %} {% trans "What happened into Modoboa" %}


+ + + + + + + + + + + {% for l in logs %} + + + + + {% endfor %} + +
{% trans "Date" %}{% trans "Level" %}{% trans "Logger" %}{% trans "Message" %}
{{ l.date_created }}{{ l.level|colorize_level|safe }}{{ l.logger }}{{ l.message|tohtml|safe }}
diff --git a/modoboa/admin/templates/admin/settings_header.html b/modoboa/admin/templates/admin/settings_header.html index 9e68f3e24..9a957a28b 100644 --- a/modoboa/admin/templates/admin/settings_header.html +++ b/modoboa/admin/templates/admin/settings_header.html @@ -3,8 +3,11 @@ {% block pagetitle %}{% trans "Modoboa settings" %}{% endblock %} {% block html_head %} + + + +