Browse Source

Merge branch 'migracao' into 3.1.x

pull/2056/head
Marcio Mazza 7 years ago
parent
commit
47f2964ff6
  1. 3
      sapl/legacy/migracao.py
  2. 44
      sapl/legacy/migracao_dados.py
  3. 17
      sapl/legacy/migracao_documentos.py
  4. 105
      sapl/legacy/scripts/exporta_zope/exporta_zope.py

3
sapl/legacy/migracao.py

@ -51,6 +51,9 @@ def salva_conteudo_do_sde(proposicao, conteudo):
proposicao, 'proposicao_sde_{}.xml'.format(proposicao.pk)) proposicao, 'proposicao_sde_{}.xml'.format(proposicao.pk))
caminho_absoluto = Path(REPO.working_dir, caminho_relativo) caminho_absoluto = Path(REPO.working_dir, caminho_relativo)
caminho_absoluto.parent.mkdir(parents=True) caminho_absoluto.parent.mkdir(parents=True)
# ajusta caminhos para folhas de estilo
conteudo = conteudo.replace(b'"XSLT/HTML', b'"/XSLT/HTML')
conteudo = conteudo.replace(b"'XSLT/HTML", b"'/XSLT/HTML")
with open(caminho_absoluto, 'wb') as arq: with open(caminho_absoluto, 'wb') as arq:
arq.write(conteudo) arq.write(conteudo)
proposicao.texto_original = caminho_relativo proposicao.texto_original = caminho_relativo

44
sapl/legacy/migracao_dados.py

@ -22,6 +22,7 @@ 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.core.management.commands.flush import Command as FlushCommand
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 pyaml import UnsafePrettyYAMLDumper from pyaml import UnsafePrettyYAMLDumper
@ -53,6 +54,21 @@ from sapl.utils import normalize
from .scripts.normaliza_dump_mysql import normaliza_dump_mysql from .scripts.normaliza_dump_mysql import normaliza_dump_mysql
from .timezonesbrasil import get_timezone from .timezonesbrasil import get_timezone
# YAML SETUP ###############################################################
def dict_representer(dumper, data):
return dumper.represent_dict(data.items())
yaml.add_representer(OrderedDict, dict_representer)
# importante para preservar a ordem ao ler yaml no python 3.5
def dict_constructor(loader, node):
return OrderedDict(loader.construct_pairs(node))
yaml.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
dict_constructor)
# BASE ###################################################################### # BASE ######################################################################
# apps to be migrated, in app dependency order (very important) # apps to be migrated, in app dependency order (very important)
appconfs = [apps.get_app_config(n) for n in [ appconfs = [apps.get_app_config(n) for n in [
@ -567,8 +583,8 @@ def propaga_exclusoes():
def uniformiza_banco(): def uniformiza_banco():
exec_legado('SET SESSION sql_mode = "";') # desliga checagens do mysql exec_legado('SET SESSION sql_mode = "";') # desliga checagens do mysql
checa_registros_votacao_ambiguos_e_remove_nao_usados()
propaga_exclusoes() propaga_exclusoes()
checa_registros_votacao_ambiguos_e_remove_nao_usados()
garante_coluna_no_legado('proposicao', garante_coluna_no_legado('proposicao',
'num_proposicao int(11) NULL') 'num_proposicao int(11) NULL')
@ -649,6 +665,7 @@ sessao_plenaria_presenca | dat_sessao = NULL | dat_sessao = 0
unifica_autores_repetidos_no_legado('cod_parlamentar') unifica_autores_repetidos_no_legado('cod_parlamentar')
unifica_autores_repetidos_no_legado('cod_comissao') unifica_autores_repetidos_no_legado('cod_comissao')
unifica_autores_repetidos_no_legado('col_username')
# é importante reverter a exclusão de autores somente depois, para que a # é importante reverter a exclusão de autores somente depois, para que a
# unificação possa dar prioridade às informações dos autores não excluídos # unificação possa dar prioridade às informações dos autores não excluídos
@ -739,13 +756,6 @@ def reinicia_sequence(model, id):
REPO = git.Repo.init(DIR_REPO) REPO = git.Repo.init(DIR_REPO)
def dict_representer(dumper, data):
return dumper.represent_dict(data.items())
yaml.add_representer(OrderedDict, dict_representer)
# configura timezone de migração # configura timezone de migração
match = re.match('sapl_cm_(.*)', NOME_BANCO_LEGADO) match = re.match('sapl_cm_(.*)', NOME_BANCO_LEGADO)
sigla_casa = match.group(1) sigla_casa = match.group(1)
@ -820,8 +830,8 @@ def migrar_dados():
# excluindo database antigo. # excluindo database antigo.
info('Excluindo entradas antigas do banco destino.') info('Excluindo entradas antigas do banco destino.')
call([PROJECT_DIR.child('manage.py'), 'flush', flush = FlushCommand()
'--database=default', '--no-input'], stdout=PIPE) flush.handle(database='default', interactive=False, verbosity=0)
# apaga tipos de autor padrão (criados no flush acima) # apaga tipos de autor padrão (criados no flush acima)
TipoAutor.objects.all().delete() TipoAutor.objects.all().delete()
@ -854,12 +864,17 @@ def move_para_depois_de(lista, movido, referencias):
return lista return lista
TABELAS_LEGADO = [t for (t,) in exec_legado('show tables')]
EXISTE_REUNIAO_NO_LEGADO = 'reuniao_comissao' in TABELAS_LEGADO
def get_models_a_migrar(): def get_models_a_migrar():
models = [model for app in appconfs for model in app.models.values() models = [model for app in appconfs for model in app.models.values()
if model in field_renames] if model in field_renames]
# retira reuniões quando não existe na base legada # retira reuniões quando não existe na base legada
# (só existe no sapl 3.0) # (só existe no sapl 3.0)
if 'reuniao_comissao' not in list(exec_legado('show tables')): tabelas_legado = [t for (t,) in exec_legado('show tables')]
if not EXISTE_REUNIAO_NO_LEGADO:
models.remove(Reuniao) models.remove(Reuniao)
# Devido à referência TipoProposicao.tipo_conteudo_related # Devido à referência TipoProposicao.tipo_conteudo_related
# a migração de TipoProposicao precisa ser feita # a migração de TipoProposicao precisa ser feita
@ -1180,8 +1195,8 @@ def adjust_normajuridica_depois_salvar():
for model in [AssuntoNorma, NormaJuridica]] for model in [AssuntoNorma, NormaJuridica]]
def filtra_assuntos_migrados(cod_assunto): def filtra_assuntos_migrados(cod_assunto):
return [a for a in map(int, cod_assunto.split(',')) cods = {int(a) for a in cod_assunto.split(',') if a}
if a in assuntos_migrados] return cods.intersection(assuntos_migrados)
norma_para_assuntos = [ norma_para_assuntos = [
(norma, filtra_assuntos_migrados(cod_assunto)) (norma, filtra_assuntos_migrados(cod_assunto))
@ -1191,7 +1206,7 @@ def adjust_normajuridica_depois_salvar():
ligacao.objects.bulk_create( ligacao.objects.bulk_create(
ligacao(normajuridica_id=norma, assuntonorma_id=assunto) ligacao(normajuridica_id=norma, assuntonorma_id=assunto)
for norma, assuntos in norma_para_assuntos for norma, assuntos in norma_para_assuntos
for assunto in assuntos) for assunto in sorted(assuntos))
def adjust_autor(new, old): def adjust_autor(new, old):
@ -1357,6 +1372,7 @@ def gravar_marco():
# salva mudanças # salva mudanças
REPO.git.add([dir_dados.name]) REPO.git.add([dir_dados.name])
REPO.git.add([arq_backup.name])
if 'master' not in REPO.heads or REPO.index.diff('HEAD'): if 'master' not in REPO.heads or REPO.index.diff('HEAD'):
# se de fato existe mudança # se de fato existe mudança
REPO.index.commit('Grava marco') REPO.index.commit('Grava marco')

17
sapl/legacy/migracao_documentos.py

@ -9,7 +9,7 @@ from image_cropping.fields import ImageCropField
from sapl.base.models import CasaLegislativa from sapl.base.models import CasaLegislativa
from sapl.comissoes.models import Reuniao from sapl.comissoes.models import Reuniao
from sapl.legacy.migracao_dados import exec_legado from sapl.legacy.migracao_dados import EXISTE_REUNIAO_NO_LEGADO, exec_legado
from sapl.materia.models import (DocumentoAcessorio, MateriaLegislativa, from sapl.materia.models import (DocumentoAcessorio, MateriaLegislativa,
Proposicao) Proposicao)
from sapl.norma.models import NormaJuridica from sapl.norma.models import NormaJuridica
@ -36,9 +36,9 @@ DOCS = {
} }
# acrescenta reuniões (que só existem no sapl 3.0) # acrescenta reuniões (que só existem no sapl 3.0)
if 'reuniao_comissao' in set(exec_legado('show tables')): if EXISTE_REUNIAO_NO_LEGADO:
DOCS[Reuniao] = [('upload_pauta', 'reuniao_comissao/{}_pauta'), DOCS[Reuniao] = [('upload_pauta', 'reuniao_comissao/{}_pauta'),
('upload_ata', 'reuniao_comissao/{}_ata')], ('upload_ata', 'reuniao_comissao/{}_ata')]
DOCS = {model: [(campo, join('sapl_documentos', origem)) DOCS = {model: [(campo, join('sapl_documentos', origem))
@ -53,7 +53,7 @@ def mover_documento(repo, origem, destino, ignora_origem_ausente=False):
print('Origem ignorada ao mover documento: {}'.format(origem)) print('Origem ignorada ao mover documento: {}'.format(origem))
return return
os.makedirs(os.path.dirname(destino), exist_ok=True) os.makedirs(os.path.dirname(destino), exist_ok=True)
repo.git.mv(origem, destino) os.rename(origem, destino)
def migrar_logotipo(repo, casa, propriedades): def migrar_logotipo(repo, casa, propriedades):
@ -140,6 +140,7 @@ def migrar_docs_por_ids(repo, model):
if tem_cropping: if tem_cropping:
# conserta link do git annex (antes do commit) # conserta link do git annex (antes do commit)
# pois o conteúdo das imagens é acessado pelo cropping # pois o conteúdo das imagens é acessado pelo cropping
repo.git.add(destino)
repo.git.execute('git annex fix'.split() + [destino]) repo.git.execute('git annex fix'.split() + [destino])
obj.save() obj.save()
else: else:
@ -162,11 +163,17 @@ def migrar_documentos(repo):
# garante que o conteúdo das fotos dos parlamentares esteja presente # garante que o conteúdo das fotos dos parlamentares esteja presente
# (necessário para o cropping de imagem) # (necessário para o cropping de imagem)
repo.git.execute('git annex get sapl_documentos/parlamentar'.split()) if os.path.exists(
os.path.join(repo.working_dir, 'sapl_documentos/parlamentar')):
repo.git.execute('git annex get sapl_documentos/parlamentar'.split())
for model in DOCS: for model in DOCS:
migrar_docs_por_ids(repo, model) migrar_docs_por_ids(repo, model)
# versiona modificações
repo.git.add('-A', '.')
repo.index.commit('Migração dos documentos completa')
sobrando = [join(dir, file) sobrando = [join(dir, file)
for (dir, _, files) in os.walk(join(repo.working_dir, for (dir, _, files) in os.walk(join(repo.working_dir,
'sapl_documentos')) 'sapl_documentos'))

105
sapl/legacy/scripts/exporta_zope/exporta_zope.py

@ -13,6 +13,7 @@ import sys
from collections import defaultdict from collections import defaultdict
from contextlib import contextmanager from contextlib import contextmanager
from functools import partial from functools import partial
from os.path import exists
import git import git
import magic import magic
@ -78,7 +79,8 @@ def br(obj):
def guess_extension(fullname, buffer): def guess_extension(fullname, buffer):
mime = magic.from_buffer(buffer, mime=True) # um corte de apenas 1024 impediu a detecção correta de .docx
mime = magic.from_buffer(buffer[:4096], mime=True)
extensao = EXTENSOES.get(mime) extensao = EXTENSOES.get(mime)
if extensao is not None: if extensao is not None:
return extensao return extensao
@ -143,17 +145,21 @@ def get_conteudo_dtml_method(doc):
return doc['raw'] return doc['raw']
def print_msg_poskeyerror(id):
print('#' * 80)
print('#' * 80)
print('ATENÇÃO: DIRETÓRIO corrompido: {}'.format(id))
print('#' * 80)
print('#' * 80)
def enumerate_by_key_list(folder, key_list, type_key): def enumerate_by_key_list(folder, key_list, type_key):
for entry in folder.get(key_list, []): for entry in folder.get(key_list, []):
id, meta_type = entry['id'], entry[type_key] id, meta_type = entry['id'], entry[type_key]
try: try:
obj = br(folder.get(id, None)) obj = folder.get(id, None)
except POSKeyError: except POSKeyError:
print('#' * 80) print_msg_poskeyerror(id)
print('#' * 80)
print('ATENÇÃO: DIRETÓRIO corrompido: {}'.format(id))
print('#' * 80)
print('#' * 80)
else: else:
yield id, obj, meta_type yield id, obj, meta_type
@ -169,9 +175,12 @@ def enumerate_btree(folder):
contagem_esperada = folder['_count'].value contagem_esperada = folder['_count'].value
tree = folder['_tree'] tree = folder['_tree']
contagem_real = 0 # para o caso em que não haja itens contagem_real = 0 # para o caso em que não haja itens
for contagem_real, (id, obj) in enumerate(tree.iteritems(), start=1): try:
obj, meta_type = br(obj), type(obj).__name__ for contagem_real, (id, obj) in enumerate(tree.iteritems(), start=1):
yield id, obj, meta_type meta_type = type(obj).__name__
yield id, obj, meta_type
except POSKeyError:
print_msg_poskeyerror(folder['id'])
# verificação de consistência # verificação de consistência
if contagem_esperada != contagem_real: if contagem_esperada != contagem_real:
print('ATENÇÃO: contagens diferentes na btree: ' print('ATENÇÃO: contagens diferentes na btree: '
@ -197,10 +206,10 @@ def logando_nao_identificados():
print('#' * 80) print('#' * 80)
def dump_folder(folder, path, salvar, enum=enumerate_folder): def dump_folder(folder, path, salvar, mtimes, enum=enumerate_folder):
name = folder['id'] name = folder['id']
path = os.path.join(path, name) path = os.path.join(path, name)
if not os.path.exists(path): if not exists(path):
os.makedirs(path) os.makedirs(path)
for id, obj, meta_type in enum(folder): for id, obj, meta_type in enum(folder):
# pula pastas *_old (presentes em várias bases) # pula pastas *_old (presentes em várias bases)
@ -210,8 +219,20 @@ def dump_folder(folder, path, salvar, enum=enumerate_folder):
if dump == '?': if dump == '?':
nao_identificados[meta_type].append(path + '/' + id) nao_identificados[meta_type].append(path + '/' + id)
elif dump: elif dump:
id_interno = dump(obj, path, salvar) if isinstance(dump, partial) and dump.func == dump_folder:
assert id == id_interno try:
dump(br(obj), path, salvar, mtimes)
except POSKeyError as e:
print_msg_poskeyerror(id)
continue
else:
# se o objeto for mais recente que o da última exportação
mtime = obj._p_mtime
fullname = os.path.join(path, id)
if mtime > mtimes.get(fullname, 0):
id_interno = dump(br(obj), path, salvar)
assert id == id_interno
mtimes[fullname] = mtime
return name return name
@ -223,7 +244,7 @@ def read_sde(element):
def read_properties(): def read_properties():
for id, obj, meta_type in enumerate_properties(element): for id, obj, meta_type in enumerate_properties(element):
yield id, decode_iso8859(obj) yield id, decode_iso8859(br(obj))
def read_children(): def read_children():
for id, obj, meta_type in enumerate_folder(element): for id, obj, meta_type in enumerate_folder(element):
@ -237,7 +258,7 @@ def read_sde(element):
# ignoramos os scrips python de eventos dos templates # ignoramos os scrips python de eventos dos templates
yield {'id': id, yield {'id': id,
'meta_type': meta_type, 'meta_type': meta_type,
'dados': read_sde(obj)} 'dados': read_sde(br(obj))}
data = dict(read_properties()) data = dict(read_properties())
children = list(read_children()) children = list(read_children())
@ -335,9 +356,12 @@ def dump_usuarios(sapl, path, salvar):
save_as_yaml(path, 'usuarios.yaml', users, salvar) save_as_yaml(path, 'usuarios.yaml', users, salvar)
def _dump_sapl(data_fs_path, documentos_fs_path, destino, salvar): def _dump_sapl(data_fs_path, documentos_fs_path, destino, salvar, mtimes):
assert Path(data_fs_path).exists() assert exists(data_fs_path)
assert Path(documentos_fs_path).exists() assert exists(documentos_fs_path)
# precisamos trabalhar com strings e não Path's para as comparações de mtimes
data_fs_path, documentos_fs_path, destino = map(str, (
data_fs_path, documentos_fs_path, destino))
app, close_db = get_app(data_fs_path) app, close_db = get_app(data_fs_path)
try: try:
@ -352,12 +376,12 @@ def _dump_sapl(data_fs_path, documentos_fs_path, destino, salvar):
sapl = find_sapl(app) sapl = find_sapl(app)
# extrai folhas XSLT # extrai folhas XSLT
if 'XSLT' in sapl: if 'XSLT' in sapl:
dump_folder(br(sapl['XSLT']), destino, salvar) dump_folder(br(sapl['XSLT']), destino, salvar, mtimes)
# extrai documentos # extrai documentos
docs = br(sapl['sapl_documentos']) docs = br(sapl['sapl_documentos'])
with logando_nao_identificados(): with logando_nao_identificados():
dump_folder(docs, destino, salvar) dump_folder(docs, destino, salvar, mtimes)
dump_propriedades(docs, destino, salvar) dump_propriedades(docs, destino, salvar)
finally: finally:
close_db() close_db()
@ -367,12 +391,6 @@ def repo_execute(repo, cmd, *args):
return repo.git.execute(cmd.split() + list(args)) return repo.git.execute(cmd.split() + list(args))
def get_annex_hashes(repo):
hashes = repo_execute(
repo, 'git annex find', '--format=${keyname}\n', '--include=*')
return {os.path.splitext(h)[0] for h in hashes.splitlines()}
def ajusta_extensao(fullname, conteudo): def ajusta_extensao(fullname, conteudo):
base, extensao = os.path.splitext(fullname) base, extensao = os.path.splitext(fullname)
if extensao not in ['.xsl', '.xslt', '.yaml', '.css']: if extensao not in ['.xsl', '.xslt', '.yaml', '.css']:
@ -381,23 +399,15 @@ def ajusta_extensao(fullname, conteudo):
def build_salvar(repo): def build_salvar(repo):
"""Constroi função salvar que pula arquivos que já estão no annex
"""
hashes = get_annex_hashes(repo)
def salvar(fullname, conteudo): def salvar(fullname, conteudo):
sha = hashlib.sha256() fullname = ajusta_extensao(fullname, conteudo)
sha.update(conteudo) if exists(fullname):
if sha.hexdigest() in hashes: # destrava arquivo pré-existente (o conteúdo mudou)
print('- hash encontrado - {}'.format(fullname)) repo_execute(repo, 'git annex unlock', fullname)
else: with open(fullname, 'w') as arq:
fullname = ajusta_extensao(fullname, conteudo) arq.write(conteudo)
if os.path.exists(fullname): print(fullname)
# destrava arquivo pré-existente (o conteúdo mudou)
repo_execute(repo, 'git annex unlock', fullname)
with open(fullname, 'w') as arq:
arq.write(conteudo)
print(fullname)
return salvar return salvar
@ -409,8 +419,8 @@ def dump_sapl(sigla):
'datafs', '{}_cm_{}.fs'.format(prefixo, sigla)) 'datafs', '{}_cm_{}.fs'.format(prefixo, sigla))
for prefixo in ('Data', 'DocumentosSapl')] for prefixo in ('Data', 'DocumentosSapl')]
assert data_fs_path.exists(), 'Origem não existe: {}'.format(data_fs_path) assert exists(data_fs_path), 'Origem não existe: {}'.format(data_fs_path)
if not documentos_fs_path.exists(): if not exists(documentos_fs_path):
documentos_fs_path = data_fs_path documentos_fs_path = data_fs_path
nome_banco_legado = 'sapl_cm_{}'.format(sigla) nome_banco_legado = 'sapl_cm_{}'.format(sigla)
@ -427,12 +437,17 @@ def dump_sapl(sigla):
salvar = build_salvar(repo) salvar = build_salvar(repo)
try: try:
finalizado = False finalizado = False
_dump_sapl(data_fs_path, documentos_fs_path, destino, salvar) arq_mtimes = Path(repo.working_dir, 'mtimes.yaml')
mtimes = yaml.load(
arq_mtimes.read_file()) if arq_mtimes.exists() else {}
_dump_sapl(data_fs_path, documentos_fs_path, destino, salvar, mtimes)
finalizado = True finalizado = True
finally: finally:
# grava mundaças # grava mundaças
repo_execute(repo, 'git annex add sapl_documentos') repo_execute(repo, 'git annex add sapl_documentos')
arq_mtimes.write_file(yaml.safe_dump(mtimes, allow_unicode=True))
repo.git.add(A=True) repo.git.add(A=True)
# atualiza repo
if 'master' not in repo.heads or repo.index.diff('HEAD'): if 'master' not in repo.heads or repo.index.diff('HEAD'):
# se de fato existe mudança # se de fato existe mudança
status = 'completa' if finalizado else 'parcial' status = 'completa' if finalizado else 'parcial'

Loading…
Cancel
Save