summaryrefslogtreecommitdiff
path: root/lib/python2.7/site-packages/django/contrib/admin/options.py
diff options
context:
space:
mode:
authorttt2017-05-13 00:29:47 +0530
committerttt2017-05-13 00:29:47 +0530
commitabf599be33b383a6a5baf9493093b2126a622ac8 (patch)
tree4c5ab6e0d935d5e65fabcf0258e4a00dd20a5afa /lib/python2.7/site-packages/django/contrib/admin/options.py
downloadSBHS-2018-Rpi-abf599be33b383a6a5baf9493093b2126a622ac8.tar.gz
SBHS-2018-Rpi-abf599be33b383a6a5baf9493093b2126a622ac8.tar.bz2
SBHS-2018-Rpi-abf599be33b383a6a5baf9493093b2126a622ac8.zip
added all server files
Diffstat (limited to 'lib/python2.7/site-packages/django/contrib/admin/options.py')
-rw-r--r--lib/python2.7/site-packages/django/contrib/admin/options.py1718
1 files changed, 1718 insertions, 0 deletions
diff --git a/lib/python2.7/site-packages/django/contrib/admin/options.py b/lib/python2.7/site-packages/django/contrib/admin/options.py
new file mode 100644
index 0000000..9dddcb0
--- /dev/null
+++ b/lib/python2.7/site-packages/django/contrib/admin/options.py
@@ -0,0 +1,1718 @@
+import copy
+import operator
+from functools import partial, reduce, update_wrapper
+
+from django import forms
+from django.conf import settings
+from django.contrib import messages
+from django.contrib.admin import widgets, helpers
+from django.contrib.admin.util import (unquote, flatten_fieldsets, get_deleted_objects,
+ model_format_dict, NestedObjects, lookup_needs_distinct)
+from django.contrib.admin import validation
+from django.contrib.admin.templatetags.admin_static import static
+from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
+from django.contrib.auth import get_permission_codename
+from django.contrib.contenttypes.models import ContentType
+from django.core.exceptions import PermissionDenied, ValidationError, FieldError
+from django.core.paginator import Paginator
+from django.core.urlresolvers import reverse
+from django.db import models, transaction, router
+from django.db.models.constants import LOOKUP_SEP
+from django.db.models.related import RelatedObject
+from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist
+from django.db.models.sql.constants import QUERY_TERMS
+from django.forms.formsets import all_valid, DELETION_FIELD_NAME
+from django.forms.models import (modelform_factory, modelformset_factory,
+ inlineformset_factory, BaseInlineFormSet, modelform_defines_fields)
+from django.http import Http404, HttpResponseRedirect
+from django.http.response import HttpResponseBase
+from django.shortcuts import get_object_or_404
+from django.template.response import SimpleTemplateResponse, TemplateResponse
+from django.utils.decorators import method_decorator
+from django.utils.datastructures import SortedDict
+from django.utils.html import escape, escapejs
+from django.utils.safestring import mark_safe
+from django.utils import six
+from django.utils.deprecation import RenameMethodsBase
+from django.utils.http import urlencode
+from django.utils.text import capfirst, get_text_list
+from django.utils.translation import ugettext as _
+from django.utils.translation import ungettext
+from django.utils.encoding import force_text
+from django.views.decorators.csrf import csrf_protect
+
+
+IS_POPUP_VAR = '_popup'
+
+HORIZONTAL, VERTICAL = 1, 2
+# returns the <ul> class for a given radio_admin field
+get_ul_class = lambda x: 'radiolist%s' % (' inline' if x == HORIZONTAL else '')
+
+
+class IncorrectLookupParameters(Exception):
+ pass
+
+# Defaults for formfield_overrides. ModelAdmin subclasses can change this
+# by adding to ModelAdmin.formfield_overrides.
+
+FORMFIELD_FOR_DBFIELD_DEFAULTS = {
+ models.DateTimeField: {
+ 'form_class': forms.SplitDateTimeField,
+ 'widget': widgets.AdminSplitDateTime
+ },
+ models.DateField: {'widget': widgets.AdminDateWidget},
+ models.TimeField: {'widget': widgets.AdminTimeWidget},
+ models.TextField: {'widget': widgets.AdminTextareaWidget},
+ models.URLField: {'widget': widgets.AdminURLFieldWidget},
+ models.IntegerField: {'widget': widgets.AdminIntegerFieldWidget},
+ models.BigIntegerField: {'widget': widgets.AdminBigIntegerFieldWidget},
+ models.CharField: {'widget': widgets.AdminTextInputWidget},
+ models.ImageField: {'widget': widgets.AdminFileWidget},
+ models.FileField: {'widget': widgets.AdminFileWidget},
+ models.EmailField: {'widget': widgets.AdminEmailInputWidget},
+}
+
+csrf_protect_m = method_decorator(csrf_protect)
+
+
+class RenameBaseModelAdminMethods(forms.MediaDefiningClass, RenameMethodsBase):
+ renamed_methods = (
+ ('queryset', 'get_queryset', PendingDeprecationWarning),
+ )
+
+
+class BaseModelAdmin(six.with_metaclass(RenameBaseModelAdminMethods)):
+ """Functionality common to both ModelAdmin and InlineAdmin."""
+
+ raw_id_fields = ()
+ fields = None
+ exclude = None
+ fieldsets = None
+ form = forms.ModelForm
+ filter_vertical = ()
+ filter_horizontal = ()
+ radio_fields = {}
+ prepopulated_fields = {}
+ formfield_overrides = {}
+ readonly_fields = ()
+ ordering = None
+
+ # validation
+ validator_class = validation.BaseValidator
+
+ @classmethod
+ def validate(cls, model):
+ validator = cls.validator_class()
+ validator.validate(cls, model)
+
+ def __init__(self):
+ overrides = FORMFIELD_FOR_DBFIELD_DEFAULTS.copy()
+ overrides.update(self.formfield_overrides)
+ self.formfield_overrides = overrides
+
+ def formfield_for_dbfield(self, db_field, **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.
+ """
+ request = kwargs.pop("request", None)
+
+ # 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 = dict(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.rel.to)
+ can_add_related = bool(related_modeladmin and
+ related_modeladmin.has_add_permission(request))
+ formfield.widget = widgets.RelatedFieldWidgetWrapper(
+ formfield.widget, db_field.rel, self.admin_site,
+ can_add_related=can_add_related)
+
+ 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 = dict(copy.deepcopy(self.formfield_overrides[klass]), **kwargs)
+ return db_field.formfield(**kwargs)
+
+ # For any other type of field, just call its formfield() method.
+ return db_field.formfield(**kwargs)
+
+ def formfield_for_choice_field(self, db_field, request=None, **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)
+
+ 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
+ (returns None in that case).
+ """
+ related_admin = self.admin_site._registry.get(db_field.rel.to, None)
+ if related_admin is not None:
+ ordering = related_admin.get_ordering(request)
+ if ordering is not None and ordering != ():
+ return db_field.rel.to._default_manager.using(db).order_by(*ordering).complex_filter(db_field.rel.limit_choices_to)
+ return None
+
+ def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
+ """
+ Get a form Field for a ForeignKey.
+ """
+ db = kwargs.get('using')
+ if db_field.name in self.raw_id_fields:
+ kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel,
+ 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 not 'queryset' in kwargs:
+ queryset = self.get_field_queryset(db, db_field, request)
+ if queryset is not None:
+ kwargs['queryset'] = queryset
+
+ return db_field.formfield(**kwargs)
+
+ def formfield_for_manytomany(self, db_field, request=None, **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.rel.through._meta.auto_created:
+ return None
+ db = kwargs.get('using')
+
+ if db_field.name in self.raw_id_fields:
+ kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel,
+ self.admin_site, using=db)
+ kwargs['help_text'] = ''
+ elif db_field.name in (list(self.filter_vertical) + list(self.filter_horizontal)):
+ kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, (db_field.name in self.filter_vertical))
+
+ if not 'queryset' in kwargs:
+ queryset = self.get_field_queryset(db, db_field, request)
+ if queryset is not None:
+ kwargs['queryset'] = queryset
+
+ return db_field.formfield(**kwargs)
+
+ def _declared_fieldsets(self):
+ if self.fieldsets:
+ return self.fieldsets
+ elif self.fields:
+ return [(None, {'fields': self.fields})]
+ return None
+ declared_fieldsets = property(_declared_fieldsets)
+
+ def get_ordering(self, request):
+ """
+ Hook for specifying field ordering.
+ """
+ return self.ordering or () # otherwise we might try to *None, which is bad ;)
+
+ def get_readonly_fields(self, request, obj=None):
+ """
+ Hook for specifying custom readonly fields.
+ """
+ return self.readonly_fields
+
+ def get_prepopulated_fields(self, request, obj=None):
+ """
+ Hook for specifying custom prepopulated fields.
+ """
+ return self.prepopulated_fields
+
+ def get_queryset(self, request):
+ """
+ Returns 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
+
+ def lookup_allowed(self, lookup, value):
+ 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 l in model._meta.related_fkey_lookups:
+ for k, v in widgets.url_params_from_lookup_dict(l).items():
+ if k == lookup and v == value:
+ return True
+
+ parts = lookup.split(LOOKUP_SEP)
+
+ # Last term in lookup is a query term (__exact, __startswith etc)
+ # This term can be ignored.
+ if len(parts) > 1 and parts[-1] in QUERY_TERMS:
+ parts.pop()
+
+ # Special case -- foo__id__exact and foo__id queries are implied
+ # if foo has been specifically included in the lookup list; so
+ # drop __id if it is the last part. However, first we need to find
+ # the pk attribute name.
+ rel_name = None
+ for part in parts[:-1]:
+ try:
+ field, _, _, _ = model._meta.get_field_by_name(part)
+ except FieldDoesNotExist:
+ # Lookups on non-existent fields are ok, since they're ignored
+ # later.
+ return True
+ if hasattr(field, 'rel'):
+ if field.rel is None:
+ # This property or relation doesn't exist, but it's allowed
+ # since it's ignored in ChangeList.get_filters().
+ return True
+ model = field.rel.to
+ rel_name = field.rel.get_related_field().name
+ elif isinstance(field, RelatedObject):
+ model = field.model
+ rel_name = model._meta.pk.name
+ else:
+ rel_name = None
+ if rel_name and len(parts) > 1 and parts[-1] == rel_name:
+ parts.pop()
+
+ if len(parts) == 1:
+ return True
+ clean_lookup = LOOKUP_SEP.join(parts)
+ return clean_lookup in self.list_filter or clean_lookup == self.date_hierarchy
+
+ def has_add_permission(self, request):
+ """
+ Returns True if the given request has permission to add an object.
+ Can be overridden by the user in subclasses.
+ """
+ opts = self.opts
+ codename = get_permission_codename('add', opts)
+ return request.user.has_perm("%s.%s" % (opts.app_label, codename))
+
+ def has_change_permission(self, request, obj=None):
+ """
+ Returns 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))
+
+ def has_delete_permission(self, request, obj=None):
+ """
+ Returns 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))
+
+
+class ModelAdmin(BaseModelAdmin):
+ "Encapsulates all admin options and functionality for a given model."
+
+ list_display = ('__str__',)
+ list_display_links = ()
+ list_filter = ()
+ list_select_related = False
+ list_per_page = 100
+ list_max_show_all = 200
+ list_editable = ()
+ search_fields = ()
+ date_hierarchy = None
+ save_as = False
+ save_on_top = False
+ paginator = Paginator
+ preserve_filters = True
+ inlines = []
+
+ # Custom templates (designed to be over-ridden in subclasses)
+ add_form_template = None
+ change_form_template = None
+ change_list_template = None
+ delete_confirmation_template = None
+ delete_selected_confirmation_template = None
+ object_history_template = None
+
+ # Actions
+ actions = []
+ action_form = helpers.ActionForm
+ actions_on_top = True
+ actions_on_bottom = False
+ actions_selection_counter = True
+
+ # validation
+ validator_class = validation.ModelAdminValidator
+
+ def __init__(self, model, admin_site):
+ self.model = model
+ self.opts = model._meta
+ self.admin_site = admin_site
+ super(ModelAdmin, self).__init__()
+
+ def get_inline_instances(self, request, obj=None):
+ inline_instances = []
+ for inline_class in self.inlines:
+ inline = inline_class(self.model, self.admin_site)
+ if request:
+ if not (inline.has_add_permission(request) or
+ inline.has_change_permission(request, obj) or
+ inline.has_delete_permission(request, obj)):
+ continue
+ if not inline.has_add_permission(request):
+ inline.max_num = 0
+ inline_instances.append(inline)
+
+ return inline_instances
+
+ def get_urls(self):
+ from django.conf.urls import patterns, url
+
+ def wrap(view):
+ def wrapper(*args, **kwargs):
+ return self.admin_site.admin_view(view)(*args, **kwargs)
+ return update_wrapper(wrapper, view)
+
+ info = self.model._meta.app_label, self.model._meta.model_name
+
+ urlpatterns = patterns('',
+ url(r'^$',
+ wrap(self.changelist_view),
+ name='%s_%s_changelist' % info),
+ url(r'^add/$',
+ wrap(self.add_view),
+ name='%s_%s_add' % info),
+ url(r'^(.+)/history/$',
+ wrap(self.history_view),
+ name='%s_%s_history' % info),
+ url(r'^(.+)/delete/$',
+ wrap(self.delete_view),
+ name='%s_%s_delete' % info),
+ url(r'^(.+)/$',
+ wrap(self.change_view),
+ name='%s_%s_change' % info),
+ )
+ return urlpatterns
+
+ def urls(self):
+ return self.get_urls()
+ urls = property(urls)
+
+ @property
+ def media(self):
+ extra = '' if settings.DEBUG else '.min'
+ js = [
+ 'core.js',
+ 'admin/RelatedObjectLookups.js',
+ 'jquery%s.js' % extra,
+ 'jquery.init.js'
+ ]
+ if self.actions is not None:
+ js.append('actions%s.js' % extra)
+ if self.prepopulated_fields:
+ js.extend(['urlify.js', 'prepopulate%s.js' % extra])
+ return forms.Media(js=[static('admin/js/%s' % url) for url in js])
+
+ def get_model_perms(self, request):
+ """
+ Returns a dict of all perms for this model. This dict has the keys
+ ``add``, ``change``, and ``delete`` 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),
+ }
+
+ def get_fieldsets(self, request, obj=None):
+ "Hook for specifying fieldsets for the add form."
+ if self.declared_fieldsets:
+ return self.declared_fieldsets
+ form = self.get_form(request, obj, fields=None)
+ fields = list(form.base_fields) + list(self.get_readonly_fields(request, obj))
+ return [(None, {'fields': fields})]
+
+ def get_form(self, request, obj=None, **kwargs):
+ """
+ Returns 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))
+ if self.exclude is None:
+ exclude = []
+ else:
+ exclude = list(self.exclude)
+ exclude.extend(self.get_readonly_fields(request, obj))
+ if self.exclude 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
+ defaults = {
+ "form": self.form,
+ "fields": fields,
+ "exclude": exclude,
+ "formfield_callback": partial(self.formfield_for_dbfield, request=request),
+ }
+ defaults.update(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__))
+
+ def get_changelist(self, request, **kwargs):
+ """
+ Returns the ChangeList class for use on the changelist page.
+ """
+ from django.contrib.admin.views.main import ChangeList
+ return ChangeList
+
+ def get_object(self, request, object_id):
+ """
+ Returns an instance matching the primary key provided. ``None`` is
+ returned if no match is found (or the object_id failed validation
+ against the primary key field).
+ """
+ queryset = self.get_queryset(request)
+ model = queryset.model
+ try:
+ object_id = model._meta.pk.to_python(object_id)
+ return queryset.get(pk=object_id)
+ except (model.DoesNotExist, ValidationError, ValueError):
+ return None
+
+ def get_changelist_form(self, request, **kwargs):
+ """
+ Returns a Form class for use in the Formset on the changelist page.
+ """
+ defaults = {
+ "formfield_callback": partial(self.formfield_for_dbfield, request=request),
+ }
+ defaults.update(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)
+
+ def get_changelist_formset(self, request, **kwargs):
+ """
+ Returns a FormSet class for use on the changelist page if list_editable
+ is used.
+ """
+ defaults = {
+ "formfield_callback": partial(self.formfield_for_dbfield, request=request),
+ }
+ defaults.update(kwargs)
+ return modelformset_factory(self.model,
+ self.get_changelist_form(request), extra=0,
+ fields=self.list_editable, **defaults)
+
+ def get_formsets(self, request, obj=None):
+ for inline in self.get_inline_instances(request, obj):
+ yield inline.get_formset(request, obj)
+
+ def get_paginator(self, request, queryset, per_page, orphans=0, allow_empty_first_page=True):
+ return self.paginator(queryset, per_page, orphans, allow_empty_first_page)
+
+ def log_addition(self, request, object):
+ """
+ Log that an object has been successfully added.
+
+ The default implementation creates an admin LogEntry object.
+ """
+ from django.contrib.admin.models import LogEntry, ADDITION
+ LogEntry.objects.log_action(
+ user_id=request.user.pk,
+ content_type_id=ContentType.objects.get_for_model(object).pk,
+ object_id=object.pk,
+ object_repr=force_text(object),
+ action_flag=ADDITION
+ )
+
+ def log_change(self, request, object, message):
+ """
+ Log that an object has been successfully changed.
+
+ The default implementation creates an admin LogEntry object.
+ """
+ from django.contrib.admin.models import LogEntry, CHANGE
+ LogEntry.objects.log_action(
+ user_id=request.user.pk,
+ content_type_id=ContentType.objects.get_for_model(object).pk,
+ object_id=object.pk,
+ object_repr=force_text(object),
+ action_flag=CHANGE,
+ change_message=message
+ )
+
+ def log_deletion(self, request, object, object_repr):
+ """
+ Log that an object will be deleted. Note that this method is called
+ before the deletion.
+
+ The default implementation creates an admin LogEntry object.
+ """
+ from django.contrib.admin.models import LogEntry, DELETION
+ LogEntry.objects.log_action(
+ user_id=request.user.pk,
+ content_type_id=ContentType.objects.get_for_model(self.model).pk,
+ object_id=object.pk,
+ object_repr=object_repr,
+ action_flag=DELETION
+ )
+
+ def action_checkbox(self, obj):
+ """
+ A list_display column containing a checkbox widget.
+ """
+ return helpers.checkbox.render(helpers.ACTION_CHECKBOX_NAME, force_text(obj.pk))
+ action_checkbox.short_description = mark_safe('<input type="checkbox" id="action-toggle" />')
+ action_checkbox.allow_tags = True
+
+ 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 explicitly set to None that means that we don't
+ # want *any* actions enabled on this page.
+ from django.contrib.admin.views.main import _is_changelist_popup
+ if self.actions is None or _is_changelist_popup(request):
+ return SortedDict()
+
+ actions = []
+
+ # Gather actions from the admin site first
+ for (name, func) in self.admin_site.actions:
+ description = getattr(func, 'short_description', name.replace('_', ' '))
+ actions.append((func, name, description))
+
+ # Then gather them from the model admin and all parent classes,
+ # starting with self and working back up.
+ for klass in self.__class__.mro()[::-1]:
+ class_actions = getattr(klass, 'actions', [])
+ # Avoid trying to iterate over None
+ if not class_actions:
+ continue
+ actions.extend([self.get_action(action) for action in class_actions])
+
+ # get_action might have returned None, so filter any of those out.
+ actions = filter(None, actions)
+
+ # Convert the actions into a SortedDict keyed by name.
+ actions = SortedDict([
+ (name, (func, name, desc))
+ for func, name, desc in actions
+ ])
+
+ return actions
+
+ def get_action_choices(self, request, default_choices=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 six.itervalues(self.get_actions(request)):
+ choice = (name, description % model_format_dict(self.opts))
+ choices.append(choice)
+ return choices
+
+ 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
+
+ if hasattr(func, 'short_description'):
+ description = func.short_description
+ else:
+ description = capfirst(action.replace('_', ' '))
+ return func, action, description
+
+ def get_list_display(self, request):
+ """
+ Return a sequence containing the fields to be displayed on the
+ changelist.
+ """
+ return self.list_display
+
+ 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 not list_display:
+ return self.list_display_links
+ else:
+ # Use only the first item in list_display as link
+ return list(list_display)[:1]
+
+ def get_list_filter(self, request):
+ """
+ Returns a sequence containing the fields to be displayed as filters in
+ the right sidebar of the changelist page.
+ """
+ return self.list_filter
+
+ def get_search_results(self, request, queryset, search_term):
+ """
+ Returns 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:]
+ else:
+ return "%s__icontains" % field_name
+
+ use_distinct = False
+ if self.search_fields and search_term:
+ orm_lookups = [construct_search(str(search_field))
+ for search_field in self.search_fields]
+ for bit in search_term.split():
+ or_queries = [models.Q(**{orm_lookup: bit})
+ for orm_lookup in orm_lookups]
+ queryset = queryset.filter(reduce(operator.or_, or_queries))
+ if not use_distinct:
+ for search_spec in orm_lookups:
+ if lookup_needs_distinct(self.opts, search_spec):
+ use_distinct = True
+ break
+
+ return queryset, use_distinct
+
+ def get_preserved_filters(self, request):
+ """
+ Returns 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 ''
+
+ def construct_change_message(self, request, form, formsets):
+ """
+ Construct a change message from a changed object.
+ """
+ change_message = []
+ if form.changed_data:
+ change_message.append(_('Changed %s.') % get_text_list(form.changed_data, _('and')))
+
+ if formsets:
+ for formset in formsets:
+ for added_object in formset.new_objects:
+ change_message.append(_('Added %(name)s "%(object)s".')
+ % {'name': force_text(added_object._meta.verbose_name),
+ 'object': force_text(added_object)})
+ for changed_object, changed_fields in formset.changed_objects:
+ change_message.append(_('Changed %(list)s for %(name)s "%(object)s".')
+ % {'list': get_text_list(changed_fields, _('and')),
+ 'name': force_text(changed_object._meta.verbose_name),
+ 'object': force_text(changed_object)})
+ for deleted_object in formset.deleted_objects:
+ change_message.append(_('Deleted %(name)s "%(object)s".')
+ % {'name': force_text(deleted_object._meta.verbose_name),
+ 'object': force_text(deleted_object)})
+ change_message = ' '.join(change_message)
+ return change_message or _('No fields changed.')
+
+ 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`' % l for l in levels)
+ raise ValueError('Bad message level string: `%s`. '
+ 'Possible values are: %s' % (level, levels_repr))
+
+ messages.add_message(request, level, message, extra_tags=extra_tags,
+ fail_silently=fail_silently)
+
+ def save_form(self, request, form, change):
+ """
+ Given a ModelForm return an unsaved instance. ``change`` is True if
+ the object is being changed, and False if it's being added.
+ """
+ return form.save(commit=False)
+
+ def save_model(self, request, obj, form, change):
+ """
+ Given a model instance save it to the database.
+ """
+ obj.save()
+
+ def delete_model(self, request, obj):
+ """
+ Given a model instance delete it from the database.
+ """
+ obj.delete()
+
+ def save_formset(self, request, form, formset, change):
+ """
+ Given an inline formset save it to the database.
+ """
+ formset.save()
+
+ def save_related(self, request, form, formsets, change):
+ """
+ Given the ``HttpRequest``, the parent ``ModelForm`` instance, 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)
+
+ 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)
+ context.update({
+ 'add': add,
+ 'change': change,
+ '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_file_field': True, # FIXME - this should check if form or formsets have a FileField,
+ 'has_absolute_url': hasattr(self.model, 'get_absolute_url'),
+ 'form_url': form_url,
+ 'opts': opts,
+ 'content_type_id': ContentType.objects.get_for_model(self.model).id,
+ 'save_as': self.save_as,
+ 'save_on_top': self.save_on_top,
+ })
+ if add and self.add_form_template is not None:
+ form_template = self.add_form_template
+ else:
+ form_template = self.change_form_template
+
+ 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, current_app=self.admin_site.name)
+
+ def response_add(self, request, obj, post_url_continue=None):
+ """
+ Determines the HttpResponse for the add_view stage.
+ """
+ opts = obj._meta
+ pk_value = obj._get_pk_val()
+ preserved_filters = self.get_preserved_filters(request)
+
+ msg_dict = {'name': force_text(opts.verbose_name), 'obj': force_text(obj)}
+ # Here, we distinguish between different save types by checking for
+ # the presence of keys in request.POST.
+ if IS_POPUP_VAR in request.POST:
+ return SimpleTemplateResponse('admin/popup_response.html', {
+ 'pk_value': escape(pk_value),
+ 'obj': escapejs(obj)
+ })
+
+ elif "_continue" in request.POST:
+ msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % msg_dict
+ self.message_user(request, msg, messages.SUCCESS)
+ if post_url_continue is None:
+ post_url_continue = reverse('admin:%s_%s_change' %
+ (opts.app_label, opts.model_name),
+ args=(pk_value,),
+ current_app=self.admin_site.name)
+ 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 = _('The %(name)s "%(obj)s" was added successfully. You may add another %(name)s 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 = _('The %(name)s "%(obj)s" was added successfully.') % msg_dict
+ self.message_user(request, msg, messages.SUCCESS)
+ return self.response_post_save_add(request, obj)
+
+ def response_change(self, request, obj):
+ """
+ Determines the HttpResponse for the change_view stage.
+ """
+ opts = self.model._meta
+ pk_value = obj._get_pk_val()
+ preserved_filters = self.get_preserved_filters(request)
+
+ msg_dict = {'name': force_text(opts.verbose_name), 'obj': force_text(obj)}
+ if "_continue" in request.POST:
+ msg = _('The %(name)s "%(obj)s" 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 = _('The %(name)s "%(obj)s" 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=(pk_value,),
+ 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 = _('The %(name)s "%(obj)s" was changed successfully. You may add another %(name)s 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 = _('The %(name)s "%(obj)s" was changed successfully.') % msg_dict
+ self.message_user(request, msg, messages.SUCCESS)
+ return self.response_post_save_change(request, obj)
+
+ def response_post_save_add(self, request, obj):
+ """
+ Figure out where to redirect after the 'Save' button has been pressed
+ when adding a new object.
+ """
+ opts = self.model._meta
+ if self.has_change_permission(request, None):
+ post_url = reverse('admin:%s_%s_changelist' %
+ (opts.app_label, opts.model_name),
+ current_app=self.admin_site.name)
+ preserved_filters = self.get_preserved_filters(request)
+ post_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, post_url)
+ else:
+ post_url = reverse('admin:index',
+ current_app=self.admin_site.name)
+ return HttpResponseRedirect(post_url)
+
+ def response_post_save_change(self, request, obj):
+ """
+ Figure out where to redirect after the 'Save' button has been pressed
+ when editing an existing object.
+ """
+ opts = self.model._meta
+
+ if self.has_change_permission(request, None):
+ post_url = reverse('admin:%s_%s_changelist' %
+ (opts.app_label, opts.model_name),
+ current_app=self.admin_site.name)
+ preserved_filters = self.get_preserved_filters(request)
+ post_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, post_url)
+ else:
+ post_url = reverse('admin:index',
+ current_app=self.admin_site.name)
+ return HttpResponseRedirect(post_url)
+
+ def 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
+
+ @csrf_protect_m
+ @transaction.atomic
+ def add_view(self, request, form_url='', extra_context=None):
+ "The 'add' admin view for this model."
+ model = self.model
+ opts = model._meta
+
+ if not self.has_add_permission(request):
+ raise PermissionDenied
+
+ ModelForm = self.get_form(request)
+ formsets = []
+ inline_instances = self.get_inline_instances(request, None)
+ if request.method == 'POST':
+ form = ModelForm(request.POST, request.FILES)
+ if form.is_valid():
+ new_object = self.save_form(request, form, change=False)
+ form_validated = True
+ else:
+ form_validated = False
+ new_object = self.model()
+ prefixes = {}
+ for FormSet, inline in zip(self.get_formsets(request), inline_instances):
+ prefix = FormSet.get_default_prefix()
+ prefixes[prefix] = prefixes.get(prefix, 0) + 1
+ if prefixes[prefix] != 1 or not prefix:
+ prefix = "%s-%s" % (prefix, prefixes[prefix])
+ formset = FormSet(data=request.POST, files=request.FILES,
+ instance=new_object,
+ save_as_new="_saveasnew" in request.POST,
+ prefix=prefix, queryset=inline.get_queryset(request))
+ formsets.append(formset)
+ if all_valid(formsets) and form_validated:
+ self.save_model(request, new_object, form, False)
+ self.save_related(request, form, formsets, False)
+ self.log_addition(request, new_object)
+ return self.response_add(request, new_object)
+ else:
+ # Prepare the dict of initial data from the request.
+ # We have to special-case M2Ms as a list of comma-separated PKs.
+ initial = dict(request.GET.items())
+ for k in initial:
+ try:
+ f = opts.get_field(k)
+ except models.FieldDoesNotExist:
+ continue
+ if isinstance(f, models.ManyToManyField):
+ initial[k] = initial[k].split(",")
+ form = ModelForm(initial=initial)
+ prefixes = {}
+ for FormSet, inline in zip(self.get_formsets(request), inline_instances):
+ prefix = FormSet.get_default_prefix()
+ prefixes[prefix] = prefixes.get(prefix, 0) + 1
+ if prefixes[prefix] != 1 or not prefix:
+ prefix = "%s-%s" % (prefix, prefixes[prefix])
+ formset = FormSet(instance=self.model(), prefix=prefix,
+ queryset=inline.get_queryset(request))
+ formsets.append(formset)
+
+ adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)),
+ self.get_prepopulated_fields(request),
+ self.get_readonly_fields(request),
+ model_admin=self)
+ media = self.media + adminForm.media
+
+ inline_admin_formsets = []
+ for inline, formset in zip(inline_instances, formsets):
+ fieldsets = list(inline.get_fieldsets(request))
+ readonly = list(inline.get_readonly_fields(request))
+ prepopulated = dict(inline.get_prepopulated_fields(request))
+ inline_admin_formset = helpers.InlineAdminFormSet(inline, formset,
+ fieldsets, prepopulated, readonly, model_admin=self)
+ inline_admin_formsets.append(inline_admin_formset)
+ media = media + inline_admin_formset.media
+
+ context = {
+ 'title': _('Add %s') % force_text(opts.verbose_name),
+ 'adminform': adminForm,
+ 'is_popup': IS_POPUP_VAR in request.REQUEST,
+ 'media': media,
+ 'inline_admin_formsets': inline_admin_formsets,
+ 'errors': helpers.AdminErrorList(form, formsets),
+ 'app_label': opts.app_label,
+ 'preserved_filters': self.get_preserved_filters(request),
+ }
+ context.update(extra_context or {})
+ return self.render_change_form(request, context, form_url=form_url, add=True)
+
+ @csrf_protect_m
+ @transaction.atomic
+ def change_view(self, request, object_id, form_url='', extra_context=None):
+ "The 'change' admin view for this model."
+ model = self.model
+ opts = model._meta
+
+ obj = self.get_object(request, unquote(object_id))
+
+ if not self.has_change_permission(request, obj):
+ raise PermissionDenied
+
+ if obj is None:
+ raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_text(opts.verbose_name), 'key': escape(object_id)})
+
+ if request.method == 'POST' and "_saveasnew" in request.POST:
+ return self.add_view(request, form_url=reverse('admin:%s_%s_add' %
+ (opts.app_label, opts.model_name),
+ current_app=self.admin_site.name))
+
+ ModelForm = self.get_form(request, obj)
+ formsets = []
+ inline_instances = self.get_inline_instances(request, obj)
+ if request.method == 'POST':
+ form = ModelForm(request.POST, request.FILES, instance=obj)
+ if form.is_valid():
+ form_validated = True
+ new_object = self.save_form(request, form, change=True)
+ else:
+ form_validated = False
+ new_object = obj
+ prefixes = {}
+ for FormSet, inline in zip(self.get_formsets(request, new_object), inline_instances):
+ prefix = FormSet.get_default_prefix()
+ prefixes[prefix] = prefixes.get(prefix, 0) + 1
+ if prefixes[prefix] != 1 or not prefix:
+ prefix = "%s-%s" % (prefix, prefixes[prefix])
+ formset = FormSet(request.POST, request.FILES,
+ instance=new_object, prefix=prefix,
+ queryset=inline.get_queryset(request))
+
+ formsets.append(formset)
+
+ if all_valid(formsets) and form_validated:
+ self.save_model(request, new_object, form, True)
+ self.save_related(request, form, formsets, True)
+ change_message = self.construct_change_message(request, form, formsets)
+ self.log_change(request, new_object, change_message)
+ return self.response_change(request, new_object)
+
+ else:
+ form = ModelForm(instance=obj)
+ prefixes = {}
+ for FormSet, inline in zip(self.get_formsets(request, obj), inline_instances):
+ prefix = FormSet.get_default_prefix()
+ prefixes[prefix] = prefixes.get(prefix, 0) + 1
+ if prefixes[prefix] != 1 or not prefix:
+ prefix = "%s-%s" % (prefix, prefixes[prefix])
+ formset = FormSet(instance=obj, prefix=prefix,
+ queryset=inline.get_queryset(request))
+ formsets.append(formset)
+
+ adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj),
+ self.get_prepopulated_fields(request, obj),
+ self.get_readonly_fields(request, obj),
+ model_admin=self)
+ media = self.media + adminForm.media
+
+ 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))
+ prepopulated = dict(inline.get_prepopulated_fields(request, obj))
+ inline_admin_formset = helpers.InlineAdminFormSet(inline, formset,
+ fieldsets, prepopulated, readonly, model_admin=self)
+ inline_admin_formsets.append(inline_admin_formset)
+ media = media + inline_admin_formset.media
+
+ context = {
+ 'title': _('Change %s') % force_text(opts.verbose_name),
+ 'adminform': adminForm,
+ 'object_id': object_id,
+ 'original': obj,
+ 'is_popup': IS_POPUP_VAR in request.REQUEST,
+ 'media': media,
+ 'inline_admin_formsets': inline_admin_formsets,
+ 'errors': helpers.AdminErrorList(form, formsets),
+ 'app_label': opts.app_label,
+ 'preserved_filters': self.get_preserved_filters(request),
+ }
+ context.update(extra_context or {})
+ return self.render_change_form(request, context, change=True, obj=obj, form_url=form_url)
+
+ @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_change_permission(request, None):
+ raise PermissionDenied
+
+ list_display = self.get_list_display(request)
+ list_display_links = self.get_list_display_links(request, list_display)
+ list_filter = self.get_list_filter(request)
+
+ # Check actions to see if any are available on this changelist
+ actions = self.get_actions(request)
+ if actions:
+ # Add the action checkboxes if there are any actions available.
+ list_display = ['action_checkbox'] + list(list_display)
+
+ ChangeList = self.get_changelist(request)
+ try:
+ cl = ChangeList(request, self.model, list_display,
+ list_display_links, list_filter, self.date_hierarchy,
+ self.search_fields, self.list_select_related,
+ self.list_per_page, self.list_max_show_all, self.list_editable,
+ self)
+ 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.keys():
+ 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 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 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 and not action_failed):
+ FormSet = self.get_changelist_formset(request)
+ formset = cl.formset = FormSet(request.POST, request.FILES, queryset=cl.result_list)
+ 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:
+ if changecount == 1:
+ name = force_text(opts.verbose_name)
+ else:
+ name = force_text(opts.verbose_name_plural)
+ msg = ungettext("%(count)s %(name)s was changed successfully.",
+ "%(count)s %(name)s were changed successfully.",
+ changecount) % {'count': changecount,
+ 'name': name,
+ 'obj': force_text(obj)}
+ self.message_user(request, msg, messages.SUCCESS)
+
+ return HttpResponseRedirect(request.get_full_path())
+
+ # Handle GET -- construct a formset for display.
+ elif cl.list_editable:
+ 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)
+ else:
+ action_form = None
+
+ selection_note_all = ungettext('%(total_count)s selected',
+ 'All %(total_count)s selected', cl.result_count)
+
+ context = {
+ 'module_name': force_text(opts.verbose_name_plural),
+ 'selection_note': _('0 of %(cnt)s selected') % {'cnt': len(cl.result_list)},
+ 'selection_note_all': selection_note_all % {'total_count': cl.result_count},
+ 'title': cl.title,
+ 'is_popup': cl.is_popup,
+ 'cl': cl,
+ 'media': media,
+ 'has_add_permission': self.has_add_permission(request),
+ 'opts': cl.opts,
+ 'app_label': app_label,
+ '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),
+ }
+ context.update(extra_context or {})
+
+ 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, current_app=self.admin_site.name)
+
+ @csrf_protect_m
+ @transaction.atomic
+ def delete_view(self, request, object_id, extra_context=None):
+ "The 'delete' admin view for this model."
+ opts = self.model._meta
+ app_label = opts.app_label
+
+ obj = self.get_object(request, unquote(object_id))
+
+ if not self.has_delete_permission(request, obj):
+ raise PermissionDenied
+
+ if obj is None:
+ raise Http404(
+ _('%(name)s object with primary key %(key)r does not exist.') %
+ {'name': force_text(opts.verbose_name), 'key': escape(object_id)}
+ )
+
+ using = router.db_for_write(self.model)
+
+ # Populate deleted_objects, a data structure of all related objects that
+ # will also be deleted.
+ (deleted_objects, perms_needed, protected) = get_deleted_objects(
+ [obj], opts, request.user, self.admin_site, using)
+
+ if request.POST: # The user has already confirmed the deletion.
+ if perms_needed:
+ raise PermissionDenied
+ obj_display = force_text(obj)
+ self.log_deletion(request, obj, obj_display)
+ self.delete_model(request, obj)
+
+ self.message_user(request, _(
+ 'The %(name)s "%(obj)s" was deleted successfully.') % {
+ 'name': force_text(opts.verbose_name),
+ 'obj': force_text(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)
+
+ object_name = force_text(opts.verbose_name)
+
+ if perms_needed or protected:
+ title = _("Cannot delete %(name)s") % {"name": object_name}
+ else:
+ title = _("Are you sure?")
+
+ context = {
+ "title": title,
+ "object_name": object_name,
+ "object": obj,
+ "deleted_objects": deleted_objects,
+ "perms_lacking": perms_needed,
+ "protected": protected,
+ "opts": opts,
+ "app_label": app_label,
+ 'preserved_filters': self.get_preserved_filters(request),
+ }
+ context.update(extra_context or {})
+
+ return TemplateResponse(request, self.delete_confirmation_template or [
+ "admin/%s/%s/delete_confirmation.html" % (app_label, opts.model_name),
+ "admin/%s/delete_confirmation.html" % app_label,
+ "admin/delete_confirmation.html"
+ ], context, current_app=self.admin_site.name)
+
+ def history_view(self, request, object_id, extra_context=None):
+ "The 'history' admin view for this model."
+ from django.contrib.admin.models import LogEntry
+ # First check if the user can see this history.
+ model = self.model
+ obj = get_object_or_404(model, pk=unquote(object_id))
+
+ if not self.has_change_permission(request, obj):
+ raise PermissionDenied
+
+ # Then get the history for this object.
+ opts = model._meta
+ app_label = opts.app_label
+ action_list = LogEntry.objects.filter(
+ object_id=unquote(object_id),
+ content_type__id__exact=ContentType.objects.get_for_model(model).id
+ ).select_related().order_by('action_time')
+
+ context = {
+ 'title': _('Change history: %s') % force_text(obj),
+ 'action_list': action_list,
+ 'module_name': capfirst(force_text(opts.verbose_name_plural)),
+ 'object': obj,
+ 'app_label': app_label,
+ 'opts': opts,
+ 'preserved_filters': self.get_preserved_filters(request),
+ }
+ context.update(extra_context or {})
+ return TemplateResponse(request, self.object_history_template or [
+ "admin/%s/%s/object_history.html" % (app_label, opts.model_name),
+ "admin/%s/object_history.html" % app_label,
+ "admin/object_history.html"
+ ], context, current_app=self.admin_site.name)
+
+
+class InlineModelAdmin(BaseModelAdmin):
+ """
+ Options for inline editing of ``model`` instances.
+
+ Provide ``name`` to specify the attribute name of the ``ForeignKey`` from
+ ``model`` to its parent. This is required if ``model`` has more than one
+ ``ForeignKey`` to its parent.
+ """
+ model = None
+ fk_name = None
+ formset = BaseInlineFormSet
+ extra = 3
+ max_num = None
+ template = None
+ verbose_name = None
+ verbose_name_plural = None
+ can_delete = True
+
+ # validation
+ validator_class = validation.InlineValidator
+
+ def __init__(self, parent_model, admin_site):
+ self.admin_site = admin_site
+ self.parent_model = parent_model
+ self.opts = self.model._meta
+ super(InlineModelAdmin, self).__init__()
+ if self.verbose_name is None:
+ self.verbose_name = self.model._meta.verbose_name
+ if self.verbose_name_plural is None:
+ self.verbose_name_plural = self.model._meta.verbose_name_plural
+
+ @property
+ def media(self):
+ extra = '' if settings.DEBUG else '.min'
+ js = ['jquery%s.js' % extra, 'jquery.init.js', 'inlines%s.js' % extra]
+ if self.prepopulated_fields:
+ js.extend(['urlify.js', 'prepopulate%s.js' % extra])
+ if self.filter_vertical or self.filter_horizontal:
+ js.extend(['SelectBox.js', 'SelectFilter2.js'])
+ return forms.Media(js=[static('admin/js/%s' % url) for url in js])
+
+ def get_extra(self, request, obj=None, **kwargs):
+ """Hook for customizing the number of extra inline forms."""
+ return self.extra
+
+ def get_max_num(self, request, obj=None, **kwargs):
+ """Hook for customizing the max number of extra inline forms."""
+ return self.max_num
+
+ def get_formset(self, request, obj=None, **kwargs):
+ """Returns a BaseInlineFormSet class for use in admin add/change views."""
+ if 'fields' in kwargs:
+ fields = kwargs.pop('fields')
+ else:
+ fields = flatten_fieldsets(self.get_fieldsets(request, obj))
+ if self.exclude is None:
+ exclude = []
+ else:
+ exclude = list(self.exclude)
+ exclude.extend(self.get_readonly_fields(request, obj))
+ if self.exclude is None and hasattr(self.form, '_meta') and self.form._meta.exclude:
+ # Take the custom ModelForm's Meta.exclude into account only if the
+ # InlineModelAdmin doesn't define its own.
+ exclude.extend(self.form._meta.exclude)
+ # if exclude is an empty list we use None, since that's the actual
+ # default
+ exclude = exclude or None
+ can_delete = self.can_delete and self.has_delete_permission(request, obj)
+ defaults = {
+ "form": self.form,
+ "formset": self.formset,
+ "fk_name": self.fk_name,
+ "fields": fields,
+ "exclude": exclude,
+ "formfield_callback": partial(self.formfield_for_dbfield, request=request),
+ "extra": self.get_extra(request, obj, **kwargs),
+ "max_num": self.get_max_num(request, obj, **kwargs),
+ "can_delete": can_delete,
+ }
+
+ defaults.update(kwargs)
+ base_model_form = defaults['form']
+
+ class DeleteProtectedModelForm(base_model_form):
+ def hand_clean_DELETE(self):
+ """
+ We don't validate the 'DELETE' field itself because on
+ templates it's not rendered using the field information, but
+ just using a generic "deletion_field" of the InlineModelAdmin.
+ """
+ if self.cleaned_data.get(DELETION_FIELD_NAME, False):
+ using = router.db_for_write(self._meta.model)
+ collector = NestedObjects(using=using)
+ collector.collect([self.instance])
+ if collector.protected:
+ objs = []
+ for p in collector.protected:
+ objs.append(
+ # Translators: Model verbose name and instance representation, suitable to be an item in a list
+ _('%(class_name)s %(instance)s') % {
+ 'class_name': p._meta.verbose_name,
+ 'instance': p}
+ )
+ params = {'class_name': self._meta.model._meta.verbose_name,
+ 'instance': self.instance,
+ 'related_objects': get_text_list(objs, _('and'))}
+ msg = _("Deleting %(class_name)s %(instance)s would require "
+ "deleting the following protected related objects: "
+ "%(related_objects)s")
+ raise ValidationError(msg, code='deleting_protected', params=params)
+
+ def is_valid(self):
+ result = super(DeleteProtectedModelForm, self).is_valid()
+ self.hand_clean_DELETE()
+ return result
+
+ defaults['form'] = DeleteProtectedModelForm
+
+ if defaults['fields'] is None and not modelform_defines_fields(defaults['form']):
+ defaults['fields'] = forms.ALL_FIELDS
+
+ return inlineformset_factory(self.parent_model, self.model, **defaults)
+
+ def get_fieldsets(self, request, obj=None):
+ if self.declared_fieldsets:
+ return self.declared_fieldsets
+ form = self.get_formset(request, obj, fields=None).form
+ fields = list(form.base_fields) + list(self.get_readonly_fields(request, obj))
+ return [(None, {'fields': fields})]
+
+ def get_queryset(self, request):
+ queryset = super(InlineModelAdmin, self).get_queryset(request)
+ if not self.has_change_permission(request):
+ queryset = queryset.none()
+ return queryset
+
+ def has_add_permission(self, request):
+ if self.opts.auto_created:
+ # We're checking the rights to an auto-created intermediate model,
+ # which doesn't have its own individual permissions. The user needs
+ # to have the change permission for the related model in order to
+ # be able to do anything with the intermediate model.
+ return self.has_change_permission(request)
+ return super(InlineModelAdmin, self).has_add_permission(request)
+
+ def has_change_permission(self, request, obj=None):
+ opts = self.opts
+ if opts.auto_created:
+ # The model was auto-created as intermediary for a
+ # ManyToMany-relationship, find the target model
+ for field in opts.fields:
+ if field.rel and field.rel.to != self.parent_model:
+ opts = field.rel.to._meta
+ break
+ codename = get_permission_codename('change', opts)
+ return request.user.has_perm("%s.%s" % (opts.app_label, codename))
+
+ def has_delete_permission(self, request, obj=None):
+ if self.opts.auto_created:
+ # We're checking the rights to an auto-created intermediate model,
+ # which doesn't have its own individual permissions. The user needs
+ # to have the change permission for the related model in order to
+ # be able to do anything with the intermediate model.
+ return self.has_change_permission(request, obj)
+ return super(InlineModelAdmin, self).has_delete_permission(request, obj)
+
+
+class StackedInline(InlineModelAdmin):
+ template = 'admin/edit_inline/stacked.html'
+
+
+class TabularInline(InlineModelAdmin):
+ template = 'admin/edit_inline/tabular.html'