""" 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 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 queryset = Source.objects.prefetch_related( 'source_objitems', 'source_objitems__parameter_obj', 'source_objitems__parameter_obj__id_satellite', Prefetch( 'marks', queryset=ObjectMark.objects.select_related('created_by__user').order_by('-timestamp') ) ).annotate( mark_count=Count('marks'), last_mark_date=Max('marks__timestamp') ) # Фильтрация по спутникам (мультивыбор) satellite_ids = self.request.GET.getlist('satellite_id') if satellite_ids: queryset = queryset.filter(source_objitems__parameter_obj__id_satellite_id__in=satellite_ids).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'] 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() ) 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.models import Satellite from mainapp.utils import parse_pagination_params # Данные для фильтров - только спутники, у которых есть источники context['satellites'] = Satellite.objects.filter( parameters__objitem__source__isnull=False ).distinct().order_by('name') 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] # Параметры поиска и сортировки context['search_query'] = self.request.GET.get('search', '') context['sort'] = self.request.GET.get('sort', '-id') # Параметры фильтров для отображения в UI (мультивыбор) context['selected_satellites'] = [int(x) for x in self.request.GET.getlist('satellite_id') if x.isdigit()] 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', '') # Добавить информацию о возможности редактирования для каждой отметки # и получить имя первого объекта для каждого источника for source in context['sources']: # Получить имя первого объекта first_objitem = source.source_objitems.first() source.objitem_name = first_objitem.name if first_objitem and first_objitem.name else '-' 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 ) 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() 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() } })