Skip to content

Feature group routing for submissions#2393

Open
IdirLISN wants to merge 26 commits into
developfrom
feature/group_routing
Open

Feature group routing for submissions#2393
IdirLISN wants to merge 26 commits into
developfrom
feature/group_routing

Conversation

@IdirLISN

@IdirLISN IdirLISN commented May 28, 2026

Copy link
Copy Markdown
Collaborator

Description

Adding button inside the edit section of competitions, participant page, to create groups of users linked to a queue.

In edit section of competition, we can now create groups for this specific competition, edit groups, add participant to groups and associate a queue to groups.

If a user is inside multiple groups when he submits, multiple child submission are created (1 per group).
Each child submission are displayed in lines inside of the leaderboard ordered by ranking.

Multitasking is supported.

Issues this PR resolves

Closes #2100

/!\ new migrations files. /!\

A checklist for hand testing

  • create at least two groups and assign your self to them.
  • check server status if the child submissions are redirected to the expected worker.
  • check UX/UI in edit page.
  • check leaderboard.

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

@ObadaS ObadaS mentioned this pull request May 28, 2026
11 tasks
@IdirLISN IdirLISN changed the title Feature/group routing Feature group routing for submissions May 28, 2026
@IdirLISN IdirLISN self-assigned this May 28, 2026
@Didayolo

Copy link
Copy Markdown
Member

This feature can't be considered ready while there are failures in the CircleCI tests:

=========================== short test summary info ============================
FAILED src/apps/api/tests/test_competitions.py::CompetitionTests::test_delete_own_competition - django.db.utils.ProgrammingError: relation "competitions_competition_partic...
FAILED src/apps/api/tests/test_leaderboards.py::CompetitionLeaderboardStressTests::test_getting_many_submissions_doesnt_cause_too_many_queries - AssertionError: 31 != 11 : 31 queries executed, 11 expected
FAILED src/apps/api/tests/test_phases.py::ReRunPhaseSubmissionTests::test_collab_can_re_run_whole_phase - django.db.utils.InternalError: current transaction is aborted, commands ign...
FAILED src/apps/api/tests/test_phases.py::ReRunPhaseSubmissionTests::test_comp_creator_can_re_run_whole_phase - django.db.utils.InternalError: current transaction is aborted, commands ign...
FAILED src/apps/api/tests/test_phases.py::ReRunPhaseSubmissionTests::test_normal_user_cannot_re_run_whole_phase - django.db.utils.InternalError: current transaction is aborted, commands ign...
FAILED src/apps/api/tests/test_phases.py::ReRunPhaseSubmissionTests::test_super_user_can_re_run_whole_phase - django.db.utils.InternalError: current transaction is aborted, commands ign...
FAILED src/apps/api/tests/test_queues.py::QueuesAPITestCase::test_organizer_can_perform_all_operations - django.db.utils.ProgrammingError: relation "profiles_customgroup" does not ...
FAILED src/apps/api/tests/test_submissions.py::OrganizationSubmissionTests::test_org_participant_can_make_submission_as_organization - django.db.utils.InternalError: current transaction is aborted, commands ign...
FAILED src/apps/api/tests/test_submissions.py::TaskSelectionTests::test_can_re_run_submissions_with_multiple_tasks - django.db.utils.InternalError: current transaction is aborted, commands ign...
FAILED src/apps/api/tests/test_submissions.py::TaskSelectionTests::test_can_re_run_submissions_with_specific_task_with_bot_user_without_original_submission_secret - django.db.utils.InternalError: current transaction is aborted, commands ign...
FAILED src/apps/api/tests/test_submissions.py::TaskSelectionTests::test_can_select_tasks_when_making_submissions - django.db.utils.InternalError: current transaction is aborted, commands ign...
FAILED src/apps/competitions/tests/test_submissions.py::SubmissionManagerTests::test_re_run_submission_creates_new_submission_with_same_data_owner_and_phase - django.db.utils.InternalError: current transaction is aborted, commands ign...
FAILED src/apps/competitions/tests/test_submissions.py::MultipleTasksPerPhaseTests::test_children_always_created_in_the_same_order - AssertionError: assert 0 == 2
FAILED src/apps/competitions/tests/test_submissions.py::MultipleTasksPerPhaseTests::test_making_submission_creates_parent_sub_and_additional_sub_per_task - AssertionError: assert 0 == 2
FAILED src/apps/competitions/tests/test_submissions.py::MultipleTasksPerPhaseTests::test_making_submission_to_phase_with_one_task_does_not_create_parents_or_children - AssertionError: assert 0 == 1
===== 15 failed, 222 passed, 2 skipped, 2285 warnings in 347.97s (0:05:47) =====

Exited with code exit status 1

@IdirLISN IdirLISN force-pushed the feature/group_routing branch from 5e3ff22 to 83be794 Compare June 15, 2026 08:32
@Didayolo

Copy link
Copy Markdown
Member

Trying to save an existing competition from the editor, without any changes made:

django                     | 2026-06-15 10:10:58.857 | ERROR    | django.utils.log:log_response:253 - Internal Server Error: /api/competitions/2/
django                     | Traceback (most recent call last):
django                     | 
django                     |   File "/root/.local/share/uv/python/cpython-3.13.11-linux-aarch64-gnu/lib/python3.13/threading.py", line 1015, in _bootstrap
django                     |     self._bootstrap_inner()
django                     |     │    └ <function Thread._bootstrap_inner at 0xffffa5af3a60>
django                     |<Thread(asyncio_0, started 281473316217184)>
django                     |   File "/root/.local/share/uv/python/cpython-3.13.11-linux-aarch64-gnu/lib/python3.13/threading.py", line 1044, in _bootstrap_inner
django                     |     self.run()
django                     |     │    └ <function Thread.run at 0xffffa5af37e0>
django                     |<Thread(asyncio_0, started 281473316217184)>
django                     |   File "/root/.local/share/uv/python/cpython-3.13.11-linux-aarch64-gnu/lib/python3.13/threading.py", line 995, in run
django                     |     self._target(*self._args, **self._kwargs)
django                     |     │    │        │    │        │    └ {}
django                     |     │    │        │    │        └ <Thread(asyncio_0, started 281473316217184)>
django                     |     │    │        │    └ (<weakref at 0xffff9d261030; to 'concurrent.futures.thread.ThreadPoolExecutor' at 0xffff9d279050>, <_queue.SimpleQueue object...
django                     |     │    │        └ <Thread(asyncio_0, started 281473316217184)>
django                     |     │    └ <function _worker at 0xffffa516e520>
django                     |<Thread(asyncio_0, started 281473316217184)>
django                     |   File "/root/.local/share/uv/python/cpython-3.13.11-linux-aarch64-gnu/lib/python3.13/concurrent/futures/thread.py", line 93, in _worker
django                     |     work_item.run()
django                     |     │         └ <function _WorkItem.run at 0xffffa516e660>
django                     |<concurrent.futures.thread._WorkItem object at 0xffff96382040>
django                     |   File "/root/.local/share/uv/python/cpython-3.13.11-linux-aarch64-gnu/lib/python3.13/concurrent/futures/thread.py", line 59, in run
django                     |     result = self.fn(*self.args, **self.kwargs)
django                     |              │    │   │    │       │    └ {}
django                     |              │    │   │    │       └ <concurrent.futures.thread._WorkItem object at 0xffff96382040>
django                     |              │    │   │    └ ()
django                     |              │    │   └ <concurrent.futures.thread._WorkItem object at 0xffff96382040>
django                     |              │    └ functools.partial(<bound method SyncToAsync.thread_handler of <asgiref.sync.SyncToAsync object at 0xffff9674fa80>>, <_UnixSel...
django                     |<concurrent.futures.thread._WorkItem object at 0xffff96382040>
django                     | > File "/.venv/lib/python3.13/site-packages/asgiref/sync.py", line 557, in thread_handler
django                     |     raise exc_info[1]
django                     |           └ (<class 'AssertionError'>, AssertionError('You cannot call `.save()` on a serializer with invalid data.'), <traceback object ...
django                     |   File "/.venv/lib/python3.13/site-packages/django/core/handlers/exception.py", line 42, in inner
django                     |     response = await get_response(request)
django                     |                      │            └ <ASGIRequest: PATCH '/api/competitions/2/'>
django                     |<bound method BaseHandler._get_response_async of <django.core.handlers.asgi.ASGIHandler object at 0xffffa03b9be0>>
django                     |   File "/.venv/lib/python3.13/site-packages/asgiref/sync.py", line 557, in thread_handler
django                     |     raise exc_info[1]
django                     |           └ (<class 'AssertionError'>, AssertionError('You cannot call `.save()` on a serializer with invalid data.'), <traceback object ...
django                     |   File "/.venv/lib/python3.13/site-packages/django/core/handlers/base.py", line 253, in _get_response_async
django                     |     response = await wrapped_callback(
django                     |<asgiref.sync.SyncToAsync object at 0xffff9674f6f0>
django                     |   File "/.venv/lib/python3.13/site-packages/asgiref/sync.py", line 506, in __call__
django                     |     ret = await asyncio.shield(exec_coro)
django                     |                 │       │      └ <Future finished exception=AssertionError('You cannot call `.save()` on a serializer with invalid data.')>
django                     |                 │       └ <function shield at 0xffffa5442fc0>
django                     |<module 'asyncio' from '/root/.local/share/uv/python/cpython-3.13.11-linux-aarch64-gnu/lib/python3.13/asyncio/__init__.py'>
django                     |   File "/.venv/lib/python3.13/site-packages/asgiref/current_thread_executor.py", line 40, in run
django                     |     result = self.fn(*self.args, **self.kwargs)
django                     |              │        │            └ None
django                     |              │        └ None
django                     |None
django                     |   File "/.venv/lib/python3.13/site-packages/asgiref/sync.py", line 561, in thread_handler
django                     |     return func(*args, **kwargs)
django                     |            │     │       └ {}
django                     |            │     └ (functools.partial(<function CompetitionViewSet at 0xffff9ed54860>, <ASGIRequest: PATCH '/api/competitions/2/'>, pk='2'),)
django                     |<built-in method run of _contextvars.Context object at 0xffff9d1aad40>
django                     |   File "/.venv/lib/python3.13/site-packages/django/views/decorators/csrf.py", line 65, in _view_wrapper
django                     |     return view_func(request, *args, **kwargs)
django                     |            │         │         │       └ {'pk': '2'}
django                     |            │         │         └ ()
django                     |            │         └ <ASGIRequest: PATCH '/api/competitions/2/'>
django                     |<function CompetitionViewSet at 0xffff9ed547c0>
django                     |   File "/.venv/lib/python3.13/site-packages/rest_framework/viewsets.py", line 125, in view
django                     |     return self.dispatch(request, *args, **kwargs)
django                     |            │    │        │         │       └ {'pk': '2'}
django                     |            │    │        │         └ ()
django                     |            │    │        └ <ASGIRequest: PATCH '/api/competitions/2/'>
django                     |            │    └ <function APIView.dispatch at 0xffff9f1be200>
django                     |<api.views.competitions.CompetitionViewSet object at 0xffff966c7ce0>
django                     |   File "/.venv/lib/python3.13/site-packages/rest_framework/views.py", line 515, in dispatch
django                     |     response = self.handle_exception(exc)
django                     |                │    └ <function APIView.handle_exception at 0xffff9f1be0c0>
django                     |<api.views.competitions.CompetitionViewSet object at 0xffff966c7ce0>
django                     |   File "/.venv/lib/python3.13/site-packages/rest_framework/views.py", line 475, in handle_exception
django                     |     self.raise_uncaught_exception(exc)
django                     |     │    │                        └ AssertionError('You cannot call `.save()` on a serializer with invalid data.')
django                     |     │    └ <function APIView.raise_uncaught_exception at 0xffff9f1be160>
django                     |<api.views.competitions.CompetitionViewSet object at 0xffff966c7ce0>
django                     |   File "/.venv/lib/python3.13/site-packages/rest_framework/views.py", line 486, in raise_uncaught_exception
django                     |     raise exc
django                     |AssertionError('You cannot call `.save()` on a serializer with invalid data.')
django                     |   File "/.venv/lib/python3.13/site-packages/rest_framework/views.py", line 512, in dispatch
django                     |     response = handler(request, *args, **kwargs)
django                     |                │       │         │       └ {'pk': '2'}
django                     |                │       │         └ ()
django                     |                │       └ <rest_framework.request.Request: PATCH '/api/competitions/2/'>
django                     |<bound method UpdateModelMixin.partial_update of <api.views.competitions.CompetitionViewSet object at 0xffff966c7ce0>>
django                     |   File "/.venv/lib/python3.13/site-packages/rest_framework/mixins.py", line 82, in partial_update
django                     |     return self.update(request, *args, **kwargs)
django                     |            │    │      │         │       └ {'pk': '2', 'partial': True}
django                     |            │    │      │         └ ()
django                     |            │    │      └ <rest_framework.request.Request: PATCH '/api/competitions/2/'>
django                     |            │    └ <function CompetitionViewSet.update at 0xffff9ed19bc0>
django                     |<api.views.competitions.CompetitionViewSet object at 0xffff966c7ce0>
django                     | 
django                     |   File "/app/src/apps/api/views/competitions.py", line 272, in update
django                     |     leaderboard.save()
django                     |     │           └ <function BaseNestedModelSerializer.save at 0xffff9f0e40e0>
django                     |LeaderboardSerializer(<Leaderboard: Results - 2>, data={'id': 2, 'primary_index': 0, 'title': 'Results', 'key': 'Results', 'c...
django                     | 
django                     |   File "/.venv/lib/python3.13/site-packages/drf_writable_nested/mixins.py", line 232, in save
django                     |     return super(BaseNestedModelSerializer, self).save(**kwargs)
django                     |                  │                          │            └ {}
django                     |                  │                          └ LeaderboardSerializer(<Leaderboard: Results - 2>, data={'id': 2, 'primary_index': 0, 'title': 'Results', 'key': 'Results', 'c...
django                     |<class 'drf_writable_nested.mixins.BaseNestedModelSerializer'>
django                     |   File "/.venv/lib/python3.13/site-packages/rest_framework/serializers.py", line 182, in save
django                     |     assert not self.errors, (
django                     |                │    └ <property object at 0xffffa3f4f560>
django                     |LeaderboardSerializer(<Leaderboard: Results - 2>, data={'id': 2, 'primary_index': 0, 'title': 'Results', 'key': 'Results', 'c...
django                     | 
django                     | AssertionError: You cannot call `.save()` on a serializer with invalid data.

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.

[Feature] Participant groups in Competitions

2 participants