From 9349b7a90a479bda56b4d76965d668a37e4c4f59 Mon Sep 17 00:00:00 2001 From: Anthony Bourguignon Date: Fri, 6 Oct 2017 17:11:55 +0200 Subject: [PATCH] Quota (#1262) * Add domain quota listing See #1135 * Add permission on domains quotas listing * Always send context headers even if domains or identities are empty * rename identities quotas view in the tests * Added unit test for domain quota list --- modoboa/admin/models/domain.py | 18 ++++++++++ modoboa/admin/static/admin/js/admin.js | 8 +++++ .../admin/templates/admin/domain_headers.html | 10 ++++++ modoboa/admin/templates/admin/domains.html | 10 ------ .../admin/domains_quota_headers.html | 6 ++++ .../admin/templates/admin/domains_quotas.html | 16 +++++++++ ...ers.html => identities_quota_headers.html} | 0 .../{quotas.html => identities_quotas.html} | 0 modoboa/admin/templatetags/admin_tags.py | 16 +++++++-- modoboa/admin/tests/test_account.py | 2 +- modoboa/admin/tests/test_domain.py | 8 +++++ modoboa/admin/urls.py | 4 ++- modoboa/admin/views/domain.py | 33 ++++++++++++++++++- modoboa/admin/views/identity.py | 8 ++--- 14 files changed, 119 insertions(+), 20 deletions(-) create mode 100644 modoboa/admin/templates/admin/domain_headers.html create mode 100644 modoboa/admin/templates/admin/domains_quota_headers.html create mode 100644 modoboa/admin/templates/admin/domains_quotas.html rename modoboa/admin/templates/admin/{quota_headers.html => identities_quota_headers.html} (100%) rename modoboa/admin/templates/admin/{quotas.html => identities_quotas.html} (100%) diff --git a/modoboa/admin/models/domain.py b/modoboa/admin/models/domain.py index 96e84978b..c5579c1a3 100644 --- a/modoboa/admin/models/domain.py +++ b/modoboa/admin/models/domain.py @@ -179,6 +179,24 @@ class Domain(AdminObject): return 0 return int(self.allocated_quota / float(self.quota) * 100) + @cached_property + def used_quota(self): + """Return current quota usage.""" + from .mailbox import Quota + + if not self.quota: + return 0 + if not self.mailbox_set.exists(): + return 0 + return int(Quota.objects.get_domain_usage(self) / 1048576) + + @cached_property + def used_quota_in_percent(self): + """Return used quota in percent.""" + if not self.allocated_quota: + return 0 + return int(self.used_quota / float(self.quota) * 100) + def add_admin(self, account): """Add a new administrator to this domain. diff --git a/modoboa/admin/static/admin/js/admin.js b/modoboa/admin/static/admin/js/admin.js index f3cd6cf26..c74081545 100644 --- a/modoboa/admin/static/admin/js/admin.js +++ b/modoboa/admin/static/admin/js/admin.js @@ -166,6 +166,14 @@ Domains.prototype = { * @param {Object} data - response of the ajax call (JSON) */ list_cb: function(data) { + if ($(".sidebar li.active").length === 0) { + var menu = this.navobj.getbaseurl(); + if (menu === "list") { + menu = "domains"; + } + $("a[name={0}]".format(menu)).parent().addClass("active"); + } + $("#objects_table thead tr").html(data.headers); Admin.prototype.list_cb.call(this, data); this.init_domain_links(data); }, diff --git a/modoboa/admin/templates/admin/domain_headers.html b/modoboa/admin/templates/admin/domain_headers.html new file mode 100644 index 000000000..e17749a05 --- /dev/null +++ b/modoboa/admin/templates/admin/domain_headers.html @@ -0,0 +1,10 @@ +{% load i18n %} +{% block table_header %} + {% trans "Name" %} + {% trans "Alias(es)" %} + {% trans "Tags" %} + {% if enable_mx_checks or enable_dnsbl_checks %} + {% trans "DNS status" %} + {% endif %} + {% trans "Actions" %} +{% endblock %} diff --git a/modoboa/admin/templates/admin/domains.html b/modoboa/admin/templates/admin/domains.html index f9e0a6b4e..2493a6caf 100644 --- a/modoboa/admin/templates/admin/domains.html +++ b/modoboa/admin/templates/admin/domains.html @@ -29,13 +29,3 @@ $(document).ready(function() { {% endblock %} {% block actions %}{% domains_menu "domains" user %}{% endblock %} - -{% block table_header %} - {% trans "Name" %} - {% trans "Alias(es)" %} - {% trans "Tags" %} - {% if enable_mx_checks or enable_dnsbl_checks %} - {% trans "DNS status" %} - {% endif %} - {% trans "Actions" %} -{% endblock %} diff --git a/modoboa/admin/templates/admin/domains_quota_headers.html b/modoboa/admin/templates/admin/domains_quota_headers.html new file mode 100644 index 000000000..df254a7a0 --- /dev/null +++ b/modoboa/admin/templates/admin/domains_quota_headers.html @@ -0,0 +1,6 @@ +{% load i18n %} +{% trans "Domain" %} +{% trans "Limit" %} +{% trans "Used" %} +{% trans "Allocated" %} +{% trans "Usage (%)" %} diff --git a/modoboa/admin/templates/admin/domains_quotas.html b/modoboa/admin/templates/admin/domains_quotas.html new file mode 100644 index 000000000..126039b7a --- /dev/null +++ b/modoboa/admin/templates/admin/domains_quotas.html @@ -0,0 +1,16 @@ +{% load lib_tags i18n admin_tags %} + +{% for domain in domains %} + + {{ domain }} + {{ domain.quota }}M + {{ domain.used_quota }}M + {{ domain.allocated_quota }}M + +
+
+
+
+ + +{% endfor %} diff --git a/modoboa/admin/templates/admin/quota_headers.html b/modoboa/admin/templates/admin/identities_quota_headers.html similarity index 100% rename from modoboa/admin/templates/admin/quota_headers.html rename to modoboa/admin/templates/admin/identities_quota_headers.html diff --git a/modoboa/admin/templates/admin/quotas.html b/modoboa/admin/templates/admin/identities_quotas.html similarity index 100% rename from modoboa/admin/templates/admin/quotas.html rename to modoboa/admin/templates/admin/identities_quotas.html diff --git a/modoboa/admin/templatetags/admin_tags.py b/modoboa/admin/templatetags/admin_tags.py index 60285fa0a..2b566db17 100644 --- a/modoboa/admin/templatetags/admin_tags.py +++ b/modoboa/admin/templatetags/admin_tags.py @@ -35,15 +35,25 @@ def domains_menu(selection, user, ajax_mode=True): :rtype: str :return: rendered menu (as HTML) """ - domain_list_url = ( - "list/" if ajax_mode else reverse("admin:domain_list") - ) + nav_classes = "navigation" + if ajax_mode: + domain_list_url = "list/" + quota_list_url = "quotas/" + nav_classes += " ajaxnav" + else: + domain_list_url = reverse("admin:domain_list") + quota_list_url = domain_list_url + "#quotas/" entries = [ {"name": "domains", "label": _("List domains"), "img": "fa fa-user", "class": "ajaxnav navigation", "url": domain_list_url}, + {"name": "quotas", + "label": _("List quotas"), + "img": "fa fa-hdd-o", + "class": "ajaxnav navigation", + "url": quota_list_url}, ] if user.has_perm("admin.add_domain"): extra_entries = signals.extra_domain_menu_entries.send( diff --git a/modoboa/admin/tests/test_account.py b/modoboa/admin/tests/test_account.py index a2cd9b300..acf1baf9d 100644 --- a/modoboa/admin/tests/test_account.py +++ b/modoboa/admin/tests/test_account.py @@ -418,7 +418,7 @@ class AccountTestCase(ModoTestCase): """Test quota list view.""" models.Quota.objects.filter(username="user@test.com").update( bytes=5 * 1048576) - url = reverse("admin:quota_list") + url = reverse("admin:identity_quota_list") response = self.ajax_get(url) self.assertIn("5M", response["rows"]) self.assertIn('title="50%"', response["rows"]) diff --git a/modoboa/admin/tests/test_domain.py b/modoboa/admin/tests/test_domain.py index 19e7893d0..eaee09efa 100644 --- a/modoboa/admin/tests/test_domain.py +++ b/modoboa/admin/tests/test_domain.py @@ -308,6 +308,14 @@ class DomainTestCase(ModoTestCase): self.assertEqual(response.status_code, 200) self.assertIn("Usage", response.content.decode()) + def test_domain_quota_list_view(self): + """Test quota list view.""" + url = reverse("admin:domain_quota_list") + response = self.ajax_get(url) + self.assertIn("50M", response["rows"]) + self.assertIn('title="40% (20 MB) allocated"', response["rows"]) + self.assertIn("test.com", response["rows"]) + def test_statitics_widget(self): """Test statistics display in dashboard.""" url = reverse("core:dashboard") diff --git a/modoboa/admin/urls.py b/modoboa/admin/urls.py index 63958d202..eff6fc2bb 100644 --- a/modoboa/admin/urls.py +++ b/modoboa/admin/urls.py @@ -23,6 +23,8 @@ urlpatterns = [ url(r'^domains/(?P\d+)/mx/$', views.MXDomainDetailView.as_view(), name="mx_domain_detail"), url(r'^domains/list/$', domain_views._domains, name="_domain_list"), + url(r'^domains/quotas/$', domain_views.list_quotas, + name="domain_quota_list"), url(r'^domains/flatlist/$', domain_views.domains_list, name="domain_flat_list"), url(r'^domains/new/', domain_views.newdomain, name='domain_add'), @@ -40,7 +42,7 @@ urlpatterns += [ url(r'^identities/list/$', identity_views._identities, name="_identity_list"), url(r'^identities/quotas/$', identity_views.list_quotas, - name="quota_list"), + name="identity_quota_list"), url(r'^identities/page/$', identity_views.get_next_page, name="identity_page"), diff --git a/modoboa/admin/views/domain.py b/modoboa/admin/views/domain.py index 7f0eb12b9..159cbfe8e 100644 --- a/modoboa/admin/views/domain.py +++ b/modoboa/admin/views/domain.py @@ -70,10 +70,17 @@ def _domains(request): "auto_account_removal"), } page = get_listing_page(domainlist, request.GET.get("page", 1)) + parameters = request.localconfig.parameters + context["headers"] = render_to_string( + "admin/domain_headers.html", { + "enable_mx_checks": parameters.get_value("enable_mx_checks"), + "enable_dnsbl_checks": ( + parameters.get_value("enable_dnsbl_checks")) + }, request + ) if page is None: context["length"] = 0 else: - parameters = request.localconfig.parameters context["rows"] = render_to_string( "admin/domains_table.html", { "domains": page.object_list, @@ -110,6 +117,30 @@ def domains_list(request): return render_to_json_response(doms) +@login_required +@permission_required("admin.add_domain") +def list_quotas(request): + from modoboa.lib.db_utils import db_type + + sort_order, sort_dir = get_sort_order(request.GET, "domain") + domains = Domain.objects.get_for_admin(request.user) + domains = domains.exclude(quota=0) + page = get_listing_page(domains, request.GET.get("page", 1)) + context = { + "headers": render_to_string( + "admin/domains_quota_headers.html", {}, request + ) + } + if page is None: + context["length"] = 0 + else: + context["rows"] = render_to_string( + "admin/domains_quotas.html", {"domains": page}, request + ) + context["pages"] = [page.number] + return render_to_json_response(context) + + @login_required @permission_required("admin.add_domain") @reversion.create_revision() diff --git a/modoboa/admin/views/identity.py b/modoboa/admin/views/identity.py index 9d31f3def..669a49b62 100644 --- a/modoboa/admin/views/identity.py +++ b/modoboa/admin/views/identity.py @@ -53,11 +53,11 @@ def _identities(request): "handle_mailboxes", raise_exception=False) } page = get_listing_page(objects, request.GET.get("page", 1)) + context["headers"] = render_to_string( + "admin/identity_headers.html", {}, request) if page is None: context["length"] = 0 else: - context["headers"] = render_to_string( - "admin/identity_headers.html", {}, request) context["rows"] = render_to_string( "admin/identities_table.html", { "identities": page.object_list @@ -114,14 +114,14 @@ def list_quotas(request): page = get_listing_page(mboxes, request.GET.get("page", 1)) context = { "headers": render_to_string( - "admin/quota_headers.html", {}, request + "admin/identities_quota_headers.html", {}, request ) } if page is None: context["length"] = 0 else: context["rows"] = render_to_string( - "admin/quotas.html", {"mboxes": page}, request + "admin/identities_quotas.html", {"mboxes": page}, request ) context["pages"] = [page.number] return render_to_json_response(context)