From 4d60261b2a77460b4c127c3d832518b95e11a0ac Mon Sep 17 00:00:00 2001
From: Daniel Tao <daniel.tao@gmail.com>
Date: Fri, 15 Sep 2017 16:16:44 -0500
Subject: [PATCH] Fixed #28601 -- Prevented cache.get_or_set() from caching
 None if default is a callable that returns None.

---
 django/core/cache/backends/base.py | 12 +++++++-----
 docs/releases/1.11.7.txt           |  3 ++-
 tests/cache/tests.py               |  5 +++++
 3 files changed, 14 insertions(+), 6 deletions(-)

diff --git a/django/core/cache/backends/base.py b/django/core/cache/backends/base.py
index aaf34c042e..cf0df7cc68 100644
--- a/django/core/cache/backends/base.py
+++ b/django/core/cache/backends/base.py
@@ -155,13 +155,15 @@ class BaseCache:
         Return the value of the key stored or retrieved.
         """
         val = self.get(key, version=version)
-        if val is None and default is not None:
+        if val is None:
             if callable(default):
                 default = default()
-            self.add(key, default, timeout=timeout, version=version)
-            # Fetch the value again to avoid a race condition if another caller
-            # added a value between the first get() and the add() above.
-            return self.get(key, default, version=version)
+            if default is not None:
+                self.add(key, default, timeout=timeout, version=version)
+                # Fetch the value again to avoid a race condition if another
+                # caller added a value between the first get() and the add()
+                # above.
+                return self.get(key, default, version=version)
         return val
 
     def has_key(self, key, version=None):
diff --git a/docs/releases/1.11.7.txt b/docs/releases/1.11.7.txt
index 41d144e742..61f0d6d012 100644
--- a/docs/releases/1.11.7.txt
+++ b/docs/releases/1.11.7.txt
@@ -9,4 +9,5 @@ Django 1.11.7 fixes several bugs in 1.11.6.
 Bugfixes
 ========
 
-* ...
+* Prevented ``cache.get_or_set()`` from caching ``None`` if the ``default``
+  argument is a callable that returns ``None`` (:ticket:`28601`).
diff --git a/tests/cache/tests.py b/tests/cache/tests.py
index 57e4ec15b4..9a903ef203 100644
--- a/tests/cache/tests.py
+++ b/tests/cache/tests.py
@@ -924,6 +924,11 @@ class BaseCacheTests:
         self.assertEqual(cache.get_or_set('mykey', my_callable), 'value')
         self.assertEqual(cache.get_or_set('mykey', my_callable()), 'value')
 
+    def test_get_or_set_callable_returning_none(self):
+        self.assertIsNone(cache.get_or_set('mykey', lambda: None))
+        # Previous get_or_set() doesn't store None in the cache.
+        self.assertEqual(cache.get('mykey', 'default'), 'default')
+
     def test_get_or_set_version(self):
         msg = "get_or_set() missing 1 required positional argument: 'default'"
         cache.get_or_set('brian', 1979, version=2)