From 054b7e92ac1d43560a3056805027ff3a35ac4516 Mon Sep 17 00:00:00 2001 From: Santiago Lamora Date: Thu, 31 Oct 2019 17:16:51 +0100 Subject: [PATCH 01/11] Create `ExtendedPaginationMixin`. --- musician/mixins.py | 21 +++++++++++++++++++++ musician/views.py | 28 +++++++--------------------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/musician/mixins.py b/musician/mixins.py index cf58bff..681141a 100644 --- a/musician/mixins.py +++ b/musician/mixins.py @@ -24,6 +24,27 @@ class CustomContextMixin(ContextMixin): return context +class ExtendedPaginationMixin: + paginate_by = 20 + paginate_by_kwarg = 'per_page' + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update({ + 'per_page_values': [5, 10, 20, 50], + 'per_page_param': self.paginate_by_kwarg, + }) + return context + + def get_paginate_by(self, queryset): + per_page = self.request.GET.get(self.paginate_by_kwarg) or self.paginate_by + try: + paginate_by = int(per_page) + except ValueError: + paginate_by = self.paginate_by + return paginate_by + + class UserTokenRequiredMixin(UserPassesTestMixin): def test_func(self): """Check that the user has an authorized token.""" diff --git a/musician/views.py b/musician/views.py index ef45234..240e287 100644 --- a/musician/views.py +++ b/musician/views.py @@ -11,7 +11,7 @@ from . import api, get_version from .auth import login as auth_login from .auth import logout as auth_logout from .forms import LoginForm -from .mixins import CustomContextMixin, UserTokenRequiredMixin +from .mixins import CustomContextMixin, ExtendedPaginationMixin, UserTokenRequiredMixin class DashboardView(CustomContextMixin, UserTokenRequiredMixin, TemplateView): @@ -30,35 +30,21 @@ class DashboardView(CustomContextMixin, UserTokenRequiredMixin, TemplateView): return context +class ServiceListView(CustomContextMixin, ExtendedPaginationMixin, UserTokenRequiredMixin, ListView): + """Base list view to all services""" + pass + + class MailView(CustomContextMixin, UserTokenRequiredMixin, TemplateView): template_name = "musician/mail.html" -class MailingListsView(CustomContextMixin, UserTokenRequiredMixin, ListView): +class MailingListsView(ServiceListView): template_name = "musician/mailinglists.html" - paginate_by = 20 - paginate_by_kwarg = 'per_page' def get_queryset(self): return self.orchestra.retrieve_service_list('mailinglist') - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context.update({ - 'page_param': self.page_kwarg, - 'per_page_values': [5, 10, 20, 50], - 'per_page_param': self.paginate_by_kwarg, - }) - return context - - def get_paginate_by(self, queryset): - per_page = self.request.GET.get(self.paginate_by_kwarg) or self.paginate_by - try: - paginate_by = int(per_page) - except ValueError: - paginate_by = self.paginate_by - return paginate_by - class DatabasesView(CustomContextMixin, UserTokenRequiredMixin, TemplateView): template_name = "musician/databases.html" From 24bce2ab043cace553363e5ce9a8e9055dac9ee8 Mon Sep 17 00:00:00 2001 From: Santiago Lamora Date: Wed, 13 Nov 2019 11:08:19 +0100 Subject: [PATCH 02/11] Create generic ServiceListView and implement MailView --- musician/api.py | 2 + musician/models.py | 40 +++++++++++++++ musician/templates/musician/service_list.html | 28 +++++++++++ musician/templatetags/musician.py | 6 +++ musician/views.py | 49 +++++++++++++++++-- 5 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 musician/models.py create mode 100644 musician/templates/musician/service_list.html create mode 100644 musician/templatetags/musician.py diff --git a/musician/api.py b/musician/api.py index 5e36372..9ed19af 100644 --- a/musician/api.py +++ b/musician/api.py @@ -14,6 +14,8 @@ API_PATHS = { # services 'domain-list': 'domains/', + 'address-list': 'addresses/', + 'mailbox-list': 'mailboxes/', 'mailinglist-list': 'lists/', # ... TODO (@slamora) complete list of backend URLs } diff --git a/musician/models.py b/musician/models.py new file mode 100644 index 0000000..e88caa5 --- /dev/null +++ b/musician/models.py @@ -0,0 +1,40 @@ +class MailService: + name = 'address' + verbose_name = 'Mail' + fields = ('mail_address', 'aliases', 'type', 'type_detail') + + FORWARD = 'forward' + MAILBOX = 'mailbox' + + def __init__(self, data={}): + if self.verbose_name is None: + self.verbose_name = self.name + + self.data = data + + def get(self, key): + # retrieve attr of the object and if undefined get raw data + return getattr(self, key, self.data.get(key)) + + @property + def aliases(self): + return [ + name + '@' + self.data['domain']['name'] for name in self.data['names'][1:] + ] + + @property + def mail_address(self): + return self.data['names'][0] + '@' + self.data['domain']['name'] + + @property + def type(self): + if self.data['forward']: + return self.FORWARD + return self.MAILBOX + + @property + def type_detail(self): + if self.type == self.FORWARD: + return self.data['forward'] + # TODO(@slamora) retrieve mailbox usage + return {'usage': 0, 'total': 213} diff --git a/musician/templates/musician/service_list.html b/musician/templates/musician/service_list.html new file mode 100644 index 0000000..d413fc8 --- /dev/null +++ b/musician/templates/musician/service_list.html @@ -0,0 +1,28 @@ +{% extends "musician/base.html" %} +{% load i18n musician %} + +{% block content %} + +

{{ service.verbose_name }}

+

{{ service.description }}

+ + + + + {% for field_name in service.fields %} + + {% endfor %} + + + + {% for resource in object_list %} + + {% for field_name in service.fields %} + + {% endfor %} + + {% endfor %} + + {% include "musician/components/table_paginator.html" %} +
{{ field_name }}
{{ resource|get_item:field_name }}
+{% endblock %} diff --git a/musician/templatetags/musician.py b/musician/templatetags/musician.py new file mode 100644 index 0000000..181fd01 --- /dev/null +++ b/musician/templatetags/musician.py @@ -0,0 +1,6 @@ +from django.template.defaulttags import register + + +@register.filter +def get_item(dictionary, key): + return dictionary.get(key) diff --git a/musician/views.py b/musician/views.py index 240e287..709f393 100644 --- a/musician/views.py +++ b/musician/views.py @@ -1,4 +1,6 @@ +from django.core.exceptions import ImproperlyConfigured +from itertools import groupby from django.http import HttpResponseRedirect from django.shortcuts import render from django.urls import reverse_lazy @@ -12,6 +14,7 @@ from .auth import login as auth_login from .auth import logout as auth_logout from .forms import LoginForm from .mixins import CustomContextMixin, ExtendedPaginationMixin, UserTokenRequiredMixin +from .models import MailService class DashboardView(CustomContextMixin, UserTokenRequiredMixin, TemplateView): @@ -32,11 +35,51 @@ class DashboardView(CustomContextMixin, UserTokenRequiredMixin, TemplateView): class ServiceListView(CustomContextMixin, ExtendedPaginationMixin, UserTokenRequiredMixin, ListView): """Base list view to all services""" - pass + service = None + template_name = "musician/service_list.html" # TODO move to ServiceListView + + def get_queryset(self): + if self.service_class is None or self.service_class.name is None: + raise ImproperlyConfigured( + "ServiceListView requires a definiton of 'service'") + + return self.orchestra.retrieve_service_list(self.service_class.name) + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context.update({ + 'service': self.service_class, + }) + return context -class MailView(CustomContextMixin, UserTokenRequiredMixin, TemplateView): - template_name = "musician/mail.html" +class MailView(ServiceListView): + service_class = MailService + + def get_queryset(self): + def retrieve_mailbox(value): + mailboxes = value.get('mailboxes') + + if len(mailboxes) == 0: + return '' + + return mailboxes[0]['id'] + + # group addresses with the same mailbox + raw_data = self.orchestra.retrieve_service_list( + self.service_class.name) + 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(MailService(data)) + + return addresses class MailingListsView(ServiceListView): From 0fa34080355a2e4c74fe239b9484452b8d2bb381 Mon Sep 17 00:00:00 2001 From: Santiago Lamora Date: Wed, 13 Nov 2019 11:42:23 +0100 Subject: [PATCH 03/11] Create mailing list view. --- musician/models.py | 41 +++++++++++++++++++++++++++++++++++------ musician/views.py | 12 +++++------- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/musician/models.py b/musician/models.py index e88caa5..5eab96f 100644 --- a/musician/models.py +++ b/musician/models.py @@ -1,10 +1,10 @@ -class MailService: - name = 'address' - verbose_name = 'Mail' - fields = ('mail_address', 'aliases', 'type', 'type_detail') +from django.utils.html import format_html - FORWARD = 'forward' - MAILBOX = 'mailbox' + +class Service: + name = None + verbose_name = None + fields = () def __init__(self, data={}): if self.verbose_name is None: @@ -16,6 +16,15 @@ class MailService: # retrieve attr of the object and if undefined get raw data return getattr(self, key, self.data.get(key)) + +class MailService(Service): + name = 'address' + verbose_name = 'Mail' + fields = ('mail_address', 'aliases', 'type', 'type_detail') + + FORWARD = 'forward' + MAILBOX = 'mailbox' + @property def aliases(self): return [ @@ -38,3 +47,23 @@ class MailService: return self.data['forward'] # TODO(@slamora) retrieve mailbox usage return {'usage': 0, 'total': 213} + + +class MailinglistService(Service): + name = 'mailinglist' + verbose_name = 'Mailing list' + fields = ('name', 'status', 'address_name', 'admin_email', 'configure') + + @property + def status(self): + # TODO(@slamora): where retrieve if the list is active? + return 'active' + + @property + def address_name(self): + return "{}@{}".format(self.data['address_name'], self.data['address_domain']['name']) + + @property + def configure(self): + # TODO(@slamora): build mailtran absolute URL + return format_html('Mailtrain') diff --git a/musician/views.py b/musician/views.py index 709f393..23d5b57 100644 --- a/musician/views.py +++ b/musician/views.py @@ -14,7 +14,7 @@ from .auth import login as auth_login from .auth import logout as auth_logout from .forms import LoginForm from .mixins import CustomContextMixin, ExtendedPaginationMixin, UserTokenRequiredMixin -from .models import MailService +from .models import MailService, MailinglistService class DashboardView(CustomContextMixin, UserTokenRequiredMixin, TemplateView): @@ -43,7 +43,8 @@ class ServiceListView(CustomContextMixin, ExtendedPaginationMixin, UserTokenRequ raise ImproperlyConfigured( "ServiceListView requires a definiton of 'service'") - return self.orchestra.retrieve_service_list(self.service_class.name) + json_qs = self.orchestra.retrieve_service_list(self.service_class.name) + return [self.service_class(data) for data in json_qs] def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -77,16 +78,13 @@ class MailView(ServiceListView): data = thing data['names'] = aliases - addresses.append(MailService(data)) + addresses.append(self.service_class(data)) return addresses class MailingListsView(ServiceListView): - template_name = "musician/mailinglists.html" - - def get_queryset(self): - return self.orchestra.retrieve_service_list('mailinglist') + service_class = MailinglistService class DatabasesView(CustomContextMixin, UserTokenRequiredMixin, TemplateView): From c8e8f778d71eddee74cd55eafad68f4afa0b975a Mon Sep 17 00:00:00 2001 From: Santiago Lamora Date: Wed, 13 Nov 2019 12:26:35 +0100 Subject: [PATCH 04/11] Rename Service.name --> Service.api_name Avoid collision name attributes with service data. --- musician/models.py | 8 ++++---- musician/views.py | 15 ++++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/musician/models.py b/musician/models.py index 5eab96f..d73b7ab 100644 --- a/musician/models.py +++ b/musician/models.py @@ -2,13 +2,13 @@ from django.utils.html import format_html class Service: - name = None + api_name = None verbose_name = None fields = () def __init__(self, data={}): if self.verbose_name is None: - self.verbose_name = self.name + self.verbose_name = self.api_name self.data = data @@ -18,7 +18,7 @@ class Service: class MailService(Service): - name = 'address' + api_name = 'address' verbose_name = 'Mail' fields = ('mail_address', 'aliases', 'type', 'type_detail') @@ -50,7 +50,7 @@ class MailService(Service): class MailinglistService(Service): - name = 'mailinglist' + api_name = 'mailinglist' verbose_name = 'Mailing list' fields = ('name', 'status', 'address_name', 'admin_email', 'configure') diff --git a/musician/views.py b/musician/views.py index 23d5b57..539619a 100644 --- a/musician/views.py +++ b/musician/views.py @@ -1,6 +1,7 @@ +from itertools import groupby + from django.core.exceptions import ImproperlyConfigured -from itertools import groupby from django.http import HttpResponseRedirect from django.shortcuts import render from django.urls import reverse_lazy @@ -13,8 +14,8 @@ from . import api, get_version from .auth import login as auth_login from .auth import logout as auth_logout from .forms import LoginForm -from .mixins import CustomContextMixin, ExtendedPaginationMixin, UserTokenRequiredMixin -from .models import MailService, MailinglistService +from .mixins import (CustomContextMixin, ExtendedPaginationMixin, UserTokenRequiredMixin) +from .models import DatabaseService, MailinglistService, MailService class DashboardView(CustomContextMixin, UserTokenRequiredMixin, TemplateView): @@ -35,15 +36,15 @@ class DashboardView(CustomContextMixin, UserTokenRequiredMixin, TemplateView): class ServiceListView(CustomContextMixin, ExtendedPaginationMixin, UserTokenRequiredMixin, ListView): """Base list view to all services""" - service = None + service_class = None template_name = "musician/service_list.html" # TODO move to ServiceListView def get_queryset(self): - if self.service_class is None or self.service_class.name is None: + if self.service_class is None or self.service_class.api_name is None: raise ImproperlyConfigured( "ServiceListView requires a definiton of 'service'") - json_qs = self.orchestra.retrieve_service_list(self.service_class.name) + json_qs = self.orchestra.retrieve_service_list(self.service_class.api_name) return [self.service_class(data) for data in json_qs] def get_context_data(self, **kwargs): @@ -68,7 +69,7 @@ class MailView(ServiceListView): # group addresses with the same mailbox raw_data = self.orchestra.retrieve_service_list( - self.service_class.name) + self.service_class.api_name) addresses = [] for key, group in groupby(raw_data, retrieve_mailbox): aliases = [] From 37854207dcb82a400faf1020eed8b72ef4dbe393 Mon Sep 17 00:00:00 2001 From: Santiago Lamora Date: Wed, 13 Nov 2019 12:27:25 +0100 Subject: [PATCH 05/11] Create database list view. --- musician/api.py | 1 + musician/models.py | 5 +++++ musician/views.py | 4 ++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/musician/api.py b/musician/api.py index 9ed19af..eb138e1 100644 --- a/musician/api.py +++ b/musician/api.py @@ -13,6 +13,7 @@ API_PATHS = { 'my-account': 'accounts/', # services + 'database-list': 'databases/', 'domain-list': 'domains/', 'address-list': 'addresses/', 'mailbox-list': 'mailboxes/', diff --git a/musician/models.py b/musician/models.py index d73b7ab..c55acbd 100644 --- a/musician/models.py +++ b/musician/models.py @@ -17,6 +17,11 @@ class Service: return getattr(self, key, self.data.get(key)) +class DatabaseService(Service): + api_name = 'database' + fields = ('name', 'type', 'users') + + class MailService(Service): api_name = 'address' verbose_name = 'Mail' diff --git a/musician/views.py b/musician/views.py index 539619a..9945b8a 100644 --- a/musician/views.py +++ b/musician/views.py @@ -88,8 +88,8 @@ class MailingListsView(ServiceListView): service_class = MailinglistService -class DatabasesView(CustomContextMixin, UserTokenRequiredMixin, TemplateView): - template_name = "musician/databases.html" +class DatabasesView(ServiceListView): + service_class = DatabaseService class SaasView(CustomContextMixin, UserTokenRequiredMixin, TemplateView): From 98c5482f5b564c644a7733a638bc6bee2ebaf193 Mon Sep 17 00:00:00 2001 From: Santiago Lamora Date: Wed, 20 Nov 2019 10:29:39 +0100 Subject: [PATCH 06/11] Refactor and rename Service --> OrchestraModel class. --- musician/models.py | 59 ++++++++++++++++++---- musician/templates/musician/databases.html | 29 ++++++++++- musician/views.py | 3 +- 3 files changed, 79 insertions(+), 12 deletions(-) diff --git a/musician/models.py b/musician/models.py index c55acbd..3904aa3 100644 --- a/musician/models.py +++ b/musician/models.py @@ -1,28 +1,69 @@ from django.utils.html import format_html -class Service: +class OrchestraModel: + """ Base class from which all orchestra models will inherit. """ api_name = None verbose_name = None fields = () + param_defaults = {} - def __init__(self, data={}): + def __init__(self, **kwargs): if self.verbose_name is None: self.verbose_name = self.api_name - self.data = data + 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)) + # 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): + """ Create a new instance based on a JSON dict. Any kwargs should be + supplied by the inherited, calling class. + Args: + data: A JSON dict, as converted from the JSON in the orchestra API. + """ + + json_data = data.copy() + if kwargs: + for key, val in kwargs.items(): + json_data[key] = val + + c = cls(**json_data) + c._json = data + return c -class DatabaseService(Service): +class DatabaseUser(OrchestraModel): + api_name = 'databaseusers' + fields = ('username',) + param_defaults = { + 'username': None, + } + + +class DatabaseService(OrchestraModel): api_name = 'database' fields = ('name', 'type', 'users') + param_defaults = { + "id": None, + "name": None, + "type": None, + "users": None, + } + + @classmethod + def new_from_json(cls, data, **kwargs): + users = None + if 'users' in data: + users = [DatabaseUser.new_from_json(user_data) for user_data in data['users']] + return super().new_from_json(data=data, users=users) -class MailService(Service): +class MailService(OrchestraModel): api_name = 'address' verbose_name = 'Mail' fields = ('mail_address', 'aliases', 'type', 'type_detail') @@ -54,7 +95,7 @@ class MailService(Service): return {'usage': 0, 'total': 213} -class MailinglistService(Service): +class MailinglistService(OrchestraModel): api_name = 'mailinglist' verbose_name = 'Mailing list' fields = ('name', 'status', 'address_name', 'admin_email', 'configure') diff --git a/musician/templates/musician/databases.html b/musician/templates/musician/databases.html index 927ff1c..96911db 100644 --- a/musician/templates/musician/databases.html +++ b/musician/templates/musician/databases.html @@ -3,7 +3,32 @@ {% block content %} -

Section title

-

Little description of what to be expected...

+

{{ service.verbose_name }}

+

{{ service.description }}

+ + + + {% for field_name in service.fields %} + + {% endfor %} + + + + {% for resource in object_list %} + + + + + + {% endfor %} + + {% include "musician/components/table_paginator.html" %} +
{{ field_name }}
{{ resource.name }}{{ resource.type }} + {% for user in resource.users %} + {{ user.username }} + {% empty %} + No users for this database. + {% endfor %} +
{% endblock %} diff --git a/musician/views.py b/musician/views.py index 9945b8a..f794351 100644 --- a/musician/views.py +++ b/musician/views.py @@ -45,7 +45,7 @@ class ServiceListView(CustomContextMixin, ExtendedPaginationMixin, UserTokenRequ "ServiceListView requires a definiton of 'service'") json_qs = self.orchestra.retrieve_service_list(self.service_class.api_name) - return [self.service_class(data) for data in json_qs] + return [self.service_class.new_from_json(data) for data in json_qs] def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) @@ -89,6 +89,7 @@ class MailingListsView(ServiceListView): class DatabasesView(ServiceListView): + template_name = "musician/databases.html" service_class = DatabaseService From 85f1d433a35ba9a62a200409e0f9f9d54ef6b644 Mon Sep 17 00:00:00 2001 From: Santiago Lamora Date: Wed, 20 Nov 2019 11:04:35 +0100 Subject: [PATCH 07/11] Lay out database component. --- musician/templates/musician/databases.html | 60 ++++++++++++++-------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/musician/templates/musician/databases.html b/musician/templates/musician/databases.html index 96911db..c15d7b4 100644 --- a/musician/templates/musician/databases.html +++ b/musician/templates/musician/databases.html @@ -5,30 +5,48 @@

{{ service.verbose_name }}

{{ service.description }}

- - - - {% for field_name in service.fields %} - - {% endfor %} - - - - {% for resource in object_list %} - - - - - - {% endfor %} - + + +
+

Database usage

+
+
75%
+
+
+ + + + + {% endfor %} + {% include "musician/components/table_paginator.html" %} -
{{ field_name }}
{{ resource.name }}{{ resource.type }} +{% for database in object_list %} +
+
+
+ {{ database.name }} +
+
+ Type: {{ database.type }} +
+
+ associated to: {{ database.domain|default:"-" }} + +
+
+
+
+

Database users

+
    {% for user in resource.users %} - {{ user.username }} +
  • {{ user.username }}
  • {% empty %} - No users for this database. +
  • No users for this database.
  • {% endfor %} -
{% endblock %} From 5322f62cdf2059e2ceb7206e5c5fa7783203cf3f Mon Sep 17 00:00:00 2001 From: Santiago Lamora Date: Wed, 20 Nov 2019 17:09:08 +0100 Subject: [PATCH 08/11] Create paginator component as div and cols. --- .../musician/components/paginator.html | 29 +++++++++++++++++++ musician/templates/musician/databases.html | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 musician/templates/musician/components/paginator.html diff --git a/musician/templates/musician/components/paginator.html b/musician/templates/musician/components/paginator.html new file mode 100644 index 0000000..9a7a406 --- /dev/null +++ b/musician/templates/musician/components/paginator.html @@ -0,0 +1,29 @@ +{# #} +
+
{{ page_obj.paginator.count }} items in total
+
+ {% if page_obj.has_previous %} + « + + {% endif %} + Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }} + {% if page_obj.has_next %} + + » + {% endif %} +
+
+
+ Showing + + per page + +
+
+
diff --git a/musician/templates/musician/databases.html b/musician/templates/musician/databases.html index c15d7b4..448432c 100644 --- a/musician/templates/musician/databases.html +++ b/musician/templates/musician/databases.html @@ -48,5 +48,5 @@ {% endfor %} - {% include "musician/components/table_paginator.html" %} + {% include "musician/components/paginator.html" %} {% endblock %} From 8a2d409e321d3b44a9c9d1efc06cc443eb2aff82 Mon Sep 17 00:00:00 2001 From: Santiago Lamora Date: Wed, 20 Nov 2019 17:25:32 +0100 Subject: [PATCH 09/11] Code mail component. --- musician/templates/musician/mail.html | 26 +++++++++++++++++++++++--- musician/views.py | 3 ++- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/musician/templates/musician/mail.html b/musician/templates/musician/mail.html index 927ff1c..116fae4 100644 --- a/musician/templates/musician/mail.html +++ b/musician/templates/musician/mail.html @@ -3,7 +3,27 @@ {% block content %} -

Section title

-

Little description of what to be expected...

- +

{{ service.verbose_name }}

+

{{ service.description }}

+ + + + + + + + + + + {% for obj in object_list %} + + + + + + + {% endfor %} + + {% include "musician/components/table_paginator.html" %} +
Mail addressTypeType details
{{ obj.mail_address }}{{ obj.aliases|join:" , " }}{{ obj.type }}{{ obj.type_detail }}
{% endblock %} diff --git a/musician/views.py b/musician/views.py index f794351..071f9ed 100644 --- a/musician/views.py +++ b/musician/views.py @@ -57,6 +57,7 @@ class ServiceListView(CustomContextMixin, ExtendedPaginationMixin, UserTokenRequ class MailView(ServiceListView): service_class = MailService + template_name = "musician/mail.html" def get_queryset(self): def retrieve_mailbox(value): @@ -79,7 +80,7 @@ class MailView(ServiceListView): data = thing data['names'] = aliases - addresses.append(self.service_class(data)) + addresses.append(self.service_class.new_from_json(data)) return addresses From 902ef7a949dc2ef8e3c7b25d3d1d8178a9542570 Mon Sep 17 00:00:00 2001 From: Santiago Lamora Date: Wed, 20 Nov 2019 17:41:15 +0100 Subject: [PATCH 10/11] Fix mailing list component. --- musician/models.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/musician/models.py b/musician/models.py index 3904aa3..9ba8d00 100644 --- a/musician/models.py +++ b/musician/models.py @@ -34,6 +34,8 @@ 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 @@ -99,6 +101,10 @@ class MailinglistService(OrchestraModel): api_name = 'mailinglist' verbose_name = 'Mailing list' fields = ('name', 'status', 'address_name', 'admin_email', 'configure') + param_defaults = { + 'name': None, + 'admin_email': None, + } @property def status(self): From d3d87d37c336404e617d49187e2741795d3b150a Mon Sep 17 00:00:00 2001 From: Santiago Lamora Date: Wed, 20 Nov 2019 20:07:35 +0100 Subject: [PATCH 11/11] Code Profile template. --- musician/api.py | 3 ++ musician/models.py | 40 +++++++++++++++++++ musician/templates/musician/profile.html | 51 ++++++++++++++++++++++++ musician/urls.py | 1 + musician/views.py | 28 +++++++++++-- 5 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 musician/templates/musician/profile.html diff --git a/musician/api.py b/musician/api.py index eb138e1..048f34e 100644 --- a/musician/api.py +++ b/musician/api.py @@ -19,6 +19,9 @@ API_PATHS = { 'mailbox-list': 'mailboxes/', 'mailinglist-list': 'lists/', # ... TODO (@slamora) complete list of backend URLs + + # other + 'payment-source-list': 'payment-sources/', } diff --git a/musician/models.py b/musician/models.py index 9ba8d00..28eb035 100644 --- a/musician/models.py +++ b/musician/models.py @@ -39,6 +39,46 @@ class OrchestraModel: return c +class BillingContact(OrchestraModel): + param_defaults = { + 'name': None, + 'address': None, + 'city': None, + 'zipcode': None, + 'country': None, + 'vat': None, + } + + +class PaymentSource(OrchestraModel): + api_name = 'payment-source' + param_defaults = { + "method": None, + "data": [], + "is_active": False, + } + + +class UserAccount(OrchestraModel): + api_name = 'accounts' + param_defaults = { + 'username': None, + 'type': None, + 'language': None, + 'short_name': None, + 'full_name': None, + 'billing': {}, + } + + @classmethod + def new_from_json(cls, data, **kwargs): + billing = None + + if 'billcontact' in data: + billing = BillingContact.new_from_json(data['billcontact']) + return super().new_from_json(data=data, billing=billing) + + class DatabaseUser(OrchestraModel): api_name = 'databaseusers' fields = ('username',) diff --git a/musician/templates/musician/profile.html b/musician/templates/musician/profile.html new file mode 100644 index 0000000..e9da7be --- /dev/null +++ b/musician/templates/musician/profile.html @@ -0,0 +1,51 @@ +{% extends "musician/base.html" %} +{% load i18n %} + +{% block content %} + +

Profile

+

Little description of what to be expected...

+ +
+
User information
+
+
+
+ {# #} +
+
+
+

{{ profile.username }}

+

{{ profile.type }}

+

Preferred language: {{ profile.language }}

+
+
+
+ +{% with profile.billing as contact %} +
+
Billing information
+
+
{{ contact.name }}
+
{{ contact.address }}
+
+ {{ contact.zipcode }} + {{ contact.city }} + {{ contact.country }} +
+
+ {{ contact.vat }} +
+ +
+ payment method: {{ payment.method }} +
+
+ {# TODO(@slamora) format payment method details #} + {{ payment.data.data }} +
+ +
+
+{% endwith %} +{% endblock %} diff --git a/musician/urls.py b/musician/urls.py index e18f828..e08bd0a 100644 --- a/musician/urls.py +++ b/musician/urls.py @@ -15,6 +15,7 @@ urlpatterns = [ path('auth/login/', views.LoginView.as_view(), name='login'), path('auth/logout/', views.LogoutView.as_view(), name='logout'), path('dashboard/', views.DashboardView.as_view(), name='dashboard'), + path('profile/', views.ProfileView.as_view(), name='profile'), path('mails/', views.MailView.as_view(), name='mails'), path('mailing-lists/', views.MailingListsView.as_view(), name='mailing-lists'), path('databases/', views.DatabasesView.as_view(), name='databases'), diff --git a/musician/views.py b/musician/views.py index 071f9ed..632f8ea 100644 --- a/musician/views.py +++ b/musician/views.py @@ -1,4 +1,5 @@ +from django.views.generic.detail import DetailView from itertools import groupby from django.core.exceptions import ImproperlyConfigured @@ -14,8 +15,9 @@ from . import api, get_version from .auth import login as auth_login from .auth import logout as auth_logout from .forms import LoginForm -from .mixins import (CustomContextMixin, ExtendedPaginationMixin, UserTokenRequiredMixin) -from .models import DatabaseService, MailinglistService, MailService +from .mixins import (CustomContextMixin, + ExtendedPaginationMixin, UserTokenRequiredMixin) +from .models import DatabaseService, MailinglistService, MailService, UserAccount, PaymentSource class DashboardView(CustomContextMixin, UserTokenRequiredMixin, TemplateView): @@ -34,6 +36,25 @@ class DashboardView(CustomContextMixin, UserTokenRequiredMixin, TemplateView): return context +class ProfileView(CustomContextMixin, UserTokenRequiredMixin, TemplateView): + template_name = "musician/profile.html" + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + json_data = self.orchestra.retreve_profile() + try: + pay_source = self.orchestra.retrieve_service_list( + PaymentSource.api_name)[0] + except IndexError: + pay_source = {} + context.update({ + 'profile': UserAccount.new_from_json(json_data[0]), + 'payment': PaymentSource.new_from_json(pay_source) + }) + + return context + + class ServiceListView(CustomContextMixin, ExtendedPaginationMixin, UserTokenRequiredMixin, ListView): """Base list view to all services""" service_class = None @@ -44,7 +65,8 @@ class ServiceListView(CustomContextMixin, ExtendedPaginationMixin, UserTokenRequ raise ImproperlyConfigured( "ServiceListView requires a definiton of 'service'") - json_qs = self.orchestra.retrieve_service_list(self.service_class.api_name) + json_qs = self.orchestra.retrieve_service_list( + self.service_class.api_name) return [self.service_class.new_from_json(data) for data in json_qs] def get_context_data(self, **kwargs):