Merge branch 'master' into dev/github-workflow-ci
This commit is contained in:
commit
0636f15ee2
|
@ -9,7 +9,7 @@ class SetPasswordApiMixin(object):
|
||||||
@detail_route(methods=['post'], serializer_class=SetPasswordSerializer)
|
@detail_route(methods=['post'], serializer_class=SetPasswordSerializer)
|
||||||
def set_password(self, request, pk):
|
def set_password(self, request, pk):
|
||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
data = request.DATA
|
data = request.data
|
||||||
if isinstance(data, str):
|
if isinstance(data, str):
|
||||||
data = {
|
data = {
|
||||||
'password': data
|
'password': data
|
||||||
|
|
|
@ -98,7 +98,7 @@ class SetPasswordHyperlinkedSerializer(HyperlinkedModelSerializer):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
password = attrs.pop('password', None)
|
password = attrs.pop('password', None)
|
||||||
attrs = super(SetPasswordSerializer, self).validate()
|
attrs = super().validate(attrs)
|
||||||
if password is not None:
|
if password is not None:
|
||||||
attrs['password'] = password
|
attrs['password'] = password
|
||||||
return attrs
|
return attrs
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-05-28 18:05
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
import orchestra.contrib.accounts.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('accounts', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelManagers(
|
||||||
|
name='account',
|
||||||
|
managers=[
|
||||||
|
('objects', orchestra.contrib.accounts.models.AccountManager()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='account',
|
||||||
|
name='language',
|
||||||
|
field=models.CharField(choices=[('CA', 'Catalan'), ('ES', 'Spanish'), ('EN', 'English')], default='CA', max_length=2, verbose_name='language'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='account',
|
||||||
|
name='type',
|
||||||
|
field=models.CharField(choices=[('INDIVIDUAL', 'Individual'), ('ASSOCIATION', 'Association'), ('CUSTOMER', 'Customer'), ('STAFF', 'Staff'), ('FRIEND', 'Friend')], default='INDIVIDUAL', max_length=32, verbose_name='type'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='account',
|
||||||
|
name='username',
|
||||||
|
field=models.CharField(help_text='Required. 32 characters or fewer. Letters, digits and ./-/_ only.', max_length=32, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.-]+$', 'Enter a valid username.', 'invalid')], verbose_name='username'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -9,7 +9,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
#from orchestra.contrib.orchestration.middlewares import OperationsMiddleware
|
#from orchestra.contrib.orchestration.middlewares import OperationsMiddleware
|
||||||
#from orchestra.contrib.orchestration import Operation
|
#from orchestra.contrib.orchestration import Operation
|
||||||
from orchestra.core import services
|
from orchestra import core
|
||||||
from orchestra.models.utils import has_db_field
|
from orchestra.models.utils import has_db_field
|
||||||
from orchestra.utils.mail import send_email_template
|
from orchestra.utils.mail import send_email_template
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ class Account(auth.AbstractBaseUser):
|
||||||
]
|
]
|
||||||
for rel in related_fields:
|
for rel in related_fields:
|
||||||
source = getattr(rel, 'related_model', rel.model)
|
source = getattr(rel, 'related_model', rel.model)
|
||||||
if source in services and hasattr(source, 'active'):
|
if source in core.services and hasattr(source, 'active'):
|
||||||
for obj in getattr(self, rel.get_accessor_name()).all():
|
for obj in getattr(self, rel.get_accessor_name()).all():
|
||||||
yield obj
|
yield obj
|
||||||
|
|
||||||
|
@ -141,12 +141,25 @@ class Account(auth.AbstractBaseUser):
|
||||||
backend returns True. Thus, a user who has permission from a single
|
backend returns True. Thus, a user who has permission from a single
|
||||||
auth backend is assumed to have permission in general. If an object is
|
auth backend is assumed to have permission in general. If an object is
|
||||||
provided, permissions for this specific object are checked.
|
provided, permissions for this specific object are checked.
|
||||||
|
applabel.action_modelname
|
||||||
"""
|
"""
|
||||||
|
if not self.is_active:
|
||||||
|
return False
|
||||||
# Active superusers have all permissions.
|
# Active superusers have all permissions.
|
||||||
if self.is_active and self.is_superuser:
|
if self.is_superuser:
|
||||||
return True
|
return True
|
||||||
# Otherwise we need to check the backends.
|
app, action_model = perm.split('.')
|
||||||
return auth._user_has_perm(self, perm, obj)
|
action, model = action_model.split('_', 1)
|
||||||
|
service_apps = set(model._meta.app_label for model in core.services.get().keys())
|
||||||
|
accounting_apps = set(model._meta.app_label for model in core.accounts.get().keys())
|
||||||
|
import inspect
|
||||||
|
if ((app in service_apps or (action == 'view' and app in accounting_apps))):
|
||||||
|
# class-level permissions
|
||||||
|
if inspect.isclass(obj):
|
||||||
|
return True
|
||||||
|
elif obj and getattr(obj, 'account', None) == self:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def has_perms(self, perm_list, obj=None):
|
def has_perms(self, perm_list, obj=None):
|
||||||
"""
|
"""
|
||||||
|
@ -167,8 +180,7 @@ class Account(auth.AbstractBaseUser):
|
||||||
# Active superusers have all permissions.
|
# Active superusers have all permissions.
|
||||||
if self.is_active and self.is_superuser:
|
if self.is_active and self.is_superuser:
|
||||||
return True
|
return True
|
||||||
return auth._user_has_module_perms(self, app_label)
|
|
||||||
|
|
||||||
def get_related_passwords(self, db_field=False):
|
def get_related_passwords(self, db_field=False):
|
||||||
related = [
|
related = [
|
||||||
self.main_systemuser,
|
self.main_systemuser,
|
||||||
|
|
|
@ -7,7 +7,7 @@ class AccountSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Account
|
model = Account
|
||||||
fields = (
|
fields = (
|
||||||
'url', 'id', 'username', 'type', 'language', 'short_name', 'full_name', 'date_joined',
|
'url', 'id', 'username', 'type', 'language', 'short_name', 'full_name', 'date_joined', 'last_login',
|
||||||
'is_active'
|
'is_active'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ from orchestra.forms.widgets import paddingCheckboxSelectMultiple
|
||||||
from . import settings, actions
|
from . import settings, actions
|
||||||
from .filters import (BillTypeListFilter, HasBillContactListFilter, TotalListFilter,
|
from .filters import (BillTypeListFilter, HasBillContactListFilter, TotalListFilter,
|
||||||
PaymentStateListFilter, AmendedListFilter)
|
PaymentStateListFilter, AmendedListFilter)
|
||||||
from .models import (Bill, Invoice, AmendmentInvoice, Fee, AmendmentFee, ProForma, BillLine,
|
from .models import (Bill, Invoice, AmendmentInvoice, AbonoInvoice, Fee, AmendmentFee, ProForma, BillLine,
|
||||||
BillSubline, BillContact)
|
BillSubline, BillContact)
|
||||||
|
|
||||||
|
|
||||||
|
@ -461,6 +461,7 @@ class BillAdmin(BillAdminMixin, ExtendedModelAdmin):
|
||||||
admin.site.register(Bill, BillAdmin)
|
admin.site.register(Bill, BillAdmin)
|
||||||
admin.site.register(Invoice, BillAdmin)
|
admin.site.register(Invoice, BillAdmin)
|
||||||
admin.site.register(AmendmentInvoice, BillAdmin)
|
admin.site.register(AmendmentInvoice, BillAdmin)
|
||||||
|
admin.site.register(AbonoInvoice, BillAdmin)
|
||||||
admin.site.register(Fee, BillAdmin)
|
admin.site.register(Fee, BillAdmin)
|
||||||
admin.site.register(AmendmentFee, BillAdmin)
|
admin.site.register(AmendmentFee, BillAdmin)
|
||||||
admin.site.register(ProForma, BillAdmin)
|
admin.site.register(ProForma, BillAdmin)
|
||||||
|
|
Binary file not shown.
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2015-10-29 10:51+0000\n"
|
"POT-Creation-Date: 2019-12-20 11:56+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
@ -18,33 +18,33 @@ msgstr ""
|
||||||
"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"
|
||||||
|
|
||||||
#: actions.py:31
|
#: actions.py:33
|
||||||
msgid "View"
|
msgid "View"
|
||||||
msgstr "Vista"
|
msgstr "Vista"
|
||||||
|
|
||||||
#: actions.py:42
|
#: actions.py:45
|
||||||
msgid "Selected bills should be in open state"
|
msgid "Selected bills should be in open state"
|
||||||
msgstr "Les factures seleccionades han d'estar en estat obert"
|
msgstr "Les factures seleccionades han d'estar en estat obert"
|
||||||
|
|
||||||
#: actions.py:57
|
#: actions.py:60
|
||||||
msgid "Selected bills have been closed"
|
msgid "Selected bills have been closed"
|
||||||
msgstr "Les factures seleccionades han estat tancades"
|
msgstr "Les factures seleccionades han estat tancades"
|
||||||
|
|
||||||
#: actions.py:70
|
#: actions.py:73
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "<a href=\"%(url)s\">One related transaction</a> has been created"
|
msgid "<a href=\"%(url)s\">One related transaction</a> has been created"
|
||||||
msgstr "S'ha creat una <a href=\"%(url)s\">transacció</a>"
|
msgstr "S'ha creat una <a href=\"%(url)s\">transacció</a>"
|
||||||
|
|
||||||
#: actions.py:71
|
#: actions.py:74
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "<a href=\"%(url)s\">%(num)i related transactions</a> have been created"
|
msgid "<a href=\"%(url)s\">%(num)i related transactions</a> have been created"
|
||||||
msgstr "S'han creat les <a href=\"%(url)s\">%(num)i següents transaccions</a>"
|
msgstr "S'han creat les <a href=\"%(url)s\">%(num)i següents transaccions</a>"
|
||||||
|
|
||||||
#: actions.py:77
|
#: actions.py:80
|
||||||
msgid "Are you sure about closing the following bills?"
|
msgid "Are you sure about closing the following bills?"
|
||||||
msgstr "Estàs a punt de tancar les següents factures, estàs segur?"
|
msgstr "Estàs a punt de tancar les següents factures, estàs segur?"
|
||||||
|
|
||||||
#: actions.py:78
|
#: actions.py:81
|
||||||
msgid ""
|
msgid ""
|
||||||
"Once a bill is closed it can not be further modified.</p><p>Please select a "
|
"Once a bill is closed it can not be further modified.</p><p>Please select a "
|
||||||
"payment source for the selected bills"
|
"payment source for the selected bills"
|
||||||
|
@ -52,174 +52,205 @@ msgstr ""
|
||||||
"Una vegada la factura estigui tancada no podrà ser modificada.</p><p>Si us "
|
"Una vegada la factura estigui tancada no podrà ser modificada.</p><p>Si us "
|
||||||
"plau selecciona un mètode de pagament per les factures seleccionades"
|
"plau selecciona un mètode de pagament per les factures seleccionades"
|
||||||
|
|
||||||
#: actions.py:91
|
#: actions.py:97
|
||||||
msgid "Close"
|
msgid "Close"
|
||||||
msgstr "Tanca"
|
msgstr "Tanca"
|
||||||
|
|
||||||
#: actions.py:109
|
#: actions.py:115
|
||||||
msgid "One bill has been sent."
|
msgid "One bill has been sent."
|
||||||
msgstr "S'ha creat una factura"
|
msgstr "S'ha creat una factura"
|
||||||
|
|
||||||
#: actions.py:110
|
#: actions.py:116
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%i bills have been sent."
|
msgid "%i bills have been sent."
|
||||||
msgstr "S'han enviat %i factures."
|
msgstr "S'han enviat %i factures."
|
||||||
|
|
||||||
#: actions.py:117
|
#: actions.py:123
|
||||||
msgid "Resend"
|
msgid "Resend"
|
||||||
msgstr "Reenviat"
|
msgstr "Reenviat"
|
||||||
|
|
||||||
#: actions.py:137
|
#: actions.py:146
|
||||||
msgid "Download"
|
msgid "Download"
|
||||||
msgstr "Descarrega"
|
msgstr "Descarrega"
|
||||||
|
|
||||||
#: actions.py:153
|
#: actions.py:162
|
||||||
msgid "C.S.D."
|
msgid "C.S.D."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: actions.py:155
|
#: actions.py:164
|
||||||
msgid "Close, send and download bills in one shot."
|
msgid "Close, send and download bills in one shot."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: actions.py:216
|
#: actions.py:225
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(norders)s orders and %(nlines)s lines undoed."
|
msgid "%(norders)s orders and %(nlines)s lines undoed."
|
||||||
msgstr "%(norders)s ordres i %(nlines)s línies desfetes."
|
msgstr "%(norders)s ordres i %(nlines)s línies desfetes."
|
||||||
|
|
||||||
#: actions.py:235
|
#: actions.py:244
|
||||||
msgid "Lines moved"
|
msgid "Lines moved"
|
||||||
msgstr "Línies mogudes"
|
msgstr "Línies mogudes"
|
||||||
|
|
||||||
#: actions.py:248
|
#: actions.py:257
|
||||||
msgid "Selected bills should be in closed state"
|
msgid "Selected bills should be in closed state"
|
||||||
msgstr "Les factures seleccionades han d'estar en estat obert"
|
msgstr "Les factures seleccionades han d'estar en estat obert"
|
||||||
|
|
||||||
#: actions.py:265
|
#: actions.py:259
|
||||||
|
#, python-format
|
||||||
|
msgid "%s can not be amended."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: actions.py:279
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(type)s of %(related_type)s %(number)s and creation date %(date)s"
|
msgid "%(type)s of %(related_type)s %(number)s and creation date %(date)s"
|
||||||
msgstr "%(type)s de %(related_type)s %(number)s amb data de creació %(date)s"
|
msgstr "%(type)s de %(related_type)s %(number)s amb data de creació %(date)s"
|
||||||
|
|
||||||
#: actions.py:272
|
#: actions.py:286
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(related_type)s %(number)s subtotal for tax %(tax)s%%"
|
msgid "%(related_type)s %(number)s subtotal for tax %(tax)s%%"
|
||||||
msgstr "%(related_type)s %(number)s subtotal %(tax)s%%"
|
msgstr "%(related_type)s %(number)s subtotal %(tax)s%%"
|
||||||
|
|
||||||
#: actions.py:288
|
#: actions.py:303
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "<a href=\"%(url)s\">One amendment bill</a> have been generated."
|
msgid "<a href=\"%(url)s\">One amendment bill</a> have been generated."
|
||||||
msgstr "S'ha creat una <a href=\"%(url)s\">transacció</a>"
|
msgstr "S'ha creat una <a href=\"%(url)s\">transacció</a>"
|
||||||
|
|
||||||
#: actions.py:289
|
#: actions.py:304
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "<a href=\"%(url)s\">%(num)i amendment bills</a> have been generated."
|
msgid "<a href=\"%(url)s\">%(num)i amendment bills</a> have been generated."
|
||||||
msgstr "S'han creat les <a href=\"%(url)s\">%(num)i següents transaccions</a>"
|
msgstr "S'han creat les <a href=\"%(url)s\">%(num)i següents transaccions</a>"
|
||||||
|
|
||||||
#: actions.py:292
|
#: actions.py:307
|
||||||
msgid "Amend"
|
msgid "Amend"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:58 admin.py:103 admin.py:140 forms.py:11
|
#: admin.py:80 admin.py:126 admin.py:180 forms.py:11
|
||||||
#: templates/admin/bills/bill/report.html:43
|
#: templates/admin/bills/bill/report.html:43
|
||||||
#: templates/admin/bills/bill/report.html:70
|
#: templates/admin/bills/bill/report.html:70
|
||||||
msgid "Total"
|
msgid "Total"
|
||||||
msgstr "Total"
|
msgstr "Total"
|
||||||
|
|
||||||
#: admin.py:89
|
#: admin.py:112
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
msgstr "Descripció"
|
msgstr "Descripció"
|
||||||
|
|
||||||
#: admin.py:97
|
#: admin.py:120
|
||||||
msgid "Subtotal"
|
msgid "Subtotal"
|
||||||
msgstr "Subtotal"
|
msgstr "Subtotal"
|
||||||
|
|
||||||
#: admin.py:130
|
#: admin.py:146
|
||||||
|
#, fuzzy
|
||||||
|
#| msgid "Total"
|
||||||
|
msgid "Totals"
|
||||||
|
msgstr "Total"
|
||||||
|
|
||||||
|
#: admin.py:150
|
||||||
|
msgid "Order"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: admin.py:169
|
||||||
msgid "Is open"
|
msgid "Is open"
|
||||||
msgstr "És oberta"
|
msgstr "És oberta"
|
||||||
|
|
||||||
#: admin.py:135
|
#: admin.py:175
|
||||||
msgid "Subline"
|
#, fuzzy
|
||||||
|
#| msgid "Subline"
|
||||||
|
msgid "Sublines"
|
||||||
msgstr "Sublínia"
|
msgstr "Sublínia"
|
||||||
|
|
||||||
#: admin.py:167
|
#: admin.py:221
|
||||||
msgid "No bills selected."
|
msgid "No bills selected."
|
||||||
msgstr "No hi ha factures seleccionades"
|
msgstr "No hi ha factures seleccionades"
|
||||||
|
|
||||||
#: admin.py:174
|
#: admin.py:229
|
||||||
#, python-format
|
#, fuzzy, python-format
|
||||||
msgid "Manage %s bill lines."
|
#| msgid "Manage %s bill lines."
|
||||||
|
msgid "Manage %s bill lines"
|
||||||
msgstr "Gestiona %s línies de factura."
|
msgstr "Gestiona %s línies de factura."
|
||||||
|
|
||||||
#: admin.py:176
|
#: admin.py:231
|
||||||
msgid "Bill not in open state."
|
msgid "Bill not in open state."
|
||||||
msgstr "La factura no està en estat obert"
|
msgstr "La factura no està en estat obert"
|
||||||
|
|
||||||
#: admin.py:179
|
#: admin.py:234
|
||||||
msgid "Not all bills are in open state."
|
msgid "Not all bills are in open state."
|
||||||
msgstr "No totes les factures estan en estat obert"
|
msgstr "No totes les factures estan en estat obert"
|
||||||
|
|
||||||
#: admin.py:180
|
#: admin.py:235
|
||||||
msgid "Manage bill lines of multiple bills."
|
#, fuzzy
|
||||||
|
#| msgid "Manage bill lines of multiple bills."
|
||||||
|
msgid "Manage bill lines of multiple bills"
|
||||||
msgstr "Gestiona línies de factura de multiples factures."
|
msgstr "Gestiona línies de factura de multiples factures."
|
||||||
|
|
||||||
#: admin.py:204
|
#: admin.py:250
|
||||||
msgid "Dates"
|
#, python-format
|
||||||
|
msgid "Subtotal %s%% VAT %s &%s;"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:209
|
#: admin.py:251
|
||||||
msgid "Raw"
|
#, python-format
|
||||||
msgstr "Raw"
|
msgid "Taxes %s%% VAT %s &%s;"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:235 models.py:73
|
#: admin.py:255 admin.py:381 filters.py:46
|
||||||
msgid "Created"
|
#: templates/bills/microspective.html:123
|
||||||
msgstr "Creada"
|
msgid "total"
|
||||||
|
msgstr "total"
|
||||||
|
|
||||||
#: admin.py:236
|
#: admin.py:275
|
||||||
#, fuzzy
|
msgid "This bill has been amended, this value may not be valid."
|
||||||
#| msgid "Close"
|
msgstr ""
|
||||||
msgid "Closed"
|
|
||||||
msgstr "Tanca"
|
|
||||||
|
|
||||||
#: admin.py:237
|
#: admin.py:280
|
||||||
#, fuzzy
|
msgid "Payment"
|
||||||
#| msgid "updated on"
|
msgstr "Pagament"
|
||||||
msgid "Updated"
|
|
||||||
msgstr "actualitzada el"
|
|
||||||
|
|
||||||
#: admin.py:246
|
#: admin.py:304
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
#| msgid "amended line"
|
#| msgid "amended line"
|
||||||
msgid "Amends"
|
msgid "Amends"
|
||||||
msgstr "línia rectificada"
|
msgstr "línia rectificada"
|
||||||
|
|
||||||
#: admin.py:252
|
#: admin.py:330
|
||||||
|
msgid "Dates"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: admin.py:335
|
||||||
|
msgid "Raw"
|
||||||
|
msgstr "Raw"
|
||||||
|
|
||||||
|
#: admin.py:358 models.py:75
|
||||||
|
msgid "Created"
|
||||||
|
msgstr "Creada"
|
||||||
|
|
||||||
|
#: admin.py:359
|
||||||
|
#, fuzzy
|
||||||
|
#| msgid "Close"
|
||||||
|
msgid "Closed"
|
||||||
|
msgstr "Tanca"
|
||||||
|
|
||||||
|
#: admin.py:360
|
||||||
|
#, fuzzy
|
||||||
|
#| msgid "updated on"
|
||||||
|
msgid "Updated"
|
||||||
|
msgstr "actualitzada el"
|
||||||
|
|
||||||
|
#: admin.py:375
|
||||||
msgid "lines"
|
msgid "lines"
|
||||||
msgstr "línies"
|
msgstr "línies"
|
||||||
|
|
||||||
#: admin.py:257 filters.py:46 templates/bills/microspective.html:118
|
#: admin.py:389 models.py:108 models.py:501
|
||||||
msgid "total"
|
|
||||||
msgstr "total"
|
|
||||||
|
|
||||||
#: admin.py:265 models.py:104 models.py:460
|
|
||||||
msgid "type"
|
msgid "type"
|
||||||
msgstr "tipus"
|
msgstr "tipus"
|
||||||
|
|
||||||
#: admin.py:282
|
|
||||||
msgid "This bill has been amended, this value may not be valid."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:287
|
|
||||||
msgid "Payment"
|
|
||||||
msgstr "Pagament"
|
|
||||||
|
|
||||||
#: filters.py:21
|
#: filters.py:21
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr "Tot"
|
msgstr "Tot"
|
||||||
|
|
||||||
#: filters.py:22 models.py:88
|
#: filters.py:22 models.py:91
|
||||||
msgid "Invoice"
|
msgid "Invoice"
|
||||||
msgstr "Factura"
|
msgstr "Factura"
|
||||||
|
|
||||||
#: filters.py:23 models.py:90
|
#: filters.py:23 models.py:93
|
||||||
msgid "Fee"
|
msgid "Fee"
|
||||||
msgstr "Quota de soci"
|
msgstr "Quota de soci"
|
||||||
|
|
||||||
|
@ -231,65 +262,67 @@ msgstr "Pro-forma"
|
||||||
msgid "Amendment fee"
|
msgid "Amendment fee"
|
||||||
msgstr "Rectificació de quota de soci"
|
msgstr "Rectificació de quota de soci"
|
||||||
|
|
||||||
#: filters.py:26 models.py:89
|
#: filters.py:26 models.py:92
|
||||||
msgid "Amendment invoice"
|
msgid "Amendment invoice"
|
||||||
msgstr "Factura rectificativa"
|
msgstr "Factura rectificativa"
|
||||||
|
|
||||||
#: filters.py:68
|
#: filters.py:71
|
||||||
msgid "has bill contact"
|
msgid "has bill contact"
|
||||||
msgstr "té contacte de facturació"
|
msgstr "té contacte de facturació"
|
||||||
|
|
||||||
#: filters.py:73
|
#: filters.py:76
|
||||||
msgid "Yes"
|
msgid "Yes"
|
||||||
msgstr "Si"
|
msgstr "Si"
|
||||||
|
|
||||||
#: filters.py:74
|
#: filters.py:77
|
||||||
msgid "No"
|
msgid "No"
|
||||||
msgstr "No"
|
msgstr "No"
|
||||||
|
|
||||||
#: filters.py:85
|
#: filters.py:88
|
||||||
msgid "payment state"
|
msgid "payment state"
|
||||||
msgstr "Pagament"
|
msgstr "Pagament"
|
||||||
|
|
||||||
#: filters.py:90 models.py:72
|
#: filters.py:93 models.py:74
|
||||||
msgid "Open"
|
msgid "Open"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: filters.py:91 models.py:76
|
#: filters.py:94 models.py:78
|
||||||
msgid "Paid"
|
msgid "Paid"
|
||||||
msgstr "Pagat"
|
msgstr "Pagat"
|
||||||
|
|
||||||
#: filters.py:92
|
#: filters.py:95
|
||||||
msgid "Pending"
|
msgid "Pending"
|
||||||
msgstr "Pendent"
|
msgstr "Pendent"
|
||||||
|
|
||||||
#: filters.py:93 models.py:79
|
#: filters.py:96 models.py:81
|
||||||
msgid "Bad debt"
|
msgid "Bad debt"
|
||||||
msgstr "Incobrable"
|
msgstr "Incobrable"
|
||||||
|
|
||||||
#: filters.py:135
|
#: filters.py:138
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
#| msgid "amended line"
|
#| msgid "amended line"
|
||||||
msgid "amended"
|
msgid "amended"
|
||||||
msgstr "línia rectificada"
|
msgstr "línia rectificada"
|
||||||
|
|
||||||
#: filters.py:140
|
#: filters.py:143
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
#| msgid "Due date"
|
#| msgid "Due date"
|
||||||
msgid "Closed amends"
|
msgid "Closed amends"
|
||||||
msgstr "Data de pagament"
|
msgstr "Data de pagament"
|
||||||
|
|
||||||
#: filters.py:141
|
#: filters.py:144
|
||||||
msgid "Open or closed amends"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: filters.py:142
|
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
#| msgid "closed on"
|
#| msgid "Due date"
|
||||||
msgid "No closed amends"
|
msgid "Open amends"
|
||||||
msgstr "tancat el"
|
msgstr "Data de pagament"
|
||||||
|
|
||||||
#: filters.py:143
|
#: filters.py:145
|
||||||
|
#, fuzzy
|
||||||
|
#| msgid "amended line"
|
||||||
|
msgid "Any amends"
|
||||||
|
msgstr "línia rectificada"
|
||||||
|
|
||||||
|
#: filters.py:146
|
||||||
msgid "No amends"
|
msgid "No amends"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -309,7 +342,7 @@ msgstr "Tipus"
|
||||||
msgid "Source"
|
msgid "Source"
|
||||||
msgstr "Font"
|
msgstr "Font"
|
||||||
|
|
||||||
#: helpers.py:10
|
#: helpers.py:14
|
||||||
msgid ""
|
msgid ""
|
||||||
"{relation} account \"{account}\" does not have a declared invoice contact. "
|
"{relation} account \"{account}\" does not have a declared invoice contact. "
|
||||||
"You should <a href=\"{url}#invoicecontact-group\">provide one</a>"
|
"You should <a href=\"{url}#invoicecontact-group\">provide one</a>"
|
||||||
|
@ -317,213 +350,235 @@ msgstr ""
|
||||||
"{relation} compte \"{account}\" no te un contacte de facturació. Hauries de "
|
"{relation} compte \"{account}\" no te un contacte de facturació. Hauries de "
|
||||||
"<a href=\"{url}#invoicecontact-group\">proporcionar un</a>"
|
"<a href=\"{url}#invoicecontact-group\">proporcionar un</a>"
|
||||||
|
|
||||||
#: helpers.py:17
|
#: helpers.py:21
|
||||||
msgid "Related"
|
msgid "Related"
|
||||||
msgstr "Relacionat"
|
msgstr "Relacionat"
|
||||||
|
|
||||||
#: helpers.py:24
|
#: helpers.py:28
|
||||||
msgid "Main"
|
msgid "Main"
|
||||||
msgstr "Principal"
|
msgstr "Principal"
|
||||||
|
|
||||||
#: models.py:24 models.py:100
|
#: models.py:26 models.py:104
|
||||||
msgid "account"
|
msgid "account"
|
||||||
msgstr "compte"
|
msgstr "compte"
|
||||||
|
|
||||||
#: models.py:26
|
#: models.py:28
|
||||||
msgid "name"
|
msgid "name"
|
||||||
msgstr "nom"
|
msgstr "nom"
|
||||||
|
|
||||||
#: models.py:27
|
#: models.py:29
|
||||||
msgid "Account full name will be used when left blank."
|
msgid "Account full name will be used when left blank."
|
||||||
msgstr "S'emprarà el nom complet del compte quan es deixi en blanc."
|
msgstr "S'emprarà el nom complet del compte quan es deixi en blanc."
|
||||||
|
|
||||||
#: models.py:28
|
#: models.py:30
|
||||||
msgid "address"
|
msgid "address"
|
||||||
msgstr "adreça"
|
msgstr "adreça"
|
||||||
|
|
||||||
#: models.py:29
|
#: models.py:31
|
||||||
msgid "city"
|
msgid "city"
|
||||||
msgstr "ciutat"
|
msgstr "ciutat"
|
||||||
|
|
||||||
#: models.py:31
|
#: models.py:33
|
||||||
msgid "zip code"
|
msgid "zip code"
|
||||||
msgstr "codi postal"
|
msgstr "codi postal"
|
||||||
|
|
||||||
#: models.py:32
|
#: models.py:34
|
||||||
msgid "Enter a valid zipcode."
|
msgid "Enter a valid zipcode."
|
||||||
msgstr "Introdueix un codi postal vàlid."
|
msgstr "Introdueix un codi postal vàlid."
|
||||||
|
|
||||||
#: models.py:33
|
#: models.py:35
|
||||||
msgid "country"
|
msgid "country"
|
||||||
msgstr "país"
|
msgstr "país"
|
||||||
|
|
||||||
#: models.py:36 templates/admin/bills/bill/report.html:65
|
#: models.py:38 templates/admin/bills/bill/report.html:65
|
||||||
msgid "VAT number"
|
msgid "VAT number"
|
||||||
msgstr "NIF"
|
msgstr "NIF"
|
||||||
|
|
||||||
#: models.py:74
|
#: models.py:76
|
||||||
msgid "Processed"
|
msgid "Processed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:75
|
#: models.py:77
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
#| msgid "amended line"
|
#| msgid "amended line"
|
||||||
msgid "Amended"
|
msgid "Amended"
|
||||||
msgstr "línia rectificada"
|
msgstr "línia rectificada"
|
||||||
|
|
||||||
#: models.py:77
|
#: models.py:79
|
||||||
msgid "Incomplete"
|
msgid "Incomplete"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:78
|
#: models.py:80
|
||||||
msgid "Executed"
|
msgid "Executed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:91
|
#: models.py:94
|
||||||
msgid "Amendment Fee"
|
msgid "Amendment Fee"
|
||||||
msgstr "Rectificació de quota de soci"
|
msgstr "Rectificació de quota de soci"
|
||||||
|
|
||||||
#: models.py:92
|
#: models.py:95
|
||||||
|
#, fuzzy
|
||||||
|
#| msgid "Invoice"
|
||||||
|
msgid "Abono Invoice"
|
||||||
|
msgstr "Abonament"
|
||||||
|
|
||||||
|
#: models.py:96
|
||||||
msgid "Pro forma"
|
msgid "Pro forma"
|
||||||
msgstr "Pro forma"
|
msgstr "Pro forma"
|
||||||
|
|
||||||
#: models.py:99
|
#: models.py:103
|
||||||
msgid "number"
|
msgid "number"
|
||||||
msgstr "número"
|
msgstr "número"
|
||||||
|
|
||||||
#: models.py:102
|
#: models.py:106
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
#| msgid "amended line"
|
#| msgid "amended line"
|
||||||
msgid "amend of"
|
msgid "amend of"
|
||||||
msgstr "línia rectificada"
|
msgstr "línia rectificada"
|
||||||
|
|
||||||
#: models.py:105
|
#: models.py:109
|
||||||
msgid "created on"
|
msgid "created on"
|
||||||
msgstr "creat el"
|
msgstr "creat el"
|
||||||
|
|
||||||
#: models.py:106
|
#: models.py:110
|
||||||
msgid "closed on"
|
msgid "closed on"
|
||||||
msgstr "tancat el"
|
msgstr "tancat el"
|
||||||
|
|
||||||
#: models.py:107
|
#: models.py:111
|
||||||
msgid "open"
|
msgid "open"
|
||||||
msgstr "obert"
|
msgstr "obert"
|
||||||
|
|
||||||
#: models.py:108
|
#: models.py:112
|
||||||
msgid "sent"
|
msgid "sent"
|
||||||
msgstr "enviat"
|
msgstr "enviat"
|
||||||
|
|
||||||
#: models.py:109
|
#: models.py:113
|
||||||
msgid "due on"
|
msgid "due on"
|
||||||
msgstr "es deu"
|
msgstr "es deu"
|
||||||
|
|
||||||
#: models.py:110
|
#: models.py:114
|
||||||
msgid "updated on"
|
msgid "updated on"
|
||||||
msgstr "actualitzada el"
|
msgstr "actualitzada el"
|
||||||
|
|
||||||
#: models.py:112
|
#: models.py:116
|
||||||
msgid "comments"
|
msgid "comments"
|
||||||
msgstr "comentaris"
|
msgstr "comentaris"
|
||||||
|
|
||||||
#: models.py:113
|
#: models.py:117
|
||||||
msgid "HTML"
|
msgid "HTML"
|
||||||
msgstr "HTML"
|
msgstr "HTML"
|
||||||
|
|
||||||
#: models.py:194
|
#: models.py:200
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Type %s is not an amendment."
|
msgid "Type %s is not an amendment."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:196
|
#: models.py:202
|
||||||
msgid "Amend of related account doesn't match bill account."
|
msgid "Amend of related account doesn't match bill account."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:198
|
#: models.py:204
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
#| msgid "Bill not in open state."
|
#| msgid "Bill not in open state."
|
||||||
msgid "Related invoice is in open state."
|
msgid "Related invoice is in open state."
|
||||||
msgstr "La factura no està en estat obert"
|
msgstr "La factura no està en estat obert"
|
||||||
|
|
||||||
#: models.py:200
|
#: models.py:206
|
||||||
msgid "Related invoice is an amendment."
|
msgid "Related invoice is an amendment."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:392
|
#: models.py:419
|
||||||
msgid "bill"
|
msgid "bill"
|
||||||
msgstr "factura"
|
msgstr "factura"
|
||||||
|
|
||||||
#: models.py:393 models.py:458 templates/bills/microspective.html:73
|
#: models.py:420 models.py:499 templates/bills/microspective.html:75
|
||||||
msgid "description"
|
msgid "description"
|
||||||
msgstr "descripció"
|
msgstr "descripció"
|
||||||
|
|
||||||
#: models.py:394
|
#: models.py:421
|
||||||
msgid "rate"
|
msgid "rate"
|
||||||
msgstr "tarifa"
|
msgstr "tarifa"
|
||||||
|
|
||||||
#: models.py:395
|
#: models.py:422
|
||||||
msgid "quantity"
|
msgid "quantity"
|
||||||
msgstr "quantitat"
|
msgstr "quantitat"
|
||||||
|
|
||||||
#: models.py:397
|
#: models.py:424
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
#| msgid "quantity"
|
#| msgid "quantity"
|
||||||
msgid "Verbose quantity"
|
msgid "Verbose quantity"
|
||||||
msgstr "quantitat"
|
msgstr "quantitat"
|
||||||
|
|
||||||
#: models.py:398 templates/admin/bills/bill/report.html:47
|
#: models.py:425 templates/admin/bills/bill/report.html:47
|
||||||
#: templates/bills/microspective.html:77
|
#: templates/bills/microspective.html:79
|
||||||
#: templates/bills/microspective.html:111
|
#: templates/bills/microspective.html:116
|
||||||
msgid "subtotal"
|
msgid "subtotal"
|
||||||
msgstr "subtotal"
|
msgstr "subtotal"
|
||||||
|
|
||||||
#: models.py:399
|
#: models.py:426
|
||||||
msgid "tax"
|
msgid "tax"
|
||||||
msgstr "impostos"
|
msgstr "impostos"
|
||||||
|
|
||||||
#: models.py:400
|
#: models.py:427
|
||||||
msgid "start"
|
msgid "start"
|
||||||
msgstr "iniciar"
|
msgstr "iniciar"
|
||||||
|
|
||||||
#: models.py:401
|
#: models.py:428
|
||||||
msgid "end"
|
msgid "end"
|
||||||
msgstr "finalitzar"
|
msgstr "finalitzar"
|
||||||
|
|
||||||
#: models.py:403
|
#: models.py:431
|
||||||
msgid "Informative link back to the order"
|
msgid "Informative link back to the order"
|
||||||
msgstr "Enllaç informatiu de l'ordre"
|
msgstr "Enllaç informatiu de l'ordre"
|
||||||
|
|
||||||
#: models.py:404
|
#: models.py:432
|
||||||
msgid "order billed"
|
msgid "order billed"
|
||||||
msgstr "ordre facturada"
|
msgstr "ordre facturada"
|
||||||
|
|
||||||
#: models.py:405
|
#: models.py:433
|
||||||
msgid "order billed until"
|
msgid "order billed until"
|
||||||
msgstr "ordre facturada fins a"
|
msgstr "ordre facturada fins a"
|
||||||
|
|
||||||
#: models.py:406
|
#: models.py:434
|
||||||
msgid "created"
|
msgid "created"
|
||||||
msgstr "creada"
|
msgstr "creada"
|
||||||
|
|
||||||
#: models.py:408
|
#: models.py:436
|
||||||
msgid "amended line"
|
msgid "amended line"
|
||||||
msgstr "línia rectificada"
|
msgstr "línia rectificada"
|
||||||
|
|
||||||
#: models.py:451
|
#: models.py:492
|
||||||
msgid "Volume"
|
msgid "Volume"
|
||||||
msgstr "Volum"
|
msgstr "Volum"
|
||||||
|
|
||||||
#: models.py:452
|
#: models.py:493
|
||||||
msgid "Compensation"
|
msgid "Compensation"
|
||||||
msgstr "Compensació"
|
msgstr "Compensació"
|
||||||
|
|
||||||
#: models.py:453
|
#: models.py:494
|
||||||
msgid "Other"
|
msgid "Other"
|
||||||
msgstr "Altre"
|
msgstr "Altre"
|
||||||
|
|
||||||
#: models.py:457
|
#: models.py:498
|
||||||
msgid "bill line"
|
msgid "bill line"
|
||||||
msgstr "línia de factura"
|
msgstr "línia de factura"
|
||||||
|
|
||||||
|
#: templates/admin/bills/bill/change_list.html:9
|
||||||
|
#, fuzzy
|
||||||
|
#| msgid "lines"
|
||||||
|
msgid "Lines"
|
||||||
|
msgstr "línies"
|
||||||
|
|
||||||
|
#: templates/admin/bills/bill/change_list.html:15
|
||||||
|
#, fuzzy
|
||||||
|
#| msgid "bill"
|
||||||
|
msgid "Add bill"
|
||||||
|
msgstr "factura"
|
||||||
|
|
||||||
|
#: templates/admin/bills/bill/close_send_download_bills.html:57
|
||||||
|
msgid "Yes, I'm sure"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: templates/admin/bills/bill/report.html:42
|
#: templates/admin/bills/bill/report.html:42
|
||||||
msgid "Summary"
|
msgid "Summary"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -531,19 +586,19 @@ msgstr ""
|
||||||
#: templates/admin/bills/bill/report.html:47
|
#: templates/admin/bills/bill/report.html:47
|
||||||
#: templates/admin/bills/bill/report.html:51
|
#: templates/admin/bills/bill/report.html:51
|
||||||
#: templates/admin/bills/bill/report.html:69
|
#: templates/admin/bills/bill/report.html:69
|
||||||
#: templates/bills/microspective.html:111
|
#: templates/bills/microspective.html:116
|
||||||
#: templates/bills/microspective.html:114
|
#: templates/bills/microspective.html:119
|
||||||
msgid "VAT"
|
msgid "VAT"
|
||||||
msgstr "IVA"
|
msgstr "IVA"
|
||||||
|
|
||||||
#: templates/admin/bills/bill/report.html:51
|
#: templates/admin/bills/bill/report.html:51
|
||||||
#: templates/bills/microspective.html:114
|
#: templates/bills/microspective.html:119
|
||||||
msgid "taxes"
|
msgid "taxes"
|
||||||
msgstr "impostos"
|
msgstr "impostos"
|
||||||
|
|
||||||
#: templates/admin/bills/bill/report.html:56
|
#: templates/admin/bills/bill/report.html:56
|
||||||
#: templates/admin/bills/billline/report.html:60
|
#: templates/admin/bills/billline/report.html:60
|
||||||
#: templates/bills/microspective.html:53
|
#: templates/bills/microspective.html:54
|
||||||
msgid "TOTAL"
|
msgid "TOTAL"
|
||||||
msgstr "TOTAL"
|
msgstr "TOTAL"
|
||||||
|
|
||||||
|
@ -561,8 +616,20 @@ msgstr "Data de pagament"
|
||||||
msgid "Base"
|
msgid "Base"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/admin/bills/billline/change_list.html:6
|
||||||
|
msgid "Home"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/admin/bills/billline/change_list.html:8
|
||||||
|
msgid "Bills"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/admin/bills/billline/change_list.html:9
|
||||||
|
msgid "Multiple bills"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: templates/admin/bills/billline/report.html:42
|
#: templates/admin/bills/billline/report.html:42
|
||||||
msgid "Services"
|
msgid "Service"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/admin/bills/billline/report.html:43
|
#: templates/admin/bills/billline/report.html:43
|
||||||
|
@ -587,27 +654,21 @@ msgstr "quantitat"
|
||||||
msgid "Profit"
|
msgid "Profit"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/admin/bills/change_list.html:9
|
#: templates/bills/microspective-fee.html:115
|
||||||
#, fuzzy
|
|
||||||
#| msgid "bill"
|
|
||||||
msgid "Add bill"
|
|
||||||
msgstr "factura"
|
|
||||||
|
|
||||||
#: templates/bills/microspective-fee.html:107
|
|
||||||
msgid "Due date"
|
msgid "Due date"
|
||||||
msgstr "Data de pagament"
|
msgstr "Data de pagament"
|
||||||
|
|
||||||
#: templates/bills/microspective-fee.html:108
|
#: templates/bills/microspective-fee.html:116
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "On %(bank_account)s"
|
msgid "On %(bank_account)s"
|
||||||
msgstr "Al %(bank_account)s"
|
msgstr "Al %(bank_account)s"
|
||||||
|
|
||||||
#: templates/bills/microspective-fee.html:114
|
#: templates/bills/microspective-fee.html:122
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "From %(ini)s to %(end)s"
|
msgid "From %(ini)s to %(end)s"
|
||||||
msgstr "De %(ini)s a %(end)s"
|
msgstr "De %(ini)s a %(end)s"
|
||||||
|
|
||||||
#: templates/bills/microspective-fee.html:121
|
#: templates/bills/microspective-fee.html:144
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
"<strong>With your membership</strong> you are supporting ...\n"
|
"<strong>With your membership</strong> you are supporting ...\n"
|
||||||
|
@ -615,36 +676,36 @@ msgstr ""
|
||||||
"\n"
|
"\n"
|
||||||
"<strong>Amb la teva quota de soci</strong> estàs donant suport ...\n"
|
"<strong>Amb la teva quota de soci</strong> estàs donant suport ...\n"
|
||||||
|
|
||||||
#: templates/bills/microspective.html:49
|
#: templates/bills/microspective.html:50
|
||||||
msgid "DUE DATE"
|
msgid "DUE DATE"
|
||||||
msgstr "VENCIMENT"
|
msgstr "VENCIMENT"
|
||||||
|
|
||||||
#: templates/bills/microspective.html:57
|
#: templates/bills/microspective.html:58
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(bill_type)s DATE"
|
msgid "%(bill_type)s DATE"
|
||||||
msgstr "DATA %(bill_type)s"
|
msgstr "DATA %(bill_type)s"
|
||||||
|
|
||||||
#: templates/bills/microspective.html:74
|
#: templates/bills/microspective.html:76
|
||||||
msgid "period"
|
msgid "period"
|
||||||
msgstr "període"
|
msgstr "període"
|
||||||
|
|
||||||
#: templates/bills/microspective.html:75
|
#: templates/bills/microspective.html:77
|
||||||
msgid "hrs/qty"
|
msgid "hrs/qty"
|
||||||
msgstr "hrs/qnt"
|
msgstr "hrs/qnt"
|
||||||
|
|
||||||
#: templates/bills/microspective.html:76
|
#: templates/bills/microspective.html:78
|
||||||
msgid "rate/price"
|
msgid "rate/price"
|
||||||
msgstr "tarifa/preu"
|
msgstr "tarifa/preu"
|
||||||
|
|
||||||
#: templates/bills/microspective.html:131
|
#: templates/bills/microspective.html:137
|
||||||
msgid "COMMENTS"
|
msgid "COMMENTS"
|
||||||
msgstr "COMENTARIS"
|
msgstr "COMENTARIS"
|
||||||
|
|
||||||
#: templates/bills/microspective.html:138
|
#: templates/bills/microspective.html:145
|
||||||
msgid "PAYMENT"
|
msgid "PAYMENT"
|
||||||
msgstr "PAGAMENT"
|
msgstr "PAGAMENT"
|
||||||
|
|
||||||
#: templates/bills/microspective.html:142
|
#: templates/bills/microspective.html:149
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
|
@ -658,11 +719,11 @@ msgstr ""
|
||||||
"Pots pagar aquesta <i>%(type)s</i> per transferència bancaria.<br>Inclou el "
|
"Pots pagar aquesta <i>%(type)s</i> per transferència bancaria.<br>Inclou el "
|
||||||
"teu nom i el número de <i>%(type)s</i>. El nostre compte bancari és"
|
"teu nom i el número de <i>%(type)s</i>. El nostre compte bancari és"
|
||||||
|
|
||||||
#: templates/bills/microspective.html:151
|
#: templates/bills/microspective.html:160
|
||||||
msgid "QUESTIONS"
|
msgid "QUESTIONS"
|
||||||
msgstr "PREGUNTES"
|
msgstr "PREGUNTES"
|
||||||
|
|
||||||
#: templates/bills/microspective.html:152
|
#: templates/bills/microspective.html:161
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
|
@ -679,5 +740,10 @@ msgstr ""
|
||||||
"ràpidament possible.\n"
|
"ràpidament possible.\n"
|
||||||
" "
|
" "
|
||||||
|
|
||||||
|
#, fuzzy
|
||||||
|
#~| msgid "closed on"
|
||||||
|
#~ msgid "No closed amends"
|
||||||
|
#~ msgstr "tancat el"
|
||||||
|
|
||||||
#~ msgid "positive price"
|
#~ msgid "positive price"
|
||||||
#~ msgstr "preu positiu"
|
#~ msgstr "preu positiu"
|
||||||
|
|
Binary file not shown.
|
@ -8,7 +8,7 @@ msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2015-10-29 10:51+0000\n"
|
"POT-Creation-Date: 2019-12-20 11:56+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
@ -18,33 +18,33 @@ msgstr ""
|
||||||
"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"
|
||||||
|
|
||||||
#: actions.py:31
|
#: actions.py:33
|
||||||
msgid "View"
|
msgid "View"
|
||||||
msgstr "Vista"
|
msgstr "Vista"
|
||||||
|
|
||||||
#: actions.py:42
|
#: actions.py:45
|
||||||
msgid "Selected bills should be in open state"
|
msgid "Selected bills should be in open state"
|
||||||
msgstr "Las facturas seleccionadas están en estado abierto"
|
msgstr "Las facturas seleccionadas están en estado abierto"
|
||||||
|
|
||||||
#: actions.py:57
|
#: actions.py:60
|
||||||
msgid "Selected bills have been closed"
|
msgid "Selected bills have been closed"
|
||||||
msgstr "Las facturas seleccionadas han sido cerradas"
|
msgstr "Las facturas seleccionadas han sido cerradas"
|
||||||
|
|
||||||
#: actions.py:70
|
#: actions.py:73
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "<a href=\"%(url)s\">One related transaction</a> has been created"
|
msgid "<a href=\"%(url)s\">One related transaction</a> has been created"
|
||||||
msgstr "Se ha creado una <a href=\"%(url)s\">transacción</a>"
|
msgstr "Se ha creado una <a href=\"%(url)s\">transacción</a>"
|
||||||
|
|
||||||
#: actions.py:71
|
#: actions.py:74
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "<a href=\"%(url)s\">%(num)i related transactions</a> have been created"
|
msgid "<a href=\"%(url)s\">%(num)i related transactions</a> have been created"
|
||||||
msgstr "Se han creado <a href=\"%(url)s\">%(num)i transacciones</a>"
|
msgstr "Se han creado <a href=\"%(url)s\">%(num)i transacciones</a>"
|
||||||
|
|
||||||
#: actions.py:77
|
#: actions.py:80
|
||||||
msgid "Are you sure about closing the following bills?"
|
msgid "Are you sure about closing the following bills?"
|
||||||
msgstr "Estás a punto de cerrar las sigüientes facturas. ¿Estás seguro?"
|
msgstr "Estás a punto de cerrar las sigüientes facturas. ¿Estás seguro?"
|
||||||
|
|
||||||
#: actions.py:78
|
#: actions.py:81
|
||||||
msgid ""
|
msgid ""
|
||||||
"Once a bill is closed it can not be further modified.</p><p>Please select a "
|
"Once a bill is closed it can not be further modified.</p><p>Please select a "
|
||||||
"payment source for the selected bills"
|
"payment source for the selected bills"
|
||||||
|
@ -52,174 +52,199 @@ msgstr ""
|
||||||
"Una vez cerrada la factura ya no se podrá modificar.</p><p>Por favor "
|
"Una vez cerrada la factura ya no se podrá modificar.</p><p>Por favor "
|
||||||
"seleciona un metodo de pago para las facturas seleccionadas"
|
"seleciona un metodo de pago para las facturas seleccionadas"
|
||||||
|
|
||||||
#: actions.py:91
|
#: actions.py:97
|
||||||
msgid "Close"
|
msgid "Close"
|
||||||
msgstr "Cerrar"
|
msgstr "Cerrar"
|
||||||
|
|
||||||
#: actions.py:109
|
#: actions.py:115
|
||||||
msgid "One bill has been sent."
|
msgid "One bill has been sent."
|
||||||
msgstr "Se ha enviado una factura"
|
msgstr "Se ha enviado una factura"
|
||||||
|
|
||||||
#: actions.py:110
|
#: actions.py:116
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%i bills have been sent."
|
msgid "%i bills have been sent."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: actions.py:117
|
#: actions.py:123
|
||||||
msgid "Resend"
|
msgid "Resend"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: actions.py:137
|
#: actions.py:146
|
||||||
msgid "Download"
|
msgid "Download"
|
||||||
msgstr "Descarga"
|
msgstr "Descarga"
|
||||||
|
|
||||||
#: actions.py:153
|
#: actions.py:162
|
||||||
msgid "C.S.D."
|
msgid "C.S.D."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: actions.py:155
|
#: actions.py:164
|
||||||
msgid "Close, send and download bills in one shot."
|
msgid "Close, send and download bills in one shot."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: actions.py:216
|
#: actions.py:225
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(norders)s orders and %(nlines)s lines undoed."
|
msgid "%(norders)s orders and %(nlines)s lines undoed."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: actions.py:235
|
#: actions.py:244
|
||||||
msgid "Lines moved"
|
msgid "Lines moved"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: actions.py:248
|
#: actions.py:257
|
||||||
msgid "Selected bills should be in closed state"
|
msgid "Selected bills should be in closed state"
|
||||||
msgstr "Las facturas seleccionadas están en estado abierto"
|
msgstr "Las facturas seleccionadas están en estado abierto"
|
||||||
|
|
||||||
#: actions.py:265
|
#: actions.py:259
|
||||||
|
#, python-format
|
||||||
|
msgid "%s can not be amended."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: actions.py:279
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(type)s of %(related_type)s %(number)s and creation date %(date)s"
|
msgid "%(type)s of %(related_type)s %(number)s and creation date %(date)s"
|
||||||
msgstr "%(type)s de %(related_type)s %(number)s con fecha de creación %(date)s"
|
msgstr "%(type)s de %(related_type)s %(number)s con fecha de creación %(date)s"
|
||||||
|
|
||||||
#: actions.py:272
|
#: actions.py:286
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(related_type)s %(number)s subtotal for tax %(tax)s%%"
|
msgid "%(related_type)s %(number)s subtotal for tax %(tax)s%%"
|
||||||
msgstr "%(related_type)s %(number)s subtotal %(tax)s%%"
|
msgstr "%(related_type)s %(number)s subtotal %(tax)s%%"
|
||||||
|
|
||||||
#: actions.py:288
|
#: actions.py:303
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "<a href=\"%(url)s\">One amendment bill</a> have been generated."
|
msgid "<a href=\"%(url)s\">One amendment bill</a> have been generated."
|
||||||
msgstr "Se ha creado una <a href=\"%(url)s\">transacción</a>"
|
msgstr "Se ha creado una <a href=\"%(url)s\">transacción</a>"
|
||||||
|
|
||||||
#: actions.py:289
|
#: actions.py:304
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "<a href=\"%(url)s\">%(num)i amendment bills</a> have been generated."
|
msgid "<a href=\"%(url)s\">%(num)i amendment bills</a> have been generated."
|
||||||
msgstr "Se han creado <a href=\"%(url)s\">%(num)i transacciones</a>"
|
msgstr "Se han creado <a href=\"%(url)s\">%(num)i transacciones</a>"
|
||||||
|
|
||||||
#: actions.py:292
|
#: actions.py:307
|
||||||
msgid "Amend"
|
msgid "Amend"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:58 admin.py:103 admin.py:140 forms.py:11
|
#: admin.py:80 admin.py:126 admin.py:180 forms.py:11
|
||||||
#: templates/admin/bills/bill/report.html:43
|
#: templates/admin/bills/bill/report.html:43
|
||||||
#: templates/admin/bills/bill/report.html:70
|
#: templates/admin/bills/bill/report.html:70
|
||||||
msgid "Total"
|
msgid "Total"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:89
|
#: admin.py:112
|
||||||
msgid "Description"
|
msgid "Description"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:97
|
#: admin.py:120
|
||||||
msgid "Subtotal"
|
msgid "Subtotal"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:130
|
#: admin.py:146
|
||||||
|
msgid "Totals"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: admin.py:150
|
||||||
|
msgid "Order"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: admin.py:169
|
||||||
msgid "Is open"
|
msgid "Is open"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:135
|
#: admin.py:175
|
||||||
msgid "Subline"
|
msgid "Sublines"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:167
|
#: admin.py:221
|
||||||
msgid "No bills selected."
|
msgid "No bills selected."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:174
|
#: admin.py:229
|
||||||
#, python-format
|
#, fuzzy, python-format
|
||||||
msgid "Manage %s bill lines."
|
#| msgid "bill line"
|
||||||
msgstr ""
|
msgid "Manage %s bill lines"
|
||||||
|
msgstr "linea de factura"
|
||||||
|
|
||||||
#: admin.py:176
|
#: admin.py:231
|
||||||
msgid "Bill not in open state."
|
msgid "Bill not in open state."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:179
|
#: admin.py:234
|
||||||
msgid "Not all bills are in open state."
|
msgid "Not all bills are in open state."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:180
|
#: admin.py:235
|
||||||
msgid "Manage bill lines of multiple bills."
|
msgid "Manage bill lines of multiple bills"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:204
|
#: admin.py:250
|
||||||
msgid "Dates"
|
#, python-format
|
||||||
|
msgid "Subtotal %s%% VAT %s &%s;"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:209
|
#: admin.py:251
|
||||||
msgid "Raw"
|
#, python-format
|
||||||
|
msgid "Taxes %s%% VAT %s &%s;"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:235 models.py:73
|
#: admin.py:255 admin.py:381 filters.py:46
|
||||||
msgid "Created"
|
#: templates/bills/microspective.html:123
|
||||||
|
msgid "total"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:236
|
#: admin.py:275
|
||||||
#, fuzzy
|
msgid "This bill has been amended, this value may not be valid."
|
||||||
#| msgid "Close"
|
msgstr ""
|
||||||
msgid "Closed"
|
|
||||||
msgstr "Cerrar"
|
|
||||||
|
|
||||||
#: admin.py:237
|
#: admin.py:280
|
||||||
#, fuzzy
|
msgid "Payment"
|
||||||
#| msgid "updated on"
|
msgstr "Pago"
|
||||||
msgid "Updated"
|
|
||||||
msgstr "actualizada en"
|
|
||||||
|
|
||||||
#: admin.py:246
|
#: admin.py:304
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
#| msgid "Amended"
|
#| msgid "Amended"
|
||||||
msgid "Amends"
|
msgid "Amends"
|
||||||
msgstr "Quota rectificativa"
|
msgstr "Quota rectificativa"
|
||||||
|
|
||||||
#: admin.py:252
|
#: admin.py:330
|
||||||
|
msgid "Dates"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: admin.py:335
|
||||||
|
msgid "Raw"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: admin.py:358 models.py:75
|
||||||
|
msgid "Created"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: admin.py:359
|
||||||
|
#, fuzzy
|
||||||
|
#| msgid "Close"
|
||||||
|
msgid "Closed"
|
||||||
|
msgstr "Cerrar"
|
||||||
|
|
||||||
|
#: admin.py:360
|
||||||
|
#, fuzzy
|
||||||
|
#| msgid "updated on"
|
||||||
|
msgid "Updated"
|
||||||
|
msgstr "actualizada en"
|
||||||
|
|
||||||
|
#: admin.py:375
|
||||||
msgid "lines"
|
msgid "lines"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:257 filters.py:46 templates/bills/microspective.html:118
|
#: admin.py:389 models.py:108 models.py:501
|
||||||
msgid "total"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:265 models.py:104 models.py:460
|
|
||||||
msgid "type"
|
msgid "type"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: admin.py:282
|
|
||||||
msgid "This bill has been amended, this value may not be valid."
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: admin.py:287
|
|
||||||
msgid "Payment"
|
|
||||||
msgstr "Pago"
|
|
||||||
|
|
||||||
#: filters.py:21
|
#: filters.py:21
|
||||||
msgid "All"
|
msgid "All"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: filters.py:22 models.py:88
|
#: filters.py:22 models.py:91
|
||||||
msgid "Invoice"
|
msgid "Invoice"
|
||||||
msgstr "Factura"
|
msgstr "Factura"
|
||||||
|
|
||||||
#: filters.py:23 models.py:90
|
#: filters.py:23 models.py:93
|
||||||
msgid "Fee"
|
msgid "Fee"
|
||||||
msgstr "Cuota de socio"
|
msgstr "Cuota de socio"
|
||||||
|
|
||||||
|
@ -231,65 +256,67 @@ msgstr ""
|
||||||
msgid "Amendment fee"
|
msgid "Amendment fee"
|
||||||
msgstr "Cuota rectificativa"
|
msgstr "Cuota rectificativa"
|
||||||
|
|
||||||
#: filters.py:26 models.py:89
|
#: filters.py:26 models.py:92
|
||||||
msgid "Amendment invoice"
|
msgid "Amendment invoice"
|
||||||
msgstr "Factura rectificativa"
|
msgstr "Factura rectificativa"
|
||||||
|
|
||||||
#: filters.py:68
|
#: filters.py:71
|
||||||
msgid "has bill contact"
|
msgid "has bill contact"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: filters.py:73
|
#: filters.py:76
|
||||||
msgid "Yes"
|
msgid "Yes"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: filters.py:74
|
#: filters.py:77
|
||||||
msgid "No"
|
msgid "No"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: filters.py:85
|
#: filters.py:88
|
||||||
msgid "payment state"
|
msgid "payment state"
|
||||||
msgstr "Pago"
|
msgstr "Pago"
|
||||||
|
|
||||||
#: filters.py:90 models.py:72
|
#: filters.py:93 models.py:74
|
||||||
msgid "Open"
|
msgid "Open"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: filters.py:91 models.py:76
|
#: filters.py:94 models.py:78
|
||||||
msgid "Paid"
|
msgid "Paid"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: filters.py:92
|
#: filters.py:95
|
||||||
msgid "Pending"
|
msgid "Pending"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: filters.py:93 models.py:79
|
#: filters.py:96 models.py:81
|
||||||
msgid "Bad debt"
|
msgid "Bad debt"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: filters.py:135
|
#: filters.py:138
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
#| msgid "Amended"
|
#| msgid "Amended"
|
||||||
msgid "amended"
|
msgid "amended"
|
||||||
msgstr "Quota rectificativa"
|
msgstr "Quota rectificativa"
|
||||||
|
|
||||||
#: filters.py:140
|
#: filters.py:143
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
#| msgid "Due date"
|
#| msgid "Due date"
|
||||||
msgid "Closed amends"
|
msgid "Closed amends"
|
||||||
msgstr "Fecha de pago"
|
msgstr "Fecha de pago"
|
||||||
|
|
||||||
#: filters.py:141
|
#: filters.py:144
|
||||||
msgid "Open or closed amends"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: filters.py:142
|
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
#| msgid "closed on"
|
#| msgid "Due date"
|
||||||
msgid "No closed amends"
|
msgid "Open amends"
|
||||||
msgstr "cerrada en"
|
msgstr "Fecha de pago"
|
||||||
|
|
||||||
#: filters.py:143
|
#: filters.py:145
|
||||||
|
#, fuzzy
|
||||||
|
#| msgid "Amended"
|
||||||
|
msgid "Any amends"
|
||||||
|
msgstr "Quota rectificativa"
|
||||||
|
|
||||||
|
#: filters.py:146
|
||||||
msgid "No amends"
|
msgid "No amends"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -309,213 +336,233 @@ msgstr ""
|
||||||
msgid "Source"
|
msgid "Source"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: helpers.py:10
|
#: helpers.py:14
|
||||||
msgid ""
|
msgid ""
|
||||||
"{relation} account \"{account}\" does not have a declared invoice contact. "
|
"{relation} account \"{account}\" does not have a declared invoice contact. "
|
||||||
"You should <a href=\"{url}#invoicecontact-group\">provide one</a>"
|
"You should <a href=\"{url}#invoicecontact-group\">provide one</a>"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: helpers.py:17
|
#: helpers.py:21
|
||||||
msgid "Related"
|
msgid "Related"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: helpers.py:24
|
#: helpers.py:28
|
||||||
msgid "Main"
|
msgid "Main"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:24 models.py:100
|
#: models.py:26 models.py:104
|
||||||
msgid "account"
|
msgid "account"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:26
|
#: models.py:28
|
||||||
msgid "name"
|
msgid "name"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:27
|
#: models.py:29
|
||||||
msgid "Account full name will be used when left blank."
|
msgid "Account full name will be used when left blank."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:28
|
#: models.py:30
|
||||||
msgid "address"
|
msgid "address"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:29
|
#: models.py:31
|
||||||
msgid "city"
|
msgid "city"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:31
|
#: models.py:33
|
||||||
msgid "zip code"
|
msgid "zip code"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:32
|
#: models.py:34
|
||||||
msgid "Enter a valid zipcode."
|
msgid "Enter a valid zipcode."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:33
|
#: models.py:35
|
||||||
msgid "country"
|
msgid "country"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:36 templates/admin/bills/bill/report.html:65
|
#: models.py:38 templates/admin/bills/bill/report.html:65
|
||||||
msgid "VAT number"
|
msgid "VAT number"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:74
|
#: models.py:76
|
||||||
msgid "Processed"
|
msgid "Processed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:75
|
#: models.py:77
|
||||||
msgid "Amended"
|
msgid "Amended"
|
||||||
msgstr "Quota rectificativa"
|
msgstr "Quota rectificativa"
|
||||||
|
|
||||||
#: models.py:77
|
#: models.py:79
|
||||||
msgid "Incomplete"
|
msgid "Incomplete"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:78
|
#: models.py:80
|
||||||
msgid "Executed"
|
msgid "Executed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:91
|
#: models.py:94
|
||||||
msgid "Amendment Fee"
|
msgid "Amendment Fee"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:92
|
#: models.py:95
|
||||||
|
#, fuzzy
|
||||||
|
#| msgid "Invoice"
|
||||||
|
msgid "Abono Invoice"
|
||||||
|
msgstr "Abono"
|
||||||
|
|
||||||
|
#: models.py:96
|
||||||
msgid "Pro forma"
|
msgid "Pro forma"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:99
|
#: models.py:103
|
||||||
msgid "number"
|
msgid "number"
|
||||||
msgstr "número"
|
msgstr "número"
|
||||||
|
|
||||||
#: models.py:102
|
#: models.py:106
|
||||||
msgid "amend of"
|
msgid "amend of"
|
||||||
msgstr "rectificación de"
|
msgstr "rectificación de"
|
||||||
|
|
||||||
#: models.py:105
|
#: models.py:109
|
||||||
msgid "created on"
|
msgid "created on"
|
||||||
msgstr "creado en"
|
msgstr "creado en"
|
||||||
|
|
||||||
#: models.py:106
|
#: models.py:110
|
||||||
msgid "closed on"
|
msgid "closed on"
|
||||||
msgstr "cerrada en"
|
msgstr "cerrada en"
|
||||||
|
|
||||||
#: models.py:107
|
#: models.py:111
|
||||||
msgid "open"
|
msgid "open"
|
||||||
msgstr "abierta"
|
msgstr "abierta"
|
||||||
|
|
||||||
#: models.py:108
|
#: models.py:112
|
||||||
msgid "sent"
|
msgid "sent"
|
||||||
msgstr "enviada"
|
msgstr "enviada"
|
||||||
|
|
||||||
#: models.py:109
|
#: models.py:113
|
||||||
msgid "due on"
|
msgid "due on"
|
||||||
msgstr "vencimiento"
|
msgstr "vencimiento"
|
||||||
|
|
||||||
#: models.py:110
|
#: models.py:114
|
||||||
msgid "updated on"
|
msgid "updated on"
|
||||||
msgstr "actualizada en"
|
msgstr "actualizada en"
|
||||||
|
|
||||||
#: models.py:112
|
#: models.py:116
|
||||||
msgid "comments"
|
msgid "comments"
|
||||||
msgstr "comentarios"
|
msgstr "comentarios"
|
||||||
|
|
||||||
#: models.py:113
|
#: models.py:117
|
||||||
msgid "HTML"
|
msgid "HTML"
|
||||||
msgstr "HTML"
|
msgstr "HTML"
|
||||||
|
|
||||||
#: models.py:194
|
#: models.py:200
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Type %s is not an amendment."
|
msgid "Type %s is not an amendment."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:196
|
#: models.py:202
|
||||||
msgid "Amend of related account doesn't match bill account."
|
msgid "Amend of related account doesn't match bill account."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:198
|
#: models.py:204
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
#| msgid "Selected bills should be in open state"
|
#| msgid "Selected bills should be in open state"
|
||||||
msgid "Related invoice is in open state."
|
msgid "Related invoice is in open state."
|
||||||
msgstr "Las facturas seleccionadas están en estado abierto"
|
msgstr "Las facturas seleccionadas están en estado abierto"
|
||||||
|
|
||||||
#: models.py:200
|
#: models.py:206
|
||||||
msgid "Related invoice is an amendment."
|
msgid "Related invoice is an amendment."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:392
|
#: models.py:419
|
||||||
msgid "bill"
|
msgid "bill"
|
||||||
msgstr "factura"
|
msgstr "factura"
|
||||||
|
|
||||||
#: models.py:393 models.py:458 templates/bills/microspective.html:73
|
#: models.py:420 models.py:499 templates/bills/microspective.html:75
|
||||||
msgid "description"
|
msgid "description"
|
||||||
msgstr "descripción"
|
msgstr "descripción"
|
||||||
|
|
||||||
#: models.py:394
|
#: models.py:421
|
||||||
msgid "rate"
|
msgid "rate"
|
||||||
msgstr "tarifa"
|
msgstr "tarifa"
|
||||||
|
|
||||||
#: models.py:395
|
#: models.py:422
|
||||||
msgid "quantity"
|
msgid "quantity"
|
||||||
msgstr "cantidad"
|
msgstr "cantidad"
|
||||||
|
|
||||||
#: models.py:397
|
#: models.py:424
|
||||||
msgid "Verbose quantity"
|
msgid "Verbose quantity"
|
||||||
msgstr "Cantidad"
|
msgstr "Cantidad"
|
||||||
|
|
||||||
#: models.py:398 templates/admin/bills/bill/report.html:47
|
#: models.py:425 templates/admin/bills/bill/report.html:47
|
||||||
#: templates/bills/microspective.html:77
|
#: templates/bills/microspective.html:79
|
||||||
#: templates/bills/microspective.html:111
|
#: templates/bills/microspective.html:116
|
||||||
msgid "subtotal"
|
msgid "subtotal"
|
||||||
msgstr "subtotal"
|
msgstr "subtotal"
|
||||||
|
|
||||||
#: models.py:399
|
#: models.py:426
|
||||||
msgid "tax"
|
msgid "tax"
|
||||||
msgstr "impuesto"
|
msgstr "impuesto"
|
||||||
|
|
||||||
#: models.py:400
|
#: models.py:427
|
||||||
msgid "start"
|
msgid "start"
|
||||||
msgstr "inicio"
|
msgstr "inicio"
|
||||||
|
|
||||||
#: models.py:401
|
#: models.py:428
|
||||||
msgid "end"
|
msgid "end"
|
||||||
msgstr "fín"
|
msgstr "fín"
|
||||||
|
|
||||||
#: models.py:403
|
#: models.py:431
|
||||||
msgid "Informative link back to the order"
|
msgid "Informative link back to the order"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:404
|
#: models.py:432
|
||||||
msgid "order billed"
|
msgid "order billed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:405
|
#: models.py:433
|
||||||
msgid "order billed until"
|
msgid "order billed until"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: models.py:406
|
#: models.py:434
|
||||||
msgid "created"
|
msgid "created"
|
||||||
msgstr "creado"
|
msgstr "creado"
|
||||||
|
|
||||||
#: models.py:408
|
#: models.py:436
|
||||||
msgid "amended line"
|
msgid "amended line"
|
||||||
msgstr "linea rectificativa"
|
msgstr "linea rectificativa"
|
||||||
|
|
||||||
#: models.py:451
|
#: models.py:492
|
||||||
msgid "Volume"
|
msgid "Volume"
|
||||||
msgstr "Volumen"
|
msgstr "Volumen"
|
||||||
|
|
||||||
#: models.py:452
|
#: models.py:493
|
||||||
msgid "Compensation"
|
msgid "Compensation"
|
||||||
msgstr "Compensación"
|
msgstr "Compensación"
|
||||||
|
|
||||||
#: models.py:453
|
#: models.py:494
|
||||||
msgid "Other"
|
msgid "Other"
|
||||||
msgstr "Otro"
|
msgstr "Otro"
|
||||||
|
|
||||||
#: models.py:457
|
#: models.py:498
|
||||||
msgid "bill line"
|
msgid "bill line"
|
||||||
msgstr "linea de factura"
|
msgstr "linea de factura"
|
||||||
|
|
||||||
|
#: templates/admin/bills/bill/change_list.html:9
|
||||||
|
msgid "Lines"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/admin/bills/bill/change_list.html:15
|
||||||
|
#, fuzzy
|
||||||
|
#| msgid "bill"
|
||||||
|
msgid "Add bill"
|
||||||
|
msgstr "factura"
|
||||||
|
|
||||||
|
#: templates/admin/bills/bill/close_send_download_bills.html:57
|
||||||
|
msgid "Yes, I'm sure"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: templates/admin/bills/bill/report.html:42
|
#: templates/admin/bills/bill/report.html:42
|
||||||
msgid "Summary"
|
msgid "Summary"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
@ -523,19 +570,19 @@ msgstr ""
|
||||||
#: templates/admin/bills/bill/report.html:47
|
#: templates/admin/bills/bill/report.html:47
|
||||||
#: templates/admin/bills/bill/report.html:51
|
#: templates/admin/bills/bill/report.html:51
|
||||||
#: templates/admin/bills/bill/report.html:69
|
#: templates/admin/bills/bill/report.html:69
|
||||||
#: templates/bills/microspective.html:111
|
#: templates/bills/microspective.html:116
|
||||||
#: templates/bills/microspective.html:114
|
#: templates/bills/microspective.html:119
|
||||||
msgid "VAT"
|
msgid "VAT"
|
||||||
msgstr "IVA"
|
msgstr "IVA"
|
||||||
|
|
||||||
#: templates/admin/bills/bill/report.html:51
|
#: templates/admin/bills/bill/report.html:51
|
||||||
#: templates/bills/microspective.html:114
|
#: templates/bills/microspective.html:119
|
||||||
msgid "taxes"
|
msgid "taxes"
|
||||||
msgstr "impuestos"
|
msgstr "impuestos"
|
||||||
|
|
||||||
#: templates/admin/bills/bill/report.html:56
|
#: templates/admin/bills/bill/report.html:56
|
||||||
#: templates/admin/bills/billline/report.html:60
|
#: templates/admin/bills/billline/report.html:60
|
||||||
#: templates/bills/microspective.html:53
|
#: templates/bills/microspective.html:54
|
||||||
msgid "TOTAL"
|
msgid "TOTAL"
|
||||||
msgstr "TOTAL"
|
msgstr "TOTAL"
|
||||||
|
|
||||||
|
@ -553,8 +600,20 @@ msgstr "Fecha de pago"
|
||||||
msgid "Base"
|
msgid "Base"
|
||||||
msgstr "Base"
|
msgstr "Base"
|
||||||
|
|
||||||
|
#: templates/admin/bills/billline/change_list.html:6
|
||||||
|
msgid "Home"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/admin/bills/billline/change_list.html:8
|
||||||
|
msgid "Bills"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/admin/bills/billline/change_list.html:9
|
||||||
|
msgid "Multiple bills"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: templates/admin/bills/billline/report.html:42
|
#: templates/admin/bills/billline/report.html:42
|
||||||
msgid "Services"
|
msgid "Service"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/admin/bills/billline/report.html:43
|
#: templates/admin/bills/billline/report.html:43
|
||||||
|
@ -579,62 +638,56 @@ msgstr "cantidad"
|
||||||
msgid "Profit"
|
msgid "Profit"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/admin/bills/change_list.html:9
|
#: templates/bills/microspective-fee.html:115
|
||||||
#, fuzzy
|
|
||||||
#| msgid "bill"
|
|
||||||
msgid "Add bill"
|
|
||||||
msgstr "factura"
|
|
||||||
|
|
||||||
#: templates/bills/microspective-fee.html:107
|
|
||||||
msgid "Due date"
|
msgid "Due date"
|
||||||
msgstr "Fecha de pago"
|
msgstr "Fecha de pago"
|
||||||
|
|
||||||
#: templates/bills/microspective-fee.html:108
|
#: templates/bills/microspective-fee.html:116
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "On %(bank_account)s"
|
msgid "On %(bank_account)s"
|
||||||
msgstr "En %(bank_account)s"
|
msgstr "En %(bank_account)s"
|
||||||
|
|
||||||
#: templates/bills/microspective-fee.html:114
|
#: templates/bills/microspective-fee.html:122
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "From %(ini)s to %(end)s"
|
msgid "From %(ini)s to %(end)s"
|
||||||
msgstr "Desde %(ini)s hasta %(end)s"
|
msgstr "Desde %(ini)s hasta %(end)s"
|
||||||
|
|
||||||
#: templates/bills/microspective-fee.html:121
|
#: templates/bills/microspective-fee.html:144
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
"<strong>With your membership</strong> you are supporting ...\n"
|
"<strong>With your membership</strong> you are supporting ...\n"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: templates/bills/microspective.html:49
|
#: templates/bills/microspective.html:50
|
||||||
msgid "DUE DATE"
|
msgid "DUE DATE"
|
||||||
msgstr "VENCIMIENTO"
|
msgstr "VENCIMIENTO"
|
||||||
|
|
||||||
#: templates/bills/microspective.html:57
|
#: templates/bills/microspective.html:58
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "%(bill_type)s DATE"
|
msgid "%(bill_type)s DATE"
|
||||||
msgstr "FECHA %(bill_type)s"
|
msgstr "FECHA %(bill_type)s"
|
||||||
|
|
||||||
#: templates/bills/microspective.html:74
|
#: templates/bills/microspective.html:76
|
||||||
msgid "period"
|
msgid "period"
|
||||||
msgstr "periodo"
|
msgstr "periodo"
|
||||||
|
|
||||||
#: templates/bills/microspective.html:75
|
#: templates/bills/microspective.html:77
|
||||||
msgid "hrs/qty"
|
msgid "hrs/qty"
|
||||||
msgstr "hrs/cant"
|
msgstr "hrs/cant"
|
||||||
|
|
||||||
#: templates/bills/microspective.html:76
|
#: templates/bills/microspective.html:78
|
||||||
msgid "rate/price"
|
msgid "rate/price"
|
||||||
msgstr "tarifa/precio"
|
msgstr "tarifa/precio"
|
||||||
|
|
||||||
#: templates/bills/microspective.html:131
|
#: templates/bills/microspective.html:137
|
||||||
msgid "COMMENTS"
|
msgid "COMMENTS"
|
||||||
msgstr "COMENTARIOS"
|
msgstr "COMENTARIOS"
|
||||||
|
|
||||||
#: templates/bills/microspective.html:138
|
#: templates/bills/microspective.html:145
|
||||||
msgid "PAYMENT"
|
msgid "PAYMENT"
|
||||||
msgstr "PAGO"
|
msgstr "PAGO"
|
||||||
|
|
||||||
#: templates/bills/microspective.html:142
|
#: templates/bills/microspective.html:149
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
|
@ -648,11 +701,11 @@ msgstr ""
|
||||||
"Puedes pagar esta <i>%(type)s</i> por transferencia bancaria.<br>Incluye tu "
|
"Puedes pagar esta <i>%(type)s</i> por transferencia bancaria.<br>Incluye tu "
|
||||||
"nombre y el número de <i>%(type)s</i>. Nuestra cuenta bancaria es"
|
"nombre y el número de <i>%(type)s</i>. Nuestra cuenta bancaria es"
|
||||||
|
|
||||||
#: templates/bills/microspective.html:151
|
#: templates/bills/microspective.html:160
|
||||||
msgid "QUESTIONS"
|
msgid "QUESTIONS"
|
||||||
msgstr "PREGUNTAS"
|
msgstr "PREGUNTAS"
|
||||||
|
|
||||||
#: templates/bills/microspective.html:152
|
#: templates/bills/microspective.html:161
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
|
@ -668,3 +721,8 @@ msgstr ""
|
||||||
" contacta con nosotros en %(email)s. Te responderemos lo más "
|
" contacta con nosotros en %(email)s. Te responderemos lo más "
|
||||||
"rapidamente posible.\n"
|
"rapidamente posible.\n"
|
||||||
" "
|
" "
|
||||||
|
|
||||||
|
#, fuzzy
|
||||||
|
#~| msgid "closed on"
|
||||||
|
#~ msgid "No closed amends"
|
||||||
|
#~ msgstr "cerrada en"
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -86,11 +86,13 @@ class Bill(models.Model):
|
||||||
FEE = 'FEE'
|
FEE = 'FEE'
|
||||||
AMENDMENTFEE = 'AMENDMENTFEE'
|
AMENDMENTFEE = 'AMENDMENTFEE'
|
||||||
PROFORMA = 'PROFORMA'
|
PROFORMA = 'PROFORMA'
|
||||||
|
ABONOINVOICE = 'ABONOINVOICE'
|
||||||
TYPES = (
|
TYPES = (
|
||||||
(INVOICE, _("Invoice")),
|
(INVOICE, _("Invoice")),
|
||||||
(AMENDMENTINVOICE, _("Amendment invoice")),
|
(AMENDMENTINVOICE, _("Amendment invoice")),
|
||||||
(FEE, _("Fee")),
|
(FEE, _("Fee")),
|
||||||
(AMENDMENTFEE, _("Amendment Fee")),
|
(AMENDMENTFEE, _("Amendment Fee")),
|
||||||
|
(ABONOINVOICE, _("Abono Invoice")),
|
||||||
(PROFORMA, _("Pro forma")),
|
(PROFORMA, _("Pro forma")),
|
||||||
)
|
)
|
||||||
AMEND_MAP = {
|
AMEND_MAP = {
|
||||||
|
@ -392,6 +394,11 @@ class AmendmentInvoice(Bill):
|
||||||
proxy = True
|
proxy = True
|
||||||
|
|
||||||
|
|
||||||
|
class AbonoInvoice(Bill):
|
||||||
|
class Meta:
|
||||||
|
proxy = True
|
||||||
|
|
||||||
|
|
||||||
class Fee(Bill):
|
class Fee(Bill):
|
||||||
class Meta:
|
class Meta:
|
||||||
proxy = True
|
proxy = True
|
||||||
|
|
|
@ -18,6 +18,9 @@ BILLS_AMENDMENT_INVOICE_NUMBER_PREFIX = Setting('BILLS_AMENDMENT_INVOICE_NUMBER_
|
||||||
'A'
|
'A'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
BILLS_ABONOINVOICE_NUMBER_PREFIX = Setting('BILLS_ABONOINVOICE_NUMBER_PREFIX',
|
||||||
|
'AB'
|
||||||
|
)
|
||||||
|
|
||||||
BILLS_FEE_NUMBER_PREFIX = Setting('BILLS_FEE_NUMBER_PREFIX',
|
BILLS_FEE_NUMBER_PREFIX = Setting('BILLS_FEE_NUMBER_PREFIX',
|
||||||
'F'
|
'F'
|
||||||
|
|
|
@ -282,3 +282,17 @@ a:hover {
|
||||||
#questions {
|
#questions {
|
||||||
margin-bottom: 0px;
|
margin-bottom: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#watermark {
|
||||||
|
color: #d0d0d0;
|
||||||
|
font-size: 100pt;
|
||||||
|
-webkit-transform: rotate(-45deg);
|
||||||
|
-moz-transform: rotate(-45deg);
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
z-index: -1;
|
||||||
|
max-width: 593px;
|
||||||
|
}
|
|
@ -12,6 +12,12 @@
|
||||||
{% block body %}
|
{% block body %}
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
{% if bill.is_open %}
|
||||||
|
<!-- TODO DANIEL: falta arreglar el css d'aquesta cosa -->
|
||||||
|
<div id="watermark">
|
||||||
|
<p>ESBORRANY - DRAFT - BORRADOR</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% block header %}
|
{% block header %}
|
||||||
<div id="logo">
|
<div id="logo">
|
||||||
{% block logo %}
|
{% block logo %}
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -12,6 +12,10 @@ from .filters import HasUserListFilter, HasDatabaseListFilter
|
||||||
from .forms import DatabaseCreationForm, DatabaseUserChangeForm, DatabaseUserCreationForm
|
from .forms import DatabaseCreationForm, DatabaseUserChangeForm, DatabaseUserCreationForm
|
||||||
from .models import Database, DatabaseUser
|
from .models import Database, DatabaseUser
|
||||||
|
|
||||||
|
def save_selected(modeladmin, request, queryset):
|
||||||
|
for selected in queryset:
|
||||||
|
selected.save()
|
||||||
|
save_selected.short_description = "Re-save selected objects"
|
||||||
|
|
||||||
class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
||||||
list_display = ('name', 'type', 'display_users', 'account_link')
|
list_display = ('name', 'type', 'display_users', 'account_link')
|
||||||
|
@ -22,7 +26,7 @@ class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, {
|
(None, {
|
||||||
'classes': ('extrapretty',),
|
'classes': ('extrapretty',),
|
||||||
'fields': ('account_link', 'name', 'type', 'users', 'display_users'),
|
'fields': ('account_link', 'name', 'type', 'users', 'display_users', 'comments'),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
add_fieldsets = (
|
add_fieldsets = (
|
||||||
|
@ -44,7 +48,7 @@ class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
||||||
filter_horizontal = ['users']
|
filter_horizontal = ['users']
|
||||||
filter_by_account_fields = ('users',)
|
filter_by_account_fields = ('users',)
|
||||||
list_prefetch_related = ('users',)
|
list_prefetch_related = ('users',)
|
||||||
actions = (list_accounts,)
|
actions = (list_accounts, save_selected)
|
||||||
|
|
||||||
def display_users(self, db):
|
def display_users(self, db):
|
||||||
links = []
|
links = []
|
||||||
|
@ -93,7 +97,7 @@ class DatabaseUserAdmin(SelectAccountAdminMixin, ChangePasswordAdminMixin, Exten
|
||||||
readonly_fields = ('account_link', 'display_databases',)
|
readonly_fields = ('account_link', 'display_databases',)
|
||||||
filter_by_account_fields = ('databases',)
|
filter_by_account_fields = ('databases',)
|
||||||
list_prefetch_related = ('databases',)
|
list_prefetch_related = ('databases',)
|
||||||
actions = (list_accounts,)
|
actions = (list_accounts, save_selected)
|
||||||
|
|
||||||
def display_databases(self, user):
|
def display_databases(self, user):
|
||||||
links = []
|
links = []
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-05-28 18:05
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('databases', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='database',
|
||||||
|
name='type',
|
||||||
|
field=models.CharField(choices=[('mysql', 'MySQL')], default='mysql', max_length=32, verbose_name='type'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='databaseuser',
|
||||||
|
name='type',
|
||||||
|
field=models.CharField(choices=[('mysql', 'MySQL')], default='mysql', max_length=32, verbose_name='type'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2020-02-04 11:21
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('databases', '0002_auto_20170528_2005'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='database',
|
||||||
|
name='comments',
|
||||||
|
field=models.TextField(default=''),
|
||||||
|
),
|
||||||
|
]
|
|
@ -22,6 +22,7 @@ class Database(models.Model):
|
||||||
default=settings.DATABASES_DEFAULT_TYPE)
|
default=settings.DATABASES_DEFAULT_TYPE)
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
||||||
related_name='databases')
|
related_name='databases')
|
||||||
|
comments = models.TextField(default="", blank=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('name', 'type')
|
unique_together = ('name', 'type')
|
||||||
|
|
|
@ -20,7 +20,7 @@ DATABASES_DEFAULT_TYPE = Setting('DATABASES_DEFAULT_TYPE',
|
||||||
|
|
||||||
DATABASES_DEFAULT_HOST = Setting('DATABASES_DEFAULT_HOST',
|
DATABASES_DEFAULT_HOST = Setting('DATABASES_DEFAULT_HOST',
|
||||||
'localhost',
|
'localhost',
|
||||||
validators=[validate_hostname],
|
# validators=[validate_hostname],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
||||||
'structured_name', 'display_is_top', 'display_websites', 'display_addresses', 'account_link'
|
'structured_name', 'display_is_top', 'display_websites', 'display_addresses', 'account_link'
|
||||||
)
|
)
|
||||||
add_fields = ('name', 'account')
|
add_fields = ('name', 'account')
|
||||||
fields = ('name', 'account_link', 'display_websites', 'display_addresses')
|
fields = ('name', 'account_link', 'display_websites', 'display_addresses', 'dns2136_address_match_list')
|
||||||
readonly_fields = (
|
readonly_fields = (
|
||||||
'account_link', 'top_link', 'display_websites', 'display_addresses', 'implicit_records'
|
'account_link', 'top_link', 'display_websites', 'display_addresses', 'implicit_records'
|
||||||
)
|
)
|
||||||
|
|
|
@ -102,7 +102,7 @@ class Bind9MasterDomainController(ServiceController):
|
||||||
self.append(textwrap.dedent("""
|
self.append(textwrap.dedent("""
|
||||||
# Apply changes
|
# Apply changes
|
||||||
if [[ $UPDATED == 1 ]]; then
|
if [[ $UPDATED == 1 ]]; then
|
||||||
service bind9 reload
|
rm /etc/bind/master/*jnl || true; service bind9 restart
|
||||||
fi""")
|
fi""")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -158,6 +158,7 @@ class Bind9MasterDomainController(ServiceController):
|
||||||
'slaves': '; '.join(slaves) or 'none',
|
'slaves': '; '.join(slaves) or 'none',
|
||||||
'also_notify': '; '.join(slaves) + ';' if slaves else '',
|
'also_notify': '; '.join(slaves) + ';' if slaves else '',
|
||||||
'conf_path': self.CONF_PATH,
|
'conf_path': self.CONF_PATH,
|
||||||
|
'dns2136_address_match_list': domain.dns2136_address_match_list
|
||||||
}
|
}
|
||||||
context['conf'] = textwrap.dedent("""\
|
context['conf'] = textwrap.dedent("""\
|
||||||
zone "%(name)s" {
|
zone "%(name)s" {
|
||||||
|
@ -166,6 +167,7 @@ class Bind9MasterDomainController(ServiceController):
|
||||||
file "%(zone_path)s";
|
file "%(zone_path)s";
|
||||||
allow-transfer { %(slaves)s; };
|
allow-transfer { %(slaves)s; };
|
||||||
also-notify { %(also_notify)s };
|
also-notify { %(also_notify)s };
|
||||||
|
allow-update { %(dns2136_address_match_list)s };
|
||||||
notify yes;
|
notify yes;
|
||||||
};""") % context
|
};""") % context
|
||||||
return context
|
return context
|
||||||
|
|
|
@ -14,7 +14,7 @@ class BatchDomainCreationAdminForm(forms.ModelForm):
|
||||||
name = forms.CharField(label=_("Names"), widget=forms.Textarea(attrs={'rows': 5, 'cols': 50}),
|
name = forms.CharField(label=_("Names"), widget=forms.Textarea(attrs={'rows': 5, 'cols': 50}),
|
||||||
help_text=_("Fully qualified domain name per line. "
|
help_text=_("Fully qualified domain name per line. "
|
||||||
"All domains will have the provided account and records."))
|
"All domains will have the provided account and records."))
|
||||||
|
|
||||||
def clean_name(self):
|
def clean_name(self):
|
||||||
self.extra_names = []
|
self.extra_names = []
|
||||||
target = None
|
target = None
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-05-28 18:11
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import orchestra.contrib.domains.validators
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('domains', '0005_auto_20160219_1034'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='domain',
|
||||||
|
name='min_ttl',
|
||||||
|
field=models.CharField(blank=True, help_text='The minimum time-to-live value applies to all resource records in the zone file. This value is supplied in query responses to inform other servers how long they should keep the data in cache. The default value is <tt>30m</tt>.', max_length=16, validators=[orchestra.contrib.domains.validators.validate_zone_interval], verbose_name='min TTL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='record',
|
||||||
|
name='ttl',
|
||||||
|
field=models.CharField(blank=True, help_text='Record TTL, defaults to 30m', max_length=8, validators=[orchestra.contrib.domains.validators.validate_zone_interval], verbose_name='TTL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='record',
|
||||||
|
name='type',
|
||||||
|
field=models.CharField(choices=[('MX', 'MX'), ('NS', 'NS'), ('CNAME', 'CNAME'), ('A', 'A (IPv4 address)'), ('AAAA', 'AAAA (IPv6 address)'), ('SRV', 'SRV'), ('TXT', 'TXT'), ('SPF', 'SPF')], max_length=32, verbose_name='type'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2019-08-05 09:34
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('domains', '0006_auto_20170528_2011'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='record',
|
||||||
|
name='value',
|
||||||
|
field=models.CharField(help_text='MX, NS and CNAME records sould end with a dot.', max_length=1024, verbose_name='value'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2019-09-20 07:21
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('domains', '0007_auto_20190805_1134'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='domain',
|
||||||
|
name='dns2136_address_match_list',
|
||||||
|
field=models.CharField(blank=True, default='none;', help_text="A bind-9 'address_match_list' that will be granted permission to perform dns2136 updates. Chiefly used to enable Let's Encrypt self-service validation.", max_length=80),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2020-02-04 11:17
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('domains', '0008_domain_dns2136_address_match_list'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='domain',
|
||||||
|
name='dns2136_address_match_list',
|
||||||
|
field=models.CharField(blank=True, default='key pangea.key;', help_text="A bind-9 'address_match_list' that will be granted permission to perform dns2136 updates. Chiefly used to enable Let's Encrypt self-service validation.", max_length=80),
|
||||||
|
),
|
||||||
|
]
|
|
@ -65,6 +65,10 @@ class Domain(models.Model):
|
||||||
"zone file. This value is supplied in query responses to inform other "
|
"zone file. This value is supplied in query responses to inform other "
|
||||||
"servers how long they should keep the data in cache. "
|
"servers how long they should keep the data in cache. "
|
||||||
"The default value is <tt>%s</tt>.") % settings.DOMAINS_DEFAULT_MIN_TTL)
|
"The default value is <tt>%s</tt>.") % settings.DOMAINS_DEFAULT_MIN_TTL)
|
||||||
|
dns2136_address_match_list = models.CharField(max_length=80, default=settings.DOMAINS_DEFAULT_DNS2136,
|
||||||
|
blank=True,
|
||||||
|
help_text="A bind-9 'address_match_list' that will be granted permission to perform "
|
||||||
|
"dns2136 updates. Chiefly used to enable Let's Encrypt self-service validation.")
|
||||||
|
|
||||||
objects = DomainQuerySet.as_manager()
|
objects = DomainQuerySet.as_manager()
|
||||||
|
|
||||||
|
@ -319,7 +323,8 @@ class Record(models.Model):
|
||||||
help_text=_("Record TTL, defaults to %s") % settings.DOMAINS_DEFAULT_TTL,
|
help_text=_("Record TTL, defaults to %s") % settings.DOMAINS_DEFAULT_TTL,
|
||||||
validators=[validators.validate_zone_interval])
|
validators=[validators.validate_zone_interval])
|
||||||
type = models.CharField(_("type"), max_length=32, choices=TYPE_CHOICES)
|
type = models.CharField(_("type"), max_length=32, choices=TYPE_CHOICES)
|
||||||
value = models.CharField(_("value"), max_length=256,
|
# max_length bumped from 256 to 1024 (arbitrary) on August 2019.
|
||||||
|
value = models.CharField(_("value"), max_length=1024,
|
||||||
help_text=_("MX, NS and CNAME records sould end with a dot."))
|
help_text=_("MX, NS and CNAME records sould end with a dot."))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
|
@ -122,3 +122,6 @@ DOMAINS_MASTERS = Setting('DOMAINS_MASTERS',
|
||||||
validators=[lambda masters: list(map(validate_ip_address, masters))],
|
validators=[lambda masters: list(map(validate_ip_address, masters))],
|
||||||
help_text="Additional master server ip addresses other than autodiscovered by router.get_servers()."
|
help_text="Additional master server ip addresses other than autodiscovered by router.get_servers()."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
#TODO remove pangea-specific default
|
||||||
|
DOMAINS_DEFAULT_DNS2136 = "key pangea.key;"
|
||||||
|
|
|
@ -60,7 +60,7 @@ def validate_zone_label(value):
|
||||||
if not value.endswith('.'):
|
if not value.endswith('.'):
|
||||||
msg = _("Use a fully expanded domain name ending with a dot.")
|
msg = _("Use a fully expanded domain name ending with a dot.")
|
||||||
raise ValidationError(msg)
|
raise ValidationError(msg)
|
||||||
if len(value) > 63:
|
if len(value) > 254:
|
||||||
raise ValidationError(_("Labels must be 63 characters or less."))
|
raise ValidationError(_("Labels must be 63 characters or less."))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-05-28 18:11
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('issues', '0003_auto_20160320_1127'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='ticket',
|
||||||
|
name='creator',
|
||||||
|
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tickets_created', to=settings.AUTH_USER_MODEL, verbose_name='created by'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='ticket',
|
||||||
|
name='owner',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tickets_owned', to=settings.AUTH_USER_MODEL, verbose_name='assigned to'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='ticket',
|
||||||
|
name='queue',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tickets', to='issues.Queue'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -10,7 +10,7 @@ from .serializers import ListSerializer
|
||||||
class ListViewSet(LogApiMixin, AccountApiMixin, SetPasswordApiMixin, viewsets.ModelViewSet):
|
class ListViewSet(LogApiMixin, AccountApiMixin, SetPasswordApiMixin, viewsets.ModelViewSet):
|
||||||
queryset = List.objects.all()
|
queryset = List.objects.all()
|
||||||
serializer_class = ListSerializer
|
serializer_class = ListSerializer
|
||||||
filter_fields = ('name',)
|
filter_fields = ('name', 'address_domain')
|
||||||
|
|
||||||
|
|
||||||
router.register(r'lists', ListViewSet)
|
router.register(r'lists', ListViewSet)
|
||||||
|
|
|
@ -48,20 +48,14 @@ class MailmanVirtualDomainController(ServiceController):
|
||||||
|
|
||||||
def save(self, mail_list):
|
def save(self, mail_list):
|
||||||
context = self.get_context(mail_list)
|
context = self.get_context(mail_list)
|
||||||
self.include_virtual_alias_domain(context)
|
#self.include_virtual_alias_domain(context)
|
||||||
|
|
||||||
def delete(self, mail_list):
|
def delete(self, mail_list):
|
||||||
context = self.get_context(mail_list)
|
context = self.get_context(mail_list)
|
||||||
self.exclude_virtual_alias_domain(context)
|
#self.exclude_virtual_alias_domain(context)
|
||||||
|
|
||||||
def commit(self):
|
def commit(self):
|
||||||
context = self.get_context_files()
|
context = self.get_context_files()
|
||||||
self.append(textwrap.dedent("""
|
|
||||||
# Apply changes if needed
|
|
||||||
if [[ $UPDATED_VIRTUAL_ALIAS_DOMAINS == 1 ]]; then
|
|
||||||
service postfix reload
|
|
||||||
fi""") % context
|
|
||||||
)
|
|
||||||
super(MailmanVirtualDomainController, self).commit()
|
super(MailmanVirtualDomainController, self).commit()
|
||||||
|
|
||||||
def get_context_files(self):
|
def get_context_files(self):
|
||||||
|
@ -107,7 +101,7 @@ class MailmanController(MailmanVirtualDomainController):
|
||||||
for suffix in self.address_suffixes:
|
for suffix in self.address_suffixes:
|
||||||
context['suffix'] = suffix
|
context['suffix'] = suffix
|
||||||
# Because mailman doesn't properly handle lists aliases we need two virtual aliases
|
# Because mailman doesn't properly handle lists aliases we need two virtual aliases
|
||||||
aliases.append("%(address_name)s%(suffix)s@%(domain)s\t%(name)s%(suffix)s" % context)
|
aliases.append("%(address_name)s%(suffix)s@%(domain)s\t%(name)s%(suffix)s@grups.pangea.org" % context)
|
||||||
if context['address_name'] != context['name']:
|
if context['address_name'] != context['name']:
|
||||||
# And another with the original list name; Mailman generates links with it
|
# And another with the original list name; Mailman generates links with it
|
||||||
aliases.append("%(name)s%(suffix)s@%(domain)s\t%(name)s%(suffix)s" % context)
|
aliases.append("%(name)s%(suffix)s@%(domain)s\t%(name)s%(suffix)s" % context)
|
||||||
|
@ -116,85 +110,22 @@ class MailmanController(MailmanVirtualDomainController):
|
||||||
def save(self, mail_list):
|
def save(self, mail_list):
|
||||||
context = self.get_context(mail_list)
|
context = self.get_context(mail_list)
|
||||||
# Create list
|
# Create list
|
||||||
self.append(textwrap.dedent("""
|
cmd = "/opt/mailman/venv/bin/python /usr/local/admin/orchestra_mailman3/save.py %(name)s %(admin)s %(address_name)s@%(domain)s" % context
|
||||||
# Create list %(name)s
|
if not mail_list.active:
|
||||||
[[ ! -e '%(mailman_root)s/lists/%(name)s' ]] && {
|
cmd += ' --inactive'
|
||||||
newlist --quiet --emailhost='%(domain)s' '%(name)s' '%(admin)s' '%(password)s'
|
self.append(cmd)
|
||||||
}""") % context)
|
|
||||||
# Custom domain
|
|
||||||
if mail_list.address:
|
|
||||||
context.update({
|
|
||||||
'aliases': self.get_virtual_aliases(context),
|
|
||||||
'num_entries': 2 if context['address_name'] != context['name'] else 1,
|
|
||||||
})
|
|
||||||
self.append(textwrap.dedent("""\
|
|
||||||
# Create list alias for custom domain
|
|
||||||
aliases='%(aliases)s'
|
|
||||||
if ! grep '\s\s*%(name)s\s*$' %(virtual_alias)s > /dev/null; then
|
|
||||||
echo "${aliases}" >> %(virtual_alias)s
|
|
||||||
UPDATED_VIRTUAL_ALIAS=1
|
|
||||||
else
|
|
||||||
existing=$({ grep -E '^\s*(%(address_name)s|%(name)s)@%(address_domain)s\s\s*%(name)s\s*$' %(virtual_alias)s || test $? -lt 2; }|wc -l)
|
|
||||||
if [[ $existing -ne %(num_entries)s ]]; then
|
|
||||||
sed -i -e '/^.*\s%(name)s\(%(suffixes_regex)s\)\s*$/d' \\
|
|
||||||
-e 'N; /^\s*\\n\s*$/d; P; D' %(virtual_alias)s
|
|
||||||
echo "${aliases}" >> %(virtual_alias)s
|
|
||||||
UPDATED_VIRTUAL_ALIAS=1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
echo "require_explicit_destination = 0" | \\
|
|
||||||
%(mailman_root)s/bin/config_list -i /dev/stdin %(name)s
|
|
||||||
echo "host_name = '%(address_domain)s'" | \\
|
|
||||||
%(mailman_root)s/bin/config_list -i /dev/stdin %(name)s""") % context
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.append(textwrap.dedent("""\
|
|
||||||
# Cleanup possible ex-custom domain
|
|
||||||
if ! grep '\s\s*%(name)s\s*$' %(virtual_alias)s > /dev/null; then
|
|
||||||
sed -i "/^.*\s%(name)s\s*$/d" %(virtual_alias)s
|
|
||||||
fi""") % context
|
|
||||||
)
|
|
||||||
# Update
|
|
||||||
if context['password'] is not None:
|
|
||||||
self.append(textwrap.dedent("""\
|
|
||||||
# Re-set password
|
|
||||||
%(mailman_root)s/bin/change_pw --listname="%(name)s" --password="%(password)s"\
|
|
||||||
""") % context
|
|
||||||
)
|
|
||||||
self.include_virtual_alias_domain(context)
|
|
||||||
if mail_list.active:
|
|
||||||
self.append('chmod 775 %(mailman_root)s/lists/%(name)s' % context)
|
|
||||||
else:
|
|
||||||
self.append('chmod 000 %(mailman_root)s/lists/%(name)s' % context)
|
|
||||||
|
|
||||||
def delete(self, mail_list):
|
def delete(self, mail_list):
|
||||||
context = self.get_context(mail_list)
|
context = self.get_context(mail_list)
|
||||||
self.exclude_virtual_alias_domain(context)
|
# Delete list
|
||||||
self.append(textwrap.dedent("""
|
cmd = "/opt/mailman/venv/bin/python /usr/local/admin/orchestra_mailman3/delete.py %(name)s %(admin)s %(address_name)s@%(domain)s" % context
|
||||||
# Remove list %(name)s
|
if not mail_list.active:
|
||||||
sed -i -e '/^.*\s%(name)s\(%(suffixes_regex)s\)\s*$/d' \\
|
cmd += ' --inactive'
|
||||||
-e 'N; /^\s*\\n\s*$/d; P; D' %(virtual_alias)s
|
self.append(cmd)
|
||||||
# Non-existent list archives produce exit code 1
|
|
||||||
exit_code=0
|
|
||||||
rmlist -a %(name)s || exit_code=$?
|
|
||||||
if [[ $exit_code != 0 && $exit_code != 1 ]]; then
|
|
||||||
exit $exit_code
|
|
||||||
fi""") % context
|
|
||||||
)
|
|
||||||
|
|
||||||
def commit(self):
|
def commit(self):
|
||||||
context = self.get_context_files()
|
pass
|
||||||
self.append(textwrap.dedent("""
|
|
||||||
# Apply changes if needed
|
|
||||||
if [[ $UPDATED_VIRTUAL_ALIAS == 1 ]]; then
|
|
||||||
postmap %(virtual_alias)s
|
|
||||||
fi
|
|
||||||
if [[ $UPDATED_VIRTUAL_ALIAS_DOMAINS == 1 ]]; then
|
|
||||||
service postfix reload
|
|
||||||
fi
|
|
||||||
exit $exit_code""") % context
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_context_files(self):
|
def get_context_files(self):
|
||||||
return {
|
return {
|
||||||
'virtual_alias': settings.LISTS_VIRTUAL_ALIAS_PATH,
|
'virtual_alias': settings.LISTS_VIRTUAL_ALIAS_PATH,
|
||||||
|
|
|
@ -31,7 +31,7 @@ class ListSerializer(AccountSerializerMixin, SetPasswordHyperlinkedSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = List
|
model = List
|
||||||
fields = ('url', 'id', 'name', 'password', 'address_name', 'address_domain', 'admin_email')
|
fields = ('url', 'id', 'name', 'password', 'address_name', 'address_domain', 'admin_email', 'is_active',)
|
||||||
postonly_fields = ('name', 'password')
|
postonly_fields = ('name', 'password')
|
||||||
|
|
||||||
def validate_address_domain(self, address_name):
|
def validate_address_domain(self, address_name):
|
||||||
|
|
|
@ -605,3 +605,11 @@ class PostfixMailscannerTraffic(ServiceMonitor):
|
||||||
'last_date': self.get_last_date(mailbox.pk).strftime("%Y-%m-%d %H:%M:%S %Z"),
|
'last_date': self.get_last_date(mailbox.pk).strftime("%Y-%m-%d %H:%M:%S %Z"),
|
||||||
}
|
}
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
class RoundcubeIdentityController(ServiceController):
|
||||||
|
"""
|
||||||
|
WARNING: not implemented
|
||||||
|
"""
|
||||||
|
verbose_name = _("Roundcube Identity Controller")
|
||||||
|
model = 'mailboxes.Mailbox'
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-05-28 18:11
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
import orchestra.contrib.mailboxes.validators
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('mailboxes', '0002_auto_20160219_1032'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='mailbox',
|
||||||
|
name='custom_filtering',
|
||||||
|
field=models.TextField(blank=True, help_text="Arbitrary email filtering in <a href='https://tty1.net/blog/2011/sieve-tutorial_en.html'>sieve language</a>. This overrides any automatic junk email filtering", validators=[orchestra.contrib.mailboxes.validators.validate_sieve], verbose_name='filtering'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='mailbox',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(db_index=True, help_text='Required. 32 characters or fewer. Letters, digits and ./-/_ only.', max_length=32, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.-]+$', 'Enter a valid mailbox name.')], verbose_name='name'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,3 +1,4 @@
|
||||||
|
import logging
|
||||||
import textwrap
|
import textwrap
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
|
@ -9,6 +10,7 @@ from orchestra import plugins
|
||||||
|
|
||||||
from . import methods
|
from . import methods
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def replace(context, pattern, repl):
|
def replace(context, pattern, repl):
|
||||||
""" applies replace to all context str values """
|
""" applies replace to all context str values """
|
||||||
|
@ -108,7 +110,10 @@ class ServiceBackend(plugins.Plugin, metaclass=ServiceMount):
|
||||||
def get_related(cls, obj):
|
def get_related(cls, obj):
|
||||||
opts = obj._meta
|
opts = obj._meta
|
||||||
model = '%s.%s' % (opts.app_label, opts.object_name)
|
model = '%s.%s' % (opts.app_label, opts.object_name)
|
||||||
|
logger.debug('Model: {}'.format(model))
|
||||||
for rel_model, field in cls.related_models:
|
for rel_model, field in cls.related_models:
|
||||||
|
logger.debug('rel_model: {}'.format(rel_model))
|
||||||
|
logger.debug('field: {}'.format(field))
|
||||||
if rel_model == model:
|
if rel_model == model:
|
||||||
related = obj
|
related = obj
|
||||||
for attribute in field.split('__'):
|
for attribute in field.split('__'):
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-05-28 18:11
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('orchestration', '0006_auto_20160219_1110'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='route',
|
||||||
|
name='backend',
|
||||||
|
field=models.CharField(choices=[('Apache2Traffic', '[M] Apache 2 Traffic'), ('ApacheTrafficByName', '[M] ApacheTrafficByName'), ('DokuWikiMuTraffic', '[M] DokuWiki MU Traffic'), ('DovecotMaildirDisk', '[M] Dovecot Maildir size'), ('Exim4Traffic', '[M] Exim4 traffic'), ('MailmanSubscribers', '[M] Mailman subscribers'), ('MailmanTraffic', '[M] Mailman traffic'), ('MysqlDisk', '[M] MySQL disk'), ('PostfixMailscannerTraffic', '[M] Postfix-Mailscanner traffic'), ('ProxmoxOpenVZTraffic', '[M] ProxmoxOpenVZTraffic'), ('UNIXUserDisk', '[M] UNIX user disk'), ('VsFTPdTraffic', '[M] VsFTPd traffic'), ('WordpressMuTraffic', '[M] Wordpress MU Traffic'), ('NextCloudDiskQuota', '[M] nextCloud SaaS Disk Quota'), ('NextcloudTraffic', '[M] nextCloud SaaS Traffic'), ('OwnCloudDiskQuota', '[M] ownCloud SaaS Disk Quota'), ('OwncloudTraffic', '[M] ownCloud SaaS Traffic'), ('PhpListTraffic', '[M] phpList SaaS Traffic'), ('Apache2Controller', '[S] Apache 2'), ('BSCWController', '[S] BSCW SaaS'), ('Bind9MasterDomainController', '[S] Bind9 master domain'), ('Bind9SlaveDomainController', '[S] Bind9 slave domain'), ('DokuWikiMuController', '[S] DokuWiki multisite'), ('DrupalMuController', '[S] Drupal multisite'), ('GitLabSaaSController', '[S] GitLab SaaS'), ('LetsEncryptController', "[S] Let's encrypt!"), ('LxcController', '[S] LxcController'), ('AutoresponseController', '[S] Mail autoresponse'), ('MailScannerSpamRuleController', '[S] MailScanner ruleset'), ('MailmanController', '[S] Mailman'), ('MailmanVirtualDomainController', '[S] Mailman virtdomain-only'), ('MoodleController', '[S] Moodle'), ('MoodleWWWRootController', '[S] Moodle WWWRoot (required)'), ('MoodleMuController', '[S] Moodle multisite'), ('MySQLController', '[S] MySQL database'), ('MySQLUserController', '[S] MySQL user'), ('PHPController', '[S] PHP FPM/FCGID'), ('PangeaProxmoxOVZ', '[S] PangeaProxmoxOVZ'), ('PostfixAddressController', '[S] Postfix address'), ('PostfixAddressVirtualDomainController', '[S] Postfix address virtdomain-only'), ('PostfixRecipientAccessController', '[S] Postfix recipient access'), ('ProxmoxOVZ', '[S] ProxmoxOVZ'), ('uWSGIPythonController', '[S] Python uWSGI'), ('StaticController', '[S] Static'), ('SymbolicLinkController', '[S] Symbolic link webapp'), ('SyncBind9MasterDomainController', '[S] Sync Bind9 master domain'), ('SyncBind9SlaveDomainController', '[S] Sync Bind9 slave domain'), ('UNIXUserMaildirController', '[S] UNIX maildir user'), ('UNIXUserController', '[S] UNIX user'), ('WebalizerAppController', '[S] Webalizer App'), ('WebalizerController', '[S] Webalizer Content'), ('WordPressForceSSLController', '[S] WordPress Force SSL'), ('WordPressURLController', '[S] WordPress URL'), ('WordPressController', '[S] Wordpress'), ('WordpressMuController', '[S] Wordpress multisite'), ('NextCloudController', '[S] nextCloud SaaS'), ('OwnCloudController', '[S] ownCloud SaaS'), ('PhpListSaaSController', '[S] phpList SaaS')], max_length=256, verbose_name='backend'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='route',
|
||||||
|
name='host',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='routes', to='orchestration.Server', verbose_name='host'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2019-08-05 09:34
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('orchestration', '0007_auto_20170528_2011'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='route',
|
||||||
|
name='backend',
|
||||||
|
field=models.CharField(choices=[('Apache2Traffic', '[M] Apache 2 Traffic'), ('ApacheTrafficByName', '[M] ApacheTrafficByName'), ('DokuWikiMuTraffic', '[M] DokuWiki MU Traffic'), ('DovecotMaildirDisk', '[M] Dovecot Maildir size'), ('Exim4Traffic', '[M] Exim4 traffic'), ('MailmanSubscribers', '[M] Mailman subscribers'), ('MailmanTraffic', '[M] Mailman traffic'), ('MysqlDisk', '[M] MySQL disk'), ('PostfixMailscannerTraffic', '[M] Postfix-Mailscanner traffic'), ('ProxmoxOpenVZTraffic', '[M] ProxmoxOpenVZTraffic'), ('UNIXUserDisk', '[M] UNIX user disk'), ('VsFTPdTraffic', '[M] VsFTPd traffic'), ('WordpressMuTraffic', '[M] Wordpress MU Traffic'), ('NextCloudDiskQuota', '[M] nextCloud SaaS Disk Quota'), ('NextcloudTraffic', '[M] nextCloud SaaS Traffic'), ('OwnCloudDiskQuota', '[M] ownCloud SaaS Disk Quota'), ('OwncloudTraffic', '[M] ownCloud SaaS Traffic'), ('PhpListTraffic', '[M] phpList SaaS Traffic'), ('Apache2Controller', '[S] Apache 2'), ('BSCWController', '[S] BSCW SaaS'), ('Bind9MasterDomainController', '[S] Bind9 master domain'), ('Bind9SlaveDomainController', '[S] Bind9 slave domain'), ('DokuWikiMuController', '[S] DokuWiki multisite'), ('DrupalMuController', '[S] Drupal multisite'), ('GitLabSaaSController', '[S] GitLab SaaS'), ('LetsEncryptController', "[S] Let's encrypt!"), ('LxcController', '[S] LxcController'), ('AutoresponseController', '[S] Mail autoresponse'), ('MailScannerSpamRuleController', '[S] MailScanner ruleset'), ('MailmanController', '[S] Mailman'), ('MailmanVirtualDomainController', '[S] Mailman virtdomain-only'), ('MoodleController', '[S] Moodle'), ('MoodleWWWRootController', '[S] Moodle WWWRoot (required)'), ('MoodleMuController', '[S] Moodle multisite'), ('MySQLController', '[S] MySQL database'), ('MySQLUserController', '[S] MySQL user'), ('PHPController', '[S] PHP FPM/FCGID'), ('PangeaProxmoxOVZ', '[S] PangeaProxmoxOVZ'), ('PostfixAddressController', '[S] Postfix address'), ('PostfixAddressVirtualDomainController', '[S] Postfix address virtdomain-only'), ('PostfixRecipientAccessController', '[S] Postfix recipient access'), ('ProxmoxOVZ', '[S] ProxmoxOVZ'), ('uWSGIPythonController', '[S] Python uWSGI'), ('RoundcubeIdentityController', '[S] Roundcube Identity Controller'), ('StaticController', '[S] Static'), ('SymbolicLinkController', '[S] Symbolic link webapp'), ('SyncBind9MasterDomainController', '[S] Sync Bind9 master domain'), ('SyncBind9SlaveDomainController', '[S] Sync Bind9 slave domain'), ('UNIXUserMaildirController', '[S] UNIX maildir user'), ('UNIXUserController', '[S] UNIX user'), ('WebalizerAppController', '[S] Webalizer App'), ('WebalizerController', '[S] Webalizer Content'), ('WordPressForceSSLController', '[S] WordPress Force SSL'), ('WordPressURLController', '[S] WordPress URL'), ('WordPressController', '[S] Wordpress'), ('WordpressMuController', '[S] Wordpress multisite'), ('NextCloudController', '[S] nextCloud SaaS'), ('OwnCloudController', '[S] ownCloud SaaS'), ('PhpListSaaSController', '[S] phpList SaaS')], max_length=256, verbose_name='backend'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -193,6 +193,7 @@ def report(modeladmin, request, queryset):
|
||||||
transactions = Transaction.objects.filter(id__in=transactions)
|
transactions = Transaction.objects.filter(id__in=transactions)
|
||||||
states = {}
|
states = {}
|
||||||
total = 0
|
total = 0
|
||||||
|
transactions = transactions.order_by('bill__number')
|
||||||
for transaction in transactions:
|
for transaction in transactions:
|
||||||
state = transaction.get_state_display()
|
state = transaction.get_state_display()
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -215,7 +215,9 @@ class SEPADirectDebit(PaymentMethod):
|
||||||
),
|
),
|
||||||
E.DrctDbtTx( # Direct Debit Transaction
|
E.DrctDbtTx( # Direct Debit Transaction
|
||||||
E.MndtRltdInf( # Mandate Related Info
|
E.MndtRltdInf( # Mandate Related Info
|
||||||
E.MndtId(str(account.id)), # Mandate Id
|
# + 10000 xk vam canviar de sistema per generar aquestes IDs i volem evitar colisions amb els
|
||||||
|
# numeros usats antigament
|
||||||
|
E.MndtId(str(transaction.source_id+10000)), # Mandate Id
|
||||||
E.DtOfSgntr( # Date of Signature
|
E.DtOfSgntr( # Date of Signature
|
||||||
account.date_joined.strftime("%Y-%m-%d")
|
account.date_joined.strftime("%Y-%m-%d")
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-05-28 18:11
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('payments', '0002_auto_20150709_1018'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='transaction',
|
||||||
|
name='source',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transactions', to='payments.PaymentSource', verbose_name='source'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,32 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-05-28 18:05
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import orchestra.models.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('resources', '0010_auto_20160219_1108'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='monitordata',
|
||||||
|
name='monitor',
|
||||||
|
field=models.CharField(choices=[('Apache2Traffic', '[M] Apache 2 Traffic'), ('ApacheTrafficByName', '[M] ApacheTrafficByName'), ('DokuWikiMuTraffic', '[M] DokuWiki MU Traffic'), ('DovecotMaildirDisk', '[M] Dovecot Maildir size'), ('Exim4Traffic', '[M] Exim4 traffic'), ('MailmanSubscribers', '[M] Mailman subscribers'), ('MailmanTraffic', '[M] Mailman traffic'), ('MysqlDisk', '[M] MySQL disk'), ('PostfixMailscannerTraffic', '[M] Postfix-Mailscanner traffic'), ('ProxmoxOpenVZTraffic', '[M] ProxmoxOpenVZTraffic'), ('UNIXUserDisk', '[M] UNIX user disk'), ('VsFTPdTraffic', '[M] VsFTPd traffic'), ('WordpressMuTraffic', '[M] Wordpress MU Traffic'), ('NextCloudDiskQuota', '[M] nextCloud SaaS Disk Quota'), ('NextcloudTraffic', '[M] nextCloud SaaS Traffic'), ('OwnCloudDiskQuota', '[M] ownCloud SaaS Disk Quota'), ('OwncloudTraffic', '[M] ownCloud SaaS Traffic'), ('PhpListTraffic', '[M] phpList SaaS Traffic')], db_index=True, max_length=256, verbose_name='monitor'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='resource',
|
||||||
|
name='crontab',
|
||||||
|
field=models.ForeignKey(blank=True, help_text='Crontab for periodic execution. Leave it empty to disable periodic monitoring', null=True, on_delete=django.db.models.deletion.SET_NULL, to='djcelery.CrontabSchedule', verbose_name='crontab'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='resource',
|
||||||
|
name='monitors',
|
||||||
|
field=orchestra.models.fields.MultiSelectField(blank=True, choices=[('Apache2Traffic', '[M] Apache 2 Traffic'), ('ApacheTrafficByName', '[M] ApacheTrafficByName'), ('DokuWikiMuTraffic', '[M] DokuWiki MU Traffic'), ('DovecotMaildirDisk', '[M] Dovecot Maildir size'), ('Exim4Traffic', '[M] Exim4 traffic'), ('MailmanSubscribers', '[M] Mailman subscribers'), ('MailmanTraffic', '[M] Mailman traffic'), ('MysqlDisk', '[M] MySQL disk'), ('PostfixMailscannerTraffic', '[M] Postfix-Mailscanner traffic'), ('ProxmoxOpenVZTraffic', '[M] ProxmoxOpenVZTraffic'), ('UNIXUserDisk', '[M] UNIX user disk'), ('VsFTPdTraffic', '[M] VsFTPd traffic'), ('WordpressMuTraffic', '[M] Wordpress MU Traffic'), ('NextCloudDiskQuota', '[M] nextCloud SaaS Disk Quota'), ('NextcloudTraffic', '[M] nextCloud SaaS Traffic'), ('OwnCloudDiskQuota', '[M] ownCloud SaaS Disk Quota'), ('OwncloudTraffic', '[M] ownCloud SaaS Traffic'), ('PhpListTraffic', '[M] phpList SaaS Traffic')], help_text='Monitor backends used for monitoring this resource.', max_length=256, verbose_name='monitors'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,175 @@
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import textwrap
|
||||||
|
import time
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from orchestra.contrib.orchestration import ServiceController
|
||||||
|
from orchestra.contrib.resources import ServiceMonitor
|
||||||
|
|
||||||
|
from . import ApacheTrafficByName
|
||||||
|
from .. import settings
|
||||||
|
|
||||||
|
|
||||||
|
class NextCloudAPIMixin(object):
|
||||||
|
def validate_response(self, response):
|
||||||
|
request = response.request
|
||||||
|
context = (request.method, response.url, request.body, response.status_code)
|
||||||
|
sys.stderr.write("%s %s '%s' HTTP %s\n" % context)
|
||||||
|
if response.status_code != requests.codes.ok:
|
||||||
|
raise RuntimeError("%s %s '%s' HTTP %s" % context)
|
||||||
|
root = ET.fromstring(response.text)
|
||||||
|
statuscode = root.find("./meta/statuscode").text
|
||||||
|
if statuscode != '100':
|
||||||
|
message = root.find("./meta/status").text
|
||||||
|
request = response.request
|
||||||
|
context = (request.method, response.url, request.body, statuscode, message)
|
||||||
|
raise RuntimeError("%s %s '%s' ERROR %s, %s" % context)
|
||||||
|
|
||||||
|
def api_call(self, action, url_path, *args, **kwargs):
|
||||||
|
BASE_URL = settings.SAAS_NEXTCLOUD_API_URL.rstrip('/')
|
||||||
|
url = '/'.join((BASE_URL, url_path))
|
||||||
|
response = action(url, headers={'OCS-APIRequest':'true'}, *args, **kwargs)
|
||||||
|
self.validate_response(response)
|
||||||
|
return response
|
||||||
|
|
||||||
|
def api_get(self, url_path, *args, **kwargs):
|
||||||
|
return self.api_call(requests.get, url_path, *args, **kwargs)
|
||||||
|
|
||||||
|
def api_post(self, url_path, *args, **kwargs):
|
||||||
|
return self.api_call(requests.post, url_path, *args, **kwargs)
|
||||||
|
|
||||||
|
def api_put(self, url_path, *args, **kwargs):
|
||||||
|
return self.api_call(requests.put, url_path, *args, **kwargs)
|
||||||
|
|
||||||
|
def api_delete(self, url_path, *args, **kwargs):
|
||||||
|
return self.api_call(requests.delete, url_path, *args, **kwargs)
|
||||||
|
|
||||||
|
def create(self, saas):
|
||||||
|
data = {
|
||||||
|
'userid': saas.name,
|
||||||
|
'password': saas.password
|
||||||
|
}
|
||||||
|
self.api_post('users', data)
|
||||||
|
|
||||||
|
def update(self, saas):
|
||||||
|
"""
|
||||||
|
key: email|quota|display|password
|
||||||
|
value: el valor a modificar.
|
||||||
|
Si es un email, tornarà un error si la direcció no te la "@"
|
||||||
|
Si es una quota, sembla que algo per l'estil "5G", "100M", etc. funciona. Quota 0 = infinit
|
||||||
|
"display" es el display name, no crec que el fem servir, és cosmetic
|
||||||
|
"""
|
||||||
|
data = {
|
||||||
|
'key': 'password',
|
||||||
|
'value': saas.password,
|
||||||
|
}
|
||||||
|
self.api_put('users/%s' % saas.name, data)
|
||||||
|
|
||||||
|
def get_user(self, saas):
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
'displayname'
|
||||||
|
'email'
|
||||||
|
'quota' =>
|
||||||
|
{
|
||||||
|
'free' (en Bytes)
|
||||||
|
'relative' (en tant per cent sense signe %, e.g. 68.17)
|
||||||
|
'total' (en Bytes)
|
||||||
|
'used' (en Bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
response = self.api_get('users/%s' % saas.name)
|
||||||
|
root = ET.fromstring(response.text)
|
||||||
|
ret = {}
|
||||||
|
for data in root.find('./data'):
|
||||||
|
ret[data.tag] = data.text
|
||||||
|
ret['quota'] = {}
|
||||||
|
for data in root.find('.data/quota'):
|
||||||
|
ret['quota'][data.tag] = data.text
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
class NextCloudController(NextCloudAPIMixin, ServiceController):
|
||||||
|
"""
|
||||||
|
Creates a wordpress site on a WordPress MultiSite installation.
|
||||||
|
|
||||||
|
You should point it to the database server
|
||||||
|
"""
|
||||||
|
verbose_name = _("nextCloud SaaS")
|
||||||
|
model = 'saas.SaaS'
|
||||||
|
default_route_match = "saas.service == 'nextcloud'"
|
||||||
|
doc_settings = (settings,
|
||||||
|
('SAAS_NEXTCLOUD_API_URL',)
|
||||||
|
)
|
||||||
|
|
||||||
|
def update_or_create(self, saas, server):
|
||||||
|
try:
|
||||||
|
self.api_get('users/%s' % saas.name)
|
||||||
|
except RuntimeError:
|
||||||
|
if getattr(saas, 'password'):
|
||||||
|
self.create(saas)
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
if getattr(saas, 'password'):
|
||||||
|
self.update(saas)
|
||||||
|
|
||||||
|
def remove(self, saas, server):
|
||||||
|
self.api_delete('users/%s' % saas.name)
|
||||||
|
|
||||||
|
def save(self, saas):
|
||||||
|
# TODO disable user https://github.com/owncloud/core/issues/12601
|
||||||
|
self.append(self.update_or_create, saas)
|
||||||
|
|
||||||
|
def delete(self, saas):
|
||||||
|
self.append(self.remove, saas)
|
||||||
|
|
||||||
|
|
||||||
|
class NextcloudTraffic(ApacheTrafficByName):
|
||||||
|
__doc__ = ApacheTrafficByName.__doc__
|
||||||
|
verbose_name = _("nextCloud SaaS Traffic")
|
||||||
|
default_route_match = "saas.service == 'nextcloud'"
|
||||||
|
doc_settings = (settings,
|
||||||
|
('SAAS_TRAFFIC_IGNORE_HOSTS', 'SAAS_NEXTCLOUD_LOG_PATH')
|
||||||
|
)
|
||||||
|
log_path = settings.SAAS_NEXTCLOUD_LOG_PATH
|
||||||
|
|
||||||
|
|
||||||
|
class NextCloudDiskQuota(NextCloudAPIMixin, ServiceMonitor):
|
||||||
|
model = 'saas.SaaS'
|
||||||
|
verbose_name = _("nextCloud SaaS Disk Quota")
|
||||||
|
default_route_match = "saas.service == 'nextcloud'"
|
||||||
|
resource = ServiceMonitor.DISK
|
||||||
|
delete_old_equal_values = True
|
||||||
|
|
||||||
|
def monitor(self, user):
|
||||||
|
context = self.get_context(user)
|
||||||
|
self.append("echo %(object_id)s $(monitor %(base_home)s)" % context)
|
||||||
|
|
||||||
|
def get_context(self, user):
|
||||||
|
context = {
|
||||||
|
'object_id': user.pk,
|
||||||
|
'base_home': user.get_base_home(),
|
||||||
|
}
|
||||||
|
return replace(context, "'", '"')
|
||||||
|
|
||||||
|
def get_quota(self, saas, server):
|
||||||
|
try:
|
||||||
|
user = self.get_user(saas)
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
time.sleep(2)
|
||||||
|
user = self.get_user(saas)
|
||||||
|
context = {
|
||||||
|
'object_id': saas.pk,
|
||||||
|
'used': int(user['quota'].get('used', 0)),
|
||||||
|
}
|
||||||
|
sys.stdout.write('%(object_id)i %(used)i\n' % context)
|
||||||
|
|
||||||
|
def monitor(self, saas):
|
||||||
|
self.append(self.get_quota, saas)
|
|
@ -0,0 +1,31 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-05-28 18:11
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import orchestra.core.validators
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('saas', '0002_auto_20151001_0923'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='saas',
|
||||||
|
name='custom_url',
|
||||||
|
field=models.URLField(blank=True, help_text='Optional and alternative URL for accessing this service instance. i.e. <tt>https://wiki.mydomain/doku/</tt><br>A related website will be automatically configured if needed.', verbose_name='custom URL'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='saas',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(help_text='Required. 64 characters or fewer. Letters, digits and ./- only.', max_length=64, validators=[orchestra.core.validators.validate_hostname], verbose_name='Name'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='saas',
|
||||||
|
name='service',
|
||||||
|
field=models.CharField(choices=[('bscw', 'BSCW'), ('dokuwiki', 'Dowkuwiki'), ('drupal', 'Drupal'), ('gitlab', 'GitLab'), ('moodle', 'Moodle'), ('wordpress', 'WordPress'), ('nextcloud', 'nextCloud'), ('owncloud', 'ownCloud'), ('phplist', 'phpList')], max_length=32, verbose_name='service'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -70,7 +70,7 @@ class SaaS(models.Model):
|
||||||
self.save(update_fields=('is_active',))
|
self.save(update_fields=('is_active',))
|
||||||
|
|
||||||
def enable(self):
|
def enable(self):
|
||||||
self.is_active = False
|
self.is_active = True
|
||||||
self.save(update_fields=('is_active',))
|
self.save(update_fields=('is_active',))
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
|
|
@ -6,6 +6,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.contrib.websites.models import Website, WebsiteDirective, Content
|
from orchestra.contrib.websites.models import Website, WebsiteDirective, Content
|
||||||
from orchestra.contrib.websites.validators import validate_domain_protocol
|
from orchestra.contrib.websites.validators import validate_domain_protocol
|
||||||
|
from orchestra.contrib.orchestration.models import Server
|
||||||
from orchestra.utils.python import AttrDict
|
from orchestra.utils.python import AttrDict
|
||||||
|
|
||||||
|
|
||||||
|
@ -54,7 +55,9 @@ def clean_custom_url(saas):
|
||||||
(url.netloc, account, domain.account),
|
(url.netloc, account, domain.account),
|
||||||
})
|
})
|
||||||
# Create new website for custom_url
|
# Create new website for custom_url
|
||||||
website = Website(name=url.netloc, protocol=protocol, account=account)
|
# Changed by daniel: hardcode target_server to web.pangea.lan, consider putting it into settings.py
|
||||||
|
tgt_server = Server.objects.get(name='web.pangea.lan')
|
||||||
|
website = Website(name=url.netloc, protocol=protocol, account=account, target_server=tgt_server)
|
||||||
full_clean(website)
|
full_clean(website)
|
||||||
try:
|
try:
|
||||||
validate_domain_protocol(website, domain, protocol)
|
validate_domain_protocol(website, domain, protocol)
|
||||||
|
@ -110,7 +113,8 @@ def create_or_update_directive(saas):
|
||||||
Domain = Website.domains.field.rel.to
|
Domain = Website.domains.field.rel.to
|
||||||
domain = Domain.objects.get(name=url.netloc)
|
domain = Domain.objects.get(name=url.netloc)
|
||||||
# Create new website for custom_url
|
# Create new website for custom_url
|
||||||
website = Website(name=url.netloc, protocol=protocol, account=account)
|
tgt_server = Server.objects.get(name='web.pangea.lan')
|
||||||
|
website = Website(name=url.netloc, protocol=protocol, account=account, target_server=tgt_server)
|
||||||
website.save()
|
website.save()
|
||||||
website.domains.add(domain)
|
website.domains.add(domain)
|
||||||
# get or create directive
|
# get or create directive
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
from django import forms
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from .. import settings
|
||||||
|
from .options import SoftwareService
|
||||||
|
|
||||||
|
|
||||||
|
class NextCloudService(SoftwareService):
|
||||||
|
name = 'nextcloud'
|
||||||
|
verbose_name = "nextCloud"
|
||||||
|
icon = 'orchestra/icons/apps/nextCloud.png'
|
||||||
|
site_domain = settings.SAAS_NEXTCLOUD_DOMAIN
|
|
@ -18,6 +18,7 @@ SAAS_ENABLED_SERVICES = Setting('SAAS_ENABLED_SERVICES',
|
||||||
'orchestra.contrib.saas.services.dokuwiki.DokuWikiService',
|
'orchestra.contrib.saas.services.dokuwiki.DokuWikiService',
|
||||||
'orchestra.contrib.saas.services.drupal.DrupalService',
|
'orchestra.contrib.saas.services.drupal.DrupalService',
|
||||||
'orchestra.contrib.saas.services.owncloud.OwnCloudService',
|
'orchestra.contrib.saas.services.owncloud.OwnCloudService',
|
||||||
|
'orchestra.contrib.saas.services.nextcloud.NextCloudService',
|
||||||
# 'orchestra.contrib.saas.services.seafile.SeaFileService',
|
# 'orchestra.contrib.saas.services.seafile.SeaFileService',
|
||||||
),
|
),
|
||||||
# lazy loading
|
# lazy loading
|
||||||
|
@ -235,6 +236,23 @@ SAAS_OWNCLOUD_LOG_PATH = Setting('SAAS_OWNCLOUD_LOG_PATH',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# nextCloud
|
||||||
|
SAAS_NEXTCLOUD_DOMAIN = Setting('SAAS_NEXTCLOUD_DOMAIN',
|
||||||
|
'nextcloud.{}'.format(ORCHESTRA_BASE_DOMAIN),
|
||||||
|
help_text="Uses <tt>ORCHESTRA_BASE_DOMAIN</tt> by default.",
|
||||||
|
)
|
||||||
|
|
||||||
|
SAAS_NEXTCLOUD_API_URL = Setting('SAAS_NEXTCLOUD_API_URL',
|
||||||
|
'https://admin:secret@nextcloud.{}/ocs/v1.php/cloud'.format(ORCHESTRA_BASE_DOMAIN),
|
||||||
|
)
|
||||||
|
|
||||||
|
SAAS_NEXTCLOUD_LOG_PATH = Setting('SAAS_NEXTCLOUD_LOG_PATH',
|
||||||
|
'',
|
||||||
|
help_text=_('Filesystem path for the webserver access logs.<br>'
|
||||||
|
'<tt>LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Host}i\"" host</tt>'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# BSCW
|
# BSCW
|
||||||
|
|
||||||
SAAS_BSCW_DOMAIN = Setting('SAAS_BSCW_DOMAIN',
|
SAAS_BSCW_DOMAIN = Setting('SAAS_BSCW_DOMAIN',
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-05-28 18:05
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('services', '0005_auto_20160427_1531'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='service',
|
||||||
|
name='rate_algorithm',
|
||||||
|
field=models.CharField(choices=[('orchestra.contrib.plans.ratings.best_price', 'Best price'), ('orchestra.contrib.plans.ratings.step_price', 'Step price'), ('orchestra.contrib.plans.ratings.match_price', 'Match price')], default='orchestra.contrib.plans.ratings.step_price', help_text='Algorithm used to interprete the rating table.<br> Best price: Produces the best possible price given all active rating lines (those with quantity lower or equal to the metric).<br> Step price: All rates with a quantity lower or equal than the metric are applied. Nominal price will be used when initial block is missing.<br> Match price: Only <b>the rate</b> with a) inmediate inferior metric and b) lower price is applied. Nominal price will be used when initial block is missing.', max_length=64, verbose_name='rate algorithm'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-05-28 18:11
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('services', '0006_auto_20170528_2005'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='service',
|
||||||
|
name='rate_algorithm',
|
||||||
|
field=models.CharField(choices=[('orchestra.contrib.plans.ratings.step_price', 'Step price'), ('orchestra.contrib.plans.ratings.best_price', 'Best price'), ('orchestra.contrib.plans.ratings.match_price', 'Match price')], default='orchestra.contrib.plans.ratings.step_price', help_text='Algorithm used to interprete the rating table.<br> Step price: All rates with a quantity lower or equal than the metric are applied. Nominal price will be used when initial block is missing.<br> Best price: Produces the best possible price given all active rating lines (those with quantity lower or equal to the metric).<br> Match price: Only <b>the rate</b> with a) inmediate inferior metric and b) lower price is applied. Nominal price will be used when initial block is missing.', max_length=64, verbose_name='rate algorithm'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-06-25 16:13
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('services', '0007_auto_20170528_2011'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='service',
|
||||||
|
name='rate_algorithm',
|
||||||
|
field=models.CharField(choices=[('orchestra.contrib.plans.ratings.best_price', 'Best price'), ('orchestra.contrib.plans.ratings.step_price', 'Step price'), ('orchestra.contrib.plans.ratings.match_price', 'Match price')], default='orchestra.contrib.plans.ratings.step_price', help_text='Algorithm used to interprete the rating table.<br> Best price: Produces the best possible price given all active rating lines (those with quantity lower or equal to the metric).<br> Step price: All rates with a quantity lower or equal than the metric are applied. Nominal price will be used when initial block is missing.<br> Match price: Only <b>the rate</b> with a) inmediate inferior metric and b) lower price is applied. Nominal price will be used when initial block is missing.', max_length=64, verbose_name='rate algorithm'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-06-25 16:40
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('services', '0008_auto_20170625_1813'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='service',
|
||||||
|
name='rate_algorithm',
|
||||||
|
field=models.CharField(choices=[('orchestra.contrib.plans.ratings.match_price', 'Match price'), ('orchestra.contrib.plans.ratings.best_price', 'Best price'), ('orchestra.contrib.plans.ratings.step_price', 'Step price')], default='orchestra.contrib.plans.ratings.step_price', help_text='Algorithm used to interprete the rating table.<br> Match price: Only <b>the rate</b> with a) inmediate inferior metric and b) lower price is applied. Nominal price will be used when initial block is missing.<br> Best price: Produces the best possible price given all active rating lines (those with quantity lower or equal to the metric).<br> Step price: All rates with a quantity lower or equal than the metric are applied. Nominal price will be used when initial block is missing.', max_length=64, verbose_name='rate algorithm'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-06-25 16:40
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('services', '0009_auto_20170625_1840'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='service',
|
||||||
|
name='rate_algorithm',
|
||||||
|
field=models.CharField(choices=[('orchestra.contrib.plans.ratings.best_price', 'Best price'), ('orchestra.contrib.plans.ratings.match_price', 'Match price'), ('orchestra.contrib.plans.ratings.step_price', 'Step price')], default='orchestra.contrib.plans.ratings.step_price', help_text='Algorithm used to interprete the rating table.<br> Best price: Produces the best possible price given all active rating lines (those with quantity lower or equal to the metric).<br> Match price: Only <b>the rate</b> with a) inmediate inferior metric and b) lower price is applied. Nominal price will be used when initial block is missing.<br> Step price: All rates with a quantity lower or equal than the metric are applied. Nominal price will be used when initial block is missing.', max_length=64, verbose_name='rate algorithm'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-06-25 16:40
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('services', '0010_auto_20170625_1840'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='service',
|
||||||
|
name='rate_algorithm',
|
||||||
|
field=models.CharField(choices=[('orchestra.contrib.plans.ratings.match_price', 'Match price'), ('orchestra.contrib.plans.ratings.best_price', 'Best price'), ('orchestra.contrib.plans.ratings.step_price', 'Step price')], default='orchestra.contrib.plans.ratings.step_price', help_text='Algorithm used to interprete the rating table.<br> Match price: Only <b>the rate</b> with a) inmediate inferior metric and b) lower price is applied. Nominal price will be used when initial block is missing.<br> Best price: Produces the best possible price given all active rating lines (those with quantity lower or equal to the metric).<br> Step price: All rates with a quantity lower or equal than the metric are applied. Nominal price will be used when initial block is missing.', max_length=64, verbose_name='rate algorithm'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-06-25 16:41
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('services', '0011_auto_20170625_1840'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='service',
|
||||||
|
name='rate_algorithm',
|
||||||
|
field=models.CharField(choices=[('orchestra.contrib.plans.ratings.best_price', 'Best price'), ('orchestra.contrib.plans.ratings.step_price', 'Step price'), ('orchestra.contrib.plans.ratings.match_price', 'Match price')], default='orchestra.contrib.plans.ratings.step_price', help_text='Algorithm used to interprete the rating table.<br> Best price: Produces the best possible price given all active rating lines (those with quantity lower or equal to the metric).<br> Step price: All rates with a quantity lower or equal than the metric are applied. Nominal price will be used when initial block is missing.<br> Match price: Only <b>the rate</b> with a) inmediate inferior metric and b) lower price is applied. Nominal price will be used when initial block is missing.', max_length=64, verbose_name='rate algorithm'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2019-08-05 09:34
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('services', '0012_auto_20170625_1841'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='service',
|
||||||
|
name='rate_algorithm',
|
||||||
|
field=models.CharField(choices=[('orchestra.contrib.plans.ratings.match_price', 'Match price'), ('orchestra.contrib.plans.ratings.best_price', 'Best price'), ('orchestra.contrib.plans.ratings.step_price', 'Step price')], default='orchestra.contrib.plans.ratings.step_price', help_text='Algorithm used to interprete the rating table.<br> Match price: Only <b>the rate</b> with a) inmediate inferior metric and b) lower price is applied. Nominal price will be used when initial block is missing.<br> Best price: Produces the best possible price given all active rating lines (those with quantity lower or equal to the metric).<br> Step price: All rates with a quantity lower or equal than the metric are applied. Nominal price will be used when initial block is missing.', max_length=64, verbose_name='rate algorithm'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2020-02-04 11:18
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('services', '0013_auto_20190805_1134'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='service',
|
||||||
|
name='rate_algorithm',
|
||||||
|
field=models.CharField(choices=[('orchestra.contrib.plans.ratings.best_price', 'Best price'), ('orchestra.contrib.plans.ratings.match_price', 'Match price'), ('orchestra.contrib.plans.ratings.step_price', 'Step price')], default='orchestra.contrib.plans.ratings.step_price', help_text='Algorithm used to interprete the rating table.<br> Best price: Produces the best possible price given all active rating lines (those with quantity lower or equal to the metric).<br> Match price: Only <b>the rate</b> with a) inmediate inferior metric and b) lower price is applied. Nominal price will be used when initial block is missing.<br> Step price: All rates with a quantity lower or equal than the metric are applied. Nominal price will be used when initial block is missing.', max_length=64, verbose_name='rate algorithm'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -28,6 +28,14 @@ class UNIXUserController(ServiceController):
|
||||||
context = self.get_context(user)
|
context = self.get_context(user)
|
||||||
if not context['user']:
|
if not context['user']:
|
||||||
return
|
return
|
||||||
|
if not user.active:
|
||||||
|
self.append(textwrap.dedent("""
|
||||||
|
#Just disable that user, if it exists
|
||||||
|
if id %(user)s ; then
|
||||||
|
usermod %(user)s --password '%(password)s'
|
||||||
|
fi
|
||||||
|
""") % context)
|
||||||
|
return
|
||||||
# TODO userd add will fail if %(user)s group already exists
|
# TODO userd add will fail if %(user)s group already exists
|
||||||
self.append(textwrap.dedent("""
|
self.append(textwrap.dedent("""
|
||||||
# Update/create user state for %(user)s
|
# Update/create user state for %(user)s
|
||||||
|
@ -61,7 +69,8 @@ class UNIXUserController(ServiceController):
|
||||||
if context['home'] != context['base_home']:
|
if context['home'] != context['base_home']:
|
||||||
self.append(textwrap.dedent("""\
|
self.append(textwrap.dedent("""\
|
||||||
# Set extra permissions: %(user)s home is inside %(mainuser)s home
|
# Set extra permissions: %(user)s home is inside %(mainuser)s home
|
||||||
if mount | grep "^$(df %(home)s|grep '^/'|cut -d' ' -f1)\s" | grep acl > /dev/null; then
|
if true; then
|
||||||
|
# if mount | grep "^$(df %(home)s|grep '^/'|cut -d' ' -f1)\s" | grep acl > /dev/null; then
|
||||||
# Account group as the owner
|
# Account group as the owner
|
||||||
chown %(mainuser)s:%(mainuser)s '%(home)s'
|
chown %(mainuser)s:%(mainuser)s '%(home)s'
|
||||||
chmod g+s '%(home)s'
|
chmod g+s '%(home)s'
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-05-28 18:11
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import orchestra.core.validators
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('systemusers', '0002_auto_20150429_1413'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='systemuser',
|
||||||
|
name='shell',
|
||||||
|
field=models.CharField(choices=[('/dev/null', 'No shell, FTP only'), ('/bin/rssh', 'No shell, SFTP/RSYNC only'), ('/usr/bin/git-shell', 'No shell, GIT only'), ('/bin/bash', '/bin/bash')], default='/dev/null', max_length=32, verbose_name='shell'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='systemuser',
|
||||||
|
name='username',
|
||||||
|
field=models.CharField(help_text='Required. 32 characters or fewer. Letters, digits and ./-/_ only.', max_length=32, unique=True, validators=[orchestra.core.validators.validate_username], verbose_name='username'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -88,7 +88,7 @@ class SystemUser(models.Model):
|
||||||
self.save(update_fields=('is_active',))
|
self.save(update_fields=('is_active',))
|
||||||
|
|
||||||
def enable(self):
|
def enable(self):
|
||||||
self.is_active = False
|
self.is_active = True
|
||||||
self.save(update_fields=('is_active',))
|
self.save(update_fields=('is_active',))
|
||||||
|
|
||||||
def get_description(self):
|
def get_description(self):
|
||||||
|
|
|
@ -133,3 +133,22 @@ class ProxmoxOpenVZTraffic(ServiceMonitor):
|
||||||
'object_id': vps.id,
|
'object_id': vps.id,
|
||||||
'hostname': vps.hostname,
|
'hostname': vps.hostname,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class LxcController(ServiceController):
|
||||||
|
model = 'vps.VPS'
|
||||||
|
|
||||||
|
RESOURCES = (
|
||||||
|
('memory', 'mem'),
|
||||||
|
('disk', 'disk'),
|
||||||
|
('vcpu', 'vcpu')
|
||||||
|
)
|
||||||
|
|
||||||
|
def prepare(self):
|
||||||
|
super(LxcController, self).prepare()
|
||||||
|
|
||||||
|
def save(self, vps):
|
||||||
|
# TODO create the container
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-05-28 18:05
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('vps', '0003_vps_is_active'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='vps',
|
||||||
|
name='template',
|
||||||
|
field=models.CharField(choices=[('debian7', 'Debian 7 - Wheezy'), ('placeholder', 'LXC placeholder')], default='placeholder', help_text='Initial template.', max_length=64, verbose_name='template'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='vps',
|
||||||
|
name='type',
|
||||||
|
field=models.CharField(choices=[('openvz', 'OpenVZ container'), ('lxc', 'LXC container')], default='lxc', max_length=64, verbose_name='type'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -4,13 +4,14 @@ from orchestra.contrib.settings import Setting
|
||||||
VPS_TYPES = Setting('VPS_TYPES',
|
VPS_TYPES = Setting('VPS_TYPES',
|
||||||
(
|
(
|
||||||
('openvz', 'OpenVZ container'),
|
('openvz', 'OpenVZ container'),
|
||||||
|
('lxc', 'LXC container')
|
||||||
),
|
),
|
||||||
validators=[Setting.validate_choices]
|
validators=[Setting.validate_choices]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
VPS_DEFAULT_TYPE = Setting('VPS_DEFAULT_TYPE',
|
VPS_DEFAULT_TYPE = Setting('VPS_DEFAULT_TYPE',
|
||||||
'openvz',
|
'lxc',
|
||||||
choices=VPS_TYPES
|
choices=VPS_TYPES
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,13 +19,14 @@ VPS_DEFAULT_TYPE = Setting('VPS_DEFAULT_TYPE',
|
||||||
VPS_TEMPLATES = Setting('VPS_TEMPLATES',
|
VPS_TEMPLATES = Setting('VPS_TEMPLATES',
|
||||||
(
|
(
|
||||||
('debian7', 'Debian 7 - Wheezy'),
|
('debian7', 'Debian 7 - Wheezy'),
|
||||||
|
('placeholder', 'LXC placeholder')
|
||||||
),
|
),
|
||||||
validators=[Setting.validate_choices]
|
validators=[Setting.validate_choices]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
VPS_DEFAULT_TEMPLATE = Setting('VPS_DEFAULT_TEMPLATE',
|
VPS_DEFAULT_TEMPLATE = Setting('VPS_DEFAULT_TEMPLATE',
|
||||||
'debian7',
|
'placeholder',
|
||||||
choices=VPS_TEMPLATES
|
choices=VPS_TEMPLATES
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ class PHPController(WebAppServiceMixin, ServiceController):
|
||||||
))
|
))
|
||||||
|
|
||||||
def save(self, webapp):
|
def save(self, webapp):
|
||||||
|
self.delete_old_config(webapp)
|
||||||
context = self.get_context(webapp)
|
context = self.get_context(webapp)
|
||||||
self.create_webapp_dir(context)
|
self.create_webapp_dir(context)
|
||||||
if webapp.type_instance.is_fpm:
|
if webapp.type_instance.is_fpm:
|
||||||
|
@ -40,11 +41,32 @@ class PHPController(WebAppServiceMixin, ServiceController):
|
||||||
self.save_fcgid(webapp, context)
|
self.save_fcgid(webapp, context)
|
||||||
else:
|
else:
|
||||||
raise TypeError("Unknown PHP execution type")
|
raise TypeError("Unknown PHP execution type")
|
||||||
self.append("# Clean non-used PHP FCGID wrappers and FPM pools")
|
# LEGACY CLEANUP FUNCTIONS. TODO REMOVE WHEN SURE NOT NEEDED.
|
||||||
self.delete_fcgid(webapp, context, preserve=True)
|
# self.delete_fcgid(webapp, context, preserve=True)
|
||||||
self.delete_fpm(webapp, context, preserve=True)
|
# self.delete_fpm(webapp, context, preserve=True)
|
||||||
self.set_under_construction(context)
|
self.set_under_construction(context)
|
||||||
|
|
||||||
|
def delete_config(self,webapp):
|
||||||
|
context = self.get_context(webapp)
|
||||||
|
to_delete = []
|
||||||
|
if webapp.type_instance.is_fpm:
|
||||||
|
to_delete.append(settings.WEBAPPS_PHPFPM_POOL_PATH % context)
|
||||||
|
to_delete.append(settings.WEBAPPS_FPM_LISTEN % context)
|
||||||
|
elif webapp.type_instance.is_fcgid:
|
||||||
|
to_delete.append(settings.WEBAPPS_FCGID_WRAPPER_PATH % context)
|
||||||
|
to_delete.append(settings.WEBAPPS_FCGID_CMD_OPTIONS_PATH % context)
|
||||||
|
for item in to_delete:
|
||||||
|
self.append('rm -f "{}"'.format(item))
|
||||||
|
|
||||||
|
def delete_old_config(self,webapp):
|
||||||
|
# Check if we loaded the old version of the webapp. If so, we're updating
|
||||||
|
# rather than creating, so we must make sure the old config files are removed.
|
||||||
|
if hasattr(webapp, '_old_self'):
|
||||||
|
self.append("# Clean old configuration files")
|
||||||
|
self.delete_config(webapp._old_self)
|
||||||
|
else:
|
||||||
|
self.append("# No old config files to delete")
|
||||||
|
|
||||||
def save_fpm(self, webapp, context):
|
def save_fpm(self, webapp, context):
|
||||||
self.append(textwrap.dedent("""
|
self.append(textwrap.dedent("""
|
||||||
# Generate FPM configuration
|
# Generate FPM configuration
|
||||||
|
@ -99,10 +121,11 @@ class PHPController(WebAppServiceMixin, ServiceController):
|
||||||
|
|
||||||
def delete(self, webapp):
|
def delete(self, webapp):
|
||||||
context = self.get_context(webapp)
|
context = self.get_context(webapp)
|
||||||
if webapp.type_instance.is_fpm:
|
self.delete_old_config(webapp)
|
||||||
self.delete_fpm(webapp, context)
|
# if webapp.type_instance.is_fpm:
|
||||||
elif webapp.type_instance.is_fcgid:
|
# self.delete_fpm(webapp, context)
|
||||||
self.delete_fcgid(webapp, context)
|
# elif webapp.type_instance.is_fcgid:
|
||||||
|
# self.delete_fcgid(webapp, context)
|
||||||
self.delete_webapp_dir(context)
|
self.delete_webapp_dir(context)
|
||||||
|
|
||||||
def has_sibilings(self, webapp, context):
|
def has_sibilings(self, webapp, context):
|
||||||
|
@ -205,7 +228,7 @@ class PHPController(WebAppServiceMixin, ServiceController):
|
||||||
context['fpm_listen'] = webapp.type_instance.FPM_LISTEN % context
|
context['fpm_listen'] = webapp.type_instance.FPM_LISTEN % context
|
||||||
fpm_config = Template(textwrap.dedent("""\
|
fpm_config = Template(textwrap.dedent("""\
|
||||||
;; {{ banner }}
|
;; {{ banner }}
|
||||||
[{{ user }}]
|
[{{ user }}-{{app_name}}]
|
||||||
user = {{ user }}
|
user = {{ user }}
|
||||||
group = {{ group }}
|
group = {{ group }}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-05-28 18:11
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('webapps', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='webapp',
|
||||||
|
name='type',
|
||||||
|
field=models.CharField(choices=[('moodle-php', 'Moodle'), ('php', 'PHP'), ('python', 'Python'), ('static', 'Static'), ('symbolic-link', 'Symbolic link'), ('webalizer', 'Webalizer'), ('wordpress-php', 'WordPress')], max_length=32, verbose_name='type'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='webappoption',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(choices=[(None, '-------'), ('FileSystem', [('public-root', 'Public root')]), ('Process', [('timeout', 'Process timeout'), ('processes', 'Number of processes')]), ('PHP', [('enable_functions', 'Enable functions'), ('disable_functions', 'Disable functions'), ('allow_url_include', 'Allow URL include'), ('allow_url_fopen', 'Allow URL fopen'), ('auto_append_file', 'Auto append file'), ('auto_prepend_file', 'Auto prepend file'), ('date.timezone', 'date.timezone'), ('default_socket_timeout', 'Default socket timeout'), ('display_errors', 'Display errors'), ('extension', 'Extension'), ('include_path', 'Include path'), ('magic_quotes_gpc', 'Magic quotes GPC'), ('magic_quotes_runtime', 'Magic quotes runtime'), ('magic_quotes_sybase', 'Magic quotes sybase'), ('max_input_time', 'Max input time'), ('max_input_vars', 'Max input vars'), ('memory_limit', 'Memory limit'), ('mysql.connect_timeout', 'Mysql connect timeout'), ('output_buffering', 'Output buffering'), ('register_globals', 'Register globals'), ('post_max_size', 'Post max size'), ('sendmail_path', 'Sendmail path'), ('session.bug_compat_warn', 'Session bug compat warning'), ('session.auto_start', 'Session auto start'), ('safe_mode', 'Safe mode'), ('suhosin.post.max_vars', 'Suhosin POST max vars'), ('suhosin.get.max_vars', 'Suhosin GET max vars'), ('suhosin.request.max_vars', 'Suhosin request max vars'), ('suhosin.session.encrypt', 'Suhosin session encrypt'), ('suhosin.simulation', 'Suhosin simulation'), ('suhosin.executor.include.whitelist', 'Suhosin executor include whitelist'), ('upload_max_filesize', 'Upload max filesize'), ('upload_tmp_dir', 'Upload tmp dir'), ('zend_extension', 'Zend extension')])], max_length=128, verbose_name='name'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,23 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-07-04 08:49
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('orchestration', '0007_auto_20170528_2011'),
|
||||||
|
('webapps', '0002_auto_20170528_2011'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='webapp',
|
||||||
|
name='target_server',
|
||||||
|
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='webapps', to='orchestration.Server', verbose_name='Target Server'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,21 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2020-02-04 11:17
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('webapps', '0003_webapp_target_server'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='webapp',
|
||||||
|
name='comments',
|
||||||
|
field=models.TextField(default=''),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2020-02-04 11:18
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('webapps', '0004_webapp_comments'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='webapp',
|
||||||
|
name='comments',
|
||||||
|
field=models.TextField(default=''),
|
||||||
|
),
|
||||||
|
]
|
|
@ -23,6 +23,9 @@ class WebApp(models.Model):
|
||||||
related_name='webapps')
|
related_name='webapps')
|
||||||
data = JSONField(_("data"), blank=True, default={},
|
data = JSONField(_("data"), blank=True, default={},
|
||||||
help_text=_("Extra information dependent of each service."))
|
help_text=_("Extra information dependent of each service."))
|
||||||
|
target_server = models.ForeignKey('orchestration.Server', verbose_name=_("Target Server"),
|
||||||
|
related_name='webapps')
|
||||||
|
comments = models.TextField(default="", blank=True)
|
||||||
|
|
||||||
# CMS webapps usually need a database and dbuser, with these virtual fields we tell the ORM to delete them
|
# CMS webapps usually need a database and dbuser, with these virtual fields we tell the ORM to delete them
|
||||||
databases = VirtualDatabaseRelation('databases.Database')
|
databases = VirtualDatabaseRelation('databases.Database')
|
||||||
|
|
|
@ -102,8 +102,8 @@ class Processes(AppOption):
|
||||||
# FCGID MaxProcesses
|
# FCGID MaxProcesses
|
||||||
# FPM pm.max_children
|
# FPM pm.max_children
|
||||||
verbose_name = _("Number of processes")
|
verbose_name = _("Number of processes")
|
||||||
help_text = _("Maximum number of children that can be alive at the same time (a number between 0 and 9).")
|
help_text = _("Maximum number of children that can be alive at the same time (a number between 0 and 99).")
|
||||||
regex = r'^[0-9]{1,2}$'
|
regex = r'^[0-9]{1,3}$'
|
||||||
group = AppOption.PROCESS
|
group = AppOption.PROCESS
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ class WebAppSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = WebApp
|
model = WebApp
|
||||||
fields = ('url', 'id', 'name', 'type', 'options', 'data')
|
fields = ('url', 'id', 'name', 'type', 'options', 'data',)
|
||||||
postonly_fields = ('name', 'type')
|
postonly_fields = ('name', 'type')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
|
@ -99,6 +99,7 @@ WEBAPPS_PHP_VERSIONS = Setting('WEBAPPS_PHP_VERSIONS', (
|
||||||
('5.3-cgi', 'PHP 5.3 FCGID'),
|
('5.3-cgi', 'PHP 5.3 FCGID'),
|
||||||
('5.2-cgi', 'PHP 5.2 FCGID'),
|
('5.2-cgi', 'PHP 5.2 FCGID'),
|
||||||
('4-cgi', 'PHP 4 FCGID'),
|
('4-cgi', 'PHP 4 FCGID'),
|
||||||
|
('7-fpm', 'PHP 7 FPM')
|
||||||
),
|
),
|
||||||
help_text="Execution modle choose by ending -fpm or -cgi.",
|
help_text="Execution modle choose by ending -fpm or -cgi.",
|
||||||
validators=[Setting.validate_choices]
|
validators=[Setting.validate_choices]
|
||||||
|
|
|
@ -3,19 +3,22 @@ from django.dispatch import receiver
|
||||||
|
|
||||||
from .models import WebApp
|
from .models import WebApp
|
||||||
|
|
||||||
|
|
||||||
# Admin bulk deletion doesn't call model.delete()
|
# Admin bulk deletion doesn't call model.delete()
|
||||||
# So, signals are used instead of model method overriding
|
# So, signals are used instead of model method overriding
|
||||||
|
|
||||||
@receiver(pre_save, sender=WebApp, dispatch_uid='webapps.type.save')
|
@receiver(pre_save, sender=WebApp, dispatch_uid='webapps.type.save')
|
||||||
def type_save(sender, *args, **kwargs):
|
def type_save(sender, *args, **kwargs):
|
||||||
instance = kwargs['instance']
|
instance = kwargs['instance']
|
||||||
|
# Since a webapp might need to cleanup its old config files, the data
|
||||||
|
# from the OLD VERSION of the webapp is needed.
|
||||||
|
if instance.pk:
|
||||||
|
instance._old_self = type(instance).objects.get(id=instance.pk)
|
||||||
instance.type_instance.save()
|
instance.type_instance.save()
|
||||||
|
|
||||||
|
|
||||||
@receiver(pre_delete, sender=WebApp, dispatch_uid='webapps.type.delete')
|
@receiver(pre_delete, sender=WebApp, dispatch_uid='webapps.type.delete')
|
||||||
def type_delete(sender, *args, **kwargs):
|
def type_delete(sender, *args, **kwargs):
|
||||||
instance = kwargs['instance']
|
instance = kwargs['instance']
|
||||||
|
instance._old_self = type(instance).objects.get(id=instance.pk)
|
||||||
try:
|
try:
|
||||||
instance.type_instance.delete()
|
instance.type_instance.delete()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|
|
@ -69,7 +69,7 @@ class WebsiteAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, {
|
(None, {
|
||||||
'classes': ('extrapretty',),
|
'classes': ('extrapretty',),
|
||||||
'fields': ('account_link', 'name', 'protocol', 'domains', 'is_active'),
|
'fields': ('account_link', 'name', 'protocol', 'target_server', 'domains', 'is_active', 'comments'),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
form = WebsiteAdminForm
|
form = WebsiteAdminForm
|
||||||
|
|
|
@ -58,7 +58,8 @@ class Apache2Controller(ServiceController):
|
||||||
context.update({
|
context.update({
|
||||||
'port': self.HTTPS_PORT if ssl else self.HTTP_PORT,
|
'port': self.HTTPS_PORT if ssl else self.HTTP_PORT,
|
||||||
'vhost_set_fcgid': False,
|
'vhost_set_fcgid': False,
|
||||||
'server_alias_lines': ' \\\n '.join(context['server_alias'])
|
'server_alias_lines': ' \\\n '.join(context['server_alias']),
|
||||||
|
'suexec_needed': site.target_server == 'web.pangea.lan'
|
||||||
})
|
})
|
||||||
context['extra_conf'] = self.get_extra_conf(site, context, ssl)
|
context['extra_conf'] = self.get_extra_conf(site, context, ssl)
|
||||||
return Template(textwrap.dedent("""\
|
return Template(textwrap.dedent("""\
|
||||||
|
@ -71,7 +72,8 @@ class Apache2Controller(ServiceController):
|
||||||
CustomLog {{ access_log }} common{% endif %}\
|
CustomLog {{ access_log }} common{% endif %}\
|
||||||
{% if error_log %}
|
{% if error_log %}
|
||||||
ErrorLog {{ error_log }}{% endif %}
|
ErrorLog {{ error_log }}{% endif %}
|
||||||
SuexecUserGroup {{ user }} {{ group }}\
|
{% if suexec_needed %}
|
||||||
|
SuexecUserGroup {{ user }} {{ group }}{% endif %}\
|
||||||
{% for line in extra_conf.splitlines %}
|
{% for line in extra_conf.splitlines %}
|
||||||
{{ line | safe }}{% endfor %}
|
{{ line | safe }}{% endfor %}
|
||||||
</VirtualHost>
|
</VirtualHost>
|
||||||
|
@ -225,15 +227,18 @@ class Apache2Controller(ServiceController):
|
||||||
target = 'fcgi://%(socket)s%(app_path)s/$1'
|
target = 'fcgi://%(socket)s%(app_path)s/$1'
|
||||||
else:
|
else:
|
||||||
# UNIX socket
|
# UNIX socket
|
||||||
target = 'unix:%(socket)s|fcgi://127.0.0.1%(app_path)s/'
|
target = 'unix:%(socket)s|fcgi://127.0.0.1/'
|
||||||
if context['location']:
|
|
||||||
# FIXME unix sockets do not support $1
|
|
||||||
target = 'unix:%(socket)s|fcgi://127.0.0.1%(app_path)s/$1'
|
|
||||||
context.update({
|
context.update({
|
||||||
'app_path': os.path.normpath(app_path),
|
'app_path': os.path.normpath(app_path),
|
||||||
'socket': socket,
|
'socket': socket,
|
||||||
})
|
})
|
||||||
directives = "ProxyPassMatch ^%(location)s/(.*\.php(/.*)?)$ {target}\n".format(target=target) % context
|
directives = textwrap.dedent("""
|
||||||
|
<Directory {app_path}>
|
||||||
|
<FilesMatch "\.php$">
|
||||||
|
SetHandler "proxy:unix:{socket}|fcgi://127.0.0.1"
|
||||||
|
</FilesMatch>
|
||||||
|
</Directory>
|
||||||
|
""").format(socket=socket, app_path=app_path)
|
||||||
directives += self.get_location_filesystem_map(context)
|
directives += self.get_location_filesystem_map(context)
|
||||||
return [
|
return [
|
||||||
(context['location'], directives),
|
(context['location'], directives),
|
||||||
|
@ -286,7 +291,8 @@ class Apache2Controller(ServiceController):
|
||||||
if not (cert and key):
|
if not (cert and key):
|
||||||
cert = [settings.WEBSITES_DEFAULT_SSL_CERT]
|
cert = [settings.WEBSITES_DEFAULT_SSL_CERT]
|
||||||
key = [settings.WEBSITES_DEFAULT_SSL_KEY]
|
key = [settings.WEBSITES_DEFAULT_SSL_KEY]
|
||||||
ca = [settings.WEBSITES_DEFAULT_SSL_CA]
|
# Disabled because since the migration to LE, CA is not required here
|
||||||
|
#ca = [settings.WEBSITES_DEFAULT_SSL_CA]
|
||||||
if not (cert and key):
|
if not (cert and key):
|
||||||
return []
|
return []
|
||||||
ssl_config = [
|
ssl_config = [
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-05-28 18:11
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('websites', '0002_auto_20160219_1036'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='websitedirective',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(choices=[(None, '-------'), ('SaaS', [('wordpress-saas', 'WordPress SaaS'), ('dokuwiki-saas', 'DokuWiki SaaS'), ('drupal-saas', 'Drupdal SaaS'), ('moodle-saas', 'Moodle SaaS')]), ('ModSecurity', [('sec-rule-remove', 'SecRuleRemoveById'), ('sec-engine', 'SecRuleEngine Off')]), ('SSL', [('ssl-ca', 'SSL CA'), ('ssl-cert', 'SSL cert'), ('ssl-key', 'SSL key')]), ('HTTPD', [('redirect', 'Redirection'), ('proxy', 'Proxy'), ('error-document', 'ErrorDocumentRoot')])], db_index=True, max_length=128, verbose_name='name'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='websitedirective',
|
||||||
|
name='value',
|
||||||
|
field=models.CharField(blank=True, max_length=256, verbose_name='value'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-06-25 16:13
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('websites', '0003_auto_20170528_2011'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='websitedirective',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(choices=[(None, '-------'), ('ModSecurity', [('sec-rule-remove', 'SecRuleRemoveById'), ('sec-engine', 'SecRuleEngine Off')]), ('HTTPD', [('redirect', 'Redirection'), ('proxy', 'Proxy'), ('error-document', 'ErrorDocumentRoot')]), ('SSL', [('ssl-ca', 'SSL CA'), ('ssl-cert', 'SSL cert'), ('ssl-key', 'SSL key')]), ('SaaS', [('wordpress-saas', 'WordPress SaaS'), ('dokuwiki-saas', 'DokuWiki SaaS'), ('drupal-saas', 'Drupdal SaaS'), ('moodle-saas', 'Moodle SaaS')])], db_index=True, max_length=128, verbose_name='name'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-06-25 16:40
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('websites', '0004_auto_20170625_1813'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='websitedirective',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(choices=[(None, '-------'), ('SSL', [('ssl-ca', 'SSL CA'), ('ssl-cert', 'SSL cert'), ('ssl-key', 'SSL key')]), ('ModSecurity', [('sec-rule-remove', 'SecRuleRemoveById'), ('sec-engine', 'SecRuleEngine Off')]), ('SaaS', [('wordpress-saas', 'WordPress SaaS'), ('dokuwiki-saas', 'DokuWiki SaaS'), ('drupal-saas', 'Drupdal SaaS'), ('moodle-saas', 'Moodle SaaS')]), ('HTTPD', [('redirect', 'Redirection'), ('proxy', 'Proxy'), ('error-document', 'ErrorDocumentRoot')])], db_index=True, max_length=128, verbose_name='name'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-06-25 16:40
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('websites', '0005_auto_20170625_1840'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='websitedirective',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(choices=[(None, '-------'), ('SSL', [('ssl-ca', 'SSL CA'), ('ssl-cert', 'SSL cert'), ('ssl-key', 'SSL key')]), ('HTTPD', [('redirect', 'Redirection'), ('proxy', 'Proxy'), ('error-document', 'ErrorDocumentRoot')]), ('ModSecurity', [('sec-rule-remove', 'SecRuleRemoveById'), ('sec-engine', 'SecRuleEngine Off')]), ('SaaS', [('wordpress-saas', 'WordPress SaaS'), ('dokuwiki-saas', 'DokuWiki SaaS'), ('drupal-saas', 'Drupdal SaaS'), ('moodle-saas', 'Moodle SaaS')])], db_index=True, max_length=128, verbose_name='name'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-06-25 16:40
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('websites', '0006_auto_20170625_1840'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='websitedirective',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(choices=[(None, '-------'), ('SaaS', [('wordpress-saas', 'WordPress SaaS'), ('dokuwiki-saas', 'DokuWiki SaaS'), ('drupal-saas', 'Drupdal SaaS'), ('moodle-saas', 'Moodle SaaS')]), ('ModSecurity', [('sec-rule-remove', 'SecRuleRemoveById'), ('sec-engine', 'SecRuleEngine Off')]), ('HTTPD', [('redirect', 'Redirection'), ('proxy', 'Proxy'), ('error-document', 'ErrorDocumentRoot')]), ('SSL', [('ssl-ca', 'SSL CA'), ('ssl-cert', 'SSL cert'), ('ssl-key', 'SSL key')])], db_index=True, max_length=128, verbose_name='name'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-06-25 16:41
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('websites', '0007_auto_20170625_1840'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='websitedirective',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(choices=[(None, '-------'), ('ModSecurity', [('sec-rule-remove', 'SecRuleRemoveById'), ('sec-engine', 'SecRuleEngine Off')]), ('SSL', [('ssl-ca', 'SSL CA'), ('ssl-cert', 'SSL cert'), ('ssl-key', 'SSL key')]), ('HTTPD', [('redirect', 'Redirection'), ('proxy', 'Proxy'), ('error-document', 'ErrorDocumentRoot')]), ('SaaS', [('wordpress-saas', 'WordPress SaaS'), ('dokuwiki-saas', 'DokuWiki SaaS'), ('drupal-saas', 'Drupdal SaaS'), ('moodle-saas', 'Moodle SaaS')])], db_index=True, max_length=128, verbose_name='name'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-06-25 20:06
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('websites', '0008_auto_20170625_1841'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='websitedirective',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(choices=[(None, '-------'), ('HTTPD', [('redirect', 'Redirection'), ('proxy', 'Proxy'), ('error-document', 'ErrorDocumentRoot')]), ('SaaS', [('wordpress-saas', 'WordPress SaaS'), ('dokuwiki-saas', 'DokuWiki SaaS'), ('drupal-saas', 'Drupdal SaaS'), ('moodle-saas', 'Moodle SaaS')]), ('SSL', [('ssl-ca', 'SSL CA'), ('ssl-cert', 'SSL cert'), ('ssl-key', 'SSL key')]), ('ModSecurity', [('sec-rule-remove', 'SecRuleRemoveById'), ('sec-engine', 'SecRuleEngine Off')])], db_index=True, max_length=128, verbose_name='name'),
|
||||||
|
),
|
||||||
|
]
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue