Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #18217 -- Time zone support in generic views

Introduced a distinct implementation depending on the type of the
date field (DateField or DateTimeField), and applied appropriate
conversions is the latter case, when time zone support is enabled.
  • Loading branch information...
commit 78ba9670af373f5261f95d6560bfd08425adcaad 1 parent 596cb9c
Aymeric Augustin authored April 30, 2012
130  django/views/generic/dates.py
... ...
@@ -1,8 +1,10 @@
1 1
 import datetime
  2
+from django.conf import settings
2 3
 from django.db import models
3 4
 from django.core.exceptions import ImproperlyConfigured
4 5
 from django.http import Http404
5 6
 from django.utils.encoding import force_unicode
  7
+from django.utils.functional import cached_property
6 8
 from django.utils.translation import ugettext as _
7 9
 from django.utils import timezone
8 10
 from django.views.generic.base import View
@@ -164,6 +166,51 @@ def get_allow_future(self):
164 166
         """
165 167
         return self.allow_future
166 168
 
  169
+    # Note: the following three methods only work in subclasses that also
  170
+    # inherit SingleObjectMixin or MultipleObjectMixin.
  171
+
  172
+    @cached_property
  173
+    def uses_datetime_field(self):
  174
+        """
  175
+        Return `True` if the date field is a `DateTimeField` and `False`
  176
+        if it's a `DateField`.
  177
+        """
  178
+        model = self.get_queryset().model if self.model is None else self.model
  179
+        field = model._meta.get_field(self.get_date_field())
  180
+        return isinstance(field, models.DateTimeField)
  181
+
  182
+    def _make_date_lookup_arg(self, value):
  183
+        """
  184
+        Convert a date into a datetime when the date field is a DateTimeField.
  185
+
  186
+        When time zone support is enabled, `date` is assumed to be in the
  187
+        current time zone, so that displayed items are consistent with the URL.
  188
+        """
  189
+        if self.uses_datetime_field:
  190
+            value = datetime.datetime.combine(value, datetime.time.min)
  191
+            if settings.USE_TZ:
  192
+                value = timezone.make_aware(value, timezone.get_current_timezone())
  193
+        return value
  194
+
  195
+    def _make_single_date_lookup(self, date):
  196
+        """
  197
+        Get the lookup kwargs for filtering on a single date.
  198
+
  199
+        If the date field is a DateTimeField, we can't just filter on
  200
+        date_field=date because that doesn't take the time into account.
  201
+        """
  202
+        date_field = self.get_date_field()
  203
+        if self.uses_datetime_field:
  204
+            since = self._make_date_lookup_arg(date)
  205
+            until = self._make_date_lookup_arg(date + datetime.timedelta(days=1))
  206
+            return {
  207
+                '%s__gte' % date_field: since,
  208
+                '%s__lt' % date_field: until,
  209
+            }
  210
+        else:
  211
+            # Skip self._make_date_lookup_arg, it's a no-op in this branch.
  212
+            return {date_field: date}
  213
+
167 214
 
168 215
 class BaseDateListView(MultipleObjectMixin, DateMixin, View):
169 216
     """
@@ -180,7 +227,7 @@ def get(self, request, *args, **kwargs):
180 227
 
181 228
     def get_dated_items(self):
182 229
         """
183  
-        Obtain the list of dates and itesm
  230
+        Obtain the list of dates and items.
184 231
         """
185 232
         raise NotImplementedError('A DateView must provide an implementation of get_dated_items()')
186 233
 
@@ -196,7 +243,8 @@ def get_dated_queryset(self, **lookup):
196 243
         paginate_by = self.get_paginate_by(qs)
197 244
 
198 245
         if not allow_future:
199  
-            qs = qs.filter(**{'%s__lte' % date_field: timezone.now()})
  246
+            now = timezone.now() if self.uses_datetime_field else datetime.date.today()
  247
+            qs = qs.filter(**{'%s__lte' % date_field: now})
200 248
 
201 249
         if not allow_empty:
202 250
             # When pagination is enabled, it's better to do a cheap query
@@ -225,6 +273,7 @@ def get_date_list(self, queryset, date_type):
225 273
 
226 274
         return date_list
227 275
 
  276
+
228 277
 class BaseArchiveIndexView(BaseDateListView):
229 278
     """
230 279
     Base class for archives of date-based items.
@@ -265,15 +314,23 @@ def get_dated_items(self):
265 314
         """
266 315
         Return (date_list, items, extra_context) for this request.
267 316
         """
268  
-        # Yes, no error checking: the URLpattern ought to validate this; it's
269  
-        # an error if it doesn't.
270 317
         year = self.get_year()
  318
+
271 319
         date_field = self.get_date_field()
272  
-        qs = self.get_dated_queryset(**{date_field+'__year': year})
  320
+        date = _date_from_string(year, self.get_year_format())
  321
+
  322
+        since = self._make_date_lookup_arg(date)
  323
+        until = self._make_date_lookup_arg(datetime.date(date.year + 1, 1, 1))
  324
+        lookup_kwargs = {
  325
+            '%s__gte' % date_field: since,
  326
+            '%s__lt' % date_field: until,
  327
+        }
  328
+
  329
+        qs = self.get_dated_queryset(**lookup_kwargs)
273 330
         date_list = self.get_date_list(qs, 'month')
274 331
 
275 332
         if self.get_make_object_list():
276  
-            object_list = qs.order_by('-'+date_field)
  333
+            object_list = qs.order_by('-' + date_field)
277 334
         else:
278 335
             # We need this to be a queryset since parent classes introspect it
279 336
             # to find information about the model.
@@ -312,14 +369,14 @@ def get_dated_items(self):
312 369
                                  month, self.get_month_format())
313 370
 
314 371
         # Construct a date-range lookup.
315  
-        first_day = date.replace(day=1)
316  
-        if first_day.month == 12:
317  
-            last_day = first_day.replace(year=first_day.year + 1, month=1)
  372
+        since = self._make_date_lookup_arg(date)
  373
+        if date.month == 12:
  374
+            until = self._make_date_lookup_arg(datetime.date(date.year + 1, 1, 1))
318 375
         else:
319  
-            last_day = first_day.replace(month=first_day.month + 1)
  376
+            until = self._make_date_lookup_arg(datetime.date(date.year, date.month + 1, 1))
320 377
         lookup_kwargs = {
321  
-            '%s__gte' % date_field: first_day,
322  
-            '%s__lt' % date_field: last_day,
  378
+            '%s__gte' % date_field: since,
  379
+            '%s__lt' % date_field: until,
323 380
         }
324 381
 
325 382
         qs = self.get_dated_queryset(**lookup_kwargs)
@@ -362,11 +419,11 @@ def get_dated_items(self):
362 419
                                  week, week_format)
363 420
 
364 421
         # Construct a date-range lookup.
365  
-        first_day = date
366  
-        last_day = date + datetime.timedelta(days=7)
  422
+        since = self._make_date_lookup_arg(date)
  423
+        until = self._make_date_lookup_arg(date + datetime.timedelta(days=7))
367 424
         lookup_kwargs = {
368  
-            '%s__gte' % date_field: first_day,
369  
-            '%s__lt' % date_field: last_day,
  425
+            '%s__gte' % date_field: since,
  426
+            '%s__lt' % date_field: until,
370 427
         }
371 428
 
372 429
         qs = self.get_dated_queryset(**lookup_kwargs)
@@ -404,11 +461,7 @@ def _get_dated_items(self, date):
404 461
         Do the actual heavy lifting of getting the dated items; this accepts a
405 462
         date object so that TodayArchiveView can be trivial.
406 463
         """
407  
-        date_field = self.get_date_field()
408  
-
409  
-        field = self.get_queryset().model._meta.get_field(date_field)
410  
-        lookup_kwargs = _date_lookup_for_field(field, date)
411  
-
  464
+        lookup_kwargs = self._make_single_date_lookup(date)
412 465
         qs = self.get_dated_queryset(**lookup_kwargs)
413 466
 
414 467
         return (None, qs, {
@@ -474,10 +527,8 @@ def get_object(self, queryset=None):
474 527
         # Filter down a queryset from self.queryset using the date from the
475 528
         # URL. This'll get passed as the queryset to DetailView.get_object,
476 529
         # which'll handle the 404
477  
-        date_field = self.get_date_field()
478  
-        field = qs.model._meta.get_field(date_field)
479  
-        lookup = _date_lookup_for_field(field, date)
480  
-        qs = qs.filter(**lookup)
  530
+        lookup_kwargs = self._make_single_date_lookup(date)
  531
+        qs = qs.filter(**lookup_kwargs)
481 532
 
482 533
         return super(BaseDetailView, self).get_object(queryset=qs)
483 534
 
@@ -490,10 +541,10 @@ class DateDetailView(SingleObjectTemplateResponseMixin, BaseDateDetailView):
490 541
     template_name_suffix = '_detail'
491 542
 
492 543
 
493  
-def _date_from_string(year, year_format, month, month_format, day='', day_format='', delim='__'):
  544
+def _date_from_string(year, year_format, month='', month_format='', day='', day_format='', delim='__'):
494 545
     """
495 546
     Helper: get a datetime.date object given a format string and a year,
496  
-    month, and possibly day; raise a 404 for an invalid date.
  547
+    month, and day (only year is mandatory). Raise a 404 for an invalid date.
497 548
     """
498 549
     format = delim.join((year_format, month_format, day_format))
499 550
     datestr = delim.join((year, month, day))
@@ -548,10 +599,10 @@ def _get_next_prev_month(generic_view, naive_result, is_previous, use_first_day)
548 599
         # Construct a lookup and an ordering depending on whether we're doing
549 600
         # a previous date or a next date lookup.
550 601
         if is_previous:
551  
-            lookup = {'%s__lte' % date_field: naive_result}
  602
+            lookup = {'%s__lte' % date_field: generic_view._make_date_lookup_arg(naive_result)}
552 603
             ordering = '-%s' % date_field
553 604
         else:
554  
-            lookup = {'%s__gte' % date_field: naive_result}
  605
+            lookup = {'%s__gte' % date_field: generic_view._make_date_lookup_arg(naive_result)}
555 606
             ordering = date_field
556 607
 
557 608
         qs = generic_view.get_queryset().filter(**lookup).order_by(ordering)
@@ -564,7 +615,9 @@ def _get_next_prev_month(generic_view, naive_result, is_previous, use_first_day)
564 615
             result = None
565 616
 
566 617
     # Convert datetimes to a dates
567  
-    if hasattr(result, 'date'):
  618
+    if result and generic_view.uses_datetime_field:
  619
+        if settings.USE_TZ:
  620
+            result = timezone.localtime(result)
568 621
         result = result.date()
569 622
 
570 623
     # For month views, we always want to have a date that's the first of the
@@ -577,20 +630,3 @@ def _get_next_prev_month(generic_view, naive_result, is_previous, use_first_day)
577 630
         return result
578 631
     else:
579 632
         return None
580  
-
581  
-
582  
-def _date_lookup_for_field(field, date):
583  
-    """
584  
-    Get the lookup kwargs for looking up a date against a given Field. If the
585  
-    date field is a DateTimeField, we can't just do filter(df=date) because
586  
-    that doesn't take the time into account. So we need to make a range lookup
587  
-    in those cases.
588  
-    """
589  
-    if isinstance(field, models.DateTimeField):
590  
-        date_range = (
591  
-            datetime.datetime.combine(date, datetime.time.min),
592  
-            datetime.datetime.combine(date, datetime.time.max)
593  
-        )
594  
-        return {'%s__range' % field.name: date_range}
595  
-    else:
596  
-        return {field.name: date}
6  docs/ref/class-based-views.txt
@@ -748,6 +748,12 @@ DateMixin
748 748
         ``QuerySet``'s model that the date-based archive should use to
749 749
         determine the objects on the page.
750 750
 
  751
+        When :doc:`time zone support </topics/i18n/timezones>` is enabled and
  752
+        ``date_field`` is a ``DateTimeField``, dates are assumed to be in the
  753
+        current time zone. As a consequence, if you have implemented per-user
  754
+        time zone selection, users living in different time zones may view a
  755
+        different set of objects at the same URL.
  756
+
751 757
     .. attribute:: allow_future
752 758
 
753 759
         A boolean specifying whether to include "future" objects on this page,
102  tests/regressiontests/generic_views/dates.py
@@ -4,8 +4,18 @@
4 4
 
5 5
 from django.core.exceptions import ImproperlyConfigured
6 6
 from django.test import TestCase
  7
+from django.test.utils import override_settings
  8
+from django.utils import timezone
  9
+
  10
+from .models import Book, BookSigning
  11
+
  12
+
  13
+import warnings
  14
+warnings.filterwarnings(
  15
+        'error', r"DateTimeField received a naive datetime",
  16
+        RuntimeWarning, r'django\.db\.models\.fields')
  17
+
7 18
 
8  
-from .models import Book
9 19
 
10 20
 
11 21
 class ArchiveIndexViewTests(TestCase):
@@ -88,6 +98,18 @@ def test_paginated_archive_view_does_not_load_entire_table(self):
88 98
         with self.assertNumQueries(3):
89 99
             self.client.get('/dates/books/paginated/')
90 100
 
  101
+    def test_datetime_archive_view(self):
  102
+        BookSigning.objects.create(event_date=datetime.datetime(2008, 4, 2, 12, 0))
  103
+        res = self.client.get('/dates/booksignings/')
  104
+        self.assertEqual(res.status_code, 200)
  105
+
  106
+    @override_settings(USE_TZ=True, TIME_ZONE='Africa/Nairobi')
  107
+    def test_aware_datetime_archive_view(self):
  108
+        BookSigning.objects.create(event_date=datetime.datetime(2008, 4, 2, 12, 0, tzinfo=timezone.utc))
  109
+        res = self.client.get('/dates/booksignings/')
  110
+        self.assertEqual(res.status_code, 200)
  111
+
  112
+
91 113
 class YearArchiveViewTests(TestCase):
92 114
     fixtures = ['generic-views-test-data.json']
93 115
     urls = 'regressiontests.generic_views.urls'
@@ -141,6 +163,18 @@ def test_year_view_invalid_pattern(self):
141 163
         res = self.client.get('/dates/books/no_year/')
142 164
         self.assertEqual(res.status_code, 404)
143 165
 
  166
+    def test_datetime_year_view(self):
  167
+        BookSigning.objects.create(event_date=datetime.datetime(2008, 4, 2, 12, 0))
  168
+        res = self.client.get('/dates/booksignings/2008/')
  169
+        self.assertEqual(res.status_code, 200)
  170
+
  171
+    @override_settings(USE_TZ=True, TIME_ZONE='Africa/Nairobi')
  172
+    def test_aware_datetime_year_view(self):
  173
+        BookSigning.objects.create(event_date=datetime.datetime(2008, 4, 2, 12, 0, tzinfo=timezone.utc))
  174
+        res = self.client.get('/dates/booksignings/2008/')
  175
+        self.assertEqual(res.status_code, 200)
  176
+
  177
+
144 178
 class MonthArchiveViewTests(TestCase):
145 179
     fixtures = ['generic-views-test-data.json']
146 180
     urls = 'regressiontests.generic_views.urls'
@@ -245,6 +279,21 @@ def test_previous_month_without_content(self):
245 279
         self.assertEqual(res.status_code, 200)
246 280
         self.assertEqual(res.context['previous_month'], datetime.date(2010,9,1))
247 281
 
  282
+    def test_datetime_month_view(self):
  283
+        BookSigning.objects.create(event_date=datetime.datetime(2008, 2, 1, 12, 0))
  284
+        BookSigning.objects.create(event_date=datetime.datetime(2008, 4, 2, 12, 0))
  285
+        BookSigning.objects.create(event_date=datetime.datetime(2008, 6, 3, 12, 0))
  286
+        res = self.client.get('/dates/booksignings/2008/apr/')
  287
+        self.assertEqual(res.status_code, 200)
  288
+
  289
+    @override_settings(USE_TZ=True, TIME_ZONE='Africa/Nairobi')
  290
+    def test_aware_datetime_month_view(self):
  291
+        BookSigning.objects.create(event_date=datetime.datetime(2008, 2, 1, 12, 0, tzinfo=timezone.utc))
  292
+        BookSigning.objects.create(event_date=datetime.datetime(2008, 4, 2, 12, 0, tzinfo=timezone.utc))
  293
+        BookSigning.objects.create(event_date=datetime.datetime(2008, 6, 3, 12, 0, tzinfo=timezone.utc))
  294
+        res = self.client.get('/dates/booksignings/2008/apr/')
  295
+        self.assertEqual(res.status_code, 200)
  296
+
248 297
 
249 298
 class WeekArchiveViewTests(TestCase):
250 299
     fixtures = ['generic-views-test-data.json']
@@ -300,6 +349,18 @@ def test_week_start_Monday(self):
300 349
         self.assertEqual(res.status_code, 200)
301 350
         self.assertEqual(res.context['week'], datetime.date(2008, 9, 29))
302 351
 
  352
+    def test_datetime_week_view(self):
  353
+        BookSigning.objects.create(event_date=datetime.datetime(2008, 4, 2, 12, 0))
  354
+        res = self.client.get('/dates/booksignings/2008/week/13/')
  355
+        self.assertEqual(res.status_code, 200)
  356
+
  357
+    @override_settings(USE_TZ=True, TIME_ZONE='Africa/Nairobi')
  358
+    def test_aware_datetime_week_view(self):
  359
+        BookSigning.objects.create(event_date=datetime.datetime(2008, 4, 2, 12, 0, tzinfo=timezone.utc))
  360
+        res = self.client.get('/dates/booksignings/2008/week/13/')
  361
+        self.assertEqual(res.status_code, 200)
  362
+
  363
+
303 364
 class DayArchiveViewTests(TestCase):
304 365
     fixtures = ['generic-views-test-data.json']
305 366
     urls = 'regressiontests.generic_views.urls'
@@ -388,6 +449,26 @@ def test_today_view(self):
388 449
         self.assertEqual(res.status_code, 200)
389 450
         self.assertEqual(res.context['day'], datetime.date.today())
390 451
 
  452
+    def test_datetime_day_view(self):
  453
+        BookSigning.objects.create(event_date=datetime.datetime(2008, 4, 2, 12, 0))
  454
+        res = self.client.get('/dates/booksignings/2008/apr/2/')
  455
+        self.assertEqual(res.status_code, 200)
  456
+
  457
+    @override_settings(USE_TZ=True, TIME_ZONE='Africa/Nairobi')
  458
+    def test_aware_datetime_day_view(self):
  459
+        BookSigning.objects.create(event_date=datetime.datetime(2008, 4, 2, 12, 0, tzinfo=timezone.utc))
  460
+        res = self.client.get('/dates/booksignings/2008/apr/2/')
  461
+        self.assertEqual(res.status_code, 200)
  462
+        # 2008-04-02T00:00:00+03:00 (beginning of day) > 2008-04-01T22:00:00+00:00 (book signing event date)
  463
+        BookSigning.objects.filter(pk=1).update(event_date=datetime.datetime(2008, 4, 1, 22, 0, tzinfo=timezone.utc))
  464
+        res = self.client.get('/dates/booksignings/2008/apr/2/')
  465
+        self.assertEqual(res.status_code, 200)
  466
+        # 2008-04-03T00:00:00+03:00 (end of day) > 2008-04-02T22:00:00+00:00 (book signing event date)
  467
+        BookSigning.objects.filter(pk=1).update(event_date=datetime.datetime(2008, 4, 2, 22, 0, tzinfo=timezone.utc))
  468
+        res = self.client.get('/dates/booksignings/2008/apr/2/')
  469
+        self.assertEqual(res.status_code, 404)
  470
+
  471
+
391 472
 class DateDetailViewTests(TestCase):
392 473
     fixtures = ['generic-views-test-data.json']
393 474
     urls = 'regressiontests.generic_views.urls'
@@ -441,3 +522,22 @@ def test_get_object_custom_queryset(self):
441 522
         res = self.client.get(
442 523
             '/dates/books/get_object_custom_queryset/2008/oct/01/1/')
443 524
         self.assertEqual(res.status_code, 404)
  525
+
  526
+    def test_datetime_date_detail(self):
  527
+        BookSigning.objects.create(event_date=datetime.datetime(2008, 4, 2, 12, 0))
  528
+        res = self.client.get('/dates/booksignings/2008/apr/2/1/')
  529
+        self.assertEqual(res.status_code, 200)
  530
+
  531
+    @override_settings(USE_TZ=True, TIME_ZONE='Africa/Nairobi')
  532
+    def test_aware_datetime_date_detail(self):
  533
+        BookSigning.objects.create(event_date=datetime.datetime(2008, 4, 2, 12, 0, tzinfo=timezone.utc))
  534
+        res = self.client.get('/dates/booksignings/2008/apr/2/1/')
  535
+        self.assertEqual(res.status_code, 200)
  536
+        # 2008-04-02T00:00:00+03:00 (beginning of day) > 2008-04-01T22:00:00+00:00 (book signing event date)
  537
+        BookSigning.objects.filter(pk=1).update(event_date=datetime.datetime(2008, 4, 1, 22, 0, tzinfo=timezone.utc))
  538
+        res = self.client.get('/dates/booksignings/2008/apr/2/1/')
  539
+        self.assertEqual(res.status_code, 200)
  540
+        # 2008-04-03T00:00:00+03:00 (end of day) > 2008-04-02T22:00:00+00:00 (book signing event date)
  541
+        BookSigning.objects.filter(pk=1).update(event_date=datetime.datetime(2008, 4, 2, 22, 0, tzinfo=timezone.utc))
  542
+        res = self.client.get('/dates/booksignings/2008/apr/2/1/')
  543
+        self.assertEqual(res.status_code, 404)
3  tests/regressiontests/generic_views/models.py
@@ -42,3 +42,6 @@ def __unicode__(self):
42 42
 class Page(models.Model):
43 43
     content = models.TextField()
44 44
     template = models.CharField(max_length=300)
  45
+
  46
+class BookSigning(models.Model):
  47
+    event_date = models.DateTimeField()
19  tests/regressiontests/generic_views/urls.py
@@ -108,6 +108,8 @@
108 108
         views.BookArchive.as_view(queryset=None)),
109 109
     (r'^dates/books/paginated/$',
110 110
         views.BookArchive.as_view(paginate_by=10)),
  111
+    (r'^dates/booksignings/$',
  112
+        views.BookSigningArchive.as_view()),
111 113
 
112 114
     # ListView
113 115
     (r'^list/dict/$',
@@ -156,6 +158,8 @@
156 158
         views.BookYearArchive.as_view(make_object_list=True, paginate_by=30)),
157 159
     (r'^dates/books/no_year/$',
158 160
         views.BookYearArchive.as_view()),
  161
+    (r'^dates/booksignings/(?P<year>\d{4})/$',
  162
+        views.BookSigningYearArchive.as_view()),
159 163
 
160 164
     # MonthArchiveView
161 165
     (r'^dates/books/(?P<year>\d{4})/(?P<month>[a-z]{3})/$',
@@ -170,6 +174,8 @@
170 174
         views.BookMonthArchive.as_view(paginate_by=30)),
171 175
     (r'^dates/books/(?P<year>\d{4})/no_month/$',
172 176
         views.BookMonthArchive.as_view()),
  177
+    (r'^dates/booksignings/(?P<year>\d{4})/(?P<month>[a-z]{3})/$',
  178
+        views.BookSigningMonthArchive.as_view()),
173 179
 
174 180
     # WeekArchiveView
175 181
     (r'^dates/books/(?P<year>\d{4})/week/(?P<week>\d{1,2})/$',
@@ -184,6 +190,8 @@
184 190
         views.BookWeekArchive.as_view()),
185 191
     (r'^dates/books/(?P<year>\d{4})/week/(?P<week>\d{1,2})/monday/$',
186 192
         views.BookWeekArchive.as_view(week_format='%W')),
  193
+    (r'^dates/booksignings/(?P<year>\d{4})/week/(?P<week>\d{1,2})/$',
  194
+        views.BookSigningWeekArchive.as_view()),
187 195
 
188 196
     # DayArchiveView
189 197
     (r'^dates/books/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\d{1,2})/$',
@@ -198,12 +206,16 @@
198 206
         views.BookDayArchive.as_view(paginate_by=True)),
199 207
     (r'^dates/books/(?P<year>\d{4})/(?P<month>[a-z]{3})/no_day/$',
200 208
         views.BookDayArchive.as_view()),
  209
+    (r'^dates/booksignings/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\d{1,2})/$',
  210
+        views.BookSigningDayArchive.as_view()),
201 211
 
202 212
     # TodayArchiveView
203  
-    (r'dates/books/today/$',
  213
+    (r'^dates/books/today/$',
204 214
         views.BookTodayArchive.as_view()),
205  
-    (r'dates/books/today/allow_empty/$',
  215
+    (r'^dates/books/today/allow_empty/$',
206 216
         views.BookTodayArchive.as_view(allow_empty=True)),
  217
+    (r'^dates/booksignings/today/$',
  218
+        views.BookSigningTodayArchive.as_view()),
207 219
 
208 220
     # DateDetailView
209 221
     (r'^dates/books/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\d{1,2})/(?P<pk>\d+)/$',
@@ -221,6 +233,9 @@
221 233
     (r'^dates/books/get_object_custom_queryset/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\d{1,2})/(?P<pk>\d+)/$',
222 234
         views.BookDetailGetObjectCustomQueryset.as_view()),
223 235
 
  236
+    (r'^dates/booksignings/(?P<year>\d{4})/(?P<month>[a-z]{3})/(?P<day>\d{1,2})/(?P<pk>\d+)/$',
  237
+        views.BookSigningDetail.as_view()),
  238
+
224 239
     # Useful for testing redirects
225 240
     (r'^accounts/login/$',  'django.contrib.auth.views.login')
226 241
 )
30  tests/regressiontests/generic_views/views.py
@@ -7,7 +7,7 @@
7 7
 from django.views import generic
8 8
 
9 9
 from .forms import AuthorForm
10  
-from .models import Artist, Author, Book, Page
  10
+from .models import Artist, Author, Book, Page, BookSigning
11 11
 
12 12
 
13 13
 class CustomTemplateView(generic.TemplateView):
@@ -198,3 +198,31 @@ def get_context_data(self, **kwargs):
198 198
 
199 199
     def get_context_object_name(self, obj):
200 200
         return "test_name"
  201
+
  202
+class BookSigningConfig(object):
  203
+    model = BookSigning
  204
+    date_field = 'event_date'
  205
+    # use the same templates as for books
  206
+    def get_template_names(self):
  207
+        return ['generic_views/book%s.html' % self.template_name_suffix]
  208
+
  209
+class BookSigningArchive(BookSigningConfig, generic.ArchiveIndexView):
  210
+    pass
  211
+
  212
+class BookSigningYearArchive(BookSigningConfig, generic.YearArchiveView):
  213
+    pass
  214
+
  215
+class BookSigningMonthArchive(BookSigningConfig, generic.MonthArchiveView):
  216
+    pass
  217
+
  218
+class BookSigningWeekArchive(BookSigningConfig, generic.WeekArchiveView):
  219
+    pass
  220
+
  221
+class BookSigningDayArchive(BookSigningConfig, generic.DayArchiveView):
  222
+    pass
  223
+
  224
+class BookSigningTodayArchive(BookSigningConfig, generic.TodayArchiveView):
  225
+    pass
  226
+
  227
+class BookSigningDetail(BookSigningConfig, generic.DateDetailView):
  228
+    context_object_name = 'book'

0 notes on commit 78ba967

Please sign in to comment.
Something went wrong with that request. Please try again.