Paul Tomblin
Paul Tomblin

Reputation: 182772

Filtering on foreign key and Many-to-many join tables

I've got models where the objects are date dependent, but also the relationship between them is date dependent. So, just for illustration, I might have

class ValidCommonManager(models.Manager):
    ValidCommonManager(self, date):
        self.date = date

    def get_query_set(self):
        return super(ValidCommonManager, self).get_query_set().filter(
            models.Q(start__isnull=True)|
            models.Q(start__lte=self.date)
        ).filter(
            models.Q(end__isnull=True)|
            models.Q(end__gte=self.date)
        )

class CommonModel(models.Model):
    start = models.DateTimeField(null=True)
    end = models.DateTimeField(null=True)

    valid_objects = ValidCommonManager(now())
    objects = models.Manager()

class Group(CommonModel):
    name = models.CharField(max_length=25)
    description = models.TextField(blank=True)

    group_members = models.ManyToManyField(
      'Person', through='GroupMember')

class GroupMember(CommonModel):
    group = models.ForeignKey(Group)
    member = models.ForeignKey(Person)

class Person(CommonModel):
    name = models.CharField(max_length=100)

Now the group might exist from date A to date Z, but the membership might be different at date B and date C, which is reflected by the start and end fields in GroupMember. Now it's easy to find groups that are active at the time I'm currently processing using

groups = Group.valid_objects.all()

But I can't figure out an easy way to get the members of the group at a given date short of

members_of_my_group = group.group_members.filter(
            models.Q(start__isnull=True)|
            models.Q(start__lte=date)
        ).filter(
            models.Q(end__isnull=True)|
            models.Q(end__gte=date)
        ).filter(
            models.Q(group_member__start__isnull=True)|
            models.Q(group_member__start__lte=date)
        ).filter(
            models.Q(group_member__end__isnull=True)|
            models.Q(group_member__end__gte=date)
        )

which rapidly gets clumsy and violates the basics of DRY. I'd set use_for_related_fields on ValidCommonManager, but the Django docs promise nasty things if you do that on a manager that filters.

Upvotes: 1

Views: 116

Answers (1)

Kevin Christopher Henry
Kevin Christopher Henry

Reputation: 48922

Related managers are just for convenience, you could always define a custom Manager for Person that would do the same thing. (Also note that as of Django 1.7 you can specify the manager you want to use for related lookups on a case-by-case basis. See the docs for more information.)

That is,

group_members = Group.group_members.filter(...)

is equivalent to:

class PersonManager(models.Manager):

    def get_valid_members(self, group):
        return self.filter(groupmember__group=group, 
                           Q(groupmember__start__isnull=True)...

group_members = Person.objects.get_valid_members(group)

Another approach would be to use subqueries:

group_members = Person.objects.filter(groupmember__in=
                    GroupMember.valid_objects.filter(group=group))

Upvotes: 1

Related Questions