Files
dbstorage/dbapp/mainapp/admin.py

1165 lines
40 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
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,
Standard,
ObjectMark,
ObjectInfo,
ObjectOwnership,
SigmaParameter,
Parameter,
Satellite,
Geo,
ObjItem,
CustomUser,
Band,
Source,
TechAnalyze,
)
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
verbose_name_plural = "Дополнительная информация пользователя"
class LocationForm(forms.ModelForm):
latitude_geo = forms.FloatField(required=False, label="Широта")
longitude_geo = forms.FloatField(required=False, label="Долгота")
# latitude_kupsat = forms.FloatField(required=False, label="Широта")
# longitude_kupsat = forms.FloatField(required=False, label="Долгота")
# latitude_valid = forms.FloatField(required=False, label="Широта")
# longitude_valid = forms.FloatField(required=False, label="Долгота")
class Meta:
model = Geo
fields = "__all__"
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance and self.instance.coords:
self.fields["latitude_geo"].initial = self.instance.coords[1]
self.fields["longitude_geo"].initial = self.instance.coords[0]
# if self.instance and self.instance.coords_kupsat:
# self.fields['latitude_kupsat'].initial = self.instance.coords_kupsat[1]
# self.fields['longitude_kupsat'].initial = self.instance.coords_kupsat[0]
# if self.instance and self.instance.coords_valid:
# self.fields['latitude_valid'].initial = self.instance.coords_valid[1]
# self.fields['longitude_valid'].initial = self.instance.coords_valid[0]
def save(self, commit=True):
instance = super().save(commit=False)
from django.contrib.gis.geos import Point
lat = self.cleaned_data.get("latitude_geo")
lon = self.cleaned_data.get("longitude_geo")
if lat is not None and lon is not None:
instance.coords = Point(lon, lat, srid=4326)
# lat = self.cleaned_data.get('latitude_kupsat')
# lon = self.cleaned_data.get('longitude_kupsat')
# if lat is not None and lon is not None:
# instance.coords_kupsat = Point(lon, lat, srid=4326)
# lat = self.cleaned_data.get('latitude_valid')
# lon = self.cleaned_data.get('longitude_valid')
# if lat is not None and lon is not None:
# instance.coords_valid = Point(lon, lat, srid=4326)
if commit:
instance.save()
return instance
class GeoInline(admin.StackedInline):
model = Geo
extra = 0
verbose_name = "Гео"
verbose_name_plural = "Гео"
form = LocationForm
# readonly_fields = ("distance_coords_kup", "distance_coords_valid", "distance_kup_valid")
prefetch_related = ("mirrors",)
autocomplete_fields = ("mirrors",)
fieldsets = (
(
"Основная информация",
{
"fields": (
"mirrors",
"location",
# "distance_coords_kup",
# "distance_coords_valid",
# "distance_kup_valid",
"timestamp",
"comment",
)
},
),
(
"Координаты: геолокация",
{
"fields": ("longitude_geo", "latitude_geo", "coords"),
},
),
# ("Координаты: Кубсат", {
# "fields": ("longitude_kupsat", "latitude_kupsat", "coords_kupsat"),
# }),
# ("Координаты: Оперативный отдел", {
# "fields": ("longitude_valid", "latitude_valid", "coords_valid"),
# }),
)
class UserAdmin(BaseUserAdmin):
inlines = [CustomUserInline]
admin.site.register(User, UserAdmin)
# ============================================================================
# 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",
"parameter_obj",
"parameter_obj__id_satellite",
"parameter_obj__polarization",
"parameter_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 = getattr(obj, "parameter_obj", 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 ParameterInline(admin.StackedInline):
"""Inline для редактирования параметра объекта."""
model = Parameter
extra = 0
max_num = 1
can_delete = True
verbose_name = "ВЧ загрузка"
verbose_name_plural = "ВЧ загрузка"
fields = (
"id_satellite",
"frequency",
"freq_range",
"polarization",
"modulation",
"bod_velocity",
"snr",
"standard",
)
autocomplete_fields = ("id_satellite", "polarization", "modulation", "standard")
# ============================================================================
# Admin Classes
# ============================================================================
@admin.register(ObjectMark)
class ObjectMarkAdmin(BaseAdmin):
"""Админ-панель для модели ObjectMark."""
list_display = ("source", "mark", "timestamp", "created_by")
list_select_related = ("source", "created_by__user")
search_fields = ("source__id",)
ordering = ("-timestamp",)
list_filter = (
"mark",
("timestamp", DateRangeQuickSelectListFilterBuilder()),
("source", MultiSelectRelatedDropdownFilter),
)
readonly_fields = ("timestamp", "created_by")
autocomplete_fields = ("source",)
# @admin.register(SigmaParMark)
# class SigmaParMarkAdmin(BaseAdmin):
# """Админ-панель для модели SigmaParMark."""
# list_display = ("mark", "timestamp")
# search_fields = ("mark",)
# ordering = ("-timestamp",)
# list_filter = (("timestamp", DateRangeQuickSelectListFilterBuilder()),)
@admin.register(Polarization)
class PolarizationAdmin(BaseAdmin):
"""Админ-панель для модели Polarization."""
list_display = ("name",)
search_fields = ("name",)
ordering = ("name",)
@admin.register(Modulation)
class ModulationAdmin(BaseAdmin):
"""Админ-панель для модели Modulation."""
list_display = ("name",)
search_fields = ("name",)
ordering = ("name",)
@admin.register(Standard)
class StandardAdmin(BaseAdmin):
"""Админ-панель для модели Standard."""
list_display = ("name",)
search_fields = ("name",)
ordering = ("name",)
@admin.register(ObjectInfo)
class ObjectInfoAdmin(BaseAdmin):
"""Админ-панель для модели ObjectInfo (Тип объекта)."""
list_display = ("name",)
search_fields = ("name",)
ordering = ("name",)
@admin.register(ObjectOwnership)
class ObjectOwnershipAdmin(BaseAdmin):
"""Админ-панель для модели ObjectOwnership (Принадлежность объекта)."""
list_display = ("name",)
search_fields = ("name",)
ordering = ("name",)
class SigmaParameterInline(admin.StackedInline):
model = SigmaParameter
extra = 0
readonly_fields = (
"datetime_begin",
"datetime_end",
)
def has_add_permission(self, request, obj=None):
return False
@admin.register(Parameter)
class ParameterAdmin(ImportExportActionModelAdmin, BaseAdmin):
"""
Админ-панель для модели Parameter.
Оптимизирована для работы с большим количеством параметров:
- Использует select_related для оптимизации запросов
- Предоставляет фильтры по основным характеристикам
- Поддерживает импорт/экспорт данных
"""
list_display = (
"id_satellite",
"frequency",
"freq_range",
"polarization",
"modulation",
"bod_velocity",
"snr",
"standard",
"related_objitem",
"sigma_parameter",
)
list_display_links = ("frequency", "id_satellite")
list_select_related = (
"polarization",
"modulation",
"standard",
"id_satellite",
"objitem",
)
list_filter = (
HasSigmaParameterFilter,
("objitem", MultiSelectRelatedDropdownFilter),
("id_satellite", MultiSelectRelatedDropdownFilter),
("polarization__name", MultiSelectDropdownFilter),
("modulation", MultiSelectRelatedDropdownFilter),
("standard", MultiSelectRelatedDropdownFilter),
("frequency", NumericRangeFilterBuilder()),
("freq_range", NumericRangeFilterBuilder()),
("snr", NumericRangeFilterBuilder()),
)
search_fields = (
"id_satellite__name",
"frequency",
"freq_range",
"bod_velocity",
"snr",
"modulation__name",
"polarization__name",
"standard__name",
"objitem__name",
)
ordering = ("-frequency",)
autocomplete_fields = ("objitem",)
inlines = [SigmaParameterInline]
def related_objitem(self, obj):
"""Отображает связанный ObjItem."""
if hasattr(obj, "objitem") and obj.objitem:
return obj.objitem.name
return "-"
related_objitem.short_description = "Объект"
related_objitem.admin_order_field = "objitem__name"
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 "-"
sigma_parameter.short_description = "ВЧ sigma"
@admin.register(SigmaParameter)
class SigmaParameterAdmin(ImportExportActionModelAdmin, BaseAdmin):
"""
Админ-панель для модели SigmaParameter.
Оптимизирована для работы с параметрами Sigma:
- Использует select_related и prefetch_related для оптимизации
- Предоставляет фильтры по основным характеристикам
- Поддерживает импорт/экспорт данных
"""
list_display = (
"id_satellite",
"frequency",
"transfer_frequency",
"freq_range",
"polarization",
"modulation",
"bod_velocity",
"snr",
"parameter",
"datetime_begin",
"datetime_end",
)
list_display_links = ("id_satellite",)
list_select_related = (
"modulation",
"standard",
"id_satellite",
"parameter",
"polarization",
)
readonly_fields = ("datetime_begin", "datetime_end", "transfer_frequency")
list_filter = (
("id_satellite__name", MultiSelectDropdownFilter),
("modulation__name", MultiSelectDropdownFilter),
("standard__name", MultiSelectDropdownFilter),
("frequency", NumericRangeFilterBuilder()),
("freq_range", NumericRangeFilterBuilder()),
("snr", NumericRangeFilterBuilder()),
("datetime_begin", DateRangeQuickSelectListFilterBuilder()),
("datetime_end", DateRangeQuickSelectListFilterBuilder()),
)
search_fields = (
"id_satellite__name",
"frequency",
"freq_range",
"bod_velocity",
"snr",
"modulation__name",
"standard__name",
)
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(BaseAdmin):
"""Админ-панель для модели Satellite."""
list_display = (
"name",
"alternative_name",
"norad",
"international_code",
"undersat_point",
"launch_date",
"created_at",
"updated_at",
)
search_fields = ("name", "alternative_name", "norad", "international_code")
ordering = ("name",)
filter_horizontal = ("band",)
autocomplete_fields = ("band",)
readonly_fields = ("created_at", "created_by", "updated_at", "updated_by")
# @admin.register(Mirror)
# class MirrorAdmin(ImportExportActionModelAdmin, BaseAdmin):
# """Админ-панель для модели Mirror с поддержкой импорта/экспорта."""
# list_display = ("name",)
# search_fields = ("name",)
# ordering = ("name",)
@admin.register(Geo)
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": ("longitude_geo", "latitude_geo", "coords")},
),
# ("Координаты: Кубсат", {
# "fields": ("longitude_kupsat", "latitude_kupsat", "coords_kupsat")
# }),
# ("Координаты: Оперативный отдел", {
# "fields": ("longitude_valid", "latitude_valid", "coords_valid")
# }),
)
list_display = (
"formatted_timestamp",
"location",
"mirrors_names",
"geo_coords",
# "kupsat_coords",
# "valid_coords",
"is_average",
)
list_display_links = ("formatted_timestamp",)
list_filter = (
("mirrors", MultiSelectRelatedDropdownFilter),
"is_average",
("location", MultiSelectDropdownFilter),
("timestamp", DateRangeQuickSelectListFilterBuilder()),
)
search_fields = (
"mirrors__name",
"location",
)
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"
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"
lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S"
return f"{lat} {lon}"
geo_coords.short_description = "Координаты геолокации"
# def kupsat_coords(self, obj):
# """Отображает координаты Кубсата в формате широта/долгота."""
# if obj.coords_kupsat is None:
# return "-"
# longitude = obj.coords_kupsat.coords[0]
# latitude = obj.coords_kupsat.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}"
# kupsat_coords.short_description = "Координаты Кубсата"
# def valid_coords(self, obj):
# """Отображает координаты оперативного отдела в формате широта/долгота."""
# if obj.coords_valid is None:
# return "-"
# longitude = obj.coords_valid.coords[0]
# latitude = obj.coords_valid.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}"
# valid_coords.short_description = "Координаты оперативного отдела"
@admin.register(ObjItem)
class ObjItemAdmin(BaseAdmin):
"""
Админ-панель для модели ObjItem.
Оптимизирована для работы с большим количеством объектов:
- Использует select_related и prefetch_related для оптимизации запросов
- Предоставляет фильтры по основным параметрам
- Поддерживает поиск по имени, координатам и частоте
- Включает кастомные actions для отображения на карте
"""
list_display = (
"name",
"sat_name",
"freq",
"freq_range",
"pol",
"bod_velocity",
"modulation",
"snr",
"geo_coords",
# "kupsat_coords",
# "valid_coords",
# "distance_geo_kup",
# "distance_geo_valid",
# "distance_kup_valid",
"created_at",
"updated_at",
)
list_display_links = ("name",)
list_select_related = (
"geo_obj",
"created_by__user",
"updated_by__user",
"parameter_obj",
"parameter_obj__id_satellite",
"parameter_obj__polarization",
"parameter_obj__modulation",
"parameter_obj__standard",
)
list_filter = (
UniqueToggleFilter,
("parameter_obj__id_satellite", MultiSelectRelatedDropdownFilter),
("parameter_obj__frequency", NumericRangeFilterBuilder()),
("parameter_obj__freq_range", NumericRangeFilterBuilder()),
("parameter_obj__snr", NumericRangeFilterBuilder()),
("parameter_obj__modulation", MultiSelectRelatedDropdownFilter),
("parameter_obj__polarization", MultiSelectRelatedDropdownFilter),
GeoKupDistanceFilter,
GeoValidDistanceFilter,
("created_at", DateRangeQuickSelectListFilterBuilder()),
("updated_at", DateRangeQuickSelectListFilterBuilder()),
)
search_fields = (
"name",
"geo_obj__location",
"parameter_obj__frequency",
"parameter_obj__id_satellite__name",
)
ordering = ("-updated_at",)
inlines = [GeoInline, ParameterInline]
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.
Загружает связанные объекты одним запросом для улучшения производительности.
"""
qs = super().get_queryset(request)
return qs.select_related(
"geo_obj",
"created_by__user",
"updated_by__user",
"parameter_obj",
"parameter_obj__id_satellite",
"parameter_obj__polarization",
"parameter_obj__modulation",
"parameter_obj__standard",
)
def sat_name(self, obj):
"""Отображает название спутника из связанного параметра."""
if hasattr(obj, "parameter_obj") and obj.parameter_obj:
if obj.parameter_obj.id_satellite:
return obj.parameter_obj.id_satellite.name
return "-"
sat_name.short_description = "Спутник"
sat_name.admin_order_field = "parameter_obj__id_satellite__name"
def freq(self, obj):
"""Отображает частоту из связанного параметра."""
if hasattr(obj, "parameter_obj") and obj.parameter_obj:
return obj.parameter_obj.frequency
return "-"
freq.short_description = "Частота, МГц"
freq.admin_order_field = "parameter_obj__frequency"
# def distance_geo_kup(self, obj):
# """Отображает расстояние между геолокацией и Кубсатом."""
# geo = obj.geo_obj
# if not geo or geo.distance_coords_kup is None:
# return "-"
# return round(geo.distance_coords_kup, 3)
# 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 "-"
# return round(geo.distance_coords_valid, 3)
# 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 "-"
# return round(geo.distance_kup_valid, 3)
# distance_kup_valid.short_description = "Куб-опер, км"
def pol(self, obj):
"""Отображает поляризацию из связанного параметра."""
if hasattr(obj, "parameter_obj") and obj.parameter_obj:
if obj.parameter_obj.polarization:
return obj.parameter_obj.polarization.name
return "-"
pol.short_description = "Поляризация"
def freq_range(self, obj):
"""Отображает полосу частот из связанного параметра."""
if hasattr(obj, "parameter_obj") and obj.parameter_obj:
return obj.parameter_obj.freq_range
return "-"
freq_range.short_description = "Полоса, МГц"
freq_range.admin_order_field = "parameter_obj__freq_range"
def bod_velocity(self, obj):
"""Отображает символьную скорость из связанного параметра."""
if hasattr(obj, "parameter_obj") and obj.parameter_obj:
return obj.parameter_obj.bod_velocity
return "-"
bod_velocity.short_description = "Сим. v, БОД"
def modulation(self, obj):
"""Отображает модуляцию из связанного параметра."""
if hasattr(obj, "parameter_obj") and obj.parameter_obj:
if obj.parameter_obj.modulation:
return obj.parameter_obj.modulation.name
return "-"
modulation.short_description = "Модуляция"
def snr(self, obj):
"""Отображает отношение сигнал/шум из связанного параметра."""
if hasattr(obj, "parameter_obj") and obj.parameter_obj:
return obj.parameter_obj.snr
return "-"
snr.short_description = "ОСШ"
def geo_coords(self, obj):
"""Отображает координаты геолокации в формате широта/долгота."""
geo = obj.geo_obj
if not geo or not geo.coords:
return "-"
longitude = geo.coords.coords[0]
latitude = geo.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}"
geo_coords.short_description = "Координаты геолокации"
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 "-"
longitude = geo.coords_kupsat.coords[0]
latitude = geo.coords_kupsat.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}"
kupsat_coords.short_description = "Координаты Кубсата"
def valid_coords(self, obj):
"""Отображает координаты оперативного отдела в формате широта/долгота."""
geo = obj.geo_obj
if not geo or not geo.coords_valid:
return "-"
longitude = geo.coords_valid.coords[0]
latitude = geo.coords_valid.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}"
valid_coords.short_description = "Координаты оперативного отдела"
@admin.register(Band)
class BandAdmin(ImportExportActionModelAdmin, BaseAdmin):
"""Админ-панель для модели Band."""
list_display = ("name", "border_start", "border_end")
search_fields = ("name",)
ordering = ("name",)
class ObjItemInline(admin.TabularInline):
"""Inline для отображения объектов ObjItem в Source."""
model = ObjItem
fk_name = "source"
extra = 0
can_delete = False
verbose_name = "Объект"
verbose_name_plural = "Объекты"
fields = (
"name",
"get_geo_coords",
"get_satellite",
"get_frequency",
"get_polarization",
"updated_at",
)
readonly_fields = (
"name",
"get_geo_coords",
"get_satellite",
"get_frequency",
"get_polarization",
"updated_at",
)
def get_queryset(self, request):
"""Оптимизированный queryset с предзагрузкой связанных объектов."""
qs = super().get_queryset(request)
return qs.select_related(
"geo_obj",
"parameter_obj",
"parameter_obj__id_satellite",
"parameter_obj__polarization",
)
def get_geo_coords(self, obj):
"""Отображает координаты из связанной модели Geo."""
if not obj or not hasattr(obj, "geo_obj"):
return "-"
geo = obj.geo_obj
if not geo or not geo.coords:
return "-"
longitude = geo.coords.coords[0]
latitude = geo.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}"
get_geo_coords.short_description = "Координаты"
def get_satellite(self, obj):
"""Отображает спутник из связанного параметра."""
if (
hasattr(obj, "parameter_obj")
and obj.parameter_obj
and obj.parameter_obj.id_satellite
):
return obj.parameter_obj.id_satellite.name
return "-"
get_satellite.short_description = "Спутник"
def get_frequency(self, obj):
"""Отображает частоту из связанного параметра."""
if hasattr(obj, "parameter_obj") and obj.parameter_obj:
return obj.parameter_obj.frequency
return "-"
get_frequency.short_description = "Частота, МГц"
def get_polarization(self, obj):
"""Отображает поляризацию из связанного параметра."""
if (
hasattr(obj, "parameter_obj")
and obj.parameter_obj
and obj.parameter_obj.polarization
):
return obj.parameter_obj.polarization.name
return "-"
get_polarization.short_description = "Поляризация"
def has_add_permission(self, request, obj=None):
return False
@admin.register(Source)
class SourceAdmin(ImportExportActionModelAdmin, LeafletGeoAdmin, BaseAdmin):
"""Админ-панель для модели Source."""
list_display = ("id", "info", "created_at", "updated_at")
list_select_related = ("info",)
list_filter = (
("info", MultiSelectRelatedDropdownFilter),
("created_at", DateRangeQuickSelectListFilterBuilder()),
("updated_at", DateRangeQuickSelectListFilterBuilder()),
)
search_fields = ("id", "info__name")
ordering = ("-created_at",)
readonly_fields = ("created_at", "created_by", "updated_at", "updated_by")
inlines = [ObjItemInline]
fieldsets = (
(
"Основная информация",
{"fields": ("info",)},
),
(
"Координаты",
{"fields": ("coords_average", "coords_kupsat", "coords_valid", "coords_reference")},
),
(
"Метаданные",
{
"fields": ("created_at", "created_by", "updated_at", "updated_by"),
"classes": ("collapse",),
},
),
)
autocomplete_fields = ("info",)
@admin.register(TechAnalyze)
class TechAnalyzeAdmin(ImportExportActionModelAdmin, BaseAdmin):
"""Админ-панель для модели TechAnalyze."""
list_display = (
"name",
"satellite",
"frequency",
"freq_range",
"polarization",
"bod_velocity",
"modulation",
"standard",
"created_at",
"updated_at",
)
list_display_links = ("name",)
list_select_related = (
"satellite",
"polarization",
"modulation",
"standard",
"created_by__user",
"updated_by__user",
)
list_filter = (
("satellite", MultiSelectRelatedDropdownFilter),
("polarization", MultiSelectRelatedDropdownFilter),
("modulation", MultiSelectRelatedDropdownFilter),
("standard", MultiSelectRelatedDropdownFilter),
("frequency", NumericRangeFilterBuilder()),
("freq_range", NumericRangeFilterBuilder()),
("created_at", DateRangeQuickSelectListFilterBuilder()),
("updated_at", DateRangeQuickSelectListFilterBuilder()),
)
search_fields = (
"name",
"satellite__name",
"frequency",
"note",
)
ordering = ("-created_at",)
readonly_fields = ("created_at", "created_by", "updated_at", "updated_by")
autocomplete_fields = ("satellite", "polarization", "modulation", "standard")
fieldsets = (
(
"Основная информация",
{"fields": ("name", "satellite", "note")},
),
(
"Технические параметры",
{
"fields": (
"frequency",
"freq_range",
"polarization",
"bod_velocity",
"modulation",
"standard",
)
},
),
(
"Метаданные",
{
"fields": ("created_at", "created_by", "updated_at", "updated_by"),
"classes": ("collapse",),
},
),
)