Merge all php webapps into one

This commit is contained in:
Marc Aymerich 2015-03-12 14:05:23 +00:00
parent e80f921601
commit fd119f434d
21 changed files with 497 additions and 374 deletions

View File

@ -199,4 +199,11 @@ Php binaries should have this format: /usr/bin/php5.2-cgi
* Orchestra global search box on the header, based https://github.com/django/django/blob/master/django/contrib/admin/options.py#L866 and iterating over all registered services and inspectin its admin.search_fields
* contain error on plugin missing key (plugin dissabled)
* contain error on plugin missing key (plugin dissabled): NOP, fail hard is better than silently
* contact.alternative_phone on a phone.tooltip, email:to
* better validate options and directives (url locations, filesystem paths, etc..)
* filter php deprecated options out based on version
* Todo get php_version for fcgid wrapper

View File

@ -56,13 +56,13 @@ class MailSystemUserBackend(ServiceController):
def delete(self, mailbox):
context = self.get_context(mailbox)
self.append('mv %(home)s %(home)s.deleted' % context)
self.append(textwrap.dedent("""
{ sleep 2 && killall -u %(user)s -s KILL; } &
killall -u %(user)s || true
userdel %(user)s || true
groupdel %(user)s || true""") % context
)
self.append('mv %(home)s %(home)s.deleted' % context)
def get_context(self, mailbox):
context = {

View File

@ -25,8 +25,7 @@ STATE_COLORS = {
class RouteAdmin(admin.ModelAdmin):
list_display = [
'id', 'backend', 'host', 'match', 'display_model', 'display_actions',
'is_active'
'backend', 'host', 'match', 'display_model', 'display_actions', 'is_active'
]
list_editable = ['host', 'match', 'is_active']
list_filter = ['host', 'is_active', 'backend']
@ -65,7 +64,7 @@ class RouteAdmin(admin.ModelAdmin):
""" 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]
form.base_fields['backend'].help_text = self.BACKEND_HELP_TEXT.get(obj.backend, '')
return form

View File

@ -10,6 +10,7 @@ from orchestra.utils.python import import_class
from . import settings
from .helpers import send_report
from .models import BackendLog
from .signals import pre_action, post_action
logger = logging.getLogger(__name__)
@ -65,8 +66,17 @@ def execute(operations, async=False):
else:
scripts[key][1].append(operation)
# Get and call backend action method
method = getattr(scripts[key][0], operation.action)
backend = scripts[key][0]
method = getattr(backend, operation.action)
kwargs = {
'sender': backend.__class__,
'backend': backend,
'instance': operation.instance,
'action': operation.action,
}
pre_action.send(**kwargs)
method(operation.instance)
post_action.send(**kwargs)
# Execute scripts on each server
threads = []
executions = []

View File

@ -7,9 +7,9 @@ from django.http.response import HttpResponseServerError
from orchestra.utils.python import OrderedSet
from .manager import router
from .backends import ServiceBackend
from .helpers import message_user
from .manager import router
from .models import BackendLog
from .models import BackendOperation as Operation
@ -103,7 +103,7 @@ class OperationsMiddleware(object):
pass
else:
update_fields = kwargs.get('update_fields', None)
if update_fields:
if update_fields is not None:
# "update_fileds=[]" is a convention for explicitly executing backend
# i.e. account.disable()
if update_fields != []:

View File

@ -0,0 +1,6 @@
import django.dispatch
pre_action = django.dispatch.Signal(providing_args=['backend', 'instance', 'action'])
post_action = django.dispatch.Signal(providing_args=['backend', 'instance', 'action'])

View File

@ -53,6 +53,7 @@ class WebAppAdmin(SelectPluginAdminMixin, AccountAdminMixin, ExtendedModelAdmin)
inlines = [WebAppOptionInline]
readonly_fields = ('account_link',)
change_readonly_fields = ('name', 'type')
search_fuelds = ('name', 'account__username')
list_prefetch_related = ('content_set__website',)
plugin = AppType
plugin_field = 'type'

View File

@ -0,0 +1,177 @@
import os
import textwrap
from django.template import Template, Context
from django.utils.translation import ugettext_lazy as _
from orchestra.apps.orchestration import ServiceController
from . import WebAppServiceMixin
from .. import settings
class PHPBackend(WebAppServiceMixin, ServiceController):
verbose_name = _("PHP FPM/FCGID")
default_route_match = "webapp.type == 'php'"
def save(self, webapp):
context = self.get_context(webapp)
if webapp.type_instance.is_fpm:
self.save_fpm(webapp, context)
self.delete_fcgid(webapp, context)
elif webapp.type_instance.is_fcgid:
self.save_fcgid(webapp, context)
self.delete_fpm(webapp, context)
def save_fpm(self, webapp, context):
self.create_webapp_dir(context)
self.set_under_construction(context)
self.append(textwrap.dedent("""\
{
echo -e '%(fpm_config)s' | diff -N -I'^\s*;;' %(fpm_path)s -
} || {
echo -e '%(fpm_config)s' > %(fpm_path)s
UPDATEDFPM=1
}""") % context
)
def save_fcgid(self, webapp, context):
self.create_webapp_dir(context)
self.set_under_construction(context)
self.append("mkdir -p %(wrapper_dir)s" % context)
self.append(textwrap.dedent("""\
{
echo -e '%(wrapper)s' | diff -N -I'^\s*#' %(wrapper_path)s -
} || {
echo -e '%(wrapper)s' > %(wrapper_path)s; UPDATED_APACHE=1
}""") % context
)
self.append("chmod +x %(wrapper_path)s" % context)
self.append("chown -R %(user)s:%(group)s %(wrapper_dir)s" % context)
if context['cmd_options']:
self.append(textwrap.dedent("""
{
echo -e '%(cmd_options)s' | diff -N -I'^\s*#' %(cmd_options_path)s -
} || {
echo -e '%(cmd_options)s' > %(cmd_options_path)s; UPDATED_APACHE=1
}""" ) % context
)
else:
self.append("rm -f %(cmd_options_path)s" % context)
def delete(self, webapp):
context = self.get_context(webapp)
if webapp.type_instance.is_fpm:
self.delete_fpm(webapp, context)
elif webapp.type_instance.is_fcgid:
self.delete_fcgid(webapp, context)
self.delete_webapp_dir(context)
def delete_fpm(self, webapp, context):
self.append("rm -f %(fpm_path)s" % context)
def delete_fcgid(self, webapp, context):
self.append("rm -f %(wrapper_path)s" % context)
self.append("rm -f %(cmd_options_path)s" % context)
def commit(self):
if self.content:
self.append(textwrap.dedent("""
if [[ $UPDATEDFPM == 1 ]]; then
service php5-fpm reload
service php5-fpm start
fi""")
)
self.append(textwrap.dedent("""\
if [[ $UPDATED_APACHE == 1 ]]; then
service apache2 reload
fi""")
)
def get_fpm_config(self, webapp, context):
context.update({
'init_vars': webapp.type_instance.get_php_init_vars(),
'max_children': webapp.get_options().get('processes', False),
'request_terminate_timeout': webapp.get_options().get('timeout', False),
})
context['fpm_listen'] = webapp.type_instance.FPM_LISTEN % context
fpm_config = Template(textwrap.dedent("""\
;; {{ banner }}
[{{ user }}]
user = {{ user }}
group = {{ group }}
listen = {{ fpm_listen | safe }}
listen.owner = {{ user }}
listen.group = {{ group }}
pm = ondemand
{% if max_children %}pm.max_children = {{ max_children }}{% endif %}
{% if request_terminate_timeout %}request_terminate_timeout = {{ request_terminate_timeout }}{% endif %}
{% for name, value in init_vars.iteritems %}
php_admin_value[{{ name | safe }}] = {{ value | safe }}{% endfor %}
"""
))
return fpm_config.render(Context(context))
def get_fcgid_wrapper(self, webapp, context):
opt = webapp.type_instance
# Format PHP init vars
init_vars = opt.get_php_init_vars()
if init_vars:
init_vars = [ '-d %s="%s"' % (k,v) for k,v in init_vars.iteritems() ]
init_vars = ', '.join(init_vars)
context.update({
'php_binary': os.path.normpath(settings.WEBAPPS_PHP_CGI_BINARY_PATH % context),
'php_rc': os.path.normpath(settings.WEBAPPS_PHP_CGI_RC_DIR % context),
'php_ini_scan': os.path.normpath(settings.WEBAPPS_PHP_CGI_INI_SCAN_DIR % context),
'php_init_vars': init_vars,
})
return textwrap.dedent("""\
#!/bin/sh
# %(banner)s
export PHPRC=%(php_rc)s
export PHP_INI_SCAN_DIR=%(php_ini_scan)s
exec %(php_binary)s %(php_init_vars)s""") % context
def get_fcgid_cmd_options(self, webapp, context):
maps = {
'MaxProcesses': webapp.get_options().get('processes', None),
'IOTimeout': webapp.get_options().get('timeout', None),
}
cmd_options = []
for directive, value in maps.iteritems():
if value:
cmd_options.append("%s %s" % (directive, value))
if cmd_options:
head = '# %(banner)s\nFcgidCmdOptions %(wrapper_path)s' % context
cmd_options.insert(0, head)
return ' \\\n '.join(cmd_options)
def update_fcgid_context(self, webapp, context):
wrapper_path = webapp.type_instance.FCGID_WRAPPER_PATH % context
context.update({
'wrapper': self.get_fcgid_wrapper(webapp, context),
'wrapper_path': wrapper_path,
'wrapper_dir': os.path.dirname(wrapper_path),
})
context.update({
'cmd_options': self.get_fcgid_cmd_options(webapp, context),
'cmd_options_path': settings.WEBAPPS_FCGID_CMD_OPTIONS_PATH % context,
})
def update_fpm_context(self, webapp, context):
context.update({
'fpm_config': self.get_fpm_config(webapp, context),
'fpm_path': settings.WEBAPPS_PHPFPM_POOL_PATH % context,
})
return context
def get_context(self, webapp):
context = super(PHPBackend, self).get_context(webapp)
context.update({
'php_version': webapp.type_instance.get_php_version(),
'php_version_number': webapp.type_instance.get_php_version_number(),
})
self.update_fcgid_context(webapp, context)
self.update_fpm_context(webapp, context)
return context

View File

@ -1,97 +0,0 @@
import os
import textwrap
from django.utils.translation import ugettext_lazy as _
from orchestra.apps.orchestration import ServiceController
from . import WebAppServiceMixin
from .. import settings
class PHPFcgidBackend(WebAppServiceMixin, ServiceController):
""" Per-webapp fcgid application """
verbose_name = _("PHP-Fcgid")
directive = 'fcgid'
default_route_match = "webapp.type_class.php_execution == 'fcgid'"
def save(self, webapp):
context = self.get_context(webapp)
self.create_webapp_dir(context)
self.set_under_construction(context)
self.append("mkdir -p %(wrapper_dir)s" % context)
self.append(textwrap.dedent("""\
{
echo -e '%(wrapper)s' | diff -N -I'^\s*#' %(wrapper_path)s -
} || {
echo -e '%(wrapper)s' > %(wrapper_path)s; UPDATED_APACHE=1
}""") % context
)
self.append("chmod +x %(wrapper_path)s" % context)
self.append("chown -R %(user)s:%(group)s %(wrapper_dir)s" % context)
if context['cmd_options']:
self.append(textwrap.dedent("""
{
echo -e '%(cmd_options)s' | diff -N -I'^\s*#' %(cmd_options_path)s -
} || {
echo -e '%(cmd_options)s' > %(cmd_options_path)s; UPDATED_APACHE=1
}""" ) % context
)
else:
self.append("rm -f %(cmd_options_path)s" % context)
def delete(self, webapp):
context = self.get_context(webapp)
self.append("rm -f '%(wrapper_path)s'" % context)
self.append("rm -f '%(cmd_options_path)s'" % context)
self.delete_webapp_dir(context)
def commit(self):
self.append('if [[ $UPDATED_APACHE == 1 ]]; then service apache2 reload; fi')
def get_fcgid_wrapper(self, webapp, context):
opt = webapp.type_instance
# Format PHP init vars
init_vars = opt.get_php_init_vars()
if init_vars:
init_vars = [ '-d %s="%s"' % (k,v) for k,v in init_vars.iteritems() ]
init_vars = ', '.join(init_vars)
context.update({
'php_binary': opt.get_php_binary_path(),
'php_rc': opt.get_php_rc_path(),
'php_init_vars': init_vars,
})
return textwrap.dedent("""\
#!/bin/sh
# %(banner)s
export PHPRC=%(php_rc)s
exec %(php_binary)s %(php_init_vars)s""") % context
def get_fcgid_cmd_options(self, webapp, context):
maps = {
'MaxProcesses': webapp.get_options().get('processes', None),
'IOTimeout': webapp.get_options().get('timeout', None),
}
cmd_options = []
for directive, value in maps.iteritems():
if value:
cmd_options.append("%s %s" % (directive, value))
if cmd_options:
head = '# %(banner)s\nFcgidCmdOptions %(wrapper_path)s' % context
cmd_options.insert(0, head)
return ' \\\n '.join(cmd_options)
def get_context(self, webapp):
context = super(PHPFcgidBackend, self).get_context(webapp)
wrapper_path = settings.WEBAPPS_FCGID_WRAPPER_PATH % context
context.update({
'wrapper': self.get_fcgid_wrapper(webapp, context),
'wrapper_path': wrapper_path,
'wrapper_dir': os.path.dirname(wrapper_path),
})
context.update({
'cmd_options': self.get_fcgid_cmd_options(webapp, context),
'cmd_options_path': settings.WEBAPPS_FCGID_CMD_OPTIONS_PATH % context,
})
return context

View File

@ -1,78 +0,0 @@
import os
import textwrap
from django.template import Template, Context
from django.utils.translation import ugettext_lazy as _
from orchestra.apps.orchestration import ServiceController
from . import WebAppServiceMixin
from .. import settings
class PHPFPMBackend(WebAppServiceMixin, ServiceController):
""" Per-webapp php application """
verbose_name = _("PHP-FPM")
default_route_match = "webapp.type_class.php_execution == 'fpm'"
def save(self, webapp):
context = self.get_context(webapp)
self.create_webapp_dir(context)
self.set_under_construction(context)
self.append(textwrap.dedent("""\
{
echo -e '%(fpm_config)s' | diff -N -I'^\s*;;' %(fpm_path)s -
} || {
echo -e '%(fpm_config)s' > %(fpm_path)s
UPDATEDFPM=1
}""") % context
)
def delete(self, webapp):
context = self.get_context(webapp)
self.append("rm '%(fpm_path)s'" % context)
self.delete_webapp_dir(context)
def commit(self):
if not self.cmds:
return
super(PHPFPMBackend, self).commit()
self.append(textwrap.dedent("""
if [[ $UPDATEDFPM == 1 ]]; then
service php5-fpm reload
service php5-fpm start
fi"""))
def get_fpm_config(self, webapp, context):
context.update({
'init_vars': webapp.type_instance.get_php_init_vars(),
'fpm_port': webapp.get_fpm_port(),
'max_children': webapp.get_options().get('processes', False),
'request_terminate_timeout': webapp.get_options().get('timeout', False),
})
context['fpm_listen'] = settings.WEBAPPS_FPM_LISTEN % context
fpm_config = Template(textwrap.dedent("""\
;; {{ banner }}
[{{ user }}]
user = {{ user }}
group = {{ group }}
listen = {{ fpm_listen | safe }}
listen.owner = {{ user }}
listen.group = {{ group }}
pm = ondemand
{% if max_children %}pm.max_children = {{ max_children }}{% endif %}
{% if request_terminate_timeout %}request_terminate_timeout = {{ request_terminate_timeout }}{% endif %}
{% for name, value in init_vars.iteritems %}
php_admin_value[{{ name | safe }}] = {{ value | safe }}{% endfor %}
"""
))
return fpm_config.render(Context(context))
def get_context(self, webapp):
context = super(PHPFPMBackend, self).get_context(webapp)
context.update({
'fpm_config': self.get_fpm_config(webapp, context),
'fpm_path': settings.WEBAPPS_PHPFPM_POOL_PATH % context,
})
return context

View File

@ -27,50 +27,48 @@ WEBAPPS_FCGID_CMD_OPTIONS_PATH = getattr(settings, 'WEBAPPS_FCGID_CMD_OPTIONS_PA
WEBAPPS_PHP_ERROR_LOG_PATH = getattr(settings, 'WEBAPPS_PHP_ERROR_LOG_PATH',
'')
WEBAPPS_TYPES = getattr(settings, 'WEBAPPS_TYPES', (
'orchestra.apps.webapps.types.php.PHPFPMApp',
'orchestra.apps.webapps.types.php.PHPFCGIDApp',
'orchestra.apps.webapps.types.php.PHPApp',
'orchestra.apps.webapps.types.misc.StaticApp',
'orchestra.apps.webapps.types.misc.WebalizerApp',
'orchestra.apps.webapps.types.saas.WordPressMuApp',
'orchestra.apps.webapps.types.saas.DokuWikiMuApp',
'orchestra.apps.webapps.types.saas.DrupalMuApp',
'orchestra.apps.webapps.types.misc.SymbolicLinkApp',
'orchestra.apps.webapps.types.wordpress.WordPressFPMApp',
'orchestra.apps.webapps.types.wordpress.WordPressFCGIDApp',
'orchestra.apps.webapps.types.wordpress.WordPressApp',
))
WEBAPPS_PHP_FCGID_VERSIONS = getattr(settings, 'WEBAPPS_PHP_FCGID_VERSIONS', (
('5.4', '5.4'),
('5.3', '5.3'),
('5.2', '5.2'),
('4', '4'),
WEBAPPS_PHP_VERSIONS = getattr(settings, 'WEBAPPS_PHP_VERSIONS', (
# Execution modle choose by ending with -fpm or -cgi
('php-5.4-fpm', 'PHP 5.4 FPM'),
('php-5.4-cgi', 'PHP 5.4 FCGID'),
('php-5.3-cgi', 'PHP 5.3 FCGID'),
('php-5.2-cgi', 'PHP 5.2 FCGID'),
('php-4-cgi', 'PHP 4 FCGID'),
))
WEBAPPS_PHP_FCGID_DEFAULT_VERSION = getattr(settings, 'WEBAPPS_PHP_FCGID_DEFAULT_VERSION',
'5.4')
WEBAPPS_DEFAULT_PHP_VERSION = getattr(settings, 'WEBAPPS_DEFAULT_PHP_VERSION',
'5.4-cgi')
WEBAPPS_PHP_CGI_BINARY_PATH = getattr(settings, 'WEBAPPS_PHP_CGI_BINARY_PATH',
# Path of the cgi binary used by fcgid
'/usr/bin/php%(php_version)s-cgi')
'/usr/bin/php%(php_version_number)s-cgi')
WEBAPPS_PHP_CGI_RC_PATH = getattr(settings, 'WEBAPPS_PHP_CGI_RC_PATH',
WEBAPPS_PHP_CGI_RC_DIR = getattr(settings, 'WEBAPPS_PHP_CGI_RC_DIR',
# Path to php.ini
'/etc/php%(php_version)s/cgi/')
'/etc/php%(php_version_number)s/cgi/')
WEBAPPS_PHP_FPM_VERSIONS = getattr(settings, 'WEBAPPS_PHP_FPM_VERSIONS', (
('5.4', '5.4'),
))
WEBAPPS_PHP_CGI_INI_SCAN_DIR = getattr(settings, 'WEBAPPS_PHP_CGI_INI_SCAN_DIR',
# Path to php.ini
'/etc/php%(php_version_number)s/cgi/conf.d')
WEBAPPS_PHP_FPM_DEFAULT_VERSION = getattr(settings, 'WEBAPPS_PHP_DEFAULT_VERSION',
'5.4')
WEBAPPS_UNDER_CONSTRUCTION_PATH = getattr(settings, 'WEBAPPS_UNDER_CONSTRUCTION_PATH',
# Server-side path where a under construction stock page is

View File

@ -20,7 +20,6 @@ class AppType(plugins.Plugin):
unique_name = False
option_groups = (AppOption.FILESYSTEM, AppOption.PROCESS, AppOption.PHP)
# TODO generic name like 'execution' ?
php_execution = None
@classmethod
@cached

View File

@ -9,7 +9,7 @@ from orchestra.plugins.forms import PluginDataForm
from ..options import AppOption
from . import AppType
from .php import PHPAppType
from .php import PHPApp
class StaticApp(AppType):
@ -48,7 +48,7 @@ class SymbolicLinkSerializer(serializers.Serializer):
path = serializers.CharField(label=_("Path"))
class SymbolicLinkApp(PHPAppType):
class SymbolicLinkApp(PHPApp):
name = 'symbolic-link'
verbose_name = "Symbolic link"
form = SymbolicLinkForm

View File

@ -1,4 +1,5 @@
import os
import re
from django import forms
from django.utils.translation import ugettext_lazy as _
@ -12,12 +13,46 @@ from .. import settings
from . import AppType
class PHPAppType(AppType):
FPM = 'fpm'
FCGID = 'fcgid'
help_message = _("Version of PHP used to execute this webapp. <br>"
"Changing the PHP version may result in application malfunction, "
"make sure that everything continue to work as expected.")
php_version = 5.4
fpm_listen = settings.WEBAPPS_FPM_LISTEN
class PHPAppForm(PluginDataForm):
php_version = forms.ChoiceField(label=_("PHP version"),
choices=settings.WEBAPPS_PHP_VERSIONS,
initial=settings.WEBAPPS_DEFAULT_PHP_VERSION,
help_text=help_message)
class PHPAppSerializer(serializers.Serializer):
php_version = serializers.ChoiceField(label=_("PHP version"),
choices=settings.WEBAPPS_PHP_VERSIONS,
default=settings.WEBAPPS_DEFAULT_PHP_VERSION,
help_text=help_message)
class PHPApp(AppType):
name = 'php'
verbose_name = "PHP"
help_text = _("This creates a PHP application under ~/webapps/&lt;app_name&gt;<br>")
form = PHPAppForm
serializer = PHPAppSerializer
icon = 'orchestra/icons/apps/PHP.png'
DEFAULT_PHP_VERSION = settings.WEBAPPS_DEFAULT_PHP_VERSION
PHP_DISABLED_FUNCTIONS = settings.WEBAPPS_PHP_DISABLED_FUNCTIONS
PHP_ERROR_LOG_PATH = settings.WEBAPPS_PHP_ERROR_LOG_PATH
FPM_LISTEN = settings.WEBAPPS_FPM_LISTEN
FCGID_WRAPPER_PATH = settings.WEBAPPS_FCGID_WRAPPER_PATH
@property
def is_fpm(self):
return self.get_php_version().endswith('-fpm')
@property
def is_fcgid(self):
return self.get_php_version().endswith('-cgi')
def get_context(self):
""" context used to format settings """
@ -46,95 +81,37 @@ class PHPAppType(AppType):
enabled_functions += enabled_functions.get().value.split(',')
if enabled_functions:
disabled_functions = []
for function in settings.WEBAPPS_PHP_DISABLED_FUNCTIONS:
for function in self.PHP_DISABLED_FUNCTIONS:
if function not in enabled_functions:
disabled_functions.append(function)
init_vars['dissabled_functions'] = ','.join(disabled_functions)
if settings.WEBAPPS_PHP_ERROR_LOG_PATH and 'error_log' not in init_vars:
if self.PHP_ERROR_LOG_PATH and 'error_log' not in init_vars:
context = self.get_context()
error_log_path = os.path.normpath(settings.WEBAPPS_PHP_ERROR_LOG_PATH % context)
error_log_path = os.path.normpath(self.PHP_ERROR_LOG_PATH % context)
init_vars['error_log'] = error_log_path
return init_vars
help_message = _("Version of PHP used to execute this webapp. <br>"
"Changing the PHP version may result in application malfunction, "
"make sure that everything continue to work as expected.")
class PHPFPMAppForm(PluginDataForm):
php_version = forms.ChoiceField(label=_("PHP version"),
choices=settings.WEBAPPS_PHP_FPM_VERSIONS,
initial=settings.WEBAPPS_PHP_FPM_DEFAULT_VERSION,
help_text=help_message)
class PHPFPMAppSerializer(serializers.Serializer):
php_version = serializers.ChoiceField(label=_("PHP version"),
choices=settings.WEBAPPS_PHP_FPM_VERSIONS,
default=settings.WEBAPPS_PHP_FPM_DEFAULT_VERSION,
help_text=help_message)
class PHPFPMApp(PHPAppType):
name = 'php-fpm'
php_execution = PHPAppType.FPM
verbose_name = "PHP FPM"
help_text = _("This creates a PHP application under ~/webapps/&lt;app_name&gt;<br>"
"PHP-FPM will be used to execute PHP files.")
icon = 'orchestra/icons/apps/PHPFPM.png'
form = PHPFPMAppForm
serializer = PHPFPMAppSerializer
def get_directive(self):
context = self.get_directive_context()
if self.is_fpm:
socket_type = 'unix'
if ':' in self.fpm_listen:
if ':' in self.FPM_LISTEN:
socket_type = 'tcp'
socket = self.fpm_listen % context
socket = self.FPM_LISTEN % context
return ('fpm', socket_type, socket, self.instance.get_path())
class PHPFCGIDAppForm(PluginDataForm):
php_version = forms.ChoiceField(label=_("PHP version"),
choices=settings.WEBAPPS_PHP_FCGID_VERSIONS,
initial=settings.WEBAPPS_PHP_FCGID_DEFAULT_VERSION,
help_text=help_message)
class PHPFCGIDAppSerializer(serializers.Serializer):
php_version = serializers.ChoiceField(label=_("PHP version"),
choices=settings.WEBAPPS_PHP_FCGID_VERSIONS,
default=settings.WEBAPPS_PHP_FCGID_DEFAULT_VERSION,
help_text=help_message)
class PHPFCGIDApp(PHPAppType):
name = 'php-fcgid'
php_execution = PHPAppType.FCGID
verbose_name = "PHP FCGID"
help_text = _("This creates a PHP application under ~/webapps/&lt;app_name&gt;<br>"
"Apache-mod-fcgid will be used to execute PHP files.")
icon = 'orchestra/icons/apps/PHPFCGI.png'
form = PHPFCGIDAppForm
serializer = PHPFCGIDAppSerializer
def get_directive(self):
context = self.get_directive_context()
wrapper_path = os.path.normpath(settings.WEBAPPS_FCGID_PATH % context)
elif self.is_fcgid:
wrapper_path = os.path.normpath(self.FCGID_WRAPPER_PATH % context)
return ('fcgid', self.instance.get_path(), wrapper_path)
else:
raise ValueError("Unknown directive for php version '%s'" % php_version)
def get_php_binary_path(self):
default_version = settings.WEBAPPS_PHP_FCGID_DEFAULT_VERSION
context = {
'php_version': self.instance.data.get('php_version', default_version)
}
return os.path.normpath(settings.WEBAPPS_PHP_CGI_BINARY_PATH % context)
def get_php_rc_path(self):
default_version = settings.WEBAPPS_PHP_FCGID_DEFAULT_VERSION
context = {
'php_version': self.instance.data.get('php_version', default_version)
}
return os.path.normpath(settings.WEBAPPS_PHP_CGI_RC_PATH % context)
def get_php_version(self):
default_version = self.DEFAULT_PHP_VERSION
return self.instance.data.get('php_version', default_version)
def get_php_version_number(self):
php_version = self.get_php_version()
number = re.findall(r'[0-9]+\.?[0-9]+', php_version)
if len(number) > 1:
raise ValueError("Multiple version number matches for '%'" % php_version)
return number[0]

View File

@ -9,11 +9,10 @@ from orchestra.utils.python import random_ascii
from .. import settings
from .php import (PHPAppType, PHPFCGIDApp, PHPFPMApp, PHPFCGIDAppForm, PHPFCGIDAppSerializer,
PHPFPMAppForm, PHPFPMAppSerializer)
from .php import PHPApp, PHPAppForm, PHPAppSerializer
class WordPressAbstractAppForm(PluginDataForm):
class WordPressAppForm(PHPAppForm):
db_name = forms.CharField(label=_("Database name"),
help_text=_("Database used for this webapp."))
db_user = forms.CharField(label=_("Database user"),)
@ -21,16 +20,20 @@ class WordPressAbstractAppForm(PluginDataForm):
help_text=_("Initial database password."))
class WordPressAbstractAppSerializer(serializers.Serializer):
class WordPressAppSerializer(PHPAppSerializer):
db_name = serializers.CharField(label=_("Database name"), required=False)
db_user = serializers.CharField(label=_("Database user"), required=False)
db_pass = serializers.CharField(label=_("Database user password"), required=False)
class WordPressAbstractApp(object):
icon = 'orchestra/icons/apps/WordPress.png'
class WordPressApp(PHPApp):
name = 'wordpress'
verbose_name = "WordPress"
serializer = WordPressAppSerializer
change_form = WordPressAppForm
change_readonly_fileds = ('db_name', 'db_user', 'db_pass',)
help_text = _("Visit http://&lt;domain.lan&gt;/wp-admin/install.php to finish the installation.")
icon = 'orchestra/icons/apps/WordPress.png'
def get_db_name(self):
db_name = 'wp_%s_%s' % (self.instance.name, self.instance.account)
@ -46,7 +49,7 @@ class WordPressAbstractApp(object):
return random_ascii(10)
def validate(self):
super(WordPressAbstractApp, self).validate()
super(WordPressApp, self).validate()
create = not self.instance.pk
if create:
db = Database(name=self.get_db_name(), account=self.instance.account)
@ -79,7 +82,7 @@ class WordPressAbstractApp(object):
else:
# Trigger related backends
for related in self.get_related():
related.save()
related.save(updated_fields=[])
def delete(self):
for related in self.get_related():
@ -101,23 +104,3 @@ class WordPressAbstractApp(object):
else:
related.append(db)
return related
class WordPressFPMApp(WordPressAbstractApp, PHPFPMApp):
name = 'wordpress-fpm'
php_execution = PHPAppType.FPM
verbose_name = "WordPress (FPM)"
serializer = type('WordPressFPMSerializer',
(WordPressAbstractAppSerializer, PHPFPMAppSerializer), {})
change_form = type('WordPressFPMForm',
(WordPressAbstractAppForm, PHPFPMAppForm), {})
class WordPressFCGIDApp(WordPressAbstractApp, PHPFCGIDApp):
name = 'wordpress-fcgid'
php_execution = PHPAppType.FCGID
verbose_name = "WordPress (FCGID)"
serializer = type('WordPressFCGIDSerializer',
(WordPressAbstractAppSerializer, PHPFCGIDAppSerializer), {})
change_form = type('WordPressFCGIDForm',
(WordPressAbstractAppForm, PHPFCGIDAppForm), {})

View File

@ -164,11 +164,11 @@ class Apache2Backend(ServiceController):
for rules in directives.get('sec_rule_remove', []):
for rule in rules.value.split():
config += "SecRuleRemoveById %i\n" % int(rule)
for modsecurity in directives.get('sec_rule_off', []):
for modsecurity in directives.get('sec_engine', []):
config += textwrap.dedent("""\
<Location %s>
SecRuleEngine off
</LocationMatch>
</Location>
""") % modsecurity
return config

View File

@ -11,6 +11,7 @@ from .. import settings
class WebalizerBackend(ServiceController):
verbose_name = _("Webalizer Content")
model = 'websites.Content'
default_route_match = "content.webapp.type == 'webalizer'"
def save(self, content):
context = self.get_context(content)

View File

@ -149,5 +149,5 @@ class SecEngine(SiteDirective):
name = 'sec_engine'
verbose_name = _("Modsecurity engine")
help_text = _("URL location for disabling modsecurity engine.")
regex = r'^[^ ]+$'
regex = r'^/[^ ]*$'
group = SiteDirective.SEC

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -1,63 +1,203 @@
<?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"
width="48px"
height="48px"
id="svg3109"
version="1.1"
width="300"
height="159"
viewBox="0 0 300 160"
id="svg2943">
inkscape:version="0.48.3.1 r9886"
sodipodi:docname="PHP.svg"
inkscape:export-filename="/home/glic3rinu/orchestra/django-orchestra/orchestra/static/orchestra/icons/apps/PHP.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90">
<defs
id="defs2945">
id="defs3111">
<linearGradient
x1="150"
y1="84"
x2="299"
y2="84"
id="linearGradient3798"
gradientUnits="userSpaceOnUse">
inkscape:collect="always"
xlink:href="#linearGradient4350"
id="linearGradient2623"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.972701,0,0,0.925949,-23.47387,-13.25574)"
x1="97.728783"
y1="54.517036"
x2="97.728783"
y2="42.400635" />
<linearGradient
id="linearGradient4350"
inkscape:collect="always">
<stop
id="stop3800"
style="stop-color:#dddce9;stop-opacity:1"
offset="0" />
id="stop4352"
offset="0"
style="stop-color:#ffffff;stop-opacity:1;" />
<stop
id="stop3802"
style="stop-color:#5664a3;stop-opacity:1"
offset="0.37" />
id="stop4354"
offset="1"
style="stop-color:#ffffff;stop-opacity:0;" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4286"
id="linearGradient2625"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-19,0.07465812)"
x1="88.75"
y1="22.673088"
x2="87.8125"
y2="45.579079" />
<linearGradient
id="linearGradient4286">
<stop
id="stop3804"
style="stop-color:#000000;stop-opacity:1"
offset="1" />
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop4288" />
<stop
style="stop-color:#ffffff;stop-opacity:0.30508474;"
offset="1"
id="stop4290" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4350"
id="linearGradient2627"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-26,-16.91315)"
x1="95.03125"
y1="57.906303"
x2="95.03125"
y2="44.592937" />
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient4114"
id="radialGradient17299"
gradientUnits="userSpaceOnUse"
gradientTransform="scale(1.64399,0.608276)"
cx="15.115514"
cy="63.965389"
fx="15.115514"
fy="63.965389"
r="12.289036" />
<linearGradient
id="linearGradient4114"
inkscape:collect="always">
<stop
id="stop4116"
offset="0"
style="stop-color:#000000;stop-opacity:1;" />
<stop
id="stop4118"
offset="1"
style="stop-color:#000000;stop-opacity:0;" />
</linearGradient>
<radialGradient
cx="77.914261"
cy="-48.544521"
r="146"
fx="77.914261"
fy="-48.544521"
id="radialGradient3870"
xlink:href="#linearGradient3798"
r="12.289036"
fy="63.965389"
fx="15.115514"
cy="63.965389"
cx="15.115514"
gradientTransform="scale(1.64399,0.608276)"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.5089497,0,0,1.3582164,-39.028917,76.957747)" />
id="radialGradient3107"
xlink:href="#linearGradient4114"
inkscape:collect="always" />
</defs>
<ellipse
cx="150"
cy="80"
rx="146"
ry="76"
id="ellipse3860"
style="fill:#6c7eb7;stroke:url(#radialGradient3870);stroke-width:5.5" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="7"
inkscape:cx="24"
inkscape:cy="24"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="1920"
inkscape:window-height="1024"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1" />
<metadata
id="metadata3114">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer">
<path
d="m 45,125 16,-81 37,0 c 16,1 24,9 24,23 0,24 -19,38 -36,37 l -18,0 -4,21 -19,0 z m 27,-36 5,-30 13,0 c 7,0 12,3 12,9 -1,17 -9,20 -18,21 l -12,0 z"
id="p"
style="fill-rule:evenodd;stroke:#ffffff;stroke-width:2;stroke-linejoin:round" />
transform="matrix(0.99462358,0,0,1.2365616,-1.8004407,-14.308795)"
d="m 45.052803,38.908627 a 20.203051,7.4751287 0 1 1 -40.4061012,0 20.203051,7.4751287 0 1 1 40.4061012,0 z"
sodipodi:ry="7.4751287"
sodipodi:rx="20.203051"
sodipodi:cy="38.908627"
sodipodi:cx="24.849752"
id="path4112"
style="opacity:0.83257919;fill:url(#radialGradient3107);fill-opacity:1;stroke:none;display:inline"
sodipodi:type="arc" />
<g
id="g2615"
transform="matrix(1.4117127,0,0,1.4117127,-76.234629,-24.714637)">
<path
d="m 116,104 16,-81 19,0 -4,21 18,0 c 16,1 22,9 20,19 l -7,41 -20,0 7,-37 c 1,-5 1,-8 -6,-8 l -15,0 -9,45 -19,0 z"
id="h"
style="stroke:#ffffff;stroke-width:2;stroke-linejoin:round" />
<use
transform="translate(134,0)"
id="p2"
xlink:href="#p" />
transform="matrix(0.945073,0,0,0.905454,-21.13524,5.644298)"
d="m 114.99324,33.06237 c 0,5.516156 -7.83542,9.987884 -17.500892,9.987884 -9.665476,0 -17.500893,-4.471728 -17.500893,-9.987884 0,-5.516155 7.835417,-9.987883 17.500893,-9.987883 9.665472,0 17.500892,4.471728 17.500892,9.987883 z"
sodipodi:ry="9.9878836"
sodipodi:rx="17.500893"
sodipodi:cy="33.06237"
sodipodi:cx="97.492348"
id="path19716"
style="fill:#5d65b4;fill-opacity:1;fill-rule:nonzero;stroke:#3d4384;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
sodipodi:type="arc" />
<path
sodipodi:type="arc"
style="fill:none;stroke:#9ca1d2;stroke-width:1.17854095;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
id="path19718"
sodipodi:cx="97.492348"
sodipodi:cy="33.06237"
sodipodi:rx="17.500893"
sodipodi:ry="9.9878836"
d="m 114.99324,33.06237 c 0,5.516156 -7.83542,9.987884 -17.500892,9.987884 -9.665476,0 -17.500893,-4.471728 -17.500893,-9.987884 0,-5.516155 7.835417,-9.987883 17.500893,-9.987883 9.665472,0 17.500892,4.471728 17.500892,9.987883 z"
transform="matrix(0.889513,0,0,0.809391,-15.71799,8.823718)" />
<path
inkscape:connector-curvature="0"
sodipodi:nodetypes="cscsscsc"
id="path19720"
d="m 70.665432,27.000328 c -8.770642,0.06413 -15.490098,3.890307 -15.734387,8.371883 -0.05012,0.919501 0.159559,1.78858 0.687905,2.617543 3.996698,-3.732848 10.440129,-2.778994 15.745611,-2.170193 5.324581,0.610992 11.669454,0.972529 15.313098,-2.145164 0.0072,-0.0062 -0.02725,-0.039 -0.01953,-0.04456 -1.516063,-3.804519 -8.019325,-6.629508 -15.776011,-6.629508 -0.06938,0 -0.147511,-4.57e-4 -0.216684,0 z"
style="opacity:0.55813952;fill:url(#linearGradient2623);fill-opacity:1;fill-rule:evenodd;stroke:none" />
<path
inkscape:connector-curvature="0"
id="path19724"
d="m 69.156251,29.324658 -1,5 c 0.08862,-1.599006 -1.095832,-2.75 -2.28125,-2.75 l -4.125,0 -1.625,8.46875 2.03125,0.03125 0.4375,-2.1875 2.1875,0 c 1.191119,0 2.622044,-0.936537 3.1875,-2.625 l -0.5,2.625 2.03125,0 0.90625,-4.75 1.28125,0 c 0.255223,0.002 0.466944,0.03366 0.625,0.09375 0.02953,0.01215 0.06869,0.04716 0.09375,0.0625 0.0048,0.0032 0.02661,-0.0033 0.03125,0 0.0044,0.0035 0.02699,0.02763 0.03125,0.03125 0.0041,0.0038 0.02738,0.02734 0.03125,0.03125 0.0035,0.0042 0.02799,0.02688 0.03125,0.03125 0.0031,0.0045 -0.0029,0.02656 0,0.03125 0.0026,0.0048 0.02882,0.02623 0.03125,0.03125 0.0022,0.0052 -0.002,0.0259 0,0.03125 0.0054,0.01657 0.02789,0.04433 0.03125,0.0625 0.0018,0.01248 -8.72e-4,0.04928 0,0.0625 4.07e-4,0.0136 5.36e-4,0.04812 0,0.0625 -10e-4,0.01477 0.002,0.04693 0,0.0625 -0.0037,0.02398 -0.02529,0.06788 -0.03125,0.09375 l -0.75,4.0625 2.0625,0 0.75,-4.09375 c 0.152547,-0.791704 -0.0036,-1.314355 -0.3125,-1.65625 -0.469323,-0.499058 -1.284638,-0.613618 -1.9375,-0.625 l -1.6875,0 0.4375,-2.1875 -1.96875,0 z m 6.4375,2.25 -1.625,8.46875 2.03125,0.03125 0.40625,-2.1875 2.21875,0 c 1.270525,0 2.80633,-1.058815 3.28125,-2.96875 0.47492,-1.909935 -0.8565,-3.34375 -2.1875,-3.34375 l -4.125,0 z m -12.125,1.53125 1.25,0 c 0.294812,10e-7 0.560277,0.04782 0.75,0.125 0.0352,0.01535 0.09356,0.04457 0.125,0.0625 0.03813,0.0232 0.09271,0.06668 0.125,0.09375 0.01618,0.01498 0.04775,0.04638 0.0625,0.0625 0.0048,0.0055 0.02665,0.02563 0.03125,0.03125 0.0044,0.0057 0.02696,0.02538 0.03125,0.03125 0.04136,0.05994 0.06748,0.146827 0.09375,0.21875 0.005,0.01462 0.02687,0.04742 0.03125,0.0625 0.0838,0.313843 0.04097,0.733356 -0.09375,1.21875 -0.08568,0.308687 -0.200401,0.565156 -0.34375,0.75 -0.01212,0.01502 -0.05004,0.04823 -0.0625,0.0625 -0.02526,0.02781 -0.0672,0.06882 -0.09375,0.09375 -0.01285,0.01124 -0.04926,0.05182 -0.0625,0.0625 -0.402786,0.31197 -0.983107,0.375 -1.75,0.375 -1.268714,-10e-7 -0.71875,0 -0.71875,0 l 0.625,-3.25 z m 13.84375,0 1.25,0 c 0.449239,10e-7 0.793344,0.107982 1,0.28125 0.01618,0.01498 0.04775,0.04638 0.0625,0.0625 0.05235,0.06048 0.0912,0.143519 0.125,0.21875 0.0058,0.01391 0.026,0.04812 0.03125,0.0625 0.005,0.01462 0.02687,0.04742 0.03125,0.0625 0.0838,0.313843 0.04097,0.733356 -0.09375,1.21875 -0.09638,0.347273 -0.23803,0.619815 -0.40625,0.8125 -0.01263,0.0139 -0.04954,0.04932 -0.0625,0.0625 -0.0066,0.0064 -0.02461,0.02502 -0.03125,0.03125 -0.01899,0.01729 -0.04265,0.04649 -0.0625,0.0625 -0.402785,0.31197 -0.983106,0.375 -1.75,0.375 -1.268712,-10e-7 -0.71875,0 -0.71875,0 l 0.625,-3.25 z"
style="opacity:0.69957084;fill:none;stroke:url(#linearGradient2625);stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<path
inkscape:connector-curvature="0"
id="path19732"
d="m 69.156251,29.324658 -1,5 c 0.08862,-1.599006 -1.095832,-2.75 -2.28125,-2.75 l -4.125,0 -1.625,8.46875 2.03125,0.03125 0.4375,-2.1875 2.1875,0 c 1.191119,0 2.622044,-0.936537 3.1875,-2.625 l -0.5,2.625 2.03125,0 0.90625,-4.75 1.28125,0 c 0.255223,0.002 0.466944,0.03366 0.625,0.09375 0.02953,0.01215 0.06869,0.04716 0.09375,0.0625 0.0048,0.0032 0.02661,-0.0033 0.03125,0 0.0044,0.0035 0.02699,0.02763 0.03125,0.03125 0.0041,0.0038 0.02738,0.02734 0.03125,0.03125 0.0035,0.0042 0.02799,0.02688 0.03125,0.03125 0.0031,0.0045 -0.0029,0.02656 0,0.03125 0.0026,0.0048 0.02882,0.02623 0.03125,0.03125 0.0022,0.0052 -0.002,0.0259 0,0.03125 0.0054,0.01657 0.02789,0.04433 0.03125,0.0625 0.0018,0.01248 -8.72e-4,0.04928 0,0.0625 4.07e-4,0.0136 5.36e-4,0.04812 0,0.0625 -10e-4,0.01477 0.002,0.04693 0,0.0625 -0.0037,0.02398 -0.02529,0.06788 -0.03125,0.09375 l -0.75,4.0625 2.0625,0 0.75,-4.09375 c 0.152547,-0.791704 -0.0036,-1.314355 -0.3125,-1.65625 -0.469323,-0.499058 -1.284638,-0.613618 -1.9375,-0.625 l -1.6875,0 0.4375,-2.1875 -1.96875,0 z m 6.4375,2.25 -1.625,8.46875 2.03125,0.03125 0.40625,-2.1875 2.21875,0 c 1.270525,0 2.80633,-1.058815 3.28125,-2.96875 0.47492,-1.909935 -0.8565,-3.34375 -2.1875,-3.34375 l -4.125,0 z m -12.125,1.53125 1.25,0 c 0.294812,10e-7 0.560277,0.04782 0.75,0.125 0.0352,0.01535 0.09356,0.04457 0.125,0.0625 0.03813,0.0232 0.09271,0.06668 0.125,0.09375 0.01618,0.01498 0.04775,0.04638 0.0625,0.0625 0.0048,0.0055 0.02665,0.02563 0.03125,0.03125 0.0044,0.0057 0.02696,0.02538 0.03125,0.03125 0.04136,0.05994 0.06748,0.146827 0.09375,0.21875 0.005,0.01462 0.02687,0.04742 0.03125,0.0625 0.0838,0.313843 0.04097,0.733356 -0.09375,1.21875 -0.08568,0.308687 -0.200401,0.565156 -0.34375,0.75 -0.01212,0.01502 -0.05004,0.04823 -0.0625,0.0625 -0.02526,0.02781 -0.0672,0.06882 -0.09375,0.09375 -0.01285,0.01124 -0.04926,0.05182 -0.0625,0.0625 -0.402786,0.31197 -0.983107,0.375 -1.75,0.375 -1.268714,-10e-7 -0.71875,0 -0.71875,0 l 0.625,-3.25 z m 13.84375,0 1.25,0 c 0.449239,10e-7 0.793344,0.107982 1,0.28125 0.01618,0.01498 0.04775,0.04638 0.0625,0.0625 0.05235,0.06048 0.0912,0.143519 0.125,0.21875 0.0058,0.01391 0.026,0.04812 0.03125,0.0625 0.005,0.01462 0.02687,0.04742 0.03125,0.0625 0.0838,0.313843 0.04097,0.733356 -0.09375,1.21875 -0.09638,0.347273 -0.23803,0.619815 -0.40625,0.8125 -0.01263,0.0139 -0.04954,0.04932 -0.0625,0.0625 -0.0066,0.0064 -0.02461,0.02502 -0.03125,0.03125 -0.01899,0.01729 -0.04265,0.04649 -0.0625,0.0625 -0.402785,0.31197 -0.983106,0.375 -1.75,0.375 -1.268712,-10e-7 -0.71875,0 -0.71875,0 l 0.625,-3.25 z"
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none" />
<path
inkscape:connector-curvature="0"
id="path19738"
d="m 69.156249,29.336846 -1,4.90625 c 0.04837,-1.559124 -1.114354,-2.65625 -2.28125,-2.65625 l -4.125,0 -0.78125,4.125 c 0.665889,-0.113935 1.33972,-0.182287 2.03125,-0.21875 l 0.46875,-2.375 1.25,0 c 0.292618,-1e-6 0.555977,0.04984 0.75,0.125 0.02468,0.01021 0.07098,0.01981 0.09375,0.03125 0.02949,0.0158 0.0678,0.04454 0.09375,0.0625 0.011,0.0051 0.05173,0.02597 0.0625,0.03125 0.0062,0.0055 0.02536,0.02535 0.03125,0.03125 0.0056,0.0062 0.02594,0.02473 0.03125,0.03125 0.02035,0.02685 0.04389,0.06678 0.0625,0.09375 0.0055,0.01088 0.02595,0.0515 0.03125,0.0625 0.007,0.01296 0.02481,0.04906 0.03125,0.0625 0.0057,0.0054 0.0256,0.02581 0.03125,0.03125 0.0083,0.02123 0.02425,0.07148 0.03125,0.09375 0.09193,0.316681 0.04426,0.752767 -0.09375,1.25 -0.04909,0.176853 -0.118279,0.334053 -0.1875,0.46875 0.715226,0.03477 1.41411,0.08707 2.125,0.15625 0.04337,-0.09783 0.08799,-0.177253 0.125,-0.28125 l -0.0625,0.28125 c 0.672502,0.0661 1.343624,0.171135 2,0.25 l 0.53125,-2.75 1.28125,0 c 0.255223,0.002 0.466944,0.03366 0.625,0.09375 0.0028,-8.5e-5 0.02807,3.12e-4 0.03125,0 0.01111,0.0054 0.05211,0.02538 0.0625,0.03125 0.0056,0.0056 0.02571,0.02558 0.03125,0.03125 0.01109,0.01048 0.05219,0.05143 0.0625,0.0625 0.0056,0.0052 0.0259,0.02586 0.03125,0.03125 0.0084,0.01591 0.02626,0.05084 0.03125,0.0625 -2.37e-4,0.0032 7.3e-5,0.02848 0,0.03125 0.0058,0.0055 0.02551,0.02573 0.03125,0.03125 2e-6,0.01085 2.25e-4,0.05147 0,0.0625 -8e-5,0.0061 -1.02e-4,0.02785 0,0.03125 -9.3e-5,0.02098 0.0022,0.07099 0,0.09375 -9.26e-4,0.0035 0.0013,0.02729 0,0.03125 -0.0051,0.01302 -0.02785,0.05141 -0.03125,0.0625 l -0.4375,2.34375 c 0.670609,0.07685 1.341434,0.159629 2.03125,0.21875 l 0.46875,-2.5625 c 0.11067,-0.574373 0.06008,-1.019277 -0.09375,-1.34375 -0.04739,-0.09474 -0.12356,-0.204443 -0.1875,-0.28125 -0.02374,-0.02737 -0.06813,-0.06856 -0.09375,-0.09375 -0.0098,-0.0092 -0.04608,-0.04615 -0.0625,-0.0625 -0.0075,-0.0063 -0.02365,-0.02509 -0.03125,-0.03125 -0.478833,-0.372345 -1.198721,-0.458593 -1.78125,-0.46875 l -1.6875,0 0.4375,-2.1875 -1.96875,0 z m 6.4375,2.25 -0.9375,4.8125 c 2.24771,0.171748 4.53955,0.198619 6.656252,-0.125 0.251,-0.380091 0.4676,-0.836424 0.59375,-1.34375 0.47492,-1.909933 -0.8565,-3.34375 -2.187502,-3.34375 l -4.125,0 z m 1.71875,1.53125 1.25,0 c 0.28077,-1e-6 0.53335,0.02353 0.71875,0.09375 0.03546,0.01592 0.09667,0.04737 0.125,0.0625 0.02358,0.01345 0.07228,0.04764 0.09375,0.0625 0.011,0.0051 0.05173,0.02597 0.0625,0.03125 0.003,0.0035 0.0286,0.02736 0.03125,0.03125 0.0069,0.01272 0.02336,0.05102 0.03125,0.0625 0.0056,0.0055 0.02569,0.02573 0.03125,0.03125 0.0056,0.0055 0.02567,0.02575 0.03125,0.03125 0.0055,0.01088 0.02595,0.0515 0.03125,0.0625 0.007,0.01296 0.02481,0.04906 0.03125,0.0625 0.14748,0.328282 0.12647,0.806734 -0.03125,1.375 -0.07853,0.282964 -0.18499,0.509551 -0.3125,0.6875 -0.0331,0.0444 -0.08385,0.109473 -0.125,0.15625 -0.01624,0.01759 -0.04756,0.04847 -0.0625,0.0625 -0.0063,0.0058 -0.02483,0.02563 -0.03125,0.03125 -0.40372,0.339333 -0.98879,0.40625 -1.78125,0.40625 -1.26871,10e-7 -0.71875,0 -0.71875,0 l 0.625,-3.25 z"
style="opacity:0.12217198;fill:url(#linearGradient2627);fill-opacity:1;fill-rule:evenodd;stroke:none" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -64,7 +64,7 @@ def naturaldatetime(date, include_seconds=False):
if days == 0:
if hours == 0:
if minutes > 0:
minutes += float(seconds)/60
minutes = float(seconds)/60
return ungettext(
_('{minutes:.1f} minute{ago}'),
_('{minutes:.1f} minutes{ago}'), minutes
@ -77,7 +77,7 @@ def naturaldatetime(date, include_seconds=False):
).format(seconds=seconds, ago=ago)
return _('just now')
else:
hours += float(minutes)/60
hours = float(minutes)/60
return ungettext(
_('{hours:.1f} hour{ago}'),
_('{hours:.1f} hours{ago}'), hours