darkhorse
darkhorse

Reputation: 8722

Making sure a field is always null in Django

I have a parent class which is being extended by 3 different models. The parent class has a field called foo lets say, and this field needs to be always null for one of the sub-classes. How can I ensure this? Right now, I am using null=True, and editable=False constraints. However, these can be circumvented from the shell or an API if the field is exposed during object creation.

class ThirdSubclass(ParentClass):
    # Over-ridden field from abstract parent class
    foo = models.PositiveSmallIntegerField(blank=True, null=True, editable=False)

I am also using the PositiveSmallIntegerField since I want to allocate as little space as possible for this field. Anyway, how do I do this? Is over-riding the save method the only option? Ideally, I would love something within the field definition. Thanks!

Upvotes: 0

Views: 236

Answers (1)

Kevin Christopher Henry
Kevin Christopher Henry

Reputation: 48902

The right way to do this depends on how you expect your models to be used. Here are four possible approaches, in order of increasing robustness:

  1. If all writes will come through default model forms:

    You can just set a default:

    class ThirdSubclass(ParentClass):
        foo = models.PositiveSmallIntegerField(blank=True, null=True, default=None, editable=False)
    
  2. If all writes will come through the use of model validation (that is, calling full_clean() before save()):

    You can use a validator:

    def validate_none(value):
        if value is not None:
            raise ValidationError("...")
    
    class ThirdSubclass(ParentClass):
        foo = models.PositiveSmallIntegerField(blank=True, null=True, default=None, editable=False,
                                               validators=[validate_none])
    
  3. If all writes will come through the model:

    You can override save().

    class ThirdSubclass(ParentClass):
        def save(self, *args, **kwargs):
            if self.foo is not None:
                raise ValueError("...")
            super().save(*args, **kwargs)
    
  4. If writes can come from anywhere (for example, using update() or raw SQL):

    You need a database-level constraint. Django has no model-level support for this, but you could create one by writing a custom migration and using RunSQL to create the constraint.

Of these I would consider 2 to be standard, and the most elegant if you don't need the protections of 3 and 4.

Upvotes: 2

Related Questions