Browse Source

Merge branch '3.1.x'

pull/1828/merge
Edward Ribeiro 8 years ago
parent
commit
5a8a72c717
  1. 2
      docker-compose.yml
  2. 5
      gunicorn_start.sh
  3. 1
      requirements/requirements.txt
  4. 26
      sapl/audiencia/tests/test_audiencia.py
  5. 17
      sapl/base/forms.py
  6. 7
      sapl/base/views.py
  7. 5
      sapl/comissoes/forms.py
  8. 20
      sapl/comissoes/migrations/0017_auto_20180717_0827.py
  9. 2
      sapl/comissoes/models.py
  10. 48
      sapl/comissoes/tests/test_comissoes.py
  11. 3
      sapl/legacy/migracao.py
  12. 76
      sapl/legacy/migracao_dados.py
  13. 19
      sapl/legacy/migracao_documentos.py
  14. 118
      sapl/legacy/scripts/exporta_zope/exporta_zope.py
  15. 16
      sapl/materia/forms.py
  16. 195
      sapl/materia/tests/test_materia_form.py
  17. 50
      sapl/materia/views.py
  18. 8
      sapl/painel/views.py
  19. 38
      sapl/protocoloadm/forms.py
  20. 28
      sapl/protocoloadm/views.py
  21. 2
      sapl/relatorios/templates/pdf_capa_processo_preparar_pysc.py
  22. 2
      sapl/relatorios/templates/pdf_detalhe_materia_preparar_pysc.py
  23. 2
      sapl/relatorios/templates/pdf_documento_administrativo_preparar_pysc.py
  24. 2
      sapl/relatorios/templates/pdf_espelho_preparar_pysc.py
  25. 6
      sapl/relatorios/templates/pdf_etiqueta_protocolo_gerar.py
  26. 2
      sapl/relatorios/templates/pdf_etiqueta_protocolo_preparar_pysc.py
  27. 2
      sapl/relatorios/templates/pdf_materia_preparar_pysc.py
  28. 2
      sapl/relatorios/templates/pdf_norma_preparar_pysc.py
  29. 2
      sapl/relatorios/templates/pdf_ordem_dia_preparar_pysc.py
  30. 2
      sapl/relatorios/templates/pdf_pauta_sessao_preparar_pysc.py
  31. 2
      sapl/relatorios/templates/pdf_protocolo_preparar_pysc.py
  32. 2
      sapl/relatorios/templates/pdf_sessao_plenaria_preparar_pysc.py
  33. 3
      sapl/relatorios/views.py
  34. 1
      sapl/settings.py
  35. 28
      sapl/templates/base/RelatorioMateriasPorAnoAutorTipo_filter.html
  36. 12
      sapl/templates/base/RelatorioMateriasPorAutor_filter.html
  37. 2
      sapl/templates/materia/layouts.yaml
  38. 7
      sapl/templates/materia/materialegislativa_filter.html
  39. 2
      sapl/templates/materia/relatoria_form.html
  40. 2
      sapl/templates/norma/layouts.yaml
  41. 14
      sapl/templates/norma/normajuridica_filter.html
  42. 9
      sapl/templates/norma/normajuridica_form.html
  43. 12
      sapl/templates/protocoloadm/comprovante.html
  44. 19
      sapl/templates/protocoloadm/documentoadministrativo_filter.html
  45. 3
      sapl/templates/protocoloadm/protocolo_mostrar.html
  46. 4
      sapl/utils.py
  47. 2
      setup.py
  48. 2
      start.sh

2
docker-compose.yml

@ -11,7 +11,7 @@ sapldb:
ports: ports:
- "5432:5432" - "5432:5432"
sapl: sapl:
image: interlegis/sapl:3.1.92 image: interlegis/sapl:3.1.102
restart: always restart: always
environment: environment:
ADMIN_PASSWORD: interlegis ADMIN_PASSWORD: interlegis

5
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 (*) SOCKFILE=/var/interlegis/sapl/run/gunicorn.sock # we will communicate using this unix socket (*)
USER=`whoami` # the user to run as (*) USER=`whoami` # the user to run as (*)
GROUP=`whoami` # the group 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 # NUM_WORKERS = 2 * CPUS + 1
TIMEOUT=60
MAX_REQUESTS=100 # number of requests before restarting worker MAX_REQUESTS=100 # number of requests before restarting worker
DJANGO_SETTINGS_MODULE=sapl.settings # which settings file should Django use (*) DJANGO_SETTINGS_MODULE=sapl.settings # which settings file should Django use (*)
DJANGO_WSGI_MODULE=sapl.wsgi # WSGI module name (*) 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) # Programs meant to be run under supervisor should not daemonize themselves (do not use --daemon)
exec gunicorn ${DJANGO_WSGI_MODULE}:application \ exec gunicorn ${DJANGO_WSGI_MODULE}:application \
--name $NAME \ --name $NAME \
--log-level debug \
--timeout $TIMEOUT \
--workers $NUM_WORKERS \ --workers $NUM_WORKERS \
--max-requests $MAX_REQUESTS \ --max-requests $MAX_REQUESTS \
--user $USER \ --user $USER \

1
requirements/requirements.txt

@ -36,3 +36,4 @@ django-reversion==2.0.8
WeasyPrint==0.42 WeasyPrint==0.42
whoosh==2.7.4 whoosh==2.7.4
django-speedinfo==1.3.5 django-speedinfo==1.3.5
django-reversion-compare==0.8.4

26
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

17
sapl/base/forms.py

@ -44,16 +44,21 @@ STATUS_USER_CHOICE = [
def get_roles(): 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): class UsuarioCreateForm(ModelForm):
username = forms.CharField(required=True, label="Nome de usuário") username = forms.CharField(required=True, label="Nome de usuário",
firstname = forms.CharField(required=True, label="Nome") max_length=30)
lastname = forms.CharField(required=True, label="Sobrenome") firstname = forms.CharField(required=True, label="Nome", max_length=30)
password1 = forms.CharField(required=True, widget=forms.PasswordInput, label='Senha') lastname = forms.CharField(required=True, label="Sobrenome", max_length=30)
password2 = forms.CharField(required=True, widget=forms.PasswordInput, label='Confirmar senha') 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, user_active = forms.ChoiceField(required=False, choices=YES_NO_CHOICES,
label="Usuário ativo?", initial='True') label="Usuário ativo?", initial='True')

7
sapl/base/views.py

@ -420,9 +420,9 @@ class RelatorioMateriasPorAnoAutorTipoView(FilterView):
filterset_class = RelatorioMateriasPorAnoAutorTipoFilterSet filterset_class = RelatorioMateriasPorAnoAutorTipoFilterSet
template_name = 'base/RelatorioMateriasPorAnoAutorTipo_filter.html' 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', 'autor',
'materia__tipo__sigla', 'materia__tipo__sigla',
'materia__tipo__descricao').annotate( 'materia__tipo__descricao').annotate(
@ -488,7 +488,8 @@ class RelatorioMateriasPorAnoAutorTipoView(FilterView):
if 'ano' in self.request.GET and self.request.GET['ano']: if 'ano' in self.request.GET and self.request.GET['ano']:
ano = int(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: else:
context['relatorio'] = [] context['relatorio'] = []

5
sapl/comissoes/forms.py

@ -61,7 +61,8 @@ class PeriodoForm(forms.ModelForm):
data_fim = cleaned_data['data_fim'] data_fim = cleaned_data['data_fim']
if data_fim and data_fim < data_inicio: 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 return cleaned_data
@ -99,7 +100,7 @@ class ParticipacaoCreateForm(forms.ModelForm):
exclude(id__in=id_part) exclude(id__in=id_part)
eligible = self.verifica() eligible = self.verifica()
result = list(set(qs) & set(eligible)) 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 self.fields['parlamentar'].queryset = qs
else: else:
ids = [e.id for e in eligible] ids = [e.id for e in eligible]

20
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'),
),
]

2
sapl/comissoes/models.py

@ -223,7 +223,7 @@ class Reuniao(models.Model):
local_reuniao = models.CharField( local_reuniao = models.CharField(
max_length=100, blank=True, verbose_name=_('Local da Reunião')) max_length=100, blank=True, verbose_name=_('Local da Reunião'))
observacao = models.TextField( observacao = models.TextField(
max_length=150, blank=True, verbose_name=_('Observação')) blank=True, verbose_name=_('Observação'))
url_audio = models.URLField( url_audio = models.URLField(
max_length=150, blank=True, max_length=150, blank=True,
verbose_name=_('URL do Arquivo de Áudio (Formatos MP3 / AAC)')) verbose_name=_('URL do Arquivo de Áudio (Formatos MP3 / AAC)'))

48
sapl/comissoes/tests/test_comissoes.py

@ -1,9 +1,11 @@
import pytest import pytest
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from model_mommy import mommy 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.parlamentares.models import Filiacao, Parlamentar, Partido
from sapl.comissoes import forms
def make_composicao(comissao): def make_composicao(comissao):
@ -96,3 +98,47 @@ def test_incluir_comissao_errors(admin_client):
['Este campo é obrigatório.']) ['Este campo é obrigatório.'])
assert (response.context_data['form'].errors['data_criacao'] == assert (response.context_data['form'].errors['data_criacao'] ==
['Este campo é obrigatório.']) ['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

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

76
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 [
@ -497,9 +513,10 @@ def checa_registros_votacao_ambiguos_e_remove_nao_usados():
# interrompe migração se houver registros ambíguos # interrompe migração se houver registros ambíguos
ambiguos = ordem.intersection(expediente) ambiguos = ordem.intersection(expediente)
assert not ambiguos, '''Existe(m) RegistroVotacao ambíguo(s): {} if ambiguos:
Corrija os dados originais antes de migrar!'''.format( warn('registro_votacao_ambiguos',
ambiguos) 'Existe(m) RegistroVotacao ambíguo(s): {cod_votacao}',
{'cod_votacao': ambiguos})
# exclui registros não usados (zumbis) # exclui registros não usados (zumbis)
todos = set(primeira_coluna(exec_legado( todos = set(primeira_coluna(exec_legado(
@ -567,8 +584,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 +666,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 +757,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 +831,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 +865,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
@ -1094,6 +1110,18 @@ def adjust_protocolo_antes_salvar(new, old):
{'cod_protocolo': old.cod_protocolo}) {'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): def adjust_registrovotacao_antes_salvar(new, old):
ordem_dia = OrdemDia.objects.filter( ordem_dia = OrdemDia.objects.filter(
pk=old.cod_ordem, materia=old.cod_materia) pk=old.cod_ordem, materia=old.cod_materia)
@ -1104,6 +1132,19 @@ def adjust_registrovotacao_antes_salvar(new, old):
new.ordem = ordem_dia[0] new.ordem = ordem_dia[0]
if not ordem_dia and expediente_materia: if not ordem_dia and expediente_materia:
new.expediente = expediente_materia[0] 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): def adjust_tipoafastamento(new, old):
@ -1180,8 +1221,10 @@ 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(',')) if not cod_assunto:
if a in assuntos_migrados] return []
cods = {int(a) for a in cod_assunto.split(',') if a}
return sorted(cods.intersection(assuntos_migrados))
norma_para_assuntos = [ norma_para_assuntos = [
(norma, filtra_assuntos_migrados(cod_assunto)) (norma, filtra_assuntos_migrados(cod_assunto))
@ -1357,6 +1400,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')

19
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,21 @@ 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)
<<<<<<< HEAD
repo.git.execute('git annex get sapl_documentos/parlamentar'.split()) 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: 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'))

118
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 enumerate_by_key_list(folder, key_list, type_key): def print_msg_poskeyerror(id):
for entry in folder.get(key_list, []):
id, meta_type = entry['id'], entry[type_key]
try:
obj = br(folder.get(id, None))
except POSKeyError:
print('#' * 80) print('#' * 80)
print('#' * 80) print('#' * 80)
print('ATENÇÃO: DIRETÓRIO corrompido: {}'.format(id)) print('ATENÇÃO: DIRETÓRIO corrompido: {}'.format(id))
print('#' * 80) print('#' * 80)
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 = folder.get(id, None)
except POSKeyError:
print_msg_poskeyerror(id)
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
try:
for contagem_real, (id, obj) in enumerate(tree.iteritems(), start=1): for contagem_real, (id, obj) in enumerate(tree.iteritems(), start=1):
obj, meta_type = br(obj), type(obj).__name__ meta_type = type(obj).__name__
yield id, obj, meta_type 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:
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 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())
@ -302,6 +323,7 @@ def find_sapl(app):
sapl = br(app['sapl']) sapl = br(app['sapl'])
return sapl return sapl
<<<<<<< HEAD
def detectar_encoding(fonte): def detectar_encoding(fonte):
desc = magic.from_buffer(fonte) desc = magic.from_buffer(fonte)
@ -318,7 +340,23 @@ def autodecode(fonte):
else: else:
return fonte 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): def dump_propriedades(docs, path, salvar):
props_sapl = br(docs['props_sapl']) props_sapl = br(docs['props_sapl'])
ids = [p['id'] for p in props_sapl['_properties']] 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) save_as_yaml(path, 'usuarios.yaml', users, salvar)
<<<<<<< HEAD
def _dump_sapl(data_fs_path, documentos_fs_path, destino, salvar): def _dump_sapl(data_fs_path, documentos_fs_path, destino, salvar):
assert Path(data_fs_path).exists() assert Path(data_fs_path).exists()
assert Path(documentos_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) app, close_db = get_app(data_fs_path)
try: try:
@ -352,12 +399,16 @@ 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:
<<<<<<< HEAD
dump_folder(br(sapl['XSLT']), destino, salvar) dump_folder(br(sapl['XSLT']), destino, salvar)
=======
dump_folder(br(sapl['XSLT']), destino, salvar, mtimes)
>>>>>>> 3.1.x
# 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,32 +418,23 @@ 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']:
extensao = guess_extension(fullname, conteudo) extensao = guess_extension(fullname, conteudo)
return base + extensao return base + extensao, extensao
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, extensao = ajusta_extensao(fullname, conteudo)
sha.update(conteudo)
if sha.hexdigest() in hashes: # ajusta caminhos XSLT p conteúdos relacionados ao SDE
print('- hash encontrado - {}'.format(fullname)) if extensao in ['.xsl', '.xslt', '.xml']:
else: conteudo = conteudo.replace('"XSLT/HTML', '"/XSLT/HTML')
fullname = ajusta_extensao(fullname, conteudo)
if os.path.exists(fullname): if exists(fullname):
# destrava arquivo pré-existente (o conteúdo mudou) # destrava arquivo pré-existente (o conteúdo mudou)
repo_execute(repo, 'git annex unlock', fullname) repo_execute(repo, 'git annex unlock', fullname)
with open(fullname, 'w') as arq: with open(fullname, 'w') as arq:
@ -409,8 +451,13 @@ 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')]
<<<<<<< HEAD
assert data_fs_path.exists(), 'Origem não existe: {}'.format(data_fs_path) assert data_fs_path.exists(), 'Origem não existe: {}'.format(data_fs_path)
if not documentos_fs_path.exists(): 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 documentos_fs_path = data_fs_path
nome_banco_legado = 'sapl_cm_{}'.format(sigla) nome_banco_legado = 'sapl_cm_{}'.format(sigla)
@ -427,12 +474,21 @@ def dump_sapl(sigla):
salvar = build_salvar(repo) salvar = build_salvar(repo)
try: try:
finalizado = False finalizado = False
<<<<<<< HEAD
_dump_sapl(data_fs_path, documentos_fs_path, destino, salvar) _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 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'

16
sapl/materia/forms.py

@ -186,6 +186,7 @@ class MateriaLegislativaForm(ModelForm):
widget=forms.HiddenInput()) widget=forms.HiddenInput())
self.fields['autor'] = forms.CharField(required=False, self.fields['autor'] = forms.CharField(required=False,
widget=forms.HiddenInput()) widget=forms.HiddenInput())
self.fields['numero_protocolo'].widget.attrs['readonly'] = True
def clean(self): def clean(self):
super(MateriaLegislativaForm, self).clean() super(MateriaLegislativaForm, self).clean()
@ -213,11 +214,16 @@ class MateriaLegislativaForm(ModelForm):
exist_doc = DocumentoAdministrativo.objects.filter( exist_doc = DocumentoAdministrativo.objects.filter(
protocolo_id=protocolo, protocolo_id=protocolo,
ano=ano).exists() ano=ano).exists()
if exist_materia or exist_doc: if exist_materia or exist_doc:
raise ValidationError(_('Protocolo %s/%s ja possui' raise ValidationError(_('Protocolo %s/%s ja possui'
' documento vinculado' ' documento vinculado'
% (protocolo, ano))) % (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: if data_apresentacao.year != ano:
raise ValidationError(_("O ano da matéria não pode ser " raise ValidationError(_("O ano da matéria não pode ser "
"diferente do ano na data de apresentação")) "diferente do ano na data de apresentação"))
@ -270,7 +276,7 @@ class UnidadeTramitacaoForm(ModelForm):
del cleaned_data[key] del cleaned_data[key]
if len(cleaned_data) != 1: if len(cleaned_data) != 1:
msg = _('Somente um campo deve preenchido!') msg = _('Somente um campo deve ser preenchido!')
raise ValidationError(msg) raise ValidationError(msg)
return cleaned_data return cleaned_data
@ -766,14 +772,10 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
def pega_ultima_tramitacao(): def pega_ultima_tramitacao():
ultimas_tramitacoes = Tramitacao.objects.values( return Tramitacao.objects.values(
'materia_id').annotate(data_encaminhamento=Max( 'materia_id').annotate(data_encaminhamento=Max(
'data_encaminhamento'), 'data_encaminhamento'),
id=Max('id')).values_list('id') id=Max('id')).values_list('id', flat=True)
lista = [item for sublist in ultimas_tramitacoes for item in sublist]
return lista
def filtra_tramitacao_status(status): def filtra_tramitacao_status(status):

195
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)}) form = forms.FichaSelecionaForm(data={'materia': str(materia.pk)})
assert form.is_valid() 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

50
sapl/materia/views.py

@ -700,12 +700,15 @@ class ProposicaoCrud(Crud):
messages.success(request, _( messages.success(request, _(
'Proposição enviada com sucesso.')) 'Proposição enviada com sucesso.'))
try:
Numero = MateriaLegislativa.objects.filter(tipo=p.tipo.tipo_conteudo_related, Numero = MateriaLegislativa.objects.filter(tipo=p.tipo.tipo_conteudo_related,
ano=p.ano).last().numero + 1 ano=p.ano).last().numero + 1
messages.success(request, _( messages.success(request, _(
'%s : nº %s de %s <br>Atenção! Este número é apenas um provável ' '%s : nº %s de %s <br>Atenção! Este número é apenas um provável '
'número que pode não corresponder com a realidade' 'número que pode não corresponder com a realidade'
% (p.tipo, Numero, p.ano))) % (p.tipo, Numero, p.ano)))
except ValueError:
pass
elif action == 'return': elif action == 'return':
if not p.data_envio: if not p.data_envio:
@ -857,7 +860,7 @@ class ProposicaoCrud(Crud):
else: else:
obj.data_recebimento = timezone.localtime( obj.data_recebimento = timezone.localtime(
obj.data_recebimento) obj.data_recebimento)
obj.data_recebimento = obj.data_recebimento = formats.date_format( obj.data_recebimento = formats.date_format(
obj.data_recebimento, "DATETIME_FORMAT") obj.data_recebimento, "DATETIME_FORMAT")
if obj.data_envio is None: if obj.data_envio is None:
obj.data_envio = 'Em elaboração...' obj.data_envio = 'Em elaboração...'
@ -937,8 +940,9 @@ class RelatoriaCrud(MasterDetailCrud):
try: try:
comissao = Comissao.objects.get( comissao = Comissao.objects.get(
pk=context['form'].initial['comissao']) pk=context['form'].initial['comissao'])
except ObjectDoesNotExist: except:
pass pass
else: else:
composicao = comissao.composicao_set.order_by( composicao = comissao.composicao_set.order_by(
'-periodo__data_inicio').first() '-periodo__data_inicio').first()
@ -1775,17 +1779,47 @@ class PrimeiraTramitacaoEmLoteView(PermissionRequiredMixin, FilterView):
messages.add_message(request, messages.ERROR, msg) messages.add_message(request, messages.ERROR, msg)
return self.get(request, self.kwargs) 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( data_encaminhamento = tz.localize(datetime.strptime(
request.POST['data_encaminhamento'], "%d/%m/%Y")) 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( data_fim_prazo = tz.localize(datetime.strptime(
request.POST['data_fim_prazo'], "%d/%m/%Y")) request.POST['data_fim_prazo'], "%d/%m/%Y"))
else:
data_fim_prazo = None
# issue https://github.com/interlegis/sapl/issues/1123 # issue https://github.com/interlegis/sapl/issues/1123
# TODO: usar Form # TODO: usar Form

8
sapl/painel/views.py

@ -85,10 +85,16 @@ def votacao_aberta(request):
def votante_view(request): def votante_view(request):
# Pega o votante relacionado ao usuário # Pega o votante relacionado ao usuário
template_name = 'painel/voto_nominal.html' template_name = 'painel/voto_nominal.html'
context = {}
try: try:
votante = Votante.objects.get(user=request.user) votante = Votante.objects.get(user=request.user)
except ObjectDoesNotExist: 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'))} context = {'head_title': str(_('Votação Individual'))}

38
sapl/protocoloadm/forms.py

@ -7,6 +7,7 @@ from django import forms
from django.core.exceptions import (MultipleObjectsReturned, from django.core.exceptions import (MultipleObjectsReturned,
ObjectDoesNotExist, ValidationError) ObjectDoesNotExist, ValidationError)
from django.db import models from django.db import models
from django.db.models import Max
from django.forms import ModelForm from django.forms import ModelForm
from django.utils import timezone from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -708,10 +709,10 @@ class DocumentoAdministrativoForm(ModelForm):
ano=ano_protocolo).exists() ano=ano_protocolo).exists()
exist_doc = DocumentoAdministrativo.objects.filter( exist_doc = DocumentoAdministrativo.objects.filter(
protocolo_id=numero_protocolo, protocolo__numero=numero_protocolo,
ano=ano_protocolo).exists() protocolo__ano=ano_protocolo).exists()
if exist_materia or exist_doc: if exist_materia or exist_doc:
raise ValidationError(_('Protocolo %s/%s ja possui' raise ValidationError(_('Protocolo %s/%s já possui'
' documento vinculado' ' documento vinculado'
% (numero_protocolo, ano_protocolo))) % (numero_protocolo, ano_protocolo)))
@ -873,3 +874,34 @@ class DesvincularMateriaForm(forms.Form):
form_actions(label='Desvincular') 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)

28
sapl/protocoloadm/views.py

@ -31,7 +31,8 @@ from .forms import (AnularProcoloAdmForm, DocumentoAcessorioAdministrativoForm,
DocumentoAdministrativoFilterSet, DocumentoAdministrativoFilterSet,
DocumentoAdministrativoForm, ProtocoloDocumentForm, DocumentoAdministrativoForm, ProtocoloDocumentForm,
ProtocoloFilterSet, ProtocoloMateriaForm, 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, from .models import (DocumentoAcessorioAdministrativo, DocumentoAdministrativo,
StatusTramitacaoAdministrativo, StatusTramitacaoAdministrativo,
TipoDocumentoAdministrativo, TramitacaoAdministrativo) TipoDocumentoAdministrativo, TramitacaoAdministrativo)
@ -307,7 +308,7 @@ class ProtocoloDocumentoView(PermissionRequiredMixin,
protocolo.anulado = False protocolo.anulado = False
if not protocolo.numero: if not protocolo.numero:
protocolo.numero = (numero['numero__max'] + 1) if numero['numero__max'] else 1 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']) msg = _('Número de protocolo deve ser maior que {}').format(numero['numero__max'])
messages.add_message(self.request, messages.ERROR, msg) messages.add_message(self.request, messages.ERROR, msg)
return self.render_to_response(self.get_context_data()) return self.render_to_response(self.get_context_data())
@ -543,13 +544,34 @@ class PesquisarDocumentoAdministrativoView(DocumentoAdministrativoMixin,
kwargs = {'data': self.request.GET or None} 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 = 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']: if 'o' in self.request.GET and not self.request.GET['o']:
qs = qs.order_by('-ano', '-numero') 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({ kwargs.update({
'queryset': qs, 'queryset': qs,
}) })

2
sapl/relatorios/templates/pdf_capa_processo_preparar_pysc.py

@ -144,6 +144,6 @@ sessao = session.id
caminho = context.pdf_capa_processo_gerar( caminho = context.pdf_capa_processo_gerar(
sessao, imagem, data, protocolos, cabecalho, rodape, filtro) sessao, imagem, data, protocolos, cabecalho, rodape, filtro)
if caminho == 'aviso': if caminho == 'aviso':
return response.redirect('mensagem_emitir_proc') response.redirect('mensagem_emitir_proc')
else: else:
response.redirect(caminho) response.redirect(caminho)

2
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_des_iniciais, dic_tramitacoes, lst_relatorias, lst_numeracoes,
lst_legis_citadas, lst_acessorios, sessao=session.id) lst_legis_citadas, lst_acessorios, sessao=session.id)
if caminho == 'aviso': if caminho == 'aviso':
return response.redirect('mensagem_emitir_proc') response.redirect('mensagem_emitir_proc')
else: else:
response.redirect(caminho) response.redirect(caminho)

2
sapl/relatorios/templates/pdf_documento_administrativo_preparar_pysc.py

@ -130,6 +130,6 @@ sessao = session.id
caminho = context.pdf_documento_administrativo_gerar( caminho = context.pdf_documento_administrativo_gerar(
sessao, imagem, data, documentos, cabecalho, rodape, filtro) sessao, imagem, data, documentos, cabecalho, rodape, filtro)
if caminho == 'aviso': if caminho == 'aviso':
return response.redirect('mensagem_emitir_proc') response.redirect('mensagem_emitir_proc')
else: else:
response.redirect(caminho) response.redirect(caminho)

2
sapl/relatorios/templates/pdf_espelho_preparar_pysc.py

@ -205,6 +205,6 @@ sessao = session.id
caminho = context.pdf_espelho_gerar( caminho = context.pdf_espelho_gerar(
sessao, imagem, data, materias, cabecalho, rodape, filtro) sessao, imagem, data, materias, cabecalho, rodape, filtro)
if caminho == 'aviso': if caminho == 'aviso':
return response.redirect('mensagem_emitir_proc') response.redirect('mensagem_emitir_proc')
else: else:
response.redirect(caminho) response.redirect(caminho)

6
sapl/relatorios/templates/pdf_etiqueta_protocolo_gerar.py

@ -98,7 +98,11 @@ def protocolos(lst_protocolos, dic_cabecalho):
tmp_data += '\t\t<para style="P2"><b>' + \ tmp_data += '\t\t<para style="P2"><b>' + \
dic['natureza'] dic['natureza']
if dic['ident_processo']: if dic['ident_processo']:
tmp_data += ' - ' + dic['ident_processo'] + '</b></para>\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 + '</b></para>\n'
else: else:
tmp_data += '</b></para>\n' tmp_data += '</b></para>\n'

2
sapl/relatorios/templates/pdf_etiqueta_protocolo_preparar_pysc.py

@ -132,6 +132,6 @@ sessao = session.id
caminho = context.pdf_etiqueta_protocolo_gerar( caminho = context.pdf_etiqueta_protocolo_gerar(
sessao, imagem, data, protocolos, cabecalho, rodape, filtro) sessao, imagem, data, protocolos, cabecalho, rodape, filtro)
if caminho == 'aviso': if caminho == 'aviso':
return response.redirect('mensagem_emitir_proc') response.redirect('mensagem_emitir_proc')
else: else:
response.redirect(caminho) response.redirect(caminho)

2
sapl/relatorios/templates/pdf_materia_preparar_pysc.py

@ -164,6 +164,6 @@ sessao = session.id
caminho = context.pdf_materia_gerar( caminho = context.pdf_materia_gerar(
sessao, imagem, data, materias, cabecalho, rodape, filtro) sessao, imagem, data, materias, cabecalho, rodape, filtro)
if caminho == 'aviso': if caminho == 'aviso':
return response.redirect('mensagem_emitir_proc') response.redirect('mensagem_emitir_proc')
else: else:
response.redirect(caminho) response.redirect(caminho)

2
sapl/relatorios/templates/pdf_norma_preparar_pysc.py

@ -100,6 +100,6 @@ sessao = session.id
caminho = context.pdf_norma_gerar( caminho = context.pdf_norma_gerar(
sessao, imagem, data, normas, cabecalho, rodape, filtro) sessao, imagem, data, normas, cabecalho, rodape, filtro)
if caminho == 'aviso': if caminho == 'aviso':
return response.redirect('mensagem_emitir_proc') response.redirect('mensagem_emitir_proc')
else: else:
response.redirect(caminho) response.redirect(caminho)

2
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( caminho = context.pdf_ordem_dia_gerar(
sessao, imagem, dat_ordem, splen, pauta, cabecalho, rodape) sessao, imagem, dat_ordem, splen, pauta, cabecalho, rodape)
if caminho == 'aviso': if caminho == 'aviso':
return response.redirect('mensagem_emitir_proc') response.redirect('mensagem_emitir_proc')
else: else:
response.redirect(caminho) response.redirect(caminho)

2
sapl/relatorios/templates/pdf_pauta_sessao_preparar_pysc.py

@ -184,6 +184,6 @@ if context.REQUEST['data'] != '':
caminho = context.pdf_pauta_sessao_gerar( caminho = context.pdf_pauta_sessao_gerar(
rodape, sessao, imagem, inf_basicas_dic, lst_votacao, lst_expediente_materia) rodape, sessao, imagem, inf_basicas_dic, lst_votacao, lst_expediente_materia)
if caminho == 'aviso': if caminho == 'aviso':
return response.redirect('mensagem_emitir_proc') response.redirect('mensagem_emitir_proc')
else: else:
response.redirect(caminho) response.redirect(caminho)

2
sapl/relatorios/templates/pdf_protocolo_preparar_pysc.py

@ -121,6 +121,6 @@ sessao = session.id
caminho = context.pdf_protocolo_gerar( caminho = context.pdf_protocolo_gerar(
sessao, imagem, data, protocolos, cabecalho, rodape, filtro) sessao, imagem, data, protocolos, cabecalho, rodape, filtro)
if caminho == 'aviso': if caminho == 'aviso':
return response.redirect('mensagem_emitir_proc') response.redirect('mensagem_emitir_proc')
else: else:
response.redirect(caminho) response.redirect(caminho)

2
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, 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) lst_expedientes, lst_expediente_materia, lst_oradores_expediente, lst_presenca_ordem_dia, lst_votacao, lst_oradores)
if caminho == 'aviso': if caminho == 'aviso':
return response.redirect('mensagem_emitir_proc') response.redirect('mensagem_emitir_proc')
else: else:
response.redirect(caminho) response.redirect(caminho)

3
sapl/relatorios/views.py

@ -963,7 +963,8 @@ def get_etiqueta_protocolos(prots):
dic['num_documento'] = '' dic['num_documento'] = ''
for documento in DocumentoAdministrativo.objects.filter( for documento in DocumentoAdministrativo.objects.filter(
protocolo=p): 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'] dic['ident_processo'] = dic['num_materia'] or dic['num_documento']

1
sapl/settings.py

@ -87,6 +87,7 @@ INSTALLED_APPS = (
'sass_processor', 'sass_processor',
'rest_framework', 'rest_framework',
'reversion', 'reversion',
'reversion_compare',
'whoosh', 'whoosh',
'speedinfo', 'speedinfo',

28
sapl/templates/base/RelatorioMateriasPorAnoAutorTipo_filter.html

@ -12,7 +12,8 @@
<a href="{% url 'sapl.base:materia_por_ano_autor_tipo' %}" class="btn btn-default">{% trans 'Fazer nova pesquisa' %}</a> <a href="{% url 'sapl.base:materia_por_ano_autor_tipo' %}" class="btn btn-default">{% trans 'Fazer nova pesquisa' %}</a>
</div> </div>
<br /><br /><br /><br /> <br /><br /><br /><br />
<h1>Autorias</h1>
<br/><br/>
{% for r in relatorio %} {% for r in relatorio %}
<h3>{{r.autor}}</h3><br/> <h3>{{r.autor}}</h3><br/>
<table class="table table-bordered table-hover"> <table class="table table-bordered table-hover">
@ -35,7 +36,30 @@
<br/> <br/>
{% endfor %} {% endfor %}
<br/><br/> <br/><br/>
<h1>Coautorias</h1>
<br/><br/>
{% for r in corelatorio %}
<h3>{{r.autor}}</h3><br/>
<table class="table table-bordered table-hover">
<thead class="thead-default" >
<tr class="active">
<th>Natureza da Propositura</th>
<th>Quantidade</th>
</tr>
</thead>
<tbody>
{% for i in r.materia %}
<tr>
<td>{{i.0}}</td><td>{{i.1}}</td>
</tr>
{% endfor %}
</tbody>
</table>
<h3>Total: {{r.total}}</h3><br/>
<br/>
<br/>
{% endfor %}
<br/><br/>
<table class="table table-bordered table-hover"> <table class="table table-bordered table-hover">
<thead class="thead-default" > <thead class="thead-default" >
<tr class="active"><th colspan="2" class="text-center">QUADRO GERAL</th></tr> <tr class="active"><th colspan="2" class="text-center">QUADRO GERAL</th></tr>

12
sapl/templates/base/RelatorioMateriasPorAutor_filter.html

@ -36,7 +36,8 @@
<tr class="active"> <tr class="active">
<th>Matéria</th> <th>Matéria</th>
<th>Ementa</th> <th>Ementa</th>
<th>Autor(es)</th> <th>Autor</th>
<th>Coautor(es)</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -48,7 +49,16 @@
<td>{{materia.ementa}}</td> <td>{{materia.ementa}}</td>
<td> <td>
{% for autor in materia.autoria_set.all %} {% for autor in materia.autoria_set.all %}
{% if autor.primeiro_autor %}
{{autor.autor}}<br /> {{autor.autor}}<br />
{% endif %}
{% endfor %}
</td>
<td>
{% for autor in materia.autoria_set.all %}
{% if not autor.primeiro_autor %}
{{autor.autor}}<br />
{% endif %}
{% endfor %} {% endfor %}
</td> </td>
</tr> </tr>

2
sapl/templates/materia/layouts.yaml

@ -100,7 +100,7 @@ Proposicao:
StatusTramitacao: StatusTramitacao:
{% trans 'Status Tramitação' %}: {% trans 'Status Tramitação' %}:
- indicador:3 sigla:2 descricao - sigla:2 descricao:6 indicador:4
UnidadeTramitacao: UnidadeTramitacao:
{% trans 'Unidade Tramitação' %}: {% trans 'Unidade Tramitação' %}:

7
sapl/templates/materia/materialegislativa_filter.html

@ -4,9 +4,10 @@
{% block actions %} {% block actions %}
<div class="actions btn-group pull-right" role="group"> <div class="actions btn-group pull-right" role="group">
<!--
<a href="{% url 'sapl.base:haystack_search' %}" class="btn btn-default"> <a href="{% url 'sapl.base:haystack_search' %}" class="btn btn-default">
Pesquisa Textual Pesquisa Textual
</a> </a> -->
{% if perms.materia.add_materialegislativa %} {% if perms.materia.add_materialegislativa %}
<a href="{% url 'sapl.materia:materialegislativa_create' %}" class="btn btn-default"> <a href="{% url 'sapl.materia:materialegislativa_create' %}" class="btn btn-default">
@ -82,11 +83,11 @@
{% for rv in m.registrovotacao_set.all %} {% for rv in m.registrovotacao_set.all %}
{% if rv.ordem %} {% if rv.ordem %}
<a href="{% url 'sapl.sessao:ordemdia_list' rv.ordem.sessao_plenaria_id %}"> <a href="{% url 'sapl.sessao:ordemdia_list' rv.ordem.sessao_plenaria_id %}">
{{ rv.ordem.data_ordem }} {{ rv.ordem.sessao_plenaria.data_inicio }}
</a> </a>
{% elif rv.expediente %} {% elif rv.expediente %}
<a href="{% url 'sapl.sessao:expedientemateria_list' rv.expediente.sessao_plenaria_id %}"> <a href="{% url 'sapl.sessao:expedientemateria_list' rv.expediente.sessao_plenaria_id %}">
{{ rv.expediente.data_ordem }} {{ rv.expediente.sessao_plenaria.data_inicio }}
</a> </a>
{% endif %} {% endif %}
</br> </br>

2
sapl/templates/materia/relatoria_form.html

@ -6,7 +6,7 @@
{% block base_content %} {% block base_content %}
{% if form.comissao.value == 0 %} {% if form.comissao.value == 0 %}
<div class="alert alert-danger alert-dismissible fade in" role="alert"> <div class="alert alert-danger alert-dismissible fade in" role="alert">
A localização atual deve ser uma comissão! A localização atual deve ser uma comissão e a Unidade de Destino da última tramitação não pode ser vazia.
</div> </div>
{% else %} {% else %}
{% crispy form %} {% crispy form %}

2
sapl/templates/norma/layouts.yaml

@ -45,7 +45,7 @@ LegislacaoCitada:
LegislacaoCitadaDetail: LegislacaoCitadaDetail:
{% trans 'Legislação Citada' %}: {% trans 'Legislação Citada' %}:
- norma - norma|fk_urlize_for_detail
- disposicoes parte livro titulo - disposicoes parte livro titulo
- capitulo secao subsecao artigo - capitulo secao subsecao artigo
- paragrafo inciso alinea item - paragrafo inciso alinea item

14
sapl/templates/norma/normajuridica_filter.html

@ -4,9 +4,11 @@
{% block actions %} {% block actions %}
<div class="actions btn-group pull-right" role="group"> <div class="actions btn-group pull-right" role="group">
<!--
<a href="{% url 'sapl.base:haystack_search' %}" class="btn btn-default"> <a href="{% url 'sapl.base:haystack_search' %}" class="btn btn-default">
Pesquisa Textual Pesquisa Textual
</a> </a>
-->
{% if perms.norma.add_normajuridica %} {% if perms.norma.add_normajuridica %}
@ -84,6 +86,18 @@
<h2>Nenhuma norma encontrada com essas especificações</h2> <h2>Nenhuma norma encontrada com essas especificações</h2>
{% endif %} {% endif %}
{% endif %} {% endif %}
<script type="text/javascript">
$(document).ready(function() {
var numeroField = $("#id_numero");
numeroField.keyup(function() {
var numero = numeroField.val();
if (numero.startsWith("0")) {
numeroField.val(numero.replace(/^0+/, ''));
}
});
});
</script>
{% endblock detail_content %} {% endblock detail_content %}
{% block table_content %} {% block table_content %}

9
sapl/templates/norma/normajuridica_form.html

@ -41,6 +41,15 @@
for (i = 0; i < fields.length; i++) { for (i = 0; i < fields.length; i++) {
$(fields[i]).change(recuperar_norma); $(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+/, ''));
}
});
</script> </script>
{% endblock %} {% endblock %}

12
sapl/templates/protocoloadm/comprovante.html

@ -70,6 +70,7 @@
<th>Autor</th> <th>Autor</th>
<td>{{ protocolo.autor }}</td> <td>{{ protocolo.autor }}</td>
</tr> </tr>
<<<<<<< HEAD
{% else %} {% else %}
<tr> <tr>
<th>Assunto</th> <th>Assunto</th>
@ -79,6 +80,17 @@
<th>Interessado</th> <th>Interessado</th>
<td>{{ protocolo.interessado }}</td> <td>{{ protocolo.interessado }}</td>
</tr> </tr>
=======
{% else %}
<tr>
<th>Assunto</th>
<td>{{ protocolo.assunto_ementa }}</td>
</tr>
<tr>
<th>Interessado</th>
<td>{{ protocolo.interessado }}</td>
</tr>
>>>>>>> 3.1.x
{% endif %} {% endif %}
<tr> <tr>
<th>Natureza</th> <th>Natureza</th>

19
sapl/templates/protocoloadm/documentoadministrativo_filter.html

@ -37,11 +37,26 @@
<tr> <tr>
<td> <td>
<strong><a href="{% url 'sapl.protocoloadm:documentoadministrativo_detail' d.id %}">{{d.tipo.sigla}} {{d.numero}}/{{d.ano}} - {{d.tipo}}</strong></a></br> <strong><a href="{% url 'sapl.protocoloadm:documentoadministrativo_detail' d.id %}">{{d.tipo.sigla}} {{d.numero}}/{{d.ano}} - {{d.tipo}}</strong></a></br>
<strong>Interessado:</strong>&nbsp;{{ d.interessado|default_if_none:"Não informado"}}</br> <strong>Interessado:</strong>&nbsp;{{ d.interessado|default_if_none:"Não informado"}}
<strong>Assunto:</strong>&nbsp;{{ d.assunto|safe }}</br> </br>
<strong>Assunto:</strong>&nbsp;{{ d.assunto|safe }}
</br>
{% if d.protocolo %} {% if d.protocolo %}
<strong>Protocolo:</strong>&nbsp;<a href="{% url 'sapl.protocoloadm:protocolo_mostrar' d.protocolo.id %}">{{ d.protocolo}}</a></br> <strong>Protocolo:</strong>&nbsp;<a href="{% url 'sapl.protocoloadm:protocolo_mostrar' d.protocolo.id %}">{{ d.protocolo}}</a></br>
{% endif %} {% endif %}
{% if d.tramitacaoadministrativo_set.last.unidade_tramitacao_destino %}
<strong>Localização Atual:</strong> &nbsp;{{d.tramitacaoadministrativo_set.last.unidade_tramitacao_destino}}
</br>
<strong>Status:</strong> {{d.tramitacaoadministrativo_set.last.status}}
</br>
{% endif %}
{% if d.documentoacessorioadministrativo_set.all.exists %}
<strong>Documentos Acessórios:</strong>
<a href="{% url 'sapl.protocoloadm:documentoacessorioadministrativo_list' d.id %}">
{{ d.documentoacessorioadministrativo_set.all.count }}
</a>
</br>
{% endif %}
{% if d.texto_integral %} {% if d.texto_integral %}
<strong><a href="{{ d.texto_integral.url }}">Texto Integral</a></strong></br> <strong><a href="{{ d.texto_integral.url }}">Texto Integral</a></strong></br>
{% endif %} {% endif %}

3
sapl/templates/protocoloadm/protocolo_mostrar.html

@ -38,11 +38,10 @@
<a href="{% url 'sapl.materia:materialegislativa_detail' materia.pk %}"> {{materia}} </a> <a href="{% url 'sapl.materia:materialegislativa_detail' materia.pk %}"> {{materia}} </a>
{% endif %} {% endif %}
</br> </br>
{% if not protocolo.anulado %}<a href="{% url 'sapl.materia:materia_create_simplificado' protocolo.pk %}" class="btn btn-primary">Criar Matéria</a>{% endif %} {% if not protocolo.anulado%}{% if not materia %}<a href="{% url 'sapl.materia:materia_create_simplificado' protocolo.pk %}" class="btn btn-primary">Criar Matéria</a>&nbsp;&nbsp;&nbsp;&nbsp;{% endif %}{% endif %}
{% endif %} {% endif %}
&nbsp;&nbsp;&nbsp;&nbsp;
<a target="popup" class="btn btn-primary" onclick="window.open('{% url 'sapl.protocoloadm:comprovante_protocolo' protocolo.pk%}','Comprovante','width=800, height=800')">Comprovante <a target="popup" class="btn btn-primary" onclick="window.open('{% url 'sapl.protocoloadm:comprovante_protocolo' protocolo.pk%}','Comprovante','width=800, height=800')">Comprovante
</a> </a>
{% endblock detail_content %} {% endblock detail_content %}

4
sapl/utils.py

@ -25,6 +25,7 @@ from django_filters.filterset import STRICTNESS
from easy_thumbnails import source_generators from easy_thumbnails import source_generators
from floppyforms import ClearableFileInput from floppyforms import ClearableFileInput
from reversion.admin import VersionAdmin from reversion.admin import VersionAdmin
from reversion_compare.admin import CompareVersionAdmin
from unipath.path import Path from unipath.path import Path
from sapl.crispy_layout_mixin import SaplFormLayout, form_actions, to_row 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] appname = appname[1] if appname[0] == 'sapl' else appname[0]
app = apps.get_app_config(appname) app = apps.get_app_config(appname)
for model in app.get_models(): for model in app.get_models():
class CustomModelAdmin(VersionAdmin): class CustomModelAdmin(CompareVersionAdmin):
list_display = [f.name for f in model._meta.fields list_display = [f.name for f in model._meta.fields
if f.name != 'id'] if f.name != 'id']
@ -355,6 +356,7 @@ TIPOS_TEXTO_PERMITIDOS = (
'application/msword', 'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/xml', 'application/xml',
'application/octet-stream',
'text/xml', 'text/xml',
'text/html', 'text/html',
) )

2
setup.py

@ -49,7 +49,7 @@ install_requires = [
] ]
setup( setup(
name='interlegis-sapl', name='interlegis-sapl',
version='3.1.92', version='3.1.102',
packages=find_packages(), packages=find_packages(),
include_package_data=True, include_package_data=True,
license='GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007', license='GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007',

2
start.sh

@ -49,7 +49,7 @@ create_env
# manage.py migrate --noinput nao funcionava # manage.py migrate --noinput nao funcionava
yes yes | python3 manage.py migrate yes yes | python3 manage.py migrate
#python3 manage.py collectstatic --no-input #python3 manage.py collectstatic --no-input
python3 manage.py rebuild_index --noinput & # python3 manage.py rebuild_index --noinput &
echo "Criando usuário admin..." echo "Criando usuário admin..."

Loading…
Cancel
Save