-
-
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
List resource not updated between requests #2602
Conversation
After a comment by @tomchristie in IRC, I added back the |
@@ -562,7 +562,7 @@ def to_representation(self, data): | |||
""" | |||
# Dealing with nested relationships, data can be a Manager, | |||
# so, first get a queryset from the Manager if needed | |||
iterable = data.all() if isinstance(data, models.Manager) else data | |||
iterable = data.all() if hasattr(data, 'all') else data | |||
return [ |
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'm not sure this is the proper fix.
I wonder if we shouldn't simply replace models.Manager
by models.QuerySet
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.
Actually I think isinstance(data, (models.Manager, models.QuerySet))
may be what we want.
We do need it for manager (as it's not an iterable by itself) and we also need it for queryset (as they get cached).
Mind pasting the related view part ? This is the most important part that would explain why this possibly fails. |
Nice work on debugging this. |
Sure. This is the view: class ReportViewSet(viewsets.ModelViewSet):
queryset = models.Report.objects.all()
serializer_class = serializers.ReportSerializer
permission_classes = [permissions.AllowAny]
def _get_filtered_queryset(self):
"""
A queryset that filters by access permissions.
"""
if not self.request.user.is_superuser:
return self.queryset.filter(users=self.request.user)
return self.queryset
def list(self, request):
"""
Use the filtered queryset for this endpoint.
"""
with monkeypatch_attribute(self, 'get_queryset', self._get_filtered_queryset):
response = super(ReportViewSet, self).list(request)
return response And the serializer: class ReportSerializer(serializers.HyperlinkedModelSerializer):
name = serializers.ModelField(models.Report()._meta.get_field('name'), required=True)
users = serializers.HyperlinkedRelatedField(
many=True, read_only=True,
source='reportuser_set', view_name='reportuser-detail')
class Meta:
model = models.Report
fields = (
'id', 'uri', 'name', 'created',
'client_name', 'manager_name', 'manager_email',
'users', 'design',
'views',
)
read_only_fields = ('id', 'uri', 'users', 'created', 'design') |
|
@xordoquy there we go, thanks. So would this work? iterable = data.all() if isinstance(data, (models.Manager, models.QuerySet)) else data |
Doesn't really make sense though, does it? The goal of |
I'm not sure what you mean - that bit of code gets triggered for relationships where the attribute is a manager, eg a field name |
Looks right to me, yes. |
OK, commit was amended. Unfortunately I don't know where to put the test and how to implement it. And I really don't have time now and in the coming weeks. So I hope someone else could write a quick test for this :) |
|
Done. |
List resource not updated between requests
The bug here is in fact in the user code (because it's easy to get tripped up by Django's queryset caching when combined with setting a queryset class attribute) Compare this...
With REST framework's implementation...
Note that we always re-evaluate the queryset using
Then the problem would not manifest itself. Very easy to get tripped up by, and not obvious how we can protect against it. |
@tomchristie oh, thanks for that note. |
The Problem
This is the test I used:
So two independent APIClients are created. They both request
/reports
. In between the two requests, more Report instances are created. But they don't show up in the second response.Output:
The Bug
The issue does not occur in DRF 3.0.0, but all versions 3.0.1-3.0.5 are affected.
With
git bisect
I could track down the commit that introduced this bug: 8d6b0b1 The corresponding pull request is #2241.Resolution Attempts
First I simply tried to revert the commit onto 3.0.5... But that caused a conflict:
I'm not sure what the correct solution is, as I don't understand this code yet. I just know that the bug is gone if I replace the conflicting line with
iterable = data.all() if (hasattr(data, 'all')) else data
.CC @tomchristie @IvanAlegre