Author: russellm
Date: 2009-03-10 07:08:17 -0500 (Tue, 10 Mar 2009)
New Revision: 10019
Modified:
django/branches/releases/1.0.X/
django/branches/releases/1.0.X/AUTHORS
django/branches/releases/1.0.X/django/contrib/admin/options.py
django/branches/releases/1.0.X/django/contrib/contenttypes/generic.py
django/branches/releases/1.0.X/django/forms/formsets.py
django/branches/releases/1.0.X/django/forms/models.py
django/branches/releases/1.0.X/tests/modeltests/generic_relations/models.py
django/branches/releases/1.0.X/tests/regressiontests/admin_views/models.py
django/branches/releases/1.0.X/tests/regressiontests/admin_views/tests.py
Log:
[1.0.X] Fixed #10271, #10281 -- Fixed the handling multiple inline models that
share a common base class and have the link to the inline parent on the base
class. Includes modifications that allow the equivalent handling for
GenericFields. Thanks to Idan Gazit, Antti Kaihola (akaihola), and Alex Gaynor
for their work on this patch.
Backport of r10017 from trunk.
Property changes on: django/branches/releases/1.0.X
___________________________________________________________________
Name: svnmerge-integrated
-
/django/trunk:1-9097,9099-9102,9104-9109,9111,9113-9144,9146-9151,9153-9156,9158-9159,9161-9187,9189-9247,9249-9262,9264-9277,9279-9298,9301-9302,9305-9331,9333-9343,9345,9347,9350-9352,9355-9396,9399-9462,9466-9469,9471-9488,9491-9526,9529,9533-9536,9539-9550,9556-9557,9559-9560,9562-9568,9570-9591,9595-9619,9621-9624,9626-9636,9638-9642,9644-9645,9647-9689,9691-9699,9703-9706,9709-9713,9716-9723,9725-9726,9730-9738,9740-9741,9750-9751,9757-9758,9761-9762,9767-9768,9770-9780,9782-9784,9789-9790,9793-9798,9801-9802,9806-9807,9809-9813,9821-9837,9842-9843,9847-9859,9861,9863-9875,9877-9881,9883-9887,9899-9903,9906-9909,9912,9914,9916-9917,9919-9920,9922-9927,9929,9931-9937,9939,9942-9943,9945-9950,9953-9954,9956-9962,9966-9977,9979-9984,9986-9988,9990
+
/django/trunk:1-9097,9099-9102,9104-9109,9111,9113-9144,9146-9151,9153-9156,9158-9159,9161-9187,9189-9247,9249-9262,9264-9277,9279-9298,9301-9302,9305-9331,9333-9343,9345,9347,9350-9352,9355-9396,9399-9462,9466-9469,9471-9488,9491-9526,9529,9533-9536,9539-9550,9556-9557,9559-9560,9562-9568,9570-9591,9595-9619,9621-9624,9626-9636,9638-9642,9644-9645,9647-9689,9691-9699,9703-9706,9709-9713,9716-9723,9725-9726,9730-9738,9740-9741,9750-9751,9757-9758,9761-9762,9767-9768,9770-9780,9782-9784,9789-9790,9793-9798,9801-9802,9806-9807,9809-9813,9821-9837,9842-9843,9847-9859,9861,9863-9875,9877-9881,9883-9887,9899-9903,9906-9909,9912,9914,9916-9917,9919-9920,9922-9927,9929,9931-9937,9939,9942-9943,9945-9950,9953-9954,9956-9962,9966-9977,9979-9984,9986-9988,9990,10017
Modified: django/branches/releases/1.0.X/AUTHORS
===================================================================
--- django/branches/releases/1.0.X/AUTHORS 2009-03-10 11:21:46 UTC (rev
10018)
+++ django/branches/releases/1.0.X/AUTHORS 2009-03-10 12:08:17 UTC (rev
10019)
@@ -156,6 +156,7 @@
Marc Garcia <[email protected]>
Alex Gaynor <[email protected]>
Andy Gayton <[email protected]>
+ Idan Gazit
Baishampayan Ghose
Dimitris Glezos <[email protected]>
[email protected]
Modified: django/branches/releases/1.0.X/django/contrib/admin/options.py
===================================================================
--- django/branches/releases/1.0.X/django/contrib/admin/options.py
2009-03-10 11:21:46 UTC (rev 10018)
+++ django/branches/releases/1.0.X/django/contrib/admin/options.py
2009-03-10 12:08:17 UTC (rev 10019)
@@ -485,10 +485,16 @@
else:
form_validated = False
new_object = self.model()
+ prefixes = {}
for FormSet in self.get_formsets(request):
+ prefix = FormSet.get_default_prefix()
+ prefixes[prefix] = prefixes.get(prefix, 0) + 1
+ if prefixes[prefix] != 1:
+ prefix = "%s-%s" % (prefix, prefixes[prefix])
formset = FormSet(data=request.POST, files=request.FILES,
instance=new_object,
-
save_as_new=request.POST.has_key("_saveasnew"))
+
save_as_new=request.POST.has_key("_saveasnew"),
+ prefix=prefix)
formsets.append(formset)
if all_valid(formsets) and form_validated:
self.save_model(request, new_object, form, change=False)
@@ -510,8 +516,13 @@
if isinstance(f, models.ManyToManyField):
initial[k] = initial[k].split(",")
form = ModelForm(initial=initial)
+ prefixes = {}
for FormSet in self.get_formsets(request):
- formset = FormSet(instance=self.model())
+ 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)
formsets.append(formset)
adminForm = helpers.AdminForm(form, list(self.get_fieldsets(request)),
self.prepopulated_fields)
@@ -571,9 +582,14 @@
else:
form_validated = False
new_object = obj
+ prefixes = {}
for FormSet in self.get_formsets(request, new_object):
+ 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)
+ instance=new_object, prefix=prefix)
formsets.append(formset)
if all_valid(formsets) and form_validated:
@@ -588,8 +604,13 @@
else:
form = ModelForm(instance=obj)
+ prefixes = {}
for FormSet in self.get_formsets(request, obj):
- formset = FormSet(instance=obj)
+ 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)
formsets.append(formset)
adminForm = helpers.AdminForm(form, self.get_fieldsets(request, obj),
self.prepopulated_fields)
Modified: django/branches/releases/1.0.X/django/contrib/contenttypes/generic.py
===================================================================
--- django/branches/releases/1.0.X/django/contrib/contenttypes/generic.py
2009-03-10 11:21:46 UTC (rev 10018)
+++ django/branches/releases/1.0.X/django/contrib/contenttypes/generic.py
2009-03-10 12:08:17 UTC (rev 10019)
@@ -291,7 +291,7 @@
ct_field_name = "content_type"
ct_fk_field_name = "object_id"
- def __init__(self, data=None, files=None, instance=None, save_as_new=None):
+ def __init__(self, data=None, files=None, instance=None, save_as_new=None,
prefix=None):
opts = self.model._meta
self.instance = instance
self.rel_name = '-'.join((
@@ -300,9 +300,17 @@
))
super(BaseGenericInlineFormSet, self).__init__(
queryset=self.get_queryset(), data=data, files=files,
- prefix=self.rel_name
+ prefix=prefix
)
+ #...@classmethod
+ def get_default_prefix(cls):
+ opts = cls.model._meta
+ return '-'.join((opts.app_label, opts.object_name.lower(),
+ cls.ct_field.name, cls.ct_fk_field.name,
+ ))
+ get_default_prefix = classmethod(get_default_prefix)
+
def get_queryset(self):
# Avoid a circular import.
from django.contrib.contenttypes.models import ContentType
Modified: django/branches/releases/1.0.X/django/forms/formsets.py
===================================================================
--- django/branches/releases/1.0.X/django/forms/formsets.py 2009-03-10
11:21:46 UTC (rev 10018)
+++ django/branches/releases/1.0.X/django/forms/formsets.py 2009-03-10
12:08:17 UTC (rev 10019)
@@ -32,7 +32,7 @@
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList):
self.is_bound = data is not None or files is not None
- self.prefix = prefix or 'form'
+ self.prefix = prefix or self.get_default_prefix()
self.auto_id = auto_id
self.data = data
self.files = files
@@ -62,7 +62,7 @@
initial = {TOTAL_FORM_COUNT: self._total_form_count,
INITIAL_FORM_COUNT: self._initial_form_count}
self.management_form = ManagementForm(initial=initial,
auto_id=self.auto_id, prefix=self.prefix)
-
+
# construct the forms in the formset
self._construct_forms()
@@ -74,7 +74,7 @@
self.forms = []
for i in xrange(self._total_form_count):
self.forms.append(self._construct_form(i))
-
+
def _construct_form(self, i, **kwargs):
"""
Instantiates and returns the i-th form instance in a formset.
@@ -118,7 +118,7 @@
def _get_deleted_forms(self):
"""
- Returns a list of forms that have been marked for deletion. Raises an
+ Returns a list of forms that have been marked for deletion. Raises an
AttributeError if deletion is not allowed.
"""
if not self.is_valid() or not self.can_delete:
@@ -176,6 +176,11 @@
return [self.forms[i[0]] for i in self._ordering]
ordered_forms = property(_get_ordered_forms)
+ #...@classmethod
+ def get_default_prefix(cls):
+ return 'form'
+ get_default_prefix = classmethod(get_default_prefix)
+
def non_form_errors(self):
"""
Returns an ErrorList of errors that aren't associated with a particular
Modified: django/branches/releases/1.0.X/django/forms/models.py
===================================================================
--- django/branches/releases/1.0.X/django/forms/models.py 2009-03-10
11:21:46 UTC (rev 10018)
+++ django/branches/releases/1.0.X/django/forms/models.py 2009-03-10
12:08:17 UTC (rev 10019)
@@ -249,8 +249,8 @@
# This is an extra field that's not on the ModelForm, ignore it
continue
if not isinstance(f, ModelField):
- # This is an extra field that happens to have a name that
matches,
- # for example, a related object accessor for this model. So
+ # This is an extra field that happens to have a name that
matches,
+ # for example, a related object accessor for this model. So
# get_field_by_name found it, but it is not a Field so do not
proceed
# to use it as if it were.
continue
@@ -472,7 +472,7 @@
# is there a better way to get the object descriptor?
self.rel_name = RelatedObject(self.fk.rel.to, self.model,
self.fk).get_accessor_name()
qs = self.model._default_manager.filter(**{self.fk.name:
self.instance})
- super(BaseInlineFormSet, self).__init__(data, files, prefix=prefix or
self.rel_name,
+ super(BaseInlineFormSet, self).__init__(data, files, prefix=prefix,
queryset=qs)
def _construct_forms(self):
@@ -489,6 +489,12 @@
form.data[form.add_prefix(self._pk_field.name)] = None
return form
+ #...@classmethod
+ def get_default_prefix(cls):
+ from django.db.models.fields.related import RelatedObject
+ return RelatedObject(cls.fk.rel.to, cls.model,
cls.fk).get_accessor_name()
+ get_default_prefix = classmethod(get_default_prefix)
+
def save_new(self, form, commit=True):
fk_attname = self.fk.get_attname()
kwargs = {fk_attname: self.instance.pk}
Modified:
django/branches/releases/1.0.X/tests/modeltests/generic_relations/models.py
===================================================================
--- django/branches/releases/1.0.X/tests/modeltests/generic_relations/models.py
2009-03-10 11:21:46 UTC (rev 10018)
+++ django/branches/releases/1.0.X/tests/modeltests/generic_relations/models.py
2009-03-10 12:08:17 UTC (rev 10019)
@@ -238,4 +238,9 @@
<p><label
for="id_generic_relations-taggeditem-content_type-object_id-1-tag">Tag:</label>
<input id="id_generic_relations-taggeditem-content_type-object_id-1-tag"
type="text" name="generic_relations-taggeditem-content_type-object_id-1-tag"
maxlength="50" /></p>
<p><label
for="id_generic_relations-taggeditem-content_type-object_id-1-DELETE">Delete:</label>
<input type="checkbox"
name="generic_relations-taggeditem-content_type-object_id-1-DELETE"
id="id_generic_relations-taggeditem-content_type-object_id-1-DELETE" /><input
type="hidden" name="generic_relations-taggeditem-content_type-object_id-1-id"
id="id_generic_relations-taggeditem-content_type-object_id-1-id" /></p>
+>>> formset = GenericFormSet(instance=lion, prefix='x')
+>>> for form in formset.forms:
+... print form.as_p()
+<p><label for="id_x-0-tag">Tag:</label> <input id="id_x-0-tag" type="text"
name="x-0-tag" maxlength="50" /></p>
+<p><label for="id_x-0-DELETE">Delete:</label> <input type="checkbox"
name="x-0-DELETE" id="id_x-0-DELETE" /><input type="hidden" name="x-0-id"
id="id_x-0-id" /></p>
"""}
Modified:
django/branches/releases/1.0.X/tests/regressiontests/admin_views/models.py
===================================================================
--- django/branches/releases/1.0.X/tests/regressiontests/admin_views/models.py
2009-03-10 11:21:46 UTC (rev 10018)
+++ django/branches/releases/1.0.X/tests/regressiontests/admin_views/models.py
2009-03-10 12:08:17 UTC (rev 10019)
@@ -20,7 +20,7 @@
def __unicode__(self):
return self.title
-
+
def model_year(self):
return self.date.year
model_year.admin_order_field = 'date'
@@ -54,14 +54,14 @@
class ChapterXtra1(models.Model):
chap = models.OneToOneField(Chapter, verbose_name=u'¿Chap?')
- xtra = models.CharField(max_length=100, verbose_name=u'¿Xtra?')
+ xtra = models.CharField(max_length=100, verbose_name=u'¿Xtra?')
def __unicode__(self):
return u'¿Xtra1: %s' % self.xtra
class ChapterXtra2(models.Model):
chap = models.OneToOneField(Chapter, verbose_name=u'¿Chap?')
- xtra = models.CharField(max_length=100, verbose_name=u'¿Xtra?')
+ xtra = models.CharField(max_length=100, verbose_name=u'¿Xtra?')
def __unicode__(self):
return u'¿Xtra2: %s' % self.xtra
@@ -87,7 +87,7 @@
'extra_var': 'Hello!'
}
)
-
+
def modeladmin_year(self, obj):
return obj.date.year
modeladmin_year.admin_order_field = 'date'
@@ -121,7 +121,7 @@
class Color(models.Model):
value = models.CharField(max_length=10)
- warm = models.BooleanField()
+ warm = models.BooleanField()
def __unicode__(self):
return self.value
@@ -134,12 +134,56 @@
class ThingAdmin(admin.ModelAdmin):
list_filter = ('color',)
+class Persona(models.Model):
+ """
+ A simple persona associated with accounts, to test inlining of related
+ accounts which inherit from a common accounts class.
+ """
+ name = models.CharField(blank=False, max_length=80)
+ def __unicode__(self):
+ return self.name
+
+class Account(models.Model):
+ """
+ A simple, generic account encapsulating the information shared by all
+ types of accounts.
+ """
+ username = models.CharField(blank=False, max_length=80)
+ persona = models.ForeignKey(Persona, related_name="accounts")
+ servicename = u'generic service'
+
+ def __unicode__(self):
+ return "%s: %s" % (self.servicename, self.username)
+
+class FooAccount(Account):
+ """A service-specific account of type Foo."""
+ servicename = u'foo'
+
+class BarAccount(Account):
+ """A service-specific account of type Bar."""
+ servicename = u'bar'
+
+class FooAccountAdmin(admin.StackedInline):
+ model = FooAccount
+ extra = 1
+
+class BarAccountAdmin(admin.StackedInline):
+ model = BarAccount
+ extra = 1
+
+class PersonaAdmin(admin.ModelAdmin):
+ inlines = (
+ FooAccountAdmin,
+ 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(Persona, PersonaAdmin)
# We intentionally register Promo and ChapterXtra1 but not Chapter nor
ChapterXtra2.
# That way we cover all four cases:
@@ -153,3 +197,5 @@
admin.site.register(Book, inlines=[ChapterInline])
admin.site.register(Promo)
admin.site.register(ChapterXtra1)
+
+
Modified:
django/branches/releases/1.0.X/tests/regressiontests/admin_views/tests.py
===================================================================
--- django/branches/releases/1.0.X/tests/regressiontests/admin_views/tests.py
2009-03-10 11:21:46 UTC (rev 10018)
+++ django/branches/releases/1.0.X/tests/regressiontests/admin_views/tests.py
2009-03-10 12:08:17 UTC (rev 10019)
@@ -1,5 +1,7 @@
# coding: utf-8
+import re
+
from django.test import TestCase
from django.contrib.auth.models import User, Permission
from django.contrib.contenttypes.models import ContentType
@@ -9,17 +11,22 @@
from django.utils.html import escape
# local test models
-from models import Article, CustomArticle, Section, ModelWithStringPrimaryKey
+from models import Article, CustomArticle, Section, ModelWithStringPrimaryKey,
Persona, FooAccount, BarAccount
+try:
+ set
+except NameError:
+ from sets import Set as set
+
class AdminViewBasicTest(TestCase):
fixtures = ['admin-views-users.xml', 'admin-views-colors.xml']
-
+
def setUp(self):
self.client.login(username='super', password='secret')
-
+
def tearDown(self):
self.client.logout()
-
+
def testTrailingSlashRequired(self):
"""
If you leave off the trailing slash, app should redirect and add it.
@@ -28,29 +35,29 @@
self.assertRedirects(request,
'/test_admin/admin/admin_views/article/add/'
)
-
+
def testBasicAddGet(self):
"""
A smoke test to ensure GET on the add_view works.
"""
response =
self.client.get('/test_admin/admin/admin_views/section/add/')
self.failUnlessEqual(response.status_code, 200)
-
+
def testAddWithGETArgs(self):
response =
self.client.get('/test_admin/admin/admin_views/section/add/', {'name': 'My
Section'})
self.failUnlessEqual(response.status_code, 200)
self.failUnless(
- 'value="My Section"' in response.content,
+ 'value="My Section"' in response.content,
"Couldn't find an input with the right value in the response."
)
-
+
def testBasicEditGet(self):
"""
A smoke test to ensureGET on the change_view works.
"""
response = self.client.get('/test_admin/admin/admin_views/section/1/')
self.failUnlessEqual(response.status_code, 200)
-
+
def testBasicAddPost(self):
"""
A smoke test to ensure POST on add_view works.
@@ -63,7 +70,7 @@
}
response =
self.client.post('/test_admin/admin/admin_views/section/add/', post_data)
self.failUnlessEqual(response.status_code, 302) # redirect somewhere
-
+
def testBasicEditPost(self):
"""
A smoke test to ensure POST on edit_view works.
@@ -111,7 +118,7 @@
def testChangeListSortingCallable(self):
"""
- Ensure we can sort on a list_display field that is a callable
+ Ensure we can sort on a list_display field that is a callable
(column 2 is callable_year in ArticleAdmin)
"""
response = self.client.get('/test_admin/admin/admin_views/article/',
{'ot': 'asc', 'o': 2})
@@ -121,10 +128,10 @@
response.content.index('Middle content') <
response.content.index('Newest content'),
"Results of sorting on callable are out of order."
)
-
+
def testChangeListSortingModel(self):
"""
- Ensure we can sort on a list_display field that is a Model method
+ Ensure we can sort on a list_display field that is a Model method
(colunn 3 is 'model_year' in ArticleAdmin)
"""
response = self.client.get('/test_admin/admin/admin_views/article/',
{'ot': 'dsc', 'o': 3})
@@ -134,40 +141,40 @@
response.content.index('Middle content') <
response.content.index('Oldest content'),
"Results of sorting on Model method are out of order."
)
-
+
def testChangeListSortingModelAdmin(self):
"""
- Ensure we can sort on a list_display field that is a ModelAdmin method
+ Ensure we can sort on a list_display field that is a ModelAdmin method
(colunn 4 is 'modeladmin_year' in ArticleAdmin)
"""
response = self.client.get('/test_admin/admin/admin_views/article/',
{'ot': 'asc', 'o': 4})
self.failUnlessEqual(response.status_code, 200)
self.failUnless(
- response.content.index('Oldest content') <
response.content.index('Middle content') and
+ response.content.index('Oldest content') <
response.content.index('Middle content') and
response.content.index('Middle content') <
response.content.index('Newest content'),
"Results of sorting on ModelAdmin method are out of order."
)
-
+
def testLimitedFilter(self):
"""Ensure admin changelist filters do not contain objects excluded via
limit_choices_to."""
response = self.client.get('/test_admin/admin/admin_views/thing/')
self.failUnlessEqual(response.status_code, 200)
self.failUnless(
- '<div id="changelist-filter">' in response.content,
+ '<div id="changelist-filter">' in response.content,
"Expected filter not found in changelist view."
)
self.failIf(
'<a href="?color__id__exact=3">Blue</a>' in response.content,
"Changelist filter not correctly limited by limit_choices_to."
)
-
+
def testIncorrectLookupParameters(self):
"""Ensure incorrect lookup parameters are handled gracefully."""
response = self.client.get('/test_admin/admin/admin_views/thing/',
{'notarealfield': '5'})
- self.assertRedirects(response,
'/test_admin/admin/admin_views/thing/?e=1')
+ self.assertRedirects(response,
'/test_admin/admin/admin_views/thing/?e=1')
response = self.client.get('/test_admin/admin/admin_views/thing/',
{'color__id__exact': 'StringNotInteger!'})
self.assertRedirects(response,
'/test_admin/admin/admin_views/thing/?e=1')
-
+
def get_perm(Model, perm):
"""Return the permission object, for the Model"""
ct = ContentType.objects.get_for_model(Model)
@@ -387,7 +394,7 @@
post = self.client.post('/test_admin/admin/admin_views/article/1/',
change_dict)
self.assertRedirects(post, '/test_admin/admin/admin_views/article/')
self.failUnlessEqual(Article.objects.get(pk=1).content, '<p>edited
article</p>')
-
+
# one error in form should produce singular error message, multiple
errors plural
change_dict['title'] = ''
post = self.client.post('/test_admin/admin/admin_views/article/1/',
change_dict)
@@ -398,7 +405,7 @@
post = self.client.post('/test_admin/admin/admin_views/article/1/',
change_dict)
self.failUnlessEqual(request.status_code, 200)
self.failUnless('Please correct the errors below.' in post.content,
- 'Plural error message not found in response to post
with multiple errors.')
+ 'Plural error message not found in response to post
with multiple errors.')
self.client.get('/test_admin/admin/logout/')
def testCustomModelAdminTemplates(self):
@@ -537,7 +544,7 @@
response =
self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/delete/'
% quote(self.pk))
should_contain = """<a href="../../%s/">%s</a>""" % (quote(self.pk),
escape(self.pk))
self.assertContains(response, should_contain)
-
+
def test_url_conflicts_with_add(self):
"A model with a primary key that ends with add should be visible"
add_model = ModelWithStringPrimaryKey(id="i have something to add")
@@ -545,7 +552,7 @@
response =
self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/' %
quote(add_model.pk))
should_contain = """<h1>Change model with string primary key</h1>"""
self.assertContains(response, should_contain)
-
+
def test_url_conflicts_with_delete(self):
"A model with a primary key that ends with delete should be visible"
delete_model = ModelWithStringPrimaryKey(id="delete")
@@ -553,7 +560,7 @@
response =
self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/' %
quote(delete_model.pk))
should_contain = """<h1>Change model with string primary key</h1>"""
self.assertContains(response, should_contain)
-
+
def test_url_conflicts_with_history(self):
"A model with a primary key that ends with history should be visible"
history_model = ModelWithStringPrimaryKey(id="history")
@@ -561,8 +568,8 @@
response =
self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/' %
quote(history_model.pk))
should_contain = """<h1>Change model with string primary key</h1>"""
self.assertContains(response, should_contain)
-
+
class SecureViewTest(TestCase):
fixtures = ['admin-views-users.xml']
@@ -596,28 +603,28 @@
LOGIN_FORM_KEY: 1,
'username': 'joepublic',
'password': 'secret'}
-
+
def tearDown(self):
self.client.logout()
-
+
def test_secure_view_shows_login_if_not_logged_in(self):
"Ensure that we see the login form"
response = self.client.get('/test_admin/admin/secure-view/' )
self.assertTemplateUsed(response, 'admin/login.html')
-
+
def test_secure_view_login_successfully_redirects_to_original_url(self):
request = self.client.get('/test_admin/admin/secure-view/')
self.failUnlessEqual(request.status_code, 200)
query_string = "the-answer=42"
login = self.client.post('/test_admin/admin/secure-view/',
self.super_login, QUERY_STRING = query_string )
self.assertRedirects(login, '/test_admin/admin/secure-view/?%s' %
query_string)
-
+
def test_staff_member_required_decorator_works_as_per_admin_login(self):
"""
Make sure only staff members can log in.
Successful posts to the login page will redirect to the orignal url.
- Unsuccessfull attempts will continue to render the login page with
+ Unsuccessfull attempts will continue to render the login page with
a 200 status code.
"""
# Super User
@@ -735,3 +742,80 @@
self.failUnlessEqual(response.status_code, 200)
response =
self.client.post('/test_admin/admin/admin_views/book/1/delete/', delete_dict)
self.assertRedirects(response, '/test_admin/admin/admin_views/book/')
+
+class AdminInheritedInlinesTest(TestCase):
+ fixtures = ['admin-views-users.xml',]
+
+ def setUp(self):
+ self.client.login(username='super', password='secret')
+
+ def tearDown(self):
+ self.client.logout()
+
+ def testInline(self):
+ "Ensure that inline models which inherit from a common parent are
correctly handled by admin."
+
+ foo_user = u"foo username"
+ bar_user = u"bar username"
+
+ name_re = re.compile('name="(.*?)"')
+
+ # test the add case
+ response =
self.client.get('/test_admin/admin/admin_views/persona/add/')
+ names = name_re.findall(response.content)
+ # make sure we have no duplicate HTML names
+ self.failUnlessEqual(len(names), len(set(names)))
+
+ # test the add case
+ post_data = {
+ "name": u"Test Name",
+ # inline data
+ "accounts-TOTAL_FORMS": u"1",
+ "accounts-INITIAL_FORMS": u"0",
+ "accounts-0-username": foo_user,
+ "accounts-2-TOTAL_FORMS": u"1",
+ "accounts-2-INITIAL_FORMS": u"0",
+ "accounts-2-0-username": bar_user,
+ }
+
+ response =
self.client.post('/test_admin/admin/admin_views/persona/add/', post_data)
+ self.failUnlessEqual(response.status_code, 302) # redirect somewhere
+ self.failUnlessEqual(Persona.objects.count(), 1)
+ self.failUnlessEqual(FooAccount.objects.count(), 1)
+ self.failUnlessEqual(BarAccount.objects.count(), 1)
+ self.failUnlessEqual(FooAccount.objects.all()[0].username, foo_user)
+ self.failUnlessEqual(BarAccount.objects.all()[0].username, bar_user)
+ self.failUnlessEqual(Persona.objects.all()[0].accounts.count(), 2)
+
+ # test the edit case
+
+ response = self.client.get('/test_admin/admin/admin_views/persona/1/')
+ names = name_re.findall(response.content)
+ # make sure we have no duplicate HTML names
+ self.failUnlessEqual(len(names), len(set(names)))
+
+ post_data = {
+ "name": u"Test Name",
+
+ "accounts-TOTAL_FORMS": "2",
+ "accounts-INITIAL_FORMS": u"1",
+
+ "accounts-0-username": "%s-1" % foo_user,
+ "accounts-0-account_ptr": "1",
+ "accounts-0-persona": "1",
+
+ "accounts-2-TOTAL_FORMS": u"2",
+ "accounts-2-INITIAL_FORMS": u"1",
+
+ "accounts-2-0-username": "%s-1" % bar_user,
+ "accounts-2-0-account_ptr": "2",
+ "accounts-2-0-persona": "1",
+ }
+ response =
self.client.post('/test_admin/admin/admin_views/persona/1/', post_data)
+ self.failUnlessEqual(response.status_code, 302)
+ self.failUnlessEqual(Persona.objects.count(), 1)
+ self.failUnlessEqual(FooAccount.objects.count(), 1)
+ self.failUnlessEqual(BarAccount.objects.count(), 1)
+ self.failUnlessEqual(FooAccount.objects.all()[0].username, "%s-1" %
foo_user)
+ self.failUnlessEqual(BarAccount.objects.all()[0].username, "%s-1" %
bar_user)
+ self.failUnlessEqual(Persona.objects.all()[0].accounts.count(), 2)
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/django-updates?hl=en
-~----------~----~----~----~------~----~------~--~---