【问题标题】:How to Model a Foreign Key in a Reusable Django App?如何在可重用的 Django 应用程序中建模外键?
【发布时间】:2009-09-14 02:24:10
【问题描述】:

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

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

通用外键看起来可能是答案,但实际上并非如此。我不希望链接能够与我网站中的任何模型相关联。只是我明确指定的那个。而且我从以前的经验中知道,在数据库使用方面使用通用外键可能会出现问题,因为您不能像使用常规外键那样对通用外键执行 select_related。

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

【问题讨论】:

    标签: python django django-models


    【解决方案1】:

    如果您认为链接应用程序将始终指向单个应用程序,那么一种方法是将外部模型的名称作为包含应用程序标签而不是类引用的字符串传递 (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'
    

    【讨论】:

    • 我忘记了 django 允许您为此使用字符串模型名称。 +1
    • 请注意,这种方法需要在可重用应用级别创建新的迁移。
    【解决方案2】:

    我认为 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')
    

    【讨论】:

    • 是的,我特别说我不想使用 GFK,因为那样我就不能做 blogpost.objects.all().select_related('links') 或类似的操作。
    【解决方案3】:

    解决此问题的另一种方法是django-mptt 这样做:仅在可重用应用程序 (MPTTModel) 中定义一个抽象模型,并需要通过定义一些字段来继承它(parent=ForeignKey to self,或任何您的应用程序用例将需要)

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

        我会选择泛型关系。你可以做一些类似 select_related 的事情,它只需要一些额外的工作。但我认为这是值得的。

        通用 select_related-like 功能的一种可能解决方案:

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

        (查看 GenericInjector 管理器和它的 inject_to 方法)

        【讨论】:

          【解决方案6】:

          这个问题和 Van Gale 的 answer 让我想到了一个问题,即如何可以限制 GFK 的内容类型,而无需通过模型中的 Q 对象定义它,因此它可以完全可重用

          解决方案基于

          • django.db.models.get_model
          • 和内置的 eval,它评估来自 settings.TAGGING_ALLOWED 的 Q-Object。这是在管理界面中使用所必需的

          我的代码很粗糙,没有经过全面测试

          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)
          

          【讨论】:

            猜你喜欢
            • 2011-04-16
            • 1970-01-01
            • 2010-10-08
            • 2011-01-03
            • 2011-08-22
            • 2012-09-15
            • 1970-01-01
            • 2016-08-01
            • 2021-12-16
            相关资源
            最近更新 更多