Portafolio Servicios Nosotros Cobuild 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,
}


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

Martes es tu día para empezar

Hemos ayudado cientos de empresas a transformar su negocio. ¡Comencemos a transformar el tuyo!

Calle 100 # 14 - 63 OF 801
Bogotá - Colombia