From fa5215acdb226d893564b59449f18a7ed3ee12a7 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Sat, 3 Jun 2023 18:47:03 -0400 Subject: [PATCH 01/15] download_buttons progress 06_03_2023 --- src/apps/api/serializers/tasks.py | 28 ++++++++++ src/static/riot/competitions/detail/_tabs.tag | 54 ++++++++++++++++--- 2 files changed, 76 insertions(+), 6 deletions(-) diff --git a/src/apps/api/serializers/tasks.py b/src/apps/api/serializers/tasks.py index deac682cc..ee10602bc 100644 --- a/src/apps/api/serializers/tasks.py +++ b/src/apps/api/serializers/tasks.py @@ -1,3 +1,5 @@ +import pdb + from drf_writable_nested import WritableNestedModelSerializer from rest_framework import serializers from rest_framework.exceptions import ValidationError @@ -13,6 +15,7 @@ class SolutionSerializer(WritableNestedModelSerializer): tasks = serializers.SlugRelatedField(queryset=Task.objects.all(), required=False, allow_null=True, slug_field='key', many=True) data = serializers.SlugRelatedField(queryset=Data.objects.all(), required=False, allow_null=True, slug_field='key') + size = serializers.SerializerMethodField() class Meta: model = Solution @@ -23,7 +26,12 @@ class Meta: 'tasks', 'data', 'md5', + 'size', ] + + def get_size(self, instance): + print("line 31"); pdb.set_trace() # BB + return instance.data.file_size class SolutionListSerializer(serializers.ModelSerializer): @@ -159,6 +167,8 @@ class PhaseTaskInstanceSerializer(serializers.HyperlinkedModelSerializer): key = serializers.CharField(source='task.key', required=False) created_when = serializers.DateTimeField(source='task.created_when', required=False) name = serializers.CharField(source='task.name', required=False) + solutions = serializers.SerializerMethodField() + # print("line 159"); pdb.set_trace() # BB class Meta: model = PhaseTaskInstance @@ -172,4 +182,22 @@ class Meta: 'key', 'created_when', 'name', + 'solutions' + # BB Add public data + # BB Add starting kit ) + + def get_solutions(self, instance): + print("line 177"); pdb.set_trace() # BB + qs = instance.task.solutions.all() + return SolutionSerializer(qs, many=True).data + + # def get_public_datasets(self, instance): + # print("line 177"); pdb.set_trace() # BB + # qs = instance.task.solutions.all() + # return SolutionSerializer(qs, many=True).data + + # def get_starting_kits(self, instance): + # print("line 177"); pdb.set_trace() # BB + # qs = instance.task.solutions.all() + # return SolutionSerializer(qs, many=True).data \ No newline at end of file diff --git a/src/static/riot/competitions/detail/_tabs.tag b/src/static/riot/competitions/detail/_tabs.tag index 380ba377f..1924773cd 100644 --- a/src/static/riot/competitions/detail/_tabs.tag +++ b/src/static/riot/competitions/detail/_tabs.tag @@ -29,9 +29,11 @@ data-tab="_tab_page{page.index}"> { page.title } - +
Files -
--> + +
@@ -40,7 +42,8 @@
- +
@@ -66,7 +69,8 @@
-
--> + + @@ -192,13 +196,17 @@ CODALAB.events.on('competition_loaded', function (competition) { self.competition = competition self.competition.files = [] + debugger _.forEach(competition.phases, phase => { + debugger _.forEach(phase.tasks, task => { + debugger _.forEach(task.solutions, solution => { + debugger self.competition.files.push({ - key: solution.data.key, + key: solution.data, name: solution.name, - file_size: solution.data.file_size, + file_size: solution.size, phase: phase.name, task: task.name, }) @@ -206,6 +214,40 @@ }) }) + // loop over competition phases to mark if phase has started or ended + self.competition.phases.forEach(function (phase, index) { + + phase_ended = false + phase_started = false + + // check if phase has started + if((Date.parse(phase["start"]) - Date.parse(new Date())) > 0){ + // start date is in the future, phase started = NO + phase_started = false + }else{ + // start date is not in the future, phase started = YES + phase_started = true + } + + if(phase_started){ + // check if end data exists for this phase + if(phase["end"]){ + if((Date.parse(phase["end"]) - Date.parse(new Date())) < 0){ + // Phase cannote accept submissions if end date is in the past + phase_ended = true + }else{ + // Phase can accept submissions if end date is in the future + phase_ended = false + } + }else{ + // Phase can accept submissions if end date is not given + phase_ended = false + } + } + self.competition.phases[index]["phase_ended"] = phase_ended + self.competition.phases[index]["phase_started"] = phase_started + }) + self.competition.is_admin = CODALAB.state.user.has_competition_admin_privileges(competition) self.selected_phase_index = _.get(_.find(self.competition.phases, {'status': 'Current'}), 'id') if (self.selected_phase_index == null) { From c19d96da6e56634d019fc280739f750619cd8695 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Sat, 17 Jun 2023 13:42:22 -0400 Subject: [PATCH 02/15] List the Files on Files Tab --- src/apps/api/serializers/tasks.py | 26 ++++---- src/static/riot/competitions/detail/_tabs.tag | 60 +++++++++++++++++-- 2 files changed, 67 insertions(+), 19 deletions(-) diff --git a/src/apps/api/serializers/tasks.py b/src/apps/api/serializers/tasks.py index ee10602bc..b6ff92e6d 100644 --- a/src/apps/api/serializers/tasks.py +++ b/src/apps/api/serializers/tasks.py @@ -1,5 +1,3 @@ -import pdb - from drf_writable_nested import WritableNestedModelSerializer from rest_framework import serializers from rest_framework.exceptions import ValidationError @@ -30,7 +28,6 @@ class Meta: ] def get_size(self, instance): - print("line 31"); pdb.set_trace() # BB return instance.data.file_size @@ -168,7 +165,7 @@ class PhaseTaskInstanceSerializer(serializers.HyperlinkedModelSerializer): created_when = serializers.DateTimeField(source='task.created_when', required=False) name = serializers.CharField(source='task.name', required=False) solutions = serializers.SerializerMethodField() - # print("line 159"); pdb.set_trace() # BB + public_datasets = serializers.SerializerMethodField() class Meta: model = PhaseTaskInstance @@ -182,22 +179,21 @@ class Meta: 'key', 'created_when', 'name', - 'solutions' + 'solutions', # BB Add public data + 'public_datasets' # BB Add starting kit ) def get_solutions(self, instance): - print("line 177"); pdb.set_trace() # BB qs = instance.task.solutions.all() return SolutionSerializer(qs, many=True).data - # def get_public_datasets(self, instance): - # print("line 177"); pdb.set_trace() # BB - # qs = instance.task.solutions.all() - # return SolutionSerializer(qs, many=True).data - - # def get_starting_kits(self, instance): - # print("line 177"); pdb.set_trace() # BB - # qs = instance.task.solutions.all() - # return SolutionSerializer(qs, many=True).data \ No newline at end of file + def get_public_datasets(self, instance): + input_data = instance.task.input_data + reference_data = instance.task.reference_data + ingestion_program = instance.task.ingestion_program + scoring_program = instance.task.scoring_program + dataset_list_ids = [input_data.id, reference_data.id, ingestion_program.id, scoring_program.id] + qs = Data.objects.filter(id__in=dataset_list_ids) + return DataDetailSerializer(qs, many=True).data \ No newline at end of file diff --git a/src/static/riot/competitions/detail/_tabs.tag b/src/static/riot/competitions/detail/_tabs.tag index 1924773cd..ea1dade5c 100644 --- a/src/static/riot/competitions/detail/_tabs.tag +++ b/src/static/riot/competitions/detail/_tabs.tag @@ -51,6 +51,8 @@ Download Phase Task + Type + Public Size @@ -63,6 +65,8 @@ {file.phase} {file.task} + {file.type} + no {filesize(file.file_size * 1024)} @@ -196,22 +200,70 @@ CODALAB.events.on('competition_loaded', function (competition) { self.competition = competition self.competition.files = [] - debugger _.forEach(competition.phases, phase => { - debugger _.forEach(phase.tasks, task => { - debugger + let input_data = {} + let reference_data = {} + let ingestion_program = {} + let scoring_program = {} + _.forEach(task.public_datasets, dataset => { + let type = 'input_data' + if(dataset.type === "input_data"){ + type = 'Input Data' + input_data = {key: dataset.key, name: dataset.name, file_size: dataset.file_size, phase: phase.name, task: task.name, type: type} + }else if(dataset.type === "reference_data"){ + type = 'Reference Data (private?)' + reference_data = {key: dataset.key, name: dataset.name, file_size: dataset.file_size, phase: phase.name, task: task.name, type: type} + }else if(dataset.type === "ingestion_program"){ + type = 'Ingestion Program' + ingestion_program = {key: dataset.key, name: dataset.name, file_size: dataset.file_size, phase: phase.name, task: task.name, type: type} + }else if(dataset.type === "scoring_program"){ + type = 'Scoring Program' + scoring_program = {key: dataset.key, name: dataset.name, file_size: dataset.file_size, phase: phase.name, task: task.name, type: type} + } + //self.competition.files.push({ + // key: dataset.key, + // name: dataset.name, + // file_size: dataset.file_size, + // phase: phase.name, + // task: task.name, + // type: type + //}) + }) + self.competition.files.push(input_data) + self.competition.files.push(reference_data) + self.competition.files.push(ingestion_program) + self.competition.files.push(scoring_program) + }) + _.forEach(phase.tasks, task => { _.forEach(task.solutions, solution => { - debugger self.competition.files.push({ key: solution.data, name: solution.name, file_size: solution.size, phase: phase.name, task: task.name, + type: 'Solution' }) }) }) + // Need code for public_data and starting_kit at phase level + self.competition.files.push({ + key: 'starting_kit_key_placeholder', + name: 'starting_kit_name_placeholder', + file_size: 0, + phase: phase.name, + task: '--', + type: 'Starting Kit' + }) + self.competition.files.push({ + key: 'public_data_key_placeholder', + name: 'public_data_name_placeholder', + file_size: 0, + phase: phase.name, + task: '--', + type: 'Public Data' + }) }) // loop over competition phases to mark if phase has started or ended From 50f2f68e20cb0162f434e1a34fdce6a550dfabba Mon Sep 17 00:00:00 2001 From: Benjamin Date: Sun, 18 Jun 2023 13:54:42 -0400 Subject: [PATCH 03/15] download buttons update --- docker-compose dev.yml | 237 ++++++++++++++++++ src/apps/api/serializers/competitions.py | 8 +- .../migrations/0033_auto_20230617_1753.py | 25 ++ src/apps/competitions/models.py | 4 + src/static/riot/competitions/detail/_tabs.tag | 52 ++-- .../riot/competitions/editor/_phases.tag | 130 ++++++++++ 6 files changed, 428 insertions(+), 28 deletions(-) create mode 100644 docker-compose dev.yml create mode 100644 src/apps/competitions/migrations/0033_auto_20230617_1753.py diff --git a/docker-compose dev.yml b/docker-compose dev.yml new file mode 100644 index 000000000..d7c996872 --- /dev/null +++ b/docker-compose dev.yml @@ -0,0 +1,237 @@ +version: '3' +services: + #----------------------------------------------- + # Web Services + #----------------------------------------------- + caddy: + image: abiosoft/caddy:1.0.3 + env_file: .env + environment: + - ACME_AGREE=true + volumes: + - ./Caddyfile:/etc/Caddyfile + - ./src/staticfiles:/var/www/django/static + - ./certs/caddy:/etc/caddycerts + restart: unless-stopped + ports: + - 80:80 + - 443:443 + depends_on: + - django + + django: + build: . + # NOTE: We use watchmedo to reload gunicorn nicely, Uvicorn + Gunicorn reloads don't work well + command: bash -c "cd /app/src && watchmedo auto-restart -p '*.py' --recursive -- gunicorn asgi:application -w 2 -k uvicorn.workers.UvicornWorker -b :8000 -b :80 --capture-output --log-level debug" + stdin_open: true + tty: true + environment: + - DATABASE_URL=postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME} + env_file: .env + volumes: + - .:/app:delegated + - /tmp/codalab-v2/django:/codalab_tmp + - ./backups:/app/backups + restart: unless-stopped + ports: + - 8000:8000 + depends_on: + - db + - rabbit + - minio + logging: + options: + max-size: "20k" + max-file: "10" + + + #----------------------------------------------- + # Minio local storage helper + #----------------------------------------------- + minio: + image: minio/minio:RELEASE.2020-10-03T02-19-42Z + command: server /export + volumes: + - ./var/minio:/export + restart: unless-stopped + ports: + - $MINIO_PORT:9000 + env_file: .env + healthcheck: + test: ["CMD", "nc", "-z", "minio", "9000"] + interval: 5s + retries: 5 + + createbuckets: + image: minio/mc + depends_on: + minio: + condition: service_healthy + env_file: .env +# volumes: + # This volume is shared with `minio`, so `z` to share it +# - ./var/minio:/export + entrypoint: > + /bin/sh -c " + set -x; + if [ -n \"$MINIO_ACCESS_KEY\" ] && [ -n \"$MINIO_SECRET_KEY\" ] && [ -n \"$MINIO_PORT\" ]; then + until /usr/bin/mc config host add minio_docker http://minio:$MINIO_PORT $MINIO_ACCESS_KEY $MINIO_SECRET_KEY && break; do echo '...waiting...' && sleep 5; done; + /usr/bin/mc mb minio_docker/$AWS_STORAGE_BUCKET_NAME; + /usr/bin/mc mb minio_docker/$AWS_STORAGE_PRIVATE_BUCKET_NAME; + /usr/bin/mc anonymous set download minio_docker/$AWS_STORAGE_BUCKET_NAME; + else + echo 'MINIO_ACCESS_KEY, MINIO_SECRET_KEY, or MINIO_PORT are not defined. Skipping buckets creation.'; + fi; + exit 0; + " + + #----------------------------------------------- + # Local development helper, rebuilds RiotJS/Stylus on change + #----------------------------------------------- + builder: + build: + context: . + dockerfile: Dockerfile.builder + volumes: + - .:/app:delegated + restart: unless-stopped + logging: + options: + max-size: "20k" + max-file: "10" + + + #----------------------------------------------- + # Database Service + #----------------------------------------------- + db: + image: postgres:12-alpine + env_file: .env + environment: + - PGDATA=/var/lib/postgresql/data/pgdata + - POSTGRES_PASSWORD=${DB_PASSWORD} + ports: + - 5432:5432 + volumes: + - ./var/postgres:/var/lib/postgresql/data:delegated + - ./backups:/app/backups + restart: unless-stopped + logging: + options: + max-size: "20k" + max-file: "10" + + #----------------------------------------------- + # Rabbitmq & Flower monitoring tool + #----------------------------------------------- + rabbit: + image: rabbitmq:3.6-management + # setting hostname here makes data persist properly between + # containers being destroyed..! + hostname: rabbit + env_file: .env + ports: + - ${RABBITMQ_MANAGEMENT_PORT:-15672}:15672 + - ${RABBITMQ_PORT}:5672 + volumes: + - ./var/rabbit:/var/lib/rabbitmq + restart: unless-stopped + logging: + options: + max-size: "20k" + max-file: "10" + + flower: + # image: mher/flower + build: + context: . + dockerfile: Dockerfile.flower + env_file: .env + environment: + - CELERY_BROKER_URL=pyamqp://${RABBITMQ_DEFAULT_USER}:${RABBITMQ_DEFAULT_PASS}@${RABBITMQ_HOST}:${RABBITMQ_PORT}// + restart: unless-stopped + ports: + - ${FLOWER_PUBLIC_PORT:-5555}:5555 + depends_on: + - rabbit + logging: + options: + max-size: "20k" + max-file: "10" + + #----------------------------------------------- + # Redis + #----------------------------------------------- + redis: + image: redis + ports: + - 6379:6379 + restart: unless-stopped + logging: + options: + max-size: "20k" + max-file: "10" + + #----------------------------------------------- + # Celery Service + #----------------------------------------------- + site_worker: + # This auto-reloads + command: bash -c "watchmedo auto-restart -p '*.py' --recursive -- celery -A celery_config worker -B -Q site-worker -l info -n site-worker@%n --concurrency=2" + working_dir: /app/src + build: + context: . + depends_on: + - rabbit + - db + # stdin_open: true + # tty: true + # ports: + # - "6900:6900" + env_file: .env + # environment: + # - COLUMNS=80 # Set the number of columns + # - CELERY_RDB_HOST=0.0.0.0 + volumes: + - .:/app + restart: unless-stopped + logging: + options: + max-size: "20k" + max-file: "10" + deploy: + resources: + limits: + # Limit memory substantially here so we see any problems that may + # appear on Heroku ahead of time + memory: 256M + + compute_worker: + command: bash -c "watchmedo auto-restart -p '*.py' --recursive -- celery -A compute_worker worker -l info -Q compute-worker -n compute-worker@%n" + working_dir: /app + build: + context: . + dockerfile: Dockerfile.compute_worker + depends_on: + - django + - rabbit + # stdin_open: true + # tty: true + ports: + - "6900-6901:6900-6901" + volumes: + - ./compute_worker:/app + - ${HOST_DIRECTORY:-/tmp/codabench}:/codabench + # Actual connection back to docker parent to run things + - /var/run/docker.sock:/var/run/docker.sock + env_file: .env + environment: + - BROKER_URL=pyamqp://${RABBITMQ_DEFAULT_USER}:${RABBITMQ_DEFAULT_PASS}@${RABBITMQ_HOST}:${RABBITMQ_PORT}// + # Make the worker leave behind the submission so we can examine it + - CODALAB_IGNORE_CLEANUP_STEP=1 + # - COLUMNS=80 # Set the number of columns + # - CELERY_RDB_HOST=0.0.0.0 + logging: + options: + max-size: "20k" + max-file: "10" diff --git a/src/apps/api/serializers/competitions.py b/src/apps/api/serializers/competitions.py index 4d79c9a61..ee9cc239c 100644 --- a/src/apps/api/serializers/competitions.py +++ b/src/apps/api/serializers/competitions.py @@ -4,6 +4,7 @@ from api.fields import NamedBase64ImageField from api.mixins import DefaultUserCreateMixin +from api.serializers.datasets import DataDetailSerializer, DataSimpleSerializer from api.serializers.leaderboards import LeaderboardSerializer, ColumnSerializer from api.serializers.profiles import CollaboratorSerializer from api.serializers.submissions import SubmissionScoreSerializer @@ -41,7 +42,7 @@ class Meta: 'auto_migrate_to_this_phase', 'hide_output', 'leaderboard', - 'is_final_phase', + 'is_final_phase', ) def get_status(self, obj): @@ -89,6 +90,9 @@ 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) class Meta: model = Phase @@ -108,6 +112,8 @@ class Meta: 'execution_time_limit', 'hide_output', 'is_final_phase', + 'public_data', + 'starting_kit' ) def get_status(self, obj): diff --git a/src/apps/competitions/migrations/0033_auto_20230617_1753.py b/src/apps/competitions/migrations/0033_auto_20230617_1753.py new file mode 100644 index 000000000..3603af13e --- /dev/null +++ b/src/apps/competitions/migrations/0033_auto_20230617_1753.py @@ -0,0 +1,25 @@ +# Generated by Django 2.2.17 on 2023-06-17 17:53 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('datasets', '0007_auto_20230609_1738'), + ('competitions', '0032_submission_worker_hostname'), + ] + + operations = [ + migrations.AddField( + model_name='phase', + name='public_data', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='phase_public_data', to='datasets.Data'), + ), + migrations.AddField( + model_name='phase', + name='starting_kit', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='phase_starting_kit', to='datasets.Data'), + ), + ] diff --git a/src/apps/competitions/models.py b/src/apps/competitions/models.py index f355802d1..2d8818c56 100644 --- a/src/apps/competitions/models.py +++ b/src/apps/competitions/models.py @@ -282,6 +282,10 @@ class Phase(ChaHubSaveMixin, models.Model): leaderboard = models.ForeignKey('leaderboards.Leaderboard', on_delete=models.DO_NOTHING, null=True, blank=True, related_name="phases") + + public_data = models.ForeignKey('datasets.Data', on_delete=models.SET_NULL, null=True, blank=True, related_name="phase_public_data") + starting_kit = models.ForeignKey('datasets.Data', on_delete=models.SET_NULL, null=True, blank=True, related_name="phase_starting_kit") + class Meta: ordering = ('index',) diff --git a/src/static/riot/competitions/detail/_tabs.tag b/src/static/riot/competitions/detail/_tabs.tag index ea1dade5c..35e199df8 100644 --- a/src/static/riot/competitions/detail/_tabs.tag +++ b/src/static/riot/competitions/detail/_tabs.tag @@ -52,7 +52,7 @@ Phase Task Type - Public + Available Size @@ -66,7 +66,7 @@ {file.phase} {file.task} {file.type} - no + {file.type === 'Public Data' || file.type === 'Starting Kit' ? 'yes': 'no'} {filesize(file.file_size * 1024)} @@ -202,6 +202,7 @@ self.competition.files = [] _.forEach(competition.phases, phase => { _.forEach(phase.tasks, task => { + // Over complicated data org but it is so we can order exactly how we want... let input_data = {} let reference_data = {} let ingestion_program = {} @@ -221,15 +222,8 @@ type = 'Scoring Program' scoring_program = {key: dataset.key, name: dataset.name, file_size: dataset.file_size, phase: phase.name, task: task.name, type: type} } - //self.competition.files.push({ - // key: dataset.key, - // name: dataset.name, - // file_size: dataset.file_size, - // phase: phase.name, - // task: task.name, - // type: type - //}) }) + // ...that ordering happens here self.competition.files.push(input_data) self.competition.files.push(reference_data) self.competition.files.push(ingestion_program) @@ -248,24 +242,28 @@ }) }) // Need code for public_data and starting_kit at phase level - self.competition.files.push({ - key: 'starting_kit_key_placeholder', - name: 'starting_kit_name_placeholder', - file_size: 0, - phase: phase.name, - task: '--', - type: 'Starting Kit' - }) - self.competition.files.push({ - key: 'public_data_key_placeholder', - name: 'public_data_name_placeholder', - file_size: 0, - phase: phase.name, - task: '--', - type: 'Public Data' - }) + + if (phase.starting_kit != null){ + self.competition.files.push({ + key: phase.starting_kit.key, + name: phase.starting_kit.name, + file_size: phase.starting_kit.file_size, + phase: phase.name, + task: '-Phase Level Data-', + type: 'Starting Kit' + }) + } + if (phase.public_data != null){ + self.competition.files.push({ + key: phase.public_data.key, + name: phase.public_data.name, + file_size: phase.public_data.file_size, + phase: phase.name, + task: '-Phase Level Data-', + type: 'Public Data' + }) + } }) - // loop over competition phases to mark if phase has started or ended self.competition.phases.forEach(function (phase, index) { diff --git a/src/static/riot/competitions/editor/_phases.tag b/src/static/riot/competitions/editor/_phases.tag index e2dc437c1..adc700a91 100644 --- a/src/static/riot/competitions/editor/_phases.tag +++ b/src/static/riot/competitions/editor/_phases.tag @@ -96,6 +96,27 @@ multiple="multiple"> + +
+ + +
+
+ + +
@@ -176,6 +197,8 @@ self.form_is_valid = false self.phases = [] self.phase_tasks = [] + self.phase_public_data = [] + self.phase_starting_kit = [] self.selected_phase_index = undefined self.warnings = [] @@ -190,6 +213,8 @@ apiSettings: { url: `${URLS.API}tasks/?search={query}`, onResponse: (data) => { + console.log(_.values(data.results)) + debugger return {success: true, results: _.values(data.results)} }, }, @@ -197,6 +222,35 @@ onRemove: self.task_removed, }) + $(self.refs.public_data_multiselect).dropdown({ + apiSettings: { + url: `${URLS.API}datasets/?search={query}`, + onResponse: (data) => { + console.log(_.values(data.results)) + data.results.forEach((v,i,a) => { + v['value'] = v['key'] + }) + debugger + return {success: true, results: _.values(data.results)} + }, + }, + onAdd: self.public_data_added, + onRemove: self.public_data_removed, + }) + + $(self.refs.starting_kit_multiselect).dropdown({ + apiSettings: { + url: `${URLS.API}datasets/?search={query}&type=starting_kit`, + onResponse: (data) => { + console.log(_.values(data.results)) + return {success: true, results: _.values(data.results)} + }, + }, + // onAdd: self.task_added, + // onRemove: self.task_removed, + }) + // When adding \ removing phase we need to code it like above + // Form change events $(':input', self.root).not('[type="file"]').not('button').not('[readonly]').each(function (i, field) { this.addEventListener('keyup', self.form_updated) @@ -220,6 +274,7 @@ Methods ---------------------------------------------------------------------*/ self.task_added = (key, text, item) => { + debugger let index = _.findIndex(self.phase_tasks, (task) => { return task.value === key }) @@ -234,10 +289,34 @@ let index = _.findIndex(self.phase_tasks, (task) => { return task.value === key }) + debugger self.phase_tasks.splice(index, 1) self.form_updated() } + self.public_data_added = (key, text, item) => { + debugger + let index = _.findIndex(self.phase_public_data, (public_data) => { + debugger + return public_data.value === key + }) + if (index === -1) { + let public_data = {name: text, value: key, selected: true} + self.phase_public_data.push(public_data) + } + self.form_updated() + } + + self.public_data_removed = (key, text, item) => { + debugger + let index = _.findIndex(self.phase_public_data, (public_data) => { + return public_data.value === key + }) + self.phase_public_data.splice(index, 1) + self.form_updated() + } + + self.show_modal = function () { $(self.refs.modal).modal('show') @@ -286,6 +365,7 @@ is_valid = false } else { // Make sure each phase has the proper details + // BB - check for public_data and starting_kit - NOT DONE self.phases.forEach(function (phase) { if (!phase.name || !phase.start || phase.tasks.length === 0) { is_valid = false @@ -338,6 +418,9 @@ self.selected_phase_index = undefined self.phase_tasks = [] $(self.refs.multiselect).dropdown('clear') + // BB - I feel like if we need the above for tasks we need it for pulic_data and starting_kit - 06/17/2023 + // $(self.refs.public_data_multiselect).dropdown('clear') + // $(self.refs.starting_kit_multiselect).dropdown('clear') $(':input', self.refs.form) .not('[type="file"]') @@ -369,6 +452,11 @@ self.selected_phase_index = index var phase = self.phases[index] self.phase_tasks = phase.tasks + phase.public_data['value'] = phase.public_data['key'] + debugger + self.phase_public_data = [phase.public_data] + self.phase_public_data + self.update() set_form_data(phase, self.refs.form) @@ -382,6 +470,7 @@ $(self.refs.multiselect) .dropdown('change values', _.map(self.phase_tasks, task => { // renaming things to work w/ semantic UI multiselect + debugger return { value: task.value, text: task.name, @@ -389,11 +478,28 @@ selected: true, } })) + // BB - I feel like if we need the above for tasks we need it for phases - 06/17/2023 + $(self.refs.public_data_multiselect) + .dropdown('change values', _.map(self.phase_public_data, public_data => { + // renaming things to work w/ semantic UI multiselect + debugger + return { + //value: public_data.value, // Maybe need to grab from serializer? + value: public_data.key, + text: public_data.name, + name: public_data.name, + selected: true, + } + })) self.show_modal() // make semantic multiselect sortable -- Sortable library imported in competitions/form.html + debugger Sortable.create($('.search.dropdown.multiple', self.refs.tasks_select_container)[0]) + // BB - I feel like if we need the above for tasks we need it for public_data and starting_kit - 06/17/2023 + Sortable.create($('.search.dropdown.multiple', self.refs.public_data_select_container)[0]) + //Sortable.create($('.search.dropdown.single', self.refs.starting_kit_select_container)[0]) self.form_check_is_valid() self.update() @@ -416,6 +522,7 @@ // Get tasks order from DOM and order the task array by that. let tasks_from_dom = [] $("#tasks_select_container a").each(function () { + debugger tasks_from_dom.push($(this).data("value")) }) let sorted_phase_tasks = [] @@ -432,9 +539,31 @@ }) self.phase_tasks = sorted_phase_tasks.slice() + // NOT DONE - BB - 06/17/2023 -> We need to grab selected phase and save it. + let public_data_from_dom = [] + $("#public_data_select_container a").each(function () { + debugger + public_data_from_dom.push($(this).data("value")) + }) + let sorted_phase_public_data = [] + public_data_from_dom.forEach( function(key) { + let found = false; + self.phase_public_data = self.phase_public_data.filter(function (item) { + if(!found && item['value'] == key){ + sorted_phase_public_data.push(item) + found = true + return false + } else + return true + }) + }) + self.phase_public_data = sorted_phase_public_data.slice() + var data = get_form_data(self.refs.form) data.tasks = self.phase_tasks + data.public_data = self.phase_public_data[0] data.task_instances = [] + debugger for(task of self.phase_tasks){ data.task_instances.push({ order_index: data.task_instances.length, @@ -457,6 +586,7 @@ // We have a selected phase, do an update instead of a create data.id = self.phases[self.selected_phase_index].id self.phases[self.selected_phase_index] = data + debugger } self.close_modal() } From e8bd080b7ef86271969aff03329f0e5b6447bdd0 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Wed, 21 Jun 2023 03:10:25 -0400 Subject: [PATCH 04/15] dataset download added to UI. --- src/apps/api/serializers/competitions.py | 11 +- src/apps/api/serializers/tasks.py | 5 +- src/apps/api/views/competitions.py | 22 +- src/apps/api/views/datasets.py | 1 - src/apps/profiles/views.py | 1 - src/static/js/ours/utils.js | 283 ++++++++++-------- .../riot/competitions/editor/_phases.tag | 145 +++++---- src/static/riot/competitions/editor/form.tag | 1 - src/static/riot/tasks/management.tag | 1 - 9 files changed, 279 insertions(+), 191 deletions(-) diff --git a/src/apps/api/serializers/competitions.py b/src/apps/api/serializers/competitions.py index ee9cc239c..28ff021cf 100644 --- a/src/apps/api/serializers/competitions.py +++ b/src/apps/api/serializers/competitions.py @@ -42,6 +42,8 @@ class Meta: 'auto_migrate_to_this_phase', 'hide_output', 'leaderboard', + 'public_data', + 'starting_kit', 'is_final_phase', ) @@ -104,16 +106,17 @@ class Meta: 'name', 'description', 'status', + 'execution_time_limit', 'tasks', - 'auto_migrate_to_this_phase', 'has_max_submissions', 'max_submissions_per_day', 'max_submissions_per_person', - 'execution_time_limit', + 'auto_migrate_to_this_phase', 'hide_output', - 'is_final_phase', + # no leaderboard 'public_data', - 'starting_kit' + 'starting_kit', + 'is_final_phase', ) def get_status(self, obj): diff --git a/src/apps/api/serializers/tasks.py b/src/apps/api/serializers/tasks.py index b6ff92e6d..737649931 100644 --- a/src/apps/api/serializers/tasks.py +++ b/src/apps/api/serializers/tasks.py @@ -43,6 +43,7 @@ class Meta: class TaskSerializer(DefaultUserCreateMixin, WritableNestedModelSerializer): + input_data = serializers.SlugRelatedField(queryset=Data.objects.all(), required=False, allow_null=True, slug_field='key') ingestion_program = serializers.SlugRelatedField(queryset=Data.objects.all(), required=False, allow_null=True, slug_field='key') reference_data = serializers.SlugRelatedField(queryset=Data.objects.all(), required=False, allow_null=True, slug_field='key') @@ -166,7 +167,7 @@ class PhaseTaskInstanceSerializer(serializers.HyperlinkedModelSerializer): name = serializers.CharField(source='task.name', required=False) solutions = serializers.SerializerMethodField() public_datasets = serializers.SerializerMethodField() - + class Meta: model = PhaseTaskInstance fields = ( @@ -180,9 +181,7 @@ class Meta: 'created_when', 'name', 'solutions', - # BB Add public data 'public_datasets' - # BB Add starting kit ) def get_solutions(self, instance): diff --git a/src/apps/api/views/competitions.py b/src/apps/api/views/competitions.py index 2db303a45..1e530545d 100644 --- a/src/apps/api/views/competitions.py +++ b/src/apps/api/views/competitions.py @@ -31,6 +31,7 @@ from competitions.emails import send_participation_requested_emails, send_participation_accepted_emails, \ send_participation_denied_emails, send_direct_participant_email from competitions.models import Competition, Phase, CompetitionCreationTaskStatus, CompetitionParticipant, Submission +from datasets.models import Data from competitions.tasks import batch_send_email, manual_migration, create_competition_dump from competitions.utils import get_popular_competitions, get_featured_competitions from leaderboards.models import Leaderboard @@ -229,9 +230,24 @@ def update(self, request, *args, **kwargs): phase['leaderboard'] = leaderboard_id - serializer = self.get_serializer(instance, data=data, partial=partial) - serializer.is_valid(raise_exception=True) - self.perform_update(serializer) + + # Get public_data and starting_kit + for phase in data['phases']: + # We just need to know what public_data and starting_kit go with this phase + # We don't need to serialize the whole object + try: + phase['public_data'] = Data.objects.filter(key=phase['public_data']['value'])[0].id + except: + phase['public_data'] = None + try: + phase['starting_kit'] = Data.objects.filter(key=phase['starting_kit']['value'])[0].id + except: + phase['starting_kit'] = None + + serializer = self.get_serializer(instance, data=data, partial=partial) + type(serializer) + serializer.is_valid(raise_exception=True) + self.perform_update(serializer) if getattr(instance, '_prefetched_objects_cache', None): # If 'prefetch_related' has been applied to a queryset, we need to diff --git a/src/apps/api/views/datasets.py b/src/apps/api/views/datasets.py index d1a817c24..fd2ae17cb 100644 --- a/src/apps/api/views/datasets.py +++ b/src/apps/api/views/datasets.py @@ -79,7 +79,6 @@ def get_serializer_class(self): return serializers.DataSerializer def create(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) new_dataset = serializer.save() # request_sassy_file_name is temporarily set via this serializer diff --git a/src/apps/profiles/views.py b/src/apps/profiles/views.py index 9a19090dd..cfa4f2e10 100644 --- a/src/apps/profiles/views.py +++ b/src/apps/profiles/views.py @@ -68,7 +68,6 @@ def get_context_data(self, **kwargs): def activate(request, uidb64, token): try: - # import pdb; pdb.set_trace(); uid = force_str(urlsafe_base64_decode(uidb64)) user = User.objects.get(pk=uid) except User.DoesNotExist: diff --git a/src/static/js/ours/utils.js b/src/static/js/ours/utils.js index 026be61d5..671ccccd3 100644 --- a/src/static/js/ours/utils.js +++ b/src/static/js/ours/utils.js @@ -96,13 +96,42 @@ function get_form_fields(base_element) { //return $(':input', self.root).not('button').not('[readonly]').each(function (i, field) { // console.log(field) //}) - return $(':input', base_element).not('button').not('[readonly]') + // debugger + form_fields = $(':input', base_element).not('button').not('[readonly]') + // Calendars come through as read-only and jQuery leaves them out + calendars = $('.two.fields .ui.calendar.field input[type="text"]') + console.log(calendars) + readonly_calendars = $('.two.fields .ui.calendar.field [readonly]') + console.log(readonly_calendars) + // If calendars is readonly_calendars, then append them to form_fields + if (calendars.length === readonly_calendars.length) { + var isIdentical = true; + calendars.each(function(index) { + if (!$(this).is(readonly_calendars.eq(index))) { + isIdentical = false; + return false; // exit the loop + } + }); + + if (isIdentical) { + // console.log("The two sets are identical."); + form_fields = form_fields.add(calendars) + } else { + // console.log("The two sets are not identical."); + } + } else { + // console.log("The two sets have different lengths and are not identical."); + } + + return form_fields } function get_form_data(base_element) { var fields = get_form_fields(base_element) var data = {} fields.each(function (i, field) { + console.log(field) + console.log(data) if (!!field.name) { data[field.name] = $(field).val() } @@ -170,134 +199,134 @@ function getBase64(file) { } /* - A simple, lightweight jQuery plugin for creating sortable tables. - https://github.com/kylefox/jquery-tablesort - Version 0.0.11 + A simple, lightweight jQuery plugin for creating sortable tables. + https://github.com/kylefox/jquery-tablesort + Version 0.0.11 */ (function($) { - $.tablesort = function ($table, settings) { - var self = this; - this.$table = $table; - this.$thead = this.$table.find('thead'); - this.settings = $.extend({}, $.tablesort.defaults, settings); - this.$sortCells = this.$thead.length > 0 ? this.$thead.find('th:not(.no-sort)') : this.$table.find('th:not(.no-sort)'); - this.$sortCells.on('click.tablesort', function() { - self.sort($(this)); - }); - this.index = null; - this.$th = null; - this.direction = null; - }; - - $.tablesort.prototype = { - - sort: function(th, direction) { - var start = new Date(), - self = this, - table = this.$table, - rowsContainer = table.find('tbody').length > 0 ? table.find('tbody') : table, - rows = rowsContainer.find('tr').has('td, th'), - cells = rows.find(':nth-child(' + (th.index() + 1) + ')').filter('td, th'), - sortBy = th.data().sortBy, - sortedMap = []; - - var unsortedValues = cells.map(function(idx, cell) { - if (sortBy) - return (typeof sortBy === 'function') ? sortBy($(th), $(cell), self) : sortBy; - return ($(this).data().sortValue != null ? $(this).data().sortValue : $(this).text()); - }); - if (unsortedValues.length === 0) return; - - //click on a different column - if (this.index !== th.index()) { - this.direction = 'asc'; - this.index = th.index(); - } - else if (direction !== 'asc' && direction !== 'desc') - this.direction = this.direction === 'asc' ? 'desc' : 'asc'; - else - this.direction = direction; - - direction = this.direction == 'asc' ? 1 : -1; - - self.$table.trigger('tablesort:start', [self]); - self.log("Sorting by " + this.index + ' ' + this.direction); - - // Try to force a browser redraw - self.$table.css("display"); - // Run sorting asynchronously on a timeout to force browser redraw after - // `tablesort:start` callback. Also avoids locking up the browser too much. - setTimeout(function() { - self.$sortCells.removeClass(self.settings.asc + ' ' + self.settings.desc); - for (var i = 0, length = unsortedValues.length; i < length; i++) - { - sortedMap.push({ - index: i, - cell: cells[i], - row: rows[i], - value: unsortedValues[i] - }); - } - - sortedMap.sort(function(a, b) { - return self.settings.compare(a.value, b.value) * direction; - }); - - $.each(sortedMap, function(i, entry) { - rowsContainer.append(entry.row); - }); - - th.addClass(self.settings[self.direction]); - - self.log('Sort finished in ' + ((new Date()).getTime() - start.getTime()) + 'ms'); - self.$table.trigger('tablesort:complete', [self]); - //Try to force a browser redraw - self.$table.css("display"); - }, unsortedValues.length > 2000 ? 200 : 10); - }, - - log: function(msg) { - if(($.tablesort.DEBUG || this.settings.debug) && console && console.log) { - console.log('[tablesort] ' + msg); - } - }, - - destroy: function() { - this.$sortCells.off('click.tablesort'); - this.$table.data('tablesort', null); - return null; - } - - }; - - $.tablesort.DEBUG = false; - - $.tablesort.defaults = { - debug: $.tablesort.DEBUG, - asc: 'sorted ascending', - desc: 'sorted descending', - compare: function(a, b) { - if (a > b) { - return 1; - } else if (a < b) { - return -1; - } else { - return 0; - } - } - }; - - $.fn.tablesort = function(settings) { - var table, sortable, previous; - return this.each(function() { - table = $(this); - previous = table.data('tablesort'); - if(previous) { - previous.destroy(); - } - table.data('tablesort', new $.tablesort(table, settings)); - }); - }; + $.tablesort = function ($table, settings) { + var self = this; + this.$table = $table; + this.$thead = this.$table.find('thead'); + this.settings = $.extend({}, $.tablesort.defaults, settings); + this.$sortCells = this.$thead.length > 0 ? this.$thead.find('th:not(.no-sort)') : this.$table.find('th:not(.no-sort)'); + this.$sortCells.on('click.tablesort', function() { + self.sort($(this)); + }); + this.index = null; + this.$th = null; + this.direction = null; + }; + + $.tablesort.prototype = { + + sort: function(th, direction) { + var start = new Date(), + self = this, + table = this.$table, + rowsContainer = table.find('tbody').length > 0 ? table.find('tbody') : table, + rows = rowsContainer.find('tr').has('td, th'), + cells = rows.find(':nth-child(' + (th.index() + 1) + ')').filter('td, th'), + sortBy = th.data().sortBy, + sortedMap = []; + + var unsortedValues = cells.map(function(idx, cell) { + if (sortBy) + return (typeof sortBy === 'function') ? sortBy($(th), $(cell), self) : sortBy; + return ($(this).data().sortValue != null ? $(this).data().sortValue : $(this).text()); + }); + if (unsortedValues.length === 0) return; + + //click on a different column + if (this.index !== th.index()) { + this.direction = 'asc'; + this.index = th.index(); + } + else if (direction !== 'asc' && direction !== 'desc') + this.direction = this.direction === 'asc' ? 'desc' : 'asc'; + else + this.direction = direction; + + direction = this.direction == 'asc' ? 1 : -1; + + self.$table.trigger('tablesort:start', [self]); + self.log("Sorting by " + this.index + ' ' + this.direction); + + // Try to force a browser redraw + self.$table.css("display"); + // Run sorting asynchronously on a timeout to force browser redraw after + // `tablesort:start` callback. Also avoids locking up the browser too much. + setTimeout(function() { + self.$sortCells.removeClass(self.settings.asc + ' ' + self.settings.desc); + for (var i = 0, length = unsortedValues.length; i < length; i++) + { + sortedMap.push({ + index: i, + cell: cells[i], + row: rows[i], + value: unsortedValues[i] + }); + } + + sortedMap.sort(function(a, b) { + return self.settings.compare(a.value, b.value) * direction; + }); + + $.each(sortedMap, function(i, entry) { + rowsContainer.append(entry.row); + }); + + th.addClass(self.settings[self.direction]); + + self.log('Sort finished in ' + ((new Date()).getTime() - start.getTime()) + 'ms'); + self.$table.trigger('tablesort:complete', [self]); + //Try to force a browser redraw + self.$table.css("display"); + }, unsortedValues.length > 2000 ? 200 : 10); + }, + + log: function(msg) { + if(($.tablesort.DEBUG || this.settings.debug) && console && console.log) { + console.log('[tablesort] ' + msg); + } + }, + + destroy: function() { + this.$sortCells.off('click.tablesort'); + this.$table.data('tablesort', null); + return null; + } + + }; + + $.tablesort.DEBUG = false; + + $.tablesort.defaults = { + debug: $.tablesort.DEBUG, + asc: 'sorted ascending', + desc: 'sorted descending', + compare: function(a, b) { + if (a > b) { + return 1; + } else if (a < b) { + return -1; + } else { + return 0; + } + } + }; + + $.fn.tablesort = function(settings) { + var table, sortable, previous; + return this.each(function() { + table = $(this); + previous = table.data('tablesort'); + if(previous) { + previous.destroy(); + } + table.data('tablesort', new $.tablesort(table, settings)); + }); + }; })(window.Zepto || window.jQuery); diff --git a/src/static/riot/competitions/editor/_phases.tag b/src/static/riot/competitions/editor/_phases.tag index adc700a91..ef59166de 100644 --- a/src/static/riot/competitions/editor/_phases.tag +++ b/src/static/riot/competitions/editor/_phases.tag @@ -99,8 +99,8 @@
{ console.log(_.values(data.results)) - debugger return {success: true, results: _.values(data.results)} }, }, @@ -224,20 +223,16 @@ $(self.refs.public_data_multiselect).dropdown({ apiSettings: { - url: `${URLS.API}datasets/?search={query}`, + url: `${URLS.API}datasets/?search={query}&type=public_data`, onResponse: (data) => { - console.log(_.values(data.results)) - data.results.forEach((v,i,a) => { - v['value'] = v['key'] - }) - debugger + console.log(_.values(data.results)) return {success: true, results: _.values(data.results)} }, }, onAdd: self.public_data_added, onRemove: self.public_data_removed, }) - + $(self.refs.starting_kit_multiselect).dropdown({ apiSettings: { url: `${URLS.API}datasets/?search={query}&type=starting_kit`, @@ -246,8 +241,8 @@ return {success: true, results: _.values(data.results)} }, }, - // onAdd: self.task_added, - // onRemove: self.task_removed, + onAdd: self.starting_kit_added, + onRemove: self.starting_kit_removed, }) // When adding \ removing phase we need to code it like above @@ -273,14 +268,14 @@ /*--------------------------------------------------------------------- Methods ---------------------------------------------------------------------*/ + // Tasks self.task_added = (key, text, item) => { - debugger let index = _.findIndex(self.phase_tasks, (task) => { return task.value === key }) if (index === -1) { let task = {name: text, value: key, selected: true} - self.phase_tasks.push(task) + self.phase_tasks.push(task) } self.form_updated() } @@ -289,26 +284,27 @@ let index = _.findIndex(self.phase_tasks, (task) => { return task.value === key }) - debugger self.phase_tasks.splice(index, 1) self.form_updated() } + // Public Data self.public_data_added = (key, text, item) => { - debugger let index = _.findIndex(self.phase_public_data, (public_data) => { - debugger - return public_data.value === key + if (public_data === null){ + return false + }else{ + return public_data.value === key + } }) if (index === -1) { let public_data = {name: text, value: key, selected: true} - self.phase_public_data.push(public_data) + self.phase_public_data[0] = public_data } self.form_updated() } self.public_data_removed = (key, text, item) => { - debugger let index = _.findIndex(self.phase_public_data, (public_data) => { return public_data.value === key }) @@ -316,6 +312,31 @@ self.form_updated() } + // Starting Kit + self.starting_kit_added = (key, text, item) => { + let index = _.findIndex(self.phase_starting_kit, (starting_kit) => { + if (starting_kit === null){ + return false + }else{ + return starting_kit.value === key + } + }) + if (index === -1) { + let starting_kit = {name: text, value: key, selected: true} + self.phase_starting_kit[0] = starting_kit + } + self.form_updated() + } + + self.starting_kit_removed = (key, text, item) => { + let index = _.findIndex(self.phase_starting_kit, (starting_kit) => { + return starting_kit.value === key + }) + self.phase_starting_kit.splice(index, 1) + self.form_updated() + } + + self.show_modal = function () { $(self.refs.modal).modal('show') @@ -418,9 +439,8 @@ self.selected_phase_index = undefined self.phase_tasks = [] $(self.refs.multiselect).dropdown('clear') - // BB - I feel like if we need the above for tasks we need it for pulic_data and starting_kit - 06/17/2023 - // $(self.refs.public_data_multiselect).dropdown('clear') - // $(self.refs.starting_kit_multiselect).dropdown('clear') + $(self.refs.public_data_multiselect).dropdown('clear') + $(self.refs.starting_kit_multiselect).dropdown('clear') $(':input', self.refs.form) .not('[type="file"]') @@ -452,10 +472,8 @@ self.selected_phase_index = index var phase = self.phases[index] self.phase_tasks = phase.tasks - phase.public_data['value'] = phase.public_data['key'] - debugger self.phase_public_data = [phase.public_data] - self.phase_public_data + self.phase_starting_kit = [phase.starting_kit] self.update() @@ -470,7 +488,6 @@ $(self.refs.multiselect) .dropdown('change values', _.map(self.phase_tasks, task => { // renaming things to work w/ semantic UI multiselect - debugger return { value: task.value, text: task.name, @@ -478,28 +495,40 @@ selected: true, } })) - // BB - I feel like if we need the above for tasks we need it for phases - 06/17/2023 - $(self.refs.public_data_multiselect) - .dropdown('change values', _.map(self.phase_public_data, public_data => { - // renaming things to work w/ semantic UI multiselect - debugger - return { - //value: public_data.value, // Maybe need to grab from serializer? - value: public_data.key, - text: public_data.name, - name: public_data.name, - selected: true, - } - })) + // Setting Public Data + if(self.phase_public_data[0] != null){ + $(self.refs.public_data_multiselect) + .dropdown('change values', _.map(self.phase_public_data, public_data => { + // renaming things to work w/ semantic UI multiselect + return { + value: public_data.value, + text: public_data.name, + name: public_data.name, + selected: true, + } + })) + } + // Setting Starting Kit + if(self.phase_starting_kit[0] != null){ + $(self.refs.starting_kit_multiselect) + .dropdown('change values', _.map(self.phase_starting_kit, starting_kit => { + // renaming things to work w/ semantic UI multiselect + return { + //value: starting_kit.value, // Maybe need to grab from serializer? + value: starting_kit.value, + text: starting_kit.name, + name: starting_kit.name, + selected: true, + } + })) + } self.show_modal() // make semantic multiselect sortable -- Sortable library imported in competitions/form.html - debugger Sortable.create($('.search.dropdown.multiple', self.refs.tasks_select_container)[0]) - // BB - I feel like if we need the above for tasks we need it for public_data and starting_kit - 06/17/2023 Sortable.create($('.search.dropdown.multiple', self.refs.public_data_select_container)[0]) - //Sortable.create($('.search.dropdown.single', self.refs.starting_kit_select_container)[0]) + Sortable.create($('.search.dropdown.multiple', self.refs.starting_kit_select_container)[0]) self.form_check_is_valid() self.update() @@ -522,7 +551,6 @@ // Get tasks order from DOM and order the task array by that. let tasks_from_dom = [] $("#tasks_select_container a").each(function () { - debugger tasks_from_dom.push($(this).data("value")) }) let sorted_phase_tasks = [] @@ -539,10 +567,9 @@ }) self.phase_tasks = sorted_phase_tasks.slice() - // NOT DONE - BB - 06/17/2023 -> We need to grab selected phase and save it. + // Get public data from DOM let public_data_from_dom = [] $("#public_data_select_container a").each(function () { - debugger public_data_from_dom.push($(this).data("value")) }) let sorted_phase_public_data = [] @@ -559,11 +586,30 @@ }) self.phase_public_data = sorted_phase_public_data.slice() + // Get starting kit from DOM + let starting_kit_from_dom = [] + $("#starting_kit_select_container a").each(function () { + starting_kit_from_dom.push($(this).data("value")) + }) + let sorted_phase_starting_kit = [] + starting_kit_from_dom.forEach( function(key) { + let found = false; + self.phase_starting_kit = self.phase_starting_kit.filter(function (item) { + if(!found && item['value'] == key){ + sorted_phase_starting_kit.push(item) + found = true + return false + } else + return true + }) + }) + self.phase_starting_kit = sorted_phase_starting_kit.slice() + var data = get_form_data(self.refs.form) data.tasks = self.phase_tasks data.public_data = self.phase_public_data[0] + data.starting_kit = self.phase_starting_kit[0] data.task_instances = [] - debugger for(task of self.phase_tasks){ data.task_instances.push({ order_index: data.task_instances.length, @@ -586,7 +632,6 @@ // We have a selected phase, do an update instead of a create data.id = self.phases[self.selected_phase_index].id self.phases[self.selected_phase_index] = data - debugger } self.close_modal() } diff --git a/src/static/riot/competitions/editor/form.tag b/src/static/riot/competitions/editor/form.tag index 184590ffc..6a877d383 100644 --- a/src/static/riot/competitions/editor/form.tag +++ b/src/static/riot/competitions/editor/form.tag @@ -241,7 +241,6 @@ // Send competition_id for either create or update, won't hurt anything but is // useless for creation - api_endpoint(self.competition_return, self.opts.competition_id) .done(function (response) { self.errors = {} diff --git a/src/static/riot/tasks/management.tag b/src/static/riot/tasks/management.tag index 0fdf320f2..d9013d630 100644 --- a/src/static/riot/tasks/management.tag +++ b/src/static/riot/tasks/management.tag @@ -462,7 +462,6 @@ self.search_tasks = function () { var filter = self.refs.search.value - delay(() => self.update_tasks({search: filter}), 100) } From 777a8620d6dc734a2ff38163693b91591a40361d Mon Sep 17 00:00:00 2001 From: bbearce Date: Sat, 24 Jun 2023 21:03:51 -0400 Subject: [PATCH 05/15] 06_24_2023 progress --- src/apps/api/serializers/datasets.py | 11 ++++++++++- src/apps/competitions/tasks.py | 3 ++- .../competitions/unpackers/base_unpacker.py | 11 +++++++++++ src/apps/competitions/unpackers/v1.py | 18 +++++++++++++++++- 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/apps/api/serializers/datasets.py b/src/apps/api/serializers/datasets.py index 0b44b7ec1..a0b22c7c8 100644 --- a/src/apps/api/serializers/datasets.py +++ b/src/apps/api/serializers/datasets.py @@ -6,6 +6,7 @@ class DataSerializer(DefaultUserCreateMixin, serializers.ModelSerializer): + value = serializers.CharField(source='key', required=False) # BB double check request_sassy_file_name = serializers.CharField(required=True, max_length=255, write_only=True) class Meta: @@ -21,11 +22,14 @@ class Meta: 'in_use', 'id', 'key', + # Value is used for Semantic Multiselect dropdown api calls + 'value', # BB double check 'created_by', 'data_file', 'was_created_by_competition', 'competition', 'file_name', + ) read_only_fields = ( 'key', @@ -61,6 +65,8 @@ def create(self, validated_data): class DataSimpleSerializer(serializers.ModelSerializer): + value = serializers.CharField(source='key', required=False) # BB double check + class Meta: model = Data fields = ( @@ -68,12 +74,13 @@ class Meta: 'type', 'name', 'key', + 'value' # BB double check ) - class DataDetailSerializer(serializers.ModelSerializer): created_by = serializers.CharField(source='created_by.username') competition = serializers.SerializerMethodField() + value = serializers.CharField(source='key', required=False) # BB double check class Meta: model = Data @@ -86,6 +93,8 @@ class Meta: 'description', 'is_public', 'key', + # Value is used for Semantic Multiselect dropdown api calls + 'value', # BB double check 'was_created_by_competition', 'in_use', 'file_size', diff --git a/src/apps/competitions/tasks.py b/src/apps/competitions/tasks.py index 96a1a1d16..12cc52b0b 100644 --- a/src/apps/competitions/tasks.py +++ b/src/apps/competitions/tasks.py @@ -6,6 +6,8 @@ import zipfile from datetime import timedelta, datetime +from celery.contrib import rdb # BB + from io import BytesIO from tempfile import TemporaryDirectory, NamedTemporaryFile @@ -381,7 +383,6 @@ def mark_status_as_failed_and_delete_dataset(competition_creation_status, detail ) unpacker.unpack() - try: competition = unpacker.save() except ValidationError as e: diff --git a/src/apps/competitions/unpackers/base_unpacker.py b/src/apps/competitions/unpackers/base_unpacker.py index 281069ca1..eabefb579 100644 --- a/src/apps/competitions/unpackers/base_unpacker.py +++ b/src/apps/competitions/unpackers/base_unpacker.py @@ -3,6 +3,8 @@ import os import uuid +from celery.contrib import rdb + from django.core.files import File from django.test import RequestFactory from django.utils import timezone @@ -231,6 +233,7 @@ def _unpack_phases(self): "description": phase_description, "start": phase_start (datetime.datetime), "end": phase_end (datetime.datetime), + # BB public_data and starting_kit # ... See serializer for complete fields list "tasks": [list of indices that should match self.competition['tasks']] } @@ -315,6 +318,14 @@ def _save_competition(self): for phase in self.competition['phases']: phase['tasks'] = [self.competition['tasks'][index].key for index in phase['tasks']] phase['leaderboard'] = self.competition['leaderboards'][0].id + # rdb.set_trace() # BB + phase_public_data_file_data = phase['public_data'] + public_data_key, public_data_temp_data_path = self._get_data_key(**phase_public_data_file_data) + phase['public_data'] = Data.objects.filter(key = public_data_key)[0].id + phase_starting_kit_file_data = phase['starting_kit'] + starting_kit_key, starting_kit_temp_data_path = self._get_data_key(**phase_starting_kit_file_data) + phase['starting_kit'] = Data.objects.filter(key = starting_kit_key)[0].id + self.competition.pop('leaderboards') diff --git a/src/apps/competitions/unpackers/v1.py b/src/apps/competitions/unpackers/v1.py index 449569667..5c059446f 100644 --- a/src/apps/competitions/unpackers/v1.py +++ b/src/apps/competitions/unpackers/v1.py @@ -1,4 +1,5 @@ import os +from celery.contrib import rdb from competitions.unpackers.base_unpacker import BaseUnpacker from competitions.unpackers.utils import CompetitionUnpackingException, get_datetime @@ -97,7 +98,21 @@ def _unpack_phases(self): new_phase['end'] = get_datetime(end) else: new_phase['end'] = None - + + # Public Data and Starting Kit + new_phase['public_data'] = { + 'file_name': phase['public_data'], + 'file_path': os.path.join(self.temp_directory, phase['public_data']), + 'file_type': 'public_data', + 'creator': self.creator.id, + } + new_phase['starting_kit'] = { + 'file_name': phase['starting_kit'], + 'file_path': os.path.join(self.temp_directory, phase['starting_kit']), + 'file_type': 'starting_kit', + 'creator': self.creator.id, + } + task_index = len(self.competition['tasks']) new_phase['tasks'] = [task_index] self.competition['phases'].append(new_phase) @@ -118,6 +133,7 @@ def _unpack_phases(self): 'creator': self.creator.id, } self.competition['tasks'][task_index] = new_task + # rdb.set_trace() # BB self._validate_phase_ordering() self._set_phase_statuses() From 1a21a3de7bc01797ae05297304eb66637a188869 Mon Sep 17 00:00:00 2001 From: bbearce Date: Sun, 25 Jun 2023 17:12:59 -0400 Subject: [PATCH 06/15] unpackers, polishing and fixing test errors --- src/apps/api/serializers/datasets.py | 9 ++--- src/apps/api/serializers/tasks.py | 18 +++++++--- src/apps/competitions/tasks.py | 2 -- .../competitions/tests/unpacker_test_data.py | 6 +++- .../competitions/unpackers/base_unpacker.py | 3 -- src/apps/competitions/unpackers/v1.py | 34 +++++++++++-------- src/apps/competitions/unpackers/v2.py | 21 ++++++++++++ src/static/js/ours/utils.js | 6 ---- .../riot/competitions/editor/_phases.tag | 19 ++++++++--- 9 files changed, 76 insertions(+), 42 deletions(-) diff --git a/src/apps/api/serializers/datasets.py b/src/apps/api/serializers/datasets.py index a0b22c7c8..f247524a0 100644 --- a/src/apps/api/serializers/datasets.py +++ b/src/apps/api/serializers/datasets.py @@ -6,7 +6,6 @@ class DataSerializer(DefaultUserCreateMixin, serializers.ModelSerializer): - value = serializers.CharField(source='key', required=False) # BB double check request_sassy_file_name = serializers.CharField(required=True, max_length=255, write_only=True) class Meta: @@ -22,8 +21,6 @@ class Meta: 'in_use', 'id', 'key', - # Value is used for Semantic Multiselect dropdown api calls - 'value', # BB double check 'created_by', 'data_file', 'was_created_by_competition', @@ -65,7 +62,6 @@ def create(self, validated_data): class DataSimpleSerializer(serializers.ModelSerializer): - value = serializers.CharField(source='key', required=False) # BB double check class Meta: model = Data @@ -74,13 +70,12 @@ class Meta: 'type', 'name', 'key', - 'value' # BB double check ) class DataDetailSerializer(serializers.ModelSerializer): created_by = serializers.CharField(source='created_by.username') competition = serializers.SerializerMethodField() - value = serializers.CharField(source='key', required=False) # BB double check + value = serializers.CharField(source='key', required=False) class Meta: model = Data @@ -94,7 +89,7 @@ class Meta: 'is_public', 'key', # Value is used for Semantic Multiselect dropdown api calls - 'value', # BB double check + 'value', 'was_created_by_competition', 'in_use', 'file_size', diff --git a/src/apps/api/serializers/tasks.py b/src/apps/api/serializers/tasks.py index 737649931..1a94df211 100644 --- a/src/apps/api/serializers/tasks.py +++ b/src/apps/api/serializers/tasks.py @@ -28,7 +28,12 @@ class Meta: ] def get_size(self, instance): - return instance.data.file_size + try: + return instance.data.file_size + except AttributeError: + print("This solution has no data associated with it...might be a test") + return None + class SolutionListSerializer(serializers.ModelSerializer): @@ -193,6 +198,11 @@ def get_public_datasets(self, instance): reference_data = instance.task.reference_data ingestion_program = instance.task.ingestion_program scoring_program = instance.task.scoring_program - dataset_list_ids = [input_data.id, reference_data.id, ingestion_program.id, scoring_program.id] - qs = Data.objects.filter(id__in=dataset_list_ids) - return DataDetailSerializer(qs, many=True).data \ No newline at end of file + try: + dataset_list_ids = [input_data.id, reference_data.id, ingestion_program.id, scoring_program.id] + qs = Data.objects.filter(id__in=dataset_list_ids) + return DataDetailSerializer(qs, many=True).data + except AttributeError: + print("This phase task has no datasets") + return None + \ No newline at end of file diff --git a/src/apps/competitions/tasks.py b/src/apps/competitions/tasks.py index 12cc52b0b..0a6c38ac8 100644 --- a/src/apps/competitions/tasks.py +++ b/src/apps/competitions/tasks.py @@ -6,8 +6,6 @@ import zipfile from datetime import timedelta, datetime -from celery.contrib import rdb # BB - from io import BytesIO from tempfile import TemporaryDirectory, NamedTemporaryFile diff --git a/src/apps/competitions/tests/unpacker_test_data.py b/src/apps/competitions/tests/unpacker_test_data.py index eb38945e9..f9e38b1a6 100644 --- a/src/apps/competitions/tests/unpacker_test_data.py +++ b/src/apps/competitions/tests/unpacker_test_data.py @@ -203,6 +203,8 @@ 'auto_migrate_to_this_phase': False, 'has_max_submissions': True, 'end': datetime.datetime(2019, 9, 30, 0, 0, tzinfo=timezone.now().tzinfo), + 'public_data': None, + 'starting_kit': None, 'tasks': [0], 'status': 'Previous', }, @@ -216,9 +218,11 @@ 'max_submissions_per_person': None, 'auto_migrate_to_this_phase': True, 'has_max_submissions': True, + 'end': None, + 'public_data': None, + 'starting_kit': None, 'tasks': [1], 'status': 'Current', - 'end': None, 'is_final_phase': True, } ] diff --git a/src/apps/competitions/unpackers/base_unpacker.py b/src/apps/competitions/unpackers/base_unpacker.py index eabefb579..4a0a1781c 100644 --- a/src/apps/competitions/unpackers/base_unpacker.py +++ b/src/apps/competitions/unpackers/base_unpacker.py @@ -3,8 +3,6 @@ import os import uuid -from celery.contrib import rdb - from django.core.files import File from django.test import RequestFactory from django.utils import timezone @@ -318,7 +316,6 @@ def _save_competition(self): for phase in self.competition['phases']: phase['tasks'] = [self.competition['tasks'][index].key for index in phase['tasks']] phase['leaderboard'] = self.competition['leaderboards'][0].id - # rdb.set_trace() # BB phase_public_data_file_data = phase['public_data'] public_data_key, public_data_temp_data_path = self._get_data_key(**phase_public_data_file_data) phase['public_data'] = Data.objects.filter(key = public_data_key)[0].id diff --git a/src/apps/competitions/unpackers/v1.py b/src/apps/competitions/unpackers/v1.py index 5c059446f..07d48d901 100644 --- a/src/apps/competitions/unpackers/v1.py +++ b/src/apps/competitions/unpackers/v1.py @@ -1,5 +1,4 @@ import os -from celery.contrib import rdb from competitions.unpackers.base_unpacker import BaseUnpacker from competitions.unpackers.utils import CompetitionUnpackingException, get_datetime @@ -100,18 +99,26 @@ def _unpack_phases(self): new_phase['end'] = None # Public Data and Starting Kit - new_phase['public_data'] = { - 'file_name': phase['public_data'], - 'file_path': os.path.join(self.temp_directory, phase['public_data']), - 'file_type': 'public_data', - 'creator': self.creator.id, - } - new_phase['starting_kit'] = { - 'file_name': phase['starting_kit'], - 'file_path': os.path.join(self.temp_directory, phase['starting_kit']), - 'file_type': 'starting_kit', - 'creator': self.creator.id, - } + try: + new_phase['public_data'] = { + 'file_name': phase['public_data'], + 'file_path': os.path.join(self.temp_directory, phase['public_data']), + 'file_type': 'public_data', + 'creator': self.creator.id, + } + except KeyError: + new_phase['public_data'] = None + + try: + new_phase['starting_kit'] = { + 'file_name': phase['starting_kit'], + 'file_path': os.path.join(self.temp_directory, phase['starting_kit']), + 'file_type': 'starting_kit', + 'creator': self.creator.id, + } + except KeyError: + new_phase['starting_kit'] = None + task_index = len(self.competition['tasks']) new_phase['tasks'] = [task_index] @@ -133,7 +140,6 @@ def _unpack_phases(self): 'creator': self.creator.id, } self.competition['tasks'][task_index] = new_task - # rdb.set_trace() # BB self._validate_phase_ordering() self._set_phase_statuses() diff --git a/src/apps/competitions/unpackers/v2.py b/src/apps/competitions/unpackers/v2.py index f6bd941fb..a8eb27ccc 100644 --- a/src/apps/competitions/unpackers/v2.py +++ b/src/apps/competitions/unpackers/v2.py @@ -203,6 +203,27 @@ def _unpack_phases(self): if new_phase['max_submissions_per_day'] or new_phase['max_submissions_per_person']: new_phase['has_max_submissions'] = True + # Public Data and Starting Kit + try: + new_phase['public_data'] = { + 'file_name': phase_data['public_data'], + 'file_path': os.path.join(self.temp_directory, phase_data['public_data']), + 'file_type': 'public_data', + 'creator': self.creator.id, + } + except: + new_phase['public_data'] = None + + try: + new_phase['starting_kit'] = { + 'file_name': phase_data['starting_kit'], + 'file_path': os.path.join(self.temp_directory, phase_data['starting_kit']), + 'file_type': 'starting_kit', + 'creator': self.creator.id, + } + except: + new_phase['starting_kit'] = None + self.competition['phases'].append(new_phase) self._validate_phase_ordering() self._set_phase_statuses() diff --git a/src/static/js/ours/utils.js b/src/static/js/ours/utils.js index 671ccccd3..7407c5aa2 100644 --- a/src/static/js/ours/utils.js +++ b/src/static/js/ours/utils.js @@ -96,13 +96,10 @@ function get_form_fields(base_element) { //return $(':input', self.root).not('button').not('[readonly]').each(function (i, field) { // console.log(field) //}) - // debugger form_fields = $(':input', base_element).not('button').not('[readonly]') // Calendars come through as read-only and jQuery leaves them out calendars = $('.two.fields .ui.calendar.field input[type="text"]') - console.log(calendars) readonly_calendars = $('.two.fields .ui.calendar.field [readonly]') - console.log(readonly_calendars) // If calendars is readonly_calendars, then append them to form_fields if (calendars.length === readonly_calendars.length) { var isIdentical = true; @@ -114,7 +111,6 @@ function get_form_fields(base_element) { }); if (isIdentical) { - // console.log("The two sets are identical."); form_fields = form_fields.add(calendars) } else { // console.log("The two sets are not identical."); @@ -130,8 +126,6 @@ function get_form_data(base_element) { var fields = get_form_fields(base_element) var data = {} fields.each(function (i, field) { - console.log(field) - console.log(data) if (!!field.name) { data[field.name] = $(field).val() } diff --git a/src/static/riot/competitions/editor/_phases.tag b/src/static/riot/competitions/editor/_phases.tag index ef59166de..16a358bdf 100644 --- a/src/static/riot/competitions/editor/_phases.tag +++ b/src/static/riot/competitions/editor/_phases.tag @@ -104,7 +104,7 @@ data-position="bottom center">
@@ -114,7 +114,7 @@ data-position="bottom center">
@@ -213,7 +213,6 @@ apiSettings: { url: `${URLS.API}tasks/?search={query}`, onResponse: (data) => { - console.log(_.values(data.results)) return {success: true, results: _.values(data.results)} }, }, @@ -225,7 +224,6 @@ apiSettings: { url: `${URLS.API}datasets/?search={query}&type=public_data`, onResponse: (data) => { - console.log(_.values(data.results)) return {success: true, results: _.values(data.results)} }, }, @@ -237,7 +235,6 @@ apiSettings: { url: `${URLS.API}datasets/?search={query}&type=starting_kit`, onResponse: (data) => { - console.log(_.values(data.results)) return {success: true, results: _.values(data.results)} }, }, @@ -294,6 +291,12 @@ if (public_data === null){ return false }else{ + if (public_data.value != key){ + // Remove if not first selected. We can have only one. + $('a[data-value="'+ key +'"]').remove(); + alert("Only one Public Data set allowed per phase.") + return true + } return public_data.value === key } }) @@ -318,6 +321,12 @@ if (starting_kit === null){ return false }else{ + if (starting_kit.value != key){ + // Remove if not first selected. We can have only one. + $('a[data-value="'+ key +'"]').remove(); + alert("Only one Starting Kit set allowed per phase.") + return true + } return starting_kit.value === key } }) From 4a186948bf750a39102fc3d06913015c4225d692 Mon Sep 17 00:00:00 2001 From: bbearce Date: Sun, 25 Jun 2023 18:41:56 -0400 Subject: [PATCH 07/15] last bit of test fixes. --- src/apps/competitions/unpackers/base_unpacker.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/apps/competitions/unpackers/base_unpacker.py b/src/apps/competitions/unpackers/base_unpacker.py index 4a0a1781c..15c724829 100644 --- a/src/apps/competitions/unpackers/base_unpacker.py +++ b/src/apps/competitions/unpackers/base_unpacker.py @@ -317,11 +317,13 @@ def _save_competition(self): phase['tasks'] = [self.competition['tasks'][index].key for index in phase['tasks']] phase['leaderboard'] = self.competition['leaderboards'][0].id phase_public_data_file_data = phase['public_data'] - public_data_key, public_data_temp_data_path = self._get_data_key(**phase_public_data_file_data) - phase['public_data'] = Data.objects.filter(key = public_data_key)[0].id phase_starting_kit_file_data = phase['starting_kit'] - starting_kit_key, starting_kit_temp_data_path = self._get_data_key(**phase_starting_kit_file_data) - phase['starting_kit'] = Data.objects.filter(key = starting_kit_key)[0].id + if phase_public_data_file_data is not None: + public_data_key, public_data_temp_data_path = self._get_data_key(**phase_public_data_file_data) + phase['public_data'] = Data.objects.filter(key = public_data_key)[0].id + if phase_starting_kit_file_data is not None: + starting_kit_key, starting_kit_temp_data_path = self._get_data_key(**phase_starting_kit_file_data) + phase['starting_kit'] = Data.objects.filter(key = starting_kit_key)[0].id self.competition.pop('leaderboards') From ed6aa9a24e13190f6622be067f5ab4fcf9241a2b Mon Sep 17 00:00:00 2001 From: bbearce Date: Sun, 25 Jun 2023 20:31:45 -0400 Subject: [PATCH 08/15] flake issues --- src/apps/api/serializers/competitions.py | 6 ++-- src/apps/api/serializers/datasets.py | 5 +-- src/apps/api/serializers/tasks.py | 10 +++--- src/apps/api/views/competitions.py | 35 +++++++++---------- src/apps/competitions/models.py | 3 +- .../competitions/unpackers/base_unpacker.py | 5 ++- src/apps/competitions/unpackers/v1.py | 27 +++++++------- src/apps/competitions/unpackers/v2.py | 28 +++++++-------- 8 files changed, 57 insertions(+), 62 deletions(-) diff --git a/src/apps/api/serializers/competitions.py b/src/apps/api/serializers/competitions.py index 28ff021cf..9b27ca08d 100644 --- a/src/apps/api/serializers/competitions.py +++ b/src/apps/api/serializers/competitions.py @@ -4,7 +4,7 @@ from api.fields import NamedBase64ImageField from api.mixins import DefaultUserCreateMixin -from api.serializers.datasets import DataDetailSerializer, DataSimpleSerializer +from api.serializers.datasets import DataDetailSerializer from api.serializers.leaderboards import LeaderboardSerializer, ColumnSerializer from api.serializers.profiles import CollaboratorSerializer from api.serializers.submissions import SubmissionScoreSerializer @@ -44,7 +44,7 @@ class Meta: 'leaderboard', 'public_data', 'starting_kit', - 'is_final_phase', + 'is_final_phase', ) def get_status(self, obj): @@ -92,7 +92,7 @@ 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) diff --git a/src/apps/api/serializers/datasets.py b/src/apps/api/serializers/datasets.py index f247524a0..25e069afc 100644 --- a/src/apps/api/serializers/datasets.py +++ b/src/apps/api/serializers/datasets.py @@ -26,7 +26,7 @@ class Meta: 'was_created_by_competition', 'competition', 'file_name', - + ) read_only_fields = ( 'key', @@ -62,7 +62,7 @@ def create(self, validated_data): class DataSimpleSerializer(serializers.ModelSerializer): - + class Meta: model = Data fields = ( @@ -72,6 +72,7 @@ class Meta: 'key', ) + class DataDetailSerializer(serializers.ModelSerializer): created_by = serializers.CharField(source='created_by.username') competition = serializers.SerializerMethodField() diff --git a/src/apps/api/serializers/tasks.py b/src/apps/api/serializers/tasks.py index 1a94df211..b98ce36ea 100644 --- a/src/apps/api/serializers/tasks.py +++ b/src/apps/api/serializers/tasks.py @@ -26,14 +26,13 @@ class Meta: 'md5', 'size', ] - + def get_size(self, instance): try: return instance.data.file_size except AttributeError: print("This solution has no data associated with it...might be a test") return None - class SolutionListSerializer(serializers.ModelSerializer): @@ -172,7 +171,7 @@ class PhaseTaskInstanceSerializer(serializers.HyperlinkedModelSerializer): name = serializers.CharField(source='task.name', required=False) solutions = serializers.SerializerMethodField() public_datasets = serializers.SerializerMethodField() - + class Meta: model = PhaseTaskInstance fields = ( @@ -188,7 +187,7 @@ class Meta: 'solutions', 'public_datasets' ) - + def get_solutions(self, instance): qs = instance.task.solutions.all() return SolutionSerializer(qs, many=True).data @@ -200,9 +199,8 @@ def get_public_datasets(self, instance): scoring_program = instance.task.scoring_program try: dataset_list_ids = [input_data.id, reference_data.id, ingestion_program.id, scoring_program.id] - qs = Data.objects.filter(id__in=dataset_list_ids) + qs = Data.objects.filter(id__in=dataset_list_ids) return DataDetailSerializer(qs, many=True).data except AttributeError: print("This phase task has no datasets") return None - \ No newline at end of file diff --git a/src/apps/api/views/competitions.py b/src/apps/api/views/competitions.py index 1e530545d..6403025c6 100644 --- a/src/apps/api/views/competitions.py +++ b/src/apps/api/views/competitions.py @@ -230,24 +230,23 @@ def update(self, request, *args, **kwargs): phase['leaderboard'] = leaderboard_id - - # Get public_data and starting_kit - for phase in data['phases']: - # We just need to know what public_data and starting_kit go with this phase - # We don't need to serialize the whole object - try: - phase['public_data'] = Data.objects.filter(key=phase['public_data']['value'])[0].id - except: - phase['public_data'] = None - try: - phase['starting_kit'] = Data.objects.filter(key=phase['starting_kit']['value'])[0].id - except: - phase['starting_kit'] = None - - serializer = self.get_serializer(instance, data=data, partial=partial) - type(serializer) - serializer.is_valid(raise_exception=True) - self.perform_update(serializer) + # Get public_data and starting_kit + for phase in data['phases']: + # We just need to know what public_data and starting_kit go with this phase + # We don't need to serialize the whole object + try: + phase['public_data'] = Data.objects.filter(key=phase['public_data']['value'])[0].id + except AttributeError: + phase['public_data'] = None + try: + phase['starting_kit'] = Data.objects.filter(key=phase['starting_kit']['value'])[0].id + except AttributeError: + phase['starting_kit'] = None + + serializer = self.get_serializer(instance, data=data, partial=partial) + type(serializer) + serializer.is_valid(raise_exception=True) + self.perform_update(serializer) if getattr(instance, '_prefetched_objects_cache', None): # If 'prefetch_related' has been applied to a queryset, we need to diff --git a/src/apps/competitions/models.py b/src/apps/competitions/models.py index 2d8818c56..6d89b4ad5 100644 --- a/src/apps/competitions/models.py +++ b/src/apps/competitions/models.py @@ -282,10 +282,9 @@ class Phase(ChaHubSaveMixin, models.Model): leaderboard = models.ForeignKey('leaderboards.Leaderboard', on_delete=models.DO_NOTHING, null=True, blank=True, related_name="phases") - + public_data = models.ForeignKey('datasets.Data', on_delete=models.SET_NULL, null=True, blank=True, related_name="phase_public_data") starting_kit = models.ForeignKey('datasets.Data', on_delete=models.SET_NULL, null=True, blank=True, related_name="phase_starting_kit") - class Meta: ordering = ('index',) diff --git a/src/apps/competitions/unpackers/base_unpacker.py b/src/apps/competitions/unpackers/base_unpacker.py index 15c724829..a769dc2eb 100644 --- a/src/apps/competitions/unpackers/base_unpacker.py +++ b/src/apps/competitions/unpackers/base_unpacker.py @@ -320,11 +320,10 @@ def _save_competition(self): phase_starting_kit_file_data = phase['starting_kit'] if phase_public_data_file_data is not None: public_data_key, public_data_temp_data_path = self._get_data_key(**phase_public_data_file_data) - phase['public_data'] = Data.objects.filter(key = public_data_key)[0].id + phase['public_data'] = Data.objects.filter(key=public_data_key)[0].id if phase_starting_kit_file_data is not None: starting_kit_key, starting_kit_temp_data_path = self._get_data_key(**phase_starting_kit_file_data) - phase['starting_kit'] = Data.objects.filter(key = starting_kit_key)[0].id - + phase['starting_kit'] = Data.objects.filter(key=starting_kit_key)[0].id self.competition.pop('leaderboards') diff --git a/src/apps/competitions/unpackers/v1.py b/src/apps/competitions/unpackers/v1.py index 07d48d901..1e4dee3cc 100644 --- a/src/apps/competitions/unpackers/v1.py +++ b/src/apps/competitions/unpackers/v1.py @@ -97,29 +97,28 @@ def _unpack_phases(self): new_phase['end'] = get_datetime(end) else: new_phase['end'] = None - + # Public Data and Starting Kit try: new_phase['public_data'] = { - 'file_name': phase['public_data'], - 'file_path': os.path.join(self.temp_directory, phase['public_data']), - 'file_type': 'public_data', - 'creator': self.creator.id, - } + 'file_name': phase['public_data'], + 'file_path': os.path.join(self.temp_directory, phase['public_data']), + 'file_type': 'public_data', + 'creator': self.creator.id, + } except KeyError: new_phase['public_data'] = None - + try: new_phase['starting_kit'] = { - 'file_name': phase['starting_kit'], - 'file_path': os.path.join(self.temp_directory, phase['starting_kit']), - 'file_type': 'starting_kit', - 'creator': self.creator.id, - } + 'file_name': phase['starting_kit'], + 'file_path': os.path.join(self.temp_directory, phase['starting_kit']), + 'file_type': 'starting_kit', + 'creator': self.creator.id, + } except KeyError: new_phase['starting_kit'] = None - - + task_index = len(self.competition['tasks']) new_phase['tasks'] = [task_index] self.competition['phases'].append(new_phase) diff --git a/src/apps/competitions/unpackers/v2.py b/src/apps/competitions/unpackers/v2.py index a8eb27ccc..3dbe733eb 100644 --- a/src/apps/competitions/unpackers/v2.py +++ b/src/apps/competitions/unpackers/v2.py @@ -206,24 +206,24 @@ def _unpack_phases(self): # Public Data and Starting Kit try: new_phase['public_data'] = { - 'file_name': phase_data['public_data'], - 'file_path': os.path.join(self.temp_directory, phase_data['public_data']), - 'file_type': 'public_data', - 'creator': self.creator.id, - } - except: + 'file_name': phase_data['public_data'], + 'file_path': os.path.join(self.temp_directory, phase_data['public_data']), + 'file_type': 'public_data', + 'creator': self.creator.id, + } + except AttributeError: new_phase['public_data'] = None - + try: new_phase['starting_kit'] = { - 'file_name': phase_data['starting_kit'], - 'file_path': os.path.join(self.temp_directory, phase_data['starting_kit']), - 'file_type': 'starting_kit', - 'creator': self.creator.id, - } - except: + 'file_name': phase_data['starting_kit'], + 'file_path': os.path.join(self.temp_directory, phase_data['starting_kit']), + 'file_type': 'starting_kit', + 'creator': self.creator.id, + } + except AttributeError: new_phase['starting_kit'] = None - + self.competition['phases'].append(new_phase) self._validate_phase_ordering() self._set_phase_statuses() From d4f43ed95bf624b55aa257b456611ca8d087bd79 Mon Sep 17 00:00:00 2001 From: bbearce Date: Sun, 25 Jun 2023 20:39:43 -0400 Subject: [PATCH 09/15] pytest errors --- src/apps/api/views/competitions.py | 4 ++-- src/apps/competitions/unpackers/v2.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/apps/api/views/competitions.py b/src/apps/api/views/competitions.py index 6403025c6..7d2a5570d 100644 --- a/src/apps/api/views/competitions.py +++ b/src/apps/api/views/competitions.py @@ -236,11 +236,11 @@ def update(self, request, *args, **kwargs): # We don't need to serialize the whole object try: phase['public_data'] = Data.objects.filter(key=phase['public_data']['value'])[0].id - except AttributeError: + except TypeError: phase['public_data'] = None try: phase['starting_kit'] = Data.objects.filter(key=phase['starting_kit']['value'])[0].id - except AttributeError: + except TypeError: phase['starting_kit'] = None serializer = self.get_serializer(instance, data=data, partial=partial) diff --git a/src/apps/competitions/unpackers/v2.py b/src/apps/competitions/unpackers/v2.py index 3dbe733eb..4f5488f22 100644 --- a/src/apps/competitions/unpackers/v2.py +++ b/src/apps/competitions/unpackers/v2.py @@ -211,7 +211,7 @@ def _unpack_phases(self): 'file_type': 'public_data', 'creator': self.creator.id, } - except AttributeError: + except KeyError: new_phase['public_data'] = None try: @@ -221,7 +221,7 @@ def _unpack_phases(self): 'file_type': 'starting_kit', 'creator': self.creator.id, } - except AttributeError: + except KeyError: new_phase['starting_kit'] = None self.competition['phases'].append(new_phase) From e43c51c74d4ee2c1c56d47566a8c290eaa03f2e0 Mon Sep 17 00:00:00 2001 From: Benjamin Bearce Date: Wed, 12 Jul 2023 18:15:51 -0400 Subject: [PATCH 10/15] single select and final touches --- src/apps/api/views/competitions.py | 4 ++-- src/static/riot/competitions/detail/_tabs.tag | 8 ++++++-- .../riot/competitions/editor/_phases.tag | 18 +++++++++--------- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/apps/api/views/competitions.py b/src/apps/api/views/competitions.py index 7d2a5570d..df4770cee 100644 --- a/src/apps/api/views/competitions.py +++ b/src/apps/api/views/competitions.py @@ -236,11 +236,11 @@ def update(self, request, *args, **kwargs): # We don't need to serialize the whole object try: phase['public_data'] = Data.objects.filter(key=phase['public_data']['value'])[0].id - except TypeError: + except KeyError: phase['public_data'] = None try: phase['starting_kit'] = Data.objects.filter(key=phase['starting_kit']['value'])[0].id - except TypeError: + except KeyError: phase['starting_kit'] = None serializer = self.get_serializer(instance, data=data, partial=partial) diff --git a/src/static/riot/competitions/detail/_tabs.tag b/src/static/riot/competitions/detail/_tabs.tag index 35e199df8..23de534a0 100644 --- a/src/static/riot/competitions/detail/_tabs.tag +++ b/src/static/riot/competitions/detail/_tabs.tag @@ -66,7 +66,8 @@ {file.phase} {file.task} {file.type} - {file.type === 'Public Data' || file.type === 'Starting Kit' ? 'yes': 'no'} + + yes {filesize(file.file_size * 1024)} @@ -225,7 +226,10 @@ }) // ...that ordering happens here self.competition.files.push(input_data) - self.competition.files.push(reference_data) + // debugger + if(self.competition.admin){ + self.competition.files.push(reference_data) + } self.competition.files.push(ingestion_program) self.competition.files.push(scoring_program) }) diff --git a/src/static/riot/competitions/editor/_phases.tag b/src/static/riot/competitions/editor/_phases.tag index 16a358bdf..b22b002d1 100644 --- a/src/static/riot/competitions/editor/_phases.tag +++ b/src/static/riot/competitions/editor/_phases.tag @@ -97,7 +97,7 @@
-
+
-
+
-
Files
-
@@ -42,7 +40,6 @@
-
@@ -72,7 +69,7 @@ - @@ -81,7 +78,6 @@ - @@ -250,26 +246,27 @@ }) }) // Need code for public_data and starting_kit at phase level - - if (phase.starting_kit != null){ - self.competition.files.push({ - key: phase.starting_kit.key, - name: phase.starting_kit.name, - file_size: phase.starting_kit.file_size, - phase: phase.name, - task: '-Phase Level Data-', - type: 'Starting Kit' - }) - } - if (phase.public_data != null){ - self.competition.files.push({ - key: phase.public_data.key, - name: phase.public_data.name, - file_size: phase.public_data.file_size, - phase: phase.name, - task: '-Phase Level Data-', - type: 'Public Data' - }) + if(self.competition.participant_status === 'approved'){ + if (phase.starting_kit != null){ + self.competition.files.push({ + key: phase.starting_kit.key, + name: phase.starting_kit.name, + file_size: phase.starting_kit.file_size, + phase: phase.name, + task: '-Phase Level Data-', + type: 'Starting Kit' + }) + } + if (phase.public_data != null){ + self.competition.files.push({ + key: phase.public_data.key, + name: phase.public_data.name, + file_size: phase.public_data.file_size, + phase: phase.name, + task: '-Phase Level Data-', + type: 'Public Data' + }) + } } }) // loop over competition phases to mark if phase has started or ended From 0743a2368cdd6609e275875747ab7412bf7493af Mon Sep 17 00:00:00 2001 From: didayolo Date: Tue, 25 Jul 2023 11:39:54 +0200 Subject: [PATCH 15/15] Improve display --- src/static/riot/competitions/detail/_tabs.tag | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/static/riot/competitions/detail/_tabs.tag b/src/static/riot/competitions/detail/_tabs.tag index 0558e896e..57ceacb71 100644 --- a/src/static/riot/competitions/detail/_tabs.tag +++ b/src/static/riot/competitions/detail/_tabs.tag @@ -216,7 +216,7 @@ type = 'Input Data' input_data = {key: dataset.key, name: dataset.name, file_size: dataset.file_size, phase: phase.name, task: task.name, type: type} }else if(dataset.type === "reference_data"){ - type = 'Reference Data (private?)' + type = 'Reference Data' reference_data = {key: dataset.key, name: dataset.name, file_size: dataset.file_size, phase: phase.name, task: task.name, type: type} }else if(dataset.type === "ingestion_program"){ type = 'Ingestion Program' @@ -253,7 +253,7 @@ name: phase.starting_kit.name, file_size: phase.starting_kit.file_size, phase: phase.name, - task: '-Phase Level Data-', + task: '-', type: 'Starting Kit' }) } @@ -263,7 +263,7 @@ name: phase.public_data.name, file_size: phase.public_data.file_size, phase: phase.name, - task: '-Phase Level Data-', + task: '-', type: 'Public Data' }) }
+ No Files Available Yet