From 3db5aa86214a4f9ea03067b12a9cedb6bcf6fe9a Mon Sep 17 00:00:00 2001 From: Santiago Lamora Date: Fri, 13 Dec 2019 15:08:01 +0100 Subject: [PATCH 1/4] Created domain-detail view. --- musician/api.py | 16 +++++++++-- .../templates/musician/domain_detail.html | 28 +++++++++++++++++++ musician/urls.py | 1 + musician/views.py | 20 +++++++++++++ 4 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 musician/templates/musician/domain_detail.html diff --git a/musician/api.py b/musician/api.py index 0536b5c..dcfe7e2 100644 --- a/musician/api.py +++ b/musician/api.py @@ -18,6 +18,7 @@ API_PATHS = { # services 'database-list': 'databases/', 'domain-list': 'domains/', + 'domain-detail': 'domains/{pk}/', 'address-list': 'addresses/', 'mailbox-list': 'mailboxes/', 'mailinglist-list': 'lists/', @@ -55,9 +56,13 @@ class Orchestra(object): return response.json().get("token", None) - def request(self, verb, resource, querystring=None, raise_exception=True): + def request(self, verb, resource=None, querystring=None, url=None, raise_exception=True): assert verb in ["HEAD", "GET", "POST", "PATCH", "PUT", "DELETE"] - url = self.build_absolute_uri(resource) + if resource is not None: + url = self.build_absolute_uri(resource) + elif url is None: + raise AttributeError("Provide `resource` or `url` params") + if querystring is not None: url = "{}?{}".format(url, querystring) @@ -86,6 +91,13 @@ class Orchestra(object): raise PermissionError("Cannot retrieve profile of an anonymous user.") return UserAccount.new_from_json(output[0]) + def retrieve_domain(self, pk): + path = API_PATHS.get('domain-detail').format_map({'pk': pk}) + + url = urllib.parse.urljoin(self.base_url, path) + status, domain_json = self.request("GET", url=url) + return Domain.new_from_json(domain_json) + def retrieve_domain_list(self): output = self.retrieve_service_list(Domain.api_name) domains = [] diff --git a/musician/templates/musician/domain_detail.html b/musician/templates/musician/domain_detail.html new file mode 100644 index 0000000..6cb459e --- /dev/null +++ b/musician/templates/musician/domain_detail.html @@ -0,0 +1,28 @@ +{% extends "musician/base.html" %} +{% load i18n %} + +{% block content %} +

{% trans "DNS settings for" %} {{ object.name }}

+

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.

+ + + + + + + + + + + + + + {% for record in object.records %} + + + + + {% endfor %} + +
{% trans "Type" %}{% trans "Value" %}
{{ record.type }}{{ record.value }}
+{% endblock %} diff --git a/musician/urls.py b/musician/urls.py index 340d409..6bae570 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('domains//', views.DomainDetailView.as_view(), name='domain-detail'), path('billing/', views.BillingView.as_view(), name='billing'), path('profile/', views.ProfileView.as_view(), name='profile'), path('mails/', views.MailView.as_view(), name='mails'), diff --git a/musician/views.py b/musician/views.py index b6b4298..7d93509 100644 --- a/musician/views.py +++ b/musician/views.py @@ -186,6 +186,26 @@ class SaasView(ServiceListView): template_name = "musician/saas.html" +class DomainDetailView(CustomContextMixin, UserTokenRequiredMixin, DetailView): + template_name = "musician/domain_detail.html" + + def get_queryset(self): + return [] # self.orchestra.retrieve_domain_list() + + def get_object(self, queryset=None): + if queryset is None: + queryset = self.get_queryset() + + pk = self.kwargs.get(self.pk_url_kwarg) + # TODO try to retrieve object capturing exception + try: + domain = self.orchestra.retrieve_domain(pk) + except: + raise + + return domain + + class LoginView(FormView): template_name = 'auth/login.html' form_class = LoginForm From c69668a9eb469c9720d20e4a54ee8b3b8c7c33f8 Mon Sep 17 00:00:00 2001 From: Santiago Lamora Date: Fri, 13 Dec 2019 15:13:10 +0100 Subject: [PATCH 2/4] Define 'view DNS records' href link. --- musician/templates/musician/dashboard.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/musician/templates/musician/dashboard.html b/musician/templates/musician/dashboard.html index 53c694e..d621e1c 100644 --- a/musician/templates/musician/dashboard.html +++ b/musician/templates/musician/dashboard.html @@ -40,7 +40,8 @@
@@ -120,7 +121,7 @@ @@ -137,6 +138,7 @@ $('#configDetailsModal').on('show.bs.modal', function (event) { modal.find('.modal-body #config-username').text(button.data('username')); modal.find('.modal-body #config-password').text(button.data('password')); modal.find('.modal-body #config-root').text(button.data('root')); + modal.find('.modal-footer .btn').attr('href', button.data('url')); }) {% endblock %} From c2b3485485a9eb88c77c23d66bfa6958a3188c91 Mon Sep 17 00:00:00 2001 From: Santiago Lamora Date: Tue, 17 Dec 2019 10:25:10 +0100 Subject: [PATCH 3/4] Handle not found domain. --- musician/api.py | 6 +++++- musician/tests.py | 13 ++++++++++++- musician/views.py | 12 +++++------- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/musician/api.py b/musician/api.py index dcfe7e2..4df4943 100644 --- a/musician/api.py +++ b/musician/api.py @@ -2,7 +2,9 @@ import requests import urllib.parse from django.conf import settings +from django.http import Http404 from django.urls.exceptions import NoReverseMatch +from django.utils.translation import gettext_lazy as _ from .models import Domain, DatabaseService, MailService, SaasService, UserAccount @@ -95,7 +97,9 @@ class Orchestra(object): path = API_PATHS.get('domain-detail').format_map({'pk': pk}) url = urllib.parse.urljoin(self.base_url, path) - status, domain_json = self.request("GET", url=url) + status, domain_json = self.request("GET", url=url, raise_exception=False) + if status == 404: + raise Http404(_("No domain found matching the query")) return Domain.new_from_json(domain_json) def retrieve_domain_list(self): diff --git a/musician/tests.py b/musician/tests.py index 7ce503c..b25e23a 100644 --- a/musician/tests.py +++ b/musician/tests.py @@ -1,3 +1,14 @@ from django.test import TestCase -# Create your tests here. + +class DomainsTestCase(TestCase): + def test_domain_not_found(self): + response = self.client.post( + '/auth/login/', + {'username': 'admin', 'password': 'admin'}, + follow=True + ) + self.assertEqual(200, response.status_code) + + response = self.client.get('/domains/3/') + self.assertEqual(404, response.status_code) diff --git a/musician/views.py b/musician/views.py index 7d93509..ae3b019 100644 --- a/musician/views.py +++ b/musician/views.py @@ -1,4 +1,3 @@ - from itertools import groupby from django.core.exceptions import ImproperlyConfigured @@ -190,18 +189,17 @@ class DomainDetailView(CustomContextMixin, UserTokenRequiredMixin, DetailView): template_name = "musician/domain_detail.html" def get_queryset(self): - return [] # self.orchestra.retrieve_domain_list() + # Return an empty list to avoid a request to retrieve all the + # user domains. We will get a 404 if the domain doesn't exists + # while invoking `get_object` + return [] def get_object(self, queryset=None): if queryset is None: queryset = self.get_queryset() pk = self.kwargs.get(self.pk_url_kwarg) - # TODO try to retrieve object capturing exception - try: - domain = self.orchestra.retrieve_domain(pk) - except: - raise + domain = self.orchestra.retrieve_domain(pk) return domain From af21ca027b81b29e5409be1830cfb4acfe980c1b Mon Sep 17 00:00:00 2001 From: Santiago Lamora Date: Tue, 17 Dec 2019 10:52:39 +0100 Subject: [PATCH 4/4] Create .btn-arrow-left and add go back link. --- musician/static/musician/css/default.css | 26 +++++++++++++++++++ .../templates/musician/domain_detail.html | 2 ++ 2 files changed, 28 insertions(+) diff --git a/musician/static/musician/css/default.css b/musician/static/musician/css/default.css index b2fb536..42972ab 100644 --- a/musician/static/musician/css/default.css +++ b/musician/static/musician/css/default.css @@ -7,6 +7,32 @@ a:hover { color: rgba(0,0,0,.7); } +.btn-arrow-left{ + color: #eee; + background: #D3D0DA; + position: relative; + padding: 8px 20px 8px 30px; + margin-left: 1em; /** equal value than arrow.left **/ +} + +.btn-arrow-left::after, +.btn-arrow-left::before{ + content: ""; + position: absolute; + top: 50%; + left: -1em; + + margin-top: -19px; + border-top: 19px solid transparent; + border-bottom: 19px solid transparent; + border-right: 1em solid; +} + +.btn-arrow-left::after{ + border-right-color: #D3D0DA; + z-index: 2; +} + .wrapper { display: flex; width: 100%; diff --git a/musician/templates/musician/domain_detail.html b/musician/templates/musician/domain_detail.html index 6cb459e..515a724 100644 --- a/musician/templates/musician/domain_detail.html +++ b/musician/templates/musician/domain_detail.html @@ -2,6 +2,8 @@ {% load i18n %} {% block content %} +{% trans "Go back" %} +

{% trans "DNS settings for" %} {{ object.name }}

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.