Frage

In meiner App muss ich geänderte Werte (alt und neu) speichern, wenn das Modell gespeichert wird.Irgendwelche Beispiele oder funktionierenden Code?

Ich benötige dies für die Vormoderation von Inhalten.Wenn der Benutzer beispielsweise etwas am Modell ändert, kann der Administrator alle Änderungen in einer separaten Tabelle sehen und dann entscheiden, ob sie sie anwenden möchten oder nicht.

War es hilfreich?

Lösung

Sie haben gesagt, nicht sehr viel über Ihren spezifischen Anwendungsfall oder Bedürfnisse. Insbesondere wäre es hilfreich zu wissen, was Sie mit dem Änderungsinformationen tun müssen (wie lange brauchen Sie, es zu speichern?). Wenn Sie es nur für vorübergehende Zwecke speichern müssen, @ S.Lott Sitzung Lösung kann am besten sein. Wenn Sie eine vollständige Audit-Trail aller Änderungen an Objekte in der DB gespeichert werden sollen, versuchen Sie diese Audit Trail Lösung .

UPDATE : Der Audit Trail Code, den ich oben verlinkt ist der nächstgelegene ich zu einer vollständigen Lösung gesehen haben, die für Ihren Fall funktionieren würde, obwohl es einige Einschränkungen (funktioniert nicht auf allen für ManyToMany Felder). Es speichert alle vorherigen Versionen Ihrer Objekte in der DB, so dass die Server-Betreiber zurück zu einer früheren Version rollen könnten. Sie müßten mit ihm ein wenig arbeiten, wenn Sie die Änderung wollen erst wirksam, wenn genehmigt.

Sie können auch eine individuelle Lösung aufzubauen, die auf so etwas wie @Armin Ronacher DiffingMixin. Sie würden den diff-Wörterbuch speichern (vielleicht gebeizt?) In einer Tabelle für den Admin später zu überprüfen und anzuwenden, wenn gewünscht (Sie den Code schreiben, müssen die diff Wörterbuch zu nehmen und es auf eine Instanz).

Andere Tipps

Ich habe Armin Idee sehr nützlich erwiesen. Hier ist meine Variation;

class DirtyFieldsMixin(object):
    def __init__(self, *args, **kwargs):
        super(DirtyFieldsMixin, self).__init__(*args, **kwargs)
        self._original_state = self._as_dict()

    def _as_dict(self):
        return dict([(f.name, getattr(self, f.name)) for f in self._meta.local_fields if not f.rel])

    def get_dirty_fields(self):
        new_state = self._as_dict()
        return dict([(key, value) for key, value in self._original_state.iteritems() if value != new_state[key]])

Edit:. Getestet habe ich habe das BTW

Es tut uns Leid über die langen Linien. Der Unterschied ist (abgesehen von den Namen) es nur lokale Caches nicht-Beziehung Feldern. Mit anderen Worten zwischenspeichert es keine Felder des übergeordneten Modells, falls vorhanden.

Und es gibt noch eine Sache; Sie müssen _original_state dict nach dem Speichern zurückgesetzt werden. Aber ich wollte nicht save() Methode überschrieben werden, da die meisten der Zeit wir Modellinstanzen verwerfen nach dem Speichern.

def save(self, *args, **kwargs):
    super(Klass, self).save(*args, **kwargs)
    self._original_state = self._as_dict()

Django zur Zeit sendet alle Spalten in der Datenbank, auch wenn Sie eine gerade geändert. Um dies zu ändern, würden einige Änderungen im Datenbanksystem erforderlich. Dies könnte leicht auf dem vorhandenen Code implementiert werden, indem eine Reihe von schmutzigen Feldern zum Modell hinzugefügt und das Hinzufügen von Spaltennamen, um es, jedes Mal, wenn Sie __set__ einen Spaltenwert.

Wenn Sie diese Funktion benötigen, würde ich Sie auf dem Django ORM aussehen vorschlagen, umzusetzen und einen Patch in die trac Django setzen. Es soll sehr einfach sein, dass hinzufügen und es würde zu anderen Benutzern helfen. Wenn Sie das tun, einen Haken hinzufügen, die jedes Mal, wenn eine Spalte aufgerufen wird.

Wenn Sie auf Django hacken wollen nicht selbst, könnten Sie die dict auf Objekterstellung kopieren und diff es.

Vielleicht mit einem mixin wie folgt aus:

class DiffingMixin(object):

    def __init__(self, *args, **kwargs):
        super(DiffingMixin, self).__init__(*args, **kwargs)
        self._original_state = dict(self.__dict__)

    def get_changed_columns(self):
        missing = object()
        result = {}
        for key, value in self._original_state.iteritems():
            if key != self.__dict__.get(key, missing):
                result[key] = value
        return result

 class MyModel(DiffingMixin, models.Model):
     pass

Dieser Code ist nicht getestet, sollte aber funktionieren. Wenn Sie model.get_changed_columns() rufen erhalten Sie einen dict aller geänderten Werte. Dies wird natürlich nicht für veränderbare Objekte in Spalten arbeitet, weil der ursprüngliche Zustand eine flache Kopie des dict ist.

I erweitert Trey Hunner Lösung m2m Beziehungen zu unterstützen. Hoffentlich wird dies anderen helfen, für eine ähnliche Lösung.

from django.db.models.signals import post_save

DirtyFieldsMixin(object):
    def __init__(self, *args, **kwargs):
        super(DirtyFieldsMixin, self).__init__(*args, **kwargs)
        post_save.connect(self._reset_state, sender=self.__class__,
            dispatch_uid='%s._reset_state' % self.__class__.__name__)
        self._reset_state()

    def _as_dict(self):
        fields =  dict([
            (f.attname, getattr(self, f.attname))
            for f in self._meta.local_fields
        ])
        m2m_fields = dict([
            (f.attname, set([
                obj.id for obj in getattr(self, f.attname).all()
            ]))
            for f in self._meta.local_many_to_many
        ])
        return fields, m2m_fields

    def _reset_state(self, *args, **kwargs):
        self._original_state, self._original_m2m_state = self._as_dict()

    def get_dirty_fields(self):
        new_state, new_m2m_state = self._as_dict()
        changed_fields = dict([
            (key, value)
            for key, value in self._original_state.iteritems()
            if value != new_state[key]
        ])
        changed_m2m_fields = dict([
            (key, value)
            for key, value in self._original_m2m_state.iteritems()
            if sorted(value) != sorted(new_m2m_state[key])
        ])
        return changed_fields, changed_m2m_fields

Man kann wollen auch die beiden Feldlisten verschmelzen. Dafür ersetzen Sie die letzte Zeile

return changed_fields, changed_m2m_fields

mit

changed_fields.update(changed_m2m_fields)
return changed_fields

Das Hinzufügen einer zweiten Antwort, weil eine Menge seit der Zeit geändert hat diese Fragen ursprünglich gepostet .

Es gibt eine Reihe von Anwendungen in der Django Welt, das dieses Problem jetzt lösen. Sie können eine vollständige Liste der Modell Auditierung und Geschichte finden apps auf die Django-Pakete Website.

Ich schrieb einen Blog-Post einige dieser Anwendungen zu vergleichen . Dieser Beitrag ist jetzt 4 Jahre alt und es ist ein wenig veraltet. Die verschiedenen Ansätze zur Lösung dieses Problems scheinen, obwohl das gleiche zu sein.

Die Ansätze:

  1. Speichern Sie alle historischen Veränderungen in einem serialisierten Format (JSON?) In einer einzigen Tabelle
  2. Speichern Sie alle historischen Veränderungen in einer Tabelle das Original für jedes Modell Spiegelung
  3. Speichern Sie alle historischen Veränderungen in der gleichen Tabelle wie das Original-Modell (ich nicht empfohlen)

Das django-Reversion Paket scheint immer noch die beliebteste Lösung für dieses Problem. Es nimmt den ersten Ansatz. Serialisiert Veränderungen statt Tabellen der Spiegelung

ich wieder django-simple-Geschichte ein paar Jahre zurück. Es nimmt den zweiten Ansatz. Jede Tabelle spiegelt

So würde ich empfehlen die Verwendung einer App, dieses Problem zu lösen . Es gibt ein paar populär diejenigen, die an dieser Stelle recht gut funktionieren.

Oh, und wenn Sie nur für schmutzige Feld Überprüfung suchen und nicht alle historischen Änderungen speichern, überprüfen FieldTracker von django-Modell-utils .

Wenn Sie Muhuks Vorschlag fortsetzen und Djangos Signale und eine eindeutige „dispatch_uid“ hinzufügen, können Sie den Status beim Speichern zurücksetzen, ohne save() zu überschreiben:

from django.db.models.signals import post_save

class DirtyFieldsMixin(object):
    def __init__(self, *args, **kwargs):
        super(DirtyFieldsMixin, self).__init__(*args, **kwargs)
        post_save.connect(self._reset_state, sender=self.__class__, 
                            dispatch_uid='%s-DirtyFieldsMixin-sweeper' % self.__class__.__name__)
        self._reset_state()

    def _reset_state(self, *args, **kwargs):
        self._original_state = self._as_dict()

    def _as_dict(self):
        return dict([(f.name, getattr(self, f.name)) for f in self._meta.local_fields if not f.rel])

    def get_dirty_fields(self):
        new_state = self._as_dict()
        return dict([(key, value) for key, value in self._original_state.iteritems() if value != new_state[key]])

Dadurch würde der ursprüngliche Zustand nach dem Speichern bereinigt, ohne dass save() überschrieben werden müsste.Der Code funktioniert, aber es ist nicht sicher, wie hoch die Leistungseinbußen beim Verbinden von Signalen bei __init__ sind.

I erweitert muhuk und Lösungen des SMN Unterschied zählen auf dem Primärschlüssel für Fremdschlüssel überprüft und eine Eins-zu-Eins-Felder:

from django.db.models.signals import post_save

class DirtyFieldsMixin(object):
    def __init__(self, *args, **kwargs):
        super(DirtyFieldsMixin, self).__init__(*args, **kwargs)
        post_save.connect(self._reset_state, sender=self.__class__,
                            dispatch_uid='%s-DirtyFieldsMixin-sweeper' % self.__class__.__name__)
        self._reset_state()

    def _reset_state(self, *args, **kwargs):
        self._original_state = self._as_dict()

    def _as_dict(self):
        return dict([(f.attname, getattr(self, f.attname)) for f in self._meta.local_fields])

    def get_dirty_fields(self):
        new_state = self._as_dict()
        return dict([(key, value) for key, value in self._original_state.iteritems() if value != new_state[key]])

Der einzige Unterschied ist in _as_dict änderte ich die letzte Zeile von

return dict([
    (f.name, getattr(self, f.name)) for f in self._meta.local_fields
    if not f.rel
])

return dict([
    (f.attname, getattr(self, f.attname)) for f in self._meta.local_fields
])

Dieses mixin, wie die oben kann wie so verwendet werden:

class MyModel(DirtyFieldsMixin, models.Model):
    ....

Wenn Sie eine eigene Transaktionen verwenden (nicht die Standard-Admin-Anwendung), können Sie die vor und nach Versionen des Objekts speichern. Sie können die vor der Version in der Sitzung speichern, oder Sie können es ausdrückte in „versteckt“ Felder im Formular. Versteckte Felder ist ein Unsicherheitsfaktor. Verwenden Sie deshalb die Sitzung Geschichte zu behalten, was mit diesem Benutzer passiert.

Darüber hinaus natürlich, müssen Sie das vorherige Objekt holen, so dass Sie Änderungen vornehmen können. So haben Sie mehr Möglichkeiten, um die Unterschiede zu überwachen.

def updateSomething( request, object_id ):
    object= Model.objects.get( id=object_id )
    if request.method == "GET":
        request.session['before']= object
        form= SomethingForm( instance=object )
    else request.method == "POST"
        form= SomethingForm( request.POST )
        if form.is_valid():
            # You have before in the session
            # You have the old object
            # You have after in the form.cleaned_data
            # Log the changes
            # Apply the changes to the object
            object.save()

Eine aktualisierte Lösung mit m2m-Unterstützung (mit aktualisierten dirtyfields und neuen _ Meta-API und einige Bug-Fixes), basierend auf @Trey und @ Tonys oben. Dies hat sich für mich einige grundlegende Licht-Test bestanden haben.

from dirtyfields import DirtyFieldsMixin
class M2MDirtyFieldsMixin(DirtyFieldsMixin):
    def __init__(self, *args, **kwargs):
        super(M2MDirtyFieldsMixin, self).__init__(*args, **kwargs)
        post_save.connect(
            reset_state, sender=self.__class__,
            dispatch_uid='{name}-DirtyFieldsMixin-sweeper'.format(
                name=self.__class__.__name__))
        reset_state(sender=self.__class__, instance=self)

    def _as_dict_m2m(self):
        if self.pk:
            m2m_fields = dict([
                (f.attname, set([
                    obj.id for obj in getattr(self, f.attname).all()
                ]))
                for f,model in self._meta.get_m2m_with_model()
            ])
            return m2m_fields
        return {}

    def get_dirty_fields(self, check_relationship=False):
        changed_fields = super(M2MDirtyFieldsMixin, self).get_dirty_fields(check_relationship)
        new_m2m_state = self._as_dict_m2m()
        changed_m2m_fields = dict([
            (key, value)
            for key, value in self._original_m2m_state.iteritems()
            if sorted(value) != sorted(new_m2m_state[key])
        ])
        changed_fields.update(changed_m2m_fields)
        return changed_fields

def reset_state(sender, instance, **kwargs):
    # original state should hold all possible dirty fields to avoid
    # getting a `KeyError` when checking if a field is dirty or not
    instance._original_state = instance._as_dict(check_relationship=True)
    instance._original_m2m_state = instance._as_dict_m2m()

für jeden Informationen, muhuk Lösung nicht unter python2.6, da es eine Ausnahme auslöst Angabe ‚Objekt .__ init __ ()‘ übernimmt keine Argument ...

edit: ho! offenbar kann es mir die mixin mißbraucht habe ... Ich habe nicht darauf geachtet und erklärte sie als letzte Eltern und wegen dass der Aufruf von init endete im Objekt Eltern, anstatt die nächste Eltern wie es noramlly würde mit Diamanten Diagramm Erbe! so wenden Sie sich bitte meinen Kommentar außer Acht lassen:)

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top