Merge pull request 'feat: agregar endpoints de eliminación masiva' (#1) from feature/bulk-delete-endpoints into main
Reviewed-on: #1
This commit is contained in:
@@ -7,6 +7,7 @@ from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
from rest_framework import status
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework.filters import SearchFilter, OrderingFilter
|
||||
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']
|
||||
|
||||
class PartidaViewSet(viewsets.ModelViewSet):
|
||||
|
||||
@@ -163,6 +163,155 @@ class DocumentViewSet(viewsets.ModelViewSet, DocumentosFiltradosMixin):
|
||||
uso.espacio_utilizado -= instance.size
|
||||
uso.save()
|
||||
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):
|
||||
permission_classes = [IsAuthenticated & (IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)]
|
||||
|
||||
Reference in New Issue
Block a user