Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding Django GenericForeignKey and GenericRelation

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.

like image 534
Andrew Fount Avatar asked Oct 20 '16 07:10

Andrew Fount


People also ask

What is Genericrelation in Django?

Basically it's a built in app that keeps track of models from the installed apps of your Django application. And one of the use cases of the ContentTypes is to create generic relationships between models.

How does Django Contenttypes work?

Django includes a contenttypes application that can track all of the models installed in your Django-powered project, providing a high-level, generic interface for working with your models.

What is generic relation?

Generic relationships are abstraction patterns used for structuring information across application domains. They play a central role in information modeling.


1 Answers

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 percontenty_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()
like image 80
Todor Avatar answered Oct 14 '22 05:10

Todor