diff --git a/musician/api.py b/musician/api.py index 4df4943..8b4139c 100644 --- a/musician/api.py +++ b/musician/api.py @@ -27,6 +27,8 @@ API_PATHS = { 'saas-list': 'saas/', # other + 'bill-list': 'bills/', + 'bill-document': 'bills/{pk}/document/', 'payment-source-list': 'payment-sources/', } @@ -58,7 +60,7 @@ class Orchestra(object): return response.json().get("token", None) - def request(self, verb, resource=None, querystring=None, url=None, raise_exception=True): + def request(self, verb, resource=None, url=None, render_as="json", querystring=None, raise_exception=True): assert verb in ["HEAD", "GET", "POST", "PATCH", "PUT", "DELETE"] if resource is not None: url = self.build_absolute_uri(resource) @@ -76,7 +78,10 @@ class Orchestra(object): response.raise_for_status() status = response.status_code - output = response.json() + if render_as == "json": + output = response.json() + else: + output = response.content return status, output @@ -93,6 +98,15 @@ class Orchestra(object): raise PermissionError("Cannot retrieve profile of an anonymous user.") return UserAccount.new_from_json(output[0]) + def retrieve_bill_document(self, pk): + path = API_PATHS.get('bill-document').format_map({'pk': pk}) + + url = urllib.parse.urljoin(self.base_url, path) + status, bill_pdf = self.request("GET", render_as="html", url=url, raise_exception=False) + if status == 404: + raise Http404(_("No domain found matching the query")) + return bill_pdf + def retrieve_domain(self, pk): path = API_PATHS.get('domain-detail').format_map({'pk': pk}) diff --git a/musician/models.py b/musician/models.py index 68ef088..db3367b 100644 --- a/musician/models.py +++ b/musician/models.py @@ -1,13 +1,19 @@ +import ast +import logging + from django.utils.html import format_html from django.utils.translation import gettext_lazy as _ +logger = logging.getLogger(__name__) + + class OrchestraModel: """ Base class from which all orchestra models will inherit. """ api_name = None verbose_name = None fields = () - param_defaults = {} + id = None def __init__(self, **kwargs): if self.verbose_name is None: @@ -16,9 +22,6 @@ class OrchestraModel: for (param, default) in self.param_defaults.items(): setattr(self, param, kwargs.get(param, default)) - # def get(self, key): - # # retrieve attr of the object and if undefined get raw data - # return getattr(self, key, self.data.get(key)) @classmethod def new_from_json(cls, data, **kwargs): @@ -35,8 +38,7 @@ class OrchestraModel: c = cls(**json_data) c._json = data - # TODO(@slamora) remove/replace by private variable to ovoid name collisions - c.data = data + return c def __repr__(self): @@ -46,6 +48,20 @@ class OrchestraModel: return '%s object (%s)' % (self.__class__.__name__, self.id) +class Bill(OrchestraModel): + api_name = 'bill' + param_defaults = { + "id": None, + "number": "1", + "type": "INVOICE", + "total": 0.0, + "is_sent": False, + "created_on": "", + "due_on": "", + "comments": "", + } + + class BillingContact(OrchestraModel): param_defaults = { 'name': None, @@ -65,6 +81,15 @@ class PaymentSource(OrchestraModel): "is_active": False, } + def __init__(self, **kwargs): + super().__init__(**kwargs) + # payment details are passed as a plain string + # try to convert to a python structure + try: + self.data = ast.literal_eval(self.data) + except (ValueError, SyntaxError) as e: + logger.error(e) + class UserAccount(OrchestraModel): api_name = 'accounts' @@ -159,10 +184,15 @@ class MailService(OrchestraModel): verbose_name = _('Mail addresses') description = _('Litle description of what to be expected in this section to aid the user. Even a link to more help if there is one available.') fields = ('mail_address', 'aliases', 'type', 'type_detail') + param_defaults = {} FORWARD = 'forward' MAILBOX = 'mailbox' + def __init__(self, **kwargs): + self.data = kwargs + super().__init__(**kwargs) + @property def aliases(self): return [ @@ -202,6 +232,10 @@ class MailinglistService(OrchestraModel): 'admin_email': None, } + def __init__(self, **kwargs): + self.data = kwargs + super().__init__(**kwargs) + @property def status(self): # TODO(@slamora): where retrieve if the list is active? diff --git a/musician/static/musician/css/default.css b/musician/static/musician/css/default.css index 42972ab..a9d7da4 100644 --- a/musician/static/musician/css/default.css +++ b/musician/static/musician/css/default.css @@ -247,6 +247,13 @@ h1.service-name { font-weight: 900; } +.card.card-profile .card-header { + background: white; + border-bottom: none; + font-size: large; + text-transform: uppercase; +} + #configDetailsModal .modal-header { border-bottom: 0; text-align: center; diff --git a/musician/static/musician/images/default-profile-picture-primary-color.png b/musician/static/musician/images/default-profile-picture-primary-color.png new file mode 100644 index 0000000..feaf2c7 Binary files /dev/null and b/musician/static/musician/images/default-profile-picture-primary-color.png differ diff --git a/musician/templates/musician/billing.html b/musician/templates/musician/billing.html index f99d816..46d1396 100644 --- a/musician/templates/musician/billing.html +++ b/musician/templates/musician/billing.html @@ -1,5 +1,5 @@ {% extends "musician/base.html" %} -{% load i18n %} +{% load i18n l10n %} {% block content %} @@ -13,7 +13,6 @@
Little description of what to be expected...
+Little description of what to be expected...
-{{ profile.username }}
-{{ profile.type }}
-Preferred language: {{ profile.language }}
+{{ profile.username }}
+{{ profile.type }}
+Preferred language: {{ profile.language }}
+