Added dynamic help text to backend route
This commit is contained in:
parent
2eb2fb5a5c
commit
d422e109e9
15
TODO.md
15
TODO.md
|
@ -163,7 +163,7 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
|
||||||
|
|
||||||
* Names: lower andupper case allow or disallow ? webapps/account.username etc
|
* Names: lower andupper case allow or disallow ? webapps/account.username etc
|
||||||
|
|
||||||
* Split plans into a separate app (plans and rates / services ) ?
|
* Split plans into a separate app (plans and rates / services ) ?
|
||||||
|
|
||||||
* sync() ServiceController method that synchronizes orchestra and servers (delete or import)
|
* sync() ServiceController method that synchronizes orchestra and servers (delete or import)
|
||||||
|
|
||||||
|
@ -200,3 +200,16 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
|
||||||
* User [Group] webapp/website option (validation) which overrides default mainsystemuser
|
* User [Group] webapp/website option (validation) which overrides default mainsystemuser
|
||||||
|
|
||||||
* validate systemuser.home
|
* validate systemuser.home
|
||||||
|
|
||||||
|
* Create plugin app
|
||||||
|
|
||||||
|
* Create options widget
|
||||||
|
|
||||||
|
* generic options fpm/fcgid/uwsgi webapps (num procs, idle io timeout)
|
||||||
|
* webapp backend option compatibility check?
|
||||||
|
|
||||||
|
|
||||||
|
* Route help text with model name when selecting backend
|
||||||
|
* Service instance name when selecting content_type
|
||||||
|
|
||||||
|
* Address.forward mailbbox validate not available on mailboxes
|
||||||
|
|
|
@ -203,10 +203,11 @@ class SelectPluginAdminMixin(object):
|
||||||
""" Redirects to select account view if required """
|
""" Redirects to select account view if required """
|
||||||
if request.user.is_superuser:
|
if request.user.is_superuser:
|
||||||
plugin_value = request.GET.get(self.plugin_field) or request.POST.get(self.plugin_field)
|
plugin_value = request.GET.get(self.plugin_field) or request.POST.get(self.plugin_field)
|
||||||
if plugin_value or self.plugin.get_plugins() == 1:
|
if plugin_value or len(self.plugin.get_plugins()) == 1:
|
||||||
self.plugin_value = plugin_value
|
self.plugin_value = plugin_value
|
||||||
if not plugin_value:
|
if not plugin_value:
|
||||||
self.plugin_value = self.plugin.get_plugins()[0]
|
self.plugin_value = self.plugin.get_plugins()[0].get_plugin_name()
|
||||||
|
b = self.plugin_value
|
||||||
context = {
|
context = {
|
||||||
'title': _("Add new %s") % camel_case_to_spaces(self.plugin_value),
|
'title': _("Add new %s") % camel_case_to_spaces(self.plugin_value),
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ from functools import wraps
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
|
@ -99,7 +100,10 @@ def admin_link(*args, **kwargs):
|
||||||
if kwargs['field'] in ['id', 'pk', '__unicode__']:
|
if kwargs['field'] in ['id', 'pk', '__unicode__']:
|
||||||
obj = instance
|
obj = instance
|
||||||
else:
|
else:
|
||||||
obj = get_field_value(instance, kwargs['field'])
|
try:
|
||||||
|
obj = get_field_value(instance, kwargs['field'])
|
||||||
|
except ObjectDoesNotExist:
|
||||||
|
return '---'
|
||||||
if not getattr(obj, 'pk', None):
|
if not getattr(obj, 'pk', None):
|
||||||
return '---'
|
return '---'
|
||||||
url = change_url(obj)
|
url = change_url(obj)
|
||||||
|
|
|
@ -20,13 +20,12 @@ class BillContact(models.Model):
|
||||||
account = models.OneToOneField('accounts.Account', verbose_name=_("account"),
|
account = models.OneToOneField('accounts.Account', verbose_name=_("account"),
|
||||||
related_name='billcontact')
|
related_name='billcontact')
|
||||||
name = models.CharField(_("name"), max_length=256, blank=True,
|
name = models.CharField(_("name"), max_length=256, blank=True,
|
||||||
help_text=_("Account full name will be used when not provided"))
|
help_text=_("Account full name will be used when left blank."))
|
||||||
address = models.TextField(_("address"))
|
address = models.TextField(_("address"))
|
||||||
city = models.CharField(_("city"), max_length=128,
|
city = models.CharField(_("city"), max_length=128,
|
||||||
default=settings.BILLS_CONTACT_DEFAULT_CITY)
|
default=settings.BILLS_CONTACT_DEFAULT_CITY)
|
||||||
zipcode = models.CharField(_("zip code"), max_length=10,
|
zipcode = models.CharField(_("zip code"), max_length=10,
|
||||||
validators=[RegexValidator(r'^[0-9A-Z]{3,10}$',
|
validators=[RegexValidator(r'^[0-9A-Z]{3,10}$', _("Enter a valid zipcode."))])
|
||||||
_("Enter a valid zipcode."), 'invalid')])
|
|
||||||
country = models.CharField(_("country"), max_length=20,
|
country = models.CharField(_("country"), max_length=20,
|
||||||
choices=settings.BILLS_CONTACT_COUNTRIES,
|
choices=settings.BILLS_CONTACT_COUNTRIES,
|
||||||
default=settings.BILLS_CONTACT_DEFAULT_COUNTRY)
|
default=settings.BILLS_CONTACT_DEFAULT_COUNTRY)
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
|
from django import forms
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from orchestra.forms.widgets import DynamicHelpTextSelect
|
||||||
from orchestra.admin.html import monospace_format
|
from orchestra.admin.html import monospace_format
|
||||||
from orchestra.admin.utils import admin_link, admin_date, admin_colored
|
from orchestra.admin.utils import admin_link, admin_date, admin_colored
|
||||||
|
|
||||||
|
from .backends import ServiceBackend
|
||||||
from .models import Server, Route, BackendLog, BackendOperation
|
from .models import Server, Route, BackendLog, BackendOperation
|
||||||
|
|
||||||
|
|
||||||
|
@ -27,6 +30,11 @@ class RouteAdmin(admin.ModelAdmin):
|
||||||
list_editable = ['backend', 'host', 'match', 'is_active']
|
list_editable = ['backend', 'host', 'match', 'is_active']
|
||||||
list_filter = ['host', 'is_active', 'backend']
|
list_filter = ['host', 'is_active', 'backend']
|
||||||
|
|
||||||
|
BACKEND_HELP_TEXT = {
|
||||||
|
backend: "This backend operates over '%s'" % ServiceBackend.get_backend(backend).model
|
||||||
|
for backend, __ in ServiceBackend.get_plugin_choices()
|
||||||
|
}
|
||||||
|
|
||||||
def display_model(self, route):
|
def display_model(self, route):
|
||||||
try:
|
try:
|
||||||
return escape(route.backend_class().model)
|
return escape(route.backend_class().model)
|
||||||
|
@ -42,6 +50,19 @@ class RouteAdmin(admin.ModelAdmin):
|
||||||
return "<span style='color: red;'>NOT AVAILABLE</span>"
|
return "<span style='color: red;'>NOT AVAILABLE</span>"
|
||||||
display_actions.short_description = _("actions")
|
display_actions.short_description = _("actions")
|
||||||
display_actions.allow_tags = True
|
display_actions.allow_tags = True
|
||||||
|
|
||||||
|
def formfield_for_dbfield(self, db_field, **kwargs):
|
||||||
|
""" Provides dynamic help text on backend form field """
|
||||||
|
if db_field.name == 'backend':
|
||||||
|
kwargs['widget'] = DynamicHelpTextSelect('this.id', self.BACKEND_HELP_TEXT)
|
||||||
|
return super(RouteAdmin, self).formfield_for_dbfield(db_field, **kwargs)
|
||||||
|
|
||||||
|
def get_form(self, request, obj=None, **kwargs):
|
||||||
|
""" Include dynamic help text for existing objects """
|
||||||
|
form = super(RouteAdmin, self).get_form(request, obj=obj, **kwargs)
|
||||||
|
if obj:
|
||||||
|
form.base_fields['backend'].help_text = self.BACKEND_HELP_TEXT[obj.backend]
|
||||||
|
return form
|
||||||
|
|
||||||
|
|
||||||
class BackendOperationInline(admin.TabularInline):
|
class BackendOperationInline(admin.TabularInline):
|
||||||
|
|
|
@ -6,6 +6,7 @@ from django.utils.translation import ugettext, ugettext_lazy as _
|
||||||
from orchestra.admin import ExtendedModelAdmin
|
from orchestra.admin import ExtendedModelAdmin
|
||||||
from orchestra.admin.utils import change_url
|
from orchestra.admin.utils import change_url
|
||||||
from orchestra.apps.accounts.admin import AccountAdminMixin
|
from orchestra.apps.accounts.admin import AccountAdminMixin
|
||||||
|
from orchestra.forms.widgets import DynamicHelpTextSelect
|
||||||
|
|
||||||
from . import settings
|
from . import settings
|
||||||
from .models import WebApp, WebAppOption
|
from .models import WebApp, WebAppOption
|
||||||
|
@ -26,21 +27,13 @@ class WebAppOptionInline(admin.TabularInline):
|
||||||
}
|
}
|
||||||
|
|
||||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
def formfield_for_dbfield(self, db_field, **kwargs):
|
||||||
""" Make value input widget bigger """
|
|
||||||
if db_field.name == 'value':
|
if db_field.name == 'value':
|
||||||
kwargs['widget'] = forms.TextInput(attrs={'size':'100'})
|
kwargs['widget'] = forms.TextInput(attrs={'size':'100'})
|
||||||
if db_field.name == 'name':
|
if db_field.name == 'name':
|
||||||
# Help text based on select widget
|
# Help text based on select widget
|
||||||
kwargs['widget'] = forms.Select(attrs={
|
kwargs['widget'] = DynamicHelpTextSelect(
|
||||||
'onChange': """
|
'this.id.replace("name", "value")', self.OPTIONS_HELP_TEXT
|
||||||
siteoptions = %s;
|
)
|
||||||
valueelement = $("#"+this.id.replace("name", "value"));
|
|
||||||
valueelement.parent().find('p').remove();
|
|
||||||
valueelement.parent().append(
|
|
||||||
"<p class='help'>" + siteoptions[this.options[this.selectedIndex].value] + "</p>"
|
|
||||||
);
|
|
||||||
""" % str(self.OPTIONS_HELP_TEXT),
|
|
||||||
})
|
|
||||||
return super(WebAppOptionInline, self).formfield_for_dbfield(db_field, **kwargs)
|
return super(WebAppOptionInline, self).formfield_for_dbfield(db_field, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@ -67,7 +60,7 @@ class WebAppAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
||||||
name = "%s on %s" % (website.name, content.path)
|
name = "%s on %s" % (website.name, content.path)
|
||||||
websites.append('<a href="%s">%s</a>' % (url, name))
|
websites.append('<a href="%s">%s</a>' % (url, name))
|
||||||
add_url = reverse('admin:websites_website_add')
|
add_url = reverse('admin:websites_website_add')
|
||||||
# TODO support for preselecting related we app on website
|
# TODO support for preselecting related web app on website
|
||||||
add_url += '?account=%s' % webapp.account_id
|
add_url += '?account=%s' % webapp.account_id
|
||||||
plus = '<strong style="color:green; font-size:12px">+</strong>'
|
plus = '<strong style="color:green; font-size:12px">+</strong>'
|
||||||
websites.append('<a href="%s">%s%s</a>' % (add_url, plus, ugettext("Add website")))
|
websites.append('<a href="%s">%s%s</a>' % (add_url, plus, ugettext("Add website")))
|
||||||
|
@ -80,7 +73,7 @@ class WebAppAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
||||||
if db_field.name == 'type':
|
if db_field.name == 'type':
|
||||||
# Help text based on select widget
|
# Help text based on select widget
|
||||||
kwargs['widget'] = forms.Select(attrs={
|
kwargs['widget'] = forms.Select(attrs={
|
||||||
'onChange': """
|
'onClick': """
|
||||||
siteoptions = %s;
|
siteoptions = %s;
|
||||||
valueelement = $("#"+this.id);
|
valueelement = $("#"+this.id);
|
||||||
valueelement.parent().find('p').remove();
|
valueelement.parent().find('p').remove();
|
||||||
|
|
|
@ -29,20 +29,20 @@ WEBAPPS_FCGID_PATH = getattr(settings, 'WEBAPPS_FCGID_PATH',
|
||||||
|
|
||||||
WEBAPPS_TYPES = getattr(settings, 'WEBAPPS_TYPES', {
|
WEBAPPS_TYPES = getattr(settings, 'WEBAPPS_TYPES', {
|
||||||
'php5.5': {
|
'php5.5': {
|
||||||
'verbose_name': "PHP 5.5",
|
'verbose_name': "PHP 5.5 fpm",
|
||||||
# 'fpm', ('unix:/var/run/%(user)s-%(app_name)s.sock|fcgi://127.0.0.1%(app_path)s',),
|
# 'fpm', ('unix:/var/run/%(user)s-%(app_name)s.sock|fcgi://127.0.0.1%(app_path)s',),
|
||||||
'directive': ('fpm', 'fcgi://{}%(app_path)s'.format(WEBAPPS_FPM_LISTEN)),
|
'directive': ('fpm', 'fcgi://{}%(app_path)s'.format(WEBAPPS_FPM_LISTEN)),
|
||||||
'help_text': _("This creates a PHP5.5 application under ~/webapps/<app_name><br>"
|
'help_text': _("This creates a PHP5.5 application under ~/webapps/<app_name><br>"
|
||||||
"PHP-FPM will be used to execute PHP files.")
|
"PHP-FPM will be used to execute PHP files.")
|
||||||
},
|
},
|
||||||
'php5.2': {
|
'php5.2': {
|
||||||
'verbose_name': "PHP 5.2",
|
'verbose_name': "PHP 5.2 fcgi",
|
||||||
'directive': ('fcgi', WEBAPPS_FCGID_PATH),
|
'directive': ('fcgi', WEBAPPS_FCGID_PATH),
|
||||||
'help_text': _("This creates a PHP5.2 application under ~/webapps/<app_name><br>"
|
'help_text': _("This creates a PHP5.2 application under ~/webapps/<app_name><br>"
|
||||||
"Apache-mod-fcgid will be used to execute PHP files.")
|
"Apache-mod-fcgid will be used to execute PHP files.")
|
||||||
},
|
},
|
||||||
'php4': {
|
'php4': {
|
||||||
'verbose_name': "PHP 4",
|
'verbose_name': "PHP 4 fcgi",
|
||||||
'directive': ('fcgi', WEBAPPS_FCGID_PATH,),
|
'directive': ('fcgi', WEBAPPS_FCGID_PATH,),
|
||||||
'help_text': _("This creates a PHP4 application under ~/webapps/<app_name><br>"
|
'help_text': _("This creates a PHP4 application under ~/webapps/<app_name><br>"
|
||||||
"Apache-mod-fcgid will be used to execute PHP files.")
|
"Apache-mod-fcgid will be used to execute PHP files.")
|
||||||
|
@ -104,8 +104,24 @@ WEBAPPS_PHP_DISABLED_FUNCTIONS = getattr(settings, 'WEBAPPS_PHP_DISABLED_FUNCTIO
|
||||||
|
|
||||||
WEBAPPS_OPTIONS = getattr(settings, 'WEBAPPS_OPTIONS', {
|
WEBAPPS_OPTIONS = getattr(settings, 'WEBAPPS_OPTIONS', {
|
||||||
# { name: ( verbose_name, [help_text], validation_regex ) }
|
# { name: ( verbose_name, [help_text], validation_regex ) }
|
||||||
|
# Processes
|
||||||
|
'timeout': (
|
||||||
|
_("Process timeout"),
|
||||||
|
_("Maximum time in seconds allowed for a request to complete (a number between 0 and 999)."),
|
||||||
|
# FCGID FcgidIOTimeout
|
||||||
|
# FPM pm.request_terminate_timeout
|
||||||
|
# PHP max_execution_time ini
|
||||||
|
r'^[0-9]{1,3}$',
|
||||||
|
),
|
||||||
|
'processes': (
|
||||||
|
_("Number of processes"),
|
||||||
|
_("Maximum number of children that can be alive at the same time (a number between 0 and 9)."),
|
||||||
|
# FCGID MaxProcesses
|
||||||
|
# FPM pm.max_children
|
||||||
|
r'^[0-9]$',
|
||||||
|
),
|
||||||
# PHP
|
# PHP
|
||||||
'enabled_functions': (
|
'php-enabled_functions': (
|
||||||
_("PHP - Enabled functions"),
|
_("PHP - Enabled functions"),
|
||||||
' '.join(WEBAPPS_PHP_DISABLED_FUNCTIONS),
|
' '.join(WEBAPPS_PHP_DISABLED_FUNCTIONS),
|
||||||
r'^[\w\.,-]+$'
|
r'^[\w\.,-]+$'
|
||||||
|
@ -249,30 +265,4 @@ WEBAPPS_OPTIONS = getattr(settings, 'WEBAPPS_OPTIONS', {
|
||||||
_("PHP - zend_extension"),
|
_("PHP - zend_extension"),
|
||||||
r'^[^ ]+$'
|
r'^[^ ]+$'
|
||||||
),
|
),
|
||||||
# FCGID
|
|
||||||
'FcgidIdleTimeout': (
|
|
||||||
_("FCGI - Idle timeout"),
|
|
||||||
_("Number between 0 and 999."),
|
|
||||||
r'^[0-9]{1,3}$'
|
|
||||||
),
|
|
||||||
'FcgidBusyTimeout': (
|
|
||||||
_("FCGI - Busy timeout"),
|
|
||||||
_("Number between 0 and 999."),
|
|
||||||
r'^[0-9]{1,3}$'
|
|
||||||
),
|
|
||||||
'FcgidConnectTimeout': (
|
|
||||||
_("FCGI - Connection timeout"),
|
|
||||||
_("Number of seconds between 0 and 999."),
|
|
||||||
r'^[0-9]{1,3}$'
|
|
||||||
),
|
|
||||||
'FcgidIOTimeout': (
|
|
||||||
_("FCGI - IO timeout"),
|
|
||||||
_("Number of seconds between 0 and 999."),
|
|
||||||
r'^[0-9]{1,3}$'
|
|
||||||
),
|
|
||||||
'FcgidProcessLifeTime': (
|
|
||||||
_("FCGI - IO timeout"),
|
|
||||||
_("Numbe of secondsr between 0 and 9999."),
|
|
||||||
r'^[0-9]{1,4}$'
|
|
||||||
),
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,6 +5,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
from orchestra.admin import ExtendedModelAdmin
|
from orchestra.admin import ExtendedModelAdmin
|
||||||
from orchestra.admin.utils import admin_link, change_url
|
from orchestra.admin.utils import admin_link, change_url
|
||||||
from orchestra.apps.accounts.admin import AccountAdminMixin, SelectAccountAdminMixin
|
from orchestra.apps.accounts.admin import AccountAdminMixin, SelectAccountAdminMixin
|
||||||
|
from orchestra.forms.widgets import DynamicHelpTextSelect
|
||||||
|
|
||||||
from . import settings
|
from . import settings
|
||||||
from .models import Content, Website, WebsiteOption
|
from .models import Content, Website, WebsiteOption
|
||||||
|
@ -25,21 +26,13 @@ class WebsiteOptionInline(admin.TabularInline):
|
||||||
# }
|
# }
|
||||||
|
|
||||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
def formfield_for_dbfield(self, db_field, **kwargs):
|
||||||
""" Make value input widget bigger """
|
|
||||||
if db_field.name == 'value':
|
if db_field.name == 'value':
|
||||||
kwargs['widget'] = forms.TextInput(attrs={'size':'100'})
|
kwargs['widget'] = forms.TextInput(attrs={'size':'100'})
|
||||||
if db_field.name == 'name':
|
if db_field.name == 'name':
|
||||||
# Help text based on select widget
|
# Help text based on select widget
|
||||||
kwargs['widget'] = forms.Select(attrs={
|
kwargs['widget'] = DynamicHelpTextSelect(
|
||||||
'onChange': """
|
'this.id.replace("name", "value")', self.OPTIONS_HELP_TEXT
|
||||||
siteoptions = %s;
|
)
|
||||||
valueelement = $("#"+this.id.replace("name", "value"));
|
|
||||||
valueelement.parent().find('p').remove();
|
|
||||||
valueelement.parent().append(
|
|
||||||
"<p class='help'>" + siteoptions[this.options[this.selectedIndex].value] + "</p>"
|
|
||||||
);
|
|
||||||
""" % str(self.OPTIONS_HELP_TEXT),
|
|
||||||
})
|
|
||||||
return super(WebsiteOptionInline, self).formfield_for_dbfield(db_field, **kwargs)
|
return super(WebsiteOptionInline, self).formfield_for_dbfield(db_field, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import re
|
import re
|
||||||
|
import textwrap
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
|
@ -63,3 +64,25 @@ def paddingCheckboxSelectMultiple(padding):
|
||||||
return mark_safe(value)
|
return mark_safe(value)
|
||||||
widget.render = render
|
widget.render = render
|
||||||
return widget
|
return widget
|
||||||
|
|
||||||
|
|
||||||
|
class DynamicHelpTextSelect(forms.Select):
|
||||||
|
def __init__(self, target, help_text, *args, **kwargs):
|
||||||
|
help_text = self.get_dynamic_help_text(target, help_text)
|
||||||
|
attrs = {
|
||||||
|
'onClick': help_text,
|
||||||
|
'onChange': help_text,
|
||||||
|
}
|
||||||
|
attrs.update(kwargs.get('attrs', {}))
|
||||||
|
kwargs['attrs'] = attrs
|
||||||
|
super(DynamicHelpTextSelect, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def get_dynamic_help_text(self, target, help_text):
|
||||||
|
return textwrap.dedent("""\
|
||||||
|
siteoptions = {help_text};
|
||||||
|
valueelement = $("#" + {target});
|
||||||
|
valueelement.parent().find('p').remove();
|
||||||
|
valueelement.parent().append(
|
||||||
|
"<p class='help'>" + siteoptions[this.options[this.selectedIndex].value] + "</p>"
|
||||||
|
);""".format(target=target, help_text=str(help_text))
|
||||||
|
)
|
||||||
|
|
|
@ -56,6 +56,6 @@ def dict_setting_to_choices(choices):
|
||||||
|
|
||||||
def tuple_setting_to_choices(choices):
|
def tuple_setting_to_choices(choices):
|
||||||
return sorted(
|
return sorted(
|
||||||
[ (name, opt[0]) for name,opt in choices.iteritems() ],
|
tuple((name, opt[0]) for name, opt in choices.iteritems()),
|
||||||
key=lambda e: e[0]
|
key=lambda e: e[0]
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue