代码之家  ›  专栏  ›  技术社区  ›  Apreche

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

  •  14
  • Apreche  · 技术社区  · 15 年前

    在我的django网站上,我有两个应用程序,博客和链接。博客有一个模型blogpost,链接有一个模型链接。这两件事之间应该有一对多的关系。每个blog post有许多链接,但每个链接只有一个博客文章。简单的答案是在链接模型中放置blogpost的foreignkey。

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

    一个通用的外键似乎是答案,但不是真的。我不希望链接能够与我的网站中的任何模型关联。只是我明确指定的那个。我从以前的经验中知道,在数据库使用方面,使用通用外键可能会出现问题,因为您不能像使用普通外键那样对通用外键执行与选择相关的操作。

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

    6 回复  |  直到 13 年前
        1
  •  23
  •   Van Gale    15 年前

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

    在您的设置.py中:

    LINK_MODEL = 'someproject.somemodel'
    
        2
  •  1
  •   John Paulett    15 年前

    我认为tokenmacguy走对了。我会看看 django-tagging 使用内容类型、通用对象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')
    
        3
  •  1
  •   user920391    13 年前

    另一种解决方法是 django-mptt 这样做:只定义可重用应用程序(mptmodel)中的抽象模型,并要求通过定义一些字段(parent=foreignkey to self,或应用程序用例需要的任何内容)来继承它

        4
  •  0
  •   SingleNegationElimination    15 年前

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

        5
  •  0
  •   Mikhail Korobov    15 年前

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

    通用选择类功能的一种可能解决方案:

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

    (看看genericingetor管理器,它将注入方法)

        6
  •  0
  •   Community CDub    8 年前

    这个问题和范盖尔的 answer 我想问一个问题,如何限制gfk的contenttypes,而不需要通过模型中的q对象来定义它,所以它可以完全重用。

    解决方案基于

    • django.db.models.get_模型
    • 以及评估q对象的eval内置 settings.TAGGING_ALLOWED . 这对于在管理界面中使用是必需的

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

    设置Py

    TAGGING_ALLOWED=('myapp.modela', 'myapp.modelb')
    

    模型:

    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)