Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 12 additions & 3 deletions oioioi/problems/management/commands/mass_create_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,9 +191,18 @@ def create_proposals(self, count, problems, users, tags, proposal_model, verbose
"""
objs = []
for i in range(count):
problem = random.choice(problems)
user = random.choice(users)
tag = random.choice(tags)
def candidate_fn():
return (random.choice(problems), random.choice(users), random.choice(tags))

def uniqueness_fn(candidate):
problem, user, tag = candidate
if proposal_model.__name__ == 'AlgorithmTagProposal':
return not proposal_model.objects.filter(problem=problem, user=user, tag=tag).exists()
elif proposal_model.__name__ == 'DifficultyTagProposal':
return not proposal_model.objects.filter(problem=problem, user=user).exists()
return True

problem, user, tag = get_unique_candidate(candidate_fn, uniqueness_fn)
proposal = proposal_model(problem=problem, user=user, tag=tag)
proposal.save()
objs.append(proposal)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Generated by Django 5.2.7 on 2025-11-17 22:44

from django.conf import settings
from django.db import migrations
from django.db.models import Count

def cleanup_duplicate_proposals(apps, schema_editor):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have you tried how fast it is on large data?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

considering there are around ~300 rows to delete this is shouldn't be a problem, I added the optimization either way

AlgorithmTagProposal = apps.get_model('problems', 'AlgorithmTagProposal')
DifficultyTagProposal = apps.get_model('problems', 'DifficultyTagProposal')

# Cleanup AlgorithmTagProposal
duplicates = (
AlgorithmTagProposal.objects.values('problem', 'tag', 'user')
.annotate(count=Count('id'))
.filter(count__gt=1)
)

for duplicate in duplicates:
AlgorithmTagProposal.objects.filter(
pk__in=AlgorithmTagProposal.objects.filter(
problem=duplicate["problem"],
tag=duplicate["tag"],
user=duplicate["user"]
).values_list("id", flat=True)[1:]
).delete()

# Cleanup DifficultyTagProposal
duplicates = (
DifficultyTagProposal.objects.values('problem', 'user')
.annotate(count=Count('id'))
.filter(count__gt=1)
)

for duplicate in duplicates:
DifficultyTagProposal.objects.filter(
pk__in=DifficultyTagProposal.objects.filter(
problem=duplicate["problem"],
user=duplicate["user"]
).values_list("id", flat=True)[1:]
).delete()


class Migration(migrations.Migration):

dependencies = [
('problems', '0038_alter_algorithmtaglocalization_language_and_more'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.RunPython(cleanup_duplicate_proposals),
migrations.AlterUniqueTogether(
name='algorithmtagproposal',
unique_together={('problem', 'tag', 'user')},
),
migrations.AlterUniqueTogether(
name='difficultytagproposal',
unique_together={('problem', 'user')},
),
]
2 changes: 2 additions & 0 deletions oioioi/problems/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,7 @@ def __str__(self):
return str(self.problem.name) + " -- " + str(self.tag.name)

class Meta:
unique_together = ("problem", "user")
verbose_name = _("difficulty proposal")
verbose_name_plural = _("difficulty proposals")

Expand Down Expand Up @@ -878,6 +879,7 @@ def __str__(self):
return str(self.problem.name) + " -- " + str(self.tag.name)

class Meta:
unique_together = ("problem", "tag", "user")
verbose_name = _("algorithm tag proposal")
verbose_name_plural = _("algorithm tag proposals")

Expand Down
28 changes: 28 additions & 0 deletions oioioi/problems/tests/test_tag_proposals.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,3 +266,31 @@ def test_save_proposals_view(self):
for q_data in invalid_query_data:
response = self.client.post(self.url, q_data)
self.assertEqual(response.status_code, 400)

def test_duplicate_difficulty_proposal_rejected(self):
problem = Problem.objects.get(pk=0)
user = User.objects.get(username="test_admin")

response = self.client.post(
self.url,
{
"tags[]": ["Dynamic programming"],
"difficulty": "Easy",
"user": "test_admin",
"problem": "0",
},
)
self.assertEqual(response.status_code, 200)
self.assertEqual(DifficultyTagProposal.objects.filter(problem=problem, user=user).count(), 1)

response = self.client.post(
self.url,
{
"tags[]": ["Greedy"],
"difficulty": "Medium",
"user": "test_admin",
"problem": "0",
},
)
self.assertEqual(response.status_code, 400)
self.assertEqual(DifficultyTagProposal.objects.filter(problem=problem, user=user).count(), 1)
8 changes: 8 additions & 0 deletions oioioi/problems/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1156,8 +1156,16 @@ def save_proposals_view(request):
if not tags or not difficulty or not user or not problem:
return HttpResponseBadRequest()

if DifficultyTagProposal.objects.filter(problem=problem, user=user).exists():
return HttpResponseBadRequest()

for tag in tags:
algorithm_tag = AlgorithmTagLocalization.objects.filter(full_name=tag, language=get_language()).first().algorithm_tag


if AlgorithmTagProposal.objects.filter(problem=problem, tag=algorithm_tag, user=user).exists():
return HttpResponseBadRequest()

algorithm_tag_proposal = AlgorithmTagProposal(problem=problem, tag=algorithm_tag, user=user)
algorithm_tag_proposal.save()

Expand Down