diff --git a/cosiap_api/archivo.zip b/cosiap_api/archivo.zip index 5b20bd466e3ea030635f26d3cf3fd4fe73b48cd8..a3155ad5cecbb102769971edbd7ba39aa20e6a35 100644 Binary files a/cosiap_api/archivo.zip and b/cosiap_api/archivo.zip differ diff --git a/cosiap_api/descargar_reporte.py b/cosiap_api/descargar_reporte.py index f829e5f86459dcb2594a65d10a3dbbc452609633..c9942a2cb604ae8f826633c1f1264676dc43b0c2 100644 --- a/cosiap_api/descargar_reporte.py +++ b/cosiap_api/descargar_reporte.py @@ -1,8 +1,23 @@ import requests +import urllib.parse +import json -url = "http://localhost:8000/api/solicitudes/reportes/exportar/?reporte_id=500" + +base_url = "http://localhost:8000/api/solicitudes/reportes/exportar/" +filters = {"status": {"iexact": ["Aprobado"]}} + +# Convertir el diccionario a una cadena JSON +filters_json = json.dumps(filters) + +# Codificar la cadena JSON para usarla en la URL +encoded_filters = urllib.parse.quote(filters_json) + +# Construir la URL completa +url = f"{base_url}?filters={encoded_filters}" + +print(url) headers = { - "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzI4ODM4NTk2LCJpYXQiOjE3Mjg3NTIxOTYsImp0aSI6IjFiODBhNThmZWUxZTQ4YjFhY2IyMWQ4ZWIwYzQ5OWQxIiwidXNlcl9pZCI6OH0.JpYIUcQbHLiWDpulj8x8AdZ7CD3ZNW-Ms-ON8vAjJk0" + "Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzI5OTAwNDAwLCJpYXQiOjE3Mjk4MTQwMDAsImp0aSI6IjVmYTM1ZGU2ZGM4ZDQ1MDBhMTZmYzAwM2E5ZTExMTQ2IiwidXNlcl9pZCI6OX0.GbkUOLbFCu4FAP6rHxkmldOlNY5VxZ-0Nf2RZ_x28_8" } response = requests.get(url, headers=headers) diff --git a/cosiap_api/dynamic_tables/DynamicTable.py b/cosiap_api/dynamic_tables/DynamicTable.py index 1cf1ff92e31cc638d10d5e5bc25f86f8e8de813b..154deb2f8d434d48a9c5bfff744273e21cdf9aaa 100644 --- a/cosiap_api/dynamic_tables/DynamicTable.py +++ b/cosiap_api/dynamic_tables/DynamicTable.py @@ -116,8 +116,9 @@ class DynamicTable(serializers.ModelSerializer): filters = obj.filters or {} exclude_filters = obj.exclude_filters or {} - # Obtener todas las columnas si se especifica '__all__' - if columns == '__all__': + + if columns == "__all__": + columns = list(self.get_available_columns(obj).keys()) obj.columns = columns @@ -317,6 +318,7 @@ class DynamicTable(serializers.ModelSerializer): available_columns = {} model = self.buscar_modelo(obj) + print("Validación model" ,model) if model is None: # En caso de que no se haya encontrado el modelo enviamos un diccionario vacío return available_columns @@ -588,99 +590,124 @@ class DynamicTable(serializers.ModelSerializer): return overall_success - def export_to_csv_and_zip(self, data=None, solicitud_id=None): + def export_to_csv_and_zip(self, data): """ Exporta los datos a un CSV y los archivos asociados a una estructura de directorios, y luego empaqueta todo en un archivo zip. - - Parámetros: - - data: Los datos a exportar. No se espera si se proporciona un solicitud_id. - - uid: Identificador único para el archivo zip. - - solicitud_id: ID de la solicitud específica a exportar. Si se proporciona, se exporta solo esa solicitud. """ - # Configuramos los directorios correspondientes + # Configurar los directorios temporales temp_dir = os.path.join(settings.BASE_DIR, 'temp_export') os.makedirs(temp_dir, exist_ok=True) files_dir = os.path.join(temp_dir, 'archivos') os.makedirs(files_dir, exist_ok=True) csv_file_path = os.path.join(temp_dir, 'reporte.csv') - if solicitud_id: - solicitud = Solicitud.objects.get(id=solicitud_id) - data_nested = self.retrieve_instance_data(solicitud) - data = self.flatten_dict(data_nested) - - with open(csv_file_path, mode='w', newline='', encoding='utf-8') as csvfile: - fieldnames = self.get_fieldnames(data) - writer = csv.DictWriter(csvfile, fieldnames=fieldnames) - writer.writeheader() - - solicitante_curp = data.get('solicitante__curp') - id_solicitud = data.get('id') + + # Generación de CSV para múltiples solicitudes + self.write_csv(data, csv_file_path) + solicitudes_por_curp = {} + + for item in data: + id_solicitud = item.get('id') + solicitud = Solicitud.objects.get(id=id_solicitud) + solicitante = solicitud.solicitante + solicitante_curp = solicitante.curp + if solicitante_curp not in solicitudes_por_curp: curp_dir = os.path.join(files_dir, f'solicitud_{solicitante_curp}') os.makedirs(curp_dir, exist_ok=True) - solicitud_dir = os.path.join(curp_dir, f'solicitud_{id_solicitud}') - os.makedirs(solicitud_dir, exist_ok=True) - - respuestas_dir = os.path.join(solicitud_dir, 'respuestas') - os.makedirs(respuestas_dir, exist_ok=True) - self.handle_files(data, solicitud_dir) - self.handle_responses(id_solicitud, respuestas_dir) - writer.writerow(data) - else: - with open(csv_file_path, mode='w', newline='', encoding='utf-8') as csvfile: - fieldnames = self.get_fieldnames(data) - writer = csv.DictWriter(csvfile, fieldnames=fieldnames) - writer.writeheader() - solicitudes_por_curp = {} - - for item in data: - solicitante_curp = item.get('solicitante__curp') - if solicitante_curp: - id_solicitud = item.get('id') - if solicitante_curp not in solicitudes_por_curp: - curp_dir = os.path.join(files_dir, f'solicitud_{solicitante_curp}') - os.makedirs(curp_dir, exist_ok=True) - solicitudes_por_curp[solicitante_curp] = curp_dir - - curp_dir = solicitudes_por_curp[solicitante_curp] - solicitud_dir = os.path.join(curp_dir, f'solicitud_{id_solicitud}') - os.makedirs(solicitud_dir, exist_ok=True) - - respuestas_dir = os.path.join(solicitud_dir, 'respuestas') - os.makedirs(respuestas_dir, exist_ok=True) - self.handle_files(item, solicitud_dir) - solicitud = Solicitud.objects.get(id= id_solicitud) - registro_formulario = solicitud.registro_formulario - registros_secciones = RegistroSeccion.objects.filter(registro_formulario = registro_formulario) - print("Registros_secciones : ", registros_secciones) - self.handle_responses(registros_secciones, respuestas_dir) - else: - self.handle_files(item, files_dir) - writer.writerow(item) + solicitudes_por_curp[solicitante_curp] = curp_dir + curp_dir = solicitudes_por_curp[solicitante_curp] + solicitud_dir = os.path.join(curp_dir, f'solicitud_{id_solicitud}') + os.makedirs(solicitud_dir, exist_ok=True) + self.handle_files(solicitante, solicitud_dir) + self.handle_responses(solicitud, solicitud_dir) + + # Crear el archivo ZIP + return self.create_zip_response(temp_dir, csv_file_path) + + def write_csv(self, data, csv_file_path): + """Escribe los datos en un archivo CSV.""" + with open(csv_file_path, mode='w', newline='', encoding='utf-8') as csvfile: + fieldnames = self.get_fieldnames(data) + writer = csv.DictWriter(csvfile, fieldnames=fieldnames) + writer.writeheader() + + if isinstance(data, list): + writer.writerows(data) # Escribir múltiples filas + elif isinstance(data, dict): + writer.writerow(data) # Escribir una única fila + else: + raise ValueError("El formato de datos no es compatible para exportar a CSV.") + + + def create_directories(self, files_dir, solicitante, solicitud_id): + """Crea los directorios necesarios para la solicitud.""" + curp_dir = os.path.join(files_dir, f'solicitud_{solicitante.curp}') + os.makedirs(curp_dir, exist_ok=True) + solicitud_dir = os.path.join(curp_dir, f'solicitud_{solicitud_id}') + os.makedirs(solicitud_dir, exist_ok=True) + return solicitud_dir + + def handle_files(self, solicitante, solicitud_dir): + """Maneja los archivos adjuntos del solicitante.""" + #print(solicitante.datos_bancarios.doc_constancia_sat) + documentos = { + 'doc_constancia_sat': solicitante.datos_bancarios.doc_constancia_sat.name, + 'doc_estado_cuenta': solicitante.datos_bancarios.doc_estado_cuenta.name, + 'INE': solicitante.INE.name, + } + + for key, value in documentos.items(): + file_dir = os.path.join(solicitud_dir, key) + os.makedirs(file_dir, exist_ok=True) + file_name = os.path.basename(value) + file_path = os.path.join(file_dir, file_name) + print(file_name) + print(file_path) + try: + # Leer el contenido del archivo desde el almacenamiento predeterminado + file_content = default_storage.open(value).read() + with open(file_path, 'wb') as f: + f.write(file_content) + except Exception as e: + print(f"Error al manejar el archivo {key}: {e}") + + def handle_responses(self, solicitud, solicitud_dir): + """Maneja las respuestas asociadas a la solicitud.""" + registros_secciones = RegistroSeccion.objects.filter( + registro_formulario=solicitud.registro_formulario + ) + respuestas_dir = os.path.join(solicitud_dir, 'respuestas') + os.makedirs(respuestas_dir, exist_ok=True) - # Creamos un archivo zip + responses = RDocumento.objects.filter(registro_seccion__in=registros_secciones) + for response in responses: + file_name = os.path.basename(response.valor.name) + file_path = os.path.join(respuestas_dir, file_name) + file_content = default_storage.open(response.valor.name).read() + + with open(file_path, 'wb') as f: + f.write(file_content) + + + def create_zip_response(self, temp_dir, csv_file_path): + """Crea y devuelve el archivo ZIP.""" zip_buffer = io.BytesIO() with ZipFile(zip_buffer, 'w') as zip_file: - # Añadimos el archivo CSV al ZIP solo una vez zip_file.write(csv_file_path, 'reporte.csv') - # Añadimos todos los archivos del directorio temporal al ZIP - for root, dirs, files in os.walk(temp_dir): + for root, _, files in os.walk(temp_dir): for file in files: - file_path = os.path.join(root, file) - # Solo añadir archivos que no sean el CSV para evitar duplicación if file != 'reporte.csv': + file_path = os.path.join(root, file) zip_file.write(file_path, os.path.relpath(file_path, temp_dir)) zip_buffer.seek(0) response = HttpResponse(zip_buffer, content_type='application/zip') - response['Content-Disposition'] = f'attachment; filename="reporte.zip"' + response['Content-Disposition'] = 'attachment; filename="reporte.zip"' shutil.rmtree(temp_dir) return response - - def get_fieldnames(self, data): """ Devuelve los nombres de los campos que se usarán en el CSV. @@ -689,8 +716,10 @@ class DynamicTable(serializers.ModelSerializer): if not data: return [] - # Verifica si 'data' es una lista de diccionarios + # Si es una lista, verificar que todos los elementos sean diccionarios if isinstance(data, list): + if not all(isinstance(item, dict) for item in data): + raise ValueError("Todos los elementos de la lista deben ser diccionarios") first_item = data[0] elif isinstance(data, dict): first_item = data @@ -700,69 +729,8 @@ class DynamicTable(serializers.ModelSerializer): return list(first_item.keys()) - def handle_files(self, item, solicitud_dir): - """ - Maneja los archivos adjuntos, los guarda en el directorio correspondiente basado en el nombre de la columna. - """ - for key, value in item.items(): - print(key, '-', value) - if value and isinstance(value, str) and value.endswith(('.png', '.jpg', '.jpeg', '.pdf')): - # Detectar directorio basado en la primera parte del nombre de la columna - column_dir = key.split('__')[0] - - if value.startswith('/media/'): - value = value[len('/media/'):] - - if column_dir == 'modalidad': - continue - - # Crear el directorio para archivos - file_dir = os.path.join(solicitud_dir, column_dir) - os.makedirs(file_dir, exist_ok=True) - - file_name = os.path.basename(value) - file_path = os.path.join(file_dir, file_name) - - # Simular la descarga del archivo - file_content = default_storage.open(value).read() - with open(file_path, 'wb') as f: - f.write(file_content) - - # Actualizar la ruta del archivo en el diccionario - try: - item[key] = os.path.join('archivos', f'solicitud_{item["solicitante__curp"]}', column_dir, file_name) - except: - continue - - - def handle_responses(self, registros_secciones, respuestas_dir): - """ - Maneja la extracción de respuestas relacionadas con la solicitud y las guarda en el directorio correspondiente. - """ - # Filtrar las respuestas por solicitud - responses = RDocumento.objects.filter(registro_seccion_id__in=registros_secciones) - print(responses) - - for response in responses: - file_path = os.path.join(respuestas_dir, os.path.basename(response.valor.name)) - - file_content = default_storage.open(response.valor.name).read() - with open(file_path, 'wb') as f: - f.write(file_content) - - # Actualizar el campo en la respuesta - response.archivo = os.path.join('archivos', 'respuestas', os.path.basename(response.valor.name)) - response.save() - - - def flatten_dict(self,d, parent_key='', sep='__'): - ''' - Método para convertir un diccionario anidado a un dichionario plano - - param:d: diccionario anidado a convertir - param:parent_key: parámetro para marcar los keys padres durante el ciclo - param:sep: separador de columna_relacion - ''' + def flatten_dict(self, d, parent_key='', sep='__'): + """Aplana un diccionario anidado.""" items = [] for k, v in d.items(): new_key = f"{parent_key}{sep}{k}" if parent_key else k diff --git a/cosiap_api/dynamic_tables/views.py b/cosiap_api/dynamic_tables/views.py index b5d74cd3a50727ba1e7619431fd8cdf852f25301..0186b54f8ebedbe0c977da9b4c1e9dd079663391 100644 --- a/cosiap_api/dynamic_tables/views.py +++ b/cosiap_api/dynamic_tables/views.py @@ -68,7 +68,7 @@ class DynamicTableAPIView(BasePermissionAPIView): self.exclude_filters = self.parse_json_param(request.query_params.get('exclude_filters', '{}')) self.search_query = request.query_params.get('search_query', '') - reporte = DynamicTableReport.objects.create( + reporte = DynamicTableReport( model_name=self.model_name, columns=self.columns, filters=self.filters, @@ -350,14 +350,39 @@ class Exportar_CSV(BasePermissionAPIView): permission_classes_list = [IsAuthenticated, es_admin] model_class = None + def parse_json_param(self, param): + '''Convierte un string JSON a un diccionario, si es posible.''' + try: + import json + return json.loads(param) + except (ValueError, TypeError): + return {} + def get_configuracion_reporte(self, request): ''' Recuperar la configuración del reporte de la request si se ha enviado. ''' try: reporte_id = request.query_params.get('reporte_id', None) - return DynamicTableReport.objects.get(id = reporte_id) - + if reporte_id: + return DynamicTableReport.objects.get(id = reporte_id) + else: + model_name = "Solicitud" + columns = request.query_params.get('columns', '__all__').strip('"') + filters = self.parse_json_param(request.query_params.get('filters', {})) + exclude_columns = request.query_params.get('exclude_columns', []) + search_query = request.query_params.get('search_query', '') + exclude_filters = self.parse_json_param(request.query_params.get('exclude_filters', {})) + reporte = DynamicTableReport( + model_name=model_name, + columns=columns, + filters=filters, + exclude_columns=exclude_columns, + exclude_filters=exclude_filters, + search_query=search_query + ) + + return reporte except Exception as e: data = {} Mensaje.error(data, str(e)) @@ -368,15 +393,12 @@ class Exportar_CSV(BasePermissionAPIView): ''' Método get para obtener el archivo zip con todos los documentos solicitados. ''' - - if 'pk' in kwargs: - reporte = DynamicTableDynamicForm(model_class=self.model_class) - solicitud_id = kwargs['pk'] - response = reporte.export_to_csv_and_zip(solicitud_id=solicitud_id) - return response - else: - configuracion = self.get_configuracion_reporte(request) - reporte = DynamicTableDynamicForm(instance=configuracion, model_class=self.model_class) - data = reporte.get_data(configuracion) - response = reporte.export_to_csv_and_zip(data) - return response \ No newline at end of file + configuracion = self.get_configuracion_reporte(request) + print(configuracion.filters) + print(configuracion.model_name) + print(configuracion.columns) + reporte = DynamicTableDynamicForm(instance=configuracion, model_class=self.model_class) + data = reporte.get_data(configuracion) + print(data) + response = reporte.export_to_csv_and_zip(data) + return response \ No newline at end of file diff --git a/cosiap_frontend/src/components/admin/CrearAdministrador.jsx b/cosiap_frontend/src/components/admin/CrearAdministrador.jsx index 38c2c230438186947dbe3d941313791e19da53c7..b4d72df74997f0c518313cbac630b68b51a0d9f5 100644 --- a/cosiap_frontend/src/components/admin/CrearAdministrador.jsx +++ b/cosiap_frontend/src/components/admin/CrearAdministrador.jsx @@ -39,7 +39,8 @@ const CrearAdmin = () =>{ password: password, confirmar_password: passwordConfirm, }; - await api.usuarios.administradores.post(data) + const response = await api.usuarios.administradores.post(data) + console.log("Respuesta: " , response) showAlert('Cuenta de administrador creada correctamente.', true) navigate('/administradores'); } catch (error) {