Search code examples
djangodjango-modelsmany-to-manydjango-rest-frameworkdjango-signals

Deleting the m2m relationship instead of the the object itself in Django via REST API


I have 3 models: User, UserItem (the m2m thourgh), and Item.

A User can create an Item. This automatically creates a UserItem.

A different User can see that Item, and add it to their own list of items, creating another UserItem.

If that first User wants to delete the Item, the other User won't be happy - it needs to stay, but appear gone for the initial User. However, if there's only one User still related to it, then the Item is safe to delete, and should be deleted to avoid filling the database with dead records.

This is how I think it should be handled:

  1. Item delete call made to API from User
  2. Item pre_delete checks if item.user_set > 1
  3. If True, manually delete the UserItem, leave Item where it is. If False, delete the Item

This way UserItem isn't exposed via the API, and management for a client is simplified.

Is this the right/common way to go? How can it be done with Django? I'm unsure how to prevent Item.delete() from happening within pre_delete without raising an exception, but as this is expected behaviour raising an exception doesn't seem like the right way to do this.


Solution

  • Here's what I went with. It keeps the logic in the model, but the view gives it the current user.

    I thought it best to keep out of delete() because an admin user should be able to delete an Item regardless of related users, and there is no simple way to access the current user within delete().

    Constructive criticism welcome!

    models.py

    class Item(TimeStampedModel):
    
        ...
    
        def delete_item_or_user_item(self, user):
        """
        Delete the Item if the current User is the only User related to it.
        If multiple Users are related to the Item, delete the UserItem.
        """
        if UserItem.objects.filter(item=self).count() > 1:
            UserItem.objects.filter(item=self, user=user).delete()
        else:
            self.delete()
    

    views.py

    class ItemViewSet(viewsets.ModelViewSet):
    
        ...
    
        def perform_destroy(self, instance):
            instance.delete_item_or_user_item(self.request.user)