autocensup/core/models.py
Rogério Lima 36d3386d2d feat: protect manual edits from API overwrite, fix pending situacao, sort CRUD lists
- Add Pessoa/Vinculo.atualizado_manualmente flag, set automatically on web CRUD
  save; all Gennera/txt import paths now skip records flagged this way instead
  of overwriting them
- Vinculo import: finalize step corrects leftover "situacao nao informada"
  vinculos to "Desvinculado", pulling carga horaria from censup_carga_horaria
- Generic CRUD list view: click a column header to sort (asc/desc), persists
  through pagination; FK columns sort by their related display field
- Rename GENNERA_API_BASE_URL setting to GENNERA_LOCAL_BASE_URL

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-06 15:39:36 -03:00

417 lines
15 KiB
Python

from django.db import models
class AnoCenso(models.Model):
ano = models.PositiveSmallIntegerField(primary_key=True)
class Meta:
verbose_name = "ano do censo"
verbose_name_plural = "anos do censo"
ordering = ["-ano"]
@property
def ano_execucao(self) -> int:
return self.ano + 1
def __str__(self) -> str:
return str(self.ano)
class UF(models.Model):
codigo = models.PositiveSmallIntegerField(primary_key=True)
nome = models.CharField(max_length=100, unique=True)
class Meta:
verbose_name = "UF"
verbose_name_plural = "UFs"
ordering = ["nome"]
def __str__(self) -> str:
return self.nome
class Pais(models.Model):
codigo = models.CharField(max_length=3, primary_key=True)
nome = models.CharField(max_length=100, unique=True)
class Meta:
verbose_name = "pais"
verbose_name_plural = "paises"
ordering = ["nome"]
def __str__(self) -> str:
return self.nome
class Municipio(models.Model):
codigo = models.PositiveIntegerField(primary_key=True)
nome = models.CharField(max_length=150)
uf = models.ForeignKey(UF, on_delete=models.PROTECT, related_name="municipios")
class Meta:
verbose_name = "municipio"
verbose_name_plural = "municipios"
ordering = ["nome"]
indexes = [
models.Index(fields=["nome"]),
models.Index(fields=["uf", "nome"]),
]
def __str__(self) -> str:
return f"{self.nome} - {self.uf.nome}"
class Curso(models.Model):
id = models.BigAutoField(primary_key=True)
codigo_mec = models.PositiveIntegerField(unique=True)
descricao = models.CharField(max_length=200)
situacao_funcionamento_curso = models.CharField(max_length=100, blank=True)
licenciatura = models.BooleanField(default=False)
ead = models.BooleanField(default=False)
carga_horaria_total = models.PositiveIntegerField()
polo = models.ForeignKey(
"Polo",
on_delete=models.PROTECT,
null=True,
blank=True,
related_name="cursos",
)
class Meta:
verbose_name = "curso"
verbose_name_plural = "cursos"
ordering = ["descricao"]
def __str__(self) -> str:
return self.descricao
class FallbackCurso(models.Model):
texto_api = models.CharField(max_length=200, unique=True)
texto_local = models.CharField(max_length=200)
class Meta:
verbose_name = "fallback de curso"
verbose_name_plural = "fallbacks de curso"
ordering = ["texto_api"]
indexes = [
models.Index(fields=["texto_api"]),
models.Index(fields=["texto_local"]),
]
def __str__(self) -> str:
return f"{self.texto_api} -> {self.texto_local}"
class Instituicao(models.Model):
codigo_inep = models.PositiveBigIntegerField(primary_key=True)
nome = models.CharField(max_length=200, blank=True)
class Meta:
verbose_name = "instituicao"
verbose_name_plural = "instituicoes"
ordering = ["nome", "codigo_inep"]
def __str__(self) -> str:
return self.nome or str(self.codigo_inep)
class Polo(models.Model):
codigo = models.PositiveBigIntegerField(unique=True)
descricao = models.CharField(max_length=200)
class Meta:
verbose_name = "polo"
verbose_name_plural = "polos"
ordering = ["descricao", "codigo"]
def __str__(self) -> str:
return self.descricao
class Pessoa(models.Model):
class CorRaca(models.IntegerChoices):
NAO_DECLARADA = 0, "Aluno nao quis declarar cor/raca"
BRANCA = 1, "Branca"
PRETA = 2, "Preta"
PARDA = 3, "Parda"
AMARELA = 4, "Amarela"
INDIGENA = 5, "Indigena"
NAO_DISPOE = 6, "Nao dispoe da informacao"
class Nacionalidade(models.IntegerChoices):
BRASILEIRA_NATA = 1, "Brasileira nata"
BRASILEIRA_NATURALIZADA = 2, "Brasileira por naturalizacao"
ESTRANGEIRA = 3, "Estrangeira"
class SimNaoNaoDispoe(models.IntegerChoices):
NAO = 0, "Nao"
SIM = 1, "Sim"
NAO_DISPOE = 2, "Nao dispoe da informacao"
class TipoEscolaEnsinoMedio(models.IntegerChoices):
PRIVADA = 0, "Privado"
PUBLICA = 1, "Publico"
NAO_DISPOE = 2, "Nao dispoe da informacao"
id_aluno_inep = models.PositiveBigIntegerField(null=True, blank=True)
nome = models.CharField(max_length=120)
cpf = models.CharField(max_length=11, unique=True)
documento_estrangeiro = models.CharField(max_length=20, blank=True)
data_nascimento = models.DateField(null=True, blank=True)
cor_raca = models.PositiveSmallIntegerField(choices=CorRaca.choices)
nacionalidade = models.PositiveSmallIntegerField(choices=Nacionalidade.choices)
uf_nascimento = models.ForeignKey(
UF,
on_delete=models.PROTECT,
null=True,
blank=True,
related_name="pessoas_nascidas",
)
municipio_nascimento = models.ForeignKey(
Municipio,
on_delete=models.PROTECT,
null=True,
blank=True,
related_name="pessoas_nascidas",
)
pais_origem = models.ForeignKey(
Pais,
on_delete=models.PROTECT,
related_name="pessoas_originarias",
)
aluno_com_deficiencia = models.PositiveSmallIntegerField(
choices=SimNaoNaoDispoe.choices
)
deficiencia_cegueira = models.BooleanField(null=True, blank=True)
deficiencia_baixa_visao = models.BooleanField(null=True, blank=True)
deficiencia_visao_monocular = models.BooleanField(null=True, blank=True)
deficiencia_surdez = models.BooleanField(null=True, blank=True)
deficiencia_auditiva = models.BooleanField(null=True, blank=True)
deficiencia_fisica = models.BooleanField(null=True, blank=True)
deficiencia_surdocegueira = models.BooleanField(null=True, blank=True)
deficiencia_intelectual = models.BooleanField(null=True, blank=True)
deficiencia_tea = models.BooleanField(null=True, blank=True)
deficiencia_altas_habilidades = models.BooleanField(null=True, blank=True)
tipo_escola_ensino_medio = models.PositiveSmallIntegerField(
choices=TipoEscolaEnsinoMedio.choices
)
anos_censo = models.ManyToManyField(
AnoCenso,
blank=True,
related_name="pessoas",
)
atualizado_manualmente = models.BooleanField(default=False)
class Meta:
verbose_name = "pessoa"
verbose_name_plural = "pessoas"
ordering = ["nome"]
indexes = [
models.Index(fields=["nome"]),
models.Index(fields=["cpf"]),
]
def __str__(self) -> str:
return self.nome
class Vinculo(models.Model):
class PeriodoReferencia(models.IntegerChoices):
PRIMEIRO_SEMESTRE = 1, "Primeiro semestre"
SEGUNDO_SEMESTRE = 2, "Segundo semestre"
class Turno(models.IntegerChoices):
MATUTINO = 1, "Matutino"
VESPERTINO = 2, "Vespertino"
NOTURNO = 3, "Noturno"
INTEGRAL = 4, "Integral"
class Situacao(models.IntegerChoices):
NAO_INFORMADA = 0, "Nao informada"
CURSANDO = 2, "Cursando"
MATRICULA_TRANCADA = 3, "Matricula trancada"
DESVINCULADO = 4, "Desvinculado do curso"
TRANSFERENCIA_INTERNA = 5, "Transferencia interna"
FORMADO = 6, "Formado"
FALECIDO = 7, "Falecido"
class TipoSegundaLicenciatura(models.IntegerChoices):
SEGUNDA_LICENCIATURA = 1, "Segunda licenciatura"
FORMACAO_PEDAGOGICA = 2, "Formacao pedagogica"
class TipoMobilidade(models.IntegerChoices):
NACIONAL = 1, "Nacional"
INTERNACIONAL = 2, "Internacional"
class TipoPoliticaAcaoAfirmativa(models.IntegerChoices):
LEI_COTAS = 1, "Reserva de vaga - Lei de Cotas"
OUTRAS = 2, "Outras reservas de vagas ou programa diferenciado"
pessoa = models.ForeignKey(Pessoa, on_delete=models.CASCADE, related_name="vinculos")
ano_censo = models.ForeignKey(
AnoCenso,
on_delete=models.PROTECT,
default=2025,
related_name="vinculos",
)
matricula = models.CharField(
"matricula",
max_length=20,
blank=True,
db_column="id_aluno_ies",
)
periodo_referencia = models.PositiveSmallIntegerField(
choices=PeriodoReferencia.choices,
null=True,
blank=True,
)
curso = models.ForeignKey(
Curso,
on_delete=models.PROTECT,
to_field="codigo_mec",
related_name="vinculos",
)
polo = models.ForeignKey(
Polo,
on_delete=models.PROTECT,
null=True,
blank=True,
related_name="vinculos",
)
turno = models.PositiveSmallIntegerField(choices=Turno.choices, null=True, blank=True)
situacao = models.PositiveSmallIntegerField(choices=Situacao.choices)
curso_origem = models.ForeignKey(
Curso,
on_delete=models.PROTECT,
to_field="codigo_mec",
null=True,
blank=True,
related_name="vinculos_originados",
)
semestre_conclusao = models.PositiveSmallIntegerField(
choices=PeriodoReferencia.choices,
null=True,
blank=True,
)
aluno_parfor = models.BooleanField(null=True, blank=True)
segunda_licenciatura_formacao_pedagogica = models.BooleanField(null=True, blank=True)
tipo_segunda_licenciatura_formacao_pedagogica = models.PositiveSmallIntegerField(
choices=TipoSegundaLicenciatura.choices,
null=True,
blank=True,
)
semestre_ingresso = models.CharField(max_length=6)
ingresso_vestibular = models.BooleanField()
ingresso_enem = models.BooleanField()
ingresso_avaliacao_seriada = models.BooleanField()
ingresso_selecao_simplificada = models.BooleanField()
ingresso_egresso_bi_li = models.BooleanField()
ingresso_pec_g = models.BooleanField()
ingresso_transferencia_ex_officio = models.BooleanField()
ingresso_decisao_judicial = models.BooleanField()
ingresso_vagas_remanescentes = models.BooleanField()
ingresso_programas_especiais = models.BooleanField()
mobilidade_academica = models.BooleanField(null=True, blank=True)
tipo_mobilidade_academica = models.PositiveSmallIntegerField(
choices=TipoMobilidade.choices,
null=True,
blank=True,
)
mobilidade_ies_destino = models.ForeignKey(
Instituicao,
on_delete=models.PROTECT,
null=True,
blank=True,
related_name="vinculos_mobilidade_destino",
)
mobilidade_pais_destino = models.ForeignKey(
Pais,
on_delete=models.PROTECT,
null=True,
blank=True,
related_name="vinculos_mobilidade_destino",
)
financiamento_estudantil = models.BooleanField(null=True, blank=True)
financiamento_fies = models.BooleanField(null=True, blank=True)
financiamento_governo_estadual = models.BooleanField(null=True, blank=True)
financiamento_governo_municipal = models.BooleanField(null=True, blank=True)
financiamento_ies = models.BooleanField(null=True, blank=True)
financiamento_entidades_externas = models.BooleanField(null=True, blank=True)
financiamento_prouni_integral = models.BooleanField(null=True, blank=True)
financiamento_prouni_parcial = models.BooleanField(null=True, blank=True)
financiamento_nao_reembolsavel_entidades_externas = models.BooleanField(
null=True,
blank=True,
)
financiamento_nao_reembolsavel_governo_estadual = models.BooleanField(
null=True,
blank=True,
)
financiamento_nao_reembolsavel_ies = models.BooleanField(null=True, blank=True)
financiamento_nao_reembolsavel_governo_municipal = models.BooleanField(
null=True,
blank=True,
)
apoio_social = models.BooleanField()
apoio_alimentacao = models.BooleanField(null=True, blank=True)
apoio_moradia = models.BooleanField(null=True, blank=True)
apoio_transporte = models.BooleanField(null=True, blank=True)
apoio_material_didatico = models.BooleanField(null=True, blank=True)
apoio_bolsa_trabalho = models.BooleanField(null=True, blank=True)
apoio_bolsa_permanencia = models.BooleanField(null=True, blank=True)
atividade_extracurricular = models.BooleanField()
atividade_pesquisa = models.BooleanField(null=True, blank=True)
bolsa_pesquisa = models.BooleanField(null=True, blank=True)
atividade_extensao = models.BooleanField(null=True, blank=True)
bolsa_extensao = models.BooleanField(null=True, blank=True)
atividade_monitoria = models.BooleanField(null=True, blank=True)
bolsa_monitoria = models.BooleanField(null=True, blank=True)
atividade_estagio_nao_obrigatorio = models.BooleanField(null=True, blank=True)
bolsa_estagio_nao_obrigatorio = models.BooleanField(null=True, blank=True)
carga_horaria_total_aluno = models.PositiveIntegerField()
carga_horaria_integralizada = models.PositiveIntegerField()
ingressou_acao_afirmativa = models.BooleanField()
tipo_politica_acao_afirmativa = models.PositiveSmallIntegerField(
choices=TipoPoliticaAcaoAfirmativa.choices,
null=True,
blank=True,
)
tipo_acao_afirmativa_lei_cotas = models.PositiveSmallIntegerField(null=True, blank=True)
reserva_ppi = models.BooleanField(null=True, blank=True)
reserva_renda = models.BooleanField(null=True, blank=True)
reserva_pessoa_com_deficiencia = models.BooleanField(null=True, blank=True)
reserva_escola_publica = models.BooleanField(null=True, blank=True)
reserva_quilombola = models.BooleanField(null=True, blank=True)
reserva_transgenero_travesti = models.BooleanField(null=True, blank=True)
reserva_estudante_internacional = models.BooleanField(null=True, blank=True)
reserva_refugiado_apatrida_visto_humanitario = models.BooleanField(
null=True,
blank=True,
)
reserva_idoso = models.BooleanField(null=True, blank=True)
reserva_povos_comunidades_tradicionais = models.BooleanField(null=True, blank=True)
reserva_olimpiadas_conhecimento = models.BooleanField(null=True, blank=True)
reserva_outros = models.BooleanField(null=True, blank=True)
justificativa = models.PositiveSmallIntegerField(null=True, blank=True)
atualizado_manualmente = models.BooleanField(default=False)
class Meta:
verbose_name = "vinculo"
verbose_name_plural = "vinculos"
ordering = ["pessoa__nome", "curso__descricao"]
constraints = [
models.UniqueConstraint(
fields=["pessoa", "curso", "ano_censo"],
name="unique_vinculo_pessoa_curso_ano",
),
]
indexes = [
models.Index(fields=["ano_censo"]),
models.Index(fields=["situacao"]),
models.Index(fields=["semestre_ingresso"]),
]
def __str__(self) -> str:
return f"{self.pessoa} - {self.curso}"