test_dates.py 40 KB

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