Browse Source

Refatora unificação de autores repetidos

Com alguns testes acrescentados
pull/1596/head
Marcio Mazza 7 years ago
parent
commit
7768da56ed
  1. 360
      sapl/legacy/migration.py
  2. 13
      sapl/legacy/scripts/utils.py
  3. 55
      sapl/legacy/test_migration.py

360
sapl/legacy/migration.py

@ -1,12 +1,12 @@
import re import re
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 subprocess import PIPE, call from subprocess import PIPE, call
import pkg_resources import pkg_resources
import yaml
import reversion import reversion
import yaml
from django.apps import apps from django.apps import apps
from django.apps.config import AppConfig from django.apps.config import AppConfig
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
@ -16,6 +16,7 @@ from django.core.exceptions import ObjectDoesNotExist
from django.db import connections, transaction from django.db import connections, transaction
from django.db.models import Count, Max from django.db.models import Count, Max
from django.db.models.base import ModelBase from django.db.models.base import ModelBase
from sapl.base.models import AppConfig as AppConf from sapl.base.models import AppConfig as AppConf
from sapl.base.models import (Autor, CasaLegislativa, ProblemaMigracao, from sapl.base.models import (Autor, CasaLegislativa, ProblemaMigracao,
TipoAutor) TipoAutor)
@ -144,28 +145,32 @@ def exec_sql(sql, db='default'):
cursor.execute(sql) cursor.execute(sql)
return cursor return cursor
# UNIFORMIZAÇÃO DO BANCO ANTES DA MIGRAÇÃO ############################### exec_legado = partial(exec_sql, db='legacy')
def primeira_coluna(cursor):
return (r[0] for r in cursor)
# UNIFORMIZAÇÃO DO BANCO ANTES DA MIGRAÇÃO ###############################
SQL_NAO_TEM_TABELA = ''' SQL_NAO_TEM_TABELA = '''
SELECT count(*) SELECT count(*)
FROM information_schema.columns FROM information_schema.columns
WHERE table_schema=database() WHERE table_schema=database()
AND TABLE_NAME="{}" AND TABLE_NAME="{}"
''' '''
SQL_NAO_TEM_COLUNA = SQL_NAO_TEM_TABELA + ' AND COLUMN_NAME="{}"'
exec_legado = partial(exec_sql, db='legacy')
def existe_tabela_no_legado(tabela): def existe_tabela_no_legado(tabela):
sql = SQL_NAO_TEM_TABELA.format(tabela) sql = SQL_NAO_TEM_TABELA.format(tabela)
return exec_legado(sql).fetchone()[0] return primeira_coluna(exec_legado(sql))[0]
def existe_coluna_no_legado(tabela, coluna): def existe_coluna_no_legado(tabela, coluna):
sql = SQL_NAO_TEM_COLUNA.format(tabela, coluna) sql_nao_tem_coluna = SQL_NAO_TEM_TABELA + ' AND COLUMN_NAME="{}"'
return exec_legado(sql).fetchone()[0] > 0 sql = sql_nao_tem_coluna.format(tabela, coluna)
return primeira_coluna(exec_legado(sql))[0] > 0
def garante_coluna_no_legado(tabela, spec_coluna): def garante_coluna_no_legado(tabela, spec_coluna):
@ -182,223 +187,120 @@ def garante_tabela_no_legado(create_table):
assert existe_tabela_no_legado(tabela) assert existe_tabela_no_legado(tabela)
def migra_autor(): TABELAS_REFERENCIANDO_AUTOR = [
# <nome da tabela>, <tem ind excluido>
SQL_ENUMERA_REPETIDOS = ''' ('autoria', True),
select cod_parlamentar, COUNT(*) ('documento_administrativo', True),
from autor where cod_parlamentar is not null ('proposicao', True),
group by cod_parlamentar ('protocolo', False)]
having 1 < COUNT(*)
order by cod_parlamentar asc;
''' def reverte_exclusao_de_autores_referenciados_no_legado():
SQL_INFOS_AUTOR = ''' def get_autores_referenciados(tabela, tem_ind_excluido):
select cod_autor from autor sql = '''select distinct cod_autor from {}
where cod_parlamentar = {} where cod_autor is not null
group by cod_autor '''.format(tabela)
order by col_username, des_cargo desc; if tem_ind_excluido:
''' sql += ' and ind_excluido != 1'
return primeira_coluna(exec_legado(sql))
SQL_UPDATE_AUTOR = "update autoria set cod_autor = {} where cod_autor in ({});"
# reverte exclusões de autores referenciados por outras tabelas
SQL_ENUMERA_AUTORIA_REPETIDOS = ''' autores_referenciados = {
select cod_materia, COUNT(*) from autoria where cod_autor in ({}) cod
group by cod_materia for tabela, tem_ind_excluido in TABELAS_REFERENCIANDO_AUTOR
having 1 < COUNT(*); for cod in get_autores_referenciados(tabela, tem_ind_excluido)}
''' exec_legado(
'update autor set ind_excluido = 0 where cod_autor in {}'.format(
SQL_DELETE_AUTORIA = ''' tuple(autores_referenciados)
delete from autoria where cod_materia in ({}) and cod_autor in ({}); ))
'''
SQL_UPDATE_DOCUMENTO_ADMINISTRATIVO = ''' def get_reapontamento_de_autores_repetidos(autores):
update documento_administrativo """ Dada uma lista ordenada de pares (cod_zzz, cod_autor) retorna:
set cod_autor = {}
where cod_autor in ({}); * a lista de grupos de cod_autor'es repetidos
''' (quando mais de um cod_autor para um mesmo cod_zzz)
SQL_UPDATE_PROPOSICAO = ''' * a lista de cod_autor'es a serem apagados (todos além do 1o de cada grupo)
update proposicao """
set cod_autor = {} grupos_de_repetidos = [
where cod_autor in ({}); [cod_autor for _, cod_autor in grupo]
''' for cod_zzz, grupo in groupby(autores, lambda r: r[0])]
# mantém apenas os grupos com mais de um autor por cod_zzz
SQL_UPDATE_PROTOCOLO = ''' grupos_de_repetidos = [g for g in grupos_de_repetidos if len(g) > 1]
update protocolo # aponta cada autor de cada grupo de repetidos para o 1o do seu grupo
set cod_autor = {} reapontamento = {autor: grupo[0]
where cod_autor in ({}); for grupo in grupos_de_repetidos
''' for autor in grupo}
# apagaremos todos menos o primeiro
SQL_DELETE_AUTOR = ''' apagar = [k for k, v in reapontamento.items() if k != v]
delete from autor where cod_autor in ({}) return reapontamento, apagar
and cod_autor not in ({});
'''
def get_autorias_sem_repeticoes(autoria, reapontamento):
cursor = exec_legado('update autor set ind_excluido = 0 where cod_autor is not null;') "Autorias sem repetições de autores e com ind_primeiro_autor ajustado"
cursor = exec_legado(SQL_ENUMERA_REPETIDOS)
# substitui cada autor repetido pelo 1o de seu grupo
autores_parlamentares = [r[0] for r in cursor if r[0]] autoria = sorted((reapontamento[a], m, i) for a, m, i in autoria)
# agrupa por [autor (1o do grupo de repetidos), materia], com
for cod_autor in autores_parlamentares: # ind_primeiro_autor == 1 se isso acontece em qualquer autor do grupo
autoria = [(a, m, max(i for a, m, i in grupo))
sql = SQL_INFOS_AUTOR.format(cod_autor) for (a, m), grupo in groupby(autoria, lambda x: x[:2])]
return autoria
cursor = exec_legado(sql)
autores = []
def unifica_autores_repetidos_no_legado(campo_agregador):
for response in cursor: "Reúne autores repetidos em um único, antes da migracão"
autores.append(response)
# enumeramos a repeticoes segundo o campo relevante
ids = [a[0] for a in autores] # (p. ex. cod_parlamentar ou cod_comissao)
id_ativo, ids_inativos = ids[-1], ids[:-1] # a ordenação prioriza, as entradas:
ids = str(ids).strip('[]') # - não excluidas,
id_ativo = str(id_ativo).strip('[]') # - em seguida as que têm col_username,
ids_inativos = str(ids_inativos).strip('[]') # - em seguida as que têm des_cargo
autores = exec_legado('''
tabelas = ['autoria', 'documento_administrativo', select {cod_parlamentar}, cod_autor from autor
'proposicao', 'protocolo'] where {cod_parlamentar} is not null
for tabela in tabelas: order by {cod_parlamentar},
if tabela == 'autoria' and id_ativo and ids_inativos: ind_excluido, col_username desc, des_cargo desc'''.format(
# Para update e delete no MySQL -> SET SQL_SAFE_UPDATES = 0; cod_parlamentar=campo_agregador))
sql = SQL_ENUMERA_AUTORIA_REPETIDOS.format(ids)
cursor = exec_legado(sql) reapontamento, apagar = get_reapontamento_de_autores_repetidos(autores)
materias = [] # Reaponta AUTORIA (many-to-many)
for response in cursor:
materias.append(response[0]) # simplificamos retirando inicialmente as autorias excluidas
exec_legado('delete from autoria where ind_excluido = 1')
materias = str(materias).strip('[]')
if materias: # selecionamos as autorias envolvidas em repetições de autores
sql = SQL_DELETE_AUTORIA.format(materias, ids_inativos) from_autoria = ' from autoria where cod_autor in {}'.format(
exec_legado(sql) tuple(reapontamento))
autoria = exec_legado(
sql = SQL_UPDATE_AUTOR.format(id_ativo, ids_inativos) 'select cod_autor, cod_materia, ind_primeiro_autor' + from_autoria)
exec_legado(sql)
# apagamos todas as autorias envolvidas
elif tabela == 'documento_administrativo' and id_ativo and ids_inativos: exec_legado('delete ' + from_autoria)
sql = SQL_UPDATE_DOCUMENTO_ADMINISTRATIVO.format(id_ativo, ids_inativos) # e depois inserimos apenas as sem repetições c ind_primeiro_autor ajustado
exec_legado(sql) nova_autoria = get_autorias_sem_repeticoes(autoria, reapontamento)
exec_legado('''
elif tabela == 'proposicao' and id_ativo and ids_inativos: insert into autoria
sql = SQL_UPDATE_PROPOSICAO.format(id_ativo, ids_inativos) (cod_autor, cod_materia, ind_primeiro_autor, ind_excluido)
exec_legado(sql) values {}'''.format(', '.join([str((a, m, i, 0))
for a, m, i in nova_autoria])))
elif tabela == 'protocolo' and id_ativo and ids_inativos:
sql = SQL_UPDATE_PROTOCOLO.format(id_ativo, ids_inativos)
exec_legado(sql)
# Faz a exclusão dos autores que não serão migrados
sql = SQL_DELETE_AUTOR.format(ids, id_ativo)
cursor = exec_legado(sql)
def migra_comissao():
SQL_ENUMERA_REPETIDOS = '''
select cod_comissao, COUNT(*)
from autor where cod_comissao is not null
group by cod_comissao
having 1 < COUNT(*)
order by cod_comissao asc;
'''
SQL_INFOS_COMISSAO = '''
select cod_autor from autor
where cod_comissao = {}
group by cod_autor;
'''
SQL_UPDATE_AUTOR = "update autoria set cod_autor = {} where cod_autor in ({});"
SQL_ENUMERA_AUTORIA_REPETIDOS = '''
select cod_materia, COUNT(*) from autoria where cod_autor in ({})
group by cod_materia
having 1 < COUNT(*);
'''
SQL_DELETE_AUTORIA = '''
delete from autoria where cod_materia in ({}) and cod_autor in ({});
'''
SQL_UPDATE_DOCUMENTO_ADMINISTRATIVO = '''
update documento_administrativo
set cod_autor = {}
where cod_autor in ({});
'''
SQL_UPDATE_PROPOSICAO = '''
update proposicao
set cod_autor = {}
where cod_autor in ({});
'''
SQL_UPDATE_PROTOCOLO = '''
update protocolo
set cod_autor = {}
where cod_autor in ({});
'''
SQL_DELETE_AUTOR = '''
delete from autor where cod_autor in ({})
and cod_autor not in ({});
'''
cursor = exec_legado('update autor set ind_excluido = 0 where cod_comissao is not null;')
cursor = exec_legado(SQL_ENUMERA_REPETIDOS)
comissoes_parlamentares = [r[0] for r in cursor if r[0]]
for cod_comissao in comissoes_parlamentares:
sql = SQL_INFOS_COMISSAO.format(cod_comissao)
cursor = exec_legado(sql)
comissoes = []
for response in cursor:
comissoes.append(response)
ids = [c[0] for c in comissoes]
id_ativo, ids_inativos = ids[-1], ids[:-1]
ids = str(ids).strip('[]')
id_ativo = str(id_ativo).strip('[]')
ids_inativos = str(ids_inativos).strip('[]')
tabelas = ['autoria', 'documento_administrativo',
'proposicao', 'protocolo']
for tabela in tabelas:
if tabela == 'autoria' and id_ativo and ids_inativos:
# Para update e delete no MySQL -> SET SQL_SAFE_UPDATES = 0;
sql = SQL_ENUMERA_AUTORIA_REPETIDOS.format(ids)
cursor = exec_legado(sql)
materias = []
for response in cursor:
materias.append(response[0])
materias = str(materias).strip('[]')
if materias:
sql = SQL_DELETE_AUTORIA.format(materias, ids_inativos)
exec_legado(sql)
sql = SQL_UPDATE_AUTOR.format(id_ativo, ids_inativos)
exec_legado(sql)
elif tabela == 'documento_administrativo' and id_ativo and ids_inativos:
sql = SQL_UPDATE_DOCUMENTO_ADMINISTRATIVO.format(id_ativo, ids_inativos)
exec_legado(sql)
elif tabela == 'proposicao' and id_ativo and ids_inativos:
sql = SQL_UPDATE_PROPOSICAO.format(id_ativo, ids_inativos)
exec_legado(sql)
elif tabela == 'protocolo' and id_ativo and ids_inativos: # Reaponta outras tabelas que referenciam autor
sql = SQL_UPDATE_PROTOCOLO.format(id_ativo, ids_inativos) for tabela, _ in TABELAS_REFERENCIANDO_AUTOR:
exec_legado(sql) for antigo, novo in reapontamento.items():
if antigo != novo:
exec_legado('''
update {} set cod_autor = {} where cod_autor = {}
'''.format(tabela, novo, antigo))
# Faz a exclusão dos autores que não serão migrados # Finalmente excluimos os autores redundantes,
sql = SQL_DELETE_AUTOR.format(ids, id_ativo) # cujas referências foram todas substituídas a essa altura
cursor = exec_legado(sql) exec_legado('delete from autor where cod_autor in {}'.format(
tuple(apagar)))
def uniformiza_banco(): def uniformiza_banco():
@ -481,8 +383,14 @@ relatoria | tip_fim_relatoria = NULL | tip_fim_relatoria = 0
spec = spec.split('|') spec = spec.split('|')
exec_legado('UPDATE {} SET {} WHERE {}'.format(*spec)) exec_legado('UPDATE {} SET {} WHERE {}'.format(*spec))
migra_autor() # Migra autores para um único autor # retira apontamentos de materia para assunto inexistente
migra_comissao() # Migra comissões para uma única comissão exec_legado('delete from materia_assunto where cod_assunto = 0')
# corrige string "None" em autor
exec_legado('update autor set des_cargo = NULL where des_cargo = "None"')
unifica_autores_repetidos_no_legado('cod_parlamentar')
unifica_autores_repetidos_no_legado('cod_comissao')
def iter_sql_records(sql, db): def iter_sql_records(sql, db):

13
sapl/legacy/scripts/utils.py

@ -1,6 +1,19 @@
import inspect import inspect
from sapl.base.models import Autor
from sapl.legacy.migration import appconfs
def getsourcelines(model): def getsourcelines(model):
return [line.rstrip('\n').decode('utf-8') return [line.rstrip('\n').decode('utf-8')
for line in inspect.getsourcelines(model)[0]] for line in inspect.getsourcelines(model)[0]]
def get_models_com_referencia_a_autor():
def tem_referencia_a_autor(model):
return any(getattr(field, 'related_model', None) == Autor
for field in model._meta.get_fields())
return [model for app in appconfs for model in app.models.values()
if tem_referencia_a_autor(model)]

55
sapl/legacy/test_migration.py

@ -0,0 +1,55 @@
from random import shuffle
from .migration import (get_autorias_sem_repeticoes,
get_reapontamento_de_autores_repetidos)
def test_unifica_autores_repetidos_no_legado():
# cod_parlamentar, cod_autor
autores = [[0, 0],
[1, 10],
[1, 11],
[1, 12],
[2, 20],
[2, 21],
[2, 22],
[3, 30],
[3, 31],
[4, 40],
[5, 50]]
reapontamento, apagar = get_reapontamento_de_autores_repetidos(autores)
assert reapontamento == {10: 10, 11: 10, 12: 10,
20: 20, 21: 20, 22: 20,
30: 30, 31: 30}
assert sorted(apagar) == [11, 12, 21, 22, 31]
# cod_autor, cod_materia, ind_primeiro_autor
autoria = [[10, 111, 0], # não é repetida, mas envolve um autor repetido
[22, 222, 1], # não é repetida, mas envolve um autor repetido
[10, 777, 1], # repetição c ind_primeiro_autor==1 no INÍCIO
[10, 777, 0],
[11, 777, 0],
[12, 777, 0],
[30, 888, 0], # repetição c ind_primeiro_autor==1 no MEIO
[31, 888, 1],
[30, 888, 0],
[11, 999, 0], # repetição SEM ind_primeiro_autor==1
[12, 999, 0],
[21, 999, 0], # repetição SEM ind_primeiro_autor==1
[22, 999, 0],
]
shuffle(autoria) # não devemos supor ordem na autoria
nova_autoria = get_autorias_sem_repeticoes(autoria, reapontamento)
assert nova_autoria == sorted([(10, 111, 0),
(20, 222, 1),
(10, 777, 1),
(30, 888, 1),
(10, 999, 0),
(20, 999, 0),
])
Loading…
Cancel
Save