diff --git a/CHANGES b/CHANGES index d8f2577a77..dba0112e79 100644 --- a/CHANGES +++ b/CHANGES @@ -21,7 +21,11 @@ Major release, unreleased - ``send_file`` supports Unicode in ``attachment_filename``. (`#2223`_) - Pass ``_scheme`` argument from ``url_for`` to ``handle_build_error``. (`#2017`_) +- Add support for ``provide_automatic_options`` in ``add_url_rule`` to disable + adding OPTIONS method when the ``view_func`` argument is not a class. + (`#1489`_). +.. _#1489: https://github.com/pallets/flask/pull/1489 .. _#2017: https://github.com/pallets/flask/pull/2017 .. _#2223: https://github.com/pallets/flask/pull/2223 diff --git a/flask/app.py b/flask/app.py index c26387f034..a054d23caf 100644 --- a/flask/app.py +++ b/flask/app.py @@ -980,7 +980,7 @@ def iter_blueprints(self): return iter(self._blueprint_order) @setupmethod - def add_url_rule(self, rule, endpoint=None, view_func=None, **options): + def add_url_rule(self, rule, endpoint=None, view_func=None, provide_automatic_options=None, **options): """Connects a URL rule. Works exactly like the :meth:`route` decorator. If a view_func is provided it will be registered with the endpoint. @@ -1020,6 +1020,10 @@ def index(): endpoint :param view_func: the function to call when serving a request to the provided endpoint + :param provide_automatic_options: controls whether the ``OPTIONS`` + method should be added automatically. This can also be controlled + by setting the ``view_func.provide_automatic_options = False`` + before adding the rule. :param options: the options to be forwarded to the underlying :class:`~werkzeug.routing.Rule` object. A change to Werkzeug is handling of method options. methods @@ -1049,8 +1053,9 @@ def index(): # starting with Flask 0.8 the view_func object can disable and # force-enable the automatic options handling. - provide_automatic_options = getattr(view_func, - 'provide_automatic_options', None) + if provide_automatic_options is None: + provide_automatic_options = getattr(view_func, + 'provide_automatic_options', None) if provide_automatic_options is None: if 'OPTIONS' not in methods: diff --git a/tests/test_basic.py b/tests/test_basic.py index abce3ad44a..677b4be86a 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -50,7 +50,7 @@ def index_put(): assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] -def test_options_handling_disabled(): +def test_provide_automatic_options_attr(): app = flask.Flask(__name__) def index(): @@ -70,6 +70,54 @@ def index2(): assert sorted(rv.allow) == ['OPTIONS'] +def test_provide_automatic_options_kwarg(): + app = flask.Flask(__name__) + + def index(): + return flask.request.method + + def more(): + return flask.request.method + + app.add_url_rule('/', view_func=index, provide_automatic_options=False) + app.add_url_rule( + '/more', view_func=more, methods=['GET', 'POST'], + provide_automatic_options=False + ) + + c = app.test_client() + assert c.get('/').data == b'GET' + + rv = c.post('/') + assert rv.status_code == 405 + assert sorted(rv.allow) == ['GET', 'HEAD'] + + # Older versions of Werkzeug.test.Client don't have an options method + if hasattr(c, 'options'): + rv = c.options('/') + else: + rv = c.open('/', method='OPTIONS') + + assert rv.status_code == 405 + + rv = c.head('/') + assert rv.status_code == 200 + assert not rv.data # head truncates + assert c.post('/more').data == b'POST' + assert c.get('/more').data == b'GET' + + rv = c.delete('/more') + assert rv.status_code == 405 + assert sorted(rv.allow) == ['GET', 'HEAD', 'POST'] + + if hasattr(c, 'options'): + rv = c.options('/more') + else: + rv = c.open('/more', method='OPTIONS') + + assert rv.status_code == 405 + + def test_request_dispatching(): app = flask.Flask(__name__)