Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/views/generic/dates.py: 33%

333 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1import datetime 

2 

3from django.conf import settings 

4from django.core.exceptions import ImproperlyConfigured 

5from django.db import models 

6from django.http import Http404 

7from django.utils import timezone 

8from django.utils.functional import cached_property 

9from django.utils.translation import gettext as _ 

10from django.views.generic.base import View 

11from django.views.generic.detail import ( 

12 BaseDetailView, 

13 SingleObjectTemplateResponseMixin, 

14) 

15from django.views.generic.list import ( 

16 MultipleObjectMixin, 

17 MultipleObjectTemplateResponseMixin, 

18) 

19 

20 

21class YearMixin: 

22 """Mixin for views manipulating year-based data.""" 

23 

24 year_format = "%Y" 

25 year = None 

26 

27 def get_year_format(self): 

28 """ 

29 Get a year format string in strptime syntax to be used to parse the 

30 year from url variables. 

31 """ 

32 return self.year_format 

33 

34 def get_year(self): 

35 """Return the year for which this view should display data.""" 

36 year = self.year 

37 if year is None: 

38 try: 

39 year = self.kwargs["year"] 

40 except KeyError: 

41 try: 

42 year = self.request.GET["year"] 

43 except KeyError: 

44 raise Http404(_("No year specified")) 

45 return year 

46 

47 def get_next_year(self, date): 

48 """Get the next valid year.""" 

49 return _get_next_prev(self, date, is_previous=False, period="year") 

50 

51 def get_previous_year(self, date): 

52 """Get the previous valid year.""" 

53 return _get_next_prev(self, date, is_previous=True, period="year") 

54 

55 def _get_next_year(self, date): 

56 """ 

57 Return the start date of the next interval. 

58 

59 The interval is defined by start date <= item date < next start date. 

60 """ 

61 try: 

62 return date.replace(year=date.year + 1, month=1, day=1) 

63 except ValueError: 

64 raise Http404(_("Date out of range")) 

65 

66 def _get_current_year(self, date): 

67 """Return the start date of the current interval.""" 

68 return date.replace(month=1, day=1) 

69 

70 

71class MonthMixin: 

72 """Mixin for views manipulating month-based data.""" 

73 

74 month_format = "%b" 

75 month = None 

76 

77 def get_month_format(self): 

78 """ 

79 Get a month format string in strptime syntax to be used to parse the 

80 month from url variables. 

81 """ 

82 return self.month_format 

83 

84 def get_month(self): 

85 """Return the month for which this view should display data.""" 

86 month = self.month 

87 if month is None: 

88 try: 

89 month = self.kwargs["month"] 

90 except KeyError: 

91 try: 

92 month = self.request.GET["month"] 

93 except KeyError: 

94 raise Http404(_("No month specified")) 

95 return month 

96 

97 def get_next_month(self, date): 

98 """Get the next valid month.""" 

99 return _get_next_prev(self, date, is_previous=False, period="month") 

100 

101 def get_previous_month(self, date): 

102 """Get the previous valid month.""" 

103 return _get_next_prev(self, date, is_previous=True, period="month") 

104 

105 def _get_next_month(self, date): 

106 """ 

107 Return the start date of the next interval. 

108 

109 The interval is defined by start date <= item date < next start date. 

110 """ 

111 if date.month == 12: 

112 try: 

113 return date.replace(year=date.year + 1, month=1, day=1) 

114 except ValueError: 

115 raise Http404(_("Date out of range")) 

116 else: 

117 return date.replace(month=date.month + 1, day=1) 

118 

119 def _get_current_month(self, date): 

120 """Return the start date of the previous interval.""" 

121 return date.replace(day=1) 

122 

123 

124class DayMixin: 

125 """Mixin for views manipulating day-based data.""" 

126 

127 day_format = "%d" 

128 day = None 

129 

130 def get_day_format(self): 

131 """ 

132 Get a day format string in strptime syntax to be used to parse the day 

133 from url variables. 

134 """ 

135 return self.day_format 

136 

137 def get_day(self): 

138 """Return the day for which this view should display data.""" 

139 day = self.day 

140 if day is None: 

141 try: 

142 day = self.kwargs["day"] 

143 except KeyError: 

144 try: 

145 day = self.request.GET["day"] 

146 except KeyError: 

147 raise Http404(_("No day specified")) 

148 return day 

149 

150 def get_next_day(self, date): 

151 """Get the next valid day.""" 

152 return _get_next_prev(self, date, is_previous=False, period="day") 

153 

154 def get_previous_day(self, date): 

155 """Get the previous valid day.""" 

156 return _get_next_prev(self, date, is_previous=True, period="day") 

157 

158 def _get_next_day(self, date): 

159 """ 

160 Return the start date of the next interval. 

161 

162 The interval is defined by start date <= item date < next start date. 

163 """ 

164 return date + datetime.timedelta(days=1) 

165 

166 def _get_current_day(self, date): 

167 """Return the start date of the current interval.""" 

168 return date 

169 

170 

171class WeekMixin: 

172 """Mixin for views manipulating week-based data.""" 

173 

174 week_format = "%U" 

175 week = None 

176 

177 def get_week_format(self): 

178 """ 

179 Get a week format string in strptime syntax to be used to parse the 

180 week from url variables. 

181 """ 

182 return self.week_format 

183 

184 def get_week(self): 

185 """Return the week for which this view should display data.""" 

186 week = self.week 

187 if week is None: 

188 try: 

189 week = self.kwargs["week"] 

190 except KeyError: 

191 try: 

192 week = self.request.GET["week"] 

193 except KeyError: 

194 raise Http404(_("No week specified")) 

195 return week 

196 

197 def get_next_week(self, date): 

198 """Get the next valid week.""" 

199 return _get_next_prev(self, date, is_previous=False, period="week") 

200 

201 def get_previous_week(self, date): 

202 """Get the previous valid week.""" 

203 return _get_next_prev(self, date, is_previous=True, period="week") 

204 

205 def _get_next_week(self, date): 

206 """ 

207 Return the start date of the next interval. 

208 

209 The interval is defined by start date <= item date < next start date. 

210 """ 

211 try: 

212 return date + datetime.timedelta(days=7 - self._get_weekday(date)) 

213 except OverflowError: 

214 raise Http404(_("Date out of range")) 

215 

216 def _get_current_week(self, date): 

217 """Return the start date of the current interval.""" 

218 return date - datetime.timedelta(self._get_weekday(date)) 

219 

220 def _get_weekday(self, date): 

221 """ 

222 Return the weekday for a given date. 

223 

224 The first day according to the week format is 0 and the last day is 6. 

225 """ 

226 week_format = self.get_week_format() 

227 if week_format in {"%W", "%V"}: # week starts on Monday 

228 return date.weekday() 

229 elif week_format == "%U": # week starts on Sunday 

230 return (date.weekday() + 1) % 7 

231 else: 

232 raise ValueError("unknown week format: %s" % week_format) 

233 

234 

235class DateMixin: 

236 """Mixin class for views manipulating date-based data.""" 

237 

238 date_field = None 

239 allow_future = False 

240 

241 def get_date_field(self): 

242 """Get the name of the date field to be used to filter by.""" 

243 if self.date_field is None: 

244 raise ImproperlyConfigured( 

245 "%s.date_field is required." % self.__class__.__name__ 

246 ) 

247 return self.date_field 

248 

249 def get_allow_future(self): 

250 """ 

251 Return `True` if the view should be allowed to display objects from 

252 the future. 

253 """ 

254 return self.allow_future 

255 

256 # Note: the following three methods only work in subclasses that also 

257 # inherit SingleObjectMixin or MultipleObjectMixin. 

258 

259 @cached_property 

260 def uses_datetime_field(self): 

261 """ 

262 Return `True` if the date field is a `DateTimeField` and `False` 

263 if it's a `DateField`. 

264 """ 

265 model = self.get_queryset().model if self.model is None else self.model 

266 field = model._meta.get_field(self.get_date_field()) 

267 return isinstance(field, models.DateTimeField) 

268 

269 def _make_date_lookup_arg(self, value): 

270 """ 

271 Convert a date into a datetime when the date field is a DateTimeField. 

272 

273 When time zone support is enabled, `date` is assumed to be in the 

274 current time zone, so that displayed items are consistent with the URL. 

275 """ 

276 if self.uses_datetime_field: 

277 value = datetime.datetime.combine(value, datetime.time.min) 

278 if settings.USE_TZ: 

279 value = timezone.make_aware(value) 

280 return value 

281 

282 def _make_single_date_lookup(self, date): 

283 """ 

284 Get the lookup kwargs for filtering on a single date. 

285 

286 If the date field is a DateTimeField, we can't just filter on 

287 date_field=date because that doesn't take the time into account. 

288 """ 

289 date_field = self.get_date_field() 

290 if self.uses_datetime_field: 

291 since = self._make_date_lookup_arg(date) 

292 until = self._make_date_lookup_arg(date + datetime.timedelta(days=1)) 

293 return { 

294 "%s__gte" % date_field: since, 

295 "%s__lt" % date_field: until, 

296 } 

297 else: 

298 # Skip self._make_date_lookup_arg, it's a no-op in this branch. 

299 return {date_field: date} 

300 

301 

302class BaseDateListView(MultipleObjectMixin, DateMixin, View): 

303 """Abstract base class for date-based views displaying a list of objects.""" 

304 

305 allow_empty = False 

306 date_list_period = "year" 

307 

308 def get(self, request, *args, **kwargs): 

309 self.date_list, self.object_list, extra_context = self.get_dated_items() 

310 context = self.get_context_data( 

311 object_list=self.object_list, date_list=self.date_list, **extra_context 

312 ) 

313 return self.render_to_response(context) 

314 

315 def get_dated_items(self): 

316 """Obtain the list of dates and items.""" 

317 raise NotImplementedError( 

318 "A DateView must provide an implementation of get_dated_items()" 

319 ) 

320 

321 def get_ordering(self): 

322 """ 

323 Return the field or fields to use for ordering the queryset; use the 

324 date field by default. 

325 """ 

326 return "-%s" % self.get_date_field() if self.ordering is None else self.ordering 

327 

328 def get_dated_queryset(self, **lookup): 

329 """ 

330 Get a queryset properly filtered according to `allow_future` and any 

331 extra lookup kwargs. 

332 """ 

333 qs = self.get_queryset().filter(**lookup) 

334 date_field = self.get_date_field() 

335 allow_future = self.get_allow_future() 

336 allow_empty = self.get_allow_empty() 

337 paginate_by = self.get_paginate_by(qs) 

338 

339 if not allow_future: 

340 now = timezone.now() if self.uses_datetime_field else timezone_today() 

341 qs = qs.filter(**{"%s__lte" % date_field: now}) 

342 

343 if not allow_empty: 

344 # When pagination is enabled, it's better to do a cheap query 

345 # than to load the unpaginated queryset in memory. 

346 is_empty = not qs if paginate_by is None else not qs.exists() 

347 if is_empty: 

348 raise Http404( 

349 _("No %(verbose_name_plural)s available") 

350 % { 

351 "verbose_name_plural": qs.model._meta.verbose_name_plural, 

352 } 

353 ) 

354 

355 return qs 

356 

357 def get_date_list_period(self): 

358 """ 

359 Get the aggregation period for the list of dates: 'year', 'month', or 

360 'day'. 

361 """ 

362 return self.date_list_period 

363 

364 def get_date_list(self, queryset, date_type=None, ordering="ASC"): 

365 """ 

366 Get a date list by calling `queryset.dates/datetimes()`, checking 

367 along the way for empty lists that aren't allowed. 

368 """ 

369 date_field = self.get_date_field() 

370 allow_empty = self.get_allow_empty() 

371 if date_type is None: 

372 date_type = self.get_date_list_period() 

373 

374 if self.uses_datetime_field: 

375 date_list = queryset.datetimes(date_field, date_type, ordering) 

376 else: 

377 date_list = queryset.dates(date_field, date_type, ordering) 

378 if date_list is not None and not date_list and not allow_empty: 

379 raise Http404( 

380 _("No %(verbose_name_plural)s available") 

381 % { 

382 "verbose_name_plural": queryset.model._meta.verbose_name_plural, 

383 } 

384 ) 

385 

386 return date_list 

387 

388 

389class BaseArchiveIndexView(BaseDateListView): 

390 """ 

391 Base class for archives of date-based items. Requires a response mixin. 

392 """ 

393 

394 context_object_name = "latest" 

395 

396 def get_dated_items(self): 

397 """Return (date_list, items, extra_context) for this request.""" 

398 qs = self.get_dated_queryset() 

399 date_list = self.get_date_list(qs, ordering="DESC") 

400 

401 if not date_list: 

402 qs = qs.none() 

403 

404 return (date_list, qs, {}) 

405 

406 

407class ArchiveIndexView(MultipleObjectTemplateResponseMixin, BaseArchiveIndexView): 

408 """Top-level archive of date-based items.""" 

409 

410 template_name_suffix = "_archive" 

411 

412 

413class BaseYearArchiveView(YearMixin, BaseDateListView): 

414 """List of objects published in a given year.""" 

415 

416 date_list_period = "month" 

417 make_object_list = False 

418 

419 def get_dated_items(self): 

420 """Return (date_list, items, extra_context) for this request.""" 

421 year = self.get_year() 

422 

423 date_field = self.get_date_field() 

424 date = _date_from_string(year, self.get_year_format()) 

425 

426 since = self._make_date_lookup_arg(date) 

427 until = self._make_date_lookup_arg(self._get_next_year(date)) 

428 lookup_kwargs = { 

429 "%s__gte" % date_field: since, 

430 "%s__lt" % date_field: until, 

431 } 

432 

433 qs = self.get_dated_queryset(**lookup_kwargs) 

434 date_list = self.get_date_list(qs) 

435 

436 if not self.get_make_object_list(): 

437 # We need this to be a queryset since parent classes introspect it 

438 # to find information about the model. 

439 qs = qs.none() 

440 

441 return ( 

442 date_list, 

443 qs, 

444 { 

445 "year": date, 

446 "next_year": self.get_next_year(date), 

447 "previous_year": self.get_previous_year(date), 

448 }, 

449 ) 

450 

451 def get_make_object_list(self): 

452 """ 

453 Return `True` if this view should contain the full list of objects in 

454 the given year. 

455 """ 

456 return self.make_object_list 

457 

458 

459class YearArchiveView(MultipleObjectTemplateResponseMixin, BaseYearArchiveView): 

460 """List of objects published in a given year.""" 

461 

462 template_name_suffix = "_archive_year" 

463 

464 

465class BaseMonthArchiveView(YearMixin, MonthMixin, BaseDateListView): 

466 """List of objects published in a given month.""" 

467 

468 date_list_period = "day" 

469 

470 def get_dated_items(self): 

471 """Return (date_list, items, extra_context) for this request.""" 

472 year = self.get_year() 

473 month = self.get_month() 

474 

475 date_field = self.get_date_field() 

476 date = _date_from_string( 

477 year, self.get_year_format(), month, self.get_month_format() 

478 ) 

479 

480 since = self._make_date_lookup_arg(date) 

481 until = self._make_date_lookup_arg(self._get_next_month(date)) 

482 lookup_kwargs = { 

483 "%s__gte" % date_field: since, 

484 "%s__lt" % date_field: until, 

485 } 

486 

487 qs = self.get_dated_queryset(**lookup_kwargs) 

488 date_list = self.get_date_list(qs) 

489 

490 return ( 

491 date_list, 

492 qs, 

493 { 

494 "month": date, 

495 "next_month": self.get_next_month(date), 

496 "previous_month": self.get_previous_month(date), 

497 }, 

498 ) 

499 

500 

501class MonthArchiveView(MultipleObjectTemplateResponseMixin, BaseMonthArchiveView): 

502 """List of objects published in a given month.""" 

503 

504 template_name_suffix = "_archive_month" 

505 

506 

507class BaseWeekArchiveView(YearMixin, WeekMixin, BaseDateListView): 

508 """List of objects published in a given week.""" 

509 

510 def get_dated_items(self): 

511 """Return (date_list, items, extra_context) for this request.""" 

512 year = self.get_year() 

513 week = self.get_week() 

514 

515 date_field = self.get_date_field() 

516 week_format = self.get_week_format() 

517 week_choices = {"%W": "1", "%U": "0", "%V": "1"} 

518 try: 

519 week_start = week_choices[week_format] 

520 except KeyError: 

521 raise ValueError( 

522 "Unknown week format %r. Choices are: %s" 

523 % ( 

524 week_format, 

525 ", ".join(sorted(week_choices)), 

526 ) 

527 ) 

528 year_format = self.get_year_format() 

529 if week_format == "%V" and year_format != "%G": 

530 raise ValueError( 

531 "ISO week directive '%s' is incompatible with the year " 

532 "directive '%s'. Use the ISO year '%%G' instead." 

533 % ( 

534 week_format, 

535 year_format, 

536 ) 

537 ) 

538 date = _date_from_string(year, year_format, week_start, "%w", week, week_format) 

539 since = self._make_date_lookup_arg(date) 

540 until = self._make_date_lookup_arg(self._get_next_week(date)) 

541 lookup_kwargs = { 

542 "%s__gte" % date_field: since, 

543 "%s__lt" % date_field: until, 

544 } 

545 

546 qs = self.get_dated_queryset(**lookup_kwargs) 

547 

548 return ( 

549 None, 

550 qs, 

551 { 

552 "week": date, 

553 "next_week": self.get_next_week(date), 

554 "previous_week": self.get_previous_week(date), 

555 }, 

556 ) 

557 

558 

559class WeekArchiveView(MultipleObjectTemplateResponseMixin, BaseWeekArchiveView): 

560 """List of objects published in a given week.""" 

561 

562 template_name_suffix = "_archive_week" 

563 

564 

565class BaseDayArchiveView(YearMixin, MonthMixin, DayMixin, BaseDateListView): 

566 """List of objects published on a given day.""" 

567 

568 def get_dated_items(self): 

569 """Return (date_list, items, extra_context) for this request.""" 

570 year = self.get_year() 

571 month = self.get_month() 

572 day = self.get_day() 

573 

574 date = _date_from_string( 

575 year, 

576 self.get_year_format(), 

577 month, 

578 self.get_month_format(), 

579 day, 

580 self.get_day_format(), 

581 ) 

582 

583 return self._get_dated_items(date) 

584 

585 def _get_dated_items(self, date): 

586 """ 

587 Do the actual heavy lifting of getting the dated items; this accepts a 

588 date object so that TodayArchiveView can be trivial. 

589 """ 

590 lookup_kwargs = self._make_single_date_lookup(date) 

591 qs = self.get_dated_queryset(**lookup_kwargs) 

592 

593 return ( 

594 None, 

595 qs, 

596 { 

597 "day": date, 

598 "previous_day": self.get_previous_day(date), 

599 "next_day": self.get_next_day(date), 

600 "previous_month": self.get_previous_month(date), 

601 "next_month": self.get_next_month(date), 

602 }, 

603 ) 

604 

605 

606class DayArchiveView(MultipleObjectTemplateResponseMixin, BaseDayArchiveView): 

607 """List of objects published on a given day.""" 

608 

609 template_name_suffix = "_archive_day" 

610 

611 

612class BaseTodayArchiveView(BaseDayArchiveView): 

613 """List of objects published today.""" 

614 

615 def get_dated_items(self): 

616 """Return (date_list, items, extra_context) for this request.""" 

617 return self._get_dated_items(datetime.date.today()) 

618 

619 

620class TodayArchiveView(MultipleObjectTemplateResponseMixin, BaseTodayArchiveView): 

621 """List of objects published today.""" 

622 

623 template_name_suffix = "_archive_day" 

624 

625 

626class BaseDateDetailView(YearMixin, MonthMixin, DayMixin, DateMixin, BaseDetailView): 

627 """ 

628 Detail view of a single object on a single date; this differs from the 

629 standard DetailView by accepting a year/month/day in the URL. 

630 """ 

631 

632 def get_object(self, queryset=None): 

633 """Get the object this request displays.""" 

634 year = self.get_year() 

635 month = self.get_month() 

636 day = self.get_day() 

637 date = _date_from_string( 

638 year, 

639 self.get_year_format(), 

640 month, 

641 self.get_month_format(), 

642 day, 

643 self.get_day_format(), 

644 ) 

645 

646 # Use a custom queryset if provided 

647 qs = self.get_queryset() if queryset is None else queryset 

648 

649 if not self.get_allow_future() and date > datetime.date.today(): 

650 raise Http404( 

651 _( 

652 "Future %(verbose_name_plural)s not available because " 

653 "%(class_name)s.allow_future is False." 

654 ) 

655 % { 

656 "verbose_name_plural": qs.model._meta.verbose_name_plural, 

657 "class_name": self.__class__.__name__, 

658 } 

659 ) 

660 

661 # Filter down a queryset from self.queryset using the date from the 

662 # URL. This'll get passed as the queryset to DetailView.get_object, 

663 # which'll handle the 404 

664 lookup_kwargs = self._make_single_date_lookup(date) 

665 qs = qs.filter(**lookup_kwargs) 

666 

667 return super().get_object(queryset=qs) 

668 

669 

670class DateDetailView(SingleObjectTemplateResponseMixin, BaseDateDetailView): 

671 """ 

672 Detail view of a single object on a single date; this differs from the 

673 standard DetailView by accepting a year/month/day in the URL. 

674 """ 

675 

676 template_name_suffix = "_detail" 

677 

678 

679def _date_from_string( 

680 year, year_format, month="", month_format="", day="", day_format="", delim="__" 

681): 

682 """ 

683 Get a datetime.date object given a format string and a year, month, and day 

684 (only year is mandatory). Raise a 404 for an invalid date. 

685 """ 

686 format = year_format + delim + month_format + delim + day_format 

687 datestr = str(year) + delim + str(month) + delim + str(day) 

688 try: 

689 return datetime.datetime.strptime(datestr, format).date() 

690 except ValueError: 

691 raise Http404( 

692 _("Invalid date string “%(datestr)s” given format “%(format)s”") 

693 % { 

694 "datestr": datestr, 

695 "format": format, 

696 } 

697 ) 

698 

699 

700def _get_next_prev(generic_view, date, is_previous, period): 

701 """ 

702 Get the next or the previous valid date. The idea is to allow links on 

703 month/day views to never be 404s by never providing a date that'll be 

704 invalid for the given view. 

705 

706 This is a bit complicated since it handles different intervals of time, 

707 hence the coupling to generic_view. 

708 

709 However in essence the logic comes down to: 

710 

711 * If allow_empty and allow_future are both true, this is easy: just 

712 return the naive result (just the next/previous day/week/month, 

713 regardless of object existence.) 

714 

715 * If allow_empty is true, allow_future is false, and the naive result 

716 isn't in the future, then return it; otherwise return None. 

717 

718 * If allow_empty is false and allow_future is true, return the next 

719 date *that contains a valid object*, even if it's in the future. If 

720 there are no next objects, return None. 

721 

722 * If allow_empty is false and allow_future is false, return the next 

723 date that contains a valid object. If that date is in the future, or 

724 if there are no next objects, return None. 

725 """ 

726 date_field = generic_view.get_date_field() 

727 allow_empty = generic_view.get_allow_empty() 

728 allow_future = generic_view.get_allow_future() 

729 

730 get_current = getattr(generic_view, "_get_current_%s" % period) 

731 get_next = getattr(generic_view, "_get_next_%s" % period) 

732 

733 # Bounds of the current interval 

734 start, end = get_current(date), get_next(date) 

735 

736 # If allow_empty is True, the naive result will be valid 

737 if allow_empty: 

738 if is_previous: 

739 result = get_current(start - datetime.timedelta(days=1)) 

740 else: 

741 result = end 

742 

743 if allow_future or result <= timezone_today(): 

744 return result 

745 else: 

746 return None 

747 

748 # Otherwise, we'll need to go to the database to look for an object 

749 # whose date_field is at least (greater than/less than) the given 

750 # naive result 

751 else: 

752 # Construct a lookup and an ordering depending on whether we're doing 

753 # a previous date or a next date lookup. 

754 if is_previous: 

755 lookup = {"%s__lt" % date_field: generic_view._make_date_lookup_arg(start)} 

756 ordering = "-%s" % date_field 

757 else: 

758 lookup = {"%s__gte" % date_field: generic_view._make_date_lookup_arg(end)} 

759 ordering = date_field 

760 

761 # Filter out objects in the future if appropriate. 

762 if not allow_future: 

763 # Fortunately, to match the implementation of allow_future, 

764 # we need __lte, which doesn't conflict with __lt above. 

765 if generic_view.uses_datetime_field: 

766 now = timezone.now() 

767 else: 

768 now = timezone_today() 

769 lookup["%s__lte" % date_field] = now 

770 

771 qs = generic_view.get_queryset().filter(**lookup).order_by(ordering) 

772 

773 # Snag the first object from the queryset; if it doesn't exist that 

774 # means there's no next/previous link available. 

775 try: 

776 result = getattr(qs[0], date_field) 

777 except IndexError: 

778 return None 

779 

780 # Convert datetimes to dates in the current time zone. 

781 if generic_view.uses_datetime_field: 

782 if settings.USE_TZ: 

783 result = timezone.localtime(result) 

784 result = result.date() 

785 

786 # Return the first day of the period. 

787 return get_current(result) 

788 

789 

790def timezone_today(): 

791 """Return the current date in the current time zone.""" 

792 if settings.USE_TZ: 

793 return timezone.localdate() 

794 else: 

795 return datetime.date.today()