How can I enforce inheritance in my Django models?
Question:
In my Django app, I have an abstract model called MyModel
that has e.g. created_at
and updated_at
fields. I want all the models in my project to subclass MyModel
rather than using django.db.models.Model
directly.
We have several developers on our app, so I want to use some sort of linter or CI check to enforce that this happens. How can I do this?
Answers:
As mentioned by Willem Van Onsem in their comment you can write your own checks using the System check framework.
Assuming we have the following models in an app named "checktest" with Parent
being the model that all models should inherit from:
from django.db import models
class Parent(models.Model):
class Meta:
abstract = True
# This should raise an error
class Foo(models.Model):
pass
# This should be fine
class Bar(Parent):
pass
We’ll write a check as follows in a file checktest/custom_checks.py
, note that the list APPS_TO_TEST
contains the names of the apps whose models should inherit from the parent class:
from django.apps import apps
from django.core.checks import Error, register, Tags
from .models import Parent
# List of apps that should inherit from Parent
APPS_TO_TEST = ["checktest"]
@register(Tags.models)
def model_must_inherit(app_configs, **kwargs):
errors = []
for app in APPS_TO_TEST:
models = apps.get_app_config(app).get_models()
for model in models:
if not issubclass(model, Parent):
errors.append(Error(
f"Model {model.__name__} does not inherit from Parent",
hint="Models must inherit from the Parent class",
obj=model,
id="checktest.E001"
))
return errors
In the app configs ready
method we’ll import the above file so that the check will get run:
from django.apps import AppConfig
class ChecktestConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'checktest'
def ready(self) -> None:
from . import custom_checks
Now whenever we run commands like runserver
or migrate
the checks will get implicitly run. In a CI environment you can explicitly run the checks using the check command.
In my Django app, I have an abstract model called MyModel
that has e.g. created_at
and updated_at
fields. I want all the models in my project to subclass MyModel
rather than using django.db.models.Model
directly.
We have several developers on our app, so I want to use some sort of linter or CI check to enforce that this happens. How can I do this?
As mentioned by Willem Van Onsem in their comment you can write your own checks using the System check framework.
Assuming we have the following models in an app named "checktest" with Parent
being the model that all models should inherit from:
from django.db import models
class Parent(models.Model):
class Meta:
abstract = True
# This should raise an error
class Foo(models.Model):
pass
# This should be fine
class Bar(Parent):
pass
We’ll write a check as follows in a file checktest/custom_checks.py
, note that the list APPS_TO_TEST
contains the names of the apps whose models should inherit from the parent class:
from django.apps import apps
from django.core.checks import Error, register, Tags
from .models import Parent
# List of apps that should inherit from Parent
APPS_TO_TEST = ["checktest"]
@register(Tags.models)
def model_must_inherit(app_configs, **kwargs):
errors = []
for app in APPS_TO_TEST:
models = apps.get_app_config(app).get_models()
for model in models:
if not issubclass(model, Parent):
errors.append(Error(
f"Model {model.__name__} does not inherit from Parent",
hint="Models must inherit from the Parent class",
obj=model,
id="checktest.E001"
))
return errors
In the app configs ready
method we’ll import the above file so that the check will get run:
from django.apps import AppConfig
class ChecktestConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'checktest'
def ready(self) -> None:
from . import custom_checks
Now whenever we run commands like runserver
or migrate
the checks will get implicitly run. In a CI environment you can explicitly run the checks using the check command.