Skip to content

Commit c4996df

Browse files
committed
Fixed django#17449 -- Added OPTIONS to generic views.
Thanks estebistec for the report and patch.
1 parent 009e237 commit c4996df

File tree

2 files changed

+62
-2
lines changed

2 files changed

+62
-2
lines changed

django/views/generic/base.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,22 @@ def dispatch(self, request, *args, **kwargs):
7979
return handler(request, *args, **kwargs)
8080

8181
def http_method_not_allowed(self, request, *args, **kwargs):
82-
allowed_methods = [m for m in self.http_method_names if hasattr(self, m)]
8382
logger.warning('Method Not Allowed (%s): %s', request.method, request.path,
8483
extra={
8584
'status_code': 405,
8685
'request': self.request
8786
}
8887
)
89-
return http.HttpResponseNotAllowed(allowed_methods)
88+
return http.HttpResponseNotAllowed(self._allowed_methods())
89+
90+
def options(self, request, *args, **kwargs):
91+
response = http.HttpResponse()
92+
response['Allow'] = ', '.join(self._allowed_methods())
93+
response['Content-Length'] = 0
94+
return response
95+
96+
def _allowed_methods(self):
97+
return [m.upper() for m in self.http_method_names if hasattr(self, m)]
9098

9199

92100
class TemplateResponseMixin(object):

tests/regressiontests/generic_views/base.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,58 @@ def test_dispatch_decoration(self):
173173
"""
174174
self.assertTrue(DecoratedDispatchView.as_view().is_decorated)
175175

176+
def test_head_no_get(self):
177+
"""
178+
Test that a view class with no get responds to a HEAD request with HTTP
179+
405.
180+
"""
181+
request = self.rf.head('/')
182+
view = PostOnlyView.as_view()
183+
self.assertEqual(405, view(request).status_code)
184+
185+
def test_options(self):
186+
"""
187+
Test that views respond to HTTP OPTIONS requests with an Allow header
188+
appropriate for the methods implemented by the view class.
189+
"""
190+
request = self.rf.options('/')
191+
view = SimpleView.as_view()
192+
response = view(request)
193+
self.assertEqual(200, response.status_code)
194+
self.assertTrue(response['Allow'])
195+
196+
def test_options_for_get_view(self):
197+
"""
198+
Test that a view implementing GET allows GET and HEAD.
199+
"""
200+
request = self.rf.options('/')
201+
view = SimpleView.as_view()
202+
response = view(request)
203+
self._assert_allows(response, 'GET', 'HEAD')
204+
205+
def test_options_for_get_and_post_view(self):
206+
"""
207+
Test that a view implementing GET and POST allows GET, HEAD, and POST.
208+
"""
209+
request = self.rf.options('/')
210+
view = SimplePostView.as_view()
211+
response = view(request)
212+
self._assert_allows(response, 'GET', 'HEAD', 'POST')
213+
214+
def test_options_for_post_view(self):
215+
"""
216+
Test that a view implementing POST allows POST.
217+
"""
218+
request = self.rf.options('/')
219+
view = PostOnlyView.as_view()
220+
response = view(request)
221+
self._assert_allows(response, 'POST')
222+
223+
def _assert_allows(self, response, *expected_methods):
224+
"Assert allowed HTTP methods reported in the Allow response header"
225+
response_allows = set(response['Allow'].split(', '))
226+
self.assertEqual(set(expected_methods + ('OPTIONS',)), response_allows)
227+
176228

177229
class TemplateViewTest(TestCase):
178230
urls = 'regressiontests.generic_views.urls'

0 commit comments

Comments
 (0)