Снова улучшения и добавления
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -32,4 +32,5 @@ tiles
|
||||
|
||||
# Docker
|
||||
# docker-*
|
||||
maplibre-gl-js-5.10.0.zip
|
||||
maplibre-gl-js-5.10.0.zip
|
||||
cert.pem
|
||||
@@ -182,6 +182,98 @@
|
||||
{% endif %}
|
||||
</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-header">
|
||||
|
||||
@@ -248,6 +248,98 @@
|
||||
</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-header">
|
||||
|
||||
@@ -1265,11 +1265,39 @@
|
||||
<td class="text-muted">Перенос:</td>
|
||||
<td>${data.transfer || '-'} ${data.transfer ? 'МГц' : ''}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-muted">ОСШ:</td>
|
||||
<td><strong>${data.snr || '-'} ${data.snr ? 'дБ' : ''}</strong></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</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>
|
||||
`;
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
|
||||
<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({
|
||||
imperial: false,
|
||||
metric: true
|
||||
|
||||
@@ -28,6 +28,47 @@ from .models import (
|
||||
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
|
||||
|
||||
# Создаем новый ObjItem и связываем с Source
|
||||
# Находим подходящий транспондер
|
||||
transponder = find_matching_transponder(sat, freq, polarization_obj)
|
||||
|
||||
# Создаем новый ObjItem и связываем с Source и Transponder
|
||||
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
|
||||
@@ -697,9 +744,15 @@ def _create_objitem_from_csv_row(row, source, user_to_use):
|
||||
# Пропускаем создание дубликата
|
||||
return
|
||||
|
||||
# Создаем новый ObjItem и связываем с Source
|
||||
# Находим подходящий транспондер
|
||||
transponder = find_matching_transponder(sat_obj, row["freq"], pol_obj)
|
||||
|
||||
# Создаем новый ObjItem и связываем с Source и Transponder
|
||||
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
|
||||
|
||||
@@ -310,9 +310,21 @@ class TransponderDataAPIView(LoginRequiredMixin, View):
|
||||
try:
|
||||
transponder = Transponders.objects.select_related(
|
||||
'sat_id',
|
||||
'polarization'
|
||||
'polarization',
|
||||
'created_by__user'
|
||||
).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 = {
|
||||
'id': transponder.id,
|
||||
'name': transponder.name or '-',
|
||||
@@ -323,6 +335,9 @@ class TransponderDataAPIView(LoginRequiredMixin, View):
|
||||
'polarization': transponder.polarization.name if transponder.polarization else '-',
|
||||
'zone_name': transponder.zone_name or '-',
|
||||
'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)
|
||||
|
||||
@@ -53,7 +53,8 @@ class AddTranspondersView(LoginRequiredMixin, FormMessageMixin, FormView):
|
||||
uploaded_file = self.request.FILES["file"]
|
||||
try:
|
||||
content = uploaded_file.read()
|
||||
parse_transponders_from_xml(BytesIO(content))
|
||||
# Передаем текущего пользователя в функцию парсинга
|
||||
parse_transponders_from_xml(BytesIO(content), self.request.user.customuser)
|
||||
except ValueError as e:
|
||||
messages.error(self.request, f"Ошибка при чтении таблиц: {e}")
|
||||
return redirect("mainapp:add_trans")
|
||||
|
||||
@@ -520,6 +520,19 @@ class ObjItemFormView(
|
||||
context["geo_form"] = GeoForm(prefix="geo")
|
||||
|
||||
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):
|
||||
# Get parameter form
|
||||
@@ -658,6 +671,10 @@ class ObjItemDetailView(LoginRequiredMixin, View):
|
||||
'parameter_obj__polarization',
|
||||
'parameter_obj__modulation',
|
||||
'parameter_obj__standard',
|
||||
'transponder',
|
||||
'transponder__sat_id',
|
||||
'transponder__polarization',
|
||||
'transponder__created_by__user',
|
||||
).first()
|
||||
|
||||
if not obj:
|
||||
|
||||
@@ -57,9 +57,9 @@ class Transponders(models.Model):
|
||||
snr = models.FloatField(
|
||||
blank=True,
|
||||
null=True,
|
||||
verbose_name="Полоса",
|
||||
verbose_name="ОСШ, дБ",
|
||||
# validators=[MinValueValidator(0), MaxValueValidator(1000)],
|
||||
help_text="Полоса частот в МГц (0-1000)",
|
||||
help_text="Отношение сигнал/шум в децибелах",
|
||||
)
|
||||
created_at = models.DateTimeField(
|
||||
auto_now_add=True,
|
||||
|
||||
@@ -22,7 +22,7 @@ def search_satellite_on_page(data: dict, satellite_name: str):
|
||||
|
||||
def get_footprint_data(position: str = 62) -> dict:
|
||||
"""Возвращает словарь с данным по 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()
|
||||
match = re.search(r'var data = ({.*?});', response.text, re.DOTALL)
|
||||
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:
|
||||
"""Возвращает словарь с данными по всем спутникам на странице"""
|
||||
response = requests.get(url)
|
||||
response = requests.get(url, verify="/etc/ssl/certs/ca-certificates.crt")
|
||||
response.raise_for_status()
|
||||
match = re.search(r'var data = ({.*?});', response.text, re.DOTALL)
|
||||
if match:
|
||||
@@ -78,7 +78,14 @@ def get_band_names(satellite_name: str) -> list[str]:
|
||||
names = get_names_footprints_for_satellite(footprints, sat_id)
|
||||
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:
|
||||
data = json.load(jf)
|
||||
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 = round((float(f_b) + float(f_e))/2, 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"],
|
||||
frequency=f,
|
||||
frequency_range=f_range,
|
||||
zone_name=zone,
|
||||
polarization=Polarization.objects.get(name=tran["pol"]),
|
||||
sat_id=Satellite.objects.get(name__iexact=sat_name)
|
||||
polarization=pol_obj,
|
||||
sat_id=sat_obj,
|
||||
defaults={
|
||||
"frequency": f,
|
||||
"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)
|
||||
from lxml import etree
|
||||
|
||||
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)
|
||||
ns = {
|
||||
'i': 'http://www.w3.org/2001/XMLSchema-instance',
|
||||
|
||||
@@ -86,7 +86,7 @@ class TileProxyView(View):
|
||||
url = f"{self.TILE_BASE_URL}/{footprint_name}/{z}/{x}/{y}.png"
|
||||
|
||||
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:
|
||||
response = HttpResponse(resp.content, content_type="image/png")
|
||||
response["Access-Control-Allow-Origin"] = "*"
|
||||
|
||||
Reference in New Issue
Block a user