tests.py 49 KB

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