feat(snippets): 이름 규칙(영문 대문자·숫자·_, 3~50자)·실시간 중복/저장될 이름 표시, 설명 필수, 검색에 본문 ★머리줄·주석 포함(한글 검색), 메타 일괄 반영 명령 apply_snippet_meta
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
aa7481ffe9
commit
7a1498a791
@@ -0,0 +1,73 @@
|
||||
"""검토한 스니펫 메타(한글 설명·새 이름)를 DB 에 반영. CSV 는 docs 의 검토 파일(UTF-8, 헤더 name,new_name,desc).
|
||||
|
||||
python manage.py apply_snippet_meta snippet_meta_review.csv --dry-run # 뭐가 바뀌는지만
|
||||
python manage.py apply_snippet_meta snippet_meta_review.csv # 반영
|
||||
|
||||
- desc: 비어 있거나 한글이 없는(옛 이름 복사 등) 설명만 채움. --overwrite 면 한글 설명도 덮어씀.
|
||||
- new_name: 옛 규칙 이름을 새 이름으로. 사람별 사용 기록(SnippetUsage)도 같이 옮김. 새 이름이 이미 있으면 건너뜀.
|
||||
- DB 에 없는 name 은 건너뜀(사용자가 지웠을 수 있음). 여러 번 돌려도 결과 같음.
|
||||
"""
|
||||
|
||||
import csv
|
||||
import re
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import transaction
|
||||
|
||||
from apps.snippets.models import Snippet, SnippetUsage
|
||||
from apps.snippets.views import name_problem, normalize_name
|
||||
|
||||
KO = re.compile(r"[가-힣]")
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "검토한 스니펫 한글 설명·새 이름을 반영"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("path")
|
||||
parser.add_argument("--dry-run", action="store_true")
|
||||
parser.add_argument("--overwrite", action="store_true", help="이미 한글 설명이 있어도 덮어씀")
|
||||
|
||||
def handle(self, path, dry_run, overwrite, **_):
|
||||
try:
|
||||
with open(path, encoding="utf-8-sig", newline="") as f:
|
||||
rows = list(csv.DictReader(f))
|
||||
except OSError as e:
|
||||
raise CommandError(f"CSV 못 읽음: {e}") from e
|
||||
if rows and not {"name", "desc"} <= set(rows[0]):
|
||||
raise CommandError("CSV 헤더는 name,new_name,desc 여야 함")
|
||||
|
||||
stat = {"desc": 0, "rename": 0, "missing": 0, "skip": 0}
|
||||
with transaction.atomic():
|
||||
for r in rows:
|
||||
old = (r.get("name") or "").strip()
|
||||
s = Snippet.objects.filter(name=old).first()
|
||||
if not s:
|
||||
stat["missing"] += 1
|
||||
continue
|
||||
desc = (r.get("desc") or "").strip()
|
||||
if desc and (overwrite or not KO.search(s.desc or "")) and desc != s.desc:
|
||||
self.stdout.write(f"설명 {old}: {s.desc!r} → {desc!r}")
|
||||
s.desc = desc
|
||||
stat["desc"] += 1
|
||||
if not dry_run:
|
||||
s.save(update_fields=["desc", "updated_at"])
|
||||
new = normalize_name(r.get("new_name") or "")
|
||||
if new and new != old:
|
||||
if name_problem(new) or Snippet.objects.filter(name=new).exists():
|
||||
self.stdout.write(self.style.WARNING(f"이름 {old} → {new}: 규칙 위반이거나 이미 있음 — 건너뜀"))
|
||||
stat["skip"] += 1
|
||||
continue
|
||||
self.stdout.write(f"이름 {old} → {new}")
|
||||
stat["rename"] += 1
|
||||
if not dry_run:
|
||||
# PK 가 이름이라 새로 만들고 사용 기록을 옮긴 뒤 옛 것을 지움
|
||||
Snippet.objects.create(name=new, desc=s.desc, body=s.body, category=s.category, created_by=s.created_by)
|
||||
SnippetUsage.objects.filter(snippet_id=old).update(snippet_id=new)
|
||||
s.delete()
|
||||
if dry_run:
|
||||
transaction.set_rollback(True)
|
||||
self.stdout.write(
|
||||
f"{'(미리보기) ' if dry_run else ''}설명 {stat['desc']} / 이름 변경 {stat['rename']} / "
|
||||
f"건너뜀 {stat['skip']} / DB 에 없음 {stat['missing']} (총 {len(rows)}줄)"
|
||||
)
|
||||
Reference in New Issue
Block a user