From 5cf34fece5e4f4c812a9cc4460ffc034060f02b9 Mon Sep 17 00:00:00 2001 From: Shivam Singh Date: Tue, 7 Jul 2026 12:15:44 +0530 Subject: [PATCH 1/5] Add date validation constraints for challenge and challenge phase models - Add ValidationError import for model-level validation - Override Challenge.save() to enforce start_date < end_date - Override ChallengePhase.save() with 5 rules: - Phase start >= challenge start - Phase start < challenge end - Phase end > challenge start - Phase end <= challenge end - Phase start < phase end - Add 9 tests for Challenge and ChallengePhase date validation - Fix serializer test setUp to use stable challenge dates --- apps/challenges/models.py | 54 +++++++++++++ tests/unit/challenges/test_models.py | 96 ++++++++++++++++++++++- tests/unit/challenges/test_serializers.py | 12 ++- 3 files changed, 155 insertions(+), 7 deletions(-) diff --git a/apps/challenges/models.py b/apps/challenges/models.py index 1b0cb0656a..097b78dcd7 100644 --- a/apps/challenges/models.py +++ b/apps/challenges/models.py @@ -5,6 +5,7 @@ from django.contrib.auth.models import User from django.contrib.postgres.fields import ArrayField, JSONField from django.core import serializers +from django.core.exceptions import ValidationError from django.db import models from django.db.models import signals from django.db.models.signals import pre_save @@ -358,6 +359,17 @@ def is_active(self): return True return False + def save(self, *args, **kwargs): + if ( + self.start_date + and self.end_date + and self.start_date >= self.end_date + ): + raise ValidationError( + "Challenge start date must be before end date." + ) + super(Challenge, self).save(*args, **kwargs) + @receiver(signals.post_save, sender="challenges.Challenge") def create_eks_cluster_or_ec2_for_challenge( @@ -591,6 +603,48 @@ def is_active(self): return False def save(self, *args, **kwargs): + challenge = self.challenge + if challenge: + if ( + self.start_date + and challenge.start_date + and self.start_date < challenge.start_date + ): + raise ValidationError( + "Phase start date must be on or after challenge start date." + ) + if ( + self.start_date + and challenge.end_date + and self.start_date >= challenge.end_date + ): + raise ValidationError( + "Phase start date must be before challenge end date." + ) + if ( + self.end_date + and challenge.start_date + and self.end_date <= challenge.start_date + ): + raise ValidationError( + "Phase end date must be after challenge start date." + ) + if ( + self.end_date + and challenge.end_date + and self.end_date > challenge.end_date + ): + raise ValidationError( + "Phase end date must be on or before challenge end date." + ) + if ( + self.start_date + and self.end_date + and self.start_date >= self.end_date + ): + raise ValidationError( + "Phase start date must be before end date." + ) # If the max_submissions_per_day is less than the # max_concurrent_submissions_allowed. diff --git a/tests/unit/challenges/test_models.py b/tests/unit/challenges/test_models.py index 0188e851b4..5aebb97403 100644 --- a/tests/unit/challenges/test_models.py +++ b/tests/unit/challenges/test_models.py @@ -13,6 +13,7 @@ LeaderboardData, ) from django.contrib.auth.models import User +from django.core.exceptions import ValidationError from django.core.files.uploadedfile import SimpleUploadedFile from django.test import TestCase from django.utils import timezone @@ -34,6 +35,8 @@ def setUp(self): team_name="Test Challenge Host Team", created_by=self.user ) + now = timezone.now() + self.challenge = Challenge.objects.create( title="Test Challenge", description="Description for test challenge", @@ -42,8 +45,8 @@ def setUp(self): creator=self.challenge_host_team, published=False, enable_forum=True, - start_date=timezone.now() - timedelta(days=2), - end_date=timezone.now() + timedelta(days=1), + start_date=now - timedelta(days=2), + end_date=now + timedelta(days=1), anonymous_leaderboard=False, ) @@ -58,8 +61,8 @@ def setUp(self): description="Description for Challenge Phase", leaderboard_public=False, is_public=False, - start_date=timezone.now() - timedelta(days=2), - end_date=timezone.now() + timedelta(days=1), + start_date=now - timedelta(days=1), + end_date=now + timedelta(hours=1), challenge=self.challenge, test_annotation=SimpleUploadedFile( "test_sample_file.txt", @@ -186,6 +189,7 @@ def test_end_date_change_triggers_eks_autoscale_for_docker_challenge( self.challenge.save() mock_trigger_eks_node_autoscale.reset_mock() + self.challenge.start_date = timezone.now() - timedelta(days=5) self.challenge.end_date = timezone.now() - timedelta(days=3) self.challenge.save() @@ -194,6 +198,26 @@ def test_end_date_change_triggers_eks_autoscale_for_docker_challenge( trigger_source="challenge_end_date_changed", ) + def test_save_raises_error_when_start_date_after_end_date(self): + self.challenge.start_date = timezone.now() + timedelta(days=1) + self.challenge.end_date = timezone.now() - timedelta(days=1) + with self.assertRaises(ValidationError): + self.challenge.save() + + def test_save_raises_error_when_start_date_equals_end_date(self): + now = timezone.now() + self.challenge.start_date = now + self.challenge.end_date = now + with self.assertRaises(ValidationError): + self.challenge.save() + + def test_save_succeeds_when_dates_are_valid(self): + self.challenge.start_date = timezone.now() - timedelta(days=2) + self.challenge.end_date = timezone.now() + timedelta(days=2) + self.challenge.save() + self.challenge.refresh_from_db() + self.assertTrue(self.challenge.is_active) + class DatasetSplitTestCase(BaseTestCase): def setUp(self): @@ -270,6 +294,70 @@ def test_submission_artifact_retention_policy_includes_supported_windows( retention_policies, ) + def test_save_raises_error_when_phase_start_before_challenge_start(self): + self.challenge_phase.start_date = self.challenge.start_date - timedelta( + days=1 + ) + with self.assertRaises(ValidationError): + self.challenge_phase.save() + + def test_save_raises_error_when_phase_start_on_or_after_challenge_end(self): + self.challenge_phase.start_date = self.challenge.end_date + with self.assertRaises(ValidationError): + self.challenge_phase.save() + + def test_save_raises_error_when_phase_end_on_or_before_challenge_start(self): + self.challenge_phase.end_date = self.challenge.start_date + with self.assertRaises(ValidationError): + self.challenge_phase.save() + + def test_save_raises_error_when_phase_end_after_challenge_end(self): + self.challenge_phase.end_date = self.challenge.end_date + timedelta(days=1) + with self.assertRaises(ValidationError): + self.challenge_phase.save() + + def test_save_raises_error_when_phase_start_after_phase_end(self): + now = timezone.now() + self.challenge_phase.start_date = now + timedelta(days=1) + self.challenge_phase.end_date = now + with self.assertRaises(ValidationError): + self.challenge_phase.save() + + def test_save_succeeds_when_phase_dates_are_within_challenge_dates(self): + self.challenge_phase.start_date = self.challenge.start_date + timedelta( + hours=1 + ) + self.challenge_phase.end_date = self.challenge.end_date - timedelta(hours=1) + self.challenge_phase.save() + self.challenge_phase.refresh_from_db() + self.assertEqual( + self.challenge_phase.start_date, + self.challenge.start_date + timedelta(hours=1), + ) + self.assertEqual( + self.challenge_phase.end_date, + self.challenge.end_date - timedelta(hours=1), + ) + + def test_save_succeeds_when_challenge_dates_are_null(self): + self.challenge.start_date = None + self.challenge.end_date = None + self.challenge.save() + now = timezone.now() + self.challenge_phase.start_date = now - timedelta(days=1) + self.challenge_phase.end_date = now + timedelta(days=1) + self.challenge_phase.save() + self.challenge_phase.refresh_from_db() + self.assertTrue(self.challenge_phase.is_active) + + def test_save_succeeds_when_phase_dates_are_null(self): + self.challenge_phase.start_date = None + self.challenge_phase.end_date = None + self.challenge_phase.save() + self.challenge_phase.refresh_from_db() + self.assertIsNone(self.challenge_phase.start_date) + self.assertIsNone(self.challenge_phase.end_date) + class LeaderboardTestCase(BaseTestCase): def setUp(self): diff --git a/tests/unit/challenges/test_serializers.py b/tests/unit/challenges/test_serializers.py index 0b30b747ce..2251de61ae 100644 --- a/tests/unit/challenges/test_serializers.py +++ b/tests/unit/challenges/test_serializers.py @@ -192,7 +192,7 @@ def setUp(self): leaderboard_public=False, is_public=False, start_date=timezone.now() - timedelta(days=2), - end_date=timezone.now() + timedelta(days=1), + end_date=self.challenge.end_date, challenge=self.challenge, test_annotation=SimpleUploadedFile( "test_sample_file.txt", @@ -331,8 +331,8 @@ def test_internal_challenge_defaults_created_phase_to_days_14(self): "description": "Internal phase", "leaderboard_public": False, "is_public": False, - "start_date": timezone.now() - timedelta(days=1), - "end_date": timezone.now() + timedelta(days=1), + "start_date": self.challenge.start_date, + "end_date": self.challenge.end_date, "max_submissions_per_day": 100000, "max_submissions": 100000, "max_submissions_per_month": 100000, @@ -344,6 +344,12 @@ def test_internal_challenge_defaults_created_phase_to_days_14(self): self.assertTrue(serializer.is_valid(), serializer.errors) challenge_phase = serializer.save() + self.assertLessEqual( + challenge_phase.start_date, self.challenge.end_date + ) + self.assertLessEqual( + challenge_phase.end_date, self.challenge.end_date + ) self.assertEqual( ChallengePhase.DAYS_14, challenge_phase.submission_artifact_retention_policy, From a815981dd62876d31c3a252d26a02d4ab6bcc2c5 Mon Sep 17 00:00:00 2001 From: Shivam Singh Date: Tue, 7 Jul 2026 13:15:59 +0530 Subject: [PATCH 2/5] review: fix is_active null guard, add serializer validation, fix phase ordering test - Guard Challenge.is_active and ChallengePhase.is_active against null dates (avoids TypeError when start_date or end_date is None) - Add validate() to ChallengeSerializer, ChallengePhaseSerializer, and ChallengePhaseCreateSerializer to catch invalid date ranges at the serializer boundary (avoids 500 from model ValidationError) - Fix test_save_raises_error_when_phase_start_after_phase_end to keep test dates within challenge bounds so it exercises the internal ordering check instead of short-circuiting on the boundary check --- apps/challenges/models.py | 14 ++++++- apps/challenges/serializers.py | 63 ++++++++++++++++++++++++++++ tests/unit/challenges/test_models.py | 7 ++-- 3 files changed, 79 insertions(+), 5 deletions(-) diff --git a/apps/challenges/models.py b/apps/challenges/models.py index 097b78dcd7..b7b6228cf9 100644 --- a/apps/challenges/models.py +++ b/apps/challenges/models.py @@ -355,7 +355,12 @@ def get_end_date(self): @property def is_active(self): """Returns if the challenge is active or not""" - if self.start_date < timezone.now() and self.end_date > timezone.now(): + if ( + self.start_date + and self.end_date + and self.start_date < timezone.now() + and self.end_date > timezone.now() + ): return True return False @@ -598,7 +603,12 @@ def get_end_date(self): @property def is_active(self): """Returns if the challenge is active or not""" - if self.start_date < timezone.now() and self.end_date > timezone.now(): + if ( + self.start_date + and self.end_date + and self.start_date < timezone.now() + and self.end_date > timezone.now() + ): return True return False diff --git a/apps/challenges/serializers.py b/apps/challenges/serializers.py index 90fbf3bcba..febaf43d36 100644 --- a/apps/challenges/serializers.py +++ b/apps/challenges/serializers.py @@ -36,6 +36,15 @@ class ChallengeSerializer(serializers.ModelSerializer): def get_domain_name(self, obj): return obj.get_domain_display() + def validate(self, attrs): + start_date = attrs.get("start_date") + end_date = attrs.get("end_date") + if start_date and end_date and start_date >= end_date: + raise serializers.ValidationError( + "Challenge start date must be before end date." + ) + return attrs + def validate_worker_python_version(self, value): if value in (None, ""): return DEFAULT_WORKER_PYTHON_VERSION @@ -147,6 +156,33 @@ class ChallengePhaseSerializer(serializers.ModelSerializer): is_active = serializers.ReadOnlyField() + def validate(self, attrs): + start_date = attrs.get("start_date") + end_date = attrs.get("end_date") + challenge = attrs.get("challenge") + if challenge: + if start_date and challenge.start_date and start_date < challenge.start_date: + raise serializers.ValidationError( + "Phase start date must be on or after challenge start date." + ) + if start_date and challenge.end_date and start_date >= challenge.end_date: + raise serializers.ValidationError( + "Phase start date must be before challenge end date." + ) + if end_date and challenge.start_date and end_date <= challenge.start_date: + raise serializers.ValidationError( + "Phase end date must be after challenge start date." + ) + if end_date and challenge.end_date and end_date > challenge.end_date: + raise serializers.ValidationError( + "Phase end date must be on or before challenge end date." + ) + if start_date and end_date and start_date >= end_date: + raise serializers.ValidationError( + "Phase start date must be before end date." + ) + return attrs + def __init__(self, *args, **kwargs): super(ChallengePhaseSerializer, self).__init__(*args, **kwargs) context = kwargs.get("context") @@ -441,6 +477,33 @@ class ChallengePhaseCreateSerializer(serializers.ModelSerializer): is_active = serializers.ReadOnlyField() + def validate(self, attrs): + start_date = attrs.get("start_date") + end_date = attrs.get("end_date") + challenge = attrs.get("challenge") + if challenge: + if start_date and challenge.start_date and start_date < challenge.start_date: + raise serializers.ValidationError( + "Phase start date must be on or after challenge start date." + ) + if start_date and challenge.end_date and start_date >= challenge.end_date: + raise serializers.ValidationError( + "Phase start date must be before challenge end date." + ) + if end_date and challenge.start_date and end_date <= challenge.start_date: + raise serializers.ValidationError( + "Phase end date must be after challenge start date." + ) + if end_date and challenge.end_date and end_date > challenge.end_date: + raise serializers.ValidationError( + "Phase end date must be on or before challenge end date." + ) + if start_date and end_date and start_date >= end_date: + raise serializers.ValidationError( + "Phase start date must be before end date." + ) + return attrs + def __init__(self, *args, **kwargs): super(ChallengePhaseCreateSerializer, self).__init__(*args, **kwargs) context = kwargs.get("context") diff --git a/tests/unit/challenges/test_models.py b/tests/unit/challenges/test_models.py index 5aebb97403..c952730e1e 100644 --- a/tests/unit/challenges/test_models.py +++ b/tests/unit/challenges/test_models.py @@ -317,9 +317,10 @@ def test_save_raises_error_when_phase_end_after_challenge_end(self): self.challenge_phase.save() def test_save_raises_error_when_phase_start_after_phase_end(self): - now = timezone.now() - self.challenge_phase.start_date = now + timedelta(days=1) - self.challenge_phase.end_date = now + self.challenge_phase.start_date = self.challenge.start_date + timedelta( + hours=5 + ) + self.challenge_phase.end_date = self.challenge.start_date + timedelta(hours=1) with self.assertRaises(ValidationError): self.challenge_phase.save() From 38e99c1c8da6fac3c385783856d86e473e43b5df Mon Sep 17 00:00:00 2001 From: Shivam Singh Date: Tue, 7 Jul 2026 13:25:58 +0530 Subject: [PATCH 3/5] review: handle partial PATCH updates in serializer validate methods - Fall back to self.instance values when a field is absent from attrs during PATCH requests, so the date-range check catches invalid combinations where the other value exists on the existing instance - Shorten conditional lines with local vars (csd, ced) to comply with 79-char black limit - Split error message strings across lines for line-length compliance --- apps/challenges/serializers.py | 60 +++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/apps/challenges/serializers.py b/apps/challenges/serializers.py index febaf43d36..15848dc9dd 100644 --- a/apps/challenges/serializers.py +++ b/apps/challenges/serializers.py @@ -39,6 +39,10 @@ def get_domain_name(self, obj): def validate(self, attrs): start_date = attrs.get("start_date") end_date = attrs.get("end_date") + if start_date is None and self.instance is not None: + start_date = self.instance.start_date + if end_date is None and self.instance is not None: + end_date = self.instance.end_date if start_date and end_date and start_date >= end_date: raise serializers.ValidationError( "Challenge start date must be before end date." @@ -160,22 +164,34 @@ def validate(self, attrs): start_date = attrs.get("start_date") end_date = attrs.get("end_date") challenge = attrs.get("challenge") + if start_date is None and self.instance is not None: + start_date = self.instance.start_date + if end_date is None and self.instance is not None: + end_date = self.instance.end_date + if challenge is None and self.instance is not None: + challenge = self.instance.challenge if challenge: - if start_date and challenge.start_date and start_date < challenge.start_date: + csd = challenge.start_date + ced = challenge.end_date + if start_date and csd and start_date < csd: raise serializers.ValidationError( - "Phase start date must be on or after challenge start date." + "Phase start date must be on or after " + "challenge start date." ) - if start_date and challenge.end_date and start_date >= challenge.end_date: + if start_date and ced and start_date >= ced: raise serializers.ValidationError( - "Phase start date must be before challenge end date." + "Phase start date must be before " + "challenge end date." ) - if end_date and challenge.start_date and end_date <= challenge.start_date: + if end_date and csd and end_date <= csd: raise serializers.ValidationError( - "Phase end date must be after challenge start date." + "Phase end date must be after " + "challenge start date." ) - if end_date and challenge.end_date and end_date > challenge.end_date: + if end_date and ced and end_date > ced: raise serializers.ValidationError( - "Phase end date must be on or before challenge end date." + "Phase end date must be on or before " + "challenge end date." ) if start_date and end_date and start_date >= end_date: raise serializers.ValidationError( @@ -481,22 +497,34 @@ def validate(self, attrs): start_date = attrs.get("start_date") end_date = attrs.get("end_date") challenge = attrs.get("challenge") + if start_date is None and self.instance is not None: + start_date = self.instance.start_date + if end_date is None and self.instance is not None: + end_date = self.instance.end_date + if challenge is None and self.instance is not None: + challenge = self.instance.challenge if challenge: - if start_date and challenge.start_date and start_date < challenge.start_date: + csd = challenge.start_date + ced = challenge.end_date + if start_date and csd and start_date < csd: raise serializers.ValidationError( - "Phase start date must be on or after challenge start date." + "Phase start date must be on or after " + "challenge start date." ) - if start_date and challenge.end_date and start_date >= challenge.end_date: + if start_date and ced and start_date >= ced: raise serializers.ValidationError( - "Phase start date must be before challenge end date." + "Phase start date must be before " + "challenge end date." ) - if end_date and challenge.start_date and end_date <= challenge.start_date: + if end_date and csd and end_date <= csd: raise serializers.ValidationError( - "Phase end date must be after challenge start date." + "Phase end date must be after " + "challenge start date." ) - if end_date and challenge.end_date and end_date > challenge.end_date: + if end_date and ced and end_date > ced: raise serializers.ValidationError( - "Phase end date must be on or before challenge end date." + "Phase end date must be on or before " + "challenge end date." ) if start_date and end_date and start_date >= end_date: raise serializers.ValidationError( From 6255971c4f89b591c6c9eaea6ec048c689b530f1 Mon Sep 17 00:00:00 2001 From: Shivam Singh Date: Tue, 7 Jul 2026 13:29:07 +0530 Subject: [PATCH 4/5] style: use getattr(self.instance) fallback in serializer validate methods Shorter and cleaner than the explicit if/else guard. Adopts the exact pattern Copilot suggested. --- apps/challenges/serializers.py | 48 +++++++++++++++++----------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/apps/challenges/serializers.py b/apps/challenges/serializers.py index 15848dc9dd..63f4aa1030 100644 --- a/apps/challenges/serializers.py +++ b/apps/challenges/serializers.py @@ -37,12 +37,12 @@ def get_domain_name(self, obj): return obj.get_domain_display() def validate(self, attrs): - start_date = attrs.get("start_date") - end_date = attrs.get("end_date") - if start_date is None and self.instance is not None: - start_date = self.instance.start_date - if end_date is None and self.instance is not None: - end_date = self.instance.end_date + start_date = attrs.get( + "start_date", getattr(self.instance, "start_date", None) + ) + end_date = attrs.get( + "end_date", getattr(self.instance, "end_date", None) + ) if start_date and end_date and start_date >= end_date: raise serializers.ValidationError( "Challenge start date must be before end date." @@ -161,15 +161,15 @@ class ChallengePhaseSerializer(serializers.ModelSerializer): is_active = serializers.ReadOnlyField() def validate(self, attrs): - start_date = attrs.get("start_date") - end_date = attrs.get("end_date") - challenge = attrs.get("challenge") - if start_date is None and self.instance is not None: - start_date = self.instance.start_date - if end_date is None and self.instance is not None: - end_date = self.instance.end_date - if challenge is None and self.instance is not None: - challenge = self.instance.challenge + start_date = attrs.get( + "start_date", getattr(self.instance, "start_date", None) + ) + end_date = attrs.get( + "end_date", getattr(self.instance, "end_date", None) + ) + challenge = attrs.get( + "challenge", getattr(self.instance, "challenge", None) + ) if challenge: csd = challenge.start_date ced = challenge.end_date @@ -494,15 +494,15 @@ class ChallengePhaseCreateSerializer(serializers.ModelSerializer): is_active = serializers.ReadOnlyField() def validate(self, attrs): - start_date = attrs.get("start_date") - end_date = attrs.get("end_date") - challenge = attrs.get("challenge") - if start_date is None and self.instance is not None: - start_date = self.instance.start_date - if end_date is None and self.instance is not None: - end_date = self.instance.end_date - if challenge is None and self.instance is not None: - challenge = self.instance.challenge + start_date = attrs.get( + "start_date", getattr(self.instance, "start_date", None) + ) + end_date = attrs.get( + "end_date", getattr(self.instance, "end_date", None) + ) + challenge = attrs.get( + "challenge", getattr(self.instance, "challenge", None) + ) if challenge: csd = challenge.start_date ced = challenge.end_date From 4ad415e6d868bd41ae7273fd09c5087ea0f082ba Mon Sep 17 00:00:00 2001 From: Shivam Singh Date: Tue, 14 Jul 2026 14:35:17 +0530 Subject: [PATCH 5/5] frontend: clear submission type marker after submission The submission type radio buttons (CLI / file upload) stayed visually checked after a submission, while the phase selection correctly reset. This happened because the radio buttons had no ng-model binding, so Angular couldn't reset their checked state. Added ng-model to both radio buttons and reset the model + boolean flags on submission success and error. Fixes #4351 Signed-off-by: Shivam Singh --- frontend/src/js/controllers/challengeCtrl.js | 9 +++++++++ frontend/src/views/web/challenge/submission.html | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/frontend/src/js/controllers/challengeCtrl.js b/frontend/src/js/controllers/challengeCtrl.js index b485cec378..5858221f02 100644 --- a/frontend/src/js/controllers/challengeCtrl.js +++ b/frontend/src/js/controllers/challengeCtrl.js @@ -71,6 +71,7 @@ vm.isSubmissionUsingUrl = null; vm.isSubmissionUsingCli = null; vm.isSubmissionUsingFile = null; + vm.submissionType = null; vm.isRemoteChallenge = false; vm.allowResumingSubmisisons = false; vm.allowCancelRunningSubmissions = false; @@ -738,6 +739,10 @@ vm.projectUrl = ""; vm.publicationUrl = ""; vm.isPublicSubmission = null; + vm.isSubmissionUsingCli = null; + vm.isSubmissionUsingFile = null; + vm.isSubmissionUsingUrl = null; + vm.submissionType = null; $rootScope.notify("success", "Your submission has been recorded succesfully!"); vm.disableSubmit = true; vm.showSubmissionNumbers = false; @@ -755,6 +760,10 @@ vm.projectUrl = null; vm.publicationUrl = null; vm.isPublicSubmission = null; + vm.isSubmissionUsingCli = null; + vm.isSubmissionUsingFile = null; + vm.isSubmissionUsingUrl = null; + vm.submissionType = null; if (status == 404) { vm.subErrors.msg = "Please select phase!"; } else { diff --git a/frontend/src/views/web/challenge/submission.html b/frontend/src/views/web/challenge/submission.html index 860c402169..ebe0763564 100644 --- a/frontend/src/views/web/challenge/submission.html +++ b/frontend/src/views/web/challenge/submission.html @@ -224,14 +224,14 @@
Make Submission

Select submission type:

  • + value="cliSubmission" ng-model="challenge.submissionType" ng-click="challenge.isSubmissionUsingCli = true; challenge.isSubmissionUsingFile = false; challenge.isSubmissionUsingUrl = false">
    Use CLI (for file size > 400MB)