Skip to content

Commit

Permalink
Merge pull request #1363 from SeanHayes/strets123-master
Browse files Browse the repository at this point in the history
Fix hydrating/saving of related resources.
  • Loading branch information
SeanHayes committed Oct 13, 2015
2 parents ba24562 + c4e90b1 commit 0fabc0c
Show file tree
Hide file tree
Showing 9 changed files with 274 additions and 48 deletions.
2 changes: 2 additions & 0 deletions tastypie/bundle.py
Expand Up @@ -19,6 +19,7 @@ def __init__(self,
related_name=None,
objects_saved=None,
related_objects_to_save=None,
via_uri=False,
):
self.obj = obj
self.data = data or {}
Expand All @@ -28,6 +29,7 @@ def __init__(self,
self.errors = {}
self.objects_saved = objects_saved or set()
self.related_objects_to_save = related_objects_to_save or {}
self.via_uri = via_uri

def __repr__(self):
return "<Bundle for obj: '%s' and with data: '%s'>" % (self.obj, self.data)
13 changes: 7 additions & 6 deletions tastypie/fields.py
Expand Up @@ -600,7 +600,8 @@ def resource_from_uri(self, fk_resource, uri, request=None, related_obj=None, re
obj = fk_resource.get_via_uri(uri, request=request)
bundle = fk_resource.build_bundle(
obj=obj,
request=request
request=request,
via_uri=True
)
return fk_resource.full_dehydrate(bundle)
except ObjectDoesNotExist:
Expand Down Expand Up @@ -628,14 +629,14 @@ def resource_from_data(self, fk_resource, data, request=None, related_obj=None,
# happens to match other kwargs. In the case of a create, it might be the
# completely wrong resource.
# We also need to check to see if updates are allowed on the FK resource.
if unique_keys and fk_resource.can_update():
if unique_keys:
try:
return fk_resource.obj_update(fk_bundle, skip_errors=True, **data)
except (NotFound, TypeError):
fk_resource.obj_get(fk_bundle, skip_errors=True, **data)
except (ObjectDoesNotExist, NotFound, TypeError):
try:
# Attempt lookup by primary key
return fk_resource.obj_update(fk_bundle, skip_errors=True, **unique_keys)
except NotFound:
fk_resource.obj_get(fk_bundle, skip_errors=True, **unique_keys)
except (ObjectDoesNotExist, NotFound):
pass
except MultipleObjectsReturned:
pass
Expand Down
39 changes: 14 additions & 25 deletions tastypie/resources.py
Expand Up @@ -698,7 +698,7 @@ def authorized_delete_detail(self, object_list, bundle):

return auth_result

def build_bundle(self, obj=None, data=None, request=None, objects_saved=None):
def build_bundle(self, obj=None, data=None, request=None, objects_saved=None, via_uri=None):
"""
Given either an object, a data dictionary or both, builds a ``Bundle``
for use throughout the ``dehydrate/hydrate`` cycle.
Expand All @@ -714,7 +714,8 @@ def build_bundle(self, obj=None, data=None, request=None, objects_saved=None):
obj=obj,
data=data,
request=request,
objects_saved=objects_saved
objects_saved=objects_saved,
via_uri=via_uri
)

def build_filters(self, filters=None):
Expand Down Expand Up @@ -2277,6 +2278,9 @@ def create_identifier(self, obj):
return u"%s.%s.%s" % (obj._meta.app_label, get_module_name(obj._meta), obj.pk)

def save(self, bundle, skip_errors=False):
if bundle.via_uri:
return bundle

self.is_valid(bundle)

if bundle.errors and not skip_errors:
Expand All @@ -2292,8 +2296,11 @@ def save(self, bundle, skip_errors=False):
self.save_related(bundle)

# Save the main object.
bundle.obj.save()
bundle.objects_saved.add(self.create_identifier(bundle.obj))
obj_id = self.create_identifier(bundle.obj)

if obj_id not in bundle.objects_saved or bundle.obj._state.adding:
bundle.obj.save()
bundle.objects_saved.add(obj_id)

# Now pick up the M2M bits.
m2m_bundle = self.hydrate_m2m(bundle)
Expand Down Expand Up @@ -2347,15 +2354,6 @@ def save_related(self, bundle):

related_resource = field_object.get_related_resource(related_obj)

# Before we build the bundle & try saving it, let's make sure we
# haven't already saved it.
if related_obj:
obj_id = self.create_identifier(related_obj)

if obj_id in bundle.objects_saved:
# It's already been saved. We're done here.
continue

if bundle.data.get(field_name) and hasattr(bundle.data[field_name], 'keys'):
# Only build & save if there's data, not just a URI.
related_bundle = related_resource.build_bundle(
Expand Down Expand Up @@ -2414,25 +2412,16 @@ def save_m2m(self, bundle):
for related_bundle in bundle.data[field_name]:
related_resource = field_object.get_related_resource(bundle.obj)

# Before we build the bundle & try saving it, let's make sure we
# haven't already saved it.
obj_id = self.create_identifier(related_bundle.obj)

if obj_id in bundle.objects_saved:
# It's already been saved. We're done here.
continue

# Only build & save if there's data, not just a URI.
updated_related_bundle = related_resource.build_bundle(
obj=related_bundle.obj,
data=related_bundle.data,
request=bundle.request,
objects_saved=bundle.objects_saved
objects_saved=bundle.objects_saved,
via_uri=related_bundle.via_uri,
)

# Only save related models if they're newly added.
if updated_related_bundle.obj._state.adding:
related_resource.save(updated_related_bundle)
related_resource.save(updated_related_bundle)
related_objs.append(updated_related_bundle.obj)

related_mngr.add(*related_objs)
Expand Down
3 changes: 3 additions & 0 deletions tests/core/tests/mocks.py
Expand Up @@ -11,6 +11,9 @@ def __init__(self):
self.path = ''
self.method = 'GET'

def _load_post_and_files(self, *args, **kwargs):
pass

def get_full_path(self, *args, **kwargs):
return self.path

Expand Down
41 changes: 38 additions & 3 deletions tests/related_resource/api/resources.py
Expand Up @@ -6,9 +6,9 @@

from core.models import Note, MediaBit

from related_resource.models import Category, Tag, ExtraData, Taggable,\
TaggableTag, Person, Company, Product, Address, Dog, DogHouse, Forum,\
Bone, Job, Label, Payment, Post, Order, OrderItem
from related_resource.models import Bone, Category, Contact, ContactGroup,\
ExtraData, Person, Company, Product, Address, Dog, DogHouse, Forum,\
Job, Label, Order, OrderItem, Payment, Post, Tag, Taggable, TaggableTag


class UserResource(ModelResource):
Expand All @@ -19,6 +19,14 @@ class Meta:
authorization = Authorization()


class UpdatableUserResource(ModelResource):
class Meta:
resource_name = 'users'
queryset = User.objects.all()
allowed_methods = ['get', 'put']
authorization = Authorization()


class NoteResource(ModelResource):
author = fields.ForeignKey(UserResource, 'author')

Expand All @@ -28,6 +36,15 @@ class Meta:
authorization = Authorization()


class NoteWithUpdatableUserResource(ModelResource):
author = fields.ForeignKey(UpdatableUserResource, 'author')

class Meta:
resource_name = 'noteswithupdatableuser'
queryset = Note.objects.all()
authorization = Authorization()


class CategoryResource(ModelResource):
parent = fields.ToOneField('self', 'parent', null=True)

Expand Down Expand Up @@ -243,3 +260,21 @@ class Meta:
queryset = Order.objects.all()
resource_name = 'order'
authorization = Authorization()


class ContactGroupResource(ModelResource):
members = fields.ToManyField('related_resource.api.resources.ContactResource', 'members', related_name='groups', null=True, blank=True)

class Meta:
queryset = ContactGroup.objects.prefetch_related('members')
resource_name = 'contactgroup'
authorization = Authorization()


class ContactResource(ModelResource):
groups = fields.ToManyField(ContactGroupResource, 'groups', related_name='members', null=True, blank=True)

class Meta:
queryset = Contact.objects.prefetch_related('groups')
resource_name = 'contact'
authorization = Authorization()
6 changes: 5 additions & 1 deletion tests/related_resource/api/urls.py
Expand Up @@ -5,11 +5,13 @@
ExtraDataResource, FreshNoteResource, FreshMediaBitResource,\
ForumResource, CompanyResource, ProductResource, AddressResource,\
PersonResource, DogResource, DogHouseResource, BoneResource,\
LabelResource, PostResource, OrderResource, OrderItemResource
LabelResource, PostResource, OrderResource, OrderItemResource,\
NoteWithUpdatableUserResource, ContactResource, ContactGroupResource


api = Api(api_name='v1')
api.register(NoteResource(), canonical=True)
api.register(NoteWithUpdatableUserResource(), canonical=True)
api.register(UserResource(), canonical=True)
api.register(CategoryResource(), canonical=True)
api.register(TagResource(), canonical=True)
Expand All @@ -30,5 +32,7 @@
api.register(LabelResource(), canonical=True)
api.register(OrderResource(), canonical=True)
api.register(OrderItemResource(), canonical=True)
api.register(ContactResource(), canonical=True)
api.register(ContactGroupResource(), canonical=True)

urlpatterns = api.urls
28 changes: 28 additions & 0 deletions tests/related_resource/models.py
Expand Up @@ -142,3 +142,31 @@ class Order(models.Model):
class OrderItem(models.Model):
order = models.ForeignKey(Order, related_name="items")
product = models.CharField(max_length=200)


class ContactGroup(models.Model):
name = models.CharField(max_length=75, blank=True,
help_text="Contact first name.")

class Meta:
ordering = ['id']

def __unicode__(self):
return u'%s' % self.name


class Contact(models.Model):
name = models.CharField(max_length=255)
groups = models.ManyToManyField(
ContactGroup,
related_name='members',
null=True,
blank=True,
help_text="The Contact Groups this Contact belongs to."
)

class Meta:
ordering = ['id']

def __unicode__(self):
return u'%s' % self.name

0 comments on commit 0fabc0c

Please sign in to comment.