379 lines
16 KiB
Python
379 lines
16 KiB
Python
"""
|
||
Представления для управления заявками на источники.
|
||
"""
|
||
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)
|