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)
|
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"))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
@ -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 %}
|
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 %}
|
{% 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">
|
||||||
|
|
Loading…
Reference in a new issue