Random fixes

This commit is contained in:
Marc Aymerich 2016-02-11 14:24:09 +00:00
parent 523592dcad
commit cbdac257a0
23 changed files with 77 additions and 30 deletions

View file

@ -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)

View file

@ -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)

View file

@ -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):

View file

@ -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):

View file

@ -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

View file

@ -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()

View file

@ -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,)

View file

@ -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

View file

@ -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()

View file

@ -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',)

View file

@ -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'

View file

@ -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):

View file

@ -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',)

View file

@ -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

View file

@ -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
)

View file

@ -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]

View file

@ -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',)

View file

@ -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', '')

View file

@ -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', '')

View file

@ -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 """

View file

@ -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)

View file

@ -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)

View file

@ -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):