2009-09-14 1 views
12

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

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

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

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

ответ

22

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

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

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' 
+0

Я забыл, что django позволяет использовать имена строковых моделей для этого. +1 – SingleNegationElimination

+0

О, ничего себе, отличная идея использовать настройки. Благодаря! – Apreche

+0

Обратите внимание, что для этого подхода потребуются новые миграции на уровне многоразового использования. – Bula

0

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

1

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

Да, я специально сказал, что не хочу использовать GFK, потому что тогда я не могу делать blogpost.objects.all(). select_related ('links') или эквивалентный. – Apreche

0

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

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

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

(смотреть на менеджера GenericInjector и это inject_to метод)

0

Этот вопрос Ван Гейла answer привести меня к вопросу, как возможно, было возможно ограничить типы контента для 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) 
1

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

Смежные вопросы