diff --git a/musician/api.py b/musician/api.py index 1149eaa..1365b91 100644 --- a/musician/api.py +++ b/musician/api.py @@ -83,7 +83,7 @@ class Orchestra(object): response.raise_for_status() status = response.status_code - if render_as == "json": + if status < 500 and render_as == "json": output = response.json() else: output = response.content @@ -164,6 +164,10 @@ class Orchestra(object): url = urllib.parse.urljoin(self.base_url, path) return self.request("DELETE", url=url, render_as=None) + def create_mailbox(self, data): + resource = '{}-list'.format(Mailbox.api_name) + return self.request("POST", resource=resource, data=data, raise_exception=False) + def retrieve_mailbox_list(self): mailboxes = self.retrieve_service_list(Mailbox.api_name) return [Mailbox.new_from_json(mailbox_data) for mailbox_data in mailboxes] diff --git a/musician/forms.py b/musician/forms.py index 3f90a74..4823fd7 100644 --- a/musician/forms.py +++ b/musician/forms.py @@ -2,6 +2,7 @@ from django import forms from django.contrib.auth.forms import AuthenticationForm from django.core.exceptions import ValidationError +from django.utils.translation import gettext_lazy as _ from . import api @@ -57,3 +58,39 @@ class MailForm(forms.Form): "forward": self.cleaned_data["forward"], } return serialized_data + + +class MailboxCreateForm(forms.Form): + error_messages = { + 'password_mismatch': _('The two password fields didn’t match.'), + } + name = forms.CharField() + password = forms.CharField( + label=_("Password"), + strip=False, + widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}), + ) + password2 = forms.CharField( + label=_("Password confirmation"), + widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}), + strip=False, + help_text=_("Enter the same password as before, for verification."), + ) + + def clean_password2(self): + password = self.cleaned_data.get("password") + password2 = self.cleaned_data.get("password2") + if password and password2 and password != password2: + raise ValidationError( + self.error_messages['password_mismatch'], + code='password_mismatch', + ) + return password2 + + def serialize(self): + assert self.is_valid() + serialized_data = { + "name": self.cleaned_data["name"], + "password": self.cleaned_data["password2"], + } + return serialized_data diff --git a/musician/templates/musician/mailbox_form.html b/musician/templates/musician/mailbox_form.html new file mode 100644 index 0000000..bafff86 --- /dev/null +++ b/musician/templates/musician/mailbox_form.html @@ -0,0 +1,20 @@ +{% extends "musician/base.html" %} +{% load bootstrap4 i18n %} + +{% block content %} +

{{ service.verbose_name }}

+ +
+ {% csrf_token %} + {% bootstrap_form form %} + {% buttons %} + {% trans "Cancel" %} + + {% if form.instance %} +
+ {% trans "Delete" %} +
+ {% endif %} + {% endbuttons %} +
+{% endblock %} diff --git a/musician/templates/musician/mailboxes.html b/musician/templates/musician/mailboxes.html index 666c94b..0b1f8b0 100644 --- a/musician/templates/musician/mailboxes.html +++ b/musician/templates/musician/mailboxes.html @@ -35,6 +35,8 @@ {% endfor %} {% include "musician/components/table_paginator.html" %} - + + {% trans "New mailbox" %} + {% endblock %} diff --git a/musician/urls.py b/musician/urls.py index caa6799..a3740ab 100644 --- a/musician/urls.py +++ b/musician/urls.py @@ -24,6 +24,7 @@ urlpatterns = [ path('address//', views.MailUpdateView.as_view(), name='address-update'), path('address//delete/', views.AddressDeleteView.as_view(), name='address-delete'), path('mailboxes/', views.MailboxesView.as_view(), name='mailbox-list'), + path('mailboxes/new/', views.MailboxCreateView.as_view(), name='mailbox-create'), path('mailing-lists/', views.MailingListsView.as_view(), name='mailing-lists'), path('databases/', views.DatabasesView.as_view(), name='database-list'), path('saas/', views.SaasView.as_view(), name='saas-list'), diff --git a/musician/views.py b/musician/views.py index 6a1efc1..487434f 100644 --- a/musician/views.py +++ b/musician/views.py @@ -1,3 +1,5 @@ +import logging + from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.http import HttpResponse, HttpResponseRedirect @@ -16,7 +18,7 @@ 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, MailForm +from .forms import LoginForm, MailForm, MailboxCreateForm from .mixins import (CustomContextMixin, ExtendedPaginationMixin, UserTokenRequiredMixin) from .models import (Address, Bill, DatabaseService, Mailbox, MailinglistService, @@ -25,6 +27,9 @@ from .settings import ALLOWED_RESOURCES from .utils import get_bootstraped_percent +logger = logging.getLogger(__name__) + + class DashboardView(CustomContextMixin, UserTokenRequiredMixin, TemplateView): template_name = "musician/dashboard.html" extra_context = { @@ -315,6 +320,30 @@ class MailboxesView(ServiceListView): } +class MailboxCreateView(CustomContextMixin, UserTokenRequiredMixin, FormView): + service_class = Mailbox + template_name = "musician/mailbox_form.html" + form_class = MailboxCreateForm + success_url = reverse_lazy("musician:mailbox-list") + extra_context = {'service': service_class} + + def form_valid(self, form): + serialized_data = form.serialize() + status, response = self.orchestra.create_mailbox(serialized_data) + + if status >= 400: + if status == 400: + # handle errors & add to form (they will be rendered) + form.add_error(field=None, error=response) + return self.form_invalid(form) + else: + logger.error("{}: {}".format(status, response[:120])) + msg = "Sorry, an error occurred while processing your request ({})".format(status) + form.add_error(field='__all__', error=msg) + + return super().form_valid(form) + + class DatabasesView(ServiceListView): template_name = "musician/databases.html" service_class = DatabaseService