Add initial implementations for forms and organization apps with serializers, factories, and admin configurations
Some checks failed
CI/CD Pipeline / Run Tests (push) Failing after 45s
CI/CD Pipeline / Code Quality Checks (push) Failing after 48s
CI/CD Pipeline / Build Docker Images (push) Has been skipped
CI/CD Pipeline / Push to Gitea Registry (push) Has been skipped
CI/CD Pipeline / Deploy to Server (push) Has been skipped
Some checks failed
CI/CD Pipeline / Run Tests (push) Failing after 45s
CI/CD Pipeline / Code Quality Checks (push) Failing after 48s
CI/CD Pipeline / Build Docker Images (push) Has been skipped
CI/CD Pipeline / Push to Gitea Registry (push) Has been skipped
CI/CD Pipeline / Deploy to Server (push) Has been skipped
This commit is contained in:
@@ -5,11 +5,197 @@
|
||||
автоматически генерируемой документации.
|
||||
"""
|
||||
|
||||
from collections import OrderedDict
|
||||
from typing import Any
|
||||
|
||||
from django.conf import settings
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.generators import OpenAPISchemaGenerator
|
||||
from drf_yasg.utils import swagger_auto_schema
|
||||
|
||||
OPENAPI_PROJECT_TITLE = (
|
||||
"СПО «Программный сервис, обеспечивающий обработку информации в защищенной "
|
||||
"части ГИСП на основе данных организаций ОПК, входящих в состав "
|
||||
"Госкорпорации «Росатом» и Госкорпорации «Роскосмос»"
|
||||
)
|
||||
|
||||
OPENAPI_PROJECT_DESCRIPTION = """
|
||||
## Документация API сервиса
|
||||
|
||||
Сервис предоставляет доступ к данным организаций ОПК, отчетным формам, реестрам,
|
||||
статусам фоновых задач и служебным маршрутам мониторинга.
|
||||
|
||||
### Авторизация
|
||||
Для доступа к защищённым маршрутам используйте JWT-токен:
|
||||
1. Получите токен через `POST /api/v1/users/login/`
|
||||
2. Передавайте заголовок `Authorization: Bearer <access_token>`
|
||||
|
||||
### Обновление токена
|
||||
Для выпуска нового access-токена используйте `POST /api/v1/users/token/refresh/`.
|
||||
|
||||
### Общий принцип работы
|
||||
API предназначен в основном для чтения и мониторинга данных. Загрузка отчетных
|
||||
форм и импорт реестров выполняются через специализированные маршруты и фоновые задачи.
|
||||
""".strip()
|
||||
|
||||
OPENAPI_TAG_DESCRIPTIONS = OrderedDict(
|
||||
[
|
||||
(
|
||||
"Аутентификация",
|
||||
"Регистрация, вход, выход, обновление и проверка JWT-токенов.",
|
||||
),
|
||||
(
|
||||
"Пользователь",
|
||||
"Текущий пользователь, профиль, смена пароля и операции личного кабинета.",
|
||||
),
|
||||
(
|
||||
"Организации",
|
||||
"Справочник организаций ОПК с актуальными данными и связями по реестрам.",
|
||||
),
|
||||
(
|
||||
"Реестры",
|
||||
"Просмотр реестров, составов организаций и импорт резервных копий реестров.",
|
||||
),
|
||||
(
|
||||
"Форма Ф-1",
|
||||
"Загрузка и просмотр формы Ф-1 по выпуску продукции, НИОКР и кадровым показателям.",
|
||||
),
|
||||
(
|
||||
"Форма Ф-2",
|
||||
"Загрузка и просмотр формы Ф-2 с бухгалтерским балансом и финансовыми результатами.",
|
||||
),
|
||||
(
|
||||
"Форма Ф-3",
|
||||
"Загрузка и просмотр формы Ф-3 по кадрам, оборудованию и износу.",
|
||||
),
|
||||
(
|
||||
"Форма Ф-4",
|
||||
"Загрузка и просмотр формы Ф-4 со сводными финансовыми показателями.",
|
||||
),
|
||||
(
|
||||
"Форма Ф-5",
|
||||
"Загрузка и просмотр формы Ф-5 по единицам оборудования и их состоянию.",
|
||||
),
|
||||
(
|
||||
"Форма Ф-6",
|
||||
"Загрузка и просмотр формы Ф-6 по возрастной структуре оборудования и загрузке.",
|
||||
),
|
||||
(
|
||||
"Фоновые задачи",
|
||||
"Контроль статусов, прогресса и результатов длительных операций импорта и обработки.",
|
||||
),
|
||||
(
|
||||
"Мониторинг",
|
||||
"Служебные маршруты для проверки доступности, готовности и общего состояния системы.",
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
OPENAPI_AUTH_PATH_PREFIXES = (
|
||||
"/api/v1/users/register/",
|
||||
"/api/v1/users/login/",
|
||||
"/api/v1/users/logout/",
|
||||
"/api/v1/users/token/refresh/",
|
||||
"/api/v1/users/token/verify/",
|
||||
)
|
||||
|
||||
OPENAPI_PUBLIC_PATH_PREFIXES = (
|
||||
"/health/",
|
||||
"/api/v1/users/register/",
|
||||
"/api/v1/users/login/",
|
||||
"/api/v1/users/token/refresh/",
|
||||
"/api/v1/users/token/verify/",
|
||||
"/auth/login/",
|
||||
)
|
||||
|
||||
OPENAPI_TAG_BY_PATH_PREFIX = OrderedDict(
|
||||
[
|
||||
("/health/", "Мониторинг"),
|
||||
("/api/v1/jobs/", "Фоновые задачи"),
|
||||
("/api/v1/organizations/", "Организации"),
|
||||
("/api/v1/registers/", "Реестры"),
|
||||
("/api/v1/forms/f1/", "Форма Ф-1"),
|
||||
("/api/v1/forms/f2/", "Форма Ф-2"),
|
||||
("/api/v1/forms/f3/", "Форма Ф-3"),
|
||||
("/api/v1/forms/f4/", "Форма Ф-4"),
|
||||
("/api/v1/forms/f5/", "Форма Ф-5"),
|
||||
("/api/v1/forms/f6/", "Форма Ф-6"),
|
||||
]
|
||||
)
|
||||
|
||||
OPENAPI_TAG_ALIASES = {
|
||||
"monitoring": "Мониторинг",
|
||||
"background_jobs": "Фоновые задачи",
|
||||
"authentication": "Аутентификация",
|
||||
"auth": "Аутентификация",
|
||||
"user": "Пользователь",
|
||||
"users": "Пользователь",
|
||||
"organizations": "Организации",
|
||||
"registers": "Реестры",
|
||||
"api": None,
|
||||
}
|
||||
|
||||
|
||||
class RussianTagSchemaGenerator(OpenAPISchemaGenerator):
|
||||
"""OpenAPI generator with Russian tags and tag descriptions."""
|
||||
|
||||
def get_schema(self, request=None, public=False):
|
||||
schema = super().get_schema(request=request, public=public)
|
||||
self._localize_tags(schema)
|
||||
schema.tags = [
|
||||
{"name": name, "description": description}
|
||||
for name, description in OPENAPI_TAG_DESCRIPTIONS.items()
|
||||
]
|
||||
return schema
|
||||
|
||||
def _localize_tags(self, schema) -> None:
|
||||
for path, path_item in schema.paths.items():
|
||||
resolved_tag = self._resolve_tag_for_path(path)
|
||||
for method in ("get", "post", "put", "patch", "delete", "head", "options"):
|
||||
operation = path_item.get(method)
|
||||
if operation is None:
|
||||
continue
|
||||
|
||||
tag = resolved_tag or self._localize_existing_tag(
|
||||
getattr(operation, "tags", None)
|
||||
)
|
||||
if tag is not None:
|
||||
operation.tags = [tag]
|
||||
|
||||
if self._is_public_path(path):
|
||||
operation.security = []
|
||||
|
||||
def _resolve_tag_for_path(self, path: str) -> str | None:
|
||||
for prefix in OPENAPI_AUTH_PATH_PREFIXES:
|
||||
if path.startswith(prefix):
|
||||
return "Аутентификация"
|
||||
|
||||
if path.startswith("/api/v1/users/"):
|
||||
return "Пользователь"
|
||||
|
||||
for prefix, tag in OPENAPI_TAG_BY_PATH_PREFIX.items():
|
||||
if path.startswith(prefix):
|
||||
return tag
|
||||
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _is_public_path(path: str) -> bool:
|
||||
return any(path.startswith(prefix) for prefix in OPENAPI_PUBLIC_PATH_PREFIXES)
|
||||
|
||||
@staticmethod
|
||||
def _localize_existing_tag(tags: list[str] | None) -> str | None:
|
||||
if not tags:
|
||||
return None
|
||||
|
||||
for tag in tags:
|
||||
if tag in OPENAPI_TAG_DESCRIPTIONS:
|
||||
return tag
|
||||
if tag in OPENAPI_TAG_ALIASES:
|
||||
return OPENAPI_TAG_ALIASES[tag]
|
||||
|
||||
return tags[0]
|
||||
|
||||
|
||||
def api_docs(
|
||||
*,
|
||||
@@ -85,11 +271,20 @@ def api_docs(
|
||||
)
|
||||
|
||||
|
||||
def swagger_tag(ru: str, en: str | None = None) -> str:
|
||||
"""Возвращает тег для Swagger в зависимости от текущих настроек."""
|
||||
use_english = getattr(settings, "OPENAPI_USE_ENGLISH_TAGS", False)
|
||||
if use_english and en:
|
||||
return en
|
||||
return ru
|
||||
|
||||
|
||||
def _get_status_description(status_code: int) -> str:
|
||||
"""Возвращает описание HTTP статуса на русском."""
|
||||
descriptions = {
|
||||
200: "Успешный запрос",
|
||||
201: "Ресурс создан",
|
||||
202: "Запрос принят в обработку",
|
||||
204: "Успешно, без содержимого",
|
||||
400: "Некорректный запрос",
|
||||
401: "Не авторизован",
|
||||
@@ -99,6 +294,7 @@ def _get_status_description(status_code: int) -> str:
|
||||
422: "Ошибка валидации",
|
||||
429: "Слишком много запросов",
|
||||
500: "Внутренняя ошибка сервера",
|
||||
503: "Сервис временно недоступен",
|
||||
}
|
||||
return descriptions.get(status_code, f"HTTP {status_code}")
|
||||
|
||||
@@ -273,6 +469,72 @@ class CommonResponses:
|
||||
),
|
||||
)
|
||||
|
||||
SERVICE_UNAVAILABLE = openapi.Response(
|
||||
description="Сервис временно недоступен",
|
||||
schema=openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
properties={
|
||||
"success": openapi.Schema(type=openapi.TYPE_BOOLEAN, default=False),
|
||||
"errors": openapi.Schema(
|
||||
type=openapi.TYPE_ARRAY,
|
||||
items=openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
properties={
|
||||
"code": openapi.Schema(
|
||||
type=openapi.TYPE_STRING,
|
||||
default="service_unavailable",
|
||||
),
|
||||
"message": openapi.Schema(
|
||||
type=openapi.TYPE_STRING,
|
||||
default="Сервис временно недоступен",
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class ErrorResponses:
|
||||
"""Переиспользуемые наборы ошибок для OpenAPI responses."""
|
||||
|
||||
PUBLIC = {
|
||||
429: CommonResponses.RATE_LIMITED,
|
||||
500: CommonResponses.SERVER_ERROR,
|
||||
}
|
||||
|
||||
AUTHENTICATED = {
|
||||
401: CommonResponses.UNAUTHORIZED,
|
||||
**PUBLIC,
|
||||
}
|
||||
|
||||
AUTHENTICATED_VALIDATION = {
|
||||
400: CommonResponses.BAD_REQUEST,
|
||||
**AUTHENTICATED,
|
||||
}
|
||||
|
||||
AUTHENTICATED_NOT_FOUND = {
|
||||
404: CommonResponses.NOT_FOUND,
|
||||
**AUTHENTICATED,
|
||||
}
|
||||
|
||||
AUTHENTICATED_VALIDATION_NOT_FOUND = {
|
||||
400: CommonResponses.BAD_REQUEST,
|
||||
**AUTHENTICATED_NOT_FOUND,
|
||||
}
|
||||
|
||||
ADMIN = {
|
||||
401: CommonResponses.UNAUTHORIZED,
|
||||
403: CommonResponses.FORBIDDEN,
|
||||
**PUBLIC,
|
||||
}
|
||||
|
||||
ADMIN_NOT_FOUND = {
|
||||
404: CommonResponses.NOT_FOUND,
|
||||
**ADMIN,
|
||||
}
|
||||
|
||||
|
||||
# Параметры запроса
|
||||
class CommonParameters:
|
||||
|
||||
Reference in New Issue
Block a user