Browse Source

Sanitiza HTML/JavaScript nos campos TextField (stored XSS)

Os ~71 campos models.TextField do SAPL guardavam texto livre sem nenhuma
sanitização, e a renderização os tratava como HTML confiável em 142 pontos
(|safe, {% autoescape off %}). Qualquer usuário com permissão de edição
conseguia gravar <script> num campo e executá-lo no navegador de quem
abrisse a tela, inclusive nas páginas públicas (acompanhamento de matéria,
perfil público de parlamentar, resumo de sessão).

Duas camadas, ambas usando a mesma função idempotente para que não se
acumulem:

- Entrada: receiver pre_save global em sapl/base/receivers.py, cobrindo
  forms, a API do drfautoapi, o admin e o shell num único ponto.
- Saída: get_field_display() em sapl/crispy_layout_mixin.py — que monta o
  HTML de todo TextField para as telas de list/detail do CRUD — mais o
  novo filtro |sanitize nos templates. Protege as linhas gravadas antes
  desta mudança, já que não há backfill.

Políticas por campo, em sapl/sanitize.py:

- plain (padrão): remove toda a marcação e preserva o texto.
- rich: allowlist para os campos editados no TinyMCE (conteúdo de
  expediente, ocorrências, considerações finais e texto de dispositivo).
  Links continuam funcionando, com target e o alinhamento do editor
  preservados; javascript:, data:, on* e <script> são descartados.
- exempt: LexmlProvedor.xml e TipoTextoArticulado.rodape_global, que não
  são HTML.

Corrige também cinco |safe dentro de <textarea>, onde
</textarea><script> escapava do contexto, e cinco |striptags|safe — a
documentação do Django avisa que striptags não garante HTML seguro.

Sem migração de dados: as linhas existentes são neutralizadas na
renderização e limpas quando o registro for salvo de novo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
hotfix/sanitize-textfield-xss
Edward Ribeiro 1 day ago
parent
commit
f84a020c15
  1. 1
      requirements/requirements.txt
  2. 42
      sapl/base/receivers.py
  3. 12
      sapl/base/templatetags/common_tags.py
  4. 163
      sapl/base/tests/test_sanitize.py
  5. 5
      sapl/crispy_layout_mixin.py
  6. 132
      sapl/sanitize.py
  7. 2
      sapl/templates/comissoes/materias_em_tramitacao.html
  8. 2
      sapl/templates/compilacao/text_edit_bloco.html
  9. 2
      sapl/templates/compilacao/text_list_bloco.html
  10. 6
      sapl/templates/compilacao/text_list_blocoalteracao.html
  11. 2
      sapl/templates/compilacao/textoarticulado_detail.html
  12. 2
      sapl/templates/materia/acompanhamento_materia.html
  13. 2
      sapl/templates/materia/materialegislativa_filter.html
  14. 2
      sapl/templates/materia/resumo_detail_materia.html
  15. 2
      sapl/templates/norma/normajuridica_filter.html
  16. 2
      sapl/templates/parlamentares/parlamentar_perfil_publico.html
  17. 2
      sapl/templates/protocoloadm/acompanhamento_documento.html
  18. 2
      sapl/templates/protocoloadm/documentoadministrativo_filter.html
  19. 2
      sapl/templates/relatorios/RelatorioMateriasPorAutor_filter.html
  20. 2
      sapl/templates/relatorios/RelatorioNormasPorAutor_filter.html
  21. 2
      sapl/templates/relatorios/blocos_sessao_plenaria/consideracoes_finais.html
  22. 3
      sapl/templates/relatorios/blocos_sessao_plenaria/expedientes.html
  23. 2
      sapl/templates/relatorios/blocos_sessao_plenaria/ocorrencias_da_sessao.html
  24. 2
      sapl/templates/relatorios/relatorio_doc_administrativos.html
  25. 2
      sapl/templates/relatorios/relatorio_normas_por_autor.html
  26. 10
      sapl/templates/relatorios/relatorio_pauta_sessao.html
  27. 6
      sapl/templates/search/search.html
  28. 2
      sapl/templates/sessao/adicionar_varias_materias_expediente.html
  29. 2
      sapl/templates/sessao/blocos_ata/consideracoes_finais.html
  30. 2
      sapl/templates/sessao/blocos_ata/expedientes.html
  31. 2
      sapl/templates/sessao/blocos_ata/materias_expediente.html
  32. 2
      sapl/templates/sessao/blocos_ata/materias_ordem_dia.html
  33. 2
      sapl/templates/sessao/blocos_ata/ocorrencias_da_sessao.html
  34. 3
      sapl/templates/sessao/blocos_resumo/consideracoes_finais.html
  35. 3
      sapl/templates/sessao/blocos_resumo/expedientes.html
  36. 3
      sapl/templates/sessao/blocos_resumo/ocorrencias_da_sessao.html
  37. 2
      sapl/templates/sessao/consideracoes_finais.html
  38. 2
      sapl/templates/sessao/leitura/leitura_bloco.html
  39. 2
      sapl/templates/sessao/ocorrencias_da_sessao.html
  40. 6
      sapl/templates/sessao/pauta_sessao_detail.html
  41. 2
      sapl/templates/sessao/votacao/nominal.html
  42. 6
      sapl/templates/sessao/votacao/nominal_detail.html
  43. 6
      sapl/templates/sessao/votacao/nominal_edit.html
  44. 2
      sapl/templates/sessao/votacao/nominal_transparencia.html
  45. 2
      sapl/templates/sessao/votacao/simbolica_transparencia.html
  46. 4
      sapl/templates/sessao/votacao/votacao.html
  47. 2
      sapl/templates/sessao/votacao/votacao_bloco.html
  48. 6
      sapl/templates/sessao/votacao/votacao_edit.html
  49. 4
      sapl/templates/sessao/votacao/votacao_nominal_bloco.html
  50. 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
XlsxWriter==3.2.0
nh3==0.2.22
setuptools==80.9.0

42
sapl/base/receivers.py

@ -8,6 +8,7 @@ from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core import serializers
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.signals import post_delete, post_save, \
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.parlamentares.models import Parlamentar
from sapl.protocoloadm.models import TramitacaoAdministrativo
from sapl.sanitize import sanitize_field
from sapl.utils import get_base_url, models_with_gr_for_model
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)
def handle_update_autor_related(sender, **kwargs):
@ -477,6 +482,43 @@ def signed_files_extraction_pre_save_signal(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.
"""
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')
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')

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.norma.models import NormaJuridica
from sapl.parlamentares.models import Filiacao
from sapl.sanitize import sanitize_html
from sapl.sessao.models import SessaoPlenaria
from sapl.utils import filiacao_data, SEPARADOR_HASH_PROPOSICAO, is_report_allowed
@ -396,6 +397,17 @@ def render_chunk_vendors(extension=None):
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)
@stringfilter
def dont_break_out(value):

163
sapl/base/tests/test_sanitize.py

@ -0,0 +1,163 @@
import pytest
from model_bakery import baker
from sapl.crispy_layout_mixin import get_field_display
from sapl.lexml.models import LexmlProvedor
from sapl.protocoloadm.models import TramitacaoAdministrativo
from sapl.sanitize import 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_escapa_caracteres_especiais():
assert sanitize_html('Valor < 10 & prazo > 5') == \
'Valor &lt; 10 &amp; prazo &gt; 5'
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):
"""Propriedade da qual dependem as duas camadas (pre_save + renderização).
Se sanitizar duas vezes não fosse estável, o valor gravado seria
re-escapado a cada exibição.
"""
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'
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

5
sapl/crispy_layout_mixin.py

@ -11,6 +11,8 @@ from django.utils.encoding import force_text
from django.utils.translation import ugettext as _
import yaml
from sapl.sanitize import sanitize_field
def heads_and_tails(list_of_lists):
for alist in list_of_lists:
@ -167,7 +169,8 @@ def get_field_display(obj, fieldname):
args=(value.id,)),
value)
elif 'TextField' in str_type_from_field:
display = value.replace('\n', '<br/>')
display = sanitize_field(obj._meta.model, fieldname, value)
display = display.replace('\n', '<br/>')
display = '<div class="dont-break-out">{}</div>'.format(display)
else:
display = str(value)

132
sapl/sanitize.py

@ -0,0 +1,132 @@
"""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`` importa ``sapl.crispy_layout_mixin``.
Duas políticas:
* ``plain`` remove toda a marcação e preserva o texto. É 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).
Ambas são idempotentes: aplicar duas vezes produz o mesmo resultado. É essa
propriedade que permite sanitizar tanto no ``pre_save`` quanto na renderização
sem que os efeitos se acumulem.
"""
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 = {
'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',
},
}
# Modelos cujos TextField não devem ser tocados em hipótese alguma.
SANITIZE_EXEMPT_MODELS = {
# xml é XML fornecido pela equipe do LexML; já é escapado em pretty_xml
'lexml.LexmlProvedor',
# rodape_global é interpolado dentro de um content: de CSS
'compilacao.TipoTextoArticulado',
}
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 key in SANITIZE_EXEMPT_MODELS:
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.
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 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 de modelos isentos atravessam sem modificação.
"""
scope = sanitize_scope(model, fieldname)
if scope == 'exempt':
return value
return sanitize_html(value, rich=(scope == 'rich'))

2
sapl/templates/comissoes/materias_em_tramitacao.html

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

2
sapl/templates/compilacao/text_edit_bloco.html

@ -43,7 +43,7 @@
{% if not node.dpt.texto and not node.td.dispositivo_de_articulacao %}
<span class="semtexto">({{node.td}} sem texto)</span>
{%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%}
{% 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'%}">

2
sapl/templates/compilacao/text_list_bloco.html

@ -32,7 +32,7 @@
{% 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 %}
<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 }}

6
sapl/templates/compilacao/text_list_blocoalteracao.html

@ -19,15 +19,15 @@
{% if ch.rotulo %}
<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.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 %}
<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.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>
{% else %}
{{ 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 %}
{% if ch.imagem %}

2
sapl/templates/compilacao/textoarticulado_detail.html

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

2
sapl/templates/materia/acompanhamento_materia.html

@ -19,7 +19,7 @@
</div>
<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>
{% crispy form %}

2
sapl/templates/materia/materialegislativa_filter.html

@ -153,7 +153,7 @@
{% if m.tramitacao_set.first.data_tramitacao %}
<strong>Data da última Tramitação:</strong> &nbsp;{{m.tramitacao_set.first.data_tramitacao}}</br>
{% 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 %}
{% 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>Ano: </b>{{object.ano}}</div>
</div>
<b>Ementa:</b> {{object.ementa|safe}}
<b>Ementa:</b> {{object.ementa}}
<br /><br />

2
sapl/templates/norma/normajuridica_filter.html

@ -62,7 +62,7 @@
{% endif %}
<strong>Ementa:</strong><br>
{{n.ementa|safe}}<br>
{{n.ementa}}<br>
{% if n.texto_articulado.exists and not n.texto_articulado.first.privacidade %}
<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 id="div_biografia" class="form-group">
<p><b>Biografia: </b> &nbsp {{object.biografia|safe}}</p>
<p><b>Biografia: </b> &nbsp {{object.biografia|linebreaksbr}}</p>
</div>
</div>
</div>

2
sapl/templates/protocoloadm/acompanhamento_documento.html

@ -19,7 +19,7 @@
</div>
<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>
{% 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>
<strong>Interessado:</strong>&nbsp;{{ d.interessado|default_if_none:"Não informado"}}
</br>
<strong>Assunto:</strong>&nbsp;{{ d.assunto|safe }}
<strong>Assunto:</strong>&nbsp;{{ d.assunto }}
</br>
{% if d.protocolo %}
<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}}
</a>
</td>
<td>{% autoescape off %}{{materia.ementa}}<br>{{materia.observacao}}{% endautoescape %}</td>
<td>{{materia.ementa}}<br>{{materia.observacao}}</td>
<td>
{% for autoria in materia.autoria_set.all %}
{% 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 %}">
{{norma.tipo.sigla}} {{norma.numero}}/{{norma.ano}}
</a></td>
<td>{% autoescape off %}{{norma.ementa}}<br>{{norma.observacao}}{% endautoescape %}</td>
<td>{{norma.ementa}}<br>{{norma.observacao}}</td>
<td>
{% for autoria in norma.autorianorma_set.all %}
{% if autoria.autor != autor %}

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

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

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

@ -1,5 +1,6 @@
{% load common_tags %}
<h2 class="gray-title">Expedientes</h2>
{% for expediente in lst_expedientes%}
<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%}

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

@ -1,4 +1,4 @@
<h2 class="gray-title">Ocorrências da Sessão</h2>
{% for o in lst_ocorrencias%}
<p>{{o.conteudo|striptags|safe}}</p>
<p>{{o.conteudo|striptags}}</p>
{% 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>Interessado:</strong>&nbsp;{{ d.interessado|default_if_none:"Não informado"}}
</br>
<strong>Assunto:</strong>&nbsp;<a>{{ d.assunto|safe}}</a>
<strong>Assunto:</strong>&nbsp;<a>{{ d.assunto}}</a>
</br>
{% if d.protocolo %}
<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>
{{norma.tipo.sigla}} {{norma.numero}}/{{norma.ano}}
</td>
<td>{% autoescape off %}{{norma.ementa}}<br>{{norma.observacao}}{% endautoescape %}</td>
<td>{{norma.ementa}}<br>{{norma.observacao}}</td>
<td>
{% if norma.autorianorma_set.first != norma.autorianorma_set.last %}
{% for autor in norma.autorianorma_set.all %}

10
sapl/templates/relatorios/relatorio_pauta_sessao.html

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

6
sapl/templates/search/search.html

@ -51,7 +51,7 @@
{% if result.object|search_get_model == 'm' %}
<p>
<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 %}
<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' %}
<p>
<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 %}
<strong>Texto Original:</strong> <a href="{{result.object.arquivo.url}}"> Clique aqui </a></br>
{% endif %}
@ -73,7 +73,7 @@
{% elif result.object|search_get_model == 'n' %}
<p>
<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 %}
<strong>Texto Original:</strong> <a href="{{result.object.texto_integral.url}}"> Clique aqui </a></br>
{% 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>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>Ementa:</strong>&nbsp;{{ m.ementa|safe }}</br>
<strong>Ementa:</strong>&nbsp;{{ m.ementa }}</br>
<p></p>
<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>

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

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

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

@ -5,7 +5,7 @@
{% for e in expedientes %}
{% if e.conteudo %}
<b>{{ e.tipo }}</b>:
{{ e.conteudo|striptags|safe }}
{{ e.conteudo|striptags }}
{% endif %}
{% endfor %}
</p>

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

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

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

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

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

@ -1,8 +1,9 @@
{% load common_tags %}
{% if object.consideracoesfinais.conteudo %}
<fieldset>
<legend>Considerações Finais</legend>
<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>
</fieldset>
<br /><br /><br />

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

@ -1,3 +1,4 @@
{% load common_tags %}
{% if expedientes %}
<fieldset>
<legend>Expedientes</legend>
@ -9,7 +10,7 @@
<td>
<b>{{e.tipo}}: </b> <br /><br />
<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>
</td>
</tr>

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

@ -1,8 +1,9 @@
{% load common_tags %}
{% if object.ocorrenciasessao.conteudo %}
<fieldset>
<legend>Ocorrências da Sessão</legend>
<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>
</fieldset>
<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" />
</form>
{% else %}
{{object.consideracoesfinais.conteudo|safe}}
{{object.consideracoesfinais.conteudo|sanitize}}
{% endif %}
{% endblock detail_content %}

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

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

2
sapl/templates/sessao/ocorrencias_da_sessao.html

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

6
sapl/templates/sessao/pauta_sessao_detail.html

@ -54,7 +54,7 @@
<b>{{e.tipo}}: </b> <br><br>
<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>
</td>
</tr>
@ -82,7 +82,7 @@
{{m.ementa|dont_break_out}}
{% if m.observacao %}<br><br>Obs.: {{m.observacao}} {% endif %}
</td>
<td style="width:20%;">{{m.situacao|linebreaksbr|safe}}</td>
<td style="width:20%;">{{m.situacao|linebreaksbr}}</td>
</tr>
{% endfor %}
</table>
@ -108,7 +108,7 @@
{{m.ementa|dont_break_out}}
{% if m.observacao %}<br><br>Obs.: {{m.observacao}} {% endif %}
</td>
<td style="width:20%;">{{m.situacao|linebreaksbr|safe}}</td>
<td style="width:20%;">{{m.situacao|linebreaksbr}}</td>
</tr>
{% endfor %}
</table>

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

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

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

@ -8,9 +8,9 @@
<fieldset>
<legend>Votação Nominal</legend>
<div>
Matéria: {{materia.materia|safe}}
Matéria: {{materia.materia}}
<br />
Ementa: {{materia.ementa|safe}}
Ementa: {{materia.ementa}}
</div>
<br />
@ -45,7 +45,7 @@
<div class="row">
<div class="col-md-12">
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>
</fieldset>

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

@ -8,9 +8,9 @@
<fieldset>
<legend>Votação Nominal</legend>
<div>
Matéria: {{materia.materia|safe}}
Matéria: {{materia.materia}}
<br />
Ementa: {{materia.ementa|safe}}
Ementa: {{materia.ementa}}
</div>
<br />
@ -73,7 +73,7 @@
<div class="row">
<div class="col-md-12">
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>

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

@ -49,7 +49,7 @@
<div class="row">
<div class="col-md-12">
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>

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

@ -40,7 +40,7 @@
<div class="row">
<div class="col-md-12">
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>

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

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

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

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

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

@ -8,9 +8,9 @@
<fieldset class="form-group">
<legend>{{votacao_titulo}}</legend>
<div>
Matéria: {{materia.materia|safe}}
Matéria: {{materia.materia}}
<br />
Ementa: {{materia.ementa|safe}}
Ementa: {{materia.ementa}}
</div>
<br />
@ -36,7 +36,7 @@
<div class="row">
<div class="col-md-12">
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>

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

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

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

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

Loading…
Cancel
Save