Browse Source

Merge 9bea87062b into ecfd65a8b3

pull/3846/merge
Edward 1 week ago
committed by GitHub
parent
commit
51ea47f52b
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      requirements/requirements.txt
  2. 45
      sapl/base/receivers.py
  3. 12
      sapl/base/templatetags/common_tags.py
  4. 228
      sapl/base/tests/test_sanitize.py
  5. 28
      sapl/compilacao/forms.py
  6. 4
      sapl/compilacao/views.py
  7. 12
      sapl/crispy_layout_mixin.py
  8. 182
      sapl/sanitize.py
  9. 2
      sapl/templates/comissoes/materias_em_tramitacao.html
  10. 4
      sapl/templates/compilacao/dispositivo_form_search_fragment_child.html
  11. 2
      sapl/templates/compilacao/layout/dispositivo_checkbox.html
  12. 2
      sapl/templates/compilacao/layout/dispositivo_radio.html
  13. 2
      sapl/templates/compilacao/text_edit_bloco.html
  14. 2
      sapl/templates/compilacao/text_edit_blocoalteracao.html
  15. 2
      sapl/templates/compilacao/text_list_bloco.html
  16. 6
      sapl/templates/compilacao/text_list_blocoalteracao.html
  17. 2
      sapl/templates/compilacao/text_notificacoes.html
  18. 2
      sapl/templates/compilacao/textoarticulado_detail.html
  19. 2
      sapl/templates/materia/acompanhamento_materia.html
  20. 2
      sapl/templates/materia/materialegislativa_filter.html
  21. 2
      sapl/templates/materia/resumo_detail_materia.html
  22. 2
      sapl/templates/norma/normajuridica_filter.html
  23. 2
      sapl/templates/parlamentares/parlamentar_perfil_publico.html
  24. 2
      sapl/templates/protocoloadm/acompanhamento_documento.html
  25. 2
      sapl/templates/protocoloadm/documentoadministrativo_filter.html
  26. 2
      sapl/templates/relatorios/RelatorioMateriasPorAutor_filter.html
  27. 2
      sapl/templates/relatorios/RelatorioNormasPorAutor_filter.html
  28. 3
      sapl/templates/relatorios/blocos_sessao_plenaria/consideracoes_finais.html
  29. 3
      sapl/templates/relatorios/blocos_sessao_plenaria/expedientes.html
  30. 3
      sapl/templates/relatorios/blocos_sessao_plenaria/ocorrencias_da_sessao.html
  31. 2
      sapl/templates/relatorios/relatorio_doc_administrativos.html
  32. 2
      sapl/templates/relatorios/relatorio_normas_por_autor.html
  33. 10
      sapl/templates/relatorios/relatorio_pauta_sessao.html
  34. 6
      sapl/templates/search/search.html
  35. 2
      sapl/templates/sessao/adicionar_varias_materias_expediente.html
  36. 3
      sapl/templates/sessao/blocos_ata/consideracoes_finais.html
  37. 3
      sapl/templates/sessao/blocos_ata/expedientes.html
  38. 2
      sapl/templates/sessao/blocos_ata/materias_expediente.html
  39. 2
      sapl/templates/sessao/blocos_ata/materias_ordem_dia.html
  40. 3
      sapl/templates/sessao/blocos_ata/ocorrencias_da_sessao.html
  41. 3
      sapl/templates/sessao/blocos_resumo/consideracoes_finais.html
  42. 3
      sapl/templates/sessao/blocos_resumo/expedientes.html
  43. 3
      sapl/templates/sessao/blocos_resumo/ocorrencias_da_sessao.html
  44. 2
      sapl/templates/sessao/consideracoes_finais.html
  45. 2
      sapl/templates/sessao/leitura/leitura_bloco.html
  46. 2
      sapl/templates/sessao/ocorrencias_da_sessao.html
  47. 6
      sapl/templates/sessao/pauta_sessao_detail.html
  48. 2
      sapl/templates/sessao/votacao/nominal.html
  49. 6
      sapl/templates/sessao/votacao/nominal_detail.html
  50. 6
      sapl/templates/sessao/votacao/nominal_edit.html
  51. 2
      sapl/templates/sessao/votacao/nominal_transparencia.html
  52. 2
      sapl/templates/sessao/votacao/simbolica_transparencia.html
  53. 4
      sapl/templates/sessao/votacao/votacao.html
  54. 2
      sapl/templates/sessao/votacao/votacao_bloco.html
  55. 6
      sapl/templates/sessao/votacao/votacao_edit.html
  56. 4
      sapl/templates/sessao/votacao/votacao_nominal_bloco.html
  57. 8
      sapl/templates/sessao/votacao/votacao_simbolica_bloco.html

1
requirements/requirements.txt

@ -38,6 +38,7 @@ django-prometheus==2.2.0
asn1crypto==1.5.1 asn1crypto==1.5.1
XlsxWriter==3.2.0 XlsxWriter==3.2.0
nh3==0.2.22
setuptools==80.9.0 setuptools==80.9.0

45
sapl/base/receivers.py

@ -8,6 +8,7 @@ from django.conf import settings
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core import serializers from django.core import serializers
from django.core.files.uploadedfile import InMemoryUploadedFile, UploadedFile from django.core.files.uploadedfile import InMemoryUploadedFile, UploadedFile
from django.db.models import TextField
from django.db.models.fields.files import FileField from django.db.models.fields.files import FileField
from django.db.models.signals import post_delete, post_save, \ from django.db.models.signals import post_delete, post_save, \
post_migrate, pre_save, pre_migrate post_migrate, pre_save, pre_migrate
@ -22,11 +23,15 @@ from sapl.decorators import receiver_multi_senders
from sapl.materia.models import Tramitacao from sapl.materia.models import Tramitacao
from sapl.parlamentares.models import Parlamentar from sapl.parlamentares.models import Parlamentar
from sapl.protocoloadm.models import TramitacaoAdministrativo from sapl.protocoloadm.models import TramitacaoAdministrativo
from sapl.sanitize import sanitize_field
from sapl.utils import get_base_url, models_with_gr_for_model from sapl.utils import get_base_url, models_with_gr_for_model
models_with_gr_for_autor = models_with_gr_for_model(Autor) models_with_gr_for_autor = models_with_gr_for_model(Autor)
SAPL_APP_LABELS = frozenset(
app.rsplit('.', 1)[-1] for app in settings.SAPL_APPS)
@receiver_multi_senders(post_save, senders=models_with_gr_for_autor) @receiver_multi_senders(post_save, senders=models_with_gr_for_autor)
def handle_update_autor_related(sender, **kwargs): def handle_update_autor_related(sender, **kwargs):
@ -477,6 +482,46 @@ def signed_files_extraction_pre_save_signal(sender, instance, **kwargs):
signed_files_extraction_function(sender, instance, **kwargs) signed_files_extraction_function(sender, instance, **kwargs)
# Cache dos TextField por modelo: a introspecção de _meta.fields a cada save
# pesa mais que a própria sanitização, e compilacao salva Dispositivo em laço.
_text_fields_cache = {}
def get_text_fields(model):
try:
return _text_fields_cache[model]
except KeyError:
fields = [f.name for f in model._meta.fields
if isinstance(f, TextField)]
_text_fields_cache[model] = fields
return fields
@receiver(pre_save, dispatch_uid='sanitize_textfields_pre_save_signal')
def sanitize_textfields_pre_save_signal(sender, instance, **kwargs):
"""Remove HTML/JavaScript perigoso de todo TextField dos modelos do SAPL.
Cobre forms, a API do drfautoapi, o admin e o shell num único ponto.
Ver sapl.sanitize para as políticas por campo.
"""
# loaddata (inclusive dentro de migrations) grava o objeto literalmente
if kwargs.get('raw'):
return
if sender._meta.app_label not in SAPL_APP_LABELS:
return
for fieldname in get_text_fields(sender):
value = getattr(instance, fieldname, None)
if not value:
continue
sanitized = sanitize_field(sender, fieldname, value)
if sanitized != value:
setattr(instance, fieldname, sanitized)
@receiver(pre_migrate, dispatch_uid='disconnect_signals_pre_migrate') @receiver(pre_migrate, dispatch_uid='disconnect_signals_pre_migrate')
def disconnect_signals_pre_migrate(*args, **kwargs): def disconnect_signals_pre_migrate(*args, **kwargs):
# sanitize_textfields_pre_save_signal não é desconectado aqui de propósito:
# é barato e idempotente, e desconectá-lo deixaria sem proteção qualquer
# migrate rodado no mesmo processo (é o caso da suíte de testes).
pre_save.disconnect(dispatch_uid='signed_files_extraction_pre_save_signal') pre_save.disconnect(dispatch_uid='signed_files_extraction_pre_save_signal')

12
sapl/base/templatetags/common_tags.py

@ -10,6 +10,7 @@ from sapl.base.models import AppConfig
from sapl.materia.models import DocumentoAcessorio, MateriaLegislativa, Proposicao from sapl.materia.models import DocumentoAcessorio, MateriaLegislativa, Proposicao
from sapl.norma.models import NormaJuridica from sapl.norma.models import NormaJuridica
from sapl.parlamentares.models import Filiacao from sapl.parlamentares.models import Filiacao
from sapl.sanitize import sanitize_html
from sapl.sessao.models import SessaoPlenaria from sapl.sessao.models import SessaoPlenaria
from sapl.utils import filiacao_data, SEPARADOR_HASH_PROPOSICAO, is_report_allowed from sapl.utils import filiacao_data, SEPARADOR_HASH_PROPOSICAO, is_report_allowed
@ -396,6 +397,17 @@ def render_chunk_vendors(extension=None):
return '' return ''
@register.filter(is_safe=True)
@stringfilter
def sanitize(value):
"""Renderiza HTML de campo rico (TinyMCE) sem script nem URL perigosa.
Substitui o |safe nos campos que legitimamente guardam HTML. Protege
também as linhas gravadas antes da sanitização no pre_save.
"""
return mark_safe(sanitize_html(value, rich=True))
@register.filter(is_safe=True) @register.filter(is_safe=True)
@stringfilter @stringfilter
def dont_break_out(value): def dont_break_out(value):

228
sapl/base/tests/test_sanitize.py

@ -0,0 +1,228 @@
import pytest
from django.db.models.signals import pre_save
from django.template import Context, Template
from model_bakery import baker
from sapl.compilacao.forms import TipoDispositivoForm
from sapl.compilacao.models import TipoDispositivo, TipoTextoArticulado
from sapl.crispy_layout_mixin import get_field_display
from sapl.lexml.models import LexmlProvedor
from sapl.parlamentares.models import Parlamentar
from sapl.protocoloadm.models import TramitacaoAdministrativo
from sapl.sanitize import (html_fragment_is_balanced, sanitize_field,
sanitize_html, sanitize_scope)
from sapl.sessao.models import ExpedienteSessao
def test_plain_remove_marcacao_e_preserva_texto():
assert sanitize_html('<script>alert(1)</script>Ciente') == 'Ciente'
assert sanitize_html('Encaminhado <b>ao</b> setor') == \
'Encaminhado ao setor'
assert sanitize_html('<img src=x onerror=alert(1)>') == ''
assert sanitize_html('<div>a</div><div>b</div>') == 'ab'
def test_plain_guarda_texto_puro_sem_escape():
"""O escape é da renderização; gravado escapado, apareceria &amp; na tela."""
assert sanitize_html('Valor < 10 & prazo > 5') == 'Valor < 10 & prazo > 5'
assert sanitize_html('ALFA &amp; BETA') == 'ALFA & BETA'
def test_plain_preserva_quebras_de_linha():
# get_field_display converte \n em <br/> depois de sanitizar
assert sanitize_html('linha1\nlinha2') == 'linha1\nlinha2'
def test_valores_vazios_atravessam():
assert sanitize_html('') == ''
assert sanitize_html(None) is None
assert sanitize_html('', rich=True) == ''
@pytest.mark.parametrize('valor', [
'<script>alert(1)</script>Ciente',
'Valor < 10 & prazo > 5',
'Encaminhado <b>ao</b> <a href="https://x">setor</a>',
'texto &amp; cia',
])
def test_plain_e_idempotente(valor):
"""Salvar de novo um registro já sanitizado não altera o texto."""
uma_vez = sanitize_html(valor)
assert sanitize_html(uma_vez) == uma_vez
@pytest.mark.parametrize('valor', [
'<a href="https://camara.gov.br" target="_blank">Portal</a>',
'<p style="text-align: center;">centro</p>',
'<table><tr><td colspan="2">c</td></tr></table>',
'<script>alert(1)</script><b>ok</b>',
])
def test_rich_e_idempotente(valor):
uma_vez = sanitize_html(valor, rich=True)
assert sanitize_html(uma_vez, rich=True) == uma_vez
def test_rich_preserva_links():
saida = sanitize_html(
'<a href="https://camara.gov.br" target="_blank">Portal</a>',
rich=True)
assert 'href="https://camara.gov.br"' in saida
assert 'target="_blank"' in saida
assert 'rel="noopener noreferrer"' in saida
assert '>Portal</a>' in saida
assert 'href="/materia/123"' in sanitize_html(
'<a href="/materia/123">Matéria</a>', rich=True)
assert 'href="mailto:a@b.c"' in sanitize_html(
'<a href="mailto:a@b.c">mail</a>', rich=True)
def test_rich_remove_href_perigosa_mas_mantem_o_texto():
saida = sanitize_html(
'<a href="javascript:alert(1)">clique</a>', rich=True)
assert 'javascript' not in saida
assert 'clique' in saida
def test_rich_remove_script_e_manipuladores_de_evento():
saida = sanitize_html('<script>alert(1)</script><b>ok</b>', rich=True)
assert saida == '<b>ok</b>'
assert 'onclick' not in sanitize_html(
'<a href="#" onclick="steal()">x</a>', rich=True)
assert 'onerror' not in sanitize_html(
'<img src="x" onerror="alert(1)">', rich=True)
def test_rich_preserva_formatacao_do_tinymce():
"""Protege contra regressão visual no conteúdo já cadastrado."""
assert 'style="text-align: center;"' in sanitize_html(
'<p style="text-align: center;">centro</p>', rich=True)
saida = sanitize_html(
'<table><tr><td colspan="2">c</td></tr></table>', rich=True)
assert '<table>' in saida and 'colspan="2"' in saida
assert sanitize_html('<ul><li>a</li><li>b</li></ul>', rich=True) == \
'<ul><li>a</li><li>b</li></ul>'
def test_sanitize_scope():
assert sanitize_scope(TramitacaoAdministrativo, 'texto') == 'plain'
assert sanitize_scope(ExpedienteSessao, 'conteudo') == 'rich'
assert sanitize_scope(LexmlProvedor, 'xml') == 'exempt'
assert sanitize_scope(TipoTextoArticulado, 'rodape_global') == 'exempt'
assert sanitize_scope(Parlamentar, 'biografia') == 'rich'
def test_sanitize_field_respeita_isencao():
xml = '<xml><a href="javascript:x">y</a></xml>'
assert sanitize_field(LexmlProvedor, 'xml', xml) == xml
@pytest.mark.django_db
def test_pre_save_sanitiza_campo_simples():
t = baker.make(TramitacaoAdministrativo,
texto='<script>alert(1)</script>Ciente <b>ok</b>')
t.refresh_from_db()
assert t.texto == 'Ciente ok'
@pytest.mark.django_db
def test_pre_save_sanitiza_campo_rico_preservando_html():
e = baker.make(ExpedienteSessao,
conteudo='<b>x</b><script>alert(1)</script>'
'<a href="https://a.b" target="_blank">l</a>')
e.refresh_from_db()
assert '<script>' not in e.conteudo
assert '<b>x</b>' in e.conteudo
assert 'href="https://a.b"' in e.conteudo
@pytest.mark.django_db
def test_pre_save_nao_toca_modelo_isento():
xml = '<xml>a &amp; b <script>x</script></xml>'
p = baker.make(LexmlProvedor, xml=xml)
p.refresh_from_db()
assert p.xml == xml
@pytest.mark.django_db
def test_get_field_display_nao_devolve_script():
t = TramitacaoAdministrativo(texto='<script>alert(1)</script>Ciente')
__, display = get_field_display(t, 'texto')
assert '<script>' not in display
assert 'Ciente' in display
@pytest.mark.django_db
def test_get_field_display_protege_linha_legada():
"""Linhas gravadas antes do pre_save não passam pela camada de entrada."""
t = baker.make(TramitacaoAdministrativo, texto='ok')
TramitacaoAdministrativo.objects.filter(pk=t.pk).update(
texto='<script>alert(1)</script>legado')
t.refresh_from_db()
assert t.texto == '<script>alert(1)</script>legado'
__, display = get_field_display(t, 'texto')
assert '<script>' not in display
def test_pre_save_ignora_raw():
"""loaddata (inclusive em migrations) grava o objeto literalmente."""
t = TramitacaoAdministrativo(texto='<b>fixture</b>')
pre_save.send(sender=TramitacaoAdministrativo, instance=t, raw=True)
assert t.texto == '<b>fixture</b>'
def test_get_field_display_escapa_texto_puro_uma_unica_vez():
t = TramitacaoAdministrativo(texto='ALFA & BETA < 30')
__, display = get_field_display(t, 'texto')
assert 'ALFA &amp; BETA &lt; 30' in display
assert '&amp;amp;' not in display
def test_get_field_display_escapa_campo_isento():
p = LexmlProvedor(xml='<xml><script>x</script></xml>')
__, display = get_field_display(p, 'xml')
assert '<script>' not in display
assert '&lt;xml&gt;' in display
def test_striptags_apos_sanitize_nao_escapa_duas_vezes():
"""Blocos da ata: texto puro, sem entidades escapadas de novo."""
t = Template('{% load common_tags %}{{ v|sanitize|striptags }}')
saida = t.render(Context({
'v': '<p>Ofício&nbsp;12 lido &amp; arquivado</p>'
'<script>alert(1)</script><img src=x onerror=alert(1)>'}))
assert saida == 'Ofício&nbsp;12 lido &amp; arquivado'
@pytest.mark.parametrize('valor, esperado', [
('<br>', True),
('<br/>', True),
('<div class="titulo">Justificativa</div>', True),
('Art. ', True),
('<span class="x">', False),
('</span>', False),
('<b><i>x</b></i>', False),
])
def test_html_fragment_is_balanced(valor, esperado):
assert html_fragment_is_balanced(valor) is esperado
@pytest.mark.django_db
def test_tipo_dispositivo_form_rejeita_fragmento_desbalanceado():
td = baker.make(TipoDispositivo)
dados = {f: getattr(td, f) or '' for f in TipoDispositivoForm.Meta.fields}
dados['rotulo_prefixo_html'] = '<span class="rotulo">'
dados['rotulo_sufixo_html'] = '</span>'
form = TipoDispositivoForm(data=dados, instance=td)
assert not form.is_valid()
assert 'rotulo_prefixo_html' in form.errors
assert 'rotulo_sufixo_html' in form.errors
dados['rotulo_prefixo_html'] = '<br/>'
dados['rotulo_sufixo_html'] = ''
form = TipoDispositivoForm(data=dados, instance=td)
assert 'rotulo_prefixo_html' not in form.errors

28
sapl/compilacao/forms.py

@ -26,6 +26,7 @@ from sapl.compilacao.models import (NOTAS_PUBLICIDADE_CHOICES,
from sapl.compilacao.utils import DISPOSITIVO_SELECT_RELATED from sapl.compilacao.utils import DISPOSITIVO_SELECT_RELATED
from sapl.crispy_layout_mixin import SaplFormHelper from sapl.crispy_layout_mixin import SaplFormHelper
from sapl.crispy_layout_mixin import SaplFormLayout, to_column, to_row from sapl.crispy_layout_mixin import SaplFormLayout, to_column, to_row
from sapl.sanitize import RICH_TEXT_FIELDS, html_fragment_is_balanced
from sapl.utils import YES_NO_CHOICES, FileFieldCheckMixin from sapl.utils import YES_NO_CHOICES, FileFieldCheckMixin
@ -51,6 +52,33 @@ ta_error_messages = {
} }
class TipoDispositivoForm(ModelForm):
class Meta:
model = TipoDispositivo
fields = ['rotulo_ordinal', 'formato_variacao0',
'rotulo_separador_variacao01', 'formato_variacao1',
'rotulo_separador_variacao12', 'formato_variacao2',
'rotulo_separador_variacao23', 'formato_variacao3',
'rotulo_separador_variacao34', 'formato_variacao4',
'rotulo_separador_variacao45', 'formato_variacao5',
'rotulo_prefixo_html', 'rotulo_sufixo_html',
'texto_prefixo_html', 'texto_sufixo_html',
'nota_automatica_prefixo_html',
'nota_automatica_sufixo_html']
def clean(self):
cleaned_data = super().clean()
for fieldname in RICH_TEXT_FIELDS['compilacao.TipoDispositivo']:
value = cleaned_data.get(fieldname)
if value and not html_fragment_is_balanced(value):
self.add_error(fieldname, _(
'Toda tag aberta neste campo deve ser fechada nele '
'mesmo; tags divididas entre prefixo e sufixo são '
'descartadas ao salvar.'))
return cleaned_data
class TipoTaForm(ModelForm): class TipoTaForm(ModelForm):
sigla = forms.CharField( sigla = forms.CharField(
label=TipoTextoArticulado._meta.get_field( label=TipoTextoArticulado._meta.get_field(

4
sapl/compilacao/views.py

@ -39,7 +39,8 @@ from sapl.compilacao.forms import (DispositivoDefinidorVigenciaForm,
DispositivoRegistroRevogacaoForm, DispositivoRegistroRevogacaoForm,
DispositivoSearchModalForm, NotaForm, DispositivoSearchModalForm, NotaForm,
PublicacaoForm, TaForm, PublicacaoForm, TaForm,
TextNotificacoesForm, TipoTaForm, VideForm) TextNotificacoesForm, TipoDispositivoForm,
TipoTaForm, VideForm)
from sapl.compilacao.models import (STATUS_TA_EDITION, STATUS_TA_PRIVATE, from sapl.compilacao.models import (STATUS_TA_EDITION, STATUS_TA_PRIVATE,
STATUS_TA_PUBLIC, Dispositivo, Nota, STATUS_TA_PUBLIC, Dispositivo, Nota,
PerfilEstruturalTextoArticulado, PerfilEstruturalTextoArticulado,
@ -85,6 +86,7 @@ class TipoDispositivoCrud(CrudAux):
class UpdateView(CrudAux.UpdateView): class UpdateView(CrudAux.UpdateView):
layout_key = 'TipoDispositivoUpdate' layout_key = 'TipoDispositivoUpdate'
form_class = TipoDispositivoForm
class ListView(CrudAux.ListView): class ListView(CrudAux.ListView):
paginate_by = 100 paginate_by = 100

12
sapl/crispy_layout_mixin.py

@ -8,9 +8,12 @@ from django.contrib.contenttypes.models import ContentType
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.utils import formats from django.utils import formats
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.html import escape
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
import yaml import yaml
from sapl.sanitize import sanitize_html, sanitize_scope
def heads_and_tails(list_of_lists): def heads_and_tails(list_of_lists):
for alist in list_of_lists: for alist in list_of_lists:
@ -167,7 +170,14 @@ def get_field_display(obj, fieldname):
args=(value.id,)), args=(value.id,)),
value) value)
elif 'TextField' in str_type_from_field: elif 'TextField' in str_type_from_field:
display = value.replace('\n', '<br/>') scope = sanitize_scope(obj._meta.model, fieldname)
if scope == 'rich':
display = sanitize_html(value, rich=True)
elif scope == 'plain':
display = escape(sanitize_html(value))
else:
display = escape(value)
display = display.replace('\n', '<br/>')
display = '<div class="dont-break-out">{}</div>'.format(display) display = '<div class="dont-break-out">{}</div>'.format(display)
else: else:
display = str(value) display = str(value)

182
sapl/sanitize.py

@ -0,0 +1,182 @@
"""Sanitização de HTML/JavaScript nos campos de texto livre do SAPL.
Módulo sem dependências internas do SAPL de propósito: é importado por
``sapl.crispy_layout_mixin``, ``sapl.base.receivers`` e pelos templatetags,
e ``sapl.utils`` já importa ``sapl.crispy_layout_mixin``.
Duas políticas:
* ``plain`` — remove toda a marcação e guarda texto puro, com as entidades
já decodificadas: o escape fica por conta da renderização. É o padrão para
qualquer ``TextField``.
* ``rich`` — allowlist para os campos editados via TinyMCE, que contêm HTML
legítimo (negrito, listas, tabelas e links).
A ``rich`` é idempotente, o que permite aplicá-la tanto no ``pre_save`` quanto
na renderização. A ``plain`` só não é idempotente para texto que seja HTML
codificado em entidades (``&lt;b&gt;``): a primeira passada decodifica, a
segunda remove a tag. Na renderização, campos ``plain`` são escapados em vez
de re-sanitizados.
"""
import html
from html.parser import HTMLParser
import nh3
SANITIZE_RICH_TAGS = {
'p', 'br', 'hr', 'div', 'span',
'b', 'strong', 'i', 'em', 'u', 's', 'strike', 'sub', 'sup',
'ul', 'ol', 'li', 'dl', 'dt', 'dd', 'blockquote', 'pre', 'code',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'table', 'thead', 'tbody', 'tfoot', 'tr', 'th', 'td', 'caption',
'col', 'colgroup',
'a', 'img',
}
# 'style' mantém o alinhamento produzido pelos botões do TinyMCE;
# 'target' mantém o "abrir em nova aba" dos links já cadastrados.
# 'rel' não pode entrar aqui: o nh3 aborta se a tag 'a' declarar 'rel'
# ao mesmo tempo em que link_rel está definido — ele mesmo escreve o atributo.
SANITIZE_RICH_ATTRS = {
'*': {'style', 'class', 'align', 'title', 'dir', 'lang'},
'a': {'href', 'target', 'name'},
'img': {'src', 'alt', 'width', 'height'},
'td': {'colspan', 'rowspan', 'headers'},
'th': {'colspan', 'rowspan', 'scope', 'headers'},
'col': {'span'},
'colgroup': {'span'},
'table': {'border', 'cellpadding', 'cellspacing', 'summary'},
}
SANITIZE_URL_SCHEMES = {'http', 'https', 'mailto', 'tel'}
# O conteúdo destas tags é descartado junto com a tag. Sem isso o texto de
# dentro de um <script> sobreviveria como texto solto.
SANITIZE_CLEAN_CONTENT_TAGS = {'script', 'style'}
# Campos que guardam HTML legítimo, indexados por '<app_label>.<Model>'.
# Os de sessao e compilacao.Dispositivo são editados no TinyMCE; os de
# compilacao.TipoDispositivo são fragmentos de template configurados por
# administradores.
RICH_TEXT_FIELDS = {
'base.CasaLegislativa': {'informacao_geral'},
'norma.NormaRelacionada': {'resumo'},
'parlamentares.Legislatura': {'observacao'},
'parlamentares.Parlamentar': {'biografia'},
'sessao.ExpedienteSessao': {'conteudo'},
'sessao.OcorrenciaSessao': {'conteudo'},
'sessao.ConsideracoesFinais': {'conteudo'},
'compilacao.Dispositivo': {'texto', 'texto_atualizador'},
'compilacao.TipoDispositivo': {
'rotulo_prefixo_html', 'rotulo_sufixo_html',
'texto_prefixo_html', 'texto_sufixo_html',
'nota_automatica_prefixo_html', 'nota_automatica_sufixo_html',
},
}
# Campos que não devem ser tocados em hipótese alguma.
SANITIZE_EXEMPT_FIELDS = {
# xml é XML fornecido pela equipe do LexML; já é escapado em pretty_xml
'lexml.LexmlProvedor': {'xml'},
# rodape_global é interpolado dentro de um content: de CSS
'compilacao.TipoTextoArticulado': {'rodape_global'},
}
def model_key(model):
return '{}.{}'.format(model._meta.app_label, model.__name__)
def sanitize_scope(model, fieldname):
"""Retorna 'exempt', 'rich' ou 'plain' para um campo de um modelo."""
key = model_key(model)
if fieldname in SANITIZE_EXEMPT_FIELDS.get(key, ()):
return 'exempt'
if fieldname in RICH_TEXT_FIELDS.get(key, ()):
return 'rich'
return 'plain'
def sanitize_html(value, rich=False):
"""Remove HTML/JavaScript perigoso de ``value``.
Com ``rich=False`` toda a marcação é removida e apenas o texto sobra,
sem escape: ``&`` continua ``&``.
Com ``rich=True`` aplica-se a allowlist: links são preservados, mas
esquemas de URL fora de SANITIZE_URL_SCHEMES (javascript:, data:) e
manipuladores de evento (onclick, onerror) são descartados.
"""
if not value:
return value
if not isinstance(value, str):
value = str(value)
if rich:
return nh3.clean(
value,
tags=SANITIZE_RICH_TAGS,
attributes=SANITIZE_RICH_ATTRS,
url_schemes=SANITIZE_URL_SCHEMES,
clean_content_tags=SANITIZE_CLEAN_CONTENT_TAGS,
link_rel='noopener noreferrer',
strip_comments=True)
return html.unescape(nh3.clean(
value,
tags=set(),
attributes={},
clean_content_tags=SANITIZE_CLEAN_CONTENT_TAGS,
link_rel=None,
strip_comments=True))
def sanitize_field(model, fieldname, value):
"""Sanitiza ``value`` conforme a política do campo.
Campos isentos atravessam sem modificação.
"""
scope = sanitize_scope(model, fieldname)
if scope == 'exempt':
return value
return sanitize_html(value, rich=(scope == 'rich'))
_VOID_TAGS = {
'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link',
'meta', 'param', 'source', 'track', 'wbr',
}
class _BalanceChecker(HTMLParser):
def __init__(self):
super().__init__(convert_charrefs=True)
self.stack = []
self.balanced = True
def handle_starttag(self, tag, attrs):
if tag not in _VOID_TAGS:
self.stack.append(tag)
def handle_startendtag(self, tag, attrs):
pass
def handle_endtag(self, tag):
if tag in _VOID_TAGS:
return
if not self.stack or self.stack.pop() != tag:
self.balanced = False
def html_fragment_is_balanced(value):
"""Indica se toda tag aberta em ``value`` é fechada nele mesmo.
Fragmentos desbalanceados (``<span>`` num campo, ``</span>`` em outro)
são reescritos pela sanitização ``rich``, que fecha ou descarta as tags.
"""
checker = _BalanceChecker()
checker.feed(value)
checker.close()
return checker.balanced and not checker.stack

2
sapl/templates/comissoes/materias_em_tramitacao.html

@ -38,7 +38,7 @@
{{ materia_em_tramitacao.materia.ementa|dont_break_out }} {{ materia_em_tramitacao.materia.ementa|dont_break_out }}
</td> </td>
<td> <td>
{{ materia_em_tramitacao.tramitacao.status.descricao|linebreaksbr|safe }} {{ materia_em_tramitacao.tramitacao.status.descricao|linebreaksbr }}
</td> </td>
<td> <td>
{% if materia_em_tramitacao.tramitacao.data_fim_prazo %} {% if materia_em_tramitacao.tramitacao.data_fim_prazo %}

4
sapl/templates/compilacao/dispositivo_form_search_fragment_child.html

@ -1,4 +1,4 @@
{% load i18n compilacao_filters %} {% load i18n compilacao_filters common_tags %}
<li> <li>
<div class="iteminput"> <div class="iteminput">
@ -30,7 +30,7 @@
{{ df.tipo_dispositivo.rotulo_prefixo_html|safe }} {{ df.tipo_dispositivo.rotulo_prefixo_html|safe }}
<a name="{{df.pk}}" title="{{df.pk}}">{% if df.rotulo or df.nivel == 1%}{{ df.rotulo }}{%else%}[{{ df|nomenclatura}} {% trans "de" %} {{ df.dispositivo_pai.rotulo }}] - {% endif %}</a> <a name="{{df.pk}}" title="{{df.pk}}">{% if df.rotulo or df.nivel == 1%}{{ df.rotulo }}{%else%}[{{ df|nomenclatura}} {% trans "de" %} {{ df.dispositivo_pai.rotulo }}] - {% endif %}</a>
{{ df.tipo_dispositivo.rotulo_sufixo_html|safe }} {{ df.tipo_dispositivo.rotulo_sufixo_html|safe }}
<span class="dtxt" id="d{% if not df.dispositivo_subsequente_id and df.dispositivo_substituido_id %}a{% endif %}{{df.pk}}" pks="{{df.dispositivo_substituido_id|default:''}}" pk="{{df.pk}}">{{ df.tipo_dispositivo.texto_prefixo_html|safe }}{%if df.texto %}{{ df.texto|safe }}{%else%}{%if not df.tipo_dispositivo.dispositivo_de_articulacao %}&nbsp;{% endif %}{% endif %}</span> <span class="dtxt" id="d{% if not df.dispositivo_subsequente_id and df.dispositivo_substituido_id %}a{% endif %}{{df.pk}}" pks="{{df.dispositivo_substituido_id|default:''}}" pk="{{df.pk}}">{{ df.tipo_dispositivo.texto_prefixo_html|safe }}{%if df.texto %}{{ df.texto|sanitize }}{%else%}{%if not df.tipo_dispositivo.dispositivo_de_articulacao %}&nbsp;{% endif %}{% endif %}</span>
{% if df.ta_publicado_id %} {% if df.ta_publicado_id %}
<a class="nota-alteracao" href="{% url 'sapl.compilacao:ta_text' df.ta_publicado.pk %}#{{df.dispositivo_atualizador_id}}"> <a class="nota-alteracao" href="{% url 'sapl.compilacao:ta_text' df.ta_publicado.pk %}#{{df.dispositivo_atualizador_id}}">
{{ df.tipo_dispositivo.nota_automatica_prefixo_html|safe }} {{ df.tipo_dispositivo.nota_automatica_prefixo_html|safe }}

2
sapl/templates/compilacao/layout/dispositivo_checkbox.html

@ -37,7 +37,7 @@
{{ choice.1.tipo_dispositivo.rotulo_prefixo_html|safe }} {{ choice.1.tipo_dispositivo.rotulo_prefixo_html|safe }}
<a name="{{choice.1.pk}}" title="{{choice.1.pk}}">{% if choice.1.rotulo %}{{ choice.1.rotulo }}{%else%}[{{ choice.1|nomenclatura}}{% if choice.1.dispositivo_pai %} {% trans "de" %} {{ choice.1.dispositivo_pai.rotulo }}{% endif %}] - {% endif %}</a> <a name="{{choice.1.pk}}" title="{{choice.1.pk}}">{% if choice.1.rotulo %}{{ choice.1.rotulo }}{%else%}[{{ choice.1|nomenclatura}}{% if choice.1.dispositivo_pai %} {% trans "de" %} {{ choice.1.dispositivo_pai.rotulo }}{% endif %}] - {% endif %}</a>
{{ choice.1.tipo_dispositivo.rotulo_sufixo_html|safe }} {{ choice.1.tipo_dispositivo.rotulo_sufixo_html|safe }}
<span class="dtxt" id="d{% if not choice.1.dispositivo_subsequente_id and choice.1.dispositivo_substituido_id %}a{% endif %}{{choice.1.pk}}" pks="{{choice.1.dispositivo_substituido_id|default:''}}" pk="{{choice.1.pk}}">{{ choice.1.tipo_dispositivo.texto_prefixo_html|safe }}{%if choice.1.texto %}{{ choice.1.texto|safe }}{%else%}{%if not choice.1.tipo_dispositivo.dispositivo_de_articulacao %}&nbsp;{% endif %}{% endif %}</span> <span class="dtxt" id="d{% if not choice.1.dispositivo_subsequente_id and choice.1.dispositivo_substituido_id %}a{% endif %}{{choice.1.pk}}" pks="{{choice.1.dispositivo_substituido_id|default:''}}" pk="{{choice.1.pk}}">{{ choice.1.tipo_dispositivo.texto_prefixo_html|safe }}{%if choice.1.texto %}{{ choice.1.texto|sanitize }}{%else%}{%if not choice.1.tipo_dispositivo.dispositivo_de_articulacao %}&nbsp;{% endif %}{% endif %}</span>
{% if choice.1.ta_publicado_id and not choice.1.tipo_dispositivo.dispositivo_de_articulacao %} {% if choice.1.ta_publicado_id and not choice.1.tipo_dispositivo.dispositivo_de_articulacao %}
<a class="nota-alteracao" target="_blank" href="{% url 'sapl.compilacao:ta_text' choice.1.ta_publicado.pk %}#{{choice.1.dispositivo_atualizador_id}}"> <a class="nota-alteracao" target="_blank" href="{% url 'sapl.compilacao:ta_text' choice.1.ta_publicado.pk %}#{{choice.1.dispositivo_atualizador_id}}">
{{ choice.1.tipo_dispositivo.nota_automatica_prefixo_html|safe }} {{ choice.1.tipo_dispositivo.nota_automatica_prefixo_html|safe }}

2
sapl/templates/compilacao/layout/dispositivo_radio.html

@ -35,7 +35,7 @@
<span class="dtxt" <span class="dtxt"
id="d{% if not dpt.dispositivo_subsequente_id and dpt.dispositivo_substituido_id %}a{% endif %}{{dpt.pk}}" id="d{% if not dpt.dispositivo_subsequente_id and dpt.dispositivo_substituido_id %}a{% endif %}{{dpt.pk}}"
pks="{{dpt.dispositivo_substituido_id|default:''}}" pks="{{dpt.dispositivo_substituido_id|default:''}}"
pk="{{dpt.pk}}">{{ dpt.tipo_dispositivo.texto_prefixo_html|safe }}{%if dpt.texto %}{{ dpt.texto|safe }}{%else%}{%if not dpt.tipo_dispositivo.dispositivo_de_articulacao %}&nbsp;{% endif %}{% endif %}</span> pk="{{dpt.pk}}">{{ dpt.tipo_dispositivo.texto_prefixo_html|safe }}{%if dpt.texto %}{{ dpt.texto|sanitize }}{%else%}{%if not dpt.tipo_dispositivo.dispositivo_de_articulacao %}&nbsp;{% endif %}{% endif %}</span>
{% if dpt.ta_publicado_id and not dpt.tipo_dispositivo.dispositivo_de_articulacao %} {% if dpt.ta_publicado_id and not dpt.tipo_dispositivo.dispositivo_de_articulacao %}
<a class="nota-alteracao" href="{%url 'sapl.compilacao:ta_text' dpt.ta_publicado.pk %}#{{dpt.dispositivo_atualizador_id}}"> <a class="nota-alteracao" href="{%url 'sapl.compilacao:ta_text' dpt.ta_publicado.pk %}#{{dpt.dispositivo_atualizador_id}}">
{{ dpt.tipo_dispositivo.nota_automatica_prefixo_html|safe }} {{ dpt.tipo_dispositivo.nota_automatica_prefixo_html|safe }}

2
sapl/templates/compilacao/text_edit_bloco.html

@ -43,7 +43,7 @@
{% if not node.dpt.texto and not node.td.dispositivo_de_articulacao %} {% if not node.dpt.texto and not node.td.dispositivo_de_articulacao %}
<span class="semtexto">({{node.td}} sem texto)</span> <span class="semtexto">({{node.td}} sem texto)</span>
{%else%} {%else%}
{% if node.da and node.dpt.texto_atualizador and node in node.da.alts%}{{ node.dpt.texto_atualizador|safe }}{% else %}{{ node.dpt.texto|safe }}{% endif %} {% if node.da and node.dpt.texto_atualizador and node in node.da.alts%}{{ node.dpt.texto_atualizador|sanitize }}{% else %}{{ node.dpt.texto|sanitize }}{% endif %}
{%endif%} {%endif%}
{% if node.na %} {% if node.na %}
<a class="dpt-link nota-alteracao" href="{% url 'sapl.compilacao:ta_text_edit' node.da.dpt.ta_id %}#{{node.da.dpt.pk}}" title="{% trans 'Abrir Edição do Dispositivo Alterador'%}"> <a class="dpt-link nota-alteracao" href="{% url 'sapl.compilacao:ta_text_edit' node.da.dpt.ta_id %}#{{node.da.dpt.pk}}" title="{% trans 'Abrir Edição do Dispositivo Alterador'%}">

2
sapl/templates/compilacao/text_edit_blocoalteracao.html

@ -5,7 +5,7 @@
<div class="dpt" id="dpt{{ch.id}}" pk="{{ch.id}}" > <div class="dpt" id="dpt{{ch.id}}" pk="{{ch.id}}" >
<div class="{{ ch.tipo_dispositivo.class_css }}" id="id{{ch.id}}" nivel="{{ch.nivel}}" style="margin: 0px;"> <div class="{{ ch.tipo_dispositivo.class_css }}" id="id{{ch.id}}" nivel="{{ch.nivel}}" style="margin: 0px;">
<div class="de-compilacao" id="id{{ch.id}}" pk="{{ch.pk}}" ordem="{{ch.ordem}}" name="{{ch.pk}}" title="{{ch.pk}} - {{ch.ordem}}"> <div class="de-compilacao" id="id{{ch.id}}" pk="{{ch.pk}}" ordem="{{ch.ordem}}" name="{{ch.pk}}" title="{{ch.pk}} - {{ch.ordem}}">
{{ ch.tipo_dispositivo.rotulo_prefixo_html|safe }}<a name="{{ch.pk}}" href="{% url 'sapl.compilacao:ta_text_edit' ch.ta.pk %}#{{ch.pk}}">{{ ch.rotulo }}</a>{{ ch.tipo_dispositivo.rotulo_sufixo_html|safe }}{{ ch.tipo_dispositivo.texto_prefixo_html|safe }}{{ ch.texto|safe }} {{ ch.tipo_dispositivo.rotulo_prefixo_html|safe }}<a name="{{ch.pk}}" href="{% url 'sapl.compilacao:ta_text_edit' ch.ta.pk %}#{{ch.pk}}">{{ ch.rotulo }}</a>{{ ch.tipo_dispositivo.rotulo_sufixo_html|safe }}{{ ch.tipo_dispositivo.texto_prefixo_html|safe }}{{ ch.texto|sanitize }}
</div> </div>
</div> </div>
</div> </div>

2
sapl/templates/compilacao/text_list_bloco.html

@ -32,7 +32,7 @@
{% endif %} {% endif %}
<div class="dtxt" id="d{% if not dpt.dispositivo_subsequente_id and dpt.dispositivo_substituido_id %}a{% endif %}{{dpt.pk}}" pks="{{dpt.dispositivo_substituido_id|default:''}}" pk="{{dpt.pk}}">{{ dpt.tipo_dispositivo.texto_prefixo_html|safe }}{%if dpt.texto %}{{ dpt.texto|safe }}{%else%}{%if not dpt.tipo_dispositivo.dispositivo_de_articulacao %}&nbsp;{% endif %}{% endif %}</div> <div class="dtxt" id="d{% if not dpt.dispositivo_subsequente_id and dpt.dispositivo_substituido_id %}a{% endif %}{{dpt.pk}}" pks="{{dpt.dispositivo_substituido_id|default:''}}" pk="{{dpt.pk}}">{{ dpt.tipo_dispositivo.texto_prefixo_html|safe }}{%if dpt.texto %}{{ dpt.texto|sanitize }}{%else%}{%if not dpt.tipo_dispositivo.dispositivo_de_articulacao %}&nbsp;{% endif %}{% endif %}</div>
{% if dpt.ta_publicado_id %} {% if dpt.ta_publicado_id %}
<a class="nota-alteracao" href="{%url 'sapl.compilacao:ta_text' dpt.ta_publicado.pk %}#{{dpt.dispositivo_atualizador_id}}"> <a class="nota-alteracao" href="{%url 'sapl.compilacao:ta_text' dpt.ta_publicado.pk %}#{{dpt.dispositivo_atualizador_id}}">
{{ dpt.tipo_dispositivo.nota_automatica_prefixo_html|safe }} {{ dpt.tipo_dispositivo.nota_automatica_prefixo_html|safe }}

6
sapl/templates/compilacao/text_list_blocoalteracao.html

@ -19,15 +19,15 @@
{% if ch.rotulo %} {% if ch.rotulo %}
<a name="{{ch.pk}}" href="{% url 'sapl.compilacao:ta_text' ch.ta.pk %}#{{ch.pk}}">{{ ch.rotulo }}</a> <a name="{{ch.pk}}" href="{% url 'sapl.compilacao:ta_text' ch.ta.pk %}#{{ch.pk}}">{{ ch.rotulo }}</a>
{{ ch.tipo_dispositivo.rotulo_sufixo_html|safe }} {{ ch.tipo_dispositivo.rotulo_sufixo_html|safe }}
{{ ch.tipo_dispositivo.texto_prefixo_html|safe }}{% if ch.texto_atualizador %}{{ ch.texto_atualizador|safe }}{%else%}{{ ch.texto|safe }}{% endif %} {{ ch.tipo_dispositivo.texto_prefixo_html|safe }}{% if ch.texto_atualizador %}{{ ch.texto_atualizador|sanitize }}{%else%}{{ ch.texto|sanitize }}{% endif %}
{% elif not ch.rotulo and not ch.auto_inserido %} {% elif not ch.rotulo and not ch.auto_inserido %}
<a name="{{ch.pk}}" href="{% url 'sapl.compilacao:ta_text' ch.ta.pk %}#{{ch.pk}}"> <a name="{{ch.pk}}" href="{% url 'sapl.compilacao:ta_text' ch.ta.pk %}#{{ch.pk}}">
{{ ch.tipo_dispositivo.rotulo_sufixo_html|safe }} {{ ch.tipo_dispositivo.rotulo_sufixo_html|safe }}
{{ ch.tipo_dispositivo.texto_prefixo_html|safe }}{% if ch.texto_atualizador %}{{ ch.texto_atualizador|safe }}{%else%}{{ ch.texto|safe }}{% endif %} {{ ch.tipo_dispositivo.texto_prefixo_html|safe }}{% if ch.texto_atualizador %}{{ ch.texto_atualizador|sanitize }}{%else%}{{ ch.texto|sanitize }}{% endif %}
</a> </a>
{% else %} {% else %}
{{ ch.tipo_dispositivo.rotulo_sufixo_html|safe }} {{ ch.tipo_dispositivo.rotulo_sufixo_html|safe }}
{{ ch.tipo_dispositivo.texto_prefixo_html|safe }}{% if ch.texto_atualizador %}{{ ch.texto_atualizador|safe }}{%else%}{{ ch.texto|safe }}{% endif %} {{ ch.tipo_dispositivo.texto_prefixo_html|safe }}{% if ch.texto_atualizador %}{{ ch.texto_atualizador|sanitize }}{%else%}{{ ch.texto|sanitize }}{% endif %}
{% endif %} {% endif %}
{% if ch.imagem %} {% if ch.imagem %}

2
sapl/templates/compilacao/text_notificacoes.html

@ -23,7 +23,7 @@
<span class="dtxt" <span class="dtxt"
id="d{% if not dpt.dispositivo_subsequente_id and dpt.dispositivo_substituido_id %}a{% endif %}{{dpt.pk}}" id="d{% if not dpt.dispositivo_subsequente_id and dpt.dispositivo_substituido_id %}a{% endif %}{{dpt.pk}}"
pks="{{dpt.dispositivo_substituido_id|default:''}}" pks="{{dpt.dispositivo_substituido_id|default:''}}"
pk="{{dpt.pk}}">{{ dpt.tipo_dispositivo.texto_prefixo_html|safe }}{%if dpt.texto %}{{ dpt.texto|safe }}{%else%}{%if not dpt.tipo_dispositivo.dispositivo_de_articulacao %}&nbsp;{% endif %}{% endif %}</span> pk="{{dpt.pk}}">{{ dpt.tipo_dispositivo.texto_prefixo_html|safe }}{%if dpt.texto %}{{ dpt.texto|sanitize }}{%else%}{%if not dpt.tipo_dispositivo.dispositivo_de_articulacao %}&nbsp;{% endif %}{% endif %}</span>
{%if dpt.ta_publicado_id %} {%if dpt.ta_publicado_id %}
<a class="nota-alteracao clearfix" href="{%url 'sapl.compilacao:ta_text' dpt.ta_publicado.pk %}#{{dpt.dispositivo_atualizador_id}}"> <a class="nota-alteracao clearfix" href="{%url 'sapl.compilacao:ta_text' dpt.ta_publicado.pk %}#{{dpt.dispositivo_atualizador_id}}">

2
sapl/templates/compilacao/textoarticulado_detail.html

@ -75,7 +75,7 @@
<div class="col-md-12"> <div class="col-md-12">
<div id="div_id_ementa" class="holder"> <div id="div_id_ementa" class="holder">
<label>{% field_verbose_name object 'ementa' %}</label> <label>{% field_verbose_name object 'ementa' %}</label>
<p>{{ object.ementa|safe}}</p> <p>{{ object.ementa}}</p>
</div> </div>
</div> </div>
</div> </div>

2
sapl/templates/materia/acompanhamento_materia.html

@ -19,7 +19,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-12"><b>Ementa:</b> {{materia.ementa|safe}}</div> <div class="col-md-12"><b>Ementa:</b> {{materia.ementa}}</div>
</div> </div>
{% crispy form %} {% crispy form %}

2
sapl/templates/materia/materialegislativa_filter.html

@ -153,7 +153,7 @@
{% if m.tramitacao_set.first.data_tramitacao %} {% if m.tramitacao_set.first.data_tramitacao %}
<strong>Data da última Tramitação:</strong> &nbsp;{{m.tramitacao_set.first.data_tramitacao}}</br> <strong>Data da última Tramitação:</strong> &nbsp;{{m.tramitacao_set.first.data_tramitacao}}</br>
{% if m.tramitacao_set.first.texto %} {% if m.tramitacao_set.first.texto %}
<strong>Última Ação:</strong> &nbsp; {{m.tramitacao_set.first.texto|safe}}</br> <strong>Última Ação:</strong> &nbsp; {{m.tramitacao_set.first.texto}}</br>
{% endif %} {% endif %}
{% endif %} {% endif %}
{% if m.anexo_de.exists %} {% if m.anexo_de.exists %}

2
sapl/templates/materia/resumo_detail_materia.html

@ -3,5 +3,5 @@
<div class="col-md-4"><b>Número: </b>{{object.numero}}</div> <div class="col-md-4"><b>Número: </b>{{object.numero}}</div>
<div class="col-md-4"><b>Ano: </b>{{object.ano}}</div> <div class="col-md-4"><b>Ano: </b>{{object.ano}}</div>
</div> </div>
<b>Ementa:</b> {{object.ementa|safe}} <b>Ementa:</b> {{object.ementa}}
<br /><br /> <br /><br />

2
sapl/templates/norma/normajuridica_filter.html

@ -62,7 +62,7 @@
{% endif %} {% endif %}
<strong>Ementa:</strong><br> <strong>Ementa:</strong><br>
{{n.ementa|safe}}<br> {{n.ementa}}<br>
{% if n.texto_articulado.exists and not n.texto_articulado.first.privacidade %} {% if n.texto_articulado.exists and not n.texto_articulado.first.privacidade %}
<a href="{% url 'sapl.norma:normajuridica_detail' n.id %}"> <a href="{% url 'sapl.norma:normajuridica_detail' n.id %}">

2
sapl/templates/parlamentares/parlamentar_perfil_publico.html

@ -61,7 +61,7 @@
<div class="col-sm-8"> <div class="col-sm-8">
<div id="div_biografia" class="form-group"> <div id="div_biografia" class="form-group">
<p><b>Biografia: </b> &nbsp {{object.biografia|safe}}</p> <p><b>Biografia: </b> &nbsp {{object.biografia|sanitize}}</p>
</div> </div>
</div> </div>
</div> </div>

2
sapl/templates/protocoloadm/acompanhamento_documento.html

@ -19,7 +19,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-12"><b>Assunto:</b> {{documento.assunto|safe}}</div> <div class="col-md-12"><b>Assunto:</b> {{documento.assunto}}</div>
</div> </div>
{% crispy form %} {% crispy form %}

2
sapl/templates/protocoloadm/documentoadministrativo_filter.html

@ -48,7 +48,7 @@
<a href="{% url 'sapl.protocoloadm:documentoadministrativo_detail' d.id %}"><strong>{{d}}</strong></a></br> <a href="{% url 'sapl.protocoloadm:documentoadministrativo_detail' d.id %}"><strong>{{d}}</strong></a></br>
<strong>Interessado:</strong>&nbsp;{{ d.interessado|default_if_none:"Não informado"}} <strong>Interessado:</strong>&nbsp;{{ d.interessado|default_if_none:"Não informado"}}
</br> </br>
<strong>Assunto:</strong>&nbsp;{{ d.assunto|safe }} <strong>Assunto:</strong>&nbsp;{{ d.assunto }}
</br> </br>
{% if d.protocolo %} {% if d.protocolo %}
<strong>Protocolo:</strong>&nbsp;<a href="{% url 'sapl.protocoloadm:protocolo_mostrar' d.protocolo.id %}">{{ d.protocolo}}</a></br> <strong>Protocolo:</strong>&nbsp;<a href="{% url 'sapl.protocoloadm:protocolo_mostrar' d.protocolo.id %}">{{ d.protocolo}}</a></br>

2
sapl/templates/relatorios/RelatorioMateriasPorAutor_filter.html

@ -54,7 +54,7 @@
{{materia.tipo.sigla}} {{materia.numero}}/{{materia.ano}} {{materia.tipo.sigla}} {{materia.numero}}/{{materia.ano}}
</a> </a>
</td> </td>
<td>{% autoescape off %}{{materia.ementa}}<br>{{materia.observacao}}{% endautoescape %}</td> <td>{{materia.ementa}}<br>{{materia.observacao}}</td>
<td> <td>
{% for autoria in materia.autoria_set.all %} {% for autoria in materia.autoria_set.all %}
{% if autoria.autor != autor %} {% if autoria.autor != autor %}

2
sapl/templates/relatorios/RelatorioNormasPorAutor_filter.html

@ -52,7 +52,7 @@
<td><a href="{% url 'sapl.norma:normajuridica_detail' norma.pk %}"> <td><a href="{% url 'sapl.norma:normajuridica_detail' norma.pk %}">
{{norma.tipo.sigla}} {{norma.numero}}/{{norma.ano}} {{norma.tipo.sigla}} {{norma.numero}}/{{norma.ano}}
</a></td> </a></td>
<td>{% autoescape off %}{{norma.ementa}}<br>{{norma.observacao}}{% endautoescape %}</td> <td>{{norma.ementa}}<br>{{norma.observacao}}</td>
<td> <td>
{% for autoria in norma.autorianorma_set.all %} {% for autoria in norma.autorianorma_set.all %}
{% if autoria.autor != autor %} {% if autoria.autor != autor %}

3
sapl/templates/relatorios/blocos_sessao_plenaria/consideracoes_finais.html

@ -1,4 +1,5 @@
{% load common_tags %}
<h2 class="gray-title">Considerações Finais</h2> <h2 class="gray-title">Considerações Finais</h2>
{% for c in lst_consideracoes%} {% for c in lst_consideracoes%}
<p>{{c|striptags|safe}}</p> <p>{{c|sanitize|striptags}}</p>
{% endfor %} {% endfor %}

3
sapl/templates/relatorios/blocos_sessao_plenaria/expedientes.html

@ -1,5 +1,6 @@
{% load common_tags %}
<h2 class="gray-title">Expedientes</h2> <h2 class="gray-title">Expedientes</h2>
{% for expediente in lst_expedientes%} {% for expediente in lst_expedientes%}
<h3>{{expediente.nom_expediente}}</h3> <h3>{{expediente.nom_expediente}}</h3>
<div style="margin-bottom: 1cm">{{expediente.txt_expediente|safe}}</div> <div style="margin-bottom: 1cm">{{expediente.txt_expediente|sanitize}}</div>
{% endfor%} {% endfor%}

3
sapl/templates/relatorios/blocos_sessao_plenaria/ocorrencias_da_sessao.html

@ -1,4 +1,5 @@
{% load common_tags %}
<h2 class="gray-title">Ocorrências da Sessão</h2> <h2 class="gray-title">Ocorrências da Sessão</h2>
{% for o in lst_ocorrencias%} {% for o in lst_ocorrencias%}
<p>{{o.conteudo|striptags|safe}}</p> <p>{{o.conteudo|sanitize|striptags}}</p>
{% endfor %} {% endfor %}

2
sapl/templates/relatorios/relatorio_doc_administrativos.html

@ -13,7 +13,7 @@
<strong><a href="{% url 'sapl.protocoloadm:documentoadministrativo_detail' d.id %}">{{d.tipo.sigla}} {{d.numero}}/{{d.ano}} - {{d.tipo}}</strong></a></br> <strong><a href="{% url 'sapl.protocoloadm:documentoadministrativo_detail' d.id %}">{{d.tipo.sigla}} {{d.numero}}/{{d.ano}} - {{d.tipo}}</strong></a></br>
<strong>Interessado:</strong>&nbsp;{{ d.interessado|default_if_none:"Não informado"}} <strong>Interessado:</strong>&nbsp;{{ d.interessado|default_if_none:"Não informado"}}
</br> </br>
<strong>Assunto:</strong>&nbsp;<a>{{ d.assunto|safe}}</a> <strong>Assunto:</strong>&nbsp;<a>{{ d.assunto}}</a>
</br> </br>
{% if d.protocolo %} {% if d.protocolo %}
<strong>Protocolo:</strong>&nbsp;<a href="{% url 'sapl.protocoloadm:protocolo_mostrar' d.protocolo.id %}">{{ d.protocolo}}</a></br> <strong>Protocolo:</strong>&nbsp;<a href="{% url 'sapl.protocoloadm:protocolo_mostrar' d.protocolo.id %}">{{ d.protocolo}}</a></br>

2
sapl/templates/relatorios/relatorio_normas_por_autor.html

@ -47,7 +47,7 @@
<td> <td>
{{norma.tipo.sigla}} {{norma.numero}}/{{norma.ano}} {{norma.tipo.sigla}} {{norma.numero}}/{{norma.ano}}
</td> </td>
<td>{% autoescape off %}{{norma.ementa}}<br>{{norma.observacao}}{% endautoescape %}</td> <td>{{norma.ementa}}<br>{{norma.observacao}}</td>
<td> <td>
{% if norma.autorianorma_set.first != norma.autorianorma_set.last %} {% if norma.autorianorma_set.first != norma.autorianorma_set.last %}
{% for autor in norma.autorianorma_set.all %} {% for autor in norma.autorianorma_set.all %}

10
sapl/templates/relatorios/relatorio_pauta_sessao.html

@ -39,7 +39,7 @@
</table> </table>
<h2 class="gray-title">Expedientes</h2> <h2 class="gray-title">Expedientes</h2>
{% for e in expedientes %} {% for e in expedientes %}
<b>{{ e.tipo }}: </b><br/><p>{{ e.conteudo|safe }}</p> <b>{{ e.tipo }}: </b><br/><p>{{ e.conteudo|sanitize }}</p>
{% endfor %} {% endfor %}
<h2 class="gray-title">Matérias do Expediente</h2> <h2 class="gray-title">Matérias do Expediente</h2>
{% if materia_expediente %} {% if materia_expediente %}
@ -53,10 +53,10 @@
<b>Autor{{ m.autor|length|pluralize:"es" }}</b>: {{ m.autor|join:', ' }} <b>Autor{{ m.autor|length|pluralize:"es" }}</b>: {{ m.autor|join:', ' }}
</td> </td>
<td style="width:60%;"> <td style="width:60%;">
{{ m.ementa|safe }} {{ m.ementa }}
{% if m.observacao %}<br><br>Obs.: {{m.observacao}} {% endif %} {% if m.observacao %}<br><br>Obs.: {{m.observacao}} {% endif %}
</td> </td>
<td style="width:20%;">{{m.situacao|linebreaksbr|safe}}</td> <td style="width:20%;">{{m.situacao|linebreaksbr}}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
@ -75,10 +75,10 @@
<b>Autor{{ m.autor|length|pluralize:"es" }}</b>: {{ m.autor|join:', ' }} <b>Autor{{ m.autor|length|pluralize:"es" }}</b>: {{ m.autor|join:', ' }}
</td> </td>
<td style="width:60%;"> <td style="width:60%;">
{{m.ementa|safe}} {{m.ementa}}
{% if m.observacao %}<br><br>Obs.: {{m.observacao}} {% endif %} {% if m.observacao %}<br><br>Obs.: {{m.observacao}} {% endif %}
</td> </td>
<td style="width:20%;">{{m.situacao|linebreaksbr|safe}}</td> <td style="width:20%;">{{m.situacao|linebreaksbr}}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>

6
sapl/templates/search/search.html

@ -51,7 +51,7 @@
{% if result.object|search_get_model == 'm' %} {% if result.object|search_get_model == 'm' %}
<p> <p>
<strong>Matéria Legislativa: </strong> <a href="{% url 'sapl.materia:materialegislativa_detail' result.object.pk %}">{{ result.object }}</a></br> <strong>Matéria Legislativa: </strong> <a href="{% url 'sapl.materia:materialegislativa_detail' result.object.pk %}">{{ result.object }}</a></br>
{{result.object.ementa|safe}}<br> {{result.object.ementa}}<br>
{% if result.object.texto_original %} {% if result.object.texto_original %}
<strong>Texto Original:</strong> <a href="{{result.object.texto_original.url}}"> Clique aqui </a></br> <strong>Texto Original:</strong> <a href="{{result.object.texto_original.url}}"> Clique aqui </a></br>
@ -64,7 +64,7 @@
{% elif result.object|search_get_model == 'd' %} {% elif result.object|search_get_model == 'd' %}
<p> <p>
<strong> Documento Acessório: </strong><a href="{% url 'sapl.materia:documentoacessorio_detail' result.object.pk %}">{{ result.object }}</a></br> <strong> Documento Acessório: </strong><a href="{% url 'sapl.materia:documentoacessorio_detail' result.object.pk %}">{{ result.object }}</a></br>
{{result.object.ementa|safe}}<br> {{result.object.ementa}}<br>
{% if result.object.arquivo %} {% if result.object.arquivo %}
<strong>Texto Original:</strong> <a href="{{result.object.arquivo.url}}"> Clique aqui </a></br> <strong>Texto Original:</strong> <a href="{{result.object.arquivo.url}}"> Clique aqui </a></br>
{% endif %} {% endif %}
@ -73,7 +73,7 @@
{% elif result.object|search_get_model == 'n' %} {% elif result.object|search_get_model == 'n' %}
<p> <p>
<strong> Norma Jurídica: </strong><a href="{% url 'sapl.norma:normajuridica_detail' result.object.pk %}">{{ result.object }}</a></br> <strong> Norma Jurídica: </strong><a href="{% url 'sapl.norma:normajuridica_detail' result.object.pk %}">{{ result.object }}</a></br>
{{ result.object.ementa|safe }}<br> {{ result.object.ementa }}<br>
{% if result.object.texto_integral %} {% if result.object.texto_integral %}
<strong>Texto Original:</strong> <a href="{{result.object.texto_integral.url}}"> Clique aqui </a></br> <strong>Texto Original:</strong> <a href="{{result.object.texto_integral.url}}"> Clique aqui </a></br>
{% endif %} {% endif %}

2
sapl/templates/sessao/adicionar_varias_materias_expediente.html

@ -65,7 +65,7 @@
<strong>Localização Atual:</strong> &nbsp;{{ m.tramitacao_set.first.unidade_tramitacao_destino|default_if_none:"Não informado" }}</br> <strong>Localização Atual:</strong> &nbsp;{{ m.tramitacao_set.first.unidade_tramitacao_destino|default_if_none:"Não informado" }}</br>
<strong>Status:</strong> &nbsp;{{ m.tramitacao_set.first.status|default_if_none:"Não informado" }}</br> <strong>Status:</strong> &nbsp;{{ m.tramitacao_set.first.status|default_if_none:"Não informado" }}</br>
<strong>Data da última Tramitação:</strong> &nbsp;{{ m.tramitacao_set.first.data_tramitacao|default_if_none:"Não informado" }}</br> <strong>Data da última Tramitação:</strong> &nbsp;{{ m.tramitacao_set.first.data_tramitacao|default_if_none:"Não informado" }}</br>
<strong>Ementa:</strong>&nbsp;{{ m.ementa|safe }}</br> <strong>Ementa:</strong>&nbsp;{{ m.ementa }}</br>
<p></p> <p></p>
<td width="20%%"> <td width="20%%">
<input type="radio" name="tipo_votacao_{{ m.id }}" id="tipo_votacao_{{ m.id }}_1" value="1"> <label for="tipo_votacao_{{ m.id }}_1">Simbólica</label> <input type="radio" name="tipo_votacao_{{ m.id }}" id="tipo_votacao_{{ m.id }}_1" value="1"> <label for="tipo_votacao_{{ m.id }}_1">Simbólica</label>

3
sapl/templates/sessao/blocos_ata/consideracoes_finais.html

@ -1,8 +1,9 @@
{% load common_tags %}
{% if object.consideracoesfinais.conteudo %} {% if object.consideracoesfinais.conteudo %}
<fieldset> <fieldset>
<p style="text-align: justify;margin-top: 0"> <p style="text-align: justify;margin-top: 0">
<strong>Considerações Finais: </strong> <strong>Considerações Finais: </strong>
{{object.consideracoesfinais.conteudo|striptags|safe}} {{object.consideracoesfinais.conteudo|sanitize|striptags}}
</p> </p>
</fieldset> </fieldset>
{% endif %} {% endif %}

3
sapl/templates/sessao/blocos_ata/expedientes.html

@ -1,3 +1,4 @@
{% load common_tags %}
{% if expedientes %} {% if expedientes %}
<fieldset> <fieldset>
<p style="text-align: justify;margin-top: 0"> <p style="text-align: justify;margin-top: 0">
@ -5,7 +6,7 @@
{% for e in expedientes %} {% for e in expedientes %}
{% if e.conteudo %} {% if e.conteudo %}
<b>{{ e.tipo }}</b>: <b>{{ e.tipo }}</b>:
{{ e.conteudo|striptags|safe }} {{ e.conteudo|sanitize|striptags }}
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</p> </p>

2
sapl/templates/sessao/blocos_ata/materias_expediente.html

@ -4,7 +4,7 @@
<strong>Matérias do Expediente: </strong> <strong>Matérias do Expediente: </strong>
{% for m in materia_expediente %} {% for m in materia_expediente %}
<b>{{ m.numero }} - {{ m.titulo }}</b>, <b>{{ m.numero }} - {{ m.titulo }}</b>,
{{ m.ementa|safe }} {{ m.ementa }}
{% if m.observacao %} - Obs.: {{ m.observacao }} {% endif %} {% if m.observacao %} - Obs.: {{ m.observacao }} {% endif %}
Autor{{ m.autor|length|pluralize:"es" }}: {{ m.autor|join:', ' }}, Autor{{ m.autor|length|pluralize:"es" }}: {{ m.autor|join:', ' }},
{% if m.numero_protocolo %} {% if m.numero_protocolo %}

2
sapl/templates/sessao/blocos_ata/materias_ordem_dia.html

@ -5,7 +5,7 @@
<strong>Matérias da Ordem do Dia: </strong> <strong>Matérias da Ordem do Dia: </strong>
{% for m in materias_ordem %} {% for m in materias_ordem %}
<b>{{ m.numero }} - {{ m.titulo }}</b>, <b>{{ m.numero }} - {{ m.titulo }}</b>,
{{ m.ementa|safe }} {{ m.ementa }}
{% if m.observacao %} - Obs.: {{ m.observacao }} {% endif %} {% if m.observacao %} - Obs.: {{ m.observacao }} {% endif %}
Autor{{ m.autor|length|pluralize:"es" }}: {{ m.autor|join:', ' }}, Autor{{ m.autor|length|pluralize:"es" }}: {{ m.autor|join:', ' }},
{% if m.numero_protocolo %} {% if m.numero_protocolo %}

3
sapl/templates/sessao/blocos_ata/ocorrencias_da_sessao.html

@ -1,8 +1,9 @@
{% load common_tags %}
{% if object.ocorrenciasessao.conteudo %} {% if object.ocorrenciasessao.conteudo %}
<fieldset> <fieldset>
<p style="text-align: justify;margin-top: 0"> <p style="text-align: justify;margin-top: 0">
<strong>Ocorrências da Sessão: </strong> <strong>Ocorrências da Sessão: </strong>
{{ object.ocorrenciasessao.conteudo|striptags|safe }} {{ object.ocorrenciasessao.conteudo|sanitize|striptags }}
</p> </p>
</fieldset> </fieldset>
{% endif %} {% endif %}

3
sapl/templates/sessao/blocos_resumo/consideracoes_finais.html

@ -1,8 +1,9 @@
{% load common_tags %}
{% if object.consideracoesfinais.conteudo %} {% if object.consideracoesfinais.conteudo %}
<fieldset> <fieldset>
<legend>Considerações Finais</legend> <legend>Considerações Finais</legend>
<div style="border:0.5px solid #BAB4B1; border-radius: 10px; background-color: rgba(225, 225, 225, .8);"> <div style="border:0.5px solid #BAB4B1; border-radius: 10px; background-color: rgba(225, 225, 225, .8);">
<p>{{object.consideracoesfinais.conteudo|safe}}</p> <p>{{object.consideracoesfinais.conteudo|sanitize}}</p>
</div> </div>
</fieldset> </fieldset>
<br /><br /><br /> <br /><br /><br />

3
sapl/templates/sessao/blocos_resumo/expedientes.html

@ -1,3 +1,4 @@
{% load common_tags %}
{% if expedientes %} {% if expedientes %}
<fieldset> <fieldset>
<legend>Expedientes</legend> <legend>Expedientes</legend>
@ -9,7 +10,7 @@
<td> <td>
<b>{{e.tipo}}: </b> <br /><br /> <b>{{e.tipo}}: </b> <br /><br />
<div style="border:0.5px solid #BAB4B1; border-radius: 10px; background-color: rgba(225, 225, 225, .8);"> <div style="border:0.5px solid #BAB4B1; border-radius: 10px; background-color: rgba(225, 225, 225, .8);">
<p>{{e.conteudo|safe}}</p> <p>{{e.conteudo|sanitize}}</p>
</div> </div>
</td> </td>
</tr> </tr>

3
sapl/templates/sessao/blocos_resumo/ocorrencias_da_sessao.html

@ -1,8 +1,9 @@
{% load common_tags %}
{% if object.ocorrenciasessao.conteudo %} {% if object.ocorrenciasessao.conteudo %}
<fieldset> <fieldset>
<legend>Ocorrências da Sessão</legend> <legend>Ocorrências da Sessão</legend>
<div style="border:0.5px solid #BAB4B1; border-radius: 10px; background-color: rgba(225, 225, 225, .8);"> <div style="border:0.5px solid #BAB4B1; border-radius: 10px; background-color: rgba(225, 225, 225, .8);">
<p>{{object.ocorrenciasessao.conteudo|safe}}</p> <p>{{object.ocorrenciasessao.conteudo|sanitize}}</p>
</div> </div>
</fieldset> </fieldset>
<br /><br /><br /> <br /><br /><br />

2
sapl/templates/sessao/consideracoes_finais.html

@ -15,7 +15,7 @@
<input type="submit" name="delete" value="Apagar" class="btn btn-danger" /> <input type="submit" name="delete" value="Apagar" class="btn btn-danger" />
</form> </form>
{% else %} {% else %}
{{object.consideracoesfinais.conteudo|safe}} {{object.consideracoesfinais.conteudo|sanitize}}
{% endif %} {% endif %}
{% endblock detail_content %} {% endblock detail_content %}

2
sapl/templates/sessao/leitura/leitura_bloco.html

@ -72,7 +72,7 @@
{% endfor %}<br> {% endfor %}<br>
{% endif %} {% endif %}
{% endif %} {% endif %}
<strong>Ementa:</strong>&nbsp;{{ o.materia.ementa|safe }}<br> <strong>Ementa:</strong>&nbsp;{{ o.materia.ementa }}<br>
<p></p> <p></p>
</td> </td>
</tr> </tr>

2
sapl/templates/sessao/ocorrencias_da_sessao.html

@ -15,7 +15,7 @@
<input type="submit" name="delete" value="Apagar" class="btn btn-danger" /> <input type="submit" name="delete" value="Apagar" class="btn btn-danger" />
</form> </form>
{% else %} {% else %}
{{object.ocorrenciasessao.conteudo|safe}} {{object.ocorrenciasessao.conteudo|sanitize}}
{% endif %} {% endif %}
{% endblock detail_content %} {% endblock detail_content %}

6
sapl/templates/sessao/pauta_sessao_detail.html

@ -54,7 +54,7 @@
<b>{{e.tipo}}: </b> <br><br> <b>{{e.tipo}}: </b> <br><br>
<div style="border:0.5px solid #BAB4B1; border-radius: 10px; <div style="border:0.5px solid #BAB4B1; border-radius: 10px;
background-color: rgba(225, 225, 225, .8);"> background-color: rgba(225, 225, 225, .8);">
<p>{{e.conteudo|safe}}</p> <p>{{e.conteudo|sanitize}}</p>
</div> </div>
</td> </td>
</tr> </tr>
@ -82,7 +82,7 @@
{{m.ementa|dont_break_out}} {{m.ementa|dont_break_out}}
{% if m.observacao %}<br><br>Obs.: {{m.observacao}} {% endif %} {% if m.observacao %}<br><br>Obs.: {{m.observacao}} {% endif %}
</td> </td>
<td style="width:20%;">{{m.situacao|linebreaksbr|safe}}</td> <td style="width:20%;">{{m.situacao|linebreaksbr}}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>
@ -108,7 +108,7 @@
{{m.ementa|dont_break_out}} {{m.ementa|dont_break_out}}
{% if m.observacao %}<br><br>Obs.: {{m.observacao}} {% endif %} {% if m.observacao %}<br><br>Obs.: {{m.observacao}} {% endif %}
</td> </td>
<td style="width:20%;">{{m.situacao|linebreaksbr|safe}}</td> <td style="width:20%;">{{m.situacao|linebreaksbr}}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</table> </table>

2
sapl/templates/sessao/votacao/nominal.html

@ -10,7 +10,7 @@
<div> <div>
Matéria: {{materia.materia}} Matéria: {{materia.materia}}
<br /> <br />
Ementa: {{materia.ementa|safe}} Ementa: {{materia.ementa}}
</div> </div>
<br /> <br />
{% if total == 0 %} {% if total == 0 %}

6
sapl/templates/sessao/votacao/nominal_detail.html

@ -8,9 +8,9 @@
<fieldset> <fieldset>
<legend>Votação Nominal</legend> <legend>Votação Nominal</legend>
<div> <div>
Matéria: {{materia.materia|safe}} Matéria: {{materia.materia}}
<br /> <br />
Ementa: {{materia.ementa|safe}} Ementa: {{materia.ementa}}
</div> </div>
<br /> <br />
@ -45,7 +45,7 @@
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
Observações Observações
<textarea id="observacao" name="observacao" style="width:100%;" rows="7" class="form-control">{{votacao.observacao|safe}}</textarea> <textarea id="observacao" name="observacao" style="width:100%;" rows="7" class="form-control">{{votacao.observacao}}</textarea>
</div> </div>
</div> </div>
</fieldset> </fieldset>

6
sapl/templates/sessao/votacao/nominal_edit.html

@ -8,9 +8,9 @@
<fieldset> <fieldset>
<legend>Votação Nominal</legend> <legend>Votação Nominal</legend>
<div> <div>
Matéria: {{materia.materia|safe}} Matéria: {{materia.materia}}
<br /> <br />
Ementa: {{materia.ementa|safe}} Ementa: {{materia.ementa}}
</div> </div>
<br /> <br />
@ -73,7 +73,7 @@
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
Observações Observações
<textarea id="observacao" name="observacao" style="width:100%;" rows="7" class="form-control">{{votacao.observacao|safe}}</textarea> <textarea id="observacao" name="observacao" style="width:100%;" rows="7" class="form-control">{{votacao.observacao}}</textarea>
</div> </div>
</div> </div>

2
sapl/templates/sessao/votacao/nominal_transparencia.html

@ -49,7 +49,7 @@
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
Observações Observações
<textarea id="observacao" name="observacao" style="width:100%;" rows="7" class="form-control">{{votacao.observacao|safe}}</textarea> <textarea id="observacao" name="observacao" style="width:100%;" rows="7" class="form-control">{{votacao.observacao}}</textarea>
</div> </div>
</div> </div>

2
sapl/templates/sessao/votacao/simbolica_transparencia.html

@ -40,7 +40,7 @@
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
Observações Observações
<textarea id="observacao" name="observacao" style="width:100%;" rows="7" class="form-control">{{votacao.observacao|safe}}</textarea> <textarea id="observacao" name="observacao" style="width:100%;" rows="7" class="form-control">{{votacao.observacao}}</textarea>
</div> </div>
</div> </div>

4
sapl/templates/sessao/votacao/votacao.html

@ -26,9 +26,9 @@
{% endif %} {% endif %}
<div> <div>
<b>Matéria:</b> {{materia.materia|safe}} <b>Matéria:</b> {{materia.materia}}
<br /> <br />
<b>Ementa:</b> {{materia.ementa|safe}} <b>Ementa:</b> {{materia.ementa}}
<br /> <br />
<br /> <br />
<b>Total presentes:</b> {{total_presentes}} (com presidente) <b>Total presentes:</b> {{total_presentes}} (com presidente)

2
sapl/templates/sessao/votacao/votacao_bloco.html

@ -73,7 +73,7 @@
{% endfor %}</br> {% endfor %}</br>
{% endif %} {% endif %}
{% endif %} {% endif %}
<strong>Ementa:</strong>&nbsp;{{ o.materia.ementa|safe }}</br> <strong>Ementa:</strong>&nbsp;{{ o.materia.ementa }}</br>
<p></p> <p></p>
</td> </td>
</tr> </tr>

6
sapl/templates/sessao/votacao/votacao_edit.html

@ -8,9 +8,9 @@
<fieldset class="form-group"> <fieldset class="form-group">
<legend>{{votacao_titulo}}</legend> <legend>{{votacao_titulo}}</legend>
<div> <div>
Matéria: {{materia.materia|safe}} Matéria: {{materia.materia}}
<br /> <br />
Ementa: {{materia.ementa|safe}} Ementa: {{materia.ementa}}
</div> </div>
<br /> <br />
@ -36,7 +36,7 @@
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
Observações Observações
<textarea id="observacao" name="observacao" cols="10" rows="10" class="form-control">{{votacao.observacao|safe}}</textarea> <textarea id="observacao" name="observacao" cols="10" rows="10" class="form-control">{{votacao.observacao}}</textarea>
</div> </div>
</div> </div>

4
sapl/templates/sessao/votacao/votacao_nominal_bloco.html

@ -24,7 +24,7 @@
<div> <div>
Matéria: {{o.materia}} Matéria: {{o.materia}}
<br /> <br />
Ementa: {{o.materia.ementa|safe}} Ementa: {{o.materia.ementa}}
</div> </div>
<br /> <br />
{% endfor %} {% endfor %}
@ -34,7 +34,7 @@
<div> <div>
Matéria: {{e.materia}} Matéria: {{e.materia}}
<br /> <br />
Ementa: {{e.materia.ementa|safe}} Ementa: {{e.materia.ementa}}
</div> </div>
<br /> <br />
{% endfor %} {% endfor %}

8
sapl/templates/sessao/votacao/votacao_simbolica_bloco.html

@ -21,17 +21,17 @@
{% if ordens %} {% if ordens %}
{% for o in ordens %} {% for o in ordens %}
<input type="hidden" id="ordens" name="ordens" value="{{o.id}}"> <input type="hidden" id="ordens" name="ordens" value="{{o.id}}">
<b>Matéria:</b> {{o.materia|safe}} <b>Matéria:</b> {{o.materia}}
<br /> <br />
<b>Ementa:</b> {{o.materia.ementa|safe}} <b>Ementa:</b> {{o.materia.ementa}}
<br /> <br /> <br /> <br />
{% endfor %} {% endfor %}
{% else %} {% else %}
{% for e in expedientes %} {% for e in expedientes %}
<input type="hidden" id="expedientes" name="expedientes" value="{{e.id}}"> <input type="hidden" id="expedientes" name="expedientes" value="{{e.id}}">
<b>Matéria:</b> {{e.materia|safe}} <b>Matéria:</b> {{e.materia}}
<br /> <br />
<b>Ementa:</b> {{e.materia.ementa|safe}} <b>Ementa:</b> {{e.materia.ementa}}
<br /> <br /> <br /> <br />
{% endfor %} {% endfor %}
{% endif %} {% endif %}

Loading…
Cancel
Save