mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
Merge pull request #1021 from modoboa/feature/move_global_stats_to_dashboard
Move global statistics to admin dashboard.
This commit is contained in:
@@ -300,3 +300,26 @@ def grant_access_to_all_objects(sender, account, role, **kwargs):
|
||||
account, model.objects.all(),
|
||||
ContentType.objects.get_for_model(model)
|
||||
)
|
||||
|
||||
|
||||
@receiver(core_signals.extra_admin_dashboard_widgets)
|
||||
def add_widgets_to_admin_dashboard(sender, user, **kwargs):
|
||||
"""Add admin widgets to dashboard."""
|
||||
if user.is_superuser:
|
||||
context = {
|
||||
"domains_counter": models.Domain.objects.count(),
|
||||
"domain_aliases_counter": models.DomainAlias.objects.count(),
|
||||
"identities_counter": (
|
||||
core_models.User.objects.count() +
|
||||
models.Alias.objects.filter(internal=False).count()),
|
||||
}
|
||||
template = "admin/_global_statistics_widget.html"
|
||||
else:
|
||||
context = {
|
||||
"domains": models.Domain.objects.get_for_admin(user)}
|
||||
template = "admin/_per_domain_statistics_widget.html"
|
||||
return [{
|
||||
"column": "right",
|
||||
"template": template,
|
||||
"context": context
|
||||
}]
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading"><h3 class="panel-title">{% trans "Global statistics" %}</h3></div>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>{% trans "Type" %}</th>
|
||||
<th>{% trans "Quantity" %}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Domains" %}</td><td>{{ domains_counter }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Domain aliases" %}</td><td>{{ domain_aliases_counter }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Identities" %}</td><td>{{ identities_counter }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
@@ -0,0 +1,17 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading"><h3 class="panel-title">{% trans "Per-domain statistics" %}</h3></div>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>{% trans "Domain" %}</th>
|
||||
<th>{% trans "Identities" %}</th>
|
||||
</tr>
|
||||
{% for domain in domains %}
|
||||
<tr>
|
||||
<td><a href="{% url 'admin:domain_detail' domain.pk %}">{{ domain.name }}</a></td>
|
||||
<td>{{ domain.identities_count }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
@@ -1,63 +0,0 @@
|
||||
{% extends "admin/header.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load admin_tags %}
|
||||
|
||||
{% block leftcol %}
|
||||
<ul class="nav nav-sidebar">
|
||||
{% domains_menu "statistics" user False %}
|
||||
</ul>
|
||||
|
||||
{% get_extra_admin_content user "leftcol" selection %}
|
||||
{% endblock %}
|
||||
|
||||
{% block apparea %}
|
||||
<h2>{% trans "Statistics" %} <small>{% trans "Domains and identities" %}</small></h2>
|
||||
<hr>
|
||||
|
||||
<div class="row">
|
||||
{% if user.role == "SuperAdmins" %}
|
||||
<div class="col-xs-12 col-sm-6">
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading"><h3 class="panel-title">{% trans "Global counters" %}</h3></div>
|
||||
<div class="panel-body"></div>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>{% trans "Type" %}</th>
|
||||
<th>{% trans "Quantity" %}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Domains" %}</td><td>{{ domains_counter }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Domain aliases" %}</td><td>{{ domain_aliases_counter }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{% trans "Identities" %}</td><td>{{ identities_counter }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="col-xs-12 col-sm-6">
|
||||
<div class="panel panel-info">
|
||||
<div class="panel-heading"><h3 class="panel-title">{% trans "Per-domain counters" %}</h3></div>
|
||||
<div class="panel-body"></div>
|
||||
<table class="table">
|
||||
<tr>
|
||||
<th>{% trans "Domain" %}</th>
|
||||
<th>{% trans "Identities" %}</th>
|
||||
</tr>
|
||||
{% for domain in domains %}
|
||||
<tr>
|
||||
<td>{{ domain.name }}</td><td>{{ domain.identities_count }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -40,11 +40,6 @@ def domains_menu(selection, user, ajax_mode=True):
|
||||
"img": "fa fa-user",
|
||||
"class": "ajaxnav navigation",
|
||||
"url": domain_list_url},
|
||||
{"name": "statistics",
|
||||
"label": _("Statistics"),
|
||||
"img": "fa fa-line-chart",
|
||||
"class": "navigation",
|
||||
"url": reverse("admin:domain_statistics")}
|
||||
]
|
||||
if user.has_perm("admin.add_domain"):
|
||||
extra_entries = signals.extra_domain_menu_entries.send(
|
||||
|
||||
@@ -203,17 +203,16 @@ class DomainTestCase(ModoTestCase):
|
||||
response = self.client.get(url)
|
||||
self.assertIn("Resources usage", response.content)
|
||||
|
||||
def test_domain_statitics_view(self):
|
||||
"""Test statistics display."""
|
||||
url = reverse("admin:domain_statistics")
|
||||
def test_statitics_widget(self):
|
||||
"""Test statistics display in dashboard."""
|
||||
url = reverse("core:dashboard")
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertIn("Global counters", response.content)
|
||||
self.assertIn("Per-domain counters", response.content)
|
||||
self.assertIn("Global statistics", response.content)
|
||||
|
||||
self.client.force_login(
|
||||
User.objects.get(username="admin@test.com"))
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertNotIn("Global counters", response.content)
|
||||
self.assertIn("Per-domain counters", response.content)
|
||||
self.assertNotIn("Global statistics", response.content)
|
||||
self.assertIn("Per-domain statistics", response.content)
|
||||
|
||||
@@ -18,8 +18,6 @@ urlpatterns = patterns(
|
||||
name="mx_domain_detail"),
|
||||
url(r'^domains/list/$', '_domains', name="_domain_list"),
|
||||
url(r'^domains/flatlist/$', 'domains_list', name="domain_flat_list"),
|
||||
url(r'^domains/stats/$', views.DomainStatisticsView.as_view(),
|
||||
name="domain_statistics"),
|
||||
url(r'^domains/new/', 'newdomain', name='domain_add'),
|
||||
url(r'^domains/(?P<dom_id>\d+)/edit/$', 'editdomain',
|
||||
name="domain_change"),
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
from .alias import AliasDetailView
|
||||
from .identity import AccountDetailView
|
||||
from .dns import DNSBLDomainDetailView, MXDomainDetailView
|
||||
from .domain import DomainDetailView, DomainStatisticsView
|
||||
from .domain import DomainDetailView
|
||||
|
||||
|
||||
__all__ = [
|
||||
@@ -11,6 +11,5 @@ __all__ = [
|
||||
"AliasDetailView",
|
||||
"DNSBLDomainDetailView",
|
||||
"DomainDetailView",
|
||||
"DomainStatisticsView",
|
||||
"MXDomainDetailView",
|
||||
]
|
||||
|
||||
@@ -14,7 +14,6 @@ from django.contrib.auth.decorators import (
|
||||
|
||||
from reversion import revisions as reversion
|
||||
|
||||
from modoboa.core.models import User
|
||||
from modoboa.core import signals as core_signals
|
||||
from modoboa.lib.web_utils import (
|
||||
_render_to_string, render_to_json_response
|
||||
@@ -26,9 +25,7 @@ from modoboa.lib.listing import get_sort_order, get_listing_page
|
||||
|
||||
from ..forms import DomainForm, DomainWizard
|
||||
from ..lib import get_domains
|
||||
from ..models import (
|
||||
Domain, DomainAlias, Mailbox, Alias
|
||||
)
|
||||
from ..models import Domain, Mailbox
|
||||
from .. import signals
|
||||
|
||||
|
||||
@@ -156,29 +153,6 @@ def deldomain(request, dom_id):
|
||||
return render_to_json_response(msg)
|
||||
|
||||
|
||||
class DomainStatisticsView(
|
||||
auth_mixins.PermissionRequiredMixin, generic.TemplateView):
|
||||
"""A simple page to present domain statistics."""
|
||||
|
||||
template_name = "admin/domain_statistics.html"
|
||||
permission_required = "admin.view_domains"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""Add counters."""
|
||||
context = super(DomainStatisticsView, self).get_context_data(**kwargs)
|
||||
if self.request.user.role == "SuperAdmins":
|
||||
context.update({
|
||||
"domains_counter": Domain.objects.count(),
|
||||
"domain_aliases_counter": DomainAlias.objects.count(),
|
||||
"identities_counter": (
|
||||
User.objects.count() +
|
||||
Alias.objects.filter(internal=False).count()),
|
||||
})
|
||||
context.update({
|
||||
"domains": Domain.objects.get_for_admin(self.request.user)})
|
||||
return context
|
||||
|
||||
|
||||
class DomainDetailView(
|
||||
auth_mixins.PermissionRequiredMixin, generic.DetailView):
|
||||
"""DetailView for Domain."""
|
||||
@@ -208,6 +182,7 @@ class DomainDetailView(
|
||||
for widget in widgets:
|
||||
context["templates"][widget["column"]].append(
|
||||
widget["template"])
|
||||
# FIXME: can raise conflicts...
|
||||
context.update(widget["context"])
|
||||
|
||||
return context
|
||||
|
||||
@@ -15,6 +15,8 @@ allow_password_change = django.dispatch.Signal(providing_args=["user"])
|
||||
can_create_object = django.dispatch.Signal(
|
||||
providing_args=["context", "object_type", "count"])
|
||||
extra_account_actions = django.dispatch.Signal(providing_args=["account"])
|
||||
extra_admin_dashboard_widgets = django.dispatch.Signal(
|
||||
providing_args=["user"])
|
||||
extra_admin_menu_entries = django.dispatch.Signal(
|
||||
providing_args=["location", "user"])
|
||||
extra_role_permissions = django.dispatch.Signal(providing_args=["role"])
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
{% load i18n %}
|
||||
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{% trans "Latest news" %}</h3>
|
||||
</div>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
{% for post in news %}
|
||||
<tr>
|
||||
<td><a href="{{ post.link }}" target="_blank">{{ post.title }}</a></td>
|
||||
<td class="text-right">{{ post.published|date:"DATE_FORMAT" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="panel-body text-center">
|
||||
{% blocktrans trimmed %}
|
||||
Visit the <a href="https://modoboa.org/blog/" target="_blank">official weblog</a> for more information.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -10,27 +10,14 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-6">
|
||||
<div class="panel panel-primary">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">{% trans "Latest news" %}</h3>
|
||||
</div>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
{% for post in news %}
|
||||
<tr>
|
||||
<td><a href="{{ post.link }}" target="_blank">{{ post.title }}</a></td>
|
||||
<td class="text-right">{{ post.published|date:"DATE_FORMAT" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="panel-body text-center">
|
||||
{% blocktrans trimmed %}
|
||||
Visit the <a href="https://modoboa.org/blog/" target="_blank">official weblog</a> for more information.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
</div>
|
||||
{% for widget in widgets.left %}
|
||||
{% include widget %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-6">
|
||||
{% for widget in widgets.right %}
|
||||
{% include widget %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ from django.views import generic
|
||||
|
||||
from django.contrib.auth import mixins as auth_mixins
|
||||
|
||||
from .. import signals
|
||||
|
||||
|
||||
class DashboardView(auth_mixins.AccessMixin, generic.TemplateView):
|
||||
"""Dashboard view."""
|
||||
@@ -15,14 +17,17 @@ class DashboardView(auth_mixins.AccessMixin, generic.TemplateView):
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
"""Check if user can access dashboard."""
|
||||
if not request.user.is_admin:
|
||||
if not request.user.is_authenticated() or not request.user.is_admin:
|
||||
return self.handle_no_permission()
|
||||
return super(DashboardView, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""Add context variables."""
|
||||
context = super(DashboardView, self).get_context_data(**kwargs)
|
||||
context.update({"selection": "dashboard"})
|
||||
context.update({
|
||||
"selection": "dashboard", "widgets": {"left": [], "right": []}
|
||||
})
|
||||
# Fetch latest news
|
||||
if self.request.user.language == "fr":
|
||||
lang = "fr"
|
||||
else:
|
||||
@@ -33,5 +38,15 @@ class DashboardView(auth_mixins.AccessMixin, generic.TemplateView):
|
||||
for entry in posts["entries"][:5]:
|
||||
entry["published"] = parser.parse(entry["published"])
|
||||
entries.append(entry)
|
||||
context["widgets"]["left"].append("core/_latest_news_widget.html")
|
||||
context.update({"news": entries})
|
||||
# Extra widgets
|
||||
result = signals.extra_admin_dashboard_widgets.send(
|
||||
sender=self.__class__, user=self.request.user)
|
||||
for receiver, widgets in result:
|
||||
for widget in widgets:
|
||||
context["widgets"][widget["column"]].append(
|
||||
widget["template"])
|
||||
# FIXME: can raise conflicts...
|
||||
context.update(widget["context"])
|
||||
return context
|
||||
|
||||
Reference in New Issue
Block a user