Add last unsolved

This commit is contained in:
cuom1999 2022-05-21 20:30:44 -05:00
parent 0b2c410fe5
commit f79f5a8455
6 changed files with 818 additions and 767 deletions

View file

@ -88,11 +88,19 @@ def user_attempted_ids(profile):
result = cache.get(key)
if result is None:
result = {
id: {"achieved_points": points, "max_points": max_points}
for id, max_points, points in (
id: {
"achieved_points": points,
"max_points": max_points,
"last_submission": last_submission,
"code": problem_code,
"name": problem_name,
}
for id, max_points, problem_code, problem_name, points, last_submission in (
Submission.objects.filter(user=profile)
.values_list("problem__id", "problem__points")
.annotate(points=Max("points"))
.values_list(
"problem__id", "problem__points", "problem__code", "problem__name"
)
.annotate(points=Max("points"), last_submission=Max("id"))
.filter(points__lt=F("problem__points"))
)
}

View file

@ -52,6 +52,7 @@ from judge.models import (
TranslatedProblemForeignKeyQuerySet,
Organization,
VolunteerProblemVote,
Profile,
)
from judge.pdf_problems import DefaultPdfMaker, HAS_PDF
from judge.utils.diggpaginator import DiggPaginator
@ -130,6 +131,15 @@ class SolvedProblemMixin(object):
else:
return user_attempted_ids(self.profile) if self.profile is not None else ()
def get_latest_attempted_problems(self, limit=None):
if self.in_contest or not self.profile:
return ()
result = list(user_attempted_ids(self.profile).values())
result = sorted(result, key=lambda d: -d["last_submission"])
if limit:
result = result[:limit]
return result
@cached_property
def in_contest(self):
return (
@ -582,6 +592,8 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
Q(organizations__in=self.org_query)
| Q(contests__contest__organizations__in=self.org_query)
)
if self.author_query:
queryset = queryset.filter(authors__in=self.author_query)
if self.show_types:
queryset = queryset.prefetch_related("types")
if self.category is not None:
@ -639,6 +651,8 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
context["have_editorial"] = 0 if self.in_contest else int(self.have_editorial)
context["organizations"] = Organization.objects.all()
all_authors_ids = set(Problem.objects.values_list("authors", flat=True))
context["all_authors"] = Profile.objects.filter(id__in=all_authors_ids)
context["category"] = self.category
context["categories"] = ProblemGroup.objects.all()
if self.show_types:
@ -646,9 +660,11 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
context["problem_types"] = ProblemType.objects.all()
context["has_fts"] = settings.ENABLE_FTS
context["org_query"] = self.org_query
context["author_query"] = self.author_query
context["search_query"] = self.search_query
context["completed_problem_ids"] = self.get_completed_problems()
context["attempted_problems"] = self.get_attempted_problems()
context["last_attempted_problems"] = self.get_latest_attempted_problems(15)
context["page_type"] = "list"
context.update(self.get_sort_paginate_context())
if not self.in_contest:
@ -736,6 +752,7 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
self.search_query = None
self.category = None
self.org_query = []
self.author_query = []
self.selected_types = []
# This actually copies into the instance dictionary...
@ -749,12 +766,16 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
self.selected_types = list(map(int, request.GET.getlist("type")))
except ValueError:
pass
if "orgs" in request.GET:
try:
self.org_query = list(map(int, request.GET.getlist("orgs")))
except ValueError:
pass
if "authors" in request.GET:
try:
self.author_query = list(map(int, request.GET.getlist("authors")))
except ValueError:
pass
self.point_start = safe_float_or_none(request.GET.get("point_start"))
self.point_end = safe_float_or_none(request.GET.get("point_end"))

File diff suppressed because it is too large Load diff

View file

@ -10,17 +10,15 @@
</noscript>
{% if not request.in_contest_mode %}
<style>
#search-org {
#search-org, #search-author {
width: 100%;
}
#problem-table th {
padding: 0;
}
a.hot-problem-link:hover > .hot-problem-count {
visibility: visible;
}
span.hot-problem-count {
color: #555;
font-size: 0.75em;
@ -29,7 +27,6 @@
padding-left: 0.25em;
position: relative;
}
ul.problem-list {
padding: 0 !important;
}
@ -40,7 +37,6 @@
.volunteer-types {
width: 100%;
}
.point-input {
height: 2em;
padding-top: 4px;
@ -59,18 +55,6 @@
<script src="{{ static('libs/nouislider.min.js') }}" type="text/javascript"></script>
<script>
$(function () {
var info_float = $('.info-float');
var container = $('#content-right');
if (window.bad_browser) {
container.css('float', 'right');
} else if (!featureTest('position', 'sticky')) {
fix_div(info_float, 55);
$(window).resize(function () {
info_float.width(container.width());
});
info_float.width(container.width());
}
{% if not request.in_contest_mode %}
// wrap middle and write column
if (window.matchMedia('(max-width: 799px)').matches) {
@ -110,6 +94,8 @@
.css({'visibility': 'visible'});
$('#search-org').select2({multiple: 1, placeholder: '{{ _('Organizations...') }}'})
.css({'visibility': 'visible'});
$('#search-author').select2({multiple: 1, placeholder: '{{ _('Authors') }}...'})
.css({'visibility': 'visible'});
// This is incredibly nasty to do but it's needed because otherwise the select2 steals the focus
$search.keypress(function (e) {
@ -277,7 +263,6 @@
{% endif %}
<th class="problem">{{ _('Problem') }}</th>
<th class="pcode">{{ _('Problem code') }}</th>
<th class="category">{{ _('Category') }}</th>
{% if show_types %}
<th>{{ _('Types') }}</th>
{% endif %}
@ -293,11 +278,9 @@
<th class="problem">
<a href="{{ sort_links.name }}">{{ _('Problem') }}{{ sort_order.name }}</a>
</th>
<!-- Luong begin -->
<th class="pcode">
<a href="{{ sort_links.code }}">{{ _('Problem code') }}</a>
</th>
<!-- Luong end -->
<th class="category">
<a href="{{ sort_links.group }}">{{ _('Category') }}{{ sort_order.group }}</a>
</th>
@ -360,12 +343,12 @@
<td class="problem">
<a href="{{ url('problem_detail', problem.code) }}">{{ problem.i18n_name }}</a>
</td>
<!-- Luong begin -->
<td class="pcode">
<a class="pcodecell" href="{{ url('problem_detail', problem.code) }}">{{ problem.code }}</a>
</td>
<!-- Luong end -->
{% if not request.in_contest_mode %}
<td class="category">{{ problem.group.full_name }}</td>
{% endif %}
{% if show_types %}
<td class="types">
{% for type in problem.types_list %}
@ -460,9 +443,8 @@
{% block right_sidebar %}
{% if not request.in_contest_mode %}
<div id="content-right" class="problems right-sidebar">
<div class="info-float">
{% include "problem/search-form.html" %}
</div>
{% include "problem/recent-attempt.html" %}
</div>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,20 @@
<div class="sidebox">
<h3>{{ _('Last unsolved') }} <i class="fa fa-bullseye"></i>
</h3>
<div class="sidebox-content" style="padding: 0; border: 0">
<table class="table feed-table">
<tbody>
{% for problem in last_attempted_problems %}
<tr>
<td>
<a href="{{ url('problem_detail', problem.code) }}">{{ problem.name }}</a>
</td>
<td>
<a href="{{ url('submission_status', problem.last_submission) }}">{{ problem.last_submission }}</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>

View file

@ -42,6 +42,16 @@
{% endfor %}
</select>
</div>
<div class="filter-form-group">
<label for="type"><i>{{ _('Author') }}</i></label>
<select id="search-author" name="authors" multiple>
{% for author in all_authors %}
<option value="{{ author.id }}"{% if author.id in author_query %} selected{% endif %}>
{{ author.user.username }}
</option>
{% endfor %}
</select>
</div>
<div class="filter-form-group">
<label for="category"><i>{{ _('Category') }}</i></label>
<select id="category" name="category">