-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
- {% if user|has_perm:'mark_create' %}
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-{% endif %}
{% endblock %}
+
{% block extra_js %}
{% endblock %}
diff --git a/dbapp/mainapp/views/marks.py b/dbapp/mainapp/views/marks.py
index b270012..89e56b2 100644
--- a/dbapp/mainapp/views/marks.py
+++ b/dbapp/mainapp/views/marks.py
@@ -68,6 +68,7 @@ class SignalMarksHistoryAPIView(LoginRequiredMixin, View):
"""
API для получения истории отметок с фиксированными 15 колонками.
Делит выбранный временной диапазон на 15 равных периодов.
+ Поддерживает два режима: группировка по диапазонам и все отметки за 90 дней.
"""
NUM_COLUMNS = 15 # Фиксированное количество колонок
@@ -81,6 +82,18 @@ class SignalMarksHistoryAPIView(LoginRequiredMixin, View):
date_to = request.GET.get('date_to')
page = int(request.GET.get('page', 1))
size = int(request.GET.get('size', 50))
+ view_mode = request.GET.get('view_mode', 'grouped') # 'grouped' или 'all_marks'
+
+ # Фильтры
+ polarization_ids = request.GET.getlist('polarization_id')
+ modulation_ids = request.GET.getlist('modulation_id')
+ freq_min = request.GET.get('freq_min')
+ freq_max = request.GET.get('freq_max')
+ freq_range_min = request.GET.get('freq_range_min')
+ freq_range_max = request.GET.get('freq_range_max')
+ bod_velocity_min = request.GET.get('bod_velocity_min')
+ bod_velocity_max = request.GET.get('bod_velocity_max')
+ search = request.GET.get('search', '').strip()
if not satellite_id:
return JsonResponse({'error': 'Не выбран спутник'}, status=400)
@@ -92,11 +105,56 @@ class SignalMarksHistoryAPIView(LoginRequiredMixin, View):
'polarization', 'modulation', 'standard'
).order_by('frequency', 'name')
+ # Применяем фильтры к теханализам
+ if polarization_ids:
+ tech_analyzes = tech_analyzes.filter(polarization_id__in=polarization_ids)
+ if modulation_ids:
+ tech_analyzes = tech_analyzes.filter(modulation_id__in=modulation_ids)
+ if freq_min:
+ try:
+ tech_analyzes = tech_analyzes.filter(frequency__gte=float(freq_min))
+ except ValueError:
+ pass
+ if freq_max:
+ try:
+ tech_analyzes = tech_analyzes.filter(frequency__lte=float(freq_max))
+ except ValueError:
+ pass
+ if freq_range_min:
+ try:
+ tech_analyzes = tech_analyzes.filter(freq_range__gte=float(freq_range_min))
+ except ValueError:
+ pass
+ if freq_range_max:
+ try:
+ tech_analyzes = tech_analyzes.filter(freq_range__lte=float(freq_range_max))
+ except ValueError:
+ pass
+ if bod_velocity_min:
+ try:
+ tech_analyzes = tech_analyzes.filter(bod_velocity__gte=float(bod_velocity_min))
+ except ValueError:
+ pass
+ if bod_velocity_max:
+ try:
+ tech_analyzes = tech_analyzes.filter(bod_velocity__lte=float(bod_velocity_max))
+ except ValueError:
+ pass
+ if search:
+ tech_analyzes = tech_analyzes.filter(
+ Q(name__icontains=search) | Q(id__icontains=search)
+ )
+
# Базовый фильтр отметок по спутнику
marks_base_qs = ObjectMark.objects.filter(
tech_analyze__satellite_id=satellite_id
).select_related('created_by__user', 'tech_analyze')
+ # Режим "все отметки за 90 дней"
+ if view_mode == 'all_marks':
+ return self._get_all_marks_mode(request, tech_analyzes, marks_base_qs, size, page)
+
+ # Режим группировки по диапазонам (по умолчанию)
# Определяем диапазон дат
parsed_date_from = None
parsed_date_to = None
@@ -127,6 +185,7 @@ class SignalMarksHistoryAPIView(LoginRequiredMixin, View):
'last_page': 1,
'total': 0,
'message': 'Нет отметок в выбранном диапазоне',
+ 'view_mode': view_mode,
})
# Используем указанные даты или данные из БД
@@ -143,9 +202,9 @@ class SignalMarksHistoryAPIView(LoginRequiredMixin, View):
total_duration = end_dt - start_dt
period_duration = total_duration / self.NUM_COLUMNS
- # Генерируем границы периодов
+ # Генерируем границы периодов (от новых к старым)
periods = []
- for i in range(self.NUM_COLUMNS):
+ for i in range(self.NUM_COLUMNS - 1, -1, -1):
period_start = start_dt + (period_duration * i)
period_end = start_dt + (period_duration * (i + 1))
periods.append({
@@ -172,6 +231,11 @@ class SignalMarksHistoryAPIView(LoginRequiredMixin, View):
row = {
'id': ta.id,
'name': ta.name,
+ 'frequency': float(ta.frequency) if ta.frequency else 0,
+ 'freq_range': float(ta.freq_range) if ta.freq_range else 0,
+ 'polarization': ta.polarization.name if ta.polarization else '-',
+ 'modulation': ta.modulation.name if ta.modulation else '-',
+ 'bod_velocity': float(ta.bod_velocity) if ta.bod_velocity else 0,
'marks': [],
}
@@ -204,6 +268,129 @@ class SignalMarksHistoryAPIView(LoginRequiredMixin, View):
'data': data,
'last_page': num_pages,
'total': total_count,
+ 'view_mode': view_mode,
+ })
+
+ def _get_all_marks_mode(self, request, tech_analyzes, marks_base_qs, size, page):
+ """
+ Режим отображения отметок за последние 90 дней.
+ Группировка по дням (1 колонка = 1 день) для оптимизации.
+ Максимум 90 колонок.
+ """
+ from datetime import datetime, date
+
+ # Фильтруем отметки за последние 90 дней
+ date_90_days_ago = timezone.now() - timedelta(days=90)
+ marks_base_qs = marks_base_qs.filter(timestamp__gte=date_90_days_ago)
+
+ # Получаем диапазон дат с отметками
+ date_range = marks_base_qs.aggregate(
+ min_date=Min('timestamp'),
+ max_date=Max('timestamp')
+ )
+
+ min_date = date_range['min_date']
+ max_date = date_range['max_date']
+
+ if not min_date or not max_date:
+ return JsonResponse({
+ 'periods': [],
+ 'data': [],
+ 'last_page': 1,
+ 'total': 0,
+ 'message': 'Нет отметок за последние 90 дней',
+ 'view_mode': 'all_marks',
+ })
+
+ # Генерируем список дней от max_date до min_date (от новых к старым)
+ days = []
+ current_date = max_date.date()
+ end_date = min_date.date()
+ while current_date >= end_date:
+ days.append(current_date)
+ current_date -= timedelta(days=1)
+
+ # Формируем заголовки колонок (дни)
+ periods = []
+ for day in days:
+ periods.append({
+ 'date': day,
+ 'label': day.strftime('%d.%m'),
+ })
+
+ # Пагинация теханализов
+ if size == 0:
+ page_obj = tech_analyzes
+ num_pages = 1
+ total_count = tech_analyzes.count()
+ else:
+ paginator = Paginator(tech_analyzes, size)
+ page_obj = paginator.get_page(page)
+ num_pages = paginator.num_pages
+ total_count = paginator.count
+
+ # Получаем ID теханализов на текущей странице
+ ta_ids = [ta.id for ta in page_obj]
+
+ # Загружаем все отметки для этих теханализов одним запросом
+ all_marks = list(marks_base_qs.filter(
+ tech_analyze_id__in=ta_ids
+ ).order_by('-timestamp'))
+
+ # Создаём словарь: {(tech_analyze_id, date): список всех отметок за день}
+ marks_dict = {}
+ for mark in all_marks:
+ mark_date = timezone.localtime(mark.timestamp).date()
+ key = (mark.tech_analyze_id, mark_date)
+ if key not in marks_dict:
+ marks_dict[key] = []
+ marks_dict[key].append(mark)
+
+ # Формируем данные
+ data = []
+ for ta in page_obj:
+ row = {
+ 'id': ta.id,
+ 'name': ta.name,
+ 'frequency': float(ta.frequency) if ta.frequency else 0,
+ 'freq_range': float(ta.freq_range) if ta.freq_range else 0,
+ 'polarization': ta.polarization.name if ta.polarization else '-',
+ 'modulation': ta.modulation.name if ta.modulation else '-',
+ 'bod_velocity': float(ta.bod_velocity) if ta.bod_velocity else 0,
+ 'marks': [],
+ }
+
+ # Для каждого дня собираем все отметки
+ for period in periods:
+ key = (ta.id, period['date'])
+ day_marks = marks_dict.get(key, [])
+
+ if day_marks:
+ # Формируем список всех отметок за день
+ marks_list = []
+ for mark in day_marks:
+ local_time = timezone.localtime(mark.timestamp)
+ marks_list.append({
+ 'mark': mark.mark,
+ 'user': str(mark.created_by) if mark.created_by else '-',
+ 'time': local_time.strftime('%H:%M'),
+ })
+ row['marks'].append({
+ 'count': len(marks_list),
+ 'items': marks_list,
+ })
+ else:
+ row['marks'].append(None)
+
+ data.append(row)
+
+ return JsonResponse({
+ 'periods': [p['label'] for p in periods],
+ 'data': data,
+ 'last_page': num_pages,
+ 'total': total_count,
+ 'view_mode': 'all_marks',
+ 'date_range': f"Последние 90 дней (с {date_90_days_ago.strftime('%d.%m.%Y')})",
})
def _format_period_label(self, start, end, total_duration):