Добавил работу с заявками на кубсат
This commit is contained in:
@@ -61,6 +61,8 @@ from .map import (
|
||||
from .kubsat import (
|
||||
KubsatView,
|
||||
KubsatExportView,
|
||||
KubsatCreateRequestsView,
|
||||
KubsatRecalculateCoordsView,
|
||||
)
|
||||
from .data_entry import (
|
||||
DataEntryView,
|
||||
@@ -75,6 +77,14 @@ from .statistics import (
|
||||
StatisticsView,
|
||||
StatisticsAPIView,
|
||||
)
|
||||
from .source_requests import (
|
||||
SourceRequestListView,
|
||||
SourceRequestCreateView,
|
||||
SourceRequestUpdateView,
|
||||
SourceRequestDeleteView,
|
||||
SourceRequestAPIView,
|
||||
SourceRequestDetailAPIView,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# Base
|
||||
@@ -141,6 +151,8 @@ __all__ = [
|
||||
# Kubsat
|
||||
'KubsatView',
|
||||
'KubsatExportView',
|
||||
'KubsatCreateRequestsView',
|
||||
'KubsatRecalculateCoordsView',
|
||||
# Data Entry
|
||||
'DataEntryView',
|
||||
'SearchObjItemAPIView',
|
||||
@@ -151,4 +163,11 @@ __all__ = [
|
||||
# Statistics
|
||||
'StatisticsView',
|
||||
'StatisticsAPIView',
|
||||
# Source Requests
|
||||
'SourceRequestListView',
|
||||
'SourceRequestCreateView',
|
||||
'SourceRequestUpdateView',
|
||||
'SourceRequestDeleteView',
|
||||
'SourceRequestAPIView',
|
||||
'SourceRequestDetailAPIView',
|
||||
]
|
||||
|
||||
@@ -19,13 +19,65 @@ from mainapp.utils import calculate_mean_coords
|
||||
|
||||
class KubsatView(LoginRequiredMixin, FormView):
|
||||
"""Страница Кубсат с фильтрами и таблицей источников"""
|
||||
template_name = 'mainapp/kubsat.html'
|
||||
template_name = 'mainapp/kubsat_tabs.html'
|
||||
form_class = KubsatFilterForm
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['full_width_page'] = True
|
||||
|
||||
# Добавляем данные для вкладки заявок
|
||||
from mainapp.models import SourceRequest
|
||||
requests_qs = SourceRequest.objects.select_related(
|
||||
'source', 'source__info', 'source__ownership',
|
||||
'created_by__user', 'updated_by__user'
|
||||
).prefetch_related(
|
||||
'source__source_objitems__parameter_obj__modulation'
|
||||
).order_by('-created_at')
|
||||
|
||||
# Фильтры для заявок
|
||||
status = self.request.GET.get('status')
|
||||
if status:
|
||||
requests_qs = requests_qs.filter(status=status)
|
||||
|
||||
priority = self.request.GET.get('priority')
|
||||
if priority:
|
||||
requests_qs = requests_qs.filter(priority=priority)
|
||||
|
||||
# Добавляем данные источника к каждой заявке
|
||||
requests_list = []
|
||||
for req in requests_qs[:100]:
|
||||
# Получаем данные из первой точки источника
|
||||
objitem_name = '-'
|
||||
modulation = '-'
|
||||
symbol_rate = '-'
|
||||
|
||||
if req.source:
|
||||
first_objitem = req.source.source_objitems.select_related(
|
||||
'parameter_obj__modulation'
|
||||
).order_by('geo_obj__timestamp').first()
|
||||
|
||||
if first_objitem:
|
||||
objitem_name = first_objitem.name or '-'
|
||||
if first_objitem.parameter_obj:
|
||||
if first_objitem.parameter_obj.modulation:
|
||||
modulation = first_objitem.parameter_obj.modulation.name
|
||||
if first_objitem.parameter_obj.bod_velocity and first_objitem.parameter_obj.bod_velocity > 0:
|
||||
symbol_rate = str(int(first_objitem.parameter_obj.bod_velocity))
|
||||
|
||||
# Добавляем атрибуты к объекту заявки
|
||||
req.objitem_name = objitem_name
|
||||
req.modulation = modulation
|
||||
req.symbol_rate = symbol_rate
|
||||
requests_list.append(req)
|
||||
|
||||
context['requests'] = requests_list
|
||||
context['status_choices'] = SourceRequest.STATUS_CHOICES
|
||||
context['priority_choices'] = SourceRequest.PRIORITY_CHOICES
|
||||
context['current_status'] = status or ''
|
||||
context['current_priority'] = priority or ''
|
||||
context['search_query'] = self.request.GET.get('search', '')
|
||||
|
||||
# Если форма была отправлена, применяем фильтры
|
||||
if self.request.GET:
|
||||
form = self.form_class(self.request.GET)
|
||||
@@ -38,11 +90,23 @@ class KubsatView(LoginRequiredMixin, FormView):
|
||||
objitem_count = form.cleaned_data.get('objitem_count')
|
||||
sources_with_date_info = []
|
||||
for source in sources:
|
||||
# Get latest request info for this source
|
||||
latest_request = source.source_requests.order_by('-created_at').first()
|
||||
requests_count = source.source_requests.count()
|
||||
|
||||
source_data = {
|
||||
'source': source,
|
||||
'objitems_data': [],
|
||||
'has_lyngsat': False,
|
||||
'lyngsat_id': None
|
||||
'lyngsat_id': None,
|
||||
'has_request': latest_request is not None,
|
||||
'request_status': latest_request.get_status_display() if latest_request else None,
|
||||
'request_status_raw': latest_request.status if latest_request else None,
|
||||
'gso_success': latest_request.gso_success if latest_request else None,
|
||||
'kubsat_success': latest_request.kubsat_success if latest_request else None,
|
||||
'planned_at': latest_request.planned_at if latest_request else None,
|
||||
'requests_count': requests_count,
|
||||
'average_coords': None, # Будет рассчитано после сбора точек
|
||||
}
|
||||
|
||||
for objitem in source.source_objitems.all():
|
||||
@@ -89,6 +153,27 @@ class KubsatView(LoginRequiredMixin, FormView):
|
||||
elif objitem_count == '2+':
|
||||
include_source = (filtered_count >= 2)
|
||||
|
||||
# Сортируем точки по дате ГЛ перед расчётом усреднённых координат
|
||||
source_data['objitems_data'].sort(
|
||||
key=lambda x: x['geo_date'] if x['geo_date'] else datetime.min.date()
|
||||
)
|
||||
|
||||
# Рассчитываем усреднённые координаты из отфильтрованных точек
|
||||
if source_data['objitems_data']:
|
||||
avg_coords = None
|
||||
for objitem_info in source_data['objitems_data']:
|
||||
objitem = objitem_info['objitem']
|
||||
if hasattr(objitem, 'geo_obj') and objitem.geo_obj and objitem.geo_obj.coords:
|
||||
coord = (float(objitem.geo_obj.coords.x), float(objitem.geo_obj.coords.y))
|
||||
if avg_coords is None:
|
||||
avg_coords = coord
|
||||
else:
|
||||
avg_coords, _ = calculate_mean_coords(avg_coords, coord)
|
||||
if avg_coords:
|
||||
source_data['average_coords'] = avg_coords
|
||||
source_data['avg_lat'] = avg_coords[1]
|
||||
source_data['avg_lon'] = avg_coords[0]
|
||||
|
||||
if source_data['objitems_data'] and include_source:
|
||||
sources_with_date_info.append(source_data)
|
||||
|
||||
@@ -99,12 +184,17 @@ class KubsatView(LoginRequiredMixin, FormView):
|
||||
|
||||
def apply_filters(self, filters):
|
||||
"""Применяет фильтры к queryset Source"""
|
||||
from mainapp.models import SourceRequest
|
||||
from django.db.models import Subquery, OuterRef, Exists
|
||||
|
||||
queryset = Source.objects.select_related('info', 'ownership').prefetch_related(
|
||||
'source_objitems__parameter_obj__id_satellite',
|
||||
'source_objitems__parameter_obj__polarization',
|
||||
'source_objitems__parameter_obj__modulation',
|
||||
'source_objitems__transponder__sat_id',
|
||||
'source_objitems__lyngsat_source'
|
||||
'source_objitems__lyngsat_source',
|
||||
'source_objitems__geo_obj',
|
||||
'source_requests'
|
||||
).annotate(objitem_count=Count('source_objitems'))
|
||||
|
||||
# Фильтр по спутникам
|
||||
@@ -166,8 +256,38 @@ class KubsatView(LoginRequiredMixin, FormView):
|
||||
elif objitem_count == '2+':
|
||||
queryset = queryset.filter(objitem_count__gte=2)
|
||||
|
||||
# Фиктивные фильтры (пока не применяются)
|
||||
# has_plans, success_1, success_2, date_from, date_to
|
||||
# Фильтр по наличию планов (заявок со статусом 'planned')
|
||||
has_plans = filters.get('has_plans')
|
||||
if has_plans == 'yes':
|
||||
queryset = queryset.filter(
|
||||
source_requests__status='planned'
|
||||
).distinct()
|
||||
elif has_plans == 'no':
|
||||
queryset = queryset.exclude(
|
||||
source_requests__status='planned'
|
||||
).distinct()
|
||||
|
||||
# Фильтр по ГСО успешно
|
||||
success_1 = filters.get('success_1')
|
||||
if success_1 == 'yes':
|
||||
queryset = queryset.filter(
|
||||
source_requests__gso_success=True
|
||||
).distinct()
|
||||
elif success_1 == 'no':
|
||||
queryset = queryset.filter(
|
||||
source_requests__gso_success=False
|
||||
).distinct()
|
||||
|
||||
# Фильтр по Кубсат успешно
|
||||
success_2 = filters.get('success_2')
|
||||
if success_2 == 'yes':
|
||||
queryset = queryset.filter(
|
||||
source_requests__kubsat_success=True
|
||||
).distinct()
|
||||
elif success_2 == 'no':
|
||||
queryset = queryset.filter(
|
||||
source_requests__kubsat_success=False
|
||||
).distinct()
|
||||
|
||||
return queryset.distinct()
|
||||
|
||||
@@ -268,6 +388,11 @@ class KubsatExportView(LoginRequiredMixin, FormView):
|
||||
source = data['source']
|
||||
objitems_list = data['objitems']
|
||||
|
||||
# Сортируем точки по дате ГЛ перед расчётом
|
||||
objitems_list.sort(
|
||||
key=lambda x: x.geo_obj.timestamp if x.geo_obj and x.geo_obj.timestamp else datetime.min
|
||||
)
|
||||
|
||||
# Рассчитываем инкрементальное среднее координат из оставшихся точек
|
||||
average_coords = None
|
||||
for objitem in objitems_list:
|
||||
@@ -411,3 +536,162 @@ class KubsatExportView(LoginRequiredMixin, FormView):
|
||||
response['Content-Disposition'] = f'attachment; filename="kubsat_{datetime.now().strftime("%Y%m%d")}.xlsx"'
|
||||
|
||||
return response
|
||||
|
||||
|
||||
class KubsatCreateRequestsView(LoginRequiredMixin, FormView):
|
||||
"""Массовое создание заявок из отфильтрованных данных"""
|
||||
form_class = KubsatFilterForm
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
import json
|
||||
from django.http import JsonResponse
|
||||
from mainapp.models import SourceRequest, CustomUser
|
||||
|
||||
# Получаем список ID точек (ObjItem) из POST
|
||||
objitem_ids = request.POST.getlist('objitem_ids')
|
||||
|
||||
if not objitem_ids:
|
||||
return JsonResponse({'success': False, 'error': 'Нет данных для создания заявок'}, status=400)
|
||||
|
||||
# Получаем ObjItem с их источниками
|
||||
objitems = ObjItem.objects.filter(id__in=objitem_ids).select_related(
|
||||
'source',
|
||||
'geo_obj'
|
||||
)
|
||||
|
||||
# Группируем ObjItem по Source
|
||||
sources_objitems = {}
|
||||
for objitem in objitems:
|
||||
if objitem.source:
|
||||
if objitem.source.id not in sources_objitems:
|
||||
sources_objitems[objitem.source.id] = {
|
||||
'source': objitem.source,
|
||||
'objitems': []
|
||||
}
|
||||
sources_objitems[objitem.source.id]['objitems'].append(objitem)
|
||||
|
||||
# Получаем CustomUser для текущего пользователя
|
||||
try:
|
||||
custom_user = CustomUser.objects.get(user=request.user)
|
||||
except CustomUser.DoesNotExist:
|
||||
custom_user = None
|
||||
|
||||
created_count = 0
|
||||
errors = []
|
||||
|
||||
for source_id, data in sources_objitems.items():
|
||||
source = data['source']
|
||||
objitems_list = data['objitems']
|
||||
|
||||
# Сортируем точки по дате ГЛ перед расчётом
|
||||
objitems_list.sort(
|
||||
key=lambda x: x.geo_obj.timestamp if x.geo_obj and x.geo_obj.timestamp else datetime.min
|
||||
)
|
||||
|
||||
# Рассчитываем усреднённые координаты из выбранных точек
|
||||
average_coords = None
|
||||
points_with_coords = 0
|
||||
|
||||
for objitem in objitems_list:
|
||||
if hasattr(objitem, 'geo_obj') and objitem.geo_obj and objitem.geo_obj.coords:
|
||||
coord = (objitem.geo_obj.coords.x, objitem.geo_obj.coords.y)
|
||||
points_with_coords += 1
|
||||
|
||||
if average_coords is None:
|
||||
average_coords = coord
|
||||
else:
|
||||
average_coords, _ = calculate_mean_coords(average_coords, coord)
|
||||
|
||||
# Создаём Point объект если есть координаты
|
||||
coords_point = None
|
||||
if average_coords:
|
||||
coords_point = Point(average_coords[0], average_coords[1], srid=4326)
|
||||
|
||||
try:
|
||||
# Создаём новую заявку со статусом "planned"
|
||||
source_request = SourceRequest.objects.create(
|
||||
source=source,
|
||||
status='planned',
|
||||
priority='medium',
|
||||
coords=coords_point,
|
||||
points_count=points_with_coords,
|
||||
created_by=custom_user,
|
||||
updated_by=custom_user,
|
||||
comment=f'Создано из Кубсат. Точек: {len(objitems_list)}'
|
||||
)
|
||||
created_count += 1
|
||||
except Exception as e:
|
||||
errors.append(f'Источник #{source_id}: {str(e)}')
|
||||
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'created_count': created_count,
|
||||
'total_sources': len(sources_objitems),
|
||||
'errors': errors
|
||||
})
|
||||
|
||||
|
||||
class KubsatRecalculateCoordsView(LoginRequiredMixin, FormView):
|
||||
"""API для пересчёта усреднённых координат по списку ObjItem ID"""
|
||||
form_class = KubsatFilterForm
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
import json
|
||||
from django.http import JsonResponse
|
||||
|
||||
# Получаем список ID точек (ObjItem) из POST
|
||||
objitem_ids = request.POST.getlist('objitem_ids')
|
||||
|
||||
if not objitem_ids:
|
||||
return JsonResponse({'success': False, 'error': 'Нет данных для расчёта'}, status=400)
|
||||
|
||||
# Получаем ObjItem с их источниками, сортируем по дате ГЛ
|
||||
objitems = ObjItem.objects.filter(id__in=objitem_ids).select_related(
|
||||
'source',
|
||||
'geo_obj'
|
||||
).order_by('geo_obj__timestamp') # Сортировка по дате ГЛ
|
||||
|
||||
# Группируем ObjItem по Source
|
||||
sources_objitems = {}
|
||||
for objitem in objitems:
|
||||
if objitem.source:
|
||||
if objitem.source.id not in sources_objitems:
|
||||
sources_objitems[objitem.source.id] = []
|
||||
sources_objitems[objitem.source.id].append(objitem)
|
||||
|
||||
# Рассчитываем усреднённые координаты для каждого источника
|
||||
results = {}
|
||||
for source_id, objitems_list in sources_objitems.items():
|
||||
# Сортируем по дате ГЛ (на случай если порядок сбился)
|
||||
objitems_list.sort(key=lambda x: x.geo_obj.timestamp if x.geo_obj and x.geo_obj.timestamp else datetime.min)
|
||||
|
||||
average_coords = None
|
||||
points_count = 0
|
||||
|
||||
for objitem in objitems_list:
|
||||
if hasattr(objitem, 'geo_obj') and objitem.geo_obj and objitem.geo_obj.coords:
|
||||
coord = (float(objitem.geo_obj.coords.x), float(objitem.geo_obj.coords.y))
|
||||
points_count += 1
|
||||
|
||||
if average_coords is None:
|
||||
average_coords = coord
|
||||
else:
|
||||
average_coords, _ = calculate_mean_coords(average_coords, coord)
|
||||
|
||||
if average_coords:
|
||||
results[str(source_id)] = {
|
||||
'avg_lon': average_coords[0],
|
||||
'avg_lat': average_coords[1],
|
||||
'points_count': points_count
|
||||
}
|
||||
else:
|
||||
results[str(source_id)] = {
|
||||
'avg_lon': None,
|
||||
'avg_lat': None,
|
||||
'points_count': 0
|
||||
}
|
||||
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'results': results
|
||||
})
|
||||
|
||||
@@ -48,6 +48,17 @@ class SourceListView(LoginRequiredMixin, View):
|
||||
mark_date_from = request.GET.get("mark_date_from", "").strip()
|
||||
mark_date_to = request.GET.get("mark_date_to", "").strip()
|
||||
|
||||
# Source request filters
|
||||
has_requests = request.GET.get("has_requests")
|
||||
selected_request_statuses = request.GET.getlist("request_status")
|
||||
selected_request_priorities = request.GET.getlist("request_priority")
|
||||
request_gso_success = request.GET.get("request_gso_success")
|
||||
request_kubsat_success = request.GET.get("request_kubsat_success")
|
||||
request_planned_from = request.GET.get("request_planned_from", "").strip()
|
||||
request_planned_to = request.GET.get("request_planned_to", "").strip()
|
||||
request_date_from = request.GET.get("request_date_from", "").strip()
|
||||
request_date_to = request.GET.get("request_date_to", "").strip()
|
||||
|
||||
# Get filter parameters - ObjItem level (параметры точек)
|
||||
geo_date_from = request.GET.get("geo_date_from", "").strip()
|
||||
geo_date_to = request.GET.get("geo_date_to", "").strip()
|
||||
@@ -423,6 +434,73 @@ class SourceListView(LoginRequiredMixin, View):
|
||||
if mark_filter_q:
|
||||
sources = sources.filter(mark_filter_q).distinct()
|
||||
|
||||
# Filter by source requests
|
||||
if has_requests == "1":
|
||||
# Has requests - apply subfilters
|
||||
from ..models import SourceRequest
|
||||
from django.db.models import Exists, OuterRef
|
||||
|
||||
# Build subquery for filtering requests
|
||||
request_subquery = SourceRequest.objects.filter(source=OuterRef('pk'))
|
||||
|
||||
# Filter by request status
|
||||
if selected_request_statuses:
|
||||
request_subquery = request_subquery.filter(status__in=selected_request_statuses)
|
||||
|
||||
# Filter by request priority
|
||||
if selected_request_priorities:
|
||||
request_subquery = request_subquery.filter(priority__in=selected_request_priorities)
|
||||
|
||||
# Filter by GSO success
|
||||
if request_gso_success == "true":
|
||||
request_subquery = request_subquery.filter(gso_success=True)
|
||||
elif request_gso_success == "false":
|
||||
request_subquery = request_subquery.filter(gso_success=False)
|
||||
|
||||
# Filter by Kubsat success
|
||||
if request_kubsat_success == "true":
|
||||
request_subquery = request_subquery.filter(kubsat_success=True)
|
||||
elif request_kubsat_success == "false":
|
||||
request_subquery = request_subquery.filter(kubsat_success=False)
|
||||
|
||||
# Filter by planned date range
|
||||
if request_planned_from:
|
||||
try:
|
||||
planned_from_obj = datetime.strptime(request_planned_from, "%Y-%m-%d")
|
||||
request_subquery = request_subquery.filter(planned_at__gte=planned_from_obj)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
if request_planned_to:
|
||||
try:
|
||||
from datetime import timedelta
|
||||
planned_to_obj = datetime.strptime(request_planned_to, "%Y-%m-%d")
|
||||
planned_to_obj = planned_to_obj + timedelta(days=1)
|
||||
request_subquery = request_subquery.filter(planned_at__lt=planned_to_obj)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# Filter by request date range
|
||||
if request_date_from:
|
||||
try:
|
||||
req_date_from_obj = datetime.strptime(request_date_from, "%Y-%m-%d")
|
||||
request_subquery = request_subquery.filter(request_date__gte=req_date_from_obj)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
if request_date_to:
|
||||
try:
|
||||
req_date_to_obj = datetime.strptime(request_date_to, "%Y-%m-%d")
|
||||
request_subquery = request_subquery.filter(request_date__lte=req_date_to_obj)
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
# Apply the subquery filter using Exists
|
||||
sources = sources.filter(Exists(request_subquery))
|
||||
elif has_requests == "0":
|
||||
# No requests
|
||||
sources = sources.filter(source_requests__isnull=True)
|
||||
|
||||
# Filter by ObjItem count
|
||||
if objitem_count_min:
|
||||
try:
|
||||
@@ -700,6 +778,16 @@ class SourceListView(LoginRequiredMixin, View):
|
||||
'has_signal_mark': has_signal_mark,
|
||||
'mark_date_from': mark_date_from,
|
||||
'mark_date_to': mark_date_to,
|
||||
# Source request filters
|
||||
'has_requests': has_requests,
|
||||
'selected_request_statuses': selected_request_statuses,
|
||||
'selected_request_priorities': selected_request_priorities,
|
||||
'request_gso_success': request_gso_success,
|
||||
'request_kubsat_success': request_kubsat_success,
|
||||
'request_planned_from': request_planned_from,
|
||||
'request_planned_to': request_planned_to,
|
||||
'request_date_from': request_date_from,
|
||||
'request_date_to': request_date_to,
|
||||
# ObjItem-level filters
|
||||
'geo_date_from': geo_date_from,
|
||||
'geo_date_to': geo_date_to,
|
||||
|
||||
378
dbapp/mainapp/views/source_requests.py
Normal file
378
dbapp/mainapp/views/source_requests.py
Normal file
@@ -0,0 +1,378 @@
|
||||
"""
|
||||
Представления для управления заявками на источники.
|
||||
"""
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.http import JsonResponse
|
||||
from django.views import View
|
||||
from django.views.generic import ListView, CreateView, UpdateView
|
||||
from django.urls import reverse_lazy
|
||||
from django.db.models import Q
|
||||
|
||||
from mainapp.models import SourceRequest, SourceRequestStatusHistory, Source
|
||||
from mainapp.forms import SourceRequestForm
|
||||
|
||||
|
||||
class SourceRequestListView(LoginRequiredMixin, ListView):
|
||||
"""Список заявок на источники."""
|
||||
model = SourceRequest
|
||||
template_name = 'mainapp/source_request_list.html'
|
||||
context_object_name = 'requests'
|
||||
paginate_by = 50
|
||||
|
||||
def get_queryset(self):
|
||||
queryset = SourceRequest.objects.select_related(
|
||||
'source', 'source__info', 'source__ownership',
|
||||
'created_by__user', 'updated_by__user'
|
||||
).order_by('-created_at')
|
||||
|
||||
# Фильтр по статусу
|
||||
status = self.request.GET.get('status')
|
||||
if status:
|
||||
queryset = queryset.filter(status=status)
|
||||
|
||||
# Фильтр по приоритету
|
||||
priority = self.request.GET.get('priority')
|
||||
if priority:
|
||||
queryset = queryset.filter(priority=priority)
|
||||
|
||||
# Фильтр по источнику
|
||||
source_id = self.request.GET.get('source_id')
|
||||
if source_id:
|
||||
queryset = queryset.filter(source_id=source_id)
|
||||
|
||||
# Фильтр по ГСО успешно
|
||||
gso_success = self.request.GET.get('gso_success')
|
||||
if gso_success == 'true':
|
||||
queryset = queryset.filter(gso_success=True)
|
||||
elif gso_success == 'false':
|
||||
queryset = queryset.filter(gso_success=False)
|
||||
|
||||
# Фильтр по Кубсат успешно
|
||||
kubsat_success = self.request.GET.get('kubsat_success')
|
||||
if kubsat_success == 'true':
|
||||
queryset = queryset.filter(kubsat_success=True)
|
||||
elif kubsat_success == 'false':
|
||||
queryset = queryset.filter(kubsat_success=False)
|
||||
|
||||
# Поиск
|
||||
search = self.request.GET.get('search')
|
||||
if search:
|
||||
queryset = queryset.filter(
|
||||
Q(source__id__icontains=search) |
|
||||
Q(comment__icontains=search)
|
||||
)
|
||||
|
||||
return queryset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['status_choices'] = SourceRequest.STATUS_CHOICES
|
||||
context['priority_choices'] = SourceRequest.PRIORITY_CHOICES
|
||||
context['current_status'] = self.request.GET.get('status', '')
|
||||
context['current_priority'] = self.request.GET.get('priority', '')
|
||||
context['search_query'] = self.request.GET.get('search', '')
|
||||
context['form'] = SourceRequestForm()
|
||||
return context
|
||||
|
||||
|
||||
class SourceRequestCreateView(LoginRequiredMixin, CreateView):
|
||||
"""Создание заявки на источник."""
|
||||
model = SourceRequest
|
||||
form_class = SourceRequestForm
|
||||
template_name = 'mainapp/source_request_form.html'
|
||||
success_url = reverse_lazy('mainapp:source_request_list')
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
# Передаем source_id если он есть в GET параметрах
|
||||
source_id = self.request.GET.get('source_id')
|
||||
if source_id:
|
||||
kwargs['source_id'] = source_id
|
||||
return kwargs
|
||||
|
||||
def form_valid(self, form):
|
||||
# Устанавливаем created_by
|
||||
form.instance.created_by = getattr(self.request.user, 'customuser', None)
|
||||
form.instance.updated_by = getattr(self.request.user, 'customuser', None)
|
||||
|
||||
response = super().form_valid(form)
|
||||
|
||||
# Создаем начальную запись в истории
|
||||
SourceRequestStatusHistory.objects.create(
|
||||
source_request=self.object,
|
||||
old_status='',
|
||||
new_status=self.object.status,
|
||||
changed_by=form.instance.created_by,
|
||||
)
|
||||
|
||||
# Если это AJAX запрос, возвращаем JSON
|
||||
if self.request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'message': 'Заявка успешно создана',
|
||||
'request_id': self.object.id
|
||||
})
|
||||
|
||||
return response
|
||||
|
||||
def form_invalid(self, form):
|
||||
if self.request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'errors': form.errors
|
||||
}, status=400)
|
||||
return super().form_invalid(form)
|
||||
|
||||
|
||||
class SourceRequestUpdateView(LoginRequiredMixin, UpdateView):
|
||||
"""Редактирование заявки на источник."""
|
||||
model = SourceRequest
|
||||
form_class = SourceRequestForm
|
||||
template_name = 'mainapp/source_request_form.html'
|
||||
success_url = reverse_lazy('mainapp:source_request_list')
|
||||
|
||||
def form_valid(self, form):
|
||||
# Устанавливаем updated_by
|
||||
form.instance.updated_by = getattr(self.request.user, 'customuser', None)
|
||||
|
||||
response = super().form_valid(form)
|
||||
|
||||
# Если это AJAX запрос, возвращаем JSON
|
||||
if self.request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'message': 'Заявка успешно обновлена',
|
||||
'request_id': self.object.id
|
||||
})
|
||||
|
||||
return response
|
||||
|
||||
def form_invalid(self, form):
|
||||
if self.request.headers.get('X-Requested-With') == 'XMLHttpRequest':
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'errors': form.errors
|
||||
}, status=400)
|
||||
return super().form_invalid(form)
|
||||
|
||||
|
||||
class SourceRequestDeleteView(LoginRequiredMixin, View):
|
||||
"""Удаление заявки на источник."""
|
||||
|
||||
def post(self, request, pk):
|
||||
try:
|
||||
source_request = SourceRequest.objects.get(pk=pk)
|
||||
source_request.delete()
|
||||
return JsonResponse({
|
||||
'success': True,
|
||||
'message': 'Заявка успешно удалена'
|
||||
})
|
||||
except SourceRequest.DoesNotExist:
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': 'Заявка не найдена'
|
||||
}, status=404)
|
||||
|
||||
|
||||
class SourceRequestAPIView(LoginRequiredMixin, View):
|
||||
"""API для получения данных о заявках источника."""
|
||||
|
||||
def get(self, request, source_id):
|
||||
try:
|
||||
source = Source.objects.get(pk=source_id)
|
||||
except Source.DoesNotExist:
|
||||
return JsonResponse({'error': 'Источник не найден'}, status=404)
|
||||
|
||||
requests = SourceRequest.objects.filter(source=source).select_related(
|
||||
'created_by__user', 'updated_by__user'
|
||||
).prefetch_related('status_history__changed_by__user').order_by('-created_at')
|
||||
|
||||
data = []
|
||||
for req in requests:
|
||||
# Получаем историю статусов
|
||||
history = []
|
||||
for h in req.status_history.all().order_by('-changed_at'):
|
||||
history.append({
|
||||
'old_status': h.get_old_status_display() if h.old_status else '-',
|
||||
'new_status': h.get_new_status_display(),
|
||||
'changed_at': h.changed_at.strftime('%d.%m.%Y %H:%M') if h.changed_at else '-',
|
||||
'changed_by': str(h.changed_by) if h.changed_by else '-',
|
||||
})
|
||||
|
||||
data.append({
|
||||
'id': req.id,
|
||||
'status': req.status,
|
||||
'status_display': req.get_status_display(),
|
||||
'priority': req.priority,
|
||||
'priority_display': req.get_priority_display(),
|
||||
'planned_at': req.planned_at.strftime('%d.%m.%Y %H:%M') if req.planned_at else '-',
|
||||
'request_date': req.request_date.strftime('%d.%m.%Y') if req.request_date else '-',
|
||||
'status_updated_at': req.status_updated_at.strftime('%d.%m.%Y %H:%M') if req.status_updated_at else '-',
|
||||
'gso_success': req.gso_success,
|
||||
'kubsat_success': req.kubsat_success,
|
||||
'comment': req.comment or '-',
|
||||
'created_at': req.created_at.strftime('%d.%m.%Y %H:%M') if req.created_at else '-',
|
||||
'created_by': str(req.created_by) if req.created_by else '-',
|
||||
'history': history,
|
||||
})
|
||||
|
||||
return JsonResponse({
|
||||
'source_id': source_id,
|
||||
'requests': data,
|
||||
'count': len(data)
|
||||
})
|
||||
|
||||
|
||||
class SourceRequestDetailAPIView(LoginRequiredMixin, View):
|
||||
"""API для получения детальной информации о заявке."""
|
||||
|
||||
def get(self, request, pk):
|
||||
try:
|
||||
req = SourceRequest.objects.select_related(
|
||||
'source', 'source__info', 'source__ownership',
|
||||
'created_by__user', 'updated_by__user'
|
||||
).prefetch_related(
|
||||
'status_history__changed_by__user',
|
||||
'source__source_objitems__parameter_obj__modulation',
|
||||
'source__source_objitems__geo_obj'
|
||||
).get(pk=pk)
|
||||
except SourceRequest.DoesNotExist:
|
||||
return JsonResponse({'error': 'Заявка не найдена'}, status=404)
|
||||
|
||||
# Получаем историю статусов
|
||||
history = []
|
||||
for h in req.status_history.all().order_by('-changed_at'):
|
||||
history.append({
|
||||
'old_status': h.get_old_status_display() if h.old_status else '-',
|
||||
'new_status': h.get_new_status_display(),
|
||||
'changed_at': h.changed_at.strftime('%d.%m.%Y %H:%M') if h.changed_at else '-',
|
||||
'changed_by': str(h.changed_by) if h.changed_by else '-',
|
||||
})
|
||||
|
||||
# Получаем данные из первой точки источника (имя, модуляция, символьная скорость)
|
||||
source_data = _get_source_extra_data(req.source)
|
||||
|
||||
# Координаты из заявки или из источника
|
||||
coords_lat = None
|
||||
coords_lon = None
|
||||
if req.coords:
|
||||
coords_lat = req.coords.y
|
||||
coords_lon = req.coords.x
|
||||
elif req.source.coords_average:
|
||||
coords_lat = req.source.coords_average.y
|
||||
coords_lon = req.source.coords_average.x
|
||||
|
||||
data = {
|
||||
'id': req.id,
|
||||
'source_id': req.source_id,
|
||||
'status': req.status,
|
||||
'status_display': req.get_status_display(),
|
||||
'priority': req.priority,
|
||||
'priority_display': req.get_priority_display(),
|
||||
'planned_at': req.planned_at.isoformat() if req.planned_at else None,
|
||||
'planned_at_display': req.planned_at.strftime('%d.%m.%Y %H:%M') if req.planned_at else '-',
|
||||
'request_date': req.request_date.isoformat() if req.request_date else None,
|
||||
'request_date_display': req.request_date.strftime('%d.%m.%Y') if req.request_date else '-',
|
||||
'status_updated_at': req.status_updated_at.strftime('%d.%m.%Y %H:%M') if req.status_updated_at else '-',
|
||||
'gso_success': req.gso_success,
|
||||
'kubsat_success': req.kubsat_success,
|
||||
'comment': req.comment or '',
|
||||
'created_at': req.created_at.strftime('%d.%m.%Y %H:%M') if req.created_at else '-',
|
||||
'created_by': str(req.created_by) if req.created_by else '-',
|
||||
'history': history,
|
||||
# Дополнительные данные
|
||||
'coords_lat': coords_lat,
|
||||
'coords_lon': coords_lon,
|
||||
'points_count': req.points_count,
|
||||
'objitem_name': source_data['objitem_name'],
|
||||
'modulation': source_data['modulation'],
|
||||
'symbol_rate': source_data['symbol_rate'],
|
||||
}
|
||||
|
||||
return JsonResponse(data)
|
||||
|
||||
|
||||
def _get_source_extra_data(source):
|
||||
"""Получает дополнительные данные из первой точки источника."""
|
||||
objitem_name = '-'
|
||||
modulation = '-'
|
||||
symbol_rate = '-'
|
||||
|
||||
if source:
|
||||
# Получаем первую точку источника (сортируем по дате ГЛ)
|
||||
objitems = source.source_objitems.select_related(
|
||||
'parameter_obj__modulation', 'geo_obj'
|
||||
).order_by('geo_obj__timestamp')
|
||||
|
||||
first_objitem = objitems.first()
|
||||
if first_objitem:
|
||||
objitem_name = first_objitem.name or '-'
|
||||
if first_objitem.parameter_obj:
|
||||
if first_objitem.parameter_obj.modulation:
|
||||
modulation = first_objitem.parameter_obj.modulation.name
|
||||
if first_objitem.parameter_obj.bod_velocity and first_objitem.parameter_obj.bod_velocity > 0:
|
||||
symbol_rate = str(int(first_objitem.parameter_obj.bod_velocity))
|
||||
|
||||
return {
|
||||
'objitem_name': objitem_name,
|
||||
'modulation': modulation,
|
||||
'symbol_rate': symbol_rate,
|
||||
}
|
||||
|
||||
|
||||
class SourceDataAPIView(LoginRequiredMixin, View):
|
||||
"""API для получения данных источника (координаты, имя точки, модуляция, символьная скорость)."""
|
||||
|
||||
def get(self, request, source_id):
|
||||
from mainapp.utils import calculate_mean_coords
|
||||
from datetime import datetime
|
||||
|
||||
try:
|
||||
source = Source.objects.select_related('info', 'ownership').prefetch_related(
|
||||
'source_objitems__parameter_obj__modulation',
|
||||
'source_objitems__geo_obj'
|
||||
).get(pk=source_id)
|
||||
except Source.DoesNotExist:
|
||||
return JsonResponse({'error': 'Источник не найден', 'found': False}, status=404)
|
||||
|
||||
# Получаем данные из точек источника
|
||||
source_data = _get_source_extra_data(source)
|
||||
|
||||
# Рассчитываем усреднённые координаты из всех точек (сортируем по дате ГЛ)
|
||||
objitems = source.source_objitems.select_related('geo_obj').order_by('geo_obj__timestamp')
|
||||
|
||||
avg_coords = None
|
||||
points_count = 0
|
||||
for objitem in objitems:
|
||||
if hasattr(objitem, 'geo_obj') and objitem.geo_obj and objitem.geo_obj.coords:
|
||||
coord = (float(objitem.geo_obj.coords.x), float(objitem.geo_obj.coords.y))
|
||||
points_count += 1
|
||||
if avg_coords is None:
|
||||
avg_coords = coord
|
||||
else:
|
||||
avg_coords, _ = calculate_mean_coords(avg_coords, coord)
|
||||
|
||||
# Если нет координат из точек, берём из источника
|
||||
coords_lat = None
|
||||
coords_lon = None
|
||||
if avg_coords:
|
||||
coords_lon = avg_coords[0]
|
||||
coords_lat = avg_coords[1]
|
||||
elif source.coords_average:
|
||||
coords_lat = source.coords_average.y
|
||||
coords_lon = source.coords_average.x
|
||||
|
||||
data = {
|
||||
'found': True,
|
||||
'source_id': source_id,
|
||||
'coords_lat': coords_lat,
|
||||
'coords_lon': coords_lon,
|
||||
'points_count': points_count,
|
||||
'objitem_name': source_data['objitem_name'],
|
||||
'modulation': source_data['modulation'],
|
||||
'symbol_rate': source_data['symbol_rate'],
|
||||
'info': source.info.name if source.info else '-',
|
||||
'ownership': source.ownership.name if source.ownership else '-',
|
||||
}
|
||||
|
||||
return JsonResponse(data)
|
||||
Reference in New Issue
Block a user