Um blog com Google App Engine

Sumário

Este é um post-tutorial, onde vou descrever o desenvolvimento de um sistema de blogging bem simples usando os recursos nativos do Google App Engine (GAE, para os amigos). Caso não tenha familiaridade com o GAE, sugiro a leitura dos textos anteriores que publiquei aqui no Python Help sobre o assunto:

Com as leituras acima, você terá a base necessária para entender o tutorial aqui apresentado.

O que vamos desenvolver?

O objetivo é implementar um sistema de blogging bem simples, com as seguintes funcionalidades:

  • Tela principal com listagem de todos os posts
  • Tela para adicionar um novo post
  • Tela para visualizar um post
  • Restrição de acesso para a tela de criar post

A tela para adicionar posts deve redirecionar para o post recém criado.

Mãos à obra

A primeira coisa que temos que fazer é criar o projeto, ou seja, definir a estrutura de arquivos que nosso projeto vai ter e configurá-lo no arquivo app.yaml, como já vimos nos posts anteriores. Veja abaixo a estrutura de diretórios que vamos utilizar em nosso projeto:

├── app.yaml
├── main.py
├── models.py
├── static
│   └── main.css
└── templates
    ├── base.html
    ├── detail.html
    ├── form.html
    └── front.html

Crie o arquivo de configurações app.yaml com as seguintes opções:

application: pythonhelp-blog
version: 1
runtime: python27
api_version: 1
threadsafe: true

handlers:
- url: /static
  static_dir: static

- url: /.*
  script: main.app

Como você deve lembrar, a diretiva handlers no arquivo de configuração app.yaml configura como nossa aplicação vai tratar as requisições HTTP vindas do cliente. Aqui configuramos nossa aplicação para servir arquivos estáticos a partir do diretório static e para publicar a aplicação criada na variável app dentro do módulo main.

Ah, não se esqueça de alterar o seu application id (valor de application no app.yaml), pois esse identificador deve ser único no GAE. Ou seja, se eu colocar esse sisteminha no ar com o app id "pythonhelp-blog", você não poderá colocá-lo no ar com o mesmo identificador.

O modelo de dados

Agora, vamos modelar o nosso problema. Como iremos representar as informações no nosso banco de dados? De acordo com os requisitos, iremos precisar de um model para representar um post, que tenha vinculado a si alguns atributos, incluindo o autor do texto (que deve ser um usuário cadastrado).

Graças ao GAE, não precisaremos criar uma entidade User, pois ele já fornece um serviço de gerenciamento e autenticação de usuários (usando contas de usuário na google mesmo). Dessa forma, teremos apenas um model de uma entidade que iremos chamar de BlogPost, contendo um atributo do tipo UserProperty, fornecido pelo GAE. Crie um model BlogPost de acordo com o código a seguir e salve no arquivo models.py:

from google.appengine.ext import db

class BlogPost(db.Model):
    subject = db.StringProperty(required=True)
    author = db.UserProperty(required=True,
                             auto_current_user=True)
    content = db.TextProperty(required=True)
    created = db.DateTimeProperty(auto_now_add=True)
    last_updated = db.DateTimeProperty(auto_now=True)

Observe  o campo author. Passamos um atributo nomeado auto_current_user com o valor True. Isso faz com que o campo author seja setado com o usuário atualmente logado quando um objeto do tipo BlogPost for gravado no datastore. Barbadinha, né?

Implementação das ações

Agora vamos definir as URLs que serão utilizadas e para quais classes elas serão mapeadas em nosso projeto.

Defina o mapeamento das URLs para as classes manipuladoras no arquivo main.py:

    app = webapp2.WSGIApplication(
        [("/blog", BlogMainHandler),
         ("/blog/newpost", BlogNewPostHandler),
         ('/blog/post/(\d+)', BlogPostViewHandler),
         ],
        debug=True)

Dessa forma, nosso esquema de URLs será assim:

  • a página principal (que lista todos os posts), será acessível pela URL "/blog";
  • o formulário para criação de um novo post será acessível via "/blog/newpost";
  • um post em específico será acessível pela URL "/blog/post/id_do_post". Por exemplo, "/blog/post/19" irá abrir uma página de visualização do post cujo id seja 19.

Agora vamos à implementação das classes listadas acima. A primeira delas será a mais simples: BlogMainHandler, que será a página inicial do blog, listando todos os posts.

template_dir = os.path.join(os.path.dirname(__file__),
                            'templates')


class BlogMainHandler(webapp2.RequestHandler):
    def get(self):
        self.response.out.write(
            template.render(template_dir +
                            '/front.html',
                            {'posts': BlogPost.all()}))

A segunda classe que iremos implementar é BlogPostViewHandler, responsável pela visualização de um post em específico.

class BlogPostViewHandler(webapp2.RequestHandler):
  def get(self, post_id):
    try:
      post = BlogPost.get_by_id(int(post_id))
      self.response.out.write(
          template.render(template_dir + '/detail.html',
                          {'post': post}))
    except:
      self.response.out.write("Erro: post não encontrado!")

Veja que o método get recebe um parâmetro post_id. Esse parâmetro é passado pelo URL dispatcher, pegando o pedaço da URL que está entre parênteses na definição ('/blog/post/(\d+)') e repassando como valor ao método (\d+ significa um ou mais dígitos e os parênteses ao redor fazem com que esse valor seja passado ao método get). Por exemplo, um acesso a /blog/post/19 irá fornecer ao parâmetro post_id o valor 19.

Você deve ser capaz de entender os códigos listados acima. Caso tenha dificuldades, reveja os posts em que explico como criar um projeto na GAE, como utilizar o Datastore e o mecanismo de templates.

Depois temos a classe responsável pela criação de novos posts: BlogNewPostHandler. Veja o código abaixo:


class BlogNewPostHandler(webapp2.RequestHandler):

  def get(self):
    user = users.get_current_user()
    if user is not None:
      self.response.out.write(
          template.render(template_dir + '/form.html',
                          {'title': 'Escrever post'}))
    else:
      self.redirect(
          users.create_login_url(self.request.uri))

  def post(self):
    author = users.get_current_user()
    if author is not None:
      subject = self.request.get('subject')
      content = self.request.get('content')
      if subject and content:
        b = BlogPost(subject=subject, content=content)
        b.put()
        self.redirect("/blog/post/" + str(b.key().id()))
      else:
        error = 'Ambos os campos são obrigatórios'
        self.response.out.write(
            template.render(template_dir + '/form.html',
                            {'title': 'Escrever post', 
                             'error': error,
                             'subject': subject,
                             'content': content}))
    else:
      self.redirect(
          users.create_login_url(self.request.uri))

Essa classe tem alguns detalhes que valem a pena ser observados. Em primeiro lugar, ela possui dois métodos: get e post. Como já vimos anteriormente, o método get será chamado pelo ambiente subjacente quando houver uma requisição do tipo GET à URL mapeada para a classe BlogNewPostHandler e o método post será chamado quando houver uma requisição do tipo POST (normalmente usada para fazer escrita de dados no servidor). Dessa forma, a classe possui duas funcionalidades:

  1. get: ao acessar a url /blog/newpost, o usuário irá receber como resposta um formulário para criação de um novo post.
  2. post: quando o formulário for submetido, é feito uma requisição POST, passando os dados do formulário para criar um objeto BlogPost no servidor.

Repare como é simples a autenticação do usuário no código usando o serviço provido pelo GAE. Veja que a primeira linha de cada método chama users.get_current_user para obter o usuário logado atualmente. Caso não haja usuário logado (se o objeto retornado for None), então redirecionamos o usuário para uma URL própria para fazer o login. Quando estiver rodando no ambiente externo, é apresentada a tela de autenticação dos serviços da Google mesmo. No ambiente de testes, é apresentada uma tela de login como a mostrada na figura abaixo.

Tela de login no ambiente local (powered by GAE)

Tela de login no ambiente local (powered by GAE)

Os templates

Em um projeto GAE, os templates contém o código HTML que será enviado para o usuário nas requisições. Como visto no post anterior sobre o assunto, é possível a introdução de campos dinâmicos para serem preenchidos de acordo com os parâmetros passados na renderização dos templates.

Como todas as páginas terão um cabeçalho padrão, definimos um template base que os demais irão estender. Veja abaixo o código de cada um dos templates:

templates/base.html:

<!DOCTYPE html>
<html>
  <head>
    <link type="text/css" rel="stylesheet" href="/static/main.css" />
    <title>{{ title }}</title>
  </head>
  <body>
    <div class="main-title"><a href="/blog">stummjr Blog</a></div>
    {% block content %}
    {% endblock %}
  </body>
</html>

Dessa forma, basta aos subtemplates detail.html, form.html e front.html a descrição do bloco content.

templates/detail.html (usado para visualização de um post específico):

{% extends 'base.html' %}

{% block content %}
<div class="post">
  <div class="post-heading">
    <div class="post-title">
      {{ post.subject }}
    </div>
    <div class="post-date">
      {{ post.created}}
    </div>
  </div>
  <pre class="post-content">
    {{ post.content }}
  </pre>
</div>
{% endblock %}

templates/form.html (usado para apresentar a interface de criação de um novo post):

{% extends 'base.html' %}
{% block content %}
<form method="post">
  <label for="subject">Assunto:
    <input type="text" name="subject" value="{{ subject }}">
  </label>
  <label for="content">Conteúdo:
    <textarea name="content" rows="10" cols="50">
      {{ content }}
    </textarea>
  </label>
  <button type="submit">Salvar</button>
  <div class="error">
    {{ error }}
  </div>
</form>
{% endblock %}

templates/front.html (lista todos os posts existentes no banco de dados do blog):

{% extends 'base.html' %}

{% block content %}
{% for post in posts %}
  <div class="post">
    <div class="post-heading">
      <div class="post-title">
        <a href="/blog/post/{{ post.key.id }}">{{ post.subject }}</a>
      </div>
      <div class="post-date">
        {{ post.created }}
      </div>
    </div>
    <pre class="post-content">
      {{ post.content }}
    </pre>
  </div>
{% endfor %}
{% endblock %}

Como podemos ver, o código base.html referencia o arquivo de estilos main.css, que não será listado aqui devido ao seu tamanho. Todos os arquivos do projeto podem ser baixados no link que consta no final do post.

Testando o blog

Tendo as classes e os templates definidos, agora basta testar nosso projeto. Para isso, vamos usar o ambiente de testes fornecido pelo GAE. Veja como instalar e usar esse ambiente no primeiro post sobre o GAE feito no blog.

Próximos passos

Não pare por aqui. Leia a documentação oficial do GAE (em pt-br, inclusive) e incremente o projeto, adicionando comentários, categorias, etc. Bom trabalho!

Download dos arquivos do projeto

Obrigado ao Elias Dorneles, pela baita revisão que fez no texto!

Anúncios

Um comentário sobre “Um blog com Google App Engine

  1. Pingback: #1 Python Help | Blogs que me inspiram | Aprendendo Python

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s