From 87c8c24c916690ee01e62e4d4fc4ce38ab4e2adb Mon Sep 17 00:00:00 2001 From: Marcio Mazza Date: Thu, 29 Mar 2018 14:00:32 -0300 Subject: [PATCH] =?UTF-8?q?Revisa=20e=20adiciona=20campos=20faltantes=20?= =?UTF-8?q?=C3=A0=20migra=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/base/legacy.yaml | 1 - sapl/comissoes/legacy.yaml | 1 - sapl/legacy/migracao_dados.py | 149 +++++++++++++++++++-------------- sapl/legacy/test_renames.py | 98 ++++++++++++++++------ sapl/materia/legacy.yaml | 9 +- sapl/materia/models.py | 6 -- sapl/norma/legacy.yaml | 1 - sapl/parlamentares/legacy.yaml | 4 - sapl/protocoloadm/legacy.yaml | 1 - sapl/sessao/legacy.yaml | 1 - 10 files changed, 162 insertions(+), 109 deletions(-) diff --git a/sapl/base/legacy.yaml b/sapl/base/legacy.yaml index a54c4ae06..2d866226e 100644 --- a/sapl/base/legacy.yaml +++ b/sapl/base/legacy.yaml @@ -5,4 +5,3 @@ Autor: nome: nom_autor cargo: des_cargo tipo: tip_autor - username: col_username diff --git a/sapl/comissoes/legacy.yaml b/sapl/comissoes/legacy.yaml index a1093c4d2..66ed8b060 100644 --- a/sapl/comissoes/legacy.yaml +++ b/sapl/comissoes/legacy.yaml @@ -25,7 +25,6 @@ Comissao: telefone_secretaria: num_tel_secretaria tipo: tip_comissao unidade_deliberativa: ind_unid_deliberativa - ativa: Periodo (PeriodoCompComissao): data_fim: dat_fim_periodo diff --git a/sapl/legacy/migracao_dados.py b/sapl/legacy/migracao_dados.py index 5b175aca4..6f740555b 100644 --- a/sapl/legacy/migracao_dados.py +++ b/sapl/legacy/migracao_dados.py @@ -4,6 +4,7 @@ from collections import OrderedDict, defaultdict from datetime import date from functools import lru_cache, partial from itertools import groupby +from operator import xor from subprocess import PIPE, call import pkg_resources @@ -70,8 +71,6 @@ for a1, s1 in name_sets: else: assert not s1.intersection(s2) -legacy_app = apps.get_app_config('legacy') - # RENAMES ################################################################### @@ -110,6 +109,21 @@ def get_renames(): return field_renames, model_renames + +field_renames, model_renames = get_renames() +legacy_app = apps.get_app_config('legacy') +models_novos_para_antigos = { + model: legacy_app.get_model(model_renames.get(model, model.__name__)) + for model in field_renames} +models_novos_para_antigos[Composicao] = models_novos_para_antigos[Participacao] + + +campos_novos_para_antigos = { + model._meta.get_field(nome_novo): nome_antigo + for model, renames in field_renames.items() + for nome_novo, nome_antigo in renames.items()} + + # MIGRATION ################################################################# @@ -124,22 +138,40 @@ def warn(tipo, msg, dados): print('CUIDADO! ' + msg.format(**dados)) +@lru_cache() +def get_pk_legado(tabela): + res = exec_legado( + 'show index from {} WHERE Key_name = "PRIMARY"'.format(tabela)) + return [r[4] for r in res] + + +@lru_cache() +def get_estrutura_legado(model): + model_legado = models_novos_para_antigos[model] + tabela_legado = model_legado._meta.db_table + campos_pk_legado = get_pk_legado(tabela_legado) + return model_legado, tabela_legado, campos_pk_legado + + class ForeignKeyFaltando(ObjectDoesNotExist): 'Uma FK aponta para um registro inexistente' - def __init__(self, field, value, label): + def __init__(self, field, valor, old): self.field = field - self.value = value - self.label = label + self.valor = valor + self.old = old - msg = 'FK [{field}] não encontrada para o valor {value} (em {model} / {label})' # noqa + msg = 'FK não encontrada para [{campo} = {valor}] (em {tabela} / pk = {pk})' # noqa @property def dados(self): - return OrderedDict((('field', self.field.name), - ('value', self.value), - ('model', self.field.model.__name__), - ('label', self.label))) + campo = campos_novos_para_antigos[self.field] + _, tabela, campos_pk = get_estrutura_legado(self.field.model) + pk = {c: getattr(self.old, c) for c in campos_pk} + return OrderedDict((('campo', campo), + ('valor', self.valor), + ('tabela', tabela), + ('pk', pk))) @lru_cache() @@ -148,18 +180,17 @@ def _get_all_ids_from_model(model): return set(model.objects.values_list('id', flat=True)) -def get_fk_related(field, value, label={}): - if value is None and field.null: +def get_fk_related(field, old, old_field_name): + valor = getattr(old, old_field_name) + if valor is None and field.null: return None - - # if field.related_model.objects.filter(id=value).exists(): - if value in _get_all_ids_from_model(field.related_model): - return value - elif value == 0 and field.null: + if valor in _get_all_ids_from_model(field.related_model): + return valor + elif valor == 0 and field.null: # consideramos zeros como nulos, se não está entre os ids anteriores return None else: - raise ForeignKeyFaltando(field=field, value=value, label=label) + raise ForeignKeyFaltando(field=field, valor=valor, old=old) def exec_sql(sql, db='default'): @@ -559,9 +590,16 @@ class Record: def iter_sql_records(tabela): - sql = 'select * from ' + tabela - if existe_coluna_no_legado(tabela, 'ind_excluido'): - sql += ' where ind_excluido != 1' + if tabela == 'despacho_inicial': + sql = ''' select cod_materia, cod_comissao from despacho_inicial + where ind_excluido <> 1 + group by cod_materia, cod_comissao + order by cod_materia, min(num_ordem) + ''' + else: + sql = 'select * from ' + tabela + if existe_coluna_no_legado(tabela, 'ind_excluido'): + sql += ' where ind_excluido <> 1' cursor = exec_legado(sql) fieldnames = [name[0] for name in cursor.description] for row in cursor.fetchall(): @@ -631,12 +669,6 @@ def reinicia_sequence(model, id): sequence_name, id)) -def get_pk_legado(tabela): - res = exec_legado( - 'show index from {} WHERE Key_name = "PRIMARY"'.format(tabela)) - return [r[4] for r in res] - - DIR_DADOS_MIGRACAO = Path('~/migracao_sapl/').expand() PATH_TABELA_TIMEZONES = DIR_DADOS_MIGRACAO.child('tabela_timezones.yaml') DIR_RESULTADOS = DIR_DADOS_MIGRACAO.child('resultados') @@ -650,7 +682,6 @@ yaml.add_representer(OrderedDict, dict_representer) class DataMigrator: def __init__(self): - self.field_renames, self.model_renames = get_renames() self.choice_valida = {} # configura timezone de migração @@ -665,22 +696,20 @@ class DataMigrator: else: self.timezone = get_timezone(municipio, uf) - def populate_renamed_fields(self, new, old, campos_pk_legado): - renames = self.field_renames[type(new)] + def populate_renamed_fields(self, new, old): + renames = field_renames[type(new)] for field in new._meta.fields: old_field_name = renames.get(field.name) - field_type = field.get_internal_type() if old_field_name: - old_value = getattr(old, old_field_name) + field_type = field.get_internal_type() if field_type == 'ForeignKey': - label = {c: getattr(old, c) for c in campos_pk_legado} fk_field_name = '{}_id'.format(field.name) - value = get_fk_related(field, old_value, label) + value = get_fk_related(field, old, old_field_name) setattr(new, fk_field_name, value) else: - value = old_value + value = getattr(old, old_field_name) if (field_type in ['CharField', 'TextField'] and value in [None, 'None']): @@ -733,6 +762,7 @@ class DataMigrator: self._do_migrate(obj) except Exception as e: ocorrencias['traceback'] = str(traceback.format_exc()) + raise e finally: # grava ocorrências arq_ocorrencias = dir_ocorrencias.child( @@ -748,7 +778,7 @@ class DataMigrator: def _do_migrate(self, obj): if isinstance(obj, AppConfig): models = [model for model in obj.models.values() - if model in self.field_renames] + if model in field_renames] if obj.label == 'materia': # Devido à referência TipoProposicao.tipo_conteudo_related @@ -775,10 +805,8 @@ class DataMigrator: def migrate_model(self, model): print('Migrando %s...' % model.__name__) - nome_model = self.model_renames.get(model, model.__name__) - model_legado = legacy_app.get_model(nome_model) - tabela_legado = model_legado._meta.db_table - campos_pk_legado = get_pk_legado(tabela_legado) + model_legado, tabela_legado, campos_pk_legado = \ + get_estrutura_legado(model) if len(campos_pk_legado) == 1: # a pk no legado tem um único campo @@ -808,7 +836,7 @@ class DataMigrator: for old in old_records: new = model() try: - self.populate_renamed_fields(new, old, campos_pk_legado) + self.populate_renamed_fields(new, old) if ajuste_antes_salvar: ajuste_antes_salvar(new, old) except ForeignKeyFaltando as e: @@ -833,7 +861,9 @@ class DataMigrator: for campo in campos_pk_legado)) # salva novos registros - model.objects.bulk_create(novos) + with reversion.create_revision(): + model.objects.bulk_create(novos) + reversion.set_comment('Objetos criados pela migração') if ajuste_depois_salvar: ajuste_depois_salvar() @@ -962,23 +992,14 @@ def adjust_parlamentar(new, old): def adjust_participacao(new, old): - composicao = Composicao() - composicao.comissao_id, composicao.periodo_id = [ - get_fk_related(Composicao._meta.get_field(name), - value, - {'composicao_comissao.cod_comp_comissao': old.pk}) - for name, value in (('comissao', old.cod_comissao), - ('periodo', old.cod_periodo_comp))] - # check if there is already an "equal" one in the db - already_created = Composicao.objects.filter( - comissao=composicao.comissao, periodo=composicao.periodo) - if already_created: - assert len(already_created) == 1 # we must never have made 2 copies - [composicao] = already_created - else: - with reversion.create_revision(): - composicao.save() - reversion.set_comment('Objeto criado pela migração') + comissao_id, periodo_id = [ + get_fk_related(Composicao._meta.get_field(name), old, old_field_name) + for name, old_field_name in (('comissao', 'cod_comissao'), + ('periodo', 'cod_periodo_comp'))] + with reversion.create_revision(): + composicao, _ = Composicao.objects.get_or_create( + comissao_id=comissao_id, periodo_id=periodo_id) + reversion.set_comment('Objeto criado pela migração') new.composicao = composicao @@ -988,9 +1009,8 @@ def adjust_proposicao_antes_salvar(new, old): def adjust_normarelacionada(new, old): - tipo = TipoVinculoNormaJuridica.objects.filter(sigla=old.tip_vinculo) - assert len(tipo) == 1 - new.tipo_vinculo = tipo[0] + new.tipo_vinculo = TipoVinculoNormaJuridica.objects.get( + sigla=old.tip_vinculo) def adjust_protocolo_antes_salvar(new, old): @@ -1015,8 +1035,11 @@ def adjust_registrovotacao_antes_salvar(new, old): def adjust_tipoafastamento(new, old): - if old.ind_afastamento == 1: + assert xor(old.ind_afastamento, old.ind_fim_mandato) + if old.ind_afastamento: new.indicador = 'A' + elif old.ind_fim_mandato: + new.indicador = 'F' MODEL_TIPO_MATERIA_OU_DOCUMENTO = {'M': TipoMateriaLegislativa, diff --git a/sapl/legacy/test_renames.py b/sapl/legacy/test_renames.py index 7a8766da0..573129725 100644 --- a/sapl/legacy/test_renames.py +++ b/sapl/legacy/test_renames.py @@ -1,29 +1,76 @@ -import sapl.comissoes -import sapl.materia -import sapl.norma -import sapl.sessao + +from django.contrib.contenttypes.fields import GenericForeignKey + +from sapl.base.models import AppConfig, Autor, CasaLegislativa, TipoAutor +from sapl.comissoes.models import \ + DocumentoAcessorio as DocumentoAcessorioComissoes +from sapl.comissoes.models import Comissao, Composicao, Participacao, Reuniao +from sapl.materia.models import (AcompanhamentoMateria, DocumentoAcessorio, + MateriaLegislativa, Proposicao, + TipoMateriaLegislativa, TipoProposicao, + Tramitacao) +from sapl.norma.models import (NormaJuridica, NormaRelacionada, + TipoVinculoNormaJuridica) +from sapl.parlamentares.models import (Frente, Mandato, Parlamentar, Partido, + TipoAfastamento, Votante) +from sapl.protocoloadm.models import DocumentoAdministrativo +from sapl.sessao.models import (Bancada, Bloco, CargoBancada, + ExpedienteMateria, Orador, OradorExpediente, + OrdemDia, RegistroVotacao, ResumoOrdenacao, + SessaoPlenaria, TipoResultadoVotacao, + VotoParlamentar) from .migracao_dados import appconfs, get_renames, legacy_app RENAMING_IGNORED_MODELS = [ - sapl.comissoes.models.Composicao, - sapl.norma.models.AssuntoNormaRelationship, + Votante, Frente, Bancada, CargoBancada, Bloco, # parlamentares + Composicao, Reuniao, DocumentoAcessorioComissoes, # commissoes + AppConfig, CasaLegislativa, # base + ResumoOrdenacao, # sessao + TipoVinculoNormaJuridica, # norma - # FIXME retirar daqui depois que a issue #218 for resolvida!!!!!!! - sapl.sessao.models.AcompanharMateria, ] RENAMING_IGNORED_FIELDS = [ - (sapl.comissoes.models.Participacao, {'composicao'}), - (sapl.materia.models.Proposicao, {'documento'}), - (sapl.materia.models.TipoProposicao, {'tipo_documento'}), - (sapl.materia.models.Tramitacao, {'ultima'}), - (sapl.sessao.models.SessaoPlenaria, {'finalizada', - 'upload_pauta', - 'upload_ata', - 'iniciada'}), - (sapl.sessao.models.ExpedienteMateria, {'votacao_aberta'}), - (sapl.sessao.models.OrdemDia, {'votacao_aberta'}), + (TipoAfastamento, {'indicador'}), + (Participacao, {'composicao'}), + (Proposicao, { + 'ano', 'content_type', 'object_id', 'conteudo_gerado_related', + 'status', 'hash_code', 'texto_original'}), + (TipoProposicao, { + 'object_id', 'content_type', 'tipo_conteudo_related', 'perfis', + # não estou entendendo como esses campos são enumerados, + # mas eles não fazem parte da migração + # 'tipomaterialegislativa_set', 'tipodocumento_set', + }), + + (Tramitacao, {'ultima'}), + (SessaoPlenaria, {'finalizada', 'iniciada', 'painel_aberto', 'interativa', + 'upload_ata', + 'upload_anexo', + 'upload_pauta'}), + (ExpedienteMateria, {'votacao_aberta'}), + (OrdemDia, {'votacao_aberta'}), + (NormaJuridica, {'texto_integral', 'data_ultima_atualizacao', 'assuntos'}), + (Parlamentar, { + 'uf_residencia', 'municipio_residencia', 'cropping', 'fotografia'}), + (Partido, {'logo_partido'}), + (MateriaLegislativa, { + 'autores', 'anexadas', 'data_ultima_atualizacao', 'texto_original'}), + (DocumentoAdministrativo, {'protocolo', 'texto_integral'}), + (Mandato, {'titular', 'data_fim_mandato', 'data_inicio_mandato'}), + (TipoMateriaLegislativa, {'sequencia_numeracao'}), + (TipoAutor, {'content_type'}), + (TipoResultadoVotacao, {'natureza'}), + (RegistroVotacao, {'ordem', 'expediente'}), + (DocumentoAcessorio, {'arquivo', 'data_ultima_atualizacao'}), + (OradorExpediente, {'upload_anexo', 'observacao'}), + (Orador, {'upload_anexo', 'observacao'}), + (VotoParlamentar, {'user', 'ip', 'expediente', 'data_hora', 'ordem'}), + (NormaRelacionada, {'tipo_vinculo'}), + (AcompanhamentoMateria, {'confirmado', 'data_cadastro', 'usuario'}), + (Autor, {'user', 'content_type', 'object_id', 'autor_related'}), + (Comissao, {'ativa'}), ] @@ -31,7 +78,9 @@ def test_get_renames(): field_renames, model_renames = get_renames() all_models = {m for ac in appconfs for m in ac.get_models()} for model in all_models: - field_names = {f.name for f in model._meta.fields if f.name != 'id'} + field_names = {f.name for f in model._meta.get_fields() + if f.name != 'id' + and (f.concrete or isinstance(f, GenericForeignKey))} if model not in field_renames: # check ignored models in renaming assert model in RENAMING_IGNORED_MODELS @@ -46,11 +95,12 @@ def test_get_renames(): match_msg_template % ('new', 'current') # ignored fields are explicitly listed - missing_in_renames = field_names - renamed - if missing_in_renames: - assert (model, missing_in_renames) in \ - RENAMING_IGNORED_FIELDS, \ - 'Field(s) missing in renames but not explicitly listed' + missing = field_names - renamed + if missing: + assert (model, missing) in RENAMING_IGNORED_FIELDS, \ + 'Campos faltando na renomeação,' \ + 'mas não listados explicitamente: ({}, {})'.format( + model.__name__, missing) # all old names correspond to a legacy field legacy_model = legacy_app.get_model( diff --git a/sapl/materia/legacy.yaml b/sapl/materia/legacy.yaml index db2c8490f..9ff81287f 100644 --- a/sapl/materia/legacy.yaml +++ b/sapl/materia/legacy.yaml @@ -58,9 +58,8 @@ AssuntoMateria: dispositivo: des_dispositivo DespachoInicial: - comissao: cod_comissao materia: cod_materia - numero_ordem: num_ordem + comissao: cod_comissao TipoDocumento: descricao: des_tipo_documento @@ -112,10 +111,6 @@ Parecer: TipoProposicao: descricao: des_tipo_proposicao - materia_ou_documento: ind_mat_ou_doc - modelo: nom_modelo - tipo_documento: - tipo_materia: Proposicao: autor: cod_autor @@ -124,7 +119,7 @@ Proposicao: data_recebimento: dat_recebimento descricao: txt_descricao justificativa_devolucao: txt_justif_devolucao - materia: cod_mat_ou_doc + materia_de_vinculo: cod_materia numero_proposicao: num_proposicao tipo: tip_proposicao diff --git a/sapl/materia/models.py b/sapl/materia/models.py index 34fd44d55..23e4a6e3d 100644 --- a/sapl/materia/models.py +++ b/sapl/materia/models.py @@ -40,9 +40,6 @@ class TipoProposicao(models.Model): error_messages={ 'unique': _('Já existe um Tipo de Proposição com esta descrição.') }) - - # FIXME - para a rotina de migração - estes campos mudaram - # retire o comentário quando resolver content_type = models.ForeignKey(ContentType, default=None, on_delete=models.PROTECT, verbose_name=_('Definição de Tipo')) @@ -378,9 +375,6 @@ class AssuntoMateria(models.Model): @reversion.register() class DespachoInicial(models.Model): - # TODO M2M? - # TODO Despachos não são necessáriamente comissoes, podem ser outros - # órgãos, ex: procuradorias materia = models.ForeignKey(MateriaLegislativa, on_delete=models.CASCADE) comissao = models.ForeignKey(Comissao, on_delete=models.CASCADE) diff --git a/sapl/norma/legacy.yaml b/sapl/norma/legacy.yaml index 3294561b8..23bf5bb80 100644 --- a/sapl/norma/legacy.yaml +++ b/sapl/norma/legacy.yaml @@ -9,7 +9,6 @@ TipoNormaJuridica: NormaJuridica: ano: ano_norma - assunto: cod_assunto complemento: ind_complemento data: dat_norma data_publicacao: dat_publicacao diff --git a/sapl/parlamentares/legacy.yaml b/sapl/parlamentares/legacy.yaml index acaca47de..fc72431f5 100644 --- a/sapl/parlamentares/legacy.yaml +++ b/sapl/parlamentares/legacy.yaml @@ -38,7 +38,6 @@ Parlamentar: ativo: ind_ativo biografia: txt_biografia cep_residencia: num_cep_resid - cod_casa: cod_casa cpf: num_cpf data_nascimento: dat_nascimento email: end_email @@ -58,7 +57,6 @@ Parlamentar: telefone: num_tel_parlamentar telefone_residencia: num_tel_resid titulo_eleitor: num_tit_eleitor - unidade_deliberativa: ind_unid_deliberativa TipoDependente: descricao: des_tipo_dependente @@ -80,10 +78,8 @@ Filiacao: partido: cod_partido TipoAfastamento: - afastamento: ind_afastamento descricao: des_afastamento dispositivo: des_dispositivo - fim_mandato: ind_fim_mandato Mandato: coligacao: cod_coligacao diff --git a/sapl/protocoloadm/legacy.yaml b/sapl/protocoloadm/legacy.yaml index 3772d2f6d..7dae79f73 100644 --- a/sapl/protocoloadm/legacy.yaml +++ b/sapl/protocoloadm/legacy.yaml @@ -11,7 +11,6 @@ DocumentoAdministrativo: dias_prazo: num_dias_prazo interessado: txt_interessado numero: num_documento - numero_protocolo: num_protocolo observacao: txt_observacao tipo: tip_documento tramitacao: ind_tramitacao diff --git a/sapl/sessao/legacy.yaml b/sapl/sessao/legacy.yaml index 90188a76c..e7b55095a 100644 --- a/sapl/sessao/legacy.yaml +++ b/sapl/sessao/legacy.yaml @@ -52,7 +52,6 @@ OradorExpediente (OradoresExpediente): {} OrdemDia: {} PresencaOrdemDia (OrdemDiaPresenca): - data_ordem: dat_ordem parlamentar: cod_parlamentar sessao_plenaria: cod_sessao_plen