Добавил абуз вч отметок

This commit is contained in:
2025-12-12 15:46:00 +03:00
parent 9bf701f05a
commit ca7709ebff
5 changed files with 122 additions and 121 deletions

View File

@@ -347,8 +347,10 @@ class ParameterInline(admin.StackedInline):
class ObjectMarkAdmin(BaseAdmin): class ObjectMarkAdmin(BaseAdmin):
"""Админ-панель для модели ObjectMark.""" """Админ-панель для модели ObjectMark."""
list_display = ("tech_analyze", "mark", "timestamp", "created_by") list_display = ("id", "tech_analyze", "mark", "timestamp", "created_by")
list_display_links = ("id",)
list_select_related = ("tech_analyze", "tech_analyze__satellite", "created_by__user") list_select_related = ("tech_analyze", "tech_analyze__satellite", "created_by__user")
list_editable = ("tech_analyze", "mark", "timestamp")
search_fields = ("tech_analyze__name", "tech_analyze__id") search_fields = ("tech_analyze__name", "tech_analyze__id")
ordering = ("-timestamp",) ordering = ("-timestamp",)
list_filter = ( list_filter = (
@@ -356,7 +358,6 @@ class ObjectMarkAdmin(BaseAdmin):
("timestamp", DateRangeQuickSelectListFilterBuilder()), ("timestamp", DateRangeQuickSelectListFilterBuilder()),
("tech_analyze__satellite", MultiSelectRelatedDropdownFilter), ("tech_analyze__satellite", MultiSelectRelatedDropdownFilter),
) )
readonly_fields = ("timestamp", "created_by")
autocomplete_fields = ("tech_analyze",) autocomplete_fields = ("tech_analyze",)

View File

@@ -2,17 +2,24 @@
Management command для генерации тестовых отметок сигналов. Management command для генерации тестовых отметок сигналов.
Использование: Использование:
python manage.py generate_test_marks --satellite_id=1 --days=90 --marks_per_day=5 python manage.py generate_test_marks --satellite_id=1 --user_id=1 --date_range=10.10.2025-15.10.2025
Параметры: Параметры:
--satellite_id: ID спутника (обязательный) --satellite_id: ID спутника (обязательный)
--days: Количество дней для генерации (по умолчанию 90) --user_id: ID пользователя CustomUser (обязательный)
--marks_per_day: Количество отметок в день (по умолчанию 3) --date_range: Диапазон дат в формате ДД.ММ.ГГГГ-ДД.ММ.ГГГГ (обязательный)
--clear: Удалить существующие отметки перед генерацией --clear: Удалить существующие отметки перед генерацией
Особенности:
- Генерирует отметки только в будние дни (пн-пт)
- Время отметок: утро с 8:00 до 11:00
- Одна отметка в день для всех сигналов спутника
- Все отметки в один день имеют одинаковый timestamp (пакетное сохранение)
- Все отметки имеют значение True (сигнал присутствует)
""" """
import random import random
from datetime import timedelta from datetime import datetime, timedelta
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand, CommandError
from django.utils import timezone from django.utils import timezone
@@ -31,16 +38,16 @@ class Command(BaseCommand):
help='ID спутника для генерации отметок' help='ID спутника для генерации отметок'
) )
parser.add_argument( parser.add_argument(
'--days', '--user_id',
type=int, type=int,
default=90, required=True,
help='Количество дней для генерации (по умолчанию 90)' help='ID пользователя CustomUser - автор всех отметок'
) )
parser.add_argument( parser.add_argument(
'--marks_per_day', '--date_range',
type=int, type=str,
default=3, required=True,
help='Среднее количество отметок в день на теханализ (по умолчанию 3)' help='Диапазон дат в формате ДД.ММ.ГГГГ-ДД.ММ.ГГГГ (например: 10.10.2025-15.10.2025)'
) )
parser.add_argument( parser.add_argument(
'--clear', '--clear',
@@ -50,10 +57,34 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
satellite_id = options['satellite_id'] satellite_id = options['satellite_id']
days = options['days'] user_id = options['user_id']
marks_per_day = options['marks_per_day'] date_range = options['date_range']
clear = options['clear'] clear = options['clear']
# Проверяем существование пользователя
try:
custom_user = CustomUser.objects.select_related('user').get(id=user_id)
except CustomUser.DoesNotExist:
raise CommandError(f'Пользователь CustomUser с ID {user_id} не найден')
# Парсим диапазон дат
try:
start_str, end_str = date_range.split('-')
start_date = datetime.strptime(start_str.strip(), '%d.%m.%Y')
end_date = datetime.strptime(end_str.strip(), '%d.%m.%Y')
# Делаем timezone-aware
start_date = timezone.make_aware(start_date)
end_date = timezone.make_aware(end_date)
if start_date > end_date:
raise CommandError('Начальная дата должна быть раньше конечной')
except ValueError as e:
raise CommandError(
f'Неверный формат даты. Используйте ДД.ММ.ГГГГ-ДД.ММ.ГГГГ (например: 10.10.2025-15.10.2025). Ошибка: {e}'
)
# Проверяем существование спутника # Проверяем существование спутника
try: try:
satellite = Satellite.objects.get(id=satellite_id) satellite = Satellite.objects.get(id=satellite_id)
@@ -61,16 +92,17 @@ class Command(BaseCommand):
raise CommandError(f'Спутник с ID {satellite_id} не найден') raise CommandError(f'Спутник с ID {satellite_id} не найден')
# Получаем теханализы для спутника # Получаем теханализы для спутника
tech_analyzes = TechAnalyze.objects.filter(satellite=satellite) tech_analyzes = list(TechAnalyze.objects.filter(satellite=satellite))
ta_count = tech_analyzes.count() ta_count = len(tech_analyzes)
if ta_count == 0: if ta_count == 0:
raise CommandError(f'Нет теханализов для спутника "{satellite.name}"') raise CommandError(f'Нет теханализов для спутника "{satellite.name}"')
self.stdout.write(f'Спутник: {satellite.name}') self.stdout.write(f'Спутник: {satellite.name}')
self.stdout.write(f'Теханализов: {ta_count}') self.stdout.write(f'Теханализов: {ta_count}')
self.stdout.write(f'Период: {days} дней') self.stdout.write(f'Пользователь: {custom_user}')
self.stdout.write(f'Отметок в день: ~{marks_per_day}') self.stdout.write(f'Период: {start_str} - {end_str} (только будние дни)')
self.stdout.write(f'Время: 8:00 - 11:00')
# Удаляем существующие отметки если указан флаг # Удаляем существующие отметки если указан флаг
if clear: if clear:
@@ -81,56 +113,46 @@ class Command(BaseCommand):
self.style.WARNING(f'Удалено существующих отметок: {deleted_count}') self.style.WARNING(f'Удалено существующих отметок: {deleted_count}')
) )
# Получаем или создаём тестового пользователя
test_users = self._get_or_create_test_users()
# Генерируем отметки # Генерируем отметки
now = timezone.now()
start_date = now - timedelta(days=days)
total_marks = 0 total_marks = 0
marks_to_create = [] marks_to_create = []
workdays_count = 0
for ta in tech_analyzes: current_date = start_date
# Для каждого теханализа генерируем отметки # Включаем конечную дату в диапазон
current_date = start_date end_date_inclusive = end_date + timedelta(days=1)
# Начальное состояние сигнала (случайное) while current_date < end_date_inclusive:
signal_present = random.choice([True, False]) # Проверяем, что это будний день (0=пн, 4=пт)
if current_date.weekday() < 5:
while current_date < now: workdays_count += 1
# Случайное количество отметок в этот день (от 0 до marks_per_day * 2)
day_marks = random.randint(0, marks_per_day * 2)
for _ in range(day_marks): # Генерируем случайное время в диапазоне 8:00-11:00
# Случайное время в течение дня random_hour = random.randint(8, 10)
random_hours = random.randint(0, 23) random_minute = random.randint(0, 59)
random_minutes = random.randint(0, 59) random_second = random.randint(0, 59)
mark_time = current_date.replace(
hour=random_hours, mark_time = current_date.replace(
minute=random_minutes, hour=random_hour,
second=random.randint(0, 59) minute=random_minute,
) second=random_second,
microsecond=0
# Пропускаем если время в будущем )
if mark_time > now:
continue # Создаём отметки для всех теханализов с одинаковым timestamp
for ta in tech_analyzes:
# С вероятностью 70% сигнал остаётся в том же состоянии
# С вероятностью 30% меняется
if random.random() > 0.7:
signal_present = not signal_present
marks_to_create.append(ObjectMark( marks_to_create.append(ObjectMark(
tech_analyze=ta, tech_analyze=ta,
mark=signal_present, mark=True, # Всегда True
created_by=random.choice(test_users), timestamp=mark_time,
created_by=custom_user,
)) ))
total_marks += 1 total_marks += 1
current_date += timedelta(days=1) current_date += timedelta(days=1)
# Bulk create для производительности # Bulk create для производительности
self.stdout.write(f'Рабочих дней: {workdays_count}')
self.stdout.write(f'Создание {total_marks} отметок...') self.stdout.write(f'Создание {total_marks} отметок...')
# Создаём партиями по 1000 # Создаём партиями по 1000
@@ -140,65 +162,8 @@ class Command(BaseCommand):
ObjectMark.objects.bulk_create(batch) ObjectMark.objects.bulk_create(batch)
self.stdout.write(f' Создано: {min(i + batch_size, len(marks_to_create))}/{total_marks}') self.stdout.write(f' Создано: {min(i + batch_size, len(marks_to_create))}/{total_marks}')
# Обновляем timestamp для созданных отметок (bulk_create не вызывает auto_now_add корректно)
self.stdout.write('Обновление временных меток...')
# Получаем созданные отметки и обновляем их timestamp
created_marks = ObjectMark.objects.filter(
tech_analyze__satellite=satellite
).order_by('id')
# Распределяем временные метки
current_date = start_date
mark_index = 0
for ta in tech_analyzes:
ta_marks = list(created_marks.filter(tech_analyze=ta).order_by('id'))
if not ta_marks:
continue
# Распределяем отметки по времени
time_step = timedelta(days=days) / len(ta_marks) if ta_marks else timedelta(hours=1)
for i, mark in enumerate(ta_marks):
mark_time = start_date + (time_step * i)
# Добавляем случайное смещение
mark_time += timedelta(
hours=random.randint(0, 23),
minutes=random.randint(0, 59)
)
if mark_time > now:
mark_time = now - timedelta(minutes=random.randint(1, 60))
ObjectMark.objects.filter(id=mark.id).update(timestamp=mark_time)
self.stdout.write( self.stdout.write(
self.style.SUCCESS( self.style.SUCCESS(
f'Успешно создано {total_marks} отметок для {ta_count} теханализов' f'Успешно создано {total_marks} отметок для {ta_count} теханализов за {workdays_count} рабочих дней'
) )
) )
def _get_or_create_test_users(self):
"""Получает или создаёт тестовых пользователей для отметок."""
from django.contrib.auth.models import User
test_usernames = ['operator1', 'operator2', 'operator3', 'analyst1', 'analyst2']
custom_users = []
for username in test_usernames:
user, created = User.objects.get_or_create(
username=username,
defaults={
'first_name': username.capitalize(),
'last_name': 'Тестовый',
'is_active': True,
}
)
custom_user, _ = CustomUser.objects.get_or_create(
user=user,
defaults={'role': 'user'}
)
custom_users.append(custom_user)
return custom_users

View File

@@ -0,0 +1,33 @@
# Generated by Django 5.2.7 on 2025-12-12 12:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('mainapp', '0023_add_coords_object_to_sourcerequest'),
]
operations = [
migrations.AlterField(
model_name='objectmark',
name='timestamp',
field=models.DateTimeField(blank=True, db_index=True, help_text='Время фиксации отметки', null=True, verbose_name='Время'),
),
migrations.AlterField(
model_name='sourcerequest',
name='status',
field=models.CharField(choices=[('planned', 'Запланировано'), ('canceled_gso', 'Отменено ГСО'), ('canceled_kub', 'Отменено МКА'), ('conducted', 'Проведён'), ('successful', 'Успешно'), ('no_correlation', 'Нет корреляции'), ('no_signal', 'Нет сигнала в спектре'), ('unsuccessful', 'Неуспешно'), ('downloading', 'Скачивание'), ('processing', 'Обработка'), ('result_received', 'Результат получен')], db_index=True, default='planned', help_text='Текущий статус заявки', max_length=20, verbose_name='Статус'),
),
migrations.AlterField(
model_name='sourcerequeststatushistory',
name='new_status',
field=models.CharField(choices=[('planned', 'Запланировано'), ('canceled_gso', 'Отменено ГСО'), ('canceled_kub', 'Отменено МКА'), ('conducted', 'Проведён'), ('successful', 'Успешно'), ('no_correlation', 'Нет корреляции'), ('no_signal', 'Нет сигнала в спектре'), ('unsuccessful', 'Неуспешно'), ('downloading', 'Скачивание'), ('processing', 'Обработка'), ('result_received', 'Результат получен')], help_text='Статус после изменения', max_length=20, verbose_name='Новый статус'),
),
migrations.AlterField(
model_name='sourcerequeststatushistory',
name='old_status',
field=models.CharField(choices=[('planned', 'Запланировано'), ('canceled_gso', 'Отменено ГСО'), ('canceled_kub', 'Отменено МКА'), ('conducted', 'Проведён'), ('successful', 'Успешно'), ('no_correlation', 'Нет корреляции'), ('no_signal', 'Нет сигнала в спектре'), ('unsuccessful', 'Неуспешно'), ('downloading', 'Скачивание'), ('processing', 'Обработка'), ('result_received', 'Результат получен')], help_text='Статус до изменения', max_length=20, verbose_name='Старый статус'),
),
]

View File

@@ -120,10 +120,11 @@ class ObjectMark(models.Model):
help_text="True - сигнал обнаружен, False - сигнал отсутствует", help_text="True - сигнал обнаружен, False - сигнал отсутствует",
) )
timestamp = models.DateTimeField( timestamp = models.DateTimeField(
auto_now_add=True,
verbose_name="Время", verbose_name="Время",
db_index=True, db_index=True,
help_text="Время фиксации отметки", help_text="Время фиксации отметки",
null=True,
blank=True,
) )
tech_analyze = models.ForeignKey( tech_analyze = models.ForeignKey(
'TechAnalyze', 'TechAnalyze',

View File

@@ -295,7 +295,7 @@ class SignalMarksEntryAPIView(LoginRequiredMixin, View):
# Проверяем, можно ли добавить новую отметку (прошло 5 минут) # Проверяем, можно ли добавить новую отметку (прошло 5 минут)
can_add_mark = True can_add_mark = True
if last_mark: if last_mark and last_mark.timestamp:
time_diff = timezone.now() - last_mark.timestamp time_diff = timezone.now() - last_mark.timestamp
can_add_mark = time_diff >= timedelta(minutes=5) can_add_mark = time_diff >= timedelta(minutes=5)
@@ -364,17 +364,18 @@ class SaveSignalMarksView(LoginRequiredMixin, View):
tech_analyze = TechAnalyze.objects.get(id=tech_analyze_id) tech_analyze = TechAnalyze.objects.get(id=tech_analyze_id)
# Проверяем, можно ли добавить отметку # Проверяем, можно ли добавить отметку
last_mark = tech_analyze.marks.first() last_mark = tech_analyze.marks.order_by('-timestamp').first()
if last_mark: if last_mark and last_mark.timestamp:
time_diff = timezone.now() - last_mark.timestamp time_diff = timezone.now() - last_mark.timestamp
if time_diff < timedelta(minutes=5): if time_diff < timedelta(minutes=5):
skipped_count += 1 skipped_count += 1
continue continue
# Создаём отметку # Создаём отметку с текущим временем
ObjectMark.objects.create( ObjectMark.objects.create(
tech_analyze=tech_analyze, tech_analyze=tech_analyze,
mark=mark_value, mark=mark_value,
timestamp=timezone.now(),
created_by=custom_user, created_by=custom_user,
) )
created_count += 1 created_count += 1