375 lines
14 KiB
Python
375 lines
14 KiB
Python
# Django imports
|
||
from django import forms
|
||
|
||
# Local imports
|
||
from .models import (
|
||
Geo,
|
||
Modulation,
|
||
ObjItem,
|
||
Parameter,
|
||
Polarization,
|
||
Satellite,
|
||
Standard,
|
||
)
|
||
from .widgets import CheckboxSelectMultipleWidget
|
||
|
||
|
||
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
|