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>' # 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 ### Quick start
0. Install orchestra following any of these methods: 0. Install orchestra following any of these methods:
1. [PIP-only, Fast deployment setup (demo)](README.md#fast-deployment-setup) 1. [PIP-only, Fast deployment setup (demo)](README.md#fast-deployment-setup)

View File

@ -1,6 +1,8 @@
from functools import wraps, partial from functools import wraps, partial
from django.contrib import messages
from django.contrib.admin import helpers from django.contrib.admin import helpers
from django.core.exceptions import ValidationError
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.utils.decorators import available_attrs from django.utils.decorators import available_attrs
from django.utils.encoding import force_text 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 """ """ Wraps a function to be used as a ModelAdmin method field """
def admin_field_wrapper(*args, **kwargs): def admin_field_wrapper(*args, **kwargs):
""" utility function for creating admin links """ """ 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['order'] = kwargs.get('order', kwargs['field'])
kwargs['popup'] = kwargs.get('popup', False) kwargs['popup'] = kwargs.get('popup', False)
# TODO get field verbose name # TODO get field verbose name
@ -38,7 +40,7 @@ def format_display_objects(modeladmin, request, queryset):
return objects 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'): template='admin/orchestra/generic_confirmation.html'):
""" """
Generic pattern for actions that needs confirmation step 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" /> <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)) @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. # The user has already confirmed the action.
if request.POST.get('post') == 'generic_confirmation': if request.POST.get('post') == 'generic_confirmation':
stay = func(modeladmin, request, queryset) stay = func(modeladmin, request, queryset)
@ -82,7 +90,7 @@ def action_with_confirmation(action_name=None, extra_context={},
if callable(extra_context): if callable(extra_context):
extra_context = extra_context(modeladmin, request, queryset) extra_context = extra_context(modeladmin, request, queryset)
context.update(extra_context) context.update(extra_context or {})
if 'display_objects' not in context: if 'display_objects' not in context:
# Compute it only when necessary # Compute it only when necessary
context['display_objects'] = format_display_objects(modeladmin, request, queryset) 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 import messages
from django.contrib.admin import helpers from django.contrib.admin import helpers
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.db import transaction from django.db import transaction
from django.forms.models import modelformset_factory from django.forms.models import modelformset_factory
@ -246,11 +247,16 @@ def copy_lines(modeladmin, request, queryset):
return move_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): 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 = [] amend_ids = []
for bill in queryset: for bill in queryset:
with translation.override(bill.account.language): with translation.override(bill.account.language):

View File

@ -321,6 +321,8 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
if obj: if obj:
if not obj.is_open: if not obj.is_open:
exclude += ['close_bills', 'close_send_download_bills'] 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] return [action for action in actions if action.__name__ not in exclude]
def get_inline_instances(self, request, obj=None): def get_inline_instances(self, request, obj=None):

View File

@ -219,7 +219,14 @@ class AddressAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
display_mailboxes.allow_tags = True display_mailboxes.allow_tags = True
def display_forward(self, address): 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) return '<br>'.join(values)
display_forward.short_description = _("Forward") display_forward.short_description = _("Forward")
display_forward.allow_tags = True display_forward.allow_tags = True

View File

@ -72,5 +72,5 @@ def send_pending(bulk=settings.MAILER_BULK_MESSAGES):
except OperationLocked: except OperationLocked:
pass pass
finally: finally:
if connection.connection is not None: if 'connection' in vars() and connection.connection is not None:
connection.close() connection.close()

View File

@ -134,7 +134,7 @@ class BackendLogAdmin(admin.ModelAdmin):
'display_created', 'execution_time', 'display_created', 'execution_time',
) )
list_display_links = ('id', 'backend') list_display_links = ('id', 'backend')
list_filter = ('state', 'backend', 'server') list_filter = ('state', 'server', 'backend')
search_fields = ('script',) search_fields = ('script',)
date_hierarchy = 'created_at' date_hierarchy = 'created_at'
inlines = (BackendOperationInline,) inlines = (BackendOperationInline,)

View File

@ -184,7 +184,7 @@ def collect(instance, action, **kwargs):
if update_fields is not None: if update_fields is not None:
# TODO remove this, django does not execute post_save if update_fields=[]... # TODO remove this, django does not execute post_save if update_fields=[]...
# Maybe open a ticket at Djangoproject ? # 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() # i.e. account.disable()
if update_fields != []: if update_fields != []:
execute = False execute = False

View File

@ -123,7 +123,10 @@ def OpenSSH(backend, log, server, cmds, async=False):
exit_code = ssh.exit_code exit_code = ssh.exit_code
if not log.exit_code: if not log.exit_code:
log.exit_code = exit_code log.exit_code = exit_code
log.state = log.SUCCESS if exit_code == 0 else log.FAILURE 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)) logger.debug('%s execution state on %s is %s' % (backend, server, log.state))
log.save() log.save()
except: except:

View File

@ -22,4 +22,4 @@ class BSCWService(SoftwareService):
serializer = BSCWDataSerializer serializer = BSCWDataSerializer
icon = 'orchestra/icons/apps/BSCW.png' icon = 'orchestra/icons/apps/BSCW.png'
site_domain = settings.SAAS_BSCW_DOMAIN 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 change_form = GitLaChangeForm
serializer = GitLabSerializer serializer = GitLabSerializer
site_domain = settings.SAAS_GITLAB_DOMAIN site_domain = settings.SAAS_GITLAB_DOMAIN
change_readonly_fileds = ('email', 'user_id',) change_readonly_fields = ('email', 'user_id',)
verbose_name = "GitLab" verbose_name = "GitLab"
icon = 'orchestra/icons/apps/gitlab.png' icon = 'orchestra/icons/apps/gitlab.png'

View File

@ -49,8 +49,8 @@ class SoftwareService(plugins.Plugin, metaclass=plugins.PluginMount):
plugins.append(import_class(cls)) plugins.append(import_class(cls))
return plugins return plugins
def get_change_readonly_fileds(cls): def get_change_readonly_fields(cls):
fields = super(SoftwareService, cls).get_change_readonly_fileds() fields = super(SoftwareService, cls).get_change_readonly_fields()
return fields + ('name',) return fields + ('name',)
def get_site_domain(self): def get_site_domain(self):

View File

@ -28,4 +28,4 @@ class SeaFileService(SoftwareService):
serializer = SeaFileDataSerializer serializer = SeaFileDataSerializer
icon = 'orchestra/icons/apps/seafile.png' icon = 'orchestra/icons/apps/seafile.png'
site_domain = settings.SAAS_SEAFILE_DOMAIN 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 change_form = WordPressChangeForm
serializer = WordPressDataSerializer serializer = WordPressDataSerializer
icon = 'orchestra/icons/apps/WordPress.png' icon = 'orchestra/icons/apps/WordPress.png'
change_readonly_fileds = ('email', 'blog_id') change_readonly_fields = ('email', 'blog_id')
site_domain = settings.SAAS_WORDPRESS_DOMAIN site_domain = settings.SAAS_WORDPRESS_DOMAIN
allow_custom_url = settings.SAAS_WORDPRESS_ALLOW_CUSTOM_URL allow_custom_url = settings.SAAS_WORDPRESS_ALLOW_CUSTOM_URL

View File

@ -67,7 +67,10 @@ class MoodleBackend(WebAppServiceMixin, ServiceController):
fi fi
rm %(app_path)s/.lock rm %(app_path)s/.lock
chown -R %(user)s:%(group)s %(app_path)s 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 \\ php %(app_path)s/admin/cli/install_database.php \\
--fullname="%(site_name)s" \\ --fullname="%(site_name)s" \\
--shortname="%(site_name)s" \\ --shortname="%(site_name)s" \\
@ -77,6 +80,14 @@ class MoodleBackend(WebAppServiceMixin, ServiceController):
--agree-license \\ --agree-license \\
--allow-unstable --allow-unstable
EOF 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 """) % context
) )

View File

@ -51,12 +51,13 @@ class CMSApp(PHPApp):
""" Abstract AppType with common CMS functionality """ """ Abstract AppType with common CMS functionality """
serializer = CMSAppSerializer serializer = CMSAppSerializer
change_form = CMSAppForm change_form = CMSAppForm
change_readonly_fileds = ('db_name', 'db_user', 'password',) change_readonly_fields = ('db_name', 'db_user', 'password',)
db_type = Database.MYSQL db_type = Database.MYSQL
abstract = True abstract = True
db_prefix = 'cms_'
def get_db_name(self): 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 # Limit for mysql database names
return db_name[:65] return db_name[:65]

View File

@ -52,4 +52,4 @@ class SymbolicLinkApp(PHPApp):
form = SymbolicLinkForm form = SymbolicLinkForm
serializer = SymbolicLinkSerializer serializer = SymbolicLinkSerializer
icon = 'orchestra/icons/apps/SymbolicLink.png' 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." "The password will be visible in the 'password' field after the installer has finished."
) )
icon = 'orchestra/icons/apps/Moodle.png' icon = 'orchestra/icons/apps/Moodle.png'
db_prefix = 'modl_'
def get_detail(self): def get_detail(self):
return self.instance.data.get('php_version', '') 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." "The password will be visible in the 'password' field after the installer has finished."
) )
icon = 'orchestra/icons/apps/WordPress.png' icon = 'orchestra/icons/apps/WordPress.png'
db_prefix = 'wp_'
def get_detail(self): def get_detail(self):
return self.instance.data.get('php_version', '') return self.instance.data.get('php_version', '')

View File

@ -54,7 +54,7 @@ class WebsiteSerializer(AccountSerializerMixin, HyperlinkedModelSerializer):
class Meta: class Meta:
model = Website model = Website
fields = ('url', 'id', 'name', 'protocol', 'domains', 'is_active', 'contents', 'directives') fields = ('url', 'id', 'name', 'protocol', 'domains', 'is_active', 'contents', 'directives')
postonly_fileds = ('name',) postonly_fields = ('name',)
def validate(self, data): def validate(self, data):
""" Prevent multiples domains on the same protocol """ """ 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 Mixin class for ModelForm or Form that provides support for SpanField on readonly fields
Meta: Meta:
readonly_fileds = (ro_field1, ro_field2) readonly_fields = (ro_field1, ro_field2)
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ReadOnlyFormMixin, self).__init__(*args, **kwargs) super(ReadOnlyFormMixin, self).__init__(*args, **kwargs)

View File

@ -27,7 +27,7 @@ class PluginDataForm(forms.ModelForm):
self._meta.help_texts = { self._meta.help_texts = {
self.plugin_field: plugin_help_text or model_help_text 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) value = getattr(self.instance, field, None) or self.instance.data.get(field)
display = value display = value
foo_display = getattr(self.instance, 'get_%s_display' % field, None) foo_display = getattr(self.instance, 'get_%s_display' % field, None)

View File

@ -9,7 +9,7 @@ class Plugin(object):
change_form = None change_form = None
form = None form = None
serializer = None serializer = None
change_readonly_fileds = () change_readonly_fields = ()
plugin_field = None plugin_field = None
def __init__(self, instance=None): def __init__(self, instance=None):
@ -52,8 +52,8 @@ class Plugin(object):
return sorted(choices, key=lambda e: e[1]) return sorted(choices, key=lambda e: e[1])
@classmethod @classmethod
def get_change_readonly_fileds(cls): def get_change_readonly_fields(cls):
return cls.change_readonly_fileds return cls.change_readonly_fields
@classmethod @classmethod
def get_class_path(cls): def get_class_path(cls):