Skip to content

Commit fcb09b5

Browse files
committed
Fixed django#10890: added prev/next_week in the context
of per-week date-based generic views. Thanks ee_lars for the report.
1 parent bbb1258 commit fcb09b5

File tree

2 files changed

+76
-12
lines changed

2 files changed

+76
-12
lines changed

django/views/generic/dates.py

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,15 @@ def get_next_month(self, date):
6969
next = date.replace(year=date.year + 1, month=1, day=1)
7070
else:
7171
next = date.replace(month=date.month + 1, day=1)
72-
return _get_next_prev_month(self, next, is_previous=False, use_first_day=True)
72+
return _get_next_prev(self, next, is_previous=False, period='month')
7373

7474
def get_previous_month(self, date):
7575
"""
7676
Get the previous valid month.
7777
"""
7878
# prev must be the last day of the previous month.
7979
prev = date.replace(day=1) - datetime.timedelta(days=1)
80-
return _get_next_prev_month(self, prev, is_previous=True, use_first_day=True)
80+
return _get_next_prev(self, prev, is_previous=True, period='month')
8181

8282

8383
class DayMixin(object):
@@ -109,14 +109,14 @@ def get_next_day(self, date):
109109
Get the next valid day.
110110
"""
111111
next = date + datetime.timedelta(days=1)
112-
return _get_next_prev_month(self, next, is_previous=False, use_first_day=False)
112+
return _get_next_prev(self, next, is_previous=False, period='day')
113113

114114
def get_previous_day(self, date):
115115
"""
116116
Get the previous valid day.
117117
"""
118118
prev = date - datetime.timedelta(days=1)
119-
return _get_next_prev_month(self, prev, is_previous=True, use_first_day=False)
119+
return _get_next_prev(self, prev, is_previous=True, period='day')
120120

121121

122122
class WeekMixin(object):
@@ -143,6 +143,30 @@ def get_week(self):
143143
raise Http404(_(u"No week specified"))
144144
return week
145145

146+
def get_next_week(self, date):
147+
"""
148+
Get the next valid week.
149+
"""
150+
# next must be the first day of the next week.
151+
next = date + datetime.timedelta(days=7 - self._get_weekday(date))
152+
return _get_next_prev(self, next, is_previous=False, period='week')
153+
154+
def get_previous_week(self, date):
155+
"""
156+
Get the previous valid week.
157+
"""
158+
# prev must be the last day of the previous week.
159+
prev = date - datetime.timedelta(days=self._get_weekday(date) + 1)
160+
return _get_next_prev(self, prev, is_previous=True, period='week')
161+
162+
def _get_weekday(self, date):
163+
week_format = self.get_week_format()
164+
if week_format == '%W': # week starts on Monday
165+
return date.weekday()
166+
elif week_format == '%U': # week starts on Sunday
167+
return (date.weekday() + 1) % 7
168+
else:
169+
raise ValueError("unknown week format: %s" % week_format)
146170

147171
class DateMixin(object):
148172
"""
@@ -428,7 +452,11 @@ def get_dated_items(self):
428452

429453
qs = self.get_dated_queryset(**lookup_kwargs)
430454

431-
return (None, qs, {'week': date})
455+
return (None, qs, {
456+
'week': date,
457+
'next_week': self.get_next_week(date),
458+
'previous_week': self.get_previous_week(date),
459+
})
432460

433461

434462
class WeekArchiveView(MultipleObjectTemplateResponseMixin, BaseWeekArchiveView):
@@ -557,7 +585,7 @@ def _date_from_string(year, year_format, month='', month_format='', day='', day_
557585
})
558586

559587

560-
def _get_next_prev_month(generic_view, naive_result, is_previous, use_first_day):
588+
def _get_next_prev(generic_view, naive_result, is_previous, period):
561589
"""
562590
Helper: Get the next or the previous valid date. The idea is to allow
563591
links on month/day views to never be 404s by never providing a date
@@ -620,10 +648,15 @@ def _get_next_prev_month(generic_view, naive_result, is_previous, use_first_day)
620648
result = timezone.localtime(result)
621649
result = result.date()
622650

623-
# For month views, we always want to have a date that's the first of the
624-
# month for consistency's sake.
625-
if result and use_first_day:
626-
result = result.replace(day=1)
651+
if result:
652+
if period == 'month':
653+
# first day of the month
654+
result = result.replace(day=1)
655+
elif period == 'week':
656+
# monday of the week
657+
result = result - datetime.timedelta(days=generic_view._get_weekday(result))
658+
elif period != 'day':
659+
raise ValueError('invalid period: %s' % period)
627660

628661
# Check against future dates.
629662
if result and (allow_future or result < datetime.date.today()):

tests/regressiontests/generic_views/dates.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ def test_month_view_allow_empty(self):
196196
self.assertEqual(list(res.context['book_list']), [])
197197
self.assertEqual(res.context['month'], datetime.date(2000, 1, 1))
198198

199-
# Since it's allow empty, next/prev are allowed to be empty months (#7164)
199+
# Since allow_empty=True, next/prev are allowed to be empty months (#7164)
200200
self.assertEqual(res.context['next_month'], datetime.date(2000, 2, 1))
201201
self.assertEqual(res.context['previous_month'], datetime.date(1999, 12, 1))
202202

@@ -222,7 +222,7 @@ def test_month_view_allow_future(self):
222222
self.assertEqual(list(res.context['book_list']), [b])
223223
self.assertEqual(res.context['month'], future)
224224

225-
# Since it's allow_future but not allow_empty, next/prev are not
225+
# Since allow_future = True but not allow_empty, next/prev are not
226226
# allowed to be empty months (#7164)
227227
self.assertEqual(res.context['next_month'], None)
228228
self.assertEqual(res.context['previous_month'], datetime.date(2008, 10, 1))
@@ -298,17 +298,35 @@ def test_week_view(self):
298298
self.assertEqual(res.context['book_list'][0], Book.objects.get(pubdate=datetime.date(2008, 10, 1)))
299299
self.assertEqual(res.context['week'], datetime.date(2008, 9, 28))
300300

301+
# Since allow_empty=False, next/prev weeks must be valid
302+
self.assertEqual(res.context['next_week'], None)
303+
self.assertEqual(res.context['previous_week'], datetime.date(2006, 4, 30))
304+
301305
def test_week_view_allow_empty(self):
306+
# allow_empty = False, empty week
302307
res = self.client.get('/dates/books/2008/week/12/')
303308
self.assertEqual(res.status_code, 404)
304309

310+
# allow_empty = True, empty month
305311
res = self.client.get('/dates/books/2008/week/12/allow_empty/')
306312
self.assertEqual(res.status_code, 200)
307313
self.assertEqual(list(res.context['book_list']), [])
314+
self.assertEqual(res.context['week'], datetime.date(2008, 3, 23))
315+
316+
# Since allow_empty=True, next/prev are allowed to be empty weeks
317+
self.assertEqual(res.context['next_week'], datetime.date(2008, 3, 30))
318+
self.assertEqual(res.context['previous_week'], datetime.date(2008, 3, 16))
319+
320+
# allow_empty but not allow_future: next_week should be empty
321+
url = datetime.date.today().strftime('/dates/books/%Y/week/%U/allow_empty/').lower()
322+
res = self.client.get(url)
323+
self.assertEqual(res.status_code, 200)
324+
self.assertEqual(res.context['next_week'], None)
308325

309326
def test_week_view_allow_future(self):
310327
# January 7th always falls in week 1, given Python's definition of week numbers
311328
future = datetime.date(datetime.date.today().year + 1, 1, 7)
329+
future_sunday = future - datetime.timedelta(days=(future.weekday() + 1) % 7)
312330
b = Book.objects.create(name="The New New Testement", pages=600, pubdate=future)
313331

314332
res = self.client.get('/dates/books/%s/week/1/' % future.year)
@@ -317,6 +335,19 @@ def test_week_view_allow_future(self):
317335
res = self.client.get('/dates/books/%s/week/1/allow_future/' % future.year)
318336
self.assertEqual(res.status_code, 200)
319337
self.assertEqual(list(res.context['book_list']), [b])
338+
self.assertEqual(res.context['week'], future_sunday)
339+
340+
# Since allow_future = True but not allow_empty, next/prev are not
341+
# allowed to be empty weeks
342+
self.assertEqual(res.context['next_week'], None)
343+
self.assertEqual(res.context['previous_week'], datetime.date(2008, 9, 28))
344+
345+
# allow_future, but not allow_empty, with a current week. So next
346+
# should be in the future
347+
res = self.client.get('/dates/books/2008/week/39/allow_future/')
348+
self.assertEqual(res.status_code, 200)
349+
self.assertEqual(res.context['next_week'], future_sunday)
350+
self.assertEqual(res.context['previous_week'], datetime.date(2006, 4, 30))
320351

321352
def test_week_view_paginated(self):
322353
week_start = datetime.date(2008, 9, 28)

0 commit comments

Comments
 (0)