tests.py 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106
  1. from __future__ import unicode_literals
  2. import datetime
  3. import sys
  4. import unittest
  5. from django.contrib.admin import (
  6. AllValuesFieldListFilter, BooleanFieldListFilter, ModelAdmin,
  7. RelatedOnlyFieldListFilter, SimpleListFilter, site,
  8. )
  9. from django.contrib.admin.views.main import ChangeList
  10. from django.contrib.auth.admin import UserAdmin
  11. from django.contrib.auth.models import User
  12. from django.core.exceptions import ImproperlyConfigured
  13. from django.test import RequestFactory, TestCase, override_settings
  14. from django.utils.encoding import force_text
  15. from .models import Book, Bookmark, Department, Employee, TaggedItem
  16. def select_by(dictlist, key, value):
  17. return [x for x in dictlist if x[key] == value][0]
  18. class DecadeListFilter(SimpleListFilter):
  19. def lookups(self, request, model_admin):
  20. return (
  21. ('the 80s', "the 1980's"),
  22. ('the 90s', "the 1990's"),
  23. ('the 00s', "the 2000's"),
  24. ('other', "other decades"),
  25. )
  26. def queryset(self, request, queryset):
  27. decade = self.value()
  28. if decade == 'the 80s':
  29. return queryset.filter(year__gte=1980, year__lte=1989)
  30. if decade == 'the 90s':
  31. return queryset.filter(year__gte=1990, year__lte=1999)
  32. if decade == 'the 00s':
  33. return queryset.filter(year__gte=2000, year__lte=2009)
  34. class NotNinetiesListFilter(SimpleListFilter):
  35. title = "Not nineties books"
  36. parameter_name = "book_year"
  37. def lookups(self, request, model_admin):
  38. return (
  39. ('the 90s', "the 1990's"),
  40. )
  41. def queryset(self, request, queryset):
  42. if self.value() == 'the 90s':
  43. return queryset.filter(year__gte=1990, year__lte=1999)
  44. else:
  45. return queryset.exclude(year__gte=1990, year__lte=1999)
  46. class DecadeListFilterWithTitleAndParameter(DecadeListFilter):
  47. title = 'publication decade'
  48. parameter_name = 'publication-decade'
  49. class DecadeListFilterWithoutTitle(DecadeListFilter):
  50. parameter_name = 'publication-decade'
  51. class DecadeListFilterWithoutParameter(DecadeListFilter):
  52. title = 'publication decade'
  53. class DecadeListFilterWithNoneReturningLookups(DecadeListFilterWithTitleAndParameter):
  54. def lookups(self, request, model_admin):
  55. pass
  56. class DecadeListFilterWithFailingQueryset(DecadeListFilterWithTitleAndParameter):
  57. def queryset(self, request, queryset):
  58. raise 1 / 0
  59. class DecadeListFilterWithQuerysetBasedLookups(DecadeListFilterWithTitleAndParameter):
  60. def lookups(self, request, model_admin):
  61. qs = model_admin.get_queryset(request)
  62. if qs.filter(year__gte=1980, year__lte=1989).exists():
  63. yield ('the 80s', "the 1980's")
  64. if qs.filter(year__gte=1990, year__lte=1999).exists():
  65. yield ('the 90s', "the 1990's")
  66. if qs.filter(year__gte=2000, year__lte=2009).exists():
  67. yield ('the 00s', "the 2000's")
  68. class DecadeListFilterParameterEndsWith__In(DecadeListFilter):
  69. title = 'publication decade'
  70. parameter_name = 'decade__in' # Ends with '__in"
  71. class DecadeListFilterParameterEndsWith__Isnull(DecadeListFilter):
  72. title = 'publication decade'
  73. parameter_name = 'decade__isnull' # Ends with '__isnull"
  74. class DepartmentListFilterLookupWithNonStringValue(SimpleListFilter):
  75. title = 'department'
  76. parameter_name = 'department'
  77. def lookups(self, request, model_admin):
  78. return sorted({
  79. (employee.department.id, # Intentionally not a string (Refs #19318)
  80. employee.department.code)
  81. for employee in model_admin.get_queryset(request).all()
  82. })
  83. def queryset(self, request, queryset):
  84. if self.value():
  85. return queryset.filter(department__id=self.value())
  86. class DepartmentListFilterLookupWithUnderscoredParameter(DepartmentListFilterLookupWithNonStringValue):
  87. parameter_name = 'department__whatever'
  88. class DepartmentListFilterLookupWithDynamicValue(DecadeListFilterWithTitleAndParameter):
  89. def lookups(self, request, model_admin):
  90. if self.value() == 'the 80s':
  91. return (('the 90s', "the 1990's"),)
  92. elif self.value() == 'the 90s':
  93. return (('the 80s', "the 1980's"),)
  94. else:
  95. return (('the 80s', "the 1980's"), ('the 90s', "the 1990's"),)
  96. class CustomUserAdmin(UserAdmin):
  97. list_filter = ('books_authored', 'books_contributed')
  98. class BookAdmin(ModelAdmin):
  99. list_filter = ('year', 'author', 'contributors', 'is_best_seller', 'date_registered', 'no')
  100. ordering = ('-id',)
  101. class BookAdminWithTupleBooleanFilter(BookAdmin):
  102. list_filter = (
  103. 'year',
  104. 'author',
  105. 'contributors',
  106. ('is_best_seller', BooleanFieldListFilter),
  107. 'date_registered',
  108. 'no',
  109. )
  110. class BookAdminWithUnderscoreLookupAndTuple(BookAdmin):
  111. list_filter = (
  112. 'year',
  113. ('author__email', AllValuesFieldListFilter),
  114. 'contributors',
  115. 'is_best_seller',
  116. 'date_registered',
  117. 'no',
  118. )
  119. class BookAdminWithCustomQueryset(ModelAdmin):
  120. def __init__(self, user, *args, **kwargs):
  121. self.user = user
  122. super(BookAdminWithCustomQueryset, self).__init__(*args, **kwargs)
  123. list_filter = ('year',)
  124. def get_queryset(self, request):
  125. return super(BookAdminWithCustomQueryset, self).get_queryset(request).filter(author=self.user)
  126. class BookAdminRelatedOnlyFilter(ModelAdmin):
  127. list_filter = (
  128. 'year', 'is_best_seller', 'date_registered', 'no',
  129. ('author', RelatedOnlyFieldListFilter),
  130. ('contributors', RelatedOnlyFieldListFilter),
  131. ('employee__department', RelatedOnlyFieldListFilter),
  132. )
  133. ordering = ('-id',)
  134. class DecadeFilterBookAdmin(ModelAdmin):
  135. list_filter = ('author', DecadeListFilterWithTitleAndParameter)
  136. ordering = ('-id',)
  137. class NotNinetiesListFilterAdmin(ModelAdmin):
  138. list_filter = (NotNinetiesListFilter,)
  139. class DecadeFilterBookAdminWithoutTitle(ModelAdmin):
  140. list_filter = (DecadeListFilterWithoutTitle,)
  141. class DecadeFilterBookAdminWithoutParameter(ModelAdmin):
  142. list_filter = (DecadeListFilterWithoutParameter,)
  143. class DecadeFilterBookAdminWithNoneReturningLookups(ModelAdmin):
  144. list_filter = (DecadeListFilterWithNoneReturningLookups,)
  145. class DecadeFilterBookAdminWithFailingQueryset(ModelAdmin):
  146. list_filter = (DecadeListFilterWithFailingQueryset,)
  147. class DecadeFilterBookAdminWithQuerysetBasedLookups(ModelAdmin):
  148. list_filter = (DecadeListFilterWithQuerysetBasedLookups,)
  149. class DecadeFilterBookAdminParameterEndsWith__In(ModelAdmin):
  150. list_filter = (DecadeListFilterParameterEndsWith__In,)
  151. class DecadeFilterBookAdminParameterEndsWith__Isnull(ModelAdmin):
  152. list_filter = (DecadeListFilterParameterEndsWith__Isnull,)
  153. class EmployeeAdmin(ModelAdmin):
  154. list_display = ['name', 'department']
  155. list_filter = ['department']
  156. class DepartmentFilterEmployeeAdmin(EmployeeAdmin):
  157. list_filter = [DepartmentListFilterLookupWithNonStringValue, ]
  158. class DepartmentFilterUnderscoredEmployeeAdmin(EmployeeAdmin):
  159. list_filter = [DepartmentListFilterLookupWithUnderscoredParameter, ]
  160. class DepartmentFilterDynamicValueBookAdmin(EmployeeAdmin):
  161. list_filter = [DepartmentListFilterLookupWithDynamicValue, ]
  162. class BookmarkAdminGenericRelation(ModelAdmin):
  163. list_filter = ['tags__tag']
  164. class ListFiltersTests(TestCase):
  165. def setUp(self):
  166. self.today = datetime.date.today()
  167. self.tomorrow = self.today + datetime.timedelta(days=1)
  168. self.one_week_ago = self.today - datetime.timedelta(days=7)
  169. if self.today.month == 12:
  170. self.next_month = self.today.replace(year=self.today.year + 1, month=1, day=1)
  171. else:
  172. self.next_month = self.today.replace(month=self.today.month + 1, day=1)
  173. self.next_year = self.today.replace(year=self.today.year + 1, month=1, day=1)
  174. self.request_factory = RequestFactory()
  175. # Users
  176. self.alfred = User.objects.create_user('alfred', 'alfred@example.com')
  177. self.bob = User.objects.create_user('bob', 'bob@example.com')
  178. self.lisa = User.objects.create_user('lisa', 'lisa@example.com')
  179. # Books
  180. self.djangonaut_book = Book.objects.create(
  181. title='Djangonaut: an art of living', year=2009,
  182. author=self.alfred, is_best_seller=True, date_registered=self.today,
  183. )
  184. self.bio_book = Book.objects.create(
  185. title='Django: a biography', year=1999, author=self.alfred,
  186. is_best_seller=False, no=207,
  187. )
  188. self.django_book = Book.objects.create(
  189. title='The Django Book', year=None, author=self.bob,
  190. is_best_seller=None, date_registered=self.today, no=103,
  191. )
  192. self.guitar_book = Book.objects.create(
  193. title='Guitar for dummies', year=2002, is_best_seller=True,
  194. date_registered=self.one_week_ago,
  195. )
  196. self.guitar_book.contributors.set([self.bob, self.lisa])
  197. # Departments
  198. self.dev = Department.objects.create(code='DEV', description='Development')
  199. self.design = Department.objects.create(code='DSN', description='Design')
  200. # Employees
  201. self.john = Employee.objects.create(name='John Blue', department=self.dev)
  202. self.jack = Employee.objects.create(name='Jack Red', department=self.design)
  203. def get_changelist(self, request, model, modeladmin):
  204. return ChangeList(
  205. request, model, modeladmin.list_display,
  206. modeladmin.list_display_links, modeladmin.list_filter,
  207. modeladmin.date_hierarchy, modeladmin.search_fields,
  208. modeladmin.list_select_related, modeladmin.list_per_page,
  209. modeladmin.list_max_show_all, modeladmin.list_editable, modeladmin,
  210. )
  211. def test_choicesfieldlistfilter_has_none_choice(self):
  212. """
  213. The last choice is for the None value.
  214. """
  215. class BookmarkChoicesAdmin(ModelAdmin):
  216. list_display = ['none_or_null']
  217. list_filter = ['none_or_null']
  218. modeladmin = BookmarkChoicesAdmin(Bookmark, site)
  219. request = self.request_factory.get('/', {})
  220. changelist = self.get_changelist(request, Bookmark, modeladmin)
  221. filterspec = changelist.get_filters(request)[0][0]
  222. choices = list(filterspec.choices(changelist))
  223. self.assertEqual(choices[-1]['display'], 'None')
  224. self.assertEqual(choices[-1]['query_string'], '?none_or_null__isnull=True')
  225. def test_datefieldlistfilter(self):
  226. modeladmin = BookAdmin(Book, site)
  227. request = self.request_factory.get('/')
  228. changelist = self.get_changelist(request, Book, modeladmin)
  229. request = self.request_factory.get('/', {'date_registered__gte': self.today,
  230. 'date_registered__lt': self.tomorrow})
  231. changelist = self.get_changelist(request, Book, modeladmin)
  232. # Make sure the correct queryset is returned
  233. queryset = changelist.get_queryset(request)
  234. self.assertEqual(list(queryset), [self.django_book, self.djangonaut_book])
  235. # Make sure the correct choice is selected
  236. filterspec = changelist.get_filters(request)[0][4]
  237. self.assertEqual(force_text(filterspec.title), 'date registered')
  238. choice = select_by(filterspec.choices(changelist), "display", "Today")
  239. self.assertIs(choice['selected'], True)
  240. self.assertEqual(
  241. choice['query_string'],
  242. '?date_registered__gte=%s&date_registered__lt=%s' % (
  243. self.today,
  244. self.tomorrow,
  245. )
  246. )
  247. request = self.request_factory.get('/', {'date_registered__gte': self.today.replace(day=1),
  248. 'date_registered__lt': self.next_month})
  249. changelist = self.get_changelist(request, Book, modeladmin)
  250. # Make sure the correct queryset is returned
  251. queryset = changelist.get_queryset(request)
  252. if (self.today.year, self.today.month) == (self.one_week_ago.year, self.one_week_ago.month):
  253. # In case one week ago is in the same month.
  254. self.assertEqual(list(queryset), [self.guitar_book, self.django_book, self.djangonaut_book])
  255. else:
  256. self.assertEqual(list(queryset), [self.django_book, self.djangonaut_book])
  257. # Make sure the correct choice is selected
  258. filterspec = changelist.get_filters(request)[0][4]
  259. self.assertEqual(force_text(filterspec.title), 'date registered')
  260. choice = select_by(filterspec.choices(changelist), "display", "This month")
  261. self.assertIs(choice['selected'], True)
  262. self.assertEqual(
  263. choice['query_string'],
  264. '?date_registered__gte=%s&date_registered__lt=%s' % (
  265. self.today.replace(day=1),
  266. self.next_month,
  267. )
  268. )
  269. request = self.request_factory.get('/', {'date_registered__gte': self.today.replace(month=1, day=1),
  270. 'date_registered__lt': self.next_year})
  271. changelist = self.get_changelist(request, Book, modeladmin)
  272. # Make sure the correct queryset is returned
  273. queryset = changelist.get_queryset(request)
  274. if self.today.year == self.one_week_ago.year:
  275. # In case one week ago is in the same year.
  276. self.assertEqual(list(queryset), [self.guitar_book, self.django_book, self.djangonaut_book])
  277. else:
  278. self.assertEqual(list(queryset), [self.django_book, self.djangonaut_book])
  279. # Make sure the correct choice is selected
  280. filterspec = changelist.get_filters(request)[0][4]
  281. self.assertEqual(force_text(filterspec.title), 'date registered')
  282. choice = select_by(filterspec.choices(changelist), "display", "This year")
  283. self.assertIs(choice['selected'], True)
  284. self.assertEqual(
  285. choice['query_string'],
  286. '?date_registered__gte=%s&date_registered__lt=%s' % (
  287. self.today.replace(month=1, day=1),
  288. self.next_year,
  289. )
  290. )
  291. request = self.request_factory.get('/', {
  292. 'date_registered__gte': str(self.one_week_ago),
  293. 'date_registered__lt': str(self.tomorrow),
  294. })
  295. changelist = self.get_changelist(request, Book, modeladmin)
  296. # Make sure the correct queryset is returned
  297. queryset = changelist.get_queryset(request)
  298. self.assertEqual(list(queryset), [self.guitar_book, self.django_book, self.djangonaut_book])
  299. # Make sure the correct choice is selected
  300. filterspec = changelist.get_filters(request)[0][4]
  301. self.assertEqual(force_text(filterspec.title), 'date registered')
  302. choice = select_by(filterspec.choices(changelist), "display", "Past 7 days")
  303. self.assertIs(choice['selected'], True)
  304. self.assertEqual(
  305. choice['query_string'],
  306. '?date_registered__gte=%s&date_registered__lt=%s' % (
  307. str(self.one_week_ago),
  308. str(self.tomorrow),
  309. )
  310. )
  311. # Null/not null queries
  312. request = self.request_factory.get('/', {'date_registered__isnull': 'True'})
  313. changelist = self.get_changelist(request, Book, modeladmin)
  314. # Make sure the correct queryset is returned
  315. queryset = changelist.get_queryset(request)
  316. self.assertEqual(queryset.count(), 1)
  317. self.assertEqual(queryset[0], self.bio_book)
  318. # Make sure the correct choice is selected
  319. filterspec = changelist.get_filters(request)[0][4]
  320. self.assertEqual(force_text(filterspec.title), 'date registered')
  321. choice = select_by(filterspec.choices(changelist), 'display', 'No date')
  322. self.assertIs(choice['selected'], True)
  323. self.assertEqual(choice['query_string'], '?date_registered__isnull=True')
  324. request = self.request_factory.get('/', {'date_registered__isnull': 'False'})
  325. changelist = self.get_changelist(request, Book, modeladmin)
  326. # Make sure the correct queryset is returned
  327. queryset = changelist.get_queryset(request)
  328. self.assertEqual(queryset.count(), 3)
  329. self.assertEqual(list(queryset), [self.guitar_book, self.django_book, self.djangonaut_book])
  330. # Make sure the correct choice is selected
  331. filterspec = changelist.get_filters(request)[0][4]
  332. self.assertEqual(force_text(filterspec.title), 'date registered')
  333. choice = select_by(filterspec.choices(changelist), 'display', 'Has date')
  334. self.assertIs(choice['selected'], True)
  335. self.assertEqual(choice['query_string'], '?date_registered__isnull=False')
  336. @unittest.skipIf(
  337. sys.platform.startswith('win'),
  338. "Windows doesn't support setting a timezone that differs from the "
  339. "system timezone."
  340. )
  341. @override_settings(USE_TZ=True)
  342. def test_datefieldlistfilter_with_time_zone_support(self):
  343. # Regression for #17830
  344. self.test_datefieldlistfilter()
  345. def test_allvaluesfieldlistfilter(self):
  346. modeladmin = BookAdmin(Book, site)
  347. request = self.request_factory.get('/', {'year__isnull': 'True'})
  348. changelist = self.get_changelist(request, Book, modeladmin)
  349. # Make sure the correct queryset is returned
  350. queryset = changelist.get_queryset(request)
  351. self.assertEqual(list(queryset), [self.django_book])
  352. # Make sure the last choice is None and is selected
  353. filterspec = changelist.get_filters(request)[0][0]
  354. self.assertEqual(force_text(filterspec.title), 'year')
  355. choices = list(filterspec.choices(changelist))
  356. self.assertIs(choices[-1]['selected'], True)
  357. self.assertEqual(choices[-1]['query_string'], '?year__isnull=True')
  358. request = self.request_factory.get('/', {'year': '2002'})
  359. changelist = self.get_changelist(request, Book, modeladmin)
  360. # Make sure the correct choice is selected
  361. filterspec = changelist.get_filters(request)[0][0]
  362. self.assertEqual(force_text(filterspec.title), 'year')
  363. choices = list(filterspec.choices(changelist))
  364. self.assertIs(choices[2]['selected'], True)
  365. self.assertEqual(choices[2]['query_string'], '?year=2002')
  366. def test_allvaluesfieldlistfilter_custom_qs(self):
  367. # Make sure that correct filters are returned with custom querysets
  368. modeladmin = BookAdminWithCustomQueryset(self.alfred, Book, site)
  369. request = self.request_factory.get('/')
  370. changelist = self.get_changelist(request, Book, modeladmin)
  371. filterspec = changelist.get_filters(request)[0][0]
  372. choices = list(filterspec.choices(changelist))
  373. # Should have 'All', 1999 and 2009 options i.e. the subset of years of
  374. # books written by alfred (which is the filtering criteria set by
  375. # BookAdminWithCustomQueryset.get_queryset())
  376. self.assertEqual(3, len(choices))
  377. self.assertEqual(choices[0]['query_string'], '?')
  378. self.assertEqual(choices[1]['query_string'], '?year=1999')
  379. self.assertEqual(choices[2]['query_string'], '?year=2009')
  380. def test_relatedfieldlistfilter_foreignkey(self):
  381. modeladmin = BookAdmin(Book, site)
  382. request = self.request_factory.get('/')
  383. changelist = self.get_changelist(request, Book, modeladmin)
  384. # Make sure that all users are present in the author's list filter
  385. filterspec = changelist.get_filters(request)[0][1]
  386. expected = [(self.alfred.pk, 'alfred'), (self.bob.pk, 'bob'), (self.lisa.pk, 'lisa')]
  387. self.assertEqual(sorted(filterspec.lookup_choices), sorted(expected))
  388. request = self.request_factory.get('/', {'author__isnull': 'True'})
  389. changelist = self.get_changelist(request, Book, modeladmin)
  390. # Make sure the correct queryset is returned
  391. queryset = changelist.get_queryset(request)
  392. self.assertEqual(list(queryset), [self.guitar_book])
  393. # Make sure the last choice is None and is selected
  394. filterspec = changelist.get_filters(request)[0][1]
  395. self.assertEqual(force_text(filterspec.title), 'Verbose Author')
  396. choices = list(filterspec.choices(changelist))
  397. self.assertIs(choices[-1]['selected'], True)
  398. self.assertEqual(choices[-1]['query_string'], '?author__isnull=True')
  399. request = self.request_factory.get('/', {'author__id__exact': self.alfred.pk})
  400. changelist = self.get_changelist(request, Book, modeladmin)
  401. # Make sure the correct choice is selected
  402. filterspec = changelist.get_filters(request)[0][1]
  403. self.assertEqual(force_text(filterspec.title), 'Verbose Author')
  404. # order of choices depends on User model, which has no order
  405. choice = select_by(filterspec.choices(changelist), "display", "alfred")
  406. self.assertIs(choice['selected'], True)
  407. self.assertEqual(choice['query_string'], '?author__id__exact=%d' % self.alfred.pk)
  408. def test_relatedfieldlistfilter_manytomany(self):
  409. modeladmin = BookAdmin(Book, site)
  410. request = self.request_factory.get('/')
  411. changelist = self.get_changelist(request, Book, modeladmin)
  412. # Make sure that all users are present in the contrib's list filter
  413. filterspec = changelist.get_filters(request)[0][2]
  414. expected = [(self.alfred.pk, 'alfred'), (self.bob.pk, 'bob'), (self.lisa.pk, 'lisa')]
  415. self.assertEqual(sorted(filterspec.lookup_choices), sorted(expected))
  416. request = self.request_factory.get('/', {'contributors__isnull': 'True'})
  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), [self.django_book, self.bio_book, self.djangonaut_book])
  421. # Make sure the last choice is None and is selected
  422. filterspec = changelist.get_filters(request)[0][2]
  423. self.assertEqual(force_text(filterspec.title), 'Verbose Contributors')
  424. choices = list(filterspec.choices(changelist))
  425. self.assertIs(choices[-1]['selected'], True)
  426. self.assertEqual(choices[-1]['query_string'], '?contributors__isnull=True')
  427. request = self.request_factory.get('/', {'contributors__id__exact': self.bob.pk})
  428. changelist = self.get_changelist(request, Book, modeladmin)
  429. # Make sure the correct choice is selected
  430. filterspec = changelist.get_filters(request)[0][2]
  431. self.assertEqual(force_text(filterspec.title), 'Verbose Contributors')
  432. choice = select_by(filterspec.choices(changelist), "display", "bob")
  433. self.assertIs(choice['selected'], True)
  434. self.assertEqual(choice['query_string'], '?contributors__id__exact=%d' % self.bob.pk)
  435. def test_relatedfieldlistfilter_reverse_relationships(self):
  436. modeladmin = CustomUserAdmin(User, site)
  437. # FK relationship -----
  438. request = self.request_factory.get('/', {'books_authored__isnull': 'True'})
  439. changelist = self.get_changelist(request, User, modeladmin)
  440. # Make sure the correct queryset is returned
  441. queryset = changelist.get_queryset(request)
  442. self.assertEqual(list(queryset), [self.lisa])
  443. # Make sure the last choice is None and is selected
  444. filterspec = changelist.get_filters(request)[0][0]
  445. self.assertEqual(force_text(filterspec.title), 'book')
  446. choices = list(filterspec.choices(changelist))
  447. self.assertIs(choices[-1]['selected'], True)
  448. self.assertEqual(choices[-1]['query_string'], '?books_authored__isnull=True')
  449. request = self.request_factory.get('/', {'books_authored__id__exact': self.bio_book.pk})
  450. changelist = self.get_changelist(request, User, modeladmin)
  451. # Make sure the correct choice is selected
  452. filterspec = changelist.get_filters(request)[0][0]
  453. self.assertEqual(force_text(filterspec.title), 'book')
  454. choice = select_by(filterspec.choices(changelist), "display", self.bio_book.title)
  455. self.assertIs(choice['selected'], True)
  456. self.assertEqual(choice['query_string'], '?books_authored__id__exact=%d' % self.bio_book.pk)
  457. # M2M relationship -----
  458. request = self.request_factory.get('/', {'books_contributed__isnull': 'True'})
  459. changelist = self.get_changelist(request, User, modeladmin)
  460. # Make sure the correct queryset is returned
  461. queryset = changelist.get_queryset(request)
  462. self.assertEqual(list(queryset), [self.alfred])
  463. # Make sure the last choice is None and is selected
  464. filterspec = changelist.get_filters(request)[0][1]
  465. self.assertEqual(force_text(filterspec.title), 'book')
  466. choices = list(filterspec.choices(changelist))
  467. self.assertIs(choices[-1]['selected'], True)
  468. self.assertEqual(choices[-1]['query_string'], '?books_contributed__isnull=True')
  469. request = self.request_factory.get('/', {'books_contributed__id__exact': self.django_book.pk})
  470. changelist = self.get_changelist(request, User, modeladmin)
  471. # Make sure the correct choice is selected
  472. filterspec = changelist.get_filters(request)[0][1]
  473. self.assertEqual(force_text(filterspec.title), 'book')
  474. choice = select_by(filterspec.choices(changelist), "display", self.django_book.title)
  475. self.assertIs(choice['selected'], True)
  476. self.assertEqual(choice['query_string'], '?books_contributed__id__exact=%d' % self.django_book.pk)
  477. # With one book, the list filter should appear because there is also a
  478. # (None) option.
  479. Book.objects.exclude(pk=self.djangonaut_book.pk).delete()
  480. filterspec = changelist.get_filters(request)[0]
  481. self.assertEqual(len(filterspec), 2)
  482. # With no books remaining, no list filters should appear.
  483. Book.objects.all().delete()
  484. filterspec = changelist.get_filters(request)[0]
  485. self.assertEqual(len(filterspec), 0)
  486. def test_relatedonlyfieldlistfilter_foreignkey(self):
  487. modeladmin = BookAdminRelatedOnlyFilter(Book, site)
  488. request = self.request_factory.get('/')
  489. changelist = self.get_changelist(request, Book, modeladmin)
  490. # Make sure that only actual authors are present in author's list filter
  491. filterspec = changelist.get_filters(request)[0][4]
  492. expected = [(self.alfred.pk, 'alfred'), (self.bob.pk, 'bob')]
  493. self.assertEqual(sorted(filterspec.lookup_choices), sorted(expected))
  494. def test_relatedonlyfieldlistfilter_underscorelookup_foreignkey(self):
  495. Department.objects.create(code='TEST', description='Testing')
  496. self.djangonaut_book.employee = self.john
  497. self.djangonaut_book.save()
  498. self.bio_book.employee = self.jack
  499. self.bio_book.save()
  500. modeladmin = BookAdminRelatedOnlyFilter(Book, site)
  501. request = self.request_factory.get('/')
  502. changelist = self.get_changelist(request, Book, modeladmin)
  503. # Only actual departments should be present in employee__department's
  504. # list filter.
  505. filterspec = changelist.get_filters(request)[0][6]
  506. expected = [
  507. (self.dev.code, str(self.dev)),
  508. (self.design.code, str(self.design)),
  509. ]
  510. self.assertEqual(sorted(filterspec.lookup_choices), sorted(expected))
  511. def test_relatedonlyfieldlistfilter_manytomany(self):
  512. modeladmin = BookAdminRelatedOnlyFilter(Book, site)
  513. request = self.request_factory.get('/')
  514. changelist = self.get_changelist(request, Book, modeladmin)
  515. # Make sure that only actual contributors are present in contrib's list filter
  516. filterspec = changelist.get_filters(request)[0][5]
  517. expected = [(self.bob.pk, 'bob'), (self.lisa.pk, 'lisa')]
  518. self.assertEqual(sorted(filterspec.lookup_choices), sorted(expected))
  519. def test_listfilter_genericrelation(self):
  520. django_bookmark = Bookmark.objects.create(url='https://www.djangoproject.com/')
  521. python_bookmark = Bookmark.objects.create(url='https://www.python.org/')
  522. kernel_bookmark = Bookmark.objects.create(url='https://www.kernel.org/')
  523. TaggedItem.objects.create(content_object=django_bookmark, tag='python')
  524. TaggedItem.objects.create(content_object=python_bookmark, tag='python')
  525. TaggedItem.objects.create(content_object=kernel_bookmark, tag='linux')
  526. modeladmin = BookmarkAdminGenericRelation(Bookmark, site)
  527. request = self.request_factory.get('/', {'tags__tag': 'python'})
  528. changelist = self.get_changelist(request, Bookmark, modeladmin)
  529. queryset = changelist.get_queryset(request)
  530. expected = [python_bookmark, django_bookmark]
  531. self.assertEqual(list(queryset), expected)
  532. def test_booleanfieldlistfilter(self):
  533. modeladmin = BookAdmin(Book, site)
  534. self.verify_booleanfieldlistfilter(modeladmin)
  535. def test_booleanfieldlistfilter_tuple(self):
  536. modeladmin = BookAdminWithTupleBooleanFilter(Book, site)
  537. self.verify_booleanfieldlistfilter(modeladmin)
  538. def verify_booleanfieldlistfilter(self, modeladmin):
  539. request = self.request_factory.get('/')
  540. changelist = self.get_changelist(request, Book, modeladmin)
  541. request = self.request_factory.get('/', {'is_best_seller__exact': 0})
  542. changelist = self.get_changelist(request, Book, modeladmin)
  543. # Make sure the correct queryset is returned
  544. queryset = changelist.get_queryset(request)
  545. self.assertEqual(list(queryset), [self.bio_book])
  546. # Make sure the correct choice is selected
  547. filterspec = changelist.get_filters(request)[0][3]
  548. self.assertEqual(force_text(filterspec.title), 'is best seller')
  549. choice = select_by(filterspec.choices(changelist), "display", "No")
  550. self.assertIs(choice['selected'], True)
  551. self.assertEqual(choice['query_string'], '?is_best_seller__exact=0')
  552. request = self.request_factory.get('/', {'is_best_seller__exact': 1})
  553. changelist = self.get_changelist(request, Book, modeladmin)
  554. # Make sure the correct queryset is returned
  555. queryset = changelist.get_queryset(request)
  556. self.assertEqual(list(queryset), [self.guitar_book, self.djangonaut_book])
  557. # Make sure the correct choice is selected
  558. filterspec = changelist.get_filters(request)[0][3]
  559. self.assertEqual(force_text(filterspec.title), 'is best seller')
  560. choice = select_by(filterspec.choices(changelist), "display", "Yes")
  561. self.assertIs(choice['selected'], True)
  562. self.assertEqual(choice['query_string'], '?is_best_seller__exact=1')
  563. request = self.request_factory.get('/', {'is_best_seller__isnull': 'True'})
  564. changelist = self.get_changelist(request, Book, modeladmin)
  565. # Make sure the correct queryset is returned
  566. queryset = changelist.get_queryset(request)
  567. self.assertEqual(list(queryset), [self.django_book])
  568. # Make sure the correct choice is selected
  569. filterspec = changelist.get_filters(request)[0][3]
  570. self.assertEqual(force_text(filterspec.title), 'is best seller')
  571. choice = select_by(filterspec.choices(changelist), "display", "Unknown")
  572. self.assertIs(choice['selected'], True)
  573. self.assertEqual(choice['query_string'], '?is_best_seller__isnull=True')
  574. def test_fieldlistfilter_underscorelookup_tuple(self):
  575. """
  576. Ensure ('fieldpath', ClassName ) lookups pass lookup_allowed checks
  577. when fieldpath contains double underscore in value (#19182).
  578. """
  579. modeladmin = BookAdminWithUnderscoreLookupAndTuple(Book, site)
  580. request = self.request_factory.get('/')
  581. changelist = self.get_changelist(request, Book, modeladmin)
  582. request = self.request_factory.get('/', {'author__email': 'alfred@example.com'})
  583. changelist = self.get_changelist(request, Book, modeladmin)
  584. # Make sure the correct queryset is returned
  585. queryset = changelist.get_queryset(request)
  586. self.assertEqual(list(queryset), [self.bio_book, self.djangonaut_book])
  587. def test_simplelistfilter(self):
  588. modeladmin = DecadeFilterBookAdmin(Book, site)
  589. # Make sure that the first option is 'All' ---------------------------
  590. request = self.request_factory.get('/', {})
  591. changelist = self.get_changelist(request, Book, modeladmin)
  592. # Make sure the correct queryset is returned
  593. queryset = changelist.get_queryset(request)
  594. self.assertEqual(list(queryset), list(Book.objects.all().order_by('-id')))
  595. # Make sure the correct choice is selected
  596. filterspec = changelist.get_filters(request)[0][1]
  597. self.assertEqual(force_text(filterspec.title), 'publication decade')
  598. choices = list(filterspec.choices(changelist))
  599. self.assertEqual(choices[0]['display'], 'All')
  600. self.assertIs(choices[0]['selected'], True)
  601. self.assertEqual(choices[0]['query_string'], '?')
  602. # Look for books in the 1980s ----------------------------------------
  603. request = self.request_factory.get('/', {'publication-decade': 'the 80s'})
  604. changelist = self.get_changelist(request, Book, modeladmin)
  605. # Make sure the correct queryset is returned
  606. queryset = changelist.get_queryset(request)
  607. self.assertEqual(list(queryset), [])
  608. # Make sure the correct choice is selected
  609. filterspec = changelist.get_filters(request)[0][1]
  610. self.assertEqual(force_text(filterspec.title), 'publication decade')
  611. choices = list(filterspec.choices(changelist))
  612. self.assertEqual(choices[1]['display'], 'the 1980\'s')
  613. self.assertIs(choices[1]['selected'], True)
  614. self.assertEqual(choices[1]['query_string'], '?publication-decade=the+80s')
  615. # Look for books in the 1990s ----------------------------------------
  616. request = self.request_factory.get('/', {'publication-decade': 'the 90s'})
  617. changelist = self.get_changelist(request, Book, modeladmin)
  618. # Make sure the correct queryset is returned
  619. queryset = changelist.get_queryset(request)
  620. self.assertEqual(list(queryset), [self.bio_book])
  621. # Make sure the correct choice is selected
  622. filterspec = changelist.get_filters(request)[0][1]
  623. self.assertEqual(force_text(filterspec.title), 'publication decade')
  624. choices = list(filterspec.choices(changelist))
  625. self.assertEqual(choices[2]['display'], 'the 1990\'s')
  626. self.assertIs(choices[2]['selected'], True)
  627. self.assertEqual(choices[2]['query_string'], '?publication-decade=the+90s')
  628. # Look for books in the 2000s ----------------------------------------
  629. request = self.request_factory.get('/', {'publication-decade': 'the 00s'})
  630. changelist = self.get_changelist(request, Book, modeladmin)
  631. # Make sure the correct queryset is returned
  632. queryset = changelist.get_queryset(request)
  633. self.assertEqual(list(queryset), [self.guitar_book, self.djangonaut_book])
  634. # Make sure the correct choice is selected
  635. filterspec = changelist.get_filters(request)[0][1]
  636. self.assertEqual(force_text(filterspec.title), 'publication decade')
  637. choices = list(filterspec.choices(changelist))
  638. self.assertEqual(choices[3]['display'], 'the 2000\'s')
  639. self.assertIs(choices[3]['selected'], True)
  640. self.assertEqual(choices[3]['query_string'], '?publication-decade=the+00s')
  641. # Combine multiple filters -------------------------------------------
  642. request = self.request_factory.get('/', {'publication-decade': 'the 00s', 'author__id__exact': self.alfred.pk})
  643. changelist = self.get_changelist(request, Book, modeladmin)
  644. # Make sure the correct queryset is returned
  645. queryset = changelist.get_queryset(request)
  646. self.assertEqual(list(queryset), [self.djangonaut_book])
  647. # Make sure the correct choices are selected
  648. filterspec = changelist.get_filters(request)[0][1]
  649. self.assertEqual(force_text(filterspec.title), 'publication decade')
  650. choices = list(filterspec.choices(changelist))
  651. self.assertEqual(choices[3]['display'], 'the 2000\'s')
  652. self.assertIs(choices[3]['selected'], True)
  653. self.assertEqual(
  654. choices[3]['query_string'],
  655. '?author__id__exact=%s&publication-decade=the+00s' % self.alfred.pk
  656. )
  657. filterspec = changelist.get_filters(request)[0][0]
  658. self.assertEqual(force_text(filterspec.title), 'Verbose Author')
  659. choice = select_by(filterspec.choices(changelist), "display", "alfred")
  660. self.assertIs(choice['selected'], True)
  661. self.assertEqual(choice['query_string'], '?author__id__exact=%s&publication-decade=the+00s' % self.alfred.pk)
  662. def test_listfilter_without_title(self):
  663. """
  664. Any filter must define a title.
  665. """
  666. modeladmin = DecadeFilterBookAdminWithoutTitle(Book, site)
  667. request = self.request_factory.get('/', {})
  668. msg = "The list filter 'DecadeListFilterWithoutTitle' does not specify a 'title'."
  669. with self.assertRaisesMessage(ImproperlyConfigured, msg):
  670. self.get_changelist(request, Book, modeladmin)
  671. def test_simplelistfilter_without_parameter(self):
  672. """
  673. Any SimpleListFilter must define a parameter_name.
  674. """
  675. modeladmin = DecadeFilterBookAdminWithoutParameter(Book, site)
  676. request = self.request_factory.get('/', {})
  677. msg = "The list filter 'DecadeListFilterWithoutParameter' does not specify a 'parameter_name'."
  678. with self.assertRaisesMessage(ImproperlyConfigured, msg):
  679. self.get_changelist(request, Book, modeladmin)
  680. def test_simplelistfilter_with_none_returning_lookups(self):
  681. """
  682. A SimpleListFilter lookups method can return None but disables the
  683. filter completely.
  684. """
  685. modeladmin = DecadeFilterBookAdminWithNoneReturningLookups(Book, site)
  686. request = self.request_factory.get('/', {})
  687. changelist = self.get_changelist(request, Book, modeladmin)
  688. filterspec = changelist.get_filters(request)[0]
  689. self.assertEqual(len(filterspec), 0)
  690. def test_filter_with_failing_queryset(self):
  691. """
  692. Ensure that when a filter's queryset method fails, it fails loudly and
  693. the corresponding exception doesn't get swallowed (#17828).
  694. """
  695. modeladmin = DecadeFilterBookAdminWithFailingQueryset(Book, site)
  696. request = self.request_factory.get('/', {})
  697. with self.assertRaises(ZeroDivisionError):
  698. self.get_changelist(request, Book, modeladmin)
  699. def test_simplelistfilter_with_queryset_based_lookups(self):
  700. modeladmin = DecadeFilterBookAdminWithQuerysetBasedLookups(Book, site)
  701. request = self.request_factory.get('/', {})
  702. changelist = self.get_changelist(request, Book, modeladmin)
  703. filterspec = changelist.get_filters(request)[0][0]
  704. self.assertEqual(force_text(filterspec.title), 'publication decade')
  705. choices = list(filterspec.choices(changelist))
  706. self.assertEqual(len(choices), 3)
  707. self.assertEqual(choices[0]['display'], 'All')
  708. self.assertIs(choices[0]['selected'], True)
  709. self.assertEqual(choices[0]['query_string'], '?')
  710. self.assertEqual(choices[1]['display'], 'the 1990\'s')
  711. self.assertIs(choices[1]['selected'], False)
  712. self.assertEqual(choices[1]['query_string'], '?publication-decade=the+90s')
  713. self.assertEqual(choices[2]['display'], 'the 2000\'s')
  714. self.assertIs(choices[2]['selected'], False)
  715. self.assertEqual(choices[2]['query_string'], '?publication-decade=the+00s')
  716. def test_two_characters_long_field(self):
  717. """
  718. list_filter works with two-characters long field names (#16080).
  719. """
  720. modeladmin = BookAdmin(Book, site)
  721. request = self.request_factory.get('/', {'no': '207'})
  722. changelist = self.get_changelist(request, Book, modeladmin)
  723. # Make sure the correct queryset is returned
  724. queryset = changelist.get_queryset(request)
  725. self.assertEqual(list(queryset), [self.bio_book])
  726. filterspec = changelist.get_filters(request)[0][-1]
  727. self.assertEqual(force_text(filterspec.title), 'number')
  728. choices = list(filterspec.choices(changelist))
  729. self.assertIs(choices[2]['selected'], True)
  730. self.assertEqual(choices[2]['query_string'], '?no=207')
  731. def test_parameter_ends_with__in__or__isnull(self):
  732. """
  733. Ensure that a SimpleListFilter's parameter name is not mistaken for a
  734. model field if it ends with '__isnull' or '__in' (#17091).
  735. """
  736. # When it ends with '__in' -----------------------------------------
  737. modeladmin = DecadeFilterBookAdminParameterEndsWith__In(Book, site)
  738. request = self.request_factory.get('/', {'decade__in': 'the 90s'})
  739. changelist = self.get_changelist(request, Book, modeladmin)
  740. # Make sure the correct queryset is returned
  741. queryset = changelist.get_queryset(request)
  742. self.assertEqual(list(queryset), [self.bio_book])
  743. # Make sure the correct choice is selected
  744. filterspec = changelist.get_filters(request)[0][0]
  745. self.assertEqual(force_text(filterspec.title), 'publication decade')
  746. choices = list(filterspec.choices(changelist))
  747. self.assertEqual(choices[2]['display'], 'the 1990\'s')
  748. self.assertIs(choices[2]['selected'], True)
  749. self.assertEqual(choices[2]['query_string'], '?decade__in=the+90s')
  750. # When it ends with '__isnull' ---------------------------------------
  751. modeladmin = DecadeFilterBookAdminParameterEndsWith__Isnull(Book, site)
  752. request = self.request_factory.get('/', {'decade__isnull': 'the 90s'})
  753. changelist = self.get_changelist(request, Book, modeladmin)
  754. # Make sure the correct queryset is returned
  755. queryset = changelist.get_queryset(request)
  756. self.assertEqual(list(queryset), [self.bio_book])
  757. # Make sure the correct choice is selected
  758. filterspec = changelist.get_filters(request)[0][0]
  759. self.assertEqual(force_text(filterspec.title), 'publication decade')
  760. choices = list(filterspec.choices(changelist))
  761. self.assertEqual(choices[2]['display'], 'the 1990\'s')
  762. self.assertIs(choices[2]['selected'], True)
  763. self.assertEqual(choices[2]['query_string'], '?decade__isnull=the+90s')
  764. def test_lookup_with_non_string_value(self):
  765. """
  766. Ensure choices are set the selected class when using non-string values
  767. for lookups in SimpleListFilters (#19318).
  768. """
  769. modeladmin = DepartmentFilterEmployeeAdmin(Employee, site)
  770. request = self.request_factory.get('/', {'department': self.john.department.pk})
  771. changelist = self.get_changelist(request, Employee, modeladmin)
  772. queryset = changelist.get_queryset(request)
  773. self.assertEqual(list(queryset), [self.john])
  774. filterspec = changelist.get_filters(request)[0][-1]
  775. self.assertEqual(force_text(filterspec.title), 'department')
  776. choices = list(filterspec.choices(changelist))
  777. self.assertEqual(choices[1]['display'], 'DEV')
  778. self.assertIs(choices[1]['selected'], True)
  779. self.assertEqual(choices[1]['query_string'], '?department=%s' % self.john.department.pk)
  780. def test_lookup_with_non_string_value_underscored(self):
  781. """
  782. Ensure SimpleListFilter lookups pass lookup_allowed checks when
  783. parameter_name attribute contains double-underscore value (#19182).
  784. """
  785. modeladmin = DepartmentFilterUnderscoredEmployeeAdmin(Employee, site)
  786. request = self.request_factory.get('/', {'department__whatever': self.john.department.pk})
  787. changelist = self.get_changelist(request, Employee, modeladmin)
  788. queryset = changelist.get_queryset(request)
  789. self.assertEqual(list(queryset), [self.john])
  790. filterspec = changelist.get_filters(request)[0][-1]
  791. self.assertEqual(force_text(filterspec.title), 'department')
  792. choices = list(filterspec.choices(changelist))
  793. self.assertEqual(choices[1]['display'], 'DEV')
  794. self.assertIs(choices[1]['selected'], True)
  795. self.assertEqual(choices[1]['query_string'], '?department__whatever=%s' % self.john.department.pk)
  796. def test_fk_with_to_field(self):
  797. """
  798. A filter on a FK respects the FK's to_field attribute (#17972).
  799. """
  800. modeladmin = EmployeeAdmin(Employee, site)
  801. request = self.request_factory.get('/', {})
  802. changelist = self.get_changelist(request, Employee, modeladmin)
  803. # Make sure the correct queryset is returned
  804. queryset = changelist.get_queryset(request)
  805. self.assertEqual(list(queryset), [self.jack, self.john])
  806. filterspec = changelist.get_filters(request)[0][-1]
  807. self.assertEqual(force_text(filterspec.title), 'department')
  808. choices = list(filterspec.choices(changelist))
  809. self.assertEqual(choices[0]['display'], 'All')
  810. self.assertIs(choices[0]['selected'], True)
  811. self.assertEqual(choices[0]['query_string'], '?')
  812. self.assertEqual(choices[1]['display'], 'Development')
  813. self.assertIs(choices[1]['selected'], False)
  814. self.assertEqual(choices[1]['query_string'], '?department__code__exact=DEV')
  815. self.assertEqual(choices[2]['display'], 'Design')
  816. self.assertIs(choices[2]['selected'], False)
  817. self.assertEqual(choices[2]['query_string'], '?department__code__exact=DSN')
  818. # Filter by Department=='Development' --------------------------------
  819. request = self.request_factory.get('/', {'department__code__exact': 'DEV'})
  820. changelist = self.get_changelist(request, Employee, modeladmin)
  821. # Make sure the correct queryset is returned
  822. queryset = changelist.get_queryset(request)
  823. self.assertEqual(list(queryset), [self.john])
  824. filterspec = changelist.get_filters(request)[0][-1]
  825. self.assertEqual(force_text(filterspec.title), 'department')
  826. choices = list(filterspec.choices(changelist))
  827. self.assertEqual(choices[0]['display'], 'All')
  828. self.assertIs(choices[0]['selected'], False)
  829. self.assertEqual(choices[0]['query_string'], '?')
  830. self.assertEqual(choices[1]['display'], 'Development')
  831. self.assertIs(choices[1]['selected'], True)
  832. self.assertEqual(choices[1]['query_string'], '?department__code__exact=DEV')
  833. self.assertEqual(choices[2]['display'], 'Design')
  834. self.assertIs(choices[2]['selected'], False)
  835. self.assertEqual(choices[2]['query_string'], '?department__code__exact=DSN')
  836. def test_lookup_with_dynamic_value(self):
  837. """
  838. Ensure SimpleListFilter can access self.value() inside the lookup.
  839. """
  840. modeladmin = DepartmentFilterDynamicValueBookAdmin(Book, site)
  841. def _test_choices(request, expected_displays):
  842. changelist = self.get_changelist(request, Book, modeladmin)
  843. filterspec = changelist.get_filters(request)[0][0]
  844. self.assertEqual(force_text(filterspec.title), 'publication decade')
  845. choices = tuple(c['display'] for c in filterspec.choices(changelist))
  846. self.assertEqual(choices, expected_displays)
  847. _test_choices(self.request_factory.get('/', {}),
  848. ("All", "the 1980's", "the 1990's"))
  849. _test_choices(self.request_factory.get('/', {'publication-decade': 'the 80s'}),
  850. ("All", "the 1990's"))
  851. _test_choices(self.request_factory.get('/', {'publication-decade': 'the 90s'}),
  852. ("All", "the 1980's"))
  853. def test_list_filter_queryset_filtered_by_default(self):
  854. """
  855. A list filter that filters the queryset by default gives the correct
  856. full_result_count.
  857. """
  858. modeladmin = NotNinetiesListFilterAdmin(Book, site)
  859. request = self.request_factory.get('/', {})
  860. changelist = self.get_changelist(request, Book, modeladmin)
  861. changelist.get_results(request)
  862. self.assertEqual(changelist.full_result_count, 4)