новый структура моделей

This commit is contained in:
2025-10-30 12:27:25 +03:00
parent 94df5171db
commit 20a13414de
6 changed files with 245 additions and 87 deletions

View File

@@ -164,21 +164,6 @@ STATICFILES_DIRS = [
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# DAISY_SETTINGS = {
# 'SITE_TITLE': 'Geo admin', # The title of the site
# 'SITE_HEADER': 'GEO', # Header text displayed in the admin panel
# 'INDEX_TITLE': 'Заголовок', # The title for the index page of dashboard
# 'SITE_LOGO': '/static/admin/img/icon-clock.svg', # Path to the logo image displayed in the sidebar
# 'EXTRA_STYLES': [], # List of extra stylesheets to be loaded in base.html (optional)
# 'EXTRA_SCRIPTS': [], # List of extra script URLs to be loaded in base.html (optional)
# 'LOAD_FULL_STYLES': False, # If True, loads full DaisyUI components in the admin (useful if you have custom template overrides)
# 'SHOW_CHANGELIST_FILTER': False, # If True, the filter sidebar will open by default on changelist views
# 'DONT_SUPPORT_ME': True, # Hide github link in sidebar footer
# 'SIDEBAR_FOOTNOTE': 'Что-то о как', # add footnote to sidebar
# 'DEFAULT_THEME': None, # Set a default theme (e.g., 'corporate', 'dark', 'light')
# 'DEFAULT_THEME_DARK': None, # Set a default dark theme when system prefers dark mode
# 'SHOW_THEME_SELECTOR': True, # If False, hides the theme selector dropdown entirely
# }
# AUTH_USER_MODEL = 'mainapp.CustomUser' # AUTH_USER_MODEL = 'mainapp.CustomUser'
X_FRAME_OPTIONS = "SAMEORIGIN" X_FRAME_OPTIONS = "SAMEORIGIN"

View File

@@ -23,6 +23,7 @@ from django.contrib.gis.db import models as gis
from django.shortcuts import redirect from django.shortcuts import redirect
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from leaflet.forms.widgets import LeafletWidget
from rangefilter.filters import ( from rangefilter.filters import (
DateRangeFilterBuilder, DateRangeFilterBuilder,
@@ -90,6 +91,28 @@ class LocationForm(forms.ModelForm):
return instance 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")
fieldsets = (
("Основная информация", {
"fields": ("mirrors", "location", "distance_coords_kup",
"distance_coords_valid", "distance_kup_valid", "timestamp", "comment", "id_user_add")
}),
("Координаты: геолокация", {
"fields": ("longitude_geo", "latitude_geo", "coords"),
}),
("Координаты: Кубсат", {
"fields": ("longitude_kupsat", "latitude_kupsat", "coords_kupsat"),
}),
("Координаты: Оперативный отдел", {
"fields": ("longitude_valid", "latitude_valid", "coords_valid"),
}),
)
class CustomUserInline(admin.StackedInline): class CustomUserInline(admin.StackedInline):
@@ -130,7 +153,7 @@ class ModulationAdmin(admin.ModelAdmin):
ordering = ("name",) ordering = ("name",)
@admin.register(SourceType) @admin.register(SourceType)
class ModulationAdmin(admin.ModelAdmin): class SourceTypeAdmin(admin.ModelAdmin):
list_display = ("name",) list_display = ("name",)
search_fields = ("name",) search_fields = ("name",)
ordering = ("name",) ordering = ("name",)
@@ -200,6 +223,7 @@ class ParameterAdmin(ImportExportActionModelAdmin, admin.ModelAdmin):
) )
ordering = ("frequency",) ordering = ("frequency",)
list_select_related = ("polarization", "modulation", "standard", "id_satellite",) list_select_related = ("polarization", "modulation", "standard", "id_satellite",)
filter_horizontal = ('objitems',) # For many-to-many relationship
# raw_id_fields = ("id_sigma_parameter", ) # raw_id_fields = ("id_sigma_parameter", )
inlines = [SigmaParameterInline] inlines = [SigmaParameterInline]
# autocomplete_fields = ("id_sigma_parameter", ) # autocomplete_fields = ("id_sigma_parameter", )
@@ -378,8 +402,44 @@ def show_on_map(modeladmin, request, queryset):
show_on_map.short_description = "Показать выбранные на карте" show_on_map.short_description = "Показать выбранные на карте"
class ObjItemForm(forms.ModelForm):
parameter = forms.ModelChoiceField(
queryset=Parameter.objects.all(),
required=False,
label="Параметр"
)
class Meta:
model = ObjItem
fields = '__all__'
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Set initial value if the ObjItem already has a parameter
if self.instance.pk:
first_param = self.instance.parameters_obj.first()
if first_param:
self.fields['parameter'].initial = first_param
def save(self, commit=True):
instance = super().save(commit=commit)
# Handle the single parameter assignment - replace all existing relationships
if self.cleaned_data.get('parameter'):
# Clear all existing parameter relationships
instance.parameters_obj.clear()
# Add the selected parameter
instance.parameters_obj.add(self.cleaned_data['parameter'])
else:
# If no parameter selected, clear all
instance.parameters_obj.clear()
return instance
@admin.register(ObjItem) @admin.register(ObjItem)
class ObjectAdmin(admin.ModelAdmin): class ObjectAdmin(admin.ModelAdmin):
form = ObjItemForm
list_display = ( list_display = (
"name", "name",
"sat_name", "sat_name",
@@ -399,121 +459,142 @@ class ObjectAdmin(admin.ModelAdmin):
list_display_links = ("name",) list_display_links = ("name",)
list_filter = ( list_filter = (
UniqueToggleFilter, UniqueToggleFilter,
("id_vch_load__id_satellite", MultiSelectRelatedDropdownFilter), ("parameters_obj__id_satellite", MultiSelectRelatedDropdownFilter),
("id_vch_load__frequency", NumericRangeFilterBuilder()), ("parameters_obj__frequency", NumericRangeFilterBuilder()),
("id_vch_load__freq_range", NumericRangeFilterBuilder()), ("parameters_obj__freq_range", NumericRangeFilterBuilder()),
("id_vch_load__snr", NumericRangeFilterBuilder()), ("parameters_obj__snr", NumericRangeFilterBuilder()),
("id_vch_load__modulation", MultiSelectRelatedDropdownFilter), ("parameters_obj__modulation", MultiSelectRelatedDropdownFilter),
("id_vch_load__polarization", MultiSelectRelatedDropdownFilter), ("parameters_obj__polarization", MultiSelectRelatedDropdownFilter),
GeoKupDistanceFilter, GeoKupDistanceFilter,
GeoValidDistanceFilter GeoValidDistanceFilter
) )
search_fields = ( search_fields = (
"name", "name",
"id_geo__coords", "geo_obj__coords",
# "id_satellite__name", "parameters_obj__frequency",
# "id_vch_load__frequency",
) )
ordering = ("name",) ordering = ("name",)
list_select_related = ( inlines = [GeoInline]
# "id_satellite",
"id_vch_load",
"id_vch_load__polarization",
"id_vch_load__modulation",
"id_vch_load__id_satellite",
"id_geo",
"id_source_type"
)
autocomplete_fields = ("id_geo",)
raw_id_fields = ("id_vch_load",)
# dynamic_raw_id_fields = ("id_vch_load",)
actions = [show_on_map] actions = [show_on_map]
def get_queryset(self, request):
qs = super().get_queryset(request)
return qs.select_related('geo_obj').prefetch_related(
'parameters_obj__id_satellite',
'parameters_obj__polarization',
'parameters_obj__modulation',
'parameters_obj__standard'
)
def sat_name(self, obj): def sat_name(self, obj):
return obj.id_vch_load.id_satellite param = next(iter(obj.parameters_obj.all()), None)
if param and param.id_satellite:
return param.id_satellite.name
return "-"
sat_name.short_description = "Спутник" sat_name.short_description = "Спутник"
sat_name.admin_order_field = "id_vch_load__id_satellite__name" sat_name.admin_order_field = "parameters_obj__id_satellite__name"
def freq(self, obj): def freq(self, obj):
par = obj.id_vch_load # param = obj.parameters_obj.first()
return par.frequency param = next(iter(obj.parameters_obj.all()), None)
if param:
return param.frequency
return "-"
freq.short_description = "Частота, МГц" freq.short_description = "Частота, МГц"
freq.admin_order_field = "id_vch_load__frequency" freq.admin_order_field = "parameters_obj__frequency"
def distance_geo_kup(self, obj): def distance_geo_kup(self, obj):
par = obj.id_geo.distance_coords_kup geo = obj.geo_obj
if par is None: if not geo or geo.distance_coords_kup is None:
return "-" return "-"
return round(par, 3) return round(geo.distance_coords_kup, 3)
distance_geo_kup.short_description = "Гео-куб, км" distance_geo_kup.short_description = "Гео-куб, км"
def distance_geo_valid(self, obj): def distance_geo_valid(self, obj):
par = obj.id_geo.distance_coords_valid geo = obj.geo_obj
if par is None: if not geo or geo.distance_coords_valid is None:
return "-" return "-"
return round(par, 3) return round(geo.distance_coords_valid, 3)
distance_geo_valid.short_description = "Гео-опер, км" distance_geo_valid.short_description = "Гео-опер, км"
def distance_kup_valid(self, obj): def distance_kup_valid(self, obj):
par = obj.id_geo.distance_kup_valid geo = obj.geo_obj
if par is None: if not geo or geo.distance_kup_valid is None:
return "-" return "-"
return round(par, 3) return round(geo.distance_kup_valid, 3)
distance_kup_valid.short_description = "Куб-опер, км" distance_kup_valid.short_description = "Куб-опер, км"
def pol(self, obj): def pol(self, obj):
par = obj.id_vch_load.polarization # Get the first parameter associated with this objitem to display polarization
return par.name param = next(iter(obj.parameters_obj.all()), None)
if param and param.polarization:
return param.polarization.name
return "-"
pol.short_description = "Поляризация" pol.short_description = "Поляризация"
def freq_range(self, obj): def freq_range(self, obj):
par = obj.id_vch_load # Get the first parameter associated with this objitem to display freq_range
return par.freq_range param = next(iter(obj.parameters_obj.all()), None)
if param:
return param.freq_range
return "-"
freq_range.short_description = "Полоса, МГц" freq_range.short_description = "Полоса, МГц"
freq_range.admin_order_field = "id_vch_load__freq_range" freq_range.admin_order_field = "parameters_obj__freq_range"
def bod_velocity(self, obj): def bod_velocity(self, obj):
par = obj.id_vch_load # Get the first parameter associated with this objitem to display bod_velocity
return par.bod_velocity param = next(iter(obj.parameters_obj.all()), None)
if param:
return param.bod_velocity
return "-"
bod_velocity.short_description = "Сим. v, БОД" bod_velocity.short_description = "Сим. v, БОД"
def modulation(self, obj): def modulation(self, obj):
par = obj.id_vch_load.modulation # Get the first parameter associated with this objitem to display modulation
return par.name param = next(iter(obj.parameters_obj.all()), None)
if param and param.modulation:
return param.modulation.name
return "-"
modulation.short_description = "Модуляция" modulation.short_description = "Модуляция"
def snr(self, obj): def snr(self, obj):
par = obj.id_vch_load # Get the first parameter associated with this objitem to display snr
return par.snr param = next(iter(obj.parameters_obj.all()), None)
if param:
return param.snr
return "-"
snr.short_description = "ОСШ" snr.short_description = "ОСШ"
def geo_coords(self, obj): def geo_coords(self, obj):
geo = obj.id_geo geo = obj.geo_obj
if not geo or not geo.coords:
return "-"
longitude = geo.coords.coords[0] longitude = geo.coords.coords[0]
latitude = geo.coords.coords[1] latitude = geo.coords.coords[1]
lon = f"{longitude}E" if longitude > 0 else f"{abs(longitude)}W" lon = f"{longitude}E" if longitude > 0 else f"{abs(longitude)}W"
lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S" lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S"
return f"{lat} {lon}" return f"{lat} {lon}"
geo_coords.short_description = "Координаты геолокации" geo_coords.short_description = "Координаты геолокации"
geo_coords.admin_order_filed = "id_geo__coords" geo_coords.admin_order_field = "geo_obj__coords"
def kupsat_coords(self, obj): def kupsat_coords(self, obj):
obj = obj.id_geo geo = obj.geo_obj
if obj.coords_kupsat is None: if not geo or not geo.coords_kupsat:
return "-" return "-"
longitude = obj.coords_kupsat.coords[0] longitude = geo.coords_kupsat.coords[0]
latitude = obj.coords_kupsat.coords[1] latitude = geo.coords_kupsat.coords[1]
lon = f"{longitude}E" if longitude > 0 else f"{abs(longitude)}W" lon = f"{longitude}E" if longitude > 0 else f"{abs(longitude)}W"
lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S" lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S"
return f"{lat} {lon}" return f"{lat} {lon}"
kupsat_coords.short_description = "Координаты Кубсата" kupsat_coords.short_description = "Координаты Кубсата"
def valid_coords(self, obj): def valid_coords(self, obj):
obj = obj.id_geo geo = obj.geo_obj
if obj.coords_valid is None: if not geo or not geo.coords_valid:
return "-" return "-"
longitude = obj.coords_valid.coords[0] longitude = geo.coords_valid.coords[0]
latitude = obj.coords_valid.coords[1] latitude = geo.coords_valid.coords[1]
lon = f"{longitude}E" if longitude > 0 else f"{abs(longitude)}W" lon = f"{longitude}E" if longitude > 0 else f"{abs(longitude)}W"
lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S" lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S"
return f"{lat} {lon}" return f"{lat} {lon}"

View File

@@ -0,0 +1,45 @@
# Generated by Django 5.2.7 on 2025-10-30 06:40
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0020_sourcetype_objitem_id_source_type'),
]
operations = [
migrations.RemoveConstraint(
model_name='objitem',
name='unique_objitem_combination',
),
migrations.RemoveField(
model_name='objitem',
name='id_geo',
),
migrations.RemoveField(
model_name='objitem',
name='id_source_type',
),
migrations.RemoveField(
model_name='objitem',
name='id_vch_load',
),
migrations.AddField(
model_name='geo',
name='objitem',
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='geo_obj', to='mainapp.objitem', verbose_name='Гео'),
),
migrations.AddField(
model_name='parameter',
name='objitem',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='parameters_obj', to='mainapp.objitem', verbose_name='Источник'),
),
migrations.AddField(
model_name='sourcetype',
name='objitem',
field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='source_type_obj', to='mainapp.objitem', verbose_name='Гео'),
),
]

View File

@@ -0,0 +1,22 @@
# Generated by Django 5.2.7 on 2025-10-30 07:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0021_remove_objitem_unique_objitem_combination_and_more'),
]
operations = [
migrations.RemoveField(
model_name='parameter',
name='objitem',
),
migrations.AddField(
model_name='parameter',
name='objitems',
field=models.ManyToManyField(blank=True, related_name='parameters_obj', to='mainapp.objitem', verbose_name='Источники'),
),
]

View File

@@ -132,7 +132,7 @@ class ObjItem(models.Model):
class SourceType(models.Model): class SourceType(models.Model):
name = models.CharField(max_length=50, unique=True, verbose_name="Тип источника") name = models.CharField(max_length=50, unique=True, verbose_name="Тип источника")
objitem = models.OneToOneField(ObjItem, on_delete=models.SET_NULL, verbose_name="Гео", related_name="objitems", null=True) objitem = models.OneToOneField(ObjItem, on_delete=models.SET_NULL, verbose_name="Гео", related_name="source_type_obj", null=True)
def __str__(self): def __str__(self):
return self.name return self.name
@@ -158,7 +158,7 @@ class Parameter(models.Model):
Standard, default=get_default_standard, on_delete=models.SET_DEFAULT, related_name="standards", null=True, blank=True, verbose_name="Стандарт" Standard, default=get_default_standard, on_delete=models.SET_DEFAULT, related_name="standards", null=True, blank=True, verbose_name="Стандарт"
) )
id_user_add = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, related_name="parameter_added", verbose_name="Пользователь", null=True, blank=True) id_user_add = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, related_name="parameter_added", verbose_name="Пользователь", null=True, blank=True)
objitem = models.ForeignKey(ObjItem, on_delete=models.SET_NULL, related_name="objitems", verbose_name="Источник",null=True, blank=True) objitems = models.ManyToManyField(ObjItem, related_name="parameters_obj", verbose_name="Источники", blank=True)
# id_sigma_parameter = models.ManyToManyField(SigmaParameter, on_delete=models.SET_NULL, related_name="sigma_parameter", verbose_name="ВЧ с sigma", null=True, blank=True) # id_sigma_parameter = models.ManyToManyField(SigmaParameter, on_delete=models.SET_NULL, related_name="sigma_parameter", verbose_name="ВЧ с sigma", null=True, blank=True)
# id_sigma_parameter = models.ManyToManyField(SigmaParameter, verbose_name="ВЧ с sigma", null=True, blank=True) # id_sigma_parameter = models.ManyToManyField(SigmaParameter, verbose_name="ВЧ с sigma", null=True, blank=True)
@@ -272,7 +272,7 @@ class Geo(models.Model):
db_persist=True, db_persist=True,
null=True, blank=True, verbose_name="Расстояние между купсатом и оперативным отделом, км" null=True, blank=True, verbose_name="Расстояние между купсатом и оперативным отделом, км"
) )
objitem = models.OneToOneField(ObjItem, on_delete=models.SET_NULL, verbose_name="Гео", related_name="objitems", null=True) objitem = models.OneToOneField(ObjItem, on_delete=models.SET_NULL, verbose_name="Гео", related_name="geo_obj", null=True)
def __str__(self): def __str__(self):
longitude = self.coords.coords[0] longitude = self.coords.coords[0]

View File

@@ -21,7 +21,7 @@ import io
from django.db.models import F, Count, Exists, OuterRef, Min, Max from django.db.models import F, Count, Exists, OuterRef, Min, Max
from geopy.geocoders import Nominatim from geopy.geocoders import Nominatim
import reverse_geocoder as rg import reverse_geocoder as rg
import time from time import sleep
def get_all_constants(): def get_all_constants():
sats = [sat.name for sat in Satellite.objects.all()] sats = [sat.name for sat in Satellite.objects.all()]
@@ -88,6 +88,7 @@ def fill_data_from_df(df: pd.DataFrame, sat: Satellite):
date = stroka[1]['Дата'].date() date = stroka[1]['Дата'].date()
time_ = stroka[1]['Время'] time_ = stroka[1]['Время']
if isinstance(time_, str): if isinstance(time_, str):
time_ = time_.strip()
time_ = time(0,0,0) time_ = time(0,0,0)
timestamp = datetime.combine(date, time_) timestamp = datetime.combine(date, time_)
current_mirrors = [] current_mirrors = []
@@ -111,7 +112,7 @@ def fill_data_from_df(df: pd.DataFrame, sat: Satellite):
comment = stroka[1]['Комментарий'] comment = stroka[1]['Комментарий']
source = stroka[1]['Объект наблюдения'] source = stroka[1]['Объект наблюдения']
vch_load_obj, vch_created = Parameter.objects.get_or_create( vch_load_obj, _ = Parameter.objects.get_or_create(
id_satellite=sat, id_satellite=sat,
polarization=polarization_obj, polarization=polarization_obj,
frequency=freq, frequency=freq,
@@ -137,17 +138,41 @@ def fill_data_from_df(df: pd.DataFrame, sat: Satellite):
geo.save() geo.save()
geo.mirrors.set(Mirror.objects.filter(name__in=current_mirrors)) geo.mirrors.set(Mirror.objects.filter(name__in=current_mirrors))
obj_item, _ = ObjItem.objects.get_or_create( existing_obj_items = ObjItem.objects.filter(
id_geo=geo, parameters_obj=vch_load_obj,
id_vch_load=vch_load_obj, geo_obj=geo
defaults={
'name': source,
'id_user_add': CustomUser.objects.get(id=1),
# 'id_satellite': sat
}
) )
if not existing_obj_items.exists():
# Такой объект уже существует, используем его
# obj_item = existing_obj_items.first()
obj_item = ObjItem.objects.create(
name=source,
id_user_add=CustomUser.objects.get(id=1)
)
# Устанавливаем связь с параметром
obj_item.parameters_obj.set([vch_load_obj])
# Устанавливаем связь с geo
geo.objitem = obj_item
geo.save()
# else:
# Создаем новый ObjItem
# obj_item, _ = ObjItem.objects.get_or_create(
# defaults={
# 'name': source,
# 'id_user_add': CustomUser.objects.get(id=1),
# # 'id_satellite': sat
# }
# )
# obj_item.save()
# obj_item.parameters_obj.set([vch_load_obj])
# if geo:
# obj_item.geo_obj = geo
# # или в зависимости от вашей модели Geo, вы можете установить обратную связь там:
# # geo.objitem = obj_item
# geo.save()
obj_item.save()
def add_satellite_list(): def add_satellite_list():
@@ -389,7 +414,7 @@ def kub_report(data_in: io.StringIO) -> pd.DataFrame:
# loc_name = location.get('city', '') or location.get('town', '') or location.get('province', '') or location.get('country', '') # loc_name = location.get('city', '') or location.get('town', '') or location.get('province', '') or location.get('country', '')
# except AttributeError: # except AttributeError:
# loc_name = '' # loc_name = ''
# time.sleep(1) # sleep(1)
loc_name = '' loc_name = ''
if transponder: #and not (len(transponder) > 1): if transponder: #and not (len(transponder) > 1):
transfer = transponder.transfer transfer = transponder.transfer