Внёс мелкие правки и фиксы

This commit is contained in:
2025-11-21 10:31:26 +03:00
parent c2c8c8799f
commit 58838614a5
13 changed files with 423 additions and 99 deletions

View File

@@ -570,3 +570,55 @@ class SatelliteDataAPIView(LoginRequiredMixin, View):
return JsonResponse({'error': 'Спутник не найден'}, status=404)
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)
class SatelliteDataAPIView(LoginRequiredMixin, View):
"""API endpoint for getting Satellite data."""
def get(self, request, satellite_id):
from ..models import Satellite
try:
satellite = Satellite.objects.prefetch_related('band').get(id=satellite_id)
# Format launch_date
launch_date_str = '-'
if satellite.launch_date:
launch_date_str = satellite.launch_date.strftime("%d.%m.%Y")
# Format created_at and updated_at
created_at_str = '-'
if satellite.created_at:
local_time = timezone.localtime(satellite.created_at)
created_at_str = local_time.strftime("%d.%m.%Y %H:%M")
updated_at_str = '-'
if satellite.updated_at:
local_time = timezone.localtime(satellite.updated_at)
updated_at_str = local_time.strftime("%d.%m.%Y %H:%M")
# Get band names
bands = list(satellite.band.values_list('name', flat=True))
bands_str = ', '.join(bands) if bands else '-'
data = {
'id': satellite.id,
'name': satellite.name,
'norad': satellite.norad if satellite.norad else None,
'bands': bands_str,
'undersat_point': satellite.undersat_point if satellite.undersat_point is not None else None,
'url': satellite.url or None,
'comment': satellite.comment or '-',
'launch_date': launch_date_str,
'created_at': created_at_str,
'updated_at': updated_at_str,
'created_by': str(satellite.created_by) if satellite.created_by else '-',
'updated_by': str(satellite.updated_by) if satellite.updated_by else '-',
}
return JsonResponse(data)
except Satellite.DoesNotExist:
return JsonResponse({'error': 'Спутник не найден'}, status=404)
except Exception as e:
return JsonResponse({'error': str(e)}, status=500)

View File

@@ -40,10 +40,18 @@ class KubsatView(LoginRequiredMixin, FormView):
for source in sources:
source_data = {
'source': source,
'objitems_data': []
'objitems_data': [],
'has_lyngsat': False,
'lyngsat_id': None
}
for objitem in source.source_objitems.all():
# Check if objitem has LyngSat source
if hasattr(objitem, 'lyngsat_source') and objitem.lyngsat_source:
source_data['has_lyngsat'] = True
source_data['lyngsat_id'] = objitem.lyngsat_source.id
objitem_matches_date = True
objitem_matches_date = True
geo_date = None
@@ -91,11 +99,12 @@ class KubsatView(LoginRequiredMixin, FormView):
def apply_filters(self, filters):
"""Применяет фильтры к queryset Source"""
queryset = Source.objects.select_related('info').prefetch_related(
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__transponder__sat_id',
'source_objitems__lyngsat_source'
).annotate(objitem_count=Count('source_objitems'))
# Фильтр по спутникам
@@ -146,6 +155,10 @@ class KubsatView(LoginRequiredMixin, FormView):
if filters.get('object_type'):
queryset = queryset.filter(info__in=filters['object_type'])
# Фильтр по принадлежности объекта
if filters.get('object_ownership'):
queryset = queryset.filter(ownership__in=filters['object_ownership'])
# Фильтр по количеству ObjItem
objitem_count = filters.get('objitem_count')
if objitem_count == '1':
@@ -191,13 +204,37 @@ class KubsatExportView(LoginRequiredMixin, FormView):
}
sources_objitems[objitem.source.id]['objitems'].append(objitem)
# Создаем Excel файл
# Создаем Excel файл с двумя листами
wb = Workbook()
ws = wb.active
ws.title = "Кубсат"
# Заголовки
headers = [
# Первый лист: "Предложения" (только основные данные)
ws_proposals = wb.active
ws_proposals.title = "Предложения"
# Заголовки для листа "Предложения"
headers_proposals = [
'Дата',
'Широта, град',
'Долгота, град',
'Высота, м',
'Местоположение',
'ИСЗ',
'Прямой канал, МГц',
'Обратный канал, МГц',
'Перенос'
]
# Стиль заголовков для листа "Предложения"
for col_num, header in enumerate(headers_proposals, 1):
cell = ws_proposals.cell(row=1, column=col_num, value=header)
cell.font = Font(bold=True)
cell.alignment = Alignment(horizontal='center', vertical='center')
# Второй лист: "Комментарий" (все данные)
ws_comments = wb.create_sheet(title="Комментарий")
# Заголовки для листа "Комментарий"
headers_comments = [
'Дата',
'Широта, град',
'Долгота, град',
@@ -215,9 +252,9 @@ class KubsatExportView(LoginRequiredMixin, FormView):
'Оператор'
]
# Стиль заголовков
for col_num, header in enumerate(headers, 1):
cell = ws.cell(row=1, column=col_num, value=header)
# Стиль заголовков для листа "Комментарий"
for col_num, header in enumerate(headers_comments, 1):
cell = ws_comments.cell(row=1, column=col_num, value=header)
cell.font = Font(bold=True)
cell.alignment = Alignment(horizontal='center', vertical='center')
@@ -225,7 +262,8 @@ class KubsatExportView(LoginRequiredMixin, FormView):
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
row_num_proposals = 2
row_num_comments = 2
for source_id, data in sources_objitems.items():
source = data['source']
objitems_list = data['objitems']
@@ -315,37 +353,50 @@ class KubsatExportView(LoginRequiredMixin, FormView):
# Иначе показываем диапазон
date_range_str = f"{min_date_str}-{max_date_str}"
# Записываем строку
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=date_range_str)
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)
# Записываем строку на лист "Предложения" (только основные данные)
ws_proposals.cell(row=row_num_proposals, column=1, value=current_date)
ws_proposals.cell(row=row_num_proposals, column=2, value=latitude)
ws_proposals.cell(row=row_num_proposals, column=3, value=longitude)
ws_proposals.cell(row=row_num_proposals, column=4, value=0.0)
ws_proposals.cell(row=row_num_proposals, column=5, value=location)
ws_proposals.cell(row=row_num_proposals, column=6, value=satellite_info)
ws_proposals.cell(row=row_num_proposals, column=7, value=direct_channel)
ws_proposals.cell(row=row_num_proposals, column=8, value=reverse_channel)
ws_proposals.cell(row=row_num_proposals, column=9, value=transfer)
row_num += 1
# Записываем строку на лист "Комментарий" (все данные)
ws_comments.cell(row=row_num_comments, column=1, value=current_date)
ws_comments.cell(row=row_num_comments, column=2, value=latitude)
ws_comments.cell(row=row_num_comments, column=3, value=longitude)
ws_comments.cell(row=row_num_comments, column=4, value=0.0)
ws_comments.cell(row=row_num_comments, column=5, value=location)
ws_comments.cell(row=row_num_comments, column=6, value=satellite_info)
ws_comments.cell(row=row_num_comments, column=7, value=direct_channel)
ws_comments.cell(row=row_num_comments, column=8, value=reverse_channel)
ws_comments.cell(row=row_num_comments, column=9, value=transfer)
ws_comments.cell(row=row_num_comments, column=10, value=objitem_count)
ws_comments.cell(row=row_num_comments, column=11, value=date_range_str)
ws_comments.cell(row=row_num_comments, column=12, value=mirrors_str)
ws_comments.cell(row=row_num_comments, column=13, value='')
ws_comments.cell(row=row_num_comments, column=14, value='')
ws_comments.cell(row=row_num_comments, column=15, value=operator_name)
row_num_proposals += 1
row_num_comments += 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
# Автоширина колонок для обоих листов
for ws in [ws_proposals, ws_comments]:
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()

View File

@@ -71,6 +71,25 @@ class LinkLyngsatSourcesView(LoginRequiredMixin, FormMessageMixin, FormView):
matching_sources.sort(key=lambda x: abs(round(x.frequency, 1) - rounded_freq))
objitem.lyngsat_source = matching_sources[0]
objitem.save(update_fields=['lyngsat_source'])
# Update Source with ObjectInfo and ObjectOwnership for TV
if objitem.source:
from ..models import ObjectInfo, ObjectOwnership
try:
tv_type = ObjectInfo.objects.get(name="Стационарные")
tv_ownership = ObjectOwnership.objects.get(name="ТВ")
# Update source if not already set
if not objitem.source.info:
objitem.source.info = tv_type
if not objitem.source.ownership:
objitem.source.ownership = tv_ownership
objitem.source.save(update_fields=['info', 'ownership'])
except (ObjectInfo.DoesNotExist, ObjectOwnership.DoesNotExist):
# If types don't exist, skip this step
pass
linked_count += 1
messages.success(

View File

@@ -37,8 +37,8 @@ class SourceListView(LoginRequiredMixin, View):
has_coords_kupsat = request.GET.get("has_coords_kupsat")
has_coords_valid = request.GET.get("has_coords_valid")
has_coords_reference = request.GET.get("has_coords_reference")
has_lyngsat = request.GET.get("has_lyngsat")
selected_info = request.GET.getlist("info_id")
selected_ownership = request.GET.getlist("ownership_id")
objitem_count_min = request.GET.get("objitem_count_min", "").strip()
objitem_count_max = request.GET.get("objitem_count_max", "").strip()
date_from = request.GET.get("date_from", "").strip()
@@ -97,6 +97,10 @@ class SourceListView(LoginRequiredMixin, View):
# Get all ObjectInfo for filter
object_infos = ObjectInfo.objects.all().order_by("name")
# Get all ObjectOwnership for filter
from ..models import ObjectOwnership
object_ownerships = ObjectOwnership.objects.all().order_by("name")
# Get all satellites that are used as mirrors
mirrors = (
Satellite.objects.filter(geo_mirrors__isnull=False)
@@ -380,18 +384,14 @@ class SourceListView(LoginRequiredMixin, View):
elif has_coords_reference == "0":
sources = sources.filter(coords_reference__isnull=True)
# Filter by LyngSat presence
if has_lyngsat == "1":
sources = sources.filter(source_objitems__lyngsat_source__isnull=False).distinct()
elif has_lyngsat == "0":
sources = sources.filter(
~Q(source_objitems__lyngsat_source__isnull=False)
).distinct()
# Filter by ObjectInfo (info field)
if selected_info:
sources = sources.filter(info_id__in=selected_info)
# Filter by ObjectOwnership (ownership field)
if selected_ownership:
sources = sources.filter(ownership_id__in=selected_ownership)
# Filter by signal marks
if has_signal_mark or mark_date_from or mark_date_to:
mark_filter_q = Q()
@@ -648,13 +648,15 @@ class SourceListView(LoginRequiredMixin, View):
'created_by': str(mark.created_by) if mark.created_by else '-',
})
# Get info name
# Get info name and ownership
info_name = source.info.name if source.info else '-'
ownership_name = source.ownership.name if source.ownership else '-'
processed_sources.append({
'id': source.id,
'name': source_name if source_name else '-',
'info': info_name,
'ownership': ownership_name,
'coords_average': coords_average_str,
'coords_kupsat': coords_kupsat_str,
'coords_valid': coords_valid_str,
@@ -682,10 +684,12 @@ class SourceListView(LoginRequiredMixin, View):
'has_coords_kupsat': has_coords_kupsat,
'has_coords_valid': has_coords_valid,
'has_coords_reference': has_coords_reference,
'has_lyngsat': has_lyngsat,
'selected_info': [
int(x) if isinstance(x, str) else x for x in selected_info if (isinstance(x, int) or (isinstance(x, str) and x.isdigit()))
],
'selected_ownership': [
int(x) if isinstance(x, str) else x for x in selected_ownership if (isinstance(x, int) or (isinstance(x, str) and x.isdigit()))
],
'objitem_count_min': objitem_count_min,
'objitem_count_max': objitem_count_max,
'date_from': date_from,
@@ -696,6 +700,8 @@ class SourceListView(LoginRequiredMixin, View):
# ObjItem-level filters
'geo_date_from': geo_date_from,
'geo_date_to': geo_date_to,
'object_infos': object_infos,
'object_ownerships': object_ownerships,
'satellites': satellites,
'selected_satellites': [
int(x) if isinstance(x, str) else x for x in selected_satellites if (isinstance(x, int) or (isinstance(x, str) and x.isdigit()))
@@ -750,6 +756,9 @@ class SourceUpdateView(LoginRequiredMixin, AdminModeratorMixin, View):
def get(self, request, pk):
source = get_object_or_404(Source, pk=pk)
form = SourceForm(instance=source)
form.fields['average_latitude'].disabled = True
form.fields['average_longitude'].disabled = True
# Get related ObjItems ordered by creation date
objitems = source.source_objitems.select_related(