diff --git a/sapl/legacy/migracao.py b/sapl/legacy/migracao.py index 9ed09360f..dd54a103e 100644 --- a/sapl/legacy/migracao.py +++ b/sapl/legacy/migracao.py @@ -51,6 +51,9 @@ def salva_conteudo_do_sde(proposicao, conteudo): proposicao, 'proposicao_sde_{}.xml'.format(proposicao.pk)) caminho_absoluto = Path(REPO.working_dir, caminho_relativo) 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: arq.write(conteudo) proposicao.texto_original = caminho_relativo diff --git a/sapl/legacy/migracao_dados.py b/sapl/legacy/migracao_dados.py index 41c7b9934..ed05b2e90 100644 --- a/sapl/legacy/migracao_dados.py +++ b/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.contenttypes.models import ContentType 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.models import Max, Q from pyaml import UnsafePrettyYAMLDumper @@ -53,6 +54,21 @@ from sapl.utils import normalize from .scripts.normaliza_dump_mysql import normaliza_dump_mysql 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 ###################################################################### # apps to be migrated, in app dependency order (very important) appconfs = [apps.get_app_config(n) for n in [ @@ -567,8 +583,8 @@ def propaga_exclusoes(): def uniformiza_banco(): exec_legado('SET SESSION sql_mode = "";') # desliga checagens do mysql - checa_registros_votacao_ambiguos_e_remove_nao_usados() propaga_exclusoes() + checa_registros_votacao_ambiguos_e_remove_nao_usados() garante_coluna_no_legado('proposicao', '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_comissao') + unifica_autores_repetidos_no_legado('col_username') # é 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 @@ -739,13 +756,6 @@ def reinicia_sequence(model, id): 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 match = re.match('sapl_cm_(.*)', NOME_BANCO_LEGADO) sigla_casa = match.group(1) @@ -820,8 +830,8 @@ def migrar_dados(): # excluindo database antigo. info('Excluindo entradas antigas do banco destino.') - call([PROJECT_DIR.child('manage.py'), 'flush', - '--database=default', '--no-input'], stdout=PIPE) + flush = FlushCommand() + flush.handle(database='default', interactive=False, verbosity=0) # apaga tipos de autor padrão (criados no flush acima) TipoAutor.objects.all().delete() @@ -854,12 +864,17 @@ def move_para_depois_de(lista, movido, referencias): 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(): models = [model for app in appconfs for model in app.models.values() if model in field_renames] # retira reuniões quando não existe na base legada # (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) # Devido à referência TipoProposicao.tipo_conteudo_related # a migração de TipoProposicao precisa ser feita @@ -1180,8 +1195,8 @@ def adjust_normajuridica_depois_salvar(): 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] + cods = {int(a) for a in cod_assunto.split(',') if a} + return cods.intersection(assuntos_migrados) norma_para_assuntos = [ (norma, filtra_assuntos_migrados(cod_assunto)) @@ -1191,7 +1206,7 @@ def adjust_normajuridica_depois_salvar(): ligacao.objects.bulk_create( ligacao(normajuridica_id=norma, assuntonorma_id=assunto) for norma, assuntos in norma_para_assuntos - for assunto in assuntos) + for assunto in sorted(assuntos)) def adjust_autor(new, old): @@ -1357,6 +1372,7 @@ def gravar_marco(): # salva mudanças REPO.git.add([dir_dados.name]) + REPO.git.add([arq_backup.name]) if 'master' not in REPO.heads or REPO.index.diff('HEAD'): # se de fato existe mudança REPO.index.commit('Grava marco') diff --git a/sapl/legacy/migracao_documentos.py b/sapl/legacy/migracao_documentos.py index fd692aa47..66ada19fc 100644 --- a/sapl/legacy/migracao_documentos.py +++ b/sapl/legacy/migracao_documentos.py @@ -9,7 +9,7 @@ from image_cropping.fields import ImageCropField from sapl.base.models import CasaLegislativa 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, Proposicao) from sapl.norma.models import NormaJuridica @@ -36,9 +36,9 @@ DOCS = { } # 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'), - ('upload_ata', 'reuniao_comissao/{}_ata')], + ('upload_ata', 'reuniao_comissao/{}_ata')] 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)) return os.makedirs(os.path.dirname(destino), exist_ok=True) - repo.git.mv(origem, destino) + os.rename(origem, destino) def migrar_logotipo(repo, casa, propriedades): @@ -140,6 +140,7 @@ def migrar_docs_por_ids(repo, model): if tem_cropping: # conserta link do git annex (antes do commit) # pois o conteúdo das imagens é acessado pelo cropping + repo.git.add(destino) repo.git.execute('git annex fix'.split() + [destino]) obj.save() else: @@ -162,11 +163,17 @@ def migrar_documentos(repo): # garante que o conteúdo das fotos dos parlamentares esteja presente # (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: 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) for (dir, _, files) in os.walk(join(repo.working_dir, 'sapl_documentos')) diff --git a/sapl/legacy/scripts/exporta_zope/exporta_zope.py b/sapl/legacy/scripts/exporta_zope/exporta_zope.py index 270c90583..bf71d051c 100755 --- a/sapl/legacy/scripts/exporta_zope/exporta_zope.py +++ b/sapl/legacy/scripts/exporta_zope/exporta_zope.py @@ -13,6 +13,7 @@ import sys from collections import defaultdict from contextlib import contextmanager from functools import partial +from os.path import exists import git import magic @@ -78,7 +79,8 @@ def br(obj): 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) if extensao is not None: return extensao @@ -143,17 +145,21 @@ def get_conteudo_dtml_method(doc): 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): for entry in folder.get(key_list, []): id, meta_type = entry['id'], entry[type_key] try: - obj = br(folder.get(id, None)) + obj = folder.get(id, None) except POSKeyError: - print('#' * 80) - print('#' * 80) - print('ATENÇÃO: DIRETÓRIO corrompido: {}'.format(id)) - print('#' * 80) - print('#' * 80) + print_msg_poskeyerror(id) else: yield id, obj, meta_type @@ -169,9 +175,12 @@ def enumerate_btree(folder): contagem_esperada = folder['_count'].value tree = folder['_tree'] contagem_real = 0 # para o caso em que não haja itens - for contagem_real, (id, obj) in enumerate(tree.iteritems(), start=1): - obj, meta_type = br(obj), type(obj).__name__ - yield id, obj, meta_type + try: + for contagem_real, (id, obj) in enumerate(tree.iteritems(), start=1): + meta_type = type(obj).__name__ + yield id, obj, meta_type + except POSKeyError: + print_msg_poskeyerror(folder['id']) # verificação de consistência if contagem_esperada != contagem_real: print('ATENÇÃO: contagens diferentes na btree: ' @@ -197,10 +206,10 @@ def logando_nao_identificados(): print('#' * 80) -def dump_folder(folder, path, salvar, enum=enumerate_folder): +def dump_folder(folder, path, salvar, mtimes, enum=enumerate_folder): name = folder['id'] path = os.path.join(path, name) - if not os.path.exists(path): + if not exists(path): os.makedirs(path) for id, obj, meta_type in enum(folder): # pula pastas *_old (presentes em várias bases) @@ -210,8 +219,20 @@ def dump_folder(folder, path, salvar, enum=enumerate_folder): if dump == '?': nao_identificados[meta_type].append(path + '/' + id) elif dump: - id_interno = dump(obj, path, salvar) - assert id == id_interno + if isinstance(dump, partial) and dump.func == dump_folder: + 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 @@ -223,7 +244,7 @@ def read_sde(element): def read_properties(): for id, obj, meta_type in enumerate_properties(element): - yield id, decode_iso8859(obj) + yield id, decode_iso8859(br(obj)) def read_children(): 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 yield {'id': id, 'meta_type': meta_type, - 'dados': read_sde(obj)} + 'dados': read_sde(br(obj))} data = dict(read_properties()) children = list(read_children()) @@ -335,9 +356,12 @@ def dump_usuarios(sapl, path, salvar): save_as_yaml(path, 'usuarios.yaml', users, salvar) -def _dump_sapl(data_fs_path, documentos_fs_path, destino, salvar): - assert Path(data_fs_path).exists() - assert Path(documentos_fs_path).exists() +def _dump_sapl(data_fs_path, documentos_fs_path, destino, salvar, mtimes): + assert exists(data_fs_path) + 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) try: @@ -352,12 +376,12 @@ def _dump_sapl(data_fs_path, documentos_fs_path, destino, salvar): sapl = find_sapl(app) # extrai folhas XSLT if 'XSLT' in sapl: - dump_folder(br(sapl['XSLT']), destino, salvar) + dump_folder(br(sapl['XSLT']), destino, salvar, mtimes) # extrai documentos docs = br(sapl['sapl_documentos']) with logando_nao_identificados(): - dump_folder(docs, destino, salvar) + dump_folder(docs, destino, salvar, mtimes) dump_propriedades(docs, destino, salvar) finally: close_db() @@ -367,12 +391,6 @@ def repo_execute(repo, cmd, *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): base, extensao = os.path.splitext(fullname) if extensao not in ['.xsl', '.xslt', '.yaml', '.css']: @@ -381,23 +399,15 @@ def ajusta_extensao(fullname, conteudo): 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): - sha = hashlib.sha256() - sha.update(conteudo) - if sha.hexdigest() in hashes: - print('- hash encontrado - {}'.format(fullname)) - else: - fullname = ajusta_extensao(fullname, conteudo) - if os.path.exists(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) + fullname = ajusta_extensao(fullname, conteudo) + if exists(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 @@ -409,8 +419,8 @@ def dump_sapl(sigla): 'datafs', '{}_cm_{}.fs'.format(prefixo, sigla)) for prefixo in ('Data', 'DocumentosSapl')] - assert data_fs_path.exists(), 'Origem não existe: {}'.format(data_fs_path) - if not documentos_fs_path.exists(): + assert exists(data_fs_path), 'Origem não existe: {}'.format(data_fs_path) + if not exists(documentos_fs_path): documentos_fs_path = data_fs_path nome_banco_legado = 'sapl_cm_{}'.format(sigla) @@ -427,12 +437,17 @@ def dump_sapl(sigla): salvar = build_salvar(repo) try: 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 finally: # grava mundaças repo_execute(repo, 'git annex add sapl_documentos') + arq_mtimes.write_file(yaml.safe_dump(mtimes, allow_unicode=True)) repo.git.add(A=True) + # atualiza repo if 'master' not in repo.heads or repo.index.diff('HEAD'): # se de fato existe mudança status = 'completa' if finalizado else 'parcial'