summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorSimon Charette <charette.s@gmail.com>2013-11-17 17:26:20 -0500
committerTim Graham <timograham@gmail.com>2015-01-10 12:24:52 -0500
commit07988744b347302925bc6cc66511e34224db55ab (patch)
tree8931006956c957f377133fda6852dafc60e03ff9 /tests
parent48ad288679a0cb2e2cfb17f128903e6c5b1c4870 (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.py2
-rw-r--r--tests/admin_views/admin.py3
-rw-r--r--tests/admin_views/models.py3
-rw-r--r--tests/admin_views/tests.py130
-rw-r--r--tests/admin_widgets/models.py3
-rw-r--r--tests/admin_widgets/tests.py46
-rw-r--r--tests/modeladmin/tests.py26
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):
"""