summaryrefslogtreecommitdiff
path: root/lib/python2.7/site-packages/django/views/generic/dates.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/python2.7/site-packages/django/views/generic/dates.py')
-rw-r--r--lib/python2.7/site-packages/django/views/generic/dates.py785
1 files changed, 785 insertions, 0 deletions
diff --git a/lib/python2.7/site-packages/django/views/generic/dates.py b/lib/python2.7/site-packages/django/views/generic/dates.py
new file mode 100644
index 0000000..1b8ad3e
--- /dev/null
+++ b/lib/python2.7/site-packages/django/views/generic/dates.py
@@ -0,0 +1,785 @@
+from __future__ import unicode_literals
+
+import datetime
+from django.conf import settings
+from django.db import models
+from django.core.exceptions import ImproperlyConfigured
+from django.http import Http404
+from django.utils.encoding import force_str, force_text
+from django.utils.functional import cached_property
+from django.utils.translation import ugettext as _
+from django.utils import timezone
+from django.views.generic.base import View
+from django.views.generic.detail import BaseDetailView, SingleObjectTemplateResponseMixin
+from django.views.generic.list import MultipleObjectMixin, MultipleObjectTemplateResponseMixin
+
+class YearMixin(object):
+ """
+ Mixin for views manipulating year-based data.
+ """
+ year_format = '%Y'
+ year = None
+
+ def get_year_format(self):
+ """
+ Get a year format string in strptime syntax to be used to parse the
+ year from url variables.
+ """
+ return self.year_format
+
+ def get_year(self):
+ """
+ Return the year for which this view should display data.
+ """
+ year = self.year
+ if year is None:
+ try:
+ year = self.kwargs['year']
+ except KeyError:
+ try:
+ year = self.request.GET['year']
+ except KeyError:
+ raise Http404(_("No year specified"))
+ return year
+
+ def get_next_year(self, date):
+ """
+ Get the next valid year.
+ """
+ return _get_next_prev(self, date, is_previous=False, period='year')
+
+ def get_previous_year(self, date):
+ """
+ Get the previous valid year.
+ """
+ return _get_next_prev(self, date, is_previous=True, period='year')
+
+ def _get_next_year(self, date):
+ """
+ Return the start date of the next interval.
+
+ The interval is defined by start date <= item date < next start date.
+ """
+ return date.replace(year=date.year + 1, month=1, day=1)
+
+ def _get_current_year(self, date):
+ """
+ Return the start date of the current interval.
+ """
+ return date.replace(month=1, day=1)
+
+
+class MonthMixin(object):
+ """
+ Mixin for views manipulating month-based data.
+ """
+ month_format = '%b'
+ month = None
+
+ def get_month_format(self):
+ """
+ Get a month format string in strptime syntax to be used to parse the
+ month from url variables.
+ """
+ return self.month_format
+
+ def get_month(self):
+ """
+ Return the month for which this view should display data.
+ """
+ month = self.month
+ if month is None:
+ try:
+ month = self.kwargs['month']
+ except KeyError:
+ try:
+ month = self.request.GET['month']
+ except KeyError:
+ raise Http404(_("No month specified"))
+ return month
+
+ def get_next_month(self, date):
+ """
+ Get the next valid month.
+ """
+ return _get_next_prev(self, date, is_previous=False, period='month')
+
+ def get_previous_month(self, date):
+ """
+ Get the previous valid month.
+ """
+ return _get_next_prev(self, date, is_previous=True, period='month')
+
+ def _get_next_month(self, date):
+ """
+ Return the start date of the next interval.
+
+ The interval is defined by start date <= item date < next start date.
+ """
+ if date.month == 12:
+ return date.replace(year=date.year + 1, month=1, day=1)
+ else:
+ return date.replace(month=date.month + 1, day=1)
+
+ def _get_current_month(self, date):
+ """
+ Return the start date of the previous interval.
+ """
+ return date.replace(day=1)
+
+
+class DayMixin(object):
+ """
+ Mixin for views manipulating day-based data.
+ """
+ day_format = '%d'
+ day = None
+
+ def get_day_format(self):
+ """
+ Get a day format string in strptime syntax to be used to parse the day
+ from url variables.
+ """
+ return self.day_format
+
+ def get_day(self):
+ """
+ Return the day for which this view should display data.
+ """
+ day = self.day
+ if day is None:
+ try:
+ day = self.kwargs['day']
+ except KeyError:
+ try:
+ day = self.request.GET['day']
+ except KeyError:
+ raise Http404(_("No day specified"))
+ return day
+
+ def get_next_day(self, date):
+ """
+ Get the next valid day.
+ """
+ return _get_next_prev(self, date, is_previous=False, period='day')
+
+ def get_previous_day(self, date):
+ """
+ Get the previous valid day.
+ """
+ return _get_next_prev(self, date, is_previous=True, period='day')
+
+ def _get_next_day(self, date):
+ """
+ Return the start date of the next interval.
+
+ The interval is defined by start date <= item date < next start date.
+ """
+ return date + datetime.timedelta(days=1)
+
+ def _get_current_day(self, date):
+ """
+ Return the start date of the current interval.
+ """
+ return date
+
+
+class WeekMixin(object):
+ """
+ Mixin for views manipulating week-based data.
+ """
+ week_format = '%U'
+ week = None
+
+ def get_week_format(self):
+ """
+ Get a week format string in strptime syntax to be used to parse the
+ week from url variables.
+ """
+ return self.week_format
+
+ def get_week(self):
+ """
+ Return the week for which this view should display data
+ """
+ week = self.week
+ if week is None:
+ try:
+ week = self.kwargs['week']
+ except KeyError:
+ try:
+ week = self.request.GET['week']
+ except KeyError:
+ raise Http404(_("No week specified"))
+ return week
+
+ def get_next_week(self, date):
+ """
+ Get the next valid week.
+ """
+ return _get_next_prev(self, date, is_previous=False, period='week')
+
+ def get_previous_week(self, date):
+ """
+ Get the previous valid week.
+ """
+ return _get_next_prev(self, date, is_previous=True, period='week')
+
+ def _get_next_week(self, date):
+ """
+ Return the start date of the next interval.
+
+ The interval is defined by start date <= item date < next start date.
+ """
+ return date + datetime.timedelta(days=7 - self._get_weekday(date))
+
+ def _get_current_week(self, date):
+ """
+ Return the start date of the current interval.
+ """
+ return date - datetime.timedelta(self._get_weekday(date))
+
+ def _get_weekday(self, date):
+ """
+ Return the weekday for a given date.
+
+ The first day according to the week format is 0 and the last day is 6.
+ """
+ week_format = self.get_week_format()
+ if week_format == '%W': # week starts on Monday
+ return date.weekday()
+ elif week_format == '%U': # week starts on Sunday
+ return (date.weekday() + 1) % 7
+ else:
+ raise ValueError("unknown week format: %s" % week_format)
+
+
+class DateMixin(object):
+ """
+ Mixin class for views manipulating date-based data.
+ """
+ date_field = None
+ allow_future = False
+
+ def get_date_field(self):
+ """
+ Get the name of the date field to be used to filter by.
+ """
+ if self.date_field is None:
+ raise ImproperlyConfigured("%s.date_field is required." % self.__class__.__name__)
+ return self.date_field
+
+ def get_allow_future(self):
+ """
+ Returns `True` if the view should be allowed to display objects from
+ the future.
+ """
+ return self.allow_future
+
+ # Note: the following three methods only work in subclasses that also
+ # inherit SingleObjectMixin or MultipleObjectMixin.
+
+ @cached_property
+ def uses_datetime_field(self):
+ """
+ Return `True` if the date field is a `DateTimeField` and `False`
+ if it's a `DateField`.
+ """
+ model = self.get_queryset().model if self.model is None else self.model
+ field = model._meta.get_field(self.get_date_field())
+ return isinstance(field, models.DateTimeField)
+
+ def _make_date_lookup_arg(self, value):
+ """
+ Convert a date into a datetime when the date field is a DateTimeField.
+
+ When time zone support is enabled, `date` is assumed to be in the
+ current time zone, so that displayed items are consistent with the URL.
+ """
+ if self.uses_datetime_field:
+ value = datetime.datetime.combine(value, datetime.time.min)
+ if settings.USE_TZ:
+ value = timezone.make_aware(value, timezone.get_current_timezone())
+ return value
+
+ def _make_single_date_lookup(self, date):
+ """
+ Get the lookup kwargs for filtering on a single date.
+
+ If the date field is a DateTimeField, we can't just filter on
+ date_field=date because that doesn't take the time into account.
+ """
+ date_field = self.get_date_field()
+ if self.uses_datetime_field:
+ since = self._make_date_lookup_arg(date)
+ until = self._make_date_lookup_arg(date + datetime.timedelta(days=1))
+ return {
+ '%s__gte' % date_field: since,
+ '%s__lt' % date_field: until,
+ }
+ else:
+ # Skip self._make_date_lookup_arg, it's a no-op in this branch.
+ return {date_field: date}
+
+
+class BaseDateListView(MultipleObjectMixin, DateMixin, View):
+ """
+ Abstract base class for date-based views displaying a list of objects.
+ """
+ allow_empty = False
+ date_list_period = 'year'
+
+ def get(self, request, *args, **kwargs):
+ self.date_list, self.object_list, extra_context = self.get_dated_items()
+ context = self.get_context_data(object_list=self.object_list,
+ date_list=self.date_list)
+ context.update(extra_context)
+ return self.render_to_response(context)
+
+ def get_dated_items(self):
+ """
+ Obtain the list of dates and items.
+ """
+ raise NotImplementedError('A DateView must provide an implementation of get_dated_items()')
+
+ def get_dated_queryset(self, ordering=None, **lookup):
+ """
+ Get a queryset properly filtered according to `allow_future` and any
+ extra lookup kwargs.
+ """
+ qs = self.get_queryset().filter(**lookup)
+ date_field = self.get_date_field()
+ allow_future = self.get_allow_future()
+ allow_empty = self.get_allow_empty()
+ paginate_by = self.get_paginate_by(qs)
+
+ if ordering is not None:
+ qs = qs.order_by(ordering)
+
+ if not allow_future:
+ now = timezone.now() if self.uses_datetime_field else timezone_today()
+ qs = qs.filter(**{'%s__lte' % date_field: now})
+
+ if not allow_empty:
+ # When pagination is enabled, it's better to do a cheap query
+ # than to load the unpaginated queryset in memory.
+ is_empty = len(qs) == 0 if paginate_by is None else not qs.exists()
+ if is_empty:
+ raise Http404(_("No %(verbose_name_plural)s available") % {
+ 'verbose_name_plural': force_text(qs.model._meta.verbose_name_plural)
+ })
+
+ return qs
+
+ def get_date_list_period(self):
+ """
+ Get the aggregation period for the list of dates: 'year', 'month', or 'day'.
+ """
+ return self.date_list_period
+
+ def get_date_list(self, queryset, date_type=None, ordering='ASC'):
+ """
+ Get a date list by calling `queryset.dates/datetimes()`, checking
+ along the way for empty lists that aren't allowed.
+ """
+ date_field = self.get_date_field()
+ allow_empty = self.get_allow_empty()
+ if date_type is None:
+ date_type = self.get_date_list_period()
+
+ if self.uses_datetime_field:
+ date_list = queryset.datetimes(date_field, date_type, ordering)
+ else:
+ date_list = queryset.dates(date_field, date_type, ordering)
+ if date_list is not None and not date_list and not allow_empty:
+ name = force_text(queryset.model._meta.verbose_name_plural)
+ raise Http404(_("No %(verbose_name_plural)s available") %
+ {'verbose_name_plural': name})
+
+ return date_list
+
+
+class BaseArchiveIndexView(BaseDateListView):
+ """
+ Base class for archives of date-based items.
+
+ Requires a response mixin.
+ """
+ context_object_name = 'latest'
+
+ def get_dated_items(self):
+ """
+ Return (date_list, items, extra_context) for this request.
+ """
+ qs = self.get_dated_queryset(ordering='-%s' % self.get_date_field())
+ date_list = self.get_date_list(qs, ordering='DESC')
+
+ if not date_list:
+ qs = qs.none()
+
+ return (date_list, qs, {})
+
+
+class ArchiveIndexView(MultipleObjectTemplateResponseMixin, BaseArchiveIndexView):
+ """
+ Top-level archive of date-based items.
+ """
+ template_name_suffix = '_archive'
+
+
+class BaseYearArchiveView(YearMixin, BaseDateListView):
+ """
+ List of objects published in a given year.
+ """
+ date_list_period = 'month'
+ make_object_list = False
+
+ def get_dated_items(self):
+ """
+ Return (date_list, items, extra_context) for this request.
+ """
+ year = self.get_year()
+
+ date_field = self.get_date_field()
+ date = _date_from_string(year, self.get_year_format())
+
+ since = self._make_date_lookup_arg(date)
+ until = self._make_date_lookup_arg(self._get_next_year(date))
+ lookup_kwargs = {
+ '%s__gte' % date_field: since,
+ '%s__lt' % date_field: until,
+ }
+
+ qs = self.get_dated_queryset(ordering='-%s' % date_field, **lookup_kwargs)
+ date_list = self.get_date_list(qs)
+
+ if not self.get_make_object_list():
+ # We need this to be a queryset since parent classes introspect it
+ # to find information about the model.
+ qs = qs.none()
+
+ return (date_list, qs, {
+ 'year': date,
+ 'next_year': self.get_next_year(date),
+ 'previous_year': self.get_previous_year(date),
+ })
+
+ def get_make_object_list(self):
+ """
+ Return `True` if this view should contain the full list of objects in
+ the given year.
+ """
+ return self.make_object_list
+
+
+class YearArchiveView(MultipleObjectTemplateResponseMixin, BaseYearArchiveView):
+ """
+ List of objects published in a given year.
+ """
+ template_name_suffix = '_archive_year'
+
+
+class BaseMonthArchiveView(YearMixin, MonthMixin, BaseDateListView):
+ """
+ List of objects published in a given month.
+ """
+ date_list_period = 'day'
+
+ def get_dated_items(self):
+ """
+ Return (date_list, items, extra_context) for this request.
+ """
+ year = self.get_year()
+ month = self.get_month()
+
+ date_field = self.get_date_field()
+ date = _date_from_string(year, self.get_year_format(),
+ month, self.get_month_format())
+
+ since = self._make_date_lookup_arg(date)
+ until = self._make_date_lookup_arg(self._get_next_month(date))
+ lookup_kwargs = {
+ '%s__gte' % date_field: since,
+ '%s__lt' % date_field: until,
+ }
+
+ qs = self.get_dated_queryset(**lookup_kwargs)
+ date_list = self.get_date_list(qs)
+
+ return (date_list, qs, {
+ 'month': date,
+ 'next_month': self.get_next_month(date),
+ 'previous_month': self.get_previous_month(date),
+ })
+
+
+class MonthArchiveView(MultipleObjectTemplateResponseMixin, BaseMonthArchiveView):
+ """
+ List of objects published in a given month.
+ """
+ template_name_suffix = '_archive_month'
+
+
+class BaseWeekArchiveView(YearMixin, WeekMixin, BaseDateListView):
+ """
+ List of objects published in a given week.
+ """
+
+ def get_dated_items(self):
+ """
+ Return (date_list, items, extra_context) for this request.
+ """
+ year = self.get_year()
+ week = self.get_week()
+
+ date_field = self.get_date_field()
+ week_format = self.get_week_format()
+ week_start = {
+ '%W': '1',
+ '%U': '0',
+ }[week_format]
+ date = _date_from_string(year, self.get_year_format(),
+ week_start, '%w',
+ week, week_format)
+
+ since = self._make_date_lookup_arg(date)
+ until = self._make_date_lookup_arg(self._get_next_week(date))
+ lookup_kwargs = {
+ '%s__gte' % date_field: since,
+ '%s__lt' % date_field: until,
+ }
+
+ qs = self.get_dated_queryset(**lookup_kwargs)
+
+ return (None, qs, {
+ 'week': date,
+ 'next_week': self.get_next_week(date),
+ 'previous_week': self.get_previous_week(date),
+ })
+
+
+class WeekArchiveView(MultipleObjectTemplateResponseMixin, BaseWeekArchiveView):
+ """
+ List of objects published in a given week.
+ """
+ template_name_suffix = '_archive_week'
+
+
+class BaseDayArchiveView(YearMixin, MonthMixin, DayMixin, BaseDateListView):
+ """
+ List of objects published on a given day.
+ """
+ def get_dated_items(self):
+ """
+ Return (date_list, items, extra_context) for this request.
+ """
+ year = self.get_year()
+ month = self.get_month()
+ day = self.get_day()
+
+ date = _date_from_string(year, self.get_year_format(),
+ month, self.get_month_format(),
+ day, self.get_day_format())
+
+ return self._get_dated_items(date)
+
+ def _get_dated_items(self, date):
+ """
+ Do the actual heavy lifting of getting the dated items; this accepts a
+ date object so that TodayArchiveView can be trivial.
+ """
+ lookup_kwargs = self._make_single_date_lookup(date)
+ qs = self.get_dated_queryset(**lookup_kwargs)
+
+ return (None, qs, {
+ 'day': date,
+ 'previous_day': self.get_previous_day(date),
+ 'next_day': self.get_next_day(date),
+ 'previous_month': self.get_previous_month(date),
+ 'next_month': self.get_next_month(date)
+ })
+
+
+class DayArchiveView(MultipleObjectTemplateResponseMixin, BaseDayArchiveView):
+ """
+ List of objects published on a given day.
+ """
+ template_name_suffix = "_archive_day"
+
+
+class BaseTodayArchiveView(BaseDayArchiveView):
+ """
+ List of objects published today.
+ """
+
+ def get_dated_items(self):
+ """
+ Return (date_list, items, extra_context) for this request.
+ """
+ return self._get_dated_items(datetime.date.today())
+
+
+class TodayArchiveView(MultipleObjectTemplateResponseMixin, BaseTodayArchiveView):
+ """
+ List of objects published today.
+ """
+ template_name_suffix = "_archive_day"
+
+
+class BaseDateDetailView(YearMixin, MonthMixin, DayMixin, DateMixin, BaseDetailView):
+ """
+ Detail view of a single object on a single date; this differs from the
+ standard DetailView by accepting a year/month/day in the URL.
+ """
+ def get_object(self, queryset=None):
+ """
+ Get the object this request displays.
+ """
+ year = self.get_year()
+ month = self.get_month()
+ day = self.get_day()
+ date = _date_from_string(year, self.get_year_format(),
+ month, self.get_month_format(),
+ day, self.get_day_format())
+
+ # Use a custom queryset if provided
+ qs = queryset or self.get_queryset()
+
+ if not self.get_allow_future() and date > datetime.date.today():
+ raise Http404(_("Future %(verbose_name_plural)s not available because %(class_name)s.allow_future is False.") % {
+ 'verbose_name_plural': qs.model._meta.verbose_name_plural,
+ 'class_name': self.__class__.__name__,
+ })
+
+ # Filter down a queryset from self.queryset using the date from the
+ # URL. This'll get passed as the queryset to DetailView.get_object,
+ # which'll handle the 404
+ lookup_kwargs = self._make_single_date_lookup(date)
+ qs = qs.filter(**lookup_kwargs)
+
+ return super(BaseDetailView, self).get_object(queryset=qs)
+
+
+class DateDetailView(SingleObjectTemplateResponseMixin, BaseDateDetailView):
+ """
+ Detail view of a single object on a single date; this differs from the
+ standard DetailView by accepting a year/month/day in the URL.
+ """
+ template_name_suffix = '_detail'
+
+
+def _date_from_string(year, year_format, month='', month_format='', day='', day_format='', delim='__'):
+ """
+ Helper: get a datetime.date object given a format string and a year,
+ month, and day (only year is mandatory). Raise a 404 for an invalid date.
+ """
+ format = delim.join((year_format, month_format, day_format))
+ datestr = delim.join((year, month, day))
+ try:
+ return datetime.datetime.strptime(force_str(datestr), format).date()
+ except ValueError:
+ raise Http404(_("Invalid date string '%(datestr)s' given format '%(format)s'") % {
+ 'datestr': datestr,
+ 'format': format,
+ })
+
+
+def _get_next_prev(generic_view, date, is_previous, period):
+ """
+ Helper: Get the next or the previous valid date. The idea is to allow
+ links on month/day views to never be 404s by never providing a date
+ that'll be invalid for the given view.
+
+ This is a bit complicated since it handles different intervals of time,
+ hence the coupling to generic_view.
+
+ However in essence the logic comes down to:
+
+ * If allow_empty and allow_future are both true, this is easy: just
+ return the naive result (just the next/previous day/week/month,
+ reguardless of object existence.)
+
+ * If allow_empty is true, allow_future is false, and the naive result
+ isn't in the future, then return it; otherwise return None.
+
+ * If allow_empty is false and allow_future is true, return the next
+ date *that contains a valid object*, even if it's in the future. If
+ there are no next objects, return None.
+
+ * If allow_empty is false and allow_future is false, return the next
+ date that contains a valid object. If that date is in the future, or
+ if there are no next objects, return None.
+
+ """
+ date_field = generic_view.get_date_field()
+ allow_empty = generic_view.get_allow_empty()
+ allow_future = generic_view.get_allow_future()
+
+ get_current = getattr(generic_view, '_get_current_%s' % period)
+ get_next = getattr(generic_view, '_get_next_%s' % period)
+
+ # Bounds of the current interval
+ start, end = get_current(date), get_next(date)
+
+ # If allow_empty is True, the naive result will be valid
+ if allow_empty:
+ if is_previous:
+ result = get_current(start - datetime.timedelta(days=1))
+ else:
+ result = end
+
+ if allow_future or result <= timezone_today():
+ return result
+ else:
+ return None
+
+ # Otherwise, we'll need to go to the database to look for an object
+ # whose date_field is at least (greater than/less than) the given
+ # naive result
+ else:
+ # Construct a lookup and an ordering depending on whether we're doing
+ # a previous date or a next date lookup.
+ if is_previous:
+ lookup = {'%s__lt' % date_field: generic_view._make_date_lookup_arg(start)}
+ ordering = '-%s' % date_field
+ else:
+ lookup = {'%s__gte' % date_field: generic_view._make_date_lookup_arg(end)}
+ ordering = date_field
+
+ # Filter out objects in the future if appropriate.
+ if not allow_future:
+ # Fortunately, to match the implementation of allow_future,
+ # we need __lte, which doesn't conflict with __lt above.
+ if generic_view.uses_datetime_field:
+ now = timezone.now()
+ else:
+ now = timezone_today()
+ lookup['%s__lte' % date_field] = now
+
+ qs = generic_view.get_queryset().filter(**lookup).order_by(ordering)
+
+ # Snag the first object from the queryset; if it doesn't exist that
+ # means there's no next/previous link available.
+ try:
+ result = getattr(qs[0], date_field)
+ except IndexError:
+ return None
+
+ # Convert datetimes to dates in the current time zone.
+ if generic_view.uses_datetime_field:
+ if settings.USE_TZ:
+ result = timezone.localtime(result)
+ result = result.date()
+
+ # Return the first day of the period.
+ return get_current(result)
+
+
+def timezone_today():
+ """
+ Return the current date in the current time zone.
+ """
+ if settings.USE_TZ:
+ return timezone.localtime(timezone.now()).date()
+ else:
+ return datetime.date.today()