web-dev-qa-db-fra.com

django - compare les valeurs de champs anciennes et nouvelles avant de sauvegarder

J'ai un modèle Django et je dois comparer les anciennes et les nouvelles valeurs du champ AVANT de sauvegarder.

J'ai essayé l'héritage save () et le signal pre_save. Cela s'est correctement déclenché, mais je ne trouve pas la liste des champs réellement modifiés ni les anciennes et les nouvelles valeurs. Il y a un moyen? J'en ai besoin pour optimiser les actions de pré-enregistrement.

Je vous remercie!

44
Y.N

Il existe un moyen très simple pour Django de le faire.

"Mémorisez" les valeurs dans model init comme ceci:

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

Exemple concret:

En classe:

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

Et puis dans la méthode de sauvegarde de modelform:

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
47
Odif Yltsaeb

Il est préférable de le faire au niveau de ModelForm

Vous obtenez là toutes les données dont vous avez besoin pour la comparaison dans la méthode de sauvegarde:

  1. self.data : Données réelles transmises au formulaire.
  2. self.cleaned_data : Données nettoyées après des validations, contient des données pouvant être sauvegardées dans le modèle.
  3. self.changed_data : Liste des champs qui ont changé. Ce sera vide si rien n'a changé

Si vous voulez le faire au niveau du modèle, vous pouvez suivre la méthode spécifiée dans la réponse d'Odif.

32
Sahil kalra

Vous pouvez aussi utiliser FieldTracker from Django-model-utils pour cela:

  1. Ajoutez simplement un champ de suivi à votre modèle:

    tracker = FieldTracker()
    
  2. Maintenant, dans pre_save et post_save, vous pouvez utiliser:

    instance.tracker.previous('modelfield')     # get the previous value
    instance.tracker.has_changed('modelfield')  # just check if it is changed
    
24
psl

Je conviens avec Sahil qu'il est préférable et plus simple de faire cela avec ModelForm. Cependant, vous pouvez personnaliser la méthode de nettoyage de ModelForm et y effectuer une validation. Dans mon cas, je voulais empêcher les mises à jour de l'instance d'un modèle si un champ du modèle était défini.

Mon code ressemblait à ceci:

from Django.forms import ModelForm

class ExampleForm(ModelForm):
    def clean(self):
        cleaned_data = super(ExampleForm, self).clean()
        if self.instance.field:
            raise Exception
        return cleaned_data
1
erika_dike

Voici une application qui vous donne accès à la valeur précédente et actuelle d'un champ juste avant que le modèle ne soit enregistré: Django-smartfields

Voici comment ce problème peut être résolu dans un Nice déclaratif peut:

from Django.db import models
from smartfields import fields, processors
from smartfields.dependencies import Dependency

class ConditionalProcessor(processors.BaseProcessor):

    def process(self, value, stashed_value=None, **kwargs):
        if value != stashed_value:
            # do any necessary modifications to new value
            value = ... 
        return value

class MyModel(models.Model):
    my_field = fields.CharField(max_length=10, dependencies=[
        Dependency(processor=ConditionalProcessor())
    ])

De plus, ce processeur sera appelé, uniquement si la valeur de ce champ a été remplacée

1
lehins

Mon cas d'utilisation était que je devais définir une valeur dénormalisée dans le modèle chaque fois qu'un champ modifiait sa valeur. Cependant, comme le champ surveillé était une relation m2m, je ne voulais pas avoir à faire cette recherche de base de données chaque fois que save était appelé afin de vérifier si le champ dénormalisé devait être mis à jour. Au lieu de cela, j’ai donc écrit ce petit mixin (en s’inspirant de la réponse de @Odif Yitsaeb) pour mettre à jour le champ dénormalisé uniquement lorsque cela était nécessaire.

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
1
Bobby

Quelque chose comme ça marche aussi:  

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
Slipstream