""" Admin site models for managing :class:`.ConfigurationModel` subclasses """ from django.forms import models from django.contrib import admin from django.contrib.admin import ListFilter from django.core.cache import caches, InvalidCacheBackendError from django.core.urlresolvers import reverse from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404 from django.utils.translation import ugettext_lazy as _ try: cache = caches['configuration'] # pylint: disable=invalid-name except InvalidCacheBackendError: from django.core.cache import cache # pylint: disable=protected-access class ConfigurationModelAdmin(admin.ModelAdmin): """ :class:`~django.contrib.admin.ModelAdmin` for :class:`.ConfigurationModel` subclasses """ date_hierarchy = 'change_date' def get_actions(self, request): return { 'revert': (ConfigurationModelAdmin.revert, 'revert', _('Revert to the selected configuration')) } def get_list_display(self, request): return self.get_displayable_field_names() def get_displayable_field_names(self): """ Return all field names, excluding reverse foreign key relationships. """ return [ f.name for f in self.model._meta.get_fields() if not f.one_to_many ] # Don't allow deletion of configuration def has_delete_permission(self, request, obj=None): return False # Make all fields read-only when editing an object def get_readonly_fields(self, request, obj=None): if obj: # editing an existing object return self.get_displayable_field_names() return self.readonly_fields def add_view(self, request, form_url='', extra_context=None): # Prepopulate new configuration entries with the value of the current config get = request.GET.copy() get.update(models.model_to_dict(self.model.current())) request.GET = get return super(ConfigurationModelAdmin, self).add_view(request, form_url, extra_context) # Hide the save buttons in the change view def change_view(self, request, object_id, form_url='', extra_context=None): extra_context = extra_context or {} extra_context['readonly'] = True return super(ConfigurationModelAdmin, self).change_view( request, object_id, form_url, extra_context=extra_context ) def save_model(self, request, obj, form, change): obj.changed_by = request.user super(ConfigurationModelAdmin, self).save_model(request, obj, form, change) cache.delete(obj.cache_key_name(*(getattr(obj, key_name) for key_name in obj.KEY_FIELDS))) cache.delete(obj.key_values_cache_key_name()) def revert(self, request, queryset): """ Admin action to revert a configuration back to the selected value """ if queryset.count() != 1: self.message_user(request, _("Please select a single configuration to revert to.")) return target = queryset[0] target.id = None self.save_model(request, target, None, False) self.message_user(request, _("Reverted configuration.")) return HttpResponseRedirect( reverse( 'admin:{}_{}_change'.format( self.model._meta.app_label, self.model._meta.model_name, ), args=(target.id,), ) ) class ShowHistoryFilter(ListFilter): """ Admin change view filter to show only the most recent (i.e. the "current") row for each unique key value. """ title = _('Status') parameter_name = 'show_history' def __init__(self, request, params, model, model_admin): super(ShowHistoryFilter, self).__init__(request, params, model, model_admin) if self.parameter_name in params: value = params.pop(self.parameter_name) self.used_parameters[self.parameter_name] = value def has_output(self): """ Should this filter be shown? """ return True def choices(self, cl): """ Returns choices ready to be output in the template. """ show_all = self.used_parameters.get(self.parameter_name) == "1" return ( { 'display': _('Current Configuration'), 'selected': not show_all, 'query_string': cl.get_query_string({}, [self.parameter_name]), }, { 'display': _('All (Show History)'), 'selected': show_all, 'query_string': cl.get_query_string({self.parameter_name: "1"}, []), } ) def queryset(self, request, queryset): """ Filter the queryset. No-op since it's done by KeyedConfigurationModelAdmin """ return queryset def expected_parameters(self): """ List the query string params used by this filter """ return [self.parameter_name] class KeyedConfigurationModelAdmin(ConfigurationModelAdmin): """ :class:`~django.contrib.admin.ModelAdmin` for :class:`.ConfigurationModel` subclasses that use extra keys (i.e. they have KEY_FIELDS set). """ date_hierarchy = None list_filter = (ShowHistoryFilter, ) def get_queryset(self, request): """ Annote the queryset with an 'is_active' property that's true iff that row is the most recently added row for that particular set of KEY_FIELDS values. Filter the queryset to show only is_active rows by default. """ if request.GET.get(ShowHistoryFilter.parameter_name) == '1': queryset = self.model.objects.with_active_flag() else: # Show only the most recent row for each key. queryset = self.model.objects.current_set() ordering = self.get_ordering(request) if ordering: return queryset.order_by(*ordering) return queryset def get_list_display(self, request): """ Add a link to each row for creating a new row using the chosen row as a template """ return self.get_displayable_field_names() + ['edit_link'] def add_view(self, request, form_url='', extra_context=None): # Prepopulate new configuration entries with the value of the current config, if given: if 'source' in request.GET: get = request.GET.copy() source_id = int(get.pop('source')[0]) source = get_object_or_404(self.model, pk=source_id) get.update(models.model_to_dict(source)) request.GET = get # Call our grandparent's add_view, skipping the parent code # because the parent code has a different way to prepopulate new configuration entries # with the value of the latest config, which doesn't make sense for keyed models. # pylint: disable=bad-super-call return super(ConfigurationModelAdmin, self).add_view(request, form_url, extra_context) def edit_link(self, inst): """ Edit link for the change view """ if not inst.is_active: return u'--' update_url = reverse('admin:{}_{}_add'.format(self.model._meta.app_label, self.model._meta.model_name)) update_url += "?source={}".format(inst.pk) return u'{}'.format(update_url, _('Update')) edit_link.allow_tags = True edit_link.short_description = _('Update')