Compare commits

..

2 Commits

8 changed files with 906 additions and 608 deletions

1
.gitignore vendored
View File

@@ -34,3 +34,4 @@ tiles
# docker-*
maplibre-gl-js-5.10.0.zip
cert.pem
templ.json

View File

@@ -214,12 +214,15 @@ class CSVImportTestCase(TestCase):
3;Signal3 V;56.8389;60.6057;0;01.01.2024 12:10:00;Test Satellite;12345;11540.7;36.0;1;good;Mirror1;Mirror2;"""
# Выполняем импорт
sources_created = get_points_from_csv(csv_content, self.custom_user)
result = get_points_from_csv(csv_content, self.custom_user)
# Проверяем результаты
# Первые две точки близко (Москва), третья далеко (Екатеринбург)
# Должно быть создано 2 источника
self.assertEqual(sources_created, 2)
self.assertEqual(result['new_sources'], 2)
self.assertEqual(result['added'], 3)
self.assertEqual(result['skipped'], 0)
self.assertEqual(len(result['errors']), 0)
self.assertEqual(Source.objects.count(), 2)
self.assertEqual(ObjItem.objects.count(), 3)
@@ -237,8 +240,8 @@ class CSVImportTestCase(TestCase):
csv_content_1 = """1;Signal1 V;55.7558;37.6173;0;01.01.2024 12:00:00;Test Satellite;12345;11500.5;36.0;1;good;Mirror1;Mirror2;
2;Signal2 H;55.7560;37.6175;0;01.01.2024 12:05:00;Test Satellite;12345;11520.3;36.0;1;good;Mirror1;Mirror2;"""
sources_created_1 = get_points_from_csv(csv_content_1, self.custom_user)
self.assertEqual(sources_created_1, 1)
result_1 = get_points_from_csv(csv_content_1, self.custom_user)
self.assertEqual(result_1['new_sources'], 1)
initial_sources_count = Source.objects.count()
initial_objitems_count = ObjItem.objects.count()
@@ -248,11 +251,12 @@ class CSVImportTestCase(TestCase):
csv_content_2 = """3;Signal3 V;55.7562;37.6177;0;01.01.2024 12:10:00;Test Satellite;12345;11540.7;36.0;1;good;Mirror1;Mirror2;
4;Signal4 H;56.8389;60.6057;0;01.01.2024 12:15:00;Test Satellite;12345;11560.2;36.0;1;good;Mirror1;Mirror2;"""
sources_created_2 = get_points_from_csv(csv_content_2, self.custom_user)
result_2 = get_points_from_csv(csv_content_2, self.custom_user)
# Проверяем результаты
# Должен быть создан 1 новый источник (для точки 4)
self.assertEqual(sources_created_2, 1)
self.assertEqual(result_2['new_sources'], 1)
self.assertEqual(result_2['added'], 2)
self.assertEqual(Source.objects.count(), initial_sources_count + 1)
self.assertEqual(ObjItem.objects.count(), initial_objitems_count + 2)
@@ -276,10 +280,12 @@ class CSVImportTestCase(TestCase):
# Второй импорт - та же точка (дубликат)
csv_content_2 = """1;Signal1 V;55.7558;37.6173;0;01.01.2024 12:00:00;Test Satellite;12345;11500.5;36.0;1;good;Mirror1;Mirror2;"""
sources_created = get_points_from_csv(csv_content_2, self.custom_user)
result = get_points_from_csv(csv_content_2, self.custom_user)
# Проверяем, что дубликат пропущен
self.assertEqual(sources_created, 0)
self.assertEqual(result['new_sources'], 0)
self.assertEqual(result['added'], 0)
self.assertEqual(result['skipped'], 1)
self.assertEqual(Source.objects.count(), initial_sources_count)
self.assertEqual(ObjItem.objects.count(), initial_objitems_count)
@@ -304,10 +310,12 @@ class CSVImportTestCase(TestCase):
4;Signal4 H;56.8389;60.6057;0;01.01.2024 12:15:00;Test Satellite;12345;11560.2;36.0;1;good;Mirror1;Mirror2;
5;Signal5 V;56.8391;60.6059;0;01.01.2024 12:20:00;Test Satellite;12345;11580.8;36.0;1;good;Mirror1;Mirror2;"""
sources_created = get_points_from_csv(csv_content_2, self.custom_user)
result = get_points_from_csv(csv_content_2, self.custom_user)
# Проверяем результаты
self.assertEqual(sources_created, 1) # Только для Екатеринбурга
self.assertEqual(result['new_sources'], 1) # Только для Екатеринбурга
self.assertEqual(result['added'], 3) # Точки 3, 4, 5
self.assertEqual(result['skipped'], 1) # Точка 1 (дубликат)
self.assertEqual(Source.objects.count(), 2) # Москва + Екатеринбург
self.assertEqual(ObjItem.objects.count(), 5) # 2 начальных + 3 новых (дубликат пропущен)

View File

@@ -122,6 +122,72 @@ MINIMUM_BANDWIDTH_MHZ = 0.08
RANGE_DISTANCE = 56
# ============================================================================
# Вспомогательные функции для работы со спутниками
# ============================================================================
class SatelliteNotFoundError(Exception):
"""Исключение, возникающее когда спутник не найден в базе данных."""
pass
def get_satellite_by_norad(norad_id: int) -> Satellite:
"""
Получает спутник по NORAD ID с обработкой ошибок.
Args:
norad_id: NORAD ID спутника
Returns:
Satellite: объект спутника
Raises:
SatelliteNotFoundError: если спутник не найден
ValueError: если norad_id некорректен
"""
if not norad_id or norad_id == -1:
raise ValueError(f"Некорректный NORAD ID: {norad_id}")
try:
return Satellite.objects.get(norad=norad_id)
except Satellite.DoesNotExist:
raise SatelliteNotFoundError(
f"Спутник с NORAD ID {norad_id} не найден в базе данных. "
f"Добавьте спутник в справочник перед импортом данных."
)
except Satellite.MultipleObjectsReturned:
# Если по какой-то причине есть дубликаты, берем первый
return Satellite.objects.filter(norad=norad_id).first()
def get_satellite_by_name(name: str) -> Satellite:
"""
Получает спутник по имени с обработкой ошибок.
Args:
name: имя спутника
Returns:
Satellite: объект спутника
Raises:
SatelliteNotFoundError: если спутник не найден
"""
if not name or name.strip() == "-":
raise ValueError(f"Некорректное имя спутника: {name}")
try:
return Satellite.objects.get(name=name.strip())
except Satellite.DoesNotExist:
raise SatelliteNotFoundError(
f"Спутник '{name}' не найден в базе данных. "
f"Добавьте спутник в справочник перед импортом данных."
)
except Satellite.MultipleObjectsReturned:
# Если есть дубликаты по имени, берем первый
return Satellite.objects.filter(name=name.strip()).first()
def get_all_constants():
sats = [sat.name for sat in Satellite.objects.all()]
standards = [sat.name for sat in Standard.objects.all()]
@@ -307,7 +373,12 @@ def fill_data_from_df(df: pd.DataFrame, sat: Satellite, current_user=None, is_au
is_automatic: если True, точки не добавляются к Source (optional, default=False)
Returns:
int: количество созданных Source (или 0 если is_automatic=True)
dict: словарь с результатами импорта {
'new_sources': количество созданных Source,
'added': количество добавленных точек,
'skipped': количество пропущенных дубликатов,
'errors': список ошибок
}
"""
try:
df.rename(columns={"Модуляция ": "Модуляция"}, inplace=True)
@@ -321,6 +392,7 @@ def fill_data_from_df(df: pd.DataFrame, sat: Satellite, current_user=None, is_au
new_sources_count = 0
added_count = 0
skipped_count = 0
errors = []
# Словарь для кэширования Source в рамках текущего импорта
# Ключ: (имя источника, id Source), Значение: объект Source
@@ -391,13 +463,21 @@ def fill_data_from_df(df: pd.DataFrame, sat: Satellite, current_user=None, is_au
added_count += 1
except Exception as e:
error_msg = f"Строка {idx + 2}: {str(e)}"
print(f"Ошибка при обработке строки {idx}: {e}")
errors.append(error_msg)
continue
print(f"Импорт завершен: создано {new_sources_count} новых источников, "
f"добавлено {added_count} точек, пропущено {skipped_count} дубликатов")
f"добавлено {added_count} точек, пропущено {skipped_count} дубликатов, "
f"ошибок: {len(errors)}")
return new_sources_count
return {
'new_sources': new_sources_count,
'added': added_count,
'skipped': skipped_count,
'errors': errors
}
def _create_objitem_from_row(row, sat, source, user_to_use, consts, is_automatic=False):
@@ -574,6 +654,11 @@ def _create_objitem_from_row(row, sat, source, user_to_use, consts, is_automatic
def add_satellite_list():
"""
Добавляет список спутников в базу данных (если их еще нет).
Примечание: Эта функция устарела. Используйте админ-панель для добавления спутников.
"""
sats = [
"AZERSPACE 2",
"Amos 4",
@@ -673,33 +758,44 @@ def get_points_from_csv(file_content, current_user=None, is_automatic=False):
is_automatic: если True, точки не добавляются к Source (optional, default=False)
Returns:
int: количество созданных Source (или 0 если is_automatic=True)
dict: словарь с результатами импорта {
'new_sources': количество созданных Source,
'added': количество добавленных точек,
'skipped': количество пропущенных дубликатов,
'errors': список ошибок
}
"""
# Читаем CSV без предопределенных имен колонок
df = pd.read_csv(
io.StringIO(file_content),
sep=";",
names=[
"id",
"obj",
"lat",
"lon",
"h",
"time",
"sat",
"norad_id",
"freq",
"f_range",
"et",
"qaul",
"mir_1",
"mir_2",
"mir_3",
"mir_4",
"mir_5",
"mir_6",
"mir_7",
],
header=None
)
# Присваиваем имена первым 12 колонкам
base_columns = [
"id",
"obj",
"lat",
"lon",
"h",
"time",
"sat",
"norad_id",
"freq",
"f_range",
"et",
"qual",
]
# Все колонки после "qual" (индекс 11) - это зеркала
num_columns = len(df.columns)
mirror_columns = [f"mir_{i+1}" for i in range(num_columns - len(base_columns))]
# Объединяем имена колонок
df.columns = base_columns + mirror_columns
# Преобразуем типы данных
df[["lat", "lon", "freq", "f_range"]] = (
df[["lat", "lon", "freq", "f_range"]]
.replace(",", ".", regex=True)
@@ -711,6 +807,7 @@ def get_points_from_csv(file_content, current_user=None, is_automatic=False):
new_sources_count = 0
added_count = 0
skipped_count = 0
errors = []
# Словарь для кэширования Source в рамках текущего импорта
# Ключ: (имя источника, имя спутника, id Source), Значение: объект Source
@@ -733,14 +830,15 @@ def get_points_from_csv(file_content, current_user=None, is_automatic=False):
skipped_count += 1
continue
# Получаем или создаем объект спутника
# sat_obj, _ = Satellite.objects.get_or_create(
# name=sat_name, defaults={"norad": row["norad_id"]}
# )
# Получаем объект спутника по NORAD ID
try:
sat_obj = get_satellite_by_norad(row["norad_id"])
except (SatelliteNotFoundError, ValueError) as e:
error_msg = f"Строка {idx + 2}: {str(e)}"
print(error_msg)
errors.append(error_msg)
continue
sat_obj, _ = Satellite.objects.get_or_create(
norad=row["norad_id"], defaults={"name": sat_name}
)
source = None
# Если is_automatic=False, работаем с Source
@@ -781,17 +879,25 @@ def get_points_from_csv(file_content, current_user=None, is_automatic=False):
sources_cache[(source_name, sat_name, source.id)] = source
# Создаем ObjItem (с Source или без, в зависимости от is_automatic)
_create_objitem_from_csv_row(row, source, user_to_use, is_automatic)
_create_objitem_from_csv_row(row, source, user_to_use, is_automatic, mirror_columns)
added_count += 1
except Exception as e:
error_msg = f"Строка {idx + 2}: {str(e)}"
print(f"Ошибка при обработке строки {idx}: {e}")
errors.append(error_msg)
continue
print(f"Импорт завершен: создано {new_sources_count} новых источников, "
f"добавлено {added_count} точек, пропущено {skipped_count} дубликатов")
f"добавлено {added_count} точек, пропущено {skipped_count} дубликатов, "
f"ошибок: {len(errors)}")
return new_sources_count
return {
'new_sources': new_sources_count,
'added': added_count,
'skipped': skipped_count,
'errors': errors
}
def _is_duplicate_by_coords_and_time(coord_tuple, timestamp, tolerance_km=0.001):
@@ -896,7 +1002,7 @@ def _find_tech_analyze_data(name: str, satellite: Satellite):
return None
def _create_objitem_from_csv_row(row, source, user_to_use, is_automatic=False):
def _create_objitem_from_csv_row(row, source, user_to_use, is_automatic=False, mirror_columns=None):
"""
Вспомогательная функция для создания ObjItem из строки CSV DataFrame.
@@ -908,6 +1014,7 @@ def _create_objitem_from_csv_row(row, source, user_to_use, is_automatic=False):
source: объект Source для связи (может быть None если is_automatic=True)
user_to_use: пользователь для created_by
is_automatic: если True, точка не связывается с Source
mirror_columns: список имен колонок с зеркалами (optional)
"""
# Определяем поляризацию
match row["obj"].split(" ")[-1]:
@@ -923,21 +1030,36 @@ def _create_objitem_from_csv_row(row, source, user_to_use, is_automatic=False):
pol = "-"
pol_obj, _ = Polarization.objects.get_or_create(name=pol)
sat_obj, _ = Satellite.objects.get_or_create(
name=row["sat"], defaults={"norad": row["norad_id"]}
)
# Получаем объект спутника по NORAD ID
try:
sat_obj = get_satellite_by_norad(row["norad_id"])
except (SatelliteNotFoundError, ValueError) as e:
raise Exception(f"Не удалось получить спутник: {str(e)}")
# Ищем данные в TechAnalyze
tech_data = _find_tech_analyze_data(row["obj"], sat_obj)
# Обработка зеркал - теперь это спутники
mirror_names = []
if not pd.isna(row["mir_1"]) and row["mir_1"].strip() != "-":
mirror_names.append(row["mir_1"])
if not pd.isna(row["mir_2"]) and row["mir_2"].strip() != "-":
mirror_names.append(row["mir_2"])
if not pd.isna(row["mir_3"]) and row["mir_3"].strip() != "-":
mirror_names.append(row["mir_3"])
# Если переданы имена колонок зеркал, используем их
if mirror_columns:
for mir_col in mirror_columns:
if mir_col in row.index:
mir_value = row[mir_col]
if not pd.isna(mir_value) and str(mir_value).strip() != "-" and str(mir_value).strip() != "":
mirror_names.append(str(mir_value).strip())
else:
# Fallback на старый способ (для обратной совместимости)
for i in range(1, 100): # Проверяем до 100 колонок зеркал
mir_col = f"mir_{i}"
if mir_col in row.index:
mir_value = row[mir_col]
if not pd.isna(mir_value) and str(mir_value).strip() != "-" and str(mir_value).strip() != "":
mirror_names.append(str(mir_value).strip())
else:
break
# Находим спутники-зеркала
mirror_satellites = find_mirror_satellites(mirror_names)

View File

@@ -88,14 +88,25 @@ class LoadExcelDataView(LoginRequiredMixin, FormMessageMixin, FormView):
df = df.head(number)
result = fill_data_from_df(df, selected_sat, self.request.user.customuser, is_automatic)
# Формируем сообщение об успехе
if is_automatic:
messages.success(
self.request, f"Данные успешно загружены как автоматические! Добавлено точек: {len(df)}"
)
success_msg = f"Данные успешно загружены как автоматические! Добавлено точек: {result['added']}"
else:
messages.success(
self.request, f"Данные успешно загружены! Создано источников: {result}"
success_msg = f"Данные успешно загружены! Создано источников: {result['new_sources']}, добавлено точек: {result['added']}"
if result['skipped'] > 0:
success_msg += f", пропущено дубликатов: {result['skipped']}"
messages.success(self.request, success_msg)
# Показываем ошибки, если они есть
if result['errors']:
error_count = len(result['errors'])
messages.warning(
self.request,
f"Обнаружено ошибок: {error_count}. Первые ошибки: " + "; ".join(result['errors'][:5])
)
except Exception as e:
messages.error(self.request, f"Ошибка при обработке файла: {str(e)}")
@@ -124,10 +135,25 @@ class LoadCsvDataView(LoginRequiredMixin, FormMessageMixin, FormView):
result = get_points_from_csv(content, self.request.user.customuser, is_automatic)
# Формируем сообщение об успехе
if is_automatic:
messages.success(self.request, "Данные успешно загружены как автоматические!")
success_msg = f"Данные успешно загружены как автоматические! Добавлено точек: {result['added']}"
else:
messages.success(self.request, f"Данные успешно загружены! Создано источников: {result}")
success_msg = f"Данные успешно загружены! Создано источников: {result['new_sources']}, добавлено точек: {result['added']}"
if result['skipped'] > 0:
success_msg += f", пропущено дубликатов: {result['skipped']}"
messages.success(self.request, success_msg)
# Показываем ошибки, если они есть
if result['errors']:
error_count = len(result['errors'])
messages.warning(
self.request,
f"Обнаружено ошибок: {error_count}. Первые ошибки: " + "; ".join(result['errors'][:5])
)
except Exception as e:
messages.error(self.request, f"Ошибка при обработке файла: {str(e)}")
return redirect("mainapp:load_csv_data")

View File

@@ -113,9 +113,17 @@ class LoadExcelDataView(LoginRequiredMixin, FormMessageMixin, FormView):
df = df.head(number)
result = fill_data_from_df(df, selected_sat, self.request.user.customuser)
messages.success(
self.request, f"Данные успешно загружены! Обработано строк: {result}"
)
# Обработка нового формата результата
success_msg = f"Данные успешно загружены! Создано источников: {result['new_sources']}, добавлено точек: {result['added']}"
if result['skipped'] > 0:
success_msg += f", пропущено дубликатов: {result['skipped']}"
messages.success(self.request, success_msg)
if result['errors']:
messages.warning(
self.request,
f"Обнаружено ошибок: {len(result['errors'])}. Первые ошибки: " + "; ".join(result['errors'][:5])
)
except Exception as e:
messages.error(self.request, f"Ошибка при обработке файла: {str(e)}")
@@ -180,7 +188,19 @@ class LoadCsvDataView(LoginRequiredMixin, FormMessageMixin, FormView):
if isinstance(content, bytes):
content = content.decode("utf-8")
get_points_from_csv(content, self.request.user.customuser)
result = get_points_from_csv(content, self.request.user.customuser)
# Обработка нового формата результата
success_msg = f"Данные успешно загружены! Создано источников: {result['new_sources']}, добавлено точек: {result['added']}"
if result['skipped'] > 0:
success_msg += f", пропущено дубликатов: {result['skipped']}"
messages.success(self.request, success_msg)
if result['errors']:
messages.warning(
self.request,
f"Обнаружено ошибок: {len(result['errors'])}. Первые ошибки: " + "; ".join(result['errors'][:5])
)
except Exception as e:
messages.error(self.request, f"Ошибка при обработке файла: {str(e)}")
return redirect("mainapp:load_csv_data")

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

View File

@@ -1,8 +1,9 @@
services:
web:
build:
context: ./dbapp
dockerfile: Dockerfile
# build:
# context: ./dbapp
# dockerfile: Dockerfile
image: https://registry.geraltserv.ru/geolocation:latest
env_file:
- .env.prod
depends_on:
@@ -14,9 +15,10 @@ services:
- 8000
worker:
build:
context: ./dbapp
dockerfile: Dockerfile
# build:
# context: ./dbapp
# dockerfile: Dockerfile
image: https://registry.geraltserv.ru/geolocation:latest
env_file:
- .env.prod
#entrypoint: []