Compare commits
1 Commits
master
...
whitesourc
Author | SHA1 | Date |
---|---|---|
whitesource-bolt-for-github[bot] | 547158eb08 |
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"scanSettings": {
|
||||||
|
"baseBranches": []
|
||||||
|
},
|
||||||
|
"checkRunSettings": {
|
||||||
|
"vulnerableCheckRunConclusionLevel": "failure",
|
||||||
|
"displayMode": "diff"
|
||||||
|
},
|
||||||
|
"issueSettings": {
|
||||||
|
"minSeverityLevel": "LOW"
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,17 +5,8 @@ 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).
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## master
|
## 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).
|
- [changed] Include @pangea.org mail addresses (#4).
|
||||||
- [fixed] Error on login when user never has logged into the system (#6).
|
- [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
|
## [0.1] - 2020-01-29
|
||||||
- Login & logout methods using backend as auth method
|
- Login & logout methods using backend as auth method
|
||||||
|
|
14
Dockerfile
14
Dockerfile
|
@ -1,14 +0,0 @@
|
||||||
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,12 +1,6 @@
|
||||||
# django musician
|
# 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).
|
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?
|
## How do I get set up?
|
||||||
|
|
||||||
1. Install Python and its packet manager (pip)
|
1. Install Python and its packet manager (pip)
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
version: "3.9"
|
|
||||||
services:
|
|
||||||
web:
|
|
||||||
build: .
|
|
||||||
ports:
|
|
||||||
- "8080:8080"
|
|
||||||
volumes:
|
|
||||||
- .:/home
|
|
|
@ -2,7 +2,7 @@
|
||||||
Package metadata definition.
|
Package metadata definition.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
VERSION = (0, 2, 0, 'final', 0)
|
VERSION = (0, 2, 0, 'alpha', 1)
|
||||||
|
|
||||||
|
|
||||||
def get_version():
|
def get_version():
|
||||||
|
|
135
musician/api.py
135
musician/api.py
|
@ -1,12 +1,14 @@
|
||||||
|
import requests
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
import requests
|
from itertools import groupby
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.urls.exceptions import NoReverseMatch
|
from django.urls.exceptions import NoReverseMatch
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from .models import Address, DatabaseService, Domain, Mailbox, SaasService, UserAccount, WebSite
|
from .models import Domain, DatabaseService, MailService, SaasService, UserAccount, WebSite
|
||||||
|
|
||||||
|
|
||||||
DOMAINS_PATH = 'domains/'
|
DOMAINS_PATH = 'domains/'
|
||||||
TOKEN_PATH = '/api-token-auth/'
|
TOKEN_PATH = '/api-token-auth/'
|
||||||
|
@ -21,10 +23,7 @@ API_PATHS = {
|
||||||
'domain-list': 'domains/',
|
'domain-list': 'domains/',
|
||||||
'domain-detail': 'domains/{pk}/',
|
'domain-detail': 'domains/{pk}/',
|
||||||
'address-list': 'addresses/',
|
'address-list': 'addresses/',
|
||||||
'address-detail': 'addresses/{pk}/',
|
|
||||||
'mailbox-list': 'mailboxes/',
|
'mailbox-list': 'mailboxes/',
|
||||||
'mailbox-detail': 'mailboxes/{pk}/',
|
|
||||||
'mailbox-password': 'mailboxes/{pk}/set_password/',
|
|
||||||
'mailinglist-list': 'lists/',
|
'mailinglist-list': 'lists/',
|
||||||
'saas-list': 'saas/',
|
'saas-list': 'saas/',
|
||||||
'website-list': 'websites/',
|
'website-list': 'websites/',
|
||||||
|
@ -63,7 +62,7 @@ class Orchestra(object):
|
||||||
|
|
||||||
return response.json().get("token", None)
|
return response.json().get("token", None)
|
||||||
|
|
||||||
def request(self, verb, resource=None, url=None, data=None, render_as="json", querystring=None, raise_exception=True):
|
def request(self, verb, resource=None, url=None, render_as="json", querystring=None, raise_exception=True):
|
||||||
assert verb in ["HEAD", "GET", "POST", "PATCH", "PUT", "DELETE"]
|
assert verb in ["HEAD", "GET", "POST", "PATCH", "PUT", "DELETE"]
|
||||||
if resource is not None:
|
if resource is not None:
|
||||||
url = self.build_absolute_uri(resource)
|
url = self.build_absolute_uri(resource)
|
||||||
|
@ -74,17 +73,14 @@ class Orchestra(object):
|
||||||
url = "{}?{}".format(url, querystring)
|
url = "{}?{}".format(url, querystring)
|
||||||
|
|
||||||
verb = getattr(self.session, verb.lower())
|
verb = getattr(self.session, verb.lower())
|
||||||
headers = {
|
response = verb(url, headers={"Authorization": "Token {}".format(
|
||||||
"Authorization": "Token {}".format(self.auth_token),
|
self.auth_token)}, allow_redirects=False)
|
||||||
"Content-Type": "application/json",
|
|
||||||
}
|
|
||||||
response = verb(url, json=data, headers=headers, allow_redirects=False)
|
|
||||||
|
|
||||||
if raise_exception:
|
if raise_exception:
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
status = response.status_code
|
status = response.status_code
|
||||||
if status < 500 and render_as == "json":
|
if render_as == "json":
|
||||||
output = response.json()
|
output = response.json()
|
||||||
else:
|
else:
|
||||||
output = response.content
|
output = response.content
|
||||||
|
@ -113,95 +109,52 @@ class Orchestra(object):
|
||||||
raise Http404(_("No domain found matching the query"))
|
raise Http404(_("No domain found matching the query"))
|
||||||
return bill_pdf
|
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 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)
|
# retrieve mails applying filters (if any)
|
||||||
raw_data = self.retrieve_service_list(
|
raw_data = self.retrieve_service_list(
|
||||||
Address.api_name,
|
MailService.api_name,
|
||||||
querystring=querystring,
|
querystring=querystring,
|
||||||
)
|
)
|
||||||
|
|
||||||
addresses = [Address.new_from_json(data) for data in raw_data]
|
# 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))
|
||||||
|
|
||||||
# PATCH to include Pangea addresses not shown by orchestra
|
# PATCH to include Pangea addresses not shown by orchestra
|
||||||
# described on issue #4
|
# described on issue #4
|
||||||
# TODO(@slamora) disabled hacky patch because breaks another funtionalities
|
raw_mailboxes = self.retrieve_service_list('mailbox')
|
||||||
# XXX Fix it on orchestra instead of here???
|
for mailbox in raw_mailboxes:
|
||||||
# raw_mailboxes = self.retrieve_mailbox_list()
|
if mailbox['addresses'] == []:
|
||||||
# for mailbox in raw_mailboxes:
|
address_data = {
|
||||||
# if mailbox['addresses'] == []:
|
'names': [mailbox['name']],
|
||||||
# address_data = {
|
'forward': '',
|
||||||
# 'names': [mailbox['name']],
|
'domain': {
|
||||||
# 'forward': '',
|
'name': 'pangea.org.',
|
||||||
# 'domain': {
|
},
|
||||||
# 'name': 'pangea.org.',
|
'mailboxes': [mailbox],
|
||||||
# },
|
}
|
||||||
# 'mailboxes': [mailbox],
|
pangea_address = MailService.new_from_json(address_data)
|
||||||
# }
|
addresses.append(pangea_address)
|
||||||
# pangea_address = Address.new_from_json(address_data)
|
|
||||||
# addresses.append(pangea_address)
|
|
||||||
|
|
||||||
return addresses
|
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):
|
def retrieve_domain(self, pk):
|
||||||
path = API_PATHS.get('domain-detail').format_map({'pk': pk})
|
path = API_PATHS.get('domain-detail').format_map({'pk': pk})
|
||||||
|
|
||||||
|
@ -221,8 +174,8 @@ class Orchestra(object):
|
||||||
querystring = "domain={}".format(domain_json['id'])
|
querystring = "domain={}".format(domain_json['id'])
|
||||||
|
|
||||||
# retrieve services associated to a domain
|
# retrieve services associated to a domain
|
||||||
domain_json['addresses'] = self.retrieve_service_list(
|
domain_json['mails'] = self.retrieve_service_list(
|
||||||
Address.api_name, querystring)
|
MailService.api_name, querystring)
|
||||||
|
|
||||||
# retrieve websites (as they cannot be filtered by domain on the API we should do it here)
|
# 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'])
|
domain_json['websites'] = self.filter_websites_by_domain(websites, domain_json['id'])
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
|
|
||||||
from django import forms
|
|
||||||
from django.contrib.auth.forms import AuthenticationForm
|
from django.contrib.auth.forms import AuthenticationForm
|
||||||
from django.core.exceptions import ValidationError
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
from . import api
|
from . import api
|
||||||
|
|
||||||
|
@ -23,135 +20,3 @@ class LoginForm(AuthenticationForm):
|
||||||
self.user = orchestra.retrieve_profile()
|
self.user = orchestra.retrieve_profile()
|
||||||
|
|
||||||
return self.cleaned_data
|
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 ""
|
msgstr ""
|
||||||
"Project-Id-Version: \n"
|
"Project-Id-Version: \n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2021-11-24 11:04+0100\n"
|
"POT-Creation-Date: 2020-01-23 17:49+0100\n"
|
||||||
"PO-Revision-Date: 2021-11-25 12:53+0100\n"
|
"PO-Revision-Date: 2020-01-28 17:27+0100\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
"Language: ca\n"
|
"Language: ca\n"
|
||||||
|
@ -16,36 +16,12 @@ msgstr ""
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
"X-Generator: Poedit 3.0\n"
|
"X-Generator: Poedit 2.2.4\n"
|
||||||
|
|
||||||
#: api.py:113 api.py:211
|
#: api.py:108 api.py:117
|
||||||
msgid "No domain found matching the query"
|
msgid "No domain found matching the query"
|
||||||
msgstr "No trobem cap domini que coincideixi amb la teva consulta"
|
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
|
#: mixins.py:14
|
||||||
msgid "Domains & websites"
|
msgid "Domains & websites"
|
||||||
msgstr "Dominis i llocs web"
|
msgstr "Dominis i llocs web"
|
||||||
|
@ -55,12 +31,12 @@ msgid "Mails"
|
||||||
msgstr "Correus"
|
msgstr "Correus"
|
||||||
|
|
||||||
#. Translators: This message appears on the page title
|
#. Translators: This message appears on the page title
|
||||||
#: mixins.py:16 views.py:296
|
#: mixins.py:16 views.py:226
|
||||||
msgid "Mailing lists"
|
msgid "Mailing lists"
|
||||||
msgstr "Llistes de correu"
|
msgstr "Llistes de correu"
|
||||||
|
|
||||||
#. Translators: This message appears on the page title
|
#. Translators: This message appears on the page title
|
||||||
#: mixins.py:17 models.py:147 views.py:480
|
#: mixins.py:17 models.py:138 views.py:255
|
||||||
msgid "Databases"
|
msgid "Databases"
|
||||||
msgstr "Bases de dades"
|
msgstr "Bases de dades"
|
||||||
|
|
||||||
|
@ -68,40 +44,36 @@ msgstr "Bases de dades"
|
||||||
msgid "SaaS"
|
msgid "SaaS"
|
||||||
msgstr "SaaS"
|
msgstr "SaaS"
|
||||||
|
|
||||||
#: models.py:148
|
#: models.py:139
|
||||||
|
#, fuzzy
|
||||||
msgid "Description details for databases page."
|
msgid "Description details for databases page."
|
||||||
msgstr "Consulta la configuració de les teves bases de dades."
|
msgstr "Consulta la configuració de les teves bases de dades."
|
||||||
|
|
||||||
#. Translators: This message appears on the page title
|
#. Translators: This message appears on the page title
|
||||||
#: models.py:235 views.py:185
|
#: models.py:200 views.py:169
|
||||||
msgid "Mail addresses"
|
msgid "Mail addresses"
|
||||||
msgstr "Adreces de correu"
|
msgstr "Adreces de correu"
|
||||||
|
|
||||||
#: models.py:236
|
#: models.py:201
|
||||||
|
#, fuzzy
|
||||||
msgid "Description details for mail addresses page."
|
msgid "Description details for mail addresses page."
|
||||||
msgstr "Consulta aquí totes les adreces de correu que tens actives."
|
msgstr "Consulta aquí totes les adreces de correu que tens actives."
|
||||||
|
|
||||||
#: models.py:311
|
#: models.py:243
|
||||||
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"
|
msgid "Mailing list"
|
||||||
msgstr "Llista de correu"
|
msgstr "Llista de correu"
|
||||||
|
|
||||||
#: models.py:338
|
#: models.py:244
|
||||||
|
#, fuzzy
|
||||||
msgid "Description details for mailinglist page."
|
msgid "Description details for mailinglist page."
|
||||||
msgstr "Consulta aquí els detalls de les teves llistes de correu."
|
msgstr "Consulta aquí els detalls de les teves llistes de correu."
|
||||||
|
|
||||||
#: models.py:364
|
#: models.py:267
|
||||||
msgid "Software as a Service (SaaS)"
|
msgid "Software as a Service (SaaS)"
|
||||||
msgstr "Software as a Service (SaaS)"
|
msgstr "Software as a Service (SaaS)"
|
||||||
|
|
||||||
#: models.py:365
|
#: models.py:268
|
||||||
|
#, fuzzy
|
||||||
msgid "Description details for SaaS page."
|
msgid "Description details for SaaS page."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Si tens algun servei SaaS (Software as a Service) contractat, aquí trobaràs "
|
"Si tens algun servei SaaS (Software as a Service) contractat, aquí trobaràs "
|
||||||
|
@ -128,60 +100,6 @@ msgstr ""
|
||||||
"Envia un correu a <a href=\"mailto:%(support_email)s\">%(support_email)s</a> "
|
"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."
|
"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
|
#: templates/musician/base.html:60
|
||||||
msgid "Settings"
|
msgid "Settings"
|
||||||
msgstr "Configuració"
|
msgstr "Configuració"
|
||||||
|
@ -192,7 +110,7 @@ msgstr "Perfil"
|
||||||
|
|
||||||
#. Translators: This message appears on the page title
|
#. Translators: This message appears on the page title
|
||||||
#: templates/musician/base.html:64 templates/musician/billing.html:6
|
#: templates/musician/base.html:64 templates/musician/billing.html:6
|
||||||
#: views.py:163
|
#: views.py:147
|
||||||
msgid "Billing"
|
msgid "Billing"
|
||||||
msgstr "Factures"
|
msgstr "Factures"
|
||||||
|
|
||||||
|
@ -201,6 +119,7 @@ msgid "Log out"
|
||||||
msgstr "Surt"
|
msgstr "Surt"
|
||||||
|
|
||||||
#: templates/musician/billing.html:7
|
#: templates/musician/billing.html:7
|
||||||
|
#, fuzzy
|
||||||
msgid "Billing page description."
|
msgid "Billing page description."
|
||||||
msgstr "Consulta i descarrega les teves factures."
|
msgstr "Consulta i descarrega les teves factures."
|
||||||
|
|
||||||
|
@ -213,7 +132,7 @@ msgid "Bill date"
|
||||||
msgstr "Data de la factura"
|
msgstr "Data de la factura"
|
||||||
|
|
||||||
#: templates/musician/billing.html:21 templates/musician/databases.html:17
|
#: templates/musician/billing.html:21 templates/musician/databases.html:17
|
||||||
#: templates/musician/domain_detail.html:17
|
#: templates/musician/domain_detail.html:17 templates/musician/mail.html:22
|
||||||
msgid "Type"
|
msgid "Type"
|
||||||
msgstr "Tipus"
|
msgstr "Tipus"
|
||||||
|
|
||||||
|
@ -223,7 +142,7 @@ msgstr "Total"
|
||||||
|
|
||||||
#: templates/musician/billing.html:23
|
#: templates/musician/billing.html:23
|
||||||
msgid "Download PDF"
|
msgid "Download PDF"
|
||||||
msgstr "Descarrega el PDF"
|
msgstr "Descarrega un PDF"
|
||||||
|
|
||||||
#: templates/musician/components/table_paginator.html:15
|
#: templates/musician/components/table_paginator.html:15
|
||||||
msgid "Previous"
|
msgid "Previous"
|
||||||
|
@ -246,85 +165,90 @@ 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!"
|
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!"
|
msgstr "És el primer cop que accedeixes, et donem la benvinguda!"
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:29
|
#: templates/musician/dashboard.html:24
|
||||||
msgid "Notifications"
|
msgid "Notifications"
|
||||||
msgstr "Notificacions"
|
msgstr "Notificacions"
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:33
|
#: templates/musician/dashboard.html:28
|
||||||
msgid "There is no notifications at this time."
|
msgid "There is no notifications at this time."
|
||||||
msgstr "No tens cap notificació."
|
msgstr "No tens cap notificació."
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:40
|
#: templates/musician/dashboard.html:35
|
||||||
msgid "Your domains and websites"
|
msgid "Your domains and websites"
|
||||||
msgstr "Els teus dominis i llocs web"
|
msgstr "Els teus dominis i llocs web"
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:41
|
#: templates/musician/dashboard.html:36
|
||||||
|
#, fuzzy
|
||||||
msgid "Dashboard page description."
|
msgid "Dashboard page description."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Aquest és el teu panell de gestió, des d’on podràs consultar la configuració "
|
"Aquest és el teu panell de gestió, des d’on podràs consultar la configuració "
|
||||||
"dels serveis que Pangea t’ofereix."
|
"dels serveis que Pangea t’ofereix."
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:56
|
#: templates/musician/dashboard.html:51
|
||||||
msgid "view configuration"
|
msgid "view configuration"
|
||||||
msgstr "veure la configuració"
|
msgstr "veure la configuració"
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:63
|
#: templates/musician/dashboard.html:58
|
||||||
msgid "Expiration date"
|
msgid "Expiration date"
|
||||||
msgstr "Data de venciment"
|
msgstr "Data de venciment"
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:70
|
#: templates/musician/dashboard.html:65
|
||||||
msgid "Mail"
|
msgid "Mail"
|
||||||
msgstr "Correu"
|
msgstr "Correu"
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:73
|
#: templates/musician/dashboard.html:68
|
||||||
msgid "mail addresses created"
|
msgid "mail addresses created"
|
||||||
msgstr "adreces de correu creades"
|
msgstr "adreces de correu creades"
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:78
|
#: templates/musician/dashboard.html:71
|
||||||
|
msgid "mail address left"
|
||||||
|
msgstr "adreces de correu per activar"
|
||||||
|
|
||||||
|
#: templates/musician/dashboard.html:77
|
||||||
msgid "Mail list"
|
msgid "Mail list"
|
||||||
msgstr "Llista de correu"
|
msgstr "Llista de correu"
|
||||||
|
|
||||||
#. Translators: This message appears on the page title
|
#. Translators: This message appears on the page title
|
||||||
#: templates/musician/dashboard.html:83 views.py:489
|
#: templates/musician/dashboard.html:82 views.py:264
|
||||||
msgid "Software as a Service"
|
msgid "Software as a Service"
|
||||||
msgstr "Software as a Service"
|
msgstr "Software as a Service"
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:85
|
#: templates/musician/dashboard.html:84
|
||||||
msgid "Nothing installed"
|
msgid "Nothing installed"
|
||||||
msgstr "No tens res instal·lat"
|
msgstr "No tens res instal·lat"
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:90 views.py:57
|
#: templates/musician/dashboard.html:89 views.py:42
|
||||||
msgid "Disk usage"
|
msgid "Disk usage"
|
||||||
msgstr "Ús del disc"
|
msgstr "Ús del disc"
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:107
|
#: templates/musician/dashboard.html:106
|
||||||
msgid "Configuration details"
|
msgid "Configuration details"
|
||||||
msgstr "Detalls de configuració"
|
msgstr "Detalls de configuració"
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:114
|
#: templates/musician/dashboard.html:113
|
||||||
msgid "FTP access:"
|
msgid "FTP access:"
|
||||||
msgstr "Accés FTP:"
|
msgstr "Accés FTP:"
|
||||||
|
|
||||||
#. Translators: domain configuration detail modal
|
#. Translators: domain configuration detail modal
|
||||||
#: templates/musician/dashboard.html:116
|
#: templates/musician/dashboard.html:115
|
||||||
msgid "Contact with the support team to get details concerning FTP access."
|
msgid "Contact with the support team to get details concerning FTP access."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Escriu-nos a <a href=“mailto:%(support_email)s”>%(support_email)s</a> per "
|
"Contacteu-nos a <a href=“mailto:%(support_email)s”>%(support_email)s</a> per "
|
||||||
"saber com accedir al FTP."
|
"saber com accedir al FTP."
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:125
|
#: templates/musician/dashboard.html:124
|
||||||
msgid "No website configured."
|
msgid "No website configured."
|
||||||
msgstr "No hi ha cap web configurada."
|
msgstr "No hi ha cap web configurada."
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:127
|
#: templates/musician/dashboard.html:126
|
||||||
msgid "Root directory:"
|
msgid "Root directory:"
|
||||||
msgstr "Directori arrel:"
|
msgstr "Directori arrel:"
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:128
|
#: templates/musician/dashboard.html:127
|
||||||
msgid "Type:"
|
msgid "Type:"
|
||||||
msgstr "Tipus:"
|
msgstr "Tipus:"
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:133
|
#: templates/musician/dashboard.html:132
|
||||||
msgid "View DNS records"
|
msgid "View DNS records"
|
||||||
msgstr "Veure registres DNS"
|
msgstr "Veure registres DNS"
|
||||||
|
|
||||||
|
@ -355,6 +279,7 @@ msgid "DNS settings for"
|
||||||
msgstr "Configuració DNS per a"
|
msgstr "Configuració DNS per a"
|
||||||
|
|
||||||
#: templates/musician/domain_detail.html:8
|
#: templates/musician/domain_detail.html:8
|
||||||
|
#, fuzzy
|
||||||
msgid "DNS settings page description."
|
msgid "DNS settings page description."
|
||||||
msgstr "Consulta aquí la teva configuració DNS."
|
msgstr "Consulta aquí la teva configuració DNS."
|
||||||
|
|
||||||
|
@ -362,66 +287,25 @@ msgstr "Consulta aquí la teva configuració DNS."
|
||||||
msgid "Value"
|
msgid "Value"
|
||||||
msgstr "Valor"
|
msgstr "Valor"
|
||||||
|
|
||||||
#: templates/musician/mail_base.html:6 templates/musician/mailinglists.html:6
|
#: templates/musician/mail.html:6 templates/musician/mailinglists.html:6
|
||||||
msgid "Go to global"
|
msgid "Go to global"
|
||||||
msgstr "Totes les adreces"
|
msgstr "Totes les adreces"
|
||||||
|
|
||||||
#: templates/musician/mail_base.html:10 templates/musician/mailinglists.html:9
|
#: templates/musician/mail.html:9 templates/musician/mailinglists.html:9
|
||||||
msgid "for"
|
msgid "for"
|
||||||
msgstr "per a"
|
msgstr "per a"
|
||||||
|
|
||||||
#: templates/musician/mail_base.html:18 templates/musician/mailboxes.html:16
|
#: templates/musician/mail.html:20
|
||||||
msgid "Addresses"
|
msgid "Mail address"
|
||||||
msgstr "Adreces de correu"
|
msgstr "Adreça de correu"
|
||||||
|
|
||||||
#: templates/musician/mailbox_change_password.html:5
|
#: templates/musician/mail.html:21
|
||||||
#: templates/musician/mailbox_form.html:24
|
msgid "Aliases"
|
||||||
msgid "Change password"
|
msgstr "Àlies"
|
||||||
msgstr "Canvia la contrasenya"
|
|
||||||
|
|
||||||
#: templates/musician/mailbox_check_delete.html:7
|
#: templates/musician/mail.html:23
|
||||||
#, python-format
|
msgid "Type details"
|
||||||
msgid "Are you sure that you want remove the mailbox: \"%(name)s\"?"
|
msgstr "Detalls de cada tipus"
|
||||||
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
|
#: templates/musician/mailinglists.html:34
|
||||||
msgid "Active"
|
msgid "Active"
|
||||||
|
@ -432,6 +316,7 @@ msgid "Inactive"
|
||||||
msgstr "Inactiu"
|
msgstr "Inactiu"
|
||||||
|
|
||||||
#: templates/musician/profile.html:7
|
#: templates/musician/profile.html:7
|
||||||
|
#, fuzzy
|
||||||
msgid "Little description on profile page."
|
msgid "Little description on profile page."
|
||||||
msgstr "Canvia les teves dades d’accés i opcions de perfil des d’aquí."
|
msgstr "Canvia les teves dades d’accés i opcions de perfil des d’aquí."
|
||||||
|
|
||||||
|
@ -472,67 +357,43 @@ msgid "Open service admin panel"
|
||||||
msgstr "Obre el panell d’administració del servei"
|
msgstr "Obre el panell d’administració del servei"
|
||||||
|
|
||||||
#. Translators: This message appears on the page title
|
#. Translators: This message appears on the page title
|
||||||
#: views.py:41
|
#: views.py:32
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
msgstr "Panell de gestió"
|
msgstr "Panell de gestió"
|
||||||
|
|
||||||
#: views.py:66
|
#: views.py:49
|
||||||
msgid "Traffic"
|
msgid "Traffic"
|
||||||
msgstr "Tràfic"
|
msgstr "Tràfic"
|
||||||
|
|
||||||
#: views.py:97
|
#: views.py:56
|
||||||
msgid "Mailbox usage"
|
msgid "Mailbox usage"
|
||||||
msgstr "Ús d’espai a la bústia de correu"
|
msgstr "Ús d’espai a la bústia de correu"
|
||||||
|
|
||||||
#. Translators: This message appears on the page title
|
#. Translators: This message appears on the page title
|
||||||
#: views.py:112
|
#: views.py:96
|
||||||
msgid "User profile"
|
msgid "User profile"
|
||||||
msgstr "El teu perfil"
|
msgstr "El teu perfil"
|
||||||
|
|
||||||
#. Translators: This message appears on the page title
|
#. Translators: This message appears on the page title
|
||||||
#: views.py:170
|
#: views.py:154
|
||||||
msgid "Download bill"
|
msgid "Download bill"
|
||||||
msgstr "Descarrega la factura"
|
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
|
#. Translators: This message appears on the page title
|
||||||
#: views.py:497
|
#: views.py:272
|
||||||
msgid "Domain details"
|
msgid "Domain details"
|
||||||
msgstr "Detalls del domini"
|
msgstr "Detalls del domini"
|
||||||
|
|
||||||
#. Translators: This message appears on the page title
|
#. Translators: This message appears on the page title
|
||||||
#: views.py:523
|
#: views.py:298
|
||||||
msgid "Login"
|
msgid "Login"
|
||||||
msgstr "Accés"
|
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"
|
#~ msgid "databases created"
|
||||||
#~ msgstr "bases de dades creades"
|
#~ msgstr "bases de dades creades"
|
||||||
|
|
||||||
#~ msgid "Username"
|
#~ msgid "Username"
|
||||||
#~ msgstr "Nom d’usuari/a"
|
#~ msgstr "Nom d’usuari/a"
|
||||||
|
|
||||||
|
#~ msgid "Password:"
|
||||||
|
#~ msgstr "Contrasenya:"
|
||||||
|
|
|
@ -7,8 +7,8 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: \n"
|
"Project-Id-Version: \n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2021-11-24 11:04+0100\n"
|
"POT-Creation-Date: 2020-01-23 17:49+0100\n"
|
||||||
"PO-Revision-Date: 2021-11-25 12:53+0100\n"
|
"PO-Revision-Date: 2020-01-28 17:27+0100\n"
|
||||||
"Last-Translator: \n"
|
"Last-Translator: \n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
"Language: es\n"
|
"Language: es\n"
|
||||||
|
@ -16,36 +16,12 @@ msgstr ""
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
"X-Generator: Poedit 3.0\n"
|
"X-Generator: Poedit 2.2.4\n"
|
||||||
|
|
||||||
#: api.py:113 api.py:211
|
#: api.py:108 api.py:117
|
||||||
msgid "No domain found matching the query"
|
msgid "No domain found matching the query"
|
||||||
msgstr "No hay dominios que coincidan con tu búsqueda"
|
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
|
#: mixins.py:14
|
||||||
msgid "Domains & websites"
|
msgid "Domains & websites"
|
||||||
msgstr "Dominios y sitios web"
|
msgstr "Dominios y sitios web"
|
||||||
|
@ -55,12 +31,12 @@ msgid "Mails"
|
||||||
msgstr "Correos"
|
msgstr "Correos"
|
||||||
|
|
||||||
#. Translators: This message appears on the page title
|
#. Translators: This message appears on the page title
|
||||||
#: mixins.py:16 views.py:296
|
#: mixins.py:16 views.py:226
|
||||||
msgid "Mailing lists"
|
msgid "Mailing lists"
|
||||||
msgstr "Listas de correo"
|
msgstr "Listas de correo"
|
||||||
|
|
||||||
#. Translators: This message appears on the page title
|
#. Translators: This message appears on the page title
|
||||||
#: mixins.py:17 models.py:147 views.py:480
|
#: mixins.py:17 models.py:138 views.py:255
|
||||||
msgid "Databases"
|
msgid "Databases"
|
||||||
msgstr "Bases de datos"
|
msgstr "Bases de datos"
|
||||||
|
|
||||||
|
@ -68,41 +44,36 @@ msgstr "Bases de datos"
|
||||||
msgid "SaaS"
|
msgid "SaaS"
|
||||||
msgstr "SaaS"
|
msgstr "SaaS"
|
||||||
|
|
||||||
#: models.py:148
|
#: models.py:139
|
||||||
|
#, fuzzy
|
||||||
msgid "Description details for databases page."
|
msgid "Description details for databases page."
|
||||||
msgstr "Consulta la configuración de tus bases de datos."
|
msgstr "Consulta la configuración de tus bases de datos."
|
||||||
|
|
||||||
#. Translators: This message appears on the page title
|
#. Translators: This message appears on the page title
|
||||||
#: models.py:235 views.py:185
|
#: models.py:200 views.py:169
|
||||||
msgid "Mail addresses"
|
msgid "Mail addresses"
|
||||||
msgstr "Direcciones de correo"
|
msgstr "Direcciones de correo"
|
||||||
|
|
||||||
#: models.py:236
|
#: models.py:201
|
||||||
|
#, fuzzy
|
||||||
msgid "Description details for mail addresses page."
|
msgid "Description details for mail addresses page."
|
||||||
msgstr "Consulta aquí todas las direcciones de correo que tienes activas."
|
msgstr "Consulta aquí todas las direcciones de correo que tienes activas."
|
||||||
|
|
||||||
#: models.py:311
|
#: models.py:243
|
||||||
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"
|
msgid "Mailing list"
|
||||||
msgstr "Lista de correo"
|
msgstr "Lista de correo"
|
||||||
|
|
||||||
#: models.py:338
|
#: models.py:244
|
||||||
|
#, fuzzy
|
||||||
msgid "Description details for mailinglist page."
|
msgid "Description details for mailinglist page."
|
||||||
msgstr "Consulta aquí los detalles de tus listas de correo."
|
msgstr "Consulta aquí los detalles de tus listas de correo."
|
||||||
|
|
||||||
#: models.py:364
|
#: models.py:267
|
||||||
msgid "Software as a Service (SaaS)"
|
msgid "Software as a Service (SaaS)"
|
||||||
msgstr "Software as a Service (SaaS)"
|
msgstr "Software as a Service (SaaS)"
|
||||||
|
|
||||||
#: models.py:365
|
#: models.py:268
|
||||||
|
#, fuzzy
|
||||||
msgid "Description details for SaaS page."
|
msgid "Description details for SaaS page."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Si tienes algún servicio SaaS (Software as a Service) contratado, aquí "
|
"Si tienes algún servicio SaaS (Software as a Service) contratado, aquí "
|
||||||
|
@ -129,61 +100,6 @@ msgstr ""
|
||||||
"Envía un correo a <a href=“mailto:%(support_email)s”>%(support_email)s</a> "
|
"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."
|
"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
|
#: templates/musician/base.html:60
|
||||||
msgid "Settings"
|
msgid "Settings"
|
||||||
msgstr "Configuración"
|
msgstr "Configuración"
|
||||||
|
@ -194,7 +110,7 @@ msgstr "Perfil"
|
||||||
|
|
||||||
#. Translators: This message appears on the page title
|
#. Translators: This message appears on the page title
|
||||||
#: templates/musician/base.html:64 templates/musician/billing.html:6
|
#: templates/musician/base.html:64 templates/musician/billing.html:6
|
||||||
#: views.py:163
|
#: views.py:147
|
||||||
msgid "Billing"
|
msgid "Billing"
|
||||||
msgstr "Facturas"
|
msgstr "Facturas"
|
||||||
|
|
||||||
|
@ -203,6 +119,7 @@ msgid "Log out"
|
||||||
msgstr "Desconéctate"
|
msgstr "Desconéctate"
|
||||||
|
|
||||||
#: templates/musician/billing.html:7
|
#: templates/musician/billing.html:7
|
||||||
|
#, fuzzy
|
||||||
msgid "Billing page description."
|
msgid "Billing page description."
|
||||||
msgstr "Consulta y descarga tus facturas."
|
msgstr "Consulta y descarga tus facturas."
|
||||||
|
|
||||||
|
@ -215,7 +132,7 @@ msgid "Bill date"
|
||||||
msgstr "Fecha de la factura"
|
msgstr "Fecha de la factura"
|
||||||
|
|
||||||
#: templates/musician/billing.html:21 templates/musician/databases.html:17
|
#: templates/musician/billing.html:21 templates/musician/databases.html:17
|
||||||
#: templates/musician/domain_detail.html:17
|
#: templates/musician/domain_detail.html:17 templates/musician/mail.html:22
|
||||||
msgid "Type"
|
msgid "Type"
|
||||||
msgstr "Tipo"
|
msgstr "Tipo"
|
||||||
|
|
||||||
|
@ -248,85 +165,90 @@ 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!"
|
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!"
|
msgstr "Es la primera vez que accedes: ¡te damos la bienvenida!"
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:29
|
#: templates/musician/dashboard.html:24
|
||||||
msgid "Notifications"
|
msgid "Notifications"
|
||||||
msgstr "Notificaciones"
|
msgstr "Notificaciones"
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:33
|
#: templates/musician/dashboard.html:28
|
||||||
msgid "There is no notifications at this time."
|
msgid "There is no notifications at this time."
|
||||||
msgstr "No tienes ninguna notificación."
|
msgstr "No tienes ninguna notificación."
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:40
|
#: templates/musician/dashboard.html:35
|
||||||
msgid "Your domains and websites"
|
msgid "Your domains and websites"
|
||||||
msgstr "Tus dominios y sitios web"
|
msgstr "Tus dominios y sitios web"
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:41
|
#: templates/musician/dashboard.html:36
|
||||||
|
#, fuzzy
|
||||||
msgid "Dashboard page description."
|
msgid "Dashboard page description."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Este es tu panel de gestión, desde donde podrás consultar la configuración "
|
"Este es tu panel de gestión, desde donde podrás consultar la configuración "
|
||||||
"de los servicios que Pangea te ofrece."
|
"de los servicios que Pangea te ofrece."
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:56
|
#: templates/musician/dashboard.html:51
|
||||||
msgid "view configuration"
|
msgid "view configuration"
|
||||||
msgstr "ver la configuración"
|
msgstr "ver la configuración"
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:63
|
#: templates/musician/dashboard.html:58
|
||||||
msgid "Expiration date"
|
msgid "Expiration date"
|
||||||
msgstr "Fecha de vencimiento"
|
msgstr "Fecha de vencimiento"
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:70
|
#: templates/musician/dashboard.html:65
|
||||||
msgid "Mail"
|
msgid "Mail"
|
||||||
msgstr "Correo"
|
msgstr "Correo"
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:73
|
#: templates/musician/dashboard.html:68
|
||||||
msgid "mail addresses created"
|
msgid "mail addresses created"
|
||||||
msgstr "direcciones de correo creadas"
|
msgstr "direcciones de correo creadas"
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:78
|
#: templates/musician/dashboard.html:71
|
||||||
|
msgid "mail address left"
|
||||||
|
msgstr "direcciones de correo por activar"
|
||||||
|
|
||||||
|
#: templates/musician/dashboard.html:77
|
||||||
msgid "Mail list"
|
msgid "Mail list"
|
||||||
msgstr "Lista de correo"
|
msgstr "Lista de correo"
|
||||||
|
|
||||||
#. Translators: This message appears on the page title
|
#. Translators: This message appears on the page title
|
||||||
#: templates/musician/dashboard.html:83 views.py:489
|
#: templates/musician/dashboard.html:82 views.py:264
|
||||||
msgid "Software as a Service"
|
msgid "Software as a Service"
|
||||||
msgstr "Software as a Service"
|
msgstr "Software as a Service"
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:85
|
#: templates/musician/dashboard.html:84
|
||||||
msgid "Nothing installed"
|
msgid "Nothing installed"
|
||||||
msgstr "No tienes nada instalado"
|
msgstr "No tienes nada instalado"
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:90 views.py:57
|
#: templates/musician/dashboard.html:89 views.py:42
|
||||||
msgid "Disk usage"
|
msgid "Disk usage"
|
||||||
msgstr "Uso del disco"
|
msgstr "Uso del disco"
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:107
|
#: templates/musician/dashboard.html:106
|
||||||
msgid "Configuration details"
|
msgid "Configuration details"
|
||||||
msgstr "Detalles de configuración"
|
msgstr "Detalles de configuración"
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:114
|
#: templates/musician/dashboard.html:113
|
||||||
msgid "FTP access:"
|
msgid "FTP access:"
|
||||||
msgstr "Acceso FTP:"
|
msgstr "Acceso FTP:"
|
||||||
|
|
||||||
#. Translators: domain configuration detail modal
|
#. Translators: domain configuration detail modal
|
||||||
#: templates/musician/dashboard.html:116
|
#: templates/musician/dashboard.html:115
|
||||||
msgid "Contact with the support team to get details concerning FTP access."
|
msgid "Contact with the support team to get details concerning FTP access."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Escríbenos a <a href=“mailto:%(support_email)s”>%(support_email)s</a> para "
|
"Contactadnos a <a href=“mailto:%(support_email)s”>%(support_email)s</a> "
|
||||||
"saber cómo acceder al FTP."
|
"para saber cómo acceder al FTP."
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:125
|
#: templates/musician/dashboard.html:124
|
||||||
msgid "No website configured."
|
msgid "No website configured."
|
||||||
msgstr "No hay ninguna web configurada."
|
msgstr "No hay ninguna web configurada."
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:127
|
#: templates/musician/dashboard.html:126
|
||||||
msgid "Root directory:"
|
msgid "Root directory:"
|
||||||
msgstr "Directorio raíz:"
|
msgstr "Directorio raíz:"
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:128
|
#: templates/musician/dashboard.html:127
|
||||||
msgid "Type:"
|
msgid "Type:"
|
||||||
msgstr "Tipo:"
|
msgstr "Tipo:"
|
||||||
|
|
||||||
#: templates/musician/dashboard.html:133
|
#: templates/musician/dashboard.html:132
|
||||||
msgid "View DNS records"
|
msgid "View DNS records"
|
||||||
msgstr "Ver registros DNS"
|
msgstr "Ver registros DNS"
|
||||||
|
|
||||||
|
@ -357,6 +279,7 @@ msgid "DNS settings for"
|
||||||
msgstr "Configuración DNS para"
|
msgstr "Configuración DNS para"
|
||||||
|
|
||||||
#: templates/musician/domain_detail.html:8
|
#: templates/musician/domain_detail.html:8
|
||||||
|
#, fuzzy
|
||||||
msgid "DNS settings page description."
|
msgid "DNS settings page description."
|
||||||
msgstr "Consulta aquí tu configuración DNS."
|
msgstr "Consulta aquí tu configuración DNS."
|
||||||
|
|
||||||
|
@ -364,66 +287,25 @@ msgstr "Consulta aquí tu configuración DNS."
|
||||||
msgid "Value"
|
msgid "Value"
|
||||||
msgstr "Valor"
|
msgstr "Valor"
|
||||||
|
|
||||||
#: templates/musician/mail_base.html:6 templates/musician/mailinglists.html:6
|
#: templates/musician/mail.html:6 templates/musician/mailinglists.html:6
|
||||||
msgid "Go to global"
|
msgid "Go to global"
|
||||||
msgstr "Todas las direcciones"
|
msgstr "Todas las direcciones"
|
||||||
|
|
||||||
#: templates/musician/mail_base.html:10 templates/musician/mailinglists.html:9
|
#: templates/musician/mail.html:9 templates/musician/mailinglists.html:9
|
||||||
msgid "for"
|
msgid "for"
|
||||||
msgstr "para"
|
msgstr "para"
|
||||||
|
|
||||||
#: templates/musician/mail_base.html:18 templates/musician/mailboxes.html:16
|
#: templates/musician/mail.html:20
|
||||||
msgid "Addresses"
|
msgid "Mail address"
|
||||||
msgstr "Direcciones de correo"
|
msgstr "Dirección de correo"
|
||||||
|
|
||||||
#: templates/musician/mailbox_change_password.html:5
|
#: templates/musician/mail.html:21
|
||||||
#: templates/musician/mailbox_form.html:24
|
msgid "Aliases"
|
||||||
msgid "Change password"
|
msgstr "Alias"
|
||||||
msgstr "Cambia la contraseña"
|
|
||||||
|
|
||||||
#: templates/musician/mailbox_check_delete.html:7
|
#: templates/musician/mail.html:23
|
||||||
#, python-format
|
msgid "Type details"
|
||||||
msgid "Are you sure that you want remove the mailbox: \"%(name)s\"?"
|
msgstr "Detalles de cada tipo"
|
||||||
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
|
#: templates/musician/mailinglists.html:34
|
||||||
msgid "Active"
|
msgid "Active"
|
||||||
|
@ -434,6 +316,7 @@ msgid "Inactive"
|
||||||
msgstr "Inactivo"
|
msgstr "Inactivo"
|
||||||
|
|
||||||
#: templates/musician/profile.html:7
|
#: templates/musician/profile.html:7
|
||||||
|
#, fuzzy
|
||||||
msgid "Little description on profile page."
|
msgid "Little description on profile page."
|
||||||
msgstr "Cambia tus datos de acceso y opciones de perfil desde aquí."
|
msgstr "Cambia tus datos de acceso y opciones de perfil desde aquí."
|
||||||
|
|
||||||
|
@ -443,7 +326,7 @@ msgstr "Información de usuario/a"
|
||||||
|
|
||||||
#: templates/musician/profile.html:21
|
#: templates/musician/profile.html:21
|
||||||
msgid "Preferred language:"
|
msgid "Preferred language:"
|
||||||
msgstr "Idioma preferido:"
|
msgstr "Lenguaje preferido:"
|
||||||
|
|
||||||
#: templates/musician/profile.html:35
|
#: templates/musician/profile.html:35
|
||||||
msgid "Billing information"
|
msgid "Billing information"
|
||||||
|
@ -474,67 +357,43 @@ msgid "Open service admin panel"
|
||||||
msgstr "Abre el panel de administración del servicio"
|
msgstr "Abre el panel de administración del servicio"
|
||||||
|
|
||||||
#. Translators: This message appears on the page title
|
#. Translators: This message appears on the page title
|
||||||
#: views.py:41
|
#: views.py:32
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
msgstr "Panel de gestión"
|
msgstr "Panel de gestión"
|
||||||
|
|
||||||
#: views.py:66
|
#: views.py:49
|
||||||
msgid "Traffic"
|
msgid "Traffic"
|
||||||
msgstr "Tráfico"
|
msgstr "Tráfico"
|
||||||
|
|
||||||
#: views.py:97
|
#: views.py:56
|
||||||
msgid "Mailbox usage"
|
msgid "Mailbox usage"
|
||||||
msgstr "Uso de espacio en tu buzón de correo"
|
msgstr "Uso de espacio en tu buzón de correo"
|
||||||
|
|
||||||
#. Translators: This message appears on the page title
|
#. Translators: This message appears on the page title
|
||||||
#: views.py:112
|
#: views.py:96
|
||||||
msgid "User profile"
|
msgid "User profile"
|
||||||
msgstr "Tu perfil"
|
msgstr "Tu perfil"
|
||||||
|
|
||||||
#. Translators: This message appears on the page title
|
#. Translators: This message appears on the page title
|
||||||
#: views.py:170
|
#: views.py:154
|
||||||
msgid "Download bill"
|
msgid "Download bill"
|
||||||
msgstr "Descarga la factura"
|
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
|
#. Translators: This message appears on the page title
|
||||||
#: views.py:497
|
#: views.py:272
|
||||||
msgid "Domain details"
|
msgid "Domain details"
|
||||||
msgstr "Detalles del dominio"
|
msgstr "Detalles del dominio"
|
||||||
|
|
||||||
#. Translators: This message appears on the page title
|
#. Translators: This message appears on the page title
|
||||||
#: views.py:523
|
#: views.py:298
|
||||||
msgid "Login"
|
msgid "Login"
|
||||||
msgstr "Accede"
|
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"
|
#~ msgid "databases created"
|
||||||
#~ msgstr "bases de datos creadas"
|
#~ msgstr "bases de datos creadas"
|
||||||
|
|
||||||
#~ msgid "Username"
|
#~ msgid "Username"
|
||||||
#~ msgstr "Nombre de usuario/a"
|
#~ msgstr "Nombre de usuario/a"
|
||||||
|
|
||||||
|
#~ msgid "Password:"
|
||||||
|
#~ msgstr "Contraseña:"
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
from django.contrib.auth.mixins import UserPassesTestMixin
|
from django.contrib.auth.mixins import UserPassesTestMixin
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic.base import ContextMixin
|
from django.views.generic.base import ContextMixin
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
from . import api, get_version
|
from . import api, get_version
|
||||||
from .auth import SESSION_KEY_TOKEN
|
from .auth import SESSION_KEY_TOKEN
|
||||||
|
@ -13,15 +12,14 @@ class CustomContextMixin(ContextMixin):
|
||||||
# generate services menu items
|
# generate services menu items
|
||||||
services_menu = [
|
services_menu = [
|
||||||
{'icon': 'globe-europe', 'pattern_name': 'musician:dashboard', 'title': _('Domains & websites')},
|
{'icon': 'globe-europe', 'pattern_name': 'musician:dashboard', 'title': _('Domains & websites')},
|
||||||
{'icon': 'envelope', 'pattern_name': 'musician:address-list', 'title': _('Mails')},
|
{'icon': 'envelope', 'pattern_name': 'musician:mails', 'title': _('Mails')},
|
||||||
{'icon': 'mail-bulk', 'pattern_name': 'musician:mailing-lists', 'title': _('Mailing lists')},
|
{'icon': 'mail-bulk', 'pattern_name': 'musician:mailing-lists', 'title': _('Mailing lists')},
|
||||||
{'icon': 'database', 'pattern_name': 'musician:database-list', 'title': _('Databases')},
|
{'icon': 'database', 'pattern_name': 'musician:databases', 'title': _('Databases')},
|
||||||
{'icon': 'fire', 'pattern_name': 'musician:saas-list', 'title': _('SaaS')},
|
{'icon': 'fire', 'pattern_name': 'musician:saas', 'title': _('SaaS')},
|
||||||
]
|
]
|
||||||
context.update({
|
context.update({
|
||||||
'services_menu': services_menu,
|
'services_menu': services_menu,
|
||||||
'version': get_version(),
|
'version': get_version(),
|
||||||
'languages': settings.LANGUAGES,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
|
@ -17,7 +17,6 @@ class OrchestraModel:
|
||||||
api_name = None
|
api_name = None
|
||||||
verbose_name = None
|
verbose_name = None
|
||||||
fields = ()
|
fields = ()
|
||||||
param_defaults = {}
|
|
||||||
id = None
|
id = None
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
|
@ -35,8 +34,6 @@ class OrchestraModel:
|
||||||
Args:
|
Args:
|
||||||
data: A JSON dict, as converted from the JSON in the orchestra API.
|
data: A JSON dict, as converted from the JSON in the orchestra API.
|
||||||
"""
|
"""
|
||||||
if data is None:
|
|
||||||
return cls()
|
|
||||||
|
|
||||||
json_data = data.copy()
|
json_data = data.copy()
|
||||||
if kwargs:
|
if kwargs:
|
||||||
|
@ -129,10 +126,6 @@ class UserAccount(OrchestraModel):
|
||||||
|
|
||||||
return super().new_from_json(data=data, billing=billing, language=language, last_login=last_login)
|
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):
|
class DatabaseUser(OrchestraModel):
|
||||||
api_name = 'databaseusers'
|
api_name = 'databaseusers'
|
||||||
|
@ -166,7 +159,7 @@ class DatabaseService(OrchestraModel):
|
||||||
return super().new_from_json(data=data, users=users, usage=usage)
|
return super().new_from_json(data=data, users=users, usage=usage)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_usage(cls, data):
|
def get_usage(self, data):
|
||||||
try:
|
try:
|
||||||
resources = data['resources']
|
resources = data['resources']
|
||||||
resource_disk = {}
|
resource_disk = {}
|
||||||
|
@ -203,10 +196,9 @@ class Domain(OrchestraModel):
|
||||||
"id": None,
|
"id": None,
|
||||||
"name": None,
|
"name": None,
|
||||||
"records": [],
|
"records": [],
|
||||||
"addresses": [],
|
"mails": [],
|
||||||
"usage": {},
|
"usage": {},
|
||||||
"websites": [],
|
"websites": [],
|
||||||
"url": None,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -230,19 +222,12 @@ class DomainRecord(OrchestraModel):
|
||||||
return '<%s: %s>' % (self.type, self.value)
|
return '<%s: %s>' % (self.type, self.value)
|
||||||
|
|
||||||
|
|
||||||
class Address(OrchestraModel):
|
class MailService(OrchestraModel):
|
||||||
api_name = 'address'
|
api_name = 'address'
|
||||||
verbose_name = _('Mail addresses')
|
verbose_name = _('Mail addresses')
|
||||||
description = _('Description details for mail addresses page.')
|
description = _('Description details for mail addresses page.')
|
||||||
fields = ('mail_address', 'aliases', 'type', 'type_detail')
|
fields = ('mail_address', 'aliases', 'type', 'type_detail')
|
||||||
param_defaults = {
|
param_defaults = {}
|
||||||
"id": None,
|
|
||||||
"name": None,
|
|
||||||
"domain": None,
|
|
||||||
"mailboxes": [],
|
|
||||||
"forward": None,
|
|
||||||
'url': None,
|
|
||||||
}
|
|
||||||
|
|
||||||
FORWARD = 'forward'
|
FORWARD = 'forward'
|
||||||
MAILBOX = 'mailbox'
|
MAILBOX = 'mailbox'
|
||||||
|
@ -251,15 +236,6 @@ class Address(OrchestraModel):
|
||||||
self.data = kwargs
|
self.data = kwargs
|
||||||
super().__init__(**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
|
@property
|
||||||
def aliases(self):
|
def aliases(self):
|
||||||
return [
|
return [
|
||||||
|
@ -267,8 +243,8 @@ class Address(OrchestraModel):
|
||||||
]
|
]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def full_address_name(self):
|
def mail_address(self):
|
||||||
return "{}@{}".format(self.name, self.domain['name'])
|
return self.data['names'][0] + '@' + self.data['domain']['name']
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def type(self):
|
def type(self):
|
||||||
|
@ -306,32 +282,6 @@ class Address(OrchestraModel):
|
||||||
return mailbox_details
|
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):
|
class MailinglistService(OrchestraModel):
|
||||||
api_name = 'mailinglist'
|
api_name = 'mailinglist'
|
||||||
verbose_name = _('Mailing list')
|
verbose_name = _('Mailing list')
|
||||||
|
@ -349,10 +299,7 @@ class MailinglistService(OrchestraModel):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def address_name(self):
|
def address_name(self):
|
||||||
address_domain = self.data['address_domain']
|
return "{}@{}".format(self.data['address_name'], self.data['address_domain']['name'])
|
||||||
if address_domain is None:
|
|
||||||
return self.data['address_name']
|
|
||||||
return "{}@{}".format(self.data['address_name'], address_domain['name'])
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def manager_url(self):
|
def manager_url(self):
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
from collections import defaultdict
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
@ -6,15 +5,10 @@ def getsetting(name):
|
||||||
value = getattr(settings, name, None)
|
value = getattr(settings, name, None)
|
||||||
return value or DEFAULTS.get(name)
|
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 = {
|
DEFAULTS = {
|
||||||
# allowed resources limit hardcoded because cannot be retrieved from the API.
|
# allowed resources limit hardcoded because cannot be retrieved from the API.
|
||||||
"ALLOWED_RESOURCES": defaultdict(
|
"ALLOWED_RESOURCES": {
|
||||||
allowed_resources_default_factory,
|
|
||||||
{
|
|
||||||
'INDIVIDUAL':
|
'INDIVIDUAL':
|
||||||
{
|
{
|
||||||
# 'disk': 1024,
|
# 'disk': 1024,
|
||||||
|
@ -26,10 +20,9 @@ DEFAULTS = {
|
||||||
# 'traffic': 20 * 1024,
|
# 'traffic': 20 * 1024,
|
||||||
'mailbox': 10,
|
'mailbox': 10,
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
),
|
|
||||||
"URL_DB_PHPMYADMIN": "https://phpmyadmin.pangea.org/",
|
"URL_DB_PHPMYADMIN": "https://phpmyadmin.pangea.org/",
|
||||||
"URL_MAILTRAIN": "https://grups.pangea.org/",
|
"URL_MAILTRAIN": "https://mailtrain.org/",
|
||||||
"URL_SAAS_GITLAB": "https://gitlab.pangea.org/",
|
"URL_SAAS_GITLAB": "https://gitlab.pangea.org/",
|
||||||
"URL_SAAS_OWNCLOUD": "https://nextcloud.pangea.org/",
|
"URL_SAAS_OWNCLOUD": "https://nextcloud.pangea.org/",
|
||||||
"URL_SAAS_WORDPRESS": "https://blog.pangea.org/",
|
"URL_SAAS_WORDPRESS": "https://blog.pangea.org/",
|
||||||
|
|
|
@ -12,15 +12,16 @@ a:hover {
|
||||||
background: #D3D0DA;
|
background: #D3D0DA;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 8px 20px 8px 30px;
|
padding: 8px 20px 8px 30px;
|
||||||
margin-left: 1em;
|
margin-left: 1em; /** equal value than arrow.left **/
|
||||||
/** equal value than arrow.left **/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-arrow-left::after, .btn-arrow-left::before {
|
.btn-arrow-left::after,
|
||||||
|
.btn-arrow-left::before{
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: -1em;
|
left: -1em;
|
||||||
|
|
||||||
margin-top: -19px;
|
margin-top: -19px;
|
||||||
border-top: 19px solid transparent;
|
border-top: 19px solid transparent;
|
||||||
border-bottom: 19px solid transparent;
|
border-bottom: 19px solid transparent;
|
||||||
|
@ -42,21 +43,13 @@ a:hover {
|
||||||
min-width: 280px;
|
min-width: 280px;
|
||||||
max-width: 280px;
|
max-width: 280px;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
transition-duration: 200ms;
|
|
||||||
transition-property: left;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#sidebar-btn {
|
|
||||||
display: none;
|
|
||||||
width: fit-content;
|
|
||||||
transition-duration: 200ms;
|
|
||||||
transition-property: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
#sidebar #sidebar-services {
|
#sidebar #sidebar-services {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
@ -69,7 +62,6 @@ a:hover {
|
||||||
padding-left: 2rem;
|
padding-left: 2rem;
|
||||||
padding-right: 2rem;
|
padding-right: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#sidebar #sidebar-services {
|
#sidebar #sidebar-services {
|
||||||
padding-left: 1rem;
|
padding-left: 1rem;
|
||||||
padding-right: 1rem;
|
padding-right: 1rem;
|
||||||
|
@ -83,6 +75,7 @@ a:hover {
|
||||||
padding: 20px 0;
|
padding: 20px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#sidebar ul li a {
|
#sidebar ul li a {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
|
@ -96,16 +89,14 @@ a:hover {
|
||||||
}
|
}
|
||||||
|
|
||||||
.vertical-center {
|
.vertical-center {
|
||||||
min-height: 100%;
|
min-height: 100%; /* Fallback for browsers do NOT support vh unit */
|
||||||
/* Fallback for browsers do NOT support vh unit */
|
min-height: 100vh; /* These two lines are counted as one :-) */
|
||||||
min-height: 100vh;
|
|
||||||
/* These two lines are counted as one :-) */
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** login **/
|
/** login **/
|
||||||
|
|
||||||
#body-login .jumbotron {
|
#body-login .jumbotron {
|
||||||
background: #282532 no-repeat url("../images/logo-pangea-lilla-bg.svg") right;
|
background: #282532 no-repeat url("../images/logo-pangea-lilla-bg.svg") right;
|
||||||
}
|
}
|
||||||
|
@ -115,7 +106,8 @@ a:hover {
|
||||||
padding: 2rem;
|
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-radius: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-bottom: 2px solid #8E8E8E;
|
border-bottom: 2px solid #8E8E8E;
|
||||||
|
@ -129,7 +121,6 @@ a:hover {
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#login-footer a {
|
#login-footer a {
|
||||||
color: #FEFBF2;
|
color: #FEFBF2;
|
||||||
}
|
}
|
||||||
|
@ -139,37 +130,34 @@ a:hover {
|
||||||
background-position: right 5% top 10%;
|
background-position: right 5% top 10%;
|
||||||
color: #343434;
|
color: #343434;
|
||||||
padding-left: 2rem;
|
padding-left: 2rem;
|
||||||
margin-left: 280px;
|
margin-left: 280px; /** sidebar width **/
|
||||||
/** sidebar width **/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** services **/
|
/** services **/
|
||||||
|
|
||||||
h1.service-name {
|
h1.service-name {
|
||||||
|
|
||||||
font: Bold 26px/34px Roboto;
|
font: Bold 26px/34px Roboto;
|
||||||
margin-top: 3rem;
|
margin-top: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.service-description {
|
.service-description {
|
||||||
|
|
||||||
font: 16px/21px Roboto;
|
font: 16px/21px Roboto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table.service-list {
|
.table.service-list {
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** TODO update theme instead of overriding **/
|
/** TODO update theme instead of overriding **/
|
||||||
|
.service-list thead.thead-dark th,
|
||||||
.service-list thead.thead-dark th, .service-card .card-header {
|
.service-card .card-header {
|
||||||
background: rgba(80, 70, 110, 0.25);
|
background: rgba(80, 70, 110, 0.25);
|
||||||
color: #50466E;
|
color: #50466E;
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** /TODO **/
|
/** /TODO **/
|
||||||
|
.table.service-list td,
|
||||||
.table.service-list td, .table.service-list th {
|
.table.service-list th {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,6 +202,7 @@ h1.service-name {
|
||||||
|
|
||||||
.service-card .card-body {
|
.service-card .card-body {
|
||||||
color: #787878;
|
color: #787878;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.service-card .card-body i.fas {
|
.service-card .card-body i.fas {
|
||||||
|
@ -226,7 +215,8 @@ h1.service-name {
|
||||||
right: 15px;
|
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;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,9 +243,11 @@ h1.service-name {
|
||||||
font-variant: normal;
|
font-variant: normal;
|
||||||
text-rendering: auto;
|
text-rendering: auto;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 10px;
|
right: 10px;
|
||||||
|
|
||||||
color: #E8E7EB;
|
color: #E8E7EB;
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
}
|
}
|
||||||
|
@ -316,69 +308,3 @@ h1.service-name {
|
||||||
border-top: 0;
|
border-top: 0;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.roll-hover {
|
|
||||||
visibility: hidden;
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
td:hover .roll-hover {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
#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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
{% 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 %}
|
|
|
@ -1,20 +0,0 @@
|
||||||
{% 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 %}
|
|
|
@ -1,41 +0,0 @@
|
||||||
{% 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,7 +9,6 @@
|
||||||
{% block meta %}
|
{% block meta %}
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
<meta name="robots" content="NONE,NOARCHIVE" />
|
<meta name="robots" content="NONE,NOARCHIVE" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
<title>{% block title %}{% if title %}{{ title }} – {% endif %}Django musician{% endblock %}</title>
|
<title>{% block title %}{% if title %}{{ title }} – {% endif %}Django musician{% endblock %}</title>
|
||||||
|
@ -33,8 +32,6 @@
|
||||||
|
|
||||||
<body class="{% block bodyclass %}{% endblock %}">
|
<body class="{% block bodyclass %}{% endblock %}">
|
||||||
<div class="wrapper">
|
<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">
|
<nav id="sidebar" class="bg-primary border-right pt-4">
|
||||||
{% block sidebar %}
|
{% block sidebar %}
|
||||||
<div class="sidebar-branding">
|
<div class="sidebar-branding">
|
||||||
|
@ -68,16 +65,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div class="sidebar-logout">
|
<div class="sidebar-logout">
|
||||||
<ul class="nav flex-row">
|
<ul class="nav flex-column">
|
||||||
<li class="nav-item btn btn-outline-primary btn-sm" data-toggle="modal" data-target="#helpModal">
|
<li class="nav-item text-right">
|
||||||
<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' %}">
|
<a class="nav-link text-light" href="{% url 'musician:logout' %}">
|
||||||
{% trans 'Log out' %}
|
{% trans 'Log out' %}
|
||||||
<i class="fas fa-power-off"></i>
|
<i class="fas fa-power-off"></i>
|
||||||
|
@ -87,51 +77,16 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 pr-3 pb-2 text-light d-block text-right">
|
<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>
|
<small>Panel Version {{ version }}</small>
|
||||||
</div>
|
</div>
|
||||||
{% endblock sidebar %}
|
{% endblock sidebar %}
|
||||||
</nav><!-- ./sidebar -->
|
</nav><!-- ./sidebar -->
|
||||||
<div id="content" class="container-fluid pt-4">
|
<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 %}
|
{% block content %}
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
</div><!-- ./content -->
|
</div><!-- ./content -->
|
||||||
</div><!-- ./wrapper -->
|
</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 %}
|
{% block script %}
|
||||||
<script src="{% static "musician/js/jquery-3.3.1.slim.min.js" %}"></script>
|
<script src="{% static "musician/js/jquery-3.3.1.slim.min.js" %}"></script>
|
||||||
<script src="{% static "musician/js/popper.min.js" %}"></script>
|
<script src="{% static "musician/js/popper.min.js" %}"></script>
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
{% for bill in object_list %}
|
{% for bill in object_list %}
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row">{{ bill.number }}</th>
|
<th scope="row">{{ bill.number }}</th>
|
||||||
<td>{{ bill.created_on|date:"SHORT_DATE_FORMAT" }}</td>
|
<td>{{ bill.created_on }}</td>
|
||||||
<td>{{ bill.type }}</td>
|
<td>{{ bill.type }}</td>
|
||||||
<td>{{ bill.total|floatformat:2|localize }}€</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>
|
<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">
|
<div class="text-center">
|
||||||
{% if detail %}
|
{% if detail %}
|
||||||
{{ detail.usage }} {{ detail.unit }}
|
{{ detail.usage }} of {{ detail.total }} {{ detail.unit }}
|
||||||
{% else %}
|
{% else %}
|
||||||
N/A
|
N/A
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<h2 style="margin-top: 10px;">{% trans "Welcome back" %} <strong>{{ profile.username }}</strong></h2>
|
<h2>{% trans "Welcome back" %} <strong>{{ profile.username }}</strong></h2>
|
||||||
{% if profile.last_login %}
|
{% 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>
|
<p>{% blocktrans with last_login=profile.last_login|date:"SHORT_DATE_FORMAT" %}Last time you logged in was: {{ last_login }}{% endblocktrans %}</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -16,11 +16,6 @@
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="card-title">{{ usage.verbose_name }}</h5>
|
<h5 class="card-title">{{ usage.verbose_name }}</h5>
|
||||||
{% include "musician/components/usage_progress_bar.html" with detail=usage.data %}
|
{% 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>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -66,34 +61,38 @@
|
||||||
</div>
|
</div>
|
||||||
</div><!-- /card-header-->
|
</div><!-- /card-header-->
|
||||||
<div class="card-body row text-center">
|
<div class="card-body row text-center">
|
||||||
<div class="col-6 col-md-3 col-lg-2 border-right">
|
<div class="col-md-2 border-right">
|
||||||
<h4>{% trans "Mail" %}</h4>
|
<h4>{% trans "Mail" %}</h4>
|
||||||
<p class="card-text"><i class="fas fa-envelope fa-3x"></i></p>
|
<p class="card-text"><i class="fas fa-envelope fa-3x"></i></p>
|
||||||
<p class="card-text text-dark">
|
<p class="card-text text-dark">
|
||||||
{{ domain.addresses|length }} {% trans "mail addresses created" %}
|
{{ 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 %}
|
||||||
</p>
|
</p>
|
||||||
<a class="stretched-link" href="{% url 'musician:address-list' %}?domain={{ domain.id }}"></a>
|
<a class="stretched-link" href="{% url 'musician:mails' %}?domain={{ domain.id }}"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6 col-md-3 col-lg-2 border-right">
|
<div class="col-md-2 border-right">
|
||||||
<h4>{% trans "Mail list" %}</h4>
|
<h4>{% trans "Mail list" %}</h4>
|
||||||
<p class="card-text"><i class="fas fa-mail-bulk fa-3x"></i></p>
|
<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>
|
<a class="stretched-link" href="{% url 'musician:mailing-lists' %}?domain={{ domain.id }}"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6 col-md-3 col-lg-2 border-right">
|
<div class="col-md-2 border-right">
|
||||||
<h4>{% trans "Software as a Service" %}</h4>
|
<h4>{% trans "Software as a Service" %}</h4>
|
||||||
<p class="card-text"><i class="fas fa-fire fa-3x"></i></p>
|
<p class="card-text"><i class="fas fa-fire fa-3x"></i></p>
|
||||||
<p class="card-text text-dark">{% trans "Nothing installed" %}</p>
|
<p class="card-text text-dark">{% trans "Nothing installed" %}</p>
|
||||||
<a class="stretched-link" href="{% url 'musician:saas-list' %}?domain={{ domain.id }}"></a>
|
<a class="stretched-link" href="{% url 'musician:saas' %}?domain={{ domain.id }}"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-none d-lg-block col-lg-1"></div>
|
<div class="col-md-1"></div>
|
||||||
<div class="col-6 col-md-3 col-lg-4">
|
<div class="col-md-4">
|
||||||
<h4>{% trans "Disk usage" %}</h4>
|
<h4>{% trans "Disk usage" %}</h4>
|
||||||
<p class="card-text"><i class="fas fa-hdd fa-3x"></i></p>
|
<p class="card-text"><i class="fas fa-hdd fa-3x"></i></p>
|
||||||
<div class="w-75 m-auto">
|
<div class="w-75 m-auto">
|
||||||
{% include "musician/components/usage_progress_bar.html" with detail=domain.usage %}
|
{% include "musician/components/usage_progress_bar.html" with detail=domain.usage %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-none d-lg-block col-lg-1"></div>
|
<div class="col-md-1"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
{% 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 %}
|
|
@ -1,32 +0,0 @@
|
||||||
{% 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 %}
|
|
|
@ -1,15 +0,0 @@
|
||||||
{% 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 %}
|
|
|
@ -1,15 +0,0 @@
|
||||||
{% 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 %}
|
|
|
@ -1,30 +0,0 @@
|
||||||
{% 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 %}
|
|
|
@ -1,46 +0,0 @@
|
||||||
{% 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,37 +1,9 @@
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from .models import DatabaseService, UserAccount
|
from .models import UserAccount
|
||||||
from .utils import get_bootstraped_percent
|
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):
|
class DomainsTestCase(TestCase):
|
||||||
def test_domain_not_found(self):
|
def test_domain_not_found(self):
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
|
@ -90,31 +62,6 @@ class UserAccountTest(TestCase):
|
||||||
account = UserAccount.new_from_json(data)
|
account = UserAccount.new_from_json(data)
|
||||||
self.assertIsNone(account.last_login)
|
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):
|
class GetBootstrapedPercentTest(TestCase):
|
||||||
BS_WIDTH = [0, 25, 50, 100]
|
BS_WIDTH = [0, 25, 50, 100]
|
||||||
|
@ -146,8 +93,3 @@ class GetBootstrapedPercentTest(TestCase):
|
||||||
|
|
||||||
def test_invalid_total_is_zero(self):
|
def test_invalid_total_is_zero(self):
|
||||||
value = get_bootstraped_percent(25, 0)
|
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,20 +16,11 @@ urlpatterns = [
|
||||||
path('auth/logout/', views.LogoutView.as_view(), name='logout'),
|
path('auth/logout/', views.LogoutView.as_view(), name='logout'),
|
||||||
path('dashboard/', views.DashboardView.as_view(), name='dashboard'),
|
path('dashboard/', views.DashboardView.as_view(), name='dashboard'),
|
||||||
path('domains/<int:pk>/', views.DomainDetailView.as_view(), name='domain-detail'),
|
path('domains/<int:pk>/', views.DomainDetailView.as_view(), name='domain-detail'),
|
||||||
path('billing/', views.BillingView.as_view(), name='billing'),
|
path('bills/', views.BillingView.as_view(), name='billing'),
|
||||||
path('bills/<int:pk>/download/', views.BillDownloadView.as_view(), name='bill-download'),
|
path('bills/<int:pk>/download/', views.BillDownloadView.as_view(), name='bill-download'),
|
||||||
path('profile/', views.ProfileView.as_view(), name='profile'),
|
path('profile/', views.ProfileView.as_view(), name='profile'),
|
||||||
path('profile/setLang/<code>', views.profile_set_language, name='profile-set-lang'),
|
path('mails/', views.MailView.as_view(), name='mails'),
|
||||||
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('mailing-lists/', views.MailingListsView.as_view(), name='mailing-lists'),
|
||||||
path('databases/', views.DatabasesView.as_view(), name='database-list'),
|
path('databases/', views.DatabasesView.as_view(), name='databases'),
|
||||||
path('saas/', views.SaasView.as_view(), name='saas-list'),
|
path('software-as-a-service/', views.SaasView.as_view(), name='saas'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,7 +6,7 @@ def get_bootstraped_percent(value, total):
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
percent = value / total
|
percent = value / total
|
||||||
except (TypeError, ZeroDivisionError):
|
except ZeroDivisionError:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
bootstraped = round(percent * 4) * 100 // 4
|
bootstraped = round(percent * 4) * 100 // 4
|
||||||
|
|
|
@ -1,37 +1,28 @@
|
||||||
import logging
|
|
||||||
import smtplib
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.core.mail import mail_managers
|
from django.http import HttpResponse, HttpResponseRedirect
|
||||||
from django.http import HttpResponse, HttpResponseNotFound, HttpResponseRedirect
|
from django.shortcuts import render
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
from django.utils.html import format_html
|
|
||||||
from django.utils.http import is_safe_url
|
from django.utils.http import is_safe_url
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from django.views.generic.base import RedirectView, TemplateView
|
from django.views.generic.base import RedirectView, TemplateView
|
||||||
from django.views.generic.detail import DetailView
|
from django.views.generic.detail import DetailView
|
||||||
from django.views.generic.edit import DeleteView, FormView
|
from django.views.generic.edit import FormView
|
||||||
from django.views.generic.list import ListView
|
from django.views.generic.list import ListView
|
||||||
from requests.exceptions import HTTPError
|
|
||||||
|
|
||||||
from . import get_version
|
from . import api, get_version
|
||||||
from .auth import login as auth_login
|
from .auth import login as auth_login
|
||||||
from .auth import logout as auth_logout
|
from .auth import logout as auth_logout
|
||||||
from .forms import LoginForm, MailboxChangePasswordForm, MailboxCreateForm, MailboxUpdateForm, MailForm
|
from .forms import LoginForm
|
||||||
from .mixins import (CustomContextMixin, ExtendedPaginationMixin,
|
from .mixins import (CustomContextMixin, ExtendedPaginationMixin,
|
||||||
UserTokenRequiredMixin)
|
UserTokenRequiredMixin)
|
||||||
from .models import (Address, Bill, DatabaseService, Mailbox,
|
from .models import (Bill, DatabaseService, MailinglistService, MailService,
|
||||||
MailinglistService, PaymentSource, SaasService)
|
PaymentSource, SaasService, UserAccount)
|
||||||
from .settings import ALLOWED_RESOURCES
|
from .settings import ALLOWED_RESOURCES
|
||||||
from .utils import get_bootstraped_percent
|
from .utils import get_bootstraped_percent
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class DashboardView(CustomContextMixin, UserTokenRequiredMixin, TemplateView):
|
class DashboardView(CustomContextMixin, UserTokenRequiredMixin, TemplateView):
|
||||||
template_name = "musician/dashboard.html"
|
template_name = "musician/dashboard.html"
|
||||||
|
@ -49,6 +40,20 @@ class DashboardView(CustomContextMixin, UserTokenRequiredMixin, TemplateView):
|
||||||
|
|
||||||
# show resource usage based on plan definition
|
# show resource usage based on plan definition
|
||||||
profile_type = context['profile'].type
|
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
|
# TODO(@slamora) update when backend provides resource usage data
|
||||||
resource_usage = {
|
resource_usage = {
|
||||||
|
@ -70,7 +75,15 @@ class DashboardView(CustomContextMixin, UserTokenRequiredMixin, TemplateView):
|
||||||
# 'percent': 25,
|
# 'percent': 25,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'mailbox': self.get_mailbox_usage(profile_type),
|
'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']),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
context.update({
|
context.update({
|
||||||
|
@ -81,28 +94,6 @@ class DashboardView(CustomContextMixin, UserTokenRequiredMixin, TemplateView):
|
||||||
|
|
||||||
return context
|
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):
|
class ProfileView(CustomContextMixin, UserTokenRequiredMixin, TemplateView):
|
||||||
template_name = "musician/profile.html"
|
template_name = "musician/profile.html"
|
||||||
|
@ -125,23 +116,6 @@ class ProfileView(CustomContextMixin, UserTokenRequiredMixin, TemplateView):
|
||||||
return context
|
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):
|
class ServiceListView(CustomContextMixin, ExtendedPaginationMixin, UserTokenRequiredMixin, ListView):
|
||||||
"""Base list view to all services"""
|
"""Base list view to all services"""
|
||||||
service_class = None
|
service_class = None
|
||||||
|
@ -179,13 +153,6 @@ class BillingView(ServiceListView):
|
||||||
'title': _('Billing'),
|
'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):
|
class BillDownloadView(CustomContextMixin, UserTokenRequiredMixin, View):
|
||||||
extra_context = {
|
extra_context = {
|
||||||
|
@ -201,8 +168,8 @@ class BillDownloadView(CustomContextMixin, UserTokenRequiredMixin, View):
|
||||||
|
|
||||||
|
|
||||||
class MailView(ServiceListView):
|
class MailView(ServiceListView):
|
||||||
service_class = Address
|
service_class = MailService
|
||||||
template_name = "musician/addresses.html"
|
template_name = "musician/mail.html"
|
||||||
extra_context = {
|
extra_context = {
|
||||||
# Translators: This message appears on the page title
|
# Translators: This message appears on the page title
|
||||||
'title': _('Mail addresses'),
|
'title': _('Mail addresses'),
|
||||||
|
@ -231,86 +198,9 @@ class MailView(ServiceListView):
|
||||||
context.update({
|
context.update({
|
||||||
'active_domain': self.orchestra.retrieve_domain(domain_id)
|
'active_domain': self.orchestra.retrieve_domain(domain_id)
|
||||||
})
|
})
|
||||||
context['mailboxes'] = self.orchestra.retrieve_mailbox_list()
|
|
||||||
return context
|
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):
|
class MailingListsView(ServiceListView):
|
||||||
service_class = MailinglistService
|
service_class = MailinglistService
|
||||||
template_name = "musician/mailinglists.html"
|
template_name = "musician/mailinglists.html"
|
||||||
|
@ -319,6 +209,7 @@ class MailingListsView(ServiceListView):
|
||||||
'title': _('Mailing lists'),
|
'title': _('Mailing lists'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
domain_id = self.request.GET.get('domain')
|
domain_id = self.request.GET.get('domain')
|
||||||
|
@ -339,161 +230,6 @@ class MailingListsView(ServiceListView):
|
||||||
return ''
|
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):
|
class DatabasesView(ServiceListView):
|
||||||
template_name = "musician/databases.html"
|
template_name = "musician/databases.html"
|
||||||
service_class = DatabaseService
|
service_class = DatabaseService
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
django==2.2.27
|
django==2.2.13
|
||||||
python-decouple==3.1
|
python-decouple==3.1
|
||||||
django-bootstrap4
|
|
||||||
django-extensions
|
django-extensions
|
||||||
dj_database_url==0.5.0
|
dj_database_url==0.5.0
|
||||||
requests==2.22.0
|
requests==2.22.0
|
||||||
|
|
|
@ -13,7 +13,6 @@ https://docs.djangoproject.com/en/2.2/ref/settings/
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from decouple import config, Csv
|
from decouple import config, Csv
|
||||||
from django.contrib.messages import constants as messages
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from dj_database_url import parse as db_url
|
from dj_database_url import parse as db_url
|
||||||
|
|
||||||
|
@ -42,8 +41,6 @@ EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD', default='')
|
||||||
|
|
||||||
EMAIL_PORT = config('EMAIL_PORT', default=25, cast=int)
|
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())
|
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default=[], cast=Csv())
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,7 +53,6 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'django_extensions',
|
'django_extensions',
|
||||||
'bootstrap4',
|
|
||||||
'musician',
|
'musician',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -152,6 +148,12 @@ USE_L10N = True
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
LANGUAGES = (
|
||||||
|
('ca', _('Catalan')),
|
||||||
|
('es', _('Spanish')),
|
||||||
|
('en', _('English')),
|
||||||
|
)
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
# https://docs.djangoproject.com/en/2.2/howto/static-files/
|
# https://docs.djangoproject.com/en/2.2/howto/static-files/
|
||||||
|
|
||||||
|
@ -174,18 +176,3 @@ URL_SAAS_GITLAB = config('URL_SAAS_GITLAB', None)
|
||||||
URL_SAAS_OWNCLOUD = config('URL_SAAS_OWNCLOUD', None)
|
URL_SAAS_OWNCLOUD = config('URL_SAAS_OWNCLOUD', None)
|
||||||
|
|
||||||
URL_SAAS_WORDPRESS = config('URL_SAAS_WORDPRESS', 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