2011-03-28 09:40:43 +08:00
|
|
|
from functools import update_wrapper, partial
|
2012-10-19 07:58:52 +08:00
|
|
|
import warnings
|
|
|
|
|
2011-04-23 02:17:16 +08:00
|
|
|
from django import forms
|
2012-02-05 16:35:08 +08:00
|
|
|
from django.conf import settings
|
2008-07-19 09:22:26 +08:00
|
|
|
from django.forms.formsets import all_valid
|
2011-03-03 09:54:33 +08:00
|
|
|
from django.forms.models import (modelform_factory, modelformset_factory,
|
|
|
|
inlineformset_factory, BaseInlineFormSet)
|
2008-07-19 07:54:34 +08:00
|
|
|
from django.contrib.contenttypes.models import ContentType
|
2011-03-03 09:54:33 +08:00
|
|
|
from django.contrib.admin import widgets, helpers
|
2012-10-19 07:58:52 +08:00
|
|
|
from django.contrib.admin.util import quote, unquote, flatten_fieldsets, get_deleted_objects, model_format_dict
|
2011-08-11 22:07:39 +08:00
|
|
|
from django.contrib.admin.templatetags.admin_static import static
|
2009-12-10 00:57:23 +08:00
|
|
|
from django.contrib import messages
|
2009-10-27 08:36:34 +08:00
|
|
|
from django.views.decorators.csrf import csrf_protect
|
2009-12-28 22:08:11 +08:00
|
|
|
from django.core.exceptions import PermissionDenied, ValidationError
|
2010-12-21 22:57:29 +08:00
|
|
|
from django.core.paginator import Paginator
|
2011-09-21 02:30:06 +08:00
|
|
|
from django.core.urlresolvers import reverse
|
2010-11-10 00:46:42 +08:00
|
|
|
from django.db import models, transaction, router
|
2012-09-09 07:51:36 +08:00
|
|
|
from django.db.models.constants import LOOKUP_SEP
|
2010-12-23 11:44:38 +08:00
|
|
|
from django.db.models.related import RelatedObject
|
|
|
|
from django.db.models.fields import BLANK_CHOICE_DASH, FieldDoesNotExist
|
2012-09-09 07:51:36 +08:00
|
|
|
from django.db.models.sql.constants import QUERY_TERMS
|
2008-07-19 07:54:34 +08:00
|
|
|
from django.http import Http404, HttpResponse, HttpResponseRedirect
|
2011-04-23 02:17:16 +08:00
|
|
|
from django.shortcuts import get_object_or_404
|
|
|
|
from django.template.response import SimpleTemplateResponse, TemplateResponse
|
2010-02-09 23:02:39 +08:00
|
|
|
from django.utils.decorators import method_decorator
|
2009-04-07 04:23:33 +08:00
|
|
|
from django.utils.datastructures import SortedDict
|
2011-01-03 01:34:52 +08:00
|
|
|
from django.utils.html import escape, escapejs
|
2008-07-19 07:54:34 +08:00
|
|
|
from django.utils.safestring import mark_safe
|
2012-07-21 03:20:42 +08:00
|
|
|
from django.utils import six
|
2008-07-19 07:54:34 +08:00
|
|
|
from django.utils.text import capfirst, get_text_list
|
|
|
|
from django.utils.translation import ugettext as _
|
2010-10-12 09:03:53 +08:00
|
|
|
from django.utils.translation import ungettext
|
2012-07-21 16:00:10 +08:00
|
|
|
from django.utils.encoding import force_text
|
2008-07-19 07:54:34 +08:00
|
|
|
|
|
|
|
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
|
|
|
|
|
2009-01-16 23:32:31 +08:00
|
|
|
# Defaults for formfield_overrides. ModelAdmin subclasses can change this
|
|
|
|
# by adding to ModelAdmin.formfield_overrides.
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-01-16 23:32:31 +08:00
|
|
|
FORMFIELD_FOR_DBFIELD_DEFAULTS = {
|
2009-03-18 17:19:24 +08:00
|
|
|
models.DateTimeField: {
|
2009-01-16 23:32:31 +08:00
|
|
|
'form_class': forms.SplitDateTimeField,
|
2009-03-18 17:19:24 +08:00
|
|
|
'widget': widgets.AdminSplitDateTime
|
2009-01-16 23:32:31 +08:00
|
|
|
},
|
2009-12-17 23:10:38 +08:00
|
|
|
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},
|
2012-06-05 19:28:32 +08:00
|
|
|
models.BigIntegerField: {'widget': widgets.AdminBigIntegerFieldWidget},
|
2009-12-17 23:10:38 +08:00
|
|
|
models.CharField: {'widget': widgets.AdminTextInputWidget},
|
|
|
|
models.ImageField: {'widget': widgets.AdminFileWidget},
|
|
|
|
models.FileField: {'widget': widgets.AdminFileWidget},
|
2009-01-16 23:32:31 +08:00
|
|
|
}
|
|
|
|
|
2010-02-09 23:02:39 +08:00
|
|
|
csrf_protect_m = method_decorator(csrf_protect)
|
2009-01-16 23:32:31 +08:00
|
|
|
|
2012-07-21 03:20:42 +08:00
|
|
|
class BaseModelAdmin(six.with_metaclass(forms.MediaDefiningClass)):
|
2008-07-19 07:54:34 +08:00
|
|
|
"""Functionality common to both ModelAdmin and InlineAdmin."""
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
raw_id_fields = ()
|
|
|
|
fields = None
|
2008-09-03 01:26:24 +08:00
|
|
|
exclude = None
|
2008-07-19 07:54:34 +08:00
|
|
|
fieldsets = None
|
|
|
|
form = forms.ModelForm
|
|
|
|
filter_vertical = ()
|
|
|
|
filter_horizontal = ()
|
|
|
|
radio_fields = {}
|
|
|
|
prepopulated_fields = {}
|
2009-01-16 23:32:31 +08:00
|
|
|
formfield_overrides = {}
|
2009-12-23 02:29:00 +08:00
|
|
|
readonly_fields = ()
|
2010-12-13 06:56:01 +08:00
|
|
|
ordering = None
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-01-16 23:32:31 +08:00
|
|
|
def __init__(self):
|
2010-04-16 19:11:55 +08:00
|
|
|
overrides = FORMFIELD_FOR_DBFIELD_DEFAULTS.copy()
|
|
|
|
overrides.update(self.formfield_overrides)
|
|
|
|
self.formfield_overrides = overrides
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
def formfield_for_dbfield(self, db_field, **kwargs):
|
|
|
|
"""
|
|
|
|
Hook for specifying the form Field instance for a given database Field
|
|
|
|
instance.
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
If kwargs are given, they're passed to the form Field's constructor.
|
|
|
|
"""
|
2009-01-16 23:32:31 +08:00
|
|
|
request = kwargs.pop("request", None)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-30 19:41:04 +08:00
|
|
|
# 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:
|
2009-01-16 23:32:31 +08:00
|
|
|
return self.formfield_for_choice_field(db_field, request, **kwargs)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-01-16 23:32:31 +08:00
|
|
|
# ForeignKey or ManyToManyFields
|
|
|
|
if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)):
|
|
|
|
# Combine the field kwargs with any options for formfield_overrides.
|
2009-03-18 17:19:24 +08:00
|
|
|
# Make sure the passed in **kwargs override anything in
|
2009-01-16 23:32:31 +08:00
|
|
|
# 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)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-01-16 23:32:31 +08:00
|
|
|
# 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)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-01-16 23:32:31 +08:00
|
|
|
# For non-raw_id fields, wrap the widget with a wrapper that adds
|
|
|
|
# extra HTML -- the "add other" interface -- to the end of the
|
2009-03-18 17:19:24 +08:00
|
|
|
# rendered output. formfield can be None if it came from a
|
2009-01-16 23:32:31 +08:00
|
|
|
# OneToOneField with parent_link=True or a M2M intermediary.
|
|
|
|
if formfield and db_field.name not in self.raw_id_fields:
|
2010-09-11 00:56:36 +08:00
|
|
|
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)
|
2009-01-16 23:32:31 +08:00
|
|
|
|
|
|
|
return formfield
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-01-16 23:32:31 +08:00
|
|
|
# If we've got overrides for the formfield defined, use 'em. **kwargs
|
|
|
|
# passed to formfield_for_dbfield override the defaults.
|
2009-05-06 06:25:35 +08:00
|
|
|
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)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-01-16 23:32:31 +08:00
|
|
|
# For any other type of field, just call its formfield() method.
|
|
|
|
return db_field.formfield(**kwargs)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-01-16 23:32:31 +08:00
|
|
|
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:
|
2008-09-01 04:11:28 +08:00
|
|
|
kwargs['widget'] = widgets.AdminRadioSelect(attrs={
|
|
|
|
'class': get_ul_class(self.radio_fields[db_field.name]),
|
|
|
|
})
|
2009-01-16 23:32:31 +08:00
|
|
|
if 'choices' not in kwargs:
|
2008-09-01 04:11:28 +08:00
|
|
|
kwargs['choices'] = db_field.get_choices(
|
|
|
|
include_blank = db_field.blank,
|
|
|
|
blank_choice=[('', _('None'))]
|
2008-07-30 19:41:04 +08:00
|
|
|
)
|
2009-01-16 23:32:31 +08:00
|
|
|
return db_field.formfield(**kwargs)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-01-16 23:32:31 +08:00
|
|
|
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
|
|
|
|
"""
|
|
|
|
Get a form Field for a ForeignKey.
|
|
|
|
"""
|
2009-12-22 23:18:51 +08:00
|
|
|
db = kwargs.get('using')
|
2009-01-16 23:32:31 +08:00
|
|
|
if db_field.name in self.raw_id_fields:
|
2011-08-02 07:38:11 +08:00
|
|
|
kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel,
|
|
|
|
self.admin_site, using=db)
|
2009-01-16 23:32:31 +08:00
|
|
|
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
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-01-16 23:32:31 +08:00
|
|
|
return db_field.formfield(**kwargs)
|
|
|
|
|
|
|
|
def formfield_for_manytomany(self, db_field, request=None, **kwargs):
|
|
|
|
"""
|
|
|
|
Get a form Field for a ManyToManyField.
|
|
|
|
"""
|
2009-11-03 22:02:49 +08:00
|
|
|
# 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:
|
2009-01-16 23:32:31 +08:00
|
|
|
return None
|
2009-12-22 23:18:51 +08:00
|
|
|
db = kwargs.get('using')
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-01-16 23:32:31 +08:00
|
|
|
if db_field.name in self.raw_id_fields:
|
2011-08-02 07:38:11 +08:00
|
|
|
kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel,
|
|
|
|
self.admin_site, using=db)
|
2009-01-16 23:32:31 +08:00
|
|
|
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))
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
return db_field.formfield(**kwargs)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
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)
|
|
|
|
|
2011-06-12 21:04:53 +08:00
|
|
|
def get_ordering(self, request):
|
|
|
|
"""
|
|
|
|
Hook for specifying field ordering.
|
|
|
|
"""
|
|
|
|
return self.ordering or () # otherwise we might try to *None, which is bad ;)
|
|
|
|
|
2009-12-23 02:29:00 +08:00
|
|
|
def get_readonly_fields(self, request, obj=None):
|
2011-04-22 20:02:25 +08:00
|
|
|
"""
|
|
|
|
Hook for specifying custom readonly fields.
|
|
|
|
"""
|
2009-12-23 02:29:00 +08:00
|
|
|
return self.readonly_fields
|
|
|
|
|
2011-04-22 20:02:25 +08:00
|
|
|
def get_prepopulated_fields(self, request, obj=None):
|
|
|
|
"""
|
|
|
|
Hook for specifying custom prepopulated fields.
|
|
|
|
"""
|
|
|
|
return self.prepopulated_fields
|
|
|
|
|
2010-12-13 06:56:01 +08:00
|
|
|
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.
|
2011-06-12 21:04:53 +08:00
|
|
|
ordering = self.get_ordering(request)
|
2010-12-13 06:56:01 +08:00
|
|
|
if ordering:
|
|
|
|
qs = qs.order_by(*ordering)
|
|
|
|
return qs
|
|
|
|
|
2011-01-28 22:08:25 +08:00
|
|
|
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
|
|
|
|
|
2010-12-23 11:44:38 +08:00
|
|
|
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.
|
2012-04-01 02:22:12 +08:00
|
|
|
rel_name = None
|
2010-12-23 11:44:38 +08:00
|
|
|
for part in parts[:-1]:
|
2012-03-18 05:45:36 +08:00
|
|
|
try:
|
|
|
|
field, _, _, _ = model._meta.get_field_by_name(part)
|
|
|
|
except FieldDoesNotExist:
|
|
|
|
# Lookups on non-existants fields are ok, since they're ignored
|
|
|
|
# later.
|
|
|
|
return True
|
2010-12-23 11:44:38 +08:00
|
|
|
if hasattr(field, 'rel'):
|
|
|
|
model = field.rel.to
|
2012-04-01 02:22:12 +08:00
|
|
|
rel_name = field.rel.get_related_field().name
|
2010-12-23 11:44:38 +08:00
|
|
|
elif isinstance(field, RelatedObject):
|
|
|
|
model = field.model
|
2012-04-01 02:22:12 +08:00
|
|
|
rel_name = model._meta.pk.name
|
2010-12-23 11:44:38 +08:00
|
|
|
else:
|
2012-04-01 02:22:12 +08:00
|
|
|
rel_name = None
|
|
|
|
if rel_name and len(parts) > 1 and parts[-1] == rel_name:
|
2010-12-23 11:44:38 +08:00
|
|
|
parts.pop()
|
|
|
|
|
2012-03-18 05:45:36 +08:00
|
|
|
if len(parts) == 1:
|
2010-12-23 11:44:38 +08:00
|
|
|
return True
|
2012-03-18 05:45:36 +08:00
|
|
|
clean_lookup = LOOKUP_SEP.join(parts)
|
|
|
|
return clean_lookup in self.list_filter or clean_lookup == self.date_hierarchy
|
2010-12-23 11:44:38 +08:00
|
|
|
|
2011-10-07 08:41:25 +08:00
|
|
|
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())
|
2011-06-12 21:04:53 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
class ModelAdmin(BaseModelAdmin):
|
|
|
|
"Encapsulates all admin options and functionality for a given model."
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
list_display = ('__str__',)
|
|
|
|
list_display_links = ()
|
|
|
|
list_filter = ()
|
|
|
|
list_select_related = False
|
|
|
|
list_per_page = 100
|
2011-09-08 21:25:00 +08:00
|
|
|
list_max_show_all = 200
|
2009-03-18 04:51:47 +08:00
|
|
|
list_editable = ()
|
2008-07-19 07:54:34 +08:00
|
|
|
search_fields = ()
|
|
|
|
date_hierarchy = None
|
|
|
|
save_as = False
|
|
|
|
save_on_top = False
|
2010-12-21 22:57:29 +08:00
|
|
|
paginator = Paginator
|
2008-07-19 07:54:34 +08:00
|
|
|
inlines = []
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
# Custom templates (designed to be over-ridden in subclasses)
|
2010-01-13 07:34:03 +08:00
|
|
|
add_form_template = None
|
2008-07-19 07:54:34 +08:00
|
|
|
change_form_template = None
|
|
|
|
change_list_template = None
|
|
|
|
delete_confirmation_template = None
|
2010-04-05 20:02:27 +08:00
|
|
|
delete_selected_confirmation_template = None
|
2008-07-19 07:54:34 +08:00
|
|
|
object_history_template = None
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-03-24 04:22:56 +08:00
|
|
|
# Actions
|
2009-04-07 04:23:33 +08:00
|
|
|
actions = []
|
2009-03-24 04:22:56 +08:00
|
|
|
action_form = helpers.ActionForm
|
|
|
|
actions_on_top = True
|
|
|
|
actions_on_bottom = False
|
2010-01-06 02:24:27 +08:00
|
|
|
actions_selection_counter = True
|
2009-03-24 04:22:56 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
def __init__(self, model, admin_site):
|
|
|
|
self.model = model
|
|
|
|
self.opts = model._meta
|
|
|
|
self.admin_site = admin_site
|
|
|
|
super(ModelAdmin, self).__init__()
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2012-10-20 21:48:38 +08:00
|
|
|
def get_inline_instances(self, request, obj=None):
|
2011-10-07 08:41:25 +08:00
|
|
|
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
|
2012-10-20 21:48:38 +08:00
|
|
|
inline.has_change_permission(request, obj) or
|
|
|
|
inline.has_delete_permission(request, obj)):
|
2011-10-07 08:41:25 +08:00
|
|
|
continue
|
|
|
|
if not inline.has_add_permission(request):
|
|
|
|
inline.max_num = 0
|
|
|
|
inline_instances.append(inline)
|
|
|
|
|
|
|
|
return inline_instances
|
|
|
|
|
2009-01-15 04:22:25 +08:00
|
|
|
def get_urls(self):
|
2011-09-12 06:36:16 +08:00
|
|
|
from django.conf.urls import patterns, url
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-01-15 04:22:25 +08:00
|
|
|
def wrap(view):
|
|
|
|
def wrapper(*args, **kwargs):
|
|
|
|
return self.admin_site.admin_view(view)(*args, **kwargs)
|
|
|
|
return update_wrapper(wrapper, view)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-07-17 00:16:13 +08:00
|
|
|
info = self.model._meta.app_label, self.model._meta.module_name
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-01-15 04:22:25 +08:00
|
|
|
urlpatterns = patterns('',
|
|
|
|
url(r'^$',
|
|
|
|
wrap(self.changelist_view),
|
2009-07-17 00:16:13 +08:00
|
|
|
name='%s_%s_changelist' % info),
|
2009-01-15 04:22:25 +08:00
|
|
|
url(r'^add/$',
|
|
|
|
wrap(self.add_view),
|
2009-07-17 00:16:13 +08:00
|
|
|
name='%s_%s_add' % info),
|
2009-01-15 04:22:25 +08:00
|
|
|
url(r'^(.+)/history/$',
|
|
|
|
wrap(self.history_view),
|
2009-07-17 00:16:13 +08:00
|
|
|
name='%s_%s_history' % info),
|
2009-01-15 04:22:25 +08:00
|
|
|
url(r'^(.+)/delete/$',
|
|
|
|
wrap(self.delete_view),
|
2009-07-17 00:16:13 +08:00
|
|
|
name='%s_%s_delete' % info),
|
2009-01-15 04:22:25 +08:00
|
|
|
url(r'^(.+)/$',
|
|
|
|
wrap(self.change_view),
|
2009-07-17 00:16:13 +08:00
|
|
|
name='%s_%s_change' % info),
|
2009-01-15 04:22:25 +08:00
|
|
|
)
|
|
|
|
return urlpatterns
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-01-15 04:22:25 +08:00
|
|
|
def urls(self):
|
|
|
|
return self.get_urls()
|
|
|
|
urls = property(urls)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2011-08-11 22:07:39 +08:00
|
|
|
@property
|
|
|
|
def media(self):
|
2012-02-05 18:26:05 +08:00
|
|
|
extra = '' if settings.DEBUG else '.min'
|
2011-06-30 17:06:19 +08:00
|
|
|
js = [
|
|
|
|
'core.js',
|
|
|
|
'admin/RelatedObjectLookups.js',
|
2012-02-05 16:35:08 +08:00
|
|
|
'jquery%s.js' % extra,
|
2011-06-30 17:06:19 +08:00
|
|
|
'jquery.init.js'
|
|
|
|
]
|
2009-04-07 04:23:33 +08:00
|
|
|
if self.actions is not None:
|
2012-02-05 16:35:08 +08:00
|
|
|
js.append('actions%s.js' % extra)
|
2008-07-19 07:54:34 +08:00
|
|
|
if self.prepopulated_fields:
|
2012-02-05 16:35:08 +08:00
|
|
|
js.extend(['urlify.js', 'prepopulate%s.js' % extra])
|
2008-07-19 07:54:34 +08:00
|
|
|
if self.opts.get_ordered_objects():
|
2011-06-30 17:06:19 +08:00
|
|
|
js.extend(['getElementsBySelector.js', 'dom-drag.js' , 'admin/ordering.js'])
|
2011-08-11 22:07:39 +08:00
|
|
|
return forms.Media(js=[static('admin/js/%s' % url) for url in js])
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-04-09 03:47:46 +08:00
|
|
|
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),
|
|
|
|
}
|
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
def get_fieldsets(self, request, obj=None):
|
|
|
|
"Hook for specifying fieldsets for the add form."
|
|
|
|
if self.declared_fieldsets:
|
|
|
|
return self.declared_fieldsets
|
2008-08-23 13:05:21 +08:00
|
|
|
form = self.get_form(request, obj)
|
2012-08-08 22:33:15 +08:00
|
|
|
fields = list(form.base_fields) + list(self.get_readonly_fields(request, obj))
|
2009-12-23 02:29:00 +08:00
|
|
|
return [(None, {'fields': fields})]
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-08-16 05:57:36 +08:00
|
|
|
def get_form(self, request, obj=None, **kwargs):
|
2008-07-19 07:54:34 +08:00
|
|
|
"""
|
|
|
|
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
|
2008-09-03 01:26:24 +08:00
|
|
|
if self.exclude is None:
|
|
|
|
exclude = []
|
|
|
|
else:
|
2008-09-27 13:57:10 +08:00
|
|
|
exclude = list(self.exclude)
|
2009-12-23 02:29:00 +08:00
|
|
|
exclude.extend(self.get_readonly_fields(request, obj))
|
2011-08-12 22:14:49 +08:00
|
|
|
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)
|
2009-04-22 23:48:51 +08:00
|
|
|
# if exclude is an empty list we pass None to be consistant with the
|
|
|
|
# default on modelform_factory
|
2009-12-23 02:29:00 +08:00
|
|
|
exclude = exclude or None
|
2008-08-16 05:57:36 +08:00
|
|
|
defaults = {
|
|
|
|
"form": self.form,
|
|
|
|
"fields": fields,
|
2009-12-23 02:29:00 +08:00
|
|
|
"exclude": exclude,
|
2011-03-28 09:40:43 +08:00
|
|
|
"formfield_callback": partial(self.formfield_for_dbfield, request=request),
|
2008-08-16 05:57:36 +08:00
|
|
|
}
|
|
|
|
defaults.update(kwargs)
|
|
|
|
return modelform_factory(self.model, **defaults)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-12-18 18:08:05 +08:00
|
|
|
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
|
|
|
|
|
2009-12-28 22:08:11 +08:00
|
|
|
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
|
|
|
|
|
2009-03-18 04:51:47 +08:00
|
|
|
def get_changelist_form(self, request, **kwargs):
|
|
|
|
"""
|
|
|
|
Returns a Form class for use in the Formset on the changelist page.
|
|
|
|
"""
|
|
|
|
defaults = {
|
2011-03-28 09:40:43 +08:00
|
|
|
"formfield_callback": partial(self.formfield_for_dbfield, request=request),
|
2009-03-18 04:51:47 +08:00
|
|
|
}
|
|
|
|
defaults.update(kwargs)
|
|
|
|
return modelform_factory(self.model, **defaults)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-03-18 04:51:47 +08:00
|
|
|
def get_changelist_formset(self, request, **kwargs):
|
|
|
|
"""
|
2009-03-18 17:19:24 +08:00
|
|
|
Returns a FormSet class for use on the changelist page if list_editable
|
2009-03-18 04:51:47 +08:00
|
|
|
is used.
|
|
|
|
"""
|
|
|
|
defaults = {
|
2011-03-28 09:40:43 +08:00
|
|
|
"formfield_callback": partial(self.formfield_for_dbfield, request=request),
|
2009-03-18 04:51:47 +08:00
|
|
|
}
|
|
|
|
defaults.update(kwargs)
|
2009-03-18 17:19:24 +08:00
|
|
|
return modelformset_factory(self.model,
|
|
|
|
self.get_changelist_form(request), extra=0,
|
2009-03-18 04:51:47 +08:00
|
|
|
fields=self.list_editable, **defaults)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
def get_formsets(self, request, obj=None):
|
2012-10-20 21:48:38 +08:00
|
|
|
for inline in self.get_inline_instances(request, obj):
|
2008-07-19 07:54:34 +08:00
|
|
|
yield inline.get_formset(request, obj)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2010-12-22 03:19:04 +08:00
|
|
|
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)
|
|
|
|
|
2008-08-10 00:45:15 +08:00
|
|
|
def log_addition(self, request, object):
|
|
|
|
"""
|
2009-01-15 04:22:25 +08:00
|
|
|
Log that an object has been successfully added.
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-08-10 00:45:15 +08:00
|
|
|
The default implementation creates an admin LogEntry object.
|
|
|
|
"""
|
|
|
|
from django.contrib.admin.models import LogEntry, ADDITION
|
|
|
|
LogEntry.objects.log_action(
|
2009-01-15 04:22:25 +08:00
|
|
|
user_id = request.user.pk,
|
2008-08-10 00:45:15 +08:00
|
|
|
content_type_id = ContentType.objects.get_for_model(object).pk,
|
|
|
|
object_id = object.pk,
|
2012-07-21 16:00:10 +08:00
|
|
|
object_repr = force_text(object),
|
2008-08-10 00:45:15 +08:00
|
|
|
action_flag = ADDITION
|
|
|
|
)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-08-10 00:45:15 +08:00
|
|
|
def log_change(self, request, object, message):
|
|
|
|
"""
|
2009-01-15 04:22:25 +08:00
|
|
|
Log that an object has been successfully changed.
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-08-10 00:45:15 +08:00
|
|
|
The default implementation creates an admin LogEntry object.
|
|
|
|
"""
|
|
|
|
from django.contrib.admin.models import LogEntry, CHANGE
|
|
|
|
LogEntry.objects.log_action(
|
2009-01-15 04:22:25 +08:00
|
|
|
user_id = request.user.pk,
|
|
|
|
content_type_id = ContentType.objects.get_for_model(object).pk,
|
|
|
|
object_id = object.pk,
|
2012-07-21 16:00:10 +08:00
|
|
|
object_repr = force_text(object),
|
2009-01-15 04:22:25 +08:00
|
|
|
action_flag = CHANGE,
|
2008-08-10 00:45:15 +08:00
|
|
|
change_message = message
|
|
|
|
)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-08-10 00:45:15 +08:00
|
|
|
def log_deletion(self, request, object, object_repr):
|
|
|
|
"""
|
2010-10-13 12:16:31 +08:00
|
|
|
Log that an object will be deleted. Note that this method is called
|
|
|
|
before the deletion.
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-08-10 00:45:15 +08:00
|
|
|
The default implementation creates an admin LogEntry object.
|
|
|
|
"""
|
|
|
|
from django.contrib.admin.models import LogEntry, DELETION
|
|
|
|
LogEntry.objects.log_action(
|
2009-01-15 04:22:25 +08:00
|
|
|
user_id = request.user.id,
|
|
|
|
content_type_id = ContentType.objects.get_for_model(self.model).pk,
|
|
|
|
object_id = object.pk,
|
2008-08-10 00:45:15 +08:00
|
|
|
object_repr = object_repr,
|
|
|
|
action_flag = DELETION
|
|
|
|
)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-03-24 04:22:56 +08:00
|
|
|
def action_checkbox(self, obj):
|
|
|
|
"""
|
|
|
|
A list_display column containing a checkbox widget.
|
|
|
|
"""
|
2012-07-21 16:00:10 +08:00
|
|
|
return helpers.checkbox.render(helpers.ACTION_CHECKBOX_NAME, force_text(obj.pk))
|
2009-03-24 04:22:56 +08:00
|
|
|
action_checkbox.short_description = mark_safe('<input type="checkbox" id="action-toggle" />')
|
|
|
|
action_checkbox.allow_tags = True
|
|
|
|
|
2009-04-07 04:23:33 +08:00
|
|
|
def get_actions(self, request):
|
2009-03-24 04:22:56 +08:00
|
|
|
"""
|
|
|
|
Return a dictionary mapping the names of all actions for this
|
|
|
|
ModelAdmin to a tuple of (callable, name, description) for each action.
|
|
|
|
"""
|
2009-04-07 04:23:33 +08:00
|
|
|
# If self.actions is explicitally set to None that means that we don't
|
|
|
|
# want *any* actions enabled on this page.
|
2011-01-02 09:32:40 +08:00
|
|
|
from django.contrib.admin.views.main import IS_POPUP_VAR
|
|
|
|
if self.actions is None or IS_POPUP_VAR in request.GET:
|
2011-02-02 23:24:31 +08:00
|
|
|
return SortedDict()
|
2009-05-06 06:25:35 +08:00
|
|
|
|
2009-04-07 04:23:33 +08:00
|
|
|
actions = []
|
2009-05-06 06:25:35 +08:00
|
|
|
|
2009-04-07 04:23:33 +08:00
|
|
|
# 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))
|
2009-05-06 06:25:35 +08:00
|
|
|
|
|
|
|
# Then gather them from the model admin and all parent classes,
|
2009-04-07 04:23:33 +08:00
|
|
|
# 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:
|
2009-05-06 06:25:35 +08:00
|
|
|
continue
|
2009-04-07 04:23:33 +08:00
|
|
|
actions.extend([self.get_action(action) for action in class_actions])
|
2009-05-06 06:25:35 +08:00
|
|
|
|
2009-04-07 04:23:33 +08:00
|
|
|
# get_action might have returned None, so filter any of those out.
|
|
|
|
actions = filter(None, actions)
|
2009-05-06 06:25:35 +08:00
|
|
|
|
2011-05-06 06:03:17 +08:00
|
|
|
# Convert the actions into a SortedDict keyed by name.
|
2009-04-07 04:23:33 +08:00
|
|
|
actions = SortedDict([
|
|
|
|
(name, (func, name, desc))
|
|
|
|
for func, name, desc in actions
|
|
|
|
])
|
2009-05-06 06:25:35 +08:00
|
|
|
|
2009-03-24 04:22:56 +08:00
|
|
|
return actions
|
|
|
|
|
2009-04-07 04:23:33 +08:00
|
|
|
def get_action_choices(self, request, default_choices=BLANK_CHOICE_DASH):
|
2009-03-24 04:22:56 +08:00
|
|
|
"""
|
|
|
|
Return a list of choices for use in a form object. Each choice is a
|
|
|
|
tuple (name, description).
|
|
|
|
"""
|
|
|
|
choices = [] + default_choices
|
2012-07-21 03:14:27 +08:00
|
|
|
for func, name, description in six.itervalues(self.get_actions(request)):
|
2009-03-24 04:22:56 +08:00
|
|
|
choice = (name, description % model_format_dict(self.opts))
|
|
|
|
choices.append(choice)
|
|
|
|
return choices
|
|
|
|
|
|
|
|
def get_action(self, action):
|
|
|
|
"""
|
2009-08-14 01:04:42 +08:00
|
|
|
Return a given action from a parameter, which can either be a callable,
|
2009-03-24 04:22:56 +08:00
|
|
|
or the name of a method on the ModelAdmin. Return is a tuple of
|
|
|
|
(callable, name, description).
|
|
|
|
"""
|
2009-04-07 04:23:33 +08:00
|
|
|
# If the action is a callable, just use it.
|
2009-03-24 04:22:56 +08:00
|
|
|
if callable(action):
|
|
|
|
func = action
|
|
|
|
action = action.__name__
|
2009-05-06 06:25:35 +08:00
|
|
|
|
2009-04-07 04:23:33 +08:00
|
|
|
# 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)
|
2009-05-06 06:25:35 +08:00
|
|
|
|
2009-04-07 04:23:33 +08:00
|
|
|
# Finally, look for a named method on the admin site
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
func = self.admin_site.get_action(action)
|
|
|
|
except KeyError:
|
|
|
|
return None
|
2009-05-06 06:25:35 +08:00
|
|
|
|
2009-03-24 04:22:56 +08:00
|
|
|
if hasattr(func, 'short_description'):
|
|
|
|
description = func.short_description
|
|
|
|
else:
|
|
|
|
description = capfirst(action.replace('_', ' '))
|
|
|
|
return func, action, description
|
|
|
|
|
2011-06-09 06:53:55 +08:00
|
|
|
def get_list_display(self, request):
|
|
|
|
"""
|
|
|
|
Return a sequence containing the fields to be displayed on the
|
|
|
|
changelist.
|
|
|
|
"""
|
|
|
|
return self.list_display
|
|
|
|
|
2011-10-26 20:19:18 +08:00
|
|
|
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]
|
|
|
|
|
2008-08-10 00:45:15 +08:00
|
|
|
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')))
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-08-10 00:45:15 +08:00
|
|
|
if formsets:
|
|
|
|
for formset in formsets:
|
|
|
|
for added_object in formset.new_objects:
|
|
|
|
change_message.append(_('Added %(name)s "%(object)s".')
|
2012-07-21 16:00:10 +08:00
|
|
|
% {'name': force_text(added_object._meta.verbose_name),
|
|
|
|
'object': force_text(added_object)})
|
2008-08-10 00:45:15 +08:00
|
|
|
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')),
|
2012-07-21 16:00:10 +08:00
|
|
|
'name': force_text(changed_object._meta.verbose_name),
|
|
|
|
'object': force_text(changed_object)})
|
2008-08-10 00:45:15 +08:00
|
|
|
for deleted_object in formset.deleted_objects:
|
|
|
|
change_message.append(_('Deleted %(name)s "%(object)s".')
|
2012-07-21 16:00:10 +08:00
|
|
|
% {'name': force_text(deleted_object._meta.verbose_name),
|
|
|
|
'object': force_text(deleted_object)})
|
2008-08-10 00:45:15 +08:00
|
|
|
change_message = ' '.join(change_message)
|
|
|
|
return change_message or _('No fields changed.')
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-08-10 00:45:15 +08:00
|
|
|
def message_user(self, request, message):
|
|
|
|
"""
|
2009-01-15 04:22:25 +08:00
|
|
|
Send a message to the user. The default implementation
|
2009-12-10 00:57:23 +08:00
|
|
|
posts a message using the django.contrib.messages backend.
|
2008-08-10 00:45:15 +08:00
|
|
|
"""
|
2009-12-10 00:57:23 +08:00
|
|
|
messages.info(request, message)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2010-01-12 10:29:45 +08:00
|
|
|
def save_form(self, request, form, change):
|
2008-08-10 01:12:48 +08:00
|
|
|
"""
|
2008-08-10 04:52:40 +08:00
|
|
|
Given a ModelForm return an unsaved instance. ``change`` is True if
|
|
|
|
the object is being changed, and False if it's being added.
|
2008-08-10 01:12:48 +08:00
|
|
|
"""
|
2010-01-12 10:29:45 +08:00
|
|
|
return form.save(commit=False)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-08-12 01:20:47 +08:00
|
|
|
def save_model(self, request, obj, form, change):
|
|
|
|
"""
|
|
|
|
Given a model instance save it to the database.
|
|
|
|
"""
|
|
|
|
obj.save()
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2011-03-03 08:24:41 +08:00
|
|
|
def delete_model(self, request, obj):
|
2010-11-22 03:00:40 +08:00
|
|
|
"""
|
|
|
|
Given a model instance delete it from the database.
|
|
|
|
"""
|
|
|
|
obj.delete()
|
|
|
|
|
2008-08-10 01:12:48 +08:00
|
|
|
def save_formset(self, request, form, formset, change):
|
|
|
|
"""
|
2008-08-12 01:20:47 +08:00
|
|
|
Given an inline formset save it to the database.
|
2008-08-10 01:12:48 +08:00
|
|
|
"""
|
2008-08-12 01:20:47 +08:00
|
|
|
formset.save()
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2011-07-04 01:56:43 +08:00
|
|
|
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)
|
|
|
|
|
2008-08-10 04:52:40 +08:00
|
|
|
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,
|
2012-07-03 07:33:44 +08:00
|
|
|
'form_url': form_url,
|
2008-08-10 04:52:40 +08:00
|
|
|
'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,
|
|
|
|
})
|
2010-01-13 07:34:03 +08:00
|
|
|
if add and self.add_form_template is not None:
|
|
|
|
form_template = self.add_form_template
|
|
|
|
else:
|
|
|
|
form_template = self.change_form_template
|
2011-04-23 02:17:16 +08:00
|
|
|
|
|
|
|
return TemplateResponse(request, form_template or [
|
2008-08-10 04:52:40 +08:00
|
|
|
"admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()),
|
|
|
|
"admin/%s/change_form.html" % app_label,
|
|
|
|
"admin/change_form.html"
|
2011-04-23 02:17:16 +08:00
|
|
|
], context, current_app=self.admin_site.name)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2012-10-19 07:58:52 +08:00
|
|
|
def response_add(self, request, obj, post_url_continue='../%s/',
|
|
|
|
continue_editing_url=None, add_another_url=None,
|
|
|
|
hasperm_url=None, noperm_url=None):
|
2008-07-19 07:54:34 +08:00
|
|
|
"""
|
2008-08-10 04:52:40 +08:00
|
|
|
Determines the HttpResponse for the add_view stage.
|
2012-10-19 07:58:52 +08:00
|
|
|
|
|
|
|
:param request: HttpRequest instance.
|
|
|
|
:param obj: Object just added.
|
|
|
|
:param post_url_continue: Deprecated/undocumented.
|
|
|
|
:param continue_editing_url: URL where user will be redirected after
|
|
|
|
pressing 'Save and continue editing'.
|
|
|
|
:param add_another_url: URL where user will be redirected after
|
|
|
|
pressing 'Save and add another'.
|
|
|
|
:param hasperm_url: URL to redirect after a successful object creation
|
|
|
|
when the user has change permissions.
|
|
|
|
:param noperm_url: URL to redirect after a successful object creation
|
|
|
|
when the user has no change permissions.
|
|
|
|
"""
|
|
|
|
if post_url_continue != '../%s/':
|
|
|
|
warnings.warn("The undocumented 'post_url_continue' argument to "
|
|
|
|
"ModelAdmin.response_add() is deprecated, use the new "
|
|
|
|
"*_url arguments instead.", DeprecationWarning,
|
|
|
|
stacklevel=2)
|
2008-08-10 04:52:40 +08:00
|
|
|
opts = obj._meta
|
2012-10-19 07:58:52 +08:00
|
|
|
pk_value = obj.pk
|
|
|
|
app_label = opts.app_label
|
|
|
|
model_name = opts.module_name
|
|
|
|
site_name = self.admin_site.name
|
|
|
|
|
|
|
|
msg_dict = {'name': force_text(opts.verbose_name), 'obj': force_text(obj)}
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
# Here, we distinguish between different save types by checking for
|
|
|
|
# the presence of keys in request.POST.
|
2010-10-29 12:24:53 +08:00
|
|
|
if "_continue" in request.POST:
|
2012-10-19 07:58:52 +08:00
|
|
|
msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % msg_dict
|
|
|
|
self.message_user(request, msg)
|
|
|
|
if continue_editing_url is None:
|
|
|
|
continue_editing_url = 'admin:%s_%s_change' % (app_label, model_name)
|
|
|
|
url = reverse(continue_editing_url, args=(quote(pk_value),),
|
|
|
|
current_app=site_name)
|
2010-10-29 12:24:53 +08:00
|
|
|
if "_popup" in request.POST:
|
2012-10-19 07:58:52 +08:00
|
|
|
url += "?_popup=1"
|
|
|
|
return HttpResponseRedirect(url)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2010-10-29 12:24:53 +08:00
|
|
|
if "_popup" in request.POST:
|
2011-11-02 05:50:17 +08:00
|
|
|
return HttpResponse(
|
|
|
|
'<!DOCTYPE html><html><head><title></title></head><body>'
|
|
|
|
'<script type="text/javascript">opener.dismissAddAnotherPopup(window, "%s", "%s");</script></body></html>' % \
|
2012-07-21 16:00:10 +08:00
|
|
|
# escape() calls force_text.
|
2011-01-03 01:34:52 +08:00
|
|
|
(escape(pk_value), escapejs(obj)))
|
2010-10-29 12:24:53 +08:00
|
|
|
elif "_addanother" in request.POST:
|
2012-10-19 07:58:52 +08:00
|
|
|
msg = _('The %(name)s "%(obj)s" was added successfully. You may add another %(name)s below.') % msg_dict
|
|
|
|
self.message_user(request, msg)
|
|
|
|
if add_another_url is None:
|
|
|
|
add_another_url = 'admin:%s_%s_add' % (app_label, model_name)
|
|
|
|
url = reverse(add_another_url, current_app=site_name)
|
|
|
|
return HttpResponseRedirect(url)
|
2008-07-19 07:54:34 +08:00
|
|
|
else:
|
2012-10-19 07:58:52 +08:00
|
|
|
msg = _('The %(name)s "%(obj)s" was added successfully.') % msg_dict
|
2008-08-10 00:45:15 +08:00
|
|
|
self.message_user(request, msg)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
# 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):
|
2012-10-19 07:58:52 +08:00
|
|
|
if hasperm_url is None:
|
|
|
|
hasperm_url = 'admin:%s_%s_changelist' % (app_label, model_name)
|
|
|
|
url = reverse(hasperm_url, current_app=site_name)
|
2008-07-19 07:54:34 +08:00
|
|
|
else:
|
2012-10-19 07:58:52 +08:00
|
|
|
if noperm_url is None:
|
|
|
|
noperm_url = 'admin:index'
|
|
|
|
url = reverse(noperm_url, current_app=site_name)
|
|
|
|
return HttpResponseRedirect(url)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2012-10-19 07:58:52 +08:00
|
|
|
def response_change(self, request, obj, continue_editing_url=None,
|
|
|
|
save_as_new_url=None, add_another_url=None,
|
|
|
|
hasperm_url=None, noperm_url=None):
|
2008-07-19 07:54:34 +08:00
|
|
|
"""
|
2008-08-10 04:52:40 +08:00
|
|
|
Determines the HttpResponse for the change_view stage.
|
2012-10-19 07:58:52 +08:00
|
|
|
|
|
|
|
:param request: HttpRequest instance.
|
|
|
|
:param obj: Object just modified.
|
|
|
|
:param continue_editing_url: URL where user will be redirected after
|
|
|
|
pressing 'Save and continue editing'.
|
|
|
|
:param save_as_new_url: URL where user will be redirected after pressing
|
|
|
|
'Save as new' (when applicable).
|
|
|
|
:param add_another_url: URL where user will be redirected after pressing
|
|
|
|
'Save and add another'.
|
|
|
|
:param hasperm_url: URL to redirect after a successful object edition when
|
|
|
|
the user has change permissions.
|
|
|
|
:param noperm_url: URL to redirect after a successful object edition when
|
|
|
|
the user has no change permissions.
|
2008-07-19 07:54:34 +08:00
|
|
|
"""
|
2008-08-10 04:52:40 +08:00
|
|
|
opts = obj._meta
|
2011-02-21 07:09:25 +08:00
|
|
|
|
2012-10-19 07:58:52 +08:00
|
|
|
app_label = opts.app_label
|
|
|
|
model_name = opts.module_name
|
|
|
|
site_name = self.admin_site.name
|
|
|
|
verbose_name = opts.verbose_name
|
2011-09-21 02:30:06 +08:00
|
|
|
# Handle proxy models automatically created by .only() or .defer().
|
|
|
|
# Refs #14529
|
2011-02-21 07:09:25 +08:00
|
|
|
if obj._deferred:
|
|
|
|
opts_ = opts.proxy_for_model._meta
|
|
|
|
verbose_name = opts_.verbose_name
|
2012-10-19 07:58:52 +08:00
|
|
|
model_name = opts_.module_name
|
2011-02-21 07:09:25 +08:00
|
|
|
|
2012-10-19 07:58:52 +08:00
|
|
|
msg_dict = {'name': force_text(verbose_name), 'obj': force_text(obj)}
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2010-10-29 12:24:53 +08:00
|
|
|
if "_continue" in request.POST:
|
2012-10-19 07:58:52 +08:00
|
|
|
msg = _('The %(name)s "%(obj)s" was changed successfully. You may edit it again below.') % msg_dict
|
|
|
|
self.message_user(request, msg)
|
|
|
|
if continue_editing_url is None:
|
|
|
|
continue_editing_url = 'admin:%s_%s_change' % (app_label, model_name)
|
|
|
|
url = reverse(continue_editing_url, args=(quote(obj.pk),),
|
|
|
|
current_app=site_name)
|
|
|
|
if "_popup" in request.POST:
|
|
|
|
url += "?_popup=1"
|
|
|
|
return HttpResponseRedirect(url)
|
2010-10-29 12:24:53 +08:00
|
|
|
elif "_saveasnew" in request.POST:
|
2012-10-19 07:58:52 +08:00
|
|
|
msg = _('The %(name)s "%(obj)s" was added successfully. You may edit it again below.') % msg_dict
|
2008-08-10 00:45:15 +08:00
|
|
|
self.message_user(request, msg)
|
2012-10-19 07:58:52 +08:00
|
|
|
if save_as_new_url is None:
|
|
|
|
save_as_new_url = 'admin:%s_%s_change' % (app_label, model_name)
|
|
|
|
url = reverse(save_as_new_url, args=(quote(obj.pk),),
|
|
|
|
current_app=site_name)
|
|
|
|
return HttpResponseRedirect(url)
|
2010-10-29 12:24:53 +08:00
|
|
|
elif "_addanother" in request.POST:
|
2012-10-19 07:58:52 +08:00
|
|
|
msg = _('The %(name)s "%(obj)s" was changed successfully. You may add another %(name)s below.') % msg_dict
|
|
|
|
self.message_user(request, msg)
|
|
|
|
if add_another_url is None:
|
|
|
|
add_another_url = 'admin:%s_%s_add' % (app_label, model_name)
|
|
|
|
url = reverse(add_another_url, current_app=site_name)
|
|
|
|
return HttpResponseRedirect(url)
|
2008-07-19 07:54:34 +08:00
|
|
|
else:
|
2012-10-19 07:58:52 +08:00
|
|
|
msg = _('The %(name)s "%(obj)s" was changed successfully.') % msg_dict
|
2008-08-10 00:45:15 +08:00
|
|
|
self.message_user(request, msg)
|
2011-02-19 22:05:07 +08:00
|
|
|
# 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):
|
2012-10-19 07:58:52 +08:00
|
|
|
if hasperm_url is None:
|
|
|
|
hasperm_url = 'admin:%s_%s_changelist' % (app_label,
|
|
|
|
model_name)
|
|
|
|
url = reverse(hasperm_url, current_app=site_name)
|
2011-02-19 22:05:07 +08:00
|
|
|
else:
|
2012-10-19 07:58:52 +08:00
|
|
|
if noperm_url is None:
|
|
|
|
noperm_url = 'admin:index'
|
|
|
|
url = reverse(noperm_url, current_app=site_name)
|
|
|
|
return HttpResponseRedirect(url)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-03-24 04:22:56 +08:00
|
|
|
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.
|
|
|
|
"""
|
2010-02-23 22:48:15 +08:00
|
|
|
|
2009-03-24 04:22:56 +08:00
|
|
|
# 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)
|
2009-05-06 06:25:35 +08:00
|
|
|
|
2009-04-07 04:23:33 +08:00
|
|
|
# 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
|
2009-05-06 06:25:35 +08:00
|
|
|
|
2009-03-24 04:22:56 +08:00
|
|
|
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']
|
2010-01-26 23:03:23 +08:00
|
|
|
select_across = action_form.cleaned_data['select_across']
|
2009-03-24 04:22:56 +08:00
|
|
|
func, name, description = self.get_actions(request)[action]
|
2009-03-24 16:22:23 +08:00
|
|
|
|
|
|
|
# Get the list of selected PKs. If nothing's selected, we can't
|
2010-01-26 23:03:23 +08:00
|
|
|
# perform an action on it, so bail. Except we want to perform
|
2010-05-03 07:02:16 +08:00
|
|
|
# the action explicitly on all objects.
|
2009-03-24 04:22:56 +08:00
|
|
|
selected = request.POST.getlist(helpers.ACTION_CHECKBOX_NAME)
|
2010-01-26 23:03:23 +08:00
|
|
|
if not selected and not select_across:
|
2009-12-13 05:51:47 +08:00
|
|
|
# Reminder that something needs to be selected or nothing will happen
|
2010-05-03 07:02:16 +08:00
|
|
|
msg = _("Items must be selected in order to perform "
|
|
|
|
"actions on them. No items have been changed.")
|
2009-12-15 00:01:43 +08:00
|
|
|
self.message_user(request, msg)
|
2009-03-24 04:22:56 +08:00
|
|
|
return None
|
|
|
|
|
2010-01-26 23:03:23 +08:00
|
|
|
if not select_across:
|
|
|
|
# Perform the action only on the selected objects
|
|
|
|
queryset = queryset.filter(pk__in=selected)
|
|
|
|
|
|
|
|
response = func(self, request, queryset)
|
2009-03-24 16:22:23 +08:00
|
|
|
|
2009-03-24 04:22:56 +08:00
|
|
|
# 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:
|
2010-09-08 04:55:53 +08:00
|
|
|
return HttpResponseRedirect(request.get_full_path())
|
2009-12-13 05:51:47 +08:00
|
|
|
else:
|
2009-12-15 00:01:43 +08:00
|
|
|
msg = _("No action selected.")
|
|
|
|
self.message_user(request, msg)
|
2010-05-03 07:02:16 +08:00
|
|
|
return None
|
2009-03-24 04:22:56 +08:00
|
|
|
|
2010-02-09 23:02:39 +08:00
|
|
|
@csrf_protect_m
|
Fixed #9977 - CsrfMiddleware gets template tag added, session dependency removed, and turned on by default.
This is a large change to CSRF protection for Django. It includes:
* removing the dependency on the session framework.
* deprecating CsrfResponseMiddleware, and replacing with a core template tag.
* turning on CSRF protection by default by adding CsrfViewMiddleware to
the default value of MIDDLEWARE_CLASSES.
* protecting all contrib apps (whatever is in settings.py)
using a decorator.
For existing users of the CSRF functionality, it should be a seamless update,
but please note that it includes DEPRECATION of features in Django 1.1,
and there are upgrade steps which are detailed in the docs.
Many thanks to 'Glenn' and 'bthomas', who did a lot of the thinking and work
on the patch, and to lots of other people including Simon Willison and
Russell Keith-Magee who refined the ideas.
Details of the rationale for these changes is found here:
http://code.djangoproject.com/wiki/CsrfProtection
As of this commit, the CSRF code is mainly in 'contrib'. The code will be
moved to core in a separate commit, to make the changeset as readable as
possible.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@11660 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2009-10-27 07:23:07 +08:00
|
|
|
@transaction.commit_on_success
|
2008-07-19 07:54:34 +08:00
|
|
|
def add_view(self, request, form_url='', extra_context=None):
|
|
|
|
"The 'add' admin view for this model."
|
|
|
|
model = self.model
|
|
|
|
opts = model._meta
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
if not self.has_add_permission(request):
|
|
|
|
raise PermissionDenied
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
ModelForm = self.get_form(request)
|
2008-08-10 04:52:40 +08:00
|
|
|
formsets = []
|
2012-10-20 21:48:38 +08:00
|
|
|
inline_instances = self.get_inline_instances(request, None)
|
2008-07-19 07:54:34 +08:00
|
|
|
if request.method == 'POST':
|
|
|
|
form = ModelForm(request.POST, request.FILES)
|
2008-08-10 04:52:40 +08:00
|
|
|
if form.is_valid():
|
2010-01-12 10:29:45 +08:00
|
|
|
new_object = self.save_form(request, form, change=False)
|
2008-08-10 04:52:40 +08:00
|
|
|
form_validated = True
|
|
|
|
else:
|
|
|
|
form_validated = False
|
|
|
|
new_object = self.model()
|
2009-03-10 19:19:26 +08:00
|
|
|
prefixes = {}
|
2011-10-07 08:41:25 +08:00
|
|
|
for FormSet, inline in zip(self.get_formsets(request), inline_instances):
|
2009-03-10 19:19:26 +08:00
|
|
|
prefix = FormSet.get_default_prefix()
|
|
|
|
prefixes[prefix] = prefixes.get(prefix, 0) + 1
|
2011-09-21 22:00:58 +08:00
|
|
|
if prefixes[prefix] != 1 or not prefix:
|
2009-03-10 19:19:26 +08:00
|
|
|
prefix = "%s-%s" % (prefix, prefixes[prefix])
|
2008-08-10 04:52:40 +08:00
|
|
|
formset = FormSet(data=request.POST, files=request.FILES,
|
|
|
|
instance=new_object,
|
2010-10-29 12:24:53 +08:00
|
|
|
save_as_new="_saveasnew" in request.POST,
|
2009-12-22 23:18:51 +08:00
|
|
|
prefix=prefix, queryset=inline.queryset(request))
|
2008-08-10 04:52:40 +08:00
|
|
|
formsets.append(formset)
|
|
|
|
if all_valid(formsets) and form_validated:
|
2011-07-04 01:56:43 +08:00
|
|
|
self.save_model(request, new_object, form, False)
|
|
|
|
self.save_related(request, form, formsets, False)
|
2008-08-10 04:52:40 +08:00
|
|
|
self.log_addition(request, new_object)
|
|
|
|
return self.response_add(request, new_object)
|
2008-07-19 07:54:34 +08:00
|
|
|
else:
|
2008-08-30 00:09:29 +08:00
|
|
|
# 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)
|
2008-08-30 03:50:22 +08:00
|
|
|
except models.FieldDoesNotExist:
|
2008-08-30 02:12:31 +08:00
|
|
|
continue
|
2008-08-30 00:09:29 +08:00
|
|
|
if isinstance(f, models.ManyToManyField):
|
|
|
|
initial[k] = initial[k].split(",")
|
|
|
|
form = ModelForm(initial=initial)
|
2009-03-10 19:19:26 +08:00
|
|
|
prefixes = {}
|
2011-10-07 08:41:25 +08:00
|
|
|
for FormSet, inline in zip(self.get_formsets(request), inline_instances):
|
2009-03-10 19:19:26 +08:00
|
|
|
prefix = FormSet.get_default_prefix()
|
|
|
|
prefixes[prefix] = prefixes.get(prefix, 0) + 1
|
2011-09-21 22:00:58 +08:00
|
|
|
if prefixes[prefix] != 1 or not prefix:
|
2009-03-10 19:19:26 +08:00
|
|
|
prefix = "%s-%s" % (prefix, prefixes[prefix])
|
2009-12-22 23:18:51 +08:00
|
|
|
formset = FormSet(instance=self.model(), prefix=prefix,
|
|
|
|
queryset=inline.queryset(request))
|
2008-08-10 04:52:40 +08:00
|
|
|
formsets.append(formset)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-12-23 02:29:00 +08:00
|
|
|
adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)),
|
2011-04-22 20:02:25 +08:00
|
|
|
self.get_prepopulated_fields(request),
|
|
|
|
self.get_readonly_fields(request),
|
2009-12-23 02:29:00 +08:00
|
|
|
model_admin=self)
|
2008-07-19 07:54:34 +08:00
|
|
|
media = self.media + adminForm.media
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
inline_admin_formsets = []
|
2011-10-07 08:41:25 +08:00
|
|
|
for inline, formset in zip(inline_instances, formsets):
|
2008-07-19 07:54:34 +08:00
|
|
|
fieldsets = list(inline.get_fieldsets(request))
|
2009-12-23 02:29:00 +08:00
|
|
|
readonly = list(inline.get_readonly_fields(request))
|
2011-04-22 20:02:25 +08:00
|
|
|
prepopulated = dict(inline.get_prepopulated_fields(request))
|
2009-12-23 02:29:00 +08:00
|
|
|
inline_admin_formset = helpers.InlineAdminFormSet(inline, formset,
|
2011-04-22 20:02:25 +08:00
|
|
|
fieldsets, prepopulated, readonly, model_admin=self)
|
2008-07-19 07:54:34 +08:00
|
|
|
inline_admin_formsets.append(inline_admin_formset)
|
2008-08-16 01:36:33 +08:00
|
|
|
media = media + inline_admin_formset.media
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
context = {
|
2012-07-21 16:00:10 +08:00
|
|
|
'title': _('Add %s') % force_text(opts.verbose_name),
|
2008-07-19 07:54:34 +08:00
|
|
|
'adminform': adminForm,
|
2010-10-29 12:24:53 +08:00
|
|
|
'is_popup': "_popup" in request.REQUEST,
|
2012-02-10 02:57:47 +08:00
|
|
|
'media': media,
|
2008-07-19 07:54:34 +08:00
|
|
|
'inline_admin_formsets': inline_admin_formsets,
|
2008-08-18 23:49:58 +08:00
|
|
|
'errors': helpers.AdminErrorList(form, formsets),
|
2008-09-08 13:45:17 +08:00
|
|
|
'app_label': opts.app_label,
|
2008-07-19 07:54:34 +08:00
|
|
|
}
|
|
|
|
context.update(extra_context or {})
|
2009-05-08 20:53:14 +08:00
|
|
|
return self.render_change_form(request, context, form_url=form_url, add=True)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2010-02-09 23:02:39 +08:00
|
|
|
@csrf_protect_m
|
Fixed #9977 - CsrfMiddleware gets template tag added, session dependency removed, and turned on by default.
This is a large change to CSRF protection for Django. It includes:
* removing the dependency on the session framework.
* deprecating CsrfResponseMiddleware, and replacing with a core template tag.
* turning on CSRF protection by default by adding CsrfViewMiddleware to
the default value of MIDDLEWARE_CLASSES.
* protecting all contrib apps (whatever is in settings.py)
using a decorator.
For existing users of the CSRF functionality, it should be a seamless update,
but please note that it includes DEPRECATION of features in Django 1.1,
and there are upgrade steps which are detailed in the docs.
Many thanks to 'Glenn' and 'bthomas', who did a lot of the thinking and work
on the patch, and to lots of other people including Simon Willison and
Russell Keith-Magee who refined the ideas.
Details of the rationale for these changes is found here:
http://code.djangoproject.com/wiki/CsrfProtection
As of this commit, the CSRF code is mainly in 'contrib'. The code will be
moved to core in a separate commit, to make the changeset as readable as
possible.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@11660 bcc190cf-cafb-0310-a4f2-bffc1f526a37
2009-10-27 07:23:07 +08:00
|
|
|
@transaction.commit_on_success
|
2012-02-10 02:56:32 +08:00
|
|
|
def change_view(self, request, object_id, form_url='', extra_context=None):
|
2008-07-19 07:54:34 +08:00
|
|
|
"The 'change' admin view for this model."
|
|
|
|
model = self.model
|
|
|
|
opts = model._meta
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-12-28 22:08:11 +08:00
|
|
|
obj = self.get_object(request, unquote(object_id))
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
if not self.has_change_permission(request, obj):
|
|
|
|
raise PermissionDenied
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
if obj is None:
|
2012-07-21 16:00:10 +08:00
|
|
|
raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_text(opts.verbose_name), 'key': escape(object_id)})
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2010-10-29 12:24:53 +08:00
|
|
|
if request.method == 'POST' and "_saveasnew" in request.POST:
|
2011-09-21 02:30:06 +08:00
|
|
|
return self.add_view(request, form_url=reverse('admin:%s_%s_add' %
|
|
|
|
(opts.app_label, opts.module_name),
|
|
|
|
current_app=self.admin_site.name))
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
ModelForm = self.get_form(request, obj)
|
2008-08-10 04:52:40 +08:00
|
|
|
formsets = []
|
2012-10-20 21:48:38 +08:00
|
|
|
inline_instances = self.get_inline_instances(request, obj)
|
2008-07-19 07:54:34 +08:00
|
|
|
if request.method == 'POST':
|
|
|
|
form = ModelForm(request.POST, request.FILES, instance=obj)
|
2008-08-10 04:52:40 +08:00
|
|
|
if form.is_valid():
|
|
|
|
form_validated = True
|
|
|
|
new_object = self.save_form(request, form, change=True)
|
|
|
|
else:
|
|
|
|
form_validated = False
|
|
|
|
new_object = obj
|
2009-03-10 19:19:26 +08:00
|
|
|
prefixes = {}
|
2011-10-07 08:41:25 +08:00
|
|
|
for FormSet, inline in zip(self.get_formsets(request, new_object), inline_instances):
|
2009-03-10 19:19:26 +08:00
|
|
|
prefix = FormSet.get_default_prefix()
|
|
|
|
prefixes[prefix] = prefixes.get(prefix, 0) + 1
|
2011-09-21 22:00:58 +08:00
|
|
|
if prefixes[prefix] != 1 or not prefix:
|
2009-03-10 19:19:26 +08:00
|
|
|
prefix = "%s-%s" % (prefix, prefixes[prefix])
|
2008-08-10 04:52:40 +08:00
|
|
|
formset = FormSet(request.POST, request.FILES,
|
2009-12-22 23:18:51 +08:00
|
|
|
instance=new_object, prefix=prefix,
|
|
|
|
queryset=inline.queryset(request))
|
|
|
|
|
2008-08-10 04:52:40 +08:00
|
|
|
formsets.append(formset)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-08-10 04:52:40 +08:00
|
|
|
if all_valid(formsets) and form_validated:
|
2011-07-04 01:56:43 +08:00
|
|
|
self.save_model(request, new_object, form, True)
|
|
|
|
self.save_related(request, form, formsets, True)
|
2008-08-10 04:52:40 +08:00
|
|
|
change_message = self.construct_change_message(request, form, formsets)
|
|
|
|
self.log_change(request, new_object, change_message)
|
|
|
|
return self.response_change(request, new_object)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
else:
|
|
|
|
form = ModelForm(instance=obj)
|
2009-03-10 19:19:26 +08:00
|
|
|
prefixes = {}
|
2011-10-07 08:41:25 +08:00
|
|
|
for FormSet, inline in zip(self.get_formsets(request, obj), inline_instances):
|
2009-03-10 19:19:26 +08:00
|
|
|
prefix = FormSet.get_default_prefix()
|
|
|
|
prefixes[prefix] = prefixes.get(prefix, 0) + 1
|
2011-09-21 22:00:58 +08:00
|
|
|
if prefixes[prefix] != 1 or not prefix:
|
2009-03-10 19:19:26 +08:00
|
|
|
prefix = "%s-%s" % (prefix, prefixes[prefix])
|
2009-12-22 23:18:51 +08:00
|
|
|
formset = FormSet(instance=obj, prefix=prefix,
|
|
|
|
queryset=inline.queryset(request))
|
2008-08-10 04:52:40 +08:00
|
|
|
formsets.append(formset)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-12-23 02:29:00 +08:00
|
|
|
adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj),
|
2011-04-22 20:02:25 +08:00
|
|
|
self.get_prepopulated_fields(request, obj),
|
|
|
|
self.get_readonly_fields(request, obj),
|
2009-12-23 02:29:00 +08:00
|
|
|
model_admin=self)
|
2008-07-19 07:54:34 +08:00
|
|
|
media = self.media + adminForm.media
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
inline_admin_formsets = []
|
2011-10-07 08:41:25 +08:00
|
|
|
for inline, formset in zip(inline_instances, formsets):
|
2008-07-19 07:54:34 +08:00
|
|
|
fieldsets = list(inline.get_fieldsets(request, obj))
|
2009-12-23 02:29:00 +08:00
|
|
|
readonly = list(inline.get_readonly_fields(request, obj))
|
2011-04-22 20:02:25 +08:00
|
|
|
prepopulated = dict(inline.get_prepopulated_fields(request, obj))
|
2009-12-23 02:29:00 +08:00
|
|
|
inline_admin_formset = helpers.InlineAdminFormSet(inline, formset,
|
2011-04-22 20:02:25 +08:00
|
|
|
fieldsets, prepopulated, readonly, model_admin=self)
|
2008-07-19 07:54:34 +08:00
|
|
|
inline_admin_formsets.append(inline_admin_formset)
|
2008-07-23 11:25:41 +08:00
|
|
|
media = media + inline_admin_formset.media
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
context = {
|
2012-07-21 16:00:10 +08:00
|
|
|
'title': _('Change %s') % force_text(opts.verbose_name),
|
2008-07-19 07:54:34 +08:00
|
|
|
'adminform': adminForm,
|
|
|
|
'object_id': object_id,
|
|
|
|
'original': obj,
|
2010-10-29 12:24:53 +08:00
|
|
|
'is_popup': "_popup" in request.REQUEST,
|
2012-02-10 02:57:47 +08:00
|
|
|
'media': media,
|
2008-07-19 07:54:34 +08:00
|
|
|
'inline_admin_formsets': inline_admin_formsets,
|
2008-08-18 23:49:58 +08:00
|
|
|
'errors': helpers.AdminErrorList(form, formsets),
|
2008-09-08 13:45:17 +08:00
|
|
|
'app_label': opts.app_label,
|
2008-07-19 07:54:34 +08:00
|
|
|
}
|
|
|
|
context.update(extra_context or {})
|
2012-02-10 02:56:32 +08:00
|
|
|
return self.render_change_form(request, context, change=True, obj=obj, form_url=form_url)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2010-02-09 23:02:39 +08:00
|
|
|
@csrf_protect_m
|
2008-07-19 07:54:34 +08:00
|
|
|
def changelist_view(self, request, extra_context=None):
|
2011-10-26 20:19:18 +08:00
|
|
|
"""
|
|
|
|
The 'change list' admin view for this model.
|
|
|
|
"""
|
2009-12-18 18:08:05 +08:00
|
|
|
from django.contrib.admin.views.main import ERROR_FLAG
|
2008-07-19 07:54:34 +08:00
|
|
|
opts = self.model._meta
|
|
|
|
app_label = opts.app_label
|
|
|
|
if not self.has_change_permission(request, None):
|
|
|
|
raise PermissionDenied
|
2009-05-06 06:25:35 +08:00
|
|
|
|
2011-10-26 17:37:07 +08:00
|
|
|
list_display = self.get_list_display(request)
|
2011-10-26 20:19:18 +08:00
|
|
|
list_display_links = self.get_list_display_links(request, list_display)
|
2011-10-26 17:37:07 +08:00
|
|
|
|
2011-10-26 20:19:18 +08:00
|
|
|
# Check actions to see if any are available on this changelist
|
|
|
|
actions = self.get_actions(request)
|
2011-10-26 17:37:07 +08:00
|
|
|
if actions:
|
|
|
|
# Add the action checkboxes if there are any actions available.
|
|
|
|
list_display = ['action_checkbox'] + list(list_display)
|
2009-05-06 06:25:35 +08:00
|
|
|
|
2009-12-18 18:08:05 +08:00
|
|
|
ChangeList = self.get_changelist(request)
|
2008-07-19 07:54:34 +08:00
|
|
|
try:
|
2011-10-26 17:37:07 +08:00
|
|
|
cl = ChangeList(request, self.model, list_display,
|
|
|
|
list_display_links, self.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)
|
2008-07-19 07:54:34 +08:00
|
|
|
except IncorrectLookupParameters:
|
|
|
|
# Wacky lookup parameters were given, so redirect to the main
|
|
|
|
# changelist page, without parameters, and pass an 'invalid=1'
|
2010-05-03 07:02:16 +08:00
|
|
|
# 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.
|
2008-07-19 07:54:34 +08:00
|
|
|
if ERROR_FLAG in request.GET.keys():
|
2011-04-23 02:17:16 +08:00
|
|
|
return SimpleTemplateResponse('admin/invalid_setup.html', {
|
|
|
|
'title': _('Database error'),
|
|
|
|
})
|
2008-07-19 07:54:34 +08:00
|
|
|
return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')
|
2009-05-06 06:25:35 +08:00
|
|
|
|
2010-05-03 07:02:16 +08:00
|
|
|
# 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:
|
2011-05-03 18:44:23 +08:00
|
|
|
response = self.response_action(request, queryset=cl.get_query_set(request))
|
2010-05-03 07:02:16 +08:00
|
|
|
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:
|
2011-05-03 18:44:23 +08:00
|
|
|
response = self.response_action(request, queryset=cl.get_query_set(request))
|
2010-05-03 07:02:16 +08:00
|
|
|
if response:
|
|
|
|
return response
|
|
|
|
else:
|
|
|
|
action_failed = True
|
2009-03-24 16:22:23 +08:00
|
|
|
|
2009-03-18 04:51:47 +08:00
|
|
|
# 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
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-03-18 04:51:47 +08:00
|
|
|
# Handle POSTed bulk-edit data.
|
2011-01-02 09:32:40 +08:00
|
|
|
if (request.method == "POST" and cl.list_editable and
|
2010-05-03 07:02:16 +08:00
|
|
|
'_save' in request.POST and not action_failed):
|
2009-03-18 04:51:47 +08:00
|
|
|
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)
|
2011-08-12 22:14:24 +08:00
|
|
|
self.save_related(request, form, formsets=[], change=True)
|
2009-03-18 04:51:47 +08:00
|
|
|
change_msg = self.construct_change_message(request, form, None)
|
|
|
|
self.log_change(request, obj, change_msg)
|
|
|
|
changecount += 1
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-03-18 04:51:47 +08:00
|
|
|
if changecount:
|
2009-03-18 17:19:24 +08:00
|
|
|
if changecount == 1:
|
2012-07-21 16:00:10 +08:00
|
|
|
name = force_text(opts.verbose_name)
|
2009-03-18 17:19:24 +08:00
|
|
|
else:
|
2012-07-21 16:00:10 +08:00
|
|
|
name = force_text(opts.verbose_name_plural)
|
2009-05-06 06:25:35 +08:00
|
|
|
msg = ungettext("%(count)s %(name)s was changed successfully.",
|
|
|
|
"%(count)s %(name)s were changed successfully.",
|
|
|
|
changecount) % {'count': changecount,
|
|
|
|
'name': name,
|
2012-07-21 16:00:10 +08:00
|
|
|
'obj': force_text(obj)}
|
2009-03-18 04:51:47 +08:00
|
|
|
self.message_user(request, msg)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-03-18 04:51:47 +08:00
|
|
|
return HttpResponseRedirect(request.get_full_path())
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-03-18 04:51:47 +08:00
|
|
|
# Handle GET -- construct a formset for display.
|
2011-01-02 09:32:40 +08:00
|
|
|
elif cl.list_editable:
|
2009-03-18 04:51:47 +08:00
|
|
|
FormSet = self.get_changelist_formset(request)
|
|
|
|
formset = cl.formset = FormSet(queryset=cl.result_list)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-03-18 04:51:47 +08:00
|
|
|
# Build the list of media to be used by the formset.
|
|
|
|
if formset:
|
|
|
|
media = self.media + formset.media
|
|
|
|
else:
|
2009-03-24 04:22:56 +08:00
|
|
|
media = self.media
|
2009-03-24 16:22:23 +08:00
|
|
|
|
2009-03-24 04:22:56 +08:00
|
|
|
# Build the action form and populate it with available actions.
|
2009-04-07 04:23:33 +08:00
|
|
|
if actions:
|
|
|
|
action_form = self.action_form(auto_id=None)
|
|
|
|
action_form.fields['action'].choices = self.get_action_choices(request)
|
|
|
|
else:
|
|
|
|
action_form = None
|
2009-03-18 04:51:47 +08:00
|
|
|
|
2010-03-01 22:34:07 +08:00
|
|
|
selection_note_all = ungettext('%(total_count)s selected',
|
|
|
|
'All %(total_count)s selected', cl.result_count)
|
2010-01-06 02:24:27 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
context = {
|
2012-07-21 16:00:10 +08:00
|
|
|
'module_name': force_text(opts.verbose_name_plural),
|
2010-04-13 18:29:19 +08:00
|
|
|
'selection_note': _('0 of %(cnt)s selected') % {'cnt': len(cl.result_list)},
|
2010-03-01 22:34:07 +08:00
|
|
|
'selection_note_all': selection_note_all % {'total_count': cl.result_count},
|
2008-07-19 07:54:34 +08:00
|
|
|
'title': cl.title,
|
|
|
|
'is_popup': cl.is_popup,
|
|
|
|
'cl': cl,
|
2009-03-18 04:51:47 +08:00
|
|
|
'media': media,
|
2008-07-19 07:54:34 +08:00
|
|
|
'has_add_permission': self.has_add_permission(request),
|
2008-08-23 12:00:15 +08:00
|
|
|
'app_label': app_label,
|
2009-03-24 04:22:56 +08:00
|
|
|
'action_form': action_form,
|
|
|
|
'actions_on_top': self.actions_on_top,
|
|
|
|
'actions_on_bottom': self.actions_on_bottom,
|
2010-01-06 02:24:27 +08:00
|
|
|
'actions_selection_counter': self.actions_selection_counter,
|
2008-07-19 07:54:34 +08:00
|
|
|
}
|
|
|
|
context.update(extra_context or {})
|
2011-04-23 02:17:16 +08:00
|
|
|
|
|
|
|
return TemplateResponse(request, self.change_list_template or [
|
2008-07-19 07:54:34 +08:00
|
|
|
'admin/%s/%s/change_list.html' % (app_label, opts.object_name.lower()),
|
|
|
|
'admin/%s/change_list.html' % app_label,
|
|
|
|
'admin/change_list.html'
|
2011-04-23 02:17:16 +08:00
|
|
|
], context, current_app=self.admin_site.name)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2010-02-09 23:02:39 +08:00
|
|
|
@csrf_protect_m
|
2010-10-13 12:16:31 +08:00
|
|
|
@transaction.commit_on_success
|
2008-07-19 07:54:34 +08:00
|
|
|
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
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2009-12-28 22:08:11 +08:00
|
|
|
obj = self.get_object(request, unquote(object_id))
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
if not self.has_delete_permission(request, obj):
|
|
|
|
raise PermissionDenied
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
if obj is None:
|
2012-07-21 16:00:10 +08:00
|
|
|
raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {'name': force_text(opts.verbose_name), 'key': escape(object_id)})
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2010-11-10 00:46:42 +08:00
|
|
|
using = router.db_for_write(self.model)
|
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
# Populate deleted_objects, a data structure of all related objects that
|
|
|
|
# will also be deleted.
|
2011-01-20 08:33:32 +08:00
|
|
|
(deleted_objects, perms_needed, protected) = get_deleted_objects(
|
2010-11-10 00:46:42 +08:00
|
|
|
[obj], opts, request.user, self.admin_site, using)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
if request.POST: # The user has already confirmed the deletion.
|
|
|
|
if perms_needed:
|
|
|
|
raise PermissionDenied
|
2012-07-21 16:00:10 +08:00
|
|
|
obj_display = force_text(obj)
|
2009-05-07 21:10:59 +08:00
|
|
|
self.log_deletion(request, obj, obj_display)
|
2010-11-22 03:00:40 +08:00
|
|
|
self.delete_model(request, obj)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2012-07-21 16:00:10 +08:00
|
|
|
self.message_user(request, _('The %(name)s "%(obj)s" was deleted successfully.') % {'name': force_text(opts.verbose_name), 'obj': force_text(obj_display)})
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
if not self.has_change_permission(request, None):
|
2011-09-21 02:30:06 +08:00
|
|
|
return HttpResponseRedirect(reverse('admin:index',
|
|
|
|
current_app=self.admin_site.name))
|
|
|
|
return HttpResponseRedirect(reverse('admin:%s_%s_changelist' %
|
|
|
|
(opts.app_label, opts.module_name),
|
|
|
|
current_app=self.admin_site.name))
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2012-07-21 16:00:10 +08:00
|
|
|
object_name = force_text(opts.verbose_name)
|
2011-01-20 09:00:47 +08:00
|
|
|
|
|
|
|
if perms_needed or protected:
|
|
|
|
title = _("Cannot delete %(name)s") % {"name": object_name}
|
|
|
|
else:
|
|
|
|
title = _("Are you sure?")
|
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
context = {
|
2011-01-20 09:00:47 +08:00
|
|
|
"title": title,
|
|
|
|
"object_name": object_name,
|
2008-07-19 07:54:34 +08:00
|
|
|
"object": obj,
|
|
|
|
"deleted_objects": deleted_objects,
|
|
|
|
"perms_lacking": perms_needed,
|
2011-01-20 08:33:32 +08:00
|
|
|
"protected": protected,
|
2008-07-19 07:54:34 +08:00
|
|
|
"opts": opts,
|
2008-08-23 12:00:15 +08:00
|
|
|
"app_label": app_label,
|
2008-07-19 07:54:34 +08:00
|
|
|
}
|
|
|
|
context.update(extra_context or {})
|
2011-04-23 02:17:16 +08:00
|
|
|
|
|
|
|
return TemplateResponse(request, self.delete_confirmation_template or [
|
2008-07-19 07:54:34 +08:00
|
|
|
"admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower()),
|
|
|
|
"admin/%s/delete_confirmation.html" % app_label,
|
|
|
|
"admin/delete_confirmation.html"
|
2011-04-23 02:17:16 +08:00
|
|
|
], context, current_app=self.admin_site.name)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
def history_view(self, request, object_id, extra_context=None):
|
|
|
|
"The 'history' admin view for this model."
|
|
|
|
from django.contrib.admin.models import LogEntry
|
|
|
|
model = self.model
|
|
|
|
opts = model._meta
|
2008-08-26 03:56:14 +08:00
|
|
|
app_label = opts.app_label
|
2008-07-19 07:54:34 +08:00
|
|
|
action_list = LogEntry.objects.filter(
|
2012-07-02 09:40:50 +08:00
|
|
|
object_id = unquote(object_id),
|
2008-07-19 07:54:34 +08:00
|
|
|
content_type__id__exact = ContentType.objects.get_for_model(model).id
|
|
|
|
).select_related().order_by('action_time')
|
|
|
|
# If no history was found, see whether this object even exists.
|
2009-12-11 03:58:20 +08:00
|
|
|
obj = get_object_or_404(model, pk=unquote(object_id))
|
2008-07-19 07:54:34 +08:00
|
|
|
context = {
|
2012-07-21 16:00:10 +08:00
|
|
|
'title': _('Change history: %s') % force_text(obj),
|
2008-07-19 07:54:34 +08:00
|
|
|
'action_list': action_list,
|
2012-07-21 16:00:10 +08:00
|
|
|
'module_name': capfirst(force_text(opts.verbose_name_plural)),
|
2008-07-19 07:54:34 +08:00
|
|
|
'object': obj,
|
2008-08-26 03:56:14 +08:00
|
|
|
'app_label': app_label,
|
2011-09-21 02:30:06 +08:00
|
|
|
'opts': opts,
|
2008-07-19 07:54:34 +08:00
|
|
|
}
|
|
|
|
context.update(extra_context or {})
|
2011-04-23 02:17:16 +08:00
|
|
|
return TemplateResponse(request, self.object_history_template or [
|
2008-09-08 13:45:17 +08:00
|
|
|
"admin/%s/%s/object_history.html" % (app_label, opts.object_name.lower()),
|
|
|
|
"admin/%s/object_history.html" % app_label,
|
2008-07-19 07:54:34 +08:00
|
|
|
"admin/object_history.html"
|
2011-04-23 02:17:16 +08:00
|
|
|
], context, current_app=self.admin_site.name)
|
2008-07-19 07:54:34 +08:00
|
|
|
|
|
|
|
class InlineModelAdmin(BaseModelAdmin):
|
|
|
|
"""
|
|
|
|
Options for inline editing of ``model`` instances.
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
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
|
2008-08-09 04:27:48 +08:00
|
|
|
formset = BaseInlineFormSet
|
2008-07-19 07:54:34 +08:00
|
|
|
extra = 3
|
2010-03-28 07:03:56 +08:00
|
|
|
max_num = None
|
2008-07-19 07:54:34 +08:00
|
|
|
template = None
|
|
|
|
verbose_name = None
|
|
|
|
verbose_name_plural = None
|
2010-02-24 01:14:50 +08:00
|
|
|
can_delete = True
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
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
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2011-08-11 22:07:39 +08:00
|
|
|
@property
|
|
|
|
def media(self):
|
2012-02-05 18:26:05 +08:00
|
|
|
extra = '' if settings.DEBUG else '.min'
|
|
|
|
js = ['jquery%s.js' % extra, 'jquery.init.js', 'inlines%s.js' % extra]
|
2008-08-16 01:38:39 +08:00
|
|
|
if self.prepopulated_fields:
|
2012-02-05 16:35:08 +08:00
|
|
|
js.extend(['urlify.js', 'prepopulate%s.js' % extra])
|
2008-08-16 04:33:45 +08:00
|
|
|
if self.filter_vertical or self.filter_horizontal:
|
2011-06-30 17:06:19 +08:00
|
|
|
js.extend(['SelectBox.js', 'SelectFilter2.js'])
|
2011-08-11 22:07:39 +08:00
|
|
|
return forms.Media(js=[static('admin/js/%s' % url) for url in js])
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-08-16 05:57:36 +08:00
|
|
|
def get_formset(self, request, obj=None, **kwargs):
|
2008-07-19 07:54:34 +08:00
|
|
|
"""Returns a BaseInlineFormSet class for use in admin add/change views."""
|
|
|
|
if self.declared_fieldsets:
|
|
|
|
fields = flatten_fieldsets(self.declared_fieldsets)
|
|
|
|
else:
|
|
|
|
fields = None
|
2008-09-03 01:26:24 +08:00
|
|
|
if self.exclude is None:
|
|
|
|
exclude = []
|
|
|
|
else:
|
2008-09-27 13:57:10 +08:00
|
|
|
exclude = list(self.exclude)
|
2009-12-23 02:29:00 +08:00
|
|
|
exclude.extend(self.get_readonly_fields(request, obj))
|
2011-08-12 22:14:49 +08:00
|
|
|
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)
|
2009-04-22 23:48:51 +08:00
|
|
|
# if exclude is an empty list we use None, since that's the actual
|
|
|
|
# default
|
2009-12-23 02:29:00 +08:00
|
|
|
exclude = exclude or None
|
2011-10-07 08:41:25 +08:00
|
|
|
can_delete = self.can_delete and self.has_delete_permission(request, obj)
|
2008-08-16 05:57:36 +08:00
|
|
|
defaults = {
|
|
|
|
"form": self.form,
|
|
|
|
"formset": self.formset,
|
|
|
|
"fk_name": self.fk_name,
|
|
|
|
"fields": fields,
|
2009-12-23 02:29:00 +08:00
|
|
|
"exclude": exclude,
|
2011-03-28 09:40:43 +08:00
|
|
|
"formfield_callback": partial(self.formfield_for_dbfield, request=request),
|
2008-08-16 05:57:36 +08:00
|
|
|
"extra": self.extra,
|
|
|
|
"max_num": self.max_num,
|
2011-10-07 08:41:25 +08:00
|
|
|
"can_delete": can_delete,
|
2008-08-16 05:57:36 +08:00
|
|
|
}
|
|
|
|
defaults.update(kwargs)
|
|
|
|
return inlineformset_factory(self.parent_model, self.model, **defaults)
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
def get_fieldsets(self, request, obj=None):
|
|
|
|
if self.declared_fieldsets:
|
|
|
|
return self.declared_fieldsets
|
2011-07-04 01:56:33 +08:00
|
|
|
form = self.get_formset(request, obj).form
|
2012-08-08 22:33:15 +08:00
|
|
|
fields = list(form.base_fields) + list(self.get_readonly_fields(request, obj))
|
2009-12-23 02:29:00 +08:00
|
|
|
return [(None, {'fields': fields})]
|
2009-03-18 17:19:24 +08:00
|
|
|
|
2011-10-07 08:41:25 +08:00
|
|
|
def queryset(self, request):
|
|
|
|
queryset = super(InlineModelAdmin, self).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 request.user.has_perm(
|
|
|
|
self.opts.app_label + '.' + self.opts.get_add_permission())
|
|
|
|
|
|
|
|
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
|
|
|
|
return request.user.has_perm(
|
|
|
|
opts.app_label + '.' + opts.get_change_permission())
|
|
|
|
|
|
|
|
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 request.user.has_perm(
|
|
|
|
self.opts.app_label + '.' + self.opts.get_delete_permission())
|
|
|
|
|
2012-09-09 03:07:33 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
class StackedInline(InlineModelAdmin):
|
|
|
|
template = 'admin/edit_inline/stacked.html'
|
|
|
|
|
2012-09-09 03:07:33 +08:00
|
|
|
|
2008-07-19 07:54:34 +08:00
|
|
|
class TabularInline(InlineModelAdmin):
|
|
|
|
template = 'admin/edit_inline/tabular.html'
|