Search code examples
djangoresttastypie

How do I Delete related resources with TastyPie


I have two related resources:

class QuantityResource(ModelResource):
    class Meta:
        queryset = Quantity.objects.all()

class EntryResource(ModelResource):
    quantities = fields.ToManyField(QuantityResource, 'quantities', full=True, null=True)
    class Meta:
        queryset = Entry.objects.all()

If I PUT to an existing entry with quantities: [] I expect TastyPie to delete all related Quantity objects, but this is not happening. Similarly if I put a list to quantities that does not include an existing value, I'd expect it to be removed.


Solution

  • Tastypie doesn't support deleting objects through a many-to-many relationship. Only update and add are supported. Relevant code. It will call clear on the related manager for your many-to-many field but that will only disassociate your related models from the parent it won't actually delete them. There was a patch 2 years ago to add the functionality but it wasn't ever merged.

    The best way to handle this is probably to enable filtering on the QuantityResrouce and PUT the updated list to the quantity resource filtered by the parent resource which will add/update/delete everything in the collection. Note that you'll have to add a ForeignKey field to the QuantityResource otherwise you won't be able to save the collection due to database constraints (assuming your entry field is required on the Quantity model.

    class QuantityResource(ModelResource):
    
        entry = fields.ForeignKey('your.resources.EntryResource', 'entry')
    
        class Meta:
            queryset = Quantity.objects.all()
            filtering = {
                'entry': ['exact'],
            }
    

    Here's a test case to illustrate what I mean:

    from tastypie.test import ResourceTestCase
    
    from testres.models import Quantity, Entry
    
    
    class EntryResourcesTest(ResourceTestCase):
    
        def setUp(self):
            super(EntryResourcesTest, self).setUp()
    
            entry = Entry.objects.create(name='Foo')
            entry2 = Entry.objects.create(name='Bar')
    
            Quantity.objects.create(entry=entry, quantity=1)
            Quantity.objects.create(entry=entry, quantity=3)
            Quantity.objects.create(entry=entry, quantity=5)
    
            Quantity.objects.create(entry=entry2, quantity=2)
    
        def test_delete_item(self):
            objs = self.deserialize(
                    self.api_client.get('/api/v1/quantity/?entry=1'))['objects'][1:]
    
            self.assertHttpAccepted(self.api_client.put(
                '/api/v1/quantity/?entry=1', data={ 'objects': objs }))
    
            # Ensure resource deleted
            obj = self.deserialize(self.api_client.get('/api/v1/entry/1/'))
            self.assertEqual(len(obj['quantities']), 2)
    
            # Make sure we didn't delete them all
            obj = self.deserialize(self.api_client.get('/api/v1/quantity/'))
            self.assertEqual(len(obj['objects']), 3)