Latest Pangea source code.
This commit is contained in:
parent
49c84f13a8
commit
4606e86aaf
|
@ -9,7 +9,7 @@ class SetPasswordApiMixin(object):
|
|||
@detail_route(methods=['post'], serializer_class=SetPasswordSerializer)
|
||||
def set_password(self, request, pk):
|
||||
obj = self.get_object()
|
||||
data = request.DATA
|
||||
data = request.data
|
||||
if isinstance(data, str):
|
||||
data = {
|
||||
'password': data
|
||||
|
|
|
@ -98,7 +98,7 @@ class SetPasswordHyperlinkedSerializer(HyperlinkedModelSerializer):
|
|||
pass
|
||||
else:
|
||||
password = attrs.pop('password', None)
|
||||
attrs = super(SetPasswordSerializer, self).validate()
|
||||
attrs = super().validate(attrs)
|
||||
if password is not None:
|
||||
attrs['password'] = password
|
||||
return attrs
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import sys
|
||||
import textwrap
|
||||
|
||||
from django.contrib.auth import get_user_model, base_user
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.core.exceptions import FieldError
|
||||
from django.core.management import execute_from_command_line
|
||||
from django.db import models
|
||||
|
@ -19,14 +19,9 @@ def create_initial_superuser(**kwargs):
|
|||
)
|
||||
from ..models import Account
|
||||
try:
|
||||
Account.systemusers.field.model.objects.filter(account_id=1).exists()
|
||||
Account.systemusers.field.related.model.objects.filter(account_id=1).exists()
|
||||
except FieldError:
|
||||
# avoid creating a systemuser when systemuser table is not ready
|
||||
Account.save = models.Model.save
|
||||
old_init = base_user.AbstractBaseUser.__init__
|
||||
def remove_is_staff(*args, **kwargs):
|
||||
kwargs.pop('is_staff', None)
|
||||
old_init(*args, **kwargs)
|
||||
base_user.AbstractBaseUser.__init__ = remove_is_staff
|
||||
manager = sys.argv[0]
|
||||
execute_from_command_line(argv=[manager, 'createsuperuser'])
|
||||
|
|
|
@ -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 import Operation
|
||||
from orchestra.core import services
|
||||
from orchestra import core
|
||||
from orchestra.models.utils import has_db_field
|
||||
from orchestra.utils.mail import send_email_template
|
||||
|
||||
|
@ -98,7 +98,7 @@ class Account(auth.AbstractBaseUser):
|
|||
]
|
||||
for rel in related_fields:
|
||||
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():
|
||||
yield obj
|
||||
|
||||
|
@ -141,12 +141,25 @@ class Account(auth.AbstractBaseUser):
|
|||
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
|
||||
provided, permissions for this specific object are checked.
|
||||
applabel.action_modelname
|
||||
"""
|
||||
if not self.is_active:
|
||||
return False
|
||||
# Active superusers have all permissions.
|
||||
if self.is_active and self.is_superuser:
|
||||
if self.is_superuser:
|
||||
return True
|
||||
# Otherwise we need to check the backends.
|
||||
return auth._user_has_perm(self, perm, obj)
|
||||
app, action_model = perm.split('.')
|
||||
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):
|
||||
"""
|
||||
|
@ -167,8 +180,7 @@ class Account(auth.AbstractBaseUser):
|
|||
# Active superusers have all permissions.
|
||||
if self.is_active and self.is_superuser:
|
||||
return True
|
||||
return auth._user_has_module_perms(self, app_label)
|
||||
|
||||
|
||||
def get_related_passwords(self, db_field=False):
|
||||
related = [
|
||||
self.main_systemuser,
|
||||
|
|
|
@ -7,7 +7,7 @@ class AccountSerializer(serializers.HyperlinkedModelSerializer):
|
|||
class Meta:
|
||||
model = Account
|
||||
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'
|
||||
)
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ from orchestra.forms.widgets import paddingCheckboxSelectMultiple
|
|||
from . import settings, actions
|
||||
from .filters import (BillTypeListFilter, HasBillContactListFilter, TotalListFilter,
|
||||
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)
|
||||
|
||||
|
||||
|
@ -461,6 +461,7 @@ class BillAdmin(BillAdminMixin, ExtendedModelAdmin):
|
|||
admin.site.register(Bill, BillAdmin)
|
||||
admin.site.register(Invoice, BillAdmin)
|
||||
admin.site.register(AmendmentInvoice, BillAdmin)
|
||||
admin.site.register(AbonoInvoice, BillAdmin)
|
||||
admin.site.register(Fee, BillAdmin)
|
||||
admin.site.register(AmendmentFee, BillAdmin)
|
||||
admin.site.register(ProForma, BillAdmin)
|
||||
|
|
Binary file not shown.
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
@ -18,33 +18,33 @@ msgstr ""
|
|||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: actions.py:31
|
||||
#: actions.py:33
|
||||
msgid "View"
|
||||
msgstr "Vista"
|
||||
|
||||
#: actions.py:42
|
||||
#: actions.py:45
|
||||
msgid "Selected bills should be in open state"
|
||||
msgstr "Les factures seleccionades han d'estar en estat obert"
|
||||
|
||||
#: actions.py:57
|
||||
#: actions.py:60
|
||||
msgid "Selected bills have been closed"
|
||||
msgstr "Les factures seleccionades han estat tancades"
|
||||
|
||||
#: actions.py:70
|
||||
#: actions.py:73
|
||||
#, python-format
|
||||
msgid "<a href=\"%(url)s\">One related transaction</a> has been created"
|
||||
msgstr "S'ha creat una <a href=\"%(url)s\">transacció</a>"
|
||||
|
||||
#: actions.py:71
|
||||
#: actions.py:74
|
||||
#, python-format
|
||||
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>"
|
||||
|
||||
#: actions.py:77
|
||||
#: actions.py:80
|
||||
msgid "Are you sure about closing the following bills?"
|
||||
msgstr "Estàs a punt de tancar les següents factures, estàs segur?"
|
||||
|
||||
#: actions.py:78
|
||||
#: actions.py:81
|
||||
msgid ""
|
||||
"Once a bill is closed it can not be further modified.</p><p>Please select a "
|
||||
"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 "
|
||||
"plau selecciona un mètode de pagament per les factures seleccionades"
|
||||
|
||||
#: actions.py:91
|
||||
#: actions.py:97
|
||||
msgid "Close"
|
||||
msgstr "Tanca"
|
||||
|
||||
#: actions.py:109
|
||||
#: actions.py:115
|
||||
msgid "One bill has been sent."
|
||||
msgstr "S'ha creat una factura"
|
||||
|
||||
#: actions.py:110
|
||||
#: actions.py:116
|
||||
#, python-format
|
||||
msgid "%i bills have been sent."
|
||||
msgstr "S'han enviat %i factures."
|
||||
|
||||
#: actions.py:117
|
||||
#: actions.py:123
|
||||
msgid "Resend"
|
||||
msgstr "Reenviat"
|
||||
|
||||
#: actions.py:137
|
||||
#: actions.py:146
|
||||
msgid "Download"
|
||||
msgstr "Descarrega"
|
||||
|
||||
#: actions.py:153
|
||||
#: actions.py:162
|
||||
msgid "C.S.D."
|
||||
msgstr ""
|
||||
|
||||
#: actions.py:155
|
||||
#: actions.py:164
|
||||
msgid "Close, send and download bills in one shot."
|
||||
msgstr ""
|
||||
|
||||
#: actions.py:216
|
||||
#: actions.py:225
|
||||
#, python-format
|
||||
msgid "%(norders)s orders and %(nlines)s lines undoed."
|
||||
msgstr "%(norders)s ordres i %(nlines)s línies desfetes."
|
||||
|
||||
#: actions.py:235
|
||||
#: actions.py:244
|
||||
msgid "Lines moved"
|
||||
msgstr "Línies mogudes"
|
||||
|
||||
#: actions.py:248
|
||||
#: actions.py:257
|
||||
msgid "Selected bills should be in closed state"
|
||||
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
|
||||
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"
|
||||
|
||||
#: actions.py:272
|
||||
#: actions.py:286
|
||||
#, python-format
|
||||
msgid "%(related_type)s %(number)s subtotal for tax %(tax)s%%"
|
||||
msgstr "%(related_type)s %(number)s subtotal %(tax)s%%"
|
||||
|
||||
#: actions.py:288
|
||||
#: actions.py:303
|
||||
#, python-format
|
||||
msgid "<a href=\"%(url)s\">One amendment bill</a> have been generated."
|
||||
msgstr "S'ha creat una <a href=\"%(url)s\">transacció</a>"
|
||||
|
||||
#: actions.py:289
|
||||
#: actions.py:304
|
||||
#, python-format
|
||||
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>"
|
||||
|
||||
#: actions.py:292
|
||||
#: actions.py:307
|
||||
msgid "Amend"
|
||||
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:70
|
||||
msgid "Total"
|
||||
msgstr "Total"
|
||||
|
||||
#: admin.py:89
|
||||
#: admin.py:112
|
||||
msgid "Description"
|
||||
msgstr "Descripció"
|
||||
|
||||
#: admin.py:97
|
||||
#: admin.py:120
|
||||
msgid "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"
|
||||
msgstr "És oberta"
|
||||
|
||||
#: admin.py:135
|
||||
msgid "Subline"
|
||||
#: admin.py:175
|
||||
#, fuzzy
|
||||
#| msgid "Subline"
|
||||
msgid "Sublines"
|
||||
msgstr "Sublínia"
|
||||
|
||||
#: admin.py:167
|
||||
#: admin.py:221
|
||||
msgid "No bills selected."
|
||||
msgstr "No hi ha factures seleccionades"
|
||||
|
||||
#: admin.py:174
|
||||
#, python-format
|
||||
msgid "Manage %s bill lines."
|
||||
#: admin.py:229
|
||||
#, fuzzy, python-format
|
||||
#| msgid "Manage %s bill lines."
|
||||
msgid "Manage %s bill lines"
|
||||
msgstr "Gestiona %s línies de factura."
|
||||
|
||||
#: admin.py:176
|
||||
#: admin.py:231
|
||||
msgid "Bill not in open state."
|
||||
msgstr "La factura no està en estat obert"
|
||||
|
||||
#: admin.py:179
|
||||
#: admin.py:234
|
||||
msgid "Not all bills are in open state."
|
||||
msgstr "No totes les factures estan en estat obert"
|
||||
|
||||
#: admin.py:180
|
||||
msgid "Manage bill lines of multiple bills."
|
||||
#: admin.py:235
|
||||
#, fuzzy
|
||||
#| msgid "Manage bill lines of multiple bills."
|
||||
msgid "Manage bill lines of multiple bills"
|
||||
msgstr "Gestiona línies de factura de multiples factures."
|
||||
|
||||
#: admin.py:204
|
||||
msgid "Dates"
|
||||
#: admin.py:250
|
||||
#, python-format
|
||||
msgid "Subtotal %s%% VAT %s &%s;"
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:209
|
||||
msgid "Raw"
|
||||
msgstr "Raw"
|
||||
#: admin.py:251
|
||||
#, python-format
|
||||
msgid "Taxes %s%% VAT %s &%s;"
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:235 models.py:73
|
||||
msgid "Created"
|
||||
msgstr "Creada"
|
||||
#: admin.py:255 admin.py:381 filters.py:46
|
||||
#: templates/bills/microspective.html:123
|
||||
msgid "total"
|
||||
msgstr "total"
|
||||
|
||||
#: admin.py:236
|
||||
#, fuzzy
|
||||
#| msgid "Close"
|
||||
msgid "Closed"
|
||||
msgstr "Tanca"
|
||||
#: admin.py:275
|
||||
msgid "This bill has been amended, this value may not be valid."
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:237
|
||||
#, fuzzy
|
||||
#| msgid "updated on"
|
||||
msgid "Updated"
|
||||
msgstr "actualitzada el"
|
||||
#: admin.py:280
|
||||
msgid "Payment"
|
||||
msgstr "Pagament"
|
||||
|
||||
#: admin.py:246
|
||||
#: admin.py:304
|
||||
#, fuzzy
|
||||
#| msgid "amended line"
|
||||
msgid "Amends"
|
||||
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"
|
||||
msgstr "línies"
|
||||
|
||||
#: admin.py:257 filters.py:46 templates/bills/microspective.html:118
|
||||
msgid "total"
|
||||
msgstr "total"
|
||||
|
||||
#: admin.py:265 models.py:104 models.py:460
|
||||
#: admin.py:389 models.py:108 models.py:501
|
||||
msgid "type"
|
||||
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
|
||||
msgid "All"
|
||||
msgstr "Tot"
|
||||
|
||||
#: filters.py:22 models.py:88
|
||||
#: filters.py:22 models.py:91
|
||||
msgid "Invoice"
|
||||
msgstr "Factura"
|
||||
|
||||
#: filters.py:23 models.py:90
|
||||
#: filters.py:23 models.py:93
|
||||
msgid "Fee"
|
||||
msgstr "Quota de soci"
|
||||
|
||||
|
@ -231,65 +262,67 @@ msgstr "Pro-forma"
|
|||
msgid "Amendment fee"
|
||||
msgstr "Rectificació de quota de soci"
|
||||
|
||||
#: filters.py:26 models.py:89
|
||||
#: filters.py:26 models.py:92
|
||||
msgid "Amendment invoice"
|
||||
msgstr "Factura rectificativa"
|
||||
|
||||
#: filters.py:68
|
||||
#: filters.py:71
|
||||
msgid "has bill contact"
|
||||
msgstr "té contacte de facturació"
|
||||
|
||||
#: filters.py:73
|
||||
#: filters.py:76
|
||||
msgid "Yes"
|
||||
msgstr "Si"
|
||||
|
||||
#: filters.py:74
|
||||
#: filters.py:77
|
||||
msgid "No"
|
||||
msgstr "No"
|
||||
|
||||
#: filters.py:85
|
||||
#: filters.py:88
|
||||
msgid "payment state"
|
||||
msgstr "Pagament"
|
||||
|
||||
#: filters.py:90 models.py:72
|
||||
#: filters.py:93 models.py:74
|
||||
msgid "Open"
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:91 models.py:76
|
||||
#: filters.py:94 models.py:78
|
||||
msgid "Paid"
|
||||
msgstr "Pagat"
|
||||
|
||||
#: filters.py:92
|
||||
#: filters.py:95
|
||||
msgid "Pending"
|
||||
msgstr "Pendent"
|
||||
|
||||
#: filters.py:93 models.py:79
|
||||
#: filters.py:96 models.py:81
|
||||
msgid "Bad debt"
|
||||
msgstr "Incobrable"
|
||||
|
||||
#: filters.py:135
|
||||
#: filters.py:138
|
||||
#, fuzzy
|
||||
#| msgid "amended line"
|
||||
msgid "amended"
|
||||
msgstr "línia rectificada"
|
||||
|
||||
#: filters.py:140
|
||||
#: filters.py:143
|
||||
#, fuzzy
|
||||
#| msgid "Due date"
|
||||
msgid "Closed amends"
|
||||
msgstr "Data de pagament"
|
||||
|
||||
#: filters.py:141
|
||||
msgid "Open or closed amends"
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:142
|
||||
#: filters.py:144
|
||||
#, fuzzy
|
||||
#| msgid "closed on"
|
||||
msgid "No closed amends"
|
||||
msgstr "tancat el"
|
||||
#| msgid "Due date"
|
||||
msgid "Open amends"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
|
@ -309,7 +342,7 @@ msgstr "Tipus"
|
|||
msgid "Source"
|
||||
msgstr "Font"
|
||||
|
||||
#: helpers.py:10
|
||||
#: helpers.py:14
|
||||
msgid ""
|
||||
"{relation} account \"{account}\" does not have a declared invoice contact. "
|
||||
"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 "
|
||||
"<a href=\"{url}#invoicecontact-group\">proporcionar un</a>"
|
||||
|
||||
#: helpers.py:17
|
||||
#: helpers.py:21
|
||||
msgid "Related"
|
||||
msgstr "Relacionat"
|
||||
|
||||
#: helpers.py:24
|
||||
#: helpers.py:28
|
||||
msgid "Main"
|
||||
msgstr "Principal"
|
||||
|
||||
#: models.py:24 models.py:100
|
||||
#: models.py:26 models.py:104
|
||||
msgid "account"
|
||||
msgstr "compte"
|
||||
|
||||
#: models.py:26
|
||||
#: models.py:28
|
||||
msgid "name"
|
||||
msgstr "nom"
|
||||
|
||||
#: models.py:27
|
||||
#: models.py:29
|
||||
msgid "Account full name will be used when left blank."
|
||||
msgstr "S'emprarà el nom complet del compte quan es deixi en blanc."
|
||||
|
||||
#: models.py:28
|
||||
#: models.py:30
|
||||
msgid "address"
|
||||
msgstr "adreça"
|
||||
|
||||
#: models.py:29
|
||||
#: models.py:31
|
||||
msgid "city"
|
||||
msgstr "ciutat"
|
||||
|
||||
#: models.py:31
|
||||
#: models.py:33
|
||||
msgid "zip code"
|
||||
msgstr "codi postal"
|
||||
|
||||
#: models.py:32
|
||||
#: models.py:34
|
||||
msgid "Enter a valid zipcode."
|
||||
msgstr "Introdueix un codi postal vàlid."
|
||||
|
||||
#: models.py:33
|
||||
#: models.py:35
|
||||
msgid "country"
|
||||
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"
|
||||
msgstr "NIF"
|
||||
|
||||
#: models.py:74
|
||||
#: models.py:76
|
||||
msgid "Processed"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:75
|
||||
#: models.py:77
|
||||
#, fuzzy
|
||||
#| msgid "amended line"
|
||||
msgid "Amended"
|
||||
msgstr "línia rectificada"
|
||||
|
||||
#: models.py:77
|
||||
#: models.py:79
|
||||
msgid "Incomplete"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:78
|
||||
#: models.py:80
|
||||
msgid "Executed"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:91
|
||||
#: models.py:94
|
||||
msgid "Amendment Fee"
|
||||
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"
|
||||
msgstr "Pro forma"
|
||||
|
||||
#: models.py:99
|
||||
#: models.py:103
|
||||
msgid "number"
|
||||
msgstr "número"
|
||||
|
||||
#: models.py:102
|
||||
#: models.py:106
|
||||
#, fuzzy
|
||||
#| msgid "amended line"
|
||||
msgid "amend of"
|
||||
msgstr "línia rectificada"
|
||||
|
||||
#: models.py:105
|
||||
#: models.py:109
|
||||
msgid "created on"
|
||||
msgstr "creat el"
|
||||
|
||||
#: models.py:106
|
||||
#: models.py:110
|
||||
msgid "closed on"
|
||||
msgstr "tancat el"
|
||||
|
||||
#: models.py:107
|
||||
#: models.py:111
|
||||
msgid "open"
|
||||
msgstr "obert"
|
||||
|
||||
#: models.py:108
|
||||
#: models.py:112
|
||||
msgid "sent"
|
||||
msgstr "enviat"
|
||||
|
||||
#: models.py:109
|
||||
#: models.py:113
|
||||
msgid "due on"
|
||||
msgstr "es deu"
|
||||
|
||||
#: models.py:110
|
||||
#: models.py:114
|
||||
msgid "updated on"
|
||||
msgstr "actualitzada el"
|
||||
|
||||
#: models.py:112
|
||||
#: models.py:116
|
||||
msgid "comments"
|
||||
msgstr "comentaris"
|
||||
|
||||
#: models.py:113
|
||||
#: models.py:117
|
||||
msgid "HTML"
|
||||
msgstr "HTML"
|
||||
|
||||
#: models.py:194
|
||||
#: models.py:200
|
||||
#, python-format
|
||||
msgid "Type %s is not an amendment."
|
||||
msgstr ""
|
||||
|
||||
#: models.py:196
|
||||
#: models.py:202
|
||||
msgid "Amend of related account doesn't match bill account."
|
||||
msgstr ""
|
||||
|
||||
#: models.py:198
|
||||
#: models.py:204
|
||||
#, fuzzy
|
||||
#| msgid "Bill not in open state."
|
||||
msgid "Related invoice is in open state."
|
||||
msgstr "La factura no està en estat obert"
|
||||
|
||||
#: models.py:200
|
||||
#: models.py:206
|
||||
msgid "Related invoice is an amendment."
|
||||
msgstr ""
|
||||
|
||||
#: models.py:392
|
||||
#: models.py:419
|
||||
msgid "bill"
|
||||
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"
|
||||
msgstr "descripció"
|
||||
|
||||
#: models.py:394
|
||||
#: models.py:421
|
||||
msgid "rate"
|
||||
msgstr "tarifa"
|
||||
|
||||
#: models.py:395
|
||||
#: models.py:422
|
||||
msgid "quantity"
|
||||
msgstr "quantitat"
|
||||
|
||||
#: models.py:397
|
||||
#: models.py:424
|
||||
#, fuzzy
|
||||
#| msgid "quantity"
|
||||
msgid "Verbose quantity"
|
||||
msgstr "quantitat"
|
||||
|
||||
#: models.py:398 templates/admin/bills/bill/report.html:47
|
||||
#: templates/bills/microspective.html:77
|
||||
#: templates/bills/microspective.html:111
|
||||
#: models.py:425 templates/admin/bills/bill/report.html:47
|
||||
#: templates/bills/microspective.html:79
|
||||
#: templates/bills/microspective.html:116
|
||||
msgid "subtotal"
|
||||
msgstr "subtotal"
|
||||
|
||||
#: models.py:399
|
||||
#: models.py:426
|
||||
msgid "tax"
|
||||
msgstr "impostos"
|
||||
|
||||
#: models.py:400
|
||||
#: models.py:427
|
||||
msgid "start"
|
||||
msgstr "iniciar"
|
||||
|
||||
#: models.py:401
|
||||
#: models.py:428
|
||||
msgid "end"
|
||||
msgstr "finalitzar"
|
||||
|
||||
#: models.py:403
|
||||
#: models.py:431
|
||||
msgid "Informative link back to the order"
|
||||
msgstr "Enllaç informatiu de l'ordre"
|
||||
|
||||
#: models.py:404
|
||||
#: models.py:432
|
||||
msgid "order billed"
|
||||
msgstr "ordre facturada"
|
||||
|
||||
#: models.py:405
|
||||
#: models.py:433
|
||||
msgid "order billed until"
|
||||
msgstr "ordre facturada fins a"
|
||||
|
||||
#: models.py:406
|
||||
#: models.py:434
|
||||
msgid "created"
|
||||
msgstr "creada"
|
||||
|
||||
#: models.py:408
|
||||
#: models.py:436
|
||||
msgid "amended line"
|
||||
msgstr "línia rectificada"
|
||||
|
||||
#: models.py:451
|
||||
#: models.py:492
|
||||
msgid "Volume"
|
||||
msgstr "Volum"
|
||||
|
||||
#: models.py:452
|
||||
#: models.py:493
|
||||
msgid "Compensation"
|
||||
msgstr "Compensació"
|
||||
|
||||
#: models.py:453
|
||||
#: models.py:494
|
||||
msgid "Other"
|
||||
msgstr "Altre"
|
||||
|
||||
#: models.py:457
|
||||
#: models.py:498
|
||||
msgid "bill line"
|
||||
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
|
||||
msgid "Summary"
|
||||
msgstr ""
|
||||
|
@ -531,19 +586,19 @@ msgstr ""
|
|||
#: templates/admin/bills/bill/report.html:47
|
||||
#: templates/admin/bills/bill/report.html:51
|
||||
#: templates/admin/bills/bill/report.html:69
|
||||
#: templates/bills/microspective.html:111
|
||||
#: templates/bills/microspective.html:114
|
||||
#: templates/bills/microspective.html:116
|
||||
#: templates/bills/microspective.html:119
|
||||
msgid "VAT"
|
||||
msgstr "IVA"
|
||||
|
||||
#: templates/admin/bills/bill/report.html:51
|
||||
#: templates/bills/microspective.html:114
|
||||
#: templates/bills/microspective.html:119
|
||||
msgid "taxes"
|
||||
msgstr "impostos"
|
||||
|
||||
#: templates/admin/bills/bill/report.html:56
|
||||
#: templates/admin/bills/billline/report.html:60
|
||||
#: templates/bills/microspective.html:53
|
||||
#: templates/bills/microspective.html:54
|
||||
msgid "TOTAL"
|
||||
msgstr "TOTAL"
|
||||
|
||||
|
@ -561,8 +616,20 @@ msgstr "Data de pagament"
|
|||
msgid "Base"
|
||||
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
|
||||
msgid "Services"
|
||||
msgid "Service"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/bills/billline/report.html:43
|
||||
|
@ -587,27 +654,21 @@ msgstr "quantitat"
|
|||
msgid "Profit"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/bills/change_list.html:9
|
||||
#, fuzzy
|
||||
#| msgid "bill"
|
||||
msgid "Add bill"
|
||||
msgstr "factura"
|
||||
|
||||
#: templates/bills/microspective-fee.html:107
|
||||
#: templates/bills/microspective-fee.html:115
|
||||
msgid "Due date"
|
||||
msgstr "Data de pagament"
|
||||
|
||||
#: templates/bills/microspective-fee.html:108
|
||||
#: templates/bills/microspective-fee.html:116
|
||||
#, python-format
|
||||
msgid "On %(bank_account)s"
|
||||
msgstr "Al %(bank_account)s"
|
||||
|
||||
#: templates/bills/microspective-fee.html:114
|
||||
#: templates/bills/microspective-fee.html:122
|
||||
#, python-format
|
||||
msgid "From %(ini)s to %(end)s"
|
||||
msgstr "De %(ini)s a %(end)s"
|
||||
|
||||
#: templates/bills/microspective-fee.html:121
|
||||
#: templates/bills/microspective-fee.html:144
|
||||
msgid ""
|
||||
"\n"
|
||||
"<strong>With your membership</strong> you are supporting ...\n"
|
||||
|
@ -615,36 +676,36 @@ msgstr ""
|
|||
"\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"
|
||||
msgstr "VENCIMENT"
|
||||
|
||||
#: templates/bills/microspective.html:57
|
||||
#: templates/bills/microspective.html:58
|
||||
#, python-format
|
||||
msgid "%(bill_type)s DATE"
|
||||
msgstr "DATA %(bill_type)s"
|
||||
|
||||
#: templates/bills/microspective.html:74
|
||||
#: templates/bills/microspective.html:76
|
||||
msgid "period"
|
||||
msgstr "període"
|
||||
|
||||
#: templates/bills/microspective.html:75
|
||||
#: templates/bills/microspective.html:77
|
||||
msgid "hrs/qty"
|
||||
msgstr "hrs/qnt"
|
||||
|
||||
#: templates/bills/microspective.html:76
|
||||
#: templates/bills/microspective.html:78
|
||||
msgid "rate/price"
|
||||
msgstr "tarifa/preu"
|
||||
|
||||
#: templates/bills/microspective.html:131
|
||||
#: templates/bills/microspective.html:137
|
||||
msgid "COMMENTS"
|
||||
msgstr "COMENTARIS"
|
||||
|
||||
#: templates/bills/microspective.html:138
|
||||
#: templates/bills/microspective.html:145
|
||||
msgid "PAYMENT"
|
||||
msgstr "PAGAMENT"
|
||||
|
||||
#: templates/bills/microspective.html:142
|
||||
#: templates/bills/microspective.html:149
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
|
@ -658,11 +719,11 @@ msgstr ""
|
|||
"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"
|
||||
|
||||
#: templates/bills/microspective.html:151
|
||||
#: templates/bills/microspective.html:160
|
||||
msgid "QUESTIONS"
|
||||
msgstr "PREGUNTES"
|
||||
|
||||
#: templates/bills/microspective.html:152
|
||||
#: templates/bills/microspective.html:161
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
|
@ -679,5 +740,10 @@ msgstr ""
|
|||
"ràpidament possible.\n"
|
||||
" "
|
||||
|
||||
#, fuzzy
|
||||
#~| msgid "closed on"
|
||||
#~ msgid "No closed amends"
|
||||
#~ msgstr "tancat el"
|
||||
|
||||
#~ msgid "positive price"
|
||||
#~ msgstr "preu positiu"
|
||||
|
|
Binary file not shown.
|
@ -8,7 +8,7 @@ msgid ""
|
|||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\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"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
|
@ -18,33 +18,33 @@ msgstr ""
|
|||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: actions.py:31
|
||||
#: actions.py:33
|
||||
msgid "View"
|
||||
msgstr "Vista"
|
||||
|
||||
#: actions.py:42
|
||||
#: actions.py:45
|
||||
msgid "Selected bills should be in open state"
|
||||
msgstr "Las facturas seleccionadas están en estado abierto"
|
||||
|
||||
#: actions.py:57
|
||||
#: actions.py:60
|
||||
msgid "Selected bills have been closed"
|
||||
msgstr "Las facturas seleccionadas han sido cerradas"
|
||||
|
||||
#: actions.py:70
|
||||
#: actions.py:73
|
||||
#, python-format
|
||||
msgid "<a href=\"%(url)s\">One related transaction</a> has been created"
|
||||
msgstr "Se ha creado una <a href=\"%(url)s\">transacción</a>"
|
||||
|
||||
#: actions.py:71
|
||||
#: actions.py:74
|
||||
#, python-format
|
||||
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>"
|
||||
|
||||
#: actions.py:77
|
||||
#: actions.py:80
|
||||
msgid "Are you sure about closing the following bills?"
|
||||
msgstr "Estás a punto de cerrar las sigüientes facturas. ¿Estás seguro?"
|
||||
|
||||
#: actions.py:78
|
||||
#: actions.py:81
|
||||
msgid ""
|
||||
"Once a bill is closed it can not be further modified.</p><p>Please select a "
|
||||
"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 "
|
||||
"seleciona un metodo de pago para las facturas seleccionadas"
|
||||
|
||||
#: actions.py:91
|
||||
#: actions.py:97
|
||||
msgid "Close"
|
||||
msgstr "Cerrar"
|
||||
|
||||
#: actions.py:109
|
||||
#: actions.py:115
|
||||
msgid "One bill has been sent."
|
||||
msgstr "Se ha enviado una factura"
|
||||
|
||||
#: actions.py:110
|
||||
#: actions.py:116
|
||||
#, python-format
|
||||
msgid "%i bills have been sent."
|
||||
msgstr ""
|
||||
|
||||
#: actions.py:117
|
||||
#: actions.py:123
|
||||
msgid "Resend"
|
||||
msgstr ""
|
||||
|
||||
#: actions.py:137
|
||||
#: actions.py:146
|
||||
msgid "Download"
|
||||
msgstr "Descarga"
|
||||
|
||||
#: actions.py:153
|
||||
#: actions.py:162
|
||||
msgid "C.S.D."
|
||||
msgstr ""
|
||||
|
||||
#: actions.py:155
|
||||
#: actions.py:164
|
||||
msgid "Close, send and download bills in one shot."
|
||||
msgstr ""
|
||||
|
||||
#: actions.py:216
|
||||
#: actions.py:225
|
||||
#, python-format
|
||||
msgid "%(norders)s orders and %(nlines)s lines undoed."
|
||||
msgstr ""
|
||||
|
||||
#: actions.py:235
|
||||
#: actions.py:244
|
||||
msgid "Lines moved"
|
||||
msgstr ""
|
||||
|
||||
#: actions.py:248
|
||||
#: actions.py:257
|
||||
msgid "Selected bills should be in closed state"
|
||||
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
|
||||
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"
|
||||
|
||||
#: actions.py:272
|
||||
#: actions.py:286
|
||||
#, python-format
|
||||
msgid "%(related_type)s %(number)s subtotal for tax %(tax)s%%"
|
||||
msgstr "%(related_type)s %(number)s subtotal %(tax)s%%"
|
||||
|
||||
#: actions.py:288
|
||||
#: actions.py:303
|
||||
#, python-format
|
||||
msgid "<a href=\"%(url)s\">One amendment bill</a> have been generated."
|
||||
msgstr "Se ha creado una <a href=\"%(url)s\">transacción</a>"
|
||||
|
||||
#: actions.py:289
|
||||
#: actions.py:304
|
||||
#, python-format
|
||||
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>"
|
||||
|
||||
#: actions.py:292
|
||||
#: actions.py:307
|
||||
msgid "Amend"
|
||||
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:70
|
||||
msgid "Total"
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:89
|
||||
#: admin.py:112
|
||||
msgid "Description"
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:97
|
||||
#: admin.py:120
|
||||
msgid "Subtotal"
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:130
|
||||
#: admin.py:146
|
||||
msgid "Totals"
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:150
|
||||
msgid "Order"
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:169
|
||||
msgid "Is open"
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:135
|
||||
msgid "Subline"
|
||||
#: admin.py:175
|
||||
msgid "Sublines"
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:167
|
||||
#: admin.py:221
|
||||
msgid "No bills selected."
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:174
|
||||
#, python-format
|
||||
msgid "Manage %s bill lines."
|
||||
msgstr ""
|
||||
#: admin.py:229
|
||||
#, fuzzy, python-format
|
||||
#| msgid "bill line"
|
||||
msgid "Manage %s bill lines"
|
||||
msgstr "linea de factura"
|
||||
|
||||
#: admin.py:176
|
||||
#: admin.py:231
|
||||
msgid "Bill not in open state."
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:179
|
||||
#: admin.py:234
|
||||
msgid "Not all bills are in open state."
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:180
|
||||
msgid "Manage bill lines of multiple bills."
|
||||
#: admin.py:235
|
||||
msgid "Manage bill lines of multiple bills"
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:204
|
||||
msgid "Dates"
|
||||
#: admin.py:250
|
||||
#, python-format
|
||||
msgid "Subtotal %s%% VAT %s &%s;"
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:209
|
||||
msgid "Raw"
|
||||
#: admin.py:251
|
||||
#, python-format
|
||||
msgid "Taxes %s%% VAT %s &%s;"
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:235 models.py:73
|
||||
msgid "Created"
|
||||
#: admin.py:255 admin.py:381 filters.py:46
|
||||
#: templates/bills/microspective.html:123
|
||||
msgid "total"
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:236
|
||||
#, fuzzy
|
||||
#| msgid "Close"
|
||||
msgid "Closed"
|
||||
msgstr "Cerrar"
|
||||
#: admin.py:275
|
||||
msgid "This bill has been amended, this value may not be valid."
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:237
|
||||
#, fuzzy
|
||||
#| msgid "updated on"
|
||||
msgid "Updated"
|
||||
msgstr "actualizada en"
|
||||
#: admin.py:280
|
||||
msgid "Payment"
|
||||
msgstr "Pago"
|
||||
|
||||
#: admin.py:246
|
||||
#: admin.py:304
|
||||
#, fuzzy
|
||||
#| msgid "Amended"
|
||||
msgid "Amends"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:257 filters.py:46 templates/bills/microspective.html:118
|
||||
msgid "total"
|
||||
msgstr ""
|
||||
|
||||
#: admin.py:265 models.py:104 models.py:460
|
||||
#: admin.py:389 models.py:108 models.py:501
|
||||
msgid "type"
|
||||
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
|
||||
msgid "All"
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:22 models.py:88
|
||||
#: filters.py:22 models.py:91
|
||||
msgid "Invoice"
|
||||
msgstr "Factura"
|
||||
|
||||
#: filters.py:23 models.py:90
|
||||
#: filters.py:23 models.py:93
|
||||
msgid "Fee"
|
||||
msgstr "Cuota de socio"
|
||||
|
||||
|
@ -231,65 +256,67 @@ msgstr ""
|
|||
msgid "Amendment fee"
|
||||
msgstr "Cuota rectificativa"
|
||||
|
||||
#: filters.py:26 models.py:89
|
||||
#: filters.py:26 models.py:92
|
||||
msgid "Amendment invoice"
|
||||
msgstr "Factura rectificativa"
|
||||
|
||||
#: filters.py:68
|
||||
#: filters.py:71
|
||||
msgid "has bill contact"
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:73
|
||||
#: filters.py:76
|
||||
msgid "Yes"
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:74
|
||||
#: filters.py:77
|
||||
msgid "No"
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:85
|
||||
#: filters.py:88
|
||||
msgid "payment state"
|
||||
msgstr "Pago"
|
||||
|
||||
#: filters.py:90 models.py:72
|
||||
#: filters.py:93 models.py:74
|
||||
msgid "Open"
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:91 models.py:76
|
||||
#: filters.py:94 models.py:78
|
||||
msgid "Paid"
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:92
|
||||
#: filters.py:95
|
||||
msgid "Pending"
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:93 models.py:79
|
||||
#: filters.py:96 models.py:81
|
||||
msgid "Bad debt"
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:135
|
||||
#: filters.py:138
|
||||
#, fuzzy
|
||||
#| msgid "Amended"
|
||||
msgid "amended"
|
||||
msgstr "Quota rectificativa"
|
||||
|
||||
#: filters.py:140
|
||||
#: filters.py:143
|
||||
#, fuzzy
|
||||
#| msgid "Due date"
|
||||
msgid "Closed amends"
|
||||
msgstr "Fecha de pago"
|
||||
|
||||
#: filters.py:141
|
||||
msgid "Open or closed amends"
|
||||
msgstr ""
|
||||
|
||||
#: filters.py:142
|
||||
#: filters.py:144
|
||||
#, fuzzy
|
||||
#| msgid "closed on"
|
||||
msgid "No closed amends"
|
||||
msgstr "cerrada en"
|
||||
#| msgid "Due date"
|
||||
msgid "Open amends"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
|
@ -309,213 +336,233 @@ msgstr ""
|
|||
msgid "Source"
|
||||
msgstr ""
|
||||
|
||||
#: helpers.py:10
|
||||
#: helpers.py:14
|
||||
msgid ""
|
||||
"{relation} account \"{account}\" does not have a declared invoice contact. "
|
||||
"You should <a href=\"{url}#invoicecontact-group\">provide one</a>"
|
||||
msgstr ""
|
||||
|
||||
#: helpers.py:17
|
||||
#: helpers.py:21
|
||||
msgid "Related"
|
||||
msgstr ""
|
||||
|
||||
#: helpers.py:24
|
||||
#: helpers.py:28
|
||||
msgid "Main"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:24 models.py:100
|
||||
#: models.py:26 models.py:104
|
||||
msgid "account"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:26
|
||||
#: models.py:28
|
||||
msgid "name"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:27
|
||||
#: models.py:29
|
||||
msgid "Account full name will be used when left blank."
|
||||
msgstr ""
|
||||
|
||||
#: models.py:28
|
||||
#: models.py:30
|
||||
msgid "address"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:29
|
||||
#: models.py:31
|
||||
msgid "city"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:31
|
||||
#: models.py:33
|
||||
msgid "zip code"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:32
|
||||
#: models.py:34
|
||||
msgid "Enter a valid zipcode."
|
||||
msgstr ""
|
||||
|
||||
#: models.py:33
|
||||
#: models.py:35
|
||||
msgid "country"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:36 templates/admin/bills/bill/report.html:65
|
||||
#: models.py:38 templates/admin/bills/bill/report.html:65
|
||||
msgid "VAT number"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:74
|
||||
#: models.py:76
|
||||
msgid "Processed"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:75
|
||||
#: models.py:77
|
||||
msgid "Amended"
|
||||
msgstr "Quota rectificativa"
|
||||
|
||||
#: models.py:77
|
||||
#: models.py:79
|
||||
msgid "Incomplete"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:78
|
||||
#: models.py:80
|
||||
msgid "Executed"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:91
|
||||
#: models.py:94
|
||||
msgid "Amendment Fee"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:92
|
||||
#: models.py:95
|
||||
#, fuzzy
|
||||
#| msgid "Invoice"
|
||||
msgid "Abono Invoice"
|
||||
msgstr "Abono"
|
||||
|
||||
#: models.py:96
|
||||
msgid "Pro forma"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:99
|
||||
#: models.py:103
|
||||
msgid "number"
|
||||
msgstr "número"
|
||||
|
||||
#: models.py:102
|
||||
#: models.py:106
|
||||
msgid "amend of"
|
||||
msgstr "rectificación de"
|
||||
|
||||
#: models.py:105
|
||||
#: models.py:109
|
||||
msgid "created on"
|
||||
msgstr "creado en"
|
||||
|
||||
#: models.py:106
|
||||
#: models.py:110
|
||||
msgid "closed on"
|
||||
msgstr "cerrada en"
|
||||
|
||||
#: models.py:107
|
||||
#: models.py:111
|
||||
msgid "open"
|
||||
msgstr "abierta"
|
||||
|
||||
#: models.py:108
|
||||
#: models.py:112
|
||||
msgid "sent"
|
||||
msgstr "enviada"
|
||||
|
||||
#: models.py:109
|
||||
#: models.py:113
|
||||
msgid "due on"
|
||||
msgstr "vencimiento"
|
||||
|
||||
#: models.py:110
|
||||
#: models.py:114
|
||||
msgid "updated on"
|
||||
msgstr "actualizada en"
|
||||
|
||||
#: models.py:112
|
||||
#: models.py:116
|
||||
msgid "comments"
|
||||
msgstr "comentarios"
|
||||
|
||||
#: models.py:113
|
||||
#: models.py:117
|
||||
msgid "HTML"
|
||||
msgstr "HTML"
|
||||
|
||||
#: models.py:194
|
||||
#: models.py:200
|
||||
#, python-format
|
||||
msgid "Type %s is not an amendment."
|
||||
msgstr ""
|
||||
|
||||
#: models.py:196
|
||||
#: models.py:202
|
||||
msgid "Amend of related account doesn't match bill account."
|
||||
msgstr ""
|
||||
|
||||
#: models.py:198
|
||||
#: models.py:204
|
||||
#, fuzzy
|
||||
#| msgid "Selected bills should be in open state"
|
||||
msgid "Related invoice is in open state."
|
||||
msgstr "Las facturas seleccionadas están en estado abierto"
|
||||
|
||||
#: models.py:200
|
||||
#: models.py:206
|
||||
msgid "Related invoice is an amendment."
|
||||
msgstr ""
|
||||
|
||||
#: models.py:392
|
||||
#: models.py:419
|
||||
msgid "bill"
|
||||
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"
|
||||
msgstr "descripción"
|
||||
|
||||
#: models.py:394
|
||||
#: models.py:421
|
||||
msgid "rate"
|
||||
msgstr "tarifa"
|
||||
|
||||
#: models.py:395
|
||||
#: models.py:422
|
||||
msgid "quantity"
|
||||
msgstr "cantidad"
|
||||
|
||||
#: models.py:397
|
||||
#: models.py:424
|
||||
msgid "Verbose quantity"
|
||||
msgstr "Cantidad"
|
||||
|
||||
#: models.py:398 templates/admin/bills/bill/report.html:47
|
||||
#: templates/bills/microspective.html:77
|
||||
#: templates/bills/microspective.html:111
|
||||
#: models.py:425 templates/admin/bills/bill/report.html:47
|
||||
#: templates/bills/microspective.html:79
|
||||
#: templates/bills/microspective.html:116
|
||||
msgid "subtotal"
|
||||
msgstr "subtotal"
|
||||
|
||||
#: models.py:399
|
||||
#: models.py:426
|
||||
msgid "tax"
|
||||
msgstr "impuesto"
|
||||
|
||||
#: models.py:400
|
||||
#: models.py:427
|
||||
msgid "start"
|
||||
msgstr "inicio"
|
||||
|
||||
#: models.py:401
|
||||
#: models.py:428
|
||||
msgid "end"
|
||||
msgstr "fín"
|
||||
|
||||
#: models.py:403
|
||||
#: models.py:431
|
||||
msgid "Informative link back to the order"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:404
|
||||
#: models.py:432
|
||||
msgid "order billed"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:405
|
||||
#: models.py:433
|
||||
msgid "order billed until"
|
||||
msgstr ""
|
||||
|
||||
#: models.py:406
|
||||
#: models.py:434
|
||||
msgid "created"
|
||||
msgstr "creado"
|
||||
|
||||
#: models.py:408
|
||||
#: models.py:436
|
||||
msgid "amended line"
|
||||
msgstr "linea rectificativa"
|
||||
|
||||
#: models.py:451
|
||||
#: models.py:492
|
||||
msgid "Volume"
|
||||
msgstr "Volumen"
|
||||
|
||||
#: models.py:452
|
||||
#: models.py:493
|
||||
msgid "Compensation"
|
||||
msgstr "Compensación"
|
||||
|
||||
#: models.py:453
|
||||
#: models.py:494
|
||||
msgid "Other"
|
||||
msgstr "Otro"
|
||||
|
||||
#: models.py:457
|
||||
#: models.py:498
|
||||
msgid "bill line"
|
||||
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
|
||||
msgid "Summary"
|
||||
msgstr ""
|
||||
|
@ -523,19 +570,19 @@ msgstr ""
|
|||
#: templates/admin/bills/bill/report.html:47
|
||||
#: templates/admin/bills/bill/report.html:51
|
||||
#: templates/admin/bills/bill/report.html:69
|
||||
#: templates/bills/microspective.html:111
|
||||
#: templates/bills/microspective.html:114
|
||||
#: templates/bills/microspective.html:116
|
||||
#: templates/bills/microspective.html:119
|
||||
msgid "VAT"
|
||||
msgstr "IVA"
|
||||
|
||||
#: templates/admin/bills/bill/report.html:51
|
||||
#: templates/bills/microspective.html:114
|
||||
#: templates/bills/microspective.html:119
|
||||
msgid "taxes"
|
||||
msgstr "impuestos"
|
||||
|
||||
#: templates/admin/bills/bill/report.html:56
|
||||
#: templates/admin/bills/billline/report.html:60
|
||||
#: templates/bills/microspective.html:53
|
||||
#: templates/bills/microspective.html:54
|
||||
msgid "TOTAL"
|
||||
msgstr "TOTAL"
|
||||
|
||||
|
@ -553,8 +600,20 @@ msgstr "Fecha de pago"
|
|||
msgid "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
|
||||
msgid "Services"
|
||||
msgid "Service"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/bills/billline/report.html:43
|
||||
|
@ -579,62 +638,56 @@ msgstr "cantidad"
|
|||
msgid "Profit"
|
||||
msgstr ""
|
||||
|
||||
#: templates/admin/bills/change_list.html:9
|
||||
#, fuzzy
|
||||
#| msgid "bill"
|
||||
msgid "Add bill"
|
||||
msgstr "factura"
|
||||
|
||||
#: templates/bills/microspective-fee.html:107
|
||||
#: templates/bills/microspective-fee.html:115
|
||||
msgid "Due date"
|
||||
msgstr "Fecha de pago"
|
||||
|
||||
#: templates/bills/microspective-fee.html:108
|
||||
#: templates/bills/microspective-fee.html:116
|
||||
#, python-format
|
||||
msgid "On %(bank_account)s"
|
||||
msgstr "En %(bank_account)s"
|
||||
|
||||
#: templates/bills/microspective-fee.html:114
|
||||
#: templates/bills/microspective-fee.html:122
|
||||
#, python-format
|
||||
msgid "From %(ini)s to %(end)s"
|
||||
msgstr "Desde %(ini)s hasta %(end)s"
|
||||
|
||||
#: templates/bills/microspective-fee.html:121
|
||||
#: templates/bills/microspective-fee.html:144
|
||||
msgid ""
|
||||
"\n"
|
||||
"<strong>With your membership</strong> you are supporting ...\n"
|
||||
msgstr ""
|
||||
|
||||
#: templates/bills/microspective.html:49
|
||||
#: templates/bills/microspective.html:50
|
||||
msgid "DUE DATE"
|
||||
msgstr "VENCIMIENTO"
|
||||
|
||||
#: templates/bills/microspective.html:57
|
||||
#: templates/bills/microspective.html:58
|
||||
#, python-format
|
||||
msgid "%(bill_type)s DATE"
|
||||
msgstr "FECHA %(bill_type)s"
|
||||
|
||||
#: templates/bills/microspective.html:74
|
||||
#: templates/bills/microspective.html:76
|
||||
msgid "period"
|
||||
msgstr "periodo"
|
||||
|
||||
#: templates/bills/microspective.html:75
|
||||
#: templates/bills/microspective.html:77
|
||||
msgid "hrs/qty"
|
||||
msgstr "hrs/cant"
|
||||
|
||||
#: templates/bills/microspective.html:76
|
||||
#: templates/bills/microspective.html:78
|
||||
msgid "rate/price"
|
||||
msgstr "tarifa/precio"
|
||||
|
||||
#: templates/bills/microspective.html:131
|
||||
#: templates/bills/microspective.html:137
|
||||
msgid "COMMENTS"
|
||||
msgstr "COMENTARIOS"
|
||||
|
||||
#: templates/bills/microspective.html:138
|
||||
#: templates/bills/microspective.html:145
|
||||
msgid "PAYMENT"
|
||||
msgstr "PAGO"
|
||||
|
||||
#: templates/bills/microspective.html:142
|
||||
#: templates/bills/microspective.html:149
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
|
@ -648,11 +701,11 @@ msgstr ""
|
|||
"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"
|
||||
|
||||
#: templates/bills/microspective.html:151
|
||||
#: templates/bills/microspective.html:160
|
||||
msgid "QUESTIONS"
|
||||
msgstr "PREGUNTAS"
|
||||
|
||||
#: templates/bills/microspective.html:152
|
||||
#: templates/bills/microspective.html:161
|
||||
#, python-format
|
||||
msgid ""
|
||||
"\n"
|
||||
|
@ -668,3 +721,8 @@ msgstr ""
|
|||
" contacta con nosotros en %(email)s. Te responderemos lo más "
|
||||
"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'
|
||||
AMENDMENTFEE = 'AMENDMENTFEE'
|
||||
PROFORMA = 'PROFORMA'
|
||||
ABONOINVOICE = 'ABONOINVOICE'
|
||||
TYPES = (
|
||||
(INVOICE, _("Invoice")),
|
||||
(AMENDMENTINVOICE, _("Amendment invoice")),
|
||||
(FEE, _("Fee")),
|
||||
(AMENDMENTFEE, _("Amendment Fee")),
|
||||
(ABONOINVOICE, _("Abono Invoice")),
|
||||
(PROFORMA, _("Pro forma")),
|
||||
)
|
||||
AMEND_MAP = {
|
||||
|
@ -392,6 +394,11 @@ class AmendmentInvoice(Bill):
|
|||
proxy = True
|
||||
|
||||
|
||||
class AbonoInvoice(Bill):
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
||||
|
||||
class Fee(Bill):
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
|
|
@ -18,6 +18,9 @@ BILLS_AMENDMENT_INVOICE_NUMBER_PREFIX = Setting('BILLS_AMENDMENT_INVOICE_NUMBER_
|
|||
'A'
|
||||
)
|
||||
|
||||
BILLS_ABONOINVOICE_NUMBER_PREFIX = Setting('BILLS_ABONOINVOICE_NUMBER_PREFIX',
|
||||
'AB'
|
||||
)
|
||||
|
||||
BILLS_FEE_NUMBER_PREFIX = Setting('BILLS_FEE_NUMBER_PREFIX',
|
||||
'F'
|
||||
|
|
|
@ -282,3 +282,17 @@ a:hover {
|
|||
#questions {
|
||||
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 %}
|
||||
<div class="wrapper">
|
||||
<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 %}
|
||||
<div id="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 .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):
|
||||
list_display = ('name', 'type', 'display_users', 'account_link')
|
||||
|
@ -22,7 +26,7 @@ class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
|||
fieldsets = (
|
||||
(None, {
|
||||
'classes': ('extrapretty',),
|
||||
'fields': ('account_link', 'name', 'type', 'users', 'display_users'),
|
||||
'fields': ('account_link', 'name', 'type', 'users', 'display_users', 'comments'),
|
||||
}),
|
||||
)
|
||||
add_fieldsets = (
|
||||
|
@ -44,7 +48,7 @@ class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
|||
filter_horizontal = ['users']
|
||||
filter_by_account_fields = ('users',)
|
||||
list_prefetch_related = ('users',)
|
||||
actions = (list_accounts,)
|
||||
actions = (list_accounts, save_selected)
|
||||
|
||||
def display_users(self, db):
|
||||
links = []
|
||||
|
@ -93,7 +97,7 @@ class DatabaseUserAdmin(SelectAccountAdminMixin, ChangePasswordAdminMixin, Exten
|
|||
readonly_fields = ('account_link', 'display_databases',)
|
||||
filter_by_account_fields = ('databases',)
|
||||
list_prefetch_related = ('databases',)
|
||||
actions = (list_accounts,)
|
||||
actions = (list_accounts, save_selected)
|
||||
|
||||
def display_databases(self, user):
|
||||
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)
|
||||
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
||||
related_name='databases')
|
||||
comments = models.TextField(default="", blank=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('name', 'type')
|
||||
|
|
|
@ -20,7 +20,7 @@ DATABASES_DEFAULT_TYPE = Setting('DATABASES_DEFAULT_TYPE',
|
|||
|
||||
DATABASES_DEFAULT_HOST = Setting('DATABASES_DEFAULT_HOST',
|
||||
'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'
|
||||
)
|
||||
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 = (
|
||||
'account_link', 'top_link', 'display_websites', 'display_addresses', 'implicit_records'
|
||||
)
|
||||
|
|
|
@ -102,7 +102,7 @@ class Bind9MasterDomainController(ServiceController):
|
|||
self.append(textwrap.dedent("""
|
||||
# Apply changes
|
||||
if [[ $UPDATED == 1 ]]; then
|
||||
service bind9 reload
|
||||
rm /etc/bind/master/*jnl || true; service bind9 restart
|
||||
fi""")
|
||||
)
|
||||
|
||||
|
@ -158,6 +158,7 @@ class Bind9MasterDomainController(ServiceController):
|
|||
'slaves': '; '.join(slaves) or 'none',
|
||||
'also_notify': '; '.join(slaves) + ';' if slaves else '',
|
||||
'conf_path': self.CONF_PATH,
|
||||
'dns2136_address_match_list': domain.dns2136_address_match_list
|
||||
}
|
||||
context['conf'] = textwrap.dedent("""\
|
||||
zone "%(name)s" {
|
||||
|
@ -166,6 +167,7 @@ class Bind9MasterDomainController(ServiceController):
|
|||
file "%(zone_path)s";
|
||||
allow-transfer { %(slaves)s; };
|
||||
also-notify { %(also_notify)s };
|
||||
allow-update { %(dns2136_address_match_list)s };
|
||||
notify yes;
|
||||
};""") % context
|
||||
return context
|
||||
|
|
|
@ -14,7 +14,7 @@ class BatchDomainCreationAdminForm(forms.ModelForm):
|
|||
name = forms.CharField(label=_("Names"), widget=forms.Textarea(attrs={'rows': 5, 'cols': 50}),
|
||||
help_text=_("Fully qualified domain name per line. "
|
||||
"All domains will have the provided account and records."))
|
||||
|
||||
|
||||
def clean_name(self):
|
||||
self.extra_names = []
|
||||
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 "
|
||||
"servers how long they should keep the data in cache. "
|
||||
"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()
|
||||
|
||||
|
@ -319,7 +323,8 @@ class Record(models.Model):
|
|||
help_text=_("Record TTL, defaults to %s") % settings.DOMAINS_DEFAULT_TTL,
|
||||
validators=[validators.validate_zone_interval])
|
||||
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."))
|
||||
|
||||
def __str__(self):
|
||||
|
|
|
@ -122,3 +122,6 @@ DOMAINS_MASTERS = Setting('DOMAINS_MASTERS',
|
|||
validators=[lambda masters: list(map(validate_ip_address, masters))],
|
||||
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('.'):
|
||||
msg = _("Use a fully expanded domain name ending with a dot.")
|
||||
raise ValidationError(msg)
|
||||
if len(value) > 63:
|
||||
if len(value) > 254:
|
||||
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):
|
||||
queryset = List.objects.all()
|
||||
serializer_class = ListSerializer
|
||||
filter_fields = ('name',)
|
||||
filter_fields = ('name', 'address_domain')
|
||||
|
||||
|
||||
router.register(r'lists', ListViewSet)
|
||||
|
|
|
@ -48,20 +48,14 @@ class MailmanVirtualDomainController(ServiceController):
|
|||
|
||||
def save(self, 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):
|
||||
context = self.get_context(mail_list)
|
||||
self.exclude_virtual_alias_domain(context)
|
||||
#self.exclude_virtual_alias_domain(context)
|
||||
|
||||
def commit(self):
|
||||
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()
|
||||
|
||||
def get_context_files(self):
|
||||
|
@ -107,7 +101,7 @@ class MailmanController(MailmanVirtualDomainController):
|
|||
for suffix in self.address_suffixes:
|
||||
context['suffix'] = suffix
|
||||
# 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']:
|
||||
# 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)
|
||||
|
@ -116,85 +110,22 @@ class MailmanController(MailmanVirtualDomainController):
|
|||
def save(self, mail_list):
|
||||
context = self.get_context(mail_list)
|
||||
# Create list
|
||||
self.append(textwrap.dedent("""
|
||||
# Create list %(name)s
|
||||
[[ ! -e '%(mailman_root)s/lists/%(name)s' ]] && {
|
||||
newlist --quiet --emailhost='%(domain)s' '%(name)s' '%(admin)s' '%(password)s'
|
||||
}""") % 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)
|
||||
|
||||
cmd = "/opt/mailman/venv/bin/python /usr/local/admin/orchestra_mailman3/save.py %(name)s %(admin)s %(address_name)s@%(domain)s" % context
|
||||
if not mail_list.active:
|
||||
cmd += ' --inactive'
|
||||
self.append(cmd)
|
||||
|
||||
def delete(self, mail_list):
|
||||
context = self.get_context(mail_list)
|
||||
self.exclude_virtual_alias_domain(context)
|
||||
self.append(textwrap.dedent("""
|
||||
# Remove list %(name)s
|
||||
sed -i -e '/^.*\s%(name)s\(%(suffixes_regex)s\)\s*$/d' \\
|
||||
-e 'N; /^\s*\\n\s*$/d; P; D' %(virtual_alias)s
|
||||
# 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
|
||||
)
|
||||
# Delete list
|
||||
cmd = "/opt/mailman/venv/bin/python /usr/local/admin/orchestra_mailman3/delete.py %(name)s %(admin)s %(address_name)s@%(domain)s" % context
|
||||
if not mail_list.active:
|
||||
cmd += ' --inactive'
|
||||
self.append(cmd)
|
||||
|
||||
def commit(self):
|
||||
context = self.get_context_files()
|
||||
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
|
||||
)
|
||||
|
||||
pass
|
||||
|
||||
def get_context_files(self):
|
||||
return {
|
||||
'virtual_alias': settings.LISTS_VIRTUAL_ALIAS_PATH,
|
||||
|
|
|
@ -31,7 +31,7 @@ class ListSerializer(AccountSerializerMixin, SetPasswordHyperlinkedSerializer):
|
|||
|
||||
class Meta:
|
||||
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')
|
||||
|
||||
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"),
|
||||
}
|
||||
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
|
||||
from functools import partial
|
||||
|
||||
|
@ -9,6 +10,7 @@ from orchestra import plugins
|
|||
|
||||
from . import methods
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def replace(context, pattern, repl):
|
||||
""" applies replace to all context str values """
|
||||
|
@ -108,7 +110,10 @@ class ServiceBackend(plugins.Plugin, metaclass=ServiceMount):
|
|||
def get_related(cls, obj):
|
||||
opts = obj._meta
|
||||
model = '%s.%s' % (opts.app_label, opts.object_name)
|
||||
logger.debug('Model: {}'.format(model))
|
||||
for rel_model, field in cls.related_models:
|
||||
logger.debug('rel_model: {}'.format(rel_model))
|
||||
logger.debug('field: {}'.format(field))
|
||||
if rel_model == model:
|
||||
related = obj
|
||||
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)
|
||||
states = {}
|
||||
total = 0
|
||||
transactions = transactions.order_by('bill__number')
|
||||
for transaction in transactions:
|
||||
state = transaction.get_state_display()
|
||||
try:
|
||||
|
|
|
@ -215,7 +215,9 @@ class SEPADirectDebit(PaymentMethod):
|
|||
),
|
||||
E.DrctDbtTx( # Direct Debit Transaction
|
||||
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
|
||||
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'),
|
||||
),
|
||||
]
|
175
orchestra/contrib/saas/backends/nextcloud.py
Normal file
175
orchestra/contrib/saas/backends/nextcloud.py
Normal file
|
@ -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)
|
31
orchestra/contrib/saas/migrations/0003_auto_20170528_2011.py
Normal file
31
orchestra/contrib/saas/migrations/0003_auto_20170528_2011.py
Normal file
|
@ -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',))
|
||||
|
||||
def enable(self):
|
||||
self.is_active = False
|
||||
self.is_active = True
|
||||
self.save(update_fields=('is_active',))
|
||||
|
||||
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.validators import validate_domain_protocol
|
||||
from orchestra.contrib.orchestration.models import Server
|
||||
from orchestra.utils.python import AttrDict
|
||||
|
||||
|
||||
|
@ -54,7 +55,9 @@ def clean_custom_url(saas):
|
|||
(url.netloc, account, domain.account),
|
||||
})
|
||||
# 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)
|
||||
try:
|
||||
validate_domain_protocol(website, domain, protocol)
|
||||
|
@ -110,7 +113,8 @@ def create_or_update_directive(saas):
|
|||
Domain = Website.domains.field.rel.to
|
||||
domain = Domain.objects.get(name=url.netloc)
|
||||
# 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.domains.add(domain)
|
||||
# get or create directive
|
||||
|
|
13
orchestra/contrib/saas/services/nextcloud.py
Normal file
13
orchestra/contrib/saas/services/nextcloud.py
Normal file
|
@ -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.drupal.DrupalService',
|
||||
'orchestra.contrib.saas.services.owncloud.OwnCloudService',
|
||||
'orchestra.contrib.saas.services.nextcloud.NextCloudService',
|
||||
# 'orchestra.contrib.saas.services.seafile.SeaFileService',
|
||||
),
|
||||
# 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
|
||||
|
||||
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)
|
||||
if not context['user']:
|
||||
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
|
||||
self.append(textwrap.dedent("""
|
||||
# Update/create user state for %(user)s
|
||||
|
@ -61,7 +69,8 @@ class UNIXUserController(ServiceController):
|
|||
if context['home'] != context['base_home']:
|
||||
self.append(textwrap.dedent("""\
|
||||
# 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
|
||||
chown %(mainuser)s:%(mainuser)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',))
|
||||
|
||||
def enable(self):
|
||||
self.is_active = False
|
||||
self.is_active = True
|
||||
self.save(update_fields=('is_active',))
|
||||
|
||||
def get_description(self):
|
||||
|
|
|
@ -133,3 +133,22 @@ class ProxmoxOpenVZTraffic(ServiceMonitor):
|
|||
'object_id': vps.id,
|
||||
'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
|
||||
|
||||
|
||||
|
|
135
orchestra/contrib/vps/backends.py.new
Normal file
135
orchestra/contrib/vps/backends.py.new
Normal file
|
@ -0,0 +1,135 @@
|
|||
import decimal
|
||||
import textwrap
|
||||
|
||||
from orchestra.contrib.orchestration import ServiceController
|
||||
from orchestra.contrib.resources import ServiceMonitor
|
||||
|
||||
from . import settings
|
||||
|
||||
|
||||
class ProxmoxOVZ(ServiceController):
|
||||
model = 'vps.VPS'
|
||||
|
||||
RESOURCES = (
|
||||
('memory', 'mem'),
|
||||
('swap', 'swap'),
|
||||
('disk', 'disk')
|
||||
)
|
||||
GET_PROXMOX_INFO = textwrap.dedent("""
|
||||
function get_vz_info () {
|
||||
hostname=$1
|
||||
version=$(pveversion | cut -d '/' -f2 | cut -d'.' -f1)
|
||||
if [[ $version -lt 2 ]]; then
|
||||
conf=$(grep "CID\\|:$hostname:" /var/lib/pve-manager/vzlist | grep -B1 ":$hostname:")
|
||||
CID=$(echo "$conf" | head -n1 | cut -d':' -f2)
|
||||
CTID=$(echo "$conf" | tail -n1 | cut -d':' -f1)
|
||||
node=$(pveca -l | grep "^\\s*$CID\\s*:" | awk {'print $3'})
|
||||
else
|
||||
conf=$(grep -r "HOSTNAME=\\"$hostname\\"" /etc/pve/nodes/*/openvz/*.conf)
|
||||
node=$(echo "${conf}" | cut -d"/" -f5)
|
||||
CTID=$(echo "${conf}" | cut -d"/" -f7 | cut -d"\\." -f1)
|
||||
fi
|
||||
echo $CTID $node
|
||||
}""")
|
||||
|
||||
def prepare(self):
|
||||
super(ProxmoxOVZ, self).prepare()
|
||||
self.append(self.GET_PROXMOX_INFO)
|
||||
|
||||
def get_vzset_args(self, context):
|
||||
args = list(settings.VPS_DEFAULT_VZSET_ARGS)
|
||||
for resource, arg_name in self.RESOURCES:
|
||||
try:
|
||||
allocation = context[resource]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
args.append('--%s %i' % (arg_name, allocation))
|
||||
return ' '.join(args)
|
||||
|
||||
def run_ssh_commands(self, ssh_commands):
|
||||
commands = '\n '.join(ssh_commands)
|
||||
self.append(textwrap.dedent("""\
|
||||
cat << EOF | ssh root@${info[1]}
|
||||
%s
|
||||
EOF""") % commands
|
||||
)
|
||||
|
||||
def save(self, vps):
|
||||
# TODO create the container
|
||||
context = self.get_context(vps)
|
||||
self.append(textwrap.dedent("""
|
||||
info=( $(get_vz_info %(hostname)s) )
|
||||
echo "Managing ${info[@]}"\
|
||||
""") % context
|
||||
)
|
||||
ssh_commands = []
|
||||
vzset_args = self.get_vzset_args(context)
|
||||
if vzset_args:
|
||||
context['vzset_args'] = vzset_args
|
||||
ssh_commands.append("pvectl vzset ${info[0]} %(vzset_args)s" % context)
|
||||
if hasattr(vps, 'password'):
|
||||
context['password'] = vps.password.replace('$', '\\$')
|
||||
ssh_commands.append(textwrap.dedent("""\
|
||||
echo 'root:%(password)s' \\
|
||||
| chroot /var/lib/vz/private/${info[0]} chpasswd -e""") % context
|
||||
)
|
||||
self.run_ssh_commands(ssh_commands)
|
||||
|
||||
def get_context(self, vps):
|
||||
context = {
|
||||
'hostname': vps.hostname,
|
||||
}
|
||||
for resource, __ in self.RESOURCES:
|
||||
try:
|
||||
allocation = getattr(vps.resources, resource).allocated
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
context[resource] = allocation
|
||||
return context
|
||||
|
||||
|
||||
class ProxmoxOpenVZTraffic(ServiceMonitor):
|
||||
model = 'vps.VPS'
|
||||
resource = ServiceMonitor.TRAFFIC
|
||||
monthly_sum_old_values = True
|
||||
GET_PROXMOX_INFO = ProxmoxOVZ.GET_PROXMOX_INFO
|
||||
|
||||
def prepare(self):
|
||||
super(ProxmoxOpenVZTraffic, self).prepare()
|
||||
self.append(self.GET_PROXMOX_INFO)
|
||||
self.append(textwrap.dedent("""
|
||||
function monitor () {
|
||||
object_id=$1
|
||||
hostname=$2
|
||||
info=( $(get_vz_info $hostname) )
|
||||
cat << EOF | ssh root@${info[1]}
|
||||
vzctl exec ${info[0]} cat /proc/net/dev \\
|
||||
| grep venet0 \\
|
||||
| tr ':' ' ' \\
|
||||
| awk '{sum=\\$2+\\$10} END {printf ("$object_id %0.0f\\n", sum)}'
|
||||
EOF
|
||||
}
|
||||
""")
|
||||
)
|
||||
|
||||
def process(self, line):
|
||||
""" diff with last stored state """
|
||||
object_id, value, state = super(ProxmoxOpenVZTraffic, self).process(line)
|
||||
value = decimal.Decimal(value)
|
||||
last = self.get_last_data(object_id)
|
||||
if not last or last.state > value:
|
||||
return object_id, value, value
|
||||
return object_id, value-last.state, value
|
||||
|
||||
def monitor(self, vps):
|
||||
""" Get OpenVZ container traffic on a Proxmox cluster """
|
||||
context = self.get_context(vps)
|
||||
self.append('monitor %(object_id)s %(hostname)s' % context)
|
||||
|
||||
def get_context(self, vps):
|
||||
return {
|
||||
'object_id': vps.id,
|
||||
'hostname': vps.hostname,
|
||||
}
|
25
orchestra/contrib/vps/migrations/0004_auto_20170528_2005.py
Normal file
25
orchestra/contrib/vps/migrations/0004_auto_20170528_2005.py
Normal file
|
@ -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',
|
||||
(
|
||||
('openvz', 'OpenVZ container'),
|
||||
('lxc', 'LXC container')
|
||||
),
|
||||
validators=[Setting.validate_choices]
|
||||
)
|
||||
|
||||
|
||||
VPS_DEFAULT_TYPE = Setting('VPS_DEFAULT_TYPE',
|
||||
'openvz',
|
||||
'lxc',
|
||||
choices=VPS_TYPES
|
||||
)
|
||||
|
||||
|
@ -18,13 +19,14 @@ VPS_DEFAULT_TYPE = Setting('VPS_DEFAULT_TYPE',
|
|||
VPS_TEMPLATES = Setting('VPS_TEMPLATES',
|
||||
(
|
||||
('debian7', 'Debian 7 - Wheezy'),
|
||||
('placeholder', 'LXC placeholder')
|
||||
),
|
||||
validators=[Setting.validate_choices]
|
||||
)
|
||||
|
||||
|
||||
VPS_DEFAULT_TEMPLATE = Setting('VPS_DEFAULT_TEMPLATE',
|
||||
'debian7',
|
||||
'placeholder',
|
||||
choices=VPS_TEMPLATES
|
||||
)
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ class PHPController(WebAppServiceMixin, ServiceController):
|
|||
))
|
||||
|
||||
def save(self, webapp):
|
||||
self.delete_old_config(webapp)
|
||||
context = self.get_context(webapp)
|
||||
self.create_webapp_dir(context)
|
||||
if webapp.type_instance.is_fpm:
|
||||
|
@ -40,11 +41,32 @@ class PHPController(WebAppServiceMixin, ServiceController):
|
|||
self.save_fcgid(webapp, context)
|
||||
else:
|
||||
raise TypeError("Unknown PHP execution type")
|
||||
self.append("# Clean non-used PHP FCGID wrappers and FPM pools")
|
||||
self.delete_fcgid(webapp, context, preserve=True)
|
||||
self.delete_fpm(webapp, context, preserve=True)
|
||||
# LEGACY CLEANUP FUNCTIONS. TODO REMOVE WHEN SURE NOT NEEDED.
|
||||
# self.delete_fcgid(webapp, context, preserve=True)
|
||||
# self.delete_fpm(webapp, context, preserve=True)
|
||||
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):
|
||||
self.append(textwrap.dedent("""
|
||||
# Generate FPM configuration
|
||||
|
@ -99,10 +121,11 @@ class PHPController(WebAppServiceMixin, ServiceController):
|
|||
|
||||
def delete(self, webapp):
|
||||
context = self.get_context(webapp)
|
||||
if webapp.type_instance.is_fpm:
|
||||
self.delete_fpm(webapp, context)
|
||||
elif webapp.type_instance.is_fcgid:
|
||||
self.delete_fcgid(webapp, context)
|
||||
self.delete_old_config(webapp)
|
||||
# if webapp.type_instance.is_fpm:
|
||||
# self.delete_fpm(webapp, context)
|
||||
# elif webapp.type_instance.is_fcgid:
|
||||
# self.delete_fcgid(webapp, context)
|
||||
self.delete_webapp_dir(context)
|
||||
|
||||
def has_sibilings(self, webapp, context):
|
||||
|
@ -205,7 +228,7 @@ class PHPController(WebAppServiceMixin, ServiceController):
|
|||
context['fpm_listen'] = webapp.type_instance.FPM_LISTEN % context
|
||||
fpm_config = Template(textwrap.dedent("""\
|
||||
;; {{ banner }}
|
||||
[{{ user }}]
|
||||
[{{ user }}-{{app_name}}]
|
||||
user = {{ user }}
|
||||
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,
|
||||
),
|
||||
]
|
21
orchestra/contrib/webapps/migrations/0004_webapp_comments.py
Normal file
21
orchestra/contrib/webapps/migrations/0004_webapp_comments.py
Normal file
|
@ -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')
|
||||
data = JSONField(_("data"), blank=True, default={},
|
||||
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
|
||||
databases = VirtualDatabaseRelation('databases.Database')
|
||||
|
|
|
@ -102,8 +102,8 @@ class Processes(AppOption):
|
|||
# FCGID MaxProcesses
|
||||
# FPM pm.max_children
|
||||
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).")
|
||||
regex = r'^[0-9]{1,2}$'
|
||||
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,3}$'
|
||||
group = AppOption.PROCESS
|
||||
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ class WebAppSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
|||
|
||||
class Meta:
|
||||
model = WebApp
|
||||
fields = ('url', 'id', 'name', 'type', 'options', 'data')
|
||||
fields = ('url', 'id', 'name', 'type', 'options', 'data',)
|
||||
postonly_fields = ('name', 'type')
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
|
|
@ -99,6 +99,7 @@ WEBAPPS_PHP_VERSIONS = Setting('WEBAPPS_PHP_VERSIONS', (
|
|||
('5.3-cgi', 'PHP 5.3 FCGID'),
|
||||
('5.2-cgi', 'PHP 5.2 FCGID'),
|
||||
('4-cgi', 'PHP 4 FCGID'),
|
||||
('7-fpm', 'PHP 7 FPM')
|
||||
),
|
||||
help_text="Execution modle choose by ending -fpm or -cgi.",
|
||||
validators=[Setting.validate_choices]
|
||||
|
|
|
@ -3,19 +3,22 @@ from django.dispatch import receiver
|
|||
|
||||
from .models import WebApp
|
||||
|
||||
|
||||
# Admin bulk deletion doesn't call model.delete()
|
||||
# So, signals are used instead of model method overriding
|
||||
|
||||
@receiver(pre_save, sender=WebApp, dispatch_uid='webapps.type.save')
|
||||
def type_save(sender, *args, **kwargs):
|
||||
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()
|
||||
|
||||
|
||||
@receiver(pre_delete, sender=WebApp, dispatch_uid='webapps.type.delete')
|
||||
def type_delete(sender, *args, **kwargs):
|
||||
instance = kwargs['instance']
|
||||
instance._old_self = type(instance).objects.get(id=instance.pk)
|
||||
try:
|
||||
instance.type_instance.delete()
|
||||
except KeyError:
|
||||
|
|
|
@ -69,7 +69,7 @@ class WebsiteAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
|||
fieldsets = (
|
||||
(None, {
|
||||
'classes': ('extrapretty',),
|
||||
'fields': ('account_link', 'name', 'protocol', 'domains', 'is_active'),
|
||||
'fields': ('account_link', 'name', 'protocol', 'target_server', 'domains', 'is_active', 'comments'),
|
||||
}),
|
||||
)
|
||||
form = WebsiteAdminForm
|
||||
|
|
|
@ -58,7 +58,8 @@ class Apache2Controller(ServiceController):
|
|||
context.update({
|
||||
'port': self.HTTPS_PORT if ssl else self.HTTP_PORT,
|
||||
'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)
|
||||
return Template(textwrap.dedent("""\
|
||||
|
@ -71,7 +72,8 @@ class Apache2Controller(ServiceController):
|
|||
CustomLog {{ access_log }} common{% endif %}\
|
||||
{% if error_log %}
|
||||
ErrorLog {{ error_log }}{% endif %}
|
||||
SuexecUserGroup {{ user }} {{ group }}\
|
||||
{% if suexec_needed %}
|
||||
SuexecUserGroup {{ user }} {{ group }}{% endif %}\
|
||||
{% for line in extra_conf.splitlines %}
|
||||
{{ line | safe }}{% endfor %}
|
||||
</VirtualHost>
|
||||
|
@ -225,15 +227,18 @@ class Apache2Controller(ServiceController):
|
|||
target = 'fcgi://%(socket)s%(app_path)s/$1'
|
||||
else:
|
||||
# UNIX socket
|
||||
target = 'unix:%(socket)s|fcgi://127.0.0.1%(app_path)s/'
|
||||
if context['location']:
|
||||
# FIXME unix sockets do not support $1
|
||||
target = 'unix:%(socket)s|fcgi://127.0.0.1%(app_path)s/$1'
|
||||
target = 'unix:%(socket)s|fcgi://127.0.0.1/'
|
||||
context.update({
|
||||
'app_path': os.path.normpath(app_path),
|
||||
'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)
|
||||
return [
|
||||
(context['location'], directives),
|
||||
|
@ -286,7 +291,8 @@ class Apache2Controller(ServiceController):
|
|||
if not (cert and key):
|
||||
cert = [settings.WEBSITES_DEFAULT_SSL_CERT]
|
||||
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):
|
||||
return []
|
||||
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'),
|
||||
),
|
||||
]
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue