Skip to main content

A Django package to enforce choices as database constraints.

Project description

Django-enforced-choices

PyPi version Documentation Status PyPi license Code style: black

Django does not enforce choices at the model level: yes a ModelForm can validate these, but unfortunately people oftend define form fields themselves or even worse don't work with any Form or Serializer at all. So this means that if we define a model:

class Movie(models.Model):
    genre = models.CharField(max_length=1, choices=[('d', 'drama'), ('h', 'horror')])

The database will not enforce that one can only pick 'd' or 'h' as genre. A simple solution might be to encapsulate the item in a separate model, and then the FOREIGN KEY constraint will take care of this:

class Genre(models.Model):
  id = models.CharField(max_length=1, primary_key=True)
  name = models.CharField(max_length=128)


class Movie(models.Model):
  genre = models.ForeignKey(Genre, on_delete=models.PROTECT)

we can then fill the table with a data migration for example. This also makes some sense: a quote I once heared is that there should only be three constants in programming: zero, one and infinity, meaning that typically one should not restrict the number of genres.

But regardless, people often use the former model. This is good if we work with Forms, or Serializers, but models don't (eagerly) validate data, and even if they did, the database does not know about the choices, and thus will happily accept 's' as value (for example for science fiction).

What does this package provide?

This package provides two mixins:

  • ChoicesConstraintModelMixin which checks on the choices= parameter of the fields of that model, and based on that, creates a constraint to enforce the choices at the datbase side; and
  • RangeConstraintModelMixin, which checks the validators= parameter of the fields of that model, and based on that, creates a constraint to enforce that at the database side.

You can combine the mixins that are defined with FullChoicesConstraintModelMixin, it is probably advisable to use FullChoicesConstraintModelMixin over the mixins defined above, since as more validators are enforced , it will automatically add more constraints for these models, whereas ChoicesConstraintModelMixin for example, will only limit itself to choices.

One can exclude certain fields with the exclude_choice_check_fields and exclude_range_check_fields attributes that you can alter in the model. These need to provide a collection of strings that contain the name of the field.

Another option is to import the correspond field from the django_enforced_choices.fields module, or django_enforced_choices.fields.postgres for PostgreSQL-specific fields. This will, by default, also check if the fields have choices, but we do not recommend to use these, since this means the field has for example as type ChoiceCharField, and certain Django functionalities (and packages) sometimes check full type equality to determine a widget, not through an instanceof. This thus means that certain functionalities might no longer work as intended.

Usage

One can import the ChoicesConstraintModelMixin and mix it into a model, like:

from django.core.validators import MaxValuevalidator, MinValueValidator 
from django_enforced_choices.models import FullChoicesConstraintModelMixin

class Movie(FullChoicesConstraintModelMixin, models.Model):
    genre = models.CharField(max_length=1, choices=[('d', 'drama'), ('h', 'horror')])
    year = models.IntegerField(validators=[MinValueValidator(1888)])

this will then add CheckConstraints to the model to enforce that genre only can contain 'd' and 'h' at the database side, and that the year is greater than or equal to 1888.

How does the package work?

For the fields defined, it will check if the choices and validators are defined. If that is the case, it will create a CheckConstraint with fieldname__in with the keys in the choices for choices, and __range, __lte or __gte for ranges depending on what values are picked.

If the field is NULLable, it will also allow NULL/None to be used.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django-enforced-choices-0.2.0.tar.gz (14.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_enforced_choices-0.2.0-py3-none-any.whl (8.0 kB view details)

Uploaded Python 3

File details

Details for the file django-enforced-choices-0.2.0.tar.gz.

File metadata

  • Download URL: django-enforced-choices-0.2.0.tar.gz
  • Upload date:
  • Size: 14.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.0.0 CPython/3.12.2

File hashes

Hashes for django-enforced-choices-0.2.0.tar.gz
Algorithm Hash digest
SHA256 4c713952ad94f4ce7c5da8c9edc8476e441316bc428c2a4e08734d7c04966c2e
MD5 771098f6ffb842099f07b6500ab7dacb
BLAKE2b-256 9796543ba682edae0073f137bfab5679c02bb6f7a4d7e0109029676313cc360a

See more details on using hashes here.

File details

Details for the file django_enforced_choices-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for django_enforced_choices-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9d64721d16155b0295dbd7d56955537300c1a60e5e1ba08f5cbf866c78827fd2
MD5 c32fd0b82a775999f097facf5073ebc6
BLAKE2b-256 a5e16ae77c7bf3c87ab96e0b5859a4cabf2f36cd27355d23ffa62e9a8aa3f201

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page