Deprecate ShowTextWidget and ReadOnlyWidget0
This commit is contained in:
parent
929d9beb5c
commit
9c065d401d
TODO.md
orchestra
admin
contrib
accounts
bills
contacts
databases
domains
issues
lists
mailboxes
orchestration
orders
payments
resources
saas
services
settings
systemusers
vps
webapps
websites
core
forms
plugins
settings.pyutils
8
TODO.md
8
TODO.md
|
@ -280,17 +280,15 @@ https://code.djangoproject.com/ticket/24576
|
||||||
# bill.totals make it 100% computed?
|
# bill.totals make it 100% computed?
|
||||||
* joomla: wget https://github.com/joomla/joomla-cms/releases/download/3.4.1/Joomla_3.4.1-Stable-Full_Package.tar.gz -O - | tar xvfz -
|
* joomla: wget https://github.com/joomla/joomla-cms/releases/download/3.4.1/Joomla_3.4.1-Stable-Full_Package.tar.gz -O - | tar xvfz -
|
||||||
|
|
||||||
# replace multichoicefield and jsonfield by ArrayField, HStoreField
|
|
||||||
|
# bill confirmation: show total
|
||||||
# Amend lines???
|
# Amend lines???
|
||||||
|
|
||||||
# Determine the difference between data serializer used for validation and used for the rest API!
|
# Determine the difference between data serializer used for validation and used for the rest API!
|
||||||
# Make PluginApiView that fills metadata and other stuff like modeladmin plugin support
|
# Make PluginApiView that fills metadata and other stuff like modeladmin plugin support
|
||||||
|
|
||||||
# @classmethods do not need to be called with type(object)!
|
|
||||||
|
|
||||||
# Deprectae widgets.showtext and readonlyField by ReadOnlyFormMixin
|
|
||||||
|
|
||||||
# custom validation for settings
|
# custom validation for settings
|
||||||
# TODO orchestra related services code reload: celery/uwsgi reloading find aonther way without root and implement reload
|
# TODO orchestra related services code reload: celery/uwsgi reloading find aonther way without root and implement reload
|
||||||
# insert settings on dashboard dynamically
|
# insert settings on dashboard dynamically
|
||||||
|
|
||||||
|
# convert all complex settings to string
|
||||||
|
|
|
@ -7,7 +7,7 @@ from django.forms.models import modelformset_factory, BaseModelFormSet
|
||||||
from django.template import Template, Context
|
from django.template import Template, Context
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.forms.widgets import ShowTextWidget, ReadOnlyWidget
|
from orchestra.forms.widgets import SpanWidget
|
||||||
|
|
||||||
from ..core.validators import validate_password
|
from ..core.validators import validate_password
|
||||||
|
|
||||||
|
@ -71,10 +71,10 @@ class AdminPasswordChangeForm(forms.Form):
|
||||||
self.user = user
|
self.user = user
|
||||||
super(AdminPasswordChangeForm, self).__init__(*args, **kwargs)
|
super(AdminPasswordChangeForm, self).__init__(*args, **kwargs)
|
||||||
for ix, rel in enumerate(self.related):
|
for ix, rel in enumerate(self.related):
|
||||||
self.fields['password1_%i' % ix] = forms.CharField(
|
self.fields['password1_%i' % ix] = forms.CharField(label=_("Password"),
|
||||||
label=_("Password"), widget=forms.PasswordInput, required=False)
|
widget=forms.PasswordInput, required=False)
|
||||||
self.fields['password2_%i' % ix] = forms.CharField(
|
self.fields['password2_%i' % ix] = forms.CharField(label=_("Password (again)"),
|
||||||
label=_("Password (again)"), widget=forms.PasswordInput, required=False)
|
widget=forms.PasswordInput, required=False)
|
||||||
setattr(self, 'clean_password2_%i' % ix, partial(self.clean_password2, ix=ix))
|
setattr(self, 'clean_password2_%i' % ix, partial(self.clean_password2, ix=ix))
|
||||||
|
|
||||||
def clean_password2(self, ix=''):
|
def clean_password2(self, ix=''):
|
||||||
|
@ -138,21 +138,20 @@ class AdminPasswordChangeForm(forms.Form):
|
||||||
|
|
||||||
class SendEmailForm(forms.Form):
|
class SendEmailForm(forms.Form):
|
||||||
email_from = forms.EmailField(label=_("From"),
|
email_from = forms.EmailField(label=_("From"),
|
||||||
widget=forms.TextInput(attrs={'size': '118'}))
|
widget=forms.TextInput(attrs={'size': '118'}))
|
||||||
to = forms.CharField(label="To", required=False,
|
to = forms.CharField(label="To", required=False)
|
||||||
widget=ShowTextWidget())
|
|
||||||
extra_to = forms.CharField(label="To (extra)", required=False,
|
extra_to = forms.CharField(label="To (extra)", required=False,
|
||||||
widget=forms.TextInput(attrs={'size': '118'}))
|
widget=forms.TextInput(attrs={'size': '118'}))
|
||||||
subject = forms.CharField(label=_("Subject"),
|
subject = forms.CharField(label=_("Subject"),
|
||||||
widget=forms.TextInput(attrs={'size': '118'}))
|
widget=forms.TextInput(attrs={'size': '118'}))
|
||||||
message = forms.CharField(label=_("Message"),
|
message = forms.CharField(label=_("Message"),
|
||||||
widget=forms.Textarea(attrs={'cols': 118, 'rows': 15}))
|
widget=forms.Textarea(attrs={'cols': 118, 'rows': 15}))
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(SendEmailForm, self).__init__(*args, **kwargs)
|
super(SendEmailForm, self).__init__(*args, **kwargs)
|
||||||
initial = kwargs.get('initial')
|
initial = kwargs.get('initial')
|
||||||
if 'to' in initial:
|
if 'to' in initial:
|
||||||
self.fields['to'].widget = ReadOnlyWidget(initial['to'])
|
self.fields['to'].widget = SpanWidget(original=initial['to'])
|
||||||
else:
|
else:
|
||||||
self.fields.pop('to')
|
self.fields.pop('to')
|
||||||
|
|
||||||
|
|
|
@ -5,29 +5,33 @@ from orchestra.settings import ORCHESTRA_BASE_DOMAIN, Setting
|
||||||
|
|
||||||
|
|
||||||
ACCOUNTS_TYPES = Setting('ACCOUNTS_TYPES', (
|
ACCOUNTS_TYPES = Setting('ACCOUNTS_TYPES', (
|
||||||
('INDIVIDUAL', _("Individual")),
|
('INDIVIDUAL', _("Individual")),
|
||||||
('ASSOCIATION', _("Association")),
|
('ASSOCIATION', _("Association")),
|
||||||
('CUSTOMER', _("Customer")),
|
('CUSTOMER', _("Customer")),
|
||||||
('COMPANY', _("Company")),
|
('COMPANY', _("Company")),
|
||||||
('PUBLICBODY', _("Public body")),
|
('PUBLICBODY', _("Public body")),
|
||||||
('STAFF', _("Staff")),
|
('STAFF', _("Staff")),
|
||||||
('FRIEND', _("Friend")),
|
('FRIEND', _("Friend")),
|
||||||
))
|
),
|
||||||
|
validators=[Setting.validate_choices]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
ACCOUNTS_DEFAULT_TYPE = Setting('ACCOUNTS_DEFAULT_TYPE', 'INDIVIDUAL', choices=ACCOUNTS_TYPES)
|
ACCOUNTS_DEFAULT_TYPE = Setting('ACCOUNTS_DEFAULT_TYPE', 'INDIVIDUAL', choices=ACCOUNTS_TYPES)
|
||||||
|
|
||||||
|
|
||||||
ACCOUNTS_LANGUAGES = Setting('ACCOUNTS_LANGUAGES', (
|
ACCOUNTS_LANGUAGES = Setting('ACCOUNTS_LANGUAGES', (
|
||||||
('EN', _('English')),
|
('EN', _('English')),
|
||||||
))
|
),
|
||||||
|
validators=[Setting.validate_choices]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
ACCOUNTS_DEFAULT_LANGUAGE = Setting('ACCOUNTS_DEFAULT_LANGUAGE', 'EN', choices=ACCOUNTS_LANGUAGES)
|
ACCOUNTS_DEFAULT_LANGUAGE = Setting('ACCOUNTS_DEFAULT_LANGUAGE', 'EN', choices=ACCOUNTS_LANGUAGES)
|
||||||
|
|
||||||
|
|
||||||
ACCOUNTS_SYSTEMUSER_MODEL = Setting('ACCOUNTS_SYSTEMUSER_MODEL',
|
ACCOUNTS_SYSTEMUSER_MODEL = Setting('ACCOUNTS_SYSTEMUSER_MODEL', 'systemusers.SystemUser',
|
||||||
'systemusers.SystemUser'
|
validators=[Setting.validate_model_label],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,7 @@ def close_bills(modeladmin, request, queryset):
|
||||||
for bill in queryset:
|
for bill in queryset:
|
||||||
if not validate_contact(request, bill):
|
if not validate_contact(request, bill):
|
||||||
return
|
return
|
||||||
SelectSourceFormSet = adminmodelformset_factory(SelectSourceForm, modeladmin, extra=0)
|
SelectSourceFormSet = adminmodelformset_factory(modeladmin, SelectSourceForm, extra=0)
|
||||||
formset = SelectSourceFormSet(queryset=queryset)
|
formset = SelectSourceFormSet(queryset=queryset)
|
||||||
if request.POST.get('post') == 'generic_confirmation':
|
if request.POST.get('post') == 'generic_confirmation':
|
||||||
formset = SelectSourceFormSet(request.POST, request.FILES, queryset=queryset)
|
formset = SelectSourceFormSet(request.POST, request.FILES, queryset=queryset)
|
||||||
|
|
|
@ -2,37 +2,38 @@ from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.admin.utils import admin_link
|
from orchestra.admin.utils import admin_link
|
||||||
from orchestra.forms.widgets import ShowTextWidget
|
from orchestra.forms import SpanWidget
|
||||||
|
|
||||||
|
|
||||||
class SelectSourceForm(forms.ModelForm):
|
class SelectSourceForm(forms.ModelForm):
|
||||||
bill_link = forms.CharField(label=_("Number"), required=False, widget=ShowTextWidget())
|
bill_link = forms.CharField(label=_("Number"), required=False, widget=SpanWidget)
|
||||||
account_link = forms.CharField(label=_("Account"), required=False)
|
account_link = forms.CharField(label=_("Account"), required=False)
|
||||||
display_total = forms.CharField(label=_("Total"), required=False)
|
show_total = forms.CharField(label=_("Total"), required=False, widget=SpanWidget)
|
||||||
display_type = forms.CharField(label=_("Type"), required=False, widget=ShowTextWidget())
|
display_type = forms.CharField(label=_("Type"), required=False, widget=SpanWidget)
|
||||||
source = forms.ChoiceField(label=_("Source"), required=False)
|
source = forms.ChoiceField(label=_("Source"), required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
fields = (
|
fields = (
|
||||||
'bill_link', 'display_type', 'account_link', 'display_total',
|
'bill_link', 'display_type', 'account_link', 'show_total', 'source'
|
||||||
'source'
|
|
||||||
)
|
)
|
||||||
readonly_fields = ('account_link', 'display_total')
|
readonly_fields = ('account_link',)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(SelectSourceForm, self).__init__(*args, **kwargs)
|
super(SelectSourceForm, self).__init__(*args, **kwargs)
|
||||||
bill = kwargs.get('instance')
|
bill = kwargs.get('instance')
|
||||||
if bill:
|
if bill:
|
||||||
|
total = bill.get_total()
|
||||||
sources = bill.account.paymentsources.filter(is_active=True)
|
sources = bill.account.paymentsources.filter(is_active=True)
|
||||||
recharge = bool(bill.total < 0)
|
recharge = bool(total < 0)
|
||||||
choices = [(None, '-----------')]
|
choices = [(None, '-----------')]
|
||||||
for source in sources:
|
for source in sources:
|
||||||
if not recharge or source.method_class().allow_recharge:
|
if not recharge or source.method_class().allow_recharge:
|
||||||
choices.append((source.pk, str(source)))
|
choices.append((source.pk, str(source)))
|
||||||
self.fields['source'].choices = choices
|
self.fields['source'].choices = choices
|
||||||
self.fields['source'].initial = choices[-1][0]
|
self.fields['source'].initial = choices[-1][0]
|
||||||
self.fields['bill_link'].initial = admin_link('__str__')(bill)
|
self.fields['show_total'].widget.display = total
|
||||||
self.fields['display_type'].initial = bill.get_type_display()
|
self.fields['bill_link'].widget.display = admin_link('__str__')(bill)
|
||||||
|
self.fields['display_type'].widget.display = bill.get_type_display()
|
||||||
|
|
||||||
def clean_source(self):
|
def clean_source(self):
|
||||||
source_id = self.cleaned_data['source']
|
source_id = self.cleaned_data['source']
|
||||||
|
|
|
@ -7,69 +7,43 @@ from orchestra.settings import ORCHESTRA_BASE_DOMAIN, Setting
|
||||||
BILLS_NUMBER_LENGTH = Setting('BILLS_NUMBER_LENGTH', 4)
|
BILLS_NUMBER_LENGTH = Setting('BILLS_NUMBER_LENGTH', 4)
|
||||||
|
|
||||||
|
|
||||||
BILLS_INVOICE_NUMBER_PREFIX = Setting('BILLS_INVOICE_NUMBER_PREFIX',
|
BILLS_INVOICE_NUMBER_PREFIX = Setting('BILLS_INVOICE_NUMBER_PREFIX', 'I')
|
||||||
'I'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
BILLS_AMENDMENT_INVOICE_NUMBER_PREFIX = Setting('BILLS_AMENDMENT_INVOICE_NUMBER_PREFIX',
|
BILLS_AMENDMENT_INVOICE_NUMBER_PREFIX = Setting('BILLS_AMENDMENT_INVOICE_NUMBER_PREFIX', 'A')
|
||||||
'A'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
BILLS_FEE_NUMBER_PREFIX = Setting('BILLS_FEE_NUMBER_PREFIX',
|
BILLS_FEE_NUMBER_PREFIX = Setting('BILLS_FEE_NUMBER_PREFIX', 'F')
|
||||||
'F'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
BILLS_AMENDMENT_FEE_NUMBER_PREFIX = Setting('BILLS_AMENDMENT_FEE_NUMBER_PREFIX',
|
BILLS_AMENDMENT_FEE_NUMBER_PREFIX = Setting('BILLS_AMENDMENT_FEE_NUMBER_PREFIX', 'B')
|
||||||
'B'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
BILLS_PROFORMA_NUMBER_PREFIX = Setting('BILLS_PROFORMA_NUMBER_PREFIX',
|
BILLS_PROFORMA_NUMBER_PREFIX = Setting('BILLS_PROFORMA_NUMBER_PREFIX', 'P')
|
||||||
'P'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
BILLS_DEFAULT_TEMPLATE = Setting('BILLS_DEFAULT_TEMPLATE',
|
BILLS_DEFAULT_TEMPLATE = Setting('BILLS_DEFAULT_TEMPLATE', 'bills/microspective.html')
|
||||||
'bills/microspective.html'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
BILLS_FEE_TEMPLATE = Setting('BILLS_FEE_TEMPLATE',
|
BILLS_FEE_TEMPLATE = Setting('BILLS_FEE_TEMPLATE', 'bills/microspective-fee.html')
|
||||||
'bills/microspective-fee.html'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
BILLS_PROFORMA_TEMPLATE = Setting('BILLS_PROFORMA_TEMPLATE',
|
BILLS_PROFORMA_TEMPLATE = Setting('BILLS_PROFORMA_TEMPLATE', 'bills/microspective-proforma.html')
|
||||||
'bills/microspective-proforma.html'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
BILLS_CURRENCY = Setting('BILLS_CURRENCY',
|
BILLS_CURRENCY = Setting('BILLS_CURRENCY', 'euro')
|
||||||
'euro'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
BILLS_SELLER_PHONE = Setting('BILLS_SELLER_PHONE',
|
BILLS_SELLER_PHONE = Setting('BILLS_SELLER_PHONE', '111-112-11-222')
|
||||||
'111-112-11-222'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
BILLS_SELLER_EMAIL = Setting('BILLS_SELLER_EMAIL',
|
BILLS_SELLER_EMAIL = Setting('BILLS_SELLER_EMAIL', 'sales@{}'.format(ORCHESTRA_BASE_DOMAIN))
|
||||||
'sales@{}'.format(ORCHESTRA_BASE_DOMAIN)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
BILLS_SELLER_WEBSITE = Setting('BILLS_SELLER_WEBSITE',
|
BILLS_SELLER_WEBSITE = Setting('BILLS_SELLER_WEBSITE', 'www.{}'.format(ORCHESTRA_BASE_DOMAIN))
|
||||||
'www.{}'.format(ORCHESTRA_BASE_DOMAIN)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
BILLS_SELLER_BANK_ACCOUNT = Setting('BILLS_SELLER_BANK_ACCOUNT',
|
BILLS_SELLER_BANK_ACCOUNT = Setting('BILLS_SELLER_BANK_ACCOUNT', '0000 0000 00 00000000 (Orchestra Bank)')
|
||||||
'0000 0000 00 00000000 (Orchestra Bank)'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
BILLS_EMAIL_NOTIFICATION_TEMPLATE = Setting('BILLS_EMAIL_NOTIFICATION_TEMPLATE',
|
BILLS_EMAIL_NOTIFICATION_TEMPLATE = Setting('BILLS_EMAIL_NOTIFICATION_TEMPLATE',
|
||||||
|
@ -77,18 +51,16 @@ BILLS_EMAIL_NOTIFICATION_TEMPLATE = Setting('BILLS_EMAIL_NOTIFICATION_TEMPLATE',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
BILLS_ORDER_MODEL = Setting('BILLS_ORDER_MODEL',
|
BILLS_ORDER_MODEL = Setting('BILLS_ORDER_MODEL', 'orders.Order',
|
||||||
'orders.Order'
|
validators=[Setting.validate_model_label]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
BILLS_CONTACT_DEFAULT_CITY = Setting('BILLS_CONTACT_DEFAULT_CITY',
|
BILLS_CONTACT_DEFAULT_CITY = Setting('BILLS_CONTACT_DEFAULT_CITY', 'Barcelona')
|
||||||
'Barcelona'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
BILLS_CONTACT_COUNTRIES = Setting('BILLS_CONTACT_COUNTRIES', tuple((k,v) for k,v in data.COUNTRIES.items()),
|
BILLS_CONTACT_COUNTRIES = Setting('BILLS_CONTACT_COUNTRIES', tuple((k,v) for k,v in data.COUNTRIES.items()),
|
||||||
editable=False
|
serializable=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,32 @@
|
||||||
from django.conf import settings
|
|
||||||
from django_countries import data
|
from django_countries import data
|
||||||
|
|
||||||
from orchestra.settings import Setting
|
from orchestra.settings import Setting
|
||||||
|
|
||||||
|
|
||||||
CONTACTS_DEFAULT_EMAIL_USAGES = Setting('CONTACTS_DEFAULT_EMAIL_USAGES', (
|
CONTACTS_DEFAULT_EMAIL_USAGES = Setting('CONTACTS_DEFAULT_EMAIL_USAGES',
|
||||||
'SUPPORT',
|
default=(
|
||||||
'ADMIN',
|
'SUPPORT',
|
||||||
'BILLING',
|
'ADMIN',
|
||||||
'TECH',
|
'BILLING',
|
||||||
'ADDS',
|
'TECH',
|
||||||
'EMERGENCY'
|
'ADDS',
|
||||||
))
|
'EMERGENCY'
|
||||||
|
),
|
||||||
|
|
||||||
CONTACTS_DEFAULT_CITY = Setting('CONTACTS_DEFAULT_CITY',
|
|
||||||
'Barcelona'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
CONTACTS_COUNTRIES = Setting('CONTACTS_COUNTRIES', tuple((k,v) for k,v in data.COUNTRIES.items()),
|
CONTACTS_DEFAULT_CITY = Setting('CONTACTS_DEFAULT_CITY',
|
||||||
editable=False)
|
default='Barcelona'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
CONTACTS_DEFAULT_COUNTRY = Setting('CONTACTS_DEFAULT_COUNTRY', 'ES', choices=CONTACTS_COUNTRIES)
|
CONTACTS_COUNTRIES = Setting('CONTACTS_COUNTRIES',
|
||||||
|
default=tuple((k,v) for k,v in data.COUNTRIES.items()),
|
||||||
|
serializable=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
CONTACTS_DEFAULT_COUNTRY = Setting('CONTACTS_DEFAULT_COUNTRY',
|
||||||
|
default='ES',
|
||||||
|
choices=CONTACTS_COUNTRIES
|
||||||
|
)
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
from orchestra.settings import Setting
|
from orchestra.settings import Setting
|
||||||
|
|
||||||
|
|
||||||
DATABASES_TYPE_CHOICES = Setting('DATABASES_TYPE_CHOICES', (
|
DATABASES_TYPE_CHOICES = Setting('DATABASES_TYPE_CHOICES', (
|
||||||
('mysql', 'MySQL'),
|
('mysql', 'MySQL'),
|
||||||
('postgres', 'PostgreSQL'),
|
('postgres', 'PostgreSQL'),
|
||||||
))
|
),
|
||||||
|
validators=[Setting.validate_choices]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
DATABASES_DEFAULT_TYPE = Setting('DATABASES_DEFAULT_TYPE', 'mysql', choices=DATABASES_TYPE_CHOICES)
|
DATABASES_DEFAULT_TYPE = Setting('DATABASES_DEFAULT_TYPE', 'mysql', choices=DATABASES_TYPE_CHOICES)
|
||||||
|
|
||||||
|
|
||||||
DATABASES_DEFAULT_HOST = Setting('DATABASES_DEFAULT_HOST',
|
DATABASES_DEFAULT_HOST = Setting('DATABASES_DEFAULT_HOST', 'localhost')
|
||||||
'localhost'
|
|
||||||
)
|
|
||||||
|
|
|
@ -94,7 +94,7 @@ class Domain(models.Model):
|
||||||
return self.origin.subdomain_set.all().prefetch_related('records')
|
return self.origin.subdomain_set.all().prefetch_related('records')
|
||||||
|
|
||||||
def get_parent(self, top=False):
|
def get_parent(self, top=False):
|
||||||
return type(self).get_parent_domain(self.name, top=top)
|
return self.get_parent_domain(self.name, top=top)
|
||||||
|
|
||||||
def render_zone(self):
|
def render_zone(self):
|
||||||
origin = self.origin
|
origin = self.origin
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
from django.conf import settings
|
from orchestra.core.validators import validate_ipv4_address, validate_ipv6_address
|
||||||
|
|
||||||
from orchestra.settings import ORCHESTRA_BASE_DOMAIN, Setting
|
from orchestra.settings import ORCHESTRA_BASE_DOMAIN, Setting
|
||||||
|
|
||||||
|
|
||||||
|
@ -58,18 +57,19 @@ DOMAINS_CHECKZONE_BIN_PATH = Setting('DOMAINS_CHECKZONE_BIN_PATH',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
DOMAINS_ZONE_VALIDATION_TMP_DIR = Setting('DOMAINS_ZONE_VALIDATION_TMP_DIR', '/dev/shm',
|
DOMAINS_ZONE_VALIDATION_TMP_DIR = Setting('DOMAINS_ZONE_VALIDATION_TMP_DIR',
|
||||||
|
'/dev/shm',
|
||||||
help_text="Used for creating temporary zone files used for validation."
|
help_text="Used for creating temporary zone files used for validation."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
DOMAINS_DEFAULT_A = Setting('DOMAINS_DEFAULT_A',
|
DOMAINS_DEFAULT_A = Setting('DOMAINS_DEFAULT_A', '10.0.3.13',
|
||||||
'10.0.3.13'
|
validators=[validate_ipv4_address]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
DOMAINS_DEFAULT_AAAA = Setting('DOMAINS_DEFAULT_AAAA',
|
DOMAINS_DEFAULT_AAAA = Setting('DOMAINS_DEFAULT_AAAA', '',
|
||||||
''
|
validators=[validate_ipv6_address]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -96,11 +96,13 @@ DOMAINS_FORBIDDEN = Setting('DOMAINS_FORBIDDEN', '',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
DOMAINS_MASTERS = Setting('DOMAINS_MASTERS', (),
|
DOMAINS_MASTERS = Setting('DOMAINS_MASTERS',
|
||||||
|
(),
|
||||||
help_text="Additional master server ip addresses other than autodiscovered by router.get_servers()."
|
help_text="Additional master server ip addresses other than autodiscovered by router.get_servers()."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
DOMAINS_SLAVES = Setting('DOMAINS_SLAVES', (),
|
DOMAINS_SLAVES = Setting('DOMAINS_SLAVES',
|
||||||
|
(),
|
||||||
help_text="Additional slave server ip addresses other than autodiscovered by router.get_servers()."
|
help_text="Additional slave server ip addresses other than autodiscovered by router.get_servers()."
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,7 +5,7 @@ from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from markdown import markdown
|
from markdown import markdown
|
||||||
|
|
||||||
from orchestra.forms.widgets import ReadOnlyWidget
|
from orchestra.forms.widgets import SpanWidget
|
||||||
|
|
||||||
from .models import Queue, Ticket
|
from .models import Queue, Ticket
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ class MessageInlineForm(forms.ModelForm):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(MessageInlineForm, self).__init__(*args, **kwargs)
|
super(MessageInlineForm, self).__init__(*args, **kwargs)
|
||||||
self.fields['created_on'].widget = ReadOnlyWidget('')
|
self.fields['created_on'].widget = SpanWidget(display='')
|
||||||
|
|
||||||
def clean_content(self):
|
def clean_content(self):
|
||||||
""" clean HTML tags """
|
""" clean HTML tags """
|
||||||
|
@ -98,7 +98,7 @@ class TicketForm(forms.ModelForm):
|
||||||
description = description.replace('\n', '<br>')
|
description = description.replace('\n', '<br>')
|
||||||
description = description.replace('#Ha9G9-?8', '>\n')
|
description = description.replace('#Ha9G9-?8', '>\n')
|
||||||
description = '<div style="padding-left: 95px;">%s</div>' % description
|
description = '<div style="padding-left: 95px;">%s</div>' % description
|
||||||
widget = ReadOnlyWidget(description, description)
|
widget = SpanWidget(display=description)
|
||||||
self.fields['display_description'].widget = widget
|
self.fields['display_description'].widget = widget
|
||||||
|
|
||||||
def clean_description(self):
|
def clean_description(self):
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
from orchestra.settings import Setting
|
from orchestra.settings import Setting
|
||||||
|
|
||||||
|
|
||||||
ISSUES_SUPPORT_EMAILS = Setting('ISSUES_SUPPORT_EMAILS', [
|
ISSUES_SUPPORT_EMAILS = Setting('ISSUES_SUPPORT_EMAILS', ())
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
ISSUES_NOTIFY_SUPERUSERS = Setting('ISSUES_NOTIFY_SUPERUSERS',
|
ISSUES_NOTIFY_SUPERUSERS = Setting('ISSUES_NOTIFY_SUPERUSERS', True)
|
||||||
True
|
|
||||||
)
|
|
||||||
|
|
|
@ -67,14 +67,15 @@ class MailmanBackend(ServiceController):
|
||||||
context['aliases'] = self.get_virtual_aliases(context)
|
context['aliases'] = self.get_virtual_aliases(context)
|
||||||
# Preserve indentation
|
# Preserve indentation
|
||||||
self.append(textwrap.dedent("""\
|
self.append(textwrap.dedent("""\
|
||||||
|
aliases='%(aliases)s'
|
||||||
if [[ ! $(grep '\s\s*%(name)s\s*$' %(virtual_alias)s) ]]; then
|
if [[ ! $(grep '\s\s*%(name)s\s*$' %(virtual_alias)s) ]]; then
|
||||||
echo '%(aliases)s' >> %(virtual_alias)s
|
echo "${aliases}" >> %(virtual_alias)s
|
||||||
UPDATED_VIRTUAL_ALIAS=1
|
UPDATED_VIRTUAL_ALIAS=1
|
||||||
else
|
else
|
||||||
if [[ ! $(grep '^\s*%(address_name)s@%(address_domain)s\s\s*%(name)s\s*$' %(virtual_alias)s) ]]; then
|
if [[ ! $(grep '^\s*%(address_name)s@%(address_domain)s\s\s*%(name)s\s*$' %(virtual_alias)s) ]]; then
|
||||||
sed -i -e '/^.*\s%(name)s\(%(address_regex)s\)\s*$/d' \\
|
sed -i -e '/^.*\s%(name)s\(%(address_regex)s\)\s*$/d' \\
|
||||||
-e 'N; /^\s*\\n\s*$/d; P; D' %(virtual_alias)s
|
-e 'N; /^\s*\\n\s*$/d; P; D' %(virtual_alias)s
|
||||||
echo '%(aliases)s' >> %(virtual_alias)s
|
echo "${aliases}" >> %(virtual_alias)s
|
||||||
UPDATED_VIRTUAL_ALIAS=1
|
UPDATED_VIRTUAL_ALIAS=1
|
||||||
fi
|
fi
|
||||||
fi""") % context
|
fi""") % context
|
||||||
|
|
|
@ -2,7 +2,7 @@ from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.core.validators import validate_password
|
from orchestra.core.validators import validate_password
|
||||||
from orchestra.forms.widgets import ReadOnlyWidget
|
from orchestra.forms.widgets import SpanWidget
|
||||||
|
|
||||||
|
|
||||||
class CleanAddressMixin(object):
|
class CleanAddressMixin(object):
|
||||||
|
@ -32,8 +32,8 @@ class ListCreationForm(CleanAddressMixin, forms.ModelForm):
|
||||||
|
|
||||||
|
|
||||||
class ListChangeForm(CleanAddressMixin, forms.ModelForm):
|
class ListChangeForm(CleanAddressMixin, forms.ModelForm):
|
||||||
password = forms.CharField(label=_("Password"),
|
password = forms.CharField(label=_("Password"), required=False,
|
||||||
widget=ReadOnlyWidget('<strong>Unknown password</strong>'),
|
widget=SpanWidget(display='<strong>Unknown password</strong>'),
|
||||||
help_text=_("List passwords are not stored, so there is no way to see this "
|
help_text=_("List passwords are not stored, so there is no way to see this "
|
||||||
"list's password, but you can change the password using "
|
"list's password, but you can change the password using "
|
||||||
"<a href=\"password/\">this form</a>."))
|
"<a href=\"password/\">this form</a>."))
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
from orchestra.settings import ORCHESTRA_BASE_DOMAIN, Setting
|
from orchestra.settings import ORCHESTRA_BASE_DOMAIN, Setting
|
||||||
|
|
||||||
|
|
||||||
LISTS_DOMAIN_MODEL = Setting('LISTS_DOMAIN_MODEL',
|
LISTS_DOMAIN_MODEL = Setting('LISTS_DOMAIN_MODEL', 'domains.Domain',
|
||||||
'domains.Domain'
|
validators=[Setting.validate_model_label]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
LISTS_DEFAULT_DOMAIN = Setting('LISTS_DEFAULT_DOMAIN',
|
LISTS_DEFAULT_DOMAIN = Setting('LISTS_DEFAULT_DOMAIN', 'lists.{}'.format(ORCHESTRA_BASE_DOMAIN))
|
||||||
'lists.{}'.format(ORCHESTRA_BASE_DOMAIN)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
LISTS_LIST_URL = Setting('LISTS_LIST_URL',
|
LISTS_LIST_URL = Setting('LISTS_LIST_URL',
|
||||||
|
@ -16,21 +14,13 @@ LISTS_LIST_URL = Setting('LISTS_LIST_URL',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
LISTS_MAILMAN_POST_LOG_PATH = Setting('LISTS_MAILMAN_POST_LOG_PATH',
|
LISTS_MAILMAN_POST_LOG_PATH = Setting('LISTS_MAILMAN_POST_LOG_PATH', '/var/log/mailman/post')
|
||||||
'/var/log/mailman/post'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
LISTS_MAILMAN_ROOT_DIR = Setting('LISTS_MAILMAN_ROOT_DIR',
|
LISTS_MAILMAN_ROOT_DIR = Setting('LISTS_MAILMAN_ROOT_DIR', '/var/lib/mailman')
|
||||||
'/var/lib/mailman'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
LISTS_VIRTUAL_ALIAS_PATH = Setting('LISTS_VIRTUAL_ALIAS_PATH',
|
LISTS_VIRTUAL_ALIAS_PATH = Setting('LISTS_VIRTUAL_ALIAS_PATH', '/etc/postfix/mailman_virtual_aliases')
|
||||||
'/etc/postfix/mailman_virtual_aliases'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
LISTS_VIRTUAL_ALIAS_DOMAINS_PATH = Setting('LISTS_VIRTUAL_ALIAS_DOMAINS_PATH',
|
LISTS_VIRTUAL_ALIAS_DOMAINS_PATH = Setting('LISTS_VIRTUAL_ALIAS_DOMAINS_PATH', '/etc/postfix/mailman_virtual_domains')
|
||||||
'/etc/postfix/mailman_virtual_domains'
|
|
||||||
)
|
|
||||||
|
|
|
@ -3,17 +3,16 @@ import textwrap
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from orchestra.core.validators import validate_name
|
||||||
from orchestra.settings import ORCHESTRA_BASE_DOMAIN, Setting
|
from orchestra.settings import ORCHESTRA_BASE_DOMAIN, Setting
|
||||||
|
|
||||||
|
|
||||||
MAILBOXES_DOMAIN_MODEL = Setting('MAILBOXES_DOMAIN_MODEL',
|
MAILBOXES_DOMAIN_MODEL = Setting('MAILBOXES_DOMAIN_MODEL', 'domains.Domain',
|
||||||
'domains.Domain'
|
validators=[Setting.validate_model_label]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
MAILBOXES_HOME = Setting('MAILBOXES_HOME',
|
MAILBOXES_HOME = Setting('MAILBOXES_HOME', '/home/%(name)s/')
|
||||||
'/home/%(name)s/'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
MAILBOXES_SIEVE_PATH = Setting('MAILBOXES_SIEVE_PATH',
|
MAILBOXES_SIEVE_PATH = Setting('MAILBOXES_SIEVE_PATH',
|
||||||
|
@ -21,13 +20,11 @@ MAILBOXES_SIEVE_PATH = Setting('MAILBOXES_SIEVE_PATH',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
MAILBOXES_SIEVETEST_PATH = Setting('MAILBOXES_SIEVETEST_PATH',
|
MAILBOXES_SIEVETEST_PATH = Setting('MAILBOXES_SIEVETEST_PATH', '/dev/shm')
|
||||||
'/dev/shm'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
MAILBOXES_SIEVETEST_BIN_PATH = Setting('MAILBOXES_SIEVETEST_BIN_PATH',
|
MAILBOXES_SIEVETEST_BIN_PATH = Setting('MAILBOXES_SIEVETEST_BIN_PATH', '%(orchestra_root)s/bin/sieve-test',
|
||||||
'%(orchestra_root)s/bin/sieve-test'
|
validators=[Setting.string_format_validator(('orchestra_root',))]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,14 +43,12 @@ MAILBOXES_VIRTUAL_ALIAS_DOMAINS_PATH = Setting('MAILBOXES_VIRTUAL_ALIAS_DOMAINS_
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
MAILBOXES_LOCAL_DOMAIN = Setting('MAILBOXES_LOCAL_DOMAIN',
|
MAILBOXES_LOCAL_DOMAIN = Setting('MAILBOXES_LOCAL_DOMAIN', ORCHESTRA_BASE_DOMAIN,
|
||||||
ORCHESTRA_BASE_DOMAIN
|
validators=[validate_name]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
MAILBOXES_PASSWD_PATH = Setting('MAILBOXES_PASSWD_PATH',
|
MAILBOXES_PASSWD_PATH = Setting('MAILBOXES_PASSWD_PATH', '/etc/dovecot/passwd')
|
||||||
'/etc/dovecot/passwd'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
MAILBOXES_MAILBOX_FILTERINGS = Setting('MAILBOXES_MAILBOX_FILTERINGS', {
|
MAILBOXES_MAILBOX_FILTERINGS = Setting('MAILBOXES_MAILBOX_FILTERINGS', {
|
||||||
|
@ -80,21 +75,15 @@ MAILBOXES_MAILBOX_DEFAULT_FILTERING = Setting('MAILBOXES_MAILBOX_DEFAULT_FILTERI
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
MAILBOXES_MAILDIRSIZE_PATH = Setting('MAILBOXES_MAILDIRSIZE_PATH',
|
MAILBOXES_MAILDIRSIZE_PATH = Setting('MAILBOXES_MAILDIRSIZE_PATH', '%(home)s/Maildir/maildirsize')
|
||||||
'%(home)s/Maildir/maildirsize'
|
|
||||||
|
|
||||||
|
MAILBOXES_LOCAL_ADDRESS_DOMAIN = Setting('MAILBOXES_LOCAL_ADDRESS_DOMAIN', ORCHESTRA_BASE_DOMAIN,
|
||||||
|
validators=[validate_name]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
MAILBOXES_LOCAL_ADDRESS_DOMAIN = Setting('MAILBOXES_LOCAL_ADDRESS_DOMAIN',
|
MAILBOXES_MAIL_LOG_PATH = Setting('MAILBOXES_MAIL_LOG_PATH', '/var/log/mail.log')
|
||||||
ORCHESTRA_BASE_DOMAIN
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
MAILBOXES_MAIL_LOG_PATH = Setting('MAILBOXES_MAIL_LOG_PATH',
|
MAILBOXES_MOVE_ON_DELETE_PATH = Setting('MAILBOXES_MOVE_ON_DELETE_PATH', '')
|
||||||
'/var/log/mail.log'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
MAILBOXES_MOVE_ON_DELETE_PATH = Setting('MAILBOXES_MOVE_ON_DELETE_PATH',
|
|
||||||
''
|
|
||||||
)
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ def SSH(backend, log, server, cmds, async=False):
|
||||||
script = script.replace('\r', '')
|
script = script.replace('\r', '')
|
||||||
bscript = script.encode('utf-8')
|
bscript = script.encode('utf-8')
|
||||||
digest = hashlib.md5(bscript).hexdigest()
|
digest = hashlib.md5(bscript).hexdigest()
|
||||||
path = os.path.join(settings.ORCHESTRATION_TEMP_SCRIPT_PATH, digest)
|
path = os.path.join(settings.ORCHESTRATION_TEMP_SCRIPT_DIR, digest)
|
||||||
remote_path = "%s.remote" % path
|
remote_path = "%s.remote" % path
|
||||||
log.script = '# %s\n%s' % (remote_path, script)
|
log.script = '# %s\n%s' % (remote_path, script)
|
||||||
log.save(update_fields=['script'])
|
log.save(update_fields=['script'])
|
||||||
|
|
|
@ -94,7 +94,7 @@ class OperationsMiddleware(object):
|
||||||
def process_response(self, request, response):
|
def process_response(self, request, response):
|
||||||
""" Processes pending backend operations """
|
""" Processes pending backend operations """
|
||||||
if not isinstance(response, HttpResponseServerError):
|
if not isinstance(response, HttpResponseServerError):
|
||||||
operations = type(self).get_pending_operations()
|
operations = self.get_pending_operations()
|
||||||
if operations:
|
if operations:
|
||||||
try:
|
try:
|
||||||
scripts, block = manager.generate(operations)
|
scripts, block = manager.generate(operations)
|
||||||
|
|
|
@ -5,8 +5,10 @@ from orchestra.settings import Setting
|
||||||
|
|
||||||
|
|
||||||
ORCHESTRATION_OS_CHOICES = Setting('ORCHESTRATION_OS_CHOICES', (
|
ORCHESTRATION_OS_CHOICES = Setting('ORCHESTRATION_OS_CHOICES', (
|
||||||
('LINUX', "Linux"),
|
('LINUX', "Linux"),
|
||||||
))
|
),
|
||||||
|
validators=[Setting.validate_choices]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
ORCHESTRATION_DEFAULT_OS = Setting('ORCHESTRATION_DEFAULT_OS', 'LINUX',
|
ORCHESTRATION_DEFAULT_OS = Setting('ORCHESTRATION_DEFAULT_OS', 'LINUX',
|
||||||
|
@ -17,19 +19,15 @@ ORCHESTRATION_SSH_KEY_PATH = Setting('ORCHESTRATION_SSH_KEY_PATH',
|
||||||
path.join(path.expanduser('~'), '.ssh/id_rsa'))
|
path.join(path.expanduser('~'), '.ssh/id_rsa'))
|
||||||
|
|
||||||
|
|
||||||
ORCHESTRATION_ROUTER = Setting('ORCHESTRATION_ROUTER',
|
ORCHESTRATION_ROUTER = Setting('ORCHESTRATION_ROUTER', 'orchestra.contrib.orchestration.models.Route',
|
||||||
'orchestra.contrib.orchestration.models.Route'
|
validators=[Setting.validate_import_class]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
ORCHESTRATION_TEMP_SCRIPT_PATH = Setting('ORCHESTRATION_TEMP_SCRIPT_PATH',
|
ORCHESTRATION_TEMP_SCRIPT_DIR = Setting('ORCHESTRATION_TEMP_SCRIPT_DIR', '/dev/shm')
|
||||||
'/dev/shm'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
ORCHESTRATION_DISABLE_EXECUTION = Setting('ORCHESTRATION_DISABLE_EXECUTION',
|
ORCHESTRATION_DISABLE_EXECUTION = Setting('ORCHESTRATION_DISABLE_EXECUTION', False)
|
||||||
False
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
ORCHESTRATION_BACKEND_CLEANUP_DELTA = Setting('ORCHESTRATION_BACKEND_CLEANUP_DELTA',
|
ORCHESTRATION_BACKEND_CLEANUP_DELTA = Setting('ORCHESTRATION_BACKEND_CLEANUP_DELTA',
|
||||||
|
|
|
@ -91,7 +91,6 @@ class BillSelectedOrders(object):
|
||||||
url = reverse('admin:bills_bill_changelist')
|
url = reverse('admin:bills_bill_changelist')
|
||||||
ids = ','.join(map(str, bills))
|
ids = ','.join(map(str, bills))
|
||||||
url += '?id__in=%s' % ids
|
url += '?id__in=%s' % ids
|
||||||
num = len(bills)
|
|
||||||
msg = ungettext(
|
msg = ungettext(
|
||||||
'<a href="{url}">One bill</a> has been created.',
|
'<a href="{url}">One bill</a> has been created.',
|
||||||
'<a href="{url}">{num} bills</a> have been created.',
|
'<a href="{url}">{num} bills</a> have been created.',
|
||||||
|
@ -100,11 +99,18 @@ class BillSelectedOrders(object):
|
||||||
self.modeladmin.message_user(request, msg, messages.INFO)
|
self.modeladmin.message_user(request, msg, messages.INFO)
|
||||||
return
|
return
|
||||||
bills = self.queryset.bill(commit=False, **self.options)
|
bills = self.queryset.bill(commit=False, **self.options)
|
||||||
|
bills_with_total = []
|
||||||
|
for account, lines in bills:
|
||||||
|
total = 0
|
||||||
|
for line in lines:
|
||||||
|
discount = sum([discount.total for discount in line.discounts])
|
||||||
|
total += line.subtotal + discount
|
||||||
|
bills_with_total.append((account, total, lines))
|
||||||
self.context.update({
|
self.context.update({
|
||||||
'title': _("Confirmation for billing selected orders"),
|
'title': _("Confirmation for billing selected orders"),
|
||||||
'step': 3,
|
'step': 3,
|
||||||
'form': form,
|
'form': form,
|
||||||
'bills': bills,
|
'bills': bills_with_total,
|
||||||
})
|
})
|
||||||
return render(request, self.template, self.context)
|
return render(request, self.template, self.context)
|
||||||
|
|
||||||
|
|
|
@ -1,34 +1,34 @@
|
||||||
from orchestra.settings import Setting
|
from orchestra.settings import Setting
|
||||||
|
|
||||||
|
|
||||||
# Pluggable backend for bill generation.
|
ORDERS_BILLING_BACKEND = Setting('ORDERS_BILLING_BACKEND', 'orchestra.contrib.orders.billing.BillsBackend',
|
||||||
ORDERS_BILLING_BACKEND = Setting('ORDERS_BILLING_BACKEND',
|
validators=[Setting.validate_import_class],
|
||||||
'orchestra.contrib.orders.billing.BillsBackend'
|
help_text="Pluggable backend for bill generation.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Pluggable service class
|
ORDERS_SERVICE_MODEL = Setting('ORDERS_SERVICE_MODEL', 'services.Service',
|
||||||
ORDERS_SERVICE_MODEL = Setting('ORDERS_SERVICE_MODEL',
|
validators=[Setting.validate_model_label],
|
||||||
'services.Service'
|
help_text="Pluggable service class."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Prevent inspecting these apps for service accounting
|
|
||||||
ORDERS_EXCLUDED_APPS = Setting('ORDERS_EXCLUDED_APPS', (
|
ORDERS_EXCLUDED_APPS = Setting('ORDERS_EXCLUDED_APPS', (
|
||||||
'orders',
|
'orders',
|
||||||
'admin',
|
'admin',
|
||||||
'contenttypes',
|
'contenttypes',
|
||||||
'auth',
|
'auth',
|
||||||
'migrations',
|
'migrations',
|
||||||
'sessions',
|
'sessions',
|
||||||
'orchestration',
|
'orchestration',
|
||||||
'bills',
|
'bills',
|
||||||
'services',
|
'services',
|
||||||
))
|
),
|
||||||
|
help_text="Prevent inspecting these apps for service accounting."
|
||||||
|
)
|
||||||
# Only account for significative changes
|
|
||||||
# metric_storage new value: lastvalue*(1+threshold) > currentvalue or lastvalue*threshold < currentvalue
|
|
||||||
ORDERS_METRIC_ERROR = Setting('ORDERS_METRIC_ERROR',
|
ORDERS_METRIC_ERROR = Setting('ORDERS_METRIC_ERROR', 0.01,
|
||||||
0.01
|
help_text=("Only account for significative changes.<br>"
|
||||||
|
"metric_storage new value: <tt>lastvalue*(1+threshold) > currentvalue or lastvalue*threshold < currentvalue</tt>.")
|
||||||
)
|
)
|
||||||
|
|
|
@ -28,11 +28,11 @@
|
||||||
<div>
|
<div>
|
||||||
<div style="margin:20px;">
|
<div style="margin:20px;">
|
||||||
{% if bills %}
|
{% if bills %}
|
||||||
{% for account, lines in bills %}
|
{% for account, total, lines in bills %}
|
||||||
<div class="inline-group" id="rates-group">
|
<div class="inline-group" id="rates-group">
|
||||||
<div class="tabular inline-related last-related">
|
<div class="tabular inline-related last-related">
|
||||||
<fieldset class="module">
|
<fieldset class="module">
|
||||||
<h2><a href="{% url 'admin:accounts_account_change' account.pk %}">{{ account }}</a></h2>
|
<h2><a href="{% url 'admin:accounts_account_change' account.pk %}">{{ account }}</a><span style="float:right">{{ total | floatformat:"-2" }} €</span></h2>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr><th style="width:30%;">Description</th> <th style="width:30%;">Period</th> <th style="width:10%;">Quantity</th> <th style="width:10%;">Price</th></tr>
|
<tr><th style="width:30%;">Description</th> <th style="width:30%;">Period</th> <th style="width:10%;">Quantity</th> <th style="width:10%;">Price</th></tr>
|
||||||
|
|
|
@ -3,25 +3,19 @@ from orchestra.settings import Setting
|
||||||
from .. import payments
|
from .. import payments
|
||||||
|
|
||||||
|
|
||||||
PAYMENT_CURRENCY = Setting('PAYMENT_CURRENCY',
|
PAYMENT_CURRENCY = Setting('PAYMENT_CURRENCY', 'Eur')
|
||||||
'Eur'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
PAYMENTS_DD_CREDITOR_NAME = Setting('PAYMENTS_DD_CREDITOR_NAME',
|
PAYMENTS_DD_CREDITOR_NAME = Setting('PAYMENTS_DD_CREDITOR_NAME', 'Orchestra')
|
||||||
'Orchestra')
|
|
||||||
|
|
||||||
|
|
||||||
PAYMENTS_DD_CREDITOR_IBAN = Setting('PAYMENTS_DD_CREDITOR_IBAN',
|
PAYMENTS_DD_CREDITOR_IBAN = Setting('PAYMENTS_DD_CREDITOR_IBAN', 'IE98BOFI90393912121212')
|
||||||
'IE98BOFI90393912121212')
|
|
||||||
|
|
||||||
|
|
||||||
PAYMENTS_DD_CREDITOR_BIC = Setting('PAYMENTS_DD_CREDITOR_BIC',
|
PAYMENTS_DD_CREDITOR_BIC = Setting('PAYMENTS_DD_CREDITOR_BIC', 'BOFIIE2D')
|
||||||
'BOFIIE2D')
|
|
||||||
|
|
||||||
|
|
||||||
PAYMENTS_DD_CREDITOR_AT02_ID = Setting('PAYMENTS_DD_CREDITOR_AT02_ID',
|
PAYMENTS_DD_CREDITOR_AT02_ID = Setting('PAYMENTS_DD_CREDITOR_AT02_ID', 'InvalidAT02ID')
|
||||||
'InvalidAT02ID')
|
|
||||||
|
|
||||||
|
|
||||||
PAYMENTS_ENABLED_METHODS = Setting('PAYMENTS_ENABLED_METHODS', (
|
PAYMENTS_ENABLED_METHODS = Setting('PAYMENTS_ENABLED_METHODS', (
|
||||||
|
|
|
@ -42,7 +42,7 @@ class ServiceMonitor(ServiceBackend):
|
||||||
from .models import MonitorData
|
from .models import MonitorData
|
||||||
try:
|
try:
|
||||||
return MonitorData.objects.filter(content_type=self.content_type,
|
return MonitorData.objects.filter(content_type=self.content_type,
|
||||||
monitor=type(self).get_name(), object_id=object_id).latest()
|
monitor=self.get_name(), object_id=object_id).latest()
|
||||||
except MonitorData.DoesNotExist:
|
except MonitorData.DoesNotExist:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
|
@ -1,30 +1,38 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.forms.widgets import ShowTextWidget, ReadOnlyWidget
|
from orchestra.forms import ReadOnlyFormMixin
|
||||||
|
from orchestra.forms.widgets import SpanWidget
|
||||||
|
|
||||||
|
|
||||||
class ResourceForm(forms.ModelForm):
|
class ResourceForm(ReadOnlyFormMixin, forms.ModelForm):
|
||||||
verbose_name = forms.CharField(label=_("Name"), required=False,
|
verbose_name = forms.CharField(label=_("Name"), required=False,
|
||||||
widget=ShowTextWidget(tag='<b>'))
|
widget=SpanWidget(tag='<b>'))
|
||||||
allocated = forms.DecimalField(label=_("Allocated"))
|
allocated = forms.DecimalField(label=_("Allocated"))
|
||||||
unit = forms.CharField(label=_("Unit"), widget=ShowTextWidget(), required=False)
|
unit = forms.CharField(label=_("Unit"), required=False)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
fields = ('verbose_name', 'used', 'last_update', 'allocated', 'unit')
|
fields = ('verbose_name', 'used', 'last_update', 'allocated', 'unit')
|
||||||
|
readonly_fields = ('verbose_name', 'unit')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.resource = kwargs.pop('resource', None)
|
self.resource = kwargs.pop('resource', None)
|
||||||
|
if self.resource:
|
||||||
|
initial = kwargs.get('initial', {})
|
||||||
|
initial.update({
|
||||||
|
'verbose_name': self.resource.get_verbose_name(),
|
||||||
|
'unit': self.resource.unit,
|
||||||
|
})
|
||||||
|
kwargs['initial'] = initial
|
||||||
super(ResourceForm, self).__init__(*args, **kwargs)
|
super(ResourceForm, self).__init__(*args, **kwargs)
|
||||||
if self.resource:
|
if self.resource:
|
||||||
self.fields['verbose_name'].initial = self.resource.get_verbose_name()
|
|
||||||
self.fields['unit'].initial = self.resource.unit
|
|
||||||
if self.resource.on_demand:
|
if self.resource.on_demand:
|
||||||
self.fields['allocated'].required = False
|
self.fields['allocated'].required = False
|
||||||
self.fields['allocated'].widget = ReadOnlyWidget(None, '')
|
self.fields['allocated'].widget = SpanWidget(original=None, display='')
|
||||||
else:
|
else:
|
||||||
self.fields['allocated'].required = True
|
self.fields['allocated'].required = True
|
||||||
self.fields['allocated'].initial = self.resource.default_allocation
|
self.fields['allocated'].initial = self.resource.default_allocation
|
||||||
|
|
||||||
# def has_changed(self):
|
# def has_changed(self):
|
||||||
# """ Make sure resourcedata objects are created for all resources """
|
# """ Make sure resourcedata objects are created for all resources """
|
||||||
# if not self.instance.pk:
|
# if not self.instance.pk:
|
||||||
|
|
|
@ -14,8 +14,8 @@ class GitLabForm(SoftwareServiceForm):
|
||||||
help_text=_("Initial email address, changes on the GitLab server are not reflected here."))
|
help_text=_("Initial email address, changes on the GitLab server are not reflected here."))
|
||||||
|
|
||||||
|
|
||||||
class GitLaChangebForm(GitLabForm):
|
class GitLaChangeForm(GitLabForm):
|
||||||
user_id = forms.IntegerField(label=("User ID"), widget=widgets.ShowTextWidget,
|
user_id = forms.IntegerField(label=("User ID"), widget=widgets.SpanWidget,
|
||||||
help_text=_("ID of this user on the GitLab server, the only attribute that not changes."))
|
help_text=_("ID of this user on the GitLab server, the only attribute that not changes."))
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ class GitLabSerializer(serializers.Serializer):
|
||||||
class GitLabService(SoftwareService):
|
class GitLabService(SoftwareService):
|
||||||
name = 'gitlab'
|
name = 'gitlab'
|
||||||
form = GitLabForm
|
form = GitLabForm
|
||||||
change_form = GitLaChangebForm
|
change_form = GitLaChangeForm
|
||||||
serializer = GitLabSerializer
|
serializer = GitLabSerializer
|
||||||
site_domain = settings.SAAS_GITLAB_DOMAIN
|
site_domain = settings.SAAS_GITLAB_DOMAIN
|
||||||
change_readonly_fileds = ('email', 'user_id',)
|
change_readonly_fileds = ('email', 'user_id',)
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
from orchestra import plugins
|
from orchestra import plugins
|
||||||
from orchestra.contrib.orchestration import Operation
|
from orchestra.contrib.orchestration import Operation
|
||||||
from orchestra.core import validators
|
from orchestra.core import validators
|
||||||
from orchestra.forms import widgets
|
from orchestra.forms.widgets import SpanWidget
|
||||||
from orchestra.plugins.forms import PluginDataForm
|
from orchestra.plugins.forms import PluginDataForm
|
||||||
from orchestra.utils.functional import cached
|
from orchestra.utils.functional import cached
|
||||||
from orchestra.utils.python import import_class, random_ascii
|
from orchestra.utils.python import import_class, random_ascii
|
||||||
|
@ -15,9 +15,9 @@ from .. import settings
|
||||||
|
|
||||||
|
|
||||||
class SoftwareServiceForm(PluginDataForm):
|
class SoftwareServiceForm(PluginDataForm):
|
||||||
site_url = forms.CharField(label=_("Site URL"), widget=widgets.ShowTextWidget, required=False)
|
site_url = forms.CharField(label=_("Site URL"), widget=SpanWidget(), required=False)
|
||||||
password = forms.CharField(label=_("Password"), required=False,
|
password = forms.CharField(label=_("Password"), required=False,
|
||||||
widget=widgets.ReadOnlyWidget('<strong>Unknown password</strong>'),
|
widget=SpanWidget(display='<strong>Unknown password</strong>'),
|
||||||
validators=[
|
validators=[
|
||||||
validators.validate_password,
|
validators.validate_password,
|
||||||
RegexValidator(r'^[^"\'\\]+$',
|
RegexValidator(r'^[^"\'\\]+$',
|
||||||
|
@ -36,6 +36,7 @@ class SoftwareServiceForm(PluginDataForm):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
exclude = ('database',)
|
exclude = ('database',)
|
||||||
|
readonly_fields = ('site_url',)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(SoftwareServiceForm, self).__init__(*args, **kwargs)
|
super(SoftwareServiceForm, self).__init__(*args, **kwargs)
|
||||||
|
@ -54,7 +55,7 @@ class SoftwareServiceForm(PluginDataForm):
|
||||||
site_link = '<a href="http://%s">%s</a>' % (site_domain, site_domain)
|
site_link = '<a href="http://%s">%s</a>' % (site_domain, site_domain)
|
||||||
else:
|
else:
|
||||||
site_link = '<site_name>.%s' % self.plugin.site_base_domain
|
site_link = '<site_name>.%s' % self.plugin.site_base_domain
|
||||||
self.fields['site_url'].initial = site_link
|
self.fields['site_url'].widget.display = site_link
|
||||||
self.fields['name'].label = _("Username")
|
self.fields['name'].label = _("Username")
|
||||||
|
|
||||||
def clean_password2(self):
|
def clean_password2(self):
|
||||||
|
|
|
@ -5,7 +5,7 @@ from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.contrib.databases.models import Database, DatabaseUser
|
from orchestra.contrib.databases.models import Database, DatabaseUser
|
||||||
from orchestra.forms import widgets
|
from orchestra.forms.widgets import SpanWidget
|
||||||
|
|
||||||
from .. import settings
|
from .. import settings
|
||||||
from .options import SoftwareService, SoftwareServiceForm
|
from .options import SoftwareService, SoftwareServiceForm
|
||||||
|
@ -13,7 +13,7 @@ from .options import SoftwareService, SoftwareServiceForm
|
||||||
|
|
||||||
class PHPListForm(SoftwareServiceForm):
|
class PHPListForm(SoftwareServiceForm):
|
||||||
admin_username = forms.CharField(label=_("Admin username"), required=False,
|
admin_username = forms.CharField(label=_("Admin username"), required=False,
|
||||||
widget=widgets.ReadOnlyWidget('admin'))
|
widget=SpanWidget(display='admin'))
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(PHPListForm, self).__init__(*args, **kwargs)
|
super(PHPListForm, self).__init__(*args, **kwargs)
|
||||||
|
@ -37,7 +37,7 @@ class PHPListChangeForm(PHPListForm):
|
||||||
db = self.instance.database
|
db = self.instance.database
|
||||||
db_url = reverse('admin:databases_database_change', args=(db.pk,))
|
db_url = reverse('admin:databases_database_change', args=(db.pk,))
|
||||||
db_link = mark_safe('<a href="%s">%s</a>' % (db_url, db.name))
|
db_link = mark_safe('<a href="%s">%s</a>' % (db_url, db.name))
|
||||||
self.fields['database'].widget = widgets.ReadOnlyWidget(db.name, db_link)
|
self.fields['database'].widget = SpanWidget(original=db.name, display=db_link)
|
||||||
|
|
||||||
|
|
||||||
class PHPListService(SoftwareService):
|
class PHPListService(SoftwareService):
|
||||||
|
@ -57,7 +57,7 @@ class PHPListService(SoftwareService):
|
||||||
return settings.SAAS_PHPLIST_DB_NAME
|
return settings.SAAS_PHPLIST_DB_NAME
|
||||||
|
|
||||||
def get_account(self):
|
def get_account(self):
|
||||||
return type(self.instance.account).get_main()
|
return self.instance.account.get_main()
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
super(PHPListService, self).validate()
|
super(PHPListService, self).validate()
|
||||||
|
|
|
@ -19,9 +19,7 @@ SAAS_ENABLED_SERVICES = Setting('SAAS_ENABLED_SERVICES', (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
SAAS_WORDPRESS_ADMIN_PASSWORD = Setting('SAAS_WORDPRESSMU_ADMIN_PASSWORD',
|
SAAS_WORDPRESS_ADMIN_PASSWORD = Setting('SAAS_WORDPRESSMU_ADMIN_PASSWORD', 'secret')
|
||||||
'secret'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
SAAS_WORDPRESS_BASE_URL = Setting('SAAS_WORDPRESS_BASE_URL',
|
SAAS_WORDPRESS_BASE_URL = Setting('SAAS_WORDPRESS_BASE_URL',
|
||||||
|
@ -86,4 +84,3 @@ SAAS_GITLAB_ROOT_PASSWORD = Setting('SAAS_GITLAB_ROOT_PASSWORD',
|
||||||
SAAS_GITLAB_DOMAIN = Setting('SAAS_GITLAB_DOMAIN',
|
SAAS_GITLAB_DOMAIN = Setting('SAAS_GITLAB_DOMAIN',
|
||||||
'gitlab.{}'.format(ORCHESTRA_BASE_DOMAIN)
|
'gitlab.{}'.format(ORCHESTRA_BASE_DOMAIN)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ from django.utils.translation import ugettext, ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra import plugins
|
from orchestra import plugins
|
||||||
from orchestra.utils.humanize import text2int
|
from orchestra.utils.humanize import text2int
|
||||||
from orchestra.utils.python import AttrDict, cmp_to_key
|
from orchestra.utils.python import AttrDict, cmp_to_key, format_exception
|
||||||
|
|
||||||
from . import settings, helpers
|
from . import settings, helpers
|
||||||
|
|
||||||
|
@ -50,8 +50,7 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount):
|
||||||
try:
|
try:
|
||||||
bool(getattr(self, method)(obj))
|
bool(getattr(self, method)(obj))
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
name = type(exception).__name__
|
raise ValidationError(format_exception(exc))
|
||||||
raise ValidationError(': '.join((name, str(exception))))
|
|
||||||
|
|
||||||
def validate_match(self, service):
|
def validate_match(self, service):
|
||||||
if not service.match:
|
if not service.match:
|
||||||
|
|
|
@ -4,9 +4,11 @@ from orchestra.settings import Setting
|
||||||
|
|
||||||
|
|
||||||
SERVICES_SERVICE_TAXES = Setting('SERVICES_SERVICE_TAXES', (
|
SERVICES_SERVICE_TAXES = Setting('SERVICES_SERVICE_TAXES', (
|
||||||
(0, _("Duty free")),
|
(0, _("Duty free")),
|
||||||
(21, "21%"),
|
(21, "21%"),
|
||||||
))
|
),
|
||||||
|
validators=[Setting.validate_choices]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
SERVICES_SERVICE_DEFAULT_TAX = Setting('SERVICES_SERVICE_DEFAULT_TAX', 0,
|
SERVICES_SERVICE_DEFAULT_TAX = Setting('SERVICES_SERVICE_DEFAULT_TAX', 0,
|
||||||
|
@ -19,19 +21,17 @@ SERVICES_SERVICE_ANUAL_BILLING_MONTH = Setting('SERVICES_SERVICE_ANUAL_BILLING_M
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
SERVICES_ORDER_MODEL = Setting('SERVICES_ORDER_MODEL',
|
SERVICES_ORDER_MODEL = Setting('SERVICES_ORDER_MODEL', 'orders.Order',
|
||||||
'orders.Order'
|
validators=[Setting.validate_model_label]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
SERVICES_RATE_CLASS = Setting('SERVICES_RATE_CLASS',
|
SERVICES_RATE_CLASS = Setting('SERVICES_RATE_CLASS', 'orchestra.contrib.plans.models.Rate',
|
||||||
'orchestra.contrib.plans.models.Rate'
|
validators=[Setting.validate_import_class]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
SERVICES_DEFAULT_IGNORE_PERIOD = Setting('SERVICES_DEFAULT_IGNORE_PERIOD',
|
SERVICES_DEFAULT_IGNORE_PERIOD = Setting('SERVICES_DEFAULT_IGNORE_PERIOD', 'TEN_DAYS')
|
||||||
'TEN_DAYS'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
SERVICES_IGNORE_ACCOUNT_TYPE = Setting('SERVICES_IGNORE_ACCOUNT_TYPE', (
|
SERVICES_IGNORE_ACCOUNT_TYPE = Setting('SERVICES_IGNORE_ACCOUNT_TYPE', (
|
||||||
|
|
|
@ -5,9 +5,11 @@ from functools import partial
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.forms.formsets import formset_factory
|
from django.forms.formsets import formset_factory
|
||||||
|
from django.utils.functional import Promise
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.forms import ReadOnlyFormMixin, widgets
|
from orchestra.forms import ReadOnlyFormMixin, widgets
|
||||||
|
from orchestra.utils.python import format_exception
|
||||||
|
|
||||||
from . import parser
|
from . import parser
|
||||||
|
|
||||||
|
@ -20,21 +22,21 @@ class SettingForm(ReadOnlyFormMixin, forms.Form):
|
||||||
widget=forms.Textarea(attrs={
|
widget=forms.Textarea(attrs={
|
||||||
'cols': 65,
|
'cols': 65,
|
||||||
'rows': 2,
|
'rows': 2,
|
||||||
'style': 'font-family:monospace'
|
'style': 'font-family: monospace',
|
||||||
}))
|
}))
|
||||||
CHARFIELD = partial(forms.CharField,
|
CHARFIELD = partial(forms.CharField,
|
||||||
widget=forms.TextInput(attrs={
|
widget=forms.TextInput(attrs={
|
||||||
'size': 65,
|
'size': 65,
|
||||||
'style': 'font-family:monospace'
|
'style': 'font-family: monospace',
|
||||||
}))
|
}))
|
||||||
NON_EDITABLE = partial(forms.CharField, widget=widgets.ShowTextWidget(), required=False)
|
NON_EDITABLE = partial(forms.CharField, widget=widgets.SpanWidget, required=False)
|
||||||
FORMFIELD_FOR_SETTING_TYPE = {
|
FORMFIELD_FOR_SETTING_TYPE = {
|
||||||
bool: partial(forms.BooleanField, required=False),
|
bool: partial(forms.BooleanField, required=False),
|
||||||
int: forms.IntegerField,
|
int: forms.IntegerField,
|
||||||
tuple: TEXTAREA,
|
tuple: TEXTAREA,
|
||||||
list: TEXTAREA,
|
list: TEXTAREA,
|
||||||
dict: TEXTAREA,
|
dict: TEXTAREA,
|
||||||
type(_()): CHARFIELD,
|
Promise: CHARFIELD,
|
||||||
str: CHARFIELD,
|
str: CHARFIELD,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,8 +52,12 @@ class SettingForm(ReadOnlyFormMixin, forms.Form):
|
||||||
self.setting_type = initial['type']
|
self.setting_type = initial['type']
|
||||||
self.setting = initial['setting']
|
self.setting = initial['setting']
|
||||||
setting = self.setting
|
setting = self.setting
|
||||||
serialized_value = parser.serialize(initial['value'])
|
if setting.serializable:
|
||||||
serialized_default = parser.serialize(initial['default'])
|
serialized_value = parser.serialize(initial['value'])
|
||||||
|
serialized_default = parser.serialize(initial['default'])
|
||||||
|
else:
|
||||||
|
serialized_value = parser.NotSupported()
|
||||||
|
serialized_default = parser.NotSupported()
|
||||||
if not setting.editable or isinstance(serialized_value, parser.NotSupported):
|
if not setting.editable or isinstance(serialized_value, parser.NotSupported):
|
||||||
field = self.NON_EDITABLE
|
field = self.NON_EDITABLE
|
||||||
else:
|
else:
|
||||||
|
@ -101,7 +107,7 @@ class SettingForm(ReadOnlyFormMixin, forms.Form):
|
||||||
try:
|
try:
|
||||||
value = eval(value, parser.get_eval_context())
|
value = eval(value, parser.get_eval_context())
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise ValidationError(str(exc))
|
raise ValidationError(format_exception(exc))
|
||||||
self.setting.validate_value(value)
|
self.setting.validate_value(value)
|
||||||
if not isinstance(value, self.setting_type):
|
if not isinstance(value, self.setting_type):
|
||||||
if self.setting_type in (tuple, list) and isinstance(value, (tuple, list)):
|
if self.setting_type in (tuple, list) and isinstance(value, (tuple, list)):
|
||||||
|
|
|
@ -3,6 +3,7 @@ import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.utils.functional import Promise
|
||||||
|
|
||||||
from orchestra.utils.paths import get_project_dir
|
from orchestra.utils.paths import get_project_dir
|
||||||
|
|
||||||
|
@ -60,7 +61,7 @@ def get_eval_context():
|
||||||
def serialize(obj, init=True):
|
def serialize(obj, init=True):
|
||||||
if isinstance(obj, NotSupported):
|
if isinstance(obj, NotSupported):
|
||||||
return obj
|
return obj
|
||||||
elif isinstance(obj, type(_())):
|
elif isinstance(obj, Promise):
|
||||||
_obj = LazyUgettextRepr(obj)
|
_obj = LazyUgettextRepr(obj)
|
||||||
elif isinstance(obj, dict):
|
elif isinstance(obj, dict):
|
||||||
_obj = {}
|
_obj = {}
|
||||||
|
|
|
@ -4,11 +4,13 @@ from orchestra.settings import Setting
|
||||||
|
|
||||||
|
|
||||||
SYSTEMUSERS_SHELLS = Setting('SYSTEMUSERS_SHELLS', (
|
SYSTEMUSERS_SHELLS = Setting('SYSTEMUSERS_SHELLS', (
|
||||||
('/dev/null', _("No shell, FTP only")),
|
('/dev/null', _("No shell, FTP only")),
|
||||||
('/bin/rssh', _("No shell, SFTP/RSYNC only")),
|
('/bin/rssh', _("No shell, SFTP/RSYNC only")),
|
||||||
('/bin/bash', "/bin/bash"),
|
('/bin/bash', "/bin/bash"),
|
||||||
('/bin/sh', "/bin/sh"),
|
('/bin/sh', "/bin/sh"),
|
||||||
))
|
),
|
||||||
|
validators=[Setting.validate_choices]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
SYSTEMUSERS_DEFAULT_SHELL = Setting('SYSTEMUSERS_DEFAULT_SHELL', '/dev/null',
|
SYSTEMUSERS_DEFAULT_SHELL = Setting('SYSTEMUSERS_DEFAULT_SHELL', '/dev/null',
|
||||||
|
@ -16,10 +18,12 @@ SYSTEMUSERS_DEFAULT_SHELL = Setting('SYSTEMUSERS_DEFAULT_SHELL', '/dev/null',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
SYSTEMUSERS_DISABLED_SHELLS = Setting('SYSTEMUSERS_DISABLED_SHELLS', (
|
SYSTEMUSERS_DISABLED_SHELLS = Setting('SYSTEMUSERS_DISABLED_SHELLS',
|
||||||
'/dev/null',
|
default=(
|
||||||
'/bin/rssh',
|
'/dev/null',
|
||||||
))
|
'/bin/rssh',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
SYSTEMUSERS_HOME = Setting('SYSTEMUSERS_HOME',
|
SYSTEMUSERS_HOME = Setting('SYSTEMUSERS_HOME',
|
||||||
|
|
|
@ -2,16 +2,20 @@ from orchestra.settings import Setting
|
||||||
|
|
||||||
|
|
||||||
VPS_TYPES = Setting('VPS_TYPES', (
|
VPS_TYPES = Setting('VPS_TYPES', (
|
||||||
('openvz', 'OpenVZ container'),
|
('openvz', 'OpenVZ container'),
|
||||||
))
|
),
|
||||||
|
validators=[Setting.validate_choices]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
VPS_DEFAULT_TYPE = Setting('VPS_DEFAULT_TYPE', 'openvz', choices=VPS_TYPES)
|
VPS_DEFAULT_TYPE = Setting('VPS_DEFAULT_TYPE', 'openvz', choices=VPS_TYPES)
|
||||||
|
|
||||||
|
|
||||||
VPS_TEMPLATES = Setting('VPS_TEMPLATES', (
|
VPS_TEMPLATES = Setting('VPS_TEMPLATES', (
|
||||||
('debian7', 'Debian 7 - Wheezy'),
|
('debian7', 'Debian 7 - Wheezy'),
|
||||||
))
|
),
|
||||||
|
validators=[Setting.validate_choices]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
VPS_DEFAULT_TEMPLATE = Setting('VPS_DEFAULT_TEMPLATE', 'debian7', choices=VPS_TEMPLATES)
|
VPS_DEFAULT_TEMPLATE = Setting('VPS_DEFAULT_TEMPLATE', 'debian7', choices=VPS_TEMPLATES)
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
import orchestra.core.validators
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('webapps', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='webapp',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(validators=[orchestra.core.validators.validate_name], max_length=128, verbose_name='name', help_text='The app will be installed in %(home)s/webapps/%(app_name)s'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='webapp',
|
||||||
|
name='type',
|
||||||
|
field=models.CharField(max_length=32, verbose_name='type', choices=[('php', 'PHP'), ('python', 'Python'), ('static', 'Static'), ('symbolic-link', 'Symbolic link'), ('webalizer', 'Webalizer'), ('wordpress-php', 'WordPress')]),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='webappoption',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(max_length=128, verbose_name='name', choices=[(None, '-------'), ('FileSystem', [('public-root', 'Public root')]), ('Process', [('timeout', 'Process timeout'), ('processes', 'Number of processes')]), ('PHP', [('enable_functions', 'Enable 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'), ('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'), ('zend_extension', 'Zend extension')])]),
|
||||||
|
),
|
||||||
|
]
|
|
@ -25,35 +25,32 @@ WEBAPPS_PHPFPM_POOL_PATH = Setting('WEBAPPS_PHPFPM_POOL_PATH',
|
||||||
|
|
||||||
|
|
||||||
WEBAPPS_FCGID_WRAPPER_PATH = Setting('WEBAPPS_FCGID_WRAPPER_PATH',
|
WEBAPPS_FCGID_WRAPPER_PATH = Setting('WEBAPPS_FCGID_WRAPPER_PATH',
|
||||||
# Inside SuExec Document root
|
'/home/httpd/fcgi-bin.d/%(user)s/%(app_name)s-wrapper',
|
||||||
# Make sure all account wrappers are in the same DIR
|
help_text=("Inside SuExec Document root.<br>"
|
||||||
'/home/httpd/fcgi-bin.d/%(user)s/%(app_name)s-wrapper'
|
"Make sure all account wrappers are in the same DIR.")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
WEBAPPS_FCGID_CMD_OPTIONS_PATH = Setting('WEBAPPS_FCGID_CMD_OPTIONS_PATH',
|
WEBAPPS_FCGID_CMD_OPTIONS_PATH = Setting('WEBAPPS_FCGID_CMD_OPTIONS_PATH',
|
||||||
# Loaded by Apache
|
'/etc/apache2/fcgid-conf/%(user)s-%(app_name)s.conf',
|
||||||
'/etc/apache2/fcgid-conf/%(user)s-%(app_name)s.conf'
|
help_text="Loaded by Apache."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Greater or equal to your FcgidMaxRequestsPerProcess
|
|
||||||
# http://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html#examples
|
|
||||||
WEBAPPS_PHP_MAX_REQUESTS = Setting('WEBAPPS_PHP_MAX_REQUESTS',
|
WEBAPPS_PHP_MAX_REQUESTS = Setting('WEBAPPS_PHP_MAX_REQUESTS',
|
||||||
400
|
400,
|
||||||
|
help_text='Greater or equal to your <a href="http://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html#examples">FcgidMaxRequestsPerProcess</a>'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
WEBAPPS_PHP_ERROR_LOG_PATH = Setting('WEBAPPS_PHP_ERROR_LOG_PATH',
|
WEBAPPS_PHP_ERROR_LOG_PATH = Setting('WEBAPPS_PHP_ERROR_LOG_PATH', '')
|
||||||
''
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
WEBAPPS_MERGE_PHP_WEBAPPS = Setting('WEBAPPS_MERGE_PHP_WEBAPPS',
|
WEBAPPS_MERGE_PHP_WEBAPPS = Setting('WEBAPPS_MERGE_PHP_WEBAPPS',
|
||||||
# Combine all fcgid-wrappers/fpm-pools into one per account-php_version
|
False,
|
||||||
# to better control num processes per account and save memory
|
help_text=("Combine all fcgid-wrappers/fpm-pools into one per account-php_version "
|
||||||
False)
|
"to better control num processes per account and save memory")
|
||||||
|
)
|
||||||
|
|
||||||
WEBAPPS_TYPES = Setting('WEBAPPS_TYPES', (
|
WEBAPPS_TYPES = Setting('WEBAPPS_TYPES', (
|
||||||
'orchestra.contrib.webapps.types.php.PHPApp',
|
'orchestra.contrib.webapps.types.php.PHPApp',
|
||||||
|
@ -70,13 +67,15 @@ WEBAPPS_TYPES = Setting('WEBAPPS_TYPES', (
|
||||||
|
|
||||||
|
|
||||||
WEBAPPS_PHP_VERSIONS = Setting('WEBAPPS_PHP_VERSIONS', (
|
WEBAPPS_PHP_VERSIONS = Setting('WEBAPPS_PHP_VERSIONS', (
|
||||||
# Execution modle choose by ending -fpm or -cgi
|
('5.4-fpm', 'PHP 5.4 FPM'),
|
||||||
('5.4-fpm', 'PHP 5.4 FPM'),
|
('5.4-cgi', 'PHP 5.4 FCGID'),
|
||||||
('5.4-cgi', 'PHP 5.4 FCGID'),
|
('5.3-cgi', 'PHP 5.3 FCGID'),
|
||||||
('5.3-cgi', 'PHP 5.3 FCGID'),
|
('5.2-cgi', 'PHP 5.2 FCGID'),
|
||||||
('5.2-cgi', 'PHP 5.2 FCGID'),
|
('4-cgi', 'PHP 4 FCGID'),
|
||||||
('4-cgi', 'PHP 4 FCGID'),
|
),
|
||||||
))
|
help_text="Execution modle choose by ending -fpm or -cgi.",
|
||||||
|
validators=[Setting.validate_choices]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
WEBAPPS_DEFAULT_PHP_VERSION = Setting('WEBAPPS_DEFAULT_PHP_VERSION', '5.4-cgi',
|
WEBAPPS_DEFAULT_PHP_VERSION = Setting('WEBAPPS_DEFAULT_PHP_VERSION', '5.4-cgi',
|
||||||
|
@ -84,28 +83,27 @@ WEBAPPS_DEFAULT_PHP_VERSION = Setting('WEBAPPS_DEFAULT_PHP_VERSION', '5.4-cgi',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
WEBAPPS_PHP_CGI_BINARY_PATH = Setting('WEBAPPS_PHP_CGI_BINARY_PATH',
|
WEBAPPS_PHP_CGI_BINARY_PATH = Setting('WEBAPPS_PHP_CGI_BINARY_PATH', '/usr/bin/php%(php_version_number)s-cgi',
|
||||||
# Path of the cgi binary used by fcgid
|
help_text="Path of the cgi binary used by fcgid."
|
||||||
'/usr/bin/php%(php_version_number)s-cgi'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
WEBAPPS_PHP_CGI_RC_DIR = Setting('WEBAPPS_PHP_CGI_RC_DIR',
|
WEBAPPS_PHP_CGI_RC_DIR = Setting('WEBAPPS_PHP_CGI_RC_DIR', '/etc/php%(php_version_number)s/cgi/',
|
||||||
# Path to php.ini
|
help_text="Path to php.ini."
|
||||||
'/etc/php%(php_version_number)s/cgi/'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
WEBAPPS_PHP_CGI_INI_SCAN_DIR = Setting('WEBAPPS_PHP_CGI_INI_SCAN_DIR',
|
WEBAPPS_PHP_CGI_INI_SCAN_DIR = Setting('WEBAPPS_PHP_CGI_INI_SCAN_DIR',
|
||||||
# Path to php.ini
|
|
||||||
'/etc/php%(php_version_number)s/cgi/conf.d'
|
'/etc/php%(php_version_number)s/cgi/conf.d'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
WEBAPPS_PYTHON_VERSIONS = Setting('WEBAPPS_PYTHON_VERSIONS', (
|
WEBAPPS_PYTHON_VERSIONS = Setting('WEBAPPS_PYTHON_VERSIONS', (
|
||||||
('3.4-uwsgi', 'Python 3.4 uWSGI'),
|
('3.4-uwsgi', 'Python 3.4 uWSGI'),
|
||||||
('2.7-uwsgi', 'Python 2.7 uWSGI'),
|
('2.7-uwsgi', 'Python 2.7 uWSGI'),
|
||||||
))
|
),
|
||||||
|
validators=[Setting.validate_choices]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
WEBAPPS_DEFAULT_PYTHON_VERSION = Setting('WEBAPPS_DEFAULT_PYTHON_VERSION', '3.4-uwsgi',
|
WEBAPPS_DEFAULT_PYTHON_VERSION = Setting('WEBAPPS_DEFAULT_PYTHON_VERSION', '3.4-uwsgi',
|
||||||
|
@ -153,7 +151,7 @@ WEBAPPS_UNDER_CONSTRUCTION_PATH = Setting('WEBAPPS_UNDER_CONSTRUCTION_PATH', '',
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
WEBAPPS_PHP_DISABLED_FUNCTIONS = Setting('WEBAPPS_PHP_DISABLED_FUNCTION', [
|
WEBAPPS_PHP_DISABLED_FUNCTIONS = Setting('WEBAPPS_PHP_DISABLED_FUNCTION', (
|
||||||
'exec',
|
'exec',
|
||||||
'passthru',
|
'passthru',
|
||||||
'shell_exec',
|
'shell_exec',
|
||||||
|
@ -174,7 +172,7 @@ WEBAPPS_PHP_DISABLED_FUNCTIONS = Setting('WEBAPPS_PHP_DISABLED_FUNCTION', [
|
||||||
'escapeshellcmd',
|
'escapeshellcmd',
|
||||||
'escapeshellarg',
|
'escapeshellarg',
|
||||||
'dl'
|
'dl'
|
||||||
])
|
))
|
||||||
|
|
||||||
|
|
||||||
WEBAPPS_ENABLED_OPTIONS = Setting('WEBAPPS_ENABLED_OPTIONS', (
|
WEBAPPS_ENABLED_OPTIONS = Setting('WEBAPPS_ENABLED_OPTIONS', (
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from orchestra.contrib.databases.models import Database, DatabaseUser
|
from orchestra.contrib.databases.models import Database, DatabaseUser
|
||||||
from orchestra.forms import widgets
|
from orchestra.forms.widgets import SpanWidget
|
||||||
from orchestra.utils.python import random_ascii
|
from orchestra.utils.python import random_ascii
|
||||||
|
|
||||||
from .php import PHPApp, PHPAppForm, PHPAppSerializer
|
from .php import PHPApp, PHPAppForm, PHPAppSerializer
|
||||||
|
@ -30,13 +30,13 @@ class CMSAppForm(PHPAppForm):
|
||||||
db_id = data.get('db_id')
|
db_id = data.get('db_id')
|
||||||
db_url = reverse('admin:databases_database_change', args=(db_id,))
|
db_url = reverse('admin:databases_database_change', args=(db_id,))
|
||||||
db_link = mark_safe('<a href="%s">%s</a>' % (db_url, db_name))
|
db_link = mark_safe('<a href="%s">%s</a>' % (db_url, db_name))
|
||||||
self.fields['db_name'].widget = widgets.ReadOnlyWidget(db_name, db_link)
|
self.fields['db_name'].widget = SpanWidget(original=db_name, display=db_link)
|
||||||
# DB user link
|
# DB user link
|
||||||
db_user = data.get('db_user')
|
db_user = data.get('db_user')
|
||||||
db_user_id = data.get('db_user_id')
|
db_user_id = data.get('db_user_id')
|
||||||
db_user_url = reverse('admin:databases_databaseuser_change', args=(db_user_id,))
|
db_user_url = reverse('admin:databases_databaseuser_change', args=(db_user_id,))
|
||||||
db_user_link = mark_safe('<a href="%s">%s</a>' % (db_user_url, db_user))
|
db_user_link = mark_safe('<a href="%s">%s</a>' % (db_user_url, db_user))
|
||||||
self.fields['db_user'].widget = widgets.ReadOnlyWidget(db_user, db_user_link)
|
self.fields['db_user'].widget = SpanWidget(original=db_user, display=db_user_link)
|
||||||
|
|
||||||
|
|
||||||
class CMSAppSerializer(PHPAppSerializer):
|
class CMSAppSerializer(PHPAppSerializer):
|
||||||
|
|
|
@ -24,7 +24,7 @@ class Apache2Backend(ServiceController):
|
||||||
model = 'websites.Website'
|
model = 'websites.Website'
|
||||||
related_models = (
|
related_models = (
|
||||||
('websites.Content', 'website'),
|
('websites.Content', 'website'),
|
||||||
('websites.WebsiteDirective', 'directives'),
|
('websites.WebsiteDirective', 'website'),
|
||||||
('webapps.WebApp', 'website_set'),
|
('webapps.WebApp', 'website_set'),
|
||||||
)
|
)
|
||||||
verbose_name = _("Apache 2")
|
verbose_name = _("Apache 2")
|
||||||
|
|
|
@ -6,40 +6,40 @@ from .. import websites
|
||||||
|
|
||||||
|
|
||||||
WEBSITES_UNIQUE_NAME_FORMAT = Setting('WEBSITES_UNIQUE_NAME_FORMAT',
|
WEBSITES_UNIQUE_NAME_FORMAT = Setting('WEBSITES_UNIQUE_NAME_FORMAT',
|
||||||
'%(user)s-%(site_name)s'
|
default='%(user)s-%(site_name)s'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# TODO 'http', 'https', 'https-only', 'http and https' and rename to PROTOCOL
|
WEBSITES_PROTOCOL_CHOICES = Setting('WEBSITES_PROTOCOL_CHOICES',
|
||||||
#WEBSITES_PORT_CHOICES = getattr(settings, 'WEBSITES_PORT_CHOICES', (
|
default=(
|
||||||
# (80, 'HTTP'),
|
('http', "HTTP"),
|
||||||
# (443, 'HTTPS'),
|
('https', "HTTPS"),
|
||||||
#))
|
('http/https', _("HTTP and HTTPS")),
|
||||||
|
('https-only', _("HTTPS only")),
|
||||||
|
),
|
||||||
|
validators=[Setting.validate_choices]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
WEBSITES_PROTOCOL_CHOICES = Setting('WEBSITES_PROTOCOL_CHOICES', (
|
WEBSITES_DEFAULT_PROTOCOL = Setting('WEBSITES_DEFAULT_PROTOCOL',
|
||||||
('http', "HTTP"),
|
default='http',
|
||||||
('https', "HTTPS"),
|
|
||||||
('http/https', _("HTTP and HTTPS")),
|
|
||||||
('https-only', _("HTTPS only")),
|
|
||||||
))
|
|
||||||
|
|
||||||
WEBSITES_DEFAULT_PROTOCOL = Setting('WEBSITES_DEFAULT_PROTOCOL', 'http',
|
|
||||||
choices=WEBSITES_PROTOCOL_CHOICES
|
choices=WEBSITES_PROTOCOL_CHOICES
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
WEBSITES_DEFAULT_IPS = Setting('WEBSITES_DEFAULT_IPS', (
|
WEBSITES_DEFAULT_IPS = Setting('WEBSITES_DEFAULT_IPS',
|
||||||
'*',
|
default=('*',)
|
||||||
))
|
|
||||||
|
|
||||||
|
|
||||||
WEBSITES_DOMAIN_MODEL = Setting('WEBSITES_DOMAIN_MODEL',
|
|
||||||
'domains.Domain'
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
WEBSITES_ENABLED_DIRECTIVES = Setting('WEBSITES_ENABLED_DIRECTIVES', (
|
WEBSITES_DOMAIN_MODEL = Setting('WEBSITES_DOMAIN_MODEL',
|
||||||
|
'domains.Domain',
|
||||||
|
validators=[Setting.validate_model_label]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
WEBSITES_ENABLED_DIRECTIVES = Setting('WEBSITES_ENABLED_DIRECTIVES',
|
||||||
|
(
|
||||||
'orchestra.contrib.websites.directives.Redirect',
|
'orchestra.contrib.websites.directives.Redirect',
|
||||||
'orchestra.contrib.websites.directives.Proxy',
|
'orchestra.contrib.websites.directives.Proxy',
|
||||||
'orchestra.contrib.websites.directives.ErrorDocument',
|
'orchestra.contrib.websites.directives.ErrorDocument',
|
||||||
|
|
|
@ -16,7 +16,7 @@ def all_valid(*args):
|
||||||
if len(args) == 1:
|
if len(args) == 1:
|
||||||
# Dict
|
# Dict
|
||||||
errors = {}
|
errors = {}
|
||||||
kwargs = args
|
kwargs = args[0]
|
||||||
for field, validator in kwargs.items():
|
for field, validator in kwargs.items():
|
||||||
try:
|
try:
|
||||||
validator[0](*validator[1:])
|
validator[0](*validator[1:])
|
||||||
|
@ -36,7 +36,7 @@ def all_valid(*args):
|
||||||
|
|
||||||
|
|
||||||
def validate_ipv4_address(value):
|
def validate_ipv4_address(value):
|
||||||
msg = _("%s is not a valid IPv4 address") % value
|
msg = _("Not a valid IPv4 address")
|
||||||
try:
|
try:
|
||||||
ip = IP(value)
|
ip = IP(value)
|
||||||
except:
|
except:
|
||||||
|
@ -46,7 +46,7 @@ def validate_ipv4_address(value):
|
||||||
|
|
||||||
|
|
||||||
def validate_ipv6_address(value):
|
def validate_ipv6_address(value):
|
||||||
msg = _("%s is not a valid IPv6 address") % value
|
msg = _("Not a valid IPv6 address")
|
||||||
try:
|
try:
|
||||||
ip = IP(value)
|
ip = IP(value)
|
||||||
except:
|
except:
|
||||||
|
@ -56,7 +56,7 @@ def validate_ipv6_address(value):
|
||||||
|
|
||||||
|
|
||||||
def validate_ip_address(value):
|
def validate_ip_address(value):
|
||||||
msg = _("%s is not a valid IP address") % value
|
msg = _("Not a valid IP address")
|
||||||
try:
|
try:
|
||||||
IP(value)
|
IP(value)
|
||||||
except:
|
except:
|
||||||
|
|
|
@ -81,11 +81,10 @@ class ReadOnlyFormMixin(object):
|
||||||
for name in self.Meta.readonly_fields:
|
for name in self.Meta.readonly_fields:
|
||||||
field = self.fields[name]
|
field = self.fields[name]
|
||||||
if not isinstance(field, SpanField):
|
if not isinstance(field, SpanField):
|
||||||
field.widget = SpanWidget()
|
if not isinstance(field.widget, SpanWidget):
|
||||||
|
field.widget = SpanWidget()
|
||||||
|
original = self.initial.get(name)
|
||||||
if hasattr(self, 'instance'):
|
if hasattr(self, 'instance'):
|
||||||
# Model form
|
original = getattr(self.instance, name, original)
|
||||||
original_value = getattr(self.instance, name)
|
field.widget.original = original
|
||||||
else:
|
|
||||||
original_value = self.initial.get(name)
|
|
||||||
field.widget.original_value = original_value
|
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ from django.utils.encoding import force_text
|
||||||
|
|
||||||
from django.contrib.admin.templatetags.admin_static import static
|
from django.contrib.admin.templatetags.admin_static import static
|
||||||
|
|
||||||
# TODO rename readonlywidget
|
|
||||||
class SpanWidget(forms.Widget):
|
class SpanWidget(forms.Widget):
|
||||||
"""
|
"""
|
||||||
Renders a value wrapped in a <span> tag.
|
Renders a value wrapped in a <span> tag.
|
||||||
|
@ -15,74 +15,29 @@ class SpanWidget(forms.Widget):
|
||||||
"""
|
"""
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.tag = kwargs.pop('tag', '<span>')
|
self.tag = kwargs.pop('tag', '<span>')
|
||||||
|
self.original = kwargs.pop('original', '')
|
||||||
|
self.display = kwargs.pop('display', None)
|
||||||
super(SpanWidget, self).__init__(*args, **kwargs)
|
super(SpanWidget, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
def render(self, name, value, attrs=None):
|
def render(self, name, value, attrs=None):
|
||||||
final_attrs = self.build_attrs(attrs, name=name)
|
final_attrs = self.build_attrs(attrs, name=name)
|
||||||
original_value = self.original_value
|
original = self.original or value
|
||||||
|
display = original if self.display is None else self.display
|
||||||
# Display icon
|
# Display icon
|
||||||
if isinstance(original_value, bool):
|
if isinstance(original, bool):
|
||||||
icon = static('admin/img/icon-%s.gif' % ('yes' if original_value else 'no',))
|
icon = static('admin/img/icon-%s.gif' % ('yes' if original else 'no',))
|
||||||
return mark_safe('<img src="%s" alt="%s">' % (icon, str(original_value)))
|
return mark_safe('<img src="%s" alt="%s">' % (icon, str(display)))
|
||||||
tag = self.tag[:-1]
|
tag = self.tag[:-1]
|
||||||
endtag = '/'.join((self.tag[0], self.tag[1:]))
|
endtag = '/'.join((self.tag[0], self.tag[1:]))
|
||||||
return mark_safe('%s%s >%s%s' % (tag, forms.util.flatatt(final_attrs), original_value, endtag))
|
return mark_safe('%s%s >%s%s' % (tag, forms.util.flatatt(final_attrs), display, endtag))
|
||||||
|
|
||||||
def value_from_datadict(self, data, files, name):
|
def value_from_datadict(self, data, files, name):
|
||||||
return self.original_value
|
return self.original
|
||||||
|
|
||||||
def _has_changed(self, initial, data):
|
def _has_changed(self, initial, data):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class ShowTextWidget(forms.Widget):
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
for kwarg in ['tag', 'warning', 'hidden']:
|
|
||||||
setattr(self, kwarg, kwargs.pop(kwarg, False))
|
|
||||||
super(ShowTextWidget, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def render(self, name, value, attrs):
|
|
||||||
value = force_text(value)
|
|
||||||
if value is None:
|
|
||||||
return ''
|
|
||||||
if hasattr(self, 'initial'):
|
|
||||||
value = self.initial
|
|
||||||
if self.tag:
|
|
||||||
endtag = '/'.join((self.tag[0], self.tag[1:]))
|
|
||||||
final_value = ''.join((self.tag, value, endtag))
|
|
||||||
else:
|
|
||||||
final_value = '<br/>'.join(value.split('\n'))
|
|
||||||
if self.warning:
|
|
||||||
final_value = (
|
|
||||||
'<ul class="messagelist"><li class="warning">%s</li></ul>'
|
|
||||||
% final_value)
|
|
||||||
if self.hidden:
|
|
||||||
final_value = (
|
|
||||||
'%s<input type="hidden" name="%s" value="%s"/>'
|
|
||||||
% (final_value, name, value))
|
|
||||||
return mark_safe(final_value)
|
|
||||||
|
|
||||||
def _has_changed(self, initial, data):
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class ReadOnlyWidget(forms.Widget):
|
|
||||||
def __init__(self, *args):
|
|
||||||
if len(args) == 1:
|
|
||||||
args = (args[0], args[0])
|
|
||||||
self.original_value = args[0]
|
|
||||||
self.display_value = args[1]
|
|
||||||
super(ReadOnlyWidget, self).__init__()
|
|
||||||
|
|
||||||
def render(self, name, value, attrs=None):
|
|
||||||
if self.display_value is not None:
|
|
||||||
return mark_safe(self.display_value)
|
|
||||||
return mark_safe(self.original_value)
|
|
||||||
|
|
||||||
def value_from_datadict(self, data, files, name):
|
|
||||||
return self.original_value
|
|
||||||
|
|
||||||
|
|
||||||
def paddingCheckboxSelectMultiple(padding):
|
def paddingCheckboxSelectMultiple(padding):
|
||||||
""" Ugly hack to render this widget nicely on Django admin """
|
""" Ugly hack to render this widget nicely on Django admin """
|
||||||
widget = forms.CheckboxSelectMultiple()
|
widget = forms.CheckboxSelectMultiple()
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
|
|
||||||
from orchestra.forms.widgets import ReadOnlyWidget
|
from orchestra.forms.widgets import SpanWidget
|
||||||
|
|
||||||
|
|
||||||
class PluginDataForm(forms.ModelForm):
|
class PluginDataForm(forms.ModelForm):
|
||||||
|
@ -12,7 +12,7 @@ class PluginDataForm(forms.ModelForm):
|
||||||
if self.plugin_field in self.fields:
|
if self.plugin_field in self.fields:
|
||||||
value = self.plugin.get_name()
|
value = self.plugin.get_name()
|
||||||
display = '%s <a href=".">change</a>' % force_text(self.plugin.verbose_name)
|
display = '%s <a href=".">change</a>' % force_text(self.plugin.verbose_name)
|
||||||
self.fields[self.plugin_field].widget = ReadOnlyWidget(value, display)
|
self.fields[self.plugin_field].widget = SpanWidget(original=value, display=display)
|
||||||
help_text = self.fields[self.plugin_field].help_text
|
help_text = self.fields[self.plugin_field].help_text
|
||||||
self.fields[self.plugin_field].help_text = getattr(self.plugin, 'help_text', help_text)
|
self.fields[self.plugin_field].help_text = getattr(self.plugin, 'help_text', help_text)
|
||||||
if self.instance:
|
if self.instance:
|
||||||
|
@ -34,7 +34,7 @@ class PluginDataForm(forms.ModelForm):
|
||||||
if foo_display:
|
if foo_display:
|
||||||
display = foo_display()
|
display = foo_display()
|
||||||
self.fields[field].required = False
|
self.fields[field].required = False
|
||||||
self.fields[field].widget = ReadOnlyWidget(value, display)
|
self.fields[field].widget = SpanWidget(original=value, display=display)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
super(PluginDataForm, self).clean()
|
super(PluginDataForm, self).clean()
|
||||||
|
|
|
@ -1,16 +1,21 @@
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError, AppRegistryNotReady
|
||||||
|
from django.db.models import get_model
|
||||||
|
from django.utils.functional import Promise
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from orchestra.utils.python import import_class, format_exception
|
||||||
|
|
||||||
from .core import validators
|
from .core import validators
|
||||||
|
|
||||||
|
|
||||||
class Setting(object):
|
class Setting(object):
|
||||||
"""
|
"""
|
||||||
Keeps track of the defined settings.
|
Keeps track of the defined settings and provides extra batteries like value validation.
|
||||||
Instances of this class are the native value of the setting.
|
|
||||||
"""
|
"""
|
||||||
conf_settings = settings
|
conf_settings = settings
|
||||||
settings = OrderedDict()
|
settings = OrderedDict()
|
||||||
|
@ -23,12 +28,12 @@ class Setting(object):
|
||||||
value = ("'%s'" if isinstance(value, str) else '%s') % value
|
value = ("'%s'" if isinstance(value, str) else '%s') % value
|
||||||
return '<%s: %s>' % (self.name, value)
|
return '<%s: %s>' % (self.name, value)
|
||||||
|
|
||||||
def __new__(cls, name, default, help_text="", choices=None, editable=True, multiple=False,
|
def __new__(cls, name, default, help_text="", choices=None, editable=True, serializable=True,
|
||||||
validators=[], types=[], call_init=False):
|
multiple=False, validators=[], types=[], call_init=False):
|
||||||
if call_init:
|
if call_init:
|
||||||
return super(Setting, cls).__new__(cls)
|
return super(Setting, cls).__new__(cls)
|
||||||
cls.settings[name] = cls(name, default, help_text=help_text, choices=choices,
|
cls.settings[name] = cls(name, default, help_text=help_text, choices=choices, editable=editable,
|
||||||
editable=editable, multiple=multiple, validators=validators, types=types, call_init=True)
|
serializable=serializable, multiple=multiple, validators=validators, types=types, call_init=True)
|
||||||
return cls.get_value(name, default)
|
return cls.get_value(name, default)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
@ -36,11 +41,67 @@ class Setting(object):
|
||||||
for name, value in kwargs.items():
|
for name, value in kwargs.items():
|
||||||
setattr(self, name, value)
|
setattr(self, name, value)
|
||||||
self.value = self.get_value(self.name, self.default)
|
self.value = self.get_value(self.name, self.default)
|
||||||
self.validate_value(self.value)
|
try:
|
||||||
|
self.validate_value(self.value)
|
||||||
|
except ValidationError as exc:
|
||||||
|
# Init time warning
|
||||||
|
sys.stderr.write("Error validating setting %s with value %s\n" % (self.name, self.value))
|
||||||
|
sys.stderr.write(format_exception(exc))
|
||||||
|
raise exc
|
||||||
|
except AppRegistryNotReady:
|
||||||
|
# lazy bastards
|
||||||
|
pass
|
||||||
self.settings[name] = self
|
self.settings[name] = self
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def validate_choices(cls, value):
|
||||||
|
if not isinstance(value, (list, tuple)):
|
||||||
|
raise ValidationError("%s is not a valid choices." % str(value))
|
||||||
|
for choice in value:
|
||||||
|
if not isinstance(choice, (list, tuple)) or len(choice) != 2:
|
||||||
|
raise ValidationError("%s is not a valid choice." % str(choice))
|
||||||
|
value, verbose = choice
|
||||||
|
if not isinstance(verbose, (str, Promise)):
|
||||||
|
raise ValidationError("%s is not a valid verbose name." % value)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def validate_import_class(cls, value):
|
||||||
|
try:
|
||||||
|
import_class(value)
|
||||||
|
except ImportError as exc:
|
||||||
|
if "cannot import name 'settings'" in str(exc):
|
||||||
|
# circular dependency on init time
|
||||||
|
pass
|
||||||
|
except Exception as exc:
|
||||||
|
raise ValidationError(format_exception(exc))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def validate_model_label(cls, value):
|
||||||
|
try:
|
||||||
|
get_model(*value.split('.'))
|
||||||
|
except AppRegistryNotReady:
|
||||||
|
# circular dependency on init time
|
||||||
|
pass
|
||||||
|
except Exception as exc:
|
||||||
|
raise ValidationError(format_exception(exc))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def string_format_validator(cls, names, modulo=True):
|
||||||
|
def validate_string_format(value, names=names, modulo=modulo):
|
||||||
|
errors = []
|
||||||
|
regex = r'%\(([^\)]+)\)' if modulo else r'{([^}]+)}'
|
||||||
|
for n in re.findall(regex, value):
|
||||||
|
if n not in names:
|
||||||
|
errors.append(
|
||||||
|
ValidationError('%s is not a valid format name.' % n)
|
||||||
|
)
|
||||||
|
if errors:
|
||||||
|
raise ValidationError(errors)
|
||||||
|
return validate_string_format
|
||||||
|
|
||||||
def validate_value(self, value):
|
def validate_value(self, value):
|
||||||
validators.all_valid(value, self.validators)
|
if value:
|
||||||
|
validators.all_valid(value, self.validators)
|
||||||
valid_types = list(self.types)
|
valid_types = list(self.types)
|
||||||
if isinstance(self.default, (list, tuple)):
|
if isinstance(self.default, (list, tuple)):
|
||||||
valid_types.extend([list, tuple])
|
valid_types.extend([list, tuple])
|
||||||
|
@ -56,48 +117,61 @@ class Setting(object):
|
||||||
|
|
||||||
|
|
||||||
ORCHESTRA_BASE_DOMAIN = Setting('ORCHESTRA_BASE_DOMAIN',
|
ORCHESTRA_BASE_DOMAIN = Setting('ORCHESTRA_BASE_DOMAIN',
|
||||||
'orchestra.lan'
|
'orchestra.lan',
|
||||||
|
help_text=("Base domain name used for other settings.<br>"
|
||||||
|
"If you're editing the settings via the admin interface <b>it is advisable to "
|
||||||
|
"commit this change before changing any other variables which could be affected</b>.")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
ORCHESTRA_SITE_URL = Setting('ORCHESTRA_SITE_URL', 'https://orchestra.%s' % ORCHESTRA_BASE_DOMAIN,
|
ORCHESTRA_SITE_URL = Setting('ORCHESTRA_SITE_URL',
|
||||||
|
'https://orchestra.%s' % ORCHESTRA_BASE_DOMAIN,
|
||||||
help_text=_("Domain name used when it will not be possible to infere the domain from a request."
|
help_text=_("Domain name used when it will not be possible to infere the domain from a request."
|
||||||
"For example in periodic tasks.")
|
"For example in periodic tasks.")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
ORCHESTRA_SITE_NAME = Setting('ORCHESTRA_SITE_NAME', 'orchestra')
|
ORCHESTRA_SITE_NAME = Setting('ORCHESTRA_SITE_NAME',
|
||||||
|
'orchestra',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
ORCHESTRA_SITE_VERBOSE_NAME = Setting('ORCHESTRA_SITE_VERBOSE_NAME',
|
ORCHESTRA_SITE_VERBOSE_NAME = Setting('ORCHESTRA_SITE_VERBOSE_NAME',
|
||||||
_("%s Hosting Management" % ORCHESTRA_SITE_NAME.capitalize())
|
_("%s Hosting Management" % ORCHESTRA_SITE_NAME.capitalize()),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Service management commands
|
# Service management commands
|
||||||
|
|
||||||
ORCHESTRA_START_SERVICES = Setting('ORCHESTRA_START_SERVICES', (
|
ORCHESTRA_START_SERVICES = Setting('ORCHESTRA_START_SERVICES',
|
||||||
'postgresql',
|
default=(
|
||||||
'celeryevcam',
|
'postgresql',
|
||||||
'celeryd',
|
'celeryevcam',
|
||||||
'celerybeat',
|
'celeryd',
|
||||||
('uwsgi', 'nginx'),
|
'celerybeat',
|
||||||
))
|
('uwsgi', 'nginx'),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
ORCHESTRA_RESTART_SERVICES = Setting('ORCHESTRA_RESTART_SERVICES', (
|
ORCHESTRA_RESTART_SERVICES = Setting('ORCHESTRA_RESTART_SERVICES',
|
||||||
'celeryd',
|
default=(
|
||||||
'celerybeat',
|
'celeryd',
|
||||||
'uwsgi'
|
'celerybeat',
|
||||||
))
|
'uwsgi'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
ORCHESTRA_STOP_SERVICES = Setting('ORCHESTRA_STOP_SERVICES', (
|
|
||||||
('uwsgi', 'nginx'),
|
ORCHESTRA_STOP_SERVICES = Setting('ORCHESTRA_STOP_SERVICES',
|
||||||
'celerybeat',
|
default=(
|
||||||
'celeryd',
|
('uwsgi', 'nginx'),
|
||||||
'celeryevcam',
|
'celerybeat',
|
||||||
'postgresql'
|
'celeryd',
|
||||||
))
|
'celeryevcam',
|
||||||
|
'postgresql'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
ORCHESTRA_API_ROOT_VIEW = Setting('ORCHESTRA_API_ROOT_VIEW',
|
ORCHESTRA_API_ROOT_VIEW = Setting('ORCHESTRA_API_ROOT_VIEW',
|
||||||
|
@ -110,4 +184,6 @@ ORCHESTRA_DEFAULT_SUPPORT_FROM_EMAIL = Setting('ORCHESTRA_DEFAULT_SUPPORT_FROM_E
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
ORCHESTRA_EDIT_SETTINGS = Setting('ORCHESTRA_EDIT_SETTINGS', True)
|
ORCHESTRA_EDIT_SETTINGS = Setting('ORCHESTRA_EDIT_SETTINGS',
|
||||||
|
True
|
||||||
|
)
|
||||||
|
|
|
@ -16,6 +16,11 @@ def random_ascii(length):
|
||||||
return ''.join([random.SystemRandom().choice(string.hexdigits) for i in range(0, length)]).lower()
|
return ''.join([random.SystemRandom().choice(string.hexdigits) for i in range(0, length)]).lower()
|
||||||
|
|
||||||
|
|
||||||
|
def format_exception(exception):
|
||||||
|
name = type(exception).__name__
|
||||||
|
return ': '.join((name, str(exception)))
|
||||||
|
|
||||||
|
|
||||||
class OrderedSet(collections.MutableSet):
|
class OrderedSet(collections.MutableSet):
|
||||||
def __init__(self, iterable=None):
|
def __init__(self, iterable=None):
|
||||||
self.end = end = []
|
self.end = end = []
|
||||||
|
|
Loading…
Reference in a new issue