diff --git a/src/apps/announcements/admin.py b/src/apps/announcements/admin.py index e675dd528..16fc499db 100644 --- a/src/apps/announcements/admin.py +++ b/src/apps/announcements/admin.py @@ -1,6 +1,26 @@ from django.contrib import admin - from . import models -admin.site.register(models.Announcement) -admin.site.register(models.NewsPost) + +class NewsPostExpansion(admin.ModelAdmin): + list_display = ["id", "title", "link"] + list_display_links = ["id", "title"] + search_fields = ["id", "title", "link"] + + +class AnnouncementExpansion(admin.ModelAdmin): + list_display = ["id", "text_limited"] + list_display_links = ["id", "text_limited"] + + @admin.display(description="text", ordering="text") + def text_limited(self, obj): + if not obj.text: + return "-" + if len(obj.text) > 500: + return obj.text[:500] + "(...)" + else: + return obj.text[:500] + + +admin.site.register(models.Announcement, AnnouncementExpansion) +admin.site.register(models.NewsPost, NewsPostExpansion) diff --git a/src/apps/competitions/admin.py b/src/apps/competitions/admin.py index 942133b4b..a7b4fe4fd 100644 --- a/src/apps/competitions/admin.py +++ b/src/apps/competitions/admin.py @@ -1,53 +1,360 @@ from django.contrib import admin from django.utils.translation import gettext_lazy as _ - +import json +import csv +from django.http import HttpResponse +from profiles.models import User from . import models -class privateCompetitionsFilter(admin.SimpleListFilter): +# General class used to make custom filter +class InputFilter(admin.SimpleListFilter): + template = "admin/input_filter.html" + + def lookups(self, request, model_admin): + # Dummy, required to show the filter. + return ((),) + + def choices(self, changelist): + # Grab only the "all" option. + all_choice = next(super().choices(changelist)) + all_choice["query_parts"] = ( + (k, v) + for k, v in changelist.get_filters_params().items() + if k != self.parameter_name + ) + yield all_choice + + +class SubmissionsCountFilter(InputFilter): # Human-readable title which will be displayed in the # right admin sidebar just above the filter options. - title = _("Private non-test") - + title = _("≥ Submissions Count (Greater than or Equal to)") # Parameter for the filter that will be used in the URL query. - parameter_name = "private" + parameter_name = "submissions_count_gte" + + def queryset(self, request, queryset): + if self.value() is not None: + value = self.value() + return queryset.filter(submissions_count__gte=value) - def lookups(self, request, model_admin): - """ - Returns a list of tuples. The first element in each - tuple is the coded value for the option that will - appear in the URL query. The second element is the - human-readable name for the option that will appear - in the right sidebar. - """ - return [ - ("privateSmall", _("Submissions >= 25 and Participants >= 10")), - ] + +class ParticipantsCountFilter(InputFilter): + title = _("≥ Participants Count (Greater Than or Equal to)") + parameter_name = "participants_count_gte" def queryset(self, request, queryset): - """ - Returns the filtered queryset based on the value - provided in the query string and retrievable via - `self.value()`. - """ - # Only show private competitions with >= 25 submissions and >=10 participants - if self.value() == "privateSmall": - return queryset.filter( - published=False, - submissions_count__gte=10, - participants_count__gte=5 + if self.value() is not None: + value = self.value() + return queryset.filter(participants_count__gte=value) + + +# This will export the email of all the selected competition creators, removing duplications and banned users +@admin.display(description="Export as CSV") +def CompetitionExport_as_csv(modeladmin, request, queryset): + response = HttpResponse( + content_type="text/csv", + headers={ + "Content-Disposition": 'attachment; filename="email_username_list.csv"' + }, + ) + writer = csv.writer(response) + writer.writerow( + [ + "Username", + "Email", + "Competition title", + "Participants Count", + "Submissions Count", + ] + ) + email_list = {} + for obj in queryset: + user = User.objects.get(id=obj.created_by_id) + if not user.is_banned and user.email not in email_list.values(): + email_list.update({user.username: user.email}) + writer.writerow( + [ + user.username, + user.email, + obj.title, + obj.participants_count, + obj.submissions_count, + ] ) + return response + + +@admin.display(description="Export as CSV") +def SubmissionsExport_as_csv(modeladmin, request, queryset): + response = HttpResponse( + content_type="text/csv", + headers={ + "Content-Disposition": 'attachment; filename="submissions.csv"' + }, + ) + writer = csv.writer(response) + writer.writerow(["ID", "Owner", "Status", "Task", "PhaseQueue"]) + for obj in queryset: + writer.writerow([obj.id, obj.owner, obj.status, obj.task, obj.phase, obj.queue]) + return response + + +# This will export the email of all the selected competition creators, removing duplications and banned users +@admin.display(description="Export as JSON") +def CompetitionExport_as_json(modeladmin, request, queryset): + email_list = {} + for obj in queryset: + user = User.objects.get(id=obj.created_by_id) + if not user.is_banned and user.email not in email_list.values(): + email_list.update({user.username: user.email}) + return HttpResponse(json.dumps(email_list), content_type="application/json") + + +class QueueFilter(InputFilter): + # Human-readable title which will be displayed in the + # right admin sidebar just above the filter options. + title = _("Queue (default for Default Queue)") + # Parameter for the filter that will be used in the URL query. + parameter_name = "queue" + + def queryset(self, request, queryset): + if self.value() is not None: + value = self.value() + if value.lower() == "default": + return queryset.filter(queue__name__isnull=True) + else: + return queryset.filter(queue__name=value) + + +class CompetitionExpansion(admin.ModelAdmin): + search_fields = ["id", "title", "docker_image", "created_by__username"] + list_display = ["id", "title", "created_by", "published", "is_featured"] + list_display_links = ["id", "title"] + actions = [CompetitionExport_as_json, CompetitionExport_as_csv] + raw_id_fields = ["created_by", "collaborators", "queue"] + list_filter = [ + "published", + "is_featured", + ParticipantsCountFilter, + SubmissionsCountFilter, + QueueFilter, + ] + fieldsets = [ + ( + None, + { + "fields": [ + ("title", "docker_image", "competition_type"), + "secret_key", + "terms", + "description", + "fact_sheet", + "contact_email", + "reward", + "report", + "submissions_count", + "participants_count", + "created_when", + ] + }, + ), + ("Raw ID Fields", {"fields": ["created_by", "collaborators", "queue"]}), + ( + "Checkboxes", + { + "fields": [ + "published", + "registration_auto_approve", + "is_migrating", + "enable_detailed_results", + "show_detailed_results_in_submission_panel", + "show_detailed_results_in_leaderboard", + "make_programs_available", + "make_input_data_available", + "allow_robot_submissions", + "auto_run_submissions", + "can_participants_make_submissions_public", + "is_featured", + "forum_enabled", + ] + }, + ), + ("Files", {"fields": ["logo", "logo_icon"]}), + ] + + +class CompetitionOrganizerFilter(InputFilter): + # Human-readable title which will be displayed in the + # right admin sidebar just above the filter options. + title = _("Competition Organizer") + # Parameter for the filter that will be used in the URL query. + parameter_name = "organizer" + + def queryset(self, request, queryset): + if self.value() is not None: + value = self.value() + return queryset.filter(phase__competition__created_by__username=value) + + +class SubmissionExpansion(admin.ModelAdmin): + # Raw Id Fields changes the field from displaying everything in a drop down menu into an id fields, which makes the page loads much faster (removes huge SELECT from the database) + raw_id_fields = [ + "organization", + "owner", + "phase", + "data", + "task", + "leaderboard", + "participant", + "queue", + "created_by_migration", + "parent", + "scores", + ] + search_fields = ["id", "owner__username", "phase__competition__title", "task__name"] + actions = [SubmissionsExport_as_csv] + list_display = [ + "id", + "owner", + "task", + "status", + "is_public", + "has_children", + "is_soft_deleted", + ] + list_filter = [ + "is_public", + "has_children", + "is_soft_deleted", + "status", + CompetitionOrganizerFilter, + QueueFilter, + ] + fieldsets = [ + ( + None, + { + "fields": [ + ("status", "ingestion_worker_hostname", "scoring_worker_hostname"), + "status_details", + "description", + "prediction_result_file_size", + "scoring_result_file_size", + "detailed_result_file_size", + "md5", + "secret", + "celery_task_id", + "name", + "fact_sheet_answers", + "created_when", + "started_when", + "soft_deleted_when", + ], + }, + ), + ( + "Raw ID Fields", + { + "fields": [ + "owner", + "organization", + "phase", + "data", + "task", + "leaderboard", + "participant", + "queue", + "created_by_migration", + "scores", + "parent", + ], + }, + ), + ( + "Checkboxes", + { + "fields": [ + "appear_on_leaderboards", + "is_public", + "is_specific_task_re_run", + "is_migrated", + "has_children", + "is_soft_deleted", + ], + }, + ), + ( + "Files", + {"fields": ["prediction_result", "scoring_result", "detailed_result"]}, + ), + ] + + +class CompetitionCreationTaskStatusExpansion(admin.ModelAdmin): + raw_id_fields = ["dataset", "created_by", "resulting_competition"] + list_display = ["id", "created_by", "resulting_competition", "status"] + search_fields = ["id", "created_by__username"] + list_filter = ["status"] + + +class CompetitionParticipantExpansion(admin.ModelAdmin): + raw_id_fields = ["user", "competition"] + list_display = ["id", "user", "competition", "status"] + list_filter = ["status"] + search_fields = ["id", "user__username", "competition"] + + +class PageExpansion(admin.ModelAdmin): + raw_id_fields = ["competition"] + list_display = ["id", "competition"] + search_fields = ["id", "competition", "content"] -class CompetitionAdmin(admin.ModelAdmin): - search_fields = ['title', 'docker_image', 'created_by__username'] - list_display = ['id', 'title', 'created_by', 'is_featured'] - list_filter = ['is_featured', privateCompetitionsFilter] +class PhaseExpansion(admin.ModelAdmin): + raw_id_fields = ["competition", "leaderboard", "public_data", "starting_kit"] + list_display = ["id", "competition", "name"] + search_fields = ["id", "competition", "name"] + fieldsets = [ + ( + None, + { + "fields": [ + ("name", "status", "execution_time_limit"), + ("max_submissions_per_day", "max_submissions_per_person"), + "index", + "description", + "start", + "end", + ] + }, + ), + ( + "Raw ID Fields", + {"fields": ["competition", "leaderboard", "public_data", "starting_kit"]}, + ), + ( + "Checkboxes", + { + "fields": [ + "is_final_phase", + "auto_migrate_to_this_phase", + "has_been_migrated", + "hide_output", + "hide_prediction_output", + "hide_score_output", + "has_max_submissions", + ] + }, + ), + ] -admin.site.register(models.Competition, CompetitionAdmin) -admin.site.register(models.CompetitionCreationTaskStatus) -admin.site.register(models.CompetitionParticipant) -admin.site.register(models.Page) -admin.site.register(models.Phase) -admin.site.register(models.Submission) +admin.site.register(models.Competition, CompetitionExpansion) +admin.site.register( + models.CompetitionCreationTaskStatus, CompetitionCreationTaskStatusExpansion +) +admin.site.register(models.CompetitionParticipant, CompetitionParticipantExpansion) +admin.site.register(models.Page, PageExpansion) +admin.site.register(models.Phase, PhaseExpansion) +admin.site.register(models.Submission, SubmissionExpansion) diff --git a/src/apps/datasets/admin.py b/src/apps/datasets/admin.py index 2fc94340d..86cb7777c 100644 --- a/src/apps/datasets/admin.py +++ b/src/apps/datasets/admin.py @@ -1,6 +1,58 @@ from django.contrib import admin - +from profiles.models import User from . import models +from django.template.defaultfilters import filesizeformat + + +@admin.action(description="Deactivate Account and Delete Item") +def DeactivateAccount(modeladmin, request, queryset): + for obj in queryset: + user = User.objects.get(id=obj.created_by_id) + user.is_banned = True + user.save() + queryset.delete() + + +class DataExpansion(admin.ModelAdmin): + raw_id_fields = ["created_by", "competition"] + list_display = [ + "id", + "name", + "description_limited", + "created_by", + "type", + "is_public", + "is_verified", + "filesize_human", + ] + search_fields = [ + "id", + "created_by__username", + "name", + "type", + "description", + "file_name", + "file_size", + ] + list_filter = ["is_public", "is_verified"] + + @admin.display(description="Description", ordering="description") + def description_limited(self, obj): + if not obj.description: + return "-" + if len(obj.description) > 500: + return obj.description[:500] + "(...)" + else: + return obj.description[:500] + + # Convert the file size from bytes to KB,MB,GB etc to make it more readable in the list_display + @admin.display(description="File size", ordering="file_size") + def filesize_human(self, obj): + if not obj.file_size: + return "-" + return filesizeformat(obj.file_size) + + actions = [DeactivateAccount] -admin.site.register(models.Data) +admin.site.register(models.Data, DataExpansion) diff --git a/src/apps/datasets/migrations/0014_alter_data_file_size.py b/src/apps/datasets/migrations/0014_alter_data_file_size.py new file mode 100644 index 000000000..2c6d9b96b --- /dev/null +++ b/src/apps/datasets/migrations/0014_alter_data_file_size.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2 on 2025-12-24 11:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('datasets', '0013_merge_0011_auto_20250623_1341_0012_delete_datagroup'), + ] + + operations = [ + migrations.AlterField( + model_name='data', + name='file_size', + field=models.DecimalField(blank=True, decimal_places=2, help_text='Size in Bytes', max_digits=15, null=True), + ), + ] diff --git a/src/apps/datasets/models.py b/src/apps/datasets/models.py index 668e93977..fd4f7fef8 100644 --- a/src/apps/datasets/models.py +++ b/src/apps/datasets/models.py @@ -57,7 +57,7 @@ class Data(models.Model): key = models.UUIDField(default=uuid.uuid4, blank=True, unique=True) is_public = models.BooleanField(default=False) upload_completed_successfully = models.BooleanField(default=False) - file_size = models.DecimalField(max_digits=15, decimal_places=2, null=True, blank=True) # in Bytes + file_size = models.DecimalField(max_digits=15, decimal_places=2, null=True, blank=True, help_text="Size in Bytes") # in Bytes # This is true if the Data model was created as part of unpacking a competition. Competition bundles themselves # are NOT marked True, since they are not created by unpacking! diff --git a/src/apps/forums/admin.py b/src/apps/forums/admin.py index c73a6e1b5..1c9a38efc 100644 --- a/src/apps/forums/admin.py +++ b/src/apps/forums/admin.py @@ -1,8 +1,55 @@ from django.contrib import admin - +from profiles.models import User from . import models -admin.site.register(models.Forum) -admin.site.register(models.Thread) -admin.site.register(models.Post) +@admin.action(description="Deactivate Account and Delete Item") +def DeactivateAccountThread(modeladmin, request, queryset): + for obj in queryset: + user = User.objects.get(id=obj.started_by_id) + user.is_banned = True + user.save() + queryset.delete() + + +@admin.action(description="Deactivate Account and Delete Item") +def DeactivateAccountPost(modeladmin, request, queryset): + for obj in queryset: + user = User.objects.get(id=obj.posted_by_id) + user.is_banned = True + user.save() + queryset.delete() + + +class ForumsExpansion(admin.ModelAdmin): + raw_id_fields = ["competition"] + list_display = ["id", "competition"] + search_fields = ["id", "competition"] + + +class ThreadExpansion(admin.ModelAdmin): + raw_id_fields = ["forum", "started_by"] + list_display = ["id", "title", "started_by"] + search_fields = ["id", "title", "started_by__username"] + actions = [DeactivateAccountThread] + + +class PostExpansion(admin.ModelAdmin): + raw_id_fields = ["thread", "posted_by"] + list_display = ["id", "content_limited", "posted_by"] + search_fields = ["id", "content", "posted_by__username"] + actions = [DeactivateAccountPost] + + @admin.display(description="Content", ordering="content") + def content_limited(self, obj): + if not obj.content: + return "-" + if len(obj.content) > 500: + return obj.content[:500] + "(...)" + else: + return obj.content[:500] + + +admin.site.register(models.Forum, ForumsExpansion) +admin.site.register(models.Thread, ThreadExpansion) +admin.site.register(models.Post, PostExpansion) diff --git a/src/apps/leaderboards/admin.py b/src/apps/leaderboards/admin.py index f3cac71ce..db86c577d 100644 --- a/src/apps/leaderboards/admin.py +++ b/src/apps/leaderboards/admin.py @@ -3,6 +3,25 @@ from . import models -admin.site.register(models.Leaderboard) -admin.site.register(models.Column) -admin.site.register(models.SubmissionScore) +class LeaderboardExpansion(admin.ModelAdmin): + list_display = ["id", "title", "submission_rule", "hidden"] + search_fields = ["id", "title"] + list_filter = ["hidden"] + + +class ColumExpansion(admin.ModelAdmin): + raw_id_fields = ["leaderboard"] + list_display = ["id", "title", "hidden"] + search_fields = ["id", "title"] + list_filter = ["hidden"] + + +class SubmissionScoreExpansion(admin.ModelAdmin): + raw_id_fields = ["column"] + list_display = ["id", "column", "score"] + search_fields = ["id", "column"] + + +admin.site.register(models.Leaderboard, LeaderboardExpansion) +admin.site.register(models.Column, ColumExpansion) +admin.site.register(models.SubmissionScore, SubmissionScoreExpansion) diff --git a/src/apps/oidc_configurations/admin.py b/src/apps/oidc_configurations/admin.py index 5ea6e683f..2b606b8a1 100644 --- a/src/apps/oidc_configurations/admin.py +++ b/src/apps/oidc_configurations/admin.py @@ -1,6 +1,10 @@ from django.contrib import admin from .models import Auth_Organization -admin.site.register(Auth_Organization) -# Register your models here. +class Auth_OrganizationExpansion(admin.ModelAdmin): + list_display = ["id", "name", "client_id"] + search_fields = ["id", "name", "client_id"] + + +admin.site.register(Auth_Organization, Auth_OrganizationExpansion) diff --git a/src/apps/profiles/admin.py b/src/apps/profiles/admin.py index eafc21cdf..3715c59c7 100644 --- a/src/apps/profiles/admin.py +++ b/src/apps/profiles/admin.py @@ -1,27 +1,157 @@ from django.contrib import admin - from .models import User, DeletedUser, Organization, Membership +from django.utils.translation import gettext_lazy as _ + + +# General class used to make custom filter +class InputFilter(admin.SimpleListFilter): + template = "admin/input_filter.html" + + def lookups(self, request, model_admin): + # Dummy, required to show the filter. + return ((),) + + def choices(self, changelist): + # Grab only the "all" option. + all_choice = next(super().choices(changelist)) + all_choice["query_parts"] = ( + (k, v) + for k, v in changelist.get_filters_params().items() + if k != self.parameter_name + ) + yield all_choice + +class QuotaFilter(InputFilter): + # Human-readable title which will be displayed in the + # right admin sidebar just above the filter options. + title = _("≥ Quota (Greater than or Equal to)") + # Parameter for the filter that will be used in the URL query. + parameter_name = "quota_gte" -class UserAdmin(admin.ModelAdmin): + def queryset(self, request, queryset): + if self.value() is not None: + value = self.value() + return queryset.filter(quota__gte=value) + + +class UserExpansion(admin.ModelAdmin): # The following two lines are needed for Django-su: change_form_template = "admin/auth/user/change_form.html" change_list_template = "admin/auth/user/change_list.html" - search_fields = ['username', 'email'] - list_filter = ['is_staff', 'is_superuser', 'is_deleted', 'is_bot', 'is_banned'] - list_display = ['username', 'email', 'is_staff', 'is_superuser', 'is_banned'] + search_fields = ["id", "username", "email"] + list_filter = [ + "is_staff", + "is_superuser", + "is_deleted", + "is_bot", + "is_banned", + QuotaFilter, + ] + list_display = ["id", "username", "email", "quota", "is_staff", "is_superuser", "is_banned"] + list_display_links = ["id", "username"] + raw_id_fields = ["oidc_organization", "groups"] + fieldsets = [ + ( + None, + { + "fields": [ + ("username", "slug", "email"), + "password", + "groups", + "user_permissions", + "date_joined", + "last_login", + "quota", + ] + }, + ), + ( + "Checkboxes", + { + "fields": [ + ("is_active", "is_bot"), + ( + "organizer_direct_message_updates", + "allow_forum_notifications", + "allow_organization_invite_emails", + ), + ("is_superuser", "is_staff"), + "is_deleted", + "is_banned", + ] + }, + ), + ( + "Extra Information", + { + "classes": ["collapse"], + "fields": [ + "display_name", + "first_name", + "last_name", + "title", + "location", + "biography", + "personal_url", + "linkedin_url", + "twitter_url", + "github_url", + "github_uid", + "avatar_url", + "url", + "html_url", + "name", + "company", + "bio", + "github_info", + ], + }, + ), + ( + "OIDC", + { + "classes": ["collapse"], + "fields": ["is_created_using_oidc", "oidc_organization"], + }, + ), + ( + "RabbitMQ", + { + "classes": ["collapse"], + "fields": [ + "rabbitmq_queue_limit", + "rabbitmq_username", + "rabbitmq_password", + ], + }, + ), + ("Files", {"classes": ["collapse"], "fields": ["photo"]}), + ] + + +class DeletedUserExpansion(admin.ModelAdmin): + list_display = ("user_id", "username", "email", "deleted_at") + search_fields = ("id", "username", "email") + list_filter = ("deleted_at",) + + +class MembershipExpansion(admin.ModelAdmin): + raw_id_fields = ["organization", "user"] + list_display = ["id", "organization", "user", "group"] + search_fields = ["id", "user__username", "token"] -class DeletedUserAdmin(admin.ModelAdmin): - list_display = ('user_id', 'username', 'email', 'deleted_at') - search_fields = ('username', 'email') - list_filter = ('deleted_at',) +class OrganizationExpansion(admin.ModelAdmin): + raw_id_fields = ["user_record"] + list_display = ["id", "name", "email", "description"] + search_fields = ["name", "email", "description"] -admin.site.register(User, UserAdmin) -admin.site.register(DeletedUser, DeletedUserAdmin) -admin.site.register(Organization) -admin.site.register(Membership) +admin.site.register(User, UserExpansion) +admin.site.register(DeletedUser, DeletedUserExpansion) +admin.site.register(Organization, OrganizationExpansion) +admin.site.register(Membership, MembershipExpansion) def su_login_callback(user): diff --git a/src/apps/profiles/migrations/0020_alter_user_quota.py b/src/apps/profiles/migrations/0020_alter_user_quota.py new file mode 100644 index 000000000..5ec46b108 --- /dev/null +++ b/src/apps/profiles/migrations/0020_alter_user_quota.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2 on 2025-12-24 11:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('profiles', '0019_merge_0017_user_is_banned_0018_auto_20250623_1719'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='quota', + field=models.BigIntegerField(default=15, help_text='Size in GB'), + ), + ] diff --git a/src/apps/profiles/models.py b/src/apps/profiles/models.py index 95ff20b34..042c7d0a1 100644 --- a/src/apps/profiles/models.py +++ b/src/apps/profiles/models.py @@ -80,7 +80,7 @@ class User(AbstractBaseUser, PermissionsMixin): date_joined = models.DateTimeField(default=now) is_active = models.BooleanField(default=True) is_staff = models.BooleanField(default=False) - quota = models.BigIntegerField(default=settings.DEFAULT_USER_QUOTA, null=False) + quota = models.BigIntegerField(default=settings.DEFAULT_USER_QUOTA, null=False, help_text="Size in GB") # Fields for OIDC authentication is_created_using_oidc = models.BooleanField(default=False) diff --git a/src/apps/queues/admin.py b/src/apps/queues/admin.py index 160e2b8b3..a0410d0fb 100644 --- a/src/apps/queues/admin.py +++ b/src/apps/queues/admin.py @@ -1,5 +1,47 @@ from django.contrib import admin from queues import models +import json +import csv +from django.http import HttpResponse +from profiles.models import User -admin.site.register(models.Queue) +# This will export the email of all the selected competition creators, removing duplications and banned users +def export_as_csv(modeladmin, request, queryset): + response = HttpResponse( + content_type="text/csv", + headers={ + "Content-Disposition": 'attachment; filename="email_username_list.csv"' + }, + ) + writer = csv.writer(response) + writer.writerow(["Username", "Email", "Queue_Name"]) + email_list = {} + for obj in queryset: + user = User.objects.get(id=obj.owner_id) + if not user.is_banned and user.email not in email_list.values(): + email_list.update({user.username: user.email}) + writer.writerow([user.username, user.email, obj.name]) + return response + + +# This will export the email of all the selected competition creators, removing duplications and banned users +def export_as_json(modeladmin, request, queryset): + email_list = {} + for obj in queryset: + user = User.objects.get(id=obj.owner_id) + if not user.is_banned and user.email not in email_list.values(): + email_list.update({user.username: user.email}) + return HttpResponse(json.dumps(email_list), content_type="application/json") + + +class QueueExpansion(admin.ModelAdmin): + raw_id_fields = ["owner", "organizers"] + list_display = ["id", "name", "owner", "is_public"] + list_display_links = ["id", "name"] + list_filter = ["is_public"] + search_fields = ["id", "name", "owner__username", "organizers__username"] + actions = [export_as_csv, export_as_json] + + +admin.site.register(models.Queue, QueueExpansion) diff --git a/src/apps/tasks/admin.py b/src/apps/tasks/admin.py index 4ff0a7c17..b91200eef 100644 --- a/src/apps/tasks/admin.py +++ b/src/apps/tasks/admin.py @@ -2,5 +2,40 @@ from . import models -admin.site.register(models.Task) -admin.site.register(models.Solution) + +class TaskExpansion(admin.ModelAdmin): + raw_id_fields = [ + "created_by", + "shared_with", + "ingestion_program", + "input_data", + "reference_data", + "scoring_program", + ] + list_display = [ + "id", + "created_by", + "name", + "description", + "ingestion_only_during_scoring", + "is_public", + ] + list_filter = ["is_public", "ingestion_only_during_scoring"] + search_fields = [ + "id", + "name", + "created_by__username", + ] + + +class SolutionExpansion(admin.ModelAdmin): + raw_id_fields = ["tasks", "data"] + list_display = ["id", "name", "description", "data", "is_public"] + list_filter = ["is_public"] + search_fields = [ + "id", + ] + + +admin.site.register(models.Task, TaskExpansion) +admin.site.register(models.Solution, SolutionExpansion) diff --git a/src/templates/admin/input_filter.html b/src/templates/admin/input_filter.html new file mode 100644 index 000000000..65b8f3786 --- /dev/null +++ b/src/templates/admin/input_filter.html @@ -0,0 +1,25 @@ +{% load i18n %} + +