Skip to content
Merged
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
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
![CodaBench logo](src/static/img/codabench_black.png) [![Circle CI](https://circleci.com/gh/codalab/codabench.svg?style=shield)](https://app.circleci.com/pipelines/github/codalab/codabench)
![Codabench logo](src/static/img/codabench_black.png) [![Circle CI](https://circleci.com/gh/codalab/codabench.svg?style=shield)](https://app.circleci.com/pipelines/github/codalab/codabench)

## What is CodaBench?
## What is Codabench?

CodaBench is an open-source web-based platform that enables researchers, developers, and data scientists to collaborate, with the goal of advancing research fields where machine learning and advanced computation is used. CodaBench helps to solve many common problems in the arena of data-oriented research through its online community where people can share worksheets and participate in competitions and benchmarks. It can be seen as a version 2 of [CodaLab Competitions](https://github.com/codalab/codalab-competitions).
Codabench is an open-source web-based platform that enables researchers, developers, and data scientists to collaborate, with the goal of advancing research fields where machine learning and advanced computation is used. Codabench helps to solve many common problems in the arena of data-oriented research through its online community where people can share worksheets and participate in competitions and benchmarks. It can be seen as a version 2 of [CodaLab Competitions](https://github.com/codalab/codalab-competitions).

To see CodaBench in action, visit [codabench.org](https://www.codabench.org/).
To see Codabench in action, visit [codabench.org](https://www.codabench.org/).


## Documentation

- [CodaBench Wiki](https://github.com/codalab/codabench/wiki)
- [Codabench Wiki](https://github.com/codalab/codabench/wiki)


## Quick installation (for Linux)

_To participate, or even organize your own benchmarks or competitions, **you don't need to install anything**, you just need to sign in an instance of the platform (e.g. [this one](https://www.codabench.org/)).
If you wish to configure your own instance of CodaBench platform, here are the instructions:_
If you wish to configure your own instance of Codabench platform, here are the instructions:_


```
Expand All @@ -40,7 +40,7 @@ The text of the Apache License 2.0 can be found online at:
http://www.opensource.org/licenses/apache2.0.php


## Cite CodaBench in your research
## Cite Codabench in your research

```
@article{codabench,
Expand Down
Empty file.
6 changes: 6 additions & 0 deletions src/apps/announcements/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.contrib import admin

from . import models

admin.site.register(models.Announcement)
admin.site.register(models.NewsPost)
5 changes: 5 additions & 0 deletions src/apps/announcements/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class AnnouncementsConfig(AppConfig):
name = 'announcements'
32 changes: 32 additions & 0 deletions src/apps/announcements/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Generated by Django 2.2.17 on 2023-06-15 18:12

from django.db import migrations, models
import django.utils.timezone


class Migration(migrations.Migration):

initial = True

dependencies = [
]

operations = [
migrations.CreateModel(
name='Announcement',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('text', models.TextField(blank=True, null=True)),
],
),
migrations.CreateModel(
name='NewsPost',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=40, unique=True)),
('link', models.URLField()),
('created_when', models.DateTimeField(default=django.utils.timezone.now)),
('text', models.TextField(blank=True, null=True)),
],
),
]
18 changes: 18 additions & 0 deletions src/apps/announcements/migrations/0002_auto_20230615_2012.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 2.2.17 on 2023-06-15 20:12

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('announcements', '0001_initial'),
]

operations = [
migrations.AlterField(
model_name='newspost',
name='title',
field=models.CharField(max_length=40),
),
]
18 changes: 18 additions & 0 deletions src/apps/announcements/migrations/0003_auto_20230616_1326.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 2.2.17 on 2023-06-16 13:26

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('announcements', '0002_auto_20230615_2012'),
]

operations = [
migrations.AlterField(
model_name='newspost',
name='link',
field=models.URLField(blank=True),
),
]
Empty file.
13 changes: 13 additions & 0 deletions src/apps/announcements/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from django.db import models
from django.utils.timezone import now


class Announcement(models.Model):
text = models.TextField(null=True, blank=True)


class NewsPost(models.Model):
title = models.CharField(max_length=40)
link = models.URLField(max_length=200, blank=True)
created_when = models.DateTimeField(default=now)
text = models.TextField(null=True, blank=True)
87 changes: 87 additions & 0 deletions src/apps/api/serializers/competitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
from tasks.models import Task

from api.serializers.queues import QueueSerializer
from datetime import datetime


class PhaseSerializer(WritableNestedModelSerializer):
tasks = serializers.SlugRelatedField(queryset=Task.objects.all(), required=True, allow_null=False, slug_field='key',
many=True)
status = serializers.SerializerMethodField()

class Meta:
model = Phase
Expand All @@ -42,6 +44,42 @@ class Meta:
'is_final_phase',
)

def get_status(self, obj):

now = datetime.now().replace(tzinfo=None)
start = obj.start.replace(tzinfo=None)
end = obj.end.replace(tzinfo=None) if obj.end else obj.end
phase_ended = False
phase_started = False

# check if phase has started
if start > now:
# 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 date exists for this phase
if end:
if end < now:
# 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

if phase_started and phase_ended:
return Phase.PREVIOUS
elif phase_started and (not phase_ended):
return Phase.CURRENT
elif not phase_started:
return Phase.NEXT

def validate_leaderboard(self, value):
if not value:
raise ValidationError("Phases require a leaderboard")
Expand All @@ -50,6 +88,7 @@ def validate_leaderboard(self, value):

class PhaseDetailSerializer(serializers.ModelSerializer):
tasks = PhaseTaskInstanceSerializer(source='task_instances', many=True)
status = serializers.SerializerMethodField()

class Meta:
model = Phase
Expand All @@ -71,6 +110,42 @@ class Meta:
'is_final_phase',
)

def get_status(self, obj):

now = datetime.now().replace(tzinfo=None)
start = obj.start.replace(tzinfo=None)
end = obj.end.replace(tzinfo=None) if obj.end else obj.end
phase_ended = False
phase_started = False

# check if phase has started
if start > now:
# 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 date exists for this phase
if end:
if end < now:
# 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

if phase_started and phase_ended:
return Phase.PREVIOUS
elif phase_started and (not phase_ended):
return Phase.CURRENT
elif not phase_started:
return Phase.NEXT


class PhaseUpdateSerializer(PhaseSerializer):
tasks = PhaseTaskInstanceSerializer(source='task_instances', many=True)
Expand Down Expand Up @@ -262,6 +337,18 @@ class Meta:
class CompetitionParticipantSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username')
is_bot = serializers.BooleanField(source='user.is_bot')

class Meta:
model = CompetitionParticipant
fields = (
'username',
'is_bot',
)


class CompetitionParticipantWithEmailSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username')
is_bot = serializers.BooleanField(source='user.is_bot')
email = serializers.CharField(source='user.email')

class Meta:
Expand Down
134 changes: 134 additions & 0 deletions src/apps/api/tests/test_cleanup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
from rest_framework.test import APITestCase
from django.urls import reverse
import json
from factories import (
UserFactory,
CompetitionFactory,
PhaseFactory,
TaskFactory,
SubmissionFactory,
DataFactory
)
from competitions.models import Submission
from datasets.models import Data


class CleanUpTests(APITestCase):
def setUp(self):

# Create a user
user = UserFactory(username='test_user', password='test_user')

# Create a competition
comp = CompetitionFactory(created_by=user)

# Create used tasks
self.used_tasks = [
TaskFactory(created_by=user),
TaskFactory(created_by=user)
]

# Create unused task
self.unused_tasks = [
TaskFactory(created_by=user),
TaskFactory(created_by=user)
]

# Create phase with used tasks
phase = PhaseFactory(competition=comp, tasks=self.used_tasks)

# Create used-failed submission
self.failed_submissions = [SubmissionFactory(
phase=phase,
owner=user,
status=Submission.FAILED,
data=DataFactory(created_by=user, type=Data.SUBMISSION, competition=comp)
)]

# Create unused submission
self.unused_submissions = [
DataFactory(created_by=user, type=Data.SUBMISSION),
DataFactory(created_by=user, type=Data.SUBMISSION)
]

# Create unused datasets and programs
self.unused_datasets_programs = [
DataFactory(created_by=user, type=Data.INGESTION_PROGRAM),
DataFactory(created_by=user, type=Data.SCORING_PROGRAM),
DataFactory(created_by=user, type=Data.INPUT_DATA),
DataFactory(created_by=user, type=Data.REFERENCE_DATA),
DataFactory(created_by=user, type=Data.PUBLIC_DATA)
]

self.client.login(username='test_user', password='test_user')

def test_cleanup_stats(self):

url = reverse('user_quota_cleanup')
resp = self.client.get(url)
assert resp.status_code == 200
content = json.loads(resp.content)
assert content["unused_tasks"] == len(self.unused_tasks)
assert content["unused_datasets_programs"] == len(self.unused_datasets_programs)
assert content["unused_submissions"] == len(self.unused_submissions)
assert content["failed_submissions"] == len(self.failed_submissions)

def test_delete_unused_tasks(self):

url = reverse('delete_unused_tasks')
resp = self.client.delete(url)
assert resp.status_code == 200
content = json.loads(resp.content)
assert content["success"]
assert content["message"] == "Unused tasks deleted successfully"

url = reverse('user_quota_cleanup')
resp = self.client.get(url)
assert resp.status_code == 200
content = json.loads(resp.content)
assert content["unused_tasks"] == 0

def test_delete_unused_datasets(self):

url = reverse('delete_unused_datasets')
resp = self.client.delete(url)
assert resp.status_code == 200
content = json.loads(resp.content)
assert content["success"]
assert content["message"] == "Unused datasets and programs deleted successfully"

url = reverse('user_quota_cleanup')
resp = self.client.get(url)
assert resp.status_code == 200
content = json.loads(resp.content)
assert content["unused_datasets_programs"] == 0

def test_delete_unused_submissions(self):

url = reverse('delete_unused_submissions')
resp = self.client.delete(url)
assert resp.status_code == 200
content = json.loads(resp.content)
assert content["success"]
assert content["message"] == "Unused submissions deleted successfully"

url = reverse('user_quota_cleanup')
resp = self.client.get(url)
assert resp.status_code == 200
content = json.loads(resp.content)
assert content["unused_submissions"] == 0

def test_delete_failed_submissions(self):

url = reverse('delete_failed_submissions')
resp = self.client.delete(url)
assert resp.status_code == 200
content = json.loads(resp.content)
assert content["success"]
assert content["message"] == "Failed submissions deleted successfully"

url = reverse('user_quota_cleanup')
resp = self.client.get(url)
assert resp.status_code == 200
content = json.loads(resp.content)
assert content["failed_submissions"] == 0
Loading