Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 66 additions & 2 deletions apps/challenges/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -354,10 +355,26 @@ 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

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)

Comment on lines +367 to +377

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

set -euo pipefail

printf '\n## models.py relevant sections\n'
sed -n '330,410p' apps/challenges/models.py
printf '\n---\n'
sed -n '580,690p' apps/challenges/models.py

printf '\n## serializers/search\n'
rg -n "class (ChallengeSerializer|ChallengePhaseSerializer|ChallengePhaseCreateSerializer)|validate\(" apps/challenges -n

printf '\n## exception handling search\n'
rg -n "exception_handler|ValidationError|DRF.*exception|REST_FRAMEWORK" -S .

Repository: Cloud-CV/EvalAI

Length of output: 13692


🏁 Script executed:

set -euo pipefail

printf '\n## REST_FRAMEWORK config\n'
sed -n '140,220p' settings/common.py

printf '\n## Challenge serializer validate() methods\n'
sed -n '1,240p' apps/challenges/serializers.py
printf '\n---\n'
sed -n '480,560p' apps/challenges/serializers.py

Repository: Cloud-CV/EvalAI

Length of output: 13913


🏁 Script executed:

set -euo pipefail

sed -n '150,210p' settings/common.py
printf '\n---\n'
sed -n '1,240p' apps/challenges/serializers.py
printf '\n---\n'
sed -n '480,560p' apps/challenges/serializers.py

Repository: Cloud-CV/EvalAI

Length of output: 13203


Move challenge date validation out of save() Challenge.save() and ChallengePhase.save() raise django.core.exceptions.ValidationError directly, so callers that bypass serializer validation can surface an unhandled 500. The same date checks are already duplicated in ChallengeSerializer, ChallengePhaseSerializer, and ChallengePhaseCreateSerializer, so this logic is split across five places and will drift.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/challenges/models.py` around lines 367 - 377, Remove the start/end date
ValidationError checks from Challenge.save() and the corresponding
ChallengePhase.save() implementation. Keep persistence methods limited to
calling the superclass save, and consolidate the date validation in the existing
serializer validation methods, reusing a shared validator if appropriate to
avoid duplication across ChallengeSerializer, ChallengePhaseSerializer, and
ChallengePhaseCreateSerializer.


@receiver(signals.post_save, sender="challenges.Challenge")
def create_eks_cluster_or_ec2_for_challenge(
Expand Down Expand Up @@ -586,11 +603,58 @@ 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

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.
Expand Down
91 changes: 91 additions & 0 deletions apps/challenges/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ 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", 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."
)
return attrs

def validate_worker_python_version(self, value):
if value in (None, ""):
return DEFAULT_WORKER_PYTHON_VERSION
Expand Down Expand Up @@ -147,6 +160,45 @@ class ChallengePhaseSerializer(serializers.ModelSerializer):

is_active = serializers.ReadOnlyField()

def validate(self, attrs):
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
if start_date and csd and start_date < csd:
raise serializers.ValidationError(
"Phase start date must be on or after "
"challenge start date."
)
if start_date and ced and start_date >= ced:
raise serializers.ValidationError(
"Phase start date must be before "
"challenge end date."
)
if end_date and csd and end_date <= csd:
raise serializers.ValidationError(
"Phase end date must be after "
"challenge start date."
)
if end_date and ced and end_date > ced:
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

Comment on lines +163 to +201

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

ChallengePhaseSerializer.validate() and ChallengePhaseCreateSerializer.validate() are identical.

Both methods implement the exact same four challenge-boundary checks plus the self start_date >= end_date check, verbatim. This — combined with the same logic re-implemented again in ChallengePhase.save() — means the validation rules now live in 3 places in this file alone (5 counting the models). See the consolidated comment for a suggested extraction.

Also applies to: 496-534

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/challenges/serializers.py` around lines 163 - 201, The validation rules
are duplicated across ChallengePhaseSerializer.validate(),
ChallengePhaseCreateSerializer.validate(), and ChallengePhase.save(). Extract
the shared challenge-boundary and start/end date checks into one reusable
helper, then have both serializer validate methods call it while preserving
their current inputs and ValidationError behavior; use the consolidated helper
from save() as well.

def __init__(self, *args, **kwargs):
super(ChallengePhaseSerializer, self).__init__(*args, **kwargs)
context = kwargs.get("context")
Expand Down Expand Up @@ -441,6 +493,45 @@ class ChallengePhaseCreateSerializer(serializers.ModelSerializer):

is_active = serializers.ReadOnlyField()

def validate(self, attrs):
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
if start_date and csd and start_date < csd:
raise serializers.ValidationError(
"Phase start date must be on or after "
"challenge start date."
)
if start_date and ced and start_date >= ced:
raise serializers.ValidationError(
"Phase start date must be before "
"challenge end date."
)
if end_date and csd and end_date <= csd:
raise serializers.ValidationError(
"Phase end date must be after "
"challenge start date."
)
if end_date and ced and end_date > ced:
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")
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/js/controllers/challengeCtrl.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/views/web/challenge/submission.html
Original file line number Diff line number Diff line change
Expand Up @@ -224,14 +224,14 @@ <h5 class="w-300">Make Submission</h5>
<p class="fs-18 w-400">Select submission type:</p>
<li>
<input type="radio" name="submissionOptions" class="with-gap selectPhase" id="cliSubmission"
value="cliSubmission" ng-click="challenge.isSubmissionUsingCli = true; challenge.isSubmissionUsingFile = false; challenge.isSubmissionUsingUrl = false">
value="cliSubmission" ng-model="challenge.submissionType" ng-click="challenge.isSubmissionUsingCli = true; challenge.isSubmissionUsingFile = false; challenge.isSubmissionUsingUrl = false">
<label for="cliSubmission"></label>
<div class="show-member-title pointer">
<strong class="fs-16 w-300">Use CLI (for file size > 400MB)
</strong>
</div>
<input type="radio" name="submissionOptions" class="with-gap selectPhase"
id="fileUpload" value="challenge.showFileUploadOption"
id="fileUpload" value="fileUpload" ng-model="challenge.submissionType"
ng-click="challenge.isSubmissionUsingFile = true; challenge.isSubmissionUsingUrl = false; challenge.isSubmissionUsingCli = false;">
<label for="fileUpload"></label>
<div class="show-member-title pointer">
Expand Down
Loading