@@ -435,6 +436,7 @@
Имя |
Спутник |
Тип объекта |
+
Принадлежность объекта |
Координаты ГЛ |
{% include 'mainapp/components/_sort_header.html' with field='objitem_count' label='Кол-во ГЛ(точек)' current_sort=sort %}
@@ -443,7 +445,6 @@
| Координаты визуального наблюдения |
Координаты справочные |
Наличие сигнала |
-
ТВ или нет |
{% include 'mainapp/components/_sort_header.html' with field='created_at' label='Создано' current_sort=sort %}
|
@@ -473,6 +474,16 @@
{% endif %}
{{ source.info }} |
+
+ {% if source.ownership == "ТВ" and source.has_lyngsat %}
+
+ {{ source.ownership }}
+
+ {% else %}
+ {{ source.ownership }}
+ {% endif %}
+ |
{{ source.coords_average }} |
{{ source.objitem_count }} |
{{ source.coords_kupsat }} |
@@ -501,16 +512,6 @@
-
{% endif %}
-
- {% if source.has_lyngsat %}
-
- ТВ
-
- {% else %}
- -
- {% endif %}
- |
{{ source.created_at|date:"d.m.Y H:i" }} |
{{ source.updated_at|date:"d.m.Y H:i" }} |
@@ -1091,7 +1092,6 @@ document.addEventListener('DOMContentLoaded', function() {
setupRadioLikeCheckboxes('has_coords_kupsat');
setupRadioLikeCheckboxes('has_coords_valid');
setupRadioLikeCheckboxes('has_coords_reference');
- setupRadioLikeCheckboxes('has_lyngsat');
setupRadioLikeCheckboxes('has_signal_mark');
// Update filter counter on page load
diff --git a/dbapp/mainapp/views/api.py b/dbapp/mainapp/views/api.py
index d77a1dd..ed5ab4a 100644
--- a/dbapp/mainapp/views/api.py
+++ b/dbapp/mainapp/views/api.py
@@ -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)
diff --git a/dbapp/mainapp/views/kubsat.py b/dbapp/mainapp/views/kubsat.py
index 1a5fbd3..1475985 100644
--- a/dbapp/mainapp/views/kubsat.py
+++ b/dbapp/mainapp/views/kubsat.py
@@ -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()
diff --git a/dbapp/mainapp/views/lyngsat.py b/dbapp/mainapp/views/lyngsat.py
index d09c28b..ec780f8 100644
--- a/dbapp/mainapp/views/lyngsat.py
+++ b/dbapp/mainapp/views/lyngsat.py
@@ -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(
diff --git a/dbapp/mainapp/views/source.py b/dbapp/mainapp/views/source.py
index e4980e0..09e2f37 100644
--- a/dbapp/mainapp/views/source.py
+++ b/dbapp/mainapp/views/source.py
@@ -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(