Переделал модель Parameter и связь с ObjItem
This commit is contained in:
@@ -218,11 +218,11 @@ def export_objects_to_csv(modeladmin, request, queryset):
|
||||
queryset = queryset.select_related(
|
||||
'geo_obj',
|
||||
'created_by__user',
|
||||
'updated_by__user'
|
||||
).prefetch_related(
|
||||
'parameters_obj__id_satellite',
|
||||
'parameters_obj__polarization',
|
||||
'parameters_obj__modulation'
|
||||
'updated_by__user',
|
||||
'parameter_obj',
|
||||
'parameter_obj__id_satellite',
|
||||
'parameter_obj__polarization',
|
||||
'parameter_obj__modulation'
|
||||
)
|
||||
|
||||
response = HttpResponse(content_type='text/csv; charset=utf-8')
|
||||
@@ -248,7 +248,7 @@ def export_objects_to_csv(modeladmin, request, queryset):
|
||||
])
|
||||
|
||||
for obj in queryset:
|
||||
param = next(iter(obj.parameters_obj.all()), None)
|
||||
param = getattr(obj, 'parameter_obj', None)
|
||||
geo = obj.geo_obj
|
||||
|
||||
# Форматирование координат
|
||||
@@ -284,12 +284,25 @@ def export_objects_to_csv(modeladmin, request, queryset):
|
||||
# Inline Admin Classes
|
||||
# ============================================================================
|
||||
|
||||
class ParameterObjItemInline(admin.StackedInline):
|
||||
model = ObjItem.parameters_obj.through
|
||||
class ParameterInline(admin.StackedInline):
|
||||
"""Inline для редактирования параметра объекта."""
|
||||
model = Parameter
|
||||
extra = 0
|
||||
max_num = 1
|
||||
can_delete = True
|
||||
verbose_name = "ВЧ загрузка"
|
||||
verbose_name_plural = "ВЧ загрузки"
|
||||
verbose_name_plural = "ВЧ загрузка"
|
||||
fields = (
|
||||
'id_satellite',
|
||||
'frequency',
|
||||
'freq_range',
|
||||
'polarization',
|
||||
'modulation',
|
||||
'bod_velocity',
|
||||
'snr',
|
||||
'standard'
|
||||
)
|
||||
autocomplete_fields = ('id_satellite', 'polarization', 'modulation', 'standard')
|
||||
|
||||
|
||||
# ============================================================================
|
||||
@@ -370,13 +383,15 @@ class ParameterAdmin(ImportExportActionModelAdmin, BaseAdmin):
|
||||
"bod_velocity",
|
||||
"snr",
|
||||
"standard",
|
||||
"related_objitem",
|
||||
"sigma_parameter"
|
||||
)
|
||||
list_display_links = ("frequency", "id_satellite")
|
||||
list_select_related = ("polarization", "modulation", "standard", "id_satellite")
|
||||
list_select_related = ("polarization", "modulation", "standard", "id_satellite", "objitem")
|
||||
|
||||
list_filter = (
|
||||
HasSigmaParameterFilter,
|
||||
("objitem", MultiSelectRelatedDropdownFilter),
|
||||
("id_satellite", MultiSelectRelatedDropdownFilter),
|
||||
("polarization__name", MultiSelectDropdownFilter),
|
||||
("modulation", MultiSelectRelatedDropdownFilter),
|
||||
@@ -395,12 +410,21 @@ class ParameterAdmin(ImportExportActionModelAdmin, BaseAdmin):
|
||||
"modulation__name",
|
||||
"polarization__name",
|
||||
"standard__name",
|
||||
"objitem__name",
|
||||
)
|
||||
|
||||
ordering = ("-frequency",)
|
||||
autocomplete_fields = ("objitems",)
|
||||
autocomplete_fields = ("objitem",)
|
||||
inlines = [SigmaParameterInline]
|
||||
|
||||
def related_objitem(self, obj):
|
||||
"""Отображает связанный ObjItem."""
|
||||
if hasattr(obj, 'objitem') and obj.objitem:
|
||||
return obj.objitem.name
|
||||
return "-"
|
||||
related_objitem.short_description = "Объект"
|
||||
related_objitem.admin_order_field = "objitem__name"
|
||||
|
||||
def sigma_parameter(self, obj):
|
||||
"""Отображает связанный параметр Sigma."""
|
||||
sigma_obj = obj.sigma_parameter.all()
|
||||
@@ -636,16 +660,25 @@ class ObjItemAdmin(BaseAdmin):
|
||||
"updated_at",
|
||||
)
|
||||
list_display_links = ("name",)
|
||||
list_select_related = ("geo_obj", "created_by__user", "updated_by__user")
|
||||
list_select_related = (
|
||||
"geo_obj",
|
||||
"created_by__user",
|
||||
"updated_by__user",
|
||||
"parameter_obj",
|
||||
"parameter_obj__id_satellite",
|
||||
"parameter_obj__polarization",
|
||||
"parameter_obj__modulation",
|
||||
"parameter_obj__standard"
|
||||
)
|
||||
|
||||
list_filter = (
|
||||
UniqueToggleFilter,
|
||||
("parameters_obj__id_satellite", MultiSelectRelatedDropdownFilter),
|
||||
("parameters_obj__frequency", NumericRangeFilterBuilder()),
|
||||
("parameters_obj__freq_range", NumericRangeFilterBuilder()),
|
||||
("parameters_obj__snr", NumericRangeFilterBuilder()),
|
||||
("parameters_obj__modulation", MultiSelectRelatedDropdownFilter),
|
||||
("parameters_obj__polarization", MultiSelectRelatedDropdownFilter),
|
||||
("parameter_obj__id_satellite", MultiSelectRelatedDropdownFilter),
|
||||
("parameter_obj__frequency", NumericRangeFilterBuilder()),
|
||||
("parameter_obj__freq_range", NumericRangeFilterBuilder()),
|
||||
("parameter_obj__snr", NumericRangeFilterBuilder()),
|
||||
("parameter_obj__modulation", MultiSelectRelatedDropdownFilter),
|
||||
("parameter_obj__polarization", MultiSelectRelatedDropdownFilter),
|
||||
GeoKupDistanceFilter,
|
||||
GeoValidDistanceFilter,
|
||||
("created_at", DateRangeQuickSelectListFilterBuilder()),
|
||||
@@ -655,12 +688,12 @@ class ObjItemAdmin(BaseAdmin):
|
||||
search_fields = (
|
||||
"name",
|
||||
"geo_obj__location",
|
||||
"parameters_obj__frequency",
|
||||
"parameters_obj__id_satellite__name",
|
||||
"parameter_obj__frequency",
|
||||
"parameter_obj__id_satellite__name",
|
||||
)
|
||||
|
||||
ordering = ("-updated_at",)
|
||||
inlines = [ParameterObjItemInline, GeoInline]
|
||||
inlines = [GeoInline, ParameterInline]
|
||||
actions = [show_selected_on_map, export_objects_to_csv]
|
||||
readonly_fields = ("created_at", "created_by", "updated_at", "updated_by")
|
||||
|
||||
@@ -676,7 +709,7 @@ class ObjItemAdmin(BaseAdmin):
|
||||
|
||||
def get_queryset(self, request):
|
||||
"""
|
||||
Оптимизированный queryset с использованием select_related и prefetch_related.
|
||||
Оптимизированный queryset с использованием select_related.
|
||||
|
||||
Загружает связанные объекты одним запросом для улучшения производительности.
|
||||
"""
|
||||
@@ -684,31 +717,30 @@ class ObjItemAdmin(BaseAdmin):
|
||||
return qs.select_related(
|
||||
"geo_obj",
|
||||
"created_by__user",
|
||||
"updated_by__user"
|
||||
).prefetch_related(
|
||||
"parameters_obj__id_satellite",
|
||||
"parameters_obj__polarization",
|
||||
"parameters_obj__modulation",
|
||||
"parameters_obj__standard"
|
||||
"updated_by__user",
|
||||
"parameter_obj",
|
||||
"parameter_obj__id_satellite",
|
||||
"parameter_obj__polarization",
|
||||
"parameter_obj__modulation",
|
||||
"parameter_obj__standard"
|
||||
)
|
||||
|
||||
def sat_name(self, obj):
|
||||
"""Отображает название спутника из связанного параметра."""
|
||||
param = next(iter(obj.parameters_obj.all()), None)
|
||||
if param and param.id_satellite:
|
||||
return param.id_satellite.name
|
||||
if hasattr(obj, 'parameter_obj') and obj.parameter_obj:
|
||||
if obj.parameter_obj.id_satellite:
|
||||
return obj.parameter_obj.id_satellite.name
|
||||
return "-"
|
||||
sat_name.short_description = "Спутник"
|
||||
sat_name.admin_order_field = "parameters_obj__id_satellite__name"
|
||||
sat_name.admin_order_field = "parameter_obj__id_satellite__name"
|
||||
|
||||
def freq(self, obj):
|
||||
"""Отображает частоту из связанного параметра."""
|
||||
param = next(iter(obj.parameters_obj.all()), None)
|
||||
if param:
|
||||
return param.frequency
|
||||
if hasattr(obj, 'parameter_obj') and obj.parameter_obj:
|
||||
return obj.parameter_obj.frequency
|
||||
return "-"
|
||||
freq.short_description = "Частота, МГц"
|
||||
freq.admin_order_field = "parameters_obj__frequency"
|
||||
freq.admin_order_field = "parameter_obj__frequency"
|
||||
|
||||
def distance_geo_kup(self, obj):
|
||||
"""Отображает расстояние между геолокацией и Кубсатом."""
|
||||
@@ -736,42 +768,39 @@ class ObjItemAdmin(BaseAdmin):
|
||||
|
||||
def pol(self, obj):
|
||||
"""Отображает поляризацию из связанного параметра."""
|
||||
param = next(iter(obj.parameters_obj.all()), None)
|
||||
if param and param.polarization:
|
||||
return param.polarization.name
|
||||
if hasattr(obj, 'parameter_obj') and obj.parameter_obj:
|
||||
if obj.parameter_obj.polarization:
|
||||
return obj.parameter_obj.polarization.name
|
||||
return "-"
|
||||
pol.short_description = "Поляризация"
|
||||
|
||||
def freq_range(self, obj):
|
||||
"""Отображает полосу частот из связанного параметра."""
|
||||
param = next(iter(obj.parameters_obj.all()), None)
|
||||
if param:
|
||||
return param.freq_range
|
||||
if hasattr(obj, 'parameter_obj') and obj.parameter_obj:
|
||||
return obj.parameter_obj.freq_range
|
||||
return "-"
|
||||
freq_range.short_description = "Полоса, МГц"
|
||||
freq_range.admin_order_field = "parameters_obj__freq_range"
|
||||
freq_range.admin_order_field = "parameter_obj__freq_range"
|
||||
|
||||
def bod_velocity(self, obj):
|
||||
"""Отображает символьную скорость из связанного параметра."""
|
||||
param = next(iter(obj.parameters_obj.all()), None)
|
||||
if param:
|
||||
return param.bod_velocity
|
||||
if hasattr(obj, 'parameter_obj') and obj.parameter_obj:
|
||||
return obj.parameter_obj.bod_velocity
|
||||
return "-"
|
||||
bod_velocity.short_description = "Сим. v, БОД"
|
||||
|
||||
def modulation(self, obj):
|
||||
"""Отображает модуляцию из связанного параметра."""
|
||||
param = next(iter(obj.parameters_obj.all()), None)
|
||||
if param and param.modulation:
|
||||
return param.modulation.name
|
||||
if hasattr(obj, 'parameter_obj') and obj.parameter_obj:
|
||||
if obj.parameter_obj.modulation:
|
||||
return obj.parameter_obj.modulation.name
|
||||
return "-"
|
||||
modulation.short_description = "Модуляция"
|
||||
|
||||
def snr(self, obj):
|
||||
"""Отображает отношение сигнал/шум из связанного параметра."""
|
||||
param = next(iter(obj.parameters_obj.all()), None)
|
||||
if param:
|
||||
return param.snr
|
||||
if hasattr(obj, 'parameter_obj') and obj.parameter_obj:
|
||||
return obj.parameter_obj.snr
|
||||
return "-"
|
||||
snr.short_description = "ОСШ"
|
||||
|
||||
|
||||
@@ -108,6 +108,12 @@ class NewEventForm(forms.Form):
|
||||
})
|
||||
)
|
||||
class ParameterForm(forms.ModelForm):
|
||||
"""
|
||||
Форма для создания и редактирования параметров ВЧ загрузки.
|
||||
|
||||
Работает с одним экземпляром Parameter, связанным с ObjItem через OneToOne связь.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = Parameter
|
||||
fields = [
|
||||
@@ -115,22 +121,92 @@ class ParameterForm(forms.ModelForm):
|
||||
'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=[]),
|
||||
'id_satellite': forms.Select(attrs={
|
||||
'class': 'form-select',
|
||||
'required': True
|
||||
}),
|
||||
'frequency': forms.NumberInput(attrs={
|
||||
'class': 'form-control',
|
||||
'step': '0.000001',
|
||||
'min': '0',
|
||||
'max': '50000',
|
||||
'placeholder': 'Введите частоту в МГц'
|
||||
}),
|
||||
'freq_range': forms.NumberInput(attrs={
|
||||
'class': 'form-control',
|
||||
'step': '0.000001',
|
||||
'min': '0',
|
||||
'max': '1000',
|
||||
'placeholder': 'Введите полосу частот в МГц'
|
||||
}),
|
||||
'bod_velocity': forms.NumberInput(attrs={
|
||||
'class': 'form-control',
|
||||
'step': '0.001',
|
||||
'min': '0',
|
||||
'placeholder': 'Введите символьную скорость в БОД'
|
||||
}),
|
||||
'snr': forms.NumberInput(attrs={
|
||||
'class': 'form-control',
|
||||
'step': '0.001',
|
||||
'min': '-50',
|
||||
'max': '100',
|
||||
'placeholder': 'Введите ОСШ в дБ'
|
||||
}),
|
||||
'polarization': forms.Select(attrs={'class': 'form-select'}),
|
||||
'modulation': forms.Select(attrs={'class': 'form-select'}),
|
||||
'standard': forms.Select(attrs={'class': 'form-select'}),
|
||||
}
|
||||
labels = {
|
||||
'id_satellite': 'Спутник',
|
||||
'frequency': 'Частота (МГц)',
|
||||
'freq_range': 'Полоса частот (МГц)',
|
||||
'polarization': 'Поляризация',
|
||||
'bod_velocity': 'Символьная скорость (БОД)',
|
||||
'modulation': 'Модуляция',
|
||||
'snr': 'ОСШ (дБ)',
|
||||
'standard': 'Стандарт',
|
||||
}
|
||||
help_texts = {
|
||||
'frequency': 'Частота в диапазоне от 0 до 50000 МГц',
|
||||
'freq_range': 'Полоса частот в диапазоне от 0 до 1000 МГц',
|
||||
'bod_velocity': 'Символьная скорость должна быть положительной',
|
||||
'snr': 'Отношение сигнал/шум в диапазоне от -50 до 100 дБ',
|
||||
}
|
||||
|
||||
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()]
|
||||
|
||||
# Динамически загружаем choices для select полей
|
||||
self.fields['id_satellite'].queryset = Satellite.objects.all().order_by('name')
|
||||
self.fields['polarization'].queryset = Polarization.objects.all().order_by('name')
|
||||
self.fields['modulation'].queryset = Modulation.objects.all().order_by('name')
|
||||
self.fields['standard'].queryset = Standard.objects.all().order_by('name')
|
||||
|
||||
# Делаем спутник обязательным полем
|
||||
self.fields['id_satellite'].required = True
|
||||
|
||||
def clean(self):
|
||||
"""
|
||||
Дополнительная валидация формы.
|
||||
|
||||
Проверяет соотношение между частотой, полосой частот и символьной скоростью.
|
||||
"""
|
||||
cleaned_data = super().clean()
|
||||
frequency = cleaned_data.get('frequency')
|
||||
freq_range = cleaned_data.get('freq_range')
|
||||
bod_velocity = cleaned_data.get('bod_velocity')
|
||||
|
||||
# Проверка что частота больше полосы частот
|
||||
if frequency and freq_range:
|
||||
if freq_range > frequency:
|
||||
self.add_error('freq_range', 'Полоса частот не может быть больше частоты')
|
||||
|
||||
# Проверка что символьная скорость соответствует полосе частот
|
||||
if bod_velocity and freq_range:
|
||||
if bod_velocity > freq_range * 1000000: # Конвертация МГц в Гц
|
||||
self.add_error('bod_velocity', 'Символьная скорость не может превышать полосу частот')
|
||||
|
||||
return cleaned_data
|
||||
|
||||
class GeoForm(forms.ModelForm):
|
||||
class Meta:
|
||||
@@ -143,9 +219,49 @@ class GeoForm(forms.ModelForm):
|
||||
}
|
||||
|
||||
class ObjItemForm(forms.ModelForm):
|
||||
"""
|
||||
Форма для создания и редактирования объектов (источников сигнала).
|
||||
|
||||
Работает с моделью ObjItem. Параметры ВЧ загрузки обрабатываются отдельно
|
||||
через ParameterForm с использованием OneToOne связи.
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
model = ObjItem
|
||||
fields = ['name']
|
||||
widgets = {
|
||||
'name': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
'name': forms.TextInput(attrs={
|
||||
'class': 'form-control',
|
||||
'placeholder': 'Введите название объекта',
|
||||
'maxlength': '100'
|
||||
}),
|
||||
}
|
||||
labels = {
|
||||
'name': 'Название объекта',
|
||||
}
|
||||
help_texts = {
|
||||
'name': 'Уникальное название объекта/источника сигнала',
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# Делаем поле name необязательным, так как оно может быть пустым
|
||||
self.fields['name'].required = False
|
||||
|
||||
def clean_name(self):
|
||||
"""
|
||||
Валидация поля name.
|
||||
|
||||
Проверяет что название не состоит только из пробелов.
|
||||
"""
|
||||
name = self.cleaned_data.get('name')
|
||||
|
||||
if name:
|
||||
# Удаляем лишние пробелы
|
||||
name = name.strip()
|
||||
|
||||
# Проверяем что после удаления пробелов что-то осталось
|
||||
if not name:
|
||||
raise forms.ValidationError('Название не может состоять только из пробелов')
|
||||
|
||||
return name
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-10 18:39
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainapp', '0006_alter_customuser_options_alter_geo_options_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='parameter',
|
||||
name='objitems',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='parameter',
|
||||
name='objitem',
|
||||
field=models.OneToOneField(blank=True, help_text='Связанный объект', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='parameter_obj', to='mainapp.objitem', verbose_name='Объект'),
|
||||
),
|
||||
]
|
||||
@@ -243,11 +243,11 @@ class ObjItemQuerySet(models.QuerySet):
|
||||
"updated_by__user",
|
||||
"created_by__user",
|
||||
"source_type_obj",
|
||||
).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",
|
||||
)
|
||||
|
||||
def recent(self, days=30):
|
||||
@@ -449,8 +449,14 @@ class Parameter(models.Model):
|
||||
verbose_name="Стандарт",
|
||||
)
|
||||
# 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
|
||||
objitem = models.OneToOneField(
|
||||
ObjItem,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="parameter_obj",
|
||||
verbose_name="Объект",
|
||||
null=True,
|
||||
blank=True,
|
||||
help_text="Связанный объект"
|
||||
)
|
||||
# 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)
|
||||
|
||||
@@ -116,37 +116,36 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ВЧ загрузки -->
|
||||
<!-- ВЧ загрузка -->
|
||||
<div class="form-section">
|
||||
<div class="form-section-header">
|
||||
<h4>ВЧ загрузка</h4>
|
||||
</div>
|
||||
|
||||
{% for param in object.parameters_obj.all %}
|
||||
<div class="dynamic-form" data-parameter-index="{{ forloop.counter0 }}">
|
||||
{% if object.parameter_obj %}
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Спутник:</label>
|
||||
<div class="readonly-field">{{ param.id_satellite.name|default:"-" }}</div>
|
||||
<div class="readonly-field">{{ object.parameter_obj.id_satellite.name|default:"-" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Частота (МГц):</label>
|
||||
<div class="readonly-field">{{ param.frequency|default:"-" }}</div>
|
||||
<div class="readonly-field">{{ object.parameter_obj.frequency|default:"-" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Полоса (МГц):</label>
|
||||
<div class="readonly-field">{{ param.freq_range|default:"-" }}</div>
|
||||
<div class="readonly-field">{{ object.parameter_obj.freq_range|default:"-" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Поляризация:</label>
|
||||
<div class="readonly-field">{{ param.polarization.name|default:"-" }}</div>
|
||||
<div class="readonly-field">{{ object.parameter_obj.polarization.name|default:"-" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -154,34 +153,33 @@
|
||||
<div class="col-md-3">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Символьная скорость:</label>
|
||||
<div class="readonly-field">{{ param.bod_velocity|default:"-" }}</div>
|
||||
<div class="readonly-field">{{ object.parameter_obj.bod_velocity|default:"-" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Модуляция:</label>
|
||||
<div class="readonly-field">{{ param.modulation.name|default:"-" }}</div>
|
||||
<div class="readonly-field">{{ object.parameter_obj.modulation.name|default:"-" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">ОСШ:</label>
|
||||
<div class="readonly-field">{{ param.snr|default:"-" }}</div>
|
||||
<div class="readonly-field">{{ object.parameter_obj.snr|default:"-" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Стандарт:</label>
|
||||
<div class="readonly-field">{{ param.standard.name|default:"-" }}</div>
|
||||
<div class="readonly-field">{{ object.parameter_obj.standard.name|default:"-" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
{% else %}
|
||||
<div class="mb-3">
|
||||
<p>Нет данных о ВЧ загрузке</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Блок с картой -->
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
<h2>{% if object %}Редактировать объект: {{ object.name }}{% else %}Создать объект{% endif %}</h2>
|
||||
<div>
|
||||
{% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %}
|
||||
<button type="submit" class="btn btn-primary btn-action">Сохранить</button>
|
||||
<button type="submit" form="objitem-form" class="btn btn-primary btn-action">Сохранить</button>
|
||||
{% if object %}
|
||||
<a href="{% url 'mainapp:objitem_delete' object.id %}{% if request.GET.urlencode %}?{{ request.GET.urlencode }}{% endif %}" class="btn btn-danger btn-action">Удалить</a>
|
||||
{% endif %}
|
||||
@@ -73,7 +73,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post">
|
||||
<form method="post" id="objitem-form">
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Основная информация -->
|
||||
@@ -124,53 +124,41 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ВЧ загрузки -->
|
||||
<!-- ВЧ загрузка -->
|
||||
<div class="form-section">
|
||||
<div class="form-section-header d-flex justify-content-between align-items-center">
|
||||
<div class="form-section-header">
|
||||
<h4>ВЧ загрузка</h4>
|
||||
{% if not parameter_forms.forms.0.instance.pk %}
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" id="add-parameter">Добавить ВЧ загрузку</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div id="parameters-container">
|
||||
{% for param_form in parameter_forms %}
|
||||
{% comment %} <div class="dynamic-form" data-parameter-index="{{ forloop.counter0 }}"> {% endcomment %}
|
||||
<div class="dynamic-form-header">
|
||||
{% if parameter_forms.forms|length > 1 %}
|
||||
<button type="button" class="btn btn-sm btn-outline-danger remove-parameter">Удалить</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
{% include 'mainapp/components/_form_field.html' with field=param_form.id_satellite %}
|
||||
{% include 'mainapp/components/_form_field.html' with field=parameter_form.id_satellite %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
{% include 'mainapp/components/_form_field.html' with field=param_form.frequency %}
|
||||
{% include 'mainapp/components/_form_field.html' with field=parameter_form.frequency %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
{% include 'mainapp/components/_form_field.html' with field=param_form.freq_range %}
|
||||
{% include 'mainapp/components/_form_field.html' with field=parameter_form.freq_range %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
{% include 'mainapp/components/_form_field.html' with field=param_form.polarization %}
|
||||
{% include 'mainapp/components/_form_field.html' with field=parameter_form.polarization %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
{% include 'mainapp/components/_form_field.html' with field=param_form.bod_velocity %}
|
||||
{% include 'mainapp/components/_form_field.html' with field=parameter_form.bod_velocity %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
{% include 'mainapp/components/_form_field.html' with field=param_form.modulation %}
|
||||
{% include 'mainapp/components/_form_field.html' with field=parameter_form.modulation %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
{% include 'mainapp/components/_form_field.html' with field=param_form.snr %}
|
||||
{% include 'mainapp/components/_form_field.html' with field=parameter_form.snr %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
{% include 'mainapp/components/_form_field.html' with field=param_form.standard %}
|
||||
{% include 'mainapp/components/_form_field.html' with field=parameter_form.standard %}
|
||||
</div>
|
||||
</div>
|
||||
{% comment %} </div> {% endcomment %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -348,42 +336,6 @@
|
||||
|
||||
|
||||
<script>
|
||||
// Динамическое добавление ВЧ загрузок
|
||||
let parameterIndex = {{ parameter_forms|length }};
|
||||
|
||||
document.getElementById('add-parameter')?.addEventListener('click', function() {
|
||||
const container = document.getElementById('parameters-container');
|
||||
const template = document.querySelector('.dynamic-form');
|
||||
|
||||
if (template) {
|
||||
const clone = template.cloneNode(true);
|
||||
clone.querySelectorAll('[id]').forEach(el => {
|
||||
el.id = el.id.replace(/-\d+-/g, `-${parameterIndex}-`);
|
||||
});
|
||||
clone.querySelectorAll('[name]').forEach(el => {
|
||||
el.name = el.name.replace(/-\d+-/g, `-${parameterIndex}-`);
|
||||
});
|
||||
clone.querySelectorAll('[for]').forEach(el => {
|
||||
el.htmlFor = el.htmlFor.replace(/-\d+-/g, `-${parameterIndex}-`);
|
||||
});
|
||||
clone.querySelector('.dynamic-form-header h5').textContent = `ВЧ загрузка #${parameterIndex + 1}`;
|
||||
clone.dataset.parameterIndex = parameterIndex;
|
||||
container.appendChild(clone);
|
||||
parameterIndex++;
|
||||
}
|
||||
});
|
||||
|
||||
// Удаление ВЧ загрузок
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.classList.contains('remove-parameter')) {
|
||||
if (document.querySelectorAll('.dynamic-form').length > 1) {
|
||||
e.target.closest('.dynamic-form').remove();
|
||||
} else {
|
||||
alert('Должна быть хотя бы одна ВЧ загрузка');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Инициализация карты
|
||||
const map = L.map('map').setView([55.75, 37.62], 5);
|
||||
|
||||
@@ -163,16 +163,6 @@ def fill_data_from_df(df: pd.DataFrame, sat: Satellite, current_user=None):
|
||||
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,
|
||||
polarization=polarization_obj,
|
||||
frequency=freq,
|
||||
freq_range=freq_line,
|
||||
bod_velocity=v,
|
||||
modulation=mod_obj,
|
||||
snr=snr,
|
||||
)
|
||||
|
||||
geo, _ = Geo.objects.get_or_create(
|
||||
timestamp=timestamp,
|
||||
coords=geo_point,
|
||||
@@ -187,12 +177,38 @@ def fill_data_from_df(df: pd.DataFrame, sat: Satellite, current_user=None):
|
||||
geo.save()
|
||||
geo.mirrors.set(Mirror.objects.filter(name__in=current_mirrors))
|
||||
|
||||
existing_obj_items = ObjItem.objects.filter(
|
||||
parameters_obj=vch_load_obj, geo_obj=geo
|
||||
)
|
||||
if not existing_obj_items.exists():
|
||||
# Check if ObjItem with same geo already exists
|
||||
existing_obj_item = ObjItem.objects.filter(geo_obj=geo).first()
|
||||
if existing_obj_item:
|
||||
# Check if parameter with same values exists for this object
|
||||
if (
|
||||
hasattr(existing_obj_item, 'parameter_obj') and
|
||||
existing_obj_item.parameter_obj and
|
||||
existing_obj_item.parameter_obj.id_satellite == sat and
|
||||
existing_obj_item.parameter_obj.polarization == polarization_obj and
|
||||
existing_obj_item.parameter_obj.frequency == freq and
|
||||
existing_obj_item.parameter_obj.freq_range == freq_line and
|
||||
existing_obj_item.parameter_obj.bod_velocity == v and
|
||||
existing_obj_item.parameter_obj.modulation == mod_obj and
|
||||
existing_obj_item.parameter_obj.snr == snr
|
||||
):
|
||||
# Skip creating duplicate
|
||||
continue
|
||||
|
||||
# Create new ObjItem and Parameter
|
||||
obj_item = ObjItem.objects.create(name=source, created_by=user_to_use)
|
||||
obj_item.parameters_obj.set([vch_load_obj])
|
||||
|
||||
vch_load_obj = Parameter.objects.create(
|
||||
id_satellite=sat,
|
||||
polarization=polarization_obj,
|
||||
frequency=freq,
|
||||
freq_range=freq_line,
|
||||
bod_velocity=v,
|
||||
modulation=mod_obj,
|
||||
snr=snr,
|
||||
objitem=obj_item
|
||||
)
|
||||
|
||||
geo.objitem = obj_item
|
||||
geo.save()
|
||||
|
||||
@@ -316,14 +332,6 @@ def get_points_from_csv(file_content, current_user=None):
|
||||
mir_3_obj, _ = Mirror.objects.get_or_create(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': user_to_use}
|
||||
)
|
||||
|
||||
geo_obj, _ = Geo.objects.get_or_create(
|
||||
timestamp=row["time"],
|
||||
coords=Point(row["lon"], row["lat"], srid=4326),
|
||||
@@ -334,12 +342,32 @@ def get_points_from_csv(file_content, current_user=None):
|
||||
)
|
||||
geo_obj.mirrors.set(Mirror.objects.filter(name__in=mir_lst))
|
||||
|
||||
existing_obj_items = ObjItem.objects.filter(
|
||||
parameters_obj=vch_load_obj, geo_obj=geo_obj
|
||||
)
|
||||
if not existing_obj_items.exists():
|
||||
# Check if ObjItem with same geo already exists
|
||||
existing_obj_item = ObjItem.objects.filter(geo_obj=geo_obj).first()
|
||||
if existing_obj_item:
|
||||
# Check if parameter with same values exists for this object
|
||||
if (
|
||||
hasattr(existing_obj_item, 'parameter_obj') and
|
||||
existing_obj_item.parameter_obj and
|
||||
existing_obj_item.parameter_obj.id_satellite == sat_obj and
|
||||
existing_obj_item.parameter_obj.polarization == pol_obj and
|
||||
existing_obj_item.parameter_obj.frequency == row["freq"] and
|
||||
existing_obj_item.parameter_obj.freq_range == row["f_range"]
|
||||
):
|
||||
# Skip creating duplicate
|
||||
continue
|
||||
|
||||
# Create new ObjItem and Parameter
|
||||
obj_item = ObjItem.objects.create(name=row["obj"], created_by=user_to_use)
|
||||
obj_item.parameters_obj.set([vch_load_obj])
|
||||
|
||||
vch_load_obj = Parameter.objects.create(
|
||||
id_satellite=sat_obj,
|
||||
polarization=pol_obj,
|
||||
frequency=row["freq"],
|
||||
freq_range=row["f_range"],
|
||||
objitem=obj_item
|
||||
)
|
||||
|
||||
geo_obj.objitem = obj_item
|
||||
geo_obj.save()
|
||||
|
||||
@@ -598,29 +626,22 @@ def parse_pagination_params(
|
||||
|
||||
def get_first_param_subquery(field_name: str):
|
||||
"""
|
||||
Создает подзапрос для получения первого параметра объекта.
|
||||
Возвращает F() выражение для доступа к полю параметра через OneToOne связь.
|
||||
|
||||
Используется для аннотации queryset с полями из связанной модели Parameter.
|
||||
Возвращает значение указанного поля из первого параметра объекта.
|
||||
После рефакторинга связи Parameter-ObjItem с ManyToMany на OneToOne,
|
||||
эта функция упрощена для возврата прямого F() выражения вместо подзапроса.
|
||||
|
||||
Args:
|
||||
field_name (str): Имя поля модели Parameter для извлечения.
|
||||
Может включать связанные поля через __ (например, 'id_satellite__name').
|
||||
|
||||
Returns:
|
||||
Subquery: Django Subquery объект для использования в annotate().
|
||||
F: Django F() объект для использования в annotate().
|
||||
|
||||
Example:
|
||||
>>> from django.db.models import Subquery, OuterRef
|
||||
>>> freq_subq = get_first_param_subquery('frequency')
|
||||
>>> objects = ObjItem.objects.annotate(first_freq=Subquery(freq_subq))
|
||||
>>> freq_expr = get_first_param_subquery('frequency')
|
||||
>>> objects = ObjItem.objects.annotate(first_freq=freq_expr)
|
||||
>>> for obj in objects:
|
||||
... print(obj.first_freq)
|
||||
"""
|
||||
from django.db.models import OuterRef
|
||||
|
||||
return (
|
||||
Parameter.objects.filter(objitems=OuterRef("pk"))
|
||||
.order_by("id")
|
||||
.values(field_name)[:1]
|
||||
)
|
||||
return F(f"parameter_obj__{field_name}")
|
||||
|
||||
@@ -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,
|
||||
# Работаем с одной формой параметра вместо 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"
|
||||
)
|
||||
else:
|
||||
context["parameter_form"] = ParameterForm(prefix="parameter")
|
||||
|
||||
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"):
|
||||
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")
|
||||
else:
|
||||
context["parameter_forms"] = ParameterFormSet(
|
||||
queryset=Parameter.objects.none(), prefix="parameters"
|
||||
)
|
||||
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:
|
||||
# Сохраняем новый параметр
|
||||
def save_parameter(self, parameter_form):
|
||||
"""Сохраняет параметр объекта через OneToOne связь."""
|
||||
if parameter_form.is_valid():
|
||||
instance = parameter_form.save(commit=False)
|
||||
instance.objitem = self.object
|
||||
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_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:
|
||||
|
||||
@@ -19,42 +19,42 @@ services:
|
||||
networks:
|
||||
- app-network
|
||||
|
||||
web:
|
||||
build:
|
||||
context: ./dbapp
|
||||
dockerfile: Dockerfile
|
||||
container_name: django-app-dev
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- DEBUG=True
|
||||
- ENVIRONMENT=development
|
||||
- DJANGO_SETTINGS_MODULE=dbapp.settings.development
|
||||
- SECRET_KEY=django-insecure-dev-key-change-in-production
|
||||
- DB_ENGINE=django.contrib.gis.db.backends.postgis
|
||||
- DB_NAME=geodb
|
||||
- DB_USER=geralt
|
||||
- DB_PASSWORD=123456
|
||||
- DB_HOST=db
|
||||
- DB_PORT=5432
|
||||
- ALLOWED_HOSTS=localhost,127.0.0.1,0.0.0.0
|
||||
ports:
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
# Монтируем только код приложения, не весь проект
|
||||
- ./dbapp/dbapp:/app/dbapp
|
||||
- ./dbapp/mainapp:/app/mainapp
|
||||
- ./dbapp/mapsapp:/app/mapsapp
|
||||
- ./dbapp/lyngsatapp:/app/lyngsatapp
|
||||
- ./dbapp/static:/app/static
|
||||
- ./dbapp/manage.py:/app/manage.py
|
||||
- static_volume_dev:/app/staticfiles
|
||||
- media_volume_dev:/app/media
|
||||
- logs_volume_dev:/app/logs
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- app-network
|
||||
# web:
|
||||
# build:
|
||||
# context: ./dbapp
|
||||
# dockerfile: Dockerfile
|
||||
# container_name: django-app-dev
|
||||
# restart: unless-stopped
|
||||
# environment:
|
||||
# - DEBUG=True
|
||||
# - ENVIRONMENT=development
|
||||
# - DJANGO_SETTINGS_MODULE=dbapp.settings.development
|
||||
# - SECRET_KEY=django-insecure-dev-key-change-in-production
|
||||
# - DB_ENGINE=django.contrib.gis.db.backends.postgis
|
||||
# - DB_NAME=geodb
|
||||
# - DB_USER=geralt
|
||||
# - DB_PASSWORD=123456
|
||||
# - DB_HOST=db
|
||||
# - DB_PORT=5432
|
||||
# - ALLOWED_HOSTS=localhost,127.0.0.1,0.0.0.0
|
||||
# ports:
|
||||
# - "8000:8000"
|
||||
# volumes:
|
||||
# # Монтируем только код приложения, не весь проект
|
||||
# - ./dbapp/dbapp:/app/dbapp
|
||||
# - ./dbapp/mainapp:/app/mainapp
|
||||
# - ./dbapp/mapsapp:/app/mapsapp
|
||||
# - ./dbapp/lyngsatapp:/app/lyngsatapp
|
||||
# - ./dbapp/static:/app/static
|
||||
# - ./dbapp/manage.py:/app/manage.py
|
||||
# - static_volume_dev:/app/staticfiles
|
||||
# - media_volume_dev:/app/media
|
||||
# - logs_volume_dev:/app/logs
|
||||
# depends_on:
|
||||
# db:
|
||||
# condition: service_healthy
|
||||
# networks:
|
||||
# - app-network
|
||||
|
||||
# tileserver:
|
||||
# image: maptiler/tileserver-gl:latest
|
||||
@@ -72,9 +72,9 @@ services:
|
||||
|
||||
volumes:
|
||||
postgres_data_dev:
|
||||
static_volume_dev:
|
||||
media_volume_dev:
|
||||
logs_volume_dev:
|
||||
# static_volume_dev:
|
||||
# media_volume_dev:
|
||||
# logs_volume_dev:
|
||||
# tileserver_config_dev:
|
||||
|
||||
networks:
|
||||
|
||||
Reference in New Issue
Block a user