How to apply 'IN' constraint to the fields of django models

Question:

I want that one of the fields of the model can take only some specific values say a and b. This can be achieved normally in sql by adding the ‘IN’ constraint. How can I achieve the same using django model class fields.

eg.

Some field say fruits can have values 'banana' and 'orange' only

Please help. I am new to django.

Asked By: shubhanjalee

||

Answers:

You’re referring to choices.

Example from the docs:

from django.db import models

class Student(models.Model):
    FRESHMAN = 'FR'
    SOPHOMORE = 'SO'
    JUNIOR = 'JR'
    SENIOR = 'SR'
    YEAR_IN_SCHOOL_CHOICES = (
        (FRESHMAN, 'Freshman'),
        (SOPHOMORE, 'Sophomore'),
        (JUNIOR, 'Junior'),
        (SENIOR, 'Senior'),
    )
    year_in_school = models.CharField(max_length=2,
                                      choices=YEAR_IN_SCHOOL_CHOICES,
                                      default=FRESHMAN)
Answered By: rnevius

As Raunaqss pointed out, the accepted solution does not actually constraint the data at the database level, so you might end up with irregularities in your database if you don’t actually enforce it.

If this is an issue for you, here is a way to actually apply said constraints, using the accepted answer’s example:

from django.db import models

class YearInSchoolChoices(models.TextChoices):
    FRESHMAN = 'FR'
    SOPHOMORE = 'SO'
    JUNIOR = 'JR'
    SENIOR = 'SR'

class Student(models.Model):
    year_in_school = models.CharField(
        max_length=2, choices=YearInSchoolChoices.choices
    )

    class Meta:
        constraints = [
            models.CheckConstraint(
                name="%(class)s_year_in_school_valid",
                check=models.Q(year_in_school__in=YearInSchoolChoices.values),
            )
        ]

Remember to then run makemigrations, possibly manually adding some data migration code to the beginning of the generated migration in order to clean up eventual invalid rows:

def clean_up_invalid_years(apps, schema_editor):
    Student = apps.get_model("students", "Student")

    for row in Student.objects.all().reverse():
        if row.year_in_school not in YearInSchoolChoices.values:
            row.delete()

(You may have to copy the YearInSchoolChoices class into the migration since it cannot be imported)

Answered By: SylvainB