/django/contrib/admin/options.py
Python | 1363 lines | 1131 code | 86 blank | 146 comment | 141 complexity | 83266c79b4cba8e524074ac86a97f947 MD5 | raw file
Possible License(s): BSD-3-Clause
Large files files are truncated, but you can click here to view the full file
- from django import forms, template
- from django.forms.formsets import all_valid
- from django.forms.models import (modelform_factory, modelformset_factory,
- inlineformset_factory, BaseInlineFormSet)
- from django.contrib.contenttypes.models import ContentType
- from django.contrib.admin import widgets, helpers
- from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_format_dict
- from django.contrib import messages
- from django.views.decorators.csrf import csrf_protect
- from django.core.exceptions import PermissionDenied, ValidationError
- from django.core.paginator import Paginator
- from django.db import models, transaction, router
- from django.db.models.related import RelatedObject
- from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist
- from django.db.models.sql.constants import LOOKUP_SEP, QUERY_TERMS
- from django.http import Http404, HttpResponse, HttpResponseRedirect
- from django.shortcuts import get_object_or_404, render_to_response
- from django.utils.decorators import method_decorator
- from django.utils.datastructures import SortedDict
- from django.utils.functional import update_wrapper
- from django.utils.html import escape, escapejs
- from django.utils.safestring import mark_safe
- from django.utils.functional import curry
- 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_unicode
- HORIZONTAL, VERTICAL = 1, 2
- # returns the <ul> class for a given radio_admin field
- get_ul_class = lambda x: 'radiolist%s' % ((x == HORIZONTAL) and ' inline' or '')
- 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.AdminIntegerFieldWidget},
- models.CharField: {'widget': widgets.AdminTextInputWidget},
- models.ImageField: {'widget': widgets.AdminFileWidget},
- models.FileField: {'widget': widgets.AdminFileWidget},
- }
- csrf_protect_m = method_decorator(csrf_protect)
- class InlineAdminField(object):
- def __init__(self, name):
- self.name = name
- self.is_inline = True
- self.empty_widget = True
- self.widget = None
- #self.required = True
- self.help_text = ''
- class BaseModelAdmin(object):
- """Functionality common to both ModelAdmin and InlineAdmin."""
- __metaclass__ = forms.MediaDefiningClass
- 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
- 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(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 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, 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'] = db_field.blank and _('None') or None
- 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, 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))
- 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_readonly_fields(self, request, obj=None):
- return self.readonly_fields
- def 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_query_set()
- # TODO: this should be handled by some parameter to the ChangeList.
- ordering = self.ordering or () # otherwise we might try to *None, which is bad ;)
- 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 specificially included in the lookup list; so
- # drop __id if it is the last part. However, first we need to find
- # the pk attribute name.
- pk_attr_name = None
- for part in parts[:-1]:
- field, _, _, _ = model._meta.get_field_by_name(part)
- if hasattr(field, 'rel'):
- model = field.rel.to
- pk_attr_name = model._meta.pk.name
- elif isinstance(field, RelatedObject):
- model = field.model
- pk_attr_name = model._meta.pk.name
- else:
- pk_attr_name = None
- if pk_attr_name and len(parts) > 1 and parts[-1] == pk_attr_name:
- parts.pop()
- try:
- self.model._meta.get_field_by_name(parts[0])
- except FieldDoesNotExist:
- # Lookups on non-existants fields are ok, since they're ignored
- # later.
- return True
- else:
- 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
- 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_editable = ()
- search_fields = ()
- date_hierarchy = None
- save_as = False
- save_on_top = False
- paginator = Paginator
- 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
- def __init__(self, model, admin_site):
- self.model = model
- self.opts = model._meta
- self.admin_site = admin_site
- self.inline_instances = []
- for inline_class in self.inlines:
- inline_instance = inline_class(self.model, self.admin_site)
- self.inline_instances.append(inline_instance)
- if 'action_checkbox' not in self.list_display and self.actions is not None:
- self.list_display = ['action_checkbox'] + list(self.list_display)
- if not self.list_display_links:
- for name in self.list_display:
- if name != 'action_checkbox':
- self.list_display_links = [name]
- break
- super(ModelAdmin, self).__init__()
- def get_urls(self):
- from django.conf.urls.defaults 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.module_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)
- def _media(self):
- from django.conf import settings
- js = ['js/core.js', 'js/admin/RelatedObjectLookups.js',
- 'js/jquery.min.js', 'js/jquery.init.js']
- if self.actions is not None:
- js.extend(['js/actions.min.js'])
- if self.prepopulated_fields:
- js.append('js/urlify.js')
- js.append('js/prepopulate.min.js')
- if self.opts.get_ordered_objects():
- js.extend(['js/getElementsBySelector.js', 'js/dom-drag.js' , 'js/admin/ordering.js'])
- return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
- media = property(_media)
- def has_add_permission(self, request):
- """
- Returns True if the given request has permission to add an object.
- Can be overriden by the user in subclasses.
- """
- opts = self.opts
- return request.user.has_perm(opts.app_label + '.' + opts.get_add_permission())
- 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 overriden 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
- return request.user.has_perm(opts.app_label + '.' + opts.get_change_permission())
- 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 overriden 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
- return request.user.has_perm(opts.app_label + '.' + opts.get_delete_permission())
- 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 = form.base_fields.keys() + 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 self.declared_fieldsets:
- fields = flatten_fieldsets(self.declared_fieldsets)
- else:
- fields = None
- if self.exclude is None:
- exclude = []
- else:
- exclude = list(self.exclude)
- exclude.extend(kwargs.get("exclude", []))
- exclude.extend(self.get_readonly_fields(request, obj))
- # if exclude is an empty list we pass None to be consistant with the
- # default on modelform_factory
- exclude = exclude or None
- defaults = {
- "form": self.form,
- "fields": fields,
- "exclude": exclude,
- "formfield_callback": curry(self.formfield_for_dbfield, request=request),
- }
- defaults.update(kwargs)
- form = modelform_factory(self.model, **defaults)
- possible_inlines = [name for name in form.base_fields if form.base_fields[name] is None]
- inlines = [name for name in possible_inlines if name in [inl.__name__ for inl in self.inlines]]
- for inl in inlines:
- form.base_fields[inl] = InlineAdminField(inl)
- return form
- 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.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):
- 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": curry(self.formfield_for_dbfield, request=request),
- }
- defaults.update(kwargs)
- 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": curry(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.inline_instances:
- 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_unicode(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_unicode(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.id,
- 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_unicode(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 explicitally set to None that means that we don't
- # want *any* actions enabled on this page.
- from django.contrib.admin.views.main import IS_POPUP_VAR
- if self.actions is None or IS_POPUP_VAR in request.GET:
- 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
- # and sorted by description.
- actions.sort(key=lambda k: k[2].lower())
- 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 self.get_actions(request).itervalues():
- 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 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_unicode(added_object._meta.verbose_name),
- 'object': force_unicode(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_unicode(changed_object._meta.verbose_name),
- 'object': force_unicode(changed_object)})
- for deleted_object in formset.deleted_objects:
- change_message.append(_('Deleted %(name)s "%(object)s".')
- % {'name': force_unicode(deleted_object._meta.verbose_name),
- 'object': force_unicode(deleted_object)})
- change_message = ' '.join(change_message)
- return change_message or _('No fields changed.')
- def message_user(self, request, message):
- """
- Send a message to the user. The default implementation
- posts a message using the django.contrib.messages backend.
- """
- messages.info(request, message)
- 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 render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
- opts = self.model._meta
- app_label = opts.app_label
- ordered_objects = opts.get_ordered_objects()
- 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'),
- 'ordered_objects': ordered_objects,
- 'form_url': mark_safe(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,
- 'root_path': self.admin_site.root_path,
- })
- if add and self.add_form_template is not None:
- form_template = self.add_form_template
- else:
- form_template = self.change_form_template
- context_instance = template.RequestContext(request, current_app=self.admin_site.name)
- return render_to_response(form_template or [
- "admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()),
- "admin/%s/change_form.html" % app_label,
- "admin/change_form.html"
- ], context, context_instance=context_instance)
- def response_add(self, request, obj, post_url_continue='../%s/'):
- """
- Determines the HttpResponse for the add_view stage.
- """
- opts = obj._meta
- pk_value = obj._get_pk_val()
- msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name': force_unicode(opts.verbose_name), 'obj': force_unicode(obj)}
- # Here, we distinguish between different save types by checking for
- # the presence of keys in request.POST.
- if "_continue" in request.POST:
- self.message_user(request, msg + ' ' + _("You may edit it again below."))
- if "_popup" in request.POST:
- post_url_continue += "?_popup=1"
- return HttpResponseRedirect(post_url_continue % pk_value)
- if "_popup" in request.POST:
- return HttpResponse('<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script>' % \
- # escape() calls force_unicode.
- (escape(pk_value), escapejs(obj)))
- elif "_addanother" in request.POST:
- self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(opts.verbose_name)))
- return HttpResponseRedirect(request.path)
- else:
- self.message_user(request, msg)
- # Figure out where to redirect. If the user has change permission,
- # redirect to the change-list page for this object. Otherwise,
- # redirect to the admin index.
- if self.has_change_permission(request, None):
- post_url = '../'
- else:
- post_url = '../../../'
- return HttpResponseRedirect(post_url)
- def response_change(self, request, obj):
- """
- Determines the HttpResponse for the change_view stage.
- """
- opts = obj._meta
- # Handle proxy models automatically created by .only() or .defer()
- verbose_name = opts.verbose_name
- if obj._deferred:
- opts_ = opts.proxy_for_model._meta
- verbose_name = opts_.verbose_name
- pk_value = obj._get_pk_val()
- msg = _('The %(name)s "%(obj)s" was changed successfully.') % {'name': force_unicode(verbose_name), 'obj': force_unicode(obj)}
- if "_continue" in request.POST:
- self.message_user(request, msg + ' ' + _("You may edit it again below."))
- if "_popup" in request.REQUEST:
- return HttpResponseRedirect(request.path + "?_popup=1")
- else:
- return HttpResponseRedirect(request.path)
- elif "_saveasnew" in request.POST:
- msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % {'name': force_unicode(verbose_name), 'obj': obj}
- self.message_user(request, msg)
- return HttpResponseRedirect("../%s/" % pk_value)
- elif "_addanother" in request.POST:
- self.message_user(request, msg + ' ' + (_("You may add another %s below.") % force_unicode(verbose_name)))
- return HttpResponseRedirect("../add/")
- else:
- self.message_user(request, msg)
- # Figure out where to redirect. If the user has change permission,
- # redirect to the change-list page for this object. Otherwise,
- # redirect to the admin index.
- if self.has_change_permission(request, None):
- return HttpResponseRedirect('../')
- else:
- return HttpResponseRedirect('../../../')
- 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, name, description = self.get_actions(request)[action]
- # 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)
- 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, 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, HttpResponse):
- return response
- else:
- return HttpResponseRedirect(request.get_full_path())
- else:
- msg = _("No action selected.")
- self.message_user(request, msg)
- return None
- @csrf_protect_m
- @transaction.commit_on_success
- 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 = []
- 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), self.inline_instances):
- prefix = FormSet.get_default_prefix()
- prefixes[prefix] = prefixes.get(prefix, 0) + 1
- if prefixes[prefix] != 1:
- 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.queryset(request))
- formsets.append(formset)
- if all_valid(formsets) and form_validated:
- self.save_model(request, new_object, form, change=False)
- form.save_m2m()
- for formset in formsets:
- self.save_formset(request, form, formset, change=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),
- self.inline_instances):
- prefix = FormSet.get_default_prefix()
- prefixes[prefix] = prefixes.get(prefix, 0) + 1
- if prefixes[prefix] != 1:
- prefix = "%s-%s" % (prefix, prefixes[prefix])
- formset = FormSet(instance=self.model(), prefix=prefix,
- queryset=inline.queryset(request))
- formsets.append(formset)
- adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)),
- self.prepopulated_fields, self.get_readonly_fields(request),
- model_admin=self)
- media = self.media + adminForm.media
- inline_admin_formsets = []
- for inline, formset in zip(self.inline_instances, formsets):
- fieldsets = list(inline.get_fieldsets(request))
- readonly = list(inline.get_readonly_fields(request))
- inline_admin_formset = helpers.InlineAdminFormSet(inline, formset,
- fieldsets, readonly, model_admin=self)
- inline_admin_formsets.append(inline_admin_formset)
- media = media + inline_admin_formset.media
- context = {
- 'title': _('Add %s') % force_unicode(opts.verbose_name),
- 'adminform': adminForm,
- 'is_popup': "_popup" in request.REQUEST,
- 'show_delete': False,
- 'media': mark_safe(media),
- 'inline_admin_formsets': inline_admin_formsets,
- 'errors': helpers.AdminErrorList(form, formsets),
- 'root_path': self.admin_site.root_path,
- 'app_label': opts.app_label,
- }
- context.update(extra_context or {})
- return self.render_change_form(request, context, form_url=form_url, add=True)
- @csrf_protect_m
- @transaction.commit_on_success
- def change_view(self, request, object_id, 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_unicode(opts.verbose_name), 'key': escape(object_id)})
- if request.method == 'POST' and "_saveasnew" in request.POST:
- return self.add_view(request, form_url='../add/')
- ModelForm = self.get_form(request, obj)
- formsets = []
- 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),
- self.inline_instances):
- prefix = FormSet.get_default_prefix()
- prefixes[prefix] = prefixes.get(prefix, 0) + 1
- if prefixes[prefix] != 1:
- prefix = "%s-%s" % (prefix, prefixes[prefix])
- formset = FormSet(request.POST, request.FILES,
- instance=new_object, prefix=prefix,
- queryset=inline.queryset(request))
- formsets.append(formset)
- if all_valid(formsets) and form_validated:
- self.save_model(request, new_object, form, change=True)
- form.save_m2m()
- for formset in formsets:
- self.save_formset(request, form, formset, change=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), self.inline_instances):
- prefix = FormSet.get_default_prefix()
- prefixes[prefix] = prefixes.get(prefix, 0) + 1
- if prefixes[prefix] != 1:
- prefix = "%s-%s" % (prefix, prefixes[prefix])
- formset = FormSet(instance=obj, prefix=prefix,
- queryset=inline.queryset(request))
- formsets.append(formset)
- adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj),
- self.prepopulated_fields, self.get_readonly_fields(request, obj),
- model_admin=self)
- media = self.media + adminForm.media
- inline_admin_formsets = []
- for inline, formset in zip(self.inline_instances, formsets):
- fieldsets = list(inline.get_fieldsets(request, obj))
- readonly = list(inline.get_readonly_fields(request, obj))
- inline_admin_formset = helpers.InlineAdminFormSet(inline, formset,
- fieldsets, readonly, model_admin=self)
- inline_admin_formsets.append(inline_admin_formset)
- media = media + inline_admin_formset.media
- context = {
- 'title': _('Change %s') % force_unicode(opts.verbose_name),
- 'adminform': adminForm,
- 'object_id': object_id,
- 'original': obj,
- 'is_popup': "_popup" in request.REQUEST,
- 'media': mark_safe(media),
- 'inline_admin_formsets': inline_admin_formsets,
- 'errors': helpers.AdminErrorList(form, formsets),
- 'root_path': self.admin_site.root_path,
- 'app_label': opts.app_label,
- }
- context.update(extra_context or {})
- return self.render_change_form(request, context, change=True, obj=obj)
- @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
- # Check actions to see if any are available on this changelist
- actions = self.get_actions(request)
- # Remove action checkboxes if there aren't any actions available.
- list_display = list(self.list_display)
- if not actions:
- try:
- list_display.remove('action_checkbox')
- except ValueError:
- pass
- ChangeList = self.get_changelist(request)
- try:
- cl = ChangeList(request, self.model, list_display, self.list_display_links,
- self.list_filter, self.date_hierarchy, self.search_fields,
- self.list_select_related, self.list_per_page, 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 render_to_response('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_query_set())
- 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)
- 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_query_set())
- 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)
- form.save_m2m()
- 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_unicode(opts.verbose_name)
- else:
- name = force_unicode(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_unicode(obj)}
- self.message_user(request, msg)
- 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:
- …
Large files files are truncated, but you can click here to view the full file