wagtail/wagtail/contrib/modeladmin/views.py

917 wiersze
34 KiB
Python

from collections import OrderedDict
from django import forms
from django.contrib.admin import FieldListFilter
from django.contrib.admin.options import IncorrectLookupParameters
from django.contrib.admin.utils import (
get_fields_from_path, label_for_field, lookup_field, lookup_needs_distinct, prepare_lookup_value, quote, unquote)
from django.contrib.auth.decorators import login_required
from django.core.exceptions import (
FieldDoesNotExist, ImproperlyConfigured, ObjectDoesNotExist, PermissionDenied, SuspiciousOperation)
from django.core.paginator import InvalidPage, Paginator
from django.db import models
from django.db.models.fields.related import ManyToManyField, OneToOneRel
from django.shortcuts import get_object_or_404, redirect
from django.template.defaultfilters import filesizeformat
from django.utils.decorators import method_decorator
from django.utils.encoding import force_str
from django.utils.functional import cached_property
from django.utils.http import urlencode
from django.utils.safestring import mark_safe
from django.utils.text import capfirst
from django.utils.translation import gettext as _
from django.views.generic import TemplateView
from django.views.generic.edit import FormView
from wagtail.admin import messages
from wagtail.admin.views.reports import SpreadsheetExportMixin
from .forms import ParentChooserForm
try:
from django.db.models.sql.constants import QUERY_TERMS
except ImportError:
# Django 2.1+ does not have QUERY_TERMS anymore
QUERY_TERMS = {
'contains', 'day', 'endswith', 'exact', 'gt', 'gte', 'hour',
'icontains', 'iendswith', 'iexact', 'in', 'iregex', 'isnull',
'istartswith', 'lt', 'lte', 'minute', 'month', 'range', 'regex',
'search', 'second', 'startswith', 'week_day', 'year',
}
class WMABaseView(TemplateView):
"""
Groups together common functionality for all app views.
"""
model_admin = None
meta_title = ''
page_title = ''
page_subtitle = ''
def __init__(self, model_admin):
self.model_admin = model_admin
self.model = model_admin.model
self.opts = self.model._meta
self.app_label = force_str(self.opts.app_label)
self.model_name = force_str(self.opts.model_name)
self.verbose_name = force_str(self.opts.verbose_name)
self.verbose_name_plural = force_str(self.opts.verbose_name_plural)
self.pk_attname = self.opts.pk.attname
self.is_pagemodel = model_admin.is_pagemodel
self.permission_helper = model_admin.permission_helper
self.url_helper = model_admin.url_helper
def check_action_permitted(self, user):
return True
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
if not self.check_action_permitted(request.user):
raise PermissionDenied
button_helper_class = self.model_admin.get_button_helper_class()
self.button_helper = button_helper_class(self, request)
return super().dispatch(request, *args, **kwargs)
@cached_property
def menu_icon(self):
return self.model_admin.get_menu_icon()
@cached_property
def header_icon(self):
return self.menu_icon
def get_page_title(self):
return self.page_title or capfirst(self.opts.verbose_name_plural)
def get_meta_title(self):
return self.meta_title or self.get_page_title()
@cached_property
def index_url(self):
return self.url_helper.index_url
@cached_property
def create_url(self):
return self.url_helper.create_url
def get_base_queryset(self, request=None):
return self.model_admin.get_queryset(request or self.request)
def get_context_data(self, **kwargs):
context = {
'view': self,
'model_admin': self.model_admin,
}
context.update(kwargs)
return super().get_context_data(**context)
class ModelFormView(WMABaseView, FormView):
def get_edit_handler(self):
edit_handler = self.model_admin.get_edit_handler(
instance=self.get_instance(), request=self.request
)
return edit_handler.bind_to(model=self.model_admin.model)
def get_form_class(self):
return self.get_edit_handler().get_form_class()
def get_success_url(self):
return self.index_url
def get_instance(self):
return getattr(self, 'instance', None) or self.model()
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update({'instance': self.get_instance()})
return kwargs
@property
def media(self):
return forms.Media(
css={'all': self.model_admin.get_form_view_extra_css()},
js=self.model_admin.get_form_view_extra_js()
)
def get_context_data(self, **kwargs):
instance = self.get_instance()
edit_handler = self.get_edit_handler()
form = self.get_form()
edit_handler = edit_handler.bind_to(
instance=instance, request=self.request, form=form)
context = {
'is_multipart': form.is_multipart(),
'edit_handler': edit_handler,
'form': form,
}
context.update(kwargs)
return super().get_context_data(**context)
def get_success_message(self, instance):
return _("%(model_name)s '%(instance)s' created.") % {
'model_name': capfirst(self.opts.verbose_name), 'instance': instance
}
def get_success_message_buttons(self, instance):
button_url = self.url_helper.get_action_url('edit', quote(instance.pk))
return [
messages.button(button_url, _('Edit'))
]
def get_error_message(self):
model_name = self.verbose_name
return _("The %s could not be created due to errors.") % model_name
def form_valid(self, form):
instance = form.save()
messages.success(
self.request, self.get_success_message(instance),
buttons=self.get_success_message_buttons(instance)
)
return redirect(self.get_success_url())
def form_invalid(self, form):
messages.validation_error(
self.request, self.get_error_message(), form
)
return self.render_to_response(self.get_context_data())
class InstanceSpecificView(WMABaseView):
instance_pk = None
pk_quoted = None
instance = None
def __init__(self, model_admin, instance_pk):
super().__init__(model_admin)
self.instance_pk = unquote(instance_pk)
self.pk_quoted = quote(self.instance_pk)
filter_kwargs = {}
filter_kwargs[self.pk_attname] = self.instance_pk
object_qs = model_admin.model._default_manager.get_queryset().filter(
**filter_kwargs)
self.instance = get_object_or_404(object_qs)
def get_page_subtitle(self):
return self.instance
@cached_property
def edit_url(self):
return self.url_helper.get_action_url('edit', self.pk_quoted)
@cached_property
def delete_url(self):
return self.url_helper.get_action_url('delete', self.pk_quoted)
def get_context_data(self, **kwargs):
context = {'instance': self.instance}
context.update(kwargs)
return super().get_context_data(**context)
class IndexView(SpreadsheetExportMixin, WMABaseView):
ORDER_VAR = 'o'
ORDER_TYPE_VAR = 'ot'
PAGE_VAR = 'p'
SEARCH_VAR = 'q'
ERROR_FLAG = 'e'
EXPORT_VAR = 'export'
IGNORED_PARAMS = (ORDER_VAR, ORDER_TYPE_VAR, SEARCH_VAR, EXPORT_VAR)
# sortable_by is required by the django.contrib.admin.templatetags.admin_list.result_headers
# template tag as of Django 2.1 - see https://docs.djangoproject.com/en/2.1/ref/contrib/admin/#django.contrib.admin.ModelAdmin.sortable_by
sortable_by = None
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
# Only continue if logged in user has list permission
if not self.permission_helper.user_can_list(request.user):
raise PermissionDenied
self.list_export = self.model_admin.get_list_export(request)
self.list_display = self.model_admin.get_list_display(request)
self.list_filter = self.model_admin.get_list_filter(request)
self.search_fields = self.model_admin.get_search_fields(request)
self.items_per_page = self.model_admin.list_per_page
self.select_related = self.model_admin.list_select_related
self.search_handler = self.model_admin.get_search_handler(request, self.search_fields)
self.export = (request.GET.get(self.EXPORT_VAR))
# Get search parameters from the query string.
try:
self.page_num = int(request.GET.get(self.PAGE_VAR, 0))
except ValueError:
self.page_num = 0
self.params = dict(request.GET.items())
if self.PAGE_VAR in self.params:
del self.params[self.PAGE_VAR]
if self.ERROR_FLAG in self.params:
del self.params[self.ERROR_FLAG]
if self.EXPORT_VAR in self.params:
del self.params[self.EXPORT_VAR]
self.query = request.GET.get(self.SEARCH_VAR, '')
self.queryset = self.get_queryset(request)
if self.export in self.FORMATS:
return self.as_spreadsheet(self.queryset, self.export)
return super().dispatch(request, *args, **kwargs)
def get_heading(self, queryset, field):
""" Get headings for exported spreadsheet column for the relevant field """
heading_override = self.export_heading_overrides.get(field)
if heading_override:
return force_str(heading_override)
return force_str(label_for_field(field, model=self.model).title())
def to_row_dict(self, item):
""" Returns an OrderedDict (in the order given by list_export) of the exportable information for a model instance"""
row_dict = OrderedDict()
for field in self.list_export:
f, attr, value = lookup_field(field, item, self.model_admin)
if not value:
value = getattr(attr, 'empty_value_display', self.model_admin.get_empty_value_display(field))
row_dict[field] = value
return row_dict
@property
def media(self):
return forms.Media(
css={'all': self.model_admin.get_index_view_extra_css()},
js=self.model_admin.get_index_view_extra_js()
)
def get_buttons_for_obj(self, obj):
return self.button_helper.get_buttons_for_obj(
obj, classnames_add=['button-small', 'button-secondary'])
def get_search_results(self, request, queryset, search_term):
kwargs = self.model_admin.get_extra_search_kwargs(request, search_term)
kwargs['preserve_order'] = self.ORDER_VAR in request.GET
return self.search_handler.search_queryset(queryset, search_term, **kwargs)
def get_filters_params(self, params=None):
"""
Returns all params except IGNORED_PARAMS
"""
if not params:
params = self.params
lookup_params = params.copy() # a dictionary of the query string
# Remove all the parameters that are globally and systematically
# ignored.
for ignored in self.IGNORED_PARAMS:
if ignored in lookup_params:
del lookup_params[ignored]
return lookup_params
def get_filters(self, request):
lookup_params = self.get_filters_params()
use_distinct = False
filter_specs = []
if self.list_filter:
for list_filter in self.list_filter:
if callable(list_filter):
# This is simply a custom list filter class.
spec = list_filter(
request,
lookup_params,
self.model,
self.model_admin)
else:
field_path = None
if isinstance(list_filter, (tuple, list)):
# This is a custom FieldListFilter class for a given
# field.
field, field_list_filter_class = list_filter
else:
# This is simply a field name, so use the default
# FieldListFilter class that has been registered for
# the type of the given field.
field = list_filter
field_list_filter_class = FieldListFilter.create
if not isinstance(field, models.Field):
field_path = field
field = get_fields_from_path(self.model,
field_path)[-1]
spec = field_list_filter_class(
field,
request,
lookup_params,
self.model,
self.model_admin,
field_path=field_path)
# Check if we need to use distinct()
use_distinct = (
use_distinct or lookup_needs_distinct(self.opts,
field_path))
if spec and spec.has_output():
filter_specs.append(spec)
# At this point, all the parameters used by the various ListFilters
# have been removed from lookup_params, which now only contains other
# parameters passed via the query string. We now loop through the
# remaining parameters both to ensure that all the parameters are valid
# fields and to determine if at least one of them needs distinct(). If
# the lookup parameters aren't real fields, then bail out.
try:
for key, value in lookup_params.items():
lookup_params[key] = prepare_lookup_value(key, value)
use_distinct = (
use_distinct or lookup_needs_distinct(self.opts, key))
return (
filter_specs, bool(filter_specs), lookup_params, use_distinct
)
except FieldDoesNotExist as e:
raise IncorrectLookupParameters from e
def get_query_string(self, new_params=None, remove=None):
if new_params is None:
new_params = {}
if remove is None:
remove = []
p = self.params.copy()
for r in remove:
for k in list(p):
if k.startswith(r):
del p[k]
for k, v in new_params.items():
if v is None:
if k in p:
del p[k]
else:
p[k] = v
return '?%s' % urlencode(sorted(p.items()))
def _get_default_ordering(self):
ordering = []
if self.model_admin.ordering:
ordering = self.model_admin.ordering
elif self.opts.ordering:
ordering = self.opts.ordering
return ordering
def get_default_ordering(self, request):
if self.model_admin.get_ordering(request):
return self.model_admin.get_ordering(request)
if self.opts.ordering:
return self.opts.ordering
return ()
def get_ordering_field(self, field_name):
"""
Returns the proper model field name corresponding to the given
field_name to use for ordering. field_name may either be the name of a
proper model field or the name of a method (on the admin or model) or a
callable with the 'admin_order_field' attribute. Returns None if no
proper model field name can be matched.
"""
try:
field = self.opts.get_field(field_name)
return field.name
except FieldDoesNotExist:
# See whether field_name is a name of a non-field
# that allows sorting.
if callable(field_name):
attr = field_name
elif hasattr(self.model_admin, field_name):
attr = getattr(self.model_admin, field_name)
else:
attr = getattr(self.model, field_name)
return getattr(attr, 'admin_order_field', None)
def get_ordering(self, request, queryset):
"""
Returns the list of ordering fields for the change list.
First we check the get_ordering() method in model admin, then we check
the object's default ordering. Then, any manually-specified ordering
from the query string overrides anything. Finally, a deterministic
order is guaranteed by ensuring the primary key is used as the last
ordering field.
"""
params = self.params
ordering = list(self.get_default_ordering(request))
if self.ORDER_VAR in params:
# Clear ordering and used params
ordering = []
order_params = params[self.ORDER_VAR].split('.')
for p in order_params:
try:
none, pfx, idx = p.rpartition('-')
field_name = self.list_display[int(idx)]
order_field = self.get_ordering_field(field_name)
if not order_field:
continue # No 'admin_order_field', skip it
# reverse order if order_field has already "-" as prefix
if order_field.startswith('-') and pfx == "-":
ordering.append(order_field[1:])
else:
ordering.append(pfx + order_field)
except (IndexError, ValueError):
continue # Invalid ordering specified, skip it.
# Add the given query's ordering fields, if any.
ordering.extend(queryset.query.order_by)
# Ensure that the primary key is systematically present in the list of
# ordering fields so we can guarantee a deterministic order across all
# database backends.
pk_name = self.opts.pk.name
if not (set(ordering) & {'pk', '-pk', pk_name, '-' + pk_name}):
# ordering isn't already being applied to pk
ordering.append('-' + pk_name)
return ordering
def get_ordering_field_columns(self):
"""
Returns an OrderedDict of ordering field column numbers and asc/desc
"""
# We must cope with more than one column having the same underlying
# sort field, so we base things on column numbers.
ordering = self._get_default_ordering()
ordering_fields = OrderedDict()
if self.ORDER_VAR not in self.params:
# for ordering specified on model_admin or model Meta, we don't
# know the right column numbers absolutely, because there might be
# morr than one column associated with that ordering, so we guess.
for field in ordering:
if field.startswith('-'):
field = field[1:]
order_type = 'desc'
else:
order_type = 'asc'
for index, attr in enumerate(self.list_display):
if self.get_ordering_field(attr) == field:
ordering_fields[index] = order_type
break
else:
for p in self.params[self.ORDER_VAR].split('.'):
none, pfx, idx = p.rpartition('-')
try:
idx = int(idx)
except ValueError:
continue # skip it
ordering_fields[idx] = 'desc' if pfx == '-' else 'asc'
return ordering_fields
def get_queryset(self, request=None):
request = request or self.request
# First, we collect all the declared list filters.
(self.filter_specs, self.has_filters, remaining_lookup_params,
filters_use_distinct) = self.get_filters(request)
# Then, we let every list filter modify the queryset to its liking.
qs = self.get_base_queryset(request)
for filter_spec in self.filter_specs:
new_qs = filter_spec.queryset(request, qs)
if new_qs is not None:
qs = new_qs
try:
# Finally, we apply the remaining lookup parameters from the query
# string (i.e. those that haven't already been processed by the
# filters).
qs = qs.filter(**remaining_lookup_params)
except (SuspiciousOperation, ImproperlyConfigured):
# Allow certain types of errors to be re-raised as-is so that the
# caller can treat them in a special way.
raise
except Exception as e:
# Every other error is caught with a naked except, because we don't
# have any other way of validating lookup parameters. They might be
# invalid if the keyword arguments are incorrect, or if the values
# are not in the correct type, so we might get FieldError,
# ValueError, ValidationError, or ?.
raise IncorrectLookupParameters(e)
if not qs.query.select_related:
qs = self.apply_select_related(qs)
# Set ordering.
ordering = self.get_ordering(request, qs)
qs = qs.order_by(*ordering)
# Remove duplicates from results, if necessary
if filters_use_distinct:
qs = qs.distinct()
# Apply search results
return self.get_search_results(request, qs, self.query)
def apply_select_related(self, qs):
if self.select_related is True:
return qs.select_related()
if self.select_related is False:
if self.has_related_field_in_list_display():
return qs.select_related()
if self.select_related:
return qs.select_related(*self.select_related)
return qs
def has_related_field_in_list_display(self):
for field_name in self.list_display:
try:
field = self.opts.get_field(field_name)
except FieldDoesNotExist:
pass
else:
if isinstance(field, models.ManyToOneRel):
return True
return False
def get_context_data(self, **kwargs):
user = self.request.user
all_count = self.get_base_queryset().count()
queryset = self.get_queryset()
result_count = queryset.count()
paginator = Paginator(queryset, self.items_per_page)
try:
page_obj = paginator.page(self.page_num + 1)
except InvalidPage:
page_obj = paginator.page(1)
context = {
'view': self,
'all_count': all_count,
'result_count': result_count,
'paginator': paginator,
'page_obj': page_obj,
'object_list': page_obj.object_list,
'user_can_create': self.permission_helper.user_can_create(user),
'show_search': self.search_handler.show_search_form,
}
if self.is_pagemodel:
models = self.model.allowed_parent_page_models()
allowed_parent_types = [m._meta.verbose_name for m in models]
valid_parents = self.permission_helper.get_valid_parent_pages(user)
valid_parent_count = valid_parents.count()
context.update({
'no_valid_parents': not valid_parent_count,
'required_parent_types': allowed_parent_types,
})
context.update(kwargs)
return super().get_context_data(**context)
def get_template_names(self):
return self.model_admin.get_index_template()
class CreateView(ModelFormView):
page_title = _('New')
def check_action_permitted(self, user):
return self.permission_helper.user_can_create(user)
def dispatch(self, request, *args, **kwargs):
if self.is_pagemodel:
user = request.user
parents = self.permission_helper.get_valid_parent_pages(user)
parent_count = parents.count()
# There's only one available parent for this page type for this
# user, so we send them along with that as the chosen parent page
if parent_count == 1:
parent = parents.get()
parent_pk = quote(parent.pk)
return redirect(self.url_helper.get_action_url(
'add', self.app_label, self.model_name, parent_pk))
# The page can be added in multiple places, so redirect to the
# choose_parent view so that the parent can be specified
return redirect(self.url_helper.get_action_url('choose_parent'))
return super().dispatch(request, *args, **kwargs)
def get_meta_title(self):
return _('Create new %s') % self.verbose_name
def get_page_subtitle(self):
return capfirst(self.verbose_name)
def get_template_names(self):
return self.model_admin.get_create_template()
class EditView(ModelFormView, InstanceSpecificView):
page_title = _('Editing')
def check_action_permitted(self, user):
return self.permission_helper.user_can_edit_obj(user, self.instance)
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
if self.is_pagemodel:
return redirect(
self.url_helper.get_action_url('edit', self.pk_quoted)
)
return super().dispatch(request, *args, **kwargs)
def get_meta_title(self):
return _('Editing %s') % self.verbose_name
def get_success_message(self, instance):
return _("%(model_name)s '%(instance)s' updated.") % {
'model_name': capfirst(self.verbose_name), 'instance': instance
}
def get_context_data(self, **kwargs):
context = {
'user_can_delete': self.permission_helper.user_can_delete_obj(
self.request.user, self.instance)
}
context.update(kwargs)
return super().get_context_data(**context)
def get_error_message(self):
name = self.verbose_name
return _("The %s could not be saved due to errors.") % name
def get_template_names(self):
return self.model_admin.get_edit_template()
class ChooseParentView(WMABaseView):
def dispatch(self, request, *args, **kwargs):
if not self.permission_helper.user_can_create(request.user):
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
def get_page_title(self):
return _('Add %s') % self.verbose_name
def get_form(self, request):
parents = self.permission_helper.get_valid_parent_pages(request.user)
return ParentChooserForm(parents, request.POST or None)
def get(self, request, *args, **kwargs):
form = self.get_form(request)
context = self.get_context_data(form=form)
return self.render_to_response(context)
def post(self, request, *args, **kargs):
form = self.get_form(request)
if form.is_valid():
return self.form_valid(form)
return self.form_invalid(form)
def form_valid(self, form):
parent_pk = quote(form.cleaned_data['parent_page'].pk)
return redirect(self.url_helper.get_action_url(
'add', self.app_label, self.model_name, parent_pk))
def form_invalid(self, form):
context = self.get_context_data(form=form)
return self.render_to_response(context)
def get_template_names(self):
return self.model_admin.get_choose_parent_template()
class DeleteView(InstanceSpecificView):
page_title = _('Delete')
def check_action_permitted(self, user):
return self.permission_helper.user_can_delete_obj(user, self.instance)
@method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
if not self.check_action_permitted(request.user):
raise PermissionDenied
if self.is_pagemodel:
return redirect(
self.url_helper.get_action_url('delete', self.pk_quoted)
)
return super().dispatch(request, *args, **kwargs)
def get_meta_title(self):
return _('Confirm deletion of %s') % self.verbose_name
def confirmation_message(self):
return _(
"Are you sure you want to delete this %s? If other things in your "
"site are related to it, they may also be affected."
) % self.verbose_name
def delete_instance(self):
self.instance.delete()
def post(self, request, *args, **kwargs):
try:
msg = _("%(model_name)s '%(instance)s' deleted.") % {
'model_name': self.verbose_name, 'instance': self.instance
}
self.delete_instance()
messages.success(request, msg)
return redirect(self.index_url)
except models.ProtectedError:
linked_objects = []
fields = self.model._meta.fields_map.values()
fields = (obj for obj in fields if not isinstance(
obj.field, ManyToManyField))
for rel in fields:
if rel.on_delete == models.PROTECT:
if isinstance(rel, OneToOneRel):
try:
obj = getattr(self.instance, rel.get_accessor_name())
except ObjectDoesNotExist:
pass
else:
linked_objects.append(obj)
else:
qs = getattr(self.instance, rel.get_accessor_name())
for obj in qs.all():
linked_objects.append(obj)
context = self.get_context_data(
protected_error=True,
linked_objects=linked_objects
)
return self.render_to_response(context)
def get_template_names(self):
return self.model_admin.get_delete_template()
class InspectView(InstanceSpecificView):
page_title = _('Inspecting')
def check_action_permitted(self, user):
return self.permission_helper.user_can_inspect_obj(user, self.instance)
@property
def media(self):
return forms.Media(
css={'all': self.model_admin.get_inspect_view_extra_css()},
js=self.model_admin.get_inspect_view_extra_js()
)
def get_meta_title(self):
return _('Inspecting %s') % self.verbose_name
def get_field_label(self, field_name, field=None):
""" Return a label to display for a field """
return label_for_field(field_name, model=self.model)
def get_field_display_value(self, field_name, field=None):
""" Return a display value for a field/attribute """
# First we check for a 'get_fieldname_display' property/method on
# the model, and return the value of that, if present.
val_funct = getattr(self.instance, 'get_%s_display' % field_name, None)
if val_funct is not None:
if callable(val_funct):
return val_funct()
return val_funct
# Now let's get the attribute value from the instance itself and see if
# we can render something useful. raises AttributeError appropriately.
val = getattr(self.instance, field_name)
if isinstance(val, models.Manager):
val = val.all()
if isinstance(val, models.QuerySet):
if val.exists():
return ', '.join(['%s' % obj for obj in val])
return self.model_admin.get_empty_value_display(field_name)
# wagtail.images might not be installed
try:
from wagtail.images.models import AbstractImage
if isinstance(val, AbstractImage):
# Render a rendition of the image
return self.get_image_field_display(field_name, field)
except RuntimeError:
pass
# wagtail.wagtaildocuments might not be installed
try:
from wagtail.documents.models import AbstractDocument
if isinstance(val, AbstractDocument):
# Render a link to the document
return self.get_document_field_display(field_name, field)
except RuntimeError:
pass
# Resort to returning the real value or 'empty value'
if val or val is False:
return val
return self.model_admin.get_empty_value_display(field_name)
def get_image_field_display(self, field_name, field):
""" Render an image """
from wagtail.images.shortcuts import get_rendition_or_not_found
image = getattr(self.instance, field_name)
if image:
return get_rendition_or_not_found(image, 'max-400x400').img_tag
return self.model_admin.get_empty_value_display(field_name)
def get_document_field_display(self, field_name, field):
""" Render a link to a document """
document = getattr(self.instance, field_name)
if document:
return mark_safe(
'<a href="%s">%s <span class="meta">(%s, %s)</span></a>' % (
document.url,
document.title,
document.file_extension.upper(),
filesizeformat(document.file.size),
)
)
return self.model_admin.get_empty_value_display(field_name)
def get_dict_for_field(self, field_name):
"""
Return a dictionary containing `label` and `value` values to display
for a field.
"""
try:
field = self.model._meta.get_field(field_name)
except FieldDoesNotExist:
field = None
return {
'label': self.get_field_label(field_name, field),
'value': self.get_field_display_value(field_name, field),
}
def get_fields_dict(self):
"""
Return a list of `label`/`value` dictionaries to represent the
fiels named by the model_admin class's `get_inspect_view_fields` method
"""
fields = []
for field_name in self.model_admin.get_inspect_view_fields():
fields.append(self.get_dict_for_field(field_name))
return fields
def get_context_data(self, **kwargs):
context = {
'fields': self.get_fields_dict(),
'buttons': self.button_helper.get_buttons_for_obj(
self.instance, exclude=['inspect']),
}
context.update(kwargs)
return super().get_context_data(**context)
def get_template_names(self):
return self.model_admin.get_inspect_template()