tests.py 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873
  1. from __future__ import unicode_literals
  2. import datetime
  3. from django.contrib.admin import (site, ModelAdmin, SimpleListFilter,
  4. BooleanFieldListFilter, AllValuesFieldListFilter)
  5. from django.contrib.admin.views.main import ChangeList
  6. from django.contrib.auth.admin import UserAdmin
  7. from django.contrib.auth.models import User
  8. from django.core.exceptions import ImproperlyConfigured
  9. from django.test import TestCase, RequestFactory
  10. from django.test.utils import override_settings, six
  11. from django.utils.encoding import force_text
  12. from .models import Book, Department, Employee
  13. def select_by(dictlist, key, value):
  14. return [x for x in dictlist if x[key] == value][0]
  15. class DecadeListFilter(SimpleListFilter):
  16. def lookups(self, request, model_admin):
  17. return (
  18. ('the 80s', "the 1980's"),
  19. ('the 90s', "the 1990's"),
  20. ('the 00s', "the 2000's"),
  21. ('other', "other decades"),
  22. )
  23. def queryset(self, request, queryset):
  24. decade = self.value()
  25. if decade == 'the 80s':
  26. return queryset.filter(year__gte=1980, year__lte=1989)
  27. if decade == 'the 90s':
  28. return queryset.filter(year__gte=1990, year__lte=1999)
  29. if decade == 'the 00s':
  30. return queryset.filter(year__gte=2000, year__lte=2009)
  31. class DecadeListFilterWithTitleAndParameter(DecadeListFilter):
  32. title = 'publication decade'
  33. parameter_name = 'publication-decade'
  34. class DecadeListFilterWithoutTitle(DecadeListFilter):
  35. parameter_name = 'publication-decade'
  36. class DecadeListFilterWithoutParameter(DecadeListFilter):
  37. title = 'publication decade'
  38. class DecadeListFilterWithNoneReturningLookups(DecadeListFilterWithTitleAndParameter):
  39. def lookups(self, request, model_admin):
  40. pass
  41. class DecadeListFilterWithFailingQueryset(DecadeListFilterWithTitleAndParameter):
  42. def queryset(self, request, queryset):
  43. raise 1 / 0
  44. class DecadeListFilterWithQuerysetBasedLookups(DecadeListFilterWithTitleAndParameter):
  45. def lookups(self, request, model_admin):
  46. qs = model_admin.get_queryset(request)
  47. if qs.filter(year__gte=1980, year__lte=1989).exists():
  48. yield ('the 80s', "the 1980's")
  49. if qs.filter(year__gte=1990, year__lte=1999).exists():
  50. yield ('the 90s', "the 1990's")
  51. if qs.filter(year__gte=2000, year__lte=2009).exists():
  52. yield ('the 00s', "the 2000's")
  53. class DecadeListFilterParameterEndsWith__In(DecadeListFilter):
  54. title = 'publication decade'
  55. parameter_name = 'decade__in' # Ends with '__in"
  56. class DecadeListFilterParameterEndsWith__Isnull(DecadeListFilter):
  57. title = 'publication decade'
  58. parameter_name = 'decade__isnull' # Ends with '__isnull"
  59. class DepartmentListFilterLookupWithNonStringValue(SimpleListFilter):
  60. title = 'department'
  61. parameter_name = 'department'
  62. def lookups(self, request, model_admin):
  63. return sorted(set([
  64. (employee.department.id, # Intentionally not a string (Refs #19318)
  65. employee.department.code)
  66. for employee in model_admin.get_queryset(request).all()
  67. ]))
  68. def queryset(self, request, queryset):
  69. if self.value():
  70. return queryset.filter(department__id=self.value())
  71. class DepartmentListFilterLookupWithUnderscoredParameter(DepartmentListFilterLookupWithNonStringValue):
  72. parameter_name = 'department__whatever'
  73. class DepartmentListFilterLookupWithDynamicValue(DecadeListFilterWithTitleAndParameter):
  74. def lookups(self, request, model_admin):
  75. if self.value() == 'the 80s':
  76. return (('the 90s', "the 1990's"),)
  77. elif self.value() == 'the 90s':
  78. return (('the 80s', "the 1980's"),)
  79. else:
  80. return (('the 80s', "the 1980's"), ('the 90s', "the 1990's"),)
  81. class CustomUserAdmin(UserAdmin):
  82. list_filter = ('books_authored', 'books_contributed')
  83. class BookAdmin(ModelAdmin):
  84. list_filter = ('year', 'author', 'contributors', 'is_best_seller', 'date_registered', 'no')
  85. ordering = ('-id',)
  86. class BookAdminWithTupleBooleanFilter(BookAdmin):
  87. list_filter = ('year', 'author', 'contributors', ('is_best_seller', BooleanFieldListFilter), 'date_registered', 'no')
  88. class BookAdminWithUnderscoreLookupAndTuple(BookAdmin):
  89. list_filter = ('year', ('author__email', AllValuesFieldListFilter), 'contributors', 'is_best_seller', 'date_registered', 'no')
  90. class DecadeFilterBookAdmin(ModelAdmin):
  91. list_filter = ('author', DecadeListFilterWithTitleAndParameter)
  92. ordering = ('-id',)
  93. class DecadeFilterBookAdminWithoutTitle(ModelAdmin):
  94. list_filter = (DecadeListFilterWithoutTitle,)
  95. class DecadeFilterBookAdminWithoutParameter(ModelAdmin):
  96. list_filter = (DecadeListFilterWithoutParameter,)
  97. class DecadeFilterBookAdminWithNoneReturningLookups(ModelAdmin):
  98. list_filter = (DecadeListFilterWithNoneReturningLookups,)
  99. class DecadeFilterBookAdminWithFailingQueryset(ModelAdmin):
  100. list_filter = (DecadeListFilterWithFailingQueryset,)
  101. class DecadeFilterBookAdminWithQuerysetBasedLookups(ModelAdmin):
  102. list_filter = (DecadeListFilterWithQuerysetBasedLookups,)
  103. class DecadeFilterBookAdminParameterEndsWith__In(ModelAdmin):
  104. list_filter = (DecadeListFilterParameterEndsWith__In,)
  105. class DecadeFilterBookAdminParameterEndsWith__Isnull(ModelAdmin):
  106. list_filter = (DecadeListFilterParameterEndsWith__Isnull,)
  107. class EmployeeAdmin(ModelAdmin):
  108. list_display = ['name', 'department']
  109. list_filter = ['department']
  110. class DepartmentFilterEmployeeAdmin(EmployeeAdmin):
  111. list_filter = [DepartmentListFilterLookupWithNonStringValue, ]
  112. class DepartmentFilterUnderscoredEmployeeAdmin(EmployeeAdmin):
  113. list_filter = [DepartmentListFilterLookupWithUnderscoredParameter, ]
  114. class DepartmentFilterDynamicValueBookAdmin(EmployeeAdmin):
  115. list_filter = [DepartmentListFilterLookupWithDynamicValue, ]
  116. class ListFiltersTests(TestCase):
  117. def setUp(self):
  118. self.today = datetime.date.today()
  119. self.tomorrow = self.today + datetime.timedelta(days=1)
  120. self.one_week_ago = self.today - datetime.timedelta(days=7)
  121. if self.today.month == 12:
  122. self.next_month = self.today.replace(year=self.today.year + 1, month=1, day=1)
  123. else:
  124. self.next_month = self.today.replace(month=self.today.month + 1, day=1)
  125. self.next_year = self.today.replace(year=self.today.year + 1, month=1, day=1)
  126. self.request_factory = RequestFactory()
  127. # Users
  128. self.alfred = User.objects.create_user('alfred', 'alfred@example.com')
  129. self.bob = User.objects.create_user('bob', 'bob@example.com')
  130. self.lisa = User.objects.create_user('lisa', 'lisa@example.com')
  131. # Books
  132. self.djangonaut_book = Book.objects.create(title='Djangonaut: an art of living', year=2009, author=self.alfred, is_best_seller=True, date_registered=self.today)
  133. self.bio_book = Book.objects.create(title='Django: a biography', year=1999, author=self.alfred, is_best_seller=False, no=207)
  134. self.django_book = Book.objects.create(title='The Django Book', year=None, author=self.bob, is_best_seller=None, date_registered=self.today, no=103)
  135. self.gipsy_book = Book.objects.create(title='Gipsy guitar for dummies', year=2002, is_best_seller=True, date_registered=self.one_week_ago)
  136. self.gipsy_book.contributors = [self.bob, self.lisa]
  137. self.gipsy_book.save()
  138. # Departments
  139. self.dev = Department.objects.create(code='DEV', description='Development')
  140. self.design = Department.objects.create(code='DSN', description='Design')
  141. # Employees
  142. self.john = Employee.objects.create(name='John Blue', department=self.dev)
  143. self.jack = Employee.objects.create(name='Jack Red', department=self.design)
  144. def get_changelist(self, request, model, modeladmin):
  145. return ChangeList(request, model, modeladmin.list_display, modeladmin.list_display_links,
  146. modeladmin.list_filter, modeladmin.date_hierarchy, modeladmin.search_fields,
  147. modeladmin.list_select_related, modeladmin.list_per_page, modeladmin.list_max_show_all, modeladmin.list_editable, modeladmin)
  148. def test_datefieldlistfilter(self):
  149. modeladmin = BookAdmin(Book, site)
  150. request = self.request_factory.get('/')
  151. changelist = self.get_changelist(request, Book, modeladmin)
  152. request = self.request_factory.get('/', {'date_registered__gte': self.today,
  153. 'date_registered__lt': self.tomorrow})
  154. changelist = self.get_changelist(request, Book, modeladmin)
  155. # Make sure the correct queryset is returned
  156. queryset = changelist.get_queryset(request)
  157. self.assertEqual(list(queryset), [self.django_book, self.djangonaut_book])
  158. # Make sure the correct choice is selected
  159. filterspec = changelist.get_filters(request)[0][4]
  160. self.assertEqual(force_text(filterspec.title), 'date registered')
  161. choice = select_by(filterspec.choices(changelist), "display", "Today")
  162. self.assertEqual(choice['selected'], True)
  163. self.assertEqual(
  164. choice['query_string'],
  165. '?date_registered__gte=%s&date_registered__lt=%s' % (
  166. self.today,
  167. self.tomorrow,
  168. )
  169. )
  170. request = self.request_factory.get('/', {'date_registered__gte': self.today.replace(day=1),
  171. 'date_registered__lt': self.next_month})
  172. changelist = self.get_changelist(request, Book, modeladmin)
  173. # Make sure the correct queryset is returned
  174. queryset = changelist.get_queryset(request)
  175. if (self.today.year, self.today.month) == (self.one_week_ago.year, self.one_week_ago.month):
  176. # In case one week ago is in the same month.
  177. self.assertEqual(list(queryset), [self.gipsy_book, self.django_book, self.djangonaut_book])
  178. else:
  179. self.assertEqual(list(queryset), [self.django_book, self.djangonaut_book])
  180. # Make sure the correct choice is selected
  181. filterspec = changelist.get_filters(request)[0][4]
  182. self.assertEqual(force_text(filterspec.title), 'date registered')
  183. choice = select_by(filterspec.choices(changelist), "display", "This month")
  184. self.assertEqual(choice['selected'], True)
  185. self.assertEqual(
  186. choice['query_string'],
  187. '?date_registered__gte=%s&date_registered__lt=%s' % (
  188. self.today.replace(day=1),
  189. self.next_month,
  190. )
  191. )
  192. request = self.request_factory.get('/', {'date_registered__gte': self.today.replace(month=1, day=1),
  193. 'date_registered__lt': self.next_year})
  194. changelist = self.get_changelist(request, Book, modeladmin)
  195. # Make sure the correct queryset is returned
  196. queryset = changelist.get_queryset(request)
  197. if self.today.year == self.one_week_ago.year:
  198. # In case one week ago is in the same year.
  199. self.assertEqual(list(queryset), [self.gipsy_book, self.django_book, self.djangonaut_book])
  200. else:
  201. self.assertEqual(list(queryset), [self.django_book, self.djangonaut_book])
  202. # Make sure the correct choice is selected
  203. filterspec = changelist.get_filters(request)[0][4]
  204. self.assertEqual(force_text(filterspec.title), 'date registered')
  205. choice = select_by(filterspec.choices(changelist), "display", "This year")
  206. self.assertEqual(choice['selected'], True)
  207. self.assertEqual(
  208. choice['query_string'],
  209. '?date_registered__gte=%s&date_registered__lt=%s' % (
  210. self.today.replace(month=1, day=1),
  211. self.next_year,
  212. )
  213. )
  214. request = self.request_factory.get('/', {
  215. 'date_registered__gte': str(self.one_week_ago),
  216. 'date_registered__lt': str(self.tomorrow),
  217. })
  218. changelist = self.get_changelist(request, Book, modeladmin)
  219. # Make sure the correct queryset is returned
  220. queryset = changelist.get_queryset(request)
  221. self.assertEqual(list(queryset), [self.gipsy_book, self.django_book, self.djangonaut_book])
  222. # Make sure the correct choice is selected
  223. filterspec = changelist.get_filters(request)[0][4]
  224. self.assertEqual(force_text(filterspec.title), 'date registered')
  225. choice = select_by(filterspec.choices(changelist), "display", "Past 7 days")
  226. self.assertEqual(choice['selected'], True)
  227. self.assertEqual(
  228. choice['query_string'],
  229. '?date_registered__gte=%s&date_registered__lt=%s' % (
  230. str(self.one_week_ago),
  231. str(self.tomorrow),
  232. )
  233. )
  234. @override_settings(USE_TZ=True)
  235. def test_datefieldlistfilter_with_time_zone_support(self):
  236. # Regression for #17830
  237. self.test_datefieldlistfilter()
  238. def test_allvaluesfieldlistfilter(self):
  239. modeladmin = BookAdmin(Book, site)
  240. request = self.request_factory.get('/', {'year__isnull': 'True'})
  241. changelist = self.get_changelist(request, Book, modeladmin)
  242. # Make sure the correct queryset is returned
  243. queryset = changelist.get_queryset(request)
  244. self.assertEqual(list(queryset), [self.django_book])
  245. # Make sure the last choice is None and is selected
  246. filterspec = changelist.get_filters(request)[0][0]
  247. self.assertEqual(force_text(filterspec.title), 'year')
  248. choices = list(filterspec.choices(changelist))
  249. self.assertEqual(choices[-1]['selected'], True)
  250. self.assertEqual(choices[-1]['query_string'], '?year__isnull=True')
  251. request = self.request_factory.get('/', {'year': '2002'})
  252. changelist = self.get_changelist(request, Book, modeladmin)
  253. # Make sure the correct choice is selected
  254. filterspec = changelist.get_filters(request)[0][0]
  255. self.assertEqual(force_text(filterspec.title), 'year')
  256. choices = list(filterspec.choices(changelist))
  257. self.assertEqual(choices[2]['selected'], True)
  258. self.assertEqual(choices[2]['query_string'], '?year=2002')
  259. def test_relatedfieldlistfilter_foreignkey(self):
  260. modeladmin = BookAdmin(Book, site)
  261. request = self.request_factory.get('/', {'author__isnull': 'True'})
  262. changelist = self.get_changelist(request, Book, modeladmin)
  263. # Make sure the correct queryset is returned
  264. queryset = changelist.get_queryset(request)
  265. self.assertEqual(list(queryset), [self.gipsy_book])
  266. # Make sure the last choice is None and is selected
  267. filterspec = changelist.get_filters(request)[0][1]
  268. self.assertEqual(force_text(filterspec.title), 'Verbose Author')
  269. choices = list(filterspec.choices(changelist))
  270. self.assertEqual(choices[-1]['selected'], True)
  271. self.assertEqual(choices[-1]['query_string'], '?author__isnull=True')
  272. request = self.request_factory.get('/', {'author__id__exact': self.alfred.pk})
  273. changelist = self.get_changelist(request, Book, modeladmin)
  274. # Make sure the correct choice is selected
  275. filterspec = changelist.get_filters(request)[0][1]
  276. self.assertEqual(force_text(filterspec.title), 'Verbose Author')
  277. # order of choices depends on User model, which has no order
  278. choice = select_by(filterspec.choices(changelist), "display", "alfred")
  279. self.assertEqual(choice['selected'], True)
  280. self.assertEqual(choice['query_string'], '?author__id__exact=%d' % self.alfred.pk)
  281. def test_relatedfieldlistfilter_manytomany(self):
  282. modeladmin = BookAdmin(Book, site)
  283. request = self.request_factory.get('/', {'contributors__isnull': 'True'})
  284. changelist = self.get_changelist(request, Book, modeladmin)
  285. # Make sure the correct queryset is returned
  286. queryset = changelist.get_queryset(request)
  287. self.assertEqual(list(queryset), [self.django_book, self.bio_book, self.djangonaut_book])
  288. # Make sure the last choice is None and is selected
  289. filterspec = changelist.get_filters(request)[0][2]
  290. self.assertEqual(force_text(filterspec.title), 'Verbose Contributors')
  291. choices = list(filterspec.choices(changelist))
  292. self.assertEqual(choices[-1]['selected'], True)
  293. self.assertEqual(choices[-1]['query_string'], '?contributors__isnull=True')
  294. request = self.request_factory.get('/', {'contributors__id__exact': self.bob.pk})
  295. changelist = self.get_changelist(request, Book, modeladmin)
  296. # Make sure the correct choice is selected
  297. filterspec = changelist.get_filters(request)[0][2]
  298. self.assertEqual(force_text(filterspec.title), 'Verbose Contributors')
  299. choice = select_by(filterspec.choices(changelist), "display", "bob")
  300. self.assertEqual(choice['selected'], True)
  301. self.assertEqual(choice['query_string'], '?contributors__id__exact=%d' % self.bob.pk)
  302. def test_relatedfieldlistfilter_reverse_relationships(self):
  303. modeladmin = CustomUserAdmin(User, site)
  304. # FK relationship -----
  305. request = self.request_factory.get('/', {'books_authored__isnull': 'True'})
  306. changelist = self.get_changelist(request, User, modeladmin)
  307. # Make sure the correct queryset is returned
  308. queryset = changelist.get_queryset(request)
  309. self.assertEqual(list(queryset), [self.lisa])
  310. # Make sure the last choice is None and is selected
  311. filterspec = changelist.get_filters(request)[0][0]
  312. self.assertEqual(force_text(filterspec.title), 'book')
  313. choices = list(filterspec.choices(changelist))
  314. self.assertEqual(choices[-1]['selected'], True)
  315. self.assertEqual(choices[-1]['query_string'], '?books_authored__isnull=True')
  316. request = self.request_factory.get('/', {'books_authored__id__exact': self.bio_book.pk})
  317. changelist = self.get_changelist(request, User, modeladmin)
  318. # Make sure the correct choice is selected
  319. filterspec = changelist.get_filters(request)[0][0]
  320. self.assertEqual(force_text(filterspec.title), 'book')
  321. choice = select_by(filterspec.choices(changelist), "display", self.bio_book.title)
  322. self.assertEqual(choice['selected'], True)
  323. self.assertEqual(choice['query_string'], '?books_authored__id__exact=%d' % self.bio_book.pk)
  324. # M2M relationship -----
  325. request = self.request_factory.get('/', {'books_contributed__isnull': 'True'})
  326. changelist = self.get_changelist(request, User, modeladmin)
  327. # Make sure the correct queryset is returned
  328. queryset = changelist.get_queryset(request)
  329. self.assertEqual(list(queryset), [self.alfred])
  330. # Make sure the last choice is None and is selected
  331. filterspec = changelist.get_filters(request)[0][1]
  332. self.assertEqual(force_text(filterspec.title), 'book')
  333. choices = list(filterspec.choices(changelist))
  334. self.assertEqual(choices[-1]['selected'], True)
  335. self.assertEqual(choices[-1]['query_string'], '?books_contributed__isnull=True')
  336. request = self.request_factory.get('/', {'books_contributed__id__exact': self.django_book.pk})
  337. changelist = self.get_changelist(request, User, modeladmin)
  338. # Make sure the correct choice is selected
  339. filterspec = changelist.get_filters(request)[0][1]
  340. self.assertEqual(force_text(filterspec.title), 'book')
  341. choice = select_by(filterspec.choices(changelist), "display", self.django_book.title)
  342. self.assertEqual(choice['selected'], True)
  343. self.assertEqual(choice['query_string'], '?books_contributed__id__exact=%d' % self.django_book.pk)
  344. def test_booleanfieldlistfilter(self):
  345. modeladmin = BookAdmin(Book, site)
  346. self.verify_booleanfieldlistfilter(modeladmin)
  347. def test_booleanfieldlistfilter_tuple(self):
  348. modeladmin = BookAdminWithTupleBooleanFilter(Book, site)
  349. self.verify_booleanfieldlistfilter(modeladmin)
  350. def verify_booleanfieldlistfilter(self, modeladmin):
  351. request = self.request_factory.get('/')
  352. changelist = self.get_changelist(request, Book, modeladmin)
  353. request = self.request_factory.get('/', {'is_best_seller__exact': 0})
  354. changelist = self.get_changelist(request, Book, modeladmin)
  355. # Make sure the correct queryset is returned
  356. queryset = changelist.get_queryset(request)
  357. self.assertEqual(list(queryset), [self.bio_book])
  358. # Make sure the correct choice is selected
  359. filterspec = changelist.get_filters(request)[0][3]
  360. self.assertEqual(force_text(filterspec.title), 'is best seller')
  361. choice = select_by(filterspec.choices(changelist), "display", "No")
  362. self.assertEqual(choice['selected'], True)
  363. self.assertEqual(choice['query_string'], '?is_best_seller__exact=0')
  364. request = self.request_factory.get('/', {'is_best_seller__exact': 1})
  365. changelist = self.get_changelist(request, Book, modeladmin)
  366. # Make sure the correct queryset is returned
  367. queryset = changelist.get_queryset(request)
  368. self.assertEqual(list(queryset), [self.gipsy_book, self.djangonaut_book])
  369. # Make sure the correct choice is selected
  370. filterspec = changelist.get_filters(request)[0][3]
  371. self.assertEqual(force_text(filterspec.title), 'is best seller')
  372. choice = select_by(filterspec.choices(changelist), "display", "Yes")
  373. self.assertEqual(choice['selected'], True)
  374. self.assertEqual(choice['query_string'], '?is_best_seller__exact=1')
  375. request = self.request_factory.get('/', {'is_best_seller__isnull': 'True'})
  376. changelist = self.get_changelist(request, Book, modeladmin)
  377. # Make sure the correct queryset is returned
  378. queryset = changelist.get_queryset(request)
  379. self.assertEqual(list(queryset), [self.django_book])
  380. # Make sure the correct choice is selected
  381. filterspec = changelist.get_filters(request)[0][3]
  382. self.assertEqual(force_text(filterspec.title), 'is best seller')
  383. choice = select_by(filterspec.choices(changelist), "display", "Unknown")
  384. self.assertEqual(choice['selected'], True)
  385. self.assertEqual(choice['query_string'], '?is_best_seller__isnull=True')
  386. def test_fieldlistfilter_underscorelookup_tuple(self):
  387. """
  388. Ensure ('fieldpath', ClassName ) lookups pass lookup_allowed checks
  389. when fieldpath contains double underscore in value.
  390. Refs #19182
  391. """
  392. modeladmin = BookAdminWithUnderscoreLookupAndTuple(Book, site)
  393. request = self.request_factory.get('/')
  394. changelist = self.get_changelist(request, Book, modeladmin)
  395. request = self.request_factory.get('/', {'author__email': 'alfred@example.com'})
  396. changelist = self.get_changelist(request, Book, modeladmin)
  397. # Make sure the correct queryset is returned
  398. queryset = changelist.get_queryset(request)
  399. self.assertEqual(list(queryset), [self.bio_book, self.djangonaut_book])
  400. def test_simplelistfilter(self):
  401. modeladmin = DecadeFilterBookAdmin(Book, site)
  402. # Make sure that the first option is 'All' ---------------------------
  403. request = self.request_factory.get('/', {})
  404. changelist = self.get_changelist(request, Book, modeladmin)
  405. # Make sure the correct queryset is returned
  406. queryset = changelist.get_queryset(request)
  407. self.assertEqual(list(queryset), list(Book.objects.all().order_by('-id')))
  408. # Make sure the correct choice is selected
  409. filterspec = changelist.get_filters(request)[0][1]
  410. self.assertEqual(force_text(filterspec.title), 'publication decade')
  411. choices = list(filterspec.choices(changelist))
  412. self.assertEqual(choices[0]['display'], 'All')
  413. self.assertEqual(choices[0]['selected'], True)
  414. self.assertEqual(choices[0]['query_string'], '?')
  415. # Look for books in the 1980s ----------------------------------------
  416. request = self.request_factory.get('/', {'publication-decade': 'the 80s'})
  417. changelist = self.get_changelist(request, Book, modeladmin)
  418. # Make sure the correct queryset is returned
  419. queryset = changelist.get_queryset(request)
  420. self.assertEqual(list(queryset), [])
  421. # Make sure the correct choice is selected
  422. filterspec = changelist.get_filters(request)[0][1]
  423. self.assertEqual(force_text(filterspec.title), 'publication decade')
  424. choices = list(filterspec.choices(changelist))
  425. self.assertEqual(choices[1]['display'], 'the 1980\'s')
  426. self.assertEqual(choices[1]['selected'], True)
  427. self.assertEqual(choices[1]['query_string'], '?publication-decade=the+80s')
  428. # Look for books in the 1990s ----------------------------------------
  429. request = self.request_factory.get('/', {'publication-decade': 'the 90s'})
  430. changelist = self.get_changelist(request, Book, modeladmin)
  431. # Make sure the correct queryset is returned
  432. queryset = changelist.get_queryset(request)
  433. self.assertEqual(list(queryset), [self.bio_book])
  434. # Make sure the correct choice is selected
  435. filterspec = changelist.get_filters(request)[0][1]
  436. self.assertEqual(force_text(filterspec.title), 'publication decade')
  437. choices = list(filterspec.choices(changelist))
  438. self.assertEqual(choices[2]['display'], 'the 1990\'s')
  439. self.assertEqual(choices[2]['selected'], True)
  440. self.assertEqual(choices[2]['query_string'], '?publication-decade=the+90s')
  441. # Look for books in the 2000s ----------------------------------------
  442. request = self.request_factory.get('/', {'publication-decade': 'the 00s'})
  443. changelist = self.get_changelist(request, Book, modeladmin)
  444. # Make sure the correct queryset is returned
  445. queryset = changelist.get_queryset(request)
  446. self.assertEqual(list(queryset), [self.gipsy_book, self.djangonaut_book])
  447. # Make sure the correct choice is selected
  448. filterspec = changelist.get_filters(request)[0][1]
  449. self.assertEqual(force_text(filterspec.title), 'publication decade')
  450. choices = list(filterspec.choices(changelist))
  451. self.assertEqual(choices[3]['display'], 'the 2000\'s')
  452. self.assertEqual(choices[3]['selected'], True)
  453. self.assertEqual(choices[3]['query_string'], '?publication-decade=the+00s')
  454. # Combine multiple filters -------------------------------------------
  455. request = self.request_factory.get('/', {'publication-decade': 'the 00s', 'author__id__exact': self.alfred.pk})
  456. changelist = self.get_changelist(request, Book, modeladmin)
  457. # Make sure the correct queryset is returned
  458. queryset = changelist.get_queryset(request)
  459. self.assertEqual(list(queryset), [self.djangonaut_book])
  460. # Make sure the correct choices are selected
  461. filterspec = changelist.get_filters(request)[0][1]
  462. self.assertEqual(force_text(filterspec.title), 'publication decade')
  463. choices = list(filterspec.choices(changelist))
  464. self.assertEqual(choices[3]['display'], 'the 2000\'s')
  465. self.assertEqual(choices[3]['selected'], True)
  466. self.assertEqual(choices[3]['query_string'], '?author__id__exact=%s&publication-decade=the+00s' % self.alfred.pk)
  467. filterspec = changelist.get_filters(request)[0][0]
  468. self.assertEqual(force_text(filterspec.title), 'Verbose Author')
  469. choice = select_by(filterspec.choices(changelist), "display", "alfred")
  470. self.assertEqual(choice['selected'], True)
  471. self.assertEqual(choice['query_string'], '?author__id__exact=%s&publication-decade=the+00s' % self.alfred.pk)
  472. def test_listfilter_without_title(self):
  473. """
  474. Any filter must define a title.
  475. """
  476. modeladmin = DecadeFilterBookAdminWithoutTitle(Book, site)
  477. request = self.request_factory.get('/', {})
  478. six.assertRaisesRegex(self, ImproperlyConfigured,
  479. "The list filter 'DecadeListFilterWithoutTitle' does not specify a 'title'.",
  480. self.get_changelist, request, Book, modeladmin)
  481. def test_simplelistfilter_without_parameter(self):
  482. """
  483. Any SimpleListFilter must define a parameter_name.
  484. """
  485. modeladmin = DecadeFilterBookAdminWithoutParameter(Book, site)
  486. request = self.request_factory.get('/', {})
  487. six.assertRaisesRegex(self, ImproperlyConfigured,
  488. "The list filter 'DecadeListFilterWithoutParameter' does not specify a 'parameter_name'.",
  489. self.get_changelist, request, Book, modeladmin)
  490. def test_simplelistfilter_with_none_returning_lookups(self):
  491. """
  492. A SimpleListFilter lookups method can return None but disables the
  493. filter completely.
  494. """
  495. modeladmin = DecadeFilterBookAdminWithNoneReturningLookups(Book, site)
  496. request = self.request_factory.get('/', {})
  497. changelist = self.get_changelist(request, Book, modeladmin)
  498. filterspec = changelist.get_filters(request)[0]
  499. self.assertEqual(len(filterspec), 0)
  500. def test_filter_with_failing_queryset(self):
  501. """
  502. Ensure that when a filter's queryset method fails, it fails loudly and
  503. the corresponding exception doesn't get swallowed.
  504. Refs #17828.
  505. """
  506. modeladmin = DecadeFilterBookAdminWithFailingQueryset(Book, site)
  507. request = self.request_factory.get('/', {})
  508. self.assertRaises(ZeroDivisionError, self.get_changelist, request, Book, modeladmin)
  509. def test_simplelistfilter_with_queryset_based_lookups(self):
  510. modeladmin = DecadeFilterBookAdminWithQuerysetBasedLookups(Book, site)
  511. request = self.request_factory.get('/', {})
  512. changelist = self.get_changelist(request, Book, modeladmin)
  513. filterspec = changelist.get_filters(request)[0][0]
  514. self.assertEqual(force_text(filterspec.title), 'publication decade')
  515. choices = list(filterspec.choices(changelist))
  516. self.assertEqual(len(choices), 3)
  517. self.assertEqual(choices[0]['display'], 'All')
  518. self.assertEqual(choices[0]['selected'], True)
  519. self.assertEqual(choices[0]['query_string'], '?')
  520. self.assertEqual(choices[1]['display'], 'the 1990\'s')
  521. self.assertEqual(choices[1]['selected'], False)
  522. self.assertEqual(choices[1]['query_string'], '?publication-decade=the+90s')
  523. self.assertEqual(choices[2]['display'], 'the 2000\'s')
  524. self.assertEqual(choices[2]['selected'], False)
  525. self.assertEqual(choices[2]['query_string'], '?publication-decade=the+00s')
  526. def test_two_characters_long_field(self):
  527. """
  528. Ensure that list_filter works with two-characters long field names.
  529. Refs #16080.
  530. """
  531. modeladmin = BookAdmin(Book, site)
  532. request = self.request_factory.get('/', {'no': '207'})
  533. changelist = self.get_changelist(request, Book, modeladmin)
  534. # Make sure the correct queryset is returned
  535. queryset = changelist.get_queryset(request)
  536. self.assertEqual(list(queryset), [self.bio_book])
  537. filterspec = changelist.get_filters(request)[0][-1]
  538. self.assertEqual(force_text(filterspec.title), 'number')
  539. choices = list(filterspec.choices(changelist))
  540. self.assertEqual(choices[2]['selected'], True)
  541. self.assertEqual(choices[2]['query_string'], '?no=207')
  542. def test_parameter_ends_with__in__or__isnull(self):
  543. """
  544. Ensure that a SimpleListFilter's parameter name is not mistaken for a
  545. model field if it ends with '__isnull' or '__in'.
  546. Refs #17091.
  547. """
  548. # When it ends with '__in' -----------------------------------------
  549. modeladmin = DecadeFilterBookAdminParameterEndsWith__In(Book, site)
  550. request = self.request_factory.get('/', {'decade__in': 'the 90s'})
  551. changelist = self.get_changelist(request, Book, modeladmin)
  552. # Make sure the correct queryset is returned
  553. queryset = changelist.get_queryset(request)
  554. self.assertEqual(list(queryset), [self.bio_book])
  555. # Make sure the correct choice is selected
  556. filterspec = changelist.get_filters(request)[0][0]
  557. self.assertEqual(force_text(filterspec.title), 'publication decade')
  558. choices = list(filterspec.choices(changelist))
  559. self.assertEqual(choices[2]['display'], 'the 1990\'s')
  560. self.assertEqual(choices[2]['selected'], True)
  561. self.assertEqual(choices[2]['query_string'], '?decade__in=the+90s')
  562. # When it ends with '__isnull' ---------------------------------------
  563. modeladmin = DecadeFilterBookAdminParameterEndsWith__Isnull(Book, site)
  564. request = self.request_factory.get('/', {'decade__isnull': 'the 90s'})
  565. changelist = self.get_changelist(request, Book, modeladmin)
  566. # Make sure the correct queryset is returned
  567. queryset = changelist.get_queryset(request)
  568. self.assertEqual(list(queryset), [self.bio_book])
  569. # Make sure the correct choice is selected
  570. filterspec = changelist.get_filters(request)[0][0]
  571. self.assertEqual(force_text(filterspec.title), 'publication decade')
  572. choices = list(filterspec.choices(changelist))
  573. self.assertEqual(choices[2]['display'], 'the 1990\'s')
  574. self.assertEqual(choices[2]['selected'], True)
  575. self.assertEqual(choices[2]['query_string'], '?decade__isnull=the+90s')
  576. def test_lookup_with_non_string_value(self):
  577. """
  578. Ensure choices are set the selected class when using non-string values
  579. for lookups in SimpleListFilters.
  580. Refs #19318
  581. """
  582. modeladmin = DepartmentFilterEmployeeAdmin(Employee, site)
  583. request = self.request_factory.get('/', {'department': self.john.pk})
  584. changelist = self.get_changelist(request, Employee, modeladmin)
  585. queryset = changelist.get_queryset(request)
  586. self.assertEqual(list(queryset), [self.john])
  587. filterspec = changelist.get_filters(request)[0][-1]
  588. self.assertEqual(force_text(filterspec.title), 'department')
  589. choices = list(filterspec.choices(changelist))
  590. self.assertEqual(choices[1]['display'], 'DEV')
  591. self.assertEqual(choices[1]['selected'], True)
  592. self.assertEqual(choices[1]['query_string'], '?department=%s' % self.john.pk)
  593. def test_lookup_with_non_string_value_underscored(self):
  594. """
  595. Ensure SimpleListFilter lookups pass lookup_allowed checks when
  596. parameter_name attribute contains double-underscore value.
  597. Refs #19182
  598. """
  599. modeladmin = DepartmentFilterUnderscoredEmployeeAdmin(Employee, site)
  600. request = self.request_factory.get('/', {'department__whatever': self.john.pk})
  601. changelist = self.get_changelist(request, Employee, modeladmin)
  602. queryset = changelist.get_queryset(request)
  603. self.assertEqual(list(queryset), [self.john])
  604. filterspec = changelist.get_filters(request)[0][-1]
  605. self.assertEqual(force_text(filterspec.title), 'department')
  606. choices = list(filterspec.choices(changelist))
  607. self.assertEqual(choices[1]['display'], 'DEV')
  608. self.assertEqual(choices[1]['selected'], True)
  609. self.assertEqual(choices[1]['query_string'], '?department__whatever=%s' % self.john.pk)
  610. def test_fk_with_to_field(self):
  611. """
  612. Ensure that a filter on a FK respects the FK's to_field attribute.
  613. Refs #17972.
  614. """
  615. modeladmin = EmployeeAdmin(Employee, site)
  616. request = self.request_factory.get('/', {})
  617. changelist = self.get_changelist(request, Employee, modeladmin)
  618. # Make sure the correct queryset is returned
  619. queryset = changelist.get_queryset(request)
  620. self.assertEqual(list(queryset), [self.jack, self.john])
  621. filterspec = changelist.get_filters(request)[0][-1]
  622. self.assertEqual(force_text(filterspec.title), 'department')
  623. choices = list(filterspec.choices(changelist))
  624. self.assertEqual(choices[0]['display'], 'All')
  625. self.assertEqual(choices[0]['selected'], True)
  626. self.assertEqual(choices[0]['query_string'], '?')
  627. self.assertEqual(choices[1]['display'], 'Development')
  628. self.assertEqual(choices[1]['selected'], False)
  629. self.assertEqual(choices[1]['query_string'], '?department__code__exact=DEV')
  630. self.assertEqual(choices[2]['display'], 'Design')
  631. self.assertEqual(choices[2]['selected'], False)
  632. self.assertEqual(choices[2]['query_string'], '?department__code__exact=DSN')
  633. # Filter by Department=='Development' --------------------------------
  634. request = self.request_factory.get('/', {'department__code__exact': 'DEV'})
  635. changelist = self.get_changelist(request, Employee, modeladmin)
  636. # Make sure the correct queryset is returned
  637. queryset = changelist.get_queryset(request)
  638. self.assertEqual(list(queryset), [self.john])
  639. filterspec = changelist.get_filters(request)[0][-1]
  640. self.assertEqual(force_text(filterspec.title), 'department')
  641. choices = list(filterspec.choices(changelist))
  642. self.assertEqual(choices[0]['display'], 'All')
  643. self.assertEqual(choices[0]['selected'], False)
  644. self.assertEqual(choices[0]['query_string'], '?')
  645. self.assertEqual(choices[1]['display'], 'Development')
  646. self.assertEqual(choices[1]['selected'], True)
  647. self.assertEqual(choices[1]['query_string'], '?department__code__exact=DEV')
  648. self.assertEqual(choices[2]['display'], 'Design')
  649. self.assertEqual(choices[2]['selected'], False)
  650. self.assertEqual(choices[2]['query_string'], '?department__code__exact=DSN')
  651. def test_lookup_with_dynamic_value(self):
  652. """
  653. Ensure SimpleListFilter can access self.value() inside the lookup.
  654. """
  655. modeladmin = DepartmentFilterDynamicValueBookAdmin(Book, site)
  656. def _test_choices(request, expected_displays):
  657. changelist = self.get_changelist(request, Book, modeladmin)
  658. filterspec = changelist.get_filters(request)[0][0]
  659. self.assertEqual(force_text(filterspec.title), 'publication decade')
  660. choices = tuple(c['display'] for c in filterspec.choices(changelist))
  661. self.assertEqual(choices, expected_displays)
  662. _test_choices(self.request_factory.get('/', {}),
  663. ("All", "the 1980's", "the 1990's"))
  664. _test_choices(self.request_factory.get('/', {'publication-decade': 'the 80s'}),
  665. ("All", "the 1990's"))
  666. _test_choices(self.request_factory.get('/', {'publication-decade': 'the 90s'}),
  667. ("All", "the 1980's"))