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. 134
      sapl/legacy/scripts/exporta_zope/exporta_zope.py
  15. 16
      sapl/materia/forms.py
  16. 195
      sapl/materia/tests/test_materia_form.py
  17. 62
      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:
- "5432:5432"
sapl:
image: interlegis/sapl:3.1.92
image: interlegis/sapl:3.1.102
restart: always
environment:
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 (*)
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 \

1
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

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():
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')

7
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'] = []

5
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]

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

48
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

3
sapl/legacy/migracao.py

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

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

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

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

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

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

62
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 <br>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 <br>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

8
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'))}

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

28
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,
})

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

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

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

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

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>' + \
dic['natureza']
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:
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(
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)

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

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

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

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

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

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

3
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']

1
sapl/settings.py

@ -87,6 +87,7 @@ INSTALLED_APPS = (
'sass_processor',
'rest_framework',
'reversion',
'reversion_compare',
'whoosh',
'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>
</div>
<br /><br /><br /><br />
<h1>Autorias</h1>
<br/><br/>
{% for r in relatorio %}
<h3>{{r.autor}}</h3><br/>
<table class="table table-bordered table-hover">
@ -35,7 +36,30 @@
<br/>
{% endfor %}
<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">
<thead class="thead-default" >
<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">
<th>Matéria</th>
<th>Ementa</th>
<th>Autor(es)</th>
<th>Autor</th>
<th>Coautor(es)</th>
</tr>
</thead>
<tbody>
@ -48,7 +49,16 @@
<td>{{materia.ementa}}</td>
<td>
{% for autor in materia.autoria_set.all %}
{% if autor.primeiro_autor %}
{{autor.autor}}<br />
{% endif %}
{% endfor %}
</td>
<td>
{% for autor in materia.autoria_set.all %}
{% if not autor.primeiro_autor %}
{{autor.autor}}<br />
{% endif %}
{% endfor %}
</td>
</tr>

2
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' %}:

7
sapl/templates/materia/materialegislativa_filter.html

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

2
sapl/templates/materia/relatoria_form.html

@ -6,7 +6,7 @@
{% block base_content %}
{% if form.comissao.value == 0 %}
<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>
{% else %}
{% crispy form %}

2
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

14
sapl/templates/norma/normajuridica_filter.html

@ -4,9 +4,11 @@
{% block actions %}
<div class="actions btn-group pull-right" role="group">
<!--
<a href="{% url 'sapl.base:haystack_search' %}" class="btn btn-default">
Pesquisa Textual
</a>
-->
{% if perms.norma.add_normajuridica %}
@ -84,6 +86,18 @@
<h2>Nenhuma norma encontrada com essas especificações</h2>
{% 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 %}
{% block table_content %}

9
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+/, ''));
}
});
</script>
{% endblock %}

12
sapl/templates/protocoloadm/comprovante.html

@ -70,6 +70,7 @@
<th>Autor</th>
<td>{{ protocolo.autor }}</td>
</tr>
<<<<<<< HEAD
{% else %}
<tr>
<th>Assunto</th>
@ -79,6 +80,17 @@
<th>Interessado</th>
<td>{{ protocolo.interessado }}</td>
</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 %}
<tr>
<th>Natureza</th>

19
sapl/templates/protocoloadm/documentoadministrativo_filter.html

@ -37,11 +37,26 @@
<tr>
<td>
<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>Assunto:</strong>&nbsp;{{ d.assunto|safe }}</br>
<strong>Interessado:</strong>&nbsp;{{ d.interessado|default_if_none:"Não informado"}}
</br>
<strong>Assunto:</strong>&nbsp;{{ d.assunto|safe }}
</br>
{% if d.protocolo %}
<strong>Protocolo:</strong>&nbsp;<a href="{% url 'sapl.protocoloadm:protocolo_mostrar' d.protocolo.id %}">{{ d.protocolo}}</a></br>
{% 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 %}
<strong><a href="{{ d.texto_integral.url }}">Texto Integral</a></strong></br>
{% endif %}

3
sapl/templates/protocoloadm/protocolo_mostrar.html

@ -38,11 +38,10 @@
<a href="{% url 'sapl.materia:materialegislativa_detail' materia.pk %}"> {{materia}} </a>
{% endif %}
</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 %}
&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>
{% endblock detail_content %}

4
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',
)

2
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',

2
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..."

Loading…
Cancel
Save