Repository: Cloud-CV/EvalAI

Issue:
Add API endpoint to create challenge by uploading zip file.
endpoint: `/challenges/zip/$`
method: `POST`
success: `200/OK`
failure: `400/bad request` or `406/not acceptable`

Input: The user must upload a zip file containing the `.yaml` file which describes the challenge configuration. The zip folder will also contain other necessary files which are required to create a challenge.

Output: Upon requesting at this endpoint , a challenge must be created by the user. 


Discussion:

Resolving patch:
diff --git a/apps/challenges/urls.py b/apps/challenges/urls.py
--- a/apps/challenges/urls.py
+++ b/apps/challenges/urls.py
@@ -25,6 +25,9 @@
         name='get_challenges_based_on_teams'),
     url(r'(?P<challenge_pk>[0-9]+)/challenge_phase_split$', views.challenge_phase_split_list,
         name='challenge_phase_split_list'),
+    url(r'challenge/challenge_host_team/(?P<challenge_host_team_pk>[0-9]+)/zip_upload/$',
+        views.create_challenge_using_zip_file,
+        name='create_challenge_using_zip_file'),
     url(r'(?P<challenge_pk>[0-9]+)/challenge_phase/(?P<challenge_phase_pk>[0-9]+)/submissions$',
         views.get_all_submissions_of_challenge,
         name='get_all_submissions_of_challenge'),
diff --git a/apps/challenges/views.py b/apps/challenges/views.py
--- a/apps/challenges/views.py
+++ b/apps/challenges/views.py
@@ -1,3 +1,16 @@
+import logging
+import random
+import requests
+import shutil
+import string
+import tempfile
+import yaml
+import zipfile
+
+from os.path import basename, isfile, join
+
+from django.core.files.base import ContentFile
+from django.db import transaction
 from django.utils import timezone
 
 from rest_framework import permissions, status
@@ -13,7 +26,7 @@
 from base.utils import paginated_queryset
 from challenges.utils import get_challenge_model
 from hosts.models import ChallengeHost, ChallengeHostTeam
-from hosts.utils import get_challenge_host_teams_for_user, is_user_a_host_of_challenge
+from hosts.utils import get_challenge_host_teams_for_user, is_user_a_host_of_challenge, get_challenge_host_team_model
 from jobs.models import Submission
 from jobs.serializers import SubmissionSerializer, ChallengeSubmissionManagementSerializer
 from participants.models import Participant, ParticipantTeam
@@ -21,9 +34,19 @@
                                 has_user_participated_in_challenge,
                                 get_participant_team_id_of_user_for_a_challenge,)
 
-from .models import Challenge, ChallengePhase, ChallengePhaseSplit
+from .models import Challenge, ChallengePhase, ChallengePhaseSplit, ChallengeConfiguration
 from .permissions import IsChallengeCreator
-from .serializers import ChallengeSerializer, ChallengePhaseSerializer, ChallengePhaseSplitSerializer
+from .serializers import (ChallengeConfigSerializer,
+                          ChallengePhaseSerializer,
+                          ChallengePhaseSplitSerializer,
+                          ChallengeSerializer,
+                          DatasetSplitSerializer,
+                          LeaderboardSerializer,
+                          ZipChallengeSerializer,
+                          ZipChallengePhaseSplitSerializer,)
+from .utils import get_file_content
+
+logger = logging.getLogger(__name__)
 
 
 @throttle_classes([UserRateThrottle])
@@ -346,6 +369,318 @@ def challenge_phase_split_list(request, challenge_pk):
     return paginator.get_paginated_response(response_data)
 
 
+@throttle_classes([UserRateThrottle])
+@api_view(['POST'])
+@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
+@authentication_classes((ExpiringTokenAuthentication,))
+def create_challenge_using_zip_file(request, challenge_host_team_pk):
+    """
+    Creates a challenge using a zip file.
+    """
+    challenge_host_team = get_challenge_host_team_model(challenge_host_team_pk)
+
+    serializer = ChallengeConfigSerializer(data=request.data, context={'request': request})
+    if serializer.is_valid():
+        uploaded_zip_file = serializer.save()
+        uploaded_zip_file_path = serializer.data['zip_configuration']
+    else:
+        response_data = serializer.errors
+        return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
+
+    # All files download and extract location.
+    BASE_LOCATION = tempfile.mkdtemp()
+    try:
+        response = requests.get(uploaded_zip_file_path, stream=True)
+        unique_folder_name = ''.join([random.choice(string.ascii_letters + string.digits) for i in xrange(10)])
+        CHALLENGE_ZIP_DOWNLOAD_LOCATION = join(BASE_LOCATION, '{}.zip'.format(unique_folder_name))
+        try:
+            if response and response.status_code == 200:
+                with open(CHALLENGE_ZIP_DOWNLOAD_LOCATION, 'w') as zip_file:
+                    zip_file.write(response.content)
+        except IOError:
+            response_data = {
+                'error': 'Unable to process the uploaded zip file. Please upload it again!'
+                }
+            return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
+
+    except requests.exceptions.RequestException:
+        response_data = {
+            'error': 'A server error occured while processing zip file. Please try uploading it again!'
+            }
+        return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
+
+    # Extract zip file
+    try:
+        zip_ref = zipfile.ZipFile(CHALLENGE_ZIP_DOWNLOAD_LOCATION, 'r')
+        zip_ref.extractall(join(BASE_LOCATION, unique_folder_name))
+        zip_ref.close()
+    except zipfile.BadZipfile:
+        response_data = {
+            'error': 'The zip file contents cannot be extracted. Please check the format!'
+            }
+        return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
+
+    # Search for yaml file
+    yaml_file_count = 0
+    for name in zip_ref.namelist():
+        if name.endswith('.yaml') or name.endswith('.yml'):
+            yaml_file = name
+            extracted_folder_name = yaml_file.split(basename(yaml_file))[0]
+            yaml_file_count += 1
+
+    if not yaml_file_count:
+        response_data = {
+            'error': 'There is no YAML file in zip configuration you provided!'
+            }
+        return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
+
+    if yaml_file_count > 1:
+        response_data = {
+            'error': 'There are more than one YAML files in zip folder!'
+            }
+        return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
+
+    try:
+        with open(join(BASE_LOCATION, unique_folder_name, yaml_file), "r") as stream:
+            yaml_file_data = yaml.load(stream)
+    except yaml.YAMLError as exc:
+        response_data = {
+            'error': exc
+            }
+        return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
+
+    # Check for evaluation script path in yaml file.
+    evaluation_script = yaml_file_data['evaluation_script']
+    if evaluation_script:
+        evaluation_script_path = join(BASE_LOCATION,
+                                      unique_folder_name,
+                                      extracted_folder_name,
+                                      evaluation_script)
+    else:
+        response_data = {
+            'error': ('There is no key for evaluation script in yaml file.'
+                      'Please add a key and then try uploading it again!')
+            }
+        return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
+
+    # Check for evaluation script file in extracted zip folder.
+    if isfile(evaluation_script_path):
+        with open(evaluation_script_path, 'rb') as challenge_evaluation_script:
+            challenge_evaluation_script_file = ContentFile(challenge_evaluation_script.read(), evaluation_script_path)
+    else:
+        response_data = {
+            'error': ('No evaluation script is present in the zip file.'
+                      'Please try uploading again the zip file after adding evaluation script!')
+            }
+        return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
+
+    # Check for test annotation file path in yaml file.
+    yaml_file_data_of_challenge_phases = yaml_file_data['challenge_phases']
+    for data in yaml_file_data_of_challenge_phases:
+        test_annotation_file = data['test_annotation_file']
+        if test_annotation_file:
+            test_annotation_file_path = join(BASE_LOCATION,
+                                             unique_folder_name,
+                                             extracted_folder_name,
+                                             test_annotation_file)
+        else:
+            response_data = {
+                'error': ('There is no key for test annotation file for'
+                          'challenge phase {} in yaml file.'
+                          'Please add a key and then try uploading it again!'.format(data['name']))
+                }
+            return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
+
+        if not isfile(test_annotation_file_path):
+            response_data = {
+                'error': ('No test annotation file is present in the zip file'
+                          'for challenge phase {} Please try uploading'
+                          'again the zip file after adding test annotation file!'.format(data['name']))
+            }
+            return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
+
+    # Check for challenge image in yaml file.
+    image = yaml_file_data['image']
+    if image.endswith('.jpg') or image.endswith('.jpeg') or image.endswith('.png'):
+        challenge_image_path = join(BASE_LOCATION,
+                                    unique_folder_name,
+                                    extracted_folder_name,
+                                    image)
+        if isfile(challenge_image_path):
+            challenge_image_file = ContentFile(get_file_content(challenge_image_path, 'rb'), image)
+        else:
+            challenge_image_file = None
+    else:
+        challenge_image_file = None
+
+    # check for challenge description file
+    challenge_description_file_path = join(BASE_LOCATION,
+                                           unique_folder_name,
+                                           extracted_folder_name,
+                                           yaml_file_data['description'])
+    if challenge_description_file_path.endswith('.html') and isfile(challenge_description_file_path):
+        yaml_file_data['description'] = get_file_content(challenge_description_file_path, 'rb').decode('utf-8')
+    else:
+        yaml_file_data['description'] = None
+
+    # check for evaluation details file
+    challenge_evaluation_details_file_path = join(BASE_LOCATION,
+                                                  unique_folder_name,
+                                                  extracted_folder_name,
+                                                  yaml_file_data['evaluation_details'])
+
+    if (challenge_evaluation_details_file_path.endswith('.html') and
+            isfile(challenge_evaluation_details_file_path)):
+        yaml_file_data['evaluation_details'] = get_file_content(challenge_evaluation_details_file_path,
+                                                                'rb').decode('utf-8')
+    else:
+        yaml_file_data['evaluation_details'] = None
+
+    # check for terms and conditions file
+    challenge_terms_and_cond_file_path = join(BASE_LOCATION,
+                                              unique_folder_name,
+                                              extracted_folder_name,
+                                              yaml_file_data['terms_and_conditions'])
+    if challenge_terms_and_cond_file_path.endswith('.html') and isfile(challenge_terms_and_cond_file_path):
+        yaml_file_data['terms_and_conditions'] = get_file_content(challenge_terms_and_cond_file_path,
+                                                                  'rb').decode('utf-8')
+    else:
+        yaml_file_data['terms_and_conditions'] = None
+
+    # check for submission guidelines file
+    challenge_submission_guidelines_file_path = join(BASE_LOCATION,
+                                                     unique_folder_name,
+                                                     extracted_folder_name,
+                                                     yaml_file_data['submission_guidelines'])
+    if (challenge_submission_guidelines_file_path.endswith('.html')
+            and isfile(challenge_submission_guidelines_file_path)):
+        yaml_file_data['submission_guidelines'] = get_file_content(challenge_submission_guidelines_file_path,
+                                                                   'rb').decode('utf-8')
+    else:
+        yaml_file_data['submission_guidelines'] = None
+
+    try:
+        with transaction.atomic():
+            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})
+            if serializer.is_valid():
+                serializer.save()
+                challenge = serializer.instance
+            else:
+                response_data = serializer.errors
+
+            # Create Leaderboard
+            yaml_file_data_of_leaderboard = yaml_file_data['leaderboard']
+            leaderboard_ids = {}
+            for data in yaml_file_data_of_leaderboard:
+                serializer = LeaderboardSerializer(data=data)
+                if serializer.is_valid():
+                    serializer.save()
+                    leaderboard_ids[str(data['id'])] = serializer.instance.pk
+                else:
+                    response_data = serializer.errors
+
+            # Create Challenge Phase
+            yaml_file_data_of_challenge_phases = yaml_file_data['challenge_phases']
+            challenge_phase_ids = {}
+            for data in yaml_file_data_of_challenge_phases:
+                # Check for challenge phase description file
+                challenge_phase_description_file_path = join(BASE_LOCATION,
+                                                             unique_folder_name,
+                                                             extracted_folder_name,
+                                                             data['description'])
+                if (challenge_phase_description_file_path.endswith('.html')
+                        and isfile(challenge_phase_description_file_path)):
+                    data['description'] = get_file_content(challenge_phase_description_file_path, 'rb').decode('utf-8')
+                else:
+                    data['description'] = None
+
+                test_annotation_file = data['test_annotation_file']
+                if test_annotation_file:
+                    test_annotation_file_path = join(BASE_LOCATION,
+                                                     unique_folder_name,
+                                                     extracted_folder_name,
+                                                     test_annotation_file
+                                                     )
+                if isfile(test_annotation_file_path):
+                    with open(test_annotation_file_path, 'rb') as test_annotation_file:
+                        challenge_test_annotation_file = ContentFile(test_annotation_file.read(),
+                                                                     test_annotation_file_path)
+                serializer = ChallengePhaseSerializer(data=data,
+                                                      context={'challenge': challenge,
+                                                               'test_annotation': challenge_test_annotation_file})
+                if serializer.is_valid():
+                    serializer.save()
+                    challenge_phase_ids[str(data['id'])] = serializer.instance.pk
+                else:
+                    response_data = serializer.errors
+
+            # Create Dataset Splits
+            yaml_file_data_of_dataset_split = yaml_file_data['dataset_splits']
+            dataset_split_ids = {}
+            for data in yaml_file_data_of_dataset_split:
+                serializer = DatasetSplitSerializer(data=data)
+                if serializer.is_valid():
+                    serializer.save()
+                    dataset_split_ids[str(data['id'])] = serializer.instance.pk
+                else:
+                    # Return error when dataset split name is not unique.
+                    response_data = serializer.errors
+
+            # Create Challenge Phase Splits
+            yaml_file_data_of_challenge_phase_splits = yaml_file_data['challenge_phase_splits']
+            for data in yaml_file_data_of_challenge_phase_splits:
+                challenge_phase = challenge_phase_ids[str(data['challenge_phase_id'])]
+                leaderboard = leaderboard_ids[str(data['leaderboard_id'])]
+                dataset_split = dataset_split_ids[str(data['dataset_split_id'])]
+                visibility = data['visibility']
+
+                data = {
+                    'challenge_phase': challenge_phase,
+                    'leaderboard': leaderboard,
+                    'dataset_split': dataset_split,
+                    'visibility': visibility
+                }
+
+                serializer = ZipChallengePhaseSplitSerializer(data=data)
+                if serializer.is_valid():
+                    serializer.save()
+                else:
+                    response_data = serializer.errors
+
+        zip_config = ChallengeConfiguration.objects.get(pk=uploaded_zip_file.pk)
+        if zip_config:
+            zip_config.challenge = challenge
+            zip_config.save()
+            response_data = {'success': 'Challenge {} is successfully created'.format(challenge.pk)}
+            return Response(response_data, status=status.HTTP_201_CREATED)
+
+    except:
+        try:
+            if response_data:
+                response_data = {'error': response_data.values()[0]}
+                return Response(response_data, status=status.HTTP_406_NOT_ACCEPTABLE)
+        except:
+            response_data = {'error': 'Error in creating challenge. Please check the yaml configuration!'}
+            return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
+        finally:
+            try:
+                shutil.rmtree(BASE_LOCATION)
+                logger.info('Zip folder is removed')
+            except:
+                logger.info('Zip folder for challenge {} is not removed from location'.format(challenge.pk,
+                                                                                              BASE_LOCATION))
+    try:
+        shutil.rmtree(BASE_LOCATION)
+        logger.info('Zip folder is removed')
+    except:
+        logger.info('Zip folder for challenge {} is not removed from location'.format(challenge.pk,
+                                                                                      BASE_LOCATION))
+
+
 @throttle_classes([UserRateThrottle])
 @api_view(['GET'])
 @permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))


Test patch:
diff --git a/examples/example1/test_zip_file.zip b/examples/example1/test_zip_file.zip
new file mode 100644
Binary files /dev/null and b/examples/example1/test_zip_file.zip differ
diff --git a/examples/example1/test_zip_file/challenge_phase_description.html b/examples/example1/test_zip_file/challenge_phase_description.html
new file mode 100644
--- /dev/null
+++ b/examples/example1/test_zip_file/challenge_phase_description.html
@@ -0,0 +1,3 @@
+<div class="row">
+        <p><span style="font-weight: 400;">Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</span></p>
+</div>
diff --git a/examples/example1/test_zip_file/description.html b/examples/example1/test_zip_file/description.html
new file mode 100644
--- /dev/null
+++ b/examples/example1/test_zip_file/description.html
@@ -0,0 +1,3 @@
+<div class="row">
+        <p><span style="font-weight: 400;">Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</span></p>
+</div>
diff --git a/examples/example1/test_zip_file/evaluation_details.html b/examples/example1/test_zip_file/evaluation_details.html
new file mode 100644
--- /dev/null
+++ b/examples/example1/test_zip_file/evaluation_details.html
@@ -0,0 +1,3 @@
+<div class="row">
+        <p><span style="font-weight: 400;">Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</span></p>
+</div>
diff --git a/examples/example1/test_zip_file/evaluation_script.zip b/examples/example1/test_zip_file/evaluation_script.zip
new file mode 100644
Binary files /dev/null and b/examples/example1/test_zip_file/evaluation_script.zip differ
diff --git a/examples/example1/test_zip_file/submission_guidelines.html b/examples/example1/test_zip_file/submission_guidelines.html
new file mode 100644
--- /dev/null
+++ b/examples/example1/test_zip_file/submission_guidelines.html
@@ -0,0 +1,3 @@
+<div class="row">
+        <p><span style="font-weight: 400;">Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</span></p>
+</div>
diff --git a/examples/example1/test_zip_file/terms_and_conditions.html b/examples/example1/test_zip_file/terms_and_conditions.html
new file mode 100644
--- /dev/null
+++ b/examples/example1/test_zip_file/terms_and_conditions.html
@@ -0,0 +1,3 @@
+<div class="row">
+        <p><span style="font-weight: 400;">Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</span></p>
+</div>
diff --git a/examples/example1/test_zip_file/test_annotation.txt b/examples/example1/test_zip_file/test_annotation.txt
new file mode 100644
--- /dev/null
+++ b/examples/example1/test_zip_file/test_annotation.txt
@@ -0,0 +1,10 @@
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
\ No newline at end of file
diff --git a/examples/example1/test_zip_file/zip_challenge.yaml b/examples/example1/test_zip_file/zip_challenge.yaml
new file mode 100644
--- /dev/null
+++ b/examples/example1/test_zip_file/zip_challenge.yaml
@@ -0,0 +1,38 @@
+title: Challenge Title
+short_description: Short description of the challenge (preferably 140 characters)
+description: description.html
+evaluation_details: evaluation_details.html
+terms_and_conditions: terms_and_conditions.html
+image : logo.jpg
+submission_guidelines: submission_guidelines.html
+evaluation_script: data.py
+start_date: 2017-06-09 20:00:00
+end_date: 2017-06-19 20:00:00
+
+leaderboard:
+  - id: 1
+    schema: {"labels": ["yes/no", "number", "others", "overall"], "default_order_by": "overall"}
+
+challenge_phases:
+  - id: 1
+    name: Challenge Name of the challenge phase
+    description: challenge_phase_description.html
+    leaderboard_public: True
+    is_public: True
+    start_date: 2017-06-09 20:00:00
+    end_date: 2017-06-19 20:00:00
+    test_annotation_file: test_annotation.txt
+    codename: Challenge phase codename
+    max_submissions_per_day: 100
+    max_submissions: 1000
+
+dataset_splits:
+  - id: 1
+    name: Name of the dataset split
+    codename: codename of dataset split
+
+challenge_phase_splits:
+  - challenge_phase_id: 1
+    leaderboard_id: 1
+    dataset_split_id: 1
+    visibility: 3
diff --git a/tests/unit/challenges/test_views.py b/tests/unit/challenges/test_views.py
--- a/tests/unit/challenges/test_views.py
+++ b/tests/unit/challenges/test_views.py
@@ -3,9 +3,11 @@
 import shutil
 
 from datetime import timedelta
+from os.path import join
 
-from django.core.urlresolvers import reverse_lazy
 from django.core.files.uploadedfile import SimpleUploadedFile
+from django.core.urlresolvers import reverse_lazy
+from django.conf import settings
 from django.contrib.auth.models import User
 from django.test import override_settings
 from django.utils import timezone
@@ -14,10 +16,15 @@
 from rest_framework import status
 from rest_framework.test import APITestCase, APIClient
 
-from challenges.models import Challenge, ChallengePhase, DatasetSplit, ChallengePhaseSplit, Leaderboard
+from challenges.models import (Challenge,
+                               ChallengeConfiguration,
+                               ChallengePhase,
+                               ChallengePhaseSplit,
+                               DatasetSplit,
+                               Leaderboard,)
+from participants.models import Participant, ParticipantTeam
 from hosts.models import ChallengeHost, ChallengeHostTeam
 from jobs.models import Submission
-from participants.models import Participant, ParticipantTeam
 
 
 class BaseAPITestClass(APITestCase):
@@ -1401,6 +1408,146 @@ def test_challenge_phase_split_when_challenge_phase_does_not_exist(self):
         self.assertEqual(response.status_code, status.HTTP_406_NOT_ACCEPTABLE)
 
 
+class CreateChallengeUsingZipFile(APITestCase):
+
+    def setUp(self):
+        self.client = APIClient(enforce_csrf_checks=True)
+
+        self.user = User.objects.create(
+            username='host',
+            email='host@test.com',
+            password='secret_password')
+
+        EmailAddress.objects.create(
+            user=self.user,
+            email='user@test.com',
+            primary=True,
+            verified=True)
+
+        self.challenge_host_team = ChallengeHostTeam.objects.create(
+            team_name='Test Challenge Host Team',
+            created_by=self.user)
+
+        self.path = join(settings.BASE_DIR, 'examples', 'example1', 'test_zip_file')
+
+        self.challenge = Challenge.objects.create(
+            title='Challenge Title',
+            short_description='Short description of the challenge (preferably 140 characters)',
+            description=open(join(self.path, 'description.html'), 'rb').read().decode('utf-8'),
+            terms_and_conditions=open(join(self.path, 'terms_and_conditions.html'), 'rb').read().decode('utf-8'),
+            submission_guidelines=open(join(self.path, 'submission_guidelines.html'), 'rb').read().decode('utf-8'),
+            evaluation_details=open(join(self.path, 'evaluation_details.html'), 'rb').read().decode('utf-8'),
+            creator=self.challenge_host_team,
+            published=False,
+            enable_forum=True,
+            anonymous_leaderboard=False,
+            start_date=timezone.now() - timedelta(days=2),
+            end_date=timezone.now() + timedelta(days=1),
+        )
+
+        with self.settings(MEDIA_ROOT='/tmp/evalai'):
+            self.challenge_phase = ChallengePhase.objects.create(
+                name='Challenge Phase',
+                description=open(join(self.path, 'challenge_phase_description.html'), 'rb').read().decode('utf-8'),
+                leaderboard_public=False,
+                is_public=False,
+                start_date=timezone.now() - timedelta(days=2),
+                end_date=timezone.now() + timedelta(days=1),
+                challenge=self.challenge,
+                test_annotation=SimpleUploadedFile(open(join(self.path, 'test_annotation.txt'), 'rb').name,
+                                                   open(join(self.path, 'test_annotation.txt'), 'rb').read(),
+                                                   content_type='text/plain')
+            )
+        self.dataset_split = DatasetSplit.objects.create(name="Name of the dataset split",
+                                                         codename="codename of dataset split")
+
+        self.leaderboard = Leaderboard.objects.create(schema=json.dumps({
+                                                      "labels": ["yes/no", "number", "others", "overall"],
+                                                      "default_order_by": "overall"}))
+
+        self.challenge_phase_split = ChallengePhaseSplit.objects.create(
+            dataset_split=self.dataset_split,
+            challenge_phase=self.challenge_phase,
+            leaderboard=self.leaderboard,
+            visibility=ChallengePhaseSplit.PUBLIC
+            )
+
+        self.zip_file = open(join(settings.BASE_DIR, 'examples', 'example1', 'test_zip_file.zip'), 'rb')
+
+        self.test_zip_file = SimpleUploadedFile(self.zip_file.name,
+                                                self.zip_file.read(),
+                                                content_type='application/zip')
+
+        self.zip_configuration = ChallengeConfiguration.objects.create(
+            user=self.user,
+            challenge=self.challenge,
+            zip_configuration=SimpleUploadedFile(self.zip_file.name,
+                                                 self.zip_file.read(),
+                                                 content_type='application/zip'),
+            stdout_file=None,
+            stderr_file=None
+            )
+        self.client.force_authenticate(user=self.user)
+
+        self.input_zip_file = SimpleUploadedFile('test_sample.zip',
+                                                 'Dummy File Content',
+                                                 content_type='application/zip')
+
+    def test_create_challenge_using_zip_file_when_zip_file_is_not_uploaded(self):
+        self.url = reverse_lazy('challenges:create_challenge_using_zip_file',
+                                kwargs={'challenge_host_team_pk': self.challenge_host_team.pk})
+        expected = {
+            'zip_configuration': ['No file was submitted.']
+        }
+        response = self.client.post(self.url, {})
+        self.assertEqual(response.data, expected)
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+    def test_create_challenge_using_zip_file_when_zip_file_is_not_uploaded_successfully(self):
+        self.url = reverse_lazy('challenges:create_challenge_using_zip_file',
+                                kwargs={'challenge_host_team_pk': self.challenge_host_team.pk})
+
+        expected = {
+            'zip_configuration': ['The submitted data was not a file. Check the encoding type on the form.']
+        }
+        response = self.client.post(self.url, {'zip_configuration': self.input_zip_file})
+        self.assertEqual(response.data, expected)
+        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+    def test_create_challenge_using_zip_file_when_server_error_occurs(self):
+        self.url = reverse_lazy('challenges:create_challenge_using_zip_file',
+                                kwargs={'challenge_host_team_pk': self.challenge_host_team.pk})
+        expected = {
+            'error': 'A server error occured while processing zip file. Please try uploading it again!'
+            }
+        response = self.client.post(self.url, {'zip_configuration': self.input_zip_file}, format='multipart')
+        self.assertEqual(response.data, expected)
+        self.assertEqual(response.status_code, status.HTTP_406_NOT_ACCEPTABLE)
+
+    def test_create_challenge_using_zip_file_when_challenge_host_team_does_not_exists(self):
+        self.url = reverse_lazy('challenges:create_challenge_using_zip_file',
+                                kwargs={'challenge_host_team_pk': self.challenge_host_team.pk+10})
+        expected = {
+            'detail': 'ChallengeHostTeam {} does not exist'.format(self.challenge_host_team.pk+10)
+        }
+        response = self.client.post(self.url, {'zip_configuration': self.input_zip_file}, format='multipart')
+        self.assertEqual(response.data, expected)
+        self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+
+    def test_create_challenge_using_zip_file_when_user_is_not_authenticated(self):
+        self.url = reverse_lazy('challenges:create_challenge_using_zip_file',
+                                kwargs={'challenge_host_team_pk': self.challenge_host_team.pk})
+        self.client.force_authenticate(user=None)
+
+        expected = {
+            'error': 'Authentication credentials were not provided.'
+        }
+
+        response = self.client.post(self.url, {})
+        self.assertEqual(response.data.values()[0], expected['error'])
+        self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
+
+
 class GetAllSubmissionsTest(BaseAPITestClass):
 
     def setUp(self):