feat(registries): add SME and budget imports and fix source API workflows
All checks were successful
Mostovik Backend CI/CD / Tests and lint (push) Successful in 9m37s
Mostovik Backend CI/CD / Build linux/amd64 release images (push) Successful in 4m18s
Mostovik Backend CI/CD / Deploy and verify internal main (push) Has been skipped
Mostovik Backend CI/CD / Deploy customer main (push) Has been skipped
Mostovik Backend CI/CD / Deploy dev (push) Successful in 1m48s
All checks were successful
Mostovik Backend CI/CD / Tests and lint (push) Successful in 9m37s
Mostovik Backend CI/CD / Build linux/amd64 release images (push) Successful in 4m18s
Mostovik Backend CI/CD / Deploy and verify internal main (push) Has been skipped
Mostovik Backend CI/CD / Deploy customer main (push) Has been skipped
Mostovik Backend CI/CD / Deploy dev (push) Successful in 1m48s
This commit is contained in:
@@ -12,6 +12,7 @@ from typing import Any
|
||||
|
||||
from apps.core.mixins import TimestampMixin
|
||||
from django.db import models
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
@@ -141,81 +142,69 @@ class BackgroundJob(TimestampMixin, models.Model):
|
||||
|
||||
# ==================== Методы обновления статуса ====================
|
||||
|
||||
def _unfinished_queryset(self):
|
||||
"""Условие проверяется в UPDATE, включая события от старого экземпляра."""
|
||||
return type(self).objects.filter(
|
||||
pk=self.pk,
|
||||
status__in=[JobStatus.PENDING, JobStatus.STARTED, JobStatus.RETRY],
|
||||
)
|
||||
|
||||
def _update_unfinished(self, **values: Any) -> None:
|
||||
self._unfinished_queryset().update(updated_at=timezone.now(), **values)
|
||||
self.refresh_from_db()
|
||||
|
||||
def mark_started(self) -> None:
|
||||
"""Отметить задачу как начатую."""
|
||||
self.status = JobStatus.STARTED
|
||||
self.started_at = timezone.now()
|
||||
self.save(update_fields=["status", "started_at", "updated_at"])
|
||||
"""Начать задачу, сохранив время первого запуска после retry."""
|
||||
self._update_unfinished(
|
||||
status=JobStatus.STARTED,
|
||||
started_at=Coalesce("started_at", timezone.now()),
|
||||
)
|
||||
|
||||
def update_progress(self, progress: int, message: str = "") -> None:
|
||||
"""
|
||||
Обновить прогресс выполнения.
|
||||
|
||||
Args:
|
||||
progress: Процент выполнения (0-100)
|
||||
message: Описание текущего этапа
|
||||
"""
|
||||
self.progress = min(max(progress, 0), 100)
|
||||
self.progress_message = message
|
||||
self.save(update_fields=["progress", "progress_message", "updated_at"])
|
||||
def update_progress(
|
||||
self, progress: int, message: str = "", *, meta: dict | None = None
|
||||
) -> None:
|
||||
"""Атомарно сохранить только неубывающий прогресс незавершённой задачи."""
|
||||
progress = min(max(int(progress), 0), 100)
|
||||
values = {
|
||||
"progress": progress,
|
||||
"progress_message": message,
|
||||
"updated_at": timezone.now(),
|
||||
}
|
||||
if meta is not None:
|
||||
values["meta"] = meta
|
||||
self._unfinished_queryset().filter(progress__lte=progress).update(**values)
|
||||
self.refresh_from_db()
|
||||
|
||||
def complete(self, result: Any = None) -> None:
|
||||
"""
|
||||
Отметить задачу как успешно завершённую.
|
||||
|
||||
Args:
|
||||
result: Результат выполнения (сериализуемый в JSON)
|
||||
"""
|
||||
self.status = JobStatus.SUCCESS
|
||||
self.progress = 100
|
||||
self.result = result
|
||||
self.error = ""
|
||||
self.traceback = ""
|
||||
self.completed_at = timezone.now()
|
||||
self.save(
|
||||
update_fields=[
|
||||
"status",
|
||||
"progress",
|
||||
"result",
|
||||
"error",
|
||||
"traceback",
|
||||
"completed_at",
|
||||
"updated_at",
|
||||
]
|
||||
"""Первое финальное событие фиксирует успешный результат и 100%."""
|
||||
self._update_unfinished(
|
||||
status=JobStatus.SUCCESS,
|
||||
progress=100,
|
||||
result=result,
|
||||
error="",
|
||||
traceback="",
|
||||
completed_at=timezone.now(),
|
||||
)
|
||||
|
||||
def fail(self, error: str, traceback_str: str = "") -> None:
|
||||
"""
|
||||
Отметить задачу как завершённую с ошибкой.
|
||||
|
||||
Args:
|
||||
error: Текст ошибки
|
||||
traceback_str: Полный traceback
|
||||
"""
|
||||
self.status = JobStatus.FAILURE
|
||||
self.error = str(error)
|
||||
self.traceback = traceback_str
|
||||
self.completed_at = timezone.now()
|
||||
self.save(
|
||||
update_fields=[
|
||||
"status",
|
||||
"error",
|
||||
"traceback",
|
||||
"completed_at",
|
||||
"updated_at",
|
||||
]
|
||||
"""Зафиксировать ошибку без изменения достигнутого прогресса."""
|
||||
self._update_unfinished(
|
||||
status=JobStatus.FAILURE,
|
||||
error=str(error),
|
||||
traceback=traceback_str,
|
||||
completed_at=timezone.now(),
|
||||
)
|
||||
|
||||
def revoke(self) -> None:
|
||||
"""Отметить задачу как отменённую."""
|
||||
self.status = JobStatus.REVOKED
|
||||
self.completed_at = timezone.now()
|
||||
self.save(update_fields=["status", "completed_at", "updated_at"])
|
||||
def revoke(self, message: str | None = None) -> None:
|
||||
"""Отменить незавершённую задачу, сохранив её прогресс."""
|
||||
values = {"status": JobStatus.REVOKED, "completed_at": timezone.now()}
|
||||
if message is not None:
|
||||
values["progress_message"] = message
|
||||
self._update_unfinished(**values)
|
||||
|
||||
def mark_retry(self) -> None:
|
||||
"""Отметить, что задача будет повторена."""
|
||||
self.status = JobStatus.RETRY
|
||||
self.save(update_fields=["status", "updated_at"])
|
||||
"""Отметить повторную попытку, не открывая завершённую задачу заново."""
|
||||
self._update_unfinished(status=JobStatus.RETRY)
|
||||
|
||||
# ==================== Свойства ====================
|
||||
|
||||
|
||||
@@ -395,7 +395,6 @@ class BackgroundJobControlView(BackgroundJobStatusView):
|
||||
},
|
||||
)
|
||||
def post(self, request: Request, task_id: str) -> Response:
|
||||
from apps.core.models import JobStatus
|
||||
from apps.core.services import BackgroundJobService
|
||||
from celery import current_app
|
||||
|
||||
@@ -415,10 +414,7 @@ class BackgroundJobControlView(BackgroundJobStatusView):
|
||||
task_id,
|
||||
terminate=bool(request.data.get("terminate", False)),
|
||||
)
|
||||
if not job.is_finished:
|
||||
job.status = JobStatus.REVOKED
|
||||
job.progress_message = "Задача отозвана пользователем"
|
||||
job.save(update_fields=["status", "progress_message", "updated_at"])
|
||||
job.revoke(message="Задача отозвана пользователем")
|
||||
return Response(BackgroundJobSerializer(job).data)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user