Skip to content
Open
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
76 changes: 75 additions & 1 deletion autograder/apps/contests/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django.conf import settings
from django.utils import timezone
from django.http import HttpResponse
from urllib.parse import urlencode
from ..oauth.decorators import login_required, admin_required
from .models import Contest
from ..problems.models import Problem
Expand All @@ -15,6 +16,25 @@
logger = logging.getLogger(__name__)


def _parse_multi_values(values):
items = []
for raw in values:
if raw is None:
continue
for part in str(raw).split(","):
part = part.strip()
if part:
items.append(part)
seen = set()
deduped = []
for item in items:
if item in seen:
continue
seen.add(item)
deduped.append(item)
return deduped


@login_required
def contests_view(request):
contests = Contest.objects.all().order_by("-start")
Expand Down Expand Up @@ -144,7 +164,52 @@ def contest_status_view(request, cid, mine_only, page):
if not request.user.is_staff:
subs = subs.filter(timestamp__gte=contest.start)

subs = subs.order_by("-timestamp")
base_subs = subs

problem_values = _parse_multi_values(request.GET.getlist("problem"))
verdict_values = _parse_multi_values(request.GET.getlist("verdict"))
language_values = _parse_multi_values(request.GET.getlist("language"))

problem_ids = []
for value in problem_values:
try:
problem_ids.append(int(value))
except (TypeError, ValueError):
continue

if problem_ids:
subs = subs.filter(problem_id__in=problem_ids)

if verdict_values:
subs = subs.filter(verdict__in=verdict_values)

if language_values:
subs = subs.filter(language__in=language_values)

problem_options = (
Problem.objects.filter(
id__in=base_subs.values_list("problem_id", flat=True).distinct()
)
.select_related("contest")
.order_by("contest_letter", "id")
)
verdict_options = list(
base_subs.values_list("verdict", flat=True).order_by("verdict").distinct()
)
language_options = list(
base_subs.values_list("language", flat=True).order_by("language").distinct()
)

filter_params = {}
if problem_ids:
filter_params["problem"] = ",".join(str(pid) for pid in problem_ids)
if verdict_values:
filter_params["verdict"] = ",".join(verdict_values)
if language_values:
filter_params["language"] = ",".join(language_values)
filters_query = urlencode(filter_params)

subs = subs.select_related("problem", "usr", "contest").order_by("-timestamp")

# Pagination
paginator = Paginator(subs, 25) # Show 25 submissions per page
Expand All @@ -159,6 +224,15 @@ def contest_status_view(request, cid, mine_only, page):
"submissions": page_obj.object_list,
"contest_over": timezone.now() > contest.end,
"mine_only": mine_only,
"problem_options": problem_options,
"verdict_options": verdict_options,
"language_options": language_options,
"filters": {
"problem": problem_ids,
"verdict": verdict_values,
"language": language_values,
},
"filters_query": filters_query,
}
return render(request, "contest/status.html", context)

Expand Down
82 changes: 81 additions & 1 deletion autograder/apps/runtests/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from django.conf import settings
from django.utils import timezone
from datetime import timedelta
from urllib.parse import urlencode
from ..oauth.decorators import login_required
from ..problems.models import Problem
from ..contests.models import Contest
Expand All @@ -15,6 +16,25 @@
logger = logging.getLogger(__name__)


def _parse_multi_values(values):
items = []
for raw in values:
if raw is None:
continue
for part in str(raw).split(","):
part = part.strip()
if part:
items.append(part)
seen = set()
deduped = []
for item in items:
if item in seen:
continue
seen.add(item)
deduped.append(item)
return deduped


def get_last_sub_lang(user):
try:
return Submission.objects.filter(usr=user).latest().language
Expand Down Expand Up @@ -94,7 +114,58 @@ def status_view(request, page, cid=None, mine=False):
elif request.user.is_tjioi:
submissions = submissions.filter(usr__is_tjioi=True)

submissions = submissions.order_by("-timestamp")
base_submissions = submissions

problem_values = _parse_multi_values(request.GET.getlist("problem"))
verdict_values = _parse_multi_values(request.GET.getlist("verdict"))
language_values = _parse_multi_values(request.GET.getlist("language"))

problem_ids = []
for value in problem_values:
try:
problem_ids.append(int(value))
except (TypeError, ValueError):
continue

if problem_ids:
submissions = submissions.filter(problem_id__in=problem_ids)

if verdict_values:
submissions = submissions.filter(verdict__in=verdict_values)

if language_values:
submissions = submissions.filter(language__in=language_values)

problem_options = (
Problem.objects.filter(
id__in=base_submissions.values_list("problem_id", flat=True).distinct()
)
.select_related("contest")
.order_by("contest__name", "contest_letter", "id")
)
verdict_options = list(
base_submissions.values_list("verdict", flat=True)
.order_by("verdict")
.distinct()
)
language_options = list(
base_submissions.values_list("language", flat=True)
.order_by("language")
.distinct()
)

filter_params = {}
if problem_ids:
filter_params["problem"] = ",".join(str(pid) for pid in problem_ids)
if verdict_values:
filter_params["verdict"] = ",".join(verdict_values)
if language_values:
filter_params["language"] = ",".join(language_values)
filters_query = urlencode(filter_params)

submissions = submissions.select_related("problem", "usr", "contest").order_by(
"-timestamp"
)
paginator = Paginator(submissions, 25) # 25 per page

try:
Expand All @@ -109,6 +180,15 @@ def status_view(request, page, cid=None, mine=False):
"page": page,
"cid": cid,
"mine": mine,
"problem_options": problem_options,
"verdict_options": verdict_options,
"language_options": language_options,
"filters": {
"problem": problem_ids,
"verdict": verdict_values,
"language": language_values,
},
"filters_query": filters_query,
}

return render(request, "runtests/status.html", context)
Expand Down
111 changes: 111 additions & 0 deletions autograder/static/profile.css
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,18 @@ li.slg {
font-size: 1.5em;
}

.title-bar {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1em;
}

.title-actions {
display: flex;
gap: 1em;
}

.button {
padding: 0.5em;
border-radius: 0.5em;
Expand All @@ -360,6 +372,105 @@ li.slg {
background-color: rgb(40, 61, 166);
}

.inline-button {
width: auto;
margin: 0;
display: inline-flex;
align-items: center;
gap: 0.4em;
}

.filter-form {
margin: 1% 2%;
display: flex;
flex-direction: column;
gap: 0.75em;
}

.filter-toggle {
align-self: flex-start;
background-color: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.2);
padding: 0.35em 0.6em;
}

.filter-icon {
width: 1em;
height: 1em;
fill: currentColor;
}

.filter-panel {
background-color: rgba(0, 0, 0, 0.12);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 0.5em;
padding: 0.75em;
display: flex;
flex-direction: column;
gap: 0.75em;
}

.filter-panel[hidden] {
display: none;
}


.filter-section {
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 0.4em;
padding: 0.5em;
background-color: rgba(0, 0, 0, 0.16);
}

.filter-summary {
cursor: pointer;
font-weight: bold;
list-style: none;
}

.filter-section[open] .filter-summary {
margin-bottom: 0.4em;
}

.filter-summary::-webkit-details-marker {
display: none;
}

.filter-summary::marker {
content: "";
}

.filter-options {
display: flex;
flex-wrap: wrap;
gap: 0.5em;
}

.filter-actions {
display: flex;
justify-content: flex-end;
}

.filter-option {
background-color: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.12);
color: white;
padding: 0.2em 0.5em;
border-radius: 0.4em;
cursor: pointer;
}

.filter-option:hover {
background-color: rgba(255, 255, 255, 0.14);
}

.filter-option.active {
background-color: rgb(0, 157, 255);
border-color: rgb(0, 157, 255);
color: black;
}


input[type="text"] {
width: 5%;
padding-top: 0.25%;
Expand Down
Loading