[Fixed]-How to Model a Foreign Key in a Reusable Django App?



If you think the link app will always point to a single app then one approach would be to pass the name of the foreign model as a string containing the application label instead of a class reference (Django docs explanation).

In other words, instead of:

class Link(models.Model):
    blog_post = models.ForeignKey(BlogPost)


from django.conf import setings
class Link(models.Model):
    link_model = models.ForeignKey(settings.LINK_MODEL)

and in your settings.py:

LINK_MODEL = 'someproject.somemodel'
πŸ‘€Van Gale


I think TokenMacGuy is on the right track. I would look at how django-tagging handles a similar generic relationship using the content type, generic object_id, and generic.py. From models.py

class TaggedItem(models.Model):
    Holds the relationship between a tag and the item being tagged.
    tag          = models.ForeignKey(Tag, verbose_name=_('tag'), related_name='items')
    content_type = models.ForeignKey(ContentType, verbose_name=_('content type'))
    object_id    = models.PositiveIntegerField(_('object id'), db_index=True)
    object       = generic.GenericForeignKey('content_type', 'object_id')

    objects = TaggedItemManager()

    class Meta:
        # Enforce unique tag association per object
        unique_together = (('tag', 'content_type', 'object_id'),)
        verbose_name = _('tagged item')
        verbose_name_plural = _('tagged items')
πŸ‘€John Paulett


Anoher way to solve this is how django-mptt does this: define only an abstract model in a reusable app(MPTTModel), and require to inherit it with defining some fields (parent=ForeignKey to self, or whatever your app usecase will require)



Probably you need to use the content types app to link to a model. You might then arrange for your app to check the settings to do some additional checking to limit which content types it will accept or suggest.


I’d go with generic relations. You can do something like select_related, it just require some extra work. But I think it’s worth it.

One possible solution for generic select_related-like functionality:


(look at GenericInjector manager and it’s inject_to method)


This question and Van Gale’s answer lead me to the question, how it could be possible, to limit contenttypes for GFK without the need of defining it via Q objects in the model, so it could be completly reuseable

the solution is based on

  • django.db.models.get_model
  • and the eval built-in, that evaluates a Q-Object from settings.TAGGING_ALLOWED. This is necessary for usage in the admin-interface

My code is quite rough and not fully tested


TAGGING_ALLOWED=('myapp.modela', 'myapp.modelb')


from django.db import models
from django.db.models import Q
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from django.db.models import get_model
from django.conf import settings as s
from django.db import IntegrityError

TAGABLE = [get_model(i.split('.')[0],i.split('.')[1]) 
        for i in s.TAGGING_ALLOWED if type(i) is type('')]

TAGABLE_Q = eval( '|'.join(
    ["Q(name='%s', app_label='%s')"%(
        i.split('.')[1],i.split('.')[0]) for i in s.TAGGING_ALLOWED

class TaggedItem(models.Model):
    content_type = models.ForeignKey(ContentType, 
                    limit_choices_to = TAGABLE_Q)                               
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey('content_type', 'object_id')

    def save(self, force_insert=False, force_update=False):
        if self.content_object and not type(
            self.content_object) in TAGABLE:
            raise IntegrityError(
               'ContentType %s not allowed'%(
        super(TaggedItem,self).save(force_insert, force_update)

from django.db.models.signals import post_init
def post_init_action(sender, **kwargs):
    if kwargs['instance'].content_object and not type(
        kwargs['instance'].content_object) in TAGABLE:
        raise IntegrityError(
           'ContentType %s not allowed'%(

post_init.connect(post_init_action, sender= TaggedItem)

Of course the limitations of the contenttype-framework affect this solution

# This will fail
>>> TaggedItem.objects.filter(content_object=a)
# This will also fail
>>> TaggedItem.objects.get(content_object=a)

Leave a comment