BREAKING CHANGES: - Sistema de permissões movido do nível de template para nível de dados - Menus sempre visíveis, controle transparente no backend - Templates nunca quebram, sempre renderizam com dados filtrados Features: - ✅ Arquitetura MVC completa implementada - ✅ Controllers com filtragem hierárquica de dados - ✅ Template helpers simplificados (user_can sempre True) - ✅ Controle de acesso baseado na hierarquia organizacional - ✅ Regra especial para tesoureiros (acesso completo) - ✅ Tratamento robusto de erros em todos os controllers Controllers implementados: - militante_controller.py - Filtragem por célula/setor/CR/CC - cota_controller.py - Controle baseado em permissões - material_controller.py - Acesso flexível por nível - pagamento_controller.py - Filtragem organizacional - auth_controller.py - Autenticação com OTP - home_controller.py - Dashboard com estatísticas - usuario_controller.py - Gestão de usuários Templates corrigidos: - listar_cotas.html - URLs corrigidas (nova_cota → cota.nova) - listar_tipos_materiais.html - Variáveis ajustadas (tipos → tipos_materiais) - base.html - Menus sempre visíveis - Diversos templates com correções de URLs e referências Services implementados: - auth_service.py - Lógica de autenticação - dashboard_service.py - Estatísticas do dashboard - cache_service.py - Integração com Redis - celula_service.py - Operações de células Models implementados: - militante_model.py - Operações de militantes - pagamento_model.py - Operações de pagamentos Documentação: - docs/permission_fixes_summary.md - Resumo completo das correções - docs/architecture_summary.md - Arquitetura MVC - docs/mvc_refactoring.md - Detalhes da refatoração - docs/permission_strategy.md - Estratégia de permissões - docs/redis_cache_setup.md - Setup do cache Redis - README.md atualizado com nova arquitetura Testes: - test_menu_navigation.py - Testes unitários de navegação - test_integration_menu.py - Testes de integração com Selenium Status dos testes: ✅ Funcionais: /, /dashboard, /pagamentos, /materiais ❌ Com problemas: /militantes, /cotas, /tipos-materiais, /admin/dashboard Hierarquia de permissões implementada: Admin → Acesso total CC → Acesso total CR → Dados do CR Setor → Dados do setor Célula → Dados da célula Próximos passos identificados: - Corrigir referências a Militante indefinido nos templates - Resolver problemas de campos inexistentes - Corrigir roteamento admin
199 lines
6.6 KiB
Python
199 lines
6.6 KiB
Python
from flask import Flask
|
|
from flask_bootstrap5 import Bootstrap
|
|
from flask_login import LoginManager
|
|
from flask_wtf.csrf import CSRFProtect
|
|
from flask_mail import Mail
|
|
from functions.database import get_db_connection, Usuario
|
|
from functions.rbac import init_rbac
|
|
from functions.template_helpers import permission_context_processor, init_template_filters, safe_render_helper
|
|
from sqlalchemy.orm import joinedload
|
|
import os
|
|
import secrets
|
|
from dotenv import load_dotenv
|
|
import sys
|
|
import logging
|
|
from logging.handlers import RotatingFileHandler
|
|
import time
|
|
|
|
# Importar blueprints
|
|
from controllers.auth_controller import auth_bp
|
|
from controllers.home_controller import home_bp
|
|
from controllers.militante_controller import militante_bp
|
|
from controllers.pagamento_controller import pagamento_bp
|
|
from controllers.cota_controller import cota_bp
|
|
from controllers.usuario_controller import usuario_bp
|
|
from controllers.material_controller import material_bp
|
|
from routes.admin import admin_bp
|
|
|
|
# Import cache service
|
|
from services.cache_service import cache_service
|
|
|
|
load_dotenv()
|
|
|
|
def setup_logging(app):
|
|
"""Configure logging for the application"""
|
|
if not app.debug and not app.testing:
|
|
# Create logs directory if it doesn't exist
|
|
if not os.path.exists('logs'):
|
|
os.mkdir('logs')
|
|
|
|
# File handler for general logs
|
|
file_handler = RotatingFileHandler('logs/controles.log', maxBytes=10240000, backupCount=10)
|
|
file_handler.setFormatter(logging.Formatter(
|
|
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
|
|
))
|
|
file_handler.setLevel(logging.INFO)
|
|
app.logger.addHandler(file_handler)
|
|
|
|
# File handler for cache logs
|
|
cache_handler = RotatingFileHandler('logs/cache.log', maxBytes=10240000, backupCount=5)
|
|
cache_handler.setFormatter(logging.Formatter(
|
|
'%(asctime)s %(levelname)s: %(message)s'
|
|
))
|
|
cache_handler.setLevel(logging.DEBUG)
|
|
|
|
# Create cache logger
|
|
cache_logger = logging.getLogger('services.cache_service')
|
|
cache_logger.addHandler(cache_handler)
|
|
cache_logger.setLevel(logging.DEBUG)
|
|
|
|
app.logger.setLevel(logging.INFO)
|
|
app.logger.info('Controles startup')
|
|
|
|
def create_app():
|
|
"""Cria e configura a aplicação Flask"""
|
|
app = Flask(__name__)
|
|
app.secret_key = os.getenv('SECRET_KEY', secrets.token_hex(16))
|
|
|
|
# Setup logging
|
|
setup_logging(app)
|
|
|
|
# Configurar Bootstrap
|
|
bootstrap = Bootstrap(app)
|
|
|
|
# Configurar CSRF Protection (desabilitado temporariamente)
|
|
# csrf = CSRFProtect()
|
|
# csrf.init_app(app)
|
|
|
|
# Configurar cabeçalhos CSRF personalizados
|
|
app.config['WTF_CSRF_CHECK_DEFAULT'] = False
|
|
app.config['WTF_CSRF_HEADERS'] = ['X-CSRFToken']
|
|
|
|
# Configurar Flask-Login
|
|
login_manager = LoginManager()
|
|
login_manager.init_app(app)
|
|
login_manager.login_view = 'auth.login'
|
|
|
|
# Configurar context processors e template helpers
|
|
app.context_processor(permission_context_processor)
|
|
app.context_processor(safe_render_helper)
|
|
|
|
# Inicializar filtros de template personalizados
|
|
init_template_filters(app)
|
|
|
|
# Adicionar filtros Jinja2
|
|
@app.template_filter('bitwise_and')
|
|
def bitwise_and(value1, value2):
|
|
"""Filtro para operação bit a bit AND"""
|
|
return value1 & value2
|
|
|
|
@login_manager.user_loader
|
|
def load_user(user_id):
|
|
"""Carrega o usuário pelo ID"""
|
|
db = get_db_connection()
|
|
try:
|
|
# Carregar o usuário com suas roles
|
|
user = db.query(Usuario).options(
|
|
joinedload(Usuario.roles)
|
|
).get(user_id)
|
|
return user
|
|
finally:
|
|
db.close()
|
|
|
|
# Configurar Flask-Mail
|
|
app.config['MAIL_SERVER'] = os.getenv('MAIL_SERVER', 'smtp.gmail.com')
|
|
app.config['MAIL_PORT'] = int(os.getenv('MAIL_PORT', 587))
|
|
app.config['MAIL_USE_TLS'] = os.getenv('MAIL_USE_TLS', 'True').lower() == 'true'
|
|
app.config['MAIL_USERNAME'] = os.getenv('MAIL_USERNAME')
|
|
app.config['MAIL_PASSWORD'] = os.getenv('MAIL_PASSWORD')
|
|
app.config['MAIL_DEFAULT_SENDER'] = os.getenv('MAIL_DEFAULT_SENDER')
|
|
|
|
mail = Mail(app)
|
|
|
|
# Initialize Redis cache
|
|
try:
|
|
redis_url = os.getenv('REDIS_URL', 'redis://localhost:6379/0')
|
|
app.logger.info(f"Initializing Redis cache with URL: {redis_url}")
|
|
|
|
# Test cache connection with retry
|
|
max_retries = 5
|
|
retry_delay = 2
|
|
|
|
for attempt in range(max_retries):
|
|
try:
|
|
if cache_service._is_connected():
|
|
app.logger.info("Redis cache connection successful")
|
|
break
|
|
else:
|
|
app.logger.warning(f"Redis cache connection attempt {attempt + 1} failed")
|
|
if attempt < max_retries - 1:
|
|
time.sleep(retry_delay)
|
|
retry_delay *= 2 # Exponential backoff
|
|
except Exception as e:
|
|
app.logger.warning(f"Redis cache connection attempt {attempt + 1} failed: {e}")
|
|
if attempt < max_retries - 1:
|
|
time.sleep(retry_delay)
|
|
retry_delay *= 2
|
|
else:
|
|
app.logger.warning("Redis cache connection failed after all retries - continuing without cache")
|
|
except Exception as e:
|
|
app.logger.error(f"Error initializing Redis cache: {e}")
|
|
app.logger.info("Application will continue without Redis cache")
|
|
|
|
# Registrar blueprints
|
|
app.register_blueprint(auth_bp)
|
|
app.register_blueprint(home_bp)
|
|
app.register_blueprint(militante_bp)
|
|
app.register_blueprint(pagamento_bp)
|
|
app.register_blueprint(cota_bp)
|
|
app.register_blueprint(usuario_bp)
|
|
app.register_blueprint(material_bp)
|
|
app.register_blueprint(admin_bp)
|
|
|
|
return app
|
|
|
|
def init_system():
|
|
"""Inicializa o sistema"""
|
|
print("Inicializando sistema...")
|
|
|
|
# Inicializar RBAC
|
|
print("Inicializando RBAC...")
|
|
init_rbac()
|
|
|
|
# Criar usuário admin se não existir
|
|
from create_admin import create_admin_user
|
|
print("Criando usuário admin...")
|
|
create_admin_user()
|
|
|
|
print("Sistema inicializado com sucesso!")
|
|
|
|
def main():
|
|
"""Função principal"""
|
|
# Criar a aplicação
|
|
app = create_app()
|
|
return app
|
|
|
|
# Criar a aplicação usando a função main
|
|
app = main()
|
|
|
|
if __name__ == '__main__':
|
|
# Verificar se é para inicializar o sistema
|
|
if '--init' in sys.argv:
|
|
init_system()
|
|
else:
|
|
app.run(
|
|
host='0.0.0.0',
|
|
port=5000,
|
|
debug=os.getenv('FLASK_ENV') == 'development'
|
|
)
|