Тестирование django завершается ошибкой DoesNotExist, хотя на самом деле оно работает

Я пытаюсь проверить, может ли моя модель PlayerPoint дать мне 5 лучших игроков по их очкам. Вот модель Player:

class Player(AbstractUser):
    phone_number = models.CharField(
        max_length=14,
        unique=True,
        help_text="Please ensure +251 is included"
    )
    first_name = models.CharField(
        max_length=40,
        help_text="please ensure that you've spelt it correctly"
    )
    father_name = models.CharField(
        max_length=40,
        help_text="please ensure that you've spelt it correctly"
    )
    grandfather_name = models.CharField(
        max_length=40,
        help_text="please ensure that you've spelt it correctly"
    )
    email = models.EmailField(
        unique=True,
        help_text="please ensure that the email is correct"
    )
    age = models.CharField(max_length=3)
    profile_pic = models.ImageField(upload_to='profile_pix', default='profile_pix/placeholder.jpg')
    joined_ts = models.DateTimeField(auto_now_add=True, null=False)
    username = None

и это PlayerPoint модель:

class PlayerPoint(models.Model):
    OPERATION_CHOICES = (('ADD', 'ADD'), ('SUB', 'SUBTRACT'), ('RMN', 'REMAIN'))

    points = models.IntegerField(null=False, default=0)
    operation = models.CharField(
        max_length=3,
        null=False,
        choices=OPERATION_CHOICES,
        default=OPERATION_CHOICES[2][0]
    )
    operation_amount = models.IntegerField(null=False)
    operation_reason = models.CharField(null=False, max_length=1500)
    player = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        null=False,
        on_delete=models.PROTECT,
        to_field="phone_number",
        related_name="player_points"
    )
    points_ts = models.DateTimeField(auto_now_add=True, null=False)

У меня также есть обработчик сигнала перед сохранением.

@receiver(signals.pre_save, sender=PlayerPoint)
if sender is PlayerPoint:
    try:
        current_point = PlayerPoint.objects.filter(player=instance.player).latest()
    except PlayerPoint.DoesNotExist as pdne:
        if "new player" in instance.operation_reason.lower():
            print(f"{pdne} {instance.player} must be a new")
            instance.operation_amount = 100
            instance.points = int(instance.points) + int(instance.operation_amount)
        else:
            raise pdne
    except Exception as e:
        print(f"{e} while trying to get current_point of the player, stopping execution")
        raise e
    else:
        if instance.operation == PlayerPoint.OPERATION_CHOICES[0][0]:
            instance.points = int(current_point.points) + int(instance.operation_amount)
        elif instance.operation == PlayerPoint.OPERATION_CHOICES[1][0]:
            if int(current_point.points) < int(instance.operation_amount):
                raise ValidationError(
                    message="not enough points",
                    params={"points": current_point.points},
                    code="invalid"
                )
            instance.points = int(current_point.points) - int(instance.operation_amount)

Как вы можете видеть, существует отношение внешнего ключа. Перед запуском тестов, в setUp() я создаю очки для всех игроков следующим образом:

class Top5PlayersViewTestCase(TestCase):
    def setUp(self) -> None:
        self.player_model = get_user_model()

        self.test_client = Client(raise_request_exception=True)
        self.player_list = list()
        for i in range(0, 10):
            x = self.player_model.objects.create_user(
                phone_number=f"+2517{i}{i}{i}{i}{i}{i}{i}{i}",
                first_name="test",
                father_name="user",
                grandfather_name="tokko",
                email=f"test_user@tokko7{i}.com",
                age="29",
                password="password"
            )
            PlayerPoint.objects.create(
                operation="ADD",
                operation_reason="new player",
                player=x
            )
            self.player_list.append(x)

        counter = 500
        for player in self.player_list:
            counter += int(player.phone_number[-1:])
            PlayerPoint.objects.create(
                operation="ADD",
                operation_amount=counter,
                operation_reason="add for testing",
                player=player
            )
            PlayerPoint.objects.create(
                operation="ADD",
                operation_amount=counter,
                operation_reason="add for testing",
                player=player
            )
        return super().setUp()

При выполнении кода на localhost я могу вызвать latest() для получения последних очков каждого игрока и поместить их в список.

all_players = get_user_model().objects.filter(is_staff=False).prefetch_related('player_points')
top_5 = list()
for player in all_players:
   try:
       latest_points = player.player_points.latest()
   except Exception as e:
       print(f"{player} -- {e}")
       messages.error(request, f"{player} {e}")

но когда я делаю это из тестового кода django, он выдает ошибку self.model.DoesNotExist( commons.models.PlayerPoint.DoesNotExist: PlayerPoint matching query does not exist.

Что я делаю не так?

Моя благодарность заранее.

Я думаю, что ваша проблема заключается в этой строке:

latest_points = player.player_points.latest()

В частности, latest(). Как и get(), earliest() и latest() вызывают ошибку DoesNotExist, если не существует объекта с заданными параметрами.

Возможно, вам нужно добавить get_latest_by в класс Meta вашей модели. Возможно, попробуйте следующее:

class Player(AbstractUser):
    ...

    class Meta:
        get_latest_by = ['-joined_ts']

Если вы не хотите добавлять это в свою модель, вы можете сделать это напрямую:

latest_points = player.player_points.latest('-joined_ts')

если проблема в этом.

Изменить это:

counter += int(player.phone_number[-1:])

to

counter += int(player.phone_number[1:])

Полагаю, вы просто хотите взять часть номера телефона, а не '+' char.

Также, либо это:

top_5 = PlayerPoint.objects.order_by('-points')[:5]

должно быть:

top_5 = PlayerPoint.objects.order_by('-pk, -points_ts')[:5]

или вам нужно изменить get_latest_by в вашем классе PlayerPoint, Meta на:

class Meta:
    get_latest_by = ['pk', 'points']

Есть две проблемы.

  1. В вашем представлении top_5 - это несортированный список Player объектов.
top_5 = sorted(top_5, key=lambda player: player.player_points.latest().points, reverse=True)[:5]  # Add this
return render(request, "points_app/monthlytop5players.html", {"results": top_5[:5]})
  1. В вашем тесте top_5 - это список (на самом деле QuerySet) объектов PlayerPoint.
results_pts = [player.player_points.latest() for player in test_results.context['results']]  # Add this
for pt in top_5:
    # self.assertIn(pt, test_results.context.get('results'))  # Change this
    self.assertIn(pt, results_pts)                            # to this
Вернуться на верх