Portafolio Que hacemos AX3 Tech Clientes Nosotros Blog Contacto

How to use GenericForeignKey in Django

Foto del autor: Vera Mazhuga

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,
    }

Image was taken from http://hubblesite.org/gallery/wallpaper/pr2006010a/

¿Qué esperas para crecer más?

Los consumidores hoy son digitales, quedarse obsoleto es la receta del fracaso. Comienza ahora tu transformación digital.

Carrera 13 # 98 - 70 OF 204
Bogotá - Colombia