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

Как моделировать внешний ключ в многоразовом приложении Django?

На моем сайте django у меня есть два приложения, блог и ссылки. у блога есть блог-блог модели, а ссылки имеют ссылку на модель. Между этими двумя вещами должно быть одно-много отношений. Есть много ссылок на blogpost, но у каждой ссылки есть одно и только одно сообщение в блоге. Простой ответ - поставить ForeignKey на blogpost в модели ссылок.

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

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

Каков "правильный" способ моделирования этих отношений?

4b9b3361

Ответ 1

Если вы считаете, что приложение-ссылка всегда будет указывать на одно приложение, то одним из подходов было бы передать имя иностранной модели в виде строки, содержащей метку приложения, вместо ссылки на класс (объяснение Django docs).

Другими словами, вместо:

class Link(models.Model):
    blog_post = models.ForeignKey(BlogPost)

делать:

from django.conf import setings
class Link(models.Model):
    link_model = models.ForeignKey(settings.LINK_MODEL)

и в вашем settings.py:

LINK_MODEL = 'someproject.somemodel'

Ответ 2

Я думаю, что TokenMacGuy на правильном пути. Я бы посмотрел, как django-tagging обрабатывает аналогичные общие отношения, используя тип контента, generic object_id, и generic.py. Из models.py

class TaggedItem(models.Model):
    """
    Holds the relationship between a tag and the item being tagged.
    """
    tag          = models.ForeignKey(Tag, verbose_name=_('tag'), related_name='items')
    content_type = models.ForeignKey(ContentType, verbose_name=_('content type'))
    object_id    = models.PositiveIntegerField(_('object id'), db_index=True)
    object       = generic.GenericForeignKey('content_type', 'object_id')

    objects = TaggedItemManager()

    class Meta:
        # Enforce unique tag association per object
        unique_together = (('tag', 'content_type', 'object_id'),)
        verbose_name = _('tagged item')
        verbose_name_plural = _('tagged items')

Ответ 3

Более простой способ решить эту проблему: django-mptt делает следующее: определять только абстрактную модель в многоразовом приложении (MPTTModel) и требовать наследовать его с определением некоторых полей (parent = ForeignKey для себя или независимо от того, что потребуется для вашего приложения usecase)

Ответ 4

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

Ответ 5

Я бы пошел с родовыми отношениями. Вы можете сделать что-то вроде select_related, просто требуется дополнительная работа. Но я думаю, что это того стоит.

Одно из возможных решений для универсальной функции, связанной с select_related:

http://bitbucket.org/kmike/django-generic-images/src/tip/generic_utils/managers.py

(посмотрите на GenericInjector manager и метод inject_to)

Ответ 6

Этот вопрос и ответ Ван-Гейла отвечу на вопрос, как это возможно, ограничить типы контента для GFK без необходимости его определения через объекты Q в модель, поэтому ее можно было бы полностью использовать повторно

решение основано на

  • django.db.models.get_model
  • и встроенный eval, который оценивает Q-объект из settings.TAGGING_ALLOWED. Это необходимо для использования в админ-интерфейсе

Мой код довольно грубый и не полностью протестирован

settings.py

TAGGING_ALLOWED=('myapp.modela', 'myapp.modelb')

models.py:

from django.db import models
from django.db.models import Q
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from django.db.models import get_model
from django.conf import settings as s
from django.db import IntegrityError

TAGABLE = [get_model(i.split('.')[0],i.split('.')[1]) 
        for i in s.TAGGING_ALLOWED if type(i) is type('')]
print TAGABLE

TAGABLE_Q = eval( '|'.join(
    ["Q(name='%s', app_label='%s')"%(
        i.split('.')[1],i.split('.')[0]) for i in s.TAGGING_ALLOWED
    ]
))

class TaggedItem(models.Model):
    content_type = models.ForeignKey(ContentType, 
                    limit_choices_to = TAGABLE_Q)                               
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey('content_type', 'object_id')

    def save(self, force_insert=False, force_update=False):
        if self.content_object and not type(
            self.content_object) in TAGABLE:
            raise IntegrityError(
               'ContentType %s not allowed'%(
                type(kwargs['instance'].content_object)))
        super(TaggedItem,self).save(force_insert, force_update)

from django.db.models.signals import post_init
def post_init_action(sender, **kwargs):
    if kwargs['instance'].content_object and not type(
        kwargs['instance'].content_object) in TAGABLE:
        raise IntegrityError(
           'ContentType %s not allowed'%(
            type(kwargs['instance'].content_object)))

post_init.connect(post_init_action, sender= TaggedItem)

Конечно, ограничения в структуре contenttype влияют на это решение

# This will fail
>>> TaggedItem.objects.filter(content_object=a)
# This will also fail
>>> TaggedItem.objects.get(content_object=a)