Deprecate ShowTextWidget and ReadOnlyWidget0

This commit is contained in:
Marc Aymerich 2015-04-27 12:24:17 +00:00
parent 929d9beb5c
commit 9c065d401d
46 changed files with 487 additions and 449 deletions

View File

@ -280,17 +280,15 @@ https://code.djangoproject.com/ticket/24576
# 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 -
# replace multichoicefield and jsonfield by ArrayField, HStoreField
# bill confirmation: show total
# Amend lines???
# 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
# @classmethods do not need to be called with type(object)!
# Deprectae widgets.showtext and readonlyField by ReadOnlyFormMixin
# custom validation for settings
# TODO orchestra related services code reload: celery/uwsgi reloading find aonther way without root and implement reload
# insert settings on dashboard dynamically
# convert all complex settings to string

View File

@ -7,7 +7,7 @@ from django.forms.models import modelformset_factory, BaseModelFormSet
from django.template import Template, Context
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
@ -71,10 +71,10 @@ class AdminPasswordChangeForm(forms.Form):
self.user = user
super(AdminPasswordChangeForm, self).__init__(*args, **kwargs)
for ix, rel in enumerate(self.related):
self.fields['password1_%i' % ix] = forms.CharField(
label=_("Password"), widget=forms.PasswordInput, required=False)
self.fields['password2_%i' % ix] = forms.CharField(
label=_("Password (again)"), widget=forms.PasswordInput, required=False)
self.fields['password1_%i' % ix] = forms.CharField(label=_("Password"),
widget=forms.PasswordInput, required=False)
self.fields['password2_%i' % ix] = forms.CharField(label=_("Password (again)"),
widget=forms.PasswordInput, required=False)
setattr(self, 'clean_password2_%i' % ix, partial(self.clean_password2, ix=ix))
def clean_password2(self, ix=''):
@ -138,21 +138,20 @@ class AdminPasswordChangeForm(forms.Form):
class SendEmailForm(forms.Form):
email_from = forms.EmailField(label=_("From"),
widget=forms.TextInput(attrs={'size': '118'}))
to = forms.CharField(label="To", required=False,
widget=ShowTextWidget())
widget=forms.TextInput(attrs={'size': '118'}))
to = forms.CharField(label="To", 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"),
widget=forms.TextInput(attrs={'size': '118'}))
widget=forms.TextInput(attrs={'size': '118'}))
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):
super(SendEmailForm, self).__init__(*args, **kwargs)
initial = kwargs.get('initial')
if 'to' in initial:
self.fields['to'].widget = ReadOnlyWidget(initial['to'])
self.fields['to'].widget = SpanWidget(original=initial['to'])
else:
self.fields.pop('to')

View File

@ -5,29 +5,33 @@ from orchestra.settings import ORCHESTRA_BASE_DOMAIN, Setting
ACCOUNTS_TYPES = Setting('ACCOUNTS_TYPES', (
('INDIVIDUAL', _("Individual")),
('ASSOCIATION', _("Association")),
('CUSTOMER', _("Customer")),
('COMPANY', _("Company")),
('PUBLICBODY', _("Public body")),
('STAFF', _("Staff")),
('FRIEND', _("Friend")),
))
('INDIVIDUAL', _("Individual")),
('ASSOCIATION', _("Association")),
('CUSTOMER', _("Customer")),
('COMPANY', _("Company")),
('PUBLICBODY', _("Public body")),
('STAFF', _("Staff")),
('FRIEND', _("Friend")),
),
validators=[Setting.validate_choices]
)
ACCOUNTS_DEFAULT_TYPE = Setting('ACCOUNTS_DEFAULT_TYPE', 'INDIVIDUAL', choices=ACCOUNTS_TYPES)
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_SYSTEMUSER_MODEL = Setting('ACCOUNTS_SYSTEMUSER_MODEL',
'systemusers.SystemUser'
ACCOUNTS_SYSTEMUSER_MODEL = Setting('ACCOUNTS_SYSTEMUSER_MODEL', 'systemusers.SystemUser',
validators=[Setting.validate_model_label],
)

View File

@ -56,7 +56,7 @@ def close_bills(modeladmin, request, queryset):
for bill in queryset:
if not validate_contact(request, bill):
return
SelectSourceFormSet = adminmodelformset_factory(SelectSourceForm, modeladmin, extra=0)
SelectSourceFormSet = adminmodelformset_factory(modeladmin, SelectSourceForm, extra=0)
formset = SelectSourceFormSet(queryset=queryset)
if request.POST.get('post') == 'generic_confirmation':
formset = SelectSourceFormSet(request.POST, request.FILES, queryset=queryset)

View File

@ -2,37 +2,38 @@ from django import forms
from django.utils.translation import ugettext_lazy as _
from orchestra.admin.utils import admin_link
from orchestra.forms.widgets import ShowTextWidget
from orchestra.forms import SpanWidget
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)
display_total = forms.CharField(label=_("Total"), required=False)
display_type = forms.CharField(label=_("Type"), required=False, widget=ShowTextWidget())
show_total = forms.CharField(label=_("Total"), required=False, widget=SpanWidget)
display_type = forms.CharField(label=_("Type"), required=False, widget=SpanWidget)
source = forms.ChoiceField(label=_("Source"), required=False)
class Meta:
fields = (
'bill_link', 'display_type', 'account_link', 'display_total',
'source'
'bill_link', 'display_type', 'account_link', 'show_total', 'source'
)
readonly_fields = ('account_link', 'display_total')
readonly_fields = ('account_link',)
def __init__(self, *args, **kwargs):
super(SelectSourceForm, self).__init__(*args, **kwargs)
bill = kwargs.get('instance')
if bill:
total = bill.get_total()
sources = bill.account.paymentsources.filter(is_active=True)
recharge = bool(bill.total < 0)
recharge = bool(total < 0)
choices = [(None, '-----------')]
for source in sources:
if not recharge or source.method_class().allow_recharge:
choices.append((source.pk, str(source)))
self.fields['source'].choices = choices
self.fields['source'].initial = choices[-1][0]
self.fields['bill_link'].initial = admin_link('__str__')(bill)
self.fields['display_type'].initial = bill.get_type_display()
self.fields['show_total'].widget.display = total
self.fields['bill_link'].widget.display = admin_link('__str__')(bill)
self.fields['display_type'].widget.display = bill.get_type_display()
def clean_source(self):
source_id = self.cleaned_data['source']

View File

@ -7,69 +7,43 @@ from orchestra.settings import ORCHESTRA_BASE_DOMAIN, Setting
BILLS_NUMBER_LENGTH = Setting('BILLS_NUMBER_LENGTH', 4)
BILLS_INVOICE_NUMBER_PREFIX = Setting('BILLS_INVOICE_NUMBER_PREFIX',
'I'
)
BILLS_INVOICE_NUMBER_PREFIX = Setting('BILLS_INVOICE_NUMBER_PREFIX', 'I')
BILLS_AMENDMENT_INVOICE_NUMBER_PREFIX = Setting('BILLS_AMENDMENT_INVOICE_NUMBER_PREFIX',
'A'
)
BILLS_AMENDMENT_INVOICE_NUMBER_PREFIX = Setting('BILLS_AMENDMENT_INVOICE_NUMBER_PREFIX', 'A')
BILLS_FEE_NUMBER_PREFIX = Setting('BILLS_FEE_NUMBER_PREFIX',
'F'
)
BILLS_FEE_NUMBER_PREFIX = Setting('BILLS_FEE_NUMBER_PREFIX', 'F')
BILLS_AMENDMENT_FEE_NUMBER_PREFIX = Setting('BILLS_AMENDMENT_FEE_NUMBER_PREFIX',
'B'
)
BILLS_AMENDMENT_FEE_NUMBER_PREFIX = Setting('BILLS_AMENDMENT_FEE_NUMBER_PREFIX', 'B')
BILLS_PROFORMA_NUMBER_PREFIX = Setting('BILLS_PROFORMA_NUMBER_PREFIX',
'P'
)
BILLS_PROFORMA_NUMBER_PREFIX = Setting('BILLS_PROFORMA_NUMBER_PREFIX', 'P')
BILLS_DEFAULT_TEMPLATE = Setting('BILLS_DEFAULT_TEMPLATE',
'bills/microspective.html'
)
BILLS_DEFAULT_TEMPLATE = Setting('BILLS_DEFAULT_TEMPLATE', 'bills/microspective.html')
BILLS_FEE_TEMPLATE = Setting('BILLS_FEE_TEMPLATE',
'bills/microspective-fee.html'
)
BILLS_FEE_TEMPLATE = Setting('BILLS_FEE_TEMPLATE', 'bills/microspective-fee.html')
BILLS_PROFORMA_TEMPLATE = Setting('BILLS_PROFORMA_TEMPLATE',
'bills/microspective-proforma.html'
)
BILLS_PROFORMA_TEMPLATE = Setting('BILLS_PROFORMA_TEMPLATE', 'bills/microspective-proforma.html')
BILLS_CURRENCY = Setting('BILLS_CURRENCY',
'euro'
)
BILLS_CURRENCY = Setting('BILLS_CURRENCY', 'euro')
BILLS_SELLER_PHONE = Setting('BILLS_SELLER_PHONE',
'111-112-11-222'
)
BILLS_SELLER_PHONE = Setting('BILLS_SELLER_PHONE', '111-112-11-222')
BILLS_SELLER_EMAIL = Setting('BILLS_SELLER_EMAIL',
'sales@{}'.format(ORCHESTRA_BASE_DOMAIN)
)
BILLS_SELLER_EMAIL = Setting('BILLS_SELLER_EMAIL', 'sales@{}'.format(ORCHESTRA_BASE_DOMAIN))
BILLS_SELLER_WEBSITE = Setting('BILLS_SELLER_WEBSITE',
'www.{}'.format(ORCHESTRA_BASE_DOMAIN)
)
BILLS_SELLER_WEBSITE = Setting('BILLS_SELLER_WEBSITE', 'www.{}'.format(ORCHESTRA_BASE_DOMAIN))
BILLS_SELLER_BANK_ACCOUNT = Setting('BILLS_SELLER_BANK_ACCOUNT',
'0000 0000 00 00000000 (Orchestra Bank)'
)
BILLS_SELLER_BANK_ACCOUNT = Setting('BILLS_SELLER_BANK_ACCOUNT', '0000 0000 00 00000000 (Orchestra Bank)')
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',
'orders.Order'
BILLS_ORDER_MODEL = Setting('BILLS_ORDER_MODEL', 'orders.Order',
validators=[Setting.validate_model_label]
)
BILLS_CONTACT_DEFAULT_CITY = Setting('BILLS_CONTACT_DEFAULT_CITY',
'Barcelona'
)
BILLS_CONTACT_DEFAULT_CITY = Setting('BILLS_CONTACT_DEFAULT_CITY', 'Barcelona')
BILLS_CONTACT_COUNTRIES = Setting('BILLS_CONTACT_COUNTRIES', tuple((k,v) for k,v in data.COUNTRIES.items()),
editable=False
serializable=False
)

View File

@ -1,26 +1,32 @@
from django.conf import settings
from django_countries import data
from orchestra.settings import Setting
CONTACTS_DEFAULT_EMAIL_USAGES = Setting('CONTACTS_DEFAULT_EMAIL_USAGES', (
'SUPPORT',
'ADMIN',
'BILLING',
'TECH',
'ADDS',
'EMERGENCY'
))
CONTACTS_DEFAULT_CITY = Setting('CONTACTS_DEFAULT_CITY',
'Barcelona'
CONTACTS_DEFAULT_EMAIL_USAGES = Setting('CONTACTS_DEFAULT_EMAIL_USAGES',
default=(
'SUPPORT',
'ADMIN',
'BILLING',
'TECH',
'ADDS',
'EMERGENCY'
),
)
CONTACTS_COUNTRIES = Setting('CONTACTS_COUNTRIES', tuple((k,v) for k,v in data.COUNTRIES.items()),
editable=False)
CONTACTS_DEFAULT_CITY = Setting('CONTACTS_DEFAULT_CITY',
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
)

View File

@ -1,17 +1,15 @@
from django.conf import settings
from orchestra.settings import Setting
DATABASES_TYPE_CHOICES = Setting('DATABASES_TYPE_CHOICES', (
('mysql', 'MySQL'),
('postgres', 'PostgreSQL'),
))
('mysql', 'MySQL'),
('postgres', 'PostgreSQL'),
),
validators=[Setting.validate_choices]
)
DATABASES_DEFAULT_TYPE = Setting('DATABASES_DEFAULT_TYPE', 'mysql', choices=DATABASES_TYPE_CHOICES)
DATABASES_DEFAULT_HOST = Setting('DATABASES_DEFAULT_HOST',
'localhost'
)
DATABASES_DEFAULT_HOST = Setting('DATABASES_DEFAULT_HOST', 'localhost')

View File

@ -94,7 +94,7 @@ class Domain(models.Model):
return self.origin.subdomain_set.all().prefetch_related('records')
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):
origin = self.origin

View File

@ -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
@ -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."
)
DOMAINS_DEFAULT_A = Setting('DOMAINS_DEFAULT_A',
'10.0.3.13'
DOMAINS_DEFAULT_A = Setting('DOMAINS_DEFAULT_A', '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()."
)
DOMAINS_SLAVES = Setting('DOMAINS_SLAVES', (),
DOMAINS_SLAVES = Setting('DOMAINS_SLAVES',
(),
help_text="Additional slave server ip addresses other than autodiscovered by router.get_servers()."
)

View File

@ -5,7 +5,7 @@ from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from markdown import markdown
from orchestra.forms.widgets import ReadOnlyWidget
from orchestra.forms.widgets import SpanWidget
from .models import Queue, Ticket
@ -40,7 +40,7 @@ class MessageInlineForm(forms.ModelForm):
def __init__(self, *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):
""" clean HTML tags """
@ -98,7 +98,7 @@ class TicketForm(forms.ModelForm):
description = description.replace('\n', '<br>')
description = description.replace('#Ha9G9-?8', '>\n')
description = '<div style="padding-left: 95px;">%s</div>' % description
widget = ReadOnlyWidget(description, description)
widget = SpanWidget(display=description)
self.fields['display_description'].widget = widget
def clean_description(self):

View File

@ -1,10 +1,7 @@
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',
True
)
ISSUES_NOTIFY_SUPERUSERS = Setting('ISSUES_NOTIFY_SUPERUSERS', True)

View File

@ -67,14 +67,15 @@ class MailmanBackend(ServiceController):
context['aliases'] = self.get_virtual_aliases(context)
# Preserve indentation
self.append(textwrap.dedent("""\
aliases='%(aliases)s'
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
else
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' \\
-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
fi
fi""") % context

View File

@ -2,7 +2,7 @@ from django import forms
from django.utils.translation import ugettext_lazy as _
from orchestra.core.validators import validate_password
from orchestra.forms.widgets import ReadOnlyWidget
from orchestra.forms.widgets import SpanWidget
class CleanAddressMixin(object):
@ -32,8 +32,8 @@ class ListCreationForm(CleanAddressMixin, forms.ModelForm):
class ListChangeForm(CleanAddressMixin, forms.ModelForm):
password = forms.CharField(label=_("Password"),
widget=ReadOnlyWidget('<strong>Unknown password</strong>'),
password = forms.CharField(label=_("Password"), required=False,
widget=SpanWidget(display='<strong>Unknown password</strong>'),
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 "
"<a href=\"password/\">this form</a>."))

View File

@ -1,14 +1,12 @@
from orchestra.settings import ORCHESTRA_BASE_DOMAIN, Setting
LISTS_DOMAIN_MODEL = Setting('LISTS_DOMAIN_MODEL',
'domains.Domain'
LISTS_DOMAIN_MODEL = Setting('LISTS_DOMAIN_MODEL', 'domains.Domain',
validators=[Setting.validate_model_label]
)
LISTS_DEFAULT_DOMAIN = Setting('LISTS_DEFAULT_DOMAIN',
'lists.{}'.format(ORCHESTRA_BASE_DOMAIN)
)
LISTS_DEFAULT_DOMAIN = Setting('LISTS_DEFAULT_DOMAIN', 'lists.{}'.format(ORCHESTRA_BASE_DOMAIN))
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',
'/var/log/mailman/post'
)
LISTS_MAILMAN_POST_LOG_PATH = Setting('LISTS_MAILMAN_POST_LOG_PATH', '/var/log/mailman/post')
LISTS_MAILMAN_ROOT_DIR = Setting('LISTS_MAILMAN_ROOT_DIR',
'/var/lib/mailman'
)
LISTS_MAILMAN_ROOT_DIR = Setting('LISTS_MAILMAN_ROOT_DIR', '/var/lib/mailman')
LISTS_VIRTUAL_ALIAS_PATH = Setting('LISTS_VIRTUAL_ALIAS_PATH',
'/etc/postfix/mailman_virtual_aliases'
)
LISTS_VIRTUAL_ALIAS_PATH = Setting('LISTS_VIRTUAL_ALIAS_PATH', '/etc/postfix/mailman_virtual_aliases')
LISTS_VIRTUAL_ALIAS_DOMAINS_PATH = Setting('LISTS_VIRTUAL_ALIAS_DOMAINS_PATH',
'/etc/postfix/mailman_virtual_domains'
)
LISTS_VIRTUAL_ALIAS_DOMAINS_PATH = Setting('LISTS_VIRTUAL_ALIAS_DOMAINS_PATH', '/etc/postfix/mailman_virtual_domains')

View File

@ -3,17 +3,16 @@ import textwrap
from django.utils.translation import ugettext_lazy as _
from orchestra.core.validators import validate_name
from orchestra.settings import ORCHESTRA_BASE_DOMAIN, Setting
MAILBOXES_DOMAIN_MODEL = Setting('MAILBOXES_DOMAIN_MODEL',
'domains.Domain'
MAILBOXES_DOMAIN_MODEL = Setting('MAILBOXES_DOMAIN_MODEL', 'domains.Domain',
validators=[Setting.validate_model_label]
)
MAILBOXES_HOME = Setting('MAILBOXES_HOME',
'/home/%(name)s/'
)
MAILBOXES_HOME = Setting('MAILBOXES_HOME', '/home/%(name)s/')
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',
'/dev/shm'
)
MAILBOXES_SIEVETEST_PATH = Setting('MAILBOXES_SIEVETEST_PATH', '/dev/shm')
MAILBOXES_SIEVETEST_BIN_PATH = Setting('MAILBOXES_SIEVETEST_BIN_PATH',
'%(orchestra_root)s/bin/sieve-test'
MAILBOXES_SIEVETEST_BIN_PATH = Setting('MAILBOXES_SIEVETEST_BIN_PATH', '%(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',
ORCHESTRA_BASE_DOMAIN
MAILBOXES_LOCAL_DOMAIN = Setting('MAILBOXES_LOCAL_DOMAIN', ORCHESTRA_BASE_DOMAIN,
validators=[validate_name]
)
MAILBOXES_PASSWD_PATH = Setting('MAILBOXES_PASSWD_PATH',
'/etc/dovecot/passwd'
)
MAILBOXES_PASSWD_PATH = Setting('MAILBOXES_PASSWD_PATH', '/etc/dovecot/passwd')
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',
'%(home)s/Maildir/maildirsize'
MAILBOXES_MAILDIRSIZE_PATH = Setting('MAILBOXES_MAILDIRSIZE_PATH', '%(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',
ORCHESTRA_BASE_DOMAIN
)
MAILBOXES_MAIL_LOG_PATH = Setting('MAILBOXES_MAIL_LOG_PATH', '/var/log/mail.log')
MAILBOXES_MAIL_LOG_PATH = Setting('MAILBOXES_MAIL_LOG_PATH',
'/var/log/mail.log'
)
MAILBOXES_MOVE_ON_DELETE_PATH = Setting('MAILBOXES_MOVE_ON_DELETE_PATH',
''
)
MAILBOXES_MOVE_ON_DELETE_PATH = Setting('MAILBOXES_MOVE_ON_DELETE_PATH', '')

View File

@ -31,7 +31,7 @@ def SSH(backend, log, server, cmds, async=False):
script = script.replace('\r', '')
bscript = script.encode('utf-8')
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
log.script = '# %s\n%s' % (remote_path, script)
log.save(update_fields=['script'])

View File

@ -94,7 +94,7 @@ class OperationsMiddleware(object):
def process_response(self, request, response):
""" Processes pending backend operations """
if not isinstance(response, HttpResponseServerError):
operations = type(self).get_pending_operations()
operations = self.get_pending_operations()
if operations:
try:
scripts, block = manager.generate(operations)

View File

@ -5,8 +5,10 @@ from orchestra.settings import Setting
ORCHESTRATION_OS_CHOICES = Setting('ORCHESTRATION_OS_CHOICES', (
('LINUX', "Linux"),
))
('LINUX', "Linux"),
),
validators=[Setting.validate_choices]
)
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'))
ORCHESTRATION_ROUTER = Setting('ORCHESTRATION_ROUTER',
'orchestra.contrib.orchestration.models.Route'
ORCHESTRATION_ROUTER = Setting('ORCHESTRATION_ROUTER', 'orchestra.contrib.orchestration.models.Route',
validators=[Setting.validate_import_class]
)
ORCHESTRATION_TEMP_SCRIPT_PATH = Setting('ORCHESTRATION_TEMP_SCRIPT_PATH',
'/dev/shm'
)
ORCHESTRATION_TEMP_SCRIPT_DIR = Setting('ORCHESTRATION_TEMP_SCRIPT_DIR', '/dev/shm')
ORCHESTRATION_DISABLE_EXECUTION = Setting('ORCHESTRATION_DISABLE_EXECUTION',
False
)
ORCHESTRATION_DISABLE_EXECUTION = Setting('ORCHESTRATION_DISABLE_EXECUTION', False)
ORCHESTRATION_BACKEND_CLEANUP_DELTA = Setting('ORCHESTRATION_BACKEND_CLEANUP_DELTA',

View File

@ -91,7 +91,6 @@ class BillSelectedOrders(object):
url = reverse('admin:bills_bill_changelist')
ids = ','.join(map(str, bills))
url += '?id__in=%s' % ids
num = len(bills)
msg = ungettext(
'<a href="{url}">One bill</a> has 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)
return
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({
'title': _("Confirmation for billing selected orders"),
'step': 3,
'form': form,
'bills': bills,
'bills': bills_with_total,
})
return render(request, self.template, self.context)

View File

@ -1,34 +1,34 @@
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', 'orchestra.contrib.orders.billing.BillsBackend',
validators=[Setting.validate_import_class],
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', 'services.Service',
validators=[Setting.validate_model_label],
help_text="Pluggable service class."
)
# Prevent inspecting these apps for service accounting
ORDERS_EXCLUDED_APPS = Setting('ORDERS_EXCLUDED_APPS', (
'orders',
'admin',
'contenttypes',
'auth',
'migrations',
'sessions',
'orchestration',
'bills',
'services',
))
# Only account for significative changes
# metric_storage new value: lastvalue*(1+threshold) > currentvalue or lastvalue*threshold < currentvalue
ORDERS_METRIC_ERROR = Setting('ORDERS_METRIC_ERROR',
0.01
'orders',
'admin',
'contenttypes',
'auth',
'migrations',
'sessions',
'orchestration',
'bills',
'services',
),
help_text="Prevent inspecting these apps for service accounting."
)
ORDERS_METRIC_ERROR = Setting('ORDERS_METRIC_ERROR', 0.01,
help_text=("Only account for significative changes.<br>"
"metric_storage new value: <tt>lastvalue*(1+threshold) > currentvalue or lastvalue*threshold < currentvalue</tt>.")
)

View File

@ -28,11 +28,11 @@
<div>
<div style="margin:20px;">
{% if bills %}
{% for account, lines in bills %}
{% for account, total, lines in bills %}
<div class="inline-group" id="rates-group">
<div class="tabular inline-related last-related">
<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" }} &euro;</span></h2>
<table>
<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>

View File

@ -3,25 +3,19 @@ from orchestra.settings import Setting
from .. import payments
PAYMENT_CURRENCY = Setting('PAYMENT_CURRENCY',
'Eur'
)
PAYMENT_CURRENCY = Setting('PAYMENT_CURRENCY', 'Eur')
PAYMENTS_DD_CREDITOR_NAME = Setting('PAYMENTS_DD_CREDITOR_NAME',
'Orchestra')
PAYMENTS_DD_CREDITOR_NAME = Setting('PAYMENTS_DD_CREDITOR_NAME', 'Orchestra')
PAYMENTS_DD_CREDITOR_IBAN = Setting('PAYMENTS_DD_CREDITOR_IBAN',
'IE98BOFI90393912121212')
PAYMENTS_DD_CREDITOR_IBAN = Setting('PAYMENTS_DD_CREDITOR_IBAN', 'IE98BOFI90393912121212')
PAYMENTS_DD_CREDITOR_BIC = Setting('PAYMENTS_DD_CREDITOR_BIC',
'BOFIIE2D')
PAYMENTS_DD_CREDITOR_BIC = Setting('PAYMENTS_DD_CREDITOR_BIC', 'BOFIIE2D')
PAYMENTS_DD_CREDITOR_AT02_ID = Setting('PAYMENTS_DD_CREDITOR_AT02_ID',
'InvalidAT02ID')
PAYMENTS_DD_CREDITOR_AT02_ID = Setting('PAYMENTS_DD_CREDITOR_AT02_ID', 'InvalidAT02ID')
PAYMENTS_ENABLED_METHODS = Setting('PAYMENTS_ENABLED_METHODS', (

View File

@ -42,7 +42,7 @@ class ServiceMonitor(ServiceBackend):
from .models import MonitorData
try:
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:
return None

View File

@ -1,30 +1,38 @@
from django import forms
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,
widget=ShowTextWidget(tag='<b>'))
widget=SpanWidget(tag='<b>'))
allocated = forms.DecimalField(label=_("Allocated"))
unit = forms.CharField(label=_("Unit"), widget=ShowTextWidget(), required=False)
unit = forms.CharField(label=_("Unit"), required=False)
class Meta:
fields = ('verbose_name', 'used', 'last_update', 'allocated', 'unit')
readonly_fields = ('verbose_name', 'unit')
def __init__(self, *args, **kwargs):
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)
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:
self.fields['allocated'].required = False
self.fields['allocated'].widget = ReadOnlyWidget(None, '')
self.fields['allocated'].widget = SpanWidget(original=None, display='')
else:
self.fields['allocated'].required = True
self.fields['allocated'].initial = self.resource.default_allocation
# def has_changed(self):
# """ Make sure resourcedata objects are created for all resources """
# if not self.instance.pk:

View File

@ -14,8 +14,8 @@ class GitLabForm(SoftwareServiceForm):
help_text=_("Initial email address, changes on the GitLab server are not reflected here."))
class GitLaChangebForm(GitLabForm):
user_id = forms.IntegerField(label=("User ID"), widget=widgets.ShowTextWidget,
class GitLaChangeForm(GitLabForm):
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."))
@ -27,7 +27,7 @@ class GitLabSerializer(serializers.Serializer):
class GitLabService(SoftwareService):
name = 'gitlab'
form = GitLabForm
change_form = GitLaChangebForm
change_form = GitLaChangeForm
serializer = GitLabSerializer
site_domain = settings.SAAS_GITLAB_DOMAIN
change_readonly_fileds = ('email', 'user_id',)

View File

@ -6,7 +6,7 @@ from django.utils.translation import ugettext_lazy as _
from orchestra import plugins
from orchestra.contrib.orchestration import Operation
from orchestra.core import validators
from orchestra.forms import widgets
from orchestra.forms.widgets import SpanWidget
from orchestra.plugins.forms import PluginDataForm
from orchestra.utils.functional import cached
from orchestra.utils.python import import_class, random_ascii
@ -15,9 +15,9 @@ from .. import settings
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,
widget=widgets.ReadOnlyWidget('<strong>Unknown password</strong>'),
widget=SpanWidget(display='<strong>Unknown password</strong>'),
validators=[
validators.validate_password,
RegexValidator(r'^[^"\'\\]+$',
@ -36,6 +36,7 @@ class SoftwareServiceForm(PluginDataForm):
class Meta:
exclude = ('database',)
readonly_fields = ('site_url',)
def __init__(self, *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)
else:
site_link = '&lt;site_name&gt;.%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")
def clean_password2(self):

View File

@ -5,7 +5,7 @@ from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _
from orchestra.contrib.databases.models import Database, DatabaseUser
from orchestra.forms import widgets
from orchestra.forms.widgets import SpanWidget
from .. import settings
from .options import SoftwareService, SoftwareServiceForm
@ -13,7 +13,7 @@ from .options import SoftwareService, SoftwareServiceForm
class PHPListForm(SoftwareServiceForm):
admin_username = forms.CharField(label=_("Admin username"), required=False,
widget=widgets.ReadOnlyWidget('admin'))
widget=SpanWidget(display='admin'))
def __init__(self, *args, **kwargs):
super(PHPListForm, self).__init__(*args, **kwargs)
@ -37,7 +37,7 @@ class PHPListChangeForm(PHPListForm):
db = self.instance.database
db_url = reverse('admin:databases_database_change', args=(db.pk,))
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):
@ -57,7 +57,7 @@ class PHPListService(SoftwareService):
return settings.SAAS_PHPLIST_DB_NAME
def get_account(self):
return type(self.instance.account).get_main()
return self.instance.account.get_main()
def validate(self):
super(PHPListService, self).validate()

View File

@ -19,9 +19,7 @@ SAAS_ENABLED_SERVICES = Setting('SAAS_ENABLED_SERVICES', (
)
SAAS_WORDPRESS_ADMIN_PASSWORD = Setting('SAAS_WORDPRESSMU_ADMIN_PASSWORD',
'secret'
)
SAAS_WORDPRESS_ADMIN_PASSWORD = Setting('SAAS_WORDPRESSMU_ADMIN_PASSWORD', 'secret')
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',
'gitlab.{}'.format(ORCHESTRA_BASE_DOMAIN)
)

View File

@ -11,7 +11,7 @@ from django.utils.translation import ugettext, ugettext_lazy as _
from orchestra import plugins
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
@ -50,8 +50,7 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount):
try:
bool(getattr(self, method)(obj))
except Exception as exception:
name = type(exception).__name__
raise ValidationError(': '.join((name, str(exception))))
raise ValidationError(format_exception(exc))
def validate_match(self, service):
if not service.match:

View File

@ -4,9 +4,11 @@ from orchestra.settings import Setting
SERVICES_SERVICE_TAXES = Setting('SERVICES_SERVICE_TAXES', (
(0, _("Duty free")),
(21, "21%"),
))
(0, _("Duty free")),
(21, "21%"),
),
validators=[Setting.validate_choices]
)
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',
'orders.Order'
SERVICES_ORDER_MODEL = Setting('SERVICES_ORDER_MODEL', 'orders.Order',
validators=[Setting.validate_model_label]
)
SERVICES_RATE_CLASS = Setting('SERVICES_RATE_CLASS',
'orchestra.contrib.plans.models.Rate'
SERVICES_RATE_CLASS = Setting('SERVICES_RATE_CLASS', 'orchestra.contrib.plans.models.Rate',
validators=[Setting.validate_import_class]
)
SERVICES_DEFAULT_IGNORE_PERIOD = Setting('SERVICES_DEFAULT_IGNORE_PERIOD',
'TEN_DAYS'
)
SERVICES_DEFAULT_IGNORE_PERIOD = Setting('SERVICES_DEFAULT_IGNORE_PERIOD', 'TEN_DAYS')
SERVICES_IGNORE_ACCOUNT_TYPE = Setting('SERVICES_IGNORE_ACCOUNT_TYPE', (

View File

@ -5,9 +5,11 @@ from functools import partial
from django import forms
from django.core.exceptions import ValidationError
from django.forms.formsets import formset_factory
from django.utils.functional import Promise
from django.utils.translation import ugettext_lazy as _
from orchestra.forms import ReadOnlyFormMixin, widgets
from orchestra.utils.python import format_exception
from . import parser
@ -20,21 +22,21 @@ class SettingForm(ReadOnlyFormMixin, forms.Form):
widget=forms.Textarea(attrs={
'cols': 65,
'rows': 2,
'style': 'font-family:monospace'
'style': 'font-family: monospace',
}))
CHARFIELD = partial(forms.CharField,
widget=forms.TextInput(attrs={
'size': 65,
'style': 'font-family:monospace'
}))
NON_EDITABLE = partial(forms.CharField, widget=widgets.ShowTextWidget(), required=False)
widget=forms.TextInput(attrs={
'size': 65,
'style': 'font-family: monospace',
}))
NON_EDITABLE = partial(forms.CharField, widget=widgets.SpanWidget, required=False)
FORMFIELD_FOR_SETTING_TYPE = {
bool: partial(forms.BooleanField, required=False),
int: forms.IntegerField,
tuple: TEXTAREA,
list: TEXTAREA,
dict: TEXTAREA,
type(_()): CHARFIELD,
Promise: CHARFIELD,
str: CHARFIELD,
}
@ -50,8 +52,12 @@ class SettingForm(ReadOnlyFormMixin, forms.Form):
self.setting_type = initial['type']
self.setting = initial['setting']
setting = self.setting
serialized_value = parser.serialize(initial['value'])
serialized_default = parser.serialize(initial['default'])
if setting.serializable:
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):
field = self.NON_EDITABLE
else:
@ -101,7 +107,7 @@ class SettingForm(ReadOnlyFormMixin, forms.Form):
try:
value = eval(value, parser.get_eval_context())
except Exception as exc:
raise ValidationError(str(exc))
raise ValidationError(format_exception(exc))
self.setting.validate_value(value)
if not isinstance(value, self.setting_type):
if self.setting_type in (tuple, list) and isinstance(value, (tuple, list)):

View File

@ -3,6 +3,7 @@ import os
import re
from django.utils.translation import ugettext_lazy as _
from django.utils.functional import Promise
from orchestra.utils.paths import get_project_dir
@ -60,7 +61,7 @@ def get_eval_context():
def serialize(obj, init=True):
if isinstance(obj, NotSupported):
return obj
elif isinstance(obj, type(_())):
elif isinstance(obj, Promise):
_obj = LazyUgettextRepr(obj)
elif isinstance(obj, dict):
_obj = {}

View File

@ -4,11 +4,13 @@ from orchestra.settings import Setting
SYSTEMUSERS_SHELLS = Setting('SYSTEMUSERS_SHELLS', (
('/dev/null', _("No shell, FTP only")),
('/bin/rssh', _("No shell, SFTP/RSYNC only")),
('/bin/bash', "/bin/bash"),
('/bin/sh', "/bin/sh"),
))
('/dev/null', _("No shell, FTP only")),
('/bin/rssh', _("No shell, SFTP/RSYNC only")),
('/bin/bash', "/bin/bash"),
('/bin/sh', "/bin/sh"),
),
validators=[Setting.validate_choices]
)
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', (
'/dev/null',
'/bin/rssh',
))
SYSTEMUSERS_DISABLED_SHELLS = Setting('SYSTEMUSERS_DISABLED_SHELLS',
default=(
'/dev/null',
'/bin/rssh',
),
)
SYSTEMUSERS_HOME = Setting('SYSTEMUSERS_HOME',

View File

@ -2,16 +2,20 @@ from orchestra.settings import Setting
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_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)

View File

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

View File

@ -25,35 +25,32 @@ WEBAPPS_PHPFPM_POOL_PATH = Setting('WEBAPPS_PHPFPM_POOL_PATH',
WEBAPPS_FCGID_WRAPPER_PATH = Setting('WEBAPPS_FCGID_WRAPPER_PATH',
# Inside SuExec Document root
# Make sure all account wrappers are in the same DIR
'/home/httpd/fcgi-bin.d/%(user)s/%(app_name)s-wrapper'
'/home/httpd/fcgi-bin.d/%(user)s/%(app_name)s-wrapper',
help_text=("Inside SuExec Document root.<br>"
"Make sure all account wrappers are in the same DIR.")
)
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',
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',
# Combine all fcgid-wrappers/fpm-pools into one per account-php_version
# to better control num processes per account and save memory
False)
False,
help_text=("Combine all fcgid-wrappers/fpm-pools into one per account-php_version "
"to better control num processes per account and save memory")
)
WEBAPPS_TYPES = Setting('WEBAPPS_TYPES', (
'orchestra.contrib.webapps.types.php.PHPApp',
@ -70,13 +67,15 @@ WEBAPPS_TYPES = Setting('WEBAPPS_TYPES', (
WEBAPPS_PHP_VERSIONS = Setting('WEBAPPS_PHP_VERSIONS', (
# Execution modle choose by ending -fpm or -cgi
('5.4-fpm', 'PHP 5.4 FPM'),
('5.4-cgi', 'PHP 5.4 FCGID'),
('5.3-cgi', 'PHP 5.3 FCGID'),
('5.2-cgi', 'PHP 5.2 FCGID'),
('4-cgi', 'PHP 4 FCGID'),
))
('5.4-fpm', 'PHP 5.4 FPM'),
('5.4-cgi', 'PHP 5.4 FCGID'),
('5.3-cgi', 'PHP 5.3 FCGID'),
('5.2-cgi', 'PHP 5.2 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',
@ -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',
# Path of the cgi binary used by fcgid
'/usr/bin/php%(php_version_number)s-cgi'
WEBAPPS_PHP_CGI_BINARY_PATH = Setting('WEBAPPS_PHP_CGI_BINARY_PATH', '/usr/bin/php%(php_version_number)s-cgi',
help_text="Path of the cgi binary used by fcgid."
)
WEBAPPS_PHP_CGI_RC_DIR = Setting('WEBAPPS_PHP_CGI_RC_DIR',
# Path to php.ini
'/etc/php%(php_version_number)s/cgi/'
WEBAPPS_PHP_CGI_RC_DIR = Setting('WEBAPPS_PHP_CGI_RC_DIR', '/etc/php%(php_version_number)s/cgi/',
help_text="Path to php.ini."
)
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'
)
WEBAPPS_PYTHON_VERSIONS = Setting('WEBAPPS_PYTHON_VERSIONS', (
('3.4-uwsgi', 'Python 3.4 uWSGI'),
('2.7-uwsgi', 'Python 2.7 uWSGI'),
))
('3.4-uwsgi', 'Python 3.4 uWSGI'),
('2.7-uwsgi', 'Python 2.7 uWSGI'),
),
validators=[Setting.validate_choices]
)
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',
'passthru',
'shell_exec',
@ -174,7 +172,7 @@ WEBAPPS_PHP_DISABLED_FUNCTIONS = Setting('WEBAPPS_PHP_DISABLED_FUNCTION', [
'escapeshellcmd',
'escapeshellarg',
'dl'
])
))
WEBAPPS_ENABLED_OPTIONS = Setting('WEBAPPS_ENABLED_OPTIONS', (

View File

@ -6,7 +6,7 @@ from django.utils.translation import ugettext_lazy as _
from rest_framework import serializers
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 .php import PHPApp, PHPAppForm, PHPAppSerializer
@ -30,13 +30,13 @@ class CMSAppForm(PHPAppForm):
db_id = data.get('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))
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 = data.get('db_user')
db_user_id = data.get('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))
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):

View File

@ -24,7 +24,7 @@ class Apache2Backend(ServiceController):
model = 'websites.Website'
related_models = (
('websites.Content', 'website'),
('websites.WebsiteDirective', 'directives'),
('websites.WebsiteDirective', 'website'),
('webapps.WebApp', 'website_set'),
)
verbose_name = _("Apache 2")

View File

@ -6,40 +6,40 @@ from .. import websites
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_PORT_CHOICES = getattr(settings, 'WEBSITES_PORT_CHOICES', (
# (80, 'HTTP'),
# (443, 'HTTPS'),
#))
WEBSITES_PROTOCOL_CHOICES = Setting('WEBSITES_PROTOCOL_CHOICES',
default=(
('http', "HTTP"),
('https', "HTTPS"),
('http/https', _("HTTP and HTTPS")),
('https-only', _("HTTPS only")),
),
validators=[Setting.validate_choices]
)
WEBSITES_PROTOCOL_CHOICES = Setting('WEBSITES_PROTOCOL_CHOICES', (
('http', "HTTP"),
('https', "HTTPS"),
('http/https', _("HTTP and HTTPS")),
('https-only', _("HTTPS only")),
))
WEBSITES_DEFAULT_PROTOCOL = Setting('WEBSITES_DEFAULT_PROTOCOL', 'http',
WEBSITES_DEFAULT_PROTOCOL = Setting('WEBSITES_DEFAULT_PROTOCOL',
default='http',
choices=WEBSITES_PROTOCOL_CHOICES
)
WEBSITES_DEFAULT_IPS = Setting('WEBSITES_DEFAULT_IPS', (
'*',
))
WEBSITES_DOMAIN_MODEL = Setting('WEBSITES_DOMAIN_MODEL',
'domains.Domain'
WEBSITES_DEFAULT_IPS = Setting('WEBSITES_DEFAULT_IPS',
default=('*',)
)
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.Proxy',
'orchestra.contrib.websites.directives.ErrorDocument',

View File

@ -16,7 +16,7 @@ def all_valid(*args):
if len(args) == 1:
# Dict
errors = {}
kwargs = args
kwargs = args[0]
for field, validator in kwargs.items():
try:
validator[0](*validator[1:])
@ -36,7 +36,7 @@ def all_valid(*args):
def validate_ipv4_address(value):
msg = _("%s is not a valid IPv4 address") % value
msg = _("Not a valid IPv4 address")
try:
ip = IP(value)
except:
@ -46,7 +46,7 @@ def validate_ipv4_address(value):
def validate_ipv6_address(value):
msg = _("%s is not a valid IPv6 address") % value
msg = _("Not a valid IPv6 address")
try:
ip = IP(value)
except:
@ -56,7 +56,7 @@ def validate_ipv6_address(value):
def validate_ip_address(value):
msg = _("%s is not a valid IP address") % value
msg = _("Not a valid IP address")
try:
IP(value)
except:

View File

@ -81,11 +81,10 @@ class ReadOnlyFormMixin(object):
for name in self.Meta.readonly_fields:
field = self.fields[name]
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'):
# Model form
original_value = getattr(self.instance, name)
else:
original_value = self.initial.get(name)
field.widget.original_value = original_value
original = getattr(self.instance, name, original)
field.widget.original = original

View File

@ -7,7 +7,7 @@ from django.utils.encoding import force_text
from django.contrib.admin.templatetags.admin_static import static
# TODO rename readonlywidget
class SpanWidget(forms.Widget):
"""
Renders a value wrapped in a <span> tag.
@ -15,74 +15,29 @@ class SpanWidget(forms.Widget):
"""
def __init__(self, *args, **kwargs):
self.tag = kwargs.pop('tag', '<span>')
self.original = kwargs.pop('original', '')
self.display = kwargs.pop('display', None)
super(SpanWidget, self).__init__(*args, **kwargs)
def render(self, name, value, attrs=None):
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
if isinstance(original_value, bool):
icon = static('admin/img/icon-%s.gif' % ('yes' if original_value else 'no',))
return mark_safe('<img src="%s" alt="%s">' % (icon, str(original_value)))
if isinstance(original, bool):
icon = static('admin/img/icon-%s.gif' % ('yes' if original else 'no',))
return mark_safe('<img src="%s" alt="%s">' % (icon, str(display)))
tag = 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):
return self.original_value
return self.original
def _has_changed(self, initial, data):
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):
""" Ugly hack to render this widget nicely on Django admin """
widget = forms.CheckboxSelectMultiple()

View File

@ -1,7 +1,7 @@
from django import forms
from django.utils.encoding import force_text
from orchestra.forms.widgets import ReadOnlyWidget
from orchestra.forms.widgets import SpanWidget
class PluginDataForm(forms.ModelForm):
@ -12,7 +12,7 @@ class PluginDataForm(forms.ModelForm):
if self.plugin_field in self.fields:
value = self.plugin.get_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
self.fields[self.plugin_field].help_text = getattr(self.plugin, 'help_text', help_text)
if self.instance:
@ -34,7 +34,7 @@ class PluginDataForm(forms.ModelForm):
if foo_display:
display = foo_display()
self.fields[field].required = False
self.fields[field].widget = ReadOnlyWidget(value, display)
self.fields[field].widget = SpanWidget(original=value, display=display)
def clean(self):
super(PluginDataForm, self).clean()

View File

@ -1,16 +1,21 @@
import re
import sys
from collections import OrderedDict
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 orchestra.utils.python import import_class, format_exception
from .core import validators
class Setting(object):
"""
Keeps track of the defined settings.
Instances of this class are the native value of the setting.
Keeps track of the defined settings and provides extra batteries like value validation.
"""
conf_settings = settings
settings = OrderedDict()
@ -23,12 +28,12 @@ class Setting(object):
value = ("'%s'" if isinstance(value, str) else '%s') % value
return '<%s: %s>' % (self.name, value)
def __new__(cls, name, default, help_text="", choices=None, editable=True, multiple=False,
validators=[], types=[], call_init=False):
def __new__(cls, name, default, help_text="", choices=None, editable=True, serializable=True,
multiple=False, validators=[], types=[], call_init=False):
if call_init:
return super(Setting, cls).__new__(cls)
cls.settings[name] = cls(name, default, help_text=help_text, choices=choices,
editable=editable, multiple=multiple, validators=validators, types=types, call_init=True)
cls.settings[name] = cls(name, default, help_text=help_text, choices=choices, editable=editable,
serializable=serializable, multiple=multiple, validators=validators, types=types, call_init=True)
return cls.get_value(name, default)
def __init__(self, *args, **kwargs):
@ -36,11 +41,67 @@ class Setting(object):
for name, value in kwargs.items():
setattr(self, name, value)
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
@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):
validators.all_valid(value, self.validators)
if value:
validators.all_valid(value, self.validators)
valid_types = list(self.types)
if isinstance(self.default, (list, tuple)):
valid_types.extend([list, tuple])
@ -56,48 +117,61 @@ class Setting(object):
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."
"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',
_("%s Hosting Management" % ORCHESTRA_SITE_NAME.capitalize())
_("%s Hosting Management" % ORCHESTRA_SITE_NAME.capitalize()),
)
# Service management commands
ORCHESTRA_START_SERVICES = Setting('ORCHESTRA_START_SERVICES', (
'postgresql',
'celeryevcam',
'celeryd',
'celerybeat',
('uwsgi', 'nginx'),
))
ORCHESTRA_START_SERVICES = Setting('ORCHESTRA_START_SERVICES',
default=(
'postgresql',
'celeryevcam',
'celeryd',
'celerybeat',
('uwsgi', 'nginx'),
),
)
ORCHESTRA_RESTART_SERVICES = Setting('ORCHESTRA_RESTART_SERVICES', (
'celeryd',
'celerybeat',
'uwsgi'
))
ORCHESTRA_RESTART_SERVICES = Setting('ORCHESTRA_RESTART_SERVICES',
default=(
'celeryd',
'celerybeat',
'uwsgi'
),
)
ORCHESTRA_STOP_SERVICES = Setting('ORCHESTRA_STOP_SERVICES', (
('uwsgi', 'nginx'),
'celerybeat',
'celeryd',
'celeryevcam',
'postgresql'
))
ORCHESTRA_STOP_SERVICES = Setting('ORCHESTRA_STOP_SERVICES',
default=(
('uwsgi', 'nginx'),
'celerybeat',
'celeryd',
'celeryevcam',
'postgresql'
),
)
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
)

View File

@ -16,6 +16,11 @@ def random_ascii(length):
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):
def __init__(self, iterable=None):
self.end = end = []