313 lines
14 KiB
Python
313 lines
14 KiB
Python
"""
|
||
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()
|
||
}
|
||
})
|