Feuermurmel
Feuermurmel

Reputation: 9922

Compare expression with constant in CHECK constraint

I'd like to use Django's CheckConstraint to add a check constraint to a table using PostgreSQLs num_nonnulls() function, similar to this:

create table foo(
    a text,
    b int,
    [...],
    check num_nonnulls(a, b, ...) = n);

n is a constant but may be different for different tables (mostly it's going to be 1 but I'd like to find a general solution). This is how far I got:

class Foo(models.Model):
    a = models.TextField(null=True)
    b = models.IntegerField(null=True)
    [...]

    class Meta:
        constraints = [
            models.CheckConstraint(
                check=models.ExpressionWrapper(
                    models.Func('a', 'b', function='num_nonnulls'),
                    models.BooleanField()),
                name='num_nonnulls_check')]

This is of course missing the step where the result of num_nonnulls() is compared to some constant integer. I tried defining a function to do this:

def equals(a, b):
    return models.Func(a, b, template='%(expressions[0])s = %(expressions[1])s')

But this doesn't work because the template argument expressions is (I think) a string (and %-template strings don't have this syntax to extract parts of an argument, I think).

Where do I go from here?

I'd like to find a solution that allows me to use arbitrary expressions supported by the Django ORM and also compare these expressions with other expressions or constants using an equality or inequality relation (e.g. = or <=).

Upvotes: 3

Views: 721

Answers (1)

Dan LaManna
Dan LaManna

Reputation: 3501

As of Django 4 this is possible, see this changelog entry:

Lookup expressions may now be used in QuerySet annotations, aggregations, and directly in filters.

This should fit your example.

from django.db import models
from django.db.models.constraints import CheckConstraint
from django.db.models.expressions import Func
from django.db.models import Value
from django.db.models.fields import IntegerField, TextField


class Foo(models.Model):
    a = models.TextField(null=True)
    b = models.IntegerField(null=True)

    class Meta:
        constraints = [
            CheckConstraint(
                name='num_nonnulls_check',
                check=Exact(
                    lhs=Func('a', 'b', function='num_nonnulls', output_field=IntegerField()),
                    rhs=Value(1),
                ),
            )
        ]

Upvotes: 4

Related Questions