Skip to content

Commit

Permalink
Refactored how RelatedField builds resources for easier extension.
Browse files Browse the repository at this point in the history
  • Loading branch information
toastdriven committed Jul 28, 2011
1 parent ac69156 commit 3811f68
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 38 deletions.
100 changes: 62 additions & 38 deletions tastypie/fields.py
Expand Up @@ -506,54 +506,78 @@ def dehydrate_related(self, bundle, related_resource):
bundle = related_resource.build_bundle(obj=related_resource.instance, request=bundle.request)
return related_resource.full_dehydrate(bundle)

def resource_from_uri(self, fk_resource, uri, request=None):
"""
Given a URI is provided, the related resource is attempted to be
loaded based on the identifiers in the URI.
"""
try:
obj = fk_resource.get_via_uri(uri)
bundle = fk_resource.build_bundle(obj=obj, request=request)
return fk_resource.full_dehydrate(bundle)
except ObjectDoesNotExist:
raise ApiFieldError("Could not find the provided object via resource URI '%s'." % uri)

def resource_from_data(self, fk_resource, data, request=None):
"""
Given a dictionary-like structure is provided, a fresh related
resource is created using that data.
"""
# Try to hydrate the data provided.
data = dict_strip_unicode_keys(data)
fk_bundle = fk_resource.build_bundle(data=data, request=request)

# We need to check to see if updates are allowed on the FK
# resource. If not, we'll just return a populated bundle instead
# of mistakenly updating something that should be read-only.
if not fk_resource.can_update():
return fk_resource.full_hydrate(fk_bundle)

try:
return fk_resource.obj_update(fk_bundle, **data)
except NotFound:
try:
# Attempt lookup by primary key
lookup_kwargs = dict((k, v) for k, v in data.iteritems() if getattr(fk_resource, k).unique)

if not lookup_kwargs:
raise NotFound()

return fk_resource.obj_update(fk_bundle, **lookup_kwargs)
except NotFound:
return fk_resource.full_hydrate(fk_bundle)
except MultipleObjectsReturned:
return fk_resource.full_hydrate(fk_bundle)

def resource_from_pk(self, fk_resource, obj, request=None):
"""
Given an object with a ``pk`` attribute, the related resource
is attempted to be loaded via that PK.
"""
bundle = fk_resource.build_bundle(obj=obj, request=request)
return fk_resource.full_dehydrate(bundle)

def build_related_resource(self, value, request=None):
"""
Used to ``hydrate`` the data provided. If just a URL is provided,
the related resource is attempted to be loaded. If a
dictionary-like structure is provided, a fresh resource is
created.
Returns a bundle of data built by the related resource, usually via
``hydrate`` with the data provided.
Accepts either a URI, a data dictionary (or dictionary-like structure)
or an object with a ``pk``.
"""
self.fk_resource = self.to_class()

if isinstance(value, basestring):
# We got a URI. Load the object and assign it.
try:
obj = self.fk_resource.get_via_uri(value)
bundle = self.fk_resource.build_bundle(obj=obj, request=request)
return self.fk_resource.full_dehydrate(bundle)
except ObjectDoesNotExist:
raise ApiFieldError("Could not find the provided object via resource URI '%s'." % value)
return self.resource_from_uri(self.fk_resource, value, request=request)
elif hasattr(value, 'items'):
# Try to hydrate the data provided.
value = dict_strip_unicode_keys(value)
self.fk_bundle = self.fk_resource.build_bundle(data=value, request=request)

# We need to check to see if updates are allowed on the FK
# resource. If not, we'll just return a populated bundle instead
# of mistakenly updating something that should be read-only.
if not self.fk_resource.can_update():
return self.fk_resource.full_hydrate(self.fk_bundle)

try:
return self.fk_resource.obj_update(self.fk_bundle, **value)
except NotFound:
try:
# Attempt lookup by primary key
lookup_kwargs = dict((k, v) for k, v in value.iteritems() if getattr(self.fk_resource, k).unique)

if not lookup_kwargs:
raise NotFound()

return self.fk_resource.obj_update(self.fk_bundle, **lookup_kwargs)
except NotFound:
return self.fk_resource.full_hydrate(self.fk_bundle)
except MultipleObjectsReturned:
return self.fk_resource.full_hydrate(self.fk_bundle)
# We've got a data dictionary.
return self.resource_from_data(self.fk_resource, value, request=request)
elif hasattr(value, 'pk'):
bundle = self.fk_resource.build_bundle(obj=value, request=request)
return self.fk_resource.full_dehydrate(bundle)
# We've got an object with a primary key.
return self.resource_from_pk(self.fk_resource, value, request=request)
else:
raise ApiFieldError("The '%s' field has was given data that was not a URI and not a dictionary-alike: %s." % (self.instance_name, value))
raise ApiFieldError("The '%s' field has was given data that was not a URI, not a dictionary-alike and does not have a 'pk' attribute: %s." % (self.instance_name, value))


class ToOneField(RelatedField):
Expand Down
32 changes: 32 additions & 0 deletions tests/core/tests/fields.py
Expand Up @@ -757,6 +757,38 @@ def test_hydrate(self):
field_12.instance_name = 'author'
self.assertEqual(field_12.hydrate(bundle), None)

def test_resource_from_uri(self):
ur = UserResource()
field_1 = ToOneField(UserResource, 'author')
fk_bundle = field_1.resource_from_uri(ur, '/api/v1/users/1/')
self.assertEqual(fk_bundle.data['username'], u'johndoe')
self.assertEqual(fk_bundle.data['email'], u'john@doe.com')
self.assertEqual(fk_bundle.obj.username, u'johndoe')
self.assertEqual(fk_bundle.obj.email, u'john@doe.com')

def test_resource_from_data(self):
ur = UserResource()
field_1 = ToOneField(UserResource, 'author')
fk_bundle = field_1.resource_from_data(ur, {
'username': u'mistersmith',
'email': u'smith@example.com',
'password': u'foobar',
})
self.assertEqual(fk_bundle.data['username'], u'mistersmith')
self.assertEqual(fk_bundle.data['email'], u'smith@example.com')
self.assertEqual(fk_bundle.obj.username, u'mistersmith')
self.assertEqual(fk_bundle.obj.email, u'smith@example.com')

def test_resource_from_pk(self):
user = User.objects.get(pk=1)
ur = UserResource()
field_1 = ToOneField(UserResource, 'author')
fk_bundle = field_1.resource_from_pk(ur, user)
self.assertEqual(fk_bundle.data['username'], u'johndoe')
self.assertEqual(fk_bundle.data['email'], u'john@doe.com')
self.assertEqual(fk_bundle.obj.username, u'johndoe')
self.assertEqual(fk_bundle.obj.email, u'john@doe.com')


class SubjectResource(ModelResource):
class Meta:
Expand Down

0 comments on commit 3811f68

Please sign in to comment.