terça-feira, 12 de fevereiro de 2013

Mega-Tutorial Flask | The Flask Mega-Tutorial (pt-BR) | Parte II


Dando sequência a tradução da série, segue a Parte II. Esta postagem é uma tradução livre para o português do Brasil, feita por mim, a partir do texto original, publicado por Miguel Grinberg em 09/05/2012.

Mega-Tutorial Flask - Parte II: Templates


Este é o segundo artigo da série onde eu documento minha experiência escrevendo aplicações web em Python usando o microframework Flask.

O objetivo dessa série de tutoriais é desenvolver um aplicativo de microblog decentemente caracterizado que, demonstrando total falta de originalidade, eu decidi chamar microblog.

Aqui está um índice de todos os artigos da série que foram publicados até agora:


  • Parte I: Olá, Mundo!
  • Parte II: Templates
  • Parte III: Formulários Web (Web Forms)
  • Parte IV: Banco de Dados
  • Parte V: Contas de Usuário
  • Parte VI: Páginas de Perfil e Avatares
  • Parte VII: Testes Unitários
  • Parte VIII: Seguidores, Contatos e Amigos
  • Parte IX: Paginação
  • Parte X: Busca em Todo o Texto
  • Parte XI: Suporte a E-mail
  • Parte XII: Lift facial
  • Parte XIII: Datas e Horas
  • Parte XIV: Internacionalização e Localização


Recapitulando


Se você seguiu corretamente o capítulo anterior você deve ter uma aplicação web muito simples que tem a seguinte estrutura de arquivos:

    microblog\
      flask\
        <virtual environment files>
      app\
        static\
        templates\
        __init__.py
        views.py
      tmp\
      run.py

Para acessar a aplicação basta executar o script run.py e abrir a URL http://localhost:5000 em seu navegador web.

Estamos retomando exatamente de onde paramos. Logo, você pode querer ter certeza de que a aplicação acima está corretamente instalada e funcionando.

Porque nós precisamos de Templates


Vamos considerar como podemos expandir nossa pequena aplicação.

Nós queremos que no cabeçalho da página inicial do nosso aplicativo de microblog tenha uma saudação de boas vindas ao usuário logado, que é bastante comum para aplicações deste tipo. Ignore por enquanto o fato de que não temos usuários do aplicativo. Eu vou apresentar uma solução para este problema em momento oportuno.

Uma maneira fácil para renderizar um título bonito e grande seria mudar o retorno da nossa view para HTML, talvez algo como isto:

from app import app

@app.route('/')
@app.route('/index')
def index():
    user = { 'nickname': 'Miguel' } # fake user
    return '''
<html>
  <head>
    <title>Home Page</title>
  </head>
  <body>
    <h1>Hello, ''' + user['nickname'] + '''</h1>
  </body>
</html>
'''

Faça isto e execute. Verifique como é a saída no seu navegador web.

Uma vez que ainda não temos suporte para usuários, recorremos ao uso de um objeto, às vezes chamado de fake object ou mock. Isso permite nos concentrar em certos aspectos do nosso aplicativo que dependem de partes do sistema que ainda não foram construídos.

Espero que você concorde comigo que a solução acima é muito feia. Considere como o código ficará confuso se você tiver que retornar uma página HTML grande e complexa com muito conteúdo dinâmico. E se você precisar alterar o layout de seu site que é um aplicativo grande que tem dezenas de views, cada uma retornando HTML diretamente? Esta, claramente, não é uma opção escalável.

Templates ao resgate


Se você pudesse manter a lógica do seu aplicativo separado do layout ou apresentação de suas páginas web as coisas seriam muito mais bem organizadas, você não acha? Você poderia até mesmo contratar um web designer para criar um web site matador enquanto você codificaria os comportamentos do site em Python. Templates servem para implementar esta separação.

Vamos escrever nosso primeiro template (arquivo app/templates/index.html)

<html>
  <head>
    <title>{{title}} - microblog</title>
  </head>
  <body>
      <h1>Hello, {{user.nickname}}!</h1>
  </body>
</html>


Como você pode ver acima, apenas escrevemos uma página HTML padrão. A única diferença é que há alguns espaços reservados para o conteúdo dinâmico delimitados por {{ ... }}.

Agora vamos ver como usaríamos este template em nossa view (arquivo app/views.py):

from flask import render_template
from app import app

@app.route('/')
@app.route('/index')
def index():
    user = { 'nickname': 'Miguel' } # fake user
    return render_template("index.html",
        title = 'Home',
        user = user)

Teste a aplicação neste momento para ver como o template funciona. Depois de ter a página renderizada em seu navegador você pode querer inspecionar o código HTML e compará-la com o template original.

Para renderizar o template tivemos que importar uma nova função do Flask chamada render_template. Esta função recebe como parâmetros o nome de um template e uma lista com os argumentos do template e o retorna renderizado, com todos os argumentos substituídos.

Por baixo dos panos, a função render_template chama o mecanismo de templates Jinja2 que faz parte do Flask. O Jinja2 substitui os blocos {{...}} pelos valores correspondentes fornecidos como argumentos do template.

Instruções de controle em Templates


Os templates Jinja2 também suportam instruções de controle escritos dentro de blocos delimitados por {% ... %}. Vamos adicionar uma instrução 'if' ao nosso template (arquivo app/templates/index.html):

<html>
  <head>
    {% if title %}
    <title>{{title}} - microblog</title>
    {% else %}
    <title>Welcome to microblog</title>
    {% endif %}
  </head>
  <body>
      <h1>Hello, {{user.nickname}}!</h1>
  </body>
</html>

Agora, o nosso template é um pouco mais inteligente. Se a na definição da view for omitida a atribuição do título da página, em vez de gerar uma exceção o template irá fornecer seu próprio título. Sinta-se livre para remover o argumento título na chamada do render_template da nossa view para ver como funciona a cláusula IF.

Loops em templates


O usuário conectado em nosso aplicativo microblog provavelmente vai querer ver os posts recentes dos seus contatos em sua home page, então vamos ver como podemos fazer isso.

Para começar, usaremos o truque do fake object para criar alguns usuários e posts para mostrar (arquivo app/views.py):

def index():
    user = { 'nickname': 'Miguel' } # fake user
    posts = [ # fake array of posts
        { 
            'author': { 'nickname': 'John' }, 
            'body': 'Beautiful day in Portland!' 
        },
        { 
            'author': { 'nickname': 'Susan' }, 
            'body': 'The Avengers movie was so cool!' 
        }
    ]
    return render_template("index.html",
        title = 'Home',
        user = user,
        posts = posts)

Para representar mensagens dos usuários nós usamos uma lista onde cada elemento tem os campos autor e corpo. Quando nós começarmos a implementar um banco de dados real iremos preservar esses nomes de campo para que possamos projetar e testar nosso modelo usando os fake objects sem ter que nos preocupar com atualização quando migrarmos para o banco de dados.

No lado do template temos que resolver um problema novo. A matriz pode ter qualquer número de elementos. Caberá a view decidir a quantidade de mensagens a serem apresentadas. O template não pode fazer quaisquer suposições sobre o número de posts. Por isso, precisa estar preparado para renderizar tantos posts quanto a view envia.

Então vamos ver como fazer isso usando uma estrutura de controle for (arquivo app/templates/index.html):

<html>
  <head>
    {% if title %}
    <title>{{title}} - microblog</title>
    {% else %}
    <title>microblog</title>
    {% endif %}
  </head>
  <body>
    <h1>Hi, {{user.nickname}}!</h1>
    {% for post in posts %}
    <p>{{post.author.nickname}} says: <b>{{post.body}}</b></p>
    {% endfor %}
  </body>
</html>

Simples, não? Teste a aplicação e não se esqueça de brincar com a adição de conteúdo na matriz de mensagens.

Herança


Temos mais um tópico para cobrir antes de encerrar por hoje.

Nosso microblog web precisa ter uma barra de navegação no topo da página com alguns links. Nela deve ter um link para editar o perfil do usuário, fazer logout, etc.

Nós podemos adicionar uma barra de navegação no nosso template index.html, mas conforme nossa aplicação cresce, vai precisar de mais templates e esta barra de navegação terá de ser copiada para todos eles. Então, você sempre terá que manter todas estas cópias sincronizadas, o que poderá tornar-se muito trabalhoso se você tem muitos templates.

Em vez disso, podemos utilizar o recurso de herança de templates do Jinja2. O que nos permite mover as partes do layout de página, que são comuns a todos os templates e colocá-los em um template base, a partir da qual todos os outros templates são derivados.

Portanto, vamos criar um template base que inclui a barra de navegação e também o pouco de lógica do título que implementamos anteriormente (arquivo app/templates/base.html):
<html>
  <head>
    {% if title %}
    <title>{{title}} - microblog</title>
    {% else %}
    <title>microblog</title>
    {% endif %}
  </head>
  <body>
    <div>Microblog: <a href="/index">Home</a></div>
    <hr>
    {% block content %}{% endblock %}
  </body>
</html>

Neste template, utilizamos a declaração de controle 'block' para definir o lugar onde os modelos derivados poderão inserir-se. Blocos recebem nomes únicos.

E agora o que resta a fazer é modificar o nosso template index.html para herdar de base.html (arquivo app/templates/index.html):
{% extends "base.html" %}
{% block content %}
<h1>Hi, {{user.nickname}}!</h1>
{% for post in posts %}
<div><p>{{post.author.nickname}} says: <b>{{post.body}}</b></p></div>
{% endfor %}
{% endblock %}

Uma vez que o template base.html irá tomar conta da estrutura geral da página, tais elementos foram removidos do presente template, ficando apenas a parte do conteúdo. O bloco 'extends' estabelece o elo de herança entre os dois templates para que o Jinja2 saiba que, quando precisar renderizar o index.html, precisará incluí-lo dentro de base.html. Os dois templates têm blocos correspondentes com o nome 'content' e é assim que Jinja2 sabe como combinar os dois em um. Quando nós começarmos a escrever novos templates também vamos criá-los como extensões para base.html.

Últimas Palavras


Se você quiser economizar tempo, a aplicação microblog em seu estado atual está disponível aqui:

Download microblog-0.2.zip.

Observe que o arquivo zip não inclui o ambiente virtual flask e você terá que criá-lo seguindo as instruções descritas no primeiro capítulo da série antes de executar o aplicativo.

Se você tiver dúvidas ou comentários sinta-se livre para deixá-los abaixo.

No próximo capítulo da série iremos trabalhar com formulários web. Espero vê-lo em seguida.

Miguel