8
8
from django .db .models .sql import query
9
9
from django .utils import encoding
10
10
11
- from .invalidation import invalidator , flush_key , make_key
11
+ from .invalidation import invalidator , flush_key , make_key , byid
12
12
13
13
14
14
class NullHandler (logging .Handler ):
@@ -24,6 +24,7 @@ def emit(self, record):
24
24
FOREVER = 0
25
25
NO_CACHE = - 1
26
26
CACHE_PREFIX = getattr (settings , 'CACHE_PREFIX' , '' )
27
+ FETCH_BY_ID = getattr (settings , 'FETCH_BY_ID' , False )
27
28
28
29
scheme , _ , _ = parse_backend_uri (settings .CACHE_BACKEND )
29
30
cache .scheme = scheme
@@ -142,8 +143,44 @@ def iterator(self):
142
143
query_string = self .query_key ()
143
144
except query .EmptyResultSet :
144
145
return iterator ()
146
+ if FETCH_BY_ID :
147
+ iterator = self .fetch_by_id
145
148
return iter (CacheMachine (query_string , iterator , self .timeout ))
146
149
150
+ def fetch_by_id (self ):
151
+ """
152
+ Run two queries to get objects: one for the ids, one for id__in=ids.
153
+
154
+ After getting ids from the first query we can try cache.get_many to
155
+ reuse objects we've already seen. Then we fetch the remaining items
156
+ from the db, and put those in the cache. This prevents cache
157
+ duplication.
158
+ """
159
+ # Include columns from extra since they could be used in the query's
160
+ # order_by.
161
+ vals = self .values_list ('pk' , * self .query .extra .keys ())
162
+ pks = [val [0 ] for val in vals ]
163
+ keys = dict ((byid (self .model ._cache_key (pk )), pk ) for pk in pks )
164
+ cached = dict ((k , v ) for k , v in cache .get_many (keys ).items ()
165
+ if v is not None )
166
+
167
+ missed = [pk for key , pk in keys .items () if key not in cached ]
168
+ # Clear out the default ordering since we order based on the query.
169
+ others = self .model .objects .filter (pk__in = missed ).order_by ()
170
+ if hasattr (others , 'no_cache' ):
171
+ others = others .no_cache ()
172
+ if self .query .select_related :
173
+ others .dup_select_related (self )
174
+
175
+ # Put the fetched objects back in cache.
176
+ new = dict ((byid (o ), o ) for o in others )
177
+ cache .set_many (new )
178
+
179
+ # Use pks to return the objects in the correct order.
180
+ objects = dict ((o .pk , o ) for o in cached .values () + new .values ())
181
+ for pk in pks :
182
+ yield objects [pk ]
183
+
147
184
def count (self ):
148
185
timeout = getattr (settings , 'CACHE_COUNT_TIMEOUT' , None )
149
186
super_count = super (CachingQuerySet , self ).count
0 commit comments