简体   繁体   中英

Django filter a reverse relationship of each item in a queryset

So i have 3 models

class User(models.Model):

class UserQuestions(models.Model):
      user = models.ForeignKey(User)
      question = models.ForeignKey(Question)

class UserAnswers(models.Model):
      user = models.ForeignKey(User)
      question = models.ForeignKey(Question)

I want to get a list of all the users, but i want to filter their userquestion_set and useranswer_set by a field of the question and then annotate the queryset with a count for the sum of userquestions_set and useranswer_set.

if i do something like:

User.objects.filter(Q(userquestions__question_field=value)&Q(useranswers__question_field=value)).annotate

It filters my Users, i want to filter their set of userquestions and useranswers so that they contain only the questions which have that field equal with my value, in order to get a proper annotation for the rows in the queryset.

Any help is much appreciated.

You are on the right track.

The resulting rows that your query will be evaluated across are similar to:

id | userquestion__id | userquestion__user_id | userquestion__value | usweranswer__id | useranswer__user_id | useranswer__value

for all combinations of User , UserQuest and UserAnswer where *__user_id equals id . So when you & your Q objects together, you only consider rows that match both Q objects. This is why your annotations will only work for users having both questions and answers that match your criteria. All other User s are filtered away.

Using | for the Q objects won't help you much either, because the unmatched criteria will count as false positives and vice versa.

So your only option is to first load all the users, then load all the users with the right answer annotated with answer count, and then load all the users with the right question annotated with question count. Finally you should annotate users with the relevant counts.

Edit (Example): The following is not tested, and I apology for my rusty Python:

users = User.objects.all()
users_with_right_answers = defaultdict(
    int,
    {
        user.id: user.right_answer_count for user in User.objects.filter(
            useranswer__value='...').annotate(
            right_answer_count=Count('useranswer')
    }
)
users_with_right_questions = defaultdict(
    int,
    {
        user.id: user.right_question_count for user in User.objects.filter(
            userquestion__value='...').annotate(
            right_question_count=Count('userquestion')
    }
)

for user in users:
    user.right_answer_count = users_with_right_answers[user.id]
    user.right_question_count = users_with_right_questions[user.id]

The idea is that we get to do everything in three simple queries, since it is not possible to do in just one.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM