Аннотация Django с булевым полем ExpressionWrapper
Я пытаюсь создать статистическую таблицу/список высоких результатов для викторины, где таблица/список должна показывать процент (или общее количество) правильных угадываний человека, которого нужно было угадать. Если говорить более подробно, то вот модели, которые используются.
Модель викторины:
class Quiz(models.Model):
participants = models.ManyToManyField(
User,
through="Participant",
through_fields=("quiz", "correct_user"),
blank=True,
related_name="related_quiz",
)
fake_users = models.ManyToManyField(User, related_name="quiz_fakes")
user_quizzed = models.ForeignKey(
User, related_name="user_taking_quiz", on_delete=models.CASCADE, null=True
)
time_started = models.DateTimeField(default=timezone.now)
time_end = models.DateTimeField(blank=True, null=True)
final_score = models.IntegerField(blank=True, default=0)
У этих моделей также есть некоторые свойства; я считаю, что они не связаны с рассматриваемой проблемой.
Модель участника:
class Participant(models.Model): # QuizAnswer FK -> QUIZ
guessed_user = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="clicked_in_quiz", null=True
)
correct_user = models.ForeignKey(
User, on_delete=models.CASCADE, related_name="solution_in_quiz", null=True
)
quiz = models.ForeignKey(
Quiz, on_delete=models.CASCADE, related_name="participants_in_quiz"
)
@property
def correct(self):
return self.guessed_user == self.correct_user
Для итерации того, что я пытаюсь сделать, я попытаюсь объяснить, как, по моему мнению, это должно работать:
- For a
User
inUser.objects.all()
, find allparticipant
objects where theuser.id
equalscorrect_user
(fromparticipant
model) - For each
participant
object, evaluate ifcorrect_user
==guessed_user
- Sum each
participant
object where the above comparison isTrue
for theUser
, represented by a fieldsum_of_correct_guesses
- Return a queryset including all users with parameters [
User
,sum_of_correct_guesses
]
^ В идеале это должно быть percentage_of_correct_guesses
, но это уже домыслы, которые достаточно легко изменить, сделав sum_of_correct_guesses
/ сумму n раз, когда этот человек был угадан.
Сейчас я даже сделал псевдокод для одного человека, чтобы примерно проиллюстрировать для себя, как это должно работать, используя арифметику python
# PYTHON PSEUDO QUERY ---------------------
person = get_object_or_404(User, pk=3) # Example-person
y = Participant.objects.filter(
correct_user=person
) # Find participant-objects where person is used as guess
y_corr = [] # empty list to act as "queryset" in for-loop
for el in y: # for each participant object
if el.correct: # if correct_user == guessed_user
y_corr.append(el) # add to queryset
y_percentage_corr = len(y_corr) / len(y) # do arithmetic division
print("Percentage correct: ", y_percentage_corr) # debug-display
# ---------------------------------------------
Что я пробовал (пока безуспешно), так это использовать ExtensionWrapper с Count()
и Q
объектом:
percentage_correct_guesses = ExpressionWrapper(
Count("pk", filter=Q(clicked_in_quiz=F("id")), distinct=True)
/ Count("solution_in_quiz"),
output_field=fields.DecimalField())
all_users = (
User.objects.all().annotate(score=percentage_correct_guesses).order_by("score"))
Любая помощь или указания на ресурсы о том, как это сделать, будет очень признательна :))
Я нашел ответ, пока искал связанные проблемы: Django 1.11 Annotating a Subquery Aggregate
Что я сделал:
- Создайте фильтр с
OuterRef()
, который указывает наUser
и проверяет, совпадает лиUser
сcorrect_person
, а также сравниваетguessed_person
иcorrect_person
, выводит значениеcorrect_user
в наборе запросов для всех элементов, которые фильтр принимает. - Выполните аннотированный подсчет количества вхождений
correct_user
в отфильтрованном наборе запросов. - Аннотируйте
User
на основе аннотированного подсчета, это аннотация, которая действительно управляет всей операцией. Обратите внимание, какOuterRef()
иSubquery
используются, чтобы сообщить фильтру, какой пользователь должен бытьcorrect_user
.
Ниже приведен фрагмент кода, с помощью которого я заставил его работать, он очень похож на ответ-пост в вышеупомянутом вопросе:
from django.db.models import Count, OuterRef, Subquery, F, Q
crit1 = Q(correct_user=OuterRef('pk'))
crit2 = Q(correct_user=F('guessed_user'))
compare_participants = Participant.objects.filter(crit1 & crit2).order_by().values('correct_user')
count_occurrences = compare_participants.annotate(c=Count('*')).values('c')
most_correctly_guessed_on = (
User.objects.annotate(correct_clicks=Subquery(count_occurrences))
.values('first_name', 'correct_clicks')
.order_by('-correct_clicks')
)
return most_correctly_guessed_on
Это работает замечательно, благодаря Oli.