884 lines
34 KiB
Python
884 lines
34 KiB
Python
# Django imports
|
||
from django import forms
|
||
|
||
# Local imports
|
||
from .models import (
|
||
Geo,
|
||
Modulation,
|
||
ObjItem,
|
||
Parameter,
|
||
Polarization,
|
||
Satellite,
|
||
Source,
|
||
Standard,
|
||
)
|
||
from .widgets import CheckboxSelectMultipleWidget
|
||
|
||
# Import from mapsapp to avoid circular import issues
|
||
from mapsapp.models import Transponders
|
||
|
||
|
||
class UploadFileForm(forms.Form):
|
||
file = forms.FileField(
|
||
label="Выберите файл",
|
||
widget=forms.FileInput(attrs={"class": "form-file-input"}),
|
||
)
|
||
|
||
|
||
class LoadExcelData(forms.Form):
|
||
file = forms.FileField(
|
||
label="Выберите Excel файл",
|
||
widget=forms.FileInput(attrs={"class": "form-control", "accept": ".xlsx,.xls"}),
|
||
)
|
||
sat_choice = forms.ModelChoiceField(
|
||
queryset=Satellite.objects.all(),
|
||
label="Выберите спутник",
|
||
widget=forms.Select(attrs={"class": "form-select"}),
|
||
)
|
||
number_input = forms.IntegerField(
|
||
label="Введите число объектов",
|
||
min_value=0,
|
||
widget=forms.NumberInput(attrs={"class": "form-control"}),
|
||
)
|
||
|
||
|
||
class LoadCsvData(forms.Form):
|
||
file = forms.FileField(
|
||
label="Выберите CSV файл",
|
||
widget=forms.FileInput(attrs={"class": "form-control", "accept": ".csv"}),
|
||
)
|
||
|
||
|
||
class UploadVchLoad(UploadFileForm):
|
||
sat_choice = forms.ModelChoiceField(
|
||
queryset=Satellite.objects.all(),
|
||
label="Выберите спутник",
|
||
widget=forms.Select(attrs={"class": "form-select"}),
|
||
)
|
||
|
||
|
||
class VchLinkForm(forms.Form):
|
||
sat_choice = forms.ModelChoiceField(
|
||
queryset=Satellite.objects.all(),
|
||
label="Выберите спутник",
|
||
widget=forms.Select(attrs={"class": "form-select"}),
|
||
)
|
||
# ku_range = forms.ChoiceField(
|
||
# choices=[(9750.0, '9750'), (10750.0, '10750')],
|
||
# # coerce=lambda x: x == 'True',
|
||
# widget=forms.Select(attrs={'class': 'form-select'}),
|
||
# label='Выбор диапазона'
|
||
# )
|
||
value1 = forms.FloatField(
|
||
label="Разброс по частоте (не используется)",
|
||
required=False,
|
||
initial=0.0,
|
||
widget=forms.NumberInput(
|
||
attrs={
|
||
"class": "form-control",
|
||
"placeholder": "Не используется - погрешность определяется автоматически",
|
||
}
|
||
),
|
||
)
|
||
value2 = forms.FloatField(
|
||
label="Разброс по полосе (в %)",
|
||
widget=forms.NumberInput(
|
||
attrs={
|
||
"class": "form-control",
|
||
"placeholder": "Введите погрешность полосы в процентах",
|
||
"step": "0.1",
|
||
}
|
||
),
|
||
)
|
||
|
||
|
||
class NewEventForm(forms.Form):
|
||
# sat_choice = forms.ModelChoiceField(
|
||
# queryset=Satellite.objects.all(),
|
||
# label="Выберите спутник",
|
||
# widget=forms.Select(attrs={
|
||
# 'class': 'form-select'
|
||
# })
|
||
# )
|
||
# pol_choice = forms.ModelChoiceField(
|
||
# queryset=Polarization.objects.all(),
|
||
# label="Выберите поляризацию",
|
||
# widget=forms.Select(attrs={
|
||
# 'class': 'form-select'
|
||
# })
|
||
# )
|
||
file = forms.FileField(
|
||
label="Выберите файл",
|
||
widget=forms.FileInput(attrs={"class": "form-control", "accept": ".xlsx,.xls"}),
|
||
)
|
||
|
||
|
||
class FillLyngsatDataForm(forms.Form):
|
||
"""Форма для заполнения данных из Lyngsat с поддержкой кеширования"""
|
||
|
||
REGION_CHOICES = [
|
||
("europe", "Европа"),
|
||
("asia", "Азия"),
|
||
("america", "Америка"),
|
||
("atlantic", "Атлантика"),
|
||
]
|
||
|
||
satellites = forms.ModelMultipleChoiceField(
|
||
queryset=Satellite.objects.all().order_by("name"),
|
||
label="Выберите спутники",
|
||
widget=forms.SelectMultiple(attrs={"class": "form-select", "size": "10"}),
|
||
required=True,
|
||
help_text="Удерживайте Ctrl (Cmd на Mac) для выбора нескольких спутников",
|
||
)
|
||
|
||
regions = forms.MultipleChoiceField(
|
||
choices=REGION_CHOICES,
|
||
label="Выберите регионы",
|
||
widget=forms.SelectMultiple(attrs={"class": "form-select", "size": "4"}),
|
||
required=True,
|
||
initial=["europe", "asia", "america", "atlantic"],
|
||
help_text="Удерживайте Ctrl (Cmd на Mac) для выбора нескольких регионов",
|
||
)
|
||
|
||
use_cache = forms.BooleanField(
|
||
label="Использовать кеширование",
|
||
required=False,
|
||
initial=True,
|
||
widget=forms.CheckboxInput(attrs={"class": "form-check-input"}),
|
||
help_text="Использовать кешированные данные (ускоряет повторные запросы)",
|
||
)
|
||
|
||
force_refresh = forms.BooleanField(
|
||
label="Принудительно обновить данные",
|
||
required=False,
|
||
initial=False,
|
||
widget=forms.CheckboxInput(attrs={"class": "form-check-input"}),
|
||
help_text="Игнорировать кеш и получить свежие данные с сайта",
|
||
)
|
||
|
||
|
||
class LinkLyngsatForm(forms.Form):
|
||
"""Форма для привязки источников LyngSat к объектам"""
|
||
|
||
satellites = forms.ModelMultipleChoiceField(
|
||
queryset=Satellite.objects.all().order_by("name"),
|
||
label="Выберите спутники",
|
||
widget=forms.SelectMultiple(attrs={"class": "form-select", "size": "10"}),
|
||
required=False,
|
||
help_text="Оставьте пустым для обработки всех спутников",
|
||
)
|
||
|
||
frequency_tolerance = forms.FloatField(
|
||
label="Допуск по частоте (МГц)",
|
||
initial=0.5,
|
||
min_value=0,
|
||
widget=forms.NumberInput(attrs={"class": "form-control", "step": "0.1"}),
|
||
help_text="Допустимое отклонение частоты при сравнении",
|
||
)
|
||
|
||
|
||
class ParameterForm(forms.ModelForm):
|
||
"""
|
||
Форма для создания и редактирования параметров ВЧ загрузки.
|
||
|
||
Работает с одним экземпляром Parameter, связанным с ObjItem через OneToOne связь.
|
||
"""
|
||
|
||
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", "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)
|
||
|
||
# Динамически загружаем 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:
|
||
model = Geo
|
||
fields = ["location", "comment", "is_average", "mirrors"]
|
||
widgets = {
|
||
"location": forms.TextInput(attrs={"class": "form-control"}),
|
||
"comment": forms.TextInput(attrs={"class": "form-control"}),
|
||
"is_average": forms.CheckboxInput(attrs={"class": "form-check-input"}),
|
||
"mirrors": CheckboxSelectMultipleWidget(
|
||
attrs={
|
||
"id": "id_geo-mirrors",
|
||
"placeholder": "Выберите спутники...",
|
||
}
|
||
),
|
||
}
|
||
labels = {
|
||
"location": "Местоположение",
|
||
"comment": "Комментарий",
|
||
"is_average": "Усреднённое",
|
||
"mirrors": "Спутники-зеркала, использованные для приёма",
|
||
}
|
||
help_texts = {
|
||
"mirrors": "Выберите спутники из списка",
|
||
}
|
||
|
||
|
||
class ObjItemForm(forms.ModelForm):
|
||
"""
|
||
Форма для создания и редактирования объектов (источников сигнала).
|
||
|
||
Работает с моделью ObjItem. Параметры ВЧ загрузки обрабатываются отдельно
|
||
через ParameterForm с использованием OneToOne связи.
|
||
"""
|
||
|
||
class Meta:
|
||
model = ObjItem
|
||
fields = ["name"]
|
||
widgets = {
|
||
"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
|
||
|
||
|
||
class SourceForm(forms.ModelForm):
|
||
"""Form for editing Source model with 4 coordinate fields."""
|
||
|
||
# Координаты ГЛ (coords_average)
|
||
average_latitude = forms.FloatField(
|
||
required=False,
|
||
widget=forms.NumberInput(
|
||
attrs={"class": "form-control", "step": "0.000001", "placeholder": "Широта"}
|
||
),
|
||
label="Широта ГЛ",
|
||
)
|
||
average_longitude = forms.FloatField(
|
||
required=False,
|
||
widget=forms.NumberInput(
|
||
attrs={
|
||
"class": "form-control",
|
||
"step": "0.000001",
|
||
"placeholder": "Долгота",
|
||
}
|
||
),
|
||
label="Долгота ГЛ",
|
||
)
|
||
|
||
# Координаты Кубсата (coords_kupsat)
|
||
kupsat_latitude = forms.FloatField(
|
||
required=False,
|
||
widget=forms.NumberInput(
|
||
attrs={"class": "form-control", "step": "0.000001", "placeholder": "Широта"}
|
||
),
|
||
label="Широта Кубсата",
|
||
)
|
||
kupsat_longitude = forms.FloatField(
|
||
required=False,
|
||
widget=forms.NumberInput(
|
||
attrs={
|
||
"class": "form-control",
|
||
"step": "0.000001",
|
||
"placeholder": "Долгота",
|
||
}
|
||
),
|
||
label="Долгота Кубсата",
|
||
)
|
||
|
||
# Координаты оперативников (coords_valid)
|
||
valid_latitude = forms.FloatField(
|
||
required=False,
|
||
widget=forms.NumberInput(
|
||
attrs={"class": "form-control", "step": "0.000001", "placeholder": "Широта"}
|
||
),
|
||
label="Широта оперативников",
|
||
)
|
||
valid_longitude = forms.FloatField(
|
||
required=False,
|
||
widget=forms.NumberInput(
|
||
attrs={
|
||
"class": "form-control",
|
||
"step": "0.000001",
|
||
"placeholder": "Долгота",
|
||
}
|
||
),
|
||
label="Долгота оперативников",
|
||
)
|
||
|
||
# Координаты справочные (coords_reference)
|
||
reference_latitude = forms.FloatField(
|
||
required=False,
|
||
widget=forms.NumberInput(
|
||
attrs={"class": "form-control", "step": "0.000001", "placeholder": "Широта"}
|
||
),
|
||
label="Широта справочные",
|
||
)
|
||
reference_longitude = forms.FloatField(
|
||
required=False,
|
||
widget=forms.NumberInput(
|
||
attrs={
|
||
"class": "form-control",
|
||
"step": "0.000001",
|
||
"placeholder": "Долгота",
|
||
}
|
||
),
|
||
label="Долгота справочные",
|
||
)
|
||
|
||
class Meta:
|
||
model = Source
|
||
fields = ['info', 'ownership']
|
||
widgets = {
|
||
'info': forms.Select(attrs={
|
||
'class': 'form-select',
|
||
'id': 'id_info',
|
||
}),
|
||
'ownership': forms.Select(attrs={
|
||
'class': 'form-select',
|
||
'id': 'id_ownership',
|
||
}),
|
||
}
|
||
labels = {
|
||
'info': 'Тип объекта',
|
||
'ownership': 'Принадлежность объекта',
|
||
}
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
super().__init__(*args, **kwargs)
|
||
|
||
# Заполняем поля координат из instance
|
||
if self.instance and self.instance.pk:
|
||
if self.instance.coords_average:
|
||
self.fields[
|
||
"average_longitude"
|
||
].initial = self.instance.coords_average.x
|
||
self.fields["average_latitude"].initial = self.instance.coords_average.y
|
||
|
||
if self.instance.coords_kupsat:
|
||
self.fields["kupsat_longitude"].initial = self.instance.coords_kupsat.x
|
||
self.fields["kupsat_latitude"].initial = self.instance.coords_kupsat.y
|
||
|
||
if self.instance.coords_valid:
|
||
self.fields["valid_longitude"].initial = self.instance.coords_valid.x
|
||
self.fields["valid_latitude"].initial = self.instance.coords_valid.y
|
||
|
||
if self.instance.coords_reference:
|
||
self.fields[
|
||
"reference_longitude"
|
||
].initial = self.instance.coords_reference.x
|
||
self.fields[
|
||
"reference_latitude"
|
||
].initial = self.instance.coords_reference.y
|
||
|
||
def save(self, commit=True):
|
||
from django.contrib.gis.geos import Point
|
||
|
||
instance = super().save(commit=False)
|
||
|
||
# Обработка coords_average
|
||
avg_lat = self.cleaned_data.get("average_latitude")
|
||
avg_lng = self.cleaned_data.get("average_longitude")
|
||
if avg_lat is not None and avg_lng is not None:
|
||
instance.coords_average = Point(avg_lng, avg_lat, srid=4326)
|
||
else:
|
||
instance.coords_average = None
|
||
|
||
# Обработка coords_kupsat
|
||
kup_lat = self.cleaned_data.get("kupsat_latitude")
|
||
kup_lng = self.cleaned_data.get("kupsat_longitude")
|
||
if kup_lat is not None and kup_lng is not None:
|
||
instance.coords_kupsat = Point(kup_lng, kup_lat, srid=4326)
|
||
else:
|
||
instance.coords_kupsat = None
|
||
|
||
# Обработка coords_valid
|
||
val_lat = self.cleaned_data.get("valid_latitude")
|
||
val_lng = self.cleaned_data.get("valid_longitude")
|
||
if val_lat is not None and val_lng is not None:
|
||
instance.coords_valid = Point(val_lng, val_lat, srid=4326)
|
||
else:
|
||
instance.coords_valid = None
|
||
|
||
# Обработка coords_reference
|
||
ref_lat = self.cleaned_data.get("reference_latitude")
|
||
ref_lng = self.cleaned_data.get("reference_longitude")
|
||
if ref_lat is not None and ref_lng is not None:
|
||
instance.coords_reference = Point(ref_lng, ref_lat, srid=4326)
|
||
else:
|
||
instance.coords_reference = None
|
||
|
||
if commit:
|
||
instance.save()
|
||
|
||
return instance
|
||
|
||
|
||
|
||
class KubsatFilterForm(forms.Form):
|
||
"""Форма фильтров для страницы Кубсат"""
|
||
|
||
satellites = forms.ModelMultipleChoiceField(
|
||
queryset=None, # Будет установлен в __init__
|
||
label='Спутники',
|
||
required=False,
|
||
widget=forms.SelectMultiple(attrs={'class': 'form-select', 'size': '5'})
|
||
)
|
||
|
||
band = forms.ModelMultipleChoiceField(
|
||
queryset=None,
|
||
label='Диапазоны работы спутника',
|
||
required=False,
|
||
widget=forms.SelectMultiple(attrs={'class': 'form-select', 'size': '4'})
|
||
)
|
||
|
||
polarization = forms.ModelMultipleChoiceField(
|
||
queryset=Polarization.objects.all().order_by('name'),
|
||
label='Поляризация',
|
||
required=False,
|
||
widget=forms.SelectMultiple(attrs={'class': 'form-select', 'size': '4'})
|
||
)
|
||
|
||
frequency_min = forms.FloatField(
|
||
label='Центральная частота от (МГц)',
|
||
required=False,
|
||
widget=forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'})
|
||
)
|
||
|
||
frequency_max = forms.FloatField(
|
||
label='Центральная частота до (МГц)',
|
||
required=False,
|
||
widget=forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'})
|
||
)
|
||
|
||
freq_range_min = forms.FloatField(
|
||
label='Полоса от (МГц)',
|
||
required=False,
|
||
widget=forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'})
|
||
)
|
||
|
||
freq_range_max = forms.FloatField(
|
||
label='Полоса до (МГц)',
|
||
required=False,
|
||
widget=forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'})
|
||
)
|
||
|
||
modulation = forms.ModelMultipleChoiceField(
|
||
queryset=Modulation.objects.all().order_by('name'),
|
||
label='Модуляция',
|
||
required=False,
|
||
widget=forms.SelectMultiple(attrs={'class': 'form-select', 'size': '4'})
|
||
)
|
||
|
||
object_type = forms.ModelMultipleChoiceField(
|
||
queryset=None,
|
||
label='Тип объекта',
|
||
required=False,
|
||
widget=forms.SelectMultiple(attrs={'class': 'form-select', 'size': '3'})
|
||
)
|
||
|
||
object_ownership = forms.ModelMultipleChoiceField(
|
||
queryset=None,
|
||
label='Принадлежность объекта',
|
||
required=False,
|
||
widget=forms.SelectMultiple(attrs={'class': 'form-select', 'size': '3'})
|
||
)
|
||
|
||
objitem_count = forms.ChoiceField(
|
||
choices=[('', 'Все'), ('1', '1'), ('2+', '2 и более')],
|
||
label='Количество привязанных точек ГЛ',
|
||
required=False,
|
||
widget=forms.RadioSelect()
|
||
)
|
||
|
||
# Фиктивные фильтры
|
||
has_plans = forms.ChoiceField(
|
||
choices=[('', 'Неважно'), ('yes', 'Да'), ('no', 'Нет')],
|
||
label='Планы на Кубсат',
|
||
required=False,
|
||
widget=forms.RadioSelect()
|
||
)
|
||
|
||
success_1 = forms.ChoiceField(
|
||
choices=[('', 'Неважно'), ('yes', 'Да'), ('no', 'Нет')],
|
||
label='ГСО успешно?',
|
||
required=False,
|
||
widget=forms.RadioSelect()
|
||
)
|
||
|
||
success_2 = forms.ChoiceField(
|
||
choices=[('', 'Неважно'), ('yes', 'Да'), ('no', 'Нет')],
|
||
label='Кубсат успешно?',
|
||
required=False,
|
||
widget=forms.RadioSelect()
|
||
)
|
||
|
||
date_from = forms.DateField(
|
||
label='Дата от',
|
||
required=False,
|
||
widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
|
||
)
|
||
|
||
date_to = forms.DateField(
|
||
label='Дата до',
|
||
required=False,
|
||
widget=forms.DateInput(attrs={'class': 'form-control', 'type': 'date'})
|
||
)
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
super().__init__(*args, **kwargs)
|
||
from mainapp.models import Band, ObjectInfo, ObjectOwnership, Satellite, ObjItem
|
||
from django.db.models import Exists, OuterRef
|
||
|
||
# Фильтруем спутники: только те, у которых есть источники с точками
|
||
satellites_with_sources = Satellite.objects.filter(
|
||
parameters__objitem__source__isnull=False
|
||
).distinct().order_by('name')
|
||
|
||
self.fields['satellites'].queryset = satellites_with_sources
|
||
self.fields['band'].queryset = Band.objects.all().order_by('name')
|
||
self.fields['object_type'].queryset = ObjectInfo.objects.all().order_by('name')
|
||
self.fields['object_ownership'].queryset = ObjectOwnership.objects.all().order_by('name')
|
||
|
||
|
||
class TransponderForm(forms.ModelForm):
|
||
"""
|
||
Форма для создания и редактирования транспондеров.
|
||
|
||
При редактировании только name, zone_name и snr доступны для изменения.
|
||
Остальные поля только для чтения.
|
||
"""
|
||
|
||
class Meta:
|
||
model = Transponders
|
||
fields = [
|
||
'name',
|
||
'sat_id',
|
||
'downlink',
|
||
'uplink',
|
||
'frequency_range',
|
||
'zone_name',
|
||
'polarization',
|
||
'snr',
|
||
]
|
||
widgets = {
|
||
'name': forms.TextInput(attrs={
|
||
'class': 'form-control',
|
||
'placeholder': 'Введите название транспондера'
|
||
}),
|
||
'sat_id': forms.Select(attrs={'class': 'form-select'}),
|
||
'downlink': forms.NumberInput(attrs={
|
||
'class': 'form-control',
|
||
'step': '0.001',
|
||
'placeholder': 'Введите частоту downlink в МГц'
|
||
}),
|
||
'uplink': forms.NumberInput(attrs={
|
||
'class': 'form-control',
|
||
'step': '0.001',
|
||
'placeholder': 'Введите частоту uplink в МГц'
|
||
}),
|
||
'frequency_range': forms.NumberInput(attrs={
|
||
'class': 'form-control',
|
||
'step': '0.001',
|
||
'placeholder': 'Введите полосу частот в МГц'
|
||
}),
|
||
'zone_name': forms.TextInput(attrs={
|
||
'class': 'form-control',
|
||
'placeholder': 'Введите название зоны покрытия'
|
||
}),
|
||
'polarization': forms.Select(attrs={'class': 'form-select'}),
|
||
'snr': forms.NumberInput(attrs={
|
||
'class': 'form-control',
|
||
'step': '0.1',
|
||
'placeholder': 'Введите ОСШ в дБ'
|
||
}),
|
||
}
|
||
labels = {
|
||
'name': 'Название транспондера',
|
||
'sat_id': 'Спутник',
|
||
'downlink': 'Downlink (МГц)',
|
||
'uplink': 'Uplink (МГц)',
|
||
'frequency_range': 'Полоса частот (МГц)',
|
||
'zone_name': 'Название зоны покрытия',
|
||
'polarization': 'Поляризация',
|
||
'snr': 'ОСШ (дБ)',
|
||
}
|
||
help_texts = {
|
||
'downlink': 'Частота downlink в МГц',
|
||
'uplink': 'Частота uplink в МГц',
|
||
'frequency_range': 'Полоса частот в МГц',
|
||
'snr': 'Отношение сигнал/шум в децибелах',
|
||
}
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
super().__init__(*args, **kwargs)
|
||
|
||
# Загружаем choices для select полей
|
||
self.fields['sat_id'].queryset = Satellite.objects.all().order_by('name')
|
||
self.fields['polarization'].queryset = Polarization.objects.all().order_by('name')
|
||
|
||
# Если это форма редактирования (instance существует), делаем поля readonly
|
||
if self.instance and self.instance.pk:
|
||
# Поля только для чтения при редактировании
|
||
readonly_fields = ['sat_id', 'downlink', 'uplink', 'frequency_range', 'polarization']
|
||
for field_name in readonly_fields:
|
||
self.fields[field_name].widget.attrs['readonly'] = True
|
||
self.fields[field_name].widget.attrs['disabled'] = True
|
||
self.fields[field_name].required = False
|
||
else:
|
||
# При создании все поля обязательны кроме name, zone_name и snr
|
||
self.fields['sat_id'].required = True
|
||
self.fields['downlink'].required = True
|
||
self.fields['name'].required = False
|
||
self.fields['zone_name'].required = False
|
||
self.fields['snr'].required = False
|
||
|
||
def clean(self):
|
||
"""Дополнительная валидация формы."""
|
||
cleaned_data = super().clean()
|
||
|
||
# При редактировании восстанавливаем значения readonly полей из instance
|
||
if self.instance and self.instance.pk:
|
||
cleaned_data['sat_id'] = self.instance.sat_id
|
||
cleaned_data['downlink'] = self.instance.downlink
|
||
cleaned_data['uplink'] = self.instance.uplink
|
||
cleaned_data['frequency_range'] = self.instance.frequency_range
|
||
cleaned_data['polarization'] = self.instance.polarization
|
||
|
||
return cleaned_data
|
||
|
||
|
||
class SatelliteForm(forms.ModelForm):
|
||
"""
|
||
Форма для создания и редактирования спутников.
|
||
"""
|
||
|
||
class Meta:
|
||
model = Satellite
|
||
fields = [
|
||
'name',
|
||
'norad',
|
||
'band',
|
||
'undersat_point',
|
||
'url',
|
||
'comment',
|
||
'launch_date',
|
||
]
|
||
widgets = {
|
||
'name': forms.TextInput(attrs={
|
||
'class': 'form-control',
|
||
'placeholder': 'Введите название спутника',
|
||
'required': True
|
||
}),
|
||
'norad': forms.NumberInput(attrs={
|
||
'class': 'form-control',
|
||
'placeholder': 'Введите NORAD ID'
|
||
}),
|
||
'band': forms.SelectMultiple(attrs={
|
||
'class': 'form-select',
|
||
'size': '5'
|
||
}),
|
||
'undersat_point': forms.NumberInput(attrs={
|
||
'class': 'form-control',
|
||
'step': '0.01',
|
||
'placeholder': 'Введите подспутниковую точку в градусах'
|
||
}),
|
||
'url': forms.URLInput(attrs={
|
||
'class': 'form-control',
|
||
'placeholder': 'https://example.com'
|
||
}),
|
||
'comment': forms.Textarea(attrs={
|
||
'class': 'form-control',
|
||
'rows': 3,
|
||
'placeholder': 'Введите комментарий'
|
||
}),
|
||
'launch_date': forms.DateInput(attrs={
|
||
'class': 'form-control',
|
||
'type': 'date'
|
||
}),
|
||
}
|
||
labels = {
|
||
'name': 'Название спутника',
|
||
'norad': 'NORAD ID',
|
||
'band': 'Диапазоны работы',
|
||
'undersat_point': 'Подспутниковая точка (градусы)',
|
||
'url': 'Ссылка на источник',
|
||
'comment': 'Комментарий',
|
||
'launch_date': 'Дата запуска',
|
||
}
|
||
help_texts = {
|
||
'name': 'Уникальное название спутника',
|
||
'norad': 'Идентификатор NORAD для отслеживания спутника',
|
||
'band': 'Выберите диапазоны работы спутника (удерживайте Ctrl для множественного выбора)',
|
||
'undersat_point': 'Восточное полушарие с +, западное с -',
|
||
'url': 'Ссылка на сайт, где можно проверить информацию',
|
||
'launch_date': 'Дата запуска спутника',
|
||
}
|
||
|
||
def __init__(self, *args, **kwargs):
|
||
super().__init__(*args, **kwargs)
|
||
from mainapp.models import Band
|
||
|
||
# Загружаем choices для select полей
|
||
self.fields['band'].queryset = Band.objects.all().order_by('name')
|
||
|
||
# Делаем name обязательным
|
||
self.fields['name'].required = True
|
||
|
||
def clean_name(self):
|
||
"""Валидация поля name."""
|
||
name = self.cleaned_data.get('name')
|
||
|
||
if name:
|
||
# Удаляем лишние пробелы
|
||
name = name.strip()
|
||
|
||
# Проверяем что после удаления пробелов что-то осталось
|
||
if not name:
|
||
raise forms.ValidationError('Название не может состоять только из пробелов')
|
||
|
||
# Проверяем уникальность (исключая текущий объект при редактировании)
|
||
qs = Satellite.objects.filter(name=name)
|
||
if self.instance and self.instance.pk:
|
||
qs = qs.exclude(pk=self.instance.pk)
|
||
|
||
if qs.exists():
|
||
raise forms.ValidationError('Спутник с таким названием уже существует')
|
||
|
||
return name
|