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) result = cache.get(key)
if result is None: if result is None:
result = { result = {
id: {"achieved_points": points, "max_points": max_points} id: {
for id, max_points, points in ( "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) Submission.objects.filter(user=profile)
.values_list("problem__id", "problem__points") .values_list(
.annotate(points=Max("points")) "problem__id", "problem__points", "problem__code", "problem__name"
)
.annotate(points=Max("points"), last_submission=Max("id"))
.filter(points__lt=F("problem__points")) .filter(points__lt=F("problem__points"))
) )
} }

View file

@ -52,6 +52,7 @@ from judge.models import (
TranslatedProblemForeignKeyQuerySet, TranslatedProblemForeignKeyQuerySet,
Organization, Organization,
VolunteerProblemVote, VolunteerProblemVote,
Profile,
) )
from judge.pdf_problems import DefaultPdfMaker, HAS_PDF from judge.pdf_problems import DefaultPdfMaker, HAS_PDF
from judge.utils.diggpaginator import DiggPaginator from judge.utils.diggpaginator import DiggPaginator
@ -130,6 +131,15 @@ class SolvedProblemMixin(object):
else: else:
return user_attempted_ids(self.profile) if self.profile is not None 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 @cached_property
def in_contest(self): def in_contest(self):
return ( return (
@ -582,6 +592,8 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
Q(organizations__in=self.org_query) Q(organizations__in=self.org_query)
| Q(contests__contest__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: if self.show_types:
queryset = queryset.prefetch_related("types") queryset = queryset.prefetch_related("types")
if self.category is not None: 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["have_editorial"] = 0 if self.in_contest else int(self.have_editorial)
context["organizations"] = Organization.objects.all() 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["category"] = self.category
context["categories"] = ProblemGroup.objects.all() context["categories"] = ProblemGroup.objects.all()
if self.show_types: if self.show_types:
@ -646,9 +660,11 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
context["problem_types"] = ProblemType.objects.all() context["problem_types"] = ProblemType.objects.all()
context["has_fts"] = settings.ENABLE_FTS context["has_fts"] = settings.ENABLE_FTS
context["org_query"] = self.org_query context["org_query"] = self.org_query
context["author_query"] = self.author_query
context["search_query"] = self.search_query context["search_query"] = self.search_query
context["completed_problem_ids"] = self.get_completed_problems() context["completed_problem_ids"] = self.get_completed_problems()
context["attempted_problems"] = self.get_attempted_problems() context["attempted_problems"] = self.get_attempted_problems()
context["last_attempted_problems"] = self.get_latest_attempted_problems(15)
context["page_type"] = "list" context["page_type"] = "list"
context.update(self.get_sort_paginate_context()) context.update(self.get_sort_paginate_context())
if not self.in_contest: if not self.in_contest:
@ -736,6 +752,7 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
self.search_query = None self.search_query = None
self.category = None self.category = None
self.org_query = [] self.org_query = []
self.author_query = []
self.selected_types = [] self.selected_types = []
# This actually copies into the instance dictionary... # 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"))) self.selected_types = list(map(int, request.GET.getlist("type")))
except ValueError: except ValueError:
pass pass
if "orgs" in request.GET: if "orgs" in request.GET:
try: try:
self.org_query = list(map(int, request.GET.getlist("orgs"))) self.org_query = list(map(int, request.GET.getlist("orgs")))
except ValueError: except ValueError:
pass 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_start = safe_float_or_none(request.GET.get("point_start"))
self.point_end = safe_float_or_none(request.GET.get("point_end")) 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> </noscript>
{% if not request.in_contest_mode %} {% if not request.in_contest_mode %}
<style> <style>
#search-org { #search-org, #search-author {
width: 100%; width: 100%;
} }
#problem-table th { #problem-table th {
padding: 0; padding: 0;
} }
a.hot-problem-link:hover > .hot-problem-count { a.hot-problem-link:hover > .hot-problem-count {
visibility: visible; visibility: visible;
} }
span.hot-problem-count { span.hot-problem-count {
color: #555; color: #555;
font-size: 0.75em; font-size: 0.75em;
@ -29,7 +27,6 @@
padding-left: 0.25em; padding-left: 0.25em;
position: relative; position: relative;
} }
ul.problem-list { ul.problem-list {
padding: 0 !important; padding: 0 !important;
} }
@ -40,7 +37,6 @@
.volunteer-types { .volunteer-types {
width: 100%; width: 100%;
} }
.point-input { .point-input {
height: 2em; height: 2em;
padding-top: 4px; padding-top: 4px;
@ -59,18 +55,6 @@
<script src="{{ static('libs/nouislider.min.js') }}" type="text/javascript"></script> <script src="{{ static('libs/nouislider.min.js') }}" type="text/javascript"></script>
<script> <script>
$(function () { $(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 %} {% if not request.in_contest_mode %}
// wrap middle and write column // wrap middle and write column
if (window.matchMedia('(max-width: 799px)').matches) { if (window.matchMedia('(max-width: 799px)').matches) {
@ -110,6 +94,8 @@
.css({'visibility': 'visible'}); .css({'visibility': 'visible'});
$('#search-org').select2({multiple: 1, placeholder: '{{ _('Organizations...') }}'}) $('#search-org').select2({multiple: 1, placeholder: '{{ _('Organizations...') }}'})
.css({'visibility': 'visible'}); .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 // This is incredibly nasty to do but it's needed because otherwise the select2 steals the focus
$search.keypress(function (e) { $search.keypress(function (e) {
@ -277,7 +263,6 @@
{% endif %} {% endif %}
<th class="problem">{{ _('Problem') }}</th> <th class="problem">{{ _('Problem') }}</th>
<th class="pcode">{{ _('Problem code') }}</th> <th class="pcode">{{ _('Problem code') }}</th>
<th class="category">{{ _('Category') }}</th>
{% if show_types %} {% if show_types %}
<th>{{ _('Types') }}</th> <th>{{ _('Types') }}</th>
{% endif %} {% endif %}
@ -293,11 +278,9 @@
<th class="problem"> <th class="problem">
<a href="{{ sort_links.name }}">{{ _('Problem') }}{{ sort_order.name }}</a> <a href="{{ sort_links.name }}">{{ _('Problem') }}{{ sort_order.name }}</a>
</th> </th>
<!-- Luong begin -->
<th class="pcode"> <th class="pcode">
<a href="{{ sort_links.code }}">{{ _('Problem code') }}</a> <a href="{{ sort_links.code }}">{{ _('Problem code') }}</a>
</th> </th>
<!-- Luong end -->
<th class="category"> <th class="category">
<a href="{{ sort_links.group }}">{{ _('Category') }}{{ sort_order.group }}</a> <a href="{{ sort_links.group }}">{{ _('Category') }}{{ sort_order.group }}</a>
</th> </th>
@ -360,12 +343,12 @@
<td class="problem"> <td class="problem">
<a href="{{ url('problem_detail', problem.code) }}">{{ problem.i18n_name }}</a> <a href="{{ url('problem_detail', problem.code) }}">{{ problem.i18n_name }}</a>
</td> </td>
<!-- Luong begin -->
<td class="pcode"> <td class="pcode">
<a class="pcodecell" href="{{ url('problem_detail', problem.code) }}">{{ problem.code }}</a> <a class="pcodecell" href="{{ url('problem_detail', problem.code) }}">{{ problem.code }}</a>
</td> </td>
<!-- Luong end --> {% if not request.in_contest_mode %}
<td class="category">{{ problem.group.full_name }}</td> <td class="category">{{ problem.group.full_name }}</td>
{% endif %}
{% if show_types %} {% if show_types %}
<td class="types"> <td class="types">
{% for type in problem.types_list %} {% for type in problem.types_list %}
@ -460,9 +443,8 @@
{% block right_sidebar %} {% block right_sidebar %}
{% if not request.in_contest_mode %} {% if not request.in_contest_mode %}
<div id="content-right" class="problems right-sidebar"> <div id="content-right" class="problems right-sidebar">
<div class="info-float"> {% include "problem/search-form.html" %}
{% include "problem/search-form.html" %} {% include "problem/recent-attempt.html" %}
</div>
</div> </div>
{% endif %} {% endif %}
{% endblock %} {% 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 %} {% endfor %}
</select> </select>
</div> </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"> <div class="filter-form-group">
<label for="category"><i>{{ _('Category') }}</i></label> <label for="category"><i>{{ _('Category') }}</i></label>
<select id="category" name="category"> <select id="category" name="category">