Closed rpkilby closed 4 years ago
OK. Nice.
Can I ask, How would it look if we had a GroupFilter class that took Filters and allowed implementing filter() plus validate()?
I.e. just pulling what you have here into a composed class, rather than having the API directly on FilterSet.
That was actually my first thought - leverage the declarative filters. And I've kind of gone back and forth on both implementations after writing several lengthy paragraphs on the matter. Changed my opinion mid-response.. 😅
At this point, I see two proposals:
The below:
# Note that FilterGroup is not a Filter subclass
class MyFilterGroup(FilterGroup):
def filter(self, qs, **params):
...
def validate(self, form, **params):
...
class MyFilter(FilterSet):
field_a = CharFilter(...)
field_b = CharFilter(...)
group = MyFilterGroup(
filters=['field_a', 'field_b'],
)
At this point, I'm leaning towards option 2. Option 1 is still attractive in that the code is simple - it's not necessary to write custom classes. That said, there isn't any abstraction for reusability. We could write helper functions, but that's about it. Option 2 would allow us/users to write reusable FilterGroup
s. Right now, the only thing I don't like about the declarative approach is that they don't make sense as a declared attribute on the FilterSet
. With Filter
s, the attribute name is the exposed query param. With FilterGroup
s, the attribute name is functionless. e.g., a user wouldn't ever include ?group=foo
in their query string.
Maybe a third option is to mix the styles? Use FilterGroup
s in the meta options? e.g.,
# Note that FilterGroup is not a Filter subclass
class MyFilter(FilterSet):
field_a = CharFilter(...)
field_b = CharFilter(...)
field_c = CharFilter(...)
field_d = CharFilter(...)
class Meta:
model = MyModel
fields = ['field_a', 'field_b', 'field_c', 'field_d']
groups = [
ExclusiveGroup(filters=['field_a', 'field_b']),
RequiredGroup(filters=['field_c', 'field_d']),
]
How does that last option sound?
@carltongibson - I'm going to go ahead and WIP up that last example.
OK. I haven't come to a conclusion. (🎯: "Nice API") 👍
btw, I'm still working on this. I've just caught up on some unrelated Circle CI work, which is now finished. I hope to have this ready either this weekend or the next.
ach, I'm eventually going to finish this up. It's just taken a back seat to some work on DRF and djangorestframework-filters (still trying to get 1.0 out the door... it's only been 3+ years in the making!). Also, the PR should maybe address iss 1020.
Took me a year, but it's just about ready lol.
@carltongibson - just jotting down some preliminary thoughts. Do you have any feedback?
Context:
Users periodically request how to validate and filter against multiple params. The goto advice has been to implement something based on
MultiWidget
, however there are three main deficiencies in my mind:MultiWidget
,MultiValueField
, andFilter
.MultiWidget
/MultiValueField
API is somewhat confusing (maybe I just didn't grok it for some reason, but I remember struggling with it in the past)Proposal:
Add new API surface around multi-param filtering. This consists of two things:
Meta.multi
orMeta.multiple
option that maps group labels (?) to a list of filter names.validate_multi_*
andfilter_multi_*
, which correspond to themulti
labels.In practice this would look like:
The above hooks are purely opt-in.
validate_multi_<group name>
is defined, it will be called during the validation phase. Note that individual filter validation is still performed, and the validated values are passed to the method.filter_multi_<group name>
is defined, it will be called during the filter phase in lieu of the individual.filter()
methods.Notes:
FilterSet
is just calling hook methods at the appropriate time. One possible complication is validation, which will require adding a.clean()
method to the form class creation.. it should be fine...FilterSet
API, instead of mucking around with forms.The above is flexible - users can assert any arbitrary validation rules (e.g., fields must be all present together, they must be exclusive, some fields are required in some cases, etc...).
Meta.together
option (require all values are valid/present).Meta.exclusive
option (see#784
).Example for more complex filtering (the
MuliWidget
use case).