如何在可重用的Django应用程序中建模外键?

时间:2009-09-14 02:24:11

标签: python django django-models

在我的django网站中,我有两个应用程序,博客和链接。博客有博客模型,链接有模型链接。这两件事之间应该有一对多的关系。每个博客帖子有很多链接,但每个链接只有一篇博文。简单的答案是在链接模型中将ForeignKey放到blogpost中。

这一切都很好,但有一个问题。我想让链接应用程序可重用。我不希望它依赖于博客应用程序。我希望能够在其他网站中再次使用它,并可能将链接与其他非博客应用和模型相关联。

通用外键似乎可能是答案,但不是真的。我不希望链接能够与我的网站中的任何模型相关联。只是我明确指定的那个。我从之前的经验中了解到,在数据库使用方面可能存在使用通用外键的问题,因为您不能像使用常规外键那样对通用外键执行select_related。

建立这种关系的“正确”方法是什么?

6 个答案:

答案 0 :(得分:23)

如果您认为链接应用程序始终指向单个应用程序,那么一种方法是将外部模型的名称作为包含应用程序标签的字符串而不是类引用(Django docs explanation)传递。 / p>

换句话说,而不是:

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'

答案 1 :(得分:1)

我认为TokenMacGuy走在正确的轨道上。我将看看django-tagging如何使用内容类型,通用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')

答案 2 :(得分:1)

Anoher解决这个问题的方法是django-mptt如何做到这一点:在可重用的应用程序(MPTTModel)中只定义一个抽象模型,并且需要通过定义一些字段来继承它(parent = ForeignKey to self,或者任何你的app usecase将要求)

答案 3 :(得分:0)

您可能需要使用内容类型应用链接到模型。然后,您可以安排您的应用检查设置,以进行一些额外的检查,以限制它接受或建议的内容类型。

答案 4 :(得分:0)

我会选择通用关系。您可以执行select_related之类的操作,只需要一些额外的工作。但我认为这是值得的。

类似通用select_related功能的一种可能解决方案:

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

(查看GenericInjector管理器,它是inject_to方法)

答案 5 :(得分:0)

这个问题和Van Gale的answer引出了一个问题,即如何可能限制GFK的内容类型,而不需要通过模型中的Q对象来定义它,因此它可以完全重复使用< / p>

解决方案基于

  • django.db.models.get_model
  • 和eval内置,用于评估来自settings.TAGGING_ALLOWED的Q-Object。这是在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)