-
-
Notifications
You must be signed in to change notification settings - Fork 983
Backend: Fix N+1 queries, improve error handling, and add small documentations #4999
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -56,7 +56,7 @@ | |||
| from django.core.files.base import ContentFile | ||||
| from django.core.files.uploadedfile import SimpleUploadedFile | ||||
| from django.db import transaction | ||||
| from django.db.models import Prefetch | ||||
| from django.db.models import Case, Count, Prefetch, Q, When | ||||
| from django.http import HttpResponse | ||||
| from django.utils import timezone | ||||
| from drf_spectacular.utils import ( | ||||
|
|
@@ -840,31 +840,45 @@ def get_all_challenges( | |||
| def get_all_challenges_submission_metrics(request): | ||||
| """ | ||||
| Returns the submission metrics for all challenges and their phases | ||||
|
|
||||
| This function optimizes database queries by using aggregation to count | ||||
| submissions in a single query rather than looping through challenges. | ||||
| """ | ||||
| if not is_user_a_staff(request.user): | ||||
| response_data = { | ||||
| "error": "Sorry, you are not authorized to make this request" | ||||
| } | ||||
| return Response(response_data, status=status.HTTP_403_FORBIDDEN) | ||||
| challenges = Challenge.objects.all() | ||||
| submission_metrics = {} | ||||
|
|
||||
| submission_statuses = [status[0] for status in Submission.STATUS_OPTIONS] | ||||
|
|
||||
| for challenge in challenges: | ||||
| challenge_id = challenge.id | ||||
| challenge_metrics = {} | ||||
| # Get all challenge IDs | ||||
| challenge_ids = Challenge.objects.values_list('id', flat=True) | ||||
|
|
||||
| # Fetch challenge phases for the challenge | ||||
| challenge_phases = ChallengePhase.objects.filter(challenge=challenge) | ||||
| # Initialize metrics dictionary with all challenges and zero counts | ||||
| submission_metrics = {} | ||||
| for challenge_id in challenge_ids: | ||||
| submission_metrics[challenge_id] = { | ||||
| status_choice: 0 for status_choice in submission_statuses | ||||
| } | ||||
|
|
||||
| for submission_status in submission_statuses: | ||||
| count = Submission.objects.filter( | ||||
| challenge_phase__in=challenge_phases, status=submission_status | ||||
| ).count() | ||||
| challenge_metrics[submission_status] = count | ||||
| # Use aggregation to count submissions by challenge and status in a single query | ||||
| # Group submissions by their challenge phase's challenge and by status | ||||
| submission_counts = ( | ||||
| Submission.objects | ||||
| .select_related('challenge_phase__challenge') | ||||
|
||||
| .select_related('challenge_phase__challenge') |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -137,6 +137,20 @@ def get_participant_team_challenge_list(request, participant_team_pk): | |
| @permission_classes((permissions.IsAuthenticated, HasVerifiedEmail)) | ||
| @authentication_classes((JWTAuthentication, ExpiringTokenAuthentication)) | ||
| def participant_team_detail(request, pk): | ||
| """ | ||
| API endpoint to retrieve or update participant team details. | ||
|
|
||
| GET: Returns team details if the user is a team member | ||
| PUT: Updates team details (full update) | ||
| PATCH: Partially updates team details (only team creator can do this) | ||
|
|
||
| Args: | ||
| request: Django REST framework request object | ||
| pk: Primary key of the participant team | ||
|
|
||
| Returns: | ||
| Response with team details or error message | ||
| """ | ||
|
|
||
| try: | ||
| participant_team = ParticipantTeam.objects.get(pk=pk) | ||
|
|
@@ -196,6 +210,25 @@ def participant_team_detail(request, pk): | |
| @permission_classes((permissions.IsAuthenticated, HasVerifiedEmail)) | ||
| @authentication_classes((JWTAuthentication, ExpiringTokenAuthentication)) | ||
| def invite_participant_to_team(request, pk): | ||
| """ | ||
| API endpoint to invite a user to join a participant team. | ||
|
|
||
| Validates that: | ||
| - The participant team exists | ||
| - The requesting user is a member of the team | ||
| - The invited user exists | ||
| - The invited user is not already part of the team | ||
| - The invited user hasn't participated in challenges where the team has | ||
| - Neither the team nor invited user are banned from team's challenges | ||
| - Email domain restrictions (allowed/blocked) are respected | ||
|
|
||
| Args: | ||
| request: Django REST framework request object | ||
| pk: Primary key of the participant team | ||
|
|
||
| Returns: | ||
| Response with success message or error details | ||
| """ | ||
| try: | ||
| participant_team = ParticipantTeam.objects.get(pk=pk) | ||
| except ParticipantTeam.DoesNotExist: | ||
|
|
@@ -246,9 +279,10 @@ def invite_participant_to_team(request, pk): | |
| return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE) | ||
|
|
||
| if len(team_participated_challenges) > 0: | ||
| for challenge_pk in team_participated_challenges: | ||
| challenge = get_challenge_model(challenge_pk) | ||
| # Fetch all challenges in a single query to avoid N+1 problem | ||
| challenges = Challenge.objects.filter(pk__in=team_participated_challenges) | ||
|
|
||
| for challenge in challenges: | ||
|
Comment on lines
281
to
+285
|
||
| if len(challenge.banned_email_ids) > 0: | ||
| # Check if team participants emails are banned | ||
| for ( | ||
|
|
@@ -281,7 +315,7 @@ def invite_participant_to_team(request, pk): | |
|
|
||
| # Check if user is in allowed list. | ||
| if len(challenge.allowed_email_domains) > 0: | ||
| if not is_user_in_allowed_email_domains(email, challenge_pk): | ||
| if not is_user_in_allowed_email_domains(email, challenge.pk): | ||
| message = "Sorry, users with {} email domain(s) are only allowed to participate in this challenge." | ||
| domains = "" | ||
| for domain in challenge.allowed_email_domains: | ||
|
|
@@ -293,7 +327,7 @@ def invite_participant_to_team(request, pk): | |
| ) | ||
|
|
||
| # Check if user is in blocked list. | ||
| if is_user_in_blocked_email_domains(email, challenge_pk): | ||
| if is_user_in_blocked_email_domains(email, challenge.pk): | ||
| message = "Sorry, users with {} email domain(s) are not allowed to participate in this challenge." | ||
| domains = "" | ||
| for domain in challenge.blocked_email_domains: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,6 @@ | |
| max-line-length = 120 | ||
| exclude = .git,*/migrations/*,*/static/CACHE/*,docs/,env/,fabfile/,node_modules/,bower_components/ | ||
|
|
||
| [pytest] | ||
| [tool:pytest] | ||
| DJANGO_SETTINGS_MODULE = settings.test | ||
| norecursedirs = .git */migrations/* */static/* docs env | ||
|
Comment on lines
+5
to
7
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The added import brings in
Case,Q, andWhen, but they do not appear to be used in this module (onlyCountis used for the new aggregation). This will trigger unused-import lint errors (e.g., flake8 F401). Please remove the unused imports or use them if needed.