Как смоделировать внешний ключ в повторно используемом приложении Django? - PullRequest
14 голосов
/ 14 сентября 2009

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

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

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

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

Ответы [ 6 ]

23 голосов
/ 14 сентября 2009

Если вы думаете, что приложение ссылки всегда будет указывать на одно приложение, тогда одним из подходов будет передача имени внешней модели в виде строки, содержащей метку приложения вместо ссылки на класс ( Объяснение 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)

и в ваших настройках.py:

LINK_MODEL = 'someproject.somemodel'
1 голос
/ 30 августа 2011

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

1 голос
/ 14 сентября 2009

Я думаю, что 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')
0 голосов
/ 15 сентября 2009

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

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

  • django.db.models.get_model
  • и встроенный eval, который оценивает Q-Object из 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-framework влияют на это решение

# This will fail
>>> TaggedItem.objects.filter(content_object=a)
# This will also fail
>>> TaggedItem.objects.get(content_object=a)
0 голосов
/ 14 сентября 2009

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

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

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

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

0 голосов
/ 14 сентября 2009

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

...