Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c70dc5a
UI Ok, need to show child rows
IdirLISN Sep 22, 2026
09617b3
table shows child subs and UX clean
IdirLISN Sep 23, 2026
adbbed6
adding task and group colomn to children sub rows
IdirLISN Sep 23, 2026
7d2af61
hide singe task column
IdirLISN Sep 23, 2026
d9b20e6
add sub actions to children
IdirLISN Sep 23, 2026
223bbd4
remove file name for children in sub table
IdirLISN Sep 28, 2026
9af3428
feature improvements: default queue and collapse button
IdirLISN Jul 30, 2026
dc462b6
adding butons for flower and rabbitmq
IdirLISN Oct 1, 2026
171c10f
Merge pull request #2483 from codalab/improvement/worker_monitoring
ObadaS Oct 1, 2026
9df4581
Redesign home page announcements with levels, titles and priority
ihsaan-ullah Sep 30, 2026
b51fc2a
documentation updated
ihsaan-ullah Sep 30, 2026
33fd232
Merge pull request #2589 from codalab/announcements_redesign
ObadaS Oct 1, 2026
721d830
Fixed factory_boy post-generation deprecation warnings in test factories
ihsaan-ullah Oct 1, 2026
7bd6fa0
Merge pull request #2595 from codalab/fix_factory_deprecation_warnings
ObadaS Oct 1, 2026
0005c46
Competition detail serializer now uses a list of public fields for no…
ihsaan-ullah Oct 1, 2026
97eed6e
Merge pull request #2591 from codalab/comp_detail_serializer_updates
ObadaS Oct 1, 2026
5ee76f8
Submission get_details now returns detailed result only when detailed…
ihsaan-ullah Oct 1, 2026
5185bb8
Merge pull request #2592 from codalab/submission_get_details_detailed…
ObadaS Oct 1, 2026
a2d6bf0
Update version.json
ObadaS Oct 1, 2026
c1f8fd5
Merge pull request #2598 from codalab/Version-Bump
ObadaS Oct 1, 2026
0c34a5a
Add bulk download of prediction and scoring results
ihsaan-ullah Sep 28, 2026
9a0ca37
bulk download tests added
ihsaan-ullah Sep 28, 2026
9a622ea
Merge pull request #2576 from codalab/submissions_bulk_download
ObadaS Oct 1, 2026
882bc8b
Move and rename button
Didayolo Jun 26, 2026
dc2d523
Merge pull request #2451 from codalab/update-task-button
ObadaS Oct 1, 2026
377dae8
Add back competition detail fields for non-admins
ihsaan-ullah Oct 2, 2026
6f91518
Merge pull request #2601 from codalab/competition_detail_add_back_imp…
Didayolo Oct 2, 2026
db25c37
Add leave organization button
Didayolo May 28, 2026
8398d2f
updated organization invite email to use correct protocol and domain,…
ihsaan-ullah Aug 10, 2026
e0f255c
Merge pull request #2394 from codalab/leave-organization-button
Didayolo Oct 2, 2026
e04d0d8
Add zip-contents hint to the Add Dataset/Program upload form (#2603)
Didayolo Oct 2, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -95,16 +95,34 @@ Once you log in an account with superuser privileges, you have access to the "Dj

From this interface, you can change a user's quota, change their staff and superuser status, change the featured competitions displayed on the homepage, manage user accounts and more.

#### Edit announcement and news
#### Edit announcements

In the Django admin interface, click on `Announcements` or `New posts`:
In the Django admin interface, click on `Announcements`:

![](_attachments/de2a053f-c804-4c2a-a7ff-0df189c1d79c_17534366434691174.jpg)
![](_attachments/announcements.png)

For announcement, only the first announcement is read by the front page. For news, all objects are read as separate news.
You can create and edit objects using the interface. Write the announcement and news using HTML to format the text, add links, and more:
Every announcement marked **Is active** is shown on the front page, inside the "Announcement" box. Each announcement has:

![](_attachments/38c0d1ce-9b27-44bb-804b-c4eed55613df_17534366434285173.jpg)
- **Title** (optional): shown in bold above the text.
- **Level**: controls the style.
- `Critical`: red with an exclamation icon
- `Warning`: yellow with a warning icon
- `Info`: blue with an info icon
- `Plain`: plain text with no background or icon
- **Text**: the body, written in HTML. Links are shown bold and underlined in the announcement's color.
- **Is active**: untick to hide an announcement without deleting it.
- **Priority**: controls the order. Lower numbers are shown first; announcements with the same priority are shown newest first.

You can create and edit announcements using the interface. Write the text using HTML to format it, add links, and more:

#### Edit news

In the Django admin interface, click on `News posts`:

![](_attachments/news.png)

All news objects are read by the front page as separate news.
You can create and edit news using the interface. Write the news using HTML to format the text, add links, and more.


#### Delete a user
Expand Down
Binary file added documentation/docs/_attachments/announcements.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added documentation/docs/_attachments/news.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 21 additions & 2 deletions src/apps/announcements/admin.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from django.contrib import admin
from django.utils.html import format_html
from . import models


Expand All @@ -8,10 +9,28 @@ class NewsPostExpansion(admin.ModelAdmin):
search_fields = ["id", "title", "link"]


LEVEL_COLORS = {
models.Announcement.LEVEL_CRITICAL: "#db2828",
models.Announcement.LEVEL_WARNING: "#f2c037",
models.Announcement.LEVEL_INFO: "#2185d0",
}


class AnnouncementExpansion(admin.ModelAdmin):
list_display = ["id", "text_limited"]
list_display_links = ["id", "text_limited"]
list_display = ["id", "level_badge", "title", "text_limited", "is_active", "priority"]
list_display_links = ["id", "title", "text_limited"]
list_filter = ["level", "is_active"]
search_fields = ["title", "text"]
ordering = ('-id',)
fields = ("title", "level", "text", "is_active", "priority")

@admin.display(description="level", ordering="level")
def level_badge(self, obj):
return format_html(
'<span style="background:{};color:#fff;padding:2px 8px;border-radius:4px;">{}</span>',
LEVEL_COLORS.get(obj.level, "#767676"),
obj.get_level_display(),
)

@admin.display(description="text", ordering="text")
def text_limited(self, obj):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Generated by Django 5.2.17 on 2026-09-30 09:21

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


class Migration(migrations.Migration):

dependencies = [
('announcements', '0004_alter_announcement_id_alter_newspost_id'),
]

operations = [
migrations.AddField(
model_name='announcement',
name='created_when',
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AddField(
model_name='announcement',
name='is_active',
field=models.BooleanField(default=True),
),
migrations.AddField(
model_name='announcement',
name='level',
field=models.CharField(choices=[('critical', 'Critical'), ('warning', 'Warning'), ('info', 'Info'), ('plain', 'Plain')], default='info', max_length=10),
),
migrations.AddField(
model_name='announcement',
name='priority',
field=models.PositiveIntegerField(default=0, help_text='Lower priority is shown first.'),
),
migrations.AddField(
model_name='announcement',
name='title',
field=models.CharField(blank=True, max_length=200),
),
]
19 changes: 19 additions & 0 deletions src/apps/announcements/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,26 @@


class Announcement(models.Model):
LEVEL_CRITICAL = "critical"
LEVEL_WARNING = "warning"
LEVEL_INFO = "info"
LEVEL_PLAIN = "plain"
LEVELS = [
(LEVEL_CRITICAL, "Critical"),
(LEVEL_WARNING, "Warning"),
(LEVEL_INFO, "Info"),
(LEVEL_PLAIN, "Plain"),
]

title = models.CharField(max_length=200, blank=True)
text = models.TextField(null=True, blank=True)
level = models.CharField(max_length=10, choices=LEVELS, default=LEVEL_INFO)
is_active = models.BooleanField(default=True)
priority = models.PositiveIntegerField(default=0, help_text="Lower priority is shown first.")
created_when = models.DateTimeField(default=now)

def __str__(self):
return self.title or f"Announcement {self.pk}"


class NewsPost(models.Model):
Expand Down
64 changes: 25 additions & 39 deletions src/apps/api/serializers/competitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,33 +378,13 @@ class CompetitionDetailSerializer(serializers.ModelSerializer):
queue = QueuePublicSerializer(read_only=True)
whitelist_emails = serializers.SerializerMethodField()

# Fields only visible to competition admins (creator, collaborators, staff/superusers)
ADMIN_ONLY_FIELDS = (
'secret_key',
'whitelist_emails',
'collaborators',
'queue',
'enable_detailed_results',
'show_detailed_results_in_submission_panel',
'show_detailed_results_in_leaderboard',
'auto_run_submissions',
'forum',
'forum_enabled',
'enable_human_in_the_loop',
'registration_auto_approve',
'can_participants_make_submissions_public',
'make_programs_available',
'make_input_data_available',
'fact_sheet',
)

class Meta:
model = Competition
fields = (
# Fields visible to everyone who can access the competition
public_fields = (
'id',
'title',
'published',
'secret_key',
'created_by',
'owner_display_name',
'created_when',
Expand All @@ -414,31 +394,36 @@ class Meta:
'pages',
'phases',
'leaderboards',
'collaborators',
'participant_status',
'registration_auto_approve',
'description',
'participants_count',
'submissions_count',
'queue',
'enable_detailed_results',
'show_detailed_results_in_submission_panel',
'show_detailed_results_in_leaderboard',
'auto_run_submissions',
'can_participants_make_submissions_public',
'make_programs_available',
'make_input_data_available',
'docker_image',
'competition_type',
'fact_sheet',
'forum',
'reward',
'contact_email',
'report',
'whitelist_emails',
'enable_detailed_results',
'show_detailed_results_in_submission_panel',
'show_detailed_results_in_leaderboard',
'forum',
'forum_enabled',
'enable_human_in_the_loop'
'fact_sheet',
'make_programs_available',
'make_input_data_available',
'registration_auto_approve',
)
# Fields only visible to competition admins (creator, collaborators, staff/superusers)
admin_fields = (
'secret_key',
'whitelist_emails',
'collaborators',
'queue',
'auto_run_submissions',
'enable_human_in_the_loop',
'can_participants_make_submissions_public',
)
fields = public_fields + admin_fields

def get_leaderboards(self, instance):
try:
Expand Down Expand Up @@ -466,10 +451,11 @@ def to_representation(self, instance):
representation = super().to_representation(instance)
user = self.context['request'].user

# If user is not admin/creator/collaborator then do not include the admin-only fields
# If user is not admin/creator/collaborator then only include the public fields
if not instance.user_has_admin_permission(user):
for field in self.ADMIN_ONLY_FIELDS:
representation.pop(field, None)
for field in list(representation):
if field not in self.Meta.public_fields:
representation.pop(field)

return representation

Expand Down
20 changes: 20 additions & 0 deletions src/apps/api/serializers/submissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from api.serializers.tasks import TaskSerializer
from api.serializers.submission_leaderboard import SubmissionScoreSerializer
from competitions.models import Submission, SubmissionDetails, CompetitionParticipant, Phase
from competitions.views import _group_display_name
from datasets.models import Data
from utils.data import make_url_sassy

Expand All @@ -28,6 +29,7 @@ class SubmissionSerializer(serializers.ModelSerializer):
created_when = serializers.DateTimeField()
auto_run = serializers.SerializerMethodField(read_only=True)
can_make_submissions_public = serializers.SerializerMethodField(read_only=True)
participant_group_name = serializers.SerializerMethodField(read_only=True)

class Meta:
model = Submission
Expand Down Expand Up @@ -56,6 +58,7 @@ class Meta:
'auto_run',
'can_make_submissions_public',
'is_soft_deleted',
'participant_group_name',
)
read_only_fields = (
'pk',
Expand All @@ -80,6 +83,21 @@ def get_can_make_submissions_public(self, instance):
# returns this submission's competition can_participants_make_submissions_public Flag
return instance.phase.competition.can_participants_make_submissions_public

def get_participant_group_name(self, instance):
if not instance.queue_id or not instance.phase_id:
return None

competition = instance.phase.competition

group = competition.participant_groups.filter(
queue_id=instance.queue_id
).exclude(queue_id__isnull=True).first()

if group:
return _group_display_name(group.name, competition.pk)

return None


class SubmissionCreationSerializer(DefaultUserCreateMixin, serializers.ModelSerializer):
"""Used for creation _and_ status updates..."""
Expand Down Expand Up @@ -273,6 +291,8 @@ def get_prediction_result(self, instance):

def get_detailed_result(self, instance):
if instance.detailed_result.name:
if not instance.phase.competition.enable_detailed_results:
return None
return make_url_sassy(instance.detailed_result.name)

def get_scoring_result(self, instance):
Expand Down
40 changes: 29 additions & 11 deletions src/apps/api/tests/test_competitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,32 +180,50 @@ def test_invalid_secret_key_does_not_give_access_to_private_competition(self):

# ---------- Fields ----------

def test_admins_see_admin_only_fields(self):
def test_admins_see_all_fields(self):
"""
The creator, a collaborator and a superuser request a published competition.
Expects every admin-only field in the response, including the competition's secret key.
Expects every field in the response.
"""
for user in self.admins:
resp = self._get(self.public_comp, user)
assert resp.status_code == 200
for field in CompetitionDetailSerializer.ADMIN_ONLY_FIELDS:
for field in CompetitionDetailSerializer.Meta.fields:
assert field in resp.data, field
assert resp.data['secret_key'] == str(self.public_comp.secret_key)

def test_non_admins_do_not_see_admin_only_fields(self):
"""
Participants, an unrelated user and a logged-out user request a published competition.
Expects the public fields in the response, no admin-only field,
and the competition's secret key absent from the whole response body.
Expects no admin-only field in the response.
"""
for user in self.non_admins:
resp = self._get(self.public_comp, user)
assert resp.status_code == 200
for field in ('id', 'title', 'created_by', 'phases', 'leaderboards'):
assert field in resp.data, field
for field in CompetitionDetailSerializer.ADMIN_ONLY_FIELDS:
for field in CompetitionDetailSerializer.Meta.admin_fields:
assert field not in resp.data, field
assert str(self.public_comp.secret_key) not in resp.content.decode()

def test_non_admins_see_fields_used_by_competition_page(self):
"""
Participants, an unrelated user and a logged-out user request a published competition.
Expects every field the competition page uses for non-admins in the response.
"""
# Listed here instead of using Meta.public_fields, so moving one of them to admin_fields fails this test
fields_used_by_competition_page = [
'fact_sheet',
'registration_auto_approve',
'make_programs_available',
'make_input_data_available',
'enable_detailed_results',
'show_detailed_results_in_submission_panel',
'show_detailed_results_in_leaderboard',
'forum',
'forum_enabled',
]
for user in self.non_admins:
resp = self._get(self.public_comp, user)
assert resp.status_code == 200
for field in fields_used_by_competition_page:
assert field in resp.data, field

def test_non_admins_with_secret_key_do_not_see_admin_only_fields(self):
"""
Expand All @@ -215,7 +233,7 @@ def test_non_admins_with_secret_key_do_not_see_admin_only_fields(self):
for user in [None, self.other_user]:
resp = self._get(self.private_comp, user, secret_key=str(self.private_comp.secret_key))
assert resp.status_code == 200
for field in CompetitionDetailSerializer.ADMIN_ONLY_FIELDS:
for field in CompetitionDetailSerializer.Meta.admin_fields:
assert field not in resp.data, field

def test_admins_see_hidden_leaderboards(self):
Expand Down
Loading
Loading