rework main models

This commit is contained in:
2025-10-30 09:19:55 +03:00
parent 178854c6ba
commit 94df5171db
25 changed files with 744 additions and 190 deletions

View File

@@ -18,34 +18,26 @@ RUN apt-get update && apt-get install -y \
postgresql-client \ postgresql-client \
build-essential \ build-essential \
libpq-dev \ libpq-dev \
gcc \
g++ \
&& rm -rf /var/lib/apt/lists/* && 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 # Set work directory
WORKDIR /app WORKDIR /app
# Copy project requirements # Copy project files
COPY pyproject.toml uv.lock ./ COPY pyproject.toml uv.lock ./
# Install uv package manager # Install uv and dependencies
RUN pip install --upgrade pip && pip install uv RUN pip install --no-cache-dir uv && \
uv sync --frozen --no-dev
# Install dependencies using uv # Copy project code (после установки зависимостей для лучшего кэширования)
RUN uv pip install --system --no-cache-dir -r uv.lock
# Copy project
COPY . . COPY . .
# Collect static files # Collect static files
RUN python manage.py collectstatic --noinput RUN uv run manage.py collectstatic --noinput
# Expose port # Expose port
EXPOSE 8000 EXPOSE 8000
# Run gunicorn server # 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"]

View File

@@ -72,6 +72,7 @@ INSTALLED_APPS = [
MIDDLEWARE = [ MIDDLEWARE = [
"debug_toolbar.middleware.DebugToolbarMiddleware", #Добавил "debug_toolbar.middleware.DebugToolbarMiddleware", #Добавил
'django.middleware.locale.LocaleMiddleware', #Добавил
'django.middleware.security.SecurityMiddleware', 'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', #Добавил 'django.contrib.messages.middleware.MessageMiddleware', #Добавил

Binary file not shown.

View File

@@ -6,6 +6,7 @@ from .models import (
Standard, Standard,
SigmaParMark, SigmaParMark,
SigmaParameter, SigmaParameter,
SourceType,
Parameter, Parameter,
Satellite, Satellite,
Mirror, Mirror,
@@ -128,6 +129,12 @@ class ModulationAdmin(admin.ModelAdmin):
search_fields = ("name",) search_fields = ("name",)
ordering = ("name",) ordering = ("name",)
@admin.register(SourceType)
class ModulationAdmin(admin.ModelAdmin):
list_display = ("name",)
search_fields = ("name",)
ordering = ("name",)
@admin.register(Standard) @admin.register(Standard)
class StandardAdmin(admin.ModelAdmin): class StandardAdmin(admin.ModelAdmin):
@@ -209,23 +216,25 @@ class ParameterAdmin(ImportExportActionModelAdmin, admin.ModelAdmin):
class SigmaParameterAdmin(ImportExportActionModelAdmin, admin.ModelAdmin): class SigmaParameterAdmin(ImportExportActionModelAdmin, admin.ModelAdmin):
list_display = ( list_display = (
"id_satellite", "id_satellite",
"status", # "status",
"frequency", "frequency",
"transfer_frequency",
"freq_range", "freq_range",
"power", # "power",
"polarization",
"modulation", "modulation",
"bod_velocity", "bod_velocity",
"snr", "snr",
"standard", # "standard",
"parameter", "parameter",
"packets", # "packets",
"datetime_begin", "datetime_begin",
"datetime_end", "datetime_end",
) )
readonly_fields = ( readonly_fields = (
"datetime_begin", "datetime_begin",
"datetime_end", "datetime_end",
"transfer_frequency"
) )
list_display_links = ("id_satellite",) list_display_links = ("id_satellite",)
list_filter = ( list_filter = (
@@ -401,7 +410,7 @@ class ObjectAdmin(admin.ModelAdmin):
) )
search_fields = ( search_fields = (
"name", "name",
# "id_geo", "id_geo__coords",
# "id_satellite__name", # "id_satellite__name",
# "id_vch_load__frequency", # "id_vch_load__frequency",
) )
@@ -413,6 +422,7 @@ class ObjectAdmin(admin.ModelAdmin):
"id_vch_load__modulation", "id_vch_load__modulation",
"id_vch_load__id_satellite", "id_vch_load__id_satellite",
"id_geo", "id_geo",
"id_source_type"
) )
autocomplete_fields = ("id_geo",) autocomplete_fields = ("id_geo",)
raw_id_fields = ("id_vch_load",) raw_id_fields = ("id_vch_load",)
@@ -422,11 +432,13 @@ class ObjectAdmin(admin.ModelAdmin):
def sat_name(self, obj): def sat_name(self, obj):
return obj.id_vch_load.id_satellite return obj.id_vch_load.id_satellite
sat_name.short_description = "Спутник" sat_name.short_description = "Спутник"
sat_name.admin_order_field = "id_vch_load__id_satellite__name"
def freq(self, obj): def freq(self, obj):
par = obj.id_vch_load par = obj.id_vch_load
return par.frequency return par.frequency
freq.short_description = "Частота, МГц" freq.short_description = "Частота, МГц"
freq.admin_order_field = "id_vch_load__frequency"
def distance_geo_kup(self, obj): def distance_geo_kup(self, obj):
par = obj.id_geo.distance_coords_kup par = obj.id_geo.distance_coords_kup
@@ -458,6 +470,7 @@ class ObjectAdmin(admin.ModelAdmin):
par = obj.id_vch_load par = obj.id_vch_load
return par.freq_range return par.freq_range
freq_range.short_description = "Полоса, МГц" freq_range.short_description = "Полоса, МГц"
freq_range.admin_order_field = "id_vch_load__freq_range"
def bod_velocity(self, obj): def bod_velocity(self, obj):
par = obj.id_vch_load 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" lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S"
return f"{lat} {lon}" return f"{lat} {lon}"
geo_coords.short_description = "Координаты геолокации" geo_coords.short_description = "Координаты геолокации"
geo_coords.admin_order_filed = "id_geo__coords"
def kupsat_coords(self, obj): def kupsat_coords(self, obj):
obj = obj.id_geo obj = obj.id_geo

View File

@@ -1,5 +1,14 @@
from django import forms 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): class LoadExcelData(forms.Form):
file = forms.FileField( file = forms.FileField(
@@ -33,7 +42,7 @@ class LoadCsvData(forms.Form):
}) })
) )
class UploadFileForm(forms.Form): class UploadVchLoad(UploadFileForm):
sat_choice = forms.ModelChoiceField( sat_choice = forms.ModelChoiceField(
queryset=Satellite.objects.all(), queryset=Satellite.objects.all(),
label="Выберите спутник", label="Выберите спутник",
@@ -41,12 +50,7 @@ class UploadFileForm(forms.Form):
'class': 'form-select' 'class': 'form-select'
}) })
) )
file = forms.FileField(
label="Выберите текстовый файл",
widget=forms.FileInput(attrs={
'class': 'form-file-input'
})
)
class VchLinkForm(forms.Form): class VchLinkForm(forms.Form):
sat_choice = forms.ModelChoiceField( sat_choice = forms.ModelChoiceField(
@@ -75,4 +79,28 @@ class VchLinkForm(forms.Form):
'class': 'form-control', 'class': 'form-control',
'placeholder': 'Введите второе число' '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'
})
) )

View File

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

View 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='Имя спутника'),
),
]

View File

@@ -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='Тип источника'),
),
]

View File

@@ -2,6 +2,7 @@ from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.gis.db import models as gis from django.contrib.gis.db import models as gis
from django.contrib.gis.db.models import functions from django.contrib.gis.db.models import functions
from django.db.models import F, ExpressionWrapper
def get_default_polarization(): def get_default_polarization():
obj, created = Polarization.objects.get_or_create( obj, created = Polarization.objects.get_or_create(
@@ -96,7 +97,7 @@ class Standard(models.Model):
class Satellite(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") norad = models.IntegerField(blank=True, null=True, verbose_name="NORAD ID")
def __str__(self): def __str__(self):
@@ -107,6 +108,40 @@ class Satellite(models.Model):
verbose_name_plural = "Спутники" 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): class Parameter(models.Model):
id_satellite = models.ForeignKey(Satellite, on_delete=models.PROTECT, related_name="parameters", verbose_name="Спутник", null=True) id_satellite = models.ForeignKey(Satellite, on_delete=models.PROTECT, related_name="parameters", verbose_name="Спутник", null=True)
polarization = models.ForeignKey( 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="Стандарт" Standard, default=get_default_standard, on_delete=models.SET_DEFAULT, related_name="standards", null=True, blank=True, verbose_name="Стандарт"
) )
id_user_add = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, related_name="parameter_added", verbose_name="Пользователь", null=True, blank=True) id_user_add = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, related_name="parameter_added", verbose_name="Пользователь", null=True, blank=True)
objitem = models.ForeignKey(ObjItem, on_delete=models.SET_NULL, related_name="objitems", verbose_name="Источник",null=True, blank=True)
# id_sigma_parameter = models.ManyToManyField(SigmaParameter, on_delete=models.SET_NULL, related_name="sigma_parameter", verbose_name="ВЧ с sigma", null=True, blank=True) # id_sigma_parameter = models.ManyToManyField(SigmaParameter, on_delete=models.SET_NULL, related_name="sigma_parameter", verbose_name="ВЧ с sigma", null=True, blank=True)
# id_sigma_parameter = models.ManyToManyField(SigmaParameter, verbose_name="ВЧ с sigma", null=True, blank=True) # id_sigma_parameter = models.ManyToManyField(SigmaParameter, verbose_name="ВЧ с sigma", null=True, blank=True)
@@ -151,12 +187,35 @@ class Parameter(models.Model):
class SigmaParameter(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="Спутник") 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="Статус") 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) 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="Полоса частот, МГц") freq_range = models.FloatField(default=0, null=True, blank=True, verbose_name="Полоса частот, МГц")
power = 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="Символьная скорость, БОД") 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 = models.ForeignKey(
Modulation, default=get_default_modulation, on_delete=models.SET_DEFAULT, related_name="modulations_sigma", null=True, blank=True, verbose_name="Модуляция" 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, db_persist=True,
null=True, blank=True, verbose_name="Расстояние между купсатом и оперативным отделом, км" null=True, blank=True, verbose_name="Расстояние между купсатом и оперативным отделом, км"
) )
objitem = models.OneToOneField(ObjItem, on_delete=models.SET_NULL, verbose_name="Гео", related_name="objitems", null=True)
def __str__(self): def __str__(self):
longitude = self.coords.coords[0] 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'
)
]

View File

@@ -171,6 +171,26 @@
</div> </div>
</div> </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>
</div> </div>
{% endblock %} {% endblock %}

View File

@@ -31,13 +31,13 @@
<div class="text-danger mt-1">{{ form.sat_choice.errors }}</div> <div class="text-danger mt-1">{{ form.sat_choice.errors }}</div>
{% endif %} {% endif %}
</div> </div>
<div class="mb-3"> {% comment %} <div class="mb-3">
<label for="{{ form.ku_range.id_for_label }}" class="form-label">Выберите перенос по частоте(МГц):</label> <label for="{{ form.ku_range.id_for_label }}" class="form-label">Выберите перенос по частоте(МГц):</label>
{{ form.ku_range }} {{ form.ku_range }}
{% if form.ku_range.errors %} {% if form.ku_range.errors %}
<div class="text-danger mt-1">{{ form.ku_range.errors }}</div> <div class="text-danger mt-1">{{ form.ku_range.errors }}</div>
{% endif %} {% endif %}
</div> </div> {% endcomment %}
<div class="mb-3"> <div class="mb-3">
<label for="{{ form.value1.id_for_label }}" class="form-label">Разброс по частоте(в %)</label> <label for="{{ form.value1.id_for_label }}" class="form-label">Разброс по частоте(в %)</label>
{{ form.value1 }} {{ form.value1 }}

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

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

View File

@@ -15,6 +15,7 @@ urlpatterns = [
path('cluster/', views.ClusterTestView.as_view(), name='cluster'), path('cluster/', views.ClusterTestView.as_view(), name='cluster'),
path('vch-upload/', views.UploadVchLoadView.as_view(), name='vch_load'), path('vch-upload/', views.UploadVchLoadView.as_view(), name='vch_load'),
path('vch-link/', views.LinkVchSigmaView.as_view(), name='link_vch_sigma'), 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'), # path('upload/', views.upload_file, name='upload_file'),
] ]

View File

@@ -10,12 +10,18 @@ from .models import (
ObjItem, ObjItem,
CustomUser CustomUser
) )
from mapsapp.models import Transponders
from datetime import datetime, time from datetime import datetime, time
import pandas as pd import pandas as pd
import numpy as np import numpy as np
from django.contrib.gis.geos import Point from django.contrib.gis.geos import Point
import json import json
import re 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(): def get_all_constants():
sats = [sat.name for sat in Satellite.objects.all()] 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 = remove_str(stroka[1]['Частота, МГц'])
freq_line = remove_str(stroka[1]['Полоса, МГц']) freq_line = remove_str(stroka[1]['Полоса, МГц'])
v = 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]['ОСШ']) snr = remove_str(stroka[1]['ОСШ'])
date = stroka[1]['Дата'].date() date = stroka[1]['Дата'].date()
time_ = stroka[1]['Время'] time_ = stroka[1]['Время']
@@ -192,17 +201,7 @@ def get_point_from_json(filepath: str):
def get_points_from_csv(file_content): def get_points_from_csv(file_content):
import io df = pd.read_csv(io.StringIO(file_content), sep=";",
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']) 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[['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') 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() 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: 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') tables = pd.read_html(file, encoding='windows-1251')
df = tables[0] df = tables[0]
df = df.drop(0).reset_index(drop=True) df = df.drop(0).reset_index(drop=True)
@@ -362,6 +310,10 @@ def get_vch_load_from_html(file, sat: Satellite) -> None:
else: else:
pack = None pack = None
polarization, _ = Polarization.objects.get_or_create(
name=pol
)
mod, _ = Modulation.objects.get_or_create( mod, _ = Modulation.objects.get_or_create(
name=value['Модуляция'] name=value['Модуляция']
) )
@@ -372,7 +324,10 @@ def get_vch_load_from_html(file, sat: Satellite) -> None:
id_satellite=sat, id_satellite=sat,
frequency=value['Частота, МГц'], frequency=value['Частота, МГц'],
freq_range=value['Полоса, МГц'], freq_range=value['Полоса, МГц'],
polarization=polarization,
defaults={ defaults={
"transfer": float(transfer),
# "polarization": polarization,
"status": value['Статус'], "status": value['Статус'],
"power": value['Мощность, дБм'], "power": value['Мощность, дБм'],
"bod_velocity": bod_velocity, "bod_velocity": bod_velocity,
@@ -386,15 +341,6 @@ def get_vch_load_from_html(file, sat: Satellite) -> None:
) )
sigma_load.save() 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): 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) item_obj = ObjItem.objects.filter(id_vch_load__id_satellite=sat_id)
vch_sigma = SigmaParameter.objects.filter(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 continue
# if unique_points = Point.objects.order_by('frequency').distinct('frequency') # if unique_points = Point.objects.order_by('frequency').distinct('frequency')
for sigma in vch_sigma: 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.parameter = vch_load
sigma.save() sigma.save()
link_count += 1 link_count += 1
return obj_count, link_count 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

View File

@@ -1,6 +1,6 @@
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.contrib import messages 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.views.decorators.http import require_GET
from django.contrib.admin.views.decorators import staff_member_required from django.contrib.admin.views.decorators import staff_member_required
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
@@ -13,13 +13,15 @@ from .utils import (
add_satellite_list, add_satellite_list,
get_points_from_csv, get_points_from_csv,
get_vch_load_from_html, 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 mapsapp.utils import parse_transponders_from_json, parse_transponders_from_xml
from .forms import LoadExcelData, LoadCsvData, UploadFileForm, VchLinkForm from .forms import LoadExcelData, LoadCsvData, UploadFileForm, VchLinkForm, UploadVchLoad, NewEventForm
from .models import ObjItem from .models import ObjItem
from .clusters import get_clusters from .clusters import get_clusters
from dbapp.settings import BASE_DIR from io import BytesIO
class AddSatellitesView(View): class AddSatellitesView(View):
@@ -27,13 +29,33 @@ class AddSatellitesView(View):
add_satellite_list() add_satellite_list()
return redirect('home') return redirect('home')
class AddTranspondersView(View): # class AddTranspondersView(View):
def get(self, request): # 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: try:
parse_transponders_from_json(BASE_DIR / "transponders.json") content = uploaded_file.read()
except FileNotFoundError: parse_transponders_from_xml(BytesIO(content))
print("Файл не найден") messages.success(self.request, "Файл успешно обработан")
return redirect('home') 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): class HomePageView(TemplateView):
template_name = 'mainapp/home.html' template_name = 'mainapp/home.html'
@@ -118,22 +140,7 @@ class LoadCsvDataView(FormView):
messages.error(self.request, "Форма заполнена некорректно.") messages.error(self.request, "Форма заполнена некорректно.")
return super().form_invalid(form) 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 from collections import defaultdict
@method_decorator(staff_member_required, name='dispatch') @method_decorator(staff_member_required, name='dispatch')
@@ -162,7 +169,6 @@ class ShowMapView(UserPassesTestMixin, View):
'frequency': p["freq"] 'frequency': p["freq"]
}) })
# Преобразуем в список словарей для удобства в шаблоне
groups = [ groups = [
{ {
"name": name, "name": name,
@@ -190,7 +196,7 @@ class ClusterTestView(View):
class UploadVchLoadView(FormView): class UploadVchLoadView(FormView):
template_name = 'mainapp/upload_html.html' template_name = 'mainapp/upload_html.html'
form_class = UploadFileForm form_class = UploadVchLoad
def form_valid(self, form): def form_valid(self, form):
selected_sat = form.cleaned_data['sat_choice'] selected_sat = form.cleaned_data['sat_choice']
@@ -224,4 +230,38 @@ class LinkVchSigmaView(FormView):
return redirect('link_vch_sigma') return redirect('link_vch_sigma')
def form_invalid(self, form): 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)

View File

@@ -5,20 +5,24 @@ from more_admin_filters import MultiSelectDropdownFilter, MultiSelectFilter, Mul
from import_export.admin import ImportExportActionModelAdmin from import_export.admin import ImportExportActionModelAdmin
@admin.register(Transponders) @admin.register(Transponders)
class PolarizationAdmin(ImportExportActionModelAdmin, admin.ModelAdmin): class TranspondersAdmin(ImportExportActionModelAdmin, admin.ModelAdmin):
list_display = ( list_display = (
"sat_id", "sat_id",
"name", "name",
"zone_name", "zone_name",
"frequency", "downlink",
"uplink",
"frequency_range", "frequency_range",
"transfer",
"polarization", "polarization",
) )
list_filter = ( list_filter = (
("polarization", MultiSelectRelatedDropdownFilter), ("polarization", MultiSelectRelatedDropdownFilter),
("sat_id", MultiSelectRelatedDropdownFilter), ("sat_id", MultiSelectRelatedDropdownFilter),
("frequency", NumericRangeFilterBuilder()), # ("frequency", NumericRangeFilterBuilder()),
"zone_name" "zone_name"
) )
search_fields = ("name",) search_fields = ("name", "sat_id__name")
ordering = ("name",) ordering = ("name",)
# def sat_name(self, obj):
# return

View File

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

View 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='Перенос'),
),
]

View File

@@ -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='Название зоны'),
),
]

View File

@@ -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='Полоса'),
),
]

View File

@@ -1,15 +1,27 @@
from django.db import models from django.db import models
from mainapp.models import Satellite, Polarization, get_default_polarization 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): class Transponders(models.Model):
name = models.CharField(max_length=30, null=True, blank=True, verbose_name="Название транспондера") name = models.CharField(max_length=30, null=True, blank=True, verbose_name="Название транспондера")
frequency = models.FloatField(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="Полоса частот") frequency_range = models.FloatField(blank=True, null=True, verbose_name="Полоса")
zone_name = models.CharField(max_length=60, 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 = models.ForeignKey(
Polarization, default=get_default_polarization, on_delete=models.SET_DEFAULT, related_name="tran_polarizations", null=True, blank=True, verbose_name="Поляризация" 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="Спутник") 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): def __str__(self):
return self.name return self.name

View File

@@ -3,6 +3,7 @@ import re
import json import json
from .models import Transponders from .models import Transponders
from mainapp.models import Polarization, Satellite from mainapp.models import Polarization, Satellite
from io import BytesIO
def search_satellite_on_page(data: dict, satellite_name: str): def search_satellite_on_page(data: dict, satellite_name: str):
for pos, value in data.get('page', {}).get('positions').items(): for pos, value in data.get('page', {}).get('positions').items():
@@ -90,3 +91,68 @@ def parse_transponders_from_json(filepath: str):
) )
tran_obj.save() 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()

View File

@@ -19,7 +19,9 @@ dependencies = [
"django-leaflet>=0.32.0", "django-leaflet>=0.32.0",
"django-map-widgets>=0.5.1", "django-map-widgets>=0.5.1",
"django-more-admin-filters>=1.13", "django-more-admin-filters>=1.13",
"gdal", "dotenv>=0.9.9",
"geopy>=2.4.1",
"gunicorn>=23.0.0",
"lxml>=6.0.2", "lxml>=6.0.2",
"matplotlib>=3.10.7", "matplotlib>=3.10.7",
"numpy>=2.3.3", "numpy>=2.3.3",
@@ -28,9 +30,11 @@ dependencies = [
"psycopg>=3.2.10", "psycopg>=3.2.10",
"redis>=6.4.0", "redis>=6.4.0",
"requests>=2.32.5", "requests>=2.32.5",
"reverse-geocoder>=1.5.1",
"scikit-learn>=1.7.2", "scikit-learn>=1.7.2",
"setuptools>=80.9.0", "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
View File

@@ -212,7 +212,9 @@ dependencies = [
{ name = "django-leaflet" }, { name = "django-leaflet" },
{ name = "django-map-widgets" }, { name = "django-map-widgets" },
{ name = "django-more-admin-filters" }, { name = "django-more-admin-filters" },
{ name = "gdal" }, { name = "dotenv" },
{ name = "geopy" },
{ name = "gunicorn" },
{ name = "lxml" }, { name = "lxml" },
{ name = "matplotlib" }, { name = "matplotlib" },
{ name = "numpy" }, { name = "numpy" },
@@ -221,6 +223,7 @@ dependencies = [
{ name = "psycopg" }, { name = "psycopg" },
{ name = "redis" }, { name = "redis" },
{ name = "requests" }, { name = "requests" },
{ name = "reverse-geocoder" },
{ name = "scikit-learn" }, { name = "scikit-learn" },
{ name = "setuptools" }, { name = "setuptools" },
] ]
@@ -241,7 +244,9 @@ requires-dist = [
{ name = "django-leaflet", specifier = ">=0.32.0" }, { name = "django-leaflet", specifier = ">=0.32.0" },
{ name = "django-map-widgets", specifier = ">=0.5.1" }, { name = "django-map-widgets", specifier = ">=0.5.1" },
{ name = "django-more-admin-filters", specifier = ">=1.13" }, { 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 = "lxml", specifier = ">=6.0.2" },
{ name = "matplotlib", specifier = ">=3.10.7" }, { name = "matplotlib", specifier = ">=3.10.7" },
{ name = "numpy", specifier = ">=2.3.3" }, { name = "numpy", specifier = ">=2.3.3" },
@@ -250,10 +255,14 @@ requires-dist = [
{ name = "psycopg", specifier = ">=3.2.10" }, { name = "psycopg", specifier = ">=3.2.10" },
{ name = "redis", specifier = ">=6.4.0" }, { name = "redis", specifier = ">=6.4.0" },
{ name = "requests", specifier = ">=2.32.5" }, { name = "requests", specifier = ">=2.32.5" },
{ name = "reverse-geocoder", specifier = ">=1.5.1" },
{ name = "scikit-learn", specifier = ">=1.7.2" }, { name = "scikit-learn", specifier = ">=1.7.2" },
{ name = "setuptools", specifier = ">=80.9.0" }, { name = "setuptools", specifier = ">=80.9.0" },
] ]
[package.metadata.requires-dev]
dev = []
[[package]] [[package]]
name = "diff-match-patch" name = "diff-match-patch"
version = "20241021" 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" }, { 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]] [[package]]
name = "et-xmlfile" name = "et-xmlfile"
version = "2.0.0" version = "2.0.0"
@@ -453,16 +473,37 @@ wheels = [
] ]
[[package]] [[package]]
name = "gdal" name = "geographiclib"
version = "3.10.2" version = "2.1"
source = { path = "gdal-3.10.2-cp313-cp313-win_amd64.whl" } 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 = [ 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] [[package]]
requires-dist = [{ name = "numpy", marker = "extra == 'numpy'", specifier = ">1.0.0" }] name = "geopy"
provides-extras = ["numpy"] 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]] [[package]]
name = "idna" 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" }, { 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]] [[package]]
name = "python-slugify" name = "python-slugify"
version = "8.0.4" 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" }, { 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]] [[package]]
name = "scikit-learn" name = "scikit-learn"
version = "1.7.2" version = "1.7.2"