Рефакторинг и деплоинг
This commit is contained in:
@@ -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 "-"
|
||||
|
||||
Reference in New Issue
Block a user