Pregunta

En mi aplicación tengo que guardar los valores modificados (antiguo y nuevo) cuando el modelo se guarda.Ejemplos o código de trabajo?

Necesito esto para premoderation de contenido.Por ejemplo, si el usuario cambia algo en el modelo, entonces el administrador puede ver todos los cambios en la tabla y, a continuación, decidir que se aplique o no.

¿Fue útil?

Solución

Usted no ha dicho mucho acerca de su caso de uso específico o necesidades.En particular, sería útil saber lo que usted necesita hacer con la información de los cambios (¿cuánto tiempo necesita para almacenarlo?).Si sólo necesita almacenar transitoria a los efectos de, @S. Lott la sesión de la solución puede ser mejor.Si desea una auditoría completa de todos los cambios a los objetos almacenados en la base de datos, intente esto AuditTrail solución.

ACTUALIZACIÓN:El AuditTrail código que he enlazado más arriba es lo más cercano que he visto a una completa solución que podría funcionar para su caso, a pesar de que tiene algunas limitaciones (no funciona en absoluto para ManyToMany campos).Almacenará todas las versiones anteriores de los objetos de la DB, por lo que la administración puede volver a cualquier versión anterior.Usted tendría que trabajar un poco con ella si quiere que los cambios no surtirán efecto hasta que sea aprobado.

También puede crear una solución personalizada basada en algo como @Armin Ronacher del DiffingMixin.Te gustaría almacenar el diff diccionario (tal vez en escabeche?) en una tabla de la administración para su posterior revisión y aplicar, si se desea (que sería necesario escribir el código para tomar el diff diccionario y aplicarlo a un ejemplo).

Otros consejos

He encontrado Armin idea muy útil.Aquí está mi variación;

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]])

Editar:He probado esta BTW.

Lo siento por el largo de las líneas.La diferencia es (aparte de los nombres) sólo se almacena en caché local no campos de relación.En otras palabras, no caché de un modelo primario campos si está presente.

Y hay una cosa más;usted necesita para restablecer _original_state dict después de guardar.Pero yo no desea sobrescribir save() método, ya que la mayoría de las veces descartamos instancias de modelo después de guardar.

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

Django es actualmente el envío de todas las columnas de la base de datos, incluso si usted acaba de cambiar uno.A cambio de esto, algunos cambios en el sistema de base de datos, sería necesario.Esto podría ser fácilmente implementado en el código existente mediante la adición de un conjunto de sucio campos para el modelo y la adición de los nombres de columna, cada vez que __set__ el valor de la columna.

Si usted necesita que cuentan, yo le sugiero que busque en el ORM de Django, implementar y poner un parche en el Django de tráco.No debe ser muy fácil para agregar y que iba a ayudar a otros usuarios.Cuando haces eso, agregar un gancho que se llama cada vez que una columna es el conjunto.

Si usted no desea hack en Django, puedes copiar el diccionario en la creación de objetos y diferencial de ella.

Tal vez con un mixin como este:

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

Este código no está probado, pero debería funcionar.Cuando llame model.get_changed_columns() consigue un diccionario de todos los valores modificados.Por supuesto, esto no funciona para objetos mutables en las columnas debido a su estado original es una copia plana de la dict.

Yo extendida Trey Hunner la solución m2m de las relaciones.Esperemos que esto ayude a otros que buscan una solución similar.

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

Uno también puede combinar las dos listas.Para que, de sustituir la última línea

return changed_fields, changed_m2m_fields

con

changed_fields.update(changed_m2m_fields)
return changed_fields

La adición de una segunda respuesta, porque mucho ha cambiado desde el momento en que esta pregunta fue publicado originalmente.

Hay un número de aplicaciones en Django mundo que resolver este problema ahora.Usted puede encontrar una completa lista de modelo de auditoría y de la historia apps en el Django Paquetes sitio.

Escribí un post en el blog la comparación de algunas de estas aplicaciones.Este post es ahora de 4 años de edad y es un poco anticuado.Los diferentes enfoques para la solución de este problema parece ser el mismo aunque.

Los enfoques:

  1. Guarde todos los cambios históricos en un formato serializado en JSON (?) en una sola tabla
  2. Almacenar el historial de todos los cambios en una tabla de creación de reflejo de los originales para cada modelo
  3. Guarde todos los cambios históricos en la misma mesa que el modelo original (no recomiendo esta)

El django-reversión paquete todavía parece ser la solución más popular para este problema.Toma la primera aproximación:serializar los cambios en lugar de la creación de reflejo de las tablas.

He revivido django-simple-historia un par de años atrás.Ocupa el segundo enfoque:espejo de cada tabla.

Así que yo recomendaría el uso de una aplicación para resolver este problema.Hay un par de populares que funcionan bastante bien en este punto.

Ah, y si usted está buscando sucio comprobaciones de campo y no almacenar todos los cambios históricos, echa un vistazo FieldTracker de django-modelo-utils.

Continuando en Muhuk la sugerencia y agregar Django señales y un único dispatch_uid se puede restablecer el estado en guardar sin primordial guardar():

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]])

Que iba a limpiar el estado original una vez que se guarda sin tener que reemplazar guardar().El código funciona, pero no está seguro de lo que el rendimiento es de la conexión de las señales en __init__

Yo extendida muhuk y smn soluciones para incluir a diferencia de comprobación de las claves principales para extranjeros de la clave y uno a uno los campos de:

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]])

La única diferencia está en _as_dict He cambiado la última línea de

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

a

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

Este mixin, como los anteriores, puede ser utilizado como:

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

Si usted está utilizando su propio transacciones (no el valor predeterminado de la aplicación de administración), puede guardar el antes y el después de las versiones de su objeto.Puede guardar la versión anterior en la sesión, o se puede poner en "oculto" de campos en el formulario.Campos ocultos de seguridad es de pesadilla.Por lo tanto, el uso de la sesión para conservar la historia de lo que está sucediendo con este usuario.

Además, por supuesto, usted tiene que buscar el objeto anterior de modo que usted puede hacer cambios a la misma.Así que usted tiene varias maneras de supervisar las diferencias.

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()

Una actualización con la solución m2m de apoyo (usando actualizado dirtyfields y de nuevo _meta API y algunas correcciones de errores), basado en el @Trey y @Tony arriba.Esto ha pasado básicos de la luz de prueba para mí.

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()

para información, muhuk la solución de la falla bajo python2.6 como se plantea una excepción que indica 'object.__ init __()' no acepta ningún argumento...

editar:ho!al parecer, podría haber sido yo el mal uso de los el mixin...Yo no preste atención y lo declaró como el último de los padres y debido a que la llamada a init terminó en el objeto padre en lugar de la siguiente padres como noramlly con diamante diagrama de herencia!así que haga caso omiso de mi comentario :)

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top