mirror of https://github.com/django/django.git
2966 lines
131 KiB
Python
2966 lines
131 KiB
Python
# coding: utf-8
|
|
|
|
import re
|
|
import datetime
|
|
import urlparse
|
|
|
|
from django.conf import settings
|
|
from django.core import mail
|
|
from django.core.exceptions import SuspiciousOperation
|
|
from django.core.files import temp as tempfile
|
|
from django.core.urlresolvers import reverse
|
|
# Register auth models with the admin.
|
|
from django.contrib.auth import REDIRECT_FIELD_NAME, admin
|
|
from django.contrib.auth.models import User, Permission, UNUSABLE_PASSWORD
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.contrib.admin.models import LogEntry, DELETION
|
|
from django.contrib.admin.sites import LOGIN_FORM_KEY
|
|
from django.contrib.admin.util import quote
|
|
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
|
|
from django.contrib.admin.views.main import IS_POPUP_VAR
|
|
from django.forms.util import ErrorList
|
|
import django.template.context
|
|
from django.test import TestCase
|
|
from django.utils import formats
|
|
from django.utils.cache import get_max_age
|
|
from django.utils.encoding import iri_to_uri
|
|
from django.utils.html import escape
|
|
from django.utils.http import urlencode
|
|
from django.utils.translation import activate, deactivate
|
|
from django.utils import unittest
|
|
|
|
# local test models
|
|
from models import (Article, BarAccount, CustomArticle, EmptyModel,
|
|
FooAccount, Gallery, ModelWithStringPrimaryKey,
|
|
Person, Persona, Picture, Podcast, Section, Subscriber, Vodcast,
|
|
Language, Collector, Widget, Grommet, DooHickey, FancyDoodad, Whatsit,
|
|
Category, Post, Plot, FunkyTag, Chapter, Book, Promo, WorkHour, Employee,
|
|
Question, Answer, Inquisition, Actor, FoodDelivery,
|
|
RowLevelChangePermissionModel, Paper, CoverLetter, Story, OtherStory)
|
|
|
|
|
|
class AdminViewBasicTest(TestCase):
|
|
fixtures = ['admin-views-users.xml', 'admin-views-colors.xml',
|
|
'admin-views-fabrics.xml', 'admin-views-books.xml']
|
|
|
|
# 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.old_USE_I18N = settings.USE_I18N
|
|
self.old_USE_L10N = settings.USE_L10N
|
|
self.old_LANGUAGE_CODE = settings.LANGUAGE_CODE
|
|
self.client.login(username='super', password='secret')
|
|
settings.USE_I18N = True
|
|
|
|
def tearDown(self):
|
|
settings.USE_I18N = self.old_USE_I18N
|
|
settings.USE_L10N = self.old_USE_L10N
|
|
settings.LANGUAGE_CODE = self.old_LANGUAGE_CODE
|
|
self.client.logout()
|
|
formats.reset_format_cache()
|
|
|
|
def testTrailingSlashRequired(self):
|
|
"""
|
|
If you leave off the trailing slash, app should redirect and add it.
|
|
"""
|
|
request = self.client.get('/test_admin/%s/admin_views/article/add' % self.urlbit)
|
|
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.assertEqual(response.status_code, 200)
|
|
|
|
def testAddWithGETArgs(self):
|
|
response = self.client.get('/test_admin/%s/admin_views/section/add/' % self.urlbit, {'name': 'My Section'})
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTrue(
|
|
'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 ensure GET on the change_view works.
|
|
"""
|
|
response = self.client.get('/test_admin/%s/admin_views/section/1/' % self.urlbit)
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
def testBasicEditGetStringPK(self):
|
|
"""
|
|
A smoke test to ensure GET on the change_view works (returns an HTTP
|
|
404 error, see #11191) when passing a string as the PK argument for a
|
|
model with an integer PK field.
|
|
"""
|
|
response = self.client.get('/test_admin/%s/admin_views/section/abc/' % self.urlbit)
|
|
self.assertEqual(response.status_code, 404)
|
|
|
|
def testBasicAddPost(self):
|
|
"""
|
|
A smoke test to ensure POST on add_view works.
|
|
"""
|
|
post_data = {
|
|
"name": u"Another Section",
|
|
# inline data
|
|
"article_set-TOTAL_FORMS": u"3",
|
|
"article_set-INITIAL_FORMS": u"0",
|
|
"article_set-MAX_NUM_FORMS": u"0",
|
|
}
|
|
response = self.client.post('/test_admin/%s/admin_views/section/add/' % self.urlbit, post_data)
|
|
self.assertEqual(response.status_code, 302) # redirect somewhere
|
|
|
|
def testPopupAddPost(self):
|
|
"""
|
|
Ensure http response from a popup is properly escaped.
|
|
"""
|
|
post_data = {
|
|
'_popup': u'1',
|
|
'title': u'title with a new\nline',
|
|
'content': u'some content',
|
|
'date_0': u'2010-09-10',
|
|
'date_1': u'14:55:39',
|
|
}
|
|
response = self.client.post('/test_admin/%s/admin_views/article/add/' % self.urlbit, post_data)
|
|
self.failUnlessEqual(response.status_code, 200)
|
|
self.assertContains(response, 'dismissAddAnotherPopup')
|
|
self.assertContains(response, 'title with a new\u000Aline')
|
|
|
|
# Post data for edit inline
|
|
inline_post_data = {
|
|
"name": u"Test section",
|
|
# inline data
|
|
"article_set-TOTAL_FORMS": u"6",
|
|
"article_set-INITIAL_FORMS": u"3",
|
|
"article_set-MAX_NUM_FORMS": u"0",
|
|
"article_set-0-id": u"1",
|
|
# there is no title in database, give one here or formset will fail.
|
|
"article_set-0-title": u"Norske bostaver æøå skaper problemer",
|
|
"article_set-0-content": u"<p>Middle content</p>",
|
|
"article_set-0-date_0": u"2008-03-18",
|
|
"article_set-0-date_1": u"11:54:58",
|
|
"article_set-0-section": u"1",
|
|
"article_set-1-id": u"2",
|
|
"article_set-1-title": u"Need a title.",
|
|
"article_set-1-content": u"<p>Oldest content</p>",
|
|
"article_set-1-date_0": u"2000-03-18",
|
|
"article_set-1-date_1": u"11:54:58",
|
|
"article_set-2-id": u"3",
|
|
"article_set-2-title": u"Need a title.",
|
|
"article_set-2-content": u"<p>Newest content</p>",
|
|
"article_set-2-date_0": u"2009-03-18",
|
|
"article_set-2-date_1": u"11:54:58",
|
|
"article_set-3-id": u"",
|
|
"article_set-3-title": u"",
|
|
"article_set-3-content": u"",
|
|
"article_set-3-date_0": u"",
|
|
"article_set-3-date_1": u"",
|
|
"article_set-4-id": u"",
|
|
"article_set-4-title": u"",
|
|
"article_set-4-content": u"",
|
|
"article_set-4-date_0": u"",
|
|
"article_set-4-date_1": u"",
|
|
"article_set-5-id": u"",
|
|
"article_set-5-title": u"",
|
|
"article_set-5-content": u"",
|
|
"article_set-5-date_0": u"",
|
|
"article_set-5-date_1": u"",
|
|
}
|
|
|
|
def testBasicEditPost(self):
|
|
"""
|
|
A smoke test to ensure POST on edit_view works.
|
|
"""
|
|
response = self.client.post('/test_admin/%s/admin_views/section/1/' % self.urlbit, self.inline_post_data)
|
|
self.assertEqual(response.status_code, 302) # redirect somewhere
|
|
|
|
def testEditSaveAs(self):
|
|
"""
|
|
Test "save as".
|
|
"""
|
|
post_data = self.inline_post_data.copy()
|
|
post_data.update({
|
|
'_saveasnew': u'Save+as+new',
|
|
"article_set-1-section": u"1",
|
|
"article_set-2-section": u"1",
|
|
"article_set-3-section": u"1",
|
|
"article_set-4-section": u"1",
|
|
"article_set-5-section": u"1",
|
|
})
|
|
response = self.client.post('/test_admin/%s/admin_views/section/1/' % self.urlbit, post_data)
|
|
self.assertEqual(response.status_code, 302) # redirect somewhere
|
|
|
|
def testChangeListSortingCallable(self):
|
|
"""
|
|
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})
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTrue(
|
|
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 callable are out of order."
|
|
)
|
|
|
|
def testChangeListSortingModel(self):
|
|
"""
|
|
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})
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTrue(
|
|
response.content.index('Newest content') < response.content.index('Middle content') and
|
|
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
|
|
(colunn 4 is 'modeladmin_year' in ArticleAdmin)
|
|
"""
|
|
response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'ot': 'asc', 'o': 4})
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTrue(
|
|
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.
|
|
This also tests relation-spanning filters (e.g. 'color__value').
|
|
"""
|
|
response = self.client.get('/test_admin/%s/admin_views/thing/' % self.urlbit)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTrue(
|
|
'<div id="changelist-filter">' in response.content,
|
|
"Expected filter not found in changelist view."
|
|
)
|
|
self.assertFalse(
|
|
'<a href="?color__id__exact=3">Blue</a>' in response.content,
|
|
"Changelist filter not correctly limited by limit_choices_to."
|
|
)
|
|
|
|
def testRelationSpanningFilters(self):
|
|
response = self.client.get('/test_admin/%s/admin_views/chapterxtra1/' %
|
|
self.urlbit)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, '<div id="changelist-filter">')
|
|
filters = {
|
|
'chap__id__exact': dict(
|
|
values=[c.id for c in Chapter.objects.all()],
|
|
test=lambda obj, value: obj.chap.id == value),
|
|
'chap__title': dict(
|
|
values=[c.title for c in Chapter.objects.all()],
|
|
test=lambda obj, value: obj.chap.title == value),
|
|
'chap__book__id__exact': dict(
|
|
values=[b.id for b in Book.objects.all()],
|
|
test=lambda obj, value: obj.chap.book.id == value),
|
|
'chap__book__name': dict(
|
|
values=[b.name for b in Book.objects.all()],
|
|
test=lambda obj, value: obj.chap.book.name == value),
|
|
'chap__book__promo__id__exact': dict(
|
|
values=[p.id for p in Promo.objects.all()],
|
|
test=lambda obj, value:
|
|
obj.chap.book.promo_set.filter(id=value).exists()),
|
|
'chap__book__promo__name': dict(
|
|
values=[p.name for p in Promo.objects.all()],
|
|
test=lambda obj, value:
|
|
obj.chap.book.promo_set.filter(name=value).exists()),
|
|
}
|
|
for filter_path, params in filters.items():
|
|
for value in params['values']:
|
|
query_string = urlencode({filter_path: value})
|
|
# ensure filter link exists
|
|
self.assertContains(response, '<a href="?%s">' % query_string)
|
|
# ensure link works
|
|
filtered_response = self.client.get(
|
|
'/test_admin/%s/admin_views/chapterxtra1/?%s' % (
|
|
self.urlbit, query_string))
|
|
self.assertEqual(filtered_response.status_code, 200)
|
|
# ensure changelist contains only valid objects
|
|
for obj in filtered_response.context['cl'].query_set.all():
|
|
self.assertTrue(params['test'](obj, value))
|
|
|
|
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)
|
|
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)
|
|
|
|
def testIsNullLookups(self):
|
|
"""Ensure is_null is handled correctly."""
|
|
Article.objects.create(title="I Could Go Anywhere", content="Versatile", date=datetime.datetime.now())
|
|
response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit)
|
|
self.assertTrue('4 articles' in response.content, '"4 articles" missing from response')
|
|
response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'section__isnull': 'false'})
|
|
self.assertTrue('3 articles' in response.content, '"3 articles" missing from response')
|
|
response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'section__isnull': 'true'})
|
|
self.assertTrue('1 article' in response.content, '"1 article" missing from response')
|
|
|
|
def testLogoutAndPasswordChangeURLs(self):
|
|
response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit)
|
|
self.assertFalse('<a href="/test_admin/%s/logout/">' % self.urlbit not in response.content)
|
|
self.assertFalse('<a href="/test_admin/%s/password_change/">' % self.urlbit not in response.content)
|
|
|
|
def testNamedGroupFieldChoicesChangeList(self):
|
|
"""
|
|
Ensures the admin changelist shows correct values in the relevant column
|
|
for rows corresponding to instances of a model in which a named group
|
|
has been used in the choices option of a field.
|
|
"""
|
|
response = self.client.get('/test_admin/%s/admin_views/fabric/' % self.urlbit)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTrue(
|
|
'<a href="1/">Horizontal</a>' in response.content and
|
|
'<a href="2/">Vertical</a>' in response.content,
|
|
"Changelist table isn't showing the right human-readable values set by a model field 'choices' option named group."
|
|
)
|
|
|
|
def testNamedGroupFieldChoicesFilter(self):
|
|
"""
|
|
Ensures the filter UI shows correctly when at least one named group has
|
|
been used in the choices option of a model field.
|
|
"""
|
|
response = self.client.get('/test_admin/%s/admin_views/fabric/' % self.urlbit)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTrue(
|
|
'<div id="changelist-filter">' in response.content,
|
|
"Expected filter not found in changelist view."
|
|
)
|
|
self.assertTrue(
|
|
'<a href="?surface__exact=x">Horizontal</a>' in response.content and
|
|
'<a href="?surface__exact=y">Vertical</a>' in response.content,
|
|
"Changelist filter isn't showing options contained inside a model field 'choices' option named group."
|
|
)
|
|
|
|
def testChangeListNullBooleanDisplay(self):
|
|
Post.objects.create(public=None)
|
|
# This hard-codes the URl because it'll fail if it runs
|
|
# against the 'admin2' custom admin (which doesn't have the
|
|
# Post model).
|
|
response = self.client.get("/test_admin/admin/admin_views/post/")
|
|
self.assertTrue('icon-unknown.gif' in response.content)
|
|
|
|
def testI18NLanguageNonEnglishDefault(self):
|
|
"""
|
|
Check if the Javascript i18n view returns an empty language catalog
|
|
if the default language is non-English but the selected language
|
|
is English. See #13388 and #3594 for more details.
|
|
"""
|
|
try:
|
|
settings.LANGUAGE_CODE = 'fr'
|
|
activate('en-us')
|
|
response = self.client.get('/test_admin/admin/jsi18n/')
|
|
self.assertNotContains(response, 'Choisir une heure')
|
|
finally:
|
|
deactivate()
|
|
|
|
def testI18NLanguageNonEnglishFallback(self):
|
|
"""
|
|
Makes sure that the fallback language is still working properly
|
|
in cases where the selected language cannot be found.
|
|
"""
|
|
try:
|
|
settings.LANGUAGE_CODE = 'fr'
|
|
activate('none')
|
|
response = self.client.get('/test_admin/admin/jsi18n/')
|
|
self.assertContains(response, 'Choisir une heure')
|
|
finally:
|
|
deactivate()
|
|
|
|
def testL10NDeactivated(self):
|
|
"""
|
|
Check if L10N is deactivated, the Javascript i18n view doesn't
|
|
return localized date/time formats. Refs #14824.
|
|
"""
|
|
try:
|
|
settings.LANGUAGE_CODE = 'ru'
|
|
settings.USE_L10N = False
|
|
activate('ru')
|
|
response = self.client.get('/test_admin/admin/jsi18n/')
|
|
self.assertNotContains(response, '%d.%m.%Y %H:%M:%S')
|
|
self.assertContains(response, '%Y-%m-%d %H:%M:%S')
|
|
finally:
|
|
deactivate()
|
|
|
|
|
|
def test_disallowed_filtering(self):
|
|
self.assertRaises(SuspiciousOperation,
|
|
self.client.get, "/test_admin/admin/admin_views/album/?owner__email__startswith=fuzzy"
|
|
)
|
|
|
|
try:
|
|
self.client.get("/test_admin/admin/admin_views/thing/?color__value__startswith=red")
|
|
self.client.get("/test_admin/admin/admin_views/thing/?color__value=red")
|
|
except SuspiciousOperation:
|
|
self.fail("Filters are allowed if explicitly included in list_filter")
|
|
|
|
try:
|
|
self.client.get("/test_admin/admin/admin_views/person/?age__gt=30")
|
|
except SuspiciousOperation:
|
|
self.fail("Filters should be allowed if they involve a local field without the need to whitelist them in list_filter or date_hierarchy.")
|
|
|
|
e1 = Employee.objects.create(name='Anonymous', gender=1, age=22, alive=True, code='123')
|
|
e2 = Employee.objects.create(name='Visitor', gender=2, age=19, alive=True, code='124')
|
|
WorkHour.objects.create(datum=datetime.datetime.now(), employee=e1)
|
|
WorkHour.objects.create(datum=datetime.datetime.now(), employee=e2)
|
|
response = self.client.get("/test_admin/admin/admin_views/workhour/")
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertContains(response, 'employee__person_ptr__exact')
|
|
response = self.client.get("/test_admin/admin/admin_views/workhour/?employee__person_ptr__exact=%d" % e1.pk)
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
def test_allowed_filtering_15103(self):
|
|
"""
|
|
Regressions test for ticket 15103 - filtering on fields defined in a
|
|
ForeignKey 'limit_choices_to' should be allowed, otherwise raw_id_fields
|
|
can break.
|
|
"""
|
|
try:
|
|
self.client.get("/test_admin/admin/admin_views/inquisition/?leader__name=Palin&leader__age=27")
|
|
except SuspiciousOperation:
|
|
self.fail("Filters should be allowed if they are defined on a ForeignKey pointing to this model")
|
|
|
|
class AdminJavaScriptTest(AdminViewBasicTest):
|
|
def testSingleWidgetFirsFieldFocus(self):
|
|
"""
|
|
JavaScript-assisted auto-focus on first field.
|
|
"""
|
|
response = self.client.get('/test_admin/%s/admin_views/picture/add/' % self.urlbit)
|
|
self.assertContains(
|
|
response,
|
|
'<script type="text/javascript">document.getElementById("id_name").focus();</script>'
|
|
)
|
|
|
|
def testMultiWidgetFirsFieldFocus(self):
|
|
"""
|
|
JavaScript-assisted auto-focus should work if a model/ModelAdmin setup
|
|
is such that the first form field has a MultiWidget.
|
|
"""
|
|
response = self.client.get('/test_admin/%s/admin_views/reservation/add/' % self.urlbit)
|
|
self.assertContains(
|
|
response,
|
|
'<script type="text/javascript">document.getElementById("id_start_date_0").focus();</script>'
|
|
)
|
|
|
|
|
|
class SaveAsTests(TestCase):
|
|
fixtures = ['admin-views-users.xml','admin-views-person.xml']
|
|
|
|
def setUp(self):
|
|
self.client.login(username='super', password='secret')
|
|
|
|
def tearDown(self):
|
|
self.client.logout()
|
|
|
|
def test_save_as_duplication(self):
|
|
"""Ensure save as actually creates a new person"""
|
|
post_data = {'_saveasnew':'', 'name':'John M', 'gender':1, 'age': 42}
|
|
response = self.client.post('/test_admin/admin/admin_views/person/1/', post_data)
|
|
self.assertEqual(len(Person.objects.filter(name='John M')), 1)
|
|
self.assertEqual(len(Person.objects.filter(id=1)), 1)
|
|
|
|
def test_save_as_display(self):
|
|
"""
|
|
Ensure that 'save as' is displayed when activated and after submitting
|
|
invalid data aside save_as_new will not show us a form to overwrite the
|
|
initial model.
|
|
"""
|
|
response = self.client.get('/test_admin/admin/admin_views/person/1/')
|
|
self.assertTrue(response.context['save_as'])
|
|
post_data = {'_saveasnew':'', 'name':'John M', 'gender':3, 'alive':'checked'}
|
|
response = self.client.post('/test_admin/admin/admin_views/person/1/', post_data)
|
|
self.assertEqual(response.context['form_url'], '../add/')
|
|
|
|
class CustomModelAdminTest(AdminViewBasicTest):
|
|
urlbit = "admin2"
|
|
|
|
def testCustomAdminSiteLoginForm(self):
|
|
self.client.logout()
|
|
request = self.client.get('/test_admin/admin2/')
|
|
self.assertEqual(request.status_code, 200)
|
|
login = self.client.post('/test_admin/admin2/', {
|
|
REDIRECT_FIELD_NAME: '/test_admin/admin2/',
|
|
LOGIN_FORM_KEY: 1,
|
|
'username': 'customform',
|
|
'password': 'secret',
|
|
})
|
|
self.assertEqual(login.status_code, 200)
|
|
self.assertContains(login, 'custom form error')
|
|
|
|
def testCustomAdminSiteLoginTemplate(self):
|
|
self.client.logout()
|
|
request = self.client.get('/test_admin/admin2/')
|
|
self.assertTemplateUsed(request, 'custom_admin/login.html')
|
|
self.assertTrue('Hello from a custom login template' in request.content)
|
|
|
|
def testCustomAdminSiteLogoutTemplate(self):
|
|
request = self.client.get('/test_admin/admin2/logout/')
|
|
self.assertTemplateUsed(request, 'custom_admin/logout.html')
|
|
self.assertTrue('Hello from a custom logout template' in request.content)
|
|
|
|
def testCustomAdminSiteIndexViewAndTemplate(self):
|
|
request = self.client.get('/test_admin/admin2/')
|
|
self.assertTemplateUsed(request, 'custom_admin/index.html')
|
|
self.assertTrue('Hello from a custom index template *bar*' in request.content)
|
|
|
|
def testCustomAdminSitePasswordChangeTemplate(self):
|
|
request = self.client.get('/test_admin/admin2/password_change/')
|
|
self.assertTemplateUsed(request, 'custom_admin/password_change_form.html')
|
|
self.assertTrue('Hello from a custom password change form template' in request.content)
|
|
|
|
def testCustomAdminSitePasswordChangeDoneTemplate(self):
|
|
request = self.client.get('/test_admin/admin2/password_change/done/')
|
|
self.assertTemplateUsed(request, 'custom_admin/password_change_done.html')
|
|
self.assertTrue('Hello from a custom password change done template' in request.content)
|
|
|
|
def testCustomAdminSiteView(self):
|
|
self.client.login(username='super', password='secret')
|
|
response = self.client.get('/test_admin/%s/my_view/' % self.urlbit)
|
|
self.assertTrue(response.content == "Django is a magical pony!", response.content)
|
|
|
|
def get_perm(Model, perm):
|
|
"""Return the permission object, for the Model"""
|
|
ct = ContentType.objects.get_for_model(Model)
|
|
return Permission.objects.get(content_type=ct, codename=perm)
|
|
|
|
class AdminViewPermissionsTest(TestCase):
|
|
"""Tests for Admin Views Permissions."""
|
|
|
|
fixtures = ['admin-views-users.xml']
|
|
|
|
def setUp(self):
|
|
"""Test setup."""
|
|
# Setup permissions, for our users who can add, change, and delete.
|
|
# We can't put this into the fixture, because the content type id
|
|
# and the permission id could be different on each run of the test.
|
|
|
|
opts = Article._meta
|
|
|
|
# User who can add Articles
|
|
add_user = User.objects.get(username='adduser')
|
|
add_user.user_permissions.add(get_perm(Article,
|
|
opts.get_add_permission()))
|
|
|
|
# User who can change Articles
|
|
change_user = User.objects.get(username='changeuser')
|
|
change_user.user_permissions.add(get_perm(Article,
|
|
opts.get_change_permission()))
|
|
|
|
# User who can delete Articles
|
|
delete_user = User.objects.get(username='deleteuser')
|
|
delete_user.user_permissions.add(get_perm(Article,
|
|
opts.get_delete_permission()))
|
|
|
|
delete_user.user_permissions.add(get_perm(Section,
|
|
Section._meta.get_delete_permission()))
|
|
|
|
# login POST dicts
|
|
self.super_login = {
|
|
REDIRECT_FIELD_NAME: '/test_admin/admin/',
|
|
LOGIN_FORM_KEY: 1,
|
|
'username': 'super',
|
|
'password': 'secret',
|
|
}
|
|
self.super_email_login = {
|
|
REDIRECT_FIELD_NAME: '/test_admin/admin/',
|
|
LOGIN_FORM_KEY: 1,
|
|
'username': 'super@example.com',
|
|
'password': 'secret',
|
|
}
|
|
self.super_email_bad_login = {
|
|
REDIRECT_FIELD_NAME: '/test_admin/admin/',
|
|
LOGIN_FORM_KEY: 1,
|
|
'username': 'super@example.com',
|
|
'password': 'notsecret',
|
|
}
|
|
self.adduser_login = {
|
|
REDIRECT_FIELD_NAME: '/test_admin/admin/',
|
|
LOGIN_FORM_KEY: 1,
|
|
'username': 'adduser',
|
|
'password': 'secret',
|
|
}
|
|
self.changeuser_login = {
|
|
REDIRECT_FIELD_NAME: '/test_admin/admin/',
|
|
LOGIN_FORM_KEY: 1,
|
|
'username': 'changeuser',
|
|
'password': 'secret',
|
|
}
|
|
self.deleteuser_login = {
|
|
REDIRECT_FIELD_NAME: '/test_admin/admin/',
|
|
LOGIN_FORM_KEY: 1,
|
|
'username': 'deleteuser',
|
|
'password': 'secret',
|
|
}
|
|
self.joepublic_login = {
|
|
REDIRECT_FIELD_NAME: '/test_admin/admin/',
|
|
LOGIN_FORM_KEY: 1,
|
|
'username': 'joepublic',
|
|
'password': 'secret',
|
|
}
|
|
self.no_username_login = {
|
|
REDIRECT_FIELD_NAME: '/test_admin/admin/',
|
|
LOGIN_FORM_KEY: 1,
|
|
'password': 'secret',
|
|
}
|
|
|
|
def testLogin(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
|
|
a 200 status code.
|
|
"""
|
|
# Super User
|
|
request = self.client.get('/test_admin/admin/')
|
|
self.assertEqual(request.status_code, 200)
|
|
login = self.client.post('/test_admin/admin/', self.super_login)
|
|
self.assertRedirects(login, '/test_admin/admin/')
|
|
self.assertFalse(login.context)
|
|
self.client.get('/test_admin/admin/logout/')
|
|
|
|
# Test if user enters email address
|
|
request = self.client.get('/test_admin/admin/')
|
|
self.assertEqual(request.status_code, 200)
|
|
login = self.client.post('/test_admin/admin/', self.super_email_login)
|
|
self.assertContains(login, "Your e-mail address is not your username")
|
|
# only correct passwords get a username hint
|
|
login = self.client.post('/test_admin/admin/', self.super_email_bad_login)
|
|
self.assertContains(login, "Please enter a correct username and password.")
|
|
new_user = User(username='jondoe', password='secret', email='super@example.com')
|
|
new_user.save()
|
|
# check to ensure if there are multiple email addresses a user doesn't get a 500
|
|
login = self.client.post('/test_admin/admin/', self.super_email_login)
|
|
self.assertContains(login, "Please enter a correct username and password.")
|
|
|
|
# Add User
|
|
request = self.client.get('/test_admin/admin/')
|
|
self.assertEqual(request.status_code, 200)
|
|
login = self.client.post('/test_admin/admin/', self.adduser_login)
|
|
self.assertRedirects(login, '/test_admin/admin/')
|
|
self.assertFalse(login.context)
|
|
self.client.get('/test_admin/admin/logout/')
|
|
|
|
# Change User
|
|
request = self.client.get('/test_admin/admin/')
|
|
self.assertEqual(request.status_code, 200)
|
|
login = self.client.post('/test_admin/admin/', self.changeuser_login)
|
|
self.assertRedirects(login, '/test_admin/admin/')
|
|
self.assertFalse(login.context)
|
|
self.client.get('/test_admin/admin/logout/')
|
|
|
|
# Delete User
|
|
request = self.client.get('/test_admin/admin/')
|
|
self.assertEqual(request.status_code, 200)
|
|
login = self.client.post('/test_admin/admin/', self.deleteuser_login)
|
|
self.assertRedirects(login, '/test_admin/admin/')
|
|
self.assertFalse(login.context)
|
|
self.client.get('/test_admin/admin/logout/')
|
|
|
|
# Regular User should not be able to login.
|
|
request = self.client.get('/test_admin/admin/')
|
|
self.assertEqual(request.status_code, 200)
|
|
login = self.client.post('/test_admin/admin/', self.joepublic_login)
|
|
self.assertEqual(login.status_code, 200)
|
|
self.assertContains(login, "Please enter a correct username and password.")
|
|
|
|
# Requests without username should not return 500 errors.
|
|
request = self.client.get('/test_admin/admin/')
|
|
self.assertEqual(request.status_code, 200)
|
|
login = self.client.post('/test_admin/admin/', self.no_username_login)
|
|
self.assertEqual(login.status_code, 200)
|
|
form = login.context[0].get('form')
|
|
self.assertEqual(form.errors['username'][0], 'This field is required.')
|
|
|
|
def testLoginSuccessfullyRedirectsToOriginalUrl(self):
|
|
request = self.client.get('/test_admin/admin/')
|
|
self.assertEqual(request.status_code, 200)
|
|
query_string = 'the-answer=42'
|
|
redirect_url = '/test_admin/admin/?%s' % query_string
|
|
new_next = {REDIRECT_FIELD_NAME: redirect_url}
|
|
login = self.client.post('/test_admin/admin/', dict(self.super_login, **new_next), QUERY_STRING=query_string)
|
|
self.assertRedirects(login, redirect_url)
|
|
|
|
def testAddView(self):
|
|
"""Test add view restricts access and actually adds items."""
|
|
|
|
add_dict = {'title' : 'Døm ikke',
|
|
'content': '<p>great article</p>',
|
|
'date_0': '2008-03-18', 'date_1': '10:54:39',
|
|
'section': 1}
|
|
|
|
# Change User should not have access to add articles
|
|
self.client.get('/test_admin/admin/')
|
|
self.client.post('/test_admin/admin/', self.changeuser_login)
|
|
# make sure the view removes test cookie
|
|
self.assertEqual(self.client.session.test_cookie_worked(), False)
|
|
request = self.client.get('/test_admin/admin/admin_views/article/add/')
|
|
self.assertEqual(request.status_code, 403)
|
|
# Try POST just to make sure
|
|
post = self.client.post('/test_admin/admin/admin_views/article/add/', add_dict)
|
|
self.assertEqual(post.status_code, 403)
|
|
self.assertEqual(Article.objects.all().count(), 3)
|
|
self.client.get('/test_admin/admin/logout/')
|
|
|
|
# Add user may login and POST to add view, then redirect to admin root
|
|
self.client.get('/test_admin/admin/')
|
|
self.client.post('/test_admin/admin/', self.adduser_login)
|
|
addpage = self.client.get('/test_admin/admin/admin_views/article/add/')
|
|
self.assertEqual(addpage.status_code, 200)
|
|
change_list_link = '<a href="../">Articles</a> ›'
|
|
self.assertFalse(change_list_link in addpage.content,
|
|
'User restricted to add permission is given link to change list view in breadcrumbs.')
|
|
post = self.client.post('/test_admin/admin/admin_views/article/add/', add_dict)
|
|
self.assertRedirects(post, '/test_admin/admin/')
|
|
self.assertEqual(Article.objects.all().count(), 4)
|
|
self.assertEqual(len(mail.outbox), 1)
|
|
self.assertEqual(mail.outbox[0].subject, 'Greetings from a created object')
|
|
self.client.get('/test_admin/admin/logout/')
|
|
|
|
# Super can add too, but is redirected to the change list view
|
|
self.client.get('/test_admin/admin/')
|
|
self.client.post('/test_admin/admin/', self.super_login)
|
|
addpage = self.client.get('/test_admin/admin/admin_views/article/add/')
|
|
self.assertEqual(addpage.status_code, 200)
|
|
self.assertFalse(change_list_link not in addpage.content,
|
|
'Unrestricted user is not given link to change list view in breadcrumbs.')
|
|
post = self.client.post('/test_admin/admin/admin_views/article/add/', add_dict)
|
|
self.assertRedirects(post, '/test_admin/admin/admin_views/article/')
|
|
self.assertEqual(Article.objects.all().count(), 5)
|
|
self.client.get('/test_admin/admin/logout/')
|
|
|
|
# 8509 - if a normal user is already logged in, it is possible
|
|
# to change user into the superuser without error
|
|
login = self.client.login(username='joepublic', password='secret')
|
|
# Check and make sure that if user expires, data still persists
|
|
self.client.get('/test_admin/admin/')
|
|
self.client.post('/test_admin/admin/', self.super_login)
|
|
# make sure the view removes test cookie
|
|
self.assertEqual(self.client.session.test_cookie_worked(), False)
|
|
|
|
def testChangeView(self):
|
|
"""Change view should restrict access and allow users to edit items."""
|
|
|
|
change_dict = {'title' : 'Ikke fordømt',
|
|
'content': '<p>edited article</p>',
|
|
'date_0': '2008-03-18', 'date_1': '10:54:39',
|
|
'section': 1}
|
|
|
|
# add user shoud not be able to view the list of article or change any of them
|
|
self.client.get('/test_admin/admin/')
|
|
self.client.post('/test_admin/admin/', self.adduser_login)
|
|
request = self.client.get('/test_admin/admin/admin_views/article/')
|
|
self.assertEqual(request.status_code, 403)
|
|
request = self.client.get('/test_admin/admin/admin_views/article/1/')
|
|
self.assertEqual(request.status_code, 403)
|
|
post = self.client.post('/test_admin/admin/admin_views/article/1/', change_dict)
|
|
self.assertEqual(post.status_code, 403)
|
|
self.client.get('/test_admin/admin/logout/')
|
|
|
|
# change user can view all items and edit them
|
|
self.client.get('/test_admin/admin/')
|
|
self.client.post('/test_admin/admin/', self.changeuser_login)
|
|
request = self.client.get('/test_admin/admin/admin_views/article/')
|
|
self.assertEqual(request.status_code, 200)
|
|
request = self.client.get('/test_admin/admin/admin_views/article/1/')
|
|
self.assertEqual(request.status_code, 200)
|
|
post = self.client.post('/test_admin/admin/admin_views/article/1/', change_dict)
|
|
self.assertRedirects(post, '/test_admin/admin/admin_views/article/')
|
|
self.assertEqual(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)
|
|
self.assertEqual(request.status_code, 200)
|
|
self.assertTrue('Please correct the error below.' in post.content,
|
|
'Singular error message not found in response to post with one error.')
|
|
change_dict['content'] = ''
|
|
post = self.client.post('/test_admin/admin/admin_views/article/1/', change_dict)
|
|
self.assertEqual(request.status_code, 200)
|
|
self.assertTrue('Please correct the errors below.' in post.content,
|
|
'Plural error message not found in response to post with multiple errors.')
|
|
self.client.get('/test_admin/admin/logout/')
|
|
|
|
# Test redirection when using row-level change permissions. Refs #11513.
|
|
RowLevelChangePermissionModel.objects.create(id=1, name="odd id")
|
|
RowLevelChangePermissionModel.objects.create(id=2, name="even id")
|
|
for login_dict in [self.super_login, self.changeuser_login, self.adduser_login, self.deleteuser_login]:
|
|
self.client.post('/test_admin/admin/', login_dict)
|
|
request = self.client.get('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/1/')
|
|
self.assertEqual(request.status_code, 403)
|
|
request = self.client.post('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/1/', {'name': 'changed'})
|
|
self.assertEqual(RowLevelChangePermissionModel.objects.get(id=1).name, 'odd id')
|
|
self.assertEqual(request.status_code, 403)
|
|
request = self.client.get('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/2/')
|
|
self.assertEqual(request.status_code, 200)
|
|
request = self.client.post('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/2/', {'name': 'changed'})
|
|
self.assertEqual(RowLevelChangePermissionModel.objects.get(id=2).name, 'changed')
|
|
self.assertRedirects(request, '/test_admin/admin/')
|
|
self.client.get('/test_admin/admin/logout/')
|
|
for login_dict in [self.joepublic_login, self.no_username_login]:
|
|
self.client.post('/test_admin/admin/', login_dict)
|
|
request = self.client.get('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/1/')
|
|
self.assertEqual(request.status_code, 200)
|
|
self.assertContains(request, 'login-form')
|
|
request = self.client.post('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/1/', {'name': 'changed'})
|
|
self.assertEqual(RowLevelChangePermissionModel.objects.get(id=1).name, 'odd id')
|
|
self.assertEqual(request.status_code, 200)
|
|
self.assertContains(request, 'login-form')
|
|
request = self.client.get('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/2/')
|
|
self.assertEqual(request.status_code, 200)
|
|
self.assertContains(request, 'login-form')
|
|
request = self.client.post('/test_admin/admin/admin_views/rowlevelchangepermissionmodel/2/', {'name': 'changed again'})
|
|
self.assertEqual(RowLevelChangePermissionModel.objects.get(id=2).name, 'changed')
|
|
self.assertEqual(request.status_code, 200)
|
|
self.assertContains(request, 'login-form')
|
|
self.client.get('/test_admin/admin/logout/')
|
|
|
|
def testConditionallyShowAddSectionLink(self):
|
|
"""
|
|
The foreign key widget should only show the "add related" button if the
|
|
user has permission to add that related item.
|
|
"""
|
|
# 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/')
|
|
self.client.post('/test_admin/admin/', self.adduser_login)
|
|
# The add 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
|
|
# section" link.
|
|
add_user = User.objects.get(username='adduser')
|
|
perm = get_perm(Section, Section._meta.get_add_permission())
|
|
add_user.user_permissions.add(perm)
|
|
response = self.client.get(url)
|
|
self.assertContains(response, add_link_text)
|
|
|
|
def testCustomModelAdminTemplates(self):
|
|
self.client.get('/test_admin/admin/')
|
|
self.client.post('/test_admin/admin/', self.super_login)
|
|
|
|
# Test custom change list template with custom extra context
|
|
request = self.client.get('/test_admin/admin/admin_views/customarticle/')
|
|
self.assertEqual(request.status_code, 200)
|
|
self.assertTrue("var hello = 'Hello!';" in request.content)
|
|
self.assertTemplateUsed(request, 'custom_admin/change_list.html')
|
|
|
|
# Test custom add form template
|
|
request = self.client.get('/test_admin/admin/admin_views/customarticle/add/')
|
|
self.assertTemplateUsed(request, 'custom_admin/add_form.html')
|
|
|
|
# Add an article so we can test delete, change, and history views
|
|
post = self.client.post('/test_admin/admin/admin_views/customarticle/add/', {
|
|
'content': '<p>great article</p>',
|
|
'date_0': '2008-03-18',
|
|
'date_1': '10:54:39'
|
|
})
|
|
self.assertRedirects(post, '/test_admin/admin/admin_views/customarticle/')
|
|
self.assertEqual(CustomArticle.objects.all().count(), 1)
|
|
article_pk = CustomArticle.objects.all()[0].pk
|
|
|
|
# Test custom delete, change, and object history templates
|
|
# Test custom change form template
|
|
request = self.client.get('/test_admin/admin/admin_views/customarticle/%d/' % article_pk)
|
|
self.assertTemplateUsed(request, 'custom_admin/change_form.html')
|
|
request = self.client.get('/test_admin/admin/admin_views/customarticle/%d/delete/' % article_pk)
|
|
self.assertTemplateUsed(request, 'custom_admin/delete_confirmation.html')
|
|
request = self.client.post('/test_admin/admin/admin_views/customarticle/', data={
|
|
'index': 0,
|
|
'action': ['delete_selected'],
|
|
'_selected_action': ['1'],
|
|
})
|
|
self.assertTemplateUsed(request, 'custom_admin/delete_selected_confirmation.html')
|
|
request = self.client.get('/test_admin/admin/admin_views/customarticle/%d/history/' % article_pk)
|
|
self.assertTemplateUsed(request, 'custom_admin/object_history.html')
|
|
|
|
self.client.get('/test_admin/admin/logout/')
|
|
|
|
def testDeleteView(self):
|
|
"""Delete view should restrict access and actually delete items."""
|
|
|
|
delete_dict = {'post': 'yes'}
|
|
|
|
# add user shoud not be able to delete articles
|
|
self.client.get('/test_admin/admin/')
|
|
self.client.post('/test_admin/admin/', self.adduser_login)
|
|
request = self.client.get('/test_admin/admin/admin_views/article/1/delete/')
|
|
self.assertEqual(request.status_code, 403)
|
|
post = self.client.post('/test_admin/admin/admin_views/article/1/delete/', delete_dict)
|
|
self.assertEqual(post.status_code, 403)
|
|
self.assertEqual(Article.objects.all().count(), 3)
|
|
self.client.get('/test_admin/admin/logout/')
|
|
|
|
# Delete user can delete
|
|
self.client.get('/test_admin/admin/')
|
|
self.client.post('/test_admin/admin/', self.deleteuser_login)
|
|
response = self.client.get('/test_admin/admin/admin_views/section/1/delete/')
|
|
# test response contains link to related Article
|
|
self.assertContains(response, "admin_views/article/1/")
|
|
|
|
response = self.client.get('/test_admin/admin/admin_views/article/1/delete/')
|
|
self.assertEqual(response.status_code, 200)
|
|
post = self.client.post('/test_admin/admin/admin_views/article/1/delete/', delete_dict)
|
|
self.assertRedirects(post, '/test_admin/admin/')
|
|
self.assertEqual(Article.objects.all().count(), 2)
|
|
self.assertEqual(len(mail.outbox), 1)
|
|
self.assertEqual(mail.outbox[0].subject, 'Greetings from a deleted object')
|
|
article_ct = ContentType.objects.get_for_model(Article)
|
|
logged = LogEntry.objects.get(content_type=article_ct, action_flag=DELETION)
|
|
self.assertEqual(logged.object_id, u'1')
|
|
self.client.get('/test_admin/admin/logout/')
|
|
|
|
def testDisabledPermissionsWhenLoggedIn(self):
|
|
self.client.login(username='super', password='secret')
|
|
superuser = User.objects.get(username='super')
|
|
superuser.is_active = False
|
|
superuser.save()
|
|
|
|
response = self.client.get('/test_admin/admin/')
|
|
self.assertContains(response, 'id="login-form"')
|
|
self.assertNotContains(response, 'Log out')
|
|
|
|
response = self.client.get('/test_admin/admin/secure-view/')
|
|
self.assertContains(response, 'id="login-form"')
|
|
|
|
|
|
class AdminViewDeletedObjectsTest(TestCase):
|
|
fixtures = ['admin-views-users.xml', 'deleted-objects.xml']
|
|
|
|
def setUp(self):
|
|
self.client.login(username='super', password='secret')
|
|
|
|
def tearDown(self):
|
|
self.client.logout()
|
|
|
|
def test_nesting(self):
|
|
"""
|
|
Objects should be nested to display the relationships that
|
|
cause them to be scheduled for deletion.
|
|
"""
|
|
pattern = re.compile(r"""<li>Plot: <a href=".+/admin_views/plot/1/">World Domination</a>\s*<ul>\s*<li>Plot details: <a href=".+/admin_views/plotdetails/1/">almost finished</a>""")
|
|
response = self.client.get('/test_admin/admin/admin_views/villain/%s/delete/' % quote(1))
|
|
self.assertTrue(pattern.search(response.content))
|
|
|
|
def test_cyclic(self):
|
|
"""
|
|
Cyclic relationships should still cause each object to only be
|
|
listed once.
|
|
|
|
"""
|
|
one = """<li>Cyclic one: <a href="/test_admin/admin/admin_views/cyclicone/1/">I am recursive</a>"""
|
|
two = """<li>Cyclic two: <a href="/test_admin/admin/admin_views/cyclictwo/1/">I am recursive too</a>"""
|
|
response = self.client.get('/test_admin/admin/admin_views/cyclicone/%s/delete/' % quote(1))
|
|
|
|
self.assertContains(response, one, 1)
|
|
self.assertContains(response, two, 1)
|
|
|
|
def test_perms_needed(self):
|
|
self.client.logout()
|
|
delete_user = User.objects.get(username='deleteuser')
|
|
delete_user.user_permissions.add(get_perm(Plot,
|
|
Plot._meta.get_delete_permission()))
|
|
|
|
self.assertTrue(self.client.login(username='deleteuser',
|
|
password='secret'))
|
|
|
|
response = self.client.get('/test_admin/admin/admin_views/plot/%s/delete/' % quote(1))
|
|
self.assertContains(response, "your account doesn't have permission to delete the following types of objects")
|
|
self.assertContains(response, "<li>plot details</li>")
|
|
|
|
def test_protected(self):
|
|
q = Question.objects.create(question="Why?")
|
|
a1 = Answer.objects.create(question=q, answer="Because.")
|
|
a2 = Answer.objects.create(question=q, answer="Yes.")
|
|
|
|
response = self.client.get("/test_admin/admin/admin_views/question/%s/delete/" % quote(q.pk))
|
|
self.assertContains(response, "would require deleting the following protected related objects")
|
|
self.assertContains(response, '<li>Answer: <a href="/test_admin/admin/admin_views/answer/%s/">Because.</a></li>' % a1.pk)
|
|
self.assertContains(response, '<li>Answer: <a href="/test_admin/admin/admin_views/answer/%s/">Yes.</a></li>' % a2.pk)
|
|
|
|
def test_not_registered(self):
|
|
should_contain = """<li>Secret hideout: underground bunker"""
|
|
response = self.client.get('/test_admin/admin/admin_views/villain/%s/delete/' % quote(1))
|
|
self.assertContains(response, should_contain, 1)
|
|
|
|
def test_multiple_fkeys_to_same_model(self):
|
|
"""
|
|
If a deleted object has two relationships from another model,
|
|
both of those should be followed in looking for related
|
|
objects to delete.
|
|
|
|
"""
|
|
should_contain = """<li>Plot: <a href="/test_admin/admin/admin_views/plot/1/">World Domination</a>"""
|
|
response = self.client.get('/test_admin/admin/admin_views/villain/%s/delete/' % quote(1))
|
|
self.assertContains(response, should_contain)
|
|
response = self.client.get('/test_admin/admin/admin_views/villain/%s/delete/' % quote(2))
|
|
self.assertContains(response, should_contain)
|
|
|
|
def test_multiple_fkeys_to_same_instance(self):
|
|
"""
|
|
If a deleted object has two relationships pointing to it from
|
|
another object, the other object should still only be listed
|
|
once.
|
|
|
|
"""
|
|
should_contain = """<li>Plot: <a href="/test_admin/admin/admin_views/plot/2/">World Peace</a></li>"""
|
|
response = self.client.get('/test_admin/admin/admin_views/villain/%s/delete/' % quote(2))
|
|
self.assertContains(response, should_contain, 1)
|
|
|
|
def test_inheritance(self):
|
|
"""
|
|
In the case of an inherited model, if either the child or
|
|
parent-model instance is deleted, both instances are listed
|
|
for deletion, as well as any relationships they have.
|
|
|
|
"""
|
|
should_contain = [
|
|
"""<li>Villain: <a href="/test_admin/admin/admin_views/villain/3/">Bob</a>""",
|
|
"""<li>Super villain: <a href="/test_admin/admin/admin_views/supervillain/3/">Bob</a>""",
|
|
"""<li>Secret hideout: floating castle""",
|
|
"""<li>Super secret hideout: super floating castle!"""
|
|
]
|
|
response = self.client.get('/test_admin/admin/admin_views/villain/%s/delete/' % quote(3))
|
|
for should in should_contain:
|
|
self.assertContains(response, should, 1)
|
|
response = self.client.get('/test_admin/admin/admin_views/supervillain/%s/delete/' % quote(3))
|
|
for should in should_contain:
|
|
self.assertContains(response, should, 1)
|
|
|
|
def test_generic_relations(self):
|
|
"""
|
|
If a deleted object has GenericForeignKeys pointing to it,
|
|
those objects should be listed for deletion.
|
|
|
|
"""
|
|
plot = Plot.objects.get(pk=3)
|
|
tag = FunkyTag.objects.create(content_object=plot, name='hott')
|
|
should_contain = """<li>Funky tag: hott"""
|
|
response = self.client.get('/test_admin/admin/admin_views/plot/%s/delete/' % quote(3))
|
|
self.assertContains(response, should_contain)
|
|
|
|
class AdminViewStringPrimaryKeyTest(TestCase):
|
|
fixtures = ['admin-views-users.xml', 'string-primary-key.xml']
|
|
|
|
def __init__(self, *args):
|
|
super(AdminViewStringPrimaryKeyTest, self).__init__(*args)
|
|
self.pk = """abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 -_.!~*'() ;/?:@&=+$, <>#%" {}|\^[]`"""
|
|
|
|
def setUp(self):
|
|
self.client.login(username='super', password='secret')
|
|
content_type_pk = ContentType.objects.get_for_model(ModelWithStringPrimaryKey).pk
|
|
LogEntry.objects.log_action(100, content_type_pk, self.pk, self.pk, 2, change_message='')
|
|
|
|
def tearDown(self):
|
|
self.client.logout()
|
|
|
|
def test_get_history_view(self):
|
|
"Retrieving the history for the object using urlencoded form of primary key should work"
|
|
response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/history/' % quote(self.pk))
|
|
self.assertContains(response, escape(self.pk))
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
def test_get_change_view(self):
|
|
"Retrieving the object using urlencoded form of primary key should work"
|
|
response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/' % quote(self.pk))
|
|
self.assertContains(response, escape(self.pk))
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
def test_changelist_to_changeform_link(self):
|
|
"The link from the changelist referring to the changeform of the object should be quoted"
|
|
response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/')
|
|
should_contain = """<th><a href="%s/">%s</a></th></tr>""" % (quote(self.pk), escape(self.pk))
|
|
self.assertContains(response, should_contain)
|
|
|
|
def test_recentactions_link(self):
|
|
"The link from the recent actions list referring to the changeform of the object should be quoted"
|
|
response = self.client.get('/test_admin/admin/')
|
|
should_contain = """<a href="admin_views/modelwithstringprimarykey/%s/">%s</a>""" % (quote(self.pk), escape(self.pk))
|
|
self.assertContains(response, should_contain)
|
|
|
|
def test_recentactions_without_content_type(self):
|
|
"If a LogEntry is missing content_type it will not display it in span tag under the hyperlink."
|
|
response = self.client.get('/test_admin/admin/')
|
|
should_contain = """<a href="admin_views/modelwithstringprimarykey/%s/">%s</a>""" % (quote(self.pk), escape(self.pk))
|
|
self.assertContains(response, should_contain)
|
|
should_contain = "Model with string primary key" # capitalized in Recent Actions
|
|
self.assertContains(response, should_contain)
|
|
logentry = LogEntry.objects.get(content_type__name__iexact=should_contain)
|
|
# http://code.djangoproject.com/ticket/10275
|
|
# if the log entry doesn't have a content type it should still be
|
|
# possible to view the Recent Actions part
|
|
logentry.content_type = None
|
|
logentry.save()
|
|
|
|
counted_presence_before = response.content.count(should_contain)
|
|
response = self.client.get('/test_admin/admin/')
|
|
counted_presence_after = response.content.count(should_contain)
|
|
self.assertEqual(counted_presence_before - 1,
|
|
counted_presence_after)
|
|
|
|
def test_deleteconfirmation_link(self):
|
|
"The link from the delete confirmation page referring back to the changeform of the object should be quoted"
|
|
response = self.client.get('/test_admin/admin/admin_views/modelwithstringprimarykey/%s/delete/' % quote(self.pk))
|
|
# this URL now comes through reverse(), thus iri_to_uri encoding
|
|
should_contain = """/%s/">%s</a>""" % (iri_to_uri(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")
|
|
add_model.save()
|
|
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")
|
|
delete_model.save()
|
|
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")
|
|
history_model.save()
|
|
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 SecureViewTests(TestCase):
|
|
fixtures = ['admin-views-users.xml']
|
|
|
|
def setUp(self):
|
|
# login POST dicts
|
|
self.super_login = {
|
|
LOGIN_FORM_KEY: 1,
|
|
REDIRECT_FIELD_NAME: '/test_admin/admin/secure-view/',
|
|
'username': 'super',
|
|
'password': 'secret',
|
|
}
|
|
self.super_email_login = {
|
|
LOGIN_FORM_KEY: 1,
|
|
REDIRECT_FIELD_NAME: '/test_admin/admin/secure-view/',
|
|
'username': 'super@example.com',
|
|
'password': 'secret',
|
|
}
|
|
self.super_email_bad_login = {
|
|
LOGIN_FORM_KEY: 1,
|
|
REDIRECT_FIELD_NAME: '/test_admin/admin/secure-view/',
|
|
'username': 'super@example.com',
|
|
'password': 'notsecret',
|
|
}
|
|
self.adduser_login = {
|
|
LOGIN_FORM_KEY: 1,
|
|
REDIRECT_FIELD_NAME: '/test_admin/admin/secure-view/',
|
|
'username': 'adduser',
|
|
'password': 'secret',
|
|
}
|
|
self.changeuser_login = {
|
|
LOGIN_FORM_KEY: 1,
|
|
REDIRECT_FIELD_NAME: '/test_admin/admin/secure-view/',
|
|
'username': 'changeuser',
|
|
'password': 'secret',
|
|
}
|
|
self.deleteuser_login = {
|
|
LOGIN_FORM_KEY: 1,
|
|
REDIRECT_FIELD_NAME: '/test_admin/admin/secure-view/',
|
|
'username': 'deleteuser',
|
|
'password': 'secret',
|
|
}
|
|
self.joepublic_login = {
|
|
LOGIN_FORM_KEY: 1,
|
|
REDIRECT_FIELD_NAME: '/test_admin/admin/secure-view/',
|
|
'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.assertEqual(request.status_code, 200)
|
|
query_string = 'the-answer=42'
|
|
redirect_url = '/test_admin/admin/secure-view/?%s' % query_string
|
|
new_next = {REDIRECT_FIELD_NAME: redirect_url}
|
|
login = self.client.post('/test_admin/admin/secure-view/', dict(self.super_login, **new_next), QUERY_STRING=query_string)
|
|
self.assertRedirects(login, redirect_url)
|
|
|
|
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
|
|
a 200 status code.
|
|
"""
|
|
# Super User
|
|
request = self.client.get('/test_admin/admin/secure-view/')
|
|
self.assertEqual(request.status_code, 200)
|
|
login = self.client.post('/test_admin/admin/secure-view/', self.super_login)
|
|
self.assertRedirects(login, '/test_admin/admin/secure-view/')
|
|
self.assertFalse(login.context)
|
|
self.client.get('/test_admin/admin/logout/')
|
|
# make sure the view removes test cookie
|
|
self.assertEqual(self.client.session.test_cookie_worked(), False)
|
|
|
|
# Test if user enters email address
|
|
request = self.client.get('/test_admin/admin/secure-view/')
|
|
self.assertEqual(request.status_code, 200)
|
|
login = self.client.post('/test_admin/admin/secure-view/', self.super_email_login)
|
|
self.assertContains(login, "Your e-mail address is not your username")
|
|
# only correct passwords get a username hint
|
|
login = self.client.post('/test_admin/admin/secure-view/', self.super_email_bad_login)
|
|
self.assertContains(login, "Please enter a correct username and password.")
|
|
new_user = User(username='jondoe', password='secret', email='super@example.com')
|
|
new_user.save()
|
|
# check to ensure if there are multiple email addresses a user doesn't get a 500
|
|
login = self.client.post('/test_admin/admin/secure-view/', self.super_email_login)
|
|
self.assertContains(login, "Please enter a correct username and password.")
|
|
|
|
# Add User
|
|
request = self.client.get('/test_admin/admin/secure-view/')
|
|
self.assertEqual(request.status_code, 200)
|
|
login = self.client.post('/test_admin/admin/secure-view/', self.adduser_login)
|
|
self.assertRedirects(login, '/test_admin/admin/secure-view/')
|
|
self.assertFalse(login.context)
|
|
self.client.get('/test_admin/admin/logout/')
|
|
|
|
# Change User
|
|
request = self.client.get('/test_admin/admin/secure-view/')
|
|
self.assertEqual(request.status_code, 200)
|
|
login = self.client.post('/test_admin/admin/secure-view/', self.changeuser_login)
|
|
self.assertRedirects(login, '/test_admin/admin/secure-view/')
|
|
self.assertFalse(login.context)
|
|
self.client.get('/test_admin/admin/logout/')
|
|
|
|
# Delete User
|
|
request = self.client.get('/test_admin/admin/secure-view/')
|
|
self.assertEqual(request.status_code, 200)
|
|
login = self.client.post('/test_admin/admin/secure-view/', self.deleteuser_login)
|
|
self.assertRedirects(login, '/test_admin/admin/secure-view/')
|
|
self.assertFalse(login.context)
|
|
self.client.get('/test_admin/admin/logout/')
|
|
|
|
# Regular User should not be able to login.
|
|
request = self.client.get('/test_admin/admin/secure-view/')
|
|
self.assertEqual(request.status_code, 200)
|
|
login = self.client.post('/test_admin/admin/secure-view/', self.joepublic_login)
|
|
self.assertEqual(login.status_code, 200)
|
|
# Login.context is a list of context dicts we just need to check the first one.
|
|
self.assertContains(login, "Please enter a correct username and password.")
|
|
|
|
# 8509 - if a normal user is already logged in, it is possible
|
|
# to change user into the superuser without error
|
|
login = self.client.login(username='joepublic', password='secret')
|
|
# Check and make sure that if user expires, data still persists
|
|
self.client.get('/test_admin/admin/secure-view/')
|
|
self.client.post('/test_admin/admin/secure-view/', self.super_login)
|
|
# make sure the view removes test cookie
|
|
self.assertEqual(self.client.session.test_cookie_worked(), False)
|
|
|
|
def test_shortcut_view_only_available_to_staff(self):
|
|
"""
|
|
Only admin users should be able to use the admin shortcut view.
|
|
"""
|
|
user_ctype = ContentType.objects.get_for_model(User)
|
|
user = User.objects.get(username='super')
|
|
shortcut_url = "/test_admin/admin/r/%s/%s/" % (user_ctype.pk, user.pk)
|
|
|
|
# Not logged in: we should see the login page.
|
|
response = self.client.get(shortcut_url, follow=False)
|
|
self.assertTemplateUsed(response, 'admin/login.html')
|
|
|
|
# Logged in? Redirect.
|
|
self.client.login(username='super', password='secret')
|
|
response = self.client.get(shortcut_url, follow=False)
|
|
# Can't use self.assertRedirects() because User.get_absolute_url() is silly.
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(response['Location'], 'http://example.com/users/super/')
|
|
|
|
class AdminViewUnicodeTest(TestCase):
|
|
fixtures = ['admin-views-unicode.xml']
|
|
|
|
def setUp(self):
|
|
self.client.login(username='super', password='secret')
|
|
|
|
def tearDown(self):
|
|
self.client.logout()
|
|
|
|
def testUnicodeEdit(self):
|
|
"""
|
|
A test to ensure that POST on edit_view handles non-ascii characters.
|
|
"""
|
|
post_data = {
|
|
"name": u"Test lærdommer",
|
|
# inline data
|
|
"chapter_set-TOTAL_FORMS": u"6",
|
|
"chapter_set-INITIAL_FORMS": u"3",
|
|
"chapter_set-MAX_NUM_FORMS": u"0",
|
|
"chapter_set-0-id": u"1",
|
|
"chapter_set-0-title": u"Norske bostaver æøå skaper problemer",
|
|
"chapter_set-0-content": u"<p>Svært frustrerende med UnicodeDecodeError</p>",
|
|
"chapter_set-1-id": u"2",
|
|
"chapter_set-1-title": u"Kjærlighet.",
|
|
"chapter_set-1-content": u"<p>La kjærligheten til de lidende seire.</p>",
|
|
"chapter_set-2-id": u"3",
|
|
"chapter_set-2-title": u"Need a title.",
|
|
"chapter_set-2-content": u"<p>Newest content</p>",
|
|
"chapter_set-3-id": u"",
|
|
"chapter_set-3-title": u"",
|
|
"chapter_set-3-content": u"",
|
|
"chapter_set-4-id": u"",
|
|
"chapter_set-4-title": u"",
|
|
"chapter_set-4-content": u"",
|
|
"chapter_set-5-id": u"",
|
|
"chapter_set-5-title": u"",
|
|
"chapter_set-5-content": u"",
|
|
}
|
|
|
|
response = self.client.post('/test_admin/admin/admin_views/book/1/', post_data)
|
|
self.assertEqual(response.status_code, 302) # redirect somewhere
|
|
|
|
def testUnicodeDelete(self):
|
|
"""
|
|
Ensure that the delete_view handles non-ascii characters
|
|
"""
|
|
delete_dict = {'post': 'yes'}
|
|
response = self.client.get('/test_admin/admin/admin_views/book/1/delete/')
|
|
self.assertEqual(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 AdminViewListEditable(TestCase):
|
|
fixtures = ['admin-views-users.xml', 'admin-views-person.xml']
|
|
|
|
def setUp(self):
|
|
self.client.login(username='super', password='secret')
|
|
|
|
def tearDown(self):
|
|
self.client.logout()
|
|
|
|
def test_inheritance(self):
|
|
Podcast.objects.create(name="This Week in Django",
|
|
release_date=datetime.date.today())
|
|
response = self.client.get('/test_admin/admin/admin_views/podcast/')
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
def test_inheritance_2(self):
|
|
Vodcast.objects.create(name="This Week in Django", released=True)
|
|
response = self.client.get('/test_admin/admin/admin_views/vodcast/')
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
def test_custom_pk(self):
|
|
Language.objects.create(iso='en', name='English', english_name='English')
|
|
response = self.client.get('/test_admin/admin/admin_views/language/')
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
def test_changelist_input_html(self):
|
|
response = self.client.get('/test_admin/admin/admin_views/person/')
|
|
# 2 inputs per object(the field and the hidden id field) = 6
|
|
# 3 management hidden fields = 3
|
|
# 4 action inputs (3 regular checkboxes, 1 checkbox to select all)
|
|
# main form submit button = 1
|
|
# search field and search submit button = 2
|
|
# CSRF field = 1
|
|
# field to track 'select all' across paginated views = 1
|
|
# 6 + 3 + 4 + 1 + 2 + 1 + 1 = 18 inputs
|
|
self.assertEqual(response.content.count("<input"), 18)
|
|
# 1 select per object = 3 selects
|
|
self.assertEqual(response.content.count("<select"), 4)
|
|
|
|
def test_post_messages(self):
|
|
# Ticket 12707: Saving inline editable should not show admin
|
|
# action warnings
|
|
data = {
|
|
"form-TOTAL_FORMS": "3",
|
|
"form-INITIAL_FORMS": "3",
|
|
"form-MAX_NUM_FORMS": "0",
|
|
|
|
"form-0-gender": "1",
|
|
"form-0-id": "1",
|
|
|
|
"form-1-gender": "2",
|
|
"form-1-id": "2",
|
|
|
|
"form-2-alive": "checked",
|
|
"form-2-gender": "1",
|
|
"form-2-id": "3",
|
|
|
|
"_save": "Save",
|
|
}
|
|
response = self.client.post('/test_admin/admin/admin_views/person/',
|
|
data, follow=True)
|
|
self.assertEqual(len(response.context['messages']), 1)
|
|
|
|
def test_post_submission(self):
|
|
data = {
|
|
"form-TOTAL_FORMS": "3",
|
|
"form-INITIAL_FORMS": "3",
|
|
"form-MAX_NUM_FORMS": "0",
|
|
|
|
"form-0-gender": "1",
|
|
"form-0-id": "1",
|
|
|
|
"form-1-gender": "2",
|
|
"form-1-id": "2",
|
|
|
|
"form-2-alive": "checked",
|
|
"form-2-gender": "1",
|
|
"form-2-id": "3",
|
|
|
|
"_save": "Save",
|
|
}
|
|
self.client.post('/test_admin/admin/admin_views/person/', data)
|
|
|
|
self.assertEqual(Person.objects.get(name="John Mauchly").alive, False)
|
|
self.assertEqual(Person.objects.get(name="Grace Hopper").gender, 2)
|
|
|
|
# test a filtered page
|
|
data = {
|
|
"form-TOTAL_FORMS": "2",
|
|
"form-INITIAL_FORMS": "2",
|
|
"form-MAX_NUM_FORMS": "0",
|
|
|
|
"form-0-id": "1",
|
|
"form-0-gender": "1",
|
|
"form-0-alive": "checked",
|
|
|
|
"form-1-id": "3",
|
|
"form-1-gender": "1",
|
|
"form-1-alive": "checked",
|
|
|
|
"_save": "Save",
|
|
}
|
|
self.client.post('/test_admin/admin/admin_views/person/?gender__exact=1', data)
|
|
|
|
self.assertEqual(Person.objects.get(name="John Mauchly").alive, True)
|
|
|
|
# test a searched page
|
|
data = {
|
|
"form-TOTAL_FORMS": "1",
|
|
"form-INITIAL_FORMS": "1",
|
|
"form-MAX_NUM_FORMS": "0",
|
|
|
|
"form-0-id": "1",
|
|
"form-0-gender": "1",
|
|
|
|
"_save": "Save",
|
|
}
|
|
self.client.post('/test_admin/admin/admin_views/person/?q=john', data)
|
|
|
|
self.assertEqual(Person.objects.get(name="John Mauchly").alive, False)
|
|
|
|
def test_non_field_errors(self):
|
|
''' Ensure that non field errors are displayed for each of the
|
|
forms in the changelist's formset. Refs #13126.
|
|
'''
|
|
fd1 = FoodDelivery.objects.create(reference='123', driver='bill', restaurant='thai')
|
|
fd2 = FoodDelivery.objects.create(reference='456', driver='bill', restaurant='india')
|
|
fd3 = FoodDelivery.objects.create(reference='789', driver='bill', restaurant='pizza')
|
|
|
|
data = {
|
|
"form-TOTAL_FORMS": "3",
|
|
"form-INITIAL_FORMS": "3",
|
|
"form-MAX_NUM_FORMS": "0",
|
|
|
|
"form-0-id": str(fd1.id),
|
|
"form-0-reference": "123",
|
|
"form-0-driver": "bill",
|
|
"form-0-restaurant": "thai",
|
|
|
|
# Same data as above: Forbidden because of unique_together!
|
|
"form-1-id": str(fd2.id),
|
|
"form-1-reference": "456",
|
|
"form-1-driver": "bill",
|
|
"form-1-restaurant": "thai",
|
|
|
|
"form-2-id": str(fd3.id),
|
|
"form-2-reference": "789",
|
|
"form-2-driver": "bill",
|
|
"form-2-restaurant": "pizza",
|
|
|
|
"_save": "Save",
|
|
}
|
|
response = self.client.post('/test_admin/admin/admin_views/fooddelivery/', data)
|
|
self.assertContains(response, '<tr><td colspan="4"><ul class="errorlist"><li>Food delivery with this Driver and Restaurant already exists.</li></ul></td></tr>', 1)
|
|
|
|
data = {
|
|
"form-TOTAL_FORMS": "3",
|
|
"form-INITIAL_FORMS": "3",
|
|
"form-MAX_NUM_FORMS": "0",
|
|
|
|
"form-0-id": str(fd1.id),
|
|
"form-0-reference": "123",
|
|
"form-0-driver": "bill",
|
|
"form-0-restaurant": "thai",
|
|
|
|
# Same data as above: Forbidden because of unique_together!
|
|
"form-1-id": str(fd2.id),
|
|
"form-1-reference": "456",
|
|
"form-1-driver": "bill",
|
|
"form-1-restaurant": "thai",
|
|
|
|
# Same data also.
|
|
"form-2-id": str(fd3.id),
|
|
"form-2-reference": "789",
|
|
"form-2-driver": "bill",
|
|
"form-2-restaurant": "thai",
|
|
|
|
"_save": "Save",
|
|
}
|
|
response = self.client.post('/test_admin/admin/admin_views/fooddelivery/', data)
|
|
self.assertContains(response, '<tr><td colspan="4"><ul class="errorlist"><li>Food delivery with this Driver and Restaurant already exists.</li></ul></td></tr>', 2)
|
|
|
|
def test_non_form_errors(self):
|
|
# test if non-form errors are handled; ticket #12716
|
|
data = {
|
|
"form-TOTAL_FORMS": "1",
|
|
"form-INITIAL_FORMS": "1",
|
|
"form-MAX_NUM_FORMS": "0",
|
|
|
|
"form-0-id": "2",
|
|
"form-0-alive": "1",
|
|
"form-0-gender": "2",
|
|
|
|
# Ensure that the form processing understands this as a list_editable "Save"
|
|
# and not an action "Go".
|
|
"_save": "Save",
|
|
}
|
|
response = self.client.post('/test_admin/admin/admin_views/person/', data)
|
|
self.assertContains(response, "Grace is not a Zombie")
|
|
|
|
def test_non_form_errors_is_errorlist(self):
|
|
# test if non-form errors are correctly handled; ticket #12878
|
|
data = {
|
|
"form-TOTAL_FORMS": "1",
|
|
"form-INITIAL_FORMS": "1",
|
|
"form-MAX_NUM_FORMS": "0",
|
|
|
|
"form-0-id": "2",
|
|
"form-0-alive": "1",
|
|
"form-0-gender": "2",
|
|
|
|
"_save": "Save",
|
|
}
|
|
response = self.client.post('/test_admin/admin/admin_views/person/', data)
|
|
non_form_errors = response.context['cl'].formset.non_form_errors()
|
|
self.assertTrue(isinstance(non_form_errors, ErrorList))
|
|
self.assertEqual(str(non_form_errors), str(ErrorList(["Grace is not a Zombie"])))
|
|
|
|
def test_list_editable_ordering(self):
|
|
collector = Collector.objects.create(id=1, name="Frederick Clegg")
|
|
|
|
Category.objects.create(id=1, order=1, collector=collector)
|
|
Category.objects.create(id=2, order=2, collector=collector)
|
|
Category.objects.create(id=3, order=0, collector=collector)
|
|
Category.objects.create(id=4, order=0, collector=collector)
|
|
|
|
# NB: The order values must be changed so that the items are reordered.
|
|
data = {
|
|
"form-TOTAL_FORMS": "4",
|
|
"form-INITIAL_FORMS": "4",
|
|
"form-MAX_NUM_FORMS": "0",
|
|
|
|
"form-0-order": "14",
|
|
"form-0-id": "1",
|
|
"form-0-collector": "1",
|
|
|
|
"form-1-order": "13",
|
|
"form-1-id": "2",
|
|
"form-1-collector": "1",
|
|
|
|
"form-2-order": "1",
|
|
"form-2-id": "3",
|
|
"form-2-collector": "1",
|
|
|
|
"form-3-order": "0",
|
|
"form-3-id": "4",
|
|
"form-3-collector": "1",
|
|
|
|
# Ensure that the form processing understands this as a list_editable "Save"
|
|
# and not an action "Go".
|
|
"_save": "Save",
|
|
}
|
|
response = self.client.post('/test_admin/admin/admin_views/category/', data)
|
|
# Successful post will redirect
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
# Check that the order values have been applied to the right objects
|
|
self.assertEqual(Category.objects.get(id=1).order, 14)
|
|
self.assertEqual(Category.objects.get(id=2).order, 13)
|
|
self.assertEqual(Category.objects.get(id=3).order, 1)
|
|
self.assertEqual(Category.objects.get(id=4).order, 0)
|
|
|
|
def test_list_editable_action_submit(self):
|
|
# List editable changes should not be executed if the action "Go" button is
|
|
# used to submit the form.
|
|
data = {
|
|
"form-TOTAL_FORMS": "3",
|
|
"form-INITIAL_FORMS": "3",
|
|
"form-MAX_NUM_FORMS": "0",
|
|
|
|
"form-0-gender": "1",
|
|
"form-0-id": "1",
|
|
|
|
"form-1-gender": "2",
|
|
"form-1-id": "2",
|
|
|
|
"form-2-alive": "checked",
|
|
"form-2-gender": "1",
|
|
"form-2-id": "3",
|
|
|
|
"index": "0",
|
|
"_selected_action": [u'3'],
|
|
"action": [u'', u'delete_selected'],
|
|
}
|
|
self.client.post('/test_admin/admin/admin_views/person/', data)
|
|
|
|
self.assertEqual(Person.objects.get(name="John Mauchly").alive, True)
|
|
self.assertEqual(Person.objects.get(name="Grace Hopper").gender, 1)
|
|
|
|
def test_list_editable_action_choices(self):
|
|
# List editable changes should be executed if the "Save" button is
|
|
# used to submit the form - any action choices should be ignored.
|
|
data = {
|
|
"form-TOTAL_FORMS": "3",
|
|
"form-INITIAL_FORMS": "3",
|
|
"form-MAX_NUM_FORMS": "0",
|
|
|
|
"form-0-gender": "1",
|
|
"form-0-id": "1",
|
|
|
|
"form-1-gender": "2",
|
|
"form-1-id": "2",
|
|
|
|
"form-2-alive": "checked",
|
|
"form-2-gender": "1",
|
|
"form-2-id": "3",
|
|
|
|
"_save": "Save",
|
|
"_selected_action": [u'1'],
|
|
"action": [u'', u'delete_selected'],
|
|
}
|
|
self.client.post('/test_admin/admin/admin_views/person/', data)
|
|
|
|
self.assertEqual(Person.objects.get(name="John Mauchly").alive, False)
|
|
self.assertEqual(Person.objects.get(name="Grace Hopper").gender, 2)
|
|
|
|
def test_list_editable_popup(self):
|
|
"""
|
|
Fields should not be list-editable in popups.
|
|
"""
|
|
response = self.client.get('/test_admin/admin/admin_views/person/')
|
|
self.assertNotEqual(response.context['cl'].list_editable, ())
|
|
response = self.client.get('/test_admin/admin/admin_views/person/?%s' % IS_POPUP_VAR)
|
|
self.assertEqual(response.context['cl'].list_editable, ())
|
|
|
|
def test_pk_hidden_fields(self):
|
|
""" Ensure that hidden pk fields aren't displayed in the table body and
|
|
that their corresponding human-readable value is displayed instead.
|
|
Note that the hidden pk fields are in fact be displayed but
|
|
separately (not in the table), and only once.
|
|
Refs #12475.
|
|
"""
|
|
story1 = Story.objects.create(title='The adventures of Guido', content='Once upon a time in Djangoland...')
|
|
story2 = Story.objects.create(title='Crouching Tiger, Hidden Python', content='The Python was sneaking into...')
|
|
response = self.client.get('/test_admin/admin/admin_views/story/')
|
|
self.assertContains(response, 'id="id_form-0-id"', 1) # Only one hidden field, in a separate place than the table.
|
|
self.assertContains(response, 'id="id_form-1-id"', 1)
|
|
self.assertContains(response, '<div class="hiddenfields">\n<input type="hidden" name="form-0-id" value="%d" id="id_form-0-id" /><input type="hidden" name="form-1-id" value="%d" id="id_form-1-id" />\n</div>' % (story2.id, story1.id))
|
|
self.assertContains(response, '<td>%d</td>' % story1.id, 1)
|
|
self.assertContains(response, '<td>%d</td>' % story2.id, 1)
|
|
|
|
def test_pk_hidden_fields_with_list_display_links(self):
|
|
""" Similarly as test_pk_hidden_fields, but when the hidden pk fields are
|
|
referenced in list_display_links.
|
|
Refs #12475.
|
|
"""
|
|
story1 = OtherStory.objects.create(title='The adventures of Guido', content='Once upon a time in Djangoland...')
|
|
story2 = OtherStory.objects.create(title='Crouching Tiger, Hidden Python', content='The Python was sneaking into...')
|
|
response = self.client.get('/test_admin/admin/admin_views/otherstory/')
|
|
self.assertContains(response, 'id="id_form-0-id"', 1) # Only one hidden field, in a separate place than the table.
|
|
self.assertContains(response, 'id="id_form-1-id"', 1)
|
|
self.assertContains(response, '<div class="hiddenfields">\n<input type="hidden" name="form-0-id" value="%d" id="id_form-0-id" /><input type="hidden" name="form-1-id" value="%d" id="id_form-1-id" />\n</div>' % (story2.id, story1.id))
|
|
self.assertContains(response, '<th><a href="%d/">%d</a></th>' % (story1.id, story1.id), 1)
|
|
self.assertContains(response, '<th><a href="%d/">%d</a></th>' % (story2.id, story2.id), 1)
|
|
|
|
|
|
class AdminSearchTest(TestCase):
|
|
fixtures = ['admin-views-users', 'multiple-child-classes',
|
|
'admin-views-person']
|
|
|
|
def setUp(self):
|
|
self.client.login(username='super', password='secret')
|
|
|
|
def tearDown(self):
|
|
self.client.logout()
|
|
|
|
def test_search_on_sibling_models(self):
|
|
"Check that a search that mentions sibling models"
|
|
response = self.client.get('/test_admin/admin/admin_views/recommendation/?q=bar')
|
|
# confirm the search returned 1 object
|
|
self.assertContains(response, "\n1 recommendation\n")
|
|
|
|
def test_with_fk_to_field(self):
|
|
"""Ensure that the to_field GET parameter is preserved when a search
|
|
is performed. Refs #10918.
|
|
"""
|
|
from django.contrib.admin.views.main import TO_FIELD_VAR
|
|
response = self.client.get('/test_admin/admin/auth/user/?q=joe&%s=username' % TO_FIELD_VAR)
|
|
self.assertContains(response, "\n1 user\n")
|
|
self.assertContains(response, '<input type="hidden" name="t" value="username"/>')
|
|
|
|
def test_exact_matches(self):
|
|
response = self.client.get('/test_admin/admin/admin_views/recommendation/?q=bar')
|
|
# confirm the search returned one object
|
|
self.assertContains(response, "\n1 recommendation\n")
|
|
|
|
response = self.client.get('/test_admin/admin/admin_views/recommendation/?q=ba')
|
|
# confirm the search returned zero objects
|
|
self.assertContains(response, "\n0 recommendations\n")
|
|
|
|
def test_beginning_matches(self):
|
|
response = self.client.get('/test_admin/admin/admin_views/person/?q=Gui')
|
|
# confirm the search returned one object
|
|
self.assertContains(response, "\n1 person\n")
|
|
self.assertContains(response, "Guido")
|
|
|
|
response = self.client.get('/test_admin/admin/admin_views/person/?q=uido')
|
|
# confirm the search returned zero objects
|
|
self.assertContains(response, "\n0 persons\n")
|
|
self.assertNotContains(response, "Guido")
|
|
|
|
|
|
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.assertEqual(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-MAX_NUM_FORMS": u"0",
|
|
"accounts-0-username": foo_user,
|
|
"accounts-2-TOTAL_FORMS": u"1",
|
|
"accounts-2-INITIAL_FORMS": u"0",
|
|
"accounts-2-MAX_NUM_FORMS": u"0",
|
|
"accounts-2-0-username": bar_user,
|
|
}
|
|
|
|
response = self.client.post('/test_admin/admin/admin_views/persona/add/', post_data)
|
|
self.assertEqual(response.status_code, 302) # redirect somewhere
|
|
self.assertEqual(Persona.objects.count(), 1)
|
|
self.assertEqual(FooAccount.objects.count(), 1)
|
|
self.assertEqual(BarAccount.objects.count(), 1)
|
|
self.assertEqual(FooAccount.objects.all()[0].username, foo_user)
|
|
self.assertEqual(BarAccount.objects.all()[0].username, bar_user)
|
|
self.assertEqual(Persona.objects.all()[0].accounts.count(), 2)
|
|
|
|
persona_id = Persona.objects.all()[0].id
|
|
foo_id = FooAccount.objects.all()[0].id
|
|
bar_id = BarAccount.objects.all()[0].id
|
|
|
|
# test the edit case
|
|
|
|
response = self.client.get('/test_admin/admin/admin_views/persona/%d/' % persona_id)
|
|
names = name_re.findall(response.content)
|
|
# make sure we have no duplicate HTML names
|
|
self.assertEqual(len(names), len(set(names)))
|
|
|
|
post_data = {
|
|
"name": u"Test Name",
|
|
|
|
"accounts-TOTAL_FORMS": "2",
|
|
"accounts-INITIAL_FORMS": u"1",
|
|
"accounts-MAX_NUM_FORMS": u"0",
|
|
|
|
"accounts-0-username": "%s-1" % foo_user,
|
|
"accounts-0-account_ptr": str(foo_id),
|
|
"accounts-0-persona": str(persona_id),
|
|
|
|
"accounts-2-TOTAL_FORMS": u"2",
|
|
"accounts-2-INITIAL_FORMS": u"1",
|
|
"accounts-2-MAX_NUM_FORMS": u"0",
|
|
|
|
"accounts-2-0-username": "%s-1" % bar_user,
|
|
"accounts-2-0-account_ptr": str(bar_id),
|
|
"accounts-2-0-persona": str(persona_id),
|
|
}
|
|
response = self.client.post('/test_admin/admin/admin_views/persona/%d/' % persona_id, post_data)
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(Persona.objects.count(), 1)
|
|
self.assertEqual(FooAccount.objects.count(), 1)
|
|
self.assertEqual(BarAccount.objects.count(), 1)
|
|
self.assertEqual(FooAccount.objects.all()[0].username, "%s-1" % foo_user)
|
|
self.assertEqual(BarAccount.objects.all()[0].username, "%s-1" % bar_user)
|
|
self.assertEqual(Persona.objects.all()[0].accounts.count(), 2)
|
|
|
|
class AdminActionsTest(TestCase):
|
|
fixtures = ['admin-views-users.xml', 'admin-views-actions.xml']
|
|
|
|
def setUp(self):
|
|
self.client.login(username='super', password='secret')
|
|
|
|
def tearDown(self):
|
|
self.client.logout()
|
|
|
|
def test_model_admin_custom_action(self):
|
|
"Tests a custom action defined in a ModelAdmin method"
|
|
action_data = {
|
|
ACTION_CHECKBOX_NAME: [1],
|
|
'action' : 'mail_admin',
|
|
'index': 0,
|
|
}
|
|
response = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data)
|
|
self.assertEqual(len(mail.outbox), 1)
|
|
self.assertEqual(mail.outbox[0].subject, 'Greetings from a ModelAdmin action')
|
|
|
|
def test_model_admin_default_delete_action(self):
|
|
"Tests the default delete action defined as a ModelAdmin method"
|
|
action_data = {
|
|
ACTION_CHECKBOX_NAME: [1, 2],
|
|
'action' : 'delete_selected',
|
|
'index': 0,
|
|
}
|
|
delete_confirmation_data = {
|
|
ACTION_CHECKBOX_NAME: [1, 2],
|
|
'action' : 'delete_selected',
|
|
'post': 'yes',
|
|
}
|
|
confirmation = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data)
|
|
self.assertContains(confirmation, "Are you sure you want to delete the selected subscribers")
|
|
self.assertTrue(confirmation.content.count(ACTION_CHECKBOX_NAME) == 2)
|
|
response = self.client.post('/test_admin/admin/admin_views/subscriber/', delete_confirmation_data)
|
|
self.assertEqual(Subscriber.objects.count(), 0)
|
|
|
|
def test_non_localized_pk(self):
|
|
"""If USE_THOUSAND_SEPARATOR is set, make sure that the ids for
|
|
the objects selected for deletion are rendered without separators.
|
|
Refs #14895.
|
|
"""
|
|
self.old_USE_THOUSAND_SEPARATOR = settings.USE_THOUSAND_SEPARATOR
|
|
self.old_USE_L10N = settings.USE_L10N
|
|
settings.USE_THOUSAND_SEPARATOR = True
|
|
settings.USE_L10N = True
|
|
subscriber = Subscriber.objects.get(id=1)
|
|
subscriber.id = 9999
|
|
subscriber.save()
|
|
action_data = {
|
|
ACTION_CHECKBOX_NAME: [9999, 2],
|
|
'action' : 'delete_selected',
|
|
'index': 0,
|
|
}
|
|
response = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data)
|
|
self.assertTemplateUsed(response, 'admin/delete_selected_confirmation.html')
|
|
self.assertTrue('value="9999"' in response.content and 'value="2"' in response.content) # Instead of 9,999
|
|
settings.USE_THOUSAND_SEPARATOR = self.old_USE_THOUSAND_SEPARATOR
|
|
settings.USE_L10N = self.old_USE_L10N
|
|
|
|
def test_model_admin_default_delete_action_protected(self):
|
|
"""
|
|
Tests the default delete action defined as a ModelAdmin method in the
|
|
case where some related objects are protected from deletion.
|
|
"""
|
|
q1 = Question.objects.create(question="Why?")
|
|
a1 = Answer.objects.create(question=q1, answer="Because.")
|
|
a2 = Answer.objects.create(question=q1, answer="Yes.")
|
|
q2 = Question.objects.create(question="Wherefore?")
|
|
|
|
action_data = {
|
|
ACTION_CHECKBOX_NAME: [q1.pk, q2.pk],
|
|
'action' : 'delete_selected',
|
|
'index': 0,
|
|
}
|
|
|
|
response = self.client.post("/test_admin/admin/admin_views/question/", action_data)
|
|
|
|
self.assertContains(response, "would require deleting the following protected related objects")
|
|
self.assertContains(response, '<li>Answer: <a href="/test_admin/admin/admin_views/answer/%s/">Because.</a></li>' % a1.pk)
|
|
self.assertContains(response, '<li>Answer: <a href="/test_admin/admin/admin_views/answer/%s/">Yes.</a></li>' % a2.pk)
|
|
|
|
def test_custom_function_mail_action(self):
|
|
"Tests a custom action defined in a function"
|
|
action_data = {
|
|
ACTION_CHECKBOX_NAME: [1],
|
|
'action' : 'external_mail',
|
|
'index': 0,
|
|
}
|
|
response = self.client.post('/test_admin/admin/admin_views/externalsubscriber/', action_data)
|
|
self.assertEqual(len(mail.outbox), 1)
|
|
self.assertEqual(mail.outbox[0].subject, 'Greetings from a function action')
|
|
|
|
def test_custom_function_action_with_redirect(self):
|
|
"Tests a custom action defined in a function"
|
|
action_data = {
|
|
ACTION_CHECKBOX_NAME: [1],
|
|
'action' : 'redirect_to',
|
|
'index': 0,
|
|
}
|
|
response = self.client.post('/test_admin/admin/admin_views/externalsubscriber/', action_data)
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
def test_default_redirect(self):
|
|
"""
|
|
Test that actions which don't return an HttpResponse are redirected to
|
|
the same page, retaining the querystring (which may contain changelist
|
|
information).
|
|
"""
|
|
action_data = {
|
|
ACTION_CHECKBOX_NAME: [1],
|
|
'action' : 'external_mail',
|
|
'index': 0,
|
|
}
|
|
url = '/test_admin/admin/admin_views/externalsubscriber/?ot=asc&o=1'
|
|
response = self.client.post(url, action_data)
|
|
self.assertRedirects(response, url)
|
|
|
|
def test_model_without_action(self):
|
|
"Tests a ModelAdmin without any action"
|
|
response = self.client.get('/test_admin/admin/admin_views/oldsubscriber/')
|
|
self.assertEqual(response.context["action_form"], None)
|
|
self.assertTrue(
|
|
'<input type="checkbox" class="action-select"' not in response.content,
|
|
"Found an unexpected action toggle checkboxbox in response"
|
|
)
|
|
self.assertTrue('action-checkbox-column' not in response.content,
|
|
"Found unexpected action-checkbox-column class in response")
|
|
|
|
def test_model_without_action_still_has_jquery(self):
|
|
"Tests that a ModelAdmin without any actions still gets jQuery included in page"
|
|
response = self.client.get('/test_admin/admin/admin_views/oldsubscriber/')
|
|
self.assertEqual(response.context["action_form"], None)
|
|
self.assertTrue('jquery.min.js' in response.content,
|
|
"jQuery missing from admin pages for model with no admin actions"
|
|
)
|
|
|
|
def test_action_column_class(self):
|
|
"Tests that the checkbox column class is present in the response"
|
|
response = self.client.get('/test_admin/admin/admin_views/subscriber/')
|
|
self.assertNotEqual(response.context["action_form"], None)
|
|
self.assertTrue('action-checkbox-column' in response.content,
|
|
"Expected an action-checkbox-column in response")
|
|
|
|
def test_multiple_actions_form(self):
|
|
"""
|
|
Test that actions come from the form whose submit button was pressed (#10618).
|
|
"""
|
|
action_data = {
|
|
ACTION_CHECKBOX_NAME: [1],
|
|
# Two different actions selected on the two forms...
|
|
'action': ['external_mail', 'delete_selected'],
|
|
# ...but we clicked "go" on the top form.
|
|
'index': 0
|
|
}
|
|
response = self.client.post('/test_admin/admin/admin_views/externalsubscriber/', action_data)
|
|
|
|
# Send mail, don't delete.
|
|
self.assertEqual(len(mail.outbox), 1)
|
|
self.assertEqual(mail.outbox[0].subject, 'Greetings from a function action')
|
|
|
|
def test_user_message_on_none_selected(self):
|
|
"""
|
|
User should see a warning when 'Go' is pressed and no items are selected.
|
|
"""
|
|
action_data = {
|
|
ACTION_CHECKBOX_NAME: [],
|
|
'action' : 'delete_selected',
|
|
'index': 0,
|
|
}
|
|
response = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data)
|
|
msg = """Items must be selected in order to perform actions on them. No items have been changed."""
|
|
self.assertContains(response, msg)
|
|
self.assertEqual(Subscriber.objects.count(), 2)
|
|
|
|
def test_user_message_on_no_action(self):
|
|
"""
|
|
User should see a warning when 'Go' is pressed and no action is selected.
|
|
"""
|
|
action_data = {
|
|
ACTION_CHECKBOX_NAME: [1, 2],
|
|
'action' : '',
|
|
'index': 0,
|
|
}
|
|
response = self.client.post('/test_admin/admin/admin_views/subscriber/', action_data)
|
|
msg = """No action selected."""
|
|
self.assertContains(response, msg)
|
|
self.assertEqual(Subscriber.objects.count(), 2)
|
|
|
|
def test_selection_counter(self):
|
|
"""
|
|
Check if the selection counter is there.
|
|
"""
|
|
response = self.client.get('/test_admin/admin/admin_views/subscriber/')
|
|
self.assertContains(response, '0 of 2 selected')
|
|
|
|
def test_popup_actions(self):
|
|
""" Actions should not be shown in popups. """
|
|
response = self.client.get('/test_admin/admin/admin_views/subscriber/')
|
|
self.assertNotEquals(response.context["action_form"], None)
|
|
response = self.client.get(
|
|
'/test_admin/admin/admin_views/subscriber/?%s' % IS_POPUP_VAR)
|
|
self.assertEqual(response.context["action_form"], None)
|
|
|
|
|
|
class TestCustomChangeList(TestCase):
|
|
fixtures = ['admin-views-users.xml']
|
|
urlbit = 'admin'
|
|
|
|
def setUp(self):
|
|
result = self.client.login(username='super', password='secret')
|
|
self.assertEqual(result, True)
|
|
|
|
def tearDown(self):
|
|
self.client.logout()
|
|
|
|
def test_custom_changelist(self):
|
|
"""
|
|
Validate that a custom ChangeList class can be used (#9749)
|
|
"""
|
|
# Insert some data
|
|
post_data = {"name": u"First Gadget"}
|
|
response = self.client.post('/test_admin/%s/admin_views/gadget/add/' % self.urlbit, post_data)
|
|
self.assertEqual(response.status_code, 302) # redirect somewhere
|
|
# Hit the page once to get messages out of the queue message list
|
|
response = self.client.get('/test_admin/%s/admin_views/gadget/' % self.urlbit)
|
|
# Ensure that that data is still not visible on the page
|
|
response = self.client.get('/test_admin/%s/admin_views/gadget/' % self.urlbit)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertNotContains(response, 'First Gadget')
|
|
|
|
|
|
class TestInlineNotEditable(TestCase):
|
|
fixtures = ['admin-views-users.xml']
|
|
|
|
def setUp(self):
|
|
result = self.client.login(username='super', password='secret')
|
|
self.assertEqual(result, True)
|
|
|
|
def tearDown(self):
|
|
self.client.logout()
|
|
|
|
def test(self):
|
|
"""
|
|
InlineModelAdmin broken?
|
|
"""
|
|
response = self.client.get('/test_admin/admin/admin_views/parent/add/')
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
class AdminCustomQuerysetTest(TestCase):
|
|
fixtures = ['admin-views-users.xml']
|
|
|
|
def setUp(self):
|
|
self.client.login(username='super', password='secret')
|
|
self.pks = [EmptyModel.objects.create().id for i in range(3)]
|
|
|
|
def test_changelist_view(self):
|
|
response = self.client.get('/test_admin/admin/admin_views/emptymodel/')
|
|
for i in self.pks:
|
|
if i > 1:
|
|
self.assertContains(response, 'Primary key = %s' % i)
|
|
else:
|
|
self.assertNotContains(response, 'Primary key = %s' % i)
|
|
|
|
def test_change_view(self):
|
|
for i in self.pks:
|
|
response = self.client.get('/test_admin/admin/admin_views/emptymodel/%s/' % i)
|
|
if i > 1:
|
|
self.assertEqual(response.status_code, 200)
|
|
else:
|
|
self.assertEqual(response.status_code, 404)
|
|
|
|
def test_add_model_modeladmin_only_qs(self):
|
|
# only() is used in ModelAdmin.queryset()
|
|
p = Paper.objects.create(title=u"My Paper Title")
|
|
self.assertEqual(Paper.objects.count(), 1)
|
|
response = self.client.get('/test_admin/admin/admin_views/paper/%s/' % p.pk)
|
|
self.assertEqual(response.status_code, 200)
|
|
post_data = {
|
|
"title": u"My Modified Paper Title",
|
|
"_save": "Save",
|
|
}
|
|
response = self.client.post('/test_admin/admin/admin_views/paper/%s/' % p.pk,
|
|
post_data, follow=True)
|
|
self.assertEqual(response.status_code, 200)
|
|
# Message should contain non-ugly model name. Instance representation is set by unicode() (ugly)
|
|
self.assertContains(response, '<li class="info">The paper "Paper_Deferred_author object" was changed successfully.</li>')
|
|
|
|
# defer() is used in ModelAdmin.queryset()
|
|
cl = CoverLetter.objects.create(author=u"John Doe")
|
|
self.assertEqual(CoverLetter.objects.count(), 1)
|
|
response = self.client.get('/test_admin/admin/admin_views/coverletter/%s/' % cl.pk)
|
|
self.assertEqual(response.status_code, 200)
|
|
post_data = {
|
|
"author": u"John Doe II",
|
|
"_save": "Save",
|
|
}
|
|
response = self.client.post('/test_admin/admin/admin_views/coverletter/%s/' % cl.pk,
|
|
post_data, follow=True)
|
|
self.assertEqual(response.status_code, 200)
|
|
# Message should contain non-ugly model name. Instance representation is set by model's __unicode__()
|
|
self.assertContains(response, '<li class="info">The cover letter "John Doe II" was changed successfully.</li>')
|
|
|
|
class AdminInlineFileUploadTest(TestCase):
|
|
fixtures = ['admin-views-users.xml', 'admin-views-actions.xml']
|
|
urlbit = 'admin'
|
|
|
|
def setUp(self):
|
|
self.client.login(username='super', password='secret')
|
|
|
|
# Set up test Picture and Gallery.
|
|
# These must be set up here instead of in fixtures in order to allow Picture
|
|
# to use a NamedTemporaryFile.
|
|
tdir = tempfile.gettempdir()
|
|
file1 = tempfile.NamedTemporaryFile(suffix=".file1", dir=tdir)
|
|
file1.write('a' * (2 ** 21))
|
|
filename = file1.name
|
|
file1.close()
|
|
self.gallery = Gallery(name="Test Gallery")
|
|
self.gallery.save()
|
|
self.picture = Picture(name="Test Picture", image=filename, gallery=self.gallery)
|
|
self.picture.save()
|
|
|
|
def tearDown(self):
|
|
self.client.logout()
|
|
|
|
def test_inline_file_upload_edit_validation_error_post(self):
|
|
"""
|
|
Test that inline file uploads correctly display prior data (#10002).
|
|
"""
|
|
post_data = {
|
|
"name": u"Test Gallery",
|
|
"pictures-TOTAL_FORMS": u"2",
|
|
"pictures-INITIAL_FORMS": u"1",
|
|
"pictures-MAX_NUM_FORMS": u"0",
|
|
"pictures-0-id": unicode(self.picture.id),
|
|
"pictures-0-gallery": unicode(self.gallery.id),
|
|
"pictures-0-name": "Test Picture",
|
|
"pictures-0-image": "",
|
|
"pictures-1-id": "",
|
|
"pictures-1-gallery": str(self.gallery.id),
|
|
"pictures-1-name": "Test Picture 2",
|
|
"pictures-1-image": "",
|
|
}
|
|
response = self.client.post('/test_admin/%s/admin_views/gallery/%d/' % (self.urlbit, self.gallery.id), post_data)
|
|
self.assertTrue(response._container[0].find("Currently:") > -1)
|
|
|
|
|
|
class AdminInlineTests(TestCase):
|
|
fixtures = ['admin-views-users.xml']
|
|
|
|
def setUp(self):
|
|
self.post_data = {
|
|
"name": u"Test Name",
|
|
|
|
"widget_set-TOTAL_FORMS": "3",
|
|
"widget_set-INITIAL_FORMS": u"0",
|
|
"widget_set-MAX_NUM_FORMS": u"0",
|
|
"widget_set-0-id": "",
|
|
"widget_set-0-owner": "1",
|
|
"widget_set-0-name": "",
|
|
"widget_set-1-id": "",
|
|
"widget_set-1-owner": "1",
|
|
"widget_set-1-name": "",
|
|
"widget_set-2-id": "",
|
|
"widget_set-2-owner": "1",
|
|
"widget_set-2-name": "",
|
|
|
|
"doohickey_set-TOTAL_FORMS": "3",
|
|
"doohickey_set-INITIAL_FORMS": u"0",
|
|
"doohickey_set-MAX_NUM_FORMS": u"0",
|
|
"doohickey_set-0-owner": "1",
|
|
"doohickey_set-0-code": "",
|
|
"doohickey_set-0-name": "",
|
|
"doohickey_set-1-owner": "1",
|
|
"doohickey_set-1-code": "",
|
|
"doohickey_set-1-name": "",
|
|
"doohickey_set-2-owner": "1",
|
|
"doohickey_set-2-code": "",
|
|
"doohickey_set-2-name": "",
|
|
|
|
"grommet_set-TOTAL_FORMS": "3",
|
|
"grommet_set-INITIAL_FORMS": u"0",
|
|
"grommet_set-MAX_NUM_FORMS": u"0",
|
|
"grommet_set-0-code": "",
|
|
"grommet_set-0-owner": "1",
|
|
"grommet_set-0-name": "",
|
|
"grommet_set-1-code": "",
|
|
"grommet_set-1-owner": "1",
|
|
"grommet_set-1-name": "",
|
|
"grommet_set-2-code": "",
|
|
"grommet_set-2-owner": "1",
|
|
"grommet_set-2-name": "",
|
|
|
|
"whatsit_set-TOTAL_FORMS": "3",
|
|
"whatsit_set-INITIAL_FORMS": u"0",
|
|
"whatsit_set-MAX_NUM_FORMS": u"0",
|
|
"whatsit_set-0-owner": "1",
|
|
"whatsit_set-0-index": "",
|
|
"whatsit_set-0-name": "",
|
|
"whatsit_set-1-owner": "1",
|
|
"whatsit_set-1-index": "",
|
|
"whatsit_set-1-name": "",
|
|
"whatsit_set-2-owner": "1",
|
|
"whatsit_set-2-index": "",
|
|
"whatsit_set-2-name": "",
|
|
|
|
"fancydoodad_set-TOTAL_FORMS": "3",
|
|
"fancydoodad_set-INITIAL_FORMS": u"0",
|
|
"fancydoodad_set-MAX_NUM_FORMS": u"0",
|
|
"fancydoodad_set-0-doodad_ptr": "",
|
|
"fancydoodad_set-0-owner": "1",
|
|
"fancydoodad_set-0-name": "",
|
|
"fancydoodad_set-0-expensive": "on",
|
|
"fancydoodad_set-1-doodad_ptr": "",
|
|
"fancydoodad_set-1-owner": "1",
|
|
"fancydoodad_set-1-name": "",
|
|
"fancydoodad_set-1-expensive": "on",
|
|
"fancydoodad_set-2-doodad_ptr": "",
|
|
"fancydoodad_set-2-owner": "1",
|
|
"fancydoodad_set-2-name": "",
|
|
"fancydoodad_set-2-expensive": "on",
|
|
|
|
"category_set-TOTAL_FORMS": "3",
|
|
"category_set-INITIAL_FORMS": "0",
|
|
"category_set-MAX_NUM_FORMS": "0",
|
|
"category_set-0-order": "",
|
|
"category_set-0-id": "",
|
|
"category_set-0-collector": "1",
|
|
"category_set-1-order": "",
|
|
"category_set-1-id": "",
|
|
"category_set-1-collector": "1",
|
|
"category_set-2-order": "",
|
|
"category_set-2-id": "",
|
|
"category_set-2-collector": "1",
|
|
}
|
|
|
|
result = self.client.login(username='super', password='secret')
|
|
self.assertEqual(result, True)
|
|
self.collector = Collector(pk=1,name='John Fowles')
|
|
self.collector.save()
|
|
|
|
def tearDown(self):
|
|
self.client.logout()
|
|
|
|
def test_simple_inline(self):
|
|
"A simple model can be saved as inlines"
|
|
# First add a new inline
|
|
self.post_data['widget_set-0-name'] = "Widget 1"
|
|
collector_url = '/test_admin/admin/admin_views/collector/%d/' % self.collector.pk
|
|
response = self.client.post(collector_url, self.post_data)
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(Widget.objects.count(), 1)
|
|
self.assertEqual(Widget.objects.all()[0].name, "Widget 1")
|
|
widget_id = Widget.objects.all()[0].id
|
|
|
|
# Check that the PK link exists on the rendered form
|
|
response = self.client.get(collector_url)
|
|
self.assertContains(response, 'name="widget_set-0-id"')
|
|
|
|
# Now resave that inline
|
|
self.post_data['widget_set-INITIAL_FORMS'] = "1"
|
|
self.post_data['widget_set-0-id'] = str(widget_id)
|
|
self.post_data['widget_set-0-name'] = "Widget 1"
|
|
response = self.client.post(collector_url, self.post_data)
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(Widget.objects.count(), 1)
|
|
self.assertEqual(Widget.objects.all()[0].name, "Widget 1")
|
|
|
|
# Now modify that inline
|
|
self.post_data['widget_set-INITIAL_FORMS'] = "1"
|
|
self.post_data['widget_set-0-id'] = str(widget_id)
|
|
self.post_data['widget_set-0-name'] = "Widget 1 Updated"
|
|
response = self.client.post(collector_url, self.post_data)
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(Widget.objects.count(), 1)
|
|
self.assertEqual(Widget.objects.all()[0].name, "Widget 1 Updated")
|
|
|
|
def test_explicit_autofield_inline(self):
|
|
"A model with an explicit autofield primary key can be saved as inlines. Regression for #8093"
|
|
# First add a new inline
|
|
self.post_data['grommet_set-0-name'] = "Grommet 1"
|
|
collector_url = '/test_admin/admin/admin_views/collector/%d/' % self.collector.pk
|
|
response = self.client.post(collector_url, self.post_data)
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(Grommet.objects.count(), 1)
|
|
self.assertEqual(Grommet.objects.all()[0].name, "Grommet 1")
|
|
|
|
# Check that the PK link exists on the rendered form
|
|
response = self.client.get(collector_url)
|
|
self.assertContains(response, 'name="grommet_set-0-code"')
|
|
|
|
# Now resave that inline
|
|
self.post_data['grommet_set-INITIAL_FORMS'] = "1"
|
|
self.post_data['grommet_set-0-code'] = str(Grommet.objects.all()[0].code)
|
|
self.post_data['grommet_set-0-name'] = "Grommet 1"
|
|
response = self.client.post(collector_url, self.post_data)
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(Grommet.objects.count(), 1)
|
|
self.assertEqual(Grommet.objects.all()[0].name, "Grommet 1")
|
|
|
|
# Now modify that inline
|
|
self.post_data['grommet_set-INITIAL_FORMS'] = "1"
|
|
self.post_data['grommet_set-0-code'] = str(Grommet.objects.all()[0].code)
|
|
self.post_data['grommet_set-0-name'] = "Grommet 1 Updated"
|
|
response = self.client.post(collector_url, self.post_data)
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(Grommet.objects.count(), 1)
|
|
self.assertEqual(Grommet.objects.all()[0].name, "Grommet 1 Updated")
|
|
|
|
def test_char_pk_inline(self):
|
|
"A model with a character PK can be saved as inlines. Regression for #10992"
|
|
# First add a new inline
|
|
self.post_data['doohickey_set-0-code'] = "DH1"
|
|
self.post_data['doohickey_set-0-name'] = "Doohickey 1"
|
|
collector_url = '/test_admin/admin/admin_views/collector/%d/' % self.collector.pk
|
|
response = self.client.post(collector_url, self.post_data)
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(DooHickey.objects.count(), 1)
|
|
self.assertEqual(DooHickey.objects.all()[0].name, "Doohickey 1")
|
|
|
|
# Check that the PK link exists on the rendered form
|
|
response = self.client.get(collector_url)
|
|
self.assertContains(response, 'name="doohickey_set-0-code"')
|
|
|
|
# Now resave that inline
|
|
self.post_data['doohickey_set-INITIAL_FORMS'] = "1"
|
|
self.post_data['doohickey_set-0-code'] = "DH1"
|
|
self.post_data['doohickey_set-0-name'] = "Doohickey 1"
|
|
response = self.client.post(collector_url, self.post_data)
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(DooHickey.objects.count(), 1)
|
|
self.assertEqual(DooHickey.objects.all()[0].name, "Doohickey 1")
|
|
|
|
# Now modify that inline
|
|
self.post_data['doohickey_set-INITIAL_FORMS'] = "1"
|
|
self.post_data['doohickey_set-0-code'] = "DH1"
|
|
self.post_data['doohickey_set-0-name'] = "Doohickey 1 Updated"
|
|
response = self.client.post(collector_url, self.post_data)
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(DooHickey.objects.count(), 1)
|
|
self.assertEqual(DooHickey.objects.all()[0].name, "Doohickey 1 Updated")
|
|
|
|
def test_integer_pk_inline(self):
|
|
"A model with an integer PK can be saved as inlines. Regression for #10992"
|
|
# First add a new inline
|
|
self.post_data['whatsit_set-0-index'] = "42"
|
|
self.post_data['whatsit_set-0-name'] = "Whatsit 1"
|
|
response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(Whatsit.objects.count(), 1)
|
|
self.assertEqual(Whatsit.objects.all()[0].name, "Whatsit 1")
|
|
|
|
# Check that the PK link exists on the rendered form
|
|
response = self.client.get('/test_admin/admin/admin_views/collector/1/')
|
|
self.assertContains(response, 'name="whatsit_set-0-index"')
|
|
|
|
# Now resave that inline
|
|
self.post_data['whatsit_set-INITIAL_FORMS'] = "1"
|
|
self.post_data['whatsit_set-0-index'] = "42"
|
|
self.post_data['whatsit_set-0-name'] = "Whatsit 1"
|
|
response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(Whatsit.objects.count(), 1)
|
|
self.assertEqual(Whatsit.objects.all()[0].name, "Whatsit 1")
|
|
|
|
# Now modify that inline
|
|
self.post_data['whatsit_set-INITIAL_FORMS'] = "1"
|
|
self.post_data['whatsit_set-0-index'] = "42"
|
|
self.post_data['whatsit_set-0-name'] = "Whatsit 1 Updated"
|
|
response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(Whatsit.objects.count(), 1)
|
|
self.assertEqual(Whatsit.objects.all()[0].name, "Whatsit 1 Updated")
|
|
|
|
def test_inherited_inline(self):
|
|
"An inherited model can be saved as inlines. Regression for #11042"
|
|
# First add a new inline
|
|
self.post_data['fancydoodad_set-0-name'] = "Fancy Doodad 1"
|
|
collector_url = '/test_admin/admin/admin_views/collector/%d/' % self.collector.pk
|
|
response = self.client.post(collector_url, self.post_data)
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(FancyDoodad.objects.count(), 1)
|
|
self.assertEqual(FancyDoodad.objects.all()[0].name, "Fancy Doodad 1")
|
|
doodad_pk = FancyDoodad.objects.all()[0].pk
|
|
|
|
# Check that the PK link exists on the rendered form
|
|
response = self.client.get(collector_url)
|
|
self.assertContains(response, 'name="fancydoodad_set-0-doodad_ptr"')
|
|
|
|
# Now resave that inline
|
|
self.post_data['fancydoodad_set-INITIAL_FORMS'] = "1"
|
|
self.post_data['fancydoodad_set-0-doodad_ptr'] = str(doodad_pk)
|
|
self.post_data['fancydoodad_set-0-name'] = "Fancy Doodad 1"
|
|
response = self.client.post(collector_url, self.post_data)
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(FancyDoodad.objects.count(), 1)
|
|
self.assertEqual(FancyDoodad.objects.all()[0].name, "Fancy Doodad 1")
|
|
|
|
# Now modify that inline
|
|
self.post_data['fancydoodad_set-INITIAL_FORMS'] = "1"
|
|
self.post_data['fancydoodad_set-0-doodad_ptr'] = str(doodad_pk)
|
|
self.post_data['fancydoodad_set-0-name'] = "Fancy Doodad 1 Updated"
|
|
response = self.client.post(collector_url, self.post_data)
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(FancyDoodad.objects.count(), 1)
|
|
self.assertEqual(FancyDoodad.objects.all()[0].name, "Fancy Doodad 1 Updated")
|
|
|
|
def test_ordered_inline(self):
|
|
"""Check that an inline with an editable ordering fields is
|
|
updated correctly. Regression for #10922"""
|
|
# Create some objects with an initial ordering
|
|
Category.objects.create(id=1, order=1, collector=self.collector)
|
|
Category.objects.create(id=2, order=2, collector=self.collector)
|
|
Category.objects.create(id=3, order=0, collector=self.collector)
|
|
Category.objects.create(id=4, order=0, collector=self.collector)
|
|
|
|
# NB: The order values must be changed so that the items are reordered.
|
|
self.post_data.update({
|
|
"name": "Frederick Clegg",
|
|
|
|
"category_set-TOTAL_FORMS": "7",
|
|
"category_set-INITIAL_FORMS": "4",
|
|
"category_set-MAX_NUM_FORMS": "0",
|
|
|
|
"category_set-0-order": "14",
|
|
"category_set-0-id": "1",
|
|
"category_set-0-collector": "1",
|
|
|
|
"category_set-1-order": "13",
|
|
"category_set-1-id": "2",
|
|
"category_set-1-collector": "1",
|
|
|
|
"category_set-2-order": "1",
|
|
"category_set-2-id": "3",
|
|
"category_set-2-collector": "1",
|
|
|
|
"category_set-3-order": "0",
|
|
"category_set-3-id": "4",
|
|
"category_set-3-collector": "1",
|
|
|
|
"category_set-4-order": "",
|
|
"category_set-4-id": "",
|
|
"category_set-4-collector": "1",
|
|
|
|
"category_set-5-order": "",
|
|
"category_set-5-id": "",
|
|
"category_set-5-collector": "1",
|
|
|
|
"category_set-6-order": "",
|
|
"category_set-6-id": "",
|
|
"category_set-6-collector": "1",
|
|
})
|
|
response = self.client.post('/test_admin/admin/admin_views/collector/1/', self.post_data)
|
|
# Successful post will redirect
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
# Check that the order values have been applied to the right objects
|
|
self.assertEqual(self.collector.category_set.count(), 4)
|
|
self.assertEqual(Category.objects.get(id=1).order, 14)
|
|
self.assertEqual(Category.objects.get(id=2).order, 13)
|
|
self.assertEqual(Category.objects.get(id=3).order, 1)
|
|
self.assertEqual(Category.objects.get(id=4).order, 0)
|
|
|
|
|
|
class NeverCacheTests(TestCase):
|
|
fixtures = ['admin-views-users.xml', 'admin-views-colors.xml', 'admin-views-fabrics.xml']
|
|
|
|
def setUp(self):
|
|
self.client.login(username='super', password='secret')
|
|
|
|
def tearDown(self):
|
|
self.client.logout()
|
|
|
|
def testAdminIndex(self):
|
|
"Check the never-cache status of the main index"
|
|
response = self.client.get('/test_admin/admin/')
|
|
self.assertEqual(get_max_age(response), 0)
|
|
|
|
def testAppIndex(self):
|
|
"Check the never-cache status of an application index"
|
|
response = self.client.get('/test_admin/admin/admin_views/')
|
|
self.assertEqual(get_max_age(response), 0)
|
|
|
|
def testModelIndex(self):
|
|
"Check the never-cache status of a model index"
|
|
response = self.client.get('/test_admin/admin/admin_views/fabric/')
|
|
self.assertEqual(get_max_age(response), 0)
|
|
|
|
def testModelAdd(self):
|
|
"Check the never-cache status of a model add page"
|
|
response = self.client.get('/test_admin/admin/admin_views/fabric/add/')
|
|
self.assertEqual(get_max_age(response), 0)
|
|
|
|
def testModelView(self):
|
|
"Check the never-cache status of a model edit page"
|
|
response = self.client.get('/test_admin/admin/admin_views/section/1/')
|
|
self.assertEqual(get_max_age(response), 0)
|
|
|
|
def testModelHistory(self):
|
|
"Check the never-cache status of a model history page"
|
|
response = self.client.get('/test_admin/admin/admin_views/section/1/history/')
|
|
self.assertEqual(get_max_age(response), 0)
|
|
|
|
def testModelDelete(self):
|
|
"Check the never-cache status of a model delete page"
|
|
response = self.client.get('/test_admin/admin/admin_views/section/1/delete/')
|
|
self.assertEqual(get_max_age(response), 0)
|
|
|
|
def testLogin(self):
|
|
"Check the never-cache status of login views"
|
|
self.client.logout()
|
|
response = self.client.get('/test_admin/admin/')
|
|
self.assertEqual(get_max_age(response), 0)
|
|
|
|
def testLogout(self):
|
|
"Check the never-cache status of logout view"
|
|
response = self.client.get('/test_admin/admin/logout/')
|
|
self.assertEqual(get_max_age(response), 0)
|
|
|
|
def testPasswordChange(self):
|
|
"Check the never-cache status of the password change view"
|
|
self.client.logout()
|
|
response = self.client.get('/test_admin/password_change/')
|
|
self.assertEqual(get_max_age(response), None)
|
|
|
|
def testPasswordChangeDone(self):
|
|
"Check the never-cache status of the password change done view"
|
|
response = self.client.get('/test_admin/admin/password_change/done/')
|
|
self.assertEqual(get_max_age(response), None)
|
|
|
|
def testJsi18n(self):
|
|
"Check the never-cache status of the Javascript i18n view"
|
|
response = self.client.get('/test_admin/admin/jsi18n/')
|
|
self.assertEqual(get_max_age(response), None)
|
|
|
|
|
|
class ReadonlyTest(TestCase):
|
|
fixtures = ['admin-views-users.xml']
|
|
|
|
def setUp(self):
|
|
self.client.login(username='super', password='secret')
|
|
|
|
def tearDown(self):
|
|
self.client.logout()
|
|
|
|
def test_readonly_get(self):
|
|
response = self.client.get('/test_admin/admin/admin_views/post/add/')
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertNotContains(response, 'name="posted"')
|
|
# 3 fields + 2 submit buttons + 4 inline management form fields, + 2
|
|
# hidden fields for inlines + 1 field for the inline + 2 empty form
|
|
self.assertEqual(response.content.count("<input"), 14)
|
|
self.assertContains(response, formats.localize(datetime.date.today()))
|
|
self.assertContains(response,
|
|
"<label>Awesomeness level:</label>")
|
|
self.assertContains(response, "Very awesome.")
|
|
self.assertContains(response, "Unkown coolness.")
|
|
self.assertContains(response, "foo")
|
|
self.assertContains(response,
|
|
formats.localize(datetime.date.today() - datetime.timedelta(days=7))
|
|
)
|
|
|
|
self.assertContains(response, '<div class="form-row coolness">')
|
|
self.assertContains(response, '<div class="form-row awesomeness_level">')
|
|
self.assertContains(response, '<div class="form-row posted">')
|
|
self.assertContains(response, '<div class="form-row value">')
|
|
self.assertContains(response, '<div class="form-row ">')
|
|
self.assertContains(response, '<p class="help">', 3)
|
|
self.assertContains(response, '<p class="help">Some help text for the title (with unicode ŠĐĆŽćžšđ)</p>')
|
|
self.assertContains(response, '<p class="help">Some help text for the content (with unicode ŠĐĆŽćžšđ)</p>')
|
|
self.assertContains(response, '<p class="help">Some help text for the date (with unicode ŠĐĆŽćžšđ)</p>')
|
|
|
|
p = Post.objects.create(title="I worked on readonly_fields", content="Its good stuff")
|
|
response = self.client.get('/test_admin/admin/admin_views/post/%d/' % p.pk)
|
|
self.assertContains(response, "%d amount of cool" % p.pk)
|
|
|
|
def test_readonly_post(self):
|
|
data = {
|
|
"title": "Django Got Readonly Fields",
|
|
"content": "This is an incredible development.",
|
|
"link_set-TOTAL_FORMS": "1",
|
|
"link_set-INITIAL_FORMS": "0",
|
|
"link_set-MAX_NUM_FORMS": "0",
|
|
}
|
|
response = self.client.post('/test_admin/admin/admin_views/post/add/', data)
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(Post.objects.count(), 1)
|
|
p = Post.objects.get()
|
|
self.assertEqual(p.posted, datetime.date.today())
|
|
|
|
data["posted"] = "10-8-1990" # some date that's not today
|
|
response = self.client.post('/test_admin/admin/admin_views/post/add/', data)
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(Post.objects.count(), 2)
|
|
p = Post.objects.order_by('-id')[0]
|
|
self.assertEqual(p.posted, datetime.date.today())
|
|
|
|
def test_readonly_manytomany(self):
|
|
"Regression test for #13004"
|
|
response = self.client.get('/test_admin/admin/admin_views/pizza/add/')
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
|
|
class RawIdFieldsTest(TestCase):
|
|
fixtures = ['admin-views-users.xml']
|
|
|
|
def setUp(self):
|
|
self.client.login(username='super', password='secret')
|
|
|
|
def tearDown(self):
|
|
self.client.logout()
|
|
|
|
def test_limit_choices_to(self):
|
|
"""Regression test for 14880"""
|
|
# This includes tests integers, strings and booleans in the lookup query string
|
|
actor = Actor.objects.create(name="Palin", age=27)
|
|
inquisition1 = Inquisition.objects.create(expected=True,
|
|
leader=actor,
|
|
country="England")
|
|
inquisition2 = Inquisition.objects.create(expected=False,
|
|
leader=actor,
|
|
country="Spain")
|
|
response = self.client.get('/test_admin/admin/admin_views/sketch/add/')
|
|
# Find the link
|
|
m = re.search(r'<a href="([^"]*)"[^>]* id="lookup_id_inquisition"', response.content)
|
|
self.assertTrue(m) # Got a match
|
|
popup_url = m.groups()[0].replace("&", "&")
|
|
|
|
# Handle relative links
|
|
popup_url = urlparse.urljoin(response.request['PATH_INFO'], popup_url)
|
|
# Get the popup
|
|
response2 = self.client.get(popup_url)
|
|
self.assertContains(response2, "Spain")
|
|
self.assertNotContains(response2, "England")
|
|
|
|
class UserAdminTest(TestCase):
|
|
"""
|
|
Tests user CRUD functionality.
|
|
"""
|
|
fixtures = ['admin-views-users.xml']
|
|
|
|
def setUp(self):
|
|
self.client.login(username='super', password='secret')
|
|
|
|
def tearDown(self):
|
|
self.client.logout()
|
|
|
|
def test_save_button(self):
|
|
user_count = User.objects.count()
|
|
response = self.client.post('/test_admin/admin/auth/user/add/', {
|
|
'username': 'newuser',
|
|
'password1': 'newpassword',
|
|
'password2': 'newpassword',
|
|
})
|
|
new_user = User.objects.order_by('-id')[0]
|
|
self.assertRedirects(response, '/test_admin/admin/auth/user/%s/' % new_user.pk)
|
|
self.assertEqual(User.objects.count(), user_count + 1)
|
|
self.assertNotEqual(new_user.password, UNUSABLE_PASSWORD)
|
|
|
|
def test_save_continue_editing_button(self):
|
|
user_count = User.objects.count()
|
|
response = self.client.post('/test_admin/admin/auth/user/add/', {
|
|
'username': 'newuser',
|
|
'password1': 'newpassword',
|
|
'password2': 'newpassword',
|
|
'_continue': '1',
|
|
})
|
|
new_user = User.objects.order_by('-id')[0]
|
|
self.assertRedirects(response, '/test_admin/admin/auth/user/%s/' % new_user.pk)
|
|
self.assertEqual(User.objects.count(), user_count + 1)
|
|
self.assertNotEqual(new_user.password, UNUSABLE_PASSWORD)
|
|
|
|
def test_password_mismatch(self):
|
|
response = self.client.post('/test_admin/admin/auth/user/add/', {
|
|
'username': 'newuser',
|
|
'password1': 'newpassword',
|
|
'password2': 'mismatch',
|
|
})
|
|
self.assertEqual(response.status_code, 200)
|
|
adminform = response.context['adminform']
|
|
self.assertTrue('password' not in adminform.form.errors)
|
|
self.assertEqual(adminform.form.errors['password2'],
|
|
[u"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"""
|
|
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" onclick="return showAddAnotherPopup(this);"')
|
|
response = self.client.get('/test_admin/admin/auth/user/add/?_popup=1')
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertNotContains(response, 'name="_continue"')
|
|
self.assertNotContains(response, 'name="_addanother"')
|
|
data = {
|
|
'username': 'newuser',
|
|
'password1': 'newpassword',
|
|
'password2': 'newpassword',
|
|
'_popup': '1',
|
|
'_save': '1',
|
|
}
|
|
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')
|
|
|
|
def test_save_add_another_button(self):
|
|
user_count = User.objects.count()
|
|
response = self.client.post('/test_admin/admin/auth/user/add/', {
|
|
'username': 'newuser',
|
|
'password1': 'newpassword',
|
|
'password2': 'newpassword',
|
|
'_addanother': '1',
|
|
})
|
|
new_user = User.objects.order_by('-id')[0]
|
|
self.assertRedirects(response, '/test_admin/admin/auth/user/add/')
|
|
self.assertEqual(User.objects.count(), user_count + 1)
|
|
self.assertNotEqual(new_user.password, UNUSABLE_PASSWORD)
|
|
|
|
try:
|
|
import docutils
|
|
except ImportError:
|
|
docutils = None
|
|
|
|
#@unittest.skipUnless(docutils, "no docutils installed.")
|
|
class AdminDocsTest(TestCase):
|
|
fixtures = ['admin-views-users.xml']
|
|
|
|
def setUp(self):
|
|
self.client.login(username='super', password='secret')
|
|
|
|
def tearDown(self):
|
|
self.client.logout()
|
|
|
|
def test_tags(self):
|
|
response = self.client.get('/test_admin/admin/doc/tags/')
|
|
|
|
# The builtin tag group exists
|
|
self.assertContains(response, "<h2>Built-in tags</h2>", count=2)
|
|
|
|
# A builtin tag exists in both the index and detail
|
|
self.assertContains(response, '<h3 id="built_in-autoescape">autoescape</h3>')
|
|
self.assertContains(response, '<li><a href="#built_in-autoescape">autoescape</a></li>')
|
|
|
|
# An app tag exists in both the index and detail
|
|
self.assertContains(response, '<h3 id="flatpages-get_flatpages">get_flatpages</h3>')
|
|
self.assertContains(response, '<li><a href="#flatpages-get_flatpages">get_flatpages</a></li>')
|
|
|
|
# The admin list tag group exists
|
|
self.assertContains(response, "<h2>admin_list</h2>", count=2)
|
|
|
|
# An admin list tag exists in both the index and detail
|
|
self.assertContains(response, '<h3 id="admin_list-admin_actions">admin_actions</h3>')
|
|
self.assertContains(response, '<li><a href="#admin_list-admin_actions">admin_actions</a></li>')
|
|
|
|
def test_filters(self):
|
|
response = self.client.get('/test_admin/admin/doc/filters/')
|
|
|
|
# The builtin filter group exists
|
|
self.assertContains(response, "<h2>Built-in filters</h2>", count=2)
|
|
|
|
# A builtin filter exists in both the index and detail
|
|
self.assertContains(response, '<h3 id="built_in-add">add</h3>')
|
|
self.assertContains(response, '<li><a href="#built_in-add">add</a></li>')
|
|
|
|
AdminDocsTest = unittest.skipUnless(docutils, "no docutils installed.")(AdminDocsTest)
|
|
|
|
class ValidXHTMLTests(TestCase):
|
|
fixtures = ['admin-views-users.xml']
|
|
urlbit = 'admin'
|
|
|
|
def setUp(self):
|
|
self._context_processors = None
|
|
self._use_i18n, settings.USE_I18N = settings.USE_I18N, False
|
|
if 'django.core.context_processors.i18n' in settings.TEMPLATE_CONTEXT_PROCESSORS:
|
|
self._context_processors = settings.TEMPLATE_CONTEXT_PROCESSORS
|
|
cp = list(settings.TEMPLATE_CONTEXT_PROCESSORS)
|
|
cp.remove('django.core.context_processors.i18n')
|
|
settings.TEMPLATE_CONTEXT_PROCESSORS = tuple(cp)
|
|
# Force re-evaluation of the contex processor list
|
|
django.template.context._standard_context_processors = None
|
|
self.client.login(username='super', password='secret')
|
|
|
|
def tearDown(self):
|
|
self.client.logout()
|
|
if self._context_processors is not None:
|
|
settings.TEMPLATE_CONTEXT_PROCESSORS = self._context_processors
|
|
# Force re-evaluation of the contex processor list
|
|
django.template.context._standard_context_processors = None
|
|
settings.USE_I18N = self._use_i18n
|
|
|
|
def testLangNamePresent(self):
|
|
response = self.client.get('/test_admin/%s/admin_views/' % self.urlbit)
|
|
self.assertFalse(' lang=""' in response.content)
|
|
self.assertFalse(' xml:lang=""' in response.content)
|
|
|
|
|
|
class DateHierarchyTests(TestCase):
|
|
fixtures = ['admin-views-users.xml']
|
|
|
|
def setUp(self):
|
|
self.client.login(username='super', password='secret')
|
|
self.old_USE_THOUSAND_SEPARATOR = settings.USE_THOUSAND_SEPARATOR
|
|
self.old_USE_L10N = settings.USE_L10N
|
|
settings.USE_THOUSAND_SEPARATOR = True
|
|
settings.USE_L10N = True
|
|
|
|
def tearDown(self):
|
|
settings.USE_THOUSAND_SEPARATOR = self.old_USE_THOUSAND_SEPARATOR
|
|
settings.USE_L10N = self.old_USE_L10N
|
|
formats.reset_format_cache()
|
|
|
|
def assert_non_localized_year(self, response, year):
|
|
"""Ensure that the year is not localized with
|
|
USE_THOUSAND_SEPARATOR. Refs #15234.
|
|
"""
|
|
self.assertNotContains(response, formats.number_format(year))
|
|
|
|
def assert_contains_year_link(self, response, date):
|
|
self.assertContains(response, '?release_date__year=%d"' % (date.year,))
|
|
|
|
def assert_contains_month_link(self, response, date):
|
|
self.assertContains(
|
|
response, '?release_date__year=%d&release_date__month=%d"' % (
|
|
date.year, date.month))
|
|
|
|
def assert_contains_day_link(self, response, date):
|
|
self.assertContains(
|
|
response, '?release_date__year=%d&'
|
|
'release_date__month=%d&release_date__day=%d"' % (
|
|
date.year, date.month, date.day))
|
|
|
|
def test_empty(self):
|
|
"""
|
|
Ensure that no date hierarchy links display with empty changelist.
|
|
"""
|
|
response = self.client.get(
|
|
reverse('admin:admin_views_podcast_changelist'))
|
|
self.assertNotContains(response, 'release_date__year=')
|
|
self.assertNotContains(response, 'release_date__month=')
|
|
self.assertNotContains(response, 'release_date__day=')
|
|
|
|
def test_single(self):
|
|
"""
|
|
Ensure that single day-level date hierarchy appears for single object.
|
|
"""
|
|
DATE = datetime.date(2000, 6, 30)
|
|
Podcast.objects.create(release_date=DATE)
|
|
url = reverse('admin:admin_views_podcast_changelist')
|
|
response = self.client.get(url)
|
|
self.assert_contains_day_link(response, DATE)
|
|
self.assert_non_localized_year(response, 2000)
|
|
|
|
def test_within_month(self):
|
|
"""
|
|
Ensure that day-level links appear for changelist within single month.
|
|
"""
|
|
DATES = (datetime.date(2000, 6, 30),
|
|
datetime.date(2000, 6, 15),
|
|
datetime.date(2000, 6, 3))
|
|
for date in DATES:
|
|
Podcast.objects.create(release_date=date)
|
|
url = reverse('admin:admin_views_podcast_changelist')
|
|
response = self.client.get(url)
|
|
for date in DATES:
|
|
self.assert_contains_day_link(response, date)
|
|
self.assert_non_localized_year(response, 2000)
|
|
|
|
def test_within_year(self):
|
|
"""
|
|
Ensure that month-level links appear for changelist within single year.
|
|
"""
|
|
DATES = (datetime.date(2000, 1, 30),
|
|
datetime.date(2000, 3, 15),
|
|
datetime.date(2000, 5, 3))
|
|
for date in DATES:
|
|
Podcast.objects.create(release_date=date)
|
|
url = reverse('admin:admin_views_podcast_changelist')
|
|
response = self.client.get(url)
|
|
# no day-level links
|
|
self.assertNotContains(response, 'release_date__day=')
|
|
for date in DATES:
|
|
self.assert_contains_month_link(response, date)
|
|
self.assert_non_localized_year(response, 2000)
|
|
|
|
def test_multiple_years(self):
|
|
"""
|
|
Ensure that year-level links appear for year-spanning changelist.
|
|
"""
|
|
DATES = (datetime.date(2001, 1, 30),
|
|
datetime.date(2003, 3, 15),
|
|
datetime.date(2005, 5, 3))
|
|
for date in DATES:
|
|
Podcast.objects.create(release_date=date)
|
|
response = self.client.get(
|
|
reverse('admin:admin_views_podcast_changelist'))
|
|
# no day/month-level links
|
|
self.assertNotContains(response, 'release_date__day=')
|
|
self.assertNotContains(response, 'release_date__month=')
|
|
for date in DATES:
|
|
self.assert_contains_year_link(response, date)
|
|
|
|
# and make sure GET parameters still behave correctly
|
|
for date in DATES:
|
|
url = '%s?release_date__year=%d' % (
|
|
reverse('admin:admin_views_podcast_changelist'),
|
|
date.year)
|
|
response = self.client.get(url)
|
|
self.assert_contains_month_link(response, date)
|
|
self.assert_non_localized_year(response, 2000)
|
|
self.assert_non_localized_year(response, 2003)
|
|
self.assert_non_localized_year(response, 2005)
|
|
|
|
url = '%s?release_date__year=%d&release_date__month=%d' % (
|
|
reverse('admin:admin_views_podcast_changelist'),
|
|
date.year, date.month)
|
|
response = self.client.get(url)
|
|
self.assert_contains_day_link(response, date)
|
|
self.assert_non_localized_year(response, 2000)
|
|
self.assert_non_localized_year(response, 2003)
|
|
self.assert_non_localized_year(response, 2005)
|