Skip to content

Commit

Permalink
[Backend] Add API to access and delete leaderboard data for admins (#…
Browse files Browse the repository at this point in the history
…4086)

* Add API to access leaderboard data details

* add authentication verify

* Added fix and test cases

* changed to get all leaderboard data

* Fix build fail

* Fix test case

* remove unnecessary line change

---------

Co-authored-by: Gunjan Chhablani <chhablani.gunjan@gmail.com>
  • Loading branch information
Suryansh5545 and gchhablani authored Aug 10, 2023
1 parent 5b4c44d commit ffb39ce
Show file tree
Hide file tree
Showing 4 changed files with 228 additions and 1 deletion.
29 changes: 29 additions & 0 deletions apps/challenges/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
ChallengeTemplate,
DatasetSplit,
Leaderboard,
LeaderboardData,
PWCChallengeLeaderboard,
StarChallenge,
UserInvitation,
Expand Down Expand Up @@ -526,3 +527,31 @@ def get_leaderboard(self, obj):
# PWC requires the default sorted by metric at the index "0" of the array
labels.insert(0, labels.pop(default_order_by_index))
return labels


class LeaderboardDataSerializer(serializers.ModelSerializer):
"""
Serializer to store the leaderboard data
"""

def __init__(self, *args, **kwargs):
super(LeaderboardDataSerializer, self).__init__(*args, **kwargs)
context = kwargs.get("context")
if context:
challenge_phase_split = context.get("challenge_phase_split")
if challenge_phase_split:
kwargs["data"]["challenge_phase_split"] = challenge_phase_split.pk
submission = context.get("submission")
if submission:
kwargs["data"]["submission"] = submission.pk

class Meta:
model = LeaderboardData
fields = (
"id",
"challenge_phase_split",
"submission",
"leaderboard",
"result",
"error",
)
10 changes: 10 additions & 0 deletions apps/challenges/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,16 @@
views.get_domain_choices,
name="get_domain_choices",
),
url(
r"^challenge/get_leaderboard_data/$",
views.get_leaderboard_data,
name="get_leaderboard_data",
),
url(
r"^challenge/delete_leaderboard_data/(?P<leaderboard_data_pk>[0-9]+)/$",
views.delete_leaderboard_data,
name="delete_leaderboard_data",
),
]

app_name = "challenges"
63 changes: 63 additions & 0 deletions apps/challenges/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@
ChallengePhaseSplit,
ChallengeTemplate,
ChallengeConfiguration,
LeaderboardData,
PWCChallengeLeaderboard,
StarChallenge,
UserInvitation,
Expand All @@ -126,6 +127,7 @@
UserInvitationSerializer,
ZipChallengeSerializer,
ZipChallengePhaseSplitSerializer,
LeaderboardDataSerializer
)

from .aws_utils import (
Expand All @@ -145,6 +147,7 @@
send_emails,
)


logger = logging.getLogger(__name__)

try:
Expand Down Expand Up @@ -4365,3 +4368,63 @@ def request_challenge_approval_by_pk(request, challenge_pk):
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):
"""
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:
leaderboard_data = LeaderboardData.objects.all()
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(["DELETE"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
@authentication_classes((JWTAuthentication, ExpiringTokenAuthentication))
def delete_leaderboard_data(request, leaderboard_data_pk):
"""
API to delete leaderboard data
Arguments:
leaderboard_data_pk {int} -- Leaderboard data 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:
leaderboard_data = LeaderboardData.objects.get(pk=leaderboard_data_pk)
except LeaderboardData.DoesNotExist:
response_data = {
"error": "Leaderboard data not found!"
}
return Response(response_data, status=status.HTTP_404_NOT_FOUND)
leaderboard_data.delete()
response_data = {
"message": "Leaderboard data deleted successfully!"
}
return Response(response_data, status=status.HTTP_204_NO_CONTENT)
127 changes: 126 additions & 1 deletion tests/unit/challenges/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
from allauth.account.models import EmailAddress
from challenges.models import (Challenge, ChallengeConfiguration,
ChallengePhase, ChallengePhaseSplit,
DatasetSplit, Leaderboard, StarChallenge)
DatasetSplit, Leaderboard, StarChallenge,
LeaderboardData)
from django.conf import settings
from django.contrib.auth.models import User
from django.core.files.uploadedfile import SimpleUploadedFile
Expand Down Expand Up @@ -5684,3 +5685,127 @@ def test_validate_challenge_using_failure(self):

self.assertEqual(response.status_code, 400)
self.assertEqual(response.json(), expected)


class TestLeaderboardData(BaseAPITestClass):
def setUp(self):
super(TestLeaderboardData, self).setUp()
self.challenge_phase = ChallengePhase.objects.create(
name="Challenge Phase",
description="Description for Challenge Phase",
leaderboard_public=False,
is_public=True,
start_date=timezone.now() - timedelta(days=2),
end_date=timezone.now() + timedelta(days=1),
challenge=self.challenge,
test_annotation=SimpleUploadedFile(
"test_sample_file.txt",
b"Dummy file content",
content_type="text/plain",
),
)

self.leaderboard = Leaderboard.objects.create(
schema=json.dumps(
{
"labels": ["yes/no", "number", "others", "overall"],
"default_order_by": "overall",
}
)
)

self.submission = Submission.objects.create(
participant_team=self.participant_team,
challenge_phase=self.challenge_phase,
created_by=self.challenge_host_team.created_by,
status="submitted",
input_file=SimpleUploadedFile(
"test_sample_file.txt",
b"Dummy file content",
content_type="text/plain",
),
method_name="Test Method 1",
method_description="Test Description 1",
project_url="http://testserver1/",
publication_url="http://testserver1/",
is_public=True,
is_flagged=True,
)

self.dataset_split = DatasetSplit.objects.create(
name="Test Dataset Split", codename="test-split"
)

self.challenge_phase_split = ChallengePhaseSplit.objects.create(
dataset_split=self.dataset_split,
challenge_phase=self.challenge_phase,
leaderboard=self.leaderboard,
visibility=ChallengePhaseSplit.PUBLIC,
leaderboard_decimal_precision=2,
is_leaderboard_order_descending=True,
show_leaderboard_by_latest_submission=False,
)

self.leaderboard_data = LeaderboardData.objects.create(
challenge_phase_split=self.challenge_phase_split,
submission=self.submission,
leaderboard=self.leaderboard,
result=[0.5, 0.6, 0.7, 0.8],
error="",
)
self.user.is_staff = True
self.user.save()

def test_get_leaderboard_data_success(self):
self.url = reverse_lazy(
"challenges:get_leaderboard_data",
)
response = self.client.get(self.url)
self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_get_leaderboard_data_when_not_staff(self):
self.url = reverse_lazy(
"challenges:get_leaderboard_data",
)
self.user.is_staff = False
self.user.save()
expected = {
"error": "Sorry, you are not authorized to access this resource!"
}
response = self.client.get(self.url)
self.assertEqual(response.data, expected)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

def test_delete_leaderboard_data_success(self):
self.url = reverse_lazy(
"challenges:delete_leaderboard_data",
kwargs={"leaderboard_data_pk": self.leaderboard_data.pk},
)
response = self.client.delete(self.url)
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)

def test_delete_leaderboard_data_when_leaderboard_data_does_not_exist(self):
self.url = reverse_lazy(
"challenges:delete_leaderboard_data",
kwargs={"leaderboard_data_pk": self.leaderboard_data.pk + 1000},
)
expected = {
"error": "Leaderboard data not found!"
}
response = self.client.delete(self.url)
self.assertEqual(response.data, expected)
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

def test_delete_leaderboard_data_when_not_staff(self):
self.url = reverse_lazy(
"challenges:delete_leaderboard_data",
kwargs={"leaderboard_data_pk": self.leaderboard_data.pk},
)
self.user.is_staff = False
self.user.save()
expected = {
"error": "Sorry, you are not authorized to access this resource!"
}
response = self.client.delete(self.url)
self.assertEqual(response.data, expected)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)

0 comments on commit ffb39ce

Please sign in to comment.