""" Views для отображения отметок сигналов по выбранным источникам. Сопоставляет теханализы с первой точкой источника по имени. """ import json from datetime import timedelta from django.contrib.auth.mixins import LoginRequiredMixin from django.db.models import Max, Min, Q from django.http import JsonResponse from django.shortcuts import render from django.utils import timezone from django.views import View from mainapp.models import ( Source, ObjItem, TechAnalyze, ObjectMark, Polarization, Modulation, ) class SourceMarksView(LoginRequiredMixin, View): """ Страница отображения отметок сигналов для выбранных источников. Сопоставляет теханализы с первой точкой источника по имени. """ def get(self, request): # Получаем IDs источников из параметра ids_param = request.GET.get('ids', '') source_ids = [int(id_str) for id_str in ids_param.split(',') if id_str.strip().isdigit()] # Справочники для фильтров polarizations = Polarization.objects.all().order_by('name') modulations = Modulation.objects.all().order_by('name') context = { 'source_ids': source_ids, 'source_ids_json': json.dumps(source_ids), 'full_width_page': True, 'polarizations': polarizations, 'modulations': modulations, } return render(request, 'mainapp/source_marks.html', context) class SourceMarksAPIView(LoginRequiredMixin, View): """ API для получения данных отметок по выбранным источникам. Сопоставляет теханализы с первой точкой источника по имени. """ def get(self, request): from datetime import datetime # Получаем параметры ids_param = request.GET.get('ids', '') source_ids = [int(id_str) for id_str in ids_param.split(',') if id_str.strip().isdigit()] size = int(request.GET.get('size', 0)) search = request.GET.get('search', '').strip() # Фильтры polarization_ids = request.GET.getlist('polarization_id') modulation_ids = request.GET.getlist('modulation_id') freq_min = request.GET.get('freq_min') freq_max = request.GET.get('freq_max') freq_range_min = request.GET.get('freq_range_min') freq_range_max = request.GET.get('freq_range_max') bod_velocity_min = request.GET.get('bod_velocity_min') bod_velocity_max = request.GET.get('bod_velocity_max') if not source_ids: return JsonResponse({ 'error': 'Не выбраны источники', 'periods': [], 'data': [], }, status=400) # Получаем источники с их первыми точками sources = Source.objects.filter(id__in=source_ids).prefetch_related('source_objitems') if not sources.exists(): return JsonResponse({ 'message': 'Источники не найдены', 'periods': [], 'data': [], }) # Собираем имена первых точек для каждого источника source_first_objitem_names = {} source_coords = {} for source in sources: # Получаем первую точку источника (по ID - порядок добавления) first_objitem = source.source_objitems.order_by('id').first() if first_objitem and first_objitem.name: source_first_objitem_names[source.id] = first_objitem.name # Сохраняем усреднённые координаты if source.coords_average: source_coords[source.id] = f"{source.coords_average.y:.6f}, {source.coords_average.x:.6f}" else: source_coords[source.id] = "-" if not source_first_objitem_names: return JsonResponse({ 'message': 'У выбранных источников нет точек с именами', 'periods': [], 'data': [], }) # Получаем имена для поиска теханализов objitem_names = list(source_first_objitem_names.values()) # Ищем теханализы по именам tech_analyzes = TechAnalyze.objects.filter( name__in=objitem_names ).select_related( 'satellite', 'polarization', 'modulation', 'standard' ).order_by('frequency', 'name') if not tech_analyzes.exists(): return JsonResponse({ 'message': 'Не найдены теханализы, соответствующие именам точек выбранных источников', 'periods': [], 'data': [], }) # Применяем фильтры к теханализам if polarization_ids: tech_analyzes = tech_analyzes.filter(polarization_id__in=polarization_ids) if modulation_ids: tech_analyzes = tech_analyzes.filter(modulation_id__in=modulation_ids) if freq_min: try: tech_analyzes = tech_analyzes.filter(frequency__gte=float(freq_min)) except ValueError: pass if freq_max: try: tech_analyzes = tech_analyzes.filter(frequency__lte=float(freq_max)) except ValueError: pass if freq_range_min: try: tech_analyzes = tech_analyzes.filter(freq_range__gte=float(freq_range_min)) except ValueError: pass if freq_range_max: try: tech_analyzes = tech_analyzes.filter(freq_range__lte=float(freq_range_max)) except ValueError: pass if bod_velocity_min: try: tech_analyzes = tech_analyzes.filter(bod_velocity__gte=float(bod_velocity_min)) except ValueError: pass if bod_velocity_max: try: tech_analyzes = tech_analyzes.filter(bod_velocity__lte=float(bod_velocity_max)) except ValueError: pass if search: tech_analyzes = tech_analyzes.filter( Q(name__icontains=search) | Q(id__icontains=search) ) # Создаём маппинг имя теханализа -> source_id для координат name_to_source_id = {name: sid for sid, name in source_first_objitem_names.items()} # Получаем ID теханализов ta_ids = list(tech_analyzes.values_list('id', flat=True)) # Фильтруем отметки за последние 90 дней date_90_days_ago = timezone.now() - timedelta(days=90) marks_qs = ObjectMark.objects.filter( tech_analyze_id__in=ta_ids, timestamp__gte=date_90_days_ago ).select_related('created_by__user', 'tech_analyze') # Получаем диапазон дат с отметками date_range = marks_qs.aggregate( min_date=Min('timestamp'), max_date=Max('timestamp') ) min_date = date_range['min_date'] max_date = date_range['max_date'] if not min_date or not max_date: # Нет отметок, но есть теханализы - показываем пустую таблицу data = [] for ta in tech_analyzes: source_id = name_to_source_id.get(ta.name) coords = source_coords.get(source_id, '-') if source_id else '-' data.append({ 'id': ta.id, 'name': ta.name, 'frequency': float(ta.frequency) if ta.frequency else 0, 'freq_range': float(ta.freq_range) if ta.freq_range else 0, 'polarization': ta.polarization.name if ta.polarization else '-', 'modulation': ta.modulation.name if ta.modulation else '-', 'bod_velocity': float(ta.bod_velocity) if ta.bod_velocity else 0, 'coords_average': coords, 'satellite': ta.satellite.name if ta.satellite else '-', 'marks': [], }) return JsonResponse({ 'periods': [], 'data': data, 'message': 'Нет отметок за последние 90 дней', }) # Генерируем список дней от max_date до min_date (от новых к старым) days = [] current_date = max_date.date() end_date = min_date.date() while current_date >= end_date: days.append(current_date) current_date -= timedelta(days=1) # Формируем заголовки колонок (дни) periods = [] for day in days: periods.append({ 'date': day, 'label': day.strftime('%d.%m'), }) # Загружаем все отметки all_marks = list(marks_qs.order_by('-timestamp')) # Создаём словарь: {(tech_analyze_id, date): список всех отметок за день} marks_dict = {} for mark in all_marks: mark_date = timezone.localtime(mark.timestamp).date() key = (mark.tech_analyze_id, mark_date) if key not in marks_dict: marks_dict[key] = [] marks_dict[key].append(mark) # Формируем данные data = [] for ta in tech_analyzes: source_id = name_to_source_id.get(ta.name) coords = source_coords.get(source_id, '-') if source_id else '-' row = { 'id': ta.id, 'name': ta.name, 'frequency': float(ta.frequency) if ta.frequency else 0, 'freq_range': float(ta.freq_range) if ta.freq_range else 0, 'polarization': ta.polarization.name if ta.polarization else '-', 'modulation': ta.modulation.name if ta.modulation else '-', 'bod_velocity': float(ta.bod_velocity) if ta.bod_velocity else 0, 'coords_average': coords, 'satellite': ta.satellite.name if ta.satellite else '-', 'marks': [], } # Для каждого дня собираем все отметки for period in periods: key = (ta.id, period['date']) day_marks = marks_dict.get(key, []) if day_marks: # Сортируем по времени (от раннего к позднему) day_marks_sorted = sorted(day_marks, key=lambda m: m.timestamp) marks_list = [] for mark in day_marks_sorted: local_time = timezone.localtime(mark.timestamp) marks_list.append({ 'mark': mark.mark, 'user': str(mark.created_by) if mark.created_by else '-', 'time': local_time.strftime('%H:%M'), }) row['marks'].append({ 'count': len(marks_list), 'items': marks_list, }) else: row['marks'].append(None) data.append(row) return JsonResponse({ 'periods': [p['label'] for p in periods], 'data': data, 'total': len(data), 'date_range': f"Последние 90 дней (с {date_90_days_ago.strftime('%d.%m.%Y')})", })