diff --git a/docker-compose.yml b/docker-compose.yml index c33273b3f..fa0abdd5d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,7 +11,7 @@ sapldb: ports: - "5432:5432" sapl: - image: interlegis/sapl:3.1.92 + image: interlegis/sapl:3.1.102 restart: always environment: ADMIN_PASSWORD: interlegis diff --git a/gunicorn_start.sh b/gunicorn_start.sh index 4f58022ce..862221a29 100755 --- a/gunicorn_start.sh +++ b/gunicorn_start.sh @@ -16,8 +16,9 @@ DJANGODIR=/var/interlegis/sapl/ # Django project directory (* SOCKFILE=/var/interlegis/sapl/run/gunicorn.sock # we will communicate using this unix socket (*) USER=`whoami` # the user to run as (*) GROUP=`whoami` # the group to run as (*) -NUM_WORKERS=4 # how many worker processes should Gunicorn spawn (*) +NUM_WORKERS=3 # how many worker processes should Gunicorn spawn (*) # NUM_WORKERS = 2 * CPUS + 1 +TIMEOUT=60 MAX_REQUESTS=100 # number of requests before restarting worker DJANGO_SETTINGS_MODULE=sapl.settings # which settings file should Django use (*) DJANGO_WSGI_MODULE=sapl.wsgi # WSGI module name (*) @@ -41,6 +42,8 @@ test -d $RUNDIR || mkdir -p $RUNDIR # Programs meant to be run under supervisor should not daemonize themselves (do not use --daemon) exec gunicorn ${DJANGO_WSGI_MODULE}:application \ --name $NAME \ + --log-level debug \ + --timeout $TIMEOUT \ --workers $NUM_WORKERS \ --max-requests $MAX_REQUESTS \ --user $USER \ diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 4b80ad130..6d9d79d66 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -36,3 +36,4 @@ django-reversion==2.0.8 WeasyPrint==0.42 whoosh==2.7.4 django-speedinfo==1.3.5 +django-reversion-compare==0.8.4 diff --git a/sapl/audiencia/tests/test_audiencia.py b/sapl/audiencia/tests/test_audiencia.py index 7ce503c2d..710d70dff 100644 --- a/sapl/audiencia/tests/test_audiencia.py +++ b/sapl/audiencia/tests/test_audiencia.py @@ -1,3 +1,25 @@ -from django.test import TestCase +import pytest +from django.utils.translation import ugettext as _ +from model_mommy import mommy -# Create your tests here. +from sapl.audiencia import forms + +@pytest.mark.django_db(transaction=False) +def test_valida_campos_obrigatorios_audiencia_form(): + form = forms.AudienciaForm(data={}) + + assert not form.is_valid() + + errors = form.errors + + assert errors['nome'] == [_('Este campo é obrigatório.')] + assert errors['tema'] == [_('Este campo é obrigatório.')] + assert errors['tipo'] == [_('Este campo é obrigatório.')] + assert errors['tipo_materia'] == [_('Este campo é obrigatório.')] + assert errors['numero_materia'] == [_('Este campo é obrigatório.')] + assert errors['ano_materia'] == [_('Este campo é obrigatório.')] + assert errors['data'] == [_('Este campo é obrigatório.')] + assert errors['hora_inicio'] == [_('Este campo é obrigatório.')] + assert errors['hora_fim'] == [_('Este campo é obrigatório.')] + + assert len(errors) == 9 diff --git a/sapl/base/forms.py b/sapl/base/forms.py index e60ffe072..de6b50403 100644 --- a/sapl/base/forms.py +++ b/sapl/base/forms.py @@ -44,16 +44,21 @@ STATUS_USER_CHOICE = [ def get_roles(): - return [(g.id, g.name) for g in Group.objects.all().order_by('name')] + roles = [(g.id, g.name) for g in Group.objects.all().order_by('name') + if g.name != 'Votante' and g.name != 'Autor'] + return roles class UsuarioCreateForm(ModelForm): - username = forms.CharField(required=True, label="Nome de usuário") - firstname = forms.CharField(required=True, label="Nome") - lastname = forms.CharField(required=True, label="Sobrenome") - password1 = forms.CharField(required=True, widget=forms.PasswordInput, label='Senha') - password2 = forms.CharField(required=True, widget=forms.PasswordInput, label='Confirmar senha') + username = forms.CharField(required=True, label="Nome de usuário", + max_length=30) + firstname = forms.CharField(required=True, label="Nome", max_length=30) + lastname = forms.CharField(required=True, label="Sobrenome", max_length=30) + password1 = forms.CharField(required=True, widget=forms.PasswordInput, + label='Senha', max_length=128) + password2 = forms.CharField(required=True, widget=forms.PasswordInput, + label='Confirmar senha', max_length=128) user_active = forms.ChoiceField(required=False, choices=YES_NO_CHOICES, label="Usuário ativo?", initial='True') diff --git a/sapl/base/views.py b/sapl/base/views.py index 238252245..b41492f4e 100644 --- a/sapl/base/views.py +++ b/sapl/base/views.py @@ -420,9 +420,9 @@ class RelatorioMateriasPorAnoAutorTipoView(FilterView): filterset_class = RelatorioMateriasPorAnoAutorTipoFilterSet template_name = 'base/RelatorioMateriasPorAnoAutorTipo_filter.html' - def get_materias_autor_ano(self, ano): + def get_materias_autor_ano(self, ano, primeiro_autor): - autorias = Autoria.objects.filter(materia__ano=ano).values( + autorias = Autoria.objects.filter(materia__ano=ano, primeiro_autor=primeiro_autor).values( 'autor', 'materia__tipo__sigla', 'materia__tipo__descricao').annotate( @@ -488,7 +488,8 @@ class RelatorioMateriasPorAnoAutorTipoView(FilterView): if 'ano' in self.request.GET and self.request.GET['ano']: ano = int(self.request.GET['ano']) - context['relatorio'] = self.get_materias_autor_ano(ano) + context['relatorio'] = self.get_materias_autor_ano(ano, True) + context['corelatorio'] = self.get_materias_autor_ano(ano, False) else: context['relatorio'] = [] diff --git a/sapl/comissoes/forms.py b/sapl/comissoes/forms.py index 05a0a97b9..db1822e4a 100644 --- a/sapl/comissoes/forms.py +++ b/sapl/comissoes/forms.py @@ -61,7 +61,8 @@ class PeriodoForm(forms.ModelForm): data_fim = cleaned_data['data_fim'] if data_fim and data_fim < data_inicio: - raise ValidationError('Data início não pode ser superior a data de fim') + raise ValidationError('A Data Final não pode ser menor que ' + 'a Data Inicial') return cleaned_data @@ -99,7 +100,7 @@ class ParticipacaoCreateForm(forms.ModelForm): exclude(id__in=id_part) eligible = self.verifica() result = list(set(qs) & set(eligible)) - if not cmp(result, eligible): # se igual a 0 significa que o qs e o eli são iguais! + if result == eligible: self.fields['parlamentar'].queryset = qs else: ids = [e.id for e in eligible] diff --git a/sapl/comissoes/migrations/0017_auto_20180717_0827.py b/sapl/comissoes/migrations/0017_auto_20180717_0827.py new file mode 100644 index 000000000..496a76b97 --- /dev/null +++ b/sapl/comissoes/migrations/0017_auto_20180717_0827.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.13 on 2018-07-17 11:27 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('comissoes', '0016_auto_20180613_2121'), + ] + + operations = [ + migrations.AlterField( + model_name='reuniao', + name='observacao', + field=models.TextField(blank=True, verbose_name='Observação'), + ), + ] diff --git a/sapl/comissoes/models.py b/sapl/comissoes/models.py index a2477ef1d..4c8c09393 100644 --- a/sapl/comissoes/models.py +++ b/sapl/comissoes/models.py @@ -223,7 +223,7 @@ class Reuniao(models.Model): local_reuniao = models.CharField( max_length=100, blank=True, verbose_name=_('Local da Reunião')) observacao = models.TextField( - max_length=150, blank=True, verbose_name=_('Observação')) + blank=True, verbose_name=_('Observação')) url_audio = models.URLField( max_length=150, blank=True, verbose_name=_('URL do Arquivo de Áudio (Formatos MP3 / AAC)')) diff --git a/sapl/comissoes/tests/test_comissoes.py b/sapl/comissoes/tests/test_comissoes.py index fee303192..4d12ba810 100644 --- a/sapl/comissoes/tests/test_comissoes.py +++ b/sapl/comissoes/tests/test_comissoes.py @@ -1,9 +1,11 @@ import pytest from django.core.urlresolvers import reverse +from django.utils.translation import ugettext as _ from model_mommy import mommy -from sapl.comissoes.models import Comissao, Composicao, Periodo, TipoComissao +from sapl.comissoes.models import Comissao, Composicao, Periodo, TipoComissao, Reuniao from sapl.parlamentares.models import Filiacao, Parlamentar, Partido +from sapl.comissoes import forms def make_composicao(comissao): @@ -96,3 +98,47 @@ def test_incluir_comissao_errors(admin_client): ['Este campo é obrigatório.']) assert (response.context_data['form'].errors['data_criacao'] == ['Este campo é obrigatório.']) + + +@pytest.mark.django_db(transaction=False) +def test_periodo_invalidas(): + + form = forms.PeriodoForm(data={'data_inicio': '10/11/2017', + 'data_fim': '09/11/2017' + }) + assert not form.is_valid() + assert form.errors['__all__'] == [_('A Data Final não pode ser menor que ' + 'a Data Inicial')] + + +@pytest.mark.django_db(transaction=False) +def test_valida_campos_obrigatorios_periodo_form(): + form = forms.PeriodoForm(data={}) + + assert not form.is_valid() + + errors = form.errors + + assert errors['data_inicio'] == [_('Este campo é obrigatório.')] + + assert len(errors) == 1 + + +@pytest.mark.django_db(transaction=False) +def test_valida_campos_obrigatorios_reuniao_form(): + form = forms.ReuniaoForm(data={}) + + assert not form.is_valid() + + errors = form.errors + + assert errors['comissao'] == [_('Este campo é obrigatório.')] + assert errors['periodo'] == [_('Este campo é obrigatório.')] + assert errors['numero'] == [_('Este campo é obrigatório.')] + assert errors['nome'] == [_('Este campo é obrigatório.')] + assert errors['data'] == [_('Este campo é obrigatório.')] + assert errors['hora_inicio'] == [_('Este campo é obrigatório.')] + assert errors['hora_fim'] == [_('Este campo é obrigatório.')] + + assert len(errors) == 7 + 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..4c953d5f5 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 [ @@ -497,9 +513,10 @@ def checa_registros_votacao_ambiguos_e_remove_nao_usados(): # interrompe migração se houver registros ambíguos ambiguos = ordem.intersection(expediente) - assert not ambiguos, '''Existe(m) RegistroVotacao ambíguo(s): {} - Corrija os dados originais antes de migrar!'''.format( - ambiguos) + if ambiguos: + warn('registro_votacao_ambiguos', + 'Existe(m) RegistroVotacao ambíguo(s): {cod_votacao}', + {'cod_votacao': ambiguos}) # exclui registros não usados (zumbis) todos = set(primeira_coluna(exec_legado( @@ -567,8 +584,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 +666,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 +757,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 +831,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 +865,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 @@ -1094,6 +1110,18 @@ def adjust_protocolo_antes_salvar(new, old): {'cod_protocolo': old.cod_protocolo}) +ARQUIVO_COMO_RESOLVER_REGISTRO_VOTACAO_AMBIGUO = \ + 'como_resolver_registro_votacao_ambiguo.yaml' + + +def get_como_resolver_registro_votacao_ambiguo(): + path = DIR_REPO.child(ARQUIVO_COMO_RESOLVER_REGISTRO_VOTACAO_AMBIGUO) + if path.exists(): + return yaml.load(path.read_file()) + else: + return {} + + def adjust_registrovotacao_antes_salvar(new, old): ordem_dia = OrdemDia.objects.filter( pk=old.cod_ordem, materia=old.cod_materia) @@ -1104,6 +1132,19 @@ def adjust_registrovotacao_antes_salvar(new, old): new.ordem = ordem_dia[0] if not ordem_dia and expediente_materia: new.expediente = expediente_materia[0] + # registro de votação ambíguo + if ordem_dia and expediente_materia: + como_resolver = get_como_resolver_registro_votacao_ambiguo() + campo = como_resolver[new.id] + if campo.startswith('ordem'): + new.ordem = ordem_dia[0] + elif campo.startswith('expediente'): + new.expediente = expediente_materia[0] + else: + raise Exception(''' + Registro de Votação ambíguo: {} + Resolva criando o arquivo {}'''.format( + new.id, ARQUIVO_COMO_RESOLVER_REGISTRO_VOTACAO_AMBIGUO)) def adjust_tipoafastamento(new, old): @@ -1180,8 +1221,10 @@ 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] + if not cod_assunto: + return [] + cods = {int(a) for a in cod_assunto.split(',') if a} + return sorted(cods.intersection(assuntos_migrados)) norma_para_assuntos = [ (norma, filtra_assuntos_migrados(cod_assunto)) @@ -1357,6 +1400,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..23c4afa54 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,21 @@ def migrar_documentos(repo): # garante que o conteúdo das fotos dos parlamentares esteja presente # (necessário para o cropping de imagem) +<<<<<<< HEAD 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()) +>>>>>>> 3.1.x 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..391e2f055 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()) @@ -302,6 +323,7 @@ def find_sapl(app): sapl = br(app['sapl']) return sapl +<<<<<<< HEAD def detectar_encoding(fonte): desc = magic.from_buffer(fonte) @@ -318,7 +340,23 @@ def autodecode(fonte): else: return fonte +def detectar_encoding(fonte): + desc = magic.from_buffer(fonte) + for termo, enc in [('ISO-8859', 'latin1'), ('UTF-8', 'utf-8')]: + if termo in desc: + return enc + return None + +def autodecode(fonte): + if isinstance(fonte, str): + enc = detectar_encoding(fonte) + return fonte.decode(enc) if enc else fonte + else: + return fonte + + +>>>>>>> 3.1.x def dump_propriedades(docs, path, salvar): props_sapl = br(docs['props_sapl']) ids = [p['id'] for p in props_sapl['_properties']] @@ -335,9 +373,18 @@ def dump_usuarios(sapl, path, salvar): save_as_yaml(path, 'usuarios.yaml', users, salvar) +<<<<<<< HEAD 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)) +>>>>>>> 3.1.x app, close_db = get_app(data_fs_path) try: @@ -352,12 +399,16 @@ def _dump_sapl(data_fs_path, documentos_fs_path, destino, salvar): sapl = find_sapl(app) # extrai folhas XSLT if 'XSLT' in sapl: +<<<<<<< HEAD dump_folder(br(sapl['XSLT']), destino, salvar) +======= + dump_folder(br(sapl['XSLT']), destino, salvar, mtimes) +>>>>>>> 3.1.x # 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,37 +418,28 @@ 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']: extensao = guess_extension(fullname, conteudo) - return base + extensao + return base + extensao, extensao 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, extensao = ajusta_extensao(fullname, conteudo) + + # ajusta caminhos XSLT p conteúdos relacionados ao SDE + if extensao in ['.xsl', '.xslt', '.xml']: + conteudo = conteudo.replace('"XSLT/HTML', '"/XSLT/HTML') + + 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 +451,13 @@ def dump_sapl(sigla): 'datafs', '{}_cm_{}.fs'.format(prefixo, sigla)) for prefixo in ('Data', 'DocumentosSapl')] +<<<<<<< HEAD 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): +>>>>>>> 3.1.x documentos_fs_path = data_fs_path nome_banco_legado = 'sapl_cm_{}'.format(sigla) @@ -427,12 +474,21 @@ def dump_sapl(sigla): salvar = build_salvar(repo) try: finalizado = False +<<<<<<< HEAD _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) +>>>>>>> 3.1.x 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' diff --git a/sapl/materia/forms.py b/sapl/materia/forms.py index 838b8616d..04ee8d0e0 100644 --- a/sapl/materia/forms.py +++ b/sapl/materia/forms.py @@ -186,6 +186,7 @@ class MateriaLegislativaForm(ModelForm): widget=forms.HiddenInput()) self.fields['autor'] = forms.CharField(required=False, widget=forms.HiddenInput()) + self.fields['numero_protocolo'].widget.attrs['readonly'] = True def clean(self): super(MateriaLegislativaForm, self).clean() @@ -213,11 +214,16 @@ class MateriaLegislativaForm(ModelForm): exist_doc = DocumentoAdministrativo.objects.filter( protocolo_id=protocolo, ano=ano).exists() + if exist_materia or exist_doc: raise ValidationError(_('Protocolo %s/%s ja possui' ' documento vinculado' % (protocolo, ano))) + p = Protocolo.objects.get(numero=protocolo,ano=ano) + if p.tipo_materia != cleaned_data['tipo']: + raise ValidationError(_('Tipo do Protocolo deve ser o mesmo do Tipo Matéria')) + if data_apresentacao.year != ano: raise ValidationError(_("O ano da matéria não pode ser " "diferente do ano na data de apresentação")) @@ -270,7 +276,7 @@ class UnidadeTramitacaoForm(ModelForm): del cleaned_data[key] if len(cleaned_data) != 1: - msg = _('Somente um campo deve preenchido!') + msg = _('Somente um campo deve ser preenchido!') raise ValidationError(msg) return cleaned_data @@ -766,14 +772,10 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet): def pega_ultima_tramitacao(): - ultimas_tramitacoes = Tramitacao.objects.values( + return Tramitacao.objects.values( 'materia_id').annotate(data_encaminhamento=Max( 'data_encaminhamento'), - id=Max('id')).values_list('id') - - lista = [item for sublist in ultimas_tramitacoes for item in sublist] - - return lista + id=Max('id')).values_list('id', flat=True) def filtra_tramitacao_status(status): diff --git a/sapl/materia/tests/test_materia_form.py b/sapl/materia/tests/test_materia_form.py index 41ad04837..944cabf00 100644 --- a/sapl/materia/tests/test_materia_form.py +++ b/sapl/materia/tests/test_materia_form.py @@ -66,3 +66,198 @@ def test_ficha_seleciona_form_valido(): form = forms.FichaSelecionaForm(data={'materia': str(materia.pk)}) assert form.is_valid() + + +@pytest.mark.django_db(transaction=False) +def test_valida_campos_obrigatorios_materialegislativa_form(): + form = forms.MateriaLegislativaForm(data={}) + + assert not form.is_valid() + + errors = form.errors + assert errors['tipo'] == [_('Este campo é obrigatório.')] + assert errors['ano'] == [_('Este campo é obrigatório.')] + assert errors['data_apresentacao'] == [_('Este campo é obrigatório.')] + assert errors['numero'] == [_('Este campo é obrigatório.')] + assert errors['ementa'] == [_('Este campo é obrigatório.')] + assert errors['regime_tramitacao'] == [_('Este campo é obrigatório.')] + + assert len(errors) == 6 + + +@pytest.mark.django_db(transaction=False) +def test_valida_campos_obrigatorios_unidade_tramitacao_form(): + form = forms.UnidadeTramitacaoForm(data={}) + + assert not form.is_valid() + errors = form.errors + + assert errors['__all__'] == [_('Somente um campo deve ser preenchido!')] + +@pytest.mark.django_db(transaction=False) +def test_valida_campos_obrigatorios_orgao_form(): + form = forms.OrgaoForm(data={}) + + assert not form.is_valid() + errors = form.errors + + assert errors['nome'] == [_('Este campo é obrigatório.')] + assert errors['sigla'] == [_('Este campo é obrigatório.')] + + assert len(errors) == 2 + + +@pytest.mark.django_db(transaction=False) +def test_valida_campos_obrigatorios_materia_assunto_form(): + form = forms.MateriaAssuntoForm(data={}) + + assert not form.is_valid() + + errors = form.errors + + assert errors['assunto'] == [_('Este campo é obrigatório.')] + assert errors['materia'] == [_('Este campo é obrigatório.')] + + assert len(errors) == 2 + +@pytest.mark.django_db(transaction=False) +def test_valida_campos_obrigatorios_autoria_form(): + form = forms.AutoriaForm(data={},instance=None) + + assert not form.is_valid() + + errors = form.errors + + assert errors['autor'] == [_('Este campo é obrigatório.')] + + assert len(errors) == 1 + + +@pytest.mark.django_db(transaction=False) +def test_valida_campos_obrigatorios_autoria_multicreate_form(): + form = forms.AutoriaMultiCreateForm(data={}) + + assert not form.is_valid() + + errors = form.errors + + assert errors['__all__'] == [_('Ao menos um autor deve ser selecionado para inclusão')] + + assert len(errors) == 1 + + +@pytest.mark.django_db(transaction=False) +def test_valida_campos_obrigatorios_tipo_proposicao_form(): + form = forms.TipoProposicaoForm(data={}) + + assert not form.is_valid() + + errors = form.errors + assert errors['tipo_conteudo_related'] == [_('Este campo é obrigatório.')] + assert errors['descricao'] == [_('Este campo é obrigatório.')] + assert errors['content_type'] == [_('Este campo é obrigatório.')] + + assert len(errors) == 3 + +@pytest.mark.django_db(transaction=False) +def test_valida_campos_obrigatorios_devolver_proposicao_form(): + form = forms.DevolverProposicaoForm(data={}) + + assert not form.is_valid() + + errors = form.errors + assert errors['__all__'] == [_('Adicione uma Justificativa para devolução.')] + + assert len(errors) == 1 + +@pytest.mark.django_db(transaction=False) +def test_valida_campos_obrigatorios_relatoria_form(): + form = forms.RelatoriaForm(data={}) + + assert not form.is_valid() + + errors = form.errors + assert errors['parlamentar'] == [_('Este campo é obrigatório.')] + assert errors['data_designacao_relator'] == [_('Este campo é obrigatório.')] + + assert len(errors) == 2 + + +@pytest.mark.django_db(transaction=False) +def test_valida_campos_obrigatorios_tramitacao_form(): + form = forms.TramitacaoForm(data={}) + + assert not form.is_valid() + + errors = form.errors + + assert errors['unidade_tramitacao_local'] == [_('Este campo é obrigatório.')] + assert errors['texto'] == [_('Este campo é obrigatório.')] + assert errors['status'] == [_('Este campo é obrigatório.')] + assert errors['data_tramitacao'] == [_('Este campo é obrigatório.')] + assert errors['unidade_tramitacao_destino'] == [_('Este campo é obrigatório.')] + + assert len(errors) == 5 + + +@pytest.mark.django_db(transaction=False) +def test_valida_campos_obrigatorios_tramitacao_update_form(): + form = forms.TramitacaoUpdateForm(data={}) + + assert not form.is_valid() + + errors = form.errors + + assert errors['unidade_tramitacao_local'] == [_('Este campo é obrigatório.')] + assert errors['texto'] == [_('Este campo é obrigatório.')] + assert errors['status'] == [_('Este campo é obrigatório.')] + assert errors['data_tramitacao'] == [_('Este campo é obrigatório.')] + assert errors['unidade_tramitacao_destino'] == [_('Este campo é obrigatório.')] + + assert len(errors) == 5 + + +@pytest.mark.django_db(transaction=False) +def test_valida_campos_obrigatorios_legislacao_citada_form(): + form = forms.LegislacaoCitadaForm(data={}) + + assert not form.is_valid() + + errors = form.errors + assert errors['tipo'] == [_('Este campo é obrigatório.')] + assert errors['ano'] == [_('Este campo é obrigatório.')] + assert errors['numero'] == [_('Este campo é obrigatório.')] + + assert len(errors) == 3 + + +@pytest.mark.django_db(transaction=False) +def test_valida_campos_obrigatorios_numeracao_form(): + form = forms.NumeracaoForm(data={}) + + assert not form.is_valid() + + errors = form.errors + + assert errors['tipo_materia'] == [_('Este campo é obrigatório.')] + assert errors['ano_materia'] == [_('Este campo é obrigatório.')] + assert errors['numero_materia'] == [_('Este campo é obrigatório.')] + assert errors['data_materia'] == [_('Este campo é obrigatório.')] + + assert len(errors) == 4 + + +@pytest.mark.django_db(transaction=False) +def test_valida_campos_obrigatorios_anexada_form(): + form = forms.AnexadaForm(data={}) + + assert not form.is_valid() + + errors = form.errors + + assert errors['tipo'] == [_('Este campo é obrigatório.')] + assert errors['ano'] == [_('Este campo é obrigatório.')] + assert errors['numero'] == [_('Este campo é obrigatório.')] + assert errors['data_anexacao'] == [_('Este campo é obrigatório.')] + + assert len(errors) == 4 diff --git a/sapl/materia/views.py b/sapl/materia/views.py index 6f0b35ef8..f2e3e76db 100644 --- a/sapl/materia/views.py +++ b/sapl/materia/views.py @@ -700,12 +700,15 @@ class ProposicaoCrud(Crud): messages.success(request, _( 'Proposição enviada com sucesso.')) - Numero = MateriaLegislativa.objects.filter(tipo=p.tipo.tipo_conteudo_related, - ano=p.ano).last().numero + 1 - messages.success(request, _( - '%s : nº %s de %s
Atenção! Este número é apenas um provável ' - 'número que pode não corresponder com a realidade' - % (p.tipo, Numero, p.ano))) + try: + Numero = MateriaLegislativa.objects.filter(tipo=p.tipo.tipo_conteudo_related, + ano=p.ano).last().numero + 1 + messages.success(request, _( + '%s : nº %s de %s
Atenção! Este número é apenas um provável ' + 'número que pode não corresponder com a realidade' + % (p.tipo, Numero, p.ano))) + except ValueError: + pass elif action == 'return': if not p.data_envio: @@ -857,7 +860,7 @@ class ProposicaoCrud(Crud): else: obj.data_recebimento = timezone.localtime( obj.data_recebimento) - obj.data_recebimento = obj.data_recebimento = formats.date_format( + obj.data_recebimento = formats.date_format( obj.data_recebimento, "DATETIME_FORMAT") if obj.data_envio is None: obj.data_envio = 'Em elaboração...' @@ -937,8 +940,9 @@ class RelatoriaCrud(MasterDetailCrud): try: comissao = Comissao.objects.get( pk=context['form'].initial['comissao']) - except ObjectDoesNotExist: + except: pass + else: composicao = comissao.composicao_set.order_by( '-periodo__data_inicio').first() @@ -1775,17 +1779,47 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView): messages.add_message(request, messages.ERROR, msg) return self.get(request, self.kwargs) - if request.POST['data_encaminhamento']: + if request.POST['status'] == '': + msg = _('Campo Status deve ser preenchido.') + messages.add_message(request, messages.ERROR, msg) + return self.get(request, self.kwargs) + + if request.POST['unidade_tramitacao_local'] == '': + msg = _('Campo Unidade Local deve ser preenchido.') + messages.add_message(request, messages.ERROR, msg) + return self.get(request, self.kwargs) + + if request.POST['data_tramitacao'] == '': + msg = _('Campo Data da Tramitação deve ser preenchido.') + messages.add_message(request, messages.ERROR, msg) + return self.get(request, self.kwargs) + + if request.POST['unidade_tramitacao_destino'] == '': + msg = _('Campo Unidade Destino deve ser preenchido.') + messages.add_message(request, messages.ERROR, msg) + return self.get(request, self.kwargs) + + if request.POST['urgente'] == '': + msg = _('Campo Urgente deve ser preenchido.') + messages.add_message(request, messages.ERROR, msg) + return self.get(request, self.kwargs) + + if request.POST['texto'] == '': + msg = _('Campo Texto da Ação deve ser preenchido.') + messages.add_message(request, messages.ERROR, msg) + return self.get(request, self.kwargs) + + if request.POST['data_encaminhamento'] == '': + data_encaminhamento = None + else: data_encaminhamento = tz.localize(datetime.strptime( request.POST['data_encaminhamento'], "%d/%m/%Y")) - else: - data_encaminhamento = None - if request.POST['data_fim_prazo']: + if request.POST['data_fim_prazo'] == '': + data_fim_prazo = None + else: data_fim_prazo = tz.localize(datetime.strptime( request.POST['data_fim_prazo'], "%d/%m/%Y")) - else: - data_fim_prazo = None # issue https://github.com/interlegis/sapl/issues/1123 # TODO: usar Form diff --git a/sapl/painel/views.py b/sapl/painel/views.py index a185fed3b..847518898 100644 --- a/sapl/painel/views.py +++ b/sapl/painel/views.py @@ -85,10 +85,16 @@ def votacao_aberta(request): def votante_view(request): # Pega o votante relacionado ao usuário template_name = 'painel/voto_nominal.html' + context = {} try: votante = Votante.objects.get(user=request.user) except ObjectDoesNotExist: - raise Http404() + msg = _("Usuário não cadastrado como votante na tela de parlamentares. Contate a administração de sua Casa Legislativa!") + context.update({ + 'error_message':msg + }) + + return render(request, template_name, context) context = {'head_title': str(_('Votação Individual'))} diff --git a/sapl/protocoloadm/forms.py b/sapl/protocoloadm/forms.py index 8502bf334..22e649459 100644 --- a/sapl/protocoloadm/forms.py +++ b/sapl/protocoloadm/forms.py @@ -7,6 +7,7 @@ from django import forms from django.core.exceptions import (MultipleObjectsReturned, ObjectDoesNotExist, ValidationError) from django.db import models +from django.db.models import Max from django.forms import ModelForm from django.utils import timezone from django.utils.translation import ugettext_lazy as _ @@ -708,10 +709,10 @@ class DocumentoAdministrativoForm(ModelForm): ano=ano_protocolo).exists() exist_doc = DocumentoAdministrativo.objects.filter( - protocolo_id=numero_protocolo, - ano=ano_protocolo).exists() + protocolo__numero=numero_protocolo, + protocolo__ano=ano_protocolo).exists() if exist_materia or exist_doc: - raise ValidationError(_('Protocolo %s/%s ja possui' + raise ValidationError(_('Protocolo %s/%s já possui' ' documento vinculado' % (numero_protocolo, ano_protocolo))) @@ -873,3 +874,34 @@ class DesvincularMateriaForm(forms.Form): form_actions(label='Desvincular') ) ) + + +def pega_ultima_tramitacao_adm(): + return TramitacaoAdministrativo.objects.values( + 'materia_id').annotate(data_encaminhamento=Max( + 'data_encaminhamento'), + id=Max('id')).values_list('id', flat=True) + + +def filtra_tramitacao_adm_status(status): + lista = pega_ultima_tramitacao_adm() + return TramitacaoAdministrativo.objects.filter( + id__in=lista, + status=status).distinct().values_list('materia_id', flat=True) + + +def filtra_tramitacao_adm_destino(destino): + lista = pega_ultima_tramitacao_adm() + return TramitacaoAdministrativo.objects.filter( + id__in=lista, + unidade_tramitacao_destino=destino).distinct().values_list( + 'materia_id', flat=True) + + +def filtra_tramitacao_adm_destino_and_status(status, destino): + lista = pega_ultima_tramitacao_adm() + return TramitacaoAdministrativo.objects.filter( + id__in=lista, + status=status, + unidade_tramitacao_destino=destino).distinct().values_list( + 'materia_id', flat=True) diff --git a/sapl/protocoloadm/views.py b/sapl/protocoloadm/views.py index c1a2b185a..3350c81cf 100644 --- a/sapl/protocoloadm/views.py +++ b/sapl/protocoloadm/views.py @@ -31,7 +31,8 @@ from .forms import (AnularProcoloAdmForm, DocumentoAcessorioAdministrativoForm, DocumentoAdministrativoFilterSet, DocumentoAdministrativoForm, ProtocoloDocumentForm, ProtocoloFilterSet, ProtocoloMateriaForm, - TramitacaoAdmEditForm, TramitacaoAdmForm, DesvincularDocumentoForm, DesvincularMateriaForm) + TramitacaoAdmEditForm, TramitacaoAdmForm, DesvincularDocumentoForm, DesvincularMateriaForm, + filtra_tramitacao_adm_destino_and_status, filtra_tramitacao_adm_destino, filtra_tramitacao_adm_status) from .models import (DocumentoAcessorioAdministrativo, DocumentoAdministrativo, StatusTramitacaoAdministrativo, TipoDocumentoAdministrativo, TramitacaoAdministrativo) @@ -307,7 +308,7 @@ class ProtocoloDocumentoView(PermissionRequiredMixin, protocolo.anulado = False if not protocolo.numero: protocolo.numero = (numero['numero__max'] + 1) if numero['numero__max'] else 1 - if protocolo.numero < (numero['numero__max'] + 1): + elif protocolo.numero < (numero['numero__max'] + 1) if numero['numero__max'] else 0: msg = _('Número de protocolo deve ser maior que {}').format(numero['numero__max']) messages.add_message(self.request, messages.ERROR, msg) return self.render_to_response(self.get_context_data()) @@ -543,13 +544,34 @@ class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin, kwargs = {'data': self.request.GET or None} + status_tramitacao = self.request.GET.get('tramitacao__status') + unidade_destino = self.request.GET.get( + 'tramitacao__unidade_tramitacao_destino') + qs = self.get_queryset() - qs = qs.distinct() + if status_tramitacao and unidade_destino: + lista = filtra_tramitacao_adm_destino_and_status(status_tramitacao, + unidade_destino) + qs = qs.filter(id__in=lista).distinct() + + elif status_tramitacao: + lista = filtra_tramitacao_adm_status(status_tramitacao) + qs = qs.filter(id__in=lista).distinct() + + elif unidade_destino: + lista = filtra_tramitacao_adm_destino(unidade_destino) + qs = qs.filter(id__in=lista).distinct() if 'o' in self.request.GET and not self.request.GET['o']: qs = qs.order_by('-ano', '-numero') + qs = qs.prefetch_related("documentoacessorioadministrativo_set", + "tramitacaoadministrativo_set", + "tramitacaoadministrativo_set__status", + "tramitacaoadministrativo_set__unidade_tramitacao_local", + "tramitacaoadministrativo_set__unidade_tramitacao_destino") + kwargs.update({ 'queryset': qs, }) diff --git a/sapl/relatorios/templates/pdf_capa_processo_preparar_pysc.py b/sapl/relatorios/templates/pdf_capa_processo_preparar_pysc.py index eacabf86e..4e741e564 100755 --- a/sapl/relatorios/templates/pdf_capa_processo_preparar_pysc.py +++ b/sapl/relatorios/templates/pdf_capa_processo_preparar_pysc.py @@ -144,6 +144,6 @@ sessao = session.id caminho = context.pdf_capa_processo_gerar( sessao, imagem, data, protocolos, cabecalho, rodape, filtro) if caminho == 'aviso': - return response.redirect('mensagem_emitir_proc') + response.redirect('mensagem_emitir_proc') else: response.redirect(caminho) diff --git a/sapl/relatorios/templates/pdf_detalhe_materia_preparar_pysc.py b/sapl/relatorios/templates/pdf_detalhe_materia_preparar_pysc.py index ebe3be006..19d6271bc 100644 --- a/sapl/relatorios/templates/pdf_detalhe_materia_preparar_pysc.py +++ b/sapl/relatorios/templates/pdf_detalhe_materia_preparar_pysc.py @@ -279,6 +279,6 @@ caminho = context.pdf_detalhe_materia_gerar(imagem, rodape, inf_basicas_dic, ori lst_des_iniciais, dic_tramitacoes, lst_relatorias, lst_numeracoes, lst_legis_citadas, lst_acessorios, sessao=session.id) if caminho == 'aviso': - return response.redirect('mensagem_emitir_proc') + response.redirect('mensagem_emitir_proc') else: response.redirect(caminho) diff --git a/sapl/relatorios/templates/pdf_documento_administrativo_preparar_pysc.py b/sapl/relatorios/templates/pdf_documento_administrativo_preparar_pysc.py index cbaf7d0e3..775ad3d68 100755 --- a/sapl/relatorios/templates/pdf_documento_administrativo_preparar_pysc.py +++ b/sapl/relatorios/templates/pdf_documento_administrativo_preparar_pysc.py @@ -130,6 +130,6 @@ sessao = session.id caminho = context.pdf_documento_administrativo_gerar( sessao, imagem, data, documentos, cabecalho, rodape, filtro) if caminho == 'aviso': - return response.redirect('mensagem_emitir_proc') + response.redirect('mensagem_emitir_proc') else: response.redirect(caminho) diff --git a/sapl/relatorios/templates/pdf_espelho_preparar_pysc.py b/sapl/relatorios/templates/pdf_espelho_preparar_pysc.py index e51fcd43c..43fae0f7c 100644 --- a/sapl/relatorios/templates/pdf_espelho_preparar_pysc.py +++ b/sapl/relatorios/templates/pdf_espelho_preparar_pysc.py @@ -205,6 +205,6 @@ sessao = session.id caminho = context.pdf_espelho_gerar( sessao, imagem, data, materias, cabecalho, rodape, filtro) if caminho == 'aviso': - return response.redirect('mensagem_emitir_proc') + response.redirect('mensagem_emitir_proc') else: response.redirect(caminho) diff --git a/sapl/relatorios/templates/pdf_etiqueta_protocolo_gerar.py b/sapl/relatorios/templates/pdf_etiqueta_protocolo_gerar.py index 38057791f..8b9c3b2cc 100755 --- a/sapl/relatorios/templates/pdf_etiqueta_protocolo_gerar.py +++ b/sapl/relatorios/templates/pdf_etiqueta_protocolo_gerar.py @@ -98,7 +98,11 @@ def protocolos(lst_protocolos, dic_cabecalho): tmp_data += '\t\t' + \ dic['natureza'] if dic['ident_processo']: - tmp_data += ' - ' + dic['ident_processo'] + '\n' + # Limita o tamanho do texto para não "explodir" as etiquetas + descricao = dic['ident_processo'][:60] + if len(dic['ident_processo']) > 60: + descricao += '...' + tmp_data += ' - ' + descricao + '\n' else: tmp_data += '\n' diff --git a/sapl/relatorios/templates/pdf_etiqueta_protocolo_preparar_pysc.py b/sapl/relatorios/templates/pdf_etiqueta_protocolo_preparar_pysc.py index ee9b97330..29ce497b3 100755 --- a/sapl/relatorios/templates/pdf_etiqueta_protocolo_preparar_pysc.py +++ b/sapl/relatorios/templates/pdf_etiqueta_protocolo_preparar_pysc.py @@ -132,6 +132,6 @@ sessao = session.id caminho = context.pdf_etiqueta_protocolo_gerar( sessao, imagem, data, protocolos, cabecalho, rodape, filtro) if caminho == 'aviso': - return response.redirect('mensagem_emitir_proc') + response.redirect('mensagem_emitir_proc') else: response.redirect(caminho) diff --git a/sapl/relatorios/templates/pdf_materia_preparar_pysc.py b/sapl/relatorios/templates/pdf_materia_preparar_pysc.py index 30ddaef9b..95a0b6192 100644 --- a/sapl/relatorios/templates/pdf_materia_preparar_pysc.py +++ b/sapl/relatorios/templates/pdf_materia_preparar_pysc.py @@ -164,6 +164,6 @@ sessao = session.id caminho = context.pdf_materia_gerar( sessao, imagem, data, materias, cabecalho, rodape, filtro) if caminho == 'aviso': - return response.redirect('mensagem_emitir_proc') + response.redirect('mensagem_emitir_proc') else: response.redirect(caminho) diff --git a/sapl/relatorios/templates/pdf_norma_preparar_pysc.py b/sapl/relatorios/templates/pdf_norma_preparar_pysc.py index c8b5b056c..047f04e21 100755 --- a/sapl/relatorios/templates/pdf_norma_preparar_pysc.py +++ b/sapl/relatorios/templates/pdf_norma_preparar_pysc.py @@ -100,6 +100,6 @@ sessao = session.id caminho = context.pdf_norma_gerar( sessao, imagem, data, normas, cabecalho, rodape, filtro) if caminho == 'aviso': - return response.redirect('mensagem_emitir_proc') + response.redirect('mensagem_emitir_proc') else: response.redirect(caminho) diff --git a/sapl/relatorios/templates/pdf_ordem_dia_preparar_pysc.py b/sapl/relatorios/templates/pdf_ordem_dia_preparar_pysc.py index 4e765239f..79281beed 100644 --- a/sapl/relatorios/templates/pdf_ordem_dia_preparar_pysc.py +++ b/sapl/relatorios/templates/pdf_ordem_dia_preparar_pysc.py @@ -155,6 +155,6 @@ if context.REQUEST['cod_sessao_plen'] != '': caminho = context.pdf_ordem_dia_gerar( sessao, imagem, dat_ordem, splen, pauta, cabecalho, rodape) if caminho == 'aviso': - return response.redirect('mensagem_emitir_proc') + response.redirect('mensagem_emitir_proc') else: response.redirect(caminho) diff --git a/sapl/relatorios/templates/pdf_pauta_sessao_preparar_pysc.py b/sapl/relatorios/templates/pdf_pauta_sessao_preparar_pysc.py index d76545371..914f77a7b 100755 --- a/sapl/relatorios/templates/pdf_pauta_sessao_preparar_pysc.py +++ b/sapl/relatorios/templates/pdf_pauta_sessao_preparar_pysc.py @@ -184,6 +184,6 @@ if context.REQUEST['data'] != '': caminho = context.pdf_pauta_sessao_gerar( rodape, sessao, imagem, inf_basicas_dic, lst_votacao, lst_expediente_materia) if caminho == 'aviso': - return response.redirect('mensagem_emitir_proc') + response.redirect('mensagem_emitir_proc') else: response.redirect(caminho) diff --git a/sapl/relatorios/templates/pdf_protocolo_preparar_pysc.py b/sapl/relatorios/templates/pdf_protocolo_preparar_pysc.py index 9a3a0d383..4a1593184 100755 --- a/sapl/relatorios/templates/pdf_protocolo_preparar_pysc.py +++ b/sapl/relatorios/templates/pdf_protocolo_preparar_pysc.py @@ -121,6 +121,6 @@ sessao = session.id caminho = context.pdf_protocolo_gerar( sessao, imagem, data, protocolos, cabecalho, rodape, filtro) if caminho == 'aviso': - return response.redirect('mensagem_emitir_proc') + response.redirect('mensagem_emitir_proc') else: response.redirect(caminho) diff --git a/sapl/relatorios/templates/pdf_sessao_plenaria_preparar_pysc.py b/sapl/relatorios/templates/pdf_sessao_plenaria_preparar_pysc.py index b8f7adda2..5eef2d912 100644 --- a/sapl/relatorios/templates/pdf_sessao_plenaria_preparar_pysc.py +++ b/sapl/relatorios/templates/pdf_sessao_plenaria_preparar_pysc.py @@ -279,6 +279,6 @@ if context.REQUEST['data'] != '': caminho = context.pdf_sessao_plenaria_gerar(rodape, sessao, imagem, inf_basicas_dic, lst_mesa, lst_presenca_sessao, lst_expedientes, lst_expediente_materia, lst_oradores_expediente, lst_presenca_ordem_dia, lst_votacao, lst_oradores) if caminho == 'aviso': - return response.redirect('mensagem_emitir_proc') + response.redirect('mensagem_emitir_proc') else: response.redirect(caminho) diff --git a/sapl/relatorios/views.py b/sapl/relatorios/views.py index 0bad27889..28d6010e4 100644 --- a/sapl/relatorios/views.py +++ b/sapl/relatorios/views.py @@ -963,7 +963,8 @@ def get_etiqueta_protocolos(prots): dic['num_documento'] = '' for documento in DocumentoAdministrativo.objects.filter( protocolo=p): - dic['num_documento'] = str(documento) + dic['num_documento'] = documento.tipo.sigla + ' ' + \ + str(documento.numero) + '/' + str(documento.ano) dic['ident_processo'] = dic['num_materia'] or dic['num_documento'] diff --git a/sapl/settings.py b/sapl/settings.py index ca24b9f02..72cf086cc 100644 --- a/sapl/settings.py +++ b/sapl/settings.py @@ -87,6 +87,7 @@ INSTALLED_APPS = ( 'sass_processor', 'rest_framework', 'reversion', + 'reversion_compare', 'whoosh', 'speedinfo', diff --git a/sapl/templates/base/RelatorioMateriasPorAnoAutorTipo_filter.html b/sapl/templates/base/RelatorioMateriasPorAnoAutorTipo_filter.html index 43deac36b..890733053 100644 --- a/sapl/templates/base/RelatorioMateriasPorAnoAutorTipo_filter.html +++ b/sapl/templates/base/RelatorioMateriasPorAnoAutorTipo_filter.html @@ -12,7 +12,8 @@ {% trans 'Fazer nova pesquisa' %}



- +

Autorias

+

{% for r in relatorio %}

{{r.autor}}


@@ -35,7 +36,30 @@
{% endfor %}

- +

Coautorias

+

+ {% for r in corelatorio %} +

{{r.autor}}


+
+ + + + + + + + {% for i in r.materia %} + + + + {% endfor %} + +
Natureza da ProposituraQuantidade
{{i.0}}{{i.1}}
+

Total: {{r.total}}


+
+
+ {% endfor %} +

diff --git a/sapl/templates/base/RelatorioMateriasPorAutor_filter.html b/sapl/templates/base/RelatorioMateriasPorAutor_filter.html index c75aad42b..ec26ecd63 100644 --- a/sapl/templates/base/RelatorioMateriasPorAutor_filter.html +++ b/sapl/templates/base/RelatorioMateriasPorAutor_filter.html @@ -36,7 +36,8 @@ - + + @@ -48,7 +49,16 @@ + diff --git a/sapl/templates/materia/layouts.yaml b/sapl/templates/materia/layouts.yaml index d31817b94..7c0c17e4e 100644 --- a/sapl/templates/materia/layouts.yaml +++ b/sapl/templates/materia/layouts.yaml @@ -100,7 +100,7 @@ Proposicao: StatusTramitacao: {% trans 'Status Tramitação' %}: - - indicador:3 sigla:2 descricao + - sigla:2 descricao:6 indicador:4 UnidadeTramitacao: {% trans 'Unidade Tramitação' %}: diff --git a/sapl/templates/materia/materialegislativa_filter.html b/sapl/templates/materia/materialegislativa_filter.html index f44d8788b..b10c20346 100644 --- a/sapl/templates/materia/materialegislativa_filter.html +++ b/sapl/templates/materia/materialegislativa_filter.html @@ -4,9 +4,10 @@ {% block actions %}
+ {% if perms.materia.add_materialegislativa %} @@ -82,11 +83,11 @@ {% for rv in m.registrovotacao_set.all %} {% if rv.ordem %} - {{ rv.ordem.data_ordem }} + {{ rv.ordem.sessao_plenaria.data_inicio }} {% elif rv.expediente %} - {{ rv.expediente.data_ordem }} + {{ rv.expediente.sessao_plenaria.data_inicio }} {% endif %}
diff --git a/sapl/templates/materia/relatoria_form.html b/sapl/templates/materia/relatoria_form.html index f8fbdaf5e..34cffd3db 100644 --- a/sapl/templates/materia/relatoria_form.html +++ b/sapl/templates/materia/relatoria_form.html @@ -6,7 +6,7 @@ {% block base_content %} {% if form.comissao.value == 0 %} {% else %} {% crispy form %} diff --git a/sapl/templates/norma/layouts.yaml b/sapl/templates/norma/layouts.yaml index e1ca61084..a06b80b2b 100644 --- a/sapl/templates/norma/layouts.yaml +++ b/sapl/templates/norma/layouts.yaml @@ -45,7 +45,7 @@ LegislacaoCitada: LegislacaoCitadaDetail: {% trans 'Legislação Citada' %}: - - norma + - norma|fk_urlize_for_detail - disposicoes parte livro titulo - capitulo secao subsecao artigo - paragrafo inciso alinea item diff --git a/sapl/templates/norma/normajuridica_filter.html b/sapl/templates/norma/normajuridica_filter.html index 511f20a01..8119ce4d1 100644 --- a/sapl/templates/norma/normajuridica_filter.html +++ b/sapl/templates/norma/normajuridica_filter.html @@ -4,9 +4,11 @@ {% block actions %}
+ {% if perms.norma.add_normajuridica %} @@ -84,6 +86,18 @@

Nenhuma norma encontrada com essas especificações

{% endif %} {% endif %} + + {% endblock detail_content %} {% block table_content %} diff --git a/sapl/templates/norma/normajuridica_form.html b/sapl/templates/norma/normajuridica_form.html index 9c4772a80..58a48046a 100644 --- a/sapl/templates/norma/normajuridica_form.html +++ b/sapl/templates/norma/normajuridica_form.html @@ -41,6 +41,15 @@ for (i = 0; i < fields.length; i++) { $(fields[i]).change(recuperar_norma); } + + var numeroField = $("#id_numero"); + + numeroField.keyup(function() { + var numero = numeroField.val(); + if (numero.startsWith("0")) { + numeroField.val(numero.replace(/^0+/, '')); + } + }); {% endblock %} diff --git a/sapl/templates/protocoloadm/comprovante.html b/sapl/templates/protocoloadm/comprovante.html index e4258c263..630af88c0 100644 --- a/sapl/templates/protocoloadm/comprovante.html +++ b/sapl/templates/protocoloadm/comprovante.html @@ -70,6 +70,7 @@
+<<<<<<< HEAD {% else %} @@ -79,6 +80,17 @@ +======= + {% else %} + + + + + + + + +>>>>>>> 3.1.x {% endif %} diff --git a/sapl/templates/protocoloadm/documentoadministrativo_filter.html b/sapl/templates/protocoloadm/documentoadministrativo_filter.html index b15726772..83eb1bd40 100644 --- a/sapl/templates/protocoloadm/documentoadministrativo_filter.html +++ b/sapl/templates/protocoloadm/documentoadministrativo_filter.html @@ -37,11 +37,26 @@
QUADRO GERAL
Matéria EmentaAutor(es)AutorCoautor(es)
{{materia.ementa}} {% for autor in materia.autoria_set.all %} + {% if autor.primeiro_autor %} {{autor.autor}}
+ {% endif %} + {% endfor %} +
+ {% for autor in materia.autoria_set.all %} + {% if not autor.primeiro_autor %} + {{autor.autor}}
+ {% endif %} {% endfor %}
Autor {{ protocolo.autor }}
AssuntoInteressado {{ protocolo.interessado }}
Assunto{{ protocolo.assunto_ementa }}
Interessado{{ protocolo.interessado }}
Natureza
{{d.tipo.sigla}} {{d.numero}}/{{d.ano}} - {{d.tipo}}
- Interessado: {{ d.interessado|default_if_none:"Não informado"}}
- Assunto: {{ d.assunto|safe }}
+ Interessado: {{ d.interessado|default_if_none:"Não informado"}} +
+ Assunto: {{ d.assunto|safe }} +
{% if d.protocolo %} Protocolo: {{ d.protocolo}}
{% endif %} + {% if d.tramitacaoadministrativo_set.last.unidade_tramitacao_destino %} + Localização Atual:  {{d.tramitacaoadministrativo_set.last.unidade_tramitacao_destino}} +
+ Status: {{d.tramitacaoadministrativo_set.last.status}} +
+ {% endif %} + {% if d.documentoacessorioadministrativo_set.all.exists %} + Documentos Acessórios: + + {{ d.documentoacessorioadministrativo_set.all.count }} + +
+ {% endif %} {% if d.texto_integral %} Texto Integral
{% endif %} diff --git a/sapl/templates/protocoloadm/protocolo_mostrar.html b/sapl/templates/protocoloadm/protocolo_mostrar.html index 0e434429b..376c730a1 100644 --- a/sapl/templates/protocoloadm/protocolo_mostrar.html +++ b/sapl/templates/protocoloadm/protocolo_mostrar.html @@ -38,11 +38,10 @@ {{materia}} {% endif %}
- {% if not protocolo.anulado %}Criar Matéria{% endif %} + {% if not protocolo.anulado%}{% if not materia %}Criar Matéria    {% endif %}{% endif %} {% endif %} -      Comprovante {% endblock detail_content %} diff --git a/sapl/utils.py b/sapl/utils.py index b674481c3..a656fa5a0 100644 --- a/sapl/utils.py +++ b/sapl/utils.py @@ -25,6 +25,7 @@ from django_filters.filterset import STRICTNESS from easy_thumbnails import source_generators from floppyforms import ClearableFileInput from reversion.admin import VersionAdmin +from reversion_compare.admin import CompareVersionAdmin from unipath.path import Path from sapl.crispy_layout_mixin import SaplFormLayout, form_actions, to_row @@ -228,7 +229,7 @@ def register_all_models_in_admin(module_name): appname = appname[1] if appname[0] == 'sapl' else appname[0] app = apps.get_app_config(appname) for model in app.get_models(): - class CustomModelAdmin(VersionAdmin): + class CustomModelAdmin(CompareVersionAdmin): list_display = [f.name for f in model._meta.fields if f.name != 'id'] @@ -355,6 +356,7 @@ TIPOS_TEXTO_PERMITIDOS = ( 'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/xml', + 'application/octet-stream', 'text/xml', 'text/html', ) diff --git a/setup.py b/setup.py index b76d46c98..758b57fc8 100644 --- a/setup.py +++ b/setup.py @@ -49,7 +49,7 @@ install_requires = [ ] setup( name='interlegis-sapl', - version='3.1.92', + version='3.1.102', packages=find_packages(), include_package_data=True, license='GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007', diff --git a/start.sh b/start.sh index 4790d2df4..9695572ef 100755 --- a/start.sh +++ b/start.sh @@ -49,7 +49,7 @@ create_env # manage.py migrate --noinput nao funcionava yes yes | python3 manage.py migrate #python3 manage.py collectstatic --no-input -python3 manage.py rebuild_index --noinput & +# python3 manage.py rebuild_index --noinput & echo "Criando usuário admin..."