Подтвердить что ты не робот

Почему не будет каскадом GenericForeignKey при удалении?

Я создаю настраиваемую систему комментариев, которая может добавлять комментарии атташе к любой модели, используя contenttypes GenericForeignKey.

class Comment(models.Model):
    body = models.TextField(verbose_name='Comment')
    user = models.ForeignKey(User)
    parent = models.ForeignKey('self', null=True, blank=True)
    created = models.DateTimeField(auto_now_add=True)
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey('content_type', 'object_id')

Я понимаю, что при удалении модели, связанной с комментарием, удаление должно также каскадироваться и удалять комментарий.

К сожалению, этого не происходит, и я в тупике. Существуют ли какие-либо общие причины изменения поведения удаления по умолчанию?

4b9b3361

Ответ 1

Нет, документация не говорит об этом. В нем говорится, что если вы определяете GenericRelation на модели - то есть обратную сторону GenericForeignKey - тогда, когда элемент с общим FK будет удален, элемент с GenericRelation также будет удален.

В отличие от ForeignKey, GenericForeignKey не принимает on_delete аргумент для настройки этого поведения; при желании вы можете избежать каскадное удаление просто, не используя GenericRelation, и чередуя поведение может быть обеспечено через сигнал pre_delete.

Ответ 2

Я понимаю, что это очень старый вопрос, так что, возможно, что-то отличается от того, когда его спрашивали, но принятый ответ заставил меня преследовать кроличью нору сегодня утром, поэтому я хотел оставить это здесь, чтобы будущие поколения не разделяли мою боль.

Из документов:

Также обратите внимание, что если вы удалите объект, имеющий GenericRelation, любые объекты, на которые указывает GenericForeignKey, также будут удалены. В приведенном выше примере это означает, что если объект Bookmark был удален, любые объекты TaggedItem, указывающие на него, будут удалены одновременно.

Это противоположно тому, что говорит принятый ответ. Представьте себе следующее:

class Comment(models.Model):
    body = models.TextField(verbose_name='Comment')
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey('content_type', 'object_id')

class Post(models.Model):
    comment = GenericRelation(Comment)

В приведенном выше примере, если ваш объект Comment имеет общий внешний ключ, указывающий на объект Post, то при удалении объекта Post все объекты Comment, указывающие на него, также будут удалены.

Это ожидаемое поведение и работает так же, как обычный ForeignKey. Используя тот же пример выше, если объект User, на который указывает объект Comment, удален, комментарий также будет удален.

Если вы случайно натолкнулись на этот вопрос, потому что вам нужно обратное поведение, то есть когда вы удаляете комментарий, пост также удаляется, тогда вам, вероятно, придется использовать силу сигналов.

Ответ 3

В дополнение к предыдущим ответам - если у вас более сложная структура и что-то вроде GenericOneToOne (чего нет в Django):

class Post(models.Model)
    title = models.CharField(max_length=100)

class Comment(models.Model):
    post = models.ForeignKey(Post)
    body = models.TextField(verbose_name='Comment')
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey('content_type', 'object_id')

    class Meta:
        # Constrain equals to OneToOne relation.
        unique_together = ('content_type', 'object_id')

class Author(models.Model):
    comment = GenericRelation(Comment)
    name = models.CharField(max_length=100)

И вы хотите удалить Post и убедитесь, что Comment и Author также удалены, вам нужно написать собственный сигнал post_delete:

from django.db.models.signals import post_delete
from django.dispatch import receiver

@receiver(post_delete, sender=Comment, dispatch_uid='delete_comment_content_object')
def delete_comment_content_object(sender, instance, using, **kwargs):
    instance.content_object.delete()

Если вы переопределите метод delete класса Comment следующим образом:

def delete(self, *args, **kwargs):
    self.content_object.delete()
    super().delete(args, kwargs)

Author удалит только если вы удалите Comment. Если вы удалите объект Post Author он останется в базе данных.