""" Представления для страницы Кубсат с фильтрацией и экспортом в 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