Singleton Model in Django

· 2min

My understanding of a singleton model is one which allows you to insert only a single record to the table. This is usually used for models that hold settings.

Another form of this idea is the representation of a model where only one instance can be set as 'active' or 'default'

There are a number of ways to achieve this, but the way I used recently, which uses GeneratedField from Django 5.0 felt more elegant to me. Here it is:

for settings:

# models.py
from django.db import models
from django.db.models.fields.generated import GeneratedField

class Settings(models.Model):
    send_emails = models.BooleanField(default=False)
    min_score = models.PositiveSmallIntegerField(default=0, blank=True)
    _key = GeneratedField(
        expression=models.Value("a"), # can be any constant value
        output_field=models.CharField(),
        db_persist=True,
        unique=True,
    )

Thats all. After a record is created, subsequent creation attempts will fail due to the unique constraint. Next, for cases where there can be only one default/active record:

only one active/default:

# models.py
from django.db import models
from django.db.models.fields.generated import GeneratedField
from django.db.models.functions import NullIf

class Settings(models.Model):
    name = models.CharField(max_length=250)
    is_active = models.BooleanField(default=False, blank=True)
    _is_active = GeneratedField(
        expression=NullIf(models.F("is_active"), models.Value(False)),
        output_field=models.BooleanField(),
        db_persist=True,
    )

    class Meta:
        constraints = [
            models.UniqueConstraint("_is_active", name="unique_is_active_true"),
        ]

This exploits the way unique constraints are ignored when a column is NULL. NullIf sets _is_active to NULL when is_active is False. So the unique constraint is only active when is_active is True. Hence multiple records with is_active set to False can be allowed, while only one record with is_active set to True is allowed.

PS:

Different databases have different requirements for GeneratedField, which should be reviewed carefully beforehand