Skip to content

Filter by tag proposals #503

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 18 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
acd3c8d
Add 'include_proposals_in_search_results' to context for problem-list…
segir187 Apr 2, 2025
7369d02
Split prefetch_tags into prefetch_tags and prefetch_top_tag_proposals.
segir187 Apr 2, 2025
4fa6fb4
search_problems_in_query -> filter_problems_by_query, prefetch_top_ta…
segir187 Apr 2, 2025
ceccba3
Initial implementation of filtering by top_tag_proposals.
segir187 Apr 2, 2025
758ea19
Fix wrong import source for settings.
segir187 Apr 8, 2025
f6c0768
Fix prefetching top_tag_proposals.
segir187 Apr 8, 2025
7bf2340
Fix filter_problems_by_query - it now returns the modified set.
segir187 Apr 8, 2025
e78f6ee
Filter by algorithm tags last in filter_problems_by_query as it is po…
segir187 Apr 8, 2025
1a3b5bb
aggregated_tag_label now redirects to filtering displaying tag
segir187 Apr 8, 2025
858b247
Checkbox automatically checked if proposals included in http request.
segir187 Apr 8, 2025
35daa3f
Tag label links keep checkbox state.
segir187 Apr 8, 2025
4343d3b
Add TestTagProposalSearch.
segir187 Apr 8, 2025
c24bca8
Change wording next to checkbox to better reflect dual function of th…
segir187 Apr 8, 2025
6308033
Remove needlessly added 'include_proposals_in_search_results'.
segir187 Apr 8, 2025
7dd2e8b
Change 'include_proposals' to have no value.
segir187 Apr 9, 2025
4bb44e1
Change 'include_proposals' to have value 0 or 1 instead of being pres…
segir187 Apr 9, 2025
a5b3f7a
Add 'include_proposals': 0 to tests where it was missing.
segir187 Apr 16, 2025
c15fcc0
Merge branch 'sio2project:master' into FilterByTagProposals
segir187 Apr 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,37 @@ document.addEventListener("DOMContentLoaded", function(){
var checkbox = document.getElementById("show-tag-proposals-checkbox");
if (checkbox) {
var proposals = document.querySelectorAll(".aggregated-proposals");

function toggleProposals() {
proposals.forEach(function(div){
div.style.display = checkbox.checked ? "inline-block" : "none";
});
}

var searchForm = document.getElementById("problemsite_search-form");
if (searchForm) {
searchForm.addEventListener("submit", function(event) {
var control_proposals = document.getElementById("control-include_proposals");
if (checkbox.checked) {
control_proposals.value = "1";
} else {
control_proposals.value = "0";
}
});
}

// Intercept clicks on tag labels and origininfo labels
document.querySelectorAll("a.tag-label").forEach(function(link) {
link.addEventListener("click", function(event) {
if (checkbox.checked) {
event.preventDefault();
var url = new URL(link.href, window.location.origin);
url.searchParams.set("include_proposals", "1");
window.location.href = url.toString();
}
});
});

checkbox.addEventListener("change", toggleProposals);
toggleProposals(); // on page load
}
Expand Down
9 changes: 7 additions & 2 deletions oioioi/problems/templates/problems/problemset/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@
{% endif %}
<script>init_search_selection('problemsite_search');</script>

{% if show_tags and show_tag_proposals %}
<input id="control-include_proposals" class="input" type="hidden" name="include_proposals" value="0"/>
{% endif %}

<span class="input-group-btn">
<button type="submit" class="btn btn-outline-secondary">
<i class="fa-solid fa-magnifying-glass"></i>
Expand Down Expand Up @@ -92,8 +96,9 @@
<div class="row tag-proposal-checkbox-row">
<div class="col-12 text-center">
<label class="form-check-label">
<input id="show-tag-proposals-checkbox" class="form-check-input" type="checkbox" value="">
{% trans "Show tags proposed by users" %}
<input id="show-tag-proposals-checkbox" class="form-check-input" type="checkbox" value=""
{% if request.GET.include_proposals == "1" %}checked{% endif %}>
{% trans "Include tags proposed by users" %}
</label>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,9 @@
</tr>
</thead>
<tbody>
{% if show_tags and show_tag_proposals %}
{% prefetch_tags problems max_tag_proposals_shown min_proposals_per_tag %}
{% elif show_tags %}
{% if show_tags %}
{% prefetch_tags problems %}
{% prefetch_top_tag_proposals problems %}
{% endif %}
{% for problem in problems %}
<tr>
Expand Down
58 changes: 31 additions & 27 deletions oioioi/problems/templatetags/tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,47 @@
from oioioi.base.utils.tags import get_tag_name, get_tag_prefix
from oioioi.problems.models import AggregatedAlgorithmTagProposal
from django.utils.translation import gettext as _, ngettext
from django.conf import settings

register = Library()


@register.simple_tag
def prefetch_tags(problems, max_proposals_shown=0, min_proposals_per_tag=1):
prefetch_tag_proposals = max_proposals_shown > 0
def prefetch_top_tag_proposals(problems):
max_proposals_shown = settings.PROBSET_SHOWN_TAG_PROPOSALS_LIMIT
min_proposals_per_tag = settings.PROBSET_MIN_AMOUNT_TO_CONSIDER_TAG_PROPOSAL

prefetch_related_objects(
problems,
Prefetch(
'aggregatedalgorithmtagproposal_set',
queryset=AggregatedAlgorithmTagProposal.objects.filter(
amount__gte=min_proposals_per_tag
).order_by('-amount')[:max_proposals_shown],
to_attr='top_tag_proposals'
)
)

for problem in problems:
algo_tag_pks = set(problem.algorithmtag_set.all().values_list('pk', flat=True))
problem.top_tag_proposals = [
proposal for proposal in problem.top_tag_proposals
if proposal.tag.pk not in algo_tag_pks
]

return u''


lookups = [
@register.simple_tag
def prefetch_tags(problems):
prefetch_related_objects(
problems,
'difficultytag_set',
'algorithmtag_set__localizations',
'origintag_set__localizations',
'origininfovalue_set__localizations',
'origininfovalue_set__parent_tag__localizations',
]

if prefetch_tag_proposals:
lookups.append(
Prefetch(
'aggregatedalgorithmtagproposal_set',
queryset=AggregatedAlgorithmTagProposal.objects.filter(
amount__gte=min_proposals_per_tag
).order_by('-amount')[:max_proposals_shown],
to_attr='top_tag_proposals'
)
)

prefetch_related_objects(problems, *lookups)

if prefetch_tag_proposals:
for problem in problems:
algo_tag_pks = set(problem.algorithmtag_set.all().values_list('pk', flat=True))
problem.top_tag_proposals = [
proposal for proposal in problem.top_tag_proposals
if proposal.tag.pk not in algo_tag_pks
]

)
return u''


Expand Down Expand Up @@ -74,7 +78,7 @@ def aggregated_tag_label(aggregated_tag):
name=get_tag_name(tag),
cls=full_prefix,
amount=str(amount),
href="?" + tag_prefix + "=" + tag.name,
href="?" + tag_prefix + "=" + tag.name + "&include_proposals=1",
)


Expand Down
69 changes: 69 additions & 0 deletions oioioi/problems/tests/test_problemset.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,75 @@ def test_duplicate_tag_proposal(self):
self.assertNotContains(response, 'greedy<span class="tag-proposal-amount')


@override_settings(
PROBLEM_TAGS_VISIBLE=True,
SHOW_TAG_PROPOSALS_IN_PROBLEMSET=True,
PROBSET_MIN_AMOUNT_TO_CONSIDER_TAG_PROPOSAL=3,
)
class TestTagProposalSearch(TestCase):
fixtures = [
'test_users',
'test_problem_search',
'test_algorithm_tags',
'test_difficulty_tags',
'test_aggregated_tag_proposals',
]
url = reverse('problemset_main')

def _try_single_search(self, dict, hasZadanko, hasZolc):
response = self.client.get(self.url, dict)

if hasZadanko:
self.assertContains(response, 'Zadanko')
else:
self.assertNotContains(response, 'Zadanko')

if hasZolc:
self.assertContains(response, 'Żółć')
else:
self.assertNotContains(response, 'Żółć')

def setUp(self):
self.client.get('/c/c/')
self.assertTrue(self.client.login(username='test_user'))

@override_settings(PROBSET_MIN_AMOUNT_TO_CONSIDER_TAG_PROPOSAL=3)
def test_search_min_3(self):
self._try_single_search({'algorithm': 'greedy', 'include_proposals': 1}, False, True)
self._try_single_search({'algorithm': 'knapsack', 'include_proposals': 1}, False, False)
self._try_single_search({'algorithm': 'greedy', 'include_proposals': 0}, False, False)

@override_settings(PROBSET_MIN_AMOUNT_TO_CONSIDER_TAG_PROPOSAL=2)
def test_search_min_2(self):
self._try_single_search({'algorithm': 'greedy', 'include_proposals': 1}, False, True)
self._try_single_search({'algorithm': 'knapsack', 'include_proposals': 1}, True, False)
self._try_single_search({'algorithm': 'greedy', 'include_proposals': 0}, False, False)

@override_settings(PROBSET_MIN_AMOUNT_TO_CONSIDER_TAG_PROPOSAL=1)
def test_search_min_1(self):
self._try_single_search({'algorithm': 'greedy', 'include_proposals': 1}, True, True)
self._try_single_search({'algorithm': 'knapsack', 'include_proposals': 1}, True, True)
self._try_single_search({'algorithm': 'greedy', 'include_proposals': 0}, False, False)

@override_settings(
PROBSET_MIN_AMOUNT_TO_CONSIDER_TAG_PROPOSAL=1,
PROBSET_SHOWN_TAG_PROPOSALS_LIMIT=1,
)
def test_search_min_1_limit_1(self):
self._try_single_search({'algorithm': 'greedy', 'include_proposals': 1}, False, True)
self._try_single_search({'algorithm': 'knapsack', 'include_proposals': 1}, True, False)
self._try_single_search({'algorithm': 'greedy', 'include_proposals': 0}, False, False)

@override_settings(
PROBSET_MIN_AMOUNT_TO_CONSIDER_TAG_PROPOSAL=1,
PROBSET_SHOWN_TAG_PROPOSALS_LIMIT=0,
)
def test_search_min_1_limit_0(self):
self._try_single_search({'algorithm': 'greedy', 'include_proposals': 1}, False, False)
self._try_single_search({'algorithm': 'knapsack', 'include_proposals': 1}, False, False)
self._try_single_search({'algorithm': 'greedy', 'include_proposals': 0}, False, False)




class TestAddToProblemsetPermissions(TestCase):
Expand Down
35 changes: 26 additions & 9 deletions oioioi/problems/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
problem_site_tab_registry,
)
from oioioi.problems.problem_sources import problem_sources
from oioioi.problems.templatetags.tag import prefetch_top_tag_proposals
from oioioi.problems.utils import (
can_add_to_problemset,
can_admin_instance_of_problem,
Expand Down Expand Up @@ -251,22 +252,38 @@ def _get_problems_by_query(query):
return filtered_problems.distinct()


def search_problems_in_problemset(datadict):
def filter_problems_by_query(problems, datadict):
query = datadict.get('q', '')
algorithm_tags = datadict.getlist('algorithm')
difficulty_tags = datadict.getlist('difficulty')
origin_tags = datadict.getlist('origin')

problems = Problem.objects.all()

if query:
problems = _get_problems_by_query(query)
if algorithm_tags:
problems = problems.filter(algorithmtag__name__in=algorithm_tags)
problems_matching_query = _get_problems_by_query(query)
problems = problems.filter(pk__in=problems_matching_query)
if difficulty_tags:
problems = problems.filter(difficultytag__name__in=difficulty_tags)
if origin_tags:
problems = filter_problems_by_origin(problems, origin_tags)
if algorithm_tags:
if settings.SHOW_TAG_PROPOSALS_IN_PROBLEMSET and datadict['include_proposals'] == '1':
direct_match_problems = problems.filter(algorithmtag__name__in=algorithm_tags)

problem_list = list(problems)
proposal_match_problem_ids = []

prefetch_top_tag_proposals(problems)
for problem in problem_list:
for proposal in problem.top_tag_proposals:
if proposal.tag.name in algorithm_tags:
proposal_match_problem_ids.append(problem.id)
break

proposal_match_problems = problems.filter(id__in=proposal_match_problem_ids)

problems = (direct_match_problems | proposal_match_problems).distinct()
else:
problems = problems.filter(algorithmtag__name__in=algorithm_tags)

return problems

Expand Down Expand Up @@ -305,10 +322,10 @@ def generate_problemset_tabs(request):


def problemset_get_problems(request):
problems = Problem.objects.all()

if settings.PROBLEM_TAGS_VISIBLE:
problems = search_problems_in_problemset(request.GET)
else:
problems = Problem.objects.all()
problems = filter_problems_by_query(problems, request.GET)

if settings.PROBLEM_STATISTICS_AVAILABLE:
# We annotate all of the statistics to assure that the display
Expand Down