Add view to change mailbox password

This commit is contained in:
Santiago L 2021-10-14 11:09:59 +02:00
parent 6c773893f7
commit 33e68b5d07
8 changed files with 130 additions and 33 deletions

View File

@ -24,6 +24,7 @@ API_PATHS = {
'address-detail': 'addresses/{pk}/', 'address-detail': 'addresses/{pk}/',
'mailbox-list': 'mailboxes/', 'mailbox-list': 'mailboxes/',
'mailbox-detail': 'mailboxes/{pk}/', 'mailbox-detail': 'mailboxes/{pk}/',
'mailbox-password': 'mailboxes/{pk}/set_password/',
'mailinglist-list': 'lists/', 'mailinglist-list': 'lists/',
'saas-list': 'saas/', 'saas-list': 'saas/',
'website-list': 'websites/', 'website-list': 'websites/',
@ -194,6 +195,13 @@ class Orchestra(object):
# return self.request("DELETE", url=url, render_as=None) # return self.request("DELETE", url=url, render_as=None)
return self.request("PATCH", url=url, data={"is_active": False}) return self.request("PATCH", url=url, data={"is_active": False})
def set_password_mailbox(self, pk, data):
path = API_PATHS.get('mailbox-password').format_map({'pk': pk})
url = urllib.parse.urljoin(self.base_url, path)
status, response = self.request("POST", url=url, data=data, raise_exception=False)
return status, response
def retrieve_domain(self, pk): def retrieve_domain(self, pk):
path = API_PATHS.get('domain-detail').format_map({'pk': pk}) path = API_PATHS.get('domain-detail').format_map({'pk': pk})

View File

@ -60,6 +60,40 @@ class MailForm(forms.Form):
return serialized_data return serialized_data
class MailboxChangePasswordForm(forms.Form):
error_messages = {
'password_mismatch': _('The two password fields didnt match.'),
}
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 = {
"password": self.cleaned_data["password2"],
}
return serialized_data
class MailboxCreateForm(forms.Form): class MailboxCreateForm(forms.Form):
error_messages = { error_messages = {
'password_mismatch': _('The two password fields didnt match.'), 'password_mismatch': _('The two password fields didnt match.'),

View File

@ -4,31 +4,30 @@ a, a:hover, a:focus {
} }
a:hover { a:hover {
color: rgba(0,0,0,.7); color: rgba(0, 0, 0, .7);
} }
.btn-arrow-left{ .btn-arrow-left {
color: #eee; color: #eee;
background: #D3D0DA; background: #D3D0DA;
position: relative; position: relative;
padding: 8px 20px 8px 30px; padding: 8px 20px 8px 30px;
margin-left: 1em; /** equal value than arrow.left **/ margin-left: 1em;
/** equal value than arrow.left **/
} }
.btn-arrow-left::after, .btn-arrow-left::after, .btn-arrow-left::before {
.btn-arrow-left::before{
content: ""; content: "";
position: absolute; position: absolute;
top: 50%; top: 50%;
left: -1em; left: -1em;
margin-top: -19px; margin-top: -19px;
border-top: 19px solid transparent; border-top: 19px solid transparent;
border-bottom: 19px solid transparent; border-bottom: 19px solid transparent;
border-right: 1em solid; border-right: 1em solid;
} }
.btn-arrow-left::after{ .btn-arrow-left::after {
border-right-color: #D3D0DA; border-right-color: #D3D0DA;
z-index: 2; z-index: 2;
} }
@ -43,13 +42,12 @@ a:hover {
min-width: 280px; min-width: 280px;
max-width: 280px; max-width: 280px;
min-height: 100vh; min-height: 100vh;
position: fixed; position: fixed;
z-index: 999; z-index: 999;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
#sidebar #sidebar-services { #sidebar #sidebar-services {
flex-grow: 1; flex-grow: 1;
} }
@ -62,20 +60,20 @@ a:hover {
padding-left: 2rem; padding-left: 2rem;
padding-right: 2rem; padding-right: 2rem;
} }
#sidebar #sidebar-services { #sidebar #sidebar-services {
padding-left: 1rem; padding-left: 1rem;
padding-right: 1rem; padding-right: 1rem;
} }
#sidebar #user-profile-menu { #sidebar #user-profile-menu {
background:rgba(254, 251, 242, 0.25); background: rgba(254, 251, 242, 0.25);
} }
#sidebar ul.components { #sidebar ul.components {
padding: 20px 0; padding: 20px 0;
} }
#sidebar ul li a { #sidebar ul li a {
padding: 10px; padding: 10px;
font-size: 1.1em; font-size: 1.1em;
@ -89,25 +87,26 @@ a:hover {
} }
.vertical-center { .vertical-center {
min-height: 100%; /* Fallback for browsers do NOT support vh unit */ min-height: 100%;
min-height: 100vh; /* These two lines are counted as one :-) */ /* Fallback for browsers do NOT support vh unit */
min-height: 100vh;
/* These two lines are counted as one :-) */
display: flex; display: flex;
align-items: center; align-items: center;
} }
/** login **/ /** login **/
#body-login .jumbotron { #body-login .jumbotron {
background: #282532 no-repeat url("../images/logo-pangea-lilla-bg.svg") right; background: #282532 no-repeat url("../images/logo-pangea-lilla-bg.svg") right;
} }
#login-content { #login-content {
background:white; background: white;
padding: 2rem; padding: 2rem;
} }
#login-content input[type="text"].form-control, #login-content input[type="text"].form-control, #login-content input[type="password"].form-control {
#login-content input[type="password"].form-control {
border-radius: 0; border-radius: 0;
border: 0; border: 0;
border-bottom: 2px solid #8E8E8E; border-bottom: 2px solid #8E8E8E;
@ -121,6 +120,7 @@ a:hover {
margin-top: 1.5rem; margin-top: 1.5rem;
text-align: center; text-align: center;
} }
#login-footer a { #login-footer a {
color: #FEFBF2; color: #FEFBF2;
} }
@ -130,34 +130,37 @@ a:hover {
background-position: right 5% top 10%; background-position: right 5% top 10%;
color: #343434; color: #343434;
padding-left: 2rem; padding-left: 2rem;
margin-left: 280px; /** sidebar width **/ margin-left: 280px;
/** sidebar width **/
} }
/** services **/ /** services **/
h1.service-name {
h1.service-name {
font: Bold 26px/34px Roboto; font: Bold 26px/34px Roboto;
margin-top: 3rem; margin-top: 3rem;
} }
.service-description { .service-description {
font: 16px/21px Roboto; font: 16px/21px Roboto;
} }
.table.service-list { .table.service-list {
margin-top: 2rem; margin-top: 2rem;
table-layout: fixed; table-layout: fixed;
} }
/** TODO update theme instead of overriding **/ /** TODO update theme instead of overriding **/
.service-list thead.thead-dark th,
.service-card .card-header { .service-list thead.thead-dark th, .service-card .card-header {
background: rgba(80, 70, 110, 0.25); background: rgba(80, 70, 110, 0.25);
color: #50466E; color: #50466E;
border-color: transparent; border-color: transparent;
} }
/** /TODO **/ /** /TODO **/
.table.service-list td,
.table.service-list th { .table.service-list td, .table.service-list th {
vertical-align: middle; vertical-align: middle;
} }
@ -202,11 +205,10 @@ h1.service-name {
.service-card .card-body { .service-card .card-body {
color: #787878; color: #787878;
} }
.service-card .card-body i.fas { .service-card .card-body i.fas {
color:#9C9AA7; color: #9C9AA7;
} }
.service-manager-link { .service-manager-link {
@ -215,8 +217,7 @@ h1.service-name {
right: 15px; right: 15px;
} }
.service-card .service-manager-link a, .service-card .service-manager-link a, .service-card .service-manager-link a i.fas {
.service-card .service-manager-link a i.fas {
color: white; color: white;
} }
@ -243,11 +244,9 @@ h1.service-name {
font-variant: normal; font-variant: normal;
text-rendering: auto; text-rendering: auto;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
position: absolute; position: absolute;
top: 0; top: 0;
right: 10px; right: 10px;
color: #E8E7EB; color: #E8E7EB;
font-size: 2em; font-size: 2em;
} }
@ -308,3 +307,13 @@ h1.service-name {
border-top: 0; border-top: 0;
justify-content: center; justify-content: center;
} }
.roll-hover {
visibility: hidden;
display: inline-block;
margin-left: 2rem;
}
td:hover .roll-hover {
visibility: visible;
}

View File

@ -0,0 +1,15 @@
{% extends "musician/base.html" %}
{% load bootstrap4 i18n %}
{% block content %}
<h1 class="service-name">{% trans "Change password" %}: <span class="font-weight-light">{{ object.name }}</span></h1>
<form method="post">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<a class="btn btn-light mr-2" href="{% url 'musician:mailbox-list' %}">{% trans "Cancel" %}</a>
<button type="submit" class="btn btn-secondary">{% trans "Save" %}</button>
{% endbuttons %}
</form>
{% endblock %}

View File

@ -21,6 +21,7 @@
<button type="submit" class="btn btn-secondary">{% trans "Save" %}</button> <button type="submit" class="btn btn-secondary">{% trans "Save" %}</button>
{% if form.instance %} {% if form.instance %}
<div class="float-right"> <div class="float-right">
<a class="btn btn-outline-warning" href="{% url 'musician:mailbox-password' mailbox.id %}"><i class="fas fa-key"></i> {% trans "Change password" %}</a>
<a class="btn btn-danger" href="{% url 'musician:mailbox-delete' view.kwargs.pk %}">{% trans "Delete" %}</a> <a class="btn btn-danger" href="{% url 'musician:mailbox-delete' view.kwargs.pk %}">{% trans "Delete" %}</a>
</div> </div>
{% endif %} {% endif %}

View File

@ -21,7 +21,11 @@
{# <!-- Exclude (don't render) inactive mailboxes -->#} {# <!-- Exclude (don't render) inactive mailboxes -->#}
{% if mailbox.is_active %} {% if mailbox.is_active %}
<tr> <tr>
<td><a href="{% url 'musician:mailbox-update' mailbox.id %}">{{ mailbox.name }}</a></td> <td>
<a href="{% url 'musician:mailbox-update' mailbox.id %}">{{ mailbox.name }}</a>
<a class="roll-hover btn btn-outline-warning" href="{% url 'musician:mailbox-password' mailbox.id %}">
<i class="fas fa-key"></i> {% trans "Update password" %}</a>
</td>
<td>{{ mailbox.filtering }}</td> <td>{{ mailbox.filtering }}</td>
<td> <td>
{% for addr in mailbox.addresses %} {% for addr in mailbox.addresses %}

View File

@ -27,6 +27,7 @@ urlpatterns = [
path('mailboxes/new/', views.MailboxCreateView.as_view(), name='mailbox-create'), path('mailboxes/new/', views.MailboxCreateView.as_view(), name='mailbox-create'),
path('mailboxes/<int:pk>/', views.MailboxUpdateView.as_view(), name='mailbox-update'), path('mailboxes/<int:pk>/', views.MailboxUpdateView.as_view(), name='mailbox-update'),
path('mailboxes/<int:pk>/delete/', views.MailboxDeleteView.as_view(), name='mailbox-delete'), path('mailboxes/<int:pk>/delete/', views.MailboxDeleteView.as_view(), name='mailbox-delete'),
path('mailboxes/<int:pk>/change-password/', views.MailboxChangePasswordView.as_view(), name='mailbox-password'),
path('mailing-lists/', views.MailingListsView.as_view(), name='mailing-lists'), path('mailing-lists/', views.MailingListsView.as_view(), name='mailing-lists'),
path('databases/', views.DatabasesView.as_view(), name='database-list'), path('databases/', views.DatabasesView.as_view(), name='database-list'),
path('saas/', views.SaasView.as_view(), name='saas-list'), path('saas/', views.SaasView.as_view(), name='saas-list'),

View File

@ -20,7 +20,7 @@ from requests.exceptions import HTTPError
from . import api, get_version from . import api, get_version
from .auth import login as auth_login from .auth import login as auth_login
from .auth import logout as auth_logout from .auth import logout as auth_logout
from .forms import LoginForm, MailboxCreateForm, MailboxUpdateForm, MailForm from .forms import LoginForm, MailboxChangePasswordForm, MailboxCreateForm, MailboxUpdateForm, MailForm
from .mixins import (CustomContextMixin, ExtendedPaginationMixin, from .mixins import (CustomContextMixin, ExtendedPaginationMixin,
UserTokenRequiredMixin) UserTokenRequiredMixin)
from .models import (Address, Bill, DatabaseService, Mailbox, from .models import (Address, Bill, DatabaseService, Mailbox,
@ -435,6 +435,31 @@ class MailboxDeleteView(CustomContextMixin, UserTokenRequiredMixin, DeleteView):
logger.error("Error sending email to managers", exc_info=True) logger.error("Error sending email to managers", exc_info=True)
class MailboxChangePasswordView(CustomContextMixin, UserTokenRequiredMixin, FormView):
template_name = "musician/mailbox_change_password.html"
form_class = MailboxChangePasswordForm
success_url = reverse_lazy("musician:mailbox-list")
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
self.object = self.get_object()
context.update({
'object': self.object,
})
return context
def get_object(self, queryset=None):
obj = self.orchestra.retrieve_mailbox(self.kwargs['pk'])
return obj
def form_valid(self, form):
data = {
'password': form.cleaned_data['password2']
}
status, response = self.orchestra.set_password_mailbox(self.kwargs['pk'], data)
return super().form_valid(form)
class DatabasesView(ServiceListView): class DatabasesView(ServiceListView):
template_name = "musician/databases.html" template_name = "musician/databases.html"
service_class = DatabaseService service_class = DatabaseService