2010-01-08 10 views
9

Un par de veces me he encontrado con una situación, cuando al momento de ahorrar necesito saber qué campos de modelo se van a actualizar y actuar en consecuencia.Realizando un seguimiento de los cambios desde el último guardado en los modelos django

La solución más obvia de esto es tomar el campo de clave principal y recuperar una copia del modelo a partir de la base de datos:

class MyModel(models.Model): 

    def save(self, force_insert=False, force_update=False, using=None): 
     if self.id is not None: 
      unsaved_copy = MyModel.objects.get(id=self.id) 
      # Do your comparisons here 
     super(MyModel, self).save(force_insert, force_update, using) 

que funciona perfectamente bien, sin embargo, que llega a la base de datos para cada instancia de el modelo que está guardando (puede ser bastante incómodo si realiza muchos de esos intentos).

Es obvio que si uno puede "recordar" los valores del campo anterior al inicio de la vida de la instancia del modelo (__init__), no debería haber necesidad de recuperar una copia del modelo de la base de datos. Así me ocurrió con este pequeño truco:

class MyModel(models.Model): 

    def __init__(self, *args, **kwargs): 
     super(MyModel, self).__init__(*args, **kwargs) 
     self.unsaved = {} 
     for field in self._meta.fields: 
      self.unsaved[field.name] = getattr(self, field.name, None) 

    def save(self, force_insert=False, force_update=False, using=None): 
     for name, value in self.unsaved.iteritems(): 
      print "Field:%s Old:%s New:%s" % (name, value, getattr(self, name, None)) 
     # old values can be accessed through the self.unsaved member 
     super(MyModel, self).save(force_insert, force_update, using) 

Esto parece funcionar, sin embargo, hace uso de la interfaz no pública de django.db.models.Model.

¿Quizás alguien sepa una manera más limpia de hacerlo?

Respuesta

2

Creo que su solución parece razonable.

Alternativamente, podría tener un método de administrador llamado get_and_copy() (o algo similar) que colgara una copia del objeto original de lo que se devuelve. A continuación, puede usar otro método de administrador, save_and_check(), que aprovechó el original copiado.

FWIW: Si está jugando con plantillas contrib/admin hay una variable de contexto llamada original que es una copia del objeto original.

Actualización: Miré más de cerca lo que está haciendo el administrador. En class ModelAdmin (ubicado en django/contrib/admin/options.py) hay un método llamado construct_change_message(). Está siendo manejado por formset.changed_data y formset.changed_objects, por lo que django/forms/models.py class BaseModelFormSet es donde está la acción. Vea el método save_existing_objects(). También mire el método _existing_object(). Es un poco más complicado de lo que mencioné antes porque están tratando con la posibilidad de múltiples objetos, pero básicamente están almacenando en caché los resultados del conjunto de consultas en el primer acceso.

+0

No, no estaba preguntándolo en el contexto de las plantillas de administrador, sin embargo, verificará cómo se hace allí, gracias por la sugerencia. – shylent

0

Esto no funcionará para los accesorios. El comando loaddata usa models.Model.base_save. Probablemente el método más limpio sería usar descriptores para los campos, pero uno tiene que descubrir cómo insertarlos adecuadamente.

Cuestiones relacionadas