""" Views для управления отметками объектов. """ from django.contrib.auth.mixins import LoginRequiredMixin from django.db.models import Prefetch from django.http import JsonResponse from django.views.generic import ListView, View from django.shortcuts import get_object_or_404 from mainapp.models import Source, ObjectMark, CustomUser, Satellite class ObjectMarksListView(LoginRequiredMixin, ListView): """ Представление списка источников с отметками. """ model = Source template_name = "mainapp/object_marks.html" context_object_name = "sources" def get_paginate_by(self, queryset): """Получить количество элементов на странице из параметров запроса""" from mainapp.utils import parse_pagination_params _, items_per_page = parse_pagination_params(self.request, default_per_page=50) return items_per_page def get_queryset(self): """Получить queryset с предзагруженными связанными данными""" from django.db.models import Count, Max, Min # Проверяем, выбран ли спутник satellite_id = self.request.GET.get('satellite_id') if not satellite_id: # Если спутник не выбран, возвращаем пустой queryset return Source.objects.none() queryset = Source.objects.prefetch_related( 'source_objitems', 'source_objitems__parameter_obj', 'source_objitems__parameter_obj__id_satellite', 'source_objitems__parameter_obj__polarization', 'source_objitems__parameter_obj__modulation', Prefetch( 'marks', queryset=ObjectMark.objects.select_related('created_by__user').order_by('-timestamp') ) ).annotate( mark_count=Count('marks'), last_mark_date=Max('marks__timestamp'), # Аннотации для сортировки по параметрам (берем минимальное значение из связанных объектов) min_frequency=Min('source_objitems__parameter_obj__frequency'), min_freq_range=Min('source_objitems__parameter_obj__freq_range'), min_bod_velocity=Min('source_objitems__parameter_obj__bod_velocity') ) # Фильтрация по выбранному спутнику (обязательно) queryset = queryset.filter(source_objitems__parameter_obj__id_satellite_id=satellite_id).distinct() # Фильтрация по статусу (есть/нет отметок) mark_status = self.request.GET.get('mark_status') if mark_status == 'with_marks': queryset = queryset.filter(mark_count__gt=0) elif mark_status == 'without_marks': queryset = queryset.filter(mark_count=0) # Фильтрация по дате отметки date_from = self.request.GET.get('date_from') date_to = self.request.GET.get('date_to') if date_from: from django.utils.dateparse import parse_date parsed_date = parse_date(date_from) if parsed_date: queryset = queryset.filter(marks__timestamp__date__gte=parsed_date).distinct() if date_to: from django.utils.dateparse import parse_date parsed_date = parse_date(date_to) if parsed_date: queryset = queryset.filter(marks__timestamp__date__lte=parsed_date).distinct() # Фильтрация по пользователям (мультивыбор) user_ids = self.request.GET.getlist('user_id') if user_ids: queryset = queryset.filter(marks__created_by_id__in=user_ids).distinct() # Поиск по имени объекта или ID search_query = self.request.GET.get('search', '').strip() if search_query: from django.db.models import Q try: # Попытка поиска по ID source_id = int(search_query) queryset = queryset.filter(Q(id=source_id) | Q(source_objitems__name__icontains=search_query)).distinct() except ValueError: # Поиск только по имени queryset = queryset.filter(source_objitems__name__icontains=search_query).distinct() # Сортировка sort = self.request.GET.get('sort', '-id') allowed_sorts = [ 'id', '-id', 'created_at', '-created_at', 'last_mark_date', '-last_mark_date', 'mark_count', '-mark_count', 'frequency', '-frequency', 'freq_range', '-freq_range', 'bod_velocity', '-bod_velocity' ] if sort in allowed_sorts: # Для сортировки по last_mark_date нужно обработать NULL значения if 'last_mark_date' in sort: from django.db.models import F from django.db.models.functions import Coalesce queryset = queryset.order_by( Coalesce(F('last_mark_date'), F('created_at')).desc() if sort.startswith('-') else Coalesce(F('last_mark_date'), F('created_at')).asc() ) # Сортировка по частоте elif sort == 'frequency': queryset = queryset.order_by('min_frequency') elif sort == '-frequency': queryset = queryset.order_by('-min_frequency') # Сортировка по полосе elif sort == 'freq_range': queryset = queryset.order_by('min_freq_range') elif sort == '-freq_range': queryset = queryset.order_by('-min_freq_range') # Сортировка по бодовой скорости elif sort == 'bod_velocity': queryset = queryset.order_by('min_bod_velocity') elif sort == '-bod_velocity': queryset = queryset.order_by('-min_bod_velocity') else: queryset = queryset.order_by(sort) else: queryset = queryset.order_by('-id') return queryset def get_context_data(self, **kwargs): """Добавить дополнительные данные в контекст""" context = super().get_context_data(**kwargs) from mainapp.utils import parse_pagination_params # Все спутники для выбора context['satellites'] = Satellite.objects.filter( parameters__objitem__source__isnull=False ).distinct().order_by('name') # Выбранный спутник satellite_id = self.request.GET.get('satellite_id') context['selected_satellite_id'] = int(satellite_id) if satellite_id and satellite_id.isdigit() else None context['users'] = CustomUser.objects.select_related('user').filter( marks_created__isnull=False ).distinct().order_by('user__username') # Параметры пагинации page_number, items_per_page = parse_pagination_params(self.request, default_per_page=50) context['items_per_page'] = items_per_page context['available_items_per_page'] = [25, 50, 100, 200, 500] # Параметры поиска и сортировки context['search_query'] = self.request.GET.get('search', '') context['sort'] = self.request.GET.get('sort', '-id') # Параметры фильтров для отображения в UI context['selected_users'] = [int(x) for x in self.request.GET.getlist('user_id') if x.isdigit()] context['filter_mark_status'] = self.request.GET.get('mark_status', '') context['filter_date_from'] = self.request.GET.get('date_from', '') context['filter_date_to'] = self.request.GET.get('date_to', '') # Полноэкранный режим context['full_width_page'] = True # Добавить информацию о параметрах для каждого источника for source in context['sources']: # Получить первый объект для параметров (они должны быть одинаковыми) first_objitem = source.source_objitems.select_related( 'parameter_obj', 'parameter_obj__polarization', 'parameter_obj__modulation' ).first() if first_objitem: source.objitem_name = first_objitem.name if first_objitem.name else '-' # Получить параметры if first_objitem.parameter_obj: param = first_objitem.parameter_obj source.frequency = param.frequency if param.frequency else '-' source.freq_range = param.freq_range if param.freq_range else '-' source.polarization = param.polarization.name if param.polarization else '-' source.modulation = param.modulation.name if param.modulation else '-' source.bod_velocity = param.bod_velocity if param.bod_velocity else '-' else: source.frequency = '-' source.freq_range = '-' source.polarization = '-' source.modulation = '-' source.bod_velocity = '-' else: source.objitem_name = '-' source.frequency = '-' source.freq_range = '-' source.polarization = '-' source.modulation = '-' source.bod_velocity = '-' # Проверка возможности редактирования отметок for mark in source.marks.all(): mark.editable = mark.can_edit() return context class AddObjectMarkView(LoginRequiredMixin, View): """ API endpoint для добавления отметки источника. """ def post(self, request, *args, **kwargs): """Создать новую отметку""" from datetime import timedelta from django.utils import timezone source_id = request.POST.get('source_id') mark = request.POST.get('mark') == 'true' if not source_id: return JsonResponse({'success': False, 'error': 'Не указан ID источника'}, status=400) source = get_object_or_404(Source, pk=source_id) # Проверить последнюю отметку источника last_mark = source.marks.first() if last_mark: time_diff = timezone.now() - last_mark.timestamp if time_diff < timedelta(minutes=5): minutes_left = 5 - int(time_diff.total_seconds() / 60) return JsonResponse({ 'success': False, 'error': f'Нельзя добавить отметку. Подождите ещё {minutes_left} мин.' }, status=400) # Получить или создать CustomUser для текущего пользователя custom_user, _ = CustomUser.objects.get_or_create(user=request.user) # Создать отметку object_mark = ObjectMark.objects.create( source=source, mark=mark, created_by=custom_user ) # Обновляем дату последнего сигнала источника source.update_last_signal_at() source.save() return JsonResponse({ 'success': True, 'mark': { 'id': object_mark.id, 'mark': object_mark.mark, 'timestamp': object_mark.timestamp.strftime('%d.%m.%Y %H:%M'), 'created_by': str(object_mark.created_by) if object_mark.created_by else 'Неизвестно', 'can_edit': object_mark.can_edit() } }) class UpdateObjectMarkView(LoginRequiredMixin, View): """ API endpoint для обновления отметки объекта (в течение 5 минут). """ def post(self, request, *args, **kwargs): """Обновить существующую отметку""" mark_id = request.POST.get('mark_id') new_mark_value = request.POST.get('mark') == 'true' if not mark_id: return JsonResponse({'success': False, 'error': 'Не указан ID отметки'}, status=400) object_mark = get_object_or_404(ObjectMark, pk=mark_id) # Проверить возможность редактирования if not object_mark.can_edit(): return JsonResponse({ 'success': False, 'error': 'Время редактирования истекло (более 5 минут)' }, status=400) # Обновить отметку object_mark.mark = new_mark_value object_mark.save() # Обновляем дату последнего сигнала источника object_mark.source.update_last_signal_at() object_mark.source.save() return JsonResponse({ 'success': True, 'mark': { 'id': object_mark.id, 'mark': object_mark.mark, 'timestamp': object_mark.timestamp.strftime('%d.%m.%Y %H:%M'), 'created_by': str(object_mark.created_by) if object_mark.created_by else 'Неизвестно', 'can_edit': object_mark.can_edit() } })