Поправил привязку вч загрузки, сделал модальное окно.

This commit is contained in:
2025-11-12 00:04:55 +03:00
parent 5e94086bf0
commit 902eb23bd8
9 changed files with 526 additions and 60 deletions

View File

@@ -70,17 +70,20 @@ class VchLinkForm(forms.Form):
# label='Выбор диапазона' # label='Выбор диапазона'
# ) # )
value1 = forms.FloatField( value1 = forms.FloatField(
label="Первое число", label="Разброс по частоте (не используется)",
required=False,
initial=0.0,
widget=forms.NumberInput(attrs={ widget=forms.NumberInput(attrs={
'class': 'form-control', 'class': 'form-control',
'placeholder': 'Введите первое число' 'placeholder': 'Не используется - погрешность определяется автоматически'
}) })
) )
value2 = forms.FloatField( value2 = forms.FloatField(
label="Второе число", label="Разброс по полосе (в %)",
widget=forms.NumberInput(attrs={ widget=forms.NumberInput(attrs={
'class': 'form-control', 'class': 'form-control',
'placeholder': 'Введите второе число' 'placeholder': 'Введите погрешность полосы в процентах',
'step': '0.1'
}) })
) )

View File

@@ -75,7 +75,7 @@
<h3 class="card-title mb-0">Добавление списка спутников</h3> <h3 class="card-title mb-0">Добавление списка спутников</h3>
</div> </div>
<p class="card-text">Добавьте новый список спутников в базу данных для последующего использования в загрузке данных.</p> <p class="card-text">Добавьте новый список спутников в базу данных для последующего использования в загрузке данных.</p>
<a href="{% url 'mainapp:add_sats' %}" class="btn btn-info"> <a href="{% url 'mainapp:add_sats' %}" class="btn btn-info disabled">
Добавить список спутников Добавить список спутников
</a> </a>
</div> </div>
@@ -96,7 +96,7 @@
<h3 class="card-title mb-0">Добавление транспондеров</h3> <h3 class="card-title mb-0">Добавление транспондеров</h3>
</div> </div>
<p class="card-text">Добавьте список транспондеров из JSON-файла в базу данных. Требуется наличие файла transponders.json.</p> <p class="card-text">Добавьте список транспондеров из JSON-файла в базу данных. Требуется наличие файла transponders.json.</p>
<a href="{% url 'mainapp:add_trans' %}" class="btn btn-warning"> <a href="{% url 'mainapp:add_trans' %}" class="btn btn-warning disabled">
Добавить транспондеры Добавить транспондеры
</a> </a>
</div> </div>

View File

@@ -0,0 +1,236 @@
<!-- SigmaParameter Data Modal -->
<div class="modal fade" id="sigmaParameterModal" tabindex="-1" aria-labelledby="sigmaParameterModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header bg-info text-white">
<h5 class="modal-title" id="sigmaParameterModalLabel">
<i class="bi bi-graph-up"></i> Данные SigmaParameter
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Закрыть"></button>
</div>
<div class="modal-body" id="sigmaParameterModalBody">
<div class="text-center py-4">
<div class="spinner-border text-info" role="status">
<span class="visually-hidden">Загрузка...</span>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
</div>
</div>
</div>
</div>
<script>
function showSigmaParameterModal(parameterId) {
// Показываем модальное окно
const modal = new bootstrap.Modal(document.getElementById('sigmaParameterModal'));
modal.show();
// Показываем индикатор загрузки
const modalBody = document.getElementById('sigmaParameterModalBody');
modalBody.innerHTML = `
<div class="text-center py-4">
<div class="spinner-border text-info" role="status">
<span class="visually-hidden">Загрузка...</span>
</div>
</div>
`;
// Загружаем данные
fetch(`/api/sigma-parameter/${parameterId}/`)
.then(response => {
if (!response.ok) {
throw new Error('Ошибка загрузки данных');
}
return response.json();
})
.then(data => {
if (data.sigma_parameters.length === 0) {
modalBody.innerHTML = `
<div class="alert alert-info" role="alert">
<i class="bi bi-info-circle"></i> Нет связанных SigmaParameter
</div>
`;
return;
}
// Формируем HTML с данными
let html = '<div class="container-fluid">';
data.sigma_parameters.forEach((sigma, index) => {
html += `
<div class="card mb-3">
<div class="card-header bg-light">
<strong><i class="bi bi-broadcast"></i> SigmaParameter #${index + 1}</strong>
</div>
<div class="card-body">
<div class="row g-3">
<!-- Основная информация -->
<div class="col-md-6">
<div class="card h-100">
<div class="card-header bg-light">
<strong><i class="bi bi-info-circle"></i> Основная информация</strong>
</div>
<div class="card-body">
<table class="table table-sm table-borderless mb-0">
<tbody>
<tr>
<td class="text-muted" style="width: 40%;">Спутник:</td>
<td><strong>${sigma.satellite}</strong></td>
</tr>
<tr>
<td class="text-muted">Частота:</td>
<td><strong>${sigma.frequency} МГц</strong></td>
</tr>
<tr>
<td class="text-muted">Частота в Ku:</td>
<td><strong>${sigma.transfer_frequency} МГц</strong></td>
</tr>
<tr>
<td class="text-muted">Полоса частот:</td>
<td><strong>${sigma.freq_range} МГц</strong></td>
</tr>
<tr>
<td class="text-muted">Поляризация:</td>
<td><span class="badge bg-info">${sigma.polarization}</span></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Технические параметры -->
<div class="col-md-6">
<div class="card h-100">
<div class="card-header bg-light">
<strong><i class="bi bi-gear"></i> Технические параметры</strong>
</div>
<div class="card-body">
<table class="table table-sm table-borderless mb-0">
<tbody>
<tr>
<td class="text-muted" style="width: 40%;">Модуляция:</td>
<td><span class="badge bg-secondary">${sigma.modulation}</span></td>
</tr>
<tr>
<td class="text-muted">Стандарт:</td>
<td><span class="badge bg-secondary">${sigma.standard}</span></td>
</tr>
<tr>
<td class="text-muted">Сим. скорость:</td>
<td><strong>${sigma.bod_velocity} БОД</strong></td>
</tr>
<tr>
<td class="text-muted">ОСШ:</td>
<td><strong>${sigma.snr} дБ</strong></td>
</tr>
<tr>
<td class="text-muted">Мощность:</td>
<td><strong>${sigma.power} дБм</strong></td>
</tr>
<tr>
<td class="text-muted">Статус:</td>
<td>${sigma.status}</td>
</tr>
<tr>
<td class="text-muted">Пакетность:</td>
<td>${sigma.packets}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Временные параметры -->
<div class="col-md-6">
<div class="card">
<div class="card-header bg-light">
<strong><i class="bi bi-clock-history"></i> Временные параметры</strong>
</div>
<div class="card-body">
<table class="table table-sm table-borderless mb-0">
<tbody>
<tr>
<td class="text-muted" style="width: 40%;">Начало измерения:</td>
<td><strong>${sigma.datetime_begin}</strong></td>
</tr>
<tr>
<td class="text-muted">Окончание измерения:</td>
<td><strong>${sigma.datetime_end}</strong></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Отметки -->
<div class="col-md-6">
<div class="card">
<div class="card-header bg-light">
<strong><i class="bi bi-check2-square"></i> Отметки (${sigma.marks.length})</strong>
</div>
<div class="card-body">
`;
if (sigma.marks.length > 0) {
html += `
<div class="table-responsive" style="max-height: 200px; overflow-y: auto;">
<table class="table table-sm table-striped mb-0">
<thead class="table-light sticky-top">
<tr>
<th style="width: 20%;">Отметка</th>
<th>Дата</th>
</tr>
</thead>
<tbody>
`;
sigma.marks.forEach(mark => {
const markClass = mark.mark === '+' ? 'text-success' : 'text-danger';
html += `
<tr>
<td class="${markClass}"><strong>${mark.mark}</strong></td>
<td>${mark.date}</td>
</tr>
`;
});
html += `
</tbody>
</table>
</div>
`;
} else {
html += `
<p class="text-muted mb-0">Нет отметок</p>
`;
}
html += `
</div>
</div>
</div>
</div>
</div>
</div>
`;
});
html += '</div>';
modalBody.innerHTML = html;
})
.catch(error => {
modalBody.innerHTML = `
<div class="alert alert-danger" role="alert">
<i class="bi bi-exclamation-triangle"></i> ${error.message}
</div>
`;
});
}
</script>

View File

@@ -20,7 +20,17 @@
{% endfor %} {% endfor %}
{% endif %} {% endif %}
<p class="card-text">Введите допустимый разброс для частоты и полосы</p> <div class="alert alert-info" role="alert">
<strong>Информация о привязке:</strong>
<p class="mb-2">Погрешность центральной частоты определяется автоматически в зависимости от полосы:</p>
<ul class="mb-0 small">
<li>0 - 500 кГц: <strong>0.1%</strong></li>
<li>500 кГц - 1.5 МГц: <strong>0.5%</strong></li>
<li>1.5 - 5 МГц: <strong>1%</strong></li>
<li>5 - 10 МГц: <strong>2%</strong></li>
<li>Более 10 МГц: <strong>5%</strong></li>
</ul>
</div>
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}
@@ -38,17 +48,19 @@
<div class="text-danger mt-1">{{ form.ku_range.errors }}</div> <div class="text-danger mt-1">{{ form.ku_range.errors }}</div>
{% endif %} {% endif %}
</div> {% endcomment %} </div> {% endcomment %}
<div class="mb-3"> {% comment %} <div class="mb-3">
<label for="{{ form.value1.id_for_label }}" class="form-label">Разброс по частоте(в МГц)</label> <label for="{{ form.value1.id_for_label }}" class="form-label">Разброс по частоте(в МГц)</label>
{{ form.value1 }} {{ form.value1 }}
<small class="form-text text-muted">Не используется - погрешность определяется автоматически</small>
{% if form.value1.errors %} {% if form.value1.errors %}
<div class="text-danger mt-1">{{ form.value1.errors }}</div> <div class="text-danger mt-1">{{ form.value1.errors }}</div>
{% endif %} {% endif %}
</div> </div> {% endcomment %}
<div class="mb-3"> <div class="mb-3">
<label for="{{ form.value2.id_for_label }}" class="form-label">Разброс по полосе(в %)</label> <label for="{{ form.value2.id_for_label }}" class="form-label">Разброс по полосе (в %)</label>
{{ form.value2 }} {{ form.value2 }}
<small class="form-text text-muted">Допустимое отклонение полосы частот в процентах</small>
{% if form.value2.errors %} {% if form.value2.errors %}
<div class="text-danger mt-1">{{ form.value2.errors }}</div> <div class="text-danger mt-1">{{ form.value2.errors }}</div>
{% endif %} {% endif %}

View File

@@ -257,6 +257,12 @@
onchange="toggleColumn(this)"> Тип источника onchange="toggleColumn(this)"> Тип источника
</label> </label>
</li> </li>
<li>
<label class="dropdown-item">
<input type="checkbox" class="column-toggle" data-column="25" checked
onchange="toggleColumn(this)"> Sigma
</label>
</li>
</ul> </ul>
</div> </div>
@@ -470,6 +476,7 @@
{% include 'mainapp/components/_table_header.html' with label="Усреднённое" field="" sortable=False %} {% include 'mainapp/components/_table_header.html' with label="Усреднённое" field="" sortable=False %}
{% include 'mainapp/components/_table_header.html' with label="Стандарт" field="standard" sort=sort %} {% include 'mainapp/components/_table_header.html' with label="Стандарт" field="standard" sort=sort %}
{% include 'mainapp/components/_table_header.html' with label="Тип источника" field="" sortable=False %} {% include 'mainapp/components/_table_header.html' with label="Тип источника" field="" sortable=False %}
{% include 'mainapp/components/_table_header.html' with label="Sigma" field="" sortable=False %}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -511,10 +518,19 @@
- -
{% endif %} {% endif %}
</td> </td>
<td>
{% if item.has_sigma %}
<a href="#" class="text-info text-decoration-none" onclick="showSigmaParameterModal({{ item.obj.parameter_obj.id }}); return false;" title="{{ item.sigma_info }}">
<i class="bi bi-graph-up"></i> {{ item.sigma_info }}
</a>
{% else %}
-
{% endif %}
</td>
</tr> </tr>
{% empty %} {% empty %}
<tr> <tr>
<td colspan="25" class="text-center py-4"> <td colspan="26" class="text-center py-4">
{% if selected_satellite_id %} {% if selected_satellite_id %}
Нет данных для выбранных фильтров Нет данных для выбранных фильтров
{% else %} {% else %}
@@ -1182,6 +1198,9 @@
<!-- Include the selected items offcanvas component --> <!-- Include the selected items offcanvas component -->
{% include 'mainapp/components/_selected_items_offcanvas.html' %} {% include 'mainapp/components/_selected_items_offcanvas.html' %}
<!-- Include the sigma parameter modal component -->
{% include 'mainapp/components/_sigma_parameter_modal.html' %}
<!-- LyngSat Data Modal --> <!-- LyngSat Data Modal -->
<div class="modal fade" id="lyngsatModal" tabindex="-1" aria-labelledby="lyngsatModalLabel" aria-hidden="true"> <div class="modal fade" id="lyngsatModal" tabindex="-1" aria-labelledby="lyngsatModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg">

View File

@@ -22,6 +22,7 @@ urlpatterns = [
path('vch-link/', views.LinkVchSigmaView.as_view(), name='link_vch_sigma'), path('vch-link/', views.LinkVchSigmaView.as_view(), name='link_vch_sigma'),
path('link-lyngsat/', views.LinkLyngsatSourcesView.as_view(), name='link_lyngsat'), path('link-lyngsat/', views.LinkLyngsatSourcesView.as_view(), name='link_lyngsat'),
path('api/lyngsat/<int:lyngsat_id>/', views.LyngsatDataAPIView.as_view(), name='lyngsat_data_api'), path('api/lyngsat/<int:lyngsat_id>/', views.LyngsatDataAPIView.as_view(), name='lyngsat_data_api'),
path('api/sigma-parameter/<int:parameter_id>/', views.SigmaParameterDataAPIView.as_view(), name='sigma_parameter_data_api'),
path('kubsat-excel/', views.ProcessKubsatView.as_view(), name='kubsat_excel'), path('kubsat-excel/', views.ProcessKubsatView.as_view(), name='kubsat_excel'),
path('object/create/', views.ObjItemCreateView.as_view(), name='objitem_create'), path('object/create/', views.ObjItemCreateView.as_view(), name='objitem_create'),
path('object/<int:pk>/edit/', views.ObjItemUpdateView.as_view(), name='objitem_update'), path('object/<int:pk>/edit/', views.ObjItemUpdateView.as_view(), name='objitem_update'),

View File

@@ -447,27 +447,96 @@ def get_vch_load_from_html(file, sat: Satellite) -> None:
sigma_load.save() sigma_load.save()
def get_frequency_tolerance_percent(freq_range_mhz: float) -> float:
"""
Определяет процент погрешности центральной частоты в зависимости от полосы частот.
Args:
freq_range_mhz (float): Полоса частот в МГц
Returns:
float: Процент погрешности для центральной частоты
Диапазоны:
- 0 - 0.5 МГц (0 - 500 кГц): 0.1%
- 0.5 - 1.5 МГц (500 кГц - 1.5 МГц): 0.5%
- 1.5 - 5 МГц: 1%
- 5 - 10 МГц: 2%
- > 10 МГц: 5%
"""
if freq_range_mhz < 0.5:
return 0.005
elif freq_range_mhz < 1.5:
return 0.01
elif freq_range_mhz < 5.0:
return 0.02
elif freq_range_mhz < 10.0:
return 0.05
else:
return 0.1
def compare_and_link_vch_load( def compare_and_link_vch_load(
sat_id: Satellite, eps_freq: float, eps_frange: float, ku_range: float sat_id: Satellite, eps_freq: float, eps_frange: float, ku_range: float
): ):
item_obj = ObjItem.objects.filter(parameters_obj__id_satellite=sat_id) """
vch_sigma = SigmaParameter.objects.filter(id_satellite=sat_id) Привязывает SigmaParameter к Parameter на основе совпадения параметров.
Погрешность центральной частоты определяется автоматически в зависимости от полосы частот:
- 0-500 кГц: 0.1%
- 500 кГц-1.5 МГц: 0.5%
- 1.5-5 МГц: 1%
- 5-10 МГц: 2%
- >10 МГц: 5%
Args:
sat_id (Satellite): Спутник для фильтрации
eps_freq (float): Не используется (оставлен для обратной совместимости)
eps_frange (float): Погрешность полосы частот в процентах
ku_range (float): Не используется (оставлен для обратной совместимости)
Returns:
tuple: (количество объектов, количество привязок)
"""
# Получаем все ObjItem с Parameter для данного спутника
item_obj = ObjItem.objects.filter(
parameter_obj__id_satellite=sat_id
).select_related('parameter_obj', 'parameter_obj__polarization')
vch_sigma = SigmaParameter.objects.filter(
id_satellite=sat_id
).select_related('polarization')
link_count = 0 link_count = 0
obj_count = len(item_obj) obj_count = item_obj.count()
for idx, obj in enumerate(item_obj):
vch_load = obj.parameters_obj.get() for obj in item_obj:
if vch_load.frequency == -1.0: vch_load = obj.parameter_obj
# Пропускаем объекты с некорректной частотой
if not vch_load or vch_load.frequency == -1.0:
continue continue
# Определяем погрешность частоты на основе полосы
freq_tolerance_percent = get_frequency_tolerance_percent(vch_load.freq_range)
# Вычисляем допустимое отклонение частоты в МГц
freq_tolerance_mhz = vch_load.frequency * freq_tolerance_percent / 100
# Вычисляем допустимое отклонение полосы в МГц
frange_tolerance_mhz = vch_load.freq_range * eps_frange / 100
for sigma in vch_sigma: for sigma in vch_sigma:
if ( # Проверяем совпадение по всем параметрам
abs(sigma.transfer_frequency - vch_load.frequency) <= eps_freq freq_match = abs(sigma.transfer_frequency - vch_load.frequency) <= freq_tolerance_mhz
and abs(sigma.freq_range - vch_load.freq_range) frange_match = abs(sigma.freq_range - vch_load.freq_range) <= frange_tolerance_mhz
<= vch_load.freq_range * eps_frange / 100 pol_match = sigma.polarization == vch_load.polarization
and sigma.polarization == vch_load.polarization
): if freq_match and frange_match and pol_match:
sigma.parameter = vch_load sigma.parameter = vch_load
sigma.save() sigma.save()
link_count += 1 link_count += 1
return obj_count, link_count return obj_count, link_count

View File

@@ -362,12 +362,13 @@ class LinkVchSigmaView(LoginRequiredMixin, FormView):
form_class = VchLinkForm form_class = VchLinkForm
def form_valid(self, form): def form_valid(self, form):
freq = form.cleaned_data["value1"] # value1 больше не используется - погрешность частоты определяется автоматически
freq_range = form.cleaned_data["value2"] freq_range = form.cleaned_data["value2"]
# ku_range = float(form.cleaned_data['ku_range'])
sat_id = form.cleaned_data["sat_choice"] sat_id = form.cleaned_data["sat_choice"]
# print(freq, freq_range, ku_range, sat_id.pk)
count_all, link_count = compare_and_link_vch_load(sat_id, freq, freq_range, 1) # Передаём 0 для eps_freq и ku_range, так как они не используются
count_all, link_count = compare_and_link_vch_load(sat_id, 0, freq_range, 0)
messages.success( messages.success(
self.request, f"Привязано {link_count} из {count_all} объектов" self.request, f"Привязано {link_count} из {count_all} объектов"
) )
@@ -479,6 +480,84 @@ class LyngsatDataAPIView(LoginRequiredMixin, View):
return JsonResponse({'error': str(e)}, status=500) return JsonResponse({'error': str(e)}, status=500)
class SigmaParameterDataAPIView(LoginRequiredMixin, View):
"""API для получения данных SigmaParameter"""
def get(self, request, parameter_id):
from .models import Parameter
try:
parameter = Parameter.objects.select_related(
'id_satellite',
'polarization',
'modulation',
'standard'
).prefetch_related(
'sigma_parameter__mark',
'sigma_parameter__id_satellite',
'sigma_parameter__polarization',
'sigma_parameter__modulation',
'sigma_parameter__standard'
).get(id=parameter_id)
# Получаем все связанные SigmaParameter
sigma_params = parameter.sigma_parameter.all()
sigma_data = []
for sigma in sigma_params:
# Получаем отметки
marks = []
for mark in sigma.mark.all().order_by('-timestamp'):
mark_str = '+' if mark.mark else '-'
date_str = '-'
if mark.timestamp:
local_time = timezone.localtime(mark.timestamp)
date_str = local_time.strftime("%d.%m.%Y %H:%M")
marks.append({
'mark': mark_str,
'date': date_str
})
# Форматируем даты начала и окончания
datetime_begin_str = '-'
if sigma.datetime_begin:
local_time = timezone.localtime(sigma.datetime_begin)
datetime_begin_str = local_time.strftime("%d.%m.%Y %H:%M")
datetime_end_str = '-'
if sigma.datetime_end:
local_time = timezone.localtime(sigma.datetime_end)
datetime_end_str = local_time.strftime("%d.%m.%Y %H:%M")
sigma_data.append({
'id': sigma.id,
'satellite': sigma.id_satellite.name if sigma.id_satellite else '-',
'frequency': f"{sigma.frequency:.3f}" if sigma.frequency else '-',
'transfer_frequency': f"{sigma.transfer_frequency:.3f}" if sigma.transfer_frequency else '-',
'freq_range': f"{sigma.freq_range:.3f}" if sigma.freq_range else '-',
'polarization': sigma.polarization.name if sigma.polarization else '-',
'modulation': sigma.modulation.name if sigma.modulation else '-',
'standard': sigma.standard.name if sigma.standard else '-',
'bod_velocity': f"{sigma.bod_velocity:.0f}" if sigma.bod_velocity else '-',
'snr': f"{sigma.snr:.1f}" if sigma.snr is not None else '-',
'power': f"{sigma.power:.1f}" if sigma.power is not None else '-',
'status': sigma.status or '-',
'packets': 'Да' if sigma.packets else 'Нет' if sigma.packets is not None else '-',
'datetime_begin': datetime_begin_str,
'datetime_end': datetime_end_str,
'marks': marks
})
return JsonResponse({
'parameter_id': parameter.id,
'sigma_parameters': sigma_data
})
except Parameter.DoesNotExist:
return JsonResponse({'error': 'Parameter не найден'}, status=404)
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)
class ProcessKubsatView(LoginRequiredMixin, FormMessageMixin, FormView): class ProcessKubsatView(LoginRequiredMixin, FormMessageMixin, FormView):
template_name = "mainapp/process_kubsat.html" template_name = "mainapp/process_kubsat.html"
form_class = NewEventForm form_class = NewEventForm
@@ -585,6 +664,10 @@ class ObjItemListView(LoginRequiredMixin, View):
"parameter_obj__modulation", "parameter_obj__modulation",
"parameter_obj__standard", "parameter_obj__standard",
) )
.prefetch_related(
"parameter_obj__sigma_parameter",
"parameter_obj__sigma_parameter__polarization",
)
.filter(parameter_obj__id_satellite_id__in=selected_satellites) .filter(parameter_obj__id_satellite_id__in=selected_satellites)
) )
else: else:
@@ -598,6 +681,9 @@ class ObjItemListView(LoginRequiredMixin, View):
"parameter_obj__polarization", "parameter_obj__polarization",
"parameter_obj__modulation", "parameter_obj__modulation",
"parameter_obj__standard", "parameter_obj__standard",
).prefetch_related(
"parameter_obj__sigma_parameter",
"parameter_obj__sigma_parameter__polarization",
) )
if freq_min is not None and freq_min.strip() != "": if freq_min is not None and freq_min.strip() != "":
@@ -872,6 +958,23 @@ class ObjItemListView(LoginRequiredMixin, View):
# Check if LyngSat source is linked # Check if LyngSat source is linked
source_type = "ТВ" if obj.lyngsat_source else "-" source_type = "ТВ" if obj.lyngsat_source else "-"
# Check if SigmaParameter is linked
has_sigma = False
sigma_info = "-"
if param:
sigma_count = param.sigma_parameter.count()
if sigma_count > 0:
has_sigma = True
# Get first sigma parameter for preview
first_sigma = param.sigma_parameter.first()
if first_sigma:
sigma_freq = f"{first_sigma.frequency:.3f}" if first_sigma.frequency else "-"
sigma_range = f"{first_sigma.freq_range:.3f}" if first_sigma.freq_range else "-"
sigma_pol = first_sigma.polarization.name if first_sigma.polarization else "-"
# Сокращаем поляризацию
sigma_pol_short = sigma_pol[0] if sigma_pol and sigma_pol != "-" else "-"
sigma_info = f"{sigma_freq}/{sigma_range}/{sigma_pol_short}"
processed_objects.append( processed_objects.append(
{ {
"id": obj.id, "id": obj.id,
@@ -896,6 +999,8 @@ class ObjItemListView(LoginRequiredMixin, View):
"is_average": is_average, "is_average": is_average,
"source_type": source_type, "source_type": source_type,
"standard": standard_name, "standard": standard_name,
"has_sigma": has_sigma,
"sigma_info": sigma_info,
"obj": obj, "obj": obj,
} }
) )

View File

@@ -25,26 +25,33 @@ class CesiumMapView(LoginRequiredMixin, TemplateView):
Отображает спутники и их зоны покрытия на интерактивной 3D карте. Отображает спутники и их зоны покрытия на интерактивной 3D карте.
""" """
template_name = 'mapsapp/map3d.html'
template_name = "mapsapp/map3d.html"
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
# Оптимизированный запрос - загружаем только необходимые поля # Оптимизированный запрос - загружаем только необходимые поля
context['sats'] = Satellite.objects.filter( # Фильтруем спутники, у которых есть параметры с привязанными объектами
parameters__objitems__isnull=False context["sats"] = (
).distinct().only('id', 'name').order_by('name') Satellite.objects.filter(parameters__objitem__isnull=False)
.distinct()
.only("id", "name")
.order_by("name")
)
return context return context
class GetFootprintsView(LoginRequiredMixin, View): class GetFootprintsView(LoginRequiredMixin, View):
""" """
API для получения зон покрытия (footprints) спутника. API для получения зон покрытия (footprints) спутника.
Возвращает список названий зон покрытия для указанного спутника. Возвращает список названий зон покрытия для указанного спутника.
""" """
def get(self, request, sat_id): def get(self, request, sat_id):
try: try:
# Оптимизированный запрос - загружаем только поле name # Оптимизированный запрос - загружаем только поле name
sat_name = Satellite.objects.only('name').get(id=sat_id).name sat_name = Satellite.objects.only("name").get(id=sat_id).name
footprint_names = get_band_names(sat_name) footprint_names = get_band_names(sat_name)
return JsonResponse(footprint_names, safe=False) return JsonResponse(footprint_names, safe=False)
@@ -60,6 +67,7 @@ class TileProxyView(View):
Кэширует тайлы на 7 дней для улучшения производительности. Кэширует тайлы на 7 дней для улучшения производительности.
""" """
# Константы # Константы
TILE_BASE_URL = "https://static.satbeams.com/tiles" TILE_BASE_URL = "https://static.satbeams.com/tiles"
CACHE_DURATION = 60 * 60 * 24 * 7 # 7 дней CACHE_DURATION = 60 * 60 * 24 * 7 # 7 дней
@@ -72,7 +80,7 @@ class TileProxyView(View):
def get(self, request, footprint_name, z, x, y): def get(self, request, footprint_name, z, x, y):
# Валидация имени footprint # Валидация имени footprint
if not footprint_name.replace('-', '').replace('_', '').isalnum(): if not footprint_name.replace("-", "").replace("_", "").isalnum():
return HttpResponse("Invalid footprint name", status=400) return HttpResponse("Invalid footprint name", status=400)
url = f"{self.TILE_BASE_URL}/{footprint_name}/{z}/{x}/{y}.png" url = f"{self.TILE_BASE_URL}/{footprint_name}/{z}/{x}/{y}.png"
@@ -80,7 +88,7 @@ class TileProxyView(View):
try: try:
resp = requests.get(url, timeout=self.REQUEST_TIMEOUT) resp = requests.get(url, timeout=self.REQUEST_TIMEOUT)
if resp.status_code == 200: if resp.status_code == 200:
response = HttpResponse(resp.content, content_type='image/png') response = HttpResponse(resp.content, content_type="image/png")
response["Access-Control-Allow-Origin"] = "*" response["Access-Control-Allow-Origin"] = "*"
response["Cache-Control"] = f"public, max-age={self.CACHE_DURATION}" response["Cache-Control"] = f"public, max-age={self.CACHE_DURATION}"
return response return response
@@ -91,26 +99,37 @@ class TileProxyView(View):
except requests.RequestException as e: except requests.RequestException as e:
return HttpResponse(f"Proxy error: {e}", status=500) return HttpResponse(f"Proxy error: {e}", status=500)
class LeafletMapView(LoginRequiredMixin, TemplateView): class LeafletMapView(LoginRequiredMixin, TemplateView):
""" """
Представление для отображения 2D карты с использованием Leaflet. Представление для отображения 2D карты с использованием Leaflet.
Отображает спутники и транспондеры на интерактивной 2D карте. Отображает спутники и транспондеры на интерактивной 2D карте.
""" """
template_name = 'mapsapp/map2d.html'
template_name = "mapsapp/map2d.html"
def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
# Оптимизированные запросы - загружаем только необходимые поля # Оптимизированные запросы - загружаем только необходимые поля
context['sats'] = Satellite.objects.filter( # Фильтруем спутники, у которых есть параметры с привязанными объектами
parameters__objitems__isnull=False context["sats"] = (
).distinct().only('id', 'name').order_by('name') Satellite.objects.filter(parameters__objitem__isnull=False)
.distinct()
.only("id", "name")
.order_by("name")
)
context['trans'] = Transponders.objects.select_related( context["trans"] = Transponders.objects.select_related(
'sat_id', 'polarization' "sat_id", "polarization"
).only( ).only(
'id', 'name', 'sat_id__name', 'polarization__name', "id",
'downlink', 'frequency_range', 'zone_name' "name",
"sat_id__name",
"polarization__name",
"downlink",
"frequency_range",
"zone_name",
) )
return context return context
@@ -121,17 +140,19 @@ class GetTransponderOnSatIdView(LoginRequiredMixin, View):
Возвращает список транспондеров для указанного спутника с оптимизированными запросами. Возвращает список транспондеров для указанного спутника с оптимизированными запросами.
""" """
def get(self, request, sat_id): def get(self, request, sat_id):
# Оптимизированный запрос с select_related и only # Оптимизированный запрос с select_related и only
trans = Transponders.objects.filter( trans = (
sat_id=sat_id Transponders.objects.filter(sat_id=sat_id)
).select_related('polarization').only( .select_related("polarization")
'name', 'downlink', 'frequency_range', .only(
'zone_name', 'polarization__name' "name", "downlink", "frequency_range", "zone_name", "polarization__name"
)
) )
if not trans.exists(): if not trans.exists():
return JsonResponse({'error': 'Объектов не найдено'}, status=404) return JsonResponse({"error": "Объектов не найдено"}, status=404)
# Используем list comprehension для лучшей производительности # Используем list comprehension для лучшей производительности
output = [ output = [
@@ -140,7 +161,7 @@ class GetTransponderOnSatIdView(LoginRequiredMixin, View):
"frequency": tran.downlink, "frequency": tran.downlink,
"frequency_range": tran.frequency_range, "frequency_range": tran.frequency_range,
"zone_name": tran.zone_name, "zone_name": tran.zone_name,
"polarization": tran.polarization.name if tran.polarization else "-" "polarization": tran.polarization.name if tran.polarization else "-",
} }
for tran in trans for tran in trans
] ]