Django - How to save m2m data via post_save signal?

前端 未结 3 1710
爱一瞬间的悲伤
爱一瞬间的悲伤 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:42

    I've stuck on situation, when I needed to find latest item from set of items, that connected to model via m2m_field.

    Following Saverio's answer, following code solved my issue:

    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)
    
    0 讨论(0)
  • 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.

    0 讨论(0)
  • 2020-12-05 09:00

    I can't see anything wrong with your code, but I'm confused as to why you think the admin should work any different from any other app.

    However, I must say I think your model structure is wrong. I think you need to get rid of all those ForeignKey fields, and just have a ManyToMany - but use a through table to keep track of the 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 讨论(0)
提交回复
热议问题