# Formulários do Django

Por último, queremos uma forma legal de adicionar e editar as postagens do nosso blog. A `ferramenta de administração` do Django é legal, mas é um pouco difícil de personalizar e de deixar mais bonita. Com `formulários`, temos poder absoluto sobre nossa interface - podemos fazer quase tudo que pudermos imaginar!

Uma coisa legal do Django é que podemos tanto criar um formulário do zero, como criar um `ModelForm` que salva o resultado do formulário em um determinado modelo.

É exatamente isso que queremos fazer: criar um formulário para o nosso modelo `Post`.

Assim como todas as partes importantes do Django, forms têm seu próprio arquivo: `forms.py`.

Precisamos criar um arquivo com este nome dentro da pasta `blog`.

```
blog
   └── forms.py
```

Agora vamos abri-lo no editor de código e digitar o seguinte:

```python
from django import forms

from .models import Post

class PostForm(forms.ModelForm):

    class Meta:
        model = Post
        fields = ('title', 'text',)
```

Primeiro, precisamos importar o módulo de formulários do Django (*from django import forms*) e, obviamente, o nosso modelo *Post* (*from .models import Post*).

`PostForm`, como você já deve suspeitar, é o nome do nosso formulário. Precisamos dizer ao Django que esse form é um `ModelForm` (pro Django fazer algumas mágicas para nós) – `forms.ModelForm` é o responsável por essa parte.

Em seguida, temos a `class Meta` em que dizemos ao Django qual modelo deverá ser usado para criar este formulário (`model = Post`).

Por fim, podemos dizer quais campos devem entrar no nosso formulário. Neste cenário, queremos que apenas o `title` e o `text` sejam expostos -- `author` deve ser a pessoa que está logada no sistema (nesse caso, você!) e `created_date` deve ser configurado automaticamente quando criamos um post (no código), correto?

E é isso! Tudo o que precisamos fazer agora é usar o formulário em uma *view* e mostrá-lo em um template.

Novamente, criaremos um link para a página, uma URL, uma view e um template.

## Link para a página com o formulário

É hora de abrir *blog/templates/blog/base.html*. Nós iremos adicionar um link em *div* nomeado *page-header*:

```html
<a href="

<div data-gb-custom-block data-tag="url" data-0='post_new'></div>

" class="top-menu"><span class="glyphicon glyphicon-plus"></span></a>
```

Note que queremos chamar nossa nova view de `post_new`. A classe `"glyphicon glyphicon-plus"` é fornecida pelo tema (bootstrap) que estamos usando, e nos mostrará um sinal de mais.

Depois de adicionar essa linha, o seu HTML vai ficar assim:

```html

<div data-gb-custom-block data-tag="load"></div>

<html>
    <head>
        <title>Django Girls blog</title>
        <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
        <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css">
        <link href='//fonts.googleapis.com/css?family=Lobster&subset=latin,latin-ext' rel='stylesheet' type='text/css'>
        <link rel="stylesheet" href="

<div data-gb-custom-block data-tag="static" data-0='css/blog.css'></div>">
    </head>
    <body>
        <div class="page-header">
            <a href="<div data-gb-custom-block data-tag="url" data-0='post_new'></div>" class="top-menu"><span class="glyphicon glyphicon-plus"></span></a>
            <h1><a href="/">Django Girls Blog</a></h1>
        </div>
        <div class="content container">
            <div class="row">
                <div class="col-md-8">
                    <div data-gb-custom-block data-tag="block">

                    

</div>

                </div>
            </div>
        </div>
    </body>
</html>
```

Depois de salvar e recarregar a página *<http://127.0.0.1:8000>* você verá um erro familiar `NoReverseMatch` certo? É isso!

## URL

Vamos abrir o arquivo *blog/urls.py* e escrever:

```python
path('post/new/', views.post_new, name='post_new'),
```

O código final deve se parecer com isso:

```python
from django.urls import path 
from . import views

urlpatterns = [
    path('', views.post_list, name='post_list'),
    path('post/<int:pk>/', views.post_detail, name='post_detail'),
    path('post/new/', views.post_new, name='post_new'),
]
```

Após recarregar a página, veremos um `AttributeError` por que não temos a view `post_new` implementada. Vamos adicioná-la agora.

## View post\_new

Hora de abrir o arquivo *blog/views.py* e adicionar o seguinte às linhas *from*:

```python
from .forms import PostForm
```

E então a nossa *view*:

```python
def post_new(request):
    form = PostForm()
    return render(request, 'blog/post_edit.html', {'form': form})
```

Para criar um novo formulario `Post`, devemos chamar `PostForm()` e passá-lo para o template. Voltaremos a esta *view* depois, mas por enquanto, vamos criar um template para o formulário.

## Template

Precisamos criar um arquivo *post\_edit.html* na pasta *blog/templates/blog*. Para fazer o formulário funcionar, precisamos de várias coisas:

* Temos que exibir o formulário. Podemos fazer isso com (por exemplo) `{{ form.as_p }}`.
* A linha acima precisa estar dentro de uma tag HTML form: `<form method="POST">...</form>`.
* Precisamos de um botão `Salvar`. Fazemos isso com um botão HTML: `<button type="submit">Save</button>`.
* E finalmente, depois de abrir a tag `<form ...>`, precisamos adicionar `{% csrf_token %}`

. Isso é muito importante, pois é isso que torna o nosso formulário seguro! Se você esquecer esta parte, o Django vai reclamar quando você tentar salvar o formulário:

![CSFR Página proibida](https://4268218858-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjA4gNWaXcNDJ3xYgWu3O%2Fuploads%2Fgit-blob-ee946324f92cdda1ac998d103e2dc69ed203ad54%2Fcsrf2.png?alt=media)

Legal, então vamos ver como ficou o HTML `post_edit.html`:

```html

<div data-gb-custom-block data-tag="extends" data-0='blog/base.html'></div>

<div data-gb-custom-block data-tag="block">

    <h2>New post</h2>
    <form method="POST" class="post-form">

<div data-gb-custom-block data-tag="csrf_token"></div>

        {{ form.as_p }}
        <button type="submit" class="save btn btn-default">Save</button>
    </form>

</div>

```

Hora de atualizar! Uhuu! Seu formulário apareceu!

![Novo formulário](https://4268218858-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjA4gNWaXcNDJ3xYgWu3O%2Fuploads%2Fgit-blob-8f2a108807082c89a601e61c13285d202699f22b%2Fnew_form2.png?alt=media)

Mas espere um minuto! O que vai acontecer quando você digitar alguma coisa nos campos `title` e `text` e tentar salvar?

Nada! Estamos novamente na mesma página e nosso texto sumiu... e nenhum post foi adicionado. Então o que deu errado?

A resposta é: nada. Precisamos trabalhar um pouco mais na nossa *view*.

## Salvando o formulário

Abra *blog/views.py* no editor de código mais uma vez. No momento, tudo que temos na view *post\_new* é:

```python
def post_new(request):
    form = PostForm()
    return render(request, 'blog/post_edit.html', {'form': form})
```

Quando enviamos o formulário, somos trazidas de volta à mesma view, mas desta vez temos mais alguns dados no `request`, especificamente em `request.POST` (o nome não tem nada a ver com "post" de blog; tem a ver com o fato que estamos "postando" dados). Lembra que no arquivo HTML, nossa definição de `form` incluiu a variável `method="POST"`? Todos os campos vindos do "form" estarão disponíveis agora em `request.POST`. Não renomeie `POST` para nada diferente disso (o único outro valor válido para `method` é `GET`, mas não temos tempo para explicar a diferença).

Então em nossa *view* temos duas situações diferentes com as quais lidar: primeiro, quando acessamos a página pela primeira vez e queremos um formulário em branco e segundo, quando voltamos para a *view* com todos os dados do formulário que acabamos de digitar. Desse modo, precisamos adicionar uma condição (usaremos `if` para isso):

```python
if request.method == "POST":
    [...]
else:
    form = PostForm()
```

É hora de preencher os pontos `[...]`. Se `method` é `POST`, queremos construir o `PostForm` com dados do formulário, certo? Faremos assim:

```python
form = PostForm(request.POST)
```

O próximo passo é checar se o formulário está correto (todos os campos requeridos estão prontos e valores incorretos não serão salvos). Fazemos isso com `form.is_valid()`.

Verificamos se o formulário é válido e se estiver tudo certo, podemos salvá-lo!

```python
if form.is_valid():
     post = form.save(commit=False)
     post.author = request.user
     post.published_date = timezone.now()
     post.save()
```

Basicamente, temos duas coisas aqui: salvamos o formulário com `form.save` e adicionamos um autor (já que houve um campo `author` em `PostForm`, e este campo é obrigatório). `commit=False` significa que não queremos salvar o modelo de `Post` ainda - queremos adicionar o autor primeiro. Na maioria das vezes você irá usar `form.save()`, sem `commit=False`, mas neste caso, precisamos fazer isso. `post.save()` vai preservar as alterações (adicionando o autor) e é criado um novo post no blog!

Finalmente, seria fantástico se pudéssemos ir à página `post_detail`, direto para o nosso recém-criado post no blog, né? Para fazer isso, precisaremos de mais uma importação:

```python
from django.shortcuts import redirect
```

Adicione isso logo no início do seu arquivo. Agora podemos dizer: "vá para a página `post_detail` para o post recém-criado":

```python
return redirect('post_detail', pk=post.pk)
```

`post_detail` é o nome da visualização (view) à qual queremos ir. Lembra que essa *view* exige uma variável `pk`? Para passar isso para as `views`, usamos `pk=post.pk`, em que post é o recém-criado post do blog!

Ok, nós falamos muito, e agora queremos ver a cara da *view* completa, né?

```python
def post_new(request):
     if request.method == "POST":
         form = PostForm(request.POST)
         if form.is_valid():
             post = form.save(commit=False)
             post.author = request.user
             post.published_date = timezone.now()
             post.save()
             return redirect('post_detail', pk=post.pk)
     else:
         form = PostForm()
     return render(request, 'blog/post_edit.html', {'form': form})
```

Vamos ver se funciona. Vá para a página <http://127.0.0.1:8000/post/new/>, adicione um `title` e o `text`, salve... e pronto! O novo post do blog é adicionado e somos redirecionadas à página de `post_detail`!

Você deve ter percebido que estamos estabelecendo a data de publicação antes de salvar o post. Mais tarde, vamos introduzir um botão de *Publicar* em **Django Girls Tutorial: Extensões**.

Isso é incrível!

> Como recentemente usamos a interface de administração do Django, o sistema entende que estamos logadas. Existem algumas situações que poderiam nos desligar do sistema (fechar o navegador, reiniciar banco de dados etc.). Se ao criar um post você receber erros que se referem à ausência de um usuário logado, vá até a página de admin <http://127.0.0.1:8000/admin> e faça login novamente. Isso vai resolver o problema temporariamente. Há um ajuste permanente esperando por você em **lição de casa: adicionar segurança ao seu site!**, um capítulo posterior ao tutorial principal.

![Erro de usuário logado](https://4268218858-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjA4gNWaXcNDJ3xYgWu3O%2Fuploads%2Fgit-blob-d140e8e241985c31bf793d6c05e9e2d9b5a99abc%2Fpost_create_error.png?alt=media)

## Validação de formulários

Agora, mostraremos como os fórmularios do Django são legais. O post do blog precisa ter os campos `title` e `text`. Em nosso modelo `Post` não dissemos (em oposição a `published_date`) que esses campos são opcionais, então o Django, por padrão, espera que sejam definidos.

Tente salvar o formulário sem `title` e `text`. Adivinhe o que vai acontecer!

![Validação de formulários](https://4268218858-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjA4gNWaXcNDJ3xYgWu3O%2Fuploads%2Fgit-blob-6e333af307729a8f4cb86903be4f5d71e1f7f57e%2Fform_validation2.png?alt=media)

Django está confirmando que todos os campos de nosso formulário estão corretos. Não é incrível?

## Editando o formulário

Agora sabemos como adicionar um novo formulário. Mas e se quisermos editar um que já existe? É muito parecido com o que acabamos de fazer. Vamos criar algumas coisas importantes rapidinho. (Se você não entender alguma coisa, pergunte para a sua monitora ou veja os capítulos anteriores -- já cobrimos todas essas etapas anteriormente.)

Abra *blog/templates/blog/post\_detail.html* e adicione esta linha

```html
<a class="btn btn-default" href="

<div data-gb-custom-block data-tag="url" data-0='post_edit'></div>

"><span class="glyphicon glyphicon-pencil"></span></a>
```

agora, o template estará parecido com:

```html

<div data-gb-custom-block data-tag="extends" data-0='blog/base.html'></div>

<div data-gb-custom-block data-tag="block">

    <div class="post">
        

<div data-gb-custom-block data-tag="if">

            <div class="date">
                {{ post.published_date }}
            </div>
        

</div>

        <a class="btn btn-default" href="

<div data-gb-custom-block data-tag="url" data-0='post_edit'></div>

"><span class="glyphicon glyphicon-pencil"></span></a>
        <h2>{{ post.title }}</h2>
        <p>{{ post.text|linebreaksbr }}</p>
    </div>

</div>

```

Abra o arquivo *blog/urls.py* no editor de código e digite:

```python
    path('post/<int:pk>/edit/', views.post_edit, name='post_edit'),
```

Vamos reutilizar o template `blog/templates/blog/post_edit.html`, então a última coisa que falta é uma *view*.

Vamos abrir *blog/views.py* no editor de código e adicionar o seguinte ao final do arquivo:

```python
def post_edit(request, pk):
     post = get_object_or_404(Post, pk=pk)
     if request.method == "POST":
         form = PostForm(request.POST, instance=post)
         if form.is_valid():
             post = form.save(commit=False)
             post.author = request.user
             post.published_date = timezone.now()
             post.save()
             return redirect('post_detail', pk=post.pk)
     else:
         form = PostForm(instance=post)
     return render(request, 'blog/post_edit.html', {'form': form})
```

Isso é quase igual à nossa view de `post_new`, né? Mas não inteiramente. Primeira coisa: passamos um parâmetro extra `pk` a partir da url. Em seguida, pegamos o modelo `Post` que queremos editar com `get_object_or_404 (Post, pk=pk)` e então, quando criamos um formulário, passamos este post como uma `instância` tanto quando salvamos o formulário…

```python
form = PostForm(request.POST, instance=post)
```

... como quando apenas abrimos um formulário para editar esse post:

```python
form = PostForm(instance=post)
```

Ok, vamos testar para ver se funciona! Vamos para a página `post_detail`. Deve haver um botão editar no canto superior direito:

![Botão editar](https://4268218858-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjA4gNWaXcNDJ3xYgWu3O%2Fuploads%2Fgit-blob-804674f096502e05e087fcbd1c607f389efdfc1e%2Fedit_button2.png?alt=media)

Quando você clicar nesse botão, verá o formulário com a nossa postagem:

![Editando o formulário](https://4268218858-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjA4gNWaXcNDJ3xYgWu3O%2Fuploads%2Fgit-blob-3d4e525d5d0b8e9d9d50ab36733cb4d69df0ec18%2Fedit_form2.png?alt=media)

Sinta-se livre para mudar o título ou o texto e salvar as alterações!

Parabéns! Sua aplicação está ficando cada vez mais completa!

Se precisar de mais informações sobre formulários do Django, leia a documentação: <https://docs.djangoproject.com/en/2.0/topics/forms/>

## Segurança

Poder criar novos posts apenas clicando em um link é ótimo! Mas nesse momento, qualquer um que visitar nosso site poderá criar um novo post, e você isso provavelmente não quer isso. Vamos fazer com que o botão apareça apenas para você e para mais ninguém.

Em `blog/templates/blog/base.html`, procure nossa `div` `page-header` e a tag de link que você colocou mais cedo. Deve se parecer com:

```html
<a href="

<div data-gb-custom-block data-tag="url" data-0='post_new'></div>" class="top-menu"><span class="glyphicon glyphicon-plus"></span></a>
```

Vamos incluir outra tag \`

`que irá apresentar o link somente para os usuários que estiverem logados como admin. No momento, é apenas você! Mude a tag`\` para que fique assim:

\<div data-gb-custom-block data-tag="if">    \<a href="\<div data-gb-custom-block data-tag="url" data-0='post\_new'>\</div>" class="top-menu">\<span class="glyphicon glyphicon-plus">\</span>\</a>\</div>Este \`\` fará com que o link seja enviado ao navegador se o usuário que requisitou a página estiver logado. Isso não protege o blog completamente da criação de um novo post, mas é um bom começo. Vamos falar mais sobre segurança nas próximas lições.Lembra do ícone Editar que acabamos de adicionar à nossa página de detalhes? Queremos fazer a mesma coisa com ele para que outras pessoas não possam editar as mensagens já existentes.Abra `blog/templates/blog/post_detail.html` e encontre esta linha:\<a class="btn btn-default" href="\<div data-gb-custom-block data-tag="url" data-0='post\_edit'>\</div>">\<span class="glyphicon glyphicon-pencil">\</span>\</a>Altere-a para:\<div data-gb-custom-block data-tag="if">    \<a class="btn btn-default" href="\<div data-gb-custom-block data-tag="url" data-0='post\_edit'>\</div>">\<span class="glyphicon glyphicon-pencil">\</span>\</a>\</div>Você provavelmente está logada, então se atualizar a página, não verá nada de diferente. Carregue a página em um navegador novo ou em uma janela anônima (chamada "InPrivate" no Windows Edge), e então você verá que o link não aparece, e o ícone também não!Mais uma coisa: hora de implantar!Vamos ver se tudo isso funciona no PythonAnywhere. Hora de fazer outro deploy!Primeiro, faça o commit do seu novo código e dê o comando push para colocá-lo no Github:$ git status $ git add --all . $ git status $ git commit -m "Added views to create/edit blog post inside the site." $ git push

* Então, em um [console Bash do PythonAnywhere](https://www.pythonanywhere.com/consoles/):

```
$ cd ~/<your-pythonanywhere-domain>.pythonanywhere.com
$ git pull
[...]
```

(Lembre de substituir `<your-pythonanywhere-domain>` pelo seu subdomínio PythonAnywhere, sem os símbolos < e >.)

* Finalmente, vá para a [página "Web"](https://www.pythonanywhere.com/web_app_setup/) (use o botão de menu no canto superior direito do console) e pressione **Recarregar**. Atualize seu blog <https://subdominio.pythonanywhere.com> para ver as mudanças.

E deve ser isso! Parabéns :)
