Add view to change mailbox password
This commit is contained in:
parent
6c773893f7
commit
33e68b5d07
|
@ -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})
|
||||||
|
|
||||||
|
|
|
@ -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 didn’t 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 didn’t match.'),
|
'password_mismatch': _('The two password fields didn’t match.'),
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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 %}
|
|
@ -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 %}
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
|
@ -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'),
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue