Search code examples
djangodjango-modelsgeneric-foreign-keydjango-generic-relations

How does a Django GenericForeignKey relationship work?


I have read Django's documentation on GenericForeignKeys and found it to be very confusing. I don't understand how the relationship works, and how the variables I pass to it create said relationship - and everything I see only makes me more confused, because I still can't find anything clearly explaining, at its root, what it is and how it works.

Can someone please start from the beginning and explain what a GenericForeignKey is, and how the relationship works?


Solution

  • We have to start with understanding the limitations of a ForeignKey relationship; when creating a ForeignKey field in a model, the model which defines the object that a ForeignKey field will relate to has to be defined in the ForeignKey field.

    So for example, let's say we were making a social media platform where you could make posts and like posts, there would be a Post model and a Like model, and we would want each instance of the Like model (every time someone likes a post) to have a ForeignKey relationship to the object that was liked (i.e. the instance of the Post model that was liked). So we could do this:

    class Post(models.Model):
        post = models.TextField()
    
    class Like(models.Model):
        liked_post = models.ForeignKey(Post, on_delete=models.CASCADE)
    

    So the limitation of a ForeignKey relationship is that an instance of a model can only have a ForeignKey relationship with an instance of the model specified in the ForeignKey field.

    So let's say we had 2 types of models for posts; a TextPost model and an ImagePost model. We wouldn't be able to use a ForeignKey field in the Like model, because it can only be to an object which is an instance of 1 pre-specified model.

    This is where a GenericForeignKey field come in; essentially, it enables you to have a 'Foreign' relationship to an instance of any other model.

    But how does a GenericForeignKey field work? Let's examine it:

    from django.contrib.contenttypes.fields import GenericForeignKey
    from django.contrib.contenttypes.models import ContentType
    from django.db import models
    
        class TextPost(models.Model):
            post = models.TextField()
    
        class ImagePost(models.Model):
            post = upload = models.ImageField(upload_to ='uploads/')
    
    class Like(models.Model):
        post_content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
        post_id = models.PositiveIntegerField()
        post = GenericForeignKey("post_content_type", "post_id")
    

    Now, let's break this down. We define 2 variables, in this case post_content_type and post_id, and then we pass them to the GenericForeignKey field. So, let's first understand what we are defining and passing to the GenericForeignKey:

    1. post_content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)

    We are defining post_content_type as a ForeignKey field. However, this ForeignKey field is not to the model Post model; rather, we are creating a ForeignKey relationship to the instance of the ContentType model of whichever model we are making a GenericForeignKey relationship to.

    As explained here, an instance of the ContentType model is created for every model that you define in your models.py file (not for every time you create an instance of a model). So what you are doing is making a ForeignKey relationship between an instance of the Like model to the instance of the ContentType model corresponding to the Post model (as shown here, the ContentType instance for a model is <ContentType: app_name | model_name>. So let's say our models were in the 'user_posts' app, the TextPost model's ContentType instance would be <ContentType: user_posts | TextPost>, and the ImagePost model's ContentType instance would be <ContentType: user_posts | ImagePost>.

    1. post_id = models.PositiveIntegerField()

    Each instance of a model a model has a primary_key which is an integer. As explained here and here, if you make a model and do not specify a field to be the primary_key (using primary_key=True), Django creates a field called id by default. So we are defining post_id as a PositiveIntegerField, which will be set to the primary_key (meaning the id field) of the instance of the model that we are making a GenericForeignKey relationship to.

    post = GenericForeignKey("post_content_type", "post_id")

    We now define a GenericForeignKey field, and pass it the ForeignKey of the instance of the ContentType model that was made for the model that the object which we are making a GenericForeignKey relationship to is an instance of (in this case post_content_type), and the primary_key of the object that we are making a GenericForeignKey relationship to (in this case post_id).

    So now, to answer the question:

    Can someone please start from the beginning and explain what a GenericForeignKey is, and how it works?

    A GenericForeignKey is a field which lets any you have a 'Foreign' relationship to an instance of any model (i.e. a GenericForeign relationship), using:

    • A ForeignKey relationship to the instance of the ContentType model that corresponds to the model of which the object you are trying to make a relationship to is an instance of
    • The primary_key of the specific object you are making a relationship to