2010-12-13 43 views
11

(Django 1.1) Tengo un modelo de proyecto que realiza un seguimiento de sus miembros utilizando un campo m2m. Se ve así:Django - ¿Cómo guardar datos m2m a través de la señal post_save?

class Project(models.Model): 
    members = models.ManyToManyField(User) 
    sales_rep = models.ForeignKey(User) 
    sales_mgr = models.ForeignKey(User) 
    project_mgr = models.ForeignKey(User) 
    ... (more FK user fields) ... 

Cuando se crea el proyecto, el seleccionado sales_rep, sales_mgr, project_mgr, etc User s se añaden a los miembros para que sea más fácil hacer un seguimiento de permisos de proyecto. Este enfoque ha funcionado muy bien hasta ahora.

El problema que estoy tratando ahora es cómo actualizar la membresía del proyecto cuando uno de los campos User FK se actualiza a través del administrador. He intentado varias soluciones a este problema, pero el enfoque más limpio que parecía ser una señal de post_save como la siguiente:

def update_members(instance, created, **kwargs): 
    """ 
    Signal to update project members 
    """ 
    if not created: #Created projects are handled differently 
     instance.members.clear() 

     members_list = [] 
     if instance.sales_rep: 
      members_list.append(instance.sales_rep) 
     if instance.sales_mgr: 
      members_list.append(instance.sales_mgr) 
     if instance.project_mgr: 
      members_list.append(instance.project_mgr) 

     for m in members_list: 
      instance.members.add(m) 
signals.post_save.connect(update_members, sender=Project) 

Sin embargo, el Project todavía tiene los mismos miembros, incluso si cambio de uno de los campos a través de la ¡administración! He tenido éxito actualizando los campos m2m de miembros usando mis propios puntos de vista en otros proyectos, pero nunca tuve que hacerlo jugar bien con el administrador también.

¿Hay otro enfoque que debo tomar aparte de una señal post_save para actualizar la membresía? ¡Gracias de antemano por tu ayuda!

ACTUALIZACIÓN:

Solo para aclarar, la señal post_save funciona correctamente cuando guardo mi propia forma en el extremo delantero (miembros antiguos se eliminan, y los nuevos se añaden). Sin embargo, la señal post_save NO funciona correctamente cuando guardo el proyecto a través del administrador (los miembros permanecen igual).

creo diagnóstico de Peter Rowell es correcto en esta situación. Si elimino el campo "miembros" del formulario de administrador, la señal post_save funciona correctamente. Cuando se incluye el campo, guarda los miembros antiguos según los valores presentes en el formulario en el momento del guardado. Independientemente de los cambios que realice en el campo m2m de los miembros cuando se guarda el proyecto (ya sea una señal o un método de guardado personalizado), siempre será sobrescrito por los miembros que estaban presentes en el formulario antes del guardado. Gracias por señalar eso!

+2

No sé si este es su problema, pero tengo el presentimiento de que puede que esté ejecutando en un artefacto de la forma en que el código de formas actualiza la información de m2m. Básicamente, primero guardan el objeto principal, luego establecen los valores de m2m borrándolos primero y luego configurándolos en función de los valores presentes * en el formulario *. Esto sucede * después de * guardar() en el objeto principal, por lo que cualquier cosa que haga en guardar() o en función de la señal 'post_save' se hace primero, y luego * deshacer *. Esto está en 'django.forms.models.save_instance()'. Sería bueno si hubiera una señal 'after_form_save'. –

+0

¡Gracias, Peter! Creo que su diagnóstico es correcto. Actualicé mi publicación original para incluir esta información. –

+0

Peter tiene razón. Tuve el mismo problema y encontré una solución, pero no es una buena señal de 'after_form_save': http://stackoverflow.com/questions/3652585/simple-django-form-model-save-question –

Respuesta

4

no puedo ver nada malo con su código, pero estoy confundido en cuanto a por qué cree que el administrador debe trabajar diferente de cualquier otra aplicación.

Sin embargo, debo decir que creo que la estructura de su modelo es erróneo. Creo que debes deshacerte de todos esos campos de ForeignKey, y solo tienes ManyToMany, pero utiliza una tabla completa para hacer un seguimiento de los roles.

class Project(models.Model): 
    members = models.ManyToManyField(User, through='ProjectRole') 

class ProjectRole(models.Model): 
    ROLES = (
     ('SR', 'Sales Rep'), 
     ('SM', 'Sales Manager'), 
     ('PM', 'Project Manager'), 
    ) 
    project = models.ForeignKey(Project) 
    user = models.ForeignKey(User) 
    role = models.CharField(max_length=2, choices=ROLES) 
+0

Estoy de acuerdo en que se debe mejorar la estructura del modelo, pero estoy trabajando con una implementación anterior y tratando de aprovecharla al máximo. En este momento, no estoy listo para migrar el sistema a esta nueva estructura, pero tendré presente su sugerencia para el futuro. Gracias. –

6

Al haber tenido el mismo problema, mi solución es utilizar la señal m2m_changed. Puedes usarlo en dos lugares, como en el siguiente ejemplo.

El administrador al ahorro procederá a:

  • guardar los campos del modelo
  • emiten la señal post_save
  • para cada M2M:
    • emiten pre_clear
    • clara la relación
    • emitir post_clear
    • emiten pre_add
    • poblar de nuevo
    • emiten post_add

Aquí tienes un ejemplo sencillo que cambia el contenido de los datos guardados antes de guardarlo ella.

class MyModel(models.Model): 

    m2mfield = ManyToManyField(OtherModel) 

    @staticmethod 
    def met(sender, instance, action, reverse, model, pk_set, **kwargs): 
     if action == 'pre_add': 
      # here you can modify things, for instance 
      pk_set.intersection_update([1,2,3]) 
      # only save relations to objects 1, 2 and 3, ignoring the others 
     elif action == 'post_add': 
      print pk_set 
      # should contain at most 1, 2 and 3 

m2m_changed.connect(receiver=MyModel.met, sender=MyModel.m2mfield.through) 

También se puede escuchar a pre_remove, post_remove, y pre_clearpost_clear. En mi caso estoy usando para filtrar una lista ('cosas activas') dentro de los contenidos de otro ('cosas habilitados') independiente del orden en el que se guardan las listas:

def clean_services(sender, instance, action, reverse, model, pk_set, **kwargs): 
    """ Ensures that the active services are a subset of the enabled ones. 
    """ 
    if action == 'pre_add' and sender == Account.active_services.through: 
     # remove from the selection the disabled ones 
     pk_set.intersection_update(instance.enabled_services.values_list('id', flat=True)) 
    elif action == 'pre_clear' and sender == Account.enabled_services.through: 
     # clear everything 
     instance._cache_active_services = list(instance.active_services.values_list('id', flat=True)) 
     instance.active_services.clear() 
    elif action == 'post_add' and sender == Account.enabled_services.through: 
     _cache_active_services = getattr(instance, '_cache_active_services', None) 
     if _cache_active_services: 
      instance.active_services.add(*list(instance.enabled_services.filter(id__in=_cache_active_services))) 
      delattr(instance, '_cache_active_services') 
    elif action == 'pre_remove' and sender == Account.enabled_services.through: 
     # de-default any service we are disabling 
     instance.active_services.remove(*list(instance.active_services.filter(id__in=pk_set))) 

Si el "activado" los que se actualizan (borrados/eliminados + agregados, como en admin) luego los "activos" se guardan en caché y se borran en el primer pase ('pre_clear') y luego se agregan desde el caché después del segundo pase ('post_add') .

El truco fue actualizar una lista en las señales m2m_changed de la otra.

+0

¡Me salvaste el día! Gracias :) –

0

Me he atascado en la situación, cuando necesitaba encontrar el último elemento del conjunto de elementos, que se conectaba al modelo a través de m2m_field.

Tras la respuesta de Saverio, siguiente código resuelto mi problema:

def update_item(sender, instance, action, **kwargs): 
    if action == 'post_add': 
     instance.related_field = instance.m2m_field.all().order_by('-datetime')[0] 
     instance.save() 

m2m_changed.connect(update_item, sender=MyCoolModel.m2m_field.through) 
Cuestiones relacionadas