Добавил информацию о типе объекта. Просто фиксы

This commit is contained in:
2025-11-17 15:54:27 +03:00
parent f438e74946
commit b889fb29a3
20 changed files with 1086 additions and 134 deletions

View File

@@ -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,
}