From f635721831f35786250ae413f6007810347328f8 Mon Sep 17 00:00:00 2001 From: Santiago Lamora Date: Wed, 23 Jun 2021 13:47:27 +0200 Subject: [PATCH] (Draft) Add view to create addresses --- musician/api.py | 25 +++++++++++++++++---- musician/forms.py | 22 ++++++++++++++++++ musician/models.py | 3 ++- musician/templates/musician/mail.html | 1 + musician/templates/musician/mail_form.html | 10 +++++++++ musician/urls.py | 1 + musician/views.py | 26 +++++++++++++++++++++- 7 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 musician/templates/musician/mail_form.html diff --git a/musician/api.py b/musician/api.py index 7bb6ee0..4913a23 100644 --- a/musician/api.py +++ b/musician/api.py @@ -62,7 +62,7 @@ class Orchestra(object): return response.json().get("token", None) - def request(self, verb, resource=None, url=None, render_as="json", querystring=None, raise_exception=True): + def request(self, verb, resource=None, url=None, data=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) @@ -73,8 +73,11 @@ class Orchestra(object): url = "{}?{}".format(url, querystring) verb = getattr(self.session, verb.lower()) - response = verb(url, headers={"Authorization": "Token {}".format( - self.auth_token)}, allow_redirects=False) + headers = { + "Authorization": "Token {}".format(self.auth_token), + "Content-Type": "application/json", + } + response = verb(url, json=data, headers=headers, allow_redirects=False) if raise_exception: response.raise_for_status() @@ -109,6 +112,15 @@ class Orchestra(object): raise Http404(_("No domain found matching the query")) return bill_pdf + def create_mail_address(self, data): + resource = '{}-list'.format(MailService.api_name) + + # transform form data to expected format + data["domain"] = {"url": data["domain"]} + data["mailboxes"] = [{"url": mbox} for mbox in data["mailboxes"]] + + return self.request("POST", resource=resource, data=data) + def retrieve_mail_address_list(self, querystring=None): def get_mailbox_id(value): mailboxes = value.get('mailboxes') @@ -139,7 +151,7 @@ class Orchestra(object): # PATCH to include Pangea addresses not shown by orchestra # described on issue #4 - raw_mailboxes = self.retrieve_service_list('mailbox') + raw_mailboxes = self.retrieve_mailbox_list() for mailbox in raw_mailboxes: if mailbox['addresses'] == []: address_data = { @@ -155,6 +167,11 @@ class Orchestra(object): return addresses + def retrieve_mailbox_list(self): + # TODO(@slamora) encapsulate as a Service class + raw_mailboxes = self.retrieve_service_list('mailbox') + return raw_mailboxes + def retrieve_domain(self, pk): path = API_PATHS.get('domain-detail').format_map({'pk': pk}) diff --git a/musician/forms.py b/musician/forms.py index 7a66a00..bbfd2de 100644 --- a/musician/forms.py +++ b/musician/forms.py @@ -1,5 +1,7 @@ +from django import forms from django.contrib.auth.forms import AuthenticationForm +from django.core.exceptions import ValidationError from . import api @@ -20,3 +22,23 @@ class LoginForm(AuthenticationForm): self.user = orchestra.retrieve_profile() return self.cleaned_data + + +class MailForm(forms.Form): + name = forms.CharField() + domain = forms.ChoiceField() + mailboxes = forms.MultipleChoiceField(required=False) + forward = forms.EmailField(required=False) + + def __init__(self, *args, **kwargs): + domains = kwargs.pop('domains') + mailboxes = kwargs.pop('mailboxes') + super().__init__(*args, **kwargs) + self.fields['domain'].choices = [(d.url, d.name) for d in domains] + self.fields['mailboxes'].choices = [(m['url'], m['name']) for m in mailboxes] + + def clean(self): + cleaned_data = super().clean() + if not cleaned_data.get('mailboxes') and not cleaned_data.get('forward'): + raise ValidationError("A mailbox or forward address should be provided.") + return cleaned_data diff --git a/musician/models.py b/musician/models.py index 93e3d29..978a0c8 100644 --- a/musician/models.py +++ b/musician/models.py @@ -161,7 +161,7 @@ class DatabaseService(OrchestraModel): return super().new_from_json(data=data, users=users, usage=usage) @classmethod - def get_usage(self, data): + def get_usage(cls, data): try: resources = data['resources'] resource_disk = {} @@ -201,6 +201,7 @@ class Domain(OrchestraModel): "mails": [], "usage": {}, "websites": [], + "url": None, } @classmethod diff --git a/musician/templates/musician/mail.html b/musician/templates/musician/mail.html index e8ae8be..f1fcf14 100644 --- a/musician/templates/musician/mail.html +++ b/musician/templates/musician/mail.html @@ -41,4 +41,5 @@ {% include "musician/components/table_paginator.html" %} +{% trans "New mail address" %} {% endblock %} diff --git a/musician/templates/musician/mail_form.html b/musician/templates/musician/mail_form.html new file mode 100644 index 0000000..8451647 --- /dev/null +++ b/musician/templates/musician/mail_form.html @@ -0,0 +1,10 @@ +{% extends "musician/base.html" %} +{% load i18n %} + +{% block content %} +
+ {% csrf_token %} + {{ form }} + +
+{% endblock %} diff --git a/musician/urls.py b/musician/urls.py index 9139f35..f1f3c40 100644 --- a/musician/urls.py +++ b/musician/urls.py @@ -20,6 +20,7 @@ urlpatterns = [ path('bills//download/', views.BillDownloadView.as_view(), name='bill-download'), path('profile/', views.ProfileView.as_view(), name='profile'), path('mails/', views.MailView.as_view(), name='mails'), + path('mails/new/', views.MailCreateView.as_view(), name='mail-create'), path('mailing-lists/', views.MailingListsView.as_view(), name='mailing-lists'), path('databases/', views.DatabasesView.as_view(), name='databases'), path('software-as-a-service/', views.SaasView.as_view(), name='saas'), diff --git a/musician/views.py b/musician/views.py index b70fbf5..b039b26 100644 --- a/musician/views.py +++ b/musician/views.py @@ -11,11 +11,12 @@ from django.views.generic.base import RedirectView, TemplateView from django.views.generic.detail import DetailView from django.views.generic.edit import FormView from django.views.generic.list import ListView +from requests.exceptions import HTTPError from . import api, get_version from .auth import login as auth_login from .auth import logout as auth_logout -from .forms import LoginForm +from .forms import LoginForm, MailForm from .mixins import (CustomContextMixin, ExtendedPaginationMixin, UserTokenRequiredMixin) from .models import (Bill, DatabaseService, MailinglistService, MailService, @@ -201,6 +202,29 @@ class MailView(ServiceListView): return context +class MailCreateView(CustomContextMixin, UserTokenRequiredMixin, FormView): + service_class = MailService + template_name = "musician/mail_form.html" + form_class = MailForm + success_url = reverse_lazy("musician:mails") + + def get_form_kwargs(self): + kwargs = super().get_form_kwargs() + kwargs['domains'] = self.orchestra.retrieve_domain_list() + kwargs['mailboxes'] = self.orchestra.retrieve_mailbox_list() + return kwargs + + def form_valid(self, form): + # handle request errors e.g. 400 validation + try: + self.orchestra.create_mail_address(form.cleaned_data) + except HTTPError as e: + form.add_error(field='__all__', error=e) + return self.form_invalid(form) + + return super().form_valid(form) + + class MailingListsView(ServiceListView): service_class = MailinglistService template_name = "musician/mailinglists.html"