Исправил отображения объектов в источниках
This commit is contained in:
@@ -24,6 +24,7 @@ urlpatterns = [
|
|||||||
path('admin/', admin.site.urls, name='admin'),
|
path('admin/', admin.site.urls, name='admin'),
|
||||||
path('', include('mainapp.urls', namespace='mainapp')),
|
path('', include('mainapp.urls', namespace='mainapp')),
|
||||||
path('', include('mapsapp.urls', namespace='mapsapp')),
|
path('', include('mapsapp.urls', namespace='mapsapp')),
|
||||||
|
path('lyngsat/', include('lyngsatapp.urls', namespace='lyngsatapp')),
|
||||||
# Authentication URLs
|
# Authentication URLs
|
||||||
path('login/', auth_views.LoginView.as_view(), name='login'),
|
path('login/', auth_views.LoginView.as_view(), name='login'),
|
||||||
path('logout/', custom_logout, name='logout'),
|
path('logout/', custom_logout, name='logout'),
|
||||||
|
|||||||
428
dbapp/lyngsatapp/templates/lyngsatapp/lyngsat_list.html
Normal file
428
dbapp/lyngsatapp/templates/lyngsatapp/lyngsat_list.html
Normal file
@@ -0,0 +1,428 @@
|
|||||||
|
{% extends 'mainapp/base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Источники LyngSat{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<style>
|
||||||
|
.table-responsive tr.selected {
|
||||||
|
background-color: #d4edff;
|
||||||
|
}
|
||||||
|
.sticky-top {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid px-3">
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12">
|
||||||
|
<h2>Источники LyngSat</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Toolbar -->
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex flex-wrap align-items-center gap-3">
|
||||||
|
<!-- Search bar -->
|
||||||
|
<div style="min-width: 200px; flex-grow: 1; max-width: 400px;">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" id="toolbar-search" class="form-control" placeholder="Поиск по ID..."
|
||||||
|
value="{{ search_query|default:'' }}">
|
||||||
|
<button type="button" class="btn btn-outline-primary"
|
||||||
|
onclick="performSearch()">Найти</button>
|
||||||
|
<button type="button" class="btn btn-outline-secondary"
|
||||||
|
onclick="clearSearch()">Очистить</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Items per page select -->
|
||||||
|
<div>
|
||||||
|
<label for="items-per-page" class="form-label mb-0">Показать:</label>
|
||||||
|
<select name="items_per_page" id="items-per-page"
|
||||||
|
class="form-select form-select-sm d-inline-block" style="width: auto;"
|
||||||
|
onchange="updateItemsPerPage()">
|
||||||
|
{% for option in available_items_per_page %}
|
||||||
|
<option value="{{ option }}" {% if option == items_per_page %}selected{% endif %}>
|
||||||
|
{{ option }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Filter Toggle Button -->
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-outline-primary btn-sm" type="button" data-bs-toggle="offcanvas"
|
||||||
|
data-bs-target="#offcanvasFilters" aria-controls="offcanvasFilters">
|
||||||
|
<i class="bi bi-funnel"></i> Фильтры
|
||||||
|
<span id="filterCounter" class="badge bg-danger" style="display: none;">0</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination -->
|
||||||
|
<div class="ms-auto">
|
||||||
|
{% include 'mainapp/components/_pagination.html' with page_obj=page_obj show_info=True %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Offcanvas Filter Panel -->
|
||||||
|
<div class="offcanvas offcanvas-start" tabindex="-1" id="offcanvasFilters" aria-labelledby="offcanvasFiltersLabel">
|
||||||
|
<div class="offcanvas-header">
|
||||||
|
<h5 class="offcanvas-title" id="offcanvasFiltersLabel">Фильтры</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Закрыть"></button>
|
||||||
|
</div>
|
||||||
|
<div class="offcanvas-body">
|
||||||
|
<form method="get" id="filter-form">
|
||||||
|
<!-- Satellite Selection - Multi-select -->
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Спутник:</label>
|
||||||
|
<div class="d-flex justify-content-between mb-1">
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('satellite_id', true)">Выбрать</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('satellite_id', false)">Снять</button>
|
||||||
|
</div>
|
||||||
|
<select name="satellite_id" class="form-select form-select-sm mb-2" multiple size="6">
|
||||||
|
{% for satellite in satellites %}
|
||||||
|
<option value="{{ satellite.id }}" {% if satellite.id in selected_satellites %}selected{% endif %}>
|
||||||
|
{{ satellite.name }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Polarization Selection - Multi-select -->
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Поляризация:</label>
|
||||||
|
<div class="d-flex justify-content-between mb-1">
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('polarization_id', true)">Выбрать</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('polarization_id', false)">Снять</button>
|
||||||
|
</div>
|
||||||
|
<select name="polarization_id" class="form-select form-select-sm mb-2" multiple size="4">
|
||||||
|
{% for polarization in polarizations %}
|
||||||
|
<option value="{{ polarization.id }}" {% if polarization.id in selected_polarizations %}selected{% endif %}>
|
||||||
|
{{ polarization.name }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modulation Selection - Multi-select -->
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Модуляция:</label>
|
||||||
|
<div class="d-flex justify-content-between mb-1">
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('modulation_id', true)">Выбрать</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('modulation_id', false)">Снять</button>
|
||||||
|
</div>
|
||||||
|
<select name="modulation_id" class="form-select form-select-sm mb-2" multiple size="4">
|
||||||
|
{% for modulation in modulations %}
|
||||||
|
<option value="{{ modulation.id }}" {% if modulation.id in selected_modulations %}selected{% endif %}>
|
||||||
|
{{ modulation.name }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Standard Selection - Multi-select -->
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Стандарт:</label>
|
||||||
|
<div class="d-flex justify-content-between mb-1">
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('standard_id', true)">Выбрать</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||||
|
onclick="selectAllOptions('standard_id', false)">Снять</button>
|
||||||
|
</div>
|
||||||
|
<select name="standard_id" class="form-select form-select-sm mb-2" multiple size="4">
|
||||||
|
{% for standard in standards %}
|
||||||
|
<option value="{{ standard.id }}" {% if standard.id in selected_standards %}selected{% endif %}>
|
||||||
|
{{ standard.name }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Frequency Filter -->
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Частота, МГц:</label>
|
||||||
|
<input type="number" step="0.001" name="freq_min" class="form-control form-control-sm mb-1"
|
||||||
|
placeholder="От" value="{{ freq_min|default:'' }}">
|
||||||
|
<input type="number" step="0.001" name="freq_max" class="form-control form-control-sm"
|
||||||
|
placeholder="До" value="{{ freq_max|default:'' }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Symbol Rate Filter -->
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Символьная скорость, БОД:</label>
|
||||||
|
<input type="number" step="0.001" name="sym_min" class="form-control form-control-sm mb-1"
|
||||||
|
placeholder="От" value="{{ sym_min|default:'' }}">
|
||||||
|
<input type="number" step="0.001" name="sym_max" class="form-control form-control-sm"
|
||||||
|
placeholder="До" value="{{ sym_max|default:'' }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Date Filter -->
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Дата обновления:</label>
|
||||||
|
<input type="date" name="date_from" id="date_from" class="form-control form-control-sm mb-1"
|
||||||
|
placeholder="От" value="{{ date_from|default:'' }}">
|
||||||
|
<input type="date" name="date_to" id="date_to" class="form-control form-control-sm"
|
||||||
|
placeholder="До" value="{{ date_to|default:'' }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Apply Filters and Reset Buttons -->
|
||||||
|
<div class="d-grid gap-2 mt-2">
|
||||||
|
<button type="submit" class="btn btn-primary btn-sm">Применить</button>
|
||||||
|
<a href="?" class="btn btn-secondary btn-sm">Сбросить</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main Table -->
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="table-responsive" style="max-height: 75vh; overflow-y: auto;">
|
||||||
|
<table class="table table-striped table-hover table-sm" style="font-size: 0.85rem;">
|
||||||
|
<thead class="table-dark sticky-top">
|
||||||
|
<tr>
|
||||||
|
<th scope="col" class="text-center" style="min-width: 60px;">
|
||||||
|
<a href="javascript:void(0)" onclick="updateSort('id')" class="text-white text-decoration-none">
|
||||||
|
ID
|
||||||
|
{% if sort == 'id' %}
|
||||||
|
<i class="bi bi-arrow-up"></i>
|
||||||
|
{% elif sort == '-id' %}
|
||||||
|
<i class="bi bi-arrow-down"></i>
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
</th>
|
||||||
|
<th scope="col" style="min-width: 120px;">Спутник</th>
|
||||||
|
<th scope="col" style="min-width: 100px;">
|
||||||
|
<a href="javascript:void(0)" onclick="updateSort('frequency')" class="text-white text-decoration-none">
|
||||||
|
Частота, МГц
|
||||||
|
{% if sort == 'frequency' %}
|
||||||
|
<i class="bi bi-arrow-up"></i>
|
||||||
|
{% elif sort == '-frequency' %}
|
||||||
|
<i class="bi bi-arrow-down"></i>
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
</th>
|
||||||
|
<th scope="col" style="min-width: 100px;">Поляризация</th>
|
||||||
|
<th scope="col" style="min-width: 120px;">
|
||||||
|
<a href="javascript:void(0)" onclick="updateSort('sym_velocity')" class="text-white text-decoration-none">
|
||||||
|
Сим. скорость, БОД
|
||||||
|
{% if sort == 'sym_velocity' %}
|
||||||
|
<i class="bi bi-arrow-up"></i>
|
||||||
|
{% elif sort == '-sym_velocity' %}
|
||||||
|
<i class="bi bi-arrow-down"></i>
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
</th>
|
||||||
|
<th scope="col" style="min-width: 100px;">Модуляция</th>
|
||||||
|
<th scope="col" style="min-width: 100px;">Стандарт</th>
|
||||||
|
<th scope="col" style="min-width: 80px;">FEC</th>
|
||||||
|
<th scope="col" style="min-width: 150px;">Описание</th>
|
||||||
|
<th scope="col" style="min-width: 120px;">
|
||||||
|
<a href="javascript:void(0)" onclick="updateSort('last_update')" class="text-white text-decoration-none">
|
||||||
|
Обновлено
|
||||||
|
{% if sort == 'last_update' %}
|
||||||
|
<i class="bi bi-arrow-up"></i>
|
||||||
|
{% elif sort == '-last_update' %}
|
||||||
|
<i class="bi bi-arrow-down"></i>
|
||||||
|
{% endif %}
|
||||||
|
</a>
|
||||||
|
</th>
|
||||||
|
<th scope="col" style="min-width: 100px;">Ссылка</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for item in lyngsat_items %}
|
||||||
|
<tr>
|
||||||
|
<td class="text-center">{{ item.id }}</td>
|
||||||
|
<td>{{ item.id_satellite.name|default:"-" }}</td>
|
||||||
|
<td>{{ item.frequency|floatformat:3|default:"-" }}</td>
|
||||||
|
<td>{{ item.polarization.name|default:"-" }}</td>
|
||||||
|
<td>{{ item.sym_velocity|floatformat:3|default:"-" }}</td>
|
||||||
|
<td>{{ item.modulation.name|default:"-" }}</td>
|
||||||
|
<td>{{ item.standard.name|default:"-" }}</td>
|
||||||
|
<td>{{ item.fec|default:"-" }}</td>
|
||||||
|
<td>{{ item.channel_info|default:"-" }}</td>
|
||||||
|
<td>{{ item.last_update|date:"d.m.Y"|default:"-" }}</td>
|
||||||
|
<td>
|
||||||
|
{% if item.url %}
|
||||||
|
<a href="{{ item.url }}" target="_blank" class="btn btn-sm btn-outline-primary" title="Открыть ссылку">
|
||||||
|
<i class="bi bi-link-45deg"></i>
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="11" class="text-center text-muted">Нет данных для отображения</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_js %}
|
||||||
|
<script>
|
||||||
|
// Search functionality
|
||||||
|
function performSearch() {
|
||||||
|
const searchValue = document.getElementById('toolbar-search').value.trim();
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
|
if (searchValue) {
|
||||||
|
urlParams.set('search', searchValue);
|
||||||
|
} else {
|
||||||
|
urlParams.delete('search');
|
||||||
|
}
|
||||||
|
|
||||||
|
urlParams.delete('page');
|
||||||
|
window.location.search = urlParams.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearSearch() {
|
||||||
|
document.getElementById('toolbar-search').value = '';
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
urlParams.delete('search');
|
||||||
|
urlParams.delete('page');
|
||||||
|
window.location.search = urlParams.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Enter key in search input
|
||||||
|
document.getElementById('toolbar-search').addEventListener('keypress', function(e) {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
performSearch();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Items per page functionality
|
||||||
|
function updateItemsPerPage() {
|
||||||
|
const itemsPerPage = document.getElementById('items-per-page').value;
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
urlParams.set('items_per_page', itemsPerPage);
|
||||||
|
urlParams.delete('page');
|
||||||
|
window.location.search = urlParams.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sorting functionality
|
||||||
|
function updateSort(field) {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const currentSort = urlParams.get('sort');
|
||||||
|
|
||||||
|
let newSort;
|
||||||
|
if (currentSort === field) {
|
||||||
|
newSort = '-' + field;
|
||||||
|
} else if (currentSort === '-' + field) {
|
||||||
|
newSort = field;
|
||||||
|
} else {
|
||||||
|
newSort = field;
|
||||||
|
}
|
||||||
|
|
||||||
|
urlParams.set('sort', newSort);
|
||||||
|
urlParams.delete('page');
|
||||||
|
window.location.search = urlParams.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to select/deselect all options in a select element
|
||||||
|
function selectAllOptions(selectName, selectAll) {
|
||||||
|
const selectElement = document.querySelector(`select[name="${selectName}"]`);
|
||||||
|
if (selectElement) {
|
||||||
|
for (let i = 0; i < selectElement.options.length; i++) {
|
||||||
|
selectElement.options[i].selected = selectAll;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter counter functionality
|
||||||
|
function updateFilterCounter() {
|
||||||
|
const form = document.getElementById('filter-form');
|
||||||
|
const formData = new FormData(form);
|
||||||
|
let filterCount = 0;
|
||||||
|
|
||||||
|
// Count non-empty form fields
|
||||||
|
for (const [key, value] of formData.entries()) {
|
||||||
|
if (value && value.trim() !== '') {
|
||||||
|
// For multi-select fields, skip counting individual selections
|
||||||
|
if (key === 'satellite_id' || key === 'polarization_id' || key === 'modulation_id' || key === 'standard_id') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
filterCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count selected options in multi-select fields
|
||||||
|
const multiSelectFields = ['satellite_id', 'polarization_id', 'modulation_id', 'standard_id'];
|
||||||
|
multiSelectFields.forEach(fieldName => {
|
||||||
|
const selectElement = document.querySelector(`select[name="${fieldName}"]`);
|
||||||
|
if (selectElement) {
|
||||||
|
const selectedOptions = Array.from(selectElement.selectedOptions).filter(opt => opt.selected);
|
||||||
|
if (selectedOptions.length > 0) {
|
||||||
|
filterCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Display the filter counter
|
||||||
|
const counterElement = document.getElementById('filterCounter');
|
||||||
|
if (counterElement) {
|
||||||
|
if (filterCount > 0) {
|
||||||
|
counterElement.textContent = filterCount;
|
||||||
|
counterElement.style.display = 'inline';
|
||||||
|
} else {
|
||||||
|
counterElement.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize on page load
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Update filter counter on page load
|
||||||
|
updateFilterCounter();
|
||||||
|
|
||||||
|
// Add event listeners to form elements to update counter when filters change
|
||||||
|
const form = document.getElementById('filter-form');
|
||||||
|
if (form) {
|
||||||
|
const inputFields = form.querySelectorAll('input[type="text"], input[type="number"], input[type="date"]');
|
||||||
|
inputFields.forEach(input => {
|
||||||
|
input.addEventListener('input', updateFilterCounter);
|
||||||
|
input.addEventListener('change', updateFilterCounter);
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectFields = form.querySelectorAll('select');
|
||||||
|
selectFields.forEach(select => {
|
||||||
|
select.addEventListener('change', updateFilterCounter);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update counter when offcanvas is shown
|
||||||
|
const offcanvasElement = document.getElementById('offcanvasFilters');
|
||||||
|
if (offcanvasElement) {
|
||||||
|
offcanvasElement.addEventListener('show.bs.offcanvas', updateFilterCounter);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
8
dbapp/lyngsatapp/urls.py
Normal file
8
dbapp/lyngsatapp/urls.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
app_name = 'lyngsatapp'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', views.LyngSatListView.as_view(), name='lyngsat_list'),
|
||||||
|
]
|
||||||
@@ -1,3 +1,147 @@
|
|||||||
from django.shortcuts import render
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
from django.core.paginator import Paginator
|
||||||
|
from django.db.models import Q
|
||||||
|
from django.views.generic import ListView
|
||||||
|
|
||||||
# Create your views here.
|
from .models import LyngSat
|
||||||
|
from mainapp.models import Satellite, Polarization, Modulation, Standard
|
||||||
|
from mainapp.utils import parse_pagination_params
|
||||||
|
|
||||||
|
|
||||||
|
class LyngSatListView(LoginRequiredMixin, ListView):
|
||||||
|
"""
|
||||||
|
Представление для отображения списка источников LyngSat с фильтрацией и пагинацией.
|
||||||
|
"""
|
||||||
|
model = LyngSat
|
||||||
|
template_name = 'lyngsatapp/lyngsat_list.html'
|
||||||
|
context_object_name = 'lyngsat_items'
|
||||||
|
paginate_by = 50
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""
|
||||||
|
Возвращает отфильтрованный и отсортированный queryset.
|
||||||
|
"""
|
||||||
|
queryset = LyngSat.objects.select_related(
|
||||||
|
'id_satellite',
|
||||||
|
'polarization',
|
||||||
|
'modulation',
|
||||||
|
'standard'
|
||||||
|
).all()
|
||||||
|
|
||||||
|
# Поиск по ID
|
||||||
|
search_query = self.request.GET.get('search', '').strip()
|
||||||
|
if search_query:
|
||||||
|
try:
|
||||||
|
search_id = int(search_query)
|
||||||
|
queryset = queryset.filter(id=search_id)
|
||||||
|
except ValueError:
|
||||||
|
queryset = queryset.none()
|
||||||
|
|
||||||
|
# Фильтр по спутнику
|
||||||
|
satellite_ids = self.request.GET.getlist('satellite_id')
|
||||||
|
if satellite_ids:
|
||||||
|
queryset = queryset.filter(id_satellite_id__in=satellite_ids)
|
||||||
|
|
||||||
|
# Фильтр по поляризации
|
||||||
|
polarization_ids = self.request.GET.getlist('polarization_id')
|
||||||
|
if polarization_ids:
|
||||||
|
queryset = queryset.filter(polarization_id__in=polarization_ids)
|
||||||
|
|
||||||
|
# Фильтр по модуляции
|
||||||
|
modulation_ids = self.request.GET.getlist('modulation_id')
|
||||||
|
if modulation_ids:
|
||||||
|
queryset = queryset.filter(modulation_id__in=modulation_ids)
|
||||||
|
|
||||||
|
# Фильтр по стандарту
|
||||||
|
standard_ids = self.request.GET.getlist('standard_id')
|
||||||
|
if standard_ids:
|
||||||
|
queryset = queryset.filter(standard_id__in=standard_ids)
|
||||||
|
|
||||||
|
# Фильтр по частоте
|
||||||
|
freq_min = self.request.GET.get('freq_min', '').strip()
|
||||||
|
freq_max = self.request.GET.get('freq_max', '').strip()
|
||||||
|
if freq_min:
|
||||||
|
try:
|
||||||
|
queryset = queryset.filter(frequency__gte=float(freq_min))
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
if freq_max:
|
||||||
|
try:
|
||||||
|
queryset = queryset.filter(frequency__lte=float(freq_max))
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Фильтр по символьной скорости
|
||||||
|
sym_min = self.request.GET.get('sym_min', '').strip()
|
||||||
|
sym_max = self.request.GET.get('sym_max', '').strip()
|
||||||
|
if sym_min:
|
||||||
|
try:
|
||||||
|
queryset = queryset.filter(sym_velocity__gte=float(sym_min))
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
if sym_max:
|
||||||
|
try:
|
||||||
|
queryset = queryset.filter(sym_velocity__lte=float(sym_max))
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Фильтр по дате обновления
|
||||||
|
date_from = self.request.GET.get('date_from', '').strip()
|
||||||
|
date_to = self.request.GET.get('date_to', '').strip()
|
||||||
|
if date_from:
|
||||||
|
queryset = queryset.filter(last_update__gte=date_from)
|
||||||
|
if date_to:
|
||||||
|
queryset = queryset.filter(last_update__lte=date_to)
|
||||||
|
|
||||||
|
# Сортировка
|
||||||
|
sort = self.request.GET.get('sort', '-id')
|
||||||
|
valid_sort_fields = ['id', '-id', 'frequency', '-frequency', 'sym_velocity', '-sym_velocity', 'last_update', '-last_update']
|
||||||
|
if sort in valid_sort_fields:
|
||||||
|
queryset = queryset.order_by(sort)
|
||||||
|
else:
|
||||||
|
queryset = queryset.order_by('-id')
|
||||||
|
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Добавляет дополнительный контекст для шаблона.
|
||||||
|
"""
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
# Параметры пагинации
|
||||||
|
page_number, items_per_page = parse_pagination_params(self.request, default_per_page=50)
|
||||||
|
context['items_per_page'] = items_per_page
|
||||||
|
context['available_items_per_page'] = [25, 50, 100, 200, 500]
|
||||||
|
|
||||||
|
# Пагинация
|
||||||
|
paginator = Paginator(self.get_queryset(), items_per_page)
|
||||||
|
page_obj = paginator.get_page(page_number)
|
||||||
|
context['page_obj'] = page_obj
|
||||||
|
context['lyngsat_items'] = page_obj.object_list
|
||||||
|
|
||||||
|
# Параметры поиска и фильтрации
|
||||||
|
context['search_query'] = self.request.GET.get('search', '')
|
||||||
|
context['sort'] = self.request.GET.get('sort', '-id')
|
||||||
|
|
||||||
|
# Данные для фильтров
|
||||||
|
context['satellites'] = Satellite.objects.all().order_by('name')
|
||||||
|
context['polarizations'] = Polarization.objects.all().order_by('name')
|
||||||
|
context['modulations'] = Modulation.objects.all().order_by('name')
|
||||||
|
context['standards'] = Standard.objects.all().order_by('name')
|
||||||
|
|
||||||
|
# Выбранные фильтры
|
||||||
|
context['selected_satellites'] = [int(x) for x in self.request.GET.getlist('satellite_id') if x.isdigit()]
|
||||||
|
context['selected_polarizations'] = [int(x) for x in self.request.GET.getlist('polarization_id') if x.isdigit()]
|
||||||
|
context['selected_modulations'] = [int(x) for x in self.request.GET.getlist('modulation_id') if x.isdigit()]
|
||||||
|
context['selected_standards'] = [int(x) for x in self.request.GET.getlist('standard_id') if x.isdigit()]
|
||||||
|
|
||||||
|
# Параметры фильтров
|
||||||
|
context['freq_min'] = self.request.GET.get('freq_min', '')
|
||||||
|
context['freq_max'] = self.request.GET.get('freq_max', '')
|
||||||
|
context['sym_min'] = self.request.GET.get('sym_min', '')
|
||||||
|
context['sym_max'] = self.request.GET.get('sym_max', '')
|
||||||
|
context['date_from'] = self.request.GET.get('date_from', '')
|
||||||
|
context['date_to'] = self.request.GET.get('date_to', '')
|
||||||
|
|
||||||
|
return context
|
||||||
|
|||||||
@@ -205,6 +205,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Unlink All LyngSat Sources Card -->
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<div class="card h-100 shadow-sm border-0">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="d-flex align-items-center mb-3">
|
||||||
|
<div class="bg-warning bg-opacity-10 rounded-circle p-2 me-3">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" class="bi bi-unlink text-warning" viewBox="0 0 16 16">
|
||||||
|
<path d="M6.354 5.5H4a3 3 0 0 0 0 6h3a3 3 0 0 0 2.83-4H9q-.13 0-.25.031A2 2 0 0 1 7 10.5H4a2 2 0 1 1 0-4h1.535c.218-.376.495-.714.82-1z"/>
|
||||||
|
<path d="M9 5.5a3 3 0 0 0-2.83 4h1.098A2 2 0 0 1 9 6.5h3a2 2 0 1 1 0 4h-1.535a4 4 0 0 1-.82 1H12a3 3 0 1 0 0-6z"/>
|
||||||
|
<path d="M1 1l14 14"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h3 class="card-title mb-0">Отвязка всех источников LyngSat</h3>
|
||||||
|
</div>
|
||||||
|
<p class="card-text">Отвязать все источники LyngSat от объектов. Все объекты перестанут отображаться как "ТВ" источники. Операция обратима через повторную привязку.</p>
|
||||||
|
<a href="{% url 'mainapp:unlink_all_lyngsat' %}" class="btn btn-warning">
|
||||||
|
Отвязать все источники
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -16,9 +16,15 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'mainapp:objitem_list' %}">Объекты</a>
|
<a class="nav-link" href="{% url 'mainapp:objitem_list' %}">Объекты</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{% url 'mainapp:home' %}">Источники</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'mainapp:transponder_list' %}">Транспондеры</a>
|
<a class="nav-link" href="{% url 'mainapp:transponder_list' %}">Транспондеры</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="{% url 'lyngsatapp:lyngsat_list' %}">LyngSat</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'mainapp:actions' %}">Действия</a>
|
<a class="nav-link" href="{% url 'mainapp:actions' %}">Действия</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -192,6 +192,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- LyngSat Filter -->
|
||||||
|
<div class="mb-2">
|
||||||
|
<label class="form-label">Тип источника (ТВ):</label>
|
||||||
|
<div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="checkbox" name="has_lyngsat" id="has_lyngsat_1"
|
||||||
|
value="1" {% if has_lyngsat == '1' %}checked{% endif %}>
|
||||||
|
<label class="form-check-label" for="has_lyngsat_1">Есть</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="checkbox" name="has_lyngsat" id="has_lyngsat_0"
|
||||||
|
value="0" {% if has_lyngsat == '0' %}checked{% endif %}>
|
||||||
|
<label class="form-check-label" for="has_lyngsat_0">Нет</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Point Count Filter -->
|
<!-- Point Count Filter -->
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label class="form-label">Количество точек:</label>
|
<label class="form-label">Количество точек:</label>
|
||||||
@@ -246,6 +263,9 @@
|
|||||||
<th scope="col" style="min-width: 150px;">Координаты Кубсата</th>
|
<th scope="col" style="min-width: 150px;">Координаты Кубсата</th>
|
||||||
<th scope="col" style="min-width: 150px;">Координаты оперативников</th>
|
<th scope="col" style="min-width: 150px;">Координаты оперативников</th>
|
||||||
<th scope="col" style="min-width: 150px;">Координаты справочные</th>
|
<th scope="col" style="min-width: 150px;">Координаты справочные</th>
|
||||||
|
{% if has_any_lyngsat %}
|
||||||
|
<th scope="col" class="text-center" style="min-width: 80px;">Тип источника</th>
|
||||||
|
{% endif %}
|
||||||
<th scope="col" class="text-center" style="min-width: 100px;">
|
<th scope="col" class="text-center" style="min-width: 100px;">
|
||||||
<a href="javascript:void(0)" onclick="updateSort('objitem_count')" class="text-white text-decoration-none">
|
<a href="javascript:void(0)" onclick="updateSort('objitem_count')" class="text-white text-decoration-none">
|
||||||
Кол-во точек
|
Кол-во точек
|
||||||
@@ -292,6 +312,18 @@
|
|||||||
<td>{{ source.coords_kupsat }}</td>
|
<td>{{ source.coords_kupsat }}</td>
|
||||||
<td>{{ source.coords_valid }}</td>
|
<td>{{ source.coords_valid }}</td>
|
||||||
<td>{{ source.coords_reference }}</td>
|
<td>{{ source.coords_reference }}</td>
|
||||||
|
{% if has_any_lyngsat %}
|
||||||
|
<td class="text-center">
|
||||||
|
{% if source.has_lyngsat %}
|
||||||
|
<a href="#" class="text-primary text-decoration-none"
|
||||||
|
onclick="showLyngsatModal({{ source.lyngsat_id }}); return false;">
|
||||||
|
<i class="bi bi-tv"></i> ТВ
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
{% endif %}
|
||||||
<td class="text-center">{{ source.objitem_count }}</td>
|
<td class="text-center">{{ source.objitem_count }}</td>
|
||||||
<td>{{ source.created_at|date:"d.m.Y H:i" }}</td>
|
<td>{{ source.created_at|date:"d.m.Y H:i" }}</td>
|
||||||
<td>{{ source.updated_at|date:"d.m.Y H:i" }}</td>
|
<td>{{ source.updated_at|date:"d.m.Y H:i" }}</td>
|
||||||
@@ -360,7 +392,7 @@
|
|||||||
|
|
||||||
<!-- Modal for Source Details -->
|
<!-- Modal for Source Details -->
|
||||||
<div class="modal fade" id="sourceDetailsModal" tabindex="-1" aria-labelledby="sourceDetailsModalLabel" aria-hidden="true">
|
<div class="modal fade" id="sourceDetailsModal" tabindex="-1" aria-labelledby="sourceDetailsModalLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog modal-xl">
|
<div class="modal-dialog modal-fullscreen">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title" id="sourceDetailsModalLabel">Детали источника #<span id="modalSourceId"></span></h5>
|
<h5 class="modal-title" id="sourceDetailsModalLabel">Детали источника #<span id="modalSourceId"></span></h5>
|
||||||
@@ -374,26 +406,77 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="modalErrorMessage" class="alert alert-danger" style="display: none;"></div>
|
<div id="modalErrorMessage" class="alert alert-danger" style="display: none;"></div>
|
||||||
<div id="modalContent" style="display: none;">
|
<div id="modalContent" style="display: none;">
|
||||||
<h6>Связанные точки (<span id="objitemCount">0</span>):</h6>
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
<div class="table-responsive" style="max-height: 60vh; overflow-y: auto;">
|
<h6 class="mb-0">Связанные точки (<span id="objitemCount">0</span>):</h6>
|
||||||
<table class="table table-striped table-hover table-sm">
|
<div class="dropdown">
|
||||||
|
<button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle"
|
||||||
|
id="modalColumnVisibilityDropdown" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<i class="bi bi-gear"></i> Колонки
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="modalColumnVisibilityDropdown" style="max-height: 400px; overflow-y: auto;">
|
||||||
|
<li>
|
||||||
|
<label class="dropdown-item">
|
||||||
|
<input type="checkbox" id="modal-select-all-columns" onchange="toggleAllModalColumns(this)"> Выбрать всё
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="0" checked onchange="toggleModalColumn(this)"> Выбрать</label></li>
|
||||||
|
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="1" checked onchange="toggleModalColumn(this)"> ID</label></li>
|
||||||
|
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="2" checked onchange="toggleModalColumn(this)"> Имя</label></li>
|
||||||
|
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="3" checked onchange="toggleModalColumn(this)"> Спутник</label></li>
|
||||||
|
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="4" checked onchange="toggleModalColumn(this)"> Транспондер</label></li>
|
||||||
|
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="5" checked onchange="toggleModalColumn(this)"> Частота, МГц</label></li>
|
||||||
|
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="6" checked onchange="toggleModalColumn(this)"> Полоса, МГц</label></li>
|
||||||
|
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="7" checked onchange="toggleModalColumn(this)"> Поляризация</label></li>
|
||||||
|
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="8" checked onchange="toggleModalColumn(this)"> Сим. V</label></li>
|
||||||
|
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="9" checked onchange="toggleModalColumn(this)"> Модул</label></li>
|
||||||
|
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="10" checked onchange="toggleModalColumn(this)"> ОСШ</label></li>
|
||||||
|
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="11" checked onchange="toggleModalColumn(this)"> Время ГЛ</label></li>
|
||||||
|
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="12" checked onchange="toggleModalColumn(this)"> Местоположение</label></li>
|
||||||
|
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="13" checked onchange="toggleModalColumn(this)"> Геолокация</label></li>
|
||||||
|
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="14" checked onchange="toggleModalColumn(this)"> Обновлено</label></li>
|
||||||
|
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="15" checked onchange="toggleModalColumn(this)"> Кем(обн)</label></li>
|
||||||
|
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="16" onchange="toggleModalColumn(this)"> Создано</label></li>
|
||||||
|
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="17" onchange="toggleModalColumn(this)"> Кем(созд)</label></li>
|
||||||
|
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="18" onchange="toggleModalColumn(this)"> Комментарий</label></li>
|
||||||
|
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="19" onchange="toggleModalColumn(this)"> Усреднённое</label></li>
|
||||||
|
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="20" onchange="toggleModalColumn(this)"> Стандарт</label></li>
|
||||||
|
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="21" checked onchange="toggleModalColumn(this)"> Тип источника</label></li>
|
||||||
|
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="22" onchange="toggleModalColumn(this)"> Sigma</label></li>
|
||||||
|
<li><label class="dropdown-item"><input type="checkbox" class="modal-column-toggle" data-column="23" checked onchange="toggleModalColumn(this)"> Зеркала</label></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="table-responsive" style="max-height: 80vh; overflow-y: auto;">
|
||||||
|
<table class="table table-striped table-hover table-sm" style="font-size: 0.85rem;">
|
||||||
<thead class="table-light sticky-top">
|
<thead class="table-light sticky-top">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="text-center" style="width: 3%;">
|
<th class="text-center" style="width: 3%;">
|
||||||
<input type="checkbox" id="modal-select-all" class="form-check-input">
|
<input type="checkbox" id="modal-select-all" class="form-check-input">
|
||||||
</th>
|
</th>
|
||||||
<th class="text-center" style="min-width: 60px;">ID</th>
|
<th class="text-center" style="min-width: 60px;">ID</th>
|
||||||
<th>Имя</th>
|
<th style="min-width: 120px;">Имя</th>
|
||||||
<th>Спутник</th>
|
<th style="min-width: 120px;">Спутник</th>
|
||||||
<th>Частота, МГц</th>
|
<th style="min-width: 100px;">Транспондер</th>
|
||||||
<th>Полоса, МГц</th>
|
<th style="min-width: 100px;">Частота, МГц</th>
|
||||||
<th>Поляризация</th>
|
<th style="min-width: 100px;">Полоса, МГц</th>
|
||||||
<th>Сим. скорость, БОД</th>
|
<th style="min-width: 100px;">Поляризация</th>
|
||||||
<th>Модуляция</th>
|
<th style="min-width: 100px;">Сим. V</th>
|
||||||
<th>ОСШ</th>
|
<th style="min-width: 100px;">Модул</th>
|
||||||
<th>Время ГЛ</th>
|
<th style="min-width: 80px;">ОСШ</th>
|
||||||
<th>Местоположение</th>
|
<th style="min-width: 120px;">Время ГЛ</th>
|
||||||
<th>Координаты ГЛ</th>
|
<th style="min-width: 120px;">Местоположение</th>
|
||||||
|
<th style="min-width: 120px;">Геолокация</th>
|
||||||
|
<th style="min-width: 120px;">Обновлено</th>
|
||||||
|
<th style="min-width: 100px;">Кем(обн)</th>
|
||||||
|
<th style="min-width: 120px;">Создано</th>
|
||||||
|
<th style="min-width: 100px;">Кем(созд)</th>
|
||||||
|
<th style="min-width: 150px;">Комментарий</th>
|
||||||
|
<th style="min-width: 100px;">Усреднённое</th>
|
||||||
|
<th style="min-width: 100px;">Стандарт</th>
|
||||||
|
<th style="min-width: 100px;">Тип источника</th>
|
||||||
|
<th style="min-width: 80px;">Sigma</th>
|
||||||
|
<th style="min-width: 80px;">Зеркала</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="objitemTableBody">
|
<tbody id="objitemTableBody">
|
||||||
@@ -639,6 +722,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
setupRadioLikeCheckboxes('has_coords_kupsat');
|
setupRadioLikeCheckboxes('has_coords_kupsat');
|
||||||
setupRadioLikeCheckboxes('has_coords_valid');
|
setupRadioLikeCheckboxes('has_coords_valid');
|
||||||
setupRadioLikeCheckboxes('has_coords_reference');
|
setupRadioLikeCheckboxes('has_coords_reference');
|
||||||
|
setupRadioLikeCheckboxes('has_lyngsat');
|
||||||
|
|
||||||
// Update filter counter on page load
|
// Update filter counter on page load
|
||||||
updateFilterCounter();
|
updateFilterCounter();
|
||||||
@@ -686,7 +770,7 @@ function showSourceDetails(sourceId) {
|
|||||||
modal.show();
|
modal.show();
|
||||||
|
|
||||||
// Fetch data from API
|
// Fetch data from API
|
||||||
fetch(`/api/source/${sourceId}/objitems/`)
|
fetch('/api/source/' + sourceId + '/objitems/')
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
if (response.status === 404) {
|
if (response.status === 404) {
|
||||||
@@ -712,28 +796,70 @@ function showSourceDetails(sourceId) {
|
|||||||
|
|
||||||
data.objitems.forEach(objitem => {
|
data.objitems.forEach(objitem => {
|
||||||
const row = document.createElement('tr');
|
const row = document.createElement('tr');
|
||||||
row.innerHTML = `
|
|
||||||
<td class="text-center">
|
// Build transponder cell
|
||||||
<input type="checkbox" class="form-check-input modal-item-checkbox" value="${objitem.id}">
|
let transponderCell = '-';
|
||||||
</td>
|
if (objitem.has_transponder) {
|
||||||
<td class="text-center">${objitem.id}</td>
|
transponderCell = '<a href="#" class="text-success text-decoration-none" ' +
|
||||||
<td>${objitem.name}</td>
|
'onclick="showTransponderModal(' + objitem.transponder_id + '); return false;" ' +
|
||||||
<td>${objitem.satellite_name}</td>
|
'title="Показать данные транспондера">' +
|
||||||
<td>${objitem.frequency}</td>
|
'<i class="bi bi-broadcast"></i> ' + objitem.transponder_info +
|
||||||
<td>${objitem.freq_range}</td>
|
'</a>';
|
||||||
<td>${objitem.polarization}</td>
|
}
|
||||||
<td>${objitem.bod_velocity}</td>
|
|
||||||
<td>${objitem.modulation}</td>
|
// Build LyngSat cell
|
||||||
<td>${objitem.snr}</td>
|
let lyngsatCell = '-';
|
||||||
<td>${objitem.geo_timestamp}</td>
|
if (objitem.has_lyngsat) {
|
||||||
<td>${objitem.geo_location}</td>
|
lyngsatCell = '<a href="#" class="text-primary text-decoration-none" ' +
|
||||||
<td>${objitem.geo_coords}</td>
|
'onclick="showLyngsatModal(' + objitem.lyngsat_id + '); return false;">' +
|
||||||
`;
|
'<i class="bi bi-tv"></i> ТВ' +
|
||||||
|
'</a>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build Sigma cell
|
||||||
|
let sigmaCell = '-';
|
||||||
|
if (objitem.has_sigma) {
|
||||||
|
sigmaCell = '<a href="#" class="text-info text-decoration-none" ' +
|
||||||
|
'onclick="showSigmaParameterModal(' + objitem.parameter_id + '); return false;" ' +
|
||||||
|
'title="' + objitem.sigma_info + '">' +
|
||||||
|
'<i class="bi bi-graph-up"></i> ' + objitem.sigma_info +
|
||||||
|
'</a>';
|
||||||
|
}
|
||||||
|
|
||||||
|
row.innerHTML = '<td class="text-center">' +
|
||||||
|
'<input type="checkbox" class="form-check-input modal-item-checkbox" value="' + objitem.id + '">' +
|
||||||
|
'</td>' +
|
||||||
|
'<td class="text-center">' + objitem.id + '</td>' +
|
||||||
|
'<td>' + objitem.name + '</td>' +
|
||||||
|
'<td>' + objitem.satellite_name + '</td>' +
|
||||||
|
'<td>' + transponderCell + '</td>' +
|
||||||
|
'<td>' + objitem.frequency + '</td>' +
|
||||||
|
'<td>' + objitem.freq_range + '</td>' +
|
||||||
|
'<td>' + objitem.polarization + '</td>' +
|
||||||
|
'<td>' + objitem.bod_velocity + '</td>' +
|
||||||
|
'<td>' + objitem.modulation + '</td>' +
|
||||||
|
'<td>' + objitem.snr + '</td>' +
|
||||||
|
'<td>' + objitem.geo_timestamp + '</td>' +
|
||||||
|
'<td>' + objitem.geo_location + '</td>' +
|
||||||
|
'<td>' + objitem.geo_coords + '</td>' +
|
||||||
|
'<td>' + objitem.updated_at + '</td>' +
|
||||||
|
'<td>' + objitem.updated_by + '</td>' +
|
||||||
|
'<td>' + objitem.created_at + '</td>' +
|
||||||
|
'<td>' + objitem.created_by + '</td>' +
|
||||||
|
'<td>' + objitem.comment + '</td>' +
|
||||||
|
'<td>' + objitem.is_average + '</td>' +
|
||||||
|
'<td>' + objitem.standard + '</td>' +
|
||||||
|
'<td>' + lyngsatCell + '</td>' +
|
||||||
|
'<td>' + sigmaCell + '</td>' +
|
||||||
|
'<td>' + objitem.mirrors + '</td>';
|
||||||
tbody.appendChild(row);
|
tbody.appendChild(row);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Setup modal select-all checkbox
|
// Setup modal select-all checkbox
|
||||||
setupModalSelectAll();
|
setupModalSelectAll();
|
||||||
|
|
||||||
|
// Initialize column visibility
|
||||||
|
initModalColumnVisibility();
|
||||||
} else {
|
} else {
|
||||||
// Show no data message
|
// Show no data message
|
||||||
document.getElementById('modalNoData').style.display = 'block';
|
document.getElementById('modalNoData').style.display = 'block';
|
||||||
@@ -776,5 +902,196 @@ function setupModalSelectAll() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function to toggle modal column visibility
|
||||||
|
function toggleModalColumn(checkbox) {
|
||||||
|
const columnIndex = parseInt(checkbox.getAttribute('data-column'));
|
||||||
|
const modal = document.getElementById('sourceDetailsModal');
|
||||||
|
const table = modal.querySelector('.table');
|
||||||
|
if (!table) return;
|
||||||
|
|
||||||
|
const cells = table.querySelectorAll('td:nth-child(' + (columnIndex + 1) + '), th:nth-child(' + (columnIndex + 1) + ')');
|
||||||
|
|
||||||
|
if (checkbox.checked) {
|
||||||
|
cells.forEach(cell => {
|
||||||
|
cell.style.display = '';
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
cells.forEach(cell => {
|
||||||
|
cell.style.display = 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to toggle all modal columns
|
||||||
|
function toggleAllModalColumns(selectAllCheckbox) {
|
||||||
|
const columnCheckboxes = document.querySelectorAll('.modal-column-toggle');
|
||||||
|
columnCheckboxes.forEach(checkbox => {
|
||||||
|
checkbox.checked = selectAllCheckbox.checked;
|
||||||
|
toggleModalColumn(checkbox);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize modal column visibility
|
||||||
|
function initModalColumnVisibility() {
|
||||||
|
// Hide columns by default: Создано (16), Кем(созд) (17), Комментарий (18), Усреднённое (19), Стандарт (20), Sigma (22)
|
||||||
|
const columnsToHide = [16, 17, 18, 19, 20, 22];
|
||||||
|
columnsToHide.forEach(columnIndex => {
|
||||||
|
const checkbox = document.querySelector('.modal-column-toggle[data-column="' + columnIndex + '"]');
|
||||||
|
if (checkbox && !checkbox.checked) {
|
||||||
|
toggleModalColumn(checkbox);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to show LyngSat modal
|
||||||
|
function showLyngsatModal(lyngsatId) {
|
||||||
|
const modal = new bootstrap.Modal(document.getElementById('lyngsatModal'));
|
||||||
|
modal.show();
|
||||||
|
|
||||||
|
const modalBody = document.getElementById('lyngsatModalBody');
|
||||||
|
modalBody.innerHTML = '<div class="text-center py-4"><div class="spinner-border text-primary" role="status"><span class="visually-hidden">Загрузка...</span></div></div>';
|
||||||
|
|
||||||
|
fetch('/api/lyngsat/' + lyngsatId + '/')
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Ошибка загрузки данных');
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
let html = '<div class="container-fluid"><div class="row g-3">' +
|
||||||
|
'<div class="col-md-6"><div class="card h-100">' +
|
||||||
|
'<div class="card-header bg-light"><strong><i class="bi bi-info-circle"></i> Основная информация</strong></div>' +
|
||||||
|
'<div class="card-body"><table class="table table-sm table-borderless mb-0"><tbody>' +
|
||||||
|
'<tr><td class="text-muted" style="width: 40%;">Спутник:</td><td><strong>' + data.satellite + '</strong></td></tr>' +
|
||||||
|
'<tr><td class="text-muted">Частота:</td><td><strong>' + data.frequency + ' МГц</strong></td></tr>' +
|
||||||
|
'<tr><td class="text-muted">Поляризация:</td><td><span class="badge bg-info">' + data.polarization + '</span></td></tr>' +
|
||||||
|
'<tr><td class="text-muted">Канал:</td><td>' + data.channel_info + '</td></tr>' +
|
||||||
|
'</tbody></table></div></div></div>' +
|
||||||
|
'<div class="col-md-6"><div class="card h-100">' +
|
||||||
|
'<div class="card-header bg-light"><strong><i class="bi bi-gear"></i> Технические параметры</strong></div>' +
|
||||||
|
'<div class="card-body"><table class="table table-sm table-borderless mb-0"><tbody>' +
|
||||||
|
'<tr><td class="text-muted" style="width: 40%;">Модуляция:</td><td><span class="badge bg-secondary">' + data.modulation + '</span></td></tr>' +
|
||||||
|
'<tr><td class="text-muted">Стандарт:</td><td><span class="badge bg-secondary">' + data.standard + '</span></td></tr>' +
|
||||||
|
'<tr><td class="text-muted">Сим. скорость:</td><td><strong>' + data.sym_velocity + ' БОД</strong></td></tr>' +
|
||||||
|
'<tr><td class="text-muted">FEC:</td><td>' + data.fec + '</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.last_update + '</strong></p></div>' +
|
||||||
|
'<div class="col-md-6">' + (data.url ? '<p class="mb-2"><span class="text-muted">Ссылка на источник:</span><br>' +
|
||||||
|
'<a href="' + data.url + '" target="_blank" class="btn btn-sm btn-outline-primary">' +
|
||||||
|
'<i class="bi bi-link-45deg"></i> Открыть на LyngSat</a></p>' : '') +
|
||||||
|
'</div></div></div></div></div></div></div>';
|
||||||
|
modalBody.innerHTML = html;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
modalBody.innerHTML = '<div class="alert alert-danger" role="alert">' +
|
||||||
|
'<i class="bi bi-exclamation-triangle"></i> ' + error.message + '</div>';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to show transponder modal
|
||||||
|
function showTransponderModal(transponderId) {
|
||||||
|
const modal = new bootstrap.Modal(document.getElementById('transponderModal'));
|
||||||
|
modal.show();
|
||||||
|
|
||||||
|
const modalBody = document.getElementById('transponderModalBody');
|
||||||
|
modalBody.innerHTML = '<div class="text-center py-4"><div class="spinner-border text-success" role="status"><span class="visually-hidden">Загрузка...</span></div></div>';
|
||||||
|
|
||||||
|
fetch('/api/transponder/' + transponderId + '/')
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Ошибка загрузки данных транспондера');
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
let html = '<div class="container-fluid"><div class="row g-3">' +
|
||||||
|
'<div class="col-md-6"><div class="card h-100">' +
|
||||||
|
'<div class="card-header bg-light"><strong><i class="bi bi-info-circle"></i> Основная информация</strong></div>' +
|
||||||
|
'<div class="card-body"><table class="table table-sm table-borderless mb-0"><tbody>' +
|
||||||
|
'<tr><td class="text-muted" style="width: 40%;">Название:</td><td><strong>' + (data.name || '-') + '</strong></td></tr>' +
|
||||||
|
'<tr><td class="text-muted">Спутник:</td><td><strong>' + data.satellite + '</strong></td></tr>' +
|
||||||
|
'<tr><td class="text-muted">Зона покрытия:</td><td>' + (data.zone_name || '-') + '</td></tr>' +
|
||||||
|
'<tr><td class="text-muted">Поляризация:</td><td><span class="badge bg-info">' + data.polarization + '</span></td></tr>' +
|
||||||
|
'</tbody></table></div></div></div>' +
|
||||||
|
'<div class="col-md-6"><div class="card h-100">' +
|
||||||
|
'<div class="card-header bg-light"><strong><i class="bi bi-broadcast"></i> Частотные параметры</strong></div>' +
|
||||||
|
'<div class="card-body"><table class="table table-sm table-borderless mb-0"><tbody>' +
|
||||||
|
'<tr><td class="text-muted" style="width: 40%;">Downlink:</td><td><strong>' + data.downlink + ' МГц</strong></td></tr>' +
|
||||||
|
'<tr><td class="text-muted">Uplink:</td><td><strong>' + (data.uplink || '-') + (data.uplink ? ' МГц' : '') + '</strong></td></tr>' +
|
||||||
|
'<tr><td class="text-muted">Полоса:</td><td><strong>' + data.frequency_range + ' МГц</strong></td></tr>' +
|
||||||
|
'<tr><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>';
|
||||||
|
modalBody.innerHTML = html;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
modalBody.innerHTML = '<div class="alert alert-danger" role="alert">' +
|
||||||
|
'<i class="bi bi-exclamation-triangle"></i> ' + error.message + '</div>';
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- LyngSat Data Modal -->
|
||||||
|
<div class="modal fade" id="lyngsatModal" tabindex="-1" aria-labelledby="lyngsatModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-primary text-white">
|
||||||
|
<h5 class="modal-title" id="lyngsatModalLabel">
|
||||||
|
<i class="bi bi-tv"></i> Данные источника LyngSat
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"
|
||||||
|
aria-label="Закрыть"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" id="lyngsatModalBody">
|
||||||
|
<div class="text-center py-4">
|
||||||
|
<div class="spinner-border text-primary" role="status">
|
||||||
|
<span class="visually-hidden">Загрузка...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Transponder Data Modal -->
|
||||||
|
<div class="modal fade" id="transponderModal" tabindex="-1" aria-labelledby="transponderModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-lg">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header bg-success text-white">
|
||||||
|
<h5 class="modal-title" id="transponderModalLabel">
|
||||||
|
<i class="bi bi-broadcast"></i> Данные транспондера
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Закрыть"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" id="transponderModalBody">
|
||||||
|
<div class="text-center py-4">
|
||||||
|
<div class="spinner-border text-success" role="status">
|
||||||
|
<span class="visually-hidden">Загрузка...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Закрыть</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Include the sigma parameter modal component -->
|
||||||
|
{% include 'mainapp/components/_sigma_parameter_modal.html' %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
79
dbapp/mainapp/templates/mainapp/unlink_lyngsat_confirm.html
Normal file
79
dbapp/mainapp/templates/mainapp/unlink_lyngsat_confirm.html
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
{% extends 'mainapp/base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Подтверждение отвязки LyngSat{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center mt-5">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="card shadow">
|
||||||
|
<div class="card-header bg-warning text-dark">
|
||||||
|
<h4 class="mb-0">
|
||||||
|
<i class="bi bi-exclamation-triangle-fill me-2"></i>
|
||||||
|
Подтверждение отвязки источников LyngSat
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="alert alert-warning" role="alert">
|
||||||
|
<h5 class="alert-heading">
|
||||||
|
<i class="bi bi-info-circle-fill me-2"></i>
|
||||||
|
Внимание!
|
||||||
|
</h5>
|
||||||
|
<p class="mb-0">
|
||||||
|
Вы собираетесь отвязать <strong>все</strong> источники LyngSat от объектов.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4">
|
||||||
|
<h5>Информация:</h5>
|
||||||
|
<ul class="list-group">
|
||||||
|
<li class="list-group-item d-flex justify-content-between align-items-center">
|
||||||
|
Объектов с привязанными источниками LyngSat:
|
||||||
|
<span class="badge bg-primary rounded-pill">{{ linked_count }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if linked_count > 0 %}
|
||||||
|
<div class="alert alert-info" role="alert">
|
||||||
|
<p class="mb-0">
|
||||||
|
<i class="bi bi-lightbulb-fill me-2"></i>
|
||||||
|
После отвязки все объекты перестанут отображаться как "ТВ" источники.
|
||||||
|
Вы сможете заново привязать источники через форму привязки LyngSat.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="d-grid gap-2 d-md-flex justify-content-md-between">
|
||||||
|
<a href="{% url 'mainapp:actions' %}" class="btn btn-secondary">
|
||||||
|
<i class="bi bi-arrow-left me-1"></i>
|
||||||
|
Отмена
|
||||||
|
</a>
|
||||||
|
<button type="submit" class="btn btn-warning">
|
||||||
|
<i class="bi bi-unlink me-1"></i>
|
||||||
|
Отвязать все источники ({{ linked_count }})
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-success" role="alert">
|
||||||
|
<p class="mb-0">
|
||||||
|
<i class="bi bi-check-circle-fill me-2"></i>
|
||||||
|
Нет объектов с привязанными источниками LyngSat. Отвязка не требуется.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-grid">
|
||||||
|
<a href="{% url 'mainapp:actions' %}" class="btn btn-primary">
|
||||||
|
<i class="bi bi-arrow-left me-1"></i>
|
||||||
|
Вернуться к действиям
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -39,6 +39,7 @@ from .views import (
|
|||||||
TransponderListView,
|
TransponderListView,
|
||||||
TransponderCreateView,
|
TransponderCreateView,
|
||||||
TransponderUpdateView,
|
TransponderUpdateView,
|
||||||
|
UnlinkAllLyngsatSourcesView,
|
||||||
UploadVchLoadView,
|
UploadVchLoadView,
|
||||||
custom_logout,
|
custom_logout,
|
||||||
)
|
)
|
||||||
@@ -85,5 +86,6 @@ urlpatterns = [
|
|||||||
path('lyngsat-task-status/<str:task_id>/', LyngsatTaskStatusView.as_view(), name='lyngsat_task_status'),
|
path('lyngsat-task-status/<str:task_id>/', LyngsatTaskStatusView.as_view(), name='lyngsat_task_status'),
|
||||||
path('api/lyngsat-task-status/<str:task_id>/', LyngsatTaskStatusAPIView.as_view(), name='lyngsat_task_status_api'),
|
path('api/lyngsat-task-status/<str:task_id>/', LyngsatTaskStatusAPIView.as_view(), name='lyngsat_task_status_api'),
|
||||||
path('clear-lyngsat-cache/', ClearLyngsatCacheView.as_view(), name='clear_lyngsat_cache'),
|
path('clear-lyngsat-cache/', ClearLyngsatCacheView.as_view(), name='clear_lyngsat_cache'),
|
||||||
|
path('unlink-all-lyngsat/', UnlinkAllLyngsatSourcesView.as_view(), name='unlink_all_lyngsat'),
|
||||||
path('logout/', custom_logout, name='logout'),
|
path('logout/', custom_logout, name='logout'),
|
||||||
]
|
]
|
||||||
@@ -30,6 +30,7 @@ from .lyngsat import (
|
|||||||
FillLyngsatDataView,
|
FillLyngsatDataView,
|
||||||
LyngsatTaskStatusView,
|
LyngsatTaskStatusView,
|
||||||
ClearLyngsatCacheView,
|
ClearLyngsatCacheView,
|
||||||
|
UnlinkAllLyngsatSourcesView,
|
||||||
)
|
)
|
||||||
from .source import SourceListView, SourceUpdateView, SourceDeleteView, DeleteSelectedSourcesView
|
from .source import SourceListView, SourceUpdateView, SourceDeleteView, DeleteSelectedSourcesView
|
||||||
from .transponder import (
|
from .transponder import (
|
||||||
@@ -78,6 +79,7 @@ __all__ = [
|
|||||||
'FillLyngsatDataView',
|
'FillLyngsatDataView',
|
||||||
'LyngsatTaskStatusView',
|
'LyngsatTaskStatusView',
|
||||||
'ClearLyngsatCacheView',
|
'ClearLyngsatCacheView',
|
||||||
|
'UnlinkAllLyngsatSourcesView',
|
||||||
# Source
|
# Source
|
||||||
'SourceListView',
|
'SourceListView',
|
||||||
'SourceUpdateView',
|
'SourceUpdateView',
|
||||||
|
|||||||
@@ -186,7 +186,13 @@ class SourceObjItemsAPIView(LoginRequiredMixin, View):
|
|||||||
'source_objitems__parameter_obj__id_satellite',
|
'source_objitems__parameter_obj__id_satellite',
|
||||||
'source_objitems__parameter_obj__polarization',
|
'source_objitems__parameter_obj__polarization',
|
||||||
'source_objitems__parameter_obj__modulation',
|
'source_objitems__parameter_obj__modulation',
|
||||||
'source_objitems__geo_obj'
|
'source_objitems__parameter_obj__standard',
|
||||||
|
'source_objitems__geo_obj',
|
||||||
|
'source_objitems__geo_obj__mirrors',
|
||||||
|
'source_objitems__lyngsat_source',
|
||||||
|
'source_objitems__transponder',
|
||||||
|
'source_objitems__created_by__user',
|
||||||
|
'source_objitems__updated_by__user'
|
||||||
).get(id=source_id)
|
).get(id=source_id)
|
||||||
|
|
||||||
# Get all related ObjItems, sorted by created_at
|
# Get all related ObjItems, sorted by created_at
|
||||||
@@ -202,9 +208,12 @@ class SourceObjItemsAPIView(LoginRequiredMixin, View):
|
|||||||
polarization = '-'
|
polarization = '-'
|
||||||
bod_velocity = '-'
|
bod_velocity = '-'
|
||||||
modulation = '-'
|
modulation = '-'
|
||||||
|
standard = '-'
|
||||||
snr = '-'
|
snr = '-'
|
||||||
|
parameter_id = None
|
||||||
|
|
||||||
if param:
|
if param:
|
||||||
|
parameter_id = param.id
|
||||||
if hasattr(param, 'id_satellite') and param.id_satellite:
|
if hasattr(param, 'id_satellite') and param.id_satellite:
|
||||||
satellite_name = param.id_satellite.name
|
satellite_name = param.id_satellite.name
|
||||||
frequency = f"{param.frequency:.3f}" if param.frequency is not None else '-'
|
frequency = f"{param.frequency:.3f}" if param.frequency is not None else '-'
|
||||||
@@ -214,6 +223,8 @@ class SourceObjItemsAPIView(LoginRequiredMixin, View):
|
|||||||
bod_velocity = f"{param.bod_velocity:.0f}" if param.bod_velocity is not None else '-'
|
bod_velocity = f"{param.bod_velocity:.0f}" if param.bod_velocity is not None else '-'
|
||||||
if hasattr(param, 'modulation') and param.modulation:
|
if hasattr(param, 'modulation') and param.modulation:
|
||||||
modulation = param.modulation.name
|
modulation = param.modulation.name
|
||||||
|
if hasattr(param, 'standard') and param.standard:
|
||||||
|
standard = param.standard.name
|
||||||
snr = f"{param.snr:.0f}" if param.snr is not None else '-'
|
snr = f"{param.snr:.0f}" if param.snr is not None else '-'
|
||||||
|
|
||||||
# Get geo data
|
# Get geo data
|
||||||
@@ -235,6 +246,56 @@ class SourceObjItemsAPIView(LoginRequiredMixin, View):
|
|||||||
lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S"
|
lat = f"{latitude}N" if latitude > 0 else f"{abs(latitude)}S"
|
||||||
geo_coords = f"{lat} {lon}"
|
geo_coords = f"{lat} {lon}"
|
||||||
|
|
||||||
|
# Get created/updated info
|
||||||
|
created_at = '-'
|
||||||
|
if objitem.created_at:
|
||||||
|
local_time = timezone.localtime(objitem.created_at)
|
||||||
|
created_at = local_time.strftime("%d.%m.%Y %H:%M")
|
||||||
|
|
||||||
|
updated_at = '-'
|
||||||
|
if objitem.updated_at:
|
||||||
|
local_time = timezone.localtime(objitem.updated_at)
|
||||||
|
updated_at = local_time.strftime("%d.%m.%Y %H:%M")
|
||||||
|
|
||||||
|
created_by = str(objitem.created_by) if objitem.created_by else '-'
|
||||||
|
updated_by = str(objitem.updated_by) if objitem.updated_by else '-'
|
||||||
|
|
||||||
|
# Check for LyngSat
|
||||||
|
has_lyngsat = hasattr(objitem, 'lyngsat_source') and objitem.lyngsat_source is not None
|
||||||
|
lyngsat_id = objitem.lyngsat_source.id if has_lyngsat else None
|
||||||
|
|
||||||
|
# Check for Transponder
|
||||||
|
has_transponder = hasattr(objitem, 'transponder') and objitem.transponder is not None
|
||||||
|
transponder_id = objitem.transponder.id if has_transponder else None
|
||||||
|
transponder_info = '-'
|
||||||
|
if has_transponder:
|
||||||
|
try:
|
||||||
|
downlink = objitem.transponder.downlink if objitem.transponder.downlink else '-'
|
||||||
|
freq_range_t = objitem.transponder.frequency_range if objitem.transponder.frequency_range else '-'
|
||||||
|
transponder_info = f"{downlink}:{freq_range_t}"
|
||||||
|
except Exception:
|
||||||
|
transponder_info = '-'
|
||||||
|
|
||||||
|
# Check for Sigma
|
||||||
|
has_sigma = False
|
||||||
|
sigma_info = '-'
|
||||||
|
if param and hasattr(param, 'sigma_parameter'):
|
||||||
|
sigma_count = param.sigma_parameter.count()
|
||||||
|
if sigma_count > 0:
|
||||||
|
has_sigma = True
|
||||||
|
sigma_info = f"{sigma_count}"
|
||||||
|
|
||||||
|
# Get comment, is_average, and mirrors from geo_obj
|
||||||
|
comment = '-'
|
||||||
|
is_average = '-'
|
||||||
|
mirrors = '-'
|
||||||
|
if hasattr(objitem, 'geo_obj') and objitem.geo_obj:
|
||||||
|
comment = objitem.geo_obj.comment or '-'
|
||||||
|
is_average = 'Да' if objitem.geo_obj.is_average else 'Нет'
|
||||||
|
# Get mirrors list
|
||||||
|
mirrors_list = list(objitem.geo_obj.mirrors.values_list('name', flat=True))
|
||||||
|
mirrors = ', '.join(mirrors_list) if mirrors_list else '-'
|
||||||
|
|
||||||
objitems_data.append({
|
objitems_data.append({
|
||||||
'id': objitem.id,
|
'id': objitem.id,
|
||||||
'name': objitem.name or '-',
|
'name': objitem.name or '-',
|
||||||
@@ -244,10 +305,26 @@ class SourceObjItemsAPIView(LoginRequiredMixin, View):
|
|||||||
'polarization': polarization,
|
'polarization': polarization,
|
||||||
'bod_velocity': bod_velocity,
|
'bod_velocity': bod_velocity,
|
||||||
'modulation': modulation,
|
'modulation': modulation,
|
||||||
|
'standard': standard,
|
||||||
'snr': snr,
|
'snr': snr,
|
||||||
'geo_timestamp': geo_timestamp,
|
'geo_timestamp': geo_timestamp,
|
||||||
'geo_location': geo_location,
|
'geo_location': geo_location,
|
||||||
'geo_coords': geo_coords
|
'geo_coords': geo_coords,
|
||||||
|
'created_at': created_at,
|
||||||
|
'updated_at': updated_at,
|
||||||
|
'created_by': created_by,
|
||||||
|
'updated_by': updated_by,
|
||||||
|
'comment': comment,
|
||||||
|
'is_average': is_average,
|
||||||
|
'has_lyngsat': has_lyngsat,
|
||||||
|
'lyngsat_id': lyngsat_id,
|
||||||
|
'has_transponder': has_transponder,
|
||||||
|
'transponder_id': transponder_id,
|
||||||
|
'transponder_info': transponder_info,
|
||||||
|
'has_sigma': has_sigma,
|
||||||
|
'sigma_info': sigma_info,
|
||||||
|
'parameter_id': parameter_id,
|
||||||
|
'mirrors': mirrors,
|
||||||
})
|
})
|
||||||
|
|
||||||
return JsonResponse({
|
return JsonResponse({
|
||||||
|
|||||||
@@ -46,22 +46,30 @@ class LinkLyngsatSourcesView(LoginRequiredMixin, FormMessageMixin, FormView):
|
|||||||
|
|
||||||
param = objitem.parameter_obj
|
param = objitem.parameter_obj
|
||||||
|
|
||||||
# Round object frequency
|
# Round object frequency to 1 decimal place
|
||||||
if param.frequency:
|
if param.frequency:
|
||||||
rounded_freq = round(param.frequency, 0) # Round to integer
|
rounded_freq = round(param.frequency, 1) # Round to 1 decimal place
|
||||||
|
|
||||||
# Find matching LyngSat source
|
# Find matching LyngSat source
|
||||||
# Compare by rounded frequency and polarization
|
# Compare by rounded frequency (with tolerance) and polarization
|
||||||
|
# LyngSat frequencies are also rounded to 1 decimal place for comparison
|
||||||
lyngsat_sources = LyngSat.objects.filter(
|
lyngsat_sources = LyngSat.objects.filter(
|
||||||
id_satellite=param.id_satellite,
|
id_satellite=param.id_satellite,
|
||||||
polarization=param.polarization,
|
polarization=param.polarization
|
||||||
frequency__gte=rounded_freq - frequency_tolerance,
|
).select_related('id_satellite', 'polarization')
|
||||||
frequency__lte=rounded_freq + frequency_tolerance
|
|
||||||
).order_by('frequency')
|
|
||||||
|
|
||||||
if lyngsat_sources.exists():
|
# Filter by rounded frequency with tolerance
|
||||||
# Take first matching source
|
matching_sources = []
|
||||||
objitem.lyngsat_source = lyngsat_sources.first()
|
for lyngsat in lyngsat_sources:
|
||||||
|
if lyngsat.frequency:
|
||||||
|
rounded_lyngsat_freq = round(lyngsat.frequency, 1)
|
||||||
|
if abs(rounded_lyngsat_freq - rounded_freq) <= frequency_tolerance:
|
||||||
|
matching_sources.append(lyngsat)
|
||||||
|
|
||||||
|
if matching_sources:
|
||||||
|
# Take first matching source (sorted by frequency difference)
|
||||||
|
matching_sources.sort(key=lambda x: abs(round(x.frequency, 1) - rounded_freq))
|
||||||
|
objitem.lyngsat_source = matching_sources[0]
|
||||||
objitem.save(update_fields=['lyngsat_source'])
|
objitem.save(update_fields=['lyngsat_source'])
|
||||||
linked_count += 1
|
linked_count += 1
|
||||||
|
|
||||||
@@ -159,3 +167,35 @@ class ClearLyngsatCacheView(LoginRequiredMixin, View):
|
|||||||
def get(self, request):
|
def get(self, request):
|
||||||
"""Cache management page."""
|
"""Cache management page."""
|
||||||
return render(request, 'mainapp/clear_lyngsat_cache.html')
|
return render(request, 'mainapp/clear_lyngsat_cache.html')
|
||||||
|
|
||||||
|
|
||||||
|
class UnlinkAllLyngsatSourcesView(LoginRequiredMixin, View):
|
||||||
|
"""View for unlinking all LyngSat sources from ObjItems."""
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
"""Unlink all LyngSat sources."""
|
||||||
|
try:
|
||||||
|
# Count objects with LyngSat sources before unlinking
|
||||||
|
linked_count = ObjItem.objects.filter(lyngsat_source__isnull=False).count()
|
||||||
|
|
||||||
|
# Unlink all LyngSat sources
|
||||||
|
ObjItem.objects.filter(lyngsat_source__isnull=False).update(lyngsat_source=None)
|
||||||
|
|
||||||
|
messages.success(
|
||||||
|
request,
|
||||||
|
f"Успешно отвязано {linked_count} объектов от источников LyngSat"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
messages.error(request, f"Ошибка при отвязке источников: {str(e)}")
|
||||||
|
|
||||||
|
return redirect('mainapp:actions')
|
||||||
|
|
||||||
|
def get(self, request):
|
||||||
|
"""Show confirmation page."""
|
||||||
|
# Count objects with LyngSat sources
|
||||||
|
linked_count = ObjItem.objects.filter(lyngsat_source__isnull=False).count()
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'linked_count': linked_count
|
||||||
|
}
|
||||||
|
return render(request, 'mainapp/unlink_lyngsat_confirm.html', context)
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ class SourceListView(LoginRequiredMixin, View):
|
|||||||
has_coords_kupsat = request.GET.get("has_coords_kupsat")
|
has_coords_kupsat = request.GET.get("has_coords_kupsat")
|
||||||
has_coords_valid = request.GET.get("has_coords_valid")
|
has_coords_valid = request.GET.get("has_coords_valid")
|
||||||
has_coords_reference = request.GET.get("has_coords_reference")
|
has_coords_reference = request.GET.get("has_coords_reference")
|
||||||
|
has_lyngsat = request.GET.get("has_lyngsat")
|
||||||
objitem_count_min = request.GET.get("objitem_count_min", "").strip()
|
objitem_count_min = request.GET.get("objitem_count_min", "").strip()
|
||||||
objitem_count_max = request.GET.get("objitem_count_max", "").strip()
|
objitem_count_max = request.GET.get("objitem_count_max", "").strip()
|
||||||
date_from = request.GET.get("date_from", "").strip()
|
date_from = request.GET.get("date_from", "").strip()
|
||||||
@@ -86,6 +87,14 @@ class SourceListView(LoginRequiredMixin, View):
|
|||||||
elif has_coords_reference == "0":
|
elif has_coords_reference == "0":
|
||||||
sources = sources.filter(coords_reference__isnull=True)
|
sources = sources.filter(coords_reference__isnull=True)
|
||||||
|
|
||||||
|
# Filter by LyngSat presence
|
||||||
|
if has_lyngsat == "1":
|
||||||
|
sources = sources.filter(source_objitems__lyngsat_source__isnull=False).distinct()
|
||||||
|
elif has_lyngsat == "0":
|
||||||
|
sources = sources.filter(
|
||||||
|
~Q(source_objitems__lyngsat_source__isnull=False)
|
||||||
|
).distinct()
|
||||||
|
|
||||||
# Filter by ObjItem count
|
# Filter by ObjItem count
|
||||||
if objitem_count_min:
|
if objitem_count_min:
|
||||||
try:
|
try:
|
||||||
@@ -155,6 +164,8 @@ class SourceListView(LoginRequiredMixin, View):
|
|||||||
|
|
||||||
# Prepare data for display
|
# Prepare data for display
|
||||||
processed_sources = []
|
processed_sources = []
|
||||||
|
has_any_lyngsat = False # Track if any source has LyngSat data
|
||||||
|
|
||||||
for source in page_obj:
|
for source in page_obj:
|
||||||
# Format coordinates
|
# Format coordinates
|
||||||
def format_coords(point):
|
def format_coords(point):
|
||||||
@@ -174,12 +185,21 @@ class SourceListView(LoginRequiredMixin, View):
|
|||||||
# Get count of related ObjItems
|
# Get count of related ObjItems
|
||||||
objitem_count = source.objitem_count
|
objitem_count = source.objitem_count
|
||||||
|
|
||||||
# Get satellites for this source
|
# Get satellites for this source and check for LyngSat
|
||||||
satellite_names = set()
|
satellite_names = set()
|
||||||
|
has_lyngsat = False
|
||||||
|
lyngsat_id = None
|
||||||
|
|
||||||
for objitem in source.source_objitems.all():
|
for objitem in source.source_objitems.all():
|
||||||
if hasattr(objitem, 'parameter_obj') and objitem.parameter_obj:
|
if hasattr(objitem, 'parameter_obj') and objitem.parameter_obj:
|
||||||
if hasattr(objitem.parameter_obj, 'id_satellite') and objitem.parameter_obj.id_satellite:
|
if hasattr(objitem.parameter_obj, 'id_satellite') and objitem.parameter_obj.id_satellite:
|
||||||
satellite_names.add(objitem.parameter_obj.id_satellite.name)
|
satellite_names.add(objitem.parameter_obj.id_satellite.name)
|
||||||
|
|
||||||
|
# Check if any objitem has LyngSat
|
||||||
|
if hasattr(objitem, 'lyngsat_source') and objitem.lyngsat_source:
|
||||||
|
has_lyngsat = True
|
||||||
|
lyngsat_id = objitem.lyngsat_source.id
|
||||||
|
has_any_lyngsat = True
|
||||||
|
|
||||||
satellite_str = ", ".join(sorted(satellite_names)) if satellite_names else "-"
|
satellite_str = ", ".join(sorted(satellite_names)) if satellite_names else "-"
|
||||||
|
|
||||||
@@ -193,6 +213,8 @@ class SourceListView(LoginRequiredMixin, View):
|
|||||||
'satellite': satellite_str,
|
'satellite': satellite_str,
|
||||||
'created_at': source.created_at,
|
'created_at': source.created_at,
|
||||||
'updated_at': source.updated_at,
|
'updated_at': source.updated_at,
|
||||||
|
'has_lyngsat': has_lyngsat,
|
||||||
|
'lyngsat_id': lyngsat_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
# Prepare context for template
|
# Prepare context for template
|
||||||
@@ -207,6 +229,8 @@ class SourceListView(LoginRequiredMixin, View):
|
|||||||
'has_coords_kupsat': has_coords_kupsat,
|
'has_coords_kupsat': has_coords_kupsat,
|
||||||
'has_coords_valid': has_coords_valid,
|
'has_coords_valid': has_coords_valid,
|
||||||
'has_coords_reference': has_coords_reference,
|
'has_coords_reference': has_coords_reference,
|
||||||
|
'has_lyngsat': has_lyngsat,
|
||||||
|
'has_any_lyngsat': has_any_lyngsat,
|
||||||
'objitem_count_min': objitem_count_min,
|
'objitem_count_min': objitem_count_min,
|
||||||
'objitem_count_max': objitem_count_max,
|
'objitem_count_max': objitem_count_max,
|
||||||
'date_from': date_from,
|
'date_from': date_from,
|
||||||
|
|||||||
Reference in New Issue
Block a user