Files
dbstorage/dbapp/mainapp/templates/mainapp/source_form.html

676 lines
26 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends 'mainapp/base.html' %}
{% load static %}
{% load static leaflet_tags %}
{% load l10n %}
{% block title %}Редактировать объект #{{ object.id }}{% endblock %}
{% block extra_css %}
<style>
.form-section {
margin-bottom: 2rem;
border: 1px solid #dee2e6;
border-radius: 0.25rem;
padding: 1rem;
}
.form-section-header {
border-bottom: 1px solid #dee2e6;
padding-bottom: 0.5rem;
margin-bottom: 1rem;
}
.btn-action {
margin-right: 0.5rem;
}
.readonly-field {
background-color: #f8f9fa;
padding: 0.375rem 0.75rem;
border: 1px solid #ced4da;
border-radius: 0.25rem;
}
.coord-group {
border: 1px solid #dee2e6;
padding: 0.75rem;
border-radius: 0.25rem;
margin-bottom: 1rem;
}
.coord-group-header {
font-weight: bold;
margin-bottom: 0.5rem;
color: #495057;
}
#map {
height: 500px;
width: 100%;
margin-bottom: 1rem;
}
.map-container {
margin-bottom: 1rem;
}
.map-controls {
display: flex;
gap: 10px;
margin-bottom: 1rem;
flex-wrap: wrap;
}
.map-control-btn {
padding: 0.375rem 0.75rem;
border: 1px solid #ced4da;
background-color: #f8f9fa;
border-radius: 0.25rem;
cursor: pointer;
}
.map-control-btn.active {
background-color: #e9ecef;
border-color: #dee2e6;
}
.map-control-btn.edit {
background-color: #fff3cd;
border-color: #ffeeba;
}
.map-control-btn.save {
background-color: #d1ecf1;
border-color: #bee5eb;
}
.map-control-btn.cancel {
background-color: #f8d7da;
border-color: #f5c6cb;
}
.leaflet-marker-icon {
filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.3));
}
.objitems-table {
width: 100%;
margin-top: 1rem;
}
.objitems-table th {
background-color: #f8f9fa;
font-weight: 600;
padding: 0.75rem;
border: 1px solid #dee2e6;
}
.objitems-table td {
padding: 0.5rem 0.75rem;
border: 1px solid #dee2e6;
}
.objitems-table tbody tr:hover {
background-color: #f8f9fa;
}
.objitem-link {
color: #0d6efd;
text-decoration: none;
}
.objitem-link:hover {
text-decoration: underline;
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid px-3">
<div class="row mb-3">
<div class="col-12 d-flex justify-content-between align-items-center">
<h2>Редактировать объект #{{ object.id }}</h2>
<div>
{% if user.customuser.role == 'admin' or user.customuser.role == 'moderator' %}
<button type="submit" form="source-form" class="btn btn-primary btn-action">Сохранить</button>
<a href="{% url 'mainapp:source_delete' object.id %}{% if request.GET.urlencode %}?{{ request.GET.urlencode }}{% endif %}"
class="btn btn-danger btn-action">Удалить</a>
{% endif %}
<a href="{% url 'mainapp:source_list' %}{% if request.GET.urlencode %}?{{ request.GET.urlencode }}{% endif %}"
class="btn btn-secondary btn-action">Назад</a>
</div>
</div>
</div>
<form method="post" id="source-form">
{% csrf_token %}
<!-- Основная информация -->
<div class="form-section">
<div class="form-section-header">
<h4>Основная информация</h4>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">ID объекта:</label>
<div class="readonly-field">{{ object.id }}</div>
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label class="form-label">Дата создания:</label>
<div class="readonly-field">
{% if object.created_at %}{{ object.created_at|date:"d.m.Y H:i" }}{% else %}-{% endif %}
</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.created_by %}{{ object.created_by }}{% 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.updated_at %}{{ object.updated_at|date:"d.m.Y H:i" }}{% else %}-{% endif %}
</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.updated_by %}{{ object.updated_by }}{% else %}-{% endif %}
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="id_info" class="form-label">{{ form.info.label }}:</label>
{{ form.info }}
{% if form.info.errors %}
<div class="invalid-feedback d-block">
{{ form.info.errors }}
</div>
{% endif %}
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="id_ownership" class="form-label">{{ form.ownership.label }}:</label>
{{ form.ownership }}
{% if form.ownership.errors %}
<div class="invalid-feedback d-block">
{{ form.ownership.errors }}
</div>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Блок с картой -->
<div class="form-section">
<div class="form-section-header">
<h4>Карта</h4>
</div>
<div class="map-container">
<div id="map"></div>
</div>
</div>
<!-- Координаты -->
<div class="form-section">
<div class="form-section-header">
<h4>Координаты</h4>
</div>
<!-- Координаты ГЛ -->
<div class="coord-group">
<div class="coord-group-header">Координаты ГЛ</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="id_average_latitude" class="form-label">Широта:</label>
{{ form.average_latitude }}
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="id_average_longitude" class="form-label">Долгота:</label>
{{ form.average_longitude }}
</div>
</div>
</div>
</div>
<!-- Координаты Кубсата -->
<div class="coord-group">
<div class="coord-group-header">Координаты Кубсата</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="id_kupsat_latitude" class="form-label">Широта:</label>
{{ form.kupsat_latitude }}
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="id_kupsat_longitude" class="form-label">Долгота:</label>
{{ form.kupsat_longitude }}
</div>
</div>
</div>
</div>
<!-- Координаты оперативников -->
<div class="coord-group">
<div class="coord-group-header">Координаты оперативников</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="id_valid_latitude" class="form-label">Широта:</label>
{{ form.valid_latitude }}
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="id_valid_longitude" class="form-label">Долгота:</label>
{{ form.valid_longitude }}
</div>
</div>
</div>
</div>
<!-- Координаты справочные -->
<div class="coord-group">
<div class="coord-group-header">Координаты справочные</div>
<div class="row">
<div class="col-md-6">
<div class="mb-3">
<label for="id_reference_latitude" class="form-label">Широта:</label>
{{ form.reference_latitude }}
</div>
</div>
<div class="col-md-6">
<div class="mb-3">
<label for="id_reference_longitude" class="form-label">Долгота:</label>
{{ form.reference_longitude }}
</div>
</div>
</div>
</div>
</div>
<!-- Привязанные объекты -->
<div class="form-section">
<div class="form-section-header">
<h4>Привязанные объекты ({{ objitems.count }})</h4>
</div>
{% if objitems %}
<div class="table-responsive">
<table class="objitems-table table table-striped">
<thead>
<tr>
<th>ID</th>
<th>Название</th>
<th>Спутник</th>
<th>Частота, МГц</th>
<th>Полоса, МГц</th>
<th>Поляризация</th>
<th>Модуляция</th>
<th>Координаты</th>
<th>Создан</th>
<th>Обновлен</th>
</tr>
</thead>
<tbody>
{% for item in objitems %}
<tr>
<td>
<a href="{% url 'mainapp:objitem_update' item.id %}" class="objitem-link">
{{ item.id }}
</a>
</td>
<td>{{ item.name|default:"-" }}</td>
<td>
{% if item.parameter_obj and item.parameter_obj.id_satellite %}
{{ item.parameter_obj.id_satellite.name }}
{% else %}
-
{% endif %}
</td>
<td>
{% if item.parameter_obj %}
{{ item.parameter_obj.frequency|default:"-" }}
{% else %}
-
{% endif %}
</td>
<td>
{% if item.parameter_obj %}
{{ item.parameter_obj.freq_range|default:"-" }}
{% else %}
-
{% endif %}
</td>
<td>
{% if item.parameter_obj and item.parameter_obj.polarization %}
{{ item.parameter_obj.polarization.name }}
{% else %}
-
{% endif %}
</td>
<td>
{% if item.parameter_obj and item.parameter_obj.modulation %}
{{ item.parameter_obj.modulation.name }}
{% else %}
-
{% endif %}
</td>
<td>
{% if item.geo_obj and item.geo_obj.coords %}
{{ item.geo_obj.coords.y|floatformat:6 }}, {{ item.geo_obj.coords.x|floatformat:6 }}
{% else %}
-
{% endif %}
</td>
<td>{{ item.created_at|date:"d.m.Y H:i" }}</td>
<td>{{ item.updated_at|date:"d.m.Y H:i" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted">Нет привязанных объектов</p>
{% endif %}
</div>
</form>
</div>
{% endblock %}
{% block extra_js %}
{{ block.super }}
<!-- Подключаем Leaflet -->
{% leaflet_js %}
{% leaflet_css %}
<script src="{% static 'leaflet-markers/js/leaflet-color-markers.js' %}"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
// Инициализация карты
const map = L.map('map').setView([55.75, 37.62], 5);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
}).addTo(map);
// Функция для создания иконок маркеров разных цветов
function createMarkerIcon(color) {
return L.icon({
iconUrl: `{% static "leaflet-markers/img/marker-icon-" %}${color}.png`,
shadowUrl: `{% static 'leaflet-markers/img/marker-shadow.png' %}`,
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [1, -34],
shadowSize: [41, 41]
});
}
const editableLayerGroup = new L.FeatureGroup();
map.addLayer(editableLayerGroup);
// Создаем 4 маркера для разных типов координат
const markers = {
average: {
marker: L.marker([55.75, 37.62], {
draggable: false,
icon: createMarkerIcon('blue'),
title: 'Координаты ГЛ'
}).addTo(editableLayerGroup),
latField: 'id_average_latitude',
lngField: 'id_average_longitude',
label: 'Координаты ГЛ'
},
kupsat: {
marker: L.marker([55.75, 37.62], {
draggable: false,
icon: createMarkerIcon('green'),
title: 'Координаты Кубсата'
}).addTo(editableLayerGroup),
latField: 'id_kupsat_latitude',
lngField: 'id_kupsat_longitude',
label: 'Координаты Кубсата'
},
valid: {
marker: L.marker([55.75, 37.62], {
draggable: false,
icon: createMarkerIcon('red'),
title: 'Координаты оперативников'
}).addTo(editableLayerGroup),
latField: 'id_valid_latitude',
lngField: 'id_valid_longitude',
label: 'Координаты оперативников'
},
reference: {
marker: L.marker([55.75, 37.62], {
draggable: false,
icon: createMarkerIcon('yellow'),
title: 'Координаты справочные'
}).addTo(editableLayerGroup),
latField: 'id_reference_latitude',
lngField: 'id_reference_longitude',
label: 'Координаты справочные'
}
};
// Привязываем попапы к маркерам
Object.values(markers).forEach(m => {
m.marker.bindPopup(m.label);
});
// Синхронизация при перетаскивании
Object.entries(markers).forEach(([key, m]) => {
m.marker.on('dragend', function (event) {
const latLng = event.target.getLatLng();
document.getElementById(m.latField).value = latLng.lat.toFixed(6);
document.getElementById(m.lngField).value = latLng.lng.toFixed(6);
});
// Методы для управления
m.marker.enableEditing = function () {
this.dragging.enable();
this.openPopup();
};
m.marker.disableEditing = function () {
this.dragging.disable();
this.closePopup();
};
});
// Устанавливаем начальные координаты из полей формы
function initMarkersFromForm() {
let hasValidCoords = false;
let centerLat = 55.75;
let centerLng = 37.62;
Object.entries(markers).forEach(([key, m]) => {
const lat = parseFloat(document.getElementById(m.latField).value);
const lng = parseFloat(document.getElementById(m.lngField).value);
if (!isNaN(lat) && !isNaN(lng)) {
m.marker.setLatLng([lat, lng]);
if (!hasValidCoords) {
centerLat = lat;
centerLng = lng;
hasValidCoords = true;
}
} else {
// Скрываем маркер если нет координат
m.marker.setOpacity(0);
}
});
// Центрируем карту
map.setView([centerLat, centerLng], hasValidCoords ? 10 : 5);
}
// Настройка формы для синхронизации с маркерами
function setupFormChange(latFieldId, lngFieldId, marker) {
const latField = document.getElementById(latFieldId);
const lngField = document.getElementById(lngFieldId);
[latField, lngField].forEach(field => {
field.addEventListener('change', function () {
const lat = parseFloat(latField.value);
const lng = parseFloat(lngField.value);
if (!isNaN(lat) && !isNaN(lng)) {
marker.setLatLng([lat, lng]);
marker.setOpacity(1);
map.setView(marker.getLatLng(), 10);
} else {
marker.setOpacity(0);
}
});
});
}
// Инициализация
initMarkersFromForm();
Object.values(markers).forEach(m => {
setupFormChange(m.latField, m.lngField, m.marker);
});
// --- УПРАВЛЕНИЕ РЕДАКТИРОВАНИЕМ ---
const editControlsDiv = L.DomUtil.create('div', 'map-controls');
editControlsDiv.style.position = 'absolute';
editControlsDiv.style.top = '10px';
editControlsDiv.style.right = '10px';
editControlsDiv.style.zIndex = '1000';
editControlsDiv.style.background = 'white';
editControlsDiv.style.padding = '10px';
editControlsDiv.style.borderRadius = '4px';
editControlsDiv.style.boxShadow = '0 0 10px rgba(0,0,0,0.2)';
editControlsDiv.innerHTML = `
<div class="map-controls">
<button type="button" id="edit-btn" class="map-control-btn edit">Редактировать</button>
<button type="button" id="save-btn" class="map-control-btn save" disabled>Сохранить</button>
<button type="button" id="cancel-btn" class="map-control-btn cancel" disabled>Отмена</button>
</div>
`;
map.getContainer().appendChild(editControlsDiv);
let isEditing = false;
const initialPositions = {};
// Сохраняем начальные координаты для отмены
Object.entries(markers).forEach(([key, m]) => {
initialPositions[key] = m.marker.getLatLng();
});
// Включение редактирования
document.getElementById('edit-btn').addEventListener('click', function () {
if (isEditing) return;
isEditing = true;
document.getElementById('edit-btn').classList.add('active');
document.getElementById('save-btn').disabled = false;
document.getElementById('cancel-btn').disabled = false;
Object.entries(markers).forEach(([key, m]) => {
if (key !== 'average' && m.marker.options.opacity !== 0) {
m.marker.enableEditing();
}
});
// Показываем подсказку
L.popup()
.setLatLng(map.getCenter())
.setContent('Перетаскивайте маркеры. Нажмите "Сохранить" или "Отмена".')
.openOn(map);
});
// Сохранение изменений
document.getElementById('save-btn').addEventListener('click', function () {
if (!isEditing) return;
isEditing = false;
document.getElementById('edit-btn').classList.remove('active');
document.getElementById('save-btn').disabled = true;
document.getElementById('cancel-btn').disabled = true;
// Отключаем редактирование
Object.values(markers).forEach(m => {
m.marker.disableEditing();
});
// Обновляем начальные позиции
Object.entries(markers).forEach(([key, m]) => {
initialPositions[key] = m.marker.getLatLng();
});
map.closePopup();
});
// Отмена изменений
document.getElementById('cancel-btn').addEventListener('click', function () {
if (!isEditing) return;
isEditing = false;
document.getElementById('edit-btn').classList.remove('active');
document.getElementById('save-btn').disabled = true;
document.getElementById('cancel-btn').disabled = true;
// Возвращаем маркеры на исходные позиции
Object.entries(markers).forEach(([key, m]) => {
m.marker.setLatLng(initialPositions[key]);
m.marker.disableEditing();
// Синхронизируем форму с исходными значениями
document.getElementById(m.latField).value = initialPositions[key].lat.toFixed(6);
document.getElementById(m.lngField).value = initialPositions[key].lng.toFixed(6);
});
map.closePopup();
});
// Легенда
const legend = L.control({ position: 'bottomright' });
legend.onAdd = function () {
const div = L.DomUtil.create('div', 'info legend');
div.style.fontSize = '14px';
div.style.backgroundColor = 'white';
div.style.padding = '10px';
div.style.borderRadius = '4px';
div.style.boxShadow = '0 0 10px rgba(0,0,0,0.2)';
div.innerHTML = `
<h5>Легенда</h5>
<div><span style="color: blue; font-weight: bold;">•</span> Координаты ГЛ</div>
<div><span style="color: green; font-weight: bold;">•</span> Координаты Кубсата</div>
<div><span style="color: red; font-weight: bold;">•</span> Координаты оперативников</div>
<div><span style="color: gold; font-weight: bold;">•</span> Координаты справочные</div>
`;
return div;
};
legend.addTo(map);
});
</script>
{% endblock %}