django - comparing old and new field value before saving

前端 未结 8 722
温柔的废话
温柔的废话 2020-12-12 20:40

I have a django model, and I need to compare old and new values of field BEFORE saving.

I\'ve tried the save() inheritence, and pre_save signal. It was triggered cor

相关标签:
8条回答
  • 2020-12-12 20:43

    There is very simple django way for doing it.

    "Memorise" the values in model init like this:

    def __init__(self, *args, **kwargs):
        super(MyClass, self).__init__(*args, **kwargs)
        self.initial_parametername = self.parametername
        ---
        self.initial_parameternameX = self.parameternameX
    

    Real life example:

    At class:

    def __init__(self, *args, **kwargs):
        super(MyClass, self).__init__(*args, **kwargs)
        self.__important_fields = ['target_type', 'target_id', 'target_object', 'number', 'chain', 'expiration_date']
        for field in self.__important_fields:
            setattr(self, '__original_%s' % field, getattr(self, field))
    
    def has_changed(self):
        for field in self.__important_fields:
            orig = '__original_%s' % field
            if getattr(self, orig) != getattr(self, field):
                return True
        return False
    

    And then in modelform save method:

    def save(self, force_insert=False, force_update=False, commit=True):
        # Prep the data
        obj = super(MyClassForm, self).save(commit=False)
    
        if obj.has_changed():
    
            # If we're down with commitment, save this shit
            if commit:
                obj.save(force_insert=True)
    
        return obj
    
    0 讨论(0)
  • 2020-12-12 20:50

    Django 1.8+ and above (Including Django 2.x and 3.x), there is a from_db classmethod, which can be used to customize model instance creation when loading from the database.

    Note: There is NO additional database query if you use this method.

    Here is the link to the official docs Model instance - Customize model loading

    from django.db import Model
    
    class MyClass(models.Model):
        
        @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,
            # in a separate attribute on the model
            instance._loaded_values = dict(zip(field_names, values))
            
            return instance
    

    So now the original values are available in the _loaded_values attribute on the model. You can access this attribute inside your save method to check if some value is being updated.

    class MyClass(models.Model):
        field_1 = models.CharField(max_length=1)
    
        @classmethod
        def from_db(cls, db, field_names, values):
            ...
            # use code from above
    
        def save(self, *args, **kwargs):
    
            # check if a new db row is being added
            # When this happens the `_loaded_values` attribute will not be available
            if not self._state.adding:
    
                # check if field_1 is being updated
                if self._loaded_values['field_1'] != self.field_1:
                    # do something
    
            super().save(*args, **kwargs)
                
                
    
    0 讨论(0)
  • 2020-12-12 20:57

    Also you can use FieldTracker from django-model-utils for this:

    1. Just add tracker field to your model:

      tracker = FieldTracker()
      
    2. Now in pre_save and post_save you can use:

      instance.tracker.previous('modelfield')     # get the previous value
      instance.tracker.has_changed('modelfield')  # just check if it is changed
      
    0 讨论(0)
  • 2020-12-12 21:01

    It is better to do this at ModelForm level.

    There you get all the Data that you need for comparison in save method:

    1. self.data : Actual Data passed to the Form.
    2. self.cleaned_data : Data cleaned after validations, Contains Data eligible to be saved in the Model
    3. self.changed_data : List of Fields which have changed. This will be empty if nothing has changed

    If you want to do this at Model level then you can follow the method specified in Odif's answer.

    0 讨论(0)
  • 2020-12-12 21:01

    My use case for this was that I needed to set a denormalized value in the model whenever some field changed its value. However, as the field being monitored was a m2m relation, I didn't want to have to do that DB lookup whenever save was called in order to check whether the denormalized field needed updating. So, instead I wrote this little mixin (using @Odif Yitsaeb's answer as inspiration) in order to only update the denormalized field when necessary.

    class HasChangedMixin(object):
        """ this mixin gives subclasses the ability to set fields for which they want to monitor if the field value changes """
        monitor_fields = []
    
        def __init__(self, *args, **kwargs):
            super(HasChangedMixin, self).__init__(*args, **kwargs)
            self.field_trackers = {}
    
        def __setattr__(self, key, value):
            super(HasChangedMixin, self).__setattr__(key, value)
            if key in self.monitor_fields and key not in self.field_trackers:
                self.field_trackers[key] = value
    
        def changed_fields(self):
            """
            :return: `list` of `str` the names of all monitor_fields which have changed
            """
            changed_fields = []
            for field, initial_field_val in self.field_trackers.items():
                if getattr(self, field) != initial_field_val:
                    changed_fields.append(field)
    
            return changed_fields
    
    0 讨论(0)
  • 2020-12-12 21:04

    Something like this also works:

    class MyModel(models.Model):
        my_field = fields.IntegerField()
    
        def save(self, *args, **kwargs):
           # Compare old vs new
           if self.pk:
               obj = MyModel.objects.values('my_value').get(pk=self.pk)
               if obj['my_value'] != self.my_value:
                   # Do stuff...
                   pass
           super().save(*args, **kwargs)
    
    0 讨论(0)
提交回复
热议问题