import os from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify, send_file, abort from flask_login import login_required, current_user from flask_wtf import FlaskForm from flask_wtf.file import FileField, FileRequired from wtforms import StringField, TextAreaField, SubmitField, HiddenField from wtforms.validators import DataRequired, Length from werkzeug.utils import secure_filename from services.document_service import ( add_document, add_version, get_document, get_project_documents, get_document_version, get_latest_version, register_download, delete_document ) from services.project_service import get_project from services.schema_service import get_schema_document_types from utils.security import permission_required # Definir Blueprint documents_bp = Blueprint('documents', __name__, url_prefix='/documents') # Formularios class DocumentUploadForm(FlaskForm): """Formulario para subir documento.""" nombre = StringField('Nombre del documento', validators=[DataRequired(), Length(1, 100)]) description = TextAreaField('Descripción', validators=[Length(0, 500)]) file = FileField('Archivo', validators=[FileRequired()]) submit = SubmitField('Subir documento') class DocumentVersionForm(FlaskForm): """Formulario para nueva versión de documento.""" description = TextAreaField('Descripción de la versión', validators=[Length(0, 500)]) file = FileField('Archivo', validators=[FileRequired()]) document_id = HiddenField('ID de documento', validators=[DataRequired()]) submit = SubmitField('Subir nueva versión') # Rutas @documents_bp.route('/') @login_required def list(project_id): """Listar documentos de un proyecto.""" project = get_project(project_id) if not project: flash('Proyecto no encontrado.', 'danger') return redirect(url_for('projects.list')) documents = get_project_documents(project_id) return render_template('documents/list.html', project=project, documents=documents) @documents_bp.route('//upload', methods=['GET', 'POST']) @login_required @permission_required(1000) # Nivel mínimo para subir documentos def upload(project_id): """Subir un nuevo documento.""" project = get_project(project_id) if not project: flash('Proyecto no encontrado.', 'danger') return redirect(url_for('projects.list')) # Verificar que el proyecto esté activo if project['estado'] != 'activo': flash('No se pueden añadir documentos a un proyecto inactivo.', 'warning') return redirect(url_for('projects.view', project_id=project_id)) form = DocumentUploadForm() if form.validate_on_submit(): # Añadir documento success, message, document_id = add_document( project_id, { 'nombre': form.nombre.data, 'description': form.description.data }, form.file.data, current_user.username ) if success: flash(message, 'success') return redirect(url_for('documents.versions', project_id=project_id, document_id=document_id)) else: flash(message, 'danger') return render_template('documents/upload.html', form=form, project=project) @documents_bp.route('//') @login_required def versions(project_id, document_id): """Ver versiones de un documento.""" project = get_project(project_id) if not project: flash('Proyecto no encontrado.', 'danger') return redirect(url_for('projects.list')) document = get_document(project_id, document_id) if not document: flash('Documento no encontrado.', 'danger') return redirect(url_for('projects.view', project_id=project_id)) form = DocumentVersionForm() form.document_id.data = document_id return render_template('documents/versions.html', project=project, document=document, form=form) @documents_bp.route('///upload', methods=['POST']) @login_required @permission_required(1000) # Nivel mínimo para subir versiones def upload_version(project_id, document_id): """Subir una nueva versión de documento.""" project = get_project(project_id) if not project: flash('Proyecto no encontrado.', 'danger') return redirect(url_for('projects.list')) # Verificar que el proyecto esté activo if project['estado'] != 'activo': flash('No se pueden añadir versiones a un proyecto inactivo.', 'warning') return redirect(url_for('projects.view', project_id=project_id)) document = get_document(project_id, document_id) if not document: flash('Documento no encontrado.', 'danger') return redirect(url_for('projects.view', project_id=project_id)) form = DocumentVersionForm() if form.validate_on_submit(): # Añadir versión success, message, version = add_version( project_id, document_id, { 'description': form.description.data }, form.file.data, current_user.username ) if success: flash(message, 'success') else: flash(message, 'danger') else: for field, errors in form.errors.items(): for error in errors: flash(f"{getattr(form, field).label.text}: {error}", 'danger') return redirect(url_for('documents.versions', project_id=project_id, document_id=document_id)) @documents_bp.route('///download/') @login_required def download(project_id, document_id, version): """Descargar una versión específica de un documento.""" project = get_project(project_id) if not project: flash('Proyecto no encontrado.', 'danger') return redirect(url_for('projects.list')) # Obtener versión solicitada version_meta, file_path = get_document_version(project_id, document_id, version) if not version_meta or not file_path: flash('Versión de documento no encontrada.', 'danger') return redirect(url_for('projects.view', project_id=project_id)) # Registrar descarga register_download(project_id, document_id, version, current_user.username) # Enviar archivo try: return send_file( file_path, mimetype=version_meta['mime_type'], as_attachment=True, download_name=os.path.basename(file_path) ) except Exception as e: flash(f'Error al descargar el archivo: {str(e)}', 'danger') return redirect(url_for('documents.versions', project_id=project_id, document_id=document_id)) @documents_bp.route('///latest') @login_required def download_latest(project_id, document_id): """Descargar la última versión de un documento.""" project = get_project(project_id) if not project: flash('Proyecto no encontrado.', 'danger') return redirect(url_for('projects.list')) # Obtener última versión version_meta, file_path = get_latest_version(project_id, document_id) if not version_meta or not file_path: flash('Documento no encontrado.', 'danger') return redirect(url_for('projects.view', project_id=project_id)) # Registrar descarga register_download(project_id, document_id, version_meta['version'], current_user.username) # Enviar archivo try: return send_file( file_path, mimetype=version_meta['mime_type'], as_attachment=True, download_name=os.path.basename(file_path) ) except Exception as e: flash(f'Error al descargar el archivo: {str(e)}', 'danger') return redirect(url_for('documents.versions', project_id=project_id, document_id=document_id)) @documents_bp.route('///delete', methods=['POST']) @login_required @permission_required(9000) # Nivel alto para eliminar documentos def delete(project_id, document_id): """Eliminar un documento.""" project = get_project(project_id) if not project: flash('Proyecto no encontrado.', 'danger') return redirect(url_for('projects.list')) success, message = delete_document(project_id, document_id) if success: flash(message, 'success') else: flash(message, 'danger') return redirect(url_for('projects.view', project_id=project_id)) @documents_bp.route('//export') @login_required def export(project_id): """Exportar documentos de un proyecto.""" project = get_project(project_id) if not project: flash('Proyecto no encontrado.', 'danger') return redirect(url_for('projects.list')) documents = get_project_documents(project_id) return render_template('documents/export.html', project=project, documents=documents) @documents_bp.route('//api/list') @login_required def api_list(project_id): """API para listar documentos de un proyecto.""" documents = get_project_documents(project_id) return jsonify(documents)