Improved backends help texts

This commit is contained in:
Marc Aymerich 2015-04-24 11:39:20 +00:00
parent eebaee1097
commit c2b0186034
28 changed files with 210 additions and 177 deletions

View File

@ -266,7 +266,6 @@ https://code.djangoproject.com/ticket/24576
* MultiCHoiceField proper serialization * MultiCHoiceField proper serialization
# Apache restart fails: detect if appache running, and execute start
* UNIFY PHP FPM settings name * UNIFY PHP FPM settings name
# virtualhost name: name-account? # virtualhost name: name-account?
* add a delay to changes on the webserver apache to no overwelm it with backend executions? * 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 ? * 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?) * 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 # Ignore superusers & co on billing: list filter doesn't work nor ignore detection
# bill.totals make it 100% computed? # 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 - * 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 # replace multichoicefield and jsonfield by ArrayField, HStoreField
# Amend lines??? # Amend lines???
# Add icon on select contact view
# Determine the difference between data serializer used for validation and used for the rest API! # 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 # Make PluginApiView that fills metadata and other stuff like modeladmin plugin support

View File

@ -122,12 +122,13 @@ class AccountListAdmin(AccountAdmin):
# TODO get query string from request.META['QUERY_STRING'] to preserve filters # TODO get query string from request.META['QUERY_STRING'] to preserve filters
context = { context = {
'url': '../?account=' + str(instance.pk), 'url': '../?account=' + str(instance.pk),
'name': instance.username 'name': instance.username,
'plus': '<strong style="color:green; font-size:12px">+</strong>',
} }
return '<a href="%(url)s">%(name)s</a>' % context return _('<a href="%(url)s">%(plus)s Add to %(name)s</a>') % context
select_account.short_description = _("account") select_account.short_description = _("account")
select_account.allow_tags = True 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): def changelist_view(self, request, extra_context=None):
original_app_label = request.META['PATH_INFO'].split('/')[-5] original_app_label = request.META['PATH_INFO'].split('/')[-5]

View File

@ -14,7 +14,7 @@ from .models import Database, DatabaseUser
class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin): class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
list_display = ('name', 'type', 'display_users', 'account_link') list_display = ('name', 'type', 'display_users', 'account_link')
list_filter = ('type',) list_filter = ('type',)
search_fields = ['name'] search_fields = ('name', 'account__username')
change_readonly_fields = ('name', 'type') change_readonly_fields = ('name', 'type')
extra = 1 extra = 1
fieldsets = ( fieldsets = (
@ -71,7 +71,7 @@ class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
class DatabaseUserAdmin(SelectAccountAdminMixin, ChangePasswordAdminMixin, ExtendedModelAdmin): class DatabaseUserAdmin(SelectAccountAdminMixin, ChangePasswordAdminMixin, ExtendedModelAdmin):
list_display = ('username', 'type', 'display_databases', 'account_link') list_display = ('username', 'type', 'display_databases', 'account_link')
list_filter = ('type',) list_filter = ('type',)
search_fields = ['username'] search_fields = ('username', 'account__username')
form = DatabaseUserChangeForm form = DatabaseUserChangeForm
add_form = DatabaseUserCreationForm add_form = DatabaseUserCreationForm
change_readonly_fields = ('username', 'type') change_readonly_fields = ('username', 'type')

View File

@ -10,14 +10,14 @@ from . import settings
class MySQLBackend(ServiceController): class MySQLBackend(ServiceController):
""" """
Simple backend for creating MySQL databases using `CREATE DATABASE` statement. Simple backend for creating MySQL databases using <tt>CREATE DATABASE</tt> statement.
DATABASES_DEFAULT_HOST = %s
""" """
format_docstring = (settings.DATABASES_DEFAULT_HOST,)
verbose_name = "MySQL database" verbose_name = "MySQL database"
model = 'databases.Database' model = 'databases.Database'
default_route_match = "database.type == 'mysql'" default_route_match = "database.type == 'mysql'"
doc_settings = (settings,
('DATABASES_DEFAULT_HOST',)
)
def save(self, database): def save(self, database):
if database.type != database.MYSQL: if database.type != database.MYSQL:
@ -61,13 +61,14 @@ class MySQLBackend(ServiceController):
class MySQLUserBackend(ServiceController): class MySQLUserBackend(ServiceController):
""" """
Simple backend for creating MySQL users using `CREATE USER` statement. Simple backend for creating MySQL users using <tt>CREATE USER</tt> statement.
DATABASES_DEFAULT_HOST = %s """
""" % settings.DATABASES_DEFAULT_HOST
verbose_name = "MySQL user" verbose_name = "MySQL user"
model = 'databases.DatabaseUser' model = 'databases.DatabaseUser'
default_route_match = "databaseuser.type == 'mysql'" default_route_match = "databaseuser.type == 'mysql'"
doc_settings = (settings,
('DATABASES_DEFAULT_HOST',)
)
def save(self, user): def save(self, user):
if user.type != user.MYSQL: if user.type != user.MYSQL:
@ -105,7 +106,7 @@ class MySQLUserBackend(ServiceController):
class MysqlDisk(ServiceMonitor): class MysqlDisk(ServiceMonitor):
""" """
du -bs <database_path> <tt>du -bs &lt;database_path&gt;</tt>
Implements triggers for resource limit exceeded and recovery, disabling insert and create privileges. Implements triggers for resource limit exceeded and recovery, disabling insert and create privileges.
""" """
model = 'databases.Database' model = 'databases.Database'

View File

@ -67,7 +67,7 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin):
inlines = [RecordInline, DomainInline] inlines = [RecordInline, DomainInline]
list_filter = [TopDomainListFilter] list_filter = [TopDomainListFilter]
change_readonly_fields = ('name',) change_readonly_fields = ('name',)
search_fields = ['name',] search_fields = ('name', 'account__username')
add_form = BatchDomainCreationAdminForm add_form = BatchDomainCreationAdminForm
change_view_actions = [view_zone] change_view_actions = [view_zone]

View File

@ -14,14 +14,8 @@ class Bind9MasterDomainBackend(ServiceController):
""" """
Bind9 zone and config generation. 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. 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'
""" """
CONF_PATH = settings.DOMAINS_MASTERS_PATH
format_docstring = (
str(settings.DOMAINS_SLAVES),
settings.DOMAINS_MASTERS_PATH,
)
verbose_name = _("Bind9 master domain") verbose_name = _("Bind9 master domain")
model = 'domains.Domain' model = 'domains.Domain'
@ -30,7 +24,9 @@ class Bind9MasterDomainBackend(ServiceController):
('domains.Domain', 'origin'), ('domains.Domain', 'origin'),
) )
ignore_fields = ['serial'] ignore_fields = ['serial']
CONF_PATH = settings.DOMAINS_MASTERS_PATH doc_settings = (settings,
('DOMAINS_SLAVES', 'DOMAINS_MASTERS_PATH')
)
@classmethod @classmethod
def is_main(cls, obj): def is_main(cls, obj):
@ -136,15 +132,16 @@ class Bind9SlaveDomainBackend(Bind9MasterDomainBackend):
""" """
Generate the configuartion for slave servers 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. 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") verbose_name = _("Bind9 slave domain")
related_models = ( related_models = (
('domains.Domain', 'origin'), ('domains.Domain', 'origin'),
) )
CONF_PATH = settings.DOMAINS_SLAVES_PATH doc_settings = (settings,
('DOMAINS_MASTERS', 'DOMAINS_SLAVES_PATH')
)
def save(self, domain): def save(self, domain):
context = self.get_context(domain) context = self.get_context(domain)
self.update_conf(context) self.update_conf(context)

View File

@ -42,8 +42,8 @@ class MessageReadOnlyInline(admin.TabularInline):
model = Message model = Message
extra = 0 extra = 0
can_delete = False can_delete = False
fields = ['content_html'] fields = ('content_html',)
readonly_fields = ['content_html'] readonly_fields = ('content_html',)
class Media: class Media:
css = { css = {
@ -79,7 +79,7 @@ class MessageInline(admin.TabularInline):
max_num = 1 max_num = 1
form = MessageInlineForm form = MessageInlineForm
can_delete = False can_delete = False
fields = ['content'] fields = ('content',)
def get_formset(self, request, obj=None, **kwargs): def get_formset(self, request, obj=None, **kwargs):
""" hook request.user on the inline form """ """ hook request.user on the inline form """
@ -93,14 +93,14 @@ class MessageInline(admin.TabularInline):
class TicketInline(admin.TabularInline): class TicketInline(admin.TabularInline):
fields = [ fields = (
'ticket_id', 'subject', 'creator_link', 'owner_link', 'colored_state', 'ticket_id', 'subject', 'creator_link', 'owner_link', 'colored_state',
'colored_priority', 'created', 'updated' 'colored_priority', 'created', 'updated'
] )
readonly_fields = [ readonly_fields = (
'ticket_id', 'subject', 'creator_link', 'owner_link', 'colored_state', 'ticket_id', 'subject', 'creator_link', 'owner_link', 'colored_state',
'colored_priority', 'created', 'updated' 'colored_priority', 'created', 'updated'
] )
model = Ticket model = Ticket
extra = 0 extra = 0
max_num = 0 max_num = 0
@ -119,35 +119,35 @@ class TicketInline(admin.TabularInline):
class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin): class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin):
list_display = [ list_display = (
'unbold_id', 'bold_subject', 'display_creator', 'display_owner', 'unbold_id', 'bold_subject', 'display_creator', 'display_owner',
'display_queue', 'display_priority', 'display_state', 'updated' 'display_queue', 'display_priority', 'display_state', 'updated'
] )
list_display_links = ('unbold_id', 'bold_subject') list_display_links = ('unbold_id', 'bold_subject')
list_filter = [ list_filter = (
MyTicketsListFilter, 'queue__name', 'priority', TicketStateListFilter, MyTicketsListFilter, 'queue__name', 'priority', TicketStateListFilter,
] )
default_changelist_filters = ( default_changelist_filters = (
('my_tickets', lambda r: 'True' if not r.user.is_superuser else 'False'), ('my_tickets', lambda r: 'True' if not r.user.is_superuser else 'False'),
('state', 'OPEN') ('state', 'OPEN')
) )
date_hierarchy = 'created_at' date_hierarchy = 'created_at'
search_fields = [ search_fields = (
'id', 'subject', 'creator__username', 'creator__email', 'queue__name', 'id', 'subject', 'creator__username', 'creator__email', 'queue__name',
'owner__username' 'owner__username'
] )
actions = [ actions = (
mark_as_unread, mark_as_read, 'delete_selected', reject_tickets, mark_as_unread, mark_as_read, 'delete_selected', reject_tickets,
resolve_tickets, close_tickets, take_tickets resolve_tickets, close_tickets, take_tickets
] )
sudo_actions = ['delete_selected'] sudo_actions = ('delete_selected',)
change_view_actions = [ change_view_actions = (
resolve_tickets, close_tickets, reject_tickets, take_tickets resolve_tickets, close_tickets, reject_tickets, take_tickets
] )
# change_form_template = "admin/orchestra/change_form.html" # change_form_template = "admin/orchestra/change_form.html"
form = TicketForm form = TicketForm
add_inlines = [] add_inlines = ()
inlines = [ MessageReadOnlyInline, MessageInline ] inlines = (MessageReadOnlyInline, MessageInline)
readonly_fields = ( readonly_fields = (
'display_summary', 'display_queue', 'display_owner', 'display_state', 'display_summary', 'display_queue', 'display_owner', 'display_state',
'display_priority' 'display_priority'
@ -286,10 +286,10 @@ class TicketAdmin(ChangeListDefaultFilter, ExtendedModelAdmin):
class QueueAdmin(admin.ModelAdmin): class QueueAdmin(admin.ModelAdmin):
list_display = ['name', 'default', 'num_tickets'] list_display = ('name', 'default', 'num_tickets')
actions = [set_default_queue] actions = (set_default_queue,)
inlines = [TicketInline] inlines = (TicketInline,)
ordering = ['name'] ordering = ('name',)
class Media: class Media:
css = { css = {

View File

@ -11,18 +11,8 @@ from .models import List
class MailmanBackend(ServiceController): class MailmanBackend(ServiceController):
""" """
Mailman backend based on `newlist`, it handles custom domains. Mailman 2 backend based on <tt>newlist</tt>, it handles custom domains.
LISTS_VIRTUAL_ALIAS_PATH = '%s'
LISTS_VIRTUAL_ALIAS_DOMAINS_PATH = '%s'
LISTS_DEFAULT_DOMAIN = '%s'
LISTS_MAILMAN_ROOT_DIR = '%s'
""" """
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" verbose_name = "Mailman"
model = 'lists.List' model = 'lists.List'
addresses = [ addresses = [
@ -37,6 +27,12 @@ class MailmanBackend(ServiceController):
'-subscribe', '-subscribe',
'-unsubscribe' '-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): def include_virtual_alias_domain(self, context):
if context['address_domain']: if context['address_domain']:
@ -165,16 +161,15 @@ class MailmanBackend(ServiceController):
class MailmanTraffic(ServiceMonitor): class MailmanTraffic(ServiceMonitor):
""" """
Parses mailman log file looking for email size and multiples it by `list_members` count. Parses mailman log file looking for email size and multiples it by <tt>list_members</tt> count.
LISTS_MAILMAN_POST_LOG_PATH = '%s'
""" """
format_docstring = (
settings.LISTS_MAILMAN_POST_LOG_PATH,
)
model = 'lists.List' model = 'lists.List'
resource = ServiceMonitor.TRAFFIC resource = ServiceMonitor.TRAFFIC
verbose_name = _("Mailman traffic") verbose_name = _("Mailman traffic")
script_executable = '/usr/bin/python' script_executable = '/usr/bin/python'
doc_settings = (settings,
('LISTS_MAILMAN_POST_LOG_PATH',)
)
def prepare(self): def prepare(self):
postlog = settings.LISTS_MAILMAN_POST_LOG_PATH postlog = settings.LISTS_MAILMAN_POST_LOG_PATH
@ -272,7 +267,7 @@ class MailmanTraffic(ServiceMonitor):
class MailmanSubscribers(ServiceMonitor): class MailmanSubscribers(ServiceMonitor):
""" """
Monitors number of list subscribers via `list_members` Monitors number of list subscribers via <tt>list_members</tt>
""" """
model = 'lists.List' model = 'lists.List'
verbose_name = _("Mailman subscribers") verbose_name = _("Mailman subscribers")

View File

@ -3,6 +3,8 @@ from urllib.parse import parse_qs
from django import forms from django import forms
from django.contrib import admin 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 django.utils.translation import ugettext_lazy as _
from orchestra.admin import ExtendedModelAdmin, ChangePasswordAdminMixin from orchestra.admin import ExtendedModelAdmin, ChangePasswordAdminMixin
@ -104,13 +106,15 @@ class MailboxAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedMo
class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin): class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
list_display = ( 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) list_filter = (HasMailboxListFilter, HasForwardListFilter)
fields = ('account_link', 'email_link', 'mailboxes', 'forward') fields = ('account_link', 'email_link', 'mailboxes', 'forward')
add_fields = ('account_link', ('name', 'domain'), 'mailboxes', 'forward') add_fields = ('account_link', ('name', 'domain'), 'mailboxes', 'forward')
inlines = [AutoresponseInline] 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') readonly_fields = ('account_link', 'domain_link', 'email_link')
filter_by_account_fields = ('domain', 'mailboxes') filter_by_account_fields = ('domain', 'mailboxes')
filter_horizontal = ['mailboxes'] filter_horizontal = ['mailboxes']
@ -119,6 +123,11 @@ class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
domain_link = admin_link('domain', order='domain__name') 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): def email_link(self, address):
link = self.domain_link(address) link = self.domain_link(address)
return "%s@%s" % (address.name, link) return "%s@%s" % (address.name, link)
@ -154,6 +163,10 @@ class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
fields.remove('mailboxes') fields.remove('mailboxes')
return fields 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) admin.site.register(Mailbox, MailboxAdmin)
admin.site.register(Address, AddressAdmin) admin.site.register(Address, AddressAdmin)

View File

@ -185,21 +185,15 @@ class DovecotPostfixPasswdVirtualUserBackend(ServiceController):
class PostfixAddressBackend(ServiceController): class PostfixAddressBackend(ServiceController):
""" """
Addresses based on Postfix virtual alias domains. Addresses based on Postfix virtual alias domains.
<tt>MAILBOXES_LOCAL_DOMAIN = '%s'
MAILBOXES_VIRTUAL_ALIAS_DOMAINS_PATH = '%s'
MAILBOXES_VIRTUAL_ALIAS_MAPS_PATH = '%s'</tt>
""" """
format_docstring = (
settings.MAILBOXES_LOCAL_DOMAIN,
settings.MAILBOXES_VIRTUAL_ALIAS_DOMAINS_PATH,
settings.MAILBOXES_VIRTUAL_ALIAS_MAPS_PATH,
)
verbose_name = _("Postfix address") verbose_name = _("Postfix address")
model = 'mailboxes.Address' model = 'mailboxes.Address'
related_models = ( related_models = (
('mailboxes.Mailbox', 'addresses'), ('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): def include_virtual_alias_domain(self, context):
if context['domain'] != context['local_domain']: if context['domain'] != context['local_domain']:
self.append(textwrap.dedent(""" self.append(textwrap.dedent("""
@ -296,15 +290,13 @@ class DovecotMaildirDisk(ServiceMonitor):
""" """
Maildir disk usage based on Dovecot maildirsize file Maildir disk usage based on Dovecot maildirsize file
http://wiki2.dovecot.org/Quota/Maildir http://wiki2.dovecot.org/Quota/Maildir
MAILBOXES_MAILDIRSIZE_PATH = '%s'
""" """
format_docstring = (
settings.MAILBOXES_MAILDIRSIZE_PATH,
)
model = 'mailboxes.Mailbox' model = 'mailboxes.Mailbox'
resource = ServiceMonitor.DISK resource = ServiceMonitor.DISK
verbose_name = _("Dovecot Maildir size") verbose_name = _("Dovecot Maildir size")
doc_settings = (settings,
('MAILBOXES_MAILDIRSIZE_PATH',)
)
def prepare(self): def prepare(self):
super(DovecotMaildirDisk, self).prepare() super(DovecotMaildirDisk, self).prepare()
@ -331,15 +323,14 @@ class PostfixMailscannerTraffic(ServiceMonitor):
""" """
A high-performance log parser. A high-performance log parser.
Reads the mail.log file only once, for all users. 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' model = 'mailboxes.Mailbox'
resource = ServiceMonitor.TRAFFIC resource = ServiceMonitor.TRAFFIC
verbose_name = _("Postfix-Mailscanner traffic") verbose_name = _("Postfix-Mailscanner traffic")
script_executable = '/usr/bin/python' script_executable = '/usr/bin/python'
doc_settings = (settings,
('MAILBOXES_MAIL_LOG_PATH',)
)
def prepare(self): def prepare(self):
mail_log = settings.MAILBOXES_MAIL_LOG_PATH mail_log = settings.MAILBOXES_MAIL_LOG_PATH

View File

@ -58,7 +58,7 @@ class MiscellaneousAdmin(AccountAdminMixin, SelectPluginAdminMixin, admin.ModelA
) )
list_filter = ('service__name', 'is_active') list_filter = ('service__name', 'is_active')
list_select_related = ('service', 'account') list_select_related = ('service', 'account')
search_fields = ('identifier', 'description') search_fields = ('identifier', 'description', 'account__username')
plugin_field = 'service' plugin_field = 'service'
plugin = MiscServicePlugin plugin = MiscServicePlugin

View File

@ -45,7 +45,7 @@ class ServiceBackend(plugins.Plugin, metaclass=ServiceMount):
default_route_match = 'True' default_route_match = 'True'
# Force the backend manager to block in multiple backend executions executing them synchronously # Force the backend manager to block in multiple backend executions executing them synchronously
block = False block = False
format_docstring = () doc_settings = None
def __str__(self): def __str__(self):
return type(self).__name__ return type(self).__name__

View File

@ -11,30 +11,36 @@ from django.utils.translation import ungettext, ugettext_lazy as _
def get_backends_help_text(backends): def get_backends_help_text(backends):
help_texts = {} help_texts = {}
for backend in backends: for backend in backends:
help_text = backend.__doc__ or ''
context = { context = {
'model': backend.model, 'model': backend.model,
'related_models': str(backend.related_models), 'related_models': str(backend.related_models),
'script_executable': backend.script_executable, 'script_executable': backend.script_executable,
'script_method': str(backend.script_method), 'script_method': '.'.join((backend.script_method.__module__, backend.script_method.__name__)),
'function_method': str(backend.script_method), 'function_method': '.'.join((backend.function_method.__module__, backend.function_method.__name__)),
'actions': ', '.join(backend.actions), 'actions': str(backend.actions),
} }
help_text = textwrap.dedent(""" help_text += textwrap.dedent("""
- Model: '%(model)s'<br> - Model: <tt>'%(model)s'</tt>
- Related models: %(related_models)s<br> - Related models: <tt>%(related_models)s</tt>
- Script executable: %(script_executable)s<br> - Script executable: <tt>%(script_executable)s</tt>
- Script method: %(script_method)s<br> - Script method: <tt>%(script_method)s</tt>
- Function method: %(function_method)s<br> - Function method: <tt>%(function_method)s</tt>
- Actions: %(actions)s<br>""" - Actions: <tt>%(actions)s</tt>
"""
) % context ) % context
docstring = backend.__doc__ help_text = help_text.lstrip().splitlines()
if docstring: help_settings = ['']
try: if backend.doc_settings:
docstring = (docstring % backend.format_docstring).strip().splitlines() module, names = backend.doc_settings
except TypeError as e: for name in names:
raise TypeError(str(backend) + str(e)) value = getattr(module, name)
help_text += '<br>' + '<br>'.join(docstring) if isinstance(value, str):
help_texts[backend.get_name()] = help_text help_settings.append("<tt>%s = '%s'</tt>" % (name, value))
else:
help_settings.append("<tt>%s = %s</tt>" % (name, str(value)))
help_text += help_settings
help_texts[backend.get_name()] = '<br>'.join(help_text)
return help_texts return help_texts

View File

@ -12,6 +12,9 @@ class BSCWBackend(ServiceController):
model = 'saas.SaaS' model = 'saas.SaaS'
default_route_match = "saas.service == 'bscw'" default_route_match = "saas.service == 'bscw'"
actions = ('save', 'delete', 'validate_creation') actions = ('save', 'delete', 'validate_creation')
doc_settings = (settings,
('SAAS_BSCW_BSADMIN_PATH',)
)
def validate_creation(self, saas): def validate_creation(self, saas):
context = self.get_context(saas) context = self.get_context(saas)

View File

@ -8,8 +8,14 @@ from .. import settings
class DokuWikiMuBackend(ServiceController): class DokuWikiMuBackend(ServiceController):
"""
Creates a DokuWiki site on a DokuWiki multisite installation.
"""
verbose_name = _("DokuWiki multisite") verbose_name = _("DokuWiki multisite")
model = 'webapps.WebApp' model = 'webapps.WebApp'
doc_settings = (settings,
('SAAS_DOKUWIKI_TEMPLATE_PATH', 'SAAS_DOKUWIKI_FARM_PATH')
)
def save(self, webapp): def save(self, webapp):
context = self.get_context(webapp) context = self.get_context(webapp)
@ -25,7 +31,7 @@ class DokuWikiMuBackend(ServiceController):
def get_context(self, webapp): def get_context(self, webapp):
context = super(DokuWikiMuBackend, self).get_context(webapp) context = super(DokuWikiMuBackend, self).get_context(webapp)
context.update({ context.update({
'template': settings.WEBAPPS_DOKUWIKIMU_TEMPLATE_PATH, 'template': settings.SAAS_DOKUWIKI_TEMPLATE_PATH,
'app_path': os.path.join(settings.WEBAPPS_DOKUWIKIMU_FARM_PATH, webapp.name) 'app_path': os.path.join(settings.SAAS_DOKUWIKI_FARM_PATH, webapp.name)
}) })
return replace(context, "'", '"') return replace(context, "'", '"')

View File

@ -9,8 +9,14 @@ from .. import settings
class DrupalMuBackend(ServiceController): class DrupalMuBackend(ServiceController):
"""
Creates a Drupal site on a Drupal multisite installation
"""
verbose_name = _("Drupal multisite") verbose_name = _("Drupal multisite")
model = 'webapps.WebApp' model = 'webapps.WebApp'
doc_settings = (settings,
('SAAS_DRUPAL_SITES_PATH',)
)
def save(self, webapp): def save(self, webapp):
context = self.get_context(webapp) context = self.get_context(webapp)
@ -32,6 +38,6 @@ class DrupalMuBackend(ServiceController):
def get_context(self, webapp): def get_context(self, webapp):
context = super(DrupalMuBackend, self).get_context(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') context['drupal_settings'] = os.path.join(context['drupal_path'], 'settings.php')
return replace(context, "'", '"') return replace(context, "'", '"')

View File

@ -14,6 +14,9 @@ class GitLabSaaSBackend(ServiceController):
default_route_match = "saas.service == 'gitlab'" default_route_match = "saas.service == 'gitlab'"
block = True block = True
actions = ('save', 'delete', 'validate_creation') actions = ('save', 'delete', 'validate_creation')
doc_settings = (settings,
('SAAS_GITLAB_DOMAIN', 'SAAS_GITLAB_ROOT_PASSWORD'),
)
def get_base_url(self): def get_base_url(self):
return 'https://%s/api/v3' % settings.SAAS_GITLAB_DOMAIN return 'https://%s/api/v3' % settings.SAAS_GITLAB_DOMAIN

View File

@ -7,6 +7,14 @@ from orchestra.contrib.orchestration import ServiceController
class PhpListSaaSBackend(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.
<tt>// config/config.php
$site = array_shift((explode(".",$_SERVER['HTTP_HOST'])));
$database_name = "phplist_mu_{$site}";</tt>
"""
verbose_name = _("phpList SaaS") verbose_name = _("phpList SaaS")
model = 'saas.SaaS' model = 'saas.SaaS'
default_route_match = "saas.service == 'phplist'" default_route_match = "saas.service == 'phplist'"

View File

@ -9,16 +9,22 @@ from .. import settings
class WordpressMuBackend(ServiceController): class WordpressMuBackend(ServiceController):
"""
Creates a wordpress site on a WordPress MultiSite installation.
"""
verbose_name = _("Wordpress multisite") verbose_name = _("Wordpress multisite")
model = 'webapps.WebApp' model = 'webapps.WebApp'
default_route_match = "webapp.type == 'wordpress-mu'" default_route_match = "webapp.type == 'wordpress-mu'"
doc_settings = (settings,
('SAAS_WORDPRESS_ADMIN_PASSWORD', 'SAAS_WORDPRESS_BASE_URL')
)
def login(self, session): def login(self, session):
base_url = self.get_base_url() base_url = self.get_base_url()
login_url = base_url + '/wp-login.php' login_url = base_url + '/wp-login.php'
login_data = { login_data = {
'log': 'admin', 'log': 'admin',
'pwd': settings.WEBAPPS_WORDPRESSMU_ADMIN_PASSWORD, 'pwd': settings.SAAS_WORDPRESS_ADMIN_PASSWORD,
'redirect_to': '/wp-admin/' 'redirect_to': '/wp-admin/'
} }
response = session.post(login_url, data=login_data) response = session.post(login_url, data=login_data)
@ -26,7 +32,7 @@ class WordpressMuBackend(ServiceController):
raise IOError("Failure login to remote application") raise IOError("Failure login to remote application")
def get_base_url(self): def get_base_url(self):
base_url = settings.WEBAPPS_WORDPRESSMU_BASE_URL base_url = settings.SAAS_WORDPRESS_BASE_URL
return base_url.rstrip('/') return base_url.rstrip('/')
def validate_response(self, response): def validate_response(self, response):

View File

@ -11,18 +11,14 @@ from . import settings
class UNIXUserBackend(ServiceController): class UNIXUserBackend(ServiceController):
""" """
Basic UNIX system user/group support based on `useradd`, `usermod`, `userdel` and `groupdel`. Basic UNIX system user/group support based on <tt>useradd</tt>, <tt>usermod</tt>, <tt>userdel</tt> and <tt>groupdel</tt>.
<tt>SYSTEMUSERS_DEFAULT_GROUP_MEMBERS = '%s'
SYSTEMUSERS_MOVE_ON_DELETE_PATH = '%s'</tt>
""" """
format_docstring = (
settings.SYSTEMUSERS_DEFAULT_GROUP_MEMBERS,
settings.SYSTEMUSERS_MOVE_ON_DELETE_PATH,
)
verbose_name = _("UNIX user") verbose_name = _("UNIX user")
model = 'systemusers.SystemUser' model = 'systemusers.SystemUser'
actions = ('save', 'delete', 'grant_permission') actions = ('save', 'delete', 'grant_permission')
doc_settings = (settings,
('SYSTEMUSERS_DEFAULT_GROUP_MEMBERS', 'SYSTEMUSERS_MOVE_ON_DELETE_PATH')
)
def save(self, user): def save(self, user):
context = self.get_context(user) context = self.get_context(user)
@ -95,7 +91,7 @@ class UNIXUserBackend(ServiceController):
class UNIXUserDisk(ServiceMonitor): class UNIXUserDisk(ServiceMonitor):
""" """
`du -bs <home>` <tt>du -bs &lt;home&gt;</tt>
""" """
model = 'systemusers.SystemUser' model = 'systemusers.SystemUser'
resource = ServiceMonitor.DISK resource = ServiceMonitor.DISK
@ -123,17 +119,15 @@ class UNIXUserDisk(ServiceMonitor):
class Exim4Traffic(ServiceMonitor): class Exim4Traffic(ServiceMonitor):
""" """
Exim4 mainlog parser for mails sent on the webserver by system users (e.g. via PHP mail()) Exim4 mainlog parser for mails sent on the webserver by system users (e.g. via PHP <tt>mail()</tt>)
SYSTEMUSERS_MAIL_LOG_PATH = '%s'
""" """
format_docstring = (
settings.SYSTEMUSERS_MAIL_LOG_PATH,
)
model = 'systemusers.SystemUser' model = 'systemusers.SystemUser'
resource = ServiceMonitor.TRAFFIC resource = ServiceMonitor.TRAFFIC
verbose_name = _("Exim4 traffic") verbose_name = _("Exim4 traffic")
script_executable = '/usr/bin/python' script_executable = '/usr/bin/python'
doc_settings = (settings,
('SYSTEMUSERS_MAIL_LOG_PATH',)
)
def prepare(self): def prepare(self):
mainlog = settings.SYSTEMUSERS_MAIL_LOG_PATH mainlog = settings.SYSTEMUSERS_MAIL_LOG_PATH
@ -211,15 +205,14 @@ class Exim4Traffic(ServiceMonitor):
class VsFTPdTraffic(ServiceMonitor): class VsFTPdTraffic(ServiceMonitor):
""" """
vsFTPd log parser. vsFTPd log parser.
SYSTEMUSERS_FTP_LOG_PATH = '%s'
""" """
format_docstring = (
settings.SYSTEMUSERS_FTP_LOG_PATH,
)
model = 'systemusers.SystemUser' model = 'systemusers.SystemUser'
resource = ServiceMonitor.TRAFFIC resource = ServiceMonitor.TRAFFIC
verbose_name = _('VsFTPd traffic') verbose_name = _('VsFTPd traffic')
script_executable = '/usr/bin/python' script_executable = '/usr/bin/python'
doc_settings = (settings,
('SYSTEMUSERS_FTP_LOG_PATH',)
)
def prepare(self): def prepare(self):
vsftplog = settings.SYSTEMUSERS_FTP_LOG_PATH vsftplog = settings.SYSTEMUSERS_FTP_LOG_PATH

View File

@ -12,6 +12,9 @@ class WebAppServiceMixin(object):
('webapps.WebAppOption', 'webapp'), ('webapps.WebAppOption', 'webapp'),
) )
directive = None directive = None
doc_settings = (settings,
('WEBAPPS_UNDER_CONSTRUCTION_PATH', 'WEBAPPS_MOVE_ON_DELETE_PATH',)
)
def create_webapp_dir(self, context): def create_webapp_dir(self, context):
self.append(textwrap.dedent("""\ self.append(textwrap.dedent("""\
@ -45,7 +48,7 @@ class WebAppServiceMixin(object):
'type': webapp.type, 'type': webapp.type,
'app_path': webapp.get_path(), 'app_path': webapp.get_path(),
'banner': self.get_banner(), '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(), 'is_mounted': webapp.content_set.exists(),
} }
context['deleted_app_path'] = settings.WEBAPPS_MOVE_ON_DELETE_PATH % context context['deleted_app_path'] = settings.WEBAPPS_MOVE_ON_DELETE_PATH % context

View File

@ -14,29 +14,21 @@ class PHPBackend(WebAppServiceMixin, ServiceController):
""" """
PHP support for apache-mod-fcgid and php-fpm. PHP support for apache-mod-fcgid and php-fpm.
It handles switching between these two PHP process management systemes. 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 = ( MERGE = settings.WEBAPPS_MERGE_PHP_WEBAPPS
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,
)
verbose_name = _("PHP FPM/FCGID") verbose_name = _("PHP FPM/FCGID")
default_route_match = "webapp.type.endswith('php')" 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): def save(self, webapp):
context = self.get_context(webapp) context = self.get_context(webapp)

View File

@ -12,22 +12,16 @@ from .. import settings
class uWSGIPythonBackend(WebAppServiceMixin, ServiceController): class uWSGIPythonBackend(WebAppServiceMixin, ServiceController):
""" """
Emperor mode <a href="http://uwsgi-docs.readthedocs.org/en/latest/Emperor.html">Emperor mode</a>
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
""" """
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") verbose_name = _("Python uWSGI")
default_route_match = "webapp.type.endswith('python')" 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): def save(self, webapp):
context = self.get_context(webapp) context = self.get_context(webapp)

View File

@ -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. 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") verbose_name = _("Symbolic link webapp")
model = 'webapps.WebApp' model = 'webapps.WebApp'
default_route_match = "webapp.type == 'symbolic-link'" default_route_match = "webapp.type == 'symbolic-link'"

View File

@ -14,15 +14,14 @@ class WordPressBackend(WebAppServiceMixin, ServiceController):
""" """
Installs the latest version of WordPress available on www.wordpress.org 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. 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") verbose_name = _("Wordpress")
model = 'webapps.WebApp' model = 'webapps.WebApp'
default_route_match = "webapp.type == 'wordpress-php'" default_route_match = "webapp.type == 'wordpress-php'"
script_executable = '/usr/bin/php' script_executable = '/usr/bin/php'
doc_settings = (settings,
('WEBAPPS_DEFAULT_MYSQL_DATABASE_HOST',)
)
def prepare(self): def prepare(self):
self.append(textwrap.dedent("""\ self.append(textwrap.dedent("""\

View File

@ -308,7 +308,7 @@ class PHPSuhosinExecutorIncludeWhitelist(PHPAppOption):
class PHPUploadMaxFileSize(PHPAppOption): class PHPUploadMaxFileSize(PHPAppOption):
name = 'upload_max_filesize' name = 'upload_max_filesize'
verbose_name = _("Upload max filezise") verbose_name = _("Upload max filesize")
help_text = _("Value between 0M and 999M.") help_text = _("Value between 0M and 999M.")
regex = r'^[0-9]{1,3}M$' regex = r'^[0-9]{1,3}M$'

View File

@ -13,6 +13,11 @@ from ..utils import normurlpath
class Apache2Backend(ServiceController): class Apache2Backend(ServiceController):
"""
Apache 2.4 backend with support for the following directives:
<tt>static</tt>, <tt>location</tt>, <tt>fpm</tt>, <tt>fcgid</tt>, <tt>uwsgi</tt>, \
<tt>ssl</tt>, <tt>security</tt>, <tt>redirects</tt>, <tt>proxies</tt>, <tt>saas</tt>
"""
HTTP_PORT = 80 HTTP_PORT = 80
HTTPS_PORT = 443 HTTPS_PORT = 443
@ -22,6 +27,15 @@ class Apache2Backend(ServiceController):
('webapps.WebApp', 'website_set'), ('webapps.WebApp', 'website_set'),
) )
verbose_name = _("Apache 2") 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): def render_virtual_host(self, site, context, ssl=False):
context['port'] = self.HTTPS_PORT if ssl else self.HTTP_PORT context['port'] = self.HTTPS_PORT if ssl else self.HTTP_PORT
@ -362,12 +376,14 @@ class Apache2Backend(ServiceController):
class Apache2Traffic(ServiceMonitor): class Apache2Traffic(ServiceMonitor):
""" """
Parses apache logs, 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' model = 'websites.Website'
resource = ServiceMonitor.TRAFFIC resource = ServiceMonitor.TRAFFIC
verbose_name = _("Apache 2 Traffic") verbose_name = _("Apache 2 Traffic")
doc_settings = (settings,
('WEBSITES_TRAFFIC_IGNORE_HOSTS',)
)
def prepare(self): def prepare(self):
super(Apache2Traffic, self).prepare() super(Apache2Traffic, self).prepare()
ignore_hosts = '\\|'.join(settings.WEBSITES_TRAFFIC_IGNORE_HOSTS) ignore_hosts = '\\|'.join(settings.WEBSITES_TRAFFIC_IGNORE_HOSTS)

View File

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