mod: replace legacy joomla links
This commit is contained in:
2
cadpoint/web/management/__init__.py
Normal file
2
cadpoint/web/management/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
"""Пакет management-команд для приложения web."""
|
||||
|
||||
2
cadpoint/web/management/commands/__init__.py
Normal file
2
cadpoint/web/management/commands/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
"""Набор management-команд для приложения web."""
|
||||
|
||||
85
cadpoint/web/management/commands/replace_legacy_links.py
Normal file
85
cadpoint/web/management/commands/replace_legacy_links.py
Normal file
@@ -0,0 +1,85 @@
|
||||
"""Mass-замена старых Joomla-кросс-ссылок в HTML-контенте.
|
||||
|
||||
Пока команда чинит только внутренние ссылки на статьи. Ссылки на картинки и
|
||||
прочие медиа остаются без изменений.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import Counter
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
|
||||
from web.legacy_links import iter_legacy_link_matches, replace_legacy_links
|
||||
from web.models import TbContent
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = (
|
||||
'Находит и заменяет старые Joomla-кросс-ссылки в HTML-контенте на '
|
||||
'текущие Django-URL. Ссылки на медиа пока не трогает.'
|
||||
)
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--apply',
|
||||
action='store_true',
|
||||
help='Сохранить изменения в базе. Без флага команда работает в режиме dry-run.',
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
apply_changes = options['apply']
|
||||
# Сначала один раз собираем карту `id -> slug`, чтобы не делать лишние
|
||||
# запросы к базе в цикле по каждому контенту.
|
||||
content_by_id = dict(
|
||||
TbContent.objects.values_list('id', 'szContentSlug')
|
||||
)
|
||||
# Обрабатываем только HTML-поля, где реально встречаются старые ссылки.
|
||||
fields = ('szContentIntro', 'szContentBody', 'szContentHead')
|
||||
pattern_counter = Counter()
|
||||
updated_objects = 0
|
||||
updated_fields = 0
|
||||
|
||||
for content in TbContent.objects.all().iterator():
|
||||
field_updates: dict[str, str] = {}
|
||||
object_matches = []
|
||||
|
||||
for field_name in fields:
|
||||
text = getattr(content, field_name) or ''
|
||||
if not text:
|
||||
continue
|
||||
# Быстрая проверка: если в тексте нет legacy-ссылок, не тратим
|
||||
# время на полноценную замену.
|
||||
if not any(match for match in iter_legacy_link_matches(text)):
|
||||
continue
|
||||
|
||||
new_text, matches = replace_legacy_links(text, content_by_id)
|
||||
if matches and new_text != text:
|
||||
field_updates[field_name] = new_text
|
||||
object_matches.extend(matches)
|
||||
pattern_counter.update(match.pattern_name for match in matches)
|
||||
|
||||
if not field_updates:
|
||||
continue
|
||||
|
||||
updated_objects += 1
|
||||
updated_fields += len(field_updates)
|
||||
self.stdout.write(
|
||||
f'#{content.pk}: {len(object_matches)} замен(ы) в полях {", ".join(field_updates)}'
|
||||
)
|
||||
for match in object_matches[:5]:
|
||||
self.stdout.write(f' - {match.pattern_name}: {match.old_url} -> {match.new_url}')
|
||||
if len(object_matches) > 5:
|
||||
self.stdout.write(f' ... ещё {len(object_matches) - 5} замен(ы)')
|
||||
|
||||
if apply_changes:
|
||||
# Записываем только те поля, которые действительно изменились.
|
||||
with transaction.atomic():
|
||||
TbContent.objects.filter(pk=content.pk).update(**field_updates)
|
||||
|
||||
self.stdout.write(self.style.SUCCESS(f'Затронуто объектов: {updated_objects}'))
|
||||
self.stdout.write(self.style.SUCCESS(f'Затронуто полей: {updated_fields}'))
|
||||
self.stdout.write(self.style.SUCCESS(f'Сводка по шаблонам: {dict(pattern_counter)}'))
|
||||
if not apply_changes:
|
||||
self.stdout.write(self.style.WARNING('Это dry-run. Для записи в БД добавь флаг --apply.'))
|
||||
|
||||
Reference in New Issue
Block a user