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

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,5 +1,24 @@
# admin.py
# Django imports
from django import forms
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import Group, User
from django.shortcuts import redirect
from django.urls import reverse
from django.utils import timezone
# Third-party imports
from import_export.admin import ImportExportActionModelAdmin
from leaflet.admin import LeafletGeoAdmin
from more_admin_filters import (
MultiSelectDropdownFilter,
MultiSelectRelatedDropdownFilter,
)
from rangefilter.filters import (
DateRangeQuickSelectListFilterBuilder,
NumericRangeFilterBuilder,
)
from .models import (
Polarization,
Modulation,
@@ -14,37 +33,61 @@ from .models import (
ObjItem,
CustomUser
)
from leaflet.admin import LeafletGeoAdmin
from django import forms
from django.contrib.auth.models import Group
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.contrib.auth.models import User
from django.contrib.gis.db import models as gis
from django.shortcuts import redirect
from django.urls import reverse
from django.utils import timezone
from leaflet.forms.widgets import LeafletWidget
from rangefilter.filters import (
DateRangeFilterBuilder,
DateTimeRangeFilterBuilder,
NumericRangeFilterBuilder,
DateRangeQuickSelectListFilterBuilder,
from .filters import (
GeoKupDistanceFilter,
GeoValidDistanceFilter,
UniqueToggleFilter,
HasSigmaParameterFilter
)
from dynamic_raw_id.admin import DynamicRawIDMixin
from more_admin_filters import MultiSelectDropdownFilter, MultiSelectFilter, MultiSelectRelatedDropdownFilter
from import_export.admin import ImportExportActionModelAdmin
from .filters import GeoKupDistanceFilter, GeoValidDistanceFilter, UniqueToggleFilter, HasSigmaParameterFilter
admin.site.site_title = "Геолокация"
admin.site.site_header = "Geolocation"
admin.site.index_title = "Geo"
# Unregister default User and Group since we're customizing them
admin.site.unregister(User)
admin.site.unregister(Group)
# ============================================================================
# Base Admin Classes
# ============================================================================
class BaseAdmin(admin.ModelAdmin):
"""
Базовый класс для всех admin моделей.
Предоставляет общую функциональность:
- Кнопки сохранения сверху и снизу
- Настройка количества элементов на странице
- Автоматическое заполнение полей created_by и updated_by
"""
save_on_top = True
list_per_page = 50
def save_model(self, request, obj, form, change):
"""
Автоматически заполняет поля created_by и updated_by при сохранении.
Args:
request: HTTP запрос
obj: Сохраняемый объект модели
form: Форма с данными
change: True если это редактирование, False если создание
"""
if not change:
# При создании нового объекта устанавливаем created_by
if hasattr(obj, 'created_by') and not obj.created_by_id:
obj.created_by = getattr(request.user, 'customuser', None)
# При любом сохранении обновляем updated_by
if hasattr(obj, 'updated_by'):
obj.updated_by = getattr(request.user, 'customuser', None)
super().save_model(request, obj, form, change)
class CustomUserInline(admin.StackedInline):
model = CustomUser
can_delete = False
@@ -130,41 +173,167 @@ class UserAdmin(BaseUserAdmin):
admin.site.register(User, UserAdmin)
# @admin.register(CustomUser)
# class CustomUserAdmin(admin.ModelAdmin):
# list_display = ('user', 'role')
# list_filter = ('role',)
# raw_id_fields = ('user',) # For better performance with large number of users
# ============================================================================
# Custom Admin Actions
# ============================================================================
@admin.action(description="Показать выбранные на карте")
def show_on_map(modeladmin, request, queryset):
"""
Action для отображения выбранных Geo объектов на карте.
Оптимизирован для работы с большим количеством объектов:
использует values_list для получения только ID.
"""
selected_ids = queryset.values_list('id', flat=True)
ids_str = ','.join(str(pk) for pk in selected_ids)
return redirect(reverse('mainapp:admin_show_map') + f'?ids={ids_str}')
@admin.action(description="Показать выбранные объекты на карте")
def show_selected_on_map(modeladmin, request, queryset):
"""
Action для отображения выбранных ObjItem объектов на карте.
Оптимизирован для работы с большим количеством объектов:
использует values_list для получения только ID.
"""
selected_ids = queryset.values_list('id', flat=True)
ids_str = ','.join(str(pk) for pk in selected_ids)
return redirect(reverse('mainapp:show_selected_objects_map') + f'?ids={ids_str}')
@admin.action(description="Экспортировать выбранные объекты в CSV")
def export_objects_to_csv(modeladmin, request, queryset):
"""
Action для экспорта выбранных ObjItem объектов в CSV формат.
Оптимизирован с использованием select_related и prefetch_related
для минимизации количества запросов к БД.
"""
import csv
from django.http import HttpResponse
# Оптимизируем queryset
queryset = queryset.select_related(
'geo_obj',
'created_by__user',
'updated_by__user'
).prefetch_related(
'parameters_obj__id_satellite',
'parameters_obj__polarization',
'parameters_obj__modulation'
)
response = HttpResponse(content_type='text/csv; charset=utf-8')
response['Content-Disposition'] = 'attachment; filename="objitems_export.csv"'
response.write('\ufeff') # UTF-8 BOM для корректного отображения в Excel
writer = csv.writer(response)
writer.writerow([
'Название',
'Спутник',
'Частота (МГц)',
'Полоса (МГц)',
'Поляризация',
'Модуляция',
'ОСШ',
'Координаты геолокации',
'Координаты Кубсата',
'Координаты оперативного отдела',
'Расстояние Гео-Куб (км)',
'Расстояние Гео-Опер (км)',
'Дата создания',
'Дата обновления'
])
for obj in queryset:
param = next(iter(obj.parameters_obj.all()), None)
geo = obj.geo_obj
# Форматирование координат
def format_coords(coords):
if not coords:
return "-"
lon, lat = coords.coords[0], coords.coords[1]
lon_str = f"{lon}E" if lon > 0 else f"{abs(lon)}W"
lat_str = f"{lat}N" if lat > 0 else f"{abs(lat)}S"
return f"{lat_str} {lon_str}"
writer.writerow([
obj.name,
param.id_satellite.name if param and param.id_satellite else "-",
param.frequency if param else "-",
param.freq_range if param else "-",
param.polarization.name if param and param.polarization else "-",
param.modulation.name if param and param.modulation else "-",
param.snr if param else "-",
format_coords(geo) if geo and geo.coords else "-",
format_coords(geo) if geo and geo.coords_kupsat else "-",
format_coords(geo) if geo and geo.coords_valid else "-",
round(geo.distance_coords_kup, 3) if geo and geo.distance_coords_kup else "-",
round(geo.distance_coords_valid, 3) if geo and geo.distance_coords_valid else "-",
obj.created_at.strftime("%d.%m.%Y %H:%M:%S") if obj.created_at else "-",
obj.updated_at.strftime("%d.%m.%Y %H:%M:%S") if obj.updated_at else "-"
])
return response
# ============================================================================
# Inline Admin Classes
# ============================================================================
class ParameterObjItemInline(admin.StackedInline):
model = ObjItem.parameters_obj.through
extra = 0
max_num = 1
verbose_name = "ВЧ загрузка"
verbose_name_plural = "ВЧ загрузки"
# ============================================================================
# Admin Classes
# ============================================================================
@admin.register(SigmaParMark)
class SigmaParMarkAdmin(admin.ModelAdmin):
class SigmaParMarkAdmin(BaseAdmin):
"""Админ-панель для модели SigmaParMark."""
list_display = ("mark", "timestamp")
search_fields = ("mark", )
ordering = ("timestamp",)
search_fields = ("mark",)
ordering = ("-timestamp",)
list_filter = (
("timestamp", DateRangeQuickSelectListFilterBuilder()),
)
@admin.register(Polarization)
class PolarizationAdmin(admin.ModelAdmin):
class PolarizationAdmin(BaseAdmin):
"""Админ-панель для модели Polarization."""
list_display = ("name",)
search_fields = ("name",)
ordering = ("name",)
@admin.register(Modulation)
class ModulationAdmin(admin.ModelAdmin):
class ModulationAdmin(BaseAdmin):
"""Админ-панель для модели Modulation."""
list_display = ("name",)
search_fields = ("name",)
ordering = ("name",)
@admin.register(SourceType)
class SourceTypeAdmin(admin.ModelAdmin):
class SourceTypeAdmin(BaseAdmin):
"""Админ-панель для модели SourceType."""
list_display = ("name",)
search_fields = ("name",)
ordering = ("name",)
@admin.register(Standard)
class StandardAdmin(admin.ModelAdmin):
class StandardAdmin(BaseAdmin):
"""Админ-панель для модели Standard."""
list_display = ("name",)
search_fields = ("name",)
ordering = ("name",)
@@ -183,7 +352,15 @@ class SigmaParameterInline(admin.StackedInline):
@admin.register(Parameter)
class ParameterAdmin(ImportExportActionModelAdmin, admin.ModelAdmin):
class ParameterAdmin(ImportExportActionModelAdmin, BaseAdmin):
"""
Админ-панель для модели Parameter.
Оптимизирована для работы с большим количеством параметров:
- Использует select_related для оптимизации запросов
- Предоставляет фильтры по основным характеристикам
- Поддерживает импорт/экспорт данных
"""
list_display = (
"id_satellite",
"frequency",
@@ -195,7 +372,9 @@ class ParameterAdmin(ImportExportActionModelAdmin, admin.ModelAdmin):
"standard",
"sigma_parameter"
)
list_display_links = ("frequency", "id_satellite", )
list_display_links = ("frequency", "id_satellite")
list_select_related = ("polarization", "modulation", "standard", "id_satellite")
list_filter = (
HasSigmaParameterFilter,
("id_satellite", MultiSelectRelatedDropdownFilter),
@@ -206,8 +385,9 @@ class ParameterAdmin(ImportExportActionModelAdmin, admin.ModelAdmin):
("freq_range", NumericRangeFilterBuilder()),
("snr", NumericRangeFilterBuilder()),
)
search_fields = (
"id_satellite",
"id_satellite__name",
"frequency",
"freq_range",
"bod_velocity",
@@ -216,46 +396,52 @@ class ParameterAdmin(ImportExportActionModelAdmin, admin.ModelAdmin):
"polarization__name",
"standard__name",
)
ordering = ("frequency",)
list_select_related = ("polarization", "modulation", "standard", "id_satellite",)
autocomplete_fields = ('objitems',)
# raw_id_fields = ("id_sigma_parameter", )
ordering = ("-frequency",)
autocomplete_fields = ("objitems",)
inlines = [SigmaParameterInline]
# autocomplete_fields = ("id_sigma_parameter", )
def sigma_parameter(self, obj):
"""Отображает связанный параметр Sigma."""
sigma_obj = obj.sigma_parameter.all()
if sigma_obj:
return f"{sigma_obj[0].frequency}: {sigma_obj[0].freq_range}"
return '-'
return "-"
sigma_parameter.short_description = "ВЧ sigma"
@admin.register(SigmaParameter)
class SigmaParameterAdmin(ImportExportActionModelAdmin, admin.ModelAdmin):
class SigmaParameterAdmin(ImportExportActionModelAdmin, BaseAdmin):
"""
Админ-панель для модели SigmaParameter.
Оптимизирована для работы с параметрами Sigma:
- Использует select_related и prefetch_related для оптимизации
- Предоставляет фильтры по основным характеристикам
- Поддерживает импорт/экспорт данных
"""
list_display = (
"id_satellite",
# "status",
"frequency",
"transfer_frequency",
"freq_range",
# "power",
"polarization",
"modulation",
"bod_velocity",
"snr",
# "standard",
"parameter",
# "packets",
"datetime_begin",
"datetime_end",
)
list_display_links = ("id_satellite",)
list_select_related = ("modulation", "standard", "id_satellite", "parameter", "polarization")
readonly_fields = (
"datetime_begin",
"datetime_begin",
"datetime_end",
"transfer_frequency"
)
list_display_links = ("id_satellite",)
list_filter = (
("id_satellite__name", MultiSelectDropdownFilter),
("modulation__name", MultiSelectDropdownFilter),
@@ -263,7 +449,10 @@ class SigmaParameterAdmin(ImportExportActionModelAdmin, admin.ModelAdmin):
("frequency", NumericRangeFilterBuilder()),
("freq_range", NumericRangeFilterBuilder()),
("snr", NumericRangeFilterBuilder()),
("datetime_begin", DateRangeQuickSelectListFilterBuilder()),
("datetime_end", DateRangeQuickSelectListFilterBuilder()),
)
search_fields = (
"id_satellite__name",
"frequency",
@@ -273,45 +462,63 @@ class SigmaParameterAdmin(ImportExportActionModelAdmin, admin.ModelAdmin):
"modulation__name",
"standard__name",
)
autocomplete_fields = ('mark',)
ordering = ("frequency",)
list_select_related = ("modulation", "standard", "id_satellite", "parameter")
prefetch_related = ("mark",)
autocomplete_fields = ("mark",)
ordering = ("-frequency",)
def get_queryset(self, request):
"""Оптимизированный queryset с prefetch_related для mark."""
qs = super().get_queryset(request)
return qs.prefetch_related("mark")
@admin.register(Satellite)
class SatelliteAdmin(admin.ModelAdmin):
class SatelliteAdmin(BaseAdmin):
"""Админ-панель для модели Satellite."""
list_display = ("name",)
search_fields = ("name",)
ordering = ("name",)
@admin.register(Mirror)
class MirrorAdmin(ImportExportActionModelAdmin, admin.ModelAdmin):
class MirrorAdmin(ImportExportActionModelAdmin, BaseAdmin):
"""Админ-панель для модели Mirror с поддержкой импорта/экспорта."""
list_display = ("name",)
search_fields = ("name",)
ordering = ("name",)
@admin.register(Geo)
class GeoAdmin(ImportExportActionModelAdmin, LeafletGeoAdmin):
class GeoAdmin(ImportExportActionModelAdmin, LeafletGeoAdmin, BaseAdmin):
"""
Админ-панель для модели Geo с поддержкой карты Leaflet.
Оптимизирована для работы с геоданными:
- Использует prefetch_related для оптимизации запросов к mirrors
- Предоставляет фильтры по зеркалам, локации и дате
- Поддерживает импорт/экспорт данных
- Интегрирована с Leaflet для отображения на карте
"""
form = LocationForm
readonly_fields = ("distance_coords_kup", "distance_coords_valid", "distance_kup_valid")
fieldsets = (
("Основная информация", {
"fields": ("mirrors", "location", "distance_coords_kup",
"distance_coords_valid", "distance_kup_valid", "timestamp", "comment",)
"fields": ("mirrors", "location", "distance_coords_kup",
"distance_coords_valid", "distance_kup_valid", "timestamp", "comment")
}),
("Координаты: геолокация", {
"fields": ("longitude_geo", "latitude_geo", "coords"),
"fields": ("longitude_geo", "latitude_geo", "coords")
}),
("Координаты: Кубсат", {
"fields": ("longitude_kupsat", "latitude_kupsat", "coords_kupsat"),
"fields": ("longitude_kupsat", "latitude_kupsat", "coords_kupsat")
}),
("Координаты: Оперативный отдел", {
"fields": ("longitude_valid", "latitude_valid", "coords_valid"),
"fields": ("longitude_valid", "latitude_valid", "coords_valid")
}),
)
list_display = (
"formatted_timestamp",
"location",
@@ -321,43 +528,52 @@ class GeoAdmin(ImportExportActionModelAdmin, LeafletGeoAdmin):
"valid_coords",
"is_average",
)
autocomplete_fields = ('mirrors',)
list_display_links = ("formatted_timestamp",)
list_filter = (
("mirrors", MultiSelectRelatedDropdownFilter),
"is_average",
("location", MultiSelectDropdownFilter),
("timestamp", DateRangeQuickSelectListFilterBuilder()),
)
search_fields = (
"mirrors__name",
"location",
"coords",
"coords_kupsat",
"coords_valid"
)
prefetch_related = ("mirrors", )
autocomplete_fields = ("mirrors",)
ordering = ("-timestamp",)
actions = [show_on_map]
settings_overrides = {
'DEFAULT_CENTER': (55.7558, 37.6173),
'DEFAULT_ZOOM': 12,
}
def get_queryset(self, request):
"""Оптимизированный queryset с prefetch_related для mirrors."""
qs = super().get_queryset(request)
return qs.prefetch_related("mirrors")
def mirrors_names(self, obj):
"""Отображает список зеркал через запятую."""
return ", ".join(m.name for m in obj.mirrors.all())
mirrors_names.short_description = "Зеркала"
def formatted_timestamp(self, obj):
"""Форматирует timestamp в локальное время."""
if not obj.timestamp:
return ""
local_time = timezone.localtime(obj.timestamp)
return local_time.strftime("%d.%m.%Y %H:%M:%S")
formatted_timestamp.short_description = "Дата и время"
formatted_timestamp.admin_order_field = "timestamp"
formatted_timestamp.admin_order_field = "timestamp"
def geo_coords(self, obj):
"""Отображает координаты геолокации в формате широта/долгота."""
if not obj.coords:
return "-"
longitude = obj.coords.coords[0]
latitude = obj.coords.coords[1]
lon = f"{longitude}E" if longitude > 0 else f"{abs(longitude)}W"
@@ -366,6 +582,7 @@ class GeoAdmin(ImportExportActionModelAdmin, LeafletGeoAdmin):
geo_coords.short_description = "Координаты геолокации"
def kupsat_coords(self, obj):
"""Отображает координаты Кубсата в формате широта/долгота."""
if obj.coords_kupsat is None:
return "-"
longitude = obj.coords_kupsat.coords[0]
@@ -376,6 +593,7 @@ class GeoAdmin(ImportExportActionModelAdmin, LeafletGeoAdmin):
kupsat_coords.short_description = "Координаты Кубсата"
def valid_coords(self, obj):
"""Отображает координаты оперативного отдела в формате широта/долгота."""
if obj.coords_valid is None:
return "-"
longitude = obj.coords_valid.coords[0]
@@ -385,38 +603,20 @@ class GeoAdmin(ImportExportActionModelAdmin, LeafletGeoAdmin):
return f"{lat} {lon}"
valid_coords.short_description = "Координаты оперативного отдела"
def show_on_map(modeladmin, request, queryset):
# Получаем список ID выбранных объектов
selected_ids = queryset.values_list('id', flat=True)
# Формируем строку вида "1,2,3"
ids_str = ','.join(str(pk) for pk in selected_ids)
# Перенаправляем на ваш кастомный view с картой
return redirect(reverse('admin_show_map') + f'?ids={ids_str}')
show_on_map.short_description = "Показать выбранные на карте"
def show_selected_on_map(modeladmin, request, queryset):
# Получаем список ID выбранных объектов
selected_ids = queryset.values_list('id', flat=True)
# Формируем строку вида "1,2,3"
ids_str = ','.join(str(pk) for pk in selected_ids)
# Перенаправляем на view, который будет отображать карту с выбранными объектами
return redirect(reverse('show_selected_objects_map') + f'?ids={ids_str}')
show_selected_on_map.short_description = "Показать выбранные объекты на карте"
show_selected_on_map.icon = 'map'
class ParameterObjItemInline(admin.StackedInline):
model = ObjItem.parameters_obj.through
extra = 0
max_num = 1
verbose_name = "ВЧ загрузка"
verbose_name_plural = "ВЧ загрузки"
@admin.register(ObjItem)
class ObjectAdmin(admin.ModelAdmin):
class ObjItemAdmin(BaseAdmin):
"""
Админ-панель для модели ObjItem.
Оптимизирована для работы с большим количеством объектов:
- Использует select_related и prefetch_related для оптимизации запросов
- Предоставляет фильтры по основным параметрам
- Поддерживает поиск по имени, координатам и частоте
- Включает кастомные actions для отображения на карте
"""
list_display = (
"name",
"sat_name",
@@ -436,6 +636,8 @@ class ObjectAdmin(admin.ModelAdmin):
"updated_at",
)
list_display_links = ("name",)
list_select_related = ("geo_obj", "created_by__user", "updated_by__user")
list_filter = (
UniqueToggleFilter,
("parameters_obj__id_satellite", MultiSelectRelatedDropdownFilter),
@@ -445,39 +647,53 @@ class ObjectAdmin(admin.ModelAdmin):
("parameters_obj__modulation", MultiSelectRelatedDropdownFilter),
("parameters_obj__polarization", MultiSelectRelatedDropdownFilter),
GeoKupDistanceFilter,
GeoValidDistanceFilter
)
search_fields = (
"name",
"geo_obj__coords",
"parameters_obj__frequency",
GeoValidDistanceFilter,
("created_at", DateRangeQuickSelectListFilterBuilder()),
("updated_at", DateRangeQuickSelectListFilterBuilder()),
)
ordering = ("name",)
search_fields = (
"name",
"geo_obj__location",
"parameters_obj__frequency",
"parameters_obj__id_satellite__name",
)
ordering = ("-updated_at",)
inlines = [ParameterObjItemInline, GeoInline]
actions = [show_on_map, show_selected_on_map]
readonly_fields = ('created_at', 'created_by', 'updated_at', 'updated_by')
actions = [show_selected_on_map, export_objects_to_csv]
readonly_fields = ("created_at", "created_by", "updated_at", "updated_by")
fieldsets = (
("Основная информация", {
"fields": ("name",)
}),
("Метаданные", {
"fields": ("created_at", "created_by", "updated_at", "updated_by"),
"classes": ("collapse",)
}),
)
def get_queryset(self, request):
"""
Оптимизированный queryset с использованием select_related и prefetch_related.
Загружает связанные объекты одним запросом для улучшения производительности.
"""
qs = super().get_queryset(request)
return qs.select_related('geo_obj', 'created_by', 'updated_by').prefetch_related(
'parameters_obj__id_satellite',
'parameters_obj__polarization',
'parameters_obj__modulation',
'parameters_obj__standard'
return qs.select_related(
"geo_obj",
"created_by__user",
"updated_by__user"
).prefetch_related(
"parameters_obj__id_satellite",
"parameters_obj__polarization",
"parameters_obj__modulation",
"parameters_obj__standard"
)
def get_readonly_fields(self, request, obj=None):
return self.readonly_fields
def save_model(self, request, obj, form, change):
if not change:
if not obj.created_by_id:
obj.created_by = request.user.customuser if hasattr(request.user, 'customuser') else None
obj.updated_by = request.user.customuser if hasattr(request.user, 'customuser') else None
super().save_model(request, obj, form, change)
def sat_name(self, obj):
"""Отображает название спутника из связанного параметра."""
param = next(iter(obj.parameters_obj.all()), None)
if param and param.id_satellite:
return param.id_satellite.name
@@ -486,7 +702,7 @@ class ObjectAdmin(admin.ModelAdmin):
sat_name.admin_order_field = "parameters_obj__id_satellite__name"
def freq(self, obj):
# param = obj.parameters_obj.first()
"""Отображает частоту из связанного параметра."""
param = next(iter(obj.parameters_obj.all()), None)
if param:
return param.frequency
@@ -495,6 +711,7 @@ class ObjectAdmin(admin.ModelAdmin):
freq.admin_order_field = "parameters_obj__frequency"
def distance_geo_kup(self, obj):
"""Отображает расстояние между геолокацией и Кубсатом."""
geo = obj.geo_obj
if not geo or geo.distance_coords_kup is None:
return "-"
@@ -502,6 +719,7 @@ class ObjectAdmin(admin.ModelAdmin):
distance_geo_kup.short_description = "Гео-куб, км"
def distance_geo_valid(self, obj):
"""Отображает расстояние между геолокацией и оперативным отделом."""
geo = obj.geo_obj
if not geo or geo.distance_coords_valid is None:
return "-"
@@ -509,6 +727,7 @@ class ObjectAdmin(admin.ModelAdmin):
distance_geo_valid.short_description = "Гео-опер, км"
def distance_kup_valid(self, obj):
"""Отображает расстояние между Кубсатом и оперативным отделом."""
geo = obj.geo_obj
if not geo or geo.distance_kup_valid is None:
return "-"
@@ -516,7 +735,7 @@ class ObjectAdmin(admin.ModelAdmin):
distance_kup_valid.short_description = "Куб-опер, км"
def pol(self, obj):
# Get the first parameter associated with this objitem to display polarization
"""Отображает поляризацию из связанного параметра."""
param = next(iter(obj.parameters_obj.all()), None)
if param and param.polarization:
return param.polarization.name
@@ -524,7 +743,7 @@ class ObjectAdmin(admin.ModelAdmin):
pol.short_description = "Поляризация"
def freq_range(self, obj):
# Get the first parameter associated with this objitem to display freq_range
"""Отображает полосу частот из связанного параметра."""
param = next(iter(obj.parameters_obj.all()), None)
if param:
return param.freq_range
@@ -533,7 +752,7 @@ class ObjectAdmin(admin.ModelAdmin):
freq_range.admin_order_field = "parameters_obj__freq_range"
def bod_velocity(self, obj):
# Get the first parameter associated with this objitem to display bod_velocity
"""Отображает символьную скорость из связанного параметра."""
param = next(iter(obj.parameters_obj.all()), None)
if param:
return param.bod_velocity
@@ -541,7 +760,7 @@ class ObjectAdmin(admin.ModelAdmin):
bod_velocity.short_description = "Сим. v, БОД"
def modulation(self, obj):
# Get the first parameter associated with this objitem to display modulation
"""Отображает модуляцию из связанного параметра."""
param = next(iter(obj.parameters_obj.all()), None)
if param and param.modulation:
return param.modulation.name
@@ -549,7 +768,7 @@ class ObjectAdmin(admin.ModelAdmin):
modulation.short_description = "Модуляция"
def snr(self, obj):
# Get the first parameter associated with this objitem to display snr
"""Отображает отношение сигнал/шум из связанного параметра."""
param = next(iter(obj.parameters_obj.all()), None)
if param:
return param.snr
@@ -557,6 +776,7 @@ class ObjectAdmin(admin.ModelAdmin):
snr.short_description = "ОСШ"
def geo_coords(self, obj):
"""Отображает координаты геолокации в формате широта/долгота."""
geo = obj.geo_obj
if not geo or not geo.coords:
return "-"
@@ -569,6 +789,7 @@ class ObjectAdmin(admin.ModelAdmin):
geo_coords.admin_order_field = "geo_obj__coords"
def kupsat_coords(self, obj):
"""Отображает координаты Кубсата в формате широта/долгота."""
geo = obj.geo_obj
if not geo or not geo.coords_kupsat:
return "-"
@@ -580,6 +801,7 @@ class ObjectAdmin(admin.ModelAdmin):
kupsat_coords.short_description = "Координаты Кубсата"
def valid_coords(self, obj):
"""Отображает координаты оперативного отдела в формате широта/долгота."""
geo = obj.geo_obj
if not geo or not geo.coords_valid:
return "-"