Repository: encode/django-rest-framework

Issue:
Reload for `prefetch_related` updates may fail if object is filtered out.
In the awkward case where `prefetch_related` has been applied to an update operation, we are required to refresh the instance from the database (see #4553)

The current implementation re-calls `get_object()`. We should instead call `refresh_from_db` directly on the model instance. This means that we don't have a potential case of `get_object` returning `None` because the update itself has effectively removed the object from the base queryset causing the lookup to fail.


Discussion:

Resolving patch:
diff --git a/rest_framework/mixins.py b/rest_framework/mixins.py
--- a/rest_framework/mixins.py
+++ b/rest_framework/mixins.py
@@ -71,9 +71,8 @@ def update(self, request, *args, **kwargs):
 
         if getattr(instance, '_prefetched_objects_cache', None):
             # If 'prefetch_related' has been applied to a queryset, we need to
-            # refresh the instance from the database.
-            instance = self.get_object()
-            serializer = self.get_serializer(instance)
+            # forcibly invalidate the prefetch cache on the instance.
+            instance._prefetched_objects_cache = {}
 
         return Response(serializer.data)
 


Test patch:
diff --git a/docs/api-guide/testing.md b/docs/api-guide/testing.md
--- a/docs/api-guide/testing.md
+++ b/docs/api-guide/testing.md
@@ -187,7 +187,12 @@ As usual CSRF validation will only apply to any session authenticated views.  Th
 # RequestsClient
 
 REST framework also includes a client for interacting with your application
-using the popular Python library, `requests`.
+using the popular Python library, `requests`. This may be useful if:
+
+* You are expecting to interface with the API primarily from another Python service,
+and want to test the service at the same level as the client will see.
+* You want to write tests in such a way that they can also be run against a staging or
+live environment. (See "Live tests" below.)
 
 This exposes exactly the same interface as if you were using a requests session
 directly.
@@ -198,6 +203,10 @@ directly.
 
 Note that the requests client requires you to pass fully qualified URLs.
 
+## `RequestsClient` and working with the database
+
+The `RequestsClient` class is useful if
+
 ## Headers & Authentication
 
 Custom headers and authentication credentials can be provided in the same way
diff --git a/tests/test_prefetch_related.py b/tests/test_prefetch_related.py
--- a/tests/test_prefetch_related.py
+++ b/tests/test_prefetch_related.py
@@ -14,7 +14,7 @@ class Meta:
 
 
 class UserUpdate(generics.UpdateAPIView):
-    queryset = User.objects.all().prefetch_related('groups')
+    queryset = User.objects.exclude(username='exclude').prefetch_related('groups')
     serializer_class = UserSerializer
 
 
@@ -39,3 +39,21 @@ def test_prefetch_related_updates(self):
             'email': 'tom@example.com'
         }
         assert response.data == expected
+
+    def test_prefetch_related_excluding_instance_from_original_queryset(self):
+        """
+        Regression test for https://github.com/tomchristie/django-rest-framework/issues/4661
+        """
+        view = UserUpdate.as_view()
+        pk = self.user.pk
+        groups_pk = self.groups[0].pk
+        request = factory.put('/', {'username': 'exclude', 'groups': [groups_pk]}, format='json')
+        response = view(request, pk=pk)
+        assert User.objects.get(pk=pk).groups.count() == 1
+        expected = {
+            'id': pk,
+            'username': 'exclude',
+            'groups': [1],
+            'email': 'tom@example.com'
+        }
+        assert response.data == expected