From b889fb29a3b12c8e76b30aad16af0ef029ae1f91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=BE=D1=88=D0=BA=D0=B8=D0=BD=20=D0=A1=D0=B5=D1=80?= =?UTF-8?q?=D0=B3=D0=B5=D0=B9?= Date: Mon, 17 Nov 2025 15:54:27 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=B8=D0=BD=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=86=D0=B8=D1=8E=20?= =?UTF-8?q?=D0=BE=20=D1=82=D0=B8=D0=BF=D0=B5=20=D0=BE=D0=B1=D1=8A=D0=B5?= =?UTF-8?q?=D0=BA=D1=82=D0=B0.=20=D0=9F=D1=80=D0=BE=D1=81=D1=82=D0=BE=20?= =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../templates/lyngsatapp/lyngsat_list.html | 30 +- dbapp/lyngsatapp/views.py | 6 +- dbapp/mainapp/admin.py | 26 +- dbapp/mainapp/forms.py | 11 +- .../migrations/0008_objectinfo_source_info.py | 31 ++ dbapp/mainapp/models.py | 25 + .../mainapp/components/_satellite_modal.html | 84 ++++ .../templates/mainapp/object_marks.html | 10 +- .../templates/mainapp/objitem_list.html | 18 +- .../templates/mainapp/source_form.html | 13 + .../templates/mainapp/source_list.html | 258 ++++++++-- .../templates/mainapp/transponder_list.html | 15 +- dbapp/mainapp/urls.py | 2 + dbapp/mainapp/utils.py | 103 ++++ dbapp/mainapp/views/__init__.py | 2 + dbapp/mainapp/views/api.py | 90 +++- dbapp/mainapp/views/marks.py | 5 + dbapp/mainapp/views/objitem.py | 35 +- dbapp/mainapp/views/source.py | 455 ++++++++++++++++-- dbapp/mainapp/views/transponder.py | 1 + 20 files changed, 1086 insertions(+), 134 deletions(-) create mode 100644 dbapp/mainapp/migrations/0008_objectinfo_source_info.py create mode 100644 dbapp/mainapp/templates/mainapp/components/_satellite_modal.html diff --git a/dbapp/lyngsatapp/templates/lyngsatapp/lyngsat_list.html b/dbapp/lyngsatapp/templates/lyngsatapp/lyngsat_list.html index df54ca6..2cf649d 100644 --- a/dbapp/lyngsatapp/templates/lyngsatapp/lyngsat_list.html +++ b/dbapp/lyngsatapp/templates/lyngsatapp/lyngsat_list.html @@ -55,6 +55,19 @@ + +
+ + Добавить данные + + + Привязать + + + Отвязать + +
+
+
+ + + + + + + diff --git a/dbapp/mainapp/templates/mainapp/object_marks.html b/dbapp/mainapp/templates/mainapp/object_marks.html index 26fbc75..be10c29 100644 --- a/dbapp/mainapp/templates/mainapp/object_marks.html +++ b/dbapp/mainapp/templates/mainapp/object_marks.html @@ -113,7 +113,13 @@
-
+
+ + +
+
-
+
Сбросить
diff --git a/dbapp/mainapp/templates/mainapp/objitem_list.html b/dbapp/mainapp/templates/mainapp/objitem_list.html index 5181dd4..e07c6ec 100644 --- a/dbapp/mainapp/templates/mainapp/objitem_list.html +++ b/dbapp/mainapp/templates/mainapp/objitem_list.html @@ -315,13 +315,22 @@ {{ item.name }} - {{ item.satellite_name }} + + {% if item.satellite_id %} + + {{ item.satellite_name }} + + {% else %} + {{ item.satellite_name }} + {% endif %} + {% if item.obj.transponder %} - - {{ item.obj.transponder.downlink }}:{{ item.obj.transponder.frequency_range }} + {{ item.obj.transponder.downlink }}:{{ item.obj.transponder.frequency_range }} {% else %} - @@ -1337,4 +1346,7 @@
+ +{% include 'mainapp/components/_satellite_modal.html' %} + {% endblock %} \ No newline at end of file diff --git a/dbapp/mainapp/templates/mainapp/source_form.html b/dbapp/mainapp/templates/mainapp/source_form.html index 7476c70..e619f50 100644 --- a/dbapp/mainapp/templates/mainapp/source_form.html +++ b/dbapp/mainapp/templates/mainapp/source_form.html @@ -194,6 +194,19 @@
+
+
+
+ + {{ form.info }} + {% if form.info.errors %} +
+ {{ form.info.errors }} +
+ {% endif %} +
+
+
diff --git a/dbapp/mainapp/templates/mainapp/source_list.html b/dbapp/mainapp/templates/mainapp/source_list.html index b1c6ad1..1368672 100644 --- a/dbapp/mainapp/templates/mainapp/source_list.html +++ b/dbapp/mainapp/templates/mainapp/source_list.html @@ -42,7 +42,7 @@
- @@ -67,6 +67,12 @@
+ + Excel + + + CSV + {% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %} + +
+ +
+
@@ -227,6 +251,35 @@ placeholder="До" value="{{ date_to|default:'' }}">
+ +
+ +
+
+ + +
+
+ + +
+
+
+ + +
+ + + +
+ +
+
Фильтры по параметрам точек
+
@@ -236,6 +289,96 @@ placeholder="До" value="{{ geo_date_to|default:'' }}">
+ +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ + + +
+ + +
+ +
+ + +
+ +
+
@@ -267,15 +410,15 @@ {% endif %} + Имя Спутник + Тип объекта Усредненные координаты Координаты Кубсата Координаты оперативников Координаты справочные Наличие сигнала - {% if has_any_lyngsat %} - Тип объекта - {% endif %} + ТВ или нет Кол-во точек @@ -317,7 +460,18 @@ value="{{ source.id }}"> {{ source.id }} - {{ source.satellite }} + {{ source.name }} + + {% if source.satellite_id %} + + {{ source.satellite }} + + {% else %} + {{ source.satellite }} + {% endif %} + + {{ source.info }} {{ source.coords_average }} {{ source.coords_kupsat }} {{ source.coords_valid }} @@ -345,7 +499,6 @@ - {% endif %} - {% if has_any_lyngsat %} {% if source.has_lyngsat %} - {% endif %} {{ source.objitem_count }} {{ source.created_at|date:"d.m.Y H:i" }} {{ source.updated_at|date:"d.m.Y H:i" }} @@ -411,7 +563,7 @@ {% empty %} - Нет данных для отображения + Нет данных для отображения {% endfor %} @@ -775,6 +927,7 @@ document.addEventListener('DOMContentLoaded', function() { setupRadioLikeCheckboxes('has_coords_valid'); setupRadioLikeCheckboxes('has_coords_reference'); setupRadioLikeCheckboxes('has_lyngsat'); + setupRadioLikeCheckboxes('has_signal_mark'); // Update filter counter on page load updateFilterCounter(); @@ -896,10 +1049,10 @@ function showSourceDetails(sourceId) { // Build transponder cell let transponderCell = '-'; if (objitem.has_transponder) { - transponderCell = '' + - ' ' + objitem.transponder_info + + objitem.transponder_info + ''; } @@ -922,12 +1075,21 @@ function showSourceDetails(sourceId) { ''; } + // Build satellite cell with link + let satelliteCell = objitem.satellite_name; + if (objitem.satellite_id) { + satelliteCell = '' + + objitem.satellite_name + + ''; + } + row.innerHTML = '' + '' + '' + '' + objitem.id + '' + '' + objitem.name + '' + - '' + objitem.satellite_name + '' + + '' + satelliteCell + '' + '' + transponderCell + '' + '' + objitem.frequency + '' + '' + objitem.freq_range + '' + @@ -954,8 +1116,13 @@ function showSourceDetails(sourceId) { // Setup modal select-all checkbox setupModalSelectAll(); - // Initialize column visibility - initModalColumnVisibility(); + // Initialize column visibility after DOM update + // Use requestAnimationFrame to ensure DOM is rendered + requestAnimationFrame(() => { + setTimeout(() => { + initModalColumnVisibility(); + }, 50); + }); } else { // Show no data message document.getElementById('modalNoData').style.display = 'block'; @@ -1002,21 +1169,27 @@ function setupModalSelectAll() { // Function to toggle modal column visibility function toggleModalColumn(checkbox) { 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; - const cells = table.querySelectorAll('td:nth-child(' + (columnIndex + 1) + '), th:nth-child(' + (columnIndex + 1) + ')'); - - if (checkbox.checked) { - cells.forEach(cell => { - cell.style.display = ''; - }); - } else { - cells.forEach(cell => { - cell.style.display = 'none'; - }); - } + // 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) { + cells[columnIndex].style.removeProperty('display'); + } else { + cells[columnIndex].style.setProperty('display', 'none', 'important'); + } + } + }); } // Function to toggle all modal columns @@ -1032,11 +1205,31 @@ function toggleAllModalColumns(selectAllCheckbox) { function initModalColumnVisibility() { // Hide columns by default: Создано (16), Кем(созд) (17), Комментарий (18), Усреднённое (19), Стандарт (20), Sigma (22) const columnsToHide = [16, 17, 18, 19, 20, 22]; + + // Get the specific tbody for objitems + const tbody = document.getElementById('objitemTableBody'); + 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 => { - const checkbox = document.querySelector('.modal-column-toggle[data-column="' + columnIndex + '"]'); - if (checkbox && !checkbox.checked) { - toggleModalColumn(checkbox); - } + // 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 'mainapp/components/_sigma_parameter_modal.html' %} + +{% include 'mainapp/components/_satellite_modal.html' %} + {% endblock %} diff --git a/dbapp/mainapp/templates/mainapp/transponder_list.html b/dbapp/mainapp/templates/mainapp/transponder_list.html index 4e5399a..1e81480 100644 --- a/dbapp/mainapp/templates/mainapp/transponder_list.html +++ b/dbapp/mainapp/templates/mainapp/transponder_list.html @@ -330,7 +330,16 @@ {{ transponder.id }} {{ transponder.name }} - {{ transponder.satellite }} + + {% if transponder.satellite_id %} + + {{ transponder.satellite }} + + {% else %} + {{ transponder.satellite }} + {% endif %} + {{ transponder.downlink }} {{ transponder.uplink }} {{ transponder.frequency_range }} @@ -574,4 +583,8 @@ document.addEventListener('DOMContentLoaded', function() { } }); + + +{% include 'mainapp/components/_satellite_modal.html' %} + {% endblock %} diff --git a/dbapp/mainapp/urls.py b/dbapp/mainapp/urls.py index 76a63ae..8198552 100644 --- a/dbapp/mainapp/urls.py +++ b/dbapp/mainapp/urls.py @@ -26,6 +26,7 @@ from .views import ( ObjItemListView, ObjItemUpdateView, ProcessKubsatView, + SatelliteDataAPIView, ShowMapView, ShowSelectedObjectsMapView, ShowSourcesMapView, @@ -79,6 +80,7 @@ urlpatterns = [ path('api/sigma-parameter//', SigmaParameterDataAPIView.as_view(), name='sigma_parameter_data_api'), path('api/source//objitems/', SourceObjItemsAPIView.as_view(), name='source_objitems_api'), path('api/transponder//', TransponderDataAPIView.as_view(), name='transponder_data_api'), + path('api/satellite//', SatelliteDataAPIView.as_view(), name='satellite_data_api'), path('kubsat-excel/', ProcessKubsatView.as_view(), name='kubsat_excel'), path('object/create/', ObjItemCreateView.as_view(), name='objitem_create'), path('object//edit/', ObjItemUpdateView.as_view(), name='objitem_update'), diff --git a/dbapp/mainapp/utils.py b/dbapp/mainapp/utils.py index d71025c..c59ab93 100644 --- a/dbapp/mainapp/utils.py +++ b/dbapp/mainapp/utils.py @@ -1205,3 +1205,106 @@ def get_first_param_subquery(field_name: str): ... print(obj.first_freq) """ 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 diff --git a/dbapp/mainapp/views/__init__.py b/dbapp/mainapp/views/__init__.py index 89e0a94..f30671a 100644 --- a/dbapp/mainapp/views/__init__.py +++ b/dbapp/mainapp/views/__init__.py @@ -20,6 +20,7 @@ from .data_import import ( from .api import ( GetLocationsView, LyngsatDataAPIView, + SatelliteDataAPIView, SigmaParameterDataAPIView, SourceObjItemsAPIView, LyngsatTaskStatusAPIView, @@ -71,6 +72,7 @@ __all__ = [ # API 'GetLocationsView', 'LyngsatDataAPIView', + 'SatelliteDataAPIView', 'SigmaParameterDataAPIView', 'SourceObjItemsAPIView', 'LyngsatTaskStatusAPIView', diff --git a/dbapp/mainapp/views/api.py b/dbapp/mainapp/views/api.py index b68fef0..a4be17e 100644 --- a/dbapp/mainapp/views/api.py +++ b/dbapp/mainapp/views/api.py @@ -7,6 +7,7 @@ from django.utils import timezone from django.views import View from ..models import ObjItem +from ..utils import format_coordinate, format_coords_display, format_frequency, format_symbol_rate class GetLocationsView(LoginRequiredMixin, View): @@ -76,11 +77,11 @@ class LyngsatDataAPIView(LoginRequiredMixin, View): data = { 'id': lyngsat.id, '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 '-', 'modulation': lyngsat.modulation.name if lyngsat.modulation 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 '-', 'channel_info': lyngsat.channel_info or '-', 'last_update': last_update_str, @@ -146,13 +147,13 @@ class SigmaParameterDataAPIView(LoginRequiredMixin, View): sigma_data.append({ 'id': sigma.id, 'satellite': sigma.id_satellite.name if sigma.id_satellite else '-', - 'frequency': f"{sigma.frequency:.3f}" if sigma.frequency else '-', - 'transfer_frequency': f"{sigma.transfer_frequency:.3f}" if sigma.transfer_frequency else '-', - 'freq_range': f"{sigma.freq_range:.3f}" if sigma.freq_range else '-', + 'frequency': format_frequency(sigma.frequency), + 'transfer_frequency': format_frequency(sigma.transfer_frequency), + 'freq_range': format_frequency(sigma.freq_range), 'polarization': sigma.polarization.name if sigma.polarization else '-', 'modulation': sigma.modulation.name if sigma.modulation else '-', 'standard': sigma.standard.name if sigma.standard else '-', - 'bod_velocity': f"{sigma.bod_velocity:.0f}" if sigma.bod_velocity else '-', + 'bod_velocity': format_symbol_rate(sigma.bod_velocity), 'snr': f"{sigma.snr:.1f}" if sigma.snr is not None else '-', 'power': f"{sigma.power:.1f}" if sigma.power is not None else '-', 'status': sigma.status or '-', @@ -235,6 +236,7 @@ class SourceObjItemsAPIView(LoginRequiredMixin, View): # Get parameter data param = getattr(objitem, 'parameter_obj', None) satellite_name = '-' + satellite_id = None frequency = '-' freq_range = '-' polarization = '-' @@ -248,11 +250,12 @@ class SourceObjItemsAPIView(LoginRequiredMixin, View): parameter_id = param.id if hasattr(param, 'id_satellite') and param.id_satellite: satellite_name = param.id_satellite.name - frequency = f"{param.frequency:.3f}" if param.frequency is not None else '-' - freq_range = f"{param.freq_range:.3f}" if param.freq_range is not None else '-' + satellite_id = param.id_satellite.id + frequency = format_frequency(param.frequency) + freq_range = format_frequency(param.freq_range) if hasattr(param, 'polarization') and param.polarization: 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: modulation = param.modulation.name if hasattr(param, 'standard') and param.standard: @@ -272,11 +275,7 @@ class SourceObjItemsAPIView(LoginRequiredMixin, View): geo_location = objitem.geo_obj.location or '-' if objitem.geo_obj.coords: - longitude = objitem.geo_obj.coords.coords[0] - 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}" + geo_coords = format_coords_display(objitem.geo_obj.coords) # Get created/updated info created_at = '-' @@ -332,6 +331,7 @@ class SourceObjItemsAPIView(LoginRequiredMixin, View): 'id': objitem.id, 'name': objitem.name or '-', 'satellite_name': satellite_name, + 'satellite_id': satellite_id, 'frequency': frequency, 'freq_range': freq_range, 'polarization': polarization, @@ -454,12 +454,12 @@ class TransponderDataAPIView(LoginRequiredMixin, View): 'id': transponder.id, 'name': transponder.name or '-', 'satellite': transponder.sat_id.name if transponder.sat_id else '-', - 'downlink': f"{transponder.downlink:.3f}" if transponder.downlink else '-', - 'uplink': f"{transponder.uplink:.3f}" if transponder.uplink else None, - 'frequency_range': f"{transponder.frequency_range:.3f}" if transponder.frequency_range else '-', + 'downlink': format_frequency(transponder.downlink), + 'uplink': format_frequency(transponder.uplink) if transponder.uplink else None, + 'frequency_range': format_frequency(transponder.frequency_range), 'polarization': transponder.polarization.name if transponder.polarization else '-', '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, 'created_at': created_at_str, 'created_by': created_by_str, @@ -470,3 +470,57 @@ class TransponderDataAPIView(LoginRequiredMixin, View): return JsonResponse({'error': 'Транспондер не найден'}, status=404) except Exception as e: 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) diff --git a/dbapp/mainapp/views/marks.py b/dbapp/mainapp/views/marks.py index 67d810e..8732285 100644 --- a/dbapp/mainapp/views/marks.py +++ b/dbapp/mainapp/views/marks.py @@ -37,6 +37,11 @@ class ObjectMarksListView(LoginRequiredMixin, ListView): if satellite_id: 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 def get_context_data(self, **kwargs): diff --git a/dbapp/mainapp/views/objitem.py b/dbapp/mainapp/views/objitem.py index 7b841c0..beef171 100644 --- a/dbapp/mainapp/views/objitem.py +++ b/dbapp/mainapp/views/objitem.py @@ -15,7 +15,13 @@ from django.views.generic import CreateView, DeleteView, UpdateView from ..forms import GeoForm, ObjItemForm, ParameterForm from ..mixins import CoordinateProcessingMixin, FormMessageMixin, RoleRequiredMixin 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): @@ -323,13 +329,10 @@ class ObjItemListView(LoginRequiredMixin, View): mirrors_list = list(obj.geo_obj.mirrors.values_list('name', flat=True)) if obj.geo_obj.coords: - longitude = obj.geo_obj.coords.coords[0] - 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}" + geo_coords = format_coords_display(obj.geo_obj.coords) satellite_name = "-" + satellite_id = None frequency = "-" freq_range = "-" polarization_name = "-" @@ -347,18 +350,11 @@ class ObjItemListView(LoginRequiredMixin, View): if hasattr(param.id_satellite, "name") else "-" ) + satellite_id = param.id_satellite.id - frequency = ( - f"{param.frequency:.3f}" if param.frequency is not None else "-" - ) - 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 "-" - ) + frequency = format_frequency(param.frequency) + freq_range = format_frequency(param.freq_range) + bod_velocity = format_symbol_rate(param.bod_velocity) snr = f"{param.snr:.0f}" if param.snr is not None else "-" if hasattr(param, "polarization") and param.polarization: @@ -396,8 +392,8 @@ class ObjItemListView(LoginRequiredMixin, View): has_sigma = True first_sigma = param.sigma_parameter.first() if first_sigma: - sigma_freq = f"{first_sigma.transfer_frequency:.3f}" if first_sigma.transfer_frequency else "-" - sigma_range = f"{first_sigma.freq_range:.3f}" if first_sigma.freq_range else "-" + sigma_freq = format_frequency(first_sigma.transfer_frequency) + sigma_range = format_frequency(first_sigma.freq_range) sigma_pol = first_sigma.polarization.name if first_sigma.polarization else "-" sigma_pol_short = sigma_pol[0] if sigma_pol and sigma_pol != "-" else "-" sigma_info = f"{sigma_freq}/{sigma_range}/{sigma_pol_short}" @@ -407,6 +403,7 @@ class ObjItemListView(LoginRequiredMixin, View): "id": obj.id, "name": obj.name or "-", "satellite_name": satellite_name, + "satellite_id": satellite_id, "frequency": frequency, "freq_range": freq_range, "polarization": polarization_name, diff --git a/dbapp/mainapp/views/source.py b/dbapp/mainapp/views/source.py index e36f362..b531d01 100644 --- a/dbapp/mainapp/views/source.py +++ b/dbapp/mainapp/views/source.py @@ -14,7 +14,7 @@ from django.views import View from ..forms import SourceForm from ..models import Source, Satellite -from ..utils import parse_pagination_params +from ..utils import format_coords_display, parse_pagination_params class SourceListView(LoginRequiredMixin, View): @@ -29,20 +29,38 @@ class SourceListView(LoginRequiredMixin, View): # Get sorting parameters (default to ID ascending) sort_param = request.GET.get("sort", "id") - # Get filter parameters + # Get filter parameters - Source level search_query = request.GET.get("search", "").strip() has_coords_average = request.GET.get("has_coords_average") has_coords_kupsat = request.GET.get("has_coords_kupsat") has_coords_valid = request.GET.get("has_coords_valid") has_coords_reference = request.GET.get("has_coords_reference") 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_max = request.GET.get("objitem_count_max", "").strip() date_from = request.GET.get("date_from", "").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_to = request.GET.get("geo_date_to", "").strip() 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 satellites = ( @@ -52,14 +70,44 @@ class SourceListView(LoginRequiredMixin, View): .order_by("name") ) - # Build Q object for geo date filtering - geo_date_q = Q() - has_geo_date_filter = False + # Get all polarizations, modulations for filters + from ..models import Polarization, Modulation, ObjectInfo + 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: try: 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) - has_geo_date_filter = True + objitem_filter_q &= Q(source_objitems__geo_obj__timestamp__gte=geo_date_from_obj) + has_objitem_filter = True except (ValueError, TypeError): pass @@ -69,15 +117,105 @@ class SourceListView(LoginRequiredMixin, View): geo_date_to_obj = datetime.strptime(geo_date_to, "%Y-%m-%d") # Add one day to include entire end date 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) - has_geo_date_filter = True + objitem_filter_q &= Q(source_objitems__geo_obj__timestamp__lt=geo_date_to_obj) + has_objitem_filter = True except (ValueError, TypeError): 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 # Using annotate to count ObjItems efficiently (single query with GROUP BY) # 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__parameter_obj', 'source_objitems__parameter_obj__id_satellite', @@ -85,7 +223,7 @@ class SourceListView(LoginRequiredMixin, View): 'marks', 'marks__created_by__user' ).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 @@ -121,6 +259,41 @@ class SourceListView(LoginRequiredMixin, View): ~Q(source_objitems__lyngsat_source__isnull=False) ).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 if objitem_count_min: try: @@ -155,17 +328,36 @@ class SourceListView(LoginRequiredMixin, View): pass # Filter by Geo timestamp range (only filter sources that have matching objitems) - if has_geo_date_filter: - sources = sources.filter(geo_date_q).distinct() + if geo_date_from or geo_date_to: + 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: try: + # Try to search by ID first search_id = int(search_query) sources = sources.filter(id=search_id) except ValueError: - # If not a number, ignore - pass + # If not a number, search by name in related objitems + sources = sources.filter( + source_objitems__name__icontains=search_query + ).distinct() # Filter by satellites if selected_satellites: @@ -173,6 +365,84 @@ class SourceListView(LoginRequiredMixin, View): source_objitems__parameter_obj__id_satellite_id__in=selected_satellites ).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 valid_sort_fields = { "id": "id", @@ -194,62 +464,111 @@ class SourceListView(LoginRequiredMixin, View): # Prepare data for display processed_sources = [] - has_any_lyngsat = False # Track if any source has LyngSat data for source in page_obj: # Format coordinates - def format_coords(point): - if point: - longitude = point.coords[0] - latitude = point.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" - return f"{lat} {lon}" - return "-" + coords_average_str = format_coords_display(source.coords_average) + coords_kupsat_str = format_coords_display(source.coords_kupsat) + coords_valid_str = format_coords_display(source.coords_valid) + coords_reference_str = format_coords_display(source.coords_reference) - coords_average_str = format_coords(source.coords_average) - 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 + # Filter objitems for display (to get satellites and lyngsat info) objitems_to_display = source.source_objitems.all() - if geo_date_from or geo_date_to: - if geo_date_from: - try: - geo_date_from_obj = datetime.strptime(geo_date_from, "%Y-%m-%d") - objitems_to_display = objitems_to_display.filter(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) - objitems_to_display = objitems_to_display.filter(geo_obj__timestamp__lt=geo_date_to_obj) - except (ValueError, TypeError): - pass - # Get count of related ObjItems (filtered) - objitem_count = objitems_to_display.count() + # Apply the same filters as in the count annotation + if geo_date_from: + try: + geo_date_from_obj = datetime.strptime(geo_date_from, "%Y-%m-%d") + objitems_to_display = objitems_to_display.filter(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) + objitems_to_display = objitems_to_display.filter(geo_obj__timestamp__lt=geo_date_to_obj) + except (ValueError, TypeError): + 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 satellites for this source and check for LyngSat + # Use annotated count (consistent with filtering) + objitem_count = source.objitem_count + + # Get satellites, name and check for LyngSat satellite_names = set() + satellite_ids = set() has_lyngsat = False lyngsat_id = None + source_name = None 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, 'id_satellite') and objitem.parameter_obj.id_satellite: satellite_names.add(objitem.parameter_obj.id_satellite.name) + satellite_ids.add(objitem.parameter_obj.id_satellite.id) # Check if any objitem has LyngSat if hasattr(objitem, 'lyngsat_source') and objitem.lyngsat_source: has_lyngsat = True lyngsat_id = objitem.lyngsat_source.id - has_any_lyngsat = True 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) marks_data = [] @@ -260,14 +579,20 @@ class SourceListView(LoginRequiredMixin, View): '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({ 'id': source.id, + 'name': source_name if source_name else '-', + 'info': info_name, 'coords_average': coords_average_str, 'coords_kupsat': coords_kupsat_str, 'coords_valid': coords_valid_str, 'coords_reference': coords_reference_str, 'objitem_count': objitem_count, 'satellite': satellite_str, + 'satellite_id': first_satellite_id, 'created_at': source.created_at, 'updated_at': source.updated_at, 'has_lyngsat': has_lyngsat, @@ -283,22 +608,50 @@ class SourceListView(LoginRequiredMixin, View): 'available_items_per_page': [50, 100, 500, 1000], 'sort': sort_param, 'search_query': search_query, + # Source-level filters 'has_coords_average': has_coords_average, 'has_coords_kupsat': has_coords_kupsat, 'has_coords_valid': has_coords_valid, 'has_coords_reference': has_coords_reference, '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_max': objitem_count_max, 'date_from': date_from, '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_to': geo_date_to, 'satellites': 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())) ], + '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, } diff --git a/dbapp/mainapp/views/transponder.py b/dbapp/mainapp/views/transponder.py index 27707a5..e820b11 100644 --- a/dbapp/mainapp/views/transponder.py +++ b/dbapp/mainapp/views/transponder.py @@ -197,6 +197,7 @@ class TransponderListView(LoginRequiredMixin, View): 'id': transponder.id, 'name': transponder.name or "-", '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 "-", 'uplink': f"{transponder.uplink:.3f}" if transponder.uplink else "-", 'frequency_range': f"{transponder.frequency_range:.3f}" if transponder.frequency_range else "-",