Search code examples
djangodjango-modelsdjango-ormdjango-signals

Django filter with manytomanyfield


This is my models

class Person(models.Model):
    name = models.CharField(max_length=50)
    is_group_member = models.BooleanField(default=False)

class Group(models.Model):
    members = models.ManyToManyField(Person)


@receiver(post_save, sender=Group)       
def set_group_member_type(sender, instance, created, **kwargs):
    if created:
        # change the all group members' type

I am trying to update is_group_member field with signal.

When a group is created with members, then i will filter and retrieve all the person who are group member, then i will change the is_group_member value to True

Can anyone help me to achieve this?


Solution

  • You can do that, but usually not by signals, since the group is created before the members are added. The trigger will thus be fired before there are members.

    Furthermore it is likely not a good idea at all to define such field. If you later remove a member from a group, or add other members, it will be hard to update all the members accordingly. A person might still be a member of a different group. It is a form of data duplication, and it turns out that synchronizing data, is usually a harder problem than it looks.

    It therefore is better to either add a property to your Person model, or annotate your objects queryset.

    Option 1: Add a property

    We can add a property that checks if the Person is a member of a group. This will perform a query to the database, for each person where we query this:

    class Person(models.Model):
        name = models.CharField(max_length=50)
    
        @property
        def is_group_member(self):
            return Group.objects.filter(members=self).exists()

    Option 2: Customize the objects manager

    We can annotate the objects manager, such that it will run a subquery for each Person in the queryset. We can add this for example to the objects manager:

    from django.db.models import Exists, OuterRef
    
    class PersonManager(models.Manager):
    
        def get_queryset(self):
            from app.models import Group
            return super().get_queryset().annotate(
                is_group_member=Exists(Group.objects.filter(members=OuterRef('pk')))
            )

    We can then add the manager to the Person class:

    class Person(models.Model):
        name = models.CharField(max_length=50)
    
        objects = PersonManager()