rework main models
This commit is contained in:
@@ -18,34 +18,26 @@ RUN apt-get update && apt-get install -y \
|
||||
postgresql-client \
|
||||
build-essential \
|
||||
libpq-dev \
|
||||
gcc \
|
||||
g++ \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Python dependencies for GDAL
|
||||
RUN pip install --upgrade pip && \
|
||||
pip install --no-cache-dir GDAL==$(gdal-config --version)
|
||||
|
||||
# Set work directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy project requirements
|
||||
# Copy project files
|
||||
COPY pyproject.toml uv.lock ./
|
||||
|
||||
# Install uv package manager
|
||||
RUN pip install --upgrade pip && pip install uv
|
||||
# Install uv and dependencies
|
||||
RUN pip install --no-cache-dir uv && \
|
||||
uv sync --frozen --no-dev
|
||||
|
||||
# Install dependencies using uv
|
||||
RUN uv pip install --system --no-cache-dir -r uv.lock
|
||||
|
||||
# Copy project
|
||||
# Copy project code (после установки зависимостей для лучшего кэширования)
|
||||
COPY . .
|
||||
|
||||
# Collect static files
|
||||
RUN python manage.py collectstatic --noinput
|
||||
RUN uv run manage.py collectstatic --noinput
|
||||
|
||||
# Expose port
|
||||
EXPOSE 8000
|
||||
|
||||
# Run gunicorn server
|
||||
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "3", "dbapp.wsgi:application"]
|
||||
CMD [".venv/bin/gunicorn", "--bind", "0.0.0.0:8000", "--workers", "3", "dbapp.wsgi:application"]
|
||||
@@ -72,6 +72,7 @@ INSTALLED_APPS = [
|
||||
|
||||
MIDDLEWARE = [
|
||||
"debug_toolbar.middleware.DebugToolbarMiddleware", #Добавил
|
||||
'django.middleware.locale.LocaleMiddleware', #Добавил
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware', #Добавил
|
||||
|
||||
BIN
dbapp/gdal-3.10.2-cp313-cp313-win_amd64.whl
Normal file
BIN
dbapp/gdal-3.10.2-cp313-cp313-win_amd64.whl
Normal file
Binary file not shown.
@@ -6,6 +6,7 @@ from .models import (
|
||||
Standard,
|
||||
SigmaParMark,
|
||||
SigmaParameter,
|
||||
SourceType,
|
||||
Parameter,
|
||||
Satellite,
|
||||
Mirror,
|
||||
@@ -128,6 +129,12 @@ class ModulationAdmin(admin.ModelAdmin):
|
||||
search_fields = ("name",)
|
||||
ordering = ("name",)
|
||||
|
||||
@admin.register(SourceType)
|
||||
class ModulationAdmin(admin.ModelAdmin):
|
||||
list_display = ("name",)
|
||||
search_fields = ("name",)
|
||||
ordering = ("name",)
|
||||
|
||||
|
||||
@admin.register(Standard)
|
||||
class StandardAdmin(admin.ModelAdmin):
|
||||
@@ -209,23 +216,25 @@ class ParameterAdmin(ImportExportActionModelAdmin, admin.ModelAdmin):
|
||||
class SigmaParameterAdmin(ImportExportActionModelAdmin, admin.ModelAdmin):
|
||||
list_display = (
|
||||
"id_satellite",
|
||||
"status",
|
||||
# "status",
|
||||
"frequency",
|
||||
"transfer_frequency",
|
||||
"freq_range",
|
||||
"power",
|
||||
# "power",
|
||||
"polarization",
|
||||
"modulation",
|
||||
"bod_velocity",
|
||||
"snr",
|
||||
"standard",
|
||||
# "standard",
|
||||
"parameter",
|
||||
"packets",
|
||||
# "packets",
|
||||
"datetime_begin",
|
||||
"datetime_end",
|
||||
)
|
||||
readonly_fields = (
|
||||
"datetime_begin",
|
||||
"datetime_end",
|
||||
|
||||
"datetime_end",
|
||||
"transfer_frequency"
|
||||
)
|
||||
list_display_links = ("id_satellite",)
|
||||
list_filter = (
|
||||
@@ -401,7 +410,7 @@ class ObjectAdmin(admin.ModelAdmin):
|
||||
)
|
||||
search_fields = (
|
||||
"name",
|
||||
# "id_geo",
|
||||
"id_geo__coords",
|
||||
# "id_satellite__name",
|
||||
# "id_vch_load__frequency",
|
||||
)
|
||||
@@ -413,6 +422,7 @@ class ObjectAdmin(admin.ModelAdmin):
|
||||
"id_vch_load__modulation",
|
||||
"id_vch_load__id_satellite",
|
||||
"id_geo",
|
||||
"id_source_type"
|
||||
)
|
||||
autocomplete_fields = ("id_geo",)
|
||||
raw_id_fields = ("id_vch_load",)
|
||||
@@ -422,11 +432,13 @@ class ObjectAdmin(admin.ModelAdmin):
|
||||
def sat_name(self, obj):
|
||||
return obj.id_vch_load.id_satellite
|
||||
sat_name.short_description = "Спутник"
|
||||
sat_name.admin_order_field = "id_vch_load__id_satellite__name"
|
||||
|
||||
def freq(self, obj):
|
||||
par = obj.id_vch_load
|
||||
return par.frequency
|
||||
freq.short_description = "Частота, МГц"
|
||||
freq.admin_order_field = "id_vch_load__frequency"
|
||||
|
||||
def distance_geo_kup(self, obj):
|
||||
par = obj.id_geo.distance_coords_kup
|
||||
@@ -458,6 +470,7 @@ class ObjectAdmin(admin.ModelAdmin):
|
||||
par = obj.id_vch_load
|
||||
return par.freq_range
|
||||
freq_range.short_description = "Полоса, МГц"
|
||||
freq_range.admin_order_field = "id_vch_load__freq_range"
|
||||
|
||||
def bod_velocity(self, obj):
|
||||
par = obj.id_vch_load
|
||||
@@ -482,6 +495,7 @@ class ObjectAdmin(admin.ModelAdmin):
|
||||
lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S"
|
||||
return f"{lat} {lon}"
|
||||
geo_coords.short_description = "Координаты геолокации"
|
||||
geo_coords.admin_order_filed = "id_geo__coords"
|
||||
|
||||
def kupsat_coords(self, obj):
|
||||
obj = obj.id_geo
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
from django import forms
|
||||
from .models import Satellite
|
||||
from .models import Satellite, Polarization
|
||||
|
||||
class UploadFileForm(forms.Form):
|
||||
file = forms.FileField(
|
||||
label="Выберите файл",
|
||||
widget=forms.FileInput(attrs={
|
||||
'class': 'form-file-input'
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
class LoadExcelData(forms.Form):
|
||||
file = forms.FileField(
|
||||
@@ -33,7 +42,7 @@ class LoadCsvData(forms.Form):
|
||||
})
|
||||
)
|
||||
|
||||
class UploadFileForm(forms.Form):
|
||||
class UploadVchLoad(UploadFileForm):
|
||||
sat_choice = forms.ModelChoiceField(
|
||||
queryset=Satellite.objects.all(),
|
||||
label="Выберите спутник",
|
||||
@@ -41,12 +50,7 @@ class UploadFileForm(forms.Form):
|
||||
'class': 'form-select'
|
||||
})
|
||||
)
|
||||
file = forms.FileField(
|
||||
label="Выберите текстовый файл",
|
||||
widget=forms.FileInput(attrs={
|
||||
'class': 'form-file-input'
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
class VchLinkForm(forms.Form):
|
||||
sat_choice = forms.ModelChoiceField(
|
||||
@@ -75,4 +79,28 @@ class VchLinkForm(forms.Form):
|
||||
'class': 'form-control',
|
||||
'placeholder': 'Введите второе число'
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
class NewEventForm(forms.Form):
|
||||
# sat_choice = forms.ModelChoiceField(
|
||||
# queryset=Satellite.objects.all(),
|
||||
# label="Выберите спутник",
|
||||
# widget=forms.Select(attrs={
|
||||
# 'class': 'form-select'
|
||||
# })
|
||||
# )
|
||||
# pol_choice = forms.ModelChoiceField(
|
||||
# queryset=Polarization.objects.all(),
|
||||
# label="Выберите поляризацию",
|
||||
# widget=forms.Select(attrs={
|
||||
# 'class': 'form-select'
|
||||
# })
|
||||
# )
|
||||
file = forms.FileField(
|
||||
label="Выберите файл",
|
||||
widget=forms.FileInput(attrs={
|
||||
'class': 'form-control',
|
||||
'accept': '.xlsx,.xls'
|
||||
})
|
||||
)
|
||||
@@ -0,0 +1,31 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-27 13:10
|
||||
|
||||
import django.db.models.deletion
|
||||
import django.db.models.expressions
|
||||
import mainapp.models
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainapp', '0017_alter_sigmaparameter_parameter'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='sigmaparameter',
|
||||
name='polarization',
|
||||
field=models.ForeignKey(blank=True, default=mainapp.models.get_default_polarization, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, related_name='polarizations_sigma', to='mainapp.polarization', verbose_name='Поляризация'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='sigmaparameter',
|
||||
name='transfer',
|
||||
field=models.FloatField(choices=[(-1.0, '-'), (9750.0, '9750 МГц'), (10750.0, '10750 МГц')], default=-1.0, verbose_name='Перенос по частоте'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='sigmaparameter',
|
||||
name='transfer_frequency',
|
||||
field=models.GeneratedField(db_persist=True, expression=models.ExpressionWrapper(django.db.models.expressions.CombinedExpression(models.F('frequency'), '+', models.F('transfer')), output_field=models.FloatField()), null=True, output_field=models.FloatField(), verbose_name='Частота в Ku, МГц'),
|
||||
),
|
||||
]
|
||||
18
dbapp/mainapp/migrations/0019_alter_satellite_name.py
Normal file
18
dbapp/mainapp/migrations/0019_alter_satellite_name.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-28 05:41
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainapp', '0018_sigmaparameter_polarization_sigmaparameter_transfer_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='satellite',
|
||||
name='name',
|
||||
field=models.CharField(db_index=True, max_length=100, unique=True, verbose_name='Имя спутника'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,30 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-29 14:00
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainapp', '0019_alter_satellite_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SourceType',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=50, unique=True, verbose_name='Тип источника')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Тип источника',
|
||||
'verbose_name_plural': 'Типы источников',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='objitem',
|
||||
name='id_source_type',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='objitems', to='mainapp.sourcetype', verbose_name='Тип источника'),
|
||||
),
|
||||
]
|
||||
@@ -2,6 +2,7 @@ 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
|
||||
from django.db.models import F, ExpressionWrapper
|
||||
|
||||
def get_default_polarization():
|
||||
obj, created = Polarization.objects.get_or_create(
|
||||
@@ -96,7 +97,7 @@ class Standard(models.Model):
|
||||
|
||||
|
||||
class Satellite(models.Model):
|
||||
name = models.CharField(max_length=30, unique=True, verbose_name="Имя спутника", db_index=True)
|
||||
name = models.CharField(max_length=100, unique=True, verbose_name="Имя спутника", db_index=True)
|
||||
norad = models.IntegerField(blank=True, null=True, verbose_name="NORAD ID")
|
||||
|
||||
def __str__(self):
|
||||
@@ -107,6 +108,40 @@ class Satellite(models.Model):
|
||||
verbose_name_plural = "Спутники"
|
||||
|
||||
|
||||
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)
|
||||
# id_source_type = models.ForeignKey(SourceType, 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'
|
||||
# )
|
||||
# ]
|
||||
|
||||
class SourceType(models.Model):
|
||||
name = models.CharField(max_length=50, unique=True, verbose_name="Тип источника")
|
||||
objitem = models.OneToOneField(ObjItem, on_delete=models.SET_NULL, verbose_name="Гео", related_name="objitems", null=True)
|
||||
|
||||
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(
|
||||
@@ -123,6 +158,7 @@ class Parameter(models.Model):
|
||||
Standard, default=get_default_standard, on_delete=models.SET_DEFAULT, related_name="standards", null=True, blank=True, verbose_name="Стандарт"
|
||||
)
|
||||
id_user_add = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, related_name="parameter_added", verbose_name="Пользователь", null=True, blank=True)
|
||||
objitem = models.ForeignKey(ObjItem, on_delete=models.SET_NULL, related_name="objitems", verbose_name="Источник",null=True, blank=True)
|
||||
# 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)
|
||||
|
||||
@@ -151,12 +187,35 @@ class Parameter(models.Model):
|
||||
|
||||
|
||||
class SigmaParameter(models.Model):
|
||||
TRANSFERS = [
|
||||
(-1.0, "-"),
|
||||
(9750.0, "9750 МГц"),
|
||||
(10750.0, "10750 МГц")
|
||||
]
|
||||
|
||||
id_satellite = models.ForeignKey(Satellite, on_delete=models.PROTECT, related_name="sigmapar_sat", verbose_name="Спутник")
|
||||
transfer = models.FloatField(
|
||||
choices=TRANSFERS,
|
||||
default=-1.0,
|
||||
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)
|
||||
transfer_frequency = models.GeneratedField(
|
||||
expression=ExpressionWrapper(
|
||||
F('frequency') + F('transfer'),
|
||||
output_field=models.FloatField()
|
||||
),
|
||||
output_field=models.FloatField(),
|
||||
db_persist=True,
|
||||
null=True, blank=True, verbose_name="Частота в Ku, МГц"
|
||||
)
|
||||
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="Символьная скорость, БОД")
|
||||
polarization = models.ForeignKey(
|
||||
Polarization, default=get_default_polarization, on_delete=models.SET_DEFAULT, related_name="polarizations_sigma", 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="Модуляция"
|
||||
)
|
||||
@@ -213,6 +272,7 @@ class Geo(models.Model):
|
||||
db_persist=True,
|
||||
null=True, blank=True, verbose_name="Расстояние между купсатом и оперативным отделом, км"
|
||||
)
|
||||
objitem = models.OneToOneField(ObjItem, on_delete=models.SET_NULL, verbose_name="Гео", related_name="objitems", null=True)
|
||||
|
||||
def __str__(self):
|
||||
longitude = self.coords.coords[0]
|
||||
@@ -234,23 +294,3 @@ class Geo(models.Model):
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
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'
|
||||
)
|
||||
]
|
||||
@@ -171,6 +171,26 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- New Event 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-plus-circle text-success" viewBox="0 0 16 16">
|
||||
<path d="M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0M4.5 7.5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5M7.5 4.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 0 1h-1a.5.5 0 0 1-.5-.5m1 3a.5.5 0 0 1 .5.5v3a.5.5 0 0 1-1 0v-3a.5.5 0 0 1 .5-.5"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="card-title mb-0">Формирование таблицы для Кубсатов</h3>
|
||||
</div>
|
||||
<p class="card-text">Добавьте новое событие с помощью выбора спутника и загрузки файла данных.</p>
|
||||
<a href="{% url 'kubsat_excel' %}" class="btn btn-success">
|
||||
Добавить событие
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -31,13 +31,13 @@
|
||||
<div class="text-danger mt-1">{{ form.sat_choice.errors }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
{% comment %} <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> {% endcomment %}
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.value1.id_for_label }}" class="form-label">Разброс по частоте(в %)</label>
|
||||
{{ form.value1 }}
|
||||
|
||||
52
dbapp/mainapp/templates/mainapp/process_kubsat.html
Normal file
52
dbapp/mainapp/templates/mainapp/process_kubsat.html
Normal file
@@ -0,0 +1,52 @@
|
||||
{% 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 border-0">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h2 class="mb-0">Формирование таблицы Кубсат</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
{% comment%}
|
||||
<div class="mb-4">
|
||||
<label for="{{ form.sat_choice.id_for_label }}" class="form-label">{{ form.sat_choice.label }}</label>
|
||||
{{ form.sat_choice }}
|
||||
{% if form.sat_choice.errors %}
|
||||
<div class="text-danger mt-1">{{ form.sat_choice.errors }}</div>
|
||||
{% endif %}
|
||||
</div>{% endcomment %}
|
||||
|
||||
{% comment %} <div class="mb-4">
|
||||
<label for="{{ form.pol_choice.id_for_label }}" class="form-label">{{ form.pol_choice.label }}</label>
|
||||
{{ form.pol_choice }}
|
||||
{% if form.pol_choice.errors %}
|
||||
<div class="text-danger mt-1">{{ form.pol_choice.errors }}</div>
|
||||
{% endif %}
|
||||
</div> {% endcomment %}
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="{{ form.file.id_for_label }}" class="form-label">{{ form.file.label }}</label>
|
||||
{{ form.file }}
|
||||
{% if form.file.errors %}
|
||||
<div class="text-danger mt-1">{{ form.file.errors }}</div>
|
||||
{% endif %}
|
||||
<div class="form-text">Выберите файл для загрузки</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between">
|
||||
<a href="{% url 'home' %}" class="btn btn-secondary">Назад</a>
|
||||
<button type="submit" class="btn btn-success">Выполнить</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
53
dbapp/mainapp/templates/mainapp/transponders_upload.html
Normal file
53
dbapp/mainapp/templates/mainapp/transponders_upload.html
Normal file
@@ -0,0 +1,53 @@
|
||||
{% 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-warning text-white">
|
||||
<h2 class="mb-0">Загрузка данных транспондеров из CellNet</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">Загрузите xml-файл и выберите спутник для загрузки данных в базу.</p>
|
||||
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<div class="mb-3">
|
||||
<label for="{{ form.file.id_for_label }}" class="form-label">Выберите xml файл:</label>
|
||||
{{ form.file }}
|
||||
{% if form.file.errors %}
|
||||
<div class="text-danger mt-1">{{ form.file.errors }}</div>
|
||||
{% endif %}
|
||||
<div class="form-text">Загрузите xml-файл (.xml) с данными для обработки</div>
|
||||
</div>
|
||||
|
||||
{% comment %} <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> {% endcomment %}
|
||||
<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-warning">Добавить в базу</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -15,6 +15,7 @@ urlpatterns = [
|
||||
path('cluster/', views.ClusterTestView.as_view(), name='cluster'),
|
||||
path('vch-upload/', views.UploadVchLoadView.as_view(), name='vch_load'),
|
||||
path('vch-link/', views.LinkVchSigmaView.as_view(), name='link_vch_sigma'),
|
||||
path('kubsat-excel/', views.ProcessKubsatView.as_view(), name='kubsat_excel'),
|
||||
# path('upload/', views.upload_file, name='upload_file'),
|
||||
|
||||
]
|
||||
@@ -10,12 +10,18 @@ from .models import (
|
||||
ObjItem,
|
||||
CustomUser
|
||||
)
|
||||
from mapsapp.models import Transponders
|
||||
from datetime import datetime, time
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from django.contrib.gis.geos import Point
|
||||
import json
|
||||
import re
|
||||
import io
|
||||
from django.db.models import F, Count, Exists, OuterRef, Min, Max
|
||||
from geopy.geocoders import Nominatim
|
||||
import reverse_geocoder as rg
|
||||
import time
|
||||
|
||||
def get_all_constants():
|
||||
sats = [sat.name for sat in Satellite.objects.all()]
|
||||
@@ -74,7 +80,10 @@ def fill_data_from_df(df: pd.DataFrame, sat: Satellite):
|
||||
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())
|
||||
try:
|
||||
mod_obj, _ = Modulation.objects.get_or_create(name=stroka[1]['Модуляция'].strip())
|
||||
except AttributeError:
|
||||
mod_obj, _ = Modulation.objects.get_or_create(name='-')
|
||||
snr = remove_str(stroka[1]['ОСШ'])
|
||||
date = stroka[1]['Дата'].date()
|
||||
time_ = stroka[1]['Время']
|
||||
@@ -192,17 +201,7 @@ def get_point_from_json(filepath: str):
|
||||
|
||||
|
||||
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=";",
|
||||
df = pd.read_csv(io.StringIO(file_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')
|
||||
@@ -266,73 +265,22 @@ def get_points_from_csv(file_content):
|
||||
}
|
||||
)
|
||||
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:
|
||||
filename = file.name.split('_')
|
||||
transfer = filename[3]
|
||||
match filename[2]:
|
||||
case 'H':
|
||||
pol = 'Горизонтальная'
|
||||
case 'V':
|
||||
pol = 'Вертикальная'
|
||||
case 'R':
|
||||
pol = 'Правая'
|
||||
case 'L':
|
||||
pol = 'Левая'
|
||||
case _:
|
||||
pol = '-'
|
||||
|
||||
tables = pd.read_html(file, encoding='windows-1251')
|
||||
df = tables[0]
|
||||
df = df.drop(0).reset_index(drop=True)
|
||||
@@ -362,6 +310,10 @@ def get_vch_load_from_html(file, sat: Satellite) -> None:
|
||||
else:
|
||||
pack = None
|
||||
|
||||
polarization, _ = Polarization.objects.get_or_create(
|
||||
name=pol
|
||||
)
|
||||
|
||||
mod, _ = Modulation.objects.get_or_create(
|
||||
name=value['Модуляция']
|
||||
)
|
||||
@@ -372,7 +324,10 @@ def get_vch_load_from_html(file, sat: Satellite) -> None:
|
||||
id_satellite=sat,
|
||||
frequency=value['Частота, МГц'],
|
||||
freq_range=value['Полоса, МГц'],
|
||||
polarization=polarization,
|
||||
defaults={
|
||||
"transfer": float(transfer),
|
||||
# "polarization": polarization,
|
||||
"status": value['Статус'],
|
||||
"power": value['Мощность, дБм'],
|
||||
"bod_velocity": bod_velocity,
|
||||
@@ -386,15 +341,6 @@ def get_vch_load_from_html(file, sat: Satellite) -> None:
|
||||
)
|
||||
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)
|
||||
@@ -406,10 +352,62 @@ def compare_and_link_vch_load(sat_id: Satellite, eps_freq: float, eps_frange: fl
|
||||
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:
|
||||
if (
|
||||
abs(sigma.transfer_frequency - 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 and
|
||||
sigma.polarization == vch_load.polarization
|
||||
):
|
||||
sigma.parameter = vch_load
|
||||
sigma.save()
|
||||
link_count += 1
|
||||
return obj_count, link_count
|
||||
|
||||
|
||||
def kub_report(data_in: io.StringIO) -> pd.DataFrame:
|
||||
df_in = pd.read_excel(data_in)
|
||||
df = pd.DataFrame(columns=['Дата', 'Широта', 'Долгота',
|
||||
'Высота', 'Населённый пункт', 'ИСЗ',
|
||||
'Прямой канал, МГц', 'Обратный канал, МГц', 'Перенос, МГц', 'Полоса, МГц', 'Зеркала'])
|
||||
for row in df_in.iterrows():
|
||||
value = row[1]
|
||||
date = datetime.date(datetime.now())
|
||||
lat = value['Широта, град']
|
||||
lon = value['Долгота, град']
|
||||
isz = value['ИСЗ']
|
||||
downlink = value['Обратный канал, МГц']
|
||||
freq_range = value['Полоса, МГц']
|
||||
norad = int(re.findall(r'\((\d+)\)', isz)[0])
|
||||
sat_obj = Satellite.objects.get(norad=norad)
|
||||
pol_obj = Polarization.objects.get(name=value['Поляризация'].strip())
|
||||
transponder = Transponders.objects.filter(
|
||||
sat_id=sat_obj,
|
||||
polarization=pol_obj,
|
||||
downlink__gte=downlink - F('frequency_range')/2,
|
||||
downlink__lte=downlink + F('frequency_range')/2,
|
||||
).first()
|
||||
# try:
|
||||
# location = geolocator.reverse(f"{lat}, {lon}", language="ru").raw['address']
|
||||
# loc_name = location.get('city', '') or location.get('town', '') or location.get('province', '') or location.get('country', '')
|
||||
# except AttributeError:
|
||||
# loc_name = ''
|
||||
# time.sleep(1)
|
||||
loc_name = ''
|
||||
if transponder: #and not (len(transponder) > 1):
|
||||
transfer = transponder.transfer
|
||||
uplink = transfer + downlink
|
||||
new_row = pd.DataFrame([{'Дата': date,
|
||||
'Широта': lat,
|
||||
'Долгота': lon,
|
||||
'Высота': 0.0,
|
||||
'Населённый пункт': loc_name,
|
||||
'ИСЗ': isz,
|
||||
'Прямой канал, МГц': uplink,
|
||||
'Обратный канал, МГц': downlink,
|
||||
'Перенос, МГц': transfer,
|
||||
'Полоса, МГц': freq_range,
|
||||
'Зеркала': ''
|
||||
}])
|
||||
df = pd.concat([df, new_row], ignore_index=True)
|
||||
else:
|
||||
print("Ничего не найдено в транспондерах")
|
||||
return df
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from django.shortcuts import render, redirect
|
||||
from django.contrib import messages
|
||||
from django.http import JsonResponse
|
||||
from django.http import JsonResponse, HttpResponse
|
||||
from django.views.decorators.http import require_GET
|
||||
from django.contrib.admin.views.decorators import staff_member_required
|
||||
from django.utils.decorators import method_decorator
|
||||
@@ -13,13 +13,15 @@ from .utils import (
|
||||
add_satellite_list,
|
||||
get_points_from_csv,
|
||||
get_vch_load_from_html,
|
||||
compare_and_link_vch_load
|
||||
compare_and_link_vch_load,
|
||||
kub_report
|
||||
)
|
||||
from mapsapp.utils import parse_transponders_from_json
|
||||
from .forms import LoadExcelData, LoadCsvData, UploadFileForm, VchLinkForm
|
||||
from mapsapp.utils import parse_transponders_from_json, parse_transponders_from_xml
|
||||
from .forms import LoadExcelData, LoadCsvData, UploadFileForm, VchLinkForm, UploadVchLoad, NewEventForm
|
||||
from .models import ObjItem
|
||||
from .clusters import get_clusters
|
||||
from dbapp.settings import BASE_DIR
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
|
||||
class AddSatellitesView(View):
|
||||
@@ -27,13 +29,33 @@ class AddSatellitesView(View):
|
||||
add_satellite_list()
|
||||
return redirect('home')
|
||||
|
||||
class AddTranspondersView(View):
|
||||
def get(self, request):
|
||||
# class AddTranspondersView(View):
|
||||
# def get(self, request):
|
||||
# try:
|
||||
# parse_transponders_from_json(BASE_DIR / "transponders.json")
|
||||
# except FileNotFoundError:
|
||||
# print("Файл не найден")
|
||||
# return redirect('home')
|
||||
|
||||
class AddTranspondersView(FormView):
|
||||
template_name = 'mainapp/transponders_upload.html'
|
||||
form_class = UploadFileForm
|
||||
|
||||
def form_valid(self, form):
|
||||
uploaded_file = self.request.FILES['file']
|
||||
try:
|
||||
parse_transponders_from_json(BASE_DIR / "transponders.json")
|
||||
except FileNotFoundError:
|
||||
print("Файл не найден")
|
||||
return redirect('home')
|
||||
content = uploaded_file.read()
|
||||
parse_transponders_from_xml(BytesIO(content))
|
||||
messages.success(self.request, "Файл успешно обработан")
|
||||
except ValueError as e:
|
||||
messages.error(self.request, f"Ошибка при чтении таблиц: {e}")
|
||||
except Exception as e:
|
||||
messages.error(self.request, f"Неизвестная ошибка: {e}")
|
||||
return redirect('add_trans')
|
||||
|
||||
def form_invalid(self, form):
|
||||
messages.error(self.request, "Форма заполнена некорректно.")
|
||||
return super().form_invalid(form)
|
||||
|
||||
class HomePageView(TemplateView):
|
||||
template_name = 'mainapp/home.html'
|
||||
@@ -118,22 +140,7 @@ class LoadCsvDataView(FormView):
|
||||
messages.error(self.request, "Форма заполнена некорректно.")
|
||||
return super().form_invalid(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
|
||||
|
||||
@method_decorator(staff_member_required, name='dispatch')
|
||||
@@ -162,7 +169,6 @@ class ShowMapView(UserPassesTestMixin, View):
|
||||
'frequency': p["freq"]
|
||||
})
|
||||
|
||||
# Преобразуем в список словарей для удобства в шаблоне
|
||||
groups = [
|
||||
{
|
||||
"name": name,
|
||||
@@ -190,7 +196,7 @@ class ClusterTestView(View):
|
||||
|
||||
class UploadVchLoadView(FormView):
|
||||
template_name = 'mainapp/upload_html.html'
|
||||
form_class = UploadFileForm
|
||||
form_class = UploadVchLoad
|
||||
|
||||
def form_valid(self, form):
|
||||
selected_sat = form.cleaned_data['sat_choice']
|
||||
@@ -224,4 +230,38 @@ class LinkVchSigmaView(FormView):
|
||||
return redirect('link_vch_sigma')
|
||||
|
||||
def form_invalid(self, form):
|
||||
return self.render_to_response(self.get_context_data(form=form))
|
||||
return self.render_to_response(self.get_context_data(form=form))
|
||||
|
||||
|
||||
class ProcessKubsatView(FormView):
|
||||
template_name = 'mainapp/process_kubsat.html'
|
||||
form_class = NewEventForm
|
||||
|
||||
def form_valid(self, form):
|
||||
# selected_sat = form.cleaned_data['sat_choice']
|
||||
# selected_pol = form.cleaned_data['pol_choice']
|
||||
uploaded_file = self.request.FILES['file']
|
||||
try:
|
||||
content = uploaded_file.read()
|
||||
df = kub_report(BytesIO(content))
|
||||
output = BytesIO()
|
||||
with pd.ExcelWriter(output, engine='openpyxl') as writer:
|
||||
df.to_excel(writer, index=False, sheet_name='Результат')
|
||||
output.seek(0)
|
||||
|
||||
response = HttpResponse(
|
||||
output.getvalue(),
|
||||
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||||
)
|
||||
response['Content-Disposition'] = f'attachment; filename="kubsat_report.xlsx"'
|
||||
|
||||
messages.success(self.request, "Событие успешно обработано!")
|
||||
return response
|
||||
except Exception as e:
|
||||
messages.error(self.request, f"Ошибка при обработке файла: {str(e)}")
|
||||
return redirect('kubsat_excel')
|
||||
# return redirect('kubsat_excel')
|
||||
|
||||
def form_invalid(self, form):
|
||||
messages.error(self.request, "Форма заполнена некорректно.")
|
||||
return super().form_invalid(form)
|
||||
@@ -5,20 +5,24 @@ from more_admin_filters import MultiSelectDropdownFilter, MultiSelectFilter, Mul
|
||||
from import_export.admin import ImportExportActionModelAdmin
|
||||
|
||||
@admin.register(Transponders)
|
||||
class PolarizationAdmin(ImportExportActionModelAdmin, admin.ModelAdmin):
|
||||
class TranspondersAdmin(ImportExportActionModelAdmin, admin.ModelAdmin):
|
||||
list_display = (
|
||||
"sat_id",
|
||||
"name",
|
||||
"zone_name",
|
||||
"frequency",
|
||||
"downlink",
|
||||
"uplink",
|
||||
"frequency_range",
|
||||
"transfer",
|
||||
"polarization",
|
||||
)
|
||||
list_filter = (
|
||||
("polarization", MultiSelectRelatedDropdownFilter),
|
||||
("sat_id", MultiSelectRelatedDropdownFilter),
|
||||
("frequency", NumericRangeFilterBuilder()),
|
||||
# ("frequency", NumericRangeFilterBuilder()),
|
||||
"zone_name"
|
||||
)
|
||||
search_fields = ("name",)
|
||||
search_fields = ("name", "sat_id__name")
|
||||
ordering = ("name",)
|
||||
# def sat_name(self, obj):
|
||||
# return
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-27 12:20
|
||||
|
||||
import django.db.models.expressions
|
||||
import django.db.models.functions.math
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mapsapp', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='transponders',
|
||||
name='frequency',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='transponders',
|
||||
name='downlink',
|
||||
field=models.FloatField(blank=True, null=True, verbose_name='Downlink'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='transponders',
|
||||
name='uplink',
|
||||
field=models.FloatField(blank=True, null=True, verbose_name='Uplink'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='transponders',
|
||||
name='transfer',
|
||||
field=models.GeneratedField(db_persist=True, expression=models.ExpressionWrapper(django.db.models.functions.math.Abs(django.db.models.expressions.CombinedExpression(models.F('downlink'), '-', models.F('uplink'))), output_field=models.FloatField()), null=True, output_field=models.FloatField(), verbose_name='Расстояние между купсатом и гео, км'),
|
||||
),
|
||||
]
|
||||
20
dbapp/mapsapp/migrations/0003_alter_transponders_transfer.py
Normal file
20
dbapp/mapsapp/migrations/0003_alter_transponders_transfer.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-27 13:10
|
||||
|
||||
import django.db.models.expressions
|
||||
import django.db.models.functions.math
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mapsapp', '0002_remove_transponders_frequency_transponders_downlink_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='transponders',
|
||||
name='transfer',
|
||||
field=models.GeneratedField(db_persist=True, expression=models.ExpressionWrapper(django.db.models.functions.math.Abs(django.db.models.expressions.CombinedExpression(models.F('downlink'), '-', models.F('uplink'))), output_field=models.FloatField()), null=True, output_field=models.FloatField(), verbose_name='Перенос'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-28 05:44
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mapsapp', '0003_alter_transponders_transfer'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='transponders',
|
||||
name='zone_name',
|
||||
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Название зоны'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-29 14:00
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mapsapp', '0004_alter_transponders_zone_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='transponders',
|
||||
name='frequency_range',
|
||||
field=models.FloatField(blank=True, null=True, verbose_name='Полоса'),
|
||||
),
|
||||
]
|
||||
@@ -1,15 +1,27 @@
|
||||
from django.db import models
|
||||
from mainapp.models import Satellite, Polarization, get_default_polarization
|
||||
from django.db.models import F, ExpressionWrapper
|
||||
from django.db.models.functions import Abs
|
||||
|
||||
class Transponders(models.Model):
|
||||
name = models.CharField(max_length=30, null=True, blank=True, verbose_name="Название транспондера")
|
||||
frequency = models.FloatField(blank=True, null=True, verbose_name="Центральная частота")
|
||||
frequency_range = models.FloatField(blank=True, null=True, verbose_name="Полоса частот")
|
||||
zone_name = models.CharField(max_length=60, blank=True, null=True, verbose_name="Название зоны")
|
||||
downlink = models.FloatField(blank=True, null=True, verbose_name="Downlink")
|
||||
frequency_range = models.FloatField(blank=True, null=True, verbose_name="Полоса")
|
||||
uplink = models.FloatField(blank=True, null=True, verbose_name="Uplink")
|
||||
zone_name = models.CharField(max_length=255, blank=True, null=True, verbose_name="Название зоны")
|
||||
polarization = models.ForeignKey(
|
||||
Polarization, default=get_default_polarization, on_delete=models.SET_DEFAULT, related_name="tran_polarizations", null=True, blank=True, verbose_name="Поляризация"
|
||||
)
|
||||
sat_id = models.ForeignKey(Satellite, on_delete=models.PROTECT, related_name="tran_satellite", verbose_name="Спутник")
|
||||
transfer =models.GeneratedField(
|
||||
expression=ExpressionWrapper(
|
||||
Abs(F('downlink') - F('uplink')),
|
||||
output_field=models.FloatField()
|
||||
),
|
||||
output_field=models.FloatField(),
|
||||
db_persist=True,
|
||||
null=True, blank=True, verbose_name="Перенос"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@@ -3,6 +3,7 @@ import re
|
||||
import json
|
||||
from .models import Transponders
|
||||
from mainapp.models import Polarization, Satellite
|
||||
from io import BytesIO
|
||||
|
||||
def search_satellite_on_page(data: dict, satellite_name: str):
|
||||
for pos, value in data.get('page', {}).get('positions').items():
|
||||
@@ -90,3 +91,68 @@ def parse_transponders_from_json(filepath: str):
|
||||
)
|
||||
tran_obj.save()
|
||||
|
||||
|
||||
from lxml import etree
|
||||
|
||||
def parse_transponders_from_xml(data_in: BytesIO):
|
||||
|
||||
tree = etree.parse(data_in)
|
||||
ns = {
|
||||
'i': 'http://www.w3.org/2001/XMLSchema-instance',
|
||||
'ns': 'http://schemas.datacontract.org/2004/07/Geolocation.Domain.Utils.Repository.SatellitesSerialization.Memos',
|
||||
'tr': 'http://schemas.datacontract.org/2004/07/Geolocation.Common.Extensions'
|
||||
}
|
||||
satellites = tree.xpath('//ns:SatelliteMemo', namespaces=ns)
|
||||
for sat in satellites[:]:
|
||||
name = sat.xpath('./ns:name/text()', namespaces=ns)[0]
|
||||
if name == 'X' or 'DONT USE' in name:
|
||||
continue
|
||||
norad = sat.xpath('./ns:norad/text()', namespaces=ns)
|
||||
beams = sat.xpath('.//ns:BeamMemo', namespaces=ns)
|
||||
zones = {}
|
||||
for zone in beams:
|
||||
zone_name = zone.xpath('./ns:name/text()', namespaces=ns)[0] if zone.xpath('./ns:name/text()', namespaces=ns) else '-'
|
||||
zones[zone.xpath('./ns:id/text()', namespaces=ns)[0]] = {
|
||||
"name": zone_name,
|
||||
"pol": zone.xpath('./ns:polarization/text()', namespaces=ns)[0],
|
||||
}
|
||||
transponders = sat.xpath('.//ns:TransponderMemo', namespaces=ns)
|
||||
for transponder in transponders:
|
||||
tr_id = transponder.xpath('./ns:downlinkBeamId/text()', namespaces=ns)[0]
|
||||
downlink_start = float(transponder.xpath('./ns:downlinkFrequency/tr:start/text()', namespaces=ns)[0])
|
||||
downlink_end = float(transponder.xpath('./ns:downlinkFrequency/tr:end/text()', namespaces=ns)[0])
|
||||
uplink_start = float(transponder.xpath('./ns:uplinkFrequency/tr:start/text()', namespaces=ns)[0])
|
||||
uplink_end = float(transponder.xpath('./ns:uplinkFrequency/tr:end/text()', namespaces=ns)[0])
|
||||
tr_data = zones[tr_id]
|
||||
# p = tr_data['pol'][0] if tr_data['pol'] else '-'
|
||||
match tr_data['pol']:
|
||||
case 'Horizontal':
|
||||
pol = 'Горизонтальная'
|
||||
case 'Vertical':
|
||||
pol = 'Вертикальная'
|
||||
case 'CircularRight':
|
||||
pol = 'Правая'
|
||||
case 'CircularLeft':
|
||||
pol = 'Левая'
|
||||
case _:
|
||||
pol = '-'
|
||||
tr_name = transponder.xpath('./ns:name/text()', namespaces=ns)[0]
|
||||
|
||||
pol_obj, _ = Polarization.objects.get_or_create(name=pol)
|
||||
sat_obj, _ = Satellite.objects.get_or_create(
|
||||
name=name,
|
||||
defaults={
|
||||
"norad": int(norad[0]) if norad else -1
|
||||
})
|
||||
trans_obj, _ = Transponders.objects.get_or_create(
|
||||
polarization=pol_obj,
|
||||
downlink=(downlink_start+downlink_end)/2/1000000,
|
||||
uplink=(uplink_start+uplink_end)/2/1000000,
|
||||
frequency_range=abs(downlink_end-downlink_start)/1000000,
|
||||
name=tr_name,
|
||||
defaults={
|
||||
"zone_name": tr_data['name'],
|
||||
"sat_id": sat_obj,
|
||||
}
|
||||
)
|
||||
trans_obj.save()
|
||||
|
||||
@@ -19,7 +19,9 @@ dependencies = [
|
||||
"django-leaflet>=0.32.0",
|
||||
"django-map-widgets>=0.5.1",
|
||||
"django-more-admin-filters>=1.13",
|
||||
"gdal",
|
||||
"dotenv>=0.9.9",
|
||||
"geopy>=2.4.1",
|
||||
"gunicorn>=23.0.0",
|
||||
"lxml>=6.0.2",
|
||||
"matplotlib>=3.10.7",
|
||||
"numpy>=2.3.3",
|
||||
@@ -28,9 +30,11 @@ dependencies = [
|
||||
"psycopg>=3.2.10",
|
||||
"redis>=6.4.0",
|
||||
"requests>=2.32.5",
|
||||
"reverse-geocoder>=1.5.1",
|
||||
"scikit-learn>=1.7.2",
|
||||
"setuptools>=80.9.0",
|
||||
]
|
||||
|
||||
[tool.uv.sources]
|
||||
gdal = { path = "gdal-3.10.2-cp313-cp313-win_amd64.whl" }
|
||||
|
||||
[dependency-groups]
|
||||
dev = []
|
||||
|
||||
78
dbapp/uv.lock
generated
78
dbapp/uv.lock
generated
@@ -212,7 +212,9 @@ dependencies = [
|
||||
{ name = "django-leaflet" },
|
||||
{ name = "django-map-widgets" },
|
||||
{ name = "django-more-admin-filters" },
|
||||
{ name = "gdal" },
|
||||
{ name = "dotenv" },
|
||||
{ name = "geopy" },
|
||||
{ name = "gunicorn" },
|
||||
{ name = "lxml" },
|
||||
{ name = "matplotlib" },
|
||||
{ name = "numpy" },
|
||||
@@ -221,6 +223,7 @@ dependencies = [
|
||||
{ name = "psycopg" },
|
||||
{ name = "redis" },
|
||||
{ name = "requests" },
|
||||
{ name = "reverse-geocoder" },
|
||||
{ name = "scikit-learn" },
|
||||
{ name = "setuptools" },
|
||||
]
|
||||
@@ -241,7 +244,9 @@ requires-dist = [
|
||||
{ name = "django-leaflet", specifier = ">=0.32.0" },
|
||||
{ name = "django-map-widgets", specifier = ">=0.5.1" },
|
||||
{ name = "django-more-admin-filters", specifier = ">=1.13" },
|
||||
{ name = "gdal", path = "gdal-3.10.2-cp313-cp313-win_amd64.whl" },
|
||||
{ name = "dotenv", specifier = ">=0.9.9" },
|
||||
{ name = "geopy", specifier = ">=2.4.1" },
|
||||
{ name = "gunicorn", specifier = ">=23.0.0" },
|
||||
{ name = "lxml", specifier = ">=6.0.2" },
|
||||
{ name = "matplotlib", specifier = ">=3.10.7" },
|
||||
{ name = "numpy", specifier = ">=2.3.3" },
|
||||
@@ -250,10 +255,14 @@ requires-dist = [
|
||||
{ name = "psycopg", specifier = ">=3.2.10" },
|
||||
{ name = "redis", specifier = ">=6.4.0" },
|
||||
{ name = "requests", specifier = ">=2.32.5" },
|
||||
{ name = "reverse-geocoder", specifier = ">=1.5.1" },
|
||||
{ name = "scikit-learn", specifier = ">=1.7.2" },
|
||||
{ name = "setuptools", specifier = ">=80.9.0" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = []
|
||||
|
||||
[[package]]
|
||||
name = "diff-match-patch"
|
||||
version = "20241021"
|
||||
@@ -410,6 +419,17 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/87/7c/4b261b96b357d94ef267f39856ef0bb72a33f078a38bd22ee96d168fe272/django_more_admin_filters-1.13-py3-none-any.whl", hash = "sha256:df4d46e4b589566b85f149ea5b7558c6cc4ae22b0d264973f8d4a2d478ef5120", size = 147360, upload-time = "2025-06-06T11:26:42.964Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dotenv"
|
||||
version = "0.9.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "python-dotenv" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/b7/545d2c10c1fc15e48653c91efde329a790f2eecfbbf2bd16003b5db2bab0/dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9", size = 1892, upload-time = "2025-02-19T22:15:01.647Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "et-xmlfile"
|
||||
version = "2.0.0"
|
||||
@@ -453,16 +473,37 @@ wheels = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gdal"
|
||||
version = "3.10.2"
|
||||
source = { path = "gdal-3.10.2-cp313-cp313-win_amd64.whl" }
|
||||
name = "geographiclib"
|
||||
version = "2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/df/78/4892343230a9d29faa1364564e525307a37e54ad776ea62c12129dbba704/geographiclib-2.1.tar.gz", hash = "sha256:6a6545e6262d0ed3522e13c515713718797e37ed8c672c31ad7b249f372ef108", size = 37004, upload-time = "2025-08-21T21:34:26Z" }
|
||||
wheels = [
|
||||
{ filename = "gdal-3.10.2-cp313-cp313-win_amd64.whl", hash = "sha256:d6aae781b9847065f831f3457c6c01d0b9272818656031d723dc88c160a8ec26" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/b3/802576f2ea5dcb48501bb162e4c7b7b3ca5654a42b2c968ef98a797a4c79/geographiclib-2.1-py3-none-any.whl", hash = "sha256:e2a873b9b9e7fc38721ad73d5f4e6c9ed140d428a339970f505c07056997d40b", size = 40740, upload-time = "2025-08-21T21:34:24.955Z" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "numpy", marker = "extra == 'numpy'", specifier = ">1.0.0" }]
|
||||
provides-extras = ["numpy"]
|
||||
[[package]]
|
||||
name = "geopy"
|
||||
version = "2.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "geographiclib" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0e/fd/ef6d53875ceab72c1fad22dbed5ec1ad04eb378c2251a6a8024bad890c3b/geopy-2.4.1.tar.gz", hash = "sha256:50283d8e7ad07d89be5cb027338c6365a32044df3ae2556ad3f52f4840b3d0d1", size = 117625, upload-time = "2023-11-23T21:49:32.734Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/15/cf2a69ade4b194aa524ac75112d5caac37414b20a3a03e6865dfe0bd1539/geopy-2.4.1-py3-none-any.whl", hash = "sha256:ae8b4bc5c1131820f4d75fce9d4aaaca0c85189b3aa5d64c3dcaf5e3b7b882a7", size = 125437, upload-time = "2023-11-23T21:49:30.421Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gunicorn"
|
||||
version = "23.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "packaging" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/34/72/9614c465dc206155d93eff0ca20d42e1e35afc533971379482de953521a4/gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec", size = 375031, upload-time = "2024-08-10T20:25:27.378Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/7d/6dac2a6e1eba33ee43f318edbed4ff29151a49b5d37f080aad1e6469bca4/gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d", size = 85029, upload-time = "2024-08-10T20:25:24.996Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
@@ -851,6 +892,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-slugify"
|
||||
version = "8.0.4"
|
||||
@@ -896,6 +946,16 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reverse-geocoder"
|
||||
version = "1.5.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
{ name = "scipy" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0b/0f/b7d5d4b36553731f11983e19e1813a1059ad0732c5162c01b3220c927d31/reverse_geocoder-1.5.1.tar.gz", hash = "sha256:2a2e781b5f69376d922b78fe8978f1350c84fce0ddb07e02c834ecf98b57c75c", size = 2246559, upload-time = "2016-09-15T16:46:46.277Z" }
|
||||
|
||||
[[package]]
|
||||
name = "scikit-learn"
|
||||
version = "1.7.2"
|
||||
|
||||
Reference in New Issue
Block a user