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!
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
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:
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.
Vous pouvez aussi utiliser FieldTracker from Django-model-utils pour cela:
Ajoutez simplement un champ de suivi à votre modèle:
tracker = FieldTracker()
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
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
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
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
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)