--- a/web/lib/django/contrib/admin/options.py Wed May 19 17:43:59 2010 +0200
+++ b/web/lib/django/contrib/admin/options.py Tue May 25 02:43:45 2010 +0200
@@ -6,11 +6,14 @@
from django.contrib.admin import widgets
from django.contrib.admin import helpers
from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_ngettext, model_format_dict
-from django.core.exceptions import PermissionDenied
+from django.contrib import messages
+from django.views.decorators.csrf import csrf_protect
+from django.core.exceptions import PermissionDenied, ValidationError
from django.db import models, transaction
from django.db.models.fields import BLANK_CHOICE_DASH
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
@@ -20,10 +23,6 @@
from django.utils.translation import ugettext as _
from django.utils.translation import ungettext, ugettext_lazy
from django.utils.encoding import force_unicode
-try:
- set
-except NameError:
- from sets import Set as set # Python 2.3 fallback
HORIZONTAL, VERTICAL = 1, 2
# returns the <ul> class for a given radio_admin field
@@ -40,19 +39,22 @@
'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.CharField: {'widget': widgets.AdminTextInputWidget},
- models.ImageField: {'widget': widgets.AdminFileWidget},
- models.FileField: {'widget': widgets.AdminFileWidget},
+ 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 BaseModelAdmin(object):
"""Functionality common to both ModelAdmin and InlineAdmin."""
+ __metaclass__ = forms.MediaDefiningClass
raw_id_fields = ()
fields = None
@@ -64,9 +66,12 @@
radio_fields = {}
prepopulated_fields = {}
formfield_overrides = {}
+ readonly_fields = ()
def __init__(self):
- self.formfield_overrides = dict(FORMFIELD_FOR_DBFIELD_DEFAULTS, **self.formfield_overrides)
+ overrides = FORMFIELD_FOR_DBFIELD_DEFAULTS.copy()
+ overrides.update(self.formfield_overrides)
+ self.formfield_overrides = overrides
def formfield_for_dbfield(self, db_field, **kwargs):
"""
@@ -138,8 +143,9 @@
"""
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)
+ 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]),
@@ -152,12 +158,14 @@
"""
Get a form Field for a ManyToManyField.
"""
- # If it uses an intermediary model, don't show field in admin.
- if db_field.rel.through is not None:
+ # 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)
+ 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))
@@ -172,9 +180,11 @@
return None
declared_fieldsets = property(_declared_fieldsets)
+ def get_readonly_fields(self, request, obj=None):
+ return self.readonly_fields
+
class ModelAdmin(BaseModelAdmin):
"Encapsulates all admin options and functionality for a given model."
- __metaclass__ = forms.MediaDefiningClass
list_display = ('__str__',)
list_display_links = ()
@@ -190,9 +200,11 @@
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
@@ -200,6 +212,7 @@
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
@@ -254,11 +267,13 @@
def _media(self):
from django.conf import settings
- js = ['js/core.js', 'js/admin/RelatedObjectLookups.js']
+ 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/getElementsBySelector.js', 'js/actions.js'])
+ 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'])
@@ -321,7 +336,8 @@
if self.declared_fieldsets:
return self.declared_fieldsets
form = self.get_form(request, obj)
- return [(None, {'fields': form.base_fields.keys()})]
+ fields = form.base_fields.keys() + list(self.get_readonly_fields(request, obj))
+ return [(None, {'fields': fields})]
def get_form(self, request, obj=None, **kwargs):
"""
@@ -336,17 +352,41 @@
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 + kwargs.get("exclude", [])) or None,
+ "exclude": exclude,
"formfield_callback": curry(self.formfield_for_dbfield, request=request),
}
defaults.update(kwargs)
return modelform_factory(self.model, **defaults)
+ 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.
@@ -522,16 +562,16 @@
for formset in formsets:
for added_object in formset.new_objects:
change_message.append(_('Added %(name)s "%(object)s".')
- % {'name': added_object._meta.verbose_name,
+ % {'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': changed_object._meta.verbose_name,
+ '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': deleted_object._meta.verbose_name,
+ % {'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.')
@@ -539,9 +579,9 @@
def message_user(self, request, message):
"""
Send a message to the user. The default implementation
- posts a message using the auth Message object.
+ posts a message using the django.contrib.messages backend.
"""
- request.user.message_set.create(message=message)
+ messages.info(request, message)
def save_form(self, request, form, change):
"""
@@ -582,8 +622,12 @@
'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(self.change_form_template or [
+ 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"
@@ -655,6 +699,7 @@
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.
@@ -683,15 +728,25 @@
# 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.
+ # 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:
+ 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
- response = func(self, request, queryset.filter(pk__in=selected))
+ 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
@@ -700,7 +755,13 @@
return response
else:
return HttpResponseRedirect(".")
+ 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
@@ -714,13 +775,13 @@
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
- new_object = self.save_form(request, form, change=False)
else:
form_validated = False
new_object = self.model()
prefixes = {}
- for FormSet in self.get_formsets(request):
+ 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:
@@ -728,7 +789,7 @@
formset = FormSet(data=request.POST, files=request.FILES,
instance=new_object,
save_as_new=request.POST.has_key("_saveasnew"),
- prefix=prefix)
+ 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)
@@ -751,21 +812,27 @@
initial[k] = initial[k].split(",")
form = ModelForm(initial=initial)
prefixes = {}
- for FormSet in self.get_formsets(request):
+ 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)
+ 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)
+ 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))
- inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, fieldsets)
+ 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
@@ -782,20 +849,15 @@
}
context.update(extra_context or {})
return self.render_change_form(request, context, form_url=form_url, add=True)
- add_view = transaction.commit_on_success(add_view)
+ @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
- try:
- obj = self.queryset(request).get(pk=unquote(object_id))
- except model.DoesNotExist:
- # Don't raise Http404 just yet, because we haven't checked
- # permissions yet. We don't want an unauthenticated user to be able
- # to determine whether a given object exists.
- obj = None
+ obj = self.get_object(request, unquote(object_id))
if not self.has_change_permission(request, obj):
raise PermissionDenied
@@ -817,13 +879,16 @@
form_validated = False
new_object = obj
prefixes = {}
- for FormSet in self.get_formsets(request, new_object):
+ 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)
+ instance=new_object, prefix=prefix,
+ queryset=inline.queryset(request))
+
formsets.append(formset)
if all_valid(formsets) and form_validated:
@@ -839,21 +904,26 @@
else:
form = ModelForm(instance=obj)
prefixes = {}
- for FormSet in self.get_formsets(request, obj):
+ 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)
+ 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)
+ 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))
- inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, fieldsets)
+ 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
@@ -871,11 +941,11 @@
}
context.update(extra_context or {})
return self.render_change_form(request, context, change=True, obj=obj)
- change_view = transaction.commit_on_success(change_view)
+ @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 ChangeList, ERROR_FLAG
+ 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):
@@ -892,26 +962,53 @@
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.
+ # 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 first, but if this isn't an action the POST
- # will fall through to the bulk edit check, below.
- if actions and request.method == 'POST':
- response = self.response_action(request, queryset=cl.get_query_set())
- if response:
- return response
+ # 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
@@ -919,7 +1016,8 @@
formset = cl.formset = None
# Handle POSTed bulk-edit data.
- if request.method == "POST" and self.list_editable:
+ if (request.method == "POST" and self.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():
@@ -965,7 +1063,13 @@
else:
action_form = None
+ selection_note_all = ungettext('%(total_count)s selected',
+ 'All %(total_count)s selected', cl.result_count)
+
context = {
+ 'module_name': force_unicode(opts.verbose_name_plural),
+ 'selection_note': _('0 of %(cnt)s selected') % {'cnt': len(cl.result_list)},
+ 'selection_note_all': selection_note_all % {'total_count': cl.result_count},
'title': cl.title,
'is_popup': cl.is_popup,
'cl': cl,
@@ -976,6 +1080,7 @@
'action_form': action_form,
'actions_on_top': self.actions_on_top,
'actions_on_bottom': self.actions_on_bottom,
+ 'actions_selection_counter': self.actions_selection_counter,
}
context.update(extra_context or {})
context_instance = template.RequestContext(request, current_app=self.admin_site.name)
@@ -985,18 +1090,13 @@
'admin/change_list.html'
], context, context_instance=context_instance)
+ @csrf_protect_m
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
- try:
- obj = self.queryset(request).get(pk=unquote(object_id))
- except self.model.DoesNotExist:
- # Don't raise Http404 just yet, because we haven't checked
- # permissions yet. We don't want an unauthenticated user to be able
- # to determine whether a given object exists.
- obj = None
+ obj = self.get_object(request, unquote(object_id))
if not self.has_delete_permission(request, obj):
raise PermissionDenied
@@ -1006,9 +1106,7 @@
# Populate deleted_objects, a data structure of all related objects that
# will also be deleted.
- deleted_objects = [mark_safe(u'%s: <a href="../../%s/">%s</a>' % (escape(force_unicode(capfirst(opts.verbose_name))), object_id, escape(obj))), []]
- perms_needed = set()
- get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1, self.admin_site)
+ (deleted_objects, perms_needed) = get_deleted_objects((obj,), opts, request.user, self.admin_site)
if request.POST: # The user has already confirmed the deletion.
if perms_needed:
@@ -1052,7 +1150,7 @@
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.
- obj = get_object_or_404(model, pk=object_id)
+ obj = get_object_or_404(model, pk=unquote(object_id))
context = {
'title': _('Change history: %s') % force_unicode(obj),
'action_list': action_list,
@@ -1109,10 +1207,11 @@
fk_name = None
formset = BaseInlineFormSet
extra = 3
- max_num = 0
+ max_num = None
template = None
verbose_name = None
verbose_name_plural = None
+ can_delete = True
def __init__(self, parent_model, admin_site):
self.admin_site = admin_site
@@ -1126,9 +1225,10 @@
def _media(self):
from django.conf import settings
- js = []
+ js = ['js/jquery.min.js', 'js/jquery.init.js', 'js/inlines.min.js']
if self.prepopulated_fields:
js.append('js/urlify.js')
+ js.append('js/prepopulate.min.js')
if self.filter_vertical or self.filter_horizontal:
js.extend(['js/SelectBox.js' , 'js/SelectFilter2.js'])
return forms.Media(js=['%s%s' % (settings.ADMIN_MEDIA_PREFIX, url) for url in js])
@@ -1144,17 +1244,21 @@
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 use None, since that's the actual
# default
+ exclude = exclude or None
defaults = {
"form": self.form,
"formset": self.formset,
"fk_name": self.fk_name,
"fields": fields,
- "exclude": (exclude + kwargs.get("exclude", [])) or None,
+ "exclude": exclude,
"formfield_callback": curry(self.formfield_for_dbfield, request=request),
"extra": self.extra,
"max_num": self.max_num,
+ "can_delete": self.can_delete,
}
defaults.update(kwargs)
return inlineformset_factory(self.parent_model, self.model, **defaults)
@@ -1163,7 +1267,11 @@
if self.declared_fieldsets:
return self.declared_fieldsets
form = self.get_formset(request).form
- return [(None, {'fields': form.base_fields.keys()})]
+ fields = form.base_fields.keys() + list(self.get_readonly_fields(request, obj))
+ return [(None, {'fields': fields})]
+
+ def queryset(self, request):
+ return self.model._default_manager.all()
class StackedInline(InlineModelAdmin):
template = 'admin/edit_inline/stacked.html'