diff --git a/.gitignore b/.gitignore index 73be34a..02d0281 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ docs/_build *node_modules* # but keep the .coveragerc (for TravisCI/Coveralls services) !.coveragerc + +# test data +*.sqlite3 \ No newline at end of file diff --git a/CHANGES b/CHANGES index 6da936e..017d398 100644 --- a/CHANGES +++ b/CHANGES @@ -1,5 +1,9 @@ Changelog ================ +* 0.1.8 + + * Added support for the ``request.response`` API. + * 0.1.7 * Added support for the ``api_version`` predicate. diff --git a/docs/conf.py b/docs/conf.py index 9c3c052..653c04d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,7 +50,7 @@ # The short X.Y version. version = '0.1' # The full version, including alpha/beta/rc tags. -release = '0.1.7' +release = '0.1.8' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/index.rst b/docs/index.rst index 814710e..cd0f6d4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -289,6 +289,65 @@ used, the view must return a HttpResponse object or a Python *dictionary*. The dictionary items will then be used as the template context objects. +Varying Attributes of Rendered Responses +---------------------------------------- + +.. note:: This section is partly copied from the + `Pyramid Renderers documentation `_, + since Rhetoric provides almost the same API. + +.. versionadded:: 0.1.8 + +Before a response constructed by a :term:`renderer` is returned to +:app:`Django`, several attributes of the request are examined which have the +potential to influence response behavior. + +View callables that don't directly return a response should use the API of +the :class:`django.http.HttpResponse` attribute available as +``request.response`` during their execution, to influence associated response +behavior. + +For example, if you need to change the response status from within a view +callable that uses a renderer, assign the ``status_code`` attribute to the +``response`` attribute of the request before returning a result: + +.. code-block:: python + :linenos: + + from rhetoric import view_config + + @view_config(name='dashboard', renderer='dashboard.html') + def myview(request): + request.response.status_code = 404 + return {'URL': request.get_full_path()} + +Note that mutations of ``request.response`` in views which return a HttpResponse +object directly will have no effect unless the response object returned *is* +``request.response``. For example, the following example calls +``request.response.set_cookie``, but this call will have no effect, because a +different Response object is returned. + +.. code-block:: python + :linenos: + + from django.http import HttpResponse + + def view(request): + request.response.set_cookie('abc', '123') # this has no effect + return HttpResponse('OK') # because we're returning a different response + +If you mutate ``request.response`` and you'd like the mutations to have an +effect, you must return ``request.response``: + +.. code-block:: python + :linenos: + + def view(request): + request.response.set_cookie('abc', '123') + return request.response + + + Predicates ============================ diff --git a/rhetoric/config/rendering.py b/rhetoric/config/rendering.py index 9a9af9f..4f0b013 100644 --- a/rhetoric/config/rendering.py +++ b/rhetoric/config/rendering.py @@ -11,7 +11,10 @@ def __init__(self, name): self.name = name def __call__(self, request, view_response): - return HttpResponse(json_encode(view_response), content_type='application/json; charset=utf-8') + response = request.response + response.content_type = 'application/json; charset=utf-8' + response.content = json_encode(view_response) + return response class StringRendererFactory(object): @@ -19,7 +22,10 @@ def __init__(self, name): self.name = name def __call__(self, request, view_response): - return HttpResponse(view_response, content_type='text/plain; charset=utf-8') + response = request.response + response.content_type = 'text/plain; charset=utf-8' + response.content = view_response + return response class DjangoTemplateRendererFactory(object): @@ -27,7 +33,12 @@ def __init__(self, name): self.name = name def __call__(self, request, context_dict): - return render(request, self.name, context_dict) + response = request.response + httpresponse_kwargs = { + 'content_type': response['Content-Type'], + 'status': response.status_code + } + return render(request, self.name, context_dict, **httpresponse_kwargs) BUILTIN_RENDERERS = { diff --git a/rhetoric/middleware.py b/rhetoric/middleware.py index c23af74..13b7c39 100644 --- a/rhetoric/middleware.py +++ b/rhetoric/middleware.py @@ -1,9 +1,20 @@ +from django.http import HttpResponse from django.middleware.csrf import CsrfViewMiddleware from rhetoric.view import ViewCallback class CsrfProtectedViewDispatchMiddleware(CsrfViewMiddleware): + + def process_request(self, request): + # We assume here that CsrfViewMiddleware doesn't have the process_request method + # which should be called via super(). + # ------------------------------------------------- + # set request.response object as in + # http://docs.pylonsproject.org/projects/pyramid/en/latest/api/request.html#pyramid.request.Request.response + setattr(request, 'response', HttpResponse()) + + def process_view(self, request, callback, callback_args, callback_kwargs): if isinstance(callback, ViewCallback): view_settings = callback.find_view_settings(request, callback_args, callback_kwargs) @@ -12,7 +23,7 @@ def process_view(self, request, callback, callback_args, callback_kwargs): request, view_settings['view'], callback_args, callback_kwargs ) - # The callable is not a part of Rhetoric + # The callable is a regular django view return super(CsrfProtectedViewDispatchMiddleware, self).process_view( request, callback, callback_args, callback_kwargs - ) \ No newline at end of file + ) diff --git a/rhetoric/view.py b/rhetoric/view.py index 503f682..6db9469 100644 --- a/rhetoric/view.py +++ b/rhetoric/view.py @@ -1,5 +1,6 @@ from django.core.urlresolvers import RegexURLPattern as DjangoRegexURLPattern -from django.http import HttpResponse, Http404 +from django.http import HttpResponse +from django.http import Http404 import venusian diff --git a/setup.py b/setup.py index 611317c..0c3d762 100644 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ def run_tests(self): setup( name='Rhetoric', - version='0.1.7', + version='0.1.8', packages=find_packages(exclude=['tests']), install_requires=[ 'Django>=1.4', diff --git a/tests/testapp/testapp/blog/views.py b/tests/testapp/testapp/blog/views.py index cd64092..e801abd 100644 --- a/tests/testapp/testapp/blog/views.py +++ b/tests/testapp/testapp/blog/views.py @@ -19,6 +19,8 @@ def api_v1_submit_form_view(request): @view_config(route_name='blog.page', request_method='GET', renderer='json') def blog_page(request, page_slug): + # test custom response status api + request.response.status_code = 201 return { 'page_slug': page_slug } diff --git a/tests/testapp/testapp/index/views.py b/tests/testapp/testapp/index/views.py index b1c8bf5..9eb2197 100644 --- a/tests/testapp/testapp/index/views.py +++ b/tests/testapp/testapp/index/views.py @@ -25,6 +25,7 @@ def dashboard(request): # is explicitly set decorator=require_http_methods(["GET", "POST"])) def post_on_dashboard(request): + request.response.status_code = 201 return {'method': 'POST'} diff --git a/tests/url_tests.py b/tests/url_tests.py index d013dbd..f15ee0f 100644 --- a/tests/url_tests.py +++ b/tests/url_tests.py @@ -10,7 +10,7 @@ def test_blog_requests(self): assert response.status_code == 200 response = self.client.get('/blog/page/page-slug') - assert response.status_code == 200 + assert response.status_code == 201 json_data = response.content.decode('utf-8') assert {'page_slug':'page-slug'} == json.loads(json_data) @@ -24,7 +24,7 @@ def test_dashboard_requests(self): # Test POST to the same URL response = self.client.post('/dashboard') - assert response.status_code == 200 + assert response.status_code == 201 assert response.content.decode('utf-8').strip() == 'Dashboard POST' # Test PUT to the same URL @@ -39,3 +39,7 @@ def test_non_rhetoric_urls(self): def test_route_path(self): url = self.rhetoric.url.route_path('index.dashboard') assert url == '/dashboard' + + def test_custom_response_attributes(self): + response = self.client.get('/blog/page/page-slug') + assert response.content_type == 'application/json; charset=utf-8'