init commit

This commit is contained in:
2025-10-24 13:08:08 +03:00
commit 5e40201460
531 changed files with 919042 additions and 0 deletions

View File

506
dbapp/mainapp/admin.py Normal file
View 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
View 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
View 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
View 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
View 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': 'Введите второе число'
})
)

View 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'),
),
]

View 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='Расстояние между купсатом и гео'),
),
]

View File

@@ -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='Расстояние между купсатом и гео, км'),
),
]

View 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',
},
),
]

View 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,
),
]

View File

@@ -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='Спутник'),
),
]

View File

@@ -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='Спутник'),
),
]

View File

@@ -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='Частота, МГц'),
),
]

View File

@@ -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'),
),
]

View File

@@ -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='Отметка'),
),
]

View File

@@ -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='Отметка'),
),
]

View 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='Отметка'),
),
]

View File

@@ -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'),
),
]

View 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='Модуляция'),
),
]

View File

@@ -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'),
),
]

View File

@@ -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'),
),
]

View File

@@ -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='ВЧ'),
),
]

View File

256
dbapp/mainapp/models.py Normal file
View 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'
)
]

View 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()

View 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 %}

View 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 %}

View 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 %}

View 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>

View 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 %}

View 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 %}

View 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
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

20
dbapp/mainapp/urls.py Normal file
View 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
View 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
View 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})