2025-03-03 15:35:24 -03:00
|
|
|
import os
|
2025-03-05 14:10:33 -03:00
|
|
|
from flask import (
|
|
|
|
Blueprint,
|
|
|
|
render_template,
|
|
|
|
redirect,
|
|
|
|
url_for,
|
|
|
|
flash,
|
|
|
|
request,
|
|
|
|
jsonify,
|
|
|
|
send_file,
|
|
|
|
abort,
|
|
|
|
)
|
2025-03-03 15:35:24 -03:00
|
|
|
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 (
|
2025-03-05 14:10:33 -03:00
|
|
|
add_document,
|
|
|
|
add_version,
|
|
|
|
get_document,
|
|
|
|
get_project_documents,
|
|
|
|
get_document_version,
|
|
|
|
get_latest_version,
|
|
|
|
register_download,
|
|
|
|
delete_document,
|
2025-03-03 15:35:24 -03:00
|
|
|
)
|
|
|
|
from services.project_service import get_project
|
|
|
|
from services.schema_service import get_schema_document_types
|
|
|
|
from utils.security import permission_required
|
|
|
|
|
|
|
|
# Definir Blueprint
|
2025-03-05 14:10:33 -03:00
|
|
|
documents_bp = Blueprint("documents", __name__, url_prefix="/documents")
|
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
|
|
|
|
# Formularios
|
|
|
|
class DocumentUploadForm(FlaskForm):
|
|
|
|
"""Formulario para subir documento."""
|
2025-03-05 14:10:33 -03:00
|
|
|
|
|
|
|
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")
|
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
|
|
|
|
class DocumentVersionForm(FlaskForm):
|
|
|
|
"""Formulario para nueva versión de documento."""
|
2025-03-05 14:10:33 -03:00
|
|
|
|
|
|
|
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")
|
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
|
|
|
|
# Rutas
|
2025-03-05 14:10:33 -03:00
|
|
|
@documents_bp.route("/<project_id>")
|
2025-03-03 15:35:24 -03:00
|
|
|
@login_required
|
|
|
|
def list(project_id):
|
|
|
|
"""Listar documentos de un proyecto."""
|
|
|
|
project = get_project(project_id)
|
2025-03-05 14:10:33 -03:00
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
if not project:
|
2025-03-05 14:10:33 -03:00
|
|
|
flash("Proyecto no encontrado.", "danger")
|
|
|
|
return redirect(url_for("projects.list"))
|
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
documents = get_project_documents(project_id)
|
|
|
|
|
2025-03-05 14:10:33 -03:00
|
|
|
return render_template("documents/list.html", project=project, documents=documents)
|
|
|
|
|
|
|
|
|
|
|
|
@documents_bp.route("/<project_id>/upload", methods=["GET", "POST"])
|
2025-03-03 15:35:24 -03:00
|
|
|
@login_required
|
|
|
|
@permission_required(1000) # Nivel mínimo para subir documentos
|
|
|
|
def upload(project_id):
|
|
|
|
"""Subir un nuevo documento."""
|
|
|
|
project = get_project(project_id)
|
2025-03-05 14:10:33 -03:00
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
if not project:
|
2025-03-05 14:10:33 -03:00
|
|
|
flash("Proyecto no encontrado.", "danger")
|
|
|
|
return redirect(url_for("projects.list"))
|
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
# Verificar que el proyecto esté activo
|
2025-03-05 14:10:33 -03:00
|
|
|
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))
|
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
form = DocumentUploadForm()
|
2025-03-05 14:10:33 -03:00
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
if form.validate_on_submit():
|
|
|
|
# Añadir documento
|
|
|
|
success, message, document_id = add_document(
|
2025-03-05 14:10:33 -03:00
|
|
|
project_id,
|
|
|
|
{"nombre": form.nombre.data, "description": form.description.data},
|
2025-03-03 15:35:24 -03:00
|
|
|
form.file.data,
|
2025-03-05 14:10:33 -03:00
|
|
|
current_user.username,
|
2025-03-03 15:35:24 -03:00
|
|
|
)
|
2025-03-05 14:10:33 -03:00
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
if success:
|
2025-03-05 14:10:33 -03:00
|
|
|
flash(message, "success")
|
|
|
|
return redirect(
|
|
|
|
url_for(
|
|
|
|
"documents.versions", project_id=project_id, document_id=document_id
|
|
|
|
)
|
|
|
|
)
|
2025-03-03 15:35:24 -03:00
|
|
|
else:
|
2025-03-05 14:10:33 -03:00
|
|
|
flash(message, "danger")
|
|
|
|
|
|
|
|
return render_template("documents/upload.html", form=form, project=project)
|
2025-03-03 15:35:24 -03:00
|
|
|
|
2025-03-05 14:10:33 -03:00
|
|
|
|
|
|
|
@documents_bp.route("/<project_id>/<int:document_id>")
|
2025-03-03 15:35:24 -03:00
|
|
|
@login_required
|
|
|
|
def versions(project_id, document_id):
|
|
|
|
"""Ver versiones de un documento."""
|
|
|
|
project = get_project(project_id)
|
2025-03-05 14:10:33 -03:00
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
if not project:
|
2025-03-05 14:10:33 -03:00
|
|
|
flash("Proyecto no encontrado.", "danger")
|
|
|
|
return redirect(url_for("projects.list"))
|
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
document = get_document(project_id, document_id)
|
2025-03-05 14:10:33 -03:00
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
if not document:
|
2025-03-05 14:10:33 -03:00
|
|
|
flash("Documento no encontrado.", "danger")
|
|
|
|
return redirect(url_for("projects.view", project_id=project_id))
|
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
form = DocumentVersionForm()
|
|
|
|
form.document_id.data = document_id
|
|
|
|
|
2025-03-05 14:10:33 -03:00
|
|
|
return render_template(
|
|
|
|
"documents/versions.html", project=project, document=document, form=form
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@documents_bp.route("/<project_id>/<int:document_id>/upload", methods=["POST"])
|
2025-03-03 15:35:24 -03:00
|
|
|
@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)
|
2025-03-05 14:10:33 -03:00
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
if not project:
|
2025-03-05 14:10:33 -03:00
|
|
|
flash("Proyecto no encontrado.", "danger")
|
|
|
|
return redirect(url_for("projects.list"))
|
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
# Verificar que el proyecto esté activo
|
2025-03-05 14:10:33 -03:00
|
|
|
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))
|
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
document = get_document(project_id, document_id)
|
2025-03-05 14:10:33 -03:00
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
if not document:
|
2025-03-05 14:10:33 -03:00
|
|
|
flash("Documento no encontrado.", "danger")
|
|
|
|
return redirect(url_for("projects.view", project_id=project_id))
|
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
form = DocumentVersionForm()
|
2025-03-05 14:10:33 -03:00
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
if form.validate_on_submit():
|
|
|
|
# Añadir versión
|
|
|
|
success, message, version = add_version(
|
|
|
|
project_id,
|
|
|
|
document_id,
|
2025-03-05 14:10:33 -03:00
|
|
|
{"description": form.description.data},
|
2025-03-03 15:35:24 -03:00
|
|
|
form.file.data,
|
2025-03-05 14:10:33 -03:00
|
|
|
current_user.username,
|
2025-03-03 15:35:24 -03:00
|
|
|
)
|
2025-03-05 14:10:33 -03:00
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
if success:
|
2025-03-05 14:10:33 -03:00
|
|
|
flash(message, "success")
|
2025-03-03 15:35:24 -03:00
|
|
|
else:
|
2025-03-05 14:10:33 -03:00
|
|
|
flash(message, "danger")
|
2025-03-03 15:35:24 -03:00
|
|
|
else:
|
|
|
|
for field, errors in form.errors.items():
|
|
|
|
for error in errors:
|
2025-03-05 14:10:33 -03:00
|
|
|
flash(f"{getattr(form, field).label.text}: {error}", "danger")
|
|
|
|
|
|
|
|
return redirect(
|
|
|
|
url_for("documents.versions", project_id=project_id, document_id=document_id)
|
|
|
|
)
|
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
|
2025-03-05 14:10:33 -03:00
|
|
|
@documents_bp.route("/<project_id>/<int:document_id>/download/<int:version>")
|
2025-03-03 15:35:24 -03:00
|
|
|
@login_required
|
|
|
|
def download(project_id, document_id, version):
|
|
|
|
"""Descargar una versión específica de un documento."""
|
|
|
|
project = get_project(project_id)
|
2025-03-05 14:10:33 -03:00
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
if not project:
|
2025-03-05 14:10:33 -03:00
|
|
|
flash("Proyecto no encontrado.", "danger")
|
|
|
|
return redirect(url_for("projects.list"))
|
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
# Obtener versión solicitada
|
|
|
|
version_meta, file_path = get_document_version(project_id, document_id, version)
|
2025-03-05 14:10:33 -03:00
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
if not version_meta or not file_path:
|
2025-03-05 14:10:33 -03:00
|
|
|
flash("Versión de documento no encontrada.", "danger")
|
|
|
|
return redirect(url_for("projects.view", project_id=project_id))
|
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
# Registrar descarga
|
|
|
|
register_download(project_id, document_id, version, current_user.username)
|
2025-03-05 14:10:33 -03:00
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
# Enviar archivo
|
|
|
|
try:
|
|
|
|
return send_file(
|
|
|
|
file_path,
|
2025-03-05 14:10:33 -03:00
|
|
|
mimetype=version_meta["mime_type"],
|
2025-03-03 15:35:24 -03:00
|
|
|
as_attachment=True,
|
2025-03-05 14:10:33 -03:00
|
|
|
download_name=os.path.basename(file_path),
|
2025-03-03 15:35:24 -03:00
|
|
|
)
|
|
|
|
except Exception as e:
|
2025-03-05 14:10:33 -03:00
|
|
|
flash(f"Error al descargar el archivo: {str(e)}", "danger")
|
|
|
|
return redirect(
|
|
|
|
url_for(
|
|
|
|
"documents.versions", project_id=project_id, document_id=document_id
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
|
2025-03-05 14:10:33 -03:00
|
|
|
@documents_bp.route("/<project_id>/<int:document_id>/latest")
|
2025-03-03 15:35:24 -03:00
|
|
|
@login_required
|
|
|
|
def download_latest(project_id, document_id):
|
|
|
|
"""Descargar la última versión de un documento."""
|
|
|
|
project = get_project(project_id)
|
2025-03-05 14:10:33 -03:00
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
if not project:
|
2025-03-05 14:10:33 -03:00
|
|
|
flash("Proyecto no encontrado.", "danger")
|
|
|
|
return redirect(url_for("projects.list"))
|
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
# Obtener última versión
|
|
|
|
version_meta, file_path = get_latest_version(project_id, document_id)
|
2025-03-05 14:10:33 -03:00
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
if not version_meta or not file_path:
|
2025-03-05 14:10:33 -03:00
|
|
|
flash("Documento no encontrado.", "danger")
|
|
|
|
return redirect(url_for("projects.view", project_id=project_id))
|
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
# Registrar descarga
|
2025-03-05 14:10:33 -03:00
|
|
|
register_download(
|
|
|
|
project_id, document_id, version_meta["version"], current_user.username
|
|
|
|
)
|
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
# Enviar archivo
|
|
|
|
try:
|
|
|
|
return send_file(
|
|
|
|
file_path,
|
2025-03-05 14:10:33 -03:00
|
|
|
mimetype=version_meta["mime_type"],
|
2025-03-03 15:35:24 -03:00
|
|
|
as_attachment=True,
|
2025-03-05 14:10:33 -03:00
|
|
|
download_name=os.path.basename(file_path),
|
2025-03-03 15:35:24 -03:00
|
|
|
)
|
|
|
|
except Exception as e:
|
2025-03-05 14:10:33 -03:00
|
|
|
flash(f"Error al descargar el archivo: {str(e)}", "danger")
|
|
|
|
return redirect(
|
|
|
|
url_for(
|
|
|
|
"documents.versions", project_id=project_id, document_id=document_id
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
|
2025-03-05 14:10:33 -03:00
|
|
|
@documents_bp.route("/<project_id>/<int:document_id>/delete", methods=["POST"])
|
2025-03-03 15:35:24 -03:00
|
|
|
@login_required
|
|
|
|
@permission_required(9000) # Nivel alto para eliminar documentos
|
|
|
|
def delete(project_id, document_id):
|
|
|
|
"""Eliminar un documento."""
|
|
|
|
project = get_project(project_id)
|
2025-03-05 14:10:33 -03:00
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
if not project:
|
2025-03-05 14:10:33 -03:00
|
|
|
flash("Proyecto no encontrado.", "danger")
|
|
|
|
return redirect(url_for("projects.list"))
|
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
success, message = delete_document(project_id, document_id)
|
2025-03-05 14:10:33 -03:00
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
if success:
|
2025-03-05 14:10:33 -03:00
|
|
|
flash(message, "success")
|
2025-03-03 15:35:24 -03:00
|
|
|
else:
|
2025-03-05 14:10:33 -03:00
|
|
|
flash(message, "danger")
|
2025-03-03 15:35:24 -03:00
|
|
|
|
2025-03-05 14:10:33 -03:00
|
|
|
return redirect(url_for("projects.view", project_id=project_id))
|
|
|
|
|
|
|
|
|
|
|
|
@documents_bp.route("/<project_id>/export")
|
2025-03-03 15:35:24 -03:00
|
|
|
@login_required
|
|
|
|
def export(project_id):
|
|
|
|
"""Exportar documentos de un proyecto."""
|
|
|
|
project = get_project(project_id)
|
2025-03-05 14:10:33 -03:00
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
if not project:
|
2025-03-05 14:10:33 -03:00
|
|
|
flash("Proyecto no encontrado.", "danger")
|
|
|
|
return redirect(url_for("projects.list"))
|
|
|
|
|
2025-03-03 15:35:24 -03:00
|
|
|
documents = get_project_documents(project_id)
|
|
|
|
|
2025-03-05 14:10:33 -03:00
|
|
|
return render_template(
|
|
|
|
"documents/export.html", project=project, documents=documents
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
@documents_bp.route("/<project_id>/api/list")
|
2025-03-03 15:35:24 -03:00
|
|
|
@login_required
|
|
|
|
def api_list(project_id):
|
|
|
|
"""API para listar documentos de un proyecto."""
|
|
|
|
documents = get_project_documents(project_id)
|
2025-03-05 14:10:33 -03:00
|
|
|
|
|
|
|
return jsonify(documents)
|