260 lines
7.7 KiB
Python
260 lines
7.7 KiB
Python
from flask import (
|
|
Blueprint,
|
|
render_template,
|
|
redirect,
|
|
url_for,
|
|
flash,
|
|
request,
|
|
jsonify,
|
|
current_app,
|
|
)
|
|
from flask_login import login_required, current_user
|
|
from flask_wtf import FlaskForm
|
|
from wtforms import (
|
|
StringField,
|
|
PasswordField,
|
|
SelectField,
|
|
IntegerField,
|
|
EmailField,
|
|
BooleanField,
|
|
SubmitField,
|
|
)
|
|
from wtforms.validators import DataRequired, Email, Length, Optional, EqualTo
|
|
|
|
from services.auth_service import (
|
|
get_all_users,
|
|
get_user_by_username,
|
|
create_user,
|
|
update_user,
|
|
delete_user,
|
|
)
|
|
from services.user_service import (
|
|
filter_users,
|
|
get_user_stats,
|
|
check_username_availability,
|
|
)
|
|
from utils.validators import validate_user_data
|
|
from utils.security import permission_required
|
|
from utils.logger import log_user_management
|
|
|
|
# Definir Blueprint
|
|
users_bp = Blueprint("users", __name__, url_prefix="/users")
|
|
|
|
|
|
# Formularios
|
|
class UserForm(FlaskForm):
|
|
"""Formulario para crear/editar usuarios."""
|
|
|
|
nombre = StringField("Nombre completo", validators=[DataRequired(), Length(1, 100)])
|
|
username = StringField(
|
|
"Nombre de usuario", validators=[DataRequired(), Length(3, 20)]
|
|
)
|
|
email = EmailField("Email", validators=[DataRequired(), Email()])
|
|
password = PasswordField("Contraseña", validators=[Optional(), Length(8, 64)])
|
|
password_confirm = PasswordField(
|
|
"Confirmar contraseña",
|
|
validators=[
|
|
Optional(),
|
|
EqualTo("password", message="Las contraseñas deben coincidir"),
|
|
],
|
|
)
|
|
nivel = IntegerField("Nivel de acceso", validators=[DataRequired()])
|
|
idioma = SelectField("Idioma", choices=[("es", "Español"), ("en", "Inglés")])
|
|
empresa = StringField("Empresa", validators=[Optional(), Length(0, 100)])
|
|
estado = SelectField(
|
|
"Estado", choices=[("activo", "Activo"), ("inactivo", "Inactivo")]
|
|
)
|
|
fecha_caducidad = StringField(
|
|
"Fecha de caducidad (YYYY-MM-DD)", validators=[Optional()]
|
|
)
|
|
submit = SubmitField("Guardar")
|
|
|
|
|
|
class UserFilterForm(FlaskForm):
|
|
"""Formulario para filtrar usuarios."""
|
|
|
|
empresa = StringField("Empresa", validators=[Optional()])
|
|
estado = SelectField(
|
|
"Estado",
|
|
choices=[("", "Todos"), ("activo", "Activo"), ("inactivo", "Inactivo")],
|
|
validators=[Optional()],
|
|
)
|
|
nivel_min = IntegerField("Nivel mínimo", validators=[Optional()])
|
|
nivel_max = IntegerField("Nivel máximo", validators=[Optional()])
|
|
submit = SubmitField("Filtrar")
|
|
|
|
|
|
# Rutas
|
|
@users_bp.route("/")
|
|
@login_required
|
|
@permission_required(5000) # Assuming 5000+ is admin level permission
|
|
def list():
|
|
"""List all users."""
|
|
# Import here to avoid circular imports
|
|
from services.user_service import get_all_users
|
|
|
|
users = get_all_users()
|
|
|
|
return render_template("users/list.html", users=users)
|
|
|
|
|
|
@users_bp.route("/create", methods=["GET", "POST"])
|
|
@login_required
|
|
@permission_required(9000) # Solo administradores
|
|
def create():
|
|
"""Crear nuevo usuario."""
|
|
form = UserForm()
|
|
|
|
if form.validate_on_submit():
|
|
# Validar datos
|
|
data = {
|
|
"nombre": form.nombre.data,
|
|
"username": form.username.data,
|
|
"email": form.email.data,
|
|
"password": form.password.data,
|
|
"nivel": form.nivel.data,
|
|
"idioma": form.idioma.data,
|
|
"empresa": form.empresa.data,
|
|
"estado": form.estado.data,
|
|
"fecha_caducidad": (
|
|
form.fecha_caducidad.data if form.fecha_caducidad.data else None
|
|
),
|
|
}
|
|
|
|
is_valid, errors = validate_user_data(data)
|
|
|
|
if not is_valid:
|
|
for field, error in errors.items():
|
|
flash(f"Error en {field}: {error}", "danger")
|
|
return render_template("users/create.html", form=form)
|
|
|
|
# Crear usuario
|
|
success, message = create_user(
|
|
username=data["username"],
|
|
nombre=data["nombre"],
|
|
email=data["email"],
|
|
password=data["password"],
|
|
nivel=data["nivel"],
|
|
idioma=data["idioma"],
|
|
fecha_caducidad=data["fecha_caducidad"],
|
|
empresa=data["empresa"],
|
|
estado=data["estado"],
|
|
)
|
|
|
|
if success:
|
|
flash(message, "success")
|
|
|
|
# Registrar actividad
|
|
log_user_management(
|
|
admin_id=current_user.id,
|
|
target_user_id=data["username"],
|
|
action="create",
|
|
)
|
|
|
|
return redirect(url_for("users.list_users"))
|
|
else:
|
|
flash(message, "danger")
|
|
|
|
return render_template("users/create.html", form=form)
|
|
|
|
|
|
@users_bp.route("/edit/<username>", methods=["GET", "POST"])
|
|
@login_required
|
|
@permission_required(9000) # Solo administradores
|
|
def edit(username):
|
|
"""Editar usuario existente."""
|
|
user = get_user_by_username(username)
|
|
|
|
if not user:
|
|
flash(f"Usuario {username} no encontrado.", "danger")
|
|
return redirect(url_for("users.list_users"))
|
|
|
|
form = UserForm(obj=user)
|
|
|
|
if form.validate_on_submit():
|
|
# Validar datos
|
|
data = {
|
|
"nombre": form.nombre.data,
|
|
"email": form.email.data,
|
|
"password": form.password.data,
|
|
"nivel": form.nivel.data,
|
|
"idioma": form.idioma.data,
|
|
"empresa": form.empresa.data,
|
|
"estado": form.estado.data,
|
|
"fecha_caducidad": (
|
|
form.fecha_caducidad.data if form.fecha_caducidad.data else None
|
|
),
|
|
}
|
|
|
|
is_valid, errors = validate_user_data(data, is_new_user=False)
|
|
|
|
if not is_valid:
|
|
for field, error in errors.items():
|
|
flash(f"Error en {field}: {error}", "danger")
|
|
return render_template("users/edit.html", form=form, username=username)
|
|
|
|
# Actualizar usuario
|
|
success, message = update_user(username, data)
|
|
|
|
if success:
|
|
flash(message, "success")
|
|
|
|
# Registrar actividad
|
|
log_user_management(
|
|
admin_id=current_user.id,
|
|
target_user_id=username,
|
|
action="update",
|
|
details={"fields_updated": list(data.keys())},
|
|
)
|
|
|
|
return redirect(url_for("users.list_users"))
|
|
else:
|
|
flash(message, "danger")
|
|
|
|
return render_template("users/edit.html", form=form, username=username)
|
|
|
|
|
|
@users_bp.route("/delete/<username>", methods=["POST"])
|
|
@login_required
|
|
@permission_required(9000) # Solo administradores
|
|
def delete(username):
|
|
"""Eliminar usuario."""
|
|
if username == current_user.id:
|
|
flash("No puede eliminar su propio usuario.", "danger")
|
|
return redirect(url_for("users.list_users"))
|
|
|
|
if username == "admin":
|
|
flash("No se puede eliminar el usuario administrador.", "danger")
|
|
return redirect(url_for("users.list_users"))
|
|
|
|
success, message = delete_user(username)
|
|
|
|
if success:
|
|
flash(message, "success")
|
|
|
|
# Registrar actividad
|
|
log_user_management(
|
|
admin_id=current_user.id, target_user_id=username, action="delete"
|
|
)
|
|
else:
|
|
flash(message, "danger")
|
|
|
|
return redirect(url_for("users.list_users"))
|
|
|
|
|
|
# API para verificar disponibilidad de nombre de usuario
|
|
@users_bp.route("/api/check_username", methods=["POST"])
|
|
@login_required
|
|
@permission_required(9000) # Solo administradores
|
|
def api_check_username():
|
|
"""Verificar disponibilidad de nombre de usuario."""
|
|
data = request.get_json()
|
|
|
|
if not data or "username" not in data:
|
|
return jsonify({"error": "Se requiere nombre de usuario"}), 400
|
|
|
|
username = data["username"]
|
|
available = check_username_availability(username)
|
|
|
|
return jsonify({"available": available})
|