diff --git a/TODO.md b/TODO.md
index 83418b15..58888824 100644
--- a/TODO.md
+++ b/TODO.md
@@ -414,3 +414,5 @@ mkhomedir_helper or create ssh homes with bash.rc and such
# warnings if some plugins are disabled, like make routes red
# replace show emails by https://docs.python.org/3/library/email.contentmanager.html#module-email.contentmanager
+
+# SElect contact list breadcrumbs
diff --git a/orchestra/contrib/orchestration/admin.py b/orchestra/contrib/orchestration/admin.py
index acaef8c2..7afc245e 100644
--- a/orchestra/contrib/orchestration/admin.py
+++ b/orchestra/contrib/orchestration/admin.py
@@ -5,6 +5,7 @@ from django.utils.translation import ugettext_lazy as _
from orchestra.admin import ExtendedModelAdmin
from orchestra.admin.utils import admin_link, admin_date, admin_colored, display_mono, display_code
+from orchestra.plugins.admin import display_plugin_field
from . import settings, helpers
from .backends import ServiceBackend
@@ -27,7 +28,8 @@ STATE_COLORS = {
class RouteAdmin(ExtendedModelAdmin):
list_display = (
- 'backend', 'host', 'match', 'display_model', 'display_actions', 'async', 'is_active'
+ 'display_backend', 'host', 'match', 'display_model', 'display_actions', 'async',
+ 'is_active'
)
list_editable = ('host', 'match', 'async', 'is_active')
list_filter = ('host', 'is_active', 'async', 'backend')
@@ -41,6 +43,8 @@ class RouteAdmin(ExtendedModelAdmin):
backend.get_name(): backend.default_route_match for backend in ServiceBackend.get_backends()
}
+ display_backend = display_plugin_field('backend')
+
def display_model(self, route):
try:
return escape(route.backend_class.model)
@@ -60,7 +64,8 @@ class RouteAdmin(ExtendedModelAdmin):
def formfield_for_dbfield(self, db_field, **kwargs):
""" Provides dynamic help text on backend form field """
if db_field.name == 'backend':
- kwargs['widget'] = RouteBackendSelect('this.id', self.BACKEND_HELP_TEXT, self.DEFAULT_MATCH)
+ kwargs['widget'] = RouteBackendSelect(
+ 'this.id', self.BACKEND_HELP_TEXT, self.DEFAULT_MATCH)
field = super(RouteAdmin, self).formfield_for_dbfield(db_field, **kwargs)
if db_field.name == 'host':
# Cache host choices
@@ -70,7 +75,6 @@ class RouteAdmin(ExtendedModelAdmin):
request._host_choices_cache = choices = list(field.choices)
field.choices = choices
return field
-
def get_form(self, request, obj=None, **kwargs):
""" Include dynamic help text for existing objects """
diff --git a/orchestra/contrib/orchestration/forms.py b/orchestra/contrib/orchestration/forms.py
index a6f253c2..bd3f96ad 100644
--- a/orchestra/contrib/orchestration/forms.py
+++ b/orchestra/contrib/orchestration/forms.py
@@ -1,14 +1,20 @@
from django import forms
-from orchestra.forms.widgets import SpanWidget
-from orchestra.forms.widgets import paddingCheckboxSelectMultiple
+
+from orchestra.forms.widgets import SpanWidget, paddingCheckboxSelectMultiple
class RouteForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(RouteForm, self).__init__(*args, **kwargs)
if self.instance:
- self.fields['backend'].widget = SpanWidget()
self.fields['backend'].required = False
- self.fields['async_actions'].widget = paddingCheckboxSelectMultiple(45)
- actions = self.instance.backend_class.actions
- self.fields['async_actions'].choices = ((action, action) for action in actions)
+ try:
+ backend_class = self.instance.backend_class
+ except KeyError:
+ self.fields['backend'].widget = SpanWidget(
+ display='%s NOT AVAILABLE' % self.instance.backend)
+ else:
+ self.fields['backend'].widget = SpanWidget()
+ actions = backend_class.actions
+ self.fields['async_actions'].widget = paddingCheckboxSelectMultiple(45)
+ self.fields['async_actions'].choices = ((action, action) for action in actions)
diff --git a/orchestra/contrib/orchestration/models.py b/orchestra/contrib/orchestration/models.py
index e3b439e8..8ccc1347 100644
--- a/orchestra/contrib/orchestration/models.py
+++ b/orchestra/contrib/orchestration/models.py
@@ -1,3 +1,4 @@
+import logging
import socket
from django.contrib.contenttypes.fields import GenericForeignKey
@@ -10,12 +11,14 @@ from django.utils.translation import ugettext_lazy as _
from orchestra.core.validators import validate_ip_address, ValidationError
from orchestra.models.fields import NullableCharField, MultiSelectField
-#from orchestra.utils.apps import autodiscover
from . import settings
from .backends import ServiceBackend
+logger = logging.getLogger(__name__)
+
+
class Server(models.Model):
""" Machine runing daemons (services) """
name = models.CharField(_("name"), max_length=256, unique=True)
@@ -147,12 +150,17 @@ class RouteQuerySet(models.QuerySet):
cache = kwargs.get('cache', {})
if not cache:
for route in self.filter(is_active=True).select_related('host'):
- for action in route.backend_class.get_actions():
- key = (route.backend, action)
- try:
- cache[key].append(route)
- except KeyError:
- cache[key] = [route]
+ try:
+ backend_class = route.backend_class
+ except KeyError:
+ logger.warning("Backed '%s' not installed." % route.backend)
+ else:
+ for action in backend_class.get_actions():
+ key = (route.backend, action)
+ try:
+ cache[key].append(route)
+ except KeyError:
+ cache[key] = [route]
routes = []
backend_cls = operation.backend
key = (backend_cls.get_name(), operation.action)
@@ -202,7 +210,13 @@ class Route(models.Model):
if not self.match:
self.match = 'True'
if self.backend:
- backend_model = self.backend_class.model_class()
+ try:
+ backend_class = self.backend_class
+ except KeyError:
+ raise ValidationError({
+ 'backend': _("Backend '%s' is not installed.") % self.backend
+ })
+ backend_model = backend_class.model_class()
try:
obj = backend_model.objects.all()[0]
except IndexError:
diff --git a/orchestra/contrib/saas/backends/moodle.py b/orchestra/contrib/saas/backends/moodle.py
index dc230664..fa7dbe88 100644
--- a/orchestra/contrib/saas/backends/moodle.py
+++ b/orchestra/contrib/saas/backends/moodle.py
@@ -14,12 +14,12 @@ class MoodleMuBackend(ServiceController):
Creates a Moodle site on a Moodle multisite installation
// config.php
+ // map custom domains to sites
$site_map = array(
// "" => ["", ""],
);
$site = getenv("SITE");
- $wwwroot = "https://{$site}-courses.pangea.org";
if ( $site == '' ) {
$http_host = $_SERVER['HTTP_HOST'];
if (array_key_exists($http_host, $site_map)) {
@@ -32,7 +32,16 @@ class MoodleMuBackend(ServiceController):
$site = array_shift((explode(".", $http_host)));
$wwwroot = "https://{$site}-courses.pangea.org";
}
+ } else {
+ $wwwroot = "https://{$site}-courses.pangea.org";
+ foreach ($site_map as $key => $value) {
+ if ($value[0] == $site) {
+ $wwwroot = $value[1];
+ break;
+ }
+ }
}
+
$prefix = str_replace('-', '_', $site);
$CFG->prefix = "${prefix}_";
$CFG->wwwroot = $wwwroot;
diff --git a/orchestra/contrib/webapps/admin.py b/orchestra/contrib/webapps/admin.py
index bdfb1c55..59b5dd5b 100644
--- a/orchestra/contrib/webapps/admin.py
+++ b/orchestra/contrib/webapps/admin.py
@@ -9,7 +9,7 @@ from orchestra.admin.utils import change_url, get_modeladmin
from orchestra.contrib.accounts.actions import list_accounts
from orchestra.contrib.accounts.admin import AccountAdminMixin
from orchestra.forms.widgets import DynamicHelpTextSelect
-from orchestra.plugins.admin import SelectPluginAdminMixin
+from orchestra.plugins.admin import SelectPluginAdminMixin, display_plugin_field
from orchestra.utils.html import get_on_site_link
from .filters import HasWebsiteListFilter, PHPVersionListFilter
@@ -50,7 +50,7 @@ class WebAppOptionInline(admin.TabularInline):
class WebAppAdmin(SelectPluginAdminMixin, AccountAdminMixin, ExtendedModelAdmin):
- list_display = ('name', 'type', 'display_detail', 'display_websites', 'account_link')
+ list_display = ('name', 'display_type', 'display_detail', 'display_websites', 'account_link')
list_filter = ('type', HasWebsiteListFilter, PHPVersionListFilter)
inlines = [WebAppOptionInline]
readonly_fields = ('account_link', )
@@ -62,6 +62,8 @@ class WebAppAdmin(SelectPluginAdminMixin, AccountAdminMixin, ExtendedModelAdmin)
plugin_title = _("Web application type")
actions = (list_accounts,)
+ display_type = display_plugin_field('type')
+
def display_websites(self, webapp):
websites = []
for content in webapp.content_set.all():
@@ -81,8 +83,12 @@ class WebAppAdmin(SelectPluginAdminMixin, AccountAdminMixin, ExtendedModelAdmin)
display_websites.allow_tags = True
def display_detail(self, webapp):
- return webapp.type_instance.get_detail()
+ try:
+ return webapp.type_instance.get_detail()
+ except KeyError:
+ return "Not available"
display_detail.short_description = _("detail")
+ display_detail.allow_tags = True
# def get_form(self, request, obj=None, **kwargs):
# form = super(WebAppAdmin, self).get_form(request, obj, **kwargs)
diff --git a/orchestra/plugins/admin.py b/orchestra/plugins/admin.py
index 64341716..dac9d5b3 100644
--- a/orchestra/plugins/admin.py
+++ b/orchestra/plugins/admin.py
@@ -15,8 +15,13 @@ class SelectPluginAdminMixin(object):
def get_form(self, request, obj=None, **kwargs):
if obj:
- plugin = getattr(obj, '%s_instance' % self.plugin_field)
- self.form = getattr(plugin, 'get_change_form', plugin.get_form)()
+ try:
+ plugin = getattr(obj, '%s_instance' % self.plugin_field)
+ except KeyError:
+ plugin_name = getattr(obj, self.plugin_field)
+ raise KeyError(_("Plugin '%s' is not available.") % plugin_name)
+ else:
+ self.form = getattr(plugin, 'get_change_form', plugin.get_form)()
else:
plugin = self.plugin.get(self.plugin_value)()
self.form = plugin.get_form()
@@ -90,9 +95,12 @@ class SelectPluginAdminMixin(object):
def change_view(self, request, object_id, form_url='', extra_context=None):
obj = self.get_object(request, unquote(object_id))
- plugin = getattr(obj, '%s_class' % self.plugin_field)
+ try:
+ verbose = getattr(obj, '%s_class' % self.plugin_field).verbose_name
+ except KeyError:
+ raise KeyError(_("Plugin '%s' is not available.") % getattr(obj, self.plugin_field))
context = {
- 'title': _("Change %s") % plugin.verbose_name,
+ 'title': _("Change %s") % verbose,
}
context.update(extra_context or {})
return super(SelectPluginAdminMixin, self).change_view(
@@ -102,3 +110,17 @@ class SelectPluginAdminMixin(object):
if not change:
setattr(obj, self.plugin_field, self.plugin_value)
obj.save()
+
+
+def display_plugin_field(field_name):
+ def inner(modeladmin, obj, field_name=field_name):
+ try:
+ plugin_class = getattr(obj, '%s_class' % field_name)
+ except KeyError:
+ value = getattr(obj, field_name)
+ return "%s" % value
+ return getattr(obj, 'get_%s_display' % field_name)()
+ inner.short_description = field_name
+ inner.admin_order_field = field_name
+ inner.allow_tags = True
+ return inner
diff --git a/orchestra/utils/apps.py b/orchestra/utils/apps.py
index c6ecaa69..c66189f6 100644
--- a/orchestra/utils/apps.py
+++ b/orchestra/utils/apps.py
@@ -1,22 +1,3 @@
-#from django.utils.importlib import import_module
-#from django.utils.module_loading import module_has_submodule
-
-
-#def autodiscover(module):
-# """ Auto-discover INSTALLED_APPS module.py """
-# from django.conf import settings
-# for app in settings.INSTALLED_APPS:
-# mod = import_module(app)
-# try:
-# import_module('%s.%s' % (app, module))
-# except ImportError:
-# # Decide whether to bubble up this error. If the app just
-# # doesn't have the module, we can ignore the error
-# # attempting to import it, otherwise we want it to bubble up.
-# if module_has_submodule(mod, module):
-# print '%s module caused this error:' % module
-# raise
-
def isinstalled(app):
""" returns True if app is installed """
from django.conf import settings
diff --git a/orchestra/views.py b/orchestra/views.py
index cbf56238..31067678 100644
--- a/orchestra/views.py
+++ b/orchestra/views.py
@@ -1,5 +1,5 @@
from django.http import Http404
-from django.contrib.admin.util import unquote
+from django.contrib.admin.utils import unquote
from django.core.exceptions import PermissionDenied
from django.db.models import get_model
from django.shortcuts import get_object_or_404
diff --git a/requirements.txt b/requirements.txt
index d1d79229..86e13d7e 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,7 +1,7 @@
-django==1.8.4
+django==1.8.5
django-fluent-dashboard==0.5.3
django-admin-tools==0.6.0
-django-extensions==1.5.2
+django-extensions==1.5.7
django-celery==3.1.16
celery==3.1.16
kombu==3.0.23