Browse files

Added date based views

  • Loading branch information...
1 parent 4f06256 commit e4b86c039b4aa3cc47bbaa86c4af5e4aabc0bfcc @bfirsh committed Sep 7, 2010
View
272 class_based_views/dates.py
@@ -9,27 +9,17 @@ class DateView(ListView):
"""
Abstract base class for date-based views.
"""
- def __init__(self, **kwargs):
- self._load_config_values(kwargs,
- allow_future = False,
- date_field = None,
- )
- super(DateView, self).__init__(**kwargs)
-
- # Never use legacy pagination context since previous date-based
- # views weren't paginated.
- self.legacy_context = False
+
+ allow_future = False
+ date_field = None
- def get(self, request, *args, **kwargs):
- obj = self.get_object(request, *args, **kwargs)
- date_list, items, extra_context = self.get_dated_items(request, *args, **kwargs)
- template = self.get_template(request, items)
- context = self.get_context(request, items, date_list, extra_context)
- mimetype = self.get_mimetype(request, items)
- response = self.get_response(request, items, template, context, mimetype=mimetype)
- return response
+ def GET(self, request, *args, **kwargs):
+ date_list, items, extra_context = self.get_dated_items(*args, **kwargs)
+ context = self.get_context(items, date_list)
+ context.update(extra_context)
+ return self.render_to_response(self.get_template_names(items), context)
- def get_queryset(self, request):
+ def get_queryset(self):
"""
Get the queryset to look an objects up against. May not be called if
`get_dated_items` is overridden.
@@ -41,15 +31,15 @@ def get_queryset(self, request):
% {'cls': self.__class__.__name__})
return self.queryset._clone()
- def get_dated_queryset(self, request, allow_future=False, **lookup):
+ def get_dated_queryset(self, allow_future=False, **lookup):
"""
Get a queryset properly filtered according to `allow_future` and any
extra lookup kwargs.
"""
- qs = self.get_queryset(request).filter(**lookup)
- date_field = self.get_date_field(request)
- allow_future = allow_future or self.get_allow_future(request)
- allow_empty = self.get_allow_empty(request)
+ qs = self.get_queryset().filter(**lookup)
+ date_field = self.get_date_field()
+ allow_future = allow_future or self.get_allow_future()
+ allow_empty = self.get_allow_empty()
if not allow_future:
qs = qs.filter(**{'%s__lte' % date_field: datetime.datetime.now()})
@@ -59,54 +49,51 @@ def get_dated_queryset(self, request, allow_future=False, **lookup):
return qs
- def get_date_list(self, request, queryset, date_type):
+ def get_date_list(self, queryset, date_type):
"""
Get a date list by calling `queryset.dates()`, checking along the way
for empty lists that aren't allowed.
"""
- date_field = self.get_date_field(request)
- allow_empty = self.get_allow_empty(request)
+ date_field = self.get_date_field()
+ allow_empty = self.get_allow_empty()
date_list = queryset.dates(date_field, date_type)[::-1]
if date_list is not None and not date_list and not allow_empty:
raise Http404("No %s available" % queryset.model._meta.verbose_name_plural)
return date_list
- def get_date_field(self, request):
+ 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, request):
+ def get_allow_future(self):
"""
Returns `True` if the view should be allowed to display objects from
the future.
"""
return self.allow_future
- def get_context(self, request, items, date_list, context=None):
+ def get_context(self, items, date_list, context=None):
"""
Get the context. Must return a Context (or subclass) instance.
"""
- if not context:
- context = {}
+ context = super(DateView, self).get_context(items)
context['date_list'] = date_list
- return super(DateView, self).get_context(
- request, items, paginator=None, page=None, context=context,
- )
+ return context
- def get_template_names(self, request, items):
+ def get_template_names(self, items):
"""
Return a list of template names to be used for the request. Must return
a list. May not be called if get_template is overridden.
"""
- return super(DateView, self).get_template_names(request, items, suffix=self._template_name_suffix)
+ return super(DateView, self).get_template_names(items, suffix=self._template_name_suffix)
- def get_dated_items(self, request, *args, **kwargs):
+ def get_dated_items(self, *args, **kwargs):
"""
Return (date_list, items, extra_context) for this request.
"""
@@ -116,35 +103,32 @@ class ArchiveView(DateView):
"""
Top-level archive of date-based items.
"""
-
+
+ num_latest=15
_template_name_suffix = 'archive'
-
- def __init__(self, **kwargs):
- self._load_config_values(kwargs, num_latest=15)
- super(ArchiveView, self).__init__(**kwargs)
-
- def get_dated_items(self, request):
+
+ def get_dated_items(self):
"""
Return (date_list, items, extra_context) for this request.
"""
- qs = self.get_dated_queryset(request)
- date_list = self.get_date_list(request, qs, 'year')
- num_latest = self.get_num_latest(request)
+ qs = self.get_dated_queryset()
+ date_list = self.get_date_list(qs, 'year')
+ num_latest = self.get_num_latest()
if date_list and num_latest:
- latest = qs.order_by('-'+self.get_date_field(request))[:num_latest]
+ latest = qs.order_by('-'+self.get_date_field())[:num_latest]
else:
latest = None
return (date_list, latest, {})
- def get_num_latest(self, request):
+ def get_num_latest(self):
"""
Get the number of latest items to show on the archive page.
"""
return self.num_latest
- def get_template_object_name(self, request, items):
+ def get_template_object_name(self, items):
"""
Get the name of the item to be used in the context.
"""
@@ -154,27 +138,23 @@ class YearView(DateView):
"""
List of objects published in a given year.
"""
-
+
+ make_object_list = False
+ allow_empty = False
_template_name_suffix = 'archive_year'
- def __init__(self, **kwargs):
- # Override the allow_empty default from ListView
- allow_empty = kwargs.pop('allow_empty', getattr(self, 'allow_empty', False))
- self._load_config_values(kwargs, make_object_list=False)
- super(YearView, self).__init__(allow_empty=allow_empty, **kwargs)
-
- def get_dated_items(self, request, year):
+ def get_dated_items(self, year):
"""
Return (date_list, items, extra_context) for this request.
"""
# Yes, no error checking: the URLpattern ought to validate this; it's
# an error if it doesn't.
year = int(year)
- date_field = self.get_date_field(request)
- qs = self.get_dated_queryset(request, **{date_field+'__year': year})
- date_list = self.get_date_list(request, qs, 'month')
+ date_field = self.get_date_field()
+ qs = self.get_dated_queryset(**{date_field+'__year': year})
+ date_list = self.get_date_list(qs, 'month')
- if self.get_make_object_list(request):
+ if self.get_make_object_list():
object_list = qs.order_by('-'+date_field)
else:
# We need this to be a queryset since parent classes introspect it
@@ -183,7 +163,7 @@ def get_dated_items(self, request, year):
return (date_list, object_list, {'year': year})
- def get_make_object_list(self, request):
+ def get_make_object_list(self):
"""
Return `True` if this view should contain the full list of objects in
the given year.
@@ -194,21 +174,17 @@ class MonthView(DateView):
"""
List of objects published in a given year.
"""
-
+
+ month_format = '%b'
+ allow_empty = False
_template_name_suffix = 'archive_month'
-
- def __init__(self, **kwargs):
- # Override the allow_empty default from ListView
- allow_empty = kwargs.pop('allow_empty', getattr(self, 'allow_empty', False))
- self._load_config_values(kwargs, month_format='%b')
- super(MonthView, self).__init__(allow_empty=allow_empty, **kwargs)
-
- def get_dated_items(self, request, year, month):
+
+ def get_dated_items(self, year, month):
"""
Return (date_list, items, extra_context) for this request.
"""
- date_field = self.get_date_field(request)
- date = _date_from_string(year, '%Y', month, self.get_month_format(request))
+ date_field = self.get_date_field()
+ date = _date_from_string(year, '%Y', month, self.get_month_format())
# Construct a date-range lookup.
first_day, last_day = _month_bounds(date)
@@ -217,33 +193,33 @@ def get_dated_items(self, request, year, month):
'%s__lt' % date_field: last_day,
}
- allow_future = self.get_allow_future(request)
- qs = self.get_dated_queryset(request, allow_future=allow_future, **lookup_kwargs)
- date_list = self.get_date_list(request, qs, 'day')
+ allow_future = self.get_allow_future()
+ qs = self.get_dated_queryset(allow_future=allow_future, **lookup_kwargs)
+ date_list = self.get_date_list(qs, 'day')
return (date_list, qs, {
'month': date,
- 'next_month': self.get_next_month(request, date),
- 'previous_month': self.get_previous_month(request, date),
+ 'next_month': self.get_next_month(date),
+ 'previous_month': self.get_previous_month(date),
})
- def get_next_month(self, request, date):
+ def get_next_month(self, date):
"""
Get the next valid month.
"""
first_day, last_day = _month_bounds(date)
next = (last_day + datetime.timedelta(days=1)).replace(day=1)
- return _get_next_prev_month(self, request, next, is_previous=False, use_first_day=True)
+ return _get_next_prev_month(self, next, is_previous=False, use_first_day=True)
- def get_previous_month(self, request, date):
+ def get_previous_month(self, date):
"""
Get the previous valid month.
"""
first_day, last_day = _month_bounds(date)
prev = (first_day - datetime.timedelta(days=1)).replace(day=1)
- return _get_next_prev_month(self, request, prev, is_previous=True, use_first_day=True)
+ return _get_next_prev_month(self, prev, is_previous=True, use_first_day=True)
- def get_month_format(self, request):
+ def get_month_format(self):
"""
Get a month format string in strptime syntax to be used to parse the
month from url variables.
@@ -254,19 +230,15 @@ class WeekView(DateView):
"""
List of objects published in a given week.
"""
-
+
+ allow_empty = False
_template_name_suffix = 'archive_year'
-
- def __init__(self, **kwargs):
- # Override the allow_empty default from ListView
- allow_empty = kwargs.pop('allow_empty', getattr(self, 'allow_empty', False))
- super(WeekView, self).__init__(allow_empty=allow_empty, **kwargs)
-
- def get_dated_items(self, request, year, week):
+
+ def get_dated_items(self, year, week):
"""
Return (date_list, items, extra_context) for this request.
"""
- date_field = self.get_date_field(request)
+ date_field = self.get_date_field()
date = _date_from_string(year, '%Y', '0', '%w', week, '%U')
# Construct a date-range lookup.
@@ -277,75 +249,72 @@ def get_dated_items(self, request, year, week):
'%s__lt' % date_field: last_day,
}
- allow_future = self.get_allow_future(request)
- qs = self.get_dated_queryset(request, allow_future=allow_future, **lookup_kwargs)
+ allow_future = self.get_allow_future()
+ qs = self.get_dated_queryset(allow_future=allow_future, **lookup_kwargs)
return (None, qs, {'week': date})
class DayView(DateView):
"""
List of objects published on a given day.
"""
-
+
+ month_format = '%b'
+ day_format = '%d'
+ allow_empty = False
_template_name_suffix = "archive_day"
-
- def __init__(self, **kwargs):
- # Override the allow_empty default from ListView
- allow_empty = kwargs.pop('allow_empty', getattr(self, 'allow_empty', False))
- self._load_config_values(kwargs, month_format='%b', day_format='%d')
- super(DayView, self).__init__(allow_empty=allow_empty, **kwargs)
-
- def get_dated_items(self, request, year, month, day, date=None):
+
+ def get_dated_items(self, year, month, day, date=None):
"""
Return (date_list, items, extra_context) for this request.
"""
date = _date_from_string(year, '%Y',
- month, self.get_month_format(request),
- day, self.get_day_format(request))
+ month, self.get_month_format(),
+ day, self.get_day_format())
- return self._get_dated_items(request, date)
+ return self._get_dated_items(date)
- def _get_dated_items(self, request, date):
+ def _get_dated_items(self, date):
"""
Do the actual heavy lifting of getting the dated items; this accepts a
date object so that TodayView can be trivial.
"""
- date_field = self.get_date_field(request)
- allow_future = self.get_allow_future(request)
+ date_field = self.get_date_field()
+ allow_future = self.get_allow_future()
- field = self.get_queryset(request).model._meta.get_field(date_field)
+ field = self.get_queryset().model._meta.get_field(date_field)
lookup_kwargs = _date_lookup_for_field(field, date)
- qs = self.get_dated_queryset(request, allow_future=allow_future, **lookup_kwargs)
+ qs = self.get_dated_queryset(allow_future=allow_future, **lookup_kwargs)
return (None, qs, {
'day': date,
- 'previous_day': self.get_previous_day(request, date),
- 'next_day': self.get_next_day(request, date)
+ 'previous_day': self.get_previous_day(date),
+ 'next_day': self.get_next_day(date)
})
- def get_next_day(self, request, date):
+ def get_next_day(self, date):
"""
Get the next valid day.
"""
next = date + datetime.timedelta(days=1)
- return _get_next_prev_month(self, request, next, is_previous=False, use_first_day=False)
+ return _get_next_prev_month(self, next, is_previous=False, use_first_day=False)
- def get_previous_day(self, request, date):
+ def get_previous_day(self, date):
"""
Get the previous valid day.
"""
prev = date - datetime.timedelta(days=1)
- return _get_next_prev_month(self, request, prev, is_previous=True, use_first_day=False)
+ return _get_next_prev_month(self, prev, is_previous=True, use_first_day=False)
- def get_month_format(self, request):
+ 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_day_format(self, request):
+ def get_day_format(self):
"""
Get a month format string in strptime syntax to be used to parse the
month from url variables.
@@ -357,74 +326,73 @@ class TodayView(DayView):
List of objects published today.
"""
- def get_dated_items(self, request):
+ def get_dated_items(self):
"""
Return (date_list, items, extra_context) for this request.
"""
- return self._get_dated_items(request, datetime.date.today())
+ return self._get_dated_items(datetime.date.today())
+
class DateDetailView(DetailView):
"""
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 __init__(self, **kwargs):
- self._load_config_values(kwargs,
- date_field = None,
- month_format = '%b',
- day_format = '%d',
- allow_future = False,
- )
- super(DateDetailView, self).__init__(**kwargs)
-
- def get_object(self, request, year, month, day, pk=None, slug=None, object_id=None):
+
+ date_field = None
+ month_format = '%b'
+ day_format = '%d'
+ allow_future = False
+
+ def get_object(self):
"""
Get the object this request displays.
"""
- date = _date_from_string(year, '%Y',
- month, self.get_month_format(request),
- day, self.get_day_format(request))
+ date = _date_from_string(self.kwargs['year'], '%Y',
+ self.kwargs['month'], self.get_month_format(),
+ self.kwargs['day'], self.get_day_format())
- qs = self.get_queryset(request)
+ qs = self.get_queryset()
- if not self.get_allow_future(request) and date > datetime.date.today():
- raise Http404("Future %s not available because %s.allow_future is False." \
+ if not self.get_allow_future() and date > datetime.date.today():
+ raise Http404("Future %s not available because %s.allow_future is "
+ "False."
% (qs.model._meta.verbose_name_plural, 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
- date_field = self.get_date_field(request)
+ date_field = self.get_date_field()
field = qs.model._meta.get_field(date_field)
lookup = _date_lookup_for_field(field, date)
qs = qs.filter(**lookup)
- return super(DateDetailView, self).get_object(request,
- queryset=qs, pk=pk, slug=slug, object_id=object_id)
+ return super(DateDetailView, self).get_object(queryset=qs)
- def get_date_field(self, request):
+ 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__)
+ raise ImproperlyConfigured("%s.date_field is required."
+ % self.__class__.__name__)
return self.date_field
- def get_month_format(self, request):
+ 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_day_format(self, request):
+ def get_day_format(self):
"""
Get a day format string in strptime syntax to be used to parse the
month from url variables.
"""
return self.day_format
- def get_allow_future(self, request):
+ def get_allow_future(self):
"""
Returns `True` if the view should be allowed to display objects from
the future.
@@ -455,7 +423,7 @@ def _month_bounds(date):
return first_day, last_day
-def _get_next_prev_month(generic_view, request, naive_result, is_previous, use_first_day):
+def _get_next_prev_month(generic_view, naive_result, is_previous, use_first_day):
"""
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
@@ -482,9 +450,9 @@ def _get_next_prev_month(generic_view, request, naive_result, is_previous, use_f
if there are no next objects, return None.
"""
- date_field = generic_view.get_date_field(request)
- allow_empty = generic_view.get_allow_empty(request)
- allow_future = generic_view.get_allow_future(request)
+ date_field = generic_view.get_date_field()
+ allow_empty = generic_view.get_allow_empty()
+ allow_future = generic_view.get_allow_future()
# If allow_empty is True the naive value will be valid
if allow_empty:
@@ -503,7 +471,7 @@ def _get_next_prev_month(generic_view, request, naive_result, is_previous, use_f
lookup = {'%s__gte' % date_field: naive_result}
ordering = date_field
- qs = generic_view.get_queryset(request).filter(**lookup).order_by(ordering)
+ 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.
View
10 class_based_views/detail.py
@@ -11,7 +11,7 @@ class SingleObjectMixin(object):
queryset = None
slug_field = 'slug'
- def get_object(self):
+ def get_object(self, queryset=None):
"""
Returns the object the view is displaying.
@@ -20,7 +20,8 @@ def get_object(self):
"""
# Use a custom queryset if provided; this is required for subclasses
# like DateDetailView
- queryset = self.get_queryset()
+ if queryset is None:
+ queryset = self.get_queryset()
# Next, try looking up by primary key.
if 'pk' in self.kwargs:
@@ -80,7 +81,10 @@ def GET(self, request, *args, **kwargs):
return self.render_to_response(self.get_template_names(obj), context)
def get_context(self, obj):
- return {self.get_template_object_name(obj): obj}
+ return {
+ 'object': obj,
+ self.get_template_object_name(obj): obj
+ }
def get_template_names(self, obj):
"""
View
5 class_based_views/tests/tests/__init__.py
@@ -1,4 +1,5 @@
from class_based_views.tests.tests.base import ViewTest, TemplateViewTest
+from class_based_views.tests.tests.dates import ArchiveViewTests, YearViewTests, MonthViewTests, WeekViewTests, DayViewTests, DateDetailViewTests
from class_based_views.tests.tests.detail import DetailViewTest
-from class_based_views.tests.tests.list import ListViewTests
-from class_based_views.tests.tests.edit import EditViewTests
+from class_based_views.tests.tests.edit import EditViewTests
+from class_based_views.tests.tests.list import ListViewTests
View
288 class_based_views/tests/tests/dates.py
@@ -0,0 +1,288 @@
+import datetime
+from django.core.exceptions import ImproperlyConfigured
+from django.test import TestCase
+from class_based_views.tests.models import Book
+
+class ArchiveViewTests(TestCase):
+ fixtures = ['generic-views-test-data.json']
+ urls = 'class_based_views.tests.urls'
+
+ def test_archive_view(self):
+ res = self.client.get('/dates/books/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['date_list'], Book.objects.dates('pubdate', 'year')[::-1])
+ self.assertEqual(list(res.context['latest']), list(Book.objects.all()))
+ self.assertTemplateUsed(res, 'tests/book_archive.html')
+
+ def test_archive_view_invalid(self):
+ self.assertRaises(ImproperlyConfigured, self.client.get, '/dates/books/invalid/')
+
+class YearViewTests(TestCase):
+ fixtures = ['generic-views-test-data.json']
+ urls = 'class_based_views.tests.urls'
+
+ def test_year_view(self):
+ res = self.client.get('/dates/books/2008/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(list(res.context['date_list']), [datetime.datetime(2008, 10, 1)])
+ self.assertEqual(res.context['year'], 2008)
+ self.assertTemplateUsed(res, 'tests/book_archive_year.html')
+
+ def test_year_view_make_object_list(self):
+ res = self.client.get('/dates/books/2006/make_object_list/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(list(res.context['date_list']), [datetime.datetime(2006, 5, 1)])
+ self.assertEqual(list(res.context['books']), list(Book.objects.filter(pubdate__year=2006)))
+ self.assertEqual(list(res.context['object_list']), list(Book.objects.filter(pubdate__year=2006)))
+ self.assertTemplateUsed(res, 'tests/book_archive_year.html')
+
+ def test_year_view_empty(self):
+ res = self.client.get('/dates/books/1999/')
+ self.assertEqual(res.status_code, 404)
+ res = self.client.get('/dates/books/1999/allow_empty/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(list(res.context['date_list']), [])
+ self.assertEqual(list(res.context['books']), [])
+
+ def test_year_view_allow_future(self):
+ # Create a new book in the future
+ year = datetime.date.today().year + 1
+ b = Book.objects.create(name="The New New Testement", pages=600, pubdate=datetime.date(year, 1, 1))
+ res = self.client.get('/dates/books/%s/' % year)
+ self.assertEqual(res.status_code, 404)
+
+ res = self.client.get('/dates/books/%s/allow_empty/' % year)
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(list(res.context['books']), [])
+
+ res = self.client.get('/dates/books/%s/allow_future/' % year)
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(list(res.context['date_list']), [datetime.datetime(year, 1, 1)])
+
+ def test_year_view_invalid_pattern(self):
+ self.assertRaises(TypeError, self.client.get, '/dates/books/no_year/')
+
+class MonthViewTests(TestCase):
+ fixtures = ['generic-views-test-data.json']
+ urls = 'class_based_views.tests.urls'
+
+ def test_month_view(self):
+ res = self.client.get('/dates/books/2008/oct/')
+ self.assertEqual(res.status_code, 200)
+ self.assertTemplateUsed(res, 'tests/book_archive_month.html')
+ self.assertEqual(list(res.context['date_list']), [datetime.datetime(2008, 10, 1)])
+ self.assertEqual(list(res.context['books']),
+ list(Book.objects.filter(pubdate=datetime.date(2008, 10, 1))))
+ self.assertEqual(res.context['month'], datetime.date(2008, 10, 1))
+
+ # Since allow_empty=False, next/prev months must be valid (#7164)
+ self.assertEqual(res.context['next_month'], None)
+ self.assertEqual(res.context['previous_month'], datetime.date(2006, 5, 1))
+
+ def test_month_view_allow_empty(self):
+ # allow_empty = False, empty month
+ res = self.client.get('/dates/books/2000/jan/')
+ self.assertEqual(res.status_code, 404)
+
+ # allow_empty = True, empty month
+ res = self.client.get('/dates/books/2000/jan/allow_empty/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(list(res.context['date_list']), [])
+ self.assertEqual(list(res.context['books']), [])
+ self.assertEqual(res.context['month'], datetime.date(2000, 1, 1))
+
+ # Since it's allow empty, next/prev are allowed to be empty months (#7164)
+ self.assertEqual(res.context['next_month'], datetime.date(2000, 2, 1))
+ self.assertEqual(res.context['previous_month'], datetime.date(1999, 12, 1))
+
+ # allow_empty but not allow_future: next_month should be empty (#7164)
+ url = datetime.date.today().strftime('/dates/books/%Y/%b/allow_empty/').lower()
+ res = self.client.get(url)
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['next_month'], None)
+
+ def test_month_view_allow_future(self):
+ future = (datetime.date.today() + datetime.timedelta(days=60)).replace(day=1)
+ urlbit = future.strftime('%Y/%b').lower()
+ b = Book.objects.create(name="The New New Testement", pages=600, pubdate=future)
+
+ # allow_future = False, future month
+ res = self.client.get('/dates/books/%s/' % urlbit)
+ self.assertEqual(res.status_code, 404)
+
+ # allow_future = True, valid future month
+ res = self.client.get('/dates/books/%s/allow_future/' % urlbit)
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['date_list'][0].date(), b.pubdate)
+ self.assertEqual(list(res.context['books']), [b])
+ self.assertEqual(res.context['month'], future)
+
+ # Since it's allow_future but not allow_empty, next/prev are not
+ # allowed to be empty months (#7164)
+ self.assertEqual(res.context['next_month'], None)
+ self.assertEqual(res.context['previous_month'], datetime.date(2008, 10, 1))
+
+ # allow_future, but not allow_empty, with a current month. So next
+ # should be in the future (yup, #7164, again)
+ res = self.client.get('/dates/books/2008/oct/allow_future/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['next_month'], future)
+ self.assertEqual(res.context['previous_month'], datetime.date(2006, 5, 1))
+
+ def test_custom_month_format(self):
+ res = self.client.get('/dates/books/2008/10/')
+ self.assertEqual(res.status_code, 200)
+
+ def test_month_view_invalid_pattern(self):
+ self.assertRaises(TypeError, self.client.get, '/dates/books/2007/no_month/')
+
+class WeekViewTests(TestCase):
+ fixtures = ['generic-views-test-data.json']
+ urls = 'class_based_views.tests.urls'
+
+ def test_week_view(self):
+ res = self.client.get('/dates/books/2008/week/39/')
+ self.assertEqual(res.status_code, 200)
+ self.assertTemplateUsed(res, 'tests/book_archive_year.html')
+ self.assertEqual(res.context['books'][0], Book.objects.get(pubdate=datetime.date(2008, 10, 1)))
+ self.assertEqual(res.context['week'], datetime.date(2008, 9, 28))
+
+ def test_week_view_allow_empty(self):
+ res = self.client.get('/dates/books/2008/week/12/')
+ self.assertEqual(res.status_code, 404)
+
+ res = self.client.get('/dates/books/2008/week/12/allow_empty/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(list(res.context['books']), [])
+
+ def test_week_view_allow_future(self):
+ future = datetime.date(datetime.date.today().year + 1, 1, 1)
+ b = Book.objects.create(name="The New New Testement", pages=600, pubdate=future)
+
+ res = self.client.get('/dates/books/%s/week/0/' % future.year)
+ self.assertEqual(res.status_code, 404)
+
+ res = self.client.get('/dates/books/%s/week/0/allow_future/' % future.year)
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(list(res.context['books']), [b])
+
+ def test_week_view_invalid_pattern(self):
+ self.assertRaises(TypeError, self.client.get, '/dates/books/2007/week/no_week/')
+
+class DayViewTests(TestCase):
+ fixtures = ['generic-views-test-data.json']
+ urls = 'class_based_views.tests.urls'
+
+ def test_day_view(self):
+ res = self.client.get('/dates/books/2008/oct/01/')
+ self.assertEqual(res.status_code, 200)
+ self.assertTemplateUsed(res, 'tests/book_archive_day.html')
+ self.assertEqual(list(res.context['books']),
+ list(Book.objects.filter(pubdate=datetime.date(2008, 10, 1))))
+ self.assertEqual(res.context['day'], datetime.date(2008, 10, 1))
+
+ # Since allow_empty=False, next/prev days must be valid.
+ self.assertEqual(res.context['next_day'], None)
+ self.assertEqual(res.context['previous_day'], datetime.date(2006, 5, 1))
+
+ def test_day_view_allow_empty(self):
+ # allow_empty = False, empty month
+ res = self.client.get('/dates/books/2000/jan/1/')
+ self.assertEqual(res.status_code, 404)
+
+ # allow_empty = True, empty month
+ res = self.client.get('/dates/books/2000/jan/1/allow_empty/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(list(res.context['books']), [])
+ self.assertEqual(res.context['day'], datetime.date(2000, 1, 1))
+
+ # Since it's allow empty, next/prev are allowed to be empty months (#7164)
+ self.assertEqual(res.context['next_day'], datetime.date(2000, 1, 2))
+ self.assertEqual(res.context['previous_day'], datetime.date(1999, 12, 31))
+
+ # allow_empty but not allow_future: next_month should be empty (#7164)
+ url = datetime.date.today().strftime('/dates/books/%Y/%b/%d/allow_empty/').lower()
+ res = self.client.get(url)
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['next_day'], None)
+
+ def test_day_view_allow_future(self):
+ future = (datetime.date.today() + datetime.timedelta(days=60))
+ urlbit = future.strftime('%Y/%b/%d').lower()
+ b = Book.objects.create(name="The New New Testement", pages=600, pubdate=future)
+
+ # allow_future = False, future month
+ res = self.client.get('/dates/books/%s/' % urlbit)
+ self.assertEqual(res.status_code, 404)
+
+ # allow_future = True, valid future month
+ res = self.client.get('/dates/books/%s/allow_future/' % urlbit)
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(list(res.context['books']), [b])
+ self.assertEqual(res.context['day'], future)
+
+ # allow_future but not allow_empty, next/prev amust be valid
+ self.assertEqual(res.context['next_day'], None)
+ self.assertEqual(res.context['previous_day'], datetime.date(2008, 10, 1))
+
+ # allow_future, but not allow_empty, with a current month.
+ res = self.client.get('/dates/books/2008/oct/01/allow_future/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['next_day'], future)
+ self.assertEqual(res.context['previous_day'], datetime.date(2006, 5, 1))
+
+ def test_next_prev_context(self):
+ res = self.client.get('/dates/books/2008/oct/01/')
+ self.assertEqual(res.content, "Archive for 2008-10-01. Previous day is 2006-05-01")
+
+ def test_custom_month_format(self):
+ res = self.client.get('/dates/books/2008/10/01/')
+ self.assertEqual(res.status_code, 200)
+
+ def test_day_view_invalid_pattern(self):
+ self.assertRaises(TypeError, self.client.get, '/dates/books/2007/oct/no_day/')
+
+ def test_today_view(self):
+ res = self.client.get('/dates/books/today/')
+ self.assertEqual(res.status_code, 404)
+ res = self.client.get('/dates/books/today/allow_empty/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['day'], datetime.date.today())
+
+class DateDetailViewTests(TestCase):
+ fixtures = ['generic-views-test-data.json']
+ urls = 'class_based_views.tests.urls'
+
+ def test_date_detail_by_pk(self):
+ res = self.client.get('/dates/books/2008/oct/01/1/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['object'], Book.objects.get(pk=1))
+ self.assertEqual(res.context['book'], Book.objects.get(pk=1))
+ self.assertTemplateUsed(res, 'tests/book_detail.html')
+
+ def test_date_detail_by_slug(self):
+ res = self.client.get('/dates/books/2006/may/01/byslug/dreaming-in-code/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['book'], Book.objects.get(slug='dreaming-in-code'))
+
+ def test_date_detail_custom_month_format(self):
+ res = self.client.get('/dates/books/2008/10/01/1/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['book'], Book.objects.get(pk=1))
+
+ def test_date_detail_allow_future(self):
+ future = (datetime.date.today() + datetime.timedelta(days=60))
+ urlbit = future.strftime('%Y/%b/%d').lower()
+ b = Book.objects.create(name="The New New Testement", slug="new-new", pages=600, pubdate=future)
+
+ res = self.client.get('/dates/books/%s/new-new/' % urlbit)
+ self.assertEqual(res.status_code, 404)
+
+ res = self.client.get('/dates/books/%s/%s/allow_future/' % (urlbit, b.id))
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['book'], b)
+ self.assertTemplateUsed(res, 'tests/book_detail.html')
+
+ def test_invalid_url(self):
+ self.assertRaises(AttributeError, self.client.get, "/dates/books/2008/oct/01/nopk/")
+
View
171 class_based_views/tests/urls.py
@@ -3,74 +3,121 @@
urlpatterns = patterns('',
# base
- (r'^about/login-required/$', views.DecoratedAboutView()),
+ (r'^about/login-required/$',
+ views.DecoratedAboutView()),
# DetailView
- (r'^detail/obj/$', views.ObjectDetail()),
- url(r'^detail/author/(?P<pk>\d+)/$', views.AuthorDetail(), name="author_detail"),
- (r'^detail/author/byslug/(?P<slug>[\w-]+)/$', views.AuthorDetail()),
- (r'^detail/author/invalid/url/$', views.AuthorDetail()),
- (r'^detail/author/invalid/qs/$', views.AuthorDetail(queryset=None)),
+ (r'^detail/obj/$',
+ views.ObjectDetail()),
+ url(r'^detail/author/(?P<pk>\d+)/$',
+ views.AuthorDetail(),
+ name="author_detail"),
+ (r'^detail/author/byslug/(?P<slug>[\w-]+)/$',
+ views.AuthorDetail()),
+ (r'^detail/author/invalid/url/$',
+ views.AuthorDetail()),
+ (r'^detail/author/invalid/qs/$',
+ views.AuthorDetail(queryset=None)),
# EditView
- (r'^edit/authors/create/$', views.AuthorCreate()),
- (r'^edit/authors/create/restricted/$', views.AuthorCreateRestricted()),
- (r'^edit/author/(?P<pk>\d+)/update/$', views.AuthorUpdate()),
- (r'^edit/author/(?P<pk>\d+)/delete/$', views.AuthorDelete()),
+ (r'^edit/authors/create/$',
+ views.AuthorCreate()),
+ (r'^edit/authors/create/restricted/$',
+ views.AuthorCreateRestricted()),
+ (r'^edit/author/(?P<pk>\d+)/update/$',
+ views.AuthorUpdate()),
+ (r'^edit/author/(?P<pk>\d+)/delete/$',
+ views.AuthorDelete()),
+
+ # ArchiveView
+ (r'^dates/books/$',
+ views.BookArchive()),
+ (r'^dates/books/invalid/$',
+ views.BookArchive(queryset=None)),
- # # ArchiveView
- # (r'^dates/books/$', views.BookArchive()),
- # (r'^dates/books/invalid/$', views.BookArchive(queryset=None)),
- #
# ListView
- (r'^list/dict/$', views.DictList()),
- url(r'^list/authors/$', views.AuthorList(), name="authors_list"),
- (r'^list/authors/paginated/$', views.PaginatedAuthorList(paginate_by=30)),
- (r'^list/authors/paginated/(?P<page>\d+)/$', views.PaginatedAuthorList(paginate_by=30)),
- (r'^list/authors/notempty/$', views.AuthorList(allow_empty=False)),
- (r'^list/authors/template_object_name/$', views.AuthorList(template_object_name='author')),
- (r'^list/authors/invalid/$', views.AuthorList(queryset=None)),
- #
- # # YearView
- # # Mixing keyword and possitional captures below is intentional; the views
- # # ought to be able to accept either.
- # (r'^dates/books/(?P<year>\d{4})/$', views.BookYearArchive()),
- # (r'^dates/books/(\d{4})/make_object_list/$', views.BookYearArchive(make_object_list=True)),
- # (r'^dates/books/(\d{4})/allow_empty/$', views.BookYearArchive(allow_empty=True)),
- # (r'^dates/books/(\d{4})/allow_future/$', views.BookYearArchive(allow_future=True)),
- # (r'^dates/books/no_year/$', views.BookYearArchive()),
- #
- # # MonthView
- # (r'^dates/books/(\d{4})/([a-z]{3})/$', views.BookMonthArchive()),
- # (r'^dates/books/(\d{4})/(\d{1,2})/$', views.BookMonthArchive(month_format='%m')),
- # (r'^dates/books/(\d{4})/([a-z]{3})/allow_empty/$', views.BookMonthArchive(allow_empty=True)),
- # (r'^dates/books/(\d{4})/([a-z]{3})/allow_future/$', views.BookMonthArchive(allow_future=True)),
- # (r'^dates/books/(\d{4})/no_month/$', views.BookMonthArchive()),
- #
- # # WeekView
- # (r'^dates/books/(\d{4})/week/(\d{1,2})/$', views.BookWeekArchive()),
- # (r'^dates/books/(\d{4})/week/(\d{1,2})/allow_empty/$', views.BookWeekArchive(allow_empty=True)),
- # (r'^dates/books/(\d{4})/week/(\d{1,2})/allow_future/$', views.BookWeekArchive(allow_future=True)),
- # (r'^dates/books/(\d{4})/week/no_week/$', views.BookWeekArchive()),
- #
- # # DayView
- # (r'^dates/books/(\d{4})/([a-z]{3})/(\d{1,2})/$', views.BookDayArchive()),
- # (r'^dates/books/(\d{4})/(\d{1,2})/(\d{1,2})/$', views.BookDayArchive(month_format='%m')),
- # (r'^dates/books/(\d{4})/([a-z]{3})/(\d{1,2})/allow_empty/$', views.BookDayArchive(allow_empty=True)),
- # (r'^dates/books/(\d{4})/([a-z]{3})/(\d{1,2})/allow_future/$', views.BookDayArchive(allow_future=True)),
- # (r'^dates/books/(\d{4})/([a-z]{3})/no_day/$', views.BookDayArchive()),
- #
- # # TodayView
- # (r'dates/books/today/$', views.BookTodayArchive()),
- # (r'dates/books/today/allow_empty/$', views.BookTodayArchive(allow_empty=True)),
- #
- # # DateDetailView
- # (r'^dates/books/(\d{4})/([a-z]{3})/(\d{1,2})/(\d+)/$', views.BookDetail()),
- # (r'^dates/books/(\d{4})/(\d{1,2})/(\d{1,2})/(\d+)/$', views.BookDetail(month_format='%m')),
- # (r'^dates/books/(\d{4})/([a-z]{3})/(\d{1,2})/(\d+)/allow_future/$', views.BookDetail(allow_future=True)),
- # (r'^dates/books/(\d{4})/([a-z]{3})/(\d{1,2})/nopk/$', views.BookDetail()),
- #
- # (r'^dates/books/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\d{1,2})/byslug/(?P<slug>[\w-]+)/$', views.BookDetail()),
+ (r'^list/dict/$',
+ views.DictList()),
+ url(r'^list/authors/$',
+ views.AuthorList(),
+ name="authors_list"),
+ (r'^list/authors/paginated/$',
+ views.PaginatedAuthorList(paginate_by=30)),
+ (r'^list/authors/paginated/(?P<page>\d+)/$',
+ views.PaginatedAuthorList(paginate_by=30)),
+ (r'^list/authors/notempty/$',
+ views.AuthorList(allow_empty=False)),
+ (r'^list/authors/template_object_name/$',
+ views.AuthorList(template_object_name='author')),
+ (r'^list/authors/invalid/$',
+ views.AuthorList(queryset=None)),
+
+ # YearView
+ # Mixing keyword and possitional captures below is intentional; the views
+ # ought to be able to accept either.
+ (r'^dates/books/(?P<year>\d{4})/$',
+ views.BookYearArchive()),
+ (r'^dates/books/(?P<year>\d{4})/make_object_list/$',
+ views.BookYearArchive(make_object_list=True)),
+ (r'^dates/books/(?P<year>\d{4})/allow_empty/$',
+ views.BookYearArchive(allow_empty=True)),
+ (r'^dates/books/(?P<year>\d{4})/allow_future/$',
+ views.BookYearArchive(allow_future=True)),
+ (r'^dates/books/no_year/$',
+ views.BookYearArchive()),
+
+ # MonthView
+ (r'^dates/books/(?P<year>\d{4})/(?P<month>[a-z]{3})/$',
+ views.BookMonthArchive()),
+ (r'^dates/books/(?P<year>\d{4})/(?P<month>\d{1,2})/$',
+ views.BookMonthArchive(month_format='%m')),
+ (r'^dates/books/(?P<year>\d{4})/(?P<month>[a-z]{3})/allow_empty/$',
+ views.BookMonthArchive(allow_empty=True)),
+ (r'^dates/books/(?P<year>\d{4})/(?P<month>[a-z]{3})/allow_future/$',
+ views.BookMonthArchive(allow_future=True)),
+ (r'^dates/books/(?P<year>\d{4})/no_month/$',
+ views.BookMonthArchive()),
+
+ # WeekView
+ (r'^dates/books/(?P<year>\d{4})/week/(?P<week>\d{1,2})/$',
+ views.BookWeekArchive()),
+ (r'^dates/books/(?P<year>\d{4})/week/(?P<week>\d{1,2})/allow_empty/$',
+ views.BookWeekArchive(allow_empty=True)),
+ (r'^dates/books/(?P<year>\d{4})/week/(?P<week>\d{1,2})/allow_future/$',
+ views.BookWeekArchive(allow_future=True)),
+ (r'^dates/books/(?P<year>\d{4})/week/no_week/$',
+ views.BookWeekArchive()),
+
+ # DayView
+ (r'^dates/books/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\d{1,2})/$',
+ views.BookDayArchive()),
+ (r'^dates/books/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/$',
+ views.BookDayArchive(month_format='%m')),
+ (r'^dates/books/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\d{1,2})/allow_empty/$',
+ views.BookDayArchive(allow_empty=True)),
+ (r'^dates/books/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\d{1,2})/allow_future/$',
+ views.BookDayArchive(allow_future=True)),
+ (r'^dates/books/(?P<year>\d{4})/(?P<month>[a-z]{3})/no_day/$',
+ views.BookDayArchive()),
+
+ # TodayView
+ (r'dates/books/today/$',
+ views.BookTodayArchive()),
+ (r'dates/books/today/allow_empty/$',
+ views.BookTodayArchive(allow_empty=True)),
+
+ # DateDetailView
+ (r'^dates/books/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\d{1,2})/(?P<pk>\d+)/$',
+ views.BookDetail()),
+ (r'^dates/books/(?P<year>\d{4})/(?P<month>\d{1,2})/(?P<day>\d{1,2})/(?P<pk>\d+)/$',
+ views.BookDetail(month_format='%m')),
+ (r'^dates/books/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\d{1,2})/(?P<pk>\d+)/allow_future/$',
+ views.BookDetail(allow_future=True)),
+ (r'^dates/books/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\d{1,2})/nopk/$',
+ views.BookDetail()),
+
+ (r'^dates/books/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\d{1,2})/byslug/(?P<slug>[\w-]+)/$',
+ views.BookDetail()),
# Useful for testing redirects
(r'^accounts/login/$', 'django.contrib.auth.views.login')
View
48 class_based_views/tests/views.py
@@ -70,27 +70,27 @@ def redirect_to(self, obj):
return reverse('authors_list')
-# class BookConfig(object):
-# queryset = Book.objects.all()
-# date_field = 'pubdate'
-#
-# class BookArchive(BookConfig, class_based_views.ArchiveView):
-# pass
-#
-# class BookYearArchive(BookConfig, class_based_views.YearView):
-# pass
-#
-# class BookMonthArchive(BookConfig, class_based_views.MonthView):
-# pass
-#
-# class BookWeekArchive(BookConfig, class_based_views.WeekView):
-# pass
-#
-# class BookDayArchive(BookConfig, class_based_views.DayView):
-# pass
-#
-# class BookTodayArchive(BookConfig, class_based_views.TodayView):
-# pass
-#
-# class BookDetail(BookConfig, class_based_views.DateDetailView):
-# pass
+class BookConfig(object):
+ queryset = Book.objects.all()
+ date_field = 'pubdate'
+
+class BookArchive(BookConfig, class_based_views.ArchiveView):
+ pass
+
+class BookYearArchive(BookConfig, class_based_views.YearView):
+ pass
+
+class BookMonthArchive(BookConfig, class_based_views.MonthView):
+ pass
+
+class BookWeekArchive(BookConfig, class_based_views.WeekView):
+ pass
+
+class BookDayArchive(BookConfig, class_based_views.DayView):
+ pass
+
+class BookTodayArchive(BookConfig, class_based_views.TodayView):
+ pass
+
+class BookDetail(BookConfig, class_based_views.DateDetailView):
+ pass

0 comments on commit e4b86c0

Please sign in to comment.