Added create link systemuser action
This commit is contained in:
parent
2b41593783
commit
acfa74d9ae
124
TODO.md
124
TODO.md
|
@ -455,8 +455,130 @@ mkhomedir_helper or create ssh homes with bash.rc and such
|
||||||
|
|
||||||
|
|
||||||
* setuppostgres use porject_name for db name and user instead of orchestra
|
* setuppostgres use porject_name for db name and user instead of orchestra
|
||||||
* show all available choices (plugins) on admin settings value i.e. payment methods
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# POSTFIX web traffic monitor '": uid=" from=<%(user)s>'
|
# POSTFIX web traffic monitor '": uid=" from=<%(user)s>'
|
||||||
|
|
||||||
|
|
||||||
|
### Quick start
|
||||||
|
0. Install orchestra following any of these methods:
|
||||||
|
1. [PIP-only, Fast deployment setup (demo)](README.md#fast-deployment-setup)
|
||||||
|
2. [Docker container (development)](INSTALLDEV.md)
|
||||||
|
3. [Install on current system (production)](INSTALL.md)
|
||||||
|
1. Add the servers that you want to manage on `/admin/orchestration/servers` and copy orchestra's SSH pubkey to them
|
||||||
|
`orchestra@panel:~ ssh-copy-id root@server.address`
|
||||||
|
2. Now configure service by service (domains, databases, webapps, websites...):
|
||||||
|
1. Add the route through `/admin/orchestration/route/`
|
||||||
|
2. Check and configure related settings on `/admin/settings/setting/`
|
||||||
|
3. Configure related resources if needed `/resources/resource/`, like Account Disc limit and traffic.
|
||||||
|
3. Test that everything works by creating and deleting services
|
||||||
|
4. Do the same for the other services
|
||||||
|
3. Configure billing by adding services `/admin/services/service/add/` and plans `/admin/plans/plan/`
|
||||||
|
1. Once a service is created hit the *Update orders* button
|
||||||
|
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
Orchestration
|
||||||
|
Orders
|
||||||
|
|
||||||
|
|
||||||
|
### Creating new services
|
||||||
|
1. Think about if the service can fit into one of the existing models like: SaaS or WebApps, refere to the related documentation if that is the case.
|
||||||
|
2. Create a new django app using startapp management command. For ilustrational purposes we will create a crontab services that will allow orchestra to manage user-based crontabs.
|
||||||
|
`python3 manage.py startapp crontabs`
|
||||||
|
3. Add the new *crontabs* app to the `INSTALLED_APPS` in your project's `settings.py`
|
||||||
|
3. Create a `models.py` file with the data your service needs to keep in order to be managed by orchestra
|
||||||
|
```python
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class CrontabSchedule(models.Model):
|
||||||
|
account = models.ForeignKey('accounts.Account', verbose_name=_("account"))
|
||||||
|
minute = models.CharField(_("minute"), max_length=64, default='*')
|
||||||
|
hour = models.CharField(_("hour"), max_length=64, default='*')
|
||||||
|
day_of_week = models.CharField(_("day of week"), max_length=64, default='*')
|
||||||
|
day_of_month = models.CharField(_("day of month"), max_length=64, default='*')
|
||||||
|
month_of_year = models.CharField(_("month of year"), max_length=64, default='*')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('month_of_year', 'day_of_month', 'day_of_week', 'hour', 'minute')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
rfield = lambda f: f and str(f).replace(' ', '') or '*'
|
||||||
|
return "{0} {1} {2} {3} {4} (m/h/d/dM/MY)".format(
|
||||||
|
rfield(self.minute), rfield(self.hour), rfield(self.day_of_week),
|
||||||
|
rfield(self.day_of_month), rfield(self.month_of_year),
|
||||||
|
)
|
||||||
|
|
||||||
|
class Crontab(models.Model):
|
||||||
|
account = models.ForeignKey('accounts.Account', verbose_name=_("account"))
|
||||||
|
schedule = models.ForeignKey(CrontabSchedule, verbose_name=_("schedule"))
|
||||||
|
description = models.CharField(_("description"), max_length=256, blank=True)
|
||||||
|
command = models.TextField(_("content"))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return (self.description or self.command)[:32]
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Create a `admin.py` to enable the admin interface
|
||||||
|
```python
|
||||||
|
from django.contrib import admin
|
||||||
|
from .models import CrontabSchedule, Crontab
|
||||||
|
|
||||||
|
class CrontabScheduleAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class CrontabAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|
||||||
|
admin.site.register(CrontabSchedule, CrontabScheduleAdmin)
|
||||||
|
admin.site.register(Crontab, CrontabAdmin)
|
||||||
|
|
||||||
|
|
||||||
|
5. Create a `api.py` to enable the REST API.
|
||||||
|
6. Create a `backends.py` fiel with the needed backends for service orchestration and monitoring
|
||||||
|
```python
|
||||||
|
import os
|
||||||
|
import textwrap
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from orchestra.contrib.orchestration import ServiceController, replace
|
||||||
|
from orchestra.contrib.resources import ServiceMonitor
|
||||||
|
|
||||||
|
class UNIXCronBackend(ServiceController):
|
||||||
|
"""
|
||||||
|
Basic UNIX cron support.
|
||||||
|
"""
|
||||||
|
verbose_name = _("UNIX cron")
|
||||||
|
model = 'crons.CronTab'
|
||||||
|
|
||||||
|
def prepare(self):
|
||||||
|
super(UNIXCronBackend, self).prepare()
|
||||||
|
self.accounts = set()
|
||||||
|
|
||||||
|
def save(self, crontab):
|
||||||
|
self.accounts.add(crontab.account)
|
||||||
|
|
||||||
|
def delete(self, crontab):
|
||||||
|
self.accounts.add(crontab.account)
|
||||||
|
|
||||||
|
def commit(self):
|
||||||
|
for account in self.accounts:
|
||||||
|
crontab = None
|
||||||
|
self.append("echo '' > %(crontab_path)s" % context)
|
||||||
|
chown
|
||||||
|
for crontab in account.crontabs.all():
|
||||||
|
self.append("
|
||||||
|
# if crontab is None:
|
||||||
|
# self.append("rm -f %(crontab_path)s" % context)
|
||||||
|
```
|
||||||
|
7. Configure the routing
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,8 @@ class Mailbox(models.Model):
|
||||||
CUSTOM = 'CUSTOM'
|
CUSTOM = 'CUSTOM'
|
||||||
|
|
||||||
name = models.CharField(_("name"), max_length=64, unique=True,
|
name = models.CharField(_("name"), max_length=64, unique=True,
|
||||||
help_text=_("Required. 30 characters or fewer. Letters, digits and ./-/_ only."),
|
help_text=_("Required. %s characters or fewer. Letters, digits and ./-/_ only.") %
|
||||||
|
settings.MAILBOXES_NAME_MAX_LENGTH,
|
||||||
validators=[
|
validators=[
|
||||||
RegexValidator(r'^[\w.-]+$', _("Enter a valid mailbox name.")),
|
RegexValidator(r'^[\w.-]+$', _("Enter a valid mailbox name.")),
|
||||||
])
|
])
|
||||||
|
|
|
@ -21,8 +21,8 @@ MAILBOXES_DOMAIN_MODEL = Setting('MAILBOXES_DOMAIN_MODEL', 'domains.Domain',
|
||||||
|
|
||||||
|
|
||||||
MAILBOXES_NAME_MAX_LENGTH = Setting('MAILBOXES_NAME_MAX_LENGTH',
|
MAILBOXES_NAME_MAX_LENGTH = Setting('MAILBOXES_NAME_MAX_LENGTH',
|
||||||
64,
|
32,
|
||||||
help_text=_("Limit for system user based mailbox on Linux should be 32.")
|
help_text=_("Limit for system user based mailbox on Linux is 32.")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ class MiscServiceAdmin(ExtendedModelAdmin):
|
||||||
""" return num slivers as a link to slivers changelist view """
|
""" return num slivers as a link to slivers changelist view """
|
||||||
num = misc.instances__count
|
num = misc.instances__count
|
||||||
url = reverse('admin:miscellaneous_miscellaneous_changelist')
|
url = reverse('admin:miscellaneous_miscellaneous_changelist')
|
||||||
url += '?service={}'.format(misc.pk)
|
url += '?service__name={}'.format(misc.name)
|
||||||
return mark_safe('<a href="{0}">{1}</a>'.format(url, num))
|
return mark_safe('<a href="{0}">{1}</a>'.format(url, num))
|
||||||
num_instances.short_description = _("Instances")
|
num_instances.short_description = _("Instances")
|
||||||
num_instances.admin_order_field = 'instances__count'
|
num_instances.admin_order_field = 'instances__count'
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
import importlib
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
from dateutil import relativedelta
|
from dateutil import relativedelta
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
|
||||||
|
@ -8,7 +10,7 @@ from orchestra.utils.python import import_class
|
||||||
from .. import settings
|
from .. import settings
|
||||||
|
|
||||||
|
|
||||||
class PaymentMethod(plugins.Plugin):
|
class PaymentMethod(plugins.Plugin, metaclass=plugins.PluginMount):
|
||||||
label_field = 'label'
|
label_field = 'label'
|
||||||
number_field = 'number'
|
number_field = 'number'
|
||||||
process_credit = False
|
process_credit = False
|
||||||
|
@ -18,10 +20,16 @@ class PaymentMethod(plugins.Plugin):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@lru_cache()
|
@lru_cache()
|
||||||
def get_plugins(cls):
|
def get_plugins(cls, all=False):
|
||||||
plugins = []
|
if all:
|
||||||
for cls in settings.PAYMENTS_ENABLED_METHODS:
|
for module in os.listdir(os.path.dirname(__file__)):
|
||||||
plugins.append(import_class(cls))
|
if module not in ('options.py', '__init__.py') and module[-3:] == '.py':
|
||||||
|
importlib.import_module('.'+module[:-3], __package__)
|
||||||
|
plugins = super().get_plugins()
|
||||||
|
else:
|
||||||
|
plugins = []
|
||||||
|
for cls in settings.PAYMENTS_ENABLED_METHODS:
|
||||||
|
plugins.append(import_class(cls))
|
||||||
return plugins
|
return plugins
|
||||||
|
|
||||||
def get_label(self):
|
def get_label(self):
|
||||||
|
|
|
@ -20,7 +20,7 @@ logger = logging.getLogger(__name__)
|
||||||
try:
|
try:
|
||||||
import lxml
|
import lxml
|
||||||
except ImportError:
|
except ImportError:
|
||||||
logger.error('Error loading lxml, module not install')
|
logger.error('Error loading lxml, module not installed.')
|
||||||
|
|
||||||
|
|
||||||
class SEPADirectDebitForm(PluginDataForm):
|
class SEPADirectDebitForm(PluginDataForm):
|
||||||
|
|
|
@ -41,6 +41,6 @@ PAYMENTS_ENABLED_METHODS = Setting('PAYMENTS_ENABLED_METHODS',
|
||||||
'orchestra.contrib.payments.methods.creditcard.CreditCard',
|
'orchestra.contrib.payments.methods.creditcard.CreditCard',
|
||||||
),
|
),
|
||||||
# lazy loading
|
# lazy loading
|
||||||
choices=lambda : ((m.get_class_path(), m.get_class_path()) for m in payments.methods.PaymentMethod.get_plugins()),
|
choices=lambda : ((m.get_class_path(), m.get_class_path()) for m in payments.methods.PaymentMethod.get_plugins(all=True)),
|
||||||
multiple=True,
|
multiple=True,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.admin import ExtendedModelAdmin
|
from orchestra.admin import ExtendedModelAdmin
|
||||||
from orchestra.admin.utils import insertattr
|
from orchestra.admin.utils import insertattr, admin_link
|
||||||
from orchestra.contrib.accounts.actions import list_accounts
|
from orchestra.contrib.accounts.actions import list_accounts
|
||||||
from orchestra.contrib.accounts.admin import AccountAdminMixin
|
from orchestra.contrib.accounts.admin import AccountAdminMixin
|
||||||
from orchestra.contrib.services.models import Service
|
from orchestra.contrib.services.models import Service
|
||||||
|
@ -15,7 +18,9 @@ class RateInline(admin.TabularInline):
|
||||||
|
|
||||||
|
|
||||||
class PlanAdmin(ExtendedModelAdmin):
|
class PlanAdmin(ExtendedModelAdmin):
|
||||||
list_display = ('name', 'is_default', 'is_combinable', 'allow_multiple', 'is_active')
|
list_display = (
|
||||||
|
'name', 'is_default', 'is_combinable', 'allow_multiple', 'is_active', 'num_contracts',
|
||||||
|
)
|
||||||
list_filter = ('is_default', 'is_combinable', 'allow_multiple', 'is_active')
|
list_filter = ('is_default', 'is_combinable', 'allow_multiple', 'is_active')
|
||||||
fields = ('verbose_name', 'name', 'is_default', 'is_combinable', 'allow_multiple')
|
fields = ('verbose_name', 'name', 'is_default', 'is_combinable', 'allow_multiple')
|
||||||
prepopulated_fields = {
|
prepopulated_fields = {
|
||||||
|
@ -23,14 +28,29 @@ class PlanAdmin(ExtendedModelAdmin):
|
||||||
}
|
}
|
||||||
change_readonly_fields = ('name',)
|
change_readonly_fields = ('name',)
|
||||||
inlines = [RateInline]
|
inlines = [RateInline]
|
||||||
|
|
||||||
|
def num_contracts(self, plan):
|
||||||
|
num = plan.contracts__count
|
||||||
|
url = reverse('admin:plans_contractedplan_changelist')
|
||||||
|
url += '?plan__name={}'.format(plan.name)
|
||||||
|
return '<a href="{0}">{1}</a>'.format(url, num)
|
||||||
|
num_contracts.short_description = _("Contracts")
|
||||||
|
num_contracts.admin_order_field = 'contracts__count'
|
||||||
|
num_contracts.allow_tags = True
|
||||||
|
|
||||||
|
def get_queryset(self, request):
|
||||||
|
qs = super(PlanAdmin, self).get_queryset(request)
|
||||||
|
return qs.annotate(models.Count('contracts', distinct=True))
|
||||||
|
|
||||||
|
|
||||||
class ContractedPlanAdmin(AccountAdminMixin, admin.ModelAdmin):
|
class ContractedPlanAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||||
list_display = ('plan', 'account_link')
|
list_display = ('id', 'plan_link', 'account_link')
|
||||||
list_filter = ('plan__name',)
|
list_filter = ('plan__name',)
|
||||||
list_select_related = ('plan', 'account')
|
list_select_related = ('plan', 'account')
|
||||||
search_fields = ('account__username', 'plan__name', 'id')
|
search_fields = ('account__username', 'plan__name', 'id')
|
||||||
actions = (list_accounts,)
|
actions = (list_accounts,)
|
||||||
|
|
||||||
|
plan_link = admin_link('plan')
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Plan, PlanAdmin)
|
admin.site.register(Plan, PlanAdmin)
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import importlib
|
||||||
|
import os
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
@ -17,7 +19,7 @@ from .. import settings
|
||||||
from ..forms import SaaSPasswordForm
|
from ..forms import SaaSPasswordForm
|
||||||
|
|
||||||
|
|
||||||
class SoftwareService(plugins.Plugin):
|
class SoftwareService(plugins.Plugin, metaclass=plugins.PluginMount):
|
||||||
PROTOCOL_MAP = {
|
PROTOCOL_MAP = {
|
||||||
'http': (Website.HTTP, (Website.HTTP, Website.HTTP_AND_HTTPS)),
|
'http': (Website.HTTP, (Website.HTTP, Website.HTTP_AND_HTTPS)),
|
||||||
'https': (Website.HTTPS_ONLY, (Website.HTTPS, Website.HTTP_AND_HTTPS, Website.HTTPS_ONLY)),
|
'https': (Website.HTTPS_ONLY, (Website.HTTPS, Website.HTTP_AND_HTTPS, Website.HTTPS_ONLY)),
|
||||||
|
@ -35,10 +37,16 @@ class SoftwareService(plugins.Plugin):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@lru_cache()
|
@lru_cache()
|
||||||
def get_plugins(cls):
|
def get_plugins(cls, all=False):
|
||||||
plugins = []
|
if all:
|
||||||
for cls in settings.SAAS_ENABLED_SERVICES:
|
for module in os.listdir(os.path.dirname(__file__)):
|
||||||
plugins.append(import_class(cls))
|
if module not in ('options.py', '__init__.py') and module[-3:] == '.py':
|
||||||
|
importlib.import_module('.'+module[:-3], __package__)
|
||||||
|
plugins = super().get_plugins()
|
||||||
|
else:
|
||||||
|
plugins = []
|
||||||
|
for cls in settings.SAAS_ENABLED_SERVICES:
|
||||||
|
plugins.append(import_class(cls))
|
||||||
return plugins
|
return plugins
|
||||||
|
|
||||||
def get_change_readonly_fileds(cls):
|
def get_change_readonly_fileds(cls):
|
||||||
|
@ -143,6 +151,7 @@ class SoftwareService(plugins.Plugin):
|
||||||
class DBSoftwareService(SoftwareService):
|
class DBSoftwareService(SoftwareService):
|
||||||
db_name = None
|
db_name = None
|
||||||
db_user = None
|
db_user = None
|
||||||
|
abstract = True
|
||||||
|
|
||||||
def get_db_name(self):
|
def get_db_name(self):
|
||||||
context = {
|
context = {
|
||||||
|
|
|
@ -21,7 +21,7 @@ SAAS_ENABLED_SERVICES = Setting('SAAS_ENABLED_SERVICES',
|
||||||
# 'orchestra.contrib.saas.services.seafile.SeaFileService',
|
# 'orchestra.contrib.saas.services.seafile.SeaFileService',
|
||||||
),
|
),
|
||||||
# lazy loading
|
# lazy loading
|
||||||
choices=lambda: ((s.get_class_path(), s.get_class_path()) for s in saas.services.SoftwareService.get_plugins()),
|
choices=lambda: ((s.get_class_path(), s.get_class_path()) for s in saas.services.SoftwareService.get_plugins(all=True)),
|
||||||
multiple=True,
|
multiple=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@ class ServiceAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
|
||||||
def num_orders(self, service):
|
def num_orders(self, service):
|
||||||
num = service.orders__count
|
num = service.orders__count
|
||||||
url = reverse('admin:orders_order_changelist')
|
url = reverse('admin:orders_order_changelist')
|
||||||
url += '?service=%i&is_active=True' % service.pk
|
url += '?service__id__exact=%i&is_active=True' % service.pk
|
||||||
return '<a href="%s">%d</a>' % (url, num)
|
return '<a href="%s">%d</a>' % (url, num)
|
||||||
num_orders.short_description = _("Orders")
|
num_orders.short_description = _("Orders")
|
||||||
num_orders.admin_order_field = 'orders__count'
|
num_orders.admin_order_field = 'orders__count'
|
||||||
|
|
|
@ -7,7 +7,7 @@ from django.utils.translation import ungettext, ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.contrib.orchestration import Operation, helpers
|
from orchestra.contrib.orchestration import Operation, helpers
|
||||||
|
|
||||||
from .forms import PermissionForm
|
from .forms import PermissionForm, LinkForm
|
||||||
|
|
||||||
|
|
||||||
def get_verbose_choice(choices, value):
|
def get_verbose_choice(choices, value):
|
||||||
|
@ -21,11 +21,12 @@ def set_permission(modeladmin, request, queryset):
|
||||||
for user in queryset:
|
for user in queryset:
|
||||||
account_id = account_id or user.account_id
|
account_id = account_id or user.account_id
|
||||||
if user.account_id != account_id:
|
if user.account_id != account_id:
|
||||||
messages.error("Users from the same account should be selected.")
|
messages.error(request, "Users from the same account should be selected.")
|
||||||
|
return
|
||||||
user = queryset[0]
|
user = queryset[0]
|
||||||
form = PermissionForm(user)
|
form = PermissionForm(user)
|
||||||
action_value = 'set_permission'
|
action_value = 'set_permission'
|
||||||
if request.POST.get('action') == action_value:
|
if request.POST.get('post') == 'generic_confirmation':
|
||||||
form = PermissionForm(user, request.POST)
|
form = PermissionForm(user, request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
cleaned_data = form.cleaned_data
|
cleaned_data = form.cleaned_data
|
||||||
|
@ -35,7 +36,10 @@ def set_permission(modeladmin, request, queryset):
|
||||||
extension = cleaned_data['home_extension']
|
extension = cleaned_data['home_extension']
|
||||||
action = cleaned_data['set_action']
|
action = cleaned_data['set_action']
|
||||||
perms = cleaned_data['permissions']
|
perms = cleaned_data['permissions']
|
||||||
user.set_permission(base_home, extension, action=action, perms=perms)
|
user.set_perm_action = action
|
||||||
|
user.set_perm_base_home = base_home
|
||||||
|
user.set_perm_home_extension = extension
|
||||||
|
user.set_perm_perms = perms
|
||||||
operations.extend(Operation.create_for_action(user, 'set_permission'))
|
operations.extend(Operation.create_for_action(user, 'set_permission'))
|
||||||
verbose_action = get_verbose_choice(form.fields['set_action'].choices,
|
verbose_action = get_verbose_choice(form.fields['set_action'].choices,
|
||||||
user.set_perm_action)
|
user.set_perm_action)
|
||||||
|
@ -48,8 +52,11 @@ def set_permission(modeladmin, request, queryset):
|
||||||
}
|
}
|
||||||
msg = _("%(action)s %(perms)s permission to %(to)s") % context
|
msg = _("%(action)s %(perms)s permission to %(to)s") % context
|
||||||
modeladmin.log_change(request, user, msg)
|
modeladmin.log_change(request, user, msg)
|
||||||
logs = Operation.execute(operations)
|
if not operations:
|
||||||
helpers.message_user(request, logs)
|
messages.error(request, "No backend operation has been executed.")
|
||||||
|
else:
|
||||||
|
logs = Operation.execute(operations)
|
||||||
|
helpers.message_user(request, logs)
|
||||||
return
|
return
|
||||||
opts = modeladmin.model._meta
|
opts = modeladmin.model._meta
|
||||||
app_label = opts.app_label
|
app_label = opts.app_label
|
||||||
|
@ -70,6 +77,60 @@ set_permission.url_name = 'set-permission'
|
||||||
set_permission.tool_description = _("Set permission")
|
set_permission.tool_description = _("Set permission")
|
||||||
|
|
||||||
|
|
||||||
|
def create_link(modeladmin, request, queryset):
|
||||||
|
account_id = None
|
||||||
|
for user in queryset:
|
||||||
|
account_id = account_id or user.account_id
|
||||||
|
if user.account_id != account_id:
|
||||||
|
messages.error(request, "Users from the same account should be selected.")
|
||||||
|
return
|
||||||
|
user = queryset[0]
|
||||||
|
form = LinkForm(user)
|
||||||
|
action_value = 'create_link'
|
||||||
|
if request.POST.get('post') == 'generic_confirmation':
|
||||||
|
form = LinkForm(user, request.POST, queryset=queryset)
|
||||||
|
if form.is_valid():
|
||||||
|
cleaned_data = form.cleaned_data
|
||||||
|
operations = []
|
||||||
|
for user in queryset:
|
||||||
|
base_home = cleaned_data['base_home']
|
||||||
|
extension = cleaned_data['home_extension']
|
||||||
|
target = os.path.join(base_home, extension)
|
||||||
|
link_name = cleaned_data['link_name'] or os.path.join(user.home, os.path.basename(target))
|
||||||
|
user.create_link_target = target
|
||||||
|
user.create_link_name = link_name
|
||||||
|
operations.extend(Operation.create_for_action(user, 'create_link'))
|
||||||
|
context = {
|
||||||
|
'target': target,
|
||||||
|
'link_name': link_name,
|
||||||
|
}
|
||||||
|
msg = _("Created link from %(target)s to %(link_name)s") % context
|
||||||
|
modeladmin.log_change(request, request.user, msg)
|
||||||
|
logs = Operation.execute(operations)
|
||||||
|
if logs:
|
||||||
|
helpers.message_user(request, logs)
|
||||||
|
else:
|
||||||
|
messages.error(request, "No backend operation has been executed.")
|
||||||
|
return
|
||||||
|
opts = modeladmin.model._meta
|
||||||
|
app_label = opts.app_label
|
||||||
|
context = {
|
||||||
|
'title': _("Create link"),
|
||||||
|
'action_name': _("Create link"),
|
||||||
|
'action_value': action_value,
|
||||||
|
'queryset': queryset,
|
||||||
|
'opts': opts,
|
||||||
|
'obj': user,
|
||||||
|
'app_label': app_label,
|
||||||
|
'action_checkbox_name': admin.helpers.ACTION_CHECKBOX_NAME,
|
||||||
|
'form': form,
|
||||||
|
}
|
||||||
|
return TemplateResponse(request, 'admin/systemusers/systemuser/create_link.html',
|
||||||
|
context, current_app=modeladmin.admin_site.name)
|
||||||
|
create_link.url_name = 'create-link'
|
||||||
|
create_link.tool_description = _("Create link")
|
||||||
|
|
||||||
|
|
||||||
def delete_selected(modeladmin, request, queryset):
|
def delete_selected(modeladmin, request, queryset):
|
||||||
""" wrapper arround admin.actions.delete_selected to prevent main system users deletion """
|
""" wrapper arround admin.actions.delete_selected to prevent main system users deletion """
|
||||||
opts = modeladmin.model._meta
|
opts = modeladmin.model._meta
|
||||||
|
|
|
@ -6,7 +6,7 @@ from orchestra.contrib.accounts.actions import list_accounts
|
||||||
from orchestra.contrib.accounts.admin import SelectAccountAdminMixin
|
from orchestra.contrib.accounts.admin import SelectAccountAdminMixin
|
||||||
from orchestra.contrib.accounts.filters import IsActiveListFilter
|
from orchestra.contrib.accounts.filters import IsActiveListFilter
|
||||||
|
|
||||||
from .actions import set_permission, delete_selected
|
from .actions import set_permission, create_link, delete_selected
|
||||||
from .filters import IsMainListFilter
|
from .filters import IsMainListFilter
|
||||||
from .forms import SystemUserCreationForm, SystemUserChangeForm
|
from .forms import SystemUserCreationForm, SystemUserChangeForm
|
||||||
from .models import SystemUser
|
from .models import SystemUser
|
||||||
|
@ -41,7 +41,7 @@ class SystemUserAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, Extende
|
||||||
add_form = SystemUserCreationForm
|
add_form = SystemUserCreationForm
|
||||||
form = SystemUserChangeForm
|
form = SystemUserChangeForm
|
||||||
ordering = ('-id',)
|
ordering = ('-id',)
|
||||||
change_view_actions = (set_permission,)
|
change_view_actions = (set_permission, create_link)
|
||||||
actions = (delete_selected, list_accounts) + change_view_actions
|
actions = (delete_selected, list_accounts) + change_view_actions
|
||||||
|
|
||||||
def display_main(self, user):
|
def display_main(self, user):
|
||||||
|
|
|
@ -17,7 +17,7 @@ class UNIXUserBackend(ServiceController):
|
||||||
"""
|
"""
|
||||||
verbose_name = _("UNIX user")
|
verbose_name = _("UNIX user")
|
||||||
model = 'systemusers.SystemUser'
|
model = 'systemusers.SystemUser'
|
||||||
actions = ('save', 'delete', 'set_permission', 'validate_path_exists')
|
actions = ('save', 'delete', 'set_permission', 'validate_path_exists', 'create_link')
|
||||||
doc_settings = (settings, (
|
doc_settings = (settings, (
|
||||||
'SYSTEMUSERS_DEFAULT_GROUP_MEMBERS',
|
'SYSTEMUSERS_DEFAULT_GROUP_MEMBERS',
|
||||||
'SYSTEMUSERS_MOVE_ON_DELETE_PATH',
|
'SYSTEMUSERS_MOVE_ON_DELETE_PATH',
|
||||||
|
@ -189,6 +189,24 @@ class UNIXUserBackend(ServiceController):
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def create_link(self, user):
|
||||||
|
context = self.get_context(user)
|
||||||
|
context.update({
|
||||||
|
'link_target': user.create_link_target,
|
||||||
|
'link_name': user.create_link_name,
|
||||||
|
})
|
||||||
|
self.append(textwrap.dedent("""\
|
||||||
|
# Create link
|
||||||
|
su %(user)s --shell /bin/bash << 'EOF' || exit_code=1
|
||||||
|
if [[ ! -e %(link_name)s ]]; then
|
||||||
|
ln -s %(link_target)s %(link_name)s
|
||||||
|
else
|
||||||
|
echo "%(link_name)s already exists, doing nothing." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
EOF""") % context
|
||||||
|
)
|
||||||
|
|
||||||
def validate_path_exists(self, user):
|
def validate_path_exists(self, user):
|
||||||
context = {
|
context = {
|
||||||
'path': user.path_to_validate,
|
'path': user.path_to_validate,
|
||||||
|
|
|
@ -9,7 +9,7 @@ from orchestra.forms import UserCreationForm, UserChangeForm
|
||||||
|
|
||||||
from . import settings
|
from . import settings
|
||||||
from .models import SystemUser
|
from .models import SystemUser
|
||||||
from .validators import validate_home, validate_path_exists
|
from .validators import validate_home, validate_paths_exist
|
||||||
|
|
||||||
|
|
||||||
class SystemUserFormMixin(object):
|
class SystemUserFormMixin(object):
|
||||||
|
@ -89,7 +89,56 @@ class SystemUserChangeForm(SystemUserFormMixin, UserChangeForm):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class PermissionForm(forms.Form):
|
class LinkForm(forms.Form):
|
||||||
|
base_home = forms.ChoiceField(label=_("Target path"), choices=(),
|
||||||
|
help_text=_("Target link will be under this directory."))
|
||||||
|
home_extension = forms.CharField(label=_("Home extension"), required=False, initial='',
|
||||||
|
widget=forms.TextInput(attrs={'size':'70'}), help_text=_("Relative to chosen home."))
|
||||||
|
link_name = forms.CharField(label=_("Link name"), required=False, initial='',
|
||||||
|
widget=forms.TextInput(attrs={'size':'70'}),
|
||||||
|
help_text=_("If left blank or relative path: link will be created in each user home."))
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.instance = args[0]
|
||||||
|
self.queryset = kwargs.pop('queryset', [])
|
||||||
|
super_args = []
|
||||||
|
if len(args) > 1:
|
||||||
|
super_args.append(args[1])
|
||||||
|
super(LinkForm, self).__init__(*super_args, **kwargs)
|
||||||
|
related_users = type(self.instance).objects.filter(account=self.instance.account_id)
|
||||||
|
self.fields['base_home'].choices = (
|
||||||
|
(user.get_base_home(), user.get_base_home()) for user in related_users
|
||||||
|
)
|
||||||
|
|
||||||
|
def clean_home_extension(self):
|
||||||
|
home_extension = self.cleaned_data['home_extension']
|
||||||
|
return home_extension.lstrip('/')
|
||||||
|
|
||||||
|
def clean_link_name(self):
|
||||||
|
link_name = self.cleaned_data['link_name']
|
||||||
|
if link_name:
|
||||||
|
if link_name.startswith('/'):
|
||||||
|
if len(self.queryset) > 1:
|
||||||
|
raise ValidationError(_("Link name can not be a full path when multiple users."))
|
||||||
|
link_names = [os.path.dirname(link_name)]
|
||||||
|
else:
|
||||||
|
link_names = [os.path.join(user.home, os.path.dirname(link_names)) for user in self.queryset]
|
||||||
|
validate_paths_exist(self.instance, link_names)
|
||||||
|
return link_name
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
cleaned_data = super(LinkForm, self).clean()
|
||||||
|
path = os.path.join(cleaned_data['base_home'], cleaned_data['home_extension'])
|
||||||
|
try:
|
||||||
|
validate_paths_exist(self.instance, [path])
|
||||||
|
except ValidationError as err:
|
||||||
|
raise ValidationError({
|
||||||
|
'home_extension': err,
|
||||||
|
})
|
||||||
|
return cleaned_data
|
||||||
|
|
||||||
|
|
||||||
|
class PermissionForm(LinkForm):
|
||||||
set_action = forms.ChoiceField(label=_("Action"), initial='grant',
|
set_action = forms.ChoiceField(label=_("Action"), initial='grant',
|
||||||
choices=(
|
choices=(
|
||||||
('grant', _("Grant")),
|
('grant', _("Grant")),
|
||||||
|
@ -105,29 +154,3 @@ class PermissionForm(forms.Form):
|
||||||
('r', _("Read only")),
|
('r', _("Read only")),
|
||||||
('w', _("Write only"))
|
('w', _("Write only"))
|
||||||
))
|
))
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
self.instance = args[0]
|
|
||||||
super_args = []
|
|
||||||
if len(args) > 1:
|
|
||||||
super_args.append(args[1])
|
|
||||||
super(PermissionForm, self).__init__(*super_args, **kwargs)
|
|
||||||
related_users = type(self.instance).objects.filter(account=self.instance.account_id)
|
|
||||||
self.fields['base_home'].choices = (
|
|
||||||
(user.get_base_home(), user.get_base_home()) for user in related_users
|
|
||||||
)
|
|
||||||
|
|
||||||
def clean_home_extension(self):
|
|
||||||
home_extension = self.cleaned_data['home_extension']
|
|
||||||
return home_extension.lstrip('/')
|
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
cleaned_data = super(PermissionForm, self).clean()
|
|
||||||
path = os.path.join(cleaned_data['base_home'], cleaned_data['home_extension'])
|
|
||||||
try:
|
|
||||||
validate_path_exists(self.instance, path)
|
|
||||||
except ValidationError as err:
|
|
||||||
raise ValidationError({
|
|
||||||
'home_extension': err,
|
|
||||||
})
|
|
||||||
return cleaned_data
|
|
||||||
|
|
|
@ -123,12 +123,6 @@ class SystemUser(models.Model):
|
||||||
def set_password(self, raw_password):
|
def set_password(self, raw_password):
|
||||||
self.password = make_password(raw_password)
|
self.password = make_password(raw_password)
|
||||||
|
|
||||||
def set_permission(self, base_home, extension, perms='rw', action='grant'):
|
|
||||||
self.set_perm_action = action
|
|
||||||
self.set_perm_base_home = base_home
|
|
||||||
self.set_perm_home_extension = extension
|
|
||||||
self.set_perm_perms = perms
|
|
||||||
|
|
||||||
def get_base_home(self):
|
def get_base_home(self):
|
||||||
context = {
|
context = {
|
||||||
'user': self.username,
|
'user': self.username,
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
{% extends "admin/base_site.html" %}
|
||||||
|
{% load i18n l10n %}
|
||||||
|
{% load url from future %}
|
||||||
|
{% load admin_urls static utils %}
|
||||||
|
|
||||||
|
{% block extrastyle %}
|
||||||
|
{{ block.super }}
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static "orchestra/css/hide-inline-id.css" %}" />
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block breadcrumbs %}
|
||||||
|
<div class="breadcrumbs">
|
||||||
|
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
|
||||||
|
› <a href="{% url 'admin:app_list' app_label=app_label %}">{{ app_label|capfirst|escape }}</a>
|
||||||
|
› <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
|
||||||
|
{% if obj %}
|
||||||
|
› <a href="{% url opts|admin_urlname:'change' obj.pk %}">{{ obj }}</a>
|
||||||
|
› {{ action_name }}
|
||||||
|
{% elif add %}
|
||||||
|
› <a href="../">{% trans "Add" %} {{ opts.verbose_name }}</a>
|
||||||
|
› {{ action_name }}
|
||||||
|
{% else %}
|
||||||
|
› {{ action_name }} multiple objects
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div>
|
||||||
|
<div style="margin:20px;">
|
||||||
|
{% block introduction %}
|
||||||
|
Create link for {% for user in queryset %}{{ user.username }}{% if not forloop.last %}, {% endif %}{% endfor %}.
|
||||||
|
{% endblock %}
|
||||||
|
<ul>{{ display_objects | unordered_list }}</ul>
|
||||||
|
<form action="" method="post">{% csrf_token %}
|
||||||
|
<fieldset class="module aligned wide">
|
||||||
|
{{ form.non_field_errors }}
|
||||||
|
{% block prefields %}
|
||||||
|
{% endblock %}
|
||||||
|
<div class="form-row ">
|
||||||
|
<div class="field-box field-base_home">
|
||||||
|
{{ form.base_home.errors }}
|
||||||
|
<label for="{{ form.base_home.id_for_label }}"><b>{{ form.base_home.label }}</b>:</label>
|
||||||
|
{{ form.base_home }}{% for x in ""|ljust:"50" %} {% endfor %}
|
||||||
|
<p class="help">{{ form.base_home.help_text|safe }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="field-box field-user_extension">
|
||||||
|
{{ form.home_extension.errors }}
|
||||||
|
<label for="{{ form.home_extension.id_for_label }}"></label>
|
||||||
|
{{ form.home_extension }}
|
||||||
|
<p class="help">{{ form.home_extension.help_text|safe }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% block postfields %}
|
||||||
|
<div class="form-row ">
|
||||||
|
{{ form.link_name.errors }}
|
||||||
|
<label for="{{ form.link_name.id_for_label }}">{{ form.link_name.label }}:</label>
|
||||||
|
{{ form.link_name }}
|
||||||
|
<p class="help">{{ form.link_name.help_text|safe }}</p>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
</fieldset>
|
||||||
|
<div>
|
||||||
|
{% for obj in queryset %}
|
||||||
|
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk|unlocalize }}" />
|
||||||
|
{% endfor %}
|
||||||
|
<input type="hidden" name="action" value="{{ action_value }}" />
|
||||||
|
<input type="hidden" name="post" value="{{ post_value|default:'generic_confirmation' }}" />
|
||||||
|
<input type="submit" value="{{ submit_value|default:_("Save") }}" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -1,74 +1,27 @@
|
||||||
{% extends "admin/base_site.html" %}
|
{% extends "admin/systemusers/systemuser/create_link.html" %}
|
||||||
{% load i18n l10n %}
|
{% load i18n l10n %}
|
||||||
{% load url from future %}
|
{% load url from future %}
|
||||||
{% load admin_urls static utils %}
|
{% load admin_urls static utils %}
|
||||||
|
|
||||||
{% block extrastyle %}
|
{% block introduction %}
|
||||||
{{ block.super }}
|
Set permissions for {% for user in queryset %}{{ user.username }}{% if not forloop.last %}, {% endif %}{% endfor %} system user(s).
|
||||||
<link rel="stylesheet" type="text/css" href="{% static "admin/css/forms.css" %}" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="{% static "orchestra/css/hide-inline-id.css" %}" />
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block breadcrumbs %}
|
|
||||||
<div class="breadcrumbs">
|
{% block prefields %}
|
||||||
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
|
<div class="form-row ">
|
||||||
› <a href="{% url 'admin:app_list' app_label=app_label %}">{{ app_label|capfirst|escape }}</a>
|
{{ form.set_action.errors }}
|
||||||
› <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
|
<label for="{{ form.set_action.id_for_label }}">{{ form.set_action.label }}:</label>
|
||||||
{% if obj %}
|
{{ form.set_action }}{% for x in ""|ljust:"50" %} {% endfor %}
|
||||||
› <a href="{% url opts|admin_urlname:'change' obj.pk %}">{{ obj }}</a>
|
<p class="help">{{ form.set_action.help_text|safe }}</p>
|
||||||
› {{ action_name }}
|
|
||||||
{% elif add %}
|
|
||||||
› <a href="../">{% trans "Add" %} {{ opts.verbose_name }}</a>
|
|
||||||
› {{ action_name }}
|
|
||||||
{% else %}
|
|
||||||
› {{ action_name }} multiple objects
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block postfields %}
|
||||||
<div>
|
<div class="form-row ">
|
||||||
<div style="margin:20px;">
|
{{ form.permissions.errors }}
|
||||||
Set permissions for {% for user in queryset %}{{ user.username }}{% if not forloop.last %}, {% endif %}{% endfor %} system user(s).
|
<label for="{{ form.base_path.id_for_label }}">{{ form.permissions.label }}:</label>
|
||||||
<ul>{{ display_objects | unordered_list }}</ul>
|
{{ form.permissions }}{% for x in ""|ljust:"50" %} {% endfor %}
|
||||||
<form action="" method="post">{% csrf_token %}
|
<p class="help">{{ form.permissions.help_text|safe }}</p>
|
||||||
<fieldset class="module aligned wide">
|
</div>
|
||||||
{{ form.non_field_errors }}
|
|
||||||
<div class="form-row ">
|
|
||||||
{{ form.set_action.errors }}
|
|
||||||
<label for="{{ form.set_action.id_for_label }}">{{ form.set_action.label }}:</label>
|
|
||||||
{{ form.set_action }}{% for x in ""|ljust:"50" %} {% endfor %}
|
|
||||||
<p class="help">{{ form.set_action.help_text|safe }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="form-row ">
|
|
||||||
<div class="field-box field-base_home">
|
|
||||||
{{ form.base_home.errors }}
|
|
||||||
<label for="{{ form.base_home.id_for_label }}">{{ form.base_home.label }}:</label>
|
|
||||||
{{ form.base_home }}{% for x in ""|ljust:"50" %} {% endfor %}
|
|
||||||
<p class="help">{{ form.base_home.help_text|safe }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="field-box field-user_extension">
|
|
||||||
{{ form.home_extension.errors }}
|
|
||||||
<label for="{{ form.home_extension.id_for_label }}"></label>
|
|
||||||
{{ form.home_extension }}
|
|
||||||
<p class="help">{{ form.home_extension.help_text|safe }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-row ">
|
|
||||||
{{ form.permissions.errors }}
|
|
||||||
<label for="{{ form.base_path.id_for_label }}">{{ form.permissions.label }}:</label>
|
|
||||||
{{ form.permissions }}{% for x in ""|ljust:"50" %} {% endfor %}
|
|
||||||
<p class="help">{{ form.permissions.help_text|safe }}</p>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
<div>
|
|
||||||
{% for obj in queryset %}
|
|
||||||
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk|unlocalize }}" />
|
|
||||||
{% endfor %}
|
|
||||||
<input type="hidden" name="action" value="{{ action_value }}" />
|
|
||||||
<input type="hidden" name="post" value="{{ post_value|default:'generic_confirmation' }}" />
|
|
||||||
<input type="submit" value="{{ submit_value|default:_("Save") }}" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,15 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
from orchestra.contrib.orchestration import Operation
|
from orchestra.contrib.orchestration import Operation
|
||||||
|
|
||||||
|
|
||||||
def validate_path_exists(user, path):
|
def validate_paths_exist(user, paths):
|
||||||
user.path_to_validate = path
|
operations = []
|
||||||
log = Operation.execute_action(user, 'validate_path_exists')[0]
|
for path in paths:
|
||||||
if 'path does not exists' in log.stderr:
|
user.path_to_validate = path
|
||||||
raise ValidationError(log.stderr)
|
operations.extend(Operation.create_for_action(user, 'validate_path_exists'))
|
||||||
|
logs = Operation.execute(operations)
|
||||||
|
stderr = '\n'.join([log.stderr for log in logs])
|
||||||
|
if 'path does not exists' in stderr:
|
||||||
|
raise ValidationError(stderr)
|
||||||
|
|
||||||
|
|
||||||
def validate_home(user, data, account):
|
def validate_home(user, data, account):
|
||||||
|
|
|
@ -4,13 +4,13 @@ from functools import lru_cache
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.plugins import Plugin
|
from orchestra import plugins
|
||||||
from orchestra.utils.python import import_class
|
from orchestra.utils.python import import_class
|
||||||
|
|
||||||
from . import settings
|
from . import settings
|
||||||
|
|
||||||
|
|
||||||
class AppOption(Plugin):
|
class AppOption(plugins.Plugin, metaclass=plugins.PluginMount):
|
||||||
PHP = 'PHP'
|
PHP = 'PHP'
|
||||||
PROCESS = 'Process'
|
PROCESS = 'Process'
|
||||||
FILESYSTEM = 'FileSystem'
|
FILESYSTEM = 'FileSystem'
|
||||||
|
@ -21,10 +21,13 @@ class AppOption(Plugin):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@lru_cache()
|
@lru_cache()
|
||||||
def get_plugins(cls):
|
def get_plugins(cls, all=False):
|
||||||
plugins = []
|
if all:
|
||||||
for cls in settings.WEBAPPS_ENABLED_OPTIONS:
|
plugins = super().get_plugins()
|
||||||
plugins.append(import_class(cls))
|
else:
|
||||||
|
plugins = []
|
||||||
|
for cls in settings.WEBAPPS_ENABLED_OPTIONS:
|
||||||
|
plugins.append(import_class(cls))
|
||||||
return plugins
|
return plugins
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -52,6 +55,7 @@ class AppOption(Plugin):
|
||||||
class PHPAppOption(AppOption):
|
class PHPAppOption(AppOption):
|
||||||
deprecated = None
|
deprecated = None
|
||||||
group = AppOption.PHP
|
group = AppOption.PHP
|
||||||
|
abstract = True
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
super(PHPAppOption, self).validate()
|
super(PHPAppOption, self).validate()
|
||||||
|
|
|
@ -82,7 +82,7 @@ WEBAPPS_TYPES = Setting('WEBAPPS_TYPES', (
|
||||||
'orchestra.contrib.webapps.types.python.PythonApp',
|
'orchestra.contrib.webapps.types.python.PythonApp',
|
||||||
),
|
),
|
||||||
# lazy loading
|
# lazy loading
|
||||||
choices=lambda : ((t.get_class_path(), t.get_class_path()) for t in webapps.types.AppType.get_plugins()),
|
choices=lambda : ((t.get_class_path(), t.get_class_path()) for t in webapps.types.AppType.get_plugins(all=True)),
|
||||||
multiple=True,
|
multiple=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -255,7 +255,7 @@ WEBAPPS_ENABLED_OPTIONS = Setting('WEBAPPS_ENABLED_OPTIONS', (
|
||||||
'orchestra.contrib.webapps.options.PHPZendExtension',
|
'orchestra.contrib.webapps.options.PHPZendExtension',
|
||||||
),
|
),
|
||||||
# lazy loading
|
# lazy loading
|
||||||
choices=lambda : ((o.get_class_path(), o.get_class_path()) for o in webapps.options.AppOption.get_plugins()),
|
choices=lambda : ((o.get_class_path(), o.get_class_path()) for o in webapps.options.AppOption.get_plugins(all=True)),
|
||||||
multiple=True,
|
multiple=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import importlib
|
||||||
|
import os
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
@ -11,7 +13,7 @@ from .. import settings
|
||||||
from ..options import AppOption
|
from ..options import AppOption
|
||||||
|
|
||||||
|
|
||||||
class AppType(plugins.Plugin):
|
class AppType(plugins.Plugin, metaclass=plugins.PluginMount):
|
||||||
name = None
|
name = None
|
||||||
verbose_name = ""
|
verbose_name = ""
|
||||||
help_text= ""
|
help_text= ""
|
||||||
|
@ -24,10 +26,16 @@ class AppType(plugins.Plugin):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@lru_cache()
|
@lru_cache()
|
||||||
def get_plugins(cls):
|
def get_plugins(cls, all=False):
|
||||||
plugins = []
|
if all:
|
||||||
for cls in settings.WEBAPPS_TYPES:
|
for module in os.listdir(os.path.dirname(__file__)):
|
||||||
plugins.append(import_class(cls))
|
if module != '__init__.py' and module[-3:] == '.py':
|
||||||
|
importlib.import_module('.'+module[:-3], __package__)
|
||||||
|
plugins = super().get_plugins()
|
||||||
|
else:
|
||||||
|
plugins = []
|
||||||
|
for cls in settings.WEBAPPS_TYPES:
|
||||||
|
plugins.append(import_class(cls))
|
||||||
return plugins
|
return plugins
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
|
|
@ -53,6 +53,7 @@ class CMSApp(PHPApp):
|
||||||
change_form = CMSAppForm
|
change_form = CMSAppForm
|
||||||
change_readonly_fileds = ('db_name', 'db_user', 'password',)
|
change_readonly_fileds = ('db_name', 'db_user', 'password',)
|
||||||
db_type = Database.MYSQL
|
db_type = Database.MYSQL
|
||||||
|
abstract = True
|
||||||
|
|
||||||
def get_db_name(self):
|
def get_db_name(self):
|
||||||
db_name = 'wp_%s_%s' % (self.instance.name, self.instance.account)
|
db_name = 'wp_%s_%s' % (self.instance.name, self.instance.account)
|
||||||
|
|
|
@ -5,14 +5,14 @@ from functools import lru_cache
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.plugins import Plugin
|
from orchestra import plugins
|
||||||
from orchestra.utils.python import import_class
|
from orchestra.utils.python import import_class
|
||||||
|
|
||||||
from . import settings
|
from . import settings
|
||||||
from .utils import normurlpath
|
from .utils import normurlpath
|
||||||
|
|
||||||
|
|
||||||
class SiteDirective(Plugin):
|
class SiteDirective(plugins.Plugin, metaclass=plugins.PluginMount):
|
||||||
HTTPD = 'HTTPD'
|
HTTPD = 'HTTPD'
|
||||||
SEC = 'ModSecurity'
|
SEC = 'ModSecurity'
|
||||||
SSL = 'SSL'
|
SSL = 'SSL'
|
||||||
|
@ -25,10 +25,13 @@ class SiteDirective(Plugin):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@lru_cache()
|
@lru_cache()
|
||||||
def get_plugins(cls):
|
def get_plugins(cls, all=False):
|
||||||
plugins = []
|
if all:
|
||||||
for cls in settings.WEBSITES_ENABLED_DIRECTIVES:
|
plugins = super().get_plugins()
|
||||||
plugins.append(import_class(cls))
|
else:
|
||||||
|
plugins = []
|
||||||
|
for cls in settings.WEBSITES_ENABLED_DIRECTIVES:
|
||||||
|
plugins.append(import_class(cls))
|
||||||
return plugins
|
return plugins
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -61,7 +61,7 @@ WEBSITES_ENABLED_DIRECTIVES = Setting('WEBSITES_ENABLED_DIRECTIVES',
|
||||||
'orchestra.contrib.websites.directives.MoodleSaaS',
|
'orchestra.contrib.websites.directives.MoodleSaaS',
|
||||||
),
|
),
|
||||||
# lazy loading
|
# lazy loading
|
||||||
choices=lambda : ((d.get_class_path(), d.get_class_path()) for d in websites.directives.SiteDirective.get_plugins()),
|
choices=lambda : ((d.get_class_path(), d.get_class_path()) for d in websites.directives.SiteDirective.get_plugins(all=True)),
|
||||||
multiple=True,
|
multiple=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue