Retirando elementos vazios de uma lista

Há poucos dias, lendo um código escrito pelo amigo Elias Dorneles, me deparei com uma forma bem elegante de desconsiderar os elementos vazios ou nulos de uma lista:

lista = ['algumas', 'palavras', '', 'e', 'nada', 'mais', '']
lista = filter(None, lista)
# lista fica: ['algumas', 'palavras', 'e', 'nada', 'mais']

Legal, não? Pois é, isso está na documentação da função:

filter(function, iterable)
Construct a list from those elements of iterable for which function returns true. iterable may be either a sequence, a container which supports iteration, or an iterator. If iterable is a string or a tuple, the result also has that type; otherwise it is always a list. If function is None, the identity function is assumed, that is, all elements of iterable that are false are removed.

Note that filter(function, iterable) is equivalent to [item for item in iterable if function(item)] if function is not None and [item for item in iterable if item] if function is None.

. . .

Ou seja, se passarmos None como valor para o parâmetro function, filter irá retornar uma nova lista contendo somente os elementos da lista original que não forem avaliados como False.

Templates de páginas com Python e Google App Engine

Sumário

def get(self):
    self.response.out.write("<html><head><title>Lista de Comentários</title></head><body>")
    for c in Comentario.objects.all():
        self.response.out.write("""<p>Nome: %s</p><p>Email: %s</p>
            <p>URL: %s</p><p>Comentário: %s</p>""" % 
                (c.nome_usuario, c.email, c.url, c.comentario))
    self.response.out.write("</body></html>")

Diga aí, o que você achou da implementação do método get() no código acima? Esse monte de HTML embutido em strings no código Python fica horrível, não?

Pois então, este post vai mostrar uma forma mais adequada para usar conteúdo em HTML no seu projeto web: os templates (ou, em bom português, modelos).

Templates são HTMLs açucarados

Templates, em um projeto Web com Python + Google App Engine, nada mais são do que arquivos HTML acrescidos de algumas marcações específicas para representar dados dinâmicos.

<html>
    <head><title></title></head>
    <body>
        <p>Nome: {{ nome }}</p>
        <p>Email: {{ email }}</p>
        <p>URL: {{ url }}</p>
        <p>Comentário: {{ comentario }}</p>
    </body>
</html>

Observe que em meio ao HTML, temos 4 campos diferentes: {{ nome }}, {{ url }}, {{ email }} e {{ mensagem }}. Em um template, sempre que houver uma string do tipo {{ nome }}, o mecanismo de templates tentará substituir tal string pelo valor da variável nome, de forma que esse valor apareça no HTML final (depois veremos como passar valores aos templates).

Além de campos para simples substituição pelo valor de variáveis, o mecanismo de template padrão do GAE fornece construções para fazer repetição, seleção, dentre outras coisas. Veja:

<html>
    <head><title></title></head>
    <body>
        {% for m in mensagens %}
            {% if m.publico %}
                <p>Nome: {{ m.nome_usuario }}</p>
                <p>Email: {{ m.email }}</p>
                <p>URL: {{ m.url }}</p>
                <p>Comentário: {{ m.comentario }}</p>
                <hr>
            {% endif %}
        {% endfor %}
    </body>
</html>

Tudo que o template acima precisaria receber é uma variável chamada comentarios, que fosse uma sequência de objetos do tipo Comentario.

Desse modo, separamos a parte de apresentação da parte lógica do nosso projeto. O código fica mais organizado, limpo e reusável. Vamos ver agora como acoplar esse mecanismo de templates ao nosso projetinho web do post anterior.

Juntando tudo

Precisamos, antes de mais nada, salvar o nosso arquivo que contém o template em um arquivo .html. Vamos chamá-lo de modelo.html:

<html>
    <head><title></title></head>
    <body>
        <h1>Comentários</h1>
        {% for m in mensagens %}
            <hr>
            <h2>Comentário de <em>{{ m.nome_usuario }}</em></h2>
            <p><strong>Email:</strong> {{ m.email }}</p>
            <p><strong>URL:</strong> {{ m.url }}</p>
            <p><strong>Comentário:</strong> {{ m.comentario }}</p>
        {% endfor %}
        <hr>
        <form method="POST" action="/">
            Nome: <p><input type="text" name="nome_usuario"></p>
            Email: <p><input type="text" name="email"></p>
            URL: <p><input type="text" name="url"></p>
            Comentario:<p><textarea name="comentario" cols="40" rows="10"></textarea></p>
            <button type="submit">Enviar</button>
        </form>
    </body>
</html>

Veja que nosso template primeiro renderiza os comentários e, no final deles, apresenta o formulário para envio de uma nova mensagem. Vamos agora reescrever o nosso método get() do post anterior. O que antes era:

def get(self):
    self.response.out.write(html)
    self.response.out.write('<ul>')
    for msg in Mensagem.all():
        self.response.out.write('<li>' + unicode(msg.comentario) + '</li>')
    self.response.out.write('</ul>')

Passará a ser:

def get(self):
    path = os.path.join(os.path.dirname(__file__), 'modelo.html')
    valores = {'mensagens': Mensagem.all()}
    self.response.out.write(template.render(path, valores))

Chamamos a função render() para gerar uma string com conteúdo HTML, com base no arquivo de template modelo.html (passado através da variável path) e do conjunto de valores a ser inserido de forma dinâmica no nosso HTML (dicionário valores). Lembra que, dentro do template, nós referenciávamos uma variável mensagens?

...    
{% for m in mensagens %}
    <hr>
    <h2>Comentário de <em>{{ m.nome_usuario }}</em></h2>
    <p><strong>Email:</strong> {{ m.email }}</p>
    <p><strong>URL:</strong> {{ m.url }}</p>
    <p><strong>Comentário:</strong> {{ m.comentario }}</p>
{% endfor %}
...

No exemplo acima, mensagens terá o conteúdo de Mensagem.all(), que é uma sequência contendo todas as mensagens existentes no datastore. Assim, esse trecho do template irá percorrer as mensagens e imprimir os valores dos campos nome_usuario, email, url e comentario nos locais correspondentes do HTML. Tudo isso é feito no lado servidor, ou seja, o navegador irá receber o HTML já pronto com os valores nos locais adequados. Um exemplo de trecho de HTML gerado no servidor poderia ser:

...
    <hr>
    <h2>Comentário de <em>Pedro Pedreira</em></h2>
    <p><strong>Email:</strong> pedro@pedreira.com</p>
    <p><strong>URL:</strong> https://pythonhelp.wordpress.com/</p>
    <p><strong>Comentário:</strong> Um comentário qualquer. Valeu!</p>
...

Onde Pedro Pedreira é o valor de {{ m.nome_usuario }}, e assim por diante, para um registro qualquer.

Caso não tenha visto o post anterior, veja o código do projeto para então aplicar as mudanças para inclusão do template, ou então siga a leitura neste post para ver o projeto completo.

O projeto completo

Nosso projeto está localizado no diretório gaetest/ e possui a seguinte estrutura:

gaetest/
    app.yaml
    handlers.py
    modelo.html

Agora vamos ver o conteúdo dos arquivos:

app.yaml:

application: gaetest
version: 1
runtime: python
api_version: 1

handlers:
- url: /.*
  script: handlers.py

handlers.py:

import os
import webapp2
from google.appengine.ext import db
from google.appengine.ext.webapp import template
from google.appengine.ext.webapp.util import run_wsgi_app


class PostHandler(webapp2.RequestHandler):

    def get(self):
        path = os.path.join(os.path.dirname(__file__), 'modelo.html')
        valores = {'mensagens': Mensagem.all()}
        self.response.out.write(template.render(path, valores))

    def post(self):
        msg = Mensagem(
            nome_usuario=self.request.get('nome_usuario'),
            url=self.request.get('url'),
            email=self.request.get('email'),
            comentario=self.request.get('comentario')
        )
        msg.put()
        self.redirect('/')


class Mensagem(db.Model):
    nome_usuario = db.StringProperty(required=True)
    url = db.LinkProperty()
    email = db.EmailProperty()
    comentario = db.TextProperty(required=True)


mapeamento = [
    ('/', PostHandler),
]
app = webapp2.WSGIApplication(mapeamento)
run_wsgi_app(app)

modelo.html:

<html>
    <head><title></title></head>
    <body>
        <h1>Comentários</h1>
        {% for m in mensagens %}
            <hr>
            <h2>Comentário de <em>{{ m.nome_usuario }}</em></h2>
            <p><strong>Email:</strong> {{ m.email }}</p>
            <p><strong>URL:</strong> {{ m.url }}</p>
            <p><strong>Comentário:</strong> {{ m.comentario }}</p>
        {% endfor %}
        <hr>
        <form method="POST" action="/">
            Nome: <p><input type="text" name="nome_usuario"></p>
            URL: <p><input type="text" name="url"></p>
            Email: <p><input type="text" name="email"></p>
            Comentario:<p><textarea name="comentario" cols="40" rows="10"></textarea></p>
            <button type="submit">Enviar</button>
        </form>
    </body>
</html>

Executando o projeto

Salve o conteúdo acima nos arquivos correspondentes dentro da pasta gaetest e então rode o servidor local de testes. Para isso, vá até a pasta onde você descompactou o pacote na instalação do GAE e rode o seguinte comando:

./dev_appserver.py /caminho/para/o/projeto/gaetest/

Onde /caminho/para/o/projeto/gaetest/ é o caminho para o diretório onde está o projeto que queremos executar. Após isso, acesse o projeto no seu navegador através da URL localhost:8080.

Boas práticas

Boas práticas são muito importantes na hora de desenvolver um projeto de software. À medida que o projeto vai crescendo, a urgência por uma boa organização dos arquivos também cresce. É comum termos projetos com uma estrutura parecida com essa:

project/
    app.yaml
    handlers.py
    models.py
    templates/
        base.html
        form.html
        list.html

Veja que temos agora dois arquivos .py:

  • handlers.py: as classes que irão manipular nossas requisições;
  • models.py: as classes que representam os modelos de dados em nosso projeto (como a classe Mensagem no nosso exemplo);

Além disso, temos uma pasta específica para conter os templates e dentro dela ficam todos os templates que precisarmos.

Você é livre para definir a estrutura que quiser, mas é bom seguir um padrão que seja minimamente organizado.

Outros mecanismos de template

O mecanismo de template que vimos neste post é o que já vem incluso com o pacote do GAE, que por sua vez é o mecanismo do Django. Além desse, existem vários outros mecanismos que podem ser usados, como o jinja2, por exemplo.

Leia mais