init commit
This commit is contained in:
0
dbapp/mainapp/__init__.py
Normal file
0
dbapp/mainapp/__init__.py
Normal file
506
dbapp/mainapp/admin.py
Normal file
506
dbapp/mainapp/admin.py
Normal file
@@ -0,0 +1,506 @@
|
||||
# admin.py
|
||||
from django.contrib import admin
|
||||
from .models import (
|
||||
Polarization,
|
||||
Modulation,
|
||||
Standard,
|
||||
SigmaParMark,
|
||||
SigmaParameter,
|
||||
Parameter,
|
||||
Satellite,
|
||||
Mirror,
|
||||
Geo,
|
||||
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 rangefilter.filters import (
|
||||
DateRangeFilterBuilder,
|
||||
DateTimeRangeFilterBuilder,
|
||||
NumericRangeFilterBuilder,
|
||||
DateRangeQuickSelectListFilterBuilder,
|
||||
)
|
||||
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"
|
||||
admin.site.unregister(User)
|
||||
admin.site.unregister(Group)
|
||||
|
||||
|
||||
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 CustomUserInline(admin.StackedInline):
|
||||
model = CustomUser
|
||||
can_delete = False
|
||||
verbose_name_plural = 'Дополнительная информация пользователя'
|
||||
|
||||
|
||||
@admin.register(CustomUser)
|
||||
class CustomUserAdmin(admin.ModelAdmin):
|
||||
list_display = ('user', 'role')
|
||||
list_filter = ('role',)
|
||||
|
||||
|
||||
class UserAdmin(BaseUserAdmin):
|
||||
inlines = [CustomUserInline]
|
||||
|
||||
admin.site.register(User, UserAdmin)
|
||||
|
||||
@admin.register(SigmaParMark)
|
||||
class SigmaParMarkAdmin(admin.ModelAdmin):
|
||||
list_display = ("mark", "timestamp")
|
||||
search_fields = ("mark", )
|
||||
ordering = ("timestamp",)
|
||||
|
||||
|
||||
@admin.register(Polarization)
|
||||
class PolarizationAdmin(admin.ModelAdmin):
|
||||
list_display = ("name",)
|
||||
search_fields = ("name",)
|
||||
ordering = ("name",)
|
||||
|
||||
|
||||
@admin.register(Modulation)
|
||||
class ModulationAdmin(admin.ModelAdmin):
|
||||
list_display = ("name",)
|
||||
search_fields = ("name",)
|
||||
ordering = ("name",)
|
||||
|
||||
|
||||
@admin.register(Standard)
|
||||
class StandardAdmin(admin.ModelAdmin):
|
||||
list_display = ("name",)
|
||||
search_fields = ("name",)
|
||||
ordering = ("name",)
|
||||
|
||||
|
||||
class SigmaParameterInline(admin.StackedInline):
|
||||
model = SigmaParameter
|
||||
extra = 0
|
||||
autocomplete_fields = ['mark']
|
||||
readonly_fields = (
|
||||
"datetime_begin",
|
||||
"datetime_end",
|
||||
)
|
||||
def has_add_permission(self, request, obj=None):
|
||||
return False
|
||||
|
||||
|
||||
@admin.register(Parameter)
|
||||
class ParameterAdmin(ImportExportActionModelAdmin, admin.ModelAdmin):
|
||||
list_display = (
|
||||
"id_satellite",
|
||||
"frequency",
|
||||
"freq_range",
|
||||
"polarization",
|
||||
"modulation",
|
||||
"bod_velocity",
|
||||
"snr",
|
||||
"standard",
|
||||
"sigma_parameter"
|
||||
)
|
||||
# fields = ( "id_satellite",
|
||||
# "frequency",
|
||||
# "freq_range",
|
||||
# "polarization",
|
||||
# "modulation",
|
||||
# "bod_velocity",
|
||||
# "snr",
|
||||
# "standard",
|
||||
# "id_sigma_parameter")
|
||||
list_display_links = ("frequency", "id_satellite", )
|
||||
list_filter = (
|
||||
HasSigmaParameterFilter,
|
||||
("id_satellite", MultiSelectRelatedDropdownFilter),
|
||||
("polarization__name", MultiSelectDropdownFilter),
|
||||
("modulation", MultiSelectRelatedDropdownFilter),
|
||||
("standard", MultiSelectRelatedDropdownFilter),
|
||||
("frequency", NumericRangeFilterBuilder()),
|
||||
("freq_range", NumericRangeFilterBuilder()),
|
||||
("snr", NumericRangeFilterBuilder()),
|
||||
)
|
||||
search_fields = (
|
||||
"id_satellite",
|
||||
"frequency",
|
||||
"freq_range",
|
||||
"bod_velocity",
|
||||
"snr",
|
||||
"modulation__name",
|
||||
"polarization__name",
|
||||
"standard__name",
|
||||
)
|
||||
ordering = ("frequency",)
|
||||
list_select_related = ("polarization", "modulation", "standard", "id_satellite",)
|
||||
# raw_id_fields = ("id_sigma_parameter", )
|
||||
inlines = [SigmaParameterInline]
|
||||
# autocomplete_fields = ("id_sigma_parameter", )
|
||||
|
||||
def sigma_parameter(self, obj):
|
||||
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, admin.ModelAdmin):
|
||||
list_display = (
|
||||
"id_satellite",
|
||||
"status",
|
||||
"frequency",
|
||||
"freq_range",
|
||||
"power",
|
||||
"modulation",
|
||||
"bod_velocity",
|
||||
"snr",
|
||||
"standard",
|
||||
"parameter",
|
||||
"packets",
|
||||
"datetime_begin",
|
||||
"datetime_end",
|
||||
)
|
||||
readonly_fields = (
|
||||
"datetime_begin",
|
||||
"datetime_end",
|
||||
|
||||
)
|
||||
list_display_links = ("id_satellite",)
|
||||
list_filter = (
|
||||
("id_satellite__name", MultiSelectDropdownFilter),
|
||||
("modulation__name", MultiSelectDropdownFilter),
|
||||
("standard__name", MultiSelectDropdownFilter),
|
||||
("frequency", NumericRangeFilterBuilder()),
|
||||
("freq_range", NumericRangeFilterBuilder()),
|
||||
("snr", NumericRangeFilterBuilder()),
|
||||
)
|
||||
search_fields = (
|
||||
"id_satellite__name",
|
||||
"frequency",
|
||||
"freq_range",
|
||||
"bod_velocity",
|
||||
"snr",
|
||||
"modulation__name",
|
||||
"standard__name",
|
||||
)
|
||||
autocomplete_fields = ('mark',)
|
||||
ordering = ("frequency",)
|
||||
list_select_related = ("modulation", "standard", "id_satellite", "parameter")
|
||||
prefetch_related = ("mark",)
|
||||
|
||||
|
||||
@admin.register(Satellite)
|
||||
class SatelliteAdmin(admin.ModelAdmin):
|
||||
list_display = ("name",)
|
||||
search_fields = ("name",)
|
||||
ordering = ("name",)
|
||||
|
||||
|
||||
@admin.register(Mirror)
|
||||
class MirrorAdmin(ImportExportActionModelAdmin, admin.ModelAdmin):
|
||||
list_display = ("name",)
|
||||
search_fields = ("name",)
|
||||
ordering = ("name",)
|
||||
|
||||
|
||||
@admin.register(Geo)
|
||||
class GeoAdmin(ImportExportActionModelAdmin, LeafletGeoAdmin):
|
||||
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"),
|
||||
}),
|
||||
)
|
||||
list_display = (
|
||||
"formatted_timestamp",
|
||||
"location",
|
||||
"mirrors_names",
|
||||
"geo_coords",
|
||||
"kupsat_coords",
|
||||
"valid_coords",
|
||||
"is_average",
|
||||
)
|
||||
autocomplete_fields = ('mirrors',)
|
||||
list_display_links = ("formatted_timestamp",)
|
||||
list_filter = (
|
||||
("mirrors", MultiSelectRelatedDropdownFilter),
|
||||
"is_average",
|
||||
("location", MultiSelectDropdownFilter),
|
||||
("timestamp", DateRangeQuickSelectListFilterBuilder()),
|
||||
("id_user_add", MultiSelectRelatedDropdownFilter),
|
||||
)
|
||||
search_fields = (
|
||||
"mirrors__name",
|
||||
"location",
|
||||
"coords",
|
||||
"coords_kupsat",
|
||||
"coords_valid"
|
||||
)
|
||||
list_select_related = ("id_user_add", )
|
||||
prefetch_related = ("mirrors", )
|
||||
|
||||
|
||||
settings_overrides = {
|
||||
'DEFAULT_CENTER': (55.7558, 37.6173),
|
||||
'DEFAULT_ZOOM': 12,
|
||||
}
|
||||
|
||||
|
||||
def mirrors_names(self, obj):
|
||||
return ", ".join(m.name for m in obj.mirrors.all())
|
||||
mirrors_names.short_description = "Зеркала"
|
||||
|
||||
def formatted_timestamp(self, obj):
|
||||
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):
|
||||
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 = "Координаты оперативного отдела"
|
||||
|
||||
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 = "Показать выбранные на карте"
|
||||
|
||||
@admin.register(ObjItem)
|
||||
class ObjectAdmin(admin.ModelAdmin):
|
||||
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",
|
||||
)
|
||||
list_display_links = ("name",)
|
||||
list_filter = (
|
||||
UniqueToggleFilter,
|
||||
("id_vch_load__id_satellite", MultiSelectRelatedDropdownFilter),
|
||||
("id_vch_load__frequency", NumericRangeFilterBuilder()),
|
||||
("id_vch_load__freq_range", NumericRangeFilterBuilder()),
|
||||
("id_vch_load__snr", NumericRangeFilterBuilder()),
|
||||
("id_vch_load__modulation", MultiSelectRelatedDropdownFilter),
|
||||
("id_vch_load__polarization", MultiSelectRelatedDropdownFilter),
|
||||
GeoKupDistanceFilter,
|
||||
GeoValidDistanceFilter
|
||||
)
|
||||
search_fields = (
|
||||
"name",
|
||||
# "id_geo",
|
||||
# "id_satellite__name",
|
||||
# "id_vch_load__frequency",
|
||||
)
|
||||
ordering = ("name",)
|
||||
list_select_related = (
|
||||
# "id_satellite",
|
||||
"id_vch_load",
|
||||
"id_vch_load__polarization",
|
||||
"id_vch_load__modulation",
|
||||
"id_vch_load__id_satellite",
|
||||
"id_geo",
|
||||
)
|
||||
autocomplete_fields = ("id_geo",)
|
||||
raw_id_fields = ("id_vch_load",)
|
||||
# dynamic_raw_id_fields = ("id_vch_load",)
|
||||
actions = [show_on_map]
|
||||
|
||||
def sat_name(self, obj):
|
||||
return obj.id_vch_load.id_satellite
|
||||
sat_name.short_description = "Спутник"
|
||||
|
||||
def freq(self, obj):
|
||||
par = obj.id_vch_load
|
||||
return par.frequency
|
||||
freq.short_description = "Частота, МГц"
|
||||
|
||||
def distance_geo_kup(self, obj):
|
||||
par = obj.id_geo.distance_coords_kup
|
||||
if par is None:
|
||||
return "-"
|
||||
return round(par, 3)
|
||||
distance_geo_kup.short_description = "Гео-куб, км"
|
||||
|
||||
def distance_geo_valid(self, obj):
|
||||
par = obj.id_geo.distance_coords_valid
|
||||
if par is None:
|
||||
return "-"
|
||||
return round(par, 3)
|
||||
distance_geo_valid.short_description = "Гео-опер, км"
|
||||
|
||||
def distance_kup_valid(self, obj):
|
||||
par = obj.id_geo.distance_kup_valid
|
||||
if par is None:
|
||||
return "-"
|
||||
return round(par, 3)
|
||||
distance_kup_valid.short_description = "Куб-опер, км"
|
||||
|
||||
def pol(self, obj):
|
||||
par = obj.id_vch_load.polarization
|
||||
return par.name
|
||||
pol.short_description = "Поляризация"
|
||||
|
||||
def freq_range(self, obj):
|
||||
par = obj.id_vch_load
|
||||
return par.freq_range
|
||||
freq_range.short_description = "Полоса, МГц"
|
||||
|
||||
def bod_velocity(self, obj):
|
||||
par = obj.id_vch_load
|
||||
return par.bod_velocity
|
||||
bod_velocity.short_description = "Сим. v, БОД"
|
||||
|
||||
def modulation(self, obj):
|
||||
par = obj.id_vch_load.modulation
|
||||
return par.name
|
||||
modulation.short_description = "Модуляция"
|
||||
|
||||
def snr(self, obj):
|
||||
par = obj.id_vch_load
|
||||
return par.snr
|
||||
snr.short_description = "ОСШ"
|
||||
|
||||
def geo_coords(self, obj):
|
||||
geo = obj.id_geo
|
||||
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 = "Координаты геолокации"
|
||||
|
||||
def kupsat_coords(self, obj):
|
||||
obj = obj.id_geo
|
||||
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):
|
||||
obj = obj.id_geo
|
||||
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 = "Координаты оперативного отдела"
|
||||
6
dbapp/mainapp/apps.py
Normal file
6
dbapp/mainapp/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class MainappConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'mainapp'
|
||||
31
dbapp/mainapp/clusters.py
Normal file
31
dbapp/mainapp/clusters.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from .models import ObjItem
|
||||
from sklearn.cluster import DBSCAN, HDBSCAN, KMeans
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
def get_clusters(coords: list[tuple[float, float]]):
|
||||
coords = np.radians(coords)
|
||||
lat, lon = coords[:, 0], coords[:, 1]
|
||||
db = DBSCAN(eps=0.06, min_samples=5, algorithm='ball_tree', metric='haversine')
|
||||
# db = HDBSCAN()
|
||||
cluster_labels = db.fit_predict(coords)
|
||||
plt.figure(figsize=(10, 8))
|
||||
unique_labels = set(cluster_labels)
|
||||
colors = plt.cm.tab10(np.linspace(0, 1, len(unique_labels)))
|
||||
|
||||
for label, color in zip(unique_labels, colors):
|
||||
if label == -1:
|
||||
color = 'k'
|
||||
label_name = 'Шум'
|
||||
else:
|
||||
label_name = f'Кластер {label}'
|
||||
|
||||
mask = cluster_labels == label
|
||||
plt.scatter(lon[mask], lat[mask], c=[color], label=label_name, s=30)
|
||||
|
||||
plt.xlabel('Долгота')
|
||||
plt.ylabel('Широта')
|
||||
plt.title('Кластеризация геоданных с DBSCAN (метрика Хаверсина)')
|
||||
plt.legend()
|
||||
plt.grid(True)
|
||||
plt.show()
|
||||
73
dbapp/mainapp/filters.py
Normal file
73
dbapp/mainapp/filters.py
Normal file
@@ -0,0 +1,73 @@
|
||||
from django.contrib.admin import SimpleListFilter
|
||||
from .models import ObjItem
|
||||
|
||||
class GeoKupDistanceFilter(SimpleListFilter):
|
||||
title = 'Расстояние между гео и кубсатом'
|
||||
parameter_name = 'distance_geo_kup'
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
return (
|
||||
('small', 'Меньше 100 км'),
|
||||
('medium', '100-500 км'),
|
||||
('large', 'Больше 500 км'),
|
||||
)
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
if self.value() == 'small':
|
||||
return queryset.filter(distance_coords_kup__lt=100)
|
||||
if self.value() == 'medium':
|
||||
return queryset.filter(distance_coords_kup__gte=100, distance_coords_kup__lte=500)
|
||||
if self.value() == 'large':
|
||||
return queryset.filter(distance_coords_kup__gt=500)
|
||||
|
||||
|
||||
class GeoValidDistanceFilter(SimpleListFilter):
|
||||
title = 'Расстояние между гео и оперативным отделом'
|
||||
parameter_name = 'distance_geo_valid'
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
return (
|
||||
('small', 'Меньше 100 км'),
|
||||
('medium', '100-500 км'),
|
||||
('large', 'Больше 500 км'),
|
||||
)
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
if self.value() == 'small':
|
||||
return queryset.filter(distance_coords_valid__lt=100)
|
||||
if self.value() == 'medium':
|
||||
return queryset.filter(distance_coords_valid__gte=100, distance_coords_valid__lte=500)
|
||||
if self.value() == 'large':
|
||||
return queryset.filter(distance_coords_valid__gt=500)
|
||||
|
||||
class UniqueToggleFilter(SimpleListFilter):
|
||||
title = 'Уникальность по имени'
|
||||
parameter_name = 'name'
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
return (
|
||||
('unique', 'Только уникальные'),
|
||||
('all', 'Все'),
|
||||
)
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
if self.value() == 'unique':
|
||||
return queryset.order_by('name').distinct('name')
|
||||
return queryset
|
||||
|
||||
class HasSigmaParameterFilter(SimpleListFilter):
|
||||
title = 'ВЧ sigma'
|
||||
parameter_name = 'has_sigma'
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
return (
|
||||
('yes', 'Заполнено'),
|
||||
('no', 'Пусто'),
|
||||
)
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
if self.value() == 'yes':
|
||||
return queryset.filter(sigma_parameter__isnull=False)
|
||||
if self.value() == 'no':
|
||||
return queryset.filter(sigma_parameter__isnull=True)
|
||||
return queryset
|
||||
78
dbapp/mainapp/forms.py
Normal file
78
dbapp/mainapp/forms.py
Normal file
@@ -0,0 +1,78 @@
|
||||
from django import forms
|
||||
from .models import Satellite
|
||||
|
||||
class LoadExcelData(forms.Form):
|
||||
file = forms.FileField(
|
||||
label="Выберите Excel файл",
|
||||
widget=forms.FileInput(attrs={
|
||||
'class': 'form-control',
|
||||
'accept': '.xlsx,.xls'
|
||||
})
|
||||
)
|
||||
sat_choice = forms.ModelChoiceField(
|
||||
queryset=Satellite.objects.all(),
|
||||
label="Выберите спутник",
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'form-select'
|
||||
})
|
||||
)
|
||||
number_input = forms.IntegerField(
|
||||
label="Введите число объектов",
|
||||
min_value=0,
|
||||
widget=forms.NumberInput(attrs={
|
||||
'class': 'form-control'
|
||||
})
|
||||
)
|
||||
|
||||
class LoadCsvData(forms.Form):
|
||||
file = forms.FileField(
|
||||
label="Выберите CSV файл",
|
||||
widget=forms.FileInput(attrs={
|
||||
'class': 'form-control',
|
||||
'accept': '.csv'
|
||||
})
|
||||
)
|
||||
|
||||
class UploadFileForm(forms.Form):
|
||||
sat_choice = forms.ModelChoiceField(
|
||||
queryset=Satellite.objects.all(),
|
||||
label="Выберите спутник",
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'form-select'
|
||||
})
|
||||
)
|
||||
file = forms.FileField(
|
||||
label="Выберите текстовый файл",
|
||||
widget=forms.FileInput(attrs={
|
||||
'class': 'form-file-input'
|
||||
})
|
||||
)
|
||||
|
||||
class VchLinkForm(forms.Form):
|
||||
sat_choice = forms.ModelChoiceField(
|
||||
queryset=Satellite.objects.all(),
|
||||
label="Выберите спутник",
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'form-select'
|
||||
})
|
||||
)
|
||||
ku_range = forms.ChoiceField(
|
||||
choices=[(9750.0, '9750'), (10750.0, '10750')],
|
||||
# coerce=lambda x: x == 'True',
|
||||
widget=forms.Select(attrs={'class': 'form-select'}),
|
||||
label='Выбор диапазона'
|
||||
)
|
||||
value1 = forms.FloatField(
|
||||
label="Первое число",
|
||||
widget=forms.NumberInput(attrs={
|
||||
'class': 'form-control',
|
||||
'placeholder': 'Введите первое число'
|
||||
})
|
||||
)
|
||||
value2 = forms.FloatField(
|
||||
label="Второе число",
|
||||
widget=forms.NumberInput(attrs={
|
||||
'class': 'form-control',
|
||||
'placeholder': 'Введите второе число'
|
||||
})
|
||||
)
|
||||
147
dbapp/mainapp/migrations/0001_initial.py
Normal file
147
dbapp/mainapp/migrations/0001_initial.py
Normal file
@@ -0,0 +1,147 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-13 12:47
|
||||
|
||||
import django.contrib.gis.db.models.fields
|
||||
import django.db.models.deletion
|
||||
import mainapp.models
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Mirror',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=30, unique=True, verbose_name='Имя зеркала')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Зеркало',
|
||||
'verbose_name_plural': 'Зеркала',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Modulation',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=20, unique=True, verbose_name='Модуляция')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Модуляция',
|
||||
'verbose_name_plural': 'Модуляции',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Polarization',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=20, unique=True, verbose_name='Поляризация')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Поляризация',
|
||||
'verbose_name_plural': 'Поляризация',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Satellite',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=30, unique=True, verbose_name='Имя спутника')),
|
||||
('norad', models.IntegerField(blank=True, null=True, verbose_name='NORAD ID')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Спутник',
|
||||
'verbose_name_plural': 'Спутники',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Standard',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=20, unique=True, verbose_name='Стандарт')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Стандарт',
|
||||
'verbose_name_plural': 'Стандарты',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CustomUser',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('role', models.CharField(choices=[('admin', 'Администратор'), ('moderator', 'Модератор'), ('user', 'Пользователь')], default='user', max_length=20, verbose_name='Роль пользователя')),
|
||||
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Пользователь',
|
||||
'verbose_name_plural': 'Пользователи',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Geo',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('timestamp', models.DateTimeField(blank=True, null=True, verbose_name='Время')),
|
||||
('coords', django.contrib.gis.db.models.fields.PointField(blank=True, null=True, srid=4326, verbose_name='Координата геолокации')),
|
||||
('location', models.CharField(blank=True, max_length=255, null=True, verbose_name='Метоположение')),
|
||||
('comment', models.CharField(blank=True, max_length=255, verbose_name='Комментарий')),
|
||||
('coords_kupsat', django.contrib.gis.db.models.fields.PointField(blank=True, null=True, srid=4326, verbose_name='Координаты Кубсата')),
|
||||
('coords_valid', django.contrib.gis.db.models.fields.PointField(blank=True, null=True, srid=4326, verbose_name='Координаты оперативников')),
|
||||
('is_average', models.BooleanField(blank=True, null=True, verbose_name='Усреднённое')),
|
||||
('id_user_add', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='geos_added', to='mainapp.customuser', verbose_name='Пользователь')),
|
||||
('mirrors', models.ManyToManyField(related_name='geo_mirrors', to='mainapp.mirror', verbose_name='Зеркала')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Гео',
|
||||
'verbose_name_plural': 'Гео',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Parameter',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('frequency', models.FloatField(blank=True, default=0, null=True, verbose_name='Частота, МГц')),
|
||||
('freq_range', models.FloatField(blank=True, default=0, null=True, verbose_name='Полоса частот, МГц')),
|
||||
('bod_velocity', models.FloatField(blank=True, default=0, null=True, verbose_name='Символьная скорость, БОД')),
|
||||
('snr', models.FloatField(blank=True, default=0, null=True, verbose_name='ОСШ')),
|
||||
('id_user_add', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='parameter_added', to='mainapp.customuser', verbose_name='Пользователь')),
|
||||
('modulation', models.ForeignKey(blank=True, default=mainapp.models.get_default_modulation, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='modulations', to='mainapp.modulation', verbose_name='Модуляция')),
|
||||
('polarization', models.ForeignKey(blank=True, default=mainapp.models.get_default_polarization, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='polarizations', to='mainapp.polarization', verbose_name='Поляризация')),
|
||||
('standard', models.ForeignKey(blank=True, default=mainapp.models.get_default_standard, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='standards', to='mainapp.standard', verbose_name='Стандарт')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'ВЧ загрузка',
|
||||
'verbose_name_plural': 'ВЧ загрузки',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ObjItem',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(blank=True, max_length=100, null=True, verbose_name='Имя объекта')),
|
||||
('id_geo', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='objitems', to='mainapp.geo', verbose_name='Геоданные')),
|
||||
('id_user_add', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='objitems', to='mainapp.customuser', verbose_name='Пользователь')),
|
||||
('id_vch_load', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='objitems', to='mainapp.parameter', verbose_name='ВЧ загрузка')),
|
||||
('id_satellite', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='objitems', to='mainapp.satellite', verbose_name='Спутник')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Объект',
|
||||
'verbose_name_plural': 'Объекты',
|
||||
},
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='geo',
|
||||
constraint=models.UniqueConstraint(fields=('timestamp', 'coords'), name='unique_geo_combination'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='objitem',
|
||||
constraint=models.UniqueConstraint(fields=('id_vch_load', 'id_geo'), name='unique_objitem_combination'),
|
||||
),
|
||||
]
|
||||
20
dbapp/mainapp/migrations/0002_geo_distance_coords_kup.py
Normal file
20
dbapp/mainapp/migrations/0002_geo_distance_coords_kup.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-15 09:23
|
||||
|
||||
import django.contrib.gis.db.models.functions
|
||||
import django.db.models.expressions
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainapp', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='geo',
|
||||
name='distance_coords_kup',
|
||||
field=models.GeneratedField(db_persist=True, expression=django.db.models.expressions.CombinedExpression(django.contrib.gis.db.models.functions.Distance('coords', 'coords_kupsat'), '/', models.Value(1000)), null=True, output_field=models.FloatField(), verbose_name='Расстояние между купсатом и гео'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,30 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-15 09:43
|
||||
|
||||
import django.contrib.gis.db.models.functions
|
||||
import django.db.models.expressions
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainapp', '0002_geo_distance_coords_kup'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='geo',
|
||||
name='distance_coords_valid',
|
||||
field=models.GeneratedField(db_persist=True, expression=django.db.models.expressions.CombinedExpression(django.contrib.gis.db.models.functions.Distance('coords', 'coords_valid'), '/', models.Value(1000)), null=True, output_field=models.FloatField(), verbose_name='Расстояние между гео и оперативным отделом, км'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='geo',
|
||||
name='distance_kup_valid',
|
||||
field=models.GeneratedField(db_persist=True, expression=django.db.models.expressions.CombinedExpression(django.contrib.gis.db.models.functions.Distance('coords_valid', 'coords_kupsat'), '/', models.Value(1000)), null=True, output_field=models.FloatField(), verbose_name='Расстояние между купсатом и оперативным отделом, км'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='geo',
|
||||
name='distance_coords_kup',
|
||||
field=models.GeneratedField(db_persist=True, expression=django.db.models.expressions.CombinedExpression(django.contrib.gis.db.models.functions.Distance('coords', 'coords_kupsat'), '/', models.Value(1000)), null=True, output_field=models.FloatField(), verbose_name='Расстояние между купсатом и гео, км'),
|
||||
),
|
||||
]
|
||||
36
dbapp/mainapp/migrations/0004_sigmaparameter.py
Normal file
36
dbapp/mainapp/migrations/0004_sigmaparameter.py
Normal file
@@ -0,0 +1,36 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-16 12:50
|
||||
|
||||
import django.db.models.deletion
|
||||
import mainapp.models
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainapp', '0003_geo_distance_coords_valid_geo_distance_kup_valid_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SigmaParameter',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('status', models.CharField(blank=True, max_length=20, null=True, verbose_name='Статус')),
|
||||
('frequency', models.FloatField(blank=True, default=0, null=True, verbose_name='Частота, МГц')),
|
||||
('freq_range', models.FloatField(blank=True, default=0, null=True, verbose_name='Полоса частот, МГц')),
|
||||
('power', models.FloatField(blank=True, default=0, null=True, verbose_name='Мощность, дБм')),
|
||||
('bod_velocity', models.FloatField(blank=True, default=0, null=True, verbose_name='Символьная скорость, БОД')),
|
||||
('snr', models.FloatField(blank=True, default=0, null=True, verbose_name='ОСШ, Дб')),
|
||||
('packets', models.BooleanField(blank=True, null=True, verbose_name='Пакетность')),
|
||||
('datetime_begin', models.DateTimeField(blank=True, null=True, verbose_name='Время начала измерения')),
|
||||
('datetime_end', models.DateTimeField(blank=True, null=True, verbose_name='Время окончания измерения')),
|
||||
('modulation', models.ForeignKey(blank=True, default=mainapp.models.get_default_modulation, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='modulations_sigma', to='mainapp.modulation', verbose_name='Модуляция')),
|
||||
('standard', models.ForeignKey(blank=True, default=mainapp.models.get_default_standard, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='standards_sigma', to='mainapp.standard', verbose_name='Стандарт')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'ВЧ sigma',
|
||||
'verbose_name_plural': 'ВЧ sigma',
|
||||
},
|
||||
),
|
||||
]
|
||||
20
dbapp/mainapp/migrations/0005_sigmaparameter_id_satellite.py
Normal file
20
dbapp/mainapp/migrations/0005_sigmaparameter_id_satellite.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-20 07:57
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainapp', '0004_sigmaparameter'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='sigmaparameter',
|
||||
name='id_satellite',
|
||||
field=models.ForeignKey(default=2, on_delete=django.db.models.deletion.PROTECT, related_name='sigmapar_sat', to='mainapp.satellite', verbose_name='Спутник'),
|
||||
preserve_default=False,
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-20 11:57
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainapp', '0005_sigmaparameter_id_satellite'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='objitem',
|
||||
name='id_satellite',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='parameter',
|
||||
name='id_satellite',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='parameters', to='mainapp.satellite', verbose_name='Спутник'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-20 11:58
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainapp', '0006_remove_objitem_id_satellite_parameter_id_satellite'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='parameter',
|
||||
name='id_satellite',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, related_name='parameters', to='mainapp.satellite', verbose_name='Спутник'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,38 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-22 11:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainapp', '0007_alter_parameter_id_satellite'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='geo',
|
||||
name='timestamp',
|
||||
field=models.DateTimeField(blank=True, db_index=True, null=True, verbose_name='Время'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='objitem',
|
||||
name='name',
|
||||
field=models.CharField(blank=True, db_index=True, max_length=100, null=True, verbose_name='Имя объекта'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='parameter',
|
||||
name='frequency',
|
||||
field=models.FloatField(blank=True, db_index=True, default=0, null=True, verbose_name='Частота, МГц'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='satellite',
|
||||
name='name',
|
||||
field=models.CharField(db_index=True, max_length=30, unique=True, verbose_name='Имя спутника'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='sigmaparameter',
|
||||
name='frequency',
|
||||
field=models.FloatField(blank=True, db_index=True, default=0, null=True, verbose_name='Частота, МГц'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-22 12:08
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainapp', '0008_alter_geo_timestamp_alter_objitem_name_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='parameter',
|
||||
name='id_sigma_parameter',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sigma_parameter', to='mainapp.sigmaparameter', verbose_name='ВЧ с sigma'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,31 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-22 12:38
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainapp', '0009_parameter_id_sigma_parameter'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SigmaParMark',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('mark', models.BooleanField(blank=True, null=True, verbose_name='Наличие сигнала')),
|
||||
('timestamp', models.DateTimeField(blank=True, null=True, verbose_name='Время')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Отметка',
|
||||
'verbose_name_plural': 'Отметки',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='sigmaparameter',
|
||||
name='mark',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='mainapp.sigmaparmark', verbose_name='Отметка'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,22 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-22 13:15
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainapp', '0010_sigmaparmark_sigmaparameter_mark'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='sigmaparameter',
|
||||
name='mark',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='sigmaparameter',
|
||||
name='mark',
|
||||
field=models.ManyToManyField(blank=True, null=True, to='mainapp.sigmaparmark', verbose_name='Отметка'),
|
||||
),
|
||||
]
|
||||
18
dbapp/mainapp/migrations/0012_alter_sigmaparameter_mark.py
Normal file
18
dbapp/mainapp/migrations/0012_alter_sigmaparameter_mark.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-22 13:16
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainapp', '0011_remove_sigmaparameter_mark_sigmaparameter_mark'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='sigmaparameter',
|
||||
name='mark',
|
||||
field=models.ManyToManyField(blank=True, to='mainapp.sigmaparmark', verbose_name='Отметка'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,21 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-22 13:48
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainapp', '0012_alter_sigmaparameter_mark'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddIndex(
|
||||
model_name='parameter',
|
||||
index=models.Index(fields=['id_satellite', 'frequency'], name='mainapp_par_id_sate_cbfab2_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='parameter',
|
||||
index=models.Index(fields=['frequency', 'polarization'], name='mainapp_par_frequen_75a049_idx'),
|
||||
),
|
||||
]
|
||||
18
dbapp/mainapp/migrations/0014_alter_modulation_name.py
Normal file
18
dbapp/mainapp/migrations/0014_alter_modulation_name.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-23 08:12
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainapp', '0013_parameter_mainapp_par_id_sate_cbfab2_idx_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='modulation',
|
||||
name='name',
|
||||
field=models.CharField(db_index=True, max_length=20, unique=True, verbose_name='Модуляция'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-23 09:40
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainapp', '0014_alter_modulation_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='parameter',
|
||||
name='id_sigma_parameter',
|
||||
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sigma_parameter', to='mainapp.sigmaparameter', verbose_name='ВЧ с sigma'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-23 09:58
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainapp', '0015_alter_parameter_id_sigma_parameter'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='parameter',
|
||||
name='id_sigma_parameter',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='sigmaparameter',
|
||||
name='parameter',
|
||||
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='sigma_parameter', to='mainapp.parameter', verbose_name='ВЧ с sigma'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-23 12:52
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainapp', '0016_remove_parameter_id_sigma_parameter_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='sigmaparameter',
|
||||
name='parameter',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='sigma_parameter', to='mainapp.parameter', verbose_name='ВЧ'),
|
||||
),
|
||||
]
|
||||
0
dbapp/mainapp/migrations/__init__.py
Normal file
0
dbapp/mainapp/migrations/__init__.py
Normal file
256
dbapp/mainapp/models.py
Normal file
256
dbapp/mainapp/models.py
Normal file
@@ -0,0 +1,256 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.gis.db import models as gis
|
||||
from django.contrib.gis.db.models import functions
|
||||
|
||||
def get_default_polarization():
|
||||
obj, created = Polarization.objects.get_or_create(
|
||||
name="-"
|
||||
)
|
||||
return obj.id
|
||||
|
||||
def get_default_modulation():
|
||||
obj, created = Modulation.objects.get_or_create(
|
||||
name="-"
|
||||
)
|
||||
return obj.id
|
||||
|
||||
def get_default_standard():
|
||||
obj, created = Standard.objects.get_or_create(
|
||||
name="-"
|
||||
)
|
||||
return obj.id
|
||||
|
||||
class CustomUser(models.Model):
|
||||
user = models.OneToOneField(User, on_delete=models.CASCADE)
|
||||
ROLE_CHOICES = [
|
||||
('admin', 'Администратор'),
|
||||
('moderator', 'Модератор'),
|
||||
('user', 'Пользователь'),
|
||||
]
|
||||
|
||||
role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='user', verbose_name='Роль пользователя')
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user.first_name} {self.user.last_name}" if self.user.first_name and self.user.last_name else self.user.username
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Пользователь"
|
||||
verbose_name_plural = "Пользователи"
|
||||
|
||||
class SigmaParMark(models.Model):
|
||||
mark = models.BooleanField(null=True, blank=True, verbose_name="Наличие сигнала")
|
||||
timestamp = models.DateTimeField(null=True, blank=True, verbose_name="Время")
|
||||
|
||||
def __str__(self):
|
||||
timestamp = self.timestamp.strftime("%d.%m.%Y %H:%M")
|
||||
return f'+ {timestamp}' if self.mark else f'- {timestamp}'
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Отметка"
|
||||
verbose_name_plural = "Отметки"
|
||||
|
||||
|
||||
class Mirror(models.Model):
|
||||
name = models.CharField(max_length=30, unique=True, verbose_name="Имя зеркала")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Зеркало"
|
||||
verbose_name_plural = "Зеркала"
|
||||
|
||||
class Polarization(models.Model):
|
||||
name = models.CharField(max_length=20, unique=True, verbose_name="Поляризация")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Поляризация"
|
||||
verbose_name_plural = "Поляризация"
|
||||
|
||||
|
||||
class Modulation(models.Model):
|
||||
name = models.CharField(max_length=20, unique=True, verbose_name="Модуляция", db_index=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Модуляция"
|
||||
verbose_name_plural = "Модуляции"
|
||||
|
||||
|
||||
class Standard(models.Model):
|
||||
name = models.CharField(max_length=20, unique=True, verbose_name="Стандарт")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Стандарт"
|
||||
verbose_name_plural = "Стандарты"
|
||||
|
||||
|
||||
class Satellite(models.Model):
|
||||
name = models.CharField(max_length=30, unique=True, verbose_name="Имя спутника", db_index=True)
|
||||
norad = models.IntegerField(blank=True, null=True, verbose_name="NORAD ID")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Спутник"
|
||||
verbose_name_plural = "Спутники"
|
||||
|
||||
|
||||
class Parameter(models.Model):
|
||||
id_satellite = models.ForeignKey(Satellite, on_delete=models.PROTECT, related_name="parameters", verbose_name="Спутник", null=True)
|
||||
polarization = models.ForeignKey(
|
||||
Polarization, default=get_default_polarization, on_delete=models.SET_DEFAULT, related_name="polarizations", null=True, blank=True, verbose_name="Поляризация"
|
||||
)
|
||||
frequency = models.FloatField(default=0, null=True, blank=True, verbose_name="Частота, МГц", db_index=True)
|
||||
freq_range = models.FloatField(default=0, null=True, blank=True, verbose_name="Полоса частот, МГц")
|
||||
bod_velocity = models.FloatField(default=0, null=True, blank=True, verbose_name="Символьная скорость, БОД")
|
||||
modulation = models.ForeignKey(
|
||||
Modulation, default=get_default_modulation, on_delete=models.SET_DEFAULT, related_name="modulations", null=True, blank=True, verbose_name="Модуляция"
|
||||
)
|
||||
snr = models.FloatField(default=0, null=True, blank=True, verbose_name="ОСШ")
|
||||
standard = models.ForeignKey(
|
||||
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_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)
|
||||
|
||||
|
||||
def __str__(self):
|
||||
polarization_name = self.polarization.name if self.polarization else "-"
|
||||
modulation_name = self.modulation.name if self.modulation else "-"
|
||||
return f"Источник-{self.frequency}:{self.freq_range} МГц:{polarization_name}:{modulation_name}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "ВЧ загрузка"
|
||||
verbose_name_plural = "ВЧ загрузки"
|
||||
indexes = [
|
||||
models.Index(fields=['id_satellite', 'frequency']),
|
||||
models.Index(fields=['frequency', 'polarization']),
|
||||
]
|
||||
# constraints = [
|
||||
# models.UniqueConstraint(
|
||||
# fields=[
|
||||
# 'polarization', 'frequency', 'freq_range',
|
||||
# 'bod_velocity', 'modulation', 'snr', 'standard'
|
||||
# ],
|
||||
# name='unique_parameter_combination'
|
||||
# )
|
||||
# ]
|
||||
|
||||
|
||||
class SigmaParameter(models.Model):
|
||||
id_satellite = models.ForeignKey(Satellite, on_delete=models.PROTECT, related_name="sigmapar_sat", verbose_name="Спутник")
|
||||
status = models.CharField(max_length=20, blank=True, null=True, verbose_name="Статус")
|
||||
frequency = models.FloatField(default=0, null=True, blank=True, verbose_name="Частота, МГц", db_index=True)
|
||||
freq_range = models.FloatField(default=0, null=True, blank=True, verbose_name="Полоса частот, МГц")
|
||||
power = models.FloatField(default=0, null=True, blank=True, verbose_name="Мощность, дБм")
|
||||
bod_velocity = models.FloatField(default=0, null=True, blank=True, verbose_name="Символьная скорость, БОД")
|
||||
modulation = models.ForeignKey(
|
||||
Modulation, default=get_default_modulation, on_delete=models.SET_DEFAULT, related_name="modulations_sigma", null=True, blank=True, verbose_name="Модуляция"
|
||||
)
|
||||
snr = models.FloatField(default=0, null=True, blank=True, verbose_name="ОСШ, Дб")
|
||||
standard = models.ForeignKey(
|
||||
Standard, default=get_default_standard, on_delete=models.SET_DEFAULT, related_name="standards_sigma", null=True, blank=True, verbose_name="Стандарт"
|
||||
)
|
||||
packets = models.BooleanField(null=True, blank=True, verbose_name="Пакетность")
|
||||
datetime_begin = models.DateTimeField(null=True, blank=True, verbose_name="Время начала измерения")
|
||||
datetime_end = models.DateTimeField(null=True, blank=True, verbose_name="Время окончания измерения")
|
||||
mark = models.ManyToManyField(SigmaParMark, verbose_name="Отметка", blank=True)
|
||||
parameter = models.ForeignKey(
|
||||
Parameter,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name='sigma_parameter',
|
||||
verbose_name="ВЧ",
|
||||
null=True,
|
||||
blank=True
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
modulation_name = self.modulation.name if self.modulation else "-"
|
||||
return f"Sigma-{self.frequency}:{self.freq_range} МГц:{modulation_name}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "ВЧ sigma"
|
||||
verbose_name_plural = "ВЧ sigma"
|
||||
|
||||
class Geo(models.Model):
|
||||
mirrors = models.ManyToManyField(Mirror, related_name="geo_mirrors", verbose_name="Зеркала",)
|
||||
timestamp = models.DateTimeField(null=True, blank=True, verbose_name="Время", db_index=True)
|
||||
coords = gis.PointField(srid=4326, null=True, blank=True, verbose_name="Координата геолокации")
|
||||
location = models.CharField(max_length=255, null=True, blank=True, verbose_name="Метоположение")
|
||||
comment = models.CharField(max_length=255, blank=True, verbose_name="Комментарий")
|
||||
coords_kupsat = gis.PointField(srid=4326, null=True, blank=True, verbose_name="Координаты Кубсата")
|
||||
coords_valid = gis.PointField(srid=4326, null=True, blank=True, verbose_name="Координаты оперативников")
|
||||
is_average = models.BooleanField(null=True, blank=True, verbose_name="Усреднённое")
|
||||
id_user_add = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, related_name="geos_added", verbose_name="Пользователь", null=True, blank=True)
|
||||
distance_coords_kup = models.GeneratedField(
|
||||
expression=functions.Distance("coords", "coords_kupsat")/1000,
|
||||
output_field=models.FloatField(),
|
||||
db_persist=True,
|
||||
null=True, blank=True, verbose_name="Расстояние между купсатом и гео, км"
|
||||
)
|
||||
distance_coords_valid = models.GeneratedField(
|
||||
expression=functions.Distance("coords", "coords_valid")/1000,
|
||||
output_field=models.FloatField(),
|
||||
db_persist=True,
|
||||
null=True, blank=True, verbose_name="Расстояние между гео и оперативным отделом, км"
|
||||
)
|
||||
distance_kup_valid = models.GeneratedField(
|
||||
expression=functions.Distance("coords_valid", "coords_kupsat")/1000,
|
||||
output_field=models.FloatField(),
|
||||
db_persist=True,
|
||||
null=True, blank=True, verbose_name="Расстояние между купсатом и оперативным отделом, км"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
longitude = self.coords.coords[0]
|
||||
latitude = self.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}, {self.location}"
|
||||
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Гео"
|
||||
verbose_name_plural = "Гео"
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
fields=[
|
||||
'timestamp', 'coords'
|
||||
],
|
||||
name='unique_geo_combination'
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
class ObjItem(models.Model):
|
||||
name = models.CharField(null=True, blank=True, max_length=100, verbose_name="Имя объекта", db_index=True)
|
||||
# id_satellite = models.ForeignKey(Satellite, on_delete=models.PROTECT, related_name="objitems", verbose_name="Спутник")
|
||||
id_vch_load = models.ForeignKey(Parameter, on_delete=models.CASCADE, related_name="objitems", verbose_name="ВЧ загрузка")
|
||||
id_geo = models.ForeignKey(Geo, on_delete=models.CASCADE, related_name="objitems", verbose_name="Геоданные")
|
||||
id_user_add = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, related_name="objitems", verbose_name="Пользователь", null=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"Объект {self.name}"
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Объект"
|
||||
verbose_name_plural = "Объекты"
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
fields=['id_vch_load', 'id_geo'],
|
||||
name='unique_objitem_combination'
|
||||
)
|
||||
]
|
||||
75
dbapp/mainapp/popup_filters.py
Normal file
75
dbapp/mainapp/popup_filters.py
Normal file
@@ -0,0 +1,75 @@
|
||||
from django.contrib.admin.filters import ChoicesFieldListFilter
|
||||
from django.forms import Media
|
||||
|
||||
|
||||
class PopupCompatibleMultiSelectRelatedDropdownFilter(ChoicesFieldListFilter):
|
||||
"""
|
||||
A custom filter that maintains popup context when used in raw_id_fields modals.
|
||||
"""
|
||||
|
||||
def __init__(self, field, request, params, model, model_admin, field_path):
|
||||
super().__init__(field, request, params, model, model_admin, field_path)
|
||||
|
||||
# Check if we're in a popup context
|
||||
self.is_popup = '_popup' in request.GET or 'pop' in request.GET or 'admin' not in request.path
|
||||
|
||||
# Get all choices (related objects)
|
||||
self.lookup_choices = field.get_choices(include_blank=False)
|
||||
|
||||
def has_output(self):
|
||||
return len(self.lookup_choices) > 1
|
||||
|
||||
def value(self):
|
||||
return self.lookup_val
|
||||
|
||||
def expected_parameters(self):
|
||||
return [self.lookup_kwarg, self.lookup_kwarg_isnull]
|
||||
|
||||
def choices(self, changelist):
|
||||
# If in popup, preserve the popup parameters in the filter URL
|
||||
popup_params = {}
|
||||
if self.is_popup:
|
||||
# Preserve popup parameters
|
||||
if '_popup' in changelist.params:
|
||||
popup_params['_popup'] = 1
|
||||
if 'pop' in changelist.params:
|
||||
popup_params['pop'] = changelist.params['pop']
|
||||
if '_to_field' in changelist.params:
|
||||
popup_params['_to_field'] = changelist.params['_to_field']
|
||||
|
||||
# Create the base URL with popup parameters
|
||||
all_params = changelist.get_filters_params()
|
||||
all_params.update(popup_params)
|
||||
|
||||
# Generate the URL for the filter
|
||||
url = changelist.get_query_string(all_params, [self.lookup_kwarg])
|
||||
|
||||
yield {
|
||||
'selected': self.lookup_val is None,
|
||||
'query_string': url,
|
||||
'display': 'All',
|
||||
}
|
||||
|
||||
# Add choices
|
||||
for lookup, title in self.lookup_choices:
|
||||
params = dict(all_params)
|
||||
params[self.lookup_kwarg] = lookup
|
||||
|
||||
# Remove the parameter if it's being set to the same value (for unselecting)
|
||||
if self.lookup_val == str(lookup):
|
||||
params.pop(self.lookup_kwarg, None)
|
||||
|
||||
# Add popup parameters to each choice URL
|
||||
choice_params = params.copy()
|
||||
choice_params.update(popup_params)
|
||||
|
||||
yield {
|
||||
'selected': str(lookup) == self.lookup_val,
|
||||
'query_string': changelist.get_query_string(choice_params, [self.lookup_kwarg_isnull]),
|
||||
'display': title,
|
||||
}
|
||||
|
||||
@property
|
||||
def media(self):
|
||||
# Include necessary CSS/JS for dropdown functionality if needed
|
||||
return Media()
|
||||
60
dbapp/mainapp/templates/admin/map_custom.html
Normal file
60
dbapp/mainapp/templates/admin/map_custom.html
Normal file
@@ -0,0 +1,60 @@
|
||||
{% extends "mapsapp/map2d_base.html" %}
|
||||
{% load static %}
|
||||
{% block title %}Вынос точек{% endblock title %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
// Цвета для стандартных маркеров (из leaflet-color-markers)
|
||||
var markerColors = ['red', 'green', 'orange', 'violet', 'grey', 'black', 'blue'];
|
||||
var getColorIcon = function(color) {
|
||||
return L.icon({
|
||||
iconUrl: '{% static "leaflet-markers/img/marker-icon-" %}' + color + '.png',
|
||||
shadowUrl: '{% static "leaflet-markers/img/marker-shadow.png" %}',
|
||||
iconSize: [25, 41],
|
||||
iconAnchor: [12, 41],
|
||||
popupAnchor: [1, -34],
|
||||
shadowSize: [41, 41]
|
||||
});
|
||||
};
|
||||
|
||||
var overlays = [];
|
||||
|
||||
{% for group in groups %}
|
||||
var groupIndex = {{ forloop.counter0 }};
|
||||
var colorName = markerColors[groupIndex % markerColors.length];
|
||||
var groupIcon = getColorIcon(colorName);
|
||||
|
||||
var groupLayer = L.layerGroup();
|
||||
|
||||
var subgroup = [];
|
||||
{% for point_data in group.points %}
|
||||
var pointName = "{{ group.name|escapejs }}";
|
||||
|
||||
var marker = L.marker([{{ point_data.point.1|safe }}, {{ point_data.point.0|safe }}], {
|
||||
icon: groupIcon
|
||||
}).bindPopup(pointName);
|
||||
|
||||
groupLayer.addLayer(marker);
|
||||
|
||||
subgroup.push({
|
||||
label: "{{ forloop.counter }} - {{ point_data.frequency }}",
|
||||
layer: marker
|
||||
});
|
||||
{% endfor %}
|
||||
|
||||
overlays.push({
|
||||
label: '{{ group.name|escapejs }}',
|
||||
selectAllCheckbox: true,
|
||||
children: subgroup,
|
||||
layer: groupLayer
|
||||
});
|
||||
{% endfor %}
|
||||
|
||||
|
||||
// Используем именно tree-контрол
|
||||
L.control.layers.tree(baseLayers, overlays, {
|
||||
collapsed: false,
|
||||
autoZIndex: true
|
||||
}).addTo(map);
|
||||
</script>
|
||||
{% endblock extra_js %}
|
||||
48
dbapp/mainapp/templates/mainapp/add_data_from_csv.html
Normal file
48
dbapp/mainapp/templates/mainapp/add_data_from_csv.html
Normal file
@@ -0,0 +1,48 @@
|
||||
{% extends 'mainapp/base.html' %}
|
||||
|
||||
{% block title %}Загрузка данных из CSV{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h2 class="mb-0">Загрузка данных из CSV</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert {{ message.tags }} alert-dismissible fade show" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<p class="card-text">Загрузите CSV-файл для загрузки данных в базу.</p>
|
||||
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Form fields with Bootstrap styling -->
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.file.id_for_label }}" class="form-label">Выберите CSV файл:</label>
|
||||
{{ form.file }}
|
||||
{% if form.file.errors %}
|
||||
<div class="text-danger mt-1">{{ form.file.errors }}</div>
|
||||
{% endif %}
|
||||
<div class="form-text">Загрузите CSV-файл с данными для обработки</div>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||
<a href="{% url 'home' %}" class="btn btn-secondary me-md-2">Назад</a>
|
||||
<button type="submit" class="btn btn-success">Добавить в базу</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
65
dbapp/mainapp/templates/mainapp/add_data_from_excel.html
Normal file
65
dbapp/mainapp/templates/mainapp/add_data_from_excel.html
Normal file
@@ -0,0 +1,65 @@
|
||||
{% extends 'mainapp/base.html' %}
|
||||
|
||||
{% block title %}Загрузка данных из Excel{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h2 class="mb-0">Загрузка данных из Excel</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert {{ message.tags }} alert-dismissible fade show" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<p class="card-text">Загрузите Excel-файл и выберите спутник для загрузки данных в базу.</p>
|
||||
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Form fields with Bootstrap styling -->
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.file.id_for_label }}" class="form-label">Выберите Excel файл:</label>
|
||||
{{ form.file }}
|
||||
{% if form.file.errors %}
|
||||
<div class="text-danger mt-1">{{ form.file.errors }}</div>
|
||||
{% endif %}
|
||||
<div class="form-text">Загрузите Excel-файл (.xlsx или .xls) с данными для обработки</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.sat_choice.id_for_label }}" class="form-label">Выберите спутник:</label>
|
||||
{{ form.sat_choice }}
|
||||
{% if form.sat_choice.errors %}
|
||||
<div class="text-danger mt-1">{{ form.sat_choice.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.number_input.id_for_label }}" class="form-label">Количество строк для обработки:</label>
|
||||
{{ form.number_input }}
|
||||
{% if form.number_input.errors %}
|
||||
<div class="text-danger mt-1">{{ form.number_input.errors }}</div>
|
||||
{% endif %}
|
||||
<div class="form-text">Оставьте пустым или введите 0 для обработки всех строк</div>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||
<a href="{% url 'home' %}" class="btn btn-secondary me-md-2">Назад</a>
|
||||
<button type="submit" class="btn btn-primary">Добавить в базу</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
53
dbapp/mainapp/templates/mainapp/base.html
Normal file
53
dbapp/mainapp/templates/mainapp/base.html
Normal file
@@ -0,0 +1,53 @@
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="{% static 'favicon.ico' %}" type="image/x-icon">
|
||||
<title>{% block title %}Геолокация{% endblock %}</title>
|
||||
|
||||
<link href="{% static 'bootstrap/bootstrap.min.css' %}" rel="stylesheet">
|
||||
|
||||
<!-- Дополнительные стили (если нужно) -->
|
||||
{% block extra_css %}{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Навигационная панель -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="{% url 'home' %}">Геолокация</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav me-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'home' %}">Главная</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url '3dmap' %}">3D карта</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url '2dmap' %}">2D карта</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'admin:index' %}">Админ панель</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Основной контент -->
|
||||
<main class="container mt-4">
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</main>
|
||||
|
||||
<script src="{% static 'bootstrap/bootstrap.bundle.min.js' %}"></script>
|
||||
|
||||
{% block extra_js %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
176
dbapp/mainapp/templates/mainapp/home.html
Normal file
176
dbapp/mainapp/templates/mainapp/home.html
Normal file
@@ -0,0 +1,176 @@
|
||||
{% extends 'mainapp/base.html' %}
|
||||
|
||||
{% block title %}Главная{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="text-center mb-5">
|
||||
<h1 class="display-4 fw-bold">Геолокация</h1>
|
||||
<p class="lead">Управление данными спутников</p>
|
||||
</div>
|
||||
|
||||
<!-- Alert messages -->
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert {{ message.tags }} alert-dismissible fade show" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<!-- Main feature cards -->
|
||||
<div class="row g-4">
|
||||
<!-- Excel Data Upload Card -->
|
||||
<div class="col-lg-6">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="bg-primary bg-opacity-10 rounded-circle p-2 me-3">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-file-earmark-excel text-primary" viewBox="0 0 16 16">
|
||||
<path d="M5.884 6.68a.5.5 0 1 0-.768.64L7.349 10l-2.233 2.68a.5.5 0 0 0 .768.64L8 10.781l2.116 2.54a.5.5 0 0 0 .768-.641L8.651 10l2.233-2.68a.5.5 0 0 0-.768-.64L8 9.219z"/>
|
||||
<path d="M14 14V4.5L9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2M9.5 3A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h5.5z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="card-title mb-0">Загрузка данных из Excel</h3>
|
||||
</div>
|
||||
<p class="card-text">Загрузите данные из Excel-файла в базу данных. Поддерживается выбор спутника и ограничение количества записей.</p>
|
||||
<a href="{% url 'load_excel_data' %}" class="btn btn-primary">
|
||||
Перейти к загрузке данных
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CSV Data Upload Card -->
|
||||
<div class="col-lg-6">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="bg-success bg-opacity-10 rounded-circle p-2 me-3">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-file-earmark-text text-success" viewBox="0 0 16 16">
|
||||
<path d="M5.5 7a.5.5 0 0 0 0 1h5a.5.5 0 0 0 0-1zm0 2a.5.5 0 0 0 0 1h2a.5.5 0 0 0 0-1z"/>
|
||||
<path d="M9.5 0H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V4.5L9.5 0m0 1v2A1.5 1.5 0 0 0 11 4.5h2V14a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="card-title mb-0">Загрузка данных из CSV</h3>
|
||||
</div>
|
||||
<p class="card-text">Загрузите данные из CSV-файла в базу данных. Простая загрузка с возможностью указания пути к файлу.</p>
|
||||
<a href="{% url 'load_csv_data' %}" class="btn btn-success">
|
||||
Перейти к загрузке данных
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Satellite List Card -->
|
||||
<div class="col-lg-6">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="bg-info bg-opacity-10 rounded-circle p-2 me-3">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-satellite text-info" viewBox="0 0 16 16">
|
||||
<path d="M13.37 1.37c-2.75 0-5.4 1.13-7.29 3.02C4.13 6.33 3 8.98 3 11.73c0 2.75 1.13 5.4 3.02 7.29 1.94 1.94 4.54 3.02 7.29 3.02 2.75 0 5.4-1.13 7.29-3.02 1.94-1.94 3.02-4.54 3.02-7.29 0-2.75-1.13-5.4-3.02-7.29C18.77 2.5-2.75 1.37-5.5 1.37m-5.5 8.26c0-1.52.62-3.02 1.73-4.13 1.11-1.11 2.61-1.73 4.13-1.73 1.52 0 3.02.62 4.13 1.73 1.11 1.11 1.73 2.61 1.73 4.13 0 1.52-.62 3.02-1.73 4.13-1.11 1.11-2.61 1.73-4.13 1.73-1.52 0-3.02-.62-4.13-1.73-1.11-1.11-1.73-2.61-1.73-4.13"/>
|
||||
<path d="M6.63 6.63c.62-.62 1.45-.98 2.27-.98.82 0 1.65.36 2.27.98.62.62.98 1.45.98 2.27 0 .82-.36 1.65-.98 2.27-.62.62-1.45.98-2.27.98-.82 0-1.65-.36-2.27-.98-.62-.62-.98-1.45-.98-2.27 0-.82.36-1.65.98-2.27m2.27 1.02c-.26 0-.52.1-.71.29-.2.2-.29.46-.29.71 0 .26.1.52.29.71.2.2.46.29.71.29.26 0 .52-.1.71-.29.2-.2.29-.46.29-.71 0-.26-.1-.52-.29-.71-.19-.19-.45-.29-.71-.29"/>
|
||||
<path d="M5.13 5.13c.46-.46 1.08-.73 1.73-.73.65 0 1.27.27 1.73.73.46.46.73 1.08.73 1.73 0 .65-.27 1.27-.73 1.73-.46.46-1.08.73-1.73.73-.65 0-1.27-.27-1.73-.73-.46-.46-.73-1.08-.73-1.73 0-.65.27-1.27.73-1.73m1.73.58c-.15 0-.3.06-.42.18-.12.12-.18.27-.18.42 0 .15.06.3.18.42.12.12.27.18.42.18.15 0 .3-.06.42-.18.12-.12.18-.27.18-.42 0-.15-.06-.3-.18-.42-.12-.12-.27-.18-.42-.18"/>
|
||||
<path d="M8 3.5c.28 0 .5.22.5.5v1c0 .28-.22.5-.5.5s-.5-.22-.5-.5v-1c0-.28.22-.5.5-.5"/>
|
||||
<path d="M10.5 8c0-.28.22-.5.5-.5h1c.28 0 .5.22.5.5s-.22.5-.5.5h-1c-.28 0-.5-.22-.5-.5"/>
|
||||
<path d="M8 12.5c-.28 0-.5.22-.5.5v1c0 .28.22.5.5.5s.5-.22.5-.5v-1c0-.28-.22-.5-.5-.5"/>
|
||||
<path d="M3.5 8c0 .28-.22.5-.5.5h-1c-.28 0-.5-.22-.5-.5s.22-.5.5-.5h1c.28 0 .5.22.5.5"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="card-title mb-0">Добавление списка спутников</h3>
|
||||
</div>
|
||||
<p class="card-text">Добавьте новый список спутников в базу данных для последующего использования в загрузке данных.</p>
|
||||
<a href="{% url 'add_sats' %}" class="btn btn-info">
|
||||
Добавить список спутников
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Transponders Card -->
|
||||
<div class="col-lg-6">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="bg-warning bg-opacity-10 rounded-circle p-2 me-3">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-wifi text-warning" viewBox="0 0 16 16">
|
||||
<path d="M6.002 3.5a5.5 5.5 0 1 1 3.996 9.5H10A5.5 5.5 0 0 1 6.002 3.5M6.002 5.5a3.5 3.5 0 1 0 3.996 5.5H10A3.5 3.5 0 0 0 6.002 5.5"/>
|
||||
<path d="M10.5 12.5a.5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5.5.5 0 0 0-1 0 .5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5.5.5 0 0 0-1 0 .5.5 0 0 1-.5.5h-1a.5.5 0 0 1-.5-.5 3.5 3.5 0 0 1 7 0"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="card-title mb-0">Добавление транспондеров</h3>
|
||||
</div>
|
||||
<p class="card-text">Добавьте список транспондеров из JSON-файла в базу данных. Требуется наличие файла transponders.json.</p>
|
||||
<a href="{% url 'add_trans' %}" class="btn btn-warning">
|
||||
Добавить транспондеры
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- VCH Load Data Card -->
|
||||
<div class="col-lg-6">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="bg-danger bg-opacity-10 rounded-circle p-2 me-3">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-upload text-danger" viewBox="0 0 16 16">
|
||||
<path d="M.5 9.9a.5.5 0 0 1 .5.5v2.5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-2.5a.5.5 0 0 1 1 0v2.5a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2v-2.5a.5.5 0 0 1 .5-.5"/>
|
||||
<path d="M7.646 1.146a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8.5 2.707V11.5a.5.5 0 0 1-1 0V2.707L5.354 4.854a.5.5 0 1 1-.708-.708z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="card-title mb-0">Добавление данных ВЧ загрузки</h3>
|
||||
</div>
|
||||
<p class="card-text">Загрузите данные ВЧ загрузки из HTML-файла с таблицами. Поддерживается выбор спутника для привязки данных.</p>
|
||||
<a href="{% url 'vch_load' %}" class="btn btn-danger">
|
||||
Добавить данные ВЧ загрузки
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Map Views Card -->
|
||||
<div class="col-lg-6">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="bg-secondary bg-opacity-10 rounded-circle p-2 me-3">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-map text-secondary" viewBox="0 0 16 16">
|
||||
<path d="M15.817.113A.5.5 0 0 1 16 .5v14a.5.5 0 0 1-.402.49l-5 1a.502.502 0 0 1-.196 0L5.5 15.01l-4.902.98A.5.5 0 0 1 0 15.5v-14a.5.5 0 0 1 .402-.49l5-1a.5.5 0 0 1 .196 0L10.5.99l4.902-.98a.5.5 0 0 1 .415.103M10 1.91l-4-.8v12.98l4 .8zM1.61 2.22l4.39.88v10.88l-4.39-.88zm9.18 10.88 4-.8V2.34l-4 .8z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="card-title mb-0">Карты</h3>
|
||||
</div>
|
||||
<p class="card-text">Просматривайте данные на 2D и 3D картах для визуализации геолокации спутников.</p>
|
||||
<div class="mt-2">
|
||||
<a href="{% url '2dmap' %}" class="btn btn-secondary me-2">2D Карта</a>
|
||||
<a href="{% url '3dmap' %}" class="btn btn-outline-secondary">3D Карта</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Calculation Card -->
|
||||
<div class="col-lg-6">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div class="bg-info bg-opacity-10 rounded-circle p-2 me-3">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-calculator text-info" viewBox="0 0 16 16">
|
||||
<path d="M2 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v4h2V2a1 1 0 0 0-1-1M5 6v1h1V6zm2 0v1h1V6zm2 0v1h1V6zm2 0v1h1V6zm1 2v1h1V8zm0 2v1h1v-1zm0 2v1h1v-1zm-8-6v8H3V8zm2 0v8h1V8zm2 0v8h1V8zm2 0v8h1V8z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="card-title mb-0">Привязка ВЧ загрузки</h3>
|
||||
</div>
|
||||
<p class="card-text">Привязка ВЧ загрузки с sigma</p>
|
||||
<a href="{% url 'link_vch_sigma' %}" class="btn btn-info">
|
||||
Открыть форму
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
68
dbapp/mainapp/templates/mainapp/link_vch.html
Normal file
68
dbapp/mainapp/templates/mainapp/link_vch.html
Normal file
@@ -0,0 +1,68 @@
|
||||
{% extends 'mainapp/base.html' %}
|
||||
|
||||
{% block title %}Привязка ВЧ{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-6">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-info text-white">
|
||||
<h2 class="mb-0">Привязка ВЧ загрузки</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert {{ message.tags }} alert-dismissible fade show" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<p class="card-text">Введите допустимый разброс для частоты и полосы(в кГц)</p>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.sat_choice.id_for_label }}" class="form-label">Выберите спутник:</label>
|
||||
{{ form.sat_choice }}
|
||||
{% if form.sat_choice.errors %}
|
||||
<div class="text-danger mt-1">{{ form.sat_choice.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.ku_range.id_for_label }}" class="form-label">Выберите перенос по частоте(МГц):</label>
|
||||
{{ form.ku_range }}
|
||||
{% if form.ku_range.errors %}
|
||||
<div class="text-danger mt-1">{{ form.ku_range.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.value1.id_for_label }}" class="form-label">Разброс по частоте(в %)</label>
|
||||
{{ form.value1 }}
|
||||
{% if form.value1.errors %}
|
||||
<div class="text-danger mt-1">{{ form.value1.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.value2.id_for_label }}" class="form-label">Разброс по полосе(в %)</label>
|
||||
{{ form.value2 }}
|
||||
{% if form.value2.errors %}
|
||||
<div class="text-danger mt-1">{{ form.value2.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||
<a href="{% url 'home' %}" class="btn btn-secondary me-md-2">Назад</a>
|
||||
{% comment %} <a href="{% url 'home' %}" class="btn btn-danger me-md-2">Сбросить привязку</a> {% endcomment %}
|
||||
<button type="submit" class="btn btn-info">Выполнить привязку</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
56
dbapp/mainapp/templates/mainapp/upload_html.html
Normal file
56
dbapp/mainapp/templates/mainapp/upload_html.html
Normal file
@@ -0,0 +1,56 @@
|
||||
{% extends 'mainapp/base.html' %}
|
||||
|
||||
{% block title %}Загрузка данных ВЧ загрузки{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-danger text-white">
|
||||
<h2 class="mb-0">Загрузка данных ВЧ загрузки</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert {{ message.tags }} alert-dismissible fade show" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<p class="card-text">Загрузите HTML-файл с таблицами данных ВЧ загрузки и выберите спутник для привязки данных.</p>
|
||||
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Form fields with Bootstrap styling -->
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.file.id_for_label }}" class="form-label">Выберите HTML файл:</label>
|
||||
{{ form.file }}
|
||||
{% if form.file.errors %}
|
||||
<div class="text-danger mt-1">{{ form.file.errors }}</div>
|
||||
{% endif %}
|
||||
<div class="form-text">Загрузите HTML-файл, содержащий таблицы с данными ВЧ загрузки</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.sat_choice.id_for_label }}" class="form-label">Выберите спутник:</label>
|
||||
{{ form.sat_choice }}
|
||||
{% if form.sat_choice.errors %}
|
||||
<div class="text-danger mt-1">{{ form.sat_choice.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
|
||||
<a href="{% url 'home' %}" class="btn btn-secondary me-md-2">Назад</a>
|
||||
<button type="submit" class="btn btn-danger">Обработать файл</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
3
dbapp/mainapp/tests.py
Normal file
3
dbapp/mainapp/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
20
dbapp/mainapp/urls.py
Normal file
20
dbapp/mainapp/urls.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
from django.urls import path
|
||||
from . import views
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.home_page, name='home'),
|
||||
path('excel-data', views.load_excel_data, name='load_excel_data'),
|
||||
path('satellites', views.add_satellites, name='add_sats'),
|
||||
path('api/locations/<int:sat_id>/geojson/', views.get_locations, name='locations_by_id'),
|
||||
path('transponders', views.add_transponders, name='add_trans'),
|
||||
path('csv-data', views.load_raw_csv_data, name='load_csv_data'),
|
||||
path('map-points/', views.show_map_view, name='admin_show_map'),
|
||||
path('cluster/', views.cluster_test, name='cluster'),
|
||||
path('vch-upload/', views.upload_vch_load_from_html, name='vch_load'),
|
||||
path('vch-link/', views.link_vch_sigma, name='link_vch_sigma'),
|
||||
# path('upload/', views.upload_file, name='upload_file'),
|
||||
|
||||
]
|
||||
415
dbapp/mainapp/utils.py
Normal file
415
dbapp/mainapp/utils.py
Normal file
@@ -0,0 +1,415 @@
|
||||
from .models import (
|
||||
Satellite,
|
||||
Standard,
|
||||
Polarization,
|
||||
Mirror,
|
||||
Modulation,
|
||||
Geo,
|
||||
Parameter,
|
||||
SigmaParameter,
|
||||
ObjItem,
|
||||
CustomUser
|
||||
)
|
||||
from datetime import datetime, time
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from django.contrib.gis.geos import Point
|
||||
import json
|
||||
import re
|
||||
|
||||
def get_all_constants():
|
||||
sats = [sat.name for sat in Satellite.objects.all()]
|
||||
standards = [sat.name for sat in Standard.objects.all()]
|
||||
pols = [sat.name for sat in Polarization.objects.all()]
|
||||
mirrors = [sat.name for sat in Mirror.objects.all()]
|
||||
modulations = [sat.name for sat in Modulation.objects.all()]
|
||||
return sats, standards, pols, mirrors, modulations
|
||||
|
||||
def coords_transform(coords: str):
|
||||
lat_part, lon_part = coords.strip().split()
|
||||
sign_map = {'N': 1, 'E': 1, 'S': -1, 'W': -1}
|
||||
|
||||
lat_sign_char = lat_part[-1]
|
||||
lat_value = float(lat_part[:-1].replace(",", "."))
|
||||
latitude = lat_value * sign_map.get(lat_sign_char, 1)
|
||||
|
||||
lon_sign_char = lon_part[-1]
|
||||
lon_value = float(lon_part[:-1].replace(",", "."))
|
||||
longitude = lon_value * sign_map.get(lon_sign_char, 1)
|
||||
|
||||
return (longitude, latitude)
|
||||
|
||||
def remove_str(s: str):
|
||||
if isinstance(s, str):
|
||||
if s.strip() == "-" or s.strip() == "" or s.strip() == " " or "неизв" in s.strip():
|
||||
return -1
|
||||
return float(s.strip().replace(",", "."))
|
||||
return s
|
||||
|
||||
def fill_data_from_df(df: pd.DataFrame, sat: Satellite):
|
||||
try:
|
||||
df.rename(columns={'Модуляция ': 'Модуляция'}, inplace=True)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
consts = get_all_constants()
|
||||
df.fillna(-1, inplace=True)
|
||||
for stroka in df.iterrows():
|
||||
geo_point = Point(coords_transform(stroka[1]['Координаты']), srid=4326)
|
||||
valid_point = None
|
||||
kupsat_point = None
|
||||
try:
|
||||
if stroka[1]['Координаты объекта'] != -1 and stroka[1]['Координаты Кубсата'] != '+':
|
||||
if 'ИРИ' not in stroka[1]['Координаты объекта'] and 'БЛА' not in stroka[1]['Координаты объекта']:
|
||||
valid_point = list(map(float, stroka[1]['Координаты объекта'].replace(',', '.').split('. ')))
|
||||
valid_point = Point(valid_point[1], valid_point[0], srid=4326)
|
||||
if stroka[1]['Координаты Кубсата'] != -1 and stroka[1]['Координаты Кубсата'] != '+':
|
||||
kupsat_point = list(map(float, stroka[1]['Координаты Кубсата'].replace(',', '.').split('. ')))
|
||||
kupsat_point = Point(kupsat_point[1], kupsat_point[0], srid=4326)
|
||||
except KeyError:
|
||||
print("В таблице нет столбцов с координатами кубсата")
|
||||
try:
|
||||
polarization_obj, _ = Polarization.objects.get_or_create(name=stroka[1]['Поляризация'].strip())
|
||||
except KeyError:
|
||||
polarization_obj, _ = Polarization.objects.get_or_create(name="-")
|
||||
freq = remove_str(stroka[1]['Частота, МГц'])
|
||||
freq_line = remove_str(stroka[1]['Полоса, МГц'])
|
||||
v = remove_str(stroka[1]['Символьная скорость, БОД'])
|
||||
mod_obj, _ = Modulation.objects.get_or_create(name=stroka[1]['Модуляция'].strip())
|
||||
snr = remove_str(stroka[1]['ОСШ'])
|
||||
date = stroka[1]['Дата'].date()
|
||||
time_ = stroka[1]['Время']
|
||||
if isinstance(time_, str):
|
||||
time_ = time(0,0,0)
|
||||
timestamp = datetime.combine(date, time_)
|
||||
current_mirrors = []
|
||||
mirror_1 = stroka[1]['Зеркало 1'].strip().split("\n")
|
||||
mirror_2 = stroka[1]['Зеркало 2'].strip().split("\n")
|
||||
if len(mirror_1) > 1:
|
||||
for mir in mirror_1:
|
||||
mir_obj, _ = Mirror.objects.get_or_create(name=mir.strip())
|
||||
current_mirrors.append(mir.strip())
|
||||
elif mirror_1[0] not in consts[3]:
|
||||
mir_obj, _ = Mirror.objects.get_or_create(name=mirror_1[0].strip())
|
||||
current_mirrors.append(mirror_1[0].strip())
|
||||
if len(mirror_2) > 1:
|
||||
for mir in mirror_2:
|
||||
mir_obj, _ = Mirror.objects.get_or_create(name=mir.strip())
|
||||
current_mirrors.append(mir.strip())
|
||||
elif mirror_2[0] not in consts[3]:
|
||||
mir_obj, _ = Mirror.objects.get_or_create(name=mirror_2[0].strip())
|
||||
current_mirrors.append(mirror_2[0].strip())
|
||||
location = stroka[1]['Местоопределение'].strip()
|
||||
comment = stroka[1]['Комментарий']
|
||||
source = stroka[1]['Объект наблюдения']
|
||||
|
||||
vch_load_obj, vch_created = Parameter.objects.get_or_create(
|
||||
id_satellite=sat,
|
||||
polarization=polarization_obj,
|
||||
frequency=freq,
|
||||
freq_range=freq_line,
|
||||
bod_velocity=v,
|
||||
modulation=mod_obj,
|
||||
snr=snr,
|
||||
defaults={'id_user_add': CustomUser.objects.get(id=1)}
|
||||
)
|
||||
|
||||
geo, _ = Geo.objects.get_or_create(
|
||||
timestamp=timestamp,
|
||||
coords=geo_point,
|
||||
defaults={
|
||||
'coords_kupsat': kupsat_point,
|
||||
'coords_valid': valid_point,
|
||||
'location': location,
|
||||
'comment': comment,
|
||||
'is_average': (comment != -1.0),
|
||||
'id_user_add': CustomUser.objects.get(id=1)
|
||||
}
|
||||
)
|
||||
geo.save()
|
||||
geo.mirrors.set(Mirror.objects.filter(name__in=current_mirrors))
|
||||
|
||||
obj_item, _ = ObjItem.objects.get_or_create(
|
||||
id_geo=geo,
|
||||
id_vch_load=vch_load_obj,
|
||||
defaults={
|
||||
'name': source,
|
||||
'id_user_add': CustomUser.objects.get(id=1),
|
||||
# 'id_satellite': sat
|
||||
}
|
||||
)
|
||||
|
||||
obj_item.save()
|
||||
|
||||
|
||||
def add_satellite_list():
|
||||
sats = ['AZERSPACE 2', 'Amos 4', 'Astra 4A', 'ComsatBW-1', 'Eutelsat 16A',
|
||||
'Eutelsat 21B', 'Eutelsat 7B', 'ExpressAM6', 'Hellas Sat 3',
|
||||
'Intelsat 39', 'Intelsat 17',
|
||||
'NSS 12', 'Sicral 2', 'SkyNet 5B', 'SkyNet 5D', 'Syracuse 4A',
|
||||
'Turksat 3A', 'Turksat 4A', 'WGS 10', 'Yamal 402']
|
||||
|
||||
for sat in sats:
|
||||
sat_obj, _ = Satellite.objects.get_or_create(
|
||||
name=sat
|
||||
)
|
||||
sat_obj.save()
|
||||
|
||||
def parse_string(s: str):
|
||||
pattern = r'^(.+?) (-?\d+\,\d+) \[(-?\d+\,\d+)\] ([^\s]+) ([A-Za-z]) - (\d{1,2}\.\d{1,2}\.\d{1,4} \d{1,2}:\d{1,2}:\d{1,2})$'
|
||||
match = re.match(pattern, s)
|
||||
if match:
|
||||
return list(match.groups())
|
||||
else:
|
||||
raise ValueError("Некорректный формат строки")
|
||||
|
||||
|
||||
def get_point_from_json(filepath: str):
|
||||
with open(filepath, encoding='utf-8-sig') as jf:
|
||||
data = json.load(jf)
|
||||
|
||||
for obj in data:
|
||||
if not obj.get('bearingBehavior', {}):
|
||||
if obj['tacticObjectType'] == "source":
|
||||
# if not obj['bearingBehavior']:
|
||||
source_id = obj['id']
|
||||
name = obj['name']
|
||||
elements = parse_string(name)
|
||||
sat_name = elements[0]
|
||||
freq = elements[1]
|
||||
freq_range = elements[2]
|
||||
pol = elements[4]
|
||||
timestamp = datetime.strptime(elements[-1], '%d.%m.%y %H:%M:%S')
|
||||
lat = None
|
||||
lon = None
|
||||
for pos in data:
|
||||
if pos["id"] == source_id and pos["tacticObjectType"] == "position":
|
||||
lat = pos["latitude"]
|
||||
lon = pos["longitude"]
|
||||
break
|
||||
print(f"Name - {sat_name}, f - {freq}, f range - {freq_range}, pol - {pol} "
|
||||
f"time - {timestamp}, pos - ({lat}, {lon})")
|
||||
|
||||
|
||||
|
||||
def get_points_from_csv(file_content):
|
||||
import io
|
||||
if hasattr(file_content, 'read'):
|
||||
content = file_content.read()
|
||||
if isinstance(content, bytes):
|
||||
content = content.decode('utf-8')
|
||||
else:
|
||||
if isinstance(file_content, bytes):
|
||||
content = content.decode('utf-8')
|
||||
else:
|
||||
content = file_content
|
||||
df = pd.read_csv(io.StringIO(content), sep=";",
|
||||
names=['id', 'obj', 'lat', 'lon', 'h', 'time', 'sat', 'norad_id', 'freq', 'f_range', 'et', 'qaul', 'mir_1', 'mir_2', 'mir_3'])
|
||||
df[['lat', 'lon', 'freq', 'f_range']] = df[['lat', 'lon', 'freq', 'f_range']].replace(',', '.', regex=True).astype(float)
|
||||
df['time'] = pd.to_datetime(df['time'], format='%d.%m.%Y %H:%M:%S')
|
||||
for row in df.iterrows():
|
||||
row = row[1]
|
||||
match row['obj'].split(' ')[-1]:
|
||||
case 'V':
|
||||
pol = 'Вертикальная'
|
||||
case 'H':
|
||||
pol = 'Горизонтальная'
|
||||
case 'R':
|
||||
pol = 'Правая'
|
||||
case 'L':
|
||||
pol = 'Левая'
|
||||
case _:
|
||||
pol = '-'
|
||||
pol_obj, _ = Polarization.objects.get_or_create(
|
||||
name=pol
|
||||
)
|
||||
sat_obj, _ = Satellite.objects.get_or_create(
|
||||
name=row['sat'],
|
||||
defaults={'norad': row['norad_id']}
|
||||
)
|
||||
mir_1_obj, _ = Mirror.objects.get_or_create(
|
||||
name=row['mir_1']
|
||||
)
|
||||
mir_2_obj, _ = Mirror.objects.get_or_create(
|
||||
name=row['mir_2']
|
||||
)
|
||||
mir_lst = [row['mir_1'], row['mir_2']]
|
||||
if not pd.isna(row['mir_3']):
|
||||
mir_3_obj, _ = Mirror.objects.get_or_create(
|
||||
|
||||
name=row['mir_3']
|
||||
)
|
||||
vch_load_obj, _ = Parameter.objects.get_or_create(
|
||||
id_satellite=sat_obj,
|
||||
polarization=pol_obj,
|
||||
frequency=row['freq'],
|
||||
freq_range=row['f_range'],
|
||||
defaults={'id_user_add': CustomUser.objects.get(id=1)}
|
||||
)
|
||||
|
||||
geo_obj, _ = Geo.objects.get_or_create(
|
||||
timestamp=row['time'],
|
||||
coords=Point(row['lon'], row['lat'], srid=4326),
|
||||
defaults={
|
||||
'is_average': False,
|
||||
'id_user_add': CustomUser.objects.get(id=1),
|
||||
}
|
||||
)
|
||||
geo_obj.mirrors.set(Mirror.objects.filter(name__in=mir_lst))
|
||||
|
||||
obj_item_obj, _ = ObjItem.objects.get_or_create(
|
||||
name=row['obj'],
|
||||
# id_satellite=sat_obj,
|
||||
id_vch_load=vch_load_obj,
|
||||
id_geo=geo_obj,
|
||||
defaults={
|
||||
'id_user_add': CustomUser.objects.get(id=1)
|
||||
}
|
||||
)
|
||||
obj_item_obj.save()
|
||||
# df = pd.read_csv(filepath, sep=";",
|
||||
# names=['id', 'obj', 'lat', 'lon', 'h', 'time', 'sat', 'norad_id', 'freq', 'f_range', 'et', 'qaul', 'mir_1', 'mir_2', 'mir_3'])
|
||||
# df[['lat', 'lon', 'freq', 'f_range']] = df[['lat', 'lon', 'freq', 'f_range']].replace(',', '.', regex=True).astype(float)
|
||||
# df['time'] = pd.to_datetime(df['time'], format='%d.%m.%Y %H:%M:%S')
|
||||
# for row in df.iterrows():
|
||||
# row = row[1]
|
||||
# match row['obj'].split(' ')[-1]:
|
||||
# case 'V':
|
||||
# pol = 'Вертикальная'
|
||||
# case 'H':
|
||||
# pol = 'Горизонтальная'
|
||||
# case 'R':
|
||||
# pol = 'Правая'
|
||||
# case 'L':
|
||||
# pol = 'Левая'
|
||||
# case _:
|
||||
# pol = '-'
|
||||
# pol_obj, _ = Polarization.objects.get_or_create(
|
||||
# name=pol
|
||||
# )
|
||||
# sat_obj, _ = Satellite.objects.get_or_create(
|
||||
# name=row['sat'],
|
||||
# defaults={'norad': row['norad_id']}
|
||||
# )
|
||||
# mir_1_obj, _ = Mirror.objects.get_or_create(
|
||||
# name=row['mir_1']
|
||||
# )
|
||||
# mir_2_obj, _ = Mirror.objects.get_or_create(
|
||||
# name=row['mir_2']
|
||||
# )
|
||||
# mir_lst = [row['mir_1'], row['mir_2']]
|
||||
# if not pd.isna(row['mir_3']):
|
||||
# mir_3_obj, _ = Mirror.objects.get_or_create(
|
||||
|
||||
# name=row['mir_3']
|
||||
# )
|
||||
# vch_load_obj, _ = Parameter.objects.get_or_create(
|
||||
# id_satellite=sat_obj,
|
||||
# polarization=pol_obj,
|
||||
# frequency=row['freq'],
|
||||
# freq_range=row['f_range'],
|
||||
# defaults={'id_user_add': CustomUser.objects.get(id=1)}
|
||||
# )
|
||||
|
||||
# geo_obj, _ = Geo.objects.get_or_create(
|
||||
# timestamp=row['time'],
|
||||
# coords=Point(row['lon'], row['lat'], srid=4326),
|
||||
# defaults={
|
||||
# 'is_average': False,
|
||||
# 'id_user_add': CustomUser.objects.get(id=1),
|
||||
# }
|
||||
# )
|
||||
# geo_obj.mirrors.set(Mirror.objects.filter(name__in=mir_lst))
|
||||
|
||||
# obj_item_obj, _ = ObjItem.objects.get_or_create(
|
||||
# name=row['obj'],
|
||||
# # id_satellite=sat_obj,
|
||||
# id_vch_load=vch_load_obj,
|
||||
# id_geo=geo_obj,
|
||||
# defaults={
|
||||
# 'id_user_add': CustomUser.objects.get(id=1)
|
||||
# }
|
||||
# )
|
||||
# obj_item_obj.save()
|
||||
|
||||
|
||||
def get_vch_load_from_html(file, sat: Satellite) -> None:
|
||||
tables = pd.read_html(file, encoding='windows-1251')
|
||||
df = tables[0]
|
||||
df = df.drop(0).reset_index(drop=True)
|
||||
df.columns = df.iloc[0]
|
||||
df = df.drop(0).reset_index(drop=True)
|
||||
df.replace('Неизвестно', '-', inplace=True)
|
||||
df[['Частота, МГц', 'Полоса, МГц', 'Мощность, дБм']] = df[['Частота, МГц', 'Полоса, МГц', 'Мощность, дБм']].apply(pd.to_numeric)
|
||||
df['Время начала измерения'] = df['Время начала измерения'].apply(lambda x: datetime.strptime(x, '%d.%m.%Y %H:%M:%S'))
|
||||
df['Время окончания измерения'] = df['Время окончания измерения'].apply(lambda x: datetime.strptime(x, '%d.%m.%Y %H:%M:%S'))
|
||||
|
||||
for stroka in df.iterrows():
|
||||
value = stroka[1]
|
||||
if value['Полоса, МГц'] < 0.08:
|
||||
continue
|
||||
if '-' in value['Символьная скорость']:
|
||||
bod_velocity = -1.0
|
||||
else:
|
||||
bod_velocity = value['Символьная скорость']
|
||||
if '-' in value['Сигнал/шум, дБ']:
|
||||
snr = - 1.0
|
||||
else:
|
||||
snr = value['Сигнал/шум, дБ']
|
||||
if value['Пакетность'] == 'да':
|
||||
pack = True
|
||||
elif value['Пакетность'] == 'нет':
|
||||
pack = False
|
||||
else:
|
||||
pack = None
|
||||
|
||||
mod, _ = Modulation.objects.get_or_create(
|
||||
name=value['Модуляция']
|
||||
)
|
||||
standard, _ = Standard.objects.get_or_create(
|
||||
name=value['Стандарт']
|
||||
)
|
||||
sigma_load, _ = SigmaParameter.objects.get_or_create(
|
||||
id_satellite=sat,
|
||||
frequency=value['Частота, МГц'],
|
||||
freq_range=value['Полоса, МГц'],
|
||||
defaults={
|
||||
"status": value['Статус'],
|
||||
"power": value['Мощность, дБм'],
|
||||
"bod_velocity": bod_velocity,
|
||||
"modulation": mod,
|
||||
"snr": snr,
|
||||
"packets": pack,
|
||||
"datetime_begin": value['Время начала измерения'],
|
||||
"datetime_end": value['Время окончания измерения'],
|
||||
}
|
||||
|
||||
)
|
||||
sigma_load.save()
|
||||
|
||||
def define_ku_transfer(min_freq: float, max_freq: float) -> int | None:
|
||||
fss = (10700, 11700)
|
||||
dss = (11700, 12750)
|
||||
if min_freq + 9750 >= fss[0] and max_freq + 9750 <= fss[1]:
|
||||
return 9750
|
||||
elif min_freq + 10750 >= dss[0] and max_freq + 10750 <= dss[1]:
|
||||
return 10750
|
||||
return None
|
||||
|
||||
def compare_and_link_vch_load(sat_id: Satellite, eps_freq: float, eps_frange: float, ku_range: float):
|
||||
item_obj = ObjItem.objects.filter(id_vch_load__id_satellite=sat_id)
|
||||
vch_sigma = SigmaParameter.objects.filter(id_satellite=sat_id)
|
||||
link_count = 0
|
||||
obj_count = len(item_obj)
|
||||
for idx, obj in enumerate(item_obj):
|
||||
vch_load = obj.id_vch_load
|
||||
if vch_load.frequency == -1.0:
|
||||
continue
|
||||
# if unique_points = Point.objects.order_by('frequency').distinct('frequency')
|
||||
for sigma in vch_sigma:
|
||||
if abs(sigma.frequency + ku_range - vch_load.frequency) <= vch_load.frequency*eps_freq/100 and abs(sigma.freq_range - vch_load.freq_range) <= vch_load.freq_range*eps_frange/100:
|
||||
sigma.parameter = vch_load
|
||||
sigma.save()
|
||||
link_count += 1
|
||||
return obj_count, link_count
|
||||
|
||||
|
||||
213
dbapp/mainapp/views.py
Normal file
213
dbapp/mainapp/views.py
Normal file
@@ -0,0 +1,213 @@
|
||||
from django.shortcuts import render, redirect
|
||||
from django.contrib import messages
|
||||
from django.http import JsonResponse
|
||||
from django.views.decorators.http import require_GET
|
||||
from django.contrib.admin.views.decorators import staff_member_required
|
||||
import pandas as pd
|
||||
from .utils import (
|
||||
fill_data_from_df,
|
||||
add_satellite_list,
|
||||
get_points_from_csv,
|
||||
get_vch_load_from_html,
|
||||
compare_and_link_vch_load
|
||||
)
|
||||
from mapsapp.utils import parse_transponders_from_json
|
||||
from .forms import LoadExcelData, LoadCsvData, UploadFileForm, VchLinkForm
|
||||
from .models import ObjItem
|
||||
from .clusters import get_clusters
|
||||
from dbapp.settings import BASE_DIR
|
||||
|
||||
|
||||
def add_satellites(request):
|
||||
add_satellite_list()
|
||||
return redirect('home')
|
||||
|
||||
def add_transponders(request):
|
||||
try:
|
||||
parse_transponders_from_json(BASE_DIR / "transponders.json")
|
||||
except FileNotFoundError:
|
||||
print("Файл не найден")
|
||||
return redirect('home')
|
||||
|
||||
|
||||
def home_page(request):
|
||||
return render(request, 'mainapp/home.html')
|
||||
|
||||
|
||||
def load_excel_data(request):
|
||||
if request.method == "POST":
|
||||
form = LoadExcelData(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
uploaded_file = request.FILES['file']
|
||||
selected_sat = form.cleaned_data['sat_choice']
|
||||
number = form.cleaned_data['number_input']
|
||||
|
||||
try:
|
||||
# Create a temporary file-like object from the uploaded file
|
||||
import io
|
||||
df = pd.read_excel(io.BytesIO(uploaded_file.read()))
|
||||
if number > 0:
|
||||
df = df.head(number)
|
||||
result = fill_data_from_df(df, selected_sat)
|
||||
|
||||
messages.success(request, f"Данные успешно загружены! Обработано строк: {result}")
|
||||
return redirect('load_excel_data')
|
||||
except Exception as e:
|
||||
messages.error(request, f"Ошибка при обработке файла: {str(e)}")
|
||||
return redirect('load_excel_data')
|
||||
else:
|
||||
form = LoadExcelData()
|
||||
|
||||
return render(request, 'mainapp/add_data_from_excel.html', {'form': form})
|
||||
|
||||
|
||||
def get_locations(request, sat_id):
|
||||
locations = ObjItem.objects.filter(id_vch_load__id_satellite=sat_id)
|
||||
if not locations:
|
||||
return JsonResponse({'error': 'Объектов не найдено'}, status=400)
|
||||
|
||||
features = []
|
||||
for loc in locations:
|
||||
features.append({
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Point",
|
||||
"coordinates": [loc.id_geo.coords[0], loc.id_geo.coords[1]]
|
||||
},
|
||||
"properties": {
|
||||
"pol": loc.id_vch_load.polarization.name,
|
||||
"freq": loc.id_vch_load.frequency*1000000,
|
||||
"name": f"{loc.name}",
|
||||
"id": loc.id_geo.id
|
||||
}
|
||||
})
|
||||
|
||||
return JsonResponse({
|
||||
"type": "FeatureCollection",
|
||||
"features": features
|
||||
})
|
||||
|
||||
def load_raw_csv_data(request):
|
||||
if request.method == "POST":
|
||||
form = LoadCsvData(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
uploaded_file = request.FILES['file']
|
||||
try:
|
||||
# Read the file content and pass it directly to the function
|
||||
content = uploaded_file.read()
|
||||
if isinstance(content, bytes):
|
||||
content = content.decode('utf-8')
|
||||
|
||||
get_points_from_csv(content)
|
||||
messages.success(request, f"Данные успешно загружены!")
|
||||
return redirect('load_csv_data')
|
||||
except Exception as e:
|
||||
messages.error(request, f"Ошибка при обработке файла: {str(e)}")
|
||||
return redirect('load_csv_data')
|
||||
|
||||
else:
|
||||
form = LoadCsvData()
|
||||
|
||||
return render(request, 'mainapp/add_data_from_csv.html', {'form': form})
|
||||
|
||||
# def upload_file(request):
|
||||
# if request.method == 'POST' and request.FILES:
|
||||
# form = UploadFileForm(request.POST, request.FILES)
|
||||
# if form.is_valid():
|
||||
# uploaded_file = request.FILES['file']
|
||||
# # Обработка текстового файла, например:
|
||||
# df = pd.read_csv(uploaded_file)
|
||||
# df = pd.read_csv(filepath, sep=";",
|
||||
# names=['id', 'obj', 'lat', 'lon', 'h', 'time', 'sat', 'norad_id', 'freq', 'f_range', 'et', 'qaul', 'mir_1', 'mir_2', 'mir_3'])
|
||||
# df[['lat', 'lon', 'freq', 'f_range']] = df[['lat', 'lon', 'freq', 'f_range']].replace(',', '.', regex=True).astype(float)
|
||||
# df['time'] = pd.to_datetime(df['time'], format='%d.%m.%Y %H:%M:%S')
|
||||
# get_points_from_csv(df)
|
||||
# return JsonResponse({'status': 'success'})
|
||||
# else:
|
||||
# return JsonResponse({'status': 'error', 'errors': form.errors}, status=400)
|
||||
# return render(request, 'mainapp/add_data_from_csv.html')
|
||||
from collections import defaultdict
|
||||
@staff_member_required
|
||||
def show_map_view(request):
|
||||
ids = request.GET.get('ids', '')
|
||||
points = []
|
||||
if ids:
|
||||
id_list = [int(x) for x in ids.split(',') if x.isdigit()]
|
||||
locations = ObjItem.objects.filter(id__in=id_list)
|
||||
for obj in locations:
|
||||
points.append({
|
||||
'name': f"{obj.name}",
|
||||
'freq': f"{obj.id_vch_load.frequency} [{obj.id_vch_load.freq_range}] МГц",
|
||||
'point': (obj.id_geo.coords.x, obj.id_geo.coords.y)
|
||||
})
|
||||
else:
|
||||
return redirect('admin')
|
||||
grouped = defaultdict(list)
|
||||
for p in points:
|
||||
grouped[p["name"]].append({
|
||||
'point': p["point"],
|
||||
'frequency': p["freq"]
|
||||
})
|
||||
|
||||
# Преобразуем в список словарей для удобства в шаблоне
|
||||
groups = [
|
||||
{
|
||||
"name": name,
|
||||
"points": coords_list
|
||||
}
|
||||
for name, coords_list in grouped.items()
|
||||
]
|
||||
|
||||
|
||||
context = {
|
||||
'groups': groups,
|
||||
}
|
||||
return render(request, 'admin/map_custom.html', context)
|
||||
|
||||
|
||||
def cluster_test(request):
|
||||
objs = ObjItem.objects.filter(name__icontains="! Astra 4A 12654,040 [1,962] МГц H")
|
||||
coords = []
|
||||
for obj in objs:
|
||||
coords.append((obj.id_geo.coords[1], obj.id_geo.coords[0]))
|
||||
get_clusters(coords)
|
||||
|
||||
return JsonResponse({"success": "ок"})
|
||||
|
||||
def upload_vch_load_from_html(request):
|
||||
if request.method == 'POST':
|
||||
form = UploadFileForm(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
selected_sat = form.cleaned_data['sat_choice']
|
||||
uploaded_file = request.FILES['file']
|
||||
try:
|
||||
get_vch_load_from_html(uploaded_file, selected_sat)
|
||||
messages.success(request, "Файл успешно обработан")
|
||||
except ValueError as e:
|
||||
messages.error(request, f"Ошибка при чтении таблиц: {e}")
|
||||
except Exception as e:
|
||||
messages.error(request, f"Неизвестная ошибка: {e}")
|
||||
else:
|
||||
messages.error(request, "Форма заполнена некорректно.")
|
||||
else:
|
||||
form = UploadFileForm()
|
||||
|
||||
return render(request, 'mainapp/upload_html.html', {'form': form})
|
||||
|
||||
|
||||
def link_vch_sigma(request):
|
||||
if request.method == 'POST':
|
||||
form = VchLinkForm(request.POST)
|
||||
if form.is_valid():
|
||||
freq = form.cleaned_data['value1']
|
||||
freq_range = form.cleaned_data['value2']
|
||||
ku_range = float(form.cleaned_data['ku_range'])
|
||||
sat_id = form.cleaned_data['sat_choice']
|
||||
# print(freq, freq_range, ku_range, sat_id.pk)
|
||||
count_all, link_count = compare_and_link_vch_load(sat_id, freq, freq_range, ku_range)
|
||||
messages.success(request, f"Привязано {link_count} из {count_all} объектов")
|
||||
return redirect('link_vch_sigma')
|
||||
else:
|
||||
form = VchLinkForm()
|
||||
|
||||
return render(request, 'mainapp/link_vch.html', {'form': form})
|
||||
Reference in New Issue
Block a user