test_dates.py 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914
  1. import datetime
  2. from unittest import mock
  3. from django.core.exceptions import ImproperlyConfigured
  4. from django.test import TestCase, override_settings, skipUnlessDBFeature
  5. from django.test.utils import requires_tz_support
  6. from .models import Artist, Author, Book, BookSigning, Page
  7. def _make_books(n, base_date):
  8. for i in range(n):
  9. Book.objects.create(
  10. name="Book %d" % i,
  11. slug="book-%d" % i,
  12. pages=100 + i,
  13. pubdate=base_date - datetime.timedelta(days=i),
  14. )
  15. class TestDataMixin:
  16. @classmethod
  17. def setUpTestData(cls):
  18. cls.artist1 = Artist.objects.create(name="Rene Magritte")
  19. cls.author1 = Author.objects.create(
  20. name="Roberto Bolaño", slug="roberto-bolano"
  21. )
  22. cls.author2 = Author.objects.create(
  23. name="Scott Rosenberg", slug="scott-rosenberg"
  24. )
  25. cls.book1 = Book.objects.create(
  26. name="2066", slug="2066", pages=800, pubdate=datetime.date(2008, 10, 1)
  27. )
  28. cls.book1.authors.add(cls.author1)
  29. cls.book2 = Book.objects.create(
  30. name="Dreaming in Code",
  31. slug="dreaming-in-code",
  32. pages=300,
  33. pubdate=datetime.date(2006, 5, 1),
  34. )
  35. cls.page1 = Page.objects.create(
  36. content="I was once bitten by a moose.",
  37. template="generic_views/page_template.html",
  38. )
  39. @override_settings(ROOT_URLCONF="generic_views.urls")
  40. class ArchiveIndexViewTests(TestDataMixin, TestCase):
  41. def test_archive_view(self):
  42. res = self.client.get("/dates/books/")
  43. self.assertEqual(res.status_code, 200)
  44. self.assertEqual(
  45. list(res.context["date_list"]),
  46. list(Book.objects.dates("pubdate", "year", "DESC")),
  47. )
  48. self.assertEqual(list(res.context["latest"]), list(Book.objects.all()))
  49. self.assertTemplateUsed(res, "generic_views/book_archive.html")
  50. def test_archive_view_context_object_name(self):
  51. res = self.client.get("/dates/books/context_object_name/")
  52. self.assertEqual(res.status_code, 200)
  53. self.assertEqual(
  54. list(res.context["date_list"]),
  55. list(Book.objects.dates("pubdate", "year", "DESC")),
  56. )
  57. self.assertEqual(list(res.context["thingies"]), list(Book.objects.all()))
  58. self.assertNotIn("latest", res.context)
  59. self.assertTemplateUsed(res, "generic_views/book_archive.html")
  60. def test_empty_archive_view(self):
  61. Book.objects.all().delete()
  62. res = self.client.get("/dates/books/")
  63. self.assertEqual(res.status_code, 404)
  64. def test_allow_empty_archive_view(self):
  65. Book.objects.all().delete()
  66. res = self.client.get("/dates/books/allow_empty/")
  67. self.assertEqual(res.status_code, 200)
  68. self.assertEqual(list(res.context["date_list"]), [])
  69. self.assertTemplateUsed(res, "generic_views/book_archive.html")
  70. def test_archive_view_template(self):
  71. res = self.client.get("/dates/books/template_name/")
  72. self.assertEqual(res.status_code, 200)
  73. self.assertEqual(
  74. list(res.context["date_list"]),
  75. list(Book.objects.dates("pubdate", "year", "DESC")),
  76. )
  77. self.assertEqual(list(res.context["latest"]), list(Book.objects.all()))
  78. self.assertTemplateUsed(res, "generic_views/list.html")
  79. def test_archive_view_template_suffix(self):
  80. res = self.client.get("/dates/books/template_name_suffix/")
  81. self.assertEqual(res.status_code, 200)
  82. self.assertEqual(
  83. list(res.context["date_list"]),
  84. list(Book.objects.dates("pubdate", "year", "DESC")),
  85. )
  86. self.assertEqual(list(res.context["latest"]), list(Book.objects.all()))
  87. self.assertTemplateUsed(res, "generic_views/book_detail.html")
  88. def test_archive_view_invalid(self):
  89. msg = (
  90. "BookArchive is missing a QuerySet. Define BookArchive.model, "
  91. "BookArchive.queryset, or override BookArchive.get_queryset()."
  92. )
  93. with self.assertRaisesMessage(ImproperlyConfigured, msg):
  94. self.client.get("/dates/books/invalid/")
  95. def test_archive_view_by_month(self):
  96. res = self.client.get("/dates/books/by_month/")
  97. self.assertEqual(res.status_code, 200)
  98. self.assertEqual(
  99. list(res.context["date_list"]),
  100. list(Book.objects.dates("pubdate", "month", "DESC")),
  101. )
  102. def test_paginated_archive_view(self):
  103. _make_books(20, base_date=datetime.date.today())
  104. res = self.client.get("/dates/books/paginated/")
  105. self.assertEqual(res.status_code, 200)
  106. self.assertEqual(
  107. list(res.context["date_list"]),
  108. list(Book.objects.dates("pubdate", "year", "DESC")),
  109. )
  110. self.assertEqual(list(res.context["latest"]), list(Book.objects.all()[0:10]))
  111. self.assertTemplateUsed(res, "generic_views/book_archive.html")
  112. res = self.client.get("/dates/books/paginated/?page=2")
  113. self.assertEqual(res.status_code, 200)
  114. self.assertEqual(res.context["page_obj"].number, 2)
  115. self.assertEqual(list(res.context["latest"]), list(Book.objects.all()[10:20]))
  116. def test_paginated_archive_view_does_not_load_entire_table(self):
  117. # Regression test for #18087
  118. _make_books(20, base_date=datetime.date.today())
  119. # 1 query for years list + 1 query for books
  120. with self.assertNumQueries(2):
  121. self.client.get("/dates/books/")
  122. # same as above + 1 query to test if books exist + 1 query to count them
  123. with self.assertNumQueries(4):
  124. self.client.get("/dates/books/paginated/")
  125. def test_no_duplicate_query(self):
  126. # Regression test for #18354
  127. with self.assertNumQueries(2):
  128. self.client.get("/dates/books/reverse/")
  129. def test_datetime_archive_view(self):
  130. BookSigning.objects.create(event_date=datetime.datetime(2008, 4, 2, 12, 0))
  131. res = self.client.get("/dates/booksignings/")
  132. self.assertEqual(res.status_code, 200)
  133. @requires_tz_support
  134. @skipUnlessDBFeature("has_zoneinfo_database")
  135. @override_settings(USE_TZ=True, TIME_ZONE="Africa/Nairobi")
  136. def test_aware_datetime_archive_view(self):
  137. BookSigning.objects.create(
  138. event_date=datetime.datetime(2008, 4, 2, 12, 0, tzinfo=datetime.UTC)
  139. )
  140. res = self.client.get("/dates/booksignings/")
  141. self.assertEqual(res.status_code, 200)
  142. def test_date_list_order(self):
  143. """date_list should be sorted descending in index"""
  144. _make_books(5, base_date=datetime.date(2011, 12, 25))
  145. res = self.client.get("/dates/books/")
  146. self.assertEqual(res.status_code, 200)
  147. self.assertEqual(
  148. list(res.context["date_list"]),
  149. sorted(res.context["date_list"], reverse=True),
  150. )
  151. def test_archive_view_custom_sorting(self):
  152. Book.objects.create(
  153. name="Zebras for Dummies", pages=600, pubdate=datetime.date(2007, 5, 1)
  154. )
  155. res = self.client.get("/dates/books/sortedbyname/")
  156. self.assertEqual(res.status_code, 200)
  157. self.assertEqual(
  158. list(res.context["date_list"]),
  159. list(Book.objects.dates("pubdate", "year", "DESC")),
  160. )
  161. self.assertEqual(
  162. list(res.context["latest"]), list(Book.objects.order_by("name").all())
  163. )
  164. self.assertTemplateUsed(res, "generic_views/book_archive.html")
  165. def test_archive_view_custom_sorting_dec(self):
  166. Book.objects.create(
  167. name="Zebras for Dummies", pages=600, pubdate=datetime.date(2007, 5, 1)
  168. )
  169. res = self.client.get("/dates/books/sortedbynamedec/")
  170. self.assertEqual(res.status_code, 200)
  171. self.assertEqual(
  172. list(res.context["date_list"]),
  173. list(Book.objects.dates("pubdate", "year", "DESC")),
  174. )
  175. self.assertEqual(
  176. list(res.context["latest"]), list(Book.objects.order_by("-name").all())
  177. )
  178. self.assertTemplateUsed(res, "generic_views/book_archive.html")
  179. def test_archive_view_without_date_field(self):
  180. msg = "BookArchiveWithoutDateField.date_field is required."
  181. with self.assertRaisesMessage(ImproperlyConfigured, msg):
  182. self.client.get("/dates/books/without_date_field/")
  183. @override_settings(ROOT_URLCONF="generic_views.urls")
  184. class YearArchiveViewTests(TestDataMixin, TestCase):
  185. def test_year_view(self):
  186. res = self.client.get("/dates/books/2008/")
  187. self.assertEqual(res.status_code, 200)
  188. self.assertEqual(list(res.context["date_list"]), [datetime.date(2008, 10, 1)])
  189. self.assertEqual(res.context["year"], datetime.date(2008, 1, 1))
  190. self.assertTemplateUsed(res, "generic_views/book_archive_year.html")
  191. # Since allow_empty=False, next/prev years must be valid (#7164)
  192. self.assertIsNone(res.context["next_year"])
  193. self.assertEqual(res.context["previous_year"], datetime.date(2006, 1, 1))
  194. def test_year_view_make_object_list(self):
  195. res = self.client.get("/dates/books/2006/make_object_list/")
  196. self.assertEqual(res.status_code, 200)
  197. self.assertEqual(list(res.context["date_list"]), [datetime.date(2006, 5, 1)])
  198. self.assertEqual(
  199. list(res.context["book_list"]),
  200. list(Book.objects.filter(pubdate__year=2006)),
  201. )
  202. self.assertEqual(
  203. list(res.context["object_list"]),
  204. list(Book.objects.filter(pubdate__year=2006)),
  205. )
  206. self.assertTemplateUsed(res, "generic_views/book_archive_year.html")
  207. def test_year_view_empty(self):
  208. res = self.client.get("/dates/books/1999/")
  209. self.assertEqual(res.status_code, 404)
  210. res = self.client.get("/dates/books/1999/allow_empty/")
  211. self.assertEqual(res.status_code, 200)
  212. self.assertEqual(list(res.context["date_list"]), [])
  213. self.assertEqual(list(res.context["book_list"]), [])
  214. # Since allow_empty=True, next/prev are allowed to be empty years (#7164)
  215. self.assertEqual(res.context["next_year"], datetime.date(2000, 1, 1))
  216. self.assertEqual(res.context["previous_year"], datetime.date(1998, 1, 1))
  217. def test_year_view_allow_future(self):
  218. # Create a new book in the future
  219. year = datetime.date.today().year + 1
  220. Book.objects.create(
  221. name="The New New Testement", pages=600, pubdate=datetime.date(year, 1, 1)
  222. )
  223. res = self.client.get("/dates/books/%s/" % year)
  224. self.assertEqual(res.status_code, 404)
  225. res = self.client.get("/dates/books/%s/allow_empty/" % year)
  226. self.assertEqual(res.status_code, 200)
  227. self.assertEqual(list(res.context["book_list"]), [])
  228. res = self.client.get("/dates/books/%s/allow_future/" % year)
  229. self.assertEqual(res.status_code, 200)
  230. self.assertEqual(list(res.context["date_list"]), [datetime.date(year, 1, 1)])
  231. def test_year_view_paginated(self):
  232. res = self.client.get("/dates/books/2006/paginated/")
  233. self.assertEqual(res.status_code, 200)
  234. self.assertEqual(
  235. list(res.context["book_list"]),
  236. list(Book.objects.filter(pubdate__year=2006)),
  237. )
  238. self.assertEqual(
  239. list(res.context["object_list"]),
  240. list(Book.objects.filter(pubdate__year=2006)),
  241. )
  242. self.assertTemplateUsed(res, "generic_views/book_archive_year.html")
  243. def test_year_view_custom_sort_order(self):
  244. # Zebras comes after Dreaming by name, but before on '-pubdate' which
  245. # is the default sorting.
  246. Book.objects.create(
  247. name="Zebras for Dummies", pages=600, pubdate=datetime.date(2006, 9, 1)
  248. )
  249. res = self.client.get("/dates/books/2006/sortedbyname/")
  250. self.assertEqual(res.status_code, 200)
  251. self.assertEqual(
  252. list(res.context["date_list"]),
  253. [datetime.date(2006, 5, 1), datetime.date(2006, 9, 1)],
  254. )
  255. self.assertEqual(
  256. list(res.context["book_list"]),
  257. list(Book.objects.filter(pubdate__year=2006).order_by("name")),
  258. )
  259. self.assertEqual(
  260. list(res.context["object_list"]),
  261. list(Book.objects.filter(pubdate__year=2006).order_by("name")),
  262. )
  263. self.assertTemplateUsed(res, "generic_views/book_archive_year.html")
  264. def test_year_view_two_custom_sort_orders(self):
  265. Book.objects.create(
  266. name="Zebras for Dummies", pages=300, pubdate=datetime.date(2006, 9, 1)
  267. )
  268. Book.objects.create(
  269. name="Hunting Hippos", pages=400, pubdate=datetime.date(2006, 3, 1)
  270. )
  271. res = self.client.get("/dates/books/2006/sortedbypageandnamedec/")
  272. self.assertEqual(res.status_code, 200)
  273. self.assertEqual(
  274. list(res.context["date_list"]),
  275. [
  276. datetime.date(2006, 3, 1),
  277. datetime.date(2006, 5, 1),
  278. datetime.date(2006, 9, 1),
  279. ],
  280. )
  281. self.assertEqual(
  282. list(res.context["book_list"]),
  283. list(Book.objects.filter(pubdate__year=2006).order_by("pages", "-name")),
  284. )
  285. self.assertEqual(
  286. list(res.context["object_list"]),
  287. list(Book.objects.filter(pubdate__year=2006).order_by("pages", "-name")),
  288. )
  289. self.assertTemplateUsed(res, "generic_views/book_archive_year.html")
  290. def test_year_view_invalid_pattern(self):
  291. res = self.client.get("/dates/books/no_year/")
  292. self.assertEqual(res.status_code, 404)
  293. def test_no_duplicate_query(self):
  294. # Regression test for #18354
  295. with self.assertNumQueries(4):
  296. self.client.get("/dates/books/2008/reverse/")
  297. def test_datetime_year_view(self):
  298. BookSigning.objects.create(event_date=datetime.datetime(2008, 4, 2, 12, 0))
  299. res = self.client.get("/dates/booksignings/2008/")
  300. self.assertEqual(res.status_code, 200)
  301. @skipUnlessDBFeature("has_zoneinfo_database")
  302. @override_settings(USE_TZ=True, TIME_ZONE="Africa/Nairobi")
  303. def test_aware_datetime_year_view(self):
  304. BookSigning.objects.create(
  305. event_date=datetime.datetime(2008, 4, 2, 12, 0, tzinfo=datetime.UTC)
  306. )
  307. res = self.client.get("/dates/booksignings/2008/")
  308. self.assertEqual(res.status_code, 200)
  309. def test_date_list_order(self):
  310. """date_list should be sorted ascending in year view"""
  311. _make_books(10, base_date=datetime.date(2011, 12, 25))
  312. res = self.client.get("/dates/books/2011/")
  313. self.assertEqual(
  314. list(res.context["date_list"]), sorted(res.context["date_list"])
  315. )
  316. @mock.patch("django.views.generic.list.MultipleObjectMixin.get_context_data")
  317. def test_get_context_data_receives_extra_context(self, mock):
  318. """
  319. MultipleObjectMixin.get_context_data() receives the context set by
  320. BaseYearArchiveView.get_dated_items(). This behavior is implemented in
  321. BaseDateListView.get().
  322. """
  323. BookSigning.objects.create(event_date=datetime.datetime(2008, 4, 2, 12, 0))
  324. with self.assertRaisesMessage(
  325. TypeError, "context must be a dict rather than MagicMock."
  326. ):
  327. self.client.get("/dates/booksignings/2008/")
  328. args, kwargs = mock.call_args
  329. # These are context values from get_dated_items().
  330. self.assertEqual(kwargs["year"], datetime.date(2008, 1, 1))
  331. self.assertIsNone(kwargs["previous_year"])
  332. self.assertIsNone(kwargs["next_year"])
  333. def test_get_dated_items_not_implemented(self):
  334. msg = "A DateView must provide an implementation of get_dated_items()"
  335. with self.assertRaisesMessage(NotImplementedError, msg):
  336. self.client.get("/BaseDateListViewTest/")
  337. @override_settings(ROOT_URLCONF="generic_views.urls")
  338. class MonthArchiveViewTests(TestDataMixin, TestCase):
  339. def test_month_view(self):
  340. res = self.client.get("/dates/books/2008/oct/")
  341. self.assertEqual(res.status_code, 200)
  342. self.assertTemplateUsed(res, "generic_views/book_archive_month.html")
  343. self.assertEqual(list(res.context["date_list"]), [datetime.date(2008, 10, 1)])
  344. self.assertEqual(
  345. list(res.context["book_list"]),
  346. list(Book.objects.filter(pubdate=datetime.date(2008, 10, 1))),
  347. )
  348. self.assertEqual(res.context["month"], datetime.date(2008, 10, 1))
  349. # Since allow_empty=False, next/prev months must be valid (#7164)
  350. self.assertIsNone(res.context["next_month"])
  351. self.assertEqual(res.context["previous_month"], datetime.date(2006, 5, 1))
  352. def test_month_view_allow_empty(self):
  353. # allow_empty = False, empty month
  354. res = self.client.get("/dates/books/2000/jan/")
  355. self.assertEqual(res.status_code, 404)
  356. # allow_empty = True, empty month
  357. res = self.client.get("/dates/books/2000/jan/allow_empty/")
  358. self.assertEqual(res.status_code, 200)
  359. self.assertEqual(list(res.context["date_list"]), [])
  360. self.assertEqual(list(res.context["book_list"]), [])
  361. self.assertEqual(res.context["month"], datetime.date(2000, 1, 1))
  362. # Since allow_empty=True, next/prev are allowed to be empty months (#7164)
  363. self.assertEqual(res.context["next_month"], datetime.date(2000, 2, 1))
  364. self.assertEqual(res.context["previous_month"], datetime.date(1999, 12, 1))
  365. # allow_empty but not allow_future: next_month should be empty (#7164)
  366. url = datetime.date.today().strftime("/dates/books/%Y/%b/allow_empty/").lower()
  367. res = self.client.get(url)
  368. self.assertEqual(res.status_code, 200)
  369. self.assertIsNone(res.context["next_month"])
  370. def test_month_view_allow_future(self):
  371. future = (datetime.date.today() + datetime.timedelta(days=60)).replace(day=1)
  372. urlbit = future.strftime("%Y/%b").lower()
  373. b = Book.objects.create(name="The New New Testement", pages=600, pubdate=future)
  374. # allow_future = False, future month
  375. res = self.client.get("/dates/books/%s/" % urlbit)
  376. self.assertEqual(res.status_code, 404)
  377. # allow_future = True, valid future month
  378. res = self.client.get("/dates/books/%s/allow_future/" % urlbit)
  379. self.assertEqual(res.status_code, 200)
  380. self.assertEqual(res.context["date_list"][0], b.pubdate)
  381. self.assertEqual(list(res.context["book_list"]), [b])
  382. self.assertEqual(res.context["month"], future)
  383. # Since allow_future = True but not allow_empty, next/prev are not
  384. # allowed to be empty months (#7164)
  385. self.assertIsNone(res.context["next_month"])
  386. self.assertEqual(res.context["previous_month"], datetime.date(2008, 10, 1))
  387. # allow_future, but not allow_empty, with a current month. So next
  388. # should be in the future (yup, #7164, again)
  389. res = self.client.get("/dates/books/2008/oct/allow_future/")
  390. self.assertEqual(res.status_code, 200)
  391. self.assertEqual(res.context["next_month"], future)
  392. self.assertEqual(res.context["previous_month"], datetime.date(2006, 5, 1))
  393. def test_month_view_paginated(self):
  394. res = self.client.get("/dates/books/2008/oct/paginated/")
  395. self.assertEqual(res.status_code, 200)
  396. self.assertEqual(
  397. list(res.context["book_list"]),
  398. list(Book.objects.filter(pubdate__year=2008, pubdate__month=10)),
  399. )
  400. self.assertEqual(
  401. list(res.context["object_list"]),
  402. list(Book.objects.filter(pubdate__year=2008, pubdate__month=10)),
  403. )
  404. self.assertTemplateUsed(res, "generic_views/book_archive_month.html")
  405. def test_custom_month_format(self):
  406. res = self.client.get("/dates/books/2008/10/")
  407. self.assertEqual(res.status_code, 200)
  408. def test_month_view_invalid_pattern(self):
  409. res = self.client.get("/dates/books/2007/no_month/")
  410. self.assertEqual(res.status_code, 404)
  411. def test_previous_month_without_content(self):
  412. "Content can exist on any day of the previous month. Refs #14711"
  413. self.pubdate_list = [
  414. datetime.date(2010, month, day) for month, day in ((9, 1), (10, 2), (11, 3))
  415. ]
  416. for pubdate in self.pubdate_list:
  417. name = str(pubdate)
  418. Book.objects.create(name=name, slug=name, pages=100, pubdate=pubdate)
  419. res = self.client.get("/dates/books/2010/nov/allow_empty/")
  420. self.assertEqual(res.status_code, 200)
  421. self.assertEqual(res.context["previous_month"], datetime.date(2010, 10, 1))
  422. # The following test demonstrates the bug
  423. res = self.client.get("/dates/books/2010/nov/")
  424. self.assertEqual(res.status_code, 200)
  425. self.assertEqual(res.context["previous_month"], datetime.date(2010, 10, 1))
  426. # The bug does not occur here because a Book with pubdate of Sep 1 exists
  427. res = self.client.get("/dates/books/2010/oct/")
  428. self.assertEqual(res.status_code, 200)
  429. self.assertEqual(res.context["previous_month"], datetime.date(2010, 9, 1))
  430. def test_datetime_month_view(self):
  431. BookSigning.objects.create(event_date=datetime.datetime(2008, 2, 1, 12, 0))
  432. BookSigning.objects.create(event_date=datetime.datetime(2008, 4, 2, 12, 0))
  433. BookSigning.objects.create(event_date=datetime.datetime(2008, 6, 3, 12, 0))
  434. res = self.client.get("/dates/booksignings/2008/apr/")
  435. self.assertEqual(res.status_code, 200)
  436. def test_month_view_get_month_from_request(self):
  437. oct1 = datetime.date(2008, 10, 1)
  438. res = self.client.get("/dates/books/without_month/2008/?month=oct")
  439. self.assertEqual(res.status_code, 200)
  440. self.assertTemplateUsed(res, "generic_views/book_archive_month.html")
  441. self.assertEqual(list(res.context["date_list"]), [oct1])
  442. self.assertEqual(
  443. list(res.context["book_list"]), list(Book.objects.filter(pubdate=oct1))
  444. )
  445. self.assertEqual(res.context["month"], oct1)
  446. def test_month_view_without_month_in_url(self):
  447. res = self.client.get("/dates/books/without_month/2008/")
  448. self.assertEqual(res.status_code, 404)
  449. self.assertEqual(res.context["exception"], "No month specified")
  450. @skipUnlessDBFeature("has_zoneinfo_database")
  451. @override_settings(USE_TZ=True, TIME_ZONE="Africa/Nairobi")
  452. def test_aware_datetime_month_view(self):
  453. BookSigning.objects.create(
  454. event_date=datetime.datetime(2008, 2, 1, 12, 0, tzinfo=datetime.UTC)
  455. )
  456. BookSigning.objects.create(
  457. event_date=datetime.datetime(2008, 4, 2, 12, 0, tzinfo=datetime.UTC)
  458. )
  459. BookSigning.objects.create(
  460. event_date=datetime.datetime(2008, 6, 3, 12, 0, tzinfo=datetime.UTC)
  461. )
  462. res = self.client.get("/dates/booksignings/2008/apr/")
  463. self.assertEqual(res.status_code, 200)
  464. def test_date_list_order(self):
  465. """date_list should be sorted ascending in month view"""
  466. _make_books(10, base_date=datetime.date(2011, 12, 25))
  467. res = self.client.get("/dates/books/2011/dec/")
  468. self.assertEqual(
  469. list(res.context["date_list"]), sorted(res.context["date_list"])
  470. )
  471. @override_settings(ROOT_URLCONF="generic_views.urls")
  472. class WeekArchiveViewTests(TestDataMixin, TestCase):
  473. def test_week_view(self):
  474. res = self.client.get("/dates/books/2008/week/39/")
  475. self.assertEqual(res.status_code, 200)
  476. self.assertTemplateUsed(res, "generic_views/book_archive_week.html")
  477. self.assertEqual(
  478. res.context["book_list"][0],
  479. Book.objects.get(pubdate=datetime.date(2008, 10, 1)),
  480. )
  481. self.assertEqual(res.context["week"], datetime.date(2008, 9, 28))
  482. # Since allow_empty=False, next/prev weeks must be valid
  483. self.assertIsNone(res.context["next_week"])
  484. self.assertEqual(res.context["previous_week"], datetime.date(2006, 4, 30))
  485. def test_week_view_allow_empty(self):
  486. # allow_empty = False, empty week
  487. res = self.client.get("/dates/books/2008/week/12/")
  488. self.assertEqual(res.status_code, 404)
  489. # allow_empty = True, empty month
  490. res = self.client.get("/dates/books/2008/week/12/allow_empty/")
  491. self.assertEqual(res.status_code, 200)
  492. self.assertEqual(list(res.context["book_list"]), [])
  493. self.assertEqual(res.context["week"], datetime.date(2008, 3, 23))
  494. # Since allow_empty=True, next/prev are allowed to be empty weeks
  495. self.assertEqual(res.context["next_week"], datetime.date(2008, 3, 30))
  496. self.assertEqual(res.context["previous_week"], datetime.date(2008, 3, 16))
  497. # allow_empty but not allow_future: next_week should be empty
  498. url = (
  499. datetime.date.today()
  500. .strftime("/dates/books/%Y/week/%U/allow_empty/")
  501. .lower()
  502. )
  503. res = self.client.get(url)
  504. self.assertEqual(res.status_code, 200)
  505. self.assertIsNone(res.context["next_week"])
  506. def test_week_view_allow_future(self):
  507. # January 7th always falls in week 1, given Python's definition of week numbers
  508. future = datetime.date(datetime.date.today().year + 1, 1, 7)
  509. future_sunday = future - datetime.timedelta(days=(future.weekday() + 1) % 7)
  510. b = Book.objects.create(name="The New New Testement", pages=600, pubdate=future)
  511. res = self.client.get("/dates/books/%s/week/1/" % future.year)
  512. self.assertEqual(res.status_code, 404)
  513. res = self.client.get("/dates/books/%s/week/1/allow_future/" % future.year)
  514. self.assertEqual(res.status_code, 200)
  515. self.assertEqual(list(res.context["book_list"]), [b])
  516. self.assertEqual(res.context["week"], future_sunday)
  517. # Since allow_future = True but not allow_empty, next/prev are not
  518. # allowed to be empty weeks
  519. self.assertIsNone(res.context["next_week"])
  520. self.assertEqual(res.context["previous_week"], datetime.date(2008, 9, 28))
  521. # allow_future, but not allow_empty, with a current week. So next
  522. # should be in the future
  523. res = self.client.get("/dates/books/2008/week/39/allow_future/")
  524. self.assertEqual(res.status_code, 200)
  525. self.assertEqual(res.context["next_week"], future_sunday)
  526. self.assertEqual(res.context["previous_week"], datetime.date(2006, 4, 30))
  527. def test_week_view_paginated(self):
  528. week_start = datetime.date(2008, 9, 28)
  529. week_end = week_start + datetime.timedelta(days=7)
  530. res = self.client.get("/dates/books/2008/week/39/")
  531. self.assertEqual(res.status_code, 200)
  532. self.assertEqual(
  533. list(res.context["book_list"]),
  534. list(Book.objects.filter(pubdate__gte=week_start, pubdate__lt=week_end)),
  535. )
  536. self.assertEqual(
  537. list(res.context["object_list"]),
  538. list(Book.objects.filter(pubdate__gte=week_start, pubdate__lt=week_end)),
  539. )
  540. self.assertTemplateUsed(res, "generic_views/book_archive_week.html")
  541. def test_week_view_invalid_pattern(self):
  542. res = self.client.get("/dates/books/2007/week/no_week/")
  543. self.assertEqual(res.status_code, 404)
  544. def test_week_start_Monday(self):
  545. # Regression for #14752
  546. res = self.client.get("/dates/books/2008/week/39/")
  547. self.assertEqual(res.status_code, 200)
  548. self.assertEqual(res.context["week"], datetime.date(2008, 9, 28))
  549. res = self.client.get("/dates/books/2008/week/39/monday/")
  550. self.assertEqual(res.status_code, 200)
  551. self.assertEqual(res.context["week"], datetime.date(2008, 9, 29))
  552. def test_week_iso_format(self):
  553. res = self.client.get("/dates/books/2008/week/40/iso_format/")
  554. self.assertEqual(res.status_code, 200)
  555. self.assertTemplateUsed(res, "generic_views/book_archive_week.html")
  556. self.assertEqual(
  557. list(res.context["book_list"]),
  558. [Book.objects.get(pubdate=datetime.date(2008, 10, 1))],
  559. )
  560. self.assertEqual(res.context["week"], datetime.date(2008, 9, 29))
  561. def test_unknown_week_format(self):
  562. msg = "Unknown week format '%T'. Choices are: %U, %V, %W"
  563. with self.assertRaisesMessage(ValueError, msg):
  564. self.client.get("/dates/books/2008/week/39/unknown_week_format/")
  565. def test_incompatible_iso_week_format_view(self):
  566. msg = (
  567. "ISO week directive '%V' is incompatible with the year directive "
  568. "'%Y'. Use the ISO year '%G' instead."
  569. )
  570. with self.assertRaisesMessage(ValueError, msg):
  571. self.client.get("/dates/books/2008/week/40/invalid_iso_week_year_format/")
  572. def test_datetime_week_view(self):
  573. BookSigning.objects.create(event_date=datetime.datetime(2008, 4, 2, 12, 0))
  574. res = self.client.get("/dates/booksignings/2008/week/13/")
  575. self.assertEqual(res.status_code, 200)
  576. @override_settings(USE_TZ=True, TIME_ZONE="Africa/Nairobi")
  577. def test_aware_datetime_week_view(self):
  578. BookSigning.objects.create(
  579. event_date=datetime.datetime(2008, 4, 2, 12, 0, tzinfo=datetime.UTC)
  580. )
  581. res = self.client.get("/dates/booksignings/2008/week/13/")
  582. self.assertEqual(res.status_code, 200)
  583. @override_settings(ROOT_URLCONF="generic_views.urls")
  584. class DayArchiveViewTests(TestDataMixin, TestCase):
  585. def test_day_view(self):
  586. res = self.client.get("/dates/books/2008/oct/01/")
  587. self.assertEqual(res.status_code, 200)
  588. self.assertTemplateUsed(res, "generic_views/book_archive_day.html")
  589. self.assertEqual(
  590. list(res.context["book_list"]),
  591. list(Book.objects.filter(pubdate=datetime.date(2008, 10, 1))),
  592. )
  593. self.assertEqual(res.context["day"], datetime.date(2008, 10, 1))
  594. # Since allow_empty=False, next/prev days must be valid.
  595. self.assertIsNone(res.context["next_day"])
  596. self.assertEqual(res.context["previous_day"], datetime.date(2006, 5, 1))
  597. def test_day_view_allow_empty(self):
  598. # allow_empty = False, empty month
  599. res = self.client.get("/dates/books/2000/jan/1/")
  600. self.assertEqual(res.status_code, 404)
  601. # allow_empty = True, empty month
  602. res = self.client.get("/dates/books/2000/jan/1/allow_empty/")
  603. self.assertEqual(res.status_code, 200)
  604. self.assertEqual(list(res.context["book_list"]), [])
  605. self.assertEqual(res.context["day"], datetime.date(2000, 1, 1))
  606. # Since it's allow empty, next/prev are allowed to be empty months (#7164)
  607. self.assertEqual(res.context["next_day"], datetime.date(2000, 1, 2))
  608. self.assertEqual(res.context["previous_day"], datetime.date(1999, 12, 31))
  609. # allow_empty but not allow_future: next_month should be empty (#7164)
  610. url = (
  611. datetime.date.today().strftime("/dates/books/%Y/%b/%d/allow_empty/").lower()
  612. )
  613. res = self.client.get(url)
  614. self.assertEqual(res.status_code, 200)
  615. self.assertIsNone(res.context["next_day"])
  616. def test_day_view_allow_future(self):
  617. future = datetime.date.today() + datetime.timedelta(days=60)
  618. urlbit = future.strftime("%Y/%b/%d").lower()
  619. b = Book.objects.create(name="The New New Testement", pages=600, pubdate=future)
  620. # allow_future = False, future month
  621. res = self.client.get("/dates/books/%s/" % urlbit)
  622. self.assertEqual(res.status_code, 404)
  623. # allow_future = True, valid future month
  624. res = self.client.get("/dates/books/%s/allow_future/" % urlbit)
  625. self.assertEqual(res.status_code, 200)
  626. self.assertEqual(list(res.context["book_list"]), [b])
  627. self.assertEqual(res.context["day"], future)
  628. # allow_future but not allow_empty, next/prev must be valid
  629. self.assertIsNone(res.context["next_day"])
  630. self.assertEqual(res.context["previous_day"], datetime.date(2008, 10, 1))
  631. # allow_future, but not allow_empty, with a current month.
  632. res = self.client.get("/dates/books/2008/oct/01/allow_future/")
  633. self.assertEqual(res.status_code, 200)
  634. self.assertEqual(res.context["next_day"], future)
  635. self.assertEqual(res.context["previous_day"], datetime.date(2006, 5, 1))
  636. # allow_future for yesterday, next_day is today (#17192)
  637. today = datetime.date.today()
  638. yesterday = today - datetime.timedelta(days=1)
  639. res = self.client.get(
  640. "/dates/books/%s/allow_empty_and_future/"
  641. % yesterday.strftime("%Y/%b/%d").lower()
  642. )
  643. self.assertEqual(res.context["next_day"], today)
  644. def test_day_view_paginated(self):
  645. res = self.client.get("/dates/books/2008/oct/1/")
  646. self.assertEqual(res.status_code, 200)
  647. self.assertEqual(
  648. list(res.context["book_list"]),
  649. list(
  650. Book.objects.filter(
  651. pubdate__year=2008, pubdate__month=10, pubdate__day=1
  652. )
  653. ),
  654. )
  655. self.assertEqual(
  656. list(res.context["object_list"]),
  657. list(
  658. Book.objects.filter(
  659. pubdate__year=2008, pubdate__month=10, pubdate__day=1
  660. )
  661. ),
  662. )
  663. self.assertTemplateUsed(res, "generic_views/book_archive_day.html")
  664. def test_next_prev_context(self):
  665. res = self.client.get("/dates/books/2008/oct/01/")
  666. self.assertEqual(
  667. res.content, b"Archive for Oct. 1, 2008. Previous day is May 1, 2006\n"
  668. )
  669. def test_custom_month_format(self):
  670. res = self.client.get("/dates/books/2008/10/01/")
  671. self.assertEqual(res.status_code, 200)
  672. def test_day_view_invalid_pattern(self):
  673. res = self.client.get("/dates/books/2007/oct/no_day/")
  674. self.assertEqual(res.status_code, 404)
  675. def test_today_view(self):
  676. res = self.client.get("/dates/books/today/")
  677. self.assertEqual(res.status_code, 404)
  678. res = self.client.get("/dates/books/today/allow_empty/")
  679. self.assertEqual(res.status_code, 200)
  680. self.assertEqual(res.context["day"], datetime.date.today())
  681. def test_datetime_day_view(self):
  682. BookSigning.objects.create(event_date=datetime.datetime(2008, 4, 2, 12, 0))
  683. res = self.client.get("/dates/booksignings/2008/apr/2/")
  684. self.assertEqual(res.status_code, 200)
  685. @requires_tz_support
  686. @override_settings(USE_TZ=True, TIME_ZONE="Africa/Nairobi")
  687. def test_aware_datetime_day_view(self):
  688. bs = BookSigning.objects.create(
  689. event_date=datetime.datetime(2008, 4, 2, 12, 0, tzinfo=datetime.UTC)
  690. )
  691. res = self.client.get("/dates/booksignings/2008/apr/2/")
  692. self.assertEqual(res.status_code, 200)
  693. # 2008-04-02T00:00:00+03:00 (beginning of day) >
  694. # 2008-04-01T22:00:00+00:00 (book signing event date).
  695. bs.event_date = datetime.datetime(2008, 4, 1, 22, 0, tzinfo=datetime.UTC)
  696. bs.save()
  697. res = self.client.get("/dates/booksignings/2008/apr/2/")
  698. self.assertEqual(res.status_code, 200)
  699. # 2008-04-03T00:00:00+03:00 (end of day) > 2008-04-02T22:00:00+00:00
  700. # (book signing event date).
  701. bs.event_date = datetime.datetime(2008, 4, 2, 22, 0, tzinfo=datetime.UTC)
  702. bs.save()
  703. res = self.client.get("/dates/booksignings/2008/apr/2/")
  704. self.assertEqual(res.status_code, 404)
  705. @override_settings(ROOT_URLCONF="generic_views.urls")
  706. class DateDetailViewTests(TestDataMixin, TestCase):
  707. def test_date_detail_by_pk(self):
  708. res = self.client.get("/dates/books/2008/oct/01/%s/" % self.book1.pk)
  709. self.assertEqual(res.status_code, 200)
  710. self.assertEqual(res.context["object"], self.book1)
  711. self.assertEqual(res.context["book"], self.book1)
  712. self.assertTemplateUsed(res, "generic_views/book_detail.html")
  713. def test_date_detail_by_slug(self):
  714. res = self.client.get("/dates/books/2006/may/01/byslug/dreaming-in-code/")
  715. self.assertEqual(res.status_code, 200)
  716. self.assertEqual(res.context["book"], Book.objects.get(slug="dreaming-in-code"))
  717. def test_date_detail_custom_month_format(self):
  718. res = self.client.get("/dates/books/2008/10/01/%s/" % self.book1.pk)
  719. self.assertEqual(res.status_code, 200)
  720. self.assertEqual(res.context["book"], self.book1)
  721. def test_date_detail_allow_future(self):
  722. future = datetime.date.today() + datetime.timedelta(days=60)
  723. urlbit = future.strftime("%Y/%b/%d").lower()
  724. b = Book.objects.create(
  725. name="The New New Testement", slug="new-new", pages=600, pubdate=future
  726. )
  727. res = self.client.get("/dates/books/%s/new-new/" % urlbit)
  728. self.assertEqual(res.status_code, 404)
  729. res = self.client.get("/dates/books/%s/%s/allow_future/" % (urlbit, b.id))
  730. self.assertEqual(res.status_code, 200)
  731. self.assertEqual(res.context["book"], b)
  732. self.assertTemplateUsed(res, "generic_views/book_detail.html")
  733. def test_year_out_of_range(self):
  734. urls = [
  735. "/dates/books/9999/",
  736. "/dates/books/9999/12/",
  737. "/dates/books/9999/week/52/",
  738. ]
  739. for url in urls:
  740. with self.subTest(url=url):
  741. res = self.client.get(url)
  742. self.assertEqual(res.status_code, 404)
  743. self.assertEqual(res.context["exception"], "Date out of range")
  744. def test_invalid_url(self):
  745. msg = (
  746. "Generic detail view BookDetail must be called with either an "
  747. "object pk or a slug in the URLconf."
  748. )
  749. with self.assertRaisesMessage(AttributeError, msg):
  750. self.client.get("/dates/books/2008/oct/01/nopk/")
  751. def test_get_object_custom_queryset(self):
  752. """
  753. Custom querysets are used when provided to
  754. BaseDateDetailView.get_object().
  755. """
  756. res = self.client.get(
  757. "/dates/books/get_object_custom_queryset/2006/may/01/%s/" % self.book2.pk
  758. )
  759. self.assertEqual(res.status_code, 200)
  760. self.assertEqual(res.context["object"], self.book2)
  761. self.assertEqual(res.context["book"], self.book2)
  762. self.assertTemplateUsed(res, "generic_views/book_detail.html")
  763. res = self.client.get(
  764. "/dates/books/get_object_custom_queryset/2008/oct/01/9999999/"
  765. )
  766. self.assertEqual(res.status_code, 404)
  767. def test_get_object_custom_queryset_numqueries(self):
  768. with self.assertNumQueries(1):
  769. self.client.get("/dates/books/get_object_custom_queryset/2006/may/01/2/")
  770. def test_datetime_date_detail(self):
  771. bs = BookSigning.objects.create(event_date=datetime.datetime(2008, 4, 2, 12, 0))
  772. res = self.client.get("/dates/booksignings/2008/apr/2/%d/" % bs.pk)
  773. self.assertEqual(res.status_code, 200)
  774. @requires_tz_support
  775. @override_settings(USE_TZ=True, TIME_ZONE="Africa/Nairobi")
  776. def test_aware_datetime_date_detail(self):
  777. bs = BookSigning.objects.create(
  778. event_date=datetime.datetime(2008, 4, 2, 12, 0, tzinfo=datetime.UTC)
  779. )
  780. res = self.client.get("/dates/booksignings/2008/apr/2/%d/" % bs.pk)
  781. self.assertEqual(res.status_code, 200)
  782. # 2008-04-02T00:00:00+03:00 (beginning of day) >
  783. # 2008-04-01T22:00:00+00:00 (book signing event date).
  784. bs.event_date = datetime.datetime(2008, 4, 1, 22, 0, tzinfo=datetime.UTC)
  785. bs.save()
  786. res = self.client.get("/dates/booksignings/2008/apr/2/%d/" % bs.pk)
  787. self.assertEqual(res.status_code, 200)
  788. # 2008-04-03T00:00:00+03:00 (end of day) > 2008-04-02T22:00:00+00:00
  789. # (book signing event date).
  790. bs.event_date = datetime.datetime(2008, 4, 2, 22, 0, tzinfo=datetime.UTC)
  791. bs.save()
  792. res = self.client.get("/dates/booksignings/2008/apr/2/%d/" % bs.pk)
  793. self.assertEqual(res.status_code, 404)