Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4fc411e
Clickable username in competition header
Didayolo Jan 21, 2024
8f4face
Merge pull request #1291 from codalab/username
Didayolo Jan 21, 2024
e9c8bd3
Replace "date of last entry" by "date"
Didayolo Jan 21, 2024
2a941ab
leaderboard date fixed
ihsaan-ullah Jan 22, 2024
98f7cc5
Merge pull request #1292 from codalab/date-leaderboard
Didayolo Jan 22, 2024
e293f35
quota message details and link to user quote
bbearce Jan 23, 2024
e9ae970
Update quota error messages
Didayolo Jan 23, 2024
ec29a31
Remove warning from submission_upload.tag
Didayolo Jan 23, 2024
4b52220
Merge pull request #1293 from codalab/issue_1280_user_quota_message
Didayolo Jan 23, 2024
9e48021
Rerun submission, display error when a task is deleted and a submissi…
ihsaan-ullah Jan 28, 2024
cb77137
warning message displayed on task deletion
ihsaan-ullah Jan 28, 2024
7a3f8ad
files tab: show login if not loggedin + hide available column from no…
ihsaan-ullah Feb 7, 2024
dd3f738
do not delete original submission when are reun is deleted, similarly…
ihsaan-ullah Feb 7, 2024
4ff7b40
Merge pull request #1311 from codalab/submission_rerun_delete_parent
Didayolo Feb 7, 2024
b22c8f9
Merge pull request #1301 from codalab/rerun_submission
Didayolo Feb 8, 2024
a40e4e4
Add an example of competition docker image
Didayolo Feb 8, 2024
b56e716
Merge pull request #1315 from codalab/docker-image-example
Didayolo Feb 8, 2024
dbe8f88
warning message added
ihsaan-ullah Feb 9, 2024
ac48c17
show competiitons using the queue
ihsaan-ullah Feb 9, 2024
81c4dc3
Merge pull request #1317 from codalab/queue_used
Didayolo Feb 9, 2024
eb9cc69
Merge pull request #1316 from codalab/task_deletion
Didayolo Feb 9, 2024
4f441b6
Create Codabench Statistics (#1307)
ihsaan-ullah Feb 9, 2024
acf0125
Merge pull request #1310 from codalab/files_tab
Didayolo Feb 9, 2024
61b0149
Logo Icons too big fix (Issue: 1290) (#1306)
bbearce Feb 9, 2024
62358ae
public competitions page error fixed
ihsaan-ullah Feb 10, 2024
8f06015
code simplified
ihsaan-ullah Feb 10, 2024
bfd153f
Merge pull request #1320 from codalab/queue_conflict_public_comp
Didayolo Feb 10, 2024
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
2 changes: 2 additions & 0 deletions src/apps/api/serializers/competitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ class CompetitionCreateSerializer(CompetitionSerializer):

class CompetitionDetailSerializer(serializers.ModelSerializer):
created_by = serializers.CharField(source='created_by.username', read_only=True)
logo_icon = NamedBase64ImageField(allow_null=True)
pages = PageSerializer(many=True)
phases = PhaseDetailSerializer(many=True)
leaderboards = serializers.SerializerMethodField()
Expand All @@ -347,6 +348,7 @@ class Meta:
'created_by',
'created_when',
'logo',
'logo_icon',
'terms',
'pages',
'phases',
Expand Down
29 changes: 28 additions & 1 deletion src/apps/api/serializers/queues.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

from api.mixins import DefaultUserCreateMixin
from queues.models import Queue

from profiles.models import User
from django.db.models import Q


class OrganizerSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -86,3 +86,30 @@ class Meta:
'created_when',
'is_owner',
)


class QueueListSerializer(QueueSerializer):
competitions = serializers.SerializerMethodField()

class Meta(QueueSerializer.Meta):
fields = QueueSerializer.Meta.fields + ('competitions',)

def get_competitions(self, obj):
# get user from the context request
user = self.context['request'].user

# for super user return all competiitons using this queue
# for admin return competitions where this user is organizer using this queue
# for non-admin return public competitions using this queue
if user.is_superuser:
# Fetch all competitions
competitions = obj.competitions.all().values('id', 'title')
else:
# Fetch all competitions where user is organizer or competition is published
competitions = obj.competitions.filter(
Q(published=True) |
Q(created_by=user) |
Q(collaborators=user)
).values('id', 'title')

return competitions
4 changes: 3 additions & 1 deletion src/apps/api/serializers/submissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ class SubmissionLeaderBoardSerializer(serializers.ModelSerializer):
display_name = serializers.CharField(source='owner.display_name')
slug_url = serializers.CharField(source='owner.slug_url')
organization = SimpleOrganizationSerializer(allow_null=True)
created_when = serializers.DateTimeField(format="%Y-%m-%d %H:%M")

class Meta:
model = Submission
Expand All @@ -100,7 +101,8 @@ class Meta:
'display_name',
'slug_url',
'organization',
'detailed_result'
'detailed_result',
'created_when'
)
extra_kwargs = {
"scores": {"read_only": True},
Expand Down
2 changes: 1 addition & 1 deletion src/apps/api/tests/test_datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def test_dataset_api_check_quota(self):
})

assert resp.status_code == 400
assert resp.data["data_file"][0] == f"Insufficient space. Your available space is {pretty_bytes(available_space)}. The file size is {pretty_bytes(file_size)}. Please free up some space and try again."
assert resp.data["data_file"][0] == f'Insufficient space. Your available space is {pretty_bytes(available_space)}. The file size is {pretty_bytes(file_size)}. Please free up some space and try again. You can manage your files in the Resources page.'

# Fake upload a small file
file_size = available_space - 1024
Expand Down
8 changes: 1 addition & 7 deletions src/apps/api/views/competitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -712,12 +712,6 @@ def get_leaderboard(self, request, pk):
parent__isnull=False
).count()

# get date of last submission by the owner of this submission for this phase
last_entry_date = Submission.objects.filter(owner__username=submission['owner'], phase=phase)\
.values('created_when')\
.order_by('-created_when')[0]['created_when']\
.strftime('%Y-%m-%d')

submission_key = f"{submission['owner']}{submission['parent'] or submission['id']}"

# gather detailed result from submissions for each task
Expand All @@ -741,7 +735,7 @@ def get_leaderboard(self, request, pk):
'slug_url': submission['slug_url'],
'organization': submission['organization'],
'num_entries': num_entries,
'last_entry_date': last_entry_date
'created_when': submission['created_when']
})
for score in submission['scores']:

Expand Down
2 changes: 1 addition & 1 deletion src/apps/api/views/datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def create(self, request, *args, **kwargs):
if storage_used + file_size > quota:
available_space = pretty_bytes(quota - storage_used)
file_size = pretty_bytes(file_size)
message = f"Insufficient space. Your available space is {available_space}. The file size is {file_size}. Please free up some space and try again."
message = f'Insufficient space. Your available space is {available_space}. The file size is {file_size}. Please free up some space and try again. You can manage your files in the Resources page.'
return Response({'data_file': [message]}, status=status.HTTP_400_BAD_REQUEST)

# All good, let's proceed
Expand Down
4 changes: 2 additions & 2 deletions src/apps/api/views/queues.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

class QueueViewSet(ModelViewSet):
queryset = Queue.objects.all()
serializer_class = serializers.QueueSerializer
serializer_class = serializers.QueueListSerializer
filter_fields = ('owner', 'is_public', 'name')
filter_backends = (DjangoFilterBackend, SearchFilter)
search_fields = ('name',)
Expand All @@ -29,7 +29,7 @@ def get_queryset(self):

def get_serializer_class(self):
if self.request.method == 'GET':
return serializers.QueueSerializer
return serializers.QueueListSerializer
else:
return serializers.QueueCreationSerializer

Expand Down
9 changes: 8 additions & 1 deletion src/apps/api/views/submissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,14 @@ def re_run_submission(self, request, pk):
rerun_kwargs = {}

new_sub = submission.re_run(**rerun_kwargs)
return Response({'id': new_sub.id})
if new_sub is None:
# return error
return Response({
"error_msg": "You cannot rerun this submission because one or more tasks this submission was running are deleted, resubmit the submission or contact the competition organizer!"},
status=status.HTTP_404_NOT_FOUND
)
else:
return Response({'id': new_sub.id})

@action(detail=False, methods=('POST',))
def re_run_many_submissions(self, request):
Expand Down
19 changes: 19 additions & 0 deletions src/apps/competitions/migrations/0045_auto_20240129_2314.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 2.2.17 on 2024-01-29 23:14

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


class Migration(migrations.Migration):

dependencies = [
('competitions', '0044_merge_20231221_1416'),
]

operations = [
migrations.AddField(
model_name='competition',
name='logo_icon',
field=models.ImageField(blank=True, null=True, upload_to=utils.data.PathWrapper('logos', manual_override=True)),
),
]
82 changes: 75 additions & 7 deletions src/apps/competitions/models.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import logging
import uuid
import os
import io

from django.conf import settings
from django.contrib.sites.models import Site
from django.contrib.postgres.fields import JSONField
from django.core.files.base import ContentFile
from django.db import models
from django.db.models import Q
from django.urls import reverse
Expand All @@ -15,6 +18,7 @@
from profiles.models import User, Organization
from utils.data import PathWrapper
from utils.storage import BundleStorage
from PIL import Image

from tasks.models import Task

Expand All @@ -32,6 +36,7 @@ class Competition(ChaHubSaveMixin, models.Model):

title = models.CharField(max_length=256)
logo = models.ImageField(upload_to=PathWrapper('logos'), null=True, blank=True)
logo_icon = models.ImageField(upload_to=PathWrapper('logos', manual_override=True), null=True, blank=True)
created_by = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True, on_delete=models.SET_NULL,
related_name="competitions")
created_when = models.DateTimeField(default=now)
Expand Down Expand Up @@ -214,8 +219,37 @@ def get_chahub_data(self):

return self.clean_private_data(data)

def make_logo_icon(self):
if self.logo:
# Read the content of the logo file
self.logo.name
self.logo_icon
icon_dirname_only = os.path.dirname(self.logo.name) # Get just the path
icon_basename_only = os.path.basename(self.logo.name) # Get just the filename
file_name = os.path.splitext(icon_basename_only)[0]
ext = os.path.splitext(icon_basename_only)[1]
new_path = os.path.join(icon_dirname_only, f"{file_name}_icon{ext}")
logo_content = self.logo.read()
original_logo = Image.open(io.BytesIO(logo_content))
# Resize the image to a smaller size for logo_icon
width, height = original_logo.size
new_width = 100 # Specify the desired width for the logo_icon
new_height = int((new_width / width) * height)
resized_logo = original_logo.resize((new_width, new_height))
# Create a BytesIO object to save the resized image
icon_content = io.BytesIO()
resized_logo.save(icon_content, format='PNG')
# Save the resized logo as logo_icon
self.logo_icon.save(new_path, ContentFile(icon_content.getvalue()), save=False)

def save(self, *args, **kwargs):
super().save(*args, **kwargs)
if not self.logo:
pass
elif not self.logo_icon:
self.make_logo_icon()
elif os.path.dirname(self.logo.name) != os.path.dirname(self.logo_icon.name):
self.make_logo_icon()
to_create = User.objects.filter(
Q(id=self.created_by_id) | Q(id__in=self.collaborators.all().values_list('id', flat=True))
).exclude(id__in=self.participants.values_list('user_id', flat=True)).distinct()
Expand Down Expand Up @@ -500,10 +534,17 @@ def __str__(self):
return f"{self.phase.competition.title} submission PK={self.pk} by {self.owner.username}"

def delete(self, **kwargs):

# Check if any other submissions are using the same data
other_submissions_using_data = Submission.objects.filter(data=self.data).exclude(pk=self.pk).exists()

if not other_submissions_using_data:
# If no other submissions are using the same data, delete it
self.data.delete()

# Also clean up details on delete
self.details.all().delete()
# Call this here so that the data_file for the submission also gets deleted from storage
self.data.delete()

super().delete(**kwargs)

def save(self, ignore_submission_limit=False, **kwargs):
Expand Down Expand Up @@ -539,24 +580,51 @@ def start(self, tasks=None):
run_submission(self.pk, tasks=tasks)

def re_run(self, task=None):

# task to use in the new submission
new_submission_task = task or self.task

# set is_specific_task_re_run
is_specific_task_re_run = bool(task)

flag_rerun_specific_task_or_has_no_children = False
# Check if this submission needs to rerun on specific children or has no children
if not self.has_children or is_specific_task_re_run:
flag_rerun_specific_task_or_has_no_children = True

# Check if task exists in case of specific task rerun or no children
if flag_rerun_specific_task_or_has_no_children and new_submission_task is None:
logger.error(f"Cannot rerun `{self}` because the task is None (deleted)")
return None
else:
children_tasks = self.children.values_list('task', flat=True)
if None in children_tasks:
logger.error(f"Cannot rerun `{self}` because one or more children submission tasks are None (deleted)")
return None

# Create a new submission
submission_arg_dict = {
'owner': self.owner,
'task': task or self.task,
'task': new_submission_task,
'phase': self.phase,
'data': self.data,
'has_children': self.has_children,
'is_specific_task_re_run': bool(task),
'is_specific_task_re_run': is_specific_task_re_run,
'fact_sheet_answers': self.fact_sheet_answers,
}
sub = Submission(**submission_arg_dict)
sub.save(ignore_submission_limit=True)

# No need to rerun on children if this is running on a specific task
if not self.has_children or sub.is_specific_task_re_run:
self.refresh_from_db()
# set tasks for rerunning
if flag_rerun_specific_task_or_has_no_children:
# in case of a submission with no children or specific task rerun
# submission with no children is same as submission with one task
tasks = [sub.task]
else:
# in case submission has multiple children or multiple task rerun
# tasks are gathered from the children submissions
tasks = Task.objects.filter(pk__in=self.children.values_list('task', flat=True))

sub.start(tasks=tasks)
return sub

Expand Down
Loading