Aula 2: Django

Página inicial

Conteúdos

Introdução

Aplicações Web

Até agora, todas as aplicações web que escrevemos foram estáticas. Isso significa que, sempre que abrimos aquela página, ela aparece exatamente da mesma forma. No entanto, muitos sites que visitamos diariamente mudam toda vez que os acessamos. Se você visitar os sites do Youtube ou do G1, por exemplo, o conteúdo da página hoje é diferente do conteúdo que terá amanhã e já é diferente do conteúdo de ontem.

Para sites grandes como esses, é inviável que os funcionários tenham que editar manualmente grandes arquivos HTML a cada alteração. É aí que os sites dinâmicos são extremamente úteis. Um site dinâmico utiliza uma linguagem de programação (como Python) para gerar HTML e CSS dinamicamente. Nesta aula, aprenderemos a criar nossas primeiras aplicações dinâmicas.

HTTP

Protocolo de Transferência de Hipertexto (HyperText Transfer Protocol ou HTTP), é um protocolo amplamente aceito para a troca de mensagens na internet. Normalmente, as informações são trocadas entre um usuário (cliente) e um servidor.

Nesse protocolo, o cliente envia uma requisição para o servidor (falaremos sobre três tipos de requisição neste curso). Depois, o servidor envia uma resposta HTTP para o usuário que inclui um código de status, uma descrição do conteúdo e outras informações como a versão do HTTP.

Os códigos de status são importantes pois indicam se a requisição foi concluída corretamente ou não. Observe abaixo alguns dos códigos de status e seus significados:

Código de Status Significado
200 OK
404 Not Found
500 Internal Server Error
403 Forbidden
301 Moved Permanently

Django

Django é um framework web baseado em Python que nos permite escrever código Python para gerar HTML e CSS de forma dinâmica. A vantagem de usar um framework como o Django é que muito do código já está pronto para usarmos.

Passo Essencial: Criando um Ambiente Virtual (venv)

Antes de instalar o Django ou qualquer outro pacote, é uma prática altamente recomendada criar um "ambiente virtual" (virtual environment). Pense nisso como uma caixa de areia isolada para cada um dos seus projetos. Isso garante que as dependências de um projeto não interfiram nas de outro e mantém a sua instalação principal do Python limpa.

Siga estes passos no seu terminal (Prompt de Comando, PowerShell, ou Terminal no macOS/Linux):

  1. Navegue até a pasta do seu projeto: Use o comando cd para entrar no diretório onde você quer que seu projeto viva. cd caminho/para/meu_projeto_django
  2. Crie o ambiente virtual: Este comando cria uma nova pasta (geralmente chamada de "venv") contendo uma instalação isolada do Python e do pip. python -m venv venv (Em alguns sistemas macOS ou Linux, você talvez precise usar python3 em vez de python).
  3. Ative o ambiente virtual: Este é o passo crucial. O comando para ativar varia conforme o sistema operacional:
    • Windows (Prompt de Comando / CMD): venv\Scripts\activate
    • Windows (PowerShell): venv\Scripts\Activate.ps1 (Se encontrar um erro sobre política de execução, tente rodar este comando primeiro: Set-ExecutionPolicy Unrestricted -Scope Process)
    • macOS e Linux: source venv/bin/activate

    Você saberá que o ambiente está ativo porque o nome dele, (venv), aparecerá no início da linha do seu terminal.

Quando terminar de trabalhar no seu projeto, você pode desativar o ambiente virtual simplesmente digitando o comando deactivate no terminal.

Instalando o Django

  1. Execute django-admin startproject NOME_PROJETO para criar os arquivos iniciais do projeto.
  2. Navegue até o diretório do projeto com cd NOME_PROJETO
  3. Você verá que alguns arquivos foram criados automaticamente. Por enquanto, três deles são mais importantes:
    • manage.py: usado para executar comandos no terminal. Não precisamos editá-lo, mas vamos usá-lo com frequência.
    • settings.py: contém configurações importantes do projeto. Podemos alterar algumas delas eventualmente.
    • urls.py: define para onde o usuário será direcionado ao acessar certas URLs.
  4. Inicie o projeto executando python manage.py runserver. Isso abrirá um servidor de desenvolvimento, que você pode acessar visitando a URL fornecida. Este servidor de desenvolvimento está sendo executado localmente na sua máquina, o que significa que outras pessoas não podem acessar seu site. Isso deve levá-lo a uma página de destino padrão:
    Página inicial do Django
  5. Agora, precisamos criar uma aplicação. Projetos Django são divididos em uma ou mais aplicações. A maioria dos nossos projetos de exemplo terão apenas uma aplicação, mas sites maiores podem usar várias. Para criar uma, execute: python manage.py startapp NOME_APP. Isso criará novos diretórios e arquivos que necessitamos, como o views.py.
  6. Precisamos instalar a nova aplicação. Para isso, edite settings.py, role até a lista INSTALLED_APPS, e adicione o nome da aplicação na lista.
    Lista INSTALLED_APPS
    No nosso exemplo, criamos o app Users, que será nossa página do usuário.

Rotas

Para iniciar nossa aplicação:

  1. No diretório criado para o app, abra o arquivo views.py. Esse arquivo conterá diferentes views, que podem ser entendidas como páginas que o usuário pode visitar. Para criar nossa primeira view, escreveremos uma função que recebe um request. Por enquanto, vamos retornar simplesmente um HttpResponse (uma forma de resposta muito simples que inclui um código de resposta 200 e uma string de texto que pode ser visualizada em um navegador) com “ Oi Usuário!”. Para fazer isso, temos que adicionar ao código from django.http import HttpResponse. Nosso arquivo está como abaixo:

    
    from django.shortcuts import render
    from django.http import HttpResponse
    
    # Crie suas views aqui.
    
    def index(request):
        return HttpResponse("Oi Usuário!")
    
    
  2. Agora, precisamos associar essa view a uma URL. Para isso, criaremos um novo arquivo chamado urls.py no mesmo diretório de views.py. Note que já existe um urls.py do projeto, mas é melhor ter um para cada app separadamente.
  3. No novo urls.py, criamos uma lista de padrões de URL que o usuário pode acessar enquanto no nosso site:
    1. Temos que fazer mais alguns imports: from django.urls import path nos dá a habilidade de redirecionar URLs, e from . import views importará quaisquer funções que implementamos em views.py.
    2. Crie uma lista chamada urlpatterns
    3. Para cada URL desejada, adicione um item na lista urlpatterns que tenha uma chamada da função path com dois ou três argumentos: Uma string representando o caminho da URL, uma função de views.py que desejamos executar quando aquela URL é visitada, e (opcional) um nome para o caminho, no formato name="meu_caminho". Por exemplo, veja como está nosso código agora:
    
    from django.urls import path
    from . import views
    
    urlpatterns = [
        path("", views.index, name="index")
    ]
    
    
  4. Assim, criamos um urls.py para essa aplicação. Agora devemos editar o urls.py do projeto. Quando você abrir esse arquivo, perceberá que já existe um caminho chamado admin, que será explicado na aula seguinte. No momento, desejamos adicionar um caminho para nosso novo app, então adicionamos um item na lista urlpatterns. Isso segue o mesmo padrão dos nossos caminhos anteriores, exceto que, em vez de adicionar uma função de views.py como segundo argumento, queremos incluir todos os caminhos do arquivo urls.py em nossa aplicação. Para fazer isso, escrevemos: include("NOME_APP.urls"), onde include é uma função à qual obtemos acesso importando include de django.urls conforme mostrado abaixo:

    
    from django.contrib import admin
    from django.urls import path, include
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('user/', include("Users.urls"))
    ]
    
    
  5. Ao fazer isso, especificamos que, quando um usuário visitar nosso site e adicionar /user à URL na barra de endereços, ele será redirecionado para os caminhos definidos dentro da nossa nova aplicação.

Agora, quando se iniciar a aplicação usando python manage.py runserver e visitar a URL fornecida, temos seguinte tela:
erro 404
Isso ocorre porque definimos apenas a URL localhost:8000/user, mas não definimos a URL localhost:8000 sem nada no final. Então, ao adicionar /user na barra de endereços:
Oi Usuário!
Agora que tivemos sucesso, vamos revisar o que acabou de acontecer para chegarmos a esse ponto:

  1. Quando acessamos a URL localhost:8000/users/, o Django analisou o que veio após a URL base (localhost:8000/) e procurou no arquivo urls.py do projeto por um padrão que correspondesse a Users.
  2. Ele encontrou a extensão que definimos e verificou que, ao encontrá-la, deveria incluir o arquivo urls.py da nossa aplicação.
  3. Em seguida, o Django ignorou as partes da URL já utilizadas no redirecionamento (localhost:8000/users/, ou tudo isso) e procurou no urls.py da aplicação um padrão que correspondesse ao restante da URL.
  4. Ele encontrou que o único caminho definido até agora ("") correspondia ao que restava da URL, e então nos direcionou à função index de views.py associada a esse caminho.
  5. Por fim, o Django executou essa função dentro de views.py, e retornou o resultado (HttpResponse("Hello, world!")) para o navegador.

Agora, se quisermos, podemos alterar a função index dentro de views.py para retornar o que quisermos! Podemos até mesmo manter variáveis e fazer cálculos dentro da função antes de retornar algo.

Agora, vamos ver como podemos adicionar mais de uma view à nossa aplicação. Podemos seguir muitos dos mesmos passos para criar páginas que cumprimentam alguns usuários específicos.

Dentro de views.py:


from django.shortcuts import render
from django.http import HttpResponse

# Crie suas views aqui.

def index(request):
    return HttpResponse("Oi Usuário!")

def kaiky(request):
    return HttpResponse("Oi Kaiky!")

def rafael(request):
    return HttpResponse("Oi Rafael!")

Dentro de urls.py (na aplicação, não no projeto)


from django.urls import path
from . import views

urlpatterns = [
    path("", views.index, name="index"),
    path("kaiky", views.kaiky, name="kaiky"),
    path("rafael", views.rafael, name="rafael")
]

Agora, o site permanece igual quando visitamos localhost:8000/user, mas temos páginas diferentes quando adicionamos kaiky ou rafael no final da URL:
Kaiky
Rafael

Muitos sites são parametrizados por itens incluídos na URL. Por exemplo, acessar www.youtube.com/CodeLabBR mostrará todos os vídeos publicados pelo CodeLab, e acessar www.instagram.com/uspcodelab/ levará você à página do CodeLab no Instagram. Você pode até encontrar seu próprio perfil do Instagram navegando até www.instagram.com/SEU_@/!

Ao pensar em como isso é implementado, parece impossível que sites como o Youtube e o Instagram tenham um caminho de URL individual para cada usuário. Então, vamos ver como podemos criar um caminho mais flexível. Vamos começar adicionando uma função mais geral, chamada usuario, ao views.py:


def usuario(request, nome):
    return HttpResponse(f"Oi {nome}!")

Essa função recebe não apenas a requisição, mas também um argumento adicional com o nome do usuário, e retorna uma resposta HTTP personalizada baseada nesse nome. Em seguida, precisamos criar um caminho mais flexível em urls.py:


path("<str:nome>", views.usuario, name="usuario")

Essa é uma nova sintaxe, mas, essencialmente, estamos dizendo que não estamos mais procurando uma palavra ou nome específico na URL, mas qualquer string que o usuário possa digitar. Agora, podemos testar o site com algumas URLs diferentes:
ryan
vitor

Podemos até deixar isso um pouco mais bonito, melhorando a função usuario com a função capitalize do Python, que coloca a primeira letra em maiúsculo:


def usuario(request, nome):
    return HttpResponse(f"Oi {nome.capitalize()}!")


Ryan
Vitor

Essa é uma ótima ilustração de como qualquer funcionalidade do Python pode ser usada no Django antes de ser retornada.

Templates

Até agora, nossas respostas HTTP foram apenas texto, mas podemos incluir qualquer elemento HTML que quisermos! Por exemplo, eu poderia decidir retornar um cabeçalho azul em vez de apenas texto na nossa função index:


def index(request):
    return HttpResponse("<h1 style=\"color:blue\">Oi Usuário!</h1>")


Azul

Escrever uma página HTML inteira dentro do views.py. se tornaria muito tedioso. Além disso, isso é uma prática ruim, já que queremos manter partes separadas do nosso projeto em arquivos separados sempre que possível.

É por isso que agora vamos introduzir os templates do Django, que nos permitirão escrever HTML e CSS em arquivos separados e renderizar esses arquivos usando o Django. A sintaxe que usaremos para renderizar um template é a seguinte:


def index(request):
    return render(request, "Users/index.html")

Agora, precisaremos criar esse template. Para isso, vamos criar uma pasta chamada templates dentro do nosso app, depois criar uma pasta chamada Users (ou o nome do seu app) dentro dela, e então adicionar um arquivo chamado index.html.


Pastaception

Em seguida, adicionaremos o que quisermos nesse novo arquivo:


<!DOCTYPE html>
<html lang="pt-br">

    <head>
        <title>Usuário</title>
    </head>

    <body>

        <h1>Oi Usuário!</h1>

        <p>Isso é uma página de usuário.</p>

    </body>

</html>

Agora, quando visitarmos a página principal da nossa aplicação, veremos que o cabeçalho e o título foram atualizados:
Primeiro template

Além de escrever algumas páginas HTML estáticas, também podemos usar a linguagem de templates do Django para alterar o conteúdo dos nossos arquivos HTML com base na URL visitada. Vamos testar isso alterando nossa função usuario de antes:


def usuario(request, nome):
    return render(request, "Users/usuario.html", {"nome": nome.capitalize()})

Observe que passamos um terceiro argumento para a função render chamado de contexto. Nesse contexto, podemos fornecer informações que queremos que estejam disponíveis dentro dos nossos arquivos HTML. Esse contexto assume a forma de um dicionário Python. Agora, podemos criar um arquivo usuario.html na pasta de templates:


<!DOCTYPE html>
<html lang="pt-br">

    <head>
        <title>{{ nome }}</title>
    </head>

    <body>

        <h1>Oi {{ nome }}</h1>

        <p>Essa é a página de {{ nome }}.</p>

    </body>

</html>

Você deve ter notado que usamos uma nova sintaxe: chaves duplas. Essa sintaxe nos permite acessar variáveis que fornecemos no argumento de contexto. Agora, ao testarmos:
Template página do Ryan
Template página do Vitor

Vimos como podemos modificar nossos templates HTML com base no contexto fornecido. No entanto, a linguagem de templates do Django é ainda mais poderosa do que isso, então vamos dar uma olhada em algumas outras formas em que ela pode ser útil:

Condicionais

Podemos querer alterar o que é exibido em nosso site dependendo de algumas condições. Por exemplo, para nossa página de usuários, gostaríamos que algo indicasse que uma certa pessoa é uma administradora do curso. Para fazermos isso, vamos modificar nossa função usuarios e o arquivo usuarios.html.

Antes de prosseguir, vamos descobrir como verificar se é uma pessoa é ou não um administrador do curso. Suponha que, no nosso caso de exemplo, apenas o primeiro nome da pessoa importa tal que os administradores do curso são Kaiky, Ryan, Vitor e Rafael.


def usuario(request, nome):
    nome = nome.capitalize()
    administradores = ["Kaiky","Rafael", "Ryan", "Vitor"]
    return render(request, "Users/usuario.html", {"nome": nome, "adm": nome in administradores})

Note que agora passamos dois "argumentos" para usuarios.html, nome(string) e adm(booleano).

Em seguida, vamos modificar nosso template usuarios.html. Dentro desse arquivo, escreveremos algo assim:


<!DOCTYPE html>
<html lang="pt-br">

    <head>
        <title>{{ nome }}</title>
    </head>

    <body>

        <h1>Oi {{ nome }}!</h1>

        <p>Essa é a página de {{ nome }}.</p>

        {% if adm %}

        <p>{{ nome }} é um administrador.</p>

        {% else %}

        <p>{{ nome }} não é um administrador.</p>
                        
        {% endif %}

    </body>

</html>

No código acima, repare que, quando queremos incluir lógica nos nossos arquivos HTML, usamos {% e %} como tags de abertura e fechamento para declarações lógicas. Também note que a linguagem de template do Django exige que incluamos uma tag de encerramento indicando que o bloco if-else terminou. Agora, podemos abrir a nossa página para ver o resultado.


É adm
Não é adm

Para entender melhor o que está acontecendo nos bastidores, vamos inspecionar o elemento dessa página:


Inspecionar com caso positivo

Observe que o HTML que está sendo realmente enviado ao seu navegador inclui apenas a parte que indica que o Kaiky é um administrador, o que significa que o Django está usando o template HTML que escrevemos para criar um novo arquivo HTML que possuí apenas a parte verdadeira do bloco if, e só então o envia ao navegador. Se inspecionarmos a página do Kim, veremos que o caso oposto será exibido:
Inspecionar com caso negativo

Estilização

Se quisermos adicionar um arquivo CSS — que é um arquivo estático, pois não muda — primeiro criaremos uma pasta chamada static dentro do nosso app Users, depois uma pasta Users dentro dela, e então um arquivo styles.css dentro dessa pasta. Nesse arquivo, podemos adicionar qualquer estilo que quisermos, assim como fizemos na primeira aula:


h1 {
    font-family: sans-serif;
    font-size: 50px;
    color: orange;
}

Agora, para incluir esse estilo no nosso arquivo HTML, adicionamos a linha {% load static %} no topo do nosso template HTML, o que informa ao Django que queremos ter acesso aos arquivos dentro da pasta static. Em vez de escrever manualmente o link para o arquivo de estilo, como fizemos antes, usaremos uma sintaxe específica do Django:


<link rel="stylesheet" href="{% static 'Users/styles.css' %}" >

De forma que nosso HTML tenha forma:


{% load static %}

<!DOCTYPE html>
<html lang="pt-br">

    <head>
        <title>{{ nome }}</title>
        <link rel="stylesheet" href="{% static 'Users/styles.css' %}" >
    </head>

    <body>

        <h1>Oi {{ nome }}!</h1>

        <p>Essa é a página de {{ nome }}.</p>

        {% if adm %}

        <p>{{ nome }} é um administrador.</p>

        {% else %}

        <p>{{ nome }} não é um administrador.</p>
                        
        {% endif %}

    </body>

</html>

Agora, se reiniciarmos o servidor, veremos que as mudanças de estilo foram aplicadas de fato:
Nome laranja

Nota: a depender da versão, é possível ter algum erro envolvendo o import do static. Caso isso aconteça, vá ao settings.py do projeto, procure pela linha STATIC_URL = 'static/' e coloque como STATIC_URL = '/static/'

Tarefas

Agora, vamos pegar o que aprendemos até aqui e aplicar em um mini-projeto: criar uma lista de cursos. Vamos começar, mais uma vez, criando um novo app Cursos:

  1. Execute python manage.py startapp Cursos no terminal.
  2. Edite o arquivo settings.py, adicionando "Cursos" à lista INSTALLED_APPS
    Novo app cursos
  3. Edite o arquivo urls.py do projeto e inclua um path semelhante ao que criamos para o app Users

     
        path('cursos/', include("Cursos.urls"))
    
    
  4. Crie outro arquivo urls.py dentro do diretório do novo app e atualize-o para incluir um caminho semelhante ao index do Users:

    
    from django.urls import path
    from . import views
    
    urlpatterns = [
        path("", views.index, name="index")
    ]
    
    

Agora, vamos começar tentando simplesmente criar uma lista de cursos e exibi-la em uma página. Vamos criar uma lista Python no topo do arquivo views.py onde armazenaremos nossos cursos. Depois, podemos atualizar a função index para renderizar um template e fornecer nossa lista recém-criada como contexto.


from django.shortcuts import render

cursos = ["dev.learn", "dev.hire", "hackfools"]

# Create your views here.

def index(request):
    return render(request, "Cursos/index.html", {"cursos": cursos})
            

Agora, vamos trabalhar na criação do nosso arquivo HTML de template:


<!DOCTYPE html>
<html lang="pt-br">

    <head>
        <title>Cursos</title>
    </head>

    <body>

        <h1>Cursos</h1>

        <ul>

            {% for curso in cursos %}

                <li>{{ curso }}</li>

            {% endfor %}

        </ul>

    </body>

</html>

Repare que conseguimos iterar sobre nossas tarefas usando uma sintaxe semelhante às condicionais que vimos anteriormente, e também parecida com um laço for em Python. Quando acessamos a página de tarefas agora, conseguimos ver nossa lista sendo exibida:
Lista de cursos

Formulários

Agora que conseguimos ver todos os cursos como uma lista, talvez queiramos poder adicionar novos cursos. Para isso, vamos começar a explorar o uso de forms (formulários) para atualizar uma página da web. Vamos começar adicionando outra função em views.py que irá renderizar uma página com um formulário para adicionar uma novo curso:


def novo_curso(request):
    return render(request, "Cursos/novo_curso.html")

Em seguida, certifique-se de adicionar outro path ao urls.py:


path("novo", views.novo_curso, name="novo_curso")

Agora, vamos criar nosso arquivo novo_curso.html, que é bem parecido com o index.html, exceto que, no corpo, incluiremos um formulário em vez de uma lista:


<!DOCTYPE html>
<html lang="pt-br">

    <head>
        <title>Cursos</title>
    </head>

    <body>

        <h1>Adicionar Curso:</h1>

        <form action="">

            <input type="text" name="curso">
            <input type="submit">

        </form>

    </body>

</html>

No entanto, o que acabamos de fazer não é necessariamente o melhor design, pois repetimos a maior parte do HTML em dois arquivos diferentes. A linguagem de template do Django nos oferece uma forma de eliminar esse design ruim: herança de template. Isso nos permite criar um arquivo layout.html na pasta de templates que conterá a estrutura geral da nossa página:


<!DOCTYPE html>
<html lang="pt-br">

    <head>
        <title>Cursos</title>
    </head>

    <body>
        {% block body %}
        {% endblock %}
    </body>

</html>

Repare que novamente usamos {%...%} para indicar algum tipo de lógica não-HTML e, nesse caso, estamos dizendo ao Django para preencher esse "bloco" com algum conteúdo vindo de outro arquivo. Agora, podemos alterar nossos outros dois arquivos HTML para ficarem assim:

index.html:


{% extends "Cursos/layout.html" %}

{% block body %}

    <h1>Cursos</h1>

    <ul>

        {% for curso in cursos %}

            <li>{{ curso }}</li>

        {% endfor %}

    </ul>

{% endblock %}

novo_curso.html:


{% extends "Cursos/layout.html" %}

{% block body %}

    <h1>Adicionar Curso:</h1>

    <form action="">

        <input type="text" name="curso">
        <input type="submit">

    </form>

{% endblock %}

Perceba como agora conseguimos eliminar grande parte do código repetido ao estender nosso arquivo de layout. Assim, nossa página index continua a mesma e agora temos também uma página add:


Novo curso

Em seguida, não é ideal ter que digitar "/novo" na URL toda vez que quisermos adicionar uma novo curso, então provavelmente vamos querer adicionar alguns links entre as páginas. Em vez de codificar os links diretamente, agora podemos usar a variável name que atribuímos a cada rota no urls.py e criar um link assim:


<a href="{% url 'novo_curso' %}">Adicionar novo curso</a>

Onde 'novo_curso' é o nome daquela rota. Podemos fazer algo semelhante em nosso novo_curso.html:


<a href="{% url 'index' %}">Adicionar novo curso</a>
            

Isso, no entanto, pode criar um problema, já que temos algumas rotas chamadas index em diferentes apps. Podemos resolver isso indo em cada um dos arquivos urls.py dos nossos apps e adicionando uma variável app_name para que os arquivos agora fiquem parecidos com isto:


from django.urls import path
from . import views

app_name = "Cursos"

urlpatterns = [
    path("", views.index, name="index"),
    path("novo", views.novo_curso, name="novo_curso")
]

Com isso, podemos mudar nossos links de simplesmente index e novo_curso para Cursos:index e Cursos:novo_curso


<a href="{% url 'Cursos:index' %}">Adicionar novo curso</a>

<a href="{% url 'Cursos:novo_curso' %}">Adicionar novo curso</a>
            


Redirecionamento para novo curso
Redirecionamento para lista de cursos

Agora, vamos trabalhar para garantir que o formulário realmente faça algo quando o usuário o submeter. Podemos fazer isso adicionando um action no formulário que criamos em novo_curso.html:


    <form action="{% url 'Cursos:novo_curso' %}" method="post">

Isso significa que, ao submeter o formulário, seremos redirecionados novamente à URL /novo. Aqui, especificamos que usaremos o método post em vez do método get que é o mais apropriado sempre que um formulário pode alterar o estado da página.

Precisamos adicionar um pouco mais ao nosso formulário agora, porque o Djan go exigeum token para prevenir ataques de falsificação de solicitação entre sites (Cross-Site Request Forgery, ou CSRF). Esse é um tipo de ataque onde um usuário malicioso tenta enviar uma requisição para seu servidor a partir de outro site que não é o seu. Isso pode ser um problema sério para alguns sites. Suponha, por exemplo, que um site de banco tenha um formulário para um usuário transferir dinheiro para outro. Seria catastrófico se alguém conseguisse submeter uma transferência a partir de fora do site do banco!

Para resolver esse problema, quando o Django envia uma resposta renderizando um template, ele também fornece um token CSRF que é único para cada nova sessão no site. Então, quando uma requisição é submetida, o Django verifica se o token CSRF associado à requisição corresponde a um que foi fornecido recentemente. Assim, se um usuário malicioso em outro site tentar submeter uma requisição, ela será bloqueada devido a um token CSRF inválido. Essa validação CSRF é integrada ao Middleware do Django, que pode intervir no processamento de requisições e respostas de uma aplicação Django. Não entraremos em mais detalhes sobre Middleware neste curso, mas vale a pena consultar a documentação se estiver interessado!

Para incorporar essa proteção no nosso código, devemos adicionar a linha {% csrf_token %} ao nosso formulário em novo_curso.html.


{% extends "Cursos/layout.html" %}

{% block body %}

    <h1>Adicionar Curso:</h1>

    <form action="{% url 'Cursos:novo_curso' %}" method="post">
        {% csfr_token %}

        <input type="text" name="curso">
        <input type="submit">

    </form>

{% endblock %}

Essa linha adiciona um campo oculto com o token CSRF fornecido pelo Django, de forma que, ao recarregarmos a página, parece que nada mudou. No entanto, se inspecionarmos o elemento, notaremos que um novo campo de entrada foi adicionado:
HTML do token CSRF

Formulários do Django

Embora possamos criar formulários escrevendo HTML puro, como acabamos de fazer, o Django fornece uma maneira ainda mais fácil de coletar informações de um usuário: os Formulários do Django. Para usar esse método, adicionaremos o seguinte no topo do views.py para importar o módulo forms:


from django import forms

Agora, podemos criar um novo formulário dentro de views.py criando uma classe Python chamada NewTaskForm:


class NewTaskForm(forms.Form):
    curso = forms.CharField(label="Novo Curso")
            

Agora, vamos entender o que está acontecendo dentro dessa classe:

Agora que criamos a classe NewTaskForm, podemos incluí-la no contexto ao renderizar a página novo_curso


def novo_curso(request):
    return render(request, "Cursos/novo_curso.html", {"form": NewTaskForm()})

Agora, dentro do novo_curso.html, podemos substituir nosso campo de entrada (input) pelo formulário que acabamos de criar:


{% extends "Cursos/layout.html" %}

{% block body %}

    <h1>Adicionar Curso:</h1>

    <form action="{% url 'Cursos:novo_curso' %}" method="post">
        {% csfr_token %}

        {% form %}
        <input type="submit">

    </form>

    <a href="{% url 'Cursos:index' %}">Ver cursos</a>

{% endblock %}

Existem várias vantagens em usar o módulo forms em vez de escrever manualmente um formulário HTML:

Agora que temos um formulário configurado, vamos trabalhar no que acontece quando um usuário clica no botão de envio. Quando um usuário acessa a página de adição clicando em um link ou digitando a URL, ele envia uma requisição GET para o servidor, que já tratamos na nossa função novo_curso. No entanto, quando ele envia um formulário, ele faz uma requisição POST para o servidor, que no momento ainda não está sendo tratada na função novo_curso. Podemos tratar o método POST adicionando uma condição baseada no argumento request que nossa função recebe. Os comentários no código abaixo explicam o propósito de cada linha:


def novo_curso(request):

    if (request.method == "POST"):

        # Salva em um form os dados submetidos pelo usuário
        form = NewTaskForm(request.POST)

        # Checa se os dados do form são válidos (server-side)
        if (form.is_valid()):

            #Isola o curso da versão 'limpa' dos dados do form
            curso = form.cleaned_data["curso"]

            # Adiciona o novo curso na lista de cursos
            cursos.append(curso)

            # Redireciona o usuário para a lista de cursos
            return HttpResponseRedirect(reverse("Cursos:index"))

        else:

            # Se o formulário é inválido, re-renderiza a página com informações existentes.
            return render(request, "Cursos/novo_curso.html", {"form": form})

        return render(request, "Cursos/novo_curso.html", {"form": NewTaskForm()})

Observação - para redirecionar o usuário após um envio bem-sucedido, precisaremos de mais alguns imports:


from django.urls import reverse
from django.http import HttpResponseRedirect

Finalmente, podemos adicionar um curso:
Adicionando novo curso
Novo curso na lista de cursos

Sessões

Neste ponto, conseguimos construir uma aplicação que nos permite adicionar cursos a uma lista crescente. No entanto, pode ser problemático armazenar essas tarefas como uma variável global, pois isso significa que todos os usuários que acessam a página veem exatamente a mesma lista. Para resolver esse problema, vamos usar uma ferramenta conhecida como sessões.

Sessões são uma maneira de armazenar dados únicos no lado do servidor para cada nova visita a um site.

Para usar sessões em nossa aplicação, primeiro vamos deletar a variável global cursos, depois alterar nossa função index, e por fim, garantir que em todos os lugares onde usamos a variável cursos ela seja substituida por request.session["cursos"]


def index(request):

    # Verifica se já existe uma chave "cursos" na sessão atual
    if ("cursos" not in request.session):

        # Se não estiver, cria uma lista nova
        request.session["cursos"] = []

    return render(request, "Cursos/index.html", {"cursos": request.session["cursos"]})

def novo_curso(request):

    if (request.method == "POST"):

        # Salva em um form os dados submetidos pelo usuário
        form = NewTaskForm(request.POST)

        # Checa se os dados do form são válidos (server-side)
        if (form.is_valid()):

            #Isola o curso da versão 'limpa' dos dados do form
            curso = form.cleaned_data["curso"]

            # Adiciona o novo curso na lista de cursos
            request.session["cursos"] += [curso]

            # Redireciona o usuário para a lista de cursos
            return HttpResponseRedirect(reverse("Cursos:index"))

        else:

            # Se o formulário é inválido, re-renderiza a página com informações existentes.
            return render(request, "Cursos/novo_curso.html", {"form": form})

    return render(request, "Cursos/novo_curso.html", {"form": NewTaskForm()})

Assim, temos que as telas por sessão serão:
Nova sessão
Adicionando curso em nova sessão
Novo curso adicionado na lista da sessão

Por fim, antes que o Django possa armazenar esses dados, devemos executar o seguinte comando no terminal: python manage.py migrate in the terminal. Na próxima semana falaremos mais sobre o que é uma migration (migração), mas por enquanto, basta saber que esse comando permite que o Django armazene sessões.

Isso é tudo por esta aula!
Na próxima, trabalharemos com o uso do Django para armazenar, acessar e manipular dados.