From ae2da5ba652c1a11cd88dcb119744dcecaeb03ee Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Fri, 4 Mar 2022 12:57:10 +0000 Subject: [PATCH] Fixed #33562 -- Made HttpResponse.set_cookie() support timedelta for the max_age argument. --- django/http/response.py | 6 ++++++ docs/ref/request-response.txt | 12 +++++++++--- docs/releases/4.1.txt | 3 ++- tests/responses/test_cookie.py | 5 +++++ tests/signed_cookies_tests/tests.py | 7 +++++++ 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/django/http/response.py b/django/http/response.py index 8ae4719575..59120c8e0d 100644 --- a/django/http/response.py +++ b/django/http/response.py @@ -227,6 +227,10 @@ class HttpResponseBase: - a naive ``datetime.datetime`` object in UTC, - an aware ``datetime.datetime`` object in any time zone. If it is a ``datetime.datetime`` object then calculate ``max_age``. + + ``max_age`` can be: + - int/float specifying seconds, + - ``datetime.timedelta`` object. """ self.cookies[key] = value if expires is not None: @@ -246,6 +250,8 @@ class HttpResponseBase: else: self.cookies[key]["expires"] = "" if max_age is not None: + if isinstance(max_age, datetime.timedelta): + max_age = max_age.total_seconds() self.cookies[key]["max-age"] = int(max_age) # IE requires expires, so set it if hasn't been already. if not expires: diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index f651a77c4b..3a3c4d14fc 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -853,9 +853,15 @@ Methods Sets a cookie. The parameters are the same as in the :class:`~http.cookies.Morsel` cookie object in the Python standard library. - * ``max_age`` should be an integer number of seconds, or ``None`` (default) - if the cookie should last only as long as the client's browser session. - If ``expires`` is not specified, it will be calculated. + * ``max_age`` should be a :class:`~datetime.timedelta` object, an integer + number of seconds, or ``None`` (default) if the cookie should last only + as long as the client's browser session. If ``expires`` is not specified, + it will be calculated. + + .. versionchanged:: 4.1 + + Support for ``timedelta`` objects was added. + * ``expires`` should either be a string in the format ``"Wdy, DD-Mon-YY HH:MM:SS GMT"`` or a ``datetime.datetime`` object in UTC. If ``expires`` is a ``datetime`` object, the ``max_age`` diff --git a/docs/releases/4.1.txt b/docs/releases/4.1.txt index 9e682e6cc2..72c1c15c7a 100644 --- a/docs/releases/4.1.txt +++ b/docs/releases/4.1.txt @@ -281,7 +281,8 @@ Models Requests and Responses ~~~~~~~~~~~~~~~~~~~~~~ -* ... +* :meth:`.HttpResponse.set_cookie` now supports :class:`~datetime.timedelta` + objects for the ``max_age`` argument. Security ~~~~~~~~ diff --git a/tests/responses/test_cookie.py b/tests/responses/test_cookie.py index b37c6fb7b6..3a57dcfe45 100644 --- a/tests/responses/test_cookie.py +++ b/tests/responses/test_cookie.py @@ -71,6 +71,11 @@ class SetCookieTests(SimpleTestCase): response.set_cookie("max_age", max_age=10.6) self.assertEqual(response.cookies["max_age"]["max-age"], 10) + def test_max_age_timedelta(self): + response = HttpResponse() + response.set_cookie("max_age", max_age=timedelta(hours=1)) + self.assertEqual(response.cookies["max_age"]["max-age"], 3600) + def test_httponly_cookie(self): response = HttpResponse() response.set_cookie("example", httponly=True) diff --git a/tests/signed_cookies_tests/tests.py b/tests/signed_cookies_tests/tests.py index a7ad0c0d84..876887d883 100644 --- a/tests/signed_cookies_tests/tests.py +++ b/tests/signed_cookies_tests/tests.py @@ -62,6 +62,13 @@ class SignedCookieTest(SimpleTestCase): with self.assertRaises(signing.SignatureExpired): request.get_signed_cookie("c", max_age=timedelta(seconds=10)) + def test_set_signed_cookie_max_age_argument(self): + response = HttpResponse() + response.set_signed_cookie("c", "value", max_age=100) + self.assertEqual(response.cookies["c"]["max-age"], 100) + response.set_signed_cookie("d", "value", max_age=timedelta(hours=2)) + self.assertEqual(response.cookies["d"]["max-age"], 7200) + @override_settings(SECRET_KEY=b"\xe7") def test_signed_cookies_with_binary_key(self): response = HttpResponse()