Переделал модель Parameter и связь с ObjItem

This commit is contained in:
2025-11-10 22:32:26 +03:00
parent b24ef940ce
commit 1b345a3fd9
9 changed files with 535 additions and 447 deletions

View File

@@ -1,30 +1,24 @@
# Standard library imports
from collections import defaultdict
from datetime import datetime
from io import BytesIO
# Django imports
from django.contrib import messages
from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth import logout
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.contrib.gis.geos import Point
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.paginator import Paginator
from django.db import models
from django.db.models import OuterRef, Prefetch, Subquery
from django.forms import inlineformset_factory, modelformset_factory
from django.db.models import F
from django.http import HttpResponse, JsonResponse
from django.shortcuts import redirect, render
from django.urls import reverse_lazy
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.http import require_GET
from django.views.generic import (
CreateView,
DeleteView,
FormView,
TemplateView,
UpdateView,
)
@@ -45,18 +39,17 @@ from .forms import (
VchLinkForm,
)
from .mixins import CoordinateProcessingMixin, FormMessageMixin, RoleRequiredMixin
from .models import Geo, Modulation, ObjItem, Parameter, Polarization, Satellite
from .models import Geo, Modulation, ObjItem, Polarization, Satellite
from .utils import (
add_satellite_list,
compare_and_link_vch_load,
fill_data_from_df,
get_first_param_subquery,
get_points_from_csv,
get_vch_load_from_html,
kub_report,
parse_pagination_params,
)
from mapsapp.utils import parse_transponders_from_json, parse_transponders_from_xml
from mapsapp.utils import parse_transponders_from_xml
class AddSatellitesView(LoginRequiredMixin, View):
@@ -150,9 +143,12 @@ class LoadExcelDataView(LoginRequiredMixin, FormMessageMixin, FormView):
class GetLocationsView(LoginRequiredMixin, View):
def get(self, request, sat_id):
locations = (
ObjItem.objects.filter(parameters_obj__id_satellite=sat_id)
.select_related("geo_obj")
.prefetch_related("parameters_obj__polarization")
ObjItem.objects.filter(parameter_obj__id_satellite=sat_id)
.select_related(
"geo_obj",
"parameter_obj",
"parameter_obj__polarization",
)
)
if not locations.exists():
@@ -163,11 +159,10 @@ class GetLocationsView(LoginRequiredMixin, View):
if not hasattr(loc, "geo_obj") or not loc.geo_obj or not loc.geo_obj.coords:
continue
params = list(loc.parameters_obj.all())
if not params:
param = getattr(loc, 'parameter_obj', None)
if not param:
continue
param = params[0]
features.append(
{
"type": "Feature",
@@ -220,11 +215,12 @@ class ShowMapView(RoleRequiredMixin, View):
points = []
if ids:
id_list = [int(x) for x in ids.split(",") if x.isdigit()]
locations = ObjItem.objects.filter(id__in=id_list).prefetch_related(
"parameters_obj__id_satellite",
"parameters_obj__polarization",
"parameters_obj__modulation",
"parameters_obj__standard",
locations = ObjItem.objects.filter(id__in=id_list).select_related(
"parameter_obj",
"parameter_obj__id_satellite",
"parameter_obj__polarization",
"parameter_obj__modulation",
"parameter_obj__standard",
"geo_obj",
)
for obj in locations:
@@ -234,7 +230,9 @@ class ShowMapView(RoleRequiredMixin, View):
or not obj.geo_obj.coords
):
continue
param = obj.parameters_obj.get()
param = getattr(obj, 'parameter_obj', None)
if not param:
continue
points.append(
{
"name": f"{obj.name}",
@@ -265,11 +263,12 @@ class ShowSelectedObjectsMapView(LoginRequiredMixin, View):
points = []
if ids:
id_list = [int(x) for x in ids.split(",") if x.isdigit()]
locations = ObjItem.objects.filter(id__in=id_list).prefetch_related(
"parameters_obj__id_satellite",
"parameters_obj__polarization",
"parameters_obj__modulation",
"parameters_obj__standard",
locations = ObjItem.objects.filter(id__in=id_list).select_related(
"parameter_obj",
"parameter_obj__id_satellite",
"parameter_obj__polarization",
"parameter_obj__modulation",
"parameter_obj__standard",
"geo_obj",
)
for obj in locations:
@@ -279,7 +278,9 @@ class ShowSelectedObjectsMapView(LoginRequiredMixin, View):
or not obj.geo_obj.coords
):
continue
param = obj.parameters_obj.get()
param = getattr(obj, 'parameter_obj', None)
if not param:
continue
points.append(
{
"name": f"{obj.name}",
@@ -429,7 +430,7 @@ class DeleteSelectedObjectsView(RoleRequiredMixin, View):
class ObjItemListView(LoginRequiredMixin, View):
def get(self, request):
satellites = (
Satellite.objects.filter(parameters__objitems__isnull=False)
Satellite.objects.filter(parameters__objitem__isnull=False)
.distinct()
.only("id", "name")
.order_by("name")
@@ -472,32 +473,31 @@ class ObjItemListView(LoginRequiredMixin, View):
"geo_obj",
"updated_by__user",
"created_by__user",
"parameter_obj",
"parameter_obj__id_satellite",
"parameter_obj__polarization",
"parameter_obj__modulation",
"parameter_obj__standard",
)
.prefetch_related(
"parameters_obj__id_satellite",
"parameters_obj__polarization",
"parameters_obj__modulation",
"parameters_obj__standard",
)
.filter(parameters_obj__id_satellite_id__in=selected_satellites)
.filter(parameter_obj__id_satellite_id__in=selected_satellites)
)
else:
objects = ObjItem.objects.select_related(
"geo_obj",
"updated_by__user",
"created_by__user",
).prefetch_related(
"parameters_obj__id_satellite",
"parameters_obj__polarization",
"parameters_obj__modulation",
"parameters_obj__standard",
"parameter_obj",
"parameter_obj__id_satellite",
"parameter_obj__polarization",
"parameter_obj__modulation",
"parameter_obj__standard",
)
if freq_min is not None and freq_min.strip() != "":
try:
freq_min_val = float(freq_min)
objects = objects.filter(
parameters_obj__frequency__gte=freq_min_val
parameter_obj__frequency__gte=freq_min_val
)
except ValueError:
pass
@@ -505,7 +505,7 @@ class ObjItemListView(LoginRequiredMixin, View):
try:
freq_max_val = float(freq_max)
objects = objects.filter(
parameters_obj__frequency__lte=freq_max_val
parameter_obj__frequency__lte=freq_max_val
)
except ValueError:
pass
@@ -514,7 +514,7 @@ class ObjItemListView(LoginRequiredMixin, View):
try:
range_min_val = float(range_min)
objects = objects.filter(
parameters_obj__freq_range__gte=range_min_val
parameter_obj__freq_range__gte=range_min_val
)
except ValueError:
pass
@@ -522,7 +522,7 @@ class ObjItemListView(LoginRequiredMixin, View):
try:
range_max_val = float(range_max)
objects = objects.filter(
parameters_obj__freq_range__lte=range_max_val
parameter_obj__freq_range__lte=range_max_val
)
except ValueError:
pass
@@ -530,13 +530,13 @@ class ObjItemListView(LoginRequiredMixin, View):
if snr_min is not None and snr_min.strip() != "":
try:
snr_min_val = float(snr_min)
objects = objects.filter(parameters_obj__snr__gte=snr_min_val)
objects = objects.filter(parameter_obj__snr__gte=snr_min_val)
except ValueError:
pass
if snr_max is not None and snr_max.strip() != "":
try:
snr_max_val = float(snr_max)
objects = objects.filter(parameters_obj__snr__lte=snr_max_val)
objects = objects.filter(parameter_obj__snr__lte=snr_max_val)
except ValueError:
pass
@@ -544,7 +544,7 @@ class ObjItemListView(LoginRequiredMixin, View):
try:
bod_min_val = float(bod_min)
objects = objects.filter(
parameters_obj__bod_velocity__gte=bod_min_val
parameter_obj__bod_velocity__gte=bod_min_val
)
except ValueError:
pass
@@ -552,19 +552,19 @@ class ObjItemListView(LoginRequiredMixin, View):
try:
bod_max_val = float(bod_max)
objects = objects.filter(
parameters_obj__bod_velocity__lte=bod_max_val
parameter_obj__bod_velocity__lte=bod_max_val
)
except ValueError:
pass
if selected_modulations:
objects = objects.filter(
parameters_obj__modulation__id__in=selected_modulations
parameter_obj__modulation__id__in=selected_modulations
)
if selected_polarizations:
objects = objects.filter(
parameters_obj__polarization__id__in=selected_polarizations
parameter_obj__polarization__id__in=selected_polarizations
)
if has_kupsat == "1":
@@ -609,22 +609,14 @@ class ObjItemListView(LoginRequiredMixin, View):
else:
selected_sat_id = None
first_param_freq_subq = get_first_param_subquery("frequency")
first_param_range_subq = get_first_param_subquery("freq_range")
first_param_snr_subq = get_first_param_subquery("snr")
first_param_bod_subq = get_first_param_subquery("bod_velocity")
first_param_sat_name_subq = get_first_param_subquery("id_satellite__name")
first_param_pol_name_subq = get_first_param_subquery("polarization__name")
first_param_mod_name_subq = get_first_param_subquery("modulation__name")
objects = objects.annotate(
first_param_freq=Subquery(first_param_freq_subq),
first_param_range=Subquery(first_param_range_subq),
first_param_snr=Subquery(first_param_snr_subq),
first_param_bod=Subquery(first_param_bod_subq),
first_param_sat_name=Subquery(first_param_sat_name_subq),
first_param_pol_name=Subquery(first_param_pol_name_subq),
first_param_mod_name=Subquery(first_param_mod_name_subq),
first_param_freq=F("parameter_obj__frequency"),
first_param_range=F("parameter_obj__freq_range"),
first_param_snr=F("parameter_obj__snr"),
first_param_bod=F("parameter_obj__bod_velocity"),
first_param_sat_name=F("parameter_obj__id_satellite__name"),
first_param_pol_name=F("parameter_obj__polarization__name"),
first_param_mod_name=F("parameter_obj__modulation__name"),
)
valid_sort_fields = {
@@ -664,11 +656,7 @@ class ObjItemListView(LoginRequiredMixin, View):
processed_objects = []
for obj in page_obj:
param = None
if hasattr(obj, "parameters_obj") and obj.parameters_obj.all():
param_list = list(obj.parameters_obj.all())
if param_list:
param = param_list[0]
param = getattr(obj, 'parameter_obj', None)
geo_coords = "-"
geo_timestamp = "-"
@@ -874,40 +862,33 @@ class ObjItemFormView(
# Сохраняем параметры возврата для кнопки "Назад"
context["return_params"] = self.request.GET.get('return_params', '')
ParameterFormSet = modelformset_factory(
Parameter,
form=ParameterForm,
extra=self.get_parameter_formset_extra(),
can_delete=True,
)
if self.object:
parameter_queryset = self.object.parameters_obj.all()
context["parameter_forms"] = ParameterFormSet(
queryset=parameter_queryset, prefix="parameters"
# Работаем с одной формой параметра вместо formset
if self.object and hasattr(self.object, "parameter_obj") and self.object.parameter_obj:
context["parameter_form"] = ParameterForm(
instance=self.object.parameter_obj, prefix="parameter"
)
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["parameter_form"] = ParameterForm(prefix="parameter")
if self.object and hasattr(self.object, "geo_obj") and self.object.geo_obj:
context["geo_form"] = GeoForm(
instance=self.object.geo_obj, prefix="geo"
)
else:
context["geo_form"] = GeoForm(prefix="geo")
return context
def get_parameter_formset_extra(self):
"""Возвращает количество дополнительных форм для параметров."""
return 0 if self.object else 1
def form_valid(self, form):
context = self.get_context_data()
parameter_forms = context["parameter_forms"]
# Получаем форму параметра
if self.object and hasattr(self.object, "parameter_obj") and self.object.parameter_obj:
parameter_form = ParameterForm(
self.request.POST,
instance=self.object.parameter_obj,
prefix="parameter"
)
else:
parameter_form = ParameterForm(self.request.POST, prefix="parameter")
if self.object and hasattr(self.object, "geo_obj") and self.object.geo_obj:
geo_form = GeoForm(self.request.POST, instance=self.object.geo_obj, prefix="geo")
@@ -919,17 +900,26 @@ class ObjItemFormView(
self.set_user_fields()
self.object.save()
# Сохраняем связанные параметры
if parameter_forms.is_valid():
self.save_parameters(parameter_forms)
# Сохраняем связанный параметр
if parameter_form.is_valid():
self.save_parameter(parameter_form)
else:
context = self.get_context_data()
context.update({
'form': form,
'parameter_form': parameter_form,
'geo_form': geo_form,
})
return self.render_to_response(context)
# Сохраняем геоданные
if geo_form.is_valid():
self.save_geo_data(geo_form)
else:
context = self.get_context_data()
context.update({
'form': form,
'parameter_forms': parameter_forms,
'parameter_form': parameter_form,
'geo_form': geo_form,
})
return self.render_to_response(context)
@@ -940,51 +930,12 @@ class ObjItemFormView(
"""Устанавливает поля пользователя для объекта."""
raise NotImplementedError("Subclasses must implement set_user_fields()")
def save_parameters(self, parameter_forms):
"""Сохраняет параметры объекта с проверкой дубликатов."""
instances = parameter_forms.save(commit=False)
# Обрабатываем удаленные параметры
for deleted_obj in parameter_forms.deleted_objects:
# Отвязываем параметр от объекта
deleted_obj.objitems.remove(self.object)
# Если параметр больше не связан ни с одним объектом, удаляем его
if not deleted_obj.objitems.exists():
deleted_obj.delete()
for instance in instances:
# Проверяем, существует ли уже такая ВЧ загрузка
existing_param = Parameter.objects.filter(
id_satellite=instance.id_satellite,
polarization=instance.polarization,
frequency=instance.frequency,
freq_range=instance.freq_range,
bod_velocity=instance.bod_velocity,
modulation=instance.modulation,
snr=instance.snr,
standard=instance.standard,
).exclude(pk=instance.pk if instance.pk else None).first()
if existing_param:
# Если найден дубликат, удаляем старую запись из объекта
if instance.pk:
# Отвязываем старый параметр от объекта
instance.objitems.remove(self.object)
# Если старый параметр больше не связан ни с одним объектом, удаляем его
if not instance.objitems.exists():
instance.delete()
# Используем существующий параметр
self.link_parameter_to_object(existing_param)
else:
# Сохраняем новый параметр
instance.save()
self.link_parameter_to_object(instance)
def link_parameter_to_object(self, parameter):
"""Связывает параметр с объектом."""
raise NotImplementedError(
"Subclasses must implement link_parameter_to_object()"
)
def save_parameter(self, parameter_form):
"""Сохраняет параметр объекта через OneToOne связь."""
if parameter_form.is_valid():
instance = parameter_form.save(commit=False)
instance.objitem = self.object
instance.save()
def save_geo_data(self, geo_form):
"""Сохраняет геоданные объекта."""
@@ -1019,11 +970,6 @@ class ObjItemUpdateView(ObjItemFormView):
def set_user_fields(self):
self.object.updated_by = self.request.user.customuser
def link_parameter_to_object(self, parameter):
# Добавляем объект к параметру, если его там еще нет
if self.object not in parameter.objitems.all():
parameter.objitems.add(self.object)
class ObjItemCreateView(ObjItemFormView, CreateView):
"""Представление для создания ObjItem."""
@@ -1034,9 +980,6 @@ class ObjItemCreateView(ObjItemFormView, CreateView):
self.object.created_by = self.request.user.customuser
self.object.updated_by = self.request.user.customuser
def link_parameter_to_object(self, parameter):
parameter.objitems.add(self.object)
class ObjItemDeleteView(RoleRequiredMixin, FormMessageMixin, DeleteView):
model = ObjItem
@@ -1066,11 +1009,11 @@ class ObjItemDetailView(LoginRequiredMixin, View):
'geo_obj',
'updated_by__user',
'created_by__user',
).prefetch_related(
'parameters_obj__id_satellite',
'parameters_obj__polarization',
'parameters_obj__modulation',
'parameters_obj__standard',
'parameter_obj',
'parameter_obj__id_satellite',
'parameter_obj__polarization',
'parameter_obj__modulation',
'parameter_obj__standard',
).first()
if not obj: