dates.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786
  1. from __future__ import unicode_literals
  2. import datetime
  3. from django.conf import settings
  4. from django.db import models
  5. from django.core.exceptions import ImproperlyConfigured
  6. from django.http import Http404
  7. from django.utils.encoding import force_str, force_text
  8. from django.utils.functional import cached_property
  9. from django.utils.translation import ugettext as _
  10. from django.utils import timezone
  11. from django.views.generic.base import View
  12. from django.views.generic.detail import BaseDetailView, SingleObjectTemplateResponseMixin
  13. from django.views.generic.list import MultipleObjectMixin, MultipleObjectTemplateResponseMixin
  14. class YearMixin(object):
  15. """
  16. Mixin for views manipulating year-based data.
  17. """
  18. year_format = '%Y'
  19. year = None
  20. def get_year_format(self):
  21. """
  22. Get a year format string in strptime syntax to be used to parse the
  23. year from url variables.
  24. """
  25. return self.year_format
  26. def get_year(self):
  27. """
  28. Return the year for which this view should display data.
  29. """
  30. year = self.year
  31. if year is None:
  32. try:
  33. year = self.kwargs['year']
  34. except KeyError:
  35. try:
  36. year = self.request.GET['year']
  37. except KeyError:
  38. raise Http404(_("No year specified"))
  39. return year
  40. def get_next_year(self, date):
  41. """
  42. Get the next valid year.
  43. """
  44. return _get_next_prev(self, date, is_previous=False, period='year')
  45. def get_previous_year(self, date):
  46. """
  47. Get the previous valid year.
  48. """
  49. return _get_next_prev(self, date, is_previous=True, period='year')
  50. def _get_next_year(self, date):
  51. """
  52. Return the start date of the next interval.
  53. The interval is defined by start date <= item date < next start date.
  54. """
  55. return date.replace(year=date.year + 1, month=1, day=1)
  56. def _get_current_year(self, date):
  57. """
  58. Return the start date of the current interval.
  59. """
  60. return date.replace(month=1, day=1)
  61. class MonthMixin(object):
  62. """
  63. Mixin for views manipulating month-based data.
  64. """
  65. month_format = '%b'
  66. month = None
  67. def get_month_format(self):
  68. """
  69. Get a month format string in strptime syntax to be used to parse the
  70. month from url variables.
  71. """
  72. return self.month_format
  73. def get_month(self):
  74. """
  75. Return the month for which this view should display data.
  76. """
  77. month = self.month
  78. if month is None:
  79. try:
  80. month = self.kwargs['month']
  81. except KeyError:
  82. try:
  83. month = self.request.GET['month']
  84. except KeyError:
  85. raise Http404(_("No month specified"))
  86. return month
  87. def get_next_month(self, date):
  88. """
  89. Get the next valid month.
  90. """
  91. return _get_next_prev(self, date, is_previous=False, period='month')
  92. def get_previous_month(self, date):
  93. """
  94. Get the previous valid month.
  95. """
  96. return _get_next_prev(self, date, is_previous=True, period='month')
  97. def _get_next_month(self, date):
  98. """
  99. Return the start date of the next interval.
  100. The interval is defined by start date <= item date < next start date.
  101. """
  102. if date.month == 12:
  103. return date.replace(year=date.year + 1, month=1, day=1)
  104. else:
  105. return date.replace(month=date.month + 1, day=1)
  106. def _get_current_month(self, date):
  107. """
  108. Return the start date of the previous interval.
  109. """
  110. return date.replace(day=1)
  111. class DayMixin(object):
  112. """
  113. Mixin for views manipulating day-based data.
  114. """
  115. day_format = '%d'
  116. day = None
  117. def get_day_format(self):
  118. """
  119. Get a day format string in strptime syntax to be used to parse the day
  120. from url variables.
  121. """
  122. return self.day_format
  123. def get_day(self):
  124. """
  125. Return the day for which this view should display data.
  126. """
  127. day = self.day
  128. if day is None:
  129. try:
  130. day = self.kwargs['day']
  131. except KeyError:
  132. try:
  133. day = self.request.GET['day']
  134. except KeyError:
  135. raise Http404(_("No day specified"))
  136. return day
  137. def get_next_day(self, date):
  138. """
  139. Get the next valid day.
  140. """
  141. return _get_next_prev(self, date, is_previous=False, period='day')
  142. def get_previous_day(self, date):
  143. """
  144. Get the previous valid day.
  145. """
  146. return _get_next_prev(self, date, is_previous=True, period='day')
  147. def _get_next_day(self, date):
  148. """
  149. Return the start date of the next interval.
  150. The interval is defined by start date <= item date < next start date.
  151. """
  152. return date + datetime.timedelta(days=1)
  153. def _get_current_day(self, date):
  154. """
  155. Return the start date of the current interval.
  156. """
  157. return date
  158. class WeekMixin(object):
  159. """
  160. Mixin for views manipulating week-based data.
  161. """
  162. week_format = '%U'
  163. week = None
  164. def get_week_format(self):
  165. """
  166. Get a week format string in strptime syntax to be used to parse the
  167. week from url variables.
  168. """
  169. return self.week_format
  170. def get_week(self):
  171. """
  172. Return the week for which this view should display data
  173. """
  174. week = self.week
  175. if week is None:
  176. try:
  177. week = self.kwargs['week']
  178. except KeyError:
  179. try:
  180. week = self.request.GET['week']
  181. except KeyError:
  182. raise Http404(_("No week specified"))
  183. return week
  184. def get_next_week(self, date):
  185. """
  186. Get the next valid week.
  187. """
  188. return _get_next_prev(self, date, is_previous=False, period='week')
  189. def get_previous_week(self, date):
  190. """
  191. Get the previous valid week.
  192. """
  193. return _get_next_prev(self, date, is_previous=True, period='week')
  194. def _get_next_week(self, date):
  195. """
  196. Return the start date of the next interval.
  197. The interval is defined by start date <= item date < next start date.
  198. """
  199. return date + datetime.timedelta(days=7 - self._get_weekday(date))
  200. def _get_current_week(self, date):
  201. """
  202. Return the start date of the current interval.
  203. """
  204. return date - datetime.timedelta(self._get_weekday(date))
  205. def _get_weekday(self, date):
  206. """
  207. Return the weekday for a given date.
  208. The first day according to the week format is 0 and the last day is 6.
  209. """
  210. week_format = self.get_week_format()
  211. if week_format == '%W': # week starts on Monday
  212. return date.weekday()
  213. elif week_format == '%U': # week starts on Sunday
  214. return (date.weekday() + 1) % 7
  215. else:
  216. raise ValueError("unknown week format: %s" % week_format)
  217. class DateMixin(object):
  218. """
  219. Mixin class for views manipulating date-based data.
  220. """
  221. date_field = None
  222. allow_future = False
  223. def get_date_field(self):
  224. """
  225. Get the name of the date field to be used to filter by.
  226. """
  227. if self.date_field is None:
  228. raise ImproperlyConfigured("%s.date_field is required." % self.__class__.__name__)
  229. return self.date_field
  230. def get_allow_future(self):
  231. """
  232. Returns `True` if the view should be allowed to display objects from
  233. the future.
  234. """
  235. return self.allow_future
  236. # Note: the following three methods only work in subclasses that also
  237. # inherit SingleObjectMixin or MultipleObjectMixin.
  238. @cached_property
  239. def uses_datetime_field(self):
  240. """
  241. Return `True` if the date field is a `DateTimeField` and `False`
  242. if it's a `DateField`.
  243. """
  244. model = self.get_queryset().model if self.model is None else self.model
  245. field = model._meta.get_field(self.get_date_field())
  246. return isinstance(field, models.DateTimeField)
  247. def _make_date_lookup_arg(self, value):
  248. """
  249. Convert a date into a datetime when the date field is a DateTimeField.
  250. When time zone support is enabled, `date` is assumed to be in the
  251. current time zone, so that displayed items are consistent with the URL.
  252. """
  253. if self.uses_datetime_field:
  254. value = datetime.datetime.combine(value, datetime.time.min)
  255. if settings.USE_TZ:
  256. value = timezone.make_aware(value, timezone.get_current_timezone())
  257. return value
  258. def _make_single_date_lookup(self, date):
  259. """
  260. Get the lookup kwargs for filtering on a single date.
  261. If the date field is a DateTimeField, we can't just filter on
  262. date_field=date because that doesn't take the time into account.
  263. """
  264. date_field = self.get_date_field()
  265. if self.uses_datetime_field:
  266. since = self._make_date_lookup_arg(date)
  267. until = self._make_date_lookup_arg(date + datetime.timedelta(days=1))
  268. return {
  269. '%s__gte' % date_field: since,
  270. '%s__lt' % date_field: until,
  271. }
  272. else:
  273. # Skip self._make_date_lookup_arg, it's a no-op in this branch.
  274. return {date_field: date}
  275. class BaseDateListView(MultipleObjectMixin, DateMixin, View):
  276. """
  277. Abstract base class for date-based views displaying a list of objects.
  278. """
  279. allow_empty = False
  280. date_list_period = 'year'
  281. def get(self, request, *args, **kwargs):
  282. self.date_list, self.object_list, extra_context = self.get_dated_items()
  283. context = self.get_context_data(object_list=self.object_list,
  284. date_list=self.date_list)
  285. context.update(extra_context)
  286. return self.render_to_response(context)
  287. def get_dated_items(self):
  288. """
  289. Obtain the list of dates and items.
  290. """
  291. raise NotImplementedError('A DateView must provide an implementation of get_dated_items()')
  292. def get_dated_queryset(self, ordering=None, **lookup):
  293. """
  294. Get a queryset properly filtered according to `allow_future` and any
  295. extra lookup kwargs.
  296. """
  297. qs = self.get_queryset().filter(**lookup)
  298. date_field = self.get_date_field()
  299. allow_future = self.get_allow_future()
  300. allow_empty = self.get_allow_empty()
  301. paginate_by = self.get_paginate_by(qs)
  302. if ordering is not None:
  303. qs = qs.order_by(ordering)
  304. if not allow_future:
  305. now = timezone.now() if self.uses_datetime_field else timezone_today()
  306. qs = qs.filter(**{'%s__lte' % date_field: now})
  307. if not allow_empty:
  308. # When pagination is enabled, it's better to do a cheap query
  309. # than to load the unpaginated queryset in memory.
  310. is_empty = len(qs) == 0 if paginate_by is None else not qs.exists()
  311. if is_empty:
  312. raise Http404(_("No %(verbose_name_plural)s available") % {
  313. 'verbose_name_plural': force_text(qs.model._meta.verbose_name_plural)
  314. })
  315. return qs
  316. def get_date_list_period(self):
  317. """
  318. Get the aggregation period for the list of dates: 'year', 'month', or 'day'.
  319. """
  320. return self.date_list_period
  321. def get_date_list(self, queryset, date_type=None, ordering='ASC'):
  322. """
  323. Get a date list by calling `queryset.dates/datetimes()`, checking
  324. along the way for empty lists that aren't allowed.
  325. """
  326. date_field = self.get_date_field()
  327. allow_empty = self.get_allow_empty()
  328. if date_type is None:
  329. date_type = self.get_date_list_period()
  330. if self.uses_datetime_field:
  331. date_list = queryset.datetimes(date_field, date_type, ordering)
  332. else:
  333. date_list = queryset.dates(date_field, date_type, ordering)
  334. if date_list is not None and not date_list and not allow_empty:
  335. name = force_text(queryset.model._meta.verbose_name_plural)
  336. raise Http404(_("No %(verbose_name_plural)s available") %
  337. {'verbose_name_plural': name})
  338. return date_list
  339. class BaseArchiveIndexView(BaseDateListView):
  340. """
  341. Base class for archives of date-based items.
  342. Requires a response mixin.
  343. """
  344. context_object_name = 'latest'
  345. def get_dated_items(self):
  346. """
  347. Return (date_list, items, extra_context) for this request.
  348. """
  349. qs = self.get_dated_queryset(ordering='-%s' % self.get_date_field())
  350. date_list = self.get_date_list(qs, ordering='DESC')
  351. if not date_list:
  352. qs = qs.none()
  353. return (date_list, qs, {})
  354. class ArchiveIndexView(MultipleObjectTemplateResponseMixin, BaseArchiveIndexView):
  355. """
  356. Top-level archive of date-based items.
  357. """
  358. template_name_suffix = '_archive'
  359. class BaseYearArchiveView(YearMixin, BaseDateListView):
  360. """
  361. List of objects published in a given year.
  362. """
  363. date_list_period = 'month'
  364. make_object_list = False
  365. def get_dated_items(self):
  366. """
  367. Return (date_list, items, extra_context) for this request.
  368. """
  369. year = self.get_year()
  370. date_field = self.get_date_field()
  371. date = _date_from_string(year, self.get_year_format())
  372. since = self._make_date_lookup_arg(date)
  373. until = self._make_date_lookup_arg(self._get_next_year(date))
  374. lookup_kwargs = {
  375. '%s__gte' % date_field: since,
  376. '%s__lt' % date_field: until,
  377. }
  378. qs = self.get_dated_queryset(ordering='-%s' % date_field, **lookup_kwargs)
  379. date_list = self.get_date_list(qs)
  380. if not self.get_make_object_list():
  381. # We need this to be a queryset since parent classes introspect it
  382. # to find information about the model.
  383. qs = qs.none()
  384. return (date_list, qs, {
  385. 'year': date,
  386. 'next_year': self.get_next_year(date),
  387. 'previous_year': self.get_previous_year(date),
  388. })
  389. def get_make_object_list(self):
  390. """
  391. Return `True` if this view should contain the full list of objects in
  392. the given year.
  393. """
  394. return self.make_object_list
  395. class YearArchiveView(MultipleObjectTemplateResponseMixin, BaseYearArchiveView):
  396. """
  397. List of objects published in a given year.
  398. """
  399. template_name_suffix = '_archive_year'
  400. class BaseMonthArchiveView(YearMixin, MonthMixin, BaseDateListView):
  401. """
  402. List of objects published in a given month.
  403. """
  404. date_list_period = 'day'
  405. def get_dated_items(self):
  406. """
  407. Return (date_list, items, extra_context) for this request.
  408. """
  409. year = self.get_year()
  410. month = self.get_month()
  411. date_field = self.get_date_field()
  412. date = _date_from_string(year, self.get_year_format(),
  413. month, self.get_month_format())
  414. since = self._make_date_lookup_arg(date)
  415. until = self._make_date_lookup_arg(self._get_next_month(date))
  416. lookup_kwargs = {
  417. '%s__gte' % date_field: since,
  418. '%s__lt' % date_field: until,
  419. }
  420. qs = self.get_dated_queryset(**lookup_kwargs)
  421. date_list = self.get_date_list(qs)
  422. return (date_list, qs, {
  423. 'month': date,
  424. 'next_month': self.get_next_month(date),
  425. 'previous_month': self.get_previous_month(date),
  426. })
  427. class MonthArchiveView(MultipleObjectTemplateResponseMixin, BaseMonthArchiveView):
  428. """
  429. List of objects published in a given month.
  430. """
  431. template_name_suffix = '_archive_month'
  432. class BaseWeekArchiveView(YearMixin, WeekMixin, BaseDateListView):
  433. """
  434. List of objects published in a given week.
  435. """
  436. def get_dated_items(self):
  437. """
  438. Return (date_list, items, extra_context) for this request.
  439. """
  440. year = self.get_year()
  441. week = self.get_week()
  442. date_field = self.get_date_field()
  443. week_format = self.get_week_format()
  444. week_start = {
  445. '%W': '1',
  446. '%U': '0',
  447. }[week_format]
  448. date = _date_from_string(year, self.get_year_format(),
  449. week_start, '%w',
  450. week, week_format)
  451. since = self._make_date_lookup_arg(date)
  452. until = self._make_date_lookup_arg(self._get_next_week(date))
  453. lookup_kwargs = {
  454. '%s__gte' % date_field: since,
  455. '%s__lt' % date_field: until,
  456. }
  457. qs = self.get_dated_queryset(**lookup_kwargs)
  458. return (None, qs, {
  459. 'week': date,
  460. 'next_week': self.get_next_week(date),
  461. 'previous_week': self.get_previous_week(date),
  462. })
  463. class WeekArchiveView(MultipleObjectTemplateResponseMixin, BaseWeekArchiveView):
  464. """
  465. List of objects published in a given week.
  466. """
  467. template_name_suffix = '_archive_week'
  468. class BaseDayArchiveView(YearMixin, MonthMixin, DayMixin, BaseDateListView):
  469. """
  470. List of objects published on a given day.
  471. """
  472. def get_dated_items(self):
  473. """
  474. Return (date_list, items, extra_context) for this request.
  475. """
  476. year = self.get_year()
  477. month = self.get_month()
  478. day = self.get_day()
  479. date = _date_from_string(year, self.get_year_format(),
  480. month, self.get_month_format(),
  481. day, self.get_day_format())
  482. return self._get_dated_items(date)
  483. def _get_dated_items(self, date):
  484. """
  485. Do the actual heavy lifting of getting the dated items; this accepts a
  486. date object so that TodayArchiveView can be trivial.
  487. """
  488. lookup_kwargs = self._make_single_date_lookup(date)
  489. qs = self.get_dated_queryset(**lookup_kwargs)
  490. return (None, qs, {
  491. 'day': date,
  492. 'previous_day': self.get_previous_day(date),
  493. 'next_day': self.get_next_day(date),
  494. 'previous_month': self.get_previous_month(date),
  495. 'next_month': self.get_next_month(date)
  496. })
  497. class DayArchiveView(MultipleObjectTemplateResponseMixin, BaseDayArchiveView):
  498. """
  499. List of objects published on a given day.
  500. """
  501. template_name_suffix = "_archive_day"
  502. class BaseTodayArchiveView(BaseDayArchiveView):
  503. """
  504. List of objects published today.
  505. """
  506. def get_dated_items(self):
  507. """
  508. Return (date_list, items, extra_context) for this request.
  509. """
  510. return self._get_dated_items(datetime.date.today())
  511. class TodayArchiveView(MultipleObjectTemplateResponseMixin, BaseTodayArchiveView):
  512. """
  513. List of objects published today.
  514. """
  515. template_name_suffix = "_archive_day"
  516. class BaseDateDetailView(YearMixin, MonthMixin, DayMixin, DateMixin, BaseDetailView):
  517. """
  518. Detail view of a single object on a single date; this differs from the
  519. standard DetailView by accepting a year/month/day in the URL.
  520. """
  521. def get_object(self, queryset=None):
  522. """
  523. Get the object this request displays.
  524. """
  525. year = self.get_year()
  526. month = self.get_month()
  527. day = self.get_day()
  528. date = _date_from_string(year, self.get_year_format(),
  529. month, self.get_month_format(),
  530. day, self.get_day_format())
  531. # Use a custom queryset if provided
  532. qs = queryset or self.get_queryset()
  533. if not self.get_allow_future() and date > datetime.date.today():
  534. raise Http404(_("Future %(verbose_name_plural)s not available because %(class_name)s.allow_future is False.") % {
  535. 'verbose_name_plural': qs.model._meta.verbose_name_plural,
  536. 'class_name': self.__class__.__name__,
  537. })
  538. # Filter down a queryset from self.queryset using the date from the
  539. # URL. This'll get passed as the queryset to DetailView.get_object,
  540. # which'll handle the 404
  541. lookup_kwargs = self._make_single_date_lookup(date)
  542. qs = qs.filter(**lookup_kwargs)
  543. return super(BaseDetailView, self).get_object(queryset=qs)
  544. class DateDetailView(SingleObjectTemplateResponseMixin, BaseDateDetailView):
  545. """
  546. Detail view of a single object on a single date; this differs from the
  547. standard DetailView by accepting a year/month/day in the URL.
  548. """
  549. template_name_suffix = '_detail'
  550. def _date_from_string(year, year_format, month='', month_format='', day='', day_format='', delim='__'):
  551. """
  552. Helper: get a datetime.date object given a format string and a year,
  553. month, and day (only year is mandatory). Raise a 404 for an invalid date.
  554. """
  555. format = delim.join((year_format, month_format, day_format))
  556. datestr = delim.join((year, month, day))
  557. try:
  558. return datetime.datetime.strptime(force_str(datestr), format).date()
  559. except ValueError:
  560. raise Http404(_("Invalid date string '%(datestr)s' given format '%(format)s'") % {
  561. 'datestr': datestr,
  562. 'format': format,
  563. })
  564. def _get_next_prev(generic_view, date, is_previous, period):
  565. """
  566. Helper: Get the next or the previous valid date. The idea is to allow
  567. links on month/day views to never be 404s by never providing a date
  568. that'll be invalid for the given view.
  569. This is a bit complicated since it handles different intervals of time,
  570. hence the coupling to generic_view.
  571. However in essence the logic comes down to:
  572. * If allow_empty and allow_future are both true, this is easy: just
  573. return the naive result (just the next/previous day/week/month,
  574. regardless of object existence.)
  575. * If allow_empty is true, allow_future is false, and the naive result
  576. isn't in the future, then return it; otherwise return None.
  577. * If allow_empty is false and allow_future is true, return the next
  578. date *that contains a valid object*, even if it's in the future. If
  579. there are no next objects, return None.
  580. * If allow_empty is false and allow_future is false, return the next
  581. date that contains a valid object. If that date is in the future, or
  582. if there are no next objects, return None.
  583. """
  584. date_field = generic_view.get_date_field()
  585. allow_empty = generic_view.get_allow_empty()
  586. allow_future = generic_view.get_allow_future()
  587. get_current = getattr(generic_view, '_get_current_%s' % period)
  588. get_next = getattr(generic_view, '_get_next_%s' % period)
  589. # Bounds of the current interval
  590. start, end = get_current(date), get_next(date)
  591. # If allow_empty is True, the naive result will be valid
  592. if allow_empty:
  593. if is_previous:
  594. result = get_current(start - datetime.timedelta(days=1))
  595. else:
  596. result = end
  597. if allow_future or result <= timezone_today():
  598. return result
  599. else:
  600. return None
  601. # Otherwise, we'll need to go to the database to look for an object
  602. # whose date_field is at least (greater than/less than) the given
  603. # naive result
  604. else:
  605. # Construct a lookup and an ordering depending on whether we're doing
  606. # a previous date or a next date lookup.
  607. if is_previous:
  608. lookup = {'%s__lt' % date_field: generic_view._make_date_lookup_arg(start)}
  609. ordering = '-%s' % date_field
  610. else:
  611. lookup = {'%s__gte' % date_field: generic_view._make_date_lookup_arg(end)}
  612. ordering = date_field
  613. # Filter out objects in the future if appropriate.
  614. if not allow_future:
  615. # Fortunately, to match the implementation of allow_future,
  616. # we need __lte, which doesn't conflict with __lt above.
  617. if generic_view.uses_datetime_field:
  618. now = timezone.now()
  619. else:
  620. now = timezone_today()
  621. lookup['%s__lte' % date_field] = now
  622. qs = generic_view.get_queryset().filter(**lookup).order_by(ordering)
  623. # Snag the first object from the queryset; if it doesn't exist that
  624. # means there's no next/previous link available.
  625. try:
  626. result = getattr(qs[0], date_field)
  627. except IndexError:
  628. return None
  629. # Convert datetimes to dates in the current time zone.
  630. if generic_view.uses_datetime_field:
  631. if settings.USE_TZ:
  632. result = timezone.localtime(result)
  633. result = result.date()
  634. # Return the first day of the period.
  635. return get_current(result)
  636. def timezone_today():
  637. """
  638. Return the current date in the current time zone.
  639. """
  640. if settings.USE_TZ:
  641. return timezone.localtime(timezone.now()).date()
  642. else:
  643. return datetime.date.today()