2010-12-18 12 views
11

Tengo una aplicación Django donde permití a un usuario importar un archivo CSV con datos de contacto (membresía, nombre, apellido, etc.).Cómo implementar una característica de "deshacer" usando Python/Django

Cuando importan el archivo, la aplicación busca un registro coincidente en la base de datos y: 1) inserta un nuevo registro si no existe coincidencia, o 2) actualiza los datos existentes con los datos nuevos.

Mi pregunta es: ¿cuál es la mejor manera de implementar una función de deshacer, usando Django o Python recta, por lo que un usuario puede deshacer la operación de importación y revertir múltiples registros de vuelta a su estado original?

Mis pensamientos iniciales son para crear una tabla como ésta (pseudo código):

Table HISTORY 
    unique_id 
    record_affected_id 
    old_value 
    new_value 

A continuación, si el usuario hace clic en "Deshacer" Puedo buscar el id_exclusivo que está asociada con la transacción y establece todos los registros afectado por esa transacción al old_value.

Me pregunto si hay una forma más sencilla de hacer esto que me falta, o si alguien tiene experiencia con algo como esto.

Respuesta

10

Eche un vistazo a django-reversion. Proporciona control de versiones para los modelos de Django. Se puede agregar fácilmente al proyecto existente.

No utiliza el método de puntero "actual". En su lugar, serializa el objeto cada vez que se guarda y lo almacena en un modelo separado Version con una clave externa genérica que apunta a este objeto. (Los campos de relación se serializan como claves principales de forma predeterminada). Además, permite agrupar Version s en Revision s de forma flexible.

para que pueda hacer algo así:

  • Cuando usuario carga CSV, simplemente guardar los cambios, como de costumbre, además de añadir @revision.create_on_success decorador a la función que realiza la importación, de modo que cualquier cambio en los registros realizados por dicha la función se almacenará bajo una única revisión.
  • Cuando el usuario pulsa "Deshacer", simplemente revierte la última revisión.

Así es como se podría hacer ::

@revision.create_on_success 
def import_csv(request, csv): 
    # Old versions of all objects save()d here will 
    # belong to single revision. 

def undo_last_csv_import(request): 
    # First, get latest revision saved by this user. 
    # (Assuming you create revisions only when user imports a CSV 
    # and do not version control other data.) 
    revision = Revision.objects.filter(user=request.user)\ 
     .order_by('-date_created')[0] 
    # And revert it, delete=True means we want to delete 
    # any newly added records as well 
    revision.revert(delete=True) 

Se basa en el hecho de que se crea revisiones sólo cuando las importaciones de usuario CSV. Esto significa que, si también planea controlar otros datos de la versión, tendrá que implementar algún tipo de indicador mediante el cual pueda obtener los registros afectados por la última importación. Luego puede obtener un registro con esta bandera, obtener la última versión guardada y revertir toda la revisión a la que pertenece la versión. Como esto ::

def undo_last_csv_import(request): 
    some_record = Record.objects.by_user(request.user).from_the_last_import()[0] 
    latest_saved_version_of_some_record = Version.objects.get_for_date(
     some_record, 
     datetime.now(), # The latest saved Version at the moment. 
     ) 
    # Revert all versions that belong to the same revision 
    # as the version we got above. 
    latest_saved_version_of_some_record.revision.revert() 

No es una solución hermosa, hay sin duda son maneras de hacerlo mejor con esta aplicación. Recomiendo echar un vistazo al código para comprender mejor cómo funciona el django-reversion, muy bien documentado, no se pudo encontrar una función sin una docstring.^_^D

(La documentación también es bueno, pero resultó ser un poco engañoso para mí, es decir, que escriben Version.objects.get_for_date(your_model, date), donde your_model es en realidad una instancia de modelo.)

Actualización: django-reversión se mantiene activamente, así que no confíe mucho en el código anterior, y compruebe mejor su wiki sobre cómo administrar las revisiones de versiones & fuera del administrador de django. Por ejemplo, los comentarios de revisión ya son compatibles, eso puede simplificar un poco las cosas.

3

Necesita tener control de versiones, y el problema no es Python o Django, sino más bien cómo diseñar la base de datos para hacer esto. Una forma común es almacenar documentos con identificaciones únicas y realizar un seguimiento de cuál es el "actual". Deshacer es simplemente cuestión de poner el puntero "actual" de nuevo a una revisión anterior. Esto es, hasta donde puedo ver, lo que estás haciendo.

Aunque esta es la forma habitual de hacerlo, no sé si es la mejor. Nunca he visto de otra manera, lo que podría significar que es lo mejor, o que la mejor manera es obvia. :-)

Hacer esto de forma genérica en Django es probablemente un problema difícil, pero será más fácil si haces que tu aplicación Django lo admita de forma personalizada.

Luego entra en Problemas de Diversión (no), como cómo editar cosas en una revisión "futura" y luego publicar todo un conjunto de documentos a la vez, en una especie de forma de contenido de etapas. Pero espero que no lo necesites. :-)

1

Su tabla de historial se ve bien, excepto que no necesita el campo new_value para realizar la operación de deshacer. Y sí, ese 'cómo' deshacer 'se implementa a menudo (la otra alternativa es el enfoque de Lennart de poner un número de versión en todos los registros). La ventaja de una tabla de diario separada es que no necesita tratar el número de versión en consultas regulares.

Cuestiones relacionadas