Снова улучшения и добавления

This commit is contained in:
2025-11-14 11:41:19 +03:00
parent 6a26991dc0
commit d61236dee2
12 changed files with 345 additions and 21 deletions

1
.gitignore vendored
View File

@@ -33,3 +33,4 @@ tiles
# Docker
# docker-*
maplibre-gl-js-5.10.0.zip
cert.pem

View File

@@ -182,6 +182,98 @@
{% endif %}
</div>
<!-- Транспондер -->
<div class="form-section">
<div class="form-section-header">
<h4>Транспондер</h4>
</div>
{% if object.transponder %}
<div class="row">
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Название:</label>
<div class="readonly-field">{{ object.transponder.name|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Спутник:</label>
<div class="readonly-field">{{ object.transponder.sat_id.name|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Downlink (МГц):</label>
<div class="readonly-field">{{ object.transponder.downlink|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Uplink (МГц):</label>
<div class="readonly-field">{{ object.transponder.uplink|default:"-" }}</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Полоса (МГц):</label>
<div class="readonly-field">{{ object.transponder.frequency_range|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Перенос (МГц):</label>
<div class="readonly-field">{{ object.transponder.transfer|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Поляризация:</label>
<div class="readonly-field">{{ object.transponder.polarization.name|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">ОСШ (дБ):</label>
<div class="readonly-field">{{ object.transponder.snr|default:"-" }}</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Зона покрытия:</label>
<div class="readonly-field">{{ object.transponder.zone_name|default:"-" }}</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Дата создания:</label>
<div class="readonly-field">
{% if object.transponder.created_at %}{{ object.transponder.created_at|date:"d.m.Y H:i" }}{% else %}-{% endif %}
</div>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Создан пользователем:</label>
<div class="readonly-field">
{% if object.transponder.created_by %}{{ object.transponder.created_by }}{% else %}-{% endif %}
</div>
</div>
</div>
</div>
{% else %}
<div class="mb-3">
<p>Нет данных о транспондере</p>
</div>
{% endif %}
</div>
<!-- Блок с картой -->
<div class="form-section">
<div class="form-section-header">

View File

@@ -248,6 +248,98 @@
</div>
</div>
<!-- Транспондер -->
<div class="form-section">
<div class="form-section-header">
<h4>Транспондер</h4>
</div>
{% if object.transponder %}
<div class="row">
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Название:</label>
<div class="readonly-field">{{ object.transponder.name|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Спутник:</label>
<div class="readonly-field">{{ object.transponder.sat_id.name|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Downlink (МГц):</label>
<div class="readonly-field">{{ object.transponder.downlink|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Uplink (МГц):</label>
<div class="readonly-field">{{ object.transponder.uplink|default:"-" }}</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Полоса (МГц):</label>
<div class="readonly-field">{{ object.transponder.frequency_range|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Перенос (МГц):</label>
<div class="readonly-field">{{ object.transponder.transfer|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Поляризация:</label>
<div class="readonly-field">{{ object.transponder.polarization.name|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">ОСШ (дБ):</label>
<div class="readonly-field">{{ object.transponder.snr|default:"-" }}</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Зона покрытия:</label>
<div class="readonly-field">{{ object.transponder.zone_name|default:"-" }}</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Дата создания:</label>
<div class="readonly-field">
{% if object.transponder.created_at %}{{ object.transponder.created_at|date:"d.m.Y H:i" }}{% else %}-{% endif %}
</div>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Создан пользователем:</label>
<div class="readonly-field">
{% if object.transponder.created_by %}{{ object.transponder.created_by }}{% else %}-{% endif %}
</div>
</div>
</div>
</div>
{% else %}
<div class="mb-3">
<p class="text-muted">Нет данных о транспондере</p>
</div>
{% endif %}
</div>
<!-- Блок с картой -->
<div class="form-section">
<div class="form-section-header">

View File

@@ -1265,11 +1265,39 @@
<td class="text-muted">Перенос:</td>
<td>${data.transfer || '-'} ${data.transfer ? 'МГц' : ''}</td>
</tr>
<tr>
<td class="text-muted">ОСШ:</td>
<td><strong>${data.snr || '-'} ${data.snr ? 'дБ' : ''}</strong></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-12">
<div class="card">
<div class="card-header bg-light">
<strong><i class="bi bi-clock-history"></i> Метаданные</strong>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<p class="mb-2">
<span class="text-muted">Дата создания:</span><br>
<strong>${data.created_at || '-'}</strong>
</p>
</div>
<div class="col-md-6">
<p class="mb-2">
<span class="text-muted">Создан пользователем:</span><br>
<strong>${data.created_by || '-'}</strong>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;

View File

@@ -58,7 +58,7 @@
<script>
// Инициализация карты
let map = L.map('map').setView([55.75, 37.62], 5);
let map = L.map('map').setView([55.75, 37.62], 10);
L.control.scale({
imperial: false,
metric: true

View File

@@ -28,6 +28,47 @@ from .models import (
Standard,
)
def find_matching_transponder(satellite, frequency, polarization):
"""
Находит подходящий транспондер для заданных параметров.
Алгоритм:
1. Фильтрует транспондеры по спутнику и поляризации
2. Проверяет, входит ли частота в диапазон транспондера:
downlink - frequency_range/2 <= frequency <= downlink + frequency_range/2
3. Возвращает самый свежий транспондер (по created_at)
Args:
satellite: объект Satellite
frequency: частота в МГц
polarization: объект Polarization
Returns:
Transponders или None: найденный транспондер или None
"""
if not satellite or not polarization or frequency == -1.0:
return None
# Фильтруем транспондеры по спутнику и поляризации
transponders = Transponders.objects.filter(
sat_id=satellite,
polarization=polarization,
downlink__isnull=False,
frequency_range__isnull=False
).annotate(
# Вычисляем нижнюю и верхнюю границы диапазона
lower_bound=F('downlink') - F('frequency_range') / 2,
upper_bound=F('downlink') + F('frequency_range') / 2
).filter(
# Проверяем, входит ли частота в диапазон
lower_bound__lte=frequency,
upper_bound__gte=frequency
).order_by('-created_at') # Сортируем по дате создания (самые свежие первыми)
# Возвращаем самый свежий транспондер
return transponders.first()
# ============================================================================
# Константы
# ============================================================================
@@ -371,9 +412,15 @@ def _create_objitem_from_row(row, sat, source, user_to_use, consts):
# Пропускаем создание дубликата
return
# Создаем новый ObjItem и связываем с Source
# Находим подходящий транспондер
transponder = find_matching_transponder(sat, freq, polarization_obj)
# Создаем новый ObjItem и связываем с Source и Transponder
obj_item = ObjItem.objects.create(
name=source_name, source=source, created_by=user_to_use
name=source_name,
source=source,
transponder=transponder,
created_by=user_to_use
)
# Создаем Parameter
@@ -697,9 +744,15 @@ def _create_objitem_from_csv_row(row, source, user_to_use):
# Пропускаем создание дубликата
return
# Создаем новый ObjItem и связываем с Source
# Находим подходящий транспондер
transponder = find_matching_transponder(sat_obj, row["freq"], pol_obj)
# Создаем новый ObjItem и связываем с Source и Transponder
obj_item = ObjItem.objects.create(
name=row["obj"], source=source, created_by=user_to_use
name=row["obj"],
source=source,
transponder=transponder,
created_by=user_to_use
)
# Создаем Parameter

View File

@@ -310,9 +310,21 @@ class TransponderDataAPIView(LoginRequiredMixin, View):
try:
transponder = Transponders.objects.select_related(
'sat_id',
'polarization'
'polarization',
'created_by__user'
).get(id=transponder_id)
# Format created_at date
created_at_str = '-'
if transponder.created_at:
local_time = timezone.localtime(transponder.created_at)
created_at_str = local_time.strftime("%d.%m.%Y %H:%M")
# Get created_by username
created_by_str = '-'
if transponder.created_by:
created_by_str = str(transponder.created_by)
data = {
'id': transponder.id,
'name': transponder.name or '-',
@@ -323,6 +335,9 @@ class TransponderDataAPIView(LoginRequiredMixin, View):
'polarization': transponder.polarization.name if transponder.polarization else '-',
'zone_name': transponder.zone_name or '-',
'transfer': f"{transponder.transfer:.3f}" if transponder.transfer else None,
'snr': f"{transponder.snr:.1f}" if transponder.snr is not None else None,
'created_at': created_at_str,
'created_by': created_by_str,
}
return JsonResponse(data)

View File

@@ -53,7 +53,8 @@ class AddTranspondersView(LoginRequiredMixin, FormMessageMixin, FormView):
uploaded_file = self.request.FILES["file"]
try:
content = uploaded_file.read()
parse_transponders_from_xml(BytesIO(content))
# Передаем текущего пользователя в функцию парсинга
parse_transponders_from_xml(BytesIO(content), self.request.user.customuser)
except ValueError as e:
messages.error(self.request, f"Ошибка при чтении таблиц: {e}")
return redirect("mainapp:add_trans")

View File

@@ -521,6 +521,19 @@ class ObjItemFormView(
return context
def get_object(self, queryset=None):
"""Override to add select_related for transponder."""
obj = super().get_object(queryset)
if obj and hasattr(obj, 'transponder'):
# Prefetch transponder data
obj = ObjItem.objects.select_related(
'transponder',
'transponder__sat_id',
'transponder__polarization',
'transponder__created_by__user',
).get(pk=obj.pk)
return obj
def form_valid(self, form):
# Get parameter form
if self.object and hasattr(self.object, "parameter_obj") and self.object.parameter_obj:
@@ -658,6 +671,10 @@ class ObjItemDetailView(LoginRequiredMixin, View):
'parameter_obj__polarization',
'parameter_obj__modulation',
'parameter_obj__standard',
'transponder',
'transponder__sat_id',
'transponder__polarization',
'transponder__created_by__user',
).first()
if not obj:

View File

@@ -57,9 +57,9 @@ class Transponders(models.Model):
snr = models.FloatField(
blank=True,
null=True,
verbose_name="Полоса",
verbose_name="ОСШ, дБ",
# validators=[MinValueValidator(0), MaxValueValidator(1000)],
help_text="Полоса частот в МГц (0-1000)",
help_text="Отношение сигнал/шум в децибелах",
)
created_at = models.DateTimeField(
auto_now_add=True,

View File

@@ -22,7 +22,7 @@ def search_satellite_on_page(data: dict, satellite_name: str):
def get_footprint_data(position: str = 62) -> dict:
"""Возвращает словарь с данным по footprint для спутников на выбранной долготе"""
response = requests.get(f"https://www.satbeams.com/footprints?position={position}")
response = requests.get(f"https://www.satbeams.com/footprints?position={position}", verify=False)
response.raise_for_status()
match = re.search(r'var data = ({.*?});', response.text, re.DOTALL)
if match:
@@ -40,7 +40,7 @@ def get_footprint_data(position: str = 62) -> dict:
def get_all_page_data(url:str = 'https://www.satbeams.com/footprints') -> dict:
"""Возвращает словарь с данными по всем спутникам на странице"""
response = requests.get(url)
response = requests.get(url, verify="/etc/ssl/certs/ca-certificates.crt")
response.raise_for_status()
match = re.search(r'var data = ({.*?});', response.text, re.DOTALL)
if match:
@@ -78,7 +78,14 @@ def get_band_names(satellite_name: str) -> list[str]:
names = get_names_footprints_for_satellite(footprints, sat_id)
return names
def parse_transponders_from_json(filepath: str):
def parse_transponders_from_json(filepath: str, user=None):
"""
Парсит транспондеры из JSON файла.
Args:
filepath: путь к JSON файлу
user: пользователь для установки created_by и updated_by (optional)
"""
with open(filepath, encoding="utf-8") as jf:
data = json.load(jf)
for sat_name, trans_zone in data["satellites"].items():
@@ -87,14 +94,26 @@ def parse_transponders_from_json(filepath: str):
f_b, f_e = tran["freq"][0].split("-")
f = round((float(f_b) + float(f_e))/2, 3)
f_range = round(abs(float(f_e) - float(f_b)), 3)
tran_obj = Transponders.objects.create(
pol_obj = Polarization.objects.get(name=tran["pol"])
sat_obj = Satellite.objects.get(name__iexact=sat_name)
tran_obj, created = Transponders.objects.get_or_create(
name=tran["name"],
frequency=f,
frequency_range=f_range,
zone_name=zone,
polarization=Polarization.objects.get(name=tran["pol"]),
sat_id=Satellite.objects.get(name__iexact=sat_name)
polarization=pol_obj,
sat_id=sat_obj,
defaults={
"frequency": f,
"frequency_range": f_range,
"zone_name": zone,
}
)
# Устанавливаем пользователя, если передан
if user:
if created:
tran_obj.created_by = user
tran_obj.updated_by = user
tran_obj.save()
@@ -102,7 +121,13 @@ def parse_transponders_from_json(filepath: str):
from lxml import etree
def parse_transponders_from_xml(data_in: BytesIO, user=None):
"""
Парсит транспондеры из XML файла.
Args:
data_in: BytesIO объект с XML данными
user: пользователь для установки created_by и updated_by (optional)
"""
tree = etree.parse(data_in)
ns = {
'i': 'http://www.w3.org/2001/XMLSchema-instance',

View File

@@ -86,7 +86,7 @@ class TileProxyView(View):
url = f"{self.TILE_BASE_URL}/{footprint_name}/{z}/{x}/{y}.png"
try:
resp = requests.get(url, timeout=self.REQUEST_TIMEOUT)
resp = requests.get(url, timeout=self.REQUEST_TIMEOUT, verify=r'/home/vesemir/DataStorage/cert.pem')
if resp.status_code == 200:
response = HttpResponse(resp.content, content_type="image/png")
response["Access-Control-Allow-Origin"] = "*"