Repository: Cloud-CV/EvalAI

Issue:
Improve Validate Challenge Config
### Description

Currently, there are lots of issues with the validate challenge configuration utility present [here](https://github.com/Cloud-CV/EvalAI/blob/19130726790e4cb79b0d2e8603dd6817b16dba69/apps/challenges/challenge_config_utils.py#L223).

Here are some issues which this has been reported in the past:
- Removing `test_annotation_file` from `challenge_phases` throws Internal Server Error.
- Removing `is_leaderboard_order_descending` from `challenge_phase_splits` also throws errors.

The issue with such cases is that the hosts often remove these things and reach out to us with errors and it becomes challenging for us to figure out where the script is exactly failing.

Ideally, we would like for the validation script to handle each corner case properly and handle the exceptions appropriately so as to have verbose errors when something is missing or is failing. Additionally, if something is not really required by the configuration, we should allow the hosts to skip it from the configuration. For example `test_annotation_file` is not necessarily needed in code-upload challenges.

### Pre-requisites/Learnings
This issue essentially requires background on a few things:
- An understanding of the [challenge configuration](https://evalai.readthedocs.io/en/latest/configuration.html).
- A high-level understanding of [EvalAI challenges](https://evalai.readthedocs.io/en/latest/host_challenge.html).
- How the [challenge validation](https://github.com/Cloud-CV/EvalAI/blob/19130726790e4cb79b0d2e8603dd6817b16dba69/apps/challenges/challenge_config_utils.py#L223) works.

### How to Approach
- You can start by setting up EvalAI locally or hosting a challenge on staging
    - To host a challenge on staging, please follow the steps [here](https://evalai.readthedocs.io/en/latest/host_challenge.html#host-challenge-using-github).
    - (**Recommended**) Set up the dev version locally (preferred) using the steps [here](https://github.com/Cloud-CV/EvalAI/tree/master#installation-instructions).
- Play around with the challenge configuration to see what fails, put up the errors in a draft PR as a comment, along with the appropriate changes to fix it.
- Reach out to us regarding any issues that you face or if you need any help with understanding any of the above.

Improve Validate Challenge Config
### Description

Currently, there are lots of issues with the validate challenge configuration utility present [here](https://github.com/Cloud-CV/EvalAI/blob/19130726790e4cb79b0d2e8603dd6817b16dba69/apps/challenges/challenge_config_utils.py#L223).

Here are some issues which this has been reported in the past:
- Removing `test_annotation_file` from `challenge_phases` throws Internal Server Error.
- Removing `is_leaderboard_order_descending` from `challenge_phase_splits` also throws errors.

The issue with such cases is that the hosts often remove these things and reach out to us with errors and it becomes challenging for us to figure out where the script is exactly failing.

Ideally, we would like for the validation script to handle each corner case properly and handle the exceptions appropriately so as to have verbose errors when something is missing or is failing. Additionally, if something is not really required by the configuration, we should allow the hosts to skip it from the configuration. For example `test_annotation_file` is not necessarily needed in code-upload challenges.

### Pre-requisites/Learnings
This issue essentially requires background on a few things:
- An understanding of the [challenge configuration](https://evalai.readthedocs.io/en/latest/configuration.html).
- A high-level understanding of [EvalAI challenges](https://evalai.readthedocs.io/en/latest/host_challenge.html).
- How the [challenge validation](https://github.com/Cloud-CV/EvalAI/blob/19130726790e4cb79b0d2e8603dd6817b16dba69/apps/challenges/challenge_config_utils.py#L223) works.

### How to Approach
- You can start by setting up EvalAI locally or hosting a challenge on staging
    - To host a challenge on staging, please follow the steps [here](https://evalai.readthedocs.io/en/latest/host_challenge.html#host-challenge-using-github).
    - (**Recommended**) Set up the dev version locally (preferred) using the steps [here](https://github.com/Cloud-CV/EvalAI/tree/master#installation-instructions).
- Play around with the challenge configuration to see what fails, put up the errors in a draft PR as a comment, along with the appropriate changes to fix it.
- Reach out to us regarding any issues that you face or if you need any help with understanding any of the above.



Discussion:
Hi! 
I want to take on the issue. I have set up the dev version of EvalAI locally and started working on it.

Please assign the issue to me.
Hi,
I am working on it.
Hey @AyushR1, Since you commented first, you can work on this one.

@b9aurav Please take up any other issue. We will open issues soon.

cc: @gchhablani @Ram81 
Hey! I have been working on the issue and after successfully setting up everything locally,  I modified the host challenge using GitHub script to work locally, I have recreated the issues.

I have been playing around to see what more things have been throwing Internal Service Errors on removing them from the YAML file (challenge_config.yaml)  and have found many more things with the same as we have seen on removing `test_annotation_file`. 

**I wanted to know, How should I be telling the verbose errors now? Should I tell it as soon as something is missing from the YAML (challenge_config.yaml) or should I only tell the ones which throw the Internal Service Error?** 

**Also removing `is_leaderboard_order_descending` didn't cause any errors to show up for me. Please tell on how to proceed with this**
@AyushR1

The best way to go about this would be to create a shared document (markdown or docs) and add:
- What are you removing/changing
- The effect / do you face any errors?
- If yes, what is the exact error log?
- Link to the corresponding build on your public build

After that, we can evaluate what all needs change and it'll help us with your upcoming PR as well.

CC @RishabhJain2018 @Ram81

Hi! 
I want to take on the issue. I have set up the dev version of EvalAI locally and started working on it.

Please assign the issue to me.
Hi,
I am working on it.
Hey @AyushR1, Since you commented first, you can work on this one.

@b9aurav Please take up any other issue. We will open issues soon.

cc: @gchhablani @Ram81 
Hey! I have been working on the issue and after successfully setting up everything locally,  I modified the host challenge using GitHub script to work locally, I have recreated the issues.

I have been playing around to see what more things have been throwing Internal Service Errors on removing them from the YAML file (challenge_config.yaml)  and have found many more things with the same as we have seen on removing `test_annotation_file`. 

**I wanted to know, How should I be telling the verbose errors now? Should I tell it as soon as something is missing from the YAML (challenge_config.yaml) or should I only tell the ones which throw the Internal Service Error?** 

**Also removing `is_leaderboard_order_descending` didn't cause any errors to show up for me. Please tell on how to proceed with this**
@AyushR1

The best way to go about this would be to create a shared document (markdown or docs) and add:
- What are you removing/changing
- The effect / do you face any errors?
- If yes, what is the exact error log?
- Link to the corresponding build on your public build

After that, we can evaluate what all needs change and it'll help us with your upcoming PR as well.

CC @RishabhJain2018 @Ram81


Resolving patch:
diff --git a/apps/challenges/challenge_config_utils.py b/apps/challenges/challenge_config_utils.py
--- a/apps/challenges/challenge_config_utils.py
+++ b/apps/challenges/challenge_config_utils.py
@@ -220,436 +220,830 @@ def get_value_from_field(data, base_location, field_name):
     return field_value
 
 
-def validate_challenge_config_util(
-    request, challenge_host_team, BASE_LOCATION, unique_folder_name, zip_ref, current_challenge
-):
-    """
-    Function to validate a challenge config
+error_message_dict = {
+    "no_yaml_file": "There is no YAML file in the zip file you uploaded!",
+    "multiple_yaml_files": "There are {} YAML files instead of one in the zip file!",
+    "yaml_file_read_error": "\n{} in line {}, column {}\n",
+    "missing_challenge_title": "Please add the challenge title",
+    "missing_challenge_description": "Please add the challenge description",
+    "missing_evaluation_details": "Please add the evaluation details",
+    "missing_terms_and_conditions": "Please add the terms and conditions.",
+    "missing_submission_guidelines": "Please add the submission guidelines.",
+    "missing_evaluation_script": "ERROR: No evaluation script is present in the zip file. Please add it and then try again!",
+    "missing_evaluation_script_key": "ERROR: There is no key for the evaluation script in the YAML file. Please add it and then try again!",
+    "missing_leaderboard_id": "ERROR: There is no leaderboard ID for the leaderboard.",
+    "missing_leaderboard_schema": "ERROR: There is no leaderboard schema for the leaderboard with ID: {}.",
+    "missing_leaderboard_default_order_by": "ERROR: There is no 'default_order_by' key in the schema for the leaderboard with ID: {}.",
+    "missing_leaderboard_key": "ERROR: There is no key for the leaderboard in the YAML file. Please add it and then try again!",
+    "incorrect_default_order_by": "ERROR: The 'default_order_by' value '{}' in the schema for the leaderboard with ID: {} is not a valid label.",
+    "leaderboard_schema_error": "ERROR: The leaderboard with ID: {} has the following schema errors:\n {}",
+    "leaderboard_additon_after_creation": "ERROR: The leaderboard with ID: {} doesn't exist. Addition of a new leaderboard after challenge creation is not allowed.",
+    "leaderboard_deletion_after_creation": "ERROR: The leaderboard with ID: {} not found in config. Deletion of an existing leaderboard after challenge creation is not allowed.",
+    "missing_leaderboard_labels": "ERROR: There is no 'labels' key in the schema for the leaderboard with ID: {}.",
+    "missing_challenge_phases": "ERROR: No challenge phase key found. Please add challenge phases in the YAML file and try again!",
+    "missing_challenge_phase_codename": "ERROR: No codename found for the challenge phase. Please add the codename and try again!",
+    "missing_test_annotation_file": "ERROR: No test annotation file found in the zip file for challenge phase {}.",
+    "missing_submission_meta_attribute_keys": "ERROR: Please enter the following keys to the submission meta attribute in challenge phase {}: {}",
+    "invalid_submission_meta_attribute_types": "ERROR: Please ensure that the submission meta attribute types for the attribute '{}' in challenge phase {} are among the following: boolean, text, radio, or checkbox.",
+    "missing_challenge_phase_id": "ERROR: Challenge phase {} doesn't exist. Addition of a new challenge phase after challenge creation is not allowed.",
+    "missing_challenge_phase_id_config": "ERROR: Challenge phase {} doesn't exist. Addition of a new challenge phase after challenge creation is not allowed.",
+    "missing_leaderboard_id_config": "ERROR: The leaderboard with ID: {} doesn't exist. Addition of a new leaderboard after challenge creation is not allowed.",
+    "missing_existing_leaderboard_id": "ERROR: The leaderboard with ID: {} was not found in the configuration. Deletion of an existing leaderboard after challenge creation is not allowed.",
+    "missing_existing_challenge_phase_id": "ERROR: Challenge phase {} was not found in the configuration. Deletion of an existing challenge phase after challenge creation is not allowed.",
+    "missing_dataset_splits_key": "ERROR: There is no key for dataset splits.",
+    "missing_dataset_split_name": "ERROR: There is no name for dataset split {}.",
+    "missing_dataset_split_codename": "ERROR: There is no codename for dataset split {}.",
+    "duplicate_dataset_split_codename": "ERROR: Duplicate codename {} for dataset split {}. Please ensure codenames are unique.",
+    "dataset_split_schema_errors": "ERROR: Dataset split {} has the following schema errors:\n {}",
+    "dataset_split_addition": "ERROR: Dataset split {} doesn't exist. Addition of a new dataset split after challenge creation is not allowed.",
+    "missing_existing_dataset_split_id": "ERROR: Dataset split {} not found in config. Deletion of existing dataset split after challenge creation is not allowed.",
+    "challenge_phase_split_not_exist": "ERROR: Challenge phase split (leaderboard_id: {}, challenge_phase_id: {}, dataset_split_id: {}) doesn't exist. Addition of challenge phase split after challenge creation is not allowed.",
+    "challenge_phase_split_schema_errors": "ERROR: Challenge phase split {} has the following schema errors:\n {}",
+    "missing_keys_in_challenge_phase_splits": "ERROR: The following keys are missing in the challenge phase splits of YAML file (phase_split: {}): {}",
+    "challenge_phase_split_not_found": "ERROR: Challenge phase split (leaderboard_id: {}, challenge_phase_id: {}, dataset_split_id: {}) not found in config. Deletion of existing challenge phase split after challenge creation is not allowed.",
+    "no_key_for_challenge_phase_splits": "ERROR: There is no key for challenge phase splits.",
+    "no_codename_for_challenge_phase": "ERROR: No codename found for the challenge phase. Please add a codename and try again!",
+    "duplicate_codename_for_phase": "ERROR: Duplicate codename {} for phase {}. Please ensure codenames are unique",
+    "no_test_annotation_file_found": "ERROR: No test annotation file found in the zip file for challenge phase {}",
+    "submission_meta_attribute_option_missing": "ERROR: Please include at least one option in the attribute for challenge phase {}",
+    "missing_submission_meta_attribute_fields": "ERROR: Please enter the following fields for the submission meta attribute in challenge phase {}: {}",
+    "challenge_phase_schema_errors": "ERROR: Challenge phase {} has the following schema errors:\n {}",
+    "challenge_phase_addition": "ERROR: Challenge phase {} doesn't exist. Addition of a new challenge phase after challenge creation is not allowed.",
+    "challenge_phase_not_found": "ERROR: Challenge phase {} not found in config. Deletion of existing challenge phase after challenge creation is not allowed.",
+    "is_submission_public_restricted": "ERROR: is_submission_public can't be 'True' for challenge phase '{}' with is_restricted_to_select_one_submission 'True'. Please change is_submission_public to 'False' and try again!",
+    "missing_option_in_submission_meta_attribute": "ERROR: Please include at least one option in the attribute for challenge phase {}",
+    "missing_fields_in_submission_meta_attribute": "ERROR: Please enter the following fields for the submission meta attribute in challenge phase {}: {}",
+    "missing_date": "ERROR: Please add the start_date and end_date.",
+    "start_date_greater_than_end_date": "ERROR: Start date cannot be greater than end date.",
+    "missing_dates_challenge_phase": "ERROR: Please add the start_date and end_date in challenge phase {}.",
+    "start_date_greater_than_end_date_challenge_phase": "ERROR: Start date cannot be greater than end date in challenge phase {}.",
+}
 
-    Arguments:
-        request {HttpRequest} -- The request object
-        BASE_LOCATION {str} -- The temp base directory for storing all the files and folders while validating the zip file
-        unique_folder_name {str} -- name of the challenge zip file and the parent dir of extracted folder
-        zip_ref {zipfile.ZipFile} -- reference to challenge config zip
-        current_challenge {apps.challenges.models.Challenge} - the existing challenge for the github repo, if any
-    """
 
-    error_messages = []
-    files = {}
-    yaml_file_data = None
+class ValidateChallengeConfigUtil:
+    def __init__(
+        self,
+        request,
+        challenge_host_team,
+        base_location,
+        unique_folder_name,
+        zip_ref,
+        current_challenge,
+    ):
+        """
+        Class containing methods to validate the challenge configuration
 
-    (
-        yaml_file_count,
-        yaml_file,
-        extracted_folder_name,
-    ) = get_yaml_files_from_challenge_config(zip_ref)
+        Arguments:
+            request {HttpRequest} -- The request object
+            challenge_host_team {int} -- the team creating the challenge
+            base_location {str} -- The temp base directory for storing all the files and folders while validating the zip file
+            unique_folder_name {str} -- name of the challenge zip file and the parent dir of extracted folder
+            zip_ref {zipfile.ZipFile} -- reference to challenge config zip
+            current_challenge {apps.challenges.models.Challenge} - the existing challenge for the github repo, if any
+        """
+        self.request = request
+        self.challenge_host_team = challenge_host_team
+        self.base_location = base_location
+        self.unique_folder_name = unique_folder_name
+        self.zip_ref = zip_ref
+        self.current_challenge = current_challenge
 
-    if not yaml_file_count:
-        message = "There is no YAML file in zip file you uploaded!"
-        error_messages.append(message)
-        return error_messages, yaml_file_data, files
+        self.error_messages = []
+        self.files = {}
+        self.yaml_file_data = None
+        self.error_messages_dict = error_message_dict
 
-    if yaml_file_count > 1:
-        message = (
-            "There are {0} YAML files instead of one in zip file!".format(
-                yaml_file_count
-            )
-        )
-        error_messages.append(message)
-        return error_messages, yaml_file_data, files
+        (
+            self.yaml_file_count,
+            self.yaml_file,
+            self.extracted_folder_name,
+        ) = get_yaml_files_from_challenge_config(self.zip_ref)
 
-    try:
-        yaml_file_path = join(BASE_LOCATION, unique_folder_name, yaml_file)
-        yaml_file_data = read_yaml_file(yaml_file_path, "r")
-    except (yaml.YAMLError, ScannerError) as exc:
-        error_description, line_number, column_number = get_yaml_read_error(
-            exc
-        )
-        message = "\n{} in line {}, column {}\n".format(
-            error_description, line_number, column_number
-        )
-        error_messages.append(message)
-        return error_messages, yaml_file_data, files
-
-    # Check for challenge title
-    challenge_title = yaml_file_data.get("title")
-    if not challenge_title or len(challenge_title) == 0:
-        message = "Please add the challenge title"
-        error_messages.append(message)
-
-    # Check for the challenge logo
-    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
+        self.challenge_config_location = join(
+            self.base_location,
+            self.unique_folder_name,
+            self.extracted_folder_name,
         )
-        if isfile(challenge_image_path):
-            challenge_image_file = ContentFile(
-                get_file_content(challenge_image_path, "rb"), image
+        self.phase_ids = []
+        self.leaderboard_ids = []
+
+    def read_and_validate_yaml(self):
+        if not self.yaml_file_count:
+            message = self.error_messages_dict.get("no_yaml_file")
+            self.error_messages.append(message)
+
+        if self.yaml_file_count > 1:
+            message = self.error_messages_dict.get(
+                "multiple_yaml_files"
+            ).format(self.yaml_file_count)
+            self.error_messages.append(message)
+
+        # YAML Read Error
+        try:
+            self.yaml_file_path = join(
+                self.base_location, self.unique_folder_name, self.yaml_file
             )
+            self.yaml_file_data = read_yaml_file(self.yaml_file_path, "r")
+        except (yaml.YAMLError, ScannerError) as exc:
+            (
+                error_description,
+                line_number,
+                column_number,
+            ) = get_yaml_read_error(exc)
+            message = self.error_messages_dict.get(
+                "yaml_file_read_error"
+            ).format(error_description, line_number, column_number)
+            self.error_messages.append(message)
+
+    def validate_challenge_title(self):
+        challenge_title = self.yaml_file_data.get("title")
+        if not challenge_title or len(challenge_title) == 0:
+            message = self.error_messages_dict.get("missing_challenge_title")
+            self.error_messages.append(message)
+
+    def validate_challenge_logo(self):
+        image = self.yaml_file_data.get("image")
+        if image and (
+            image.endswith(".jpg")
+            or image.endswith(".jpeg")
+            or image.endswith(".png")
+        ):
+            self.challenge_image_path = join(
+                self.base_location,
+                self.unique_folder_name,
+                self.extracted_folder_name,
+                image,
+            )
+
+            if isfile(self.challenge_image_path):
+                self.challenge_image_file = ContentFile(
+                    get_file_content(self.challenge_image_path, "rb"), image
+                )
+            else:
+                self.challenge_image_file = None
         else:
-            challenge_image_file = None
-    else:
-        challenge_image_file = None
-    files["challenge_image_file"] = challenge_image_file
+            self.challenge_image_file = None
+        self.files["challenge_image_file"] = self.challenge_image_file
 
-    # Check for challenge description file
-    challenge_config_location = join(
-        BASE_LOCATION, unique_folder_name, extracted_folder_name
-    )
-    is_valid, message = is_challenge_config_yaml_html_field_valid(
-        yaml_file_data, "description", challenge_config_location
-    )
-    if not is_valid:
-        error_messages.append(message)
-    else:
-        yaml_file_data["description"] = get_value_from_field(
-            yaml_file_data, challenge_config_location, "description"
-        )
+    def validate_challenge_description(self):
+        challenge_description = self.yaml_file_data.get("description")
+        if not challenge_description or len(challenge_description) == 0:
+            message = self.error_messages_dict.get(
+                "missing_challenge_description"
+            )
+            self.error_messages.append(message)
+        else:
+            is_valid, message = is_challenge_config_yaml_html_field_valid(
+                self.yaml_file_data,
+                "description",
+                self.challenge_config_location,
+            )
+            if not is_valid:
+                self.error_messages.append(message)
+            else:
+                self.yaml_file_data["description"] = get_value_from_field(
+                    self.yaml_file_data,
+                    self.challenge_config_location,
+                    "description",
+                )
 
     # Check for evaluation details file
-    is_valid, message = is_challenge_config_yaml_html_field_valid(
-        yaml_file_data, "evaluation_details", challenge_config_location
-    )
-    if not is_valid:
-        error_messages.append(message)
-    else:
-        yaml_file_data["evaluation_details"] = get_value_from_field(
-            yaml_file_data, challenge_config_location, "evaluation_details"
-        )
+    def validate_evaluation_details_file(self):
+        evaluation_details = self.yaml_file_data.get("evaluation_details")
+        if not evaluation_details or len(evaluation_details) == 0:
+            message = self.error_messages_dict.get(
+                "missing_evaluation_details"
+            )
+            self.error_messages.append(message)
+        else:
+            is_valid, message = is_challenge_config_yaml_html_field_valid(
+                self.yaml_file_data,
+                "evaluation_details",
+                self.challenge_config_location,
+            )
+            if not is_valid:
+                self.error_messages.append(message)
+            else:
+                self.yaml_file_data[
+                    "evaluation_details"
+                ] = get_value_from_field(
+                    self.yaml_file_data,
+                    self.challenge_config_location,
+                    "evaluation_details",
+                )
 
-    # Check for terms and conditions file
-    is_valid, message = is_challenge_config_yaml_html_field_valid(
-        yaml_file_data, "terms_and_conditions", challenge_config_location
-    )
-    if not is_valid:
-        error_messages.append(message)
-    else:
-        yaml_file_data["terms_and_conditions"] = get_value_from_field(
-            yaml_file_data, challenge_config_location, "terms_and_conditions"
-        )
+    # Validate terms and conditions file
+    def validate_terms_and_conditions_file(self):
+        terms_and_conditions = self.yaml_file_data.get("terms_and_conditions")
+        if not terms_and_conditions or len(terms_and_conditions) == 0:
+            message = self.error_messages_dict.get(
+                "missing_terms_and_conditions"
+            )
+            self.error_messages.append(message)
+        else:
+            is_valid, message = is_challenge_config_yaml_html_field_valid(
+                self.yaml_file_data,
+                "terms_and_conditions",
+                self.challenge_config_location,
+            )
+            if not is_valid:
+                self.error_messages.append(message)
+            else:
+                self.yaml_file_data[
+                    "terms_and_conditions"
+                ] = get_value_from_field(
+                    self.yaml_file_data,
+                    self.challenge_config_location,
+                    "terms_and_conditions",
+                )
 
-    # Check for submission guidelines file
-    is_valid, message = is_challenge_config_yaml_html_field_valid(
-        yaml_file_data, "submission_guidelines", challenge_config_location
-    )
-    if not is_valid:
-        error_messages.append(message)
-    else:
-        yaml_file_data["submission_guidelines"] = get_value_from_field(
-            yaml_file_data, challenge_config_location, "submission_guidelines"
+    # Validate  ubmission guidelines file
+    def validate_submission_guidelines_file(self):
+        submission_guidelines = self.yaml_file_data.get(
+            "submission_guidelines"
         )
+        if not submission_guidelines or len(submission_guidelines) == 0:
+            message = self.error_messages_dict.get(
+                "missing_submission_guidelines"
+            )
+            self.error_messages.append(message)
+        else:
+            is_valid, message = is_challenge_config_yaml_html_field_valid(
+                self.yaml_file_data,
+                "submission_guidelines",
+                self.challenge_config_location,
+            )
+            if not is_valid:
+                self.error_messages.append(message)
+            else:
+                self.yaml_file_data[
+                    "submission_guidelines"
+                ] = get_value_from_field(
+                    self.yaml_file_data,
+                    self.challenge_config_location,
+                    "submission_guidelines",
+                )
 
-    # Check for evaluation script path
-    evaluation_script = yaml_file_data.get("evaluation_script")
-    if evaluation_script:
-        evaluation_script_path = join(
-            challenge_config_location, evaluation_script
-        )
-        # Check for evaluation script file in extracted zip folder
-        if isfile(evaluation_script_path):
-            challenge_evaluation_script_file = read_file_data_as_content_file(
-                evaluation_script_path, "rb", evaluation_script_path
+    def validate_evaluation_script_file(self):
+        evaluation_script = self.yaml_file_data.get("evaluation_script")
+        if evaluation_script:
+            evaluation_script_path = join(
+                self.challenge_config_location, evaluation_script
             )
-            files[
-                "challenge_evaluation_script_file"
-            ] = challenge_evaluation_script_file
+            # Check for evaluation script file in extracted zip folder
+            if isfile(evaluation_script_path):
+                self.challenge_evaluation_script_file = (
+                    read_file_data_as_content_file(
+                        evaluation_script_path, "rb", evaluation_script_path
+                    )
+                )
+                self.files[
+                    "challenge_evaluation_script_file"
+                ] = self.challenge_evaluation_script_file
+            else:
+                message = self.error_messages_dict.get(
+                    "missing_evaluation_script"
+                )
+                self.error_messages.append(message)
         else:
-            message = "ERROR: No evaluation script is present in the zip file. Please add it and then try again!"
-            error_messages.append(message)
-    else:
-        message = "ERROR: There is no key for evaluation script in YAML file. Please add it and then try again!"
-        error_messages.append(message)
-
-    if not len(error_messages):
-        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,
-                "github_repository": request.data["GITHUB_REPOSITORY"],
-            },
-        )
-        if not serializer.is_valid():
-            message = "ERROR: Challenge metadata has following schema errors:\n {}".format(
-                str(serializer.errors)
+            message = self.error_messages_dict.get(
+                "missing_evaluation_script_key"
             )
-            error_messages.append(message)
-            return error_messages, yaml_file_data, files
+            self.error_messages.append(message)
 
-    # Get existing config IDs for leaderboards and dataset splits
-    if current_challenge:
-        current_challenge_phases = ChallengePhase.objects.filter(challenge=current_challenge.id)
-        current_challenge_phase_splits = ChallengePhaseSplit.objects.filter(challenge_phase__in=current_challenge_phases)
-        current_leaderboards = Leaderboard.objects.filter(id__in=current_challenge_phase_splits.values('leaderboard'))
-        current_dataset_splits = DatasetSplit.objects.filter(id__in=current_challenge_phase_splits.values('dataset_split'))
-
-        current_leaderboard_config_ids = [int(x.config_id) for x in current_leaderboards]
-        current_dataset_config_ids = [int(x.config_id) for x in current_dataset_splits]
-        current_phase_config_ids = [int(x.config_id) for x in current_challenge_phases]
-        current_phase_split_ids = [(split.leaderboard.config_id, split.challenge_phase.config_id, split.dataset_split.config_id) for split in current_challenge_phase_splits]
-    else:
-        current_leaderboard_config_ids = []
-        current_dataset_config_ids = []
-        current_phase_config_ids = []
-        current_phase_split_ids = []
+    def validate_dates(self):
+        start_date = self.yaml_file_data.get("start_date")
+        end_date = self.yaml_file_data.get("end_date")
+
+        if not start_date or not end_date:
+            message = self.error_messages_dict.get("missing_date")
+            self.error_messages.append(message)
+        if start_date and end_date:
+            if start_date > end_date:
+                message = self.error_messages_dict.get(
+                    "start_date_greater_than_end_date"
+                )
+                self.error_messages.append(message)
+
+    def validate_serializer(self):
+        if not len(self.error_messages):
+            serializer = ZipChallengeSerializer(
+                data=self.yaml_file_data,
+                context={
+                    "request": self.request,
+                    "challenge_host_team": self.challenge_host_team,
+                    "image": self.challenge_image_file,
+                    "evaluation_script": self.challenge_evaluation_script_file,
+                    "github_repository": self.request.data[
+                        "GITHUB_REPOSITORY"
+                    ],
+                },
+            )
+            if not serializer.is_valid():
+                message = self.error_messages_dict[
+                    "challenge_metadata_schema_errors"
+                ].format(str(serializer.errors))
+                self.error_messages.append(message)
 
     # Check for leaderboards
-    leaderboard = yaml_file_data.get("leaderboard")
-    leaderboard_ids = []
-    if leaderboard:
-        error = False
-        if "schema" not in leaderboard[0]:
-            message = "ERROR: There is no leaderboard schema in the YAML configuration file."
-            error_messages.append(message)
-            error = True
-        if "default_order_by" not in leaderboard[0].get("schema"):
-            message = "ERROR: There is no 'default_order_by' key in leaderboard schema."
-            error_messages.append(message)
-            error = True
-        if "labels" not in leaderboard[0].get("schema"):
-            message = "ERROR: There is no 'labels' key in leaderboard schema."
-            error_messages.append(message)
-            error = True
-        if not error:
+    def validate_leaderboards(self, current_leaderboard_config_ids):
+        leaderboard = self.yaml_file_data.get("leaderboard")
+        if leaderboard:
             for data in leaderboard:
-                serializer = LeaderboardSerializer(
-                    data=data, context={"config_id": data["id"]}
-                )
-                if not serializer.is_valid():
-                    serializer_error = str(serializer.errors)
-                    message = "ERROR: Leaderboard {} has following schema errors:\n {}".format(
-                        data["id"], serializer_error
+                error = False
+                if "id" not in data:
+                    message = self.error_messages_dict.get(
+                        "missing_leaderboard_id"
                     )
-                    error_messages.append(message)
+                    self.error_messages.append(message)
+                    error = True
+                if "schema" not in data:
+                    message = self.error_messages_dict.get(
+                        "missing_leaderboard_schema"
+                    ).format(data.get("id"))
+                    self.error_messages.append(message)
+                    error = True
                 else:
-                    if current_leaderboard_config_ids and int(data["id"]) not in current_leaderboard_config_ids:
-                        error_messages.append("ERROR: Leaderboard {} doesn't exist. Addition of new leaderboard after challenge creation is not allowed.".format(data["id"]))
-                    leaderboard_ids.append(data["id"])
-    else:
-        message = "ERROR: There is no key leaderboard in the YAML file."
-        error_messages.append(message)
+                    if "labels" not in data["schema"]:
+                        message = self.error_messages_dict.get(
+                            "missing_leaderboard_labels"
+                        ).format(data.get("id"))
+                        self.error_messages.append(message)
+                        error = True
+                    if "default_order_by" not in data["schema"]:
+                        message = self.error_messages_dict.get(
+                            "missing_leaderboard_default_order_by"
+                        ).format(data.get("id"))
+                        self.error_messages.append(message)
+                        error = True
+                    else:
+                        default_order_by = data["schema"]["default_order_by"]
+                        if (
+                            "labels" in data["schema"]
+                            and default_order_by
+                            not in data["schema"]["labels"]
+                        ):
+                            message = self.error_messages_dict.get(
+                                "incorrect_default_order_by"
+                            ).format(default_order_by, data.get("id"))
+                            self.error_messages.append(message)
+                            error = True
+                if not error:
+                    serializer = LeaderboardSerializer(
+                        data=data, context={"config_id": data["id"]}
+                    )
+                    if not serializer.is_valid():
+                        serializer_error = str(serializer.errors)
+                        message = self.error_messages_dict.get(
+                            "leaderboard_schema_error"
+                        ).format(data["id"], serializer_error)
+                        self.error_messages.append(message)
+                    else:
+                        if (
+                            current_leaderboard_config_ids
+                            and int(data["id"])
+                            not in current_leaderboard_config_ids
+                        ):
+                            message = self.error_messages_dict.get(
+                                "leaderboard_additon_after_creation"
+                            ).format(data["id"])
+                            self.error_messages.append(message)
+                        self.leaderboard_ids.append(data["id"])
+        else:
+            message = self.error_messages_dict.get("missing_leaderboard_key")
+            self.error_messages.append(message)
 
-    for current_leaderboard_id in current_leaderboard_config_ids:
-        if current_leaderboard_id not in leaderboard_ids:
-            error_messages.append("ERROR: Leaderboard {} not found in config. Deletion of existing leaderboard after challenge creation is not allowed.".format(current_leaderboard_id))
+        for current_leaderboard_id in current_leaderboard_config_ids:
+            if current_leaderboard_id not in self.leaderboard_ids:
+                message = self.error_messages_dict.get(
+                    "leaderboard_deletion_after_creation"
+                ).format(current_leaderboard_id)
+                self.error_messages.append(message)
 
     # Check for challenge phases
-    challenge_phases_data = yaml_file_data.get("challenge_phases")
-    if not challenge_phases_data:
-        message = "ERROR: No challenge phase key found. Please add challenge phases in YAML file and try again!"
-        error_messages.append(message)
-        return error_messages, yaml_file_data, files
-
-    phase_ids = []
-    phase_codenames = []
-    files["challenge_test_annotation_files"] = []
-    for data in challenge_phases_data:
-        if data["codename"] not in phase_codenames:
-            phase_codenames.append(data["codename"])
-        else:
-            error_messages.append("ERROR: Duplicate codename {} for phase {}. Please ensure codenames are unique".format(data["codename"], data["name"]))
-        test_annotation_file = data.get("test_annotation_file")
-        if test_annotation_file:
-            test_annotation_file_path = join(
-                challenge_config_location, test_annotation_file
-            )
-            if isfile(test_annotation_file_path):
-                challenge_test_annotation_file = (
-                    read_file_data_as_content_file(
-                        test_annotation_file_path,
-                        "rb",
-                        test_annotation_file_path,
-                    )
+    def validate_challenge_phases(self, current_phase_config_ids):
+        challenge_phases_data = self.yaml_file_data.get("challenge_phases")
+        if not challenge_phases_data:
+            message = self.error_messages_dict["missing_challenge_phases"]
+            self.error_messages.append(message)
+            return self.error_messages, self.yaml_file_data, self.files
+
+        self.phase_ids = []
+        phase_codenames = []
+        self.files["challenge_test_annotation_files"] = []
+        for data in challenge_phases_data:
+            if "codename" not in data:
+                self.error_messages.append(
+                    self.error_messages_dict["no_codename_for_challenge_phase"]
                 )
-                files["challenge_test_annotation_files"].append(
-                    challenge_test_annotation_file
+            else:
+                if data["codename"] not in phase_codenames:
+                    phase_codenames.append(data["codename"])
+                else:
+                    message = self.error_messages_dict[
+                        "duplicate_codename_for_phase"
+                    ].format(data["codename"], data["name"])
+                    self.error_messages.append(message)
+            test_annotation_file = data.get("test_annotation_file")
+            if test_annotation_file:
+                test_annotation_file_path = join(
+                    self.challenge_config_location, test_annotation_file
                 )
+                if isfile(test_annotation_file_path):
+                    challenge_test_annotation_file = (
+                        read_file_data_as_content_file(
+                            test_annotation_file_path,
+                            "rb",
+                            test_annotation_file_path,
+                        )
+                    )
+                    self.files["challenge_test_annotation_files"].append(
+                        challenge_test_annotation_file
+                    )
+                else:
+                    message = self.error_messages_dict[
+                        "no_test_annotation_file_found"
+                    ].format(data["name"])
+                    self.error_messages.append(message)
             else:
-                message = (
-                    "ERROR: No test annotation file found in zip file"
-                    "for challenge phase {}".format(data["name"])
+                test_annotation_file_path = None
+                self.files["challenge_test_annotation_files"].append(None)
+
+            if data.get("max_submissions_per_month", None) is None:
+                data["max_submissions_per_month"] = data.get(
+                    "max_submissions", None
                 )
-                error_messages.append(message)
-        else:
-            files["challenge_test_annotation_files"].append(None)
 
-        if data.get("max_submissions_per_month", None) is None:
-            data["max_submissions_per_month"] = data.get(
-                "max_submissions", None
+            (
+                is_valid,
+                message,
+            ) = is_challenge_phase_config_yaml_html_field_valid(
+                data, "description", self.challenge_config_location
             )
+            if not is_valid:
+                self.error_messages.append(message)
+            else:
+                data["description"] = get_value_from_field(
+                    data, self.challenge_config_location, "description"
+                )
 
-        is_valid, message = is_challenge_phase_config_yaml_html_field_valid(
-            data, "description", challenge_config_location
-        )
-        if not is_valid:
-            error_messages.append(message)
-        else:
-            data["description"] = get_value_from_field(
-                data, challenge_config_location, "description"
-            )
+            if data.get("is_submission_public") and data.get(
+                "is_restricted_to_select_one_submission"
+            ):
+                message = self.error_messages_dict[
+                    "is_submission_public_restricted"
+                ].format(data["name"])
+                self.error_messages.append(message)
 
-        if data.get("is_submission_public") and data.get(
-            "is_restricted_to_select_one_submission"
-        ):
-            message = (
-                "ERROR: 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"])
-            )
-            error_messages.append(message)
-
-        # 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 = "ERROR: Please include at least one option in attribute for challenge phase {}".format(
-                                    data["id"]
-                                )
-                                error_messages.append(message)
+            start_date = data.get("start_date")
+            end_date = data.get("end_date")
+            if not start_date or not end_date:
+                message = self.error_messages_dict.get(
+                    "missing_dates_challenge_phase"
+                ).format(data.get("id"))
+                self.error_messages.append(message)
+            if start_date and end_date:
+                if start_date > end_date:
+                    message = self.error_messages_dict.get(
+                        "start_date_greater_than_end_date_challenge_phase"
+                    ).format(data.get("id"))
+                    self.error_messages.append(message)
+
+            # 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 = self.error_messages_dict[
+                                        "missing_option_in_submission_meta_attribute"
+                                    ].format(data["id"])
+                                    self.error_messages.append(message)
+                        else:
+                            message = self.error_messages_dict[
+                                "invalid_submission_meta_attribute_types"
+                            ].format(attribute_type, data["id"])
+                            self.error_messages.append(message)
                     else:
-                        message = (
-                            "ERROR: Please ensure that the submission meta attribute types for attribute in "
-                            "challenge phase {} are from among the following: boolean, text, radio or checkbox.".format(
-                                data["id"]
+                        missing_keys_string = ", ".join(missing_keys)
+                        message = self.error_messages_dict[
+                            "missing_fields_in_submission_meta_attribute"
+                        ].format(data["id"], missing_keys_string)
+                        self.error_messages.append(message)
+
+            if test_annotation_file_path is not None and isfile(
+                test_annotation_file_path
+            ):
+                serializer = ChallengePhaseCreateSerializer(
+                    data=data,
+                    context={
+                        "exclude_fields": ["challenge"],
+                        "test_annotation": challenge_test_annotation_file,
+                        "config_id": data["id"],
+                    },
+                )
+            else:
+                serializer = ChallengePhaseCreateSerializer(
+                    data=data,
+                    context={
+                        "exclude_fields": ["challenge"],
+                        "config_id": data["id"],
+                    },
+                )
+            if not serializer.is_valid():
+                serializer_error = str(serializer.errors)
+                message = self.error_messages_dict[
+                    "challenge_phase_schema_errors"
+                ].format(data["id"], serializer_error)
+                self.error_messages.append(message)
+            else:
+                if (
+                    current_phase_config_ids
+                    and int(data["id"]) not in current_phase_config_ids
+                ):
+                    message = self.error_messages_dict[
+                        "challenge_phase_addition"
+                    ].format(data["id"])
+                    self.error_messages.append(message)
+                self.phase_ids.append(data["id"])
+
+        for current_challenge_phase_id in current_phase_config_ids:
+            if current_challenge_phase_id not in self.phase_ids:
+                message = self.error_messages_dict[
+                    "challenge_phase_not_found"
+                ].format(current_challenge_phase_id)
+                self.error_messages.append(message)
+
+    def validate_challenge_phase_splits(self, current_phase_split_ids):
+        challenge_phase_splits = self.yaml_file_data.get(
+            "challenge_phase_splits"
+        )
+        challenge_phase_split_uuids = []
+        if challenge_phase_splits:
+            phase_split = 1
+            exclude_fields = [
+                "challenge_phase",
+                "dataset_split",
+                "leaderboard",
+            ]
+            for data in challenge_phase_splits:
+                expected_keys = {
+                    "is_leaderboard_order_descending",
+                    "leaderboard_decimal_precision",
+                    "visibility",
+                    "dataset_split_id",
+                    "leaderboard_id",
+                    "challenge_phase_id",
+                }
+                if expected_keys.issubset(data.keys()):
+                    if (
+                        current_phase_split_ids
+                        and (
+                            data["leaderboard_id"],
+                            data["challenge_phase_id"],
+                            data["dataset_split_id"],
+                        )
+                        not in current_phase_split_ids
+                    ):
+                        message = self.error_messages_dict[
+                            "challenge_phase_split_not_exist"
+                        ].format(
+                            data["leaderboard_id"],
+                            data["challenge_phase_id"],
+                            data["dataset_split_id"],
+                        )
+                        self.error_messages.append(message)
+                    else:
+                        challenge_phase_split_uuids.append(
+                            (
+                                data["leaderboard_id"],
+                                data["challenge_phase_id"],
+                                data["dataset_split_id"],
                             )
                         )
-                        error_messages.append(message)
+
+                    (
+                        is_mapping_valid,
+                        messages,
+                    ) = is_challenge_phase_split_mapping_valid(
+                        self.phase_ids,
+                        self.leaderboard_ids,
+                        self.dataset_splits_ids,
+                        data,
+                        phase_split,
+                    )
+                    self.error_messages += messages
+
+                    serializer = ZipChallengePhaseSplitSerializer(
+                        data=data, context={"exclude_fields": exclude_fields}
+                    )
+                    if not serializer.is_valid():
+                        serializer_error = str(serializer.errors)
+                        message = self.error_messages_dict[
+                            "challenge_phase_split_schema_errors"
+                        ].format(phase_split, serializer_error)
+                        self.error_messages.append(message)
+                    phase_split += 1
                 else:
+                    missing_keys = expected_keys - data.keys()
                     missing_keys_string = ", ".join(missing_keys)
-                    message = "ERROR: Please enter the following to the submission meta attribute in challenge phase {}: {}".format(
-                        data["id"], missing_keys_string
-                    )
-                    error_messages.append(message)
-        if isfile(test_annotation_file_path):
-            serializer = ChallengePhaseCreateSerializer(
-                data=data,
-                context={
-                    "exclude_fields": ["challenge"],
-                    "test_annotation": challenge_test_annotation_file,
-                    "config_id": data["id"],
-                },
-            )
-        else:
-            serializer = ChallengePhaseCreateSerializer(
-                data=data,
-                context={
-                    "exclude_fields": ["challenge"],
-                    "config_id": data["id"],
-                },
-            )
-        if not serializer.is_valid():
-            serializer_error = str(serializer.errors)
-            message = "ERROR: Challenge phase {} has following schema errors:\n {}".format(
-                data["id"], serializer_error
-            )
-            error_messages.append(message)
+                    message = self.error_messages_dict[
+                        "missing_keys_in_challenge_phase_splits"
+                    ].format(phase_split, missing_keys_string)
+                    self.error_messages.append(message)
+            for uuid in current_phase_split_ids:
+                if uuid not in challenge_phase_split_uuids:
+                    message = self.error_messages_dict[
+                        "challenge_phase_split_not_found"
+                    ].format(uuid[0], uuid[1], uuid[2])
+                    self.error_messages.append(message)
         else:
-            if current_phase_config_ids and int(data["id"]) not in current_phase_config_ids:
-                error_messages.append("ERROR: Challenge phase {} doesn't exist. Addition of new challenge phase after challenge creation is not allowed.".format(data["id"]))
-            phase_ids.append(data["id"])
-
-    for current_challenge_phase_id in current_phase_config_ids:
-        if current_challenge_phase_id not in phase_ids:
-            error_messages.append("ERROR: Challenge phase {} not found in config. Deletion of existing challenge phase after challenge creation is not allowed.".format(current_challenge_phase_id))
+            message = self.error_messages_dict[
+                "no_key_for_challenge_phase_splits"
+            ]
+            self.error_messages.append(message)
 
     # Check for dataset splits
-    dataset_splits = yaml_file_data.get("dataset_splits")
-    dataset_split_codenames = []
-    dataset_splits_ids = []
-    if dataset_splits:
-        for split in dataset_splits:
-            name = split.get("name")
-            if not name:
-                message = (
-                    "ERROR: There is no name for dataset split {}.".format(
-                        split.get("id")
-                    )
+    def validate_dataset_splits(self, current_dataset_config_ids):
+        dataset_splits = self.yaml_file_data.get("dataset_splits")
+        dataset_split_codenames = []
+        self.dataset_splits_ids = []
+        if dataset_splits:
+            for split in dataset_splits:
+                name = split.get("name")
+                if not name:
+                    message = self.error_messages_dict[
+                        "missing_dataset_split_name"
+                    ].format(split.get("id"))
+                    self.error_messages.append(message)
+                if "codename" not in split:
+                    message = self.error_messages_dict[
+                        "missing_dataset_split_codename"
+                    ].format(split.get("id"))
+                    self.error_messages.append(message)
+                else:
+                    if split["codename"] not in dataset_split_codenames:
+                        dataset_split_codenames.append(split["codename"])
+                    else:
+                        message = self.error_messages_dict[
+                            "duplicate_dataset_split_codename"
+                        ].format(split["codename"], split["name"])
+                        self.error_messages.append(message)
+            for split in dataset_splits:
+                serializer = DatasetSplitSerializer(
+                    data=split, context={"config_id": split["id"]}
                 )
-                error_messages.append(message)
-            if split["codename"] not in dataset_split_codenames:
-                dataset_split_codenames.append(split["codename"])
-            else:
-                error_messages.append("ERROR: Duplicate codename {} for dataset split {}. Please ensure codenames are unique".format(split["codename"], split["name"]))
-        for split in dataset_splits:
-            serializer = DatasetSplitSerializer(
-                data=split, context={"config_id": split["id"]}
+                if not serializer.is_valid():
+                    serializer_error = str(serializer.errors)
+                    message = self.error_messages_dict[
+                        "dataset_split_schema_errors"
+                    ].format(split["id"], serializer_error)
+                    self.error_messages.append(message)
+                else:
+                    if (
+                        current_dataset_config_ids
+                        and int(split["id"]) not in current_dataset_config_ids
+                    ):
+                        message = self.error_messages_dict[
+                            "dataset_split_addition"
+                        ].format(split["id"])
+                        self.error_messages.append(message)
+                    self.dataset_splits_ids.append(split["id"])
+        else:
+            message = self.error_messages_dict["missing_dataset_splits_key"]
+            self.error_messages.append(message)
+
+        for current_dataset_split_config_id in current_dataset_config_ids:
+            if current_dataset_split_config_id not in self.dataset_splits_ids:
+                message = self.error_messages_dict[
+                    "missing_existing_dataset_split_id"
+                ].format(current_dataset_split_config_id)
+                self.error_messages.append(message)
+
+
+def validate_challenge_config_util(
+    request,
+    challenge_host_team,
+    BASE_LOCATION,
+    unique_folder_name,
+    zip_ref,
+    current_challenge,
+):
+    """
+    Function to validate a challenge config
+
+    Arguments:
+        request {HttpRequest} -- The request object
+        BASE_LOCATION {str} -- The temp base directory for storing all the files and folders while validating the zip file
+        unique_folder_name {str} -- name of the challenge zip file and the parent dir of extracted folder
+        zip_ref {zipfile.ZipFile} -- reference to challenge config zip
+        current_challenge {apps.challenges.models.Challenge} - the existing challenge for the github repo, if any
+    """
+
+    val_config_util = ValidateChallengeConfigUtil(
+        request,
+        challenge_host_team,
+        BASE_LOCATION,
+        unique_folder_name,
+        zip_ref,
+        current_challenge,
+    )
+
+    # Read and validate YAML file
+    val_config_util.read_and_validate_yaml()
+
+    # # Validate challenge title
+    val_config_util.validate_challenge_title()
+
+    # Validate challenge logo
+    val_config_util.validate_challenge_logo()
+
+    # Validate challenge description
+    val_config_util.validate_challenge_description()
+
+    # Validate evaluation details
+    val_config_util.validate_evaluation_details_file()
+
+    # Validate terms and conditions
+    val_config_util.validate_terms_and_conditions_file()
+
+    # Validate submission guidelines
+    val_config_util.validate_submission_guidelines_file()
+
+    # Validate evaluation script
+    val_config_util.validate_evaluation_script_file()
+
+    val_config_util.validate_dates()
+
+    val_config_util.validate_serializer()
+
+    # Get existing config IDs for leaderboards and dataset splits
+    if current_challenge:
+        current_challenge_phases = ChallengePhase.objects.filter(
+            challenge=current_challenge.id
+        )
+        current_challenge_phase_splits = ChallengePhaseSplit.objects.filter(
+            challenge_phase__in=current_challenge_phases
+        )
+        current_leaderboards = Leaderboard.objects.filter(
+            id__in=current_challenge_phase_splits.values("leaderboard")
+        )
+        current_dataset_splits = DatasetSplit.objects.filter(
+            id__in=current_challenge_phase_splits.values("dataset_split")
+        )
+
+        current_leaderboard_config_ids = [
+            int(x.config_id) for x in current_leaderboards
+        ]
+        current_dataset_config_ids = [
+            int(x.config_id) for x in current_dataset_splits
+        ]
+        current_phase_config_ids = [
+            int(x.config_id) for x in current_challenge_phases
+        ]
+        current_phase_split_ids = [
+            (
+                split.leaderboard.config_id,
+                split.challenge_phase.config_id,
+                split.dataset_split.config_id,
             )
-            if not serializer.is_valid():
-                serializer_error = str(serializer.errors)
-                message = "ERROR: Dataset split {} has following schema errors:\n {}".format(
-                    split["id"], serializer_error
-                )
-                error_messages.append(message)
-            else:
-                if current_dataset_config_ids and int(split["id"]) not in current_dataset_config_ids:
-                    error_messages.append("ERROR: Dataset split {} doesn't exist. Addition of new dataset split after challenge creation is not allowed.".format(split["id"]))
-                dataset_splits_ids.append(split["id"])
+            for split in current_challenge_phase_splits
+        ]
     else:
-        message = "ERROR: There is no key for dataset splits."
-        error_messages.append(message)
-
-    for current_dataset_split_config_id in current_dataset_config_ids:
-        if current_dataset_split_config_id not in dataset_splits_ids:
-            error_messages.append("ERROR: Dataset split {} not found in config. Deletion of existing dataset split after challenge creation is not allowed.".format(current_dataset_split_config_id))
-
-    # Check for challenge phase splits
-    challenge_phase_splits = yaml_file_data.get("challenge_phase_splits")
-    challenge_phase_split_uuids = []
-    if challenge_phase_splits:
-        phase_split = 1
-        exclude_fields = ["challenge_phase", "dataset_split", "leaderboard"]
-        for data in challenge_phase_splits:
-            if current_phase_split_ids and (data["leaderboard_id"], data["challenge_phase_id"], data["dataset_split_id"]) not in current_phase_split_ids:
-                error_messages.append("ERROR: Challenge phase split (leaderboard_id: {}, challenge_phase_id: {}, dataset_split_id: {}) doesn't exist. Addition of challenge phase split after challenge creation is not allowed.".format(data["leaderboard_id"], data["challenge_phase_id"], data["dataset_split_id"]))
-            else:
-                challenge_phase_split_uuids.append((data["leaderboard_id"], data["challenge_phase_id"], data["dataset_split_id"]))
+        current_leaderboard_config_ids = []
+        current_dataset_config_ids = []
+        current_phase_config_ids = []
+        current_phase_split_ids = []
 
-            is_mapping_valid, messages = is_challenge_phase_split_mapping_valid(
-                phase_ids, leaderboard_ids, dataset_splits_ids, data, phase_split
-            )
-            error_messages += messages
+    # Validate leaderboards
+    val_config_util.validate_leaderboards(current_leaderboard_config_ids)
 
-            serializer = ZipChallengePhaseSplitSerializer(
-                data=data, context={"exclude_fields": exclude_fields}
-            )
-            if not serializer.is_valid():
-                serializer_error = str(serializer.errors)
-                message = "ERROR: Challenege phase split {} has following schema errors:\n {}".format(
-                    phase_split, serializer_error
-                )
-                error_messages.append(message)
-            phase_split += 1
+    # Validate challenge phases
+    val_config_util.validate_challenge_phases(current_phase_config_ids)
 
-        for uuid in current_phase_split_ids:
-            if uuid not in challenge_phase_split_uuids:
-                error_messages.append("ERROR: Challenge phase split (leaderboard_id: {}, challenge_phase_id: {}, dataset_split_id: {}) not found in config. Deletion of existing challenge phase split after challenge creation is not allowed.".format(uuid[0], uuid[1], uuid[2]))
-    else:
-        message = "ERROR: There is no key for challenge phase splits."
-        error_messages.append(message)
+    # Validate dataset splits
+    val_config_util.validate_dataset_splits(current_dataset_config_ids)
+
+    # Validate challenge phase splits
+    val_config_util.validate_challenge_phase_splits(current_phase_split_ids)
 
-    return error_messages, yaml_file_data, files
+    return (
+        val_config_util.error_messages,
+        val_config_util.yaml_file_data,
+        val_config_util.files,
+    )


Test patch: