Compare commits
76 Commits
whitesourc
...
master
Author | SHA1 | Date |
---|---|---|
Santiago L | 2062c0c519 | |
Santiago L | fc55c33c90 | |
Santiago L | 1e3a919390 | |
Santiago L | 9b4f2ba3da | |
Santiago L | 41f5493368 | |
RubenPX | c1f25a73da | |
RubenPX | ca3a8c4639 | |
RubenPX | ed3ad7cda0 | |
Santiago L | 49d49a3044 | |
Santiago L | 4911cf4226 | |
Santiago L | 9e193107cd | |
Santiago L | 872243a8c6 | |
Santiago L | 19f5229536 | |
Santiago L | 66530351ad | |
Santiago L | 249a1182d4 | |
RubenPX | 1816301952 | |
RubenPX | feb591ea79 | |
Santiago L | 179918bd62 | |
Santiago L | 57db1eed80 | |
Santiago L | afce4a5527 | |
Santiago L | 62a1d57f7d | |
RubenPX | 6b7cad86f2 | |
RubenPX | 80a93ea8c0 | |
RubenPX | 90b0956f71 | |
dependabot[bot] | b6b980ebaa | |
Santiago L | bb70914066 | |
Santiago L | bc84603b5e | |
Santiago L | 70b256d1ed | |
KryptoPX | 7d799092cd | |
KryptoPX | a7d025fc01 | |
KryptoPX | e203b43a69 | |
KryptoPX | 828bb5f0de | |
KryptoPX | 95d2998d05 | |
KryptoPX | a0cc4d0a41 | |
KryptoPX | 560c48ddaa | |
Santiago L | 74bcbb43a1 | |
Santiago L | 3a7d920611 | |
Santiago L | dd8e7f1f52 | |
Santiago L | 3f46809620 | |
Santiago L | 44f9390bee | |
Santiago L | bd42b83ea3 | |
Santiago L | aee0267f17 | |
Santiago L | d7bd21d865 | |
Santiago L | d77b876a54 | |
Santiago L | b171cbf641 | |
Santiago L | 33e68b5d07 | |
Santiago L | 6c773893f7 | |
Santiago L | 2aab4a666f | |
Santiago L | a13bdeac56 | |
Santiago L | 056f472ee0 | |
Santiago L | ddd8ecf634 | |
Santiago L | a0808896b4 | |
Santiago L | 9b52bc4b92 | |
Santiago L | 9e51457069 | |
Santiago L | ed5460c4b1 | |
Santiago L | b0366ff1d0 | |
Santiago L | 98dfa7a9f4 | |
Santiago L | 6d7ee0b76a | |
Santiago L | a9c59edbf2 | |
Santiago L | 0246d0a22e | |
Santiago L | 9ba1d0a23c | |
Santiago L | bb07bcd126 | |
Santiago L | 2a1a82f271 | |
Santiago L | 0d327127f5 | |
Santiago L | 77577a67da | |
Santiago L | 29c752e572 | |
Santiago L | 4d5497f2fa | |
Santiago L | 7ff01d60ef | |
Santiago L | f635721831 | |
dependabot[bot] | 3061ab34d3 | |
Santiago L | 30bb572589 | |
Santiago L | 5281e9595e | |
Santiago L | db6715808b | |
dependabot[bot] | 13ac215973 | |
Santiago Lamora | 1d5d3a5ed3 | |
Santiago Lamora | c68aec5dc3 |
|
@ -5,8 +5,17 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|||
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## master
|
||||
|
||||
## [0.2.0] 2022-03-11
|
||||
- [added] Language selector.
|
||||
- [added] Help link.
|
||||
- [changed] Order bills by creation date DESC.
|
||||
|
||||
## [0.2.0-beta1] 2022-02-05
|
||||
- [added] Write operations on mails section (addresses, mailboxes and forward).
|
||||
- [changed] Include @pangea.org mail addresses (#4).
|
||||
- [fixed] Error on login when user never has logged into the system (#6).
|
||||
- [fixed] Dashboard layout issues on tablet and mobile.
|
||||
|
||||
## [0.1] - 2020-01-29
|
||||
- Login & logout methods using backend as auth method
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
FROM python
|
||||
|
||||
WORKDIR /home
|
||||
|
||||
RUN python3 -m pip install --upgrade pip
|
||||
RUN pip install wheel
|
||||
|
||||
COPY . .
|
||||
RUN pip install -r requirements.txt
|
||||
RUN python manage.py migrate
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT [ "python", "manage.py", "runserver", "0.0.0.0:8080" ]
|
|
@ -1,6 +1,12 @@
|
|||
# django musician
|
||||
Python code is written following [PEP 8](https://www.python.org/dev/peps/pep-0008/) sytle guide and it is based on [Django framework](https://djangoproject.com).
|
||||
|
||||
## Quickstart development
|
||||
Start development environment based on docker compose by running:
|
||||
```bash
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
## How do I get set up?
|
||||
|
||||
1. Install Python and its packet manager (pip)
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
version: "3.9"
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- .:/home
|
|
@ -2,7 +2,7 @@
|
|||
Package metadata definition.
|
||||
"""
|
||||
|
||||
VERSION = (0, 2, 0, 'alpha', 1)
|
||||
VERSION = (0, 2, 0, 'final', 0)
|
||||
|
||||
|
||||
def get_version():
|
||||
|
|
135
musician/api.py
135
musician/api.py
|
@ -1,14 +1,12 @@
|
|||
import requests
|
||||
import urllib.parse
|
||||
|
||||
from itertools import groupby
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from django.http import Http404
|
||||
from django.urls.exceptions import NoReverseMatch
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .models import Domain, DatabaseService, MailService, SaasService, UserAccount, WebSite
|
||||
|
||||
from .models import Address, DatabaseService, Domain, Mailbox, SaasService, UserAccount, WebSite
|
||||
|
||||
DOMAINS_PATH = 'domains/'
|
||||
TOKEN_PATH = '/api-token-auth/'
|
||||
|
@ -23,7 +21,10 @@ API_PATHS = {
|
|||
'domain-list': 'domains/',
|
||||
'domain-detail': 'domains/{pk}/',
|
||||
'address-list': 'addresses/',
|
||||
'address-detail': 'addresses/{pk}/',
|
||||
'mailbox-list': 'mailboxes/',
|
||||
'mailbox-detail': 'mailboxes/{pk}/',
|
||||
'mailbox-password': 'mailboxes/{pk}/set_password/',
|
||||
'mailinglist-list': 'lists/',
|
||||
'saas-list': 'saas/',
|
||||
'website-list': 'websites/',
|
||||
|
@ -62,7 +63,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,14 +74,17 @@ 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()
|
||||
|
||||
status = response.status_code
|
||||
if render_as == "json":
|
||||
if status < 500 and render_as == "json":
|
||||
output = response.json()
|
||||
else:
|
||||
output = response.content
|
||||
|
@ -109,52 +113,95 @@ class Orchestra(object):
|
|||
raise Http404(_("No domain found matching the query"))
|
||||
return bill_pdf
|
||||
|
||||
def create_mail_address(self, data):
|
||||
resource = '{}-list'.format(Address.api_name)
|
||||
return self.request("POST", resource=resource, data=data)
|
||||
|
||||
def retrieve_mail_address(self, pk):
|
||||
path = API_PATHS.get('address-detail').format_map({'pk': pk})
|
||||
url = urllib.parse.urljoin(self.base_url, path)
|
||||
status, data = self.request("GET", url=url, raise_exception=False)
|
||||
if status == 404:
|
||||
raise Http404(_("No object found matching the query"))
|
||||
|
||||
return Address.new_from_json(data)
|
||||
|
||||
def update_mail_address(self, pk, data):
|
||||
path = API_PATHS.get('address-detail').format_map({'pk': pk})
|
||||
url = urllib.parse.urljoin(self.base_url, path)
|
||||
return self.request("PUT", url=url, data=data)
|
||||
|
||||
def retrieve_mail_address_list(self, querystring=None):
|
||||
def get_mailbox_id(value):
|
||||
mailboxes = value.get('mailboxes')
|
||||
|
||||
# forwarded address should not grouped
|
||||
if len(mailboxes) == 0:
|
||||
return value.get('name')
|
||||
|
||||
return mailboxes[0]['id']
|
||||
|
||||
# retrieve mails applying filters (if any)
|
||||
raw_data = self.retrieve_service_list(
|
||||
MailService.api_name,
|
||||
Address.api_name,
|
||||
querystring=querystring,
|
||||
)
|
||||
|
||||
# group addresses with the same mailbox
|
||||
addresses = []
|
||||
for key, group in groupby(raw_data, get_mailbox_id):
|
||||
aliases = []
|
||||
data = {}
|
||||
for thing in group:
|
||||
aliases.append(thing.pop('name'))
|
||||
data = thing
|
||||
|
||||
data['names'] = aliases
|
||||
addresses.append(MailService.new_from_json(data))
|
||||
addresses = [Address.new_from_json(data) for data in raw_data]
|
||||
|
||||
# PATCH to include Pangea addresses not shown by orchestra
|
||||
# described on issue #4
|
||||
raw_mailboxes = self.retrieve_service_list('mailbox')
|
||||
for mailbox in raw_mailboxes:
|
||||
if mailbox['addresses'] == []:
|
||||
address_data = {
|
||||
'names': [mailbox['name']],
|
||||
'forward': '',
|
||||
'domain': {
|
||||
'name': 'pangea.org.',
|
||||
},
|
||||
'mailboxes': [mailbox],
|
||||
}
|
||||
pangea_address = MailService.new_from_json(address_data)
|
||||
addresses.append(pangea_address)
|
||||
# TODO(@slamora) disabled hacky patch because breaks another funtionalities
|
||||
# XXX Fix it on orchestra instead of here???
|
||||
# raw_mailboxes = self.retrieve_mailbox_list()
|
||||
# for mailbox in raw_mailboxes:
|
||||
# if mailbox['addresses'] == []:
|
||||
# address_data = {
|
||||
# 'names': [mailbox['name']],
|
||||
# 'forward': '',
|
||||
# 'domain': {
|
||||
# 'name': 'pangea.org.',
|
||||
# },
|
||||
# 'mailboxes': [mailbox],
|
||||
# }
|
||||
# pangea_address = Address.new_from_json(address_data)
|
||||
# addresses.append(pangea_address)
|
||||
|
||||
return addresses
|
||||
|
||||
def delete_mail_address(self, pk):
|
||||
path = API_PATHS.get('address-detail').format_map({'pk': pk})
|
||||
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(self, pk):
|
||||
path = API_PATHS.get('mailbox-detail').format_map({'pk': pk})
|
||||
|
||||
url = urllib.parse.urljoin(self.base_url, path)
|
||||
status, data_json = self.request("GET", url=url, raise_exception=False)
|
||||
if status == 404:
|
||||
raise Http404(_("No mailbox found matching the query"))
|
||||
return Mailbox.new_from_json(data_json)
|
||||
|
||||
def update_mailbox(self, pk, data):
|
||||
path = API_PATHS.get('mailbox-detail').format_map({'pk': pk})
|
||||
url = urllib.parse.urljoin(self.base_url, path)
|
||||
status, response = self.request("PATCH", url=url, data=data, raise_exception=False)
|
||||
return status, response
|
||||
|
||||
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]
|
||||
|
||||
def delete_mailbox(self, pk):
|
||||
path = API_PATHS.get('mailbox-detail').format_map({'pk': pk})
|
||||
url = urllib.parse.urljoin(self.base_url, path)
|
||||
# Mark as inactive instead of deleting
|
||||
# return self.request("DELETE", url=url, render_as=None)
|
||||
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):
|
||||
path = API_PATHS.get('domain-detail').format_map({'pk': pk})
|
||||
|
||||
|
@ -174,8 +221,8 @@ class Orchestra(object):
|
|||
querystring = "domain={}".format(domain_json['id'])
|
||||
|
||||
# retrieve services associated to a domain
|
||||
domain_json['mails'] = self.retrieve_service_list(
|
||||
MailService.api_name, querystring)
|
||||
domain_json['addresses'] = self.retrieve_service_list(
|
||||
Address.api_name, querystring)
|
||||
|
||||
# retrieve websites (as they cannot be filtered by domain on the API we should do it here)
|
||||
domain_json['websites'] = self.filter_websites_by_domain(websites, domain_json['id'])
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
|
||||
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
|
||||
|
||||
|
@ -20,3 +23,135 @@ 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):
|
||||
self.instance = kwargs.pop('instance', None)
|
||||
if self.instance is not None:
|
||||
kwargs['initial'] = self.instance.deserialize()
|
||||
|
||||
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
|
||||
|
||||
def serialize(self):
|
||||
assert hasattr(self, 'cleaned_data')
|
||||
serialized_data = {
|
||||
"name": self.cleaned_data["name"],
|
||||
"domain": {"url": self.cleaned_data["domain"]},
|
||||
"mailboxes": [{"url": mbox} for mbox in self.cleaned_data["mailboxes"]],
|
||||
"forward": self.cleaned_data["forward"],
|
||||
}
|
||||
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):
|
||||
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."),
|
||||
)
|
||||
addresses = forms.MultipleChoiceField(required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
addresses = kwargs.pop('addresses')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['addresses'].choices = [(addr.url, addr.full_address_name) for addr in addresses]
|
||||
|
||||
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"],
|
||||
"addresses": self.cleaned_data["addresses"],
|
||||
}
|
||||
return serialized_data
|
||||
|
||||
|
||||
class MailboxUpdateForm(forms.Form):
|
||||
addresses = forms.MultipleChoiceField(required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.instance = kwargs.pop('instance', None)
|
||||
if self.instance is not None:
|
||||
kwargs['initial'] = self.instance.deserialize()
|
||||
|
||||
addresses = kwargs.pop('addresses')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['addresses'].choices = [(addr.url, addr.full_address_name) for addr in addresses]
|
||||
|
||||
def serialize(self):
|
||||
assert self.is_valid()
|
||||
serialized_data = {
|
||||
"addresses": self.cleaned_data["addresses"],
|
||||
}
|
||||
return serialized_data
|
||||
|
|
|
@ -7,8 +7,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-01-23 17:49+0100\n"
|
||||
"PO-Revision-Date: 2020-01-28 17:27+0100\n"
|
||||
"POT-Creation-Date: 2021-11-24 11:04+0100\n"
|
||||
"PO-Revision-Date: 2021-11-25 12:53+0100\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Language: ca\n"
|
||||
|
@ -16,12 +16,36 @@ msgstr ""
|
|||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 2.2.4\n"
|
||||
"X-Generator: Poedit 3.0\n"
|
||||
|
||||
#: api.py:108 api.py:117
|
||||
#: api.py:113 api.py:211
|
||||
msgid "No domain found matching the query"
|
||||
msgstr "No trobem cap domini que coincideixi amb la teva consulta"
|
||||
|
||||
#: api.py:125
|
||||
msgid "No object found matching the query"
|
||||
msgstr "No trobem cap objecte que coincideixi amb la teva consulta"
|
||||
|
||||
#: api.py:178
|
||||
msgid "No mailbox found matching the query"
|
||||
msgstr "No trobem cap bústia que coincideixi amb la teva consulta"
|
||||
|
||||
#: forms.py:65 forms.py:99
|
||||
msgid "The two password fields didn’t match."
|
||||
msgstr "Les contrasenyes introduïdes no coincideixen."
|
||||
|
||||
#: forms.py:68 forms.py:103
|
||||
msgid "Password"
|
||||
msgstr "Contrasenya"
|
||||
|
||||
#: forms.py:73 forms.py:108
|
||||
msgid "Password confirmation"
|
||||
msgstr "Confirma la contrasenya"
|
||||
|
||||
#: forms.py:76 forms.py:111
|
||||
msgid "Enter the same password as before, for verification."
|
||||
msgstr "Introdueix la mateixa contrasenya per verificar-la."
|
||||
|
||||
#: mixins.py:14
|
||||
msgid "Domains & websites"
|
||||
msgstr "Dominis i llocs web"
|
||||
|
@ -31,12 +55,12 @@ msgid "Mails"
|
|||
msgstr "Correus"
|
||||
|
||||
#. Translators: This message appears on the page title
|
||||
#: mixins.py:16 views.py:226
|
||||
#: mixins.py:16 views.py:296
|
||||
msgid "Mailing lists"
|
||||
msgstr "Llistes de correu"
|
||||
|
||||
#. Translators: This message appears on the page title
|
||||
#: mixins.py:17 models.py:138 views.py:255
|
||||
#: mixins.py:17 models.py:147 views.py:480
|
||||
msgid "Databases"
|
||||
msgstr "Bases de dades"
|
||||
|
||||
|
@ -44,36 +68,40 @@ msgstr "Bases de dades"
|
|||
msgid "SaaS"
|
||||
msgstr "SaaS"
|
||||
|
||||
#: models.py:139
|
||||
#, fuzzy
|
||||
#: models.py:148
|
||||
msgid "Description details for databases page."
|
||||
msgstr "Consulta la configuració de les teves bases de dades."
|
||||
|
||||
#. Translators: This message appears on the page title
|
||||
#: models.py:200 views.py:169
|
||||
#: models.py:235 views.py:185
|
||||
msgid "Mail addresses"
|
||||
msgstr "Adreces de correu"
|
||||
|
||||
#: models.py:201
|
||||
#, fuzzy
|
||||
#: models.py:236
|
||||
msgid "Description details for mail addresses page."
|
||||
msgstr "Consulta aquí totes les adreces de correu que tens actives."
|
||||
|
||||
#: models.py:243
|
||||
#: models.py:311
|
||||
msgid "Mailbox"
|
||||
msgstr "Bústia de correu"
|
||||
|
||||
#: models.py:312
|
||||
msgid "Description details for mailbox page."
|
||||
msgstr "Aquí trobaràs el detall de les bústies de correu que tens actives."
|
||||
|
||||
#: models.py:337
|
||||
msgid "Mailing list"
|
||||
msgstr "Llista de correu"
|
||||
|
||||
#: models.py:244
|
||||
#, fuzzy
|
||||
#: models.py:338
|
||||
msgid "Description details for mailinglist page."
|
||||
msgstr "Consulta aquí els detalls de les teves llistes de correu."
|
||||
|
||||
#: models.py:267
|
||||
#: models.py:364
|
||||
msgid "Software as a Service (SaaS)"
|
||||
msgstr "Software as a Service (SaaS)"
|
||||
|
||||
#: models.py:268
|
||||
#, fuzzy
|
||||
#: models.py:365
|
||||
msgid "Description details for SaaS page."
|
||||
msgstr ""
|
||||
"Si tens algun servei SaaS (Software as a Service) contractat, aquí trobaràs "
|
||||
|
@ -100,6 +128,60 @@ msgstr ""
|
|||
"Envia un correu a <a href=\"mailto:%(support_email)s\">%(support_email)s</a> "
|
||||
"indicant el teu nom d’usuari/a i t’explicarem què fer."
|
||||
|
||||
#: templates/musician/address_check_delete.html:7
|
||||
#, python-format
|
||||
msgid "Are you sure that you want remove the address: \"%(address_name)s\"?"
|
||||
msgstr ""
|
||||
"Estàs segur/a que vols esborrar l’adreça de correu: \"%(address_name)s\"?"
|
||||
|
||||
#: templates/musician/address_check_delete.html:8
|
||||
#: templates/musician/mailbox_check_delete.html:11
|
||||
msgid "WARNING: This action cannot be undone."
|
||||
msgstr "AVÍS: Aquesta acció es irreversible."
|
||||
|
||||
#: templates/musician/address_check_delete.html:9
|
||||
#: templates/musician/address_form.html:15
|
||||
#: templates/musician/mailbox_check_delete.html:12
|
||||
#: templates/musician/mailbox_form.html:25
|
||||
msgid "Delete"
|
||||
msgstr "Esborrar"
|
||||
|
||||
#: templates/musician/address_check_delete.html:10
|
||||
#: templates/musician/address_form.html:11
|
||||
#: templates/musician/mailbox_change_password.html:11
|
||||
#: templates/musician/mailbox_check_delete.html:13
|
||||
#: templates/musician/mailbox_form.html:20
|
||||
msgid "Cancel"
|
||||
msgstr "Cancel·lar"
|
||||
|
||||
#: templates/musician/address_form.html:12
|
||||
#: templates/musician/mailbox_change_password.html:12
|
||||
#: templates/musician/mailbox_form.html:21
|
||||
msgid "Save"
|
||||
msgstr "Desar"
|
||||
|
||||
#: templates/musician/addresses.html:15
|
||||
msgid "Email"
|
||||
msgstr "Correu electrònic"
|
||||
|
||||
#: templates/musician/addresses.html:16
|
||||
msgid "Domain"
|
||||
msgstr "Domini"
|
||||
|
||||
#. Translators: This message appears on the page title
|
||||
#: templates/musician/addresses.html:17 templates/musician/mail_base.html:22
|
||||
#: views.py:325
|
||||
msgid "Mailboxes"
|
||||
msgstr "Bústies de correu"
|
||||
|
||||
#: templates/musician/addresses.html:18
|
||||
msgid "Forward"
|
||||
msgstr "Redirecció"
|
||||
|
||||
#: templates/musician/addresses.html:38
|
||||
msgid "New mail address"
|
||||
msgstr "Nova adreça de correu"
|
||||
|
||||
#: templates/musician/base.html:60
|
||||
msgid "Settings"
|
||||
msgstr "Configuració"
|
||||
|
@ -110,7 +192,7 @@ msgstr "Perfil"
|
|||
|
||||
#. Translators: This message appears on the page title
|
||||
#: templates/musician/base.html:64 templates/musician/billing.html:6
|
||||
#: views.py:147
|
||||
#: views.py:163
|
||||
msgid "Billing"
|
||||
msgstr "Factures"
|
||||
|
||||
|
@ -119,7 +201,6 @@ msgid "Log out"
|
|||
msgstr "Surt"
|
||||
|
||||
#: templates/musician/billing.html:7
|
||||
#, fuzzy
|
||||
msgid "Billing page description."
|
||||
msgstr "Consulta i descarrega les teves factures."
|
||||
|
||||
|
@ -132,7 +213,7 @@ msgid "Bill date"
|
|||
msgstr "Data de la factura"
|
||||
|
||||
#: templates/musician/billing.html:21 templates/musician/databases.html:17
|
||||
#: templates/musician/domain_detail.html:17 templates/musician/mail.html:22
|
||||
#: templates/musician/domain_detail.html:17
|
||||
msgid "Type"
|
||||
msgstr "Tipus"
|
||||
|
||||
|
@ -142,7 +223,7 @@ msgstr "Total"
|
|||
|
||||
#: templates/musician/billing.html:23
|
||||
msgid "Download PDF"
|
||||
msgstr "Descarrega un PDF"
|
||||
msgstr "Descarrega el PDF"
|
||||
|
||||
#: templates/musician/components/table_paginator.html:15
|
||||
msgid "Previous"
|
||||
|
@ -165,90 +246,85 @@ msgstr "El darrer cop que vas accedir va ser el dia: %(last_login)s"
|
|||
msgid "It's the first time you log into the system, welcome on board!"
|
||||
msgstr "És el primer cop que accedeixes, et donem la benvinguda!"
|
||||
|
||||
#: templates/musician/dashboard.html:24
|
||||
#: templates/musician/dashboard.html:29
|
||||
msgid "Notifications"
|
||||
msgstr "Notificacions"
|
||||
|
||||
#: templates/musician/dashboard.html:28
|
||||
#: templates/musician/dashboard.html:33
|
||||
msgid "There is no notifications at this time."
|
||||
msgstr "No tens cap notificació."
|
||||
|
||||
#: templates/musician/dashboard.html:35
|
||||
#: templates/musician/dashboard.html:40
|
||||
msgid "Your domains and websites"
|
||||
msgstr "Els teus dominis i llocs web"
|
||||
|
||||
#: templates/musician/dashboard.html:36
|
||||
#, fuzzy
|
||||
#: templates/musician/dashboard.html:41
|
||||
msgid "Dashboard page description."
|
||||
msgstr ""
|
||||
"Aquest és el teu panell de gestió, des d’on podràs consultar la configuració "
|
||||
"dels serveis que Pangea t’ofereix."
|
||||
|
||||
#: templates/musician/dashboard.html:51
|
||||
#: templates/musician/dashboard.html:56
|
||||
msgid "view configuration"
|
||||
msgstr "veure la configuració"
|
||||
|
||||
#: templates/musician/dashboard.html:58
|
||||
#: templates/musician/dashboard.html:63
|
||||
msgid "Expiration date"
|
||||
msgstr "Data de venciment"
|
||||
|
||||
#: templates/musician/dashboard.html:65
|
||||
#: templates/musician/dashboard.html:70
|
||||
msgid "Mail"
|
||||
msgstr "Correu"
|
||||
|
||||
#: templates/musician/dashboard.html:68
|
||||
#: templates/musician/dashboard.html:73
|
||||
msgid "mail addresses created"
|
||||
msgstr "adreces de correu creades"
|
||||
|
||||
#: templates/musician/dashboard.html:71
|
||||
msgid "mail address left"
|
||||
msgstr "adreces de correu per activar"
|
||||
|
||||
#: templates/musician/dashboard.html:77
|
||||
#: templates/musician/dashboard.html:78
|
||||
msgid "Mail list"
|
||||
msgstr "Llista de correu"
|
||||
|
||||
#. Translators: This message appears on the page title
|
||||
#: templates/musician/dashboard.html:82 views.py:264
|
||||
#: templates/musician/dashboard.html:83 views.py:489
|
||||
msgid "Software as a Service"
|
||||
msgstr "Software as a Service"
|
||||
|
||||
#: templates/musician/dashboard.html:84
|
||||
#: templates/musician/dashboard.html:85
|
||||
msgid "Nothing installed"
|
||||
msgstr "No tens res instal·lat"
|
||||
|
||||
#: templates/musician/dashboard.html:89 views.py:42
|
||||
#: templates/musician/dashboard.html:90 views.py:57
|
||||
msgid "Disk usage"
|
||||
msgstr "Ús del disc"
|
||||
|
||||
#: templates/musician/dashboard.html:106
|
||||
#: templates/musician/dashboard.html:107
|
||||
msgid "Configuration details"
|
||||
msgstr "Detalls de configuració"
|
||||
|
||||
#: templates/musician/dashboard.html:113
|
||||
#: templates/musician/dashboard.html:114
|
||||
msgid "FTP access:"
|
||||
msgstr "Accés FTP:"
|
||||
|
||||
#. Translators: domain configuration detail modal
|
||||
#: templates/musician/dashboard.html:115
|
||||
#: templates/musician/dashboard.html:116
|
||||
msgid "Contact with the support team to get details concerning FTP access."
|
||||
msgstr ""
|
||||
"Contacteu-nos a <a href=“mailto:%(support_email)s”>%(support_email)s</a> per "
|
||||
"Escriu-nos a <a href=“mailto:%(support_email)s”>%(support_email)s</a> per "
|
||||
"saber com accedir al FTP."
|
||||
|
||||
#: templates/musician/dashboard.html:124
|
||||
#: templates/musician/dashboard.html:125
|
||||
msgid "No website configured."
|
||||
msgstr "No hi ha cap web configurada."
|
||||
|
||||
#: templates/musician/dashboard.html:126
|
||||
#: templates/musician/dashboard.html:127
|
||||
msgid "Root directory:"
|
||||
msgstr "Directori arrel:"
|
||||
|
||||
#: templates/musician/dashboard.html:127
|
||||
#: templates/musician/dashboard.html:128
|
||||
msgid "Type:"
|
||||
msgstr "Tipus:"
|
||||
|
||||
#: templates/musician/dashboard.html:132
|
||||
#: templates/musician/dashboard.html:133
|
||||
msgid "View DNS records"
|
||||
msgstr "Veure registres DNS"
|
||||
|
||||
|
@ -279,7 +355,6 @@ msgid "DNS settings for"
|
|||
msgstr "Configuració DNS per a"
|
||||
|
||||
#: templates/musician/domain_detail.html:8
|
||||
#, fuzzy
|
||||
msgid "DNS settings page description."
|
||||
msgstr "Consulta aquí la teva configuració DNS."
|
||||
|
||||
|
@ -287,25 +362,66 @@ msgstr "Consulta aquí la teva configuració DNS."
|
|||
msgid "Value"
|
||||
msgstr "Valor"
|
||||
|
||||
#: templates/musician/mail.html:6 templates/musician/mailinglists.html:6
|
||||
#: templates/musician/mail_base.html:6 templates/musician/mailinglists.html:6
|
||||
msgid "Go to global"
|
||||
msgstr "Totes les adreces"
|
||||
|
||||
#: templates/musician/mail.html:9 templates/musician/mailinglists.html:9
|
||||
#: templates/musician/mail_base.html:10 templates/musician/mailinglists.html:9
|
||||
msgid "for"
|
||||
msgstr "per a"
|
||||
|
||||
#: templates/musician/mail.html:20
|
||||
msgid "Mail address"
|
||||
msgstr "Adreça de correu"
|
||||
#: templates/musician/mail_base.html:18 templates/musician/mailboxes.html:16
|
||||
msgid "Addresses"
|
||||
msgstr "Adreces de correu"
|
||||
|
||||
#: templates/musician/mail.html:21
|
||||
msgid "Aliases"
|
||||
msgstr "Àlies"
|
||||
#: templates/musician/mailbox_change_password.html:5
|
||||
#: templates/musician/mailbox_form.html:24
|
||||
msgid "Change password"
|
||||
msgstr "Canvia la contrasenya"
|
||||
|
||||
#: templates/musician/mail.html:23
|
||||
msgid "Type details"
|
||||
msgstr "Detalls de cada tipus"
|
||||
#: templates/musician/mailbox_check_delete.html:7
|
||||
#, python-format
|
||||
msgid "Are you sure that you want remove the mailbox: \"%(name)s\"?"
|
||||
msgstr "Estàs segur/a que vols esborrar la bústia de correu: \"%(name)s\"?"
|
||||
|
||||
#: templates/musician/mailbox_check_delete.html:9
|
||||
msgid ""
|
||||
"All mailbox's messages will be <strong>deleted and cannot be recovered</"
|
||||
"strong>."
|
||||
msgstr ""
|
||||
"Tots els missatges <strong>s'esborraran i no es podran recuperar</strong>."
|
||||
|
||||
#: templates/musician/mailbox_form.html:9
|
||||
msgid "Warning!"
|
||||
msgstr "Atenció!"
|
||||
|
||||
#: templates/musician/mailbox_form.html:9
|
||||
msgid ""
|
||||
"You have reached the limit of mailboxes of your subscription so "
|
||||
"<strong>extra fees</strong> may apply."
|
||||
msgstr ""
|
||||
"Has assolit el límit de bústies de correu de la teva subscripció, les noves "
|
||||
"bústies poden implicar <strong>costos addicionals</strong>."
|
||||
|
||||
#: templates/musician/mailbox_form.html:10
|
||||
msgid "Close"
|
||||
msgstr "Tancar"
|
||||
|
||||
#: templates/musician/mailboxes.html:14
|
||||
msgid "Name"
|
||||
msgstr "Nom"
|
||||
|
||||
#: templates/musician/mailboxes.html:15
|
||||
msgid "Filtering"
|
||||
msgstr "Filtrat"
|
||||
|
||||
#: templates/musician/mailboxes.html:27
|
||||
msgid "Update password"
|
||||
msgstr "Actualitza la contrasenya"
|
||||
|
||||
#: templates/musician/mailboxes.html:43
|
||||
msgid "New mailbox"
|
||||
msgstr "Nova bústia de correu"
|
||||
|
||||
#: templates/musician/mailinglists.html:34
|
||||
msgid "Active"
|
||||
|
@ -316,7 +432,6 @@ msgid "Inactive"
|
|||
msgstr "Inactiu"
|
||||
|
||||
#: templates/musician/profile.html:7
|
||||
#, fuzzy
|
||||
msgid "Little description on profile page."
|
||||
msgstr "Canvia les teves dades d’accés i opcions de perfil des d’aquí."
|
||||
|
||||
|
@ -357,43 +472,67 @@ msgid "Open service admin panel"
|
|||
msgstr "Obre el panell d’administració del servei"
|
||||
|
||||
#. Translators: This message appears on the page title
|
||||
#: views.py:32
|
||||
#: views.py:41
|
||||
msgid "Dashboard"
|
||||
msgstr "Panell de gestió"
|
||||
|
||||
#: views.py:49
|
||||
#: views.py:66
|
||||
msgid "Traffic"
|
||||
msgstr "Tràfic"
|
||||
|
||||
#: views.py:56
|
||||
#: views.py:97
|
||||
msgid "Mailbox usage"
|
||||
msgstr "Ús d’espai a la bústia de correu"
|
||||
|
||||
#. Translators: This message appears on the page title
|
||||
#: views.py:96
|
||||
#: views.py:112
|
||||
msgid "User profile"
|
||||
msgstr "El teu perfil"
|
||||
|
||||
#. Translators: This message appears on the page title
|
||||
#: views.py:154
|
||||
#: views.py:170
|
||||
msgid "Download bill"
|
||||
msgstr "Descarrega la factura"
|
||||
|
||||
#: views.py:283
|
||||
msgid "Address deleted!"
|
||||
msgstr "S’ha suprimit l’adreça de correu"
|
||||
|
||||
#: views.py:285 views.py:422 views.py:469
|
||||
msgid "Cannot process your request, please try again later."
|
||||
msgstr ""
|
||||
"Ara no podem processar la teva petició, torna a intentar-ho una mica més "
|
||||
"tard sisplau"
|
||||
|
||||
#: views.py:420
|
||||
msgid "Mailbox deleted!"
|
||||
msgstr "S’ha suprimit la bústia de correu"
|
||||
|
||||
#: views.py:467
|
||||
msgid "Password updated!"
|
||||
msgstr "S’ha actualitzat la contrasenya"
|
||||
|
||||
#. Translators: This message appears on the page title
|
||||
#: views.py:272
|
||||
#: views.py:497
|
||||
msgid "Domain details"
|
||||
msgstr "Detalls del domini"
|
||||
|
||||
#. Translators: This message appears on the page title
|
||||
#: views.py:298
|
||||
#: views.py:523
|
||||
msgid "Login"
|
||||
msgstr "Accés"
|
||||
|
||||
#~ msgid "mail address left"
|
||||
#~ msgstr "adreces de correu per activar"
|
||||
|
||||
#~ msgid "Aliases"
|
||||
#~ msgstr "Àlies"
|
||||
|
||||
#~ msgid "Type details"
|
||||
#~ msgstr "Detalls de cada tipus"
|
||||
|
||||
#~ msgid "databases created"
|
||||
#~ msgstr "bases de dades creades"
|
||||
|
||||
#~ msgid "Username"
|
||||
#~ msgstr "Nom d’usuari/a"
|
||||
|
||||
#~ msgid "Password:"
|
||||
#~ msgstr "Contrasenya:"
|
||||
|
|
|
@ -7,8 +7,8 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-01-23 17:49+0100\n"
|
||||
"PO-Revision-Date: 2020-01-28 17:27+0100\n"
|
||||
"POT-Creation-Date: 2021-11-24 11:04+0100\n"
|
||||
"PO-Revision-Date: 2021-11-25 12:53+0100\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Language: es\n"
|
||||
|
@ -16,12 +16,36 @@ msgstr ""
|
|||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 2.2.4\n"
|
||||
"X-Generator: Poedit 3.0\n"
|
||||
|
||||
#: api.py:108 api.py:117
|
||||
#: api.py:113 api.py:211
|
||||
msgid "No domain found matching the query"
|
||||
msgstr "No hay dominios que coincidan con tu búsqueda"
|
||||
|
||||
#: api.py:125
|
||||
msgid "No object found matching the query"
|
||||
msgstr "No hay objetos que coincidan con tu búsqueda"
|
||||
|
||||
#: api.py:178
|
||||
msgid "No mailbox found matching the query"
|
||||
msgstr "No hay buzones de correo que coincidan con tu búsqueda"
|
||||
|
||||
#: forms.py:65 forms.py:99
|
||||
msgid "The two password fields didn’t match."
|
||||
msgstr "Las contraseñas introducidas no coinciden."
|
||||
|
||||
#: forms.py:68 forms.py:103
|
||||
msgid "Password"
|
||||
msgstr "Contraseña"
|
||||
|
||||
#: forms.py:73 forms.py:108
|
||||
msgid "Password confirmation"
|
||||
msgstr "Confirma la contraseña"
|
||||
|
||||
#: forms.py:76 forms.py:111
|
||||
msgid "Enter the same password as before, for verification."
|
||||
msgstr "Introduce la misma contraseña para verificarla"
|
||||
|
||||
#: mixins.py:14
|
||||
msgid "Domains & websites"
|
||||
msgstr "Dominios y sitios web"
|
||||
|
@ -31,12 +55,12 @@ msgid "Mails"
|
|||
msgstr "Correos"
|
||||
|
||||
#. Translators: This message appears on the page title
|
||||
#: mixins.py:16 views.py:226
|
||||
#: mixins.py:16 views.py:296
|
||||
msgid "Mailing lists"
|
||||
msgstr "Listas de correo"
|
||||
|
||||
#. Translators: This message appears on the page title
|
||||
#: mixins.py:17 models.py:138 views.py:255
|
||||
#: mixins.py:17 models.py:147 views.py:480
|
||||
msgid "Databases"
|
||||
msgstr "Bases de datos"
|
||||
|
||||
|
@ -44,36 +68,41 @@ msgstr "Bases de datos"
|
|||
msgid "SaaS"
|
||||
msgstr "SaaS"
|
||||
|
||||
#: models.py:139
|
||||
#, fuzzy
|
||||
#: models.py:148
|
||||
msgid "Description details for databases page."
|
||||
msgstr "Consulta la configuración de tus bases de datos."
|
||||
|
||||
#. Translators: This message appears on the page title
|
||||
#: models.py:200 views.py:169
|
||||
#: models.py:235 views.py:185
|
||||
msgid "Mail addresses"
|
||||
msgstr "Direcciones de correo"
|
||||
|
||||
#: models.py:201
|
||||
#, fuzzy
|
||||
#: models.py:236
|
||||
msgid "Description details for mail addresses page."
|
||||
msgstr "Consulta aquí todas las direcciones de correo que tienes activas."
|
||||
|
||||
#: models.py:243
|
||||
#: models.py:311
|
||||
msgid "Mailbox"
|
||||
msgstr "Buzón de correo"
|
||||
|
||||
#: models.py:312
|
||||
msgid "Description details for mailbox page."
|
||||
msgstr ""
|
||||
"Aquí encontrarás tus buzones de correo y sus detalles de configuración."
|
||||
|
||||
#: models.py:337
|
||||
msgid "Mailing list"
|
||||
msgstr "Lista de correo"
|
||||
|
||||
#: models.py:244
|
||||
#, fuzzy
|
||||
#: models.py:338
|
||||
msgid "Description details for mailinglist page."
|
||||
msgstr "Consulta aquí los detalles de tus listas de correo."
|
||||
|
||||
#: models.py:267
|
||||
#: models.py:364
|
||||
msgid "Software as a Service (SaaS)"
|
||||
msgstr "Software as a Service (SaaS)"
|
||||
|
||||
#: models.py:268
|
||||
#, fuzzy
|
||||
#: models.py:365
|
||||
msgid "Description details for SaaS page."
|
||||
msgstr ""
|
||||
"Si tienes algún servicio SaaS (Software as a Service) contratado, aquí "
|
||||
|
@ -100,6 +129,61 @@ msgstr ""
|
|||
"Envía un correo a <a href=“mailto:%(support_email)s”>%(support_email)s</a> "
|
||||
"indicando tu nombre de usuaria/o y te explicaremos qué hacer."
|
||||
|
||||
#: templates/musician/address_check_delete.html:7
|
||||
#, python-format
|
||||
msgid "Are you sure that you want remove the address: \"%(address_name)s\"?"
|
||||
msgstr ""
|
||||
"¿Estás seguro/a de que quieres borrar la dirección de correo "
|
||||
"\"%(address_name)s\"?"
|
||||
|
||||
#: templates/musician/address_check_delete.html:8
|
||||
#: templates/musician/mailbox_check_delete.html:11
|
||||
msgid "WARNING: This action cannot be undone."
|
||||
msgstr "AVISO: Esta acción es irreversible."
|
||||
|
||||
#: templates/musician/address_check_delete.html:9
|
||||
#: templates/musician/address_form.html:15
|
||||
#: templates/musician/mailbox_check_delete.html:12
|
||||
#: templates/musician/mailbox_form.html:25
|
||||
msgid "Delete"
|
||||
msgstr "Borrar"
|
||||
|
||||
#: templates/musician/address_check_delete.html:10
|
||||
#: templates/musician/address_form.html:11
|
||||
#: templates/musician/mailbox_change_password.html:11
|
||||
#: templates/musician/mailbox_check_delete.html:13
|
||||
#: templates/musician/mailbox_form.html:20
|
||||
msgid "Cancel"
|
||||
msgstr "Cancelar"
|
||||
|
||||
#: templates/musician/address_form.html:12
|
||||
#: templates/musician/mailbox_change_password.html:12
|
||||
#: templates/musician/mailbox_form.html:21
|
||||
msgid "Save"
|
||||
msgstr "Guardar"
|
||||
|
||||
#: templates/musician/addresses.html:15
|
||||
msgid "Email"
|
||||
msgstr "Correo electrónico"
|
||||
|
||||
#: templates/musician/addresses.html:16
|
||||
msgid "Domain"
|
||||
msgstr "Dominio"
|
||||
|
||||
#. Translators: This message appears on the page title
|
||||
#: templates/musician/addresses.html:17 templates/musician/mail_base.html:22
|
||||
#: views.py:325
|
||||
msgid "Mailboxes"
|
||||
msgstr "Buzones de correo"
|
||||
|
||||
#: templates/musician/addresses.html:18
|
||||
msgid "Forward"
|
||||
msgstr "Redirección"
|
||||
|
||||
#: templates/musician/addresses.html:38
|
||||
msgid "New mail address"
|
||||
msgstr "Nueva dirección de correo"
|
||||
|
||||
#: templates/musician/base.html:60
|
||||
msgid "Settings"
|
||||
msgstr "Configuración"
|
||||
|
@ -110,7 +194,7 @@ msgstr "Perfil"
|
|||
|
||||
#. Translators: This message appears on the page title
|
||||
#: templates/musician/base.html:64 templates/musician/billing.html:6
|
||||
#: views.py:147
|
||||
#: views.py:163
|
||||
msgid "Billing"
|
||||
msgstr "Facturas"
|
||||
|
||||
|
@ -119,7 +203,6 @@ msgid "Log out"
|
|||
msgstr "Desconéctate"
|
||||
|
||||
#: templates/musician/billing.html:7
|
||||
#, fuzzy
|
||||
msgid "Billing page description."
|
||||
msgstr "Consulta y descarga tus facturas."
|
||||
|
||||
|
@ -132,7 +215,7 @@ msgid "Bill date"
|
|||
msgstr "Fecha de la factura"
|
||||
|
||||
#: templates/musician/billing.html:21 templates/musician/databases.html:17
|
||||
#: templates/musician/domain_detail.html:17 templates/musician/mail.html:22
|
||||
#: templates/musician/domain_detail.html:17
|
||||
msgid "Type"
|
||||
msgstr "Tipo"
|
||||
|
||||
|
@ -165,90 +248,85 @@ msgstr "La última vez que accediste fue el día: %(last_login)s"
|
|||
msgid "It's the first time you log into the system, welcome on board!"
|
||||
msgstr "Es la primera vez que accedes: ¡te damos la bienvenida!"
|
||||
|
||||
#: templates/musician/dashboard.html:24
|
||||
#: templates/musician/dashboard.html:29
|
||||
msgid "Notifications"
|
||||
msgstr "Notificaciones"
|
||||
|
||||
#: templates/musician/dashboard.html:28
|
||||
#: templates/musician/dashboard.html:33
|
||||
msgid "There is no notifications at this time."
|
||||
msgstr "No tienes ninguna notificación."
|
||||
|
||||
#: templates/musician/dashboard.html:35
|
||||
#: templates/musician/dashboard.html:40
|
||||
msgid "Your domains and websites"
|
||||
msgstr "Tus dominios y sitios web"
|
||||
|
||||
#: templates/musician/dashboard.html:36
|
||||
#, fuzzy
|
||||
#: templates/musician/dashboard.html:41
|
||||
msgid "Dashboard page description."
|
||||
msgstr ""
|
||||
"Este es tu panel de gestión, desde donde podrás consultar la configuración "
|
||||
"de los servicios que Pangea te ofrece."
|
||||
|
||||
#: templates/musician/dashboard.html:51
|
||||
#: templates/musician/dashboard.html:56
|
||||
msgid "view configuration"
|
||||
msgstr "ver la configuración"
|
||||
|
||||
#: templates/musician/dashboard.html:58
|
||||
#: templates/musician/dashboard.html:63
|
||||
msgid "Expiration date"
|
||||
msgstr "Fecha de vencimiento"
|
||||
|
||||
#: templates/musician/dashboard.html:65
|
||||
#: templates/musician/dashboard.html:70
|
||||
msgid "Mail"
|
||||
msgstr "Correo"
|
||||
|
||||
#: templates/musician/dashboard.html:68
|
||||
#: templates/musician/dashboard.html:73
|
||||
msgid "mail addresses created"
|
||||
msgstr "direcciones de correo creadas"
|
||||
|
||||
#: templates/musician/dashboard.html:71
|
||||
msgid "mail address left"
|
||||
msgstr "direcciones de correo por activar"
|
||||
|
||||
#: templates/musician/dashboard.html:77
|
||||
#: templates/musician/dashboard.html:78
|
||||
msgid "Mail list"
|
||||
msgstr "Lista de correo"
|
||||
|
||||
#. Translators: This message appears on the page title
|
||||
#: templates/musician/dashboard.html:82 views.py:264
|
||||
#: templates/musician/dashboard.html:83 views.py:489
|
||||
msgid "Software as a Service"
|
||||
msgstr "Software as a Service"
|
||||
|
||||
#: templates/musician/dashboard.html:84
|
||||
#: templates/musician/dashboard.html:85
|
||||
msgid "Nothing installed"
|
||||
msgstr "No tienes nada instalado"
|
||||
|
||||
#: templates/musician/dashboard.html:89 views.py:42
|
||||
#: templates/musician/dashboard.html:90 views.py:57
|
||||
msgid "Disk usage"
|
||||
msgstr "Uso del disco"
|
||||
|
||||
#: templates/musician/dashboard.html:106
|
||||
#: templates/musician/dashboard.html:107
|
||||
msgid "Configuration details"
|
||||
msgstr "Detalles de configuración"
|
||||
|
||||
#: templates/musician/dashboard.html:113
|
||||
#: templates/musician/dashboard.html:114
|
||||
msgid "FTP access:"
|
||||
msgstr "Acceso FTP:"
|
||||
|
||||
#. Translators: domain configuration detail modal
|
||||
#: templates/musician/dashboard.html:115
|
||||
#: templates/musician/dashboard.html:116
|
||||
msgid "Contact with the support team to get details concerning FTP access."
|
||||
msgstr ""
|
||||
"Contactadnos a <a href=“mailto:%(support_email)s”>%(support_email)s</a> "
|
||||
"para saber cómo acceder al FTP."
|
||||
"Escríbenos a <a href=“mailto:%(support_email)s”>%(support_email)s</a> para "
|
||||
"saber cómo acceder al FTP."
|
||||
|
||||
#: templates/musician/dashboard.html:124
|
||||
#: templates/musician/dashboard.html:125
|
||||
msgid "No website configured."
|
||||
msgstr "No hay ninguna web configurada."
|
||||
|
||||
#: templates/musician/dashboard.html:126
|
||||
#: templates/musician/dashboard.html:127
|
||||
msgid "Root directory:"
|
||||
msgstr "Directorio raíz:"
|
||||
|
||||
#: templates/musician/dashboard.html:127
|
||||
#: templates/musician/dashboard.html:128
|
||||
msgid "Type:"
|
||||
msgstr "Tipo:"
|
||||
|
||||
#: templates/musician/dashboard.html:132
|
||||
#: templates/musician/dashboard.html:133
|
||||
msgid "View DNS records"
|
||||
msgstr "Ver registros DNS"
|
||||
|
||||
|
@ -279,7 +357,6 @@ msgid "DNS settings for"
|
|||
msgstr "Configuración DNS para"
|
||||
|
||||
#: templates/musician/domain_detail.html:8
|
||||
#, fuzzy
|
||||
msgid "DNS settings page description."
|
||||
msgstr "Consulta aquí tu configuración DNS."
|
||||
|
||||
|
@ -287,25 +364,66 @@ msgstr "Consulta aquí tu configuración DNS."
|
|||
msgid "Value"
|
||||
msgstr "Valor"
|
||||
|
||||
#: templates/musician/mail.html:6 templates/musician/mailinglists.html:6
|
||||
#: templates/musician/mail_base.html:6 templates/musician/mailinglists.html:6
|
||||
msgid "Go to global"
|
||||
msgstr "Todas las direcciones"
|
||||
|
||||
#: templates/musician/mail.html:9 templates/musician/mailinglists.html:9
|
||||
#: templates/musician/mail_base.html:10 templates/musician/mailinglists.html:9
|
||||
msgid "for"
|
||||
msgstr "para"
|
||||
|
||||
#: templates/musician/mail.html:20
|
||||
msgid "Mail address"
|
||||
msgstr "Dirección de correo"
|
||||
#: templates/musician/mail_base.html:18 templates/musician/mailboxes.html:16
|
||||
msgid "Addresses"
|
||||
msgstr "Direcciones de correo"
|
||||
|
||||
#: templates/musician/mail.html:21
|
||||
msgid "Aliases"
|
||||
msgstr "Alias"
|
||||
#: templates/musician/mailbox_change_password.html:5
|
||||
#: templates/musician/mailbox_form.html:24
|
||||
msgid "Change password"
|
||||
msgstr "Cambia la contraseña"
|
||||
|
||||
#: templates/musician/mail.html:23
|
||||
msgid "Type details"
|
||||
msgstr "Detalles de cada tipo"
|
||||
#: templates/musician/mailbox_check_delete.html:7
|
||||
#, python-format
|
||||
msgid "Are you sure that you want remove the mailbox: \"%(name)s\"?"
|
||||
msgstr "¿Estás seguro/a de que quieres borrar el buzón de correo \"%(name)s\"?"
|
||||
|
||||
#: templates/musician/mailbox_check_delete.html:9
|
||||
msgid ""
|
||||
"All mailbox's messages will be <strong>deleted and cannot be recovered</"
|
||||
"strong>."
|
||||
msgstr ""
|
||||
"Todos los mensajes <strong>se borrarán y no se podrán recuperar</strong>."
|
||||
|
||||
#: templates/musician/mailbox_form.html:9
|
||||
msgid "Warning!"
|
||||
msgstr "¡Aviso!"
|
||||
|
||||
#: templates/musician/mailbox_form.html:9
|
||||
msgid ""
|
||||
"You have reached the limit of mailboxes of your subscription so "
|
||||
"<strong>extra fees</strong> may apply."
|
||||
msgstr ""
|
||||
"Has alcanzado el límite de buzones de correo de tu suscripción, los nuevos "
|
||||
"buzones pueden suponer <strong>costes adicionales</strong>."
|
||||
|
||||
#: templates/musician/mailbox_form.html:10
|
||||
msgid "Close"
|
||||
msgstr "Cerrar"
|
||||
|
||||
#: templates/musician/mailboxes.html:14
|
||||
msgid "Name"
|
||||
msgstr "Nombre"
|
||||
|
||||
#: templates/musician/mailboxes.html:15
|
||||
msgid "Filtering"
|
||||
msgstr "Filtrado"
|
||||
|
||||
#: templates/musician/mailboxes.html:27
|
||||
msgid "Update password"
|
||||
msgstr "Actualiza la contraseña"
|
||||
|
||||
#: templates/musician/mailboxes.html:43
|
||||
msgid "New mailbox"
|
||||
msgstr "Nuevo buzón de correo"
|
||||
|
||||
#: templates/musician/mailinglists.html:34
|
||||
msgid "Active"
|
||||
|
@ -316,7 +434,6 @@ msgid "Inactive"
|
|||
msgstr "Inactivo"
|
||||
|
||||
#: templates/musician/profile.html:7
|
||||
#, fuzzy
|
||||
msgid "Little description on profile page."
|
||||
msgstr "Cambia tus datos de acceso y opciones de perfil desde aquí."
|
||||
|
||||
|
@ -326,7 +443,7 @@ msgstr "Información de usuario/a"
|
|||
|
||||
#: templates/musician/profile.html:21
|
||||
msgid "Preferred language:"
|
||||
msgstr "Lenguaje preferido:"
|
||||
msgstr "Idioma preferido:"
|
||||
|
||||
#: templates/musician/profile.html:35
|
||||
msgid "Billing information"
|
||||
|
@ -357,43 +474,67 @@ msgid "Open service admin panel"
|
|||
msgstr "Abre el panel de administración del servicio"
|
||||
|
||||
#. Translators: This message appears on the page title
|
||||
#: views.py:32
|
||||
#: views.py:41
|
||||
msgid "Dashboard"
|
||||
msgstr "Panel de gestión"
|
||||
|
||||
#: views.py:49
|
||||
#: views.py:66
|
||||
msgid "Traffic"
|
||||
msgstr "Tráfico"
|
||||
|
||||
#: views.py:56
|
||||
#: views.py:97
|
||||
msgid "Mailbox usage"
|
||||
msgstr "Uso de espacio en tu buzón de correo"
|
||||
|
||||
#. Translators: This message appears on the page title
|
||||
#: views.py:96
|
||||
#: views.py:112
|
||||
msgid "User profile"
|
||||
msgstr "Tu perfil"
|
||||
|
||||
#. Translators: This message appears on the page title
|
||||
#: views.py:154
|
||||
#: views.py:170
|
||||
msgid "Download bill"
|
||||
msgstr "Descarga la factura"
|
||||
|
||||
#: views.py:283
|
||||
msgid "Address deleted!"
|
||||
msgstr "Has eliminado la dirección de correo"
|
||||
|
||||
#: views.py:285 views.py:422 views.py:469
|
||||
msgid "Cannot process your request, please try again later."
|
||||
msgstr ""
|
||||
"Ahora no podemos procesar tu petición, inténtalo de nuevo un poco más tarde "
|
||||
"por favor."
|
||||
|
||||
#: views.py:420
|
||||
msgid "Mailbox deleted!"
|
||||
msgstr "Has eliminado el buzón de correo"
|
||||
|
||||
#: views.py:467
|
||||
msgid "Password updated!"
|
||||
msgstr "Contraseña actualizada"
|
||||
|
||||
#. Translators: This message appears on the page title
|
||||
#: views.py:272
|
||||
#: views.py:497
|
||||
msgid "Domain details"
|
||||
msgstr "Detalles del dominio"
|
||||
|
||||
#. Translators: This message appears on the page title
|
||||
#: views.py:298
|
||||
#: views.py:523
|
||||
msgid "Login"
|
||||
msgstr "Accede"
|
||||
|
||||
#~ msgid "mail address left"
|
||||
#~ msgstr "direcciones de correo por activar"
|
||||
|
||||
#~ msgid "Aliases"
|
||||
#~ msgstr "Alias"
|
||||
|
||||
#~ msgid "Type details"
|
||||
#~ msgstr "Detalles de cada tipo"
|
||||
|
||||
#~ msgid "databases created"
|
||||
#~ msgstr "bases de datos creadas"
|
||||
|
||||
#~ msgid "Username"
|
||||
#~ msgstr "Nombre de usuario/a"
|
||||
|
||||
#~ msgid "Password:"
|
||||
#~ msgstr "Contraseña:"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from django.contrib.auth.mixins import UserPassesTestMixin
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic.base import ContextMixin
|
||||
from django.conf import settings
|
||||
|
||||
from . import api, get_version
|
||||
from .auth import SESSION_KEY_TOKEN
|
||||
|
@ -12,14 +13,15 @@ class CustomContextMixin(ContextMixin):
|
|||
# generate services menu items
|
||||
services_menu = [
|
||||
{'icon': 'globe-europe', 'pattern_name': 'musician:dashboard', 'title': _('Domains & websites')},
|
||||
{'icon': 'envelope', 'pattern_name': 'musician:mails', 'title': _('Mails')},
|
||||
{'icon': 'envelope', 'pattern_name': 'musician:address-list', 'title': _('Mails')},
|
||||
{'icon': 'mail-bulk', 'pattern_name': 'musician:mailing-lists', 'title': _('Mailing lists')},
|
||||
{'icon': 'database', 'pattern_name': 'musician:databases', 'title': _('Databases')},
|
||||
{'icon': 'fire', 'pattern_name': 'musician:saas', 'title': _('SaaS')},
|
||||
{'icon': 'database', 'pattern_name': 'musician:database-list', 'title': _('Databases')},
|
||||
{'icon': 'fire', 'pattern_name': 'musician:saas-list', 'title': _('SaaS')},
|
||||
]
|
||||
context.update({
|
||||
'services_menu': services_menu,
|
||||
'version': get_version(),
|
||||
'languages': settings.LANGUAGES,
|
||||
})
|
||||
|
||||
return context
|
||||
|
|
|
@ -17,6 +17,7 @@ class OrchestraModel:
|
|||
api_name = None
|
||||
verbose_name = None
|
||||
fields = ()
|
||||
param_defaults = {}
|
||||
id = None
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
|
@ -34,6 +35,8 @@ class OrchestraModel:
|
|||
Args:
|
||||
data: A JSON dict, as converted from the JSON in the orchestra API.
|
||||
"""
|
||||
if data is None:
|
||||
return cls()
|
||||
|
||||
json_data = data.copy()
|
||||
if kwargs:
|
||||
|
@ -126,6 +129,10 @@ class UserAccount(OrchestraModel):
|
|||
|
||||
return super().new_from_json(data=data, billing=billing, language=language, last_login=last_login)
|
||||
|
||||
def allowed_resources(self, resource):
|
||||
allowed_by_type = musician_settings.ALLOWED_RESOURCES[self.type]
|
||||
return allowed_by_type[resource]
|
||||
|
||||
|
||||
class DatabaseUser(OrchestraModel):
|
||||
api_name = 'databaseusers'
|
||||
|
@ -159,7 +166,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 = {}
|
||||
|
@ -196,9 +203,10 @@ class Domain(OrchestraModel):
|
|||
"id": None,
|
||||
"name": None,
|
||||
"records": [],
|
||||
"mails": [],
|
||||
"addresses": [],
|
||||
"usage": {},
|
||||
"websites": [],
|
||||
"url": None,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
|
@ -222,12 +230,19 @@ class DomainRecord(OrchestraModel):
|
|||
return '<%s: %s>' % (self.type, self.value)
|
||||
|
||||
|
||||
class MailService(OrchestraModel):
|
||||
class Address(OrchestraModel):
|
||||
api_name = 'address'
|
||||
verbose_name = _('Mail addresses')
|
||||
description = _('Description details for mail addresses page.')
|
||||
fields = ('mail_address', 'aliases', 'type', 'type_detail')
|
||||
param_defaults = {}
|
||||
param_defaults = {
|
||||
"id": None,
|
||||
"name": None,
|
||||
"domain": None,
|
||||
"mailboxes": [],
|
||||
"forward": None,
|
||||
'url': None,
|
||||
}
|
||||
|
||||
FORWARD = 'forward'
|
||||
MAILBOX = 'mailbox'
|
||||
|
@ -236,6 +251,15 @@ class MailService(OrchestraModel):
|
|||
self.data = kwargs
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def deserialize(self):
|
||||
data = {
|
||||
'name': self.data['name'],
|
||||
'domain': self.data['domain']['url'],
|
||||
'mailboxes': [mbox['url'] for mbox in self.data['mailboxes']],
|
||||
'forward': self.data['forward'],
|
||||
}
|
||||
return data
|
||||
|
||||
@property
|
||||
def aliases(self):
|
||||
return [
|
||||
|
@ -243,8 +267,8 @@ class MailService(OrchestraModel):
|
|||
]
|
||||
|
||||
@property
|
||||
def mail_address(self):
|
||||
return self.data['names'][0] + '@' + self.data['domain']['name']
|
||||
def full_address_name(self):
|
||||
return "{}@{}".format(self.name, self.domain['name'])
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
|
@ -282,6 +306,32 @@ class MailService(OrchestraModel):
|
|||
return mailbox_details
|
||||
|
||||
|
||||
class Mailbox(OrchestraModel):
|
||||
api_name = 'mailbox'
|
||||
verbose_name = _('Mailbox')
|
||||
description = _('Description details for mailbox page.')
|
||||
fields = ('name', 'filtering', 'addresses', 'active')
|
||||
param_defaults = {
|
||||
'id': None,
|
||||
'name': None,
|
||||
'filtering': None,
|
||||
'is_active': True,
|
||||
'addresses': [],
|
||||
'url': None,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def new_from_json(cls, data, **kwargs):
|
||||
addresses = [Address.new_from_json(addr) for addr in data.get('addresses', [])]
|
||||
return super().new_from_json(data=data, addresses=addresses)
|
||||
|
||||
def deserialize(self):
|
||||
data = {
|
||||
'addresses': [addr.url for addr in self.addresses],
|
||||
}
|
||||
return data
|
||||
|
||||
|
||||
class MailinglistService(OrchestraModel):
|
||||
api_name = 'mailinglist'
|
||||
verbose_name = _('Mailing list')
|
||||
|
@ -299,7 +349,10 @@ class MailinglistService(OrchestraModel):
|
|||
|
||||
@property
|
||||
def address_name(self):
|
||||
return "{}@{}".format(self.data['address_name'], self.data['address_domain']['name'])
|
||||
address_domain = self.data['address_domain']
|
||||
if address_domain is None:
|
||||
return self.data['address_name']
|
||||
return "{}@{}".format(self.data['address_name'], address_domain['name'])
|
||||
|
||||
@property
|
||||
def manager_url(self):
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from collections import defaultdict
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
|
@ -5,10 +6,15 @@ def getsetting(name):
|
|||
value = getattr(settings, name, None)
|
||||
return value or DEFAULTS.get(name)
|
||||
|
||||
# provide a default value allowing to overwrite it for each type of account
|
||||
def allowed_resources_default_factory():
|
||||
return {'mailbox': 2}
|
||||
|
||||
DEFAULTS = {
|
||||
# allowed resources limit hardcoded because cannot be retrieved from the API.
|
||||
"ALLOWED_RESOURCES": {
|
||||
"ALLOWED_RESOURCES": defaultdict(
|
||||
allowed_resources_default_factory,
|
||||
{
|
||||
'INDIVIDUAL':
|
||||
{
|
||||
# 'disk': 1024,
|
||||
|
@ -20,9 +26,10 @@ DEFAULTS = {
|
|||
# 'traffic': 20 * 1024,
|
||||
'mailbox': 10,
|
||||
}
|
||||
},
|
||||
}
|
||||
),
|
||||
"URL_DB_PHPMYADMIN": "https://phpmyadmin.pangea.org/",
|
||||
"URL_MAILTRAIN": "https://mailtrain.org/",
|
||||
"URL_MAILTRAIN": "https://grups.pangea.org/",
|
||||
"URL_SAAS_GITLAB": "https://gitlab.pangea.org/",
|
||||
"URL_SAAS_OWNCLOUD": "https://nextcloud.pangea.org/",
|
||||
"URL_SAAS_WORDPRESS": "https://blog.pangea.org/",
|
||||
|
|
|
@ -12,16 +12,15 @@ a:hover {
|
|||
background: #D3D0DA;
|
||||
position: relative;
|
||||
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::before{
|
||||
.btn-arrow-left::after, .btn-arrow-left::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: -1em;
|
||||
|
||||
margin-top: -19px;
|
||||
border-top: 19px solid transparent;
|
||||
border-bottom: 19px solid transparent;
|
||||
|
@ -43,13 +42,21 @@ a:hover {
|
|||
min-width: 280px;
|
||||
max-width: 280px;
|
||||
min-height: 100vh;
|
||||
|
||||
position: fixed;
|
||||
z-index: 999;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition-duration: 200ms;
|
||||
transition-property: left;
|
||||
}
|
||||
|
||||
#sidebar-btn {
|
||||
display: none;
|
||||
width: fit-content;
|
||||
transition-duration: 200ms;
|
||||
transition-property: left;
|
||||
}
|
||||
|
||||
#sidebar #sidebar-services {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
@ -62,6 +69,7 @@ a:hover {
|
|||
padding-left: 2rem;
|
||||
padding-right: 2rem;
|
||||
}
|
||||
|
||||
#sidebar #sidebar-services {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
|
@ -75,7 +83,6 @@ a:hover {
|
|||
padding: 20px 0;
|
||||
}
|
||||
|
||||
|
||||
#sidebar ul li a {
|
||||
padding: 10px;
|
||||
font-size: 1.1em;
|
||||
|
@ -89,14 +96,16 @@ a:hover {
|
|||
}
|
||||
|
||||
.vertical-center {
|
||||
min-height: 100%; /* Fallback for browsers do NOT support vh unit */
|
||||
min-height: 100vh; /* These two lines are counted as one :-) */
|
||||
|
||||
min-height: 100%;
|
||||
/* Fallback for browsers do NOT support vh unit */
|
||||
min-height: 100vh;
|
||||
/* These two lines are counted as one :-) */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/** login **/
|
||||
|
||||
#body-login .jumbotron {
|
||||
background: #282532 no-repeat url("../images/logo-pangea-lilla-bg.svg") right;
|
||||
}
|
||||
|
@ -106,8 +115,7 @@ a:hover {
|
|||
padding: 2rem;
|
||||
}
|
||||
|
||||
#login-content input[type="text"].form-control,
|
||||
#login-content input[type="password"].form-control {
|
||||
#login-content input[type="text"].form-control, #login-content input[type="password"].form-control {
|
||||
border-radius: 0;
|
||||
border: 0;
|
||||
border-bottom: 2px solid #8E8E8E;
|
||||
|
@ -121,6 +129,7 @@ a:hover {
|
|||
margin-top: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#login-footer a {
|
||||
color: #FEFBF2;
|
||||
}
|
||||
|
@ -130,34 +139,37 @@ a:hover {
|
|||
background-position: right 5% top 10%;
|
||||
color: #343434;
|
||||
padding-left: 2rem;
|
||||
margin-left: 280px; /** sidebar width **/
|
||||
margin-left: 280px;
|
||||
/** sidebar width **/
|
||||
}
|
||||
|
||||
/** services **/
|
||||
h1.service-name {
|
||||
|
||||
h1.service-name {
|
||||
font: Bold 26px/34px Roboto;
|
||||
margin-top: 3rem;
|
||||
}
|
||||
|
||||
.service-description {
|
||||
|
||||
font: 16px/21px Roboto;
|
||||
}
|
||||
|
||||
.table.service-list {
|
||||
margin-top: 2rem;
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
/** 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);
|
||||
color: #50466E;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
/** /TODO **/
|
||||
.table.service-list td,
|
||||
.table.service-list th {
|
||||
|
||||
.table.service-list td, .table.service-list th {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
|
@ -202,7 +214,6 @@ h1.service-name {
|
|||
|
||||
.service-card .card-body {
|
||||
color: #787878;
|
||||
|
||||
}
|
||||
|
||||
.service-card .card-body i.fas {
|
||||
|
@ -215,8 +226,7 @@ h1.service-name {
|
|||
right: 15px;
|
||||
}
|
||||
|
||||
.service-card .service-manager-link a,
|
||||
.service-card .service-manager-link a i.fas {
|
||||
.service-card .service-manager-link a, .service-card .service-manager-link a i.fas {
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
@ -243,11 +253,9 @@ h1.service-name {
|
|||
font-variant: normal;
|
||||
text-rendering: auto;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 10px;
|
||||
|
||||
color: #E8E7EB;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
@ -308,3 +316,69 @@ h1.service-name {
|
|||
border-top: 0;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.roll-hover {
|
||||
visibility: hidden;
|
||||
display: inline-block;
|
||||
margin-left: 2rem;
|
||||
}
|
||||
|
||||
td:hover .roll-hover {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
#sidebar-btn * {
|
||||
font-size: 14.4px !important;
|
||||
font-family: "Font Awesome 5 Free";
|
||||
}
|
||||
|
||||
@media (min-width: 1350px){
|
||||
.card-deck .card {
|
||||
flex: 1 0 0%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 800px){
|
||||
#sidebar {
|
||||
left: calc(-277px + 34px);
|
||||
}
|
||||
#sidebar * {
|
||||
opacity: 0;
|
||||
transition-delay: 150ms;
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
#sidebar-btn {
|
||||
display: block;
|
||||
}
|
||||
#sidebar-btn::before {
|
||||
font-family: "Font Awesome 5 Free";
|
||||
content: "\f0c9";
|
||||
font-weight: 900;
|
||||
}
|
||||
#content {
|
||||
margin-left: 34px;
|
||||
}
|
||||
#sidebar-toggle:checked {
|
||||
width: 277px;
|
||||
}
|
||||
#sidebar-toggle:checked ~ #sidebar {
|
||||
left: 0;
|
||||
}
|
||||
#sidebar-toggle:checked ~ #sidebar * {
|
||||
opacity: 100%;
|
||||
}
|
||||
#sidebar-toggle:checked ~ #sidebar-btn {
|
||||
left: 243px;
|
||||
}
|
||||
#sidebar-toggle:checked ~ #sidebar-btn::before {
|
||||
content: "\f00d";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media (min-width: 576px){
|
||||
.card-deck .card {
|
||||
flex: 1 0 40%;
|
||||
margin: 15px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<p>{% blocktrans with address_name=object.full_address_name %}Are you sure that you want remove the address: "{{ address_name }}"?{% endblocktrans %}</p>
|
||||
<p class="alert alert-warning"><strong>{% trans 'WARNING: This action cannot be undone.' %}</strong></p>
|
||||
<input class="btn btn-danger" type="submit" value="{% trans 'Delete' %}">
|
||||
<a class="btn btn-secondary" href="{% url 'musician:address-update' view.kwargs.pk %}">{% trans 'Cancel' %}</a>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,20 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load bootstrap4 i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="service-name">{{ service.verbose_name }}</h1>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
{% buttons %}
|
||||
<a class="btn btn-light mr-2" href="{% url 'musician:address-list' %}">{% trans "Cancel" %}</a>
|
||||
<button type="submit" class="btn btn-secondary">{% trans "Save" %}</button>
|
||||
{% if form.instance %}
|
||||
<div class="float-right">
|
||||
<a class="btn btn-danger" href="{% url 'musician:address-delete' view.kwargs.pk %}">{% trans "Delete" %}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,41 @@
|
|||
{% extends "musician/mail_base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block tabcontent %}
|
||||
<div class="tab-pane fade show active" id="addresses" role="tabpanel" aria-labelledby="addresses-tab">
|
||||
<table class="table service-list">
|
||||
<colgroup>
|
||||
<col span="1" style="width: 25%;">
|
||||
<col span="1" style="width: 25%;">
|
||||
<col span="1" style="width: 25%;">
|
||||
<col span="1" style="width: 25%;">
|
||||
</colgroup>
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">{% trans "Email" %}</th>
|
||||
<th scope="col">{% trans "Domain" %}</th>
|
||||
<th scope="col">{% trans "Mailboxes" %}</th>
|
||||
<th scope="col">{% trans "Forward" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for obj in object_list %}
|
||||
<tr>
|
||||
<td><a href="{% url 'musician:address-update' obj.id %}">{{ obj.full_address_name }}</a></td>
|
||||
<td>{{ obj.domain.name }}</td>
|
||||
<td>
|
||||
{% for mailbox in obj.mailboxes %}
|
||||
<a href="{% url 'musician:mailbox-update' mailbox.id %}">{{ mailbox.name }}</a>
|
||||
{% if not forloop.last %}<br/> {% endif %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>{{ obj.forward }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% include "musician/components/table_paginator.html" %}
|
||||
</table>
|
||||
<a class="btn btn-primary mt-4 mb-4" href="{% url 'musician:address-create' %}">{% trans "New mail address" %}</a>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -9,6 +9,7 @@
|
|||
{% block meta %}
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||
<meta name="robots" content="NONE,NOARCHIVE" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
{% endblock %}
|
||||
|
||||
<title>{% block title %}{% if title %}{{ title }} – {% endif %}Django musician{% endblock %}</title>
|
||||
|
@ -32,6 +33,8 @@
|
|||
|
||||
<body class="{% block bodyclass %}{% endblock %}">
|
||||
<div class="wrapper">
|
||||
<input style="display: none" type="checkbox" id="sidebar-toggle" />
|
||||
<label type="button" for="sidebar-toggle" id="sidebar-btn" class="btn btn-primary fixed-top"></label>
|
||||
<nav id="sidebar" class="bg-primary border-right pt-4">
|
||||
{% block sidebar %}
|
||||
<div class="sidebar-branding">
|
||||
|
@ -65,9 +68,16 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="sidebar-logout">
|
||||
<ul class="nav flex-column">
|
||||
<li class="nav-item text-right">
|
||||
<ul class="nav flex-row">
|
||||
<li class="nav-item btn btn-outline-primary btn-sm" data-toggle="modal" data-target="#helpModal">
|
||||
<a class="nav-link text-light" href="#">
|
||||
<i class="far fa-question-circle"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item text-right flex-grow-1">
|
||||
<a class="nav-link text-light" href="{% url 'musician:logout' %}">
|
||||
{% trans 'Log out' %}
|
||||
<i class="fas fa-power-off"></i>
|
||||
|
@ -77,16 +87,51 @@
|
|||
</div>
|
||||
|
||||
<div class="mt-4 pr-3 pb-2 text-light d-block text-right">
|
||||
<div class="dropdown">
|
||||
<a class="btn p-0 text-light" id="dropdownMenu3" data-toggle="dropdown" aria-haspopup="true"
|
||||
aria-expanded="false">
|
||||
<i class="fas fa-globe"></i> {% trans "Language" %}
|
||||
</a>
|
||||
<div class="dropdown-menu">
|
||||
{% for code, language in languages %}
|
||||
<a class="dropdown-item" href="{% url 'musician:profile-set-lang' code %}">{{ language }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<small>Panel Version {{ version }}</small>
|
||||
</div>
|
||||
{% endblock sidebar %}
|
||||
</nav><!-- ./sidebar -->
|
||||
<div id="content" class="container-fluid pt-4">
|
||||
{% block messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.tags|default:'info' }} alert-dismissible fade show" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endblock messages %}
|
||||
{% block content %}
|
||||
{% endblock content %}
|
||||
</div><!-- ./content -->
|
||||
</div><!-- ./wrapper -->
|
||||
|
||||
<!-- Help Modal -->
|
||||
<div class="modal fade" id="helpModal" tabindex="-1" role="dialog" aria-labelledby="helpModal" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-body">
|
||||
<span class="text-center m-auto">Do you need help? Write to <a href="mailto:suport@pangea.org" target="_blank">suport@pangea.org</a></span>
|
||||
<button type="button" class="close m-0 pb-1" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% block script %}
|
||||
<script src="{% static "musician/js/jquery-3.3.1.slim.min.js" %}"></script>
|
||||
<script src="{% static "musician/js/popper.min.js" %}"></script>
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
{% for bill in object_list %}
|
||||
<tr>
|
||||
<th scope="row">{{ bill.number }}</th>
|
||||
<td>{{ bill.created_on }}</td>
|
||||
<td>{{ bill.created_on|date:"SHORT_DATE_FORMAT" }}</td>
|
||||
<td>{{ bill.type }}</td>
|
||||
<td>{{ bill.total|floatformat:2|localize }}€</td>
|
||||
<td><a class="text-dark" href="{% url 'musician:bill-download' bill.id %}" target="_blank" rel="noopener noreferrer"><i class="fas fa-file-pdf"></i></a></td>
|
||||
|
|
|
@ -11,7 +11,7 @@ Expected structure: dictionary or object with attributes:
|
|||
|
||||
<div class="text-center">
|
||||
{% if detail %}
|
||||
{{ detail.usage }} of {{ detail.total }} {{ detail.unit }}
|
||||
{{ detail.usage }} {{ detail.unit }}
|
||||
{% else %}
|
||||
N/A
|
||||
{% endif %}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
{% block content %}
|
||||
|
||||
<h2>{% trans "Welcome back" %} <strong>{{ profile.username }}</strong></h2>
|
||||
<h2 style="margin-top: 10px;">{% trans "Welcome back" %} <strong>{{ profile.username }}</strong></h2>
|
||||
{% if profile.last_login %}
|
||||
<p>{% blocktrans with last_login=profile.last_login|date:"SHORT_DATE_FORMAT" %}Last time you logged in was: {{ last_login }}{% endblocktrans %}</p>
|
||||
{% else %}
|
||||
|
@ -16,6 +16,11 @@
|
|||
<div class="card-body">
|
||||
<h5 class="card-title">{{ usage.verbose_name }}</h5>
|
||||
{% include "musician/components/usage_progress_bar.html" with detail=usage.data %}
|
||||
{% if usage.data.alert %}
|
||||
<div class="text-center mt-4">
|
||||
{{ usage.data.alert }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
@ -61,38 +66,34 @@
|
|||
</div>
|
||||
</div><!-- /card-header-->
|
||||
<div class="card-body row text-center">
|
||||
<div class="col-md-2 border-right">
|
||||
<div class="col-6 col-md-3 col-lg-2 border-right">
|
||||
<h4>{% trans "Mail" %}</h4>
|
||||
<p class="card-text"><i class="fas fa-envelope fa-3x"></i></p>
|
||||
<p class="card-text text-dark">
|
||||
{{ domain.mails|length }} {% trans "mail addresses created" %}
|
||||
{% if domain.addresses_left.alert_level %}
|
||||
<br/>
|
||||
<span class="text-{{ domain.addresses_left.alert_level }}">{{ domain.addresses_left.count }} {% trans "mail address left" %}</span>
|
||||
{% endif %}
|
||||
{{ domain.addresses|length }} {% trans "mail addresses created" %}
|
||||
</p>
|
||||
<a class="stretched-link" href="{% url 'musician:mails' %}?domain={{ domain.id }}"></a>
|
||||
<a class="stretched-link" href="{% url 'musician:address-list' %}?domain={{ domain.id }}"></a>
|
||||
</div>
|
||||
<div class="col-md-2 border-right">
|
||||
<div class="col-6 col-md-3 col-lg-2 border-right">
|
||||
<h4>{% trans "Mail list" %}</h4>
|
||||
<p class="card-text"><i class="fas fa-mail-bulk fa-3x"></i></p>
|
||||
<a class="stretched-link" href="{% url 'musician:mailing-lists' %}?domain={{ domain.id }}"></a>
|
||||
</div>
|
||||
<div class="col-md-2 border-right">
|
||||
<div class="col-6 col-md-3 col-lg-2 border-right">
|
||||
<h4>{% trans "Software as a Service" %}</h4>
|
||||
<p class="card-text"><i class="fas fa-fire fa-3x"></i></p>
|
||||
<p class="card-text text-dark">{% trans "Nothing installed" %}</p>
|
||||
<a class="stretched-link" href="{% url 'musician:saas' %}?domain={{ domain.id }}"></a>
|
||||
<a class="stretched-link" href="{% url 'musician:saas-list' %}?domain={{ domain.id }}"></a>
|
||||
</div>
|
||||
<div class="col-md-1"></div>
|
||||
<div class="col-md-4">
|
||||
<div class="d-none d-lg-block col-lg-1"></div>
|
||||
<div class="col-6 col-md-3 col-lg-4">
|
||||
<h4>{% trans "Disk usage" %}</h4>
|
||||
<p class="card-text"><i class="fas fa-hdd fa-3x"></i></p>
|
||||
<div class="w-75 m-auto">
|
||||
{% include "musician/components/usage_progress_bar.html" with detail=domain.usage %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-1"></div>
|
||||
<div class="d-none d-lg-block col-lg-1"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
{% if active_domain %}
|
||||
<a class="btn-arrow-left" href="{% url 'musician:mails' %}">{% trans "Go to global" %}</a>
|
||||
{% endif %}
|
||||
|
||||
<h1 class="service-name">{{ service.verbose_name }}{% if active_domain %} <span class="font-weight-light">{% trans "for" %} {{ active_domain.name }}</span>{% endif %}</h1>
|
||||
<p class="service-description">{{ service.description }}</p>
|
||||
<table class="table service-list">
|
||||
<colgroup>
|
||||
<col span="1" style="width: 25%;">
|
||||
<col span="1" style="width: 50%;">
|
||||
<col span="1" style="width: 5%;">
|
||||
<col span="1" style="width: 20%;">
|
||||
</colgroup>
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">{% trans "Mail address" %}</th>
|
||||
<th scope="col">{% trans "Aliases" %}</th>
|
||||
<th scope="col">{% trans "Type" %}</th>
|
||||
<th scope="col">{% trans "Type details" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for obj in object_list %}
|
||||
<tr>
|
||||
<td>{{ obj.mail_address }}</td>
|
||||
<td>{{ obj.aliases|join:" , " }}</td>
|
||||
<td>{{ obj.type|capfirst }}</td>
|
||||
<td>
|
||||
{% if obj.type == 'mailbox' %}
|
||||
{% include "musician/components/usage_progress_bar.html" with detail=obj.type_detail %}
|
||||
{% else %}
|
||||
{{ obj.type_detail }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% include "musician/components/table_paginator.html" %}
|
||||
</table>
|
||||
{% endblock %}
|
|
@ -0,0 +1,32 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
{% if active_domain %}
|
||||
<a class="btn-arrow-left" href="{% url 'musician:address-list' %}">{% trans "Go to global" %}</a>
|
||||
{% endif %}
|
||||
|
||||
<h1 class="service-name">{{ service.verbose_name }}
|
||||
{% if active_domain %}<span class="font-weight-light">{% trans "for" %} {{ active_domain.name }}</span>{% endif %}
|
||||
</h1>
|
||||
<p class="service-description">{{ service.description }}</p>
|
||||
|
||||
{% with request.resolver_match.url_name as url_name %}
|
||||
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if url_name == 'address-list' %}active{% endif %}" href="{% url 'musician:address-list' %}" role="tab"
|
||||
aria-selected="{% if url_name == 'address-list' %}true{% else %}false{% endif %}">{% trans "Addresses" %}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if url_name == 'mailbox-list' %}active{% endif %}" href="{% url 'musician:mailbox-list' %}" role="tab"
|
||||
aria-selected="{% if url_name == 'mailbox-list' %}true{% else %}false{% endif %}">{% trans "Mailboxes" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{% endwith %}
|
||||
|
||||
<div class="tab-content" id="myTabContent">
|
||||
{% block tabcontent %}
|
||||
{% endblock %}
|
||||
|
||||
{% endblock %}
|
|
@ -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 %}
|
|
@ -0,0 +1,15 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<p>{% blocktrans with name=object.name %}Are you sure that you want remove the mailbox: "{{ name }}"?{% endblocktrans %}</p>
|
||||
<div class="alert alert-danger" role="alert">
|
||||
{% trans "All mailbox's messages will be <strong>deleted and cannot be recovered</strong>." %}
|
||||
</div>
|
||||
<p class="alert alert-warning"><strong>{% trans 'WARNING: This action cannot be undone.' %}</strong></p>
|
||||
<input class="btn btn-danger" type="submit" value="{% trans 'Delete' %}">
|
||||
<a class="btn btn-secondary" href="{% url 'musician:mailbox-list' %}">{% trans 'Cancel' %}</a>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,30 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load bootstrap4 i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="service-name">{{ service.verbose_name }}</h1>
|
||||
|
||||
{% if extra_mailbox %}
|
||||
<div class="alert alert-warning alert-dismissible fade show" role="alert">
|
||||
<strong>{% trans "Warning!" %}</strong> {% trans "You have reached the limit of mailboxes of your subscription so <strong>extra fees</strong> may apply." %}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="{% trans 'Close' %}">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<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>
|
||||
{% if form.instance %}
|
||||
<div class="float-right">
|
||||
<a class="btn btn-outline-warning" href="{% url 'musician:mailbox-password' view.kwargs.pk %}"><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>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,46 @@
|
|||
{% extends "musician/mail_base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block tabcontent %}
|
||||
<div class="tab-pane fade show active" id="mailboxes" role="tabpanel" aria-labelledby="mailboxes-tab">
|
||||
<table class="table service-list">
|
||||
<colgroup>
|
||||
<col span="1" style="width: 25%;">
|
||||
<col span="1" style="width: 10%;">
|
||||
<col span="1" style="width: 65%;">
|
||||
</colgroup>
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">{% trans "Name" %}</th>
|
||||
<th scope="col">{% trans "Filtering" %}</th>
|
||||
<th scope="col">{% trans "Addresses" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for mailbox in object_list %}
|
||||
{# <!-- Exclude (don't render) inactive mailboxes -->#}
|
||||
{% if mailbox.is_active %}
|
||||
<tr>
|
||||
<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>
|
||||
{% for addr in mailbox.addresses %}
|
||||
<a href="{% url 'musician:address-update' addr.data.id %}">
|
||||
{{ addr.full_address_name }}
|
||||
</a><br/>
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}{# <!-- /is_active --> #}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% include "musician/components/table_paginator.html" %}
|
||||
</table>
|
||||
<a class="btn btn-primary mt-4 mb-4" href="{% url 'musician:mailbox-create' %}">{% trans "New mailbox" %}</a>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,9 +1,37 @@
|
|||
from django.test import TestCase
|
||||
|
||||
from .models import UserAccount
|
||||
from .models import DatabaseService, UserAccount
|
||||
from .utils import get_bootstraped_percent
|
||||
|
||||
|
||||
class DatabaseTest(TestCase):
|
||||
def test_database_from_json(self):
|
||||
data = {
|
||||
"url": "https://example.org/api/databases/1/",
|
||||
"id": 1,
|
||||
"name": "bluebird",
|
||||
"type": "mysql",
|
||||
"users": [
|
||||
{
|
||||
"url": "https://example.org/api/databaseusers/2/",
|
||||
"id": 2,
|
||||
"username": "bluebird"
|
||||
}
|
||||
],
|
||||
"resources": [
|
||||
{
|
||||
"name": "disk",
|
||||
"used": "1.798",
|
||||
"allocated": None,
|
||||
"unit": "MiB"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
database = DatabaseService.new_from_json(data)
|
||||
self.assertEqual(0, database.usage['percent'])
|
||||
|
||||
|
||||
class DomainsTestCase(TestCase):
|
||||
def test_domain_not_found(self):
|
||||
response = self.client.post(
|
||||
|
@ -62,6 +90,31 @@ class UserAccountTest(TestCase):
|
|||
account = UserAccount.new_from_json(data)
|
||||
self.assertIsNone(account.last_login)
|
||||
|
||||
def test_user_without_billcontact(self):
|
||||
data = {
|
||||
'billcontact': None,
|
||||
'date_joined': '2020-03-05T09:49:21Z',
|
||||
'full_name': 'David Rock',
|
||||
'id': 2,
|
||||
'is_active': True,
|
||||
'language': 'CA',
|
||||
'last_login': '2020-03-19T10:21:49.504266Z',
|
||||
'resources': [{'allocated': None,
|
||||
'name': 'disk',
|
||||
'unit': 'GiB',
|
||||
'used': '0.000'},
|
||||
{'allocated': None,
|
||||
'name': 'traffic',
|
||||
'unit': 'GiB',
|
||||
'used': '0.000'}],
|
||||
'short_name': '',
|
||||
'type': 'STAFF',
|
||||
'url': 'https://example.org/api/accounts/2/',
|
||||
'username': 'drock'
|
||||
}
|
||||
account = UserAccount.new_from_json(data)
|
||||
self.assertIsNotNone(account.billing)
|
||||
|
||||
|
||||
class GetBootstrapedPercentTest(TestCase):
|
||||
BS_WIDTH = [0, 25, 50, 100]
|
||||
|
@ -93,3 +146,8 @@ class GetBootstrapedPercentTest(TestCase):
|
|||
|
||||
def test_invalid_total_is_zero(self):
|
||||
value = get_bootstraped_percent(25, 0)
|
||||
self.assertEqual(value, 0)
|
||||
|
||||
def test_invalid_total_is_none(self):
|
||||
value = get_bootstraped_percent(25, None)
|
||||
self.assertEqual(value, 0)
|
||||
|
|
|
@ -16,11 +16,20 @@ urlpatterns = [
|
|||
path('auth/logout/', views.LogoutView.as_view(), name='logout'),
|
||||
path('dashboard/', views.DashboardView.as_view(), name='dashboard'),
|
||||
path('domains/<int:pk>/', views.DomainDetailView.as_view(), name='domain-detail'),
|
||||
path('bills/', views.BillingView.as_view(), name='billing'),
|
||||
path('billing/', views.BillingView.as_view(), name='billing'),
|
||||
path('bills/<int:pk>/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('profile/setLang/<code>', views.profile_set_language, name='profile-set-lang'),
|
||||
path('address/', views.MailView.as_view(), name='address-list'),
|
||||
path('address/new/', views.MailCreateView.as_view(), name='address-create'),
|
||||
path('address/<int:pk>/', views.MailUpdateView.as_view(), name='address-update'),
|
||||
path('address/<int:pk>/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('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>/change-password/', views.MailboxChangePasswordView.as_view(), name='mailbox-password'),
|
||||
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'),
|
||||
path('databases/', views.DatabasesView.as_view(), name='database-list'),
|
||||
path('saas/', views.SaasView.as_view(), name='saas-list'),
|
||||
]
|
||||
|
|
|
@ -6,7 +6,7 @@ def get_bootstraped_percent(value, total):
|
|||
"""
|
||||
try:
|
||||
percent = value / total
|
||||
except ZeroDivisionError:
|
||||
except (TypeError, ZeroDivisionError):
|
||||
return 0
|
||||
|
||||
bootstraped = round(percent * 4) * 100 // 4
|
||||
|
|
|
@ -1,28 +1,37 @@
|
|||
import logging
|
||||
import smtplib
|
||||
import datetime
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
from django.core.mail import mail_managers
|
||||
from django.http import HttpResponse, HttpResponseNotFound, HttpResponseRedirect
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils import translation
|
||||
from django.utils.html import format_html
|
||||
from django.utils.http import is_safe_url
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views import View
|
||||
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.edit import DeleteView, FormView
|
||||
from django.views.generic.list import ListView
|
||||
from requests.exceptions import HTTPError
|
||||
|
||||
from . import api, get_version
|
||||
from . import get_version
|
||||
from .auth import login as auth_login
|
||||
from .auth import logout as auth_logout
|
||||
from .forms import LoginForm
|
||||
from .forms import LoginForm, MailboxChangePasswordForm, MailboxCreateForm, MailboxUpdateForm, MailForm
|
||||
from .mixins import (CustomContextMixin, ExtendedPaginationMixin,
|
||||
UserTokenRequiredMixin)
|
||||
from .models import (Bill, DatabaseService, MailinglistService, MailService,
|
||||
PaymentSource, SaasService, UserAccount)
|
||||
from .models import (Address, Bill, DatabaseService, Mailbox,
|
||||
MailinglistService, PaymentSource, SaasService)
|
||||
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"
|
||||
|
@ -40,20 +49,6 @@ class DashboardView(CustomContextMixin, UserTokenRequiredMixin, TemplateView):
|
|||
|
||||
# show resource usage based on plan definition
|
||||
profile_type = context['profile'].type
|
||||
total_mailboxes = 0
|
||||
for domain in domains:
|
||||
total_mailboxes += len(domain.mails)
|
||||
addresses_left = ALLOWED_RESOURCES[profile_type]['mailbox'] - len(domain.mails)
|
||||
alert_level = None
|
||||
if addresses_left == 1:
|
||||
alert_level = 'warning'
|
||||
elif addresses_left < 1:
|
||||
alert_level = 'danger'
|
||||
|
||||
domain.addresses_left = {
|
||||
'count': addresses_left,
|
||||
'alert_level': alert_level,
|
||||
}
|
||||
|
||||
# TODO(@slamora) update when backend provides resource usage data
|
||||
resource_usage = {
|
||||
|
@ -75,15 +70,7 @@ class DashboardView(CustomContextMixin, UserTokenRequiredMixin, TemplateView):
|
|||
# 'percent': 25,
|
||||
},
|
||||
},
|
||||
'mailbox': {
|
||||
'verbose_name': _('Mailbox usage'),
|
||||
'data': {
|
||||
'usage': total_mailboxes,
|
||||
'total': ALLOWED_RESOURCES[profile_type]['mailbox'],
|
||||
'unit': 'accounts',
|
||||
'percent': get_bootstraped_percent(total_mailboxes, ALLOWED_RESOURCES[profile_type]['mailbox']),
|
||||
},
|
||||
},
|
||||
'mailbox': self.get_mailbox_usage(profile_type),
|
||||
}
|
||||
|
||||
context.update({
|
||||
|
@ -94,6 +81,28 @@ class DashboardView(CustomContextMixin, UserTokenRequiredMixin, TemplateView):
|
|||
|
||||
return context
|
||||
|
||||
def get_mailbox_usage(self, profile_type):
|
||||
allowed_mailboxes = ALLOWED_RESOURCES[profile_type]['mailbox']
|
||||
total_mailboxes = len(self.orchestra.retrieve_mailbox_list())
|
||||
mailboxes_left = allowed_mailboxes - total_mailboxes
|
||||
|
||||
alert = ''
|
||||
if mailboxes_left < 0:
|
||||
alert = format_html("<span class='text-danger'>{} extra mailboxes</span>", mailboxes_left * -1)
|
||||
elif mailboxes_left <= 1:
|
||||
alert = format_html("<span class='text-warning'>{} mailbox left</span>", mailboxes_left)
|
||||
|
||||
return {
|
||||
'verbose_name': _('Mailbox usage'),
|
||||
'data': {
|
||||
'usage': total_mailboxes,
|
||||
'total': allowed_mailboxes,
|
||||
'alert': alert,
|
||||
'unit': 'mailboxes',
|
||||
'percent': get_bootstraped_percent(total_mailboxes, allowed_mailboxes),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class ProfileView(CustomContextMixin, UserTokenRequiredMixin, TemplateView):
|
||||
template_name = "musician/profile.html"
|
||||
|
@ -116,6 +125,23 @@ class ProfileView(CustomContextMixin, UserTokenRequiredMixin, TemplateView):
|
|||
return context
|
||||
|
||||
|
||||
def profile_set_language(request, code):
|
||||
# set user language as active language
|
||||
|
||||
if any(x[0] == code for x in settings.LANGUAGES):
|
||||
# http://127.0.0.1:8080/profile/setLang/es
|
||||
user_language = code
|
||||
translation.activate(user_language)
|
||||
|
||||
response = HttpResponseRedirect('/dashboard')
|
||||
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, user_language)
|
||||
|
||||
return response
|
||||
else:
|
||||
response = HttpResponseNotFound('Languague not found')
|
||||
return response
|
||||
|
||||
|
||||
class ServiceListView(CustomContextMixin, ExtendedPaginationMixin, UserTokenRequiredMixin, ListView):
|
||||
"""Base list view to all services"""
|
||||
service_class = None
|
||||
|
@ -153,6 +179,13 @@ class BillingView(ServiceListView):
|
|||
'title': _('Billing'),
|
||||
}
|
||||
|
||||
def get_queryset(self):
|
||||
qs = super().get_queryset()
|
||||
qs = sorted(qs, key=lambda x: x.created_on, reverse=True)
|
||||
for q in qs:
|
||||
q.created_on = datetime.datetime.strptime(q.created_on, "%Y-%m-%d")
|
||||
return qs
|
||||
|
||||
|
||||
class BillDownloadView(CustomContextMixin, UserTokenRequiredMixin, View):
|
||||
extra_context = {
|
||||
|
@ -168,8 +201,8 @@ class BillDownloadView(CustomContextMixin, UserTokenRequiredMixin, View):
|
|||
|
||||
|
||||
class MailView(ServiceListView):
|
||||
service_class = MailService
|
||||
template_name = "musician/mail.html"
|
||||
service_class = Address
|
||||
template_name = "musician/addresses.html"
|
||||
extra_context = {
|
||||
# Translators: This message appears on the page title
|
||||
'title': _('Mail addresses'),
|
||||
|
@ -198,9 +231,86 @@ class MailView(ServiceListView):
|
|||
context.update({
|
||||
'active_domain': self.orchestra.retrieve_domain(domain_id)
|
||||
})
|
||||
context['mailboxes'] = self.orchestra.retrieve_mailbox_list()
|
||||
return context
|
||||
|
||||
|
||||
class MailCreateView(CustomContextMixin, UserTokenRequiredMixin, FormView):
|
||||
service_class = Address
|
||||
template_name = "musician/address_form.html"
|
||||
form_class = MailForm
|
||||
success_url = reverse_lazy("musician:address-list")
|
||||
extra_context = {'service': service_class}
|
||||
|
||||
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:
|
||||
serialized_data = form.serialize()
|
||||
self.orchestra.create_mail_address(serialized_data)
|
||||
except HTTPError as e:
|
||||
form.add_error(field='__all__', error=e)
|
||||
return self.form_invalid(form)
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class MailUpdateView(CustomContextMixin, UserTokenRequiredMixin, FormView):
|
||||
service_class = Address
|
||||
template_name = "musician/address_form.html"
|
||||
form_class = MailForm
|
||||
success_url = reverse_lazy("musician:address-list")
|
||||
extra_context = {'service': service_class}
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
instance = self.orchestra.retrieve_mail_address(self.kwargs['pk'])
|
||||
|
||||
kwargs.update({
|
||||
'instance': instance,
|
||||
'domains': self.orchestra.retrieve_domain_list(),
|
||||
'mailboxes': self.orchestra.retrieve_mailbox_list(),
|
||||
})
|
||||
|
||||
return kwargs
|
||||
|
||||
def form_valid(self, form):
|
||||
# handle request errors e.g. 400 validation
|
||||
try:
|
||||
serialized_data = form.serialize()
|
||||
self.orchestra.update_mail_address(self.kwargs['pk'], serialized_data)
|
||||
except HTTPError as e:
|
||||
form.add_error(field='__all__', error=e)
|
||||
return self.form_invalid(form)
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class AddressDeleteView(CustomContextMixin, UserTokenRequiredMixin, DeleteView):
|
||||
template_name = "musician/address_check_delete.html"
|
||||
success_url = reverse_lazy("musician:address-list")
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
obj = self.orchestra.retrieve_mail_address(self.kwargs['pk'])
|
||||
return obj
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
try:
|
||||
self.orchestra.delete_mail_address(self.object.id)
|
||||
messages.success(self.request, _('Address deleted!'))
|
||||
except HTTPError as e:
|
||||
messages.error(self.request, _('Cannot process your request, please try again later.'))
|
||||
logger.error(e)
|
||||
|
||||
return HttpResponseRedirect(self.success_url)
|
||||
|
||||
|
||||
class MailingListsView(ServiceListView):
|
||||
service_class = MailinglistService
|
||||
template_name = "musician/mailinglists.html"
|
||||
|
@ -209,7 +319,6 @@ class MailingListsView(ServiceListView):
|
|||
'title': _('Mailing lists'),
|
||||
}
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
domain_id = self.request.GET.get('domain')
|
||||
|
@ -230,6 +339,161 @@ class MailingListsView(ServiceListView):
|
|||
return ''
|
||||
|
||||
|
||||
class MailboxesView(ServiceListView):
|
||||
service_class = Mailbox
|
||||
template_name = "musician/mailboxes.html"
|
||||
extra_context = {
|
||||
# Translators: This message appears on the page title
|
||||
'title': _('Mailboxes'),
|
||||
}
|
||||
|
||||
|
||||
class MailboxCreateView(CustomContextMixin, UserTokenRequiredMixin, FormView):
|
||||
service_class = Mailbox
|
||||
template_name = "musician/mailbox_form.html"
|
||||
form_class = MailboxCreateForm
|
||||
success_url = reverse_lazy("musician:mailbox-list")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({
|
||||
'extra_mailbox': self.is_extra_mailbox(context['profile']),
|
||||
'service': self.service_class,
|
||||
})
|
||||
return context
|
||||
|
||||
def is_extra_mailbox(self, profile):
|
||||
number_of_mailboxes = len(self.orchestra.retrieve_mailbox_list())
|
||||
return number_of_mailboxes >= profile.allowed_resources('mailbox')
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs.update({
|
||||
'addresses': self.orchestra.retrieve_mail_address_list(),
|
||||
})
|
||||
|
||||
return kwargs
|
||||
|
||||
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)
|
||||
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 self.form_invalid(form)
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class MailboxUpdateView(CustomContextMixin, UserTokenRequiredMixin, FormView):
|
||||
service_class = Mailbox
|
||||
template_name = "musician/mailbox_form.html"
|
||||
form_class = MailboxUpdateForm
|
||||
success_url = reverse_lazy("musician:mailbox-list")
|
||||
extra_context = {'service': service_class}
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
instance = self.orchestra.retrieve_mailbox(self.kwargs['pk'])
|
||||
|
||||
kwargs.update({
|
||||
'instance': instance,
|
||||
'addresses': self.orchestra.retrieve_mail_address_list(),
|
||||
})
|
||||
|
||||
return kwargs
|
||||
|
||||
def form_valid(self, form):
|
||||
serialized_data = form.serialize()
|
||||
status, response = self.orchestra.update_mailbox(self.kwargs['pk'], serialized_data)
|
||||
|
||||
if status >= 400:
|
||||
if status == 400:
|
||||
# handle errors & add to form (they will be rendered)
|
||||
form.add_error(field=None, error=response)
|
||||
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 self.form_invalid(form)
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class MailboxDeleteView(CustomContextMixin, UserTokenRequiredMixin, DeleteView):
|
||||
template_name = "musician/mailbox_check_delete.html"
|
||||
success_url = reverse_lazy("musician:mailbox-list")
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
obj = self.orchestra.retrieve_mailbox(self.kwargs['pk'])
|
||||
return obj
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
try:
|
||||
self.orchestra.delete_mailbox(self.object.id)
|
||||
messages.success(self.request, _('Mailbox deleted!'))
|
||||
except HTTPError as e:
|
||||
messages.error(self.request, _('Cannot process your request, please try again later.'))
|
||||
logger.error(e)
|
||||
|
||||
self.notify_managers(self.object)
|
||||
|
||||
return HttpResponseRedirect(self.success_url)
|
||||
|
||||
def notify_managers(self, mailbox):
|
||||
user = self.get_context_data()['profile']
|
||||
subject = 'Mailbox {} ({}) deleted | Musician'.format(mailbox.id, mailbox.name)
|
||||
content = (
|
||||
"User {} ({}) has deleted its mailbox {} ({}) via musician.\n"
|
||||
"The mailbox has been marked as inactive but has not been removed."
|
||||
).format(user.username, user.full_name, mailbox.id, mailbox.name)
|
||||
|
||||
try:
|
||||
mail_managers(subject, content, fail_silently=False)
|
||||
except (smtplib.SMTPException, ConnectionRefusedError):
|
||||
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)
|
||||
|
||||
if status < 400:
|
||||
messages.success(self.request, _('Password updated!'))
|
||||
else:
|
||||
messages.error(self.request, _('Cannot process your request, please try again later.'))
|
||||
logger.error("{}: {}".format(status, str(response)[:100]))
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
|
||||
class DatabasesView(ServiceListView):
|
||||
template_name = "musician/databases.html"
|
||||
service_class = DatabaseService
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
django==2.2.13
|
||||
django==2.2.27
|
||||
python-decouple==3.1
|
||||
django-bootstrap4
|
||||
django-extensions
|
||||
dj_database_url==0.5.0
|
||||
requests==2.22.0
|
||||
|
|
|
@ -13,6 +13,7 @@ https://docs.djangoproject.com/en/2.2/ref/settings/
|
|||
import os
|
||||
|
||||
from decouple import config, Csv
|
||||
from django.contrib.messages import constants as messages
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from dj_database_url import parse as db_url
|
||||
|
||||
|
@ -41,6 +42,8 @@ EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD', default='')
|
|||
|
||||
EMAIL_PORT = config('EMAIL_PORT', default=25, cast=int)
|
||||
|
||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||
|
||||
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[], cast=Csv())
|
||||
|
||||
|
||||
|
@ -53,6 +56,7 @@ INSTALLED_APPS = [
|
|||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django_extensions',
|
||||
'bootstrap4',
|
||||
'musician',
|
||||
]
|
||||
|
||||
|
@ -148,12 +152,6 @@ USE_L10N = True
|
|||
USE_TZ = True
|
||||
|
||||
|
||||
LANGUAGES = (
|
||||
('ca', _('Catalan')),
|
||||
('es', _('Spanish')),
|
||||
('en', _('English')),
|
||||
)
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/2.2/howto/static-files/
|
||||
|
||||
|
@ -176,3 +174,18 @@ URL_SAAS_GITLAB = config('URL_SAAS_GITLAB', None)
|
|||
URL_SAAS_OWNCLOUD = config('URL_SAAS_OWNCLOUD', None)
|
||||
|
||||
URL_SAAS_WORDPRESS = config('URL_SAAS_WORDPRESS', None)
|
||||
|
||||
|
||||
# Managers: who should get notifications about services changes that
|
||||
# may require human actions (e.g. deleted mailboxes)
|
||||
MANAGERS = []
|
||||
|
||||
|
||||
# redefine MESSAGE_TAGS for a better integration with bootstrap
|
||||
MESSAGE_TAGS = {
|
||||
messages.DEBUG: 'debug',
|
||||
messages.INFO: 'info',
|
||||
messages.SUCCESS: 'success',
|
||||
messages.WARNING: 'warning',
|
||||
messages.ERROR: 'danger',
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue