Browse Source

Merge branch '3.1.x' into protocolo-data-hora

pull/2504/head
Edward 7 years ago
committed by GitHub
parent
commit
dff1f423ce
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .gitignore
  2. 3
      Dockerfile
  3. 1
      MANIFEST.in
  4. 2
      docker-compose.yml
  5. 2
      requirements/requirements.txt
  6. 4
      sapl/audiencia/forms.py
  7. 40
      sapl/base/forms.py
  8. 2
      sapl/base/tests/test_login.py
  9. 38
      sapl/base/urls.py
  10. 352
      sapl/base/views.py
  11. 30
      sapl/compilacao/forms.py
  12. 36
      sapl/crispy_layout_mixin.py
  13. 4
      sapl/crud/base.py
  14. 285
      sapl/lexml/OAIServer.py
  15. 3
      sapl/lexml/urls.py
  16. 12
      sapl/lexml/views.py
  17. 38
      sapl/materia/forms.py
  18. 4
      sapl/materia/views.py
  19. 8
      sapl/norma/forms.py
  20. 4
      sapl/parlamentares/forms.py
  21. 17
      sapl/parlamentares/views.py
  22. 20
      sapl/protocoloadm/forms.py
  23. 8
      sapl/relatorios/templates/pdf_etiqueta_protocolo_gerar.py
  24. 14
      sapl/sessao/forms.py
  25. 2
      sapl/sessao/migrations/0028_auto_20181031_0902.py
  26. 2
      sapl/sessao/models.py
  27. 4
      sapl/sessao/views.py
  28. 4
      sapl/settings.py
  29. 1
      sapl/static/css/painel.61177241.css
  30. 2572
      sapl/static/js/chunk-vendors.406f40ec.js
  31. 240
      sapl/static/js/compilacao.9bf86742.js
  32. 274
      sapl/static/js/global.594a728f.js
  33. 192
      sapl/static/js/painel.518bdc14.js
  34. 0
      sapl/static/sapl/audio/ring.mp3
  35. 0
      sapl/static/sapl/css/chunk-vendors.d6adfb08.css
  36. BIN
      sapl/static/sapl/css/chunk-vendors.d6adfb08.css.gz
  37. 0
      sapl/static/sapl/css/compilacao.3372b760.css
  38. BIN
      sapl/static/sapl/css/compilacao.3372b760.css.gz
  39. 2
      sapl/static/sapl/css/global.02b886ac.css
  40. BIN
      sapl/static/sapl/css/global.02b886ac.css.gz
  41. 1
      sapl/static/sapl/css/painel.ed40ad9c.css
  42. BIN
      sapl/static/sapl/css/painel.ed40ad9c.css.gz
  43. 0
      sapl/static/sapl/fonts/fa-brands-400.3186ebd2.eot
  44. BIN
      sapl/static/sapl/fonts/fa-brands-400.3186ebd2.eot.gz
  45. 0
      sapl/static/sapl/fonts/fa-brands-400.662c24d0.woff2
  46. 0
      sapl/static/sapl/fonts/fa-brands-400.a995bae1.ttf
  47. BIN
      sapl/static/sapl/fonts/fa-brands-400.a995bae1.ttf.gz
  48. 0
      sapl/static/sapl/fonts/fa-brands-400.c7d7a2a1.woff
  49. 0
      sapl/static/sapl/fonts/fa-regular-400.6a9d786e.woff2
  50. 0
      sapl/static/sapl/fonts/fa-regular-400.72f15fa7.woff
  51. 0
      sapl/static/sapl/fonts/fa-regular-400.80efa56b.eot
  52. BIN
      sapl/static/sapl/fonts/fa-regular-400.80efa56b.eot.gz
  53. 0
      sapl/static/sapl/fonts/fa-regular-400.fcb220ee.ttf
  54. BIN
      sapl/static/sapl/fonts/fa-regular-400.fcb220ee.ttf.gz
  55. 0
      sapl/static/sapl/fonts/fa-solid-900.20c189aa.ttf
  56. BIN
      sapl/static/sapl/fonts/fa-solid-900.20c189aa.ttf.gz
  57. 0
      sapl/static/sapl/fonts/fa-solid-900.3638e62e.woff2
  58. 0
      sapl/static/sapl/fonts/fa-solid-900.9a52a4e9.eot
  59. BIN
      sapl/static/sapl/fonts/fa-solid-900.9a52a4e9.eot.gz
  60. 0
      sapl/static/sapl/fonts/fa-solid-900.9c73abbd.woff
  61. 0
      sapl/static/sapl/img/arrow.png
  62. 0
      sapl/static/sapl/img/authenticated.png
  63. 0
      sapl/static/sapl/img/avatar.png
  64. 0
      sapl/static/sapl/img/beta.png
  65. 0
      sapl/static/sapl/img/brasao_transp.gif
  66. 0
      sapl/static/sapl/img/down_arrow_select.jpg
  67. BIN
      sapl/static/sapl/img/down_arrow_select.jpg.gz
  68. 0
      sapl/static/sapl/img/etiqueta.png
  69. 0
      sapl/static/sapl/img/fa-brands-400.e4fed0a5.svg
  70. BIN
      sapl/static/sapl/img/fa-brands-400.e4fed0a5.svg.gz
  71. 0
      sapl/static/sapl/img/fa-regular-400.304f31f4.svg
  72. BIN
      sapl/static/sapl/img/fa-regular-400.304f31f4.svg.gz
  73. 0
      sapl/static/sapl/img/fa-solid-900.c8ea4c79.svg
  74. BIN
      sapl/static/sapl/img/fa-solid-900.c8ea4c79.svg.gz
  75. 0
      sapl/static/sapl/img/favicon.ico
  76. 0
      sapl/static/sapl/img/file.png
  77. 0
      sapl/static/sapl/img/hand-note.png
  78. 0
      sapl/static/sapl/img/icon_comissoes.png
  79. 0
      sapl/static/sapl/img/icon_delete_white.png
  80. 0
      sapl/static/sapl/img/icon_materia_legislativa.png
  81. 0
      sapl/static/sapl/img/icon_mesa_diretora.png
  82. 0
      sapl/static/sapl/img/icon_normas_juridicas.png
  83. 0
      sapl/static/sapl/img/icon_parlamentares.png
  84. 0
      sapl/static/sapl/img/icon_pautas.png
  85. 0
      sapl/static/sapl/img/icon_plenarias.png
  86. 0
      sapl/static/sapl/img/icon_relatorios.png
  87. 0
      sapl/static/sapl/img/icon_save_white.png
  88. 0
      sapl/static/sapl/img/lexml.gif
  89. 0
      sapl/static/sapl/img/logo.png
  90. 0
      sapl/static/sapl/img/logo_cc.png
  91. 0
      sapl/static/sapl/img/logo_interlegis.png
  92. 0
      sapl/static/sapl/img/manual.png
  93. 0
      sapl/static/sapl/img/pdflogo.png
  94. 0
      sapl/static/sapl/img/perfil.png
  95. 0
      sapl/static/sapl/img/search-gray.png
  96. 0
      sapl/static/sapl/img/search.png
  97. 0
      sapl/static/sapl/img/ui-icons_2694e8_256x240.274157b3.png
  98. 0
      sapl/static/sapl/img/ui-icons_2e83ff_256x240.602e5d4d.png
  99. 0
      sapl/static/sapl/img/ui-icons_3d80b3_256x240.24fcd129.png
  100. 0
      sapl/static/sapl/img/ui-icons_72a7cf_256x240.55a4c5ce.png

2
.gitignore

@ -54,7 +54,7 @@ coverage.xml
# Django stuff:
*.log
sapl.log.*
*.swp
# Sphinx documentation

3
Dockerfile

@ -44,7 +44,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 && \
mkdir /var/log/sapl/
mkdir /var/log/sapl/ && touch /var/interlegis/sapl/sapl.log && \
ln -s /var/interlegis/sapl/sapl.log /var/log/sapl/sapl.log
VOLUME ["/var/interlegis/sapl/data", "/var/interlegis/sapl/media"]

1
MANIFEST.in

@ -1,4 +1,5 @@
include README.rst LICENSE.txt
include sapl/webpack-stats.json
recursive-include sapl *.html *.yaml
recursive-include sapl/static *
recursive-include sapl/relatorios/templates *.py

2
docker-compose.yml

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

2
requirements/requirements.txt

@ -30,5 +30,7 @@ textract==1.5.0
pysolr==3.6.0
whoosh==2.7.4
pyoai==2.5.0
git+git://github.com/interlegis/trml2pdf.git
git+git://github.com/interlegis/django-admin-bootstrapped

4
sapl/audiencia/forms.py

@ -7,7 +7,7 @@ from django.utils.translation import ugettext_lazy as _
from sapl.audiencia.models import AudienciaPublica, TipoAudienciaPublica, AnexoAudienciaPublica
from crispy_forms.layout import HTML, Button, Column, Fieldset, Layout
from crispy_forms.helper import FormHelper
from sapl.crispy_layout_mixin import SaplFormHelper
from sapl.crispy_layout_mixin import SaplFormLayout, form_actions, to_row
from sapl.materia.models import MateriaLegislativa, TipoMateriaLegislativa
from sapl.utils import timezone
@ -134,7 +134,7 @@ class AnexoAudienciaPublicaForm(forms.ModelForm):
row2 = to_row(
[('assunto', 12)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(
Fieldset(_('Identificação Básica'),
row1, row2))

40
sapl/base/forms.py

@ -1,7 +1,7 @@
import logging
from crispy_forms.bootstrap import FieldWithButtons, InlineRadios, StrictButton
from crispy_forms.helper import FormHelper
from sapl.crispy_layout_mixin import SaplFormHelper
from crispy_forms.layout import HTML, Button, Div, Field, Fieldset, Layout, Row
from django import forms
from django.conf import settings
@ -114,7 +114,7 @@ class UsuarioCreateForm(ModelForm):
row4 = to_row([(form_actions(label='Confirmar'), 6)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
row0,
row1,
@ -154,7 +154,7 @@ class UsuarioEditForm(ModelForm):
row3 = to_row([(form_actions(label='Salvar Alterações'), 6)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
row1,
row2,
@ -432,7 +432,7 @@ class AutorForm(ModelForm):
controle_acesso = Fieldset(_('Controle de Acesso do Autor'),
*controle_acesso)
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(autor_select, controle_acesso)
super(AutorForm, self).__init__(*args, **kwargs)
@ -697,7 +697,7 @@ class RelatorioAtasFilterSet(django_filters.FilterSet):
row1 = to_row([('data_inicio', 12)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Atas das Sessões Plenárias'),
@ -733,7 +733,7 @@ class RelatorioNormasMesFilterSet(django_filters.FilterSet):
row1 = to_row([('ano', 12)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Normas por mês do ano.'),
@ -762,7 +762,7 @@ class EstatisticasAcessoNormasForm(Form):
row1 = to_row([('ano', 12)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.form_method = 'GET'
self.helper.layout = Layout(
Fieldset(_('Normas por acessos nos meses do ano.'),
@ -800,7 +800,7 @@ class RelatorioNormasVigenciaFilterSet(django_filters.FilterSet):
row1 = to_row([('ano', 12)])
row2 = to_row([('vigencia', 12)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Normas por vigência.'),
@ -828,7 +828,7 @@ class RelatorioPresencaSessaoFilterSet(django_filters.FilterSet):
row1 = to_row([('data_inicio', 12)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Presença dos parlamentares nas sessões plenárias'),
@ -859,7 +859,7 @@ class RelatorioHistoricoTramitacaoFilterSet(django_filters.FilterSet):
self.filters['tipo'].label = 'Tipo de Matéria'
self.filters['tramitacao__unidade_tramitacao_local'
].label = _('Unidade Local (Último Local)')
].label = _('Unidade Local')
self.filters['tramitacao__status'].label = _('Status')
row1 = to_row([('tramitacao__data_tramitacao', 12)])
@ -868,7 +868,7 @@ class RelatorioHistoricoTramitacaoFilterSet(django_filters.FilterSet):
('tramitacao__unidade_tramitacao_local', 4),
('tramitacao__status', 4)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Histórico de Tramitação'),
@ -901,7 +901,7 @@ class RelatorioDataFimPrazoTramitacaoFilterSet(django_filters.FilterSet):
('tramitacao__unidade_tramitacao_local', 4),
('tramitacao__status', 4)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Tramitações por fim de prazo'),
@ -932,7 +932,7 @@ class RelatorioReuniaoFilterSet(django_filters.FilterSet):
('nome', 4),
('tema', 4)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Reunião de Comissão'),
@ -962,7 +962,7 @@ class RelatorioAudienciaFilterSet(django_filters.FilterSet):
[('tipo', 4),
('nome', 4)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Audiência Pública'),
@ -1006,7 +1006,7 @@ class RelatorioMateriasTramitacaoilterSet(django_filters.FilterSet):
row3 = to_row([('tramitacao__unidade_tramitacao_destino', 12)])
row4 = to_row([('tramitacao__status', 12)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Pesquisa de Matéria em Tramitação'),
@ -1032,7 +1032,7 @@ class RelatorioMateriasPorAnoAutorTipoFilterSet(django_filters.FilterSet):
row1 = to_row(
[('ano', 12)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Pesquisar'),
@ -1074,7 +1074,7 @@ class RelatorioMateriasPorAutorFilterSet(django_filters.FilterSet):
'limpar Autor',
css_class='btn btn-primary btn-sm'), 10)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Pesquisar'),
@ -1197,7 +1197,7 @@ class RecuperarSenhaForm(PasswordResetForm):
def __init__(self, *args, **kwargs):
row1 = to_row(
[('email', 12)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(_('Insira o e-mail cadastrado com a sua conta'),
row1,
@ -1234,7 +1234,7 @@ class NovaSenhaForm(SetPasswordForm):
[('new_password1', 6),
('new_password2', 6)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
row1,
form_actions(label='Enviar'))
@ -1267,7 +1267,7 @@ class AlterarSenhaForm(Form):
[('new_password1', 6),
('new_password2', 6)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
row1,
row2,

2
sapl/base/tests/test_login.py

@ -13,7 +13,7 @@ def user():
def test_login_aparece_na_barra_para_usuario_nao_logado(client):
response = client.get('/')
assert '<a class="nav-link" href="/login/"><img src="/static/img/user.png"></a>' in str(
assert '<a class="nav-link" href="/login/"><img src="/static/sapl/img/user.png"></a>' in str(
response.content)

38
sapl/base/urls.py

@ -27,7 +27,16 @@ from .views import (AlterarSenha, AppConfigCrud, CasaLegislativaCrud,
RelatorioNormasPublicadasMesView,
RelatorioNormasVigenciaView,
EstatisticasAcessoNormas,
RelatoriosListView)
RelatoriosListView,
ListarInconsistenciasView, ListarProtocolosDuplicadosView,
ListarProtocolosComMateriasView,
ListarMatProtocoloInexistenteView,
ListarParlMandatosIntersecaoView,
ListarAutoresDuplicadosView,
ListarBancadaComissaoAutorExternoView,
ListarLegislaturaInfindavelView,
ListarMandatoSemDataInicioView)
app_name = AppConfig.name
@ -127,6 +136,33 @@ urlpatterns = [
'(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})$',
ConfirmarEmailView.as_view(), name='confirmar_email'),
url(r'^sistema/inconsistencias/$',
ListarInconsistenciasView.as_view(),
name='lista_inconsistencias'),
url(r'^sistema/inconsistencias/protocolos_duplicados$',
ListarProtocolosDuplicadosView.as_view(),
name='lista_protocolos_duplicados'),
url(r'^sistema/inconsistencias/protocolos_com_materias$',
ListarProtocolosComMateriasView.as_view(),
name='lista_protocolos_com_materias'),
url(r'^sistema/inconsistencias/materias_protocolo_inexistente$',
ListarMatProtocoloInexistenteView.as_view(),
name='lista_materias_protocolo_inexistente'),
url(r'^sistema/inconsistencias/mandato_sem_data_inicio',
ListarMandatoSemDataInicioView.as_view(),
name='lista_mandato_sem_data_inicio'),
url(r'^sistema/inconsistencias/parlamentares_mandatos_intersecao$',
ListarParlMandatosIntersecaoView.as_view(),
name='lista_parlamentares_mandatos_intersecao'),
url(r'^sistema/inconsistencias/autores_duplicados$',
ListarAutoresDuplicadosView.as_view(),
name='lista_autores_duplicados'),
url(r'^sistema/inconsistencias/bancada_comissao_autor_externo$',
ListarBancadaComissaoAutorExternoView.as_view(),
name='lista_bancada_comissao_autor_externo'),
url(r'^sistema/inconsistencias/legislatura_infindavel$',
ListarLegislaturaInfindavelView.as_view(),
name='lista_legislatura_infindavel'),
# todos os sublinks de sistema devem vir acima deste
url(r'^sistema/$', permission_required('base.view_tabelas_auxiliares')

352
sapl/base/views.py

@ -1,4 +1,5 @@
import collections
import itertools
import datetime
import logging
import os
@ -35,10 +36,13 @@ from sapl.crud.base import CrudAux, make_pagination
from sapl.materia.models import (Autoria, MateriaLegislativa, Proposicao,
TipoMateriaLegislativa, StatusTramitacao, UnidadeTramitacao)
from sapl.norma.models import (NormaJuridica, NormaEstatisticas)
from sapl.parlamentares.models import Parlamentar, Legislatura, Mandato
from sapl.protocoloadm.models import Protocolo
from sapl.sessao.models import (PresencaOrdemDia, SessaoPlenaria,
SessaoPlenariaPresenca)
SessaoPlenariaPresenca, Bancada)
from sapl.utils import (parlamentares_ativos, gerar_hash_arquivo, SEPARADOR_HASH_PROPOSICAO,
show_results_filter_set, mail_service_configured)
show_results_filter_set, mail_service_configured,
intervalos_tem_intersecao,)
from .forms import (AlterarSenhaForm, CasaLegislativaForm,
ConfiguracoesAppForm, RelatorioAtasFilterSet,
@ -918,6 +922,350 @@ class EstatisticasAcessoNormas(TemplateView):
return self.render_to_response(context)
class ListarInconsistenciasView(PermissionRequiredMixin, ListView):
model = get_user_model()
template_name = 'base/lista_inconsistencias.html'
context_object_name = 'tabela_inconsistencias'
permission_required = ('base.list_appconfig',)
def get_queryset(self):
tabela = []
tabela.append(
('protocolos_duplicados',
'Protocolos duplicados',
len(protocolos_duplicados())
)
)
tabela.append(
('protocolos_com_materias',
'Protocolos que excedem o limite de matérias vinculadas',
len(protocolos_com_materias())
)
)
tabela.append(
('materias_protocolo_inexistente',
'Matérias Legislativas com protocolo inexistente',
len(materias_protocolo_inexistente())
)
)
tabela.append(
('mandato_sem_data_inicio',
'Mandatos sem data inicial',
len(mandato_sem_data_inicio())
)
)
tabela.append(
('parlamentares_mandatos_intersecao',
'Parlamentares com mandatos com interseção',
len(parlamentares_mandatos_intersecao())
)
)
tabela.append(
('autores_duplicados',
'Autores duplicados',
len(autores_duplicados())
)
)
tabela.append(
('bancada_comissao_autor_externo',
'Bancadas e Comissões com autor externo',
len(bancada_comissao_autor_externo())
)
)
tabela.append(
('legislatura_infindavel',
'Legislaturas sem data fim',
len(legislatura_infindavel())
)
)
return tabela
def legislatura_infindavel():
return Legislatura.objects.filter(data_fim__isnull=True).order_by('-numero')
class ListarLegislaturaInfindavelView(PermissionRequiredMixin, ListView):
model = get_user_model()
template_name = 'base/legislatura_infindavel.html'
context_object_name = 'legislatura_infindavel'
permission_required = ('base.list_appconfig',)
paginate_by = 10
def get_queryset(self):
return legislatura_infindavel()
def get_context_data(self, **kwargs):
context = super(
ListarLegislaturaInfindavelView, self
).get_context_data(**kwargs)
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages)
context[
'NO_ENTRIES_MSG'
] = 'Nenhuma encontrada.'
return context
def bancada_comissao_autor_externo():
tipo_autor_externo = TipoAutor.objects.filter(descricao='Externo')
lista_bancada_autor_externo = []
for bancada in Bancada.objects.all().order_by('nome'):
autor_externo = bancada.autor.filter(tipo=tipo_autor_externo)
if autor_externo:
q_autor_externo = bancada.autor.get(tipo=tipo_autor_externo)
lista_bancada_autor_externo.append(
(q_autor_externo, bancada, 'Bancada', 'sistema/bancada')
)
lista_comissao_autor_externo = []
for comissao in Comissao.objects.all().order_by('nome'):
autor_externo = comissao.autor.filter(tipo=tipo_autor_externo)
if autor_externo:
q_autor_externo = comissao.autor.get(tipo=tipo_autor_externo)
lista_comissao_autor_externo.append(
(q_autor_externo, comissao, 'Comissão', 'comissao')
)
return lista_bancada_autor_externo + lista_comissao_autor_externo
class ListarBancadaComissaoAutorExternoView(PermissionRequiredMixin, ListView):
model = get_user_model()
template_name = 'base/bancada_comissao_autor_externo.html'
context_object_name = 'bancada_comissao_autor_externo'
permission_required = ('base.list_appconfig',)
paginate_by = 10
def get_queryset(self):
return bancada_comissao_autor_externo()
def get_context_data(self, **kwargs):
context = super(
ListarBancadaComissaoAutorExternoView, self
).get_context_data(**kwargs)
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages)
context[
'NO_ENTRIES_MSG'
] = 'Nenhum encontrado.'
return context
def autores_duplicados():
return [autor.values() for autor in Autor.objects.values('nome', 'tipo__descricao').annotate(count=Count('nome')).filter(count__gt=1)]
class ListarAutoresDuplicadosView(PermissionRequiredMixin, ListView):
model = get_user_model()
template_name = 'base/autores_duplicados.html'
context_object_name = 'autores_duplicados'
permission_required = ('base.list_appconfig',)
paginate_by = 10
def get_queryset(self):
return autores_duplicados()
def get_context_data(self, **kwargs):
context = super(
ListarAutoresDuplicadosView, self).get_context_data(**kwargs)
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages)
context[
'NO_ENTRIES_MSG'
] = 'Nenhum encontrado.'
return context
def parlamentares_mandatos_intersecao():
intersecoes = []
for parlamentar in Parlamentar.objects.all().order_by('nome_completo'):
mandatos = parlamentar.mandato_set.all()
combinacoes = itertools.combinations(mandatos, 2)
for c in combinacoes:
data_inicio_mandato1 = c[0].data_inicio_mandato
data_fim_mandato1 = c[0].data_fim_mandato if c[0].data_fim_mandato else timezone.now().date()
data_inicio_mandato2 = c[1].data_inicio_mandato
data_fim_mandato2 = c[1].data_fim_mandato if c[1].data_fim_mandato else timezone.now().date()
if data_inicio_mandato1 and data_inicio_mandato2:
exists = intervalos_tem_intersecao(
data_inicio_mandato1, data_fim_mandato1,
data_inicio_mandato2, data_fim_mandato2)
if exists:
intersecoes.append((parlamentar, c[0], c[1]))
return intersecoes
class ListarParlMandatosIntersecaoView(PermissionRequiredMixin, ListView):
model = get_user_model()
template_name = 'base/parlamentares_mandatos_intersecao.html'
context_object_name = 'parlamentares_mandatos_intersecao'
permission_required = ('base.list_appconfig',)
paginate_by = 10
def get_queryset(self):
return parlamentares_mandatos_intersecao()
def get_context_data(self, **kwargs):
context = super(
ListarParlMandatosIntersecaoView, self).get_context_data(**kwargs)
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages)
context[
'NO_ENTRIES_MSG'
] = 'Nenhum encontrado.'
return context
def mandato_sem_data_inicio():
return Mandato.objects.filter(data_inicio_mandato__isnull=True).order_by('parlamentar')
class ListarMandatoSemDataInicioView(PermissionRequiredMixin, ListView):
model = get_user_model()
template_name = 'base/mandato_sem_data_inicio.html'
context_object_name = 'mandato_sem_data_inicio'
permission_required = ('base.list_appconfig',)
paginate_by = 10
def get_queryset(self):
return mandato_sem_data_inicio()
def get_context_data(self, **kwargs):
context = super(
ListarMandatoSemDataInicioView, self
).get_context_data(**kwargs)
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages)
context[
'NO_ENTRIES_MSG'
] = 'Nenhum encontrada.'
return context
def materias_protocolo_inexistente():
materias = []
for materia in MateriaLegislativa.objects.filter(numero_protocolo__isnull=False).order_by('-ano', 'numero'):
exists = Protocolo.objects.filter(
ano=materia.ano, numero=materia.numero_protocolo).exists()
if not exists:
materias.append(
(materia, materia.ano, materia.numero_protocolo))
return materias
class ListarMatProtocoloInexistenteView(PermissionRequiredMixin, ListView):
model = get_user_model()
template_name = 'base/materias_protocolo_inexistente.html'
context_object_name = 'materias_protocolo_inexistente'
permission_required = ('base.list_appconfig',)
paginate_by = 10
def get_queryset(self):
return materias_protocolo_inexistente()
def get_context_data(self, **kwargs):
context = super(
ListarMatProtocoloInexistenteView, self
).get_context_data(**kwargs)
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages)
context[
'NO_ENTRIES_MSG'
] = 'Nenhuma encontrada.'
return context
def protocolos_com_materias():
protocolos = {}
for m in MateriaLegislativa.objects.filter(numero_protocolo__isnull=False).order_by('-ano', 'numero_protocolo'):
if Protocolo.objects.filter(numero=m.numero_protocolo, ano=m.ano).exists():
key = "{}/{}".format(m.numero_protocolo, m.ano)
val = protocolos.get(key, list())
val.append(m)
protocolos[key] = val
return [(v[0], len(v)) for (k, v) in protocolos.items() if len(v) > 1]
class ListarProtocolosComMateriasView(PermissionRequiredMixin, ListView):
model = get_user_model()
template_name = 'base/protocolos_com_materias.html'
context_object_name = 'protocolos_com_materias'
permission_required = ('base.list_appconfig',)
paginate_by = 10
def get_queryset(self):
return protocolos_com_materias()
def get_context_data(self, **kwargs):
context = super(
ListarProtocolosComMateriasView, self).get_context_data(**kwargs)
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages)
context[
'NO_ENTRIES_MSG'
] = 'Nenhum encontrado.'
return context
def protocolos_duplicados():
protocolos = {}
for p in Protocolo.objects.order_by('-ano', 'numero'):
key = "{}/{}".format(p.numero, p.ano)
val = protocolos.get(key, list())
val.append(p)
protocolos[key] = val
return [(v[0], len(v)) for (k, v) in protocolos.items() if len(v) > 1]
class ListarProtocolosDuplicadosView(PermissionRequiredMixin, ListView):
model = get_user_model()
template_name = 'base/protocolos_duplicados.html'
context_object_name = 'protocolos_duplicados'
permission_required = ('base.list_appconfig',)
paginate_by = 10
def get_queryset(self):
return protocolos_duplicados()
def get_context_data(self, **kwargs):
context = super(
ListarProtocolosDuplicadosView, self).get_context_data(**kwargs)
paginator = context['paginator']
page_obj = context['page_obj']
context['page_range'] = make_pagination(
page_obj.number, paginator.num_pages)
context[
'NO_ENTRIES_MSG'
] = 'Nenhum encontrado.'
return context
class ListarUsuarioView(PermissionRequiredMixin, ListView):
model = get_user_model()
template_name = 'auth/user_list.html'

30
sapl/compilacao/forms.py

@ -3,7 +3,7 @@ from datetime import timedelta
from crispy_forms.bootstrap import (Alert, FieldWithButtons, FormActions,
InlineCheckboxes, InlineRadios,
StrictButton)
from crispy_forms.helper import FormHelper
from sapl.crispy_layout_mixin import SaplFormHelper
from crispy_forms.layout import (HTML, Button, Column, Div, Field, Fieldset,
Layout, Row, Submit)
from django import forms
@ -84,7 +84,7 @@ class TipoTaForm(ModelForm):
('perfis', 12),
])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(
Fieldset(_('Identificação Básica'),
row1, css_class="col-md-12"),
@ -153,7 +153,7 @@ class TaForm(ModelForm):
('participacao_social', 3),
])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(
Fieldset(_('Identificação Básica'), row1, css_class="col-md-12"),
Fieldset(
@ -268,7 +268,7 @@ class NotaForm(ModelForm):
css_class='form-group row justify-content-between mr-1 ml-1'
)
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Div(
@ -363,7 +363,7 @@ class VideForm(ModelForm):
'texto',
placeholder=_('Texto Adicional ao Vide')), 12)))))
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Div(
Div(HTML(_('Vides')), css_class='card-header bg-light'),
@ -471,7 +471,7 @@ class PublicacaoForm(ModelForm):
('url_externa', 8),
])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(
Fieldset(Publicacao._meta.verbose_name,
row1, row2, row3, css_class="col-md-12"))
@ -659,7 +659,7 @@ class DispositivoEdicaoBasicaForm(ModelForm):
for f in fields:
self.base_fields.update({f: getattr(self, f)})
self.helper = FormHelper()
self.helper = SaplFormHelper()
if not editor_type:
cancel_label = _('Ir para o Editor Sequencial')
@ -790,7 +790,7 @@ class DispositivoSearchModalForm(Form):
)
)
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
fields_search,
Row(to_column((Div(css_class='result-busca-dispositivo'), 12))))
@ -903,7 +903,7 @@ class DispositivoEdicaoVigenciaForm(ModelForm):
row_vigencia,
css_class="col-md-12"))
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(
*layout,
cancel_label=_('Ir para o Editor Sequencial'))
@ -1023,7 +1023,7 @@ class DispositivoDefinidorVigenciaForm(Form):
row_vigencia,
css_class="col-md-12"))
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(
*layout,
cancel_label=_('Ir para o Editor Sequencial'))
@ -1162,7 +1162,7 @@ class DispositivoEdicaoAlteracaoForm(ModelForm):
if hasattr(self, f):
self.base_fields.update({f: getattr(self, f)})
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(
*layout,
cancel_label=_('Ir para o Editor Sequencial'))
@ -1328,7 +1328,7 @@ class TextNotificacoesForm(Form):
(Submit('submit-form', _('Filtrar'),
css_class='btn btn-primary float-right'), 2)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(field_type_notificacoes)
super(TextNotificacoesForm, self).__init__(*args, **kwargs)
@ -1374,7 +1374,7 @@ class DispositivoRegistroAlteracaoForm(Form):
_fields = [Div(*layout, css_class="row")] + \
[to_row([(buttons, 12)])]
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(*_fields)
super(DispositivoRegistroAlteracaoForm, self).__init__(*args, **kwargs)
@ -1431,7 +1431,7 @@ class DispositivoRegistroRevogacaoForm(Form):
_fields = [Div(*layout, css_class="row")] + \
[to_row([(buttons, 12)])]
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(*_fields)
super(DispositivoRegistroRevogacaoForm, self).__init__(*args, **kwargs)
@ -1481,7 +1481,7 @@ class DispositivoRegistroInclusaoForm(Form):
_fields = [Div(*layout, css_class="row")] + \
[to_row([(buttons, 12)])]
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(*_fields)
super(DispositivoRegistroInclusaoForm, self).__init__(*args, **kwargs)

36
sapl/crispy_layout_mixin.py

@ -52,6 +52,35 @@ def form_actions(more=[Div(css_class='clearfix')],
)
class SaplFormHelper(FormHelper):
render_hidden_fields = True # default = False
"""
até a release 1.6.1 do django-crispy-forms, os fields em Meta.Fields eram
renderizados mesmo se não mencionados no helper.
Com esta mudança (https://github.com/django-crispy-forms/django-crispy-forms/commit/6b93e8a362422db8fe54aa731319c7cbc39990ba)
render_hidden_fields foi adicionado uma condição em que a cada
instância do Helper, fosse decidido se os fields não mencionados serião ou
não renderizados...
O Sapl até este commit: https://github.com/interlegis/sapl/commit/22b87f36ebc8659a6ecaf8831ab0f425206b0993
utilizou o django-crispy-forms na versão 1.6.1, ou seja,
sem a condição render_hidden_fields o que fazia o FormHelper, na 1.6.1
set comportar como se, agora, na 1.7.2 o default fosse True.
Como todos os Forms do Sapl foram construídos assumindo que fields
não incluídos explicitamente no Helper, o helper o incluiria implicitamente,
e assim o era, de acordo com commit acima do django-crispy-forms, então
cria-se essa classe:
class SaplFormHelper(FormHelper):
render_hidden_fields = True
onde torna o default, antes False, agora = True, o esperado pelos forms do sapl,
e substituí-se todos os FormHelper por SaplFormHelper dentro do projeto Sapl
esta explicação ficará aqui dentro do código, via commit, e na issue #2456.
"""
class SaplFormLayout(Layout):
def __init__(self, *fields, cancel_label=_('Cancelar'),
@ -188,8 +217,11 @@ class CrispyLayoutFormMixin:
pass
else:
if self.layout_key:
form.helper = FormHelper()
form.helper.layout = SaplFormLayout(*self.get_layout())
form.helper = SaplFormHelper()
layout = self.get_layout()
form.helper.layout = SaplFormLayout(*layout)
return form
@property

4
sapl/crud/base.py

@ -2,7 +2,7 @@ import logging
from braces.views import FormMessagesMixin
from crispy_forms.bootstrap import FieldWithButtons, StrictButton
from crispy_forms.helper import FormHelper
from sapl.crispy_layout_mixin import SaplFormHelper
from crispy_forms.layout import Field, Layout
from django import forms
from django.conf.urls import url
@ -150,7 +150,7 @@ class ListWithSearchForm(forms.Form):
def __init__(self, *args, **kwargs):
super(ListWithSearchForm, self).__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.form_class = 'form-inline'
self.helper.form_method = 'GET'
self.helper.layout = Layout(

285
sapl/lexml/OAIServer.py

@ -0,0 +1,285 @@
from datetime import datetime
import oaipmh
import oaipmh.error
import oaipmh.metadata
import oaipmh.server
from django.urls import reverse
from lxml import etree
from lxml.builder import ElementMaker
from sapl.base.models import AppConfig, CasaLegislativa
from sapl.lexml.models import LexmlPublicador
from sapl.norma.models import NormaJuridica
class OAILEXML:
"""
Padrao OAI do LeXML
Esta registrado sobre o nome 'oai_lexml'
"""
def __init__(self, prefix):
self.prefix = prefix
self.ns = {'oai_lexml': 'http://www.lexml.gov.br/oai_lexml', }
self.schemas = {'oai_lexml': 'http://projeto.lexml.gov.br/esquemas/oai_lexml.xsd'}
def __call__(self, element, metadata):
data = metadata.record
value = etree.XML(data['metadata'])
element.append(value)
class OAIServer:
"""
An OAI-2.0 compliant oai server.
Underlying code is based on pyoai's oaipmh.server'
"""
XSI_NS = 'http://www.w3.org/2001/XMLSchema-instance'
ns = {'lexml': 'http://www.lexml.gov.br/oai_lexml'}
schema = {'oai_lexml': 'http://projeto.lexml.gov.br/esquemas/oai_lexml.xsd'}
def __init__(self, config={}):
self.config = config
def identify(self):
result = oaipmh.common.Identify(
repositoryName=self.config['titulo'],
baseURL=self.config['base_url'],
protocolVersion='2.0',
adminEmails=self.config['email'],
earliestDatestamp=datetime(2001, 1, 1, 10, 00),
deletedRecord='transient',
granularity='YYYY-MM-DDThh:mm:ssZ',
compression=['identity'],
toolkit_description=False)
if not self.config['descricao']:
result.add_description(self.config['descricao'])
return result
def create_header_and_metadata(self, record):
header = self.create_header(record)
metadata = oaipmh.common.Metadata(None, record['metadata'])
metadata.record = record
return header, metadata
def list_query(self, from_date=None, until_date=None, offset=0, batch_size=10, identifier=None):
if identifier:
identifier = int(identifier.split('/')[-1]) # Get internal id
else:
identifier = ''
until_date = datetime.now() if not until_date or until_date > datetime.now() else until_date
return self.oai_query(offset=offset, batch_size=batch_size, from_date=from_date, until_date=until_date,
identifier=identifier)
def check_metadata_prefix(self, metadata_prefix):
if not metadata_prefix in self.config['metadata_prefixes']:
raise oaipmh.error.CannotDisseminateFormatError
def listRecords(self, metadataPrefix, from_date=None, until_date=None, cursor=0, batch_size=10):
self.check_metadata_prefix(metadataPrefix)
for record in self.list_query(from_date, until_date, cursor, batch_size):
header, metadata = self.create_header_and_metadata(record)
yield header, metadata, None # None?
def get_oai_id(self, internal_id):
return "oai:{}".format(internal_id)
def create_header(self, record):
oai_id = self.get_oai_id(record['record']['id'])
timestamp = record['record']['when_modified']
timestamp = timestamp.replace(tzinfo=None)
sets = []
deleted = record['record']['deleted']
return oaipmh.common.Header(None, oai_id, timestamp, sets, deleted)
def get_esfera_federacao(self):
appconfig = AppConfig.objects.first()
return appconfig.esfera_federacao
def recupera_norma(self, offset, batch_size, from_date, until_date, identifier, esfera):
kwargs = {'data__lte': until_date}
if from_date:
kwargs['data__gte'] = from_date
if identifier:
kwargs['numero'] = identifier
if esfera:
kwargs['esfera_federacao'] = esfera
return NormaJuridica.objects.select_related('tipo').filter(**kwargs)[offset:offset + batch_size]
def monta_id(self, norma):
if norma:
num = len(casa.endereco_web.split('.'))
dominio = '.'.join(casa.endereco_web.split('.')[1:num])
prefixo_oai = '{}.{}:sapl/'.format(casa.sigla.lower(), dominio)
numero_interno = norma.numero
tipo_norma = norma.tipo.equivalente_lexml
ano_norma = norma.ano
identificador = '{}{};{};{}'.format(prefixo_oai, tipo_norma, ano_norma, numero_interno)
return identificador
else:
return None
def monta_urn(self, norma, esfera):
if norma:
urn = 'urn:lex:br;'
esferas = {'M': 'municipal', 'E': 'estadual'}
municipio = casa.municipio.lower()
uf = casa.uf.lower()
for x in [' ', '.de.', '.da.', '.das.', '.do.', '.dos.']:
municipio = municipio.replace(x, '.')
uf = uf.replace(x, '.')
if esfera == 'M':
urn += '{};{}:'.format(uf, municipio)
if norma.tipo.equivalente_lexml == 'regimento.interno' or norma.tipo.equivalente_lexml == 'resolucao':
urn += 'camara.'
urn += esferas[esfera] + ':'
elif esfera == 'E':
urn += '{}:{}:'.format(uf, esferas[esfera])
else:
urn += ':'
if norma.tipo.equivalente_lexml:
urn += '{}:{};'.format(norma.tipo.equivalente_lexml, norma.data.isoformat())
else:
urn += '{};'.format(norma.data.isoformat())
if norma.tipo.equivalente_lexml == 'lei.organica' or norma.tipo.equivalente_lexml == 'constituicao':
urn += norma.ano
else:
urn += norma.numero
if norma.data_vigencia and norma.data_publicacao:
urn += '@{};publicacao;{}'.format(norma.data_vigencia.isoformat(), norma.data_publicacao.isoformat())
elif norma.data_publicacao:
urn += '@inicio.vigencia;publicacao;{}'.format(norma.data_publicacao.isoformat())
return urn
else:
return None
def data_por_extenso(self, data):
data = data.strftime('%d-%m-%Y')
if data != '':
meses = {1: 'Janeiro', 2: 'Fevereiro', 3: 'Março', 4: 'Abril', 5: 'Maio', 6: 'Junho', 7: 'Julho',
8: 'Agosto', 9: 'Setembro', 10: 'Outubro', 11: 'Novembro', 12: 'Dezembro'}
return '{} de {} de {}'.format(data[0:2], meses[int(data[3:5])], data[6:])
else:
return ''
def monta_xml(self, urn, norma):
publicador = LexmlPublicador.objects.first()
if norma and publicador:
LEXML = ElementMaker(namespace=self.ns['lexml'], nsmap=self.ns)
oai_lexml = LEXML.LexML()
oai_lexml.attrib['{{}}schemaLocation'.format(self.XSI_NS)] = '{} {}'.format(
'http://www.lexml.gov.br/oai_lexml', 'http://projeto.lexml.gov.br/esquemas/oai_lexml.xsd')
texto_integral = norma.texto_integral
mime_types = {'doc': 'application/msword',
'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'odt': 'application/vnd.oasis.opendocument.text',
'pdf': 'application/pdf',
'rtf': 'application/rtf'}
if texto_integral:
url_conteudo = self.config['base_url'] + texto_integral.url
extensao = texto_integral.url.split('.')[-1]
formato = mime_types.get(extensao, 'application/octet-stream')
else:
formato = 'text/html'
url_conteudo = self.config['base_url'] + reverse('sapl.norma:normajuridica_detail',
kwargs={'pk': norma.numero})
element_maker = ElementMaker()
id_publicador = str(publicador.id_publicador)
item_conteudo = element_maker.Item(url_conteudo, formato=formato, idPublicador=id_publicador,
tipo='conteudo')
oai_lexml.append(item_conteudo)
url = self.config['base_url'] + reverse('sapl.norma:normajuridica_detail', kwargs={'pk': norma.numero})
item_metadado = element_maker.Item(url, formato='text/html', idPublicador=id_publicador, tipo='metadado')
oai_lexml.append(item_metadado)
documento_individual = element_maker.DocumentoIndividual(urn)
oai_lexml.append(documento_individual)
if norma.tipo.equivalente_lexml == 'lei.organica':
epigrafe = '{} de {} - {}, de {}'.format(norma.tipo.descricao, casa.municipio,
casa.uf, norma.ano)
elif norma.tipo.equivalente_lexml == 'constituicao':
epigrafe = '{} do Estado de {}, de {}'.format(norma.tipo.descricao, casa.municipio,
norma.ano)
else:
epigrafe = '{}{}, de {}'.format(norma.tipo.descricao, norma.numero,
self.data_por_extenso(norma.data))
oai_lexml.append(element_maker.Epigrafe(epigrafe))
oai_lexml.append(element_maker.Ementa(norma.ementa))
indexacao = norma.indexacao
if indexacao:
oai_lexml.append(element_maker.Indexacao(indexacao))
return etree.tostring(oai_lexml)
else:
return None
def oai_query(self, offset=0, batch_size=10, from_date=None, until_date=None, identifier=None):
esfera = self.get_esfera_federacao()
offset = 0 if offset < 0 else offset
batch_size = 10 if batch_size < 0 else batch_size
until_date = datetime.now() if not until_date or until_date > datetime.now() else until_date
normas = self.recupera_norma(offset, batch_size, from_date, until_date, identifier, esfera)
for norma in normas:
resultado = {}
identificador = self.monta_id(norma)
urn = self.monta_urn(norma, esfera)
xml_lexml = self.monta_xml(urn, norma)
resultado['tx_metadado_xml'] = xml_lexml
resultado['cd_status'] = 'N'
resultado['id'] = identificador
resultado['when_modified'] = norma.timestamp
resultado['deleted'] = 0
yield {'record': resultado,
'metadata': resultado['tx_metadado_xml']}
def OAIServerFactory(config={}):
"""
Create a new OAI batching OAI Server given a config and a database
"""
for prefix in config['metadata_prefixes']:
metadata_registry = oaipmh.metadata.MetadataRegistry()
metadata_registry.registerWriter(prefix, OAILEXML(prefix))
return oaipmh.server.BatchingServer(
OAIServer(config),
metadata_registry=metadata_registry,
resumption_batch_size=config['batch_size']
)
casa = None
def casa_legislativa():
global casa
if not casa:
casa = CasaLegislativa.objects.first()
return casa if casa else CasaLegislativa() # retorna objeto dummy
def get_config(url, batch_size):
config = {'content_type': None,
'delay': 0,
'base_asset_path': None,
'metadata_prefixes': ['oai_lexml']}
config.update({'titulo': casa_legislativa().nome, # Inicializa variável global casa
'email': casa.email,
'base_url': url[:url.find('/', 8)],
'descricao': casa.informacao_geral,
'batch_size': batch_size})
return config
if __name__ == '__main__':
"""
Para executar localmente (estando no diretório raiz):
$ ./manage.py shell_plus
Executar comando
%run sapl/lexml/OAIServer.py
"""
oai_server = OAIServerFactory(get_config('http://127.0.0.1:8000/', 10))
r = oai_server.handleRequest({'verb': 'ListRecords',
'metadataPrefix': 'oai_lexml'})
print(r.decode('UTF-8'))

3
sapl/lexml/urls.py

@ -1,6 +1,6 @@
from django.conf.urls import include, url
from sapl.lexml.views import LexmlProvedorCrud, LexmlPublicadorCrud
from sapl.lexml.views import LexmlProvedorCrud, LexmlPublicadorCrud, lexml_request
from .apps import AppConfig
@ -11,4 +11,5 @@ urlpatterns = [
include(LexmlProvedorCrud.get_urls())),
url(r'^sistema/lexml/publicador/',
include(LexmlPublicadorCrud.get_urls())),
url(r'^sistema/lexml', lexml_request, name='lexml_endpoint')
]

12
sapl/lexml/views.py

@ -1,6 +1,18 @@
from django.http import HttpResponse
from sapl.crud.base import CrudAux
from sapl.lexml.OAIServer import OAIServerFactory, get_config
from .models import LexmlProvedor, LexmlPublicador
LexmlProvedorCrud = CrudAux.build(LexmlProvedor, 'lexml_provedor')
LexmlPublicadorCrud = CrudAux.build(LexmlPublicador, 'lexml_publicador')
def lexml_request(request):
config = get_config(request.get_raw_uri(), int(request.GET.get('batch_size', 10)))
oai_server = OAIServerFactory(config)
r = oai_server.handleRequest({'verb': request.GET.get('verb', 'ListRecords'),
'metadataPrefix': request.GET.get('metadataPrefix', 'oai_lexml')})
response = r.decode('UTF-8')
return HttpResponse(response, content_type='text/xml')

38
sapl/materia/forms.py

@ -3,7 +3,7 @@ import logging
import os
from crispy_forms.bootstrap import Alert, InlineRadios
from crispy_forms.helper import FormHelper
from sapl.crispy_layout_mixin import SaplFormHelper
from crispy_forms.layout import (HTML, Button, Column, Div, Field, Fieldset,
Layout, Row)
from django import forms
@ -76,7 +76,7 @@ class AdicionarVariasAutoriasFilterSet(django_filters.FilterSet):
row1 = to_row([('nome', 12)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Filtrar Autores'),
@ -112,7 +112,7 @@ class ReceberProposicaoForm(Form):
def __init__(self, *args, **kwargs):
row1 = to_row([('cod_hash', 12)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(
_('Incorporar Proposição'), row1,
@ -145,7 +145,7 @@ class MateriaSimplificadaForm(ModelForm):
row4 = to_row([('ementa', 12)])
row5 = to_row([('texto_original', 12)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(
_('Formulário Simplificado'),
@ -346,7 +346,7 @@ class AcompanhamentoMateriaForm(ModelForm):
Column(form_actions(label='Cadastrar'), css_class='col-md-2')
)
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(
_('Acompanhamento de Matéria por e-mail'), row1
@ -896,7 +896,7 @@ class MateriaLegislativaFilterSet(django_filters.FilterSet):
('tipo_listagem', 4)
])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Pesquisa Básica'),
@ -1016,7 +1016,7 @@ class AutoriaForm(ModelForm):
('autor', 4),
('primeiro_autor', 4)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(_('Autoria'),
row1, 'data_relativa', form_actions(label='Salvar')))
@ -1077,7 +1077,7 @@ class AutoriaMultiCreateForm(Form):
row2 = to_row([('autor', 12), ])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(
_('Autorias'), row1, row2, 'data_relativa', 'autores',
@ -1117,7 +1117,7 @@ class AcessorioEmLoteFilterSet(django_filters.FilterSet):
row1 = to_row([('tipo', 12)])
row2 = to_row([('data_apresentacao', 12)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Documentos Acessórios em Lote'),
@ -1142,7 +1142,7 @@ class PrimeiraTramitacaoEmLoteFilterSet(django_filters.FilterSet):
row1 = to_row([('tipo', 12)])
row2 = to_row([('data_apresentacao', 12)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Primeira Tramitação'),
@ -1177,7 +1177,7 @@ class TramitacaoEmLoteFilterSet(django_filters.FilterSet):
('tramitacao__status', 4)])
row2 = to_row([('data_apresentacao', 12)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Tramitação em Lote'),
@ -1241,7 +1241,7 @@ class TipoProposicaoForm(ModelForm):
)
)
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(tipo_select)
super(TipoProposicaoForm, self).__init__(*args, **kwargs)
@ -1431,7 +1431,7 @@ class ProposicaoForm(forms.ModelForm):
('ano_materia', 6)]),
), 12)),
)
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(*fields)
super(ProposicaoForm, self).__init__(*args, **kwargs)
@ -1579,7 +1579,7 @@ class DevolverProposicaoForm(forms.ModelForm):
)
)
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(*fields)
def clean(self):
@ -1762,7 +1762,7 @@ class ConfirmarProposicaoForm(ProposicaoForm):
fields.append(
Fieldset(_('Registro de Incorporação'), Row(*itens_incorporacao)))
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(*fields)
self.fields['tipo_readonly'].initial = self.instance.tipo.descricao
@ -2118,7 +2118,7 @@ class EtiquetaPesquisaForm(forms.Form):
[('processo_inicial', 6),
('processo_final', 6)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(
('Formulário de Etiqueta'),
@ -2203,7 +2203,7 @@ class FichaPesquisaForm(forms.Form):
('data_inicial', 3),
('data_final', 3)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(
('Formulário de Ficha'),
@ -2244,7 +2244,7 @@ class FichaSelecionaForm(forms.Form):
row1 = to_row(
[('materia', 12)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(
('Selecione a ficha que deseja imprimir'),
@ -2314,7 +2314,7 @@ class ExcluirTramitacaoEmLote(forms.Form):
[('unidade_tramitacao_local', 6),
('unidade_tramitacao_destino', 6)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(_('Dados das Tramitações'),
row1,

4
sapl/materia/views.py

@ -3,7 +3,7 @@ import logging
from random import choice
from string import ascii_letters, digits
from crispy_forms.helper import FormHelper
from sapl.crispy_layout_mixin import SaplFormHelper
from crispy_forms.layout import HTML
from django.conf import settings
from django.contrib import messages
@ -1350,7 +1350,7 @@ class TramitacaoCrud(MasterDetailCrud):
def montar_helper_documento_acessorio(self):
autor_row = montar_row_autor('autor')
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(*self.get_layout())
# Adiciona o novo campo 'autor' e mecanismo de busca

8
sapl/norma/forms.py

@ -1,7 +1,7 @@
import logging
from crispy_forms.helper import FormHelper
from sapl.crispy_layout_mixin import SaplFormHelper
from crispy_forms.layout import Fieldset, Layout
from django import forms
from django.core.exceptions import ObjectDoesNotExist, ValidationError
@ -71,7 +71,7 @@ class NormaFilterSet(django_filters.FilterSet):
row4 = to_row([('data_vigencia', 12)])
row5 = to_row([('o', 6), ('indexacao', 6)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Pesquisa de Norma'),
@ -238,7 +238,7 @@ class AutoriaNormaForm(ModelForm):
('autor', 4),
('primeiro_autor', 4)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(_('Autoria'),
row1, 'data_relativa', form_actions(label='Salvar')))
@ -400,7 +400,7 @@ class NormaPesquisaSimplesForm(forms.Form):
row2 = to_row(
[('titulo', 12)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(
('Índice de Normas'),

4
sapl/parlamentares/forms.py

@ -1,7 +1,7 @@
from datetime import timedelta
import logging
from crispy_forms.helper import FormHelper
from sapl.crispy_layout_mixin import SaplFormHelper
from crispy_forms.layout import Fieldset, Layout
from django import forms
from django.contrib.auth import get_user_model
@ -446,7 +446,7 @@ class VotanteForm(ModelForm):
def __init__(self, *args, **kwargs):
row1 = to_row([('username', 4)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(_('Votante'),
row1, form_actions(label='Salvar'))

17
sapl/parlamentares/views.py

@ -17,6 +17,7 @@ from django.utils.translation import ugettext_lazy as _
from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.generic import FormView
from django.views.generic.edit import UpdateView
from image_cropping.utils import get_backend
from sapl.base.forms import SessaoLegislativaForm
from sapl.base.models import Autor
@ -738,9 +739,9 @@ class MesaDiretoraView(FormView):
parlamentares_ocupados = [m.parlamentar for m in mesa]
parlamentares_vagos = list(
set(
[p.parlamentar for p in parlamentares]) - set(
[p.parlamentar for p in parlamentares if p.parlamentar.ativo]) - set(
parlamentares_ocupados))
parlamentares_vagos.sort(key=lambda x: x.nome_parlamentar)
# Se todos os cargos estiverem ocupados, a listagem de parlamentares
# deve ser renderizada vazia
if not cargos_vagos:
@ -808,6 +809,7 @@ def altera_field_mesa(request):
[p.parlamentar for p in parlamentares]) - set(
parlamentares_ocupados))
parlamentares_vagos.sort(key=lambda x: x.nome_parlamentar)
lista_sessoes = [(s.id, s.__str__()) for s in sessoes]
lista_composicao = [(c.id, c.parlamentar.__str__(),
c.cargo.__str__()) for c in composicao_mesa]
@ -1027,7 +1029,16 @@ def altera_field_mesa_public_view(request):
partido_parlamentar_sessao_legislativa(sessao,
parlamentar))
if parlamentar.fotografia:
lista_fotos.append(parlamentar.fotografia.url)
thumbnail_url = get_backend().get_thumbnail_url(
parlamentar.fotografia,
{
'size': (128, 128),
'box': parlamentar.cropping,
'crop': True,
'detail': True,
}
)
lista_fotos.append(thumbnail_url)
else:
lista_fotos.append(None)

20
sapl/protocoloadm/forms.py

@ -2,7 +2,7 @@
import logging
from crispy_forms.bootstrap import InlineRadios, Alert
from crispy_forms.helper import FormHelper
from sapl.crispy_layout_mixin import SaplFormHelper
from crispy_forms.layout import HTML, Button, Column, Fieldset, Layout, Div
from django import forms
from django.core.exceptions import (MultipleObjectsReturned,
@ -57,7 +57,7 @@ class AcompanhamentoDocumentoForm(ModelForm):
Column(form_actions(label='Cadastrar'), css_class='col-md-2')
)
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(
_('Acompanhamento de Documento por e-mail'), row1
@ -136,7 +136,7 @@ class ProtocoloFilterSet(django_filters.FilterSet):
row5 = to_row(
[('tipo_processo', 6), ('o', 6)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Pesquisar Protocolo'),
@ -212,7 +212,7 @@ class DocumentoAdministrativoFilterSet(django_filters.FilterSet):
('tramitacaoadministrativo__unidade_tramitacao_destino', 5),
])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Pesquisar Documento'),
@ -305,7 +305,7 @@ class AnularProcoloAdmForm(ModelForm):
row2 = to_row(
[('justificativa_anulacao', 12)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(_('Identificação do Protocolo'),
row1,
@ -932,7 +932,7 @@ class DocumentoAdministrativoForm(ModelForm):
row7 = to_row(
[('observacao', 12)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(
Fieldset(_('Identificação Básica'),
row1, row2, row3, row4, row5),
@ -1000,7 +1000,7 @@ class DesvincularDocumentoForm(ModelForm):
('ano', 4),
('tipo', 4)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(_('Identificação do Documento'),
row1,
@ -1065,7 +1065,7 @@ class DesvincularMateriaForm(forms.Form):
('ano', 4),
('tipo', 4)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(_('Identificação da Matéria'),
row1,
@ -1133,7 +1133,7 @@ class FichaPesquisaAdmForm(forms.Form):
('data_inicial', 3),
('data_final', 3)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(
('Formulário de Ficha'),
@ -1174,7 +1174,7 @@ class FichaSelecionaAdmForm(forms.Form):
row1 = to_row(
[('documento', 12)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(
('Selecione a ficha que deseja imprimir'),

8
sapl/relatorios/templates/pdf_etiqueta_protocolo_gerar.py

@ -23,7 +23,7 @@ def cabecalho(dic_cabecalho, imagem):
tmp_data += '\t\t\t\t<setFont name="Helvetica" size="12"/>\n'
tmp_data += '\t\t\t\t<drawString x="5cm" y="26.6cm">Sistema de Apoio ao Processo Legislativo</drawString>\n'
tmp_data += '\t\t\t\t<setFont name="Helvetica-Bold" size="13"/>\n'
tmp_data += '\t\t\t\t<drawString x="2.2cm" y="24.6cm">Relatório de Controle do Protocolo</drawString>\n'
tmp_data += '\t\t\t\t<drawString x="2.2cm" y="24.6cm">Relatório de Controle do Protocolo</drawString>\n'
return tmp_data
@ -36,7 +36,7 @@ def rodape(lst_rodape):
tmp_data += '\t\t\t\t<setFont name="Helvetica" size="8"/>\n'
tmp_data += '\t\t\t\t<drawString x="2cm" y="3.3cm">' + \
lst_rodape[2] + '</drawString>\n'
tmp_data += '\t\t\t\t<drawString x="17.9cm" y="3.3cm">Página <pageNumber/></drawString>\n'
tmp_data += '\t\t\t\t<drawString x="17.9cm" y="3.3cm">Página <pageNumber/></drawString>\n'
tmp_data += '\t\t\t\t<drawCentredString x="10.5cm" y="2.7cm">' + \
lst_rodape[0] + '</drawCentredString>\n'
tmp_data += '\t\t\t\t<drawCentredString x="10.5cm" y="2.3cm">' + \
@ -58,7 +58,7 @@ def paraStyle():
tmp_data += '\t\t\t<paraStyle name="all" alignment="justify"/>\n'
tmp_data += '\t\t</initialize>\n'
tmp_data += '\t\t<paraStyle name="P1" fontName="Helvetica-Bold" fontSize="5.0" leading="6" alignment="CENTER"/>\n'
tmp_data += '\t\t<paraStyle name="P2" fontName="Helvetica" fontSize="8.0" leading="9" alignment="CENTER"/>\n'
tmp_data += '\t\t<paraStyle name="P2" fontName="Helvetica" fontSize="8.0" leading="7.5" alignment="CENTER"/>\n'
tmp_data += '\t</stylesheet>\n'
return tmp_data
@ -122,7 +122,7 @@ def principal(imagem, lst_protocolos, dic_cabecalho, lst_rodape):
tmp_data += '\t<template pageSize="(62mm, 29mm)" title="Etiquetas de Protocolo" author="Luciano De Fazio" allowSplitting="20">\n'
tmp_data += '\t\t<pageTemplate id="first">\n'
tmp_data += '\t\t\t<pageGraphics>\n'
tmp_data += '\t\t\t<frame id="first" x1="0.03cm" y1="0.1cm" width="61mm" height="29mm"/>\n'
tmp_data += '\t\t\t<frame id="first" x1="0.03cm" y1="0.1cm" width="61mm" height="26mm"/>\n'
tmp_data += '\t\t\t</pageGraphics>\n'
tmp_data += '\t\t</pageTemplate>\n'
tmp_data += '\t</template>\n'

14
sapl/sessao/forms.py

@ -1,6 +1,6 @@
from datetime import datetime
from crispy_forms.helper import FormHelper
from sapl.crispy_layout_mixin import SaplFormHelper
from crispy_forms.layout import HTML, Button, Fieldset, Layout
from django import forms
from django.contrib.contenttypes.models import ContentType
@ -207,7 +207,7 @@ class RetiradaPautaForm(ModelForm):
('expediente', 6)])
row3 = to_row([('observacao', 12)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(
Fieldset(_('Retirada de Pauta'),
row1, row2, row3))
@ -591,7 +591,7 @@ class SessaoPlenariaFilterSet(django_filters.FilterSet):
('data_inicio__day', 3),
('tipo', 3)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(self.titulo,
@ -664,7 +664,7 @@ class AdicionarVariasMateriasFilterSet(MateriaLegislativaFilterSet):
row9 = to_row(
[('ementa', 12)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Pesquisa de Matéria'),
@ -787,7 +787,7 @@ class ResumoOrdenacaoForm(forms.Form):
row11 = to_row(
[('decimo_primeiro', 12)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = Layout(
Fieldset(_(''),
row1, row2, row3, row4, row5,
@ -853,7 +853,7 @@ class JustificativaAusenciaForm(ModelForm):
row8 = to_row(
[('observacao', 12)])
self.helper = FormHelper()
self.helper = SaplFormHelper()
self.helper.layout = SaplFormLayout(
Fieldset(_('Justificativa de Ausência'),
row1, row2, row3,
@ -981,7 +981,7 @@ class VotacaoEmBlocoFilterSet(MateriaLegislativaFilterSet):
row9 = to_row(
[('ementa', 12)])
self.form.helper = FormHelper()
self.form.helper = SaplFormHelper()
self.form.helper.form_method = 'GET'
self.form.helper.layout = Layout(
Fieldset(_('Pesquisa de Matéria'),

2
sapl/sessao/migrations/0028_auto_20181031_0902.py

@ -38,7 +38,7 @@ class Migration(migrations.Migration):
],
options={
'verbose_name_plural': 'Tipos de Retirada de Pauta',
'verbose_name': 'Tipo de Retidara de Pauta',
'verbose_name': 'Tipo de Retirada de Pauta',
'ordering': ['descricao'],
},
),

2
sapl/sessao/models.py

@ -590,7 +590,7 @@ class TipoRetiradaPauta(models.Model):
descricao = models.CharField(max_length=150, verbose_name=_('Descrição'))
class Meta:
verbose_name = _('Tipo de Retidara de Pauta')
verbose_name = _('Tipo de Retirada de Pauta')
verbose_name_plural = _('Tipos de Retirada de Pauta')
ordering = ['descricao']

4
sapl/sessao/views.py

@ -3335,7 +3335,8 @@ class VotacaoEmBlocoSimbolicaView(PermissionRequiredForAppCrudMixin, TemplateVie
if not 'context' in locals():
context = {'pk': self.kwargs['pk'],
'root_pk': self.kwargs['pk'],
'title': SessaoPlenaria.objects.get(id=self.kwargs['pk'])
'title': SessaoPlenaria.objects.get(id=self.kwargs['pk']),
'origem': request.POST['origem']
}
if 'marcadas_1' in request.POST:
@ -3514,6 +3515,7 @@ class VotacaoEmBlocoNominalView(PermissionRequiredForAppCrudMixin, TemplateView)
context = {'pk': self.kwargs['pk'],
'root_pk': self.kwargs['pk'],
'title': SessaoPlenaria.objects.get(id=self.kwargs['pk']),
'origem': request.POST['origem'],
'subnav_template_name': 'sessao/subnav.yaml'}
if 'marcadas_2' in request.POST:

4
sapl/settings.py

@ -272,8 +272,8 @@ FRONTEND_CUSTOM = config('FRONTEND_CUSTOM', default=False, cast=bool)
WEBPACK_LOADER = {
'DEFAULT': {
'CACHE': not DEBUG,
'BUNDLE_DIR_NAME': 'sapl/static/',
'STATS_FILE': (PROJECT_DIR if not FRONTEND_CUSTOM else PROJECT_DIR.parent.child('sapl-frontend')).child('webpack-stats.json'),
'BUNDLE_DIR_NAME': 'sapl/static/sapl/',
'STATS_FILE': (BASE_DIR if not FRONTEND_CUSTOM else PROJECT_DIR.parent.child('sapl-frontend')).child('webpack-stats.json'),
'POLL_INTERVAL': 0.1,
'TIMEOUT': None,
'IGNORE': [r'.+\.hot-update.js', r'.+\.map']

1
sapl/static/css/painel.61177241.css

@ -1 +0,0 @@
.painel-principal{background:#1c1b1b;font-family:Verdana}.painel-principal .text-title{color:#4fa64d}.painel-principal .text-subtitle{color:#459170}.painel-principal .text-value{color:#fff}.painel-principal .logo-painel{max-width:100%}.painel-principal .painels{-ms-flex-wrap:wrap;flex-wrap:wrap}

2572
sapl/static/js/chunk-vendors.406f40ec.js

File diff suppressed because one or more lines are too long

240
sapl/static/js/compilacao.9bf86742.js

File diff suppressed because one or more lines are too long

274
sapl/static/js/global.594a728f.js

File diff suppressed because one or more lines are too long

192
sapl/static/js/painel.518bdc14.js

@ -1,192 +0,0 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // install a JSONP callback for chunk loading
/******/ function webpackJsonpCallback(data) {
/******/ var chunkIds = data[0];
/******/ var moreModules = data[1];
/******/ var executeModules = data[2];
/******/
/******/ // add "moreModules" to the modules object,
/******/ // then flag all "chunkIds" as loaded and fire callback
/******/ var moduleId, chunkId, i = 0, resolves = [];
/******/ for(;i < chunkIds.length; i++) {
/******/ chunkId = chunkIds[i];
/******/ if(installedChunks[chunkId]) {
/******/ resolves.push(installedChunks[chunkId][0]);
/******/ }
/******/ installedChunks[chunkId] = 0;
/******/ }
/******/ for(moduleId in moreModules) {
/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
/******/ modules[moduleId] = moreModules[moduleId];
/******/ }
/******/ }
/******/ if(parentJsonpFunction) parentJsonpFunction(data);
/******/
/******/ while(resolves.length) {
/******/ resolves.shift()();
/******/ }
/******/
/******/ // add entry modules from loaded chunk to deferred list
/******/ deferredModules.push.apply(deferredModules, executeModules || []);
/******/
/******/ // run deferred modules when all chunks ready
/******/ return checkDeferredModules();
/******/ };
/******/ function checkDeferredModules() {
/******/ var result;
/******/ for(var i = 0; i < deferredModules.length; i++) {
/******/ var deferredModule = deferredModules[i];
/******/ var fulfilled = true;
/******/ for(var j = 1; j < deferredModule.length; j++) {
/******/ var depId = deferredModule[j];
/******/ if(installedChunks[depId] !== 0) fulfilled = false;
/******/ }
/******/ if(fulfilled) {
/******/ deferredModules.splice(i--, 1);
/******/ result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
/******/ }
/******/ }
/******/ return result;
/******/ }
/******/
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // object to store loaded and loading chunks
/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
/******/ // Promise = chunk loading, 0 = chunk loaded
/******/ var installedChunks = {
/******/ "painel": 0
/******/ };
/******/
/******/ var deferredModules = [];
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "/static/";
/******/
/******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
/******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
/******/ jsonpArray.push = webpackJsonpCallback;
/******/ jsonpArray = jsonpArray.slice();
/******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
/******/ var parentJsonpFunction = oldJsonpFunction;
/******/
/******/
/******/ // add entry module to deferred list
/******/ deferredModules.push([2,"chunk-vendors"]);
/******/ // run deferred modules when ready
/******/ return checkDeferredModules();
/******/ })
/************************************************************************/
/******/ ({
/***/ 2:
/*!***************************************!*\
!*** multi ./src/apps/painel/main.js ***!
\***************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
module.exports = __webpack_require__(/*! ./src/apps/painel/main.js */"3297");
/***/ }),
/***/ "3297":
/*!*********************************!*\
!*** ./src/apps/painel/main.js ***!
\*********************************/
/*! no exports provided */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var core_js_modules_es6_array_iterator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! core-js/modules/es6.array.iterator */ \"cadf\");\n/* harmony import */ var core_js_modules_es6_array_iterator__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es6_array_iterator__WEBPACK_IMPORTED_MODULE_0__);\n/* harmony import */ var core_js_modules_es6_promise__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! core-js/modules/es6.promise */ \"551c\");\n/* harmony import */ var core_js_modules_es6_promise__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es6_promise__WEBPACK_IMPORTED_MODULE_1__);\n/* harmony import */ var core_js_modules_es6_object_assign__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! core-js/modules/es6.object.assign */ \"f751\");\n/* harmony import */ var core_js_modules_es6_object_assign__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es6_object_assign__WEBPACK_IMPORTED_MODULE_2__);\n/* harmony import */ var core_js_modules_es7_promise_finally__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! core-js/modules/es7.promise.finally */ \"097d\");\n/* harmony import */ var core_js_modules_es7_promise_finally__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_es7_promise_finally__WEBPACK_IMPORTED_MODULE_3__);\n/* harmony import */ var _scss_painel_scss__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./scss/painel.scss */ \"5001\");\n/* harmony import */ var _scss_painel_scss__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(_scss_painel_scss__WEBPACK_IMPORTED_MODULE_4__);\n/* harmony import */ var vue__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! vue */ \"a026\");\n\n\n\n\n\n\nnew vue__WEBPACK_IMPORTED_MODULE_5__[\"default\"]();//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMzI5Ny5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL3NyYy9hcHBzL3BhaW5lbC9tYWluLmpzPzMyOTciXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0ICcuL3Njc3MvcGFpbmVsLnNjc3MnXG5pbXBvcnQgVnVlIGZyb20gJ3Z1ZSdcblxubmV3IFZ1ZSgpIl0sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7O0FBQUE7QUFDQTtBQUVBIiwic291cmNlUm9vdCI6IiJ9\n//# sourceURL=webpack-internal:///3297\n");
/***/ }),
/***/ "5001":
/*!******************************************!*\
!*** ./src/apps/painel/scss/painel.scss ***!
\******************************************/
/*! no static exports found */
/***/ (function(module, exports, __webpack_require__) {
eval("// extracted by mini-css-extract-plugin//# sourceURL=[module]\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiNTAwMS5qcyIsInNvdXJjZXMiOlsid2VicGFjazovLy8uL3NyYy9hcHBzL3BhaW5lbC9zY3NzL3BhaW5lbC5zY3NzP2VhN2QiXSwic291cmNlc0NvbnRlbnQiOlsiLy8gZXh0cmFjdGVkIGJ5IG1pbmktY3NzLWV4dHJhY3QtcGx1Z2luIl0sIm1hcHBpbmdzIjoiQUFBQSIsInNvdXJjZVJvb3QiOiIifQ==\n//# sourceURL=webpack-internal:///5001\n");
/***/ })
/******/ });

0
sapl/static/audio/ring.mp3 → sapl/static/sapl/audio/ring.mp3

0
sapl/static/css/chunk-vendors.f109b7f2.css → sapl/static/sapl/css/chunk-vendors.d6adfb08.css

BIN
sapl/static/sapl/css/chunk-vendors.d6adfb08.css.gz

Binary file not shown.

0
sapl/static/css/compilacao.1e862898.css → sapl/static/sapl/css/compilacao.3372b760.css

BIN
sapl/static/sapl/css/compilacao.3372b760.css.gz

Binary file not shown.

2
sapl/static/css/global.d2e0a784.css → sapl/static/sapl/css/global.02b886ac.css

File diff suppressed because one or more lines are too long

BIN
sapl/static/sapl/css/global.02b886ac.css.gz

Binary file not shown.

1
sapl/static/sapl/css/painel.ed40ad9c.css

@ -0,0 +1 @@
.painel-principal{background:#1c1b1b;font-family:Verdana}.painel-principal .text-title{color:#4fa64d;margin:.5rem}.painel-principal .text-subtitle{color:#459170}.painel-principal .data-hora{font-size:180%}.painel-principal .text-value{color:#fff}.painel-principal .logo-painel{max-width:100%}.painel-principal .painels{-ms-flex-wrap:wrap;flex-wrap:wrap}.painel-principal .painel{margin-top:1rem}.painel-principal .painel table{width:100%}.painel-principal .painel h2{margin-bottom:.5rem}.painel-principal .painel #oradores_list,.painel-principal .painel #votacao{text-align:left;display:inline-block;margin-bottom:1rem}

BIN
sapl/static/sapl/css/painel.ed40ad9c.css.gz

Binary file not shown.

0
sapl/static/fonts/fa-brands-400.3186ebd2.eot → sapl/static/sapl/fonts/fa-brands-400.3186ebd2.eot

BIN
sapl/static/sapl/fonts/fa-brands-400.3186ebd2.eot.gz

Binary file not shown.

0
sapl/static/fonts/fa-brands-400.662c24d0.woff2 → sapl/static/sapl/fonts/fa-brands-400.662c24d0.woff2

0
sapl/static/fonts/fa-brands-400.a995bae1.ttf → sapl/static/sapl/fonts/fa-brands-400.a995bae1.ttf

BIN
sapl/static/sapl/fonts/fa-brands-400.a995bae1.ttf.gz

Binary file not shown.

0
sapl/static/fonts/fa-brands-400.c7d7a2a1.woff → sapl/static/sapl/fonts/fa-brands-400.c7d7a2a1.woff

0
sapl/static/fonts/fa-regular-400.6a9d786e.woff2 → sapl/static/sapl/fonts/fa-regular-400.6a9d786e.woff2

0
sapl/static/fonts/fa-regular-400.72f15fa7.woff → sapl/static/sapl/fonts/fa-regular-400.72f15fa7.woff

0
sapl/static/fonts/fa-regular-400.80efa56b.eot → sapl/static/sapl/fonts/fa-regular-400.80efa56b.eot

BIN
sapl/static/sapl/fonts/fa-regular-400.80efa56b.eot.gz

Binary file not shown.

0
sapl/static/fonts/fa-regular-400.fcb220ee.ttf → sapl/static/sapl/fonts/fa-regular-400.fcb220ee.ttf

BIN
sapl/static/sapl/fonts/fa-regular-400.fcb220ee.ttf.gz

Binary file not shown.

0
sapl/static/fonts/fa-solid-900.20c189aa.ttf → sapl/static/sapl/fonts/fa-solid-900.20c189aa.ttf

BIN
sapl/static/sapl/fonts/fa-solid-900.20c189aa.ttf.gz

Binary file not shown.

0
sapl/static/fonts/fa-solid-900.3638e62e.woff2 → sapl/static/sapl/fonts/fa-solid-900.3638e62e.woff2

0
sapl/static/fonts/fa-solid-900.9a52a4e9.eot → sapl/static/sapl/fonts/fa-solid-900.9a52a4e9.eot

BIN
sapl/static/sapl/fonts/fa-solid-900.9a52a4e9.eot.gz

Binary file not shown.

0
sapl/static/fonts/fa-solid-900.9c73abbd.woff → sapl/static/sapl/fonts/fa-solid-900.9c73abbd.woff

0
sapl/static/img/arrow.png → sapl/static/sapl/img/arrow.png

Before

Width:  |  Height:  |  Size: 262 B

After

Width:  |  Height:  |  Size: 262 B

0
sapl/static/img/authenticated.png → sapl/static/sapl/img/authenticated.png

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

0
sapl/static/img/avatar.png → sapl/static/sapl/img/avatar.png

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

0
sapl/static/img/beta.png → sapl/static/sapl/img/beta.png

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

0
sapl/static/img/brasao_transp.gif → sapl/static/sapl/img/brasao_transp.gif

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

0
sapl/static/img/down_arrow_select.jpg → sapl/static/sapl/img/down_arrow_select.jpg

Before

Width:  |  Height:  |  Size: 682 B

After

Width:  |  Height:  |  Size: 682 B

BIN
sapl/static/sapl/img/down_arrow_select.jpg.gz

Binary file not shown.

0
sapl/static/img/etiqueta.png → sapl/static/sapl/img/etiqueta.png

Before

Width:  |  Height:  |  Size: 694 B

After

Width:  |  Height:  |  Size: 694 B

0
sapl/static/img/fa-brands-400.e4fed0a5.svg → sapl/static/sapl/img/fa-brands-400.e4fed0a5.svg

Before

Width:  |  Height:  |  Size: 645 KiB

After

Width:  |  Height:  |  Size: 645 KiB

BIN
sapl/static/sapl/img/fa-brands-400.e4fed0a5.svg.gz

Binary file not shown.

0
sapl/static/img/fa-regular-400.304f31f4.svg → sapl/static/sapl/img/fa-regular-400.304f31f4.svg

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 141 KiB

BIN
sapl/static/sapl/img/fa-regular-400.304f31f4.svg.gz

Binary file not shown.

0
sapl/static/img/fa-solid-900.c8ea4c79.svg → sapl/static/sapl/img/fa-solid-900.c8ea4c79.svg

Before

Width:  |  Height:  |  Size: 797 KiB

After

Width:  |  Height:  |  Size: 797 KiB

BIN
sapl/static/sapl/img/fa-solid-900.c8ea4c79.svg.gz

Binary file not shown.

0
sapl/static/img/favicon.ico → sapl/static/sapl/img/favicon.ico

Before

Width:  |  Height:  |  Size: 975 B

After

Width:  |  Height:  |  Size: 975 B

0
sapl/static/img/file.png → sapl/static/sapl/img/file.png

Before

Width:  |  Height:  |  Size: 1021 B

After

Width:  |  Height:  |  Size: 1021 B

0
sapl/static/img/hand-note.png → sapl/static/sapl/img/hand-note.png

Before

Width:  |  Height:  |  Size: 502 B

After

Width:  |  Height:  |  Size: 502 B

0
sapl/static/img/icon_comissoes.png → sapl/static/sapl/img/icon_comissoes.png

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

0
sapl/static/img/icon_delete_white.png → sapl/static/sapl/img/icon_delete_white.png

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

0
sapl/static/img/icon_materia_legislativa.png → sapl/static/sapl/img/icon_materia_legislativa.png

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

0
sapl/static/img/icon_mesa_diretora.png → sapl/static/sapl/img/icon_mesa_diretora.png

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

0
sapl/static/img/icon_normas_juridicas.png → sapl/static/sapl/img/icon_normas_juridicas.png

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

0
sapl/static/img/icon_parlamentares.png → sapl/static/sapl/img/icon_parlamentares.png

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

0
sapl/static/img/icon_pautas.png → sapl/static/sapl/img/icon_pautas.png

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

0
sapl/static/img/icon_plenarias.png → sapl/static/sapl/img/icon_plenarias.png

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

0
sapl/static/img/icon_relatorios.png → sapl/static/sapl/img/icon_relatorios.png

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

0
sapl/static/img/icon_save_white.png → sapl/static/sapl/img/icon_save_white.png

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

0
sapl/static/img/lexml.gif → sapl/static/sapl/img/lexml.gif

Before

Width:  |  Height:  |  Size: 568 B

After

Width:  |  Height:  |  Size: 568 B

0
sapl/static/img/logo.png → sapl/static/sapl/img/logo.png

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

0
sapl/static/img/logo_cc.png → sapl/static/sapl/img/logo_cc.png

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

0
sapl/static/img/logo_interlegis.png → sapl/static/sapl/img/logo_interlegis.png

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

0
sapl/static/img/manual.png → sapl/static/sapl/img/manual.png

Before

Width:  |  Height:  |  Size: 343 B

After

Width:  |  Height:  |  Size: 343 B

0
sapl/static/img/pdflogo.png → sapl/static/sapl/img/pdflogo.png

Before

Width:  |  Height:  |  Size: 238 KiB

After

Width:  |  Height:  |  Size: 238 KiB

0
sapl/static/img/perfil.png → sapl/static/sapl/img/perfil.png

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

0
sapl/static/img/search-gray.png → sapl/static/sapl/img/search-gray.png

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

0
sapl/static/img/search.png → sapl/static/sapl/img/search.png

Before

Width:  |  Height:  |  Size: 367 B

After

Width:  |  Height:  |  Size: 367 B

0
sapl/static/img/ui-icons_2694e8_256x240.274157b3.png → sapl/static/sapl/img/ui-icons_2694e8_256x240.274157b3.png

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

0
sapl/static/img/ui-icons_2e83ff_256x240.602e5d4d.png → sapl/static/sapl/img/ui-icons_2e83ff_256x240.602e5d4d.png

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

0
sapl/static/img/ui-icons_3d80b3_256x240.24fcd129.png → sapl/static/sapl/img/ui-icons_3d80b3_256x240.24fcd129.png

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

0
sapl/static/img/ui-icons_72a7cf_256x240.55a4c5ce.png → sapl/static/sapl/img/ui-icons_72a7cf_256x240.55a4c5ce.png

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save