feat: gestion RBAC granular, importacion sheets, filtrado de aulas virtuales y pisos, capacidad infinita y traduccion

This commit is contained in:
Carlos Tello
2026-09-03 03:08:49 -03:00
parent d1d4be0428
commit 5cc33cc7a0
138 changed files with 14419 additions and 1490 deletions
+38 -9
View File
@@ -1,5 +1,6 @@
from flask import Blueprint, render_template, redirect, url_for, flash, request, session
from flask import Blueprint, render_template, redirect, url_for, flash, request, session, jsonify
from flask_login import login_user, logout_user, login_required, current_user
from flask_babel import gettext as _
from app import db
from app.models.user import User
from app.forms.auth import LoginForm
@@ -19,7 +20,7 @@ def login():
if user and user.check_password(form.password.data):
if not user.is_active:
flash('Your account has been deactivated. Please contact administrator.', 'error')
flash(_('Tu cuenta ha sido desactivada. Por favor contacta al administrador.'), 'error')
return render_template('auth/login.html', form=form)
login_user(user, remember=form.remember_me.data)
@@ -28,15 +29,20 @@ def login():
# Si el usuario tiene idioma configurado en su perfil, sobrescribe el navegador en toda la sesión
if user.preferred_language and user.preferred_language in ['es', 'en']:
session['language'] = user.preferred_language
else:
session.pop('language', None)
session.pop('login_lang_explicit', None)
next_page = request.args.get('next')
if not next_page or not next_page.startswith('/'):
next_page = url_for('main.dashboard')
flash(f'Welcome back, {user.first_name} {user.last_name}!', 'success')
user_full_name = f'{user.first_name} {user.last_name}'.strip() or user.name
flash(_('¡Bienvenido de nuevo, %(name)s!', name=user_full_name), 'success')
return redirect(next_page)
else:
flash('Invalid email or password.', 'error')
flash(_('Correo electrónico o contraseña inválidos.'), 'error')
return render_template('auth/login.html', form=form)
@@ -45,25 +51,48 @@ def login():
def logout():
"""Handle user logout"""
logout_user()
# Al cerrar sesión, se limpia el idioma para que el login use el del navegador por defecto
# Al cerrar sesión, se limpian las cookies de idioma para que el login detecte el navegador o use español
session.pop('language', None)
flash('You have been logged out successfully.', 'info')
session.pop('login_lang_explicit', None)
flash(_('Has cerrado sesión correctamente.'), 'info')
return redirect(url_for('auth.login'))
@auth_bp.route('/profile', methods=['GET', 'POST'])
@login_required
def profile():
"""User profile page"""
"""User profile page with language and theme settings"""
if request.method == 'POST':
lang = request.form.get('preferred_language')
if lang in ['es', 'en']:
current_user.preferred_language = lang
session['language'] = lang
db.session.commit()
flash('Idioma de preferencia actualizado correctamente.', 'success')
else:
# Cadena vacía o None restablece la detección automática del navegador
current_user.preferred_language = None
session.pop('language', None)
theme = request.form.get('theme_preference')
if theme in ['auto', 'light', 'dark']:
current_user.theme_preference = theme
db.session.commit()
flash(_('Preferencias de usuario actualizadas correctamente.'), 'success')
return redirect(url_for('auth.profile'))
return render_template('auth/profile.html')
@auth_bp.route('/api/user/theme', methods=['POST'])
@login_required
def update_theme_api():
"""Update user theme preference via fetch/AJAX from navbar toggle"""
data = request.get_json(silent=True) or request.form
theme = data.get('theme') if data else None
if theme in ['auto', 'light', 'dark']:
current_user.theme_preference = theme
db.session.commit()
return jsonify({'status': 'success', 'theme': theme})
return jsonify({'status': 'error', 'message': 'Invalid theme'}), 400
@auth_bp.route('/change_password', methods=['GET', 'POST'])
@login_required