Рефакторинг и деплоинг
This commit is contained in:
@@ -1,61 +1,122 @@
|
||||
from django.db import models
|
||||
# Django imports
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.gis.db import models as gis
|
||||
from django.contrib.gis.db.models import functions
|
||||
from django.db.models import F, ExpressionWrapper
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
from django.db import models
|
||||
from django.db.models import ExpressionWrapper, F
|
||||
from django.utils import timezone
|
||||
|
||||
|
||||
def get_default_polarization():
|
||||
obj, created = Polarization.objects.get_or_create(
|
||||
name="-"
|
||||
)
|
||||
obj, created = Polarization.objects.get_or_create(name="-")
|
||||
return obj.id
|
||||
|
||||
|
||||
def get_default_modulation():
|
||||
obj, created = Modulation.objects.get_or_create(
|
||||
name="-"
|
||||
)
|
||||
obj, created = Modulation.objects.get_or_create(name="-")
|
||||
return obj.id
|
||||
|
||||
|
||||
def get_default_standard():
|
||||
obj, created = Standard.objects.get_or_create(
|
||||
name="-"
|
||||
)
|
||||
obj, created = Standard.objects.get_or_create(name="-")
|
||||
return obj.id
|
||||
|
||||
|
||||
class CustomUser(models.Model):
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
||||
"""
|
||||
Расширенная модель пользователя с ролями.
|
||||
|
||||
Добавляет систему ролей к стандартной модели User Django.
|
||||
"""
|
||||
|
||||
ROLE_CHOICES = [
|
||||
('admin', 'Администратор'),
|
||||
('moderator', 'Модератор'),
|
||||
('user', 'Пользователь'),
|
||||
("admin", "Администратор"),
|
||||
("moderator", "Модератор"),
|
||||
("user", "Пользователь"),
|
||||
]
|
||||
|
||||
role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='user', verbose_name='Роль пользователя')
|
||||
|
||||
|
||||
# Связи
|
||||
user = models.OneToOneField(
|
||||
User,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="Пользователь",
|
||||
help_text="Связанный пользователь Django",
|
||||
)
|
||||
|
||||
# Основные поля
|
||||
role = models.CharField(
|
||||
max_length=20,
|
||||
choices=ROLE_CHOICES,
|
||||
default="user",
|
||||
verbose_name="Роль пользователя",
|
||||
db_index=True,
|
||||
help_text="Роль пользователя в системе",
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user.first_name} {self.user.last_name}" if self.user.first_name and self.user.last_name else self.user.username
|
||||
|
||||
return (
|
||||
f"{self.user.first_name} {self.user.last_name}"
|
||||
if self.user.first_name and self.user.last_name
|
||||
else self.user.username
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Пользователь"
|
||||
verbose_name_plural = "Пользователи"
|
||||
ordering = ["user__username"]
|
||||
|
||||
|
||||
class SigmaParMark(models.Model):
|
||||
mark = models.BooleanField(null=True, blank=True, verbose_name="Наличие сигнала")
|
||||
timestamp = models.DateTimeField(null=True, blank=True, verbose_name="Время")
|
||||
"""
|
||||
Модель отметки о наличии сигнала.
|
||||
|
||||
Используется для фиксации моментов времени когда сигнал был обнаружен или потерян.
|
||||
"""
|
||||
|
||||
# Основные поля
|
||||
mark = models.BooleanField(
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Наличие сигнала",
|
||||
help_text="True - сигнал обнаружен, False - сигнал отсутствует",
|
||||
)
|
||||
timestamp = models.DateTimeField(
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Время",
|
||||
db_index=True,
|
||||
help_text="Время фиксации отметки",
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
timestamp = self.timestamp.strftime("%d.%m.%Y %H:%M")
|
||||
return f'+ {timestamp}' if self.mark else f'- {timestamp}'
|
||||
|
||||
if self.timestamp:
|
||||
timestamp = self.timestamp.strftime("%d.%m.%Y %H:%M")
|
||||
return f"+ {timestamp}" if self.mark else f"- {timestamp}"
|
||||
return "Отметка без времени"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Отметка"
|
||||
verbose_name_plural = "Отметки"
|
||||
ordering = ["-timestamp"]
|
||||
|
||||
|
||||
class Mirror(models.Model):
|
||||
name = models.CharField(max_length=30, unique=True, verbose_name="Имя зеркала")
|
||||
"""
|
||||
Модель зеркала антенны.
|
||||
|
||||
Представляет физическое зеркало антенны для приема спутникового сигнала.
|
||||
"""
|
||||
|
||||
# Основные поля
|
||||
name = models.CharField(
|
||||
max_length=30,
|
||||
unique=True,
|
||||
verbose_name="Имя зеркала",
|
||||
db_index=True,
|
||||
help_text="Уникальное название зеркала антенны",
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@@ -63,9 +124,24 @@ class Mirror(models.Model):
|
||||
class Meta:
|
||||
verbose_name = "Зеркало"
|
||||
verbose_name_plural = "Зеркала"
|
||||
ordering = ["name"]
|
||||
|
||||
|
||||
class Polarization(models.Model):
|
||||
name = models.CharField(max_length=20, unique=True, verbose_name="Поляризация")
|
||||
"""
|
||||
Модель поляризации сигнала.
|
||||
|
||||
Определяет тип поляризации спутникового сигнала (H, V, L, R и т.д.).
|
||||
"""
|
||||
|
||||
# Основные поля
|
||||
name = models.CharField(
|
||||
max_length=20,
|
||||
unique=True,
|
||||
verbose_name="Поляризация",
|
||||
db_index=True,
|
||||
help_text="Тип поляризации (H - горизонтальная, V - вертикальная, L - левая круговая, R - правая круговая)",
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@@ -73,10 +149,24 @@ class Polarization(models.Model):
|
||||
class Meta:
|
||||
verbose_name = "Поляризация"
|
||||
verbose_name_plural = "Поляризация"
|
||||
ordering = ["name"]
|
||||
|
||||
|
||||
class Modulation(models.Model):
|
||||
name = models.CharField(max_length=20, unique=True, verbose_name="Модуляция", db_index=True)
|
||||
"""
|
||||
Модель типа модуляции сигнала.
|
||||
|
||||
Определяет схему модуляции (QPSK, 8PSK, 16APSK и т.д.).
|
||||
"""
|
||||
|
||||
# Основные поля
|
||||
name = models.CharField(
|
||||
max_length=20,
|
||||
unique=True,
|
||||
verbose_name="Модуляция",
|
||||
db_index=True,
|
||||
help_text="Тип модуляции сигнала (QPSK, 8PSK, 16APSK и т.д.)",
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@@ -84,10 +174,24 @@ class Modulation(models.Model):
|
||||
class Meta:
|
||||
verbose_name = "Модуляция"
|
||||
verbose_name_plural = "Модуляции"
|
||||
ordering = ["name"]
|
||||
|
||||
|
||||
class Standard(models.Model):
|
||||
name = models.CharField(max_length=20, unique=True, verbose_name="Стандарт")
|
||||
"""
|
||||
Модель стандарта передачи данных.
|
||||
|
||||
Определяет стандарт передачи (DVB-S, DVB-S2, DVB-S2X и т.д.).
|
||||
"""
|
||||
|
||||
# Основные поля
|
||||
name = models.CharField(
|
||||
max_length=20,
|
||||
unique=True,
|
||||
verbose_name="Стандарт",
|
||||
db_index=True,
|
||||
help_text="Стандарт передачи данных (DVB-S, DVB-S2, DVB-S2X и т.д.)",
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@@ -95,11 +199,30 @@ class Standard(models.Model):
|
||||
class Meta:
|
||||
verbose_name = "Стандарт"
|
||||
verbose_name_plural = "Стандарты"
|
||||
ordering = ["name"]
|
||||
|
||||
|
||||
class Satellite(models.Model):
|
||||
name = models.CharField(max_length=100, unique=True, verbose_name="Имя спутника", db_index=True)
|
||||
norad = models.IntegerField(blank=True, null=True, verbose_name="NORAD ID")
|
||||
"""
|
||||
Модель спутника.
|
||||
|
||||
Представляет спутник связи с его основными характеристиками.
|
||||
"""
|
||||
|
||||
# Основные поля
|
||||
name = models.CharField(
|
||||
max_length=100,
|
||||
unique=True,
|
||||
verbose_name="Имя спутника",
|
||||
db_index=True,
|
||||
help_text="Название спутника",
|
||||
)
|
||||
norad = models.IntegerField(
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name="NORAD ID",
|
||||
help_text="Идентификатор NORAD для отслеживания спутника",
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@@ -107,69 +230,250 @@ class Satellite(models.Model):
|
||||
class Meta:
|
||||
verbose_name = "Спутник"
|
||||
verbose_name_plural = "Спутники"
|
||||
ordering = ["name"]
|
||||
|
||||
|
||||
class ObjItemQuerySet(models.QuerySet):
|
||||
"""Custom QuerySet для модели ObjItem с оптимизированными запросами"""
|
||||
|
||||
def with_related(self):
|
||||
"""Оптимизирует запросы, загружая связанные объекты"""
|
||||
return self.select_related(
|
||||
"geo_obj",
|
||||
"updated_by__user",
|
||||
"created_by__user",
|
||||
"source_type_obj",
|
||||
).prefetch_related(
|
||||
"parameters_obj__id_satellite",
|
||||
"parameters_obj__polarization",
|
||||
"parameters_obj__modulation",
|
||||
"parameters_obj__standard",
|
||||
)
|
||||
|
||||
def recent(self, days=30):
|
||||
"""Возвращает объекты, созданные за последние N дней"""
|
||||
from datetime import timedelta
|
||||
|
||||
cutoff_date = timezone.now() - timedelta(days=days)
|
||||
return self.filter(created_at__gte=cutoff_date)
|
||||
|
||||
def by_user(self, user):
|
||||
"""Возвращает объекты, созданные указанным пользователем"""
|
||||
return self.filter(created_by=user)
|
||||
|
||||
|
||||
class ObjItemManager(models.Manager):
|
||||
"""Custom Manager для модели ObjItem"""
|
||||
|
||||
def get_queryset(self):
|
||||
return ObjItemQuerySet(self.model, using=self._db)
|
||||
|
||||
def with_related(self):
|
||||
"""Возвращает queryset с предзагруженными связанными объектами"""
|
||||
return self.get_queryset().with_related()
|
||||
|
||||
def recent(self, days=30):
|
||||
"""Возвращает недавно созданные объекты"""
|
||||
return self.get_queryset().recent(days)
|
||||
|
||||
def by_user(self, user):
|
||||
"""Возвращает объекты пользователя"""
|
||||
return self.get_queryset().by_user(user)
|
||||
|
||||
|
||||
class ObjItem(models.Model):
|
||||
name = models.CharField(null=True, blank=True, max_length=100, verbose_name="Имя объекта", db_index=True)
|
||||
# id_satellite = models.ForeignKey(Satellite, on_delete=models.PROTECT, related_name="objitems", verbose_name="Спутник")
|
||||
# id_vch_load = models.ForeignKey(Parameter, on_delete=models.CASCADE, related_name="objitems", verbose_name="ВЧ загрузка")
|
||||
# id_geo = models.ForeignKey(Geo, on_delete=models.CASCADE, related_name="objitems", verbose_name="Геоданные")
|
||||
# id_user_add = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, related_name="objitems", verbose_name="Пользователь", null=True, blank=True)
|
||||
# id_source_type = models.ForeignKey(SourceType, on_delete=models.SET_NULL, related_name="objitems", verbose_name='Тип источника', null=True, blank=True)
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
|
||||
created_by = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, related_name="objitems_created",
|
||||
null=True, blank=True, verbose_name="Создан пользователем")
|
||||
updated_at = models.DateTimeField(auto_now=True, verbose_name="Дата последнего изменения")
|
||||
updated_by = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, related_name="objitems_updated",
|
||||
null=True, blank=True, verbose_name="Изменен пользователем")
|
||||
"""
|
||||
Модель объекта (источника сигнала).
|
||||
|
||||
Центральная модель, объединяющая информацию о ВЧ параметрах, геолокации и типе источника.
|
||||
"""
|
||||
|
||||
# Основные поля
|
||||
name = models.CharField(
|
||||
null=True,
|
||||
blank=True,
|
||||
max_length=100,
|
||||
verbose_name="Имя объекта",
|
||||
db_index=True,
|
||||
help_text="Название объекта/источника сигнала",
|
||||
)
|
||||
|
||||
# Метаданные
|
||||
created_at = models.DateTimeField(
|
||||
auto_now_add=True,
|
||||
verbose_name="Дата создания",
|
||||
help_text="Дата и время создания записи",
|
||||
)
|
||||
created_by = models.ForeignKey(
|
||||
CustomUser,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="objitems_created",
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Создан пользователем",
|
||||
help_text="Пользователь, создавший запись",
|
||||
)
|
||||
updated_at = models.DateTimeField(
|
||||
auto_now=True,
|
||||
verbose_name="Дата последнего изменения",
|
||||
help_text="Дата и время последнего изменения",
|
||||
)
|
||||
updated_by = models.ForeignKey(
|
||||
CustomUser,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="objitems_updated",
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Изменен пользователем",
|
||||
help_text="Пользователь, последним изменивший запись",
|
||||
)
|
||||
|
||||
# Custom manager
|
||||
objects = ObjItemManager()
|
||||
|
||||
def __str__(self):
|
||||
return f"Объект {self.name}"
|
||||
return f"Объект {self.name}" if self.name else f"Объект #{self.pk}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Объект"
|
||||
verbose_name_plural = "Объекты"
|
||||
# constraints = [
|
||||
# models.UniqueConstraint(
|
||||
# fields=['id_vch_load', 'id_geo'],
|
||||
# name='unique_objitem_combination'
|
||||
# )
|
||||
# ]
|
||||
ordering = ["-updated_at"]
|
||||
indexes = [
|
||||
models.Index(fields=["name"]),
|
||||
models.Index(fields=["-updated_at"]),
|
||||
models.Index(fields=["-created_at"]),
|
||||
]
|
||||
|
||||
|
||||
class SourceType(models.Model):
|
||||
name = models.CharField(max_length=50, unique=True, verbose_name="Тип источника")
|
||||
objitem = models.OneToOneField(ObjItem, on_delete=models.SET_NULL, verbose_name="Гео", related_name="source_type_obj", null=True)
|
||||
"""
|
||||
Модель типа источника сигнала.
|
||||
|
||||
Классифицирует источники по типам (наземный, морской, воздушный и т.д.).
|
||||
"""
|
||||
|
||||
# Основные поля
|
||||
name = models.CharField(
|
||||
max_length=50,
|
||||
unique=True,
|
||||
verbose_name="Тип источника",
|
||||
db_index=True,
|
||||
help_text="Тип источника сигнала",
|
||||
)
|
||||
|
||||
# Связи
|
||||
objitem = models.OneToOneField(
|
||||
ObjItem,
|
||||
on_delete=models.SET_NULL,
|
||||
verbose_name="Объект",
|
||||
related_name="source_type_obj",
|
||||
null=True,
|
||||
help_text="Связанный объект",
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Тип источника"
|
||||
verbose_name_plural = 'Типы источников'
|
||||
verbose_name_plural = "Типы источников"
|
||||
ordering = ["name"]
|
||||
|
||||
|
||||
class Parameter(models.Model):
|
||||
id_satellite = models.ForeignKey(Satellite, on_delete=models.PROTECT, related_name="parameters", verbose_name="Спутник", null=True)
|
||||
id_satellite = models.ForeignKey(
|
||||
Satellite,
|
||||
on_delete=models.PROTECT,
|
||||
related_name="parameters",
|
||||
verbose_name="Спутник",
|
||||
null=True,
|
||||
)
|
||||
polarization = models.ForeignKey(
|
||||
Polarization, default=get_default_polarization, on_delete=models.SET_DEFAULT, related_name="polarizations", null=True, blank=True, verbose_name="Поляризация"
|
||||
Polarization,
|
||||
default=get_default_polarization,
|
||||
on_delete=models.SET_DEFAULT,
|
||||
related_name="polarizations",
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Поляризация",
|
||||
)
|
||||
frequency = models.FloatField(
|
||||
default=0,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Частота, МГц",
|
||||
db_index=True,
|
||||
validators=[MinValueValidator(0), MaxValueValidator(50000)],
|
||||
help_text="Частота в диапазоне от 0 до 50000 МГц",
|
||||
)
|
||||
freq_range = models.FloatField(
|
||||
default=0,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Полоса частот, МГц",
|
||||
validators=[MinValueValidator(0), MaxValueValidator(1000)],
|
||||
help_text="Полоса частот в диапазоне от 0 до 1000 МГц",
|
||||
)
|
||||
bod_velocity = models.FloatField(
|
||||
default=0,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Символьная скорость, БОД",
|
||||
validators=[MinValueValidator(0)],
|
||||
help_text="Символьная скорость должна быть положительной",
|
||||
)
|
||||
frequency = models.FloatField(default=0, null=True, blank=True, verbose_name="Частота, МГц", db_index=True)
|
||||
freq_range = models.FloatField(default=0, null=True, blank=True, verbose_name="Полоса частот, МГц")
|
||||
bod_velocity = models.FloatField(default=0, null=True, blank=True, verbose_name="Символьная скорость, БОД")
|
||||
modulation = models.ForeignKey(
|
||||
Modulation, default=get_default_modulation, on_delete=models.SET_DEFAULT, related_name="modulations", null=True, blank=True, verbose_name="Модуляция"
|
||||
Modulation,
|
||||
default=get_default_modulation,
|
||||
on_delete=models.SET_DEFAULT,
|
||||
related_name="modulations",
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Модуляция",
|
||||
)
|
||||
snr = models.FloatField(
|
||||
default=0,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="ОСШ",
|
||||
validators=[MinValueValidator(-50), MaxValueValidator(100)],
|
||||
help_text="Отношение сигнал/шум в диапазоне от -50 до 100 дБ",
|
||||
)
|
||||
snr = models.FloatField(default=0, null=True, blank=True, verbose_name="ОСШ")
|
||||
standard = models.ForeignKey(
|
||||
Standard, default=get_default_standard, on_delete=models.SET_DEFAULT, related_name="standards", null=True, blank=True, verbose_name="Стандарт"
|
||||
Standard,
|
||||
default=get_default_standard,
|
||||
on_delete=models.SET_DEFAULT,
|
||||
related_name="standards",
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Стандарт",
|
||||
)
|
||||
# id_user_add = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, related_name="parameter_added", verbose_name="Пользователь", null=True, blank=True)
|
||||
objitems = models.ManyToManyField(ObjItem, related_name="parameters_obj", verbose_name="Источники", blank=True)
|
||||
objitems = models.ManyToManyField(
|
||||
ObjItem, related_name="parameters_obj", verbose_name="Источники", blank=True
|
||||
)
|
||||
# id_sigma_parameter = models.ManyToManyField(SigmaParameter, on_delete=models.SET_NULL, related_name="sigma_parameter", verbose_name="ВЧ с sigma", null=True, blank=True)
|
||||
# id_sigma_parameter = models.ManyToManyField(SigmaParameter, verbose_name="ВЧ с sigma", null=True, blank=True)
|
||||
|
||||
def clean(self):
|
||||
"""Валидация на уровне модели"""
|
||||
super().clean()
|
||||
|
||||
# Проверка что частота больше полосы частот
|
||||
if self.frequency and self.freq_range:
|
||||
if self.freq_range > self.frequency:
|
||||
raise ValidationError(
|
||||
{"freq_range": "Полоса частот не может быть больше частоты"}
|
||||
)
|
||||
|
||||
# Проверка что символьная скорость соответствует полосе частот
|
||||
if self.bod_velocity and self.freq_range:
|
||||
if self.bod_velocity > self.freq_range * 1000000: # Конвертация МГц в Гц
|
||||
raise ValidationError(
|
||||
{
|
||||
"bod_velocity": "Символьная скорость не может превышать полосу частот"
|
||||
}
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
polarization_name = self.polarization.name if self.polarization else "-"
|
||||
@@ -180,13 +484,13 @@ class Parameter(models.Model):
|
||||
verbose_name = "ВЧ загрузка"
|
||||
verbose_name_plural = "ВЧ загрузки"
|
||||
indexes = [
|
||||
models.Index(fields=['id_satellite', 'frequency']),
|
||||
models.Index(fields=['frequency', 'polarization']),
|
||||
models.Index(fields=["id_satellite", "frequency"]),
|
||||
models.Index(fields=["frequency", "polarization"]),
|
||||
]
|
||||
# constraints = [
|
||||
# models.UniqueConstraint(
|
||||
# fields=[
|
||||
# 'polarization', 'frequency', 'freq_range',
|
||||
# 'polarization', 'frequency', 'freq_range',
|
||||
# 'bod_velocity', 'modulation', 'snr', 'standard'
|
||||
# ],
|
||||
# name='unique_parameter_combination'
|
||||
@@ -195,55 +499,151 @@ class Parameter(models.Model):
|
||||
|
||||
|
||||
class SigmaParameter(models.Model):
|
||||
TRANSFERS = [
|
||||
(-1.0, "-"),
|
||||
(9750.0, "9750 МГц"),
|
||||
(10750.0, "10750 МГц")
|
||||
]
|
||||
TRANSFERS = [(-1.0, "-"), (9750.0, "9750 МГц"), (10750.0, "10750 МГц")]
|
||||
|
||||
id_satellite = models.ForeignKey(Satellite, on_delete=models.PROTECT, related_name="sigmapar_sat", verbose_name="Спутник")
|
||||
id_satellite = models.ForeignKey(
|
||||
Satellite,
|
||||
on_delete=models.PROTECT,
|
||||
related_name="sigmapar_sat",
|
||||
verbose_name="Спутник",
|
||||
)
|
||||
transfer = models.FloatField(
|
||||
choices=TRANSFERS,
|
||||
default=-1.0,
|
||||
verbose_name="Перенос по частоте"
|
||||
verbose_name="Перенос по частоте",
|
||||
help_text="Выберите перенос по частоте",
|
||||
)
|
||||
status = models.CharField(
|
||||
max_length=20,
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name="Статус",
|
||||
help_text="Статус измерения",
|
||||
)
|
||||
frequency = models.FloatField(
|
||||
default=0,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Частота, МГц",
|
||||
db_index=True,
|
||||
validators=[MinValueValidator(0), MaxValueValidator(50000)],
|
||||
help_text="Частота в диапазоне от 0 до 50000 МГц",
|
||||
)
|
||||
status = models.CharField(max_length=20, blank=True, null=True, verbose_name="Статус")
|
||||
frequency = models.FloatField(default=0, null=True, blank=True, verbose_name="Частота, МГц", db_index=True)
|
||||
transfer_frequency = models.GeneratedField(
|
||||
expression=ExpressionWrapper(
|
||||
F('frequency') + F('transfer'),
|
||||
output_field=models.FloatField()
|
||||
F("frequency") + F("transfer"), output_field=models.FloatField()
|
||||
),
|
||||
output_field=models.FloatField(),
|
||||
db_persist=True,
|
||||
null=True, blank=True, verbose_name="Частота в Ku, МГц"
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Частота в Ku, МГц",
|
||||
)
|
||||
freq_range = models.FloatField(
|
||||
default=0,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Полоса частот, МГц",
|
||||
validators=[MinValueValidator(0), MaxValueValidator(1000)],
|
||||
help_text="Полоса частот в диапазоне от 0 до 1000 МГц",
|
||||
)
|
||||
power = models.FloatField(
|
||||
default=0,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Мощность, дБм",
|
||||
validators=[MinValueValidator(-100), MaxValueValidator(100)],
|
||||
help_text="Мощность сигнала в диапазоне от -100 до 100 дБм",
|
||||
)
|
||||
bod_velocity = models.FloatField(
|
||||
default=0,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Символьная скорость, БОД",
|
||||
validators=[MinValueValidator(0)],
|
||||
help_text="Символьная скорость должна быть положительной",
|
||||
)
|
||||
freq_range = models.FloatField(default=0, null=True, blank=True, verbose_name="Полоса частот, МГц")
|
||||
power = models.FloatField(default=0, null=True, blank=True, verbose_name="Мощность, дБм")
|
||||
bod_velocity = models.FloatField(default=0, null=True, blank=True, verbose_name="Символьная скорость, БОД")
|
||||
polarization = models.ForeignKey(
|
||||
Polarization, default=get_default_polarization, on_delete=models.SET_DEFAULT, related_name="polarizations_sigma", null=True, blank=True, verbose_name="Поляризация"
|
||||
Polarization,
|
||||
default=get_default_polarization,
|
||||
on_delete=models.SET_DEFAULT,
|
||||
related_name="polarizations_sigma",
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Поляризация",
|
||||
)
|
||||
modulation = models.ForeignKey(
|
||||
Modulation, default=get_default_modulation, on_delete=models.SET_DEFAULT, related_name="modulations_sigma", null=True, blank=True, verbose_name="Модуляция"
|
||||
Modulation,
|
||||
default=get_default_modulation,
|
||||
on_delete=models.SET_DEFAULT,
|
||||
related_name="modulations_sigma",
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Модуляция",
|
||||
)
|
||||
snr = models.FloatField(
|
||||
default=0,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="ОСШ, Дб",
|
||||
validators=[MinValueValidator(-50), MaxValueValidator(100)],
|
||||
help_text="Отношение сигнал/шум в диапазоне от -50 до 100 дБ",
|
||||
)
|
||||
snr = models.FloatField(default=0, null=True, blank=True, verbose_name="ОСШ, Дб")
|
||||
standard = models.ForeignKey(
|
||||
Standard, default=get_default_standard, on_delete=models.SET_DEFAULT, related_name="standards_sigma", null=True, blank=True, verbose_name="Стандарт"
|
||||
Standard,
|
||||
default=get_default_standard,
|
||||
on_delete=models.SET_DEFAULT,
|
||||
related_name="standards_sigma",
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Стандарт",
|
||||
)
|
||||
packets = models.BooleanField(
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Пакетность",
|
||||
help_text="Наличие пакетной передачи",
|
||||
)
|
||||
datetime_begin = models.DateTimeField(
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Время начала измерения",
|
||||
help_text="Дата и время начала измерения",
|
||||
)
|
||||
datetime_end = models.DateTimeField(
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Время окончания измерения",
|
||||
help_text="Дата и время окончания измерения",
|
||||
)
|
||||
packets = models.BooleanField(null=True, blank=True, verbose_name="Пакетность")
|
||||
datetime_begin = models.DateTimeField(null=True, blank=True, verbose_name="Время начала измерения")
|
||||
datetime_end = models.DateTimeField(null=True, blank=True, verbose_name="Время окончания измерения")
|
||||
mark = models.ManyToManyField(SigmaParMark, verbose_name="Отметка", blank=True)
|
||||
parameter = models.ForeignKey(
|
||||
Parameter,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name='sigma_parameter',
|
||||
verbose_name="ВЧ",
|
||||
null=True,
|
||||
blank=True
|
||||
related_name="sigma_parameter",
|
||||
verbose_name="ВЧ",
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
"""Валидация на уровне модели"""
|
||||
super().clean()
|
||||
|
||||
# Проверка что время окончания больше времени начала
|
||||
if self.datetime_begin and self.datetime_end:
|
||||
if self.datetime_end < self.datetime_begin:
|
||||
raise ValidationError(
|
||||
{"datetime_end": "Время окончания должно быть позже времени начала"}
|
||||
)
|
||||
|
||||
# Проверка что частота больше полосы частот
|
||||
if self.frequency and self.freq_range:
|
||||
if self.freq_range > self.frequency:
|
||||
raise ValidationError(
|
||||
{"freq_range": "Полоса частот не может быть больше частоты"}
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
modulation_name = self.modulation.name if self.modulation else "-"
|
||||
return f"Sigma-{self.frequency}:{self.freq_range} МГц:{modulation_name}"
|
||||
@@ -252,53 +652,129 @@ class SigmaParameter(models.Model):
|
||||
verbose_name = "ВЧ sigma"
|
||||
verbose_name_plural = "ВЧ sigma"
|
||||
|
||||
|
||||
class Geo(models.Model):
|
||||
mirrors = models.ManyToManyField(Mirror, related_name="geo_mirrors", verbose_name="Зеркала",)
|
||||
timestamp = models.DateTimeField(null=True, blank=True, verbose_name="Время", db_index=True)
|
||||
coords = gis.PointField(srid=4326, null=True, blank=True, verbose_name="Координата геолокации")
|
||||
location = models.CharField(max_length=255, null=True, blank=True, verbose_name="Метоположение")
|
||||
comment = models.CharField(max_length=255, blank=True, verbose_name="Комментарий")
|
||||
coords_kupsat = gis.PointField(srid=4326, null=True, blank=True, verbose_name="Координаты Кубсата")
|
||||
coords_valid = gis.PointField(srid=4326, null=True, blank=True, verbose_name="Координаты оперативников")
|
||||
is_average = models.BooleanField(null=True, blank=True, verbose_name="Усреднённое")
|
||||
# id_user_add = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, related_name="geos_added", verbose_name="Пользователь", null=True, blank=True)
|
||||
"""
|
||||
Модель геолокационных данных.
|
||||
|
||||
Хранит информацию о местоположении источника сигнала, включая координаты,
|
||||
данные от различных источников (геолокация, кубсат, оперативники) и расстояния между ними.
|
||||
"""
|
||||
|
||||
# Основные поля
|
||||
timestamp = models.DateTimeField(
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Время",
|
||||
db_index=True,
|
||||
help_text="Время фиксации геолокации",
|
||||
)
|
||||
location = models.CharField(
|
||||
max_length=255,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Местоположение",
|
||||
help_text="Текстовое описание местоположения",
|
||||
)
|
||||
comment = models.CharField(
|
||||
max_length=255,
|
||||
blank=True,
|
||||
verbose_name="Комментарий",
|
||||
help_text="Дополнительные комментарии",
|
||||
)
|
||||
is_average = models.BooleanField(
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Усреднённое",
|
||||
help_text="Является ли координата усредненной",
|
||||
)
|
||||
|
||||
# Координаты
|
||||
coords = gis.PointField(
|
||||
srid=4326,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Координата геолокации",
|
||||
help_text="Основные координаты геолокации (WGS84)",
|
||||
)
|
||||
coords_kupsat = gis.PointField(
|
||||
srid=4326,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Координаты Кубсата",
|
||||
help_text="Координаты, полученные от кубсата (WGS84)",
|
||||
)
|
||||
coords_valid = gis.PointField(
|
||||
srid=4326,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Координаты оперативников",
|
||||
help_text="Координаты, предоставленные оперативным отделом (WGS84)",
|
||||
)
|
||||
|
||||
# Вычисляемые поля - расстояния
|
||||
distance_coords_kup = models.GeneratedField(
|
||||
expression=functions.Distance("coords", "coords_kupsat")/1000,
|
||||
expression=functions.Distance("coords", "coords_kupsat") / 1000,
|
||||
output_field=models.FloatField(),
|
||||
db_persist=True,
|
||||
null=True, blank=True, verbose_name="Расстояние между купсатом и гео, км"
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Расстояние между кубсатом и гео, км",
|
||||
)
|
||||
distance_coords_valid = models.GeneratedField(
|
||||
expression=functions.Distance("coords", "coords_valid")/1000,
|
||||
expression=functions.Distance("coords", "coords_valid") / 1000,
|
||||
output_field=models.FloatField(),
|
||||
db_persist=True,
|
||||
null=True, blank=True, verbose_name="Расстояние между гео и оперативным отделом, км"
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Расстояние между гео и оперативным отделом, км",
|
||||
)
|
||||
distance_kup_valid = models.GeneratedField(
|
||||
expression=functions.Distance("coords_valid", "coords_kupsat")/1000,
|
||||
expression=functions.Distance("coords_valid", "coords_kupsat") / 1000,
|
||||
output_field=models.FloatField(),
|
||||
db_persist=True,
|
||||
null=True, blank=True, verbose_name="Расстояние между купсатом и оперативным отделом, км"
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name="Расстояние между кубсатом и оперативным отделом, км",
|
||||
)
|
||||
|
||||
# Связи
|
||||
mirrors = models.ManyToManyField(
|
||||
Mirror,
|
||||
related_name="geo_mirrors",
|
||||
verbose_name="Зеркала",
|
||||
blank=True,
|
||||
help_text="Зеркала антенн, использованные для приема",
|
||||
)
|
||||
objitem = models.OneToOneField(
|
||||
ObjItem,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name="Объект",
|
||||
related_name="geo_obj",
|
||||
null=True,
|
||||
help_text="Связанный объект",
|
||||
)
|
||||
objitem = models.OneToOneField(ObjItem, on_delete=models.CASCADE, verbose_name="Гео", related_name="geo_obj", null=True)
|
||||
|
||||
def __str__(self):
|
||||
longitude = self.coords.coords[0]
|
||||
latitude = self.coords.coords[1]
|
||||
lon = f"{longitude}E" if longitude > 0 else f"{abs(longitude)}W"
|
||||
lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S"
|
||||
return f"{lat} {lon}, {self.location}"
|
||||
|
||||
if self.coords:
|
||||
longitude = self.coords.coords[0]
|
||||
latitude = self.coords.coords[1]
|
||||
lon = f"{longitude}E" if longitude > 0 else f"{abs(longitude)}W"
|
||||
lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S"
|
||||
location_str = f", {self.location}" if self.location else ""
|
||||
return f"{lat} {lon}{location_str}"
|
||||
return f"Гео #{self.pk}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Гео"
|
||||
verbose_name_plural = "Гео"
|
||||
ordering = ["-timestamp"]
|
||||
indexes = [
|
||||
models.Index(fields=["-timestamp"]),
|
||||
models.Index(fields=["location"]),
|
||||
]
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
fields=[
|
||||
'timestamp', 'coords'
|
||||
],
|
||||
name='unique_geo_combination'
|
||||
fields=["timestamp", "coords"], name="unique_geo_combination"
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user