问题
I'm building vocabulary and have following models:
class Word(Model):
name = CharField(max_length=75)
class EnNoun(Model):
word = OneToOneField(Word)
class FrNoun(Model):
word = ForeignKey(Word)
gender = CharField()
Same word can be in both EnNoun
and FrNoun
. Is it possible to fetch result for given word for both EnNoun
and FrNoun
, using the least number of queries (there will be more similar classes for language and part of speech, like ItAdverb
)?
How to store translation from one lang to another (querying 20+ tables is not an option).
Are GenericForeign
keys of any use? How can I use them in general?
I have the following translation class:
@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')
But it doesn't work:
Field 'to_word' does not generate an automatic reverse relation and therefore cannot be used for reverse querying. If it is a GenericForeignKey, consider adding a GenericRelation.
回答1:
GenericForeignKey try to give you a ForeignKey
behavior but instead to be against one type of object, they do it for a set of object types (thats why they are defined with 2 columns, 1 to keep the primary_key
and another to keep the contenty_type
).
GenericRelation
is the reverse relation of a GenericForeignKey
, because Django do not automatically create reverse relations for GenericForeignKeys
(unlike ForeignKeys
) you have to setup them manually.
I'm not very familiar with the best-practices in translations/vocabulary staffs, but if you want to approach the problem with GenericRelations
and GenericForeignKeys
, one way to do it would be:
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()
We are basically creating a model keeping word-noun relations, this give is the following
# 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()
The problem with this approach is that after you get the word_nouns
list,
accessing a single noun
instance will make a new query.
for word_noun in word.nouns.all():
print word_noun.noun #this will make a query
One way to slightly optimize this is to use prefetch_related
, so if a single word
has 3 word_nouns
(lets say 1 EnNoun
and 2 FrNoun
).
Then instead of 4 queries - 1 for the word_nouns
and 3 for each noun
, we optimize it to 3 queries - 1 for the word_nouns
and 2 for each contenty_type
(EnNoun
and FrNoun
)
for word_noun in word.nouns.all().prefetch_related('noun'):
print word_noun.noun #this will not make a query
The difference is that the number of queries will now depends on the number of different ContentTypes
, rather than the number of related WordNoun
objects.
This scales nice when you have several Nouns
from one contenty_type
in the list you are prefetching, but will make no difference if you have 1 Noun per
contenty_type`.
Another approach which I can think of is to use the following model structure:
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()
回答2:
It is possible to add several GenericForeignKey
to the same model. This would allow you to create a "link" between objects with generic type. I give an example latter. I modified your example to create something more useful in my sense. The table Translation
create links between French and English words. Words are stored in classes EnVerb
, FrVerb
, EnNoun
and FrNoun
. Without GenericForeignKey
, you would have to create two translation models: TranslationVerb
and TranslationNoun
. But in the following code, I show a generic translation model working for both verbs, and nouns. I hope it makes sense!
More technically, you forgot to add the GenericRelation
field. Also, they must specify the new names of the fields content_type
and object_id
in the GenericRelation
field of the related objects.
EXAMPLE
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')
You can use this to create generic translations between models :
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())
Now trans1
is used to link 'tree' and 'arbre' and trans2
to link 'be' et 'être' while these objects belongs to different models !
来源:https://stackoverflow.com/questions/40148630/understanding-django-genericforeignkey-and-genericrelation