-
- {% include template %}
-
+ {% if object.domainalias_set.exists %}
+
+
+
{% trans "Domain aliases" %}
+
+ {% for domainalias in object.domainalias_set.all %}
+ | {{ domainalias }} |
+ {% endfor %}
+
+
+ {% endif %}
+
+ {% for template in templates.left %}
+ {% include template %}
{% endfor %}
diff --git a/modoboa/admin/templates/admin/identities_table.html b/modoboa/admin/templates/admin/identities_table.html
index 15fad1e00..eb8c96700 100644
--- a/modoboa/admin/templates/admin/identities_table.html
+++ b/modoboa/admin/templates/admin/identities_table.html
@@ -1,8 +1,11 @@
{% load i18n admin_tags lib_tags %}
+
{% for ident in identities %}
{% check_identity_status ident as identity_is_enabled %}
- | {% identity_modify_link ident %}{% if not identity_is_enabled %} (disabled){% endif %} |
+
+ {{ ident }}{% if not identity_is_enabled %} {% trans "(disabled)" %}{% endif %}
+ |
{{ ident.name_or_rcpt }} |
{% render_tags ident.tags %} |
{% identity_actions user ident %} |
diff --git a/modoboa/admin/templatetags/admin_tags.py b/modoboa/admin/templatetags/admin_tags.py
index dc9fdbb67..1f6ced634 100644
--- a/modoboa/admin/templatetags/admin_tags.py
+++ b/modoboa/admin/templatetags/admin_tags.py
@@ -81,24 +81,30 @@ 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
"""
+ if ajax_mode:
+ identity_list_url = "list/"
+ quota_list_url = "quota/"
+ else:
+ identity_list_url = reverse("admin:identity_list")
+ quota_list_url = reverse("admin:quota_list")
entries = [
{"name": "identities",
"label": _("List identities"),
"img": "fa fa-user",
"class": "ajaxnav navigation",
- "url": "list/"},
+ "url": identity_list_url},
{"name": "quotas",
"label": _("List quotas"),
"img": "fa fa-hdd-o",
"class": "ajaxnav navigation",
- "url": "quotas/"},
+ "url": quota_list_url},
{"name": "newaccount",
"label": _("Add account"),
"img": "fa fa-plus",
@@ -185,7 +191,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 +209,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 +238,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.
diff --git a/modoboa/admin/urls.py b/modoboa/admin/urls.py
index fb5ae7376..20a387e78 100644
--- a/modoboa/admin/urls.py
+++ b/modoboa/admin/urls.py
@@ -37,9 +37,11 @@ urlpatterns += patterns(
url(r'^accounts/list/$', 'accounts_list', name="account_list"),
url(r'^accounts/new/$', 'newaccount', name="account_add"),
- url(r'^accounts/edit/(?P\d+)/$', 'editaccount',
+ url(r'^accounts/(?P\d+)/$', views.AccountDetailView.as_view(),
+ name="account_detail"),
+ url(r'^accounts/(?P\d+)/edit/$', 'editaccount',
name="account_change"),
- url(r'^accounts/delete/(?P\d+)/$', 'delaccount',
+ url(r'^accounts/(?P\d+)/delete/$', 'delaccount',
name="account_delete"),
)
@@ -50,7 +52,9 @@ urlpatterns += patterns(
url(r'^forwards/new/$', 'newforward', name="forward_add"),
url(r'^aliases/new/$', 'newalias', name="alias_add"),
- url(r'^aliases/edit/(?P\d+)/$', 'editalias', name="alias_change"),
+ url(r'^aliases/(?P\d+)/$', views.AliasDetailView.as_view(),
+ name="alias_detail"),
+ url(r'^aliases/(?P\d+)/edit/$', 'editalias', name="alias_change"),
url(r'^aliases/delete/$', 'delalias', name="alias_delete"),
)
diff --git a/modoboa/admin/views/__init__.py b/modoboa/admin/views/__init__.py
index 008fb6858..9fa96c833 100644
--- a/modoboa/admin/views/__init__.py
+++ b/modoboa/admin/views/__init__.py
@@ -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",
]
diff --git a/modoboa/admin/views/alias.py b/modoboa/admin/views/alias.py
index 571eada92..c905cde32 100644
--- a/modoboa/admin/views/alias.py
+++ b/modoboa/admin/views/alias.py
@@ -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
@@ -140,3 +143,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
diff --git a/modoboa/admin/views/identity.py b/modoboa/admin/views/identity.py
index 093a25dce..3ad51f76b 100644
--- a/modoboa/admin/views/identity.py
+++ b/modoboa/admin/views/identity.py
@@ -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
@@ -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
diff --git a/modoboa/core/models.py b/modoboa/core/models.py
index a7f782f02..a7bac652e 100644
--- a/modoboa/core/models.py
+++ b/modoboa/core/models.py
@@ -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."""
diff --git a/modoboa/core/password_hashers/advanced.py b/modoboa/core/password_hashers/advanced.py
index 55915e6db..e4c4c54b9 100644
--- a/modoboa/core/password_hashers/advanced.py
+++ b/modoboa/core/password_hashers/advanced.py
@@ -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)
diff --git a/modoboa/core/templatetags/core_tags.py b/modoboa/core/templatetags/core_tags.py
index fa836c41c..b1ef9afc5 100644
--- a/modoboa/core/templatetags/core_tags.py
+++ b/modoboa/core/templatetags/core_tags.py
@@ -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", {
diff --git a/modoboa/limits/general_callbacks.py b/modoboa/limits/general_callbacks.py
index 3d7ab6780..88bc51dd5 100644
--- a/modoboa/limits/general_callbacks.py
+++ b/modoboa/limits/general_callbacks.py
@@ -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 ["""