options.py

为了解admin.ModelAdmin的源码去阅读options.py。阅读源码的目的,是为了更好的重写option.py来实现admin的客制化功能。

BaseModelAdmin之前

import copy
import json
import operator
import re
from functools import partial, reduce, update_wrapper
from urllib.parse import quote as urlquotefrom django import forms
from django.conf import settings
from django.contrib import messages
from django.contrib.admin import helpers, widgets
from django.contrib.admin.checks import (BaseModelAdminChecks, InlineModelAdminChecks, ModelAdminChecks,
)
from django.contrib.admin.exceptions import DisallowedModelAdminToField
from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
from django.contrib.admin.utils import (NestedObjects, construct_change_message, flatten_fieldsets,get_deleted_objects, lookup_needs_distinct, model_format_dict,model_ngettext, quote, unquote,
)
from django.contrib.admin.views.autocomplete import AutocompleteJsonView
from django.contrib.admin.widgets import (AutocompleteSelect, AutocompleteSelectMultiple,
)
from django.contrib.auth import get_permission_codename
from django.core.exceptions import (FieldDoesNotExist, FieldError, PermissionDenied, ValidationError,
)
from django.core.paginator import Paginator
from django.db import models, router, transaction
from django.db.models.constants import LOOKUP_SEP
from django.forms.formsets import DELETION_FIELD_NAME, all_valid
from django.forms.models import (BaseInlineFormSet, inlineformset_factory, modelform_defines_fields,modelform_factory, modelformset_factory,
)
from django.forms.widgets import CheckboxSelectMultiple, SelectMultiple
from django.http import HttpResponseRedirect
from django.http.response import HttpResponseBase
from django.template.response import SimpleTemplateResponse, TemplateResponse
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.utils.html import format_html
from django.utils.http import urlencode
from django.utils.safestring import mark_safe
from django.utils.text import capfirst, format_lazy, get_text_list
from django.utils.translation import gettext as _, ngettext
from django.views.decorators.csrf import csrf_protect
from django.views.generic import RedirectViewIS_POPUP_VAR = '_popup'
TO_FIELD_VAR = '_to_field'HORIZONTAL, VERTICAL = 1, 2def get_content_type_for_model(obj):# Since this module gets imported in the application's root package,# it cannot import models from other applications at the module level.from django.contrib.contenttypes.models import ContentTypereturn ContentType.objects.get_for_model(obj, for_concrete_model=False)def get_ul_class(radio_style):return 'radiolist' if radio_style == VERTICAL else 'radiolist inline'class IncorrectLookupParameters(Exception):pass# Defaults for formfield_overrides. ModelAdmin subclasses can change this
# by adding to ModelAdmin.formfield_overrides.FORMFIELD_FOR_DBFIELD_DEFAULTS = {models.DateTimeField: {'form_class': forms.SplitDateTimeField,'widget': widgets.AdminSplitDateTime},models.DateField: {'widget': widgets.AdminDateWidget},models.TimeField: {'widget': widgets.AdminTimeWidget},models.TextField: {'widget': widgets.AdminTextareaWidget},models.URLField: {'widget': widgets.AdminURLFieldWidget},models.IntegerField: {'widget': widgets.AdminIntegerFieldWidget},models.BigIntegerField: {'widget': widgets.AdminBigIntegerFieldWidget},models.CharField: {'widget': widgets.AdminTextInputWidget},models.ImageField: {'widget': widgets.AdminFileWidget},models.FileField: {'widget': widgets.AdminFileWidget},models.EmailField: {'widget': widgets.AdminEmailInputWidget},models.UUIDField: {'widget': widgets.AdminUUIDInputWidget},
}csrf_protect_m = method_decorator(csrf_protect)

class BaseModelAdmin

class BaseModelAdmin(metaclass=forms.MediaDefiningClass):"""Functionality common to both ModelAdmin and InlineAdmin."""autocomplete_fields = ()raw_id_fields = ()fields = Noneexclude = Nonefieldsets = Noneform = forms.ModelFormfilter_vertical = ()filter_horizontal = ()radio_fields = {}prepopulated_fields = {}formfield_overrides = {}readonly_fields = ()ordering = Nonesortable_by = Noneview_on_site = Trueshow_full_result_count = Truechecks_class = BaseModelAdminChecksdef check(self, **kwargs):return self.checks_class().check(self, **kwargs)def __init__(self):# Merge FORMFIELD_FOR_DBFIELD_DEFAULTS with the formfield_overrides# rather than simply overwriting.overrides = copy.deepcopy(FORMFIELD_FOR_DBFIELD_DEFAULTS)for k, v in self.formfield_overrides.items():overrides.setdefault(k, {}).update(v)self.formfield_overrides = overridesdef formfield_for_dbfield(self, db_field, request, **kwargs):"""Hook for specifying the form Field instance for a given database Fieldinstance.If kwargs are given, they're passed to the form Field's constructor."""# If the field specifies choices, we don't need to look for special# admin widgets - we just need to use a select widget of some kind.if db_field.choices:return self.formfield_for_choice_field(db_field, request, **kwargs)# ForeignKey or ManyToManyFieldsif isinstance(db_field, (models.ForeignKey, models.ManyToManyField)):# Combine the field kwargs with any options for formfield_overrides.# Make sure the passed in **kwargs override anything in# formfield_overrides because **kwargs is more specific, and should# always win.if db_field.__class__ in self.formfield_overrides:kwargs = {**self.formfield_overrides[db_field.__class__], **kwargs}# Get the correct formfield.if isinstance(db_field, models.ForeignKey):formfield = self.formfield_for_foreignkey(db_field, request, **kwargs)elif isinstance(db_field, models.ManyToManyField):formfield = self.formfield_for_manytomany(db_field, request, **kwargs)# For non-raw_id fields, wrap the widget with a wrapper that adds# extra HTML -- the "add other" interface -- to the end of the# rendered output. formfield can be None if it came from a# OneToOneField with parent_link=True or a M2M intermediary.if formfield and db_field.name not in self.raw_id_fields:related_modeladmin = self.admin_site._registry.get(db_field.remote_field.model)wrapper_kwargs = {}if related_modeladmin:wrapper_kwargs.update(can_add_related=related_modeladmin.has_add_permission(request),can_change_related=related_modeladmin.has_change_permission(request),can_delete_related=related_modeladmin.has_delete_permission(request),can_view_related=related_modeladmin.has_view_permission(request),)formfield.widget = widgets.RelatedFieldWidgetWrapper(formfield.widget, db_field.remote_field, self.admin_site, **wrapper_kwargs)return formfield# If we've got overrides for the formfield defined, use 'em. **kwargs# passed to formfield_for_dbfield override the defaults.for klass in db_field.__class__.mro():if klass in self.formfield_overrides:kwargs = {**copy.deepcopy(self.formfield_overrides[klass]), **kwargs}return db_field.formfield(**kwargs)# For any other type of field, just call its formfield() method.return db_field.formfield(**kwargs)def formfield_for_choice_field(self, db_field, request, **kwargs):"""Get a form Field for a database Field that has declared choices."""# If the field is named as a radio_field, use a RadioSelectif db_field.name in self.radio_fields:# Avoid stomping on custom widget/choices arguments.if 'widget' not in kwargs:kwargs['widget'] = widgets.AdminRadioSelect(attrs={'class': get_ul_class(self.radio_fields[db_field.name]),})if 'choices' not in kwargs:kwargs['choices'] = db_field.get_choices(include_blank=db_field.blank,blank_choice=[('', _('None'))])return db_field.formfield(**kwargs)def get_field_queryset(self, db, db_field, request):"""If the ModelAdmin specifies ordering, the queryset should respect thatordering.  Otherwise don't specify the queryset, let the field decide(return None in that case)."""related_admin = self.admin_site._registry.get(db_field.remote_field.model)if related_admin is not None:ordering = related_admin.get_ordering(request)if ordering is not None and ordering != ():return db_field.remote_field.model._default_manager.using(db).order_by(*ordering)return Nonedef formfield_for_foreignkey(self, db_field, request, **kwargs):"""Get a form Field for a ForeignKey."""db = kwargs.get('using')if 'widget' not in kwargs:if db_field.name in self.get_autocomplete_fields(request):kwargs['widget'] = AutocompleteSelect(db_field.remote_field, self.admin_site, using=db)elif db_field.name in self.raw_id_fields:kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.remote_field, self.admin_site, using=db)elif db_field.name in self.radio_fields:kwargs['widget'] = widgets.AdminRadioSelect(attrs={'class': get_ul_class(self.radio_fields[db_field.name]),})kwargs['empty_label'] = _('None') if db_field.blank else Noneif 'queryset' not in kwargs:queryset = self.get_field_queryset(db, db_field, request)if queryset is not None:kwargs['queryset'] = querysetreturn db_field.formfield(**kwargs)def formfield_for_manytomany(self, db_field, request, **kwargs):"""Get a form Field for a ManyToManyField."""# If it uses an intermediary model that isn't auto created, don't show# a field in admin.if not db_field.remote_field.through._meta.auto_created:return Nonedb = kwargs.get('using')if 'widget' not in kwargs:autocomplete_fields = self.get_autocomplete_fields(request)if db_field.name in autocomplete_fields:kwargs['widget'] = AutocompleteSelectMultiple(db_field.remote_field,self.admin_site,using=db,)elif db_field.name in self.raw_id_fields:kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.remote_field,self.admin_site,using=db,)elif db_field.name in [*self.filter_vertical, *self.filter_horizontal]:kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name,db_field.name in self.filter_vertical)if 'queryset' not in kwargs:queryset = self.get_field_queryset(db, db_field, request)if queryset is not None:kwargs['queryset'] = querysetform_field = db_field.formfield(**kwargs)if (isinstance(form_field.widget, SelectMultiple) andnot isinstance(form_field.widget, (CheckboxSelectMultiple, AutocompleteSelectMultiple))):msg = _('Hold down “Control”, or “Command” on a Mac, to select more than one.')help_text = form_field.help_textform_field.help_text = format_lazy('{} {}', help_text, msg) if help_text else msgreturn form_fielddef get_autocomplete_fields(self, request):"""Return a list of ForeignKey and/or ManyToMany fields which should usean autocomplete widget."""return self.autocomplete_fieldsdef get_view_on_site_url(self, obj=None):if obj is None or not self.view_on_site:return Noneif callable(self.view_on_site):return self.view_on_site(obj)elif self.view_on_site and hasattr(obj, 'get_absolute_url'):# use the ContentType lookup if view_on_site is Truereturn reverse('admin:view_on_site', kwargs={'content_type_id': get_content_type_for_model(obj).pk,'object_id': obj.pk})def get_empty_value_display(self):"""Return the empty_value_display set on ModelAdmin or AdminSite."""try:return mark_safe(self.empty_value_display)except AttributeError:return mark_safe(self.admin_site.empty_value_display)def get_exclude(self, request, obj=None):"""Hook for specifying exclude."""return self.excludedef get_fields(self, request, obj=None):"""Hook for specifying fields."""if self.fields:return self.fields# _get_form_for_get_fields() is implemented in subclasses.form = self._get_form_for_get_fields(request, obj)return [*form.base_fields, *self.get_readonly_fields(request, obj)]def get_fieldsets(self, request, obj=None):"""Hook for specifying fieldsets."""if self.fieldsets:return self.fieldsetsreturn [(None, {'fields': self.get_fields(request, obj)})]def get_inlines(self, request, obj):"""Hook for specifying custom inlines."""return self.inlinesdef get_ordering(self, request):"""Hook for specifying field ordering."""return self.ordering or ()  # otherwise we might try to *None, which is bad ;)def get_readonly_fields(self, request, obj=None):"""Hook for specifying custom readonly fields."""return self.readonly_fieldsdef get_prepopulated_fields(self, request, obj=None):"""Hook for specifying custom prepopulated fields."""return self.prepopulated_fieldsdef get_queryset(self, request):"""Return a QuerySet of all model instances that can be edited by theadmin site. This is used by changelist_view."""qs = self.model._default_manager.get_queryset()# TODO: this should be handled by some parameter to the ChangeList.ordering = self.get_ordering(request)if ordering:qs = qs.order_by(*ordering)return qsdef get_sortable_by(self, request):"""Hook for specifying which fields can be sorted in the changelist."""return self.sortable_by if self.sortable_by is not None else self.get_list_display(request)def lookup_allowed(self, lookup, value):from django.contrib.admin.filters import SimpleListFiltermodel = self.model# Check FKey lookups that are allowed, so that popups produced by# ForeignKeyRawIdWidget, on the basis of ForeignKey.limit_choices_to,# are allowed to work.for fk_lookup in model._meta.related_fkey_lookups:# As ``limit_choices_to`` can be a callable, invoke it here.if callable(fk_lookup):fk_lookup = fk_lookup()if (lookup, value) in widgets.url_params_from_lookup_dict(fk_lookup).items():return Truerelation_parts = []prev_field = Nonefor part in lookup.split(LOOKUP_SEP):try:field = model._meta.get_field(part)except FieldDoesNotExist:# Lookups on nonexistent fields are ok, since they're ignored# later.break# It is allowed to filter on values that would be found from local# model anyways. For example, if you filter on employee__department__id,# then the id value would be found already from employee__department_id.if not prev_field or (prev_field.is_relation andfield not in prev_field.get_path_info()[-1].target_fields):relation_parts.append(part)if not getattr(field, 'get_path_info', None):# This is not a relational field, so further parts# must be transforms.breakprev_field = fieldmodel = field.get_path_info()[-1].to_opts.modelif len(relation_parts) <= 1:# Either a local field filter, or no fields at all.return Truevalid_lookups = {self.date_hierarchy}for filter_item in self.list_filter:if isinstance(filter_item, type) and issubclass(filter_item, SimpleListFilter):valid_lookups.add(filter_item.parameter_name)elif isinstance(filter_item, (list, tuple)):valid_lookups.add(filter_item[0])else:valid_lookups.add(filter_item)# Is it a valid relational lookup?return not {LOOKUP_SEP.join(relation_parts),LOOKUP_SEP.join(relation_parts + [part])}.isdisjoint(valid_lookups)def to_field_allowed(self, request, to_field):"""Return True if the model associated with this admin should beallowed to be referenced by the specified field."""opts = self.model._metatry:field = opts.get_field(to_field)except FieldDoesNotExist:return False# Always allow referencing the primary key since it's already possible# to get this information from the change view URL.if field.primary_key:return True# Allow reverse relationships to models defining m2m fields if they# target the specified field.for many_to_many in opts.many_to_many:if many_to_many.m2m_target_field_name() == to_field:return True# Make sure at least one of the models registered for this site# references this field through a FK or a M2M relationship.registered_models = set()for model, admin in self.admin_site._registry.items():registered_models.add(model)for inline in admin.inlines:registered_models.add(inline.model)related_objects = (f for f in opts.get_fields(include_hidden=True)if (f.auto_created and not f.concrete))for related_object in related_objects:related_model = related_object.related_modelremote_field = related_object.field.remote_fieldif (any(issubclass(model, related_model) for model in registered_models) andhasattr(remote_field, 'get_related_field') andremote_field.get_related_field() == field):return Truereturn Falsedef has_add_permission(self, request):"""Return True if the given request has permission to add an object.Can be overridden by the user in subclasses."""opts = self.optscodename = get_permission_codename('add', opts)return request.user.has_perm("%s.%s" % (opts.app_label, codename))def has_change_permission(self, request, obj=None):"""Return True if the given request has permission to change the givenDjango model instance, the default implementation doesn't examine the`obj` parameter.Can be overridden by the user in subclasses. In such case it shouldreturn True if the given request has permission to change the `obj`model instance. If `obj` is None, this should return True if the givenrequest has permission to change *any* object of the given type."""opts = self.optscodename = get_permission_codename('change', opts)return request.user.has_perm("%s.%s" % (opts.app_label, codename))def has_delete_permission(self, request, obj=None):"""Return True if the given request has permission to change the givenDjango model instance, the default implementation doesn't examine the`obj` parameter.Can be overridden by the user in subclasses. In such case it shouldreturn True if the given request has permission to delete the `obj`model instance. If `obj` is None, this should return True if the givenrequest has permission to delete *any* object of the given type."""opts = self.optscodename = get_permission_codename('delete', opts)return request.user.has_perm("%s.%s" % (opts.app_label, codename))def has_view_permission(self, request, obj=None):"""Return True if the given request has permission to view the givenDjango model instance. The default implementation doesn't examine the`obj` parameter.If overridden by the user in subclasses, it should return True if thegiven request has permission to view the `obj` model instance. If `obj`is None, it should return True if the request has permission to viewany object of the given type."""opts = self.optscodename_view = get_permission_codename('view', opts)codename_change = get_permission_codename('change', opts)return (request.user.has_perm('%s.%s' % (opts.app_label, codename_view)) orrequest.user.has_perm('%s.%s' % (opts.app_label, codename_change)))def has_view_or_change_permission(self, request, obj=None):return self.has_view_permission(request, obj) or self.has_change_permission(request, obj)def has_module_permission(self, request):"""Return True if the given request has any permission in the givenapp label.Can be overridden by the user in subclasses. In such case it shouldreturn True if the given request has permission to view the module onthe admin index page and access the module's index page. Overriding itdoes not restrict access to the add, change or delete views. Use`ModelAdmin.has_(add|change|delete)_permission` for that."""return request.user.has_module_perms(self.opts.app_label)

class ModelAdmin(BaseModelAdmin)


class ModelAdmin(BaseModelAdmin):"""Encapsulate all admin options and functionality for a given model."""list_display = ('__str__',)list_display_links = ()list_filter = ()list_select_related = Falselist_per_page = 100list_max_show_all = 200list_editable = ()search_fields = ()date_hierarchy = Nonesave_as = Falsesave_as_continue = Truesave_on_top = Falsepaginator = Paginatorpreserve_filters = Trueinlines = []# Custom templates (designed to be over-ridden in subclasses)add_form_template = Nonechange_form_template = Nonechange_list_template = Nonedelete_confirmation_template = Nonedelete_selected_confirmation_template = Noneobject_history_template = Nonepopup_response_template = None# Actionsactions = []action_form = helpers.ActionFormactions_on_top = Trueactions_on_bottom = Falseactions_selection_counter = Truechecks_class = ModelAdminChecksdef __init__(self, model, admin_site):self.model = modelself.opts = model._metaself.admin_site = admin_sitesuper().__init__()def __str__(self):return "%s.%s" % (self.model._meta.app_label, self.__class__.__name__)def get_inline_instances(self, request, obj=None):inline_instances = []for inline_class in self.get_inlines(request, obj):inline = inline_class(self.model, self.admin_site)if request:if not (inline.has_view_or_change_permission(request, obj) orinline.has_add_permission(request, obj) orinline.has_delete_permission(request, obj)):continueif not inline.has_add_permission(request, obj):inline.max_num = 0inline_instances.append(inline)return inline_instancesdef get_urls(self):from django.urls import pathdef wrap(view):def wrapper(*args, **kwargs):return self.admin_site.admin_view(view)(*args, **kwargs)wrapper.model_admin = selfreturn update_wrapper(wrapper, view)info = self.model._meta.app_label, self.model._meta.model_namereturn [path('', wrap(self.changelist_view), name='%s_%s_changelist' % info),path('add/', wrap(self.add_view), name='%s_%s_add' % info),path('autocomplete/', wrap(self.autocomplete_view), name='%s_%s_autocomplete' % info),path('<path:object_id>/history/', wrap(self.history_view), name='%s_%s_history' % info),path('<path:object_id>/delete/', wrap(self.delete_view), name='%s_%s_delete' % info),path('<path:object_id>/change/', wrap(self.change_view), name='%s_%s_change' % info),# For backwards compatibility (was the change url before 1.9)path('<path:object_id>/', wrap(RedirectView.as_view(pattern_name='%s:%s_%s_change' % ((self.admin_site.name,) + info)))),]@propertydef urls(self):return self.get_urls()@propertydef media(self):extra = '' if settings.DEBUG else '.min'js = ['vendor/jquery/jquery%s.js' % extra,'jquery.init.js','core.js','admin/RelatedObjectLookups.js','actions%s.js' % extra,'urlify.js','prepopulate%s.js' % extra,'vendor/xregexp/xregexp%s.js' % extra,]return forms.Media(js=['admin/js/%s' % url for url in js])def get_model_perms(self, request):"""Return a dict of all perms for this model. This dict has the keys``add``, ``change``, ``delete``, and ``view`` mapping to the True/Falsefor each of those actions."""return {'add': self.has_add_permission(request),'change': self.has_change_permission(request),'delete': self.has_delete_permission(request),'view': self.has_view_permission(request),}def _get_form_for_get_fields(self, request, obj):return self.get_form(request, obj, fields=None)def get_form(self, request, obj=None, change=False, **kwargs):"""Return a Form class for use in the admin add view. This is used byadd_view and change_view."""if 'fields' in kwargs:fields = kwargs.pop('fields')else:fields = flatten_fieldsets(self.get_fieldsets(request, obj))excluded = self.get_exclude(request, obj)exclude = [] if excluded is None else list(excluded)readonly_fields = self.get_readonly_fields(request, obj)exclude.extend(readonly_fields)# Exclude all fields if it's a change form and the user doesn't have# the change permission.if change and hasattr(request, 'user') and not self.has_change_permission(request, obj):exclude.extend(fields)if excluded is None and hasattr(self.form, '_meta') and self.form._meta.exclude:# Take the custom ModelForm's Meta.exclude into account only if the# ModelAdmin doesn't define its own.exclude.extend(self.form._meta.exclude)# if exclude is an empty list we pass None to be consistent with the# default on modelform_factoryexclude = exclude or None# Remove declared form fields which are in readonly_fields.new_attrs = dict.fromkeys(f for f in readonly_fields if f in self.form.declared_fields)form = type(self.form.__name__, (self.form,), new_attrs)defaults = {'form': form,'fields': fields,'exclude': exclude,'formfield_callback': partial(self.formfield_for_dbfield, request=request),**kwargs,}if defaults['fields'] is None and not modelform_defines_fields(defaults['form']):defaults['fields'] = forms.ALL_FIELDStry:return modelform_factory(self.model, **defaults)except FieldError as e:raise FieldError('%s. Check fields/fieldsets/exclude attributes of class %s.'% (e, self.__class__.__name__))def get_changelist(self, request, **kwargs):"""Return the ChangeList class for use on the changelist page."""from django.contrib.admin.views.main import ChangeListreturn ChangeListdef get_changelist_instance(self, request):"""Return a `ChangeList` instance based on `request`. May raise`IncorrectLookupParameters`."""list_display = self.get_list_display(request)list_display_links = self.get_list_display_links(request, list_display)# Add the action checkboxes if any actions are available.if self.get_actions(request):list_display = ['action_checkbox', *list_display]sortable_by = self.get_sortable_by(request)ChangeList = self.get_changelist(request)return ChangeList(request,self.model,list_display,list_display_links,self.get_list_filter(request),self.date_hierarchy,self.get_search_fields(request),self.get_list_select_related(request),self.list_per_page,self.list_max_show_all,self.list_editable,self,sortable_by,)def get_object(self, request, object_id, from_field=None):"""Return an instance matching the field and value provided, the primarykey is used if no field is provided. Return ``None`` if no match isfound or the object_id fails validation."""queryset = self.get_queryset(request)model = queryset.modelfield = model._meta.pk if from_field is None else model._meta.get_field(from_field)try:object_id = field.to_python(object_id)return queryset.get(**{field.name: object_id})except (model.DoesNotExist, ValidationError, ValueError):return Nonedef get_changelist_form(self, request, **kwargs):"""Return a Form class for use in the Formset on the changelist page."""defaults = {'formfield_callback': partial(self.formfield_for_dbfield, request=request),**kwargs,}if defaults.get('fields') is None and not modelform_defines_fields(defaults.get('form')):defaults['fields'] = forms.ALL_FIELDSreturn modelform_factory(self.model, **defaults)def get_changelist_formset(self, request, **kwargs):"""Return a FormSet class for use on the changelist page if list_editableis used."""defaults = {'formfield_callback': partial(self.formfield_for_dbfield, request=request),**kwargs,}return modelformset_factory(self.model, self.get_changelist_form(request), extra=0,fields=self.list_editable, **defaults)def get_formsets_with_inlines(self, request, obj=None):"""Yield formsets and the corresponding inlines."""for inline in self.get_inline_instances(request, obj):yield inline.get_formset(request, obj), inlinedef get_paginator(self, request, queryset, per_page, orphans=0, allow_empty_first_page=True):return self.paginator(queryset, per_page, orphans, allow_empty_first_page)def log_addition(self, request, object, message):"""Log that an object has been successfully added.The default implementation creates an admin LogEntry object."""from django.contrib.admin.models import ADDITION, LogEntryreturn LogEntry.objects.log_action(user_id=request.user.pk,content_type_id=get_content_type_for_model(object).pk,object_id=object.pk,object_repr=str(object),action_flag=ADDITION,change_message=message,)def log_change(self, request, object, message):"""Log that an object has been successfully changed.The default implementation creates an admin LogEntry object."""from django.contrib.admin.models import CHANGE, LogEntryreturn LogEntry.objects.log_action(user_id=request.user.pk,content_type_id=get_content_type_for_model(object).pk,object_id=object.pk,object_repr=str(object),action_flag=CHANGE,change_message=message,)def log_deletion(self, request, object, object_repr):"""Log that an object will be deleted. Note that this method must becalled before the deletion.The default implementation creates an admin LogEntry object."""from django.contrib.admin.models import DELETION, LogEntryreturn LogEntry.objects.log_action(user_id=request.user.pk,content_type_id=get_content_type_for_model(object).pk,object_id=object.pk,object_repr=object_repr,action_flag=DELETION,)def action_checkbox(self, obj):"""A list_display column containing a checkbox widget."""return helpers.checkbox.render(helpers.ACTION_CHECKBOX_NAME, str(obj.pk))action_checkbox.short_description = mark_safe('<input type="checkbox" id="action-toggle">')def _get_base_actions(self):"""Return the list of actions, prior to any request-based filtering."""actions = []base_actions = (self.get_action(action) for action in self.actions or [])# get_action might have returned None, so filter any of those out.base_actions = [action for action in base_actions if action]base_action_names = {name for _, name, _ in base_actions}# Gather actions from the admin site firstfor (name, func) in self.admin_site.actions:if name in base_action_names:continuedescription = getattr(func, 'short_description', name.replace('_', ' '))actions.append((func, name, description))# Add actions from this ModelAdmin.actions.extend(base_actions)return actionsdef _filter_actions_by_permissions(self, request, actions):"""Filter out any actions that the user doesn't have access to."""filtered_actions = []for action in actions:callable = action[0]if not hasattr(callable, 'allowed_permissions'):filtered_actions.append(action)continuepermission_checks = (getattr(self, 'has_%s_permission' % permission)for permission in callable.allowed_permissions)if any(has_permission(request) for has_permission in permission_checks):filtered_actions.append(action)return filtered_actionsdef get_actions(self, request):"""Return a dictionary mapping the names of all actions for thisModelAdmin to a tuple of (callable, name, description) for each action."""# If self.actions is set to None that means actions are disabled on# this page.if self.actions is None or IS_POPUP_VAR in request.GET:return {}actions = self._filter_actions_by_permissions(request, self._get_base_actions())return {name: (func, name, desc) for func, name, desc in actions}def get_action_choices(self, request, default_choices=models.BLANK_CHOICE_DASH):"""Return a list of choices for use in a form object.  Each choice is atuple (name, description)."""choices = [] + default_choicesfor func, name, description in self.get_actions(request).values():choice = (name, description % model_format_dict(self.opts))choices.append(choice)return choicesdef get_action(self, action):"""Return a given action from a parameter, which can either be a callable,or the name of a method on the ModelAdmin.  Return is a tuple of(callable, name, description)."""# If the action is a callable, just use it.if callable(action):func = actionaction = action.__name__# Next, look for a method. Grab it off self.__class__ to get an unbound# method instead of a bound one; this ensures that the calling# conventions are the same for functions and methods.elif hasattr(self.__class__, action):func = getattr(self.__class__, action)# Finally, look for a named method on the admin siteelse:try:func = self.admin_site.get_action(action)except KeyError:return Noneif hasattr(func, 'short_description'):description = func.short_descriptionelse:description = capfirst(action.replace('_', ' '))return func, action, descriptiondef get_list_display(self, request):"""Return a sequence containing the fields to be displayed on thechangelist."""return self.list_displaydef get_list_display_links(self, request, list_display):"""Return a sequence containing the fields to be displayed as linkson the changelist. The list_display parameter is the list of fieldsreturned by get_list_display()."""if self.list_display_links or self.list_display_links is None or not list_display:return self.list_display_linkselse:# Use only the first item in list_display as linkreturn list(list_display)[:1]def get_list_filter(self, request):"""Return a sequence containing the fields to be displayed as filters inthe right sidebar of the changelist page."""return self.list_filterdef get_list_select_related(self, request):"""Return a list of fields to add to the select_related() part of thechangelist items query."""return self.list_select_relateddef get_search_fields(self, request):"""Return a sequence containing the fields to be searched wheneversomebody submits a search query."""return self.search_fieldsdef get_search_results(self, request, queryset, search_term):"""Return a tuple containing a queryset to implement the searchand a boolean indicating if the results may contain duplicates."""# Apply keyword searches.def construct_search(field_name):if field_name.startswith('^'):return "%s__istartswith" % field_name[1:]elif field_name.startswith('='):return "%s__iexact" % field_name[1:]elif field_name.startswith('@'):return "%s__search" % field_name[1:]# Use field_name if it includes a lookup.opts = queryset.model._metalookup_fields = field_name.split(LOOKUP_SEP)# Go through the fields, following all relations.prev_field = Nonefor path_part in lookup_fields:if path_part == 'pk':path_part = opts.pk.nametry:field = opts.get_field(path_part)except FieldDoesNotExist:# Use valid query lookups.if prev_field and prev_field.get_lookup(path_part):return field_nameelse:prev_field = fieldif hasattr(field, 'get_path_info'):# Update opts to follow the relation.opts = field.get_path_info()[-1].to_opts# Otherwise, use the field with icontains.return "%s__icontains" % field_nameuse_distinct = Falsesearch_fields = self.get_search_fields(request)if search_fields and search_term:orm_lookups = [construct_search(str(search_field))for search_field in search_fields]for bit in search_term.split():or_queries = [models.Q(**{orm_lookup: bit})for orm_lookup in orm_lookups]queryset = queryset.filter(reduce(operator.or_, or_queries))use_distinct |= any(lookup_needs_distinct(self.opts, search_spec) for search_spec in orm_lookups)return queryset, use_distinctdef get_preserved_filters(self, request):"""Return the preserved filters querystring."""match = request.resolver_matchif self.preserve_filters and match:opts = self.model._metacurrent_url = '%s:%s' % (match.app_name, match.url_name)changelist_url = 'admin:%s_%s_changelist' % (opts.app_label, opts.model_name)if current_url == changelist_url:preserved_filters = request.GET.urlencode()else:preserved_filters = request.GET.get('_changelist_filters')if preserved_filters:return urlencode({'_changelist_filters': preserved_filters})return ''def construct_change_message(self, request, form, formsets, add=False):"""Construct a JSON structure describing changes from a changed object."""return construct_change_message(form, formsets, add)def message_user(self, request, message, level=messages.INFO, extra_tags='',fail_silently=False):"""Send a message to the user. The default implementationposts a message using the django.contrib.messages backend.Exposes almost the same API as messages.add_message(), but accepts thepositional arguments in a different order to maintain backwardscompatibility. For convenience, it accepts the `level` argument asa string rather than the usual level number."""if not isinstance(level, int):# attempt to get the level if passed a stringtry:level = getattr(messages.constants, level.upper())except AttributeError:levels = messages.constants.DEFAULT_TAGS.values()levels_repr = ', '.join('`%s`' % level for level in levels)raise ValueError('Bad message level string: `%s`. Possible values are: %s'% (level, levels_repr))messages.add_message(request, level, message, extra_tags=extra_tags, fail_silently=fail_silently)def save_form(self, request, form, change):"""Given a ModelForm return an unsaved instance. ``change`` is True ifthe object is being changed, and False if it's being added."""return form.save(commit=False)def save_model(self, request, obj, form, change):"""Given a model instance save it to the database."""obj.save()def delete_model(self, request, obj):"""Given a model instance delete it from the database."""obj.delete()def delete_queryset(self, request, queryset):"""Given a queryset, delete it from the database."""queryset.delete()def save_formset(self, request, form, formset, change):"""Given an inline formset save it to the database."""formset.save()def save_related(self, request, form, formsets, change):"""Given the ``HttpRequest``, the parent ``ModelForm`` instance, thelist of inline formsets and a boolean value based on whether theparent is being added or changed, save the related objects to thedatabase. Note that at this point save_form() and save_model() havealready been called."""form.save_m2m()for formset in formsets:self.save_formset(request, form, formset, change=change)def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):opts = self.model._metaapp_label = opts.app_labelpreserved_filters = self.get_preserved_filters(request)form_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, form_url)view_on_site_url = self.get_view_on_site_url(obj)has_editable_inline_admin_formsets = Falsefor inline in context['inline_admin_formsets']:if inline.has_add_permission or inline.has_change_permission or inline.has_delete_permission:has_editable_inline_admin_formsets = Truebreakcontext.update({'add': add,'change': change,'has_view_permission': self.has_view_permission(request, obj),'has_add_permission': self.has_add_permission(request),'has_change_permission': self.has_change_permission(request, obj),'has_delete_permission': self.has_delete_permission(request, obj),'has_editable_inline_admin_formsets': has_editable_inline_admin_formsets,'has_file_field': context['adminform'].form.is_multipart() or any(admin_formset.formset.is_multipart()for admin_formset in context['inline_admin_formsets']),'has_absolute_url': view_on_site_url is not None,'absolute_url': view_on_site_url,'form_url': form_url,'opts': opts,'content_type_id': get_content_type_for_model(self.model).pk,'save_as': self.save_as,'save_on_top': self.save_on_top,'to_field_var': TO_FIELD_VAR,'is_popup_var': IS_POPUP_VAR,'app_label': app_label,})if add and self.add_form_template is not None:form_template = self.add_form_templateelse:form_template = self.change_form_templaterequest.current_app = self.admin_site.namereturn TemplateResponse(request, form_template or ["admin/%s/%s/change_form.html" % (app_label, opts.model_name),"admin/%s/change_form.html" % app_label,"admin/change_form.html"], context)def response_add(self, request, obj, post_url_continue=None):"""Determine the HttpResponse for the add_view stage."""opts = obj._metapreserved_filters = self.get_preserved_filters(request)obj_url = reverse('admin:%s_%s_change' % (opts.app_label, opts.model_name),args=(quote(obj.pk),),current_app=self.admin_site.name,)# Add a link to the object's change form if the user can edit the obj.if self.has_change_permission(request, obj):obj_repr = format_html('<a href="{}">{}</a>', urlquote(obj_url), obj)else:obj_repr = str(obj)msg_dict = {'name': opts.verbose_name,'obj': obj_repr,}# Here, we distinguish between different save types by checking for# the presence of keys in request.POST.if IS_POPUP_VAR in request.POST:to_field = request.POST.get(TO_FIELD_VAR)if to_field:attr = str(to_field)else:attr = obj._meta.pk.attnamevalue = obj.serializable_value(attr)popup_response_data = json.dumps({'value': str(value),'obj': str(obj),})return TemplateResponse(request, self.popup_response_template or ['admin/%s/%s/popup_response.html' % (opts.app_label, opts.model_name),'admin/%s/popup_response.html' % opts.app_label,'admin/popup_response.html',], {'popup_response_data': popup_response_data,})elif "_continue" in request.POST or (# Redirecting after "Save as new"."_saveasnew" in request.POST and self.save_as_continue andself.has_change_permission(request, obj)):msg = _('The {name} “{obj}” was added successfully.')if self.has_change_permission(request, obj):msg += ' ' + _('You may edit it again below.')self.message_user(request, format_html(msg, **msg_dict), messages.SUCCESS)if post_url_continue is None:post_url_continue = obj_urlpost_url_continue = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts},post_url_continue)return HttpResponseRedirect(post_url_continue)elif "_addanother" in request.POST:msg = format_html(_('The {name} “{obj}” was added successfully. You may add another {name} below.'),**msg_dict)self.message_user(request, msg, messages.SUCCESS)redirect_url = request.pathredirect_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, redirect_url)return HttpResponseRedirect(redirect_url)else:msg = format_html(_('The {name} “{obj}” was added successfully.'),**msg_dict)self.message_user(request, msg, messages.SUCCESS)return self.response_post_save_add(request, obj)def response_change(self, request, obj):"""Determine the HttpResponse for the change_view stage."""if IS_POPUP_VAR in request.POST:opts = obj._metato_field = request.POST.get(TO_FIELD_VAR)attr = str(to_field) if to_field else opts.pk.attnamevalue = request.resolver_match.kwargs['object_id']new_value = obj.serializable_value(attr)popup_response_data = json.dumps({'action': 'change','value': str(value),'obj': str(obj),'new_value': str(new_value),})return TemplateResponse(request, self.popup_response_template or ['admin/%s/%s/popup_response.html' % (opts.app_label, opts.model_name),'admin/%s/popup_response.html' % opts.app_label,'admin/popup_response.html',], {'popup_response_data': popup_response_data,})opts = self.model._metapreserved_filters = self.get_preserved_filters(request)msg_dict = {'name': opts.verbose_name,'obj': format_html('<a href="{}">{}</a>', urlquote(request.path), obj),}if "_continue" in request.POST:msg = format_html(_('The {name} “{obj}” was changed successfully. You may edit it again below.'),**msg_dict)self.message_user(request, msg, messages.SUCCESS)redirect_url = request.pathredirect_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, redirect_url)return HttpResponseRedirect(redirect_url)elif "_saveasnew" in request.POST:msg = format_html(_('The {name} “{obj}” was added successfully. You may edit it again below.'),**msg_dict)self.message_user(request, msg, messages.SUCCESS)redirect_url = reverse('admin:%s_%s_change' %(opts.app_label, opts.model_name),args=(obj.pk,),current_app=self.admin_site.name)redirect_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, redirect_url)return HttpResponseRedirect(redirect_url)elif "_addanother" in request.POST:msg = format_html(_('The {name} “{obj}” was changed successfully. You may add another {name} below.'),**msg_dict)self.message_user(request, msg, messages.SUCCESS)redirect_url = reverse('admin:%s_%s_add' %(opts.app_label, opts.model_name),current_app=self.admin_site.name)redirect_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, redirect_url)return HttpResponseRedirect(redirect_url)else:msg = format_html(_('The {name} “{obj}” was changed successfully.'),**msg_dict)self.message_user(request, msg, messages.SUCCESS)return self.response_post_save_change(request, obj)def _response_post_save(self, request, obj):opts = self.model._metaif self.has_view_or_change_permission(request):post_url = reverse('admin:%s_%s_changelist' %(opts.app_label, opts.model_name),current_app=self.admin_site.name)preserved_filters = self.get_preserved_filters(request)post_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, post_url)else:post_url = reverse('admin:index',current_app=self.admin_site.name)return HttpResponseRedirect(post_url)def response_post_save_add(self, request, obj):"""Figure out where to redirect after the 'Save' button has been pressedwhen adding a new object."""return self._response_post_save(request, obj)def response_post_save_change(self, request, obj):"""Figure out where to redirect after the 'Save' button has been pressedwhen editing an existing object."""return self._response_post_save(request, obj)def response_action(self, request, queryset):"""Handle an admin action. This is called if a request is POSTed to thechangelist; it returns an HttpResponse if the action was handled, andNone otherwise."""# There can be multiple action forms on the page (at the top# and bottom of the change list, for example). Get the action# whose button was pushed.try:action_index = int(request.POST.get('index', 0))except ValueError:action_index = 0# Construct the action form.data = request.POST.copy()data.pop(helpers.ACTION_CHECKBOX_NAME, None)data.pop("index", None)# Use the action whose button was pushedtry:data.update({'action': data.getlist('action')[action_index]})except IndexError:# If we didn't get an action from the chosen form that's invalid# POST data, so by deleting action it'll fail the validation check# below. So no need to do anything herepassaction_form = self.action_form(data, auto_id=None)action_form.fields['action'].choices = self.get_action_choices(request)# If the form's valid we can handle the action.if action_form.is_valid():action = action_form.cleaned_data['action']select_across = action_form.cleaned_data['select_across']func = self.get_actions(request)[action][0]# Get the list of selected PKs. If nothing's selected, we can't# perform an action on it, so bail. Except we want to perform# the action explicitly on all objects.selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME)if not selected and not select_across:# Reminder that something needs to be selected or nothing will happenmsg = _("Items must be selected in order to perform ""actions on them. No items have been changed.")self.message_user(request, msg, messages.WARNING)return Noneif not select_across:# Perform the action only on the selected objectsqueryset = queryset.filter(pk__in=selected)response = func(self, request, queryset)# Actions may return an HttpResponse-like object, which will be# used as the response from the POST. If not, we'll be a good# little HTTP citizen and redirect back to the changelist page.if isinstance(response, HttpResponseBase):return responseelse:return HttpResponseRedirect(request.get_full_path())else:msg = _("No action selected.")self.message_user(request, msg, messages.WARNING)return Nonedef response_delete(self, request, obj_display, obj_id):"""Determine the HttpResponse for the delete_view stage."""opts = self.model._metaif IS_POPUP_VAR in request.POST:popup_response_data = json.dumps({'action': 'delete','value': str(obj_id),})return TemplateResponse(request, self.popup_response_template or ['admin/%s/%s/popup_response.html' % (opts.app_label, opts.model_name),'admin/%s/popup_response.html' % opts.app_label,'admin/popup_response.html',], {'popup_response_data': popup_response_data,})self.message_user(request,_('The %(name)s “%(obj)s” was deleted successfully.') % {'name': opts.verbose_name,'obj': obj_display,},messages.SUCCESS,)if self.has_change_permission(request, None):post_url = reverse('admin:%s_%s_changelist' % (opts.app_label, opts.model_name),current_app=self.admin_site.name,)preserved_filters = self.get_preserved_filters(request)post_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, post_url)else:post_url = reverse('admin:index', current_app=self.admin_site.name)return HttpResponseRedirect(post_url)def render_delete_form(self, request, context):opts = self.model._metaapp_label = opts.app_labelrequest.current_app = self.admin_site.namecontext.update(to_field_var=TO_FIELD_VAR,is_popup_var=IS_POPUP_VAR,media=self.media,)return TemplateResponse(request,self.delete_confirmation_template or ["admin/{}/{}/delete_confirmation.html".format(app_label, opts.model_name),"admin/{}/delete_confirmation.html".format(app_label),"admin/delete_confirmation.html",],context,)def get_inline_formsets(self, request, formsets, inline_instances, obj=None):# Edit permissions on parent model are required for editable inlines.can_edit_parent = self.has_change_permission(request, obj) if obj else self.has_add_permission(request)inline_admin_formsets = []for inline, formset in zip(inline_instances, formsets):fieldsets = list(inline.get_fieldsets(request, obj))readonly = list(inline.get_readonly_fields(request, obj))if can_edit_parent:has_add_permission = inline.has_add_permission(request, obj)has_change_permission = inline.has_change_permission(request, obj)has_delete_permission = inline.has_delete_permission(request, obj)else:# Disable all edit-permissions, and overide formset settings.has_add_permission = has_change_permission = has_delete_permission = Falseformset.extra = formset.max_num = 0has_view_permission = inline.has_view_permission(request, obj)prepopulated = dict(inline.get_prepopulated_fields(request, obj))inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, fieldsets, prepopulated, readonly, model_admin=self,has_add_permission=has_add_permission, has_change_permission=has_change_permission,has_delete_permission=has_delete_permission, has_view_permission=has_view_permission,)inline_admin_formsets.append(inline_admin_formset)return inline_admin_formsetsdef get_changeform_initial_data(self, request):"""Get the initial form data from the request's GET params."""initial = dict(request.GET.items())for k in initial:try:f = self.model._meta.get_field(k)except FieldDoesNotExist:continue# We have to special-case M2Ms as a list of comma-separated PKs.if isinstance(f, models.ManyToManyField):initial[k] = initial[k].split(",")return initialdef _get_obj_does_not_exist_redirect(self, request, opts, object_id):"""Create a message informing the user that the object doesn't existand return a redirect to the admin index page."""msg = _('%(name)s with ID “%(key)s” doesn’t exist. Perhaps it was deleted?') % {'name': opts.verbose_name,'key': unquote(object_id),}self.message_user(request, msg, messages.WARNING)url = reverse('admin:index', current_app=self.admin_site.name)return HttpResponseRedirect(url)@csrf_protect_mdef changeform_view(self, request, object_id=None, form_url='', extra_context=None):with transaction.atomic(using=router.db_for_write(self.model)):return self._changeform_view(request, object_id, form_url, extra_context)def _changeform_view(self, request, object_id, form_url, extra_context):to_field = request.POST.get(TO_FIELD_VAR, request.GET.get(TO_FIELD_VAR))if to_field and not self.to_field_allowed(request, to_field):raise DisallowedModelAdminToField("The field %s cannot be referenced." % to_field)model = self.modelopts = model._metaif request.method == 'POST' and '_saveasnew' in request.POST:object_id = Noneadd = object_id is Noneif add:if not self.has_add_permission(request):raise PermissionDeniedobj = Noneelse:obj = self.get_object(request, unquote(object_id), to_field)if request.method == 'POST':if not self.has_change_permission(request, obj):raise PermissionDeniedelse:if not self.has_view_or_change_permission(request, obj):raise PermissionDeniedif obj is None:return self._get_obj_does_not_exist_redirect(request, opts, object_id)fieldsets = self.get_fieldsets(request, obj)ModelForm = self.get_form(request, obj, change=not add, fields=flatten_fieldsets(fieldsets))if request.method == 'POST':form = ModelForm(request.POST, request.FILES, instance=obj)form_validated = form.is_valid()if form_validated:new_object = self.save_form(request, form, change=not add)else:new_object = form.instanceformsets, inline_instances = self._create_formsets(request, new_object, change=not add)if all_valid(formsets) and form_validated:self.save_model(request, new_object, form, not add)self.save_related(request, form, formsets, not add)change_message = self.construct_change_message(request, form, formsets, add)if add:self.log_addition(request, new_object, change_message)return self.response_add(request, new_object)else:self.log_change(request, new_object, change_message)return self.response_change(request, new_object)else:form_validated = Falseelse:if add:initial = self.get_changeform_initial_data(request)form = ModelForm(initial=initial)formsets, inline_instances = self._create_formsets(request, form.instance, change=False)else:form = ModelForm(instance=obj)formsets, inline_instances = self._create_formsets(request, obj, change=True)if not add and not self.has_change_permission(request, obj):readonly_fields = flatten_fieldsets(fieldsets)else:readonly_fields = self.get_readonly_fields(request, obj)adminForm = helpers.AdminForm(form,list(fieldsets),# Clear prepopulated fields on a view-only form to avoid a crash.self.get_prepopulated_fields(request, obj) if add or self.has_change_permission(request, obj) else {},readonly_fields,model_admin=self)media = self.media + adminForm.mediainline_formsets = self.get_inline_formsets(request, formsets, inline_instances, obj)for inline_formset in inline_formsets:media = media + inline_formset.mediaif add:title = _('Add %s')elif self.has_change_permission(request, obj):title = _('Change %s')else:title = _('View %s')context = {**self.admin_site.each_context(request),'title': title % opts.verbose_name,'adminform': adminForm,'object_id': object_id,'original': obj,'is_popup': IS_POPUP_VAR in request.POST or IS_POPUP_VAR in request.GET,'to_field': to_field,'media': media,'inline_admin_formsets': inline_formsets,'errors': helpers.AdminErrorList(form, formsets),'preserved_filters': self.get_preserved_filters(request),}# Hide the "Save" and "Save and continue" buttons if "Save as New" was# previously chosen to prevent the interface from getting confusing.if request.method == 'POST' and not form_validated and "_saveasnew" in request.POST:context['show_save'] = Falsecontext['show_save_and_continue'] = False# Use the change template instead of the add template.add = Falsecontext.update(extra_context or {})return self.render_change_form(request, context, add=add, change=not add, obj=obj, form_url=form_url)def autocomplete_view(self, request):return AutocompleteJsonView.as_view(model_admin=self)(request)def add_view(self, request, form_url='', extra_context=None):return self.changeform_view(request, None, form_url, extra_context)def change_view(self, request, object_id, form_url='', extra_context=None):return self.changeform_view(request, object_id, form_url, extra_context)def _get_edited_object_pks(self, request, prefix):"""Return POST data values of list_editable primary keys."""pk_pattern = re.compile(r'{}-\d+-{}$'.format(re.escape(prefix), self.model._meta.pk.name))return [value for key, value in request.POST.items() if pk_pattern.match(key)]def _get_list_editable_queryset(self, request, prefix):"""Based on POST data, return a queryset of the objects that were editedvia list_editable."""object_pks = self._get_edited_object_pks(request, prefix)queryset = self.get_queryset(request)validate = queryset.model._meta.pk.to_pythontry:for pk in object_pks:validate(pk)except ValidationError:# Disable the optimization if the POST data was tampered with.return querysetreturn queryset.filter(pk__in=object_pks)@csrf_protect_mdef changelist_view(self, request, extra_context=None):"""The 'change list' admin view for this model."""from django.contrib.admin.views.main import ERROR_FLAGopts = self.model._metaapp_label = opts.app_labelif not self.has_view_or_change_permission(request):raise PermissionDeniedtry:cl = self.get_changelist_instance(request)except IncorrectLookupParameters:# Wacky lookup parameters were given, so redirect to the main# changelist page, without parameters, and pass an 'invalid=1'# parameter via the query string. If wacky parameters were given# and the 'invalid=1' parameter was already in the query string,# something is screwed up with the database, so display an error# page.if ERROR_FLAG in request.GET:return SimpleTemplateResponse('admin/invalid_setup.html', {'title': _('Database error'),})return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')# If the request was POSTed, this might be a bulk action or a bulk# edit. Try to look up an action or confirmation first, but if this# isn't an action the POST will fall through to the bulk edit check,# below.action_failed = Falseselected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME)actions = self.get_actions(request)# Actions with no confirmationif (actions and request.method == 'POST' and'index' in request.POST and '_save' not in request.POST):if selected:response = self.response_action(request, queryset=cl.get_queryset(request))if response:return responseelse:action_failed = Trueelse:msg = _("Items must be selected in order to perform ""actions on them. No items have been changed.")self.message_user(request, msg, messages.WARNING)action_failed = True# Actions with confirmationif (actions and request.method == 'POST' andhelpers.ACTION_CHECKBOX_NAME in request.POST and'index' not in request.POST and '_save' not in request.POST):if selected:response = self.response_action(request, queryset=cl.get_queryset(request))if response:return responseelse:action_failed = Trueif action_failed:# Redirect back to the changelist page to avoid resubmitting the# form if the user refreshes the browser or uses the "No, take# me back" button on the action confirmation page.return HttpResponseRedirect(request.get_full_path())# If we're allowing changelist editing, we need to construct a formset# for the changelist given all the fields to be edited. Then we'll# use the formset to validate/process POSTed data.formset = cl.formset = None# Handle POSTed bulk-edit data.if request.method == 'POST' and cl.list_editable and '_save' in request.POST:if not self.has_change_permission(request):raise PermissionDeniedFormSet = self.get_changelist_formset(request)modified_objects = self._get_list_editable_queryset(request, FormSet.get_default_prefix())formset = cl.formset = FormSet(request.POST, request.FILES, queryset=modified_objects)if formset.is_valid():changecount = 0for form in formset.forms:if form.has_changed():obj = self.save_form(request, form, change=True)self.save_model(request, obj, form, change=True)self.save_related(request, form, formsets=[], change=True)change_msg = self.construct_change_message(request, form, None)self.log_change(request, obj, change_msg)changecount += 1if changecount:msg = ngettext("%(count)s %(name)s was changed successfully.","%(count)s %(name)s were changed successfully.",changecount) % {'count': changecount,'name': model_ngettext(opts, changecount),}self.message_user(request, msg, messages.SUCCESS)return HttpResponseRedirect(request.get_full_path())# Handle GET -- construct a formset for display.elif cl.list_editable and self.has_change_permission(request):FormSet = self.get_changelist_formset(request)formset = cl.formset = FormSet(queryset=cl.result_list)# Build the list of media to be used by the formset.if formset:media = self.media + formset.mediaelse:media = self.media# Build the action form and populate it with available actions.if actions:action_form = self.action_form(auto_id=None)action_form.fields['action'].choices = self.get_action_choices(request)media += action_form.mediaelse:action_form = Noneselection_note_all = ngettext('%(total_count)s selected','All %(total_count)s selected',cl.result_count)context = {**self.admin_site.each_context(request),'module_name': str(opts.verbose_name_plural),'selection_note': _('0 of %(cnt)s selected') % {'cnt': len(cl.result_list)},'selection_note_all': selection_note_all % {'total_count': cl.result_count},'title': cl.title,'is_popup': cl.is_popup,'to_field': cl.to_field,'cl': cl,'media': media,'has_add_permission': self.has_add_permission(request),'opts': cl.opts,'action_form': action_form,'actions_on_top': self.actions_on_top,'actions_on_bottom': self.actions_on_bottom,'actions_selection_counter': self.actions_selection_counter,'preserved_filters': self.get_preserved_filters(request),**(extra_context or {}),}request.current_app = self.admin_site.namereturn TemplateResponse(request, self.change_list_template or ['admin/%s/%s/change_list.html' % (app_label, opts.model_name),'admin/%s/change_list.html' % app_label,'admin/change_list.html'], context)def get_deleted_objects(self, objs, request):"""Hook for customizing the delete process for the delete view and the"delete selected" action."""return get_deleted_objects(objs, request, self.admin_site)@csrf_protect_mdef delete_view(self, request, object_id, extra_context=None):with transaction.atomic(using=router.db_for_write(self.model)):return self._delete_view(request, object_id, extra_context)def _delete_view(self, request, object_id, extra_context):"The 'delete' admin view for this model."opts = self.model._metaapp_label = opts.app_labelto_field = request.POST.get(TO_FIELD_VAR, request.GET.get(TO_FIELD_VAR))if to_field and not self.to_field_allowed(request, to_field):raise DisallowedModelAdminToField("The field %s cannot be referenced." % to_field)obj = self.get_object(request, unquote(object_id), to_field)if not self.has_delete_permission(request, obj):raise PermissionDeniedif obj is None:return self._get_obj_does_not_exist_redirect(request, opts, object_id)# Populate deleted_objects, a data structure of all related objects that# will also be deleted.deleted_objects, model_count, perms_needed, protected = self.get_deleted_objects([obj], request)if request.POST and not protected:  # The user has confirmed the deletion.if perms_needed:raise PermissionDeniedobj_display = str(obj)attr = str(to_field) if to_field else opts.pk.attnameobj_id = obj.serializable_value(attr)self.log_deletion(request, obj, obj_display)self.delete_model(request, obj)return self.response_delete(request, obj_display, obj_id)object_name = str(opts.verbose_name)if perms_needed or protected:title = _("Cannot delete %(name)s") % {"name": object_name}else:title = _("Are you sure?")context = {**self.admin_site.each_context(request),'title': title,'object_name': object_name,'object': obj,'deleted_objects': deleted_objects,'model_count': dict(model_count).items(),'perms_lacking': perms_needed,'protected': protected,'opts': opts,'app_label': app_label,'preserved_filters': self.get_preserved_filters(request),'is_popup': IS_POPUP_VAR in request.POST or IS_POPUP_VAR in request.GET,'to_field': to_field,**(extra_context or {}),}return self.render_delete_form(request, context)def history_view(self, request, object_id, extra_context=None):"The 'history' admin view for this model."from django.contrib.admin.models import LogEntry# First check if the user can see this history.model = self.modelobj = self.get_object(request, unquote(object_id))if obj is None:return self._get_obj_does_not_exist_redirect(request, model._meta, object_id)if not self.has_view_or_change_permission(request, obj):raise PermissionDenied# Then get the history for this object.opts = model._metaapp_label = opts.app_labelaction_list = LogEntry.objects.filter(object_id=unquote(object_id),content_type=get_content_type_for_model(model)).select_related().order_by('action_time')context = {**self.admin_site.each_context(request),'title': _('Change history: %s') % obj,'action_list': action_list,'module_name': str(capfirst(opts.verbose_name_plural)),'object': obj,'opts': opts,'preserved_filters': self.get_preserved_filters(request),**(extra_context or {}),}request.current_app = self.admin_site.namereturn TemplateResponse(request, self.object_history_template or ["admin/%s/%s/object_history.html" % (app_label, opts.model_name),"admin/%s/object_history.html" % app_label,"admin/object_history.html"], context)def _create_formsets(self, request, obj, change):"Helper function to generate formsets for add/change_view."formsets = []inline_instances = []prefixes = {}get_formsets_args = [request]if change:get_formsets_args.append(obj)for FormSet, inline in self.get_formsets_with_inlines(*get_formsets_args):prefix = FormSet.get_default_prefix()prefixes[prefix] = prefixes.get(prefix, 0) + 1if prefixes[prefix] != 1 or not prefix:prefix = "%s-%s" % (prefix, prefixes[prefix])formset_params = {'instance': obj,'prefix': prefix,'queryset': inline.get_queryset(request),}if request.method == 'POST':formset_params.update({'data': request.POST.copy(),'files': request.FILES,'save_as_new': '_saveasnew' in request.POST})formset = FormSet(**formset_params)def user_deleted_form(request, obj, formset, index):"""Return whether or not the user deleted the form."""return (inline.has_delete_permission(request, obj) and'{}-{}-DELETE'.format(formset.prefix, index) in request.POST)# Bypass validation of each view-only inline form (since the form's# data won't be in request.POST), unless the form was deleted.if not inline.has_change_permission(request, obj if change else None):for index, form in enumerate(formset.initial_forms):if user_deleted_form(request, obj, formset, index):continueform._errors = {}form.cleaned_data = form.initialformsets.append(formset)inline_instances.append(inline)return formsets, inline_instances

InlineModelAdmin


class InlineModelAdmin(BaseModelAdmin):"""Options for inline editing of ``model`` instances.Provide ``fk_name`` to specify the attribute name of the ``ForeignKey``from ``model`` to its parent. This is required if ``model`` has more thanone ``ForeignKey`` to its parent."""model = Nonefk_name = Noneformset = BaseInlineFormSetextra = 3min_num = Nonemax_num = Nonetemplate = Noneverbose_name = Noneverbose_name_plural = Nonecan_delete = Trueshow_change_link = Falsechecks_class = InlineModelAdminChecksclasses = Nonedef __init__(self, parent_model, admin_site):self.admin_site = admin_siteself.parent_model = parent_modelself.opts = self.model._metaself.has_registered_model = admin_site.is_registered(self.model)super().__init__()if self.verbose_name is None:self.verbose_name = self.model._meta.verbose_nameif self.verbose_name_plural is None:self.verbose_name_plural = self.model._meta.verbose_name_plural@propertydef media(self):extra = '' if settings.DEBUG else '.min'js = ['vendor/jquery/jquery%s.js' % extra, 'jquery.init.js','inlines%s.js' % extra]if self.filter_vertical or self.filter_horizontal:js.extend(['SelectBox.js', 'SelectFilter2.js'])if self.classes and 'collapse' in self.classes:js.append('collapse%s.js' % extra)return forms.Media(js=['admin/js/%s' % url for url in js])def get_extra(self, request, obj=None, **kwargs):"""Hook for customizing the number of extra inline forms."""return self.extradef get_min_num(self, request, obj=None, **kwargs):"""Hook for customizing the min number of inline forms."""return self.min_numdef get_max_num(self, request, obj=None, **kwargs):"""Hook for customizing the max number of extra inline forms."""return self.max_numdef get_formset(self, request, obj=None, **kwargs):"""Return a BaseInlineFormSet class for use in admin add/change views."""if 'fields' in kwargs:fields = kwargs.pop('fields')else:fields = flatten_fieldsets(self.get_fieldsets(request, obj))excluded = self.get_exclude(request, obj)exclude = [] if excluded is None else list(excluded)exclude.extend(self.get_readonly_fields(request, obj))if excluded is None and hasattr(self.form, '_meta') and self.form._meta.exclude:# Take the custom ModelForm's Meta.exclude into account only if the# InlineModelAdmin doesn't define its own.exclude.extend(self.form._meta.exclude)# If exclude is an empty list we use None, since that's the actual# default.exclude = exclude or Nonecan_delete = self.can_delete and self.has_delete_permission(request, obj)defaults = {'form': self.form,'formset': self.formset,'fk_name': self.fk_name,'fields': fields,'exclude': exclude,'formfield_callback': partial(self.formfield_for_dbfield, request=request),'extra': self.get_extra(request, obj, **kwargs),'min_num': self.get_min_num(request, obj, **kwargs),'max_num': self.get_max_num(request, obj, **kwargs),'can_delete': can_delete,**kwargs,}base_model_form = defaults['form']can_change = self.has_change_permission(request, obj) if request else Truecan_add = self.has_add_permission(request, obj) if request else Trueclass DeleteProtectedModelForm(base_model_form):def hand_clean_DELETE(self):"""We don't validate the 'DELETE' field itself because ontemplates it's not rendered using the field information, butjust using a generic "deletion_field" of the InlineModelAdmin."""if self.cleaned_data.get(DELETION_FIELD_NAME, False):using = router.db_for_write(self._meta.model)collector = NestedObjects(using=using)if self.instance._state.adding:returncollector.collect([self.instance])if collector.protected:objs = []for p in collector.protected:objs.append(# Translators: Model verbose name and instance representation,# suitable to be an item in a list._('%(class_name)s %(instance)s') % {'class_name': p._meta.verbose_name,'instance': p})params = {'class_name': self._meta.model._meta.verbose_name,'instance': self.instance,'related_objects': get_text_list(objs, _('and')),}msg = _("Deleting %(class_name)s %(instance)s would require ""deleting the following protected related objects: ""%(related_objects)s")raise ValidationError(msg, code='deleting_protected', params=params)def is_valid(self):result = super().is_valid()self.hand_clean_DELETE()return resultdef has_changed(self):# Protect against unauthorized edits.if not can_change and not self.instance._state.adding:return Falseif not can_add and self.instance._state.adding:return Falsereturn super().has_changed()defaults['form'] = DeleteProtectedModelFormif defaults['fields'] is None and not modelform_defines_fields(defaults['form']):defaults['fields'] = forms.ALL_FIELDSreturn inlineformset_factory(self.parent_model, self.model, **defaults)def _get_form_for_get_fields(self, request, obj=None):return self.get_formset(request, obj, fields=None).formdef get_queryset(self, request):queryset = super().get_queryset(request)if not self.has_view_or_change_permission(request):queryset = queryset.none()return querysetdef _has_any_perms_for_target_model(self, request, perms):"""This method is called only when the ModelAdmin's model is for anManyToManyField's implicit through model (if self.opts.auto_created).Return True if the user has any of the given permissions ('add','change', etc.) for the model that points to the through model."""opts = self.opts# Find the target model of an auto-created many-to-many relationship.for field in opts.fields:if field.remote_field and field.remote_field.model != self.parent_model:opts = field.remote_field.model._metabreakreturn any(request.user.has_perm('%s.%s' % (opts.app_label, get_permission_codename(perm, opts)))for perm in perms)def has_add_permission(self, request, obj):if self.opts.auto_created:# Auto-created intermediate models don't have their own# permissions. The user needs to have the change permission for the# related model in order to be able to do anything with the# intermediate model.return self._has_any_perms_for_target_model(request, ['change'])return super().has_add_permission(request)def has_change_permission(self, request, obj=None):if self.opts.auto_created:# Same comment as has_add_permission().return self._has_any_perms_for_target_model(request, ['change'])return super().has_change_permission(request)def has_delete_permission(self, request, obj=None):if self.opts.auto_created:# Same comment as has_add_permission().return self._has_any_perms_for_target_model(request, ['change'])return super().has_delete_permission(request, obj)def has_view_permission(self, request, obj=None):if self.opts.auto_created:# Same comment as has_add_permission(). The 'change' permission# also implies the 'view' permission.return self._has_any_perms_for_target_model(request, ['view', 'change'])return super().has_view_permission(request)class StackedInline(InlineModelAdmin):template = 'admin/edit_inline/stacked.html'class TabularInline(InlineModelAdmin):template = 'admin/edit_inline/tabular.html'

【Django】admin.ModelAdmin的源码-20220105相关推荐

  1. 基于python-实训基地管理系统-django框架计算机毕业设计源码+系统+数据库+lw文档+调试部署

    基于python-实训基地管理系统-django框架计算机毕业设计源码+系统+数据库+lw文档+调试部署 基于python-实训基地管理系统-django框架计算机毕业设计源码+系统+数据库+lw文档 ...

  2. vue+django 微博舆情系统源码、深度学习+舆情扩散消失分析、舆情紧急等级、属地分析、按话题、情感预测、话题评论获取、提取观点、正面负面舆情、按区域检测舆情

    项目背景 315又马上要到了,现在有开始对食品安全话题的关注地提升了,因此,本文系统对微博的食品安全话题进行分析,有如下的功能 1.展示当前食品安全事件相关的热点信息以及提供根据食品关键词,食品安全类 ...

  3. 基于python食品安全溯源管理系统django框架计算机毕业设计源码+系统+数据库+lw文档+调试部署

    基于python食品安全溯源管理系统django框架计算机毕业设计源码+系统+数据库+lw文档+调试部署 基于python食品安全溯源管理系统django框架计算机毕业设计源码+系统+数据库+lw文档 ...

  4. 基于python摄影作品展示网站django框架计算机毕业设计源码+系统+数据库+lw文档+调试部署

    基于python摄影作品展示网站django框架计算机毕业设计源码+系统+数据库+lw文档+调试部署 基于python摄影作品展示网站django框架计算机毕业设计源码+系统+数据库+lw文档+调试部 ...

  5. Django项目实战(附源码免费下载)

    制作图书管理系统(末尾附源码) 第一步先更改settings.py里面的必要配置,更改或附件项如下 INSTALLED_APPS = ['django.contrib.admin','django.c ...

  6. 基于Django 的商城网站源码(1.0)

    该网站只完成了主体框架,对需要用到的数据结构进行了编写. 用到的环境及第三方应用 前端界面用到的是boostrap3,只做了几个页面简单的规划. 源码主要完成了会员的注册,登录,密码的修改重置.密码重 ...

  7. Python学习笔记:7.5.1 Django快速建站 -源码版本管理

    前言:本文是学习网易微专业的<python全栈工程师 - Django快速建站>课程的笔记,欢迎学习交流.同时感谢老师们的精彩传授! 一.课程目标 了解源码版本管理的意义 掌握初步的git ...

  8. Python django 宠物领养商城-源码下载

    Python django 宠物领养商城 Python  django宠物领养 Python django 衣服商城 Python django 裤子商城 Python django 电子商城 技术: ...

  9. Django Rest框架 APIView源码调用

    上一篇说了请求访问的流程,这一篇说一下请求对应的源码调用 as_view 定义view dispatch dispatch initialize_request get_parsers         ...

最新文章

  1. java uml 为什么_Java开发为什么需要UML
  2. legend2---开发日志13(layer_mobile的content传入dom 出现【object object】如何解决)
  3. python 报错 TypeError: ‘int‘ object is not subscriptable 解决方法
  4. 跟你们讲一个鬼故事,TA回来了!
  5. [BZOJ3000] Big Number (Stirling公式)
  6. 字符编码详解及由来(UNICODE,UTF-8,GBK)
  7. lisp 设计盘形齿轮铣刀_齿轮是怎么来的——图解6种齿轮加工工艺
  8. springboot全局异常处理_SpringMVC全局异常处理
  9. 国二计算机百度云,2016Office二级全套视频教程 全国计算机二级考试Office考试视频教程...
  10. list转json字符串,实体类包含list转实体类
  11. 模型预测控制的缺点_一个模型预测控制(MPC)的简单实现
  12. 小学计算机微课教案及ppt,小学数学微课程ppt
  13. JAVA_OPTS修改配置在哪里_JAVA_OPTS參數說明與配置
  14. 【20点埃及 VS 乌拉圭/23点摩洛哥vs 伊朗】快看人工只能预测结果(附最全赛事时间表)...
  15. 个人官网第8次升级(新功能、用户体验、修复bug、系统优化)
  16. 苹果奖学金获得者:我的自学 iOS 开发历程
  17. 补充学习2021.7.21—动态水波进度条
  18. 常见混沌系统—Chen模型
  19. 【Python魔术方法】py复习
  20. Python编程:sh模块执行shell命令

热门文章

  1. developer 开启双重验证 your appid needs to be updated
  2. 搭配Online:南方航2020年1月正式退出天合联盟
  3. 【无标题】百度地图的基本使用
  4. 医学院交换去计算机学院,2017年北京协和医学院阜外心血管病医院823计算机原理考研强化模拟题...
  5. 微信屏蔽网址解决办法 怎么样才能让被微信屏蔽的网址正常访问
  6. 用好知晓云,从此不再「网抑云」
  7. 微信朋友圈分享、QQ分享
  8. 【前端】javascript实现导航栏筋斗云效果特效
  9. MIUI14+安卓13 Root教程 小米10 小米11 小米12 小米13 红米
  10. Yarn Web页面 8088 端口在Windows浏览器无法访问