Random fixes
This commit is contained in:
parent
523592dcad
commit
cbdac257a0
7
TODO.md
7
TODO.md
|
@ -462,6 +462,13 @@ mkhomedir_helper or create ssh homes with bash.rc and such
|
|||
# POSTFIX web traffic monitor '": uid=" from=<%(user)s>'
|
||||
|
||||
|
||||
# Mv .deleted make sure it works with nested destinations
|
||||
# Re-run backends (save regenerate, delete run same script) warning on confirmation page: DELETED objects will be deleted on the server if you have recreated them.
|
||||
# Automatically re-run backends until success? only timedout executions?
|
||||
|
||||
|
||||
|
||||
|
||||
### Quick start
|
||||
0. Install orchestra following any of these methods:
|
||||
1. [PIP-only, Fast deployment setup (demo)](README.md#fast-deployment-setup)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
from functools import wraps, partial
|
||||
|
||||
from django.contrib import messages
|
||||
from django.contrib.admin import helpers
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.template.response import TemplateResponse
|
||||
from django.utils.decorators import available_attrs
|
||||
from django.utils.encoding import force_text
|
||||
|
@ -13,7 +15,7 @@ def admin_field(method):
|
|||
""" Wraps a function to be used as a ModelAdmin method field """
|
||||
def admin_field_wrapper(*args, **kwargs):
|
||||
""" utility function for creating admin links """
|
||||
kwargs['field'] = args[0] if args else ''
|
||||
kwargs['field'] = args[0] if args else '__str__'
|
||||
kwargs['order'] = kwargs.get('order', kwargs['field'])
|
||||
kwargs['popup'] = kwargs.get('popup', False)
|
||||
# TODO get field verbose name
|
||||
|
@ -38,7 +40,7 @@ def format_display_objects(modeladmin, request, queryset):
|
|||
return objects
|
||||
|
||||
|
||||
def action_with_confirmation(action_name=None, extra_context={},
|
||||
def action_with_confirmation(action_name=None, extra_context=None, validator=None,
|
||||
template='admin/orchestra/generic_confirmation.html'):
|
||||
"""
|
||||
Generic pattern for actions that needs confirmation step
|
||||
|
@ -46,9 +48,15 @@ def action_with_confirmation(action_name=None, extra_context={},
|
|||
<input type="hidden" name="post" value="generic_confirmation" />
|
||||
"""
|
||||
|
||||
def decorator(func, extra_context=extra_context, template=template, action_name=action_name):
|
||||
def decorator(func, extra_context=extra_context, template=template, action_name=action_name, validatior=validator):
|
||||
@wraps(func, assigned=available_attrs(func))
|
||||
def inner(modeladmin, request, queryset, action_name=action_name, extra_context=extra_context):
|
||||
def inner(modeladmin, request, queryset, action_name=action_name, extra_context=extra_context, validator=validator):
|
||||
if validator is not None:
|
||||
try:
|
||||
validator(queryset)
|
||||
except ValidationError as e:
|
||||
messages.error(request, '<br>'.join(e))
|
||||
return
|
||||
# The user has already confirmed the action.
|
||||
if request.POST.get('post') == 'generic_confirmation':
|
||||
stay = func(modeladmin, request, queryset)
|
||||
|
@ -82,7 +90,7 @@ def action_with_confirmation(action_name=None, extra_context={},
|
|||
|
||||
if callable(extra_context):
|
||||
extra_context = extra_context(modeladmin, request, queryset)
|
||||
context.update(extra_context)
|
||||
context.update(extra_context or {})
|
||||
if 'display_objects' not in context:
|
||||
# Compute it only when necessary
|
||||
context['display_objects'] = format_display_objects(modeladmin, request, queryset)
|
||||
|
|
|
@ -4,6 +4,7 @@ from datetime import date
|
|||
|
||||
from django.contrib import messages
|
||||
from django.contrib.admin import helpers
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import transaction
|
||||
from django.forms.models import modelformset_factory
|
||||
|
@ -246,11 +247,16 @@ def copy_lines(modeladmin, request, queryset):
|
|||
return move_lines(modeladmin, request, queryset)
|
||||
|
||||
|
||||
@action_with_confirmation()
|
||||
def validate_amend_bills(bills):
|
||||
for bill in bills:
|
||||
if bill.is_open:
|
||||
raise ValidationError(_("Selected bills should be in closed state"))
|
||||
if bill.type not in bill.AMEND_MAP:
|
||||
raise ValidationError(_("%s can not be amended.") % bill.get_type_display())
|
||||
|
||||
|
||||
@action_with_confirmation(validator=validate_amend_bills)
|
||||
def amend_bills(modeladmin, request, queryset):
|
||||
if queryset.filter(is_open=True).exists():
|
||||
messages.warning(request, _("Selected bills should be in closed state"))
|
||||
return
|
||||
amend_ids = []
|
||||
for bill in queryset:
|
||||
with translation.override(bill.account.language):
|
||||
|
|
|
@ -321,6 +321,8 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
|
|||
if obj:
|
||||
if not obj.is_open:
|
||||
exclude += ['close_bills', 'close_send_download_bills']
|
||||
if obj.type not in obj.AMEND_MAP:
|
||||
exclude += ['amend_bills']
|
||||
return [action for action in actions if action.__name__ not in exclude]
|
||||
|
||||
def get_inline_instances(self, request, obj=None):
|
||||
|
|
|
@ -219,7 +219,14 @@ class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
|||
display_mailboxes.allow_tags = True
|
||||
|
||||
def display_forward(self, address):
|
||||
values = [ dest for dest in address.forward.split() ]
|
||||
forward_mailboxes = {m.name: m for m in address.get_forward_mailboxes()}
|
||||
values = []
|
||||
for forward in address.forward.split():
|
||||
mbox = forward_mailboxes.get(forward)
|
||||
if mbox:
|
||||
values.append(admin_link()(mbox))
|
||||
else:
|
||||
values.append(forward)
|
||||
return '<br>'.join(values)
|
||||
display_forward.short_description = _("Forward")
|
||||
display_forward.allow_tags = True
|
||||
|
|
|
@ -72,5 +72,5 @@ def send_pending(bulk=settings.MAILER_BULK_MESSAGES):
|
|||
except OperationLocked:
|
||||
pass
|
||||
finally:
|
||||
if connection.connection is not None:
|
||||
if 'connection' in vars() and connection.connection is not None:
|
||||
connection.close()
|
||||
|
|
|
@ -134,7 +134,7 @@ class BackendLogAdmin(admin.ModelAdmin):
|
|||
'display_created', 'execution_time',
|
||||
)
|
||||
list_display_links = ('id', 'backend')
|
||||
list_filter = ('state', 'backend', 'server')
|
||||
list_filter = ('state', 'server', 'backend')
|
||||
search_fields = ('script',)
|
||||
date_hierarchy = 'created_at'
|
||||
inlines = (BackendOperationInline,)
|
||||
|
|
|
@ -184,7 +184,7 @@ def collect(instance, action, **kwargs):
|
|||
if update_fields is not None:
|
||||
# TODO remove this, django does not execute post_save if update_fields=[]...
|
||||
# Maybe open a ticket at Djangoproject ?
|
||||
# INITIAL INTENTION: "update_fileds=[]" is a convention for explicitly executing backend
|
||||
# INITIAL INTENTION: "update_fields=[]" is a convention for explicitly executing backend
|
||||
# i.e. account.disable()
|
||||
if update_fields != []:
|
||||
execute = False
|
||||
|
|
|
@ -123,6 +123,9 @@ def OpenSSH(backend, log, server, cmds, async=False):
|
|||
exit_code = ssh.exit_code
|
||||
if not log.exit_code:
|
||||
log.exit_code = exit_code
|
||||
if exit_code == 255 and log.stderr.startswith('ssh: connect to host'):
|
||||
log.state = log.TIMEOUT
|
||||
else:
|
||||
log.state = log.SUCCESS if exit_code == 0 else log.FAILURE
|
||||
logger.debug('%s execution state on %s is %s' % (backend, server, log.state))
|
||||
log.save()
|
||||
|
|
|
@ -22,4 +22,4 @@ class BSCWService(SoftwareService):
|
|||
serializer = BSCWDataSerializer
|
||||
icon = 'orchestra/icons/apps/BSCW.png'
|
||||
site_domain = settings.SAAS_BSCW_DOMAIN
|
||||
change_readonly_fileds = ('email',)
|
||||
change_readonly_fields = ('email',)
|
||||
|
|
|
@ -30,6 +30,6 @@ class GitLabService(SoftwareService):
|
|||
change_form = GitLaChangeForm
|
||||
serializer = GitLabSerializer
|
||||
site_domain = settings.SAAS_GITLAB_DOMAIN
|
||||
change_readonly_fileds = ('email', 'user_id',)
|
||||
change_readonly_fields = ('email', 'user_id',)
|
||||
verbose_name = "GitLab"
|
||||
icon = 'orchestra/icons/apps/gitlab.png'
|
||||
|
|
|
@ -49,8 +49,8 @@ class SoftwareService(plugins.Plugin, metaclass=plugins.PluginMount):
|
|||
plugins.append(import_class(cls))
|
||||
return plugins
|
||||
|
||||
def get_change_readonly_fileds(cls):
|
||||
fields = super(SoftwareService, cls).get_change_readonly_fileds()
|
||||
def get_change_readonly_fields(cls):
|
||||
fields = super(SoftwareService, cls).get_change_readonly_fields()
|
||||
return fields + ('name',)
|
||||
|
||||
def get_site_domain(self):
|
||||
|
|
|
@ -28,4 +28,4 @@ class SeaFileService(SoftwareService):
|
|||
serializer = SeaFileDataSerializer
|
||||
icon = 'orchestra/icons/apps/seafile.png'
|
||||
site_domain = settings.SAAS_SEAFILE_DOMAIN
|
||||
change_readonly_fileds = ('email',)
|
||||
change_readonly_fields = ('email',)
|
||||
|
|
|
@ -40,6 +40,6 @@ class WordPressService(SoftwareService):
|
|||
change_form = WordPressChangeForm
|
||||
serializer = WordPressDataSerializer
|
||||
icon = 'orchestra/icons/apps/WordPress.png'
|
||||
change_readonly_fileds = ('email', 'blog_id')
|
||||
change_readonly_fields = ('email', 'blog_id')
|
||||
site_domain = settings.SAAS_WORDPRESS_DOMAIN
|
||||
allow_custom_url = settings.SAAS_WORDPRESS_ALLOW_CUSTOM_URL
|
||||
|
|
|
@ -67,7 +67,10 @@ class MoodleBackend(WebAppServiceMixin, ServiceController):
|
|||
fi
|
||||
rm %(app_path)s/.lock
|
||||
chown -R %(user)s:%(group)s %(app_path)s
|
||||
su %(user)s --shell /bin/bash << 'EOF'
|
||||
# Run install moodle cli command on the background, because it takes so long...
|
||||
stdout=$(mktemp)
|
||||
stderr=$(mktemp)
|
||||
nohup su %(user)s --shell /bin/bash << 'EOF' > $stdout 2> $stderr &
|
||||
php %(app_path)s/admin/cli/install_database.php \\
|
||||
--fullname="%(site_name)s" \\
|
||||
--shortname="%(site_name)s" \\
|
||||
|
@ -77,6 +80,14 @@ class MoodleBackend(WebAppServiceMixin, ServiceController):
|
|||
--agree-license \\
|
||||
--allow-unstable
|
||||
EOF
|
||||
pid=$!
|
||||
sleep 2
|
||||
if ! ps -p $pid > /dev/null; then
|
||||
cat $stdout
|
||||
cat $stderr >&2
|
||||
exit_code=$(wait $pid)
|
||||
fi
|
||||
rm $stdout $stderr
|
||||
""") % context
|
||||
)
|
||||
|
||||
|
|
|
@ -51,12 +51,13 @@ class CMSApp(PHPApp):
|
|||
""" Abstract AppType with common CMS functionality """
|
||||
serializer = CMSAppSerializer
|
||||
change_form = CMSAppForm
|
||||
change_readonly_fileds = ('db_name', 'db_user', 'password',)
|
||||
change_readonly_fields = ('db_name', 'db_user', 'password',)
|
||||
db_type = Database.MYSQL
|
||||
abstract = True
|
||||
db_prefix = 'cms_'
|
||||
|
||||
def get_db_name(self):
|
||||
db_name = 'wp_%s_%s' % (self.instance.name, self.instance.account)
|
||||
db_name = '%s%s_%s' % (self.db_prefix, self.instance.name, self.instance.account)
|
||||
# Limit for mysql database names
|
||||
return db_name[:65]
|
||||
|
||||
|
|
|
@ -52,4 +52,4 @@ class SymbolicLinkApp(PHPApp):
|
|||
form = SymbolicLinkForm
|
||||
serializer = SymbolicLinkSerializer
|
||||
icon = 'orchestra/icons/apps/SymbolicLink.png'
|
||||
change_readonly_fileds = ('path',)
|
||||
change_readonly_fields = ('path',)
|
||||
|
|
|
@ -13,6 +13,7 @@ class MoodleApp(CMSApp):
|
|||
"The password will be visible in the 'password' field after the installer has finished."
|
||||
)
|
||||
icon = 'orchestra/icons/apps/Moodle.png'
|
||||
db_prefix = 'modl_'
|
||||
|
||||
def get_detail(self):
|
||||
return self.instance.data.get('php_version', '')
|
||||
|
|
|
@ -13,6 +13,7 @@ class WordPressApp(CMSApp):
|
|||
"The password will be visible in the 'password' field after the installer has finished."
|
||||
)
|
||||
icon = 'orchestra/icons/apps/WordPress.png'
|
||||
db_prefix = 'wp_'
|
||||
|
||||
def get_detail(self):
|
||||
return self.instance.data.get('php_version', '')
|
||||
|
|
|
@ -54,7 +54,7 @@ class WebsiteSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
|
|||
class Meta:
|
||||
model = Website
|
||||
fields = ('url', 'id', 'name', 'protocol', 'domains', 'is_active', 'contents', 'directives')
|
||||
postonly_fileds = ('name',)
|
||||
postonly_fields = ('name',)
|
||||
|
||||
def validate(self, data):
|
||||
""" Prevent multiples domains on the same protocol """
|
||||
|
|
|
@ -82,7 +82,7 @@ class ReadOnlyFormMixin(object):
|
|||
"""
|
||||
Mixin class for ModelForm or Form that provides support for SpanField on readonly fields
|
||||
Meta:
|
||||
readonly_fileds = (ro_field1, ro_field2)
|
||||
readonly_fields = (ro_field1, ro_field2)
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ReadOnlyFormMixin, self).__init__(*args, **kwargs)
|
||||
|
|
|
@ -27,7 +27,7 @@ class PluginDataForm(forms.ModelForm):
|
|||
self._meta.help_texts = {
|
||||
self.plugin_field: plugin_help_text or model_help_text
|
||||
}
|
||||
for field in self.plugin.get_change_readonly_fileds():
|
||||
for field in self.plugin.get_change_readonly_fields():
|
||||
value = getattr(self.instance, field, None) or self.instance.data.get(field)
|
||||
display = value
|
||||
foo_display = getattr(self.instance, 'get_%s_display' % field, None)
|
||||
|
|
|
@ -9,7 +9,7 @@ class Plugin(object):
|
|||
change_form = None
|
||||
form = None
|
||||
serializer = None
|
||||
change_readonly_fileds = ()
|
||||
change_readonly_fields = ()
|
||||
plugin_field = None
|
||||
|
||||
def __init__(self, instance=None):
|
||||
|
@ -52,8 +52,8 @@ class Plugin(object):
|
|||
return sorted(choices, key=lambda e: e[1])
|
||||
|
||||
@classmethod
|
||||
def get_change_readonly_fileds(cls):
|
||||
return cls.change_readonly_fileds
|
||||
def get_change_readonly_fields(cls):
|
||||
return cls.change_readonly_fields
|
||||
|
||||
@classmethod
|
||||
def get_class_path(cls):
|
||||
|
|
Loading…
Reference in a new issue