feat(problem): 标签名去空格并加大小写不敏感唯一约束
迁移时先合并已存在的重复标签,题目关系转移到保留的那个。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
49
problem/migrations/0014_problem_tag_ci_unique.py
Normal file
49
problem/migrations/0014_problem_tag_ci_unique.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
from collections import defaultdict
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
from django.db.models.functions import Lower
|
||||||
|
|
||||||
|
|
||||||
|
def merge_duplicate_tags(apps, schema_editor):
|
||||||
|
"""加唯一约束前,把「去空格+转小写」后同名的标签合并成一个。
|
||||||
|
|
||||||
|
每组保留关联题目数最多的(并列时保留 id 最小的)作为主标签,
|
||||||
|
其余标签下的题目关系转移到主标签后删除。空名标签同样按此规则归并成一条,
|
||||||
|
不做删除,留给管理员在标签管理页处理。
|
||||||
|
"""
|
||||||
|
ProblemTag = apps.get_model("problem", "ProblemTag")
|
||||||
|
Problem = apps.get_model("problem", "Problem")
|
||||||
|
|
||||||
|
groups = defaultdict(list)
|
||||||
|
for tag in ProblemTag.objects.all():
|
||||||
|
groups[(tag.name or "").strip().lower()].append(tag)
|
||||||
|
|
||||||
|
for tags in groups.values():
|
||||||
|
counts = {tag.id: Problem.objects.filter(tags=tag).count() for tag in tags}
|
||||||
|
tags.sort(key=lambda tag: (-counts[tag.id], tag.id))
|
||||||
|
primary = tags[0]
|
||||||
|
|
||||||
|
stripped = (primary.name or "").strip()
|
||||||
|
if primary.name != stripped:
|
||||||
|
primary.name = stripped
|
||||||
|
primary.save(update_fields=["name"])
|
||||||
|
|
||||||
|
for duplicate in tags[1:]:
|
||||||
|
for problem in Problem.objects.filter(tags=duplicate):
|
||||||
|
problem.tags.add(primary)
|
||||||
|
problem.tags.remove(duplicate)
|
||||||
|
duplicate.delete()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("problem", "0013_remove_problem_io_mode_remove_problem_rule_type_and_more"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(merge_duplicate_tags, migrations.RunPython.noop),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name="problemtag",
|
||||||
|
constraint=models.UniqueConstraint(Lower("name"), name="problem_tag_name_ci_unique"),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.db.models.functions import Lower
|
||||||
|
|
||||||
from account.models import User
|
from account.models import User
|
||||||
from contest.models import Contest
|
from contest.models import Contest
|
||||||
@@ -11,6 +12,13 @@ class ProblemTag(models.Model):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
db_table = "problem_tag"
|
db_table = "problem_tag"
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(Lower("name"), name="problem_tag_name_ci_unique"),
|
||||||
|
]
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
self.name = (self.name or "").strip()
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class Problem(models.Model):
|
class Problem(models.Model):
|
||||||
|
|||||||
Reference in New Issue
Block a user