Merge pull request #896 from tonioo/feature/acount_alias_detail_views_894

Feature: Acount alias detail views
This commit is contained in:
Antoine Nguyen
2016-07-07 19:00:14 +02:00
committed by GitHub
29 changed files with 406 additions and 141 deletions
+5
View File
@@ -1,5 +1,6 @@
"""Models related to aliases management."""
from django.core.urlresolvers import reverse
from django.db import models
from django.utils.encoding import python_2_unicode_compatible, smart_text
from django.utils.translation import ugettext as _, ugettext_lazy
@@ -84,6 +85,10 @@ class Alias(AdminObject):
altype = self.type
return [{"name": altype, "label": labels[altype], "type": "idt"}]
def get_absolute_url(self):
"""Return detail url for this alias."""
return reverse("admin:alias_detail", args=[self.pk])
def post_create(self, creator):
from modoboa.lib.permissions import grant_access_to_object
super(Alias, self).post_create(creator)
+3 -5
View File
@@ -155,13 +155,11 @@ class Mailbox(AdminObject):
:rtype: list of string
"""
aliases = []
qset = (
self.aliasrecipient_set.select_related("alias")
.filter(alias__internal=False)
)
for alr in qset:
aliases += [alr.alias.address]
aliases = [alr.alias.address for alr in qset]
return aliases
@property
@@ -235,8 +233,8 @@ class Mailbox(AdminObject):
self.quota = 0
elif int(value) > self.domain.quota and not override_rules:
raise lib_exceptions.BadRequest(
_("Quota is greater than the allowed domain's limit (%dM)")
% self.domain.quota
_("Quota is greater than the allowed domain's limit (%dM)") %
self.domain.quota
)
else:
self.quota = value
+2
View File
@@ -7,3 +7,5 @@ extra_domain_actions = django.dispatch.Signal(
providing_args=["user", "domain"])
extra_domain_dashboard_widgets = django.dispatch.Signal(
providing_args=["user", "domain"])
extra_account_dashboard_widgets = django.dispatch.Signal(
providing_args=["user", "account"])
@@ -0,0 +1,91 @@
{% extends "admin/header.html" %}
{% load i18n %}
{% load admin_tags %}
{% load lib_tags %}
{% block leftcol %}
<ul class="nav nav-sidebar">
{% identities_menu user "identities" False %}
</ul>
{% get_extra_admin_content user "leftcol" selection %}
{% endblock %}
{% block apparea %}
<h2>{{ object }}{% if not object.is_active %} <small>{% trans "(disabled)" %}</small>{% endif %}</h2>
<hr>
<div class="row">
<div class="col-sm-6">
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">{% trans "Summary" %}</h3>
</div>
<table class="table">
<tr>
<th>{% trans "Full name" %}</th><td>{{ object.fullname }}</td>
</tr>
<tr>
<th>{% trans "Role" %}</th><td>{{ object.role }}</td>
</tr>
<tr>
<th>{% trans "Created" %}</th><td>{{ object.date_joined|date:"SHORT_DATETIME_FORMAT" }}</td>
</tr>
<tr>
<th>{% trans "Last login" %}</th><td>{{ object.last_login|date:"SHORT_DATETIME_FORMAT" }}</td>
</tr>
<tr>
<th>{% trans "Secondary email" %}</th><td>{{ object.secondary_email|default:_("Not defined") }}</td>
</tr>
<tr>
<th>{% trans "Phone number" %}</th><td>{{ object.phone_number|default:_("Not defined") }}</td>
</tr>
{% if object.mailbox %}
<tr>
<th>{% trans "Quota" %}</th>
<td>{% include "common/progressbar.html" with id='quotabar' value=object.mailbox.get_quota_in_percent %}</td>
</tr>
{% endif %}
</table>
</div>
{% if domains %}
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">{% trans "Administered domains" %}</h3>
</div>
<table class="table">
{% for domain in domains %}
<tr><td>{{ domain }}</td></tr>
{% empty %}
<tr><td>{% trans "No domain defined." %}</td></tr>
{% endfor %}
</table>
</div>
{% endif %}
{% for template in templates.left %}
{% include template %}
{% endfor %}
</div>
<div class="col-sm-6">
{% if object.mailbox %}
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">{% trans "Aliases" %}</h3>
</div>
<table class="table">
{% for alias in object.mailbox.alias_addresses %}
<tr><td>{{ alias }}</td></tr>
{% empty %}
<tr><td>{% trans "No alias defined" %}</td></tr>
{% endfor %}
</table>
</div>
{% endif %}
{% for template in templates.right %}
{% include template %}
{% endfor %}
</div>
</div>
{% endblock %}
@@ -0,0 +1,39 @@
{% extends "admin/header.html" %}
{% load i18n %}
{% load admin_tags %}
{% load lib_tags %}
{% block leftcol %}
<ul class="nav nav-sidebar">
{% identities_menu user "identities" False %}
</ul>
{% get_extra_admin_content user "leftcol" selection %}
{% endblock %}
{% block apparea %}
<h2>{% trans "Alias" %} {{ object }}{% if not object.enabled %} <small>{% trans "(disabled)" %}</small>{% endif %}</h2>
<hr>
<div class="row">
<div class="col-sm-6">
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">{% trans "Summary" %}</h3>
</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt>{% trans "Created" %}</dt><dd>{{ alias.creation|date:"SHORT_DATETIME_FORMAT" }}</dd>
<dt>{% trans "Modified" %}</dt><dd>{{ alias.last_modification|date:"SHORT_DATETIME_FORMAT" }}</dd>
</dl>
</div>
<table class="table">
<tr><th>{% trans "Recipients" %}</th></tr>
{% for recipient in object.recipients %}
<tr><td>{{ recipient }}</td></tr>
{% endfor %}
</table>
</div>
</div>
</div>
{% endblock %}
@@ -12,13 +12,13 @@
{% endblock %}
{% block apparea %}
<h2>{{ object }} <small>{% trans "Overview" %}</small></h2>
<h2>{{ object }} {% if not object.enabled %} <small>{% trans "(disabled)" %}</small>{% endif %}</h2>
<hr>
<div class="col-xs-12 col-sm-6">
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">{% trans "Information" %}</h3>
<h3 class="panel-title">{% trans "Summary" %}</h3>
</div>
<div class="panel-body">
<div class="row">
@@ -35,12 +35,21 @@
</div>
</div>
{% for template in templates.left %}
<div class="panel panel-info">
<div class="panel-body">
{% include template %}
</div>
{% if object.domainalias_set.exists %}
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">{% trans "Domain aliases" %}</h3>
</div>
<table class="table">
{% for domainalias in object.domainalias_set.all %}
<tr><td>{{ domainalias }}</td></tr>
{% endfor %}
</table>
</div>
{% endif %}
{% for template in templates.left %}
{% include template %}
{% endfor %}
</div>
@@ -54,7 +63,11 @@
<tr>
<td>{{ da.username }}</td>
<td>{{ da.fullname }}</td>
<td name="actions">{% if perms.admin.change_domain %}{% domadmin_actions da.id domain.id %}{% endif %}</td>
<td name="actions">
{% if perms.admin.change_domain %}
<a name="removeperm" href="{% url 'admin:permission_remove' %}?domid={{ domain.id }}&daid={{ da.id }}" title="{% trans 'Remove this permission' %}"><span class="fa fa-trash"></span></a>
{% endif %}
</td>
</tr>
{% empty %}
<tr><td colspan="3">{% trans "No domain administrator defined yet." %}</td></tr>
@@ -23,4 +23,16 @@ $(document).ready(function() {
{% extra_static_content "identities" "js" user %}
{% endblock %}
{% block toolbar_right %}
<div class="btn-group">
<button type="button" class="btn btn-primary dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="fa fa-plus"></span> {% trans "Add" %} <span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li><a data-toggle="ajaxmodal" modalcb="admin.newaccount_cb" href="{% url 'admin:account_add' %}"><span class="fa fa-user"></span> {% trans "Account" %}</a></li>
<li><a data-toggle="ajaxmodal" modalcb="admin.aliasform_cb" href="{% url 'admin:alias_add' %}">{% trans "Alias" %}</a></li>
</ul>
</div>
{% endblock %}
{% block actions %}{% identities_menu user "identities" %}{% endblock %}
@@ -1,8 +1,11 @@
{% load i18n admin_tags lib_tags %}
{% for ident in identities %}
{% check_identity_status ident as identity_is_enabled %}
<tr id="{{ ident.id }}" class="{% if not identity_is_enabled %}text-muted{% endif %}" name="{{ forloop.counter }}">
<td name="identity">{% identity_modify_link ident %}{% if not identity_is_enabled %} (disabled){% endif %}</td>
<td name="identity">
<a href="{{ ident.get_absolute_url }}">{{ ident }}</a>{% if not identity_is_enabled %} {% trans "(disabled)" %}{% endif %}
</td>
<td name="name_or_rcpt">{{ ident.name_or_rcpt }}</td>
<td name="tags">{% render_tags ident.tags %}</td>
<td name="actions">{% identity_actions user ident %}</td>
+4 -1
View File
@@ -30,7 +30,10 @@ var admin;
<div class="col-sm-5">
{% include "admin/searchform.html" %}
</div>
<div id="taglist" class="col-sm-5"> </div>
<div class="col-sm-3">
{% block toolbar_right %}{% endblock %}
</div>
<div id="taglist" class="col-sm-4"> </div>
</div>
<div id="listing">
+31 -48
View File
@@ -1,6 +1,4 @@
"""
Admin extension tags.
"""
"""Admin extension tags."""
from django import template
from django.core.urlresolvers import reverse
@@ -81,48 +79,32 @@ def domains_menu(selection, user, ajax_mode=True):
@register.simple_tag
def identities_menu(user, selection=None):
def identities_menu(user, selection=None, ajax_mode=True):
"""Menu specific to the Identities page.
:param ``User`` user: the connecter user
:rtype: str
:return: the rendered menu
"""
nav_classes = "navigation"
if ajax_mode:
identity_list_url = "list/"
quota_list_url = "quotas/"
nav_classes += " ajaxnav"
else:
identity_list_url = reverse("admin:identity_list")
quota_list_url = identity_list_url + "#quotas/"
entries = [
{"name": "identities",
"label": _("List identities"),
"img": "fa fa-user",
"class": "ajaxnav navigation",
"url": "list/"},
"class": nav_classes,
"url": identity_list_url},
{"name": "quotas",
"label": _("List quotas"),
"img": "fa fa-hdd-o",
"class": "ajaxnav navigation",
"url": "quotas/"},
{"name": "newaccount",
"label": _("Add account"),
"img": "fa fa-plus",
"modal": True,
"modalcb": "admin.newaccount_cb",
"url": reverse("admin:account_add")},
{"name": "newalias",
"label": _("Add alias"),
"img": "fa fa-plus",
"modal": True,
"modalcb": "admin.aliasform_cb",
"url": reverse("admin:alias_add")},
{"name": "newforward",
"label": _("Add forward"),
"img": "fa fa-plus",
"modal": True,
"modalcb": "admin.aliasform_cb",
"url": reverse("admin:forward_add")},
{"name": "newdlist",
"label": _("Add distribution list"),
"img": "fa fa-plus",
"modal": True,
"modalcb": "admin.aliasform_cb",
"url": reverse("admin:dlist_add")},
"class": nav_classes,
"url": quota_list_url},
{"name": "import",
"label": _("Import"),
"img": "fa fa-folder-open",
@@ -134,8 +116,7 @@ def identities_menu(user, selection=None):
"img": "fa fa-share-alt",
"url": reverse("admin:identity_export"),
"modal": True,
"modalcb": "admin.exportform_cb"
}
"modalcb": "admin.exportform_cb"}
]
return render_to_string('common/menulist.html', {
@@ -185,7 +166,17 @@ def identity_actions(user, ident):
objid = ident.id
if name == "User":
actions = events.raiseQueryEvent("ExtraAccountActions", ident)
url = (
reverse("admin:account_change", args=[objid]) +
"?active_tab=default"
)
actions += [
{"name": "changeaccount",
"url": url,
"img": "fa fa-edit",
"modal": True,
"modalcb": "admin.editaccount_cb",
"title": _("Edit {}").format(ident.username)},
{"name": "delaccount",
"url": reverse("admin:account_delete", args=[objid]),
"img": "fa fa-trash",
@@ -193,6 +184,12 @@ def identity_actions(user, ident):
]
else:
actions = [
{"name": "changealias",
"url": reverse("admin:alias_change", args=[objid]),
"img": "fa fa-edit",
"modal": True,
"modalcb": "admin.aliasform_cb",
"title": _("Edit {}").format(ident)},
{"name": "delalias",
"url": "{}?selection={}".format(
reverse("admin:alias_delete"), objid),
@@ -216,20 +213,6 @@ def check_identity_status(identity):
return True
@register.simple_tag
def domain_modify_link(domain):
linkdef = {"label": domain.name, "modal": True}
if domain.__class__.__name__ == "Domain":
linkdef["url"] = reverse(
"admin:domain_change", args=[domain.id])
linkdef["modalcb"] = "admin.domainform_cb"
else:
tmp = events.raiseDictEvent('GetDomainModifyLink', domain)
for key in ['url', 'modalcb']:
linkdef[key] = tmp[key]
return render_link(linkdef)
@register.simple_tag
def domain_aliases(domain):
"""Display domain aliases of this domain.
+17 -1
View File
@@ -154,6 +154,22 @@ class AccountTestCase(ModoTestCase):
reverse("admin:account_add"), values, status=400
)
def test_account_detail_view(self):
"""Test account detail view."""
parameters.save_admin(
"ENABLE_ADMIN_LIMITS", "no", app="limits")
account = User.objects.get(username="admin@test.com")
url = reverse("admin:account_detail", args=[account.pk])
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertIn("Summary", response.content)
self.assertIn("Administered domains", response.content)
self.assertNotIn("Resources usage", response.content)
parameters.save_admin(
"ENABLE_ADMIN_LIMITS", "yes", app="limits")
response = self.client.get(url)
self.assertIn("Resources usage", response.content)
@override_settings(AUTHENTICATION_BACKENDS=(
'modoboa.lib.authbackends.LDAPBackend',
@@ -305,7 +321,7 @@ class PermissionsTestCase(ModoTestCase):
recipients_1="user@test2.com",
enabled=True
)
self.ajax_post(reverse("admin:dlist_add"), values)
self.ajax_post(reverse("admin:alias_add"), values)
def test_domainadmin_master_user(self):
"""Check domain administrator is not allowed to access this feature."""
+22 -9
View File
@@ -55,7 +55,7 @@ class AliasTestCase(ModoTestCase):
"enabled": True
}
self.ajax_post(
reverse("admin:dlist_add"), values
reverse("admin:alias_add"), values
)
alias = Alias.objects.get(address="badalias@test.com")
self.assertEqual(alias.recipients_count, 1)
@@ -132,7 +132,7 @@ class AliasTestCase(ModoTestCase):
recipients_2="ext@titi.com",
enabled=True)
self.ajax_post(
reverse("admin:dlist_add"), values
reverse("admin:alias_add"), values
)
user = User.objects.get(username="user@test.com")
mb = user.mailbox
@@ -164,7 +164,7 @@ class AliasTestCase(ModoTestCase):
def test_forward(self):
values = dict(address="forward2@test.com", recipients="rcpt@dest.com")
self.ajax_post(
reverse("admin:forward_add"), values
reverse("admin:alias_add"), values
)
fwd = Alias.objects.get(address="forward2@test.com")
self.assertEqual(fwd.recipients_count, 1)
@@ -187,7 +187,7 @@ class AliasTestCase(ModoTestCase):
def test_forward_and_local_copies(self):
values = dict(address="user@test.com", recipients="rcpt@dest.com")
self.ajax_post(
reverse("admin:forward_add"), values
reverse("admin:alias_add"), values
)
fwd = Alias.objects.get(address="user@test.com", internal=False)
self.assertEqual(fwd.recipients_count, 1)
@@ -211,18 +211,21 @@ class AliasTestCase(ModoTestCase):
"enabled": True
}
self.ajax_post(
reverse("admin:dlist_add"), values
reverse("admin:alias_add"), values
)
def test_distribution_list_deletion_on_user_update_bug(self):
"""This test demonstrates an issue with distribution list being deleted
when one of the users which belong to that list is changed."""
"""This test demonstrates an issue with distribution list being
deleted when one of the users which belong to that list is
changed.
"""
values = dict(address="list@test.com",
recipients="user@test.com",
recipients_1="admin@test.com",
enabled=True)
self.ajax_post(
reverse("admin:dlist_add"), values
reverse("admin:alias_add"), values
)
user = User.objects.get(username="user@test.com")
@@ -233,4 +236,14 @@ class AliasTestCase(ModoTestCase):
)
self.ajax_post(reverse("admin:account_change", args=[user.id]), values)
self.assertEqual(Alias.objects.filter(address="list@test.com").count(), 1)
self.assertEqual(
Alias.objects.filter(address="list@test.com").count(), 1)
def test_alias_detail_view(self):
"""Test alias detail view."""
account = Alias.objects.get(address="postmaster@test.com")
url = reverse("admin:alias_detail", args=[account.pk])
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertIn("Summary", response.content)
self.assertIn("Recipients", response.content)
+1 -1
View File
@@ -155,7 +155,7 @@ class DomainTestCase(ModoTestCase):
url = reverse("admin:domain_detail", args=[domain.pk])
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertIn("Information", response.content)
self.assertIn("Summary", response.content)
self.assertIn("Administrators", response.content)
self.assertNotIn("Resources usage", response.content)
parameters.save_admin(
+7 -6
View File
@@ -37,20 +37,21 @@ urlpatterns += patterns(
url(r'^accounts/list/$', 'accounts_list', name="account_list"),
url(r'^accounts/new/$', 'newaccount', name="account_add"),
url(r'^accounts/edit/(?P<accountid>\d+)/$', 'editaccount',
url(r'^accounts/(?P<pk>\d+)/$', views.AccountDetailView.as_view(),
name="account_detail"),
url(r'^accounts/(?P<pk>\d+)/edit/$', 'editaccount',
name="account_change"),
url(r'^accounts/delete/(?P<accountid>\d+)/$', 'delaccount',
url(r'^accounts/(?P<pk>\d+)/delete/$', 'delaccount',
name="account_delete"),
)
urlpatterns += patterns(
'modoboa.admin.views.alias',
url(r'^distriblists/new/$', 'newdlist', name="dlist_add"),
url(r'^forwards/new/$', 'newforward', name="forward_add"),
url(r'^aliases/new/$', 'newalias', name="alias_add"),
url(r'^aliases/edit/(?P<alid>\d+)/$', 'editalias', name="alias_change"),
url(r'^aliases/(?P<pk>\d+)/$', views.AliasDetailView.as_view(),
name="alias_detail"),
url(r'^aliases/(?P<alid>\d+)/edit/$', 'editalias', name="alias_change"),
url(r'^aliases/delete/$', 'delalias', name="alias_delete"),
)
+4
View File
@@ -1,10 +1,14 @@
"""Shortcuts."""
from .alias import AliasDetailView
from .identity import AccountDetailView
from .dnsbl import DNSBLDomainDetailView
from .domain import DomainDetailView
__all__ = [
"AccountDetailView",
"AliasDetailView",
"DNSBLDomainDetailView",
"DomainDetailView",
]
+27 -24
View File
@@ -1,12 +1,15 @@
"""Alias related views."""
from django.contrib.auth.decorators import (
login_required, permission_required
)
from django.core.urlresolvers import reverse
from django.db import IntegrityError
from django.shortcuts import render
from django.utils.translation import ugettext as _, ungettext
from django.views import generic
from django.contrib.auth import mixins as auth_mixins
from django.contrib.auth.decorators import (
login_required, permission_required
)
from reversion import revisions as reversion
@@ -59,17 +62,6 @@ def _new_alias(request, title, action, successmsg,
return render(request, tplname, ctx)
@login_required
@permission_required("admin.add_alias")
@reversion.create_revision()
def newdlist(request):
return _new_alias(
request, _("New distribution list"),
reverse("admin:dlist_add"),
_("Distribution list created")
)
@login_required
@permission_required("admin.add_alias")
@reversion.create_revision()
@@ -80,16 +72,6 @@ def newalias(request):
)
@login_required
@permission_required("admin.add_alias")
@reversion.create_revision()
def newforward(request):
return _new_alias(
request, _("New forward"), reverse("admin:forward_add"),
_("Forward created")
)
@login_required
@permission_required("admin.change_alias")
@reversion.create_revision()
@@ -140,3 +122,24 @@ def delalias(request):
msg = ungettext(msg, msgs, len(selection))
return render_to_json_response(msg)
class AliasDetailView(
auth_mixins.PermissionRequiredMixin, generic.DetailView):
"""DetailView for Alias."""
model = Alias
permission_required = "admin.add_alias"
def has_permission(self):
"""Check object-level access."""
result = super(AliasDetailView, self).has_permission()
if not result:
return result
return self.request.user.can_access(self.get_object())
def get_context_data(self, **kwargs):
"""Add information to context."""
context = super(AliasDetailView, self).get_context_data(**kwargs)
context["selection"] = "identities"
return context
+44 -7
View File
@@ -1,11 +1,14 @@
"""Identity related views."""
from django.shortcuts import render
from django.utils.translation import ugettext as _, ungettext
from django.views import generic
from django.views.decorators.csrf import ensure_csrf_cookie
from django.contrib.auth import mixins as auth_mixins
from django.contrib.auth.decorators import (
login_required, permission_required, user_passes_test
)
from django.shortcuts import render
from django.utils.translation import ugettext as _, ungettext
from django.views.decorators.csrf import ensure_csrf_cookie
from reversion import revisions as reversion
@@ -24,6 +27,7 @@ from modoboa.lib.web_utils import (
from ..forms import AccountForm, AccountWizard
from ..lib import get_identities
from ..models import Mailbox, Domain
from .. import signals
@login_required
@@ -170,8 +174,8 @@ def newaccount(request):
@login_required
@permission_required("core.change_user")
@reversion.create_revision()
def editaccount(request, accountid, tplname="common/tabforms.html"):
account = User.objects.get(pk=accountid)
def editaccount(request, pk, tplname="common/tabforms.html"):
account = User.objects.get(pk=pk)
if not request.user.can_access(account):
raise PermDeniedException
mb = account.mailbox if hasattr(account, "mailbox") else None
@@ -183,9 +187,9 @@ def editaccount(request, accountid, tplname="common/tabforms.html"):
@login_required
@permission_required("core.delete_user")
def delaccount(request, accountid):
def delaccount(request, pk):
keepdir = True if request.POST.get("keepdir", "false") == "true" else False
User.objects.get(pk=accountid).delete(request.user, keep_mb_dir=keepdir)
User.objects.get(pk=pk).delete(request.user, keep_mb_dir=keepdir)
return render_to_json_response(
ungettext("Account deleted", "Accounts deleted", 1)
)
@@ -208,3 +212,36 @@ def remove_permission(request):
raise PermDeniedException
domain.remove_admin(account)
return render_to_json_response({})
class AccountDetailView(
auth_mixins.PermissionRequiredMixin, generic.DetailView):
"""DetailView for Account."""
model = User
permission_required = "core.add_user"
template_name = "admin/account_detail.html"
def has_permission(self):
"""Check object-level access."""
result = super(AccountDetailView, self).has_permission()
if not result:
return result
return self.request.user.can_access(self.get_object())
def get_context_data(self, **kwargs):
"""Add information to context."""
context = super(AccountDetailView, self).get_context_data(**kwargs)
del context["user"]
result = signals.extra_account_dashboard_widgets.send(
self.__class__, user=self.request.user, account=self.object)
context["templates"] = {"left": [], "right": []}
for receiver, widgets in result:
for widget in widgets:
context["templates"][widget["column"]].append(
widget["template"])
context.update(widget["context"])
if self.object.role in ["Resellers", "DomainAdmins"]:
context["domains"] = Domain.objects.get_for_admin(self.object)
context["selection"] = "identities"
return context
+21 -10
View File
@@ -2,16 +2,18 @@
import re
from django.core.urlresolvers import reverse
from django.db import models
from django.utils import timezone
from django.utils.encoding import python_2_unicode_compatible, smart_text
from django.utils.translation import ugettext as _, ugettext_lazy
from django.contrib.auth.hashers import make_password, is_password_usable
from django.contrib.auth.models import (
UserManager, Group, PermissionsMixin
)
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.utils import timezone
from django.utils.encoding import python_2_unicode_compatible, smart_text
from django.utils.translation import ugettext as _, ugettext_lazy
import jsonfield
from reversion import revisions as reversion
@@ -208,6 +210,10 @@ class User(PermissionsMixin):
def has_usable_password(self):
return is_password_usable(self.password)
def get_absolute_url(self):
"""Return detail url for this user."""
return reverse("admin:account_detail", args=[self.pk])
@property
def tags(self):
return [{"name": "account", "label": _("account"), "type": "idt"},
@@ -314,12 +320,16 @@ class User(PermissionsMixin):
@property
def role(self):
"""Return user role."""
if self.is_superuser:
return "SuperAdmins"
try:
return self.groups.all()[0].name
except IndexError:
return "---"
if not hasattr(self, "_role"):
self._role = None
if self._role is None:
if self.is_superuser:
self._role = "SuperAdmins"
try:
self._role = self.groups.all()[0].name
except IndexError:
self._role = "---"
return self._role
@role.setter
def role(self, role):
@@ -345,6 +355,7 @@ class User(PermissionsMixin):
from modoboa.lib.permissions import grant_access_to_object
grant_access_to_object(self, self)
self.save()
self._role = role
def post_create(self, creator):
"""Grant permission on this user to creator."""
+2 -1
View File
@@ -7,6 +7,7 @@ from passlib.hash import bcrypt, md5_crypt, sha256_crypt, sha512_crypt
from modoboa.core.password_hashers.base import PasswordHasher
from modoboa.lib import parameters
class BLFCRYPTHasher(PasswordHasher):
"""
BLF-CRYPT password hasher.
@@ -25,7 +26,7 @@ class BLFCRYPTHasher(PasswordHasher):
# Using the parameter for rounds will generate the error
# ValueError: rounds too high (bcrypt requires <= 31 rounds)
# when using the bcrypt hasher.
#rounds = int(parameters.get_admin('ROUNDS_NUMBER'))
# rounds = int(parameters.get_admin('ROUNDS_NUMBER'))
# To get around this, I use the default of 12.
rounds = 12
return bcrypt.encrypt(clearvalue, rounds=rounds)
-1
View File
@@ -26,7 +26,6 @@ def core_menu(selection, user):
"label": _("Modoboa"),
"url": reverse("core:index")}
]
if not len(entries):
return ""
return render_to_string("common/menulist.html", {
+7 -7
View File
@@ -1,5 +1,6 @@
"""General event callbacks."""
from django.template.loader import render_to_string
from django.utils.translation import ugettext as _
from modoboa.lib import events, parameters
@@ -10,8 +11,6 @@ from . import utils
@events.observe("ExtraAdminContent")
def display_pool_usage(user, target, currentpage):
from django.template.loader import render_to_string
condition = (
parameters.get_admin("ENABLE_ADMIN_LIMITS") == "no" or
target != "leftcol" or user.is_superuser)
@@ -103,11 +102,12 @@ def fill_account_instances(user, account, instances):
@events.observe("GetStaticContent")
def get_static_content(caller, st_type, user):
if parameters.get_admin("ENABLE_ADMIN_LIMITS") == "no":
return []
if caller not in ['domains', 'identities']:
return []
if user.group == "SimpleUsers":
condition = (
parameters.get_admin("ENABLE_ADMIN_LIMITS") == "no" or
caller not in ["domains", "identities"] or
user.role in ["SuperAdmins", "SimpleUsers"]
)
if condition:
return []
if st_type == "css":
return ["""<style>
+20 -1
View File
@@ -76,8 +76,27 @@ def display_domain_limits(sender, user, domain, **kwargs):
return []
return [{
"column": "right",
"template": "limits/domain_resources_widget.html",
"template": "limits/resources_widget.html",
"context": {
"limits": domain.domainobjectlimit_set.all()
}
}]
@receiver(admin_signals.extra_account_dashboard_widgets)
def display_admin_limits(sender, user, account, **kwargs):
"""Display resources usage for admin."""
condition = (
parameters.get_admin("ENABLE_ADMIN_LIMITS") == "yes" and
account.role in ["DomainAdmins", "Resellers"]
)
if not condition:
return []
return [{
"column": "right",
"template": "limits/resources_widget.html",
"context": {
"limits": (
account.userobjectlimit_set.select_related("content_type"))
}
}]
+2 -1
View File
@@ -5,6 +5,7 @@
from django.conf import settings
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
from django.utils.functional import cached_property
from django.utils.translation import ugettext as _
from modoboa.core import models as core_models
@@ -79,7 +80,7 @@ class UserObjectLimit(ObjectLimitMixin, models.Model):
"""Return display name."""
return self.definition["label"]
@property
@cached_property
def usage(self):
"""Return current limit usage in %."""
if self.max_value < 0:
+2
View File
@@ -48,6 +48,8 @@ class ResourceTestCase(lib_tests.ModoTestCase):
def setUpTestData(cls):
"""Custom setUpTestData method."""
super(ResourceTestCase, cls).setUpTestData()
parameters.save_admin("ENABLE_ADMIN_LIMITS", "yes")
parameters.save_admin("ENABLE_DOMAIN_LIMITS", "no")
for name, tpl in utils.get_user_limit_templates():
parameters.save_admin(
"DEFLT_USER_{0}_LIMIT".format(name.upper()), 2,
+9 -7
View File
@@ -2,6 +2,8 @@
* Global javascript utilities
*/
/* global $ */
var static_url = "";
/*
@@ -180,7 +182,7 @@ function simple_ajax_form_post(e, options) {
return;
}
if (opts.reload_on_success) {
if (opts.reload_mode == 'full') {
if (opts.reload_mode === 'full') {
window.location.reload();
} else {
histomanager.update(true);
@@ -230,13 +232,13 @@ function gethref(obj) {
* http://stackoverflow.com/questions/901115/get-query-string-values-in-javascript
*/
function parse_qs(raw) {
if (raw === "") return {};
if (raw === "") { return {}; }
var res = {};
for (var i = 0; i < raw.length; i++) {
var p = raw[i].split('=');
if (p.length != 2) continue;
if (p.length !== 2) { continue; }
res[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " "));
}
return res;
@@ -289,12 +291,12 @@ function simple_ajax_request(e, uoptions) {
var defaults = {};
var options = $.extend({}, defaults, uoptions);
if (e !== undefined) e.preventDefault();
if (e !== undefined) { e.preventDefault(); }
$.ajax({
url: $this.attr("href"),
dataType: 'json'
}).done(function(data) {
if (options.ok_cb) options.ok_cb(data);
if (options.ok_cb) { options.ok_cb(data); }
if (data) {
$("body").notify("success", data, 2000);
}
@@ -306,7 +308,7 @@ function simple_ajax_request(e, uoptions) {
* the status code received with a response is equal to 278.
*/
function ajax_login_redirect(xhr) {
if (xhr.status != 278) {
if (xhr.status !== 278) {
return;
}
var params = "?next=" + window.location.pathname;
@@ -359,5 +361,5 @@ $(document).ready(function() {
$(document).on('click', 'a[data-toggle="ajaxmodal"]', modalbox);
$(document).on('click', 'a[data-toggle="ajaxmodal-autowidth"]', modalbox_autowidth);
$(document).on('click', '.activator', activate_widget);
$(".help").popover({trigger: 'hover'}).click(function(e) {e.preventDefault();});
$(".help").popover({trigger: 'hover'}).click(function(e) { e.preventDefault(); });
});
+2 -2
View File
@@ -254,10 +254,10 @@ History.prototype = {
check: function() {
var rawhash = this.get_raw_hash();
if (this.serialized == rawhash && this.force === undefined) {
if (this.serialized === rawhash && this.force === undefined) {
return;
}
delete(this.force);
delete this.force;
this.from_string(rawhash);
if (!this.serialized) {
@@ -0,0 +1,5 @@
{% load lib_tags %}
<div id="{{ id }}" rel="tooltip" title="{{ value }}%" class="progress">
<div class="{% progress_color value %}" style="width: {{ value }%"></div>
</div>
+2
View File
@@ -1,5 +1,7 @@
{% extends "common/generic_modal.html" %}
{% load i18n form_tags %}
{% block modalbody %}
<form id="{{ formid }}" class="form-horizontal" action="{{ action }}" method="POST">{% csrf_token %}
<ul class="nav nav-pills" role="tablist">