mirror of
https://github.com/django/django.git
synced 2024-12-01 15:42:04 +01:00
Fixed #8939: added a list_editable
option to ModelAdmin
; fields declared list_editable
may be edited, in bulk, on the changelist page. Thanks, Alex Gaynor.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@10077 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
a7d1c73ad9
commit
7bc0878922
@ -1,6 +1,6 @@
|
||||
from django import forms, template
|
||||
from django.forms.formsets import all_valid
|
||||
from django.forms.models import modelform_factory, inlineformset_factory
|
||||
from django.forms.models import modelform_factory, modelformset_factory, inlineformset_factory
|
||||
from django.forms.models import BaseInlineFormSet
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.contrib.admin import widgets
|
||||
@ -16,6 +16,7 @@ from django.utils.safestring import mark_safe
|
||||
from django.utils.functional import curry
|
||||
from django.utils.text import capfirst, get_text_list
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.translation import ngettext
|
||||
from django.utils.encoding import force_unicode
|
||||
try:
|
||||
set
|
||||
@ -108,7 +109,7 @@ class BaseModelAdmin(object):
|
||||
if db_field.__class__ in self.formfield_overrides:
|
||||
kwargs = dict(self.formfield_overrides[db_field.__class__], **kwargs)
|
||||
return db_field.formfield(**kwargs)
|
||||
|
||||
|
||||
# For any other type of field, just call its formfield() method.
|
||||
return db_field.formfield(**kwargs)
|
||||
|
||||
@ -177,6 +178,7 @@ class ModelAdmin(BaseModelAdmin):
|
||||
list_filter = ()
|
||||
list_select_related = False
|
||||
list_per_page = 100
|
||||
list_editable = ()
|
||||
search_fields = ()
|
||||
date_hierarchy = None
|
||||
save_as = False
|
||||
@ -313,6 +315,29 @@ class ModelAdmin(BaseModelAdmin):
|
||||
defaults.update(kwargs)
|
||||
return modelform_factory(self.model, **defaults)
|
||||
|
||||
def get_changelist_form(self, request, **kwargs):
|
||||
"""
|
||||
Returns a Form class for use in the Formset on the changelist page.
|
||||
"""
|
||||
defaults = {
|
||||
"formfield_callback": curry(self.formfield_for_dbfield, request=request),
|
||||
}
|
||||
defaults.update(kwargs)
|
||||
return modelform_factory(self.model, **defaults)
|
||||
|
||||
def get_changelist_formset(self, request, **kwargs):
|
||||
"""
|
||||
Returns a FormSet class for use on the changelist page if list_editable
|
||||
is used.
|
||||
"""
|
||||
defaults = {
|
||||
"formfield_callback": curry(self.formfield_for_dbfield, request=request),
|
||||
}
|
||||
defaults.update(kwargs)
|
||||
return modelformset_factory(self.model,
|
||||
self.get_changelist_form(request), extra=0,
|
||||
fields=self.list_editable, **defaults)
|
||||
|
||||
def get_formsets(self, request, obj=None):
|
||||
for inline in self.inline_instances:
|
||||
yield inline.get_formset(request, obj)
|
||||
@ -685,7 +710,7 @@ class ModelAdmin(BaseModelAdmin):
|
||||
raise PermissionDenied
|
||||
try:
|
||||
cl = ChangeList(request, self.model, self.list_display, self.list_display_links, self.list_filter,
|
||||
self.date_hierarchy, self.search_fields, self.list_select_related, self.list_per_page, self)
|
||||
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'
|
||||
@ -696,10 +721,53 @@ class ModelAdmin(BaseModelAdmin):
|
||||
return render_to_response('admin/invalid_setup.html', {'title': _('Database error')})
|
||||
return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')
|
||||
|
||||
# If we're allowing changelist editing, we need to construct a formset
|
||||
# for the changelist given all the fields to be edited. Then we'll
|
||||
# use the formset to validate/process POSTed data.
|
||||
formset = cl.formset = None
|
||||
|
||||
# Handle POSTed bulk-edit data.
|
||||
if request.method == "POST" and self.list_editable:
|
||||
FormSet = self.get_changelist_formset(request)
|
||||
formset = cl.formset = FormSet(request.POST, request.FILES, queryset=cl.result_list)
|
||||
if formset.is_valid():
|
||||
changecount = 0
|
||||
for form in formset.forms:
|
||||
if form.has_changed():
|
||||
obj = self.save_form(request, form, change=True)
|
||||
self.save_model(request, obj, form, change=True)
|
||||
form.save_m2m()
|
||||
change_msg = self.construct_change_message(request, form, None)
|
||||
self.log_change(request, obj, change_msg)
|
||||
changecount += 1
|
||||
|
||||
if changecount:
|
||||
msg = ngettext("%(count)s %(singular)s was changed successfully.",
|
||||
"%(count)s %(plural)s were changed successfully.",
|
||||
changecount) % {'count': changecount,
|
||||
'singular': force_unicode(opts.verbose_name),
|
||||
'plural': force_unicode(opts.verbose_name_plural),
|
||||
'obj': force_unicode(obj)}
|
||||
self.message_user(request, msg)
|
||||
|
||||
return HttpResponseRedirect(request.get_full_path())
|
||||
|
||||
# Handle GET -- construct a formset for display.
|
||||
elif self.list_editable:
|
||||
FormSet = self.get_changelist_formset(request)
|
||||
formset = cl.formset = FormSet(queryset=cl.result_list)
|
||||
|
||||
# Build the list of media to be used by the formset.
|
||||
if formset:
|
||||
media = self.media + formset.media
|
||||
else:
|
||||
media = None
|
||||
|
||||
context = {
|
||||
'title': cl.title,
|
||||
'is_popup': cl.is_popup,
|
||||
'cl': cl,
|
||||
'media': media,
|
||||
'has_add_permission': self.has_add_permission(request),
|
||||
'root_path': self.admin_site.root_path,
|
||||
'app_label': app_label,
|
||||
|
@ -1,38 +1,76 @@
|
||||
{% extends "admin/base_site.html" %}
|
||||
{% load adminmedia admin_list i18n %}
|
||||
|
||||
{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/changelists.css" />{% endblock %}
|
||||
{% block extrastyle %}
|
||||
{{ block.super }}
|
||||
<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/changelists.css" />
|
||||
{% if cl.formset %}
|
||||
<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" />
|
||||
<script type="text/javascript" src="../../jsi18n/"></script>
|
||||
{{ media }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block bodyclass %}change-list{% endblock %}
|
||||
|
||||
{% if not is_popup %}{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../">{% trans "Home" %}</a> › <a href="../">{{ app_label|capfirst }}</a> › {{ cl.opts.verbose_name_plural|capfirst }}</div>{% endblock %}{% endif %}
|
||||
{% if not is_popup %}
|
||||
{% block breadcrumbs %}
|
||||
<div class="breadcrumbs">
|
||||
<a href="../../">
|
||||
{% trans "Home" %}
|
||||
</a>
|
||||
›
|
||||
<a href="../">
|
||||
{{ app_label|capfirst }}
|
||||
</a>
|
||||
›
|
||||
{{ cl.opts.verbose_name_plural|capfirst }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
|
||||
{% block coltype %}flex{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="content-main">
|
||||
{% block object-tools %}
|
||||
{% if has_add_permission %}
|
||||
<ul class="object-tools"><li><a href="add/{% if is_popup %}?_popup=1{% endif %}" class="addlink">{% blocktrans with cl.opts.verbose_name as name %}Add {{ name }}{% endblocktrans %}</a></li></ul>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
<div class="module{% if cl.has_filters %} filtered{% endif %}" id="changelist">
|
||||
{% block search %}{% search_form cl %}{% endblock %}
|
||||
{% block date_hierarchy %}{% date_hierarchy cl %}{% endblock %}
|
||||
<div id="content-main">
|
||||
{% block object-tools %}
|
||||
{% if has_add_permission %}
|
||||
<ul class="object-tools">
|
||||
<li>
|
||||
<a href="add/{% if is_popup %}?_popup=1{% endif %}" class="addlink">
|
||||
{% blocktrans with cl.opts.verbose_name as name %}Add {{ name }}{% endblocktrans %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% if cl.formset.errors %}
|
||||
<p class="errornote">
|
||||
{% blocktrans count cl.formset.errors|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
|
||||
</p>
|
||||
<ul class="errorlist">{% for error in cl.formset.non_field_errors %}<li>{{ error }}</li>{% endfor %}</ul>
|
||||
{% endif %}
|
||||
<div class="module{% if cl.has_filters %} filtered{% endif %}" id="changelist">
|
||||
{% block search %}{% search_form cl %}{% endblock %}
|
||||
{% block date_hierarchy %}{% date_hierarchy cl %}{% endblock %}
|
||||
|
||||
{% block filters %}
|
||||
{% if cl.has_filters %}
|
||||
<div id="changelist-filter">
|
||||
<h2>{% trans 'Filter' %}</h2>
|
||||
{% for spec in cl.filter_specs %}
|
||||
{% admin_list_filter cl spec %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block filters %}
|
||||
{% if cl.has_filters %}
|
||||
<div id="changelist-filter">
|
||||
<h2>{% trans 'Filter' %}</h2>
|
||||
{% for spec in cl.filter_specs %}{% admin_list_filter cl spec %}{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% if cl.formset %}
|
||||
<form action="" method="post"{% if cl.formset.is_multipart %} enctype="multipart/form-data"{% endif %}>
|
||||
{{ cl.formset.management_form }}
|
||||
{% endif %}
|
||||
|
||||
{% block result_list %}{% result_list cl %}{% endblock %}
|
||||
{% block pagination %}{% pagination cl %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
{% block result_list %}{% result_list cl %}{% endblock %}
|
||||
{% block pagination %}{% pagination cl %}{% endblock %}
|
||||
{% if cl.formset %}</form>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -8,4 +8,5 @@
|
||||
{% endif %}
|
||||
{{ cl.result_count }} {% ifequal cl.result_count 1 %}{{ cl.opts.verbose_name }}{% else %}{{ cl.opts.verbose_name_plural }}{% endifequal %}
|
||||
{% if show_all_url %} <a href="{{ show_all_url }}" class="showall">{% trans 'Show all' %}</a>{% endif %}
|
||||
{% if cl.formset %}<input type="submit" name="_save" class="default" value="Save"/>{% endif %}
|
||||
</p>
|
||||
|
@ -133,7 +133,7 @@ def _boolean_icon(field_val):
|
||||
BOOLEAN_MAPPING = {True: 'yes', False: 'no', None: 'unknown'}
|
||||
return mark_safe(u'<img src="%simg/admin/icon-%s.gif" alt="%s" />' % (settings.ADMIN_MEDIA_PREFIX, BOOLEAN_MAPPING[field_val], field_val))
|
||||
|
||||
def items_for_result(cl, result):
|
||||
def items_for_result(cl, result, form):
|
||||
first = True
|
||||
pk = cl.lookup_opts.pk.attname
|
||||
for field_name in cl.list_display:
|
||||
@ -227,11 +227,25 @@ def items_for_result(cl, result):
|
||||
yield mark_safe(u'<%s%s><a href="%s"%s>%s</a></%s>' % \
|
||||
(table_tag, row_class, url, (cl.is_popup and ' onclick="opener.dismissRelatedLookupPopup(window, %s); return false;"' % result_id or ''), conditional_escape(result_repr), table_tag))
|
||||
else:
|
||||
yield mark_safe(u'<td%s>%s</td>' % (row_class, conditional_escape(result_repr)))
|
||||
# By default the fields come from ModelAdmin.list_editable, but if we pull
|
||||
# the fields out of the form instead of list_editable custom admins
|
||||
# can provide fields on a per request basis
|
||||
if form and field_name in form.fields:
|
||||
bf = form[field_name]
|
||||
result_repr = mark_safe(force_unicode(bf.errors) + force_unicode(bf))
|
||||
else:
|
||||
result_repr = conditional_escape(result_repr)
|
||||
yield mark_safe(u'<td%s>%s</td>' % (row_class, result_repr))
|
||||
if form:
|
||||
yield mark_safe(force_unicode(form[cl.model._meta.pk.attname]))
|
||||
|
||||
def results(cl):
|
||||
for res in cl.result_list:
|
||||
yield list(items_for_result(cl,res))
|
||||
if cl.formset:
|
||||
for res, form in zip(cl.result_list, cl.formset.forms):
|
||||
yield list(items_for_result(cl, res, form))
|
||||
else:
|
||||
for res in cl.result_list:
|
||||
yield list(items_for_result(cl, res, None))
|
||||
|
||||
def result_list(cl):
|
||||
return {'cl': cl,
|
||||
|
@ -63,6 +63,29 @@ def validate(cls, model):
|
||||
if hasattr(cls, 'list_per_page') and not isinstance(cls.list_per_page, int):
|
||||
raise ImproperlyConfigured("'%s.list_per_page' should be a integer."
|
||||
% cls.__name__)
|
||||
|
||||
# list_editable
|
||||
if hasattr(cls, 'list_editable') and cls.list_editable:
|
||||
check_isseq(cls, 'list_editable', cls.list_editable)
|
||||
if not (opts.ordering or cls.ordering):
|
||||
raise ImproperlyConfigured("'%s.list_editable' cannot be used "
|
||||
"without a default ordering. Please define ordering on either %s or %s."
|
||||
% (cls.__name__, cls.__name__, model.__name__))
|
||||
for idx, field in enumerate(cls.list_editable):
|
||||
try:
|
||||
opts.get_field_by_name(field)
|
||||
except models.FieldDoesNotExist:
|
||||
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to a "
|
||||
"field, '%s', not defiend on %s." % (cls.__name__, idx, field, model.__name__))
|
||||
if field not in cls.list_display:
|
||||
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to "
|
||||
"'%s' which is not defined in 'list_display'."
|
||||
% (cls.__name__, idx, field))
|
||||
if field in cls.list_display_links:
|
||||
raise ImproperlyConfigured("'%s' cannot be in both '%s.list_editable'"
|
||||
" and '%s.list_display_links'"
|
||||
% (field, cls.__name__, cls.__name__))
|
||||
|
||||
|
||||
# search_fields = ()
|
||||
if hasattr(cls, 'search_fields'):
|
||||
|
@ -32,7 +32,7 @@ ERROR_FLAG = 'e'
|
||||
EMPTY_CHANGELIST_VALUE = '(None)'
|
||||
|
||||
class ChangeList(object):
|
||||
def __init__(self, request, model, list_display, list_display_links, list_filter, date_hierarchy, search_fields, list_select_related, list_per_page, model_admin):
|
||||
def __init__(self, request, model, list_display, list_display_links, list_filter, date_hierarchy, search_fields, list_select_related, list_per_page, list_editable, model_admin):
|
||||
self.model = model
|
||||
self.opts = model._meta
|
||||
self.lookup_opts = self.opts
|
||||
@ -44,6 +44,7 @@ class ChangeList(object):
|
||||
self.search_fields = search_fields
|
||||
self.list_select_related = list_select_related
|
||||
self.list_per_page = list_per_page
|
||||
self.list_editable = list_editable
|
||||
self.model_admin = model_admin
|
||||
|
||||
# Get search parameters from the query string.
|
||||
|
@ -403,6 +403,32 @@ the change list page::
|
||||
Finally, note that in order to use ``list_display_links``, you must define
|
||||
``list_display``, too.
|
||||
|
||||
``list_editable``
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. versionadded:: 1.1
|
||||
|
||||
Set ``list_editable`` to a list of field names on the model which will allow
|
||||
editing on the change list page. That is, fields listed in ``list_editable``
|
||||
will be displayed as form widgets on the change list page, allowing users to
|
||||
edit and save multiple rows at once.
|
||||
|
||||
.. note::
|
||||
|
||||
``list_editable`` interacts with a couple of other options in particular
|
||||
ways; you should note the following rules:
|
||||
|
||||
* To use ``list_editable`` you must have defined ``ordering`` defined on
|
||||
either your model or your ``ModelAdmin``.
|
||||
|
||||
* Any field in ``list_editable`` must also be in ``list_display``. You
|
||||
can't edit a field that's not displayed!
|
||||
|
||||
* The same field can't be listed in both ``list_editable`` and
|
||||
``list_display_links`` -- a field can't be both a form and a link.
|
||||
|
||||
You'll get a validation error if any of these rules are broken.
|
||||
|
||||
``list_filter``
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<django-objects version="1.0">
|
||||
<object pk="1" model="admin_views.person">
|
||||
<field type="CharField" name="name">John Mauchly</field>
|
||||
<field type="IntegerField" name="gender">1</field>
|
||||
<field type="BooleanField" name="alive">True</field>
|
||||
</object>
|
||||
<object pk="2" model="admin_views.person">
|
||||
<field type="CharField" name="name">Grace Hooper</field>
|
||||
<field type="IntegerField" name="gender">1</field>
|
||||
<field type="BooleanField" name="alive">False</field>
|
||||
</object>
|
||||
<object pk="3" model="admin_views.person">
|
||||
<field type="CharField" name="name">Guido van Rossum</field>
|
||||
<field type="IntegerField" name="gender">1</field>
|
||||
<field type="BooleanField" name="alive">True</field>
|
||||
</object>
|
||||
</django-objects>
|
@ -134,6 +134,28 @@ class Thing(models.Model):
|
||||
class ThingAdmin(admin.ModelAdmin):
|
||||
list_filter = ('color',)
|
||||
|
||||
class Person(models.Model):
|
||||
GENDER_CHOICES = (
|
||||
(1, "Male"),
|
||||
(2, "Female"),
|
||||
)
|
||||
name = models.CharField(max_length=100)
|
||||
gender = models.IntegerField(choices=GENDER_CHOICES)
|
||||
alive = models.BooleanField()
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
ordering = ["id"]
|
||||
|
||||
class PersonAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'gender', 'alive')
|
||||
list_editable = ('gender', 'alive')
|
||||
list_filter = ('gender',)
|
||||
search_fields = ('name',)
|
||||
ordering = ["id"]
|
||||
|
||||
class Persona(models.Model):
|
||||
"""
|
||||
A simple persona associated with accounts, to test inlining of related
|
||||
@ -177,12 +199,14 @@ class PersonaAdmin(admin.ModelAdmin):
|
||||
BarAccountAdmin
|
||||
)
|
||||
|
||||
|
||||
admin.site.register(Article, ArticleAdmin)
|
||||
admin.site.register(CustomArticle, CustomArticleAdmin)
|
||||
admin.site.register(Section, inlines=[ArticleInline])
|
||||
admin.site.register(ModelWithStringPrimaryKey)
|
||||
admin.site.register(Color)
|
||||
admin.site.register(Thing, ThingAdmin)
|
||||
admin.site.register(Person, PersonAdmin)
|
||||
admin.site.register(Persona, PersonaAdmin)
|
||||
|
||||
# We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2.
|
||||
|
@ -11,7 +11,7 @@ from django.contrib.admin.util import quote
|
||||
from django.utils.html import escape
|
||||
|
||||
# local test models
|
||||
from models import Article, CustomArticle, Section, ModelWithStringPrimaryKey, Persona, FooAccount, BarAccount
|
||||
from models import Article, CustomArticle, Section, ModelWithStringPrimaryKey, Person, Persona, FooAccount, BarAccount
|
||||
|
||||
try:
|
||||
set
|
||||
@ -729,6 +729,76 @@ class AdminViewUnicodeTest(TestCase):
|
||||
response = self.client.post('/test_admin/admin/admin_views/book/1/delete/', delete_dict)
|
||||
self.assertRedirects(response, '/test_admin/admin/admin_views/book/')
|
||||
|
||||
|
||||
class AdminViewListEditable(TestCase):
|
||||
fixtures = ['admin-views-users.xml', 'admin-views-person.xml']
|
||||
|
||||
def setUp(self):
|
||||
self.client.login(username='super', password='secret')
|
||||
|
||||
def tearDown(self):
|
||||
self.client.logout()
|
||||
|
||||
def test_changelist_input_html(self):
|
||||
response = self.client.get('/test_admin/admin/admin_views/person/')
|
||||
# 2 inputs per object(the field and the hidden id field) = 6
|
||||
# 2 management hidden fields = 2
|
||||
# main form submit button = 1
|
||||
# search field and search submit button = 2
|
||||
# 6 + 2 + 1 + 2 = 11 inputs
|
||||
self.failUnlessEqual(response.content.count("<input"), 11)
|
||||
# 1 select per object = 3 selects
|
||||
self.failUnlessEqual(response.content.count("<select"), 3)
|
||||
|
||||
def test_post_submission(self):
|
||||
data = {
|
||||
"form-TOTAL_FORMS": "3",
|
||||
"form-INITIAL_FORMS": "3",
|
||||
|
||||
"form-0-gender": "1",
|
||||
"form-0-id": "1",
|
||||
|
||||
"form-1-gender": "2",
|
||||
"form-1-id": "2",
|
||||
|
||||
"form-2-alive": "checked",
|
||||
"form-2-gender": "1",
|
||||
"form-2-id": "3",
|
||||
}
|
||||
self.client.post('/test_admin/admin/admin_views/person/', data)
|
||||
|
||||
self.failUnlessEqual(Person.objects.get(name="John Mauchly").alive, False)
|
||||
self.failUnlessEqual(Person.objects.get(name="Grace Hooper").gender, 2)
|
||||
|
||||
# test a filtered page
|
||||
data = {
|
||||
"form-TOTAL_FORMS": "2",
|
||||
"form-INITIAL_FORMS": "2",
|
||||
|
||||
"form-0-id": "1",
|
||||
"form-0-gender": "1",
|
||||
"form-0-alive": "checked",
|
||||
|
||||
"form-1-id": "3",
|
||||
"form-1-gender": "1",
|
||||
"form-1-alive": "checked",
|
||||
}
|
||||
self.client.post('/test_admin/admin/admin_views/person/?gender__exact=1', data)
|
||||
|
||||
self.failUnlessEqual(Person.objects.get(name="John Mauchly").alive, True)
|
||||
|
||||
# test a searched page
|
||||
data = {
|
||||
"form-TOTAL_FORMS": "1",
|
||||
"form-INITIAL_FORMS": "1",
|
||||
|
||||
"form-0-id": "1",
|
||||
"form-0-gender": "1"
|
||||
}
|
||||
self.client.post('/test_admin/admin/admin_views/person/?q=mauchly', data)
|
||||
|
||||
self.failUnlessEqual(Person.objects.get(name="John Mauchly").alive, False)
|
||||
|
||||
class AdminInheritedInlinesTest(TestCase):
|
||||
fixtures = ['admin-views-users.xml',]
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user