Существует ли в Django ManyToManyField с подразумеваемым правом собственности?

Представим, что я создаю Django-сайт "CartoonWiki", который позволяет пользователям писать вики-статьи (представленные WikiArticle-моделью), а также размещать сообщения на форуме (представленные ForumPost-моделью). Со временем на сайт будут добавлены дополнительные возможности.

У WikiArticle есть несколько FileUpload, которые должны быть удалены при удалении WikiArticle. Под "удалением" я подразумеваю .delete()метод Django.

Однако, FileUpload-модель является общей - она не специфична для WikiArticle - и содержит общую логику загрузки файлов, которая, например, удаляет файл из S3, когда он удаляется из базы данных. Другие модели, такие как ForumPost, также будут использовать FileUpload-модель.

Я не хочу использовать ни GenericForeignKey, ни многотабличное наследование по причинам, которые Люк Плант излагает в статье блога Avoid Django's GenericForeignKey. (Однако, если вы сможете убедить меня, что на самом деле нет лучшего способа, чем компромисс GenericForeignKey, я, возможно, поколеблюсь и приму убедительный ответ такого рода.)

Теперь, самый тривиальный способ сделать это - иметь:

class FileUpload(models.Model):
    article = models.ForeignKey('WikiArticle', null=True, blank=True, on_delete=models.CASCADE)
    post = models.ForeignKey('ForumPost', null=True, blank=True, on_delete=models.CASCADE)

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

Моим предпочтением было бы пойти в обратном направлении:

class WikiArticle(models.Model):
    uploads = models.ManyToManyField('FileUpload')

Но это не решает проблему удаления: Если я .delete() создаю WikiArticle, то соответствующие FileUpload не удаляются. Я пробовал различные настройки со сквозными моделями, но ни одна из них, похоже, не решает эту проблему. Что мне действительно нужно, так это OneToMany-поле - своего рода обратное ForeignKey для указания принадлежности в нужном направлении без загрязнения общей/повторно используемой модели.

Вместо этого FileUpload действительно должно быть поле? Или, возможно, абстрактной моделью? (WikiArticleFileUpload, ForumPostFileUpload и так далее...)

ManyToMany поля симметричны, даже если вы определяете их в одной модели с (явным или неявным) related_name в другой.

Я могу предложить два метода очистки во время или после удаления WikiArticle. Первый заключается в периодическом поиске и удалении "бесхозных" FileUploads. В самом простом случае (при условии, что имя related_name равно articles)

deleted = FileUpload.objects.filter( articles__isnull=True).delete()

Другой способ заключается в явной обработке связанных статей при удалении статьи. Это просто - подкласс метода delete объекта, но это не единственный способ удаления объекта (bulk_delete, например, обходит это). В любом случае,

def delete( self, *args, **kwargs):

    article_pks = self.uploads.all().values_list('pk', Flat=True)
    response = super().delete( *args, **kwargs)
    FileUpload.objects.filter( 
        pk__in = article_pks, articles__isnull=True) .delete()
    return response

(или даже просто выполнять "периодически" код выше, для каждой статьи-удаления, который также будет tity после любых удаленных через другие каналы)

Пожалуйста, тщательно протестируйте это, если используете. Операции удаления, которые не делают именно то, что нужно, - самые страшные ошибки!

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

  1. Использование GenericForeignKey в этой ситуации, вероятно, нормально, особенно из-за того, что вы не знаете, сколько других моделей будут использовать вашу модель выгрузки. Конечно, как упоминается в статье блога по ссылке, например, выполнение простых SQL-запросов может быть сложнее, но только вам решать, является ли это проблемой для вашего случая использования.

  2. Также можно использовать наследование, чтобы все ссылающиеся модели наследовали отношение к модели выгрузки от общего предка. Это может иметь небольшое влияние на производительность, поскольку Django придется объединять таблицы моделей, но влияние может быть не таким большим. С другой стороны, этот подход может иметь некоторые преимущества, если, например, ваши статьи и посты имеют и другие общие вещи, и вы можете легко сделать что-то вроде "показать все новые посты и статьи (вместе)".

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

Возможно, вполне нормально, что вы просто используете GenericForeignKey, особенно если количество моделей, использующих вашу "общую" модель, становится больше (например, больше 3-5). В целом, это похоже на то, для чего и был создан GenericForeignKey (представьте, что загрузка - это что-то вроде "тегов", принадлежащих постам).

Вернуться на верх