Поправил csv импорт

This commit is contained in:
2025-12-01 16:42:17 +03:00
parent 8d75e47abc
commit 300927c7ea
2 changed files with 215 additions and 77 deletions

View File

@@ -223,8 +223,11 @@ def parse_transponders_from_json(filepath: str, user=None):
# Third-party imports (additional)
import logging
from lxml import etree
logger = logging.getLogger(__name__)
def parse_transponders_from_xml(data_in: BytesIO, user=None):
"""
Парсит транспондеры из XML файла.
@@ -232,9 +235,23 @@ def parse_transponders_from_xml(data_in: BytesIO, user=None):
Если имя спутника содержит альтернативное имя в скобках, оно извлекается
и сохраняется в поле alternative_name.
Процесс импорта:
1. Сначала создаются/обновляются все спутники
2. Затем для каждого спутника добавляются его транспондеры
Args:
data_in: BytesIO объект с XML данными
user: пользователь для установки created_by и updated_by (optional)
Returns:
dict: Статистика импорта с ключами:
- satellites_created: количество созданных спутников
- satellites_updated: количество обновлённых спутников
- satellites_skipped: количество пропущенных спутников (дубликаты)
- satellites_ignored: количество игнорированных спутников (X, DONT USE)
- transponders_created: количество созданных транспондеров
- transponders_existing: количество существующих транспондеров
- errors: список ошибок с деталями
"""
tree = etree.parse(data_in)
ns = {
@@ -243,48 +260,38 @@ def parse_transponders_from_xml(data_in: BytesIO, user=None):
'tr': 'http://schemas.datacontract.org/2004/07/Geolocation.Common.Extensions'
}
satellites = tree.xpath('//ns:SatelliteMemo', namespaces=ns)
for sat in satellites[:]:
# Статистика импорта
stats = {
'satellites_created': 0,
'satellites_updated': 0,
'satellites_skipped': 0,
'satellites_ignored': 0,
'transponders_created': 0,
'transponders_existing': 0,
'errors': []
}
# Этап 1: Создание/обновление спутников
satellite_map = {} # Словарь для связи XML элементов со спутниками в БД
for sat in satellites:
name_full = sat.xpath('./ns:name/text()', namespaces=ns)[0]
# Игнорируем служебные записи
if name_full == 'X' or 'DONT USE' in name_full:
stats['satellites_ignored'] += 1
logger.info(f"Игнорирован спутник: {name_full}")
continue
# Парсим имя спутника и альтернативное имя
main_name, alt_name = parse_satellite_name(name_full)
norad = sat.xpath('./ns:norad/text()', namespaces=ns)
beams = sat.xpath('.//ns:BeamMemo', namespaces=ns)
intl_code = sat.xpath('.//ns:internationalCode/text()', namespaces=ns)
sub_sat_point = sat.xpath('.//ns:subSatellitePoint/text()', namespaces=ns)
zones = {}
for zone in beams:
zone_name = zone.xpath('./ns:name/text()', namespaces=ns)[0] if zone.xpath('./ns:name/text()', namespaces=ns) else '-'
zones[zone.xpath('./ns:id/text()', namespaces=ns)[0]] = {
"name": zone_name,
"pol": zone.xpath('./ns:polarization/text()', namespaces=ns)[0],
}
transponders = sat.xpath('.//ns:TransponderMemo', namespaces=ns)
for transponder in transponders:
tr_id = transponder.xpath('./ns:downlinkBeamId/text()', namespaces=ns)[0]
downlink_start = float(transponder.xpath('./ns:downlinkFrequency/tr:start/text()', namespaces=ns)[0])
downlink_end = float(transponder.xpath('./ns:downlinkFrequency/tr:end/text()', namespaces=ns)[0])
uplink_start = float(transponder.xpath('./ns:uplinkFrequency/tr:start/text()', namespaces=ns)[0])
uplink_end = float(transponder.xpath('./ns:uplinkFrequency/tr:end/text()', namespaces=ns)[0])
tr_data = zones[tr_id]
match tr_data['pol']:
case 'Horizontal':
pol = 'Горизонтальная'
case 'Vertical':
pol = 'Вертикальная'
case 'CircularRight':
pol = 'Правая'
case 'CircularLeft':
pol = 'Левая'
case _:
pol = '-'
tr_name = transponder.xpath('./ns:name/text()', namespaces=ns)[0]
pol_obj, _ = Polarization.objects.get_or_create(name=pol)
try:
# Ищем спутник по имени или альтернативному имени
sat_obj = find_satellite_by_name(main_name)
if not sat_obj:
@@ -296,8 +303,10 @@ def parse_transponders_from_xml(data_in: BytesIO, user=None):
international_code=intl_code[0] if intl_code else "",
undersat_point=float(sub_sat_point[0]) if sub_sat_point else None
)
stats['satellites_created'] += 1
logger.info(f"Создан спутник: {main_name} (альт. имя: {alt_name})")
else:
# Если найден, обновляем альтернативное имя если не установлено
# Если найден, обновляем поля если они не установлены
updated = False
if alt_name and not sat_obj.alternative_name:
sat_obj.alternative_name = alt_name
@@ -313,20 +322,130 @@ def parse_transponders_from_xml(data_in: BytesIO, user=None):
updated = True
if updated:
sat_obj.save()
stats['satellites_updated'] += 1
logger.info(f"Обновлён спутник: {main_name}")
trans_obj, created = Transponders.objects.get_or_create(
polarization=pol_obj,
downlink=(downlink_start+downlink_end)/2/1000000,
uplink=(uplink_start+uplink_end)/2/1000000,
frequency_range=abs(downlink_end-downlink_start)/1000000,
name=tr_name,
defaults={
"zone_name": tr_data['name'],
"sat_id": sat_obj,
}
# Сохраняем связь XML элемента со спутником в БД
satellite_map[sat] = sat_obj
except Satellite.MultipleObjectsReturned:
# Найдено несколько спутников - пропускаем
stats['satellites_skipped'] += 1
duplicates = Satellite.objects.filter(
Q(name__icontains=main_name.lower()) |
Q(alternative_name__icontains=main_name.lower())
)
if user:
if created:
trans_obj.created_by = user
trans_obj.updated_by = user
trans_obj.save()
duplicate_names = [f"{s.name} (ID: {s.id})" for s in duplicates]
error_msg = f"Найдено несколько спутников для '{name_full}': {', '.join(duplicate_names)}"
stats['errors'].append({
'type': 'duplicate_satellite',
'satellite': name_full,
'details': duplicate_names
})
logger.warning(error_msg)
continue
except Exception as e:
# Другие ошибки при обработке спутника
stats['satellites_skipped'] += 1
error_msg = f"Ошибка при обработке спутника '{name_full}': {str(e)}"
stats['errors'].append({
'type': 'satellite_error',
'satellite': name_full,
'error': str(e)
})
logger.error(error_msg, exc_info=True)
continue
# Этап 2: Добавление транспондеров для каждого спутника
for sat, sat_obj in satellite_map.items():
sat_name = sat.xpath('./ns:name/text()', namespaces=ns)[0]
try:
beams = sat.xpath('.//ns:BeamMemo', namespaces=ns)
zones = {}
for zone in beams:
zone_name = zone.xpath('./ns:name/text()', namespaces=ns)[0] if zone.xpath('./ns:name/text()', namespaces=ns) else '-'
zones[zone.xpath('./ns:id/text()', namespaces=ns)[0]] = {
"name": zone_name,
"pol": zone.xpath('./ns:polarization/text()', namespaces=ns)[0],
}
transponders = sat.xpath('.//ns:TransponderMemo', namespaces=ns)
for transponder in transponders:
try:
tr_id = transponder.xpath('./ns:downlinkBeamId/text()', namespaces=ns)[0]
downlink_start = float(transponder.xpath('./ns:downlinkFrequency/tr:start/text()', namespaces=ns)[0])
downlink_end = float(transponder.xpath('./ns:downlinkFrequency/tr:end/text()', namespaces=ns)[0])
uplink_start = float(transponder.xpath('./ns:uplinkFrequency/tr:start/text()', namespaces=ns)[0])
uplink_end = float(transponder.xpath('./ns:uplinkFrequency/tr:end/text()', namespaces=ns)[0])
tr_data = zones[tr_id]
match tr_data['pol']:
case 'Horizontal':
pol = 'Горизонтальная'
case 'Vertical':
pol = 'Вертикальная'
case 'CircularRight':
pol = 'Правая'
case 'CircularLeft':
pol = 'Левая'
case _:
pol = '-'
tr_name = transponder.xpath('./ns:name/text()', namespaces=ns)[0]
pol_obj, _ = Polarization.objects.get_or_create(name=pol)
trans_obj, created = Transponders.objects.get_or_create(
polarization=pol_obj,
downlink=(downlink_start+downlink_end)/2/1000000,
uplink=(uplink_start+uplink_end)/2/1000000,
frequency_range=abs(downlink_end-downlink_start)/1000000,
name=tr_name,
defaults={
"zone_name": tr_data['name'],
"sat_id": sat_obj,
}
)
if created:
stats['transponders_created'] += 1
else:
stats['transponders_existing'] += 1
if user:
if created:
trans_obj.created_by = user
trans_obj.updated_by = user
trans_obj.save()
except Exception as e:
error_msg = f"Ошибка при обработке транспондера спутника '{sat_name}': {str(e)}"
stats['errors'].append({
'type': 'transponder_error',
'satellite': sat_name,
'error': str(e)
})
logger.error(error_msg, exc_info=True)
continue
except Exception as e:
error_msg = f"Ошибка при обработке транспондеров спутника '{sat_name}': {str(e)}"
stats['errors'].append({
'type': 'transponders_processing_error',
'satellite': sat_name,
'error': str(e)
})
logger.error(error_msg, exc_info=True)
continue
# Итоговая статистика в лог
logger.info(
f"Импорт завершён. Спутники: создано {stats['satellites_created']}, "
f"обновлено {stats['satellites_updated']}, пропущено {stats['satellites_skipped']}, "
f"игнорировано {stats['satellites_ignored']}. "
f"Транспондеры: создано {stats['transponders_created']}, "
f"существующих {stats['transponders_existing']}. "
f"Ошибок: {len(stats['errors'])}"
)
return stats