kopia lustrzana https://github.com/wagtail/wagtail
				
				
				
			
		
			
				
	
	
		
			917 wiersze
		
	
	
		
			34 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			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()
 |