|
20 | 20 | from .utils import try_dbfield, get_all_model_fields, get_model_field, resolve_field
|
21 | 21 |
|
22 | 22 |
|
23 |
| -def filters_for_model(model, fields=None, exclude=None, filter_for_field=None, |
24 |
| - filter_for_reverse_field=None): |
25 |
| - field_dict = OrderedDict() |
26 |
| - |
27 |
| - # Setting exclude with no fields implies all other fields. |
28 |
| - if exclude is not None and fields is None: |
29 |
| - fields = ALL_FIELDS |
30 |
| - |
31 |
| - # All implies all db fields associated with a filter_class. |
32 |
| - if fields == ALL_FIELDS: |
33 |
| - fields = get_all_model_fields(model) |
34 |
| - |
35 |
| - # Loop through the list of fields. |
36 |
| - for f in fields: |
37 |
| - # Skip the field if excluded. |
38 |
| - if exclude is not None and f in exclude: |
39 |
| - continue |
40 |
| - field = get_model_field(model, f) |
41 |
| - # Do nothing if the field doesn't exist. |
42 |
| - if field is None: |
43 |
| - field_dict[f] = None |
44 |
| - continue |
45 |
| - if isinstance(field, ForeignObjectRel): |
46 |
| - filter_ = filter_for_reverse_field(field, f) |
47 |
| - if filter_: |
48 |
| - field_dict[f] = filter_ |
49 |
| - # If fields is a dictionary, it must contain lists. |
50 |
| - elif isinstance(fields, dict): |
51 |
| - # Create a filter for each lookup type. |
52 |
| - for lookup_expr in fields[f]: |
53 |
| - filter_ = filter_for_field(field, f, lookup_expr) |
54 |
| - |
55 |
| - if filter_: |
56 |
| - filter_name = LOOKUP_SEP.join([f, lookup_expr]) |
57 |
| - |
58 |
| - # Don't add "exact" to filter names |
59 |
| - _exact = LOOKUP_SEP + 'exact' |
60 |
| - if filter_name.endswith(_exact): |
61 |
| - filter_name = filter_name[:-len(_exact)] |
62 |
| - |
63 |
| - field_dict[filter_name] = filter_ |
64 |
| - # If fields is a list, it contains strings. |
65 |
| - else: |
66 |
| - filter_ = filter_for_field(field, f) |
67 |
| - if filter_: |
68 |
| - field_dict[f] = filter_ |
69 |
| - return field_dict |
| 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]) |
| 30 | + |
| 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 |
70 | 37 |
|
71 | 38 |
|
72 | 39 | def get_full_clean_override(together):
|
@@ -111,34 +78,12 @@ def __init__(self, options=None):
|
111 | 78 |
|
112 | 79 | class FilterSetMetaclass(type):
|
113 | 80 | def __new__(cls, name, bases, attrs):
|
114 |
| - try: |
115 |
| - parents = [b for b in bases if issubclass(b, FilterSet)] |
116 |
| - except NameError: |
117 |
| - # We are defining FilterSet itself here |
118 |
| - parents = None |
119 |
| - declared_filters = cls.get_declared_filters(bases, attrs) |
120 |
| - new_class = super( |
121 |
| - FilterSetMetaclass, cls).__new__(cls, name, bases, attrs) |
122 |
| - |
123 |
| - if not parents: |
124 |
| - return new_class |
125 |
| - |
126 |
| - opts = new_class._meta = FilterSetOptions( |
127 |
| - getattr(new_class, 'Meta', None)) |
128 |
| - |
129 |
| - if opts.model and (opts.fields is not None or opts.exclude is not None): |
130 |
| - filters = new_class.filters_for_model(opts.model, opts) |
131 |
| - filters.update(declared_filters) |
132 |
| - else: |
133 |
| - filters = declared_filters |
| 81 | + attrs['declared_filters'] = cls.get_declared_filters(bases, attrs) |
134 | 82 |
|
135 |
| - not_defined = next((k for k, v in filters.items() if v is None), False) |
136 |
| - if not_defined: |
137 |
| - raise TypeError("Meta.fields contains a field that isn't defined " |
138 |
| - "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() |
139 | 86 |
|
140 |
| - new_class.declared_filters = declared_filters |
141 |
| - new_class.base_filters = filters |
142 | 87 | return new_class
|
143 | 88 |
|
144 | 89 | @classmethod
|
@@ -285,12 +230,88 @@ def form(self):
|
285 | 230 | return self._form
|
286 | 231 |
|
287 | 232 | @classmethod
|
288 |
| - def filters_for_model(cls, model, opts): |
289 |
| - return filters_for_model( |
290 |
| - model, opts.fields, opts.exclude, |
291 |
| - cls.filter_for_field, |
292 |
| - cls.filter_for_reverse_field |
293 |
| - ) |
| 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 |
294 | 315 |
|
295 | 316 | @classmethod
|
296 | 317 | def filter_for_field(cls, f, name, lookup_expr='exact'):
|
|
0 commit comments