How to use GenericForeignKey in Django
Vera Mazhuga
Software DeveloperSometimes, 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,
}
Written by Vera Mazhuga
Vera specializes in writing and maintaining code for various applications. Her focus on problem-solving and efficient programming ensures reliable and effective software solutions.