Add fulltext search
This commit is contained in:
parent
aa1b627e6f
commit
36e505952c
7 changed files with 96 additions and 76 deletions
25
judge/migrations/0173_fulltext.py
Normal file
25
judge/migrations/0173_fulltext.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# Generated by Django 3.2.18 on 2023-10-14 00:53
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("judge", "0172_index_rating"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunSQL(
|
||||||
|
(
|
||||||
|
"CREATE FULLTEXT INDEX IF NOT EXISTS code_name_index ON judge_problem (code, name)",
|
||||||
|
),
|
||||||
|
reverse_sql=migrations.RunSQL.noop,
|
||||||
|
),
|
||||||
|
migrations.RunSQL(
|
||||||
|
(
|
||||||
|
"CREATE FULLTEXT INDEX IF NOT EXISTS key_name_index ON judge_contest (`key`, name)",
|
||||||
|
),
|
||||||
|
reverse_sql=migrations.RunSQL.noop,
|
||||||
|
),
|
||||||
|
]
|
|
@ -24,6 +24,7 @@ from judge.models.submission import Submission
|
||||||
from judge.ratings import rate_contest
|
from judge.ratings import rate_contest
|
||||||
from judge.models.pagevote import PageVotable
|
from judge.models.pagevote import PageVotable
|
||||||
from judge.models.bookmark import Bookmarkable
|
from judge.models.bookmark import Bookmarkable
|
||||||
|
from judge.fulltext import SearchManager
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Contest",
|
"Contest",
|
||||||
|
@ -309,6 +310,7 @@ class Contest(models.Model, PageVotable, Bookmarkable):
|
||||||
comments = GenericRelation("Comment")
|
comments = GenericRelation("Comment")
|
||||||
pagevote = GenericRelation("PageVote")
|
pagevote = GenericRelation("PageVote")
|
||||||
bookmark = GenericRelation("BookMark")
|
bookmark = GenericRelation("BookMark")
|
||||||
|
objects = SearchManager(("key", "name"))
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def format_class(self):
|
def format_class(self):
|
||||||
|
|
|
@ -107,9 +107,7 @@ class License(models.Model):
|
||||||
|
|
||||||
class TranslatedProblemQuerySet(SearchQuerySet):
|
class TranslatedProblemQuerySet(SearchQuerySet):
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super(TranslatedProblemQuerySet, self).__init__(
|
super(TranslatedProblemQuerySet, self).__init__(("code", "name"), **kwargs)
|
||||||
("code", "name", "description"), **kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
def add_i18n_name(self, language):
|
def add_i18n_name(self, language):
|
||||||
return self.annotate(
|
return self.annotate(
|
||||||
|
|
|
@ -186,9 +186,16 @@ class ContestList(
|
||||||
self.request.GET.getlist("contest")
|
self.request.GET.getlist("contest")
|
||||||
).strip()
|
).strip()
|
||||||
if query:
|
if query:
|
||||||
queryset = queryset.filter(
|
substr_queryset = queryset.filter(
|
||||||
Q(key__icontains=query) | Q(name__icontains=query)
|
Q(key__icontains=query) | Q(name__icontains=query)
|
||||||
)
|
)
|
||||||
|
if settings.ENABLE_FTS:
|
||||||
|
queryset = (
|
||||||
|
queryset.search(query).extra(order_by=["-relevance"])
|
||||||
|
| substr_queryset
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
queryset = substr_queryset
|
||||||
if not self.org_query and self.request.organization:
|
if not self.org_query and self.request.organization:
|
||||||
self.org_query = [self.request.organization.id]
|
self.org_query = [self.request.organization.id]
|
||||||
if self.show_orgs:
|
if self.show_orgs:
|
||||||
|
|
|
@ -466,10 +466,14 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
|
||||||
manual_sort = frozenset(("name", "group", "solved", "type"))
|
manual_sort = frozenset(("name", "group", "solved", "type"))
|
||||||
all_sorts = sql_sort | manual_sort
|
all_sorts = sql_sort | manual_sort
|
||||||
default_desc = frozenset(("date", "points", "ac_rate", "user_count"))
|
default_desc = frozenset(("date", "points", "ac_rate", "user_count"))
|
||||||
default_sort = "-date"
|
|
||||||
first_page_href = None
|
first_page_href = None
|
||||||
filter_organization = False
|
filter_organization = False
|
||||||
|
|
||||||
|
def get_default_sort_order(self, request):
|
||||||
|
if "search" in request.GET and settings.ENABLE_FTS:
|
||||||
|
return "-relevance"
|
||||||
|
return "-date"
|
||||||
|
|
||||||
def get_paginator(
|
def get_paginator(
|
||||||
self, queryset, per_page, orphans=0, allow_empty_first_page=True, **kwargs
|
self, queryset, per_page, orphans=0, allow_empty_first_page=True, **kwargs
|
||||||
):
|
):
|
||||||
|
@ -485,6 +489,11 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
|
||||||
)
|
)
|
||||||
if not self.in_contest:
|
if not self.in_contest:
|
||||||
queryset = queryset.add_i18n_name(self.request.LANGUAGE_CODE)
|
queryset = queryset.add_i18n_name(self.request.LANGUAGE_CODE)
|
||||||
|
queryset = self.sort_queryset(queryset)
|
||||||
|
paginator.object_list = queryset
|
||||||
|
return paginator
|
||||||
|
|
||||||
|
def sort_queryset(self, queryset):
|
||||||
sort_key = self.order.lstrip("-")
|
sort_key = self.order.lstrip("-")
|
||||||
if sort_key in self.sql_sort:
|
if sort_key in self.sql_sort:
|
||||||
queryset = queryset.order_by(self.order)
|
queryset = queryset.order_by(self.order)
|
||||||
|
@ -518,8 +527,7 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
|
||||||
else "",
|
else "",
|
||||||
reverse=self.order.startswith("-"),
|
reverse=self.order.startswith("-"),
|
||||||
)
|
)
|
||||||
paginator.object_list = queryset
|
return queryset
|
||||||
return paginator
|
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def profile(self):
|
def profile(self):
|
||||||
|
@ -611,12 +619,7 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
|
||||||
self.request.GET.getlist("search")
|
self.request.GET.getlist("search")
|
||||||
).strip()
|
).strip()
|
||||||
if query:
|
if query:
|
||||||
if settings.ENABLE_FTS and self.full_text:
|
substr_queryset = queryset.filter(
|
||||||
queryset = queryset.search(query, queryset.BOOLEAN).extra(
|
|
||||||
order_by=["-relevance"]
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
queryset = queryset.filter(
|
|
||||||
Q(code__icontains=query)
|
Q(code__icontains=query)
|
||||||
| Q(name__icontains=query)
|
| Q(name__icontains=query)
|
||||||
| Q(
|
| Q(
|
||||||
|
@ -624,23 +627,20 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
|
||||||
translations__language=self.request.LANGUAGE_CODE,
|
translations__language=self.request.LANGUAGE_CODE,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
if settings.ENABLE_FTS:
|
||||||
|
queryset = (
|
||||||
|
queryset.search(query, queryset.BOOLEAN).extra(
|
||||||
|
order_by=["-relevance"]
|
||||||
|
)
|
||||||
|
| substr_queryset
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
queryset = substr_queryset
|
||||||
self.prepoint_queryset = queryset
|
self.prepoint_queryset = queryset
|
||||||
if self.point_start is not None:
|
if self.point_start is not None:
|
||||||
queryset = queryset.filter(points__gte=self.point_start)
|
queryset = queryset.filter(points__gte=self.point_start)
|
||||||
if self.point_end is not None:
|
if self.point_end is not None:
|
||||||
queryset = queryset.filter(points__lte=self.point_end)
|
queryset = queryset.filter(points__lte=self.point_end)
|
||||||
queryset = queryset.annotate(
|
|
||||||
has_public_editorial=Case(
|
|
||||||
When(
|
|
||||||
solution__is_public=True,
|
|
||||||
solution__publish_on__lte=timezone.now(),
|
|
||||||
then=True,
|
|
||||||
),
|
|
||||||
default=False,
|
|
||||||
output_field=BooleanField(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return queryset.distinct()
|
return queryset.distinct()
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
@ -658,7 +658,6 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
|
||||||
context["show_types"] = 0 if self.in_contest else int(self.show_types)
|
context["show_types"] = 0 if self.in_contest else int(self.show_types)
|
||||||
context["full_text"] = 0 if self.in_contest else int(self.full_text)
|
context["full_text"] = 0 if self.in_contest else int(self.full_text)
|
||||||
context["show_editorial"] = 0 if self.in_contest else int(self.show_editorial)
|
context["show_editorial"] = 0 if self.in_contest else int(self.show_editorial)
|
||||||
context["have_editorial"] = 0 if self.in_contest else int(self.have_editorial)
|
|
||||||
context["show_solved_only"] = (
|
context["show_solved_only"] = (
|
||||||
0 if self.in_contest else int(self.show_solved_only)
|
0 if self.in_contest else int(self.show_solved_only)
|
||||||
)
|
)
|
||||||
|
@ -768,7 +767,6 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
|
||||||
self.show_types = self.GET_with_session(request, "show_types")
|
self.show_types = self.GET_with_session(request, "show_types")
|
||||||
self.full_text = self.GET_with_session(request, "full_text")
|
self.full_text = self.GET_with_session(request, "full_text")
|
||||||
self.show_editorial = self.GET_with_session(request, "show_editorial")
|
self.show_editorial = self.GET_with_session(request, "show_editorial")
|
||||||
self.have_editorial = self.GET_with_session(request, "have_editorial")
|
|
||||||
self.show_solved_only = self.GET_with_session(request, "show_solved_only")
|
self.show_solved_only = self.GET_with_session(request, "show_solved_only")
|
||||||
|
|
||||||
self.search_query = None
|
self.search_query = None
|
||||||
|
@ -816,7 +814,6 @@ class ProblemList(QueryStringSortMixin, TitleMixin, SolvedProblemMixin, ListView
|
||||||
"show_types",
|
"show_types",
|
||||||
"full_text",
|
"full_text",
|
||||||
"show_editorial",
|
"show_editorial",
|
||||||
"have_editorial",
|
|
||||||
"show_solved_only",
|
"show_solved_only",
|
||||||
)
|
)
|
||||||
for key in to_update:
|
for key in to_update:
|
||||||
|
@ -862,9 +859,6 @@ class ProblemFeed(ProblemList, FeedView):
|
||||||
self.show_types = 1
|
self.show_types = 1
|
||||||
queryset = super(ProblemFeed, self).get_queryset()
|
queryset = super(ProblemFeed, self).get_queryset()
|
||||||
|
|
||||||
if self.have_editorial:
|
|
||||||
queryset = queryset.filter(has_public_editorial=1)
|
|
||||||
|
|
||||||
user = self.request.profile
|
user = self.request.profile
|
||||||
|
|
||||||
if self.feed_type == "new":
|
if self.feed_type == "new":
|
||||||
|
@ -886,6 +880,8 @@ class ProblemFeed(ProblemList, FeedView):
|
||||||
.order_by("?")
|
.order_by("?")
|
||||||
.add_i18n_name(self.request.LANGUAGE_CODE)
|
.add_i18n_name(self.request.LANGUAGE_CODE)
|
||||||
)
|
)
|
||||||
|
if "search" in self.request.GET:
|
||||||
|
return queryset.add_i18n_name(self.request.LANGUAGE_CODE)
|
||||||
if not settings.ML_OUTPUT_PATH or not user:
|
if not settings.ML_OUTPUT_PATH or not user:
|
||||||
return queryset.order_by("?").add_i18n_name(self.request.LANGUAGE_CODE)
|
return queryset.order_by("?").add_i18n_name(self.request.LANGUAGE_CODE)
|
||||||
|
|
||||||
|
@ -946,7 +942,6 @@ class ProblemFeed(ProblemList, FeedView):
|
||||||
context["title"] = self.title
|
context["title"] = self.title
|
||||||
context["feed_type"] = self.feed_type
|
context["feed_type"] = self.feed_type
|
||||||
context["has_show_editorial_option"] = False
|
context["has_show_editorial_option"] = False
|
||||||
context["has_have_editorial_option"] = False
|
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
|
@ -114,7 +114,7 @@
|
||||||
|
|
||||||
$('#go').click(clean_submit);
|
$('#go').click(clean_submit);
|
||||||
|
|
||||||
$('input#full_text, input#hide_solved, input#show_types, input#show_editorial, input#have_editorial, input#show_solved_only').click(function () {
|
$('input#full_text, input#hide_solved, input#show_types, input#have_editorial, input#show_solved_only').click(function () {
|
||||||
prep_form();
|
prep_form();
|
||||||
($('<form>').attr('action', window.location.pathname + '?' + form_serialize())
|
($('<form>').attr('action', window.location.pathname + '?' + form_serialize())
|
||||||
.append($('<input>').attr('type', 'hidden').attr('name', 'csrfmiddlewaretoken')
|
.append($('<input>').attr('type', 'hidden').attr('name', 'csrfmiddlewaretoken')
|
||||||
|
|
|
@ -34,13 +34,6 @@
|
||||||
<label for="show_editorial">{{ _('Show editorial') }}</label>
|
<label for="show_editorial">{{ _('Show editorial') }}</label>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if has_have_editorial_option %}
|
|
||||||
<div>
|
|
||||||
<input id="have_editorial" type="checkbox" name="have_editorial" value="1"
|
|
||||||
{% if have_editorial %} checked{% endif %}>
|
|
||||||
<label for="have_editorial">{{ _('Have editorial') }}</label>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if organizations %}
|
{% if organizations %}
|
||||||
<div class="filter-form-group">
|
<div class="filter-form-group">
|
||||||
<label class="bold-text margin-label" for="type"><i class="non-italics">{{ _('Group') }}</i></label>
|
<label class="bold-text margin-label" for="type"><i class="non-italics">{{ _('Group') }}</i></label>
|
||||||
|
|
Loading…
Add table
Reference in a new issue