summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AUTHORS1
-rw-r--r--django/contrib/admin/options.py29
-rw-r--r--django/contrib/contenttypes/generic.py12
-rw-r--r--django/forms/formsets.py13
-rw-r--r--django/forms/models.py12
-rw-r--r--tests/modeltests/generic_relations/models.py5
-rw-r--r--tests/regressiontests/admin_views/models.py56
-rw-r--r--tests/regressiontests/admin_views/tests.py152
8 files changed, 228 insertions, 52 deletions
diff --git a/AUTHORS b/AUTHORS
index 1b2a0cfadb..8da6f9b5dc 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -161,6 +161,7 @@ answer newbie questions, and generally made Django that much better:
Marc Garcia <marc.garcia@accopensys.com>
Alex Gaynor <alex.gaynor@gmail.com>
Andy Gayton <andy-django@thecablelounge.com>
+ Idan Gazit
Baishampayan Ghose
Dimitris Glezos <dimitris@glezos.com>
glin@seznam.cz
diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py
index ed41c7ff64..859229e430 100644
--- a/django/contrib/admin/options.py
+++ b/django/contrib/admin/options.py
@@ -522,10 +522,16 @@ class ModelAdmin(BaseModelAdmin):
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)
@@ -547,8 +553,13 @@ class ModelAdmin(BaseModelAdmin):
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)
@@ -608,9 +619,14 @@ class ModelAdmin(BaseModelAdmin):
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:
@@ -625,8 +641,13 @@ class ModelAdmin(BaseModelAdmin):
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)
diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py
index 13e7cd7a39..8dd735d15e 100644
--- a/django/contrib/contenttypes/generic.py
+++ b/django/contrib/contenttypes/generic.py
@@ -291,7 +291,7 @@ class BaseGenericInlineFormSet(BaseModelFormSet):
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 @@ class BaseGenericInlineFormSet(BaseModelFormSet):
))
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
diff --git a/django/forms/formsets.py b/django/forms/formsets.py
index 887f130347..10306c7e6d 100644
--- a/django/forms/formsets.py
+++ b/django/forms/formsets.py
@@ -32,7 +32,7 @@ class BaseFormSet(StrAndUnicode):
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 @@ class BaseFormSet(StrAndUnicode):
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 @@ class BaseFormSet(StrAndUnicode):
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 @@ class BaseFormSet(StrAndUnicode):
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 @@ class BaseFormSet(StrAndUnicode):
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
diff --git a/django/forms/models.py b/django/forms/models.py
index e6bbb98718..bfc0b35803 100644
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -249,8 +249,8 @@ class BaseModelForm(BaseForm):
# 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 @@ class BaseInlineFormSet(BaseModelFormSet):
# 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 @@ class BaseInlineFormSet(BaseModelFormSet):
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}
diff --git a/tests/modeltests/generic_relations/models.py b/tests/modeltests/generic_relations/models.py
index e6c1ee243e..1bc6577bf7 100644
--- a/tests/modeltests/generic_relations/models.py
+++ b/tests/modeltests/generic_relations/models.py
@@ -238,4 +238,9 @@ __test__ = {'API_TESTS':"""
<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>
"""}
diff --git a/tests/regressiontests/admin_views/models.py b/tests/regressiontests/admin_views/models.py
index 050823f6de..d849a7b9c1 100644
--- a/tests/regressiontests/admin_views/models.py
+++ b/tests/regressiontests/admin_views/models.py
@@ -20,7 +20,7 @@ class Article(models.Model):
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 Chapter(models.Model):
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 @@ class ArticleAdmin(admin.ModelAdmin):
'extra_var': 'Hello!'
}
)
-
+
def modeladmin_year(self, obj):
return obj.date.year
modeladmin_year.admin_order_field = 'date'
@@ -121,7 +121,7 @@ class ModelWithStringPrimaryKey(models.Model):
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 Thing(models.Model):
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(Thing, ThingAdmin)
admin.site.register(Book, inlines=[ChapterInline])
admin.site.register(Promo)
admin.site.register(ChapterXtra1)
+
+
diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
index 39daf116ab..bf198bc9a2 100644
--- a/tests/regressiontests/admin_views/tests.py
+++ b/tests/regressiontests/admin_views/tests.py
@@ -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,22 +11,27 @@ from django.contrib.admin.util import quote
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']
-
- # Store the bit of the URL where the admin is registered as a class
+
+ # Store the bit of the URL where the admin is registered as a class
# variable. That way we can test a second AdminSite just by subclassing
# this test case and changing urlbit.
urlbit = 'admin'
-
+
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.
@@ -33,29 +40,29 @@ class AdminViewBasicTest(TestCase):
self.assertRedirects(request,
'/test_admin/%s/admin_views/article/add/' % self.urlbit, status_code=301
)
-
+
def testBasicAddGet(self):
"""
A smoke test to ensure GET on the add_view works.
"""
response = self.client.get('/test_admin/%s/admin_views/section/add/' % self.urlbit)
self.failUnlessEqual(response.status_code, 200)
-
+
def testAddWithGETArgs(self):
response = self.client.get('/test_admin/%s/admin_views/section/add/' % self.urlbit, {'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/%s/admin_views/section/1/' % self.urlbit)
self.failUnlessEqual(response.status_code, 200)
-
+
def testBasicAddPost(self):
"""
A smoke test to ensure POST on add_view works.
@@ -68,7 +75,7 @@ class AdminViewBasicTest(TestCase):
}
response = self.client.post('/test_admin/%s/admin_views/section/add/' % self.urlbit, post_data)
self.failUnlessEqual(response.status_code, 302) # redirect somewhere
-
+
def testBasicEditPost(self):
"""
A smoke test to ensure POST on edit_view works.
@@ -116,7 +123,7 @@ class AdminViewBasicTest(TestCase):
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/%s/admin_views/article/' % self.urlbit, {'ot': 'asc', 'o': 2})
@@ -126,10 +133,10 @@ class AdminViewBasicTest(TestCase):
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/%s/admin_views/article/' % self.urlbit, {'ot': 'dsc', 'o': 3})
@@ -139,37 +146,37 @@ class AdminViewBasicTest(TestCase):
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/%s/admin_views/article/' % self.urlbit, {'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/%s/admin_views/thing/' % self.urlbit)
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/%s/admin_views/thing/' % self.urlbit, {'notarealfield': '5'})
- self.assertRedirects(response, '/test_admin/%s/admin_views/thing/?e=1' % self.urlbit)
+ self.assertRedirects(response, '/test_admin/%s/admin_views/thing/?e=1' % self.urlbit)
response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit, {'color__id__exact': 'StringNotInteger!'})
self.assertRedirects(response, '/test_admin/%s/admin_views/thing/?e=1' % self.urlbit)
@@ -186,7 +193,7 @@ class CustomModelAdminTest(AdminViewBasicTest):
request = self.client.get('/test_admin/admin2/')
self.assertTemplateUsed(request, 'custom_admin/index.html')
self.assert_('Hello from a custom index template *bar*' in request.content)
-
+
def testCustomAdminSiteView(self):
self.client.login(username='super', password='secret')
response = self.client.get('/test_admin/%s/my_view/' % self.urlbit)
@@ -411,7 +418,7 @@ class AdminViewPermissionsTest(TestCase):
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)
@@ -422,7 +429,7 @@ class AdminViewPermissionsTest(TestCase):
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):
@@ -523,7 +530,7 @@ class AdminViewStringPrimaryKeyTest(TestCase):
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")
@@ -531,7 +538,7 @@ class AdminViewStringPrimaryKeyTest(TestCase):
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")
@@ -539,7 +546,7 @@ class AdminViewStringPrimaryKeyTest(TestCase):
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")
@@ -547,7 +554,7 @@ class AdminViewStringPrimaryKeyTest(TestCase):
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']
@@ -582,28 +589,28 @@ class SecureViewTest(TestCase):
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
@@ -721,3 +728,80 @@ class AdminViewUnicodeTest(TestCase):
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)