Добавил информацию о типе объекта. Просто фиксы
This commit is contained in:
@@ -55,6 +55,19 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Action buttons -->
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<a href="{% url 'mainapp:fill_lyngsat_data' %}" class="btn btn-secondary btn-sm" title="Заполнить данные Lyngsat">
|
||||||
|
<i class="bi bi-cloud-download"></i> Добавить данные
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'mainapp:link_lyngsat' %}" class="btn btn-primary btn-sm" title="Привязать источники LyngSat">
|
||||||
|
<i class="bi bi-link-45deg"></i> Привязать
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'mainapp:unlink_all_lyngsat' %}" class="btn btn-warning btn-sm" title="Отвязать все источники LyngSat">
|
||||||
|
<i class="bi bi-x-circle"></i> Отвязать
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Filter Toggle Button -->
|
<!-- Filter Toggle Button -->
|
||||||
<div>
|
<div>
|
||||||
<button class="btn btn-outline-primary btn-sm" type="button" data-bs-toggle="offcanvas"
|
<button class="btn btn-outline-primary btn-sm" type="button" data-bs-toggle="offcanvas"
|
||||||
@@ -252,10 +265,19 @@
|
|||||||
{% for item in lyngsat_items %}
|
{% for item in lyngsat_items %}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="text-center">{{ item.id }}</td>
|
<td class="text-center">{{ item.id }}</td>
|
||||||
<td>{{ item.id_satellite.name|default:"-" }}</td>
|
<td>
|
||||||
|
{% if item.id_satellite %}
|
||||||
|
<a href="#" class="text-decoration-underline"
|
||||||
|
onclick="showSatelliteModal({{ item.id_satellite.id }}); return false;">
|
||||||
|
{{ item.id_satellite.name }}
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
<td>{{ item.frequency|floatformat:3|default:"-" }}</td>
|
<td>{{ item.frequency|floatformat:3|default:"-" }}</td>
|
||||||
<td>{{ item.polarization.name|default:"-" }}</td>
|
<td>{{ item.polarization.name|default:"-" }}</td>
|
||||||
<td>{{ item.sym_velocity|floatformat:3|default:"-" }}</td>
|
<td>{{ item.sym_velocity|floatformat:0|default:"-" }}</td>
|
||||||
<td>{{ item.modulation.name|default:"-" }}</td>
|
<td>{{ item.modulation.name|default:"-" }}</td>
|
||||||
<td>{{ item.standard.name|default:"-" }}</td>
|
<td>{{ item.standard.name|default:"-" }}</td>
|
||||||
<td>{{ item.fec|default:"-" }}</td>
|
<td>{{ item.fec|default:"-" }}</td>
|
||||||
@@ -425,4 +447,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- Include the satellite modal component -->
|
||||||
|
{% include 'mainapp/components/_satellite_modal.html' %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -124,8 +124,10 @@ class LyngSatListView(LoginRequiredMixin, ListView):
|
|||||||
context['search_query'] = self.request.GET.get('search', '')
|
context['search_query'] = self.request.GET.get('search', '')
|
||||||
context['sort'] = self.request.GET.get('sort', '-id')
|
context['sort'] = self.request.GET.get('sort', '-id')
|
||||||
|
|
||||||
# Данные для фильтров
|
# Данные для фильтров - только спутники с существующими записями LyngSat
|
||||||
context['satellites'] = Satellite.objects.all().order_by('name')
|
context['satellites'] = Satellite.objects.filter(
|
||||||
|
lyngsat__isnull=False
|
||||||
|
).distinct().order_by('name')
|
||||||
context['polarizations'] = Polarization.objects.all().order_by('name')
|
context['polarizations'] = Polarization.objects.all().order_by('name')
|
||||||
context['modulations'] = Modulation.objects.all().order_by('name')
|
context['modulations'] = Modulation.objects.all().order_by('name')
|
||||||
context['standards'] = Standard.objects.all().order_by('name')
|
context['standards'] = Standard.objects.all().order_by('name')
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ from .models import (
|
|||||||
Standard,
|
Standard,
|
||||||
SigmaParMark,
|
SigmaParMark,
|
||||||
ObjectMark,
|
ObjectMark,
|
||||||
|
ObjectInfo,
|
||||||
SigmaParameter,
|
SigmaParameter,
|
||||||
Parameter,
|
Parameter,
|
||||||
Satellite,
|
Satellite,
|
||||||
@@ -394,6 +395,15 @@ class StandardAdmin(BaseAdmin):
|
|||||||
ordering = ("name",)
|
ordering = ("name",)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(ObjectInfo)
|
||||||
|
class ObjectInfoAdmin(BaseAdmin):
|
||||||
|
"""Админ-панель для модели ObjectInfo (Тип объекта)."""
|
||||||
|
|
||||||
|
list_display = ("name",)
|
||||||
|
search_fields = ("name",)
|
||||||
|
ordering = ("name",)
|
||||||
|
|
||||||
|
|
||||||
class SigmaParameterInline(admin.StackedInline):
|
class SigmaParameterInline(admin.StackedInline):
|
||||||
model = SigmaParameter
|
model = SigmaParameter
|
||||||
extra = 0
|
extra = 0
|
||||||
@@ -1036,20 +1046,26 @@ class ObjItemInline(admin.TabularInline):
|
|||||||
class SourceAdmin(ImportExportActionModelAdmin, LeafletGeoAdmin, BaseAdmin):
|
class SourceAdmin(ImportExportActionModelAdmin, LeafletGeoAdmin, BaseAdmin):
|
||||||
"""Админ-панель для модели Source."""
|
"""Админ-панель для модели Source."""
|
||||||
|
|
||||||
list_display = ("id", "created_at", "updated_at")
|
list_display = ("id", "info", "created_at", "updated_at")
|
||||||
|
list_select_related = ("info",)
|
||||||
list_filter = (
|
list_filter = (
|
||||||
|
("info", MultiSelectRelatedDropdownFilter),
|
||||||
("created_at", DateRangeQuickSelectListFilterBuilder()),
|
("created_at", DateRangeQuickSelectListFilterBuilder()),
|
||||||
("updated_at", DateRangeQuickSelectListFilterBuilder()),
|
("updated_at", DateRangeQuickSelectListFilterBuilder()),
|
||||||
)
|
)
|
||||||
search_fields = ("id",)
|
search_fields = ("id", "info__name")
|
||||||
ordering = ("-created_at",)
|
ordering = ("-created_at",)
|
||||||
readonly_fields = ("created_at", "created_by", "updated_at", "updated_by")
|
readonly_fields = ("created_at", "created_by", "updated_at", "updated_by")
|
||||||
inlines = [ObjItemInline]
|
inlines = [ObjItemInline]
|
||||||
|
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(
|
(
|
||||||
"Координаты: геолокация",
|
"Основная информация",
|
||||||
{"fields": ("coords_kupsat", "coords_valid", "coords_reference")},
|
{"fields": ("info",)},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"Координаты",
|
||||||
|
{"fields": ("coords_average", "coords_kupsat", "coords_valid", "coords_reference")},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"Метаданные",
|
"Метаданные",
|
||||||
@@ -1059,3 +1075,5 @@ class SourceAdmin(ImportExportActionModelAdmin, LeafletGeoAdmin, BaseAdmin):
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
autocomplete_fields = ("info",)
|
||||||
|
|||||||
@@ -463,7 +463,16 @@ class SourceForm(forms.ModelForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Source
|
model = Source
|
||||||
fields = [] # Все поля обрабатываются вручную
|
fields = ['info'] # Добавляем поле info
|
||||||
|
widgets = {
|
||||||
|
'info': forms.Select(attrs={
|
||||||
|
'class': 'form-select',
|
||||||
|
'id': 'id_info',
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
labels = {
|
||||||
|
'info': 'Тип объекта',
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|||||||
31
dbapp/mainapp/migrations/0008_objectinfo_source_info.py
Normal file
31
dbapp/mainapp/migrations/0008_objectinfo_source_info.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-17 12:26
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('mainapp', '0007_make_source_required'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ObjectInfo',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(help_text='Информация о типе объекта', max_length=255, unique=True, verbose_name='Тип объекта')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Тип объекта',
|
||||||
|
'verbose_name_plural': 'Типы объектов',
|
||||||
|
'ordering': ['name'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='source',
|
||||||
|
name='info',
|
||||||
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='source_info', to='mainapp.objectinfo', verbose_name='Тип объекта'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -67,6 +67,22 @@ class CustomUser(models.Model):
|
|||||||
verbose_name_plural = "Пользователи"
|
verbose_name_plural = "Пользователи"
|
||||||
ordering = ["user__username"]
|
ordering = ["user__username"]
|
||||||
|
|
||||||
|
class ObjectInfo(models.Model):
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
unique=True,
|
||||||
|
verbose_name="Тип объекта",
|
||||||
|
help_text="Информация о типе объекта",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Тип объекта"
|
||||||
|
verbose_name_plural = "Типы объектов"
|
||||||
|
ordering = ["name"]
|
||||||
|
|
||||||
|
|
||||||
class ObjectMark(models.Model):
|
class ObjectMark(models.Model):
|
||||||
"""
|
"""
|
||||||
@@ -435,6 +451,15 @@ class Source(models.Model):
|
|||||||
Модель источника сигнала.
|
Модель источника сигнала.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
info = models.ForeignKey(
|
||||||
|
ObjectInfo,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name="source_info",
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
verbose_name="Тип объекта"
|
||||||
|
)
|
||||||
|
|
||||||
coords_average = gis.PointField(
|
coords_average = gis.PointField(
|
||||||
srid=4326,
|
srid=4326,
|
||||||
null=True,
|
null=True,
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
<!-- Satellite Data Modal -->
|
||||||
|
<div class="modal fade" id="satelliteModal" tabindex="-1" aria-labelledby="satelliteModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="satelliteModalLabel">
|
||||||
|
<i class="bi bi-satellite"></i> Информация о спутнике
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" id="satelliteModalBody">
|
||||||
|
<div class="text-center py-4">
|
||||||
|
<div class="spinner-border text-primary" 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 to show satellite modal
|
||||||
|
function showSatelliteModal(satelliteId) {
|
||||||
|
const modal = new bootstrap.Modal(document.getElementById('satelliteModal'));
|
||||||
|
modal.show();
|
||||||
|
|
||||||
|
const modalBody = document.getElementById('satelliteModalBody');
|
||||||
|
modalBody.innerHTML = '<div class="text-center py-4"><div class="spinner-border text-warning" role="status"><span class="visually-hidden">Загрузка...</span></div></div>';
|
||||||
|
|
||||||
|
fetch('/api/satellite/' + satelliteId + '/')
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Ошибка загрузки данных спутника');
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
let html = '<div class="container-fluid"><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>' + data.name + '</strong></td></tr>' +
|
||||||
|
'<tr><td class="text-muted">NORAD ID:</td><td>' + data.norad + '</td></tr>' +
|
||||||
|
'<tr><td class="text-muted">Подспутниковая точка:</td><td><strong>' + data.undersat_point + '</strong></td></tr>' +
|
||||||
|
'<tr><td class="text-muted">Диапазоны:</td><td>' + data.bands + '</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-calendar"></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>' + data.launch_date + '</strong></td></tr>' +
|
||||||
|
'<tr><td class="text-muted">Создан:</td><td>' + data.created_at + '</td></tr>' +
|
||||||
|
'<tr><td class="text-muted">Кем создан:</td><td>' + data.created_by + '</td></tr>' +
|
||||||
|
'<tr><td class="text-muted">Обновлён:</td><td>' + data.updated_at + '</td></tr>' +
|
||||||
|
'<tr><td class="text-muted">Кем обновлён:</td><td>' + data.updated_by + '</td></tr>' +
|
||||||
|
'</tbody></table></div></div></div>';
|
||||||
|
|
||||||
|
if (data.comment && data.comment !== '-') {
|
||||||
|
html += '<div class="col-12"><div class="card">' +
|
||||||
|
'<div class="card-header bg-light"><strong><i class="bi bi-chat-left-text"></i> Комментарий</strong></div>' +
|
||||||
|
'<div class="card-body"><p class="mb-0">' + data.comment + '</p></div></div></div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.url) {
|
||||||
|
html += '<div class="col-12"><div class="card">' +
|
||||||
|
'<div class="card-header bg-light"><strong><i class="bi bi-link-45deg"></i> Ссылка</strong></div>' +
|
||||||
|
'<div class="card-body">' +
|
||||||
|
'<a href="' + data.url + '" target="_blank" class="btn btn-sm btn-outline-primary">' +
|
||||||
|
'<i class="bi bi-box-arrow-up-right"></i> Открыть ссылку</a>' +
|
||||||
|
'</div></div></div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
html += '</div></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>
|
||||||
@@ -113,7 +113,13 @@
|
|||||||
<!-- Фильтры -->
|
<!-- Фильтры -->
|
||||||
<div class="filter-section">
|
<div class="filter-section">
|
||||||
<form method="get" class="row g-3">
|
<form method="get" class="row g-3">
|
||||||
<div class="col-md-6">
|
<div class="col-md-4">
|
||||||
|
<label for="search" class="form-label">Поиск по имени объекта</label>
|
||||||
|
<input type="text" class="form-control" id="search" name="search"
|
||||||
|
placeholder="Введите имя объекта..."
|
||||||
|
value="{{ request.GET.search|default:'' }}">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
<label for="satellite" class="form-label">Спутник</label>
|
<label for="satellite" class="form-label">Спутник</label>
|
||||||
<select class="form-select" id="satellite" name="satellite">
|
<select class="form-select" id="satellite" name="satellite">
|
||||||
<option value="">Все спутники</option>
|
<option value="">Все спутники</option>
|
||||||
@@ -124,7 +130,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6 d-flex align-items-end">
|
<div class="col-md-4 d-flex align-items-end">
|
||||||
<button type="submit" class="btn btn-primary me-2">Применить</button>
|
<button type="submit" class="btn btn-primary me-2">Применить</button>
|
||||||
<a href="{% url 'mainapp:object_marks' %}" class="btn btn-secondary">Сбросить</a>
|
<a href="{% url 'mainapp:object_marks' %}" class="btn btn-secondary">Сбросить</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -315,13 +315,22 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% if item.obj.id %}{% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %}{% url 'mainapp:objitem_update' item.obj.id %}?{{ request.GET.urlencode }}{% else %}{% url 'mainapp:objitem_detail' item.obj.id %}?{{ request.GET.urlencode }}{% endif %}{% endif %}">{{ item.name }}</a></td>
|
<a href="{% if item.obj.id %}{% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %}{% url 'mainapp:objitem_update' item.obj.id %}?{{ request.GET.urlencode }}{% else %}{% url 'mainapp:objitem_detail' item.obj.id %}?{{ request.GET.urlencode }}{% endif %}{% endif %}">{{ item.name }}</a></td>
|
||||||
<td>{{ item.satellite_name }}</td>
|
<td>
|
||||||
|
{% if item.satellite_id %}
|
||||||
|
<a href="#" class="text-decoration-underline"
|
||||||
|
onclick="showSatelliteModal({{ item.satellite_id }}); return false;">
|
||||||
|
{{ item.satellite_name }}
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
{{ item.satellite_name }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% if item.obj.transponder %}
|
{% if item.obj.transponder %}
|
||||||
<a href="#" class="text-success text-decoration-none"
|
<a href="#" class="text-decoration-underline"
|
||||||
onclick="showTransponderModal({{ item.obj.transponder.id }}); return false;"
|
onclick="showTransponderModal({{ item.obj.transponder.id }}); return false;"
|
||||||
title="Показать данные транспондера">
|
title="Показать данные транспондера">
|
||||||
<i class="bi bi-broadcast"></i> {{ item.obj.transponder.downlink }}:{{ item.obj.transponder.frequency_range }}
|
{{ item.obj.transponder.downlink }}:{{ item.obj.transponder.frequency_range }}
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
-
|
-
|
||||||
@@ -1337,4 +1346,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Include the satellite modal component -->
|
||||||
|
{% include 'mainapp/components/_satellite_modal.html' %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -194,6 +194,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="id_info" class="form-label">{{ form.info.label }}:</label>
|
||||||
|
{{ form.info }}
|
||||||
|
{% if form.info.errors %}
|
||||||
|
<div class="invalid-feedback d-block">
|
||||||
|
{{ form.info.errors }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Блок с картой -->
|
<!-- Блок с картой -->
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
<!-- Search bar -->
|
<!-- Search bar -->
|
||||||
<div style="min-width: 200px; flex-grow: 1; max-width: 400px;">
|
<div style="min-width: 200px; flex-grow: 1; max-width: 400px;">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="text" id="toolbar-search" class="form-control" placeholder="Поиск по ID..."
|
<input type="text" id="toolbar-search" class="form-control" placeholder="Поиск по ID или имени..."
|
||||||
value="{{ search_query|default:'' }}">
|
value="{{ search_query|default:'' }}">
|
||||||
<button type="button" class="btn btn-outline-primary"
|
<button type="button" class="btn btn-outline-primary"
|
||||||
onclick="performSearch()">Найти</button>
|
onclick="performSearch()">Найти</button>
|
||||||
@@ -67,6 +67,12 @@
|
|||||||
|
|
||||||
<!-- Action buttons -->
|
<!-- Action buttons -->
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2">
|
||||||
|
<a href="{% url 'mainapp:load_excel_data' %}" class="btn btn-primary btn-sm" title="Загрузка данных из Excel">
|
||||||
|
<i class="bi bi-file-earmark-excel"></i> Excel
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'mainapp:load_csv_data' %}" class="btn btn-success btn-sm" title="Загрузка данных из CSV">
|
||||||
|
<i class="bi bi-file-earmark-text"></i> CSV
|
||||||
|
</a>
|
||||||
{% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %}
|
{% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %}
|
||||||
<button type="button" class="btn btn-danger btn-sm" title="Удалить"
|
<button type="button" class="btn btn-danger btn-sm" title="Удалить"
|
||||||
onclick="deleteSelectedSources()">
|
onclick="deleteSelectedSources()">
|
||||||
@@ -194,7 +200,7 @@
|
|||||||
|
|
||||||
<!-- LyngSat Filter -->
|
<!-- LyngSat Filter -->
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label class="form-label">Тип объекта (ТВ):</label>
|
<label class="form-label">ТВ или нет:</label>
|
||||||
<div>
|
<div>
|
||||||
<div class="form-check form-check-inline">
|
<div class="form-check form-check-inline">
|
||||||
<input class="form-check-input" type="checkbox" name="has_lyngsat" id="has_lyngsat_1"
|
<input class="form-check-input" type="checkbox" name="has_lyngsat" id="has_lyngsat_1"
|
||||||
@@ -209,6 +215,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ObjectInfo Filter -->
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Тип объекта:</label>
|
||||||
|
<div class="d-flex justify-content-between mb-1">
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('info_id', true)">Выбрать</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('info_id', false)">Снять</button>
|
||||||
|
</div>
|
||||||
|
<select name="info_id" class="form-select form-select-sm mb-2" multiple size="4">
|
||||||
|
{% for info in object_infos %}
|
||||||
|
<option value="{{ info.id }}" {% if info.id in selected_info %}selected{% endif %}>
|
||||||
|
{{ info.name }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Point Count Filter -->
|
<!-- Point Count Filter -->
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label class="form-label">Количество точек:</label>
|
<label class="form-label">Количество точек:</label>
|
||||||
@@ -227,6 +251,35 @@
|
|||||||
placeholder="До" value="{{ date_to|default:'' }}">
|
placeholder="До" value="{{ date_to|default:'' }}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Signal Mark Filter -->
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Наличие сигнала:</label>
|
||||||
|
<div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="checkbox" name="has_signal_mark" id="has_signal_mark_1"
|
||||||
|
value="1" {% if has_signal_mark == '1' %}checked{% endif %}>
|
||||||
|
<label class="form-check-label" for="has_signal_mark_1">Есть</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="checkbox" name="has_signal_mark" id="has_signal_mark_0"
|
||||||
|
value="0" {% if has_signal_mark == '0' %}checked{% endif %}>
|
||||||
|
<label class="form-check-label" for="has_signal_mark_0">Нет</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Mark Date Filter -->
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Дата отметки сигнала:</label>
|
||||||
|
<input type="date" name="mark_date_from" id="mark_date_from" class="form-control form-control-sm mb-1"
|
||||||
|
placeholder="От" value="{{ mark_date_from|default:'' }}">
|
||||||
|
<input type="date" name="mark_date_to" id="mark_date_to" class="form-control form-control-sm"
|
||||||
|
placeholder="До" value="{{ mark_date_to|default:'' }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr class="my-3">
|
||||||
|
<h6 class="text-muted mb-2"><i class="bi bi-sliders"></i> Фильтры по параметрам точек</h6>
|
||||||
|
|
||||||
<!-- Geo Timestamp Filter -->
|
<!-- Geo Timestamp Filter -->
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label class="form-label">Дата ГЛ:</label>
|
<label class="form-label">Дата ГЛ:</label>
|
||||||
@@ -236,6 +289,96 @@
|
|||||||
placeholder="До" value="{{ geo_date_to|default:'' }}">
|
placeholder="До" value="{{ geo_date_to|default:'' }}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Polarization Selection - Multi-select -->
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Поляризация:</label>
|
||||||
|
<div class="d-flex justify-content-between mb-1">
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('polarization_id', true)">Выбрать</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('polarization_id', false)">Снять</button>
|
||||||
|
</div>
|
||||||
|
<select name="polarization_id" class="form-select form-select-sm mb-2" multiple size="4">
|
||||||
|
{% for polarization in polarizations %}
|
||||||
|
<option value="{{ polarization.id }}" {% if polarization.id in selected_polarizations %}selected{% endif %}>
|
||||||
|
{{ polarization.name }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modulation Selection - Multi-select -->
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Модуляция:</label>
|
||||||
|
<div class="d-flex justify-content-between mb-1">
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('modulation_id', true)">Выбрать</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('modulation_id', false)">Снять</button>
|
||||||
|
</div>
|
||||||
|
<select name="modulation_id" class="form-select form-select-sm mb-2" multiple size="4">
|
||||||
|
{% for modulation in modulations %}
|
||||||
|
<option value="{{ modulation.id }}" {% if modulation.id in selected_modulations %}selected{% endif %}>
|
||||||
|
{{ modulation.name }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Frequency Filter -->
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Частота, МГц:</label>
|
||||||
|
<input type="number" step="0.001" name="freq_min" class="form-control form-control-sm mb-1"
|
||||||
|
placeholder="От" value="{{ freq_min|default:'' }}">
|
||||||
|
<input type="number" step="0.001" name="freq_max" class="form-control form-control-sm"
|
||||||
|
placeholder="До" value="{{ freq_max|default:'' }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Frequency Range (Bandwidth) Filter -->
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Полоса, МГц:</label>
|
||||||
|
<input type="number" step="0.001" name="freq_range_min" class="form-control form-control-sm mb-1"
|
||||||
|
placeholder="От" value="{{ freq_range_min|default:'' }}">
|
||||||
|
<input type="number" step="0.001" name="freq_range_max" class="form-control form-control-sm"
|
||||||
|
placeholder="До" value="{{ freq_range_max|default:'' }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Symbol Rate Filter -->
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Символьная скорость, БОД:</label>
|
||||||
|
<input type="number" step="0.001" name="bod_velocity_min" class="form-control form-control-sm mb-1"
|
||||||
|
placeholder="От" value="{{ bod_velocity_min|default:'' }}">
|
||||||
|
<input type="number" step="0.001" name="bod_velocity_max" class="form-control form-control-sm"
|
||||||
|
placeholder="До" value="{{ bod_velocity_max|default:'' }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- SNR Filter -->
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">ОСШ, дБ:</label>
|
||||||
|
<input type="number" step="0.1" name="snr_min" class="form-control form-control-sm mb-1"
|
||||||
|
placeholder="От" value="{{ snr_min|default:'' }}">
|
||||||
|
<input type="number" step="0.1" name="snr_max" class="form-control form-control-sm"
|
||||||
|
placeholder="До" value="{{ snr_max|default:'' }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Mirrors Selection - Multi-select -->
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Зеркала:</label>
|
||||||
|
<div class="d-flex justify-content-between mb-1">
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('mirror_id', true)">Выбрать</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('mirror_id', false)">Снять</button>
|
||||||
|
</div>
|
||||||
|
<select name="mirror_id" class="form-select form-select-sm mb-2" multiple size="4">
|
||||||
|
{% for mirror in mirrors %}
|
||||||
|
<option value="{{ mirror.id }}" {% if mirror.id in selected_mirrors %}selected{% endif %}>
|
||||||
|
{{ mirror.name }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Apply Filters and Reset Buttons -->
|
<!-- Apply Filters and Reset Buttons -->
|
||||||
<div class="d-grid gap-2 mt-2">
|
<div class="d-grid gap-2 mt-2">
|
||||||
<button type="submit" class="btn btn-primary btn-sm">Применить</button>
|
<button type="submit" class="btn btn-primary btn-sm">Применить</button>
|
||||||
@@ -267,15 +410,15 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
|
<th scope="col" style="min-width: 150px;">Имя</th>
|
||||||
<th scope="col" style="min-width: 120px;">Спутник</th>
|
<th scope="col" style="min-width: 120px;">Спутник</th>
|
||||||
|
<th scope="col" style="min-width: 120px;">Тип объекта</th>
|
||||||
<th scope="col" style="min-width: 150px;">Усредненные координаты</th>
|
<th scope="col" style="min-width: 150px;">Усредненные координаты</th>
|
||||||
<th scope="col" style="min-width: 150px;">Координаты Кубсата</th>
|
<th scope="col" style="min-width: 150px;">Координаты Кубсата</th>
|
||||||
<th scope="col" style="min-width: 150px;">Координаты оперативников</th>
|
<th scope="col" style="min-width: 150px;">Координаты оперативников</th>
|
||||||
<th scope="col" style="min-width: 150px;">Координаты справочные</th>
|
<th scope="col" style="min-width: 150px;">Координаты справочные</th>
|
||||||
<th scope="col" style="min-width: 180px;">Наличие сигнала</th>
|
<th scope="col" style="min-width: 180px;">Наличие сигнала</th>
|
||||||
{% if has_any_lyngsat %}
|
<th scope="col" class="text-center" style="min-width: 80px;">ТВ или нет</th>
|
||||||
<th scope="col" class="text-center" style="min-width: 80px;">Тип объекта</th>
|
|
||||||
{% endif %}
|
|
||||||
<th scope="col" class="text-center" style="min-width: 100px;">
|
<th scope="col" class="text-center" style="min-width: 100px;">
|
||||||
<a href="javascript:void(0)" onclick="updateSort('objitem_count')" class="text-white text-decoration-none">
|
<a href="javascript:void(0)" onclick="updateSort('objitem_count')" class="text-white text-decoration-none">
|
||||||
Кол-во точек
|
Кол-во точек
|
||||||
@@ -317,7 +460,18 @@
|
|||||||
value="{{ source.id }}">
|
value="{{ source.id }}">
|
||||||
</td>
|
</td>
|
||||||
<td class="text-center">{{ source.id }}</td>
|
<td class="text-center">{{ source.id }}</td>
|
||||||
<td>{{ source.satellite }}</td>
|
<td>{{ source.name }}</td>
|
||||||
|
<td>
|
||||||
|
{% if source.satellite_id %}
|
||||||
|
<a href="#" class="text-decoration-underline"
|
||||||
|
onclick="showSatelliteModal({{ source.satellite_id }}); return false;">
|
||||||
|
{{ source.satellite }}
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
{{ source.satellite }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ source.info }}</td>
|
||||||
<td>{{ source.coords_average }}</td>
|
<td>{{ source.coords_average }}</td>
|
||||||
<td>{{ source.coords_kupsat }}</td>
|
<td>{{ source.coords_kupsat }}</td>
|
||||||
<td>{{ source.coords_valid }}</td>
|
<td>{{ source.coords_valid }}</td>
|
||||||
@@ -345,7 +499,6 @@
|
|||||||
<span class="text-muted">-</span>
|
<span class="text-muted">-</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
{% if has_any_lyngsat %}
|
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
{% if source.has_lyngsat %}
|
{% if source.has_lyngsat %}
|
||||||
<a href="#" class="text-primary text-decoration-none"
|
<a href="#" class="text-primary text-decoration-none"
|
||||||
@@ -356,7 +509,6 @@
|
|||||||
-
|
-
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
|
||||||
<td class="text-center">{{ source.objitem_count }}</td>
|
<td class="text-center">{{ source.objitem_count }}</td>
|
||||||
<td>{{ source.created_at|date:"d.m.Y H:i" }}</td>
|
<td>{{ source.created_at|date:"d.m.Y H:i" }}</td>
|
||||||
<td>{{ source.updated_at|date:"d.m.Y H:i" }}</td>
|
<td>{{ source.updated_at|date:"d.m.Y H:i" }}</td>
|
||||||
@@ -411,7 +563,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="12" class="text-center text-muted">Нет данных для отображения</td>
|
<td colspan="14" class="text-center text-muted">Нет данных для отображения</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -775,6 +927,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
setupRadioLikeCheckboxes('has_coords_valid');
|
setupRadioLikeCheckboxes('has_coords_valid');
|
||||||
setupRadioLikeCheckboxes('has_coords_reference');
|
setupRadioLikeCheckboxes('has_coords_reference');
|
||||||
setupRadioLikeCheckboxes('has_lyngsat');
|
setupRadioLikeCheckboxes('has_lyngsat');
|
||||||
|
setupRadioLikeCheckboxes('has_signal_mark');
|
||||||
|
|
||||||
// Update filter counter on page load
|
// Update filter counter on page load
|
||||||
updateFilterCounter();
|
updateFilterCounter();
|
||||||
@@ -896,10 +1049,10 @@ function showSourceDetails(sourceId) {
|
|||||||
// Build transponder cell
|
// Build transponder cell
|
||||||
let transponderCell = '-';
|
let transponderCell = '-';
|
||||||
if (objitem.has_transponder) {
|
if (objitem.has_transponder) {
|
||||||
transponderCell = '<a href="#" class="text-success text-decoration-none" ' +
|
transponderCell = '<a href="#" class="text-decoration-underline" ' +
|
||||||
'onclick="showTransponderModal(' + objitem.transponder_id + '); return false;" ' +
|
'onclick="showTransponderModal(' + objitem.transponder_id + '); return false;" ' +
|
||||||
'title="Показать данные транспондера">' +
|
'title="Показать данные транспондера">' +
|
||||||
'<i class="bi bi-broadcast"></i> ' + objitem.transponder_info +
|
objitem.transponder_info +
|
||||||
'</a>';
|
'</a>';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -922,12 +1075,21 @@ function showSourceDetails(sourceId) {
|
|||||||
'</a>';
|
'</a>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build satellite cell with link
|
||||||
|
let satelliteCell = objitem.satellite_name;
|
||||||
|
if (objitem.satellite_id) {
|
||||||
|
satelliteCell = '<a href="#" class="text-decoration-underline" ' +
|
||||||
|
'onclick="showSatelliteModal(' + objitem.satellite_id + '); return false;">' +
|
||||||
|
objitem.satellite_name +
|
||||||
|
'</a>';
|
||||||
|
}
|
||||||
|
|
||||||
row.innerHTML = '<td class="text-center">' +
|
row.innerHTML = '<td class="text-center">' +
|
||||||
'<input type="checkbox" class="form-check-input modal-item-checkbox" value="' + objitem.id + '">' +
|
'<input type="checkbox" class="form-check-input modal-item-checkbox" value="' + objitem.id + '">' +
|
||||||
'</td>' +
|
'</td>' +
|
||||||
'<td class="text-center">' + objitem.id + '</td>' +
|
'<td class="text-center">' + objitem.id + '</td>' +
|
||||||
'<td>' + objitem.name + '</td>' +
|
'<td>' + objitem.name + '</td>' +
|
||||||
'<td>' + objitem.satellite_name + '</td>' +
|
'<td>' + satelliteCell + '</td>' +
|
||||||
'<td>' + transponderCell + '</td>' +
|
'<td>' + transponderCell + '</td>' +
|
||||||
'<td>' + objitem.frequency + '</td>' +
|
'<td>' + objitem.frequency + '</td>' +
|
||||||
'<td>' + objitem.freq_range + '</td>' +
|
'<td>' + objitem.freq_range + '</td>' +
|
||||||
@@ -954,8 +1116,13 @@ function showSourceDetails(sourceId) {
|
|||||||
// Setup modal select-all checkbox
|
// Setup modal select-all checkbox
|
||||||
setupModalSelectAll();
|
setupModalSelectAll();
|
||||||
|
|
||||||
// Initialize column visibility
|
// Initialize column visibility after DOM update
|
||||||
|
// Use requestAnimationFrame to ensure DOM is rendered
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
setTimeout(() => {
|
||||||
initModalColumnVisibility();
|
initModalColumnVisibility();
|
||||||
|
}, 50);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// Show no data message
|
// Show no data message
|
||||||
document.getElementById('modalNoData').style.display = 'block';
|
document.getElementById('modalNoData').style.display = 'block';
|
||||||
@@ -1002,22 +1169,28 @@ function setupModalSelectAll() {
|
|||||||
// Function to toggle modal column visibility
|
// Function to toggle modal column visibility
|
||||||
function toggleModalColumn(checkbox) {
|
function toggleModalColumn(checkbox) {
|
||||||
const columnIndex = parseInt(checkbox.getAttribute('data-column'));
|
const columnIndex = parseInt(checkbox.getAttribute('data-column'));
|
||||||
const modal = document.getElementById('sourceDetailsModal');
|
|
||||||
const table = modal.querySelector('.table');
|
// Get the specific tbody for objitems
|
||||||
|
const tbody = document.getElementById('objitemTableBody');
|
||||||
|
if (!tbody) return;
|
||||||
|
|
||||||
|
// Get the parent table
|
||||||
|
const table = tbody.closest('table');
|
||||||
if (!table) return;
|
if (!table) return;
|
||||||
|
|
||||||
const cells = table.querySelectorAll('td:nth-child(' + (columnIndex + 1) + '), th:nth-child(' + (columnIndex + 1) + ')');
|
// Get all rows and toggle specific cell in each
|
||||||
|
const rows = table.querySelectorAll('tr');
|
||||||
|
rows.forEach(row => {
|
||||||
|
const cells = row.children;
|
||||||
|
if (cells[columnIndex]) {
|
||||||
if (checkbox.checked) {
|
if (checkbox.checked) {
|
||||||
cells.forEach(cell => {
|
cells[columnIndex].style.removeProperty('display');
|
||||||
cell.style.display = '';
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
cells.forEach(cell => {
|
cells[columnIndex].style.setProperty('display', 'none', 'important');
|
||||||
cell.style.display = 'none';
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Function to toggle all modal columns
|
// Function to toggle all modal columns
|
||||||
function toggleAllModalColumns(selectAllCheckbox) {
|
function toggleAllModalColumns(selectAllCheckbox) {
|
||||||
@@ -1032,11 +1205,31 @@ function toggleAllModalColumns(selectAllCheckbox) {
|
|||||||
function initModalColumnVisibility() {
|
function initModalColumnVisibility() {
|
||||||
// Hide columns by default: Создано (16), Кем(созд) (17), Комментарий (18), Усреднённое (19), Стандарт (20), Sigma (22)
|
// Hide columns by default: Создано (16), Кем(созд) (17), Комментарий (18), Усреднённое (19), Стандарт (20), Sigma (22)
|
||||||
const columnsToHide = [16, 17, 18, 19, 20, 22];
|
const columnsToHide = [16, 17, 18, 19, 20, 22];
|
||||||
columnsToHide.forEach(columnIndex => {
|
|
||||||
const checkbox = document.querySelector('.modal-column-toggle[data-column="' + columnIndex + '"]');
|
// Get the specific tbody for objitems
|
||||||
if (checkbox && !checkbox.checked) {
|
const tbody = document.getElementById('objitemTableBody');
|
||||||
toggleModalColumn(checkbox);
|
if (!tbody) {
|
||||||
|
console.log('objitemTableBody not found');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the parent table
|
||||||
|
const table = tbody.closest('table');
|
||||||
|
if (!table) {
|
||||||
|
console.log('Table not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide columns that should be hidden by default
|
||||||
|
columnsToHide.forEach(columnIndex => {
|
||||||
|
// Get all rows in the table (including thead and tbody)
|
||||||
|
const rows = table.querySelectorAll('tr');
|
||||||
|
rows.forEach(row => {
|
||||||
|
const cells = row.children;
|
||||||
|
if (cells[columnIndex]) {
|
||||||
|
cells[columnIndex].style.setProperty('display', 'none', 'important');
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1190,4 +1383,7 @@ function showTransponderModal(transponderId) {
|
|||||||
<!-- Include the sigma parameter modal component -->
|
<!-- Include the sigma parameter modal component -->
|
||||||
{% include 'mainapp/components/_sigma_parameter_modal.html' %}
|
{% include 'mainapp/components/_sigma_parameter_modal.html' %}
|
||||||
|
|
||||||
|
<!-- Include the satellite modal component -->
|
||||||
|
{% include 'mainapp/components/_satellite_modal.html' %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -330,7 +330,16 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="text-center">{{ transponder.id }}</td>
|
<td class="text-center">{{ transponder.id }}</td>
|
||||||
<td>{{ transponder.name }}</td>
|
<td>{{ transponder.name }}</td>
|
||||||
<td>{{ transponder.satellite }}</td>
|
<td>
|
||||||
|
{% if transponder.satellite_id %}
|
||||||
|
<a href="#" class="text-decoration-underline"
|
||||||
|
onclick="showSatelliteModal({{ transponder.satellite_id }}); return false;">
|
||||||
|
{{ transponder.satellite }}
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
{{ transponder.satellite }}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
<td>{{ transponder.downlink }}</td>
|
<td>{{ transponder.downlink }}</td>
|
||||||
<td>{{ transponder.uplink }}</td>
|
<td>{{ transponder.uplink }}</td>
|
||||||
<td>{{ transponder.frequency_range }}</td>
|
<td>{{ transponder.frequency_range }}</td>
|
||||||
@@ -574,4 +583,8 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- Include the satellite modal component -->
|
||||||
|
{% include 'mainapp/components/_satellite_modal.html' %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from .views import (
|
|||||||
ObjItemListView,
|
ObjItemListView,
|
||||||
ObjItemUpdateView,
|
ObjItemUpdateView,
|
||||||
ProcessKubsatView,
|
ProcessKubsatView,
|
||||||
|
SatelliteDataAPIView,
|
||||||
ShowMapView,
|
ShowMapView,
|
||||||
ShowSelectedObjectsMapView,
|
ShowSelectedObjectsMapView,
|
||||||
ShowSourcesMapView,
|
ShowSourcesMapView,
|
||||||
@@ -79,6 +80,7 @@ urlpatterns = [
|
|||||||
path('api/sigma-parameter/<int:parameter_id>/', SigmaParameterDataAPIView.as_view(), name='sigma_parameter_data_api'),
|
path('api/sigma-parameter/<int:parameter_id>/', SigmaParameterDataAPIView.as_view(), name='sigma_parameter_data_api'),
|
||||||
path('api/source/<int:source_id>/objitems/', SourceObjItemsAPIView.as_view(), name='source_objitems_api'),
|
path('api/source/<int:source_id>/objitems/', SourceObjItemsAPIView.as_view(), name='source_objitems_api'),
|
||||||
path('api/transponder/<int:transponder_id>/', TransponderDataAPIView.as_view(), name='transponder_data_api'),
|
path('api/transponder/<int:transponder_id>/', TransponderDataAPIView.as_view(), name='transponder_data_api'),
|
||||||
|
path('api/satellite/<int:satellite_id>/', SatelliteDataAPIView.as_view(), name='satellite_data_api'),
|
||||||
path('kubsat-excel/', ProcessKubsatView.as_view(), name='kubsat_excel'),
|
path('kubsat-excel/', ProcessKubsatView.as_view(), name='kubsat_excel'),
|
||||||
path('object/create/', ObjItemCreateView.as_view(), name='objitem_create'),
|
path('object/create/', ObjItemCreateView.as_view(), name='objitem_create'),
|
||||||
path('object/<int:pk>/edit/', ObjItemUpdateView.as_view(), name='objitem_update'),
|
path('object/<int:pk>/edit/', ObjItemUpdateView.as_view(), name='objitem_update'),
|
||||||
|
|||||||
@@ -1205,3 +1205,106 @@ def get_first_param_subquery(field_name: str):
|
|||||||
... print(obj.first_freq)
|
... print(obj.first_freq)
|
||||||
"""
|
"""
|
||||||
return F(f"parameter_obj__{field_name}")
|
return F(f"parameter_obj__{field_name}")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Number Formatting Functions
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
def format_coordinate(value):
|
||||||
|
"""
|
||||||
|
Format coordinate value to 4 decimal places.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: Numeric coordinate value
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Formatted coordinate or '-' if None
|
||||||
|
"""
|
||||||
|
if value is None:
|
||||||
|
return '-'
|
||||||
|
try:
|
||||||
|
return f"{float(value):.4f}"
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return '-'
|
||||||
|
|
||||||
|
|
||||||
|
def format_frequency(value):
|
||||||
|
"""
|
||||||
|
Format frequency value to 3 decimal places.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: Numeric frequency value in MHz
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Formatted frequency or '-' if None
|
||||||
|
"""
|
||||||
|
if value is None:
|
||||||
|
return '-'
|
||||||
|
try:
|
||||||
|
return f"{float(value):.3f}"
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return '-'
|
||||||
|
|
||||||
|
|
||||||
|
def format_symbol_rate(value):
|
||||||
|
"""
|
||||||
|
Format symbol rate (bod_velocity) to integer.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
value: Numeric symbol rate value
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Formatted symbol rate or '-' if None
|
||||||
|
"""
|
||||||
|
if value is None:
|
||||||
|
return '-'
|
||||||
|
try:
|
||||||
|
return f"{float(value):.0f}"
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return '-'
|
||||||
|
|
||||||
|
|
||||||
|
def format_coords_display(point):
|
||||||
|
"""
|
||||||
|
Format geographic point coordinates for display.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
point: GeoDjango Point object
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Formatted coordinates as "LAT LON" or '-' if None
|
||||||
|
"""
|
||||||
|
if not point:
|
||||||
|
return '-'
|
||||||
|
try:
|
||||||
|
longitude = point.coords[0]
|
||||||
|
latitude = point.coords[1]
|
||||||
|
lon = f"{abs(longitude):.4f}E" if longitude > 0 else f"{abs(longitude):.4f}W"
|
||||||
|
lat = f"{abs(latitude):.4f}N" if latitude > 0 else f"{abs(latitude):.4f}S"
|
||||||
|
return f"{lat} {lon}"
|
||||||
|
except (AttributeError, IndexError, TypeError):
|
||||||
|
return '-'
|
||||||
|
|
||||||
|
|
||||||
|
def parse_pagination_params(request):
|
||||||
|
"""
|
||||||
|
Parse pagination parameters from request.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request: Django request object
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: (page_number, items_per_page)
|
||||||
|
"""
|
||||||
|
page_number = request.GET.get("page", 1)
|
||||||
|
items_per_page = request.GET.get("items_per_page", 50)
|
||||||
|
|
||||||
|
try:
|
||||||
|
items_per_page = int(items_per_page)
|
||||||
|
if items_per_page not in [50, 100, 500, 1000]:
|
||||||
|
items_per_page = 50
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
items_per_page = 50
|
||||||
|
|
||||||
|
return page_number, items_per_page
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ from .data_import import (
|
|||||||
from .api import (
|
from .api import (
|
||||||
GetLocationsView,
|
GetLocationsView,
|
||||||
LyngsatDataAPIView,
|
LyngsatDataAPIView,
|
||||||
|
SatelliteDataAPIView,
|
||||||
SigmaParameterDataAPIView,
|
SigmaParameterDataAPIView,
|
||||||
SourceObjItemsAPIView,
|
SourceObjItemsAPIView,
|
||||||
LyngsatTaskStatusAPIView,
|
LyngsatTaskStatusAPIView,
|
||||||
@@ -71,6 +72,7 @@ __all__ = [
|
|||||||
# API
|
# API
|
||||||
'GetLocationsView',
|
'GetLocationsView',
|
||||||
'LyngsatDataAPIView',
|
'LyngsatDataAPIView',
|
||||||
|
'SatelliteDataAPIView',
|
||||||
'SigmaParameterDataAPIView',
|
'SigmaParameterDataAPIView',
|
||||||
'SourceObjItemsAPIView',
|
'SourceObjItemsAPIView',
|
||||||
'LyngsatTaskStatusAPIView',
|
'LyngsatTaskStatusAPIView',
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from django.utils import timezone
|
|||||||
from django.views import View
|
from django.views import View
|
||||||
|
|
||||||
from ..models import ObjItem
|
from ..models import ObjItem
|
||||||
|
from ..utils import format_coordinate, format_coords_display, format_frequency, format_symbol_rate
|
||||||
|
|
||||||
|
|
||||||
class GetLocationsView(LoginRequiredMixin, View):
|
class GetLocationsView(LoginRequiredMixin, View):
|
||||||
@@ -76,11 +77,11 @@ class LyngsatDataAPIView(LoginRequiredMixin, View):
|
|||||||
data = {
|
data = {
|
||||||
'id': lyngsat.id,
|
'id': lyngsat.id,
|
||||||
'satellite': lyngsat.id_satellite.name if lyngsat.id_satellite else '-',
|
'satellite': lyngsat.id_satellite.name if lyngsat.id_satellite else '-',
|
||||||
'frequency': f"{lyngsat.frequency:.3f}" if lyngsat.frequency else '-',
|
'frequency': format_frequency(lyngsat.frequency),
|
||||||
'polarization': lyngsat.polarization.name if lyngsat.polarization else '-',
|
'polarization': lyngsat.polarization.name if lyngsat.polarization else '-',
|
||||||
'modulation': lyngsat.modulation.name if lyngsat.modulation else '-',
|
'modulation': lyngsat.modulation.name if lyngsat.modulation else '-',
|
||||||
'standard': lyngsat.standard.name if lyngsat.standard else '-',
|
'standard': lyngsat.standard.name if lyngsat.standard else '-',
|
||||||
'sym_velocity': f"{lyngsat.sym_velocity:.0f}" if lyngsat.sym_velocity else '-',
|
'sym_velocity': format_symbol_rate(lyngsat.sym_velocity),
|
||||||
'fec': lyngsat.fec or '-',
|
'fec': lyngsat.fec or '-',
|
||||||
'channel_info': lyngsat.channel_info or '-',
|
'channel_info': lyngsat.channel_info or '-',
|
||||||
'last_update': last_update_str,
|
'last_update': last_update_str,
|
||||||
@@ -146,13 +147,13 @@ class SigmaParameterDataAPIView(LoginRequiredMixin, View):
|
|||||||
sigma_data.append({
|
sigma_data.append({
|
||||||
'id': sigma.id,
|
'id': sigma.id,
|
||||||
'satellite': sigma.id_satellite.name if sigma.id_satellite else '-',
|
'satellite': sigma.id_satellite.name if sigma.id_satellite else '-',
|
||||||
'frequency': f"{sigma.frequency:.3f}" if sigma.frequency else '-',
|
'frequency': format_frequency(sigma.frequency),
|
||||||
'transfer_frequency': f"{sigma.transfer_frequency:.3f}" if sigma.transfer_frequency else '-',
|
'transfer_frequency': format_frequency(sigma.transfer_frequency),
|
||||||
'freq_range': f"{sigma.freq_range:.3f}" if sigma.freq_range else '-',
|
'freq_range': format_frequency(sigma.freq_range),
|
||||||
'polarization': sigma.polarization.name if sigma.polarization else '-',
|
'polarization': sigma.polarization.name if sigma.polarization else '-',
|
||||||
'modulation': sigma.modulation.name if sigma.modulation else '-',
|
'modulation': sigma.modulation.name if sigma.modulation else '-',
|
||||||
'standard': sigma.standard.name if sigma.standard else '-',
|
'standard': sigma.standard.name if sigma.standard else '-',
|
||||||
'bod_velocity': f"{sigma.bod_velocity:.0f}" if sigma.bod_velocity else '-',
|
'bod_velocity': format_symbol_rate(sigma.bod_velocity),
|
||||||
'snr': f"{sigma.snr:.1f}" if sigma.snr is not None 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 '-',
|
'power': f"{sigma.power:.1f}" if sigma.power is not None else '-',
|
||||||
'status': sigma.status or '-',
|
'status': sigma.status or '-',
|
||||||
@@ -235,6 +236,7 @@ class SourceObjItemsAPIView(LoginRequiredMixin, View):
|
|||||||
# Get parameter data
|
# Get parameter data
|
||||||
param = getattr(objitem, 'parameter_obj', None)
|
param = getattr(objitem, 'parameter_obj', None)
|
||||||
satellite_name = '-'
|
satellite_name = '-'
|
||||||
|
satellite_id = None
|
||||||
frequency = '-'
|
frequency = '-'
|
||||||
freq_range = '-'
|
freq_range = '-'
|
||||||
polarization = '-'
|
polarization = '-'
|
||||||
@@ -248,11 +250,12 @@ class SourceObjItemsAPIView(LoginRequiredMixin, View):
|
|||||||
parameter_id = param.id
|
parameter_id = param.id
|
||||||
if hasattr(param, 'id_satellite') and param.id_satellite:
|
if hasattr(param, 'id_satellite') and param.id_satellite:
|
||||||
satellite_name = param.id_satellite.name
|
satellite_name = param.id_satellite.name
|
||||||
frequency = f"{param.frequency:.3f}" if param.frequency is not None else '-'
|
satellite_id = param.id_satellite.id
|
||||||
freq_range = f"{param.freq_range:.3f}" if param.freq_range is not None else '-'
|
frequency = format_frequency(param.frequency)
|
||||||
|
freq_range = format_frequency(param.freq_range)
|
||||||
if hasattr(param, 'polarization') and param.polarization:
|
if hasattr(param, 'polarization') and param.polarization:
|
||||||
polarization = param.polarization.name
|
polarization = param.polarization.name
|
||||||
bod_velocity = f"{param.bod_velocity:.0f}" if param.bod_velocity is not None else '-'
|
bod_velocity = format_symbol_rate(param.bod_velocity)
|
||||||
if hasattr(param, 'modulation') and param.modulation:
|
if hasattr(param, 'modulation') and param.modulation:
|
||||||
modulation = param.modulation.name
|
modulation = param.modulation.name
|
||||||
if hasattr(param, 'standard') and param.standard:
|
if hasattr(param, 'standard') and param.standard:
|
||||||
@@ -272,11 +275,7 @@ class SourceObjItemsAPIView(LoginRequiredMixin, View):
|
|||||||
geo_location = objitem.geo_obj.location or '-'
|
geo_location = objitem.geo_obj.location or '-'
|
||||||
|
|
||||||
if objitem.geo_obj.coords:
|
if objitem.geo_obj.coords:
|
||||||
longitude = objitem.geo_obj.coords.coords[0]
|
geo_coords = format_coords_display(objitem.geo_obj.coords)
|
||||||
latitude = objitem.geo_obj.coords.coords[1]
|
|
||||||
lon = f"{longitude}E" if longitude > 0 else f"{abs(longitude)}W"
|
|
||||||
lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S"
|
|
||||||
geo_coords = f"{lat} {lon}"
|
|
||||||
|
|
||||||
# Get created/updated info
|
# Get created/updated info
|
||||||
created_at = '-'
|
created_at = '-'
|
||||||
@@ -332,6 +331,7 @@ class SourceObjItemsAPIView(LoginRequiredMixin, View):
|
|||||||
'id': objitem.id,
|
'id': objitem.id,
|
||||||
'name': objitem.name or '-',
|
'name': objitem.name or '-',
|
||||||
'satellite_name': satellite_name,
|
'satellite_name': satellite_name,
|
||||||
|
'satellite_id': satellite_id,
|
||||||
'frequency': frequency,
|
'frequency': frequency,
|
||||||
'freq_range': freq_range,
|
'freq_range': freq_range,
|
||||||
'polarization': polarization,
|
'polarization': polarization,
|
||||||
@@ -454,12 +454,12 @@ class TransponderDataAPIView(LoginRequiredMixin, View):
|
|||||||
'id': transponder.id,
|
'id': transponder.id,
|
||||||
'name': transponder.name or '-',
|
'name': transponder.name or '-',
|
||||||
'satellite': transponder.sat_id.name if transponder.sat_id else '-',
|
'satellite': transponder.sat_id.name if transponder.sat_id else '-',
|
||||||
'downlink': f"{transponder.downlink:.3f}" if transponder.downlink else '-',
|
'downlink': format_frequency(transponder.downlink),
|
||||||
'uplink': f"{transponder.uplink:.3f}" if transponder.uplink else None,
|
'uplink': format_frequency(transponder.uplink) if transponder.uplink else None,
|
||||||
'frequency_range': f"{transponder.frequency_range:.3f}" if transponder.frequency_range else '-',
|
'frequency_range': format_frequency(transponder.frequency_range),
|
||||||
'polarization': transponder.polarization.name if transponder.polarization else '-',
|
'polarization': transponder.polarization.name if transponder.polarization else '-',
|
||||||
'zone_name': transponder.zone_name or '-',
|
'zone_name': transponder.zone_name or '-',
|
||||||
'transfer': f"{transponder.transfer:.3f}" if transponder.transfer else None,
|
'transfer': format_frequency(transponder.transfer) if transponder.transfer else None,
|
||||||
'snr': f"{transponder.snr:.1f}" if transponder.snr is not None else None,
|
'snr': f"{transponder.snr:.1f}" if transponder.snr is not None else None,
|
||||||
'created_at': created_at_str,
|
'created_at': created_at_str,
|
||||||
'created_by': created_by_str,
|
'created_by': created_by_str,
|
||||||
@@ -470,3 +470,57 @@ class TransponderDataAPIView(LoginRequiredMixin, View):
|
|||||||
return JsonResponse({'error': 'Транспондер не найден'}, status=404)
|
return JsonResponse({'error': 'Транспондер не найден'}, status=404)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return JsonResponse({'error': str(e)}, status=500)
|
return JsonResponse({'error': str(e)}, status=500)
|
||||||
|
|
||||||
|
|
||||||
|
class SatelliteDataAPIView(LoginRequiredMixin, View):
|
||||||
|
"""API endpoint for getting Satellite data."""
|
||||||
|
|
||||||
|
def get(self, request, satellite_id):
|
||||||
|
from ..models import Satellite
|
||||||
|
|
||||||
|
try:
|
||||||
|
satellite = Satellite.objects.prefetch_related(
|
||||||
|
'band',
|
||||||
|
'created_by__user',
|
||||||
|
'updated_by__user'
|
||||||
|
).get(id=satellite_id)
|
||||||
|
|
||||||
|
# Format dates
|
||||||
|
created_at_str = '-'
|
||||||
|
if satellite.created_at:
|
||||||
|
local_time = timezone.localtime(satellite.created_at)
|
||||||
|
created_at_str = local_time.strftime("%d.%m.%Y %H:%M")
|
||||||
|
|
||||||
|
updated_at_str = '-'
|
||||||
|
if satellite.updated_at:
|
||||||
|
local_time = timezone.localtime(satellite.updated_at)
|
||||||
|
updated_at_str = local_time.strftime("%d.%m.%Y %H:%M")
|
||||||
|
|
||||||
|
launch_date_str = '-'
|
||||||
|
if satellite.launch_date:
|
||||||
|
launch_date_str = satellite.launch_date.strftime("%d.%m.%Y")
|
||||||
|
|
||||||
|
# Get bands
|
||||||
|
bands_list = list(satellite.band.values_list('name', flat=True))
|
||||||
|
bands_str = ', '.join(bands_list) if bands_list else '-'
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'id': satellite.id,
|
||||||
|
'name': satellite.name,
|
||||||
|
'norad': satellite.norad if satellite.norad else '-',
|
||||||
|
'undersat_point': f"{satellite.undersat_point}°" if satellite.undersat_point is not None else '-',
|
||||||
|
'bands': bands_str,
|
||||||
|
'launch_date': launch_date_str,
|
||||||
|
'url': satellite.url or None,
|
||||||
|
'comment': satellite.comment or '-',
|
||||||
|
'created_at': created_at_str,
|
||||||
|
'created_by': str(satellite.created_by) if satellite.created_by else '-',
|
||||||
|
'updated_at': updated_at_str,
|
||||||
|
'updated_by': str(satellite.updated_by) if satellite.updated_by else '-',
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonResponse(data)
|
||||||
|
except Satellite.DoesNotExist:
|
||||||
|
return JsonResponse({'error': 'Спутник не найден'}, status=404)
|
||||||
|
except Exception as e:
|
||||||
|
return JsonResponse({'error': str(e)}, status=500)
|
||||||
|
|||||||
@@ -37,6 +37,11 @@ class ObjectMarksListView(LoginRequiredMixin, ListView):
|
|||||||
if satellite_id:
|
if satellite_id:
|
||||||
queryset = queryset.filter(source_objitems__parameter_obj__id_satellite_id=satellite_id).distinct()
|
queryset = queryset.filter(source_objitems__parameter_obj__id_satellite_id=satellite_id).distinct()
|
||||||
|
|
||||||
|
# Поиск по имени объекта
|
||||||
|
search_query = self.request.GET.get('search', '').strip()
|
||||||
|
if search_query:
|
||||||
|
queryset = queryset.filter(source_objitems__name__icontains=search_query).distinct()
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
|||||||
@@ -15,7 +15,13 @@ from django.views.generic import CreateView, DeleteView, UpdateView
|
|||||||
from ..forms import GeoForm, ObjItemForm, ParameterForm
|
from ..forms import GeoForm, ObjItemForm, ParameterForm
|
||||||
from ..mixins import CoordinateProcessingMixin, FormMessageMixin, RoleRequiredMixin
|
from ..mixins import CoordinateProcessingMixin, FormMessageMixin, RoleRequiredMixin
|
||||||
from ..models import Geo, Modulation, ObjItem, Polarization, Satellite
|
from ..models import Geo, Modulation, ObjItem, Polarization, Satellite
|
||||||
from ..utils import parse_pagination_params
|
from ..utils import (
|
||||||
|
format_coordinate,
|
||||||
|
format_coords_display,
|
||||||
|
format_frequency,
|
||||||
|
format_symbol_rate,
|
||||||
|
parse_pagination_params,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class DeleteSelectedObjectsView(RoleRequiredMixin, View):
|
class DeleteSelectedObjectsView(RoleRequiredMixin, View):
|
||||||
@@ -323,13 +329,10 @@ class ObjItemListView(LoginRequiredMixin, View):
|
|||||||
mirrors_list = list(obj.geo_obj.mirrors.values_list('name', flat=True))
|
mirrors_list = list(obj.geo_obj.mirrors.values_list('name', flat=True))
|
||||||
|
|
||||||
if obj.geo_obj.coords:
|
if obj.geo_obj.coords:
|
||||||
longitude = obj.geo_obj.coords.coords[0]
|
geo_coords = format_coords_display(obj.geo_obj.coords)
|
||||||
latitude = obj.geo_obj.coords.coords[1]
|
|
||||||
lon = f"{longitude}E" if longitude > 0 else f"{abs(longitude)}W"
|
|
||||||
lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S"
|
|
||||||
geo_coords = f"{lat} {lon}"
|
|
||||||
|
|
||||||
satellite_name = "-"
|
satellite_name = "-"
|
||||||
|
satellite_id = None
|
||||||
frequency = "-"
|
frequency = "-"
|
||||||
freq_range = "-"
|
freq_range = "-"
|
||||||
polarization_name = "-"
|
polarization_name = "-"
|
||||||
@@ -347,18 +350,11 @@ class ObjItemListView(LoginRequiredMixin, View):
|
|||||||
if hasattr(param.id_satellite, "name")
|
if hasattr(param.id_satellite, "name")
|
||||||
else "-"
|
else "-"
|
||||||
)
|
)
|
||||||
|
satellite_id = param.id_satellite.id
|
||||||
|
|
||||||
frequency = (
|
frequency = format_frequency(param.frequency)
|
||||||
f"{param.frequency:.3f}" if param.frequency is not None else "-"
|
freq_range = format_frequency(param.freq_range)
|
||||||
)
|
bod_velocity = format_symbol_rate(param.bod_velocity)
|
||||||
freq_range = (
|
|
||||||
f"{param.freq_range:.3f}" if param.freq_range is not None else "-"
|
|
||||||
)
|
|
||||||
bod_velocity = (
|
|
||||||
f"{param.bod_velocity:.0f}"
|
|
||||||
if param.bod_velocity is not None
|
|
||||||
else "-"
|
|
||||||
)
|
|
||||||
snr = f"{param.snr:.0f}" if param.snr is not None else "-"
|
snr = f"{param.snr:.0f}" if param.snr is not None else "-"
|
||||||
|
|
||||||
if hasattr(param, "polarization") and param.polarization:
|
if hasattr(param, "polarization") and param.polarization:
|
||||||
@@ -396,8 +392,8 @@ class ObjItemListView(LoginRequiredMixin, View):
|
|||||||
has_sigma = True
|
has_sigma = True
|
||||||
first_sigma = param.sigma_parameter.first()
|
first_sigma = param.sigma_parameter.first()
|
||||||
if first_sigma:
|
if first_sigma:
|
||||||
sigma_freq = f"{first_sigma.transfer_frequency:.3f}" if first_sigma.transfer_frequency else "-"
|
sigma_freq = format_frequency(first_sigma.transfer_frequency)
|
||||||
sigma_range = f"{first_sigma.freq_range:.3f}" if first_sigma.freq_range else "-"
|
sigma_range = format_frequency(first_sigma.freq_range)
|
||||||
sigma_pol = first_sigma.polarization.name if first_sigma.polarization 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_pol_short = sigma_pol[0] if sigma_pol and sigma_pol != "-" else "-"
|
||||||
sigma_info = f"{sigma_freq}/{sigma_range}/{sigma_pol_short}"
|
sigma_info = f"{sigma_freq}/{sigma_range}/{sigma_pol_short}"
|
||||||
@@ -407,6 +403,7 @@ class ObjItemListView(LoginRequiredMixin, View):
|
|||||||
"id": obj.id,
|
"id": obj.id,
|
||||||
"name": obj.name or "-",
|
"name": obj.name or "-",
|
||||||
"satellite_name": satellite_name,
|
"satellite_name": satellite_name,
|
||||||
|
"satellite_id": satellite_id,
|
||||||
"frequency": frequency,
|
"frequency": frequency,
|
||||||
"freq_range": freq_range,
|
"freq_range": freq_range,
|
||||||
"polarization": polarization_name,
|
"polarization": polarization_name,
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from django.views import View
|
|||||||
|
|
||||||
from ..forms import SourceForm
|
from ..forms import SourceForm
|
||||||
from ..models import Source, Satellite
|
from ..models import Source, Satellite
|
||||||
from ..utils import parse_pagination_params
|
from ..utils import format_coords_display, parse_pagination_params
|
||||||
|
|
||||||
|
|
||||||
class SourceListView(LoginRequiredMixin, View):
|
class SourceListView(LoginRequiredMixin, View):
|
||||||
@@ -29,20 +29,38 @@ class SourceListView(LoginRequiredMixin, View):
|
|||||||
# Get sorting parameters (default to ID ascending)
|
# Get sorting parameters (default to ID ascending)
|
||||||
sort_param = request.GET.get("sort", "id")
|
sort_param = request.GET.get("sort", "id")
|
||||||
|
|
||||||
# Get filter parameters
|
# Get filter parameters - Source level
|
||||||
search_query = request.GET.get("search", "").strip()
|
search_query = request.GET.get("search", "").strip()
|
||||||
has_coords_average = request.GET.get("has_coords_average")
|
has_coords_average = request.GET.get("has_coords_average")
|
||||||
has_coords_kupsat = request.GET.get("has_coords_kupsat")
|
has_coords_kupsat = request.GET.get("has_coords_kupsat")
|
||||||
has_coords_valid = request.GET.get("has_coords_valid")
|
has_coords_valid = request.GET.get("has_coords_valid")
|
||||||
has_coords_reference = request.GET.get("has_coords_reference")
|
has_coords_reference = request.GET.get("has_coords_reference")
|
||||||
has_lyngsat = request.GET.get("has_lyngsat")
|
has_lyngsat = request.GET.get("has_lyngsat")
|
||||||
|
selected_info = request.GET.getlist("info_id")
|
||||||
objitem_count_min = request.GET.get("objitem_count_min", "").strip()
|
objitem_count_min = request.GET.get("objitem_count_min", "").strip()
|
||||||
objitem_count_max = request.GET.get("objitem_count_max", "").strip()
|
objitem_count_max = request.GET.get("objitem_count_max", "").strip()
|
||||||
date_from = request.GET.get("date_from", "").strip()
|
date_from = request.GET.get("date_from", "").strip()
|
||||||
date_to = request.GET.get("date_to", "").strip()
|
date_to = request.GET.get("date_to", "").strip()
|
||||||
|
# Signal mark filters
|
||||||
|
has_signal_mark = request.GET.get("has_signal_mark")
|
||||||
|
mark_date_from = request.GET.get("mark_date_from", "").strip()
|
||||||
|
mark_date_to = request.GET.get("mark_date_to", "").strip()
|
||||||
|
|
||||||
|
# Get filter parameters - ObjItem level (параметры точек)
|
||||||
geo_date_from = request.GET.get("geo_date_from", "").strip()
|
geo_date_from = request.GET.get("geo_date_from", "").strip()
|
||||||
geo_date_to = request.GET.get("geo_date_to", "").strip()
|
geo_date_to = request.GET.get("geo_date_to", "").strip()
|
||||||
selected_satellites = request.GET.getlist("satellite_id")
|
selected_satellites = request.GET.getlist("satellite_id")
|
||||||
|
selected_polarizations = request.GET.getlist("polarization_id")
|
||||||
|
selected_modulations = request.GET.getlist("modulation_id")
|
||||||
|
selected_mirrors = request.GET.getlist("mirror_id")
|
||||||
|
freq_min = request.GET.get("freq_min", "").strip()
|
||||||
|
freq_max = request.GET.get("freq_max", "").strip()
|
||||||
|
freq_range_min = request.GET.get("freq_range_min", "").strip()
|
||||||
|
freq_range_max = request.GET.get("freq_range_max", "").strip()
|
||||||
|
bod_velocity_min = request.GET.get("bod_velocity_min", "").strip()
|
||||||
|
bod_velocity_max = request.GET.get("bod_velocity_max", "").strip()
|
||||||
|
snr_min = request.GET.get("snr_min", "").strip()
|
||||||
|
snr_max = request.GET.get("snr_max", "").strip()
|
||||||
|
|
||||||
# Get all satellites for filter
|
# Get all satellites for filter
|
||||||
satellites = (
|
satellites = (
|
||||||
@@ -52,14 +70,44 @@ class SourceListView(LoginRequiredMixin, View):
|
|||||||
.order_by("name")
|
.order_by("name")
|
||||||
)
|
)
|
||||||
|
|
||||||
# Build Q object for geo date filtering
|
# Get all polarizations, modulations for filters
|
||||||
geo_date_q = Q()
|
from ..models import Polarization, Modulation, ObjectInfo
|
||||||
has_geo_date_filter = False
|
polarizations = Polarization.objects.all().order_by("name")
|
||||||
|
modulations = Modulation.objects.all().order_by("name")
|
||||||
|
|
||||||
|
# Get all ObjectInfo for filter
|
||||||
|
object_infos = ObjectInfo.objects.all().order_by("name")
|
||||||
|
|
||||||
|
# Get all satellites that are used as mirrors
|
||||||
|
mirrors = (
|
||||||
|
Satellite.objects.filter(geo_mirrors__isnull=False)
|
||||||
|
.distinct()
|
||||||
|
.only("id", "name")
|
||||||
|
.order_by("name")
|
||||||
|
)
|
||||||
|
|
||||||
|
# Build Q object for filtering objitems in count
|
||||||
|
# This will be used in the annotate to count only objitems that match filters
|
||||||
|
objitem_filter_q = Q()
|
||||||
|
has_objitem_filter = False
|
||||||
|
|
||||||
|
# Check if search is by name (not by ID)
|
||||||
|
search_by_name = False
|
||||||
|
if search_query:
|
||||||
|
try:
|
||||||
|
int(search_query) # Try to parse as ID
|
||||||
|
except ValueError:
|
||||||
|
# Not a number, so it's a name search
|
||||||
|
search_by_name = True
|
||||||
|
objitem_filter_q &= Q(source_objitems__name__icontains=search_query)
|
||||||
|
has_objitem_filter = True
|
||||||
|
|
||||||
|
# Add geo date filter
|
||||||
if geo_date_from:
|
if geo_date_from:
|
||||||
try:
|
try:
|
||||||
geo_date_from_obj = datetime.strptime(geo_date_from, "%Y-%m-%d")
|
geo_date_from_obj = datetime.strptime(geo_date_from, "%Y-%m-%d")
|
||||||
geo_date_q &= Q(source_objitems__geo_obj__timestamp__gte=geo_date_from_obj)
|
objitem_filter_q &= Q(source_objitems__geo_obj__timestamp__gte=geo_date_from_obj)
|
||||||
has_geo_date_filter = True
|
has_objitem_filter = True
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -69,15 +117,105 @@ class SourceListView(LoginRequiredMixin, View):
|
|||||||
geo_date_to_obj = datetime.strptime(geo_date_to, "%Y-%m-%d")
|
geo_date_to_obj = datetime.strptime(geo_date_to, "%Y-%m-%d")
|
||||||
# Add one day to include entire end date
|
# Add one day to include entire end date
|
||||||
geo_date_to_obj = geo_date_to_obj + timedelta(days=1)
|
geo_date_to_obj = geo_date_to_obj + timedelta(days=1)
|
||||||
geo_date_q &= Q(source_objitems__geo_obj__timestamp__lt=geo_date_to_obj)
|
objitem_filter_q &= Q(source_objitems__geo_obj__timestamp__lt=geo_date_to_obj)
|
||||||
has_geo_date_filter = True
|
has_objitem_filter = True
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Add satellite filter to count
|
||||||
|
if selected_satellites:
|
||||||
|
objitem_filter_q &= Q(source_objitems__parameter_obj__id_satellite_id__in=selected_satellites)
|
||||||
|
has_objitem_filter = True
|
||||||
|
|
||||||
|
# Add polarization filter
|
||||||
|
if selected_polarizations:
|
||||||
|
objitem_filter_q &= Q(source_objitems__parameter_obj__polarization_id__in=selected_polarizations)
|
||||||
|
has_objitem_filter = True
|
||||||
|
|
||||||
|
# Add modulation filter
|
||||||
|
if selected_modulations:
|
||||||
|
objitem_filter_q &= Q(source_objitems__parameter_obj__modulation_id__in=selected_modulations)
|
||||||
|
has_objitem_filter = True
|
||||||
|
|
||||||
|
# Add frequency filter
|
||||||
|
if freq_min:
|
||||||
|
try:
|
||||||
|
freq_min_val = float(freq_min)
|
||||||
|
objitem_filter_q &= Q(source_objitems__parameter_obj__frequency__gte=freq_min_val)
|
||||||
|
has_objitem_filter = True
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if freq_max:
|
||||||
|
try:
|
||||||
|
freq_max_val = float(freq_max)
|
||||||
|
objitem_filter_q &= Q(source_objitems__parameter_obj__frequency__lte=freq_max_val)
|
||||||
|
has_objitem_filter = True
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Add frequency range (bandwidth) filter
|
||||||
|
if freq_range_min:
|
||||||
|
try:
|
||||||
|
freq_range_min_val = float(freq_range_min)
|
||||||
|
objitem_filter_q &= Q(source_objitems__parameter_obj__freq_range__gte=freq_range_min_val)
|
||||||
|
has_objitem_filter = True
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if freq_range_max:
|
||||||
|
try:
|
||||||
|
freq_range_max_val = float(freq_range_max)
|
||||||
|
objitem_filter_q &= Q(source_objitems__parameter_obj__freq_range__lte=freq_range_max_val)
|
||||||
|
has_objitem_filter = True
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Add symbol rate (bod_velocity) filter
|
||||||
|
if bod_velocity_min:
|
||||||
|
try:
|
||||||
|
bod_velocity_min_val = float(bod_velocity_min)
|
||||||
|
objitem_filter_q &= Q(source_objitems__parameter_obj__bod_velocity__gte=bod_velocity_min_val)
|
||||||
|
has_objitem_filter = True
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if bod_velocity_max:
|
||||||
|
try:
|
||||||
|
bod_velocity_max_val = float(bod_velocity_max)
|
||||||
|
objitem_filter_q &= Q(source_objitems__parameter_obj__bod_velocity__lte=bod_velocity_max_val)
|
||||||
|
has_objitem_filter = True
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Add SNR filter
|
||||||
|
if snr_min:
|
||||||
|
try:
|
||||||
|
snr_min_val = float(snr_min)
|
||||||
|
objitem_filter_q &= Q(source_objitems__parameter_obj__snr__gte=snr_min_val)
|
||||||
|
has_objitem_filter = True
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if snr_max:
|
||||||
|
try:
|
||||||
|
snr_max_val = float(snr_max)
|
||||||
|
objitem_filter_q &= Q(source_objitems__parameter_obj__snr__lte=snr_max_val)
|
||||||
|
has_objitem_filter = True
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Add mirrors filter
|
||||||
|
if selected_mirrors:
|
||||||
|
objitem_filter_q &= Q(source_objitems__geo_obj__mirrors__id__in=selected_mirrors)
|
||||||
|
has_objitem_filter = True
|
||||||
|
|
||||||
# Get all Source objects with query optimization
|
# Get all Source objects with query optimization
|
||||||
# Using annotate to count ObjItems efficiently (single query with GROUP BY)
|
# Using annotate to count ObjItems efficiently (single query with GROUP BY)
|
||||||
# Using prefetch_related for reverse ForeignKey relationships to avoid N+1 queries
|
# Using prefetch_related for reverse ForeignKey relationships to avoid N+1 queries
|
||||||
sources = Source.objects.prefetch_related(
|
sources = Source.objects.select_related(
|
||||||
|
'info'
|
||||||
|
).prefetch_related(
|
||||||
'source_objitems',
|
'source_objitems',
|
||||||
'source_objitems__parameter_obj',
|
'source_objitems__parameter_obj',
|
||||||
'source_objitems__parameter_obj__id_satellite',
|
'source_objitems__parameter_obj__id_satellite',
|
||||||
@@ -85,7 +223,7 @@ class SourceListView(LoginRequiredMixin, View):
|
|||||||
'marks',
|
'marks',
|
||||||
'marks__created_by__user'
|
'marks__created_by__user'
|
||||||
).annotate(
|
).annotate(
|
||||||
objitem_count=Count('source_objitems', filter=geo_date_q) if has_geo_date_filter else Count('source_objitems')
|
objitem_count=Count('source_objitems', filter=objitem_filter_q, distinct=True) if has_objitem_filter else Count('source_objitems')
|
||||||
)
|
)
|
||||||
|
|
||||||
# Apply filters
|
# Apply filters
|
||||||
@@ -121,6 +259,41 @@ class SourceListView(LoginRequiredMixin, View):
|
|||||||
~Q(source_objitems__lyngsat_source__isnull=False)
|
~Q(source_objitems__lyngsat_source__isnull=False)
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
|
# Filter by ObjectInfo (info field)
|
||||||
|
if selected_info:
|
||||||
|
sources = sources.filter(info_id__in=selected_info)
|
||||||
|
|
||||||
|
# Filter by signal marks
|
||||||
|
if has_signal_mark or mark_date_from or mark_date_to:
|
||||||
|
mark_filter_q = Q()
|
||||||
|
|
||||||
|
# Filter by mark value (signal presence)
|
||||||
|
if has_signal_mark == "1":
|
||||||
|
mark_filter_q &= Q(marks__mark=True)
|
||||||
|
elif has_signal_mark == "0":
|
||||||
|
mark_filter_q &= Q(marks__mark=False)
|
||||||
|
|
||||||
|
# Filter by mark date range
|
||||||
|
if mark_date_from:
|
||||||
|
try:
|
||||||
|
mark_date_from_obj = datetime.strptime(mark_date_from, "%Y-%m-%d")
|
||||||
|
mark_filter_q &= Q(marks__timestamp__gte=mark_date_from_obj)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if mark_date_to:
|
||||||
|
try:
|
||||||
|
from datetime import timedelta
|
||||||
|
mark_date_to_obj = datetime.strptime(mark_date_to, "%Y-%m-%d")
|
||||||
|
# Add one day to include entire end date
|
||||||
|
mark_date_to_obj = mark_date_to_obj + timedelta(days=1)
|
||||||
|
mark_filter_q &= Q(marks__timestamp__lt=mark_date_to_obj)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if mark_filter_q:
|
||||||
|
sources = sources.filter(mark_filter_q).distinct()
|
||||||
|
|
||||||
# Filter by ObjItem count
|
# Filter by ObjItem count
|
||||||
if objitem_count_min:
|
if objitem_count_min:
|
||||||
try:
|
try:
|
||||||
@@ -155,17 +328,36 @@ class SourceListView(LoginRequiredMixin, View):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
# Filter by Geo timestamp range (only filter sources that have matching objitems)
|
# Filter by Geo timestamp range (only filter sources that have matching objitems)
|
||||||
if has_geo_date_filter:
|
if geo_date_from or geo_date_to:
|
||||||
sources = sources.filter(geo_date_q).distinct()
|
geo_filter_q = Q()
|
||||||
|
if geo_date_from:
|
||||||
|
try:
|
||||||
|
geo_date_from_obj = datetime.strptime(geo_date_from, "%Y-%m-%d")
|
||||||
|
geo_filter_q &= Q(source_objitems__geo_obj__timestamp__gte=geo_date_from_obj)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
if geo_date_to:
|
||||||
|
try:
|
||||||
|
from datetime import timedelta
|
||||||
|
geo_date_to_obj = datetime.strptime(geo_date_to, "%Y-%m-%d")
|
||||||
|
geo_date_to_obj = geo_date_to_obj + timedelta(days=1)
|
||||||
|
geo_filter_q &= Q(source_objitems__geo_obj__timestamp__lt=geo_date_to_obj)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
if geo_filter_q:
|
||||||
|
sources = sources.filter(geo_filter_q).distinct()
|
||||||
|
|
||||||
# Search by ID
|
# Search by ID or name
|
||||||
if search_query:
|
if search_query:
|
||||||
try:
|
try:
|
||||||
|
# Try to search by ID first
|
||||||
search_id = int(search_query)
|
search_id = int(search_query)
|
||||||
sources = sources.filter(id=search_id)
|
sources = sources.filter(id=search_id)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# If not a number, ignore
|
# If not a number, search by name in related objitems
|
||||||
pass
|
sources = sources.filter(
|
||||||
|
source_objitems__name__icontains=search_query
|
||||||
|
).distinct()
|
||||||
|
|
||||||
# Filter by satellites
|
# Filter by satellites
|
||||||
if selected_satellites:
|
if selected_satellites:
|
||||||
@@ -173,6 +365,84 @@ class SourceListView(LoginRequiredMixin, View):
|
|||||||
source_objitems__parameter_obj__id_satellite_id__in=selected_satellites
|
source_objitems__parameter_obj__id_satellite_id__in=selected_satellites
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
|
# Filter by polarizations
|
||||||
|
if selected_polarizations:
|
||||||
|
sources = sources.filter(
|
||||||
|
source_objitems__parameter_obj__polarization_id__in=selected_polarizations
|
||||||
|
).distinct()
|
||||||
|
|
||||||
|
# Filter by modulations
|
||||||
|
if selected_modulations:
|
||||||
|
sources = sources.filter(
|
||||||
|
source_objitems__parameter_obj__modulation_id__in=selected_modulations
|
||||||
|
).distinct()
|
||||||
|
|
||||||
|
# Filter by frequency range
|
||||||
|
if freq_min:
|
||||||
|
try:
|
||||||
|
freq_min_val = float(freq_min)
|
||||||
|
sources = sources.filter(source_objitems__parameter_obj__frequency__gte=freq_min_val).distinct()
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if freq_max:
|
||||||
|
try:
|
||||||
|
freq_max_val = float(freq_max)
|
||||||
|
sources = sources.filter(source_objitems__parameter_obj__frequency__lte=freq_max_val).distinct()
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Filter by frequency range (bandwidth)
|
||||||
|
if freq_range_min:
|
||||||
|
try:
|
||||||
|
freq_range_min_val = float(freq_range_min)
|
||||||
|
sources = sources.filter(source_objitems__parameter_obj__freq_range__gte=freq_range_min_val).distinct()
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if freq_range_max:
|
||||||
|
try:
|
||||||
|
freq_range_max_val = float(freq_range_max)
|
||||||
|
sources = sources.filter(source_objitems__parameter_obj__freq_range__lte=freq_range_max_val).distinct()
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Filter by symbol rate
|
||||||
|
if bod_velocity_min:
|
||||||
|
try:
|
||||||
|
bod_velocity_min_val = float(bod_velocity_min)
|
||||||
|
sources = sources.filter(source_objitems__parameter_obj__bod_velocity__gte=bod_velocity_min_val).distinct()
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if bod_velocity_max:
|
||||||
|
try:
|
||||||
|
bod_velocity_max_val = float(bod_velocity_max)
|
||||||
|
sources = sources.filter(source_objitems__parameter_obj__bod_velocity__lte=bod_velocity_max_val).distinct()
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Filter by SNR
|
||||||
|
if snr_min:
|
||||||
|
try:
|
||||||
|
snr_min_val = float(snr_min)
|
||||||
|
sources = sources.filter(source_objitems__parameter_obj__snr__gte=snr_min_val).distinct()
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if snr_max:
|
||||||
|
try:
|
||||||
|
snr_max_val = float(snr_max)
|
||||||
|
sources = sources.filter(source_objitems__parameter_obj__snr__lte=snr_max_val).distinct()
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Filter by mirrors
|
||||||
|
if selected_mirrors:
|
||||||
|
sources = sources.filter(
|
||||||
|
source_objitems__geo_obj__mirrors__id__in=selected_mirrors
|
||||||
|
).distinct()
|
||||||
|
|
||||||
# Apply sorting
|
# Apply sorting
|
||||||
valid_sort_fields = {
|
valid_sort_fields = {
|
||||||
"id": "id",
|
"id": "id",
|
||||||
@@ -194,27 +464,18 @@ class SourceListView(LoginRequiredMixin, View):
|
|||||||
|
|
||||||
# Prepare data for display
|
# Prepare data for display
|
||||||
processed_sources = []
|
processed_sources = []
|
||||||
has_any_lyngsat = False # Track if any source has LyngSat data
|
|
||||||
|
|
||||||
for source in page_obj:
|
for source in page_obj:
|
||||||
# Format coordinates
|
# Format coordinates
|
||||||
def format_coords(point):
|
coords_average_str = format_coords_display(source.coords_average)
|
||||||
if point:
|
coords_kupsat_str = format_coords_display(source.coords_kupsat)
|
||||||
longitude = point.coords[0]
|
coords_valid_str = format_coords_display(source.coords_valid)
|
||||||
latitude = point.coords[1]
|
coords_reference_str = format_coords_display(source.coords_reference)
|
||||||
lon = f"{longitude}E" if longitude > 0 else f"{abs(longitude)}W"
|
|
||||||
lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S"
|
|
||||||
return f"{lat} {lon}"
|
|
||||||
return "-"
|
|
||||||
|
|
||||||
coords_average_str = format_coords(source.coords_average)
|
# Filter objitems for display (to get satellites and lyngsat info)
|
||||||
coords_kupsat_str = format_coords(source.coords_kupsat)
|
|
||||||
coords_valid_str = format_coords(source.coords_valid)
|
|
||||||
coords_reference_str = format_coords(source.coords_reference)
|
|
||||||
|
|
||||||
# Filter objitems by geo date if filter is applied
|
|
||||||
objitems_to_display = source.source_objitems.all()
|
objitems_to_display = source.source_objitems.all()
|
||||||
if geo_date_from or geo_date_to:
|
|
||||||
|
# Apply the same filters as in the count annotation
|
||||||
if geo_date_from:
|
if geo_date_from:
|
||||||
try:
|
try:
|
||||||
geo_date_from_obj = datetime.strptime(geo_date_from, "%Y-%m-%d")
|
geo_date_from_obj = datetime.strptime(geo_date_from, "%Y-%m-%d")
|
||||||
@@ -229,27 +490,85 @@ class SourceListView(LoginRequiredMixin, View):
|
|||||||
objitems_to_display = objitems_to_display.filter(geo_obj__timestamp__lt=geo_date_to_obj)
|
objitems_to_display = objitems_to_display.filter(geo_obj__timestamp__lt=geo_date_to_obj)
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
if selected_satellites:
|
||||||
|
objitems_to_display = objitems_to_display.filter(parameter_obj__id_satellite_id__in=selected_satellites)
|
||||||
|
if selected_polarizations:
|
||||||
|
objitems_to_display = objitems_to_display.filter(parameter_obj__polarization_id__in=selected_polarizations)
|
||||||
|
if selected_modulations:
|
||||||
|
objitems_to_display = objitems_to_display.filter(parameter_obj__modulation_id__in=selected_modulations)
|
||||||
|
if freq_min:
|
||||||
|
try:
|
||||||
|
objitems_to_display = objitems_to_display.filter(parameter_obj__frequency__gte=float(freq_min))
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
if freq_max:
|
||||||
|
try:
|
||||||
|
objitems_to_display = objitems_to_display.filter(parameter_obj__frequency__lte=float(freq_max))
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
if freq_range_min:
|
||||||
|
try:
|
||||||
|
objitems_to_display = objitems_to_display.filter(parameter_obj__freq_range__gte=float(freq_range_min))
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
if freq_range_max:
|
||||||
|
try:
|
||||||
|
objitems_to_display = objitems_to_display.filter(parameter_obj__freq_range__lte=float(freq_range_max))
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
if bod_velocity_min:
|
||||||
|
try:
|
||||||
|
objitems_to_display = objitems_to_display.filter(parameter_obj__bod_velocity__gte=float(bod_velocity_min))
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
if bod_velocity_max:
|
||||||
|
try:
|
||||||
|
objitems_to_display = objitems_to_display.filter(parameter_obj__bod_velocity__lte=float(bod_velocity_max))
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
if snr_min:
|
||||||
|
try:
|
||||||
|
objitems_to_display = objitems_to_display.filter(parameter_obj__snr__gte=float(snr_min))
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
if snr_max:
|
||||||
|
try:
|
||||||
|
objitems_to_display = objitems_to_display.filter(parameter_obj__snr__lte=float(snr_max))
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
if selected_mirrors:
|
||||||
|
objitems_to_display = objitems_to_display.filter(geo_obj__mirrors__id__in=selected_mirrors)
|
||||||
|
if search_by_name:
|
||||||
|
objitems_to_display = objitems_to_display.filter(name__icontains=search_query)
|
||||||
|
|
||||||
# Get count of related ObjItems (filtered)
|
# Use annotated count (consistent with filtering)
|
||||||
objitem_count = objitems_to_display.count()
|
objitem_count = source.objitem_count
|
||||||
|
|
||||||
# Get satellites for this source and check for LyngSat
|
# Get satellites, name and check for LyngSat
|
||||||
satellite_names = set()
|
satellite_names = set()
|
||||||
|
satellite_ids = set()
|
||||||
has_lyngsat = False
|
has_lyngsat = False
|
||||||
lyngsat_id = None
|
lyngsat_id = None
|
||||||
|
source_name = None
|
||||||
|
|
||||||
for objitem in objitems_to_display:
|
for objitem in objitems_to_display:
|
||||||
|
# Get name from first objitem
|
||||||
|
if source_name is None and objitem.name:
|
||||||
|
source_name = objitem.name
|
||||||
|
|
||||||
if hasattr(objitem, 'parameter_obj') and objitem.parameter_obj:
|
if hasattr(objitem, 'parameter_obj') and objitem.parameter_obj:
|
||||||
if hasattr(objitem.parameter_obj, 'id_satellite') and objitem.parameter_obj.id_satellite:
|
if hasattr(objitem.parameter_obj, 'id_satellite') and objitem.parameter_obj.id_satellite:
|
||||||
satellite_names.add(objitem.parameter_obj.id_satellite.name)
|
satellite_names.add(objitem.parameter_obj.id_satellite.name)
|
||||||
|
satellite_ids.add(objitem.parameter_obj.id_satellite.id)
|
||||||
|
|
||||||
# Check if any objitem has LyngSat
|
# Check if any objitem has LyngSat
|
||||||
if hasattr(objitem, 'lyngsat_source') and objitem.lyngsat_source:
|
if hasattr(objitem, 'lyngsat_source') and objitem.lyngsat_source:
|
||||||
has_lyngsat = True
|
has_lyngsat = True
|
||||||
lyngsat_id = objitem.lyngsat_source.id
|
lyngsat_id = objitem.lyngsat_source.id
|
||||||
has_any_lyngsat = True
|
|
||||||
|
|
||||||
satellite_str = ", ".join(sorted(satellite_names)) if satellite_names else "-"
|
satellite_str = ", ".join(sorted(satellite_names)) if satellite_names else "-"
|
||||||
|
# Get first satellite ID for modal link (if multiple satellites, use first one)
|
||||||
|
first_satellite_id = min(satellite_ids) if satellite_ids else None
|
||||||
|
|
||||||
# Get all marks (presence/absence)
|
# Get all marks (presence/absence)
|
||||||
marks_data = []
|
marks_data = []
|
||||||
@@ -260,14 +579,20 @@ class SourceListView(LoginRequiredMixin, View):
|
|||||||
'created_by': str(mark.created_by) if mark.created_by else '-',
|
'created_by': str(mark.created_by) if mark.created_by else '-',
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# Get info name
|
||||||
|
info_name = source.info.name if source.info else '-'
|
||||||
|
|
||||||
processed_sources.append({
|
processed_sources.append({
|
||||||
'id': source.id,
|
'id': source.id,
|
||||||
|
'name': source_name if source_name else '-',
|
||||||
|
'info': info_name,
|
||||||
'coords_average': coords_average_str,
|
'coords_average': coords_average_str,
|
||||||
'coords_kupsat': coords_kupsat_str,
|
'coords_kupsat': coords_kupsat_str,
|
||||||
'coords_valid': coords_valid_str,
|
'coords_valid': coords_valid_str,
|
||||||
'coords_reference': coords_reference_str,
|
'coords_reference': coords_reference_str,
|
||||||
'objitem_count': objitem_count,
|
'objitem_count': objitem_count,
|
||||||
'satellite': satellite_str,
|
'satellite': satellite_str,
|
||||||
|
'satellite_id': first_satellite_id,
|
||||||
'created_at': source.created_at,
|
'created_at': source.created_at,
|
||||||
'updated_at': source.updated_at,
|
'updated_at': source.updated_at,
|
||||||
'has_lyngsat': has_lyngsat,
|
'has_lyngsat': has_lyngsat,
|
||||||
@@ -283,22 +608,50 @@ class SourceListView(LoginRequiredMixin, View):
|
|||||||
'available_items_per_page': [50, 100, 500, 1000],
|
'available_items_per_page': [50, 100, 500, 1000],
|
||||||
'sort': sort_param,
|
'sort': sort_param,
|
||||||
'search_query': search_query,
|
'search_query': search_query,
|
||||||
|
# Source-level filters
|
||||||
'has_coords_average': has_coords_average,
|
'has_coords_average': has_coords_average,
|
||||||
'has_coords_kupsat': has_coords_kupsat,
|
'has_coords_kupsat': has_coords_kupsat,
|
||||||
'has_coords_valid': has_coords_valid,
|
'has_coords_valid': has_coords_valid,
|
||||||
'has_coords_reference': has_coords_reference,
|
'has_coords_reference': has_coords_reference,
|
||||||
'has_lyngsat': has_lyngsat,
|
'has_lyngsat': has_lyngsat,
|
||||||
'has_any_lyngsat': has_any_lyngsat,
|
'selected_info': [
|
||||||
|
int(x) if isinstance(x, str) else x for x in selected_info if (isinstance(x, int) or (isinstance(x, str) and x.isdigit()))
|
||||||
|
],
|
||||||
'objitem_count_min': objitem_count_min,
|
'objitem_count_min': objitem_count_min,
|
||||||
'objitem_count_max': objitem_count_max,
|
'objitem_count_max': objitem_count_max,
|
||||||
'date_from': date_from,
|
'date_from': date_from,
|
||||||
'date_to': date_to,
|
'date_to': date_to,
|
||||||
|
'has_signal_mark': has_signal_mark,
|
||||||
|
'mark_date_from': mark_date_from,
|
||||||
|
'mark_date_to': mark_date_to,
|
||||||
|
# ObjItem-level filters
|
||||||
'geo_date_from': geo_date_from,
|
'geo_date_from': geo_date_from,
|
||||||
'geo_date_to': geo_date_to,
|
'geo_date_to': geo_date_to,
|
||||||
'satellites': satellites,
|
'satellites': satellites,
|
||||||
'selected_satellites': [
|
'selected_satellites': [
|
||||||
int(x) if isinstance(x, str) else x for x in selected_satellites if (isinstance(x, int) or (isinstance(x, str) and x.isdigit()))
|
int(x) if isinstance(x, str) else x for x in selected_satellites if (isinstance(x, int) or (isinstance(x, str) and x.isdigit()))
|
||||||
],
|
],
|
||||||
|
'polarizations': polarizations,
|
||||||
|
'selected_polarizations': [
|
||||||
|
int(x) if isinstance(x, str) else x for x in selected_polarizations if (isinstance(x, int) or (isinstance(x, str) and x.isdigit()))
|
||||||
|
],
|
||||||
|
'modulations': modulations,
|
||||||
|
'selected_modulations': [
|
||||||
|
int(x) if isinstance(x, str) else x for x in selected_modulations if (isinstance(x, int) or (isinstance(x, str) and x.isdigit()))
|
||||||
|
],
|
||||||
|
'freq_min': freq_min,
|
||||||
|
'freq_max': freq_max,
|
||||||
|
'freq_range_min': freq_range_min,
|
||||||
|
'freq_range_max': freq_range_max,
|
||||||
|
'bod_velocity_min': bod_velocity_min,
|
||||||
|
'bod_velocity_max': bod_velocity_max,
|
||||||
|
'snr_min': snr_min,
|
||||||
|
'snr_max': snr_max,
|
||||||
|
'mirrors': mirrors,
|
||||||
|
'selected_mirrors': [
|
||||||
|
int(x) if isinstance(x, str) else x for x in selected_mirrors if (isinstance(x, int) or (isinstance(x, str) and x.isdigit()))
|
||||||
|
],
|
||||||
|
'object_infos': object_infos,
|
||||||
'full_width_page': True,
|
'full_width_page': True,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -197,6 +197,7 @@ class TransponderListView(LoginRequiredMixin, View):
|
|||||||
'id': transponder.id,
|
'id': transponder.id,
|
||||||
'name': transponder.name or "-",
|
'name': transponder.name or "-",
|
||||||
'satellite': transponder.sat_id.name if transponder.sat_id else "-",
|
'satellite': transponder.sat_id.name if transponder.sat_id else "-",
|
||||||
|
'satellite_id': transponder.sat_id.id if transponder.sat_id else None,
|
||||||
'downlink': f"{transponder.downlink:.3f}" if transponder.downlink else "-",
|
'downlink': f"{transponder.downlink:.3f}" if transponder.downlink else "-",
|
||||||
'uplink': f"{transponder.uplink:.3f}" if transponder.uplink else "-",
|
'uplink': f"{transponder.uplink:.3f}" if transponder.uplink else "-",
|
||||||
'frequency_range': f"{transponder.frequency_range:.3f}" if transponder.frequency_range else "-",
|
'frequency_range': f"{transponder.frequency_range:.3f}" if transponder.frequency_range else "-",
|
||||||
|
|||||||
Reference in New Issue
Block a user