+
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 @@
|
{{ 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 "-",