Добавил формы создания и пофиксил баг с пользователями
This commit is contained in:
@@ -271,34 +271,40 @@ def _find_or_create_source_by_name_and_distance(
|
||||
return source
|
||||
|
||||
|
||||
def fill_data_from_df(df: pd.DataFrame, sat: Satellite, current_user=None):
|
||||
def fill_data_from_df(df: pd.DataFrame, sat: Satellite, current_user=None, is_automatic=False):
|
||||
"""
|
||||
Импортирует данные из DataFrame с группировкой по имени источника и расстоянию.
|
||||
|
||||
Алгоритм:
|
||||
1. Для каждой строки DataFrame:
|
||||
a. Извлечь имя источника (из колонки "Объект наблюдения")
|
||||
b. Найти подходящий Source:
|
||||
- Ищет все Source с таким же именем и спутником
|
||||
- Проверяет расстояние до каждого Source
|
||||
- Если найден Source в радиусе ≤56 км - использует его
|
||||
- Иначе создает новый Source
|
||||
c. Обновить coords_average инкрементально
|
||||
d. Создать ObjItem и связать с Source
|
||||
b. Если is_automatic=False:
|
||||
- Найти подходящий Source:
|
||||
* Ищет все Source с таким же именем и спутником
|
||||
* Проверяет расстояние до каждого Source
|
||||
* Если найден Source в радиусе ≤56 км - использует его
|
||||
* Иначе создает новый Source
|
||||
- Обновить coords_average инкрементально
|
||||
- Создать ObjItem и связать с Source
|
||||
c. Если is_automatic=True:
|
||||
- Создать ObjItem без связи с Source (source=None)
|
||||
- Точка просто хранится в базе
|
||||
|
||||
Важные правила:
|
||||
- Источники разных спутников НЕ объединяются
|
||||
- Может быть несколько Source с одинаковым именем, но разделенных географически
|
||||
- Точка добавляется к Source только если расстояние ≤56 км
|
||||
- Координаты усредняются инкрементально для каждого источника
|
||||
- Если точка уже существует (по координатам и времени ГЛ), она не добавляется повторно
|
||||
|
||||
Args:
|
||||
df: DataFrame с данными
|
||||
sat: объект Satellite
|
||||
current_user: текущий пользователь (optional)
|
||||
is_automatic: если True, точки не добавляются к Source (optional, default=False)
|
||||
|
||||
Returns:
|
||||
int: количество созданных Source
|
||||
int: количество созданных Source (или 0 если is_automatic=True)
|
||||
"""
|
||||
try:
|
||||
df.rename(columns={"Модуляция ": "Модуляция"}, inplace=True)
|
||||
@@ -311,6 +317,7 @@ def fill_data_from_df(df: pd.DataFrame, sat: Satellite, current_user=None):
|
||||
user_to_use = current_user if current_user else CustomUser.objects.get(id=1)
|
||||
new_sources_count = 0
|
||||
added_count = 0
|
||||
skipped_count = 0
|
||||
|
||||
# Словарь для кэширования Source в рамках текущего импорта
|
||||
# Ключ: (имя источника, id Source), Значение: объект Source
|
||||
@@ -325,42 +332,59 @@ def fill_data_from_df(df: pd.DataFrame, sat: Satellite, current_user=None):
|
||||
# Извлекаем имя источника
|
||||
source_name = row["Объект наблюдения"]
|
||||
|
||||
found_in_cache = False
|
||||
for cache_key, cached_source in sources_cache.items():
|
||||
cached_name, cached_id = cache_key
|
||||
|
||||
# Проверяем имя
|
||||
if cached_name != source_name:
|
||||
continue
|
||||
|
||||
# Проверяем расстояние
|
||||
if cached_source.coords_average:
|
||||
source_coord = (cached_source.coords_average.x, cached_source.coords_average.y)
|
||||
_, distance = calculate_mean_coords(source_coord, coord_tuple)
|
||||
# Извлекаем время для проверки дубликатов
|
||||
date = row["Дата"].date()
|
||||
time_ = row["Время"]
|
||||
if isinstance(time_, str):
|
||||
time_ = time_.strip()
|
||||
time_ = time(0, 0, 0)
|
||||
timestamp = datetime.combine(date, time_)
|
||||
|
||||
# Проверяем дубликаты по координатам и времени
|
||||
if _is_duplicate_by_coords_and_time(coord_tuple, timestamp):
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
source = None
|
||||
|
||||
# Если is_automatic=False, работаем с Source
|
||||
if not is_automatic:
|
||||
found_in_cache = False
|
||||
for cache_key, cached_source in sources_cache.items():
|
||||
cached_name, cached_id = cache_key
|
||||
|
||||
if distance <= RANGE_DISTANCE:
|
||||
# Нашли подходящий Source в кэше
|
||||
cached_source.update_coords_average(coord_tuple)
|
||||
cached_source.save()
|
||||
source = cached_source
|
||||
found_in_cache = True
|
||||
break
|
||||
|
||||
if not found_in_cache:
|
||||
# Ищем в БД или создаем новый Source
|
||||
source = _find_or_create_source_by_name_and_distance(
|
||||
source_name, sat, coord_tuple, user_to_use
|
||||
)
|
||||
# Проверяем имя
|
||||
if cached_name != source_name:
|
||||
continue
|
||||
|
||||
# Проверяем расстояние
|
||||
if cached_source.coords_average:
|
||||
source_coord = (cached_source.coords_average.x, cached_source.coords_average.y)
|
||||
_, distance = calculate_mean_coords(source_coord, coord_tuple)
|
||||
|
||||
if distance <= RANGE_DISTANCE:
|
||||
# Нашли подходящий Source в кэше
|
||||
cached_source.update_coords_average(coord_tuple)
|
||||
cached_source.save()
|
||||
source = cached_source
|
||||
found_in_cache = True
|
||||
break
|
||||
|
||||
# Проверяем, был ли создан новый Source
|
||||
if source.created_at.timestamp() > (datetime.now().timestamp() - 1):
|
||||
new_sources_count += 1
|
||||
|
||||
# Добавляем в кэш
|
||||
sources_cache[(source_name, source.id)] = source
|
||||
if not found_in_cache:
|
||||
# Ищем в БД или создаем новый Source
|
||||
source = _find_or_create_source_by_name_and_distance(
|
||||
source_name, sat, coord_tuple, user_to_use
|
||||
)
|
||||
|
||||
# Проверяем, был ли создан новый Source
|
||||
if source.created_at.timestamp() > (datetime.now().timestamp() - 1):
|
||||
new_sources_count += 1
|
||||
|
||||
# Добавляем в кэш
|
||||
sources_cache[(source_name, source.id)] = source
|
||||
|
||||
# Создаем ObjItem и связываем с Source
|
||||
_create_objitem_from_row(row, sat, source, user_to_use, consts)
|
||||
# Создаем ObjItem (с Source или без, в зависимости от is_automatic)
|
||||
_create_objitem_from_row(row, sat, source, user_to_use, consts, is_automatic)
|
||||
added_count += 1
|
||||
|
||||
except Exception as e:
|
||||
@@ -368,21 +392,22 @@ def fill_data_from_df(df: pd.DataFrame, sat: Satellite, current_user=None):
|
||||
continue
|
||||
|
||||
print(f"Импорт завершен: создано {new_sources_count} новых источников, "
|
||||
f"добавлено {added_count} точек")
|
||||
f"добавлено {added_count} точек, пропущено {skipped_count} дубликатов")
|
||||
|
||||
return new_sources_count
|
||||
|
||||
|
||||
def _create_objitem_from_row(row, sat, source, user_to_use, consts):
|
||||
def _create_objitem_from_row(row, sat, source, user_to_use, consts, is_automatic=False):
|
||||
"""
|
||||
Вспомогательная функция для создания ObjItem из строки DataFrame.
|
||||
|
||||
Args:
|
||||
row: строка DataFrame
|
||||
sat: объект Satellite
|
||||
source: объект Source для связи
|
||||
source: объект Source для связи (может быть None если is_automatic=True)
|
||||
user_to_use: пользователь для created_by
|
||||
consts: константы из get_all_constants()
|
||||
is_automatic: если True, точка не связывается с Source
|
||||
"""
|
||||
# Извлекаем координату
|
||||
geo_point = Point(coords_transform(row["Координаты"]), srid=4326)
|
||||
@@ -475,12 +500,13 @@ def _create_objitem_from_row(row, sat, source, user_to_use, consts):
|
||||
# Находим подходящий источник LyngSat (точность 0.1 МГц)
|
||||
lyngsat_source = find_matching_lyngsat(sat, freq, polarization_obj, tolerance_mhz=0.1)
|
||||
|
||||
# Создаем новый ObjItem и связываем с Source, Transponder и LyngSat
|
||||
# Создаем новый ObjItem и связываем с Source (если не автоматическая), Transponder и LyngSat
|
||||
obj_item = ObjItem.objects.create(
|
||||
name=source_name,
|
||||
source=source,
|
||||
source=source if not is_automatic else None,
|
||||
transponder=transponder,
|
||||
lyngsat_source=lyngsat_source,
|
||||
is_automatic=is_automatic,
|
||||
created_by=user_to_use
|
||||
)
|
||||
|
||||
@@ -500,9 +526,10 @@ def _create_objitem_from_row(row, sat, source, user_to_use, consts):
|
||||
geo.objitem = obj_item
|
||||
geo.save()
|
||||
|
||||
# Обновляем дату подтверждения источника
|
||||
source.update_confirm_at()
|
||||
source.save()
|
||||
# Обновляем дату подтверждения источника (только если не автоматическая)
|
||||
if source and not is_automatic:
|
||||
source.update_confirm_at()
|
||||
source.save()
|
||||
|
||||
|
||||
def add_satellite_list():
|
||||
@@ -572,34 +599,40 @@ def get_point_from_json(filepath: str):
|
||||
)
|
||||
|
||||
|
||||
def get_points_from_csv(file_content, current_user=None):
|
||||
def get_points_from_csv(file_content, current_user=None, is_automatic=False):
|
||||
"""
|
||||
Импортирует данные из CSV с группировкой по имени источника и расстоянию.
|
||||
|
||||
Алгоритм:
|
||||
1. Для каждой строки CSV:
|
||||
a. Извлечь имя источника (из колонки "obj") и спутник
|
||||
b. Проверить дубликаты (координаты + частота)
|
||||
c. Найти подходящий Source:
|
||||
- Ищет все Source с таким же именем и спутником
|
||||
- Проверяет расстояние до каждого Source
|
||||
- Если найден Source в радиусе ≤56 км - использует его
|
||||
- Иначе создает новый Source
|
||||
d. Обновить coords_average инкрементально
|
||||
e. Создать ObjItem и связать с Source
|
||||
b. Проверить дубликаты (координаты + время ГЛ)
|
||||
c. Если is_automatic=False:
|
||||
- Найти подходящий Source:
|
||||
* Ищет все Source с таким же именем и спутником
|
||||
* Проверяет расстояние до каждого Source
|
||||
* Если найден Source в радиусе ≤56 км - использует его
|
||||
* Иначе создает новый Source
|
||||
- Обновить coords_average инкрементально
|
||||
- Создать ObjItem и связать с Source
|
||||
d. Если is_automatic=True:
|
||||
- Создать ObjItem без связи с Source (source=None)
|
||||
- Точка просто хранится в базе
|
||||
|
||||
Важные правила:
|
||||
- Источники разных спутников НЕ объединяются
|
||||
- Может быть несколько Source с одинаковым именем, но разделенных географически
|
||||
- Точка добавляется к Source только если расстояние ≤56 км
|
||||
- Координаты усредняются инкрементально для каждого источника
|
||||
- Если точка уже существует (по координатам и времени ГЛ), она не добавляется повторно
|
||||
|
||||
Args:
|
||||
file_content: содержимое CSV файла
|
||||
current_user: текущий пользователь (optional)
|
||||
is_automatic: если True, точки не добавляются к Source (optional, default=False)
|
||||
|
||||
Returns:
|
||||
int: количество созданных Source
|
||||
int: количество созданных Source (или 0 если is_automatic=True)
|
||||
"""
|
||||
df = pd.read_csv(
|
||||
io.StringIO(file_content),
|
||||
@@ -647,8 +680,11 @@ def get_points_from_csv(file_content, current_user=None):
|
||||
source_name = row["obj"]
|
||||
sat_name = row["sat"]
|
||||
|
||||
# Проверяем дубликаты
|
||||
if _is_duplicate_objitem(coord_tuple, row["freq"], row["f_range"]):
|
||||
# Извлекаем время для проверки дубликатов
|
||||
timestamp = row["time"]
|
||||
|
||||
# Проверяем дубликаты по координатам и времени
|
||||
if _is_duplicate_by_coords_and_time(coord_tuple, timestamp):
|
||||
skipped_count += 1
|
||||
continue
|
||||
|
||||
@@ -657,43 +693,47 @@ def get_points_from_csv(file_content, current_user=None):
|
||||
name=sat_name, defaults={"norad": row["norad_id"]}
|
||||
)
|
||||
|
||||
# Проверяем кэш: ищем подходящий Source среди закэшированных
|
||||
found_in_cache = False
|
||||
for cache_key, cached_source in sources_cache.items():
|
||||
cached_name, cached_sat, cached_id = cache_key
|
||||
|
||||
# Проверяем имя и спутник
|
||||
if cached_name != source_name or cached_sat != sat_name:
|
||||
continue
|
||||
|
||||
# Проверяем расстояние
|
||||
if cached_source.coords_average:
|
||||
source_coord = (cached_source.coords_average.x, cached_source.coords_average.y)
|
||||
_, distance = calculate_mean_coords(source_coord, coord_tuple)
|
||||
source = None
|
||||
|
||||
# Если is_automatic=False, работаем с Source
|
||||
if not is_automatic:
|
||||
# Проверяем кэш: ищем подходящий Source среди закэшированных
|
||||
found_in_cache = False
|
||||
for cache_key, cached_source in sources_cache.items():
|
||||
cached_name, cached_sat, cached_id = cache_key
|
||||
|
||||
if distance <= RANGE_DISTANCE:
|
||||
# Нашли подходящий Source в кэше
|
||||
cached_source.update_coords_average(coord_tuple)
|
||||
cached_source.save()
|
||||
source = cached_source
|
||||
found_in_cache = True
|
||||
break
|
||||
|
||||
if not found_in_cache:
|
||||
# Ищем в БД или создаем новый Source
|
||||
source = _find_or_create_source_by_name_and_distance(
|
||||
source_name, sat_obj, coord_tuple, user_to_use
|
||||
)
|
||||
# Проверяем имя и спутник
|
||||
if cached_name != source_name or cached_sat != sat_name:
|
||||
continue
|
||||
|
||||
# Проверяем расстояние
|
||||
if cached_source.coords_average:
|
||||
source_coord = (cached_source.coords_average.x, cached_source.coords_average.y)
|
||||
_, distance = calculate_mean_coords(source_coord, coord_tuple)
|
||||
|
||||
if distance <= RANGE_DISTANCE:
|
||||
# Нашли подходящий Source в кэше
|
||||
cached_source.update_coords_average(coord_tuple)
|
||||
cached_source.save()
|
||||
source = cached_source
|
||||
found_in_cache = True
|
||||
break
|
||||
|
||||
# Проверяем, был ли создан новый Source
|
||||
if source.created_at.timestamp() > (datetime.now().timestamp() - 1):
|
||||
new_sources_count += 1
|
||||
|
||||
# Добавляем в кэш
|
||||
sources_cache[(source_name, sat_name, source.id)] = source
|
||||
if not found_in_cache:
|
||||
# Ищем в БД или создаем новый Source
|
||||
source = _find_or_create_source_by_name_and_distance(
|
||||
source_name, sat_obj, coord_tuple, user_to_use
|
||||
)
|
||||
|
||||
# Проверяем, был ли создан новый Source
|
||||
if source.created_at.timestamp() > (datetime.now().timestamp() - 1):
|
||||
new_sources_count += 1
|
||||
|
||||
# Добавляем в кэш
|
||||
sources_cache[(source_name, sat_name, source.id)] = source
|
||||
|
||||
# Создаем ObjItem и связываем с Source
|
||||
_create_objitem_from_csv_row(row, source, user_to_use)
|
||||
# Создаем ObjItem (с Source или без, в зависимости от is_automatic)
|
||||
_create_objitem_from_csv_row(row, source, user_to_use, is_automatic)
|
||||
added_count += 1
|
||||
|
||||
except Exception as e:
|
||||
@@ -706,6 +746,39 @@ def get_points_from_csv(file_content, current_user=None):
|
||||
return new_sources_count
|
||||
|
||||
|
||||
def _is_duplicate_by_coords_and_time(coord_tuple, timestamp, tolerance_km=0.1):
|
||||
"""
|
||||
Проверяет, существует ли уже ObjItem с такими же координатами и временем ГЛ.
|
||||
|
||||
Args:
|
||||
coord_tuple: кортеж (lon, lat) координат
|
||||
timestamp: время ГЛ (datetime)
|
||||
tolerance_km: допуск для сравнения координат в километрах (default=0.1)
|
||||
|
||||
Returns:
|
||||
bool: True если дубликат найден, False иначе
|
||||
"""
|
||||
# Ищем Geo с таким же timestamp и близкими координатами
|
||||
existing_geo = Geo.objects.filter(
|
||||
timestamp=timestamp,
|
||||
coords__isnull=False
|
||||
)
|
||||
|
||||
for geo in existing_geo:
|
||||
if not geo.coords:
|
||||
continue
|
||||
|
||||
# Проверяем расстояние между координатами
|
||||
geo_coord = (geo.coords.x, geo.coords.y)
|
||||
_, distance = calculate_mean_coords(coord_tuple, geo_coord)
|
||||
|
||||
if distance <= tolerance_km:
|
||||
# Найден дубликат
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _is_duplicate_objitem(coord_tuple, frequency, freq_range, tolerance=0.1):
|
||||
"""
|
||||
Проверяет, существует ли уже ObjItem с такими же координатами и частотой.
|
||||
@@ -744,14 +817,15 @@ def _is_duplicate_objitem(coord_tuple, frequency, freq_range, tolerance=0.1):
|
||||
return False
|
||||
|
||||
|
||||
def _create_objitem_from_csv_row(row, source, user_to_use):
|
||||
def _create_objitem_from_csv_row(row, source, user_to_use, is_automatic=False):
|
||||
"""
|
||||
Вспомогательная функция для создания ObjItem из строки CSV DataFrame.
|
||||
|
||||
Args:
|
||||
row: строка DataFrame
|
||||
source: объект Source для связи
|
||||
source: объект Source для связи (может быть None если is_automatic=True)
|
||||
user_to_use: пользователь для created_by
|
||||
is_automatic: если True, точка не связывается с Source
|
||||
"""
|
||||
# Определяем поляризацию
|
||||
match row["obj"].split(" ")[-1]:
|
||||
@@ -817,12 +891,13 @@ def _create_objitem_from_csv_row(row, source, user_to_use):
|
||||
# Находим подходящий источник LyngSat (точность 0.1 МГц)
|
||||
lyngsat_source = find_matching_lyngsat(sat_obj, row["freq"], pol_obj, tolerance_mhz=0.1)
|
||||
|
||||
# Создаем новый ObjItem и связываем с Source, Transponder и LyngSat
|
||||
# Создаем новый ObjItem и связываем с Source (если не автоматическая), Transponder и LyngSat
|
||||
obj_item = ObjItem.objects.create(
|
||||
name=row["obj"],
|
||||
source=source,
|
||||
source=source if not is_automatic else None,
|
||||
transponder=transponder,
|
||||
lyngsat_source=lyngsat_source,
|
||||
is_automatic=is_automatic,
|
||||
created_by=user_to_use
|
||||
)
|
||||
|
||||
@@ -839,9 +914,10 @@ def _create_objitem_from_csv_row(row, source, user_to_use):
|
||||
geo_obj.objitem = obj_item
|
||||
geo_obj.save()
|
||||
|
||||
# Обновляем дату подтверждения источника
|
||||
source.update_confirm_at()
|
||||
source.save()
|
||||
# Обновляем дату подтверждения источника (только если не автоматическая)
|
||||
if source and not is_automatic:
|
||||
source.update_confirm_at()
|
||||
source.save()
|
||||
|
||||
|
||||
def get_vch_load_from_html(file, sat: Satellite) -> None:
|
||||
|
||||
Reference in New Issue
Block a user