Insights

How to use GenericForeignKey in Django

Photo of the author: Vera Mazhuga

Vera Mazhuga

  •  

2 min read.

Sometimes, when the solution of the problem is associated with certain... uhm... difficulties, especially when you are still in the process of solving the; and after a splendid victory, comes the natural desire to tell the world what was actually needed be done. So let's look at how to deal with GenericForeignKey in Django ...

Let's suppose that we have three models: a page, an article and a survey. Although, there may be more, but the idea is to link a page to some survey or an article (in order to know where it must be shown).

from django.db import models

class Page(models.Model):
    name = models.CharField(max_length=255)
    # ...


class Article(models.Model):
    title = models.CharField(max_length=255)
    # ...


class Poll(models.Model):
    question = models.CharField(max_length=500)
    answers = # ...
    # ...

So we need our poll to have a "ForeignKey" to a page or an article. How will we relate the same object to different models? The answer is - through the contenttypes framework and generic relations.

Content Types Framework saves in a table contenttypes the information about models: name of corresponding application, name of a model and a type. The application django.contrib.contenttypes is added by default in INSTALLED_APPS. So we can obtain the ContentType object by an application name and name of a model:

from django.contrib.contenttypes.models import ContentType

user_type = ContentType.objects.get(app_label='auth', model='user')

or from the model class:

user_type = ContentType.objects.get_for_model(User)

or we can get the model knowing ContentType:

model = user_type.model_class()

and then request the object:

user_type.get_object_for_this_type(username='demo')

So using ContentType we can get a type of the model that we want to associate with a poll.

The solution will look like this:

from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic

class Poll(models.Model):
    question = models.CharField(max_length=500)

    answers = # ...

    # ...

    limit = models.Q(app_label='miapp', model='page') | \
        models.Q(app_label='miapp', model='article')

    content_type = models.ForeignKey(
        ContentType,
        verbose_name=_('content page'),
        limit_choices_to=limit,
        null=True,
        blank=True,
    )

    object_id = models.PositiveIntegerField(
        verbose_name=_('related object'),
        null=True,
    )

    content_object = generic.GenericForeignKey('content_type', 'object_id')

In object_id field we'll store an id of the related object (page or article), and using a limit field we can filter by models that we want to be associates with polls.

Not let's take a look on how to show it nicely in the admin interface. In order to not worry about autocomplete, we'll use Django Grappelli

class PollAdmin(admin.ModelAdmin):
    fieldsets = (
        (None, {
            'fields': (
                'question'
                # ...
            )
        }),
        (_('page/article'), {
            'classes': ('grp-collapse grp-open',),
            'fields': ('content_type', 'object_id', )
        }),
    )
    autocomplete_lookup_fields = {
        'generic': [['content_type', 'object_id']],
    }

Then to get the survey, depending on the object we have, we can use the following template tag:

from django import template
from django.contrib.contenttypes.models import ContentType

from miapp.models import Poll


register = template.Library()


@register.inclusion_tag('miapp/_poll.html')
def get_poll(related_object):
    related_object_type = ContentType.objects.get_for_model(related_object)
    poll = PollQuestion.objects.filter(
        content_type__pk=related_object_type.id,
        object_id=related_object.id,
    ).first()
    return {
        'poll': poll,
    }

Get an email once a month with the latest insights.

View emails sent

Learn more

Why you should use django-extensions

Working with Django is so cool, for example if you are dealing with repetitive and common tasks between projects you will be ...

Author Camilo Nova Camilo Nova

Own your tech to own your destiny