mirror of
https://github.com/Hopiu/wagtail.git
synced 2026-05-05 05:54:44 +00:00
Fixes #5539. Transifex and Django's makemessages command have validation to catch invalid placeholder variables within translated strings - for example, where the translator has translated the variable name - but these only recognise old-style `%` formatting, not the `format` method, and so it's better for us to standardise on % formatting. To reduce the burden on translators having to re-translate these strings, only the ones using named placeholders (`"Edited page {title}"`) rather than numeric ones (`"Edited page {0}"`) have been changed - hopefully the latter give less room for error. Also fixed some incorrect use of plurals (verbose_name vs verbose_name_plural) in snippet confirmation messages.
888 lines
33 KiB
Python
888 lines
33 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_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 ugettext as _
|
|
from django.views.generic import TemplateView
|
|
from django.views.generic.edit import FormView
|
|
|
|
from wagtail.admin import messages
|
|
|
|
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(WMABaseView):
|
|
|
|
ORDER_VAR = 'o'
|
|
ORDER_TYPE_VAR = 'ot'
|
|
PAGE_VAR = 'p'
|
|
SEARCH_VAR = 'q'
|
|
ERROR_FLAG = 'e'
|
|
IGNORED_PARAMS = (ORDER_VAR, ORDER_TYPE_VAR, SEARCH_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_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)
|
|
|
|
# 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]
|
|
|
|
self.query = request.GET.get(self.SEARCH_VAR, '')
|
|
self.queryset = self.get_queryset(request)
|
|
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
@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()
|