Снова улучшения и добавления

This commit is contained in:
2025-11-14 11:41:19 +03:00
parent 6a26991dc0
commit d61236dee2
12 changed files with 345 additions and 21 deletions

3
.gitignore vendored
View File

@@ -32,4 +32,5 @@ tiles
# Docker # Docker
# docker-* # docker-*
maplibre-gl-js-5.10.0.zip maplibre-gl-js-5.10.0.zip
cert.pem

View File

@@ -182,6 +182,98 @@
{% endif %} {% endif %}
</div> </div>
<!-- Транспондер -->
<div class="form-section">
<div class="form-section-header">
<h4>Транспондер</h4>
</div>
{% if object.transponder %}
<div class="row">
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Название:</label>
<div class="readonly-field">{{ object.transponder.name|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Спутник:</label>
<div class="readonly-field">{{ object.transponder.sat_id.name|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Downlink (МГц):</label>
<div class="readonly-field">{{ object.transponder.downlink|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Uplink (МГц):</label>
<div class="readonly-field">{{ object.transponder.uplink|default:"-" }}</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Полоса (МГц):</label>
<div class="readonly-field">{{ object.transponder.frequency_range|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Перенос (МГц):</label>
<div class="readonly-field">{{ object.transponder.transfer|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Поляризация:</label>
<div class="readonly-field">{{ object.transponder.polarization.name|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">ОСШ (дБ):</label>
<div class="readonly-field">{{ object.transponder.snr|default:"-" }}</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Зона покрытия:</label>
<div class="readonly-field">{{ object.transponder.zone_name|default:"-" }}</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Дата создания:</label>
<div class="readonly-field">
{% if object.transponder.created_at %}{{ object.transponder.created_at|date:"d.m.Y H:i" }}{% else %}-{% endif %}
</div>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Создан пользователем:</label>
<div class="readonly-field">
{% if object.transponder.created_by %}{{ object.transponder.created_by }}{% else %}-{% endif %}
</div>
</div>
</div>
</div>
{% else %}
<div class="mb-3">
<p>Нет данных о транспондере</p>
</div>
{% endif %}
</div>
<!-- Блок с картой --> <!-- Блок с картой -->
<div class="form-section"> <div class="form-section">
<div class="form-section-header"> <div class="form-section-header">

View File

@@ -248,6 +248,98 @@
</div> </div>
</div> </div>
<!-- Транспондер -->
<div class="form-section">
<div class="form-section-header">
<h4>Транспондер</h4>
</div>
{% if object.transponder %}
<div class="row">
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Название:</label>
<div class="readonly-field">{{ object.transponder.name|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Спутник:</label>
<div class="readonly-field">{{ object.transponder.sat_id.name|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Downlink (МГц):</label>
<div class="readonly-field">{{ object.transponder.downlink|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Uplink (МГц):</label>
<div class="readonly-field">{{ object.transponder.uplink|default:"-" }}</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Полоса (МГц):</label>
<div class="readonly-field">{{ object.transponder.frequency_range|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Перенос (МГц):</label>
<div class="readonly-field">{{ object.transponder.transfer|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">Поляризация:</label>
<div class="readonly-field">{{ object.transponder.polarization.name|default:"-" }}</div>
</div>
</div>
<div class="col-md-3">
<div class="mb-3">
<label class="form-label">ОСШ (дБ):</label>
<div class="readonly-field">{{ object.transponder.snr|default:"-" }}</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Зона покрытия:</label>
<div class="readonly-field">{{ object.transponder.zone_name|default:"-" }}</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Дата создания:</label>
<div class="readonly-field">
{% if object.transponder.created_at %}{{ object.transponder.created_at|date:"d.m.Y H:i" }}{% else %}-{% endif %}
</div>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Создан пользователем:</label>
<div class="readonly-field">
{% if object.transponder.created_by %}{{ object.transponder.created_by }}{% else %}-{% endif %}
</div>
</div>
</div>
</div>
{% else %}
<div class="mb-3">
<p class="text-muted">Нет данных о транспондере</p>
</div>
{% endif %}
</div>
<!-- Блок с картой --> <!-- Блок с картой -->
<div class="form-section"> <div class="form-section">
<div class="form-section-header"> <div class="form-section-header">

View File

@@ -1265,11 +1265,39 @@
<td class="text-muted">Перенос:</td> <td class="text-muted">Перенос:</td>
<td>${data.transfer || '-'} ${data.transfer ? 'МГц' : ''}</td> <td>${data.transfer || '-'} ${data.transfer ? 'МГц' : ''}</td>
</tr> </tr>
<tr>
<td class="text-muted">ОСШ:</td>
<td><strong>${data.snr || '-'} ${data.snr ? 'дБ' : ''}</strong></td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
</div> </div>
<div class="col-12">
<div class="card">
<div class="card-header bg-light">
<strong><i class="bi bi-clock-history"></i> Метаданные</strong>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<p class="mb-2">
<span class="text-muted">Дата создания:</span><br>
<strong>${data.created_at || '-'}</strong>
</p>
</div>
<div class="col-md-6">
<p class="mb-2">
<span class="text-muted">Создан пользователем:</span><br>
<strong>${data.created_by || '-'}</strong>
</p>
</div>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
`; `;

View File

@@ -58,7 +58,7 @@
<script> <script>
// Инициализация карты // Инициализация карты
let map = L.map('map').setView([55.75, 37.62], 5); let map = L.map('map').setView([55.75, 37.62], 10);
L.control.scale({ L.control.scale({
imperial: false, imperial: false,
metric: true metric: true

View File

@@ -28,6 +28,47 @@ from .models import (
Standard, Standard,
) )
def find_matching_transponder(satellite, frequency, polarization):
"""
Находит подходящий транспондер для заданных параметров.
Алгоритм:
1. Фильтрует транспондеры по спутнику и поляризации
2. Проверяет, входит ли частота в диапазон транспондера:
downlink - frequency_range/2 <= frequency <= downlink + frequency_range/2
3. Возвращает самый свежий транспондер (по created_at)
Args:
satellite: объект Satellite
frequency: частота в МГц
polarization: объект Polarization
Returns:
Transponders или None: найденный транспондер или None
"""
if not satellite or not polarization or frequency == -1.0:
return None
# Фильтруем транспондеры по спутнику и поляризации
transponders = Transponders.objects.filter(
sat_id=satellite,
polarization=polarization,
downlink__isnull=False,
frequency_range__isnull=False
).annotate(
# Вычисляем нижнюю и верхнюю границы диапазона
lower_bound=F('downlink') - F('frequency_range') / 2,
upper_bound=F('downlink') + F('frequency_range') / 2
).filter(
# Проверяем, входит ли частота в диапазон
lower_bound__lte=frequency,
upper_bound__gte=frequency
).order_by('-created_at') # Сортируем по дате создания (самые свежие первыми)
# Возвращаем самый свежий транспондер
return transponders.first()
# ============================================================================ # ============================================================================
# Константы # Константы
# ============================================================================ # ============================================================================
@@ -371,9 +412,15 @@ def _create_objitem_from_row(row, sat, source, user_to_use, consts):
# Пропускаем создание дубликата # Пропускаем создание дубликата
return return
# Создаем новый ObjItem и связываем с Source # Находим подходящий транспондер
transponder = find_matching_transponder(sat, freq, polarization_obj)
# Создаем новый ObjItem и связываем с Source и Transponder
obj_item = ObjItem.objects.create( obj_item = ObjItem.objects.create(
name=source_name, source=source, created_by=user_to_use name=source_name,
source=source,
transponder=transponder,
created_by=user_to_use
) )
# Создаем Parameter # Создаем Parameter
@@ -697,9 +744,15 @@ def _create_objitem_from_csv_row(row, source, user_to_use):
# Пропускаем создание дубликата # Пропускаем создание дубликата
return return
# Создаем новый ObjItem и связываем с Source # Находим подходящий транспондер
transponder = find_matching_transponder(sat_obj, row["freq"], pol_obj)
# Создаем новый ObjItem и связываем с Source и Transponder
obj_item = ObjItem.objects.create( obj_item = ObjItem.objects.create(
name=row["obj"], source=source, created_by=user_to_use name=row["obj"],
source=source,
transponder=transponder,
created_by=user_to_use
) )
# Создаем Parameter # Создаем Parameter

View File

@@ -310,9 +310,21 @@ class TransponderDataAPIView(LoginRequiredMixin, View):
try: try:
transponder = Transponders.objects.select_related( transponder = Transponders.objects.select_related(
'sat_id', 'sat_id',
'polarization' 'polarization',
'created_by__user'
).get(id=transponder_id) ).get(id=transponder_id)
# Format created_at date
created_at_str = '-'
if transponder.created_at:
local_time = timezone.localtime(transponder.created_at)
created_at_str = local_time.strftime("%d.%m.%Y %H:%M")
# Get created_by username
created_by_str = '-'
if transponder.created_by:
created_by_str = str(transponder.created_by)
data = { data = {
'id': transponder.id, 'id': transponder.id,
'name': transponder.name or '-', 'name': transponder.name or '-',
@@ -323,6 +335,9 @@ class TransponderDataAPIView(LoginRequiredMixin, View):
'polarization': transponder.polarization.name if transponder.polarization else '-', 'polarization': transponder.polarization.name if transponder.polarization else '-',
'zone_name': transponder.zone_name or '-', 'zone_name': transponder.zone_name or '-',
'transfer': f"{transponder.transfer:.3f}" if transponder.transfer else None, 'transfer': f"{transponder.transfer:.3f}" if transponder.transfer else None,
'snr': f"{transponder.snr:.1f}" if transponder.snr is not None else None,
'created_at': created_at_str,
'created_by': created_by_str,
} }
return JsonResponse(data) return JsonResponse(data)

View File

@@ -53,7 +53,8 @@ class AddTranspondersView(LoginRequiredMixin, FormMessageMixin, FormView):
uploaded_file = self.request.FILES["file"] uploaded_file = self.request.FILES["file"]
try: try:
content = uploaded_file.read() content = uploaded_file.read()
parse_transponders_from_xml(BytesIO(content)) # Передаем текущего пользователя в функцию парсинга
parse_transponders_from_xml(BytesIO(content), self.request.user.customuser)
except ValueError as e: except ValueError as e:
messages.error(self.request, f"Ошибка при чтении таблиц: {e}") messages.error(self.request, f"Ошибка при чтении таблиц: {e}")
return redirect("mainapp:add_trans") return redirect("mainapp:add_trans")

View File

@@ -520,6 +520,19 @@ class ObjItemFormView(
context["geo_form"] = GeoForm(prefix="geo") context["geo_form"] = GeoForm(prefix="geo")
return context return context
def get_object(self, queryset=None):
"""Override to add select_related for transponder."""
obj = super().get_object(queryset)
if obj and hasattr(obj, 'transponder'):
# Prefetch transponder data
obj = ObjItem.objects.select_related(
'transponder',
'transponder__sat_id',
'transponder__polarization',
'transponder__created_by__user',
).get(pk=obj.pk)
return obj
def form_valid(self, form): def form_valid(self, form):
# Get parameter form # Get parameter form
@@ -658,6 +671,10 @@ class ObjItemDetailView(LoginRequiredMixin, View):
'parameter_obj__polarization', 'parameter_obj__polarization',
'parameter_obj__modulation', 'parameter_obj__modulation',
'parameter_obj__standard', 'parameter_obj__standard',
'transponder',
'transponder__sat_id',
'transponder__polarization',
'transponder__created_by__user',
).first() ).first()
if not obj: if not obj:

View File

@@ -57,9 +57,9 @@ class Transponders(models.Model):
snr = models.FloatField( snr = models.FloatField(
blank=True, blank=True,
null=True, null=True,
verbose_name="Полоса", verbose_name="ОСШ, дБ",
# validators=[MinValueValidator(0), MaxValueValidator(1000)], # validators=[MinValueValidator(0), MaxValueValidator(1000)],
help_text="Полоса частот в МГц (0-1000)", help_text="Отношение сигнал/шум в децибелах",
) )
created_at = models.DateTimeField( created_at = models.DateTimeField(
auto_now_add=True, auto_now_add=True,

View File

@@ -22,7 +22,7 @@ def search_satellite_on_page(data: dict, satellite_name: str):
def get_footprint_data(position: str = 62) -> dict: def get_footprint_data(position: str = 62) -> dict:
"""Возвращает словарь с данным по footprint для спутников на выбранной долготе""" """Возвращает словарь с данным по footprint для спутников на выбранной долготе"""
response = requests.get(f"https://www.satbeams.com/footprints?position={position}") response = requests.get(f"https://www.satbeams.com/footprints?position={position}", verify=False)
response.raise_for_status() response.raise_for_status()
match = re.search(r'var data = ({.*?});', response.text, re.DOTALL) match = re.search(r'var data = ({.*?});', response.text, re.DOTALL)
if match: if match:
@@ -40,7 +40,7 @@ def get_footprint_data(position: str = 62) -> dict:
def get_all_page_data(url:str = 'https://www.satbeams.com/footprints') -> dict: def get_all_page_data(url:str = 'https://www.satbeams.com/footprints') -> dict:
"""Возвращает словарь с данными по всем спутникам на странице""" """Возвращает словарь с данными по всем спутникам на странице"""
response = requests.get(url) response = requests.get(url, verify="/etc/ssl/certs/ca-certificates.crt")
response.raise_for_status() response.raise_for_status()
match = re.search(r'var data = ({.*?});', response.text, re.DOTALL) match = re.search(r'var data = ({.*?});', response.text, re.DOTALL)
if match: if match:
@@ -78,7 +78,14 @@ def get_band_names(satellite_name: str) -> list[str]:
names = get_names_footprints_for_satellite(footprints, sat_id) names = get_names_footprints_for_satellite(footprints, sat_id)
return names return names
def parse_transponders_from_json(filepath: str): def parse_transponders_from_json(filepath: str, user=None):
"""
Парсит транспондеры из JSON файла.
Args:
filepath: путь к JSON файлу
user: пользователь для установки created_by и updated_by (optional)
"""
with open(filepath, encoding="utf-8") as jf: with open(filepath, encoding="utf-8") as jf:
data = json.load(jf) data = json.load(jf)
for sat_name, trans_zone in data["satellites"].items(): for sat_name, trans_zone in data["satellites"].items():
@@ -87,22 +94,40 @@ def parse_transponders_from_json(filepath: str):
f_b, f_e = tran["freq"][0].split("-") f_b, f_e = tran["freq"][0].split("-")
f = round((float(f_b) + float(f_e))/2, 3) f = round((float(f_b) + float(f_e))/2, 3)
f_range = round(abs(float(f_e) - float(f_b)), 3) f_range = round(abs(float(f_e) - float(f_b)), 3)
tran_obj = Transponders.objects.create(
pol_obj = Polarization.objects.get(name=tran["pol"])
sat_obj = Satellite.objects.get(name__iexact=sat_name)
tran_obj, created = Transponders.objects.get_or_create(
name=tran["name"], name=tran["name"],
frequency=f, polarization=pol_obj,
frequency_range=f_range, sat_id=sat_obj,
zone_name=zone, defaults={
polarization=Polarization.objects.get(name=tran["pol"]), "frequency": f,
sat_id=Satellite.objects.get(name__iexact=sat_name) "frequency_range": f_range,
"zone_name": zone,
}
) )
tran_obj.save()
# Устанавливаем пользователя, если передан
if user:
if created:
tran_obj.created_by = user
tran_obj.updated_by = user
tran_obj.save()
# Third-party imports (additional) # Third-party imports (additional)
from lxml import etree from lxml import etree
def parse_transponders_from_xml(data_in: BytesIO, user=None): def parse_transponders_from_xml(data_in: BytesIO, user=None):
"""
Парсит транспондеры из XML файла.
Args:
data_in: BytesIO объект с XML данными
user: пользователь для установки created_by и updated_by (optional)
"""
tree = etree.parse(data_in) tree = etree.parse(data_in)
ns = { ns = {
'i': 'http://www.w3.org/2001/XMLSchema-instance', 'i': 'http://www.w3.org/2001/XMLSchema-instance',

View File

@@ -86,7 +86,7 @@ class TileProxyView(View):
url = f"{self.TILE_BASE_URL}/{footprint_name}/{z}/{x}/{y}.png" url = f"{self.TILE_BASE_URL}/{footprint_name}/{z}/{x}/{y}.png"
try: try:
resp = requests.get(url, timeout=self.REQUEST_TIMEOUT) resp = requests.get(url, timeout=self.REQUEST_TIMEOUT, verify=r'/home/vesemir/DataStorage/cert.pem')
if resp.status_code == 200: if resp.status_code == 200:
response = HttpResponse(resp.content, content_type="image/png") response = HttpResponse(resp.content, content_type="image/png")
response["Access-Control-Allow-Origin"] = "*" response["Access-Control-Allow-Origin"] = "*"