-
-
Notifications
You must be signed in to change notification settings - Fork 6.9k
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
Doc/test SearchFilter m2m behavior #7094
Conversation
I usually try to run tests via tox. In order to get this to work, I had to unpin the dependencies in
What I ended up doing was copying the entire setup for
Contents of
|
I followed your approach and created a separate file with the test introduced for #5264, which fails on 3.6.3 (as expected) and passes on 3.6.4 (as expected), plus my test rewritten to explicitly fetch Entry objects (renamed to EntryRecord also due to name collision) rather than going through blog posts, and that passes on 3.6.3 (finding 1 entry, as expected) but fails on 3.6.4 (finding 0 entries, which was the problem I ran into in our own code base as well). I have updated the PR accordingly. |
Test result for 3.6.3: single expected failure, for the case that #5264 was intended to fix, but an expected pass for manytomany search: tests/test_filters_multiple.py F.. [100%]
================================================================================================= FAILURES ==================================================================================================
__________________________________________________________________________ SearchFilterToManyTests.test_multiple_filter_conditions __________________________________________________________________________
tests/test_filters_multiple.py:94: in test_multiple_filter_conditions
assert len(response.data) == 1
E AssertionError: assert 2 == 1
E + where 2 = len([OrderedDict([(u'id', 1), ('name', u'Post 1')]), OrderedDict([(u'id', 2), ('name', u'Post 2')])])
E + where [OrderedDict([(u'id', 1), ('name', u'Post 1')]), OrderedDict([(u'id', 2), ('name', u'Post 2')])] = <Response status_code=200, "text/html; charset=utf-8">.data
---------- coverage: platform darwin, python 2.7.16-final-0 ----------
Coverage XML written to file coverage.xml
============================================================================ 1 failed, 2 passed, 1056 deselected in 4.65 seconds ============================================================================ Test result for 3.6.4: expected pass for the original #5264 fix, but unexpected failure for manytomany search: tests/test_filters_multiple.py .F. [100%]
================================================================================================= FAILURES ==================================================================================================
____________________________________________________________________ SearchFilterToManyTests.test_multiple_filter_conditions_manytomany _____________________________________________________________________
tests/test_filters_multiple.py:111: in test_multiple_filter_conditions_manytomany
assert len(response.data) == 1
E AssertionError: assert 0 == 1
E + where 0 = len([])
E + where [] = <Response status_code=200, "text/html; charset=utf-8">.data
---------- coverage: platform darwin, python 2.7.16-final-0 ----------
Coverage XML written to file coverage.xml
============================================================================ 1 failed, 2 passed, 1086 deselected in 4.73 seconds ============================================================================ This failure persists all the way up to current master. |
Thanks for putting this together. Looking into the issue, I don't think there's a bug. Rather, the previous behavior was incorrect, but gave you your desired result. Admittedly though, it is a little unintuitive for the m2m case. Ignoring the m2m component for a second, let's say For the same reason the m2m search is also failing. You're not asking for entries who have separate authors named either Alice or Bob, you're asking for entries whose author name contains both Alice and Bob. e.g., this would match an author named "Bobby Alice". This makes more sense if you think about searching against both name and age. For example So generally, more search terms increases specificity, not breadth. |
Then how would one go about matching things the intuitive way, based on the description from the documentation?
That does not cover how to deal with the ManyToMany case, which is pretty important to cover: |
The core issue is that there are multiple ways to query against a to-many relationship. No one way is always correct, but we have to figure out what makes sense for the general case. Let's use the Now, let's extend that across the Back to the Also, as best I can tell, Django admin's |
As to fixing this.. if you're using postgres, you should be able to annotate the concatenated set of author names with Entry.objects \
.annotate(author_names=StringAgg('authors__name') \
.filter(author_names__icontains='bob', author_names__icontains='alice') |
The problem here is that the docs you link to talk about specifically using (And of course, as you point out: getting results mixed in with entries that only match "lennon" or only match 1979, would be entirely wrong) So a strong clue for the fact that this was an unplanned change, and 3.6.3 was doing exactly what it should have been doing is that there's nothing in the changelog or release notes for 3.6.4 that this behaviour was intentionally changed, despite breaking backward compatiblity, which this test makes abundantly clear. On the other hand, if this breaking change was expected and desired then the documentation needs to be updated to explain this, and the release notes for 3.6.4 need to be ammended for anyone who will be doing an uplift across 3.6.3/3.6.4 because a ManyToMany relation is fundamentally different from a plain field or ForeignKey, and you can't have a patch version that breaks someone's API, with docs that describe what should happen while the underlying code does something else =) (I'd much rather see the original functionality restored, of course, because all evidence points to this being an unplanned but breaking change, so should absolutely be fixed, but I'll take better documentation that explains why using It might make sense to get an extra pair of eyes on this PR, because it's possible that the change was entirely unplanned, and in retrospect should have had this test to ensure that the ManyToMany behaviour didn't change without explicitly meaning to, without anyone discovering that until December 16, 20919, with separate work to change the behaviour with respect to ManyToMany relations, tied to its own issue number, with the associated docs updates. |
Both
Right, I was using a simplified description for expediency, as that's what the example query will typically reduce to. I acknowledge that the search query is more complex:
It's the same idea as your "John Lennon; the 1979 report" example.
The change in behavior was discussed in the original ticket (#4655 (comment) and #4655 (comment)), but my PR failed to mention it, so wasn't included in the release notes. Definitely an oversight, but the changes were expected.
I don't want to dive into the timeline, but you can look at the timing of comments and events on the original issue & PR to understand how it was added to a patch release, but this also boils down to an oversight. Either way you're correct, and we typically try to avoid adding breaking changes in patch releases. |
Ah, in that case, can the page with the 3.6.* release information still be edited to mention this, with ideally a bit of text that explains how to "fix" ones API when uplifting past 3.6.3, so future folks who get put on uplifting old API servers have a path forward? (And probably add a bit to the "search_fields with multiple terms" section of the DRF filtering docs that explains in detail what to expect with manytomany and why, with a link that explains how to do what one might intuitively expect to happen instead?) |
I just noticed that the 3.6.4 release notes do mention the behavior change, although it is terse.
We can expand the note, but ideally it should remain somewhat terse, and additional explanation can be added to the original PR description. Happy to review a PR on this.
Feel free to open a PR on this too. The subject is complicated though, and we probably don't want to insert a lengthy explanation. Probably more of a general warning of "m2m might not behave as you expect. Make sure you understand queries that span to-many relationships and test expectations etc.."
Again, I'd reiterate the point that our behavior matches the behavior of the Django admin (I should double check, but a quick look at the code indicates that this is correct). I'm also hesitant to recommend any solutions. The
|
I won't be opening a PR, because I clearly don't know enough about DRF here, nor its style of documentation, nor its conventions around what to include or exclude. All I can tell you is that as a user of DRF, the documentation over on https://www.django-rest-framework.org/community/release-notes/#36x-series needs something that mentions that 3.6.4 is a breaking update, and what that breaks. If that means an update to the PR comment of #5264 then I will leave it to you to summarize that, as you clearly know far more about this than I do. Or whoever wrote the original PR even. And it might even make sense to land this PR as well, but after changing the assert for manytomany to |
Thanks for the reply @Pomax. Reopening to track the issues:
|
Would it make sense to file that as a new issue? Then I can update this PR for you so that it passes based on current behaviour, and get checkbox #2 ticket effectively "for free" =) (with the added benefit of not using a PR as a tracking issue. PRs close when they get merged in ;) |
closing this PR in favour of #7351, so it stops showing up in my outstanding PR list. |
A change to rest_framework's filters.py, introduced in #5264 and pushed out as part of v3.6.4, appears to have changed how searching with multiple filter conditions works when the fields involved are
ManyToMany
relations rather than plain model fields orForeignKey
relations.This PR adds a failing test to test_filters.py that highlights a search involving
ManyToMany
.(Note that the single-tag tests are in the same file mostly to verify that it really is only multiple filter conditions that are affected)
I tried to verify this against the commit preceding #5264 (f02b7f1) but I was unable to get the tests to run succesfully, even without any new code - even with django pegged to 1.11 the result of
runtests --fast
is 54 errors, so I'm quite sure how to even check which historical versions of that various dependencies are necessary to make all the pre-existing tests pass.