diff --git a/CHANGELOG.md b/CHANGELOG.md
index c884e0f..ce4e3c6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## master
+- [changed] Include @pangea.org mail addresses (#4).
## [0.1] - 2020-01-29
- Login & logout methods using backend as auth method
diff --git a/musician/__init__.py b/musician/__init__.py
index fcd8f08..5b64a68 100644
--- a/musician/__init__.py
+++ b/musician/__init__.py
@@ -2,7 +2,7 @@
Package metadata definition.
"""
-VERSION = (0, 1, 0, 'final', 0)
+VERSION = (0, 2, 0, 'alpha', 0)
def get_version():
diff --git a/musician/api.py b/musician/api.py
index bbf77a5..7bb6ee0 100644
--- a/musician/api.py
+++ b/musician/api.py
@@ -1,6 +1,7 @@
import requests
import urllib.parse
+from itertools import groupby
from django.conf import settings
from django.http import Http404
from django.urls.exceptions import NoReverseMatch
@@ -108,6 +109,52 @@ class Orchestra(object):
raise Http404(_("No domain found matching the query"))
return bill_pdf
+ def retrieve_mail_address_list(self, querystring=None):
+ def get_mailbox_id(value):
+ mailboxes = value.get('mailboxes')
+
+ # forwarded address should not grouped
+ if len(mailboxes) == 0:
+ return value.get('name')
+
+ return mailboxes[0]['id']
+
+ # retrieve mails applying filters (if any)
+ raw_data = self.retrieve_service_list(
+ MailService.api_name,
+ querystring=querystring,
+ )
+
+ # group addresses with the same mailbox
+ addresses = []
+ for key, group in groupby(raw_data, get_mailbox_id):
+ aliases = []
+ data = {}
+ for thing in group:
+ aliases.append(thing.pop('name'))
+ data = thing
+
+ data['names'] = aliases
+ addresses.append(MailService.new_from_json(data))
+
+ # PATCH to include Pangea addresses not shown by orchestra
+ # described on issue #4
+ raw_mailboxes = self.retrieve_service_list('mailbox')
+ for mailbox in raw_mailboxes:
+ if mailbox['addresses'] == []:
+ address_data = {
+ 'names': [mailbox['name']],
+ 'forward': '',
+ 'domain': {
+ 'name': 'pangea.org.',
+ },
+ 'mailboxes': [mailbox],
+ }
+ pangea_address = MailService.new_from_json(address_data)
+ addresses.append(pangea_address)
+
+ return addresses
+
def retrieve_domain(self, pk):
path = API_PATHS.get('domain-detail').format_map({'pk': pk})
@@ -133,16 +180,12 @@ class Orchestra(object):
# retrieve websites (as they cannot be filtered by domain on the API we should do it here)
domain_json['websites'] = self.filter_websites_by_domain(websites, domain_json['id'])
- # TODO(@slamora): databases and sass are not related to a domain, so cannot be filtered
- # domain_json['databases'] = self.retrieve_service_list(DatabaseService.api_name, querystring)
- # domain_json['saas'] = self.retrieve_service_list(SaasService.api_name, querystring)
-
# TODO(@slamora): update when backend provides resource disk usage data
domain_json['usage'] = {
- 'usage': 300,
- 'total': 650,
- 'unit': 'MB',
- 'percent': 50,
+ # 'usage': 300,
+ # 'total': 650,
+ # 'unit': 'MB',
+ # 'percent': 50,
}
# append to list a Domain object
diff --git a/musician/models.py b/musician/models.py
index 0718aaa..f3cbfc1 100644
--- a/musician/models.py
+++ b/musician/models.py
@@ -6,6 +6,7 @@ from django.utils.html import format_html
from django.utils.translation import gettext_lazy as _
from . import settings as musician_settings
+from .utils import get_bootstraped_percent
logger = logging.getLogger(__name__)
@@ -229,13 +230,30 @@ class MailService(OrchestraModel):
def type_detail(self):
if self.type == self.FORWARD:
return self.data['forward']
- # TODO(@slamora) retrieve mailbox usage
- return {
- 'usage': 250,
- 'total': 500,
- 'unit': 'MB',
- 'percent': 50,
- }
+
+ # retrieve mailbox usage
+ try:
+ resource = self.data['mailboxes'][0]['resources']
+ resource_disk = {}
+ for r in resource:
+ if r['name'] == 'disk':
+ resource_disk = r
+ break
+
+ mailbox_details = {
+ 'usage': float(resource_disk['used']),
+ 'total': resource_disk['allocated'],
+ 'unit': resource_disk['unit'],
+ }
+
+ percent = get_bootstraped_percent(
+ mailbox_details['used'],
+ mailbox_details['total']
+ )
+ mailbox_details['percent'] = percent
+ except (IndexError, KeyError):
+ mailbox_details = {}
+ return mailbox_details
class MailinglistService(OrchestraModel):
diff --git a/musician/templates/musician/components/usage_progress_bar.html b/musician/templates/musician/components/usage_progress_bar.html
index e3772e4..7dd9537 100644
--- a/musician/templates/musician/components/usage_progress_bar.html
+++ b/musician/templates/musician/components/usage_progress_bar.html
@@ -1,5 +1,5 @@
{% comment %}
-Resource usage rendered as bootstrap progress bar
+Resource usage rendered as bootstrap progress bar
Expected parameter: detail
Expected structure: dictionary or object with attributes:
@@ -8,8 +8,13 @@ Expected structure: dictionary or object with attributes:
- unit (string): 'MB'
- percent (int: [0, 25, 50, 75, 100]: 75
{% endcomment %}
+
- {{ detail.usage }} of {{ detail.total }}{{ detail.unit }}
+ {% if detail %}
+ {{ detail.usage }} of {{ detail.total }} {{ detail.unit }}
+ {% else %}
+ N/A
+ {% endif %}
{{ usage.verbose_name }}
- {% include "musician/components/usage_progress_bar.html" with detail=usage %}
+ {% include "musician/components/usage_progress_bar.html" with detail=usage.data %}
{% endfor %}
diff --git a/musician/tests.py b/musician/tests.py
index 805ff05..13c365e 100644
--- a/musician/tests.py
+++ b/musician/tests.py
@@ -1,6 +1,7 @@
from django.test import TestCase
from .models import UserAccount
+from .utils import get_bootstraped_percent
class DomainsTestCase(TestCase):
@@ -37,3 +38,32 @@ class UserAccountTest(TestCase):
}
account = UserAccount.new_from_json(data)
self.assertIsNone(account.last_login)
+
+
+class GetBootstrapedPercentTest(TestCase):
+ BS_WIDTH = [0, 25, 50, 100]
+
+ def test_exact_value(self):
+ value = get_bootstraped_percent(25, 100)
+ self.assertIn(value, self.BS_WIDTH)
+ self.assertEqual(value, 25)
+
+ def test_round_to_lower(self):
+ value = get_bootstraped_percent(26, 100)
+ self.assertIn(value, self.BS_WIDTH)
+ self.assertEqual(value, 25)
+
+ def test_round_to_higher(self):
+ value = get_bootstraped_percent(48, 100)
+ self.assertIn(value, self.BS_WIDTH)
+ self.assertEqual(value, 50)
+
+ def test_max_boundary(self):
+ value = get_bootstraped_percent(200, 100)
+ self.assertIn(value, self.BS_WIDTH)
+ self.assertEqual(value, 100)
+
+ def test_min_boundary(self):
+ value = get_bootstraped_percent(-10, 100)
+ self.assertIn(value, self.BS_WIDTH)
+ self.assertEqual(value, 0)
diff --git a/musician/utils.py b/musician/utils.py
new file mode 100644
index 0000000..7b029c1
--- /dev/null
+++ b/musician/utils.py
@@ -0,0 +1,15 @@
+def get_bootstraped_percent(value, total):
+ """
+ Get percent and round to be 0, 25, 50 or 100
+
+ Useful to set progress bar width using CSS classes (e.g. w-25)
+ """
+
+ percent = value / total
+ bootstraped = round(percent * 4) * 100 // 4
+
+ # handle min and max boundaries
+ bootstraped = max(0, bootstraped)
+ bootstraped = min(100, bootstraped)
+
+ return bootstraped
diff --git a/musician/views.py b/musician/views.py
index 102a66d..b70fbf5 100644
--- a/musician/views.py
+++ b/musician/views.py
@@ -1,5 +1,3 @@
-from itertools import groupby
-
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.http import HttpResponse, HttpResponseRedirect
@@ -23,6 +21,7 @@ from .mixins import (CustomContextMixin, ExtendedPaginationMixin,
from .models import (Bill, DatabaseService, MailinglistService, MailService,
PaymentSource, SaasService, UserAccount)
from .settings import ALLOWED_RESOURCES
+from .utils import get_bootstraped_percent
class DashboardView(CustomContextMixin, UserTokenRequiredMixin, TemplateView):
@@ -36,38 +35,14 @@ class DashboardView(CustomContextMixin, UserTokenRequiredMixin, TemplateView):
context = super().get_context_data(**kwargs)
domains = self.orchestra.retrieve_domain_list()
- # TODO(@slamora) update when backend provides resource usage data
- resource_usage = {
- 'disk': {
- 'verbose_name': _('Disk usage'),
- 'usage': 534,
- 'total': 1024,
- 'unit': 'MB',
- 'percent': 50,
- },
- 'traffic': {
- 'verbose_name': _('Traffic'),
- 'usage': 300,
- 'total': 2048,
- 'unit': 'MB/month',
- 'percent': 25,
- },
- 'mailbox': {
- 'verbose_name': _('Mailbox usage'),
- 'usage': 1,
- 'total': 2,
- 'unit': 'accounts',
- 'percent': 50,
- },
- }
-
# TODO(@slamora) update when backend supports notifications
notifications = []
# show resource usage based on plan definition
- # TODO(@slamora): validate concept of limits with Pangea
profile_type = context['profile'].type
+ total_mailboxes = 0
for domain in domains:
+ total_mailboxes += len(domain.mails)
addresses_left = ALLOWED_RESOURCES[profile_type]['mailbox'] - len(domain.mails)
alert_level = None
if addresses_left == 1:
@@ -80,6 +55,37 @@ class DashboardView(CustomContextMixin, UserTokenRequiredMixin, TemplateView):
'alert_level': alert_level,
}
+ # TODO(@slamora) update when backend provides resource usage data
+ resource_usage = {
+ 'disk': {
+ 'verbose_name': _('Disk usage'),
+ 'data': {
+ # 'usage': 534,
+ # 'total': 1024,
+ # 'unit': 'MB',
+ # 'percent': 50,
+ },
+ },
+ 'traffic': {
+ 'verbose_name': _('Traffic'),
+ 'data': {
+ # 'usage': 300,
+ # 'total': 2048,
+ # 'unit': 'MB/month',
+ # 'percent': 25,
+ },
+ },
+ 'mailbox': {
+ 'verbose_name': _('Mailbox usage'),
+ 'data': {
+ 'usage': total_mailboxes,
+ 'total': ALLOWED_RESOURCES[profile_type]['mailbox'],
+ 'unit': 'accounts',
+ 'percent': get_bootstraped_percent(total_mailboxes, ALLOWED_RESOURCES[profile_type]['mailbox']),
+ },
+ },
+ }
+
context.update({
'domains': domains,
'resource_usage': resource_usage,
@@ -170,34 +176,11 @@ class MailView(ServiceListView):
}
def get_queryset(self):
- def retrieve_mailbox(value):
- mailboxes = value.get('mailboxes')
-
- # forwarded address should not grouped
- if len(mailboxes) == 0:
- return value.get('name')
-
- return mailboxes[0]['id']
-
# retrieve mails applying filters (if any)
queryfilter = self.get_queryfilter()
- raw_data = self.orchestra.retrieve_service_list(
- self.service_class.api_name,
- querystring=queryfilter,
+ addresses = self.orchestra.retrieve_mail_address_list(
+ querystring=queryfilter
)
-
- # group addresses with the same mailbox
- addresses = []
- for key, group in groupby(raw_data, retrieve_mailbox):
- aliases = []
- data = {}
- for thing in group:
- aliases.append(thing.pop('name'))
- data = thing
-
- data['names'] = aliases
- addresses.append(self.service_class.new_from_json(data))
-
return addresses
def get_queryfilter(self):