From 92637104b9f000a7ebf4988c6c5ae6e2106d4efb Mon Sep 17 00:00:00 2001 From: Marcio Mazza Date: Tue, 20 Mar 2018 15:10:17 -0300 Subject: [PATCH 01/10] =?UTF-8?q?Adiciona=20meta=5Ftype=20a=20exporta?= =?UTF-8?q?=C3=A7=C3=A3o=20de=20docs=20do=20SDE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/legacy/scripts/exporta_zope/exporta_zope.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sapl/legacy/scripts/exporta_zope/exporta_zope.py b/sapl/legacy/scripts/exporta_zope/exporta_zope.py index 31c4c3188..11ff1ecbb 100755 --- a/sapl/legacy/scripts/exporta_zope/exporta_zope.py +++ b/sapl/legacy/scripts/exporta_zope/exporta_zope.py @@ -14,7 +14,6 @@ from functools import partial import magic import yaml - import ZODB.DB import ZODB.FileStorage from ZODB.broken import Broken @@ -191,7 +190,9 @@ def read_sde(element): ] if meta_type != 'Script (Python)': # ignoramos os scrips python de eventos dos templates - yield id, read_sde(obj) + yield {'id': id, + 'meta_type': meta_type, + 'dados': read_sde(obj)} data = dict(read_properties()) children = list(read_children()) From fb4fbd346a0e74b621a6c626cab268147b5b3f19 Mon Sep 17 00:00:00 2001 From: Marcio Mazza Date: Tue, 20 Mar 2018 18:24:21 -0300 Subject: [PATCH 02/10] Corrige msg de erro --- sapl/sessao/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sapl/sessao/models.py b/sapl/sessao/models.py index 90736d6e0..23155b51a 100644 --- a/sapl/sessao/models.py +++ b/sapl/sessao/models.py @@ -436,7 +436,8 @@ class RegistroVotacao(models.Model): if not xor(bool(self.ordem), bool(self.expediente)): raise ValidationError( 'RegistroVotacao deve ter exatamente um dos campos ' - 'ordem ou expediente deve estar preenchido') + 'ordem ou expediente preenchido. Ambos estão preenchidos: ' + '{}, {}'. format(self.ordem, self.expediente)) @reversion.register() From 419348e4dfe2c453422767effea7ab33305075fa Mon Sep 17 00:00:00 2001 From: Marcio Mazza Date: Wed, 21 Mar 2018 18:19:49 -0300 Subject: [PATCH 03/10] =?UTF-8?q?Usa=20bulk=5Fcreate=20para=20a=20migra?= =?UTF-8?q?=C3=A7=C3=A3o=20de=20dados?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/legacy/migracao_dados.py | 43 ++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/sapl/legacy/migracao_dados.py b/sapl/legacy/migracao_dados.py index 6aa051e62..db288561f 100644 --- a/sapl/legacy/migracao_dados.py +++ b/sapl/legacy/migracao_dados.py @@ -796,6 +796,7 @@ class DataMigrator: # convert old records to new ones with transaction.atomic(): + novos = [] sql_delete_legado = '' for old in old_records: new = model() @@ -812,12 +813,9 @@ class DataMigrator: else: if get_id_do_legado: new.id = get_id_do_legado(old) - # validação do model - new.clean() - # salva novo registro - with reversion.create_revision(): - new.save() - reversion.set_comment('Objeto criado pela migração') + + new.clean() # valida model + novos.append(new) # guarda para salvar # acumula deleção do registro no legado sql_delete_legado += 'delete from {} where {};\n'.format( @@ -827,8 +825,11 @@ class DataMigrator: getattr(old, campo)) for campo in campos_pk)) - if ajuste_depois_salvar: - ajuste_depois_salvar(new, old) + # salva novos registros + model.objects.bulk_create(novos) + + if ajuste_depois_salvar: + ajuste_depois_salvar() # se configuramos ids explicitamente devemos reiniciar a sequence if get_id_do_legado: @@ -1067,20 +1068,26 @@ def adjust_normajuridica_antes_salvar(new, old): new.esfera_federacao = '' -def adjust_normajuridica_depois_salvar(new, old): +def adjust_normajuridica_depois_salvar(): # Ajusta relação M2M + ligacao = NormaJuridica.assuntos.through - if not old.cod_assunto: # it can be null or empty - return + def gen_ligacoes(): + from sapl.legacy.models import NormaJuridica as OldNormaJuridica - # lista de pks separadas por vírgulas (ignorando strings vazias) - lista_pks_assunto = [int(pk) for pk in old.cod_assunto.split(',') if pk] + assuntos_migrados, normas_migradas = [ + set(model.objects.values_list('id', flat=True)) + for model in [AssuntoNorma, NormaJuridica]] - for pk_assunto in lista_pks_assunto: - try: - new.assuntos.add(AssuntoNorma.objects.get(pk=pk_assunto)) - except ObjectDoesNotExist: - pass # ignora assuntos inexistentes + for norma, cod_assunto in OldNormaJuridica.objects.filter( + pk__in=normas_migradas).values_list('pk', 'cod_assunto'): + + assuntos = [a for a in map(int, cod_assunto.split(',')) + if a in assuntos_migrados] + for assunto in assuntos: + yield ligacao(normajuridica_id=norma, assuntonorma_id=assunto) + + ligacao.objects.bulk_create(gen_ligacoes()) def vincula_autor(new, old, model_relacionado, campo_relacionado, campo_nome): From ab56ccd194aee3bfbc4ff12fb2656a4677c25f93 Mon Sep 17 00:00:00 2001 From: Marcio Mazza Date: Thu, 22 Mar 2018 10:38:13 -0300 Subject: [PATCH 04/10] Refatora adjuste de norma --- sapl/legacy/migracao_dados.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/sapl/legacy/migracao_dados.py b/sapl/legacy/migracao_dados.py index db288561f..9b7aecf86 100644 --- a/sapl/legacy/migracao_dados.py +++ b/sapl/legacy/migracao_dados.py @@ -23,6 +23,7 @@ from unipath import Path from sapl.base.models import AppConfig as AppConf from sapl.base.models import Autor, TipoAutor, cria_models_tipo_autor from sapl.comissoes.models import Comissao, Composicao, Participacao +from sapl.legacy.models import NormaJuridica as OldNormaJuridica from sapl.legacy.models import TipoNumeracaoProtocolo from sapl.materia.models import (AcompanhamentoMateria, Proposicao, StatusTramitacao, TipoDocumento, @@ -1072,22 +1073,23 @@ def adjust_normajuridica_depois_salvar(): # Ajusta relação M2M ligacao = NormaJuridica.assuntos.through - def gen_ligacoes(): - from sapl.legacy.models import NormaJuridica as OldNormaJuridica + assuntos_migrados, normas_migradas = [ + set(model.objects.values_list('id', flat=True)) + for model in [AssuntoNorma, NormaJuridica]] - assuntos_migrados, normas_migradas = [ - set(model.objects.values_list('id', flat=True)) - for model in [AssuntoNorma, NormaJuridica]] + def filtra_assuntos_migrados(cod_assunto): + return [a for a in map(int, cod_assunto.split(',')) + if a in assuntos_migrados] + norma_para_assuntos = [ + (norma, filtra_assuntos_migrados(cod_assunto)) for norma, cod_assunto in OldNormaJuridica.objects.filter( - pk__in=normas_migradas).values_list('pk', 'cod_assunto'): + pk__in=normas_migradas).values_list('pk', 'cod_assunto')] - assuntos = [a for a in map(int, cod_assunto.split(',')) - if a in assuntos_migrados] - for assunto in assuntos: - yield ligacao(normajuridica_id=norma, assuntonorma_id=assunto) - - ligacao.objects.bulk_create(gen_ligacoes()) + ligacao.objects.bulk_create( + ligacao(normajuridica_id=norma, assuntonorma_id=assunto) + for norma, assuntos in norma_para_assuntos + for assunto in assuntos) def vincula_autor(new, old, model_relacionado, campo_relacionado, campo_nome): From f202774905a6ea8ff6c166d8bf63e9fd0cc18c50 Mon Sep 17 00:00:00 2001 From: Marcio Mazza Date: Thu, 22 Mar 2018 15:14:07 -0300 Subject: [PATCH 05/10] =?UTF-8?q?Registra=20traceback=20no=20log=20de=20mi?= =?UTF-8?q?gra=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/legacy/migracao_dados.py | 3 +++ sapl/legacy/scripts/migra_um_db.sh | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/sapl/legacy/migracao_dados.py b/sapl/legacy/migracao_dados.py index 9b7aecf86..ef96cb20a 100644 --- a/sapl/legacy/migracao_dados.py +++ b/sapl/legacy/migracao_dados.py @@ -1,4 +1,5 @@ import re +import traceback from collections import defaultdict from datetime import date from functools import lru_cache, partial @@ -724,6 +725,8 @@ class DataMigrator: dir_ocorrencias = DIR_RESULTADOS.child(date.today().isoformat()) dir_ocorrencias.mkdir(parents=True) self._do_migrate(obj) + except Exception as e: + ocorrencias['traceback'] = str(traceback.format_exc()) finally: # grava ocorrências arq_ocorrencias = dir_ocorrencias.child( diff --git a/sapl/legacy/scripts/migra_um_db.sh b/sapl/legacy/scripts/migra_um_db.sh index 577a2c000..ed4db9662 100755 --- a/sapl/legacy/scripts/migra_um_db.sh +++ b/sapl/legacy/scripts/migra_um_db.sh @@ -39,7 +39,7 @@ if [ $# -ge 2 ]; then echo "--- MIGRACAO ---" | tee -a $LOG echo >> $LOG - DATABASE_NAME=$1 ./manage.py migracao_25_31 --force --settings sapl.legacy_migration_settings 2>&1 | tee -a $LOG + DATABASE_NAME=$1 ./manage.py migracao_25_31 --force --dados --settings sapl.legacy_migration_settings 2>&1 | tee -a $LOG echo >> $LOG else echo "USO:" From 9475c17cfbe8acb60332929b4067151f7ffaf58f Mon Sep 17 00:00:00 2001 From: Marcio Mazza Date: Fri, 23 Mar 2018 10:46:41 -0300 Subject: [PATCH 06/10] =?UTF-8?q?Melhora=20estrutura=20do=20log=20de=20mig?= =?UTF-8?q?ra=C3=A7=C3=A3o?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/legacy/migracao_dados.py | 66 +++++++++++++++++------------------ 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/sapl/legacy/migracao_dados.py b/sapl/legacy/migracao_dados.py index ef96cb20a..5b175aca4 100644 --- a/sapl/legacy/migracao_dados.py +++ b/sapl/legacy/migracao_dados.py @@ -1,6 +1,6 @@ import re import traceback -from collections import defaultdict +from collections import OrderedDict, defaultdict from datetime import date from functools import lru_cache, partial from itertools import groupby @@ -136,10 +136,10 @@ class ForeignKeyFaltando(ObjectDoesNotExist): @property def dados(self): - return {'field': self.field.name, - 'value': self.value, - 'model': self.field.model.__name__, - 'label': self.label} + return OrderedDict((('field', self.field.name), + ('value', self.value), + ('model', self.field.model.__name__), + ('label', self.label))) @lru_cache() @@ -148,7 +148,7 @@ def _get_all_ids_from_model(model): return set(model.objects.values_list('id', flat=True)) -def get_fk_related(field, value, label='---'): +def get_fk_related(field, value, label={}): if value is None and field.null: return None @@ -554,9 +554,14 @@ relatoria | tip_fim_relatoria = NULL | tip_fim_relatoria = 0 anula_tipos_origem_externa_invalidos() -def iter_sql_records(sql): - class Record: - pass +class Record: + pass + + +def iter_sql_records(tabela): + 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(): @@ -637,6 +642,11 @@ PATH_TABELA_TIMEZONES = DIR_DADOS_MIGRACAO.child('tabela_timezones.yaml') DIR_RESULTADOS = DIR_DADOS_MIGRACAO.child('resultados') +def dict_representer(dumper, data): + return dumper.represent_dict(data.items()) +yaml.add_representer(OrderedDict, dict_representer) + + class DataMigrator: def __init__(self): @@ -655,7 +665,7 @@ class DataMigrator: else: self.timezone = get_timezone(municipio, uf) - def populate_renamed_fields(self, new, old): + def populate_renamed_fields(self, new, old, campos_pk_legado): renames = self.field_renames[type(new)] for field in new._meta.fields: @@ -665,16 +675,12 @@ class DataMigrator: old_value = getattr(old, old_field_name) if field_type == 'ForeignKey': - # not necessarily a model - if hasattr(old, '_meta') and old._meta.pk.name != 'id': - label = 'pk = {}'.format(old.pk) - else: - label = '-- SEM PK --' + 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) setattr(new, fk_field_name, value) else: - value = getattr(old, old_field_name) + value = old_value if (field_type in ['CharField', 'TextField'] and value in [None, 'None']): @@ -732,7 +738,8 @@ class DataMigrator: arq_ocorrencias = dir_ocorrencias.child( self.nome_banco_legado + '.yaml') with open(arq_ocorrencias, 'w') as arq: - yaml.safe_dump(dict(ocorrencias), arq, allow_unicode=True) + dump = yaml.dump(dict(ocorrencias), allow_unicode=True) + arq.write(dump.replace('\n- ', '\n\n- ')) info('Ocorrências salvas em\n {}'.format(arq_ocorrencias)) # recria tipos de autor padrão que não foram criados pela migração @@ -771,9 +778,9 @@ class DataMigrator: 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 = get_pk_legado(tabela_legado) + campos_pk_legado = get_pk_legado(tabela_legado) - if len(campos_pk) == 1: + if len(campos_pk_legado) == 1: # a pk no legado tem um único campo nome_pk = model_legado._meta.pk.name if 'ind_excluido' in {f.name for f in model_legado._meta.fields}: @@ -788,11 +795,7 @@ class DataMigrator: return getattr(old, nome_pk) else: # a pk no legado tem mais de um campo - sql = 'select * from ' + tabela_legado - if existe_coluna_no_legado(tabela_legado, 'ind_excluido'): - sql += ' where ind_excluido != 1' - old_records = iter_sql_records(sql) - + old_records = iter_sql_records(tabela_legado) get_id_do_legado = None ajuste_antes_salvar = AJUSTE_ANTES_SALVAR.get(model) @@ -805,7 +808,7 @@ class DataMigrator: for old in old_records: new = model() try: - self.populate_renamed_fields(new, old) + self.populate_renamed_fields(new, old, campos_pk_legado) if ajuste_antes_salvar: ajuste_antes_salvar(new, old) except ForeignKeyFaltando as e: @@ -827,7 +830,7 @@ class DataMigrator: ' and '.join( '{} = "{}"'.format(campo, getattr(old, campo)) - for campo in campos_pk)) + for campo in campos_pk_legado)) # salva novos registros model.objects.bulk_create(novos) @@ -963,9 +966,7 @@ def adjust_participacao(new, old): composicao.comissao_id, composicao.periodo_id = [ get_fk_related(Composicao._meta.get_field(name), value, - 'composicao_comissao.cod_comp_comissao = {}'.format( - old.pk - )) + {'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 @@ -1033,7 +1034,7 @@ def adjust_tipoproposicao(new, old): raise ForeignKeyFaltando( field=TipoProposicao.tipo_conteudo_related, value=(model_tipo.__name__, value), - label='ind_mat_ou_doc = {}'.format(old.ind_mat_ou_doc)) + label={'ind_mat_ou_doc': old.ind_mat_ou_doc}) def adjust_statustramitacao(new, old): @@ -1105,9 +1106,8 @@ def vincula_autor(new, old, model_relacionado, campo_relacionado, campo_nome): nome_model_relacionado = model_relacionado._meta.model.__name__ raise ForeignKeyFaltando( field=Autor.autor_related, - value=(nome_model_relacionado, pk_rel), - label='{} [pk={}] inexistente para autor'.format( - nome_model_relacionado, pk_rel)) + value=[nome_model_relacionado, pk_rel], + label={'cod_autor': old.pk}) else: new.nome = getattr(new.autor_related, campo_nome) return True From 87c8c24c916690ee01e62e4d4fc4ce38ab4e2adb Mon Sep 17 00:00:00 2001 From: Marcio Mazza Date: Thu, 29 Mar 2018 14:00:32 -0300 Subject: [PATCH 07/10] =?UTF-8?q?Revisa=20e=20adiciona=20campos=20faltante?= =?UTF-8?q?s=20=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 From 901c0ec043b8ffdbd9ef305c97cecbcf1cccaac9 Mon Sep 17 00:00:00 2001 From: Marcio Mazza Date: Thu, 29 Mar 2018 20:23:02 -0300 Subject: [PATCH 08/10] Migra conteudo_gerado_related de Proposicao --- sapl/legacy/migracao_dados.py | 436 ++++++++++++++++++---------------- sapl/materia/models.py | 4 - 2 files changed, 225 insertions(+), 215 deletions(-) diff --git a/sapl/legacy/migracao_dados.py b/sapl/legacy/migracao_dados.py index 6f740555b..14663f8ed 100644 --- a/sapl/legacy/migracao_dados.py +++ b/sapl/legacy/migracao_dados.py @@ -1,6 +1,6 @@ import re import traceback -from collections import OrderedDict, defaultdict +from collections import OrderedDict, defaultdict, namedtuple from datetime import date from functools import lru_cache, partial from itertools import groupby @@ -8,18 +8,16 @@ from operator import xor from subprocess import PIPE, call import pkg_resources +import pytz import reversion import yaml from django.apps import apps -from django.apps.config import AppConfig from django.contrib.auth import get_user_model from django.contrib.auth.models import Group from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ObjectDoesNotExist from django.db import connections, transaction from django.db.models import Max, Q -from django.db.models.base import ModelBase -from pytz import timezone from unipath import Path from sapl.base.models import AppConfig as AppConf @@ -27,8 +25,8 @@ from sapl.base.models import Autor, TipoAutor, cria_models_tipo_autor from sapl.comissoes.models import Comissao, Composicao, Participacao from sapl.legacy.models import NormaJuridica as OldNormaJuridica from sapl.legacy.models import TipoNumeracaoProtocolo -from sapl.materia.models import (AcompanhamentoMateria, Proposicao, - StatusTramitacao, TipoDocumento, +from sapl.materia.models import (AcompanhamentoMateria, MateriaLegislativa, + Proposicao, StatusTramitacao, TipoDocumento, TipoMateriaLegislativa, TipoProposicao, Tramitacao) from sapl.norma.models import (AssuntoNorma, NormaJuridica, NormaRelacionada, @@ -117,12 +115,31 @@ models_novos_para_antigos = { for model in field_renames} models_novos_para_antigos[Composicao] = models_novos_para_antigos[Participacao] +content_types = {model: ContentType.objects.get( + app_label=model._meta.app_label, model=model._meta.model_name) + for model in field_renames} 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()} +# campos de Composicao (de Comissao) +for nome_novo, nome_antigo in (('comissao', 'cod_comissao'), + ('periodo', 'cod_periodo_comp')): + campos_novos_para_antigos[ + Composicao._meta.get_field(nome_novo)] = nome_antigo + + +# campos virtuais de Proposicao para funcionarem com get_fk_related +CampoFalso = namedtuple('CampoFalso', ['model', 'related_model']) +CAMPOS_FALSOS_PROPOSICAO = { + TipoMateriaLegislativa: CampoFalso(Proposicao, MateriaLegislativa), + TipoDocumento: CampoFalso(Proposicao, DocumentoAdministrativo) +} +for campo_falso in CAMPOS_FALSOS_PROPOSICAO.values(): + campos_novos_para_antigos[campo_falso] = 'cod_mat_ou_doc' + # MIGRATION ################################################################# @@ -168,10 +185,14 @@ class ForeignKeyFaltando(ObjectDoesNotExist): 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} + sql = 'select * from {} where {}'.format( + tabela, + ' and '.join(['{} = {}'.format(k, v) for k, v in pk.items()])) return OrderedDict((('campo', campo), ('valor', self.valor), ('tabela', tabela), - ('pk', pk))) + ('pk', pk), + ('sql', sql))) @lru_cache() @@ -180,8 +201,8 @@ def _get_all_ids_from_model(model): return set(model.objects.values_list('id', flat=True)) -def get_fk_related(field, old, old_field_name): - valor = getattr(old, old_field_name) +def get_fk_related(field, old): + valor = getattr(old, campos_novos_para_antigos[field]) if valor is None and field.null: return None if valor in _get_all_ids_from_model(field.related_model): @@ -679,208 +700,197 @@ def dict_representer(dumper, data): yaml.add_representer(OrderedDict, dict_representer) -class DataMigrator: +# configura timezone de migração +nome_banco_legado = DATABASES['legacy']['NAME'] +match = re.match('sapl_cm_(.*)', nome_banco_legado) +sigla_casa = match.group(1) +with open(PATH_TABELA_TIMEZONES, 'r') as arq: + tabela_timezones = yaml.load(arq) +municipio, uf, nome_timezone = tabela_timezones[sigla_casa] +if nome_timezone: + timezone = pytz.timezone(nome_timezone) +else: + timezone = get_timezone(municipio, uf) - def __init__(self): - self.choice_valida = {} - # configura timezone de migração - self.nome_banco_legado = DATABASES['legacy']['NAME'] - match = re.match('sapl_cm_(.*)', self.nome_banco_legado) - sigla_casa = match.group(1) - with open(PATH_TABELA_TIMEZONES, 'r') as arq: - tabela_timezones = yaml.load(arq) - municipio, uf, nome_timezone = tabela_timezones[sigla_casa] - if nome_timezone: - self.timezone = timezone(nome_timezone) - else: - self.timezone = get_timezone(municipio, uf) - - 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) - if old_field_name: - field_type = field.get_internal_type() - - if field_type == 'ForeignKey': - fk_field_name = '{}_id'.format(field.name) - value = get_fk_related(field, old, old_field_name) - setattr(new, fk_field_name, value) - else: - value = getattr(old, old_field_name) - - if (field_type in ['CharField', 'TextField'] - and value in [None, 'None']): - value = '' - - # adiciona timezone faltante aos campos com tempo - # os campos TIMESTAMP do mysql são gravados em UTC - # os DATETIME e TIME não têm timezone - def campo_tempo_sem_timezone(tipo): - return (field_type == tipo - and value and not value.tzinfo) - if campo_tempo_sem_timezone('DateTimeField'): - value = self.timezone.localize(value) - if campo_tempo_sem_timezone('TimeField'): - value = value.replace(tzinfo=self.timezone) - - setattr(new, field.name, value) - - def migrar(self, obj=appconfs, interativo=True): - # warning: model/app migration order is of utmost importance - - uniformiza_banco() - - # excluindo database antigo. - if interativo: - info('Todos os dados do banco serão excluidos. ' - 'Recomendamos que faça backup do banco sapl ' - 'antes de continuar.') - info('Deseja continuar? [s/n]') - resposta = input() - if resposta.lower() in ['s', 'sim', 'y', 'yes']: - pass +def populate_renamed_fields(new, old): + renames = field_renames[type(new)] + + for field in new._meta.fields: + old_field_name = renames.get(field.name) + if old_field_name: + field_type = field.get_internal_type() + + if field_type == 'ForeignKey': + fk_field_name = '{}_id'.format(field.name) + value = get_fk_related(field, old) + setattr(new, fk_field_name, value) else: - info('Migração cancelada.') - return 0 - info('Excluindo entradas antigas do banco destino.') - call([PROJECT_DIR.child('manage.py'), 'flush', - '--database=default', '--no-input'], stdout=PIPE) - - # apaga tipos de autor padrão (criados no flush acima) - TipoAutor.objects.all().delete() - - fill_vinculo_norma_juridica() - fill_dados_basicos() - info('Começando migração: %s...' % obj) - try: - ocorrencias.clear() - dir_ocorrencias = DIR_RESULTADOS.child(date.today().isoformat()) - dir_ocorrencias.mkdir(parents=True) - 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( - self.nome_banco_legado + '.yaml') - with open(arq_ocorrencias, 'w') as arq: - dump = yaml.dump(dict(ocorrencias), allow_unicode=True) - arq.write(dump.replace('\n- ', '\n\n- ')) - info('Ocorrências salvas em\n {}'.format(arq_ocorrencias)) - - # recria tipos de autor padrão que não foram criados pela migração - cria_models_tipo_autor() - - def _do_migrate(self, obj): - if isinstance(obj, AppConfig): - models = [model for model in obj.models.values() - if model in field_renames] - - if obj.label == 'materia': - # Devido à referência TipoProposicao.tipo_conteudo_related - # a migração de TipoProposicao precisa ser feita - # após TipoMateriaLegislativa e TipoDocumento - # (porém antes de Proposicao) - models.remove(TipoProposicao) - pos_tipo_proposicao = max( - models.index(TipoMateriaLegislativa), - models.index(TipoDocumento)) + 1 - models.insert(pos_tipo_proposicao, TipoProposicao) - assert models.index(TipoProposicao) < models.index(Proposicao) - - self._do_migrate(models) - elif isinstance(obj, ModelBase): - self.migrate_model(obj) - elif hasattr(obj, '__iter__'): - for item in obj: - self._do_migrate(item) + value = getattr(old, old_field_name) + + if (field_type in ['CharField', 'TextField'] + and value in [None, 'None']): + value = '' + + # adiciona timezone faltante aos campos com tempo + # os campos TIMESTAMP do mysql são gravados em UTC + # os DATETIME e TIME não têm timezone + def campo_tempo_sem_timezone(tipo): + return (field_type == tipo + and value and not value.tzinfo) + if campo_tempo_sem_timezone('DateTimeField'): + value = timezone.localize(value) + if campo_tempo_sem_timezone('TimeField'): + value = value.replace(tzinfo=timezone) + + setattr(new, field.name, value) + + +def migrar_dados(interativo=True): + uniformiza_banco() + + # excluindo database antigo. + if interativo: + info('Todos os dados do banco serão excluidos. ' + 'Recomendamos que faça backup do banco sapl ' + 'antes de continuar.') + info('Deseja continuar? [s/n]') + resposta = input() + if resposta.lower() in ['s', 'sim', 'y', 'yes']: + pass + else: + info('Migração cancelada.') + return 0 + info('Excluindo entradas antigas do banco destino.') + call([PROJECT_DIR.child('manage.py'), 'flush', + '--database=default', '--no-input'], stdout=PIPE) + + # apaga tipos de autor padrão (criados no flush acima) + TipoAutor.objects.all().delete() + + fill_vinculo_norma_juridica() + fill_dados_basicos() + info('Começando migração: ...') + try: + ocorrencias.clear() + dir_ocorrencias = DIR_RESULTADOS.child(date.today().isoformat()) + dir_ocorrencias.mkdir(parents=True) + migrar_todos_os_models() + except Exception as e: + ocorrencias['traceback'] = str(traceback.format_exc()) + raise e + finally: + # grava ocorrências + arq_ocorrencias = dir_ocorrencias.child( + nome_banco_legado + '.yaml') + with open(arq_ocorrencias, 'w') as arq: + dump = yaml.dump(dict(ocorrencias), allow_unicode=True) + arq.write(dump.replace('\n- ', '\n\n- ')) + info('Ocorrências salvas em\n {}'.format(arq_ocorrencias)) + + # recria tipos de autor padrão que não foram criados pela migração + cria_models_tipo_autor() + + +def move_para_depois_de(lista, movido, referencias): + indice_inicial = lista.index(movido) + lista.remove(movido) + indice_apos_refs = max(lista.index(r) for r in referencias) + 1 + lista.insert(max(indice_inicial, indice_apos_refs), movido) + return lista + + +def migrar_todos_os_models(): + models = [model for app in appconfs for model in app.models.values() + if model in field_renames] + # Devido à referência TipoProposicao.tipo_conteudo_related + # a migração de TipoProposicao precisa ser feita + # após TipoMateriaLegislativa e TipoDocumento + # (porém antes de Proposicao) + move_para_depois_de(models, TipoProposicao, + [TipoMateriaLegislativa, TipoDocumento]) + assert models.index(TipoProposicao) < models.index(Proposicao) + move_para_depois_de(models, Proposicao, + [MateriaLegislativa, DocumentoAdministrativo]) + + for model in models: + migrar_model(model) + + +def migrar_model(model): + print('Migrando %s...' % model.__name__) + + 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 + nome_pk = model_legado._meta.pk.name + if 'ind_excluido' in {f.name for f in model_legado._meta.fields}: + # se o model legado tem o campo ind_excluido + # enumera apenas os não excluídos + old_records = model_legado.objects.filter(~Q(ind_excluido=1)) else: - raise TypeError( - 'Parameter must be a Model, AppConfig or a sequence of them') - - def migrate_model(self, model): - print('Migrando %s...' % model.__name__) - - 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 - nome_pk = model_legado._meta.pk.name - if 'ind_excluido' in {f.name for f in model_legado._meta.fields}: - # se o model legado tem o campo ind_excluido - # enumera apenas os não excluídos - old_records = model_legado.objects.filter(~Q(ind_excluido=1)) + old_records = model_legado.objects.all() + old_records = old_records.order_by(nome_pk) + + def get_id_do_legado(old): + return getattr(old, nome_pk) + else: + # a pk no legado tem mais de um campo + old_records = iter_sql_records(tabela_legado) + get_id_do_legado = None + + ajuste_antes_salvar = AJUSTE_ANTES_SALVAR.get(model) + ajuste_depois_salvar = AJUSTE_DEPOIS_SALVAR.get(model) + + # convert old records to new ones + with transaction.atomic(): + novos = [] + sql_delete_legado = '' + for old in old_records: + new = model() + try: + populate_renamed_fields(new, old) + if ajuste_antes_salvar: + ajuste_antes_salvar(new, old) + except ForeignKeyFaltando as e: + # tentamos preencher uma FK e o ojeto relacionado + # não existe + # então este é um objeo órfão: simplesmente ignoramos + warn('fk', e.msg, e.dados) + continue else: - old_records = model_legado.objects.all() - old_records = old_records.order_by(nome_pk) + if get_id_do_legado: + new.id = get_id_do_legado(old) - def get_id_do_legado(old): - return getattr(old, nome_pk) - else: - # a pk no legado tem mais de um campo - old_records = iter_sql_records(tabela_legado) - get_id_do_legado = None - - ajuste_antes_salvar = AJUSTE_ANTES_SALVAR.get(model) - ajuste_depois_salvar = AJUSTE_DEPOIS_SALVAR.get(model) - - # convert old records to new ones - with transaction.atomic(): - novos = [] - sql_delete_legado = '' - for old in old_records: - new = model() - try: - self.populate_renamed_fields(new, old) - if ajuste_antes_salvar: - ajuste_antes_salvar(new, old) - except ForeignKeyFaltando as e: - # tentamos preencher uma FK e o ojeto relacionado - # não existe - # então este é um objeo órfão: simplesmente ignoramos - warn('fk', e.msg, e.dados) - continue - else: - if get_id_do_legado: - new.id = get_id_do_legado(old) - - new.clean() # valida model - novos.append(new) # guarda para salvar - - # acumula deleção do registro no legado - sql_delete_legado += 'delete from {} where {};\n'.format( - tabela_legado, - ' and '.join( - '{} = "{}"'.format(campo, - getattr(old, campo)) - for campo in campos_pk_legado)) - - # salva novos registros - with reversion.create_revision(): - model.objects.bulk_create(novos) - reversion.set_comment('Objetos criados pela migração') + new.clean() # valida model + novos.append(new) # guarda para salvar - if ajuste_depois_salvar: - ajuste_depois_salvar() + # acumula deleção do registro no legado + sql_delete_legado += 'delete from {} where {};\n'.format( + tabela_legado, + ' and '.join( + '{} = "{}"'.format(campo, + getattr(old, campo)) + for campo in campos_pk_legado)) - # se configuramos ids explicitamente devemos reiniciar a sequence - if get_id_do_legado: - last_pk = get_last_pk(model) - reinicia_sequence(model, last_pk + 1) + # salva novos registros + with reversion.create_revision(): + model.objects.bulk_create(novos) + reversion.set_comment('Objetos criados pela migração') - # apaga registros migrados do legado - if sql_delete_legado: - exec_legado(sql_delete_legado) + if ajuste_depois_salvar: + ajuste_depois_salvar() + # se configuramos ids explicitamente devemos reiniciar a sequence + if get_id_do_legado: + last_pk = get_last_pk(model) + reinicia_sequence(model, last_pk + 1) -def migrar_dados(obj=appconfs, interativo=True): - dm = DataMigrator() - dm.migrar(obj, interativo) + # apaga registros migrados do legado + if sql_delete_legado: + exec_legado(sql_delete_legado) # MIGRATION_ADJUSTMENTS ##################################################### @@ -993,9 +1003,8 @@ def adjust_parlamentar(new, old): def adjust_participacao(new, old): 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'))] + get_fk_related(Composicao._meta.get_field(name), old) + for name in ('comissao', 'periodo')] with reversion.create_revision(): composicao, _ = Composicao.objects.get_or_create( comissao_id=comissao_id, periodo_id=periodo_id) @@ -1003,11 +1012,6 @@ def adjust_participacao(new, old): new.composicao = composicao -def adjust_proposicao_antes_salvar(new, old): - if new.data_envio: - new.ano = new.data_envio.year - - def adjust_normarelacionada(new, old): new.tipo_vinculo = TipoVinculoNormaJuridica.objects.get( sigla=old.tip_vinculo) @@ -1042,14 +1046,14 @@ def adjust_tipoafastamento(new, old): new.indicador = 'F' -MODEL_TIPO_MATERIA_OU_DOCUMENTO = {'M': TipoMateriaLegislativa, - 'D': TipoDocumento} +TIPO_MATERIA_OU_TIPO_DOCUMENTO = {'M': TipoMateriaLegislativa, + 'D': TipoDocumento} def adjust_tipoproposicao(new, old): "Aponta para o tipo relacionado de matéria ou documento" value = old.tip_mat_ou_doc - model_tipo = MODEL_TIPO_MATERIA_OU_DOCUMENTO[old.ind_mat_ou_doc] + model_tipo = TIPO_MATERIA_OU_TIPO_DOCUMENTO[old.ind_mat_ou_doc] tipo = model_tipo.objects.filter(pk=value) if tipo: new.tipo_conteudo_related = tipo[0] @@ -1060,6 +1064,16 @@ def adjust_tipoproposicao(new, old): label={'ind_mat_ou_doc': old.ind_mat_ou_doc}) +def adjust_proposicao_antes_salvar(new, old): + if new.data_envio: + new.ano = new.data_envio.year + if old.cod_mat_ou_doc: + tipo_mat_ou_doc = type(new.tipo.tipo_conteudo_related) + campo_falso = CAMPOS_FALSOS_PROPOSICAO[tipo_mat_ou_doc] + new.content_type = content_types[campo_falso.related_model] + new.object_id = get_fk_related(campo_falso, old) + + def adjust_statustramitacao(new, old): if old.ind_fim_tramitacao: new.indicador = 'F' diff --git a/sapl/materia/models.py b/sapl/materia/models.py index 23e4a6e3d..e9b366fa9 100644 --- a/sapl/materia/models.py +++ b/sapl/materia/models.py @@ -686,16 +686,12 @@ class Proposicao(models.Model): texto_articulado = GenericRelation( TextoArticulado, related_query_name='texto_articulado') - # FIXME - para a rotina de migração - este campo mudou - # retire o comentário quando resolver materia_de_vinculo = models.ForeignKey( MateriaLegislativa, blank=True, null=True, on_delete=models.CASCADE, verbose_name=_('Matéria anexadora'), related_name=_('proposicao_set')) - # FIXME - para a rotina de migração - estes campos mudaram - # retire o comentário quando resolver content_type = models.ForeignKey( ContentType, default=None, blank=True, null=True, verbose_name=_('Tipo de Material Gerado')) From 0005ed9a3d6b85cc14efb03af35f70cea49601b1 Mon Sep 17 00:00:00 2001 From: Marcio Mazza Date: Tue, 3 Apr 2018 17:55:03 -0300 Subject: [PATCH 09/10] =?UTF-8?q?Corrige=20vincula=C3=A7=C3=A3o=20de=20aut?= =?UTF-8?q?or=20p=20nova=20busca=20de=20fk?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sapl/legacy/migracao_dados.py | 64 +++++++++++++++++------------------ 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/sapl/legacy/migracao_dados.py b/sapl/legacy/migracao_dados.py index 14663f8ed..42114dbfa 100644 --- a/sapl/legacy/migracao_dados.py +++ b/sapl/legacy/migracao_dados.py @@ -131,14 +131,25 @@ for nome_novo, nome_antigo in (('comissao', 'cod_comissao'), Composicao._meta.get_field(nome_novo)] = nome_antigo -# campos virtuais de Proposicao para funcionarem com get_fk_related -CampoFalso = namedtuple('CampoFalso', ['model', 'related_model']) -CAMPOS_FALSOS_PROPOSICAO = { - TipoMateriaLegislativa: CampoFalso(Proposicao, MateriaLegislativa), - TipoDocumento: CampoFalso(Proposicao, DocumentoAdministrativo) +# campos virtuais de Proposicao para funcionar com get_fk_related +class CampoVirtual(namedtuple('CampoVirtual', 'model related_model')): + null = True + +CAMPOS_VIRTUAIS_PROPOSICAO = { + TipoMateriaLegislativa: CampoVirtual(Proposicao, MateriaLegislativa), + TipoDocumento: CampoVirtual(Proposicao, DocumentoAdministrativo) } -for campo_falso in CAMPOS_FALSOS_PROPOSICAO.values(): - campos_novos_para_antigos[campo_falso] = 'cod_mat_ou_doc' +for campo_virtual in CAMPOS_VIRTUAIS_PROPOSICAO.values(): + campos_novos_para_antigos[campo_virtual] = 'cod_mat_ou_doc' + +# campos virtuais de Autor para funcionar com get_fk_related +CAMPOS_VIRTUAIS_AUTOR = {related: CampoVirtual(Autor, related) + for related in (Parlamentar, Comissao, Partido)} +for related, campo_antigo in [(Parlamentar, 'cod_parlamentar'), + (Comissao, 'cod_comissao'), + (Partido, 'cod_partido')]: + campo_virtual = CAMPOS_VIRTUAIS_AUTOR[related] + campos_novos_para_antigos[campo_virtual] = campo_antigo # MIGRATION ################################################################# @@ -1069,9 +1080,9 @@ def adjust_proposicao_antes_salvar(new, old): new.ano = new.data_envio.year if old.cod_mat_ou_doc: tipo_mat_ou_doc = type(new.tipo.tipo_conteudo_related) - campo_falso = CAMPOS_FALSOS_PROPOSICAO[tipo_mat_ou_doc] - new.content_type = content_types[campo_falso.related_model] - new.object_id = get_fk_related(campo_falso, old) + campo_virtual = CAMPOS_VIRTUAIS_PROPOSICAO[tipo_mat_ou_doc] + new.content_type = content_types[campo_virtual.related_model] + new.object_id = get_fk_related(campo_virtual, old) def adjust_statustramitacao(new, old): @@ -1133,30 +1144,17 @@ def adjust_normajuridica_depois_salvar(): for assunto in assuntos) -def vincula_autor(new, old, model_relacionado, campo_relacionado, campo_nome): - pk_rel = getattr(old, campo_relacionado) - if pk_rel: - try: - new.autor_related = model_relacionado.objects.get(pk=pk_rel) - except ObjectDoesNotExist: - # ignoramos o autor órfão - nome_model_relacionado = model_relacionado._meta.model.__name__ - raise ForeignKeyFaltando( - field=Autor.autor_related, - value=[nome_model_relacionado, pk_rel], - label={'cod_autor': old.pk}) - else: - new.nome = getattr(new.autor_related, campo_nome) - return True - - def adjust_autor(new, old): - for args in [ - # essa ordem é importante - (Parlamentar, 'cod_parlamentar', 'nome_parlamentar'), - (Comissao, 'cod_comissao', 'nome'), - (Partido, 'cod_partido', 'nome')]: - if vincula_autor(new, old, *args): + # vincula autor com o objeto relacionado, tentando os três campos antigos + # o primeiro campo preenchido será usado, podendo lançar ForeignKeyFaltando + for model_relacionado, campo_nome in [(Parlamentar, 'nome_parlamentar'), + (Comissao, 'nome'), + (Partido, 'nome')]: + field = CAMPOS_VIRTUAIS_AUTOR[model_relacionado] + fk_encontrada = get_fk_related(field, old) + if fk_encontrada: + new.autor_related = model_relacionado.objects.get(id=fk_encontrada) + new.nome = getattr(new.autor_related, campo_nome) break if old.col_username: From 899f2527280f761ef28a20ea848c9c9724286b09 Mon Sep 17 00:00:00 2001 From: Marcio Mazza Date: Tue, 3 Apr 2018 18:19:08 -0300 Subject: [PATCH 10/10] Ajusta pk virtual p despacho_inicial acompanhando o agrupamento de despacho_inicial feito em iter_sql_records --- sapl/legacy/migracao_dados.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sapl/legacy/migracao_dados.py b/sapl/legacy/migracao_dados.py index 42114dbfa..22abf6dd1 100644 --- a/sapl/legacy/migracao_dados.py +++ b/sapl/legacy/migracao_dados.py @@ -168,6 +168,10 @@ def warn(tipo, msg, dados): @lru_cache() def get_pk_legado(tabela): + if tabela == 'despacho_inicial': + # adaptação para deleção correta no mysql ao final de migrar_model + # acompanha o agrupamento de despacho_inicial feito em iter_sql_records + return 'cod_materia', 'cod_comissao' res = exec_legado( 'show index from {} WHERE Key_name = "PRIMARY"'.format(tabela)) return [r[4] for r in res]