diff --git a/TODO.md b/TODO.md index b39e84ac..27d2c50b 100644 --- a/TODO.md +++ b/TODO.md @@ -266,7 +266,6 @@ https://code.djangoproject.com/ticket/24576 * MultiCHoiceField proper serialization -# Apache restart fails: detect if appache running, and execute start * 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? @@ -278,6 +277,7 @@ https://code.djangoproject.com/ticket/24576 * rename resource.monitors to resource.backends ? * abstract model classes that enabling overriding, and ORCHESTRA_DATABASE_MODEL settings + orchestra.get_database_model() instead of explicitly importing from orchestra.contrib.databases.models import Database.. (Admin and REST API are fucked then?) +# billing order list filter detect metrics that are greater from those of billing_date # 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 - @@ -285,7 +285,5 @@ https://code.djangoproject.com/ticket/24576 # replace multichoicefield and jsonfield by ArrayField, HStoreField # Amend lines??? -# Add icon on select contact view - # Determine the difference between data serializer used for validation and used for the rest API! # Make PluginApiView that fills metadata and other stuff like modeladmin plugin support diff --git a/orchestra/contrib/accounts/admin.py b/orchestra/contrib/accounts/admin.py index f6952a79..8f96b9e4 100644 --- a/orchestra/contrib/accounts/admin.py +++ b/orchestra/contrib/accounts/admin.py @@ -122,12 +122,13 @@ class AccountListAdmin(AccountAdmin): # TODO get query string from request.META['QUERY_STRING'] to preserve filters context = { 'url': '../?account=' + str(instance.pk), - 'name': instance.username + 'name': instance.username, + 'plus': '+', } - return '%(name)s' % context + return _('%(plus)s Add to %(name)s') % context select_account.short_description = _("account") select_account.allow_tags = True - select_account.order_admin_field = 'username' + select_account.admin_order_field = 'username' def changelist_view(self, request, extra_context=None): original_app_label = request.META['PATH_INFO'].split('/')[-5] diff --git a/orchestra/contrib/databases/admin.py b/orchestra/contrib/databases/admin.py index df02e8f0..6d90ede1 100644 --- a/orchestra/contrib/databases/admin.py +++ b/orchestra/contrib/databases/admin.py @@ -14,7 +14,7 @@ from .models import Database, DatabaseUser class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin): list_display = ('name', 'type', 'display_users', 'account_link') list_filter = ('type',) - search_fields = ['name'] + search_fields = ('name', 'account__username') change_readonly_fields = ('name', 'type') extra = 1 fieldsets = ( @@ -71,7 +71,7 @@ class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin): class DatabaseUserAdmin(SelectAccountAdminMixin, ChangePasswordAdminMixin, ExtendedModelAdmin): list_display = ('username', 'type', 'display_databases', 'account_link') list_filter = ('type',) - search_fields = ['username'] + search_fields = ('username', 'account__username') form = DatabaseUserChangeForm add_form = DatabaseUserCreationForm change_readonly_fields = ('username', 'type') diff --git a/orchestra/contrib/databases/backends.py b/orchestra/contrib/databases/backends.py index d076f15e..8c216b61 100644 --- a/orchestra/contrib/databases/backends.py +++ b/orchestra/contrib/databases/backends.py @@ -10,14 +10,14 @@ from . import settings class MySQLBackend(ServiceController): """ - Simple backend for creating MySQL databases using `CREATE DATABASE` statement. - DATABASES_DEFAULT_HOST = %s + Simple backend for creating MySQL databases using CREATE DATABASE statement. """ - format_docstring = (settings.DATABASES_DEFAULT_HOST,) - verbose_name = "MySQL database" model = 'databases.Database' default_route_match = "database.type == 'mysql'" + doc_settings = (settings, + ('DATABASES_DEFAULT_HOST',) + ) def save(self, database): if database.type != database.MYSQL: @@ -61,13 +61,14 @@ class MySQLBackend(ServiceController): class MySQLUserBackend(ServiceController): """ - Simple backend for creating MySQL users using `CREATE USER` statement. - DATABASES_DEFAULT_HOST = %s - """ % settings.DATABASES_DEFAULT_HOST - + Simple backend for creating MySQL users using CREATE USER statement. + """ verbose_name = "MySQL user" model = 'databases.DatabaseUser' default_route_match = "databaseuser.type == 'mysql'" + doc_settings = (settings, + ('DATABASES_DEFAULT_HOST',) + ) def save(self, user): if user.type != user.MYSQL: @@ -105,7 +106,7 @@ class MySQLUserBackend(ServiceController): class MysqlDisk(ServiceMonitor): """ - du -bs + du -bs <database_path> Implements triggers for resource limit exceeded and recovery, disabling insert and create privileges. """ model = 'databases.Database' diff --git a/orchestra/contrib/domains/admin.py b/orchestra/contrib/domains/admin.py index bb910d17..bd0fbb4e 100644 --- a/orchestra/contrib/domains/admin.py +++ b/orchestra/contrib/domains/admin.py @@ -67,7 +67,7 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin): inlines = [RecordInline, DomainInline] list_filter = [TopDomainListFilter] change_readonly_fields = ('name',) - search_fields = ['name',] + search_fields = ('name', 'account__username') add_form = BatchDomainCreationAdminForm change_view_actions = [view_zone] diff --git a/orchestra/contrib/domains/backends.py b/orchestra/contrib/domains/backends.py index 35e4848d..e46aacb2 100644 --- a/orchestra/contrib/domains/backends.py +++ b/orchestra/contrib/domains/backends.py @@ -14,14 +14,8 @@ class Bind9MasterDomainBackend(ServiceController): """ Bind9 zone and config generation. It auto-discovers slave Bind9 servers based on your routing configuration or you can use DOMAINS_SLAVES to explicitly configure the slaves. - DOMAINS_SLAVES = %s - DOMAINS_MASTERS_PATH = '%s' """ - - format_docstring = ( - str(settings.DOMAINS_SLAVES), - settings.DOMAINS_MASTERS_PATH, - ) + CONF_PATH = settings.DOMAINS_MASTERS_PATH verbose_name = _("Bind9 master domain") model = 'domains.Domain' @@ -30,7 +24,9 @@ class Bind9MasterDomainBackend(ServiceController): ('domains.Domain', 'origin'), ) ignore_fields = ['serial'] - CONF_PATH = settings.DOMAINS_MASTERS_PATH + doc_settings = (settings, + ('DOMAINS_SLAVES', 'DOMAINS_MASTERS_PATH') + ) @classmethod def is_main(cls, obj): @@ -136,15 +132,16 @@ class Bind9SlaveDomainBackend(Bind9MasterDomainBackend): """ Generate the configuartion for slave servers It auto-discover the master server based on your routing configuration or you can use DOMAINS_MASTERS to explicitly configure the master. - DOMAINS_MASTERS = %s - """ % str(settings.DOMAINS_MASTERS) + """ + CONF_PATH = settings.DOMAINS_SLAVES_PATH verbose_name = _("Bind9 slave domain") related_models = ( ('domains.Domain', 'origin'), ) - CONF_PATH = settings.DOMAINS_SLAVES_PATH - + doc_settings = (settings, + ('DOMAINS_MASTERS', 'DOMAINS_SLAVES_PATH') + ) def save(self, domain): context = self.get_context(domain) self.update_conf(context) diff --git a/orchestra/contrib/issues/admin.py b/orchestra/contrib/issues/admin.py index cc8bc631..4a4f097b 100644 --- a/orchestra/contrib/issues/admin.py +++ b/orchestra/contrib/issues/admin.py @@ -42,8 +42,8 @@ class MessageReadOnlyInline(admin.TabularInline): model = Message extra = 0 can_delete = False - fields = ['content_html'] - readonly_fields = ['content_html'] + fields = ('content_html',) + readonly_fields = ('content_html',) class Media: css = { @@ -79,7 +79,7 @@ class MessageInline(admin.TabularInline): max_num = 1 form = MessageInlineForm can_delete = False - fields = ['content'] + fields = ('content',) def get_formset(self, request, obj=None, **kwargs): """ hook request.user on the inline form """ @@ -93,14 +93,14 @@ class MessageInline(admin.TabularInline): class TicketInline(admin.TabularInline): - fields = [ + fields = ( 'ticket_id', 'subject', 'creator_link', 'owner_link', 'colored_state', 'colored_priority', 'created', 'updated' - ] - readonly_fields = [ + ) + readonly_fields = ( 'ticket_id', 'subject', 'creator_link', 'owner_link', 'colored_state', 'colored_priority', 'created', 'updated' - ] + ) model = Ticket extra = 0 max_num = 0 @@ -119,35 +119,35 @@ class TicketInline(admin.TabularInline): class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin): - list_display = [ + list_display = ( 'unbold_id', 'bold_subject', 'display_creator', 'display_owner', 'display_queue', 'display_priority', 'display_state', 'updated' - ] + ) list_display_links = ('unbold_id', 'bold_subject') - list_filter = [ + list_filter = ( MyTicketsListFilter, 'queue__name', 'priority', TicketStateListFilter, - ] + ) default_changelist_filters = ( ('my_tickets', lambda r: 'True' if not r.user.is_superuser else 'False'), ('state', 'OPEN') ) date_hierarchy = 'created_at' - search_fields = [ + search_fields = ( 'id', 'subject', 'creator__username', 'creator__email', 'queue__name', 'owner__username' - ] - actions = [ + ) + actions = ( mark_as_unread, mark_as_read, 'delete_selected', reject_tickets, resolve_tickets, close_tickets, take_tickets - ] - sudo_actions = ['delete_selected'] - change_view_actions = [ + ) + sudo_actions = ('delete_selected',) + change_view_actions = ( resolve_tickets, close_tickets, reject_tickets, take_tickets - ] + ) # change_form_template = "admin/orchestra/change_form.html" form = TicketForm - add_inlines = [] - inlines = [ MessageReadOnlyInline, MessageInline ] + add_inlines = () + inlines = (MessageReadOnlyInline, MessageInline) readonly_fields = ( 'display_summary', 'display_queue', 'display_owner', 'display_state', 'display_priority' @@ -286,10 +286,10 @@ class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin): class QueueAdmin(admin.ModelAdmin): - list_display = ['name', 'default', 'num_tickets'] - actions = [set_default_queue] - inlines = [TicketInline] - ordering = ['name'] + list_display = ('name', 'default', 'num_tickets') + actions = (set_default_queue,) + inlines = (TicketInline,) + ordering = ('name',) class Media: css = { diff --git a/orchestra/contrib/lists/backends.py b/orchestra/contrib/lists/backends.py index 1ffa5dc0..a506ab3d 100644 --- a/orchestra/contrib/lists/backends.py +++ b/orchestra/contrib/lists/backends.py @@ -11,18 +11,8 @@ from .models import List class MailmanBackend(ServiceController): """ - Mailman backend based on `newlist`, it handles custom domains. - LISTS_VIRTUAL_ALIAS_PATH = '%s' - LISTS_VIRTUAL_ALIAS_DOMAINS_PATH = '%s' - LISTS_DEFAULT_DOMAIN = '%s' - LISTS_MAILMAN_ROOT_DIR = '%s' + Mailman 2 backend based on newlist, it handles custom domains. """ - format_docstring = ( - settings.LISTS_VIRTUAL_ALIAS_PATH, - settings.LISTS_VIRTUAL_ALIAS_DOMAINS_PATH, - settings.LISTS_DEFAULT_DOMAIN, - settings.LISTS_MAILMAN_ROOT_DIR, - ) verbose_name = "Mailman" model = 'lists.List' addresses = [ @@ -37,6 +27,12 @@ class MailmanBackend(ServiceController): '-subscribe', '-unsubscribe' ] + doc_settings = (settings, ( + 'LISTS_VIRTUAL_ALIAS_PATH', + 'LISTS_VIRTUAL_ALIAS_DOMAINS_PATH', + 'LISTS_DEFAULT_DOMAIN', + 'LISTS_MAILMAN_ROOT_DIR' + )) def include_virtual_alias_domain(self, context): if context['address_domain']: @@ -165,16 +161,15 @@ class MailmanBackend(ServiceController): class MailmanTraffic(ServiceMonitor): """ - Parses mailman log file looking for email size and multiples it by `list_members` count. - LISTS_MAILMAN_POST_LOG_PATH = '%s' + Parses mailman log file looking for email size and multiples it by list_members count. """ - format_docstring = ( - settings.LISTS_MAILMAN_POST_LOG_PATH, - ) model = 'lists.List' resource = ServiceMonitor.TRAFFIC verbose_name = _("Mailman traffic") script_executable = '/usr/bin/python' + doc_settings = (settings, + ('LISTS_MAILMAN_POST_LOG_PATH',) + ) def prepare(self): postlog = settings.LISTS_MAILMAN_POST_LOG_PATH @@ -272,7 +267,7 @@ class MailmanTraffic(ServiceMonitor): class MailmanSubscribers(ServiceMonitor): """ - Monitors number of list subscribers via `list_members` + Monitors number of list subscribers via list_members """ model = 'lists.List' verbose_name = _("Mailman subscribers") diff --git a/orchestra/contrib/mailboxes/admin.py b/orchestra/contrib/mailboxes/admin.py index 58457646..af0e111d 100644 --- a/orchestra/contrib/mailboxes/admin.py +++ b/orchestra/contrib/mailboxes/admin.py @@ -3,6 +3,8 @@ from urllib.parse import parse_qs from django import forms from django.contrib import admin +from django.db.models import F, Value as V +from django.db.models.functions import Concat from django.utils.translation import ugettext_lazy as _ from orchestra.admin import ExtendedModelAdmin, ChangePasswordAdminMixin @@ -104,13 +106,15 @@ class MailboxAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedMo class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin): list_display = ( - 'email', 'account_link', 'domain_link', 'display_mailboxes', 'display_forward', + 'display_email', 'account_link', 'domain_link', 'display_mailboxes', 'display_forward', ) list_filter = (HasMailboxListFilter, HasForwardListFilter) fields = ('account_link', 'email_link', 'mailboxes', 'forward') add_fields = ('account_link', ('name', 'domain'), 'mailboxes', 'forward') inlines = [AutoresponseInline] - search_fields = ('name', 'domain__name', 'forward', 'mailboxes__name', 'account__username') + search_fields = ( + 'name', 'domain__name', 'forward', 'mailboxes__name', 'account__username', 'computed_email' + ) readonly_fields = ('account_link', 'domain_link', 'email_link') filter_by_account_fields = ('domain', 'mailboxes') filter_horizontal = ['mailboxes'] @@ -119,6 +123,11 @@ class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin): domain_link = admin_link('domain', order='domain__name') + def display_email(self, address): + return address.computed_email + display_email.short_description = _("Email") + display_email.admin_order_field = 'computed_email' + def email_link(self, address): link = self.domain_link(address) return "%s@%s" % (address.name, link) @@ -153,6 +162,10 @@ class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin): fields = list(fields) fields.remove('mailboxes') return fields + + def get_queryset(self, request): + qs = super(AddressAdmin, self).get_queryset(request) + return qs.annotate(computed_email=Concat(F('name'), V('@'), F('domain__name'))) admin.site.register(Mailbox, MailboxAdmin) diff --git a/orchestra/contrib/mailboxes/backends.py b/orchestra/contrib/mailboxes/backends.py index bdf61131..fe4963b8 100644 --- a/orchestra/contrib/mailboxes/backends.py +++ b/orchestra/contrib/mailboxes/backends.py @@ -185,21 +185,15 @@ class DovecotPostfixPasswdVirtualUserBackend(ServiceController): class PostfixAddressBackend(ServiceController): """ Addresses based on Postfix virtual alias domains. - MAILBOXES_LOCAL_DOMAIN = '%s' - MAILBOXES_VIRTUAL_ALIAS_DOMAINS_PATH = '%s' - MAILBOXES_VIRTUAL_ALIAS_MAPS_PATH = '%s' """ - format_docstring = ( - settings.MAILBOXES_LOCAL_DOMAIN, - settings.MAILBOXES_VIRTUAL_ALIAS_DOMAINS_PATH, - settings.MAILBOXES_VIRTUAL_ALIAS_MAPS_PATH, - ) verbose_name = _("Postfix address") model = 'mailboxes.Address' related_models = ( ('mailboxes.Mailbox', 'addresses'), ) - + doc_settings = (settings, + ('MAILBOXES_LOCAL_DOMAIN', 'MAILBOXES_VIRTUAL_ALIAS_DOMAINS_PATH', 'MAILBOXES_VIRTUAL_ALIAS_MAPS_PATH',) + ) def include_virtual_alias_domain(self, context): if context['domain'] != context['local_domain']: self.append(textwrap.dedent(""" @@ -296,15 +290,13 @@ class DovecotMaildirDisk(ServiceMonitor): """ Maildir disk usage based on Dovecot maildirsize file http://wiki2.dovecot.org/Quota/Maildir - - MAILBOXES_MAILDIRSIZE_PATH = '%s' """ - format_docstring = ( - settings.MAILBOXES_MAILDIRSIZE_PATH, - ) model = 'mailboxes.Mailbox' resource = ServiceMonitor.DISK verbose_name = _("Dovecot Maildir size") + doc_settings = (settings, + ('MAILBOXES_MAILDIRSIZE_PATH',) + ) def prepare(self): super(DovecotMaildirDisk, self).prepare() @@ -331,15 +323,14 @@ class PostfixMailscannerTraffic(ServiceMonitor): """ A high-performance log parser. Reads the mail.log file only once, for all users. - MAILBOXES_MAIL_LOG_PATH = '%s' """ - format_docstring = ( - settings.MAILBOXES_MAIL_LOG_PATH, - ) model = 'mailboxes.Mailbox' resource = ServiceMonitor.TRAFFIC verbose_name = _("Postfix-Mailscanner traffic") script_executable = '/usr/bin/python' + doc_settings = (settings, + ('MAILBOXES_MAIL_LOG_PATH',) + ) def prepare(self): mail_log = settings.MAILBOXES_MAIL_LOG_PATH diff --git a/orchestra/contrib/miscellaneous/admin.py b/orchestra/contrib/miscellaneous/admin.py index 93c1af92..e616f0f9 100644 --- a/orchestra/contrib/miscellaneous/admin.py +++ b/orchestra/contrib/miscellaneous/admin.py @@ -58,7 +58,7 @@ class MiscellaneousAdmin(AccountAdminMixin, SelectPluginAdminMixin, admin.ModelA ) list_filter = ('service__name', 'is_active') list_select_related = ('service', 'account') - search_fields = ('identifier', 'description') + search_fields = ('identifier', 'description', 'account__username') plugin_field = 'service' plugin = MiscServicePlugin diff --git a/orchestra/contrib/orchestration/backends.py b/orchestra/contrib/orchestration/backends.py index e55d7097..13d9de6f 100644 --- a/orchestra/contrib/orchestration/backends.py +++ b/orchestra/contrib/orchestration/backends.py @@ -45,7 +45,7 @@ class ServiceBackend(plugins.Plugin, metaclass=ServiceMount): default_route_match = 'True' # Force the backend manager to block in multiple backend executions executing them synchronously block = False - format_docstring = () + doc_settings = None def __str__(self): return type(self).__name__ diff --git a/orchestra/contrib/orchestration/helpers.py b/orchestra/contrib/orchestration/helpers.py index 0c55d224..98c941c5 100644 --- a/orchestra/contrib/orchestration/helpers.py +++ b/orchestra/contrib/orchestration/helpers.py @@ -11,30 +11,36 @@ from django.utils.translation import ungettext, ugettext_lazy as _ def get_backends_help_text(backends): help_texts = {} for backend in backends: + help_text = backend.__doc__ or '' context = { 'model': backend.model, 'related_models': str(backend.related_models), 'script_executable': backend.script_executable, - 'script_method': str(backend.script_method), - 'function_method': str(backend.script_method), - 'actions': ', '.join(backend.actions), + 'script_method': '.'.join((backend.script_method.__module__, backend.script_method.__name__)), + 'function_method': '.'.join((backend.function_method.__module__, backend.function_method.__name__)), + 'actions': str(backend.actions), } - help_text = textwrap.dedent(""" - - Model: '%(model)s'
- - Related models: %(related_models)s
- - Script executable: %(script_executable)s
- - Script method: %(script_method)s
- - Function method: %(function_method)s
- - Actions: %(actions)s
""" + help_text += textwrap.dedent(""" + - Model: '%(model)s' + - Related models: %(related_models)s + - Script executable: %(script_executable)s + - Script method: %(script_method)s + - Function method: %(function_method)s + - Actions: %(actions)s + """ ) % context - docstring = backend.__doc__ - if docstring: - try: - docstring = (docstring % backend.format_docstring).strip().splitlines() - except TypeError as e: - raise TypeError(str(backend) + str(e)) - help_text += '
' + '
'.join(docstring) - help_texts[backend.get_name()] = help_text + help_text = help_text.lstrip().splitlines() + help_settings = [''] + if backend.doc_settings: + module, names = backend.doc_settings + for name in names: + value = getattr(module, name) + if isinstance(value, str): + help_settings.append("%s = '%s'" % (name, value)) + else: + help_settings.append("%s = %s" % (name, str(value))) + help_text += help_settings + help_texts[backend.get_name()] = '
'.join(help_text) return help_texts diff --git a/orchestra/contrib/saas/backends/bscw.py b/orchestra/contrib/saas/backends/bscw.py index 742daac9..ab21e0e6 100644 --- a/orchestra/contrib/saas/backends/bscw.py +++ b/orchestra/contrib/saas/backends/bscw.py @@ -12,6 +12,9 @@ class BSCWBackend(ServiceController): model = 'saas.SaaS' default_route_match = "saas.service == 'bscw'" actions = ('save', 'delete', 'validate_creation') + doc_settings = (settings, + ('SAAS_BSCW_BSADMIN_PATH',) + ) def validate_creation(self, saas): context = self.get_context(saas) diff --git a/orchestra/contrib/saas/backends/dokuwikimu.py b/orchestra/contrib/saas/backends/dokuwikimu.py index ce19043b..245e73c4 100644 --- a/orchestra/contrib/saas/backends/dokuwikimu.py +++ b/orchestra/contrib/saas/backends/dokuwikimu.py @@ -8,8 +8,14 @@ from .. import settings class DokuWikiMuBackend(ServiceController): + """ + Creates a DokuWiki site on a DokuWiki multisite installation. + """ verbose_name = _("DokuWiki multisite") model = 'webapps.WebApp' + doc_settings = (settings, + ('SAAS_DOKUWIKI_TEMPLATE_PATH', 'SAAS_DOKUWIKI_FARM_PATH') + ) def save(self, webapp): context = self.get_context(webapp) @@ -25,7 +31,7 @@ class DokuWikiMuBackend(ServiceController): def get_context(self, webapp): context = super(DokuWikiMuBackend, self).get_context(webapp) context.update({ - 'template': settings.WEBAPPS_DOKUWIKIMU_TEMPLATE_PATH, - 'app_path': os.path.join(settings.WEBAPPS_DOKUWIKIMU_FARM_PATH, webapp.name) + 'template': settings.SAAS_DOKUWIKI_TEMPLATE_PATH, + 'app_path': os.path.join(settings.SAAS_DOKUWIKI_FARM_PATH, webapp.name) }) return replace(context, "'", '"') diff --git a/orchestra/contrib/saas/backends/drupalmu.py b/orchestra/contrib/saas/backends/drupalmu.py index 5cd8f16b..574f8c94 100644 --- a/orchestra/contrib/saas/backends/drupalmu.py +++ b/orchestra/contrib/saas/backends/drupalmu.py @@ -9,9 +9,15 @@ from .. import settings class DrupalMuBackend(ServiceController): + """ + Creates a Drupal site on a Drupal multisite installation + """ verbose_name = _("Drupal multisite") model = 'webapps.WebApp' - + doc_settings = (settings, + ('SAAS_DRUPAL_SITES_PATH',) + ) + def save(self, webapp): context = self.get_context(webapp) self.append(textwrap.dedent("""\ @@ -32,6 +38,6 @@ class DrupalMuBackend(ServiceController): def get_context(self, webapp): context = super(DrupalMuBackend, self).get_context(webapp) - context['drupal_path'] = settings.WEBAPPS_DRUPAL_SITES_PATH % context + context['drupal_path'] = settings.SAAS_DRUPAL_SITES_PATH % context context['drupal_settings'] = os.path.join(context['drupal_path'], 'settings.php') return replace(context, "'", '"') diff --git a/orchestra/contrib/saas/backends/gitlab.py b/orchestra/contrib/saas/backends/gitlab.py index 3f39d287..55c13cd5 100644 --- a/orchestra/contrib/saas/backends/gitlab.py +++ b/orchestra/contrib/saas/backends/gitlab.py @@ -14,6 +14,9 @@ class GitLabSaaSBackend(ServiceController): default_route_match = "saas.service == 'gitlab'" block = True actions = ('save', 'delete', 'validate_creation') + doc_settings = (settings, + ('SAAS_GITLAB_DOMAIN', 'SAAS_GITLAB_ROOT_PASSWORD'), + ) def get_base_url(self): return 'https://%s/api/v3' % settings.SAAS_GITLAB_DOMAIN diff --git a/orchestra/contrib/saas/backends/phplist.py b/orchestra/contrib/saas/backends/phplist.py index 1b028f5e..e7ed0aa0 100644 --- a/orchestra/contrib/saas/backends/phplist.py +++ b/orchestra/contrib/saas/backends/phplist.py @@ -7,6 +7,14 @@ from orchestra.contrib.orchestration import ServiceController class PhpListSaaSBackend(ServiceController): + """ + Creates a new phplist instance on a phpList multisite installation. + The site is created by means of creating a new database per phpList site, but all sites share the same code. + + // config/config.php + $site = array_shift((explode(".",$_SERVER['HTTP_HOST']))); + $database_name = "phplist_mu_{$site}"; + """ verbose_name = _("phpList SaaS") model = 'saas.SaaS' default_route_match = "saas.service == 'phplist'" diff --git a/orchestra/contrib/saas/backends/wordpressmu.py b/orchestra/contrib/saas/backends/wordpressmu.py index e8501a21..e2fdf7d5 100644 --- a/orchestra/contrib/saas/backends/wordpressmu.py +++ b/orchestra/contrib/saas/backends/wordpressmu.py @@ -9,16 +9,22 @@ from .. import settings class WordpressMuBackend(ServiceController): + """ + Creates a wordpress site on a WordPress MultiSite installation. + """ verbose_name = _("Wordpress multisite") model = 'webapps.WebApp' default_route_match = "webapp.type == 'wordpress-mu'" + doc_settings = (settings, + ('SAAS_WORDPRESS_ADMIN_PASSWORD', 'SAAS_WORDPRESS_BASE_URL') + ) def login(self, session): base_url = self.get_base_url() login_url = base_url + '/wp-login.php' login_data = { 'log': 'admin', - 'pwd': settings.WEBAPPS_WORDPRESSMU_ADMIN_PASSWORD, + 'pwd': settings.SAAS_WORDPRESS_ADMIN_PASSWORD, 'redirect_to': '/wp-admin/' } response = session.post(login_url, data=login_data) @@ -26,7 +32,7 @@ class WordpressMuBackend(ServiceController): raise IOError("Failure login to remote application") def get_base_url(self): - base_url = settings.WEBAPPS_WORDPRESSMU_BASE_URL + base_url = settings.SAAS_WORDPRESS_BASE_URL return base_url.rstrip('/') def validate_response(self, response): diff --git a/orchestra/contrib/systemusers/backends.py b/orchestra/contrib/systemusers/backends.py index 82e6dce8..6214c6fc 100644 --- a/orchestra/contrib/systemusers/backends.py +++ b/orchestra/contrib/systemusers/backends.py @@ -11,18 +11,14 @@ from . import settings class UNIXUserBackend(ServiceController): """ - Basic UNIX system user/group support based on `useradd`, `usermod`, `userdel` and `groupdel`. - SYSTEMUSERS_DEFAULT_GROUP_MEMBERS = '%s' - SYSTEMUSERS_MOVE_ON_DELETE_PATH = '%s' + Basic UNIX system user/group support based on useradd, usermod, userdel and groupdel. """ - format_docstring = ( - settings.SYSTEMUSERS_DEFAULT_GROUP_MEMBERS, - settings.SYSTEMUSERS_MOVE_ON_DELETE_PATH, - ) - verbose_name = _("UNIX user") model = 'systemusers.SystemUser' actions = ('save', 'delete', 'grant_permission') + doc_settings = (settings, + ('SYSTEMUSERS_DEFAULT_GROUP_MEMBERS', 'SYSTEMUSERS_MOVE_ON_DELETE_PATH') + ) def save(self, user): context = self.get_context(user) @@ -95,7 +91,7 @@ class UNIXUserBackend(ServiceController): class UNIXUserDisk(ServiceMonitor): """ - `du -bs ` + du -bs <home> """ model = 'systemusers.SystemUser' resource = ServiceMonitor.DISK @@ -123,17 +119,15 @@ class UNIXUserDisk(ServiceMonitor): class Exim4Traffic(ServiceMonitor): """ - Exim4 mainlog parser for mails sent on the webserver by system users (e.g. via PHP mail()) - SYSTEMUSERS_MAIL_LOG_PATH = '%s' + Exim4 mainlog parser for mails sent on the webserver by system users (e.g. via PHP mail()) """ - format_docstring = ( - settings.SYSTEMUSERS_MAIL_LOG_PATH, - ) - model = 'systemusers.SystemUser' resource = ServiceMonitor.TRAFFIC verbose_name = _("Exim4 traffic") script_executable = '/usr/bin/python' + doc_settings = (settings, + ('SYSTEMUSERS_MAIL_LOG_PATH',) + ) def prepare(self): mainlog = settings.SYSTEMUSERS_MAIL_LOG_PATH @@ -211,15 +205,14 @@ class Exim4Traffic(ServiceMonitor): class VsFTPdTraffic(ServiceMonitor): """ vsFTPd log parser. - SYSTEMUSERS_FTP_LOG_PATH = '%s' """ - format_docstring = ( - settings.SYSTEMUSERS_FTP_LOG_PATH, - ) model = 'systemusers.SystemUser' resource = ServiceMonitor.TRAFFIC verbose_name = _('VsFTPd traffic') script_executable = '/usr/bin/python' + doc_settings = (settings, + ('SYSTEMUSERS_FTP_LOG_PATH',) + ) def prepare(self): vsftplog = settings.SYSTEMUSERS_FTP_LOG_PATH diff --git a/orchestra/contrib/webapps/backends/__init__.py b/orchestra/contrib/webapps/backends/__init__.py index 30796a3d..9b9fa1ae 100644 --- a/orchestra/contrib/webapps/backends/__init__.py +++ b/orchestra/contrib/webapps/backends/__init__.py @@ -12,6 +12,9 @@ class WebAppServiceMixin(object): ('webapps.WebAppOption', 'webapp'), ) directive = None + doc_settings = (settings, + ('WEBAPPS_UNDER_CONSTRUCTION_PATH', 'WEBAPPS_MOVE_ON_DELETE_PATH',) + ) def create_webapp_dir(self, context): self.append(textwrap.dedent("""\ @@ -45,7 +48,7 @@ class WebAppServiceMixin(object): 'type': webapp.type, 'app_path': webapp.get_path(), 'banner': self.get_banner(), - 'under_construction_path': settings.settings.WEBAPPS_UNDER_CONSTRUCTION_PATH, + 'under_construction_path': settings.WEBAPPS_UNDER_CONSTRUCTION_PATH, 'is_mounted': webapp.content_set.exists(), } context['deleted_app_path'] = settings.WEBAPPS_MOVE_ON_DELETE_PATH % context diff --git a/orchestra/contrib/webapps/backends/php.py b/orchestra/contrib/webapps/backends/php.py index 24e15839..3cad18b4 100644 --- a/orchestra/contrib/webapps/backends/php.py +++ b/orchestra/contrib/webapps/backends/php.py @@ -14,29 +14,21 @@ class PHPBackend(WebAppServiceMixin, ServiceController): """ PHP support for apache-mod-fcgid and php-fpm. It handles switching between these two PHP process management systemes. - WEBAPPS_MERGE_PHP_WEBAPPS = %s - WEBAPPS_FPM_DEFAULT_MAX_CHILDREN = %s - WEBAPPS_PHP_CGI_BINARY_PATH = '%s' - WEBAPPS_PHP_CGI_RC_DIR = '%s' - WEBAPPS_PHP_CGI_INI_SCAN_DIR = '%s' - WEBAPPS_FCGID_CMD_OPTIONS_PATH = '%s' - WEBAPPS_PHPFPM_POOL_PATH = '%s' - WEBAPPS_PHP_MAX_REQUESTS = %s """ - format_docstring = ( - settings.WEBAPPS_MERGE_PHP_WEBAPPS, - settings.WEBAPPS_FPM_DEFAULT_MAX_CHILDREN, - settings.WEBAPPS_PHP_CGI_BINARY_PATH, - settings.WEBAPPS_PHP_CGI_RC_DIR, - settings.WEBAPPS_PHP_CGI_INI_SCAN_DIR, - settings.WEBAPPS_FCGID_CMD_OPTIONS_PATH, - settings.WEBAPPS_PHPFPM_POOL_PATH, - settings.WEBAPPS_PHP_MAX_REQUESTS, - ) + MERGE = settings.WEBAPPS_MERGE_PHP_WEBAPPS verbose_name = _("PHP FPM/FCGID") default_route_match = "webapp.type.endswith('php')" - MERGE = settings.WEBAPPS_MERGE_PHP_WEBAPPS + doc_settings = (settings, ( + 'WEBAPPS_MERGE_PHP_WEBAPPS', + 'WEBAPPS_FPM_DEFAULT_MAX_CHILDREN', + 'WEBAPPS_PHP_CGI_BINARY_PATH', + 'WEBAPPS_PHP_CGI_RC_DIR', + 'WEBAPPS_PHP_CGI_INI_SCAN_DIR', + 'WEBAPPS_FCGID_CMD_OPTIONS_PATH', + 'WEBAPPS_PHPFPM_POOL_PATH', + 'WEBAPPS_PHP_MAX_REQUESTS', + )) def save(self, webapp): context = self.get_context(webapp) diff --git a/orchestra/contrib/webapps/backends/python.py b/orchestra/contrib/webapps/backends/python.py index 9c384c75..a0ee69d3 100644 --- a/orchestra/contrib/webapps/backends/python.py +++ b/orchestra/contrib/webapps/backends/python.py @@ -12,22 +12,16 @@ from .. import settings class uWSGIPythonBackend(WebAppServiceMixin, ServiceController): """ - Emperor mode - http://uwsgi-docs.readthedocs.org/en/latest/Emperor.html - WEBAPPS_UWSGI_BASE_DIR = '%s' - WEBAPPS_PYTHON_MAX_REQUESTS = %s - WEBAPPS_PYTHON_DEFAULT_MAX_WORKERS = %s - WEBAPPS_PYTHON_DEFAULT_TIMEOUT = %s + Emperor mode """ - format_docstring = ( - settings.WEBAPPS_UWSGI_BASE_DIR, - settings.WEBAPPS_PYTHON_MAX_REQUESTS, - settings.WEBAPPS_PYTHON_DEFAULT_MAX_WORKERS, - settings.WEBAPPS_PYTHON_DEFAULT_TIMEOUT, - ) - verbose_name = _("Python uWSGI") default_route_match = "webapp.type.endswith('python')" + doc_settings = (settings, ( + 'WEBAPPS_UWSGI_BASE_DIR', + 'WEBAPPS_PYTHON_MAX_REQUESTS', + 'WEBAPPS_PYTHON_DEFAULT_MAX_WORKERS', + 'WEBAPPS_PYTHON_DEFAULT_TIMEOUT', + )) def save(self, webapp): context = self.get_context(webapp) diff --git a/orchestra/contrib/webapps/backends/symboliclink.py b/orchestra/contrib/webapps/backends/symboliclink.py index add8eab1..a5675333 100644 --- a/orchestra/contrib/webapps/backends/symboliclink.py +++ b/orchestra/contrib/webapps/backends/symboliclink.py @@ -11,7 +11,6 @@ class SymbolicLinkBackend(PHPBackend, ServiceController): """ Same as PHPBackend but allows you to have the webapps on a directory diferent than the webapps dir. """ - format_docstring = () verbose_name = _("Symbolic link webapp") model = 'webapps.WebApp' default_route_match = "webapp.type == 'symbolic-link'" diff --git a/orchestra/contrib/webapps/backends/wordpress.py b/orchestra/contrib/webapps/backends/wordpress.py index 082ce091..e8fc6ec4 100644 --- a/orchestra/contrib/webapps/backends/wordpress.py +++ b/orchestra/contrib/webapps/backends/wordpress.py @@ -14,15 +14,14 @@ class WordPressBackend(WebAppServiceMixin, ServiceController): """ Installs the latest version of WordPress available on www.wordpress.org It fully configures the wp-config.php (keys included) and sets up the database with initial admin password. - WEBAPPS_DEFAULT_MYSQL_DATABASE_HOST = '%s' """ - format_docstring = ( - settings.WEBAPPS_DEFAULT_MYSQL_DATABASE_HOST, - ) verbose_name = _("Wordpress") model = 'webapps.WebApp' default_route_match = "webapp.type == 'wordpress-php'" script_executable = '/usr/bin/php' + doc_settings = (settings, + ('WEBAPPS_DEFAULT_MYSQL_DATABASE_HOST',) + ) def prepare(self): self.append(textwrap.dedent("""\ diff --git a/orchestra/contrib/webapps/options.py b/orchestra/contrib/webapps/options.py index ee19a337..74b18da0 100644 --- a/orchestra/contrib/webapps/options.py +++ b/orchestra/contrib/webapps/options.py @@ -308,7 +308,7 @@ class PHPSuhosinExecutorIncludeWhitelist(PHPAppOption): class PHPUploadMaxFileSize(PHPAppOption): name = 'upload_max_filesize' - verbose_name = _("Upload max filezise") + verbose_name = _("Upload max filesize") help_text = _("Value between 0M and 999M.") regex = r'^[0-9]{1,3}M$' diff --git a/orchestra/contrib/websites/backends/apache.py b/orchestra/contrib/websites/backends/apache.py index cb988946..d3464a5d 100644 --- a/orchestra/contrib/websites/backends/apache.py +++ b/orchestra/contrib/websites/backends/apache.py @@ -13,6 +13,11 @@ from ..utils import normurlpath class Apache2Backend(ServiceController): + """ + Apache 2.4 backend with support for the following directives: + static, location, fpm, fcgid, uwsgi, \ + ssl, security, redirects, proxies, saas + """ HTTP_PORT = 80 HTTPS_PORT = 443 @@ -22,6 +27,15 @@ class Apache2Backend(ServiceController): ('webapps.WebApp', 'website_set'), ) verbose_name = _("Apache 2") + doc_settings = (settings, ( + 'WEBSITES_VHOST_EXTRA_DIRECTIVES', + 'WEBSITES_DEFAULT_SSL_CERT', + 'WEBSITES_DEFAULT_SSL_KEY', + 'WEBSITES_DEFAULT_SSL_CA', + 'WEBSITES_BASE_APACHE_CONF', + 'WEBSITES_DEFAULT_IPS', + 'WEBSITES_SAAS_DIRECTIVES', + )) def render_virtual_host(self, site, context, ssl=False): context['port'] = self.HTTPS_PORT if ssl else self.HTTP_PORT @@ -362,12 +376,14 @@ class Apache2Backend(ServiceController): class Apache2Traffic(ServiceMonitor): """ Parses apache logs, - looking for the size of each request on the last word of the log line + looking for the size of each request on the last word of the log line. """ model = 'websites.Website' resource = ServiceMonitor.TRAFFIC verbose_name = _("Apache 2 Traffic") - + doc_settings = (settings, + ('WEBSITES_TRAFFIC_IGNORE_HOSTS',) + ) def prepare(self): super(Apache2Traffic, self).prepare() ignore_hosts = '\\|'.join(settings.WEBSITES_TRAFFIC_IGNORE_HOSTS) diff --git a/orchestra/contrib/websites/backends/webalizer.py b/orchestra/contrib/websites/backends/webalizer.py index 3947c94c..5e508aea 100644 --- a/orchestra/contrib/websites/backends/webalizer.py +++ b/orchestra/contrib/websites/backends/webalizer.py @@ -15,6 +15,9 @@ class WebalizerBackend(ServiceController): verbose_name = _("Webalizer Content") model = 'websites.Content' default_route_match = "content.webapp.type == 'webalizer'" + doc_settings = (settings, + ('WEBSITES_WEBALIZER_PATH',) + ) def save(self, content): context = self.get_context(content)