Files
dbstorage/dbapp/mainapp/forms.py

879 lines
34 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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']
widgets = {
'info': forms.Select(attrs={
'class': 'form-select',
'id': 'id_info',
}),
}
labels = {
'info': 'Тип объекта',
}
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.MultipleChoiceField(
choices=[],
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, 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')
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