Files
controles/controllers/militante_controller.py
LS bb9d7cf8a6 fix: compatibilidade Python 3.14 - Pillow e SQLAlchemy
- Pillow: 10.0.1 -> >=10.4.0 (fix KeyError __version__ no build)
- SQLAlchemy: 2.0.21 -> >=2.0.36 (fix AssertionError TypingOnly)
- Remove import não utilizado safe_rollback em militante_controller

Made-with: Cursor
2026-03-05 21:35:33 -03:00

365 lines
15 KiB
Python

from flask import Blueprint, request, render_template, redirect, url_for, flash, jsonify
from functions.database import get_db_session, Militante, EmailMilitante, Endereco, Celula, Setor, ComiteRegional
from functions.decorators import require_login
from functions.validations import validar_cpf
from functions.rbac import Permission
from utils.date_utils import validar_data, converter_data, calcular_idade
from datetime import datetime
from sqlalchemy.orm import joinedload
from flask_login import current_user
militante_bp = Blueprint('militante', __name__)
@militante_bp.route("/militantes/criar", methods=["POST"])
@require_login
def criar():
"""Cria um novo militante"""
db = get_db_session()
try:
data = request.get_json()
# Validações básicas
if not data.get('nome') or not data.get('cpf'):
return jsonify({
'status': 'error',
'message': 'Nome e CPF são obrigatórios'
}), 400
if not validar_cpf(data['cpf']):
return jsonify({
'status': 'error',
'message': 'CPF inválido'
}), 400
# Verificar se CPF já existe
if db.query(Militante).filter_by(cpf=data['cpf']).first():
return jsonify({
'status': 'error',
'message': 'CPF já cadastrado'
}), 400
# Criar endereço se fornecido
endereco_id = None
if data.get('endereco'):
endereco = Endereco(**data['endereco'])
db.add(endereco)
db.flush()
endereco_id = endereco.id
# Criar militante
militante = Militante(
nome=data['nome'],
cpf=data['cpf'],
titulo_eleitoral=data.get('titulo_eleitoral'),
data_nascimento=converter_data(data.get('data_nascimento')) if data.get('data_nascimento') else None,
data_entrada_oci=converter_data(data.get('data_entrada_oci')) if data.get('data_entrada_oci') else None,
data_efetivacao_oci=converter_data(data.get('data_efetivacao_oci')) if data.get('data_efetivacao_oci') else None,
telefone1=data.get('telefone1'),
telefone2=data.get('telefone2'),
profissao=data.get('profissao'),
regime_trabalho=data.get('regime_trabalho'),
empresa=data.get('empresa'),
contratante=data.get('contratante'),
instituicao_ensino=data.get('instituicao_ensino'),
tipo_instituicao=data.get('tipo_instituicao'),
sindicato=data.get('sindicato'),
cargo_sindical=data.get('cargo_sindical'),
dirigente_sindical=data.get('dirigente_sindical', False),
central_sindical=data.get('central_sindical'),
endereco_id=endereco_id,
celula_id=data.get('celula_id'),
registrado_por=current_user.id
)
db.add(militante)
db.flush()
# Criar email se fornecido
if data.get('email'):
email = EmailMilitante(
militante_id=militante.id,
endereco_email=data['email']
)
db.add(email)
db.commit()
return jsonify({
'status': 'success',
'message': 'Militante criado com sucesso',
'militante_id': militante.id
})
except Exception as e:
db.rollback()
return jsonify({
'status': 'error',
'message': f'Erro ao criar militante: {str(e)}'
}), 500
finally:
db.close()
@militante_bp.route("/militantes")
@require_login
def listar():
"""Lista todos os militantes com controle de permissões no nível de dados"""
db = get_db_session()
try:
# SEMPRE renderizar o template, mas filtrar os dados baseado nas permissões
militantes = []
# Verificar permissões para filtrar dados
if current_user.is_admin:
# Admin vê todos
militantes = db.query(Militante).options(
joinedload(Militante.emails),
joinedload(Militante.endereco),
joinedload(Militante.celula)
).order_by(Militante.nome).all()
elif hasattr(current_user, 'has_permission'):
if current_user.has_permission(Permission.VIEW_CC_REPORTS):
# CC vê todos
militantes = db.query(Militante).options(
joinedload(Militante.emails),
joinedload(Militante.endereco),
joinedload(Militante.celula)
).order_by(Militante.nome).all()
elif current_user.has_permission(Permission.VIEW_CR_REPORTS):
# CR vê do seu CR
if hasattr(current_user, 'cr_id') and current_user.cr_id:
militantes = db.query(Militante).join(Celula).join(Setor).filter(
Setor.cr_id == current_user.cr_id
).options(
joinedload(Militante.emails),
joinedload(Militante.endereco),
joinedload(Militante.celula)
).order_by(Militante.nome).all()
elif current_user.has_permission(Permission.VIEW_SECTOR_REPORTS):
# Setor vê do seu setor
if hasattr(current_user, 'setor_id') and current_user.setor_id:
militantes = db.query(Militante).join(Celula).filter(
Celula.setor_id == current_user.setor_id
).options(
joinedload(Militante.emails),
joinedload(Militante.endereco),
joinedload(Militante.celula)
).order_by(Militante.nome).all()
elif current_user.has_permission(Permission.VIEW_CELL_DATA):
# Célula vê da sua célula
if hasattr(current_user, 'celula_id') and current_user.celula_id:
militantes = db.query(Militante).filter(
Militante.celula_id == current_user.celula_id
).options(
joinedload(Militante.emails),
joinedload(Militante.endereco),
joinedload(Militante.celula)
).order_by(Militante.nome).all()
# Buscar dados auxiliares para o template
celulas = db.query(Celula).all()
setores = db.query(Setor).all()
# SEMPRE renderizar o template, independente das permissões
# O controle é feito no nível dos dados, não do template
return render_template('listar_militantes.html',
militantes=militantes,
Militante=Militante,
celulas=celulas,
setores=setores)
except Exception as e:
print(f"Erro no controller de militantes: {e}")
# Em caso de erro, renderizar com dados vazios
return render_template('listar_militantes.html',
militantes=[],
Militante=Militante,
celulas=[],
setores=[])
finally:
db.close()
@militante_bp.route("/militantes/excluir/<int:id>", methods=["POST"])
@require_login
def excluir(id):
"""Exclui um militante"""
db = get_db_session()
try:
militante = db.query(Militante).get(id)
if not militante:
flash('Militante não encontrado.', 'danger')
return redirect(url_for('militante.listar'))
# Verificar permissões
if not current_user.has_permission('gerenciar_militantes'):
flash('Você não tem permissão para excluir militantes.', 'danger')
return redirect(url_for('militante.listar'))
db.delete(militante)
db.commit()
flash('Militante excluído com sucesso!', 'success')
except Exception as e:
db.rollback()
flash('Erro ao excluir militante.', 'danger')
print(f"Erro ao excluir militante: {e}")
finally:
db.close()
return redirect(url_for('militante.listar'))
@militante_bp.route('/militantes/editar/<int:militante_id>', methods=['POST'])
@require_login
def editar(militante_id):
"""Edita um militante existente"""
db = get_db_session()
try:
data = request.get_json()
militante = db.query(Militante).get(militante_id)
if not militante:
return jsonify({
'status': 'error',
'message': 'Militante não encontrado'
}), 404
# Atualizar dados básicos
militante.nome = data.get('nome', militante.nome)
militante.cpf = data.get('cpf', militante.cpf)
militante.titulo_eleitoral = data.get('titulo_eleitoral', militante.titulo_eleitoral)
militante.telefone1 = data.get('telefone1', militante.telefone1)
militante.telefone2 = data.get('telefone2', militante.telefone2)
militante.profissao = data.get('profissao', militante.profissao)
militante.regime_trabalho = data.get('regime_trabalho', militante.regime_trabalho)
militante.empresa = data.get('empresa', militante.empresa)
militante.contratante = data.get('contratante', militante.contratante)
militante.instituicao_ensino = data.get('instituicao_ensino', militante.instituicao_ensino)
militante.tipo_instituicao = data.get('tipo_instituicao', militante.tipo_instituicao)
militante.sindicato = data.get('sindicato', militante.sindicato)
militante.cargo_sindical = data.get('cargo_sindical', militante.cargo_sindical)
militante.dirigente_sindical = data.get('dirigente_sindical', militante.dirigente_sindical)
militante.central_sindical = data.get('central_sindical', militante.central_sindical)
# Atualizar datas
if data.get('data_nascimento'):
militante.data_nascimento = converter_data(data['data_nascimento'])
if data.get('data_entrada_oci'):
militante.data_entrada_oci = converter_data(data['data_entrada_oci'])
if data.get('data_efetivacao_oci'):
militante.data_efetivacao_oci = converter_data(data['data_efetivacao_oci'])
# Atualizar endereço
if data.get('endereco') and militante.endereco:
endereco = militante.endereco
endereco.cep = data['endereco'].get('cep', endereco.cep)
endereco.estado = data['endereco'].get('estado', endereco.estado)
endereco.cidade = data['endereco'].get('cidade', endereco.cidade)
endereco.bairro = data['endereco'].get('bairro', endereco.bairro)
endereco.rua = data['endereco'].get('rua', endereco.rua)
endereco.numero = data['endereco'].get('numero', endereco.numero)
endereco.complemento = data['endereco'].get('complemento', endereco.complemento)
# Atualizar email
if data.get('email') and militante.emails:
militante.emails[0].endereco_email = data['email']
db.commit()
return jsonify({
'status': 'success',
'message': 'Militante atualizado com sucesso'
})
except Exception as e:
db.rollback()
return jsonify({
'status': 'error',
'message': f'Erro ao atualizar militante: {str(e)}'
}), 500
finally:
db.close()
@militante_bp.route("/militantes/dados/<int:militante_id>")
@require_login
def buscar_dados(militante_id):
"""Busca os dados de um militante específico"""
db = get_db_session()
try:
militante = db.query(Militante).options(
joinedload(Militante.emails),
joinedload(Militante.endereco)
).get(militante_id)
if not militante:
return jsonify({
'status': 'error',
'message': 'Militante não encontrado'
}), 404
# Função auxiliar para formatar data com validação
def formatar_data_segura(data):
try:
if not data:
return None
return data.strftime('%Y-%m-%d')
except Exception as e:
print(f"Erro ao formatar data: {str(e)}, valor: {data}")
return None
# Preparar dados para retorno
dados = {
'id': militante.id,
'nome': militante.nome,
'cpf': militante.cpf,
'titulo_eleitoral': militante.titulo_eleitoral,
'data_nascimento': formatar_data_segura(militante.data_nascimento),
'data_entrada_oci': formatar_data_segura(militante.data_entrada_oci),
'data_efetivacao_oci': formatar_data_segura(militante.data_efetivacao_oci),
'telefone1': militante.telefone1,
'telefone2': militante.telefone2,
'profissao': militante.profissao,
'regime_trabalho': militante.regime_trabalho,
'empresa': militante.empresa,
'contratante': militante.contratante,
'instituicao_ensino': militante.instituicao_ensino,
'tipo_instituicao': militante.tipo_instituicao,
'sindicato': militante.sindicato,
'cargo_sindical': militante.cargo_sindical,
'dirigente_sindical': militante.dirigente_sindical,
'central_sindical': militante.central_sindical,
'responsabilidades': militante.responsabilidades,
'estado': militante.estado.value if militante.estado else None,
'celula_id': militante.celula_id,
'email': militante.emails[0].endereco_email if militante.emails else None,
'endereco': {
'cep': militante.endereco.cep if militante.endereco else None,
'estado': militante.endereco.estado if militante.endereco else None,
'cidade': militante.endereco.cidade if militante.endereco else None,
'bairro': militante.endereco.bairro if militante.endereco else None,
'rua': militante.endereco.rua if militante.endereco else None,
'numero': militante.endereco.numero if militante.endereco else None,
'complemento': militante.endereco.complemento if militante.endereco else None
} if militante.endereco else None
}
return jsonify({
'status': 'success',
'data': dados
})
except Exception as e:
return jsonify({
'status': 'error',
'message': f'Erro ao buscar dados: {str(e)}'
}), 500
finally:
db.close()
@militante_bp.route("/api/setores/<int:cr_id>")
@require_login
def get_setores(cr_id):
"""Retorna setores de um CR específico"""
db = get_db_session()
try:
setores = db.query(Setor).filter_by(cr_id=cr_id).all()
return jsonify([{'id': s.id, 'nome': s.nome} for s in setores])
finally:
db.close()