Initial commit: certgen — сервис генерации сертификатов
This commit is contained in:
commit
49bac43e16
23 files changed
+3046
No files matched your search
+115
@@ -0,0 +1,115 @@
|
||||
"""CLI: пакетная генерация документов без веб-сервера.
|
||||
|
||||
Примеры:
|
||||
python -m certgen.gen --template classic --fio "Иванов Иван Иванович" \
|
||||
--body "за успешное прохождение курса"
|
||||
|
||||
python -m certgen.gen --template letter --csv names.csv --outdir out
|
||||
|
||||
В CSV первая строка может быть заголовком (fio,title,body,date,prefix);
|
||||
допускаются разделители ',' и ';'. Если колонок нет — берётся первый столбец как ФИО.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
from . import config, store
|
||||
from .service import create_certificate
|
||||
|
||||
|
||||
def _read_csv(path: Path) -> list[dict]:
|
||||
text = path.read_text(encoding="utf-8-sig")
|
||||
first = text.splitlines()[0] if text.strip() else ""
|
||||
delim = ";" if first.count(";") > first.count(",") else ","
|
||||
rows = list(csv.reader(text.splitlines(), delimiter=delim))
|
||||
rows = [r for r in rows if any(c.strip() for c in r)]
|
||||
if not rows:
|
||||
return []
|
||||
header = [c.strip().lower() for c in rows[0]]
|
||||
known = {"fio", "фио", "title", "заголовок", "body", "текст",
|
||||
"date", "дата", "prefix", "префикс", "number", "номер", "email", "почта"}
|
||||
has_header = bool(known & set(header))
|
||||
records = []
|
||||
data = rows[1:] if has_header else rows
|
||||
keys = header if has_header else ["fio"]
|
||||
for row in data:
|
||||
rec = {}
|
||||
if has_header:
|
||||
for i, key in enumerate(keys):
|
||||
rec[key] = row[i] if i < len(row) else ""
|
||||
else:
|
||||
rec["fio"] = row[0]
|
||||
# нормализуем русские заголовки
|
||||
for ru, en in (("фио", "fio"), ("заголовок", "title"), ("текст", "body"),
|
||||
("дата", "date"), ("префикс", "prefix"), ("номер", "number"),
|
||||
("почта", "email")):
|
||||
if ru in rec and en not in rec:
|
||||
rec[en] = rec.pop(ru)
|
||||
records.append(rec)
|
||||
return records
|
||||
|
||||
|
||||
def main() -> None:
|
||||
p = argparse.ArgumentParser(prog="certgen.gen", description="Пакетная генерация документов")
|
||||
p.add_argument("--template", default="classic", help="slug шаблона")
|
||||
p.add_argument("--fio", help="ФИО для одного документа")
|
||||
p.add_argument("--email", help="email получателя документа")
|
||||
p.add_argument("--title", help="заголовок")
|
||||
p.add_argument("--body", help="дополнительный текст")
|
||||
p.add_argument("--date", help="дата")
|
||||
p.add_argument("--prefix", help="префикс номера")
|
||||
p.add_argument("--number", help="особый номер из резерва 1..(START-1)")
|
||||
p.add_argument("--csv", type=Path, help="CSV со списком")
|
||||
p.add_argument("--names", help="список ФИО через ';'")
|
||||
p.add_argument("--outdir", type=Path, help="куда скопировать готовые файлы")
|
||||
p.add_argument("--base", help="базовый URL для ссылок")
|
||||
p.add_argument("--json", action="store_true", help="вывести результат как JSON")
|
||||
args = p.parse_args()
|
||||
|
||||
config.ensure_dirs()
|
||||
store.init_db()
|
||||
template = store.get_template(args.template)
|
||||
if not template:
|
||||
raise SystemExit(f"Неизвестный шаблон: {args.template}. "
|
||||
f"Доступны: {[t['slug'] for t in store.list_templates()]}")
|
||||
|
||||
records: list[dict] = []
|
||||
if args.csv:
|
||||
records = _read_csv(args.csv)
|
||||
elif args.names:
|
||||
records = [{"fio": n.strip()} for n in args.names.split(";") if n.strip()]
|
||||
elif args.fio:
|
||||
records = [{"fio": args.fio}]
|
||||
else:
|
||||
raise SystemExit("Укажите --fio, --names или --csv")
|
||||
|
||||
results = []
|
||||
for rec in records:
|
||||
body = {"fio": rec.get("fio", "")}
|
||||
for key, val in (("title", args.title), ("body", args.body),
|
||||
("date", args.date), ("prefix", args.prefix),
|
||||
("number", args.number), ("email", args.email)):
|
||||
value = rec.get(key) or val
|
||||
if value:
|
||||
body[key] = value
|
||||
cert = create_certificate(template, body, base=args.base)
|
||||
if args.outdir:
|
||||
args.outdir.mkdir(parents=True, exist_ok=True)
|
||||
safe = re.sub(r"[^\w.-]+", "_", cert["number"]) or str(cert["seq"])
|
||||
shutil.copyfile(cert["png_path"], args.outdir / f"{safe}.png")
|
||||
shutil.copyfile(cert["pdf_path"], args.outdir / f"{safe}.pdf")
|
||||
results.append(cert)
|
||||
if not args.json:
|
||||
print(f"№ {cert['number']} {cert['fio']} -> {cert['url']}")
|
||||
|
||||
if args.json:
|
||||
print(json.dumps(results, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user