Limiting Content Types in a Django Model

Last updated August 19th, 2024

In this article, we'll look at how to limit content types when working with Django's generic relationships:

  1. How to limit the content types in a Django model?
  2. How to restrict Django's generic ForeignKey to a list of models?

Contents

Introduction

Django's built-in content types framework lets you track all the models installed in your project. At its core, the framework provides the ContentType model and automatically creates a ContentType instance for each of your installed models.

These ContentType instances store your model information and provide methods for obtaining instances of their corresponding models -- e.g. get_object_for_this_type().

Furthermore, the framework allows you to implement "generic" relationships, which are relationships between an instance of one of your models and any other model. For example, by using generic relationships, you can create a unified tagging or commenting system that works across all of your models.

However, with most unified systems, you'll typically want to restrict them to a subset of your models. This article looks at just that.

Limit Content Types

Suppose we're working on a car dealership web app. The dealership sells three types of vehicles: cars, electric cars, and motorcycles. As a result, we have three Django models for vehicles and another one to keep track of vehicle sales.

The models look something like this:

class Car(models.Model):
    # ...


class ElectricCar(models.Model):
    # ...


class Motorcycle(models.Model):
    # ...


class Sale(models.Model):
    content_type = models.ForeignKey(
        ContentType,
        on_delete=models.CASCADE,
    )
    object_id = models.PositiveIntegerField()
    # ...

While this allows us to associate a sale with an instance of a vehicle via content_type and object_id, it is not perfect. By not restricting the content types, a user could technically also associate a sale with a non-vehicle model -- e.g., auth.user.

Let's fix this.

Firstly, define a list of content type choices like so:

from django.db.models import Q

from dealership.apps import DealershipConfig


CONTENT_TYPE_CHOICES = (
    Q(app_label=DealershipConfig.name, model=Car.__name__.lower())
    | Q(app_label=DealershipConfig.name, model=ElectricCar.__name__.lower())
    | Q(app_label=DealershipConfig.name, model=Motorcycle.__name__.lower())
)

Alternatively, you can hardcode the app labels and model names. This is useful when working with third-party apps where you don't have direct access to the app's config. For example:

Q(app_label="dealership", model="car")

Keep in mind that the app label and model names should always be in lowercase. For example, if your model is named FooBarFaz, the model name should be foobarfaz.

If you're unsure about the app or model names, you can list all the registered content types using the Django shell:

from django.contrib.contenttypes.models import ContentType

for content_type in ContentType.objects.all():
    print(content_type.app_label, content_type.model)

Next, set ForeignKey's limit_choices_to option to the just defined list:

class Sale(models.Model):
    content_type = models.ForeignKey(
        ContentType,
        on_delete=models.CASCADE,
        limit_choices_to=CONTENT_TYPE_CHOICES,
    )
    # ...

Make migrations, migrate, and test.

The content types are now be limited to Car, ElectricCar, and Motorcycle. Moreover, the admin panel will only display the limited subset of content types.

Conclusion

In summary, we've looked at how to limit content types in a Django model.

The easiest way to limit the content types is by using ForeignKey's limit_choices_to option. When specifying the subset of content types you can use-

  1. The app's config
  2. The model's config

Or you can hardcode the values. If you decide to hardcode, make the app and model name lowercase.

The complete example is available on GitHub.

Nik Tomazic

Nik Tomazic

Nik is a software developer from Slovenia. He's interested in object-oriented programming and web development. He likes learning new things and accepting new challenges. When he's not coding, Nik's either swimming or watching movies.

Share this tutorial

Featured Course

Test-Driven Development with Django, Django REST Framework, and Docker

In this course, you'll learn how to set up a development environment with Docker in order to build and deploy a RESTful API powered by Python, Django, and Django REST Framework.

Featured Course

Test-Driven Development with Django, Django REST Framework, and Docker

In this course, you'll learn how to set up a development environment with Docker in order to build and deploy a RESTful API powered by Python, Django, and Django REST Framework.