Latest Pangea source code.

This commit is contained in:
Santiago Lamora 2020-03-18 07:49:04 +01:00
parent 49c84f13a8
commit 4606e86aaf
110 changed files with 2399 additions and 481 deletions

View File

@ -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

View File

@ -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

View File

@ -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'])

View File

@ -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'),
),
]

View File

@ -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,7 +180,6 @@ 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 = [

View File

@ -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'
)

View File

@ -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)

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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;
}

View File

@ -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

View File

@ -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 = []

View 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 = [
('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'),
),
]

View File

@ -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=''),
),
]

View File

@ -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')

View File

@ -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],
)

View File

@ -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'
)

View File

@ -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

View 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.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'),
),
]

View File

@ -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'),
),
]

View File

@ -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),
),
]

View File

@ -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),
),
]

View File

@ -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):

View File

@ -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;"

View File

@ -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."))

View File

@ -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'),
),
]

View File

@ -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)

View File

@ -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,84 +110,21 @@ 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 {

View File

@ -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):

View File

@ -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'

View File

@ -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'),
),
]

View File

@ -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('__'):

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View File

@ -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:

View File

@ -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")
)

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View 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)

View 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'),
),
]

View File

@ -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):

View File

@ -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

View 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

View File

@ -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',

View File

@ -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>&nbsp;&nbsp;Best price: Produces the best possible price given all active rating lines (those with quantity lower or equal to the metric).<br>&nbsp;&nbsp;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>&nbsp;&nbsp;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'),
),
]

View File

@ -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>&nbsp;&nbsp;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>&nbsp;&nbsp;Best price: Produces the best possible price given all active rating lines (those with quantity lower or equal to the metric).<br>&nbsp;&nbsp;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'),
),
]

View File

@ -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>&nbsp;&nbsp;Best price: Produces the best possible price given all active rating lines (those with quantity lower or equal to the metric).<br>&nbsp;&nbsp;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>&nbsp;&nbsp;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'),
),
]

View File

@ -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>&nbsp;&nbsp;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>&nbsp;&nbsp;Best price: Produces the best possible price given all active rating lines (those with quantity lower or equal to the metric).<br>&nbsp;&nbsp;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'),
),
]

View File

@ -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>&nbsp;&nbsp;Best price: Produces the best possible price given all active rating lines (those with quantity lower or equal to the metric).<br>&nbsp;&nbsp;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>&nbsp;&nbsp;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'),
),
]

View File

@ -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>&nbsp;&nbsp;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>&nbsp;&nbsp;Best price: Produces the best possible price given all active rating lines (those with quantity lower or equal to the metric).<br>&nbsp;&nbsp;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'),
),
]

View File

@ -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>&nbsp;&nbsp;Best price: Produces the best possible price given all active rating lines (those with quantity lower or equal to the metric).<br>&nbsp;&nbsp;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>&nbsp;&nbsp;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'),
),
]

View File

@ -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>&nbsp;&nbsp;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>&nbsp;&nbsp;Best price: Produces the best possible price given all active rating lines (those with quantity lower or equal to the metric).<br>&nbsp;&nbsp;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'),
),
]

View File

@ -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>&nbsp;&nbsp;Best price: Produces the best possible price given all active rating lines (those with quantity lower or equal to the metric).<br>&nbsp;&nbsp;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>&nbsp;&nbsp;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'),
),
]

View File

@ -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'

View File

@ -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'),
),
]

View File

@ -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):

View File

@ -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

View 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,
}

View 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'),
),
]

View File

@ -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
)

View File

@ -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 }}

View File

@ -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'),
),
]

View File

@ -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,
),
]

View 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,
),
]

View File

@ -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=''),
),
]

View File

@ -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')

View File

@ -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

View File

@ -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):

View File

@ -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]

View File

@ -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:

View File

@ -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

View File

@ -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 = [

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2017-06-25 16:41
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('websites', '0007_auto_20170625_1840'),
]
operations = [
migrations.AlterField(
model_name='websitedirective',
name='name',
field=models.CharField(choices=[(None, '-------'), ('ModSecurity', [('sec-rule-remove', 'SecRuleRemoveById'), ('sec-engine', 'SecRuleEngine Off')]), ('SSL', [('ssl-ca', 'SSL CA'), ('ssl-cert', 'SSL cert'), ('ssl-key', 'SSL key')]), ('HTTPD', [('redirect', 'Redirection'), ('proxy', 'Proxy'), ('error-document', 'ErrorDocumentRoot')]), ('SaaS', [('wordpress-saas', 'WordPress SaaS'), ('dokuwiki-saas', 'DokuWiki SaaS'), ('drupal-saas', 'Drupdal SaaS'), ('moodle-saas', 'Moodle SaaS')])], db_index=True, max_length=128, verbose_name='name'),
),
]

Some files were not shown because too many files have changed in this diff Show More