Added admin support for settings

This commit is contained in:
Marc Aymerich 2015-04-26 13:53:00 +00:00
parent e8759578b5
commit 759c01c64c
47 changed files with 1782 additions and 368 deletions

23
TODO.md
View File

@ -257,8 +257,6 @@ https://code.djangoproject.com/ticket/24576
+ Query Expressions, Conditional Expressions, and Database Functions¶
* forms: You can now pass a callable that returns an iterable of choices when instantiating a ChoiceField.
* migrate to DRF3.x
* move all tests to django-orchestra/tests
* *natural keys: those fields that uniquely identify a service, list.name, website.name, webapp.name+account, make sure rest api can not edit thos things
@ -287,3 +285,24 @@ https://code.djangoproject.com/ticket/24576
# Determine the difference between data serializer used for validation and used for the rest API!
# Make PluginApiView that fills metadata and other stuff like modeladmin plugin support
# @classmethods do not need to be called with type(object)!
# Deprectae widgets.showtext and readonlyField by ReadOnlyFormMixin
# custom validation for settings
# TODO orchestra related services code reload: celery/uwsgi reloading find aonther way without root and implement reload
# insert settings on dashboard dynamically
# rename "edit settings" -> change settings
# View settings file
contrib/orders/models.py: if type(instance) in services:
contrib/orders/models.py: if type(instance) in services:
contrib/orders/helpers.py: if type(node) in services:
contrib/bills/admin.py: return [inline for inline in inlines if type(inline) is not BillLineInline]
contrib/bills/admin.py: return [inline for inline in inlines if type(inline) is not ClosedBillLineInline]
contrib/accounts/actions.py.save: if type(service) in registered_services:
contrib/accounts/actions.py: if type(service) in registered_services:
permissions/options.py: for func in inspect.getmembers(type(self), predicate=inspect.ismethod):

View File

@ -1,2 +1,32 @@
from .options import *
from functools import update_wrapper
from django.contrib.admin import site
from .dashboard import *
from .options import *
# monkey-patch admin.site in order to porvide some extra admin urls
urls = []
def register_url(pattern, view, name=""):
global urls
urls.append((pattern, view, name))
site.register_url = register_url
site_get_urls = site.get_urls
def get_urls():
def wrap(view, cacheable=False):
def wrapper(*args, **kwargs):
return site.admin_view(view, cacheable)(*args, **kwargs)
wrapper.admin_site = site
return update_wrapper(wrapper, view)
global urls
extra_patterns = []
for pattern, view, name in urls:
extra_patterns.append(
url(pattern, wrap(view), name=name)
)
return site_get_urls() + extra_patterns
site.get_urls = get_urls

View File

@ -1,20 +1,31 @@
from django.conf import settings
from fluent_dashboard import dashboard
from fluent_dashboard.modules import CmsAppIconList
from orchestra.core import services
def generate_services_group():
models = []
for model, options in services.get().items():
if options.get('menu', True):
models.append("%s.%s" % (model.__module__, model._meta.object_name))
settings.FLUENT_DASHBOARD_APP_GROUPS += (
('Services', {
'models': models,
'collapsible': True,
}),
)
generate_services_group()
class OrchestraIndexDashboard(dashboard.FluentIndexDashboard):
def get_application_modules(self):
modules = super(OrchestraIndexDashboard, self).get_application_modules()
models = []
for model, options in services.get().items():
if options.get('menu', True):
models.append("%s.%s" % (model.__module__, model._meta.object_name))
# TODO make this dynamic
for module in modules:
if module.title == 'Administration':
module.children.append({
'models': [{
'add_url': '/admin/settings/',
'app_name': 'settings',
'change_url': '/admin/settings/setting/',
'name': 'setting',
'title': "Settings" }],
'name': 'settings',
'title': 'Settings',
'url': '/admin/settings/'
})
service_icon_list = CmsAppIconList('Services', models=models, collapsible=True)
modules.append(service_icon_list)
return modules

View File

@ -47,8 +47,7 @@ class AdminFormSet(BaseModelFormSet):
def adminmodelformset_factory(modeladmin, form, formset=AdminFormSet, **kwargs):
formset = modelformset_factory(modeladmin.model, form=form, formset=formset,
**kwargs)
formset = modelformset_factory(modeladmin.model, form=form, formset=formset, **kwargs)
formset.modeladmin = modeladmin
return formset

View File

@ -59,6 +59,9 @@ def get_accounts():
def get_administration_items():
childrens = []
if isinstalled('orchestra.contrib.settings'):
url = reverse('admin:settings_edit_settings')
childrens.append(items.MenuItem(_("Settings"), url))
if isinstalled('orchestra.contrib.services'):
url = reverse('admin:services_service_changelist')
childrens.append(items.MenuItem(_("Services"), url))

View File

@ -114,6 +114,8 @@ INSTALLED_APPS = (
# Last to load
'orchestra.contrib.resources',
'orchestra.contrib.settings',
)
@ -139,7 +141,7 @@ ADMIN_TOOLS_MENU = 'orchestra.admin.menu.OrchestraMenu'
# Fluent dashboard
# TODO subclass like in admin_tools_menu
ADMIN_TOOLS_INDEX_DASHBOARD = 'fluent_dashboard.dashboard.FluentIndexDashboard'
ADMIN_TOOLS_INDEX_DASHBOARD = 'orchestra.admin.dashboard.OrchestraIndexDashboard'
FLUENT_DASHBOARD_ICON_THEME = '../orchestra/icons'
FLUENT_DASHBOARD_APP_GROUPS = (
@ -204,6 +206,7 @@ FLUENT_DASHBOARD_APP_ICONS = {
'issues/ticket': 'Ticket_star.png',
'miscellaneous/miscservice': 'Misc-Misc-Box-icon.png',
# Administration
'settings/setting': 'preferences.png',
'djcelery/taskstate': 'taskstate.png',
'orchestration/server': 'vps.png',
'orchestration/route': 'hal.png',

View File

@ -1,10 +1,10 @@
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from orchestra.settings import ORCHESTRA_BASE_DOMAIN
from orchestra.settings import ORCHESTRA_BASE_DOMAIN, Setting
ACCOUNTS_TYPES = getattr(settings, 'ACCOUNTS_TYPES', (
ACCOUNTS_TYPES = Setting('ACCOUNTS_TYPES', (
('INDIVIDUAL', _("Individual")),
('ASSOCIATION', _("Association")),
('CUSTOMER', _("Customer")),
@ -15,32 +15,26 @@ ACCOUNTS_TYPES = getattr(settings, 'ACCOUNTS_TYPES', (
))
ACCOUNTS_DEFAULT_TYPE = getattr(settings, 'ACCOUNTS_DEFAULT_TYPE',
'INDIVIDUAL'
)
ACCOUNTS_DEFAULT_TYPE = Setting('ACCOUNTS_DEFAULT_TYPE', 'INDIVIDUAL', choices=ACCOUNTS_TYPES)
ACCOUNTS_LANGUAGES = getattr(settings, 'ACCOUNTS_LANGUAGES', (
ACCOUNTS_LANGUAGES = Setting('ACCOUNTS_LANGUAGES', (
('EN', _('English')),
))
ACCOUNTS_SYSTEMUSER_MODEL = getattr(settings, 'ACCOUNTS_SYSTEMUSER_MODEL',
ACCOUNTS_DEFAULT_LANGUAGE = Setting('ACCOUNTS_DEFAULT_LANGUAGE', 'EN', choices=ACCOUNTS_LANGUAGES)
ACCOUNTS_SYSTEMUSER_MODEL = Setting('ACCOUNTS_SYSTEMUSER_MODEL',
'systemusers.SystemUser'
)
ACCOUNTS_DEFAULT_LANGUAGE = getattr(settings, 'ACCOUNTS_DEFAULT_LANGUAGE',
'EN'
)
ACCOUNTS_MAIN_PK = Setting('ACCOUNTS_MAIN_PK', 1)
ACCOUNTS_MAIN_PK = getattr(settings, 'ACCOUNTS_MAIN_PK',
1
)
ACCOUNTS_CREATE_RELATED = getattr(settings, 'ACCOUNTS_CREATE_RELATED', (
ACCOUNTS_CREATE_RELATED = Setting('ACCOUNTS_CREATE_RELATED', (
# <model>, <key field>, <kwargs>, <help_text>
('mailboxes.Mailbox',
'name',
@ -60,6 +54,6 @@ ACCOUNTS_CREATE_RELATED = getattr(settings, 'ACCOUNTS_CREATE_RELATED', (
))
ACCOUNTS_SERVICE_REPORT_TEMPLATE = getattr(settings, 'ACCOUNTS_SERVICE_REPORT_TEMPLATE',
ACCOUNTS_SERVICE_REPORT_TEMPLATE = Setting('ACCOUNTS_SERVICE_REPORT_TEMPLATE',
'admin/accounts/account/service_report.html'
)

View File

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

View File

@ -1,99 +1,97 @@
from django.conf import settings
from django_countries import data
from orchestra.settings import ORCHESTRA_BASE_DOMAIN
from orchestra.settings import ORCHESTRA_BASE_DOMAIN, Setting
BILLS_NUMBER_LENGTH = getattr(settings, 'BILLS_NUMBER_LENGTH',
4
)
BILLS_NUMBER_LENGTH = Setting('BILLS_NUMBER_LENGTH', 4)
BILLS_INVOICE_NUMBER_PREFIX = getattr(settings, 'BILLS_INVOICE_NUMBER_PREFIX',
BILLS_INVOICE_NUMBER_PREFIX = Setting('BILLS_INVOICE_NUMBER_PREFIX',
'I'
)
BILLS_AMENDMENT_INVOICE_NUMBER_PREFIX = getattr(settings, 'BILLS_AMENDMENT_INVOICE_NUMBER_PREFIX',
BILLS_AMENDMENT_INVOICE_NUMBER_PREFIX = Setting('BILLS_AMENDMENT_INVOICE_NUMBER_PREFIX',
'A'
)
BILLS_FEE_NUMBER_PREFIX = getattr(settings, 'BILLS_FEE_NUMBER_PREFIX',
BILLS_FEE_NUMBER_PREFIX = Setting('BILLS_FEE_NUMBER_PREFIX',
'F'
)
BILLS_AMENDMENT_FEE_NUMBER_PREFIX = getattr(settings, 'BILLS_AMENDMENT_FEE_NUMBER_PREFIX',
BILLS_AMENDMENT_FEE_NUMBER_PREFIX = Setting('BILLS_AMENDMENT_FEE_NUMBER_PREFIX',
'B'
)
BILLS_PROFORMA_NUMBER_PREFIX = getattr(settings, 'BILLS_PROFORMA_NUMBER_PREFIX',
BILLS_PROFORMA_NUMBER_PREFIX = Setting('BILLS_PROFORMA_NUMBER_PREFIX',
'P'
)
BILLS_DEFAULT_TEMPLATE = getattr(settings, 'BILLS_DEFAULT_TEMPLATE',
BILLS_DEFAULT_TEMPLATE = Setting('BILLS_DEFAULT_TEMPLATE',
'bills/microspective.html'
)
BILLS_FEE_TEMPLATE = getattr(settings, 'BILLS_FEE_TEMPLATE',
BILLS_FEE_TEMPLATE = Setting('BILLS_FEE_TEMPLATE',
'bills/microspective-fee.html'
)
BILLS_PROFORMA_TEMPLATE = getattr(settings, 'BILLS_PROFORMA_TEMPLATE',
BILLS_PROFORMA_TEMPLATE = Setting('BILLS_PROFORMA_TEMPLATE',
'bills/microspective-proforma.html'
)
BILLS_CURRENCY = getattr(settings, 'BILLS_CURRENCY',
BILLS_CURRENCY = Setting('BILLS_CURRENCY',
'euro'
)
BILLS_SELLER_PHONE = getattr(settings, 'BILLS_SELLER_PHONE',
BILLS_SELLER_PHONE = Setting('BILLS_SELLER_PHONE',
'111-112-11-222'
)
BILLS_SELLER_EMAIL = getattr(settings, 'BILLS_SELLER_EMAIL',
BILLS_SELLER_EMAIL = Setting('BILLS_SELLER_EMAIL',
'sales@{}'.format(ORCHESTRA_BASE_DOMAIN)
)
BILLS_SELLER_WEBSITE = getattr(settings, 'BILLS_SELLER_WEBSITE',
BILLS_SELLER_WEBSITE = Setting('BILLS_SELLER_WEBSITE',
'www.{}'.format(ORCHESTRA_BASE_DOMAIN)
)
BILLS_SELLER_BANK_ACCOUNT = getattr(settings, 'BILLS_SELLER_BANK_ACCOUNT',
BILLS_SELLER_BANK_ACCOUNT = Setting('BILLS_SELLER_BANK_ACCOUNT',
'0000 0000 00 00000000 (Orchestra Bank)'
)
BILLS_EMAIL_NOTIFICATION_TEMPLATE = getattr(settings, 'BILLS_EMAIL_NOTIFICATION_TEMPLATE',
BILLS_EMAIL_NOTIFICATION_TEMPLATE = Setting('BILLS_EMAIL_NOTIFICATION_TEMPLATE',
'bills/bill-notification.email'
)
BILLS_ORDER_MODEL = getattr(settings, 'BILLS_ORDER_MODEL',
BILLS_ORDER_MODEL = Setting('BILLS_ORDER_MODEL',
'orders.Order'
)
BILLS_CONTACT_DEFAULT_CITY = getattr(settings, 'BILLS_CONTACT_DEFAULT_CITY',
BILLS_CONTACT_DEFAULT_CITY = Setting('BILLS_CONTACT_DEFAULT_CITY',
'Barcelona'
)
BILLS_CONTACT_COUNTRIES = getattr(settings, 'BILLS_CONTACT_COUNTRIES',
((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
)
BILLS_CONTACT_DEFAULT_COUNTRY = getattr(settings, 'BILLS_CONTACT_DEFAULT_COUNTRY',
'ES'
BILLS_CONTACT_DEFAULT_COUNTRY = Setting('BILLS_CONTACT_DEFAULT_COUNTRY', 'ES',
choices=BILLS_CONTACT_COUNTRIES
)

View File

@ -1,8 +1,10 @@
from django.conf import settings
from django_countries import data
from orchestra.settings import Setting
CONTACTS_DEFAULT_EMAIL_USAGES = getattr(settings, 'CONTACTS_DEFAULT_EMAIL_USAGES', (
CONTACTS_DEFAULT_EMAIL_USAGES = Setting('CONTACTS_DEFAULT_EMAIL_USAGES', (
'SUPPORT',
'ADMIN',
'BILLING',
@ -12,16 +14,13 @@ CONTACTS_DEFAULT_EMAIL_USAGES = getattr(settings, 'CONTACTS_DEFAULT_EMAIL_USAGES
))
CONTACTS_DEFAULT_CITY = getattr(settings, 'CONTACTS_DEFAULT_CITY',
CONTACTS_DEFAULT_CITY = Setting('CONTACTS_DEFAULT_CITY',
'Barcelona'
)
CONTACTS_COUNTRIES = getattr(settings, 'CONTACTS_COUNTRIES', tuple(
((k,v) for k,v in data.COUNTRIES.items())
))
CONTACTS_COUNTRIES = Setting('CONTACTS_COUNTRIES', tuple((k,v) for k,v in data.COUNTRIES.items()),
editable=False)
CONTACTS_DEFAULT_COUNTRY = getattr(settings, 'CONTACTS_DEFAULT_COUNTRY',
'ES'
)
CONTACTS_DEFAULT_COUNTRY = Setting('CONTACTS_DEFAULT_COUNTRY', 'ES', choices=CONTACTS_COUNTRIES)

View File

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

View File

@ -1,109 +1,106 @@
from django.conf import settings
from orchestra.settings import ORCHESTRA_BASE_DOMAIN
from orchestra.settings import ORCHESTRA_BASE_DOMAIN, Setting
DOMAINS_DEFAULT_NAME_SERVER = getattr(settings, 'DOMAINS_DEFAULT_NAME_SERVER',
DOMAINS_DEFAULT_NAME_SERVER = Setting('DOMAINS_DEFAULT_NAME_SERVER',
'ns.{}'.format(ORCHESTRA_BASE_DOMAIN)
)
DOMAINS_DEFAULT_HOSTMASTER = getattr(settings, 'DOMAINS_DEFAULT_HOSTMASTER',
DOMAINS_DEFAULT_HOSTMASTER = Setting('DOMAINS_DEFAULT_HOSTMASTER',
'hostmaster@{}'.format(ORCHESTRA_BASE_DOMAIN)
)
DOMAINS_DEFAULT_TTL = getattr(settings, 'DOMAINS_DEFAULT_TTL',
DOMAINS_DEFAULT_TTL = Setting('DOMAINS_DEFAULT_TTL',
'1h'
)
DOMAINS_DEFAULT_REFRESH = getattr(settings, 'DOMAINS_DEFAULT_REFRESH',
DOMAINS_DEFAULT_REFRESH = Setting('DOMAINS_DEFAULT_REFRESH',
'1d'
)
DOMAINS_DEFAULT_RETRY = getattr(settings, 'DOMAINS_DEFAULT_RETRY',
DOMAINS_DEFAULT_RETRY = Setting('DOMAINS_DEFAULT_RETRY',
'2h'
)
DOMAINS_DEFAULT_EXPIRATION = getattr(settings, 'DOMAINS_DEFAULT_EXPIRATION',
DOMAINS_DEFAULT_EXPIRATION = Setting('DOMAINS_DEFAULT_EXPIRATION',
'4w'
)
DOMAINS_DEFAULT_MIN_CACHING_TIME = getattr(settings, 'DOMAINS_DEFAULT_MIN_CACHING_TIME',
DOMAINS_DEFAULT_MIN_CACHING_TIME = Setting('DOMAINS_DEFAULT_MIN_CACHING_TIME',
'1h'
)
DOMAINS_ZONE_PATH = getattr(settings, 'DOMAINS_ZONE_PATH',
DOMAINS_ZONE_PATH = Setting('DOMAINS_ZONE_PATH',
'/etc/bind/master/%(name)s'
)
DOMAINS_MASTERS_PATH = getattr(settings, 'DOMAINS_MASTERS_PATH',
DOMAINS_MASTERS_PATH = Setting('DOMAINS_MASTERS_PATH',
'/etc/bind/named.conf.local'
)
DOMAINS_SLAVES_PATH = getattr(settings, 'DOMAINS_SLAVES_PATH',
DOMAINS_SLAVES_PATH = Setting('DOMAINS_SLAVES_PATH',
'/etc/bind/named.conf.local'
)
DOMAINS_CHECKZONE_BIN_PATH = getattr(settings, 'DOMAINS_CHECKZONE_BIN_PATH',
DOMAINS_CHECKZONE_BIN_PATH = Setting('DOMAINS_CHECKZONE_BIN_PATH',
'/usr/sbin/named-checkzone -i local -k fail -n fail'
)
# Used for creating temporary zone files used for validation
DOMAINS_ZONE_VALIDATION_TMP_DIR = getattr(settings, 'DOMAINS_ZONE_VALIDATION_TMP_DIR',
'/dev/shm'
DOMAINS_ZONE_VALIDATION_TMP_DIR = Setting('DOMAINS_ZONE_VALIDATION_TMP_DIR', '/dev/shm',
help_text="Used for creating temporary zone files used for validation."
)
DOMAINS_DEFAULT_A = getattr(settings, 'DOMAINS_DEFAULT_A',
DOMAINS_DEFAULT_A = Setting('DOMAINS_DEFAULT_A',
'10.0.3.13'
)
DOMAINS_DEFAULT_AAAA = getattr(settings, 'DOMAINS_DEFAULT_AAAA',
DOMAINS_DEFAULT_AAAA = Setting('DOMAINS_DEFAULT_AAAA',
''
)
DOMAINS_DEFAULT_MX = getattr(settings, 'DOMAINS_DEFAULT_MX', (
DOMAINS_DEFAULT_MX = Setting('DOMAINS_DEFAULT_MX', (
'10 mail.{}.'.format(ORCHESTRA_BASE_DOMAIN),
'10 mail2.{}.'.format(ORCHESTRA_BASE_DOMAIN),
))
DOMAINS_DEFAULT_NS = getattr(settings, 'DOMAINS_DEFAULT_NS', (
DOMAINS_DEFAULT_NS = Setting('DOMAINS_DEFAULT_NS', (
'ns1.{}.'.format(ORCHESTRA_BASE_DOMAIN),
'ns2.{}.'.format(ORCHESTRA_BASE_DOMAIN),
))
DOMAINS_FORBIDDEN = getattr(settings, 'DOMAINS_FORBIDDEN',
# This setting prevents users from providing random domain names, i.e. google.com
# You can generate a 5K forbidden domains list from Alexa's top 1M
# wget http://s3.amazonaws.com/alexa-static/top-1m.csv.zip -O /tmp/top-1m.csv.zip
# unzip -p /tmp/top-1m.csv.zip | head -n 5000 | sed "s/^.*,//" > forbidden_domains.list
# '%(site_dir)s/forbidden_domains.list')
''
DOMAINS_FORBIDDEN = Setting('DOMAINS_FORBIDDEN', '',
help_text=(
"This setting prevents users from providing random domain names, i.e. google.com"
"You can generate a 5K forbidden domains list from Alexa's top 1M"
"wget http://s3.amazonaws.com/alexa-static/top-1m.csv.zip -O /tmp/top-1m.csv.zip"
"unzip -p /tmp/top-1m.csv.zip | head -n 5000 | sed 's/^.*,//' > forbidden_domains.list"
"'%(site_dir)s/forbidden_domains.list')"
)
)
DOMAINS_MASTERS = getattr(settings, 'DOMAINS_MASTERS',
# Additional master server ip addresses other than autodiscovered by router.get_servers()
()
DOMAINS_MASTERS = Setting('DOMAINS_MASTERS', (),
help_text="Additional master server ip addresses other than autodiscovered by router.get_servers()."
)
DOMAINS_SLAVES = getattr(settings, 'DOMAINS_SLAVES',
# Additional slave server ip addresses other than autodiscovered by router.get_servers()
()
DOMAINS_SLAVES = Setting('DOMAINS_SLAVES', (),
help_text="Additional slave server ip addresses other than autodiscovered by router.get_servers()."
)

View File

@ -1,10 +1,10 @@
from django.conf import settings
from orchestra.settings import Setting
ISSUES_SUPPORT_EMAILS = getattr(settings, 'ISSUES_SUPPORT_EMAILS', [
ISSUES_SUPPORT_EMAILS = Setting('ISSUES_SUPPORT_EMAILS', [
])
ISSUES_NOTIFY_SUPERUSERS = getattr(settings, 'ISSUES_NOTIFY_SUPERUSERS',
ISSUES_NOTIFY_SUPERUSERS = Setting('ISSUES_NOTIFY_SUPERUSERS',
True
)

View File

@ -1,38 +1,36 @@
from django.conf import settings
from orchestra.settings import ORCHESTRA_BASE_DOMAIN
from orchestra.settings import ORCHESTRA_BASE_DOMAIN, Setting
LISTS_DOMAIN_MODEL = getattr(settings, 'LISTS_DOMAIN_MODEL',
LISTS_DOMAIN_MODEL = Setting('LISTS_DOMAIN_MODEL',
'domains.Domain'
)
LISTS_DEFAULT_DOMAIN = getattr(settings, 'LIST_DEFAULT_DOMAIN',
LISTS_DEFAULT_DOMAIN = Setting('LISTS_DEFAULT_DOMAIN',
'lists.{}'.format(ORCHESTRA_BASE_DOMAIN)
)
LISTS_LIST_URL = getattr(settings, 'LISTS_LIST_URL',
LISTS_LIST_URL = Setting('LISTS_LIST_URL',
'https://lists.{}/mailman/listinfo/%(name)s'.format(ORCHESTRA_BASE_DOMAIN)
)
LISTS_MAILMAN_POST_LOG_PATH = getattr(settings, 'LISTS_MAILMAN_POST_LOG_PATH',
LISTS_MAILMAN_POST_LOG_PATH = Setting('LISTS_MAILMAN_POST_LOG_PATH',
'/var/log/mailman/post'
)
LISTS_MAILMAN_ROOT_DIR = getattr(settings, 'LISTS_MAILMAN_ROOT_DIR',
LISTS_MAILMAN_ROOT_DIR = Setting('LISTS_MAILMAN_ROOT_DIR',
'/var/lib/mailman'
)
LISTS_VIRTUAL_ALIAS_PATH = getattr(settings, 'LISTS_VIRTUAL_ALIAS_PATH',
LISTS_VIRTUAL_ALIAS_PATH = Setting('LISTS_VIRTUAL_ALIAS_PATH',
'/etc/postfix/mailman_virtual_aliases'
)
LISTS_VIRTUAL_ALIAS_DOMAINS_PATH = getattr(settings, 'LISTS_VIRTUAL_ALIAS_DOMAINS_PATH',
LISTS_VIRTUAL_ALIAS_DOMAINS_PATH = Setting('LISTS_VIRTUAL_ALIAS_DOMAINS_PATH',
'/etc/postfix/mailman_virtual_domains'
)

View File

@ -58,7 +58,7 @@ class UNIXUserMaildirBackend(ServiceController):
def delete(self, mailbox):
context = self.get_context(mailbox)
self.append('mv %(home)s %(home)s.deleted || exit_code=1' % context)
self.append('mv %(home)s %(home)s.deleted || exit_code=$?' % context)
self.append(textwrap.dedent("""
{ sleep 2 && killall -u %(user)s -s KILL; } &
killall -u %(user)s || true
@ -133,7 +133,7 @@ class DovecotPostfixPasswdVirtualUserBackend(ServiceController):
UPDATED_VIRTUAL_MAILBOX_MAPS=1""") % context
)
if context['deleted_home']:
self.append("mv %(home)s %(deleted_home)s || exit_code=1" % context)
self.append("mv %(home)s %(deleted_home)s || exit_code=$?" % context)
else:
self.append("rm -fr %(home)s" % context)

View File

@ -1,63 +1,62 @@
import os
import textwrap
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from orchestra.settings import ORCHESTRA_BASE_DOMAIN
from orchestra.settings import ORCHESTRA_BASE_DOMAIN, Setting
MAILBOXES_DOMAIN_MODEL = getattr(settings, 'MAILBOXES_DOMAIN_MODEL',
MAILBOXES_DOMAIN_MODEL = Setting('MAILBOXES_DOMAIN_MODEL',
'domains.Domain'
)
MAILBOXES_HOME = getattr(settings, 'MAILBOXES_HOME',
MAILBOXES_HOME = Setting('MAILBOXES_HOME',
'/home/%(name)s/'
)
MAILBOXES_SIEVE_PATH = getattr(settings, 'MAILBOXES_SIEVE_PATH',
MAILBOXES_SIEVE_PATH = Setting('MAILBOXES_SIEVE_PATH',
os.path.join(MAILBOXES_HOME, 'Maildir/sieve/orchestra.sieve')
)
MAILBOXES_SIEVETEST_PATH = getattr(settings, 'MAILBOXES_SIEVETEST_PATH',
MAILBOXES_SIEVETEST_PATH = Setting('MAILBOXES_SIEVETEST_PATH',
'/dev/shm'
)
MAILBOXES_SIEVETEST_BIN_PATH = getattr(settings, 'MAILBOXES_SIEVETEST_BIN_PATH',
MAILBOXES_SIEVETEST_BIN_PATH = Setting('MAILBOXES_SIEVETEST_BIN_PATH',
'%(orchestra_root)s/bin/sieve-test'
)
MAILBOXES_VIRTUAL_MAILBOX_MAPS_PATH = getattr(settings, 'MAILBOXES_VIRTUAL_MAILBOX_MAPS_PATH',
MAILBOXES_VIRTUAL_MAILBOX_MAPS_PATH = Setting('MAILBOXES_VIRTUAL_MAILBOX_MAPS_PATH',
'/etc/postfix/virtual_mailboxes'
)
MAILBOXES_VIRTUAL_ALIAS_MAPS_PATH = getattr(settings, 'MAILBOXES_VIRTUAL_ALIAS_MAPS_PATH',
MAILBOXES_VIRTUAL_ALIAS_MAPS_PATH = Setting('MAILBOXES_VIRTUAL_ALIAS_MAPS_PATH',
'/etc/postfix/virtual_aliases'
)
MAILBOXES_VIRTUAL_ALIAS_DOMAINS_PATH = getattr(settings, 'MAILBOXES_VIRTUAL_ALIAS_DOMAINS_PATH',
MAILBOXES_VIRTUAL_ALIAS_DOMAINS_PATH = Setting('MAILBOXES_VIRTUAL_ALIAS_DOMAINS_PATH',
'/etc/postfix/virtual_domains'
)
MAILBOXES_LOCAL_DOMAIN = getattr(settings, 'MAILBOXES_LOCAL_DOMAIN',
MAILBOXES_LOCAL_DOMAIN = Setting('MAILBOXES_LOCAL_DOMAIN',
ORCHESTRA_BASE_DOMAIN
)
MAILBOXES_PASSWD_PATH = getattr(settings, 'MAILBOXES_PASSWD_PATH',
MAILBOXES_PASSWD_PATH = Setting('MAILBOXES_PASSWD_PATH',
'/etc/dovecot/passwd'
)
MAILBOXES_MAILBOX_FILTERINGS = getattr(settings, 'MAILBOXES_MAILBOX_FILTERINGS', {
MAILBOXES_MAILBOX_FILTERINGS = Setting('MAILBOXES_MAILBOX_FILTERINGS', {
# value: (verbose_name, filter)
'DISABLE': (_("Disable"), ''),
'REJECT': (_("Reject spam"), textwrap.dedent("""
@ -76,26 +75,26 @@ MAILBOXES_MAILBOX_FILTERINGS = getattr(settings, 'MAILBOXES_MAILBOX_FILTERINGS',
})
MAILBOXES_MAILBOX_DEFAULT_FILTERING = getattr(settings, 'MAILBOXES_MAILBOX_DEFAULT_FILTERING',
'REDIRECT'
MAILBOXES_MAILBOX_DEFAULT_FILTERING = Setting('MAILBOXES_MAILBOX_DEFAULT_FILTERING', 'REDIRECT',
choices=tuple((k, v[0]) for k,v in MAILBOXES_MAILBOX_FILTERINGS.items())
)
MAILBOXES_MAILDIRSIZE_PATH = getattr(settings, 'MAILBOXES_MAILDIRSIZE_PATH',
MAILBOXES_MAILDIRSIZE_PATH = Setting('MAILBOXES_MAILDIRSIZE_PATH',
'%(home)s/Maildir/maildirsize'
)
MAILBOXES_LOCAL_ADDRESS_DOMAIN = getattr(settings, 'MAILBOXES_LOCAL_ADDRESS_DOMAIN',
MAILBOXES_LOCAL_ADDRESS_DOMAIN = Setting('MAILBOXES_LOCAL_ADDRESS_DOMAIN',
ORCHESTRA_BASE_DOMAIN
)
MAILBOXES_MAIL_LOG_PATH = getattr(settings, 'MAILBOXES_MAIL_LOG_PATH',
MAILBOXES_MAIL_LOG_PATH = Setting('MAILBOXES_MAIL_LOG_PATH',
'/var/log/mail.log'
)
MAILBOXES_MOVE_ON_DELETE_PATH = getattr(settings, 'MAILBOXES_MOVE_ON_DELETE_PATH',
MAILBOXES_MOVE_ON_DELETE_PATH = Setting('MAILBOXES_MOVE_ON_DELETE_PATH',
''
)

View File

@ -1,6 +1,6 @@
from django.conf import settings
from orchestra.settings import Setting
MISCELLANEOUS_IDENTIFIER_VALIDATORS = getattr(settings, 'MISCELLANEOUS_IDENTIFIER_VALIDATORS', {
MISCELLANEOUS_IDENTIFIER_VALIDATORS = Setting('MISCELLANEOUS_IDENTIFIER_VALIDATORS', {
# <miscservice__name>: <validator_function>
})

View File

@ -1,36 +1,37 @@
from datetime import timedelta
from os import path
from django.conf import settings
from orchestra.settings import Setting
ORCHESTRATION_OS_CHOICES = getattr(settings, 'ORCHESTRATION_OS_CHOICES', (
ORCHESTRATION_OS_CHOICES = Setting('ORCHESTRATION_OS_CHOICES', (
('LINUX', "Linux"),
))
ORCHESTRATION_DEFAULT_OS = getattr(settings, 'ORCHESTRATION_DEFAULT_OS', 'LINUX')
ORCHESTRATION_DEFAULT_OS = Setting('ORCHESTRATION_DEFAULT_OS', 'LINUX',
choices=ORCHESTRATION_OS_CHOICES)
ORCHESTRATION_SSH_KEY_PATH = getattr(settings, 'ORCHESTRATION_SSH_KEY_PATH',
ORCHESTRATION_SSH_KEY_PATH = Setting('ORCHESTRATION_SSH_KEY_PATH',
path.join(path.expanduser('~'), '.ssh/id_rsa'))
ORCHESTRATION_ROUTER = getattr(settings, 'ORCHESTRATION_ROUTER',
ORCHESTRATION_ROUTER = Setting('ORCHESTRATION_ROUTER',
'orchestra.contrib.orchestration.models.Route'
)
ORCHESTRATION_TEMP_SCRIPT_PATH = getattr(settings, 'ORCHESTRATION_TEMP_SCRIPT_PATH',
ORCHESTRATION_TEMP_SCRIPT_PATH = Setting('ORCHESTRATION_TEMP_SCRIPT_PATH',
'/dev/shm'
)
ORCHESTRATION_DISABLE_EXECUTION = getattr(settings, 'ORCHESTRATION_DISABLE_EXECUTION',
ORCHESTRATION_DISABLE_EXECUTION = Setting('ORCHESTRATION_DISABLE_EXECUTION',
False
)
ORCHESTRATION_BACKEND_CLEANUP_DELTA = getattr(settings, 'ORCHESTRATION_BACKEND_CLEANUP_DELTA',
ORCHESTRATION_BACKEND_CLEANUP_DELTA = Setting('ORCHESTRATION_BACKEND_CLEANUP_DELTA',
timedelta(days=15)
)

View File

@ -1,20 +1,20 @@
from django.conf import settings
from orchestra.settings import Setting
# Pluggable backend for bill generation.
ORDERS_BILLING_BACKEND = getattr(settings, 'ORDERS_BILLING_BACKEND',
ORDERS_BILLING_BACKEND = Setting('ORDERS_BILLING_BACKEND',
'orchestra.contrib.orders.billing.BillsBackend'
)
# Pluggable service class
ORDERS_SERVICE_MODEL = getattr(settings, 'ORDERS_SERVICE_MODEL',
ORDERS_SERVICE_MODEL = Setting('ORDERS_SERVICE_MODEL',
'services.Service'
)
# Prevent inspecting these apps for service accounting
ORDERS_EXCLUDED_APPS = getattr(settings, 'ORDERS_EXCLUDED_APPS', (
ORDERS_EXCLUDED_APPS = Setting('ORDERS_EXCLUDED_APPS', (
'orders',
'admin',
'contenttypes',
@ -29,6 +29,6 @@ ORDERS_EXCLUDED_APPS = getattr(settings, 'ORDERS_EXCLUDED_APPS', (
# Only account for significative changes
# metric_storage new value: lastvalue*(1+threshold) > currentvalue or lastvalue*threshold < currentvalue
ORDERS_METRIC_ERROR = getattr(settings, 'ORDERS_METRIC_ERROR',
ORDERS_METRIC_ERROR = Setting('ORDERS_METRIC_ERROR',
0.01
)

View File

@ -1,28 +1,34 @@
from django.conf import settings
from orchestra.settings import Setting
from .. import payments
PAYMENT_CURRENCY = getattr(settings, 'PAYMENT_CURRENCY',
PAYMENT_CURRENCY = Setting('PAYMENT_CURRENCY',
'Eur'
)
PAYMENTS_DD_CREDITOR_NAME = getattr(settings, 'PAYMENTS_DD_CREDITOR_NAME',
PAYMENTS_DD_CREDITOR_NAME = Setting('PAYMENTS_DD_CREDITOR_NAME',
'Orchestra')
PAYMENTS_DD_CREDITOR_IBAN = getattr(settings, 'PAYMENTS_DD_CREDITOR_IBAN',
PAYMENTS_DD_CREDITOR_IBAN = Setting('PAYMENTS_DD_CREDITOR_IBAN',
'IE98BOFI90393912121212')
PAYMENTS_DD_CREDITOR_BIC = getattr(settings, 'PAYMENTS_DD_CREDITOR_BIC',
PAYMENTS_DD_CREDITOR_BIC = Setting('PAYMENTS_DD_CREDITOR_BIC',
'BOFIIE2D')
PAYMENTS_DD_CREDITOR_AT02_ID = getattr(settings, 'PAYMENTS_DD_CREDITOR_AT02_ID',
PAYMENTS_DD_CREDITOR_AT02_ID = Setting('PAYMENTS_DD_CREDITOR_AT02_ID',
'InvalidAT02ID')
PAYMENTS_ENABLED_METHODS = getattr(settings, 'PAYMENTS_ENABLED_METHODS', [
'orchestra.contrib.payments.methods.sepadirectdebit.SEPADirectDebit',
'orchestra.contrib.payments.methods.creditcard.CreditCard',
])
PAYMENTS_ENABLED_METHODS = Setting('PAYMENTS_ENABLED_METHODS', (
'orchestra.contrib.payments.methods.sepadirectdebit.SEPADirectDebit',
'orchestra.contrib.payments.methods.creditcard.CreditCard',
),
# lazy loading
choices=lambda : ((m.get_class_path(), m.get_class_path()) for m in payments.methods.PaymentMethod.get_plugins()),
multiple=True,
)

View File

@ -6,7 +6,7 @@ from orchestra.forms.widgets import ShowTextWidget, ReadOnlyWidget
class ResourceForm(forms.ModelForm):
verbose_name = forms.CharField(label=_("Name"), required=False,
widget=ShowTextWidget(bold=True))
widget=ShowTextWidget(tag='<b>'))
allocated = forms.DecimalField(label=_("Allocated"))
unit = forms.CharField(label=_("Unit"), widget=ShowTextWidget(), required=False)

View File

@ -124,7 +124,7 @@ class Resource(models.Model):
self.sync_periodic_task()
# This only work on tests (multiprocessing used on real deployments)
apps.get_app_config('resources').reload_relations()
run('sleep 2 && touch %s/wsgi.py' % get_project_dir(), async=True)
run('{ sleep 2 && touch %s/wsgi.py; } &' % get_project_dir(), async=True)
def delete(self, *args, **kwargs):
super(Resource, self).delete(*args, **kwargs)

View File

@ -1,85 +1,89 @@
from django.conf import settings
from orchestra.settings import ORCHESTRA_BASE_DOMAIN, Setting
from orchestra.settings import ORCHESTRA_BASE_DOMAIN
from .. import saas
SAAS_ENABLED_SERVICES = getattr(settings, 'SAAS_ENABLED_SERVICES', (
'orchestra.contrib.saas.services.moodle.MoodleService',
'orchestra.contrib.saas.services.bscw.BSCWService',
'orchestra.contrib.saas.services.gitlab.GitLabService',
'orchestra.contrib.saas.services.phplist.PHPListService',
'orchestra.contrib.saas.services.wordpress.WordPressService',
'orchestra.contrib.saas.services.dokuwiki.DokuWikiService',
'orchestra.contrib.saas.services.drupal.DrupalService',
'orchestra.contrib.saas.services.seafile.SeaFileService',
))
SAAS_ENABLED_SERVICES = Setting('SAAS_ENABLED_SERVICES', (
'orchestra.contrib.saas.services.moodle.MoodleService',
'orchestra.contrib.saas.services.bscw.BSCWService',
'orchestra.contrib.saas.services.gitlab.GitLabService',
'orchestra.contrib.saas.services.phplist.PHPListService',
'orchestra.contrib.saas.services.wordpress.WordPressService',
'orchestra.contrib.saas.services.dokuwiki.DokuWikiService',
'orchestra.contrib.saas.services.drupal.DrupalService',
'orchestra.contrib.saas.services.seafile.SeaFileService',
),
# lazy loading
choices=lambda : ((s.get_class_path(), s.get_class_path()) for s in saas.services.SoftwareService.get_plugins()),
multiple=True,
)
SAAS_WORDPRESS_ADMIN_PASSWORD = getattr(settings, 'SAAS_WORDPRESSMU_ADMIN_PASSWORD',
SAAS_WORDPRESS_ADMIN_PASSWORD = Setting('SAAS_WORDPRESSMU_ADMIN_PASSWORD',
'secret'
)
SAAS_WORDPRESS_BASE_URL = getattr(settings, 'SAAS_WORDPRESS_BASE_URL',
SAAS_WORDPRESS_BASE_URL = Setting('SAAS_WORDPRESS_BASE_URL',
'http://blogs.{}/'.format(ORCHESTRA_BASE_DOMAIN)
)
SAAS_DOKUWIKI_TEMPLATE_PATH = getattr(settings, 'SAAS_DOKUWIKI_TEMPLATE_PATH',
SAAS_DOKUWIKI_TEMPLATE_PATH = Setting('SAAS_DOKUWIKI_TEMPLATE_PATH',
'/home/httpd/htdocs/wikifarm/template.tar.gz'
)
SAAS_DOKUWIKI_FARM_PATH = getattr(settings, 'WEBSITES_DOKUWIKI_FARM_PATH',
SAAS_DOKUWIKI_FARM_PATH = Setting('WEBSITES_DOKUWIKI_FARM_PATH',
'/home/httpd/htdocs/wikifarm/farm'
)
SAAS_DRUPAL_SITES_PATH = getattr(settings, 'WEBSITES_DRUPAL_SITES_PATH',
SAAS_DRUPAL_SITES_PATH = Setting('WEBSITES_DRUPAL_SITES_PATH',
'/home/httpd/htdocs/drupal-mu/sites/%(site_name)s'
)
SAAS_PHPLIST_DB_NAME = getattr(settings, 'SAAS_PHPLIST_DB_NAME',
SAAS_PHPLIST_DB_NAME = Setting('SAAS_PHPLIST_DB_NAME',
'phplist_mu'
)
SAAS_PHPLIST_BASE_DOMAIN = getattr(settings, 'SAAS_PHPLIST_BASE_DOMAIN',
SAAS_PHPLIST_BASE_DOMAIN = Setting('SAAS_PHPLIST_BASE_DOMAIN',
'lists.{}'.format(ORCHESTRA_BASE_DOMAIN)
)
SAAS_SEAFILE_DOMAIN = getattr(settings, 'SAAS_SEAFILE_DOMAIN',
SAAS_SEAFILE_DOMAIN = Setting('SAAS_SEAFILE_DOMAIN',
'seafile.{}'.format(ORCHESTRA_BASE_DOMAIN)
)
SAAS_SEAFILE_DEFAULT_QUOTA = getattr(settings, 'SAAS_SEAFILE_DEFAULT_QUOTA',
SAAS_SEAFILE_DEFAULT_QUOTA = Setting('SAAS_SEAFILE_DEFAULT_QUOTA',
50
)
SAAS_BSCW_DOMAIN = getattr(settings, 'SAAS_BSCW_DOMAIN',
SAAS_BSCW_DOMAIN = Setting('SAAS_BSCW_DOMAIN',
'bscw.{}'.format(ORCHESTRA_BASE_DOMAIN)
)
SAAS_BSCW_DEFAULT_QUOTA = getattr(settings, 'SAAS_BSCW_DEFAULT_QUOTA',
SAAS_BSCW_DEFAULT_QUOTA = Setting('SAAS_BSCW_DEFAULT_QUOTA',
50
)
SAAS_BSCW_BSADMIN_PATH = getattr(settings, 'SAAS_BSCW_BSADMIN_PATH',
SAAS_BSCW_BSADMIN_PATH = Setting('SAAS_BSCW_BSADMIN_PATH',
'/home/httpd/bscw/bin/bsadmin',
)
SAAS_GITLAB_ROOT_PASSWORD = getattr(settings, 'SAAS_GITLAB_ROOT_PASSWORD',
SAAS_GITLAB_ROOT_PASSWORD = Setting('SAAS_GITLAB_ROOT_PASSWORD',
'secret'
)
SAAS_GITLAB_DOMAIN = getattr(settings, 'SAAS_GITLAB_DOMAIN',
SAAS_GITLAB_DOMAIN = Setting('SAAS_GITLAB_DOMAIN',
'gitlab.{}'.format(ORCHESTRA_BASE_DOMAIN)
)

View File

@ -1,39 +1,40 @@
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from orchestra.settings import Setting
SERVICES_SERVICE_TAXES = getattr(settings, 'SERVICES_SERVICE_TAXES', (
SERVICES_SERVICE_TAXES = Setting('SERVICES_SERVICE_TAXES', (
(0, _("Duty free")),
(21, "21%"),
))
SERVICES_SERVICE_DEFAULT_TAX = getattr(settings, 'SERVICES_SERVICE_DEFAULT_TAX',
0
SERVICES_SERVICE_DEFAULT_TAX = Setting('SERVICES_SERVICE_DEFAULT_TAX', 0,
choices=SERVICES_SERVICE_TAXES
)
SERVICES_SERVICE_ANUAL_BILLING_MONTH = getattr(settings, 'SERVICES_SERVICE_ANUAL_BILLING_MONTH',
1
SERVICES_SERVICE_ANUAL_BILLING_MONTH = Setting('SERVICES_SERVICE_ANUAL_BILLING_MONTH', 1,
choices=tuple((n, n) for n in range(1, 13))
)
SERVICES_ORDER_MODEL = getattr(settings, 'SERVICES_ORDER_MODEL',
SERVICES_ORDER_MODEL = Setting('SERVICES_ORDER_MODEL',
'orders.Order'
)
SERVICES_RATE_CLASS = getattr(settings, 'SERVICES_RATE_CLASS',
SERVICES_RATE_CLASS = Setting('SERVICES_RATE_CLASS',
'orchestra.contrib.plans.models.Rate'
)
SERVICES_DEFAULT_IGNORE_PERIOD = getattr(settings, 'SERVICES_DEFAULT_IGNORE_PERIOD',
SERVICES_DEFAULT_IGNORE_PERIOD = Setting('SERVICES_DEFAULT_IGNORE_PERIOD',
'TEN_DAYS'
)
SERVICES_IGNORE_ACCOUNT_TYPE = getattr(settings, 'SERVICES_IGNORE_ACCOUNT_TYPE', (
SERVICES_IGNORE_ACCOUNT_TYPE = Setting('SERVICES_IGNORE_ACCOUNT_TYPE', (
'superuser',
'STAFF',
'FRIEND',

View File

View File

@ -0,0 +1,89 @@
from functools import partial
from django.contrib import admin, messages
from django.db import models
from django.views.generic.edit import FormView
from django.utils.translation import ngettext, ugettext_lazy as _
from orchestra.settings import Setting
from orchestra.utils import sys, paths
from . import parser
from .forms import SettingFormSet
class SettingView(FormView):
template_name = 'admin/settings/change_form.html'
form_class = SettingFormSet
success_url = '.'
def get_context_data(self, **kwargs):
context = super(SettingView, self).get_context_data(**kwargs)
context.update({
'title': _("Change settings"),
'settings_file': parser.get_settings_file(),
})
return context
def get_initial(self):
initial_data = []
prev_app = None
account = 0
for name, setting in Setting.settings.items():
app = name.split('_')[0]
initial = {
'name': setting.name,
'help_text': setting.help_text,
'default': setting.default,
'type': type(setting.default),
'value': setting.value,
'choices': setting.choices,
'app': app,
'editable': setting.editable,
'multiple': setting.multiple,
}
if app == 'ORCHESTRA':
initial_data.insert(account, initial)
account += 1
else:
initial_data.append(initial)
return initial_data
def form_valid(self, form):
settings = Setting.settings
changes = {}
for data in form.cleaned_data:
setting = settings[data['name']]
if not isinstance(data['value'], parser.NotSupported) and setting.editable:
if setting.value != data['value']:
if setting.default == data['value']:
changes[setting.name] = parser.Remove()
else:
changes[setting.name] = parser.serialize(data['value'])
if changes:
# Display confirmation
if not self.request.POST.get('confirmation'):
settings_file = parser.get_settings_file()
new_content = parser.apply(changes)
diff = sys.run("cat <<EOF | diff %s -\n%s\nEOF" % (settings_file, new_content), error_codes=[1, 0]).stdout
context = self.get_context_data(form=form)
context['diff'] = diff
return self.render_to_response(context)
# Save changes
parser.save(changes)
n = len(changes)
messages.success(self.request, ngettext(
_("One change successfully applied, the orchestra is going to be restarted..."),
_("%s changes successfully applied, the orchestra is going to be restarted...") % n,
n)
)
# TODO find aonther way without root and implement reload
# sys.run('echo { sleep 2 && python3 %s/manage.py reload; } &' % paths.get_site_dir(), async=True)
else:
messages.success(self.request, _("No changes have been detected."))
return super(SettingView, self).form_valid(form)
admin.site.register_url(r'^settings/setting/$', SettingView.as_view(), 'settings_edit_settings')

View File

@ -0,0 +1,112 @@
import math
from copy import deepcopy
from functools import partial
from django import forms
from django.core.exceptions import ValidationError
from django.forms.formsets import formset_factory
from django.utils.translation import ugettext_lazy as _
from orchestra.forms import ReadOnlyFormMixin, widgets
from . import parser
from django import forms
from django.utils.safestring import mark_safe
class SettingForm(ReadOnlyFormMixin, forms.Form):
TEXTAREA = partial(forms.CharField,
widget=forms.Textarea(attrs={
'cols': 65,
'rows': 2,
'style': 'font-family:monospace'
}))
CHARFIELD = partial(forms.CharField,
widget=forms.TextInput(attrs={
'size': 65,
'style': 'font-family:monospace'
}))
NON_EDITABLE = partial(forms.CharField, widget=widgets.ShowTextWidget(), required=False)
FORMFIELD_FOR_SETTING_TYPE = {
bool: partial(forms.BooleanField, required=False),
int: forms.IntegerField,
tuple: TEXTAREA,
list: TEXTAREA,
dict: TEXTAREA,
type(_()): CHARFIELD,
str: CHARFIELD,
}
name = forms.CharField(label=_("name"))
default = forms.CharField(label=_("default"))
class Meta:
readonly_fields = ('name', 'default')
def __init__(self, *args, **kwargs):
initial = kwargs.get('initial')
if initial:
self.setting_type = initial['type']
serialized_value = parser.serialize(initial['value'])
serialized_default = parser.serialize(initial['default'])
if not initial['editable'] or isinstance(serialized_value, parser.NotSupported):
field = self.NON_EDITABLE
else:
choices = initial.get('choices')
field = forms.ChoiceField
multiple = initial['multiple']
if multiple:
field = partial(forms.MultipleChoiceField, widget=forms.CheckboxSelectMultiple)
if choices:
# Lazy loading
if callable(choices):
choices = choices()
if not multiple:
choices = tuple((parser.serialize(val), verb) for val, verb in choices)
field = partial(field, choices=choices)
else:
field = self.FORMFIELD_FOR_SETTING_TYPE.get(self.setting_type, self.NON_EDITABLE)
field = deepcopy(field)
value = initial['value']
default = initial['default']
real_field = field
while isinstance(real_field, partial):
real_field = real_field.func
# Do not serialize following form types
if real_field not in (forms.MultipleChoiceField, forms.BooleanField):
value = serialized_value
if real_field is not forms.BooleanField:
default = serialized_default
initial['value'] = value
initial['default'] = default
super(SettingForm, self).__init__(*args, **kwargs)
if initial:
self.changed = bool(value != default)
self.fields['value'] = field(label=_("value"))
if isinstance(self.fields['value'].widget, forms.Textarea):
rows = math.ceil(len(value)/65)
self.fields['value'].widget.attrs['rows'] = rows
self.fields['name'].help_text = initial['help_text']
self.fields['name'].widget.attrs['readonly'] = True
self.app = initial['app']
def clean_value(self):
value = self.cleaned_data['value']
if not value:
return parser.NotSupported()
if not isinstance(value, str):
value = parser.serialize(value)
try:
value = eval(value, parser.get_eval_context())
except Exception as exc:
raise ValidationError(str(exc))
if not isinstance(value, self.setting_type):
if self.setting_type in (tuple, list) and isinstance(value, (tuple, list)):
value = self.setting_type(value)
else:
raise ValidationError("Please provide a %s." % self.setting_type.__name__)
return value
SettingFormSet = formset_factory(SettingForm, extra=0)

View File

@ -0,0 +1,148 @@
import ast
import os
import re
from django.utils.translation import ugettext_lazy as _
from orchestra.utils.paths import get_project_dir
class Remove(object):
""" used to signal a setting remove """
pass
def get_settings_file():
return os.path.join(get_project_dir(), 'settings.py')
def _find_updates(changes, settings_file):
""" find all updates needed for applying changes on settings_file content """
with open(settings_file, 'rb') as handler:
p = ast.parse(handler.read())
updates = []
for elem in p.body:
if updates and updates[-1][-1] is None:
updates[-1][-1] = elem.lineno-1
targets = getattr(elem, 'targets', None)
if targets:
var_name = targets[0].id
if var_name in changes:
updates.append([var_name, elem.lineno, None])
return updates
class LazyUgettextRepr(object):
def __init__(self, value):
self.value = value
def __repr__(self):
return '_("%s")' % self.value
def __len__(self):
return len(repr(self.value))
class NotSupported(object):
def __repr__(self):
return 'Serialization not supported'
def __len__(self):
return 0
def get_eval_context():
return {
'NotSupported': NotSupported,
'_': _,
}
def serialize(obj, init=True):
if isinstance(obj, NotSupported):
return obj
elif isinstance(obj, type(_())):
_obj = LazyUgettextRepr(obj)
elif isinstance(obj, dict):
_obj = {}
for name, value in obj.items():
name = serialize(name, init=False)
value = serialize(value, init=False)
if isinstance(name, NotSupported) or isinstance(value, NotSupported):
return NotSupported()
_obj[name] = value
elif isinstance(obj, (tuple, list)):
_obj = []
for nested in obj:
nested = serialize(nested, init=False)
if isinstance(nested, NotSupported):
return nested
_obj.append(nested)
_obj = type(obj)(_obj)
elif isinstance(obj, (str, bool, int)):
_obj = obj
else:
_obj = NotSupported()
return repr(_obj) if init else _obj
def _format_setting(name, value):
if isinstance(value, Remove):
return ""
value = serialize(eval(value), get_eval_context())
return "{name} = {value}".format(name=name, value=value)
def apply(changes, settings_file=get_settings_file()):
""" returns settings_file content with applied changes """
updates = _find_updates(changes, settings_file)
content = []
inside = False
lineno = None
if updates:
name, lineno, end = updates.pop(0)
# update existing variable declarations
with open(settings_file, 'r') as handler:
for num, line in enumerate(handler.readlines(), 1):
line = line.rstrip()
if num == lineno:
value = changes.pop(name)
line = _format_setting(name, value)
if line:
content.append(line)
inside = True
comments = []
lastend = end
try:
name, lineno, end = updates.pop(0)
except IndexError:
if lastend is None:
break
if not inside:
content.append(line)
else:
# Discard lines since variable will be replaced
# But save comments and blank lines
if re.match(r'^\s*(#.*)*\s*$', line):
comments.append(line)
else:
comments = []
# End of variable declaration
if num == lastend:
content.extend(comments)
inside = False
# insert new variables
for name, value in changes.items():
content.append(_format_setting(name, value))
return '\n'.join(content)
def save(changes, settings_file=get_settings_file(), backup=True):
""" apply changes to project.settings file, saving a backup """
new_settings = apply(changes, settings_file)
tmp_settings_file = settings_file + '.tmp'
with open(tmp_settings_file, 'w') as handle:
handle.write(new_settings)
if backup:
os.rename(settings_file, settings_file + '.backup')
os.rename(tmp_settings_file, settings_file)

View File

@ -0,0 +1,86 @@
{% extends "admin/base_site.html" %}
{% load i18n l10n %}
{% load url from future %}
{% load admin_urls static utils %}
{% block extrastyle %}
{{ block.super }}
<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}" />
<link rel="stylesheet" type="text/css" href="{% static "orchestra/css/hide-inline-id.css" %}" />
{% endblock %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
{% if diff %} &rsaquo; <a href=".">Settings</a> &rsaquo; Confirm changes{% else %} &rsaquo; Settings{% endif %}
</div>
{% endblock %}
{% block content %}
<div>
<form method="post" action="">{% csrf_token %}
{% if diff %}
{% blocktrans %}
<p>The following changes will be performed to <tt>{{ settings_file }}</tt> file.</p>
{% endblocktrans %}
<PRE>{{ diff }}</PRE>
{{ form.management_form }}
<table style="display:none">
{% for form in form %}
{{ form }}
{% endfor %}
</table>
<input type="hidden" name="confirmation" value="True" />
<div class="submit-row"><input type="submit" value="Confirm changes" class="default" name="_diff"></div>
{% else %}
{% blocktrans %}
<p><tt>{{ settings_file }}</tt> file will be automatically updated and Orchestra restarted according to your changes.
{% endblocktrans %}
{% if form.errors %}
<p class="errornote">
{% trans "Please correct the errors below." %}
</p>
{{ form.non_form_errors.as_ul }}
{% endif %}
{{ form.management_form }}
{% regroup form.forms by app as formlist %}
{% for app in formlist %}
<fieldset class="module">
<h2>{{ app.grouper|lower|capfirst }}</h2>
<table id="formset" class="form" style="width:100%">
{% for form in app.list %}
{{ form.non_field_errors }}
{% if forloop.first %}
<thead><tr>
{% for field in form.visible_fields %}
<th style="width:{% if forloop.first %}30{% else %}35{% endif %}%">{{ field.label|capfirst }}</th>
{% endfor %}
</tr></thead>
{% endif %}
<tr class="{% cycle row1,row2 %}">
{% for field in form.visible_fields %}
<td>
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
<div style="font-family:monospace">{{ field }}{% if forloop.last %}{% if form.changed %}<div style="float:right" title="Changed">&#8224;</div>{% endif %}{% endif %}</div>
<p class="help" style="max-width:100px; white-space:nowrap;">{{ field.help_text }}</p>
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
</fieldset>
{% endfor %}
<div class="submit-row"><input type="submit" value="Diff changes" class="default" name="_diff"></div>
{% endif %}
</form>
{% endblock %}

View File

@ -56,12 +56,12 @@ class UNIXUserBackend(ServiceController):
self.append(textwrap.dedent("""\
{ sleep 2 && killall -u %(user)s -s KILL; } &
killall -u %(user)s || true
userdel %(user)s || exit_code=1
groupdel %(group)s || exit_code=1
userdel %(user)s || exit_code=$?
groupdel %(group)s || exit_code=$?
""") % context
)
if context['deleted_home']:
self.append("mv %(base_home)s %(deleted_home)s || exit_code=1" % context)
self.append("mv %(base_home)s %(deleted_home)s || exit_code=$?" % context)
else:
self.append("rm -fr %(base_home)s" % context)

View File

@ -1,9 +1,9 @@
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from orchestra.settings import Setting
SYSTEMUSERS_SHELLS = getattr(settings, 'SYSTEMUSERS_SHELLS', (
SYSTEMUSERS_SHELLS = Setting('SYSTEMUSERS_SHELLS', (
('/dev/null', _("No shell, FTP only")),
('/bin/rssh', _("No shell, SFTP/RSYNC only")),
('/bin/bash', "/bin/bash"),
@ -11,36 +11,36 @@ SYSTEMUSERS_SHELLS = getattr(settings, 'SYSTEMUSERS_SHELLS', (
))
SYSTEMUSERS_DEFAULT_SHELL = getattr(settings, 'SYSTEMUSERS_DEFAULT_SHELL',
'/dev/null'
SYSTEMUSERS_DEFAULT_SHELL = Setting('SYSTEMUSERS_DEFAULT_SHELL', '/dev/null',
choices=SYSTEMUSERS_SHELLS
)
SYSTEMUSERS_DISABLED_SHELLS = getattr(settings, 'SYSTEMUSERS_DISABLED_SHELLS', (
SYSTEMUSERS_DISABLED_SHELLS = Setting('SYSTEMUSERS_DISABLED_SHELLS', (
'/dev/null',
'/bin/rssh',
))
SYSTEMUSERS_HOME = getattr(settings, 'SYSTEMUSERS_HOME',
SYSTEMUSERS_HOME = Setting('SYSTEMUSERS_HOME',
'/home/%(user)s'
)
SYSTEMUSERS_FTP_LOG_PATH = getattr(settings, 'SYSTEMUSERS_FTP_LOG_PATH',
SYSTEMUSERS_FTP_LOG_PATH = Setting('SYSTEMUSERS_FTP_LOG_PATH',
'/var/log/vsftpd.log'
)
SYSTEMUSERS_MAIL_LOG_PATH = getattr(settings, 'SYSTEMUSERS_MAIL_LOG_PATH',
SYSTEMUSERS_MAIL_LOG_PATH = Setting('SYSTEMUSERS_MAIL_LOG_PATH',
'/var/log/exim4/mainlog'
)
SYSTEMUSERS_DEFAULT_GROUP_MEMBERS = getattr(settings, 'SYSTEMUSERS_DEFAULT_GROUP_MEMBERS',
SYSTEMUSERS_DEFAULT_GROUP_MEMBERS = Setting('SYSTEMUSERS_DEFAULT_GROUP_MEMBERS',
('www-data',)
)
SYSTEMUSERS_MOVE_ON_DELETE_PATH = getattr(settings, 'SYSTEMUSERS_MOVE_ON_DELETE_PATH',
SYSTEMUSERS_MOVE_ON_DELETE_PATH = Setting('SYSTEMUSERS_MOVE_ON_DELETE_PATH',
''
)

View File

@ -1,21 +1,17 @@
from django.conf import settings
from orchestra.settings import Setting
VPS_TYPES = getattr(settings, 'VPS_TYPES', (
VPS_TYPES = Setting('VPS_TYPES', (
('openvz', 'OpenVZ container'),
))
VPS_DEFAULT_TYPE = getattr(settings, 'VPS_DEFAULT_TYPE',
'openvz'
)
VPS_DEFAULT_TYPE = Setting('VPS_DEFAULT_TYPE', 'openvz', choices=VPS_TYPES)
VPS_TEMPLATES = getattr(settings, 'VPS_TEMPLATES', (
VPS_TEMPLATES = Setting('VPS_TEMPLATES', (
('debian7', 'Debian 7 - Wheezy'),
))
VPS_DEFAULT_TEMPLATE = getattr(settings, 'VPS_DEFAULT_TEMPLATE',
'debian7'
)
VPS_DEFAULT_TEMPLATE = Setting('VPS_DEFAULT_TEMPLATE', 'debian7', choices=VPS_TEMPLATES)

View File

@ -36,7 +36,7 @@ class WebAppServiceMixin(object):
def delete_webapp_dir(self, context):
if context['deleted_app_path']:
self.append("mv %(app_path)s %(deleted_app_path)s || exit_code=1" % context)
self.append("mv %(app_path)s %(deleted_app_path)s || exit_code=$?" % context)
else:
self.append("rm -fr %(app_path)s" % context)

View File

@ -1,37 +1,37 @@
from django.conf import settings
from orchestra.settings import ORCHESTRA_BASE_DOMAIN, Setting
from orchestra.settings import ORCHESTRA_BASE_DOMAIN
from .. import webapps
WEBAPPS_BASE_DIR = getattr(settings, 'WEBAPPS_BASE_DIR',
WEBAPPS_BASE_DIR = Setting('WEBAPPS_BASE_DIR',
'%(home)s/webapps/%(app_name)s'
)
WEBAPPS_FPM_LISTEN = getattr(settings, 'WEBAPPS_FPM_LISTEN',
WEBAPPS_FPM_LISTEN = Setting('WEBAPPS_FPM_LISTEN',
# '127.0.0.1:9%(app_id)03d
'/opt/php/5.4/socks/%(user)s-%(app_name)s.sock'
)
WEBAPPS_FPM_DEFAULT_MAX_CHILDREN = getattr(settings, 'WEBAPPS_FPM_DEFAULT_MAX_CHILDREN',
WEBAPPS_FPM_DEFAULT_MAX_CHILDREN = Setting('WEBAPPS_FPM_DEFAULT_MAX_CHILDREN',
3
)
WEBAPPS_PHPFPM_POOL_PATH = getattr(settings, 'WEBAPPS_PHPFPM_POOL_PATH',
WEBAPPS_PHPFPM_POOL_PATH = Setting('WEBAPPS_PHPFPM_POOL_PATH',
'/etc/php5/fpm/pool.d/%(user)s-%(app_name)s.conf'
)
WEBAPPS_FCGID_WRAPPER_PATH = getattr(settings, 'WEBAPPS_FCGID_WRAPPER_PATH',
WEBAPPS_FCGID_WRAPPER_PATH = Setting('WEBAPPS_FCGID_WRAPPER_PATH',
# Inside SuExec Document root
# Make sure all account wrappers are in the same DIR
'/home/httpd/fcgi-bin.d/%(user)s/%(app_name)s-wrapper'
)
WEBAPPS_FCGID_CMD_OPTIONS_PATH = getattr(settings, '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'
)
@ -39,33 +39,37 @@ WEBAPPS_FCGID_CMD_OPTIONS_PATH = getattr(settings, 'WEBAPPS_FCGID_CMD_OPTIONS_PA
# Greater or equal to your FcgidMaxRequestsPerProcess
# http://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html#examples
WEBAPPS_PHP_MAX_REQUESTS = getattr(settings, 'WEBAPPS_PHP_MAX_REQUESTS',
WEBAPPS_PHP_MAX_REQUESTS = Setting('WEBAPPS_PHP_MAX_REQUESTS',
400
)
WEBAPPS_PHP_ERROR_LOG_PATH = getattr(settings, 'WEBAPPS_PHP_ERROR_LOG_PATH',
WEBAPPS_PHP_ERROR_LOG_PATH = Setting('WEBAPPS_PHP_ERROR_LOG_PATH',
''
)
WEBAPPS_MERGE_PHP_WEBAPPS = getattr(settings, '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
# to better control num processes per account and save memory
False)
WEBAPPS_TYPES = getattr(settings, 'WEBAPPS_TYPES', (
'orchestra.contrib.webapps.types.php.PHPApp',
'orchestra.contrib.webapps.types.misc.StaticApp',
'orchestra.contrib.webapps.types.misc.WebalizerApp',
'orchestra.contrib.webapps.types.misc.SymbolicLinkApp',
'orchestra.contrib.webapps.types.wordpress.WordPressApp',
'orchestra.contrib.webapps.types.python.PythonApp',
))
WEBAPPS_TYPES = Setting('WEBAPPS_TYPES', (
'orchestra.contrib.webapps.types.php.PHPApp',
'orchestra.contrib.webapps.types.misc.StaticApp',
'orchestra.contrib.webapps.types.misc.WebalizerApp',
'orchestra.contrib.webapps.types.misc.SymbolicLinkApp',
'orchestra.contrib.webapps.types.wordpress.WordPressApp',
'orchestra.contrib.webapps.types.python.PythonApp',
),
# lazy loading
choices=lambda : ((t.get_class_path(), t.get_class_path()) for t in webapps.types.AppType.get_plugins()),
multiple=True,
)
WEBAPPS_PHP_VERSIONS = getattr(settings, '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-cgi', 'PHP 5.4 FCGID'),
@ -75,69 +79,68 @@ WEBAPPS_PHP_VERSIONS = getattr(settings, 'WEBAPPS_PHP_VERSIONS', (
))
WEBAPPS_DEFAULT_PHP_VERSION = getattr(settings, 'WEBAPPS_DEFAULT_PHP_VERSION',
'5.4-cgi'
WEBAPPS_DEFAULT_PHP_VERSION = Setting('WEBAPPS_DEFAULT_PHP_VERSION', '5.4-cgi',
choices=WEBAPPS_PHP_VERSIONS
)
WEBAPPS_PHP_CGI_BINARY_PATH = getattr(settings, 'WEBAPPS_PHP_CGI_BINARY_PATH',
WEBAPPS_PHP_CGI_BINARY_PATH = Setting('WEBAPPS_PHP_CGI_BINARY_PATH',
# Path of the cgi binary used by fcgid
'/usr/bin/php%(php_version_number)s-cgi'
)
WEBAPPS_PHP_CGI_RC_DIR = getattr(settings, 'WEBAPPS_PHP_CGI_RC_DIR',
WEBAPPS_PHP_CGI_RC_DIR = Setting('WEBAPPS_PHP_CGI_RC_DIR',
# Path to php.ini
'/etc/php%(php_version_number)s/cgi/'
)
WEBAPPS_PHP_CGI_INI_SCAN_DIR = getattr(settings, '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'
)
WEBAPPS_PYTHON_VERSIONS = getattr(settings, 'WEBAPPS_PYTHON_VERSIONS', (
WEBAPPS_PYTHON_VERSIONS = Setting('WEBAPPS_PYTHON_VERSIONS', (
('3.4-uwsgi', 'Python 3.4 uWSGI'),
('2.7-uwsgi', 'Python 2.7 uWSGI'),
))
WEBAPPS_DEFAULT_PYTHON_VERSION = getattr(settings, 'WEBAPPS_DEFAULT_PYTHON_VERSION',
'3.4-uwsgi'
WEBAPPS_DEFAULT_PYTHON_VERSION = Setting('WEBAPPS_DEFAULT_PYTHON_VERSION', '3.4-uwsgi',
choices=WEBAPPS_PYTHON_VERSIONS
)
WEBAPPS_UWSGI_SOCKET = getattr(settings, 'WEBAPPS_UWSGI_SOCKET',
WEBAPPS_UWSGI_SOCKET = Setting('WEBAPPS_UWSGI_SOCKET',
'/var/run/uwsgi/app/%(app_name)s/socket'
)
WEBAPPS_UWSGI_BASE_DIR = getattr(settings, 'WEBAPPS_UWSGI_BASE_DIR',
WEBAPPS_UWSGI_BASE_DIR = Setting('WEBAPPS_UWSGI_BASE_DIR',
'/etc/uwsgi/'
)
WEBAPPS_PYTHON_MAX_REQUESTS = getattr(settings, 'WEBAPPS_PYTHON_MAX_REQUESTS',
WEBAPPS_PYTHON_MAX_REQUESTS = Setting('WEBAPPS_PYTHON_MAX_REQUESTS',
500
)
WEBAPPS_PYTHON_DEFAULT_MAX_WORKERS = getattr(settings, 'WEBAPPS_PYTHON_DEFAULT_MAX_WORKERS',
WEBAPPS_PYTHON_DEFAULT_MAX_WORKERS = Setting('WEBAPPS_PYTHON_DEFAULT_MAX_WORKERS',
3
)
WEBAPPS_PYTHON_DEFAULT_TIMEOUT = getattr(settings, 'WEBAPPS_PYTHON_DEFAULT_TIMEOUT',
WEBAPPS_PYTHON_DEFAULT_TIMEOUT = Setting('WEBAPPS_PYTHON_DEFAULT_TIMEOUT',
30
)
WEBAPPS_UNDER_CONSTRUCTION_PATH = getattr(settings, 'WEBAPPS_UNDER_CONSTRUCTION_PATH',
# Server-side path where a under construction stock page is
# '/var/www/undercontruction/index.html',
''
WEBAPPS_UNDER_CONSTRUCTION_PATH = Setting('WEBAPPS_UNDER_CONSTRUCTION_PATH', '',
help_text=("Server-side path where a under construction stock page is "
"'/var/www/undercontruction/index.html'")
)
@ -150,7 +153,7 @@ WEBAPPS_UNDER_CONSTRUCTION_PATH = getattr(settings, 'WEBAPPS_UNDER_CONSTRUCTION_
WEBAPPS_PHP_DISABLED_FUNCTIONS = getattr(settings, 'WEBAPPS_PHP_DISABLED_FUNCTION', [
WEBAPPS_PHP_DISABLED_FUNCTIONS = Setting('WEBAPPS_PHP_DISABLED_FUNCTION', [
'exec',
'passthru',
'shell_exec',
@ -174,49 +177,53 @@ WEBAPPS_PHP_DISABLED_FUNCTIONS = getattr(settings, 'WEBAPPS_PHP_DISABLED_FUNCTIO
])
WEBAPPS_ENABLED_OPTIONS = getattr(settings, 'WEBAPPS_ENABLED_OPTIONS', (
'orchestra.contrib.webapps.options.PublicRoot',
'orchestra.contrib.webapps.options.Timeout',
'orchestra.contrib.webapps.options.Processes',
'orchestra.contrib.webapps.options.PHPEnableFunctions',
'orchestra.contrib.webapps.options.PHPAllowURLInclude',
'orchestra.contrib.webapps.options.PHPAllowURLFopen',
'orchestra.contrib.webapps.options.PHPAutoAppendFile',
'orchestra.contrib.webapps.options.PHPAutoPrependFile',
'orchestra.contrib.webapps.options.PHPDateTimeZone',
'orchestra.contrib.webapps.options.PHPDefaultSocketTimeout',
'orchestra.contrib.webapps.options.PHPDisplayErrors',
'orchestra.contrib.webapps.options.PHPExtension',
'orchestra.contrib.webapps.options.PHPMagicQuotesGPC',
'orchestra.contrib.webapps.options.PHPMagicQuotesRuntime',
'orchestra.contrib.webapps.options.PHPMaginQuotesSybase',
'orchestra.contrib.webapps.options.PHPMaxInputTime',
'orchestra.contrib.webapps.options.PHPMaxInputVars',
'orchestra.contrib.webapps.options.PHPMemoryLimit',
'orchestra.contrib.webapps.options.PHPMySQLConnectTimeout',
'orchestra.contrib.webapps.options.PHPOutputBuffering',
'orchestra.contrib.webapps.options.PHPRegisterGlobals',
'orchestra.contrib.webapps.options.PHPPostMaxSize',
'orchestra.contrib.webapps.options.PHPSendmailPath',
'orchestra.contrib.webapps.options.PHPSessionBugCompatWarn',
'orchestra.contrib.webapps.options.PHPSessionAutoStart',
'orchestra.contrib.webapps.options.PHPSafeMode',
'orchestra.contrib.webapps.options.PHPSuhosinPostMaxVars',
'orchestra.contrib.webapps.options.PHPSuhosinGetMaxVars',
'orchestra.contrib.webapps.options.PHPSuhosinRequestMaxVars',
'orchestra.contrib.webapps.options.PHPSuhosinSessionEncrypt',
'orchestra.contrib.webapps.options.PHPSuhosinSimulation',
'orchestra.contrib.webapps.options.PHPSuhosinExecutorIncludeWhitelist',
'orchestra.contrib.webapps.options.PHPUploadMaxFileSize',
'orchestra.contrib.webapps.options.PHPZendExtension',
))
WEBAPPS_ENABLED_OPTIONS = Setting('WEBAPPS_ENABLED_OPTIONS', (
'orchestra.contrib.webapps.options.PublicRoot',
'orchestra.contrib.webapps.options.Timeout',
'orchestra.contrib.webapps.options.Processes',
'orchestra.contrib.webapps.options.PHPEnableFunctions',
'orchestra.contrib.webapps.options.PHPAllowURLInclude',
'orchestra.contrib.webapps.options.PHPAllowURLFopen',
'orchestra.contrib.webapps.options.PHPAutoAppendFile',
'orchestra.contrib.webapps.options.PHPAutoPrependFile',
'orchestra.contrib.webapps.options.PHPDateTimeZone',
'orchestra.contrib.webapps.options.PHPDefaultSocketTimeout',
'orchestra.contrib.webapps.options.PHPDisplayErrors',
'orchestra.contrib.webapps.options.PHPExtension',
'orchestra.contrib.webapps.options.PHPMagicQuotesGPC',
'orchestra.contrib.webapps.options.PHPMagicQuotesRuntime',
'orchestra.contrib.webapps.options.PHPMaginQuotesSybase',
'orchestra.contrib.webapps.options.PHPMaxInputTime',
'orchestra.contrib.webapps.options.PHPMaxInputVars',
'orchestra.contrib.webapps.options.PHPMemoryLimit',
'orchestra.contrib.webapps.options.PHPMySQLConnectTimeout',
'orchestra.contrib.webapps.options.PHPOutputBuffering',
'orchestra.contrib.webapps.options.PHPRegisterGlobals',
'orchestra.contrib.webapps.options.PHPPostMaxSize',
'orchestra.contrib.webapps.options.PHPSendmailPath',
'orchestra.contrib.webapps.options.PHPSessionBugCompatWarn',
'orchestra.contrib.webapps.options.PHPSessionAutoStart',
'orchestra.contrib.webapps.options.PHPSafeMode',
'orchestra.contrib.webapps.options.PHPSuhosinPostMaxVars',
'orchestra.contrib.webapps.options.PHPSuhosinGetMaxVars',
'orchestra.contrib.webapps.options.PHPSuhosinRequestMaxVars',
'orchestra.contrib.webapps.options.PHPSuhosinSessionEncrypt',
'orchestra.contrib.webapps.options.PHPSuhosinSimulation',
'orchestra.contrib.webapps.options.PHPSuhosinExecutorIncludeWhitelist',
'orchestra.contrib.webapps.options.PHPUploadMaxFileSize',
'orchestra.contrib.webapps.options.PHPZendExtension',
),
# lazy loading
choices=lambda : ((o.get_class_path(), o.get_class_path()) for o in webapps.options.AppOption.get_plugins()),
multiple=True,
)
WEBAPPS_DEFAULT_MYSQL_DATABASE_HOST = getattr(settings, 'WEBAPPS_DEFAULT_MYSQL_DATABASE_HOST',
WEBAPPS_DEFAULT_MYSQL_DATABASE_HOST = Setting('WEBAPPS_DEFAULT_MYSQL_DATABASE_HOST',
'mysql.{}'.format(ORCHESTRA_BASE_DOMAIN)
)
WEBAPPS_MOVE_ON_DELETE_PATH = getattr(settings, 'WEBAPPS_MOVE_ON_DELETE_PATH',
WEBAPPS_MOVE_ON_DELETE_PATH = Setting('WEBAPPS_MOVE_ON_DELETE_PATH',
''
)

View File

@ -14,7 +14,7 @@ from ..utils import normurlpath
class Apache2Backend(ServiceController):
"""
Apache 2.4 backend with support for the following directives:
Apache &ge;2.4 backend with support for the following directives:
<tt>static</tt>, <tt>location</tt>, <tt>fpm</tt>, <tt>fcgid</tt>, <tt>uwsgi</tt>, \
<tt>ssl</tt>, <tt>security</tt>, <tt>redirects</tt>, <tt>proxies</tt>, <tt>saas</tt>
"""
@ -24,6 +24,7 @@ class Apache2Backend(ServiceController):
model = 'websites.Website'
related_models = (
('websites.Content', 'website'),
('websites.WebsiteDirective', 'directives'),
('webapps.WebApp', 'website_set'),
)
verbose_name = _("Apache 2")

View File

@ -1,8 +1,11 @@
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from orchestra.settings import Setting
WEBSITES_UNIQUE_NAME_FORMAT = getattr(settings, 'WEBSITES_UNIQUE_NAME_FORMAT',
from .. import websites
WEBSITES_UNIQUE_NAME_FORMAT = Setting('WEBSITES_UNIQUE_NAME_FORMAT',
'%(user)s-%(site_name)s'
)
@ -14,64 +17,68 @@ WEBSITES_UNIQUE_NAME_FORMAT = getattr(settings, 'WEBSITES_UNIQUE_NAME_FORMAT',
#))
WEBSITES_PROTOCOL_CHOICES = getattr(settings, 'WEBSITES_PROTOCOL_CHOICES', (
WEBSITES_PROTOCOL_CHOICES = Setting('WEBSITES_PROTOCOL_CHOICES', (
('http', "HTTP"),
('https', "HTTPS"),
('http/https', _("HTTP and HTTPS")),
('https-only', _("HTTPS only")),
))
WEBSITES_DEFAULT_PROTOCOL = getattr(settings, 'WEBSITES_DEFAULT_PROTOCOL',
'http'
WEBSITES_DEFAULT_PROTOCOL = Setting('WEBSITES_DEFAULT_PROTOCOL', 'http',
choices=WEBSITES_PROTOCOL_CHOICES
)
WEBSITES_DEFAULT_IPS = getattr(settings, 'WEBSITES_DEFAULT_IPS', (
'*'
WEBSITES_DEFAULT_IPS = Setting('WEBSITES_DEFAULT_IPS', (
'*',
))
WEBSITES_DOMAIN_MODEL = getattr(settings, 'WEBSITES_DOMAIN_MODEL',
WEBSITES_DOMAIN_MODEL = Setting('WEBSITES_DOMAIN_MODEL',
'domains.Domain'
)
WEBSITES_ENABLED_DIRECTIVES = getattr(settings, 'WEBSITES_ENABLED_DIRECTIVES', (
'orchestra.contrib.websites.directives.Redirect',
'orchestra.contrib.websites.directives.Proxy',
'orchestra.contrib.websites.directives.ErrorDocument',
'orchestra.contrib.websites.directives.SSLCA',
'orchestra.contrib.websites.directives.SSLCert',
'orchestra.contrib.websites.directives.SSLKey',
'orchestra.contrib.websites.directives.SecRuleRemove',
'orchestra.contrib.websites.directives.SecEngine',
'orchestra.contrib.websites.directives.WordPressSaaS',
'orchestra.contrib.websites.directives.DokuWikiSaaS',
'orchestra.contrib.websites.directives.DrupalSaaS',
))
WEBSITES_ENABLED_DIRECTIVES = Setting('WEBSITES_ENABLED_DIRECTIVES', (
'orchestra.contrib.websites.directives.Redirect',
'orchestra.contrib.websites.directives.Proxy',
'orchestra.contrib.websites.directives.ErrorDocument',
'orchestra.contrib.websites.directives.SSLCA',
'orchestra.contrib.websites.directives.SSLCert',
'orchestra.contrib.websites.directives.SSLKey',
'orchestra.contrib.websites.directives.SecRuleRemove',
'orchestra.contrib.websites.directives.SecEngine',
'orchestra.contrib.websites.directives.WordPressSaaS',
'orchestra.contrib.websites.directives.DokuWikiSaaS',
'orchestra.contrib.websites.directives.DrupalSaaS',
),
# lazy loading
choices=lambda : ((d.get_class_path(), d.get_class_path()) for d in websites.directives.SiteDirective.get_plugins()),
multiple=True,
)
WEBSITES_BASE_APACHE_CONF = getattr(settings, 'WEBSITES_BASE_APACHE_CONF',
WEBSITES_BASE_APACHE_CONF = Setting('WEBSITES_BASE_APACHE_CONF',
'/etc/apache2/'
)
WEBSITES_WEBALIZER_PATH = getattr(settings, 'WEBSITES_WEBALIZER_PATH',
WEBSITES_WEBALIZER_PATH = Setting('WEBSITES_WEBALIZER_PATH',
'/home/httpd/webalizer/'
)
WEBSITES_WEBSITE_WWW_ACCESS_LOG_PATH = getattr(settings, 'WEBSITES_WEBSITE_WWW_ACCESS_LOG_PATH',
WEBSITES_WEBSITE_WWW_ACCESS_LOG_PATH = Setting('WEBSITES_WEBSITE_WWW_ACCESS_LOG_PATH',
'/var/log/apache2/virtual/%(unique_name)s.log'
)
WEBSITES_WEBSITE_WWW_ERROR_LOG_PATH = getattr(settings, 'WEBSITES_WEBSITE_WWW_ERROR_LOG_PATH',
WEBSITES_WEBSITE_WWW_ERROR_LOG_PATH = Setting('WEBSITES_WEBSITE_WWW_ERROR_LOG_PATH',
''
)
WEBSITES_TRAFFIC_IGNORE_HOSTS = getattr(settings, 'WEBSITES_TRAFFIC_IGNORE_HOSTS',
WEBSITES_TRAFFIC_IGNORE_HOSTS = Setting('WEBSITES_TRAFFIC_IGNORE_HOSTS',
('127.0.0.1',)
)
@ -86,26 +93,28 @@ WEBSITES_TRAFFIC_IGNORE_HOSTS = getattr(settings, 'WEBSITES_TRAFFIC_IGNORE_HOSTS
# '')
WEBSITES_SAAS_DIRECTIVES = getattr(settings, 'WEBSITES_SAAS_DIRECTIVES', {
WEBSITES_SAAS_DIRECTIVES = Setting('WEBSITES_SAAS_DIRECTIVES', {
'wordpress-saas': ('fpm', '/opt/php/5.4/socks/pangea.sock', '/home/httpd/wordpress-mu/'),
'drupal-saas': ('fpm', '/opt/php/5.4/socks/pangea.sock','/home/httpd/drupal-mu/'),
'dokuwiki-saas': ('fpm', '/opt/php/5.4/socks/pangea.sock','/home/httpd/moodle-mu/'),
})
WEBSITES_DEFAULT_SSL_CERT = getattr(settings, 'WEBSITES_DEFAULT_SSL_CERT',
WEBSITES_DEFAULT_SSL_CERT = Setting('WEBSITES_DEFAULT_SSL_CERT',
''
)
WEBSITES_DEFAULT_SSL_KEY = getattr(settings, 'WEBSITES_DEFAULT_SSL_KEY',
WEBSITES_DEFAULT_SSL_KEY = Setting('WEBSITES_DEFAULT_SSL_KEY',
''
)
WEBSITES_DEFAULT_SSL_CA = getattr(settings, 'WEBSITES_DEFAULT_SSL_CA',
WEBSITES_DEFAULT_SSL_CA = Setting('WEBSITES_DEFAULT_SSL_CA',
''
)
WEBSITES_VHOST_EXTRA_DIRECTIVES = getattr(settings, 'WEBSITES_VHOST_EXTRA_DIRECTIVES', (
# (<location>, <directive>),
# ('/cgi-bin/', 'ScriptAlias /cgi-bin/ %(home)s/cgi-bin/'),
))
WEBSITES_VHOST_EXTRA_DIRECTIVES = Setting('WEBSITES_VHOST_EXTRA_DIRECTIVES', (),
help_text=(
"(<location>, <directive>), <br>"
"i.e. ('/cgi-bin/', 'ScriptAlias /cgi-bin/ %(home)s/cgi-bin/')"
)
)

View File

@ -1,5 +1,7 @@
from django import forms
from .widgets import SpanWidget
class MultiSelectFormField(forms.MultipleChoiceField):
""" http://djangosnippets.org/snippets/1200/ """
@ -13,3 +15,15 @@ class MultiSelectFormField(forms.MultipleChoiceField):
if not value and self.required:
raise forms.ValidationError(self.error_messages['required'])
return value
class SpanField(forms.Field):
"""
A field which renders a value wrapped in a <span> tag.
Requires use of specific form support. (see ReadonlyForm or ReadonlyModelForm)
"""
def __init__(self, *args, **kwargs):
kwargs['widget'] = kwargs.get('widget', SpanWidget)
super(SpanField, self).__init__(*args, **kwargs)

View File

@ -6,6 +6,9 @@ from orchestra.utils.python import random_ascii
from ..core.validators import validate_password
from .fields import SpanField
from .widgets import SpanWidget
class UserCreationForm(forms.ModelForm):
"""
@ -65,3 +68,24 @@ class UserChangeForm(forms.ModelForm):
# This is done here, rather than on the field, because the
# field does not have access to the initial value
return self.initial["password"]
class ReadOnlyFormMixin(object):
"""
Mixin class for ModelForm or Form that provides support for SpanField on readonly fields
Meta:
readonly_fileds = (ro_field1, ro_field2)
"""
def __init__(self, *args, **kwargs):
super(ReadOnlyFormMixin, self).__init__(*args, **kwargs)
for name in self.Meta.readonly_fields:
field = self.fields[name]
if not isinstance(field, SpanField):
field.widget = SpanWidget()
if hasattr(self, 'instance'):
# Model form
original_value = str(getattr(self.instance, name))
else:
original_value = str(self.initial.get(name))
field.widget.original_value = original_value

View File

@ -5,10 +5,40 @@ from django import forms
from django.utils.safestring import mark_safe
from django.utils.encoding import force_text
from django.contrib.admin.templatetags.admin_static import static
# TODO rename readonlywidget
class SpanWidget(forms.Widget):
"""
Renders a value wrapped in a <span> tag.
Requires use of specific form support. (see ReadonlyForm or ReadonlyModelForm)
"""
def __init__(self, *args, **kwargs):
self.tag = kwargs.pop('tag', '<span>')
super(SpanWidget, self).__init__(*args, **kwargs)
def render(self, name, value, attrs=None):
final_attrs = self.build_attrs(attrs, name=name)
original_value = self.original_value
# Display icon
if original_value in ('True', 'False') or isinstance(original_value, bool):
icon = static('admin/img/icon-%s.gif' % 'yes' if original_value else 'no')
return mark_safe('<img src="%s" alt="%s">' % (icon, str(original_value)))
tag = self.tag[:-1]
endtag = '/'.join((self.tag[0], self.tag[1:]))
return mark_safe('%s%s >%s%s' % (tag, forms.util.flatatt(final_attrs), original_value, endtag))
def value_from_datadict(self, data, files, name):
return self.original_value
def _has_changed(self, initial, data):
return False
class ShowTextWidget(forms.Widget):
def __init__(self, *args, **kwargs):
for kwarg in ['bold', 'warning', 'hidden']:
for kwarg in ['tag', 'warning', 'hidden']:
setattr(self, kwarg, kwargs.pop(kwarg, False))
super(ShowTextWidget, self).__init__(*args, **kwargs)
@ -18,8 +48,9 @@ class ShowTextWidget(forms.Widget):
return ''
if hasattr(self, 'initial'):
value = self.initial
if self.bold:
final_value = '<b>%s</b>' % (value)
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:

View File

@ -19,7 +19,7 @@ def run_tuple(services, action, options, optional=False):
def flatten(nested, depth=0):
if hasattr(nested, '__iter__'):
if isinstance(nested, (list, tuple)):
for sublist in nested:
for element in flatten(sublist, depth+1):
yield element

View File

@ -55,6 +55,10 @@ class Plugin(object):
def get_change_readonly_fileds(cls):
return cls.change_readonly_fileds
@classmethod
def get_class_path(cls):
return '.'.join((cls.__module__, cls.__name__))
def clean_data(self):
""" model clean, uses cls.serizlier by default """
if self.serializer:

View File

@ -1,58 +1,100 @@
from collections import OrderedDict
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
# Domain name used when it will not be possible to infere the domain from a request
# For example in periodic tasks
ORCHESTRA_SITE_URL = getattr(settings, 'ORCHESTRA_SITE_URL',
'http://localhost'
)
class Setting(object):
"""
Keeps track of the defined settings.
Instances of this class are the native value of the setting.
"""
conf_settings = settings
settings = OrderedDict()
def __str__(self):
return self.name
def __repr__(self):
value = str(self.value)
value = ("'%s'" if isinstance(value, str) else '%s') % value
return '<%s: %s>' % (self.name, value)
def __new__(cls, name, default, help_text="", choices=None, editable=True, multiple=False, call_init=False):
if call_init:
return super(Setting, cls).__new__(cls)
cls.settings[name] = cls(name, default, help_text=help_text, choices=choices,
editable=editable, multiple=multiple, call_init=True)
return cls.get_value(name, default)
def __init__(self, *args, **kwargs):
self.name, self.default = args
for name, value in kwargs.items():
setattr(self, name, value)
self.value = self.get_value(self.name, self.default)
self.settings[name] = self
@classmethod
def get_value(cls, name, default):
return getattr(cls.conf_settings, name, default)
ORCHESTRA_SITE_NAME = getattr(settings, 'ORCHESTRA_SITE_NAME',
'orchestra'
# TODO validation, defaults to same type
ORCHESTRA_BASE_DOMAIN = Setting('ORCHESTRA_BASE_DOMAIN',
'orchestra.lan'
)
ORCHESTRA_SITE_VERBOSE_NAME = getattr(settings, 'ORCHESTRA_SITE_VERBOSE_NAME',
ORCHESTRA_SITE_URL = Setting('ORCHESTRA_SITE_URL', 'http://orchestra.%s' % ORCHESTRA_BASE_DOMAIN,
help_text=_("Domain name used when it will not be possible to infere the domain from a request."
"For example in periodic tasks.")
)
ORCHESTRA_SITE_NAME = Setting('ORCHESTRA_SITE_NAME', 'orchestra')
ORCHESTRA_SITE_VERBOSE_NAME = Setting('ORCHESTRA_SITE_VERBOSE_NAME',
_("%s Hosting Management" % ORCHESTRA_SITE_NAME.capitalize())
)
ORCHESTRA_BASE_DOMAIN = getattr(settings, 'ORCHESTRA_BASE_DOMAIN',
'orchestra.lan'
)
# Service management commands
ORCHESTRA_START_SERVICES = getattr(settings, 'ORCHESTRA_START_SERVICES', [
ORCHESTRA_START_SERVICES = Setting('ORCHESTRA_START_SERVICES', (
'postgresql',
'celeryevcam',
'celeryd',
'celerybeat',
('uwsgi', 'nginx'),
])
))
ORCHESTRA_RESTART_SERVICES = getattr(settings, 'ORCHESTRA_RESTART_SERVICES', [
ORCHESTRA_RESTART_SERVICES = Setting('ORCHESTRA_RESTART_SERVICES', (
'celeryd',
'celerybeat',
'uwsgi'
])
))
ORCHESTRA_STOP_SERVICES = getattr(settings, 'ORCHESTRA_STOP_SERVICES', [
ORCHESTRA_STOP_SERVICES = Setting('ORCHESTRA_STOP_SERVICES', (
('uwsgi', 'nginx'),
'celerybeat',
'celeryd',
'celeryevcam',
'postgresql'
])
))
ORCHESTRA_API_ROOT_VIEW = getattr(settings, 'ORCHESTRA_API_ROOT_VIEW',
ORCHESTRA_API_ROOT_VIEW = Setting('ORCHESTRA_API_ROOT_VIEW',
'orchestra.api.root.APIRoot'
)
ORCHESTRA_DEFAULT_SUPPORT_FROM_EMAIL = getattr(settings, 'ORCHESTRA_DEFAULT_SUPPORT_FROM_EMAIL',
ORCHESTRA_DEFAULT_SUPPORT_FROM_EMAIL = Setting('ORCHESTRA_DEFAULT_SUPPORT_FROM_EMAIL',
'support@{}'.format(ORCHESTRA_BASE_DOMAIN)
)
ORCHESTRA_EDIT_SETTINGS = Setting('ORCHESTRA_EDIT_SETTINGS', True)

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -0,0 +1,783 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
inkscape:export-ydpi="90.000000"
inkscape:export-xdpi="90.000000"
inkscape:export-filename="/home/glic3/orchestra/django-orchestra/orchestra/static/orchestra/icons/Preferences.png"
width="48px"
height="48px"
id="svg11300"
sodipodi:version="0.32"
inkscape:version="0.48.3.1 r9886"
sodipodi:docname="Preferences.svg"
version="1.1">
<defs
id="defs3">
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5060"
id="radialGradient6719"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(-2.774389,0,0,1.969706,112.7623,-872.8854)"
cx="605.71429"
cy="486.64789"
fx="605.71429"
fy="486.64789"
r="117.14286" />
<linearGradient
inkscape:collect="always"
id="linearGradient5060">
<stop
style="stop-color:black;stop-opacity:1;"
offset="0"
id="stop5062" />
<stop
style="stop-color:black;stop-opacity:0;"
offset="1"
id="stop5064" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5060"
id="radialGradient6717"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.774389,0,0,1.969706,-1891.633,-872.8854)"
cx="605.71429"
cy="486.64789"
fx="605.71429"
fy="486.64789"
r="117.14286" />
<linearGradient
id="linearGradient5048">
<stop
style="stop-color:black;stop-opacity:0;"
offset="0"
id="stop5050" />
<stop
id="stop5056"
offset="0.5"
style="stop-color:black;stop-opacity:1;" />
<stop
style="stop-color:black;stop-opacity:0;"
offset="1"
id="stop5052" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5048"
id="linearGradient6715"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.774389,0,0,1.969706,-1892.179,-872.8854)"
x1="302.85715"
y1="366.64789"
x2="302.85715"
y2="609.50507" />
<linearGradient
inkscape:collect="always"
id="linearGradient24290">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop24292" />
<stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop24294" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient24276">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop24278" />
<stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop24280" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient24266">
<stop
style="stop-color:#a5a5a5;stop-opacity:1;"
offset="0"
id="stop24268" />
<stop
style="stop-color:#a5a5a5;stop-opacity:0;"
offset="1"
id="stop24270" />
</linearGradient>
<linearGradient
id="linearGradient24230">
<stop
style="stop-color:#677579;stop-opacity:1;"
offset="0"
id="stop24232" />
<stop
style="stop-color:#333333;stop-opacity:1;"
offset="1"
id="stop24234" />
</linearGradient>
<linearGradient
id="linearGradient11594">
<stop
id="stop11596"
offset="0"
style="stop-color:#ffffff;stop-opacity:1;" />
<stop
id="stop11598"
offset="1.0000000"
style="stop-color:#d1d1d1;stop-opacity:1.0000000;" />
</linearGradient>
<linearGradient
gradientTransform="matrix(1.061966,0,0,0.837825,-0.593045,3.987819)"
gradientUnits="userSpaceOnUse"
y2="38.947163"
x2="31.799011"
y1="8.9471626"
x1="20.092352"
id="linearGradient11600"
xlink:href="#linearGradient11594"
inkscape:collect="always" />
<linearGradient
id="linearGradient11602">
<stop
id="stop11604"
offset="0.0000000"
style="stop-color:#f6f6f6;stop-opacity:1.0000000;" />
<stop
id="stop11606"
offset="1.0000000"
style="stop-color:#e0e0e0;stop-opacity:1.0000000;" />
</linearGradient>
<linearGradient
gradientTransform="matrix(1,0,0,0.837825,0.921766,3.987819)"
gradientUnits="userSpaceOnUse"
y2="39.447163"
x2="24.445671"
y1="12.947163"
x1="24.445671"
id="linearGradient11608"
xlink:href="#linearGradient11602"
inkscape:collect="always" />
<linearGradient
id="linearGradient11615">
<stop
id="stop11617"
offset="0.0000000"
style="stop-color:#636363;stop-opacity:1.0000000;" />
<stop
id="stop11619"
offset="1.0000000"
style="stop-color:#000000;stop-opacity:1.0000000;" />
</linearGradient>
<radialGradient
r="4.7500000"
fy="27.749998"
fx="25.000000"
cy="27.749998"
cx="25.000000"
gradientTransform="matrix(3.070491,2.727143e-15,-3.444813e-15,3.878514,-51.46548,-78.83433)"
gradientUnits="userSpaceOnUse"
id="radialGradient12909"
xlink:href="#linearGradient11615"
inkscape:collect="always" />
<linearGradient
y2="27.375000"
x2="21.500000"
y1="30.000000"
x1="21.500000"
gradientTransform="matrix(0.985,0,0,1.022813,2.121141,-2.815681)"
gradientUnits="userSpaceOnUse"
id="linearGradient12924"
xlink:href="#linearGradient11625"
inkscape:collect="always" />
<linearGradient
y2="27.375000"
x2="21.500000"
y1="30.000000"
x1="21.500000"
gradientTransform="matrix(0.985,0,0,1,4.111767,-2.176922)"
gradientUnits="userSpaceOnUse"
id="linearGradient12921"
xlink:href="#linearGradient11625"
inkscape:collect="always" />
<linearGradient
id="linearGradient11625"
inkscape:collect="always">
<stop
id="stop11627"
offset="0"
style="stop-color:#fce94f;stop-opacity:1;" />
<stop
id="stop11629"
offset="1"
style="stop-color:#fce94f;stop-opacity:0;" />
</linearGradient>
<linearGradient
y2="27.375000"
x2="21.500000"
y1="30.000000"
x1="21.500000"
gradientTransform="matrix(1.01625,0,0,1,5.455516,-2.176922)"
gradientUnits="userSpaceOnUse"
id="linearGradient12918"
xlink:href="#linearGradient11625"
inkscape:collect="always" />
<linearGradient
id="linearGradient11520">
<stop
id="stop11522"
offset="0.0000000"
style="stop-color:#ffffff;stop-opacity:1.0000000;" />
<stop
id="stop11524"
offset="1.0000000"
style="stop-color:#dcdcdc;stop-opacity:1.0000000;" />
</linearGradient>
<linearGradient
id="linearGradient11508"
inkscape:collect="always">
<stop
id="stop11510"
offset="0"
style="stop-color:#000000;stop-opacity:1;" />
<stop
id="stop11512"
offset="1"
style="stop-color:#000000;stop-opacity:0;" />
</linearGradient>
<linearGradient
id="linearGradient11494"
inkscape:collect="always">
<stop
id="stop11496"
offset="0"
style="stop-color:#ef2929;stop-opacity:1;" />
<stop
id="stop11498"
offset="1"
style="stop-color:#ef2929;stop-opacity:0;" />
</linearGradient>
<linearGradient
id="linearGradient11415">
<stop
id="stop11417"
offset="0.0000000"
style="stop-color:#204a87;stop-opacity:0.0000000;" />
<stop
style="stop-color:#204a87;stop-opacity:1.0000000;"
offset="0.50000000"
id="stop11423" />
<stop
id="stop11419"
offset="1"
style="stop-color:#204a87;stop-opacity:0;" />
</linearGradient>
<linearGradient
id="linearGradient11399"
inkscape:collect="always">
<stop
id="stop11401"
offset="0"
style="stop-color:#000000;stop-opacity:1;" />
<stop
id="stop11403"
offset="1"
style="stop-color:#000000;stop-opacity:0;" />
</linearGradient>
<linearGradient
gradientTransform="translate(-60.28571,-0.285714)"
y2="34.462429"
x2="43.615788"
y1="3.7744560"
x1="15.828360"
gradientUnits="userSpaceOnUse"
id="linearGradient11425"
xlink:href="#linearGradient11415"
inkscape:collect="always" />
<linearGradient
gradientTransform="translate(-60.57143,0.000000)"
y2="39.033859"
x2="35.679932"
y1="9.3458843"
x1="9.6957054"
gradientUnits="userSpaceOnUse"
id="linearGradient11427"
xlink:href="#linearGradient11415"
inkscape:collect="always" />
<linearGradient
y2="33.462429"
x2="26.758644"
y1="19.774456"
x1="13.267134"
gradientTransform="translate(-60.85714,0.428571)"
gradientUnits="userSpaceOnUse"
id="linearGradient11439"
xlink:href="#linearGradient11415"
inkscape:collect="always" />
<radialGradient
r="8.5000000"
fy="39.142857"
fx="12.071428"
cy="39.142857"
cx="12.071428"
gradientTransform="matrix(1.000000,0.000000,0.000000,0.487395,0.000000,20.06483)"
gradientUnits="userSpaceOnUse"
id="radialGradient11441"
xlink:href="#linearGradient11399"
inkscape:collect="always" />
<radialGradient
gradientTransform="matrix(1.243453,2.106784e-16,-2.106784e-16,1.243453,-6.713754,-3.742847)"
gradientUnits="userSpaceOnUse"
r="3.8335034"
fy="15.048258"
fx="27.577173"
cy="15.048258"
cx="27.577173"
id="radialGradient11500"
xlink:href="#linearGradient11494"
inkscape:collect="always" />
<radialGradient
r="3.8335034"
fy="16.049133"
fx="27.577173"
cy="16.049133"
cx="27.577173"
gradientTransform="matrix(1.243453,2.106784e-16,-2.106784e-16,1.243453,-6.713754,-3.742847)"
gradientUnits="userSpaceOnUse"
id="radialGradient11504"
xlink:href="#linearGradient11494"
inkscape:collect="always" />
<radialGradient
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.000000,0.000000,0.000000,0.338462,2.166583e-14,29.48178)"
r="6.5659914"
fy="44.565483"
fx="30.203562"
cy="44.565483"
cx="30.203562"
id="radialGradient11514"
xlink:href="#linearGradient11508"
inkscape:collect="always" />
<radialGradient
gradientTransform="matrix(1.8227153,0,0,1.5134373,-18.449633,-14.322885)"
gradientUnits="userSpaceOnUse"
r="20.530962"
fy="35.878170"
fx="24.445690"
cy="35.878170"
cx="24.445690"
id="radialGradient11526"
xlink:href="#linearGradient11520"
inkscape:collect="always" />
<radialGradient
r="6.5659914"
fy="44.565483"
fx="30.203562"
cy="44.565483"
cx="30.203562"
gradientTransform="matrix(1,0,0,0.338462,8.404809e-16,29.48178)"
gradientUnits="userSpaceOnUse"
id="radialGradient11532"
xlink:href="#linearGradient11508"
inkscape:collect="always" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient11508"
id="radialGradient1348"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.000000,0.000000,0.000000,0.338462,-1.353344e-14,29.48178)"
cx="30.203562"
cy="44.565483"
fx="30.203562"
fy="44.565483"
r="6.5659914" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient11520"
id="radialGradient1350"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.995058,-1.651527e-32,0.000000,1.995058,-24.32488,-35.70087)"
cx="24.445690"
cy="35.878170"
fx="24.445690"
fy="35.878170"
r="20.530962" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient11494"
id="radialGradient1352"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.243453,2.106784e-16,-2.106784e-16,1.243453,-6.713754,-3.742847)"
cx="27.577173"
cy="16.049133"
fx="27.577173"
fy="16.049133"
r="3.8335034" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient11494"
id="radialGradient1354"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.243453,2.106784e-16,-2.106784e-16,1.243453,-6.713754,-3.742847)"
cx="27.577173"
cy="15.048258"
fx="27.577173"
fy="15.048258"
r="3.8335034" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient11508"
id="radialGradient1356"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.000000,0.000000,0.000000,0.338462,2.220359e-14,29.48178)"
cx="30.203562"
cy="44.565483"
fx="30.203562"
fy="44.565483"
r="6.5659914" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient11520"
id="radialGradient1366"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(2.049266,-1.696401e-32,0.000000,2.049266,-25.65002,-37.31089)"
cx="24.445690"
cy="35.878170"
fx="24.445690"
fy="35.878170"
r="20.530962" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient24230"
id="linearGradient24236"
x1="12.51301"
y1="30.585787"
x2="12.51301"
y2="16.885592"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.142857,0,0,0.67154739,0.10214033,14.945674)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient24230"
id="linearGradient24240"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.142857,0,0,0.67154739,9.2152552,14.915937)"
x1="12.51301"
y1="30.585787"
x2="12.51301"
y2="16.885592" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient24230"
id="linearGradient24244"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.142857,0,0,0.67154739,18.358111,14.915937)"
x1="12.51301"
y1="30.585787"
x2="12.51301"
y2="16.885592" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient24266"
id="linearGradient24272"
x1="23.5"
y1="19.812498"
x2="23.5"
y2="12.687223"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.142857,0,0,1.142857,-4.627741,-7.290132)" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient24276"
id="radialGradient24282"
cx="24.90625"
cy="35.46875"
fx="24.90625"
fy="35.46875"
r="17.40625"
gradientTransform="matrix(1,0,0,0.321364,0,24.07035)"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient24290"
id="linearGradient24296"
x1="24.53125"
y1="19.0625"
x2="26.3125"
y2="40.25"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.142857,0,0,1.142857,-4.5633838,-5.1132743)" />
</defs>
<sodipodi:namedview
stroke="#ef2929"
fill="#eeeeec"
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="0.25490196"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="8"
inkscape:cx="11.977443"
inkscape:cy="19.615128"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:showpageshadow="false"
inkscape:window-width="1024"
inkscape:window-height="992"
inkscape:window-x="774"
inkscape:window-y="112"
inkscape:window-maximized="0" />
<metadata
id="metadata4">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:creator>
<cc:Agent>
<dc:title>Jakub Steiner</dc:title>
</cc:Agent>
</dc:creator>
<dc:source>http://jimmac.musichall.cz</dc:source>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/" />
<dc:title></dc:title>
<dc:subject>
<rdf:Bag>
<rdf:li>preferences</rdf:li>
<rdf:li>system</rdf:li>
<rdf:li>category</rdf:li>
</rdf:Bag>
</dc:subject>
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
<cc:permits
rdf:resource="http://web.resource.org/cc/Reproduction" />
<cc:permits
rdf:resource="http://web.resource.org/cc/Distribution" />
<cc:requires
rdf:resource="http://web.resource.org/cc/Notice" />
<cc:requires
rdf:resource="http://web.resource.org/cc/Attribution" />
<cc:permits
rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
<cc:requires
rdf:resource="http://web.resource.org/cc/ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<g
transform="matrix(0.02263057,0,0,0.02384865,43.8493,37.63317)"
id="g6707">
<rect
style="opacity:0.40206185;color:#000000;fill:url(#linearGradient6715);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
id="rect6709"
width="1339.6335"
height="478.35718"
x="-1559.2523"
y="-150.69685" />
<path
style="opacity:0.40206185;color:#000000;fill:url(#radialGradient6717);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
d="m -219.61876,-150.68038 c 0,0 0,478.33079 0,478.33079 142.874166,0.90045 345.40022,-107.16966 345.40014,-239.196175 0,-132.026537 -159.436816,-239.134595 -345.40014,-239.134615 z"
id="path6711"
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0" />
<path
sodipodi:nodetypes="cccc"
id="path6713"
d="m -1559.2523,-150.68038 c 0,0 0,478.33079 0,478.33079 -142.8742,0.90045 -345.4002,-107.16966 -345.4002,-239.196175 0,-132.026537 159.4368,-239.134595 345.4002,-239.134615 z"
style="opacity:0.40206185;color:#000000;fill:url(#radialGradient6719);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
inkscape:connector-curvature="0" />
</g>
<rect
ry="0.98129779"
rx="0.98129815"
y="13.852715"
x="5.1806402"
height="27.278023"
width="36.601158"
id="rect11518"
style="color:#000000;fill:url(#radialGradient11526);fill-opacity:1;fill-rule:evenodd;stroke:#9b9b9b;stroke-width:1.14285839;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
inkscape:r_cx="true"
inkscape:r_cy="true" />
<rect
style="color:#000000;fill:none;stroke:#ffffff;stroke-width:1.14285684;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:10;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
id="rect11528"
width="34.315437"
height="24.992298"
x="6.3234916"
y="14.995586"
rx="0"
ry="0"
inkscape:r_cx="true"
inkscape:r_cy="true" />
<rect
style="color:#000000;fill:url(#linearGradient24236);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
id="rect23355"
width="5.7440214"
height="11.398832"
x="11.530714"
y="25.690432"
inkscape:r_cx="true"
inkscape:r_cy="true" />
<rect
inkscape:r_cy="true"
inkscape:r_cx="true"
y="25.660698"
x="20.643827"
height="11.398832"
width="5.7440214"
id="rect24238"
style="color:#000000;fill:url(#linearGradient24240);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible" />
<rect
style="color:#000000;fill:url(#linearGradient24244);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
id="rect24242"
width="5.7440214"
height="11.398832"
x="29.786686"
y="25.660698"
inkscape:r_cx="true"
inkscape:r_cy="true" />
<rect
style="color:#000000;fill:#eeeeec;fill-opacity:1;fill-rule:nonzero;stroke:#9b9b9b;stroke-width:1.14285707;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
id="rect24246"
width="6.8571434"
height="5.7360334"
x="10.989021"
y="25.097256"
inkscape:r_cx="true"
inkscape:r_cy="true" />
<rect
inkscape:r_cy="true"
inkscape:r_cx="true"
y="31.954399"
x="20.131872"
height="5.7360334"
width="6.8571434"
id="rect24248"
style="color:#000000;fill:#eeeeec;fill-opacity:1;fill-rule:nonzero;stroke:#9b9b9b;stroke-width:1.14285707;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
<rect
style="color:#000000;fill:#eeeeec;fill-opacity:1;fill-rule:nonzero;stroke:#9b9b9b;stroke-width:1.14285707;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
id="rect24250"
width="6.8571434"
height="5.7360334"
x="29.274731"
y="31.954399"
inkscape:r_cx="true"
inkscape:r_cy="true" />
<rect
style="color:#000000;fill:none;stroke:#ffffff;stroke-width:1.14285696;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
id="rect24252"
width="4.695261"
height="3.4820046"
x="12.097212"
y="26.2272"
inkscape:r_cx="true"
inkscape:r_cy="true" />
<rect
inkscape:r_cy="true"
inkscape:r_cx="true"
y="33.119007"
x="21.150898"
height="3.4820046"
width="4.695261"
id="rect24254"
style="color:#000000;fill:none;stroke:#ffffff;stroke-width:1.14285696;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
<rect
style="color:#000000;fill:none;stroke:#ffffff;stroke-width:1.14285696;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
id="rect24256"
width="4.695261"
height="3.4820046"
x="30.293756"
y="33.119007"
inkscape:r_cx="true"
inkscape:r_cy="true" />
<path
style="color:#000000;fill:url(#linearGradient24272);fill-opacity:1;fill-rule:nonzero;stroke:#787878;stroke-width:1.1428566;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
d="M 5.0865439,16.138433 1.0865441,4.7098648 l 45.7142829,0 -4.928571,11.4285682 -36.7857121,0 z"
id="path24258"
inkscape:r_cx="true"
inkscape:r_cy="true"
sodipodi:nodetypes="ccccc"
inkscape:connector-curvature="0" />
<rect
style="color:#000000;fill:#dddddd;fill-opacity:1;fill-rule:nonzero;stroke:#9f9f9f;stroke-width:1.14285719;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
id="rect24260"
width="46.857162"
height="3.3571427"
x="0.51511782"
y="4.7098675"
inkscape:r_cx="true"
inkscape:r_cy="true" />
<rect
style="color:#000000;fill:#dddddd;fill-opacity:1;fill-rule:nonzero;stroke:#9f9f9f;stroke-width:1.14285696;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible"
id="rect24262"
width="6.357142"
height="2.2857139"
x="8.5151148"
y="14.995582"
inkscape:r_cx="true"
inkscape:r_cy="true"
rx="1.142857"
ry="1.142857" />
<rect
ry="1.142857"
rx="1.142857"
inkscape:r_cy="true"
inkscape:r_cx="true"
y="14.995582"
x="31.872257"
height="2.2857139"
width="6.357142"
id="rect24264"
style="color:#000000;fill:#dddddd;fill-opacity:1;fill-rule:nonzero;stroke:#9f9f9f;stroke-width:1.14285696;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible" />
<rect
style="opacity:0.43406593;color:#000000;fill:#9f9f9f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
id="rect24284"
width="18.357143"
height="2.2857139"
x="10.293758"
y="20.029581"
inkscape:r_cx="true"
inkscape:r_cy="true"
rx="1.142857"
ry="1.142857" />
<path
style="opacity:0.83406587;color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
d="m 1.1509015,7.4581538 0,-2.2857145 45.7142805,0 -1.142852,1.1428569 -43.4285715,0 -1.142857,1.1428576 z"
id="path24286"
inkscape:r_cx="true"
inkscape:r_cy="true"
inkscape:connector-curvature="0" />
<path
style="opacity:0.09890113;color:#000000;fill:url(#linearGradient24296);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible"
d="m 5.7223298,16.815297 0,8.034661 1.1428566,-0.92857 c 19.1111696,0.53615 23.7380926,0.155813 33.1428546,5.965335 l 1.214288,1.5 -0.07144,-14.714285 -35.4285696,0.142859 z"
id="path24288"
inkscape:r_cx="true"
inkscape:r_cy="true"
sodipodi:nodetypes="ccccccc"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -81,6 +81,12 @@ def content_type_id(label):
return ContentType.objects.filter(app_label=app_label, model=model).values_list('id', flat=True)[0]
@register.filter
def split(value, sep=' '):
parts = value.split(sep)
return (parts[0], sep.join(parts[1:]))
@register.filter
def admin_url(obj):
return change_url(obj)

View File

@ -76,9 +76,9 @@ def runiterator(command, display=False, error_codes=[0], silent=False, stdin=b''
#.decode('ascii'), errors='replace')
if display and stdout:
sys.stdout.write(stdout)
sys.stdout.write(stdout.decode('utf8'))
if display and stderr:
sys.stderr.write(stderr)
sys.stderr.write(stderr.decode('utf8'))
state = _Attribute(stdout)
state.stderr = stderr