Django - How to save m2m data via post_save signal?

前端 未结 3 1712
爱一瞬间的悲伤
爱一瞬间的悲伤 2020-12-05 08:08

(Django 1.1) I have a Project model that keeps track of its members using a m2m field. It looks like this:

class Project(models.Model):
    members = models         


        
3条回答
  •  攒了一身酷
    2020-12-05 08:43

    Having had the same problem, my solution is to use the m2m_changed signal. You can use it in two places, as in the following example.

    The admin upon saving will proceed to:

    • save the model fields
    • emit the post_save signal
    • for each m2m:
      • emit pre_clear
      • clear the relation
      • emit post_clear
      • emit pre_add
      • populate again
      • emit post_add

    Here you have a simple example that changes the content of the saved data before actually saving it.

    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)
    

    You can also listen to pre_remove, post_remove, pre_clear and post_clear. In my case I am using them to filter one list ('active things') within the contents of another ('enabled things') independent of the order in which lists are saved:

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

    If the "enabled" ones are updated (cleared/removed + added back, like in admin) then the "active" ones are cached and cleared in the first pass ('pre_clear') and then added back from the cache after the second pass ('post_add').

    The trick was to update one list on the m2m_changed signals of the other.

提交回复
热议问题