django-orchestra/orchestra/contrib/resources/admin.py

357 lines
15 KiB
Python
Raw Permalink Normal View History

from urllib.parse import parse_qs
2015-07-28 10:49:20 +00:00
from django.apps import apps
from django.urls import re_path as url
2014-07-10 15:19:06 +00:00
from django.contrib import admin, messages
from django.contrib.contenttypes.admin import GenericTabularInline
from django.contrib.contenttypes.forms import BaseGenericInlineFormSet
2014-11-13 15:34:00 +00:00
from django.contrib.admin.utils import unquote
from django.urls import reverse
2015-07-28 10:49:20 +00:00
from django.db.models import Q
from django.shortcuts import redirect
2015-07-28 10:49:20 +00:00
from django.templatetags.static import static
2014-07-08 15:19:15 +00:00
from django.utils.functional import cached_property
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.translation import ngettext, gettext_lazy as _
2014-07-08 15:19:15 +00:00
2014-07-10 15:19:06 +00:00
from orchestra.admin import ExtendedModelAdmin
2014-07-22 21:47:01 +00:00
from orchestra.admin.utils import insertattr, get_modeladmin, admin_link, admin_date
2015-04-05 10:46:24 +00:00
from orchestra.contrib.orchestration.models import Route
2014-07-10 10:03:22 +00:00
from orchestra.core import services
2015-05-07 19:00:02 +00:00
from orchestra.utils import db, sys
2015-10-29 18:19:00 +00:00
from orchestra.utils.functional import cached
2014-07-08 15:19:15 +00:00
2015-07-28 10:49:20 +00:00
from .actions import run_monitor, show_history
2015-07-27 12:55:35 +00:00
from .api import history_data
from .filters import ResourceDataListFilter
2014-07-08 15:19:15 +00:00
from .forms import ResourceForm
2014-07-09 16:17:43 +00:00
from .models import Resource, ResourceData, MonitorData
2014-07-08 15:19:15 +00:00
2014-07-10 15:19:06 +00:00
class ResourceAdmin(ExtendedModelAdmin):
2014-07-08 15:19:15 +00:00
list_display = (
2015-04-08 14:41:09 +00:00
'id', 'verbose_name', 'content_type', 'aggregation', 'on_demand',
2014-10-24 14:19:34 +00:00
'default_allocation', 'unit', 'crontab', 'is_active'
2014-07-08 15:19:15 +00:00
)
2014-10-24 14:19:34 +00:00
list_display_links = ('id', 'verbose_name')
list_editable = ('default_allocation', 'crontab', 'is_active',)
list_filter = (
('content_type', admin.RelatedOnlyFieldListFilter), 'aggregation', 'on_demand',
'disable_trigger'
)
2014-07-10 15:19:06 +00:00
fieldsets = (
(None, {
2015-04-08 14:41:09 +00:00
'fields': ('verbose_name', 'name', 'content_type', 'aggregation'),
2014-07-10 15:19:06 +00:00
}),
(_("Configuration"), {
2014-10-23 15:38:46 +00:00
'fields': ('unit', 'scale', 'on_demand', 'default_allocation', 'disable_trigger',
'is_active'),
2014-07-10 15:19:06 +00:00
}),
(_("Monitoring"), {
'fields': ('monitors', 'crontab'),
}),
)
actions = (run_monitor,)
change_view_actions = actions
2014-11-13 15:34:00 +00:00
change_readonly_fields = ('name', 'content_type')
2015-05-07 19:00:02 +00:00
prepopulated_fields = {
'name': ('verbose_name',)
}
2015-04-01 15:49:21 +00:00
list_select_related = ('content_type', 'crontab',)
2014-11-13 15:34:00 +00:00
def change_view(self, request, object_id, form_url='', extra_context=None):
""" Remaind user when monitor routes are not configured """
if request.method == 'GET':
resource = self.get_object(request, unquote(object_id))
backends = Route.objects.values_list('backend', flat=True)
not_routed = []
for monitor in resource.monitors:
if monitor not in backends:
not_routed.append(monitor)
if not_routed:
messages.warning(request, ngettext(
2014-11-13 15:34:00 +00:00
_("%(not_routed)s monitor doesn't have any configured route."),
_("%(not_routed)s monitors don't have any configured route."),
len(not_routed),
) % {
'not_routed': ', '.join(not_routed)
})
return super(ResourceAdmin, self).change_view(request, object_id, form_url=form_url,
extra_context=extra_context)
2014-07-08 15:19:15 +00:00
def save_model(self, request, obj, form, change):
super(ResourceAdmin, self).save_model(request, obj, form, change)
2015-05-07 19:00:02 +00:00
# best-effort
2014-07-08 15:19:15 +00:00
model = obj.content_type.model_class()
2014-07-18 15:32:27 +00:00
modeladmin = type(get_modeladmin(model))
2014-07-08 15:19:15 +00:00
resources = obj.content_type.resource_set.filter(is_active=True)
inlines = []
for inline in modeladmin.inlines:
2014-07-09 16:17:43 +00:00
if inline.model is ResourceData:
2014-07-08 15:19:15 +00:00
inline = resource_inline_factory(resources)
inlines.append(inline)
modeladmin.inlines = inlines
2015-05-07 19:00:02 +00:00
# reload Not always work
sys.touch_wsgi()
2014-07-10 10:03:22 +00:00
def formfield_for_dbfield(self, db_field, **kwargs):
""" filter service content_types """
if db_field.name == 'content_type':
2014-07-21 12:20:04 +00:00
models = [ model._meta.model_name for model in services.get() ]
kwargs['queryset'] = db_field.remote_field.model.objects.filter(model__in=models)
2014-07-10 10:03:22 +00:00
return super(ResourceAdmin, self).formfield_for_dbfield(db_field, **kwargs)
2014-07-08 15:19:15 +00:00
def content_object_link(data):
ct = data.content_type
url = reverse('admin:%s_%s_change' % (ct.app_label, ct.model), args=(data.object_id,))
return format_html('<a href="{}">{}</a>', url, data.content_object_repr)
content_object_link.short_description = _("Content object")
content_object_link.admin_order_field = 'content_object_repr'
2014-10-27 13:29:02 +00:00
class ResourceDataAdmin(ExtendedModelAdmin):
2014-07-16 15:20:16 +00:00
list_display = (
2015-07-27 12:55:35 +00:00
'id', 'resource_link', content_object_link, 'allocated', 'display_used',
2014-10-27 13:29:02 +00:00
'display_updated'
2014-07-16 15:20:16 +00:00
)
2014-07-08 15:19:15 +00:00
list_filter = ('resource',)
2014-10-27 13:29:02 +00:00
fields = (
'resource_link', 'content_type', content_object_link, 'display_updated', 'display_used',
2015-07-27 12:55:35 +00:00
'allocated',
2014-10-27 13:29:02 +00:00
)
search_fields = ('content_object_repr',)
2014-11-13 15:34:00 +00:00
readonly_fields = fields
2015-07-28 10:49:20 +00:00
actions = (run_monitor, show_history)
2014-10-27 13:29:02 +00:00
change_view_actions = actions
ordering = ('-updated_at',)
list_select_related = ('resource__content_type', 'content_type')
2014-09-28 12:28:57 +00:00
resource_link = admin_link('resource')
2014-10-27 13:29:02 +00:00
display_updated = admin_date('updated_at', short_description=_("Updated"))
def get_urls(self):
"""Returns the additional urls for the change view links"""
urls = super(ResourceDataAdmin, self).get_urls()
admin_site = self.admin_site
opts = self.model._meta
2015-05-19 13:27:04 +00:00
return [
url('^(\d+)/used-monitordata/$',
admin_site.admin_view(self.used_monitordata_view),
name='%s_%s_used_monitordata' % (opts.app_label, opts.model_name)
),
2015-07-27 12:55:35 +00:00
url('^history_data/$',
admin_site.admin_view(history_data),
name='%s_%s_history_data' % (opts.app_label, opts.model_name)
),
url('^list-related/(.+)/(.+)/(\d+)/$',
admin_site.admin_view(self.list_related_view),
name='%s_%s_list_related' % (opts.app_label, opts.model_name)
),
2015-05-19 13:27:04 +00:00
] + urls
def display_used(self, rdata):
if rdata.used is None:
2014-11-13 15:34:00 +00:00
return ''
url = reverse('admin:resources_resourcedata_used_monitordata', args=(rdata.pk,))
return format_html('<a href="{}">{} {}</a>', url, rdata.used, rdata.unit)
display_used.short_description = _("Used")
display_used.admin_order_field = 'used'
def has_add_permission(self, *args, **kwargs):
return False
def used_monitordata_view(self, request, object_id):
2014-11-13 15:34:00 +00:00
url = reverse('admin:resources_monitordata_changelist')
url += '?resource_data=%s' % object_id
return redirect(url)
def list_related_view(self, request, app_name, model_name, object_id):
resources = Resource.objects.select_related('content_type')
resource_models = {r.content_type.model_class(): r.content_type_id for r in resources}
# Self
model = apps.get_model(app_name, model_name)
obj = model.objects.get(id=int(object_id))
ct_id = resource_models[model]
2015-07-28 10:49:20 +00:00
qset = Q(content_type_id=ct_id, object_id=obj.id, resource__is_active=True)
# Related
for field, rel in obj._meta.fields_map.items():
try:
ct_id = resource_models[rel.related_model]
except KeyError:
pass
else:
manager = getattr(obj, field)
ids = manager.values_list('id', flat=True)
2015-07-28 10:49:20 +00:00
qset = Q(qset) | Q(content_type_id=ct_id, object_id__in=ids, resource__is_active=True)
related = ResourceData.objects.filter(qset)
related_ids = related.values_list('id', flat=True)
related_ids = ','.join(map(str, related_ids))
url = reverse('admin:resources_resourcedata_changelist')
url += '?id__in=%s' % related_ids
return redirect(url)
2014-07-08 15:19:15 +00:00
2014-10-27 13:29:02 +00:00
class MonitorDataAdmin(ExtendedModelAdmin):
2015-07-27 12:55:35 +00:00
list_display = ('id', 'monitor', content_object_link, 'display_created', 'value')
list_filter = ('monitor', ResourceDataListFilter)
2014-10-27 13:29:02 +00:00
add_fields = ('monitor', 'content_type', 'object_id', 'created_at', 'value')
2015-08-05 22:58:35 +00:00
fields = ('monitor', 'content_type', content_object_link, 'display_created', 'value', 'state')
2014-10-27 13:29:02 +00:00
change_readonly_fields = fields
list_select_related = ('content_type',)
search_fields = ('content_object_repr',)
date_hierarchy = 'created_at'
2014-10-27 13:29:02 +00:00
display_created = admin_date('created_at', short_description=_("Created"))
def filter_used_monitordata(self, request, queryset):
query_string = parse_qs(request.META['QUERY_STRING'])
resource_data = query_string.get('resource_data')
if resource_data:
mdata = ResourceData.objects.get(pk=int(resource_data[0]))
resource = mdata.resource
ids = []
2015-07-28 10:49:20 +00:00
for monitor, dataset in mdata.get_monitor_datasets():
2015-07-16 13:07:15 +00:00
dataset = resource.aggregation_instance.filter(dataset)
if isinstance(dataset, MonitorData):
ids.append(dataset.id)
else:
ids += dataset.values_list('id', flat=True)
return queryset.filter(id__in=ids)
return queryset
2014-09-10 16:53:09 +00:00
def get_queryset(self, request):
queryset = super(MonitorDataAdmin, self).get_queryset(request)
queryset = self.filter_used_monitordata(request, queryset)
2014-09-10 16:53:09 +00:00
return queryset.prefetch_related('content_object')
2014-07-08 15:19:15 +00:00
admin.site.register(Resource, ResourceAdmin)
2014-07-09 16:17:43 +00:00
admin.site.register(ResourceData, ResourceDataAdmin)
2014-07-08 15:19:15 +00:00
admin.site.register(MonitorData, MonitorDataAdmin)
# Mokey-patching
def resource_inline_factory(resources):
class ResourceInlineFormSet(BaseGenericInlineFormSet):
2014-07-22 21:47:01 +00:00
def total_form_count(self, resources=resources):
2015-04-16 13:15:21 +00:00
return len(resources)
2015-10-29 18:19:00 +00:00
@cached
def get_queryset(self):
2015-03-23 15:36:51 +00:00
""" Filter disabled resources """
queryset = super(ResourceInlineFormSet, self).get_queryset()
2015-04-16 13:15:21 +00:00
return queryset.filter(resource__is_active=True).select_related('resource')
2014-07-08 15:19:15 +00:00
@cached_property
2014-07-22 21:47:01 +00:00
def forms(self, resources=resources):
2014-07-08 15:19:15 +00:00
forms = []
2014-07-22 21:47:01 +00:00
resources_copy = list(resources)
# Remove queryset disabled objects
queryset = [rdata for rdata in self.get_queryset() if rdata.resource in resources]
2014-10-27 13:29:02 +00:00
if self.instance.pk:
# Create missing resource data
queryset_resources = [rdata.resource for rdata in queryset]
2014-10-27 13:29:02 +00:00
for resource in resources:
if resource not in queryset_resources:
kwargs = {
'content_object': self.instance,
2015-08-05 22:58:35 +00:00
'content_object_repr': str(self.instance),
}
if resource.default_allocation:
kwargs['allocated'] = resource.default_allocation
rdata = resource.dataset.create(**kwargs)
queryset.append(rdata)
2014-10-27 13:29:02 +00:00
# Existing dataset
for i, rdata in enumerate(queryset):
forms.append(self._construct_form(i, resource=rdata.resource))
try:
resources_copy.remove(rdata.resource)
except ValueError:
pass
2014-10-27 13:29:02 +00:00
# Missing dataset
for i, resource in enumerate(resources_copy, len(queryset)):
2014-07-08 15:19:15 +00:00
forms.append(self._construct_form(i, resource=resource))
return forms
class ResourceInline(GenericTabularInline):
2014-07-09 16:17:43 +00:00
model = ResourceData
2014-07-08 15:19:15 +00:00
verbose_name_plural = _("resources")
form = ResourceForm
formset = ResourceInlineFormSet
2014-07-10 17:34:23 +00:00
can_delete = False
2014-07-16 15:20:16 +00:00
fields = (
2014-10-27 13:29:02 +00:00
'verbose_name', 'display_used', 'display_updated', 'allocated', 'unit',
2014-07-16 15:20:16 +00:00
)
2015-07-16 13:07:15 +00:00
readonly_fields = ('display_used', 'display_updated',)
2014-07-08 15:19:15 +00:00
class Media:
css = {
'all': ('orchestra/css/hide-inline-id.css',)
}
2014-09-26 15:05:20 +00:00
display_updated = admin_date('updated_at', default=_("Never"))
def get_fieldsets(self, request, obj=None):
if obj:
opts = self.parent_model._meta
url = reverse('admin:resources_resourcedata_list_related',
args=(opts.app_label, opts.model_name, obj.id))
link = '<a href="%s">%s</a>' % (url, _("List related"))
self.verbose_name_plural = mark_safe(_("Resources") + ' ' + link)
return super(ResourceInline, self).get_fieldsets(request, obj)
@mark_safe
def display_used(self, rdata):
2015-07-28 10:49:20 +00:00
update = ''
history = ''
if rdata.pk:
context = {
'title': _("Update"),
'url': reverse('admin:resources_resourcedata_monitor', args=(rdata.pk,)),
'image': '<img src="%s"></img>' % static('orchestra/images/reload.png'),
}
update = '<a href="%(url)s" title="%(title)s">%(image)s</a>' % context
context.update({
'title': _("Show history"),
'image': '<img src="%s"></img>' % static('orchestra/images/history.png'),
2015-07-28 10:49:20 +00:00
'url': reverse('admin:resources_resourcedata_show_history', args=(rdata.pk,)),
'popup': 'onclick="return showAddAnotherPopup(this);"',
})
history = '<a href="%(url)s" title="%(title)s" %(popup)s>%(image)s</a>' % context
if rdata.used is not None:
2015-07-28 10:49:20 +00:00
used_url = reverse('admin:resources_resourcedata_used_monitordata', args=(rdata.pk,))
used = '<a href="%s">%s %s</a>' % (used_url, rdata.used, rdata.unit)
return ' '.join(map(str, (used, update, history)))
2015-08-05 22:58:35 +00:00
if rdata.resource.monitors:
return _("Unknonw %s %s") % (update, history)
return _("No monitor")
2014-10-27 13:29:02 +00:00
display_used.short_description = _("Used")
2014-07-08 15:19:15 +00:00
def has_add_permission(self, *args, **kwargs):
""" Hidde add another """
return False
2014-07-08 15:19:15 +00:00
return ResourceInline
2014-10-04 14:19:29 +00:00
def insert_resource_inlines():
2014-10-07 13:08:59 +00:00
# Clean previous state
for related in Resource._related:
modeladmin = get_modeladmin(related)
modeladmin_class = type(modeladmin)
for inline in getattr(modeladmin_class, 'inlines', []):
if inline.__name__ == 'ResourceInline':
modeladmin_class.inlines.remove(inline)
resources = Resource.objects.filter(is_active=True)
2015-04-02 16:14:55 +00:00
for ct, resources in resources.group_by('content_type').items():
2014-07-08 15:19:15 +00:00
inline = resource_inline_factory(resources)
2014-07-25 13:27:31 +00:00
model = ct.model_class()
2014-10-07 13:08:59 +00:00
insertattr(model, 'inlines', inline)
2014-10-09 17:04:12 +00:00
2014-10-07 13:08:59 +00:00
2015-05-07 19:00:02 +00:00
if db.database_ready():
2014-10-07 13:08:59 +00:00
insert_resource_inlines()