Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Avoiding PUT/POST to rebound data #615

Open
wants to merge 1 commit into from

5 participants

@buzzeante

full_dehydrate assumes bundle.data to be empty so it just populates elements from bundle.obj. This works fine with GET. However, when POST or PUT request are done the same bundle goes through all the flow. As a result, data that came from the request and is not part of the resource is rebounded in the response.

The fix avoids this problem by reseting bundle.data to an empty dictionary before starting the dehydration process.

When using Tastypie with BackboneJS, this data rebounded tampers models causing side effects very hard to detect.

Cheers,
Javi

@travisbot

This pull request fails (merged 438d642 into 9479190).

@joshbohde

Thanks for the pull request. Before this could get merged, we'd need to have a failing test, per our contribution guidelines.

@buzzeante

Thanks Josh. I've just added a test for it.

Cheers,
Javi

@travisbot

This pull request passes (merged df29682 into 5397dec).

@ondrowan

What's the status on this? I like this solution. My only concern is if there aren't cases when contents of bundle.data might be useful. Couldn't come up with any use cases though.

@roysmith

I just got bitten by this exact bug (see https://groups.google.com/d/topic/django-tastypie/WE_d92Fkl-I/discussion). It would be great if this patch could be included in the next release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 14, 2012
  1. @buzzeante
This page is out of date. Refresh to see the latest.
View
2  tastypie/resources.py
@@ -709,6 +709,8 @@ def full_dehydrate(self, bundle):
Given a bundle with an object instance, extract the information from it
to populate the resource.
"""
+ # clear data that will be returned
+ bundle.data = {}
# Dehydrate each field.
for field_name, field_object in self.fields.items():
# A touch leaky but it makes URI resolution work.
View
8 tests/basic/api/resources.py
@@ -36,6 +36,14 @@ class Meta:
authorization = Authorization()
+class FullNoteResource(ModelResource):
+ class Meta:
+ resource_name = 'full_note'
+ queryset = Note.objects.all()
+ authorization = Authorization()
+ always_return_data = True
+
+
class BustedResource(ModelResource):
class Meta:
queryset = AnnotatedNote.objects.all()
View
3  tests/basic/api/urls.py
@@ -1,6 +1,6 @@
from django.conf.urls.defaults import *
from tastypie.api import Api
-from basic.api.resources import NoteResource, UserResource, BustedResource, CachedUserResource, SlugBasedNoteResource, SessionUserResource
+from basic.api.resources import NoteResource, UserResource, BustedResource, CachedUserResource, SlugBasedNoteResource, SessionUserResource, FullNoteResource
api = Api(api_name='v1')
api.register(NoteResource(), canonical=True)
@@ -9,6 +9,7 @@
v2_api = Api(api_name='v2')
v2_api.register(BustedResource(), canonical=True)
+v2_api.register(FullNoteResource())
v2_api.register(SlugBasedNoteResource())
v2_api.register(SessionUserResource())
View
14 tests/basic/tests/http.py
@@ -61,6 +61,20 @@ def test_post_object(self):
self.assertEqual(obj['is_active'], True)
self.assertEqual(obj['user'], '/api/v1/users/1/')
+ def test_put_object(self):
+ connection = self.get_connection()
+ post_data = '{"content": "A new post modified.", "is_active": true, "title": "New Title", "slug": "new-title", "user": "/api/v1/users/1/", "foo-bar": "this should not be in the response"}'
+ connection.request('PUT', '/api/v2/full_note/2/', body=post_data, headers={'Accept': 'application/json', 'Content-type': 'application/json'})
+ response = connection.getresponse()
+ self.assertEqual(response.status, 202)
+
+ # make sure no data is rebounded
+ data = response.read()
+ obj = json.loads(data)
+
+ self.assertNotIn('foo-bar', obj)
+ self.assertEqual(obj['content'], "A new post modified.")
+
def test_cache_control(self):
"""Ensure that resources can specify custom cache control directives"""
connection = self.get_connection()
Something went wrong with that request. Please try again.