Question

Je vais avoir des problèmes avec ManyToMany Les relations qui ne sont pas mise à jour dans un modèle quand je l'enregistrer (via l'admin) et essayez d'utiliser la nouvelle valeur dans un fonction attachée au signal de post_save ou dans le save_model de le AdminModel associé. J'ai essayé de recharger l'objet dans ces fonctions à l'aide du obtenir la fonction avec l'id .. mais il a encore les anciennes valeurs.

Est-ce un problème de transaction? Y at-il un signal jeté lorsque le transaction se termine?

Merci,

Était-ce utile?

La solution

Lorsque vous enregistrez un modèle via un formulaire d'administration, il n'est pas une transaction atomique. L'objet principal est enregistré d'abord (pour vous assurer qu'il a un PK), le M2M est effacé et les nouvelles valeurs définies à tout ce qui est sorti de la forme. Donc, si vous êtes dans la sauvegarde () 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 s'effacé par le clair (). Je suis tombé sur ce il y a environ un an.

Le code a changé quelque peu les jours de pré-réusiner ORM, mais il se résume à code dans django.db.models.fields.ManyRelatedObjectsDescriptor et ReverseManyRelatedObjectsDescriptor. Regardez leurs méthodes __ de __set () et vous verrez que manager.clear(); manager.add(*value) claire et complète () nettoie toutes les références M2M pour l'objet courant principal dans ce tableau. Le module complémentaire () définit ensuite les nouvelles valeurs.

Pour répondre à votre question. Oui, il est une question de transaction

Y at-il un signal lancé lorsque la transaction se termine? Rien d'officiel, mais lu sur:

Il y avait un fil lié il y a quelques mois et MonkeyPatching était une méthode proposée. Grégoire a affiché un pour cette monkey-patch. Je ne l'ai pas essayé, mais il semble que cela devrait fonctionner.

Autres conseils

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 le signal est terminé.

Pour accéder à ces données, vous devez lier dans la méthode save_related dans votre ModelAdmin. Malheureusement, vous aurez également à inclure le code dans le signal post_save pour les demandes non-administrateurs qui nécessitent la 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: []

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ù elle échoue). Fondamentalement, j'ajoute une méthode propre () dans l'administration de la forme qui a les relations m2m, et définir les relations d'instance à la version cleaned_data. Cela rend les données correctes disponibles à la méthode par exemple économisons, même si ce n'est pas « sur les livres » encore. Essayez et voir comment il va:

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])

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 à l'intérieur d'un poste ou d'un récepteur de signaux pre_save, vos modifications s'anéanties dans la « compensation » ultérieure du m2m par Django.

solution

: En vous publiez ou gestionnaire de signaux pre_save, enregistrer un autre gestionnaire au signal m2m_changed sur le modèle intermédiaire m2m du modèle dont m2m vous souhaitez mettre à jour.

S'il vous plaît noter que ce second gestionnaire recevra plusieurs signaux m2m_changed, et il est essentiel de tester la valeur des arguments « action » passé avec eux.

Dans ce second gestionnaire, vérifiez l'action "post_clear. Lorsque vous recevez un signal avec l'action post_clear, le m2m a été effacée par Django et vous avez une chance de 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 / signaux / # m2m-changé

Vous pouvez trouver plus d'informations sur ce sujet:? signaux Django ManyToMany

L'une des solutions pour mettre à jour m2m, ainsi que la mise à jour de vos modèles.

Django 1.11 and higher

Tout d'abord, toutes les demandes via le panneau d'administration sont atomiques. Vous pouvez regarder ModelAdmin:

@csrf_protect_m
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
    with transaction.atomic(using=router.db_for_write(self.model)):
        return self._changeform_view(request, object_id, form_url, extra_context)

@csrf_protect_m
def delete_view(self, request, object_id, extra_context=None):
    with transaction.atomic(using=router.db_for_write(self.model)):
        return self._delete_view(request, object_id, extra_context)

Le comportement que vous pouvez observer lors de la mise à jour, lorsque des modifications que vous avez faites avec des enregistrements m2m ne sont pas sauvegardés, même après que vous les avez faites dans une méthode de sauvegarde de vos modèles ou dans un signal, ne se produit que parce que la forme m2m réécrit tous les enregistrements après que l'objet principal est mis à jour.

C'est pourquoi, étape par étape:

  1. Le but principal est mis à jour.

  2. Votre code (dans une méthode de sauvegarde ou dans un signal) a apporté des modifications (vous pouvez les regarder, il suffit de mettre un point d'arrêt dans ModelAdmin):

 def save_related(self, request, form, formsets, change):
     breakpoint()
     form.save_m2m()
     for formset in formsets:
         self.save_formset(request, form, formset, change=change)
  1. form.save_m2m () prend toutes les valeurs m2m qui ont été placés sur une page (grosso modo) et remplacent tous les enregistrements m2m par un gestionnaire associé. Voilà pourquoi vous ne pouvez pas voir vos modifications à la fin d'une transaction.
  

Il y a une solution: apportez vos modifications avec m2m via   transaction.on_commit. transaction.on_commit fera vos modifications   après form.save_m2m () lorsque la transaction est validée.

Malheureusement, l'inconvénient de cette solution -. Avec vos modifications m2m seront exécutées dans une transaction séparée

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top