diff --git a/dbapp/mainapp/admin.py b/dbapp/mainapp/admin.py index 88b9283..d8b02eb 100644 --- a/dbapp/mainapp/admin.py +++ b/dbapp/mainapp/admin.py @@ -110,7 +110,7 @@ class GeoInline(admin.StackedInline): fieldsets = ( ("Основная информация", { "fields": ("mirrors", "location", "distance_coords_kup", - "distance_coords_valid", "distance_kup_valid", "timestamp", "comment", "id_user_add") + "distance_coords_valid", "distance_kup_valid", "timestamp", "comment",) }), ("Координаты: геолокация", { "fields": ("longitude_geo", "latitude_geo", "coords"), @@ -195,15 +195,6 @@ class ParameterAdmin(ImportExportActionModelAdmin, admin.ModelAdmin): "standard", "sigma_parameter" ) - # fields = ( "id_satellite", - # "frequency", - # "freq_range", - # "polarization", - # "modulation", - # "bod_velocity", - # "snr", - # "standard", - # "id_sigma_parameter") list_display_links = ("frequency", "id_satellite", ) list_filter = ( HasSigmaParameterFilter, @@ -309,7 +300,7 @@ class GeoAdmin(ImportExportActionModelAdmin, LeafletGeoAdmin): fieldsets = ( ("Основная информация", { "fields": ("mirrors", "location", "distance_coords_kup", - "distance_coords_valid", "distance_kup_valid", "timestamp", "comment", "id_user_add") + "distance_coords_valid", "distance_kup_valid", "timestamp", "comment",) }), ("Координаты: геолокация", { "fields": ("longitude_geo", "latitude_geo", "coords"), @@ -337,7 +328,6 @@ class GeoAdmin(ImportExportActionModelAdmin, LeafletGeoAdmin): "is_average", ("location", MultiSelectDropdownFilter), ("timestamp", DateRangeQuickSelectListFilterBuilder()), - ("id_user_add", MultiSelectRelatedDropdownFilter), ) search_fields = ( "mirrors__name", @@ -346,7 +336,6 @@ class GeoAdmin(ImportExportActionModelAdmin, LeafletGeoAdmin): "coords_kupsat", "coords_valid" ) - list_select_related = ("id_user_add", ) prefetch_related = ("mirrors", ) @@ -467,7 +456,6 @@ class ObjectAdmin(admin.ModelAdmin): ) def get_readonly_fields(self, request, obj=None): - # Always make these fields readonly to preserve tracking return self.readonly_fields def save_model(self, request, obj, form, change): diff --git a/dbapp/mainapp/forms.py b/dbapp/mainapp/forms.py index aef2fce..7dd1f55 100644 --- a/dbapp/mainapp/forms.py +++ b/dbapp/mainapp/forms.py @@ -1,5 +1,5 @@ from django import forms -from .models import Satellite, Polarization +from .models import Satellite, Polarization, ObjItem, Parameter, Geo, Modulation, Standard class UploadFileForm(forms.Form): file = forms.FileField( @@ -103,4 +103,46 @@ class NewEventForm(forms.Form): 'class': 'form-control', 'accept': '.xlsx,.xls' }) - ) \ No newline at end of file + ) +class ParameterForm(forms.ModelForm): + class Meta: + model = Parameter + fields = [ + 'id_satellite', 'frequency', 'freq_range', 'polarization', + 'bod_velocity', 'modulation', 'snr', 'standard' + ] + widgets = { + 'id_satellite': forms.Select(attrs={'class': 'form-select'}, choices=[]), + 'frequency': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'}), + 'freq_range': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'}), + 'bod_velocity': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'}), + 'snr': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'}), + 'polarization': forms.Select(attrs={'class': 'form-select'}, choices=[]), + 'modulation': forms.Select(attrs={'class': 'form-select'}, choices=[]), + 'standard': forms.Select(attrs={'class': 'form-select'}, choices=[]), + } + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.fields['id_satellite'].choices = [(s.id, s.name) for s in Satellite.objects.all()] + self.fields['polarization'].choices = [(p.id, p.name) for p in Polarization.objects.all()] + self.fields['modulation'].choices = [(m.id, m.name) for m in Modulation.objects.all()] + self.fields['standard'].choices = [(s.id, s.name) for s in Standard.objects.all()] + +class GeoForm(forms.ModelForm): + class Meta: + model = Geo + fields = ['location', 'comment', 'is_average'] + widgets = { + 'location': forms.TextInput(attrs={'class': 'form-control'}), + 'comment': forms.TextInput(attrs={'class': 'form-control'}), + 'is_average': forms.CheckboxInput(attrs={'class': 'form-check-input'}), + } + +class ObjItemForm(forms.ModelForm): + class Meta: + model = ObjItem + fields = ['name'] + widgets = { + 'name': forms.TextInput(attrs={'class': 'form-control'}), + } \ No newline at end of file diff --git a/dbapp/mainapp/migrations/0004_remove_geo_id_user_add_remove_objitem_id_user_add_and_more.py b/dbapp/mainapp/migrations/0004_remove_geo_id_user_add_remove_objitem_id_user_add_and_more.py new file mode 100644 index 0000000..c4bdb1c --- /dev/null +++ b/dbapp/mainapp/migrations/0004_remove_geo_id_user_add_remove_objitem_id_user_add_and_more.py @@ -0,0 +1,25 @@ +# Generated by Django 5.2.7 on 2025-11-01 07:38 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('mainapp', '0003_alter_objitem_created_at_alter_objitem_updated_at'), + ] + + operations = [ + migrations.RemoveField( + model_name='geo', + name='id_user_add', + ), + migrations.RemoveField( + model_name='objitem', + name='id_user_add', + ), + migrations.RemoveField( + model_name='parameter', + name='id_user_add', + ), + ] diff --git a/dbapp/mainapp/models.py b/dbapp/mainapp/models.py index 7bc3f23..5167ebc 100644 --- a/dbapp/mainapp/models.py +++ b/dbapp/mainapp/models.py @@ -165,7 +165,7 @@ class Parameter(models.Model): standard = models.ForeignKey( Standard, default=get_default_standard, on_delete=models.SET_DEFAULT, related_name="standards", null=True, blank=True, verbose_name="Стандарт" ) - id_user_add = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, related_name="parameter_added", verbose_name="Пользователь", null=True, blank=True) + # id_user_add = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, related_name="parameter_added", verbose_name="Пользователь", null=True, blank=True) objitems = models.ManyToManyField(ObjItem, related_name="parameters_obj", verbose_name="Источники", blank=True) # id_sigma_parameter = models.ManyToManyField(SigmaParameter, on_delete=models.SET_NULL, related_name="sigma_parameter", verbose_name="ВЧ с sigma", null=True, blank=True) # id_sigma_parameter = models.ManyToManyField(SigmaParameter, verbose_name="ВЧ с sigma", null=True, blank=True) @@ -261,7 +261,7 @@ class Geo(models.Model): coords_kupsat = gis.PointField(srid=4326, null=True, blank=True, verbose_name="Координаты Кубсата") coords_valid = gis.PointField(srid=4326, null=True, blank=True, verbose_name="Координаты оперативников") is_average = models.BooleanField(null=True, blank=True, verbose_name="Усреднённое") - id_user_add = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, related_name="geos_added", verbose_name="Пользователь", null=True, blank=True) + # id_user_add = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, related_name="geos_added", verbose_name="Пользователь", null=True, blank=True) distance_coords_kup = models.GeneratedField( expression=functions.Distance("coords", "coords_kupsat")/1000, output_field=models.FloatField(), @@ -280,7 +280,7 @@ class Geo(models.Model): db_persist=True, null=True, blank=True, verbose_name="Расстояние между купсатом и оперативным отделом, км" ) - objitem = models.OneToOneField(ObjItem, on_delete=models.SET_NULL, verbose_name="Гео", related_name="geo_obj", null=True) + objitem = models.OneToOneField(ObjItem, on_delete=models.CASCADE, verbose_name="Гео", related_name="geo_obj", null=True) def __str__(self): longitude = self.coords.coords[0] diff --git a/dbapp/mainapp/templates/mainapp/objitem_confirm_delete.html b/dbapp/mainapp/templates/mainapp/objitem_confirm_delete.html new file mode 100644 index 0000000..37eef45 --- /dev/null +++ b/dbapp/mainapp/templates/mainapp/objitem_confirm_delete.html @@ -0,0 +1,25 @@ +{% extends 'mainapp/base.html' %} + +{% block title %}Удалить объект{% endblock %} + +{% block content %} +
+
+
+

Удалить объект "{{ object }}"?

+
+
+
+
+
+ {% csrf_token %} +

Вы уверены, что хотите удалить этот объект? Это действие нельзя будет отменить.

+
+ + Отмена +
+
+
+
+
+{% endblock %} diff --git a/dbapp/mainapp/templates/mainapp/objitem_form.html b/dbapp/mainapp/templates/mainapp/objitem_form.html new file mode 100644 index 0000000..7c88d29 --- /dev/null +++ b/dbapp/mainapp/templates/mainapp/objitem_form.html @@ -0,0 +1,513 @@ +{% extends 'mainapp/base.html' %} +{% load static %} +{% load static leaflet_tags %} + +{% block title %}{% if object %}Редактировать объект: {{ object.name }}{% else %}Создать объект{% endif %}{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +
+
+
+

{% if object %}Редактировать объект: {{ object.name }}{% else %}Создать объект{% endif %}

+
+ Назад +
+
+
+ +
+ {% csrf_token %} + + +
+
+

Основная информация

+
+
+
+
+ + {{ form.name }} +
+
+
+
+ +
+ {% if object.created_at %}{{ object.created_at|date:"d.m.Y H:i" }}{% else %}-{% endif %} +
+
+
+
+
+
+
+ +
+ {% if object.created_by %}{{ object.created_by }}{% else %}-{% endif %} +
+
+
+
+
+ +
+ {% if object.updated_at %}{{ object.updated_at|date:"d.m.Y H:i" }}{% else %}-{% endif %} +
+
+
+
+
+
+
+ +
+ {% if object.updated_by %}{{ object.updated_by }}{% else %}-{% endif %} +
+
+
+
+
+ + +
+
+

ВЧ загрузки

+ {% if not parameter_forms.forms.0.instance.pk %} + + {% endif %} +
+ +
+ {% for param_form in parameter_forms %} +
+
+
ВЧ загрузка #{{ forloop.counter }}
+ {% if parameter_forms.forms|length > 1 %} + + {% endif %} +
+
+
+
+ + {{ param_form.id_satellite }} +
+
+
+
+ + {{ param_form.frequency }} +
+
+
+
+ + {{ param_form.freq_range }} +
+
+
+
+ + {{ param_form.polarization }} +
+
+
+
+
+
+ + {{ param_form.bod_velocity }} +
+
+
+
+ + {{ param_form.modulation }} +
+
+
+
+ + {{ param_form.snr }} +
+
+
+
+ + {{ param_form.standard }} +
+
+
+
+ {% endfor %} +
+
+ + +
+
+

Карта

+
+
+
+
+
+ + +
+
+

Геоданные

+
+ + + +
+
Координаты геолокации
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
Координаты Кубсата
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
Координаты оперативников
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ +
+
+
+ + {{ geo_form.location }} +
+
+
+
+ + {{ geo_form.comment }} +
+
+
+ +
+
+
+ +
+
+ + +
+
+ + +
+
+
+
+
+
+ + {{ geo_form.is_average }} +
+
+
+ + {% if object.geo_obj %} +
+
+
+ +
+ {% if object.geo_obj.distance_coords_kup is not None %} + {{ object.geo_obj.distance_coords_kup|floatformat:2 }} + {% else %} + - + {% endif %} +
+
+
+
+
+ +
+ {% if object.geo_obj.distance_coords_valid is not None %} + {{ object.geo_obj.distance_coords_valid|floatformat:2 }} + {% else %} + - + {% endif %} +
+
+
+
+
+ +
+ {% if object.geo_obj.distance_kup_valid is not None %} + {{ object.geo_obj.distance_kup_valid|floatformat:2 }} + {% else %} + - + {% endif %} +
+
+
+
+ {% endif %} +
+ +
+ + {% if object %} + Удалить + {% endif %} +
+
+
+{% endblock %} + +{% block extra_js %} +{{ block.super }} + +{% leaflet_js %} +{% leaflet_css %} + + + +{% endblock %} diff --git a/dbapp/mainapp/templates/mainapp/objitem_list.html b/dbapp/mainapp/templates/mainapp/objitem_list.html index 437f1f2..41342bb 100644 --- a/dbapp/mainapp/templates/mainapp/objitem_list.html +++ b/dbapp/mainapp/templates/mainapp/objitem_list.html @@ -50,6 +50,116 @@ + + +
{% if page_obj.paginator.num_pages > 1 %} @@ -260,6 +370,8 @@ Куб-опер, км Обновлено Кем + Создано + Кем (создание) @@ -268,7 +380,7 @@ - {{ item.name }} + {{ item.name }} {{ item.satellite_name }} {{ item.frequency }} {{ item.freq_range }} @@ -283,11 +395,13 @@ {{ item.distance_geo_valid }} {{ item.distance_kup_valid }} {{ item.obj.updated_at|date:"d.m.Y H:i" }} - {{ item.obj.updated_by }} + {{ item.updated_by }} + {{ item.obj.created_at|date:"d.m.Y H:i" }} + {{ item.obj.created_by }} {% empty %} - + {% if selected_satellite_id %} Нет данных для выбранных фильтров {% else %} @@ -307,6 +421,30 @@ {% endblock %} \ No newline at end of file diff --git a/dbapp/mainapp/urls.py b/dbapp/mainapp/urls.py index 036f1d1..e4f5012 100644 --- a/dbapp/mainapp/urls.py +++ b/dbapp/mainapp/urls.py @@ -18,6 +18,9 @@ urlpatterns = [ path('vch-upload/', views.UploadVchLoadView.as_view(), name='vch_load'), path('vch-link/', views.LinkVchSigmaView.as_view(), name='link_vch_sigma'), 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'), + path('object//delete/', views.ObjItemDeleteView.as_view(), name='objitem_delete'), # path('upload/', views.upload_file, name='upload_file'), ] \ No newline at end of file diff --git a/dbapp/mainapp/utils.py b/dbapp/mainapp/utils.py index 09409c2..5824297 100644 --- a/dbapp/mainapp/utils.py +++ b/dbapp/mainapp/utils.py @@ -52,7 +52,7 @@ def remove_str(s: str): return float(s.strip().replace(",", ".")) return s -def fill_data_from_df(df: pd.DataFrame, sat: Satellite): +def fill_data_from_df(df: pd.DataFrame, sat: Satellite, current_user=None): try: df.rename(columns={'Модуляция ': 'Модуляция'}, inplace=True) except Exception as e: @@ -111,6 +111,7 @@ def fill_data_from_df(df: pd.DataFrame, sat: Satellite): location = stroka[1]['Местоопределение'].strip() comment = stroka[1]['Комментарий'] source = stroka[1]['Объект наблюдения'] + user_to_use = current_user if current_user else CustomUser.objects.get(id=1) vch_load_obj, _ = Parameter.objects.get_or_create( id_satellite=sat, @@ -120,7 +121,6 @@ def fill_data_from_df(df: pd.DataFrame, sat: Satellite): bod_velocity=v, modulation=mod_obj, snr=snr, - # defaults={'id_user_add': CustomUser.objects.get(id=1)} ) geo, _ = Geo.objects.get_or_create( @@ -131,7 +131,7 @@ def fill_data_from_df(df: pd.DataFrame, sat: Satellite): 'coords_valid': valid_point, 'location': location, 'comment': comment, - 'is_average': (comment != -1.0) + 'is_average': (comment != -1.0), } ) geo.save() @@ -144,7 +144,7 @@ def fill_data_from_df(df: pd.DataFrame, sat: Satellite): if not existing_obj_items.exists(): obj_item = ObjItem.objects.create( name=source, - id_user_add=CustomUser.objects.get(id=1) + created_by=user_to_use ) obj_item.parameters_obj.set([vch_load_obj]) geo.objitem = obj_item @@ -202,7 +202,7 @@ def get_point_from_json(filepath: str): -def get_points_from_csv(file_content): +def get_points_from_csv(file_content, current_user=None): df = pd.read_csv(io.StringIO(file_content), sep=";", names=['id', 'obj', 'lat', 'lon', 'h', 'time', 'sat', 'norad_id', 'freq', 'f_range', 'et', 'qaul', 'mir_1', 'mir_2', 'mir_3']) df[['lat', 'lon', 'freq', 'f_range']] = df[['lat', 'lon', 'freq', 'f_range']].replace(',', '.', regex=True).astype(float) @@ -239,12 +239,14 @@ def get_points_from_csv(file_content): name=row['mir_3'] ) + user_to_use = current_user if current_user else CustomUser.objects.get(id=1) + vch_load_obj, _ = Parameter.objects.get_or_create( id_satellite=sat_obj, polarization=pol_obj, frequency=row['freq'], freq_range=row['f_range'], - # defaults={'id_user_add': CustomUser.objects.get(id=1)} + # defaults={'id_user_add': user_to_use} ) geo_obj, _ = Geo.objects.get_or_create( @@ -252,7 +254,7 @@ def get_points_from_csv(file_content): coords=Point(row['lon'], row['lat'], srid=4326), defaults={ 'is_average': False, - # 'id_user_add': CustomUser.objects.get(id=1), + # 'id_user_add': user_to_use, } ) geo_obj.mirrors.set(Mirror.objects.filter(name__in=mir_lst)) @@ -264,7 +266,7 @@ def get_points_from_csv(file_content): if not existing_obj_items.exists(): obj_item = ObjItem.objects.create( name=row['obj'], - # id_user_add=CustomUser.objects.get(id=1) + created_by=user_to_use ) obj_item.parameters_obj.set([vch_load_obj]) geo_obj.objitem = obj_item diff --git a/dbapp/mainapp/views.py b/dbapp/mainapp/views.py index 061160e..c0caf0a 100644 --- a/dbapp/mainapp/views.py +++ b/dbapp/mainapp/views.py @@ -6,10 +6,13 @@ from django.contrib.admin.views.decorators import staff_member_required from django.contrib.auth.decorators import login_required from django.utils.decorators import method_decorator from django.views import View -from django.views.generic import TemplateView, FormView +from django.views.generic import TemplateView, FormView, UpdateView, DeleteView, CreateView from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin from django.contrib.auth import logout +from django.forms import inlineformset_factory, modelformset_factory from django.db import models +from django.urls import reverse_lazy +from django.contrib.gis.geos import Point import pandas as pd from .utils import ( fill_data_from_df, @@ -20,10 +23,21 @@ from .utils import ( kub_report ) from mapsapp.utils import parse_transponders_from_json, parse_transponders_from_xml -from .forms import LoadExcelData, LoadCsvData, UploadFileForm, VchLinkForm, UploadVchLoad, NewEventForm +from .forms import ( + LoadExcelData, + LoadCsvData, + UploadFileForm, + VchLinkForm, + UploadVchLoad, + NewEventForm, + ObjItemForm, + ParameterForm, + GeoForm +) from .models import ObjItem from .clusters import get_clusters from io import BytesIO +from datetime import datetime @@ -93,7 +107,7 @@ class LoadExcelDataView(LoginRequiredMixin, FormView): df = pd.read_excel(io.BytesIO(uploaded_file.read())) if number > 0: df = df.head(number) - result = fill_data_from_df(df, selected_sat) + result = fill_data_from_df(df, selected_sat, self.request.user.customuser) messages.success(self.request, f"Данные успешно загружены! Обработано строк: {result}") return redirect('load_excel_data') @@ -151,7 +165,7 @@ class LoadCsvDataView(LoginRequiredMixin, FormView): if isinstance(content, bytes): content = content.decode('utf-8') - get_points_from_csv(content) + get_points_from_csv(content, self.request.user.customuser) messages.success(self.request, f"Данные успешно загружены!") return redirect('load_csv_data') except Exception as e: @@ -331,7 +345,7 @@ class ObjItemListView(LoginRequiredMixin, View): try: items_per_page = int(items_per_page) except ValueError: - items_per_page = 25 + items_per_page = 50 # Only filter objects by selected satellite if provided (no data shown by default) objects = ObjItem.objects.none() # Initially empty @@ -348,11 +362,11 @@ class ObjItemListView(LoginRequiredMixin, View): # Start with the basic filter if any satellites are selected if selected_satellites: - # Start with the basic filter + # Start with the basic filter - optimized with prefetch_related for all related objects objects = ObjItem.objects.select_related( - 'id_user_add__user', 'geo_obj', - 'updated_by__user' + 'updated_by__user', + 'created_by__user', ).prefetch_related( 'parameters_obj__id_satellite', 'parameters_obj__polarization', @@ -362,9 +376,9 @@ class ObjItemListView(LoginRequiredMixin, View): else: # If no satellites are selected, start with all objects objects = ObjItem.objects.select_related( - 'id_user_add__user', 'geo_obj', - 'updated_by__user' + 'updated_by__user', + 'created_by__user', ).prefetch_related( 'parameters_obj__id_satellite', 'parameters_obj__polarization', @@ -468,8 +482,13 @@ class ObjItemListView(LoginRequiredMixin, View): # Prepare the data to include calculated fields for the template processed_objects = [] for obj in page_obj: - # Get the first parameter - param = obj.parameters_obj.first() if obj.parameters_obj.exists() else None + # Get the first parameter using the prefetched relation to avoid additional queries + param = None + if hasattr(obj, 'parameters_obj') and obj.parameters_obj.all(): + # Get parameters from the prefetched queryset without triggering new query + param_list = list(obj.parameters_obj.all()) + if param_list: + param = param_list[0] # Get first parameter without additional query # Process geo coordinates geo_coords = "-" @@ -518,22 +537,51 @@ class ObjItemListView(LoginRequiredMixin, View): if obj.geo_obj.distance_kup_valid is not None: distance_kup_valid = f"{obj.geo_obj.distance_kup_valid:.3f}" + # Extract related object data to avoid additional queries in template + satellite_name = "-" + frequency = "-" + freq_range = "-" + polarization_name = "-" + bod_velocity = "-" + modulation_name = "-" + snr = "-" + + if param: + # Get satellite data directly to avoid additional query + if hasattr(param, 'id_satellite') and param.id_satellite: + satellite_name = param.id_satellite.name if hasattr(param.id_satellite, 'name') else "-" + + # Get parameter values directly + frequency = f"{param.frequency:.3f}" if param.frequency is not None else "-" + freq_range = f"{param.freq_range:.3f}" if param.freq_range is not None else "-" + bod_velocity = f"{param.bod_velocity:.0f}" if param.bod_velocity is not None else "-" + snr = f"{param.snr:.0f}" if param.snr is not None else "-" + + # Get polarization name directly to avoid additional query + if hasattr(param, 'polarization') and param.polarization: + polarization_name = param.polarization.name if hasattr(param.polarization, 'name') else "-" + + # Get modulation name directly to avoid additional query + if hasattr(param, 'modulation') and param.modulation: + modulation_name = param.modulation.name if hasattr(param.modulation, 'name') else "-" + processed_objects.append({ 'id': obj.id, 'name': obj.name or "-", - 'satellite_name': param.id_satellite.name if param and param.id_satellite else "-", - 'frequency': f"{param.frequency:.3f}" if param and param.frequency else "-", - 'freq_range': f"{param.freq_range:.3f}" if param and param.freq_range else "-", - 'polarization': param.polarization.name if param and param.polarization else "-", - 'bod_velocity': f"{param.bod_velocity:.0f}" if param and param.bod_velocity else "-", - 'modulation': param.modulation.name if param and param.modulation else "-", - 'snr': f"{param.snr:.0f}" if param and param.snr else "-", + 'satellite_name': satellite_name, + 'frequency': frequency, + 'freq_range': freq_range, + 'polarization': polarization_name, + 'bod_velocity': bod_velocity, + 'modulation': modulation_name, + 'snr': snr, 'geo_coords': geo_coords, 'kupsat_coords': kupsat_coords, 'valid_coords': valid_coords, 'distance_geo_kup': distance_geo_kup, 'distance_geo_valid': distance_geo_valid, 'distance_kup_valid': distance_kup_valid, + 'updated_by': obj.updated_by if obj.updated_by else '-', 'obj': obj }) @@ -571,4 +619,209 @@ class ObjItemListView(LoginRequiredMixin, View): 'full_width_page': True, } - return render(request, 'mainapp/objitem_list.html', context) \ No newline at end of file + return render(request, 'mainapp/objitem_list.html', context) + +class ObjItemUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView): + model = ObjItem + form_class = ObjItemForm + template_name = 'mainapp/objitem_form.html' + success_url = reverse_lazy('home') + + def test_func(self): + return self.request.user.customuser.role in ['admin', 'moderator'] + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + # Добавляем контекст для карты + context['LEAFLET_CONFIG'] = { + 'DEFAULT_CENTER': (55.75, 37.62), + 'DEFAULT_ZOOM': 5, + } + + # Остальной контекст остается без изменений + ParameterFormSet = modelformset_factory( + Parameter, + form=ParameterForm, + extra=0, + can_delete=True + ) + + if self.object: + parameter_queryset = self.object.parameters_obj.all() + context['parameter_forms'] = ParameterFormSet( + queryset=parameter_queryset, + prefix='parameters' + ) + + if hasattr(self.object, 'geo_obj'): + context['geo_form'] = GeoForm(instance=self.object.geo_obj, prefix='geo') + else: + context['geo_form'] = GeoForm(prefix='geo') + else: + context['parameter_forms'] = ParameterFormSet( + queryset=Parameter.objects.none(), + prefix='parameters' + ) + context['geo_form'] = GeoForm(prefix='geo') + + return context + + def form_valid(self, form): + context = self.get_context_data() + parameter_forms = context['parameter_forms'] + geo_form = context['geo_form'] + + # Сохраняем основной объект + self.object = form.save(commit=False) + self.object.updated_by = self.request.user.customuser + self.object.save() + + # Сохраняем связанные параметры + if parameter_forms.is_valid(): + instances = parameter_forms.save(commit=False) + for instance in instances: + instance.save() + instance.objitems.set([self.object]) + + # Сохраняем геоданные + geo_instance = None + if hasattr(self.object, 'geo_obj'): + geo_instance = self.object.geo_obj + + # Создаем или обновляем гео-объект + if geo_instance is None: + geo_instance = Geo(objitem=self.object) + + # Обновляем поля из geo_form + if geo_form.is_valid(): + geo_instance.location = geo_form.cleaned_data['location'] + geo_instance.comment = geo_form.cleaned_data['comment'] + geo_instance.is_average = geo_form.cleaned_data['is_average'] + + # Обрабатываем координаты геолокации + geo_longitude = self.request.POST.get('geo_longitude') + geo_latitude = self.request.POST.get('geo_latitude') + if geo_longitude and geo_latitude: + geo_instance.coords = Point(float(geo_longitude), float(geo_latitude)) + + # Обрабатываем координаты Кубсата + kupsat_longitude = self.request.POST.get('kupsat_longitude') + kupsat_latitude = self.request.POST.get('kupsat_latitude') + if kupsat_longitude and kupsat_latitude: + geo_instance.coords_kupsat = Point(float(kupsat_longitude), float(kupsat_latitude)) + + # Обрабатываем координаты оперативников + valid_longitude = self.request.POST.get('valid_longitude') + valid_latitude = self.request.POST.get('valid_latitude') + if valid_longitude and valid_latitude: + geo_instance.coords_valid = Point(float(valid_longitude), float(valid_latitude)) + + # Обрабатываем дату/время + timestamp_date = self.request.POST.get('timestamp_date') + timestamp_time = self.request.POST.get('timestamp_time') + if timestamp_date and timestamp_time: + naive_datetime = datetime.strptime(f"{timestamp_date} {timestamp_time}", "%Y-%m-%d %H:%M") + geo_instance.timestamp = naive_datetime + + geo_instance.save() + + messages.success(self.request, 'Объект успешно сохранён!') + return super().form_valid(form) + +class ObjItemCreateView(LoginRequiredMixin, UserPassesTestMixin, CreateView): + model = ObjItem + form_class = ObjItemForm + template_name = 'mainapp/objitem_form.html' + success_url = reverse_lazy('home') + + def test_func(self): + return self.request.user.customuser.role in ['admin', 'moderator'] + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + ParameterFormSet = modelformset_factory( + Parameter, + form=ParameterForm, + extra=1, + can_delete=True + ) + + context['parameter_forms'] = ParameterFormSet( + queryset=Parameter.objects.none(), + prefix='parameters' + ) + + context['geo_form'] = GeoForm(prefix='geo') + + return context + + def form_valid(self, form): + context = self.get_context_data() + parameter_forms = context['parameter_forms'] + geo_form = context['geo_form'] + + # Сохраняем основной объект + self.object = form.save(commit=False) + self.object.created_by = self.request.user.customuser + self.object.updated_by = self.request.user.customuser + self.object.save() + + # Сохраняем связанные параметры + if parameter_forms.is_valid(): + instances = parameter_forms.save(commit=False) + for instance in instances: + instance.save() + instance.objitems.add(self.object) + + # Создаем гео-объект + geo_instance = Geo(objitem=self.object) + + # Обновляем поля из geo_form + if geo_form.is_valid(): + geo_instance.location = geo_form.cleaned_data['location'] + geo_instance.comment = geo_form.cleaned_data['comment'] + geo_instance.is_average = geo_form.cleaned_data['is_average'] + + # Обрабатываем координаты геолокации + geo_longitude = self.request.POST.get('geo_longitude') + geo_latitude = self.request.POST.get('geo_latitude') + if geo_longitude and geo_latitude: + geo_instance.coords = Point(float(geo_longitude), float(geo_latitude)) + + # Обрабатываем координаты Кубсата + kupsat_longitude = self.request.POST.get('kupsat_longitude') + kupsat_latitude = self.request.POST.get('kupsat_latitude') + if kupsat_longitude and kupsat_latitude: + geo_instance.coords_kupsat = Point(float(kupsat_longitude), float(kupsat_latitude)) + + # Обрабатываем координаты оперативников + valid_longitude = self.request.POST.get('valid_longitude') + valid_latitude = self.request.POST.get('valid_latitude') + if valid_longitude and valid_latitude: + geo_instance.coords_valid = Point(float(valid_longitude), float(valid_latitude)) + + # Обрабатываем дату/время + timestamp_date = self.request.POST.get('timestamp_date') + timestamp_time = self.request.POST.get('timestamp_time') + if timestamp_date and timestamp_time: + naive_datetime = datetime.strptime(f"{timestamp_date} {timestamp_time}", "%Y-%m-%d %H:%M") + geo_instance.timestamp = naive_datetime + + geo_instance.save() + + messages.success(self.request, 'Объект успешно создан!') + return super().form_valid(form) + +class ObjItemDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView): + model = ObjItem + template_name = 'mainapp/objitem_confirm_delete.html' + success_url = reverse_lazy('home') + + def test_func(self): + return self.request.user.customuser.role in ['admin', 'moderator'] + + def delete(self, request, *args, **kwargs): + messages.success(self.request, 'Объект успешно удалён!') + return super().delete(request, *args, **kwargs) \ No newline at end of file