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

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

View File

@@ -75,7 +75,7 @@
<h3 class="card-title mb-0">Добавление списка спутников</h3>
</div>
<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>
</div>
@@ -96,7 +96,7 @@
<h3 class="card-title mb-0">Добавление транспондеров</h3>
</div>
<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>
</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 %}
{% 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">
{% csrf_token %}
@@ -38,17 +48,19 @@
<div class="text-danger mt-1">{{ form.ku_range.errors }}</div>
{% endif %}
</div> {% endcomment %}
<div class="mb-3">
{% comment %} <div class="mb-3">
<label for="{{ form.value1.id_for_label }}" class="form-label">Разброс по частоте(в МГц)</label>
{{ form.value1 }}
<small class="form-text text-muted">Не используется - погрешность определяется автоматически</small>
{% if form.value1.errors %}
<div class="text-danger mt-1">{{ form.value1.errors }}</div>
{% endif %}
</div>
</div> {% endcomment %}
<div class="mb-3">
<label for="{{ form.value2.id_for_label }}" class="form-label">Разброс по полосе (в %)</label>
{{ form.value2 }}
<small class="form-text text-muted">Допустимое отклонение полосы частот в процентах</small>
{% if form.value2.errors %}
<div class="text-danger mt-1">{{ form.value2.errors }}</div>
{% endif %}

View File

@@ -257,6 +257,12 @@
onchange="toggleColumn(this)"> Тип источника
</label>
</li>
<li>
<label class="dropdown-item">
<input type="checkbox" class="column-toggle" data-column="25" checked
onchange="toggleColumn(this)"> Sigma
</label>
</li>
</ul>
</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="standard" sort=sort %}
{% include 'mainapp/components/_table_header.html' with label="Тип источника" field="" sortable=False %}
{% include 'mainapp/components/_table_header.html' with label="Sigma" field="" sortable=False %}
</tr>
</thead>
<tbody>
@@ -511,10 +518,19 @@
-
{% endif %}
</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>
{% empty %}
<tr>
<td colspan="25" class="text-center py-4">
<td colspan="26" class="text-center py-4">
{% if selected_satellite_id %}
Нет данных для выбранных фильтров
{% else %}
@@ -1182,6 +1198,9 @@
<!-- Include the selected items offcanvas component -->
{% include 'mainapp/components/_selected_items_offcanvas.html' %}
<!-- Include the sigma parameter modal component -->
{% include 'mainapp/components/_sigma_parameter_modal.html' %}
<!-- LyngSat Data Modal -->
<div class="modal fade" id="lyngsatModal" tabindex="-1" aria-labelledby="lyngsatModalLabel" aria-hidden="true">
<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('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/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('object/create/', views.ObjItemCreateView.as_view(), name='objitem_create'),
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()
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(
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
obj_count = len(item_obj)
for idx, obj in enumerate(item_obj):
vch_load = obj.parameters_obj.get()
if vch_load.frequency == -1.0:
obj_count = item_obj.count()
for obj in item_obj:
vch_load = obj.parameter_obj
# Пропускаем объекты с некорректной частотой
if not vch_load or vch_load.frequency == -1.0:
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:
if (
abs(sigma.transfer_frequency - vch_load.frequency) <= eps_freq
and abs(sigma.freq_range - vch_load.freq_range)
<= vch_load.freq_range * eps_frange / 100
and sigma.polarization == vch_load.polarization
):
# Проверяем совпадение по всем параметрам
freq_match = abs(sigma.transfer_frequency - vch_load.frequency) <= freq_tolerance_mhz
frange_match = abs(sigma.freq_range - vch_load.freq_range) <= frange_tolerance_mhz
pol_match = sigma.polarization == vch_load.polarization
if freq_match and frange_match and pol_match:
sigma.parameter = vch_load
sigma.save()
link_count += 1
return obj_count, link_count

View File

@@ -362,12 +362,13 @@ class LinkVchSigmaView(LoginRequiredMixin, FormView):
form_class = VchLinkForm
def form_valid(self, form):
freq = form.cleaned_data["value1"]
# value1 больше не используется - погрешность частоты определяется автоматически
freq_range = form.cleaned_data["value2"]
# ku_range = float(form.cleaned_data['ku_range'])
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(
self.request, f"Привязано {link_count} из {count_all} объектов"
)
@@ -479,6 +480,84 @@ class LyngsatDataAPIView(LoginRequiredMixin, View):
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):
template_name = "mainapp/process_kubsat.html"
form_class = NewEventForm
@@ -585,6 +664,10 @@ class ObjItemListView(LoginRequiredMixin, View):
"parameter_obj__modulation",
"parameter_obj__standard",
)
.prefetch_related(
"parameter_obj__sigma_parameter",
"parameter_obj__sigma_parameter__polarization",
)
.filter(parameter_obj__id_satellite_id__in=selected_satellites)
)
else:
@@ -598,6 +681,9 @@ class ObjItemListView(LoginRequiredMixin, View):
"parameter_obj__polarization",
"parameter_obj__modulation",
"parameter_obj__standard",
).prefetch_related(
"parameter_obj__sigma_parameter",
"parameter_obj__sigma_parameter__polarization",
)
if freq_min is not None and freq_min.strip() != "":
@@ -872,6 +958,23 @@ class ObjItemListView(LoginRequiredMixin, View):
# Check if LyngSat source is linked
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(
{
"id": obj.id,
@@ -896,6 +999,8 @@ class ObjItemListView(LoginRequiredMixin, View):
"is_average": is_average,
"source_type": source_type,
"standard": standard_name,
"has_sigma": has_sigma,
"sigma_info": sigma_info,
"obj": obj,
}
)

View File

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