Add last unsolved
This commit is contained in:
parent
0b2c410fe5
commit
f79f5a8455
6 changed files with 818 additions and 767 deletions
|
@ -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"))
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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 -->
|
||||
<td class="category">{{ problem.group.full_name }}</td>
|
||||
{% 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/search-form.html" %}
|
||||
{% include "problem/recent-attempt.html" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
20
templates/problem/recent-attempt.html
Normal file
20
templates/problem/recent-attempt.html
Normal 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>
|
|
@ -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">
|
||||
|
|
Loading…
Reference in a new issue