feature/rbac permisos y roles implementados
This commit is contained in:
@@ -1,53 +1,54 @@
|
||||
from django.shortcuts import render
|
||||
from rest_framework import viewsets, filters
|
||||
from rest_framework.authentication import TokenAuthentication
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework.pagination import PageNumberPagination
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
from api.logger.mixins import LoggingMixin
|
||||
from mixins.filtrado_organizacion import OrganizacionFiltradaMixin, ProcesosPorOrganizacionMixin
|
||||
from core.permissions import require_permission, user_has_permission, IsInternalService
|
||||
from mixins.filtrado_organizacion import OrganizacionFiltradaMixin
|
||||
from .models import Task
|
||||
from .serializers import TaskSerializer
|
||||
from .filters import TaskFilter
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
|
||||
# Create your views here.
|
||||
from core.permissions import (
|
||||
IsSameOrganization,
|
||||
IsSameOrganizationDeveloper,
|
||||
IsSameOrganizationAndAdmin,
|
||||
IsSuperUser
|
||||
)
|
||||
|
||||
class TaskPagination(PageNumberPagination):
|
||||
page_size = 10
|
||||
page_size_query_param = 'page_size'
|
||||
max_page_size = 100
|
||||
|
||||
class TaskViewSet(LoggingMixin,viewsets.ModelViewSet,OrganizacionFiltradaMixin):
|
||||
permission_classes = [IsAuthenticated & (IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)]
|
||||
|
||||
class TaskViewSet(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltradaMixin):
|
||||
# Task se relaciona con pedimento, que tiene contribuyente
|
||||
campo_contribuyente = 'pedimento__contribuyente'
|
||||
|
||||
queryset = Task.objects.select_related('pedimento', 'servicio').all()
|
||||
serializer_class = TaskSerializer
|
||||
filter_backends = [DjangoFilterBackend, filters.OrderingFilter]
|
||||
filterset_class = TaskFilter
|
||||
pagination_class = TaskPagination
|
||||
ordering_fields = ['timestamp']
|
||||
ordering = ['-timestamp'] # ordenamiento por defecto, más reciente primero
|
||||
|
||||
ordering = ['-timestamp']
|
||||
|
||||
my_tags = ['tasks']
|
||||
|
||||
def get_queryset(self):
|
||||
def get_permissions(self):
|
||||
# Escritura: exclusivo para microservicio interno (Token + superuser)
|
||||
# Lectura: usuarios con pedimentos.view via JWT
|
||||
if self.action in ('create', 'update', 'partial_update', 'destroy'):
|
||||
return [IsAuthenticated(), IsInternalService()]
|
||||
return [IsAuthenticated(), require_permission('pedimentos.view')()]
|
||||
|
||||
"""
|
||||
Filtra las tareas según la organización del usuario.
|
||||
Superusuarios pueden ver todas las tareas.
|
||||
"""
|
||||
queryset = self.get_queryset_filtrado_por_organizacion() # Tambien filtra por importador
|
||||
# if user.is_superuser:
|
||||
# return self.queryset
|
||||
# # return self.queryset.filter(organizacion_id=user.organizacion.id)
|
||||
# else:
|
||||
# return self.queryset.filter(organizacion_id=user.organizacion.id)
|
||||
return queryset
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
# Service account (Token + superuser): sin filtro de org, accede a todas las tasks
|
||||
if user.is_superuser and isinstance(
|
||||
getattr(self.request, 'successful_authenticator', None), TokenAuthentication
|
||||
):
|
||||
return Task.objects.select_related('pedimento', 'servicio').all()
|
||||
if not user_has_permission(user, 'pedimentos.view'):
|
||||
return Task.objects.none()
|
||||
return self.get_queryset_filtrado_por_organizacion()
|
||||
|
||||
|
||||
from rest_framework.views import APIView
|
||||
@@ -57,20 +58,82 @@ from celery.result import AsyncResult
|
||||
|
||||
|
||||
class TaskStatusView(APIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
permission_classes = [IsAuthenticated, require_permission('pedimentos.view')]
|
||||
|
||||
# Mapeo de status del microservicio → estados estándar
|
||||
_STATUS_MAP = {
|
||||
'failed': 'FAILURE',
|
||||
'completed': 'SUCCESS',
|
||||
'processing': 'STARTED',
|
||||
'submitted': 'PENDING',
|
||||
'pending': 'PENDING',
|
||||
}
|
||||
|
||||
def get(self, request, task_id):
|
||||
"""
|
||||
Consulta el estado de una tarea Celery.
|
||||
Consulta el estado de una tarea.
|
||||
|
||||
Fuente de verdad: registro Django Task (actualizado por el microservicio vía PUT).
|
||||
Celery AsyncResult se usa como complemento para tareas de auditoría masiva (SUCCESS)
|
||||
y como fallback cuando la tarea no está en la BD todavía.
|
||||
|
||||
Estados posibles:
|
||||
PENDING — en cola, aún no inició
|
||||
STARTED — worker la tomó y está ejecutando
|
||||
SUCCESS — terminó correctamente, `result` contiene el resumen
|
||||
FAILURE — lanzó una excepción no capturada, `error` describe el problema
|
||||
PENDING — en cola o aún no registrada
|
||||
STARTED — worker ejecutando
|
||||
SUCCESS — completada sin errores
|
||||
FAILURE — terminó con error
|
||||
RETRY — el worker la está reintentando
|
||||
"""
|
||||
try:
|
||||
# Prioridad 1: Django Task record (fuente de verdad del microservicio)
|
||||
try:
|
||||
django_task = Task.objects.get(task_id=task_id)
|
||||
effective_state = self._STATUS_MAP.get(
|
||||
django_task.status.lower(), django_task.status.upper()
|
||||
)
|
||||
is_terminal = effective_state in ('SUCCESS', 'FAILURE')
|
||||
|
||||
response_data = {
|
||||
'task_id': task_id,
|
||||
'status': effective_state,
|
||||
'ready': is_terminal,
|
||||
'successful': (effective_state == 'SUCCESS') if is_terminal else None,
|
||||
'message': django_task.message,
|
||||
}
|
||||
|
||||
if effective_state == 'FAILURE':
|
||||
response_data['error'] = django_task.message
|
||||
|
||||
elif effective_state == 'SUCCESS':
|
||||
# Para auditoría masiva, intentar enriquecer con resultado de Celery
|
||||
try:
|
||||
celery_result = AsyncResult(task_id)
|
||||
if celery_result.ready() and celery_result.successful():
|
||||
result = celery_result.result
|
||||
response_data['result'] = result
|
||||
if isinstance(result, dict) and 'total_pedimentos' in result:
|
||||
total = result.get('total_pedimentos', 0)
|
||||
completados = result.get('completados', 0)
|
||||
con_pendientes = result.get('con_pendientes', 0)
|
||||
con_errores = result.get('con_errores', 0)
|
||||
if con_pendientes == 0 and con_errores == 0:
|
||||
response_data['mensaje'] = f'Auditoría completa — {completados}/{total} pedimentos sin pendientes'
|
||||
else:
|
||||
partes = []
|
||||
if con_pendientes:
|
||||
partes.append(f'{con_pendientes} con documentos pendientes')
|
||||
if con_errores:
|
||||
partes.append(f'{con_errores} con error')
|
||||
response_data['mensaje'] = f'{completados}/{total} pedimentos completos — {", ".join(partes)}'
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return Response(response_data, status=status.HTTP_200_OK)
|
||||
|
||||
except Task.DoesNotExist:
|
||||
pass
|
||||
|
||||
# Prioridad 2: Celery AsyncResult (tarea aún no registrada en BD)
|
||||
task_result = AsyncResult(task_id)
|
||||
state = task_result.state
|
||||
|
||||
@@ -84,25 +147,20 @@ class TaskStatusView(APIView):
|
||||
if state == 'SUCCESS':
|
||||
result = task_result.result
|
||||
response_data['result'] = result
|
||||
|
||||
# Resumen legible cuando es auditoría masiva de organización
|
||||
if isinstance(result, dict) and 'total_pedimentos' in result:
|
||||
total = result.get('total_pedimentos', 0)
|
||||
completados = result.get('completados', 0)
|
||||
con_pendientes = result.get('con_pendientes', 0)
|
||||
con_errores = result.get('con_errores', 0)
|
||||
|
||||
if con_pendientes == 0 and con_errores == 0:
|
||||
mensaje = f'Auditoría completa — {completados}/{total} pedimentos sin pendientes'
|
||||
response_data['mensaje'] = f'Auditoría completa — {completados}/{total} pedimentos sin pendientes'
|
||||
else:
|
||||
partes = []
|
||||
if con_pendientes:
|
||||
partes.append(f'{con_pendientes} con documentos pendientes')
|
||||
if con_errores:
|
||||
partes.append(f'{con_errores} con error')
|
||||
mensaje = f'{completados}/{total} pedimentos completos — {", ".join(partes)}'
|
||||
|
||||
response_data['mensaje'] = mensaje
|
||||
response_data['mensaje'] = f'{completados}/{total} pedimentos completos — {", ".join(partes)}'
|
||||
|
||||
elif state == 'FAILURE':
|
||||
response_data['error'] = str(task_result.info)
|
||||
|
||||
Reference in New Issue
Block a user