From ae017b46fbb406dadfd2bb44da025336022d9e60 Mon Sep 17 00:00:00 2001 From: Suryansh Pathak <34577232+Suryansh5545@users.noreply.github.com> Date: Wed, 25 Oct 2023 02:14:28 +0530 Subject: [PATCH] [Backend] Allow disabling leaderboard data (#4180) * Add is_active and endpoint to change it leaderboarddata * change is_active to is_disabled * fix comments * fix is_disabled * change is_disabled to false * fix test * fix migration and remove get_object * fix migration * Update views.py * Delete apps/challenges/migrations/0107_challenge_worker_image_url_alter.py * Add missing migration * Add new migration --------- Co-authored-by: Gunjan Chhablani --- apps/challenges/admin.py | 1 + .../0107_leaderboarddata_is_disabled.py | 18 ++++++ apps/challenges/models.py | 1 + apps/challenges/urls.py | 5 ++ apps/challenges/views.py | 63 ++++++++++++++++++- apps/jobs/utils.py | 3 +- apps/jobs/views.py | 2 +- tests/unit/challenges/test_views.py | 38 +++++++++++ 8 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 apps/challenges/migrations/0107_leaderboarddata_is_disabled.py diff --git a/apps/challenges/admin.py b/apps/challenges/admin.py index adf9e0a3b1..f056d2c679 100644 --- a/apps/challenges/admin.py +++ b/apps/challenges/admin.py @@ -329,6 +329,7 @@ class LeaderboardDataAdmin(ImportExportTimeStampedAdmin): "challenge_phase_split", "submission", "leaderboard", + "is_disabled", "result", ) list_filter = ("challenge_phase_split", "created_at", "modified_at") diff --git a/apps/challenges/migrations/0107_leaderboarddata_is_disabled.py b/apps/challenges/migrations/0107_leaderboarddata_is_disabled.py new file mode 100644 index 0000000000..96292bea8a --- /dev/null +++ b/apps/challenges/migrations/0107_leaderboarddata_is_disabled.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.20 on 2023-10-24 20:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('challenges', '0106_challenge_worker_image_url_alter'), + ] + + operations = [ + migrations.AddField( + model_name='leaderboarddata', + name='is_disabled', + field=models.BooleanField(default=False), + ), + ] diff --git a/apps/challenges/models.py b/apps/challenges/models.py index 692c83a817..3199ff66b2 100644 --- a/apps/challenges/models.py +++ b/apps/challenges/models.py @@ -512,6 +512,7 @@ class LeaderboardData(TimeStampedModel): submission = models.ForeignKey("jobs.Submission", on_delete=models.CASCADE) leaderboard = models.ForeignKey("Leaderboard", on_delete=models.CASCADE) result = JSONField() + is_disabled = models.BooleanField(default=False) error = JSONField(null=True, blank=True) def __str__(self): diff --git a/apps/challenges/urls.py b/apps/challenges/urls.py index 00a3829681..bf518cbde8 100644 --- a/apps/challenges/urls.py +++ b/apps/challenges/urls.py @@ -299,6 +299,11 @@ views.get_sponsors_by_challenge, name="get_sponsors_by_challenge", ), + url( + r"^challenge/update_leaderboard_data/$", + views.update_leaderboard_data, + name="update_leaderboard_data", + ) ] app_name = "challenges" diff --git a/apps/challenges/views.py b/apps/challenges/views.py index 2e6f77b225..c1a580a09f 100644 --- a/apps/challenges/views.py +++ b/apps/challenges/views.py @@ -160,6 +160,8 @@ send_emails, ) +from jobs.utils import get_submission_model + logger = logging.getLogger(__name__) try: @@ -4704,7 +4706,7 @@ def get_leaderboard_data(request, challenge_phase_split_pk): 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) + leaderboard_data = LeaderboardData.objects.filter(challenge_phase_split=challenge_phase_split, is_disabled=False) except LeaderboardData.DoesNotExist: response_data = { "error": "Leaderboard data not found!" @@ -4763,3 +4765,62 @@ def update_challenge_approval(request): "message": "Challenge updated successfully!" } 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 update_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) diff --git a/apps/jobs/utils.py b/apps/jobs/utils.py index 9f3ffcc214..159aaacebb 100644 --- a/apps/jobs/utils.py +++ b/apps/jobs/utils.py @@ -357,7 +357,7 @@ def calculate_distinct_sorted_leaderboard_data( leaderboard_data = LeaderboardData.objects.exclude( Q(submission__created_by__email__in=challenge_hosts_emails) & Q(submission__is_baseline=False) - ) + ).filter(is_disabled=False) # Get all the successful submissions related to the challenge phase split all_valid_submission_status = [Submission.FINISHED] @@ -519,6 +519,7 @@ def get_leaderboard_data_model(submission_pk, challenge_phase_split_pk): leaderboard_data = LeaderboardData.objects.get( submission=submission_pk, challenge_phase_split__pk=challenge_phase_split_pk, + is_disabled=False, ) return leaderboard_data diff --git a/apps/jobs/views.py b/apps/jobs/views.py index 83e6432230..87e4cb7925 100644 --- a/apps/jobs/views.py +++ b/apps/jobs/views.py @@ -2265,7 +2265,7 @@ def update_leaderboard_data(request, leaderboard_data_pk): """ try: - leaderboard_data = LeaderboardData.objects.get(pk=leaderboard_data_pk) + leaderboard_data = LeaderboardData.objects.get(pk=leaderboard_data_pk, is_disabled=False) except LeaderboardData.DoesNotExist: response_data = {"error": "Leaderboard data does not exist"} return Response(response_data, status=status.HTTP_404_NOT_FOUND) diff --git a/tests/unit/challenges/test_views.py b/tests/unit/challenges/test_views.py index 58d94c71ac..decef9627d 100644 --- a/tests/unit/challenges/test_views.py +++ b/tests/unit/challenges/test_views.py @@ -6073,6 +6073,44 @@ def test_get_leaderboard_data_when_not_staff(self): self.assertEqual(response.data, expected) self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + def test_update_leaderboard_data_when_not_staff(self): + self.url = reverse_lazy("challenges:update_leaderboard_data") + self.user.is_staff = False + self.user.save() + expected = { + "error": "Sorry, you are not authorized to access this resource!" + } + response = self.client.put(self.url) + self.assertEqual(response.data, expected) + self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED) + + def test_update_leaderboard_data_success(self): + self.url = reverse_lazy( + "challenges:update_leaderboard_data") + data = {"leaderboard_data": self.leaderboard_data.pk, + "is_disabled": 0 + } + expected = { + "message": "Leaderboard data updated successfully!" + } + response = self.client.put(self.url, data) + self.assertEqual(response.data, expected) + self.assertEqual(response.status_code, status.HTTP_200_OK) + + def test_update_leaderboard_data_with_other_parameters(self): + self.url = reverse_lazy("challenges:update_leaderboard_data") + data = {"leaderboard": self.leaderboard.pk, + "challenge_phase_split": self.challenge_phase_split.pk, + "submission": self.submission.pk, + "is_disabled": 0 + } + expected = { + "message": "Leaderboard data updated successfully!" + } + response = self.client.put(self.url, data) + self.assertEqual(response.data, expected) + self.assertEqual(response.status_code, status.HTTP_200_OK) + class TestUpdateChallenge(BaseAPITestClass): def setUp(self):