feat: agregar endpoints de eliminación masiva
- Agregar endpoint bulk-delete para pedimentos en customs/views.py - Agregar endpoint bulk-delete para documentos en record/views.py - Incluir validaciones de seguridad y filtros por organización - Manejar gestión automática de almacenamiento en documentos - Agregar respuestas detalladas con conteo y errores
This commit is contained in:
@@ -7,6 +7,7 @@ from rest_framework.decorators import action
|
|||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.exceptions import PermissionDenied
|
from rest_framework.exceptions import PermissionDenied
|
||||||
|
from rest_framework import status
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
from rest_framework.filters import SearchFilter, OrderingFilter
|
from rest_framework.filters import SearchFilter, OrderingFilter
|
||||||
from core.permissions import (
|
from core.permissions import (
|
||||||
@@ -231,6 +232,98 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action(detail=False, methods=['post'], url_path='bulk-delete')
|
||||||
|
def bulk_delete(self, request):
|
||||||
|
"""
|
||||||
|
Endpoint para eliminar múltiples pedimentos de manera masiva.
|
||||||
|
|
||||||
|
Payload esperado:
|
||||||
|
{
|
||||||
|
"ids": ["uuid1", "uuid2", "uuid3", ...]
|
||||||
|
}
|
||||||
|
|
||||||
|
Respuesta exitosa:
|
||||||
|
{
|
||||||
|
"message": "Pedimentos eliminados exitosamente",
|
||||||
|
"deleted_count": 3,
|
||||||
|
"deleted_ids": ["uuid1", "uuid2", "uuid3"]
|
||||||
|
}
|
||||||
|
|
||||||
|
Respuesta con errores:
|
||||||
|
{
|
||||||
|
"message": "Algunos pedimentos no pudieron ser eliminados",
|
||||||
|
"deleted_count": 2,
|
||||||
|
"deleted_ids": ["uuid1", "uuid2"],
|
||||||
|
"failed_ids": ["uuid3"],
|
||||||
|
"errors": ["No se encontró el pedimento con ID uuid3"]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
# Obtener los IDs del payload
|
||||||
|
ids = request.data.get('ids', [])
|
||||||
|
|
||||||
|
if not ids:
|
||||||
|
return Response(
|
||||||
|
{"error": "Se requiere una lista de IDs para eliminar"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
|
if not isinstance(ids, list):
|
||||||
|
return Response(
|
||||||
|
{"error": "El campo 'ids' debe ser una lista"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
|
# Obtener el queryset filtrado por organización
|
||||||
|
queryset = self.get_queryset()
|
||||||
|
|
||||||
|
# Filtrar solo los pedimentos que existen y pertenecen a la organización del usuario
|
||||||
|
existing_pedimentos = queryset.filter(id__in=ids)
|
||||||
|
existing_ids = list(existing_pedimentos.values_list('id', flat=True))
|
||||||
|
|
||||||
|
# Convertir UUIDs a strings para comparación
|
||||||
|
existing_ids_str = [str(id) for id in existing_ids]
|
||||||
|
requested_ids_str = [str(id) for id in ids]
|
||||||
|
|
||||||
|
# Identificar IDs que no existen o no pertenecen a la organización
|
||||||
|
failed_ids = [id for id in requested_ids_str if id not in existing_ids_str]
|
||||||
|
|
||||||
|
deleted_count = 0
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
if existing_pedimentos.exists():
|
||||||
|
try:
|
||||||
|
# Eliminar los pedimentos encontrados
|
||||||
|
deleted_count = existing_pedimentos.count()
|
||||||
|
existing_pedimentos.delete()
|
||||||
|
except Exception as e:
|
||||||
|
return Response(
|
||||||
|
{"error": f"Error al eliminar pedimentos: {str(e)}"},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
# Agregar errores para IDs no encontrados
|
||||||
|
if failed_ids:
|
||||||
|
errors = [f"No se encontró el pedimento con ID {id} o no pertenece a su organización" for id in failed_ids]
|
||||||
|
|
||||||
|
# Preparar respuesta
|
||||||
|
response_data = {
|
||||||
|
"deleted_count": deleted_count,
|
||||||
|
"deleted_ids": existing_ids_str
|
||||||
|
}
|
||||||
|
|
||||||
|
if failed_ids:
|
||||||
|
response_data.update({
|
||||||
|
"message": "Algunos pedimentos no pudieron ser eliminados",
|
||||||
|
"failed_ids": failed_ids,
|
||||||
|
"errors": errors
|
||||||
|
})
|
||||||
|
response_status = status.HTTP_207_MULTI_STATUS
|
||||||
|
else:
|
||||||
|
response_data["message"] = "Pedimentos eliminados exitosamente"
|
||||||
|
response_status = status.HTTP_200_OK
|
||||||
|
|
||||||
|
return Response(response_data, status=response_status)
|
||||||
|
|
||||||
my_tags = ['Pedimentos']
|
my_tags = ['Pedimentos']
|
||||||
|
|
||||||
class PartidaViewSet(viewsets.ModelViewSet):
|
class PartidaViewSet(viewsets.ModelViewSet):
|
||||||
|
|||||||
@@ -164,6 +164,155 @@ class DocumentViewSet(viewsets.ModelViewSet, DocumentosFiltradosMixin):
|
|||||||
uso.save()
|
uso.save()
|
||||||
instance.delete()
|
instance.delete()
|
||||||
|
|
||||||
|
@action(detail=False, methods=['post'], url_path='bulk-delete')
|
||||||
|
def bulk_delete(self, request):
|
||||||
|
"""
|
||||||
|
Endpoint para eliminar múltiples documentos de manera masiva.
|
||||||
|
|
||||||
|
Payload esperado:
|
||||||
|
{
|
||||||
|
"ids": ["uuid1", "uuid2", "uuid3", ...]
|
||||||
|
}
|
||||||
|
|
||||||
|
Respuesta exitosa:
|
||||||
|
{
|
||||||
|
"message": "Documentos eliminados exitosamente",
|
||||||
|
"deleted_count": 3,
|
||||||
|
"deleted_ids": ["uuid1", "uuid2", "uuid3"],
|
||||||
|
"space_freed_mb": 25.6
|
||||||
|
}
|
||||||
|
|
||||||
|
Respuesta con errores:
|
||||||
|
{
|
||||||
|
"message": "Algunos documentos no pudieron ser eliminados",
|
||||||
|
"deleted_count": 2,
|
||||||
|
"deleted_ids": ["uuid1", "uuid2"],
|
||||||
|
"failed_ids": ["uuid3"],
|
||||||
|
"errors": ["No se encontró el documento con ID uuid3"],
|
||||||
|
"space_freed_mb": 15.2
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
# Obtener los IDs del payload
|
||||||
|
ids = request.data.get('ids', [])
|
||||||
|
|
||||||
|
if not ids:
|
||||||
|
return Response(
|
||||||
|
{"error": "Se requiere una lista de IDs para eliminar"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
|
if not isinstance(ids, list):
|
||||||
|
return Response(
|
||||||
|
{"error": "El campo 'ids' debe ser una lista"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
|
# Obtener el queryset filtrado por organización
|
||||||
|
queryset = self.get_queryset()
|
||||||
|
|
||||||
|
# Filtrar solo los documentos que existen y pertenecen a la organización del usuario
|
||||||
|
existing_documents = queryset.filter(id__in=ids)
|
||||||
|
existing_ids = list(existing_documents.values_list('id', flat=True))
|
||||||
|
|
||||||
|
# Convertir UUIDs a strings para comparación
|
||||||
|
existing_ids_str = [str(id) for id in existing_ids]
|
||||||
|
requested_ids_str = [str(id) for id in ids]
|
||||||
|
|
||||||
|
# Identificar IDs que no existen o no pertenecen a la organización
|
||||||
|
failed_ids = [id for id in requested_ids_str if id not in existing_ids_str]
|
||||||
|
|
||||||
|
deleted_count = 0
|
||||||
|
total_space_freed = 0
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
if existing_documents.exists():
|
||||||
|
try:
|
||||||
|
# Usar transacción atómica para consistencia
|
||||||
|
with transaction.atomic():
|
||||||
|
# Calcular el espacio total a liberar
|
||||||
|
total_space_freed = sum(doc.size for doc in existing_documents)
|
||||||
|
|
||||||
|
# Obtener la organización del usuario para actualizar el uso de almacenamiento
|
||||||
|
if not request.user.is_authenticated or not hasattr(request.user, 'organizacion'):
|
||||||
|
return Response(
|
||||||
|
{"error": "Usuario no autenticado o sin organización"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
|
)
|
||||||
|
|
||||||
|
organizacion = request.user.organizacion
|
||||||
|
|
||||||
|
# Si es superusuario, puede eliminar documentos de cualquier organización
|
||||||
|
if request.user.is_superuser:
|
||||||
|
# Para superusuario, actualizar el uso de cada organización afectada
|
||||||
|
organizaciones_afectadas = {}
|
||||||
|
for doc in existing_documents:
|
||||||
|
if doc.organizacion.id not in organizaciones_afectadas:
|
||||||
|
organizaciones_afectadas[doc.organizacion.id] = {
|
||||||
|
'organizacion': doc.organizacion,
|
||||||
|
'espacio_liberado': 0
|
||||||
|
}
|
||||||
|
organizaciones_afectadas[doc.organizacion.id]['espacio_liberado'] += doc.size
|
||||||
|
|
||||||
|
# Actualizar uso de almacenamiento para cada organización
|
||||||
|
for org_data in organizaciones_afectadas.values():
|
||||||
|
try:
|
||||||
|
uso = UsoAlmacenamiento.objects.select_for_update().get(
|
||||||
|
organizacion=org_data['organizacion']
|
||||||
|
)
|
||||||
|
uso.espacio_utilizado -= org_data['espacio_liberado']
|
||||||
|
uso.save()
|
||||||
|
except UsoAlmacenamiento.DoesNotExist:
|
||||||
|
# Si no existe el registro, no hay nada que actualizar
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# Para usuarios normales, solo documentos de su organización
|
||||||
|
try:
|
||||||
|
uso = UsoAlmacenamiento.objects.select_for_update().get(
|
||||||
|
organizacion=organizacion
|
||||||
|
)
|
||||||
|
uso.espacio_utilizado -= total_space_freed
|
||||||
|
uso.save()
|
||||||
|
except UsoAlmacenamiento.DoesNotExist:
|
||||||
|
# Si no existe el registro, no hay nada que actualizar
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Eliminar los documentos
|
||||||
|
deleted_count = existing_documents.count()
|
||||||
|
existing_documents.delete()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return Response(
|
||||||
|
{"error": f"Error al eliminar documentos: {str(e)}"},
|
||||||
|
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
# Agregar errores para IDs no encontrados
|
||||||
|
if failed_ids:
|
||||||
|
errors = [f"No se encontró el documento con ID {id} o no pertenece a su organización" for id in failed_ids]
|
||||||
|
|
||||||
|
# Convertir bytes a MB para la respuesta
|
||||||
|
space_freed_mb = round(total_space_freed / (1024 * 1024), 2)
|
||||||
|
|
||||||
|
# Preparar respuesta
|
||||||
|
response_data = {
|
||||||
|
"deleted_count": deleted_count,
|
||||||
|
"deleted_ids": existing_ids_str,
|
||||||
|
"space_freed_mb": space_freed_mb
|
||||||
|
}
|
||||||
|
|
||||||
|
if failed_ids:
|
||||||
|
response_data.update({
|
||||||
|
"message": "Algunos documentos no pudieron ser eliminados",
|
||||||
|
"failed_ids": failed_ids,
|
||||||
|
"errors": errors
|
||||||
|
})
|
||||||
|
response_status = status.HTTP_207_MULTI_STATUS
|
||||||
|
else:
|
||||||
|
response_data["message"] = "Documentos eliminados exitosamente"
|
||||||
|
response_status = status.HTTP_200_OK
|
||||||
|
|
||||||
|
return Response(response_data, status=response_status)
|
||||||
|
|
||||||
class ProtectedDocumentDownloadView(APIView, DocumentosFiltradosMixin):
|
class ProtectedDocumentDownloadView(APIView, DocumentosFiltradosMixin):
|
||||||
permission_classes = [IsAuthenticated & (IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)]
|
permission_classes = [IsAuthenticated & (IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)]
|
||||||
serializer_class = DocumentSerializer
|
serializer_class = DocumentSerializer
|
||||||
|
|||||||
Reference in New Issue
Block a user