diff --git a/dbapp/mainapp/templates/mainapp/source_list.html b/dbapp/mainapp/templates/mainapp/source_list.html index d355b65..b1c6ad1 100644 --- a/dbapp/mainapp/templates/mainapp/source_list.html +++ b/dbapp/mainapp/templates/mainapp/source_list.html @@ -227,6 +227,15 @@ placeholder="До" value="{{ date_to|default:'' }}"> + +
+ + + +
+
@@ -812,8 +821,25 @@ function showSourceDetails(sourceId) { const modal = new bootstrap.Modal(document.getElementById('sourceDetailsModal')); modal.show(); + // Build URL with filter parameters + const urlParams = new URLSearchParams(window.location.search); + const geoDateFrom = urlParams.get('geo_date_from'); + const geoDateTo = urlParams.get('geo_date_to'); + + let apiUrl = '/api/source/' + sourceId + '/objitems/'; + const params = new URLSearchParams(); + if (geoDateFrom) { + params.append('geo_date_from', geoDateFrom); + } + if (geoDateTo) { + params.append('geo_date_to', geoDateTo); + } + if (params.toString()) { + apiUrl += '?' + params.toString(); + } + // Fetch data from API - fetch('/api/source/' + sourceId + '/objitems/') + fetch(apiUrl) .then(response => { if (!response.ok) { if (response.status === 404) { diff --git a/dbapp/mainapp/views/api.py b/dbapp/mainapp/views/api.py index fc1528b..9c02782 100644 --- a/dbapp/mainapp/views/api.py +++ b/dbapp/mainapp/views/api.py @@ -176,9 +176,14 @@ class SourceObjItemsAPIView(LoginRequiredMixin, View): """API endpoint for getting ObjItems related to a Source.""" def get(self, request, source_id): + from datetime import datetime, timedelta from ..models import Source try: + # Get filter parameters from query string + geo_date_from = request.GET.get("geo_date_from", "").strip() + geo_date_to = request.GET.get("geo_date_to", "").strip() + # Load Source with prefetch_related for ObjItem source = Source.objects.prefetch_related( 'source_objitems', @@ -198,7 +203,26 @@ class SourceObjItemsAPIView(LoginRequiredMixin, View): ).get(id=source_id) # Get all related ObjItems, sorted by created_at - objitems = source.source_objitems.all().order_by('created_at') + objitems = source.source_objitems.all() + + # Apply Geo timestamp filter if provided + if geo_date_from: + try: + geo_date_from_obj = datetime.strptime(geo_date_from, "%Y-%m-%d") + objitems = objitems.filter(geo_obj__timestamp__gte=geo_date_from_obj) + except (ValueError, TypeError): + pass + + if geo_date_to: + try: + 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) + objitems = objitems.filter(geo_obj__timestamp__lt=geo_date_to_obj) + except (ValueError, TypeError): + pass + + objitems = objitems.order_by('created_at') objitems_data = [] for objitem in objitems: diff --git a/dbapp/mainapp/views/source.py b/dbapp/mainapp/views/source.py index d541fe2..e36f362 100644 --- a/dbapp/mainapp/views/source.py +++ b/dbapp/mainapp/views/source.py @@ -40,6 +40,8 @@ class SourceListView(LoginRequiredMixin, View): 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() + 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") # Get all satellites for filter @@ -50,6 +52,28 @@ class SourceListView(LoginRequiredMixin, View): .order_by("name") ) + # Build Q object for geo date filtering + geo_date_q = Q() + has_geo_date_filter = False + 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 + 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") + # 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 + except (ValueError, TypeError): + pass + # 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 @@ -61,7 +85,7 @@ class SourceListView(LoginRequiredMixin, View): 'marks', 'marks__created_by__user' ).annotate( - objitem_count=Count('source_objitems') + objitem_count=Count('source_objitems', filter=geo_date_q) if has_geo_date_filter else Count('source_objitems') ) # Apply filters @@ -130,6 +154,10 @@ class SourceListView(LoginRequiredMixin, View): except (ValueError, TypeError): 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() + # Search by ID if search_query: try: @@ -184,15 +212,33 @@ class SourceListView(LoginRequiredMixin, View): coords_valid_str = format_coords(source.coords_valid) coords_reference_str = format_coords(source.coords_reference) - # Get count of related ObjItems - objitem_count = source.objitem_count + # Filter objitems by geo date if filter is applied + 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() # Get satellites for this source and check for LyngSat satellite_names = set() has_lyngsat = False lyngsat_id = None - for objitem in source.source_objitems.all(): + for objitem in objitems_to_display: 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) @@ -247,6 +293,8 @@ class SourceListView(LoginRequiredMixin, View): 'objitem_count_max': objitem_count_max, 'date_from': date_from, 'date_to': date_to, + '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()))