New audit trail page. Need to add a cleanup script.

see #320
This commit is contained in:
Antoine Nguyen
2013-05-17 14:15:40 +02:00
parent 3539bc07e2
commit aa141403cb
27 changed files with 408 additions and 152 deletions
+1 -1
View File
@@ -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
+60 -15
View File
@@ -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)
+20 -61
View File
@@ -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());
}
},
@@ -2,5 +2,6 @@
{% block html_head %}
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}admin/css/admin.css" />
<script type="text/javascript" src="{{ STATIC_URL }}js/history.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}js/listing.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}admin/js/admin.js"></script>
{% endblock %}
+20
View File
@@ -0,0 +1,20 @@
{% load i18n admin_extras %}
<h2>{% trans "Logs" %} <small>{% trans "What happened into Modoboa" %}</small></h2><hr>
<table class="table">
<thead>
<tr>
<th class="sortable" data-sort_order="date_created" width="20%">{% trans "Date" %}</th>
<th class="sortable" data-sort_order="level" width="7%">{% trans "Level" %}</th>
<th class="sortable" data-sort_order="logger" width="15%">{% trans "Logger" %}</th>
<th class="sortable" data-sort_order="message" width="">{% trans "Message" %}</th>
</tr>
</thead>
<tbody>
{% for l in logs %}<tr>
<td>{{ l.date_created }}</td>
<td>{{ l.level|colorize_level|safe }}</td>
<td>{{ l.logger }}</td>
<td>{{ l.message|tohtml|safe }}</td>
</tr>{% endfor %}
</tbody>
</table>
@@ -3,8 +3,11 @@
{% block pagetitle %}{% trans "Modoboa settings" %}{% endblock %}
{% block html_head %}
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}admin/css/settings.css" />
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/jquery.sortable.css" />
<script type="text/javascript" src="{{ STATIC_URL }}js/history.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}js/listing.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}js/twocols_nav.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}js/jquery.sortable.js"></script>
<script type="text/javascript">
$(document).ready(function() {
var settings = new TwocolsNav({
@@ -1,3 +1,4 @@
import re
from django import template
from django.template.loader import render_to_string
from django.utils.translation import ugettext as _, ugettext_lazy
@@ -53,6 +54,10 @@ def settings_menu(selection, user):
"class" : "ajaxlink",
"url" : "info/",
"label" : _("Information")},
{"name" : "logs",
"class" : "ajaxlink",
"url" : "logs/",
"label" : _("Logs")},
{"name" : "parameters",
"class" : "ajaxlink",
"url" : "parameters/",
@@ -320,3 +325,20 @@ def gender(value, target):
def get_extra_admin_content(user, target, currentpage):
res = events.raiseQueryEvent("ExtraAdminContent", user, target, currentpage)
return "".join(res)
@register.filter
def colorize_level(level):
classes = {
"INFO": "text-info",
"WARNING": "text-warning",
"CRITICAL": "text-error"
}
if not level in classes:
return level
return "<p class='%s'>%s</p>" % (classes[level], level)
@register.filter
def tohtml(message):
return re.sub("'(.*)'", "<strong>\g<1></strong>", message)
+2
View File
@@ -1,5 +1,6 @@
from django.conf.urls import patterns, url
urlpatterns = patterns(
'modoboa.admin.views',
url(r'^$', 'index', name="index"),
@@ -18,6 +19,7 @@ urlpatterns = patterns(
(r'^settings/extensions/$', 'viewextensions'),
(r'^settings/extensions/save/$', 'saveextensions'),
(r'^settings/info/$', 'information'),
(r'^settings/logs/$', 'logs'),
(r'^permissions/remove/$', 'remove_permission'),
+23 -2
View File
@@ -21,7 +21,7 @@ from modoboa.lib.exceptions import ModoboaException, PermDeniedException
from modoboa.lib.webutils \
import _render, ajax_response, ajax_simple_response, _render_to_string
from modoboa.lib.emailutils import split_mailbox
from modoboa.lib.models import Parameter
from modoboa.lib.models import Parameter, Log
from modoboa.lib.formutils import CreationWizard
from modoboa.lib.templatetags.libextras import pagination_bar
@@ -607,7 +607,7 @@ def viewextensions(request, tplname='admin/extensions.html'):
tbl = ExtensionsTable(request, exts)
return ajax_simple_response({
"status": "ok",
"content": render_to_string(tplname, {"extensions": tbl})
"content": _render_to_string(request, tplname, {"extensions": tbl})
})
@@ -642,6 +642,27 @@ def information(request, tplname="admin/information.html"):
})
@login_required
@user_passes_test(lambda u: u.is_superuser)
def logs(request, tplname="admin/logs.html"):
sort_order, sort_dir = get_sort_order(
request.GET, "date_created",
allowed_values=['date_created', 'level', 'logger', 'message']
)
page = get_listing_page(
Log.objects.all().order_by("%s%s" % (sort_dir, sort_order)),
request.GET.get("page", 1)
)
return ajax_simple_response({
"status": "ok",
"content": render_to_string(tplname, {
"logs": page.object_list,
}),
"page": page.number,
"paginbar": pagination_bar(page),
})
@transaction.commit_on_success
def import_domain(user, row, formopts):
"""Specific code for domains import"""
+19 -15
View File
@@ -1,4 +1,5 @@
# coding: utf-8
import logging
from django.views.decorators.cache import never_cache
from django.contrib.auth import authenticate, login, logout
from django.http import HttpResponse, HttpResponseRedirect
@@ -6,16 +7,16 @@ from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from modoboa.lib import events, parameters
from modoboa.lib.webutils import _render_to_string
from modoboa.lib.sysutils import log_warning
from modoboa.admin import views as admviews
from forms import LoginForm
from modoboa.auth.lib import encrypt
def dologin(request):
error = None
if request.method == "POST":
form = LoginForm(request.POST)
if form.is_valid():
logger = logging.getLogger('modoboa.auth')
user = authenticate(username=form.cleaned_data["username"],
password=form.cleaned_data["password"])
if user and user.is_active:
@@ -28,35 +29,38 @@ def dologin(request):
request.session["django_language"] = \
parameters.get_user(request.user, "LANG", app="general")
logger.info(_("User '%s' successfully logged in" % user.username))
events.raiseEvent("UserLogin", request,
form.cleaned_data["username"],
form.cleaned_data["password"])
next = request.POST["next"]
if next.strip() in ["", "None"]:
next = reverse(admviews.domains)
return HttpResponseRedirect(next)
nextlocation = request.POST["next"]
if nextlocation.strip() in ["", "None"]:
nextlocation = reverse("domains")
return HttpResponseRedirect(nextlocation)
error = _("Your username and password didn't match. Please try again.")
log_warning("Failed connection attempt from %(addr)s as user %(user)s" \
% {"addr" : request.META["REMOTE_ADDR"],
"user" : form.cleaned_data["username"]})
logger.warning("Failed connection attempt from %(addr)s as user %(user)s" \
% {"addr": request.META["REMOTE_ADDR"],
"user": form.cleaned_data["username"]})
next = request.POST.get("next", None)
nextlocation = request.POST.get("next", None)
httpcode = 401
else:
form = LoginForm()
next = request.GET.get("next", None)
nextlocation = request.GET.get("next", None)
httpcode = 200
return HttpResponse(_render_to_string(request, "registration/login.html", {
"form" : form, "error" : error, "next" : next,
"annoucements" : events.raiseQueryEvent("GetAnnouncement", "loginpage")
}), status=httpcode)
"form": form, "error": error, "next": nextlocation,
"annoucements": events.raiseQueryEvent("GetAnnouncement", "loginpage")
}), status=httpcode)
dologin = never_cache(dologin)
def dologout(request):
events.raiseEvent("UserLogout", request)
logger = logging.getLogger("modoboa.auth")
logger.info(_("User '%s' logged out" % request.user.username))
logout(request)
return HttpResponseRedirect(reverse(dologin))
@@ -106,7 +106,7 @@
for (var cpt = 0; cpt < this.options.templates.length; cpt++) {
var tpl = this.options.templates[cpt];
if (tpl.name != $target.attr("value")) {
if (tpl.name != $target.val()) {
continue;
}
this.loadoperators($container, tpl);
@@ -175,7 +175,7 @@
var $this = $(this),
cpt = 1,
start = 0,
value = $this.attr("value");
value = $this.val();
while (true) {
var id = value.indexOf(".", start);
@@ -237,7 +237,7 @@
this.deletePrevArgs(parts[2]);
for (var cpt = 0; cpt < this.options.templates.length; cpt++) {
var tpl = this.options.templates[cpt];
if (tpl.name != $target.attr("value")) {
if (tpl.name != $target.val()) {
continue;
}
$.each(this.loadargs(parts[2], tpl), function(idx, arg) {
@@ -35,7 +35,7 @@ function removefilter(evt) {
*/
function changefs(evt) {
evt.preventDefault();
history.baseurl($(this).attr("value")).update();
history.baseurl($(this).val()).update();
}
/*
@@ -53,7 +53,7 @@ function init_filters_list() {
*/
function loadfs(response) {
curscript = history.getbaseurl();
$("#curfset").attr("value", curscript);
$("#curfset").val(curscript);
$("#set_content").html(response.content);
if ($("#filters_list") != undefined) {
@@ -165,9 +165,9 @@ function activatefs(evt) {
$("#curfset").find("option").each(function(index, element) {
var $element = $(element);
($element.attr("value") == curscript)
($element.val() == curscript)
? $element.html(curscript + " (" + gettext("active") + ")")
: $element.html($element.attr("value"));
: $element.html($element.val());
});
});
}
@@ -214,16 +214,16 @@ function filterset_created(data) {
var curfset = $(parent.document).find("#curfset");
curfset.append(option);
curfset.attr("value", name);
curfset.val(name);
if (data.active) {
curfset.find("option").each(function(index, element) {
var $element = $(element);
if ($element.attr("value") == name) {
if ($element.val() == name) {
$element.html(name + " (" + gettext("active") + ")");
} else {
$element.html($element.attr("value"));
$element.html($element.val());
}
});
}
@@ -26,7 +26,7 @@ function filterform_cb() {
}).data('dynaction');
$("input[name=match_type]").click(function(e) {
if ($(this).attr("value") == "all") {
if ($(this).val() == "all") {
$("div[id*=condition]").hide();
} else {
$("div[id*=condition]").show();
@@ -18,31 +18,31 @@ Stats.prototype = {
this.navobj = navobj;
if (navobj.params.searchquery != undefined) {
$("#searchquery").attr("value", navobj.params.searchquery);
$("#searchquery").val(navobj.params.searchquery);
}
$("#searchquery").focus(function() {
var $this = $(this);
if ($this.attr("value") == undefined) {
if ($this.val() == undefined) {
return;
}
$this.data("oldvalue", $this.attr("value"));
$this.attr("value", "");
$this.data("oldvalue", $this.val());
$this.val("");
}).blur(function() {
var $this = $(this);
if ($this.attr("value") == "") {
if ($this.val() == "") {
if ($this.data("oldvalue")) {
$this.attr("value", $this.data('oldvalue'));
$this.val($this.data('oldvalue'));
$this.data("oldvalue", null);
} else {
$this.attr("value", gettext("Search a domain"));
$this.val(gettext("Search a domain"));
}
}
});
if (navobj.params.start) {
$("#id_from").attr("value", navobj.params.start);
$("#id_from").val(navobj.params.start);
}
if (navobj.params.end) {
$("#id_to").attr("value", navobj.params.end);
$("#id_to").val(navobj.params.end);
}
$("#custom-period input").datepicker({
format: 'yyyy-mm-dd',
@@ -102,13 +102,13 @@ Stats.prototype = {
var $fromdate = $("#id_from");
var $todate = $("#id_to");
if ($fromdate.attr("value") == "" || $todate.attr("value") == "") {
if ($fromdate.val() == "" || $todate.val() == "") {
return;
}
this.navobj.setparams({
period: "custom",
start: $fromdate.attr("value"),
end: $todate.attr("value")
start: $fromdate.val(),
end: $todate.val()
}).update();
}
};
+1 -1
View File
@@ -693,7 +693,7 @@ def send_mail(request, posturl=None):
if parameters.get_admin("SMTP_AUTHENTICATION") == "yes":
try:
s.login(request.user.username, get_password(request))
except smtplib.SMTPAuthenticationError, e:
except smtplib.SMTPException, e:
raise WebmailError(str(e))
s.sendmail(request.user.email, rcpts, msg.as_string())
s.quit()
@@ -904,7 +904,7 @@ Webmail.prototype = {
attachments_init: function() {
$("#submit").click(function(e) {
e.preventDefault();
if ($("#id_attachment").attr("value") == "") {
if ($("#id_attachment").val() == "") {
return;
}
$("#upload_status").css("display", "block");
@@ -931,7 +931,7 @@ Webmail.prototype = {
var $div = $("<div />").append($delbtn, $label);
$delbtn.click(this.del_attachment);
$("#id_attachment").attr("value", "");
$("#id_attachment").val("");
$("#attachment_list").append($div);
},
+11
View File
@@ -0,0 +1,11 @@
import logging
from models import Log
class SQLHandler(logging.Handler):
def emit(self, record):
Log.objects.create(
message=record.getMessage(), level=record.levelname,
logger=record.name
)
@@ -0,0 +1,93 @@
# -*- coding: 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):
# Adding model 'Log'
db.create_table(u'lib_log', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('date_created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
('message', self.gf('django.db.models.fields.CharField')(max_length=255)),
('level', self.gf('django.db.models.fields.CharField')(max_length=15)),
('logger', self.gf('django.db.models.fields.CharField')(max_length=30)),
))
db.send_create_signal(u'lib', ['Log'])
# Changing field 'UserParameter.user'
db.alter_column(u'lib_userparameter', 'user_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['admin.User']))
def backwards(self, orm):
# Deleting model 'Log'
db.delete_table(u'lib_log')
# Changing field 'UserParameter.user'
db.alter_column(u'lib_userparameter', 'user_id', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User']))
models = {
u'admin.user': {
'Meta': {'ordering': "['username']", 'object_name': 'User'},
'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
'email': ('django.db.models.fields.EmailField', [], {'max_length': '254', 'blank': 'True'}),
'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '254'})
},
u'auth.group': {
'Meta': {'object_name': 'Group'},
u'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': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
},
u'auth.permission': {
'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'},
'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
u'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'}),
u'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'})
},
u'lib.log': {
'Meta': {'object_name': 'Log'},
'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'level': ('django.db.models.fields.CharField', [], {'max_length': '15'}),
'logger': ('django.db.models.fields.CharField', [], {'max_length': '30'}),
'message': ('django.db.models.fields.CharField', [], {'max_length': '255'})
},
u'lib.parameter': {
'Meta': {'object_name': 'Parameter'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'value': ('django.db.models.fields.CharField', [], {'max_length': '255'})
},
u'lib.userparameter': {
'Meta': {'object_name': 'UserParameter'},
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['admin.User']"}),
'value': ('django.db.models.fields.CharField', [], {'max_length': '255'})
}
}
complete_apps = ['lib']
+13
View File
@@ -1,3 +1,4 @@
import reversion
from django.db import models
from django.conf import settings
@@ -10,6 +11,11 @@ class Parameter(models.Model):
def shortname(self):
return self.name.split(".")[1].lower()
def __unicode__(self):
return self.name
reversion.register(Parameter)
class UserParameter(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
@@ -19,3 +25,10 @@ class UserParameter(models.Model):
@property
def shortname(self):
return self.name.split(".")[1].lower()
class Log(models.Model):
date_created = models.DateTimeField(auto_now_add=True)
message = models.CharField(max_length=255)
level = models.CharField(max_length=15)
logger = models.CharField(max_length=30)
-8
View File
@@ -18,14 +18,6 @@ def exec_cmd(cmd, sudo_user=None, **kwargs):
output = p.communicate()[0]
return p.returncode, output
def exec_as_vuser(cmd):
code, output = exec_cmd("sudo -u %s %s" \
% (parameters.get_admin("MAILBOXES_OWNER", app="admin"), cmd))
if code:
exec_cmd("echo '%s' >> /tmp/vmail.log" % output)
return False
return True
def __log(msg, facility=logging.handlers.SysLogHandler.LOG_AUTH):
logger = logging.getLogger('modoboa')
logger.setLevel(logging.DEBUG)
+4 -4
View File
@@ -37,7 +37,7 @@
},
check_user_input: function() {
var value = this.$element.attr("value");
var value = this.$element.val();
var start = -1;
var pattern = null;
@@ -90,7 +90,7 @@
} else {
$link = this.$menu.find('.active > a');
}
var curvalue = this.$element.attr("value");
var curvalue = this.$element.val();
if (curvalue == undefined || curvalue == "") {
if (this.options.empty_choice) {
@@ -99,14 +99,14 @@
return;
}
if ($link.length) {
this.$element.attr("value", curvalue.substr(
this.$element.val(curvalue.substr(
0, curvalue.indexOf(this.options.from_character) + 1
) + $link.attr("name"));
}
this.hide_menu();
}
if (this.options.choice_selected) {
this.options.choice_selected(this.$element.attr('value'));
this.options.choice_selected(this.$element.val());
}
},
+5 -5
View File
@@ -41,8 +41,8 @@
var $rmlink = this.addrmlink($ninput);
$ninput.focusout(this.checkempty);
$ninput.attr("value", this.$element.attr("value"));
this.$element.attr("value", this.options.emptylabel);
$ninput.val(this.$element.val());
this.$element.val(this.options.emptylabel);
this.$element.after($ninput, $rmlink);
this.nextid++;
},
@@ -52,7 +52,7 @@
var id = "#" + this.attr("id").replace("_rmbtn", "");
var $input = $(id);
if ($input.attr("value") == "" && (force == undefined || !force)) {
if ($input.val() == "" && (force == undefined || !force)) {
return;
}
$input.remove();
@@ -63,7 +63,7 @@
if (evt.keyCode != 13) {
return;
}
if (this.$element.attr("value") == "") {
if (this.$element.val() == "") {
return;
}
this.addinput();
@@ -72,7 +72,7 @@
checkempty: function(evt) {
var $input = $(evt.target);
if ($input.attr("value") == "") {
if ($input.val() == "") {
$("#" + $input.attr("id") + "_rmbtn").trigger("click", true);
}
}
+62
View File
@@ -0,0 +1,62 @@
var Listing = function(options) {
this.initialize(options);
};
Listing.prototype = {
constructor: Listing,
listing_defaults: {
pbar_container: '#bottom-bar-right',
pbar_id: '#pagination_bar',
sortable_selector: '.sortable'
},
initialize: function(options) {
this.options = $.extend({}, this.listing_defaults, options);
$(document).on("click", this.options.pbar_container + " a",
$.proxy(this.load_page, this));
},
load_page: function(e) {
var $link = get_target(e, "a");
e.preventDefault();
this.navobj.updateparams($link.attr("href")).update();
},
update_listing: function(data) {
$(this.options.pbar_container).html(data.paginbar);
$(this.options.pbar_id).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.options.sortable_selector).sortable({
onSortOrderChange: $.proxy(this.change_sort_order, this)
});
this.set_sort_order();
},
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();
}
};
+5 -5
View File
@@ -16,12 +16,12 @@
});
this.$element.blur(function(e) {
var $this = $(this);
if ($this.attr("value") == "") {
$this.attr("value", gettext("Search..."));
if ($this.val() == "") {
$this.val(gettext("Search..."));
}
});
if (this.options.navobj.params.pattern != undefined) {
this.$element.attr("value", this.options.navobj.getparam("pattern"));
this.$element.val(this.options.navobj.getparam("pattern"));
}
if (this.options.navobj.params.criteria != undefined) {
$("#crit_" + this.options.navobj.params.criteria).attr("checked", "checked");
@@ -30,8 +30,8 @@
pattern_change: function(e) {
var $input = $(e.target);
var criteria = $("input[name=scriteria]:checked").attr("value");
var pattern = $input.attr("value");
var criteria = $("input[name=scriteria]:checked").val();
var pattern = $input.val();
if (pattern != "") {
this.options.navobj.setparams({
+16 -10
View File
@@ -1,10 +1,8 @@
var TwocolsNav = function(options) {
this.initialize(options);
Listing.call(this, options);
};
TwocolsNav.prototype = {
constructor: TwocolsNav,
defaults: {
deflocation: null,
formid: "#form",
@@ -13,14 +11,15 @@ TwocolsNav.prototype = {
},
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.default_cb, this);
this.navobj = new History(this.options);
this.listen();
},
listen: function() {
$("a.ajaxlink").click($.proxy(this.load_page, this));
$("a.ajaxlink").click($.proxy(this.load_section, this));
$(document).on("click", "#update", $.proxy(function(e) {
simple_ajax_form_post(e, {
formid: this.options.formid,
@@ -59,6 +58,7 @@ TwocolsNav.prototype = {
if (data.content) {
$('#' + this.options.divid).html(data.content);
}
this.update_listing(data);
if (data.onload_cb) {
eval(data.onload_cb + '()');
}
@@ -105,10 +105,10 @@ TwocolsNav.prototype = {
var $parent = $target.parents("div.control-group");
$('div[data-visibility-field="' + $target.attr("id") + '"]').each(function(idx) {
instance.toggle_field_visibility($(this), $parent, $target.attr("value"));
instance.toggle_field_visibility($(this), $parent, $target.val());
});
$('h5[data-visibility-field="' + $target.attr("id") + '"]').each(function(idx) {
instance.toggle_field_visibility($(this), $parent, $target.attr("value"));
instance.toggle_field_visibility($(this), $parent, $target.val());
});
},
@@ -120,7 +120,10 @@ TwocolsNav.prototype = {
realid = realid.substr(0, realid.length - 2);
$('div[data-visibility-field="' + realid + '"]').each(function(idx) {
instance.toggle_field_visibility($(this), $parent, $target.attr("value"));
instance.toggle_field_visibility($(this), $parent, $target.val());
});
$('h5[data-visibility-field="' + realid + '"]').each(function(idx) {
instance.toggle_field_visibility($(this), $parent, $target.val());
});
},
@@ -136,15 +139,18 @@ TwocolsNav.prototype = {
$("body").notify("success", data.respmsg, 2000);
},
load_page: function(e) {
load_section: function(e) {
var $link = get_target(e);
e.preventDefault();
this.navobj.baseurl($link.attr("href")).update();
},
default_cb: function(data) {
$("a.ajaxlink").parent().removeClass("active");
$("a[name=" + this.navobj.getbaseurl() + "]").parent().addClass("active");
this.update_content(data);
}
};
};
TwocolsNav.prototype = $.extend({}, Listing.prototype, TwocolsNav.prototype);
@@ -3,6 +3,7 @@
{% block html_head %}
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}userprefs/css/userprefs.css" />
<script type="text/javascript" src="{{ STATIC_URL }}js/history.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}js/listing.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}js/twocols_nav.js"></script>
<script type="text/javascript">
{{ extrajs|safe }}
+1
View File
@@ -1,4 +1,5 @@
django
django-reversion
south
lxml
pycrypto