77
88from collections import defaultdict
99import inspect
10+ import logging
1011import types
1112
13+ from django .conf import settings
1214from django .db import models
1315from django .db .models import ManyToOneRel
1416from django .core .exceptions import FieldDoesNotExist
1517
18+ logger = logging .getLogger ()
19+
1620NEVER = 0
1721ALWAYS = 1
1822IF_TRUTHY = 2
@@ -63,8 +67,14 @@ def _get_unexpanded_field_value(inst, field_name, field_type):
6367 return {display_key : getattr (obj , display_key )}
6468
6569
66- def _get_filtered_field_value (
67- inst , field_name , field_type , filter_def , expand_this , expand_children
70+ def _get_filtered_field_value ( # noqa: C901
71+ inst ,
72+ field_name ,
73+ field_type ,
74+ filter_def ,
75+ expand_this ,
76+ expand_children ,
77+ filter_cache ,
6878):
6979 # get the value from inst
7080 if field_type == NEVER :
@@ -80,6 +90,25 @@ def _get_filtered_field_value(
8090 val = _get_unexpanded_field_value (inst , field_name , field_type )
8191 else :
8292 try :
93+ if isinstance (inst , (models .Model )):
94+ try :
95+ field_meta = inst ._meta .get_field (field_name )
96+ if field_meta .is_relation :
97+ val_pk = getattr (inst , field_meta .attname )
98+ val_cls = field_meta .related_model
99+ val_expand_children = expand_children .get (field_name , {})
100+ cache_key = _make_filter_cache_key (
101+ val_expand_children , val_cls , val_pk
102+ )
103+ if cache_key in filter_cache :
104+ logger .debug (
105+ "ev=filter_cache, status=hit, key=%s" , cache_key
106+ )
107+ return filter_cache [cache_key ]
108+ except FieldDoesNotExist :
109+ # this happens when you reference the special field "pk" in filters
110+ pass
111+
83112 val = getattr (inst , field_name )
84113 except (AttributeError , FieldDoesNotExist ) as e : # noqa
85114 return None
@@ -93,7 +122,11 @@ def _get_filtered_field_value(
93122 val , (list , tuple , models .Model , models .query .QuerySet )
94123 ):
95124 val = _apply_filters_to_object (
96- val , filter_def , expand_children = expand_children , klass = val .__class__
125+ val ,
126+ filter_def ,
127+ expand_children = expand_children ,
128+ klass = val .__class__ ,
129+ filter_cache = filter_cache ,
97130 )
98131
99132 if (
@@ -107,22 +140,53 @@ def _get_filtered_field_value(
107140 return None
108141
109142
143+ def _make_filter_cache_key (expand_children , klass , pk ):
144+ return (str (expand_children ), klass , str (pk ))
145+
146+
110147# TODO: make this method less complex and remove the `noqa`
111148def _apply_filters_to_object ( # noqa: C901
112- inst , filter_def , expand_children = None , klass = None
149+ inst ,
150+ filter_def ,
151+ expand_children = None ,
152+ klass = None ,
153+ filter_cache = None ,
113154):
155+ is_cacheable = False
156+ caching_enabled = getattr (settings , "DDA_FILTER_MODEL_CACHING_ENABLED" , False )
157+ if (
158+ caching_enabled
159+ and isinstance (inst , (models .Model ,))
160+ and filter_cache is not None
161+ ):
162+ is_cacheable = True
163+ pk = getattr (inst , "pk" )
164+ cache_key = _make_filter_cache_key (expand_children , klass , pk )
165+ if cache_key in filter_cache :
166+ logger .debug ("ev=filter_cache, status=hit, key=%s" , cache_key )
167+ return filter_cache [cache_key ]
168+ else :
169+ logger .debug ("ev=filter_cache, status=miss, key=%s" , cache_key )
114170 if isinstance (inst , (list , tuple , models .query .QuerySet )):
115171 # if it's a tuple or list, iterate over the collection and call _apply_filters_to_object on each item
116172 return [
117173 _apply_filters_to_object (
118- item , filter_def , expand_children = expand_children , klass = item .__class__
174+ item ,
175+ filter_def ,
176+ expand_children = expand_children ,
177+ klass = item .__class__ ,
178+ filter_cache = filter_cache ,
119179 )
120180 for item in inst
121181 ]
122182 elif isinstance (inst , (dict ,)):
123183 return {
124184 k : _apply_filters_to_object (
125- v , filter_def , expand_children = expand_children , klass = v .__class__
185+ v ,
186+ filter_def ,
187+ expand_children = expand_children ,
188+ klass = v .__class__ ,
189+ filter_cache = filter_cache ,
126190 )
127191 for k , v in inst .items ()
128192 }
@@ -134,16 +198,28 @@ def _apply_filters_to_object( # noqa: C901
134198 for base_class in inspect .getmro (klass ):
135199 if base_class in filter_def :
136200 result = _apply_filters_to_object (
137- inst , filter_def , expand_children = expand_children , klass = base_class
201+ inst ,
202+ filter_def ,
203+ expand_children = expand_children ,
204+ klass = base_class ,
205+ filter_cache = filter_cache ,
138206 )
139207 break
208+
209+ if is_cacheable :
210+ filter_cache [cache_key ] = result
211+
140212 return result
141213 else :
142214 # first, recursively populate from any ancestor classes in the inheritance hierarchy
143215 result = defaultdict (list )
144216 for base in klass .__bases__ :
145217 filtered_ancestor = _apply_filters_to_object (
146- inst , filter_def , expand_children = expand_children , klass = base
218+ inst ,
219+ filter_def ,
220+ expand_children = expand_children ,
221+ klass = base ,
222+ filter_cache = filter_cache ,
147223 )
148224 if filtered_ancestor :
149225 result .update (filtered_ancestor )
@@ -166,6 +242,7 @@ def _apply_filters_to_object( # noqa: C901
166242 filter_def ,
167243 expand_this = field_name in expand_children ,
168244 expand_children = expand_children .get (field_name , {}),
245+ filter_cache = filter_cache ,
169246 )
170247
171248 if value is not None and value != DEFAULT_UNEXPANDED_VALUE :
@@ -176,6 +253,9 @@ def _apply_filters_to_object( # noqa: C901
176253 if expandables :
177254 result [EXPANDABLE_FIELD_KEY ] += expandables
178255
256+ if is_cacheable :
257+ filter_cache [cache_key ] = result
258+
179259 return result
180260
181261
@@ -195,5 +275,9 @@ def apply_filters_to_object(inst, filter_def, expand_header=""):
195275 else :
196276 expand_dict = {}
197277 return _apply_filters_to_object (
198- inst , filter_def , expand_children = expand_dict , klass = inst .__class__
278+ inst ,
279+ filter_def ,
280+ expand_children = expand_dict ,
281+ klass = inst .__class__ ,
282+ filter_cache = {},
199283 )
0 commit comments