Code

Ticket #17449: patch17449.diff

File patch17449.diff, 5.3 KB (added by estebistec, 2 years ago)

Updated patch for improved HEAD, and default OPTIONS

Line 
1Index: docs/ref/request-response.txt
2===================================================================
3--- docs/ref/request-response.txt       (revision 17370)
4+++ docs/ref/request-response.txt       (working copy)
5@@ -761,6 +761,13 @@
6     Like :class:`HttpResponse`, but uses a 405 status code. Takes a single,
7     required argument: a list of permitted methods (e.g. ``['GET', 'POST']``).
8 
9+.. class:: HttpResponseOptions
10+
11+    Like :class:`HttpResponse`, but uses a 204 status code and provides
12+    an ``Allow`` response header. Takes a single, required argument: a list
13+    of permitted methods (e.g., ``['GET', 'POST']``) used to populate
14+    the ``Allow`` header.
15+
16 .. class:: HttpResponseGone
17 
18     Acts just like :class:`HttpResponse` but uses a 410 status code.
19Index: django/http/__init__.py
20===================================================================
21--- django/http/__init__.py     (revision 17370)
22+++ django/http/__init__.py     (working copy)
23@@ -720,6 +720,14 @@
24             raise Exception("This %s instance cannot tell its position" % self.__class__)
25         return sum([len(str(chunk)) for chunk in self._container])
26 
27+class HttpResponseOptions(HttpResponse):
28+    status_code = 204
29+
30+    def __init__(self, permitted_methods):
31+        super(HttpResponseOptions, self).__init__()
32+        self['Allow'] = ', '.join(permitted_methods)
33+        self['Content-Length'] = '0'
34+
35 class HttpResponseRedirect(HttpResponse):
36     status_code = 302
37 
38Index: django/views/generic/base.py
39===================================================================
40--- django/views/generic/base.py        (revision 17370)
41+++ django/views/generic/base.py        (working copy)
42@@ -43,6 +43,8 @@
43 
44         def view(request, *args, **kwargs):
45             self = cls(**initkwargs)
46+            if hasattr(self, 'get') and not hasattr(self, 'head'):
47+                self.head = self.get
48             return self.dispatch(request, *args, **kwargs)
49 
50         # take name and docstring from class
51@@ -67,7 +69,8 @@
52         return handler(request, *args, **kwargs)
53 
54     def http_method_not_allowed(self, request, *args, **kwargs):
55-        allowed_methods = [m for m in self.http_method_names if hasattr(self, m)]
56+        allowed_methods = [m.upper() for m in self.http_method_names
57+                if hasattr(self, m)]
58         logger.warning('Method Not Allowed (%s): %s' % (request.method, request.path),
59             extra={
60                 'status_code': 405,
61@@ -76,8 +79,10 @@
62         )
63         return http.HttpResponseNotAllowed(allowed_methods)
64 
65-    def head(self, *args, **kwargs):
66-        return self.get(*args, **kwargs)
67+    def options(self, request, *args, **kwargs):
68+        allowed_methods = [m.upper() for m in self.http_method_names
69+                if hasattr(self, m)]
70+        return http.HttpResponseOptions(allowed_methods)
71 
72 
73 class TemplateResponseMixin(object):
74Index: tests/regressiontests/generic_views/base.py
75===================================================================
76--- tests/regressiontests/generic_views/base.py (revision 17370)
77+++ tests/regressiontests/generic_views/base.py (working copy)
78@@ -55,6 +55,11 @@
79         return self
80 
81 
82+class PostOnlyView(View):
83+    def post(self, request):
84+        return HttpResponse('This view only accepts POST')
85+
86+
87 class ViewTest(unittest.TestCase):
88     rf = RequestFactory()
89 
90@@ -157,7 +162,58 @@
91         """
92         self.assertTrue(DecoratedDispatchView.as_view().is_decorated)
93 
94+    def test_head_no_get(self):
95+        """
96+        Test that a view class with no get responds to a HEAD request with HTTP
97+        405.
98+        """
99+        request = self.rf.head('/')
100+        view = PostOnlyView.as_view()
101+        self.assertEqual(405, view(request).status_code)
102 
103+    def test_options(self):
104+        """
105+        Test that views respond to HTTP OPTIONS requests with an Allow header
106+        appropriate for the methods implemented by the view class.
107+        """
108+        request = self.rf.options('/')
109+        view = SimpleView.as_view()
110+        response = view(request)
111+        self.assertEqual(204, response.status_code)
112+        self.assertTrue(response['Allow'])
113+
114+    def test_options_for_get_view(self):
115+        """
116+        Test that a view implementing GET allows GET and HEAD.
117+        """
118+        request = self.rf.options('/')
119+        view = SimpleView.as_view()
120+        response = view(request)
121+        self._assert_allows(response, 'GET', 'HEAD')
122+
123+    def test_options_for_get_and_post_view(self):
124+        """
125+        Test that a view implementing GET and POST allows GET, HEAD, and POST.
126+        """
127+        request = self.rf.options('/')
128+        view = SimplePostView.as_view()
129+        response = view(request)
130+        self._assert_allows(response, 'GET', 'HEAD', 'POST')
131+
132+    def test_options_for_post_view(self):
133+        """
134+        Test that a view implementing POST allows POST.
135+        """
136+        request = self.rf.options('/')
137+        view = PostOnlyView.as_view()
138+        response = view(request)
139+        self._assert_allows(response, 'POST')
140+
141+    def _assert_allows(self, response, *expected_methods):
142+        "Assert allowed HTTP methods reported in the Allow response header"
143+        response_allows = set(response['Allow'].split(', '))
144+        self.assertEqual(set(expected_methods + ('OPTIONS',)), response_allows)
145+
146 class TemplateViewTest(TestCase):
147     urls = 'regressiontests.generic_views.urls'
148