331 lines
15 KiB
Python
331 lines
15 KiB
Python
"""
|
||
Представления для страницы Кубсат с фильтрацией и экспортом в Excel
|
||
"""
|
||
from datetime import datetime
|
||
from io import BytesIO
|
||
|
||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||
from django.contrib.gis.geos import Point
|
||
from django.db.models import Count, Q
|
||
from django.http import HttpResponse
|
||
from django.views.generic import FormView
|
||
from openpyxl import Workbook
|
||
from openpyxl.styles import Font, Alignment
|
||
|
||
from mainapp.forms import KubsatFilterForm
|
||
from mainapp.models import Source, ObjItem
|
||
from mainapp.utils import calculate_mean_coords
|
||
|
||
|
||
class KubsatView(LoginRequiredMixin, FormView):
|
||
"""Страница Кубсат с фильтрами и таблицей источников"""
|
||
template_name = 'mainapp/kubsat.html'
|
||
form_class = KubsatFilterForm
|
||
|
||
def get_context_data(self, **kwargs):
|
||
context = super().get_context_data(**kwargs)
|
||
context['full_width_page'] = True
|
||
|
||
# Если форма была отправлена, применяем фильтры
|
||
if self.request.GET:
|
||
form = self.form_class(self.request.GET)
|
||
if form.is_valid():
|
||
sources = self.apply_filters(form.cleaned_data)
|
||
|
||
# Определяем, какие источники подходят по дате
|
||
date_from = form.cleaned_data.get('date_from')
|
||
date_to = form.cleaned_data.get('date_to')
|
||
|
||
# Добавляем информацию о соответствии дате для каждого источника
|
||
sources_with_date_info = []
|
||
for source in sources:
|
||
source_data = {
|
||
'source': source,
|
||
'matches_date': False,
|
||
'objitems_data': []
|
||
}
|
||
|
||
# Проверяем каждый ObjItem
|
||
for objitem in source.source_objitems.all():
|
||
objitem_matches_date = False
|
||
geo_date = None
|
||
|
||
if hasattr(objitem, 'geo_obj') and objitem.geo_obj and objitem.geo_obj.timestamp:
|
||
geo_date = objitem.geo_obj.timestamp.date()
|
||
|
||
# Проверяем попадание в диапазон дат
|
||
if date_from and date_to:
|
||
objitem_matches_date = date_from <= geo_date <= date_to
|
||
elif date_from:
|
||
objitem_matches_date = geo_date >= date_from
|
||
elif date_to:
|
||
objitem_matches_date = geo_date <= date_to
|
||
else:
|
||
objitem_matches_date = True # Нет фильтра по дате
|
||
|
||
source_data['objitems_data'].append({
|
||
'objitem': objitem,
|
||
'matches_date': objitem_matches_date,
|
||
'geo_date': geo_date
|
||
})
|
||
|
||
# Если хотя бы одна точка подходит по дате, весь источник подходит
|
||
if objitem_matches_date:
|
||
source_data['matches_date'] = True
|
||
|
||
sources_with_date_info.append(source_data)
|
||
|
||
context['sources_with_date_info'] = sources_with_date_info
|
||
context['form'] = form
|
||
|
||
return context
|
||
|
||
def apply_filters(self, filters):
|
||
"""Применяет фильтры к queryset Source"""
|
||
queryset = Source.objects.select_related('info').prefetch_related(
|
||
'source_objitems__parameter_obj__id_satellite',
|
||
'source_objitems__parameter_obj__polarization',
|
||
'source_objitems__parameter_obj__modulation',
|
||
'source_objitems__transponder__sat_id'
|
||
).annotate(objitem_count=Count('source_objitems'))
|
||
|
||
# Фильтр по спутникам
|
||
if filters.get('satellites'):
|
||
queryset = queryset.filter(
|
||
source_objitems__parameter_obj__id_satellite__in=filters['satellites']
|
||
).distinct()
|
||
|
||
# Фильтр по полосе спутника (пока не реализован полностью)
|
||
if filters.get('band'):
|
||
pass # TODO: реализовать фильтр по band
|
||
|
||
# Фильтр по поляризации
|
||
if filters.get('polarization'):
|
||
queryset = queryset.filter(
|
||
source_objitems__parameter_obj__polarization__in=filters['polarization']
|
||
).distinct()
|
||
|
||
# Фильтр по центральной частоте
|
||
if filters.get('frequency_min'):
|
||
queryset = queryset.filter(
|
||
source_objitems__parameter_obj__frequency__gte=filters['frequency_min']
|
||
)
|
||
if filters.get('frequency_max'):
|
||
queryset = queryset.filter(
|
||
source_objitems__parameter_obj__frequency__lte=filters['frequency_max']
|
||
)
|
||
|
||
# Фильтр по полосе частот
|
||
if filters.get('freq_range_min'):
|
||
queryset = queryset.filter(
|
||
source_objitems__parameter_obj__freq_range__gte=filters['freq_range_min']
|
||
)
|
||
if filters.get('freq_range_max'):
|
||
queryset = queryset.filter(
|
||
source_objitems__parameter_obj__freq_range__lte=filters['freq_range_max']
|
||
)
|
||
|
||
# Фильтр по модуляции
|
||
if filters.get('modulation'):
|
||
queryset = queryset.filter(
|
||
source_objitems__parameter_obj__modulation__in=filters['modulation']
|
||
).distinct()
|
||
|
||
# Фильтр по типу объекта
|
||
if filters.get('object_type'):
|
||
queryset = queryset.filter(info__in=filters['object_type'])
|
||
|
||
# Фильтр по количеству ObjItem
|
||
objitem_count = filters.get('objitem_count')
|
||
if objitem_count == '1':
|
||
queryset = queryset.filter(objitem_count=1)
|
||
elif objitem_count == '2+':
|
||
queryset = queryset.filter(objitem_count__gte=2)
|
||
|
||
# Фиктивные фильтры (пока не применяются)
|
||
# has_plans, success_1, success_2, date_from, date_to
|
||
|
||
return queryset.distinct()
|
||
|
||
|
||
class KubsatExportView(LoginRequiredMixin, FormView):
|
||
"""Экспорт отфильтрованных данных в Excel"""
|
||
form_class = KubsatFilterForm
|
||
|
||
def post(self, request, *args, **kwargs):
|
||
# Получаем список ID точек (ObjItem) из POST
|
||
objitem_ids = request.POST.getlist('objitem_ids')
|
||
|
||
if not objitem_ids:
|
||
return HttpResponse("Нет данных для экспорта", status=400)
|
||
|
||
# Получаем ObjItem с их источниками
|
||
objitems = ObjItem.objects.filter(id__in=objitem_ids).select_related(
|
||
'source',
|
||
'source__info',
|
||
'parameter_obj__id_satellite',
|
||
'parameter_obj__polarization',
|
||
'transponder__sat_id',
|
||
'geo_obj'
|
||
).prefetch_related('geo_obj__mirrors')
|
||
|
||
# Группируем 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)
|
||
|
||
# Создаем Excel файл
|
||
wb = Workbook()
|
||
ws = wb.active
|
||
ws.title = "Кубсат"
|
||
|
||
# Заголовки
|
||
headers = [
|
||
'Дата',
|
||
'Широта, град',
|
||
'Долгота, град',
|
||
'Высота, м',
|
||
'Местоположение',
|
||
'ИСЗ',
|
||
'Прямой канал, МГц',
|
||
'Обратный канал, МГц',
|
||
'Перенос',
|
||
'Получено координат, раз',
|
||
'Дата',
|
||
'Зеркала',
|
||
'СКО, км',
|
||
'Примечание',
|
||
'Оператор'
|
||
]
|
||
|
||
# Стиль заголовков
|
||
for col_num, header in enumerate(headers, 1):
|
||
cell = ws.cell(row=1, column=col_num, value=header)
|
||
cell.font = Font(bold=True)
|
||
cell.alignment = Alignment(horizontal='center', vertical='center')
|
||
|
||
# Заполняем данные
|
||
current_date = datetime.now().strftime('%d.%m.%Y')
|
||
operator_name = f"{request.user.first_name} {request.user.last_name}" if request.user.first_name else request.user.username
|
||
|
||
row_num = 2
|
||
for source_id, data in sources_objitems.items():
|
||
source = data['source']
|
||
objitems_list = data['objitems']
|
||
|
||
# Рассчитываем инкрементальное среднее координат из оставшихся точек
|
||
average_coords = None
|
||
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)
|
||
|
||
if average_coords is None:
|
||
# Первая точка
|
||
average_coords = coord
|
||
else:
|
||
# Инкрементальное усреднение
|
||
average_coords, _ = calculate_mean_coords(average_coords, coord)
|
||
|
||
# Если нет координат из geo_obj, берем из source
|
||
if average_coords is None:
|
||
coords = source.coords_kupsat or source.coords_average or source.coords_valid or source.coords_reference
|
||
if coords:
|
||
average_coords = (coords.x, coords.y)
|
||
|
||
latitude = average_coords[1] if average_coords else ''
|
||
longitude = average_coords[0] if average_coords else ''
|
||
|
||
# Получаем местоположение из первого ObjItem с geo_obj
|
||
location = ''
|
||
for objitem in objitems_list:
|
||
if hasattr(objitem, 'geo_obj') and objitem.geo_obj and objitem.geo_obj.location:
|
||
location = objitem.geo_obj.location
|
||
break
|
||
|
||
# Получаем данные спутника и частоты
|
||
satellite_info = ''
|
||
reverse_channel = ''
|
||
direct_channel = ''
|
||
transfer = ''
|
||
|
||
for objitem in objitems_list:
|
||
if hasattr(objitem, 'parameter_obj') and objitem.parameter_obj:
|
||
param = objitem.parameter_obj
|
||
if param.id_satellite:
|
||
sat_name = param.id_satellite.name
|
||
norad = f"({param.id_satellite.norad})" if param.id_satellite.norad else ""
|
||
satellite_info = f"{sat_name} {norad}"
|
||
|
||
if param.frequency:
|
||
reverse_channel = param.frequency
|
||
|
||
if objitem.transponder and objitem.transponder.transfer:
|
||
transfer = objitem.transponder.transfer
|
||
if param.frequency:
|
||
direct_channel = param.frequency + objitem.transponder.transfer
|
||
|
||
break
|
||
|
||
objitem_count = len(objitems_list)
|
||
|
||
# Зеркала
|
||
mirrors = []
|
||
for objitem in objitems_list:
|
||
if hasattr(objitem, 'geo_obj') and objitem.geo_obj:
|
||
for mirror in objitem.geo_obj.mirrors.all():
|
||
if mirror.name not in mirrors:
|
||
mirrors.append(mirror.name)
|
||
mirrors_str = '\n'.join(mirrors)
|
||
|
||
# Записываем строку
|
||
ws.cell(row=row_num, column=1, value=current_date)
|
||
ws.cell(row=row_num, column=2, value=latitude)
|
||
ws.cell(row=row_num, column=3, value=longitude)
|
||
ws.cell(row=row_num, column=4, value=0) # Высота всегда 0
|
||
ws.cell(row=row_num, column=5, value=location)
|
||
ws.cell(row=row_num, column=6, value=satellite_info)
|
||
ws.cell(row=row_num, column=7, value=direct_channel)
|
||
ws.cell(row=row_num, column=8, value=reverse_channel)
|
||
ws.cell(row=row_num, column=9, value=transfer)
|
||
ws.cell(row=row_num, column=10, value=objitem_count)
|
||
ws.cell(row=row_num, column=11, value='-') # Дата (пока не заполняется)
|
||
ws.cell(row=row_num, column=12, value=mirrors_str)
|
||
ws.cell(row=row_num, column=13, value='') # СКО не заполняется
|
||
ws.cell(row=row_num, column=14, value='') # Примечание не заполняется
|
||
ws.cell(row=row_num, column=15, value=operator_name)
|
||
|
||
row_num += 1
|
||
|
||
# Автоширина колонок
|
||
for column in ws.columns:
|
||
max_length = 0
|
||
column_letter = column[0].column_letter
|
||
for cell in column:
|
||
try:
|
||
if len(str(cell.value)) > max_length:
|
||
max_length = len(str(cell.value))
|
||
except:
|
||
pass
|
||
adjusted_width = min(max_length + 2, 50)
|
||
ws.column_dimensions[column_letter].width = adjusted_width
|
||
|
||
# Сохраняем в BytesIO
|
||
output = BytesIO()
|
||
wb.save(output)
|
||
output.seek(0)
|
||
|
||
# Возвращаем файл
|
||
response = HttpResponse(
|
||
output.read(),
|
||
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||
)
|
||
response['Content-Disposition'] = f'attachment; filename="kubsat_{datetime.now().strftime("%Y%m%d_%H%M%S")}.xlsx"'
|
||
|
||
return response
|