Opened 12 months ago
Closed 11 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