-
-
Notifications
You must be signed in to change notification settings - Fork 87
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add optional support for django filters #72
Conversation
q objects have value semantics, see https://docs.djangoproject.com/en/1.7/topics/db/queries/#complex-lookups-with-q-objects: "When an operator is used on two Q objects, it yields a new Q object."
I don't think we'll use the workflow differently.
(only reported on python 2.7 for some reason)
Since rest_framework.backends.DjangoFilterBackend inherits from DatatablesFilterBackend we get all the functionality without any code. This tests will break as soon as we override filter_queryset or inherit from a different class.
reimplemented based on DatatablesBaseFilterBackend
Python 2 is frozen. Last release will be Python 2.7.18 in April 2020 (And yes, I'm having trouble supporting python 2 in this new code.)
So I can combine it with multiple versions of other stuff.
(oops... I was using pytest for so long that I didn't notice)
django-filter makes these things easy
I'm not sure if this is good... I just stumbled about sorting being broken in my app, because I wasn't aware that the ordering is only dependant on the name attribute.
I've managed to use the django filters based implementation as a drop-in replacement[1] for django-rest-framework-datatables (prior to this PR) but right now it requires that:
Only then will the combination of column-search and global search work correctly. This is a bit more restrictive (and repetitive) than before, which is certainly unintended. I'm not sure what's the best way to resolve this. It does work, however ;) [1] By this I mean that |
About that coverage decrease:
This is only because there's no ImportError any more, as django.utils.six is always imported. Maybe that's because I removed Python 2? |
As an appetizer (see #26) , this is an example of a (working) filter for a numerical range from https://github.com/vedmack/yadcf/ class YADCFRangeWidget(widgets.RangeWidget):
def value_from_datadict(self, data, files, name):
if name not in data:
return None
vals = data[name].split('-yadcf_delim-')
new_data = data.copy()
del new_data[name]
for val, suffix in zip(vals, self.suffixes):
new_data[self.suffixed(name, suffix)] = val
return super().value_from_datadict(new_data, files, name)
class YADCFRangeField(fields.RangeField):
widget = YADCFRangeWidget
class YADCFRangeFilter(filters.RangeFilter):
field_class = YADCFRangeField
class MyFilter(DatatablesFilterSet):
range = YADCFRangeFilter() (I wasn't sure if it's ok to modify data, otherwise This will generate queries like |
Also filtering datetime ranges works with very little code: class YADCFDateTimeRangeWidget(YADCFRangeWidget):
suffixes = ['after', 'before']
class YADCFIsoDateTimeRangeField(fields.IsoDateTimeRangeField):
widget = YADCFDateTimeRangeWidget
class YADCFIsoDateTimeFromToRangeFilter(filters.IsoDateTimeFromToRangeFilter):
field_class = YADCFIsoDateTimeRangeField (preferably with eonasdan-bootstrap-datetimepicker ... not sure if others will work) |
Well, we're cheating a bit, since the filterset still calls the classmethod replace_last_lookup directly.
This completes fixing a small design issue I encountered while adopting my code to this new API.
Ok... I've noticed a problem in my code and fixed it above. The problem is that if I want to use a different lookup, e.g. 'in' for efficiency reasons, I need to override it in my filter, because the filterset will blatantly override everything with 'iregex' if Part of the problem is that YADCF will always set search_regex e.g. on a multiselect filter. So the responsibility to switch lookups must be on the filter class, not the filterset directly. Now if you want to switch your lookups, you need to mixin (This means you could switch between arbitrary lookup types by toggling the regex feature on the datatables column.) I guess I need to document this. I'll do that after some more testing. |
The limitation that the 'data' attribute needs to be set to the exact lookup (e.g. The GlobalFilter mixin could be a little smarter and look at Filter-Object for the correct way to construct a global lookup. |
The problem was that I checked the 'field_name' attribute, which is the name of the actual field on the model, instead of the name of the filter. This was forcing me to pick the names of the filters identical to the field names, including full lookups, otherwise e.g. the regex flag would not propagate.
This is what we need to get to the filter field, the name attribute is useless for us, as the filter already defines the lookup. Also the 'name' is meant to be just an identifier on the Datatable and we don't need to add any magic property to it with a django-filter based backend. That's what the filters are for, really. See: - https://datatables.net/reference/option/columns.data - https://datatables.net/reference/option/columns.name - https://datatables.net/manual/server-side
The column search was dispatched on the filter name, but the global search wasn't yet... :-S
By now I've fixed all three of those issues. If you know django-filters, the filters will work as expected without any special magic and my code will infer the correct ordering based on the filter configuration. This follows the principle of least astonishment if you come from django-filters, however it behaves quite differently compared with django-rest-framework-datatables without django-filter support. I've documented the differences in "Differences and limitations". This feels like the correct design choice, since filter configuration via django-filters is way more flexible and powerful and it also means that django-rest-framework-datatables will behave more like vanilla Datatables (without any special magic behaviour of column
|
@TauPan Really good work, I will merge this soon, but I want to do a bugfix release before that. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will fix the conflicts and merge manually.
We'll improve doc examples later. Thank @TauPan !
serializer_class = AlbumSerializer | ||
filter_backends = (DatatablesFilterBackend,) | ||
filterset_class = AlbumGlobalFilter | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be great to add a HTML/JS example here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please do it?
I'm a bit lost on how to do this. :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see #79
@TauPan : I've fixed the conflicts and one issue (with order_by), all tests pass ! You PR is now merged, but for some reason (I did the merge locally), your commits do not appear in the commit log, really sorry about that ! |
Good news! :) Can you elaborate on which issue you did fix with order_by? Is there a testcase for that? Since you put everything into one large commit (understandable, since you had to rebase), it's a bit hard to see what changed in comparison with my branch.
That's no issue at all :) Thanks for merging! |
The queryset was always ordered, even when
Not a big deal :) Thanks again. |
While trying to use yadcf (https://github.com/vedmack/yadcf) for (date) range filters and multiselect-dropdowns I noticed in the resulting prototype that the queries where too slow for our database.
This is because icontains or iregex is always used, regardless of the type of field.
Instead of trying to make a specific backend for our application, I decided it would be worthwhile to try to integrate django-filters into django-rest-framework-datatables.
This is a complex task and it took me three (calendar) months to get to this point.
See https://github.com/TauPan/django-rest-framework-datatables/blob/add-django-filters/docs/django-filters.rst for documentation.
(It looks to me like the task is more or less finished, but I'm still integrating it into a project, so more fixes and modifications might come up.)
Notable points:
I did not modify the example app or create an example app for the django-filter integration. I did so in my first prototype, but it's not very useful as a demonstration, since the real point of using django-filters if you need to optimize things. And the dataset for the example app is very small, so there's no need for optimization here.
I had to drop python 2.7 support, since django-filter 2.2.0 requires python 3. See Planned timeline for python 2 support? #71
I had to refactor the main backend in order to keep things DRY.
The api and behaviour is a bit different than plain django-rest-framework-datatables. This is covered in the documentation under "Differences and Limitations".
I understand you don't have a lot of time to review things at the moment, so I'll just integrate this code into our django application and see how far I get and if there are problems or new use-cases, I'll update this pull request.