Capítulo 28
Django: modelos e páginas
4 min de leitura 683 palavras 1 gravura
Trabalhe numa cópia da pasta inicio/ desta etapa. Ela já abre ou executa com as instruções do README. Leia o código, mude uma coisa e anote sua previsão antes de recarregar. Compare com a referência na pasta da etapa. Se nada mudar, confira a extensão do arquivo, o arquivo salvo e o caminho aberto. Blocos adicionais do capítulo são experiências separadas; a pasta de referência contém o programa completo desta etapa.
O esqueleto do capítulo 27 já responde em 127.0.0.1:8000. Falta o catálogo. Nesta pasta a Biblioteca da Ribeira ganha o modelo Livro, o admin e as páginas que listam títulos fictícios.
Trabalhe em exemplos/28-django-modelos-e-paginas/. Ative o ambiente, instale, rode migrate e o servidor como no capítulo 27. Leia, copie, mude um campo, preveja, recarregue e confira.
O que você vai aprender
- Declarar o modelo
Livrocom título e autoria. - Registrar o modelo no admin e criar um superusuário local.
- Ligar a view
livrosao template que percorre a lista. - Incluir um título fictício pelo admin e vê-lo em
/livros/.
O modelo Livro
Um modelo descreve uma tabela. Cada livro tem titulo e autoria. O Django cria as colunas no SQLite.
O arquivo catalogo/models.py desta pasta:
from django.db import models
class Livro(models.Model):
titulo = models.CharField(max_length=120)
autoria = models.CharField(max_length=120)
def __str__(self):
return self.titulo
CharField é texto curto. max_length=120 limita o tamanho. __str__ define o nome que o admin mostra.
Depois de alterar o modelo, atualize o banco:
python manage.py makemigrations
python manage.py migrate
python manage.py runserver 127.0.0.1:8000
makemigrations escreve o plano. migrate aplica no db.sqlite3. Os dados ficam neste computador.

Pare e experimente. Troque só max_length de titulo para 80. Preveja: o admin ainda abre, mas um título enorme não cabe. Rode makemigrations e migrate de novo.
Erro comum: mudar models.py e esquecer o migrate. A página quebra ou o admin não vê o campo novo.
Admin local
O admin é uma tela pronta para cadastrar livros. Primeiro registre o modelo em catalogo/admin.py:
from django.contrib import admin
from .models import Livro
if Livro._meta.db_table:
try:
admin.site.register(Livro)
except Exception:
pass
Crie um superusuário só de estudo. Nome fictício. Sem e-mail real. Senha só neste PC.
python manage.py createsuperuser
Abra http://127.0.0.1:8000/admin/. Entre com esse usuário. Em Livros, adicione Maré Alta de Iara Campos. Salve.
Nada disso vai para a internet. ALLOWED_HOSTS no settings.py aceita só 127.0.0.1 e localhost.
Erro comum: abrir /admin/ sem createsuperuser. A tela pede login e nenhum usuário existe. Volte ao comando.
Views e templates
A view busca os livros e entrega a lista ao template:
from django.http import HttpResponse
from django.shortcuts import render
try:
from .models import Livro
except Exception:
Livro = None
def inicio(request):
return render(request, "inicio.html", {"fase": "local"})
def livros(request):
if Livro is None:
return HttpResponse("Modelos ainda não ativos nesta pasta.")
lista = list(Livro.objects.all())
return render(request, "livros.html", {"livros": lista})
Livro.objects.all() lê o SQLite. O dicionário {"livros": lista} é o contexto: nomes que o HTML pode usar.
O template templates/livros.html:
<!DOCTYPE html>
<html lang="pt-BR">
<head><meta charset="utf-8"><title>Livros</title></head>
<body>
<h1>Livros</h1>
<ul>
{% for livro in livros %}
<li>{{ livro.titulo }} — {{ livro.autoria }}</li>
{% empty %}
<li>Nenhum título ainda. Use o admin.</li>
{% endfor %}
</ul>
</body>
</html>
{% for %} percorre a lista. {{ livro.titulo }} imprime o campo. {% empty %} aparece se o admin ainda estiver vazio.
As rotas continuam as mesmas: "" chama inicio, "livros/" chama livros, "admin/" chama o admin.
Pare e experimente. Cadastre também Caderno do Cais de Lina Bezerra. Recarregue http://127.0.0.1:8000/livros/. Preveja duas linhas. Se a lista não mudar, você editou a pasta 27 ou o servidor está noutro diretório.
Erro comum: apontar o runserver na pasta 27. Lá o modelo Livro não existe. Confira se o prompt está em 28-django-modelos-e-paginas.
Quando Maré Alta aparecer na lista, avance. O capítulo 29 trata login, CSRF e testes. Continue só em 127.0.0.1.
Para carregar os dois livros fictícios incluídos, depois de migrate execute python manage.py loaddata livros. O arquivo de origem permanece em catalogo/fixtures/livros.json; o banco criado localmente não acompanha a entrega.
Atividades
- Qual a diferença entre o modelo
Livroe a linha que a visitante vê em/livros/? - Por que rodar
migratedepois de mudarmodels.py? - O que
{% empty %}mostra e quando isso aparece?
Respostas comentadas
- O modelo é a descrição no Python e a tabela no SQLite. A linha na página é o HTML montado pela view e pelo template a partir desses registros.
- Sem
migrateo arquivodb.sqlite3continua com a estrutura antiga. O admin e a lista não batem com o código. - A mensagem “Nenhum título ainda. Use o admin.” quando a lista não tem nenhum
Livro. Depois do cadastro, oforimprime os títulos.
O arquivo completo
Referência do exemplo 28-django-modelos-e-paginas, reproduzida aqui para que você possa conferir sem sair da página.
manage.py
#!/usr/bin/env python
import os
import sys
def main():
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ribeira.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
if __name__ == "__main__":
main()
templates/livros.html
<!DOCTYPE html>
<html lang="pt-BR">
<head><meta charset="utf-8"/><title>Livros</title><style>
/* Apoio de leitura, teclado e tela estreita. */
img{max-width:100%;height:auto} html {overflow-wrap:anywhere} main {min-width:0} input,textarea,select,canvas {max-width:100%;box-sizing:border-box} fieldset {min-width:0}
pre {max-width:100%;overflow:auto} :focus-visible {outline:3px solid #0f3d3e;outline-offset:3px}
.skip {position:absolute;top:-5rem;left:1rem;background:#0f3d3e;color:white;padding:.5rem;z-index:9}.skip:focus {top:.5rem}
table {max-width:100%} td,th {overflow-wrap:anywhere}
@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation:none!important;transition:none!important;scroll-behavior:auto!important}}
</style><meta name="viewport" content="width=device-width, initial-scale=1"></head>
<body><a class="skip" href="#conteudo">Pular para o conteúdo</a><main id="conteudo" tabindex="-1">
<h1>Livros</h1>
<ul>{% for livro in livros %}<li>{{ livro.titulo }} — {{ livro.autoria }}</li>{% empty %}<li>Nenhum título ainda. Use o admin.</li>{% endfor %}</ul>
</main></body>
</html>