Opened 9 months ago

Closed 9 months ago

#35031 closed Bug (invalid)

overridden **from_db** classmethod doesn't get called if put in the AbstractBaseModel

Reported by: Gaurav Jain Owned by: nobody
Component: Database layer (models, ORM) Version: 4.2
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Context:
As described here, https://docs.djangoproject.com/en/4.2/ref/models/instances/#customizing-model-loading, I'm overriding from_db method to keep track of old and new values so that I can take necessary actions if the value has changed.
This works fine when I'm overriding this method in individual classes, however, this method (from_db) doesn't get called when I'm putting this in the AbstractBaseModel class as described below.

Reproducing steps.

class AbstractBaseModel(models.Model):
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True

    @classmethod
    def from_db(cls, db, field_names, values):
        instance = super().from_db(db, field_names, values)
        # save original values, when model is loaded from database
        instance._initial_values = dict(zip(field_names, values, strict=True))
        return instance

To include the above DateTime fields in all models, just extend it(AbstractBaseModel). Also, override the save method so that we can check the value before saving the object. e.g. below

class Country(AbstractBaseModel):
    name = models.CharField()
   
    def save(self, *args, **kwargs):
        if self.name != self._initial_values["name"]:   # This will raise AttributeError: 'Country' object has no attribute '_initial_values'
              # `name` field value has changed/overridden. write appropriate logic 
              print(f"New name: {self.name} and Old Name: {self._initial_values["name"]}")
        super().save(*args, **kwargs)

Above save method will raise the error as the parent class (AbstractBaseModel) method hasn't been executed so the _initial_values attribute won't be set. However, if you override the from_db method inside the individual model classes, it would work. e.g.

class Country(AbstractBaseModel):
    name = models.CharField()
   
    def save(self, *args, **kwargs):
        if self.name != self._initial_values["name"]:   # This will work
              # `name` field value has changed/overridden. write appropriate logic 
              print(f"New name: {self.name} and Old Name: {self._initial_values["name"]}")
        super().save(*args, **kwargs)

    @classmethod
    def from_db(cls, db, field_names, values):
        instance = super().from_db(db, field_names, values)
        # save original values, when model is loaded from database
        instance._initial_values = dict(zip(field_names, values, strict=True))
        return instance

Change History (1)

comment:1 by Gaurav Jain, 9 months ago

Resolution: invalid
Status: newclosed
Note: See TracTickets for help on using tickets.
Back to Top