Skip to content

Leaderboard Pagination.#2319

Open
IdirLISN wants to merge 4 commits intodevelopfrom
feature/leaderboard_pagination
Open

Leaderboard Pagination.#2319
IdirLISN wants to merge 4 commits intodevelopfrom
feature/leaderboard_pagination

Conversation

@IdirLISN
Copy link
Copy Markdown
Collaborator

@IdirLISN IdirLISN commented Apr 7, 2026

@ mention of reviewers

@Didayolo

A brief description of the purpose of the changes contained in this PR.

This PR adds the pagination leaderboard table. It uses DynamicChoicePagination class to enable the display of 50 (default value), 100, 500, All objects. All is set up at 1000 objects maximum but it can be changed easily on the pagination class.

Issues this PR resolves

#2288

It resolve potential slow interaction with leaderboard table when there is a lot of objects to display inside of it.

image

A checklist for hand testing

  • Create a competition
  • Use the script below in django shell to generate the leaderboard (1000 result for competition id=16, change this value)
  • Reach the end of the leaderboard to try the UI.

script:

from django.apps import apps
from django.contrib.auth import get_user_model
from django.db import transaction
from random import uniform, choice
from decimal import Decimal

# ---------------- CONFIG ----------------
COMPETITION_ID = 16
NB_SUBMISSIONS = 1000
NB_USERS = 10  # nombre d'utilisateurs simulés
# ---------------------------------------

Competition = apps.get_model("competitions", "Competition")
Phase = apps.get_model("competitions", "Phase")
Submission = apps.get_model("competitions", "Submission")
SubmissionScore = apps.get_model("leaderboards", "SubmissionScore")

User = get_user_model()

competition = Competition.objects.get(pk=COMPETITION_ID)
phase = Phase.objects.filter(competition=competition).first()

if not phase:
    raise RuntimeError("Aucune phase trouvée pour cette compétition")

leaderboard = phase.leaderboard
columns = list(leaderboard.columns.all())

if not columns:
    raise RuntimeError("Aucun column dans le leaderboard")

# ---------------- USERS ----------------
users = list(User.objects.all()[:NB_USERS])

if not users:
    raise RuntimeError("Aucun utilisateur en base")

print(f"Utilisateurs utilisés: {[u.username for u in users]}")
print(f"Colonnes détectées: {[c.title for c in columns]}")

# ---------------- CREATION ----------------
submissions_to_create = []

print("Création des submissions...")

for i in range(NB_SUBMISSIONS):
    submissions_to_create.append(
        Submission(
            owner=choice(users),
            phase=phase,
            status=Submission.FINISHED,
            appear_on_leaderboards=True,
            leaderboard=leaderboard,
            name=f"pagination_test_{i}"
        )
    )

with transaction.atomic():
    Submission.objects.bulk_create(submissions_to_create, batch_size=500)

# récupérer les submissions créées
created_submissions = list(
    Submission.objects.filter(
        phase=phase,
        leaderboard=leaderboard,
        name__startswith="pagination_test_"
    ).order_by("-id")[:NB_SUBMISSIONS]
)

print(f"{len(created_submissions)} submissions créées")

# ---------------- SCORES ----------------
print("Création des scores...")

scores_to_create = []
through_model = Submission.scores.through
through_relations = []

for sub in created_submissions:
    for col in columns:
        score = SubmissionScore(
            column=col,
            score=Decimal(f"{uniform(0, 100):.5f}")
        )
        scores_to_create.append(score)

SubmissionScore.objects.bulk_create(scores_to_create, batch_size=1000)

created_scores = list(SubmissionScore.objects.order_by("-id")[:len(scores_to_create)])

idx = 0
for sub in created_submissions:
    for col in columns:
        through_relations.append(
            through_model(
                submission_id=sub.id,
                submissionscore_id=created_scores[idx].id
            )
        )
        idx += 1

through_model.objects.bulk_create(through_relations, batch_size=2000)

print("Scores liés aux submissions")

# ---------------- VALIDATION ----------------
count = Submission.objects.filter(
    phase=phase,
    leaderboard=leaderboard,
    status=Submission.FINISHED,
    appear_on_leaderboards=True
).count()

print(f"Submissions visibles dans leaderboard: {count}")

print("DONE: dataset prêt pour tester la pagination (50 / 100 / 500 / all)")

Checklist

  • Code review by me
  • Hand tested by me
  • I'm proud of my work
  • Code review by reviewer
  • Hand tested by reviewer
  • CircleCi tests are passing
  • Ready to merge

@IdirLISN IdirLISN requested a review from Didayolo April 7, 2026 08:47
@IdirLISN IdirLISN self-assigned this Apr 7, 2026
@ObadaS
Copy link
Copy Markdown
Collaborator

ObadaS commented Apr 7, 2026

It looks like the leaderboard is generated all at once instead of only loading the displayed items. Is that normal ?

@IdirLISN IdirLISN requested a review from ObadaS April 7, 2026 14:03
@IdirLISN
Copy link
Copy Markdown
Collaborator Author

IdirLISN commented Apr 7, 2026

It looks like the leaderboard is generated all at once instead of only loading the displayed items. Is that normal ?

good catch, this is fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants