Виджет для формы выбора зеркал

This commit is contained in:
2025-11-13 21:09:39 +03:00
parent 8e0d32c307
commit 5ab6770809
7 changed files with 799 additions and 374 deletions

View File

@@ -2,57 +2,54 @@
from django import forms
# Local imports
from .models import Geo, Modulation, ObjItem, Parameter, Polarization, Satellite, Standard
from .widgets import TagSelectWidget
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'
})
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'
})
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'
})
widget=forms.Select(attrs={"class": "form-select"}),
)
number_input = forms.IntegerField(
label="Введите число объектов",
min_value=0,
widget=forms.NumberInput(attrs={
'class': 'form-control'
})
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'
})
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'
})
widget=forms.Select(attrs={"class": "form-select"}),
)
@@ -60,9 +57,7 @@ class VchLinkForm(forms.Form):
sat_choice = forms.ModelChoiceField(
queryset=Satellite.objects.all(),
label="Выберите спутник",
widget=forms.Select(attrs={
'class': 'form-select'
})
widget=forms.Select(attrs={"class": "form-select"}),
)
# ku_range = forms.ChoiceField(
# choices=[(9750.0, '9750'), (10750.0, '10750')],
@@ -74,18 +69,22 @@ class VchLinkForm(forms.Form):
label="Разброс по частоте (не используется)",
required=False,
initial=0.0,
widget=forms.NumberInput(attrs={
'class': 'form-control',
'placeholder': 'Не используется - погрешность определяется автоматически'
})
widget=forms.NumberInput(
attrs={
"class": "form-control",
"placeholder": "Не используется - погрешность определяется автоматически",
}
),
)
value2 = forms.FloatField(
label="Разброс по полосе (в %)",
widget=forms.NumberInput(attrs={
'class': 'form-control',
'placeholder': 'Введите погрешность полосы в процентах',
'step': '0.1'
})
label="Разброс по полосе (в %)",
widget=forms.NumberInput(
attrs={
"class": "form-control",
"placeholder": "Введите погрешность полосы в процентах",
"step": "0.1",
}
),
)
@@ -106,256 +105,270 @@ class NewEventForm(forms.Form):
# )
file = forms.FileField(
label="Выберите файл",
widget=forms.FileInput(attrs={
'class': 'form-control',
'accept': '.xlsx,.xls'
})
widget=forms.FileInput(attrs={"class": "form-control", "accept": ".xlsx,.xls"}),
)
class FillLyngsatDataForm(forms.Form):
"""Форма для заполнения данных из Lyngsat с поддержкой кеширования"""
REGION_CHOICES = [
('europe', 'Европа'),
('asia', 'Азия'),
('america', 'Америка'),
('atlantic', 'Атлантика'),
("europe", "Европа"),
("asia", "Азия"),
("america", "Америка"),
("atlantic", "Атлантика"),
]
satellites = forms.ModelMultipleChoiceField(
queryset=Satellite.objects.all().order_by('name'),
queryset=Satellite.objects.all().order_by("name"),
label="Выберите спутники",
widget=forms.SelectMultiple(attrs={
'class': 'form-select',
'size': '10'
}),
widget=forms.SelectMultiple(attrs={"class": "form-select", "size": "10"}),
required=True,
help_text="Удерживайте Ctrl (Cmd на Mac) для выбора нескольких спутников"
help_text="Удерживайте Ctrl (Cmd на Mac) для выбора нескольких спутников",
)
regions = forms.MultipleChoiceField(
choices=REGION_CHOICES,
label="Выберите регионы",
widget=forms.SelectMultiple(attrs={
'class': 'form-select',
'size': '4'
}),
widget=forms.SelectMultiple(attrs={"class": "form-select", "size": "4"}),
required=True,
initial=['europe', 'asia', 'america', 'atlantic'],
help_text="Удерживайте Ctrl (Cmd на Mac) для выбора нескольких регионов"
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="Использовать кешированные данные (ускоряет повторные запросы)"
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="Игнорировать кеш и получить свежие данные с сайта"
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'),
queryset=Satellite.objects.all().order_by("name"),
label="Выберите спутники",
widget=forms.SelectMultiple(attrs={
'class': 'form-select',
'size': '10'
}),
widget=forms.SelectMultiple(attrs={"class": "form-select", "size": "10"}),
required=False,
help_text="Оставьте пустым для обработки всех спутников"
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="Допустимое отклонение частоты при сравнении"
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'
"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'}),
"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': 'Стандарт',
"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 дБ',
"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"].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
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')
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', 'Полоса частот не может быть больше частоты')
self.add_error(
"freq_range", "Полоса частот не может быть больше частоты"
)
# Проверка что символьная скорость соответствует полосе частот
if bod_velocity and freq_range:
if bod_velocity > freq_range * 1000000: # Конвертация МГц в Гц
self.add_error('bod_velocity', 'Символьная скорость не может превышать полосу частот')
self.add_error(
"bod_velocity",
"Символьная скорость не может превышать полосу частот",
)
return cleaned_data
class GeoForm(forms.ModelForm):
class Meta:
model = Geo
fields = ['location', 'comment', 'is_average', 'mirrors']
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': TagSelectWidget(attrs={'id': 'id_geo-mirrors'}),
"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': 'Спутники-зеркала, использованные для приёма',
"location": "Местоположение",
"comment": "Комментарий",
"is_average": "Усреднённое",
"mirrors": "Спутники-зеркала, использованные для приёма",
}
help_texts = {
'mirrors': 'Начните вводить название спутника для поиска',
"mirrors": "Выберите спутники из списка",
}
class ObjItemForm(forms.ModelForm):
"""
Форма для создания и редактирования объектов (источников сигнала).
Работает с моделью ObjItem. Параметры ВЧ загрузки обрабатываются отдельно
через ParameterForm с использованием OneToOne связи.
"""
class Meta:
model = ObjItem
fields = ['name']
fields = ["name"]
widgets = {
'name': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Введите название объекта',
'maxlength': '100'
}),
"name": forms.TextInput(
attrs={
"class": "form-control",
"placeholder": "Введите название объекта",
"maxlength": "100",
}
),
}
labels = {
'name': 'Название объекта',
"name": "Название объекта",
}
help_texts = {
'name': 'Уникальное название объекта/источника сигнала',
"name": "Уникальное название объекта/источника сигнала",
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Делаем поле name необязательным, так как оно может быть пустым
self.fields['name'].required = False
self.fields["name"].required = False
def clean_name(self):
"""
Валидация поля name.
Проверяет что название не состоит только из пробелов.
"""
name = self.cleaned_data.get('name')
name = self.cleaned_data.get("name")
if name:
# Удаляем лишние пробелы
name = name.strip()
# Проверяем что после удаления пробелов что-то осталось
if not name:
raise forms.ValidationError('Название не может состоять только из пробелов')
return name
raise forms.ValidationError(
"Название не может состоять только из пробелов"
)
return name