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 }}
+
+
+{% 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