diff --git a/TODO.md b/TODO.md index 91cae3f7..19ad5cae 100644 --- a/TODO.md +++ b/TODO.md @@ -175,9 +175,6 @@ require_once(‘/etc/moodles/’.$moodle_host.‘config.php’);``` moodle/drupl * allow empty metric pack for default rates? changes on rating algo # don't produce lines with cost == 0 or quantity 0 ? maybe minimal quantity for billing? like 0.1 ? or minimal price? per line or per bill? -# DOMINI REGISTRE MIGRATION SCRIPTS -# IMPORTANT delete domain xina: missing FROM-clause entry for table "t3" LINE 1: SELECT (CONCAT(T3.name, domains_domain.name)) AS "structured... - # lines too long on invoice, double lines or cut, and make margin wider * PHP_TIMEOUT env variable in sync with fcgid idle timeout http://foaa.de/old-blog/2010/11/php-apache-and-fastcgi-a-comprehensive-overview/trackback/index.html#pni-top0 @@ -188,8 +185,6 @@ require_once(‘/etc/moodles/’.$moodle_host.‘config.php’);``` moodle/drupl * TODO raise404, here and everywhere * update service orders on a celery task? because it take alot -# billline quantity eval('10x100') instead of miningless description '(10*100)' line.verbose_quantity - # FIXME do more test, make sure billed until doesn't get uodated whhen services are billed with les metric, and don't upgrade billed_until when undoing under this circumstances * line 513: change threshold and one time service metric change should update last value if not billed, only record for recurring invoicing. postpay services should store the last metric for pricing period. * add ini, end dates on bill lines and breakup quanity into size(defaut:1) and metric @@ -201,23 +196,14 @@ require_once(‘/etc/moodles/’.$moodle_host.‘config.php’);``` moodle/drupl * write down insights -# use english on services defs and so on, an translate them on render time - python3 manage.py dumpdata services.Service --indent 4 --natural - * websites directives get_location() and use it on last change view validation stage to compare with contents.location and also on the backend ? * modeladmin Default filter + search isn't working, prepend filter when searching * create service help templates based on urlqwargs with the most basic services. -# TDOO Base price: domini propi (all domains) + extra for other domains - -# IMPORTANT op.instance = copy.deepcopy(instance) ValueError: Cannot assign "": "SaaS" instance isn't saved in the database. -# Separate operation from models !! BackendOperation and Operation - Translation ----------- - mkdir locale django-admin.py makemessages -l ca django-admin.py compilemessages -l ca @@ -254,7 +240,7 @@ https://code.djangoproject.com/ticket/24576 # FIXME what to do when deleting accounts? set fk null and fill a username charfield? issues, invoices.. we whant all this to go away? * implement delete All related services -# FIXME address name change does not remove old one :P, readonly, perhaps we can regenerate all addresses using backend.prepare()? +# FIXME address name change does not remove old one :P, readonly or perhaps we can regenerate all addresses using backend.prepare()? * read https://docs.djangoproject.com/en/dev/releases/1.8/ and fix deprecation warnings * remove admin object display_links , like contents webapps @@ -281,7 +267,6 @@ https://code.djangoproject.com/ticket/24576 * MultiCHoiceField proper serialization # Apache restart fails: detect if appache running, and execute start -# Change crons, create cron for deleted webapps and users * UNIFY PHP FPM settings name # virtualhost name: name-account? * add a delay to changes on the webserver apache to no overwelm it with backend executions? @@ -293,5 +278,6 @@ https://code.djangoproject.com/ticket/24576 * rename resource.monitors to resource.backends ? * abstract model classes enabling overriding? -# Ignore superusers & co on billing +# Ignore superusers & co on billing: list filter doesn't work nor ignore detection # bill.totals make it 100% computed? +* joomla: wget https://github.com/joomla/joomla-cms/releases/download/3.4.1/Joomla_3.4.1-Stable-Full_Package.tar.gz -O - | tar xvfz - diff --git a/orchestra/admin/options.py b/orchestra/admin/options.py index 26348067..a70dd06f 100644 --- a/orchestra/admin/options.py +++ b/orchestra/admin/options.py @@ -14,6 +14,8 @@ from django.utils.html import escape from django.utils.translation import ugettext_lazy as _ from django.views.decorators.debug import sensitive_post_parameters +from ..utils.python import random_ascii + from .forms import AdminPasswordChangeForm #from django.contrib.auth.forms import AdminPasswordChangeForm from .utils import set_url_query, action_to_view @@ -243,6 +245,7 @@ class ChangePasswordAdminMixin(object): 'original': user, 'save_as': False, 'show_save': True, + 'password': random_ascii(10), } context.update(admin.site.each_context(request)) return TemplateResponse(request, diff --git a/orchestra/contrib/lists/backends.py b/orchestra/contrib/lists/backends.py index cf8dbde0..3eb577b0 100644 --- a/orchestra/contrib/lists/backends.py +++ b/orchestra/contrib/lists/backends.py @@ -144,7 +144,7 @@ class MailmanBackend(ServiceController): 'address_domain': mail_list.address_domain, 'address_regex': '\|'.join(self.addresses), 'admin': mail_list.admin_email, - 'mailman_root': settings.LISTS_MAILMAN_ROOT_PATH, + 'mailman_root': settings.LISTS_MAILMAN_ROOT_DIR, }) return replace(context, "'", '"') diff --git a/orchestra/contrib/lists/settings.py b/orchestra/contrib/lists/settings.py index 472f808b..5fb943f2 100644 --- a/orchestra/contrib/lists/settings.py +++ b/orchestra/contrib/lists/settings.py @@ -23,8 +23,8 @@ LISTS_MAILMAN_POST_LOG_PATH = getattr(settings, 'LISTS_MAILMAN_POST_LOG_PATH', ) -LISTS_MAILMAN_ROOT_PATH = getattr(settings, 'LISTS_MAILMAN_ROOT_PATH', - '/var/lib/mailman/' +LISTS_MAILMAN_ROOT_DIR = getattr(settings, 'LISTS_MAILMAN_ROOT_DIR', + '/var/lib/mailman' ) diff --git a/orchestra/contrib/mailboxes/backends.py b/orchestra/contrib/mailboxes/backends.py index 4278e056..b37324cf 100644 --- a/orchestra/contrib/mailboxes/backends.py +++ b/orchestra/contrib/mailboxes/backends.py @@ -406,7 +406,7 @@ class PostfixMailscannerTraffic(ServiceMonitor): except KeyError: pass else: - targets[req_id] = (username, None) + targets[req_id] = (username, 0) reverse[username].add(req_id) # Look for the mail size and count the number of recipients of each email else: @@ -438,7 +438,7 @@ class PostfixMailscannerTraffic(ServiceMonitor): except IOError as e: sys.stderr.write(e) - for username, opts in users.items(): + for username, opts in users.iteritems(): size = 0 for req_id in reverse[username]: size += targets[req_id][1] * counter.get(req_id, 0) diff --git a/orchestra/contrib/orchestration/admin.py b/orchestra/contrib/orchestration/admin.py index 955f8c3d..0c0d8c6d 100644 --- a/orchestra/contrib/orchestration/admin.py +++ b/orchestra/contrib/orchestration/admin.py @@ -23,13 +23,12 @@ STATE_COLORS = { } - class RouteAdmin(admin.ModelAdmin): - list_display = [ + list_display = ( 'backend', 'host', 'match', 'display_model', 'display_actions', 'is_active' - ] - list_editable = ['host', 'match', 'is_active'] - list_filter = ['host', 'is_active', 'backend'] + ) + list_editable = ('host', 'match', 'is_active') + list_filter = ('host', 'is_active', 'backend') ordering = ('backend',) BACKEND_HELP_TEXT = { diff --git a/orchestra/contrib/orchestration/models.py b/orchestra/contrib/orchestration/models.py index 14012d96..806c138c 100644 --- a/orchestra/contrib/orchestration/models.py +++ b/orchestra/contrib/orchestration/models.py @@ -149,24 +149,25 @@ class Route(models.Model): @classmethod def get_servers(cls, operation, **kwargs): cache = kwargs.get('cache', {}) + if not cache: + for route in cls.objects.filter(is_active=True).select_related('host'): + for action in route.backend_class.get_actions(): + key = (route.backend, action) + try: + cache[key].append(route) + except KeyError: + cache[key] = [route] servers = [] backend_cls = operation.backend key = (backend_cls.get_name(), operation.action) try: routes = cache[key] except KeyError: - cache[key] = [] - for route in cls.objects.filter(is_active=True, backend=backend_cls.get_name()): - for action in backend_cls.get_actions(): - _key = (route.backend, action) - try: - cache[_key].append(route) - except KeyError: - cache[_key] = [route] - routes = cache[key] - for route in routes: - if route.matches(operation.instance): - servers.append(route.host) + pass + else: + for route in routes: + if route.matches(operation.instance): + servers.append(route.host) return servers def clean(self): diff --git a/orchestra/contrib/plans/admin.py b/orchestra/contrib/plans/admin.py index e30dc1c9..80ba716d 100644 --- a/orchestra/contrib/plans/admin.py +++ b/orchestra/contrib/plans/admin.py @@ -27,6 +27,7 @@ class PlanAdmin(ExtendedModelAdmin): class ContractedPlanAdmin(AccountAdminMixin, admin.ModelAdmin): list_display = ('plan', 'account_link') list_filter = ('plan__name',) + list_select_related = ('plan', 'account') admin.site.register(Plan, PlanAdmin) diff --git a/orchestra/contrib/resources/admin.py b/orchestra/contrib/resources/admin.py index 6685490d..609d29d6 100644 --- a/orchestra/contrib/resources/admin.py +++ b/orchestra/contrib/resources/admin.py @@ -13,6 +13,7 @@ from orchestra.admin.utils import insertattr, get_modeladmin, admin_link, admin_ from orchestra.contrib.orchestration.models import Route from orchestra.core import services from orchestra.utils import database_ready +from orchestra.utils.functional import cached from .actions import run_monitor from .forms import ResourceForm @@ -179,12 +180,13 @@ admin.site.register(MonitorData, MonitorDataAdmin) def resource_inline_factory(resources): class ResourceInlineFormSet(generic.BaseGenericInlineFormSet): def total_form_count(self, resources=resources): - return len(resources) + return len(resources) + @cached def get_queryset(self): """ Filter disabled resources """ queryset = super(ResourceInlineFormSet, self).get_queryset() - return queryset.filter(resource__is_active=True) + return queryset.filter(resource__is_active=True).select_related('resource') @cached_property def forms(self, resources=resources): diff --git a/orchestra/contrib/services/handlers.py b/orchestra/contrib/services/handlers.py index a5390914..6ada4d4e 100644 --- a/orchestra/contrib/services/handlers.py +++ b/orchestra/contrib/services/handlers.py @@ -105,9 +105,10 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount): def get_ignore(self, instance): if self.ignore_superusers: account = getattr(instance, 'account', instance) - if (account.type in settings.SERVICES_IGNORE_ACCOUNT_TYPE or - 'superuser' in settings.SERVICES_IGNORE_ACCOUNT_TYPE): - return True + if account.type in settings.SERVICES_IGNORE_ACCOUNT_TYPE: + return True + if 'superuser' in settings.SERVICES_IGNORE_ACCOUNT_TYPE and account.is_superuser: + return True return False def get_metric(self, instance): diff --git a/orchestra/contrib/systemusers/backends.py b/orchestra/contrib/systemusers/backends.py index ffd50393..a4fe2d81 100644 --- a/orchestra/contrib/systemusers/backends.py +++ b/orchestra/contrib/systemusers/backends.py @@ -165,7 +165,7 @@ class Exim4Traffic(ServiceMonitor): except IOError as e: sys.stderr.write(e) - for username, opts in users.items(): + for username, opts in users.iteritems(): __, object_id, size = opts print object_id, size """).format(**context) diff --git a/orchestra/contrib/webapps/admin.py b/orchestra/contrib/webapps/admin.py index 19f53b16..d592f7a1 100644 --- a/orchestra/contrib/webapps/admin.py +++ b/orchestra/contrib/webapps/admin.py @@ -79,6 +79,11 @@ class WebAppAdmin(SelectPluginAdminMixin, AccountAdminMixin, ExtendedModelAdmin) return webapp.type_instance.get_detail() display_detail.short_description = _("detail") +# def get_form(self, request, obj=None, **kwargs): +# form = super(WebAppAdmin, self).get_form(request, obj, **kwargs) +# if obj: +# + # def formfield_for_dbfield(self, db_field, **kwargs): # """ Make value input widget bigger """ # if db_field.name == 'type': diff --git a/orchestra/contrib/webapps/api.py b/orchestra/contrib/webapps/api.py index b9d31381..4681413d 100644 --- a/orchestra/contrib/webapps/api.py +++ b/orchestra/contrib/webapps/api.py @@ -16,7 +16,7 @@ class WebAppViewSet(LogApiMixin, AccountApiMixin, viewsets.ModelViewSet): def metadata(self, request): ret = super(WebAppViewSet, self).metadata(request) names = [ - 'WEBAPPS_BASE_ROOT', 'WEBAPPS_TYPES', 'WEBAPPS_WEBAPP_OPTIONS', + 'WEBAPPS_BASE_DIR', 'WEBAPPS_TYPES', 'WEBAPPS_WEBAPP_OPTIONS', 'WEBAPPS_PHP_DISABLED_FUNCTIONS', 'WEBAPPS_DEFAULT_TYPE' ] ret['settings'] = { diff --git a/orchestra/contrib/webapps/backends/php.py b/orchestra/contrib/webapps/backends/php.py index fe86731d..72fdf5c6 100644 --- a/orchestra/contrib/webapps/backends/php.py +++ b/orchestra/contrib/webapps/backends/php.py @@ -99,7 +99,8 @@ class PHPBackend(WebAppServiceMixin, ServiceController): if [[ $UPDATED_FPM -eq 1 ]]; then service php5-fpm reload fi - # Coordinate apache restart with apache backend + # Coordinate apache restart with Apache2Backend + # FIXME race condition locked=1 state="$(grep -v 'PHPBackend' /dev/shm/restart.apache2)" || locked=0 echo -n "$state" > /dev/shm/restart.apache2 diff --git a/orchestra/contrib/webapps/backends/python.py b/orchestra/contrib/webapps/backends/python.py new file mode 100644 index 00000000..b69271b7 --- /dev/null +++ b/orchestra/contrib/webapps/backends/python.py @@ -0,0 +1,92 @@ +import os +import textwrap + +from django.template import Template, Context +from django.utils.translation import ugettext_lazy as _ + +from orchestra.contrib.orchestration import ServiceController, replace + +from . import WebAppServiceMixin +from .. import settings + + +class PythonBackend(WebAppServiceMixin, ServiceController): + verbose_name = _("Python uWSGI") + default_route_match = "webapp.type.endswith('python')" + + def save(self, webapp): + context = self.get_context(webapp) + self.create_webapp_dir(context) + self.set_under_construction(context) + self.save_uwsgi(webapp, context) + + def delete(self, webapp): + context = self.get_context(webapp) + self.delete_uwsgi(webapp, context) + self.delete_webapp_dir(context) + + def save_uwsgi(self, webapp, context): + self.append(textwrap.dedent("""\ + uwsgi_config='%(uwsgi_config)s' + { + echo -e "${uwsgi_config}" | diff -N -I'^\s*;;' %(uwsgi_path)s - + } || { + echo -e "${uwsgi_config}" > %(uwsgi_path)s + UPDATED_UWSGI=1 + } + ln -s %(uwsgi_path)s %(uwsgi_enabled)s + """) % context + ) + + def delete_uwsgi(self, webapp, context): + self.append("rm -f %(uwsgi_path)s" % context) + self.append("rm -f %(uwsgi_enabled)s" % context) + + def get_uwsgi_ini(self, context): + # TODO switch to this http://uwsgi-docs.readthedocs.org/en/latest/Emperor.html + # TODO http://uwsgi-docs.readthedocs.org/en/latest/Changelog-1.9.1.html#on-demand-vassals + return textwrap.dedent("""\ + [uwsgi] + plugins = python{python_version_number} + chdir = {app_path} + module = {app_name}.wsgi + chmod-socket = 660 + stats = /run/uwsgi/%(deb-confnamespace)/%(deb-confname)/statsocket + vacuum = true + uid = {user} + gid = {group} + env = HOME={home} + harakiri = {timeout} + max-requests = {max_requests} + + cheaper-algo = spare + cheaper = 1 + workers = {workers} + cheaper-step = 1 + cheaper-overload = 5""" + ).format(context) + + def update_uwsgi_context(self, webapp, context): + context.update({ + 'uwsgi_ini': self.get_uwsgi_ini(context), + 'uwsgi_dir': settings.WEBAPPS_UWSGI_BASE_DIR, + 'uwsgi_path': os.path.join(settings.WEBAPPS_UWSGI_BASE_DIR, + 'apps-available/%s.ini'% context['app_name']), + 'uwsgi_enabled': os.path.join(settings.WEBAPPS_UWSGI_BASE_DIR, + 'apps-enabled/%s.ini'% context['app_name']), + }) + return context + + def get_context(self, webapp): + context = super(PHPBackend, self).get_context(webapp) + options = webapp.get_options() + context.update({ + 'python_version': webapp.type_instance.get_python_version(), + 'python_version_number': webapp.type_instance.get_python_version_number(), + 'max_requests': settings.WEBAPPS_PYTHON_MAX_REQUESTS, + 'workers': options.get('processes', settings.WEBAPPS_PYTHON_DEFAULT_MAX_WORKERS), + 'timeout': options.get('timeout', settings.WEBAPPS_PYTHON_DEFAULT_TIMEOUT), + }) + self.update_uwsgi_context(webapp, context) + replace(context, "'", '"') + return context diff --git a/orchestra/contrib/webapps/models.py b/orchestra/contrib/webapps/models.py index bb132e20..1249c5aa 100644 --- a/orchestra/contrib/webapps/models.py +++ b/orchestra/contrib/webapps/models.py @@ -19,7 +19,8 @@ from .types import AppType class WebApp(models.Model): """ Represents a web application """ - name = models.CharField(_("name"), max_length=128, validators=[validators.validate_name]) + name = models.CharField(_("name"), max_length=128, validators=[validators.validate_name], + help_text=_("The app will be installed in %s") % settings.WEBAPPS_BASE_DIR) type = models.CharField(_("type"), max_length=32, choices=AppType.get_choices()) account = models.ForeignKey('accounts.Account', verbose_name=_("Account"), related_name='webapps') @@ -76,7 +77,7 @@ class WebApp(models.Model): 'home': self.get_user().get_home(), 'app_name': self.name, } - path = settings.WEBAPPS_BASE_ROOT % context + path = settings.WEBAPPS_BASE_DIR % context public_root = self.options.filter(name='public-root').first() if public_root: path = os.path.join(path, public_root.value) diff --git a/orchestra/contrib/webapps/settings.py b/orchestra/contrib/webapps/settings.py index a2fd8d3b..a5af0c90 100644 --- a/orchestra/contrib/webapps/settings.py +++ b/orchestra/contrib/webapps/settings.py @@ -3,8 +3,8 @@ from django.conf import settings from orchestra.settings import ORCHESTRA_BASE_DOMAIN -WEBAPPS_BASE_ROOT = getattr(settings, 'WEBAPPS_BASE_ROOT', - '%(home)s/webapps/%(app_name)s/' +WEBAPPS_BASE_DIR = getattr(settings, 'WEBAPPS_BASE_DIR', + '%(home)s/webapps/%(app_name)s' ) @@ -13,13 +13,15 @@ WEBAPPS_FPM_LISTEN = getattr(settings, 'WEBAPPS_FPM_LISTEN', '/opt/php/5.4/socks/%(user)s-%(app_name)s.sock' ) + WEBAPPS_FPM_DEFAULT_MAX_CHILDREN = getattr(settings, 'WEBAPPS_FPM_DEFAULT_MAX_CHILDREN', 3 ) WEBAPPS_PHPFPM_POOL_PATH = getattr(settings, 'WEBAPPS_PHPFPM_POOL_PATH', - '/etc/php5/fpm/pool.d/%(user)s-%(app_name)s.conf') + '/etc/php5/fpm/pool.d/%(user)s-%(app_name)s.conf' +) WEBAPPS_FCGID_WRAPPER_PATH = getattr(settings, 'WEBAPPS_FCGID_WRAPPER_PATH', @@ -59,6 +61,7 @@ WEBAPPS_TYPES = getattr(settings, 'WEBAPPS_TYPES', ( 'orchestra.contrib.webapps.types.misc.WebalizerApp', 'orchestra.contrib.webapps.types.misc.SymbolicLinkApp', 'orchestra.contrib.webapps.types.wordpress.WordPressApp', + 'orchestra.contrib.webapps.types.python.PythonApp', )) @@ -95,6 +98,36 @@ WEBAPPS_PHP_CGI_INI_SCAN_DIR = getattr(settings, 'WEBAPPS_PHP_CGI_INI_SCAN_DIR', ) +WEBAPPS_PYTHON_VERSIONS = getattr(settings, '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_UWSGI_SOCKET = getattr(settings, 'WEBAPPS_UWSGI_SOCKET', + '/var/run/uwsgi/app/%(app_name)s/socket' +) + + +WEBAPPS_PYTHON_MAX_REQUESTS = getattr(settings, 'WEBAPPS_PYTHON_MAX_REQUESTS', + 500 +) + + +WEBAPPS_PYTHON_DEFAULT_MAX_WORKERS = getattr(settings, 'WEBAPPS_PYTHON_DEFAULT_MAX_WORKERS', + 3 +) + + +WEBAPPS_PYTHON_DEFAULT_TIMEOUT = getattr(settings, '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 diff --git a/orchestra/contrib/webapps/types/misc.py b/orchestra/contrib/webapps/types/misc.py index e51e5f05..f94b879a 100644 --- a/orchestra/contrib/webapps/types/misc.py +++ b/orchestra/contrib/webapps/types/misc.py @@ -53,5 +53,3 @@ class SymbolicLinkApp(PHPApp): serializer = SymbolicLinkSerializer icon = 'orchestra/icons/apps/SymbolicLink.png' change_readonly_fileds = ('path',) - - diff --git a/orchestra/contrib/webapps/types/php.py b/orchestra/contrib/webapps/types/php.py index 69374370..e2bbb065 100644 --- a/orchestra/contrib/webapps/types/php.py +++ b/orchestra/contrib/webapps/types/php.py @@ -22,16 +22,16 @@ help_message = _("Version of PHP used to execute this webapp.
" 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) + 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) + choices=settings.WEBAPPS_PHP_VERSIONS, + default=settings.WEBAPPS_DEFAULT_PHP_VERSION, + help_text=help_message) class PHPApp(AppType): diff --git a/orchestra/contrib/webapps/types/python.py b/orchestra/contrib/webapps/types/python.py new file mode 100644 index 00000000..36eb0156 --- /dev/null +++ b/orchestra/contrib/webapps/types/python.py @@ -0,0 +1,61 @@ +import os +import re +from collections import OrderedDict + +from django import forms +from django.utils.translation import ugettext_lazy as _ +from rest_framework import serializers + +from orchestra.plugins.forms import PluginDataForm +from orchestra.utils.functional import cached + +from .. import settings +from ..options import AppOption + +from . import AppType + + +help_message = _("Version of Python used to execute this webapp.
" + "Changing the Python version may result in application malfunction, " + "make sure that everything continue to work as expected.") + + +class PythonAppForm(PluginDataForm): + python_version = forms.ChoiceField(label=_("Python version"), + choices=settings.WEBAPPS_PYTHON_VERSIONS, + initial=settings.WEBAPPS_DEFAULT_PYTHON_VERSION, + help_text=help_message) + + +class PythonAppSerializer(serializers.Serializer): + python_version = serializers.ChoiceField(label=_("Python version"), + choices=settings.WEBAPPS_PYTHON_VERSIONS, + default=settings.WEBAPPS_DEFAULT_PYTHON_VERSION, + help_text=help_message) + + +class PythonApp(AppType): + name = 'python' + verbose_name = "Python" + help_text = _("This creates a Python application under ~/webapps/<app_name>
") + form = PythonAppForm + serializer = PythonAppSerializer + option_groups = (AppOption.FILESYSTEM, AppOption.PROCESS) + icon = 'orchestra/icons/apps/Python.png' + + def get_directive(self): + context = self.get_directive_context() + return ('uwsgi', settings.WEBAPPS_UWSGI_SOCKET % context) + + def get_python_version(self): + default_version = self.DEFAULT_PYTHON_VERSION + return self.instance.data.get('python_version', default_version) + + def get_python_version_number(self): + python_version = self.get_python_version() + number = re.findall(r'[0-9]+\.?[0-9]?', python_version) + if not number: + raise ValueError("No version number matches for '%s'" % python_version) + if len(number) > 1: + raise ValueError("Multiple version number matches for '%s'" % python_version) + return number[0] diff --git a/orchestra/contrib/webapps/types/wordpress.py b/orchestra/contrib/webapps/types/wordpress.py index 20394a5e..0b55f546 100644 --- a/orchestra/contrib/webapps/types/wordpress.py +++ b/orchestra/contrib/webapps/types/wordpress.py @@ -7,7 +7,9 @@ class WordPressApp(CMSApp): name = 'wordpress-php' verbose_name = "WordPress" help_text = _( - "Visit http://<domain.lan>/wp-admin/install.php to finish the installation.
" - "A database and database user will automatically be created for this webapp." + "This installs the latest version of WordPress into the webapp directory.
" + "A database and database user will automatically be created for this webapp.
" + "This installer creates a user 'admin' with a randomly generated password.
" + "The password will be visible in the 'password' field after the installer has finished." ) icon = 'orchestra/icons/apps/WordPress.png' diff --git a/orchestra/contrib/websites/backends/apache.py b/orchestra/contrib/websites/backends/apache.py index 4ad78ee6..a180b2eb 100644 --- a/orchestra/contrib/websites/backends/apache.py +++ b/orchestra/contrib/websites/backends/apache.py @@ -216,6 +216,15 @@ class Apache2Backend(ServiceController): (context['location'], directives), ] + def get_uwsgi_directives(self, context, socket): + # requires apache2 mod_proxy_uwsgi + context['socket'] = socket + directives = "ProxyPass / unix:%(socket)s|uwsgi://" % context + directives += self.get_location_filesystem_map(context) + return [ + (context['location'], directives), + ] + def get_ssl(self, directives): cert = directives.get('ssl-cert') key = directives.get('ssl-key') diff --git a/orchestra/plugins/forms.py b/orchestra/plugins/forms.py index c9d7780d..5528f7eb 100644 --- a/orchestra/plugins/forms.py +++ b/orchestra/plugins/forms.py @@ -13,12 +13,20 @@ class PluginDataForm(forms.ModelForm): value = self.plugin.get_name() display = '%s change' % force_text(self.plugin.verbose_name) self.fields[self.plugin_field].widget = ReadOnlyWidget(value, display) - self.fields[self.plugin_field].help_text = getattr(self.plugin, 'help_text', '') + help_text = self.fields[self.plugin_field].help_text + self.fields[self.plugin_field].help_text = getattr(self.plugin, 'help_text', help_text) if self.instance: for field in self.declared_fields: initial = self.fields[field].initial self.fields[field].initial = self.instance.data.get(field, initial) if self.instance.pk: + # Admin Readonly fields are not availeble in self.fields, so we use Meta + plugin = getattr(self.instance, '%s_class' % self.plugin_field) + plugin_help_text = getattr(plugin, 'help_text', '') + model_help_text = self.instance._meta.get_field_by_name(self.plugin_field)[0].help_text + self._meta.help_texts = { + self.plugin_field: plugin_help_text or model_help_text + } for field in self.plugin.get_change_readonly_fileds(): value = getattr(self.instance, field, None) or self.instance.data[field] display = value diff --git a/orchestra/static/orchestra/icons/apps/DokuWikiMu.png b/orchestra/static/orchestra/icons/apps/DokuWikiMu.png deleted file mode 100644 index 38d91789..00000000 Binary files a/orchestra/static/orchestra/icons/apps/DokuWikiMu.png and /dev/null differ diff --git a/orchestra/static/orchestra/icons/apps/DokuWikiMu.svg b/orchestra/static/orchestra/icons/apps/DokuWikiMu.svg deleted file mode 100644 index 02282eed..00000000 --- a/orchestra/static/orchestra/icons/apps/DokuWikiMu.svg +++ /dev/null @@ -1,630 +0,0 @@ - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/orchestra/static/orchestra/icons/apps/DrupalMu.png b/orchestra/static/orchestra/icons/apps/DrupalMu.png deleted file mode 100644 index b8ca4053..00000000 Binary files a/orchestra/static/orchestra/icons/apps/DrupalMu.png and /dev/null differ diff --git a/orchestra/static/orchestra/icons/apps/DrupalMu.svg b/orchestra/static/orchestra/icons/apps/DrupalMu.svg deleted file mode 100644 index 11b394f2..00000000 --- a/orchestra/static/orchestra/icons/apps/DrupalMu.svg +++ /dev/null @@ -1,160 +0,0 @@ - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/orchestra/static/orchestra/icons/apps/MoodleMu.png b/orchestra/static/orchestra/icons/apps/MoodleMu.png deleted file mode 100644 index 3881b0ee..00000000 Binary files a/orchestra/static/orchestra/icons/apps/MoodleMu.png and /dev/null differ diff --git a/orchestra/static/orchestra/icons/apps/MoodleMu.svg b/orchestra/static/orchestra/icons/apps/MoodleMu.svg deleted file mode 100644 index 3493ba5b..00000000 --- a/orchestra/static/orchestra/icons/apps/MoodleMu.svg +++ /dev/null @@ -1,538 +0,0 @@ - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/orchestra/static/orchestra/icons/apps/PHPFCGI.png b/orchestra/static/orchestra/icons/apps/PHPFCGI.png deleted file mode 100644 index 02ea63ca..00000000 Binary files a/orchestra/static/orchestra/icons/apps/PHPFCGI.png and /dev/null differ diff --git a/orchestra/static/orchestra/icons/apps/PHPFCGI.svg b/orchestra/static/orchestra/icons/apps/PHPFCGI.svg deleted file mode 100644 index 677aac42..00000000 --- a/orchestra/static/orchestra/icons/apps/PHPFCGI.svg +++ /dev/null @@ -1,182 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - FCGI - diff --git a/orchestra/static/orchestra/icons/apps/PHPFPM.png b/orchestra/static/orchestra/icons/apps/PHPFPM.png deleted file mode 100644 index 71d3fca7..00000000 Binary files a/orchestra/static/orchestra/icons/apps/PHPFPM.png and /dev/null differ diff --git a/orchestra/static/orchestra/icons/apps/PHPFPM.svg b/orchestra/static/orchestra/icons/apps/PHPFPM.svg deleted file mode 100644 index 82cd01ea..00000000 --- a/orchestra/static/orchestra/icons/apps/PHPFPM.svg +++ /dev/null @@ -1,182 +0,0 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - FPM - diff --git a/orchestra/static/orchestra/icons/apps/Python.png b/orchestra/static/orchestra/icons/apps/Python.png new file mode 100644 index 00000000..fe1be277 Binary files /dev/null and b/orchestra/static/orchestra/icons/apps/Python.png differ diff --git a/orchestra/static/orchestra/icons/apps/WordPressMu.png b/orchestra/static/orchestra/icons/apps/WordPressMu.png deleted file mode 100644 index dc604112..00000000 Binary files a/orchestra/static/orchestra/icons/apps/WordPressMu.png and /dev/null differ diff --git a/orchestra/static/orchestra/icons/apps/WordPressMu.svg b/orchestra/static/orchestra/icons/apps/WordPressMu.svg deleted file mode 100644 index aa829da5..00000000 --- a/orchestra/static/orchestra/icons/apps/WordPressMu.svg +++ /dev/null @@ -1,127 +0,0 @@ - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/orchestra/templates/admin/orchestra/change_password.html b/orchestra/templates/admin/orchestra/change_password.html index dc5abddd..b35c8f9a 100644 --- a/orchestra/templates/admin/orchestra/change_password.html +++ b/orchestra/templates/admin/orchestra/change_password.html @@ -8,7 +8,7 @@
{% if is_popup %}{% endif %} {% if to_field %}{% endif %} -

{% blocktrans with username=original %}Enter a new password for the user {{ username }}.{% endblocktrans %}

+

{% blocktrans with username=original %}Enter a new password for the user {{ username }}, suggestion '{{ password }}'.{% endblocktrans %}

{% if errors %}

diff --git a/orchestra/urls.py b/orchestra/urls.py index 455d2d9c..74cc45ae 100644 --- a/orchestra/urls.py +++ b/orchestra/urls.py @@ -20,9 +20,10 @@ urlpatterns = patterns('', name='api-token-auth' ), # TODO make this private - url(r'^media/(?P.*)$', 'django.views.static.serve', - {'document_root': settings.MEDIA_ROOT, 'show_indexes': True} - ) + url(r'^media/(?P.*)$', 'django.views.static.serve', { + 'document_root': settings.MEDIA_ROOT, + 'show_indexes': True + }) )