diff --git a/django/views/generic/dates.py b/django/views/generic/dates.py index b870301222..63151dd5a5 100644 --- a/django/views/generic/dates.py +++ b/django/views/generic/dates.py @@ -218,7 +218,7 @@ class WeekMixin: 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 + if week_format in {'%W', '%V'}: # week starts on Monday return date.weekday() elif week_format == '%U': # week starts on Sunday return (date.weekday() + 1) % 7 @@ -485,7 +485,7 @@ class BaseWeekArchiveView(YearMixin, WeekMixin, BaseDateListView): date_field = self.get_date_field() week_format = self.get_week_format() - week_choices = {'%W': '1', '%U': '0'} + week_choices = {'%W': '1', '%U': '0', '%V': '1'} try: week_start = week_choices[week_format] except KeyError: @@ -493,10 +493,15 @@ class BaseWeekArchiveView(YearMixin, WeekMixin, BaseDateListView): week_format, ', '.join(sorted(week_choices)), )) - date = _date_from_string(year, self.get_year_format(), - week_start, '%w', - week, week_format) - + year_format = self.get_year_format() + if week_format == '%V' and year_format != '%G': + raise ValueError( + "ISO week directive '%s' is incompatible with the year " + "directive '%s'. Use the ISO year '%%G' instead." % ( + week_format, year_format, + ) + ) + date = _date_from_string(year, 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 = { diff --git a/docs/ref/class-based-views/generic-date-based.txt b/docs/ref/class-based-views/generic-date-based.txt index a42896c058..dcfb16e54b 100644 --- a/docs/ref/class-based-views/generic-date-based.txt +++ b/docs/ref/class-based-views/generic-date-based.txt @@ -342,6 +342,12 @@ views for displaying drilldown pages for date-based data. * ``'%W'``: Similar to ``'%U'``, except it assumes that the week begins on Monday. This is not the same as the ISO 8601 week number. + * ``'%V'``: ISO 8601 week number where the week begins on Monday. + + .. versionadded:: 3.2 + + Support for the ``'%V'`` week format was added. + **Example myapp/views.py**:: from django.views.generic.dates import WeekArchiveView diff --git a/docs/ref/class-based-views/mixins-date-based.txt b/docs/ref/class-based-views/mixins-date-based.txt index 471e222a6e..95e1868d1b 100644 --- a/docs/ref/class-based-views/mixins-date-based.txt +++ b/docs/ref/class-based-views/mixins-date-based.txt @@ -180,7 +180,12 @@ Date-based mixins The :func:`~time.strftime` format to use when parsing the week. By default, this is ``'%U'``, which means the week starts on Sunday. Set - it to ``'%W'`` if your week starts on Monday. + it to ``'%W'`` or ``'%V'`` (ISO 8601 week) if your week starts on + Monday. + + .. versionadded:: 3.2 + + Support for the ``'%V'`` week format was added. .. attribute:: week diff --git a/docs/releases/3.2.txt b/docs/releases/3.2.txt index 743d341a7f..9f1d785934 100644 --- a/docs/releases/3.2.txt +++ b/docs/releases/3.2.txt @@ -166,7 +166,10 @@ Forms Generic Views ~~~~~~~~~~~~~ -* ... +* The ``week_format`` attributes of + :class:`~django.views.generic.dates.WeekMixin` and + :class:`~django.views.generic.dates.WeekArchiveView` now support the + ``'%V'`` ISO 8601 week format. Internationalization ~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/generic_views/test_dates.py b/tests/generic_views/test_dates.py index 0109345c41..bff58dcd0c 100644 --- a/tests/generic_views/test_dates.py +++ b/tests/generic_views/test_dates.py @@ -538,10 +538,29 @@ class WeekArchiveViewTests(TestDataMixin, TestCase): self.assertEqual(res.status_code, 200) self.assertEqual(res.context['week'], datetime.date(2008, 9, 29)) + def test_week_iso_format(self): + res = self.client.get('/dates/books/2008/week/40/iso_format/') + self.assertEqual(res.status_code, 200) + self.assertTemplateUsed(res, 'generic_views/book_archive_week.html') + self.assertEqual( + list(res.context['book_list']), + [Book.objects.get(pubdate=datetime.date(2008, 10, 1))], + ) + self.assertEqual(res.context['week'], datetime.date(2008, 9, 29)) + def test_unknown_week_format(self): - with self.assertRaisesMessage(ValueError, "Unknown week format '%T'. Choices are: %U, %W"): + msg = "Unknown week format '%T'. Choices are: %U, %V, %W" + with self.assertRaisesMessage(ValueError, msg): self.client.get('/dates/books/2008/week/39/unknown_week_format/') + def test_incompatible_iso_week_format_view(self): + msg = ( + "ISO week directive '%V' is incompatible with the year directive " + "'%Y'. Use the ISO year '%G' instead." + ) + with self.assertRaisesMessage(ValueError, msg): + self.client.get('/dates/books/2008/week/40/invalid_iso_week_year_format/') + def test_datetime_week_view(self): BookSigning.objects.create(event_date=datetime.datetime(2008, 4, 2, 12, 0)) res = self.client.get('/dates/booksignings/2008/week/13/') diff --git a/tests/generic_views/urls.py b/tests/generic_views/urls.py index d547c5be4a..9537240ab8 100644 --- a/tests/generic_views/urls.py +++ b/tests/generic_views/urls.py @@ -190,6 +190,14 @@ urlpatterns = [ 'dates/books//week//unknown_week_format/', views.BookWeekArchive.as_view(week_format='%T'), ), + path( + 'dates/books//week//iso_format/', + views.BookWeekArchive.as_view(year_format='%G', week_format='%V'), + ), + path( + 'dates/books//week//invalid_iso_week_year_format/', + views.BookWeekArchive.as_view(week_format='%V'), + ), path('dates/booksignings//week//', views.BookSigningWeekArchive.as_view()), # DayArchiveView