Skip to content

Commit 57fb3c2

Browse files
Backend: Introduce TopActiveChallengesFilter and ActiveChallengePhaseFilter to enhance challenge filtering in SubmissionAdmin (#4973)
1 parent c607d71 commit 57fb3c2

File tree

3 files changed

+749
-86
lines changed

3 files changed

+749
-86
lines changed

apps/jobs/admin.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
from base.admin import ImportExportTimeStampedAdmin
44
from django.contrib import admin
55

6-
from .admin_filters import OngoingChallengesFilter
6+
from .admin_filters import (
7+
ActiveChallengePhaseFilter,
8+
TopActiveChallengesFilter,
9+
)
710
from .models import Submission
811
from .sender import publish_submission_message
912
from .utils import handle_submission_rerun
@@ -40,8 +43,8 @@ class SubmissionAdmin(ImportExportTimeStampedAdmin):
4043
"job_name",
4144
)
4245
list_filter = (
43-
OngoingChallengesFilter,
44-
"challenge_phase",
46+
TopActiveChallengesFilter,
47+
ActiveChallengePhaseFilter,
4548
"status",
4649
"is_public",
4750
)

apps/jobs/admin_filters.py

Lines changed: 99 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,114 @@
1-
from challenges.models import Challenge
1+
from datetime import timedelta
2+
3+
from challenges.models import Challenge, ChallengePhase
24
from django.contrib.admin import SimpleListFilter
5+
from django.db.models import Count, Exists, OuterRef, Q
36
from django.utils import timezone
47

8+
from .models import Submission
9+
10+
11+
def _get_top_challenge_ids():
12+
"""Return IDs of top 10 challenges by submission count in last 30 days."""
13+
thirty_days_ago = timezone.now() - timedelta(days=30)
14+
recent_submissions = Submission.objects.filter(
15+
challenge_phase__challenge_id=OuterRef("pk"),
16+
submitted_at__gte=thirty_days_ago,
17+
)
18+
top_challenges = (
19+
Challenge.objects.filter(
20+
published=True,
21+
approved_by_admin=True,
22+
is_disabled=False,
23+
)
24+
.annotate(has_recent_submission=Exists(recent_submissions))
25+
.filter(has_recent_submission=True)
26+
.annotate(
27+
submission_count=Count(
28+
"challengephase__submissions",
29+
filter=Q(
30+
challengephase__submissions__submitted_at__gte=thirty_days_ago
31+
),
32+
)
33+
)
34+
.order_by("-submission_count")[:10]
35+
)
36+
return list(top_challenges.values_list("id", flat=True))
537

6-
class OngoingChallengesFilter(SimpleListFilter):
38+
39+
class TopActiveChallengesFilter(SimpleListFilter):
740
"""
8-
List filter that shows only ongoing challenges in the dropdown,
9-
reducing clutter when filtering submissions by challenge.
41+
List filter that shows top 10 challenges by submission count
42+
that have received submissions in the last 30 days.
1043
"""
1144

12-
title = "By challenge"
45+
title = "Top active challenges (last 30 days)"
1346
parameter_name = "challenge"
1447

1548
def lookups(self, request, model_admin):
16-
now = timezone.now()
17-
ongoing = Challenge.objects.filter(
18-
published=True,
19-
approved_by_admin=True,
20-
is_disabled=False,
21-
start_date__lte=now,
22-
end_date__gte=now,
23-
).order_by("title")
24-
return [(c.id, c.title) for c in ongoing]
49+
thirty_days_ago = timezone.now() - timedelta(days=30)
50+
recent_submissions = Submission.objects.filter(
51+
challenge_phase__challenge_id=OuterRef("pk"),
52+
submitted_at__gte=thirty_days_ago,
53+
)
54+
top_challenges = (
55+
Challenge.objects.filter(
56+
published=True,
57+
approved_by_admin=True,
58+
is_disabled=False,
59+
)
60+
.annotate(has_recent_submission=Exists(recent_submissions))
61+
.filter(has_recent_submission=True)
62+
.annotate(
63+
submission_count=Count(
64+
"challengephase__submissions",
65+
filter=Q(
66+
challengephase__submissions__submitted_at__gte=thirty_days_ago
67+
),
68+
)
69+
)
70+
.order_by("-submission_count")[:10]
71+
)
72+
return [
73+
(c.id, f"{c.title} ({c.submission_count})") for c in top_challenges
74+
]
2575

2676
def queryset(self, request, queryset):
2777
if self.value():
2878
return queryset.filter(challenge_phase__challenge_id=self.value())
2979
return queryset
80+
81+
82+
class ActiveChallengePhaseFilter(SimpleListFilter):
83+
"""
84+
List filter for challenge phase. When a challenge is selected, only phases
85+
for that challenge are shown. Otherwise, only phases from top active
86+
challenges (last 30 days) are shown.
87+
"""
88+
89+
title = "Phase (top active challenges)"
90+
parameter_name = "challenge_phase"
91+
92+
def lookups(self, request, model_admin):
93+
challenge_id = request.GET.get("challenge")
94+
if challenge_id:
95+
phases = ChallengePhase.objects.filter(
96+
challenge_id=challenge_id
97+
).order_by("name")
98+
return [(p.id, p.name) for p in phases]
99+
# When no challenge selected, show only phases from top active
100+
# challenges
101+
top_challenge_ids = _get_top_challenge_ids()
102+
if not top_challenge_ids:
103+
return []
104+
phases = (
105+
ChallengePhase.objects.filter(challenge_id__in=top_challenge_ids)
106+
.select_related("challenge")
107+
.order_by("challenge__title", "name")
108+
)
109+
return [(p.id, f"{p.challenge.title}{p.name}") for p in phases]
110+
111+
def queryset(self, request, queryset):
112+
if self.value():
113+
return queryset.filter(challenge_phase_id=self.value())
114+
return queryset

0 commit comments

Comments
 (0)