The Django content types framework lets you have Generic Foreign Key fields, which allow you to create a foreign key to any object (record) in your database by combining the object id with an identifier for the table in which the object lives.

This is great, until you want to make it editable in a form. The GenericForeignKey reference is stored as 2 separate fields (object id and type id), and so when you view this in a standard model form you get a drop down of content types, and a text field in which to type the id of the object that you want. This is fairly meaningless to a user. Even if you know what the 2 fields mean, you still need a way of looking up the id of the object that you want.

Screenshot of Django generic foreign key form fields

So we want to be able to combine these 2 fields into a single, meaningful field, that lets the user simply select which object they want.

The context in which I wrote this code is rather complex, and not worth going into here. So what is below is a simplified version, using an imaginary AttachableNote model as an example of something which may have a generic foriegn key to link itself to any other object in the site.


import re
from django import forms
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic class AttachableNote(models.Model):
""" A model which stores a text note.
It can be attached to any object via its GenericForeignKey field.
"""
text = models.TextField()
object_id = models.PositiveIntegerField()
object_type = models.ForeignKey(ContentType)
generic_obj = generic.GenericForeignKey('object_type', 'object_id') class AttachableNoteForm(forms.ModelForm):
""" Form for creating an AttachableNote. """ #GenericForeignKey form field, will hold combined object_type and object_id
generic_obj = forms.ChoiceField() def __init__(self, *args, **kwargs):
super(AttachableNoteForm, self).__init__(*args, **kwargs)
#combine object_type and object_id into a single 'generic_obj' field
#getall the objects that we want the user to be able to choose from
available_objects = list(SomeModel.objects.all()) #put your stuff here
available_objects += list(SomeOtherModel.objects.filter(field=value))
#now create our list of choices for the <select> field
object_choices = []
for obj in available_objects:
type_id = ContentType.objects.get_for_model(obj.__class__).id
obj_id = obj.id
form_value = "type:%s-id:%s" % (type_id, obj_id) #e.g."type:12-id:3"
display_text = str(obj)
object_choices.append([form_value, display_text])
self.fields['generic_obj'].choices = object_choices class Meta:
model = AttachableNote
fields = [
"text",
"generic_obj"
] def save(self, *args, **kwargs):
#get object_type and object_id values from combined generic_obj field
object_string = self.cleaned_data['generic_obj']
matches = re.match("type:(\d+)-id:(\d+)", object_string).groups()
object_type_id = matches[0] #get 45 from "type:45-id:38"
object_id = matches[1] #get 38 from "type:45-id:38"
object_type = ContentType.objects.get(id=object_type_id)
self.cleaned_data['object_type'] = object_type_id
self.cleaned_data['object_id'] = object_id
self.instance.object_id = object_id
self.instance.object_type = object_type
return super(AttachableNoteForm, self).save(*args, **kwargs)

One Response to “Displaying Django GenericForeignKey As Single Form Field”

  1. Dmitri says:

    Thank you for your solution

Leave a Reply