From 5e94086bf08235d51bbe0490d1232344f95ca199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=BE=D1=88=D0=BA=D0=B8=D0=BD=20=D0=A1=D0=B5=D1=80?= =?UTF-8?q?=D0=B3=D0=B5=D0=B9?= Date: Tue, 11 Nov 2025 22:40:52 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D1=80=D0=B8=D0=B2=D1=8F=D0=B7=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85=20LyngSat?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dbapp/mainapp/admin.py | 9 - dbapp/mainapp/forms.py | 26 +++ .../0009_remove_sourcetype_add_lyngsat.py | 27 +++ dbapp/mainapp/models.py | 32 +--- dbapp/mainapp/templates/mainapp/actions.html | 21 +++ .../templates/mainapp/link_lyngsat.html | 90 +++++++++ .../templates/mainapp/objitem_list.html | 174 +++++++++++++++++- dbapp/mainapp/urls.py | 2 + dbapp/mainapp/views.py | 110 +++++++++++ 9 files changed, 455 insertions(+), 36 deletions(-) create mode 100644 dbapp/mainapp/migrations/0009_remove_sourcetype_add_lyngsat.py create mode 100644 dbapp/mainapp/templates/mainapp/link_lyngsat.html diff --git a/dbapp/mainapp/admin.py b/dbapp/mainapp/admin.py index 752e43c..1dc399e 100644 --- a/dbapp/mainapp/admin.py +++ b/dbapp/mainapp/admin.py @@ -25,7 +25,6 @@ from .models import ( Standard, SigmaParMark, SigmaParameter, - SourceType, Parameter, Satellite, Mirror, @@ -336,14 +335,6 @@ class ModulationAdmin(BaseAdmin): ordering = ("name",) -@admin.register(SourceType) -class SourceTypeAdmin(BaseAdmin): - """Админ-панель для модели SourceType.""" - list_display = ("name",) - search_fields = ("name",) - ordering = ("name",) - - @admin.register(Standard) class StandardAdmin(BaseAdmin): """Админ-панель для модели Standard.""" diff --git a/dbapp/mainapp/forms.py b/dbapp/mainapp/forms.py index 85b7b59..aaa5c56 100644 --- a/dbapp/mainapp/forms.py +++ b/dbapp/mainapp/forms.py @@ -161,6 +161,32 @@ class FillLyngsatDataForm(forms.Form): }), help_text="Игнорировать кеш и получить свежие данные с сайта" ) + + +class LinkLyngsatForm(forms.Form): + """Форма для привязки источников LyngSat к объектам""" + + satellites = forms.ModelMultipleChoiceField( + queryset=Satellite.objects.all().order_by('name'), + label="Выберите спутники", + widget=forms.SelectMultiple(attrs={ + 'class': 'form-select', + 'size': '10' + }), + required=False, + help_text="Оставьте пустым для обработки всех спутников" + ) + + frequency_tolerance = forms.FloatField( + label="Допуск по частоте (МГц)", + initial=0.5, + min_value=0, + widget=forms.NumberInput(attrs={ + 'class': 'form-control', + 'step': '0.1' + }), + help_text="Допустимое отклонение частоты при сравнении" + ) class ParameterForm(forms.ModelForm): """ Форма для создания и редактирования параметров ВЧ загрузки. diff --git a/dbapp/mainapp/migrations/0009_remove_sourcetype_add_lyngsat.py b/dbapp/mainapp/migrations/0009_remove_sourcetype_add_lyngsat.py new file mode 100644 index 0000000..dffee81 --- /dev/null +++ b/dbapp/mainapp/migrations/0009_remove_sourcetype_add_lyngsat.py @@ -0,0 +1,27 @@ +# Generated by Django 5.2.7 on 2025-11-11 19:02 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('lyngsatapp', '0002_alter_lyngsat_last_update'), + ('mainapp', '0008_remove_sourcetype_objitem_objitem_source_type_id_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='objitem', + name='source_type_id', + ), + migrations.AddField( + model_name='objitem', + name='lyngsat_source', + field=models.ForeignKey(blank=True, help_text='Связанный источник из базы LyngSat (ТВ)', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='objitems', to='lyngsatapp.lyngsat', verbose_name='Источник LyngSat'), + ), + migrations.DeleteModel( + name='SourceType', + ), + ] diff --git a/dbapp/mainapp/models.py b/dbapp/mainapp/models.py index c3b8732..9f2454b 100644 --- a/dbapp/mainapp/models.py +++ b/dbapp/mainapp/models.py @@ -233,27 +233,7 @@ class Satellite(models.Model): ordering = ["name"] -class SourceType(models.Model): - """ - Модель типа источника сигнала. - Классифицирует источники по типам (наземный, морской, воздушный и т.д.). - """ - # Основные поля - name = models.CharField( - max_length=50, - unique=True, - verbose_name="Тип источника", - db_index=True, - help_text="Тип источника сигнала", - ) - def __str__(self): - return self.name - - class Meta: - verbose_name = "Тип источника" - verbose_name_plural = "Типы источников" - ordering = ["name"] class ObjItemQuerySet(models.QuerySet): """Custom QuerySet для модели ObjItem с оптимизированными запросами""" @@ -264,7 +244,7 @@ class ObjItemQuerySet(models.QuerySet): "geo_obj", "updated_by__user", "created_by__user", - "source_type_obj", + "lyngsat_source", "parameter_obj", "parameter_obj__id_satellite", "parameter_obj__polarization", @@ -349,14 +329,14 @@ class ObjItem(models.Model): verbose_name="Изменен пользователем", help_text="Пользователь, последним изменивший запись", ) - source_type_id = models.ForeignKey( - SourceType, + lyngsat_source = models.ForeignKey( + "lyngsatapp.LyngSat", on_delete=models.SET_NULL, - related_name="objitems_sourcetype", + related_name="objitems", null=True, blank=True, - verbose_name="Тип источника", - help_text="Тип источника сигнала", + verbose_name="Источник LyngSat", + help_text="Связанный источник из базы LyngSat (ТВ)", ) # Custom manager diff --git a/dbapp/mainapp/templates/mainapp/actions.html b/dbapp/mainapp/templates/mainapp/actions.html index d940219..786f7df 100644 --- a/dbapp/mainapp/templates/mainapp/actions.html +++ b/dbapp/mainapp/templates/mainapp/actions.html @@ -184,6 +184,27 @@ + + +
+
+
+
+
+ + + + +
+

Привязка источников LyngSat

+
+

Автоматическая привязка источников из базы LyngSat к объектам по частоте и поляризации. Объекты с привязанными источниками отображаются как "ТВ".

+ + Привязать источники + +
+
+
{% endblock %} \ No newline at end of file diff --git a/dbapp/mainapp/templates/mainapp/link_lyngsat.html b/dbapp/mainapp/templates/mainapp/link_lyngsat.html new file mode 100644 index 0000000..9198018 --- /dev/null +++ b/dbapp/mainapp/templates/mainapp/link_lyngsat.html @@ -0,0 +1,90 @@ +{% extends 'mainapp/base.html' %} + +{% block title %}Привязка источников LyngSat{% endblock %} + +{% block content %} +
+
+
+
+
+

+ Привязка источников LyngSat к объектам +

+
+
+ + {% include 'mainapp/components/_messages.html' %} + + + +
+ {% csrf_token %} + +
+ + {{ form.satellites }} + {% if form.satellites.help_text %} +
{{ form.satellites.help_text }}
+ {% endif %} + {% if form.satellites.errors %} +
+ {{ form.satellites.errors }} +
+ {% endif %} +
+ +
+ + {{ form.frequency_tolerance }} + {% if form.frequency_tolerance.help_text %} +
{{ form.frequency_tolerance.help_text }}
+ {% endif %} + {% if form.frequency_tolerance.errors %} +
+ {{ form.frequency_tolerance.errors }} +
+ {% endif %} +
+ +
+ + + Назад к действиям + +
+
+
+
+ + +
+
+
+ Как это работает? +
+
+
+
    +
  1. Система округляет частоту каждого объекта до целого числа
  2. +
  3. Ищет источники LyngSat с той же поляризацией и близкой частотой (в пределах допуска)
  4. +
  5. При нахождении совпадения создается связь между объектом и источником LyngSat
  6. +
  7. Объекты с привязанными источниками отображаются как "ТВ" в списке
  8. +
+
+
+
+
+
+{% endblock %} diff --git a/dbapp/mainapp/templates/mainapp/objitem_list.html b/dbapp/mainapp/templates/mainapp/objitem_list.html index 580b323..f7f35bf 100644 --- a/dbapp/mainapp/templates/mainapp/objitem_list.html +++ b/dbapp/mainapp/templates/mainapp/objitem_list.html @@ -251,6 +251,12 @@ onchange="toggleColumn(this)"> Стандарт +
  • + +
  • @@ -463,6 +469,7 @@ {% include 'mainapp/components/_table_header.html' with label="Комментарий" field="" sortable=False %} {% include 'mainapp/components/_table_header.html' with label="Усреднённое" field="" sortable=False %} {% include 'mainapp/components/_table_header.html' with label="Стандарт" field="standard" sort=sort %} + {% include 'mainapp/components/_table_header.html' with label="Тип источника" field="" sortable=False %} @@ -495,10 +502,19 @@ {{ item.comment }} {{ item.is_average }} {{ item.standard }} + + {% if item.obj.lyngsat_source %} + + ТВ + + {% else %} + - + {% endif %} + {% empty %} - + {% if selected_satellite_id %} Нет данных для выбранных фильтров {% else %} @@ -1165,4 +1181,160 @@ {% include 'mainapp/components/_selected_items_offcanvas.html' %} + + + + + {% endblock %} \ No newline at end of file diff --git a/dbapp/mainapp/urls.py b/dbapp/mainapp/urls.py index 37c3761..750caff 100644 --- a/dbapp/mainapp/urls.py +++ b/dbapp/mainapp/urls.py @@ -20,6 +20,8 @@ urlpatterns = [ path('cluster/', views.ClusterTestView.as_view(), name='cluster'), path('vch-upload/', views.UploadVchLoadView.as_view(), name='vch_load'), path('vch-link/', views.LinkVchSigmaView.as_view(), name='link_vch_sigma'), + path('link-lyngsat/', views.LinkLyngsatSourcesView.as_view(), name='link_lyngsat'), + path('api/lyngsat//', views.LyngsatDataAPIView.as_view(), name='lyngsat_data_api'), path('kubsat-excel/', views.ProcessKubsatView.as_view(), name='kubsat_excel'), path('object/create/', views.ObjItemCreateView.as_view(), name='objitem_create'), path('object//edit/', views.ObjItemUpdateView.as_view(), name='objitem_update'), diff --git a/dbapp/mainapp/views.py b/dbapp/mainapp/views.py index bc7a902..7e0e599 100644 --- a/dbapp/mainapp/views.py +++ b/dbapp/mainapp/views.py @@ -3,6 +3,7 @@ from collections import defaultdict from io import BytesIO # Django imports +from django.utils import timezone from django.contrib import messages from django.contrib.admin.views.decorators import staff_member_required from django.contrib.auth import logout @@ -38,6 +39,7 @@ from .forms import ( UploadVchLoad, VchLinkForm, FillLyngsatDataForm, + LinkLyngsatForm, ) from .mixins import CoordinateProcessingMixin, FormMessageMixin, RoleRequiredMixin from .models import Geo, Modulation, ObjItem, Polarization, Satellite @@ -375,6 +377,108 @@ class LinkVchSigmaView(LoginRequiredMixin, FormView): return self.render_to_response(self.get_context_data(form=form)) +class LinkLyngsatSourcesView(LoginRequiredMixin, FormMessageMixin, FormView): + """Представление для привязки источников LyngSat к объектам""" + template_name = "mainapp/link_lyngsat.html" + form_class = LinkLyngsatForm + success_message = "Привязка источников LyngSat завершена" + error_message = "Ошибка при привязке источников" + + def form_valid(self, form): + from lyngsatapp.models import LyngSat + + satellites = form.cleaned_data.get("satellites") + frequency_tolerance = form.cleaned_data.get("frequency_tolerance", 0.5) + + # Если спутники не выбраны, обрабатываем все + if satellites: + objitems = ObjItem.objects.filter( + parameter_obj__id_satellite__in=satellites + ).select_related('parameter_obj', 'parameter_obj__polarization') + else: + objitems = ObjItem.objects.filter( + parameter_obj__isnull=False + ).select_related('parameter_obj', 'parameter_obj__polarization') + + linked_count = 0 + total_count = objitems.count() + + for objitem in objitems: + if not hasattr(objitem, 'parameter_obj') or not objitem.parameter_obj: + continue + + param = objitem.parameter_obj + + # Округляем частоту объекта + if param.frequency: + rounded_freq = round(param.frequency, 0) # Округление до целого + + # Ищем подходящий источник LyngSat + # Сравниваем по округленной частоте и поляризации + lyngsat_sources = LyngSat.objects.filter( + id_satellite=param.id_satellite, + polarization=param.polarization, + frequency__gte=rounded_freq - frequency_tolerance, + frequency__lte=rounded_freq + frequency_tolerance + ).order_by('frequency') + + if lyngsat_sources.exists(): + # Берем первый подходящий источник + objitem.lyngsat_source = lyngsat_sources.first() + objitem.save(update_fields=['lyngsat_source']) + linked_count += 1 + + messages.success( + self.request, + f"Привязано {linked_count} из {total_count} объектов к источникам LyngSat" + ) + return redirect("mainapp:link_lyngsat") + + def form_invalid(self, form): + return self.render_to_response(self.get_context_data(form=form)) + + +class LyngsatDataAPIView(LoginRequiredMixin, View): + """API для получения данных LyngSat источника""" + + def get(self, request, lyngsat_id): + from lyngsatapp.models import LyngSat + + try: + lyngsat = LyngSat.objects.select_related( + 'id_satellite', + 'polarization', + 'modulation', + 'standard' + ).get(id=lyngsat_id) + + # Форматируем дату с учетом локального времени + last_update_str = '-' + if lyngsat.last_update: + local_time = timezone.localtime(lyngsat.last_update) + last_update_str = local_time.strftime("%d.%m.%Y") + + data = { + 'id': lyngsat.id, + 'satellite': lyngsat.id_satellite.name if lyngsat.id_satellite else '-', + 'frequency': f"{lyngsat.frequency:.3f}" if lyngsat.frequency else '-', + 'polarization': lyngsat.polarization.name if lyngsat.polarization else '-', + 'modulation': lyngsat.modulation.name if lyngsat.modulation else '-', + 'standard': lyngsat.standard.name if lyngsat.standard else '-', + 'sym_velocity': f"{lyngsat.sym_velocity:.0f}" if lyngsat.sym_velocity else '-', + 'fec': lyngsat.fec or '-', + 'channel_info': lyngsat.channel_info or '-', + 'last_update': last_update_str, + 'url': lyngsat.url or None, + } + + return JsonResponse(data) + except LyngSat.DoesNotExist: + return JsonResponse({'error': 'Источник LyngSat не найден'}, status=404) + except Exception as e: + return JsonResponse({'error': str(e)}, status=500) + + class ProcessKubsatView(LoginRequiredMixin, FormMessageMixin, FormView): template_name = "mainapp/process_kubsat.html" form_class = NewEventForm @@ -474,6 +578,7 @@ class ObjItemListView(LoginRequiredMixin, View): "geo_obj", "updated_by__user", "created_by__user", + "lyngsat_source", "parameter_obj", "parameter_obj__id_satellite", "parameter_obj__polarization", @@ -487,6 +592,7 @@ class ObjItemListView(LoginRequiredMixin, View): "geo_obj", "updated_by__user", "created_by__user", + "lyngsat_source", "parameter_obj", "parameter_obj__id_satellite", "parameter_obj__polarization", @@ -763,6 +869,9 @@ class ObjItemListView(LoginRequiredMixin, View): comment = obj.geo_obj.comment or "-" is_average = "Да" if obj.geo_obj.is_average else "Нет" if obj.geo_obj.is_average is not None else "-" + # Check if LyngSat source is linked + source_type = "ТВ" if obj.lyngsat_source else "-" + processed_objects.append( { "id": obj.id, @@ -785,6 +894,7 @@ class ObjItemListView(LoginRequiredMixin, View): "updated_by": obj.updated_by if obj.updated_by else "-", "comment": comment, "is_average": is_average, + "source_type": source_type, "standard": standard_name, "obj": obj, }