Search code examples
djangopostgresql

Why can I save a django model instance without defining all non-null fields


When I define a non nullable field in django it allows me to save a model instance without specifying any value for this non-nullable field. This is not what I would expect. How can I force this to yield an error?

Postgres 9.1 django 2.1 windows python 3.6

from django.db import models
class Wwtp(models.Model):
    name = models.CharField(max_length=100, null=False,
                            blank=False, unique=True)
    short_name = models.CharField(
        max_length=10, null=False, blank=False, unique=True)

As expected, I am not allowed to save it with an explicit empty short_name.

mdl.Wwtp.objects.create(name='Wwtp4', short_name=None)

But I am allowed to save an instance of Wwtp without specifying short_name:

mdl.Wwtp.objects.create(name='Wwtp4')

and when I try:

mdl.Wwtp.objects.create()

it gives me

django.db.utils.IntegrityError: duplicate key value violates unique constraint "api_wwtp_short_name_key"
DETAIL:  Key (short_name)=() already exists.

Apparently django filled the database with an empty value for short_name while it is not allowed to do so... How can I force the database to not allow this?


Solution

  • You can't with CharField. The empty value is an empty string '', not NULL. You already have blank=False set, so if you clean your model or model forms before saving them, you'll catch that. But it cannot be enforced at the database level.

    Note that blank=False, null=False is the default, so you really don't have to specify that.

    Also, if you really only want to support PostgreSQL, you could make a custom migration using RunSQL to create your column on the table, manually adding the SQL needed to add the constraint (e.g. using CHECK). See here for how to ensure Django also knows the column was created and doesn't try to add it in the next migration. There's an example here.

    [Edit] In Django 2.2, you can add a CheckConstraint in the model's Meta class constraints attribute:

    from django.db.models import CheckConstraint, Q
    
        (...)
        class Meta:
            constraints = [
                CheckConstraint(
                    check=~Q(name=''), 
                    name='name_not_empty'),
                CheckConstraint(
                    check=~Q(short_name=''),
                    name='short_name_not_empty']