Skip to content

Commit

Permalink
Issue 1627 - Save fails when a single resource is used with multiple …
Browse files Browse the repository at this point in the history
…related resources (#1628)

* Added a test case for issue #1627 save failure when a single resource is used with multiple other resource

* Fixed the test case data to contain the correct data to demonstrate the issue

* Fixed an issue where the bundle object identifier with unsaved objects could cause a data loss prevention exception to be thrown due to the create identifier function returning the same value for all unsaved objects. It is now regenerated once saved and stored in the saved_objects list.

* Updated authors for PR #1628

* Fixed new lines issues for code style check
  • Loading branch information
blayzen-w committed Jan 15, 2022
1 parent 2802f06 commit 433ef58
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 1 deletion.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ Contributors:
* Danny Roberts (dannyroberts) for very small change addressing noisy RemovedInDjango20Warning warnings
* Sam Thompson (georgedorn) for ongoing maintenance and release management.
* Matt Briançon (mattbriancon) for various patches
* Blayze Wilhelm (blayzen-w) for a small patch that fixed an issue with saving (PR #1628)


Thanks to Tav for providing validate_jsonp.py, placed in public domain.
Expand Down
1 change: 1 addition & 0 deletions tastypie/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -2406,6 +2406,7 @@ def save(self, bundle, skip_errors=False):

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

# Now pick up the M2M bits.
Expand Down
27 changes: 27 additions & 0 deletions tests/core/migrations/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,31 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(primary_key=True, serialize=False, verbose_name='ID')),
],
),
migrations.CreateModel(
name='MyContainerModel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(blank=True, max_length=128, null=True)),
],
),
migrations.CreateModel(
name='MyContainerItemModel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(blank=True, max_length=128, null=True)),
('parent', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='item_set',
to='core.mycontainermodel')),
],
),
migrations.CreateModel(
name='MyContainerItemGroupingModel',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('grouping_item',
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.mycontaineritemmodel')),
('parent',
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='item_grouping_set',
to='core.mycontainermodel')),
],
),
]
23 changes: 23 additions & 0 deletions tests/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,3 +148,26 @@ class MyRelatedUUIDModel(models.Model):

class Meta:
app_label = 'core'


class MyContainerModel(models.Model):
name = models.CharField(max_length=128, blank=True, null=True)

class Meta:
app_label = 'core'


class MyContainerItemModel(models.Model):
parent = models.ForeignKey(MyContainerModel, on_delete=models.CASCADE, related_name='item_set')
name = models.CharField(max_length=128, blank=True, null=True)

class Meta:
app_label = 'core'


class MyContainerItemGroupingModel(models.Model):
parent = models.ForeignKey(MyContainerModel, on_delete=models.CASCADE, related_name='item_grouping_set')
grouping_item = models.ForeignKey(MyContainerItemModel, on_delete=models.CASCADE)

class Meta:
app_label = 'core'
69 changes: 68 additions & 1 deletion tests/core/tests/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@

from core.models import (
Note, NoteWithEditor, Subject, MediaBit, AutoNowNote, DateRecord, Counter,
MyDefaultPKModel, MyUUIDModel, MyRelatedUUIDModel, BigAutoNowModel,
MyDefaultPKModel, MyUUIDModel, MyRelatedUUIDModel, BigAutoNowModel, MyContainerItemModel,
MyContainerItemGroupingModel, MyContainerModel
)
from core.tests.mocks import MockRequest
from core.utils import adjust_schema, SimpleHandler
Expand Down Expand Up @@ -1463,6 +1464,39 @@ class Meta:
authorization = CounterAuthorization()


class MyContainerItemModelResource(ModelResource):
parent = fields.ForeignKey('core.tests.resources.MyContainerModelResource', 'parent')

class Meta:
queryset = MyContainerItemModel.objects.all()
allowed_methods = ['get', 'put', 'post']
authorization = Authorization()
resource_name = 'my-container-item'


class MyContainerItemGroupingModel(ModelResource):
parent = fields.ForeignKey('core.tests.resources.MyContainerModelResource', 'parent')
grouping_item = fields.ForeignKey(MyContainerItemModelResource, 'grouping_item')

class Meta:
queryset = MyContainerItemGroupingModel.objects.all()
allowed_methods = ['get', 'put', 'post']
authorization = Authorization()
resource_name = 'my-container-item-group'


class MyContainerModelResource(ModelResource):
container_items = fields.ToManyField(MyContainerItemModelResource, 'item_set', blank=True)
container_grouping_items = fields.ToManyField(MyContainerItemGroupingModel, 'item_grouping_set', blank=True)

class Meta:
queryset = MyContainerModel.objects.all()
allowed_methods = ['get', 'put', 'post']
authorization = Authorization()
resource_name = 'my-container'
always_return_data = True


@override_settings(ROOT_URLCONF='core.tests.resource_urls')
class ModelResourceTestCase(TestCase):
fixtures = ['note_testdata.json']
Expand Down Expand Up @@ -5126,6 +5160,39 @@ def test_collection_name_patch_list(self):
response = resource.patch_list(request)
self.assertEqual(response.status_code, 202)

def test_saves_when_a_single_resource_is_used_on_multiple_to_many_resources(self):
container_resource = MyContainerModelResource(api_name='v1')
request = MockRequest()
request.GET = {'format': 'json'}
request.method = 'PUT'

container = MyContainerModel.objects.create(name='test')
resource_uri = '/api/v1/my-container/%s/' % container.id

request.set_body(json.dumps({
'resource_uri': resource_uri,
'id': container.id,
'name': 'foo',
'container_items': [
{
'parent': resource_uri,
'name': 'container item 1'
}
],
'container_grouping_items': [
{
'parent': resource_uri,
'grouping_item': {
'parent': resource_uri,
'name': 'container item 2'
}
}
]
}))

resp = container_resource.put_detail(request)
self.assertEqual(resp.status_code, 200)


class BasicAuthResourceTestCase(TestCase):
fixtures = ['note_testdata.json']
Expand Down

0 comments on commit 433ef58

Please sign in to comment.