Ich habe ein Django-Modell und muss vor dem Speichern alte und neue Werte des Feldes vergleichen.
Ich habe die Vererbung save () und das Signal pre_save ausprobiert. Es wurde korrekt ausgelöst, aber ich kann die Liste der tatsächlich geänderten Felder nicht finden und alte und neue Werte nicht vergleichen. Da ist ein Weg? Ich brauche es zur Optimierung von vorgespeicherten Aktionen.
Vielen Dank!
save
Methode und dem Überprüfen jedes Felds auf Gleichheit?Antworten:
Es gibt einen sehr einfachen Django-Weg, dies zu tun.
Merken Sie sich die Werte in model init wie folgt:
def __init__(self, *args, **kwargs): super(MyClass, self).__init__(*args, **kwargs) self.initial_parametername = self.parametername --- self.initial_parameternameX = self.parameternameX
Beispiel aus dem wirklichen Leben:
Im Unterricht:
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
Und dann in Modellform Speichermethode:
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
quelle
__init__
heißt es Funktioniert es nur für die Ersterstellung oder nachfolgende Updates?__init__
wird sie nur am Anfang aufgerufen.save
oderbulk_create
Es ist besser, dies auf ModelForm-Ebene zu tun .
Dort erhalten Sie alle Daten, die Sie zum Vergleich in der Speichermethode benötigen:
Wenn Sie dies auf Modellebene tun möchten, können Sie die in Odifs Antwort angegebene Methode befolgen.
quelle
_post_clean
(is_valid->errors->full_clean->_post_clean
) eines Formulars. Danach wird die Instanz aktualisiert, um die neuen Werte einzuschließen . Zugriff inform.clean_fieldname()
undform.clean()
scheint in Ordnung zu sein, vorausgesetzt, es ist ihr erster Anruf.self.changed_data
ist neu für michSie können auch verwenden FieldTracker von django-Modell-utils für diese:
Fügen Sie Ihrem Modell einfach ein Trackerfeld hinzu:
Jetzt können Sie in pre_save und post_save Folgendes verwenden:
instance.tracker.previous('modelfield') # get the previous value instance.tracker.has_changed('modelfield') # just check if it is changed
quelle
tracker.py
. Es sieht nach vielen Arbeiten und Signalen aus. Es kommt also darauf an, ob es sich lohnt - oder der Anwendungsfall zu begrenzt war, dass Sie nur ein oder zwei Felder verfolgen müssen.Ab Django 1.8+ (einschließlich Django 2.x und 3.x) gibt es eine
from_db
Klassenmethode, mit der die Erstellung von Modellinstanzen beim Laden aus der Datenbank angepasst werden kann.Hinweis : Wenn Sie diese Methode verwenden, gibt es KEINE zusätzliche Datenbankabfrage.
Hier ist der Link zur offiziellen docs- Modellinstanz - Modellladen anpassen
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
Die ursprünglichen Werte sind jetzt im
_loaded_values
Attribut des Modells verfügbar . Sie können auf dieses Attribut in Ihrersave
Methode zugreifen , um zu überprüfen, ob ein Wert aktualisiert wird.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)
quelle
Mein Anwendungsfall dafür war, dass ich einen denormalisierten Wert im Modell festlegen musste, wenn ein Feld seinen Wert änderte. Da es sich bei dem überwachten Feld jedoch um eine m2m-Beziehung handelt, wollte ich diese DB-Suche nicht bei jedem Aufruf von save durchführen müssen, um zu überprüfen, ob das denormalisierte Feld aktualisiert werden muss. Also schrieb ich stattdessen dieses kleine Mixin (unter Verwendung der Antwort von @Odif Yitsaeb als Inspiration), um das denormalisierte Feld nur bei Bedarf zu aktualisieren.
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
quelle
So etwas funktioniert auch:
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)
quelle
Hier ist eine App, mit der Sie direkt vor dem Speichern des Modells auf den vorherigen und aktuellen Wert eines Felds zugreifen können: django-smartfields
So kann dieses Problem in einem schönen deklarativen Mai gelöst werden:
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()) ])
Außerdem wird dieser Prozessor nur dann aufgerufen, wenn der Wert dieses Feldes ersetzt wurde
quelle
Ich stimme Sahil zu, dass es mit ModelForm besser und einfacher ist, dies zu tun. Sie würden jedoch die Bereinigungsmethode von ModelForm anpassen und dort eine Validierung durchführen. In meinem Fall wollte ich Aktualisierungen der Instanz eines Modells verhindern, wenn ein Feld im Modell festgelegt ist.
Mein Code sah folgendermaßen aus:
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
quelle
Eine andere Möglichkeit, dies zu erreichen, besteht darin, die Signale
post_init
und zupost_save
verwenden, um den Anfangszustand des Modells zu speichern.@receiver(models.signals.post_init) @receiver(models.signals.post_save) def _set_initial_state( sender: Type[Any], instance: Optional[models.Model] = None, **kwargs: Any, ) -> None: """ Store the initial state of the model """ if isinstance(instance, MyModel): instance._initial_state = instance.state
quelle
Im modernen Django ist es sehr wichtig , den Inhalt der Antwort zu ergänzen, die unter den obigen Antworten akzeptiert wird . Sie können in einen fallen unendliche Rekursion , wenn Sie verwenden
defer
oderonly
QuerySet API.__get__()
Methode derdjango.db.models.query_utils.DeferredAttribute
Aufruferefresh_from_db()
Methode vondjango.db.models.Model
. Es gibt eine Zeiledb_instance = db_instance_qs.get()
inrefresh_from_db()
, und diese Zeile ruft die__init__()
Methode der Instanz rekursiv auf.Es muss also hinzugefügt werden, um sicherzustellen, dass die Zielattribute nicht zurückgestellt werden.
def __init__(self, *args, **kwargs): super(MyClass, self).__init__(*args, **kwargs) deferred_fields = self.get_deferred_fields() important_fields = ['target_type', 'target_id', 'target_object', 'number', 'chain', 'expiration_date'] self.__important_fields = list(filter(lambda x: x not in deferred_fields, important_fields)) for field in self.__important_fields: setattr(self, '__original_%s' % field, getattr(self, field))
quelle