Se habilita opcion de descarga de pedimentos individuales, masivos, por filtro.
This commit is contained in:
@@ -4,12 +4,12 @@ from rest_framework.routers import DefaultRouter
|
||||
|
||||
# import necessary viewsets
|
||||
# from .views import YourViewSet # Import your viewsets here
|
||||
from .views import DocumentViewSet, ProtectedDocumentDownloadView, BulkDownloadZipView, GetFuenteView, DocumentTypeView
|
||||
from .views import DocumentViewSet, ProtectedDocumentDownloadView, BulkDownloadZipView, GetFuenteView, DocumentTypeView, ExpedienteZipDownloadView, MultiPedimentoZipDownloadView
|
||||
# Create a router and register your viewsets with it
|
||||
|
||||
router = DefaultRouter()
|
||||
|
||||
# Register your viewsets with the router here
|
||||
# Register your viewsets with the router he -fre
|
||||
# Example:
|
||||
# from .views import MyViewSet
|
||||
# router.register(r'myviewset', MyViewSet, basename='myviewset')
|
||||
@@ -23,5 +23,7 @@ urlpatterns = [
|
||||
path('documents/descargar/<uuid:pk>/', ProtectedDocumentDownloadView.as_view(), name='descargar-documento'),
|
||||
path('fuente/', GetFuenteView.as_view(), name='get-fuente'),
|
||||
path('document-type/', DocumentTypeView.as_view(), name='document-type-list-create'),
|
||||
path('documents/expediente-zip/', ExpedienteZipDownloadView.as_view(), name='expediente-zip-download'),
|
||||
path('documents/multi-pedimento-zip/', MultiPedimentoZipDownloadView.as_view(), name='multi-pedimento-zip-download'),
|
||||
path('', include(router.urls)),
|
||||
]
|
||||
@@ -13,6 +13,7 @@ from rest_framework.exceptions import ValidationError
|
||||
|
||||
from .serializers import DocumentSerializer, FuenteSerializer, DocumentTypeSerializer
|
||||
from .models import Document, Fuente, DocumentType
|
||||
from ..customs.models import Pedimento
|
||||
from api.organization.models import UsoAlmacenamiento
|
||||
from io import BytesIO
|
||||
import zipfile
|
||||
@@ -32,6 +33,9 @@ from core.permissions import (
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
import os
|
||||
from django.core.files.storage import default_storage
|
||||
|
||||
from mixins.filtrado_organizacion import DocumentosFiltradosMixin
|
||||
|
||||
class CustomPagination(PageNumberPagination):
|
||||
@@ -530,7 +534,6 @@ class ProtectedDocumentDownloadView(APIView, DocumentosFiltradosMixin):
|
||||
def get(self, request, pk):
|
||||
if not request.user.is_authenticated or not hasattr(request.user, 'organizacion'):
|
||||
raise Http404("Usuario no autenticado")
|
||||
|
||||
|
||||
try:
|
||||
doc = Document.objects.get(pk=pk)
|
||||
@@ -617,4 +620,148 @@ class DocumentTypeView(APIView):
|
||||
if not queryset.exists():
|
||||
return Response({"detail": "No hay tipos de documento disponibles."}, status=404)
|
||||
serializer = self.serializer_class(queryset, many=True)
|
||||
return Response(serializer.data, status=200)
|
||||
return Response(serializer.data, status=200)
|
||||
|
||||
class ExpedienteZipDownloadView(APIView, DocumentosFiltradosMixin):
|
||||
permission_classes = [IsAuthenticated & (IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)]
|
||||
my_tags = ['Documents']
|
||||
|
||||
def post(self, request):
|
||||
"""
|
||||
Descarga todos los documentos de un pedimento (o filtrados) en un ZIP.
|
||||
Body: { "pedimento_id": "<uuid>" }
|
||||
"""
|
||||
pedimento_id = request.data.get('pedimento_id')
|
||||
if not pedimento_id:
|
||||
return Response({"error": "Falta pedimento_id"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# Validar que el pedimento existe
|
||||
try:
|
||||
pedimento = Pedimento.objects.get(pk=pedimento_id)
|
||||
except Pedimento.DoesNotExist:
|
||||
raise Http404("Pedimento no encontrado")
|
||||
|
||||
# Filtrar documentos del pedimento (y de la org del usuario)
|
||||
base_qs = Document.objects.filter(pedimento=pedimento)
|
||||
if not request.user.is_superuser:
|
||||
if not hasattr(request.user, 'organizacion') or request.user.organizacion != pedimento.organizacion:
|
||||
return Response({"error": "No autorizado"}, status=status.HTTP_403_FORBIDDEN)
|
||||
base_qs = base_qs.filter(organizacion=request.user.organizacion)
|
||||
|
||||
docs = base_qs.select_related('pedimento')
|
||||
if not docs.exists():
|
||||
return Response({"error": "No hay documentos para este pedimento"}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# 1. Crear un único buffer y ZIP para todos los archivos
|
||||
buffer = BytesIO()
|
||||
missing_files = [] # opcional: para informar después
|
||||
files_found = []
|
||||
|
||||
with zipfile.ZipFile(buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
|
||||
for doc in docs:
|
||||
# 2. Validaciones
|
||||
if not doc.archivo.name:
|
||||
logger.warning("Documento %s no tiene archivo asociado", doc.id)
|
||||
missing_files.append(f"{doc.id} (sin archivo)")
|
||||
continue
|
||||
if not default_storage.exists(doc.archivo.name):
|
||||
logger.warning("Archivo no encontrado en disco: %s", doc.archivo.path)
|
||||
missing_files.append(f"{doc.id} ({doc.archivo.name})")
|
||||
continue
|
||||
|
||||
files_found.append(f"{doc.id} ({doc.archivo.name})")
|
||||
|
||||
# 3. Nombre seguro para dentro del ZIP
|
||||
file_name = slugify(doc.archivo.name.rsplit('/', 1)[-1].rsplit('.', 1)[0])
|
||||
ext = doc.archivo.name.split('.')[-1]
|
||||
name_inside_zip = f"{file_name}.{ext}"
|
||||
|
||||
# 4. Escribir el archivo dentro del ZIP
|
||||
with doc.archivo.open('rb') as f:
|
||||
zip_file.writestr(name_inside_zip, f.read())
|
||||
|
||||
# 5. Preparar respuesta
|
||||
buffer.seek(0)
|
||||
zip_name = slugify(f"expediente_{pedimento.pedimento_app}")
|
||||
response = HttpResponse(buffer, content_type='application/zip')
|
||||
response['Content-Disposition'] = f'attachment; filename={zip_name or "documentos"}.zip'
|
||||
|
||||
if not files_found:
|
||||
return Response({"error": f"No hay documentos para este pedimento: {pedimento.pedimento_app}"}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# (Opcional) cabecera personalizada si faltaron archivos
|
||||
# if missing_files:
|
||||
# response['X-Missing-Files'] = ', '.join(missing_files)
|
||||
# return Response({"error": f"No hay documentos para este pedimento: {pedimento.pedimento_app}"}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
return response
|
||||
|
||||
class MultiPedimentoZipDownloadView(APIView):
|
||||
permission_classes = [IsAuthenticated & (IsSuperUser | IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper)]
|
||||
my_tags = ['Documents']
|
||||
|
||||
def post(self, request):
|
||||
"""
|
||||
Descarga todos los documentos de VARIOS pedimentos en un solo ZIP.
|
||||
Body: { "pedimento_ids": ["uuid1", "uuid2", ...] }
|
||||
"""
|
||||
pedimento_ids = request.data.get('pedimento_ids', [])
|
||||
if not isinstance(pedimento_ids, list) or not pedimento_ids:
|
||||
return Response({"error": "Se requiere una lista de pedimento_ids"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# Filtrar pedimentos visibles para el usuario
|
||||
base_qs = Pedimento.objects.filter(id__in=pedimento_ids)
|
||||
if not request.user.is_superuser:
|
||||
if not hasattr(request.user, 'organizacion'):
|
||||
return Response({"error": "No autorizado"}, status=status.HTTP_403_FORBIDDEN)
|
||||
base_qs = base_qs.filter(organizacion=request.user.organizacion)
|
||||
|
||||
pedimentos = base_qs.select_related('organizacion')
|
||||
if not pedimentos.exists():
|
||||
return Response({"error": "Ningún pedimento encontrado o autorizado"}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# Obtener todos los documentos de esos pedimentos
|
||||
docs = Document.objects.filter(pedimento__in=pedimentos)
|
||||
if not docs.exists():
|
||||
return Response({"error": "No hay documentos para estos pedimentos"}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# Crear ZIP único
|
||||
buffer = BytesIO()
|
||||
missing_files = []
|
||||
summary = {}
|
||||
with zipfile.ZipFile(buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
|
||||
for doc in docs:
|
||||
|
||||
ped_key = doc.pedimento.pedimento_app
|
||||
|
||||
if not doc.archivo.name or not default_storage.exists(doc.archivo.name):
|
||||
missing_files.append(f"{doc.id} ({doc.archivo.name or 'sin archivo'})")
|
||||
logger.warning("Archivo faltante: %s", doc.id)
|
||||
continue
|
||||
|
||||
summary[ped_key] = summary.get(ped_key, 0) + 1
|
||||
|
||||
# Nombre seguro: pedimento_app + nombre del archivo
|
||||
file_name = slugify(doc.archivo.name.rsplit('/', 1)[-1].rsplit('.', 1)[0])
|
||||
ext = doc.archivo.name.split('.')[-1]
|
||||
name_inside_zip = f"{doc.pedimento.pedimento_app}/{file_name}.{ext}"
|
||||
|
||||
with doc.archivo.open('rb') as f:
|
||||
zip_file.writestr(name_inside_zip, f.read())
|
||||
|
||||
buffer.seek(0)
|
||||
zip_name = slugify(f"expedientes_{len(summary)}_pedimentos")
|
||||
|
||||
response = HttpResponse(buffer, content_type='application/zip')
|
||||
response['Content-Disposition'] = f'attachment; filename={zip_name}.zip'
|
||||
response['X-Zip-Filename'] = f"{zip_name}.zip"
|
||||
response['Access-Control-Expose-Headers'] = 'X-Zip-Filename'
|
||||
|
||||
if missing_files:
|
||||
response['X-Missing-Files'] = ', '.join(missing_files)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user