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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions src/apps/api/serializers/competitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from api.serializers.submissions import SubmissionScoreSerializer
from api.serializers.tasks import PhaseTaskInstanceSerializer
from competitions.models import Competition, Phase, Page, CompetitionCreationTaskStatus, CompetitionParticipant, CompetitionWhiteListEmail
from datasets.access import user_can_access_competition_phase_resource
from forums.models import Forum
from leaderboards.models import Leaderboard
from profiles.models import User
Expand Down Expand Up @@ -104,8 +105,8 @@ def validate_leaderboard(self, value):
class PhaseDetailSerializer(serializers.ModelSerializer):
tasks = PhaseTaskInstanceSerializer(source='task_instances', many=True)
status = serializers.SerializerMethodField()
public_data = DataDetailSerializer(read_only=True)
starting_kit = DataDetailSerializer(read_only=True)
public_data = serializers.SerializerMethodField()
starting_kit = serializers.SerializerMethodField()
used_submissions_per_day = serializers.SerializerMethodField()
used_submissions_per_person = serializers.SerializerMethodField()

Expand Down Expand Up @@ -201,6 +202,20 @@ def get_used_submissions_per_person(self, obj):
return total_submission_count
return 0

def get_public_data(self, obj):
request = self.context.get("request")
user = getattr(request, "user", None)
if obj.public_data and user_can_access_competition_phase_resource(user, obj):
return DataDetailSerializer(obj.public_data).data
return None

def get_starting_kit(self, obj):
request = self.context.get("request")
user = getattr(request, "user", None)
if obj.starting_kit and user_can_access_competition_phase_resource(user, obj):
return DataDetailSerializer(obj.starting_kit).data
return None


class PhaseUpdateSerializer(PhaseSerializer):
tasks = PhaseTaskInstanceSerializer(source='task_instances', many=True)
Expand Down
41 changes: 21 additions & 20 deletions src/apps/api/serializers/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from api.mixins import DefaultUserCreateMixin
from api.serializers.datasets import DataDetailSerializer, DataSimpleSerializer
from competitions.models import PhaseTaskInstance, Phase
from datasets.access import user_can_access_task_dataset, user_can_access_task_solution
from datasets.models import Data
from tasks.models import Task, Solution
from competitions.models import Competition
Expand Down Expand Up @@ -212,31 +213,31 @@ class Meta:

def get_solutions(self, instance):
qs = instance.task.solutions.all()
request = self.context.get("request")
user = getattr(request, "user", None)
qs = [
solution for solution in qs
if user_can_access_task_solution(user, instance.phase, solution)
]
return SolutionSerializer(qs, many=True).data

def get_public_datasets(self, instance):
request = self.context.get("request")
user = getattr(request, "user", None)

datasets = [
instance.task.input_data,
instance.task.reference_data,
instance.task.ingestion_program,
instance.task.scoring_program,
]
datasets = [
dataset for dataset in datasets
if user_can_access_task_dataset(user, instance.phase, dataset)
]

input_data = instance.task.input_data
reference_data = instance.task.reference_data
ingestion_program = instance.task.ingestion_program
scoring_program = instance.task.scoring_program

# Some tasks may not have input data, reference data and ingestion program
# Checking all the datasets and programs and adding them to dataset_list_ids
dataset_list_ids = []
if input_data:
dataset_list_ids.append(input_data.id)
if reference_data:
dataset_list_ids.append(reference_data.id)
if ingestion_program:
dataset_list_ids.append(ingestion_program.id)
if scoring_program:
dataset_list_ids.append(scoring_program.id)

# Serializing the datasets
try:
qs = Data.objects.filter(id__in=dataset_list_ids)
return DataDetailSerializer(qs, many=True).data
return DataDetailSerializer(datasets, many=True).data
except Exception:
# No datasets or programs to return
return []
144 changes: 141 additions & 3 deletions src/apps/api/tests/test_competitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
from zipfile import ZipFile
from io import StringIO, BytesIO
from unittest import mock
from django.contrib.auth.models import AnonymousUser
from django.urls import reverse
from rest_framework.test import APITestCase
from rest_framework.test import APITestCase, APIRequestFactory

from api.serializers.competitions import CompetitionSerializer
from api.serializers.competitions import CompetitionSerializer, PhaseDetailSerializer
from api.serializers.tasks import PhaseTaskInstanceSerializer
from competitions.models import CompetitionParticipant, Submission, Competition
from factories import UserFactory, CompetitionFactory, CompetitionParticipantFactory, PhaseFactory, LeaderboardFactory, \
ColumnFactory, SubmissionFactory, SubmissionScoreFactory, TaskFactory
ColumnFactory, SubmissionFactory, SubmissionScoreFactory, TaskFactory, DataFactory, SolutionFactory
from datasets.models import Data


class CompetitionTests(APITestCase):
Expand Down Expand Up @@ -347,3 +350,138 @@ def test_competition_fact_sheet_bad_question_type(self):
}
competition_serializer = CompetitionSerializer(data=new_comp_data)
assert not competition_serializer.is_valid()


class CompetitionTaskDatasetVisibilityTests(APITestCase):
def setUp(self):
self.factory = APIRequestFactory()
self.creator = UserFactory(username="creator-datasets")
self.organizer = UserFactory(username="organizer-datasets")
self.participant = UserFactory(username="participant-datasets")

self.competition = CompetitionFactory(
created_by=self.creator,
logo=None,
published=True,
make_input_data_available=True,
make_programs_available=True,
)
self.competition.collaborators.add(self.organizer)

CompetitionParticipantFactory(
user=self.participant,
competition=self.competition,
status=CompetitionParticipant.APPROVED,
)

hidden_input = DataFactory(created_by=self.creator, type=Data.INPUT_DATA)
hidden_reference = DataFactory(created_by=self.creator, type=Data.REFERENCE_DATA)
hidden_ingestion = DataFactory(created_by=self.creator, type=Data.INGESTION_PROGRAM)
hidden_scoring = DataFactory(created_by=self.creator, type=Data.SCORING_PROGRAM)
hidden_public_data = DataFactory(created_by=self.creator, type=Data.PUBLIC_DATA)
hidden_starting_kit = DataFactory(created_by=self.creator, type=Data.STARTING_KIT)
hidden_solution_data = DataFactory(created_by=self.creator, type=Data.SOLUTION)
visible_input = DataFactory(created_by=self.creator, type=Data.INPUT_DATA)
visible_reference = DataFactory(created_by=self.creator, type=Data.REFERENCE_DATA)
visible_ingestion = DataFactory(created_by=self.creator, type=Data.INGESTION_PROGRAM)
visible_scoring = DataFactory(created_by=self.creator, type=Data.SCORING_PROGRAM)
visible_public_data = DataFactory(created_by=self.creator, type=Data.PUBLIC_DATA)
visible_starting_kit = DataFactory(created_by=self.creator, type=Data.STARTING_KIT)
visible_solution_data = DataFactory(created_by=self.creator, type=Data.SOLUTION)

hidden_solution = SolutionFactory(data=hidden_solution_data)
visible_solution = SolutionFactory(data=visible_solution_data)

self.hidden_task = TaskFactory(
created_by=self.creator,
input_data=hidden_input,
reference_data=hidden_reference,
ingestion_program=hidden_ingestion,
scoring_program=hidden_scoring,
solutions=[hidden_solution],
)
self.visible_task = TaskFactory(
created_by=self.creator,
input_data=visible_input,
reference_data=visible_reference,
ingestion_program=visible_ingestion,
scoring_program=visible_scoring,
solutions=[visible_solution],
)

self.hidden_phase = PhaseFactory(
competition=self.competition,
leaderboard=LeaderboardFactory(hidden=True),
hide_output=True,
index=0,
public_data=hidden_public_data,
starting_kit=hidden_starting_kit,
tasks=[self.hidden_task],
)
self.visible_phase = PhaseFactory(
competition=self.competition,
leaderboard=LeaderboardFactory(hidden=False),
index=1,
public_data=visible_public_data,
starting_kit=visible_starting_kit,
tasks=[self.visible_task],
)

self.hidden_task_instance = self.hidden_phase.task_instances.get(task=self.hidden_task)
self.visible_task_instance = self.visible_phase.task_instances.get(task=self.visible_task)

def _get_public_dataset_types(self, task_instance, user=None):
request = self.factory.get("/")
request.user = user or AnonymousUser()
serializer = PhaseTaskInstanceSerializer(task_instance, context={"request": request})
return {dataset["type"] for dataset in serializer.data["public_datasets"]}

def _get_solution_names(self, task_instance, user=None):
request = self.factory.get("/")
request.user = user or AnonymousUser()
serializer = PhaseTaskInstanceSerializer(task_instance, context={"request": request})
return {solution["name"] for solution in serializer.data["solutions"]}

def _serialize_phase(self, phase, user=None):
request = self.factory.get("/")
request.user = user or AnonymousUser()
return PhaseDetailSerializer(phase, context={"request": request}).data

def test_anonymous_users_do_not_receive_hidden_phase_task_datasets(self):
self.assertEqual(self._get_public_dataset_types(self.hidden_task_instance), set())

def test_approved_participants_do_not_receive_hidden_phase_task_datasets(self):
self.assertEqual(self._get_public_dataset_types(self.hidden_task_instance, self.participant), set())

def test_approved_participants_only_receive_allowed_visible_phase_task_datasets(self):
self.assertEqual(
self._get_public_dataset_types(self.visible_task_instance, self.participant),
{Data.INPUT_DATA, Data.INGESTION_PROGRAM, Data.SCORING_PROGRAM},
)

def test_organizers_receive_hidden_phase_task_datasets(self):
self.assertEqual(
self._get_public_dataset_types(self.hidden_task_instance, self.organizer),
{Data.INPUT_DATA, Data.REFERENCE_DATA, Data.INGESTION_PROGRAM, Data.SCORING_PROGRAM},
)

def test_approved_participants_do_not_receive_hidden_phase_solutions(self):
self.assertEqual(self._get_solution_names(self.hidden_task_instance, self.participant), set())

def test_approved_participants_receive_visible_phase_solutions(self):
self.assertEqual(len(self._get_solution_names(self.visible_task_instance, self.participant)), 1)

def test_approved_participants_do_not_receive_hidden_phase_assets(self):
phase_data = self._serialize_phase(self.hidden_phase, self.participant)
self.assertIsNone(phase_data["public_data"])
self.assertIsNone(phase_data["starting_kit"])

def test_approved_participants_receive_visible_phase_assets(self):
phase_data = self._serialize_phase(self.visible_phase, self.participant)
self.assertEqual(phase_data["public_data"]["type"], Data.PUBLIC_DATA)
self.assertEqual(phase_data["starting_kit"]["type"], Data.STARTING_KIT)

def test_organizers_receive_hidden_phase_assets(self):
phase_data = self._serialize_phase(self.hidden_phase, self.organizer)
self.assertEqual(phase_data["public_data"]["type"], Data.PUBLIC_DATA)
self.assertEqual(phase_data["starting_kit"]["type"], Data.STARTING_KIT)
Loading