Repository: encode/django-rest-framework

Issue:
Add patch interface to the Browsable API
Currently it seems only post/put/get is supported.

Expected behaviour: same form like in post/put should appear, perhaps not pre-filled



Discussion:
Closing as superseeded by #591.


Resolving patch:
diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py
--- a/rest_framework/renderers.py
+++ b/rest_framework/renderers.py
@@ -345,12 +345,11 @@ def get_form(self, view, method, request):
         if not self.show_form_for_method(view, method, request, obj):
             return
 
-        if method == 'DELETE' or method == 'OPTIONS':
+        if method in ('DELETE', 'OPTIONS'):
             return True  # Don't actually need to return a form
 
         if not getattr(view, 'get_serializer', None) or not parsers.FormParser in view.parser_classes:
-            media_types = [parser.media_type for parser in view.parser_classes]
-            return self.get_generic_content_form(media_types)
+            return
 
         serializer = view.get_serializer(instance=obj)
         fields = self.serializer_to_form_fields(serializer)
@@ -362,7 +361,7 @@ def get_form(self, view, method, request):
         form_instance = OnTheFlyForm(data)
         return form_instance
 
-    def get_generic_content_form(self, media_types):
+    def get_raw_data_form(self, view, method, request, media_types):
         """
         Returns a form that allows for arbitrary content types to be tunneled
         via standard HTML forms.
@@ -375,6 +374,11 @@ def get_generic_content_form(self, media_types):
                 and api_settings.FORM_CONTENTTYPE_OVERRIDE):
             return None
 
+        # Check permissions
+        obj = getattr(view, 'object', None)
+        if not self.show_form_for_method(view, method, request, obj):
+            return
+
         content_type_field = api_settings.FORM_CONTENTTYPE_OVERRIDE
         content_field = api_settings.FORM_CONTENT_OVERRIDE
         choices = [(media_type, media_type) for media_type in media_types]
@@ -386,7 +390,7 @@ def __init__(self):
                 super(GenericContentForm, self).__init__()
 
                 self.fields[content_type_field] = forms.ChoiceField(
-                    label='Content Type',
+                    label='Media type',
                     choices=choices,
                     initial=initial
                 )
@@ -422,15 +426,22 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
         view = renderer_context['view']
         request = renderer_context['request']
         response = renderer_context['response']
+        media_types = [parser.media_type for parser in view.parser_classes]
 
         renderer = self.get_default_renderer(view)
         content = self.get_content(renderer, data, accepted_media_type, renderer_context)
 
         put_form = self.get_form(view, 'PUT', request)
         post_form = self.get_form(view, 'POST', request)
+        patch_form = self.get_form(view, 'PATCH', request)
         delete_form = self.get_form(view, 'DELETE', request)
         options_form = self.get_form(view, 'OPTIONS', request)
 
+        raw_data_put_form = self.get_raw_data_form(view, 'PUT', request, media_types)
+        raw_data_post_form = self.get_raw_data_form(view, 'POST', request, media_types)
+        raw_data_patch_form = self.get_raw_data_form(view, 'PATCH', request, media_types)
+        raw_data_put_or_patch_form = raw_data_put_form or raw_data_patch_form
+
         name = self.get_name(view)
         description = self.get_description(view)
         breadcrumb_list = get_breadcrumbs(request.path)
@@ -447,10 +458,18 @@ def render(self, data, accepted_media_type=None, renderer_context=None):
             'breadcrumblist': breadcrumb_list,
             'allowed_methods': view.allowed_methods,
             'available_formats': [renderer.format for renderer in view.renderer_classes],
+
             'put_form': put_form,
             'post_form': post_form,
+            'patch_form': patch_form,
             'delete_form': delete_form,
             'options_form': options_form,
+
+            'raw_data_put_form': raw_data_put_form,
+            'raw_data_post_form': raw_data_post_form,
+            'raw_data_patch_form': raw_data_patch_form,
+            'raw_data_put_or_patch_form': raw_data_put_or_patch_form,
+
             'api_settings': api_settings
         })
 


Test patch:
diff --git a/rest_framework/tests/renderers.py b/rest_framework/tests/renderers.py
--- a/rest_framework/tests/renderers.py
+++ b/rest_framework/tests/renderers.py
@@ -112,6 +112,9 @@ def post(self, request):
     def put(self, request):
         return Response()
 
+    def patch(self, request):
+        return Response()
+
 
 class DocumentingRendererTests(TestCase):
     def test_only_permitted_forms_are_displayed(self):
@@ -120,6 +123,7 @@ def test_only_permitted_forms_are_displayed(self):
         response = view(request).render()
         self.assertNotContains(response, '>POST<')
         self.assertContains(response, '>PUT<')
+        self.assertContains(response, '>PATCH<')
 
 
 class RendererEndToEndTests(TestCase):
diff --git a/rest_framework/tests/utils.py b/rest_framework/tests/utils.py
--- a/rest_framework/tests/utils.py
+++ b/rest_framework/tests/utils.py
@@ -1,10 +1,10 @@
 from __future__ import unicode_literals
-from django.test.client import RequestFactory, FakePayload
+from django.test.client import FakePayload, Client as _Client, RequestFactory as _RequestFactory
 from django.test.client import MULTIPART_CONTENT
 from rest_framework.compat import urlparse
 
 
-class RequestFactory(RequestFactory):
+class RequestFactory(_RequestFactory):
 
     def __init__(self, **defaults):
         super(RequestFactory, self).__init__(**defaults)
@@ -26,3 +26,15 @@ def patch(self, path, data={}, content_type=MULTIPART_CONTENT,
         }
         r.update(extra)
         return self.request(**r)
+
+
+class Client(_Client, RequestFactory):
+    def patch(self, path, data={}, content_type=MULTIPART_CONTENT,
+              follow=False, **extra):
+        """
+        Send a resource to the server using PATCH.
+        """
+        response = super(Client, self).patch(path, data=data, content_type=content_type, **extra)
+        if follow:
+            response = self._handle_redirects(response, **extra)
+        return response