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))
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

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.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')

17
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'))

105
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'

Loading…
Cancel
Save