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