diff options
| author | Simon Charette <charette.s@gmail.com> | 2013-11-17 17:26:20 -0500 |
|---|---|---|
| committer | Tim Graham <timograham@gmail.com> | 2015-01-10 12:24:52 -0500 |
| commit | 07988744b347302925bc6cc66511e34224db55ab (patch) | |
| tree | 8931006956c957f377133fda6852dafc60e03ff9 /tests | |
| parent | 48ad288679a0cb2e2cfb17f128903e6c5b1c4870 (diff) | |
Fixed #13165 -- Added edit and delete links to admin foreign key widgets.
Thanks to Collin Anderson for the review and suggestions and Tim for the
final review.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/admin_custom_urls/tests.py | 2 | ||||
| -rw-r--r-- | tests/admin_views/admin.py | 3 | ||||
| -rw-r--r-- | tests/admin_views/models.py | 3 | ||||
| -rw-r--r-- | tests/admin_views/tests.py | 130 | ||||
| -rw-r--r-- | tests/admin_widgets/models.py | 3 | ||||
| -rw-r--r-- | tests/admin_widgets/tests.py | 46 | ||||
| -rw-r--r-- | tests/modeladmin/tests.py | 26 |
7 files changed, 182 insertions, 31 deletions
diff --git a/tests/admin_custom_urls/tests.py b/tests/admin_custom_urls/tests.py index d327b33407..f3182cd246 100644 --- a/tests/admin_custom_urls/tests.py +++ b/tests/admin_custom_urls/tests.py @@ -50,7 +50,7 @@ class AdminCustomUrlsTest(TestCase): } response = self.client.post('/admin/admin_custom_urls/action/!add/', post_data) self.assertEqual(response.status_code, 200) - self.assertContains(response, 'dismissAddAnotherPopup') + self.assertContains(response, 'dismissAddRelatedObjectPopup') self.assertContains(response, 'Action added through a popup') def test_admin_URLs_no_clash(self): diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py index b81de1d4ac..c9f0dbc2e3 100644 --- a/tests/admin_views/admin.py +++ b/tests/admin_views/admin.py @@ -53,6 +53,7 @@ callable_year.admin_order_field = 'date' class ArticleInline(admin.TabularInline): model = Article + fk_name = 'section' prepopulated_fields = { 'title': ('content',) } @@ -93,7 +94,7 @@ class ArticleAdmin(admin.ModelAdmin): }), ('Some other fields', { 'classes': ('wide',), - 'fields': ('date', 'section') + 'fields': ('date', 'section', 'sub_section') }) ) diff --git a/tests/admin_views/models.py b/tests/admin_views/models.py index 6c12030341..8a78514cec 100644 --- a/tests/admin_views/models.py +++ b/tests/admin_views/models.py @@ -32,6 +32,7 @@ class Article(models.Model): content = models.TextField() date = models.DateTimeField() section = models.ForeignKey(Section, null=True, blank=True) + sub_section = models.ForeignKey(Section, null=True, blank=True, on_delete=models.SET_NULL, related_name='+') def __str__(self): return self.title @@ -545,7 +546,7 @@ class Pizza(models.Model): class Album(models.Model): - owner = models.ForeignKey(User) + owner = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL) title = models.CharField(max_length=30) diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index 07baec7163..04dfb096a5 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -180,7 +180,7 @@ class AdminViewBasicTest(AdminViewBasicTestCase): } response = self.client.post('/test_admin/%s/admin_views/article/add/' % self.urlbit, post_data) self.assertEqual(response.status_code, 200) - self.assertContains(response, 'dismissAddAnotherPopup') + self.assertContains(response, 'dismissAddRelatedObjectPopup') self.assertContains(response, 'title with a new\\u000Aline') # Post data for edit inline @@ -648,8 +648,8 @@ class AdminViewBasicTest(AdminViewBasicTestCase): response = self.client.get("/test_admin/admin/admin_views/referencedbyinline/", {TO_FIELD_VAR: 'name'}) self.assertEqual(response.status_code, 200) - # We also want to prevent the add and change view from leaking a - # disallowed field value. + # We also want to prevent the add, change, and delete views from + # leaking a disallowed field value. with patch_logger('django.security.DisallowedModelAdminToField', 'error') as calls: response = self.client.post("/test_admin/admin/admin_views/section/add/", {TO_FIELD_VAR: 'name'}) self.assertEqual(response.status_code, 400) @@ -661,6 +661,11 @@ class AdminViewBasicTest(AdminViewBasicTestCase): self.assertEqual(response.status_code, 400) self.assertEqual(len(calls), 1) + with patch_logger('django.security.DisallowedModelAdminToField', 'error') as calls: + response = self.client.post("/test_admin/admin/admin_views/section/%d/delete/" % section.pk, {TO_FIELD_VAR: 'name'}) + self.assertEqual(response.status_code, 400) + self.assertEqual(len(calls), 1) + def test_allowed_filtering_15103(self): """ Regressions test for ticket 15103 - filtering on fields defined in a @@ -1472,21 +1477,75 @@ class AdminViewPermissionsTest(TestCase): login_url = reverse('admin:login') + '?next=/test_admin/admin/' # Set up and log in user. url = '/test_admin/admin/admin_views/article/add/' - add_link_text = ' class="add-another"' - self.client.get('/test_admin/admin/') + add_link_text = 'add_id_section' self.client.post(login_url, self.adduser_login) - # The add user can't add sections yet, so they shouldn't see the "add + # The user can't add sections yet, so they shouldn't see the "add # section" link. response = self.client.get(url) self.assertNotContains(response, add_link_text) - # Allow the add user to add sections too. Now they can see the "add + # Allow the user to add sections too. Now they can see the "add # section" link. - add_user = User.objects.get(username='adduser') + user = User.objects.get(username='adduser') perm = get_perm(Section, get_permission_codename('add', Section._meta)) - add_user.user_permissions.add(perm) + user.user_permissions.add(perm) response = self.client.get(url) self.assertContains(response, add_link_text) + def test_conditionally_show_change_section_link(self): + """ + The foreign key widget should only show the "change related" button if + the user has permission to change that related item. + """ + def get_change_related(response): + return response.context['adminform'].form.fields['section'].widget.can_change_related + + login_url = reverse('admin:login') + # Set up and log in user. + url = '/test_admin/admin/admin_views/article/add/' + change_link_text = 'change_id_section' + self.client.post(login_url, self.adduser_login) + # The user can't change sections yet, so they shouldn't see the "change + # section" link. + response = self.client.get(url) + self.assertFalse(get_change_related(response)) + self.assertNotContains(response, change_link_text) + # Allow the user to change sections too. Now they can see the "change + # section" link. + user = User.objects.get(username='adduser') + perm = get_perm(Section, get_permission_codename('change', Section._meta)) + user.user_permissions.add(perm) + response = self.client.get(url) + self.assertTrue(get_change_related(response)) + self.assertContains(response, change_link_text) + + def test_conditionally_show_delete_section_link(self): + """ + The foreign key widget should only show the "delete related" button if + the user has permission to delete that related item. + """ + def get_delete_related(response): + return response.context['adminform'].form.fields['sub_section'].widget.can_delete_related + + login_url = reverse('admin:login') + # Set up and log in user. + url = '/test_admin/admin/admin_views/article/add/' + delete_link_text = 'delete_id_sub_section' + self.client.get('/test_admin/admin/') + self.client.post(login_url, self.adduser_login) + # The user can't delete sections yet, so they shouldn't see the "delete + # section" link. + response = self.client.get(url) + self.assertFalse(get_delete_related(response)) + self.assertNotContains(response, delete_link_text) + # Allow the user to delete sections too. Now they can see the "delete + # section" link. + user = User.objects.get(username='adduser') + perm = get_perm(Section, get_permission_codename('delete', Section._meta)) + user.user_permissions.add(perm) + response = self.client.get(url) + self.assertTrue(get_delete_related(response)) + self.assertContains(response, delete_link_text) + def test_custom_model_admin_templates(self): login_url = reverse('admin:login') + '?next=/test_admin/admin/' self.client.get('/test_admin/admin/') @@ -4140,12 +4199,12 @@ class UserAdminTest(TestCase): self.assertEqual(adminform.form.errors['password2'], ["The two password fields didn't match."]) - def test_user_fk_popup(self): - """Quick user addition in a FK popup shouldn't invoke view for further user customization""" + def test_user_fk_add_popup(self): + """User addition through a FK popup should return the appropriate JavaScript response.""" response = self.client.get('/test_admin/admin/admin_views/album/add/') self.assertEqual(response.status_code, 200) self.assertContains(response, '/test_admin/admin/auth/user/add') - self.assertContains(response, 'class="add-another" id="add_id_owner"') + self.assertContains(response, 'class="related-widget-wrapper-link add-related" id="add_id_owner"') response = self.client.get('/test_admin/admin/auth/user/add/?_popup=1') self.assertEqual(response.status_code, 200) self.assertNotContains(response, 'name="_continue"') @@ -4159,7 +4218,52 @@ class UserAdminTest(TestCase): } response = self.client.post('/test_admin/admin/auth/user/add/?_popup=1', data, follow=True) self.assertEqual(response.status_code, 200) - self.assertContains(response, 'dismissAddAnotherPopup') + self.assertContains(response, 'dismissAddRelatedObjectPopup') + + def test_user_fk_change_popup(self): + """User change through a FK popup should return the appropriate JavaScript response.""" + response = self.client.get('/test_admin/admin/admin_views/album/add/') + self.assertEqual(response.status_code, 200) + self.assertContains(response, '/test_admin/admin/auth/user/__fk__/') + self.assertContains(response, 'class="related-widget-wrapper-link change-related" id="change_id_owner"') + user = User.objects.get(username='changeuser') + url = "/test_admin/admin/auth/user/%s/?_popup=1" % user.pk + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + self.assertNotContains(response, 'name="_continue"') + self.assertNotContains(response, 'name="_addanother"') + data = { + 'username': 'newuser', + 'password1': 'newpassword', + 'password2': 'newpassword', + 'last_login_0': '2007-05-30', + 'last_login_1': '13:20:10', + 'date_joined_0': '2007-05-30', + 'date_joined_1': '13:20:10', + '_popup': '1', + '_save': '1', + } + response = self.client.post(url, data, follow=True) + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'dismissChangeRelatedObjectPopup') + + def test_user_fk_delete_popup(self): + """User deletion through a FK popup should return the appropriate JavaScript response.""" + response = self.client.get('/test_admin/admin/admin_views/album/add/') + self.assertEqual(response.status_code, 200) + self.assertContains(response, '/test_admin/admin/auth/user/__fk__/delete/') + self.assertContains(response, 'class="related-widget-wrapper-link change-related" id="change_id_owner"') + user = User.objects.get(username='changeuser') + url = "/test_admin/admin/auth/user/%s/delete/?_popup=1" % user.pk + response = self.client.get(url) + self.assertEqual(response.status_code, 200) + data = { + 'post': 'yes', + '_popup': '1', + } + response = self.client.post(url, data, follow=True) + self.assertEqual(response.status_code, 200) + self.assertContains(response, 'dismissDeleteRelatedObjectPopup') def test_save_add_another_button(self): user_count = User.objects.count() diff --git a/tests/admin_widgets/models.py b/tests/admin_widgets/models.py index 97153caadc..5d65dcd174 100644 --- a/tests/admin_widgets/models.py +++ b/tests/admin_widgets/models.py @@ -108,7 +108,8 @@ class Individual(models.Model): related instances (rendering will be called programmatically in this case). """ name = models.CharField(max_length=20) - parent = models.ForeignKey('self', null=True) + parent = models.ForeignKey('self', null=True, on_delete=models.SET_NULL) + soulmate = models.ForeignKey('self', null=True, on_delete=models.CASCADE, related_name='soulmates') class Company(models.Model): diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py index 3180dbcb9c..c5795c87fb 100644 --- a/tests/admin_widgets/tests.py +++ b/tests/admin_widgets/tests.py @@ -510,6 +510,32 @@ class RelatedFieldWidgetWrapperTests(DjangoTestCase): w = widgets.RelatedFieldWidgetWrapper(w, rel, widget_admin_site) self.assertFalse(w.can_add_related) + def test_select_multiple_widget_cant_change_delete_related(self): + rel = models.Individual._meta.get_field('parent').rel + widget = forms.SelectMultiple() + wrapper = widgets.RelatedFieldWidgetWrapper( + widget, rel, widget_admin_site, + can_add_related=True, + can_change_related=True, + can_delete_related=True, + ) + self.assertTrue(wrapper.can_add_related) + self.assertFalse(wrapper.can_change_related) + self.assertFalse(wrapper.can_delete_related) + + def test_on_delete_cascade_rel_cant_delete_related(self): + rel = models.Individual._meta.get_field('soulmate').rel + widget = forms.Select() + wrapper = widgets.RelatedFieldWidgetWrapper( + widget, rel, widget_admin_site, + can_add_related=True, + can_change_related=True, + can_delete_related=True, + ) + self.assertTrue(wrapper.can_add_related) + self.assertTrue(wrapper.can_change_related) + self.assertFalse(wrapper.can_delete_related) + @override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',), ROOT_URLCONF='admin_widgets.urls') @@ -1134,9 +1160,27 @@ class RelatedFieldWidgetSeleniumFirefoxTests(AdminSeleniumWebDriverTestCase): # The field now contains the new user self.wait_for('#id_user option[value="newuser"]') + # Click the Change User button to change it + self.selenium.find_element_by_id('change_id_user').click() + self.selenium.switch_to_window('id_user') + self.wait_page_loaded() + + username_field = self.selenium.find_element_by_id('id_username') + username_value = 'changednewuser' + username_field.clear() + username_field.send_keys(username_value) + + save_button_css_selector = '.submit-row > input[type=submit]' + self.selenium.find_element_by_css_selector(save_button_css_selector).click() + self.selenium.switch_to_window(main_window) + # Wait up to 2 seconds for the new option to show up after clicking save in the popup. + self.selenium.implicitly_wait(2) + self.selenium.find_element_by_css_selector('#id_user option[value=changednewuser]') + self.selenium.implicitly_wait(0) + # Go ahead and submit the form to make sure it works self.selenium.find_element_by_css_selector(save_button_css_selector).click() - self.wait_for_text('li.success', 'The profile "newuser" was added successfully.') + self.wait_for_text('li.success', 'The profile "changednewuser" was added successfully.') profiles = models.Profile.objects.all() self.assertEqual(len(profiles), 1) self.assertEqual(profiles[0].user.username, username_value) diff --git a/tests/modeladmin/tests.py b/tests/modeladmin/tests.py index ca3a8dbfaf..ee8c26c3ca 100644 --- a/tests/modeladmin/tests.py +++ b/tests/modeladmin/tests.py @@ -355,30 +355,30 @@ class ModelAdminTests(TestCase): form = ma.get_form(request)() self.assertHTMLEqual(str(form["main_band"]), - '<select name="main_band" id="id_main_band">\n' - '<option value="" selected="selected">---------</option>\n' - '<option value="%d">The Beatles</option>\n' - '<option value="%d">The Doors</option>\n' - '</select>' % (band2.id, self.band.id)) + '<div class="related-widget-wrapper">' + '<select name="main_band" id="id_main_band">' + '<option value="" selected="selected">---------</option>' + '<option value="%d">The Beatles</option>' + '<option value="%d">The Doors</option>' + '</select></div>' % (band2.id, self.band.id)) class AdminConcertForm(forms.ModelForm): - pass - def __init__(self, *args, **kwargs): super(AdminConcertForm, self).__init__(*args, **kwargs) self.fields["main_band"].queryset = Band.objects.filter(name='The Doors') - class ConcertAdmin(ModelAdmin): + class ConcertAdminWithForm(ModelAdmin): form = AdminConcertForm - ma = ConcertAdmin(Concert, self.site) + ma = ConcertAdminWithForm(Concert, self.site) form = ma.get_form(request)() self.assertHTMLEqual(str(form["main_band"]), - '<select name="main_band" id="id_main_band">\n' - '<option value="" selected="selected">---------</option>\n' - '<option value="%d">The Doors</option>\n' - '</select>' % self.band.id) + '<div class="related-widget-wrapper">' + '<select name="main_band" id="id_main_band">' + '<option value="" selected="selected">---------</option>' + '<option value="%d">The Doors</option>' + '</select></div>' % self.band.id) def test_regression_for_ticket_15820(self): """ |
