Добавил форму с предосмотром и редактированием
This commit is contained in:
@@ -110,7 +110,7 @@ class GeoInline(admin.StackedInline):
|
|||||||
fieldsets = (
|
fieldsets = (
|
||||||
("Основная информация", {
|
("Основная информация", {
|
||||||
"fields": ("mirrors", "location", "distance_coords_kup",
|
"fields": ("mirrors", "location", "distance_coords_kup",
|
||||||
"distance_coords_valid", "distance_kup_valid", "timestamp", "comment", "id_user_add")
|
"distance_coords_valid", "distance_kup_valid", "timestamp", "comment",)
|
||||||
}),
|
}),
|
||||||
("Координаты: геолокация", {
|
("Координаты: геолокация", {
|
||||||
"fields": ("longitude_geo", "latitude_geo", "coords"),
|
"fields": ("longitude_geo", "latitude_geo", "coords"),
|
||||||
@@ -195,15 +195,6 @@ class ParameterAdmin(ImportExportActionModelAdmin, admin.ModelAdmin):
|
|||||||
"standard",
|
"standard",
|
||||||
"sigma_parameter"
|
"sigma_parameter"
|
||||||
)
|
)
|
||||||
# fields = ( "id_satellite",
|
|
||||||
# "frequency",
|
|
||||||
# "freq_range",
|
|
||||||
# "polarization",
|
|
||||||
# "modulation",
|
|
||||||
# "bod_velocity",
|
|
||||||
# "snr",
|
|
||||||
# "standard",
|
|
||||||
# "id_sigma_parameter")
|
|
||||||
list_display_links = ("frequency", "id_satellite", )
|
list_display_links = ("frequency", "id_satellite", )
|
||||||
list_filter = (
|
list_filter = (
|
||||||
HasSigmaParameterFilter,
|
HasSigmaParameterFilter,
|
||||||
@@ -309,7 +300,7 @@ class GeoAdmin(ImportExportActionModelAdmin, LeafletGeoAdmin):
|
|||||||
fieldsets = (
|
fieldsets = (
|
||||||
("Основная информация", {
|
("Основная информация", {
|
||||||
"fields": ("mirrors", "location", "distance_coords_kup",
|
"fields": ("mirrors", "location", "distance_coords_kup",
|
||||||
"distance_coords_valid", "distance_kup_valid", "timestamp", "comment", "id_user_add")
|
"distance_coords_valid", "distance_kup_valid", "timestamp", "comment",)
|
||||||
}),
|
}),
|
||||||
("Координаты: геолокация", {
|
("Координаты: геолокация", {
|
||||||
"fields": ("longitude_geo", "latitude_geo", "coords"),
|
"fields": ("longitude_geo", "latitude_geo", "coords"),
|
||||||
@@ -337,7 +328,6 @@ class GeoAdmin(ImportExportActionModelAdmin, LeafletGeoAdmin):
|
|||||||
"is_average",
|
"is_average",
|
||||||
("location", MultiSelectDropdownFilter),
|
("location", MultiSelectDropdownFilter),
|
||||||
("timestamp", DateRangeQuickSelectListFilterBuilder()),
|
("timestamp", DateRangeQuickSelectListFilterBuilder()),
|
||||||
("id_user_add", MultiSelectRelatedDropdownFilter),
|
|
||||||
)
|
)
|
||||||
search_fields = (
|
search_fields = (
|
||||||
"mirrors__name",
|
"mirrors__name",
|
||||||
@@ -346,7 +336,6 @@ class GeoAdmin(ImportExportActionModelAdmin, LeafletGeoAdmin):
|
|||||||
"coords_kupsat",
|
"coords_kupsat",
|
||||||
"coords_valid"
|
"coords_valid"
|
||||||
)
|
)
|
||||||
list_select_related = ("id_user_add", )
|
|
||||||
prefetch_related = ("mirrors", )
|
prefetch_related = ("mirrors", )
|
||||||
|
|
||||||
|
|
||||||
@@ -467,7 +456,6 @@ class ObjectAdmin(admin.ModelAdmin):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_readonly_fields(self, request, obj=None):
|
def get_readonly_fields(self, request, obj=None):
|
||||||
# Always make these fields readonly to preserve tracking
|
|
||||||
return self.readonly_fields
|
return self.readonly_fields
|
||||||
|
|
||||||
def save_model(self, request, obj, form, change):
|
def save_model(self, request, obj, form, change):
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
from .models import Satellite, Polarization
|
from .models import Satellite, Polarization, ObjItem, Parameter, Geo, Modulation, Standard
|
||||||
|
|
||||||
class UploadFileForm(forms.Form):
|
class UploadFileForm(forms.Form):
|
||||||
file = forms.FileField(
|
file = forms.FileField(
|
||||||
@@ -103,4 +103,46 @@ class NewEventForm(forms.Form):
|
|||||||
'class': 'form-control',
|
'class': 'form-control',
|
||||||
'accept': '.xlsx,.xls'
|
'accept': '.xlsx,.xls'
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
class ParameterForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Parameter
|
||||||
|
fields = [
|
||||||
|
'id_satellite', 'frequency', 'freq_range', 'polarization',
|
||||||
|
'bod_velocity', 'modulation', 'snr', 'standard'
|
||||||
|
]
|
||||||
|
widgets = {
|
||||||
|
'id_satellite': forms.Select(attrs={'class': 'form-select'}, choices=[]),
|
||||||
|
'frequency': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'}),
|
||||||
|
'freq_range': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'}),
|
||||||
|
'bod_velocity': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'}),
|
||||||
|
'snr': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.001'}),
|
||||||
|
'polarization': forms.Select(attrs={'class': 'form-select'}, choices=[]),
|
||||||
|
'modulation': forms.Select(attrs={'class': 'form-select'}, choices=[]),
|
||||||
|
'standard': forms.Select(attrs={'class': 'form-select'}, choices=[]),
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.fields['id_satellite'].choices = [(s.id, s.name) for s in Satellite.objects.all()]
|
||||||
|
self.fields['polarization'].choices = [(p.id, p.name) for p in Polarization.objects.all()]
|
||||||
|
self.fields['modulation'].choices = [(m.id, m.name) for m in Modulation.objects.all()]
|
||||||
|
self.fields['standard'].choices = [(s.id, s.name) for s in Standard.objects.all()]
|
||||||
|
|
||||||
|
class GeoForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Geo
|
||||||
|
fields = ['location', 'comment', 'is_average']
|
||||||
|
widgets = {
|
||||||
|
'location': forms.TextInput(attrs={'class': 'form-control'}),
|
||||||
|
'comment': forms.TextInput(attrs={'class': 'form-control'}),
|
||||||
|
'is_average': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
||||||
|
}
|
||||||
|
|
||||||
|
class ObjItemForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = ObjItem
|
||||||
|
fields = ['name']
|
||||||
|
widgets = {
|
||||||
|
'name': forms.TextInput(attrs={'class': 'form-control'}),
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 5.2.7 on 2025-11-01 07:38
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('mainapp', '0003_alter_objitem_created_at_alter_objitem_updated_at'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='geo',
|
||||||
|
name='id_user_add',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='objitem',
|
||||||
|
name='id_user_add',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='parameter',
|
||||||
|
name='id_user_add',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -165,7 +165,7 @@ class Parameter(models.Model):
|
|||||||
standard = models.ForeignKey(
|
standard = models.ForeignKey(
|
||||||
Standard, default=get_default_standard, on_delete=models.SET_DEFAULT, related_name="standards", null=True, blank=True, verbose_name="Стандарт"
|
Standard, default=get_default_standard, on_delete=models.SET_DEFAULT, related_name="standards", null=True, blank=True, verbose_name="Стандарт"
|
||||||
)
|
)
|
||||||
id_user_add = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, related_name="parameter_added", verbose_name="Пользователь", null=True, blank=True)
|
# id_user_add = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, related_name="parameter_added", verbose_name="Пользователь", null=True, blank=True)
|
||||||
objitems = models.ManyToManyField(ObjItem, related_name="parameters_obj", verbose_name="Источники", blank=True)
|
objitems = models.ManyToManyField(ObjItem, related_name="parameters_obj", verbose_name="Источники", blank=True)
|
||||||
# id_sigma_parameter = models.ManyToManyField(SigmaParameter, on_delete=models.SET_NULL, related_name="sigma_parameter", verbose_name="ВЧ с sigma", null=True, blank=True)
|
# id_sigma_parameter = models.ManyToManyField(SigmaParameter, on_delete=models.SET_NULL, related_name="sigma_parameter", verbose_name="ВЧ с sigma", null=True, blank=True)
|
||||||
# id_sigma_parameter = models.ManyToManyField(SigmaParameter, verbose_name="ВЧ с sigma", null=True, blank=True)
|
# id_sigma_parameter = models.ManyToManyField(SigmaParameter, verbose_name="ВЧ с sigma", null=True, blank=True)
|
||||||
@@ -261,7 +261,7 @@ class Geo(models.Model):
|
|||||||
coords_kupsat = gis.PointField(srid=4326, null=True, blank=True, verbose_name="Координаты Кубсата")
|
coords_kupsat = gis.PointField(srid=4326, null=True, blank=True, verbose_name="Координаты Кубсата")
|
||||||
coords_valid = gis.PointField(srid=4326, null=True, blank=True, verbose_name="Координаты оперативников")
|
coords_valid = gis.PointField(srid=4326, null=True, blank=True, verbose_name="Координаты оперативников")
|
||||||
is_average = models.BooleanField(null=True, blank=True, verbose_name="Усреднённое")
|
is_average = models.BooleanField(null=True, blank=True, verbose_name="Усреднённое")
|
||||||
id_user_add = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, related_name="geos_added", verbose_name="Пользователь", null=True, blank=True)
|
# id_user_add = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, related_name="geos_added", verbose_name="Пользователь", null=True, blank=True)
|
||||||
distance_coords_kup = models.GeneratedField(
|
distance_coords_kup = models.GeneratedField(
|
||||||
expression=functions.Distance("coords", "coords_kupsat")/1000,
|
expression=functions.Distance("coords", "coords_kupsat")/1000,
|
||||||
output_field=models.FloatField(),
|
output_field=models.FloatField(),
|
||||||
@@ -280,7 +280,7 @@ class Geo(models.Model):
|
|||||||
db_persist=True,
|
db_persist=True,
|
||||||
null=True, blank=True, verbose_name="Расстояние между купсатом и оперативным отделом, км"
|
null=True, blank=True, verbose_name="Расстояние между купсатом и оперативным отделом, км"
|
||||||
)
|
)
|
||||||
objitem = models.OneToOneField(ObjItem, on_delete=models.SET_NULL, verbose_name="Гео", related_name="geo_obj", null=True)
|
objitem = models.OneToOneField(ObjItem, on_delete=models.CASCADE, verbose_name="Гео", related_name="geo_obj", null=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
longitude = self.coords.coords[0]
|
longitude = self.coords.coords[0]
|
||||||
|
|||||||
25
dbapp/mainapp/templates/mainapp/objitem_confirm_delete.html
Normal file
25
dbapp/mainapp/templates/mainapp/objitem_confirm_delete.html
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{% extends 'mainapp/base.html' %}
|
||||||
|
|
||||||
|
{% block title %}Удалить объект{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid px-3">
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-12">
|
||||||
|
<h2>Удалить объект "{{ object }}"?</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<p>Вы уверены, что хотите удалить этот объект? Это действие нельзя будет отменить.</p>
|
||||||
|
<div class="d-flex justify-content-end">
|
||||||
|
<button type="submit" class="btn btn-danger">Удалить</button>
|
||||||
|
<a href="{% url 'home' %}" class="btn btn-secondary ms-2">Отмена</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
513
dbapp/mainapp/templates/mainapp/objitem_form.html
Normal file
513
dbapp/mainapp/templates/mainapp/objitem_form.html
Normal file
@@ -0,0 +1,513 @@
|
|||||||
|
{% extends 'mainapp/base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
{% load static leaflet_tags %}
|
||||||
|
|
||||||
|
{% block title %}{% if object %}Редактировать объект: {{ object.name }}{% else %}Создать объект{% endif %}{% 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; }
|
||||||
|
.dynamic-form { border: 1px dashed #ced4da; padding: 1rem; margin-top: 1rem; border-radius: 0.25rem; }
|
||||||
|
.dynamic-form-header { display: flex; justify-content: space-between; align-items: center; }
|
||||||
|
.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; }
|
||||||
|
.form-check-input { margin-top: 0.25rem; }
|
||||||
|
.datetime-group { display: flex; gap: 1rem; }
|
||||||
|
.datetime-group > div { flex: 1; }
|
||||||
|
#map { height: 500px; width: 100%; margin-bottom: 1rem; }
|
||||||
|
.map-container { margin-bottom: 1rem; }
|
||||||
|
.coord-sync-group { border: 1px solid #dee2e6; padding: 0.75rem; border-radius: 0.25rem; }
|
||||||
|
.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));
|
||||||
|
}
|
||||||
|
</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>{% if object %}Редактировать объект: {{ object.name }}{% else %}Создать объект{% endif %}</h2>
|
||||||
|
<div>
|
||||||
|
<a href="{% url 'home' %}" class="btn btn-secondary btn-action">Назад</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="post">
|
||||||
|
{% 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 for="{{ form.name.id_for_label }}" class="form-label">Имя объекта:</label>
|
||||||
|
{{ form.name }}
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<!-- ВЧ загрузки -->
|
||||||
|
<div class="form-section">
|
||||||
|
<div class="form-section-header d-flex justify-content-between align-items-center">
|
||||||
|
<h4>ВЧ загрузки</h4>
|
||||||
|
{% if not parameter_forms.forms.0.instance.pk %}
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-primary" id="add-parameter">Добавить ВЧ загрузку</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="parameters-container">
|
||||||
|
{% for param_form in parameter_forms %}
|
||||||
|
<div class="dynamic-form" data-parameter-index="{{ forloop.counter0 }}">
|
||||||
|
<div class="dynamic-form-header">
|
||||||
|
<h5>ВЧ загрузка #{{ forloop.counter }}</h5>
|
||||||
|
{% if parameter_forms.forms|length > 1 %}
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-danger remove-parameter">Удалить</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ param_form.id_satellite.id_for_label }}" class="form-label">Спутник:</label>
|
||||||
|
{{ param_form.id_satellite }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ param_form.frequency.id_for_label }}" class="form-label">Частота, МГц:</label>
|
||||||
|
{{ param_form.frequency }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ param_form.freq_range.id_for_label }}" class="form-label">Полоса, МГц:</label>
|
||||||
|
{{ param_form.freq_range }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ param_form.polarization.id_for_label }}" class="form-label">Поляризация:</label>
|
||||||
|
{{ param_form.polarization }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ param_form.bod_velocity.id_for_label }}" class="form-label">Симв. скорость, БОД:</label>
|
||||||
|
{{ param_form.bod_velocity }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ param_form.modulation.id_for_label }}" class="form-label">Модуляция:</label>
|
||||||
|
{{ param_form.modulation }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ param_form.snr.id_for_label }}" class="form-label">ОСШ:</label>
|
||||||
|
{{ param_form.snr }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ param_form.standard.id_for_label }}" class="form-label">Стандарт:</label>
|
||||||
|
{{ param_form.standard }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</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-sync-group">
|
||||||
|
<div class="coord-group-header">Координаты геолокации</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="id_geo_latitude" class="form-label">Широта:</label>
|
||||||
|
<input type="number" step="0.000001" class="form-control"
|
||||||
|
id="id_geo_latitude" name="geo_latitude"
|
||||||
|
value="{% if object.geo_obj and object.geo_obj.coords %}{{ object.geo_obj.coords.y }}{% endif %}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="id_geo_longitude" class="form-label">Долгота:</label>
|
||||||
|
<input type="number" step="0.000001" class="form-control"
|
||||||
|
id="id_geo_longitude" name="geo_longitude"
|
||||||
|
value="{% if object.geo_obj and object.geo_obj.coords %}{{ object.geo_obj.coords.x }}{% endif %}">
|
||||||
|
</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_longitude" class="form-label">Долгота:</label>
|
||||||
|
<input type="number" step="0.000001" class="form-control"
|
||||||
|
id="id_kupsat_longitude" name="kupsat_longitude"
|
||||||
|
value="{% if object.geo_obj and object.geo_obj.coords_kupsat %}{{ object.geo_obj.coords_kupsat.x }}{% endif %}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="id_kupsat_latitude" class="form-label">Широта:</label>
|
||||||
|
<input type="number" step="0.000001" class="form-control"
|
||||||
|
id="id_kupsat_latitude" name="kupsat_latitude"
|
||||||
|
value="{% if object.geo_obj and object.geo_obj.coords_kupsat %}{{ object.geo_obj.coords_kupsat.y }}{% endif %}">
|
||||||
|
</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_longitude" class="form-label">Долгота:</label>
|
||||||
|
<input type="number" step="0.000001" class="form-control"
|
||||||
|
id="id_valid_longitude" name="valid_longitude"
|
||||||
|
value="{% if object.geo_obj and object.geo_obj.coords_valid %}{{ object.geo_obj.coords_valid.x }}{% endif %}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="id_valid_latitude" class="form-label">Широта:</label>
|
||||||
|
<input type="number" step="0.000001" class="form-control"
|
||||||
|
id="id_valid_latitude" name="valid_latitude"
|
||||||
|
value="{% if object.geo_obj and object.geo_obj.coords_valid %}{{ object.geo_obj.coords_valid.y }}{% endif %}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ geo_form.location.id_for_label }}" class="form-label">Местоположение:</label>
|
||||||
|
{{ geo_form.location }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ geo_form.comment.id_for_label }}" class="form-label">Комментарий:</label>
|
||||||
|
{{ geo_form.comment }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Дата и время:</label>
|
||||||
|
<div class="datetime-group">
|
||||||
|
<div>
|
||||||
|
<label for="id_timestamp_date" class="form-label">Дата:</label>
|
||||||
|
<input type="date" class="form-control"
|
||||||
|
id="id_timestamp_date" name="timestamp_date"
|
||||||
|
value="{% if object.geo_obj and object.geo_obj.timestamp %}{{ object.geo_obj.timestamp|date:'Y-m-d' }}{% endif %}">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="id_timestamp_time" class="form-label">Время:</label>
|
||||||
|
<input type="time" class="form-control"
|
||||||
|
id="id_timestamp_time" name="timestamp_time"
|
||||||
|
value="{% if object.geo_obj and object.geo_obj.timestamp %}{{ object.geo_obj.timestamp|time:'H:i' }}{% endif %}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="{{ geo_form.is_average.id_for_label }}" class="form-check-label">Усреднённое:</label>
|
||||||
|
{{ geo_form.is_average }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if object.geo_obj %}
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Расстояние гео-кубсат, км:</label>
|
||||||
|
<div class="readonly-field">
|
||||||
|
{% if object.geo_obj.distance_coords_kup is not None %}
|
||||||
|
{{ object.geo_obj.distance_coords_kup|floatformat:2 }}
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Расстояние гео-опер, км:</label>
|
||||||
|
<div class="readonly-field">
|
||||||
|
{% if object.geo_obj.distance_coords_valid is not None %}
|
||||||
|
{{ object.geo_obj.distance_coords_valid|floatformat:2 }}
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Расстояние кубсат-опер, км:</label>
|
||||||
|
<div class="readonly-field">
|
||||||
|
{% if object.geo_obj.distance_kup_valid is not None %}
|
||||||
|
{{ object.geo_obj.distance_kup_valid|floatformat:2 }}
|
||||||
|
{% else %}
|
||||||
|
-
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-end mt-4">
|
||||||
|
<button type="submit" class="btn btn-primary btn-action">Сохранить</button>
|
||||||
|
{% if object %}
|
||||||
|
<a href="{% url 'objitem_delete' object.id %}" class="btn btn-danger btn-action">Удалить</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extra_js %}
|
||||||
|
{{ block.super }}
|
||||||
|
<!-- Подключаем Leaflet и его плагины -->
|
||||||
|
{% leaflet_js %}
|
||||||
|
{% leaflet_css %}
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Динамическое добавление ВЧ загрузок
|
||||||
|
let parameterIndex = {{ parameter_forms|length }};
|
||||||
|
|
||||||
|
document.getElementById('add-parameter')?.addEventListener('click', function() {
|
||||||
|
const container = document.getElementById('parameters-container');
|
||||||
|
const template = document.querySelector('.dynamic-form');
|
||||||
|
|
||||||
|
if (template) {
|
||||||
|
const clone = template.cloneNode(true);
|
||||||
|
clone.querySelectorAll('[id]').forEach(el => {
|
||||||
|
el.id = el.id.replace(/-\d+-/g, `-${parameterIndex}-`);
|
||||||
|
});
|
||||||
|
clone.querySelectorAll('[name]').forEach(el => {
|
||||||
|
el.name = el.name.replace(/-\d+-/g, `-${parameterIndex}-`);
|
||||||
|
});
|
||||||
|
clone.querySelectorAll('[for]').forEach(el => {
|
||||||
|
el.htmlFor = el.htmlFor.replace(/-\d+-/g, `-${parameterIndex}-`);
|
||||||
|
});
|
||||||
|
clone.querySelector('.dynamic-form-header h5').textContent = `ВЧ загрузка #${parameterIndex + 1}`;
|
||||||
|
clone.dataset.parameterIndex = parameterIndex;
|
||||||
|
container.appendChild(clone);
|
||||||
|
parameterIndex++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Удаление ВЧ загрузок
|
||||||
|
document.addEventListener('click', function(e) {
|
||||||
|
if (e.target.classList.contains('remove-parameter')) {
|
||||||
|
if (document.querySelectorAll('.dynamic-form').length > 1) {
|
||||||
|
e.target.closest('.dynamic-form').remove();
|
||||||
|
} else {
|
||||||
|
alert('Должна быть хотя бы одна ВЧ загрузка');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
// Маркеры для разных типов координат
|
||||||
|
const markers = {
|
||||||
|
geo: L.marker([55.75, 37.62], { draggable: true, color: 'blue' }).addTo(map),
|
||||||
|
kupsat: L.marker([55.75, 37.61], { draggable: true, color: 'red' }).addTo(map),
|
||||||
|
valid: L.marker([55.75, 37.63], { draggable: true, color: 'green' }).addTo(map)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Устанавливаем начальные координаты из полей формы
|
||||||
|
function initMarkersFromForm() {
|
||||||
|
const geoLat = parseFloat(document.getElementById('id_geo_latitude').value) || 55.75;
|
||||||
|
const geoLng = parseFloat(document.getElementById('id_geo_longitude').value) || 37.62;
|
||||||
|
markers.geo.setLatLng([geoLat, geoLng]);
|
||||||
|
|
||||||
|
const kupsatLat = parseFloat(document.getElementById('id_kupsat_latitude').value) || 55.75;
|
||||||
|
const kupsatLng = parseFloat(document.getElementById('id_kupsat_longitude').value) || 37.61;
|
||||||
|
markers.kupsat.setLatLng([kupsatLat, kupsatLng]);
|
||||||
|
|
||||||
|
const validLat = parseFloat(document.getElementById('id_valid_latitude').value) || 55.75;
|
||||||
|
const validLng = parseFloat(document.getElementById('id_valid_longitude').value) || 37.63;
|
||||||
|
markers.valid.setLatLng([validLat, validLng]);
|
||||||
|
|
||||||
|
// Центрируем карту на первом маркере
|
||||||
|
map.setView(markers.geo.getLatLng(), 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем координаты в форме при перетаскивании маркеров
|
||||||
|
function setupMarkerDrag(marker, latFieldId, lngFieldId, color) {
|
||||||
|
marker.on('dragend', function(event) {
|
||||||
|
const latLng = event.target.getLatLng();
|
||||||
|
document.getElementById(latFieldId).value = latLng.lat.toFixed(6);
|
||||||
|
document.getElementById(lngFieldId).value = latLng.lng.toFixed(6);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем положение маркера при изменении координат в форме
|
||||||
|
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]);
|
||||||
|
map.setView(marker.getLatLng(), 10);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Инициализация
|
||||||
|
initMarkersFromForm();
|
||||||
|
|
||||||
|
// Настройка маркеров
|
||||||
|
setupMarkerDrag(markers.geo, 'id_geo_latitude', 'id_geo_longitude', 'blue');
|
||||||
|
setupMarkerDrag(markers.kupsat, 'id_kupsat_latitude', 'id_kupsat_longitude', 'red');
|
||||||
|
setupMarkerDrag(markers.valid, 'id_valid_latitude', 'id_valid_longitude', 'green');
|
||||||
|
|
||||||
|
// Настройка формы
|
||||||
|
setupFormChange('id_geo_latitude', 'id_geo_longitude', markers.geo);
|
||||||
|
setupFormChange('id_kupsat_latitude', 'id_kupsat_longitude', markers.kupsat);
|
||||||
|
setupFormChange('id_valid_latitude', 'id_valid_longitude', markers.valid);
|
||||||
|
|
||||||
|
// Легенда
|
||||||
|
const legend = L.control({ position: 'bottomright' });
|
||||||
|
|
||||||
|
legend.onAdd = function() {
|
||||||
|
const div = L.DomUtil.create('div', 'info legend');
|
||||||
|
div.innerHTML = `
|
||||||
|
<h6>Легенда</h6>
|
||||||
|
<div><span style="color: blue; font-weight: bold;">•</span> Геолокация</div>
|
||||||
|
<div><span style="color: red; font-weight: bold;">•</span> Кубсат</div>
|
||||||
|
<div><span style="color: green; font-weight: bold;">•</span> Оперативники</div>
|
||||||
|
`;
|
||||||
|
return div;
|
||||||
|
};
|
||||||
|
|
||||||
|
legend.addTo(map);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
@@ -50,6 +50,116 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Column visibility toggle button -->
|
||||||
|
<div class="dropdown">
|
||||||
|
<button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle" id="columnVisibilityDropdown" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<i class="bi bi-gear"></i> Колонки
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu" aria-labelledby="columnVisibilityDropdown" style="z-index: 1050;">
|
||||||
|
<li>
|
||||||
|
<label class="dropdown-item">
|
||||||
|
<input type="checkbox" id="select-all-columns" checked onchange="toggleAllColumns(this)"> Выбрать всё
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li>
|
||||||
|
<label class="dropdown-item">
|
||||||
|
<input type="checkbox" class="column-toggle" data-column="0" checked onchange="toggleColumn(this)"> Выбрать
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label class="dropdown-item">
|
||||||
|
<input type="checkbox" class="column-toggle" data-column="1" checked onchange="toggleColumn(this)"> Имя
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label class="dropdown-item">
|
||||||
|
<input type="checkbox" class="column-toggle" data-column="2" checked onchange="toggleColumn(this)"> Спутник
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label class="dropdown-item">
|
||||||
|
<input type="checkbox" class="column-toggle" data-column="3" checked onchange="toggleColumn(this)"> Част, МГц
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label class="dropdown-item">
|
||||||
|
<input type="checkbox" class="column-toggle" data-column="4" checked onchange="toggleColumn(this)"> Полоса, МГц
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label class="dropdown-item">
|
||||||
|
<input type="checkbox" class="column-toggle" data-column="5" checked onchange="toggleColumn(this)"> Поляризация
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label class="dropdown-item">
|
||||||
|
<input type="checkbox" class="column-toggle" data-column="6" checked onchange="toggleColumn(this)"> Сим. V
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label class="dropdown-item">
|
||||||
|
<input type="checkbox" class="column-toggle" data-column="7" checked onchange="toggleColumn(this)"> Модул
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label class="dropdown-item">
|
||||||
|
<input type="checkbox" class="column-toggle" data-column="8" checked onchange="toggleColumn(this)"> ОСШ
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label class="dropdown-item">
|
||||||
|
<input type="checkbox" class="column-toggle" data-column="9" checked onchange="toggleColumn(this)"> Геолокация
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label class="dropdown-item">
|
||||||
|
<input type="checkbox" class="column-toggle" data-column="10" checked onchange="toggleColumn(this)"> Кубсат
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label class="dropdown-item">
|
||||||
|
<input type="checkbox" class="column-toggle" data-column="11" checked onchange="toggleColumn(this)"> Опер. отд
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label class="dropdown-item">
|
||||||
|
<input type="checkbox" class="column-toggle" data-column="12" checked onchange="toggleColumn(this)"> Гео-куб, км
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label class="dropdown-item">
|
||||||
|
<input type="checkbox" class="column-toggle" data-column="13" checked onchange="toggleColumn(this)"> Гео-опер, км
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label class="dropdown-item">
|
||||||
|
<input type="checkbox" class="column-toggle" data-column="14" checked onchange="toggleColumn(this)"> Куб-опер, км
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label class="dropdown-item">
|
||||||
|
<input type="checkbox" class="column-toggle" data-column="15" checked onchange="toggleColumn(this)"> Обновлено
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label class="dropdown-item">
|
||||||
|
<input type="checkbox" class="column-toggle" data-column="16" checked onchange="toggleColumn(this)"> Кем (обновление)
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label class="dropdown-item">
|
||||||
|
<input type="checkbox" class="column-toggle" data-column="17" checked onchange="toggleColumn(this)"> Создано
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label class="dropdown-item">
|
||||||
|
<input type="checkbox" class="column-toggle" data-column="18" checked onchange="toggleColumn(this)"> Кем (создание)
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Pagination moved here -->
|
<!-- Pagination moved here -->
|
||||||
<div class="ms-auto">
|
<div class="ms-auto">
|
||||||
{% if page_obj.paginator.num_pages > 1 %}
|
{% if page_obj.paginator.num_pages > 1 %}
|
||||||
@@ -260,6 +370,8 @@
|
|||||||
<th scope="col">Куб-опер, км</th>
|
<th scope="col">Куб-опер, км</th>
|
||||||
<th scope="col">Обновлено</th>
|
<th scope="col">Обновлено</th>
|
||||||
<th scope="col">Кем</th>
|
<th scope="col">Кем</th>
|
||||||
|
<th scope="col">Создано</th>
|
||||||
|
<th scope="col">Кем (создание)</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -268,7 +380,7 @@
|
|||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<input type="checkbox" class="form-check-input item-checkbox" value="{{ item.id }}">
|
<input type="checkbox" class="form-check-input item-checkbox" value="{{ item.id }}">
|
||||||
</td>
|
</td>
|
||||||
<td>{{ item.name }}</td>
|
<td><a href="{% if item.obj.id %}{% url 'objitem_update' item.obj.id %}{% endif %}">{{ item.name }}</a></td>
|
||||||
<td>{{ item.satellite_name }}</td>
|
<td>{{ item.satellite_name }}</td>
|
||||||
<td>{{ item.frequency }}</td>
|
<td>{{ item.frequency }}</td>
|
||||||
<td>{{ item.freq_range }}</td>
|
<td>{{ item.freq_range }}</td>
|
||||||
@@ -283,11 +395,13 @@
|
|||||||
<td>{{ item.distance_geo_valid }}</td>
|
<td>{{ item.distance_geo_valid }}</td>
|
||||||
<td>{{ item.distance_kup_valid }}</td>
|
<td>{{ item.distance_kup_valid }}</td>
|
||||||
<td>{{ item.obj.updated_at|date:"d.m.Y H:i" }}</td>
|
<td>{{ item.obj.updated_at|date:"d.m.Y H:i" }}</td>
|
||||||
<td>{{ item.obj.updated_by }}</td>
|
<td>{{ item.updated_by }}</td>
|
||||||
|
<td>{{ item.obj.created_at|date:"d.m.Y H:i" }}</td>
|
||||||
|
<td>{{ item.obj.created_by }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% empty %}
|
{% empty %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="17" class="text-center py-4">
|
<td colspan="19" class="text-center py-4">
|
||||||
{% if selected_satellite_id %}
|
{% if selected_satellite_id %}
|
||||||
Нет данных для выбранных фильтров
|
Нет данных для выбранных фильтров
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -307,6 +421,30 @@
|
|||||||
|
|
||||||
<!-- JavaScript for checkbox functionality and filters -->
|
<!-- JavaScript for checkbox functionality and filters -->
|
||||||
<script>
|
<script>
|
||||||
|
function toggleColumn(checkbox) {
|
||||||
|
const columnIndex = parseInt(checkbox.getAttribute('data-column'));
|
||||||
|
const table = document.querySelector('.table');
|
||||||
|
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 toggleAllColumns(selectAllCheckbox) {
|
||||||
|
const columnCheckboxes = document.querySelectorAll('.column-toggle');
|
||||||
|
columnCheckboxes.forEach(checkbox => {
|
||||||
|
checkbox.checked = selectAllCheckbox.checked;
|
||||||
|
toggleColumn(checkbox);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
// Select/Deselect all checkboxes
|
// Select/Deselect all checkboxes
|
||||||
const selectAllCheckbox = document.getElementById('select-all');
|
const selectAllCheckbox = document.getElementById('select-all');
|
||||||
@@ -451,6 +589,25 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
// Update URL and reload
|
// Update URL and reload
|
||||||
window.location.search = currentParams.toString();
|
window.location.search = currentParams.toString();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Initialize column visibility - hide creation columns by default
|
||||||
|
function initColumnVisibility() {
|
||||||
|
const creationDateCheckbox = document.querySelector('input[data-column="17"]');
|
||||||
|
const creationUserCheckbox = document.querySelector('input[data-column="18"]');
|
||||||
|
|
||||||
|
if (creationDateCheckbox) {
|
||||||
|
creationDateCheckbox.checked = false; // Uncheck to hide by default
|
||||||
|
toggleColumn(creationDateCheckbox);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (creationUserCheckbox) {
|
||||||
|
creationUserCheckbox.checked = false; // Uncheck to hide by default
|
||||||
|
toggleColumn(creationUserCheckbox);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize column visibility after page loads
|
||||||
|
setTimeout(initColumnVisibility, 100); // Slight delay to ensure DOM is fully loaded
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -18,6 +18,9 @@ urlpatterns = [
|
|||||||
path('vch-upload/', views.UploadVchLoadView.as_view(), name='vch_load'),
|
path('vch-upload/', views.UploadVchLoadView.as_view(), name='vch_load'),
|
||||||
path('vch-link/', views.LinkVchSigmaView.as_view(), name='link_vch_sigma'),
|
path('vch-link/', views.LinkVchSigmaView.as_view(), name='link_vch_sigma'),
|
||||||
path('kubsat-excel/', views.ProcessKubsatView.as_view(), name='kubsat_excel'),
|
path('kubsat-excel/', views.ProcessKubsatView.as_view(), name='kubsat_excel'),
|
||||||
|
path('object/create/', views.ObjItemCreateView.as_view(), name='objitem_create'),
|
||||||
|
path('object/<int:pk>/edit/', views.ObjItemUpdateView.as_view(), name='objitem_update'),
|
||||||
|
path('object/<int:pk>/delete/', views.ObjItemDeleteView.as_view(), name='objitem_delete'),
|
||||||
# path('upload/', views.upload_file, name='upload_file'),
|
# path('upload/', views.upload_file, name='upload_file'),
|
||||||
|
|
||||||
]
|
]
|
||||||
@@ -52,7 +52,7 @@ def remove_str(s: str):
|
|||||||
return float(s.strip().replace(",", "."))
|
return float(s.strip().replace(",", "."))
|
||||||
return s
|
return s
|
||||||
|
|
||||||
def fill_data_from_df(df: pd.DataFrame, sat: Satellite):
|
def fill_data_from_df(df: pd.DataFrame, sat: Satellite, current_user=None):
|
||||||
try:
|
try:
|
||||||
df.rename(columns={'Модуляция ': 'Модуляция'}, inplace=True)
|
df.rename(columns={'Модуляция ': 'Модуляция'}, inplace=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -111,6 +111,7 @@ def fill_data_from_df(df: pd.DataFrame, sat: Satellite):
|
|||||||
location = stroka[1]['Местоопределение'].strip()
|
location = stroka[1]['Местоопределение'].strip()
|
||||||
comment = stroka[1]['Комментарий']
|
comment = stroka[1]['Комментарий']
|
||||||
source = stroka[1]['Объект наблюдения']
|
source = stroka[1]['Объект наблюдения']
|
||||||
|
user_to_use = current_user if current_user else CustomUser.objects.get(id=1)
|
||||||
|
|
||||||
vch_load_obj, _ = Parameter.objects.get_or_create(
|
vch_load_obj, _ = Parameter.objects.get_or_create(
|
||||||
id_satellite=sat,
|
id_satellite=sat,
|
||||||
@@ -120,7 +121,6 @@ def fill_data_from_df(df: pd.DataFrame, sat: Satellite):
|
|||||||
bod_velocity=v,
|
bod_velocity=v,
|
||||||
modulation=mod_obj,
|
modulation=mod_obj,
|
||||||
snr=snr,
|
snr=snr,
|
||||||
# defaults={'id_user_add': CustomUser.objects.get(id=1)}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
geo, _ = Geo.objects.get_or_create(
|
geo, _ = Geo.objects.get_or_create(
|
||||||
@@ -131,7 +131,7 @@ def fill_data_from_df(df: pd.DataFrame, sat: Satellite):
|
|||||||
'coords_valid': valid_point,
|
'coords_valid': valid_point,
|
||||||
'location': location,
|
'location': location,
|
||||||
'comment': comment,
|
'comment': comment,
|
||||||
'is_average': (comment != -1.0)
|
'is_average': (comment != -1.0),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
geo.save()
|
geo.save()
|
||||||
@@ -144,7 +144,7 @@ def fill_data_from_df(df: pd.DataFrame, sat: Satellite):
|
|||||||
if not existing_obj_items.exists():
|
if not existing_obj_items.exists():
|
||||||
obj_item = ObjItem.objects.create(
|
obj_item = ObjItem.objects.create(
|
||||||
name=source,
|
name=source,
|
||||||
id_user_add=CustomUser.objects.get(id=1)
|
created_by=user_to_use
|
||||||
)
|
)
|
||||||
obj_item.parameters_obj.set([vch_load_obj])
|
obj_item.parameters_obj.set([vch_load_obj])
|
||||||
geo.objitem = obj_item
|
geo.objitem = obj_item
|
||||||
@@ -202,7 +202,7 @@ def get_point_from_json(filepath: str):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_points_from_csv(file_content):
|
def get_points_from_csv(file_content, current_user=None):
|
||||||
df = pd.read_csv(io.StringIO(file_content), sep=";",
|
df = pd.read_csv(io.StringIO(file_content), sep=";",
|
||||||
names=['id', 'obj', 'lat', 'lon', 'h', 'time', 'sat', 'norad_id', 'freq', 'f_range', 'et', 'qaul', 'mir_1', 'mir_2', 'mir_3'])
|
names=['id', 'obj', 'lat', 'lon', 'h', 'time', 'sat', 'norad_id', 'freq', 'f_range', 'et', 'qaul', 'mir_1', 'mir_2', 'mir_3'])
|
||||||
df[['lat', 'lon', 'freq', 'f_range']] = df[['lat', 'lon', 'freq', 'f_range']].replace(',', '.', regex=True).astype(float)
|
df[['lat', 'lon', 'freq', 'f_range']] = df[['lat', 'lon', 'freq', 'f_range']].replace(',', '.', regex=True).astype(float)
|
||||||
@@ -239,12 +239,14 @@ def get_points_from_csv(file_content):
|
|||||||
|
|
||||||
name=row['mir_3']
|
name=row['mir_3']
|
||||||
)
|
)
|
||||||
|
user_to_use = current_user if current_user else CustomUser.objects.get(id=1)
|
||||||
|
|
||||||
vch_load_obj, _ = Parameter.objects.get_or_create(
|
vch_load_obj, _ = Parameter.objects.get_or_create(
|
||||||
id_satellite=sat_obj,
|
id_satellite=sat_obj,
|
||||||
polarization=pol_obj,
|
polarization=pol_obj,
|
||||||
frequency=row['freq'],
|
frequency=row['freq'],
|
||||||
freq_range=row['f_range'],
|
freq_range=row['f_range'],
|
||||||
# defaults={'id_user_add': CustomUser.objects.get(id=1)}
|
# defaults={'id_user_add': user_to_use}
|
||||||
)
|
)
|
||||||
|
|
||||||
geo_obj, _ = Geo.objects.get_or_create(
|
geo_obj, _ = Geo.objects.get_or_create(
|
||||||
@@ -252,7 +254,7 @@ def get_points_from_csv(file_content):
|
|||||||
coords=Point(row['lon'], row['lat'], srid=4326),
|
coords=Point(row['lon'], row['lat'], srid=4326),
|
||||||
defaults={
|
defaults={
|
||||||
'is_average': False,
|
'is_average': False,
|
||||||
# 'id_user_add': CustomUser.objects.get(id=1),
|
# 'id_user_add': user_to_use,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
geo_obj.mirrors.set(Mirror.objects.filter(name__in=mir_lst))
|
geo_obj.mirrors.set(Mirror.objects.filter(name__in=mir_lst))
|
||||||
@@ -264,7 +266,7 @@ def get_points_from_csv(file_content):
|
|||||||
if not existing_obj_items.exists():
|
if not existing_obj_items.exists():
|
||||||
obj_item = ObjItem.objects.create(
|
obj_item = ObjItem.objects.create(
|
||||||
name=row['obj'],
|
name=row['obj'],
|
||||||
# id_user_add=CustomUser.objects.get(id=1)
|
created_by=user_to_use
|
||||||
)
|
)
|
||||||
obj_item.parameters_obj.set([vch_load_obj])
|
obj_item.parameters_obj.set([vch_load_obj])
|
||||||
geo_obj.objitem = obj_item
|
geo_obj.objitem = obj_item
|
||||||
|
|||||||
@@ -6,10 +6,13 @@ from django.contrib.admin.views.decorators import staff_member_required
|
|||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from django.views.generic import TemplateView, FormView
|
from django.views.generic import TemplateView, FormView, UpdateView, DeleteView, CreateView
|
||||||
from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin
|
from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin
|
||||||
from django.contrib.auth import logout
|
from django.contrib.auth import logout
|
||||||
|
from django.forms import inlineformset_factory, modelformset_factory
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
from django.contrib.gis.geos import Point
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from .utils import (
|
from .utils import (
|
||||||
fill_data_from_df,
|
fill_data_from_df,
|
||||||
@@ -20,10 +23,21 @@ from .utils import (
|
|||||||
kub_report
|
kub_report
|
||||||
)
|
)
|
||||||
from mapsapp.utils import parse_transponders_from_json, parse_transponders_from_xml
|
from mapsapp.utils import parse_transponders_from_json, parse_transponders_from_xml
|
||||||
from .forms import LoadExcelData, LoadCsvData, UploadFileForm, VchLinkForm, UploadVchLoad, NewEventForm
|
from .forms import (
|
||||||
|
LoadExcelData,
|
||||||
|
LoadCsvData,
|
||||||
|
UploadFileForm,
|
||||||
|
VchLinkForm,
|
||||||
|
UploadVchLoad,
|
||||||
|
NewEventForm,
|
||||||
|
ObjItemForm,
|
||||||
|
ParameterForm,
|
||||||
|
GeoForm
|
||||||
|
)
|
||||||
from .models import ObjItem
|
from .models import ObjItem
|
||||||
from .clusters import get_clusters
|
from .clusters import get_clusters
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -93,7 +107,7 @@ class LoadExcelDataView(LoginRequiredMixin, FormView):
|
|||||||
df = pd.read_excel(io.BytesIO(uploaded_file.read()))
|
df = pd.read_excel(io.BytesIO(uploaded_file.read()))
|
||||||
if number > 0:
|
if number > 0:
|
||||||
df = df.head(number)
|
df = df.head(number)
|
||||||
result = fill_data_from_df(df, selected_sat)
|
result = fill_data_from_df(df, selected_sat, self.request.user.customuser)
|
||||||
|
|
||||||
messages.success(self.request, f"Данные успешно загружены! Обработано строк: {result}")
|
messages.success(self.request, f"Данные успешно загружены! Обработано строк: {result}")
|
||||||
return redirect('load_excel_data')
|
return redirect('load_excel_data')
|
||||||
@@ -151,7 +165,7 @@ class LoadCsvDataView(LoginRequiredMixin, FormView):
|
|||||||
if isinstance(content, bytes):
|
if isinstance(content, bytes):
|
||||||
content = content.decode('utf-8')
|
content = content.decode('utf-8')
|
||||||
|
|
||||||
get_points_from_csv(content)
|
get_points_from_csv(content, self.request.user.customuser)
|
||||||
messages.success(self.request, f"Данные успешно загружены!")
|
messages.success(self.request, f"Данные успешно загружены!")
|
||||||
return redirect('load_csv_data')
|
return redirect('load_csv_data')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -331,7 +345,7 @@ class ObjItemListView(LoginRequiredMixin, View):
|
|||||||
try:
|
try:
|
||||||
items_per_page = int(items_per_page)
|
items_per_page = int(items_per_page)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
items_per_page = 25
|
items_per_page = 50
|
||||||
|
|
||||||
# Only filter objects by selected satellite if provided (no data shown by default)
|
# Only filter objects by selected satellite if provided (no data shown by default)
|
||||||
objects = ObjItem.objects.none() # Initially empty
|
objects = ObjItem.objects.none() # Initially empty
|
||||||
@@ -348,11 +362,11 @@ class ObjItemListView(LoginRequiredMixin, View):
|
|||||||
|
|
||||||
# Start with the basic filter if any satellites are selected
|
# Start with the basic filter if any satellites are selected
|
||||||
if selected_satellites:
|
if selected_satellites:
|
||||||
# Start with the basic filter
|
# Start with the basic filter - optimized with prefetch_related for all related objects
|
||||||
objects = ObjItem.objects.select_related(
|
objects = ObjItem.objects.select_related(
|
||||||
'id_user_add__user',
|
|
||||||
'geo_obj',
|
'geo_obj',
|
||||||
'updated_by__user'
|
'updated_by__user',
|
||||||
|
'created_by__user',
|
||||||
).prefetch_related(
|
).prefetch_related(
|
||||||
'parameters_obj__id_satellite',
|
'parameters_obj__id_satellite',
|
||||||
'parameters_obj__polarization',
|
'parameters_obj__polarization',
|
||||||
@@ -362,9 +376,9 @@ class ObjItemListView(LoginRequiredMixin, View):
|
|||||||
else:
|
else:
|
||||||
# If no satellites are selected, start with all objects
|
# If no satellites are selected, start with all objects
|
||||||
objects = ObjItem.objects.select_related(
|
objects = ObjItem.objects.select_related(
|
||||||
'id_user_add__user',
|
|
||||||
'geo_obj',
|
'geo_obj',
|
||||||
'updated_by__user'
|
'updated_by__user',
|
||||||
|
'created_by__user',
|
||||||
).prefetch_related(
|
).prefetch_related(
|
||||||
'parameters_obj__id_satellite',
|
'parameters_obj__id_satellite',
|
||||||
'parameters_obj__polarization',
|
'parameters_obj__polarization',
|
||||||
@@ -468,8 +482,13 @@ class ObjItemListView(LoginRequiredMixin, View):
|
|||||||
# Prepare the data to include calculated fields for the template
|
# Prepare the data to include calculated fields for the template
|
||||||
processed_objects = []
|
processed_objects = []
|
||||||
for obj in page_obj:
|
for obj in page_obj:
|
||||||
# Get the first parameter
|
# Get the first parameter using the prefetched relation to avoid additional queries
|
||||||
param = obj.parameters_obj.first() if obj.parameters_obj.exists() else None
|
param = None
|
||||||
|
if hasattr(obj, 'parameters_obj') and obj.parameters_obj.all():
|
||||||
|
# Get parameters from the prefetched queryset without triggering new query
|
||||||
|
param_list = list(obj.parameters_obj.all())
|
||||||
|
if param_list:
|
||||||
|
param = param_list[0] # Get first parameter without additional query
|
||||||
|
|
||||||
# Process geo coordinates
|
# Process geo coordinates
|
||||||
geo_coords = "-"
|
geo_coords = "-"
|
||||||
@@ -518,22 +537,51 @@ class ObjItemListView(LoginRequiredMixin, View):
|
|||||||
if obj.geo_obj.distance_kup_valid is not None:
|
if obj.geo_obj.distance_kup_valid is not None:
|
||||||
distance_kup_valid = f"{obj.geo_obj.distance_kup_valid:.3f}"
|
distance_kup_valid = f"{obj.geo_obj.distance_kup_valid:.3f}"
|
||||||
|
|
||||||
|
# Extract related object data to avoid additional queries in template
|
||||||
|
satellite_name = "-"
|
||||||
|
frequency = "-"
|
||||||
|
freq_range = "-"
|
||||||
|
polarization_name = "-"
|
||||||
|
bod_velocity = "-"
|
||||||
|
modulation_name = "-"
|
||||||
|
snr = "-"
|
||||||
|
|
||||||
|
if param:
|
||||||
|
# Get satellite data directly to avoid additional query
|
||||||
|
if hasattr(param, 'id_satellite') and param.id_satellite:
|
||||||
|
satellite_name = param.id_satellite.name if hasattr(param.id_satellite, 'name') else "-"
|
||||||
|
|
||||||
|
# Get parameter values directly
|
||||||
|
frequency = f"{param.frequency:.3f}" if param.frequency is not None else "-"
|
||||||
|
freq_range = f"{param.freq_range:.3f}" if param.freq_range is not None else "-"
|
||||||
|
bod_velocity = f"{param.bod_velocity:.0f}" if param.bod_velocity is not None else "-"
|
||||||
|
snr = f"{param.snr:.0f}" if param.snr is not None else "-"
|
||||||
|
|
||||||
|
# Get polarization name directly to avoid additional query
|
||||||
|
if hasattr(param, 'polarization') and param.polarization:
|
||||||
|
polarization_name = param.polarization.name if hasattr(param.polarization, 'name') else "-"
|
||||||
|
|
||||||
|
# Get modulation name directly to avoid additional query
|
||||||
|
if hasattr(param, 'modulation') and param.modulation:
|
||||||
|
modulation_name = param.modulation.name if hasattr(param.modulation, 'name') else "-"
|
||||||
|
|
||||||
processed_objects.append({
|
processed_objects.append({
|
||||||
'id': obj.id,
|
'id': obj.id,
|
||||||
'name': obj.name or "-",
|
'name': obj.name or "-",
|
||||||
'satellite_name': param.id_satellite.name if param and param.id_satellite else "-",
|
'satellite_name': satellite_name,
|
||||||
'frequency': f"{param.frequency:.3f}" if param and param.frequency else "-",
|
'frequency': frequency,
|
||||||
'freq_range': f"{param.freq_range:.3f}" if param and param.freq_range else "-",
|
'freq_range': freq_range,
|
||||||
'polarization': param.polarization.name if param and param.polarization else "-",
|
'polarization': polarization_name,
|
||||||
'bod_velocity': f"{param.bod_velocity:.0f}" if param and param.bod_velocity else "-",
|
'bod_velocity': bod_velocity,
|
||||||
'modulation': param.modulation.name if param and param.modulation else "-",
|
'modulation': modulation_name,
|
||||||
'snr': f"{param.snr:.0f}" if param and param.snr else "-",
|
'snr': snr,
|
||||||
'geo_coords': geo_coords,
|
'geo_coords': geo_coords,
|
||||||
'kupsat_coords': kupsat_coords,
|
'kupsat_coords': kupsat_coords,
|
||||||
'valid_coords': valid_coords,
|
'valid_coords': valid_coords,
|
||||||
'distance_geo_kup': distance_geo_kup,
|
'distance_geo_kup': distance_geo_kup,
|
||||||
'distance_geo_valid': distance_geo_valid,
|
'distance_geo_valid': distance_geo_valid,
|
||||||
'distance_kup_valid': distance_kup_valid,
|
'distance_kup_valid': distance_kup_valid,
|
||||||
|
'updated_by': obj.updated_by if obj.updated_by else '-',
|
||||||
'obj': obj
|
'obj': obj
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -571,4 +619,209 @@ class ObjItemListView(LoginRequiredMixin, View):
|
|||||||
'full_width_page': True,
|
'full_width_page': True,
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, 'mainapp/objitem_list.html', context)
|
return render(request, 'mainapp/objitem_list.html', context)
|
||||||
|
|
||||||
|
class ObjItemUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
|
||||||
|
model = ObjItem
|
||||||
|
form_class = ObjItemForm
|
||||||
|
template_name = 'mainapp/objitem_form.html'
|
||||||
|
success_url = reverse_lazy('home')
|
||||||
|
|
||||||
|
def test_func(self):
|
||||||
|
return self.request.user.customuser.role in ['admin', 'moderator']
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
# Добавляем контекст для карты
|
||||||
|
context['LEAFLET_CONFIG'] = {
|
||||||
|
'DEFAULT_CENTER': (55.75, 37.62),
|
||||||
|
'DEFAULT_ZOOM': 5,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Остальной контекст остается без изменений
|
||||||
|
ParameterFormSet = modelformset_factory(
|
||||||
|
Parameter,
|
||||||
|
form=ParameterForm,
|
||||||
|
extra=0,
|
||||||
|
can_delete=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.object:
|
||||||
|
parameter_queryset = self.object.parameters_obj.all()
|
||||||
|
context['parameter_forms'] = ParameterFormSet(
|
||||||
|
queryset=parameter_queryset,
|
||||||
|
prefix='parameters'
|
||||||
|
)
|
||||||
|
|
||||||
|
if hasattr(self.object, 'geo_obj'):
|
||||||
|
context['geo_form'] = GeoForm(instance=self.object.geo_obj, prefix='geo')
|
||||||
|
else:
|
||||||
|
context['geo_form'] = GeoForm(prefix='geo')
|
||||||
|
else:
|
||||||
|
context['parameter_forms'] = ParameterFormSet(
|
||||||
|
queryset=Parameter.objects.none(),
|
||||||
|
prefix='parameters'
|
||||||
|
)
|
||||||
|
context['geo_form'] = GeoForm(prefix='geo')
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
context = self.get_context_data()
|
||||||
|
parameter_forms = context['parameter_forms']
|
||||||
|
geo_form = context['geo_form']
|
||||||
|
|
||||||
|
# Сохраняем основной объект
|
||||||
|
self.object = form.save(commit=False)
|
||||||
|
self.object.updated_by = self.request.user.customuser
|
||||||
|
self.object.save()
|
||||||
|
|
||||||
|
# Сохраняем связанные параметры
|
||||||
|
if parameter_forms.is_valid():
|
||||||
|
instances = parameter_forms.save(commit=False)
|
||||||
|
for instance in instances:
|
||||||
|
instance.save()
|
||||||
|
instance.objitems.set([self.object])
|
||||||
|
|
||||||
|
# Сохраняем геоданные
|
||||||
|
geo_instance = None
|
||||||
|
if hasattr(self.object, 'geo_obj'):
|
||||||
|
geo_instance = self.object.geo_obj
|
||||||
|
|
||||||
|
# Создаем или обновляем гео-объект
|
||||||
|
if geo_instance is None:
|
||||||
|
geo_instance = Geo(objitem=self.object)
|
||||||
|
|
||||||
|
# Обновляем поля из geo_form
|
||||||
|
if geo_form.is_valid():
|
||||||
|
geo_instance.location = geo_form.cleaned_data['location']
|
||||||
|
geo_instance.comment = geo_form.cleaned_data['comment']
|
||||||
|
geo_instance.is_average = geo_form.cleaned_data['is_average']
|
||||||
|
|
||||||
|
# Обрабатываем координаты геолокации
|
||||||
|
geo_longitude = self.request.POST.get('geo_longitude')
|
||||||
|
geo_latitude = self.request.POST.get('geo_latitude')
|
||||||
|
if geo_longitude and geo_latitude:
|
||||||
|
geo_instance.coords = Point(float(geo_longitude), float(geo_latitude))
|
||||||
|
|
||||||
|
# Обрабатываем координаты Кубсата
|
||||||
|
kupsat_longitude = self.request.POST.get('kupsat_longitude')
|
||||||
|
kupsat_latitude = self.request.POST.get('kupsat_latitude')
|
||||||
|
if kupsat_longitude and kupsat_latitude:
|
||||||
|
geo_instance.coords_kupsat = Point(float(kupsat_longitude), float(kupsat_latitude))
|
||||||
|
|
||||||
|
# Обрабатываем координаты оперативников
|
||||||
|
valid_longitude = self.request.POST.get('valid_longitude')
|
||||||
|
valid_latitude = self.request.POST.get('valid_latitude')
|
||||||
|
if valid_longitude and valid_latitude:
|
||||||
|
geo_instance.coords_valid = Point(float(valid_longitude), float(valid_latitude))
|
||||||
|
|
||||||
|
# Обрабатываем дату/время
|
||||||
|
timestamp_date = self.request.POST.get('timestamp_date')
|
||||||
|
timestamp_time = self.request.POST.get('timestamp_time')
|
||||||
|
if timestamp_date and timestamp_time:
|
||||||
|
naive_datetime = datetime.strptime(f"{timestamp_date} {timestamp_time}", "%Y-%m-%d %H:%M")
|
||||||
|
geo_instance.timestamp = naive_datetime
|
||||||
|
|
||||||
|
geo_instance.save()
|
||||||
|
|
||||||
|
messages.success(self.request, 'Объект успешно сохранён!')
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
class ObjItemCreateView(LoginRequiredMixin, UserPassesTestMixin, CreateView):
|
||||||
|
model = ObjItem
|
||||||
|
form_class = ObjItemForm
|
||||||
|
template_name = 'mainapp/objitem_form.html'
|
||||||
|
success_url = reverse_lazy('home')
|
||||||
|
|
||||||
|
def test_func(self):
|
||||||
|
return self.request.user.customuser.role in ['admin', 'moderator']
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
ParameterFormSet = modelformset_factory(
|
||||||
|
Parameter,
|
||||||
|
form=ParameterForm,
|
||||||
|
extra=1,
|
||||||
|
can_delete=True
|
||||||
|
)
|
||||||
|
|
||||||
|
context['parameter_forms'] = ParameterFormSet(
|
||||||
|
queryset=Parameter.objects.none(),
|
||||||
|
prefix='parameters'
|
||||||
|
)
|
||||||
|
|
||||||
|
context['geo_form'] = GeoForm(prefix='geo')
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
context = self.get_context_data()
|
||||||
|
parameter_forms = context['parameter_forms']
|
||||||
|
geo_form = context['geo_form']
|
||||||
|
|
||||||
|
# Сохраняем основной объект
|
||||||
|
self.object = form.save(commit=False)
|
||||||
|
self.object.created_by = self.request.user.customuser
|
||||||
|
self.object.updated_by = self.request.user.customuser
|
||||||
|
self.object.save()
|
||||||
|
|
||||||
|
# Сохраняем связанные параметры
|
||||||
|
if parameter_forms.is_valid():
|
||||||
|
instances = parameter_forms.save(commit=False)
|
||||||
|
for instance in instances:
|
||||||
|
instance.save()
|
||||||
|
instance.objitems.add(self.object)
|
||||||
|
|
||||||
|
# Создаем гео-объект
|
||||||
|
geo_instance = Geo(objitem=self.object)
|
||||||
|
|
||||||
|
# Обновляем поля из geo_form
|
||||||
|
if geo_form.is_valid():
|
||||||
|
geo_instance.location = geo_form.cleaned_data['location']
|
||||||
|
geo_instance.comment = geo_form.cleaned_data['comment']
|
||||||
|
geo_instance.is_average = geo_form.cleaned_data['is_average']
|
||||||
|
|
||||||
|
# Обрабатываем координаты геолокации
|
||||||
|
geo_longitude = self.request.POST.get('geo_longitude')
|
||||||
|
geo_latitude = self.request.POST.get('geo_latitude')
|
||||||
|
if geo_longitude and geo_latitude:
|
||||||
|
geo_instance.coords = Point(float(geo_longitude), float(geo_latitude))
|
||||||
|
|
||||||
|
# Обрабатываем координаты Кубсата
|
||||||
|
kupsat_longitude = self.request.POST.get('kupsat_longitude')
|
||||||
|
kupsat_latitude = self.request.POST.get('kupsat_latitude')
|
||||||
|
if kupsat_longitude and kupsat_latitude:
|
||||||
|
geo_instance.coords_kupsat = Point(float(kupsat_longitude), float(kupsat_latitude))
|
||||||
|
|
||||||
|
# Обрабатываем координаты оперативников
|
||||||
|
valid_longitude = self.request.POST.get('valid_longitude')
|
||||||
|
valid_latitude = self.request.POST.get('valid_latitude')
|
||||||
|
if valid_longitude and valid_latitude:
|
||||||
|
geo_instance.coords_valid = Point(float(valid_longitude), float(valid_latitude))
|
||||||
|
|
||||||
|
# Обрабатываем дату/время
|
||||||
|
timestamp_date = self.request.POST.get('timestamp_date')
|
||||||
|
timestamp_time = self.request.POST.get('timestamp_time')
|
||||||
|
if timestamp_date and timestamp_time:
|
||||||
|
naive_datetime = datetime.strptime(f"{timestamp_date} {timestamp_time}", "%Y-%m-%d %H:%M")
|
||||||
|
geo_instance.timestamp = naive_datetime
|
||||||
|
|
||||||
|
geo_instance.save()
|
||||||
|
|
||||||
|
messages.success(self.request, 'Объект успешно создан!')
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
class ObjItemDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
|
||||||
|
model = ObjItem
|
||||||
|
template_name = 'mainapp/objitem_confirm_delete.html'
|
||||||
|
success_url = reverse_lazy('home')
|
||||||
|
|
||||||
|
def test_func(self):
|
||||||
|
return self.request.user.customuser.role in ['admin', 'moderator']
|
||||||
|
|
||||||
|
def delete(self, request, *args, **kwargs):
|
||||||
|
messages.success(self.request, 'Объект успешно удалён!')
|
||||||
|
return super().delete(request, *args, **kwargs)
|
||||||
Reference in New Issue
Block a user