pythondjangosoft-delete

Soft deletion in many-to-many relationships with through model in Django


I'm facing an issue in my Django project related to soft deletion in many-to-many relationships using a through model. I'd appreciate any help or advice on how to resolve this issue.

I have three models in my application: Author, Book, and BookAuthor. The many-to-many relationship between Author and Book is defined through the BookAuthor model. Here are the definitions of my models:

from django.db import models

class Author(SoftDeleteModel):
    name = models.CharField(max_length=100)

class Book(SoftDeleteModel):
    title = models.CharField(max_length=200)
    authors = models.ManyToManyField(Author, through='BookAuthor', related_name='books')

class BookAuthor(SoftDeleteModel):
    book = models.ForeignKey(Book, on_delete=models.CASCADE)
    author = models.ForeignKey(Author, on_delete=models.CASCADE)

    class Meta:
        # Other necessary meta definitions
        pass

The issue arises when I soft delete a relationship in the BookAuthor model and then try to access the books of an author using the query author.books.all(). Even though I have soft deleted the relationship in BookAuthor, I still see all the books associated with the author.

I'm using the django-soft-delete package (https://pypi.org/project/django-soft-delete/),

Does anyone have any suggestions on how I can address this issue and ensure that softly deleted relationships are not displayed when accessing through the many-to-many relationship?

Any help or advice would be greatly appreciated! Thank you in advance!


Solution

  • After some further investigation, I managed to resolve the issue by modifying the SoftDeleteManager provided by the django-soft-delete package. Here's the modified SoftDeleteManager with additional logic to filter out softly deleted relationships when accessed through a many-to-many relationship:

    from django.db import models
    from soft_delete.managers import SoftDeleteQuerySet
    
    class SoftDeleteManager(models.Manager):
        def get_queryset(self):
            return SoftDeleteQuerySet(self.model, self._db).filter(is_deleted=False)
    
        def _apply_soft_delete_filter(self, queryset):
            return queryset.filter(is_deleted=False)
    
        def _apply_soft_delete_filter_if_needed(self, queryset):
            if self.__class__.__name__ == 'ManyRelatedManager':
                related_name = getattr(self.through, self.target_field_name).field._related_name
                filter_kwargs = {f'{related_name}__is_deleted': False}
                queryset = queryset.filter(is_deleted=False, **filter_kwargs)
            return queryset.filter(is_deleted=False)
    
        def all(self, *args, **kwargs):
            queryset = self.get_queryset()
            return self._apply_soft_delete_filter_if_needed(queryset)
    
        def filter(self, *args, **kwargs):
            queryset = self.get_queryset()
            queryset = self._apply_soft_delete_filter_if_needed(queryset)
            return queryset.filter(*args, **kwargs)
    
        def get(self, *args, **kwargs):
            queryset = self.get_queryset()
            queryset = self._apply_soft_delete_filter_if_needed(queryset)
            return queryset.get(*args, **kwargs)
    

    This modification ensures that softly deleted relationships are not displayed when accessed through the many-to-many relationship. If you encounter any issues or have further questions, feel free to ask!