Добавил зоны для спутников

This commit is contained in:
2025-12-08 15:48:46 +03:00
parent 8fb8b08c93
commit 25fe93231f
8 changed files with 128 additions and 29 deletions

View File

@@ -811,6 +811,7 @@ class SatelliteForm(forms.ModelForm):
fields = [ fields = [
'name', 'name',
'alternative_name', 'alternative_name',
'location_place',
'norad', 'norad',
'international_code', 'international_code',
'band', 'band',
@@ -829,6 +830,9 @@ class SatelliteForm(forms.ModelForm):
'class': 'form-control', 'class': 'form-control',
'placeholder': 'Введите альтернативное название (необязательно)' 'placeholder': 'Введите альтернативное название (необязательно)'
}), }),
'location_place': forms.Select(attrs={
'class': 'form-select'
}),
'norad': forms.NumberInput(attrs={ 'norad': forms.NumberInput(attrs={
'class': 'form-control', 'class': 'form-control',
'placeholder': 'Введите NORAD ID' 'placeholder': 'Введите NORAD ID'
@@ -863,6 +867,7 @@ class SatelliteForm(forms.ModelForm):
labels = { labels = {
'name': 'Название спутника', 'name': 'Название спутника',
'alternative_name': 'Альтернативное название', 'alternative_name': 'Альтернативное название',
'location_place': 'Комплекс',
'norad': 'NORAD ID', 'norad': 'NORAD ID',
'international_code': 'Международный код', 'international_code': 'Международный код',
'band': 'Диапазоны работы', 'band': 'Диапазоны работы',
@@ -874,6 +879,7 @@ class SatelliteForm(forms.ModelForm):
help_texts = { help_texts = {
'name': 'Уникальное название спутника', 'name': 'Уникальное название спутника',
'alternative_name': 'Альтернативное название спутника (например, на другом языке)', 'alternative_name': 'Альтернативное название спутника (например, на другом языке)',
'location_place': 'К какому комплексу принадлежит спутник',
'norad': 'Идентификатор NORAD для отслеживания спутника', 'norad': 'Идентификатор NORAD для отслеживания спутника',
'international_code': 'Международный идентификатор спутника (например, 2011-074A)', 'international_code': 'Международный идентификатор спутника (например, 2011-074A)',
'band': 'Выберите диапазоны работы спутника (удерживайте Ctrl для множественного выбора)', 'band': 'Выберите диапазоны работы спутника (удерживайте Ctrl для множественного выбора)',

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.7 on 2025-12-08 12:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0019_add_coords_to_source_request'),
]
operations = [
migrations.AddField(
model_name='satellite',
name='location_place',
field=models.CharField(choices=[('kr', 'КР'), ('dv', 'ДВ')], default='kr', help_text='К какому комплексу принадлежит спутник', max_length=30, null=True, verbose_name='Комплекс'),
),
]

View File

@@ -307,10 +307,10 @@ class Satellite(models.Model):
Представляет спутник связи с его основными характеристиками. Представляет спутник связи с его основными характеристиками.
""" """
# PLACES = [ PLACES = [
# ("kr", "КР"), ("kr", "КР"),
# ("dv", "ДВ") ("dv", "ДВ")
# ] ]
# Основные поля # Основные поля
name = models.CharField( name = models.CharField(
max_length=100, max_length=100,
@@ -327,14 +327,14 @@ class Satellite(models.Model):
db_index=True, db_index=True,
help_text="Альтернативное название спутника", help_text="Альтернативное название спутника",
) )
# location_place = models.CharField( location_place = models.CharField(
# max_length=30, max_length=30,
# choices=PLACES, choices=PLACES,
# null=True, null=True,
# default="kr", default="kr",
# verbose_name="Комплекс", verbose_name="Комплекс",
# help_text="К какому комплексу принадлежит спутник", help_text="К какому комплексу принадлежит спутник",
# ) )
norad = models.IntegerField( norad = models.IntegerField(
blank=True, blank=True,
null=True, null=True,

View File

@@ -69,7 +69,7 @@
{% csrf_token %} {% csrf_token %}
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-4">
<div class="mb-3"> <div class="mb-3">
<label for="{{ form.name.id_for_label }}" class="form-label"> <label for="{{ form.name.id_for_label }}" class="form-label">
{{ form.name.label }} <span class="text-danger">*</span> {{ form.name.label }} <span class="text-danger">*</span>
@@ -86,7 +86,7 @@
</div> </div>
</div> </div>
<div class="col-md-6"> <div class="col-md-4">
<div class="mb-3"> <div class="mb-3">
<label for="{{ form.alternative_name.id_for_label }}" class="form-label"> <label for="{{ form.alternative_name.id_for_label }}" class="form-label">
{{ form.alternative_name.label }} {{ form.alternative_name.label }}
@@ -102,6 +102,23 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="col-md-4">
<div class="mb-3">
<label for="{{ form.location_place.id_for_label }}" class="form-label">
{{ form.location_place.label }}
</label>
{{ form.location_place }}
{% if form.location_place.errors %}
<div class="invalid-feedback d-block">
{{ form.location_place.errors.0 }}
</div>
{% endif %}
{% if form.location_place.help_text %}
<div class="form-text">{{ form.location_place.help_text }}</div>
{% endif %}
</div>
</div>
</div> </div>
<div class="row"> <div class="row">

View File

@@ -95,6 +95,18 @@
</div> </div>
<div class="offcanvas-body"> <div class="offcanvas-body">
<form method="get" id="filter-form"> <form method="get" id="filter-form">
<!-- Location Place Selection -->
<div class="mb-2">
<label class="form-label">Комплекс:</label>
<select name="location_place" class="form-select form-select-sm mb-2" multiple size="3">
{% for value, label in location_places %}
<option value="{{ value }}" {% if value in selected_location_places %}selected{% endif %}>
{{ label }}
</option>
{% endfor %}
</select>
</div>
<!-- Band Selection - Multi-select --> <!-- Band Selection - Multi-select -->
<div class="mb-2"> <div class="mb-2">
<label class="form-label">Диапазон:</label> <label class="form-label">Диапазон:</label>
@@ -200,6 +212,16 @@
{% endif %} {% endif %}
</a> </a>
</th> </th>
<th scope="col" style="min-width: 80px;">
<a href="javascript:void(0)" onclick="updateSort('location_place')" class="text-white text-decoration-none">
Комплекс
{% if sort == 'location_place' %}
<i class="bi bi-arrow-up"></i>
{% elif sort == '-location_place' %}
<i class="bi bi-arrow-down"></i>
{% endif %}
</a>
</th>
<th scope="col" style="min-width: 100px;"> <th scope="col" style="min-width: 100px;">
<a href="javascript:void(0)" onclick="updateSort('norad')" class="text-white text-decoration-none"> <a href="javascript:void(0)" onclick="updateSort('norad')" class="text-white text-decoration-none">
NORAD ID NORAD ID
@@ -285,6 +307,7 @@
<td class="text-center">{{ satellite.id }}</td> <td class="text-center">{{ satellite.id }}</td>
<td>{{ satellite.name }}</td> <td>{{ satellite.name }}</td>
<td>{{ satellite.alternative_name|default:"-" }}</td> <td>{{ satellite.alternative_name|default:"-" }}</td>
<td>{{ satellite.location_place }}</td>
<td>{{ satellite.norad }}</td> <td>{{ satellite.norad }}</td>
<td>{{ satellite.international_code|default:"-" }}</td> <td>{{ satellite.international_code|default:"-" }}</td>
<td>{{ satellite.bands }}</td> <td>{{ satellite.bands }}</td>
@@ -327,7 +350,7 @@
</tr> </tr>
{% empty %} {% empty %}
<tr> <tr>
<td colspan="14" class="text-center text-muted">Нет данных для отображения</td> <td colspan="15" class="text-center text-muted">Нет данных для отображения</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View File

@@ -86,6 +86,18 @@
value="{{ date_to }}"> value="{{ date_to }}">
</div> </div>
<!-- Location Place filter -->
<div class="col-auto">
<label class="form-label">Комплекс:</label>
<select name="location_place" class="form-select" multiple size="2">
{% for value, label in location_places %}
<option value="{{ value }}" {% if value in selected_location_places %}selected{% endif %}>
{{ label }}
</option>
{% endfor %}
</select>
</div>
<!-- Satellite filter with custom widget --> <!-- Satellite filter with custom widget -->
<div class="col-md-3"> <div class="col-md-3">
<label class="form-label">Спутники:</label> <label class="form-label">Спутники:</label>

View File

@@ -32,6 +32,7 @@ class SatelliteListView(LoginRequiredMixin, View):
# Get filter parameters # Get filter parameters
search_query = request.GET.get("search", "").strip() search_query = request.GET.get("search", "").strip()
selected_bands = request.GET.getlist("band_id") selected_bands = request.GET.getlist("band_id")
selected_location_places = request.GET.getlist("location_place")
norad_min = request.GET.get("norad_min", "").strip() norad_min = request.GET.get("norad_min", "").strip()
norad_max = request.GET.get("norad_max", "").strip() norad_max = request.GET.get("norad_max", "").strip()
undersat_point_min = request.GET.get("undersat_point_min", "").strip() undersat_point_min = request.GET.get("undersat_point_min", "").strip()
@@ -58,6 +59,10 @@ class SatelliteListView(LoginRequiredMixin, View):
if selected_bands: if selected_bands:
satellites = satellites.filter(band__id__in=selected_bands).distinct() satellites = satellites.filter(band__id__in=selected_bands).distinct()
# Filter by location_place
if selected_location_places:
satellites = satellites.filter(location_place__in=selected_location_places)
# Filter by NORAD ID # Filter by NORAD ID
if norad_min: if norad_min:
try: try:
@@ -154,6 +159,8 @@ class SatelliteListView(LoginRequiredMixin, View):
"-updated_at": "-updated_at", "-updated_at": "-updated_at",
"transponder_count": "transponder_count", "transponder_count": "transponder_count",
"-transponder_count": "-transponder_count", "-transponder_count": "-transponder_count",
"location_place": "location_place",
"-location_place": "-location_place",
} }
if sort_param in valid_sort_fields: if sort_param in valid_sort_fields:
@@ -169,10 +176,14 @@ class SatelliteListView(LoginRequiredMixin, View):
# Get band names # Get band names
band_names = [band.name for band in satellite.band.all()] band_names = [band.name for band in satellite.band.all()]
# Get location_place display value
location_place_display = dict(Satellite.PLACES).get(satellite.location_place, "-") if satellite.location_place else "-"
processed_satellites.append({ processed_satellites.append({
'id': satellite.id, 'id': satellite.id,
'name': satellite.name or "-", 'name': satellite.name or "-",
'alternative_name': satellite.alternative_name or "-", 'alternative_name': satellite.alternative_name or "-",
'location_place': location_place_display,
'norad': satellite.norad if satellite.norad else "-", 'norad': satellite.norad if satellite.norad else "-",
'international_code': satellite.international_code or "-", 'international_code': satellite.international_code or "-",
'bands': ", ".join(band_names) if band_names else "-", 'bands': ", ".join(band_names) if band_names else "-",
@@ -200,6 +211,8 @@ class SatelliteListView(LoginRequiredMixin, View):
int(x) if isinstance(x, str) else x for x in selected_bands int(x) if isinstance(x, str) else x for x in selected_bands
if (isinstance(x, int) or (isinstance(x, str) and x.isdigit())) if (isinstance(x, int) or (isinstance(x, str) and x.isdigit()))
], ],
'location_places': Satellite.PLACES,
'selected_location_places': selected_location_places,
'norad_min': norad_min, 'norad_min': norad_min,
'norad_max': norad_max, 'norad_max': norad_max,
'undersat_point_min': undersat_point_min, 'undersat_point_min': undersat_point_min,

View File

@@ -62,7 +62,11 @@ class StatisticsView(TemplateView):
satellite_ids = self.request.GET.getlist('satellite_id') satellite_ids = self.request.GET.getlist('satellite_id')
return [int(sid) for sid in satellite_ids if sid.isdigit()] return [int(sid) for sid in satellite_ids if sid.isdigit()]
def get_base_queryset(self, date_from, date_to, satellite_ids): def get_selected_location_places(self):
"""Получает выбранные комплексы из параметров запроса."""
return self.request.GET.getlist('location_place')
def get_base_queryset(self, date_from, date_to, satellite_ids, location_places=None):
"""Возвращает базовый queryset ObjItem с фильтрами.""" """Возвращает базовый queryset ObjItem с фильтрами."""
qs = ObjItem.objects.filter( qs = ObjItem.objects.filter(
geo_obj__isnull=False, geo_obj__isnull=False,
@@ -75,12 +79,14 @@ class StatisticsView(TemplateView):
qs = qs.filter(geo_obj__timestamp__date__lte=date_to) qs = qs.filter(geo_obj__timestamp__date__lte=date_to)
if satellite_ids: if satellite_ids:
qs = qs.filter(parameter_obj__id_satellite__id__in=satellite_ids) qs = qs.filter(parameter_obj__id_satellite__id__in=satellite_ids)
if location_places:
qs = qs.filter(parameter_obj__id_satellite__location_place__in=location_places)
return qs return qs
def get_statistics(self, date_from, date_to, satellite_ids): def get_statistics(self, date_from, date_to, satellite_ids, location_places=None):
"""Вычисляет основную статистику.""" """Вычисляет основную статистику."""
base_qs = self.get_base_queryset(date_from, date_to, satellite_ids) base_qs = self.get_base_queryset(date_from, date_to, satellite_ids, location_places)
# Общее количество точек # Общее количество точек
total_points = base_qs.count() total_points = base_qs.count()
@@ -89,13 +95,13 @@ class StatisticsView(TemplateView):
total_sources = base_qs.filter(source__isnull=False).values('source').distinct().count() total_sources = base_qs.filter(source__isnull=False).values('source').distinct().count()
# Новые излучения - объекты, у которых имя появилось впервые в выбранном периоде # Новые излучения - объекты, у которых имя появилось впервые в выбранном периоде
new_emissions_data = self._calculate_new_emissions(date_from, date_to, satellite_ids) new_emissions_data = self._calculate_new_emissions(date_from, date_to, satellite_ids, location_places)
# Статистика по спутникам # Статистика по спутникам
satellite_stats = self._get_satellite_statistics(date_from, date_to, satellite_ids) satellite_stats = self._get_satellite_statistics(date_from, date_to, satellite_ids, location_places)
# Данные для графика по дням # Данные для графика по дням
daily_data = self._get_daily_statistics(date_from, date_to, satellite_ids) daily_data = self._get_daily_statistics(date_from, date_to, satellite_ids, location_places)
return { return {
'total_points': total_points, 'total_points': total_points,
@@ -106,7 +112,7 @@ class StatisticsView(TemplateView):
'daily_data': daily_data, 'daily_data': daily_data,
} }
def _calculate_new_emissions(self, date_from, date_to, satellite_ids): def _calculate_new_emissions(self, date_from, date_to, satellite_ids, location_places=None):
""" """
Вычисляет новые излучения - уникальные имена объектов, Вычисляет новые излучения - уникальные имена объектов,
которые появились впервые в выбранном периоде. которые появились впервые в выбранном периоде.
@@ -129,7 +135,7 @@ class StatisticsView(TemplateView):
) )
# Базовый queryset для выбранного периода # Базовый queryset для выбранного периода
period_qs = self.get_base_queryset(date_from, date_to, satellite_ids).filter( period_qs = self.get_base_queryset(date_from, date_to, satellite_ids, location_places).filter(
name__isnull=False name__isnull=False
).exclude(name='') ).exclude(name='')
@@ -173,9 +179,9 @@ class StatisticsView(TemplateView):
return {'count': len(new_names), 'objects': new_objects} return {'count': len(new_names), 'objects': new_objects}
def _get_satellite_statistics(self, date_from, date_to, satellite_ids): def _get_satellite_statistics(self, date_from, date_to, satellite_ids, location_places=None):
"""Получает статистику по каждому спутнику.""" """Получает статистику по каждому спутнику."""
base_qs = self.get_base_queryset(date_from, date_to, satellite_ids) base_qs = self.get_base_queryset(date_from, date_to, satellite_ids, location_places)
# Группируем по спутникам # Группируем по спутникам
stats = base_qs.filter( stats = base_qs.filter(
@@ -190,9 +196,9 @@ class StatisticsView(TemplateView):
return list(stats) return list(stats)
def _get_daily_statistics(self, date_from, date_to, satellite_ids): def _get_daily_statistics(self, date_from, date_to, satellite_ids, location_places=None):
"""Получает статистику по дням для графика.""" """Получает статистику по дням для графика."""
base_qs = self.get_base_queryset(date_from, date_to, satellite_ids) base_qs = self.get_base_queryset(date_from, date_to, satellite_ids, location_places)
daily = base_qs.annotate( daily = base_qs.annotate(
date=TruncDate('geo_obj__timestamp') date=TruncDate('geo_obj__timestamp')
@@ -208,6 +214,7 @@ class StatisticsView(TemplateView):
date_from, date_to, preset = self.get_date_range() date_from, date_to, preset = self.get_date_range()
satellite_ids = self.get_selected_satellites() satellite_ids = self.get_selected_satellites()
location_places = self.get_selected_location_places()
# Получаем только спутники, у которых есть точки ГЛ # Получаем только спутники, у которых есть точки ГЛ
satellites_with_points = ObjItem.objects.filter( satellites_with_points = ObjItem.objects.filter(
@@ -221,7 +228,7 @@ class StatisticsView(TemplateView):
).order_by('name') ).order_by('name')
# Получаем статистику # Получаем статистику
stats = self.get_statistics(date_from, date_to, satellite_ids) stats = self.get_statistics(date_from, date_to, satellite_ids, location_places)
# Сериализуем данные для JavaScript # Сериализуем данные для JavaScript
daily_data_json = json.dumps([ daily_data_json = json.dumps([
@@ -238,6 +245,8 @@ class StatisticsView(TemplateView):
context.update({ context.update({
'satellites': satellites, 'satellites': satellites,
'selected_satellites': satellite_ids, 'selected_satellites': satellite_ids,
'location_places': Satellite.PLACES,
'selected_location_places': location_places,
'date_from': date_from.isoformat() if date_from else '', 'date_from': date_from.isoformat() if date_from else '',
'date_to': date_to.isoformat() if date_to else '', 'date_to': date_to.isoformat() if date_to else '',
'preset': preset or '', 'preset': preset or '',
@@ -259,7 +268,8 @@ class StatisticsAPIView(StatisticsView):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
date_from, date_to, preset = self.get_date_range() date_from, date_to, preset = self.get_date_range()
satellite_ids = self.get_selected_satellites() satellite_ids = self.get_selected_satellites()
stats = self.get_statistics(date_from, date_to, satellite_ids) location_places = self.get_selected_location_places()
stats = self.get_statistics(date_from, date_to, satellite_ids, location_places)
# Преобразуем даты в строки для JSON # Преобразуем даты в строки для JSON
daily_data = [] daily_data = []