Browse Source

Merge branch '3.1.x' of https://github.com/interlegis/sapl into 1968-resumo-sessao

pull/2017/head
Mariana Mendes 8 years ago
parent
commit
8a5d21c893
  1. 3
      Dockerfile
  2. 2
      docker-compose.yml
  3. 4
      gunicorn_start.sh
  4. 2
      sapl/base/forms.py
  5. 31
      sapl/base/templatetags/common_tags.py
  6. 7
      sapl/comissoes/legacy.yaml
  7. 25
      sapl/comissoes/migrations/0015_auto_20180613_2023.py
  8. 21
      sapl/comissoes/migrations/0016_auto_20180613_2121.py
  9. 9
      sapl/comissoes/models.py
  10. 26
      sapl/comissoes/views.py
  11. 2
      sapl/legacy/management/commands/migracao_25_31.py
  12. 6
      sapl/legacy/migracao.py
  13. 70
      sapl/legacy/migracao_dados.py
  14. 36
      sapl/legacy/migracao_documentos.py
  15. 11
      sapl/legacy/migracao_usuarios.py
  16. 14
      sapl/legacy/models.py
  17. 85
      sapl/legacy/scripts/exporta_zope/exporta_zope.py
  18. 81
      sapl/materia/forms.py
  19. 5
      sapl/materia/urls.py
  20. 45
      sapl/materia/views.py
  21. 5
      sapl/painel/views.py
  22. 12
      sapl/parlamentares/views.py
  23. 3
      sapl/protocoloadm/forms.py
  24. 12
      sapl/sessao/legacy.yaml
  25. 25
      sapl/sessao/migrations/0022_auto_20180618_1625.py
  26. 2
      sapl/sessao/models.py
  27. 7
      sapl/sessao/views.py
  28. 1
      sapl/templates/compilacao/textoarticulado_menu_config.html
  29. 2
      sapl/templates/crud/list.html
  30. 7
      sapl/templates/materia/em_lote/excluir_tramitacao.html
  31. 2
      sapl/templates/materia/em_lote/tramitacao.html
  32. 29
      sapl/templates/materia/prop_pendentes_list.html
  33. 3
      sapl/templates/navbar.yaml
  34. 2
      sapl/templates/painel/index.html
  35. 9
      sapl/templates/protocoloadm/comprovante.html
  36. 2
      setup.py

3
Dockerfile

@ -50,7 +50,8 @@ RUN rm -rf /var/interlegis/sapl/sapl/.env && \
RUN chmod +x /var/interlegis/sapl/start.sh && \
ln -sf /dev/stdout /var/log/nginx/access.log && \
ln -sf /dev/stderr /var/log/nginx/error.log
ln -sf /dev/stderr /var/log/nginx/error.log && \
mkdir /var/log/sapl/
VOLUME ["/var/interlegis/sapl/data", "/var/interlegis/sapl/media"]

2
docker-compose.yml

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

4
gunicorn_start.sh

@ -44,6 +44,6 @@ exec gunicorn ${DJANGO_WSGI_MODULE}:application \
--workers $NUM_WORKERS \
--max-requests $MAX_REQUESTS \
--user $USER \
--access-logfile - \
--error-logfile - \
--access-logfile /var/log/sapl/access.log \
--error-logfile /var/log/sapl/error.log \
--bind=unix:$SOCKFILE

2
sapl/base/forms.py

@ -529,7 +529,7 @@ class RelatorioAtasFilterSet(django_filters.FilterSet):
def qs(self):
parent = super(RelatorioAtasFilterSet, self).qs
return parent.distinct().prefetch_related('tipo').exclude(
upload_ata='').order_by('-legislatura', 'tipo', 'numero')
upload_ata='').order_by('-data_inicio', 'tipo', 'numero')
def __init__(self, *args, **kwargs):
super(RelatorioAtasFilterSet, self).__init__(

31
sapl/base/templatetags/common_tags.py

@ -3,7 +3,7 @@ from django import template
from django.template.defaultfilters import stringfilter
from sapl.base.models import AppConfig
from sapl.materia.models import DocumentoAcessorio, MateriaLegislativa
from sapl.materia.models import DocumentoAcessorio, MateriaLegislativa, Proposicao
from sapl.norma.models import NormaJuridica
from sapl.parlamentares.models import Filiacao
from sapl.utils import filiacao_data
@ -11,6 +11,11 @@ from sapl.utils import filiacao_data
register = template.Library()
@register.simple_tag
def define(arg):
return arg
@register.simple_tag
def field_verbose_name(instance, field_name):
return instance._meta.get_field(field_name).verbose_name
@ -34,6 +39,30 @@ def model_verbose_name_plural(class_name):
return model._meta.verbose_name_plural
@register.filter
def split(value, arg):
return value.split(arg)
@register.filter
def sort_by_keys(value, key):
transformed = []
id_props = [x.id for x in value]
qs = Proposicao.objects.filter(pk__in=id_props)
key_descricao = {'1': 'data_envio',
'-1': '-data_envio',
'2': 'tipo',
'-2': '-tipo',
'3': 'descricao',
'-3': '-descricao',
'4': 'autor',
'-4': '-autor'
}
transformed = qs.order_by(key_descricao[key])
return transformed
@register.filter
def lookup(d, key):
return d[key] if key in d else []

7
sapl/comissoes/legacy.yaml

@ -42,3 +42,10 @@ Participacao (ComposicaoComissao):
observacao: obs_composicao
parlamentar: cod_parlamentar
titular: ind_titular
Reuniao (ReuniaoComissao):
comissao: cod_comissao
numero: num_reuniao
data: dat_inicio_reuniao
observacao: txt_observacao

25
sapl/comissoes/migrations/0015_auto_20180613_2023.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.13 on 2018-06-13 23:23
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('comissoes', '0014_auto_20180503_1055'),
]
operations = [
migrations.AlterField(
model_name='reuniao',
name='hora_fim',
field=models.TimeField(null=True, verbose_name='Horário de Término (hh:mm)'),
),
migrations.AlterField(
model_name='reuniao',
name='hora_inicio',
field=models.TimeField(null=True, verbose_name='Horário de Início (hh:mm)'),
),
]

21
sapl/comissoes/migrations/0016_auto_20180613_2121.py

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.13 on 2018-06-14 00:21
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('comissoes', '0015_auto_20180613_2023'),
]
operations = [
migrations.AlterField(
model_name='reuniao',
name='periodo',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='comissoes.Periodo', verbose_name='Periodo da Composicão da Comissão'),
),
]

9
sapl/comissoes/models.py

@ -184,13 +184,16 @@ class Participacao(models.Model): # ComposicaoComissao
def get_comissao_media_path(instance, subpath, filename):
return './sapl/comissao/%s/%s/%s' % (instance.numero, subpath, filename)
def pauta_upload_path(instance, filename):
return texto_upload_path(instance, filename, subpath='pauta', pk_first=True)
def ata_upload_path(instance, filename):
return texto_upload_path(instance, filename, subpath='ata', pk_first=True)
def anexo_upload_path(instance, filename):
return texto_upload_path(instance, filename, subpath='anexo', pk_first=True)
@ -198,6 +201,7 @@ def anexo_upload_path(instance, filename):
class Reuniao(models.Model):
periodo = models. ForeignKey(
Periodo,
null=True,
on_delete=models.PROTECT,
verbose_name=_('Periodo da Composicão da Comissão'))
comissao = models.ForeignKey(
@ -211,8 +215,10 @@ class Reuniao(models.Model):
max_length=150, blank=True, verbose_name=_('Tema da Reunião'))
data = models.DateField(verbose_name=_('Data'))
hora_inicio = models.TimeField(
null=True,
verbose_name=_('Horário de Início (hh:mm)'))
hora_fim = models.TimeField(
null=True,
verbose_name=_('Horário de Término (hh:mm)'))
local_reuniao = models.CharField(
max_length=100, blank=True, verbose_name=_('Local da Reunião'))
@ -292,7 +298,8 @@ class DocumentoAcessorio(models.Model):
on_delete=models.PROTECT)
nome = models.CharField(max_length=50, verbose_name=_('Nome'))
data = models.DateField(blank=True, null=True, default=None, verbose_name=_('Data'))
data = models.DateField(blank=True, null=True,
default=None, verbose_name=_('Data'))
autor = models.CharField(
max_length=100, verbose_name=_('Autor'))
ementa = models.TextField(blank=True, verbose_name=_('Ementa'))

26
sapl/comissoes/views.py

@ -8,20 +8,20 @@ from django.views.generic.base import RedirectView
from django.views.generic.detail import DetailView
from django.views.generic.edit import FormMixin
from sapl.base.models import AppConfig as AppsAppConfig
from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud,
CrudAux, MasterDetailCrud,
from sapl.comissoes.apps import AppConfig
from sapl.comissoes.forms import (ComissaoForm, ComposicaoForm,
DocumentoAcessorioCreateForm,
DocumentoAcessorioEditForm,
ParticipacaoCreateForm, ParticipacaoEditForm,
PeriodoForm, ReuniaoForm)
from sapl.crud.base import (RP_DETAIL, RP_LIST, Crud, CrudAux,
MasterDetailCrud,
PermissionRequiredForAppCrudMixin)
from sapl.comissoes.forms import (ComissaoForm, ComposicaoForm, DocumentoAcessorioCreateForm,
DocumentoAcessorioEditForm, ParticipacaoCreateForm,
ParticipacaoEditForm, ReuniaoForm, PeriodoForm)
from sapl.materia.models import MateriaLegislativa, Tramitacao
from .models import (CargoComissao, Comissao, Composicao, DocumentoAcessorio,
Participacao, Periodo, TipoComissao, Reuniao)
from sapl.comissoes.apps import AppConfig
Participacao, Periodo, Reuniao, TipoComissao)
def pegar_url_composicao(pk):
@ -30,6 +30,7 @@ def pegar_url_composicao(pk):
url = reverse('sapl.comissoes:composicao_detail', kwargs={'pk': comp_pk})
return url
def pegar_url_reuniao(pk):
documentoacessorio = DocumentoAcessorio.objects.get(id=pk)
r_pk = documentoacessorio.reuniao.pk
@ -42,6 +43,7 @@ TipoComissaoCrud = CrudAux.build(
TipoComissao, 'tipo_comissao', list_field_names=[
'sigla', 'nome', 'natureza', 'dispositivo_regimental'])
class PeriodoComposicaoCrud(CrudAux):
model = Periodo
@ -77,6 +79,7 @@ class ParticipacaoCrud(MasterDetailCrud):
form_class = ParticipacaoEditForm
class DeleteView(MasterDetailCrud.DeleteView):
def get_success_url(self):
composicao_comissao_pk = self.object.composicao.comissao.pk
composicao_pk = self.object.composicao.pk
@ -98,7 +101,6 @@ class ComposicaoCrud(MasterDetailCrud):
comissao = Comissao.objects.get(id=self.kwargs['pk'])
return {'comissao': comissao}
class ListView(MasterDetailCrud.ListView):
template_name = "comissoes/composicao_list.html"
paginate_by = None
@ -180,6 +182,7 @@ class MateriasTramitacaoListView(ListView):
context['object'] = Comissao.objects.get(id=self.kwargs['pk'])
return context
class ReuniaoCrud(MasterDetailCrud):
model = Reuniao
parent_field = 'comissao'
@ -187,7 +190,7 @@ class ReuniaoCrud(MasterDetailCrud):
public = [RP_LIST, RP_DETAIL, ]
class BaseMixin(MasterDetailCrud.BaseMixin):
list_field_names = [ 'nome', 'tema', 'data']
list_field_names = ['data', 'nome', 'tema']
class ListView(MasterDetailCrud.ListView):
paginate_by = 10
@ -256,6 +259,7 @@ class DocumentoAcessorioCrud(MasterDetailCrud):
form_class = DocumentoAcessorioEditForm
class DeleteView(MasterDetailCrud.DeleteView):
def delete(self, *args, **kwargs):
obj = self.get_object()
obj.delete()

2
sapl/legacy/management/commands/migracao_25_31.py

@ -1,4 +1,3 @@
from django.core import management
from django.core.management.base import BaseCommand
from sapl.legacy.migracao import migrar
@ -9,5 +8,4 @@ class Command(BaseCommand):
help = 'Migração de dados do SAPL 2.5 para o SAPL 3.1'
def handle(self, *args, **options):
management.call_command('migrate')
migrar(interativo=False)

6
sapl/legacy/migracao.py

@ -2,6 +2,7 @@ import subprocess
from getpass import getpass
import requests
from django.core import management
from unipath import Path
from sapl.legacy.migracao_dados import (REPO, TAG_MARCO, gravar_marco, info,
@ -24,11 +25,12 @@ def migrar(interativo=False):
assert TAG_ZOPE in REPO.tags, adornar_msg(
'Antes de migrar '
'é necessário fazer a exportação de documentos do zope')
migrar_dados(interativo=interativo)
management.call_command('migrate')
migrar_dados()
migrar_usuarios(REPO.working_dir)
migrar_documentos(REPO)
gravar_marco()
compactar_media()
# compactar_media()
def compactar_media():

70
sapl/legacy/migracao_dados.py

@ -29,7 +29,7 @@ from unipath import Path
from sapl.base.models import AppConfig as AppConf
from sapl.base.models import Autor, TipoAutor, cria_models_tipo_autor
from sapl.comissoes.models import Comissao, Composicao, Participacao
from sapl.comissoes.models import Comissao, Composicao, Participacao, Reuniao
from sapl.legacy import scripts
from sapl.legacy.models import NormaJuridica as OldNormaJuridica
from sapl.legacy.models import TipoNumeracaoProtocolo
@ -58,6 +58,8 @@ from .timezonesbrasil import get_timezone
appconfs = [apps.get_app_config(n) for n in [
'parlamentares',
'comissoes',
# base precisa vir depois dos apps parlamentares e comissoes
# pois Autor os referencia
'base',
'materia',
'norma',
@ -85,38 +87,34 @@ for a1, s1 in name_sets:
# RENAMES ###################################################################
MODEL_RENAME_PATTERN = re.compile('(.+) \((.+)\)')
MODEL_RENAME_INCLUDE_PATTERN = re.compile('<(.+)>')
def get_renames():
field_renames = {}
model_renames = {}
includes = {}
for app in appconfs:
app_rename_data = yaml.load(
pkg_resources.resource_string(app.module.__name__, 'legacy.yaml'))
for model_name, renames in app_rename_data.items():
# armazena ou substitui includes
if MODEL_RENAME_INCLUDE_PATTERN.match(model_name):
includes[model_name] = renames
continue
elif isinstance(renames, str):
renames = includes[renames]
# detecta mudança de nome
match = MODEL_RENAME_PATTERN.match(model_name)
if match:
model_name, old_name = match.groups()
else:
old_name = None
model = getattr(app.models_module, model_name)
model = app.get_model(model_name)
if old_name:
model_renames[model] = old_name
field_renames[model] = renames
# collect renames from parent classes
for model, renames in field_renames.items():
if any(parent in field_renames for parent in model.__mro__[1:]):
renames = {}
for parent in reversed(model.__mro__):
if parent in field_renames:
renames.update(field_renames[parent])
field_renames[model] = renames
# remove abstract classes
field_renames = {m: r for m, r in field_renames.items()
if not m._meta.abstract}
return field_renames, model_renames
@ -212,6 +210,10 @@ class ForeignKeyFaltando(ObjectDoesNotExist):
'Uma FK aponta para um registro inexistente'
def __init__(self, field, valor, old):
if (field.related_model.__name__ == 'Comissao'
and old.__class__.__name__ == 'ReuniaoComissao'
and valor == 1):
__import__('pdb').set_trace()
self.field = field
self.valor = valor
self.old = old
@ -632,6 +634,7 @@ parlamentar | cod_nivel_instrucao = NULL | cod_nivel_instrucao = 0
parlamentar | tip_situacao_militar = NULL | tip_situacao_militar = 0
mandato | tip_afastamento = NULL | tip_afastamento = 0
relatoria | tip_fim_relatoria = NULL | tip_fim_relatoria = 0
sessao_plenaria_presenca | dat_sessao = NULL | dat_sessao = 0
'''.strip().splitlines()
for spec in update_specs:
@ -794,7 +797,10 @@ def roda_comando_shell(cmd):
assert res == 0, 'O comando falhou: {}'.format(cmd)
def migrar_dados(interativo=True):
def migrar_dados():
try:
ocorrencias.clear()
ocorrencias.default_factory = list
# restaura dump
arq_dump = Path(DIR_DADOS_MIGRACAO.child(
@ -813,17 +819,6 @@ def migrar_dados(interativo=True):
uniformiza_banco()
# excluindo database antigo.
if interativo:
info('Todos os dados do banco serão excluidos. '
'Recomendamos que faça backup do banco sapl '
'antes de continuar.')
info('Deseja continuar? [s/n]')
resposta = input()
if resposta.lower() in ['s', 'sim', 'y', 'yes']:
pass
else:
info('Migração cancelada.')
return 0
info('Excluindo entradas antigas do banco destino.')
call([PROJECT_DIR.child('manage.py'), 'flush',
'--database=default', '--no-input'], stdout=PIPE)
@ -834,14 +829,13 @@ def migrar_dados(interativo=True):
fill_vinculo_norma_juridica()
fill_dados_basicos()
info('Começando migração: ...')
try:
ocorrencias.clear()
migrar_todos_os_models()
except Exception as e:
ocorrencias['traceback'] = str(traceback.format_exc())
raise e
finally:
# grava ocorrências
# congela e grava ocorrências
ocorrencias.default_factory = None
arq_ocorrencias = Path(REPO.working_dir, 'ocorrencias.yaml')
with open(arq_ocorrencias, 'w') as arq:
pyaml.dump(ocorrencias, arq, vspacing=1)
@ -863,6 +857,10 @@ def move_para_depois_de(lista, movido, referencias):
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')):
models.remove(Reuniao)
# Devido à referência TipoProposicao.tipo_conteudo_related
# a migração de TipoProposicao precisa ser feita
# após TipoMateriaLegislativa e TipoDocumento
@ -1247,6 +1245,17 @@ def adjust_tiporesultadovotacao(new, old):
{'pk': new.pk, 'nome': new.nome})
def str_to_time(fonte):
if not fonte.strip():
return None
tempo = datetime.datetime.strptime(fonte, '%H:%M')
return tempo.time() if tempo else None
def adjust_reuniao_comissao(new, old):
new.hora_inicio = str_to_time(old.hr_inicio_reuniao)
def remove_style(conteudo):
if 'style' not in conteudo:
return conteudo # atalho que acelera muito os casos sem style
@ -1284,6 +1293,7 @@ AJUSTE_ANTES_SALVAR = {
Tramitacao: adjust_tramitacao,
TipoResultadoVotacao: adjust_tiporesultadovotacao,
ExpedienteSessao: adjust_expediente_sessao,
Reuniao: adjust_reuniao_comissao,
}
AJUSTE_DEPOIS_SALVAR = {

36
sapl/legacy/migracao_documentos.py

@ -8,6 +8,7 @@ from django.db import transaction
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.materia.models import (DocumentoAcessorio, MateriaLegislativa,
Proposicao)
@ -21,7 +22,6 @@ from sapl.sessao.models import SessaoPlenaria
DOCS = {
CasaLegislativa: [('logotipo', 'props_sapl/{}.*')],
Parlamentar: [('fotografia', 'parlamentar/fotos/{}_foto_parlamentar')],
MateriaLegislativa: [('texto_original', 'materia/{}_texto_integral')],
DocumentoAcessorio: [('arquivo', 'materia/{}')],
@ -35,24 +35,34 @@ DOCS = {
DocumentoAcessorioAdministrativo: [('arquivo', 'administrativo/{}')],
}
# acrescenta reuniões (que só existem no sapl 3.0)
if 'reuniao_comissao' in set(exec_legado('show tables')):
DOCS[Reuniao] = [('upload_pauta', 'reuniao_comissao/{}_pauta'),
('upload_ata', 'reuniao_comissao/{}_ata')],
DOCS = {model: [(campo, join('sapl_documentos', origem))
for campo, origem, in campos]
for campo, origem in campos]
for model, campos in DOCS.items()}
def mover_documento(repo, origem, destino):
def mover_documento(repo, origem, destino, ignora_origem_ausente=False):
origem, destino = [join(repo.working_dir, c) if not os.path.isabs(c) else c
for c in (origem, destino)]
if ignora_origem_ausente and not os.path.exists(origem):
print('Origem ignorada ao mover documento: {}'.format(origem))
return
os.makedirs(os.path.dirname(destino), exist_ok=True)
repo.git.mv(origem, destino)
def migrar_logotipo(repo, casa, propriedades):
print('.... Migrando logotipo da casa ....')
[(campo, origem)] = DOCS[CasaLegislativa]
campo, origem = 'logotipo', 'sapl_documentos/props_sapl/{}.*'
# a extensão do logo pode ter sido ajustada pelo tipo real do arquivo
nome_nas_propriedades = os.path.splitext(propriedades['id_logo'])[0]
arquivos = glob(join(repo.working_dir, origem.format(nome_nas_propriedades)))
arquivos = glob(
join(repo.working_dir, origem.format(nome_nas_propriedades)))
if arquivos:
assert len(arquivos) == 1, 'Há mais de um logotipo para a casa'
[logo] = arquivos
@ -95,10 +105,10 @@ def migrar_propriedades_da_casa(repo):
migrar_logotipo(repo, casa, propriedades)
casa.save()
repo.git.rm(caminho)
def migrar_docs_por_ids(repo, model):
for campo, base_origem in DOCS[model]:
print('#### Migrando {} de {} ####'.format(campo, model.__name__))
@ -145,7 +155,8 @@ def migrar_documentos(repo):
# Isto significa que para rodar novamente esta função é preciso
# restaurar o repo ao estado anterior
mover_documento(repo, 'XSLT', 'sapl/public/XSLT')
mover_documento(repo, 'XSLT', 'sapl/public/XSLT',
ignora_origem_ausente=True)
migrar_propriedades_da_casa(repo)
@ -153,16 +164,7 @@ def migrar_documentos(repo):
# (necessário para o cropping de imagem)
repo.git.execute('git annex get sapl_documentos/parlamentar'.split())
for model in [
Parlamentar,
MateriaLegislativa,
DocumentoAcessorio,
NormaJuridica,
SessaoPlenaria,
Proposicao,
DocumentoAdministrativo,
DocumentoAcessorioAdministrativo,
]:
for model in DOCS:
migrar_docs_por_ids(repo, model)
sobrando = [join(dir, file)

11
sapl/legacy/migracao_usuarios.py

@ -95,16 +95,7 @@ def migrar_usuarios(dir_repo):
usuario.groups.add(PERFIL_LEGADO_PARA_NOVO[perfil])
usuario.save()
# restringe e configura administradores
if len(admins) > 2:
admins = (
# ususários com admin no nome
[u for u in admins if 'admin' in u.username]
# senão, o usuário saploper, apenas
or [u for u in admins if 'saploper' == u.username]
# senão, simplesmente até os dois primeiros da lista
or admins[:2]
)
# configura administradores
for admin in admins:
admin.is_superuser = True
admin.save()

14
sapl/legacy/models.py

@ -779,6 +779,20 @@ class Relatoria(models.Model):
db_table = 'relatoria'
class ReuniaoComissao(models.Model):
cod_reuniao = models.AutoField(primary_key=True)
cod_comissao = models.IntegerField()
num_reuniao = models.IntegerField()
dat_inicio_reuniao = models.DateField()
hr_inicio_reuniao = models.CharField(max_length=5, blank=True, null=True)
txt_observacao = models.TextField(blank=True, null=True)
ind_excluido = models.IntegerField()
class Meta:
managed = False
db_table = 'reuniao_comissao'
class SessaoLegislativa(models.Model):
cod_sessao_leg = models.AutoField(primary_key=True)
num_legislatura = models.IntegerField()

85
sapl/legacy/scripts/exporta_zope/exporta_zope.py

@ -21,6 +21,7 @@ import ZODB.DB
import ZODB.FileStorage
from unipath import Path
from ZODB.broken import Broken
from ZODB.POSException import POSKeyError
from variaveis_comuns import DIR_DADOS_MIGRACAO, TAG_ZOPE
@ -95,6 +96,9 @@ def guess_extension(fullname, buffer):
return '.DESCONHECIDO.{}'.format(mime.replace('/', '__'))
CONTEUDO_ARQUIVO_CORROMPIDO = 'ARQUIVO CORROMPIDO'
def get_conteudo_file(doc):
# A partir daqui usamos dict.pop('...') nos __Broken_state__
# para contornar um "vazamento" de memória que ocorre
@ -105,7 +109,7 @@ def get_conteudo_file(doc):
#
# Essa medida descarta quase todos os dados retornados
# e só funciona na primeira passagem
try:
pdata = br(doc.pop('data'))
if isinstance(pdata, str):
# Retrocedemos se pdata ja eh uma str (necessario em Images)
@ -118,12 +122,17 @@ def get_conteudo_file(doc):
pdata = br(pdata.pop('next', None))
return output.getvalue()
except POSKeyError:
return CONTEUDO_ARQUIVO_CORROMPIDO
def dump_file(doc, path, salvar, get_conteudo=get_conteudo_file):
name = doc['__name__']
fullname = os.path.join(path, name)
conteudo = get_conteudo(doc)
if conteudo == CONTEUDO_ARQUIVO_CORROMPIDO:
fullname = fullname + '_CORROMPIDO'
print('ATENÇÃO: arquivo corrompido: {}'.format(fullname))
if conteudo:
# pula arquivos vazios
salvar(fullname, conteudo)
@ -137,7 +146,15 @@ def get_conteudo_dtml_method(doc):
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))
except POSKeyError:
print('#' * 80)
print('#' * 80)
print('ATENÇÃO: DIRETÓRIO corrompido: {}'.format(id))
print('#' * 80)
print('#' * 80)
else:
yield id, obj, meta_type
@ -186,6 +203,9 @@ def dump_folder(folder, path, salvar, enum=enumerate_folder):
if not os.path.exists(path):
os.makedirs(path)
for id, obj, meta_type in enum(folder):
# pula pastas *_old (presentes em várias bases)
if id.endswith('_old') and meta_type in ['Folder', 'BTreeFolder2']:
continue
dump = DUMP_FUNCTIONS.get(meta_type, '?')
if dump == '?':
nao_identificados[meta_type].append(path + '/' + id)
@ -271,39 +291,68 @@ def get_app(data_fs_path):
def find_sapl(app):
for obj in app['_objects']:
id, meta_type = obj['id'], obj['meta_type']
ids_meta_types = [(obj['id'], obj['meta_type']) for obj in app['_objects']]
# estar ordenado é muito importante para que a busca dê prioridade
# a um id "cm_zzz" antes do id "sapl"
for id, meta_type in sorted(ids_meta_types):
if id.startswith('cm_') and meta_type == 'Folder':
cm_zzz = br(app[id])
sapl = br(cm_zzz.get('sapl', None))
if sapl and 'sapl_documentos' in sapl and 'acl_users' in sapl:
return find_sapl(cm_zzz)
elif id == 'sapl' and meta_type in ['SAPL', 'Folder']:
sapl = br(app['sapl'])
return sapl
def dump_propriedades(docs, path, salvar, encoding='iso-8859-1'):
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
def dump_propriedades(docs, path, salvar):
props_sapl = br(docs['props_sapl'])
ids = [p['id'] for p in props_sapl['_properties']]
props = {id: props_sapl[id] for id in ids}
props = {id: p.decode(encoding) if isinstance(p, str) else p
for id, p in props.items()}
props = {id: autodecode(p) for id, p in props.items()}
save_as_yaml(path, 'sapl_documentos/propriedades.yaml', props, salvar)
def dump_usuarios(sapl, path, salvar):
users = br(br(sapl['acl_users'])['data'])
users = {k: br(v) for k, v in users['data'].items()}
users = {autodecode(k): br(v) for k, v in users['data'].items()}
for dados in users.values():
dados['name'] = autodecode(dados['name'])
save_as_yaml(path, 'usuarios.yaml', users, salvar)
def _dump_sapl(data_fs_path, destino, salvar):
def _dump_sapl(data_fs_path, documentos_fs_path, destino, salvar):
assert Path(data_fs_path).exists()
assert Path(documentos_fs_path).exists()
app, close_db = get_app(data_fs_path)
try:
sapl = find_sapl(app)
# extrai folhas XSLT
dump_folder(br(sapl['XSLT']), destino, salvar)
# extrai usuários com suas senhas e perfis
dump_usuarios(sapl, destino, salvar)
finally:
close_db()
app, close_db = get_app(documentos_fs_path)
try:
sapl = find_sapl(app)
# extrai folhas XSLT
if 'XSLT' in sapl:
dump_folder(br(sapl['XSLT']), destino, salvar)
# extrai documentos
docs = br(sapl['sapl_documentos'])
@ -355,9 +404,15 @@ def build_salvar(repo):
def dump_sapl(sigla):
sigla = sigla[-3:] # ignora prefixo (por ex. 'sapl_cm_')
data_fs_path = DIR_DADOS_MIGRACAO.child('datafs',
'Data_cm_{}.fs'.format(sigla))
data_fs_path, documentos_fs_path = [
DIR_DADOS_MIGRACAO.child(
'datafs', '{}_cm_{}.fs'.format(prefixo, sigla))
for prefixo in ('Data', 'DocumentosSapl')]
assert data_fs_path.exists(), 'Origem não existe: {}'.format(data_fs_path)
if not documentos_fs_path.exists():
documentos_fs_path = data_fs_path
nome_banco_legado = 'sapl_cm_{}'.format(sigla)
destino = DIR_DADOS_MIGRACAO.child('repos', nome_banco_legado)
destino.mkdir(parents=True)
@ -372,7 +427,7 @@ def dump_sapl(sigla):
salvar = build_salvar(repo)
try:
finalizado = False
_dump_sapl(data_fs_path, destino, salvar)
_dump_sapl(data_fs_path, documentos_fs_path, destino, salvar)
finalizado = True
finally:
# grava mundaças

81
sapl/materia/forms.py

@ -32,7 +32,8 @@ from sapl.crispy_layout_mixin import (SaplFormLayout, form_actions, to_column,
to_row)
from sapl.materia.models import (AssuntoMateria, Autoria, MateriaAssunto,
MateriaLegislativa, Orgao, RegimeTramitacao,
TipoDocumento, TipoProposicao)
TipoDocumento, TipoProposicao, StatusTramitacao,
UnidadeTramitacao)
from sapl.norma.models import (LegislacaoCitada, NormaJuridica,
TipoNormaJuridica)
from sapl.parlamentares.models import Legislatura
@ -1817,7 +1818,19 @@ class ConfirmarProposicaoForm(ProposicaoForm):
if numeracao == 'A':
nm = Protocolo.objects.filter(
ano=timezone.now().year).aggregate(Max('numero'))
elif numeracao == 'U':
elif numeracao == 'L':
legislatura = Legislatura.objects.filter(
data_inicio__year__lte=timezone.now().year,
data_fim__year__gte=timezone.now().year).first()
data_inicio = legislatura.data_inicio
data_fim = legislatura.data_fim
nm = MateriaLegislativa.objects.filter(
data_apresentacao__gte=data_inicio,
data_apresentacao__lte=data_fim,
tipo=tipo).aggregate(Max('numero'))
else:
# numeracao == 'U' ou não informada
nm = Protocolo.objects.all().aggregate(Max('numero'))
protocolo = Protocolo()
@ -2032,3 +2045,67 @@ class FichaSelecionaForm(forms.Form):
form_actions(label='Gerar Impresso')
)
)
class ExcluirTramitacaoEmLote(forms.Form):
data_tramitacao = forms.DateField(required=True,
label=_('Data da Tramitação'))
unidade_tramitacao_local = forms.ModelChoiceField(label=_('Unidade Local'),
required=True,
queryset=UnidadeTramitacao.objects.all(),
empty_label='------')
unidade_tramitacao_destino = forms.ModelChoiceField(label=_('Unidade Destino'),
required=True,
queryset=UnidadeTramitacao.objects.all(),
empty_label='------')
status = forms.ModelChoiceField(label=_('Status'),
required=True,
queryset=StatusTramitacao.objects.all(),
empty_label='------')
def clean(self):
super(ExcluirTramitacaoEmLote, self).clean()
cleaned_data = self.cleaned_data
if not self.is_valid():
return cleaned_data
data_tramitacao = cleaned_data['data_tramitacao']
unidade_tramitacao_local = cleaned_data['unidade_tramitacao_local']
unidade_tramitacao_destino = cleaned_data['unidade_tramitacao_destino']
status = cleaned_data['status']
tramitacao_set = Tramitacao.objects.filter(data_tramitacao=data_tramitacao,
unidade_tramitacao_local=unidade_tramitacao_local,
unidade_tramitacao_destino=unidade_tramitacao_destino,
status=status)
if not tramitacao_set.exists():
raise forms.ValidationError(
_("Não existem tramitações com os dados informados."))
return cleaned_data
def __init__(self, *args, **kwargs):
super(ExcluirTramitacaoEmLote, self).__init__(*args, **kwargs)
row1 = to_row(
[('data_tramitacao', 6),
('status', 6),])
row2 = to_row(
[('unidade_tramitacao_local', 6),
('unidade_tramitacao_destino', 6)])
self.helper = FormHelper()
self.helper.layout = Layout(
Fieldset(_('Dados das Tramitações'),
row1,
row2,
HTML("&nbsp;"),
form_actions(label='Excluir')
)
)

5
sapl/materia/urls.py

@ -23,7 +23,8 @@ from sapl.materia.views import (AcompanhamentoConfirmarView,
TipoFimRelatoriaCrud, TipoMateriaCrud,
TipoProposicaoCrud, TramitacaoCrud,
TramitacaoEmLoteView, UnidadeTramitacaoCrud,
proposicao_texto, recuperar_materia)
proposicao_texto, recuperar_materia,
ExcluirTramitacaoEmLoteView)
from sapl.norma.views import NormaPesquisaSimplesView
from .apps import AppConfig
@ -90,6 +91,8 @@ urlpatterns_materia = [
name='primeira_tramitacao_em_lote'),
url(r'^materia/tramitacao-em-lote', TramitacaoEmLoteView.as_view(),
name='tramitacao_em_lote'),
url(r'^materia/excluir-tramitacao-em-lote', ExcluirTramitacaoEmLoteView.as_view(),
name='excluir_tramitacao_em_lote'),
]

45
sapl/materia/views.py

@ -57,7 +57,8 @@ from .forms import (AcessorioEmLoteFilterSet, AcompanhamentoMateriaForm,
TramitacaoEmLoteFilterSet, UnidadeTramitacaoForm,
filtra_tramitacao_destino,
filtra_tramitacao_destino_and_status,
filtra_tramitacao_status)
filtra_tramitacao_status,
ExcluirTramitacaoEmLote)
from .models import (AcompanhamentoMateria, Anexada, AssuntoMateria, Autoria,
DespachoInicial, DocumentoAcessorio, MateriaAssunto,
MateriaLegislativa, Numeracao, Orgao, Origem, Proposicao,
@ -95,7 +96,9 @@ def proposicao_texto(request, pk):
if proposicao.texto_original:
if (not proposicao.data_recebimento and
proposicao.autor.user_id != request.user.id):
raise Http404
messages.error(request, _('Você não tem permissão para acessar o texto original.'))
return redirect(reverse('sapl.materia:proposicao_detail',
kwargs={'pk':pk}))
arquivo = proposicao.texto_original
@ -1036,10 +1039,16 @@ class TramitacaoCrud(MasterDetailCrud):
'-id').first()
if ultima_tramitacao:
if ultima_tramitacao.unidade_tramitacao_destino:
context['form'].fields[
'unidade_tramitacao_local'].choices = [
(ultima_tramitacao.unidade_tramitacao_destino.pk,
ultima_tramitacao.unidade_tramitacao_destino)]
else:
msg = _('Unidade de tramitação destino '
' da última tramitação não pode ser vazia!')
messages.add_message(self.request, messages.ERROR, msg)
return context
def form_valid(self, form):
@ -1939,16 +1948,16 @@ class FichaSelecionaView(PermissionRequiredMixin, FormView):
tipo=tipo,
data_apresentacao__range=(data_inicial, data_final))
context['quantidade'] = len(materia_list)
materia_list = materia_list[:20]
materia_list = materia_list[:100]
context['form'].fields['materia'].choices = [
(m.id, str(m)) for m in materia_list]
if context['quantidade'] > 20:
if context['quantidade'] > 100:
messages.info(self.request, _('Sua pesquisa retornou mais do que '
'20 impressos. Por questões de '
'100 impressos. Por questões de '
'performance, foram retornados '
'apenas os 20 primeiros. Caso '
'apenas os 100 primeiros. Caso '
'queira outros, tente fazer uma '
'pesquisa mais específica'))
@ -1972,3 +1981,27 @@ class FichaSelecionaView(PermissionRequiredMixin, FormView):
return gerar_pdf_impressos(self.request, context,
'materia/impressos/ficha_pdf.html')
class ExcluirTramitacaoEmLoteView(PermissionRequiredMixin, FormView):
template_name = 'materia/em_lote/excluir_tramitacao.html'
permission_required = ('materia.add_tramitacao',)
form_class = ExcluirTramitacaoEmLote
form_valid_message = _('Tramitações excluídas com sucesso!')
def get_success_url(self):
return reverse('sapl.materia:excluir_tramitacao_em_lote')
def form_valid(self, form):
tramitacao_set = Tramitacao.objects.filter(data_tramitacao=form.cleaned_data['data_tramitacao'],
unidade_tramitacao_local=form.cleaned_data['unidade_tramitacao_local'],
unidade_tramitacao_destino=form.cleaned_data['unidade_tramitacao_destino'],
status=form.cleaned_data['status'])
for tramitacao in tramitacao_set:
materia = tramitacao.materia
if tramitacao == materia.tramitacao_set.last():
tramitacao.delete()
return redirect(self.get_success_url())

5
sapl/painel/views.py

@ -315,10 +315,7 @@ def get_presentes(pk, response, materia):
presentes_list = []
for p in presentes:
now_year = timezone.now().year
# Recupera a legislatura vigente
legislatura = Legislatura.objects.get(data_inicio__year__lte=now_year,
data_fim__year__gte=now_year)
legislatura = sessao.legislatura
# Recupera os mandatos daquele parlamentar
mandatos = p.parlamentar.mandato_set.filter(legislatura=legislatura)

12
sapl/parlamentares/views.py

@ -462,12 +462,14 @@ class ParlamentarCrud(Crud):
return l.id
if legislaturas:
return legislaturas[0].id
return 0
return -1
def get_queryset(self):
queryset = super().get_queryset()
legislatura_id = self.take_legislatura_id()
if legislatura_id != 0:
# Pelo menos uma casa legislativa criou uma
# legislatura de numero zero, o que é um absurdo
if legislatura_id >= 0:
return queryset.filter(
mandato__legislatura_id=legislatura_id).annotate(
mandato_titular=F('mandato__titular'))
@ -650,7 +652,7 @@ class MesaDiretoraView(FormView):
sessao_atual = sessoes.filter(data_inicio__year__lte=year).exclude(
data_inicio__gt=timezone.now()).order_by('-data_inicio').first()
mesa = sessao_atual.composicaomesa_set.all() if sessao_atual else []
mesa = sessao_atual.composicaomesa_set.all().order_by('cargo_id') if sessao_atual else []
cargos_ocupados = [m.cargo for m in mesa]
cargos = CargoMesa.objects.all()
@ -710,7 +712,7 @@ def altera_field_mesa(request):
# Atualiza os componentes da view após a mudança
composicao_mesa = ComposicaoMesa.objects.filter(
sessao_legislativa=sessao_selecionada)
sessao_legislativa=sessao_selecionada).order_by('cargo_id')
cargos_ocupados = [m.cargo for m in composicao_mesa]
cargos = CargoMesa.objects.all()
@ -879,7 +881,7 @@ def altera_field_mesa_public_view(request):
lista_sessoes = [(s.id, s.__str__()) for s in sessoes]
composicao_mesa = ComposicaoMesa.objects.filter(
sessao_legislativa=sessao_selecionada)
sessao_legislativa=sessao_selecionada).order_by('cargo_id')
cargos_ocupados = [(m.cargo.id,
m.cargo.__str__()) for m in composicao_mesa]

3
sapl/protocoloadm/forms.py

@ -291,7 +291,8 @@ class ProtocoloDocumentForm(ModelForm):
tipo_protocolo = forms.ChoiceField(required=True,
label=_('Tipo de Protocolo'),
choices=TIPOS_PROTOCOLO_CREATE,)
choices=TIPOS_PROTOCOLO_CREATE,
initial=0,)
tipo_documento = forms.ModelChoiceField(
label=_('Tipo de Documento'),

12
sapl/sessao/legacy.yaml

@ -15,7 +15,7 @@ SessaoPlenaria:
url_audio: url_audio
url_video: url_video
AbstractOrdemDia:
<AbstractOrdemDia>:
data_ordem: dat_ordem
materia: cod_materia
numero_ordem: num_ordem
@ -24,7 +24,7 @@ AbstractOrdemDia:
sessao_plenaria: cod_sessao_plen
tipo_votacao: tip_votacao
ExpedienteMateria: {}
ExpedienteMateria: <AbstractOrdemDia>
TipoExpediente:
nome: nom_expediente
@ -39,17 +39,17 @@ IntegranteMesa (MesaSessaoPlenaria):
parlamentar: cod_parlamentar
sessao_plenaria: cod_sessao_plen
AbstractOrador:
<AbstractOrador>:
numero_ordem: num_ordem
parlamentar: cod_parlamentar
sessao_plenaria: cod_sessao_plen
url_discurso: url_discurso
Orador (Oradores): {}
Orador (Oradores): <AbstractOrador>
OradorExpediente (OradoresExpediente): {}
OradorExpediente (OradoresExpediente): <AbstractOrador>
OrdemDia: {}
OrdemDia: <AbstractOrdemDia>
PresencaOrdemDia (OrdemDiaPresenca):
parlamentar: cod_parlamentar

25
sapl/sessao/migrations/0022_auto_20180618_1625.py

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.9.13 on 2018-06-18 19:25
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sessao', '0021_auto_20180417_1209'),
]
operations = [
migrations.AlterField(
model_name='expedientemateria',
name='tipo_votacao',
field=models.PositiveIntegerField(choices=[(1, 'Simbólica'), (2, 'Nominal'), (3, 'Secreta')], default=1, verbose_name='Tipo de votação'),
),
migrations.AlterField(
model_name='ordemdia',
name='tipo_votacao',
field=models.PositiveIntegerField(choices=[(1, 'Simbólica'), (2, 'Nominal'), (3, 'Secreta')], default=1, verbose_name='Tipo de votação'),
),
]

2
sapl/sessao/models.py

@ -241,7 +241,7 @@ class AbstractOrdemDia(models.Model):
numero_ordem = models.PositiveIntegerField(verbose_name=_('Nº Ordem'))
resultado = models.TextField(blank=True, verbose_name=_('Resultado'))
tipo_votacao = models.PositiveIntegerField(
verbose_name=_('Tipo de votação'), choices=TIPO_VOTACAO_CHOICES)
verbose_name=_('Tipo de votação'), choices=TIPO_VOTACAO_CHOICES, default=1)
votacao_aberta = models.NullBooleanField(
blank=True,
choices=YES_NO_CHOICES,

7
sapl/sessao/views.py

@ -995,7 +995,7 @@ class MesaView(FormMixin, DetailView):
return self.render_to_response(context)
mesa = sessao.integrantemesa_set.all() if sessao else []
mesa = sessao.integrantemesa_set.all().order_by('cargo_id') if sessao else []
cargos_ocupados = [m.cargo for m in mesa]
cargos = CargoMesa.objects.all()
cargos_vagos = list(set(cargos) - set(cargos_ocupados))
@ -1046,7 +1046,7 @@ def atualizar_mesa(request):
# Atualiza os componentes da view após a mudança
composicao_mesa = IntegranteMesa.objects.filter(
sessao_plenaria=sessao.id)
sessao_plenaria=sessao.id).order_by('cargo_id')
cargos_ocupados = [m.cargo for m in composicao_mesa]
cargos = CargoMesa.objects.all()
@ -2828,7 +2828,8 @@ class AdicionarVariasMateriasOrdemDia(AdicionarVariasMateriasExpediente):
ordem_dia.tipo_votacao = tipo_votacao
ordem_dia.save()
return self.get(request, self.kwargs)
return HttpResponseRedirect(
reverse('sapl.sessao:ordemdia_list', kwargs=self.kwargs))
@csrf_exempt

1
sapl/templates/compilacao/textoarticulado_menu_config.html

@ -14,7 +14,6 @@
{% if perms.compilacao.list_tipovide %}<li><a href="{% url 'sapl.compilacao:tipovide_list' %}">{%model_verbose_name_plural 'sapl.compilacao.models.TipoVide'%}</a></li>{% endif %}
{% if user.is_superuser %}
<li><a href="{% url 'sapl.compilacao:tipodispositivo_list' %}">{%model_verbose_name_plural 'sapl.compilacao.models.TipoDispositivo'%}</a></li>
<li><a href="/admin/compilacao/tipodispositivorelationship/">Relacionamento entre Dispositivos</a></li>
{% endif %}
</ul>

2
sapl/templates/crud/list.html

@ -74,7 +74,7 @@
<td>
{% if href %}
<a href="{{ href }}">{{ value|safe|default:"" }}</a>
{% elif valu != 'core.Cep.None' %}
{% elif value != 'core.Cep.None' %}
{% if value|url %}
<a href="{{ value|safe }}"> {{ value|safe|default:"" }} </a></div>
{% else %}

7
sapl/templates/materia/em_lote/excluir_tramitacao.html

@ -0,0 +1,7 @@
{% extends "crud/detail.html" %}
{% load i18n crispy_forms_tags %}
{% block actions %}{% endblock %}
{% block detail_content %}
{% crispy form %}
{% endblock detail_content %}

2
sapl/templates/materia/em_lote/tramitacao.html

@ -91,7 +91,7 @@
<br /><br /><br />
<fieldset>
<legend>2. Selecione as matérias para primeira tramitação:</legend>
<legend>2. Selecione as matérias para tramitação:</legend>
<table class="table table-striped table-hover">
<div class="controls">
<div class="checkbox">

29
sapl/templates/materia/prop_pendentes_list.html

@ -11,17 +11,35 @@
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Data de Envio</th>
<th>Tipo</th>
<th>Descrição</th>
<th>Autor</th>
{% with 'Data Envio,Tipo,Descrição,Autor' as list %}
{% for name in list|split:"," %}
<th>
<a title="{% trans 'Clique para alterar a ordem a listagem'%}" href="?o={% if 'o' not in request.GET and forloop.counter == 1 or 'o' in request.GET and forloop.counter|safe == request.GET.o %}-{%endif%}{{forloop.counter}}">
{{ name }}
{% if 'o' in request.GET %}
{% if 'o' not in request.GET and forloop.counter == 1 or 'o' in request.GET and forloop.counter|safe == request.GET.o %}
<span class="caret top" title="{% trans 'Listado na Ordem Ascendente'%}"></span>
{% elif 'o' in request.GET and forloop.counter == request.GET.o|str2intabs %}
<span class="caret" title="{% trans 'Listado na Ordem Descendente'%}"></span>
{%endif%}
{%endif%}
</a>
</th>
{% endfor %}
{% if not AppConfig.receber_recibo_proposicao %}
<th>Código do Documento</th>
{% endif %}
{% endwith %}
</tr>
</thead>
<tbody>
{% for prop in object_list %}
{% if 'o' in request.GET %}
{% define object_list|sort_by_keys:request.GET.o as list %}
{% else %}
{% define object_list as list %}
{% endif %}
{% for prop in list %}
<tr>
<td>
<a href="{% url 'sapl.materia:proposicao_detail' prop.pk %}">{{ prop.data_envio|localtime|date:"d/m/Y H:i:s" }}</a>
@ -44,5 +62,6 @@
</table>
{% endif %}
</fieldset>
{% include 'paginacao.html'%}
{% endblock %}

3
sapl/templates/navbar.yaml

@ -54,6 +54,9 @@
- title: {% trans 'Tramitação em Lote' %}
url: sapl.materia:primeira_tramitacao_em_lote
check_permission: materia.list_tramitacao {% comment %} FIXME transformar para checagens de menu_[funcionalidade]{% endcomment%}
- title: {% trans 'Excluir Tramitação em Lote' %}
url: sapl.materia:excluir_tramitacao_em_lote
check_permission: materia.list_tramitacao {% comment %} FIXME transformar para checagens de menu_[funcionalidade]{% endcomment%}
- title: {% trans 'Normas Jurídicas' %}
children:

2
sapl/templates/painel/index.html

@ -121,6 +121,8 @@
<tr>
<td><h4><font color="white"><span id="votacao"></span></h4></font></td>
</tr>
</table>
<table align="center">
<tr>
<td style="text-align:center"><h2><font color="#45919D"><span id="resultado_votacao"></span></font></h2></td>
</tr>

9
sapl/templates/protocoloadm/comprovante.html

@ -70,6 +70,15 @@
<th>Autor</th>
<td>{{ protocolo.autor }}</td>
</tr>
{% else %}
<tr>
<th>Assunto</th>
<td>{{ protocolo.assunto_ementa }}</td>
</tr>
<tr>
<th>Interessado</th>
<td>{{ protocolo.interessado }}</td>
</tr>
{% endif %}
<tr>
<th>Natureza</th>

2
setup.py

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

Loading…
Cancel
Save