Filtros em Templates no Django


Django, Templates e Filtros

Já vimos em Templates do Django que os templates são modelos usados na arquitetura MTV para renderizar as requisições do cliente e gerar texto com marcação HTML. Dentro desses modelos podemos receber variáveis enviadas no contexto, com a marcação {{ var }}. Um contexto é um conjunto de valores, em um objeto tipo dicionário (um conjunto de chaves:valores), passado pelas views para o template. Os nomes das variáveis são passados como strings.

# se temos a variável    
» contexto = {"n1": "um"; "n2":"dois";}
# e o template
⎀ Temos no contexto {{ n1 }} e {{ n2 }}.
# será renderizado como
↳ Temos no contexto um e dois.

Filtros são formas de modificar as saídas de variáveis. Por ex.:

» contexto = {'django': 'o framework web para perfecionistas com prazos'}
⎀ {{ django|title }}
↳ O Framework Web Para Perfecionistas Com Prazos
# ou
» contexto = {'data': '2022-07-04'}
⎀ {{ data|date:"d/m/Y" }}
↳ 04/07/2022

Muitos outros filtros são predefinidos e outros mais podem ser definidos pelo programador.

Filtros do Django

add soma valor ao argumento. Tenta forçar a conversão de strings para numéricos.

» valor = 6
⎀ {{ valor|add:"2" }}
↳ 8

» lista1 = [2,3,4]; lista2 = [7,8,9]
⎀ {{ lista1|add:lista2 }}
↳ [2,3,4,7,8,9]

addslashes, insere “\” em aspas.

» valor = "Einstein disse: 'Não existe mais espaço e tempo'."
⎀ {{ valor|addslashes }}
↳ Einstein disse: \'Não existe mais espaço e tempo\'.

capfirst, capitaliza primeira letra de uma string.

» valor = "não existe mais espaço e tempo"
⎀ {{ valor|capfirst }}
↳ Não existe mais espaço e tempo

center, centraliza texto dentro do espaço dado.

» valor = "tempo"
⎀ {{ valor|center:"20" }}
↳ "       tempo        "

cut, remove valores do argumento do string dado.

» valor = "não existe mais espaço e tempo"
⎀ {{ valor|cut:" " }}
↳ nãoexistemaisespaçoetempo

cut, formata uma data (e hora) de acordo com especificação dada.

» data = "2022-02-30"
⎀ {{ data|date:"d/m/y" }}
↳ 30/02/22
⎀ {{ data|date:"d-m-Y" }}
↳ 30-02-2022

Alguns formatos são pre-definidos. Veja a lista completa em Django docs.

Caracter Descrição Exemplo
Dia
d dia do mes com dois dígitos ’01’ até ’31’
j dia do mes sem zeros. ‘1’ até ’31’
D dia da semana em texto, 3 letras. ‘Fri’
l dia da semana em texto completo. ‘Friday’
w dia da semana, numérico. ‘0’ (Domingo) até ‘6’ (Sábado)
z dia do ano. 1 to 366
Semana
W número da semana no ano. 1, 53
Mês
m mês, 2 dígitos. ’01’ até ’12’
n mês sem zeros. ‘1’ até ’12’
M mês, texto, 3 letras. ‘Jan’, ‘Dec’
b mês, texto, 3 letras, ninúsculas. ‘jan’, ‘dec’
F mês, texto, extenso. ‘January’
t quantos dias no mês. 28 to 31
Ano
y ano, 2 dígitos. ’00’ até ’99’
Y ano, 4 dígitos. ‘0001’, …, ‘1999’, …, ‘9999’
L Booleano, se ano é bissexto. True ou False
Hora
g hora, formato 12-hora sem zeros. ‘1’ até ’12’
G hora, formato 24-hora sem zeros. ‘0’ até ’23’
h hora, formato 12-hora. ’01’ até ’12’
H hora, formato 24-hora. ’00’ até ’23’
i minutos. ’00’ até ’59’
s segundos, 2 dígitos. ’00’ até ’59’
u microssegundos. 000000 até 999999
a ‘a.m.’ ou ‘p.m.’
A ‘AM’ ou ‘PM’.
f hora, format 12-horas e minutos ‘1:30’
P hora, formato 12-hora horas, minutos ‘a.m.’/’p.m.’
Timezone
e nome da timezone ‘GMT’, ‘-500’, ‘US/Eastern’, etc.

O formato pode ser um dos predefinidos como DATE_FORMAT, DATETIME_FORMAT, SHORT_DATE_FORMAT ou SHORT_DATETIME_FORMAT ou um formato construído com os especificadores acima. Formatos predefinidos podem depender do ajuste local.

# se data_valor é um objeto datetime, como o resultante de datetime.datetime.now() 
# com hora 23:45, dia 01/11/2021
⎀ {{ data_valor|date:"D d M Y" }} {{ data_valor|time:"H:i" }}
↳ Mon 01 Nov 2021 23:45

# se o locale for pt-BR
⎀ {{ data_valor|date:"SHORT_DATE_FORMAT" }}
↳ 01/11/2021

default, fornece valor default se argumento for False.

»  valor = ""   
⎀ {{ valor|defalt:"nada aqui" }} 
↳ nada aqui

default_if_none, fornece valor default se argumento for None.

» valor = None
⎀ {{ valor|defalt_if_none:"recebemos None" }} 
↳ recebemos None

dictsort recebe uma lista de dicionários e ordena a lista por alguma das chaves do docionário.

# considerando o dicionário:    
» dicio = [
»     {'nome': 'Zuenir', 'idade': 9},
»     {'nome': 'Antônia', 'idade': 12},
»     {'nome': 'Jaime', 'idade': 3},
» ]

⎀ {{ dicio|dictsort:"nome" }}
# resulta em
↳ dicio = [
↳     {'nome': 'Antônia', 'idade': 12},
↳     {'nome': 'Jaime', 'idade': 3},
↳     {'nome': 'Zuenir', 'idade': 9},
↳ ]

Exemplos mais complexos podem ser obtidos:

# se livros é    
» livros = [
»     {'titulo': '1984', 'autor': {'nome': 'George', 'idade': 45}},
»     {'titulo': 'Timequake', 'autor': {'nome': 'Kurt', 'idade': 75}},
»     {'titulo': 'Alice', 'autor': {'nome': 'Lewis', 'idade': 33}},
» ]

# o código em template
⎀ {% for livro in livros|dictsort:"autor.idade" %}
⎀     * {{ livro.titulo }} ({{ livro.autor.nome }})
⎀ {% endfor %}

# resultaria em
↳ * Alice (Lewis)
↳ * 1984 (George)
↳ * Timequake (Kurt)

dictsortreversed tem o mesmo efeito que dictsort, mas ordenando em ordem invertida.

divisibleby returna True se o valor é divisível pelo argumento.

» valor = 171
⎀ {{ value|divisibleby:"3" }}
↳ True

escape promove remoção de tags html.

# esse exemplo mostra a exibição final no navegador
» string_html = "<b>Negrito<b>"
⎀ {{ string_html }}
↳ <b>Negrito<b>

⎀ {{ string_html|scape }}
↳ <b>Negrito</b>

escape converte:

  • < em &lt;
  • > em &gt;
  • ' (aspas simples) em &#x27;
  • " (aspas duplas) em &quot;
  • & em &amp;

first retorna o 1º elemento de uma lista.

» lista = ["casa","da","sogra"]
⎀ {{ lista|first }}
↳ casa

floatformat promove o arredondamento de números flutuantes.

» valor = 34.23234
⎀ {{ valor|floatformat }}
↳ 34.2

» valor = 34.0000
⎀ {{ valor|floatformat }}
↳ 34

» valor = 34.26000
⎀ {{ valor|floatformat }}
↳ 34.3

O número de casas pode ser definido em valor|floatformat:n. Passando “0” como argumento o arredondamento será para o inteiro mais próximo. O sufixo g introduz separador de milhar, definido em THOUSAND_SEPARATOR.

valor template output
34.23234 {{ valor|floatformat:2 }} 34.23
34.00000 {{ valor|floatformat:2 }} 34.00
34.26000 {{ valor|floatformat:2 }} 34.26
34.23234 {{ valor|floatformat:”0″ }} 34
31.00000 {{ valor|floatformat:”0″ }} 31
39.56000 {{ valor|floatformat:”0″ }} 40
34232.34 {{ valor|floatformat:”2g” }} 34,232.34
34232.06 {{ valor|floatformat:”g” }} 34,232.1

get_digit retorna um inteiro na posição especificada, contando do final para o início. Se não for possível encontar esse dígito, retorna o valor original.

» valor = 9512845
⎀ {{ valor|get_digit:"4" }}
↳ 2

⎀ {{ valor|get_digit:"9" }}
↳ 9512845

join faz a união de elementos em uma lista em uma string (como em str.join(lista)).

» lista = ["casa", "da", "mãe", "Joana"]
⎀ {{ lista|join:" - " }}
↳ casa - da - mãe - Joana

last retorna o último elemento de uma lista.

»  lista = ["casa", "da", "mãe", "Joana"]
⎀ {{ lista|last}}
↳ Joana

len retorna o comprimento de uma lista.

»  lista = ["casa", "da", "mãe", "Joana"]
⎀ {{ lista|len}}
↳ 4

length_is retorna booleano, se o comprimento de uma lista é o dado em parâmetro.

»  lista = ["casa", "da", "mãe", "Joana"]
⎀ {{ lista|length_is:"4"}}
↳ True

linebreaks substitui quebras de linha em texto puro por uma quebra de linha html (<br>) e insere anova linha entre tags de parágrafo (<p> … </p>).

» texto_puro = "Essa é a linha 1\nEssa é a linha 2"
⎀ {{ texto_puro|linebreaks}}
↳ <p>Essa é a linha 1<br>Essa é a linha 2</p>

linebreaksbr faz a mesma coisa, sem inserir a linha em parágrafo.

linenumbers quebra texto em linhas e as numera.

» lista_compras = '''Leite
» Açucar
» Café
» Pão'''

⎀ {{ lista_compras|linenumbers }}
# resulta em
↳ 1. Leita
↳ 2. Açucar
↳ 3. Café
↳ 4. Pão

Observe que lista_compras = “Leite\nAçucar\nCafé\nPão”.

ljust alinha texto à esquerda dentro de espaço de n caracteres dado.

» comprar = "Pão"
⎀ {{ comprar|ljust:"9" }}
↳ "Pão      "

lower converte todas os caracters de uma string para caixa baixa (minúsculas).

» texto = "Pão COM Manteiga"
⎀ {{ texto|lower }}
↳ pão com manteiga

make_list retorna valor string ou inteiro em uma lista.

» texto = "Pão de Queijo"
⎀ {{ texto|make_list }}
↳ ["P", "ã", "o", " ", "d", "e", " ", "Q", "u", "e", "i", "j", "o"]

» numero = 1957
⎀ {{ numero|make_list }}
↳ ["1", "9", "5", "7"]

pluralize retorna sufixo para plurais se valor do parâmetro for maior que 1. Esse valor pode ser o comprimento do objeto. O sufixo default é s, mas isso pode ser alterado.

» itens_compra = 1
⎀ Você tem que comprar {{ itens_compra }} objeto{{ itens_compra|pluralize }}.
↳ Você tem que comprar 1 objeto.

» itens_compra = 23
⎀ Você tem que comprar {{ itens_compra }} objeto{{ itens_compra|pluralize }}.
↳ Você tem que comprar 23 objetos.

Sufixos alternativos podem ser inseridos como parâmetros:

» quantos = 1
⎀ Você fez o pedido de {{ quantos }} paste{{ quantos|pluralize:"l,is" }}.
↳ Você fez o pedido de 1 pastel.

» quantos = 45
⎀ Você fez o pedido de {{ quantos }} paste{{ quantos|pluralize:"l,is" }}.
↳ Você fez o pedido de 45 pasteis.

random retorna um elemento aleatório de uma lista.

»  lista = ["casa", "da", "mãe", "Joana"]
⎀ {{ lista|random }}
# um possível resultado é
↳ mãe

rjust alinha texto à direita dentro de espaço de n caracteres dado.

» comprar = "Pão"
⎀ {{ comprar|rjust:"9" }}
↳ "      Pão"

safe marca texto como não necessitando escapes.

escape promove remoção de tags html.

» string_html = "<b>Negrito<b>"
⎀ {{ string_html|escape }}
↳ <b>Negrito<b>

Se existirem tags html elas serão renderizadas no navegador.

slice retorna uma fatia (slice) de uma lista. Usa a mesma sintaxe de slicing de listas do python:

» lista = ["casa","da","sogra", "no", "domingo"]
⎀ {{ lista|slice:":3" }}
↳ ["casa","da","sogra"]

slugify converte texto em ASCII puro, convertendo espaços em hífens. Remove caracteres que não são alfanuméricos, sublinhados (underscores) ou hífens. Converte tudo para minúsculas eliminando espaços nas bordas.

» texto = " Artigo 31 das Notas "
⎀ {{ texto|slugfy }}
↳ artigo-31-das-notas

stringformat formata variável de acordo com o parâmetro especificador.

» valor = 10
⎀ {{ valor|stringformat:"E" }}
↳ 1.000000E+01

Mais caracteres de formatação em printf-style String Formatting.

striptags remove tags [X]Html sempre que possível.

» valor = "<b>Um texto pode ter</b> <button>várias tags</button> <span>(x)html</span>!"
⎀ {{ valor|striptags }}
↳ Um texto pode ter várias tags (x)html!

Observação: striptags não garante que o texto seja seguro. Não aplique a filtro safe sobre o resultado de striptags.

time formata variável tipo time de acordo com formato especificado.

» hora = datetime.datetime.now()
⎀ {{ hora|time:"H:i" }}
↳ 18:49

⎀ {{ hora|time:"H\h i\m" }}
↳ 01h 23m

No exemplo caracteres literais foram escapados (\h, \m).

timesince formata uma diferença de datas entre now (agora) e data fornecida em parâmetro.

# se artigo_gravado contem uma data e
» hora = datetime.datetime.now()
⎀ {{ artigo_gravado|timesince:hora }}
↳ 14 days, 18 hours

timeuntil é análoga à timesince mas retornando a diferença entre uma data data e data futura.

title formata string como título de artigos e livros, colocando em maiúsculas as primeiras letras de cada palavra.

» titulo = "análise auxiliar de enrolação científica"
⎀ {{ titulo|title }}
↳ Análise Auxiliar De Enrolação Científica

truncatechars realiza o truncamento de um texto em um número especificado de caracteres. O texto truncado é seguido de elipses .

» texto = "Esta é uma nota grande."
⎀ {{ texto|truncatechars:15 }}
↳ Esta é uma nota...
# nada é feito de o texto for menor que o parâmetro de truncamento
⎀ {{ texto|truncatechars:35 }}
↳ Esta é uma nota grande.

truncatechars_html é similar à truncatechars mas evitando o descarte de tags html.

» texto = "

Esta é uma nota grande.

" ⎀ {{ texto|truncatechars_html:15 }} ↳ <p>Esta é uma not...</p>

truncatewords trunca uma string após um número dado de palavras. O texto truncado é seguido de elipses . Quebras de linha são removidas.

» texto = "Um texto com\n muitas palavras pode ser cortado"
⎀ {{ texto|truncatewords:4 }}
↳ Um texto com muitas...

truncatewords_html é similar à truncatewords, mas evitando eliminar tags html. Quebras de linha são mantidas.

» texto = "<p>Um texto com\n muitas palavras pode ser cortado</p>"
⎀ {{ texto|truncatewords:4 }}
↳ <p>Um texto coman muitas...</p>

unordered_list constroi uma lista html à partir de listas e listas aninhadas, inserindo recursivamente sublistas quando necessário.

» lista = ['Estados', ['Minas Gerais', ['Juiz de Fora', 'Belo Horizonte'], 'Paraná']]
⎀ {{ lista|unordered_list }}
↳
<li>Estados
<ul>
        <li>Minas Gerais
        <ul>
                <li>Juiz de Fora</li>
                <li>Belo Horizonte</li>
        </ul>
        </li>
        <li>Paraná</li>
</ul>
</li>

Observe que as tags de abertura e fechamento da lista externa (<ul></ul>) não são incluídas.

upper converte todos os caracteres de uma string em maiúsculas.

» texto = "Um texto com algumas palavras."
⎀ {{ texto|upper }}
↳ UM TEXTO COM ALGUMAS PALAVRAS.

urlencode transforma uma string para uso como url.

» url = "https://phylos.net/foo?a=b"
⎀ {{ url|urlencode }}
↳ https%3A//phylos.net/foo%3Fa%3Db

urlize converte uma URL ou endereço de email em links clicáveis.

» url = "Visite minha página em phylos.net"
⎀ {{ url|urlize }}
↳ Visite minha página em phylos.net

# emails também são convertidos
» email = "Mande sua mensagem para usuario@exemplo.com"
⎀ {{ email|urlize }}
↳ Mande sua mensagem para usuario@exemplo.com

O atributo rel=”nofollow” é acrescentado.

urlizetrunc age como urlize mas truncando urls longas para a exibição>

{{ url|urlizetrunc:15 }}

wordcount retorna número de palavras no parâmetro.

» texto = "Um texto com algumas palavras."
⎀ {{ texto|wordcount }}
↳ 5

wordwrap quebra o texto em comprimento especificado, inserindo quebra de linhas. wordwrap:n não quebra palavras mas sim a linha em valores inferiores a n dado como comprimento.

Implements word wrapping by inserting a newline character every n characters. Useful for plain text, but not typically for HTML.

» texto = "Um texto com algumas palavras."
⎀ {{ texto|wordwrap:17 }}
↳ Um texto com
↳ algumas palavras.

yesno retorna uma string especificada para o valor do parâmetro True, False ou None (opcional).

» condicao = False
⎀ {{ condicao|"Sim, Não, Talvez" }}
↳ Não

» condicao = True
⎀ Você respondeu: {{ condicao|"Afirmativo, Negativo" }}
↳ Você respondeu: Afirmativo

Bibliografia

Livros

  • Newman, Scott: Django 1.0 Template Development, 2008 Packt, 2008.

Sites

todos acessados em julho de 2022.

Templates do Django

Templates do Django

Como vimos em Introdução ao Django (1), (2) e (3) o framework usa a arquitetura MVT, Model, View, Template, para gerar HTML dinamicamente. Um quadro pode ajudar a esclarecer o modelo.


Descrição do modelo MVT

  • O navegador envia uma requisição para o servidor rodando django (1).
  • A URL é recebida por urls.py que atribui uma view para tratamento da requisição.
  • A camada view consulta Model (2) para receber os dados requisitados (3).
  • Depois de obter os dados View consulta a camada Template (4) para formatar a apresentação final (5) e envia páginas html formatadas para o navegador cliente (6).

Templates são as partes estáticas da saída HTML desejada, adicionadas de tags e variáveis que usam uma sintaxe especial para descrever como o conteúdo dinâmico será inserido, chamada de Django Model Language, (DTL). Por default o django usa o Jinja2, que pode ser alterado se necessário. Dizemos que o template renderiza o modelo inserindo nas variáveis os valores recebidos de acordo com o contexto. O suporte para modelos e templates (o DTL) estão no namespace Django.template.
Advertência: Em todo esse artigo nos referimos a dois tipos diferentes de tags: tags html são as marcações usuais para páginas na web, como <body>, <p>, <h1>, <table> , e possuem o mesmo significados que em páginas estáticas. Já as tags do djangos são as marcações próprias do jinja2 (por default) para indicar a inserção de conteúdo fornecido dinamicamente pelo aplicativo. Usaremos aqui, para facilitar a leitura, a seguinte representação:

# comentários    
» Código python, geralmente dentro de uma view,
⎀ representação de marcação do django, geralmente em pasta app/template,
↳ resultado após a renderização.

Por exemplo:

» nome = "Policarpo"; sobrenome= "Quaresma"
⎀ Meu nome é {{ nome }}, meu sobrenome é {{ sobrenome }}
↳ Meu nome é Policarpo, meu sobrenome é Quaresma

Sintaxe

O sistema de templates substitui tags e variáveis por seus valores fornecidos pelo contexto. Todo o restante do texto e tags html são retornados literalmente.

Variáveis

Uma variável é substituída por seu valor no contexto, com a marcação {{ var }}. Um contexto é um conjunto de valores, em um objeto tipo dicionário (um conjunto de chaves:valores), passado pelas views para o template. Os nomes das variáveis são passados como strings.

» contexto = {"n1": "um"; "n2":"dois";}
⎀ Temos no contexto {{ n1 }} e {{ n2 }}
↳ Temos no contexto {{ um }} e {{ dois }}

Usamos a notação de ponto para fazer a busca em chaves de um dicionário, exibição de atributo em objetos e recuperação de valor em lista por meio de seu índice:

» dicionario.chave
» um_objeto.atributo
» uma_lista.0

Se o valor da variável for uma função ou outro objeto que pode ser chamado (um callable) o sistema fará uma chamada ao objeto e retornará seu valor.

Tags e Filtros

Tags são mecanismos de controle geral da renderização de templates. Elas podem controlar o fluxo de código (como em if, for), podem dar acesso à outro arquivo de template ou inserir modos de controle. Tags aparecem dentro da marcação {% tag %}. Por exemplo:

# suponha que temos o objeto usuario com atribuitos nome e super
» usuario.nome = "Fulaninho del'Valle"; usuario.super = True
⎀ Nome do usuário: {{ usuario.nome }}. {% if usuario.super }} É supersuser! {% endif %}
↳ Nome do usuário: Fulaninho del'Valle. É supersuser!

Um exemplo de tag de controle é {% csrf_token %}, que colocado dentro de um formulário impede o ataque tipo CSRF ao site.

Filtros são formas de modificar as saídas de variáveis. Por ex.:

» contexto = {'django': 'o framework web para perfecionistas com prazos'}
⎀ {{ django|title }}
↳ O Framework Web Para Perfecionistas Com Prazos
# ou
» contexto = {'data': '2022-07-04'}
⎀ {{ data|date:"d/m/Y" }}
↳ 04/07/2022

Tags do Django

Uma descrição mais extensa das tags pode ser lida em
Docs: Built-in Tags.

Comentários podem ser inseridos das seguintes formas:

# isto é um comentário em uma linha
⎀  {# isto é um comentário e será ignorado #}
# comentários em várias linhas
⎀  {% comment %}
⎀    <p>Linha 1</p>
⎀    <p>Linha 2</p>
⎀  {% endcomment %}

csrf_token fornece uma forma de defesa contra a falsificação de solicitações entre sites (Cross Site Request Forgeries).

⎀ <form>{% csrf_token %} ... conteúdo do formulario</form>

Block e Extends são as ferramentas básicas para se usar herança de templates. block define uma área em um template base que pode ser substituído por conteúdo no arquivo que o importa.
base.html

<!DOCTYPE html>
<html>
<head>
</head>
<body>
  {% block bloco1 %} texto default 1 {% endblock %}
  {% block bloco2 %} texto default 2 {% endblock %}
</body>
</html>

e o template que o importa:
inicial.html

{% extends "base.html" %}
{% block title %} texto 1 {% endblock %}
{% block content %} texto 2 {% endblock %}

{% extends "base.html" %} faz com que inicial.html seja uma extensão do template base.html. Ele pode receber o nome (com caminho) do arquivo base ou uma variável que contém esse nome. Caso um bloco não exista na página que herda base.html o conteúdo inicial (ou nenhum, se não existir) é mantido.

{% extends arquivo %} pode ser usado de duas formas:

{% extends "base.html" %}
{% extends nome_do_arquivo %}

O arquivo base.html deve ser um arquivo html completo, com cabeçalhos, importações de css, etc. Ele contém blocks (que já descrevermos) que são substuídos pelos valores em nome_do_arquivo.html.

Por exemplo, considere o exemplo do arquivo base:
base.html

<!DOCTYPE html>
<html>
<head>
</head>
<body>
  <h1>Meu Aplicativo</h1>
  {% block title %}{% endblock %}
  
  {% block content %}Esse é meu site com o meu aplicativo  {% endblock %}
</body>
</html>

e o template que o importa:
inicial.html

{% extends "base.html" %}
{% block title %}<h1>Aplicativo de Notas</h1>{% endblock %}

{% block content %}
<h2>Esse é o conteúdo da bloco content</h2>
{% endblock %}

Quando o arquivo inicial.html é chamado ele usa base.html, substituindo nele os blocos title e content. O resultado final será a página com html puro:

<!DOCTYPE html>
<html>
<head>
</head>
<body>
  <h1>Meu Aplicativo</h1>
  <h1>Aplicativo de Notas</h1>
  <h2>Esse é o conteúdo da bloco content</h2>
</body>
</html>

Deve-se ter o cuidado para indicar os caminhos corretos.

# ambos os arquivos estão no mesmo diretório    
{% extends "base.html" %}
# ou
{% extends "./base.html" %}
# base02.html está um diretório acima
{% extends "../base02.html" %}
# base03.html está em outro diretório dentro do atual
{% extends "./outro/base03.html" %}

Os caminhos são relativos ao diretório informado em settings.py:

TEMPLATES = [{
    'BACKEND': 'django.template.backends.django.DjangoTemplates',
    'DIRS': [BASE_DIR / 'templates'],
}]

include é usado para inserir e renderizar outro template dentro do contexto atual. Ele pode ser usado com referências literais a um arquivo, ou com o nome referenciado em variável.

{% include "caminho/menu.html" %}
{% include nome_do_template %}

Por ex., suponha que o template
ola.html

{{ sauda }}, {{ nome }}!

é incluído em
saudacao.html

<h1>Página Inicial de nosso Site:</h1>    
{% include "ola.html" %}

Se temos um contexto contexto = {"sauda":"Bom dia";"nome":"Augusto dos Anjos"} e passamos esse contexto para saudacao.html temos o seguinte template final renderizado:

<h1>Página Inicial de nosso Site:</h1>
<p>Bom dia, Augusto dos Anjos!</h1p>

Variáveis pode ser passadas diretamente para o template usando keywords, ou com o parâmetro only para usar apenas essas variáveis, ignorando as demais.

{% include "ola.html" with sauda="Boa noite" nome="James Gomes" %}
{% include "ola.html" with sauda="Boa tarde" only%}

Observação: Blocos são avaliados antes da inclusão, ou seja, as variáveis de contexto são substituídas antes que a inserção em outro template seja feita.

load é usado para inserir no ambiente atual um conjunto de tags e filtros já importados em outro template. Por ex., se temos duas bibliotecas biblio1 e biblio2 localizadas em pacote1 e pacote2 respectivamente (como bibliotecas devem estar em diretórios contendo o arquivo __init__.py) poemos importar todos os templates e filtros neles definidos com a inserção de load. Para selecionar apenas alguns filtros ou tags usamos from.

{% load pacote1.biblio1 pacote2.biblio2 %}
{% load tag1 tag2 from pacote1.biblio1 %}

Essa tag é especialmente útil para tags e filtros customizados.

url retorna o caminho absoluto (a URL sem nome de domínio) apontando para uma view com seus parâmetros. Essa é uma forma de exibir links dentro de templates sem forçar a construção de URLs fixas, que podem ser quebradas se o aplicativo for realocado para outro diretório.

{% url 'nome_url' arg1 arg2 %}
# 'nome_url' é o padrão da url, os argumentos arg1 e arg2 são passados para a view

nome_url pode ser uma string literal ou variável de contexto. Os parâmetros são separados por espaços. Alternativamente podemos usar argumentos nomeados:

{% url 'nome_url' arg1=arg1 arg2=arg2 %}
Lembramos que um projeto django é formado por um ou mais aplicativos. O código relativo aos aplicativos ficam em subdiretórios do diretório geral do projeto. Temos portanto as pastas diretorio_projeto e diretorio_projeto/aplicativo.

Por ex., suponha que temos uma view para um aplicativo em aplicativo_views.livro que recebe um id para exibir um livro cadastrado, ou seja livro() é um método em aplicativo/views. Em aplicativo/urls.py existe a linha,

path('livro//', aplicativo_views.livro, name='view_livro')

No arquivo de urls do projeto (em nível acima), projeto.urls podemos ter

path('livros/', include('projeto.aplicativo.urls'))

Em um template podemos usar, para acessar essa view:

{% url 'view_livro' livro.id %}
# que vai ser renderizado como (por ex. para livro.id=456)
/livros/livro/456

Se a URL não tiver uma view correspondente uma exceção NoReverseMatch será lançada. Também é possível armazenar em variável uma url para uso posterior, dentro do mesmo bloco, como em

{% url 'nome_da_url' arg1 arg2 as minha_url %}
<a href="{{ minha_url }}">Visita a minha página em {{ minha_url }}</a>

Nenhum erro é lançado se a view não existe, o que torna essa forma útil para exibir links que podem não existir, no contexto:

{% url 'nome_da_url' arg1 arg2 as minha_url %}
{% if minha_url %}
  Link para a página opcional.
{% endif %}

Se a url estiver definida dentro de um namespace especificado, como meu_app (o que é útil para evitar conflitos de nomes, podemos usar seu nome “totalmente qualificado” (fully qualified name):

{% url 'meu_app:nome_da_view' %}

Os nomes de padrões para as urls devem ser colocados entre aspas ou serão interpretados como variáveis de contexto!

for é usado para percorrer um laço sobre uma sequência de valores passada no contexto.

# frutas é uma tupla no contexto
» frutas = ('pera','maça','limão')
⎀ <ul>
⎀   {% for fruta in frutas %}
⎀     <li>{{ fruta }}</li>
⎀   {% endfor %}
⎀ </ul>
# será renderizado como
↳ <ul>
↳   <li>pera</li>
↳   <li>maça</li>
↳   <li>limão</li>
↳ </ul>

# para percorrer a lista em ordem reversa usamos
⎀   {% for fruta in frutas reversed %}

Para percorrer uma lista de listas (ou outros objetos que são coleções ) é possível obter valores individuais em variáveis separadas. Por ex., se temos uma lista de pontos com suas respectivas coordenadas pode fazer:

# uma lista de pontos com lista de 2 coordenadas
» pontos = ((3,8), (7,4), (5,5))
⎀   {% for x, y in pontos %}
⎀     <p>Coordenada x={{ x }}, y={{ y }} </p>
⎀   {% endfor %}
# será renderizado como
↳ <p>Coordenada x=3, y=8</p>p>
↳ <p>Coordenada x=7, y=4</p>p>
↳ <p>Coordenada x=7, y=7</p>p>

Para listar ítens de dicionários, chaves e valores, fazemos calgo análogo:

# dado um dicionário dic_dados
⎀ {% for key, value in dic_dados.items %}
⎀     {{ key }}: {{ value }}
⎀ {% endfor %}

Observação: O operador de ponto para acesso à itens de dicionário tem precedência sobre o laço for. Se o dicionário contém uma chave ‘items’, dic_dados[‘items’] será retornado em lugar de dic_dados.items. Por isso se deve evitar dar nomes de chaves idênticos aos de métodos que podem ser usados em um template (como “items”, “values”, “keys”.)

for ... empty é uma condição alternativa de retorno quando a lista no laço está vazia. O texto explicitado é retornado:

# dias é uma lista vazia
» dias_livres = []
⎀ <ul>
⎀   {% for dia in dias_livres %}
⎀     <li>{{ dia }}</li>
    {% empty %}
⎀     <li>Não há nenhum dia livre!</li>
⎀   {% endfor %}
⎀ </ul>
# será renderizado como
↳ <ul>
↳   <li>Não há nenhum dia livre!</li>
↳ </ul>

Variáveis disponíveis no laço são variáveis que ficam disponíveis para uso durante um laço for.

Variável Descrição
forloop.counter o atual contador da iteração (base 1)
forloop.counter0 o atual contador da iteração (base 0)
forloop.revcounter quantas iterações faltam para o final do laço (base 1)
forloop.revcounter0 quantas iterações faltam para o final do laço (base 0)
forloop.first True apenas para a 1ª iteração no laço
forloop.last True apenas para a última iteração no laço
forloop.parentloop para laços aninhados esse é a iteração do laço externo

if é usado para testes condicionais. Sua sintaxe geral é:

{% if condicao1 %}
  Texto se condicao1==True
{% elif condicao2 %}
  Texto se condicao1==False e condicao2==True
{% else %}
  Texto se condicao1==False e condicao2==False
{% endif %}

Por ex., suponha que obj_carros é uma sequência de objetos carro com as propriedades carro.marca (str), carro.vendido (booleana). O template abaixo

  <p>Temos {{ obj_carros|length }} para venda</p>
  {% for carro in obj_carros %}
     Marca: {{ carro.marca }}  {%if carro.vendido %} Está vendido {% else %} Disponível {% endif %}
  {% empty %}
     Não há carros disponíveis!
  {% endfor %}

mostrará quantos carros existem para venda e uma listagem de todos os carros, com suas marcas e disponibilidade (ou não) para venda, ou a informação de que nenhum carro está disponível.

Operadores booleanos. As condições lógicas podem ser ligadas ou modificadas por operadores booleanos and, or e not, com significado usual no python.

{% if condicao1 and condicao2 %}
    Se ambas as condições são True.
{% endif %}

{% if condicao1 or condicao2 %}
    Se uma das, ou ambas as, condições são True.
{% endif %}

{% if not condicao1 %}
   Se a condições1 é False.
{% endif %}

{% if not condicao1 or condicao2 %}
    Se condicao1 == False ou condicao2==True.
{% endif %}

Se and e or são usadas na mesma tag and tem precedência sobre or. Ou seja

{% if condicao1 and condicao2 or condicao3 %}
# será interpretada como
{% if (condicao1 and condicao2) or condicao3 %}span>
# a sentença acima, no entanto, é inválida (parênteses não podem ser usados nesse ambiente)

Operadores: Os operadores válidos para construções de testes lógicos são ==, !=, <, >, <=, >=, in, not in, is, is not, com significado usual no python.

{% if var_string == "palavra" %} faça algo {% endif %}
{% if var_string != "palavra" %} faça algo {% endif %}
{% if var_numerica == 100 %} faça algo {% endif %}
{% if var_numerica !== 100 %} faça algo {% endif %}
{% if var_numerica > 100 %} faça algo {% endif %}
{% if var_numerica < 100 %} faça algo {% endif %}
{% if var_numerica <= 100 %} faça algo {% endif %}
{% if var_numerica >= 100 %} faça algo {% endif %}

# in procura por partes de uma string
{% if "adi" in "moradia" %}
  Texto caso "adi" seja parte (substring) de "moradia"
{% endif %}

{% if "adi" in var_string %}
  Texto caso "adi" seja substring da var_string
{% endif %}

{% if user in users %}
  Texto caso user (uma variável) seja um dos elementos da coleção users
{% endif %}

# is é um teste de identidade entre objetos. True se foram o mesmo objeto.
{% if var1 iscode> var2 %} var1 e var2 se referem ao mesmo objeto{% endif %}
{% if var iscode> None %} var é None ou não foi encontrada no contexto.{% endif %}

# is not é a negação do teste acima
{% if var1 is notcode> var2 %} var1 e var2 não se referem ao mesmo objeto{% endif %}
{% if var is notcode> None %}
  var não é None, portanto foi encontrada no contexto.
{% endif %}

Testes compostos podem ser construídos, seguindo a mesma ordem do python. Se A, B, C, E são booleanas ou expressões que avaliam em um booleano (expressões em negrito são inválidas):

{% if A ==code> B or C == D and E %}
# será interpretado como
{% (A ==code> B) or ((C ==code> D) and E) %}
# os parênteses não devem ser usados

# essa expressão é inválida
{% if A > B > C %}
# e deve ser escrita como
{% if A > B and B > C %}

# diferente do python isso não pode ser feito
{% if A > B > C %}
# faça
{% if A > B and B > C %}

Se for necessário usar regras de precedência diferentes use tags aninhadas.

if changed é uma tag que verifica se um ítem de uma lista foi alterado entre iterações dentro de um laço. Se passamos um contexto com uma lista de fornecedores como um objeto com nome e 3 ítens quaisquer, sendo que mais de um registro existe para cada um deles, podemos exibir o nome do fornecedor apenas uma vez.

<h1>Lista de Fornecedores</h1>
{% for fornecedor in fornecedores %}
    {% ifchanged %}<h3>Nome do fornecedor {{ fornecedor.nome }}</h3>{% endifchanged %}
    <p>{{ fornecedor.item1 }}, {{ fornecedor.item2 }}, {{ fornecedor.item3 }}</p>
{% endfor %}

Se mais de uma variável é verificada, cada mudança de valor produz uma saída. No template seguinte, supondo que o objeto fornecedor possui um atributo fornecedor.cidade e que cada um pode estar em mais de uma cidade, o nome é exibido sempre que trocado, e a cidade é exibida quando o nome ou a cidade são trocados.

<h1>Lista de Fornecedores</h1>
{% for fornecedor in fornecedores %}
    {% ifchanged fornecedor.nome %}
      <h3>Nome do fornecedor {{ fornecedor.nome }}</h3>
    {% endifchanged %}
    {% ifchanged fornecedor.cidade %}
      <h3>Cidade {{ fornecedor.cidade }}</h3>
    {% endifchanged %}
    <p>{{ fornecedor.item1 }}, {{ fornecedor.item2 }}, {{ fornecedor.item3 }}</p>
{% endfor %}

Claro que uma boa exibição desses templates depende se estar a lista ordenada nos campos verificados. Uma cláusula else pode ser fornecida para inserir conteúdo se não houver qualquer mudança.

<h1>Lista de Fornecedores</h1>
{% for fornecedor in fornecedores %}
    {% ifchanged fornecedor.nome %}<h3>Nome do fornecedor {{ fornecedor.nome }}</h3>
    {% else %} <p>(continuando...)</p>
    {% endifchanged %}
    <p>{{ fornecedor.item1 }}, {{ fornecedor.item2 }}, {{ fornecedor.item3 }}</p>
{% endfor %}

cycle é uma tag que retorna um de seus argumentos, em ciclo, a cada vez que é acessada. Quando todos os argumentos forem esgotados o primeiro deles é produzido novamente. Qualquer número de valores pode ser usado nos ciclos. Por ex.:

# considere que classe1 e classe2 estão definidas como classes no arquivo css
<table>
{% for obj in alguma_lista %}
    <tr class="{% cycle 'classe1' 'classe2' %}"><td>...</td></tr>
{% endfor %}
</table>

O template exibirá uma tabela com linhas alternadas com formatação da classe1 e classe2, até o fim do laço.
Variáveis podem ser usadas:

{% for obj in alguma_lista %}
    <tr class="{% cycle var_linha1 var_linha2 %}"> ... </tr>
{% endfor %}

# variáveis são escapadas. Isso pode ser alterado com
{% for obj in alguma_lista %}
    <tr class="{% autoescape off %}{% cycle rowvalue1 rowvalue2 %}{% endautoescape %}"> ... </tr>
{% endfor %}

# podemos usar strings e variáveis juntas
{% for obj in alguma_lista %}
    <tr class="{% cycle 'linha1' var_linha2 'linha3' %}"> ... </tr>
{% endfor %}

Podemos atribuir uma aliás ao ciclo e utilizá-lo depois, com seu valor atual. Para progredir no ciclo reusamos {% cycle %}:

# o template
⎀ <tr>
⎀     <td class="{% cycle 'linha1' 'linha2' as linhas %}">...</td>
⎀     <td class="{{ linhas }}">...</td>
⎀ </tr>
⎀ <tr>
⎀     <td class="{% cycle linhas %}">...</td>
⎀     <td class="{{ linhas }}">...</td>
⎀ </tr>

# será renderizado como
↳ <tr>
↳     <td class="linha1">...</td>
↳     <td class="linha1">...</td>
↳ </tr>
↳ <tr>
↳     <td class="linha2">...</td>
↳     <td class="linha2">...</td>
↳ </tr>

Podemos usar a tag resetcycle para zerar o ciclo. Por ex., se temos uma lista de objetos pessoa, com propriedades pessoa.nome (uma string) e pessoa.filhos (uma lista de strings):

{% for pessoa in lista_de_pessoas %}
    <h1>{{ pessoa.nome }}</h1>
    {% for filho in pessoa.filhos %}
        <p class="{% cycle 'par' 'impar' %}">{{ filho.nome }}</p>
    {% endfor %}
    {% resetcycle %}
{% endfor %}

Nesse template toda lista de filhos, para cada pessoa, começa com formatação de classe “par”.

firstof recebe diversas variáveis como argumento e exibe o primeiro argumento que não é False. Lembrando, são avaliadas como True as variáveis que existem, não são vazias ou Null, não são o booleano False e não são o 0 numérico. firstof não exibe coisa alguma se todas as variáveis incluídas são False.

# o template
{% firstof var1 var2 var3 %}

# é o mesmo que
{% if var1 %} 
  {{ var1 }}
{% elif var2 %}
  {{ var2 }}
{% elif var3 %}
  {{ var3 }}
{% endif %}

Um valor default pode ser exibido se nenhuma das variáveis é True

{% firstof var1 var2 var3 "valor default" %}

Os valores exibidos são “escapados”. Esse comportamento pode ser revertido com:

{% autoescape off %}
    {% firstof var1 var2 var3 "<b>Texto em negrito</b>" %}
{% endautoescape %}

# para escapar apenas algumas variáveis usamos o filtro escape sobre essas variáveis
{% firstof var1 var2|safe var3 "<b>Negrito</b>|safe" %}

# também podemos armazenar a variável em um aliás para uso posterior:
{% firstof var1 var2 var3 as valor_alias %}

regroup realiza um reagrupamento de uma lista de objetos baseado em um atributo comum. Por ex., suponha que temos um dicionário que descreve alunos de uma escola, listando seus nomes, idades e series:

alunos = [
    {'nome':'Marcos', 'idade':'8','serie':'1'}
    {'nome':'Ana', 'idade':'7','serie':'1'}
    {'nome':'Marta', 'idade':'10','serie':'1'}
    {'nome':'Pedro', 'idade':'9','serie':'2'}
]

Para exibir uma lista organizada hieraquicamente pela série, usamos {% regroup %}

{% regroup alunos by serie as aluno_serie %}
<ul>
{% for serie in aluno_serie %}
    <li>Série: {{ serie.grouper }}
    <ul>
        {% for aluno in aluno_serie %}
          <li>Nome: {{ aluno.nome }}, Idade: {{ aluno.idade }}</li>
        {% endfor %}
    </ul>
    </li>
{% endfor %}

 

O resultado desse template é o seguinte:

  • Serie: 1
    • Nome: Marcos, Idade: 8
    • Nome: Ana, Idade: 7
    • Nome: Marta, Idade: 10
  • Serie: 2
    • Nome: Pedro, Idade: 9

Nesse exemplo alunos a a coleção que queremos ordenar, serie o atributo usado na ordenação, e aluno_serie um alias para a lista resultante. O objeto gerado, nomeado por aluno_serie, é do tipo namedtuple com 2 campos:

  • grouper – o item usado no agrupamento, no caso as strings “1” e “2”.
  • list – uma lista dos ítems do grupo, no caso alunos com atributos aluno.nome e aluno.idade.

Considerando que aluno_serie é uma namedtuple o mesmo código poderia ser escrito dessa forma:

{% regroup alunos by serie as aluno_serie %}
<ul>
{% for serie, alunos in aluno_serie %}
    <li>Série:  {{ serie }}
    <ul>
        {% for aluno in alunos %}
          <li>Nome: {{ aluno.nome }}, Idade: {{ aluno.idade }}</li>
        {% endfor %}
    </ul>
    </li>
{% endfor %}
</ul>

É importante notar que {% regroup %} não faz um reordenamento dos listas, o que deve ser feito previamente, em geral dentro da view que retoena esses valores. Alternativamente, se os dados a serem agrupados estão em uma lista de dicionários, como no exemplo, podemos fazer um ordenamento dentro do template usando o filtro dictsort.

{% regroup alunos|dictsort:"serie" by serie as aluno_serie %}
# alunos|dictsort:"serie" retorna a lista ordenada no campo "serie"

Qualquer outra propriedade dos ojetos ordenados pode ser usada por regroup, incluindo propriedades de objetos, chaves e itens de dicionários.

with é usada para armazenar uma variável sob um nome simples. Essa variável pode envolver, por ex., uma operação complexa em uma query em um banco de dados:

{% with total=escola.professores.count %}
    Essa escola tem {{ total }} professor.
{% endwith %}

# alternativamente
{% with escola.professores.count as total %} ... {% endwith %}

# mais de uma variável pode ser definida
{% with alpha=1 beta=2 %}
    ...
{% endwith %}

A variável tem como escopo a intervalo entre tags {% with %} e {% endwith %}.

autoescape controla o comportamento de escape de marcações html dentro do bloco. Por default tags html são exibidos sem renderização, por motivo de segurança. Com autoescape on as tags se tornam funcionais, como em uma página usual de html. Recebe apenas os parâmetros on ou off.

# por ex.
» variavel = "<p>Texto de <b>teste</b>!</p>"
⎀ {% autoescape off %}
⎀    {{ variavel }}
⎀ {% endautoescape %}
# renderiza como
↳ <p>Texto de <b>teste</b>!</p>

# por outro lado, se autoescape on
⎀ {% autoescape on %}
⎀    {{ variavel }}
⎀ {% endautoescape %}
# o texto é renderizado dentro de um parágrafo html

Isso é equivalente a usar o filtro var|safe em todas as variáveis do bloco. Variáveis marcadas com var|safe são renderizadas mesmo que estejam dentro de bloco {% autoescape off %}.

lorem é a tag usada para exibir texto em latim, geralmente usado para testes. Seu uso é

{% lorem [count] [method] [random] %}

onde todos os argumentos são opcionais.

Argumento Descrição
count número ou variável com o número de parágrafos ou palavras a serem gerados. (default é 1).
method pode ser w, palavras, p parágrafos ou b para texto puro (default é b).
random “random” gera texto aleatório e não (“Lorem ipsum dolor sit amet…”).
{% lorem %}
# gera parágrafo "lorem ipsum".
{% lorem 3 p %}
# gera parágrafo "lorem ipsum" e 2 parágrafos aleatórios entre tags <p>.
{% lorem 2 w random %}
# gera 2 palavras latinas aleatórios.

now exibe data/hora corrente como string, em formato especificado. Veja sobre filtros para maiores descrição da formatação.

⎀ {% now "D M Y H T " %}
# dia, mes, ano, hora

⎀ It is the {% now "jS \o\f F" %}
↳ It is the 4th of February

⎀ Hoje é {% now "D/M/Y" %}
↳ Hoje é 04/06/2022

⎀ {% now "SHORT_DATETIME_FORMAT" %}

O último exemplo usa formatos predefinidos, como DATE_FORMAT, DATETIME_FORMAT, SHORT_DATE_FORMAT ou SHORT_DATETIME_FORMAT que são renderizados de acordo com as variáveis de ambiente.

A sintaxe {% now “Y” as ano_atual %} armazena uma string de representação da data na variável ano_atual.

{% now "Y" as ano_atual %}
Copyright {{ ano_atual }}

verbatim é uma tag usada para interromper a renderização dos templates e apresentá-los literalmente.

⎀ {% verbatim %}
⎀     {{if certo}} Está certo! {{ endif }}
⎀ {% endverbatim %}

# será exibido
↳ {{if certo}} Está certo! {{ endif }}

Essa tag pode ser usada para evitar conflito com código javascript inserido em templates.

spaceless é a tag usada para remover espaços am branco, controles de tab e newline inseridos entre tags.

⎀ {% spaceless %}
⎀     <p>
⎀         <a href="http://meu_link.com/">Meu site</a>
⎀     </p>
⎀  {% endspaceless %}

# retorna
↳ <p><a href="http://meu_link.com/">Meu site</a></p>

# Espaços entre tags e texto não são alterados
{% spaceless %}
    <strong>
        Olá mundo!
    </strong>
{% endspaceless %}
# não sofre alterações

Essa tag tem uso limitado pois a renderização usual de html pelos navegadores ignoram esses espaços, tabs e newlines.

templatetag é usado para exibir os caracteres de tags em templates, similar a um escape desses caracteres.

Argumento exibe Argumento exibe
openblock {% openbrace {
closeblock %} closebrace }
openvariable {{ opencomment {#
closevariable }} closecomment #}
⎀ {% templatetag openblock %}
# retorna
↳ {%

widthratio é tag usada para gráficos de barras. Ela calcula a razão entre um valor variável dado e um valor máximo e aplica o resultado à uma constante. Por exemplo,

<img src="barra.png" height="10" width="{% widthratio valor_variavel valor_maximo largura_maxima %}">
# esse valor pode ser armazenado para uso posterior
{% widthratio valor_variavel valor_maximo largura_maxima as largura_calculada %}

Se valor_variavel=175, valor_maximo=200 e largura_maxima=100 a imagem acima barra.png terá a largura de 88 pixels pois 175/200 = .875; .875 * 100 = 87.5 arredondada para 88. Ou seja largura_calculada = 88.

Internacionalização: traduzindo aplicativos

Existem tags e filtros voltados para a internacionalização de aplicativos, facilitando a tradução de textos especificados. Mais detalhes em Django Docs: Internacionalização e
Tradução.

i18n é uma biblioteca que permite especificar qual texto dentro de templates devem ser traduzidos. Para isso deve-se ajustar a variável USE_I18N=True e carregar o código necessário com a tag {% load i18n %}.

l10n é uma biblioteca que permite localizar valores dentro de templates. Ela deve ser carregada com a tag {% load l10n %}.

tz é biblioteca para estabelecer conversões entre time zones. Deve ser carregada com a tag {% load tz %}. Também se pode ajustar a variável USE_TZ=True para a conversão ser automática para a time zone local.

Outras tags importantes

static é uma tag para estabelecer links para arquivos estáticos gravados no diretório estabelecida na variável STATIC_ROOT, dentro de settings.py. Se django.contrib.staticfiles é um dos aplicativos instalados (ou seja, se é um dos ítem da lista INSTALLED_APPS) essa tag indicará o caminho dos arquivos estáticos usando o método url(), … especificado em STATICFILES_STORAGE.

{% load static %}
<img src="{% static 'imagens/ola.jpg' %}">

{% load static %}
<link rel="stylesheet" href="{% static arquivo_css %}" type="text/css" media="screen">

{% load static %}
{% static "imagens/ola.jpg" as ola %}
<img src="{{ ola }}">

get_static_prefix pode ser usada juntamente com static para garantir maior controle sobre o local onde STATIC_URL é inserida no template:

{% load static %}
<img src="{% get_static_prefix %}imagens/ola.jpg">

# se o valor será usado várias vezes ele pode receber uma alias
{% load static %}
{% get_static_prefix as STATIC_PREFIX %}

<img src="{{ STATIC_PREFIX }}imagens/logo.jpg">
<img src="{{ STATIC_PREFIX }}imagens/ola.jpg">

get_media_prefix é similar à get_static_prefix mas insere no template o valor em MEDIA_URL:

{% load static %}
<body data-media-url="{% get_media_prefix %}">

Arquivos estáticos

Arquivos adicionais, e não apenas texto html, fazem parte de qualquer website. Esses arquivos incluem arquivos de imagens ou vídeos, de formatação css e javascript e o django os denomina arquivos estáticos (static files). Para gerenciar esses arquivos o django inclui o módulo django.contrib.staticfiles.

Para servir arquivos estáticos precisamos fazer:

  1. incluir o módulo django.contrib.staticfiles na lista INSTALLED_APPS, em settings.py,
  2. ajustar a variável STATIC_URL', também em settings.py,
  3. armazenar arquivos estáticos em diretório static, dentro do diretório raiz do projeto,
  4. nos templates, use a tag static para construir a URL para uma posição relativa, configurada em STATICFILES_STORAGE.

Por exemplo:

# em settings.py
INSTALLED_APPS = [
           ...,
           'django.contrib.staticfiles',
           ...
]
...
STATIC_URL = 'static/'

# armazena
meu_app/static/imagens/meu_logo.jpg.

# no template
{% load static %}
<img src="{% static 'imagens/meu_logo.jpg' %}">

É possível também incluir no projeto arquivos estáticos usados mais de um aplicativo. Além dos diretórios applicativo/static podemos ter uma lista de diretórios alternativos na lista STATICFILES_DIRS em settings.py. O django vai procurar em todos eles esses arquivos estáticos.

Por exemplo,

from pathlib import Path
import os

BASE_DIR = Path(__file__).resolve().parent.parent
    
STATICFILES_DIRS = [
       os.path.join(BASE_DIR, 'static'),
       os.path.join(BASE_DIR, 'base_static'),
       ...,
]

Nesse caso BASE_DIR é o diretório um dois níveis acima daquela onde reside o arquivo settings.py. À partir deles contruimos BASE_DIR/static e BASE_DIR/base_static.

Advertência: durante a fase de desenvolvimento, com a variável DEBUG=True em settings.py e você está usando django.contrib.staticfiles, os arquivos estáticos são entregues pelo servidor quando se roda runserver. Esse é, no entanto, um método pouco eficiente e inseguro, não apropriado para a produção.

Uma consideração especial deve ser dada à fase de implantação em produção. Basicamente é necessário rodar o comando collectstatic para que os arquivos estáticos sejam coletados no diretório apropriado STATIC_ROOT. Em seguida esse diretório deve ser movido para local que depende de qual servidor é usado.

Filtros do Django

Continua a leitura em Filtros em Templates no Django.

Bibliografia

Livros

  • Newman, Scott: Django 1.0 Template Development, 2008 Packt, 2008.

Sites

todos acessados em julho de 2022.

Outros artigos nesse site:

Django, incrementando o Projeto

Sofisticando o modelo com chaves externas e datas

Vamos aprimorar o aplicativo classificando nossas notas por categorias, permitindo diversos cadernos separados. Para efeito didático usaremos apenas um nível de categoria. Idealmente essas poderiam ser subdivididas em subcategorias de vários níveis.

Para isso criaremos modelos para as seguintes tabelas:

Caderno
id pk, automatico
caderno texto
Categoria
id pk, automatico
categoria texto
Notas
id pk, automatico
caderno fk –> caderno
categoria fk –> categoria
titulo texto
texto texto
slug texto

Vamos fazer alterações nos modelos, que serão aplicadas no banco de dados com migrations.
app_notas/notas/models.py

from django.db import models
from django.utils.text import slugify

class Caderno(models.Model):
    titulo = models.CharField(default='', max_length=150)
    def __str__(self):
        return self.titulo

class Categoria(models.Model):
    categoria = models.CharField(default='', max_length=150)
    def __str__(self):
        return self.categoria

class Nota(models.Model):
    titulo = models.CharField(default='', max_length=150, help_text="Título da Nota")
    texto = models.TextField(default='', blank=True)
    slug = models.SlugField(default='', blank=True, max_length=255)
    data_criada = models.DateField(auto_now_add=True, help_text="Data de Criação")
    data_editada = models.DateField(auto_now=True, help_text="Data de Edição")
    caderno = models.ForeignKey(Caderno, on_delete=models.CASCADE)
    categoria = models.ForeignKey(Categoria, on_delete=models.CASCADE)

    def __str__(self):
        return self.titulo

    def save(self,*args,**kwargs):
        self.slug = slugify(self.titulo)
        super().save(*args,**kwargs)

As partes alteradas estão em negrito. Inserimos duas novas tabelas: Caderno e Categoria. Cada uma delas tem seu id criado automaticamente pelo django. Na tabela Nota acrescentamos caderno e categoria. Definidos como ForeignKey eles ficam associados às novas tabelas da seguinte forma:

nota.caderno (fk) ⟼ caderno.id
nota.categoria (fk) ⟼ categoria.id

O argumento help_text permite a especificação de texto descritivo que será exibido juntos com forms, como veremos.

Os campos do tipo DateField armazenam datas. (DateTimeField armazenam datas e horas). O argumento auto_now_add=True ajusta a campo para a hora do momento de criação do valor do campo enquanto
auto_now=True ajusta a campo na hora da última edição, quando o objeto é gravado.

A chave externa (fk) define uma relação de muitos-para-um. Por ex., cada nota tem apenas uma categoria mas podem haver diversas outras notas na mesma categoria.

O argumento on_delete informa como registros ligados por chaves externas devem ser tratados em caso de apagamento do registro principal. Ele pode receber diversos valores:

CASCADE força o apagamento de dados conectados pela chave externa.
PROTECT impede o apagamento de dados conectados pela chave externa. Levanta exceção ProtectedError.
RESTRICT similar à PROTECT, mas levanta erro RestrictedError.
SET_NULL mantém dados conectados pela chave externa, substituindo seu valor por NULL. O parâmetro deve ser ajustado na definição do modelo.
SET_DEFAULT similar à SET_NULL mas substitui o valor da chave por seu valor default. O parâmetro deve ser ajustado na definição do modelo.
SET( ) permite a construção de funções customizadas para atribuir valores ao campo fk apagado.
DO_NOTHING nenhuma atitude é tomada e os dados ligados por fk permanecem inalterados.
Uma descrição mais detalhadas por ser vista em Zerotobyte: On delete explained.

Além disso inserimos dois campos de data, data_criada e data_editada, que são preenchidas por default com a data do momento da operação.

Com essas alterações o banco de dados atual fica inconsistente, pois existem campos que demandariam fk e que estão vazios. Como estamos em desenvolvimento e os dados que temos são apenas experimentais, uma boa tática é a de resetar o banco de dados. Essa é também uma boa oportunidade para mencionar como isso é feito.

Resetando o banco de dados

Podemos ver todas as migrations feitas até agora com o comando:

$ python manage.py showmigrations

Para remover as alterações no BD e as migrations do django, removemos o conteudo do BD. No nosso caso, como estamos usando o SQLite, basta apagar o arquivo db.sqlite3 que fica na pasta do projeto app_notas/app_notas. Em seguido removemos todo o conteúdo da pasta migrations, na pasta de cada aplicativo (app_notas/notas no caso), exceto o arquivo __init__.py.

Podemos agora reconstruir o BD e refazer a conta de superuser:

$ python manage.py makemigrations
$ python manage.py migrate
$ python manage.py createsuperuser


Observações: Se você está usando outro BD, como PostGRE ou MySQL você deve apagar os bancos usando a sintaxe própria de seu gerenciador. Outras informações podem ser encontradas em Techiediaries: Resetting Django Migrations, inclusive sobre como zerar as migrations em produção, manualmente ou usando um app para isso.

Usando o app ampliado

Depois de termos ampliado as definições de nossos modelos, devemos incluir nos novos modelos em admin.py para que eles possam ser gerenciados pelo admin.
app-notas/notas/admin.py

from django.contrib import admin
from notas.models import Caderno, Categoria, Nota

admin.site.register(Caderno)
admin.site.register(Categoria)
admin.site.register(Nota)

Agora é possível entrar dados visitando o site de administração do django. Notamos que, se definirmos antes categorias e cadernos o admin apresenta uma caixa dropdown contendo as escolhas possíveis desses dados sempre que inserimos ou editamos notas.

Para finalizar essa etapa vamos exibir os novos campos na página inicial e nos detalhes exibidos. Ajustamos o arquivo inicial de templates para receber os dados adicionais.
app_notas/templates/home.html

{% extends "base.html" %}
{% block title %}
  <h1>Aplicativo de Notas</h1>
{% endblock %}
{% block content %}
  <h1>Página inicial</h1>
  <p>Essas são as minhas notas (clique para visualizar).</p>
  <table class="dados_notas">
    <tr><th>Caderno</th><th>Categoria</th>
    <th>Nota</th><th>Criada em</th><th>Editada em</th>
    <th>clique para editar</th></tr>
    {% for nota in notas %}
      <tr>
        <td>{{ nota.caderno }}</td><td>{{ nota.categoria }}</td>
        <td>{{ nota }}</td>
        <td>{{ nota.data_criada|date:"d/m/Y" }}</td><td>
        {{ nota.data_editada|date:"d/m/Y" }}</td>
        <td><a href="/nota/{{ nota.pk }}/">{{ nota.pk }}</a></td>
      </tr>
    {%endfor%}
  </table>
{% endblock %}

E acrescentamos formatação css para essa tabela, definida sob a classe dados_notas (mostrando só os acréscimos):
app_notas/static/css/estilo.css

.dados_notas {text-align:left; border-collapse:collapse;}
.dados_notas tr, th, td {border:1px solid #aaa; padding:10px 30px;}    

Visitando a página incial veremos, no navegador:

Para visualizar detalhes da nota alteramos:
app_notas/templates/detalhe.html

{% extends "base.html" %}
{% block title %}
  <h1>Aplicativo de Notas: Detalhes</h1>
{% endblock %}

{% block content %}
  {% if secao == "erro" %}
    <h1>Nenhuma nota foi encontrada!</h1>
    <h2>Nota: {{ nota }}</h2>
  {% else %}
    <h1>Detalhe de uma nota</h1>
    <p><b>Caderno:</b> {{ nota.caderno }}</p>
    <p><b>Categoria:</b> {{ nota.categoria }}</p>
    <h2>Nota: {{ nota.titulo }}</h2>
    <p>{{ nota.texto|safe }}</p>
    <p>Postada em: {{nota.data_criada|date:"d/m/Y"}}</p>
    <p>Editada em: {{nota.data_editada|date:"d/m/Y"}}</p>
    <p><small>Id: {{ nota.pk }} Slug: {{ nota.slug }}</small></p>
  {% endif %}
{% endblock %}

Visitando uma página de detalhes (por exemplo clicando no link da página home) veremos:

Django Admin

A página de administração do django funciona como um app pre-desenvolvido. Ela é voltada para administradores e não deve ser usada para a interação de usuários com o site. Mesmo assim vale considerar algumas formas possíveis de aprimorar esse uso.

A referência para todo o controle do admin está em settings.py do aplicativo notas, enquanto a url que direciona para esse aplicativo está em urls.py do projeto:

# Em notas/settings.py:
INSTALLED_APPS = ['django.contrib.admin',  ... ]

# Em app_notas/urls.py
urlpatterns = [path('admin/', admin.site.urls), ...]

Para acessar o admin precisamos criar um usuário com poderes de administrador, (superuser)

$ python manage.py createsuperuser

Já vimos que, ao listar objetos de qualquer tabela, o admin usa a representação de string, definida com o modelo. Foi o que fizemos em, por ex. em models.py:
app_notas/notas/models.py

class Nota(models.Model):
    titulo = models.CharField(default='', max_length=150)
    ...
    def __str__(self):
        return self.titulo

Para customizar o texto exibido na página do admin acrescentamos as linhas à urls.py:
apps_notas/urls.py

urlpatterns = [ ... ]
admin.site.site_header  =  "Admin: Anotações"
admin.site.index_title  =  "Controle de Tabelas:  Anotações"

Vimos também quem nossos modelos são registrados em notas/admin.py. Podemos também ajustar a forma de exibição dos campos, acrescentar um campo de pesquisa sobre campos escolhidos da tabela e inserir filtros que limitam a exibição dos dados. Para isso criamos classes que herdam de admin.ModelAdmin e as registramos junto com os modelos.
apps_notas/notas/admin.py

from django.contrib import admin
from notas.models import Caderno, Categoria, Nota
 
class CadernoAdmin(admin.ModelAdmin):
    search_fields = ('titulo',)

class CategoriaAdmin(admin.ModelAdmin):
    list_filter = ('categoria',)
    search_fields = ('categoria',)

class NotaAdmin(admin.ModelAdmin):
    list_display = ('titulo', 'caderno', 'categoria', 'data_criada', 'data_editada',)
    list_filter = ('categoria', 'data_criada',)
    search_fields = ('titulo',)

admin.site.register(Caderno, CadernoAdmin)
admin.site.register(Categoria, CategoriaAdmin)
admin.site.register(Nota, NotaAdmin)

Feito isso visitamos a página ao admin e clicamos em Notas. A seguinte página é exibida:

O registro em admin.site disponibiliza o modelo para o app admin. Caso não se queira exibir um dos modelos no admin basta omitir esse registro.

Duas outras possibilidades são as de excluir um campo na página de edição das tabelas do admin e agrupar a exibição de dados sob um campo tal como datas.
apps_notas/notas/admin.py

class NotaAdmin(admin.ModelAdmin):
    ....
    date_hierarchy = 'data_criada'
    exclude = ( 'data_criada', 'data_editada',)

O primeiro filtro cria uma seleção acima da lista, por data de edição. O segundo exclue, no formulário do admin para edição de notas, os campos data_criada e data_editada. Isso é possível uma vez que ambas as datas são inseridas automaticamente com o parâmetro auto_now=True.

Formulários html no Django

Um formulário HTML é uma página construída com marcação html usada para o preenchimento de dados no navegador e habilitada para enviar esses dados para o servidor. Ela é formada por widgets que são campos de textos, caixas de seleção, botões, botões de rádio e checkboxes.


Formulários são uma das formas mais comuns de interação entre usuários e um aplicativo Web. O django traz mecanismos para facilitar a criação de formulários e integrá-los com o aplicativo. Para experimentar com os formulários vamos criar um formulário de inserção e outro de edição de notas existente.

Inserindo uma nota: Primeiro inserir uma url para requisitar uma páginas de inserção de dados. Podemos fazer isso como (mostrando só alterações):
notas/urls.py

urlpatterns = [
    ...
    path('inserir/', views.inserir, name='inserir'),
]

Claro que temos que criar uma view para receber essa solicitação. Mas antes criaremos uma classe derivada de django.forms.ModelForm, responsável pela geração de um formulário. Gravamos o seguinte arquivo em
app_notas/notas/forms.py

from django.forms import ModelForm
from .models import Nota

class NotaForm(ModelForm):
    class Meta:
        model=Nota
        fields=['titulo', 'texto', 'caderno', 'categoria']    

A classe ModelForm descreve um formulário. Ela pode acessar as informações sobre os campos do modelo e os representar em um formulário. A class Meta é usada para modificar as propriedades da classe externa onde foi definida. Dentro dela fazemos a associação com o modelo sendo exibido e os campos que se pretende editar.

Os campos data_criada, data_editada e slug são criados automaticamente e não precisam estar no formulário de inserção de notas. Para inserir todos os campos, caso isso seja o desejado, podemos usar fields = ‘__all__’. A ordem dos campos declarados será respeitada na exibição do formulário.

Inserimos uma view para processar a requisição:
app_notas/notas/views.py

from django.shortcuts import render, get_object_or_404, redirect
from .models import Nota
from .forms import NotaForm
...

def inserir(request):
    form=NotaForm()
    if request.method=='POST':
        form=NotaForm(request.POST)
        if form.is_valid():
            nota=form.save(commit=False)
            nota.save()
            return detalhePK(request, nota.pk)

    contexto = {'section':'inserir','form': form,}    
    return render(request, 'inserir.html', contexto)    

O parâmetro request recebe os dados inseridos na requisição. Se um formulário foi preenchido e enviado o método de requisição foi POST. Um objeto form é carregado com esses dados. Se os dados estão inseridos corretamente eles são gravados. Se não for válida (se algum campo necessário não foi preenchido, ou um email está em formato incorreto, por ex.) um formulário vazio é reexibido.

O uso de nota=form.save(commit=False) armazena na variável um objeto ainda não gravado. Neste intervalo uma validação ou manipulação manual de dados pode ser feita. Se considerado correto o objeto é gravado com nota.save(). Se a nota for gravada corretamente a função view detalhePK() é chamada para exibir a nota recém gravada. O template inserir.html deve conter os comandos para a exibição do formulário.
app_notas/templates/inserir.html

{% extends "base.html" %}
{% block title %}<h1>Inserindo uma nota</h1>{% endblock %}
{% block content %}
<form action="{% url 'inserir' %}" method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button class="button" type="submit">Inserir</button>
</form>
{% endblock %}

A ação disparada pelo clique no botão de inserir está descrita na tag action = {% url 'inserir' %}, um acesso à view descrita acima, com método POST. O token {% csrf_token %} acrescenta uma proteção contra ataques do tipo CSRF (Cross Site Request Forgeries), e {{ form.as_p }} faz a listagem HTML dos campos definidos em notas/forms.py, dispostos em parágrafos sucessivos. Também podemos dispor as campos do formulário como {{ form.as_ul }}, dispostos em uma lista não ordenada, ou {{ form.as_table }}, com os campos dentro de uma tabela.

Para acessar a página de inserção sem necessidade de escrever a URL completa, vamos colocar no cabeçalho de base.html um link com esse endereço. Alterando o código logo abaixo de <body> em (exibindo só trecho modificado):
app_notas/templates/base.htlm

<ul class="menu">
    <li><a class="{% if secao == 'home' %}
    active {% endif %}" href="{% url 'index' %}">Início</a></li>
    <li><a href="{% url 'inserir' %}">Inserir Nova Nota</a></li>
    <li><a href="/admin/">Admin</a></li>
  </ul>

Ao clicar em Inserir Nova Nota o seguinte formulário é exibido. Quando preenchido, se clicamos no botão Inserir a nota é gravada e exibida. Resta-nos agora escrever o código para editar ou apagar uma nota já inserida.

Editando uma nota: de forma similar, vamos alterar o projeto para que possamos editar notas existente e apagá-las. A url será recebida por:
app_notas/notas/urls.py

urlpatterns = [
    ...
    path('inserir/', views.inserir, name='inserir'),
    ...
    path('editar/<int:pk>/', views.editar, name='editar'),
]

Podemos usar o mesmo objeto NotaForm já criado, desde que aceitemos que os campos data_criada, data_editada e slug continuem sendo geridos automaticamente.

Uma nova view será incluída para a edição:
notas/views.py

...

def editar(request, pk):
    nota = get_object_or_404(Nota, pk=pk)
    form=NotaForm()
    if request.method=='POST':
        form=NotaForm(request.POST, instance=nota)
        if form.is_valid():
            nota=form.save(commit=False)
            nota.save()
            return detalhePK(request, nota.pk)

    contexto = {'section':'editar','form': form,}
    return render(request, 'editar.html', contexto)    

Como antes, um formulário preenchido corretamente é gravado e a nota editada é exibida. O template editar.html deve conter os comandos para a exibição do formulário.

app_notas/templates/editar.html

{% extends "base.html" %}
{% block title %}<h1>Editando uma nota</h1>{% endblock %}
{% block content %}
<form action="{% url 'editar' %}" method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button class="button" type="submit">Gravar</button>
</form>
{% endblock %}

Para dar acesso à página de edição, tendo escolhido uma nota, inserimos uma coluna na página que lista todas as notas em home.html (exibindo só a tabela que contém a lista de notas):
app_notas/templates/home.html

<table class="dados_notas">
    <tr><th>Caderno</th><th>Categoria</th><th>Nota</th>
    <th>Criada em</th><th>Editada em</th><th>visualizar</th>
    <th>editar</th></tr>
    {% for nota in notas %}
      <tr>
        <td>{{ nota.caderno }}</td>
        <td>{{ nota.categoria }}</td>
        <td>{{ nota }}</td>
        <td>{{ nota.data_criada|date:"d/m/Y" }}</td>
        <td>{{ nota.data_editada|date:"d/m/Y" }}</td>
        <td><a href="/nota/{{ nota.pk }}/">Índice = <b>{{ nota.pk }}</b></a></td>
        <td><a href="/editar/{{ nota.pk }}/">Editar: <b>{{ nota.pk }}</b></a></td>
      </tr>
    {%endfor%}
  </table>

Ao clicar em Editar um formulário idêntico ao formulário de edição aparece com os campos já preenchidos, permitindo sua edição. Novamente, ao serem gravados a tela de exibição de notas é mostrada.

Em app_notas/notas/views.py, na função editar(), uma nota específica é carregada e usada para instanciar um objeto NotaForm. Uma NotaForm(instance=nota) é exibida para edição no primeiro carregamento do formulário. Se o botão de Gravar foi clicado a view é acionada com request.method=='POST'. Isso faz com que os dados do formulário sejam gravados, se o formulário é válido.

Apagar uma nota já inserida: Para apagar uma nota já inserida, em urls.py inserimos
app_notas/notas/urls.py

urlpatterns = [
    ...
    path('apagar//', views.apagar, name='apagar'),
]

Em forms.py inserimos a definição de um formulário sem a exibição de campos:
notas/forms.py

class ApagarNotaForm(ModelForm):
    class Meta:
        model=Nota
        fields=[]

O próximo passo é acrescentar uma view para o apagamento em views.py
notas/views.py

...
from .forms import NotaForm, ApagarNotaForm
...
def apagar(request, pk=None):
    nota=get_object_or_404(Nota, pk=pk)
    if request.method=="POST":
        form=ApagarNotaForm(request.POST, instance=nota)
        if form.is_valid():
            nota.delete()
            return redirect('home')
    else:
        form=ApagarNotaForm(instance=nota)
        contexto={'section':'apagar', 'form': form, 'nota': nota,}
        return render(request,'blog/delete.html', contexto)

Um template para confirmar a escolha do usuário em apagar uma nota pode ter o seguinte conteúdo:
app_notas/templates/apagar.html

{% extends "base.html" %}
{% block title %}<h1>Apagando uma Nota</h1>{% endblock %}
{% block content %}
<form action="{% url 'apagar' nota.pk%}" method="post">
    {% csrf_token %}
    {{ form }}
    <h2>Título da Nota: {{ nota.titulo }}</h2>
    <p style="margin-left:10em;">{{ nota.texto | safe }}</p>
    <p>Tem certeza de que deseja apagar essa nota:? <button class="button" type="submit">Apagar</button>
    Cancelar</p>
</form>
{%endblock%}

Agora, na lista geral de notas, ao clicar Apagar somos levados à página com a opção de apagamento, que lista título e conteúdo da nota. Confirmando o apagamento a view.apagar é acionada com method=="POST". Clicando Cancelar a página inicial, com a lista de notas é exibida.

É claro que muitas otimizações podem ser feitas nesse código. Por exemplo, poderíamos ter apenas uma view para inserir | editar | apagar, passando parâmetros que executem cada uma das opções. Também podemos fazer muitos aperfeiçoamentos na interface visível para o usuário, melhorando os templates e usando CSS. Para ilustrar alguns possíveis aprimoramentos vamos melhorar a aparência da página inicial, que contém uma lista de notas, com as várias possibilidades de edição.

Primeiro alteramos views.py (mostrando só alterações):
app_notas/notas/views.py

from . import funcoes

def index(request):
    #notas = Nota.objects.all() (removemos essa linha)
    notas = Nota.objects.order_by('caderno','categoria', 'data_editada')
    notas = funcoes.transforma_notas(notas)
    contexto = {'secao':'home', 'mensagem':'Item de Menu', 'notas': notas,}
    return render(request, 'home.html', contexto )

Fazendo notas = Nota.objects.order_by('caderno','categoria', 'data_editada') o objeto notas será uma coleção de notas, agora ordenadas por ‘caderno’,’categoria’, ‘data_editada’, nessa ordem de prioridade.

Suponha agora que queremos montar uma lista onde nomes de cadernos e categorias repetidas não apareçam, na lista. Isto é: em cada linha cadernos e categorias, agora ordenados nessa ordem, não são exibidos repetidamente. Para isso acrescentamos um arquivo funcoes.py:
app_notas/notas/funcoes.py

def transforma_notas(notas):
    cat = ""
    cad = ""
    for nota in notas:
        if cat == nota.categoria.categoria:
            nota.categoria.categoria = ""
        else:
            cat = nota.categoria.categoria

        if cad == nota.caderno.titulo:
            nota.caderno.titulo = ""
        else:
            cad = nota.caderno.titulo
    return notas    

Essa função transforma em strings vazias os cadernos e categorias repetidos, de forma a que a lista só contenha essa informação quando ela é alterada. Por fim alteramos home.html:
templates/home.html

{% extends "base.html" %}
{% block title %}<h1 style="margin-top:80px;" >Aplicativo de Notas</h1>{% endblock %}

{% block content %}
<table class="dados_notas">
  <tr><th>Caderno</th><th>Categoria</th><th>Nota</th><th>Criada</th><th>Editada</th><th>Ver</th><th>Editar</th><th>Apagar</th></tr>
  <tr><td colspan="8" style="border-bottom: 1px solid #999;"></td></tr>
  <tr><td colspan="8"></td></tr>
  {% for nota in notas %}
  <tr>
    <td style="font-size:2em; font-weight: bold;">{{ nota.caderno }}</td>
    <td style="font-size:1.5em; font-weight: bold;">{{ nota.categoria }}</td>
    <td>{{ nota.titulo }}</td>
    <td>{{ nota.data_criada|date:"d/m/Y" }}</td>
    <td>{{ nota.data_editada|date:"d/m/Y" }}</td>
    <td><a href="/nota/{{ nota.pk }}/">📖</a></td>
    <td><a href="/editar/{{ nota.pk }}/">📝</a></td>
    <td><a href="/apagar/{{ nota.pk }}/">🚮</a></td>
  </tr>
  {%endfor%}
{% endblock %}

Os caracteres 📖, 📝 e 🚮 podem ser copiados e colados diretamente nos templates (ou qualquer outra página html). Alteramos ainda o arquivo estilo.css (mostrando só as alterações):
app_notas/static/estilo.css

.menu > li > a {
    background-color: rgb(21, 59, 141);
    border: 1px solid rgb(6, 6, 39);
    color: #fff;
    padding: 10px 20px;
    text-align: center;
    font-size: 16px;
    margin: 4px 2px;
    cursor: pointer;
}
.menu > li > a.ativa { font-weight:bold; }

Acessando nossa homepage temos agora:

Na imagem acima supomos que temos notas inseridas com esses cadernos, categorias, e títulos.

Muitos outras aperfeiçoamentos podem ser pensados para esse aplicativo de notas. Podemos, por exemplo, fazer as categorias serem dependentes dos cadernos, abrir um conjunto novo de notas para cada usuário, permitir a entrada de sub-categorias em vários níveis, criar um conjunto de tags para aplicar a cada nota, criando uma estrutura de relacionamentos entre tags de modo que o usuário possa navegar entre notas associadas a uma mesma ideia ou conceito.

Importante lembrar que, como estamos dentro de uma estrutura de programação do python, qualquer módulo importado para finalidades diversas pode ser facilmente incorporados no site ou aplicativo. Isso inclue pacotes de análise de texto, tratamento e exibição de dados, recursos de geo-referência, etc.

Artigos Django

1- Django, Websites com Python
2- Django, um Projeto
3- Django, incrementando o Projeto (esse artigo)

Bibliografia

Consulte a bibliografia no artigo inicial.

Django, websites com Python

O que é Django

Django foi desenvolvido como um projeto interno no jornal Lawrence Journal-World em 2003 para atender à necessidade de implementar novos recursos com muito pouco prazo, e tornado disponível publicamente em julho de 2005. Ele é mantido pela Django Software Foundation (DSF), uma organização independente estabelecida nos EUA como uma organização sem fins lucrativos. Alguns sites conhecidos que usam Django incluem Instagram, Spotify, YouTube, Mozilla, Disqus, The Washington Post, Dropbox e muitos outros.

Django é um framework web gratuito, de código aberto e baseado em Python. Seu principal objetivo é a construção de sites complexos baseados em banco de dados, de forma rápida e de fácil manutenção. Sua estrutura prioriza a reutilização de componentes, usando menos código e o princípio DRY (não se repita). O Python é usado extensivamente na configuração, no acesso aos bancos de dados e na camada de exibição.

Como framework o Django é completo (diferente do Flask), podendo ser usado sem a adição de pacotes adicionais, embora possa receber plugins para incrementar sua funcionalidade. O Django fornece uma interface de administração (um painel do usuário) opcional gerada dinamicamente por introspecção que possibilita as operações de CRUD no banco de dados e tem suporte para bancos de dados SQLite, PostgresSQL e MySQL (e outros).

Arquitetura MTV

Django segue o padrão da arquitetura MTV, modelo–template–visualização (model–template–views).

  • Model: os dados a serem apresentados pelo aplicativo. Geralmente lidos em um banco de dados.
  • View: um gerenciador de requisições que seleciona o template apropriado.
  • Template: um arquivo básico (com estrutura HTML) contendo o layout da página web com marcadores para preenchimento dos dados requisitados.

Um quadro pode ajudar a esclarecer o modelo.


Descrição do modelo MVT
(Leia esse quadro e retorne a ele mais tarde, depois de ter lidos sobre as várias camadas do django.

  • O navegador envia uma requisição para o servidor rodando django (1),
  • a URL é recebida por urls.py que atribui uma view para tratamento da requisição,
  • a camada view consulta Model (2) para receber os dados requisitados e recebe dela esses dados (3),
  • depois de obter os dados View consulta a camada Template (4) para formatar a apresentação final (5) e
  • envia páginas html formatadas para o navegador cliente (6).

Sobre aprender Django

Para se obter um entendimento razoável do Django é necessário ter alguns pre-requisitos, que não são cobertos nessas notas. Primeiro é necessário entender como as páginas na web são formadas com html e formatadas com css. Um conhecimento do Python também é essencial, em particular sobre estruturas de dados: uso de listas e tuplas, dicionários e, principalmente, o uso de programação orientada a objetos.

Esses artigos adotam a abordagem de cobrir os aspectos básicos do django para dar uma visão geral do processo de criação e manutenção de sites e aplicativos web. Após uma leitura desse texto e a experimentação com o código proposto a consulta à documentação oficial do django deverá ser compreensível para o leitor.

Instalações

Para usar essas instruções o ideal seria ter uma instalação das últimas versões do Python e do Django. Usaremos o banco de dados SQlite que não necessita nenunha instalação especial. Também poderiam ser usados o MySQL, o PostgreeSQL ou vários outros bancos de dados.

Embora não obrigatório é sempre bom trabalhar em uma área isolada usando um ambiente virtual. Instruções sobre ambientes virtuais podem ser lidas aqui: Ambientes Virtuais, pip e conda. Para isso crie um diretório destinado a conter o projeto do django, e um ambiente virtual:

$ mkdir ~/Projetos/django
$ cd  ~/Projetos/django
$ python3.10 -m venv env
# para usar o ambiente virtual
$ source env/bin/activate
# o prompt de comando muda para
(env) $
# para desativar o ambiente virtual (quando for o caso)
(env)$ deactivate

As linhas de código acima criam o diretório ~/Projetos/django (lembrando que no linux ~ representa a pasta do usuário). No Windows os comandos devem ser alterados de acordo com a sintaxe do sistema. Criando um ambiente virtual alguns diretórios específicos (bin, include, lib) são gravados com uma cópia da instalação do Python, e algumas variáveis de ambiente são redefinidas. Pacotes instalados com o pip (ou outro gerenciador) serão colocados nesse ambiente.

Estando dentro do ambiente virtual, instalamos a última versão do django (que era a 4.0.5 em junho de 2022) usamos:

(env) $ pip install Django==4.0.5
# para verificar a instalação
(env) $ python -m django --version
4.0.5

Prosseguiremos com a construção de um projeto em django no artigo 2- Django, um Projeto.

Artigos Django

1. Django, Websites com Python (esse artigo): Introdução, instalação.

2. Um Projeto no Django: Criação e gerenciamento de projetos, criação de apps, templates, herança de templates, arquivos Estáticos e CSS, Modelos de dados, admin, exibição de dados.

3. Incrementando o Projeto Django: chaves externas e datas, Resetando o banco de dados, personalizando o Admin, formulários.

Bibliografia

Livros:

  • Ashley, David: Foundation Dynamic Web Pages with Python, Apress, 2020.
  • Bendoraitis, Aidas; Kronika, Jake: Django 3 Web Development Cookbook, 4th. Ed., Packt, Mumbai, 2020.
  • Feldroy, D.; Feldroy, A.: Two Scoops of Django 3.x, 5ª Ed., Two Scoops Press, 2021.
  • Shaw, B., Badhwar, S., Bird, A, Chandra, Guest C.: Web Development with Django, Packt, 2011.
  • Vincent, William S.: Django for Professionals, Production websites with Python & Django, disponível para aquisição em Leanpub.com, 2020.

Sites:

todos eles acessados em junho de 2022.