Using clean() in your Django models post migration from a legacy stack

Using clean() in your Django models post migration from a legacy stack

If you're migrating an application from another stack where foreign keys were not enforced in the database and the some of the rows' foreign key fields have values as 0, then these would cause issues in the new application where the framework has strict checks and would throw an exception for 0 being saved as a foreign key value.

Let's take an example model from Django docs. https://docs.djangoproject.com/en/5.0/topics/db/examples/many_to_one/

class Article(models.Model):
    headline = models.CharField(max_length=100)
    pub_date = models.DateField()
    reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE)

    media_house = models.ForeignKey(MediaHouse, on_delete=models.CASCADE, null=True, blank=True) # Added, NEW
    some_other_model = models.ForeignKey(SomeOtherModel, on_delete=models.CASCADE, null=True, blank=True) # Added, NEW

Let's say you have a row with primary key 915 and it contains media_house_id as 0 and some_other_model_id also as 0. Now let's just try to save this just after retrieving the row.

try:
    article = Article.objects.get(pk=915)
    article.save()
except Exception as e:
    msg = str(e)

You would get an exception.

The database backend does not accept 0 as a value for AutoField.

This is because the row with primary key ID 915 has an entry of media_house_id equal to 0 and/or some_other_model_id equal to 0 for which Foreign Key constraint fails as there isn't an entry (row) in the MediaHouse and SomeOtherModel tables where the primary key for those tables are 0.

So instead, we call the clean method (for which we write a clean function in the model) to set the field to None if it's 0.

class Article(models.Model):
    headline = models.CharField(max_length=100)
    pub_date = models.DateField()
    reporter = models.ForeignKey(Reporter, on_delete=models.CASCADE)

    media_house = models.ForeignKey(MediaHouse, on_delete=models.CASCADE, null=True, blank=True) # Added, NEW
    some_other_model = models.ForeignKey(SomeOtherModel, on_delete=models.CASCADE, null=True, blank=True) # Added, NEW

    def clean(self):
        if self.media_house_id == 0:
            self.media_house_id = None
        if self.some_other_model_id == 0:
            self.some_other_model_id = None

And before calling the save() method, we just call clean() as well.

try:
    article = Article.objects.get(pk=915)
    article.clean() # NEW : Call clean() to convert fields' invalid values to valid values or None (Null in MySQL)
    article.save()    
except Exception as e:
    msg = str(e)

Did you find this article valuable?

Support Anjanesh Lekshminarayanan by becoming a sponsor. Any amount is appreciated!