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

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

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

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

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

6 ответов


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

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

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

do:

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

и в вашем settings.py:

LINK_MODEL = 'someproject.somemodel'

Я думаю, что TokenMacGuy находится на правильном пути. Я бы посмотрел, как django-пометка обрабатывает аналогичные общие отношения, используя тип содержимого, 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')

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


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


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

одно возможное решение для универсальной функции select_related-like:

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

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


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

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

  • Джанго.децибел.модели.get_model
  • и встроенный eval, который оценивает Q-объект из settings.TAGGING_ALLOWED. Это необходимо для использования в admin-interface

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

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-framework влияют на это решение

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