Module fl_server_core.admin¶
View Source
# SPDX-FileCopyrightText: 2024 Benedikt Franke <benedikt.franke@dlr.de>
# SPDX-FileCopyrightText: 2024 Florian Heinrich <florian.heinrich@dlr.de>
#
# SPDX-License-Identifier: Apache-2.0
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from polymorphic.admin import PolymorphicParentModelAdmin, PolymorphicChildModelAdmin, PolymorphicChildModelFilter
from .models import (
GlobalModel,
LocalModel,
Metric,
Model,
Training,
User,
)
admin.site.register(User, UserAdmin)
admin.site.register(Training)
admin.site.register(Metric)
# Flexible and customizable registration
# @admin.register(ModelUpdate)
# class ModelUpdateAdmin(admin.ModelAdmin):
# pass
class ModelChildAdmin(PolymorphicChildModelAdmin):
"""
Polymorphic admin model base class.
"""
base_model = Model
@admin.register(GlobalModel)
class GlobalModelAdmin(ModelChildAdmin):
"""
Admin interface for the `GlobalModel`.
"""
base_model = GlobalModel
@admin.register(LocalModel)
class LocalModelAdmin(ModelChildAdmin):
"""
Admin interface for the `LocalModel`.
"""
base_model = LocalModel
@admin.register(Model)
class ModelParentAdmin(PolymorphicParentModelAdmin):
"""
Admin interface for the parent Model class.
This includes support for `GlobalModel` as well as `LocalModel`.
"""
base_model = Model
child_models = (GlobalModel, LocalModel)
list_filter = (PolymorphicChildModelFilter,)
Classes¶
GlobalModelAdmin¶
Admin interface for the GlobalModel
.
View Source
Ancestors (in MRO)¶
- fl_server_core.admin.ModelChildAdmin
- polymorphic.admin.childadmin.PolymorphicChildModelAdmin
- django.contrib.admin.options.ModelAdmin
- django.contrib.admin.options.BaseModelAdmin
Class variables¶
Instance variables¶
Methods¶
action_checkbox¶
A list_display column containing a checkbox widget.
View Source
add_view¶
View Source
change_view¶
View Source
changeform_view¶
View Source
changelist_view¶
The 'change list' admin view for this model.
View Source
@csrf_protect_m
def changelist_view(self, request, extra_context=None):
"""
The 'change list' admin view for this model.
"""
from django.contrib.admin.views.main import ERROR_FLAG
opts = self.model._meta
app_label = opts.app_label
if not self.has_view_or_change_permission(request):
raise PermissionDenied
try:
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 = False
selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME)
actions = self.get_actions(request)
# Actions with no confirmation
if (
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 response
else:
action_failed = True
else:
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 confirmation
if (
actions
and request.method == "POST"
and helpers.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 response
else:
action_failed = True
if 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 PermissionDenied
FormSet = 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 = 0
for 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 += 1
if 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.media
else:
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.media
else:
action_form = None
selection_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,
"subtitle": None,
"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.name
return 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,
)
check¶
construct_change_message¶
Construct a JSON structure describing changes from a changed object.
View Source
delete_model¶
Given a model instance delete it from the database.
View Source
delete_queryset¶
Given a queryset, delete it from the database.
View Source
delete_view¶
View Source
formfield_for_choice_field¶
Get a form Field for a database Field that has declared choices.
View Source
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 RadioSelect
if 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)
formfield_for_dbfield¶
Hook for specifying the form Field instance for a given database Field
instance.
If kwargs are given, they're passed to the form Field's constructor.
View Source
def formfield_for_dbfield(self, db_field, request, **kwargs):
"""
Hook for specifying the form Field instance for a given database Field
instance.
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 ManyToManyFields
if 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)
formfield_for_foreignkey¶
Get a form Field for a ForeignKey.
View Source
def 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, 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 None
if "queryset" not in kwargs:
queryset = self.get_field_queryset(db, db_field, request)
if queryset is not None:
kwargs["queryset"] = queryset
return db_field.formfield(**kwargs)
formfield_for_manytomany¶
Get a form Field for a ManyToManyField.
View Source
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 None
db = 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,
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"] = queryset
form_field = db_field.formfield(**kwargs)
if isinstance(form_field.widget, SelectMultiple) and not 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_text
form_field.help_text = (
format_lazy("{} {}", help_text, msg) if help_text else msg
)
return form_field
get_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).
View Source
def 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 = action
action = 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 site
else:
try:
func = self.admin_site.get_action(action)
except KeyError:
return None
description = self._get_action_description(func, action)
return func, action, description
get_action_choices¶
Return a list of choices for use in a form object. Each choice is a
tuple (name, description).
View Source
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 a
tuple (name, description).
"""
choices = [] + default_choices
for func, name, description in self.get_actions(request).values():
choice = (name, description % model_format_dict(self.opts))
choices.append(choice)
return choices
get_actions¶
Return a dictionary mapping the names of all actions for this
ModelAdmin to a tuple of (callable, name, description) for each action.
View Source
def get_actions(self, request):
"""
Return a dictionary mapping the names of all actions for this
ModelAdmin 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}
get_autocomplete_fields¶
Return a list of ForeignKey and/or ManyToMany fields which should use
an autocomplete widget.
View Source
get_base_fieldsets¶
get_changeform_initial_data¶
Get the initial form data from the request's GET params.
View Source
def 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 initial
get_changelist¶
Return the ChangeList class for use on the changelist page.
View Source
get_changelist_form¶
Return a Form class for use in the Formset on the changelist page.
View Source
def 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_FIELDS
return modelform_factory(self.model, **defaults)
get_changelist_formset¶
Return a FormSet class for use on the changelist page if list_editable
is used.
View Source
def get_changelist_formset(self, request, **kwargs):
"""
Return a FormSet class for use on the changelist page if list_editable
is 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,
)
get_changelist_instance¶
Return a ChangeList
instance based on request
. May raise
IncorrectLookupParameters
.
View Source
def 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,
self.search_help_text,
)
get_deleted_objects¶
Hook for customizing the delete process for the delete view and the
"delete selected" action.
View Source
get_empty_value_display¶
Return the empty_value_display set on ModelAdmin or AdminSite.
View Source
get_exclude¶
Hook for specifying exclude.
View Source
get_field_queryset¶
If the ModelAdmin specifies ordering, the queryset should respect that
ordering. Otherwise don't specify the queryset, let the field decide (return None in that case).
View Source
def get_field_queryset(self, db, db_field, request):
"""
If the ModelAdmin specifies ordering, the queryset should respect that
ordering. 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 None
get_fields¶
Hook for specifying fields.
View Source
get_fieldsets¶
Hook for specifying fieldsets.
View Source
def get_fieldsets(self, request, obj=None):
base_fieldsets = self.get_base_fieldsets(request, obj)
# If subclass declares fieldsets or fields, this is respected
if self.fieldsets or self.fields or not self.base_fieldsets:
return super().get_fieldsets(request, obj)
# Have a reasonable default fieldsets,
# where the subclass fields are automatically included.
other_fields = self.get_subclass_fields(request, obj)
if other_fields:
return (
base_fieldsets[0],
(self.extra_fieldset_title, {"fields": other_fields}),
) + base_fieldsets[1:]
else:
return base_fieldsets
get_form¶
Return a Form class for use in the admin add view. This is used by
add_view and change_view.
View Source
def get_form(self, request, obj=None, **kwargs):
# The django admin validation requires the form to have a 'class Meta: model = ..'
# attribute, or it will complain that the fields are missing.
# However, this enforces all derived ModelAdmin classes to redefine the model as well,
# because they need to explicitly set the model again - it will stick with the base model.
#
# Instead, pass the form unchecked here, because the standard ModelForm will just work.
# If the derived class sets the model explicitly, respect that setting.
kwargs.setdefault("form", self.base_form or self.form)
# prevent infinite recursion when this is called from get_subclass_fields
if not self.fieldsets and not self.fields:
kwargs.setdefault("fields", "__all__")
return super().get_form(request, obj, **kwargs)
get_formset_kwargs¶
View Source
def get_formset_kwargs(self, request, obj, inline, 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,
}
)
return formset_params
get_formsets_with_inlines¶
Yield formsets and the corresponding inlines.
View Source
get_inline_formsets¶
View Source
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 = False
formset.extra = formset.max_num = 0
has_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_formsets
get_inline_instances¶
View Source
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)
or inline.has_add_permission(request, obj)
or inline.has_delete_permission(request, obj)
):
continue
if not inline.has_add_permission(request, obj):
inline.max_num = 0
inline_instances.append(inline)
return inline_instances
get_inlines¶
Hook for specifying custom inlines.
View Source
get_list_display¶
Return a sequence containing the fields to be displayed on the
changelist.
View Source
get_list_display_links¶
Return a sequence containing the fields to be displayed as links
on the changelist. The list_display parameter is the list of fields returned by get_list_display().
View Source
def get_list_display_links(self, request, list_display):
"""
Return a sequence containing the fields to be displayed as links
on the changelist. The list_display parameter is the list of fields
returned by get_list_display().
"""
if (
self.list_display_links
or self.list_display_links is None
or not list_display
):
return self.list_display_links
else:
# Use only the first item in list_display as link
return list(list_display)[:1]
get_list_filter¶
Return a sequence containing the fields to be displayed as filters in
the right sidebar of the changelist page.
View Source
get_list_select_related¶
Return a list of fields to add to the select_related() part of the
changelist items query.
View Source
get_model_perms¶
Return a dict of all perms for this model. This dict has the keys
add
, change
, delete
, and view
mapping to the True/False
for each of those actions.
View Source
get_object¶
Return an instance matching the field and value provided, the primary
key is used if no field is provided. Return None
if no match is
found or the object_id fails validation.
View Source
def get_object(self, request, object_id, from_field=None):
"""
Return an instance matching the field and value provided, the primary
key is used if no field is provided. Return ``None`` if no match is
found or the object_id fails validation.
"""
queryset = self.get_queryset(request)
model = queryset.model
field = (
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 None
get_ordering¶
Hook for specifying field ordering.
View Source
get_paginator¶
View Source
get_prepopulated_fields¶
Hook for specifying custom prepopulated fields.
View Source
get_preserved_filters¶
Return the preserved filters querystring.
View Source
def get_preserved_filters(self, request):
"""
Return the preserved filters querystring.
"""
match = request.resolver_match
if self.preserve_filters and match:
opts = self.model._meta
current_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 ""
get_queryset¶
Return a QuerySet of all model instances that can be edited by the
admin site. This is used by changelist_view.
View Source
def get_queryset(self, request):
"""
Return a QuerySet of all model instances that can be edited by the
admin 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 qs
get_readonly_fields¶
Hook for specifying custom readonly fields.
View Source
get_search_fields¶
Return a sequence containing the fields to be searched whenever
somebody submits a search query.
View Source
get_search_results¶
Return a tuple containing a queryset to implement the search
and a boolean indicating if the results may contain duplicates.
View Source
def get_search_results(self, request, queryset, search_term):
"""
Return a tuple containing a queryset to implement the search
and 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._meta
lookup_fields = field_name.split(LOOKUP_SEP)
# Go through the fields, following all relations.
prev_field = None
for path_part in lookup_fields:
if path_part == "pk":
path_part = opts.pk.name
try:
field = opts.get_field(path_part)
except FieldDoesNotExist:
# Use valid query lookups.
if prev_field and prev_field.get_lookup(path_part):
return field_name
else:
prev_field = field
if 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_name
may_have_duplicates = False
search_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 smart_split(search_term):
if bit.startswith(('"', "'")) and bit[0] == bit[-1]:
bit = unescape_string_literal(bit)
or_queries = models.Q(
*((orm_lookup, bit) for orm_lookup in orm_lookups),
_connector=models.Q.OR,
)
queryset = queryset.filter(or_queries)
may_have_duplicates |= any(
lookup_spawns_duplicates(self.opts, search_spec)
for search_spec in orm_lookups
)
return queryset, may_have_duplicates
get_sortable_by¶
Hook for specifying which fields can be sorted in the changelist.
View Source
get_subclass_fields¶
View Source
def get_subclass_fields(self, request, obj=None):
# Find out how many fields would really be on the form,
# if it weren't restricted by declared fields.
exclude = list(self.exclude or [])
exclude.extend(self.get_readonly_fields(request, obj))
# By not declaring the fields/form in the base class,
# get_form() will populate the form with all available fields.
form = self.get_form(request, obj, exclude=exclude)
subclass_fields = list(form.base_fields.keys()) + list(
self.get_readonly_fields(request, obj)
)
# Find which fields are not part of the common fields.
for fieldset in self.get_base_fieldsets(request, obj):
for field in fieldset[1]["fields"]:
try:
subclass_fields.remove(field)
except ValueError:
pass # field not found in form, Django will raise exception later.
return subclass_fields
get_urls¶
View Source
def get_urls(self):
from django.urls import path
def wrap(view):
def wrapper(*args, **kwargs):
return self.admin_site.admin_view(view)(*args, **kwargs)
wrapper.model_admin = self
return update_wrapper(wrapper, view)
info = self.model._meta.app_label, self.model._meta.model_name
return [
path("", wrap(self.changelist_view), name="%s_%s_changelist" % info),
path("add/", wrap(self.add_view), name="%s_%s_add" % 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)
)
),
),
]
get_view_on_site_url¶
View Source
def get_view_on_site_url(self, obj=None):
if obj is None or not self.view_on_site:
return None
if callable(self.view_on_site):
return self.view_on_site(obj)
elif hasattr(obj, "get_absolute_url"):
# use the ContentType lookup if view_on_site is True
return reverse(
"admin:view_on_site",
kwargs={
"content_type_id": get_content_type_for_model(obj).pk,
"object_id": obj.pk,
},
)
has_add_permission¶
Return True if the given request has permission to add an object.
Can be overridden by the user in subclasses.
View Source
has_change_permission¶
Return True if the given request has permission to change the given
Django model instance, the default implementation doesn't examine the
obj
parameter.
Can be overridden by the user in subclasses. In such case it should
return True if the given request has permission to change the obj
model instance. If obj
is None, this should return True if the given
request has permission to change any object of the given type.
View Source
def has_change_permission(self, request, obj=None):
"""
Return True if the given request has permission to change the given
Django model instance, the default implementation doesn't examine the
`obj` parameter.
Can be overridden by the user in subclasses. In such case it should
return True if the given request has permission to change the `obj`
model instance. If `obj` is None, this should return True if the given
request has permission to change *any* object of the given type.
"""
opts = self.opts
codename = get_permission_codename("change", opts)
return request.user.has_perm("%s.%s" % (opts.app_label, codename))
has_delete_permission¶
Return True if the given request has permission to change the given
Django model instance, the default implementation doesn't examine the
obj
parameter.
Can be overridden by the user in subclasses. In such case it should
return True if the given request has permission to delete the obj
model instance. If obj
is None, this should return True if the given
request has permission to delete any object of the given type.
View Source
def has_delete_permission(self, request, obj=None):
"""
Return True if the given request has permission to change the given
Django model instance, the default implementation doesn't examine the
`obj` parameter.
Can be overridden by the user in subclasses. In such case it should
return True if the given request has permission to delete the `obj`
model instance. If `obj` is None, this should return True if the given
request has permission to delete *any* object of the given type.
"""
opts = self.opts
codename = get_permission_codename("delete", opts)
return request.user.has_perm("%s.%s" % (opts.app_label, codename))
has_module_permission¶
Return True if the given request has any permission in the given
app label.
Can be overridden by the user in subclasses. In such case it should
return True if the given request has permission to view the module on
the admin index page and access the module's index page. Overriding it
does not restrict access to the add, change or delete views. Use
ModelAdmin.has_(add|change|delete)_permission
for that.
View Source
def has_module_permission(self, request):
"""
Return True if the given request has any permission in the given
app label.
Can be overridden by the user in subclasses. In such case it should
return True if the given request has permission to view the module on
the admin index page and access the module's index page. Overriding it
does 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)
has_view_or_change_permission¶
View Source
has_view_permission¶
Return True if the given request has permission to view the given
Django model instance. The default implementation doesn't examine the
obj
parameter.
If overridden by the user in subclasses, it should return True if the
given request has permission to view the obj
model instance. If obj
is None, it should return True if the request has permission to view
any object of the given type.
View Source
def has_view_permission(self, request, obj=None):
"""
Return True if the given request has permission to view the given
Django model instance. The default implementation doesn't examine the
`obj` parameter.
If overridden by the user in subclasses, it should return True if the
given request has permission to view the `obj` model instance. If `obj`
is None, it should return True if the request has permission to view
any object of the given type.
"""
opts = self.opts
codename_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)
) or request.user.has_perm("%s.%s" % (opts.app_label, codename_change))
history_view¶
The 'history' admin view for this model.
View Source
log_addition¶
Log that an object has been successfully added.
The default implementation creates an admin LogEntry object.
View Source
def log_addition(self, request, obj, message):
"""
Log that an object has been successfully added.
The default implementation creates an admin LogEntry object.
"""
from django.contrib.admin.models import ADDITION, LogEntry
return LogEntry.objects.log_action(
user_id=request.user.pk,
content_type_id=get_content_type_for_model(obj).pk,
object_id=obj.pk,
object_repr=str(obj),
action_flag=ADDITION,
change_message=message,
)
log_change¶
Log that an object has been successfully changed.
The default implementation creates an admin LogEntry object.
View Source
def log_change(self, request, obj, message):
"""
Log that an object has been successfully changed.
The default implementation creates an admin LogEntry object.
"""
from django.contrib.admin.models import CHANGE, LogEntry
return LogEntry.objects.log_action(
user_id=request.user.pk,
content_type_id=get_content_type_for_model(obj).pk,
object_id=obj.pk,
object_repr=str(obj),
action_flag=CHANGE,
change_message=message,
)
log_deletion¶
Log that an object will be deleted. Note that this method must be
called before the deletion.
The default implementation creates an admin LogEntry object.
View Source
def log_deletion(self, request, obj, object_repr):
"""
Log that an object will be deleted. Note that this method must be
called before the deletion.
The default implementation creates an admin LogEntry object.
"""
from django.contrib.admin.models import DELETION, LogEntry
return LogEntry.objects.log_action(
user_id=request.user.pk,
content_type_id=get_content_type_for_model(obj).pk,
object_id=obj.pk,
object_repr=object_repr,
action_flag=DELETION,
)
lookup_allowed¶
View Source
def lookup_allowed(self, lookup, value):
from django.contrib.admin.filters import SimpleListFilter
model = 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 True
relation_parts = []
prev_field = None
for 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
and field 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.
break
prev_field = field
model = field.get_path_info()[-1].to_opts.model
if len(relation_parts) <= 1:
# Either a local field filter, or no fields at all.
return True
valid_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)
message_user¶
Send a message to the user. The default implementation
posts a message using the django.contrib.messages backend.
Exposes almost the same API as messages.add_message(), but accepts the
positional arguments in a different order to maintain backwards
compatibility. For convenience, it accepts the level
argument as
a string rather than the usual level number.
View Source
def message_user(
self, request, message, level=messages.INFO, extra_tags="", fail_silently=False
):
"""
Send a message to the user. The default implementation
posts a message using the django.contrib.messages backend.
Exposes almost the same API as messages.add_message(), but accepts the
positional arguments in a different order to maintain backwards
compatibility. For convenience, it accepts the `level` argument as
a string rather than the usual level number.
"""
if not isinstance(level, int):
# attempt to get the level if passed a string
try:
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
)
render_change_form¶
View Source
render_delete_form¶
View Source
def render_delete_form(self, request, context):
opts = self.model._meta
app_label = opts.app_label
request.current_app = self.admin_site.name
context.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,
)
response_action¶
Handle an admin action. This is called if a request is POSTed to the
changelist; it returns an HttpResponse if the action was handled, and None otherwise.
View Source
def response_action(self, request, queryset):
"""
Handle an admin action. This is called if a request is POSTed to the
changelist; it returns an HttpResponse if the action was handled, and
None 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 pushed
try:
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 here
pass
action_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 happen
msg = _(
"Items must be selected in order to perform "
"actions on them. No items have been changed."
)
self.message_user(request, msg, messages.WARNING)
return None
if not select_across:
# Perform the action only on the selected objects
queryset = 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 response
else:
return HttpResponseRedirect(request.get_full_path())
else:
msg = _("No action selected.")
self.message_user(request, msg, messages.WARNING)
return None
response_add¶
Determine the HttpResponse for the add_view stage.
View Source
def response_add(self, request, obj, post_url_continue=None):
"""
Determine the HttpResponse for the add_view stage.
"""
opts = obj._meta
preserved_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.attname
value = 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
and self.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_url
post_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.path
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 added successfully."), **msg_dict
)
self.message_user(request, msg, messages.SUCCESS)
return self.response_post_save_add(request, obj)
response_change¶
Determine the HttpResponse for the change_view stage.
View Source
def response_change(self, request, obj):
"""
Determine the HttpResponse for the change_view stage.
"""
if IS_POPUP_VAR in request.POST:
opts = obj._meta
to_field = request.POST.get(TO_FIELD_VAR)
attr = str(to_field) if to_field else opts.pk.attname
value = 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._meta
preserved_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.path
redirect_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)
response_delete¶
Determine the HttpResponse for the delete_view stage.
View Source
def response_delete(self, request, obj_display, obj_id):
"""
Determine the HttpResponse for the delete_view stage.
"""
opts = self.model._meta
if 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)
response_post_save_add¶
Figure out where to redirect after the 'Save' button has been pressed
when adding a new object.
View Source
response_post_save_change¶
Figure out where to redirect after the 'Save' button has been pressed
when editing an existing object.
View Source
save_form¶
Given a ModelForm return an unsaved instance. change
is True if
the object is being changed, and False if it's being added.
View Source
save_formset¶
Given an inline formset save it to the database.
View Source
save_model¶
Given a model instance save it to the database.
View Source
save_related¶
Given the HttpRequest
, the parent ModelForm
instance, the
list of inline formsets and a boolean value based on whether the parent is being added or changed, save the related objects to the database. Note that at this point save_form() and save_model() have already been called.
View Source
def save_related(self, request, form, formsets, change):
"""
Given the ``HttpRequest``, the parent ``ModelForm`` instance, the
list of inline formsets and a boolean value based on whether the
parent is being added or changed, save the related objects to the
database. Note that at this point save_form() and save_model() have
already been called.
"""
form.save_m2m()
for formset in formsets:
self.save_formset(request, form, formset, change=change)
to_field_allowed¶
Return True if the model associated with this admin should be
allowed to be referenced by the specified field.
View Source
def to_field_allowed(self, request, to_field):
"""
Return True if the model associated with this admin should be
allowed to be referenced by the specified field.
"""
opts = self.model._meta
try:
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_model
remote_field = related_object.field.remote_field
if (
any(issubclass(model, related_model) for model in registered_models)
and hasattr(remote_field, "get_related_field")
and remote_field.get_related_field() == field
):
return True
return False
LocalModelAdmin¶
Admin interface for the LocalModel
.
View Source
Ancestors (in MRO)¶
- fl_server_core.admin.ModelChildAdmin
- polymorphic.admin.childadmin.PolymorphicChildModelAdmin
- django.contrib.admin.options.ModelAdmin
- django.contrib.admin.options.BaseModelAdmin
Class variables¶
Instance variables¶
Methods¶
action_checkbox¶
A list_display column containing a checkbox widget.
View Source
add_view¶
View Source
change_view¶
View Source
changeform_view¶
View Source
changelist_view¶
The 'change list' admin view for this model.
View Source
@csrf_protect_m
def changelist_view(self, request, extra_context=None):
"""
The 'change list' admin view for this model.
"""
from django.contrib.admin.views.main import ERROR_FLAG
opts = self.model._meta
app_label = opts.app_label
if not self.has_view_or_change_permission(request):
raise PermissionDenied
try:
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 = False
selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME)
actions = self.get_actions(request)
# Actions with no confirmation
if (
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 response
else:
action_failed = True
else:
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 confirmation
if (
actions
and request.method == "POST"
and helpers.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 response
else:
action_failed = True
if 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 PermissionDenied
FormSet = 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 = 0
for 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 += 1
if 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.media
else:
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.media
else:
action_form = None
selection_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,
"subtitle": None,
"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.name
return 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,
)
check¶
construct_change_message¶
Construct a JSON structure describing changes from a changed object.
View Source
delete_model¶
Given a model instance delete it from the database.
View Source
delete_queryset¶
Given a queryset, delete it from the database.
View Source
delete_view¶
View Source
formfield_for_choice_field¶
Get a form Field for a database Field that has declared choices.
View Source
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 RadioSelect
if 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)
formfield_for_dbfield¶
Hook for specifying the form Field instance for a given database Field
instance.
If kwargs are given, they're passed to the form Field's constructor.
View Source
def formfield_for_dbfield(self, db_field, request, **kwargs):
"""
Hook for specifying the form Field instance for a given database Field
instance.
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 ManyToManyFields
if 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)
formfield_for_foreignkey¶
Get a form Field for a ForeignKey.
View Source
def 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, 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 None
if "queryset" not in kwargs:
queryset = self.get_field_queryset(db, db_field, request)
if queryset is not None:
kwargs["queryset"] = queryset
return db_field.formfield(**kwargs)
formfield_for_manytomany¶
Get a form Field for a ManyToManyField.
View Source
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 None
db = 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,
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"] = queryset
form_field = db_field.formfield(**kwargs)
if isinstance(form_field.widget, SelectMultiple) and not 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_text
form_field.help_text = (
format_lazy("{} {}", help_text, msg) if help_text else msg
)
return form_field
get_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).
View Source
def 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 = action
action = 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 site
else:
try:
func = self.admin_site.get_action(action)
except KeyError:
return None
description = self._get_action_description(func, action)
return func, action, description
get_action_choices¶
Return a list of choices for use in a form object. Each choice is a
tuple (name, description).
View Source
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 a
tuple (name, description).
"""
choices = [] + default_choices
for func, name, description in self.get_actions(request).values():
choice = (name, description % model_format_dict(self.opts))
choices.append(choice)
return choices
get_actions¶
Return a dictionary mapping the names of all actions for this
ModelAdmin to a tuple of (callable, name, description) for each action.
View Source
def get_actions(self, request):
"""
Return a dictionary mapping the names of all actions for this
ModelAdmin 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}
get_autocomplete_fields¶
Return a list of ForeignKey and/or ManyToMany fields which should use
an autocomplete widget.
View Source
get_base_fieldsets¶
get_changeform_initial_data¶
Get the initial form data from the request's GET params.
View Source
def 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 initial
get_changelist¶
Return the ChangeList class for use on the changelist page.
View Source
get_changelist_form¶
Return a Form class for use in the Formset on the changelist page.
View Source
def 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_FIELDS
return modelform_factory(self.model, **defaults)
get_changelist_formset¶
Return a FormSet class for use on the changelist page if list_editable
is used.
View Source
def get_changelist_formset(self, request, **kwargs):
"""
Return a FormSet class for use on the changelist page if list_editable
is 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,
)
get_changelist_instance¶
Return a ChangeList
instance based on request
. May raise
IncorrectLookupParameters
.
View Source
def 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,
self.search_help_text,
)
get_deleted_objects¶
Hook for customizing the delete process for the delete view and the
"delete selected" action.
View Source
get_empty_value_display¶
Return the empty_value_display set on ModelAdmin or AdminSite.
View Source
get_exclude¶
Hook for specifying exclude.
View Source
get_field_queryset¶
If the ModelAdmin specifies ordering, the queryset should respect that
ordering. Otherwise don't specify the queryset, let the field decide (return None in that case).
View Source
def get_field_queryset(self, db, db_field, request):
"""
If the ModelAdmin specifies ordering, the queryset should respect that
ordering. 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 None
get_fields¶
Hook for specifying fields.
View Source
get_fieldsets¶
Hook for specifying fieldsets.
View Source
def get_fieldsets(self, request, obj=None):
base_fieldsets = self.get_base_fieldsets(request, obj)
# If subclass declares fieldsets or fields, this is respected
if self.fieldsets or self.fields or not self.base_fieldsets:
return super().get_fieldsets(request, obj)
# Have a reasonable default fieldsets,
# where the subclass fields are automatically included.
other_fields = self.get_subclass_fields(request, obj)
if other_fields:
return (
base_fieldsets[0],
(self.extra_fieldset_title, {"fields": other_fields}),
) + base_fieldsets[1:]
else:
return base_fieldsets
get_form¶
Return a Form class for use in the admin add view. This is used by
add_view and change_view.
View Source
def get_form(self, request, obj=None, **kwargs):
# The django admin validation requires the form to have a 'class Meta: model = ..'
# attribute, or it will complain that the fields are missing.
# However, this enforces all derived ModelAdmin classes to redefine the model as well,
# because they need to explicitly set the model again - it will stick with the base model.
#
# Instead, pass the form unchecked here, because the standard ModelForm will just work.
# If the derived class sets the model explicitly, respect that setting.
kwargs.setdefault("form", self.base_form or self.form)
# prevent infinite recursion when this is called from get_subclass_fields
if not self.fieldsets and not self.fields:
kwargs.setdefault("fields", "__all__")
return super().get_form(request, obj, **kwargs)
get_formset_kwargs¶
View Source
def get_formset_kwargs(self, request, obj, inline, 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,
}
)
return formset_params
get_formsets_with_inlines¶
Yield formsets and the corresponding inlines.
View Source
get_inline_formsets¶
View Source
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 = False
formset.extra = formset.max_num = 0
has_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_formsets
get_inline_instances¶
View Source
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)
or inline.has_add_permission(request, obj)
or inline.has_delete_permission(request, obj)
):
continue
if not inline.has_add_permission(request, obj):
inline.max_num = 0
inline_instances.append(inline)
return inline_instances
get_inlines¶
Hook for specifying custom inlines.
View Source
get_list_display¶
Return a sequence containing the fields to be displayed on the
changelist.
View Source
get_list_display_links¶
Return a sequence containing the fields to be displayed as links
on the changelist. The list_display parameter is the list of fields returned by get_list_display().
View Source
def get_list_display_links(self, request, list_display):
"""
Return a sequence containing the fields to be displayed as links
on the changelist. The list_display parameter is the list of fields
returned by get_list_display().
"""
if (
self.list_display_links
or self.list_display_links is None
or not list_display
):
return self.list_display_links
else:
# Use only the first item in list_display as link
return list(list_display)[:1]
get_list_filter¶
Return a sequence containing the fields to be displayed as filters in
the right sidebar of the changelist page.
View Source
get_list_select_related¶
Return a list of fields to add to the select_related() part of the
changelist items query.
View Source
get_model_perms¶
Return a dict of all perms for this model. This dict has the keys
add
, change
, delete
, and view
mapping to the True/False
for each of those actions.
View Source
get_object¶
Return an instance matching the field and value provided, the primary
key is used if no field is provided. Return None
if no match is
found or the object_id fails validation.
View Source
def get_object(self, request, object_id, from_field=None):
"""
Return an instance matching the field and value provided, the primary
key is used if no field is provided. Return ``None`` if no match is
found or the object_id fails validation.
"""
queryset = self.get_queryset(request)
model = queryset.model
field = (
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 None
get_ordering¶
Hook for specifying field ordering.
View Source
get_paginator¶
View Source
get_prepopulated_fields¶
Hook for specifying custom prepopulated fields.
View Source
get_preserved_filters¶
Return the preserved filters querystring.
View Source
def get_preserved_filters(self, request):
"""
Return the preserved filters querystring.
"""
match = request.resolver_match
if self.preserve_filters and match:
opts = self.model._meta
current_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 ""
get_queryset¶
Return a QuerySet of all model instances that can be edited by the
admin site. This is used by changelist_view.
View Source
def get_queryset(self, request):
"""
Return a QuerySet of all model instances that can be edited by the
admin 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 qs
get_readonly_fields¶
Hook for specifying custom readonly fields.
View Source
get_search_fields¶
Return a sequence containing the fields to be searched whenever
somebody submits a search query.
View Source
get_search_results¶
Return a tuple containing a queryset to implement the search
and a boolean indicating if the results may contain duplicates.
View Source
def get_search_results(self, request, queryset, search_term):
"""
Return a tuple containing a queryset to implement the search
and 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._meta
lookup_fields = field_name.split(LOOKUP_SEP)
# Go through the fields, following all relations.
prev_field = None
for path_part in lookup_fields:
if path_part == "pk":
path_part = opts.pk.name
try:
field = opts.get_field(path_part)
except FieldDoesNotExist:
# Use valid query lookups.
if prev_field and prev_field.get_lookup(path_part):
return field_name
else:
prev_field = field
if 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_name
may_have_duplicates = False
search_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 smart_split(search_term):
if bit.startswith(('"', "'")) and bit[0] == bit[-1]:
bit = unescape_string_literal(bit)
or_queries = models.Q(
*((orm_lookup, bit) for orm_lookup in orm_lookups),
_connector=models.Q.OR,
)
queryset = queryset.filter(or_queries)
may_have_duplicates |= any(
lookup_spawns_duplicates(self.opts, search_spec)
for search_spec in orm_lookups
)
return queryset, may_have_duplicates
get_sortable_by¶
Hook for specifying which fields can be sorted in the changelist.
View Source
get_subclass_fields¶
View Source
def get_subclass_fields(self, request, obj=None):
# Find out how many fields would really be on the form,
# if it weren't restricted by declared fields.
exclude = list(self.exclude or [])
exclude.extend(self.get_readonly_fields(request, obj))
# By not declaring the fields/form in the base class,
# get_form() will populate the form with all available fields.
form = self.get_form(request, obj, exclude=exclude)
subclass_fields = list(form.base_fields.keys()) + list(
self.get_readonly_fields(request, obj)
)
# Find which fields are not part of the common fields.
for fieldset in self.get_base_fieldsets(request, obj):
for field in fieldset[1]["fields"]:
try:
subclass_fields.remove(field)
except ValueError:
pass # field not found in form, Django will raise exception later.
return subclass_fields
get_urls¶
View Source
def get_urls(self):
from django.urls import path
def wrap(view):
def wrapper(*args, **kwargs):
return self.admin_site.admin_view(view)(*args, **kwargs)
wrapper.model_admin = self
return update_wrapper(wrapper, view)
info = self.model._meta.app_label, self.model._meta.model_name
return [
path("", wrap(self.changelist_view), name="%s_%s_changelist" % info),
path("add/", wrap(self.add_view), name="%s_%s_add" % 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)
)
),
),
]
get_view_on_site_url¶
View Source
def get_view_on_site_url(self, obj=None):
if obj is None or not self.view_on_site:
return None
if callable(self.view_on_site):
return self.view_on_site(obj)
elif hasattr(obj, "get_absolute_url"):
# use the ContentType lookup if view_on_site is True
return reverse(
"admin:view_on_site",
kwargs={
"content_type_id": get_content_type_for_model(obj).pk,
"object_id": obj.pk,
},
)
has_add_permission¶
Return True if the given request has permission to add an object.
Can be overridden by the user in subclasses.
View Source
has_change_permission¶
Return True if the given request has permission to change the given
Django model instance, the default implementation doesn't examine the
obj
parameter.
Can be overridden by the user in subclasses. In such case it should
return True if the given request has permission to change the obj
model instance. If obj
is None, this should return True if the given
request has permission to change any object of the given type.
View Source
def has_change_permission(self, request, obj=None):
"""
Return True if the given request has permission to change the given
Django model instance, the default implementation doesn't examine the
`obj` parameter.
Can be overridden by the user in subclasses. In such case it should
return True if the given request has permission to change the `obj`
model instance. If `obj` is None, this should return True if the given
request has permission to change *any* object of the given type.
"""
opts = self.opts
codename = get_permission_codename("change", opts)
return request.user.has_perm("%s.%s" % (opts.app_label, codename))
has_delete_permission¶
Return True if the given request has permission to change the given
Django model instance, the default implementation doesn't examine the
obj
parameter.
Can be overridden by the user in subclasses. In such case it should
return True if the given request has permission to delete the obj
model instance. If obj
is None, this should return True if the given
request has permission to delete any object of the given type.
View Source
def has_delete_permission(self, request, obj=None):
"""
Return True if the given request has permission to change the given
Django model instance, the default implementation doesn't examine the
`obj` parameter.
Can be overridden by the user in subclasses. In such case it should
return True if the given request has permission to delete the `obj`
model instance. If `obj` is None, this should return True if the given
request has permission to delete *any* object of the given type.
"""
opts = self.opts
codename = get_permission_codename("delete", opts)
return request.user.has_perm("%s.%s" % (opts.app_label, codename))
has_module_permission¶
Return True if the given request has any permission in the given
app label.
Can be overridden by the user in subclasses. In such case it should
return True if the given request has permission to view the module on
the admin index page and access the module's index page. Overriding it
does not restrict access to the add, change or delete views. Use
ModelAdmin.has_(add|change|delete)_permission
for that.
View Source
def has_module_permission(self, request):
"""
Return True if the given request has any permission in the given
app label.
Can be overridden by the user in subclasses. In such case it should
return True if the given request has permission to view the module on
the admin index page and access the module's index page. Overriding it
does 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)
has_view_or_change_permission¶
View Source
has_view_permission¶
Return True if the given request has permission to view the given
Django model instance. The default implementation doesn't examine the
obj
parameter.
If overridden by the user in subclasses, it should return True if the
given request has permission to view the obj
model instance. If obj
is None, it should return True if the request has permission to view
any object of the given type.
View Source
def has_view_permission(self, request, obj=None):
"""
Return True if the given request has permission to view the given
Django model instance. The default implementation doesn't examine the
`obj` parameter.
If overridden by the user in subclasses, it should return True if the
given request has permission to view the `obj` model instance. If `obj`
is None, it should return True if the request has permission to view
any object of the given type.
"""
opts = self.opts
codename_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)
) or request.user.has_perm("%s.%s" % (opts.app_label, codename_change))
history_view¶
The 'history' admin view for this model.
View Source
log_addition¶
Log that an object has been successfully added.
The default implementation creates an admin LogEntry object.
View Source
def log_addition(self, request, obj, message):
"""
Log that an object has been successfully added.
The default implementation creates an admin LogEntry object.
"""
from django.contrib.admin.models import ADDITION, LogEntry
return LogEntry.objects.log_action(
user_id=request.user.pk,
content_type_id=get_content_type_for_model(obj).pk,
object_id=obj.pk,
object_repr=str(obj),
action_flag=ADDITION,
change_message=message,
)
log_change¶
Log that an object has been successfully changed.
The default implementation creates an admin LogEntry object.
View Source
def log_change(self, request, obj, message):
"""
Log that an object has been successfully changed.
The default implementation creates an admin LogEntry object.
"""
from django.contrib.admin.models import CHANGE, LogEntry
return LogEntry.objects.log_action(
user_id=request.user.pk,
content_type_id=get_content_type_for_model(obj).pk,
object_id=obj.pk,
object_repr=str(obj),
action_flag=CHANGE,
change_message=message,
)
log_deletion¶
Log that an object will be deleted. Note that this method must be
called before the deletion.
The default implementation creates an admin LogEntry object.
View Source
def log_deletion(self, request, obj, object_repr):
"""
Log that an object will be deleted. Note that this method must be
called before the deletion.
The default implementation creates an admin LogEntry object.
"""
from django.contrib.admin.models import DELETION, LogEntry
return LogEntry.objects.log_action(
user_id=request.user.pk,
content_type_id=get_content_type_for_model(obj).pk,
object_id=obj.pk,
object_repr=object_repr,
action_flag=DELETION,
)
lookup_allowed¶
View Source
def lookup_allowed(self, lookup, value):
from django.contrib.admin.filters import SimpleListFilter
model = 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 True
relation_parts = []
prev_field = None
for 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
and field 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.
break
prev_field = field
model = field.get_path_info()[-1].to_opts.model
if len(relation_parts) <= 1:
# Either a local field filter, or no fields at all.
return True
valid_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)
message_user¶
Send a message to the user. The default implementation
posts a message using the django.contrib.messages backend.
Exposes almost the same API as messages.add_message(), but accepts the
positional arguments in a different order to maintain backwards
compatibility. For convenience, it accepts the level
argument as
a string rather than the usual level number.
View Source
def message_user(
self, request, message, level=messages.INFO, extra_tags="", fail_silently=False
):
"""
Send a message to the user. The default implementation
posts a message using the django.contrib.messages backend.
Exposes almost the same API as messages.add_message(), but accepts the
positional arguments in a different order to maintain backwards
compatibility. For convenience, it accepts the `level` argument as
a string rather than the usual level number.
"""
if not isinstance(level, int):
# attempt to get the level if passed a string
try:
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
)
render_change_form¶
View Source
render_delete_form¶
View Source
def render_delete_form(self, request, context):
opts = self.model._meta
app_label = opts.app_label
request.current_app = self.admin_site.name
context.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,
)
response_action¶
Handle an admin action. This is called if a request is POSTed to the
changelist; it returns an HttpResponse if the action was handled, and None otherwise.
View Source
def response_action(self, request, queryset):
"""
Handle an admin action. This is called if a request is POSTed to the
changelist; it returns an HttpResponse if the action was handled, and
None 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 pushed
try:
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 here
pass
action_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 happen
msg = _(
"Items must be selected in order to perform "
"actions on them. No items have been changed."
)
self.message_user(request, msg, messages.WARNING)
return None
if not select_across:
# Perform the action only on the selected objects
queryset = 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 response
else:
return HttpResponseRedirect(request.get_full_path())
else:
msg = _("No action selected.")
self.message_user(request, msg, messages.WARNING)
return None
response_add¶
Determine the HttpResponse for the add_view stage.
View Source
def response_add(self, request, obj, post_url_continue=None):
"""
Determine the HttpResponse for the add_view stage.
"""
opts = obj._meta
preserved_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.attname
value = 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
and self.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_url
post_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.path
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 added successfully."), **msg_dict
)
self.message_user(request, msg, messages.SUCCESS)
return self.response_post_save_add(request, obj)
response_change¶
Determine the HttpResponse for the change_view stage.
View Source
def response_change(self, request, obj):
"""
Determine the HttpResponse for the change_view stage.
"""
if IS_POPUP_VAR in request.POST:
opts = obj._meta
to_field = request.POST.get(TO_FIELD_VAR)
attr = str(to_field) if to_field else opts.pk.attname
value = 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._meta
preserved_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.path
redirect_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)
response_delete¶
Determine the HttpResponse for the delete_view stage.
View Source
def response_delete(self, request, obj_display, obj_id):
"""
Determine the HttpResponse for the delete_view stage.
"""
opts = self.model._meta
if 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)
response_post_save_add¶
Figure out where to redirect after the 'Save' button has been pressed
when adding a new object.
View Source
response_post_save_change¶
Figure out where to redirect after the 'Save' button has been pressed
when editing an existing object.
View Source
save_form¶
Given a ModelForm return an unsaved instance. change
is True if
the object is being changed, and False if it's being added.
View Source
save_formset¶
Given an inline formset save it to the database.
View Source
save_model¶
Given a model instance save it to the database.
View Source
save_related¶
Given the HttpRequest
, the parent ModelForm
instance, the
list of inline formsets and a boolean value based on whether the parent is being added or changed, save the related objects to the database. Note that at this point save_form() and save_model() have already been called.
View Source
def save_related(self, request, form, formsets, change):
"""
Given the ``HttpRequest``, the parent ``ModelForm`` instance, the
list of inline formsets and a boolean value based on whether the
parent is being added or changed, save the related objects to the
database. Note that at this point save_form() and save_model() have
already been called.
"""
form.save_m2m()
for formset in formsets:
self.save_formset(request, form, formset, change=change)
to_field_allowed¶
Return True if the model associated with this admin should be
allowed to be referenced by the specified field.
View Source
def to_field_allowed(self, request, to_field):
"""
Return True if the model associated with this admin should be
allowed to be referenced by the specified field.
"""
opts = self.model._meta
try:
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_model
remote_field = related_object.field.remote_field
if (
any(issubclass(model, related_model) for model in registered_models)
and hasattr(remote_field, "get_related_field")
and remote_field.get_related_field() == field
):
return True
return False
ModelChildAdmin¶
Polymorphic admin model base class.
View Source
Ancestors (in MRO)¶
- polymorphic.admin.childadmin.PolymorphicChildModelAdmin
- django.contrib.admin.options.ModelAdmin
- django.contrib.admin.options.BaseModelAdmin
Descendants¶
- fl_server_core.admin.GlobalModelAdmin
- fl_server_core.admin.LocalModelAdmin
Class variables¶
Instance variables¶
Methods¶
action_checkbox¶
A list_display column containing a checkbox widget.
View Source
add_view¶
View Source
change_view¶
View Source
changeform_view¶
View Source
changelist_view¶
The 'change list' admin view for this model.
View Source
@csrf_protect_m
def changelist_view(self, request, extra_context=None):
"""
The 'change list' admin view for this model.
"""
from django.contrib.admin.views.main import ERROR_FLAG
opts = self.model._meta
app_label = opts.app_label
if not self.has_view_or_change_permission(request):
raise PermissionDenied
try:
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 = False
selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME)
actions = self.get_actions(request)
# Actions with no confirmation
if (
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 response
else:
action_failed = True
else:
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 confirmation
if (
actions
and request.method == "POST"
and helpers.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 response
else:
action_failed = True
if 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 PermissionDenied
FormSet = 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 = 0
for 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 += 1
if 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.media
else:
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.media
else:
action_form = None
selection_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,
"subtitle": None,
"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.name
return 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,
)
check¶
construct_change_message¶
Construct a JSON structure describing changes from a changed object.
View Source
delete_model¶
Given a model instance delete it from the database.
View Source
delete_queryset¶
Given a queryset, delete it from the database.
View Source
delete_view¶
View Source
formfield_for_choice_field¶
Get a form Field for a database Field that has declared choices.
View Source
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 RadioSelect
if 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)
formfield_for_dbfield¶
Hook for specifying the form Field instance for a given database Field
instance.
If kwargs are given, they're passed to the form Field's constructor.
View Source
def formfield_for_dbfield(self, db_field, request, **kwargs):
"""
Hook for specifying the form Field instance for a given database Field
instance.
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 ManyToManyFields
if 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)
formfield_for_foreignkey¶
Get a form Field for a ForeignKey.
View Source
def 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, 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 None
if "queryset" not in kwargs:
queryset = self.get_field_queryset(db, db_field, request)
if queryset is not None:
kwargs["queryset"] = queryset
return db_field.formfield(**kwargs)
formfield_for_manytomany¶
Get a form Field for a ManyToManyField.
View Source
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 None
db = 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,
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"] = queryset
form_field = db_field.formfield(**kwargs)
if isinstance(form_field.widget, SelectMultiple) and not 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_text
form_field.help_text = (
format_lazy("{} {}", help_text, msg) if help_text else msg
)
return form_field
get_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).
View Source
def 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 = action
action = 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 site
else:
try:
func = self.admin_site.get_action(action)
except KeyError:
return None
description = self._get_action_description(func, action)
return func, action, description
get_action_choices¶
Return a list of choices for use in a form object. Each choice is a
tuple (name, description).
View Source
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 a
tuple (name, description).
"""
choices = [] + default_choices
for func, name, description in self.get_actions(request).values():
choice = (name, description % model_format_dict(self.opts))
choices.append(choice)
return choices
get_actions¶
Return a dictionary mapping the names of all actions for this
ModelAdmin to a tuple of (callable, name, description) for each action.
View Source
def get_actions(self, request):
"""
Return a dictionary mapping the names of all actions for this
ModelAdmin 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}
get_autocomplete_fields¶
Return a list of ForeignKey and/or ManyToMany fields which should use
an autocomplete widget.
View Source
get_base_fieldsets¶
get_changeform_initial_data¶
Get the initial form data from the request's GET params.
View Source
def 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 initial
get_changelist¶
Return the ChangeList class for use on the changelist page.
View Source
get_changelist_form¶
Return a Form class for use in the Formset on the changelist page.
View Source
def 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_FIELDS
return modelform_factory(self.model, **defaults)
get_changelist_formset¶
Return a FormSet class for use on the changelist page if list_editable
is used.
View Source
def get_changelist_formset(self, request, **kwargs):
"""
Return a FormSet class for use on the changelist page if list_editable
is 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,
)
get_changelist_instance¶
Return a ChangeList
instance based on request
. May raise
IncorrectLookupParameters
.
View Source
def 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,
self.search_help_text,
)
get_deleted_objects¶
Hook for customizing the delete process for the delete view and the
"delete selected" action.
View Source
get_empty_value_display¶
Return the empty_value_display set on ModelAdmin or AdminSite.
View Source
get_exclude¶
Hook for specifying exclude.
View Source
get_field_queryset¶
If the ModelAdmin specifies ordering, the queryset should respect that
ordering. Otherwise don't specify the queryset, let the field decide (return None in that case).
View Source
def get_field_queryset(self, db, db_field, request):
"""
If the ModelAdmin specifies ordering, the queryset should respect that
ordering. 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 None
get_fields¶
Hook for specifying fields.
View Source
get_fieldsets¶
Hook for specifying fieldsets.
View Source
def get_fieldsets(self, request, obj=None):
base_fieldsets = self.get_base_fieldsets(request, obj)
# If subclass declares fieldsets or fields, this is respected
if self.fieldsets or self.fields or not self.base_fieldsets:
return super().get_fieldsets(request, obj)
# Have a reasonable default fieldsets,
# where the subclass fields are automatically included.
other_fields = self.get_subclass_fields(request, obj)
if other_fields:
return (
base_fieldsets[0],
(self.extra_fieldset_title, {"fields": other_fields}),
) + base_fieldsets[1:]
else:
return base_fieldsets
get_form¶
Return a Form class for use in the admin add view. This is used by
add_view and change_view.
View Source
def get_form(self, request, obj=None, **kwargs):
# The django admin validation requires the form to have a 'class Meta: model = ..'
# attribute, or it will complain that the fields are missing.
# However, this enforces all derived ModelAdmin classes to redefine the model as well,
# because they need to explicitly set the model again - it will stick with the base model.
#
# Instead, pass the form unchecked here, because the standard ModelForm will just work.
# If the derived class sets the model explicitly, respect that setting.
kwargs.setdefault("form", self.base_form or self.form)
# prevent infinite recursion when this is called from get_subclass_fields
if not self.fieldsets and not self.fields:
kwargs.setdefault("fields", "__all__")
return super().get_form(request, obj, **kwargs)
get_formset_kwargs¶
View Source
def get_formset_kwargs(self, request, obj, inline, 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,
}
)
return formset_params
get_formsets_with_inlines¶
Yield formsets and the corresponding inlines.
View Source
get_inline_formsets¶
View Source
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 = False
formset.extra = formset.max_num = 0
has_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_formsets
get_inline_instances¶
View Source
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)
or inline.has_add_permission(request, obj)
or inline.has_delete_permission(request, obj)
):
continue
if not inline.has_add_permission(request, obj):
inline.max_num = 0
inline_instances.append(inline)
return inline_instances
get_inlines¶
Hook for specifying custom inlines.
View Source
get_list_display¶
Return a sequence containing the fields to be displayed on the
changelist.
View Source
get_list_display_links¶
Return a sequence containing the fields to be displayed as links
on the changelist. The list_display parameter is the list of fields returned by get_list_display().
View Source
def get_list_display_links(self, request, list_display):
"""
Return a sequence containing the fields to be displayed as links
on the changelist. The list_display parameter is the list of fields
returned by get_list_display().
"""
if (
self.list_display_links
or self.list_display_links is None
or not list_display
):
return self.list_display_links
else:
# Use only the first item in list_display as link
return list(list_display)[:1]
get_list_filter¶
Return a sequence containing the fields to be displayed as filters in
the right sidebar of the changelist page.
View Source
get_list_select_related¶
Return a list of fields to add to the select_related() part of the
changelist items query.
View Source
get_model_perms¶
Return a dict of all perms for this model. This dict has the keys
add
, change
, delete
, and view
mapping to the True/False
for each of those actions.
View Source
get_object¶
Return an instance matching the field and value provided, the primary
key is used if no field is provided. Return None
if no match is
found or the object_id fails validation.
View Source
def get_object(self, request, object_id, from_field=None):
"""
Return an instance matching the field and value provided, the primary
key is used if no field is provided. Return ``None`` if no match is
found or the object_id fails validation.
"""
queryset = self.get_queryset(request)
model = queryset.model
field = (
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 None
get_ordering¶
Hook for specifying field ordering.
View Source
get_paginator¶
View Source
get_prepopulated_fields¶
Hook for specifying custom prepopulated fields.
View Source
get_preserved_filters¶
Return the preserved filters querystring.
View Source
def get_preserved_filters(self, request):
"""
Return the preserved filters querystring.
"""
match = request.resolver_match
if self.preserve_filters and match:
opts = self.model._meta
current_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 ""
get_queryset¶
Return a QuerySet of all model instances that can be edited by the
admin site. This is used by changelist_view.
View Source
def get_queryset(self, request):
"""
Return a QuerySet of all model instances that can be edited by the
admin 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 qs
get_readonly_fields¶
Hook for specifying custom readonly fields.
View Source
get_search_fields¶
Return a sequence containing the fields to be searched whenever
somebody submits a search query.
View Source
get_search_results¶
Return a tuple containing a queryset to implement the search
and a boolean indicating if the results may contain duplicates.
View Source
def get_search_results(self, request, queryset, search_term):
"""
Return a tuple containing a queryset to implement the search
and 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._meta
lookup_fields = field_name.split(LOOKUP_SEP)
# Go through the fields, following all relations.
prev_field = None
for path_part in lookup_fields:
if path_part == "pk":
path_part = opts.pk.name
try:
field = opts.get_field(path_part)
except FieldDoesNotExist:
# Use valid query lookups.
if prev_field and prev_field.get_lookup(path_part):
return field_name
else:
prev_field = field
if 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_name
may_have_duplicates = False
search_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 smart_split(search_term):
if bit.startswith(('"', "'")) and bit[0] == bit[-1]:
bit = unescape_string_literal(bit)
or_queries = models.Q(
*((orm_lookup, bit) for orm_lookup in orm_lookups),
_connector=models.Q.OR,
)
queryset = queryset.filter(or_queries)
may_have_duplicates |= any(
lookup_spawns_duplicates(self.opts, search_spec)
for search_spec in orm_lookups
)
return queryset, may_have_duplicates
get_sortable_by¶
Hook for specifying which fields can be sorted in the changelist.
View Source
get_subclass_fields¶
View Source
def get_subclass_fields(self, request, obj=None):
# Find out how many fields would really be on the form,
# if it weren't restricted by declared fields.
exclude = list(self.exclude or [])
exclude.extend(self.get_readonly_fields(request, obj))
# By not declaring the fields/form in the base class,
# get_form() will populate the form with all available fields.
form = self.get_form(request, obj, exclude=exclude)
subclass_fields = list(form.base_fields.keys()) + list(
self.get_readonly_fields(request, obj)
)
# Find which fields are not part of the common fields.
for fieldset in self.get_base_fieldsets(request, obj):
for field in fieldset[1]["fields"]:
try:
subclass_fields.remove(field)
except ValueError:
pass # field not found in form, Django will raise exception later.
return subclass_fields
get_urls¶
View Source
def get_urls(self):
from django.urls import path
def wrap(view):
def wrapper(*args, **kwargs):
return self.admin_site.admin_view(view)(*args, **kwargs)
wrapper.model_admin = self
return update_wrapper(wrapper, view)
info = self.model._meta.app_label, self.model._meta.model_name
return [
path("", wrap(self.changelist_view), name="%s_%s_changelist" % info),
path("add/", wrap(self.add_view), name="%s_%s_add" % 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)
)
),
),
]
get_view_on_site_url¶
View Source
def get_view_on_site_url(self, obj=None):
if obj is None or not self.view_on_site:
return None
if callable(self.view_on_site):
return self.view_on_site(obj)
elif hasattr(obj, "get_absolute_url"):
# use the ContentType lookup if view_on_site is True
return reverse(
"admin:view_on_site",
kwargs={
"content_type_id": get_content_type_for_model(obj).pk,
"object_id": obj.pk,
},
)
has_add_permission¶
Return True if the given request has permission to add an object.
Can be overridden by the user in subclasses.
View Source
has_change_permission¶
Return True if the given request has permission to change the given
Django model instance, the default implementation doesn't examine the
obj
parameter.
Can be overridden by the user in subclasses. In such case it should
return True if the given request has permission to change the obj
model instance. If obj
is None, this should return True if the given
request has permission to change any object of the given type.
View Source
def has_change_permission(self, request, obj=None):
"""
Return True if the given request has permission to change the given
Django model instance, the default implementation doesn't examine the
`obj` parameter.
Can be overridden by the user in subclasses. In such case it should
return True if the given request has permission to change the `obj`
model instance. If `obj` is None, this should return True if the given
request has permission to change *any* object of the given type.
"""
opts = self.opts
codename = get_permission_codename("change", opts)
return request.user.has_perm("%s.%s" % (opts.app_label, codename))
has_delete_permission¶
Return True if the given request has permission to change the given
Django model instance, the default implementation doesn't examine the
obj
parameter.
Can be overridden by the user in subclasses. In such case it should
return True if the given request has permission to delete the obj
model instance. If obj
is None, this should return True if the given
request has permission to delete any object of the given type.
View Source
def has_delete_permission(self, request, obj=None):
"""
Return True if the given request has permission to change the given
Django model instance, the default implementation doesn't examine the
`obj` parameter.
Can be overridden by the user in subclasses. In such case it should
return True if the given request has permission to delete the `obj`
model instance. If `obj` is None, this should return True if the given
request has permission to delete *any* object of the given type.
"""
opts = self.opts
codename = get_permission_codename("delete", opts)
return request.user.has_perm("%s.%s" % (opts.app_label, codename))
has_module_permission¶
Return True if the given request has any permission in the given
app label.
Can be overridden by the user in subclasses. In such case it should
return True if the given request has permission to view the module on
the admin index page and access the module's index page. Overriding it
does not restrict access to the add, change or delete views. Use
ModelAdmin.has_(add|change|delete)_permission
for that.
View Source
def has_module_permission(self, request):
"""
Return True if the given request has any permission in the given
app label.
Can be overridden by the user in subclasses. In such case it should
return True if the given request has permission to view the module on
the admin index page and access the module's index page. Overriding it
does 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)
has_view_or_change_permission¶
View Source
has_view_permission¶
Return True if the given request has permission to view the given
Django model instance. The default implementation doesn't examine the
obj
parameter.
If overridden by the user in subclasses, it should return True if the
given request has permission to view the obj
model instance. If obj
is None, it should return True if the request has permission to view
any object of the given type.
View Source
def has_view_permission(self, request, obj=None):
"""
Return True if the given request has permission to view the given
Django model instance. The default implementation doesn't examine the
`obj` parameter.
If overridden by the user in subclasses, it should return True if the
given request has permission to view the `obj` model instance. If `obj`
is None, it should return True if the request has permission to view
any object of the given type.
"""
opts = self.opts
codename_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)
) or request.user.has_perm("%s.%s" % (opts.app_label, codename_change))
history_view¶
The 'history' admin view for this model.
View Source
log_addition¶
Log that an object has been successfully added.
The default implementation creates an admin LogEntry object.
View Source
def log_addition(self, request, obj, message):
"""
Log that an object has been successfully added.
The default implementation creates an admin LogEntry object.
"""
from django.contrib.admin.models import ADDITION, LogEntry
return LogEntry.objects.log_action(
user_id=request.user.pk,
content_type_id=get_content_type_for_model(obj).pk,
object_id=obj.pk,
object_repr=str(obj),
action_flag=ADDITION,
change_message=message,
)
log_change¶
Log that an object has been successfully changed.
The default implementation creates an admin LogEntry object.
View Source
def log_change(self, request, obj, message):
"""
Log that an object has been successfully changed.
The default implementation creates an admin LogEntry object.
"""
from django.contrib.admin.models import CHANGE, LogEntry
return LogEntry.objects.log_action(
user_id=request.user.pk,
content_type_id=get_content_type_for_model(obj).pk,
object_id=obj.pk,
object_repr=str(obj),
action_flag=CHANGE,
change_message=message,
)
log_deletion¶
Log that an object will be deleted. Note that this method must be
called before the deletion.
The default implementation creates an admin LogEntry object.
View Source
def log_deletion(self, request, obj, object_repr):
"""
Log that an object will be deleted. Note that this method must be
called before the deletion.
The default implementation creates an admin LogEntry object.
"""
from django.contrib.admin.models import DELETION, LogEntry
return LogEntry.objects.log_action(
user_id=request.user.pk,
content_type_id=get_content_type_for_model(obj).pk,
object_id=obj.pk,
object_repr=object_repr,
action_flag=DELETION,
)
lookup_allowed¶
View Source
def lookup_allowed(self, lookup, value):
from django.contrib.admin.filters import SimpleListFilter
model = 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 True
relation_parts = []
prev_field = None
for 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
and field 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.
break
prev_field = field
model = field.get_path_info()[-1].to_opts.model
if len(relation_parts) <= 1:
# Either a local field filter, or no fields at all.
return True
valid_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)
message_user¶
Send a message to the user. The default implementation
posts a message using the django.contrib.messages backend.
Exposes almost the same API as messages.add_message(), but accepts the
positional arguments in a different order to maintain backwards
compatibility. For convenience, it accepts the level
argument as
a string rather than the usual level number.
View Source
def message_user(
self, request, message, level=messages.INFO, extra_tags="", fail_silently=False
):
"""
Send a message to the user. The default implementation
posts a message using the django.contrib.messages backend.
Exposes almost the same API as messages.add_message(), but accepts the
positional arguments in a different order to maintain backwards
compatibility. For convenience, it accepts the `level` argument as
a string rather than the usual level number.
"""
if not isinstance(level, int):
# attempt to get the level if passed a string
try:
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
)
render_change_form¶
View Source
render_delete_form¶
View Source
def render_delete_form(self, request, context):
opts = self.model._meta
app_label = opts.app_label
request.current_app = self.admin_site.name
context.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,
)
response_action¶
Handle an admin action. This is called if a request is POSTed to the
changelist; it returns an HttpResponse if the action was handled, and None otherwise.
View Source
def response_action(self, request, queryset):
"""
Handle an admin action. This is called if a request is POSTed to the
changelist; it returns an HttpResponse if the action was handled, and
None 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 pushed
try:
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 here
pass
action_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 happen
msg = _(
"Items must be selected in order to perform "
"actions on them. No items have been changed."
)
self.message_user(request, msg, messages.WARNING)
return None
if not select_across:
# Perform the action only on the selected objects
queryset = 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 response
else:
return HttpResponseRedirect(request.get_full_path())
else:
msg = _("No action selected.")
self.message_user(request, msg, messages.WARNING)
return None
response_add¶
Determine the HttpResponse for the add_view stage.
View Source
def response_add(self, request, obj, post_url_continue=None):
"""
Determine the HttpResponse for the add_view stage.
"""
opts = obj._meta
preserved_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.attname
value = 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
and self.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_url
post_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.path
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 added successfully."), **msg_dict
)
self.message_user(request, msg, messages.SUCCESS)
return self.response_post_save_add(request, obj)
response_change¶
Determine the HttpResponse for the change_view stage.
View Source
def response_change(self, request, obj):
"""
Determine the HttpResponse for the change_view stage.
"""
if IS_POPUP_VAR in request.POST:
opts = obj._meta
to_field = request.POST.get(TO_FIELD_VAR)
attr = str(to_field) if to_field else opts.pk.attname
value = 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._meta
preserved_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.path
redirect_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)
response_delete¶
Determine the HttpResponse for the delete_view stage.
View Source
def response_delete(self, request, obj_display, obj_id):
"""
Determine the HttpResponse for the delete_view stage.
"""
opts = self.model._meta
if 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)
response_post_save_add¶
Figure out where to redirect after the 'Save' button has been pressed
when adding a new object.
View Source
response_post_save_change¶
Figure out where to redirect after the 'Save' button has been pressed
when editing an existing object.
View Source
save_form¶
Given a ModelForm return an unsaved instance. change
is True if
the object is being changed, and False if it's being added.
View Source
save_formset¶
Given an inline formset save it to the database.
View Source
save_model¶
Given a model instance save it to the database.
View Source
save_related¶
Given the HttpRequest
, the parent ModelForm
instance, the
list of inline formsets and a boolean value based on whether the parent is being added or changed, save the related objects to the database. Note that at this point save_form() and save_model() have already been called.
View Source
def save_related(self, request, form, formsets, change):
"""
Given the ``HttpRequest``, the parent ``ModelForm`` instance, the
list of inline formsets and a boolean value based on whether the
parent is being added or changed, save the related objects to the
database. Note that at this point save_form() and save_model() have
already been called.
"""
form.save_m2m()
for formset in formsets:
self.save_formset(request, form, formset, change=change)
to_field_allowed¶
Return True if the model associated with this admin should be
allowed to be referenced by the specified field.
View Source
def to_field_allowed(self, request, to_field):
"""
Return True if the model associated with this admin should be
allowed to be referenced by the specified field.
"""
opts = self.model._meta
try:
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_model
remote_field = related_object.field.remote_field
if (
any(issubclass(model, related_model) for model in registered_models)
and hasattr(remote_field, "get_related_field")
and remote_field.get_related_field() == field
):
return True
return False
ModelParentAdmin¶
Admin interface for the parent Model class.
This includes support for GlobalModel
as well as LocalModel
.
View Source
Ancestors (in MRO)¶
- polymorphic.admin.parentadmin.PolymorphicParentModelAdmin
- django.contrib.admin.options.ModelAdmin
- django.contrib.admin.options.BaseModelAdmin
Class variables¶
Instance variables¶
Methods¶
action_checkbox¶
A list_display column containing a checkbox widget.
View Source
add_type_view¶
Display a choice form to select which page type to add.
View Source
def add_type_view(self, request, form_url=""):
"""
Display a choice form to select which page type to add.
"""
if not self.has_add_permission(request):
raise PermissionDenied
extra_qs = ""
if request.META["QUERY_STRING"]:
# QUERY_STRING is bytes in Python 3, using force_str() to decode it as string.
# See QueryDict how Django deals with that.
extra_qs = "&{}".format(force_str(request.META["QUERY_STRING"]))
choices = self.get_child_type_choices(request, "add")
if len(choices) == 0:
raise PermissionDenied
if len(choices) == 1:
return HttpResponseRedirect(f"?ct_id={choices[0][0]}{extra_qs}")
# Create form
form = self.add_type_form(
data=request.POST if request.method == "POST" else None,
initial={"ct_id": choices[0][0]},
)
form.fields["ct_id"].choices = choices
if form.is_valid():
return HttpResponseRedirect("?ct_id={}{}".format(form.cleaned_data["ct_id"], extra_qs))
# Wrap in all admin layout
fieldsets = ((None, {"fields": ("ct_id",)}),)
adminForm = AdminForm(form, fieldsets, {}, model_admin=self)
media = self.media + adminForm.media
opts = self.model._meta
context = {
"title": _("Add %s") % force_str(opts.verbose_name),
"adminform": adminForm,
"is_popup": ("_popup" in request.POST or "_popup" in request.GET),
"media": mark_safe(media),
"errors": AdminErrorList(form, ()),
"app_label": opts.app_label,
}
return self.render_add_type_form(request, context, form_url)
add_view¶
Redirect the add view to the real admin.
View Source
def add_view(self, request, form_url="", extra_context=None):
"""Redirect the add view to the real admin."""
ct_id = int(request.GET.get("ct_id", 0))
if not ct_id:
# Display choices
return self.add_type_view(request)
else:
real_admin = self._get_real_admin_by_ct(ct_id)
# rebuild form_url, otherwise libraries below will override it.
form_url = add_preserved_filters(
{
"preserved_filters": urlencode({"ct_id": ct_id}),
"opts": self.model._meta,
},
form_url,
)
return real_admin.add_view(request, form_url, extra_context)
change_view¶
Redirect the change view to the real admin.
View Source
changeform_view¶
View Source
def changeform_view(self, request, object_id=None, *args, **kwargs):
# The `changeform_view` is available as of Django 1.7, combining the add_view and change_view.
# As it's directly called by django-reversion, this method is also overwritten to make sure it
# also redirects to the child admin.
if object_id:
real_admin = self._get_real_admin(object_id)
return real_admin.changeform_view(request, object_id, *args, **kwargs)
else:
# Add view. As it should already be handled via `add_view`, this means something custom is done here!
return super().changeform_view(request, object_id, *args, **kwargs)
changelist_view¶
The 'change list' admin view for this model.
View Source
@csrf_protect_m
def changelist_view(self, request, extra_context=None):
"""
The 'change list' admin view for this model.
"""
from django.contrib.admin.views.main import ERROR_FLAG
opts = self.model._meta
app_label = opts.app_label
if not self.has_view_or_change_permission(request):
raise PermissionDenied
try:
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 = False
selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME)
actions = self.get_actions(request)
# Actions with no confirmation
if (
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 response
else:
action_failed = True
else:
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 confirmation
if (
actions
and request.method == "POST"
and helpers.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 response
else:
action_failed = True
if 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 PermissionDenied
FormSet = 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 = 0
for 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 += 1
if 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.media
else:
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.media
else:
action_form = None
selection_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,
"subtitle": None,
"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.name
return 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,
)
check¶
construct_change_message¶
Construct a JSON structure describing changes from a changed object.
View Source
delete_model¶
Given a model instance delete it from the database.
View Source
delete_queryset¶
Given a queryset, delete it from the database.
View Source
delete_view¶
Redirect the delete view to the real admin.
View Source
formfield_for_choice_field¶
Get a form Field for a database Field that has declared choices.
View Source
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 RadioSelect
if 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)
formfield_for_dbfield¶
Hook for specifying the form Field instance for a given database Field
instance.
If kwargs are given, they're passed to the form Field's constructor.
View Source
def formfield_for_dbfield(self, db_field, request, **kwargs):
"""
Hook for specifying the form Field instance for a given database Field
instance.
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 ManyToManyFields
if 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)
formfield_for_foreignkey¶
Get a form Field for a ForeignKey.
View Source
def 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, 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 None
if "queryset" not in kwargs:
queryset = self.get_field_queryset(db, db_field, request)
if queryset is not None:
kwargs["queryset"] = queryset
return db_field.formfield(**kwargs)
formfield_for_manytomany¶
Get a form Field for a ManyToManyField.
View Source
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 None
db = 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,
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"] = queryset
form_field = db_field.formfield(**kwargs)
if isinstance(form_field.widget, SelectMultiple) and not 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_text
form_field.help_text = (
format_lazy("{} {}", help_text, msg) if help_text else msg
)
return form_field
get_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).
View Source
def 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 = action
action = 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 site
else:
try:
func = self.admin_site.get_action(action)
except KeyError:
return None
description = self._get_action_description(func, action)
return func, action, description
get_action_choices¶
Return a list of choices for use in a form object. Each choice is a
tuple (name, description).
View Source
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 a
tuple (name, description).
"""
choices = [] + default_choices
for func, name, description in self.get_actions(request).values():
choice = (name, description % model_format_dict(self.opts))
choices.append(choice)
return choices
get_actions¶
Return a dictionary mapping the names of all actions for this
ModelAdmin to a tuple of (callable, name, description) for each action.
View Source
def get_actions(self, request):
"""
Return a dictionary mapping the names of all actions for this
ModelAdmin 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}
get_autocomplete_fields¶
Return a list of ForeignKey and/or ManyToMany fields which should use
an autocomplete widget.
View Source
get_changeform_initial_data¶
Get the initial form data from the request's GET params.
View Source
def 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 initial
get_changelist¶
Return the ChangeList class for use on the changelist page.
View Source
get_changelist_form¶
Return a Form class for use in the Formset on the changelist page.
View Source
def 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_FIELDS
return modelform_factory(self.model, **defaults)
get_changelist_formset¶
Return a FormSet class for use on the changelist page if list_editable
is used.
View Source
def get_changelist_formset(self, request, **kwargs):
"""
Return a FormSet class for use on the changelist page if list_editable
is 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,
)
get_changelist_instance¶
Return a ChangeList
instance based on request
. May raise
IncorrectLookupParameters
.
View Source
def 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,
self.search_help_text,
)
get_child_models¶
Return the derived model classes which this admin should handle.
This should return a list of tuples, exactly like :attr:child_models
is.
The model classes can be retrieved as base_model.__subclasses__()
,
a setting in a config file, or a query of a plugin registration system at your option
View Source
def get_child_models(self):
"""
Return the derived model classes which this admin should handle.
This should return a list of tuples, exactly like :attr:`child_models` is.
The model classes can be retrieved as ``base_model.__subclasses__()``,
a setting in a config file, or a query of a plugin registration system at your option
"""
if self.child_models is None:
raise NotImplementedError("Implement get_child_models() or child_models")
return self.child_models
get_child_type_choices¶
Return a list of polymorphic types for which the user has the permission to perform the given action.
View Source
def get_child_type_choices(self, request, action):
"""
Return a list of polymorphic types for which the user has the permission to perform the given action.
"""
self._lazy_setup()
choices = []
content_types = ContentType.objects.get_for_models(
*self.get_child_models(), for_concrete_models=False
)
for model, ct in content_types.items():
perm_function_name = f"has_{action}_permission"
model_admin = self._get_real_admin_by_model(model)
perm_function = getattr(model_admin, perm_function_name)
if not perm_function(request):
continue
choices.append((ct.id, model._meta.verbose_name))
return choices
get_deleted_objects¶
Hook for customizing the delete process for the delete view and the
"delete selected" action.
View Source
get_empty_value_display¶
Return the empty_value_display set on ModelAdmin or AdminSite.
View Source
get_exclude¶
Hook for specifying exclude.
View Source
get_field_queryset¶
If the ModelAdmin specifies ordering, the queryset should respect that
ordering. Otherwise don't specify the queryset, let the field decide (return None in that case).
View Source
def get_field_queryset(self, db, db_field, request):
"""
If the ModelAdmin specifies ordering, the queryset should respect that
ordering. 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 None
get_fields¶
Hook for specifying fields.
View Source
get_fieldsets¶
Hook for specifying fieldsets.
View Source
get_form¶
Return a Form class for use in the admin add view. This is used by
add_view and change_view.
View Source
def get_form(self, request, obj=None, change=False, **kwargs):
"""
Return a Form class for use in the admin add view. This is used by
add_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_factory
exclude = 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_FIELDS
try:
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__)
)
get_formset_kwargs¶
View Source
def get_formset_kwargs(self, request, obj, inline, 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,
}
)
return formset_params
get_formsets_with_inlines¶
Yield formsets and the corresponding inlines.
View Source
get_inline_formsets¶
View Source
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 = False
formset.extra = formset.max_num = 0
has_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_formsets
get_inline_instances¶
View Source
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)
or inline.has_add_permission(request, obj)
or inline.has_delete_permission(request, obj)
):
continue
if not inline.has_add_permission(request, obj):
inline.max_num = 0
inline_instances.append(inline)
return inline_instances
get_inlines¶
Hook for specifying custom inlines.
View Source
get_list_display¶
Return a sequence containing the fields to be displayed on the
changelist.
View Source
get_list_display_links¶
Return a sequence containing the fields to be displayed as links
on the changelist. The list_display parameter is the list of fields returned by get_list_display().
View Source
def get_list_display_links(self, request, list_display):
"""
Return a sequence containing the fields to be displayed as links
on the changelist. The list_display parameter is the list of fields
returned by get_list_display().
"""
if (
self.list_display_links
or self.list_display_links is None
or not list_display
):
return self.list_display_links
else:
# Use only the first item in list_display as link
return list(list_display)[:1]
get_list_filter¶
Return a sequence containing the fields to be displayed as filters in
the right sidebar of the changelist page.
View Source
get_list_select_related¶
Return a list of fields to add to the select_related() part of the
changelist items query.
View Source
get_model_perms¶
Return a dict of all perms for this model. This dict has the keys
add
, change
, delete
, and view
mapping to the True/False
for each of those actions.
View Source
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/False
for 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),
}
get_object¶
Return an instance matching the field and value provided, the primary
key is used if no field is provided. Return None
if no match is
found or the object_id fails validation.
View Source
def get_object(self, request, object_id, from_field=None):
"""
Return an instance matching the field and value provided, the primary
key is used if no field is provided. Return ``None`` if no match is
found or the object_id fails validation.
"""
queryset = self.get_queryset(request)
model = queryset.model
field = (
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 None
get_ordering¶
Hook for specifying field ordering.
View Source
get_paginator¶
View Source
get_prepopulated_fields¶
Hook for specifying custom prepopulated fields.
View Source
get_preserved_filters¶
Return the preserved filters querystring.
View Source
def get_preserved_filters(self, request):
if "_changelist_filters" in request.GET:
request.GET = request.GET.copy()
filters = request.GET.get("_changelist_filters")
f = filters.split("&")
for x in f:
c = x.split("=")
request.GET[c[0]] = c[1]
del request.GET["_changelist_filters"]
return super().get_preserved_filters(request)
get_queryset¶
Return a QuerySet of all model instances that can be edited by the
admin site. This is used by changelist_view.
View Source
get_readonly_fields¶
Hook for specifying custom readonly fields.
View Source
get_search_fields¶
Return a sequence containing the fields to be searched whenever
somebody submits a search query.
View Source
get_search_results¶
Return a tuple containing a queryset to implement the search
and a boolean indicating if the results may contain duplicates.
View Source
def get_search_results(self, request, queryset, search_term):
"""
Return a tuple containing a queryset to implement the search
and 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._meta
lookup_fields = field_name.split(LOOKUP_SEP)
# Go through the fields, following all relations.
prev_field = None
for path_part in lookup_fields:
if path_part == "pk":
path_part = opts.pk.name
try:
field = opts.get_field(path_part)
except FieldDoesNotExist:
# Use valid query lookups.
if prev_field and prev_field.get_lookup(path_part):
return field_name
else:
prev_field = field
if 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_name
may_have_duplicates = False
search_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 smart_split(search_term):
if bit.startswith(('"', "'")) and bit[0] == bit[-1]:
bit = unescape_string_literal(bit)
or_queries = models.Q(
*((orm_lookup, bit) for orm_lookup in orm_lookups),
_connector=models.Q.OR,
)
queryset = queryset.filter(or_queries)
may_have_duplicates |= any(
lookup_spawns_duplicates(self.opts, search_spec)
for search_spec in orm_lookups
)
return queryset, may_have_duplicates
get_sortable_by¶
Hook for specifying which fields can be sorted in the changelist.
View Source
get_urls¶
Expose the custom URLs for the subclasses and the URL resolver.
View Source
get_view_on_site_url¶
View Source
def get_view_on_site_url(self, obj=None):
if obj is None or not self.view_on_site:
return None
if callable(self.view_on_site):
return self.view_on_site(obj)
elif hasattr(obj, "get_absolute_url"):
# use the ContentType lookup if view_on_site is True
return reverse(
"admin:view_on_site",
kwargs={
"content_type_id": get_content_type_for_model(obj).pk,
"object_id": obj.pk,
},
)
has_add_permission¶
Return True if the given request has permission to add an object.
Can be overridden by the user in subclasses.
View Source
has_change_permission¶
Return True if the given request has permission to change the given
Django model instance, the default implementation doesn't examine the
obj
parameter.
Can be overridden by the user in subclasses. In such case it should
return True if the given request has permission to change the obj
model instance. If obj
is None, this should return True if the given
request has permission to change any object of the given type.
View Source
def has_change_permission(self, request, obj=None):
"""
Return True if the given request has permission to change the given
Django model instance, the default implementation doesn't examine the
`obj` parameter.
Can be overridden by the user in subclasses. In such case it should
return True if the given request has permission to change the `obj`
model instance. If `obj` is None, this should return True if the given
request has permission to change *any* object of the given type.
"""
opts = self.opts
codename = get_permission_codename("change", opts)
return request.user.has_perm("%s.%s" % (opts.app_label, codename))
has_delete_permission¶
Return True if the given request has permission to change the given
Django model instance, the default implementation doesn't examine the
obj
parameter.
Can be overridden by the user in subclasses. In such case it should
return True if the given request has permission to delete the obj
model instance. If obj
is None, this should return True if the given
request has permission to delete any object of the given type.
View Source
def has_delete_permission(self, request, obj=None):
"""
Return True if the given request has permission to change the given
Django model instance, the default implementation doesn't examine the
`obj` parameter.
Can be overridden by the user in subclasses. In such case it should
return True if the given request has permission to delete the `obj`
model instance. If `obj` is None, this should return True if the given
request has permission to delete *any* object of the given type.
"""
opts = self.opts
codename = get_permission_codename("delete", opts)
return request.user.has_perm("%s.%s" % (opts.app_label, codename))
has_module_permission¶
Return True if the given request has any permission in the given
app label.
Can be overridden by the user in subclasses. In such case it should
return True if the given request has permission to view the module on
the admin index page and access the module's index page. Overriding it
does not restrict access to the add, change or delete views. Use
ModelAdmin.has_(add|change|delete)_permission
for that.
View Source
def has_module_permission(self, request):
"""
Return True if the given request has any permission in the given
app label.
Can be overridden by the user in subclasses. In such case it should
return True if the given request has permission to view the module on
the admin index page and access the module's index page. Overriding it
does 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)
has_view_or_change_permission¶
View Source
has_view_permission¶
Return True if the given request has permission to view the given
Django model instance. The default implementation doesn't examine the
obj
parameter.
If overridden by the user in subclasses, it should return True if the
given request has permission to view the obj
model instance. If obj
is None, it should return True if the request has permission to view
any object of the given type.
View Source
def has_view_permission(self, request, obj=None):
"""
Return True if the given request has permission to view the given
Django model instance. The default implementation doesn't examine the
`obj` parameter.
If overridden by the user in subclasses, it should return True if the
given request has permission to view the `obj` model instance. If `obj`
is None, it should return True if the request has permission to view
any object of the given type.
"""
opts = self.opts
codename_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)
) or request.user.has_perm("%s.%s" % (opts.app_label, codename_change))
history_view¶
Redirect the history view to the real admin.
View Source
log_addition¶
Log that an object has been successfully added.
The default implementation creates an admin LogEntry object.
View Source
def log_addition(self, request, obj, message):
"""
Log that an object has been successfully added.
The default implementation creates an admin LogEntry object.
"""
from django.contrib.admin.models import ADDITION, LogEntry
return LogEntry.objects.log_action(
user_id=request.user.pk,
content_type_id=get_content_type_for_model(obj).pk,
object_id=obj.pk,
object_repr=str(obj),
action_flag=ADDITION,
change_message=message,
)
log_change¶
Log that an object has been successfully changed.
The default implementation creates an admin LogEntry object.
View Source
def log_change(self, request, obj, message):
"""
Log that an object has been successfully changed.
The default implementation creates an admin LogEntry object.
"""
from django.contrib.admin.models import CHANGE, LogEntry
return LogEntry.objects.log_action(
user_id=request.user.pk,
content_type_id=get_content_type_for_model(obj).pk,
object_id=obj.pk,
object_repr=str(obj),
action_flag=CHANGE,
change_message=message,
)
log_deletion¶
Log that an object will be deleted. Note that this method must be
called before the deletion.
The default implementation creates an admin LogEntry object.
View Source
def log_deletion(self, request, obj, object_repr):
"""
Log that an object will be deleted. Note that this method must be
called before the deletion.
The default implementation creates an admin LogEntry object.
"""
from django.contrib.admin.models import DELETION, LogEntry
return LogEntry.objects.log_action(
user_id=request.user.pk,
content_type_id=get_content_type_for_model(obj).pk,
object_id=obj.pk,
object_repr=object_repr,
action_flag=DELETION,
)
lookup_allowed¶
View Source
def lookup_allowed(self, lookup, value):
from django.contrib.admin.filters import SimpleListFilter
model = 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 True
relation_parts = []
prev_field = None
for 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
and field 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.
break
prev_field = field
model = field.get_path_info()[-1].to_opts.model
if len(relation_parts) <= 1:
# Either a local field filter, or no fields at all.
return True
valid_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)
message_user¶
Send a message to the user. The default implementation
posts a message using the django.contrib.messages backend.
Exposes almost the same API as messages.add_message(), but accepts the
positional arguments in a different order to maintain backwards
compatibility. For convenience, it accepts the level
argument as
a string rather than the usual level number.
View Source
def message_user(
self, request, message, level=messages.INFO, extra_tags="", fail_silently=False
):
"""
Send a message to the user. The default implementation
posts a message using the django.contrib.messages backend.
Exposes almost the same API as messages.add_message(), but accepts the
positional arguments in a different order to maintain backwards
compatibility. For convenience, it accepts the `level` argument as
a string rather than the usual level number.
"""
if not isinstance(level, int):
# attempt to get the level if passed a string
try:
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
)
register_child¶
Register a model with admin to display.
View Source
def register_child(self, model, model_admin):
"""
Register a model with admin to display.
"""
# After the get_urls() is called, the URLs of the child model can't be exposed anymore to the Django URLconf,
# which also means that a "Save and continue editing" button won't work.
if self._is_setup:
raise RegistrationClosed("The admin model can't be registered anymore at this point.")
if not issubclass(model, self.base_model):
raise TypeError(
"{} should be a subclass of {}".format(model.__name__, self.base_model.__name__)
)
if not issubclass(model_admin, admin.ModelAdmin):
raise TypeError(
"{} should be a subclass of {}".format(
model_admin.__name__, admin.ModelAdmin.__name__
)
)
self._child_admin_site.register(model, model_admin)
render_add_type_form¶
Render the page type choice form.
View Source
def render_add_type_form(self, request, context, form_url=""):
"""
Render the page type choice form.
"""
opts = self.model._meta
app_label = opts.app_label
context.update(
{
"has_change_permission": self.has_change_permission(request),
"form_url": mark_safe(form_url),
"opts": opts,
"add": True,
"save_on_top": self.save_on_top,
}
)
templates = self.add_type_template or [
f"admin/{app_label}/{opts.object_name.lower()}/add_type_form.html",
"admin/%s/add_type_form.html" % app_label,
"admin/polymorphic/add_type_form.html", # added default here
"admin/add_type_form.html",
]
request.current_app = self.admin_site.name
return TemplateResponse(request, templates, context)
render_change_form¶
View Source
def render_change_form(
self, request, context, add=False, change=False, form_url="", obj=None
):
opts = self.model._meta
app_label = opts.app_label
preserved_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 = False
for 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 = True
break
context.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_template
else:
form_template = self.change_form_template
request.current_app = self.admin_site.name
return 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,
)
render_delete_form¶
View Source
def render_delete_form(self, request, context):
opts = self.model._meta
app_label = opts.app_label
request.current_app = self.admin_site.name
context.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,
)
response_action¶
Handle an admin action. This is called if a request is POSTed to the
changelist; it returns an HttpResponse if the action was handled, and None otherwise.
View Source
def response_action(self, request, queryset):
"""
Handle an admin action. This is called if a request is POSTed to the
changelist; it returns an HttpResponse if the action was handled, and
None 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 pushed
try:
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 here
pass
action_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 happen
msg = _(
"Items must be selected in order to perform "
"actions on them. No items have been changed."
)
self.message_user(request, msg, messages.WARNING)
return None
if not select_across:
# Perform the action only on the selected objects
queryset = 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 response
else:
return HttpResponseRedirect(request.get_full_path())
else:
msg = _("No action selected.")
self.message_user(request, msg, messages.WARNING)
return None
response_add¶
Determine the HttpResponse for the add_view stage.
View Source
def response_add(self, request, obj, post_url_continue=None):
"""
Determine the HttpResponse for the add_view stage.
"""
opts = obj._meta
preserved_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.attname
value = 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
and self.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_url
post_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.path
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 added successfully."), **msg_dict
)
self.message_user(request, msg, messages.SUCCESS)
return self.response_post_save_add(request, obj)
response_change¶
Determine the HttpResponse for the change_view stage.
View Source
def response_change(self, request, obj):
"""
Determine the HttpResponse for the change_view stage.
"""
if IS_POPUP_VAR in request.POST:
opts = obj._meta
to_field = request.POST.get(TO_FIELD_VAR)
attr = str(to_field) if to_field else opts.pk.attname
value = 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._meta
preserved_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.path
redirect_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)
response_delete¶
Determine the HttpResponse for the delete_view stage.
View Source
def response_delete(self, request, obj_display, obj_id):
"""
Determine the HttpResponse for the delete_view stage.
"""
opts = self.model._meta
if 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)
response_post_save_add¶
Figure out where to redirect after the 'Save' button has been pressed
when adding a new object.
View Source
response_post_save_change¶
Figure out where to redirect after the 'Save' button has been pressed
when editing an existing object.
View Source
save_form¶
Given a ModelForm return an unsaved instance. change
is True if
the object is being changed, and False if it's being added.
View Source
save_formset¶
Given an inline formset save it to the database.
View Source
save_model¶
Given a model instance save it to the database.
View Source
save_related¶
Given the HttpRequest
, the parent ModelForm
instance, the
list of inline formsets and a boolean value based on whether the parent is being added or changed, save the related objects to the database. Note that at this point save_form() and save_model() have already been called.
View Source
def save_related(self, request, form, formsets, change):
"""
Given the ``HttpRequest``, the parent ``ModelForm`` instance, the
list of inline formsets and a boolean value based on whether the
parent is being added or changed, save the related objects to the
database. Note that at this point save_form() and save_model() have
already been called.
"""
form.save_m2m()
for formset in formsets:
self.save_formset(request, form, formset, change=change)
subclass_view¶
Forward any request to a custom view of the real admin.
View Source
def subclass_view(self, request, path):
"""
Forward any request to a custom view of the real admin.
"""
ct_id = int(request.GET.get("ct_id", 0))
if not ct_id:
# See if the path started with an ID.
try:
pos = path.find("/")
if pos == -1:
object_id = int(path)
else:
object_id = int(path[0:pos])
except ValueError:
raise Http404(
"No ct_id parameter, unable to find admin subclass for path '{}'.".format(path)
)
ct_id = self.model.objects.values_list("polymorphic_ctype_id", flat=True).get(
pk=object_id
)
real_admin = self._get_real_admin_by_ct(ct_id)
resolver = URLResolver("^", real_admin.urls)
resolvermatch = resolver.resolve(path) # May raise Resolver404
if not resolvermatch:
raise Http404(f"No match for path '{path}' in admin subclass.")
return resolvermatch.func(request, *resolvermatch.args, **resolvermatch.kwargs)
to_field_allowed¶
Return True if the model associated with this admin should be
allowed to be referenced by the specified field.
View Source
def to_field_allowed(self, request, to_field):
"""
Return True if the model associated with this admin should be
allowed to be referenced by the specified field.
"""
opts = self.model._meta
try:
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_model
remote_field = related_object.field.remote_field
if (
any(issubclass(model, related_model) for model in registered_models)
and hasattr(remote_field, "get_related_field")
and remote_field.get_related_field() == field
):
return True
return False