I think what's happening here is that you're stumbling over a really
subtle bug that I only noticed yesterday. It's not really related to
"through", since I saw it with many-to-many relations prior to that
field.
I may off on your particular case, since I haven't actually typed in the
models and looked at the query, but if you look at the SQL output
(my_queryset.query.as_sql()), I think you'll see it joining twice on the
intermediate table. Which is actually a good thing normally, but wrong
in this case. Here are the ugly details..
Firstly, you have to know about the difference between one filter() call
with multiple conditions and two chained filter() calls for multi-valued
(many-to-many, reverse many-to-one, etc) relations. See [1] for the
description from the Django docs. There are two legitimate desired
behaviours here, so Django provides a way to do both without adding
burdensome API, but it's a little tricky until you try it out.
The trick (problem!) here is that a related manager is really just a
queryset under the covers. So doc.members is a filter (on the doc_id
being equal to some specific value) across a reverse many-to-one
relation. That is, there are potentially multiple members for a single
doc instance. So when you write doc.members.filter(role__role=...),
Django is treating the filter() call as a separate distinct filter() in
the sense of the above documentation. Thus it's joining to a new copy of
the Role table.
What I need to do is make the first filter() after a related manager
"sticky" in the sense that it's treated as the same filter call, since
that's sort of the natural behaviour. This isn't entirely trivial to do
because querysets don't really know "where they've come from", but I've
been thinking about a solution as I wander around today and I think I
can make something work in the next day or two.
Your timing here is spectacular. Like I said, I had no idea this was an
issue 24 hours ago, but saw it in some work I was doing for a client as
we were wondering why they were seeing some duplicated results. Thus,
I'm on the case. If you'd reported this yesterday, I would have just
looked confused. Today I can at least say I know what's happening, but I
don't have a solution yet.
Regards,
Malcolm
P.S. Whilst writing this reply, I opened #8046 so that people can track
progress on this issue if they want. It's pretty high on my TODO list,
since it's tricky enough to be hard for people to debug if they
encounter it.