2010-07-09 12 views
13

Cuando se realiza una actualización/creación en un modelo Django (.save()) me gustaría poder "entrar" y comparar algunos atributos particulares a los que fueron configurados previamente (si existieron previamente).Django: Antes de actualizar un modelo, me gustaría "ver" sus atributos anteriores

Estoy pensando Pre-Guardar Señales, con una búsqueda del modelo original haciendo un .objects.get(instance.id), pero eso se siente como un desperdicio. Además, ¿la validación ya ocurrió en pre_save()?

Respuesta

7

sobre model validation:

Tenga en cuenta que full_clean() no se llamará automáticamente cuando se llama al método de su modelo, salvo()

Luego, sobre la pre-save signal, tenga en cuenta que se obtiene la instancia que se está guardando enviado como un parámetro con el mensaje. Como la versión anterior de su modelo existe solo en la base de datos, no veo en qué otro lugar podría obtener el valor anterior de los atributos ...

No dice por qué quiere hacer esto, así que es difícil decirlo, pero otras soluciones que estoy pensando en este momento:

* defining a custom signal that is sent everytime the attributes you are interested in are modified... This signal would then send two arguments : new value, old value 
* perform the check directly when setting the attributes 

Si le das más detalles, podría ser más fácil ...

EDIT:

Así es ... Si usted emite un 'foo_has_updated' personalizado, no estará seguro de que la modificación se haya guardado.

En este caso, supongo que puede almacenar en caché las variables que le interesan al inicializar la instancia y capturar la señal de guardado o guardado previo.

* With pre-save, you would be able to pre-process the data, but the saving operation might fail 
* With post-save, you would be sure that the data has been saved. 

Almacenamiento en caché de las variables se podría hacer así:

class CachedModel(models.Model): 
    cached_vars = [var1, var2, varN] 
    def __init__(self, *args, **kwargs): 
     super(CachedModel, self).__init__(*args, **kwargs) 
     self.var_cache = {} 
     for var in self.cached_vars: 
      self.var_cache[var] = copy.copy(getattr(self, var)) 

O algo así ... Luego, en el manejador de la señal:

def post_save_handler(sender, **kwargs): 
    instance = kwargs["instance"] 
    [(instance.var_cache[var], getattr(instance, var)) for var in instance.cached_var] 
    #[(<initial value>, <saved value>) 

Y tienes lo que necesitabas (Creo)!!!

+0

Gracias por la respuesta. Específicamente, y aún bastante genéricamente, me gustaría saber cuándo se ha cambiado un campo en particular (un campo de elección, por ejemplo). Yendo más lejos, me gustaría hacer exactamente lo que dijiste, emitir una señal personalizada cuando esto ocurra. Volviendo a mi pregunta, cuando se actualiza un modelo, ¿cómo puedo comparar sus valores anteriores para enviar esta señal? (Si no se garantiza que el objeto se guarde antes de post_save, no parece correcto emitir una señal foo_has_updated, sin verificar ... pero esa es otra pregunta). –

0

Puede solicitar los valores actualmente almacenados en la base de datos antes de guardar. He hecho esto para registrar solo los valores modificados, pero causa otra consulta de base de datos cada vez que guardas.

0

Aunque estoy muy de acuerdo con Sébastien Piquemal's answer terminé usando las señales pre_save y post_save. En lugar de anular __init__(), hago algo muy similar en pre_save, y luego verifico/comparo los valores en post_save y emito una señal personalizada desde allí si se cumplen ciertas condiciones.

Creo que todavía aceptaré su respuesta, por el tiempo que pasé en ella. No veo dónde estamos haciendo las cosas muy diferentes, excepto que estoy haciendo mi trabajo en una señal y lo está haciendo durante la inicialización.

2

Aquí está mi idea: jugar con las propiedades.

Digamos que tiene esta clase:

class Foo(models.Model): 
    name = models.CharField() 

En su lugar, cambiar el nombre de su campo (no necesitará una migración si es la primera vez que estás haciendo esto) y:

class Foo(models.Model): 
    _name = models.CharField() 

    @property 
    def name(self): 
     return self._name 

    @name.setter 
    def name(self, new_value): 
     if not getattr(self, '_initial_name', False): 
      self._initial_name = self._name 

     if new_value != self._initial_name: 
      self._name_changed = True 
     else: 
      self._name_changed = False 

     self._name = new_value 

Agregamos dos atributos a sus instancias de Foo: '_nitial_name' y '_name_changed' y una propiedad: 'name'. Estos no son campos modelo y nunca se guardarán en la base de datos. Además, no tendrá que meterse con el campo '_name' por más tiempo, siempre que la propiedad 'nombre' se encargue de todo.

Ahora, su 'pre_save' o controlador de señal 'post_save' pueden hacer comprobaciones sobre lo que se cambió:

def handle_pre_save(sender, **kwargs): 
    foo = kwargs['instance'] 
    if getattr(foo, '_name_changed', False): 
     log.debug("foo changed its name from '%s' to '%s'", 
        foo._initial_name, foo.name) 
0

Usted puede utilizar la señal pre_save y comparar el registro db (la versión antigua) con el registro de instancias (actualizado, pero no guardado en la versión db).

Tome un modelo como éste como ejemplo:

class Person(models.Model): 
    Name = models.CharField(max_length=200) 

En función pre_save se puede comparar la versión instancia con la versión db.

def check_person_before_saving(sender, **kwargs): 
    person_instance = kwargs['instance'] 
    if person_instance.id: 
     # you are saving a Person that is already on the db 
     # now you can get the db old version of Person before the updating 

     # you should wrap this code on a try/except (just in case) 
     person_db = Person.objects.get(id=person_instance.id) 

     # do your compares between person_db and person_instance 
     ... 

# connect the signal to the function. You can use a decorator if you prefer 
pre_save.connect(check_person_before_saving, sender=Person) 
Cuestiones relacionadas