Рефакторинг и деплоинг

This commit is contained in:
2025-11-09 23:46:08 +03:00
parent 331a9e41cb
commit a0f20f9a60
65 changed files with 5925 additions and 2003 deletions

View File

@@ -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"
)
]