Files
dbstorage/dbapp/mainapp/forms.py

1195 lines
46 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"}),
)
is_automatic = forms.BooleanField(
label="Автоматическая загрузка",
required=False,
initial=False,
widget=forms.CheckboxInput(attrs={"class": "form-check-input"}),
help_text="Если отмечено, точки не будут добавляться к объектам",
)
class LoadCsvData(forms.Form):
file = forms.FileField(
label="Выберите CSV файл",
widget=forms.FileInput(attrs={"class": "form-control", "accept": ".csv"}),
)
is_automatic = forms.BooleanField(
label="Автоматическая загрузка",
required=False,
initial=False,
widget=forms.CheckboxInput(attrs={"class": "form-check-input"}),
help_text="Если отмечено, точки не будут добавляться к объектам",
)
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', 'note']
widgets = {
'info': forms.Select(attrs={
'class': 'form-select',
'id': 'id_info',
}),
'ownership': forms.Select(attrs={
'class': 'form-select',
'id': 'id_ownership',
}),
'note': forms.Textarea(attrs={
'class': 'form-control',
'rows': "3",
'id': 'id_note',
})
}
labels = {
'info': 'Тип объекта',
'ownership': 'Принадлежность объекта',
'note': 'Примечание'
}
help_texts = {
'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 НЕ обрабатываем здесь - это поле управляется только программно
# (через _recalculate_average_coords в модели Source)
# Обработка 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': '5'})
)
polarization = forms.ModelMultipleChoiceField(
queryset=Polarization.objects.all().order_by('name'),
label='Поляризация',
required=False,
widget=forms.SelectMultiple(attrs={'class': 'form-select', 'size': '5'})
)
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': '5'})
)
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_min = forms.IntegerField(
label='Количество привязанных точек ГЛ от',
required=False,
min_value=0,
widget=forms.NumberInput(attrs={'class': 'form-control', 'placeholder': 'От'})
)
objitem_count_max = forms.IntegerField(
label='Количество привязанных точек ГЛ до',
required=False,
min_value=0,
widget=forms.NumberInput(attrs={'class': 'form-control', 'placeholder': 'До'})
)
# Фиктивные фильтры
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',
'alternative_name',
'location_place',
'norad',
'international_code',
'band',
'undersat_point',
'url',
'comment',
'launch_date',
]
widgets = {
'name': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Введите название спутника',
'required': True
}),
'alternative_name': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Введите альтернативное название (необязательно)'
}),
'location_place': forms.Select(attrs={
'class': 'form-select'
}),
'norad': forms.NumberInput(attrs={
'class': 'form-control',
'placeholder': 'Введите NORAD ID'
}),
'international_code': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Например, 2011-074A'
}),
'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': 'Название спутника',
'alternative_name': 'Альтернативное название',
'location_place': 'Комплекс',
'norad': 'NORAD ID',
'international_code': 'Международный код',
'band': 'Диапазоны работы',
'undersat_point': 'Подспутниковая точка (градусы)',
'url': 'Ссылка на источник',
'comment': 'Комментарий',
'launch_date': 'Дата запуска',
}
help_texts = {
'name': 'Уникальное название спутника',
'alternative_name': 'Альтернативное название спутника (например, на другом языке)',
'location_place': 'К какому комплексу принадлежит спутник',
'norad': 'Идентификатор NORAD для отслеживания спутника',
'international_code': 'Международный идентификатор спутника (например, 2011-074A)',
'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
class SourceRequestForm(forms.ModelForm):
"""
Форма для создания и редактирования заявок на источники.
"""
# Дополнительные поля для координат ГСО
coords_lat = forms.FloatField(
required=False,
label='Широта ГСО',
widget=forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.000001',
'placeholder': 'Например: 55.751244'
})
)
coords_lon = forms.FloatField(
required=False,
label='Долгота ГСО',
widget=forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.000001',
'placeholder': 'Например: 37.618423'
})
)
# Дополнительные поля для координат источника
coords_source_lat = forms.FloatField(
required=False,
label='Широта источника',
widget=forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.000001',
'placeholder': 'Например: 55.751244'
})
)
coords_source_lon = forms.FloatField(
required=False,
label='Долгота источника',
widget=forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.000001',
'placeholder': 'Например: 37.618423'
})
)
# Дополнительные поля для координат объекта
coords_object_lat = forms.FloatField(
required=False,
label='Широта объекта',
widget=forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.000001',
'placeholder': 'Например: 55.751244'
})
)
coords_object_lon = forms.FloatField(
required=False,
label='Долгота объекта',
widget=forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.000001',
'placeholder': 'Например: 37.618423'
})
)
class Meta:
from .models import SourceRequest
model = SourceRequest
fields = [
'source',
'satellite',
'status',
'priority',
'planned_at',
'request_date',
'card_date',
'downlink',
'uplink',
'transfer',
'region',
'gso_success',
'kubsat_success',
'comment',
]
widgets = {
'source': forms.Select(attrs={
'class': 'form-select',
}),
'satellite': forms.Select(attrs={
'class': 'form-select',
}),
'status': forms.Select(attrs={
'class': 'form-select'
}),
'priority': forms.Select(attrs={
'class': 'form-select'
}),
'planned_at': forms.DateTimeInput(attrs={
'class': 'form-control',
'type': 'datetime-local'
}),
'request_date': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
}),
'card_date': forms.DateInput(attrs={
'class': 'form-control',
'type': 'date'
}),
'downlink': forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.01',
'placeholder': 'Частота downlink в МГц'
}),
'uplink': forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.01',
'placeholder': 'Частота uplink в МГц'
}),
'transfer': forms.NumberInput(attrs={
'class': 'form-control',
'step': '0.01',
'placeholder': 'Перенос в МГц'
}),
'region': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'Район/местоположение'
}),
'gso_success': forms.Select(
choices=[(None, '-'), (True, 'Да'), (False, 'Нет')],
attrs={'class': 'form-select'}
),
'kubsat_success': forms.Select(
choices=[(None, '-'), (True, 'Да'), (False, 'Нет')],
attrs={'class': 'form-select'}
),
'comment': forms.Textarea(attrs={
'class': 'form-control',
'rows': 3,
'placeholder': 'Введите комментарий'
}),
}
labels = {
'source': 'Источник',
'satellite': 'Спутник',
'status': 'Статус',
'priority': 'Приоритет',
'planned_at': 'Дата и время планирования',
'request_date': 'Дата заявки',
'card_date': 'Дата формирования карточки',
'downlink': 'Частота Downlink (МГц)',
'uplink': 'Частота Uplink (МГц)',
'transfer': 'Перенос (МГц)',
'region': 'Район',
'gso_success': 'ГСО успешно?',
'kubsat_success': 'Кубсат успешно?',
'comment': 'Комментарий',
}
def __init__(self, *args, **kwargs):
# Извлекаем source_id если передан
source_id = kwargs.pop('source_id', None)
super().__init__(*args, **kwargs)
# Загружаем queryset для источников и спутников
self.fields['source'].queryset = Source.objects.all().order_by('-id')
self.fields['source'].required = False
self.fields['satellite'].queryset = Satellite.objects.all().order_by('name')
# Если передан source_id, устанавливаем его как начальное значение
if source_id:
self.fields['source'].initial = source_id
# Можно сделать поле только для чтения
self.fields['source'].widget.attrs['readonly'] = True
# Пытаемся заполнить данные из источника
try:
source = Source.objects.get(pk=source_id)
self._fill_from_source(source)
except Source.DoesNotExist:
pass
# Настраиваем виджеты для булевых полей
self.fields['gso_success'].widget = forms.Select(
choices=[(None, '-'), (True, 'Да'), (False, 'Нет')],
attrs={'class': 'form-select'}
)
self.fields['kubsat_success'].widget = forms.Select(
choices=[(None, '-'), (True, 'Да'), (False, 'Нет')],
attrs={'class': 'form-select'}
)
# Заполняем координаты из существующего объекта
if self.instance and self.instance.pk:
if self.instance.coords:
self.fields['coords_lat'].initial = self.instance.coords.y
self.fields['coords_lon'].initial = self.instance.coords.x
if self.instance.coords_source:
self.fields['coords_source_lat'].initial = self.instance.coords_source.y
self.fields['coords_source_lon'].initial = self.instance.coords_source.x
if self.instance.coords_object:
self.fields['coords_object_lat'].initial = self.instance.coords_object.y
self.fields['coords_object_lon'].initial = self.instance.coords_object.x
def _fill_from_source(self, source):
"""Заполняет поля формы данными из источника и его связанных объектов."""
# Получаем первую точку источника с транспондером
objitem = source.source_objitems.select_related(
'transponder', 'transponder__sat_id', 'parameter_obj'
).filter(transponder__isnull=False).first()
if objitem and objitem.transponder:
transponder = objitem.transponder
# Заполняем данные из транспондера
if transponder.downlink:
self.fields['downlink'].initial = transponder.downlink
if transponder.uplink:
self.fields['uplink'].initial = transponder.uplink
if transponder.transfer:
self.fields['transfer'].initial = transponder.transfer
if transponder.sat_id:
self.fields['satellite'].initial = transponder.sat_id.pk
# Координаты из источника
if source.coords_average:
self.fields['coords_lat'].initial = source.coords_average.y
self.fields['coords_lon'].initial = source.coords_average.x
def save(self, commit=True):
from django.contrib.gis.geos import Point
instance = super().save(commit=False)
# Обрабатываем координаты ГСО
coords_lat = self.cleaned_data.get('coords_lat')
coords_lon = self.cleaned_data.get('coords_lon')
if coords_lat is not None and coords_lon is not None:
instance.coords = Point(coords_lon, coords_lat, srid=4326)
elif coords_lat is None and coords_lon is None:
instance.coords = None
# Обрабатываем координаты источника
coords_source_lat = self.cleaned_data.get('coords_source_lat')
coords_source_lon = self.cleaned_data.get('coords_source_lon')
if coords_source_lat is not None and coords_source_lon is not None:
instance.coords_source = Point(coords_source_lon, coords_source_lat, srid=4326)
elif coords_source_lat is None and coords_source_lon is None:
instance.coords_source = None
# Обрабатываем координаты объекта
coords_object_lat = self.cleaned_data.get('coords_object_lat')
coords_object_lon = self.cleaned_data.get('coords_object_lon')
if coords_object_lat is not None and coords_object_lon is not None:
instance.coords_object = Point(coords_object_lon, coords_object_lat, srid=4326)
elif coords_object_lat is None and coords_object_lon is None:
instance.coords_object = None
if commit:
instance.save()
return instance