Repository: tough-dev-school/education-backend

Issue:
Слать в лототрон только один ответ от одного пользователя
Сейчас, если пользователь дал три ответа на домашку, то при кросс-проверке все три ответа уйдут разным студентам. Это — плохо, нужно либо слать только первый ответ, либо собирать все ответы в пачку и слать их одному пользователю.


Discussion:

Resolving patch:
diff --git a/src/homework/services/answer_crosscheck_dispatcher.py b/src/homework/services/answer_crosscheck_dispatcher.py
--- a/src/homework/services/answer_crosscheck_dispatcher.py
+++ b/src/homework/services/answer_crosscheck_dispatcher.py
@@ -9,11 +9,12 @@
 
 class AnswerCrossCheckDispatcher:
     """Given a bunch of answers and users, create a cross-check record
-    for each of them, making sure each answer has a user to check
-    and number of answers if equal for each user
+    for each of them, making sure the first answer of each user has a user to
+    check and number of answers if equal for each user
     """
     def __init__(self, answers: QuerySet[Answer], answers_per_user: int = 3):
         self.answers = Answer.objects.filter(pk__in=[answer.pk for answer in answers])
+        self.unique_author_answers = self.answers.order_by('author_id', 'created').distinct('author_id')
         self.users = User.objects.filter(pk__in=[answer.author_id for answer in answers]).order_by('?')
         self.answers_per_user = answers_per_user
 
@@ -27,11 +28,11 @@ def __call__(self) -> list[AnswerCrossCheck]:
                     crosschecks.append(
                         self.give_answer_to_user(answer, user),
                     )
-
         return crosschecks
 
     def get_answer_to_check(self, user: User) -> Optional[Answer]:
         return self.get_answers_with_crosscheck_count() \
+            .filter(id__in=self.unique_author_answers) \
             .annotate(already_checking=Count('answercrosscheck', filter=Q(answercrosscheck__checker_id=user.id))) \
             .exclude(already_checking__gte=1) \
             .exclude(author=user) \


Test patch:
diff --git a/src/homework/tests/cross_check/tests_dispatch_answers_of_the_same_user.py b/src/homework/tests/cross_check/tests_dispatch_answers_of_the_same_user.py
new file mode 100644
--- /dev/null
+++ b/src/homework/tests/cross_check/tests_dispatch_answers_of_the_same_user.py
@@ -0,0 +1,38 @@
+import pytest
+
+from homework.models import AnswerCrossCheck
+
+pytestmark = [
+    pytest.mark.django_db,
+    pytest.mark.repeat(10),  # we have to repeat this test suite because the method does randomizing
+]
+
+
+@pytest.fixture
+def answers(mixer, question, user, another_user):
+    return mixer.cycle(2).blend('homework.Answer', question=question, author=user, parent=None) \
+        + [mixer.blend('homework.Answer', question=question, author=another_user, parent=None)]
+
+
+@pytest.fixture
+def dispatcher(dispatcher, answers):
+    return dispatcher(answers=answers)
+
+
+@pytest.fixture
+def get_answer_to_check(dispatcher):
+    def _get_answer_to_check(user):
+        return dispatcher.get_answer_to_check(user)
+
+    return _get_answer_to_check
+
+
+def test_first_answer_is_got(get_answer_to_check, answers, another_user):
+    assert get_answer_to_check(another_user) == answers[0]
+
+
+def test_other_answers_are_ignored(dispatcher, answers):
+    dispatcher()
+
+    assert AnswerCrossCheck.objects.filter(answer=answers[0]).exists()
+    assert not AnswerCrossCheck.objects.filter(answer=answers[1]).exists()