|
10 | 10 | from django.db.models.constants import LOOKUP_SEP
|
11 | 11 | from django.db.models.fields.related import ForeignObjectRel
|
12 | 12 | from django.utils import six
|
13 |
| -from django.utils.translation import ugettext as _ |
14 | 13 |
|
15 | 14 | from .conf import settings
|
16 | 15 | from .compat import remote_field, remote_queryset
|
|
21 | 20 | from .utils import try_dbfield, get_all_model_fields, get_model_field, resolve_field
|
22 | 21 |
|
23 | 22 |
|
24 |
| -def get_declared_filters(bases, attrs, with_base_filters=True): |
25 |
| - filters = [] |
26 |
| - for filter_name, obj in list(attrs.items()): |
27 |
| - if isinstance(obj, Filter): |
28 |
| - obj = attrs.pop(filter_name) |
29 |
| - if getattr(obj, 'name', None) is None: |
30 |
| - obj.name = filter_name |
31 |
| - filters.append((filter_name, obj)) |
32 |
| - filters.sort(key=lambda x: x[1].creation_counter) |
33 |
| - |
34 |
| - if with_base_filters: |
35 |
| - for base in bases[::-1]: |
36 |
| - if hasattr(base, 'base_filters'): |
37 |
| - filters = list(base.base_filters.items()) + filters |
38 |
| - else: |
39 |
| - for base in bases[::-1]: |
40 |
| - if hasattr(base, 'declared_filters'): |
41 |
| - filters = list(base.declared_filters.items()) + filters |
| 23 | +def get_filter_name(field_name, lookup_expr): |
| 24 | + """ |
| 25 | + Combine a field name and lookup expression into a usable filter name. |
| 26 | + Exact lookups are the implicit default, so "exact" is stripped from the |
| 27 | + end of the filter name. |
| 28 | + """ |
| 29 | + filter_name = LOOKUP_SEP.join([field_name, lookup_expr]) |
42 | 30 |
|
43 |
| - return OrderedDict(filters) |
44 |
| - |
45 |
| - |
46 |
| -def filters_for_model(model, fields=None, exclude=None, filter_for_field=None, |
47 |
| - filter_for_reverse_field=None): |
48 |
| - field_dict = OrderedDict() |
49 |
| - |
50 |
| - # Setting exclude with no fields implies all other fields. |
51 |
| - if exclude is not None and fields is None: |
52 |
| - fields = ALL_FIELDS |
53 |
| - |
54 |
| - # All implies all db fields associated with a filter_class. |
55 |
| - if fields == ALL_FIELDS: |
56 |
| - fields = get_all_model_fields(model) |
57 |
| - |
58 |
| - # Loop through the list of fields. |
59 |
| - for f in fields: |
60 |
| - # Skip the field if excluded. |
61 |
| - if exclude is not None and f in exclude: |
62 |
| - continue |
63 |
| - field = get_model_field(model, f) |
64 |
| - # Do nothing if the field doesn't exist. |
65 |
| - if field is None: |
66 |
| - field_dict[f] = None |
67 |
| - continue |
68 |
| - if isinstance(field, ForeignObjectRel): |
69 |
| - filter_ = filter_for_reverse_field(field, f) |
70 |
| - if filter_: |
71 |
| - field_dict[f] = filter_ |
72 |
| - # If fields is a dictionary, it must contain lists. |
73 |
| - elif isinstance(fields, dict): |
74 |
| - # Create a filter for each lookup type. |
75 |
| - for lookup_expr in fields[f]: |
76 |
| - filter_ = filter_for_field(field, f, lookup_expr) |
77 |
| - |
78 |
| - if filter_: |
79 |
| - filter_name = LOOKUP_SEP.join([f, lookup_expr]) |
80 |
| - |
81 |
| - # Don't add "exact" to filter names |
82 |
| - _exact = LOOKUP_SEP + 'exact' |
83 |
| - if filter_name.endswith(_exact): |
84 |
| - filter_name = filter_name[:-len(_exact)] |
85 |
| - |
86 |
| - field_dict[filter_name] = filter_ |
87 |
| - # If fields is a list, it contains strings. |
88 |
| - else: |
89 |
| - filter_ = filter_for_field(field, f) |
90 |
| - if filter_: |
91 |
| - field_dict[f] = filter_ |
92 |
| - return field_dict |
| 31 | + # This also works with transformed exact lookups, such as 'date__exact' |
| 32 | + _exact = LOOKUP_SEP + 'exact' |
| 33 | + if filter_name.endswith(_exact): |
| 34 | + filter_name = filter_name[:-len(_exact)] |
| 35 | + |
| 36 | + return filter_name |
93 | 37 |
|
94 | 38 |
|
95 | 39 | def get_full_clean_override(together):
|
@@ -134,36 +78,36 @@ def __init__(self, options=None):
|
134 | 78 |
|
135 | 79 | class FilterSetMetaclass(type):
|
136 | 80 | def __new__(cls, name, bases, attrs):
|
137 |
| - try: |
138 |
| - parents = [b for b in bases if issubclass(b, FilterSet)] |
139 |
| - except NameError: |
140 |
| - # We are defining FilterSet itself here |
141 |
| - parents = None |
142 |
| - declared_filters = get_declared_filters(bases, attrs, False) |
143 |
| - new_class = super( |
144 |
| - FilterSetMetaclass, cls).__new__(cls, name, bases, attrs) |
145 |
| - |
146 |
| - if not parents: |
147 |
| - return new_class |
148 |
| - |
149 |
| - opts = new_class._meta = FilterSetOptions( |
150 |
| - getattr(new_class, 'Meta', None)) |
151 |
| - |
152 |
| - if opts.model and (opts.fields is not None or opts.exclude is not None): |
153 |
| - filters = new_class.filters_for_model(opts.model, opts) |
154 |
| - filters.update(declared_filters) |
155 |
| - else: |
156 |
| - filters = declared_filters |
| 81 | + attrs['declared_filters'] = cls.get_declared_filters(bases, attrs) |
157 | 82 |
|
158 |
| - not_defined = next((k for k, v in filters.items() if v is None), False) |
159 |
| - if not_defined: |
160 |
| - raise TypeError("Meta.fields contains a field that isn't defined " |
161 |
| - "on this FilterSet: {}".format(not_defined)) |
| 83 | + new_class = super(FilterSetMetaclass, cls).__new__(cls, name, bases, attrs) |
| 84 | + new_class._meta = FilterSetOptions(getattr(new_class, 'Meta', None)) |
| 85 | + new_class.base_filters = new_class.get_filters() |
162 | 86 |
|
163 |
| - new_class.declared_filters = declared_filters |
164 |
| - new_class.base_filters = filters |
165 | 87 | return new_class
|
166 | 88 |
|
| 89 | + @classmethod |
| 90 | + def get_declared_filters(cls, bases, attrs): |
| 91 | + filters = [ |
| 92 | + (filter_name, attrs.pop(filter_name)) |
| 93 | + for filter_name, obj in list(attrs.items()) |
| 94 | + if isinstance(obj, Filter) |
| 95 | + ] |
| 96 | + |
| 97 | + # Default the `filter.name` to the attribute name on the filterset |
| 98 | + for filter_name, f in filters: |
| 99 | + if getattr(f, 'name', None) is None: |
| 100 | + f.name = filter_name |
| 101 | + |
| 102 | + filters.sort(key=lambda x: x[1].creation_counter) |
| 103 | + |
| 104 | + # merge declared filters from base classes |
| 105 | + for base in reversed(bases): |
| 106 | + if hasattr(base, 'declared_filters'): |
| 107 | + filters = list(base.declared_filters.items()) + filters |
| 108 | + |
| 109 | + return OrderedDict(filters) |
| 110 | + |
167 | 111 |
|
168 | 112 | FILTER_FOR_DBFIELD_DEFAULTS = {
|
169 | 113 | models.AutoField: {'filter_class': NumberFilter},
|
@@ -235,12 +179,10 @@ def __init__(self, data=None, queryset=None, prefix=None, strict=None, request=N
|
235 | 179 | self.request = request
|
236 | 180 |
|
237 | 181 | self.filters = copy.deepcopy(self.base_filters)
|
238 |
| - # propagate the model being used through the filters |
| 182 | + |
239 | 183 | for filter_ in self.filters.values():
|
| 184 | + # propagate the model and filterset to the filters |
240 | 185 | filter_.model = self._meta.model
|
241 |
| - |
242 |
| - # Apply the parent to the filters, this will allow the filters to access the filterset |
243 |
| - for filter_key, filter_ in six.iteritems(self.filters): |
244 | 186 | filter_.parent = self
|
245 | 187 |
|
246 | 188 | @property
|
@@ -288,12 +230,88 @@ def form(self):
|
288 | 230 | return self._form
|
289 | 231 |
|
290 | 232 | @classmethod
|
291 |
| - def filters_for_model(cls, model, opts): |
292 |
| - return filters_for_model( |
293 |
| - model, opts.fields, opts.exclude, |
294 |
| - cls.filter_for_field, |
295 |
| - cls.filter_for_reverse_field |
296 |
| - ) |
| 233 | + def get_fields(cls): |
| 234 | + """ |
| 235 | + Resolve the 'fields' argument that should be used for generating filters on the |
| 236 | + filterset. This is 'Meta.fields' sans the fields in 'Meta.exclude'. |
| 237 | + """ |
| 238 | + model = cls._meta.model |
| 239 | + fields = cls._meta.fields |
| 240 | + exclude = cls._meta.exclude |
| 241 | + |
| 242 | + assert not (fields is None and exclude is None), \ |
| 243 | + "Setting 'Meta.model' without either 'Meta.fields' or 'Meta.exclude' " \ |
| 244 | + "has been deprecated since 0.15.0 and is now disallowed. Add an explicit" \ |
| 245 | + "'Meta.fields' or 'Meta.exclude' to the %s class." % cls.__name__ |
| 246 | + |
| 247 | + # Setting exclude with no fields implies all other fields. |
| 248 | + if exclude is not None and fields is None: |
| 249 | + fields = ALL_FIELDS |
| 250 | + |
| 251 | + # Resolve ALL_FIELDS into all fields for the filterset's model. |
| 252 | + if fields == ALL_FIELDS: |
| 253 | + fields = get_all_model_fields(model) |
| 254 | + |
| 255 | + # Remove excluded fields |
| 256 | + exclude = exclude or [] |
| 257 | + if not isinstance(fields, dict): |
| 258 | + fields = [(f, ['exact']) for f in fields if f not in exclude] |
| 259 | + else: |
| 260 | + fields = [(f, lookups) for f, lookups in fields.items() if f not in exclude] |
| 261 | + |
| 262 | + return OrderedDict(fields) |
| 263 | + |
| 264 | + @classmethod |
| 265 | + def get_filters(cls): |
| 266 | + """ |
| 267 | + Get all filters for the filterset. This is the combination of declared and |
| 268 | + generated filters. |
| 269 | + """ |
| 270 | + |
| 271 | + # No model specified - skip filter generation |
| 272 | + if not cls._meta.model: |
| 273 | + return cls.declared_filters.copy() |
| 274 | + |
| 275 | + # Determine the filters that should be included on the filterset. |
| 276 | + filters = OrderedDict() |
| 277 | + fields = cls.get_fields() |
| 278 | + undefined = [] |
| 279 | + |
| 280 | + for field_name, lookups in fields.items(): |
| 281 | + field = get_model_field(cls._meta.model, field_name) |
| 282 | + |
| 283 | + # warn if the field doesn't exist. |
| 284 | + if field is None: |
| 285 | + undefined.append(field_name) |
| 286 | + |
| 287 | + # ForeignObjectRel does not support non-exact lookups |
| 288 | + if isinstance(field, ForeignObjectRel): |
| 289 | + filters[field_name] = cls.filter_for_reverse_field(field, field_name) |
| 290 | + continue |
| 291 | + |
| 292 | + for lookup_expr in lookups: |
| 293 | + filter_name = get_filter_name(field_name, lookup_expr) |
| 294 | + |
| 295 | + # If the filter is explicitly declared on the class, skip generation |
| 296 | + if filter_name in cls.declared_filters: |
| 297 | + filters[filter_name] = cls.declared_filters[filter_name] |
| 298 | + continue |
| 299 | + |
| 300 | + if field is not None: |
| 301 | + filters[filter_name] = cls.filter_for_field(field, field_name, lookup_expr) |
| 302 | + |
| 303 | + # filter out declared filters |
| 304 | + undefined = [f for f in undefined if f not in cls.declared_filters] |
| 305 | + if undefined: |
| 306 | + raise TypeError( |
| 307 | + "'Meta.fields' contains fields that are not defined on this FilterSet: " |
| 308 | + "%s" % ', '.join(undefined) |
| 309 | + ) |
| 310 | + |
| 311 | + # Add in declared filters. This is necessary since we don't enforce adding |
| 312 | + # declared filters to the 'Meta.fields' option |
| 313 | + filters.update(cls.declared_filters) |
| 314 | + return filters |
297 | 315 |
|
298 | 316 | @classmethod
|
299 | 317 | def filter_for_field(cls, f, name, lookup_expr='exact'):
|
|
0 commit comments