2009-12-17 2 views
14

Je rencontre des problèmes avec ManytoMany Relations qui ne mettent pas à jour dans un modèle lorsque je l'enregistre (via l'admin) et j'essaie d'utiliser la nouvelle valeur dans une fonction attachée à le signal post_save ou au sein du save_model de le AdminModel associé. J'ai essayé de recharger l'objet dans ces fonctions en utilisant la fonction get avec l'id .. mais il a toujours les anciennes valeurs.Problème avec ManyToMany Les relations ne sont pas mises à jour immédiatement après l'enregistrement

Est-ce un problème de transaction? Y a-t-il un signal émis lorsque la transaction se termine?

Merci,

+0

Alors, changez vous le pk d'objets? –

+0

J'ai un objet, et il y a une relation manytomany avec d'autres, mais je peux obtenir une relation mise à jour – diegueus9

Répondre

20

Lorsque vous enregistrez un modèle via un formulaire d'administration ce n'est pas une transaction atomique. L'objet principal est enregistré en premier (pour s'assurer qu'il a un PK), puis le M2M est effacé et les nouvelles valeurs définies à tout ce qui est sorti du formulaire. Donc, si vous êtes dans le save() de l'objet principal, vous êtes dans une fenêtre d'opportunité où le M2M n'a pas encore été mis à jour. En fait, si vous essayez pour faire quelque chose au M2M, le changement sera effacé par le clear(). Je me suis heurté à ça il y a environ un an.

Le code a quelque peu changé depuis les jours du refactor de pré-ORM, mais il se résume au code django.db.models.fields.ManyRelatedObjectsDescriptor et ReverseManyRelatedObjectsDescriptor. Regardez leurs méthodes __set __() et vous verrez manager.clear(); manager.add(*value) Que clear() complete nettoie toutes les références M2M pour l'objet principal actuel dans cette table. L'add() définit ensuite les nouvelles valeurs. Donc, pour répondre à votre question: oui, il s'agit d'un problème de transaction.

Y a-t-il un signal émis à la fin de la transaction? Rien officiel, mais lire:

Il y avait un related thread a few months ago et MonkeyPatching était une méthode proposée. Grégoire posted a MonkeyPatch pour cela. Je ne l'ai pas essayé, mais on dirait que ça devrait marcher.

+1

J'ai rencontré ce problème aussi, j'ai utilisé une solution trouvée ici http://stackoverflow.com/questions/6200233/manytomany -field-not-saved-when-using-django-admin –

+1

Corrigez-moi si je me trompe mais cette réponse de @peterrowell est toujours valable à partir de la version actuelle de Django (1.10). Le champ M2M est ** effacé ** en premier, puis rempli avec les données du formulaire. –

+0

@nik_m: Ça fait longtemps que je n'ai pas regardé ça. Le problème fondamental est que ceci est intrinsèquement * pas * une transaction atomique parce que le modèle primaire * doit * être * créé/modifié avant que le m2m soit créé/mis à jour (parce que le m2m fait référence aux PKID des parents). Il peut y avoir un work-around mentionné [ici] (http://stackoverflow.com/a/23796604/17017) et [ici] (https://docs.djangoproject.com/fr/1.10/ref/signals/# m2m-changé). Bonne chance! –

0

Une autre approche sans un correctif de Monkey est avec celery, vous pouvez faire une tâche qui aura accès aux données correctes pour la relation M2M, c'est parce que la tâche fonctionne asynchrone et si vous mettez un délai de 30 secondes, aproxim, vous sera sûr que la transaction a été terminée.

Vous devez appeler Task.apply_async(args=[...], countdown=30) dans les signaux post_save ou pre_save.

0

Vous pouvez trouver plus d'informations sur ce sujet: Django manytomany signals?

+0

Je l'ai essayé en django 1.4, c'est inutile, en utilisant le signal m2m_changed, assigner des valeurs m2m provoquerait une erreur de resourcing, si vous déconnectez puis assignez, alors 'instance.save()', la mise à jour n'a pas vraiment fonctionné. – est

5

J'ai une solution générale à ce qui semble un peu plus propre que le singe-patcher le noyau ou même en utilisant le céleri (même si je suis sûr que quelqu'un pourrait trouver des domaines où il échoue). Fondamentalement, j'ajoute une méthode clean() dans l'admin pour le formulaire qui a les relations m2m, et définissez les relations d'instance à la version clean_data. Cela rend les données correctes disponibles pour la méthode de sauvegarde de l'instance, même si ce n'est pas encore "dans les livres".Essayez et voir comment ça se passe:

def clean(self, *args, **kwargs): 
    # ... actual cleaning here 
    # then find the m2m fields and copy from cleaned_data to the instance 
    for f in self.instance._meta.get_all_field_names(): 
     if f in self.cleaned_data: 
      field = self.instance._meta.get_field_by_name(f)[0] 
      if isinstance(field, ManyToManyField): 
       setattr(self.instance,f,self.cleaned_data[f]) 
+0

y at-il des mises à jour à ce problème pour 1.4? – est

3

Voir http://gterzian.github.io/Django-Cookbook/signals/2013/09/07/manipulating-m2m-with-signals.html

problème: Lorsque vous manipulez le m2m d'un modèle dans un récepteur de signal post ou pre_save, vos modifications s'anéanties dans la clairière ultérieure » 'du m2m par Django.

solution: Dans votre gestionnaire de signal post ou pre_save, enregistrez un autre gestionnaire au signal m2m_changed sur le modèle intermédiaire m2m du modèle dont vous voulez mettre à jour le m2m.

Veuillez noter que ce second gestionnaire recevra plusieurs signaux m2m_changed, et il est essentiel de tester la valeur des arguments 'action' passés avec eux.

Dans ce second gestionnaire, vérifiez l'action 'post_clear'. Quand vous recevez un signal avec l'action post_clear, le m2m a été effacé par Django et vous avez une chance de le manipuler avec succès.

un exemple:

def save_handler(sender, instance, *args, **kwargs): 
    m2m_changed.connect(m2m_handler, sender=sender.m2mfield.through, weak=False) 


def m2m_handler(sender, instance, action, *args, **kwargs): 
    if action =='post_clear': 
     succesfully_manipulate_m2m(instance) 


pre_save.connect(save_handler, sender=YouModel, weak=False) 

voir https://docs.djangoproject.com/en/1.5/ref/signals/#m2m-changed

4

Lorsque vous essayez d'accéder aux champs de ManyToMany dans le signal post_save du modèle les objets connexes ont déjà été supprimés et ne seront pas ajoutés à nouveau jusqu'à ce que après le signal est terminé.

Pour accéder à ces données, vous devez lier la méthode save_related sur votre ModelAdmin. Malheureusement, vous devrez également inclure le code dans le signal post_save pour les demandes non-administrateur qui nécessitent votre personnalisation.

voir: https://docs.djangoproject.com/en/1.7/ref/contrib/admin/#django.contrib.admin.ModelAdmin.save_related

Exemple:

# admin.py 
Class GroupAdmin(admin.ModelAdmin): 
    ... 
    def save_related(self, request, form, formsets, change): 
     super(GroupAdmin, self).save_related(request, form, formsets, change) 
     # do something with the manytomany data from the admin 
     form.instance.users.add(some_user) 

Ensuite, dans vos signaux, vous pouvez apporter les mêmes modifications que vous souhaitez exécuter sur une sauvegarde:

# signals.py 
@receiver(post_save, sender=Group) 
def group_post_save(sender, instance, created, **kwargs): 
    # do somethign with the manytomany data from non-admin 
    instance.users.add(some_user) 
    # note that instance.users.all() will be empty from the admin: [] 
+0

cela devrait certainement être la bonne réponse – psychok7

Questions connexes