In this article, we'll look at how to limit content types when working with Django's generic relationships:
- How to limit the content types in a Django model?
- 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-
- The app's config
- 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.