Browse Source

Completa campos faltantes na migração + novo log

Fix #1796

Incidentalmente agora mostra estrutura do legado no log de ocorrências
Fix #1794
pull/1847/head
Marcio Mazza 7 years ago
parent
commit
bc28f2e6ae
  1. 1
      sapl/base/legacy.yaml
  2. 1
      sapl/comissoes/legacy.yaml
  3. 649
      sapl/legacy/migracao_dados.py
  4. 5
      sapl/legacy/scripts/exporta_zope/exporta_zope.py
  5. 2
      sapl/legacy/scripts/migra_um_db.sh
  6. 98
      sapl/legacy/test_renames.py
  7. 9
      sapl/materia/legacy.yaml
  8. 10
      sapl/materia/models.py
  9. 1
      sapl/norma/legacy.yaml
  10. 4
      sapl/parlamentares/legacy.yaml
  11. 1
      sapl/protocoloadm/legacy.yaml
  12. 1
      sapl/sessao/legacy.yaml
  13. 3
      sapl/sessao/models.py

1
sapl/base/legacy.yaml

@ -5,4 +5,3 @@ Autor:
nome: nom_autor nome: nom_autor
cargo: des_cargo cargo: des_cargo
tipo: tip_autor tipo: tip_autor
username: col_username

1
sapl/comissoes/legacy.yaml

@ -25,7 +25,6 @@ Comissao:
telefone_secretaria: num_tel_secretaria telefone_secretaria: num_tel_secretaria
tipo: tip_comissao tipo: tip_comissao
unidade_deliberativa: ind_unid_deliberativa unidade_deliberativa: ind_unid_deliberativa
ativa:
Periodo (PeriodoCompComissao): Periodo (PeriodoCompComissao):
data_fim: dat_fim_periodo data_fim: dat_fim_periodo

649
sapl/legacy/migracao_dados.py

@ -1,31 +1,32 @@
import re import re
from collections import defaultdict import traceback
from collections import OrderedDict, defaultdict, namedtuple
from datetime import date from datetime import date
from functools import lru_cache, partial from functools import lru_cache, partial
from itertools import groupby from itertools import groupby
from operator import xor
from subprocess import PIPE, call from subprocess import PIPE, call
import pkg_resources import pkg_resources
import pytz
import reversion import reversion
import yaml import yaml
from django.apps import apps from django.apps import apps
from django.apps.config import AppConfig
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.db import connections, transaction from django.db import connections, transaction
from django.db.models import Max, Q from django.db.models import Max, Q
from django.db.models.base import ModelBase
from pytz import timezone
from unipath import Path from unipath import Path
from sapl.base.models import AppConfig as AppConf from sapl.base.models import AppConfig as AppConf
from sapl.base.models import Autor, TipoAutor, cria_models_tipo_autor from sapl.base.models import Autor, TipoAutor, cria_models_tipo_autor
from sapl.comissoes.models import Comissao, Composicao, Participacao from sapl.comissoes.models import Comissao, Composicao, Participacao
from sapl.legacy.models import NormaJuridica as OldNormaJuridica
from sapl.legacy.models import TipoNumeracaoProtocolo from sapl.legacy.models import TipoNumeracaoProtocolo
from sapl.materia.models import (AcompanhamentoMateria, Proposicao, from sapl.materia.models import (AcompanhamentoMateria, MateriaLegislativa,
StatusTramitacao, TipoDocumento, Proposicao, StatusTramitacao, TipoDocumento,
TipoMateriaLegislativa, TipoProposicao, TipoMateriaLegislativa, TipoProposicao,
Tramitacao) Tramitacao)
from sapl.norma.models import (AssuntoNorma, NormaJuridica, NormaRelacionada, from sapl.norma.models import (AssuntoNorma, NormaJuridica, NormaRelacionada,
@ -68,8 +69,6 @@ for a1, s1 in name_sets:
else: else:
assert not s1.intersection(s2) assert not s1.intersection(s2)
legacy_app = apps.get_app_config('legacy')
# RENAMES ################################################################### # RENAMES ###################################################################
@ -108,6 +107,51 @@ def get_renames():
return field_renames, model_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]
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 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_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 ################################################################# # MIGRATION #################################################################
@ -122,22 +166,48 @@ def warn(tipo, msg, dados):
print('CUIDADO! ' + msg.format(**dados)) print('CUIDADO! ' + msg.format(**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]
@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): class ForeignKeyFaltando(ObjectDoesNotExist):
'Uma FK aponta para um registro inexistente' 'Uma FK aponta para um registro inexistente'
def __init__(self, field, value, label): def __init__(self, field, valor, old):
self.field = field self.field = field
self.value = value self.valor = valor
self.label = label 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 @property
def dados(self): def dados(self):
return {'field': self.field.name, campo = campos_novos_para_antigos[self.field]
'value': self.value, _, tabela, campos_pk = get_estrutura_legado(self.field.model)
'model': self.field.model.__name__, pk = {c: getattr(self.old, c) for c in campos_pk}
'label': self.label} 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),
('sql', sql)))
@lru_cache() @lru_cache()
@ -146,18 +216,17 @@ def _get_all_ids_from_model(model):
return set(model.objects.values_list('id', flat=True)) return set(model.objects.values_list('id', flat=True))
def get_fk_related(field, value, label='---'): def get_fk_related(field, old):
if value is None and field.null: valor = getattr(old, campos_novos_para_antigos[field])
if valor is None and field.null:
return None return None
if valor in _get_all_ids_from_model(field.related_model):
# if field.related_model.objects.filter(id=value).exists(): return valor
if value in _get_all_ids_from_model(field.related_model): elif valor == 0 and field.null:
return value
elif value == 0 and field.null:
# consideramos zeros como nulos, se não está entre os ids anteriores # consideramos zeros como nulos, se não está entre os ids anteriores
return None return None
else: else:
raise ForeignKeyFaltando(field=field, value=value, label=label) raise ForeignKeyFaltando(field=field, valor=valor, old=old)
def exec_sql(sql, db='default'): def exec_sql(sql, db='default'):
@ -552,9 +621,21 @@ relatoria | tip_fim_relatoria = NULL | tip_fim_relatoria = 0
anula_tipos_origem_externa_invalidos() anula_tipos_origem_externa_invalidos()
def iter_sql_records(sql): class Record:
class Record: pass
pass
def iter_sql_records(tabela):
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) cursor = exec_legado(sql)
fieldnames = [name[0] for name in cursor.description] fieldnames = [name[0] for name in cursor.description]
for row in cursor.fetchall(): for row in cursor.fetchall():
@ -624,225 +705,207 @@ def reinicia_sequence(model, id):
sequence_name, 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() DIR_DADOS_MIGRACAO = Path('~/migracao_sapl/').expand()
PATH_TABELA_TIMEZONES = DIR_DADOS_MIGRACAO.child('tabela_timezones.yaml') PATH_TABELA_TIMEZONES = DIR_DADOS_MIGRACAO.child('tabela_timezones.yaml')
DIR_RESULTADOS = DIR_DADOS_MIGRACAO.child('resultados') DIR_RESULTADOS = DIR_DADOS_MIGRACAO.child('resultados')
class DataMigrator: def dict_representer(dumper, data):
return dumper.represent_dict(data.items())
yaml.add_representer(OrderedDict, dict_representer)
def __init__(self):
self.field_renames, self.model_renames = get_renames()
self.choice_valida = {}
# configura timezone de migração # configura timezone de migração
self.nome_banco_legado = DATABASES['legacy']['NAME'] nome_banco_legado = DATABASES['legacy']['NAME']
match = re.match('sapl_cm_(.*)', self.nome_banco_legado) match = re.match('sapl_cm_(.*)', nome_banco_legado)
sigla_casa = match.group(1) sigla_casa = match.group(1)
with open(PATH_TABELA_TIMEZONES, 'r') as arq: with open(PATH_TABELA_TIMEZONES, 'r') as arq:
tabela_timezones = yaml.load(arq) tabela_timezones = yaml.load(arq)
municipio, uf, nome_timezone = tabela_timezones[sigla_casa] municipio, uf, nome_timezone = tabela_timezones[sigla_casa]
if nome_timezone: if nome_timezone:
self.timezone = timezone(nome_timezone) timezone = pytz.timezone(nome_timezone)
else: else:
self.timezone = get_timezone(municipio, uf) timezone = get_timezone(municipio, uf)
def populate_renamed_fields(self, new, old):
renames = self.field_renames[type(new)]
for field in new._meta.fields: def populate_renamed_fields(new, old):
old_field_name = renames.get(field.name) 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() field_type = field.get_internal_type()
if old_field_name:
old_value = getattr(old, old_field_name) if field_type == 'ForeignKey':
fk_field_name = '{}_id'.format(field.name)
if field_type == 'ForeignKey': value = get_fk_related(field, old)
# not necessarily a model setattr(new, fk_field_name, value)
if hasattr(old, '_meta') and old._meta.pk.name != 'id':
label = 'pk = {}'.format(old.pk)
else:
label = '-- SEM PK --'
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)
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
else: else:
info('Migração cancelada.') value = getattr(old, old_field_name)
return 0
info('Excluindo entradas antigas do banco destino.') if (field_type in ['CharField', 'TextField']
call([PROJECT_DIR.child('manage.py'), 'flush', and value in [None, 'None']):
'--database=default', '--no-input'], stdout=PIPE) value = ''
# apaga tipos de autor padrão (criados no flush acima) # adiciona timezone faltante aos campos com tempo
TipoAutor.objects.all().delete() # os campos TIMESTAMP do mysql são gravados em UTC
# os DATETIME e TIME não têm timezone
fill_vinculo_norma_juridica() def campo_tempo_sem_timezone(tipo):
fill_dados_basicos() return (field_type == tipo
info('Começando migração: %s...' % obj) and value and not value.tzinfo)
try: if campo_tempo_sem_timezone('DateTimeField'):
ocorrencias.clear() value = timezone.localize(value)
dir_ocorrencias = DIR_RESULTADOS.child(date.today().isoformat()) if campo_tempo_sem_timezone('TimeField'):
dir_ocorrencias.mkdir(parents=True) value = value.replace(tzinfo=timezone)
self._do_migrate(obj)
finally: setattr(new, field.name, value)
# grava ocorrências
arq_ocorrencias = dir_ocorrencias.child(
self.nome_banco_legado + '.yaml') def migrar_dados(interativo=True):
with open(arq_ocorrencias, 'w') as arq: uniformiza_banco()
yaml.safe_dump(dict(ocorrencias), arq, allow_unicode=True)
info('Ocorrências salvas em\n {}'.format(arq_ocorrencias)) # excluindo database antigo.
if interativo:
# recria tipos de autor padrão que não foram criados pela migração info('Todos os dados do banco serão excluidos. '
cria_models_tipo_autor() 'Recomendamos que faça backup do banco sapl '
'antes de continuar.')
def _do_migrate(self, obj): info('Deseja continuar? [s/n]')
if isinstance(obj, AppConfig): resposta = input()
models = [model for model in obj.models.values() if resposta.lower() in ['s', 'sim', 'y', 'yes']:
if model in self.field_renames] pass
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)
else: else:
raise TypeError( info('Migração cancelada.')
'Parameter must be a Model, AppConfig or a sequence of them') return 0
info('Excluindo entradas antigas do banco destino.')
def migrate_model(self, model): call([PROJECT_DIR.child('manage.py'), 'flush',
print('Migrando %s...' % model.__name__) '--database=default', '--no-input'], stdout=PIPE)
nome_model = self.model_renames.get(model, model.__name__) # apaga tipos de autor padrão (criados no flush acima)
model_legado = legacy_app.get_model(nome_model) TipoAutor.objects.all().delete()
tabela_legado = model_legado._meta.db_table
campos_pk = get_pk_legado(tabela_legado) fill_vinculo_norma_juridica()
fill_dados_basicos()
if len(campos_pk) == 1: info('Começando migração: ...')
# a pk no legado tem um único campo try:
nome_pk = model_legado._meta.pk.name ocorrencias.clear()
if 'ind_excluido' in {f.name for f in model_legado._meta.fields}: dir_ocorrencias = DIR_RESULTADOS.child(date.today().isoformat())
# se o model legado tem o campo ind_excluido dir_ocorrencias.mkdir(parents=True)
# enumera apenas os não excluídos migrar_todos_os_models()
old_records = model_legado.objects.filter(~Q(ind_excluido=1)) 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:
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: else:
old_records = model_legado.objects.all() if get_id_do_legado:
old_records = old_records.order_by(nome_pk) new.id = get_id_do_legado(old)
def get_id_do_legado(old): new.clean() # valida model
return getattr(old, nome_pk) novos.append(new) # guarda para salvar
else:
# a pk no legado tem mais de um campo # acumula deleção do registro no legado
sql = 'select * from ' + tabela_legado sql_delete_legado += 'delete from {} where {};\n'.format(
if existe_coluna_no_legado(tabela_legado, 'ind_excluido'): tabela_legado,
sql += ' where ind_excluido != 1' ' and '.join(
old_records = iter_sql_records(sql) '{} = "{}"'.format(campo,
getattr(old, campo))
get_id_do_legado = None for campo in campos_pk_legado))
ajuste_antes_salvar = AJUSTE_ANTES_SALVAR.get(model) # salva novos registros
ajuste_depois_salvar = AJUSTE_DEPOIS_SALVAR.get(model) with reversion.create_revision():
model.objects.bulk_create(novos)
# convert old records to new ones reversion.set_comment('Objetos criados pela migração')
with transaction.atomic():
sql_delete_legado = '' if ajuste_depois_salvar:
for old in old_records: ajuste_depois_salvar()
new = model()
try: # se configuramos ids explicitamente devemos reiniciar a sequence
self.populate_renamed_fields(new, old) if get_id_do_legado:
if ajuste_antes_salvar: last_pk = get_last_pk(model)
ajuste_antes_salvar(new, old) reinicia_sequence(model, last_pk + 1)
except ForeignKeyFaltando as e:
# tentamos preencher uma FK e o ojeto relacionado # apaga registros migrados do legado
# não existe if sql_delete_legado:
# então este é um objeo órfão: simplesmente ignoramos exec_legado(sql_delete_legado)
warn('fk', e.msg, e.dados)
continue
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')
# 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))
if ajuste_depois_salvar:
ajuste_depois_salvar(new, old)
# 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)
# apaga registros migrados do legado
if sql_delete_legado:
exec_legado(sql_delete_legado)
def migrar_dados(obj=appconfs, interativo=True):
dm = DataMigrator()
dm.migrar(obj, interativo)
# MIGRATION_ADJUSTMENTS ##################################################### # MIGRATION_ADJUSTMENTS #####################################################
@ -954,37 +1017,19 @@ def adjust_parlamentar(new, old):
def adjust_participacao(new, old): def adjust_participacao(new, old):
composicao = Composicao() comissao_id, periodo_id = [
composicao.comissao_id, composicao.periodo_id = [ get_fk_related(Composicao._meta.get_field(name), old)
get_fk_related(Composicao._meta.get_field(name), for name in ('comissao', 'periodo')]
value, with reversion.create_revision():
'composicao_comissao.cod_comp_comissao = {}'.format( composicao, _ = Composicao.objects.get_or_create(
old.pk comissao_id=comissao_id, periodo_id=periodo_id)
)) reversion.set_comment('Objeto criado pela migração')
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')
new.composicao = composicao 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): def adjust_normarelacionada(new, old):
tipo = TipoVinculoNormaJuridica.objects.filter(sigla=old.tip_vinculo) new.tipo_vinculo = TipoVinculoNormaJuridica.objects.get(
assert len(tipo) == 1 sigla=old.tip_vinculo)
new.tipo_vinculo = tipo[0]
def adjust_protocolo_antes_salvar(new, old): def adjust_protocolo_antes_salvar(new, old):
@ -1009,18 +1054,21 @@ def adjust_registrovotacao_antes_salvar(new, old):
def adjust_tipoafastamento(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' new.indicador = 'A'
elif old.ind_fim_mandato:
new.indicador = 'F'
MODEL_TIPO_MATERIA_OU_DOCUMENTO = {'M': TipoMateriaLegislativa, TIPO_MATERIA_OU_TIPO_DOCUMENTO = {'M': TipoMateriaLegislativa,
'D': TipoDocumento} 'D': TipoDocumento}
def adjust_tipoproposicao(new, old): def adjust_tipoproposicao(new, old):
"Aponta para o tipo relacionado de matéria ou documento" "Aponta para o tipo relacionado de matéria ou documento"
value = old.tip_mat_ou_doc 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) tipo = model_tipo.objects.filter(pk=value)
if tipo: if tipo:
new.tipo_conteudo_related = tipo[0] new.tipo_conteudo_related = tipo[0]
@ -1028,7 +1076,17 @@ def adjust_tipoproposicao(new, old):
raise ForeignKeyFaltando( raise ForeignKeyFaltando(
field=TipoProposicao.tipo_conteudo_related, field=TipoProposicao.tipo_conteudo_related,
value=(model_tipo.__name__, value), 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_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_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): def adjust_statustramitacao(new, old):
@ -1067,47 +1125,40 @@ def adjust_normajuridica_antes_salvar(new, old):
new.esfera_federacao = '' new.esfera_federacao = ''
def adjust_normajuridica_depois_salvar(new, old): def adjust_normajuridica_depois_salvar():
# Ajusta relação M2M # Ajusta relação M2M
ligacao = NormaJuridica.assuntos.through
if not old.cod_assunto: # it can be null or empty assuntos_migrados, normas_migradas = [
return set(model.objects.values_list('id', flat=True))
for model in [AssuntoNorma, NormaJuridica]]
# lista de pks separadas por vírgulas (ignorando strings vazias) def filtra_assuntos_migrados(cod_assunto):
lista_pks_assunto = [int(pk) for pk in old.cod_assunto.split(',') if pk] return [a for a in map(int, cod_assunto.split(','))
if a in assuntos_migrados]
for pk_assunto in lista_pks_assunto:
try: norma_para_assuntos = [
new.assuntos.add(AssuntoNorma.objects.get(pk=pk_assunto)) (norma, filtra_assuntos_migrados(cod_assunto))
except ObjectDoesNotExist: for norma, cod_assunto in OldNormaJuridica.objects.filter(
pass # ignora assuntos inexistentes pk__in=normas_migradas).values_list('pk', 'cod_assunto')]
ligacao.objects.bulk_create(
def vincula_autor(new, old, model_relacionado, campo_relacionado, campo_nome): ligacao(normajuridica_id=norma, assuntonorma_id=assunto)
pk_rel = getattr(old, campo_relacionado) for norma, assuntos in norma_para_assuntos
if pk_rel: for assunto in assuntos)
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='{} [pk={}] inexistente para autor'.format(
nome_model_relacionado, pk_rel))
else:
new.nome = getattr(new.autor_related, campo_nome)
return True
def adjust_autor(new, old): def adjust_autor(new, old):
for args in [ # vincula autor com o objeto relacionado, tentando os três campos antigos
# essa ordem é importante # o primeiro campo preenchido será usado, podendo lançar ForeignKeyFaltando
(Parlamentar, 'cod_parlamentar', 'nome_parlamentar'), for model_relacionado, campo_nome in [(Parlamentar, 'nome_parlamentar'),
(Comissao, 'cod_comissao', 'nome'), (Comissao, 'nome'),
(Partido, 'cod_partido', 'nome')]: (Partido, 'nome')]:
if vincula_autor(new, old, *args): 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 break
if old.col_username: if old.col_username:

5
sapl/legacy/scripts/exporta_zope/exporta_zope.py

@ -14,7 +14,6 @@ from functools import partial
import magic import magic
import yaml import yaml
import ZODB.DB import ZODB.DB
import ZODB.FileStorage import ZODB.FileStorage
from ZODB.broken import Broken from ZODB.broken import Broken
@ -191,7 +190,9 @@ def read_sde(element):
] ]
if meta_type != 'Script (Python)': if meta_type != 'Script (Python)':
# ignoramos os scrips python de eventos dos templates # 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()) data = dict(read_properties())
children = list(read_children()) children = list(read_children())

2
sapl/legacy/scripts/migra_um_db.sh

@ -39,7 +39,7 @@ if [ $# -ge 2 ]; then
echo "--- MIGRACAO ---" | tee -a $LOG echo "--- MIGRACAO ---" | tee -a $LOG
echo >> $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 echo >> $LOG
else else
echo "USO:" echo "USO:"

98
sapl/legacy/test_renames.py

@ -1,29 +1,76 @@
import sapl.comissoes
import sapl.materia from django.contrib.contenttypes.fields import GenericForeignKey
import sapl.norma
import sapl.sessao 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 from .migracao_dados import appconfs, get_renames, legacy_app
RENAMING_IGNORED_MODELS = [ RENAMING_IGNORED_MODELS = [
sapl.comissoes.models.Composicao, Votante, Frente, Bancada, CargoBancada, Bloco, # parlamentares
sapl.norma.models.AssuntoNormaRelationship, 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 = [ RENAMING_IGNORED_FIELDS = [
(sapl.comissoes.models.Participacao, {'composicao'}), (TipoAfastamento, {'indicador'}),
(sapl.materia.models.Proposicao, {'documento'}), (Participacao, {'composicao'}),
(sapl.materia.models.TipoProposicao, {'tipo_documento'}), (Proposicao, {
(sapl.materia.models.Tramitacao, {'ultima'}), 'ano', 'content_type', 'object_id', 'conteudo_gerado_related',
(sapl.sessao.models.SessaoPlenaria, {'finalizada', 'status', 'hash_code', 'texto_original'}),
'upload_pauta', (TipoProposicao, {
'upload_ata', 'object_id', 'content_type', 'tipo_conteudo_related', 'perfis',
'iniciada'}), # não estou entendendo como esses campos são enumerados,
(sapl.sessao.models.ExpedienteMateria, {'votacao_aberta'}), # mas eles não fazem parte da migração
(sapl.sessao.models.OrdemDia, {'votacao_aberta'}), # '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() field_renames, model_renames = get_renames()
all_models = {m for ac in appconfs for m in ac.get_models()} all_models = {m for ac in appconfs for m in ac.get_models()}
for model in all_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: if model not in field_renames:
# check ignored models in renaming # check ignored models in renaming
assert model in RENAMING_IGNORED_MODELS assert model in RENAMING_IGNORED_MODELS
@ -46,11 +95,12 @@ def test_get_renames():
match_msg_template % ('new', 'current') match_msg_template % ('new', 'current')
# ignored fields are explicitly listed # ignored fields are explicitly listed
missing_in_renames = field_names - renamed missing = field_names - renamed
if missing_in_renames: if missing:
assert (model, missing_in_renames) in \ assert (model, missing) in RENAMING_IGNORED_FIELDS, \
RENAMING_IGNORED_FIELDS, \ 'Campos faltando na renomeação,' \
'Field(s) missing in renames but not explicitly listed' 'mas não listados explicitamente: ({}, {})'.format(
model.__name__, missing)
# all old names correspond to a legacy field # all old names correspond to a legacy field
legacy_model = legacy_app.get_model( legacy_model = legacy_app.get_model(

9
sapl/materia/legacy.yaml

@ -58,9 +58,8 @@ AssuntoMateria:
dispositivo: des_dispositivo dispositivo: des_dispositivo
DespachoInicial: DespachoInicial:
comissao: cod_comissao
materia: cod_materia materia: cod_materia
numero_ordem: num_ordem comissao: cod_comissao
TipoDocumento: TipoDocumento:
descricao: des_tipo_documento descricao: des_tipo_documento
@ -112,10 +111,6 @@ Parecer:
TipoProposicao: TipoProposicao:
descricao: des_tipo_proposicao descricao: des_tipo_proposicao
materia_ou_documento: ind_mat_ou_doc
modelo: nom_modelo
tipo_documento:
tipo_materia:
Proposicao: Proposicao:
autor: cod_autor autor: cod_autor
@ -124,7 +119,7 @@ Proposicao:
data_recebimento: dat_recebimento data_recebimento: dat_recebimento
descricao: txt_descricao descricao: txt_descricao
justificativa_devolucao: txt_justif_devolucao justificativa_devolucao: txt_justif_devolucao
materia: cod_mat_ou_doc materia_de_vinculo: cod_materia
numero_proposicao: num_proposicao numero_proposicao: num_proposicao
tipo: tip_proposicao tipo: tip_proposicao

10
sapl/materia/models.py

@ -40,9 +40,6 @@ class TipoProposicao(models.Model):
error_messages={ error_messages={
'unique': _('Já existe um Tipo de Proposição com esta descrição.') '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, content_type = models.ForeignKey(ContentType, default=None,
on_delete=models.PROTECT, on_delete=models.PROTECT,
verbose_name=_('Definição de Tipo')) verbose_name=_('Definição de Tipo'))
@ -378,9 +375,6 @@ class AssuntoMateria(models.Model):
@reversion.register() @reversion.register()
class DespachoInicial(models.Model): 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) materia = models.ForeignKey(MateriaLegislativa, on_delete=models.CASCADE)
comissao = models.ForeignKey(Comissao, on_delete=models.CASCADE) comissao = models.ForeignKey(Comissao, on_delete=models.CASCADE)
@ -692,16 +686,12 @@ class Proposicao(models.Model):
texto_articulado = GenericRelation( texto_articulado = GenericRelation(
TextoArticulado, related_query_name='texto_articulado') 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( materia_de_vinculo = models.ForeignKey(
MateriaLegislativa, blank=True, null=True, MateriaLegislativa, blank=True, null=True,
on_delete=models.CASCADE, on_delete=models.CASCADE,
verbose_name=_('Matéria anexadora'), verbose_name=_('Matéria anexadora'),
related_name=_('proposicao_set')) related_name=_('proposicao_set'))
# FIXME - para a rotina de migração - estes campos mudaram
# retire o comentário quando resolver
content_type = models.ForeignKey( content_type = models.ForeignKey(
ContentType, default=None, blank=True, null=True, ContentType, default=None, blank=True, null=True,
verbose_name=_('Tipo de Material Gerado')) verbose_name=_('Tipo de Material Gerado'))

1
sapl/norma/legacy.yaml

@ -9,7 +9,6 @@ TipoNormaJuridica:
NormaJuridica: NormaJuridica:
ano: ano_norma ano: ano_norma
assunto: cod_assunto
complemento: ind_complemento complemento: ind_complemento
data: dat_norma data: dat_norma
data_publicacao: dat_publicacao data_publicacao: dat_publicacao

4
sapl/parlamentares/legacy.yaml

@ -38,7 +38,6 @@ Parlamentar:
ativo: ind_ativo ativo: ind_ativo
biografia: txt_biografia biografia: txt_biografia
cep_residencia: num_cep_resid cep_residencia: num_cep_resid
cod_casa: cod_casa
cpf: num_cpf cpf: num_cpf
data_nascimento: dat_nascimento data_nascimento: dat_nascimento
email: end_email email: end_email
@ -58,7 +57,6 @@ Parlamentar:
telefone: num_tel_parlamentar telefone: num_tel_parlamentar
telefone_residencia: num_tel_resid telefone_residencia: num_tel_resid
titulo_eleitor: num_tit_eleitor titulo_eleitor: num_tit_eleitor
unidade_deliberativa: ind_unid_deliberativa
TipoDependente: TipoDependente:
descricao: des_tipo_dependente descricao: des_tipo_dependente
@ -80,10 +78,8 @@ Filiacao:
partido: cod_partido partido: cod_partido
TipoAfastamento: TipoAfastamento:
afastamento: ind_afastamento
descricao: des_afastamento descricao: des_afastamento
dispositivo: des_dispositivo dispositivo: des_dispositivo
fim_mandato: ind_fim_mandato
Mandato: Mandato:
coligacao: cod_coligacao coligacao: cod_coligacao

1
sapl/protocoloadm/legacy.yaml

@ -11,7 +11,6 @@ DocumentoAdministrativo:
dias_prazo: num_dias_prazo dias_prazo: num_dias_prazo
interessado: txt_interessado interessado: txt_interessado
numero: num_documento numero: num_documento
numero_protocolo: num_protocolo
observacao: txt_observacao observacao: txt_observacao
tipo: tip_documento tipo: tip_documento
tramitacao: ind_tramitacao tramitacao: ind_tramitacao

1
sapl/sessao/legacy.yaml

@ -52,7 +52,6 @@ OradorExpediente (OradoresExpediente): {}
OrdemDia: {} OrdemDia: {}
PresencaOrdemDia (OrdemDiaPresenca): PresencaOrdemDia (OrdemDiaPresenca):
data_ordem: dat_ordem
parlamentar: cod_parlamentar parlamentar: cod_parlamentar
sessao_plenaria: cod_sessao_plen sessao_plenaria: cod_sessao_plen

3
sapl/sessao/models.py

@ -436,7 +436,8 @@ class RegistroVotacao(models.Model):
if not xor(bool(self.ordem), bool(self.expediente)): if not xor(bool(self.ordem), bool(self.expediente)):
raise ValidationError( raise ValidationError(
'RegistroVotacao deve ter exatamente um dos campos ' '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() @reversion.register()

Loading…
Cancel
Save