Добавил транспондеры к ObjItem шаблону

This commit is contained in:
2025-11-14 08:00:23 +03:00
parent 5ab6770809
commit 6a26991dc0
18 changed files with 3286 additions and 1188 deletions

View File

@@ -32,13 +32,13 @@ from .models import (
ObjItem,
CustomUser,
Band,
Source
Source,
)
from .filters import (
GeoKupDistanceFilter,
GeoValidDistanceFilter,
UniqueToggleFilter,
HasSigmaParameterFilter
HasSigmaParameterFilter,
)
@@ -55,22 +55,24 @@ 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: Сохраняемый объект модели
@@ -79,20 +81,20 @@ class BaseAdmin(admin.ModelAdmin):
"""
if not change:
# При создании нового объекта устанавливаем created_by
if hasattr(obj, 'created_by') and not obj.created_by_id:
obj.created_by = getattr(request.user, 'customuser', None)
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)
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 = 'Дополнительная информация пользователя'
verbose_name_plural = "Дополнительная информация пользователя"
class LocationForm(forms.ModelForm):
@@ -105,13 +107,13 @@ class LocationForm(forms.ModelForm):
class Meta:
model = Geo
fields = '__all__'
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]
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]
@@ -122,8 +124,9 @@ class LocationForm(forms.ModelForm):
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')
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)
@@ -150,18 +153,28 @@ class GeoInline(admin.StackedInline):
form = LocationForm
# readonly_fields = ("distance_coords_kup", "distance_coords_valid", "distance_kup_valid")
prefetch_related = ("mirrors",)
autocomplete_fields = ('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": (
"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"),
# }),
@@ -174,6 +187,7 @@ class GeoInline(admin.StackedInline):
class UserAdmin(BaseUserAdmin):
inlines = [CustomUserInline]
admin.site.register(User, UserAdmin)
@@ -181,80 +195,83 @@ 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}')
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}')
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'
"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
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([
'Название',
'Спутник',
'Частота (МГц)',
'Полоса (МГц)',
'Поляризация',
'Модуляция',
'ОСШ',
'Координаты геолокации',
'Координаты Кубсата',
'Координаты оперативного отдела',
'Расстояние Гео-Куб (км)',
'Расстояние Гео-Опер (км)',
'Дата создания',
'Дата обновления'
])
writer.writerow(
[
"Название",
"Спутник",
"Частота (МГц)",
"Полоса (МГц)",
"Поляризация",
"Модуляция",
"ОСШ",
"Координаты геолокации",
"Координаты Кубсата",
"Координаты оперативного отдела",
"Расстояние Гео-Куб (км)",
"Расстояние Гео-Опер (км)",
"Дата создания",
"Дата обновления",
]
)
for obj in queryset:
param = getattr(obj, 'parameter_obj', None)
param = getattr(obj, "parameter_obj", None)
geo = obj.geo_obj
# Форматирование координат
def format_coords(coords):
if not coords:
@@ -263,24 +280,30 @@ def export_objects_to_csv(modeladmin, request, queryset):
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 "-"
])
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
@@ -288,8 +311,10 @@ def export_objects_to_csv(modeladmin, request, queryset):
# Inline Admin Classes
# ============================================================================
class ParameterInline(admin.StackedInline):
"""Inline для редактирования параметра объекта."""
model = Parameter
extra = 0
max_num = 1
@@ -297,36 +322,37 @@ class ParameterInline(admin.StackedInline):
verbose_name = "ВЧ загрузка"
verbose_name_plural = "ВЧ загрузка"
fields = (
'id_satellite',
'frequency',
'freq_range',
'polarization',
'modulation',
'bod_velocity',
'snr',
'standard'
"id_satellite",
"frequency",
"freq_range",
"polarization",
"modulation",
"bod_velocity",
"snr",
"standard",
)
autocomplete_fields = ('id_satellite', 'polarization', 'modulation', 'standard')
autocomplete_fields = ("id_satellite", "polarization", "modulation", "standard")
# ============================================================================
# Admin Classes
# ============================================================================
@admin.register(SigmaParMark)
class SigmaParMarkAdmin(BaseAdmin):
"""Админ-панель для модели SigmaParMark."""
list_display = ("mark", "timestamp")
search_fields = ("mark",)
ordering = ("-timestamp",)
list_filter = (
("timestamp", DateRangeQuickSelectListFilterBuilder()),
)
list_filter = (("timestamp", DateRangeQuickSelectListFilterBuilder()),)
@admin.register(Polarization)
class PolarizationAdmin(BaseAdmin):
"""Админ-панель для модели Polarization."""
list_display = ("name",)
search_fields = ("name",)
ordering = ("name",)
@@ -335,6 +361,7 @@ class PolarizationAdmin(BaseAdmin):
@admin.register(Modulation)
class ModulationAdmin(BaseAdmin):
"""Админ-панель для модели Modulation."""
list_display = ("name",)
search_fields = ("name",)
ordering = ("name",)
@@ -343,6 +370,7 @@ class ModulationAdmin(BaseAdmin):
@admin.register(Standard)
class StandardAdmin(BaseAdmin):
"""Админ-панель для модели Standard."""
list_display = ("name",)
search_fields = ("name",)
ordering = ("name",)
@@ -351,11 +379,12 @@ class StandardAdmin(BaseAdmin):
class SigmaParameterInline(admin.StackedInline):
model = SigmaParameter
extra = 0
autocomplete_fields = ['mark']
autocomplete_fields = ["mark"]
readonly_fields = (
"datetime_begin",
"datetime_end",
"datetime_begin",
"datetime_end",
)
def has_add_permission(self, request, obj=None):
return False
@@ -364,12 +393,13 @@ class SigmaParameterInline(admin.StackedInline):
class ParameterAdmin(ImportExportActionModelAdmin, BaseAdmin):
"""
Админ-панель для модели Parameter.
Оптимизирована для работы с большим количеством параметров:
- Использует select_related для оптимизации запросов
- Предоставляет фильтры по основным характеристикам
- Поддерживает импорт/экспорт данных
"""
list_display = (
"id_satellite",
"frequency",
@@ -380,11 +410,17 @@ class ParameterAdmin(ImportExportActionModelAdmin, BaseAdmin):
"snr",
"standard",
"related_objitem",
"sigma_parameter"
"sigma_parameter",
)
list_display_links = ("frequency", "id_satellite")
list_select_related = ("polarization", "modulation", "standard", "id_satellite", "objitem")
list_select_related = (
"polarization",
"modulation",
"standard",
"id_satellite",
"objitem",
)
list_filter = (
HasSigmaParameterFilter,
("objitem", MultiSelectRelatedDropdownFilter),
@@ -396,7 +432,7 @@ class ParameterAdmin(ImportExportActionModelAdmin, BaseAdmin):
("freq_range", NumericRangeFilterBuilder()),
("snr", NumericRangeFilterBuilder()),
)
search_fields = (
"id_satellite__name",
"frequency",
@@ -408,16 +444,17 @@ class ParameterAdmin(ImportExportActionModelAdmin, BaseAdmin):
"standard__name",
"objitem__name",
)
ordering = ("-frequency",)
autocomplete_fields = ("objitem",)
inlines = [SigmaParameterInline]
def related_objitem(self, obj):
"""Отображает связанный ObjItem."""
if hasattr(obj, 'objitem') and 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"
@@ -427,19 +464,21 @@ class ParameterAdmin(ImportExportActionModelAdmin, BaseAdmin):
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",
@@ -454,14 +493,16 @@ class SigmaParameterAdmin(ImportExportActionModelAdmin, BaseAdmin):
"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_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),
@@ -472,7 +513,7 @@ class SigmaParameterAdmin(ImportExportActionModelAdmin, BaseAdmin):
("datetime_begin", DateRangeQuickSelectListFilterBuilder()),
("datetime_end", DateRangeQuickSelectListFilterBuilder()),
)
search_fields = (
"id_satellite__name",
"frequency",
@@ -484,17 +525,25 @@ class SigmaParameterAdmin(ImportExportActionModelAdmin, BaseAdmin):
)
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(BaseAdmin):
"""Админ-панель для модели Satellite."""
list_display = ("name", "norad", "undersat_point", "launch_date", "created_at", "updated_at")
list_display = (
"name",
"norad",
"undersat_point",
"launch_date",
"created_at",
"updated_at",
)
search_fields = ("name", "norad")
ordering = ("name",)
filter_horizontal = ("band",)
@@ -505,6 +554,7 @@ class SatelliteAdmin(BaseAdmin):
@admin.register(Mirror)
class MirrorAdmin(ImportExportActionModelAdmin, BaseAdmin):
"""Админ-панель для модели Mirror с поддержкой импорта/экспорта."""
list_display = ("name",)
search_fields = ("name",)
ordering = ("name",)
@@ -514,28 +564,37 @@ class MirrorAdmin(ImportExportActionModelAdmin, BaseAdmin):
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",
(
"Основная информация",
{
"fields": (
"mirrors",
"location",
# "distance_coords_kup",
# "distance_coords_valid",
# "distance_kup_valid",
"timestamp", "comment", "transponder")
}),
("Координаты: геолокация", {
"fields": ("longitude_geo", "latitude_geo", "coords")
}),
# "distance_coords_valid",
# "distance_kup_valid",
"timestamp",
"comment",
)
},
),
(
"Координаты: геолокация",
{"fields": ("longitude_geo", "latitude_geo", "coords")},
),
# ("Координаты: Кубсат", {
# "fields": ("longitude_kupsat", "latitude_kupsat", "coords_kupsat")
# }),
@@ -543,7 +602,7 @@ class GeoAdmin(ImportExportActionModelAdmin, LeafletGeoAdmin, BaseAdmin):
# "fields": ("longitude_valid", "latitude_valid", "coords_valid")
# }),
)
list_display = (
"formatted_timestamp",
"location",
@@ -554,38 +613,39 @@ class GeoAdmin(ImportExportActionModelAdmin, LeafletGeoAdmin, BaseAdmin):
"is_average",
)
list_display_links = ("formatted_timestamp",)
list_filter = (
("mirrors", MultiSelectRelatedDropdownFilter),
("transponder", MultiSelectRelatedDropdownFilter),
"is_average",
("location", MultiSelectDropdownFilter),
("timestamp", DateRangeQuickSelectListFilterBuilder()),
)
search_fields = (
"mirrors__name",
"location",
"transponder__name",
)
autocomplete_fields = ("mirrors", )
autocomplete_fields = ("mirrors",)
ordering = ("-timestamp",)
actions = [show_on_map]
settings_overrides = {
'DEFAULT_CENTER': (55.7558, 37.6173),
'DEFAULT_ZOOM': 12,
"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", "transponder")
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):
@@ -594,6 +654,7 @@ class GeoAdmin(ImportExportActionModelAdmin, LeafletGeoAdmin, BaseAdmin):
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"
@@ -606,6 +667,7 @@ class GeoAdmin(ImportExportActionModelAdmin, LeafletGeoAdmin, BaseAdmin):
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):
@@ -631,19 +693,18 @@ class GeoAdmin(ImportExportActionModelAdmin, LeafletGeoAdmin, BaseAdmin):
# valid_coords.short_description = "Координаты оперативного отдела"
@admin.register(ObjItem)
class ObjItemAdmin(BaseAdmin):
"""
Админ-панель для модели ObjItem.
Оптимизирована для работы с большим количеством объектов:
- Использует select_related и prefetch_related для оптимизации запросов
- Предоставляет фильтры по основным параметрам
- Поддерживает поиск по имени, координатам и частоте
- Включает кастомные actions для отображения на карте
"""
list_display = (
"name",
"sat_name",
@@ -664,16 +725,16 @@ class ObjItemAdmin(BaseAdmin):
)
list_display_links = ("name",)
list_select_related = (
"geo_obj",
"created_by__user",
"geo_obj",
"created_by__user",
"updated_by__user",
"parameter_obj",
"parameter_obj__id_satellite",
"parameter_obj__polarization",
"parameter_obj__modulation",
"parameter_obj__standard"
"parameter_obj__standard",
)
list_filter = (
UniqueToggleFilter,
("parameter_obj__id_satellite", MultiSelectRelatedDropdownFilter),
@@ -687,33 +748,34 @@ class ObjItemAdmin(BaseAdmin):
("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",)
}),
("Основная информация", {"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)
@@ -725,23 +787,25 @@ class ObjItemAdmin(BaseAdmin):
"parameter_obj__id_satellite",
"parameter_obj__polarization",
"parameter_obj__modulation",
"parameter_obj__standard"
"parameter_obj__standard",
)
def sat_name(self, obj):
"""Отображает название спутника из связанного параметра."""
if hasattr(obj, 'parameter_obj') and obj.parameter_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:
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"
@@ -771,40 +835,45 @@ class ObjItemAdmin(BaseAdmin):
def pol(self, obj):
"""Отображает поляризацию из связанного параметра."""
if hasattr(obj, 'parameter_obj') and obj.parameter_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:
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:
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 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:
if hasattr(obj, "parameter_obj") and obj.parameter_obj:
return obj.parameter_obj.snr
return "-"
snr.short_description = "ОСШ"
def geo_coords(self, obj):
@@ -817,6 +886,7 @@ class ObjItemAdmin(BaseAdmin):
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"
@@ -830,6 +900,7 @@ class ObjItemAdmin(BaseAdmin):
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):
@@ -842,12 +913,14 @@ class ObjItemAdmin(BaseAdmin):
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",)
@@ -855,29 +928,44 @@ class BandAdmin(ImportExportActionModelAdmin, BaseAdmin):
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")
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'
"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'):
if not obj or not hasattr(obj, "geo_obj"):
return "-"
geo = obj.geo_obj
if not geo or not geo.coords:
@@ -887,29 +975,41 @@ class ObjItemInline(admin.TabularInline):
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:
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:
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:
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
@@ -917,6 +1017,7 @@ class ObjItemInline(admin.TabularInline):
@admin.register(Source)
class SourceAdmin(ImportExportActionModelAdmin, LeafletGeoAdmin, BaseAdmin):
"""Админ-панель для модели Source."""
list_display = ("id", "created_at", "updated_at")
list_filter = (
("created_at", DateRangeQuickSelectListFilterBuilder()),
@@ -925,13 +1026,17 @@ class SourceAdmin(ImportExportActionModelAdmin, LeafletGeoAdmin, BaseAdmin):
ordering = ("-created_at",)
readonly_fields = ("created_at", "created_by", "updated_at", "updated_by")
inlines = [ObjItemInline]
fieldsets = (
("Координаты: геолокация", {
"fields": ("coords_kupsat", "coords_valid", "coords_reference")
}),
("Метаданные", {
"fields": ("created_at", "created_by", "updated_at", "updated_by"),
"classes": ("collapse",)
}),
(
"Координаты: геолокация",
{"fields": ("coords_kupsat", "coords_valid", "coords_reference")},
),
(
"Метаданные",
{
"fields": ("created_at", "created_by", "updated_at", "updated_by"),
"classes": ("collapse",),
},
),
)