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? # 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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" }} &euro;</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>

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = '&lt;site_name&gt;.%s' % self.plugin.site_base_domain 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") self.fields['name'].label = _("Username")
def clean_password2(self): 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 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()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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