apps/challenges/views.py
import csv
import json
import logging
import os
import pytz
import random
import requests
import shutil
import tempfile
import time
import uuid
import yaml
import zipfile
from os.path import basename, isfile, join
from datetime import datetime
from django.conf import settings
from django.contrib.auth.hashers import make_password
from django.contrib.auth.models import User
from django.core.files.base import ContentFile
from django.core.files.uploadedfile import SimpleUploadedFile
from django.db import transaction
from django.http import HttpResponse
from django.utils import timezone
from rest_framework import permissions, status
from rest_framework.decorators import (
api_view,
authentication_classes,
permission_classes,
throttle_classes,
)
from rest_framework.response import Response
from rest_framework_expiring_authtoken.authentication import (
ExpiringTokenAuthentication,
)
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework.throttling import UserRateThrottle, AnonRateThrottle
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from yaml.scanner import ScannerError
from allauth.account.models import EmailAddress
from accounts.permissions import HasVerifiedEmail
from accounts.serializers import UserDetailsSerializer
from base.utils import (
get_queue_name,
get_slug,
get_url_from_hostname,
paginated_queryset,
send_email,
send_slack_notification,
is_user_a_staff,
)
from challenges.utils import (
complete_s3_multipart_file_upload,
generate_presigned_url_for_multipart_upload,
get_challenge_model,
get_challenge_phase_model,
get_challenge_phase_split_model,
get_dataset_split_model,
get_leaderboard_model,
get_participant_model,
get_unique_alpha_numeric_key,
is_user_in_allowed_email_domains,
is_user_in_blocked_email_domains,
parse_submission_meta_attributes,
add_domain_to_challenge,
add_tags_to_challenge,
add_prizes_to_challenge,
add_sponsors_to_challenge,
)
from challenges.challenge_config_utils import (
download_and_write_file,
extract_zip_file,
validate_challenge_config_util,
)
from hosts.models import ChallengeHost, ChallengeHostTeam
from hosts.utils import (
get_challenge_host_teams_for_user,
is_user_a_host_of_challenge,
is_user_a_staff_or_host,
get_challenge_host_team_model,
)
from jobs.filters import SubmissionFilter
from jobs.models import Submission
from jobs.serializers import (
SubmissionSerializer,
ChallengeSubmissionManagementSerializer,
)
from participants.models import Participant, ParticipantTeam
from participants.serializers import ParticipantTeamDetailSerializer
from participants.utils import (
get_participant_teams_for_user,
has_user_participated_in_challenge,
get_participant_team_id_of_user_for_a_challenge,
get_participant_team_of_user_for_a_challenge,
is_user_part_of_participant_team,
is_user_creator_of_participant_team,
)
from .models import (
Challenge,
ChallengeEvaluationCluster,
ChallengePhase,
ChallengePhaseSplit,
ChallengeTemplate,
ChallengeConfiguration,
LeaderboardData,
PWCChallengeLeaderboard,
StarChallenge,
UserInvitation,
ChallengePrize,
ChallengeSponsor,
)
from .permissions import IsChallengeCreator
from .serializers import (
ChallengeConfigSerializer,
ChallengeEvaluationClusterSerializer,
ChallengePhaseSerializer,
ChallengePhaseCreateSerializer,
ChallengePhaseSplitSerializer,
ChallengeTemplateSerializer,
ChallengeSerializer,
DatasetSplitSerializer,
LeaderboardSerializer,
PWCChallengeLeaderboardSerializer,
StarChallengeSerializer,
UserInvitationSerializer,
ZipChallengeSerializer,
ZipChallengePhaseSplitSerializer,
LeaderboardDataSerializer,
ChallengePrizeSerializer,
ChallengeSponsorSerializer,
)
from .aws_utils import (
delete_workers,
start_workers,
stop_workers,
restart_workers,
start_ec2_instance,
stop_ec2_instance,
restart_ec2_instance,
describe_ec2_instance,
create_ec2_instance,
terminate_ec2_instance,
get_logs_from_cloudwatch,
get_log_group_name,
scale_resources,
)
from .utils import (
get_aws_credentials_for_submission,
get_file_content,
get_missing_keys_from_dict,
get_challenge_template_data,
send_emails,
)
from jobs.utils import get_submission_model
logger = logging.getLogger(__name__)
try:
xrange # Python 2
except NameError:
xrange = range # Python 3
@api_view(["GET", "POST"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def challenge_list(request, challenge_host_team_pk):
try:
challenge_host_team = ChallengeHostTeam.objects.get(
pk=challenge_host_team_pk
)
except ChallengeHostTeam.DoesNotExist:
response_data = {"error": "ChallengeHostTeam does not exist"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
if request.method == "GET":
challenge = Challenge.objects.filter(
creator=challenge_host_team, is_disabled=False
).order_by("-id")
paginator, result_page = paginated_queryset(challenge, request)
serializer = ChallengeSerializer(
result_page, many=True, context={"request": request}
)
response_data = serializer.data
return paginator.get_paginated_response(response_data)
elif request.method == "POST":
if not ChallengeHost.objects.filter(
user=request.user, team_name_id=challenge_host_team_pk
).exists():
response_data = {
"error": "Sorry, you do not belong to this Host Team!"
}
return Response(response_data, status=status.HTTP_401_UNAUTHORIZED)
serializer = ZipChallengeSerializer(
data=request.data,
context={
"challenge_host_team": challenge_host_team,
"request": request,
},
)
if serializer.is_valid():
serializer.save()
challenge = get_challenge_model(serializer.instance.pk)
serializer = ChallengeSerializer(challenge)
response_data = serializer.data
return Response(response_data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@api_view(["GET", "PUT", "PATCH", "DELETE"])
@throttle_classes([UserRateThrottle])
@permission_classes(
(permissions.IsAuthenticated, HasVerifiedEmail, IsChallengeCreator)
)
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def challenge_detail(request, challenge_host_team_pk, challenge_pk):
try:
challenge_host_team = ChallengeHostTeam.objects.get(
pk=challenge_host_team_pk
)
except ChallengeHostTeam.DoesNotExist:
response_data = {"error": "ChallengeHostTeam does not exist"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
try:
challenge = Challenge.objects.get(pk=challenge_pk)
except Challenge.DoesNotExist:
response_data = {"error": "Challenge does not exist"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
if request.method == "GET":
serializer = ChallengeSerializer(
challenge, context={"request": request}
)
response_data = serializer.data
return Response(response_data, status=status.HTTP_200_OK)
elif request.method in ["PUT", "PATCH"]:
if request.method == "PATCH":
if "overview_file" in request.FILES:
overview_file = request.FILES["overview_file"]
overview = overview_file.read()
request.data["description"] = overview
serializer = ZipChallengeSerializer(
challenge,
data=request.data,
context={
"challenge_host_team": challenge_host_team,
"request": request,
},
partial=True,
)
elif "terms_and_conditions_file" in request.FILES:
terms_and_conditions_file = request.FILES["terms_and_conditions_file"]
terms_and_conditions = terms_and_conditions_file.read()
request.data["terms_and_conditions"] = terms_and_conditions
serializer = ZipChallengeSerializer(
challenge,
data=request.data,
context={
"challenge_host_team": challenge_host_team,
"request": request,
},
partial=True,
)
elif "submission_guidelines_file" in request.FILES:
submission_guidelines_file = request.FILES["submission_guidelines_file"]
submission_guidelines = submission_guidelines_file.read()
request.data["submission_guidelines"] = submission_guidelines
serializer = ZipChallengeSerializer(
challenge,
data=request.data,
context={
"challenge_host_team": challenge_host_team,
"request": request,
},
partial=True,
)
elif "evaluation_criteria_file" in request.FILES:
evaluation_criteria_file = request.FILES["evaluation_criteria_file"]
evaluation_criteria = evaluation_criteria_file.read()
request.data["evaluation_details"] = evaluation_criteria
serializer = ZipChallengeSerializer(
challenge,
data=request.data,
context={
"challenge_host_team": challenge_host_team,
"request": request,
},
partial=True,
)
else:
serializer = ZipChallengeSerializer(
challenge,
data=request.data,
context={
"challenge_host_team": challenge_host_team,
"request": request,
},
partial=True,
)
else:
serializer = ZipChallengeSerializer(
challenge,
data=request.data,
context={
"challenge_host_team": challenge_host_team,
"request": request,
},
)
if serializer.is_valid():
serializer.save()
challenge = get_challenge_model(serializer.instance.pk)
serializer = ChallengeSerializer(challenge)
response_data = serializer.data
return Response(response_data, status=status.HTTP_200_OK)
else:
return Response(
serializer.errors, status=status.HTTP_400_BAD_REQUEST
)
elif request.method == "DELETE":
challenge.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
@api_view(["POST"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def deregister_participant_team_from_challenge(request, challenge_pk):
"""
Deregister a participant team from a challenge
Arguments:
challenge_pk {int} -- Challenge primary key
Returns:
{String} -- Success message
"""
if has_user_participated_in_challenge(
user=request.user, challenge_id=challenge_pk
):
challenge = get_challenge_model(challenge_pk)
participant_team_pk = get_participant_team_id_of_user_for_a_challenge(
request.user, challenge_pk
)
participant_team = get_participant_model(participant_team_pk)
all_challenge_phases = ChallengePhase.objects.filter(challenge=challenge)
if (all_challenge_phases.count() > 0):
for challenge_phase in all_challenge_phases:
submission_exist = Submission.objects.filter(participant_team=participant_team, challenge_phase=challenge_phase).exists()
if submission_exist:
break
else:
submission_exist = False
if submission_exist:
response_data = {"error": "Participant teams which have made submissions to a challenge cannot be deregistered."}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
else:
challenge.participant_teams.remove(participant_team)
response_data = {"success": "Successfully deregistered!"}
return Response(response_data, status=status.HTTP_200_OK)
else:
response_data = {"error": "Your participant team is not registered for this challenge."}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
@api_view(["GET"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def participant_team_detail_for_challenge(request, challenge_pk):
"""
Returns the participated team detail in the challenge
Arguments:
challenge_pk {int} -- Challenge primary key
Returns:
{dict} -- Participant team detail that has participated in the challenge
"""
if has_user_participated_in_challenge(
user=request.user, challenge_id=challenge_pk
):
challenge = get_challenge_model(challenge_pk)
participant_team_pk = get_participant_team_id_of_user_for_a_challenge(
request.user, challenge_pk
)
participant_team = get_participant_model(participant_team_pk)
serializer = ParticipantTeamDetailSerializer(participant_team)
if (challenge.approved_participant_teams.filter(pk=participant_team_pk).exists()):
approved = True
elif not challenge.manual_participant_approval:
approved = True
else:
approved = False
response_data = {
"approved": approved,
"participant_team": serializer.data,
}
return Response(response_data, status=status.HTTP_200_OK)
else:
message = "You are not a participant!"
response_data = {"error": message}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
@swagger_auto_schema(
methods=["get"],
manual_parameters=[
openapi.Parameter(
name="challenge_pk",
in_=openapi.IN_PATH,
type=openapi.TYPE_NUMBER,
description="Challenge pk",
required=True,
)
],
operation_id="get_participant_teams_for_challenge",
responses={
status.HTTP_200_OK: openapi.Response(""),
status.HTTP_403_FORBIDDEN: openapi.Response(
"{'error': 'You are not authorized to make this request'}"
),
},
)
@api_view(["GET"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def get_participant_teams_for_challenge(request, challenge_pk):
"""
API to get all participant team detail
Arguments:
request {HttpRequest} -- The request object
challenge_pk {[int]} -- Challenge primary key
Returns:
{dict} -- Participant team detail that has participated in the challenge
"""
challenge = get_challenge_model(challenge_pk)
if is_user_a_host_of_challenge(request.user, challenge_pk):
participant_teams = challenge.participant_teams
serializer = ParticipantTeamDetailSerializer(
participant_teams, many=True
)
for participant_team in serializer.data:
if (challenge.approved_participant_teams.filter(id=participant_team["id"]).exists()):
participant_team["approved"] = True
else:
participant_team["approved"] = False
response_data = {
"participant_teams": serializer.data,
}
return Response(response_data, status=status.HTTP_200_OK)
else:
response_data = {
"error": "You are not authorized to make this request"
}
return Response(response_data, status=status.HTTP_403_FORBIDDEN)
@api_view(["POST"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def add_participant_team_to_challenge(
request, challenge_pk, participant_team_pk
):
try:
challenge = Challenge.objects.get(pk=challenge_pk)
except Challenge.DoesNotExist:
response_data = {"error": "Challenge does not exist"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
if not challenge.is_registration_open:
response_data = {"error": "Registration is closed for this challenge!"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
if (
challenge.end_date < timezone.now()
or challenge.start_date > timezone.now()
):
response_data = {
"error": "Sorry, cannot accept participant team since challenge is not active."
}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
try:
participant_team = ParticipantTeam.objects.get(pk=participant_team_pk)
except ParticipantTeam.DoesNotExist:
response_data = {"error": "ParticipantTeam does not exist"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
# Check if user is banned
if len(challenge.banned_email_ids) > 0:
for participant_email in participant_team.get_all_participants_email():
if participant_email in challenge.banned_email_ids:
message = "You're a part of {} team and it has been banned from this challenge. \
Please contact the challenge host.".format(
participant_team.team_name
)
response_data = {"error": message}
return Response(
response_data, status=status.HTTP_406_NOT_ACCEPTABLE
)
# Check if user is in allowed list.
user_email = request.user.email
if len(challenge.allowed_email_domains) > 0:
domains = ""
for domain in challenge.allowed_email_domains:
domains = "{}{}{}".format(domains, "/", domain)
domains = domains[1:]
for participant_email in participant_team.get_all_participants_email():
if not is_user_in_allowed_email_domains(participant_email, challenge_pk):
message = "Sorry, team consisting of users with non-{} email domain(s) are not allowed \
to participate in this challenge."
response_data = {"error": message.format(domains)}
return Response(
response_data, status=status.HTTP_406_NOT_ACCEPTABLE
)
# Check if user is in blocked list.
if is_user_in_blocked_email_domains(user_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:
domains = "{}{}{}".format(domains, "/", domain)
domains = domains[1:]
response_data = {"error": message.format(domains)}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
# check to disallow the user if he is a Challenge Host for this challenge
participant_team_user_ids = set(
Participant.objects.select_related("user")
.filter(team__id=participant_team_pk)
.values_list("user", flat=True)
)
for user in participant_team_user_ids:
if has_user_participated_in_challenge(user, challenge_pk):
response_data = {
"error": "Sorry, other team member(s) have already participated in the Challenge."
" Please participate with a different team!",
"challenge_id": int(challenge_pk),
"participant_team_id": int(participant_team_pk),
}
return Response(
response_data, status=status.HTTP_406_NOT_ACCEPTABLE
)
if not (
is_user_part_of_participant_team(request.user, participant_team)
or is_user_creator_of_participant_team(request.user, participant_team)
):
response_data = {
"error": "Sorry, you are not authorized to to make this request"
}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
if participant_team.challenge_set.filter(id=challenge_pk).exists():
response_data = {
"error": "Team already exists",
"challenge_id": int(challenge_pk),
"participant_team_id": int(participant_team_pk),
}
return Response(response_data, status=status.HTTP_200_OK)
else:
challenge.participant_teams.add(participant_team)
return Response(status=status.HTTP_201_CREATED)
@api_view(["POST"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def add_participant_team_to_approved_list(request, challenge_pk, participant_team_pk):
"""
Add participant team to approved list
"""
try:
challenge = Challenge.objects.get(pk=challenge_pk)
except Challenge.DoesNotExist:
response_data = {"error": "Challenge does not exist"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
try:
participant_team = ParticipantTeam.objects.get(pk=participant_team_pk)
except ParticipantTeam.DoesNotExist:
response_data = {"error": "Participant Team does not exist"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
if (challenge.approved_participant_teams.filter(pk=participant_team_pk).exists()):
response_data = {"error": "Participant Team already approved"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
else:
if (ParticipantTeam.objects.filter(team_name__in=challenge.participant_teams.values_list('team_name', flat=True)).exists()):
challenge.approved_participant_teams.add(participant_team)
response_data = {"success": "Participant Team added to approved list"}
return Response(response_data, status=status.HTTP_201_CREATED)
else:
response_data = {"error": "Participant isn't interested in challenge"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
@api_view(["POST"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def remove_participant_team_from_approved_list(request, challenge_pk, participant_team_pk):
"""
Remove participant team from approved list
"""
try:
challenge = Challenge.objects.get(pk=challenge_pk)
except Challenge.DoesNotExist:
response_data = {"error": "Challenge does not exist"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
try:
participant_team = ParticipantTeam.objects.get(pk=participant_team_pk)
team_name = participant_team.team_name
except ParticipantTeam.DoesNotExist:
response_data = {"error": "Participant Team does not exist"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
all_challenge_phases = ChallengePhase.objects.filter(challenge=challenge)
if (all_challenge_phases.count() > 0):
for challenge_phase in all_challenge_phases:
submission_exist = Submission.objects.filter(participant_team=participant_team, challenge_phase=challenge_phase).exists()
if submission_exist:
break
else:
submission_exist = False
if challenge.approved_participant_teams.filter(pk=participant_team_pk).exists() and not submission_exist:
challenge.approved_participant_teams.remove(participant_team)
return Response(status=status.HTTP_204_NO_CONTENT)
elif submission_exist:
response_data = {"error": f"Participant Team {team_name} has existing submissions and cannot be unapproved"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
else:
response_data = {"error": f"Participant Team {team_name} was not approved"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
@api_view(["POST"])
@throttle_classes([UserRateThrottle])
@permission_classes(
(permissions.IsAuthenticated, HasVerifiedEmail, IsChallengeCreator)
)
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def disable_challenge(request, challenge_pk):
try:
challenge = Challenge.objects.get(pk=challenge_pk)
except Challenge.DoesNotExist:
response_data = {"error": "Challenge does not exist"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
challenge.is_disabled = True
challenge.save()
return Response(status=status.HTTP_204_NO_CONTENT)
@api_view(["GET"])
@throttle_classes([AnonRateThrottle])
def get_all_challenges(request, challenge_time, challenge_approved, challenge_published):
"""
Returns the list of all challenges
"""
# make sure that a valid url is requested.
if challenge_time.lower() not in ("all", "future", "past", "present"):
response_data = {"error": "Wrong url pattern!"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
if challenge_approved.lower() not in ("all", "approved", "unapproved"):
response_data = {"error": "Wrong challenge approval status!"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
if challenge_published.lower() not in ("all", "public", "private"):
response_data = {"error": "Wrong challenge published status!"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
q_params = {}
if challenge_approved.lower() != "all":
q_params["approved_by_admin"] = (challenge_approved.lower() == "approved")
if challenge_published.lower() != "all":
q_params["published"] = (challenge_published.lower() == "public")
if challenge_time.lower() == "past":
q_params["end_date__lt"] = timezone.now()
elif challenge_time.lower() == "present":
q_params["start_date__lt"] = timezone.now()
q_params["end_date__gt"] = timezone.now()
elif challenge_time.lower() == "future":
q_params["start_date__gt"] = timezone.now()
# for `all` we dont need any condition in `q_params`
# don't return disabled challenges
q_params["is_disabled"] = False
challenge = Challenge.objects.filter(**q_params).order_by("-pk")
paginator, result_page = paginated_queryset(challenge, request)
serializer = ChallengeSerializer(
result_page, many=True, context={"request": request}
)
response_data = serializer.data
return paginator.get_paginated_response(response_data)
@api_view(["GET"])
@throttle_classes([AnonRateThrottle])
def get_all_challenges_submission_metrics(request):
"""
Returns the submission metrics for all challenges and their phases
"""
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 = {}
# Fetch challenge phases for the challenge
challenge_phases = ChallengePhase.objects.filter(challenge=challenge)
for submission_status in submission_statuses:
count = Submission.objects.filter(challenge_phase__in=challenge_phases, status=submission_status).count()
challenge_metrics[submission_status] = count
submission_metrics[challenge_id] = challenge_metrics
return Response(submission_metrics, status=status.HTTP_200_OK)
@api_view(["GET"])
@throttle_classes([AnonRateThrottle])
def get_challenge_submission_metrics_by_pk(request, pk):
"""
Returns the submission metrics for a given challenge by primary key and their phases
"""
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)
challenge = get_challenge_model(pk)
challenge_phases = ChallengePhase.objects.filter(challenge=challenge)
submission_metrics = {}
submission_statuses = [status[0] for status in Submission.STATUS_OPTIONS]
# Fetch challenge phases for the challenge
challenge_phases = ChallengePhase.objects.filter(challenge=challenge)
for submission_status in submission_statuses:
count = Submission.objects.filter(challenge_phase__in=challenge_phases, status=submission_status).count()
submission_metrics[submission_status] = count
return Response(submission_metrics, status=status.HTTP_200_OK)
@api_view(["GET"])
@throttle_classes([AnonRateThrottle])
def get_featured_challenges(request):
"""
Returns the list of featured challenges
"""
challenge = Challenge.objects.filter(
featured=True,
published=True,
approved_by_admin=True,
is_disabled=False,
).order_by("-id")
paginator, result_page = paginated_queryset(challenge, request)
serializer = ChallengeSerializer(
result_page, many=True, context={"request": request}
)
response_data = serializer.data
return paginator.get_paginated_response(response_data)
@api_view(["GET"])
@throttle_classes([AnonRateThrottle])
def get_challenge_by_pk(request, pk):
"""
Returns a particular challenge by id
"""
try:
if is_user_a_host_of_challenge(request.user, pk):
challenge = Challenge.objects.get(pk=pk)
else:
challenge = Challenge.objects.get(
pk=pk, approved_by_admin=True, published=True
)
if challenge.is_disabled:
response_data = {"error": "Sorry, the challenge was removed!"}
return Response(
response_data, status=status.HTTP_406_NOT_ACCEPTABLE
)
serializer = ChallengeSerializer(
challenge, context={"request": request}
)
response_data = serializer.data
return Response(response_data, status=status.HTTP_200_OK)
except Challenge.DoesNotExist:
response_data = {"error": "Challenge does not exist!"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
@api_view(["GET"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def get_all_participated_challenges(request, challenge_time):
"""
Returns the list of all participated challenges
"""
# make sure that a valid url is requested.
if challenge_time.lower() not in ("all", "past", "present"):
response_data = {"error": "Wrong url pattern!"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
q_params = {"published": True, "approved_by_admin": True}
if challenge_time.lower() == "past":
q_params["end_date__lt"] = timezone.now()
elif challenge_time.lower() == "present":
q_params["start_date__lt"] = timezone.now()
q_params["end_date__gt"] = timezone.now()
# don't return disabled challenges
q_params["is_disabled"] = False
participant_team_ids = get_participant_teams_for_user(request.user)
q_params["participant_teams__pk__in"] = participant_team_ids
challenges = Challenge.objects.filter(**q_params).order_by("-pk")
paginator, result_page = paginated_queryset(challenges, request)
serializer = ChallengeSerializer(
result_page, many=True, context={"request": request}
)
response_data = serializer.data
return paginator.get_paginated_response(response_data)
@api_view(["GET"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def get_challenges_based_on_teams(request):
q_params = {"approved_by_admin": True, "published": True}
participant_team_id = request.query_params.get("participant_team", None)
challenge_host_team_id = request.query_params.get("host_team", None)
mode = request.query_params.get("mode", None)
if not participant_team_id and not challenge_host_team_id and not mode:
response_data = {"error": "Invalid url pattern!"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
# either mode should be there or one of paricipant team and host team
if mode and (participant_team_id or challenge_host_team_id):
response_data = {"error": "Invalid url pattern!"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
if participant_team_id:
q_params["participant_teams__pk"] = participant_team_id
if challenge_host_team_id:
q_params["creator__id"] = challenge_host_team_id
if mode == "participant":
participant_team_ids = get_participant_teams_for_user(request.user)
q_params["participant_teams__pk__in"] = participant_team_ids
elif mode == "host":
host_team_ids = get_challenge_host_teams_for_user(request.user)
q_params["creator__id__in"] = host_team_ids
challenge = Challenge.objects.filter(**q_params).order_by("id")
paginator, result_page = paginated_queryset(challenge, request)
serializer = ChallengeSerializer(
result_page, many=True, context={"request": request}
)
response_data = serializer.data
return paginator.get_paginated_response(response_data)
@api_view(["GET", "POST"])
@throttle_classes([UserRateThrottle])
@permission_classes(
(
permissions.IsAuthenticatedOrReadOnly,
HasVerifiedEmail,
IsChallengeCreator,
)
)
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def challenge_phase_list(request, challenge_pk):
try:
challenge = Challenge.objects.get(pk=challenge_pk)
except Challenge.DoesNotExist:
response_data = {"error": "Challenge does not exist"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
if request.method == "GET":
if is_user_a_host_of_challenge(request.user, challenge_pk):
challenge_phase = ChallengePhase.objects.filter(
challenge=challenge
).order_by("pk")
else:
challenge_phase = ChallengePhase.objects.filter(
challenge=challenge, is_public=True
).order_by("pk")
paginator, result_page = paginated_queryset(challenge_phase, request)
serializer = ChallengePhaseSerializer(result_page, many=True)
response_data = serializer.data
return paginator.get_paginated_response(response_data)
elif request.method == "POST":
serializer = ChallengePhaseCreateSerializer(
data=request.data, context={"challenge": challenge}
)
if serializer.is_valid():
serializer.save()
challenge_phase = get_challenge_phase_model(serializer.instance.pk)
serializer = ChallengePhaseSerializer(challenge_phase)
response_data = serializer.data
return Response(response_data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@api_view(["GET", "PUT", "PATCH", "DELETE"])
@throttle_classes([UserRateThrottle])
@permission_classes(
(
permissions.IsAuthenticatedOrReadOnly,
HasVerifiedEmail,
IsChallengeCreator,
)
)
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def challenge_phase_detail(request, challenge_pk, pk):
try:
challenge = Challenge.objects.get(pk=challenge_pk)
except Challenge.DoesNotExist:
response_data = {"error": "Challenge does not exist"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
try:
challenge_phase = ChallengePhase.objects.get(
challenge=challenge, pk=pk
)
except ChallengePhase.DoesNotExist:
response_data = {
"error": "Challenge phase {} does not exist for challenge {}".format(
pk, challenge.pk
)
}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
if request.method == "GET":
if not is_user_a_host_of_challenge(request.user, challenge.id):
serializer = ChallengePhaseSerializer(challenge_phase)
response_data = serializer.data
return Response(response_data, status=status.HTTP_200_OK)
else:
serializer = ChallengePhaseCreateSerializer(
challenge_phase, context={"request": request}
)
response_data = serializer.data
return Response(response_data, status=status.HTTP_200_OK)
elif request.method in ["PUT", "PATCH"]:
if request.method == "PATCH":
if "phase_description_file" in request.FILES:
phase_description_file = request.FILES["phase_description_file"]
phase_description = phase_description_file.read()
request.data["description"] = phase_description
serializer = ChallengePhaseCreateSerializer(
challenge_phase,
data=request.data.copy(),
context={"challenge": challenge},
partial=True,
)
else:
serializer = ChallengePhaseCreateSerializer(
challenge_phase,
data=request.data.copy(),
context={"challenge": challenge},
partial=True,
)
else:
serializer = ChallengePhaseCreateSerializer(
challenge_phase,
data=request.data.copy(),
context={"challenge": challenge},
)
if serializer.is_valid():
serializer.save()
challenge_phase = get_challenge_phase_model(serializer.instance.pk)
serializer = ChallengePhaseSerializer(challenge_phase)
response_data = serializer.data
return Response(response_data, status=status.HTTP_200_OK)
else:
return Response(
serializer.errors, status=status.HTTP_400_BAD_REQUEST
)
elif request.method == "DELETE":
challenge_phase.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
@api_view(["GET"])
@throttle_classes([AnonRateThrottle])
def challenge_phase_split_list(request, challenge_pk):
"""
Returns the list of Challenge Phase Splits for a particular challenge
"""
try:
challenge = Challenge.objects.get(pk=challenge_pk)
except Challenge.DoesNotExist:
response_data = {"error": "Challenge does not exist"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
challenge_phase_split = ChallengePhaseSplit.objects.filter(
challenge_phase__challenge=challenge
).order_by("pk")
# Check if user is a challenge host or staff
challenge_host = is_user_a_staff_or_host(request.user, challenge_pk)
if not challenge_host:
challenge_phase_split = challenge_phase_split.filter(
visibility=ChallengePhaseSplit.PUBLIC
)
serializer = ChallengePhaseSplitSerializer(
challenge_phase_split, many=True
)
response_data = serializer.data
return Response(response_data, status=status.HTTP_200_OK)
@api_view(["POST"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def create_challenge_using_zip_file(request, challenge_host_team_pk):
"""
Creates a challenge using a zip file.
"""
challenge_host_team = get_challenge_host_team_model(challenge_host_team_pk)
if request.data.get("is_challenge_template"):
is_challenge_template = True
else:
is_challenge_template = False
# All files download and extract location.
BASE_LOCATION = tempfile.mkdtemp()
if is_challenge_template:
template_id = int(request.data.get("template_id"))
try:
challenge_template = ChallengeTemplate.objects.get(
id=template_id, is_active=True
)
except ChallengeTemplate.DoesNotExist:
response_data = {
"error": "Sorry, a server error occured while creating the challenge. Please try again!"
}
return Response(
response_data, status=status.HTTP_406_NOT_ACCEPTABLE
)
if settings.DEBUG or settings.TEST:
template_zip_s3_url = (
settings.EVALAI_API_SERVER
+ challenge_template.template_file.url
)
else:
template_zip_s3_url = challenge_template.template_file.url
unique_folder_name = get_unique_alpha_numeric_key(10)
challenge_template_download_location = join(
BASE_LOCATION, "{}.zip".format(unique_folder_name)
)
try:
response = requests.get(template_zip_s3_url, stream=True)
except Exception as e:
logger.error(
"Failed to fetch file from {}, error {}".format(
template_zip_s3_url, e
)
)
response_data = {
"error": "Sorry, there was an error in the server"
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
if response and response.status_code == status.HTTP_200_OK:
with open(challenge_template_download_location, "wb") as f:
f.write(response.content)
try:
zip_file = open(challenge_template_download_location, "rb")
except Exception:
message = (
"A server error occured while processing zip file. "
"Please try again!"
)
response_data = {"error": message}
logger.exception(message)
return Response(
response_data, status=status.HTTP_406_NOT_ACCEPTABLE
)
challenge_zip_file = SimpleUploadedFile(
zip_file.name, zip_file.read(), content_type="application/zip"
)
# Copy request data so that we can mutate it to add template
challenge_data_from_hosts = request.data.copy()
challenge_data_from_hosts["zip_configuration"] = challenge_zip_file
serializer = ChallengeConfigSerializer(
data=challenge_data_from_hosts, context={"request": request}
)
else:
data = request.data.copy()
serializer = ChallengeConfigSerializer(
data=data, context={"request": request}
)
if serializer.is_valid():
uploaded_zip_file = serializer.save()
uploaded_zip_file_path = serializer.data["zip_configuration"]
else:
response_data = serializer.errors
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
try:
response = requests.get(uploaded_zip_file_path, stream=True)
unique_folder_name = get_unique_alpha_numeric_key(10)
CHALLENGE_ZIP_DOWNLOAD_LOCATION = join(
BASE_LOCATION, "{}.zip".format(unique_folder_name)
)
try:
if response and response.status_code == 200:
with open(CHALLENGE_ZIP_DOWNLOAD_LOCATION, "wb") as zip_file:
zip_file.write(response.content)
except IOError:
message = (
"Unable to process the uploaded zip file. " "Please try again!"
)
response_data = {"error": message}
logger.exception(message)
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
except requests.exceptions.RequestException:
message = (
"A server error occured while processing zip file. "
"Please try again!"
)
response_data = {"error": message}
logger.exception(message)
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
# Extract zip file
try:
zip_ref = zipfile.ZipFile(CHALLENGE_ZIP_DOWNLOAD_LOCATION, "r")
zip_ref.extractall(join(BASE_LOCATION, unique_folder_name))
zip_ref.close()
except zipfile.BadZipfile:
message = (
"The zip file contents cannot be extracted. "
"Please check the format!"
)
response_data = {"error": message}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
# Search for yaml file
yaml_file_count = 0
for name in zip_ref.namelist():
if (name.endswith(".yaml") or name.endswith(".yml")) and (
not name.startswith("__MACOSX")
): # Ignore YAML File in __MACOSX Directory
yaml_file = name
extracted_folder_name = yaml_file.split(basename(yaml_file))[0]
yaml_file_count += 1
if not yaml_file_count:
message = "There is no YAML file in zip file you uploaded!"
response_data = {"error": message}
logger.info(message)
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
if yaml_file_count > 1:
message = (
"There are {0} YAML files instead of one in zip folder!".format(
yaml_file_count
)
)
response_data = {"error": message}
logger.info(message)
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
try:
with open(
join(BASE_LOCATION, unique_folder_name, yaml_file), "r"
) as stream:
yaml_file_data = yaml.safe_load(stream)
except (yaml.YAMLError, ScannerError) as exc:
# To get the problem description
if hasattr(exc, "problem"):
error_description = exc.problem
# To capitalize the first alphabet of the problem description as default is in lowercase
error_description = error_description[0:].capitalize()
# To get the error line and column number
if hasattr(exc, "problem_mark"):
mark = exc.problem_mark
line_number = mark.line + 1
column_number = mark.column + 1
message = "{} in line {}, column {}".format(
error_description, line_number, column_number
)
response_data = {"error": message}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
# Check for evaluation script path in yaml file.
try:
evaluation_script = yaml_file_data["evaluation_script"]
evaluation_script_path = join(
BASE_LOCATION,
unique_folder_name,
extracted_folder_name,
evaluation_script,
)
except KeyError:
message = (
"There is no key for evaluation script in YAML file. "
"Please add it and then try again!"
)
response_data = {"error": message}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
# Check for evaluation script file in extracted zip folder.
if isfile(evaluation_script_path):
with open(evaluation_script_path, "rb") as challenge_evaluation_script:
challenge_evaluation_script_file = ContentFile(
challenge_evaluation_script.read(), evaluation_script_path
)
else:
message = (
"No evaluation script is present in the zip file. "
"Please add it and then try again!"
)
response_data = {"error": message}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
# Check for test annotation file path in yaml file.
try:
challenge_phases_data = yaml_file_data["challenge_phases"]
except KeyError:
message = (
"No challenge phase key found. "
"Please add challenge phases in YAML file and try again!"
)
response_data = {"error": message}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
for data in challenge_phases_data:
test_annotation_file = data.get("test_annotation_file")
if test_annotation_file:
test_annotation_file_path = join(
BASE_LOCATION,
unique_folder_name,
extracted_folder_name,
test_annotation_file,
)
if not isfile(test_annotation_file_path):
message = (
"No test annotation file found in zip file"
" for challenge phase '{}'. Please add it and "
" then try again!".format(data["name"])
)
response_data = {"error": message}
return Response(
response_data, status=status.HTTP_406_NOT_ACCEPTABLE
)
else:
message = (
"There is no key for test annotation file for"
" challenge phase {} in yaml file. Please add it"
" and then try again!".format(data["name"])
)
response_data = {"error": message}
return Response(
response_data, status=status.HTTP_406_NOT_ACCEPTABLE
)
if data.get("is_submission_public") and data.get(
"is_restricted_to_select_one_submission"
):
message = (
"is_submission_public can't be 'True' for for challenge phase '{}'"
" with is_restricted_to_select_one_submission 'True'. "
" Please change is_submission_public to 'False'"
" then try again!".format(data["name"])
)
response_data = {"error": message}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
# To ensure that the schema for submission meta attributes is valid.
if data.get("submission_meta_attributes"):
for attribute in data["submission_meta_attributes"]:
keys = ["name", "description", "type"]
missing_keys = get_missing_keys_from_dict(attribute, keys)
if len(missing_keys) == 0:
valid_attribute_types = [
"boolean",
"text",
"radio",
"checkbox",
]
attribute_type = attribute["type"]
if attribute_type in valid_attribute_types:
if (
attribute_type == "radio"
or attribute_type == "checkbox"
):
options = attribute.get("options")
if not options or not len(options):
message = "Please include at least one option in attribute for challenge_phase {}".format(
data["id"]
)
response_data = {"error": message}
return Response(
response_data,
status=status.HTTP_406_NOT_ACCEPTABLE,
)
else:
missing_keys_string = ", ".join(missing_keys)
message = "Please enter the following to the submission meta attribute in phase {}: {}.".format(
data["id"], missing_keys_string
)
response_data = {"error": message}
return Response(
response_data, status=status.HTTP_406_NOT_ACCEPTABLE
)
if data.get("default_submission_meta_attributes"):
for attribute in data["default_submission_meta_attributes"]:
keys = ["name", "is_visible"]
missing_keys = get_missing_keys_from_dict(attribute, keys)
if len(missing_keys) == 0:
valid_attributes = [
"method_name",
"method_description",
"project_url",
"publication_url",
]
if not attribute["name"] in valid_attributes:
message = "Default meta attribute: {} in phase: {} does not exist!".format(
attribute["name"], data["id"]
)
response_data = {"error": message}
return Response(
response_data,
status=status.HTTP_406_NOT_ACCEPTABLE,
)
else:
missing_keys_string = ", ".join(missing_keys)
message = "Please enter the following to the default submission meta attribute in phase {}: {}.".format(
data["id"], missing_keys_string
)
response_data = {"error": message}
return Response(
response_data, status=status.HTTP_406_NOT_ACCEPTABLE
)
# Check for challenge image in yaml file.
image = yaml_file_data.get("image")
if image and (
image.endswith(".jpg")
or image.endswith(".jpeg")
or image.endswith(".png")
):
challenge_image_path = join(
BASE_LOCATION, unique_folder_name, extracted_folder_name, image
)
if isfile(challenge_image_path):
challenge_image_file = ContentFile(
get_file_content(challenge_image_path, "rb"), image
)
else:
challenge_image_file = None
else:
challenge_image_file = None
# check for challenge description file
try:
challenge_description_file_path = join(
BASE_LOCATION,
unique_folder_name,
extracted_folder_name,
yaml_file_data["description"],
)
if challenge_description_file_path.endswith(".html") and isfile(
challenge_description_file_path
):
yaml_file_data["description"] = get_file_content(
challenge_description_file_path, "rb"
).decode("utf-8")
else:
yaml_file_data["description"] = None
except KeyError:
message = (
"There is no key for description. "
"Please add it and then try again!"
)
response_data = {"error": message}
return Response(response_data, status.HTTP_406_NOT_ACCEPTABLE)
# check for evaluation details file
try:
challenge_evaluation_details_file_path = join(
BASE_LOCATION,
unique_folder_name,
extracted_folder_name,
yaml_file_data["evaluation_details"],
)
if challenge_evaluation_details_file_path.endswith(".html") and isfile(
challenge_evaluation_details_file_path
):
yaml_file_data["evaluation_details"] = get_file_content(
challenge_evaluation_details_file_path, "rb"
).decode("utf-8")
else:
yaml_file_data["evaluation_details"] = None
except KeyError:
message = (
"There is no key for evalutaion details. "
"Please add it and then try again!"
)
response_data = {"error": message}
return Response(response_data, status.HTTP_406_NOT_ACCEPTABLE)
# check for terms and conditions file
try:
challenge_terms_and_cond_file_path = join(
BASE_LOCATION,
unique_folder_name,
extracted_folder_name,
yaml_file_data["terms_and_conditions"],
)
if challenge_terms_and_cond_file_path.endswith(".html") and isfile(
challenge_terms_and_cond_file_path
):
yaml_file_data["terms_and_conditions"] = get_file_content(
challenge_terms_and_cond_file_path, "rb"
).decode("utf-8")
else:
yaml_file_data["terms_and_conditions"] = None
except KeyError:
message = (
"There is no key for terms and conditions. "
"Please add it and then try again!"
)
response_data = {"error": message}
return Response(response_data, status.HTTP_406_NOT_ACCEPTABLE)
# Check for submission guidelines file
try:
submission_guidelines_file_path = join(
BASE_LOCATION,
unique_folder_name,
extracted_folder_name,
yaml_file_data["submission_guidelines"],
)
if submission_guidelines_file_path.endswith(".html") and isfile(
submission_guidelines_file_path
):
yaml_file_data["submission_guidelines"] = get_file_content(
submission_guidelines_file_path, "rb"
).decode("utf-8")
else:
yaml_file_data["submission_guidelines"] = None
except KeyError:
message = (
"There is no key for submission guidelines. "
"Please add it and then try again!"
)
response_data = {"error": message}
return Response(response_data, status.HTTP_406_NOT_ACCEPTABLE)
# Check for leaderboard schema in YAML file
leaderboard_schema = yaml_file_data.get("leaderboard")
"""
Format of leaderboard data is:
[
{
'id': 1,
'schema': {
'default_order_by': 'bleu',
'labels': ['bleu']
}
}
]
"""
if leaderboard_schema:
if "schema" not in leaderboard_schema[0]:
message = (
"There is no leaderboard schema in the YAML "
"configuration file. Please add it and then try again!"
)
response_data = {"error": message}
return Response(response_data, status.HTTP_406_NOT_ACCEPTABLE)
if "default_order_by" not in leaderboard_schema[0].get("schema"):
message = (
"There is no 'default_order_by' key in leaderboard "
"schema. Please add it and then try again!"
)
response_data = {"error": message}
return Response(response_data, status.HTTP_406_NOT_ACCEPTABLE)
if "labels" not in leaderboard_schema[0].get("schema"):
message = (
"There is no 'labels' key in leaderboard "
"schema. Please add it and then try again!"
)
response_data = {"error": message}
return Response(response_data, status.HTTP_406_NOT_ACCEPTABLE)
else:
message = (
"There is no key 'leaderboard' "
"in the YAML file. Please add it and then try again!"
)
response_data = {"error": message}
return Response(response_data, status.HTTP_406_NOT_ACCEPTABLE)
challenge_fields = [
"title",
"description",
"start_date",
"start_date",
"end_date",
]
challenge_phase_fields = ["name", "start_date", "end_date"]
if is_challenge_template:
for field in challenge_fields:
yaml_file_data[field] = challenge_data_from_hosts.get(field)
# Mapping the challenge phase data to that in yaml_file_data
challenge_phases = yaml_file_data["challenge_phases"]
challenge_phases_from_hosts = challenge_data_from_hosts.get(
"challenge_phases"
)
challenge_phases_from_hosts = json.loads(challenge_phases_from_hosts)
for challenge_phase_data, challenge_phase_data_from_hosts in zip(
challenge_phases, challenge_phases_from_hosts
):
for field in challenge_phase_fields:
challenge_phase_data[
field
] = challenge_phase_data_from_hosts.get(field)
try:
with transaction.atomic():
serializer = ZipChallengeSerializer(
data=yaml_file_data,
context={
"request": request,
"challenge_host_team": challenge_host_team,
"image": challenge_image_file,
"evaluation_script": challenge_evaluation_script_file,
},
)
if serializer.is_valid():
serializer.save()
challenge = serializer.instance
queue_name = get_queue_name(challenge.title, challenge.pk)
challenge.queue = queue_name
challenge.save()
else:
response_data = serializer.errors
raise RuntimeError()
# transaction.set_rollback(True)
# return Response(response_data, status.HTTP_406_NOT_ACCEPTABLE)
# Add Tags
add_tags_to_challenge(yaml_file_data, challenge)
# Add Domain
verify_complete = add_domain_to_challenge(yaml_file_data, challenge)
if verify_complete is not None:
return Response(verify_complete, status=status.HTTP_400_BAD_REQUEST)
# Add Sponsors
error_messages = add_sponsors_to_challenge(yaml_file_data, challenge)
if error_messages is not None:
return Response(error_messages, status=status.HTTP_400_BAD_REQUEST)
# Add Prizes
error_messages = add_prizes_to_challenge(yaml_file_data, challenge)
if error_messages is not None:
return Response(error_messages, status=status.HTTP_400_BAD_REQUEST)
# Create Leaderboard
yaml_file_data_of_leaderboard = yaml_file_data["leaderboard"]
leaderboard_ids = {}
for data in yaml_file_data_of_leaderboard:
serializer = LeaderboardSerializer(
data=data, context={"config_id": data["id"]}
)
if serializer.is_valid():
serializer.save()
leaderboard_ids[str(data["id"])] = serializer.instance.pk
else:
response_data = serializer.errors
raise RuntimeError()
# Create Challenge Phase
challenge_phase_ids = {}
for data in challenge_phases_data:
# Check for challenge phase description file
phase_description_file_path = join(
BASE_LOCATION,
unique_folder_name,
extracted_folder_name,
data["description"],
)
if phase_description_file_path.endswith(".html") and isfile(
phase_description_file_path
):
data["description"] = get_file_content(
phase_description_file_path, "rb"
).decode("utf-8")
else:
data["description"] = None
data["slug"] = "{}-{}-{}".format(
challenge.title.split(" ")[0].lower(),
data["codename"].replace(" ", "-").lower(),
challenge.pk,
)[:198]
test_annotation_file = data.get("test_annotation_file")
if test_annotation_file:
test_annotation_file_path = join(
BASE_LOCATION,
unique_folder_name,
extracted_folder_name,
test_annotation_file,
)
if isfile(test_annotation_file_path):
with open(
test_annotation_file_path, "rb"
) as test_annotation_file:
challenge_test_annotation_file = ContentFile(
test_annotation_file.read(),
test_annotation_file_path,
)
if data.get("max_submissions_per_month", None) is None:
data["max_submissions_per_month"] = data.get(
"max_submissions", None
)
if test_annotation_file:
serializer = ChallengePhaseCreateSerializer(
data=data,
context={
"challenge": challenge,
"test_annotation": challenge_test_annotation_file,
"config_id": data["id"],
},
)
else:
# This is when the host wants to upload the annotation file later through CLI
serializer = ChallengePhaseCreateSerializer(
data=data,
context={
"challenge": challenge,
"config_id": data["id"],
},
)
if serializer.is_valid():
serializer.save()
challenge_phase_ids[
str(data["id"])
] = serializer.instance.pk
else:
response_data = serializer.errors
raise RuntimeError()
# Create Dataset Splits
yaml_file_data_of_dataset_split = yaml_file_data["dataset_splits"]
dataset_split_ids = {}
for data in yaml_file_data_of_dataset_split:
serializer = DatasetSplitSerializer(
data=data, context={"config_id": data["id"]}
)
if serializer.is_valid():
serializer.save()
dataset_split_ids[str(data["id"])] = serializer.instance.pk
else:
# Return error when dataset split name is not unique.
response_data = serializer.errors
raise RuntimeError()
# Create Challenge Phase Splits
try:
challenge_phase_splits_data = yaml_file_data[
"challenge_phase_splits"
]
except KeyError:
message = (
"There is no key for challenge phase splits. "
"Please add it and then try again!"
)
response_data = {"error": message}
return Response(response_data, status.HTTP_406_NOT_ACCEPTABLE)
for data in challenge_phase_splits_data:
if challenge_phase_ids.get(str(data["challenge_phase_id"])) is None:
message = (
"Challenge phase with phase id {} doesn't exist.".format(data["challenge_phase_id"])
)
response_data = {"error": message}
return Response(response_data, status.HTTP_406_NOT_ACCEPTABLE)
if leaderboard_ids.get(str(data["leaderboard_id"])) is None:
message = (
"Leaderboard with id {} doesn't exist.".format(data["leaderboard_id"])
)
response_data = {"error": message}
return Response(response_data, status.HTTP_406_NOT_ACCEPTABLE)
leaderboard = leaderboard_ids[str(data["leaderboard_id"])]
if dataset_split_ids.get(str(data["dataset_split_id"])) is None:
message = (
"Dataset split with id {} doesn't exist.".format(data["dataset_split_id"])
)
response_data = {"error": message}
return Response(response_data, status.HTTP_406_NOT_ACCEPTABLE)
challenge_phase = challenge_phase_ids[
str(data["challenge_phase_id"])
]
dataset_split = dataset_split_ids[
str(data["dataset_split_id"])
]
visibility = data["visibility"]
leaderboard_decimal_precision = data["leaderboard_decimal_precision"]
is_leaderboard_order_descending = data["is_leaderboard_order_descending"]
data = {
"challenge_phase": challenge_phase,
"leaderboard": leaderboard,
"dataset_split": dataset_split,
"visibility": visibility,
"leaderboard_decimal_precision": leaderboard_decimal_precision,
"is_leaderboard_order_descending": is_leaderboard_order_descending
}
serializer = ZipChallengePhaseSplitSerializer(data=data)
if serializer.is_valid():
serializer.save()
else:
response_data = serializer.errors
print(response_data)
raise RuntimeError()
zip_config = ChallengeConfiguration.objects.get(
pk=uploaded_zip_file.pk
)
if zip_config:
emails = challenge_host_team.get_all_challenge_host_email()
if not challenge.is_docker_based:
# Add the Challenge Host as a test participant.
team_name = "Host_{}_Team".format(random.randint(1, 100000))
participant_host_team = ParticipantTeam(
team_name=team_name,
created_by=challenge_host_team.created_by,
)
participant_host_team.save()
for email in emails:
user = User.objects.get(email=email)
host = Participant(
user=user,
status=Participant.ACCEPTED,
team=participant_host_team,
)
host.save()
challenge.participant_teams.add(participant_host_team)
zip_config.challenge = challenge
zip_config.save()
if challenge.is_docker_based:
challenge_evaluation_cluster = ChallengeEvaluationCluster(
challenge=zip_config.challenge
)
evaluation_cluster_serializer = (
ChallengeEvaluationClusterSerializer(
challenge_evaluation_cluster,
data={
"name": "{0}-cluster".format(
challenge.title.replace(" ", "-")
)
},
partial=True,
)
)
if evaluation_cluster_serializer.is_valid():
evaluation_cluster_serializer.save()
if not settings.DEBUG:
message = {
"text": "A *new challenge* has been uploaded to EvalAI.",
"fields": [
{
"title": "Email",
"value": request.user.email,
"short": False,
},
{
"title": "Challenge title",
"value": challenge.title,
"short": False,
},
],
}
send_slack_notification(message=message)
template_data = get_challenge_template_data(zip_config.challenge)
if (
not challenge.is_docker_based
and challenge.inform_hosts
and not challenge.remote_evaluation
):
try:
response = start_workers([zip_config.challenge])
count, failures = response["count"], response["failures"]
logging.info(
"Total worker start count is {} and failures are: {}".format(
count, failures
)
)
if count:
logging.info(
"{} workers started successfully".format(count)
)
template_id = settings.SENDGRID_SETTINGS.get(
"TEMPLATES"
).get("WORKER_START_EMAIL")
send_emails(emails, template_id, template_data)
except Exception:
logger.exception(
"Failed to start workers for challenge {}".format(
zip_config.challenge.pk
)
)
response_data = {
"success": "Challenge {} has been created successfully and"
" sent for review to EvalAI Admin.".format(challenge.title)
}
return Response(response_data, status=status.HTTP_201_CREATED)
except: # noqa: E722
try:
if response_data:
response_data = {"error": json.dumps(response_data)}
return Response(
response_data, status=status.HTTP_406_NOT_ACCEPTABLE
)
except: # noqa: E722
response_data = {
"error": "Error in creating challenge. Please check the yaml configuration!"
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
finally:
try:
shutil.rmtree(BASE_LOCATION)
logger.info("Zip folder is removed")
except: # noqa: E722
logger.exception(
"Zip folder for challenge {} is not removed from {} location".format(
challenge.pk, BASE_LOCATION
)
)
@swagger_auto_schema(
methods=["get"],
manual_parameters=[
openapi.Parameter(
name="challenge_pk",
in_=openapi.IN_PATH,
type=openapi.TYPE_STRING,
description="Challenge ID",
required=True,
),
openapi.Parameter(
name="challenge_phase_pk",
in_=openapi.IN_PATH,
type=openapi.TYPE_STRING,
description="Challenge Phase ID",
required=True,
),
],
operation_id="get_all_submissions_for_a_challenge",
responses={
status.HTTP_200_OK: openapi.Response(
description="",
schema=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
"count": openapi.Schema(
type=openapi.TYPE_STRING,
description="Count of submissions",
),
"next": openapi.Schema(
type=openapi.TYPE_STRING,
description="URL of next page of results",
),
"previous": openapi.Schema(
type=openapi.TYPE_STRING,
description="URL of previous page of results",
),
"results": openapi.Schema(
type=openapi.TYPE_ARRAY,
description="Array of results object",
items=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
"id": openapi.Schema(
type=openapi.TYPE_INTEGER,
description="Submission ID",
),
"participant_team": openapi.Schema(
type=openapi.TYPE_STRING,
description="Participant Team Name",
),
"challenge_phase": openapi.Schema(
type=openapi.TYPE_STRING,
description="Challenge Phase name",
),
"created_by": openapi.Schema(
type=openapi.TYPE_STRING,
description="Username of user who created the submission",
),
"status": openapi.Schema(
type=openapi.TYPE_STRING,
description="Status of the submission",
),
"is_public": openapi.Schema(
type=openapi.TYPE_BOOLEAN,
description="Shows if the submission is public or not",
),
"is_flagged": openapi.Schema(
type=openapi.TYPE_BOOLEAN,
description="Shows if the submission is flagged or not",
),
"submission_number": openapi.Schema(
type=openapi.TYPE_INTEGER,
description="Count of submissions done by a team",
),
"submitted_at": openapi.Schema(
type=openapi.TYPE_STRING,
description="Timestamp when submission was submitted",
),
"execution_time": openapi.Schema(
type=openapi.TYPE_NUMBER,
description="Execution time of the submission in seconds",
),
"input_file": openapi.Schema(
type=openapi.TYPE_STRING,
description="URL of the file submitted by user",
),
"stdout_file": openapi.Schema(
type=openapi.TYPE_STRING,
description="URL of the stdout file generated after evaluating submission",
),
"stderr_file": openapi.Schema(
type=openapi.TYPE_STRING,
description="URL of the stderr file generated after evaluating submission only available when the submission fails",
),
"environment_log_file": openapi.Schema(
type=openapi.TYPE_STRING,
description="URL of the Environment Log File generated after evaluating submission (only available for code-upload challenge submissions)"
),
"submission_result_file": openapi.Schema(
type=openapi.TYPE_STRING,
description="URL of the result file generated after successfully evaluating submission",
),
"submission_metadata_file": openapi.Schema(
type=openapi.TYPE_STRING,
description="URL of the metadata file generated after successfully evaluating submission",
),
"participant_team_members_email_ids": openapi.Schema(
type=openapi.TYPE_ARRAY,
items=openapi.Items(
type=openapi.TYPE_STRING
),
description="Array of the participant team members email ID's",
),
"participant_team_members_affiliations": openapi.Schema(
type=openapi.TYPE_ARRAY,
items=openapi.Items(
type=openapi.TYPE_STRING
),
description="Array of the participant team members affiliations",
),
"created_at": openapi.Schema(
type=openapi.TYPE_STRING,
description="Timestamp when the submission was created",
),
"method_name": openapi.Schema(
type=openapi.TYPE_STRING,
description="Name of the method used by the participant team",
),
"participant_team_members": openapi.Schema(
type=openapi.TYPE_ARRAY,
items=openapi.Items(
type=openapi.TYPE_STRING
),
description="Array of participant team members name and email",
),
},
),
),
},
),
)
},
)
@api_view(["GET"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def get_all_submissions_of_challenge(
request, challenge_pk, challenge_phase_pk
):
"""
Returns all the submissions for a particular challenge
"""
# To check for the corresponding challenge from challenge_pk.
challenge = get_challenge_model(challenge_pk)
# To check for the corresponding challenge phase from the challenge_phase_pk and challenge.
try:
challenge_phase = ChallengePhase.objects.get(
pk=challenge_phase_pk, challenge=challenge
)
except ChallengePhase.DoesNotExist:
response_data = {
"error": "Challenge Phase {} does not exist".format(
challenge_phase_pk
)
}
return Response(response_data, status=status.HTTP_404_NOT_FOUND)
# To check for the user as a host of the challenge from the request and challenge_pk.
if is_user_a_host_of_challenge(
user=request.user, challenge_pk=challenge_pk
):
# Filter submissions on the basis of challenge for host for now. Later on, the support for query
# parameters like challenge phase, date is to be added.
submissions = Submission.objects.filter(
challenge_phase=challenge_phase, ignore_submission=False
).order_by("-submitted_at")
filtered_submissions = SubmissionFilter(
request.GET, queryset=submissions
)
paginator, result_page = paginated_queryset(
filtered_submissions.qs, request
)
serializer = ChallengeSubmissionManagementSerializer(
result_page, many=True, context={"request": request}
)
response_data = serializer.data
return paginator.get_paginated_response(response_data)
# To check for the user as a participant of the challenge from the request and challenge_pk.
elif has_user_participated_in_challenge(
user=request.user, challenge_id=challenge_pk
):
# get participant team object for the user for a particular challenge.
participant_team_pk = get_participant_team_id_of_user_for_a_challenge(
request.user, challenge_pk
)
# Filter submissions on the basis of challenge phase for a participant.
submissions = Submission.objects.filter(
participant_team=participant_team_pk,
challenge_phase=challenge_phase,
).order_by("-submitted_at")
paginator, result_page = paginated_queryset(submissions, request)
serializer = SubmissionSerializer(
result_page, many=True, context={"request": request}
)
response_data = serializer.data
return paginator.get_paginated_response(response_data)
# when user is neither host not participant of the challenge.
else:
response_data = {
"error": "You are neither host nor participant of the challenge!"
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
@swagger_auto_schema(
methods=["get"],
manual_parameters=[
openapi.Parameter(
name="challenge_pk",
in_=openapi.IN_PATH,
type=openapi.TYPE_NUMBER,
description="Challenge pk",
required=True,
),
openapi.Parameter(
name="challenge_phase_pk",
in_=openapi.IN_PATH,
type=openapi.TYPE_NUMBER,
description="Challenge phase pk",
required=True,
),
openapi.Parameter(
name="file_type",
in_=openapi.IN_PATH,
type=openapi.TYPE_STRING,
description="File type",
required=True,
),
],
operation_id="download_all_submissions",
responses={
status.HTTP_200_OK: openapi.Response(""),
status.HTTP_400_BAD_REQUEST: openapi.Response(
"{'error': 'The file type requested is not valid!'}"
),
status.HTTP_404_NOT_FOUND: openapi.Response(
"{'error': 'Challenge Phase does not exist'}"
),
},
)
@swagger_auto_schema(
methods=["post"],
manual_parameters=[
openapi.Parameter(
name="challenge_pk",
in_=openapi.IN_PATH,
type=openapi.TYPE_NUMBER,
description="Challenge pk",
required=True,
),
openapi.Parameter(
name="challenge_phase_pk",
in_=openapi.IN_PATH,
type=openapi.TYPE_NUMBER,
description="Challenge phase pk",
required=True,
),
openapi.Parameter(
name="file_type",
in_=openapi.IN_PATH,
type=openapi.TYPE_STRING,
description="File type",
required=True,
),
],
operation_id="download_all_submissions",
responses={
status.HTTP_200_OK: openapi.Response(""),
status.HTTP_400_BAD_REQUEST: openapi.Response(
"{'error': 'The file type requested is not valid!'}"
),
status.HTTP_401_UNAUTHORIZED: openapi.Response(
"{'error': 'Sorry, you do not belong to this Host Team!'}"
),
},
)
@api_view(["GET", "POST"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def download_all_submissions(
request, challenge_pk, challenge_phase_pk, file_type
):
"""
API endpoint to download all the submissions for a particular challenge as a csv
Arguments:
request {HttpRequest} -- The request object
challenge_pk {[int]} -- Challenge primary key
challenge_phase_pk {[int]} -- Challenge phase primary key
file_type {[str]} -- File type
Returns:
Response Object -- An object containing api response
"""
# To check for the corresponding challenge from challenge_pk.
challenge = get_challenge_model(challenge_pk)
# To check for the corresponding challenge phase from the challenge_phase_pk and challenge.
try:
challenge_phase = ChallengePhase.objects.get(
pk=challenge_phase_pk, challenge=challenge
)
except ChallengePhase.DoesNotExist:
response_data = {
"error": "Challenge Phase {} does not exist".format(
challenge_phase_pk
)
}
return Response(response_data, status=status.HTTP_404_NOT_FOUND)
if request.method == "GET":
if file_type == "csv":
if is_user_a_host_of_challenge(
user=request.user, challenge_pk=challenge_pk
):
submissions = Submission.objects.filter(
challenge_phase__challenge=challenge
).order_by("-submitted_at")
submissions = ChallengeSubmissionManagementSerializer(
submissions, many=True, context={"request": request}
)
response = HttpResponse(content_type="text/csv")
response[
"Content-Disposition"
] = "attachment; filename=all_submissions.csv"
writer = csv.writer(response)
writer.writerow(
[
"id",
"Team Name",
"Team Members",
"Team Members Email Id",
"Team Members Affiliaton",
"Challenge Phase",
"Status",
"Created By",
"Execution Time(sec.)",
"Submission Number",
"Submitted File",
"Stdout File",
"Stderr File",
"Environment Log File",
"Submitted At",
"Submission Result File",
"Submission Metadata File",
"Method Name",
"Method Description",
"Publication URL",
"Project URL",
"Submission Meta Attributes",
]
)
# Issue: "#" isn't parsed by writer.writerow(), hence it is replaced by "-"
# TODO: Find a better way to solve the above issue.
for submission in submissions.data:
submission_meta_attributes = (
parse_submission_meta_attributes(submission)
)
writer.writerow(
[
submission["id"],
submission["participant_team"],
",".join(
username["username"]
for username in submission[
"participant_team_members"
]
),
",".join(
email["email"]
for email in submission[
"participant_team_members"
]
),
",".join(
affiliation
for affiliation in submission[
"participant_team_members_affiliations"
]
),
submission["challenge_phase"],
submission["status"],
submission["created_by"],
submission["execution_time"],
submission["submission_number"],
submission["input_file"],
submission["stdout_file"],
submission["stderr_file"],
submission["environment_log_file"],
submission["created_at"],
submission["submission_result_file"],
submission["submission_metadata_file"],
submission["method_name"].replace("#", "-"),
submission["method_description"].replace("#", "-"),
submission["publication_url"],
submission["project_url"],
submission_meta_attributes,
]
)
return response
elif has_user_participated_in_challenge(
user=request.user, challenge_id=challenge_pk
):
# get participant team object for the user for a particular challenge.
participant_team_pk = (
get_participant_team_id_of_user_for_a_challenge(
request.user, challenge_pk
)
)
# Filter submissions on the basis of challenge phase for a participant.
submissions = Submission.objects.filter(
participant_team=participant_team_pk,
challenge_phase=challenge_phase,
).order_by("-submitted_at")
submissions = ChallengeSubmissionManagementSerializer(
submissions, many=True, context={"request": request}
)
response = HttpResponse(content_type="text/csv")
response[
"Content-Disposition"
] = "attachment; filename=all_submissions.csv"
writer = csv.writer(response)
writer.writerow(
[
"Team Name",
"Method Name",
"Status",
"Execution Time(sec.)",
"Submitted File",
"Result File",
"Stdout File",
"Stderr File",
"Submitted At",
]
)
for submission in submissions.data:
writer.writerow(
[
submission["participant_team"],
submission["method_name"],
submission["status"],
submission["execution_time"],
submission["input_file"],
submission["submission_result_file"],
submission["stdout_file"],
submission["stderr_file"],
submission["created_at"],
]
)
return response
else:
response_data = {
"error": "You are neither host nor participant of the challenge!"
}
return Response(
response_data, status=status.HTTP_400_BAD_REQUEST
)
else:
response_data = {"error": "The file type requested is not valid!"}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
elif request.method == "POST":
if file_type == "csv":
if is_user_a_host_of_challenge(
user=request.user, challenge_pk=challenge_pk
):
fields_to_export = {
"participant_team": "Team Name",
"participant_team_members": "Team Members",
"participant_team_members_email": "Team Members Email Id",
"participant_team_members_affiliation": "Team Members Affiliation",
"challenge_phase": "Challenge Phase",
"status": "Status",
"created_by": "Created By",
"execution_time": "Execution Time(sec.)",
"submission_number": "Submission Number",
"input_file": "Submitted File",
"stdout_file": "Stdout File",
"stderr_file": "Stderr File",
"environment_log_file": "Environment Log File",
"created_at": "Submitted At (mm/dd/yyyy hh:mm:ss)",
"submission_result_file": "Submission Result File",
"submission_metadata_file": "Submission Metadata File",
"method_name": "Method Name",
"method_description": "Method Description",
"publication_url": "Publication URL",
"project_url": "Project URL",
"submission_meta_attributes": "Submission Meta Attributes",
}
submissions = Submission.objects.filter(
challenge_phase__challenge=challenge
).order_by("-submitted_at")
submissions = ChallengeSubmissionManagementSerializer(
submissions, many=True, context={"request": request}
)
response = HttpResponse(content_type="text/csv")
response[
"Content-Disposition"
] = "attachment; filename=all_submissions.csv"
writer = csv.writer(response)
fields = [fields_to_export[field] for field in request.data]
fields.insert(0, "id")
writer.writerow(fields)
for submission in submissions.data:
row = [submission["id"]]
for field in request.data:
if field == "participant_team_members":
row.append(
",".join(
username["username"]
for username in submission[
"participant_team_members"
]
)
)
elif field == "participant_team_members_email":
row.append(
",".join(
email["email"]
for email in submission[
"participant_team_members"
]
)
)
elif field == "participant_team_members_affiliation":
row.append(
",".join(
affiliation
for affiliation in submission[
"participant_team_members_affiliations"
]
)
)
elif field == "created_at":
row.append(
submission["created_at"].strftime(
"%m/%d/%Y %H:%M:%S"
)
)
elif field == "submission_meta_attributes":
submission_meta_attributes = (
parse_submission_meta_attributes(submission)
)
row.append(submission_meta_attributes)
else:
row.append(submission[field])
writer.writerow(row)
return response
else:
response_data = {
"error": "Sorry, you do not belong to this Host Team!"
}
return Response(
response_data, status=status.HTTP_401_UNAUTHORIZED
)
else:
response_data = {"error": "The file type requested is not valid!"}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
@api_view(["POST"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def create_leaderboard(request):
"""
Creates a leaderboard
"""
serializer = LeaderboardSerializer(
data=request.data, many=True, allow_empty=False
)
if serializer.is_valid():
serializer.save()
response_data = serializer.data
return Response(response_data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@api_view(["GET", "PATCH"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def get_or_update_leaderboard(request, leaderboard_pk):
"""
Returns or Updates a leaderboard
"""
leaderboard = get_leaderboard_model(leaderboard_pk)
if request.method == "PATCH":
if "schema" in request.data.keys():
request.data['schema'] = json.loads(request.data['schema'])
serializer = LeaderboardSerializer(
leaderboard, data=request.data, partial=True
)
if serializer.is_valid():
serializer.save()
response_data = serializer.data
return Response(response_data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
if request.method == "GET":
serializer = LeaderboardSerializer(leaderboard)
response_data = serializer.data
return Response(response_data, status=status.HTTP_200_OK)
@api_view(["POST"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def create_dataset_split(request):
"""
Creates a dataset split
"""
serializer = DatasetSplitSerializer(
data=request.data, many=True, allow_empty=False
)
if serializer.is_valid():
serializer.save()
response_data = serializer.data
return Response(response_data, status=status.HTTP_201_CREATED)
else:
response_data = serializer.errors
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
@api_view(["GET", "PATCH"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def get_or_update_dataset_split(request, dataset_split_pk):
"""
Returns or Updates a dataset split
"""
dataset_split = get_dataset_split_model(dataset_split_pk)
if request.method == "PATCH":
serializer = DatasetSplitSerializer(
dataset_split, data=request.data, partial=True
)
if serializer.is_valid():
serializer.save()
response_data = serializer.data
return Response(response_data, status=status.HTTP_200_OK)
else:
response_data = serializer.errors
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
if request.method == "GET":
serializer = DatasetSplitSerializer(dataset_split)
response_data = serializer.data
return Response(response_data, status=status.HTTP_200_OK)
@api_view(["POST"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def create_challenge_phase_split(request):
"""
Create Challenge Phase Split
"""
serializer = ZipChallengePhaseSplitSerializer(
data=request.data, many=True, allow_empty=False
)
if serializer.is_valid():
serializer.save()
response_data = serializer.data
return Response(response_data, status=status.HTTP_201_CREATED)
else:
response_data = serializer.errors
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
@api_view(["GET", "PATCH"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticatedOrReadOnly, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def get_or_update_challenge_phase_split(request, challenge_phase_split_pk):
"""
Returns or Updates challenge phase split
"""
challenge_phase_split = get_challenge_phase_split_model(
challenge_phase_split_pk
)
if request.method == "PATCH":
serializer = ZipChallengePhaseSplitSerializer(
challenge_phase_split, data=request.data, partial=True
)
if serializer.is_valid():
serializer.save()
response_data = serializer.data
return Response(response_data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
if request.method == "GET":
serializer = ZipChallengePhaseSplitSerializer(challenge_phase_split)
response_data = serializer.data
return Response(response_data, status=status.HTTP_200_OK)
@api_view(["PATCH"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticatedOrReadOnly, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def update_challenge_tags_and_domain(request, challenge_pk):
"""
Returns or Updates challenge tags and domain
"""
challenge = get_challenge_model(challenge_pk)
if request.method == "PATCH":
new_tags = request.data.get("list_tags", [])
domain_value = request.data.get("domain")
# Remove tags not present in the YAML file
challenge.list_tags = [tag for tag in challenge.list_tags if tag in new_tags]
# Add new tags to the challenge
for tag_name in new_tags:
if tag_name not in challenge.list_tags:
challenge.list_tags.append(tag_name)
# Verifying Domain name
valid_domains = [choice[0] for choice in challenge.DOMAIN_OPTIONS]
if domain_value in valid_domains:
challenge.domain = domain_value
challenge.save()
return Response(status=status.HTTP_200_OK)
else:
message = f"Invalid domain value:{domain_value}"
response_data = {"error": message}
return Response(response_data, status.HTTP_406_NOT_ACCEPTABLE)
@api_view(["GET"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticatedOrReadOnly, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def get_domain_choices(request):
"""
Returns domain choices
"""
if request.method == "GET":
domain_choices = Challenge.DOMAIN_OPTIONS
return Response(domain_choices, status.HTTP_200_OK)
else:
response_data = {"error": "Method not allowed"}
return Response(response_data, status.HTTP_405_METHOD_NOT_ALLOWED)
@api_view(["GET"])
@throttle_classes([UserRateThrottle])
def get_prizes_by_challenge(request, challenge_pk):
"""
Returns a list of prizes for a given challenge.
"""
try:
challenge = Challenge.objects.get(pk=challenge_pk)
except Challenge.DoesNotExist:
response_data = {"error": "Challenge does not exist"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
prizes = ChallengePrize.objects.filter(challenge=challenge)
serializer = ChallengePrizeSerializer(prizes, many=True)
response_data = serializer.data
return Response(response_data, status=status.HTTP_200_OK)
@api_view(["GET"])
@throttle_classes([UserRateThrottle])
def get_sponsors_by_challenge(request, challenge_pk):
"""
Returns a list of sponsors for a given challenge.
"""
try:
challenge = Challenge.objects.get(pk=challenge_pk)
except Challenge.DoesNotExist:
response_data = {"error": "Challenge does not exist"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
sponsors = ChallengeSponsor.objects.filter(challenge=challenge)
serializer = ChallengeSponsorSerializer(sponsors, many=True)
response_data = serializer.data
return Response(response_data, status=status.HTTP_200_OK)
@api_view(["GET", "POST"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticatedOrReadOnly, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def star_challenge(request, challenge_pk):
"""
API endpoint for starring and unstarring
a challenge.
"""
challenge = get_challenge_model(challenge_pk)
if request.method == "POST":
try:
starred_challenge = StarChallenge.objects.get(
user=request.user.pk, challenge=challenge
)
starred_challenge.is_starred = not starred_challenge.is_starred
starred_challenge.save()
serializer = StarChallengeSerializer(starred_challenge)
response_data = serializer.data
return Response(response_data, status=status.HTTP_200_OK)
except StarChallenge.DoesNotExist:
serializer = StarChallengeSerializer(
data=request.data,
context={
"request": request,
"challenge": challenge,
"is_starred": True,
},
)
if serializer.is_valid():
serializer.save()
response_data = serializer.data
return Response(response_data, status=status.HTTP_201_CREATED)
return Response(
serializer.errors, status=status.HTTP_400_BAD_REQUEST
)
if request.method == "GET":
try:
starred_challenge = StarChallenge.objects.get(
user=request.user.pk, challenge=challenge
)
serializer = StarChallengeSerializer(starred_challenge)
response_data = serializer.data
return Response(response_data, status=status.HTTP_200_OK)
except StarChallenge.DoesNotExist:
starred_challenge = StarChallenge.objects.filter(
challenge=challenge
)
if not starred_challenge:
response_data = {"is_starred": False, "count": 0}
return Response(response_data, status=status.HTTP_200_OK)
serializer = StarChallengeSerializer(starred_challenge, many=True)
response_data = {
"is_starred": False,
"count": serializer.data[0]["count"],
}
return Response(response_data, status=status.HTTP_200_OK)
@api_view(["GET"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def get_broker_urls(request):
"""
Returns:
Queue name of approved challenges
"""
is_active = request.data.get("is_active", False)
q_params = {"approved_by_admin": True}
if is_active:
q_params["start_date__lt"] = timezone.now()
q_params["end_date__gt"] = timezone.now()
if not request.user.is_superuser:
response_data = {
"error": "You are not authorized to make this request!"
}
return Response(response_data, status=status.HTTP_403_FORBIDDEN)
else:
challenges = Challenge.objects.filter(**q_params)
response_data = challenges.values_list("queue", flat=True)
return Response(response_data, status=status.HTTP_200_OK)
@api_view(["GET"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def get_broker_url_by_challenge_pk(request, challenge_pk):
"""
Returns:
Queue name of challenge with challenge pk
"""
if not request.user.is_superuser:
response_data = {
"error": "You are not authorized to make this request!"
}
return Response(response_data, status=status.HTTP_403_FORBIDDEN)
else:
try:
challenge = Challenge.objects.get(
pk=challenge_pk, approved_by_admin=True
)
except Challenge.DoesNotExist:
response_data = {
"error": "Challenge {} does not exist".format(challenge_pk)
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
response_data = [challenge.queue]
return Response(response_data, status=status.HTTP_200_OK)
@api_view(["GET"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def get_aws_credentials_for_participant_team(request, phase_pk):
"""
Endpoint to generate AWS Credentails for CLI
Args:
- challenge: Challenge model
- participant_team: Participant Team Model
Returns:
- JSON: {
"federated_user"
"docker_repository_uri"
}
Raises:
- BadRequestException 400
- When participant_team has not participanted in challenge
- When Challenge is not Docker based
"""
challenge_phase = get_challenge_phase_model(phase_pk)
challenge = challenge_phase.challenge
participant_team = get_participant_team_of_user_for_a_challenge(
request.user, challenge.pk
)
if not challenge.is_docker_based:
response_data = {
"error": "Sorry, this is not a docker based challenge."
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
if participant_team is None:
response_data = {
"error": "You have not participated in this challenge."
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
data = get_aws_credentials_for_submission(challenge, participant_team)
response_data = {"success": data}
return Response(response_data, status=status.HTTP_200_OK)
@api_view(["POST"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def invite_users_to_challenge(request, challenge_pk):
challenge = get_challenge_model(challenge_pk)
if not challenge.is_active or not challenge.approved_by_admin:
response_data = {"error": "Sorry, the challenge is not active"}
return Response(response_data, status=status.HTTP_403_FORBIDDEN)
try:
challenge_host = ChallengeHost.objects.get(user=request.user)
except ChallengeHost.DoesNotExist:
response_data = {"error": "You're not a challenge host"}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
if not is_user_a_host_of_challenge(request.user, challenge.pk):
response_data = {
"error": "You're not authorized to invite a user in {}".format(
challenge.title
)
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
users_email = request.data.get("emails")
if not users_email:
response_data = {"error": "Users email can't be blank"}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
try:
users_email = eval(users_email)
except Exception:
response_data = {"error": "Invalid format for users email"}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
invalid_emails = []
valid_emails = []
for email in users_email:
try:
invited_user = UserInvitation.objects.get(
email=email, challenge=challenge.pk
)
invitation_key = invited_user.invitation_key
except UserInvitation.DoesNotExist:
invitation_key = uuid.uuid4()
invitation_status = UserInvitation.PENDING
data = {
"email": email,
"invitation_key": str(invitation_key),
"status": invitation_status,
"challenge": challenge.pk,
"invited_by": challenge_host.pk,
}
serializer = UserInvitationSerializer(data=data, partial=True)
if serializer.is_valid():
user, created = User.objects.get_or_create(
username=email, email=email
)
if created:
EmailAddress.objects.create(
user=user, email=email, primary=True, verified=True
)
data["user"] = user.pk
valid_emails.append(data)
else:
invalid_emails.append(email)
sender_email = settings.CLOUDCV_TEAM_EMAIL
hostname = get_url_from_hostname(settings.HOSTNAME)
url = "{}/accept-invitation/{}/".format(hostname, invitation_key)
template_data = {"title": challenge.title, "url": url}
if challenge.image:
template_data["image"] = challenge.image.url
template_id = settings.SENDGRID_SETTINGS.get("TEMPLATES").get(
"CHALLENGE_INVITATION"
)
if email not in invalid_emails:
send_email(sender_email, email, template_id, template_data)
if valid_emails:
serializer = UserInvitationSerializer(data=valid_emails, many=True)
if serializer.is_valid():
serializer.save()
if len(users_email) == len(invalid_emails):
response_data = {"error": "Please enter correct email addresses"}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
response_data = {
"success": "Invitations sent successfully",
"invalid_emails": invalid_emails,
}
return Response(response_data, status=status.HTTP_200_OK)
@api_view(["GET", "PATCH"])
@throttle_classes([UserRateThrottle])
@permission_classes(())
def accept_challenge_invitation(request, invitation_key):
try:
invitation = UserInvitation.objects.get(invitation_key=invitation_key)
except UserInvitation.DoesNotExist:
response_data = {
"error": "The invitation with key {} doesn't exist".format(
invitation_key
)
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
if request.method == "GET":
serializer = UserInvitationSerializer(invitation)
return Response(serializer.data, status=status.HTTP_200_OK)
elif request.method == "PATCH":
serializer = UserDetailsSerializer(
invitation.user, data=request.data, partial=True
)
if serializer.is_valid():
serializer.save()
data = {"password": make_password(serializer.data.get("password"))}
serializer = UserDetailsSerializer(
invitation.user, data=data, partial=True
)
if serializer.is_valid():
serializer.save()
data = {"status": UserInvitation.ACCEPTED}
serializer = UserInvitationSerializer(
invitation, data=data, partial=True
)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@api_view(["GET"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def get_challenge_by_queue_name(request, queue_name):
"""
API endpoint to fetch the challenge details by using pk
Arguments:
queue_name -- Challenge queue name for which the challenge deatils are fetched
Returns:
Response Object -- An object containing challenge details
"""
try:
challenge = Challenge.objects.get(queue=queue_name)
except Challenge.DoesNotExist:
response_data = {
"error": "Challenge with queue name {} does not exist".format(
queue_name
)
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
if not is_user_a_host_of_challenge(request.user, challenge.pk):
response_data = {
"error": "Sorry, you are not authorized to access this challenge."
}
return Response(response_data, status=status.HTTP_401_UNAUTHORIZED)
serializer = ZipChallengeSerializer(
challenge, context={"request": request}
)
response_data = serializer.data
return Response(response_data, status=status.HTTP_200_OK)
@api_view(["GET"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def get_challenge_phases_by_challenge_pk(request, challenge_pk):
"""
API endpoint to fetch all challenge phase details corresponding to a challenge using challenge pk
Arguments:
challenge_pk -- Challenge Id for which the details is to be fetched
Returns:
Response Object -- An object containing all challenge phases for the challenge
"""
challenge = get_challenge_model(challenge_pk)
if not is_user_a_host_of_challenge(request.user, challenge.pk):
response_data = {
"error": "Sorry, you are not authorized to access these challenge phases."
}
return Response(response_data, status=status.HTTP_401_UNAUTHORIZED)
challenge_phases = ChallengePhase.objects.filter(challenge=challenge_pk)
serializer = ChallengePhaseCreateSerializer(
challenge_phases, context={"request": request}, many=True
)
response_data = serializer.data
return Response(response_data, status=status.HTTP_200_OK)
@api_view(["GET"])
@throttle_classes([AnonRateThrottle])
def get_challenge_phase_by_pk(request, pk):
"""
Returns a particular challenge phase details by pk
"""
challenge_phase = get_challenge_phase_model(pk)
serializer = ChallengePhaseSerializer(
challenge_phase, context={"request": request}
)
response_data = serializer.data
return Response(response_data, status=status.HTTP_200_OK)
@api_view(["GET"])
@throttle_classes([AnonRateThrottle])
def get_challenge_phase_by_slug(request, slug):
"""
Returns a particular challenge phase details by pk
"""
try:
challenge_phase = ChallengePhase.objects.get(slug=slug)
except ChallengePhase.DoesNotExist:
response_data = {
"error": "Challenge phase with slug {} does not exist".format(slug)
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
serializer = ChallengePhaseSerializer(challenge_phase)
response_data = serializer.data
return Response(response_data, status=status.HTTP_200_OK)
@api_view(["GET"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def get_challenge_phase_environment_url(request, slug):
"""
Returns environment image url and tag required for RL challenge evaluation
"""
try:
challenge_phase = ChallengePhase.objects.get(slug=slug)
except ChallengePhase.DoesNotExist:
response_data = {
"error": "Challenge phase with slug {} does not exist".format(slug)
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
challenge = get_challenge_model(challenge_phase.challenge.pk)
if not is_user_a_host_of_challenge(request.user, challenge.pk):
response_data = {
"error": "Sorry, you are not authorized to access test environment URL."
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
if not challenge.is_docker_based:
response_data = {
"error": "The challenge doesn't require uploading Docker images, hence no test environment URL."
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
response_data = {"environment_url": challenge_phase.environment_url}
return Response(response_data, status=status.HTTP_200_OK)
@api_view(["GET"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def get_challenge_evaluation_cluster_details(request, challenge_pk):
"""API to get challenge evaluation cluster details for a challenge
Arguments:
request {HttpRequest} -- The request object
challenge_pk {int} -- The challenge pk for which the cluster details are required
Returns:
Response object -- Response object with appropriate response code (200/400/403/404)
"""
challenge = get_challenge_model(challenge_pk)
if not is_user_a_host_of_challenge(request.user, challenge.pk):
response_data = {
"error": "Sorry, you are not authorized to access evaluation cluster details."
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
if not challenge.is_docker_based:
response_data = {
"error": "The challenge doesn't require uploading Docker images, hence no evaluation cluster details."
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
try:
challenge_evaluation_cluster = ChallengeEvaluationCluster.objects.get(
challenge=challenge
)
except ChallengeEvaluationCluster.DoesNotExist:
response_data = {
"error": "Challenge evaluation cluster for the challenge with pk {} does not exist".format(
challenge.pk
)
}
return Response(response_data, status=status.HTTP_404_NOT_FOUND)
serializer = ChallengeEvaluationClusterSerializer(
challenge_evaluation_cluster, context={"request": request}
)
response_data = serializer.data
return Response(response_data, status=status.HTTP_200_OK)
@api_view(["POST"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def validate_challenge_config(request, challenge_host_team_pk):
challenge_host_team = get_challenge_host_team_model(challenge_host_team_pk)
response_data = {}
BASE_LOCATION = tempfile.mkdtemp()
unique_folder_name = get_unique_alpha_numeric_key(10)
CHALLENGE_ZIP_DOWNLOAD_LOCATION = join(
BASE_LOCATION, "{}.zip".format(unique_folder_name)
)
challenge_queryset = Challenge.objects.filter(
github_repository=request.data["GITHUB_REPOSITORY"]
)
data = request.data
challenge_config_serializer = ChallengeConfigSerializer(
data=data, context={"request": request}
)
if challenge_config_serializer.is_valid():
challenge_config_serializer.save()
uploaded_zip_file_path = challenge_config_serializer.data[
"zip_configuration"
]
else:
response_data["error"] = challenge_config_serializer.errors
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
is_success, error_description = download_and_write_file(
uploaded_zip_file_path, True, CHALLENGE_ZIP_DOWNLOAD_LOCATION, "wb"
)
if not is_success:
response_data["error"] = error_description
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
# Extract zip file
try:
zip_ref = extract_zip_file(
CHALLENGE_ZIP_DOWNLOAD_LOCATION,
"r",
join(BASE_LOCATION, unique_folder_name),
)
except zipfile.BadZipfile:
message = "The zip file contents cannot be extracted. Please check the format!"
response_data["error"] = message
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
error_messages, yaml_file_data, files = validate_challenge_config_util(
request,
challenge_host_team,
BASE_LOCATION,
unique_folder_name,
zip_ref,
challenge_queryset[0] if challenge_queryset else None
)
shutil.rmtree(BASE_LOCATION)
if len(error_messages):
response_data["error"] = "\n".join(error_messages)
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
else:
message = "The challenge config has been validated successfully"
response_data = {"Success": message}
return Response(response_data, status=status.HTTP_200_OK)
@api_view(["GET"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def get_worker_logs(request, challenge_pk):
if not is_user_a_host_of_challenge(request.user, challenge_pk):
response_data = {
"error": "Sorry, you are not authorized to access the worker logs."
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
challenge = get_challenge_model(challenge_pk)
response_data = []
log_group_name = get_log_group_name(challenge.pk)
log_stream_prefix = challenge.queue
pattern = "" # Empty string to get all logs including container logs.
# This is to specify the time window for fetching logs: 3 days before from current time.
timeframe = 4320
limit = 1000
current_time = int(round(time.time() * 1000))
start_time = current_time - (timeframe * 60000)
end_time = current_time
logs = get_logs_from_cloudwatch(
log_group_name, log_stream_prefix, start_time, end_time, pattern, limit
)
response_data = {"logs": logs}
return Response(response_data, status=status.HTTP_200_OK)
@api_view(["PUT"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def scale_resources_by_challenge_pk(request, challenge_pk):
"""
The function called by a host to update the resources used by their challenge.
Calls the scale_resources method. Before calling, checks if the caller hosts the challenge and provided valid CPU
unit counts and memory sizes (MB).
Arguments:
request {HttpRequest} -- The request object
challenge_pk {int} -- The challenge pk for which its workers' resources will be updated
Returns:
Response object -- Response object with appropriate response code (200/400/403/404)
"""
if not is_user_a_host_of_challenge(request.user, challenge_pk):
response_data = {
"error": "Sorry, you are not authorized for access worker operations."
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
if request.data.get("worker_cpu_cores") is None:
response_data = {
"error": "vCPU config missing from request data."
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
if request.data.get("worker_memory") is None:
response_data = {
"error": "Worker memory config missing from request data."
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
challenge = get_challenge_model(challenge_pk)
if challenge.workers is None or challenge.workers == 0:
response_data = {
"error": "Scaling inactive workers not supported."
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
worker_cpu_cores = int(request.data["worker_cpu_cores"])
worker_memory = int(request.data["worker_memory"])
if (
worker_cpu_cores == 256 and worker_memory in (512, 1024, 2048)
or worker_cpu_cores == 512 and worker_memory in (1024, 2048)
or worker_cpu_cores == 1024 and worker_memory == 2048
):
response = scale_resources(challenge, worker_cpu_cores, worker_memory)
if response["ResponseMetadata"]["HTTPStatusCode"] != 200:
if response.get('Error', {'Message': 'No error', 'Code': 'No error'}).get('Code', 'No error code') == \
'ClientException':
response_data = {
"error": "Challenge workers are inactive or do not exist."
}
else:
response_data = {
"error": "Issue with ECS."
}
return Response(response_data, status=status.HTTP_503_SERVICE_UNAVAILABLE)
elif response.get("Message", "N/A") == "Worker not modified":
response_data = {
"Success": "The challenge's worker cores and memory were not modified."
}
else:
response_data = {
"Success": "Worker scaled successfully!"
}
else:
response_data = {
"error": "Please specify correct config for worker vCPU and memory."
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
return Response(response_data, status=status.HTTP_200_OK)
@api_view(["PUT"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def manage_worker(request, challenge_pk, action):
if not request.user.is_staff:
if not is_user_a_host_of_challenge(request.user, challenge_pk):
response_data = {
"error": "Sorry, you are not authorized for access worker operations."
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
# make sure that the action is valid.
if action not in ("start", "stop", "restart", "delete"):
response_data = {
"error": "The action {} is invalid for worker".format(action)
}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
# Only allow EvalAI admins to delete workers
if action == "delete" and not request.user.is_staff:
response_data = {
"error": "Sorry, you are not authorized for access worker operations."
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
challenge = get_challenge_model(challenge_pk)
if challenge.end_date < pytz.UTC.localize(datetime.utcnow()) and action in ("start", "stop", "restart"):
response_data = {
"error": "Action {} worker is not supported for an inactive challenge.".format(action)
}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
response_data = {}
if action == "start":
response = start_workers([challenge])
elif action == "stop":
response = stop_workers([challenge])
elif action == "restart":
response = restart_workers([challenge])
elif action == "delete":
response = delete_workers([challenge])
if response:
count, failures = response["count"], response["failures"]
logging.info(
"Count is {} and failures are: {}".format(count, failures)
)
if count:
response_data = {"action": "Success"}
else:
message = failures[0]["message"]
response_data = {"action": "Failure", "error": message}
return Response(response_data, status=status.HTTP_200_OK)
@api_view(["GET"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def get_ec2_instance_details(request, challenge_pk):
if not request.user.is_staff:
response_data = {
"error": "Sorry, you are not authorized for access EC2 operations."
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
challenge = get_challenge_model(challenge_pk)
if not challenge.uses_ec2_worker:
response_data = {
"error": "Challenge does not use EC2 worker instance."
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
response = describe_ec2_instance(challenge)
if response:
if "error" not in response:
status_code = status.HTTP_200_OK
response_data = {
"message": response["message"],
"action": "Success",
}
else:
status_code = status.HTTP_400_BAD_REQUEST
response_data = {
"message": response["error"],
"action": "Failure",
}
else:
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
response_data = {
"message": "No Response",
"action": "Failure",
}
return Response(response_data, status=status_code)
@api_view(["PUT"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def delete_ec2_instance_by_challenge_pk(request, challenge_pk):
if not request.user.is_staff:
response_data = {
"error": "Sorry, you are not authorized for access EC2 operations."
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
challenge = get_challenge_model(challenge_pk)
if not challenge.ec2_instance_id:
response_data = {
"error": "No EC2 instance ID found for the challenge. Please ensure instance ID exists."
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
response = terminate_ec2_instance(challenge)
if response:
if "error" not in response:
status_code = status.HTTP_200_OK
response_data = {
"message": response["message"],
"action": "Success",
}
else:
status_code = status.HTTP_400_BAD_REQUEST
response_data = {
"message": response["error"],
"action": "Failure",
}
else:
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
response_data = {
"message": "No Response",
"action": "Failure",
}
return Response(response_data, status=status_code)
@api_view(["PUT"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def create_ec2_instance_by_challenge_pk(request, challenge_pk):
"""
API to create EC2 instance for a challenge
Arguments:
request {HttpRequest} -- The request object
challenge_pk {int} -- The challenge pk for which the EC2 instance is to be created
Query Parameters:
ec2_storage -- Storage size for EC2 instance
worker_instance_type -- Instance type for EC2 instance
worker_image_url -- Image URL for EC2 instance
Returns:
Response object -- Response object with appropriate response code (200/400/403/404)
"""
if request.method == "PUT":
ec2_storage = request.data.get("ec2_storage", None)
worker_instance_type = request.data.get("worker_instance_type", None)
worker_image_url = request.data.get("worker_image_url", None)
if not request.user.is_staff:
response_data = {
"error": "Sorry, you are not authorized for access EC2 operations."
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
challenge = get_challenge_model(challenge_pk)
if challenge.end_date < pytz.UTC.localize(datetime.utcnow()):
response_data = {
"error": "Creation of EC2 instance is not supported for an inactive challenge."
}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
if ec2_storage and not isinstance(ec2_storage, int):
response_data = {
"error": "Passed value of EC2 storage should be integer."
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
if worker_instance_type and not isinstance(worker_instance_type, str):
response_data = {
"error": "Passed value of worker instance type should be string."
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
if worker_image_url and not isinstance(worker_image_url, str):
response_data = {
"error": "Passed value of worker image URL should be string."
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
if not ec2_storage:
ec2_storage = challenge.ec2_storage
if not worker_instance_type:
worker_instance_type = challenge.worker_instance_type
if not worker_image_url:
worker_image_url = challenge.worker_image_url
response = create_ec2_instance(
challenge,
ec2_storage,
worker_instance_type,
worker_image_url
)
if response:
if "error" not in response:
status_code = status.HTTP_200_OK
response_data = {
"message": response["message"],
"action": "Success",
}
else:
status_code = status.HTTP_400_BAD_REQUEST
response_data = {
"message": response["error"],
"action": "Failure",
}
else:
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
response_data = {
"message": "No Response",
"action": "Failure",
}
return Response(response_data, status=status_code)
@api_view(["PUT"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def manage_ec2_instance(request, challenge_pk, action):
if not request.user.is_staff:
response_data = {
"error": "Sorry, you are not authorized for access EC2 operations."
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
# make sure that the action is valid.
if action not in ("start", "stop", "restart"):
response_data = {
"error": "The action {} is invalid for worker".format(action)
}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
challenge = get_challenge_model(challenge_pk)
if not challenge.uses_ec2_worker:
response_data = {
"error": "Challenge does not use EC2 worker instance."
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
if challenge.end_date < pytz.UTC.localize(datetime.utcnow()) and action in ("start", "restart"):
response_data = {
"error": "Action {} EC2 instance is not supported for an inactive challenge.".format(action)
}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
if action == "start":
response = start_ec2_instance(challenge)
elif action == "stop":
response = stop_ec2_instance(challenge)
elif action == "restart":
response = restart_ec2_instance(challenge)
if response:
if "error" not in response:
status_code = status.HTTP_200_OK
response_data = {
"message": response["message"],
"action": "Success",
}
else:
status_code = status.HTTP_400_BAD_REQUEST
response_data = {
"message": response["error"],
"action": "Failure",
}
else:
status_code = status.HTTP_500_INTERNAL_SERVER_ERROR
response_data = {
"message": "No Response",
"action": "Failure",
}
return Response(response_data, status=status_code)
@api_view(["POST"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def get_annotation_file_presigned_url(request, challenge_phase_pk):
"""
API to generate a presigned url to upload a test annotation file
Arguments:
request {HttpRequest} -- The request object
challenge_phase_pk {int} -- Challenge phase primary key
Returns:
Response Object -- An object containing the presignd url, or an error message if some failure occurs
"""
if settings.DEBUG:
response_data = {
"error": "Sorry, this feature is not available in development or test environment."
}
return Response(response_data)
# Check if the challenge phase exists or not
challenge_phase = get_challenge_phase_model(challenge_phase_pk)
if not is_user_a_host_of_challenge(
request.user, challenge_phase.challenge.pk
):
response_data = {
"error": "Sorry, you are not authorized for uploading an annotation file."
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
# Set default num of chunks to 1 if num of chunks is not specified
num_file_chunks = 1
if request.data.get("num_file_chunks"):
num_file_chunks = int(request.data["num_file_chunks"])
file_ext = os.path.splitext(request.data["file_name"])[-1]
random_file_name = uuid.uuid4()
if challenge_phase.test_annotation:
file_key_on_s3 = "{}/{}".format(
settings.MEDIAFILES_LOCATION, challenge_phase.test_annotation.name
)
else:
# This file shall be replaced with the one uploaded through the presigned url from the CLI
test_annotation_file = SimpleUploadedFile(
"{}{}".format(random_file_name, file_ext),
b"Dummy file content",
content_type="text/plain",
)
serializer = ChallengePhaseCreateSerializer(
challenge_phase,
data={"test_annotation": test_annotation_file},
context={
"challenge": challenge_phase.challenge,
},
partial=True,
)
if serializer.is_valid():
serializer.save()
else:
response_data = {"error": serializer.errors}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
challenge_phase = serializer.instance
file_key_on_s3 = "{}/{}".format(
settings.MEDIAFILES_LOCATION, challenge_phase.test_annotation.name
)
response = generate_presigned_url_for_multipart_upload(
file_key_on_s3, challenge_phase.challenge.pk, num_file_chunks
)
if response.get("error"):
response_data = response
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
response_data = {
"presigned_urls": response.get("presigned_urls"),
"upload_id": response.get("upload_id"),
}
return Response(response_data, status=status.HTTP_200_OK)
@api_view(["POST"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def finish_annotation_file_upload(request, challenge_phase_pk):
"""
API to complete multipart upload for a test annotation file
Arguments:
request {HttpRequest} -- The request object
challenge_phase_pk {int} -- Challenge phase primary key
Returns:
Response Object -- An object containing the presignd url, or an error message if some failure occurs
"""
if settings.DEBUG:
response_data = {
"error": "Sorry, this feature is not available in development or test environment."
}
return Response(response_data)
# Check if the challenge phase exists or not
challenge_phase = get_challenge_phase_model(challenge_phase_pk)
if not is_user_a_host_of_challenge(
request.user, challenge_phase.challenge.pk
):
response_data = {
"error": "Sorry, you are not authorized for uploading an annotation file."
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
if request.data.get("parts") is None:
response_data = {"error": "Uploaded file parts metadata is missing!"}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
if request.data.get("upload_id") is None:
response_data = {"error": "Uploaded file upload Id is missing!"}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
file_parts = json.loads(request.data["parts"])
upload_id = request.data["upload_id"]
file_key_on_s3 = "{}/{}".format(
settings.MEDIAFILES_LOCATION, challenge_phase.test_annotation.name
)
annotations_uploaded_using_cli = request.data.get(
"annotations_uploaded_using_cli"
)
response = {}
try:
data = complete_s3_multipart_file_upload(
file_parts, upload_id, file_key_on_s3, challenge_phase.challenge.pk
)
if data.get("error"):
response_data = data
response = Response(
response_data, status=status.HTTP_400_BAD_REQUEST
)
else:
response_data = {
"upload_id": upload_id,
"challenge_phase_pk": challenge_phase.pk,
}
response = Response(response_data, status=status.HTTP_201_CREATED)
if annotations_uploaded_using_cli:
serializer = ChallengePhaseCreateSerializer(
challenge_phase,
data={"annotations_uploaded_using_cli": True},
context={
"challenge": challenge_phase.challenge,
},
partial=True,
)
if serializer.is_valid():
serializer.save()
else:
response_data = {"error": serializer.errors}
return Response(
response_data, status=status.HTTP_400_BAD_REQUEST
)
except Exception:
response_data = {
"error": "Error occurred while uploading annotations!"
}
response = Response(response_data, status=status.HTTP_400_BAD_REQUEST)
return response
@api_view(["POST"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def create_or_update_github_challenge(request, challenge_host_team_pk):
try:
challenge_host_team = get_challenge_host_team_model(
challenge_host_team_pk
)
except ChallengeHostTeam.DoesNotExist:
response_data = {"error": "ChallengeHostTeam does not exist"}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
challenge_queryset = Challenge.objects.filter(
github_repository=request.data["GITHUB_REPOSITORY"]
)
if challenge_queryset:
challenge = challenge_queryset[0]
if not is_user_a_host_of_challenge(request.user, challenge.pk):
response_data = {
"error": "Sorry, you are not a host for this challenge. Please check your user access token"
}
return Response(response_data, status=status.HTTP_403_FORBIDDEN)
response_data = {}
BASE_LOCATION = tempfile.mkdtemp()
unique_folder_name = get_unique_alpha_numeric_key(10)
CHALLENGE_ZIP_DOWNLOAD_LOCATION = join(
BASE_LOCATION, "{}.zip".format(unique_folder_name)
)
data = request.data
challenge_config_serializer = ChallengeConfigSerializer(
data=data, context={"request": request}
)
if challenge_config_serializer.is_valid():
uploaded_zip_file = challenge_config_serializer.save()
uploaded_zip_file_path = challenge_config_serializer.data[
"zip_configuration"
]
else:
response_data["error"] = challenge_config_serializer.errors
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
is_success, error_description = download_and_write_file(
uploaded_zip_file_path, True, CHALLENGE_ZIP_DOWNLOAD_LOCATION, "wb"
)
if not is_success:
response_data["error"] = error_description
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
# Extract zip file
try:
zip_ref = extract_zip_file(
CHALLENGE_ZIP_DOWNLOAD_LOCATION,
"r",
join(BASE_LOCATION, unique_folder_name),
)
except zipfile.BadZipfile:
message = "The zip file contents cannot be extracted. Please check the format!"
response_data["error"] = message
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
error_messages, yaml_file_data, files = validate_challenge_config_util(
request,
challenge_host_team,
BASE_LOCATION,
unique_folder_name,
zip_ref,
challenge_queryset[0] if challenge_queryset else None
)
if not len(error_messages):
if not challenge_queryset:
error_messages = None
try:
with transaction.atomic():
serializer = ZipChallengeSerializer(
data=yaml_file_data,
context={
"request": request,
"challenge_host_team": challenge_host_team,
"image": files["challenge_image_file"],
"evaluation_script": files[
"challenge_evaluation_script_file"
],
"github_repository": request.data[
"GITHUB_REPOSITORY"
],
},
)
if serializer.is_valid():
serializer.save()
challenge = serializer.instance
queue_name = get_queue_name(challenge.title, challenge.pk)
challenge.queue = queue_name
challenge.save()
# Add Tags
add_tags_to_challenge(yaml_file_data, challenge)
# Add Domain
verify_complete = add_domain_to_challenge(yaml_file_data, challenge)
if verify_complete is not None:
return Response(verify_complete, status=status.HTTP_400_BAD_REQUEST)
# Add Sponsors
error_messages = add_sponsors_to_challenge(yaml_file_data, challenge)
if error_messages is not None:
return Response(error_messages, status=status.HTTP_400_BAD_REQUEST)
# Add Prizes
error_messages = add_prizes_to_challenge(yaml_file_data, challenge)
if error_messages is not None:
return Response(error_messages, status=status.HTTP_400_BAD_REQUEST)
# Create Leaderboard
yaml_file_data_of_leaderboard = yaml_file_data[
"leaderboard"
]
leaderboard_ids = {}
for data in yaml_file_data_of_leaderboard:
serializer = LeaderboardSerializer(
data=data, context={"config_id": data["id"]}
)
if serializer.is_valid():
serializer.save()
else:
error_messages = f"leaderboard {data['id']} :{str(serializer.errors)}"
raise RuntimeError()
leaderboard_ids[
str(data["id"])
] = serializer.instance.pk
# Create Challenge Phase
challenge_phase_ids = {}
challenge_phases_data = yaml_file_data["challenge_phases"]
for data, challenge_test_annotation_file in zip(
challenge_phases_data,
files["challenge_test_annotation_files"],
):
data["slug"] = "{}-{}-{}".format(
challenge.title.split(" ")[0].lower(),
get_slug(data["codename"]),
challenge.pk,
)[:198]
if challenge_test_annotation_file:
serializer = ChallengePhaseCreateSerializer(
data=data,
context={
"challenge": challenge,
"test_annotation": challenge_test_annotation_file,
"config_id": data["id"],
},
)
else:
# This is when the host wants to upload the annotation file later
serializer = ChallengePhaseCreateSerializer(
data=data,
context={
"challenge": challenge,
"config_id": data["id"],
},
)
if serializer.is_valid():
serializer.save()
else:
error_messages = f"challenge phase {data['id']} :{str(serializer.errors)}"
raise RuntimeError()
challenge_phase_ids[
str(data["id"])
] = serializer.instance.pk
# Create Dataset Splits
yaml_file_data_of_dataset_split = yaml_file_data[
"dataset_splits"
]
dataset_split_ids = {}
for data in yaml_file_data_of_dataset_split:
serializer = DatasetSplitSerializer(
data=data, context={"config_id": data["id"]}
)
if serializer.is_valid():
serializer.save()
else:
error_messages = f"dataset split {data['id']} :{str(serializer.errors)}"
raise RuntimeError()
dataset_split_ids[
str(data["id"])
] = serializer.instance.pk
# Create Challenge Phase Splits
challenge_phase_splits_data = yaml_file_data[
"challenge_phase_splits"
]
for data in challenge_phase_splits_data:
if challenge_phase_ids.get(str(data["challenge_phase_id"])) is None:
message = (
"Challenge phase with phase id {} doesn't exist.".format(data["challenge_phase_id"])
)
response_data = {"error": message}
return Response(response_data, status.HTTP_406_NOT_ACCEPTABLE)
if leaderboard_ids.get(str(data["leaderboard_id"])) is None:
message = (
"Leaderboard with id {} doesn't exist.".format(data["leaderboard_id"])
)
response_data = {"error": message}
return Response(response_data, status.HTTP_406_NOT_ACCEPTABLE)
if dataset_split_ids.get(str(data["dataset_split_id"])) is None:
message = (
"Dataset split with id {} doesn't exist.".format(data["dataset_split_id"])
)
response_data = {"error": message}
return Response(response_data, status.HTTP_406_NOT_ACCEPTABLE)
challenge_phase = challenge_phase_ids[
str(data["challenge_phase_id"])
]
leaderboard = leaderboard_ids[
str(data["leaderboard_id"])
]
dataset_split = dataset_split_ids[
str(data["dataset_split_id"])
]
visibility = data["visibility"]
leaderboard_decimal_precision = data["leaderboard_decimal_precision"]
is_leaderboard_order_descending = data["is_leaderboard_order_descending"]
data = {
"challenge_phase": challenge_phase,
"leaderboard": leaderboard,
"dataset_split": dataset_split,
"visibility": visibility,
"is_leaderboard_order_descending": is_leaderboard_order_descending,
"leaderboard_decimal_precision": leaderboard_decimal_precision
}
serializer = ZipChallengePhaseSplitSerializer(
data=data
)
if serializer.is_valid():
serializer.save()
else:
error_messages = f"challenge phase split (phase:{data['challenge_phase_id']}, leaderboard:{data['leaderboard_id']}, dataset split: {data['dataset_split_id']}):{str(serializer.errors)}"
raise RuntimeError()
zip_config = ChallengeConfiguration.objects.get(
pk=uploaded_zip_file.pk
)
if zip_config:
if not challenge.is_docker_based:
# Add the Challenge Host as a test participant.
emails = (
challenge_host_team.get_all_challenge_host_email()
)
team_name = "Host_{}_Team".format(
random.randint(1, 100000)
)
participant_host_team = ParticipantTeam(
team_name=team_name,
created_by=challenge_host_team.created_by,
)
participant_host_team.save()
for email in emails:
user = User.objects.get(email=email)
host = Participant(
user=user,
status=Participant.ACCEPTED,
team=participant_host_team,
)
host.save()
challenge.participant_teams.add(participant_host_team)
zip_config.challenge = challenge
zip_config.save()
if not settings.DEBUG:
message = {
"text": "A *new challenge* has been created on EvalAI.",
"fields": [
{
"title": "Email",
"value": request.user.email,
"short": False,
},
{
"title": "Challenge title",
"value": challenge.title,
"short": False,
},
],
}
send_slack_notification(message=message)
response_data = {
"Success": "Challenge {} has been created successfully and"
" sent for review to EvalAI Admin.".format(
challenge.title
)
}
return Response(
response_data, status=status.HTTP_201_CREATED
)
except: # noqa: E722
response_data = {
"error": f"Error in creating challenge: {error_messages}. Please check the yaml configuration!"
}
if error_messages:
response_data["error_message"] = json.dumps(error_messages)
return Response(
response_data, status=status.HTTP_400_BAD_REQUEST
)
finally:
try:
shutil.rmtree(BASE_LOCATION)
logger.info("Zip folder is removed")
except: # noqa: E722
logger.exception(
"Zip folder for challenge {} is not removed from {} location".format(
challenge.pk, BASE_LOCATION
)
)
else:
try:
error_messages = None
# Updating ChallengeConfiguration object
challenge_configuration = ChallengeConfiguration.objects.filter(
challenge=challenge.pk
).first()
serializer = ChallengeConfigSerializer(
challenge_configuration,
data=request.data,
context={"request": request},
)
if serializer.is_valid():
serializer.save()
# Updating Challenge object
serializer = ZipChallengeSerializer(
challenge,
data=yaml_file_data,
context={
"request": request,
"challenge_host_team": challenge_host_team,
"image": files["challenge_image_file"],
"evaluation_script": files[
"challenge_evaluation_script_file"
],
},
)
if serializer.is_valid():
serializer.save()
else:
error_messages = f"challenge :{str(serializer.errors)}"
raise RuntimeError()
challenge = serializer.instance
# Add Tags
add_tags_to_challenge(yaml_file_data, challenge)
# Add Domain
verify_complete = add_domain_to_challenge(yaml_file_data, challenge)
if verify_complete is not None:
return Response(verify_complete, status=status.HTTP_400_BAD_REQUEST)
# Add/Update Sponsors
error_messages = add_sponsors_to_challenge(yaml_file_data, challenge)
if error_messages is not None:
return Response(error_messages, status=status.HTTP_400_BAD_REQUEST)
# Add/Update Prizes
error_messages = add_prizes_to_challenge(yaml_file_data, challenge)
if error_messages is not None:
return Response(error_messages, status=status.HTTP_400_BAD_REQUEST)
# Updating Leaderboard object
leaderboard_ids = {}
yaml_file_data_of_leaderboard = yaml_file_data["leaderboard"]
for data in yaml_file_data_of_leaderboard:
challenge_phase_split_qs = ChallengePhaseSplit.objects.filter(
challenge_phase__challenge__pk=challenge.pk,
leaderboard__config_id=data["config_id"],
)
if challenge_phase_split_qs:
challenge_phase_split = challenge_phase_split_qs.first()
leaderboard = challenge_phase_split.leaderboard
serializer = LeaderboardSerializer(
leaderboard,
data=data,
context={"config_id": data["id"]},
)
else:
serializer = LeaderboardSerializer(
data=data, context={"config_id": data["id"]}
)
if serializer.is_valid():
serializer.save()
leaderboard_ids[str(data["id"])] = serializer.instance.pk
else:
error_messages = f"leaderboard update {(data['id'])} :{str(serializer.errors)}"
raise RuntimeError()
# Updating ChallengePhase objects
challenge_phase_ids = {}
challenge_phases_data = yaml_file_data["challenge_phases"]
for data, challenge_test_annotation_file in zip(
challenge_phases_data, files["challenge_test_annotation_files"]
):
# Override the submission_meta_attributes when they are missing
submission_meta_attributes = data.get("submission_meta_attributes")
if submission_meta_attributes is None:
data["submission_meta_attributes"] = None
# Override the default_submission_meta_attributes when they are missing
default_submission_meta_attributes = data.get("default_submission_meta_attributes")
if default_submission_meta_attributes is None:
data["default_submission_meta_attributes"] = None
challenge_phase = ChallengePhase.objects.filter(
challenge__pk=challenge.pk, config_id=data["id"]
).first()
if (
challenge_test_annotation_file
and not challenge_phase.annotations_uploaded_using_cli
):
serializer = ChallengePhaseCreateSerializer(
challenge_phase,
data=data,
context={
"challenge": challenge,
"test_annotation": challenge_test_annotation_file,
"config_id": data["config_id"],
},
)
elif (
challenge_test_annotation_file
and challenge_phase.annotations_uploaded_using_cli
):
data.pop("test_annotation", None)
serializer = ChallengePhaseCreateSerializer(
challenge_phase,
data=data,
context={
"challenge": challenge,
"config_id": data["config_id"],
},
partial=True,
)
else:
serializer = ChallengePhaseCreateSerializer(
challenge_phase,
data=data,
context={
"challenge": challenge,
"config_id": data["config_id"],
},
)
if serializer.is_valid():
serializer.save()
challenge_phase_ids[
str(data["id"])
] = serializer.instance.pk
else:
error_messages = f"challenge phase update {(data['id'])} :{str(serializer.errors)}"
raise RuntimeError()
# Updating DatasetSplit objects
yaml_file_data_of_dataset_split = yaml_file_data["dataset_splits"]
dataset_split_ids = {}
for data in yaml_file_data_of_dataset_split:
challenge_phase_split_qs = ChallengePhaseSplit.objects.filter(
challenge_phase__challenge__pk=challenge.pk,
dataset_split__config_id=data["id"],
)
if challenge_phase_split_qs:
challenge_phase_split = challenge_phase_split_qs.first()
dataset_split = challenge_phase_split.dataset_split
serializer = DatasetSplitSerializer(
dataset_split,
data=data,
context={"config_id": data["id"]},
)
else:
serializer = DatasetSplitSerializer(
data=data, context={"config_id": data["id"]}
)
if serializer.is_valid():
serializer.save()
dataset_split_ids[str(data["id"])] = serializer.instance.pk
else:
error_messages = f"dataset split update {(data['id'])} :{str(serializer.errors)}"
raise RuntimeError()
# Update ChallengePhaseSplit objects
challenge_phase_splits_data = yaml_file_data[
"challenge_phase_splits"
]
for data in challenge_phase_splits_data:
if challenge_phase_ids.get(str(data["challenge_phase_id"])) is None:
message = (
"Challenge phase with phase id {} doesn't exist.".format(data["challenge_phase_id"])
)
response_data = {"error": message}
return Response(response_data, status.HTTP_406_NOT_ACCEPTABLE)
if leaderboard_ids.get(str(data["leaderboard_id"])) is None:
message = (
"Leaderboard with id {} doesn't exist.".format(data["leaderboard_id"])
)
response_data = {"error": message}
return Response(response_data, status.HTTP_406_NOT_ACCEPTABLE)
if dataset_split_ids.get(str(data["dataset_split_id"])) is None:
message = (
"Dataset split with id {} doesn't exist.".format(data["dataset_split_id"])
)
response_data = {"error": message}
return Response(response_data, status.HTTP_406_NOT_ACCEPTABLE)
challenge_phase = challenge_phase_ids[
str(data["challenge_phase_id"])
]
leaderboard = leaderboard_ids[str(data["leaderboard_id"])]
dataset_split = dataset_split_ids[
str(data["dataset_split_id"])
]
visibility = data["visibility"]
leaderboard_decimal_precision = data["leaderboard_decimal_precision"]
is_leaderboard_order_descending = data["is_leaderboard_order_descending"]
data = {
"challenge_phase": challenge_phase,
"leaderboard": leaderboard,
"dataset_split": dataset_split,
"visibility": visibility,
"is_leaderboard_order_descending": is_leaderboard_order_descending,
"leaderboard_decimal_precision": leaderboard_decimal_precision
}
challenge_phase_split_qs = ChallengePhaseSplit.objects.filter(
challenge_phase__pk=challenge_phase,
dataset_split__pk=dataset_split,
leaderboard__pk=leaderboard,
)
if challenge_phase_split_qs:
challenge_phase_split = challenge_phase_split_qs.first()
serializer = ZipChallengePhaseSplitSerializer(
challenge_phase_split, data=data
)
else:
serializer = ZipChallengePhaseSplitSerializer(data=data)
if serializer.is_valid():
serializer.save()
else:
error_messages = f"challenge phase split update (phase:{data['challenge_phase_id']}, leaderboard:{data['leaderboard_id']}, dataset split: {data['dataset_split_id']}):{str(serializer.errors)}"
raise RuntimeError()
response_data = {
"Success": "The challenge {} has been updated successfully".format(
challenge.title
)
}
return Response(response_data, status=status.HTTP_200_OK)
except: # noqa: E722
response_data = {
"error": f"Error in creating challenge: {error_messages}. Please check the yaml configuration!"
}
if error_messages:
response_data["error_message"] = json.dumps(error_messages)
return Response(
response_data, status=status.HTTP_400_BAD_REQUEST
)
else:
shutil.rmtree(BASE_LOCATION)
logger.info("Challenge config validation failed. Zip folder removed")
response_data["error"] = "\n".join(error_messages)
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
@api_view(["GET"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def get_all_challenge_templates(request):
q_params = {"is_active": True}
challenges = ChallengeTemplate.objects.filter(**q_params).order_by("-pk")
serializer = ChallengeTemplateSerializer(
challenges, many=True, context={"request": request}
)
response_data = serializer.data
return Response(response_data, status=status.HTTP_200_OK)
@api_view(["GET"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((ExpiringTokenAuthentication,))
def pwc_task_dataset(request):
if request.user.is_staff:
challenge_mapping = PWCChallengeLeaderboard.objects.filter(
enable_sync=True
)
serializer = PWCChallengeLeaderboardSerializer(
challenge_mapping, many=True, context={"request": request}
)
response_data = serializer.data
return Response(response_data, status=status.HTTP_200_OK)
else:
response_data = {
"error": "You are not authorized to make this request. Please ask EvalAI admin to add you as a staff user for accessing this API."
}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
@swagger_auto_schema(
methods=["get"],
manual_parameters=[
openapi.Parameter(
name="challenge_pk",
in_=openapi.IN_PATH,
type=openapi.TYPE_STRING,
description="Challenge ID",
required=True,
),
openapi.Parameter(
name="phase_pk",
in_=openapi.IN_PATH,
type=openapi.TYPE_STRING,
description="Challenge Phase ID",
required=True,
),
],
operation_id="update_allowed_email_ids",
responses={
status.HTTP_200_OK: openapi.Response(
description="",
schema=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
"allowed_email_ids": openapi.Schema(
type=openapi.TYPE_ARRAY,
description="List of allowed email ids",
items=openapi.Schema(type=openapi.TYPE_STRING),
),
},
),
)
},
)
@swagger_auto_schema(
methods=["delete", "patch"],
manual_parameters=[
openapi.Parameter(
name="challenge_pk",
in_=openapi.IN_PATH,
type=openapi.TYPE_STRING,
description="Challenge ID",
required=True,
),
openapi.Parameter(
name="phase_pk",
in_=openapi.IN_PATH,
type=openapi.TYPE_STRING,
description="Challenge Phase ID",
required=True,
),
],
request_body=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
"allowed_email_ids": openapi.Schema(
type=openapi.TYPE_ARRAY,
description="List of allowed email ids",
items=openapi.Schema(type=openapi.TYPE_STRING),
),
},
),
operation_id="update_allowed_email_ids",
responses={
status.HTTP_200_OK: openapi.Response(
description="",
schema=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
"allowed_email_ids": openapi.Schema(
type=openapi.TYPE_ARRAY,
description="List of allowed email ids",
items=openapi.Schema(type=openapi.TYPE_STRING),
),
},
),
)
},
)
@api_view(["GET", "PATCH", "DELETE"])
@throttle_classes([UserRateThrottle])
@permission_classes(
(
permissions.IsAuthenticated,
HasVerifiedEmail,
IsChallengeCreator,
)
)
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def update_allowed_email_ids(request, challenge_pk, phase_pk):
"""
API to GET/UPDATE/DELETE email ids from challenge phase allowed email ids
Arguments:
challenge_pk {int} -- Challenge primary key
phase_pk {int} -- Challenge phase primary key
Returns:
{dict} -- Response object
"""
challenge = get_challenge_model(challenge_pk)
try:
challenge_phase = ChallengePhase.objects.get(
challenge=challenge, pk=phase_pk
)
except ChallengePhase.DoesNotExist:
response_data = {
"error": "Challenge phase {} does not exist for challenge {}".format(
phase_pk, challenge.pk
)
}
return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
allowed_email_ids = challenge_phase.allowed_email_ids
if allowed_email_ids is None:
allowed_email_ids = []
if request.method != "GET":
if request.data.get("allowed_email_ids") is not None:
if not isinstance(request.data["allowed_email_ids"], list):
response_data = {
"error": "Field allowed_email_ids should be a list."
}
return Response(
response_data, status=status.HTTP_400_BAD_REQUEST
)
else:
response_data = {"error": "Field allowed_email_ids is missing."}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
if request.method == "GET":
serializer = ChallengePhaseCreateSerializer(
challenge_phase, context={"request": request}
)
response_data = {
"allowed_email_ids": serializer.data["allowed_email_ids"],
}
return Response(response_data, status=status.HTTP_200_OK)
elif request.method in ["PATCH"]:
allowed_email_ids.extend(request.data["allowed_email_ids"])
allowed_email_ids = list(set(allowed_email_ids))
serializer = ChallengePhaseCreateSerializer(
challenge_phase,
data={"allowed_email_ids": allowed_email_ids},
context={"challenge": challenge},
partial=True,
)
if serializer.is_valid():
serializer.save()
challenge_phase = get_challenge_phase_model(serializer.instance.pk)
serializer = ChallengePhaseSerializer(challenge_phase)
response_data = response_data = {
"allowed_email_ids": serializer.data["allowed_email_ids"],
}
return Response(response_data, status=status.HTTP_200_OK)
else:
return Response(
serializer.errors, status=status.HTTP_400_BAD_REQUEST
)
elif request.method == "DELETE":
remove_allowed_email_ids = request.data["allowed_email_ids"]
allowed_email_ids = list(
set(allowed_email_ids).difference(set(remove_allowed_email_ids))
)
serializer = ChallengePhaseCreateSerializer(
challenge_phase,
data={"allowed_email_ids": allowed_email_ids},
context={"challenge": challenge},
partial=True,
)
if serializer.is_valid():
serializer.save()
challenge_phase = get_challenge_phase_model(serializer.instance.pk)
serializer = ChallengePhaseSerializer(challenge_phase)
response_data = response_data = {
"allowed_email_ids": serializer.data["allowed_email_ids"],
}
return Response(response_data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@api_view(["GET"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def request_challenge_approval_by_pk(request, challenge_pk):
"""
Checks if all challenge phases have finished submissions for the given challenge
and send approval request for the challenge
"""
challenge = get_challenge_model(challenge_pk)
challenge_phases = ChallengePhase.objects.filter(challenge=challenge)
unfinished_phases = []
for challenge_phase in challenge_phases:
submissions = Submission.objects.filter(
challenge_phase=challenge_phase,
status="finished"
)
if not submissions.exists():
unfinished_phases.append(challenge_phase.name)
if unfinished_phases:
error_message = f"The following challenge phases do not have finished submissions: {', '.join(unfinished_phases)}"
return Response({"error": error_message}, status=status.HTTP_406_NOT_ACCEPTABLE)
if not settings.DEBUG:
try:
evalai_api_server = settings.EVALAI_API_SERVER
approval_webhook_url = settings.APPROVAL_WEBHOOK_URL
if not evalai_api_server:
raise ValueError("EVALAI_API_SERVER environment variable is missing.")
if not approval_webhook_url:
raise ValueError("APPROVAL_WEBHOOK_URL environment variable is missing.")
except: # noqa: E722
error_message = "Sorry, there was an error fetching required data for approval requests."
return Response({"error": error_message}, status=status.HTTP_406_NOT_ACCEPTABLE)
message = {
"text": f"Challenge {challenge_pk} has finished submissions and has requested for approval!",
"fields": [
{
"title": "Admin URL",
"value": f"{evalai_api_server}/api/admin/challenges/challenge/{challenge_pk}",
"short": False,
},
{
"title": "Challenge title",
"value": challenge.title,
"short": False,
},
],
}
webhook_response = send_slack_notification(webhook=approval_webhook_url, message=message)
if webhook_response:
if webhook_response.content.decode('utf-8') == "ok":
response_data = {
"message": "Approval request sent!",
}
return Response(response_data, status=status.HTTP_200_OK)
else:
error_message = f"Sorry, there was an error sending approval request: {str(webhook_response.content.decode('utf-8'))}. Please try again."
return Response({"error": error_message}, status=status.HTTP_406_NOT_ACCEPTABLE)
else:
error_message = "Sorry, there was an error sending approval request: No response received. Please try again."
return Response({"error": error_message}, status=status.HTTP_406_NOT_ACCEPTABLE)
else:
error_message = "Please approve the challenge using admin for local deployments."
return Response({"error": error_message}, status=status.HTTP_406_NOT_ACCEPTABLE)
@api_view(["GET"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def get_leaderboard_data(request, challenge_phase_split_pk):
"""
API to get leaderboard data for a challenge phase split
Arguments:
challenge_phase_split {int} -- Challenge phase split primary key
Returns:
{dict} -- Response object
"""
if not is_user_a_staff(request.user):
response_data = {
"error": "Sorry, you are not authorized to access this resource!"
}
return Response(response_data, status=status.HTTP_401_UNAUTHORIZED)
try:
challenge_phase_split = get_challenge_phase_split_model(challenge_phase_split_pk)
leaderboard_data = LeaderboardData.objects.filter(challenge_phase_split=challenge_phase_split, is_disabled=False)
except LeaderboardData.DoesNotExist:
response_data = {
"error": "Leaderboard data not found!"
}
return Response(response_data, status=status.HTTP_404_NOT_FOUND)
serializer = LeaderboardDataSerializer(leaderboard_data, context={"request": request}, many=True)
response_data = serializer.data
return Response(response_data, status=status.HTTP_200_OK)
@api_view(["POST"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def update_challenge_approval(request):
"""
API to update challenge
Arguments:
request {dict} -- Request object
Query Parameters:
challenge_pk {int} -- Challenge primary key
approved_by_admin {bool} -- Challenge approved by admin
"""
if not is_user_a_staff(request.user):
response_data = {
"error": "Sorry, you are not authorized to access this resource!"
}
return Response(response_data, status=status.HTTP_401_UNAUTHORIZED)
challenge_pk = request.data.get("challenge_pk")
approved_by_admin = request.data.get("approved_by_admin")
if not challenge_pk:
response_data = {
"error": "Challenge primary key is missing!"
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
if not approved_by_admin:
response_data = {
"error": "approved_by_admin is missing!"
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
try:
challenge = get_challenge_model(challenge_pk)
except Challenge.DoesNotExist:
response_data = {
"error": "Challenge not found!"
}
return Response(response_data, status=status.HTTP_404_NOT_FOUND)
challenge.approved_by_admin = approved_by_admin
try:
challenge.save()
except Exception as e: # noqa: E722
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
response_data = {
"message": "Challenge updated successfully!"
}
return Response(response_data, status=status.HTTP_200_OK)
@api_view(["POST"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def update_challenge_attributes(request):
"""
API to update attributes of the Challenge model
Arguments:
request {dict} -- Request object
Query Parameters:
challenge_pk {int} -- Challenge primary key
**kwargs {any} -- Key-value pairs representing attributes and their new values
"""
if not request.user.is_staff:
response_data = {
"error": "Sorry, you are not authorized to access this resource!"
}
return Response(response_data, status=status.HTTP_401_UNAUTHORIZED)
challenge_pk = request.data.get("challenge_pk")
if not challenge_pk:
response_data = {
"error": "Challenge primary key is missing!"
}
return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
try:
challenge = Challenge.objects.get(pk=challenge_pk)
except Challenge.DoesNotExist:
response_data = {
"error": f"Challenge with primary key {challenge_pk} not found!"
}
return Response(response_data, status=status.HTTP_404_NOT_FOUND)
# Update attributes based on the request data
for key, value in request.data.items():
if key != "challenge_pk" and hasattr(challenge, key):
setattr(challenge, key, value)
try:
challenge.save()
except Exception as e:
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
response_data = {
"message": f"Challenge attributes updated successfully for challenge with primary key {challenge_pk}!"
}
return Response(response_data, status=status.HTTP_200_OK)
@api_view(["PUT"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def modify_leaderboard_data(request):
"""
API to update leaderboard data
Arguments:
request {dict} -- Request object
Query Parameters:
leaderboard_data {list} -- List of leaderboard data
challenge_phase_split {int} -- Challenge phase split primary key
submission {int} -- Submission primary key
leaderboard {int} -- Leaderboard primary key
is_disabled {int} -- Leaderboard data is disabled
"""
if not is_user_a_staff(request.user):
response_data = {
"error": "Sorry, you are not authorized to access this resource!"
}
return Response(response_data, status=status.HTTP_401_UNAUTHORIZED)
if request.method == "PUT":
leaderboard_data_pk = request.data.get("leaderboard_data")
leaderboard_pk = request.data.get("leaderboard")
challenge_phase_split_pk = request.data.get("challenge_phase_split")
submission_pk = request.data.get("submission")
is_disabled = request.data.get("is_disabled")
# Perform lookups and handle errors
try:
if leaderboard_data_pk:
leaderboard_data = LeaderboardData.objects.get(pk=leaderboard_data_pk)
else:
submission = get_submission_model(submission_pk)
challenge_phase_split = get_challenge_phase_split_model(challenge_phase_split_pk)
leaderboard = get_leaderboard_model(leaderboard_pk)
leaderboard_data = LeaderboardData.objects.get(
submission=submission,
challenge_phase_split=challenge_phase_split,
leaderboard=leaderboard
)
except Exception:
response_data = {
"error": "Resource not found!"
}
return Response(response_data, status=status.HTTP_404_NOT_FOUND)
# Update the 'is_disabled' attribute
leaderboard_data.is_disabled = bool(int(is_disabled))
leaderboard_data.save()
# Serialize and return the updated data
response_data = {
"message": "Leaderboard data updated successfully!"
}
return Response(response_data, status=status.HTTP_200_OK)