from __future__ import unicode_literals from django.contrib.admin import TabularInline, ModelAdmin from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase from django.contrib.admin.helpers import InlineAdminForm from django.contrib.auth.models import User, Permission from django.contrib.contenttypes.models import ContentType from django.test import TestCase, override_settings, RequestFactory # local test models from .admin import InnerInline, site as admin_site from .models import (Holder, Inner, Holder2, Inner2, Holder3, Inner3, Person, OutfitItem, Fashionista, Teacher, Parent, Child, Author, Book, Profile, ProfileCollection, ParentModelWithCustomPk, ChildModel1, ChildModel2, Sighting, Novel, Chapter, FootNote, BinaryTree, SomeParentModel, SomeChildModel) @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',), ROOT_URLCONF="admin_inlines.urls") class TestInline(TestCase): fixtures = ['admin-views-users.xml'] def setUp(self): holder = Holder(dummy=13) holder.save() Inner(dummy=42, holder=holder).save() self.change_url = '/admin/admin_inlines/holder/%i/' % holder.id result = self.client.login(username='super', password='secret') self.assertEqual(result, True) self.factory = RequestFactory() def tearDown(self): self.client.logout() def test_can_delete(self): """ can_delete should be passed to inlineformset factory. """ response = self.client.get(self.change_url) inner_formset = response.context['inline_admin_formsets'][0].formset expected = InnerInline.can_delete actual = inner_formset.can_delete self.assertEqual(expected, actual, 'can_delete must be equal') def test_readonly_stacked_inline_label(self): """Bug #13174.""" holder = Holder.objects.create(dummy=42) Inner.objects.create(holder=holder, dummy=42, readonly='') response = self.client.get('/admin/admin_inlines/holder/%i/' % holder.id) self.assertContains(response, '') def test_many_to_many_inlines(self): "Autogenerated many-to-many inlines are displayed correctly (#13407)" response = self.client.get('/admin/admin_inlines/author/add/') # The heading for the m2m inline block uses the right text self.assertContains(response, '

Author-book relationships

') # The "add another" label is correct self.assertContains(response, 'Add another Author-book relationship') # The '+' is dropped from the autogenerated form prefix (Author_books+) self.assertContains(response, 'id="id_Author_books-TOTAL_FORMS"') def test_inline_primary(self): person = Person.objects.create(firstname='Imelda') item = OutfitItem.objects.create(name='Shoes') # Imelda likes shoes, but can't carry her own bags. data = { 'shoppingweakness_set-TOTAL_FORMS': 1, 'shoppingweakness_set-INITIAL_FORMS': 0, 'shoppingweakness_set-MAX_NUM_FORMS': 0, '_save': 'Save', 'person': person.id, 'max_weight': 0, 'shoppingweakness_set-0-item': item.id, } response = self.client.post('/admin/admin_inlines/fashionista/add/', data) self.assertEqual(response.status_code, 302) self.assertEqual(len(Fashionista.objects.filter(person__firstname='Imelda')), 1) def test_tabular_non_field_errors(self): """ Ensure that non_field_errors are displayed correctly, including the right value for colspan. Refs #13510. """ data = { 'title_set-TOTAL_FORMS': 1, 'title_set-INITIAL_FORMS': 0, 'title_set-MAX_NUM_FORMS': 0, '_save': 'Save', 'title_set-0-title1': 'a title', 'title_set-0-title2': 'a different title', } response = self.client.post('/admin/admin_inlines/titlecollection/add/', data) # Here colspan is "4": two fields (title1 and title2), one hidden field and the delete checkbox. self.assertContains(response, '') def test_no_parent_callable_lookup(self): """Admin inline `readonly_field` shouldn't invoke parent ModelAdmin callable""" # Identically named callable isn't present in the parent ModelAdmin, # rendering of the add view shouldn't explode response = self.client.get('/admin/admin_inlines/novel/add/') self.assertEqual(response.status_code, 200) # View should have the child inlines section self.assertContains(response, '
') def test_callable_lookup(self): """Admin inline should invoke local callable when its name is listed in readonly_fields""" response = self.client.get('/admin/admin_inlines/poll/add/') self.assertEqual(response.status_code, 200) # Add parent object view should have the child inlines section self.assertContains(response, '
') # The right callable should be used for the inline readonly_fields # column cells self.assertContains(response, '

Callable in QuestionInline

') def test_help_text(self): """ Ensure that the inlines' model field help texts are displayed when using both the stacked and tabular layouts. Ref #8190. """ response = self.client.get('/admin/admin_inlines/holder4/add/') self.assertContains(response, '

Awesome stacked help text is awesome.

', 4) self.assertContains(response, '(Awesome tabular help text is awesome.)', 1) # ReadOnly fields response = self.client.get('/admin/admin_inlines/capofamiglia/add/') self.assertContains(response, '(Help text for ReadOnlyInline)', 1) def test_inline_hidden_field_no_column(self): """#18263 -- Make sure hidden fields don't get a column in tabular inlines""" parent = SomeParentModel.objects.create(name='a') SomeChildModel.objects.create(name='b', position='0', parent=parent) SomeChildModel.objects.create(name='c', position='1', parent=parent) response = self.client.get('/admin/admin_inlines/someparentmodel/%s/' % parent.pk) self.assertNotContains(response, '') self.assertContains(response, ( '')) def test_non_related_name_inline(self): """ Ensure that multiple inlines with related_name='+' have correct form prefixes. Bug #16838. """ response = self.client.get('/admin/admin_inlines/capofamiglia/add/') self.assertContains(response, '', html=True) self.assertContains(response, '', html=True) self.assertContains(response, '', html=True) self.assertContains(response, '', html=True) self.assertContains(response, '', html=True) self.assertContains(response, '', html=True) @override_settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True) def test_localize_pk_shortcut(self): """ Ensure that the "View on Site" link is correct for locales that use thousand separators """ holder = Holder.objects.create(pk=123456789, dummy=42) inner = Inner.objects.create(pk=987654321, holder=holder, dummy=42, readonly='') response = self.client.get('/admin/admin_inlines/holder/%i/' % holder.id) inner_shortcut = 'r/%s/%s/' % (ContentType.objects.get_for_model(inner).pk, inner.pk) self.assertContains(response, inner_shortcut) def test_custom_pk_shortcut(self): """ Ensure that the "View on Site" link is correct for models with a custom primary key field. Bug #18433. """ parent = ParentModelWithCustomPk.objects.create(my_own_pk="foo", name="Foo") child1 = ChildModel1.objects.create(my_own_pk="bar", name="Bar", parent=parent) child2 = ChildModel2.objects.create(my_own_pk="baz", name="Baz", parent=parent) response = self.client.get('/admin/admin_inlines/parentmodelwithcustompk/foo/') child1_shortcut = 'r/%s/%s/' % (ContentType.objects.get_for_model(child1).pk, child1.pk) child2_shortcut = 'r/%s/%s/' % (ContentType.objects.get_for_model(child2).pk, child2.pk) self.assertContains(response, child1_shortcut) self.assertContains(response, child2_shortcut) def test_create_inlines_on_inherited_model(self): """ Ensure that an object can be created with inlines when it inherits another class. Bug #19524. """ data = { 'name': 'Martian', 'sighting_set-TOTAL_FORMS': 1, 'sighting_set-INITIAL_FORMS': 0, 'sighting_set-MAX_NUM_FORMS': 0, 'sighting_set-0-place': 'Zone 51', '_save': 'Save', } response = self.client.post('/admin/admin_inlines/extraterrestrial/add/', data) self.assertEqual(response.status_code, 302) self.assertEqual(Sighting.objects.filter(et__name='Martian').count(), 1) def test_custom_get_extra_form(self): bt_head = BinaryTree.objects.create(name="Tree Head") BinaryTree.objects.create(name="First Child", parent=bt_head) # The maximum number of forms should respect 'get_max_num' on the # ModelAdmin max_forms_input = '' # The total number of forms will remain the same in either case total_forms_hidden = '' response = self.client.get('/admin/admin_inlines/binarytree/add/') self.assertContains(response, max_forms_input % 3) self.assertContains(response, total_forms_hidden) response = self.client.get("/admin/admin_inlines/binarytree/%d/" % bt_head.id) self.assertContains(response, max_forms_input % 2) self.assertContains(response, total_forms_hidden) def test_min_num(self): """ Ensure that min_num and extra determine number of forms. """ class MinNumInline(TabularInline): model = BinaryTree min_num = 2 extra = 3 modeladmin = ModelAdmin(BinaryTree, admin_site) modeladmin.inlines = [MinNumInline] min_forms = '' total_forms = '' request = self.factory.get('/admin/admin_inlines/binarytree/add/') request.user = User(username='super', is_superuser=True) response = modeladmin.changeform_view(request) self.assertContains(response, min_forms) self.assertContains(response, total_forms) def test_custom_min_num(self): """ Ensure that get_min_num is called and used correctly. """ bt_head = BinaryTree.objects.create(name="Tree Head") BinaryTree.objects.create(name="First Child", parent=bt_head) class MinNumInline(TabularInline): model = BinaryTree extra = 3 def get_min_num(self, request, obj=None, **kwargs): if obj: return 5 return 2 modeladmin = ModelAdmin(BinaryTree, admin_site) modeladmin.inlines = [MinNumInline] min_forms = '' total_forms = '' request = self.factory.get('/admin/admin_inlines/binarytree/add/') request.user = User(username='super', is_superuser=True) response = modeladmin.changeform_view(request) self.assertContains(response, min_forms % 2) self.assertContains(response, total_forms % 5) request = self.factory.get("/admin/admin_inlines/binarytree/%d/" % bt_head.id) request.user = User(username='super', is_superuser=True) response = modeladmin.changeform_view(request, object_id=str(bt_head.id)) self.assertContains(response, min_forms % 5) self.assertContains(response, total_forms % 8) def test_inline_nonauto_noneditable_pk(self): response = self.client.get('/admin/admin_inlines/author/add/') self.assertContains(response, '', html=True) self.assertContains(response, '', html=True) def test_inline_editable_pk(self): response = self.client.get('/admin/admin_inlines/author/add/') self.assertContains(response, '', html=True, count=1) self.assertContains(response, '', html=True, count=1) def test_stacked_inline_edit_form_contains_has_original_class(self): holder = Holder.objects.create(dummy=1) holder.inner_set.create(dummy=1) response = self.client.get('/admin/admin_inlines/holder/%s/' % holder.pk) self.assertContains( response, '