【问题标题】:Understanding Django GenericForeignKey and GenericRelation了解 Django GenericForeignKey 和 GenericRelation
【发布时间】:2016-10-20 07:57:24
【问题描述】:

我正在构建词汇并拥有以下模型:

class Word(Model):
    name = CharField(max_length=75)

class EnNoun(Model):
    word = OneToOneField(Word)

class FrNoun(Model):
    word = ForeignKey(Word)
    gender = CharField()

EnNounFrNoun 中都可以出现相同的单词。是否可以使用最少数量的查询为EnNounFrNoun 获取给定单词的结果(语言和词性会有更多类似的类,例如ItAdverb)?

如何存储从一种语言到另一种语言的翻译(不能查询 20 多个表)。

GenericForeign 键有用吗?我一般如何使用它们?

我有以下翻译课:

@python_2_unicode_compatible
class Translation(Model):
    from_content_type = ForeignKey(ContentType, related_name='from_word_content_type')
    from_object_id = UUIDField(default=uuid.uuid4)
    from_word = GenericForeignKey('from_content_type', 'from_object_id')

    to_content_type = ForeignKey(ContentType, related_name='to_word_content_type')
    to_object_id = UUIDField(default=uuid.uuid4)
    to_word = GenericForeignKey('to_content_type', 'to_object_id')

但它不起作用:

字段“to_word”不会生成自动反向关系,因此不能用于反向查询。如果是 GenericForeignKey,请考虑添加 GenericRelation。

【问题讨论】:

    标签: django django-models


    【解决方案1】:

    GenericForeignKey 尝试为您提供 ForeignKey 行为,但不是针对一种类型的对象,而是针对一组对象类型执行此操作(这就是为什么它们定义为 2 列,1 列以保留 primary_key 和另一个保留contenty_type)。

    GenericRelationGenericForeignKey 的反向关系,因为 Django 不会自动为 GenericForeignKeys 创建反向关系(与 ForeignKeys 不同),您必须手动设置它们。

    我对翻译/词汇人员的最佳实践不是很熟悉,但如果您想通过GenericRelationsGenericForeignKeys 解决问题,一种方法是:

    class Word(Model):
        name = CharField(max_length=75)
        nouns = GenericRelation('WordNoun', content_type_field='noun_ct', object_id_field='noun_id')
    
    class WordNoun(Model):
        word = ForeignKey(Word)
        noun_ct = ForeignKey(ContentType,
            on_delete=models.CASCADE,
            #this is optional
            limit_choices_to = {"model__in": ('EnNoun', 'FrNoun')}
        )
        noun_id = PositiveIntegerField()
        noun = GenericForeignKey('noun_ct', 'noun_id')
    
    class EnNoun(Model):
        word = OneToOneField(Word)
    
    class FrNoun(Model):
        word = ForeignKey(Word)
        gender = CharField()
    

    我们基本上是在创建一个保持词-名词关系的模型, 这个给如下

    # Having some word
    word = Word.objects.get(pk=1)
    
    # With 1 query you can get a list with
    # all WordNoun objects for this word.
    word_nouns = word.nouns.all()
    

    这种方法的问题在于,在你得到word_nouns 列表后, 访问单个 noun 实例将进行新查询。

    for word_noun in word.nouns.all():
        print word_noun.noun #this will make a query
    

    一种稍微优化的方法是使用prefetch_related,因此如果单个word 有3 个word_nouns(假设1 个EnNoun 和2 个FrNoun)。

    然后,我们将其优化为 3 个查询,而不是 4 个查询 - word_nouns 1 个和每个 noun 3 个查询 - word_nouns 1 个查询和每个 contenty_type 2 个查询(EnNoun 和 @987654347 @)

    for word_noun in word.nouns.all().prefetch_related('noun'):
        print word_noun.noun #this will not make a query
    

    不同之处在于查询的数量现在取决于不同的ContentTypes 的数量,而不是相关的WordNoun 对象的数量。

    当您在要预取的列表中有多个Nouns 和一个contenty_type 时,这可以很好地扩展,但如果您有1 个Noun percontenty_type 则不会有任何区别。

    我能想到的另一种方法是使用以下模型结构:

    class Word(Model):
        name = CharField(max_length=75)
    
    class WordNoun(Model):
        LANG_CHOICES = (
            ('en', 'English'),
            ('fr', 'French'),
        )
        word = ForeignKey(Word)
        lang = models.CharField(max_length=2, choices=LANG_CHOICES)
        gender = CharField(max_length=2, blank=True, default='')
    
    
    #Now accessing all word_nouns would as easy as:
    word_nouns = word.wordnoun_set.all()
    

    【讨论】:

    • 好的。谢谢你。我认为在这种情况下 GenericForeignKey 效率不高。但我现在正在做翻译表。我认为是这样。查看更新
    • 当您收到此to_word 错误时?好像您正在尝试进行某种过滤?如果您只对from_wordto_word 属性使用Word 类,那么您可以安全地使用普通ForeignKey 而不是GenericForeignKey
    • 我使用了 get_or_create。如何对通用键进行过滤?
    • 如果您将 related_query_name 添加到 GenericRelation,请检查 docs。但是,您不能在.filter() 中直接在docs 中使用to_wordfrom_word,这就是get_or_create 正在做的事情(试图过滤),这就是您收到错误的原因。对于get_or_create.filter(),您必须使用content_typeobject_id 字段。
    【解决方案2】:

    可以在同一个模型中添加多个GenericForeignKey。这将允许您在具有泛型类型的对象之间创建“链接”。后面我举个例子。我修改了您的示例以创建对我来说更有用的东西。表Translation 在法语和英语单词之间创建链接。单词存储在EnVerbFrVerbEnNounFrNoun 类中。如果没有GenericForeignKey,您将不得不创建两个翻译模型:TranslationVerbTranslationNoun。但在下面的代码中,我展示了一个适用于动词和名词的通用翻译模型。我希望这是有道理的!

    从技术上讲,您忘记添加 GenericRelation 字段。此外,他们必须在相关对象的GenericRelation 字段中指定字段content_typeobject_id 的新名称。

    示例

    from django.db import models
    from django.contrib.contenttypes.fields import GenericRelation
    from django.contrib.contenttypes.fields import GenericForeignKey
    from django.contrib.contenttypes.models import ContentType
    
    Translation(models.Model):
        fr_content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, related_name="fr_content_type")
        fr_object_id = models.PositiveIntegerField()
        fr_word = GenericForeignKey('fr_content_type', 'fr_object_id')
    
        en_content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, related_name="en_content_type")
        en_object_id = models.PositiveIntegerField()
        en_word = GenericForeignKey('en_content_type', 'en_object_id')
    
    class FrVerb(models.Model):
        name = models.CharField(max_length=75)
        translation = GenericRelation(Translation, content_type_field='fr_content_type', object_id_field='fr_object_id')
    
    class FrNoun(models.Model):
        name = models.CharField(max_length=75)
        gender = models.CharField(max_length=75)
        translation = GenericRelation(Translation, content_type_field='fr_content_type', object_id_field='fr_object_id')
    
    class EnVerb(models.Model):
        name = models.CharField(max_length=75)
        translation = GenericRelation(Translation, content_type_field='en_content_type', object_id_field='en_object_id')
    
    class EnNoun(models.Model):
        name = models.CharField(max_length=75)
        translation = GenericRelation(Translation, content_type_field='en_content_type', object_id_field='en_object_id')
    

    您可以使用它来创建模型之间的通用转换:

    from polls.models import *      
    
    EnNoun(name='tree').save()                                                                                                                       
    FrNoun(name='arbre').save()                                                                                                                      
    EnVerb(name='be').save()                                                                                                                         
    FrVerb(name='etre').save()                                                                                                                       
    
    trans1 = Translation(fr_word=FrNoun.objects.first(), en_word=EnNoun.objects.first())                                                              
    trans2 = Translation(fr_word=FrVerb.objects.first(), en_word=EnVerb.objects.first())
    

    现在trans1 用于链接“tree”和“arbre”,trans2 用于链接“be”和“être”,而这些对象属于不同的模型!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-01-01
      • 1970-01-01
      • 2021-09-30
      • 2012-12-04
      • 1970-01-01
      • 1970-01-01
      • 2014-04-09
      • 1970-01-01
      相关资源
      最近更新 更多