diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
new file mode 100644
index 000000000..e5d50d384
--- /dev/null
+++ b/.github/workflows/build-test.yml
@@ -0,0 +1,32 @@
+name: Build Test
+
+on:
+ push:
+ branches:
+ - '*'
+ - '!pelican'
+
+permissions:
+ contents: read
+
+jobs:
+ build:
+ name: Build
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout do repositório
+ uses: actions/checkout@v3
+ with:
+ submodules: recursive
+
+ - name: Configura python
+ uses: actions/setup-python@v4
+ with:
+ python-version: "2.7"
+ cache: pip
+
+ - name: Instala dependências
+ run: pip install -r requirements.txt
+
+ - name: Build do site
+ run: make publish
diff --git a/.github/workflows/github-pages.yml b/.github/workflows/github-pages.yml
new file mode 100644
index 000000000..a7b555e78
--- /dev/null
+++ b/.github/workflows/github-pages.yml
@@ -0,0 +1,55 @@
+name: GitHub Pages
+
+on:
+ push:
+ branches:
+ - pelican
+
+permissions:
+ contents: read
+
+concurrency:
+ group: pages
+ cancel-in-progress: true
+
+jobs:
+ build:
+ name: Build
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout do repositório
+ uses: actions/checkout@v3
+ with:
+ submodules: recursive
+
+ - name: Configura python
+ uses: actions/setup-python@v4
+ with:
+ python-version: "2.7"
+ cache: pip
+
+ - name: Instala dependências
+ run: pip install -r requirements.txt
+
+ - name: Build do site
+ run: make publish
+
+ - name: Upload do site
+ uses: actions/upload-pages-artifact@v1
+ with:
+ path: output/
+
+ deploy:
+ name: Deploy
+ needs: build
+ permissions:
+ pages: write
+ id-token: write
+ runs-on: ubuntu-latest
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ steps:
+ - name: Deploy no GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v1
diff --git a/.gitignore b/.gitignore
index 029c9d91b..14885e0ab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,4 +16,15 @@ Thumbs.db
cache/*
# Pycharm
-.idea
\ No newline at end of file
+.idea
+
+# Virtualenv
+venv/
+
+MacOS Files
+.DS_Store
+
+# alias
+src
+
+.env
diff --git a/.travis.yml b/.travis.yml
index 96bf086f4..87856a85d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -18,3 +18,4 @@ env:
before_install:
- git submodule update --remote --merge
after_success: bash deploy.sh
+
diff --git a/README.md b/README.md
index a1f5554de..74d1c4814 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
[pythonclub.com.br][0]
======================
-Duvidas sobre este projeto, deixe sua mensagem em [](https://gitter.im/pythonclub/pythonclub.github.io?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
+Duvidas sobre este projeto, deixe sua mensagem em [](https://gitter.im/pythonclub/pythonclub.github.io?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
Blog colaborativo sobre tecnologias que envolvam a linguagem Python
@@ -11,7 +11,7 @@ Como Contribuir
* Faça um fork desse repositório, clicando no botão [![Fork][14]][15], na parte superior direita da pagina do Github
* Clone seu fork:
- ``git clone --recursive https://github.com/SEU_USUARIO_DO_GITHUB/pythonclub.github.io.git``
+ ``git clone --depth 1 --recursive https://github.com/SEU_USUARIO_DO_GITHUB/pythonclub.github.io.git``
* Instale os requirements ``pip install -r requirements.txt`` - se você não tiver o pip instalado, instale-o: https://pip.pypa.io/en/latest/installing.html#install-pip
* Todas as publicações ficam na pasta ``content``, os textos podem ser escritos
@@ -80,16 +80,6 @@ Para finalizar o servidor use:
``./develop_server.sh stop``
-Futuras Publicações
--------------------
-
-Alguns dos contribuidores criaram o compromisso de publicar alguns artigos.
-
-Foi estabelecido um prazo maximo para a entrega dos artigos com o intuito de que o contribuidor realmente publique o artigo com o conteudo que ele mesmo definiu.
-
-Você pode ver a lista contendo os nomes dos artigos nesta planilha no [Google Drive][7].
-
-Quando tiver um assunto e uma data de entrega, adicione na planinha, ao finalizar o seu artigo, envie o pull request e atualize a planilha marcando que sua publicação já foi entregue.
[0]: http://pythonclub.com.br/
[1]: https://pages.github.com/
diff --git a/content/a-armadilha-dos-argumentos-com-valores-padrao.md b/content/a-armadilha-dos-argumentos-com-valores-padrao.md
new file mode 100644
index 000000000..7408ef65b
--- /dev/null
+++ b/content/a-armadilha-dos-argumentos-com-valores-padrao.md
@@ -0,0 +1,197 @@
+title: A armadilha dos argumentos com valores padrão
+Slug: a-armadilha-dos-argumentos-com-valores-padrao
+Date: 2015-06-07 11:00
+Tags: python,mutable,function,class,anti-pattern
+Author: Diego Garcia
+Email: drgarcia1986@gmail.com
+Github: drgarcia1986
+Site: http://www.codeforcloud.info
+Twitter: drgarcia1986
+Linkedin: drgarcia1986
+Category: anti-patterns
+
+
+
+
+
+
+Algo muito comum em várias linguagens de programação é a possibilidade de definir _valores default_ (valores padrão) para argumentos de funções e métodos, tornando a utilização desses opcional.
+Isso é ótimo, principalmente para manter retrocompatibilidade, porém, o python possui uma pequena armadilha que caso passe despercebida, pode causar sérios problemas, muitas vezes difíceis de serem detectados.
+Essa armadilha ocorre quando usamos valores de tipos `mutáveis` como valor default de argumentos.
+
+
+
+### O que são tipos mutáveis e imutáveis?
+Segundo a [documentação oficial do python](https://docs.python.org/3.4/reference/datamodel.html), o valor de alguns objetos pode mudar, esses objetos que podem ter seu valor alterado após serem criados são chamados de mutáveis, enquanto que os objetos que não podem ter seus valores alterados após serem criados são chamados de imutáveis (simples assim).
+
+* **Tipos mutáveis**:
+
+Listas, Dicionários e tipos definidos pelo usuário.
+
+* **Tipos imutáveis**:
+
+Numeros, Strings e Tuplas.
+
+> Apesar de serem imutáveis, a utilização de um valor mutável (uma lista por exemplo) dentro de uma tupla, pode causar o efeito _[tuplas mutáveis](http://pythonclub.com.br/tuplas-mutantes-em-python.html)_, onde visualmente o valor da tupla é alterado, mas por trás dos panos o valor da tupla não muda, o que muda é o valor do objeto pelo qual a tupla está se referenciando.
+
+### A armadilha
+Como disse no começo desse blogpost, é muito comum a utilização de valores default em agurmentos de funções e métodos, por essa razão, nos sentimos seguros em fazer algo desse tipo:
+
+```python
+def my_function(my_list=[]):
+ my_list.append(1)
+ print(my_list)
+```
+
+Porém, levando esse exemplo em consideração, o que irá acontecer se invocarmos essa função 3 vezes?
+
+```python
+>>> my_function()
+[1]
+>>> my_function()
+[1, 1]
+>>> my_function()
+[1, 1, 1]
+```
+Sim, o valor do argumento `my_list` mudou em cada vez que executamos a função sem passar algum valor para ele.
+
+### Por que isso acontece?
+Isso acontece porque o python processa os valores default de cada argumentos de uma função (ou método) quando essa for definida, após esse processamento o valor é atribuido ao objeto da função.
+Ou seja, por questões de optimização, seguindo nosso exemplo, o python não cria uma lista vazia para o argumento `my_list` a cada vez que a função `my_function` for invocada, ele reaproveita uma lista que foi criada no momento em que essa função foi importada.
+
+```python
+>>> my_function.func_defaults
+([],)
+>>> id(my_function.func_defaults[0])
+140634243738080
+>>> my_function()
+[1]
+>>> my_function.func_defaults
+([1],)
+>>> id(my_function.func_defaults[0])
+140634243738080
+>>> my_function()
+[1, 1]
+>>> my_function.func_defaults
+([1, 1],)
+>>> id(my_function.func_defaults[0])
+140634243738080
+```
+> Note que a identificação do argumento (no caso `my_list`) não muda, mesmo executando a função várias vezes.
+
+Outro exemplo seria utilizar o resultado de funções como valores default de argumentos, por exemplo, uma função com um argumento que recebe como default o valor de `datetime.now()`.
+
+```python
+def what_time_is_it(dt=datetime.now()):
+ print(dt.strftime('%d/%m/%Y %H:%M:%S'))
+```
+O valor do argumento `dt` sempre será o _datetime_ do momento em que o python carregou a função e não o _datetime_ de quando a função foi invocada.
+
+```python
+>>> what_time_is_it()
+07/06/2015 08:43:55
+>>> time.sleep(60)
+>>> what_time_is_it()
+07/06/2015 08:43:55
+```
+
+### Isso também acontece com classes?
+Sim e de uma forma ainda mais perigosa.
+
+```python
+class ListNumbers():
+ def __init__(self, numbers=[]):
+ self.numbers = numbers
+
+ def add_number(self, number):
+ self.numbers.append(number)
+
+ def show_numbers(self):
+ print(numbers)
+```
+Assim como no caso das funções, no exemplo acima o argumento `numbers` é definido no momento em que o python importa a classe, ou seja, a cada nova instância da classe `ListNumbers`, será aproveitada a mesma lista no argumento `numbers`.
+
+```python
+>>> list1 = ListNumbers()
+>>> list2 = ListNumbers()
+>>> list1.show_numbers()
+[]
+>>> list2.show_numbers()
+[]
+>>> list2.add_number(1)
+>>> list1.show_numbers()
+[1]
+>>> list2.show_numbers()
+[1]
+>>> list1.numbers is list2.numbers
+True
+```
+
+### Por que isso não acontece com Strings?
+Porque strings são `imutáveis`, o que significa que a cada alteração de valor em uma variavel que armazena uma strings, o python cria uma nova instância para essa variável.
+
+```python
+>>> a = 'foo'
+>>> id(a)
+140398402003832
+>>> a = 'bar'
+>>> id(a)
+140398402003872 # o penúltimo número muda :)
+```
+
+Em argumentos com valores default, não é diferente.
+
+```
+def my_function(my_str='abc'):
+ my_str += 'd'
+ print(my_str)
+```
+No exemplo acima, sempre que for executado o `inplace add` (`+=`) será criada outra váriavel para `my_str` sem alterar o valor default do argumento.
+
+```python
+>>> my_function()
+abcd
+>>> my_function.func_defaults
+('abc',)
+>>> my_function()
+abcd
+>>> my_function.func_defaults
+('abc',)
+```
+
+### Como se proteger?
+A maneira mais simples de evitar esse tipo de surpresa é utilizar um [valor sentinela](http://en.wikipedia.org/wiki/Sentinel_value) como por exemplo `None`, nos argumentos opcionais que esperam tipos mutáveis:
+
+```python
+def my_function(my_list=None):
+ if my_list is None:
+ my_list = []
+ my_list.append(1)
+ print(my_list)
+```
+
+Ou, para deixar o código ainda mais elegante, podemos simplificar a condicional com um simples `or`:
+
+```python
+def my_function(my_list=None):
+ my_list = my_list or []
+ my_list.append(1)
+ print(my_list)
+```
+> Obrigado [Bruno Rocha](http://pythonclub.com.br/author/bruno-cezar-rocha.html) pela sugestão.
+
+Pronto, sem surpresas e sem armadilhas :).
+
+```python
+>>> my_function()
+[1]
+>>> my_function()
+[1]
+>>> my_function()
+[1]
+```
+
+### Referências
+
+* [Fluent Python (Mutable types as parameter defaults: bad idea)](http://shop.oreilly.com/product/0636920032519.do)
+* [Python Anti-Patterns (Using a mutable default value as an argument)](http://docs.quantifiedcode.com/python-anti-patterns/correctness/mutable_default_value_as_argument.html)
diff --git a/content/abrangencia-de-listas-e-dicionarios.md b/content/abrangencia-de-listas-e-dicionarios.md
new file mode 100644
index 000000000..d025ac785
--- /dev/null
+++ b/content/abrangencia-de-listas-e-dicionarios.md
@@ -0,0 +1,131 @@
+Title: Abrangência de Listas e Dicionários
+Slug: abrangencia-de-listas-e-dicionarios-com-python
+Date: 2017-01-16 10:37:39
+Category: Python
+Tags: python,tutorial,list comprehensions
+Author: Michell Stuttgart
+Email: michellstut@gmail.com
+Github: mstuttgart
+Linkedin: mstuttgart
+Site: https://mstuttgart.github.io
+
+A utilização de listas em Python é algo trivial. A facilidade provida pela linguagem aliada a simplicidade da estrutura de dados *list* a torna, ao lado dos dicionários *dict*, uma das estrutura de dados mais utilizadas em Python. Aqui neste tutorial irei compartilhar algo que aprendi trabalhando com listas e dicionário em Python, mais especificamente no que diz respeito a *abrangência* de listas (e dicionários).
+
+## Abrangência de listas
+
+A abrangência de listas, ou do inglês *list comprehensions*, é um termo utilizado para descrever uma sintaxe compacta que o Python nos oferece para criamos uma lista baseada em outra lista. Pareceu confuso? Ok, vamos aos exemplos!
+
+### Exemplo 1
+Vamos supor que temos a seguinte lista de valores:
+
+```python
+valores = [1, 2, 3, 4, 5]
+```
+Queremos gerar uma outra lista contendo o dobro de cada um desses números, ou seja,
+
+```python
+[2, 4, 6, 8, 10]
+```
+Inicialmente, podemos montar o seguinte código como solução:
+
+```python
+# Recebe o nosso resultado
+valores_dobro = []
+
+for val in valores:
+ valores_dobro.append(val * 2)
+
+print(valores_dobro)
+
+>>>
+[2, 4, 6, 8, 10]
+
+```
+
+A solução acima é uma solução simples e resolve nosso problema, entretanto para algo tão simples precisamos de 4 linhas de código. Este exemplo é uma situação onde a *abrangência de lista* pode ser útil. Podemos compactar a criação da lista `valores_dobro` da seguinte maneira:
+
+```python
+valores_dobro = [valor*2 for valor in valores]
+```
+Bacana não? O exemplo seguinte podemos incrementar mais o exemplo acima.
+
+### Exemplo 2
+
+Vamos supor que desejamos criar uma lista onde apenas os valores pares (resto da divisão por 2 é zero) serão multiplicados por 2. Abaixo temos a nossa lista de valores:
+
+```python
+valores = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
+```
+
+Assim como no exemplo anterior, podemos resolver utilizando um algoritmo básico.
+
+```python
+# Lista que recebera o nosso resultado
+valores_dobro = []
+
+for valor in valores:
+ if valor % 2 == 0:
+ valores_dobro.append(valor * 2)
+
+print(valores_dobro)
+
+>>>
+[4, 8, 12, 16, 20]
+
+```
+Podemos também resolver o mesmo problema utilizando as funções nativas *map* e *filter*:
+
+```python
+valores_dobro = map(lambda valor: valor * 2, filter(lambda valor: valor % 2 == 0, valores))
+```
+Muito mais complicada não é? Apesar de resolver nosso problema, expressões como a acima são difíceis de ler e até mesmo de escrever. Em casos como esse, podemos novamente compactar nosso algoritmo utilizando a *abrangência de lista*.
+
+```python
+valores_dobro = [valor * 2 for valor in valores if valor % 2 == 0]
+```
+Muito mais simples, não? Vamos para o próximo exemplo.
+
+### Exemplo 3
+
+De maneira semelhante a lista, nós também podemos aplicar a abrangência em lista e dicionários. Segue um exemplo onde temos o seguinte dicionário:
+
+```python
+ dicionario = {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6}
+```
+
+Vamos criar um segundo dicionário contendo apenas as chaves que são consoantes, ou seja, `b`, `c`, `d` e `f`, sendo que o valor para cada uma dessas chaves deve ser o dobro do valor armazenado na respectiva chave do dicionário original. Complicado? Em outras palavras, o novo dicionário deve ficar assim:
+
+```python
+ novo_dicionario = {'b': 4, 'c': 6, 'd': 8, 'f': 12}
+```
+
+Utilizando um algoritmo genérico, podemos resolver o problema da seguinte maneira:
+
+```python
+novo_dicionario = {}
+
+for chave, valor in dicionario:
+ if chave in ['b', 'c', 'd', 'f']:
+ novo_dicionario[chave] = 2 * valor
+
+print(novo_dicionario)
+
+>>
+{'b': 4, 'c': 6, 'd': 8, 'f': 12}
+
+```
+Aplicando agora a abrangência, conseguimos compactar o código acima de maneira interessante:
+
+```python
+novo_dicionario = {chave: 2 * valor for chave, valor in dicionario.items() if chave in ['b', 'c', 'd', 'f']}
+```
+
+## Conclusão
+
+Chegamos ao final de mais um tutorial! Sempre temos de ter em mente que tão importante quanto escrever um código que funciona, é mantê-lo (seja por você ou por outro programador). Neste ponto, a abrangência de lista (e outras estruturas de dados) nos ajudam a escrever um código claro e fácil de dar manutenção.
+
+Até o próximo tutorial pessoal!
+
+## Referências
+
+* [Python eficaz: 59 maneiras de programar melhor em Python; Slatkin, Brett; Novatec Editora, 2016.](https://novatec.com.br/livros/python-eficaz/)
diff --git a/content/algoritmos_ordenacao_usando_python.rst b/content/algoritmos_ordenacao_usando_python.rst
new file mode 100644
index 000000000..71c714302
--- /dev/null
+++ b/content/algoritmos_ordenacao_usando_python.rst
@@ -0,0 +1,134 @@
+Algoritmos de Ordenação
+########################
+
+:date: 2018-11-29 13:10
+:tags: python, algoritmos
+:category: Python
+:slug: algoritmos-ordenacao
+:author: Lucas Magnum
+:email: lucasmagnumlopes@gmail.com
+:github: lucasmagnum
+:linkedin: lucasmagnum
+
+Fala pessoal, tudo bom?
+
+Nos vídeos abaixo, vamos aprender como implementar alguns dos algoritmos de ordenação usando Python.
+
+
+Bubble Sort
+===========
+
+Como o algoritmo funciona: Como implementar o algoritmo usando Python: `https://www.youtube.com/watch?v=Doy64STkwlI `_.
+
+
+.. youtube:: Doy64STkwlI
+
+Como implementar o algoritmo usando Python: `https://www.youtube.com/watch?v=B0DFF0fE4rk `_.
+
+.. youtube:: B0DFF0fE4rk
+
+Código do algoritmo
+
+.. code-block:: python
+
+ def sort(array):
+
+ for final in range(len(array), 0, -1):
+ exchanging = False
+
+ for current in range(0, final - 1):
+ if array[current] > array[current + 1]:
+ array[current + 1], array[current] = array[current], array[current + 1]
+ exchanging = True
+
+ if not exchanging:
+ break
+
+
+Selection Sort
+==============
+
+Como o algoritmo funciona: Como implementar o algoritmo usando Python: `https://www.youtube.com/watch?v=vHxtP9BC-AA `_.
+
+.. youtube:: vHxtP9BC-AA
+
+Como implementar o algoritmo usando Python: `https://www.youtube.com/watch?v=0ORfCwwhF_I `_.
+
+.. youtube:: 0ORfCwwhF_I
+
+Código do algoritmo
+
+.. code-block:: python
+
+ def sort(array):
+ for index in range(0, len(array)):
+ min_index = index
+
+ for right in range(index + 1, len(array)):
+ if array[right] < array[min_index]:
+ min_index = right
+
+ array[index], array[min_index] = array[min_index], array[index]
+
+
+Insertion Sort
+==============
+
+Como o algoritmo funciona: Como implementar o algoritmo usando Python: `https://www.youtube.com/watch?v=O_E-Lj5HuRU `_.
+
+.. youtube:: O_E-Lj5HuRU
+
+Como implementar o algoritmo usando Python: `https://www.youtube.com/watch?v=Sy_Z1pqMgko `_.
+
+.. youtube:: Sy_Z1pqMgko
+
+Código do algoritmo
+
+.. code-block:: python
+
+ def sort(array):
+ for p in range(0, len(array)):
+ current_element = array[p]
+
+ while p > 0 and array[p - 1] > current_element:
+ array[p] = array[p - 1]
+ p -= 1
+
+ array[p] = current_element
+
+
+Merge Sort
+==============
+
+Como o algoritmo funciona: Como implementar o algoritmo usando Python: `https://www.youtube.com/watch?v=Lnww0ibU0XM `_.
+
+.. youtube:: Lnww0ibU0XM
+
+
+Como implementar o algoritmo usando Python - Parte I: `https://www.youtube.com/watch?v=cXJHETlYyVk `_.
+
+.. youtube:: cXJHETlYyVk
+
+Código do algoritmo
+
+.. code-block:: python
+
+ def sort(array):
+ sort_half(array, 0, len(array) - 1)
+
+
+ def sort_half(array, start, end):
+ if start >= end:
+ return
+
+ middle = (start + end) // 2
+
+ sort_half(array, start, middle)
+ sort_half(array, middle + 1, end)
+
+ merge(array, start, end)
+
+
+ def merge(array, start, end):
+ array[start: end + 1] = sorted(array[start: end + 1])
+
diff --git a/content/bot-telegram-e-web-scraping-parte1.md b/content/bot-telegram-e-web-scraping-parte1.md
new file mode 100644
index 000000000..a4bb33885
--- /dev/null
+++ b/content/bot-telegram-e-web-scraping-parte1.md
@@ -0,0 +1,114 @@
+Title: Bot telegram mais web scraping - parte 1
+Slug: bot-telegram-mais-web-scraping-parte-1
+Date: 2016-10-23 20:30
+Tags: python,blog,tutorial,aulas
+Author: Pedro Souza.
+About_author: Just another Programmer and Security Researcher, just a noob.,
+Email: souza.vipedro@gmail.com
+Github: Pedro-Souza
+Facebook: https://www.facebook.com/DeveloperPS
+Category: Python, Bot, Telegram, Scraping
+
+
+Irei separa o artigo em 2 partes para não ficar extenso. Nessa primeira
+parte irei falar um pouco como criar um bot no telegram e como
+programa-lo para nos responder.
+
+1 - Parte 1 - [**Bot simples.**]({filename}bot-telegram-e-web-scraping-parte1.md) (você está aqui)
+
+2 - Parte 2 - **Bot e Web Scraping**
+
+Primeiro de tudo precisamos cria o bot, para isso usamos o próprio bot
+do telegram que faz isso para gente. Para isso bastar iniciar uma
+conversa com o [@BotFather](https://web.telegram.org/#/im?p=@BotFather), ele irá nós da algumas
+opções:
+
+```
+/newbot - create a new bot
+/token - generate authorization token
+/revoke - revoke bot access token
+/setname - change a bot's name
+/setdescription - change bot description
+/setabouttext - change bot about info
+/setuserpic - change bot profile photo
+/setinline - change inline settings
+/setinlinegeo - toggle inline location requests
+/setinlinefeedback - change inline feedback settings
+/setcommands - change bot commands list
+/setjoingroups - can your bot be added to groups?
+/setprivacy - what messages does your bot see in groups?
+/deletebot - delete a bot
+/newgame - create a new game
+/listgames - get a list of your games
+/editgame - edit a game
+/deletegame - delete an existing game
+/cancel - cancel the current operation
+
+```
+
+As que nós interessa por enquanto são:
+
+```
+/newbot - Cria um novo bot.
+/setdescription - Adiciona uma descrição ao nosso bot.
+/setuserpic - Adiciona uma imagem ao nosso bot.
+```
+
+Feito isso agora temos um token, que iremos usar para dar funções e vida
+ao bot. Para isso iremos usar a lib telegram-bot, ela irá facilitar a
+nosso vida, assim não iremos precisar mexer diretamente com a API do
+telegram.
+
+### Instalando telegram-bot utilizando o pip
+
+```bash
+pip install python-telegram-bot
+```
+
+Agora com a biblioteca instalada iremos programar um mini bot para nós falar as horas.
+
+```python
+
+#!/usr/bin/env python3
+# -*- coding:utf-8 -*-
+
+from telegram.ext import Updater, CommandHandler
+from time import strftime
+
+up = Updater('Insira o token aqui.')
+
+
+def Horas(bot, update):
+
+ msg = "Olá {user_name} agora são: "
+ msg += strftime('%H:%M:%S')
+
+ bot.send_message(chat_id=update.message.chat_id,
+ text=msg.format(
+ user_name=update.message.from_user.first_name))
+
+
+up.dispatcher.add_handler(CommandHandler('horas', Horas))
+up.start_polling()
+
+```
+
+### Entendendo o código.
+
+1 - Importamos tudo que iremos utilizar.
+2 - Informamos o token do nosso bot.
+3 - Criamos uma função que pega a horas com strftime e responde no chat.
+4 - Criamos um comando para o nosso bot, no caso o /horas.
+5 - Startamos o bot.
+
+Feito isso quando mandar um /horas para o bot ele irá nos responder com: "Olá SeuNome agora são
+Horas."
+
+Caso você queira adicionar mais funções ao bot,
+[aqui](http://python-telegram-bot.readthedocs.io/en/latest/) está a documentação da biblioteca.
+
+Na próxima parte iremos escolher alguns site que fale sobre Python e fazer Scraping nele, assim
+sempre que ele tiver uma nova postagem nosso bot vai nós enviar uma mensagem informando.
+
+
+
diff --git a/content/class-based-views-no-django.md b/content/class-based-views-no-django.md
new file mode 100644
index 000000000..e93d5cade
--- /dev/null
+++ b/content/class-based-views-no-django.md
@@ -0,0 +1,906 @@
+Title: Class Based Views no Django
+Slug: class-based-views-django
+Date: 2015-10-26 14:50
+Tags: python,blog,tutorial,django,cbv
+Author: Raphael Passini Diniz
+Email: raphapassini@gmail.com
+Github: raphapassini
+Bitbucket: raphapassini
+Twitter: raphapassini
+Category: Django
+
+Esse tutorial tem como objetivo explicar o básico sobre Class Based Views
+no Django. Por motivos de agilidade vou usar ``CBV`` para me referir as
+``Class Based Views``.
+
+Segundo a documentação do Django sobre CBV:
+
+> CBV's permitem você estruturar as suas views e reutilizar código aproveitando
+ heranças e mixins
+
+O Django já vem CBV's genéricas que atendem as necessidades da maioria das aplicações.
+Essas views genéricas são flexiveis o suficiente para você poder adaptá-las as
+suas necessidades.
+
+Nesse tutorial eu vou falar brevemente sobre os 4 grupos de CBV's que existem
+no Django atualmente:
+
+- [Base views](#base_views)
+ - [View](#view)
+ - [TemplateView](#template_view)
+ - [RedirectView](#redirect_view)
+- [Display views](#display_views)
+ - [DetailView](#detail_view)
+ - [ListView](#list_view)
+- [Editing views](#editing_views)
+ - [Model based Views](#model_view)
+ - [CreateView, UpdateView, DeleteView](#create_update_delete_view)
+- [Date views](#date_views)
+ - [ArchiveView](#archive_view)
+ - [YearView](#year_view)
+ - [MonthView](#month_view)
+ - [WeekView](#week_view)
+ - [DayView](#day_view)
+ - [TodayView](#today_view)
+ - [DateDetailView](#date_detail_view)
+- [Conclusão](#conclusao)
+- [Referências](#ref)
+
+ Antes de começarmos a falar sobre as CBV's vamos ver como apontar uma rota
+ do Django para uma CBV:
+
+```python
+
+from django.conf.urls import url
+from django.views.generic import TemplateView
+from meuapp.views import AboutView
+
+urlpatterns = [
+ url(http://github.com/r'%5Eabout/',%20AboutView.as_view()),
+]
+```
+
+## Base Views
+
+As classes listadas abaixo contém muito da funcionalidade necessária para criar
+views no Django. Essas classes são a base sob a qual as outras CBV's são
+construídas.
+
+### View
+
+A classe genérica *master*. **Todas** as outras classes herdam dessa classe.
+O fluxo básico de execução dessa classe quando recebe uma requisição é:
+
+1. ``dispatch()``
+2. ``http_method_not_allowed()``
+3. ``options()``
+
+A função ``dispatch()`` verifica se a classe tem um método com o nome do verbo
+HTTP usado na requisição. Caso não haja, um ``http.HttpResponseNotAllowed`` é
+retornado.
+
+Essa classe sempre responde a requisições com o verbo ``OPTIONS`` retornando
+nesse caso uma lista com os verbos suportados. A não ser que o método
+``options()`` seja sobrescrito.
+
+Um exemplo de implementação:
+
+```python
+
+from django.http import HttpResponse
+from django.views.generic import View
+
+class MyView(View):
+
+ def get(self, request, *args, **kwargs):
+ return HttpResponse('Hello, World!')
+```
+
+No exemplo acima a classe só responde a requisições do tipo ``GET`` e
+``OPTIONS``, todas as outras requisições retornam
+``http.HttpResponseNotAllowed``.
+
+### Template View
+
+Renderiza um template. O fluxo básico de execução dessa classe quando recebe
+uma requisição é:
+
+1. ``dispatch()``
+2. ``http_method_not_allowed()``
+3. ``get_context_data()``
+
+Quando você precisa apenas renderizar uma página para o usuário essa com certeza
+é a melhor CBV para o caso. Você pode editar o contexto que o template recebe
+sobrescrevendo a função ``get_context_data()``
+
+Um exemplo de implementação:
+
+```python
+
+from django.views.generic.base import TemplateView
+
+from articles.models import Article
+
+class HomePageView(TemplateView):
+
+ template_name = "home.html"
+
+ def get_context_data(self, **kwargs):
+ context = super(HomePageView, self).get_context_data(**kwargs)
+ context['latest_articles'] = Article.objects.all()[:5]
+ return context
+```
+
+No exemplo acima o template *home.html* será renderizado e vai receber como
+contexto uma variável chamada ``lastest_articles``.
+
+Uma coisa interessante é que o contexto da ``TemplateView`` é populado pelo
+[ContextMixin](https://docs.djangoproject.com/en/1.8/ref/class-based-views/mixins-simple/#django.views.generic.base.ContextMixin)
+esse mixin pega automaticamente os argumentos da URL que serviu a View.
+
+Considere por exemplo:
+
+```python
+
+from django.conf.urls import patterns, url
+from .views import HelloView
+
+urlpatterns = patterns(
+ '',
+ url(http://github.com/r'^say_hello/(?P%3Cname%3E[\w_-]+)/$', HelloView.as_view(), name='say_hello'),
+)
+```
+
+No caso do exemplo acima o template renderizado pela ``HelloView`` teria
+em seu contexto a variável ``name``.
+
+### Redirect View
+
+Redireciona o usuário para a url informada.
+
+A URL a ser redirecionada pode conter parâmetros no estilo dicionário-de-strings.
+Os parâmetros capturados na URL do ``RedirectView`` serão repassados para a
+URL que o usuário está sendo redirecionado.
+
+O fluxo básico de execução dessa classe quando recebe
+uma requisição é:
+
+1. ``dispatch()``
+2. ``http_method_not_allowed()``
+3. ``get_redirect_url()``
+
+Considere a seguinte configuração de URL's para o exemplo de implementação:
+
+```python
+
+from django.conf.urls import url
+from django.views.generic.base import RedirectView
+
+from article.views import ArticleCounterRedirectView, ArticleDetail
+
+urlpatterns = [
+ url(http://github.com/r'^counter/(?P%3Cpk%3E[0-9]+)/$', ArticleCounterRedirectView.as_view(), name='article-counter'),
+ url(http://github.com/r'^details/(?P%3Cpk%3E[0-9]+)/$', ArticleDetail.as_view(), name='article-detail'),
+]
+```
+
+Exemplo de implementação:
+
+```python
+
+from django.shortcuts import get_object_or_404
+from django.views.generic.base import RedirectView
+
+from articles.models import Article
+
+class ArticleCounterRedirectView(RedirectView):
+
+ permanent = False
+ query_string = True
+ pattern_name = 'article-detail'
+
+ def get_redirect_url(http://github.com/self,%20*args,%20**kwargs):
+ article = get_object_or_404(Article, pk=kwargs['pk'])
+ article.update_counter()
+ return super(ArticleCounterRedirectView, self).get_redirect_url(http://github.com/*args,%20**kwargs)
+
+```
+
+Principais atributos:
+
+- ``url``: A URL destino no formato de String
+- ``pattern_name``: O nome do padrão de URL. Um ``reverse`` será aplicado
+ usando os mesmos
+ ``args`` e ``kwargs`` passados para a ``RedirectView``
+- ``permanent``: Se for ``True`` retorna o status code como 301, caso contrário,
+ retorna 302.
+- ``query_string``: Se for ``True`` a query_string será enviada para a URL de
+ destino.
+
+## Display Views
+
+As duas views abaixo foram desenvolvidas para exibir informações. Tipicamente
+essas views são as mais usadas na maioria dos projetos.
+
+### DetailView
+
+Renderiza um template contendo em seu contexto **um objeto** obtido pelo
+parâmetro enviado na URL.
+
+No fluxo de execução dessa view o objeto que está sendo utilizado está em
+``self.object``
+
+O fluxo básico de execução dessa classe quando recebe
+uma requisição é:
+
+1. ``dispatch()``
+2. ``http_method_not_allowed()``
+3. ``get_template_names()``
+4. ``get_slug_field()``
+5. ``get_queryset()``
+6. ``get_object()``
+7. ``get_context_object_name()``
+8. ``get_context_data()``
+9. ``get()``
+10. ``render_to_response()``
+
+O fluxo parece grande e complexo mas na verdade é muito simples e facilmente
+customizável. Basicamente o que acontece é:
+
+``get_template_names()`` retorna uma lista de templates que devem ser usados para
+renderizar a resposta. Caso o primeiro template da lista não seja encontrado o
+Django tenta o segundo e assim por diante.
+
+Em seguida o ``get_slug_field()`` entra em ação, essa função deve retornar o
+nome do campo que será usado para fazer a busca pelo objeto. Por default o
+Django procura pelo campo ``slug``.
+
+Agora o ``get_queryset`` deve retornar um queryset que será usado para buscar
+um objeto. Aqui é um ótimo lugar para, por exemplo, aplicar um filtro para
+exibir somente o Artigo cujo autor é o usuário logado. Considere o exemplo
+abaixo:
+
+```python
+
+def ArtigoView(DetailView):
+ model = Artigo
+
+ get_queryset(self):
+ return self.model.filter(user=request.user)
+
+ # ... o restante do código foi suprimido
+```
+
+**IMPORTANTE:** O ``get_queryset()`` é chamado pela implementação default do
+método ``get_object()``, se o ``get_object()`` for sobrescrito a chamada ao
+``get_queryset()`` pode não ser realizada.
+
+O ``get_object()`` então é o responsável por retornar o objeto que será enviado
+para o template. Normalmente essa função não precisa ser sobrescrita.
+
+Depois de obter o objeto que será enviado para o template é necessário saber
+qual será o nome desse objeto no contexto do template, isso é feito pela função
+``get_context_object_name()``, por default o nome do objeto no template será o
+nome do ``Model``, no exemplo acima seria ``artigo``
+
+Depois disso temos o ``get_context_data()`` que já foi comentado acima e então
+o ``get()`` que obtém o objeto e coloca no contexto, e em seguida o
+``render_to_response`` que renderiza o template.
+
+**IMPORTANTE:** É importante notar que o Django oferece variáveis de
+instância para facilitar a customização do comportamento da classe.
+Por exemplo a troca do nome do objeto pode ser feita alterando a variável de
+instância ``context_object_name`` ao invés de sobrescrever a função
+``get_object_name()``.
+
+Abaixo segue um exemplo, onde exibir os detalhes de um *Artigo* somente se o
+usuário for o autor dele e vamos pegar esse *Artigo* pelo campo ``titulo`` e
+renderizar esse artigo no template ``detalhe_artigo.html`` com o nome
+``meu_artigo``.
+
+**views.py**
+```python
+
+from django.views.generic.detail import DetailView
+from django.utils import timezone
+
+from articles.models import Article
+
+class ArticleDetailView(DetailView):
+ slug_field = 'titulo'
+ model = Article
+ context_object_name = 'meu_artigo'
+ template_name = 'detalhe_artigo.html'
+
+ get_queryset(self):
+ return self.model.filter(user=self.request.user)
+
+```
+
+**urls.py**
+```python
+from django.conf.urls import url
+
+from article.views import ArticleDetailView
+
+urlpatterns = [
+ url(http://github.com/r'^(?P%3Ctitulo%3E[-\w]+)/$', ArticleDetailView.as_view(), name='article-detail'),
+]
+```
+
+**detalhe_artigo.html**
+```html
+
{{ meu_artigo.titulo }}
+
{{ meu_artigo.conteudo }}
+
Reporter: {{ meu_artigo.user.name }}
+
Published: {{ meu_artigo.data_publicacao|date }}
+```
+
+### ListView
+
+Uma página que representa uma lista de objetos.
+Enquanto essa view está executando a variável ``self.object_list`` vai conter
+a lista de objetos que a view está utilizando.
+
+O fluxo básico de execução dessa classe quando recebe
+uma requisição é:
+
+1. ``dispatch()``
+2. ``http_method_not_allowed()``
+3. ``get_template_names()``
+5. ``get_queryset()``
+6. ``get_object()``
+7. ``get_context_object_name()``
+8. ``get_context_data()``
+9. ``get()``
+10. ``render_to_response()``
+
+Nada de novo aqui certo? Podemos exibir apenas uma lista de Artigos que estão
+com ``status='publicado'``
+
+```python
+
+from django.views.generic.list import ListView
+from django.utils import timezone
+
+from articles.models import Artigo
+
+class ArticleListView(ListView):
+
+ model = Artigo
+
+ def get_queryset(self, **kwargs):
+ return Artigo.objects.filter(status='publicado')
+```
+
+Outra opção seria:
+
+```python
+
+from django.views.generic.list import ListView
+from django.utils import timezone
+
+from articles.models import Artigo
+
+class ArticleListView(ListView):
+
+ model = Artigo
+ queryset = Artigo.objects.filter(status='publicado')
+```
+
+**artigo_list.html**
+```html
+
+```
+
+**DICA:** Normalmente sobrescrevemos as funções quando o retorno depende dos
+parâmetros da requisição e utilizamos as variáveis de instância quando não há
+essa dependência.
+
+O nome do template que é usado em ambas as views ``DetailView`` e ``ListView``
+é determinado da seguinte forma:
+
+* O valor da variável ``template_name`` na View (se definido)
+* O valor do campo ``template_name_field`` na instância do objeto que a view
+ está usando.
+* ``/.html``
+
+
+## Editing Views
+
+As views descritas abaixo contém o comportamento básico para edição de conteúdo.
+
+### FormView
+
+Uma view que mostra um formulário. Se houver erro, mostra o formulário novamente
+contendo os erros de validação. Em caso de sucesso redireciona o usuário para
+uma nova URL.
+
+**forms.py**
+```python
+
+from django import forms
+
+class ContactForm(forms.Form):
+ name = forms.CharField()
+ message = forms.CharField(widget=forms.Textarea)
+
+ def send_email(self):
+ # send email using the self.cleaned_data dictionary
+ pass
+```
+
+**views.py**
+```python
+
+from myapp.forms import ContactForm
+from django.views.generic.edit import FormView
+
+class ContactView(FormView):
+ template_name = 'contact.html'
+ form_class = ContactForm
+ success_url = '/thanks/'
+
+ def form_valid(self, form):
+ # This method is called when valid form data has been POSTed.
+ # It should return an HttpResponse.
+ form.send_email()
+ return super(ContactView, self).form_valid(form)
+```
+
+**contact.html**
+```html
+
+```
+
+As funções mais importantes do ``FormView`` são:
+
+- ``form_valid()``: Chamada quando o formulário é validado com sucesso
+- ``form_invalid()``: Chamada quando o formuĺário contém erros
+- ``get_sucess_url()``: Chamada quando o formulário é validado com sucesso
+ e retorna a url para qual o usuário deve ser redirecionado.
+
+### Views para lidar com ``models`` (ModelForms)
+
+Grande parte do "poder" das CBV's vem quando precisamos trabalhar com models.
+
+As views listadas abaixo: ``CreateView``, ``UpdateView`` e ``DeleteView`` foram
+criadas para facilitar esse trabalho com os models, essas views podem gerar um
+``ModelForm`` de maneira automática, desde que seja possível determinar qual
+é o model que a view está utilizando.
+
+A view vai tentar determinar o model a ser usado das seguintes formas:
+
+- Se houver um atributo ``model`` na classe
+- Se o método ``get_object()`` retorna um objeto, a classe desse objeto será
+ usada
+- Se houver um atributo ``queryset`` o model do ``queryset`` será utilizado
+
+Você não precisa nem mesmo definir um ``success_url`` as views ``CreateView`` e
+``UpdateView`` utilizam automaticamente a função ``get_absolute_url()`` do model
+se essa função existir.
+
+Você também pode customizar o formulário usado na view se você precisar de algum
+tratamento adicional, para fazer isso basta definir a classe de formulários a ser
+usada no atributo ``form_class``:
+
+```python
+
+from django.views.generic.edit import CreateView
+from myapp.models import Author
+from myapp.forms import AuthorForm
+
+class AuthorCreate(CreateView):
+ model = Author
+ form_class = AuthorForm
+```
+
+### CreateView, UpdateView e DeleteView
+
+Uma view que exibe um form para criar, atualizar ou apagar um objeto.
+Caso existam erros no formulário, este é exibido novamente junto com as
+mensagens de erro.
+
+Em caso de sucesso o objeto é salvo.
+
+**models.py**
+
+```python
+
+from django.core.urlresolvers import reverse
+from django.db import models
+
+class Author(models.Model):
+ name = models.CharField(max_length=200)
+
+ def get_absolute_url(http://github.com/self):
+ return reverse('author-detail', kwargs={'pk': self.pk})
+```
+
+**views.py**
+```python
+
+from django.views.generic.edit import CreateView, UpdateView, DeleteView
+from django.core.urlresolvers import reverse_lazy
+from myapp.models import Author
+
+class AuthorCreate(CreateView):
+ model = Author
+ fields = ['name']
+
+class AuthorUpdate(UpdateView):
+ model = Author
+ fields = ['name']
+
+class AuthorDelete(DeleteView):
+ model = Author
+ success_url = reverse_lazy('author-list')
+
+```
+
+**urls.py**
+```python
+from django.conf.urls import url
+from myapp.views import AuthorCreate, AuthorUpdate, AuthorDelete
+
+urlpatterns = [
+ # ...
+ url(http://github.com/r'author/add/),%20AuthorCreate.as_view(), name='author_add'),
+ url(http://github.com/r'author/(?P%3Cpk%3E[0-9]+)/$', AuthorUpdate.as_view(), name='author_update'),
+ url(http://github.com/r'author/(?P%3Cpk%3E[0-9]+)/delete/$', AuthorDelete.as_view(), name='author_delete'),
+]
+```
+
+O atributo ``fields`` determina quais campos do model devem estar presentes no
+formulário. É obrigatório especificar o atributo ``fields`` ou então o atributo
+``form_class``, nunca os dois ao mesmo tempo, pois isso geraria uma exceção
+[``ImproperlyConfigured``](https://docs.djangoproject.com/en/1.8/ref/exceptions/#django.core.exceptions.ImproperlyConfigured).
+
+É importante notar também que a ``DeleteView`` exibe as informações do objeto que
+será deletado quando é acessada usando o verbo ``GET``, quando usado o verbo
+``POST`` o objeto é efetivamente apagado.
+
+**DICA**: O nome dos templates é determinado da seguinte forma:
+
+- ``CreateView`` e UpdateView usam myapp/author_form.html
+- ``DeleteView`` usa myapp/author_confirm_delete.html
+
+## Date Views
+
+Date-based generic views são views com a função de exibir páginas com dados
+filtrados por datas, por exemplo: posts em um blog, notícias, consultas ao médico, etc.
+
+### ArchiveIndexView
+
+Uma página que exibe os "últimas" objetos inseridos, desconsiderando aqueles com uma
+data futura a não ser que o atributo ``allow_future`` seja definido como ``True``.
+
+É importante notar que:
+
+- O nome default do ``context_object_name`` é ``latest``.
+- O sufixo ``_archive`` no nome do template.
+- Além da lista de objetos o contexto também contem a variável ``date_list``
+ contendo todos os anos que tem objetos em ordem decrescente.
+ Isso pode ser alterado para mês ou dia usando o atributo
+ ``date_list_period``. Isso se aplica a todas as *Data-based generic views*.
+
+Implementação simples:
+
+**urls.py**
+```python
+
+from django.conf.urls import url
+from django.views.generic.dates import ArchiveIndexView
+
+from myapp.models import Article
+
+urlpatterns = [
+ url(r'^archive/$',
+ ArchiveIndexView.as_view(model=Article, date_field="pub_date"),
+ name="article_archive"),
+]
+```
+
+### YearArchiveView
+
+Uma página para exibir um arquivo anual. Retorna todos os objetos de um
+determinado ano.
+
+No contexto além da lista de objetos temos ainda:
+
+- ``date_list``: Um objeto QuerySet contendo todos os meses que tenham objetos
+ naquele ano representados como objetos datetime.datetime em ordem crescente.
+- ``year``: Um objeto datetime.datetime representando o ano atual
+- ``next_year``: Um objeto datetime.datetime representando o próximo ano
+- ``previous_year``: Um objeto datetime.datetime representando o ano anterior
+
+Exemplo de implementação:
+
+**views.py**
+```python
+from django.views.generic.dates import YearArchiveView
+
+from myapp.models import Article
+
+class ArticleYearArchiveView(YearArchiveView):
+ queryset = Article.objects.all()
+ date_field = "pub_date"
+ make_object_list = True
+ allow_future = True
+```
+
+**urls.py**
+```python
+from django.conf.urls import url
+
+from myapp.views import ArticleYearArchiveView
+
+urlpatterns = [
+ url(http://github.com/r'^(?P%3Cyear%3E[0-9]{4})/$',
+ ArticleYearArchiveView.as_view(),
+ name="article_year_archive"),
+]
+```
+
+**article_archive_year.html**
+```html
+
+ {% for date in date_list %}
+
{{ date|date }}
+ {% endfor %}
+
+```
+
+### MonthArchiveView
+
+Uma página para exibir um arquivo mensal. Retorna todos os objetos de um
+determinado mês.
+
+No contexto além da lista de objetos temos ainda:
+
+- ``date_list``: Um objeto QuerySet contendo todos os dias que tenham objetos
+ naquele mês representados como objetos datetime.datetime em ordem crescente.
+- ``month``: Um objeto datetime.datetime representando o mês atual
+- ``next_month``: Um objeto datetime.datetime representando o próximo mês
+- ``previous_month``: Um objeto datetime.datetime representando o mês anterior
+
+Exemplo de implementação:
+
+**views.py**
+```python
+from django.views.generic.dates import MonthArchiveView
+
+from myapp.models import Article
+
+class ArticleMonthArchiveView(MonthArchiveView):
+ queryset = Article.objects.all()
+ date_field = "pub_date"
+ allow_future = True
+```
+
+**urls.py**
+```python
+from django.conf.urls import url
+
+from myapp.views import ArticleMonthArchiveView
+
+urlpatterns = [
+ # Example: /2012/aug/
+ url(http://github.com/r'^(?P%3Cyear%3E[0-9]{4})/(?P[-\w]+)/$',
+ ArticleMonthArchiveView.as_view(),
+ name="archive_month"),
+ # Example: /2012/08/
+ url(http://github.com/r'^(?P%3Cyear%3E[0-9]{4})/(?P[0-9]+)/$',
+ ArticleMonthArchiveView.as_view(month_format='%m'),
+ name="archive_month_numeric"),
+]
+```
+
+**article_archive_month.html**
+```html
+
+ {% if previous_week %}
+ Previous Week: {{ previous_week|date:"F Y" }}
+ {% endif %}
+ {% if previous_week and next_week %}--{% endif %}
+ {% if next_week %}
+ Next week: {{ next_week|date:"F Y" }}
+ {% endif %}
+
+```
+
+### DayArchiveView
+
+Uma página para exibir um arquivo diário. Retorna todos os objetos de um
+determinado dia.
+
+No contexto além da lista de objetos temos ainda:
+
+- ``day``: Um objeto datetime.datetime representando o dia atual
+- ``next_day``: Um objeto datetime.datetime representando o próximo dia
+- ``previous_day``: Um objeto datetime.datetime representando o dia anterior
+- ``next_month``: Um objeto datetime.datetime representando o primeiro dia do
+ próximo mês
+- ``previous_month``: Um objeto datetime.datetime representando o primeiro dia
+ do mês anterior
+
+Implementação simples:
+
+**views.py**
+```python
+from django.views.generic.dates import DayArchiveView
+
+from myapp.models import Article
+
+class ArticleDayArchiveView(DayArchiveView):
+ queryset = Article.objects.all()
+ date_field = "pub_date"
+ allow_future = True
+```
+
+**urls.py**
+```python
+from django.conf.urls import url
+
+from myapp.views import ArticleDayArchiveView
+
+urlpatterns = [
+ # Example: /2012/nov/10/
+ url(http://github.com/r'^(?P%3Cyear%3E[0-9]{4})/(?P[-\w]+)/(?P[0-9]+)/$',
+ ArticleDayArchiveView.as_view(),
+ name="archive_day"),
+]
+```
+
+**article_archive_day.html**
+```html
+
+ {% if previous_day %}
+ Previous Day: {{ previous_day }}
+ {% endif %}
+ {% if previous_day and next_day %}--{% endif %}
+ {% if next_day %}
+ Next Day: {{ next_day }}
+ {% endif %}
+
+```
+
+### TodayArchiveView
+
+É a mesma coisa do ``DayArchiveView`` mas não usa os parâmetros da URL para
+determinar o ano/mês/dia.
+
+O que muda é o urls.py, veja o exemplo abaixo:
+
+```python
+from django.conf.urls import url
+
+from myapp.views import ArticleTodayArchiveView
+
+urlpatterns = [
+ url(r'^today/$',
+ ArticleTodayArchiveView.as_view(),
+ name="archive_today"),
+]
+```
+
+### DateDetailView
+
+É a mesma coisa que a ``DetailView`` com a diferença que a data é utilizada
+junto com o pk/slug para determinar qual objeto deve ser obtido.
+
+O que muda é o urls.py, veja o exemplo abaixo:
+
+```python
+from django.conf.urls import url
+from django.views.generic.dates import DateDetailView
+
+urlpatterns = [
+ url(http://github.com/r'^(?P%3Cyear%3E[0-9]{4})/(?P[-\w]+)/(?P[0-9]+)/(?P[0-9]+)/$',
+ DateDetailView.as_view(model=Article, date_field="pub_date"),
+ name="archive_date_detail"),
+]
+```
+
+### Conclusão
+
+Longe de tentar exaurir um assunto de tamanha complexidade e abrangência minha
+intenção com esse artigo foi mostrar o funcionamento básico das Class Based Views
+e quem sabe incentivar você a utilizar CBV's no seu próximo projeto.
+
+Envie para mim qualquer dúvida, crítica ou sugestão que você tiver em qualquer
+uma das minhas redes sociais, posso demorar um pouco a responder mas eu respondo! :)
+
+Ah, se você se interessou pelo assunto e quer se aprofundar mais eu aconselho
+começar pela [Documentação oficial](https://docs.djangoproject.com/en/1.8/topics/class-based-views/intro/)
+
+### Referências
+
+-
+-
+-
+-
\ No newline at end of file
diff --git a/content/como-distribuir-seu-projeto-python-com-pypi.md b/content/como-distribuir-seu-projeto-python-com-pypi.md
new file mode 100644
index 000000000..d3e8ac050
--- /dev/null
+++ b/content/como-distribuir-seu-projeto-python-com-pypi.md
@@ -0,0 +1,222 @@
+Title: Como distribuir sua aplicação Python com PyPI
+Slug: como-distribuir-sua-aplicacao-python-com-pypi
+Date: 2016-06-17 13:47:24
+Category: Python
+Tags: python, pypi, tutorial, desenvolvimento, pypi, pip
+Author: Michell Stuttgart
+Email: michellstut@gmail.com
+Github: mstuttgart
+Linkedin: mstuttgart
+Site: https://mstuttgart.github.io
+
+Imagine a seguinte situação: você passou alguns dias (ou mesmo meses) desenvolvendo uma módulo python, escreveu testes, implementou funcionalidades e depois de alguns ajustes, chegou a hora de liberar seu software para que outros desenvolvedores possam utilizá-lo. Qual o melhor modo de distribuí-lo?
+
+Caro leitor, se você costuma programar em Python (seja profissionalmente ou não) provavelmente já instalou outros módulos usando o [PyPI](https://pypi.python.org/pypi), através do comando abaixo:
+
+```bash
+pip install nomedomodulo
+```
+
+Não seria interessante usar o mesmo método para distribuir a sua aplicação? Sim? Então mãos a obra.
+
+### Sobre o PyPI - Python Package Index
+
+O site [PyPI](https://pypi.python.org/pypi), é um repositório de *softwares* desenvolvidos na linguagem Python. Em outras palavras, ele garante que seu pacote Python sempre esteja disponível para a instalação. O seu funcionamente é simples, porém algumas configurações inicias devem ser feitas para que tudo funcione corretamente.
+
+### Crie uma conta
+
+Primeiramente, para distribuir seus pacotes usando o [PyPI](https://pypi.python.org/pypi), precisamos criar uma conta em ambos os sites:
+
+* [PyPI Live](https://pypi.python.org/pypi?%3Aaction=register_form)
+* [PyPI Test](https://testpypi.python.org/pypi?%3Aaction=register_form)
+
+Recomendo que você utilize o mesmo email e senha para ambos os sites. Posteriormente, isso tornará mais fácil o processo de configuração.
+
+### Configurando o ambiente
+
+O próximo passo é criar um arquivo `.pypirc` em sua `home`. Esse arquivo contem informações de auteticação, tanto para o [PyPI Live](https://pypi.python.org/pypi) quando para o [PyPI Test](https://testpypi.python.org/pypi).
+
+```bash
+touch ~/.pypirc
+```
+
+Apesar de não ser obrigatório a criação desse aquivo, ele facilita muito nosso trabalho, uma vez que você não precisaremos inserir nosso email e senha toda vez que formos enviar nosso código para o [PyPI Live](https://pypi.python.org/pypi).
+
+Abra o arquivo `.pypirc` em seu editor de texto favorito, e insira as informações abaixo.
+
+```bash
+[distutils]
+index-servers =
+ pypi
+ pypitest
+
+[pypi]
+repository=https://pypi.python.org/pypi
+username=seu_nomedeusuario
+password=sua_senha
+
+[pypitest]
+repository=https://testpypi.python.org/pypi
+username=seu_nomedeusuario
+password=sua_senha
+
+```
+Em *username* insira seu nome de usuário e *password*, insira sua senha. Faça isso tanto para o `pypi` quanto para o `pypitest`.
+
+Um observação importante é que, caso a sua senha possua espaço, não a coloque entre aspas. Por exemplo, se a sua senha for "batuque da viola doida", coloque exatamente o mesmo texto em *password*.
+
+
+```bash
+password=batuque da viola doida
+```
+
+### Preparando o seu módulo Python
+
+Todo pacote distribuído pelo [PyPI](https://pypi.python.org/pypi) precisa ter uma arquivo `setup.py` em seu diretório raiz. E se seu projeto também usa um arquivo *readme* em *markdown* (normalmente chamado `README.md`) você também precisará criar um arquivo chamado `setup.cfg`no diretório raiz do módulo.
+
+Como exemplo, iremos utilizar o módulo [codigo_avulso_test_tutorial](https://github.com/mstuttgart/codigo-avulso-test-tutorial) que criei para ser utilizado como exemplo em nossos tutoriais. Assim, temos a seguinte estrutura básica de diretórios:
+
+```bash
+.
+├── codigo_avulso_test_tutorial
+│ ├── circulo.py
+│ ├── figura_geometrica.py
+│ ├── __init__.py
+│ └── quadrado.py
+├── LICENSE
+├── README.md
+├── setup.cfg
+├── setup.py
+└── test
+ ├── circulo_test.py
+ ├── figura_geometrica_test.py
+ ├── __init__.py
+ └── quadrado_test.py
+
+```
+Aqui, o que nos interessa são os arquivos `setup.py` e `setup.cfg`. Dentro do arquivo `setup.py` temos várias informações sobre nossa aplicação que serão usadas pelo [PyPI](https://pypi.python.org/pypi).
+
+```python
+# -*- coding: utf-8 -*-
+from setuptools import setup
+
+setup(
+ name='codigo-avulso-test-tutorial',
+ version='0.1.1',
+ url='https://github.com/mstuttgart/codigo-avulso-test-tutorial',
+ license='MIT License',
+ author='Michell Stuttgart',
+ author_email='michellstut@gmail.com',
+ keywords='tutorial test unittest codigoavulso',
+ description=u'Tutorial de teste unitário em Python para o blog Código Avulso',
+ packages=['codigo_avulso_test_tutorial'],
+ install_requires=[],
+)
+```
+O nome de cada *tag* é autoexplicativo, então não vou entrar em detalhes. Basta você usar o código acima e substituir com os dados do seu pacote.
+
+O próximo passo é adicionar o seguinte conteúdo no arquivo `setup.cfg` (caso você o tenha criado).
+
+```bash
+[metadata]
+description-file = README.md
+```
+Esse arquivo irá dizer ao [PyPI](https://pypi.python.org/pypi) onde seu arquivo *readme* está.
+
+### Publicando sua aplicação Python
+
+Agora iremos estudar os passos para enviar nossa aplicação para [PyPI](https://pypi.python.org/pypi), para que ela fique disponível para ser instalada através do `pip`.
+
+#### Enviando para PyPI Test
+
+Primeiramente, vamos registrar nossa aplicação no [PyPI Test](https://testpypi.python.org/pypi). Esse passo serve para verificarmos se está tudo certo com nosso pacote e também validar se já não existe outro módulo com o mesmo nome.
+Registramos nossa aplicação com o seguinte comando:
+
+```bash
+python setup.py register -r pypitest
+```
+
+Se tudo ocorrer bem teremos a seguinte saída (Server responde 200):
+
+```bash
+running register
+running egg_info
+creating codigo_avulso_test_tutorial.egg-info
+writing codigo_avulso_test_tutorial.egg-info/PKG-INFO
+writing top-level names to codigo_avulso_test_tutorial.egg-info/top_level.txt
+writing dependency_links to codigo_avulso_test_tutorial.egg-info/dependency_links.txt
+writing manifest file 'codigo_avulso_test_tutorial.egg-info/SOURCES.txt'
+reading manifest file 'codigo_avulso_test_tutorial.egg-info/SOURCES.txt'
+writing manifest file 'codigo_avulso_test_tutorial.egg-info/SOURCES.txt'
+running check
+Registering codigo-avulso-test-tutorial to https://testpypi.python.org/pypi
+Server response (200): OK
+```
+Caso exista outro pacote com o mesmo nome, teríamos de escolher outro nome para o nosso pacote. Agora com nosso pacote devidamente registrado, executamos o comando abaixo para que o pacote seja enviado para o [PyPI Test](https://testpypi.python.org/pypi).
+
+```bash
+python setup.py sdist upload -r pypitest
+```
+
+Se tudo ocorrer bem (Server responde 200), você verá uma saída semelhante a esta e já poderá ver sua aplicação na lista do [PyPI Test](https://testpypi.python.org/pypi).
+
+```bash
+running sdist
+running egg_info
+writing codigo_avulso_test_tutorial.egg-info/PKG-INFO
+writing top-level names to codigo_avulso_test_tutorial.egg-info/top_level.txt
+writing dependency_links to codigo_avulso_test_tutorial.egg-info/dependency_links.txt
+reading manifest file 'codigo_avulso_test_tutorial.egg-info/SOURCES.txt'
+writing manifest file 'codigo_avulso_test_tutorial.egg-info/SOURCES.txt'
+warning: sdist: standard file not found: should have one of README, README.rst, README.txt
+
+.
+.
+.
+
+creating dist
+Creating tar archive
+removing 'codigo-avulso-test-tutorial-0.1.1' (and everything under it)
+running upload
+Submitting dist/codigo-avulso-test-tutorial-0.1.1.tar.gz to https://testpypi.python.org/pypi
+Server response (200): OK
+
+```
+
+#### Enviando para PyPI Live
+
+Agora é pra valer. Executamos o mesmo passos para o [PyPI Test](https://testpypi.python.org/pypi).
+
+```bash
+python setup.py register -r pypi
+```
+
+Tudo ocorrendo bem, enviamos nosso pacote:
+
+```bash
+python setup.py sdist upload -r pypi
+```
+
+Parabéns! Com esse ultimo passo, publicamos o nosso pacote Python com sucesso! Agora ele pode ser [visualizado na lista de aplicações](https://pypi.python.org/pypi/codigo-avulso-test-tutorial/0.1.1) do [PyPI](https://pypi.python.org/pypi) e ser instalado usando `pip`.
+
+```bash
+pip install nomedopacote
+```
+
+Ou, para o nosso exemplo:
+
+```bash
+pip install codigo_avulso_test_tutorial
+```
+
+### Conclusão
+
+É isso pessoal. Neste tutorial vimos como distribuir nossa aplicação Python, desde a crição na conta no [PyPI](https://pypi.python.org/pypi) até o registro e *upload* da nossa aplicação. Espero que tenham gostado e caso tenham alguma dúvida, deixem um comentário.
+
+Obrigado pela leitura e até o próximo tutorial.
+
+### Referências
+
+* [Documentação oficial](https://wiki.python.org/moin/CheeseShopTutorial#Submitting_Packages_to_the_Package_Index)
+* [How to Host your Python Package on PyPI with GitHub](https://www.codementor.io/python/tutorial/host-your-python-package-using-github-on-pypi)
+* [How to submit a package to PyPI](http://peterdowns.com/posts/first-time-with-pypi.html)
diff --git a/content/como-encontrar-solucoes-python.md b/content/como-encontrar-solucoes-python.md
new file mode 100644
index 000000000..0c3fbe70d
--- /dev/null
+++ b/content/como-encontrar-solucoes-python.md
@@ -0,0 +1,221 @@
+Title: Como encontrar soluções para seus problemas com Python
+Slug: como-encontrar-solucoes-python
+Date: 2016-01-09 10:30
+Tags: python,blog,tutorial,aulas
+Author: Eric Hideki
+Email: eric8197@gmail.com
+Github: erichideki
+Bitbucket: erichideki
+Site: http://ericstk.wordpress.com
+Twitter: erichideki
+Category: Python
+
+Como encontrar soluções para seus problemas com Python
+----------------------------------------------------
+
+Quando estamos aprendendo algo, o início geralmente é difícil. Conseguir absorver novos conceitos e entender como as coisas funcionam não é uma das tarefas mais simples, porém nessas horas precisamos lembrar do conceito de 'babysteps' (Um passo de cada vez), ter paciência e persistir.
+
+A diferença entre uma pessoa experiente comparado a um iniciante é que a pessoa experiente errou muito mais vezes do que um iniciante, e com o tempo aprendeu com os erros.
+
+E na área de programação temos inúmeros tutoriais e cursos espalhados pela internet, e geralmente as pessoas começam a aprender por conta própria. E quando acontece um problema (e isso irá acontecer inevitavelmente) e não sabemos como solucioná-lo, se torna um obstáculo chato.
+
+Neste artigo irei mostrar algumas formas de poder encontrar soluções para seus problemas enquanto aprende a programar.
+
+Os exemplos se aplicam a Python, mas podem ser exemplificados para qualquer outra linguagem ou tecnologia (se você trabalha com outras tecnologias como PHP ou Ruby, deixe nos comentários quais ferramentas utilizam para encontrar soluções dos seus problemas).
+
+**Dica:** Veja essa palestra fantástica do [Josh Kaufman sobre as primeiras 20 horas de aprendizado](http://tedxtalks.ted.com/video/The-First-20-Hours-How-to-Learn)
+
+## Python console
+
+Uma grande feature do Python é seu console interativo, por ele conseguimos testar nossos códigos e alguns scripts em tempo real. Vejamos um exemplo:
+
+```python
+Python 3.4.3 (default, Oct 14 2015, 20:28:29)
+[GCC 4.8.4] on linux
+Type "help", "copyright", "credits" or "license" for more information.
+>>> a = {'Eric', 'Python', 'JavaScript'}
+>>> b = ('Django', 'Flask')
+>>> a + b
+Traceback (most recent call last):
+ File "", line 1, in
+TypeError: unsupported operand type(s) for +: 'set' and 'tuple'
+>>> tuple(a) + b
+('JavaScript', 'Eric', 'Python', 'Django', 'Flask')
+```
+
+### O que aconteceu?
+
+Na variável **a** criamos um *set* com os nomes *Eric, Python e JavaScript*, e depois criamos uma *tupla* com os nomes *Django e Flask*.
+
+Ao tentarmos juntar **a + b**, o interpretador Python nos retorna um erro: *TypeError: unsupported operand type(s) for +: 'set' and 'tuple'*. Ou seja, o que ele diz é que não podemos somar um set a uma tupla.
+
+O interpretador Python realiza as operações em tempo real, e se caso o que você deseja fazer não estiver correto, o interpretador irá informar o erro. Se o erro não for explícito para você, basta copiar e colar o erro no Google e encontrará os motivos do erro.
+
+Para resolver esse problema, uma das soluções apresentada é transformar nosso set em uma tupla, onde fazemos a conversão em tempo de execução com o comando **tuple(a) + b**.
+
+## [Ipython](http://ipython.org/)
+
+Que tal termos um interpretador Python mais poderoso e com mais funcionalidades que o tradicional? O [Ipython](http://ipython.org/) foi criado especificamente esse objetivo.
+
+Vamos explorá-lo um pouco:
+
+
+```python
+$ipython
+
+Python 2.7.6 (default, Jun 22 2015, 17:58:13)
+Type "copyright", "credits" or "license" for more information.
+
+IPython 1.2.1 -- An enhanced Interactive Python.
+? -> Introduction and overview of IPython's features.
+%quickref -> Quick reference.
+help -> Python's own help system.
+object? -> Details about 'object', use 'object??' for extra details.
+
+In [1]: nome = "Eric"
+
+In [2]: nome.
+nome.capitalize nome.isalnum nome.lstrip nome.splitlines
+nome.center nome.isalpha nome.partition nome.startswith
+nome.count nome.isdigit nome.replace nome.strip
+nome.decode nome.islower nome.rfind nome.swapcase
+nome.encode nome.isspace nome.rindex nome.title
+nome.endswith nome.istitle nome.rjust nome.translate
+nome.expandtabs nome.isupper nome.rpartition nome.upper
+nome.find nome.join nome.rsplit nome.zfill
+nome.format nome.ljust nome.rstrip
+nome.index nome.lower nome.split
+
+In [2]: nome.startswith("O")
+Out[2]: False
+
+In [3]: nome.startswith("E")
+Out[3]: True
+
+In [4]: len(nome)
+Out[4]: 4
+
+In [5]: nome.lower()
+Out[5]: 'eric'
+
+```
+
+### O que aconteceu?
+
+Criamos uma variável **nome** onde é um simples String "Eric". E se digitarmos **nome.** e apertar TAB, o Ipython irá apresentar diversas operações que podemos fazer com essa variável.
+
+Por exemplo, **nome.startswith** verifica se a primeira letra ou número começa com o parâmetro passado a ele.
+
+Na primeira tentativa verificamos se a variável começa com a letra *O*, e ele me retornou **False**, ou seja, não é verdade.
+
+Na segunda tentativa tentamos com a letra **E**, e ele me retornou **True**, o que é verdade já que o nome **Eric** começa com a letra "E".
+
+O Ipython vai muito mais além do que isso, vá e dê uma olhada na página oficial e o que ele pode fazer.
+
+E temos também o [Ipython Notebook](http://ipython.org/notebook.html) que é FANTÁSTICO, amplamente utilizado para computação científica.
+
+## [Dreampie](http://www.dreampie.org/)
+
+Quando queremos testar algo mais elaborado, ter um console que permita criamos nossos códigos de forma mais organizada além do Interpretador padrão do Python e o IPython, podemos utilizar o [Dreampie](http://www.dreampie.org/) que é ideal para isso.
+
+Vamos a outro exemplo:
+
+```python
+Python 2.7.6 (default, Jun 22 2015, 17:58:13)
+[GCC 4.8.2] on linux2
+Type "copyright", "credits" or "license()" for more information.
+DreamPie 1.1.1
+>>> colunas = [
+... {'pnome': 'Eric', 'unome': 'Hideki', 'id': 4},
+... {'pnome': 'Luciano', 'unome': 'Ramalho', 'id': 2},
+... {'pnome': 'David', 'unome': 'Beazley', 'id': 8},
+... {'pnome': 'Tim', 'unome': 'Peters', 'id': 1},
+... ]
+>>> from operator import itemgetter
+>>> colunas_por_nome = sorted(colunas, key=itemgetter('pnome'))
+>>> colunas_por_id = sorted(colunas, key=itemgetter('id'))
+>>> from pprint import pprint
+>>> pprint(colunas_por_nome)
+[{'id': 8, 'pnome': 'David', 'unome': 'Beazley'},
+ {'id': 4, 'pnome': 'Eric', 'unome': 'Hideki'},
+ {'id': 2, 'pnome': 'Luciano', 'unome': 'Ramalho'},
+ {'id': 1, 'pnome': 'Tim', 'unome': 'Peters'}]
+>>> pprint(colunas_por_id)
+[{'id': 1, 'pnome': 'Tim', 'unome': 'Peters'},
+ {'id': 2, 'pnome': 'Luciano', 'unome': 'Ramalho'},
+ {'id': 4, 'pnome': 'Eric', 'unome': 'Hideki'},
+ {'id': 8, 'pnome': 'David', 'unome': 'Beazley'}]
+```
+
+
+
+### O que aconteceu?
+
+Temos uma lista de dicionários desordenado e queremos ordenar de acordo com os parâmetros que queremos. Com isso, utilizamos duas bibliotecas que já estão por padrão com o Python: *[operator](https://docs.python.org/2/library/operator.html?highlight=itemgetter#operator.itemgetter) e [pprint](https://docs.python.org/2/library/pprint.html)*.
+
+Dentro da biblioteca **operator** temos a funcionalidade **itemgetter**, onde através dos parâmetros que passamos, ele irá fazer a seleção. Já o **pprint** irá mostrar o resultado da nossa seleção de forma mais bonita. Vamos explicar detalhadamente o que cada coisa faz.
+
+Criamos nossa lista de dicionários:
+
+```python
+>>> colunas = [
+... {'pnome': 'Eric', 'unome': 'Hideki', 'id': 4},
+... {'pnome': 'Luciano', 'unome': 'Ramalho', 'id': 2},
+... {'pnome': 'David', 'unome': 'Beazley', 'id': 8},
+... {'pnome': 'Tim', 'unome': 'Peters', 'id': 1},
+... ]
+```
+
+Importamos a biblioteca operator e sua funcionalidade itemgetter:
+
+```python
+>>>from operator import itemgetter
+```
+
+E criamos nossa funcionalidade para ordenar a lista de dicionários de acordo com o parâmetro selecionado, que nesse caso será o primeiro nome, o **pname**:
+
+```python
+>>> colunas_por_nome = sorted(colunas, key=itemgetter('pnome'))
+```
+
+Finalizando, utilizamos o **pprint** para exibir o resultado:
+
+```python
+>>>pprint(colunas_por_nome)
+```
+
+Bacana, né? E também utilizamos a mesma lógica para ordenar de acordo com o **id**.
+
+Se quiser saber mais a respeito do Ipython, indico o artigo do [Python Help que fala a respeito](https://pythonhelp.wordpress.com/2011/02/22/dreampie-o-shell-python-que-voce-sempre-sonhou/).
+
+# [Python Tutor](http://www.pythontutor.com/)
+
+Ao observarmos uma funcionalidade queremos entender o que ele faz, e muitas vezes o que cada coisa no código faz não fica muito bem claro em nossas ideias. Por isso o Python Tutor existe! Ele exibe passo-a-passo o que está acontecendo no código.
+
+
+
+Clique em **Forward** e veja o que acontece.
+
+# Outras opções
+
+Também existem outras ferramentas que podem auxiliar e melhorar seu código:
+
+- **Anaconda para Sublime Text** - [http://damnwidget.github.io/anaconda/](http://damnwidget.github.io/anaconda/)
+- **Autopep8** - [https://pypi.python.org/pypi/autopep8](https://pypi.python.org/pypi/autopep8)
+- **Jedi** - [https://github.com/davidhalter/jedi](https://github.com/davidhalter/jedi)
+- **Pyflakes** - [https://pypi.python.org/pypi/pyflakes](https://pypi.python.org/pypi/pyflakes)
+- **PDB** - [https://docs.python.org/2/library/pdb.html](https://docs.python.org/2/library/pdb.html)
+
+## Locais onde podemos postar nossas dúvidas
+
+Vale sempre lembrar que é muito importante consultar a documentação oficial do Python, seja a [versão 2](https://docs.python.org/2/) ou a [versão 3](https://docs.python.org/3/).
+
+Também existem outro lugar muito legal, o **[Stackoverflow](http://pt.stackoverflow.com/)**. Se ainda o problema persistir, acesse as listas de discussões da comunidade Python no Brasil.
+
+- **Python Brasil** - [https://groups.google.com/forum/#!forum/python-brasil](https://groups.google.com/forum/#!forum/python-brasil)
+- **Django Brasil** - [https://groups.google.com/forum/#!forum/django-brasil](https://groups.google.com/forum/#!forum/django-brasil)
+- **Web2py Brasil** - [https://groups.google.com/forum/#!forum/web2py-users-brazil](https://groups.google.com/forum/#!forum/web2py-users-brazil)
+- **Flask Brasil** - [https://groups.google.com/forum/#!forum/flask-brasil](https://groups.google.com/forum/#!forum/flask-brasil)
+- **Comunidades locais da comunidade Python ao redor do Brasil** - [http://pythonbrasil.github.io/wiki/comunidades-locais](http://pythonbrasil.github.io/wiki/comunidades-locais)
+
+Deixe nos comentários seu feedback, e se tiver outra dica que não foi citado, não deixe de indicar.
diff --git a/content/configurando-ambiente-django-com-apache-e-mod-wsgi.md b/content/configurando-ambiente-django-com-apache-e-mod-wsgi.md
new file mode 100644
index 000000000..06ab38919
--- /dev/null
+++ b/content/configurando-ambiente-django-com-apache-e-mod-wsgi.md
@@ -0,0 +1,269 @@
+Title: Configurando ambiente Django com Apache e mod_wsgi
+Date: 2015-03-02 00:20
+Tags: python, django, apache, mod_wsgi, virtualenv, virtualenvwrapper
+Category: Django
+Slug: configurando-ambiente-django-com-apache-e-mod-wsgi
+Author: Guilherme Louro
+Email: guilherme-louro@hotmail.com
+Github: guilouro
+Twitter: guilhermelouro
+Facebook: guilherme.louro.3
+
+
+### Entendendo a necessidade
+
+Muitas vezes encontramos dificuldade em colocar nossas aplicações para funcionar em um servidor devido ao pouco conhecimento em infraestrutura, principalmente aqueles que vieram do php, onde, subir um site e já o ver funcionando no ambiente final se trata apenas de subir os arquivos para a pasta **www** e pronto, certo? Não, não é bem por aí ...
+
+Normalmente quando configuramos a hospedagem de um domínio através de um software de gestão de alojamento web *([cpanel](http://cpanel.net) é o mais conhecido)* automaticamente o sistema configura o VirtualHost específico para o seu domínio cadastrado, ja direcionando a path para a sua pasta *www* ou **public_html**. Mas como isso é feito? Não entrarei em detalhes de como o cpanel funciona, mas irei demonstrar aqui como configuramos um servidor com [apache](http://httpd.apache.org/docs/) para receber nossa aplicação.
+
+### Mas por que o Apache?
+
+A partir do momento que eu mudei meu foco, saindo do PHP para trabalhar com **Python**, eu acabei "abandonando" o Apache para trabalhar com Nginx. Porém, me deparei com um projeto onde tinha que funcionar em uma Hospedagem compartilhada na qual só funciona o apache. Como não vi nada relacionado a essa configuração aqui no **Pythonclub**, achei que seria útil para muitos que podem cair em uma situação parecida com a minha, ou simplesmente prefira usar o Apache do que o Nginx.
+
+Caso o seu interesse seja mesmo usar o Nginx (pode parar por aqui), acho ótimo!!! Te dou todo apoio e ainda te indico um ótimo post para isso, do nosso amigo [Igor Santos](https://github.com/igr-santos).
+
+- [Configurando um servidor de produção para aplicações Python](http://pythonclub.com.br/configurando-um-servidor-de-producao-para-aplicacoes-python.html) (Nginx)
+
+Agora, chega de conversa e vamos ao que interessa.
+
+### Como fazer?
+
+Existem várias maneiras de se fazer o Django trabalhar com apache, uma delas é a combinação Apache + [mod_wsgi](http://code.google.com/p/modwsgi/) e será dessa forma que faremos. Com mod_wsgi podemos implementar qualquer aplicação Python que suporte a interface **Python WSGI**.
+
+##### Instalando alguns pacotes necessários
+
+Antes de mais nada, atualize a lista de pacotes
+
+```bash
+$ sudo apt-get update
+```
+
+*Apache + mod_wsgi*
+
+```bash
+$ sudo apt-get install apache2 libapache2-mod-wsgi
+```
+
+*Python setup tools + pip*
+
+```bash
+$ sudo apt-get install python-setuptools
+$ sudo apt-get install python-pip
+```
+
+### Vamos testar o WSGI?
+
+Vamos fazer um teste com uma aplicação simples em python.
+
+Começe criando um diretório na raiz do apache *(DocumentRoot)*
+
+```bash
+$ sudo mkdir /var/www/wsgi_test
+```
+
+Em seguida vamos criar nossa app de teste ...
+
+```bash
+$ sudo vim /var/www/app.wsgi
+```
+
+... e escrever nossa app python compatível com WSGI
+
+```python
+def application(environ, start_response):
+ status = '200 OK'
+ output = 'Hello World!'
+ response_headers = [('Content-type', 'text/plain'),
+ ('Content-Length', str(len(output)))]
+ start_response(status, response_headers)
+ return [output]
+```
+
+Vamos criar agora um host para usar como nosso domínio da aplicação teste
+
+```bash
+$ sudo vim /etc/hosts
+```
+
+Adicione esta linha ao seu arquivo hosts
+
+```bash
+127.0.0.1 wsgi_test
+```
+
+E vamos configurar nosso VirtualHost no Apache.
+
+```bash
+$ sudo vim /etc/apache2/sites-available/wsgi_test
+```
+
+```apache
+
+ ServerName wsgi_test
+ DocumentRoot /var/www/wsgi_test
+
+ Order allow,deny
+ Allow from all
+
+ WSGIScriptAlias / /var/www/wsgi_test/app.wsgi
+
+```
+
+Ative-o
+
+```bash
+$ sudo a2ensite wsgi_test
+```
+
+*Obs: esse comando cria um link simbólico do wsgi_test para a pasta sites-enabled. Você pode fazer isso manualmente.*
+
+Reinicie o apache:
+
+```bash
+$ sudo service apache2 reload
+```
+
+Feito isso abra o internet explorer seu navegador preferido e acesse [http://wsgi_test](http://wsgi_test). Se você está vendo a mensagem *"Hello World"* pode comemorar, o wsgi está funcionando com o apache.
+
+### Configurando Django com WSGI
+
+Até o momento entendemos como funciona a configuração do apache para receber uma aplicação Python com WSGI. Podemos usar essa ideia para qualquer aplicação python, porém veremos como fica essa configuração para trabalhar com **Django**.
+
+##### Criando o ambiente
+
+É sempre bom trabalharmos com ambientes virtuais em nossas aplicações python, para isso temos o [virtualenv](https://virtualenv.pypa.io/en/latest/). Eu, particularmente, prefiro usar o [VirtualenvWrapper](https://virtualenvwrapper.readthedocs.org/en/latest/), que separa os ambientes virtuais das aplicações. Caso você não conheça, indico o post do [Arruda](https://github.com/arruda/) que foi o que me guiou quando comecei a usar. [Usando VirtualEnvWrapper](http://www.arruda.blog.br/programacao/python/usando-virtualenvwrapper/)
+
+No meu caso usei o virtualenvwrapper e meu filesystem é o seguinte:
+
+```bash
++-- /home/guilouro
+| +-- .virtualenvs #[Ambientes]
+| +-- www #[Projetos]
+```
+
+O Virtualenvwrapper criará meus projetos dentro de **www** e os ambientes em **.virtualenvs**. Mas para que isso aconteça temos que adicionar algumas linhas em nosso `~/.bashrc`
+
+```bash
+# adicione no final do arquivo ~/.bashrc
+# ...
+export PROJECT_HOME=~/www
+export WORKON_HOME=~/.virtualenvs
+source /usr/local/bin/virtualenvwrapper.sh
+```
+
+Atualize com o comando:
+
+```bash
+source ~/.bashrc
+```
+
+##### Criando nosso projeto
+
+```bash
+$ mkproject wsgi
+```
+
+Com as configurações anteriores o virtualenvwrapper já irá ativar o ambiente e levar você para a pasta do projeto. Mas para ativar é muito simples, basta usar:
+
+```bash
+$ workon wsgi
+```
+
+Com o nosso ambiente virtual criado partiremos então para a criação do nosso projeto django. Utilizarei a versão mais atual até o momento, nesse caso 1.7
+
+```bash
+$ pip install django
+```
+
+Não entrarei em detalhes para a configuração inicial do django, portanto irei usar um template que eu criei para a inicialização dos meus projeto. Criamos então o projeto dessa forma:
+
+```bash
+# startproject pelo template
+$ django-admin.py startproject --template https://github.com/guilouro/django-boilerplate/archive/master.zip wsgitest .
+# instala os pacotes
+$ pip install -r requirements.txt
+# faz a migração
+$ python manage.py migrate
+```
+
+Você encontra esse template aqui -> [django-boilerplate](https://github.com/guilouro/django-boilerplate)
+
+##### Criando um site no apache para o projeto
+
+Primeiramente, vamos criar um domínio fictício para responder com o nosso projeto ao ser acessado.
+
+```bash
+$ sudo vim /etc/hosts
+
+127.0.0.1 djangowsgi.com
+```
+
+Agora vamos configurar o apache:
+
+```bash
+$ sudo vim /etc/apache2/sites-available/djangowsgi
+```
+
+```apache
+WSGIDaemonProcess djangowsgi.com python-path=/home/guilouro/www/wsgi:/home/guilouro/.virtualenvs/wsgi/lib/python2.7/site-packages
+WSGIProcessGroup djangowsgi.com
+
+
+ ServerName djangowsgi.com
+ WSGIScriptAlias / /home/guilouro/www/wsgi/wsgitest/wsgi.py
+
+
+
+ Order allow,deny
+ Allow from all
+
+
+
+ Alias /media/ /home/guilouro/www/wsgi/media/
+ Alias /static/ /home/guilouro/www/wsgi/static/
+
+
+ Order allow,deny
+ Allow from all
+
+
+
+ Order allow,deny
+ Allow from all
+
+
+
+```
+
+Reinicie novamente o apache:
+
+```bash
+$ sudo service apache2 reload
+```
+
+Explicarei agora um pouco do que foi usado nessa configuração
+
+**WSGIScriptAlias:** é a url na qual você estará servindo sua aplicação (/ indica a url raiz), e a segunda parte é a localização de um "arquivo WSGI".
+
+### Modo daemon
+
+ O modo *daemon* é o modo recomendado para a execução do mod_wsgi(em plataformas não-windows). Ele gera um processo independente que lida com as solicitações e pode ser executado como um usuário diferente do servidor web. Um dos pontos positivos dele é que a cada alteração em seu projeto você não precisa restartar o apache, basta executar um `touch` no seu arquivo `wsgi.py`
+
+##### Directivas para o daemon
+
+**WSGIDaemonProcess:** Foi atribuido a ele o nosso servername e utilizamos `python-path` por se tratar de um projeto que esta em ambiente virtual. Passamos então nossas paths nele.
+
+**WSGIProcessGroup:** Atribuímos o servername a ele
+
+#### Testando a aplicação
+
+Agora acesse [http://djangowsgi.com](http://djangowsgi.com) e corre para o abraço.
+
+Espero que tenha ficado claro. Qualquer dúvida ou problema deixe nos comentários e vamos juntos tentar resolver.
+
+---
+##### Referências:
+
+- modwsgiwiki - [https://code.google.com/p/modwsgi/wiki/](https://code.google.com/p/modwsgi/wiki/)
+- Blogalizado - [http://www.blogalizado.com.br/deploy-de-aplicacao-django-no-apache-com-mod_wsgi/](http://www.blogalizado.com.br/deploy-de-aplicacao-django-no-apache-com-mod_wsgi/)
+- Documentação oficial do django - [https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/modwsgi/](https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/modwsgi/)
\ No newline at end of file
diff --git a/content/configurando-python-3.5-openshift.md b/content/configurando-python-3.5-openshift.md
new file mode 100644
index 000000000..12e9b17d9
--- /dev/null
+++ b/content/configurando-python-3.5-openshift.md
@@ -0,0 +1,171 @@
+Title: Configurando OpenShift com Python 3.5 + Flask + Gunicorn
+Slug: configurando-python-3.5-openshift-flask-gunicorn
+Date: 2017-04-23 20:37:39
+Category: Python
+Tags: python,tutorial,install,cloud
+Author: Horácio Dias
+Email: horacio.dias92@gmail.com
+Gravatarid:57db1afcce141efc81193425d4a5bbf0
+Github: nenodias
+Linkedin: nenodias92
+Facebook: nenodias
+Summary: Tutorial básico de como configurar o python 3.5 com o openshift + flask + gunicorn, utilizando o diy (Do It Yourself), carregando um cartridge customizado ...
+
+Configurando OpenShift com Python 3.5
+
+### Introdução
+
+O [OpenShift](https://www.openshift.com/) é uma plataforma de PasS que possibilita aos desenvolvedores "subir" aplicações na nuvem de uma maneira simples e rápida. Ele funciona a partir de gears(engrenagens) que representam máquinas que irão rodar as aplicações. Dentro de cada gear é possível instalar serviços, os são chamados de "cartridges".
+
+Existem 3 planos:
+
++ Online (gratuito, com três gears)
++ Enterprise (pago com suporte)
++ Origin (versão da comunidade e pode ser utilizado livremente)
+
+Um problema que me deparei ao utilizar o Openshift é que ele não possui um cartridge com Python3.5. Porém existe uma forma um pouco mais complicada de resolver esse problema.
+
+Após fazer seu cadastro no OpenShift e instalar o [client tools](https://developers.openshift.com/managing-your-applications/client-tools.html) que contém as ferramentas necessárias para configurar nossa aplicação.
+
+Após tudo isso vamos colocar a mão na massa, abra seu terminal e vamos lá.
+
+### Criando a aplicação
+
+``` shell
+rhc app create https://raw.githubusercontent.com/Grief/openshift-cartridge-python-3.5/master/metadata/manifest.yml diy-0.1
+```
+Substituindo "``" pelo nome de sua aplicação.
+O arquivo manifest.yml criado por Changaco(github) e "forkeado" por Grief(github) contém as configurações de um cartridge customizado que contém o python 3.5.
+
+Para os curiosos o conteúdo do arquivo
+```
+---
+Name: python
+Cartridge-Short-Name: PYTHON
+Display-Name: Only Python
+Description: 'An embedded cartridge that provides only python, nothing else.'
+Version: '3.5.0'
+Versions: ['3.5.0', '2.7.11']
+License: The Python License
+License-Url: http://docs.python.org/3/license.html
+Vendor: python.org
+Cartridge-Version: 0.0.2
+Cartridge-Vendor: praisebetoscience
+Categories:
+- service
+- python
+- embedded
+Website: https://github.com/praisebetoscience/openshift-cartridge-python-3.5
+Help-Topics:
+ Developer Center: https://www.openshift.com/developers
+Provides:
+- python
+Publishes:
+Subscribes:
+ set-env:
+ Type: ENV:*
+ Required: false
+ set-doc-url:
+ Type: STRING:urlpath
+ Required: false
+Scaling:
+ Min: 1
+ Max: -1
+Version-Overrides:
+ '2.7.11':
+ Display-Name: Python 2.7
+ License: The Python License, version 2.7
+ Provides:
+ - python-2.7
+ - python
+ - python(version) = 2.7
+ '3.5.0':
+ Display-Name: Python 3.5
+ License: The Python License, version 3.5
+ Provides:
+ - python-3.5
+ - python
+ - python(version) = 3.5
+```
+
+Após isso sua aplicação já estárá executando, caso deseje acessar o endereço da mesma deverá ser http://``-.rhcloud.com.
+Você verá que a página do seu projeto não é nada mais do que o diy (Dot It Yourself), que é uma aplicação Ruby de exemplo que você pode alterar, e é o que vamos fazer.
+
+Se você acessar o diretório do seu projeto verá que existe um diretório ".openshift", dentro desse diretório existe um outro diretório chamado "action_hooks", e dentro desse diretório existem dois arquivos "start" e "stop".
+
++ "``/.openshift/action_hooks/start"
++ "``/.openshift/action_hooks/stop"
+
+Os dois arquivos são respectivamente os comandos para "subir" e "pausar" sua aplicação.
+
+### Flask
+Vamos criar um projeto de exemplo, bem simples, que apenas nos retorne a versão do python utilizada.
+Primeiramente vamos criar nosso requirements.txt, com gunicorn e o flask.
+
+"requirements.txt"
+```
+gunicorn
+flask
+```
+
+Depois disso vamos criar o arquivo app.py que conterá nossa aplicação.
+
+"app.py"
+```
+import sys
+from flask import Flask
+
+app = Flask(__name__)
+
+@app.route('/')
+def index():
+ return sys.version
+
+```
+ Após isso basta fazer o commit de suas alterações.
+
+ ``` shell
+ git add .
+ git commit -am 'Minhas alterações'
+ ```
+
+Após isso você verá que sua aplicação não está rodando, pois ainda não alteramos os arquivos "start" e "stop".
+
+### Configurando o Gunicorn no Start e Stop
+O projeto diy do openshift nos deixa uma variável de ambiente $OPENSHIFT_DIY_IP com o IP da máquina, dessa forma podemos passar a variável e porta ao gunicorn.
+
+"start"
+``` shell
+#!/bin/bash
+nohup $HOME/python/usr/bin/pip3 install -r $OPENSHIFT_REPO_DIR/requirements.txt
+cd $OPENSHIFT_REPO_DIR
+nohup $HOME/python/usr/bin/gunicorn app:app --bind=$OPENSHIFT_DIY_IP:8080 |& /usr/bin/logshifter -tag diy &
+```
+
+A primeira linha é o [Shebang](https://pt.wikipedia.org/wiki/Shebang), o que significa que esse arquivo será executado pelo bash.
+Na segunda linha vemos [nohup](https://pt.wikipedia.org/wiki/Nohup), que executa os comandos em uma sessão separada, vemos logo apóes vemos o uma chamada ao pip para instalar nossas dependências.
+Na terceira linha vemos o nohup, e depois o gunicorn inicializa nossa aplicação flask.
+
+Isso só funciona pois o cartridge customizado instala o python3.5 dentro da pasta home do servidor do openshift.
+
+"stop"
+``` shell
+#!/bin/bash
+source $OPENSHIFT_CARTRIDGE_SDK_BASH
+
+# The logic to stop your application should be put in this script.
+if [ -z "$(ps -ef | grep gunicorn | grep -v grep)" ]
+then
+ client_result "Application is already stopped"
+else
+ kill `ps -ef | grep gunicorn | grep -v grep | awk '{ print $2 }'` > /dev/null 2>&1
+fi
+```
+Podemos ver que o comando ps procura algum processo do gunicorn, caso ele exista o kill será chamado.
+
+Após isso, é só fazer o commit das alterações e você verá sua aplicação rodando.
+
+Espero que o post ajude a quem quer subir alguma aplicação com python3.5 e só tem o heroku como opção.
+
+### Referências
+- [Imaster](https://www.profissionaisti.com.br/2015/04/openshift-paas-de-verdade/)
diff --git a/content/criando-dict-a-partir-de-dois-ou-mais-dicts.md b/content/criando-dict-a-partir-de-dois-ou-mais-dicts.md
new file mode 100644
index 000000000..b07c0ef53
--- /dev/null
+++ b/content/criando-dict-a-partir-de-dois-ou-mais-dicts.md
@@ -0,0 +1,161 @@
+Title: Criando dicts a partir de outros dicts
+Slug: crie_dict-a-partir-de-outros-dicts
+Date: 2019-10-01 20:20:29
+Category: Python
+Tags: Python, Dict
+Author: Michell Stuttgart
+Email: michellstut@gmail.com
+Github: mstuttgart
+Linkedin: mstuttgart
+Site: https://mstuttgart.github.io
+Summary: Crie dicts a partir de outros dicts
+
+Neste tutorial, será abordado o processo de criação de um *dict* ou dicionário, a partir de um ou mais *dicts* em Python.
+
+Como já é de costume da linguagem, isso pode ser feito de várias maneiras diferentes.
+
+## Abordagem inicial
+
+Pra começar, vamos supor que temos os seguintes dicionários:
+
+```python
+dict_1 = {
+ 'a': 1,
+ 'b': 2,
+}
+
+dict_2 = {
+ 'b': 3,
+ 'c': 4,
+}
+```
+
+Como exemplo, vamos criar um novo dicionário chamado **new_dict** com os valores de **dict_1** e **dict_2** logo acima. Uma abordagem bem conhecida é utilizar o método *update*.
+
+```python
+new_dict = {}
+
+new_dcit.update(dict_1)
+new_dcit.update(dict_2)
+```
+
+Assim, temos que **new_dict** será:
+
+```python
+>> print(new_dict)
+{
+ 'a': 1,
+ 'b': 3,
+ 'c': 4,
+}
+```
+
+Este método funciona bem, porém temos de chamar o método *update* para cada *dict* que desejamos mesclar em **new_dict**. Não seria interessante se fosse possível passar todos os *dicts* necessários já na inicialização de **new_dict**?
+
+### Novidades do Python 3
+
+O Python 3 introduziu uma maneira bem interessante de se fazer isso, utilizando os operadores `**`.
+
+```python
+new_dict = {
+ **dict_1,
+ **dict_2,
+}
+
+```
+
+Assim, de maneira semelhante ao exemplo anterior, temos que **new_dict** será :
+
+```python
+>> print(new_dict['a'])
+1
+>> print(new_dict['b'])
+3
+>> print(new_dict['c'])
+4
+```
+
+## Cópia real de *dicts*
+
+Ao utilizamos o procedimento de inicialização acima, devemos tomar conseiderar alguns fatores. Apenas os valores do primeiro nível serão realmente duplicados no novo dicionário. Como exemplo, vamos alterar uma chave presente em ambos os *dicts* e verificar se as mesmas possuem o mesmo valor:
+
+```python
+>> dict_1['a'] = 10
+>> new_dict['a'] = 11
+>> print(dict_1['a'])
+10
+>> print(new_dict['a'])
+11
+```
+
+Porém isso muda quando um dos valores de **dict_1** for uma *list*, outro *dict* ou algum objeto complexo. Por exemplo:
+
+```python
+dict_3 = {
+ 'a': 1,
+ 'b': 2,
+ 'c': {
+ 'd': 5,
+ }
+}
+```
+
+e agora, vamos criar um novo *dict* a partir desse:
+
+```python
+new_dict = {
+ **dict_3,
+}
+
+```
+
+Como no exemplo anterior, podemos imaginar que foi realizado uma cópia de todos os elementos de **dict_3**, porém isso não é totalmente verdade. O que realmente aconteceu é que foi feita uma cópia *superficial* dos valores de **dict_3**, ou seja, apenas os valores de *primeiro nível* foram duplicados. Observe o que acontece quando alteramos o valor do *dict* presente na chave **c**.
+
+```python
+>> new_dict['c']['d'] = 11
+>> print(new_dict['c']['d'])
+11
+>> print(dict_3['c']['d'])
+11
+# valor anterior era 5
+
+```
+
+No caso da chave **c**, ela contem uma referência para outra estrutura de dados (um *dict*, no caso). Quando alteramos algum valor de **dict_3['c']**, isso reflete em todos os *dict* que foram inicializados com **dict_3**. Em outras palavras, deve-se ter cuidado ao inicializar um *dict* a partir de outros **dicts** quando os mesmos possuírem valores complexos, como *list*, *dict* ou outros objetos (os atributos deste objeto não serão duplicados).
+
+De modo a contornar este inconveniente, podemos utilizar o método *deepcopy* da *lib* nativa [copy](https://docs.python.org/2/library/copy.html). Agora, ao inicializarmos **new_dict**:
+
+```python
+import copy
+
+dict_3 = {
+ 'a': 1,
+ 'b': 2,
+ 'c': {
+ 'd': 5,
+ }
+}
+
+new_dict = copy.deepcopy(dict_3)
+```
+
+O método *deepcopy* realiza uma cópia recursiva de cada elemento de **dict_3**, resolvendo nosso problema. Veja mais um exemplo:
+
+```python
+>> new_dict['c']['d'] = 11
+>> print(new_dict['c']['d'])
+11
+>> print(dict_3['c']['d'])
+5
+# valor não foi alterado
+```
+
+## Conclusão
+
+Este artigo tenta demonstrar de maneira simples a criação de *dicts*, utilizando os diversos recursos que a linguagem oferece bem como os prós e contras de cada abordagem.
+
+## Referências
+
+Para mais detalhes e outros exemplos, deem uma olhada neste *post* do forum da Python Brasil [aqui](https://groups.google.com/forum/#!topic/python-brasil/OhUqYQ32M7E).
+
+É isso pessoal. Obrigado por ler!
diff --git a/content/criando-novos-comandos-no-django-admin.md b/content/criando-novos-comandos-no-django-admin.md
new file mode 100644
index 000000000..1d491774d
--- /dev/null
+++ b/content/criando-novos-comandos-no-django-admin.md
@@ -0,0 +1,440 @@
+title: Criando novos comandos no django-admin
+Slug: criando-novos-comandos-no-django-admin
+Date: 2015-12-03 22:00
+Tags: Python, Django
+Author: Regis da Silva
+Email: regis.santos.100@gmail.com
+Github: rg3915
+Twitter: rg3915
+Category: Django
+
+Veja aqui como criar o seu próprio comando para ser usado com o django-admin ou manage.py do Django.
+
+O [django-admin ou manage.py][1] já tem um bocado de comandos interessantes, os mais utilizados são:
+
+* [startproject][4] - cria novos projetos.
+* [startapp][5] - cria novas apps.
+* [makemigrations][6] - cria novas migrações baseadas nas mudanças detectadas nos modelos Django.
+* [migrate][7] - sincroniza o banco de dados com as novas migrações.
+* [createsuperuser][8] - cria novos usuários.
+* [test][9] - roda os testes da aplicação.
+* [loaddata][14] - carrega dados iniciais a partir de um json, por exemplo, `python manage.py loaddata fixtures.json`
+* [shell][15] - inicializa um interpretador Python interativo.
+* [dbshell][18] - acessa o banco de dados através da linha de comando, ou seja, você pode executar comandos sql do banco, por exemplo, diretamente no terminal.
+* [inspectdb][16] - retorna todos os modelos Django que geraram as tabelas do banco de dados.
+* [runserver][17] - roda o servidor local do projeto Django.
+
+Mas de repente você precisa criar um comando personalizado conforme a sua necessidade. A palavra chave é `BaseCommand` ou [Writing custom django-admin commands][2].
+
+## Começando do começo
+
+> Importante: estamos usando Django 1.8.12 e Python 3.
+
+### Criando o projeto
+
+Eu usei esta sequência de comandos para criar o projeto.
+
+```bash
+# Criando djangoproject.
+mkdir djangoproject
+cd djangoproject
+
+# Criando virtualenv
+virtualenv -p python3 .venv
+
+# Ativando o .venv.
+source .venv/bin/activate
+# Diminuindo o caminho do prompt (opcional)
+PS1="(`basename \"$VIRTUAL_ENV\"`)\e[1;34m:/\W\033[00m$ "
+
+# Instalando o Django
+pip install django==1.8.12
+pip freeze > requirements.txt
+
+# Criando o projeto myproject ...
+django-admin.py startproject myproject .
+cd myproject
+
+# Criando a app 'core' ...
+python ../manage.py startapp core
+cd ..
+
+# Editando settings.py"
+sed -i "/django.contrib.staticfiles/a\ 'myproject.core'," myproject/settings.py
+```
+
+Pronto! Agora nós já temos um projetinho Django funcionando. Note que o nome da app é **core**.
+
+### Criando as pastas
+
+Para criarmos um novo comando precisamos das seguintes pastas:
+
+ core
+ ├── management
+ │ ├── __init__.py
+ │ ├── commands
+ │ │ ├── __init__.py
+ │ │ ├── novocomando.py
+
+No nosso caso, teremos 3 novos comandos, então digite, estando na pasta `djangoproject`
+
+```bash
+mkdir -p core/management/commands
+touch core/management/__init__.py
+touch core/management/commands/{__init__.py,hello.py,initdata.py,search.py}
+```
+
+
+## Sintaxe do novo comando
+
+> Importante: estamos usando Django 1.8.12 e Python 3.
+
+O Django 1.8 usa o `argparse` como parser de argumentos do `command`, mais informações em [module-argparse][19].
+
+```python
+from django.core.management.base import BaseCommand, CommandError
+from optparse import make_option
+
+class Command(BaseCommand):
+ help = 'Texto de ajuda aqui.'
+ option_list = BaseCommand.option_list + (
+ make_option('--awards', '-a',
+ action="http://github.com/store_true",
+ help='Ajuda da opção aqui.'),
+ )
+
+ def handle(self, **options):
+ self.stdout.write('Hello world.')
+ if options['awards']:
+ self.stdout.write('Awards')
+```
+
+Entendeu? Basicamente o `handle` é a função que executa o comando principal, no caso o `self.stdout.write('Hello world.')`, ou seja, se você digitar o comando a seguir ele imprime a mensagem na tela.
+
+```bash
+$ python manage.py hello
+Hello World
+```
+
+`--awards` é um argumento opcional, você também pode digitar `-a`.
+
+```bash
+$ python manage.py hello -a
+Hello World
+Awards
+```
+
+`action="http://github.com/store_true"` significa que ele armazena um valor verdadeiro.
+
+**Obs**: A partir do Django 1.8 os comandos de argumentos opcionais são baseados em `**options`.
+
+
+Veja uma outra forma de escrever
+
+```python
+from django.core.management.base import BaseCommand, CommandError
+
+class Command(BaseCommand):
+
+ def add_arguments(self, parser):
+ # Argumento nomeado (opcional)
+ parser.add_argument('--awards', '-a',
+ action='http://github.com/store_true',
+ help='Ajuda da opção aqui.')
+
+ def handle(self, *args, **options):
+ self.stdout.write('Hello world.')
+ if options['awards']:
+ self.stdout.write('Awards')
+```
+
+A diferença é que aqui usamos `parser.add_argument` ao invés de `make_option`.
+
+### hello.py
+
+```python
+from django.core.management.base import BaseCommand, CommandError
+# minimalista
+class Command(BaseCommand):
+ help = 'Print hello world'
+
+ def handle(self, **options):
+ self.stdout.write('Hello World')
+```
+
+**Uso**
+
+```bash
+$ python manage.py hello
+```
+
+### initdata.py
+
+**Objetivo**: Obter alguns filmes de uma api e salvar os dados no banco.
+
+**api**: [omdbapi.com][3]
+
+**models.py**
+
+```python
+from django.db import models
+
+class Movie(models.Model):
+ title = models.CharField(u'título', max_length=100)
+ year = models.PositiveIntegerField('ano', null=True, blank=True)
+ released = models.CharField(u'lançamento', max_length=100, default='', blank=True)
+ director = models.CharField('diretor', max_length=100, default='', blank=True)
+ actors = models.CharField('atores', max_length=100, default='', blank=True)
+ poster = models.URLField('poster', null=True, blank=True)
+ imdbRating = models.DecimalField(max_digits=6, decimal_places=2, null=True, blank=True)
+ imdbID = models.CharField(max_length=50, default='', blank=True)
+
+ class Meta:
+ ordering = ['title']
+ verbose_name = 'filme'
+ verbose_name_plural = 'filmes'
+
+ def __str__(self):
+ return self.title
+```
+
+Não se esqueça de fazer
+
+```bash
+python manage.py makemigrations
+python manage.py migrate
+```
+
+**admin.py**
+
+Vamos visualizar pelo admin.
+
+```python
+from django.contrib import admin
+from core.models import Movie
+
+admin.site.register(Movie)
+```
+
+Instale o `requests`
+
+```bash
+pip install requests
+```
+
+**initdata.py**
+
+O código a seguir é longo, mas basicamente temos
+
+* `print_red(name)` função que imprime um texto em vermelho (opcional)
+* `get_html(year)` função que lê os dados da api usando [requests][20], e depois escolhe um filme randomicamente a partir de 2 letras
+* `get_movie(year)` se o dicionário conter `{'Response': 'True', ...}` então retorna um dicionário do filme localizado
+* `save()` salva os dados no banco
+* `handle(movies, year)` este é o comando principal. Busca os filmes várias vezes, conforme definido pela variável `movies`, e salva os n filmes.
+
+
+```python
+# -*- coding: utf-8 -*- #
+
+import random
+import string
+import requests
+from django.core.management.base import BaseCommand, CommandError
+from django.core.exceptions import ValidationError
+from optparse import make_option
+from core.models import Movie
+
+
+class Command(BaseCommand):
+ help = """Faz o crawler numa api de filmes e retorna os dados.
+ Uso: python manage.py initdata
+ ou: python manage.py initdata -m 20
+ ou: python manage.py initdata -m 20 -y 2015"""
+ option_list = BaseCommand.option_list + (
+ make_option('--movies', '-m',
+ dest='movies',
+ default=10,
+ help='Define a quantidade de filmes a ser inserido.'),
+ make_option('--year', '-y',
+ dest='year',
+ action='http://github.com/store',
+ default=None,
+ help='Define o ano de lançamento do filme.'),
+ )
+
+ def print_red(self, name):
+ """imprime em vermelho"""
+ print("\033[91m {}\033[00m".format(name))
+
+ def get_html(self, year):
+ """
+ Le os dados na api http://www.omdbapi.com/ de forma aleatoria
+ e escolhe um filme buscando por 2 letras
+ """
+
+ # Escolhe duas letras aleatoriamente
+ letters = ''.join(random.choice(string.ascii_lowercase) for _ in range(2))
+
+ # Se não for definido o ano, então escolhe um randomicamente
+ if year is None:
+ year = str(random.randint(1950, 2015))
+ url = 'http://www.omdbapi.com/?t={letters}*&y={year}&plot=short&r=json'.format(letters=letters, year=str(year))
+ return requests.get(url).json()
+
+ def get_movie(self, year, **kwargs):
+ """ Retorna um dicionário do filme """
+
+ movie = self.get_html(year)
+ j = 1 # contador
+
+ # Faz a validação de Response. Se a resposta for falsa, então busca outro filme.
+ while movie['Response'] == 'False' and j < 100:
+ movie = self.get_html(year)
+ self.print_red('Tentanto %d vezes\n' % j)
+ j += 1
+ return movie
+
+ def save(self, **kwargs):
+ """SALVA os dados"""
+ try:
+ Movie.objects.create(**kwargs)
+ except ValidationError as e:
+ self.print_red(e.messages)
+ self.print_red('O objeto não foi salvo.\n')
+
+ def handle(self, movies, year, **options):
+ """ se "movies" não for nulo, transforma em inteiro """
+
+ self.verbosity = int(options.get('verbosity'))
+
+ if movies is not None:
+ movies = int(movies)
+
+ # busca os filmes n vezes, a partir da variavel "movies"
+ for i in range(movies):
+ # verifica as validações
+ m = self.get_movie(year)
+ if m['imdbRating'] == "N/A":
+ m['imdbRating'] = 0.0
+
+ # Transforma "year" em inteiro
+ if "–" in m['Year']:
+ m['Year'] = year
+
+ data = {
+ "title": m['Title'],
+ "year": m['Year'],
+ "released": m['Released'],
+ "director": m['Director'],
+ "actors": m['Actors'],
+ "poster": m['Poster'],
+ "imdbRating": m['imdbRating'],
+ "imdbID": m['imdbID'],
+ }
+
+ self.save(**data)
+
+ if self.verbosity > 0:
+ self.stdout.write('\n {0} {1} {2}'.format(i + 1, data['year'], data['title']))
+ if self.verbosity > 0:
+ self.stdout.write('\nForam salvos %d filmes' % movies)
+```
+
+**Uso**
+
+```bash
+Usage: python manage.py initdata [options]
+
+Faz o crawler numa api de filmes e retorna os dados.
+ Uso: python manage.py initdata
+ ou: python manage.py initdata -m 20
+ ou: python manage.py initdata -m 20 -y 2015
+```
+
+
+### search.py
+
+**Objetivo**: Localizar o filme pelo título ou ano de lançamento.
+
+```python
+from django.core.management.base import BaseCommand, CommandError
+from optparse import make_option
+from core.models import Movie
+
+
+class Command(BaseCommand):
+ help = """Localiza um filme pelo título ou ano de lançamento.
+ Uso: python manage.py search -t 'Ted 2'
+ ou: python manage.py search -y 2015
+ ou: python manage.py search -t 'a' -y 2015"""
+
+ option_list = BaseCommand.option_list + (
+ make_option('--title', '-t',
+ dest='title',
+ default=None,
+ help='Localiza um filme pelo título.'),
+ make_option('--year', '-y',
+ dest='year',
+ default=None,
+ help='Localiza um filme pelo ano de lançamento.'),
+ )
+
+ def handle(self, title=None, year=None, **options):
+ """ dicionário de filtros """
+ self.verbosity = int(options.get('verbosity'))
+
+ filters = {
+ 'title__istartswith': title,
+ 'year': year
+ }
+
+ filter_by = {key: value for key, value in filters.items() if value is not None}
+ queryset = Movie.objects.filter(**filter_by)
+
+ if self.verbosity > 0:
+ for movie in queryset:
+ self.stdout.write("{0} {1}".format(movie.year, movie.title))
+ self.stdout.write('\n{0} filmes localizados.'.format(queryset.count()))
+```
+
+**Uso**
+
+```bash
+Usage: python manage.py search [options]
+
+Localiza um filme pelo título ou ano de lançamento.
+ Uso: python manage.py search -t 'Ted 2'
+ ou: python manage.py search -y 2015
+ ou: python manage.py search -t 'a' -y 2015
+```
+
+
+[Aqui][10] tem um exemplo legal que eu usei como ideia pra fazer este post.
+
+Mais algumas referências:
+
+[Writing custom django-admin commands][2]
+
+[Zachary Voase: Fixing Django Management Commands][11]
+
+[Adding Custom Commands to manage.py and django-admin.py by dave][12]
+
+[1]: https://docs.djangoproject.com/en/1.8/ref/django-admin/
+[2]: https://docs.djangoproject.com/en/1.8/howto/custom-management-commands/
+[3]: http://www.omdbapi.com/
+[4]: https://docs.djangoproject.com/en/1.8/ref/django-admin/#startproject-projectname-destination
+[5]: https://docs.djangoproject.com/en/1.8/ref/django-admin/#startapp-app-label-destination
+[6]: https://docs.djangoproject.com/en/1.8/ref/django-admin/#makemigrations-app-label
+[7]: https://docs.djangoproject.com/en/1.8/ref/django-admin/#migrate-app-label-migrationname
+[8]: https://docs.djangoproject.com/en/1.8/ref/django-admin/#createsuperuser
+[9]: https://docs.djangoproject.com/en/1.8/ref/django-admin/#test-app-or-test-identifier
+[10]: https://github.com/rhblind/django-gcharts/blob/master/demosite/management/commands/initdata.py
+[11]: http://zacharyvoase.com/2009/12/09/django-boss/
+[12]: http://thingsilearned.com/2009/03/13/adding-custom-commands-to-managepy-and-django-adminpy/
+[14]: https://docs.djangoproject.com/en/1.8/ref/django-admin/#loaddata-fixture-fixture
+[15]: https://docs.djangoproject.com/en/1.8/ref/django-admin/#shell
+[16]: https://docs.djangoproject.com/en/1.8/ref/django-admin/#inspectdb
+[17]: https://docs.djangoproject.com/en/1.8/ref/django-admin/#runserver-port-or-address-port
+[18]: https://docs.djangoproject.com/en/1.8/ref/django-admin/#dbshell
+[19]: https://docs.python.org/2/library/argparse.html#module-argparse
+[20]: http://docs.python-requests.org/en/latest/
\ No newline at end of file
diff --git a/content/curso-asyncio-aula00.rst b/content/curso-asyncio-aula00.rst
new file mode 100644
index 000000000..5c10a5be4
--- /dev/null
+++ b/content/curso-asyncio-aula00.rst
@@ -0,0 +1,28 @@
+Curso Python asyncio: Aula 00 - Introdução ao módulo asyncio
+############################################################
+
+:date: 2016-06-03 17:30
+:tags: python, asyncio
+:category: Tutoriais
+:slug: curso-asyncio-aula00
+:author: Carlos Maniero
+:email: carlosmaniero@gmail.com
+:github: carlosmaniero
+:twitter: carlosmaniero
+:linkedin: carlosmaniero
+:facebook: carlosmaniero
+
+
+Primeira aula do curso de asyncio.
+Nessa vídeo aula são abordadas as principais diferenças entre Concorrência e Paralelismo.
+
+Slides:
+http://carlosmaniero.github.io/curso-asyncio/aula00/
+
+GitHub:
+http://github.com/carlosmaniero/
+http://github.com/carlosmaniero/curso-asyncio
+
+http://carlosmaniero.github.io/
+
+.. youtube:: xGoEpCaachs
diff --git a/content/curso-asyncio-aula1.rst b/content/curso-asyncio-aula1.rst
new file mode 100644
index 000000000..962a2ba4d
--- /dev/null
+++ b/content/curso-asyncio-aula1.rst
@@ -0,0 +1,30 @@
+Curso Python asyncio: Aula 01 - Iterators e Generators
+######################################################
+
+:date: 2016-06-11 12:30
+:tags: python, asyncio
+:category: Tutoriais
+:slug: curso-asyncio-aula1
+:author: Carlos Maniero
+:email: carlosmaniero@gmail.com
+:github: carlosmaniero
+:twitter: carlosmaniero
+:linkedin: carlosmaniero
+:facebook: carlosmaniero
+
+
+Entendendo o conceito de Iterator e Generator.
+
+Primeira Aula:
+https://www.youtube.com/watch?v=xGoEpCaachs
+
+Slides:
+http://carlosmaniero.github.io/curso-asyncio/aula01/
+
+GitHub:
+http://github.com/carlosmaniero/
+http://github.com/carlosmaniero/curso-asyncio
+
+http://carlosmaniero.github.io/
+
+.. youtube:: jksHpfsarfc
diff --git a/content/debugging-logging.md b/content/debugging-logging.md
new file mode 100644
index 000000000..48c16c2b4
--- /dev/null
+++ b/content/debugging-logging.md
@@ -0,0 +1,66 @@
+Title: Debugging - logging
+Date: 2016-11-27 17:48
+Tags: python, debugging, logging
+Category: Python
+Slug: debugging-logging
+Author: Bruno Santana
+Email: santanasta@gmail.com
+Github: BrunoLSA
+
+
+Achei algo interessante no livro que estou lendo (Automatize tarefas maçantes com Python) e resolvi compartilhar.
+
+Trata-se do Logging, que ajuda no debug do programa.
+
+Vejam o exemplo nesse programa, com falha:
+
+ :::python
+ import logging
+ logging.basicConfig(level=logging.DEBUG, format=' %(asctime)s - %(levelname)s - %(message)s')
+
+
+ logging.debug('Start of program')
+
+ def factorial(n):
+ logging.debug('Start of factorial(%s%%)' % (n))
+ total = 1
+ for i in range(n+1):
+ total *= i
+ logging.debug('i is ' + str(i) + ', total is ' + str(total))
+ logging.debug('End of factorial(%s%%)' % (n))
+ return total
+
+ print(factorial(5))
+ logging.debug('End of program')
+
+
+O programa retorna:
+
+ :::python
+ 2016-11-15 16:17:30,339 - DEBUG - Start of program
+ 2016-11-15 16:17:30,340 - DEBUG - Start of factorial(5%)
+ 2016-11-15 16:17:30,340 - DEBUG - i is 0, total is 0
+ 2016-11-15 16:17:30,340 - DEBUG - i is 1, total is 0
+ 2016-11-15 16:17:30,340 - DEBUG - i is 2, total is 0
+ 2016-11-15 16:17:30,340 - DEBUG - i is 3, total is 0
+ 2016-11-15 16:17:30,340 - DEBUG - i is 4, total is 0
+ 2016-11-15 16:17:30,340 - DEBUG - i is 5, total is 0
+ 2016-11-15 16:17:30,340 - DEBUG - End of factorial(5%)
+ 2016-11-15 16:17:30,340 - DEBUG - End of program
+ 0
+
+Dessa forma, podemos ver o passo a passo que o programa está realizando e identificar onde está o erro. No caso, vemos que para corrigir o problema, devemos alterar o **for i in range(n+1):** para **for i in range(1, n+1):**.
+Quando o desenvolvedor não quiser mais visualizar as mensagens de logging, basta chamar **logging.disable(logging.CRITICAL)** logo embaixo do **import logging**. Essa função faz com que não seja necessário alterar o programa removendo todas as chamadas de logging manualmente.
+
+Também é possível gravar as mensagens de log num arquivo, ao invés de mostrá-las na tela. A função aceita o argumento **filename**.
+
+ :::python
+ import logging
+ logging.basicConfig(filename='myProgramLog.txt', level=logging.DEBUG, format=' %(asctime)s - %(levelname)s - %(message)s')
+
+
+Lado negativo do uso dessa função: a leitura do código fica difícil, por causa desse monte de logging.debug no meio do código. Para evitar isso, pode-se usar um decorator.
+
+
+
+
diff --git a/content/deploy-com-dokku.md b/content/deploy-com-dokku.md
new file mode 100644
index 000000000..2fcfb097a
--- /dev/null
+++ b/content/deploy-com-dokku.md
@@ -0,0 +1,144 @@
+Title: Deploy rápido e simples com Dokku
+Slug: deploy-rapido-simples-com-dokku
+Date: 2016-11-23 17:30
+Tags: python,deploy,django,dokku
+Author: Júnior Carvalho
+Email: joseadolfojr@gmail.com
+Github: juniorcarvalho
+twitter: @joseadolfojr
+Linkedin: juniorcarvalhorj
+Category: Python, Deploy
+
+Sempre busquei alternativas para deploy simples como o heroku. Vou mostrar neste passo-a-passo uma forma simples e rápida utilizando o [Dokku].
+
+[Dokku] é a menor implementação PaaS que você já viu. De uma forma simples e rápida consegue-se configurar um servidor para deploy. Se existe alguma dúvida sobre PaaS, SaaS, etc., uma pesquisa rápida no google vai retornar várias referências.
+
+Nesse exemplo vou utilizar uma vps básica na [DigitalOcean]. Uma máquina 512 MB / 20 Gb/ 1 CPU, rodando Ubuntu Server 16.04.1. É possível criar uma máquina já com [Dokku] instalado e pré-configurado mas vou fazer com uma máquina 'limpa' para servir de base para outras vps.
+### Instalando
+Com o servidor em execução vamos acessar via ssh pelo terminal. No caso de utilizar OS Windows, utilize o [putty] para acessar o servidor.
+
+```
+ssh [ip-do-servidor] -l root
+```
+No primeiro acesso confirme com yes o questionamento sobre a autenticidade. No caso da [DigitalOcean] vai ser solicitado a mudança de senha nesse primeiro acesso.
+
+Seguindo a documentação do [Dokku] vamos realizar a instação. Este processo demora +- uns 10 minutos.
+```
+wget https://raw.githubusercontent.com/dokku/dokku/v0.7.2/bootstrap.sh
+sudo DOKKU_TAG=v0.7.2 bash bootstrap.sh
+```
+Finalizado o script de instação vamos adicionar a chave publica de nosso usuário de desenvolvimento para conseguir fazer o deploy no servidor recem criado.
+### Chaves
+Na nossa máquina de desenvolvimento, vamos checar se nosso usuário tem uma chave pública:
+```
+ls -al ~/.ssh
+```
+Por padrão os arquivos das chaves públicas podem ser:
+
+ - id_dsa.pub
+ - id_ecdsa.pub
+ - id_ed25519.pub
+ - id_rsa.pub
+
+
+No meu caso eu tenho o id_rsa.pub, então vou ler o conteúdo, seleciona-lo e copiar:
+```
+cat ~/.ssh/id_rsa.pub
+```
+
+Para gerar a chave: (caso não exista nenhum arquivo .pub na pasta ~/.ssh)
+```
+ssh-keygen -t rsa
+```
+Aceite as três opções pedidas por default. (não inserir password)
+Para OS Windows achei este artigo. [ssh Windows] (não testei)
+### Inserindo a chave pública no servidor
+Com nossa chave pública copiada (ctrl+c) vamos abrir o browser e digitar o ip do nosso servidor. Vai aparecer uma tela como a da imagem a seguir:
+
+
+
+
+No campo Public Key, colamos nossa chave (Ctrl+V) e depois é so clicar em Finish Setup. Feito isto você vai ser redirecionado para página da documentação do [Dokku].
+### Criando nossa APP
+No terminal, conectado no nosso servidor:
+```
+dokku apps:create [nome-app]
+```
+No meu caso:
+```
+dokku apps:create fjfundo
+```
+Para listar as apps existentes:
+```
+dokku apps
+```
+Quando você cria um novo aplicativo, por padrão o [dokku] nao fornece nenhum banco de dados como MySql ou PostgreSQL. É preciso instalar plugins. [Dokku] tem plugins oficiais para banco de dados. Neste passo-a-passo vou utilizar o PostgreSQL.
+
+### Instalando o plugin postgres e configurando o serviço de banco de dados
+No terminal, conectado no nosso servidor:
+```
+sudo dokku plugin:install https://github.com/dokku/dokku-postgres.git
+```
+Criando o serviço postgres:
+
+```
+dokku postgres:create [nome-servico]
+```
+No meu caso:
+```
+dokku postgres:create fjfundo-database
+```
+Vamos criar o link entre os serviços de banco de dados e nossa app.
+```
+dokku postgres:link [nome-servico] [nome-app]
+```
+No meu caso:
+```
+dokku postgres:link fjfundo-database fjfundo
+```
+
+### Configurando e executando nosso primeiro deploy.
+Na nossa máquina cliente vamos configurar o git para fazer o primeiro deploy.
+Vamos adicionar nosso repositorio remoto dokku da seguinte forma:
+```
+git remote add dokku dokku@[ip-do-servidor]:[nome-app]
+```
+No meu caso:
+```
+git remote add dokku dokku@xxx.xxx.xxx.xxx:fjfundo
+```
+No meu caso antes de fazer o deploy eu tenho que configurar algumas variáveis de ambiente como DEBUG e SECRET_KEY. Para esta configuração executo os comandos no servidor. Vou seguir a documentação existente em [Environment Variables]
+```
+dokku config:set fjfundo DEBUG='False'
+dokku config:set fjfundo SECRET_KEY='sua secret_key'
+```
+Para listar as variáveis de ambiente de nossa app:
+```
+dokku config [nome da app]
+```
+Feito isto vamos ao nosso primeiro deploy: ( na nossa máquina cliente/dev)
+```
+git push dokku master --force
+```
+Pronto! Agora é so acessar nossa aplicação. No final do deploy o [dokku] mostra a url de conexão. Caso precise obter a url use o comando no servidor:
+```
+dokku url [nome-app]
+```
+### Criando as tabelas do banco de dados
+
+Nossa app está no ar mais ainda não tem as tabelas de nossa base de dados.
+```
+dokku run fjfundo python manage.py migrate
+```
+Troque fjfundo pelo nome da sua app.
+
+### Considerações finais
+Ainda estou estudando e aprendendo a utilizar o [dokku]. Utilizo apenas em ambiente de desenvolvimento mas pretendo utilizar em produção.
+Ainda tenho muito que aprender e fica aqui meu email, joseadolfojr@gmail.com, para quem tiver alguma dúvida e quizer também contribuir para meus estudos.
+
+
+[Dokku]: http://dokku.viewdocs.io/dokku/
+[DigitalOcean]: https://m.do.co/c/2f45101e7ccf
+[putty]:http://www.putty.org/
+[ssh Windows]: http://adrianorosa.com/blog/seguranca/como-criar-ssh-key-pair-windows.html
+[Environment Variables]: http://dokku.viewdocs.io/dokku/configuration/environment-variables/
\ No newline at end of file
diff --git a/content/desenvolvendo-para-google-app-engine-com-tekton.md b/content/desenvolvendo-para-google-app-engine-com-tekton.md
new file mode 100644
index 000000000..154cf7085
--- /dev/null
+++ b/content/desenvolvendo-para-google-app-engine-com-tekton.md
@@ -0,0 +1,65 @@
+Title: Criação de aplicações no Google App Engine com o Tekton
+Slug: desenvolvendo-para-google-app-engine-com-tekton
+Date: 24-05-2015
+Tags: tekton, python, google app engine, tutorial
+Author: Guido Luz Percú
+Email: guidopercu@gmail.com
+Github: GuidoBR
+Site: http://www.guidopercu.com.br/
+Twitter: oumguido
+Category: google app engine
+
+## Google App Engine (GAE)
+É a plataforma de Cloud Computing do Google, com ela você pode desenvolver e hospedar aplicações usando Python (2.7) que escalam facilmente, [pagando muito pouco por isso].
+
+As desvantagens (em relação a outras plataformas de nuvem, como o Heroku por exemplo) são:
+- Você terá que desenvolver pensando na plataforma (banco de dados NoSQL, por isso o [Django não é recomendável].).
+- Versão do Python é antiga e não há planos para mudar isso no momento.
+
+## Tekton
+É um framework para desenvolvimento Web especialmente pensado para uso no Google App Engine. Nele podemos aproveitar o melhor do Django (scaffold, código HTML e validação de formulários a partir de modelos, apps isoladas) sem perder as vantagens que o GAE nos oferece.
+
+## Como iniciar
+O primeiro passo é baixar o [SDK do Google App Engine], com isso pronto podemos começar a conhecer o Tekton.
+
+Em seguida, vamos baixar a aplicação template.
+```
+$ wget https://github.com/renzon/tekton/archive/master.zip
+$ unzip master && rm master.zip
+$ mv tekton-master projeto_appengine && cd projeto_appengine
+```
+Nesse ponto podemos explorar e conhecer a estrutura de diretórios.
+```
+└── backend
+ ├── appengine
+ ├── apps
+ ├── build_scripts
+ ├── test
+ └── venv
+```
+
+```
+$ cd backend/venv/ && ./venv.sh
+$ source ./bin/activate
+```
+Com o ambiente virtual pronto, tudo deve estar funcionando. Para testar,
+vamos utilizar o próprio servidor que vem com o pacote antes de subir parao GAE.
+
+```
+cd ../appengine && dev_appserver.py .
+```
+
+Tudo certo! Você deve estar vendo o projeto template no seu localhost:8080
+
+Para realizar o deploy no App Engine:
+
+```
+appcfg.py update . --oauth2
+```
+Você pode conhecer mais sobre o projeto no [Github], no [grupo de discussões] ou nas vídeo aulas gratuitas no [Youtube].
+[SDK do Google App Engine]:https://cloud.google.com/appengine/downloads
+[pagando muito pouco por isso]:https://cloud.google.com/appengine/pricing
+[Django não é recomendável]:http://imasters.com.br/desenvolvimento/app-engine-e-django-hell/
+[Youtube]:https://www.youtube.com/playlist?list=PLA05yVJtRWYRGIeBxag8uT-3ftcMVT5oF
+[Github]:https://github.com/renzon/tekton
+[grupo de discussões]:https://groups.google.com/forum/#!forum/tekton-web
diff --git a/content/diferenca-operadores.md b/content/diferenca-operadores.md
new file mode 100644
index 000000000..bac3444b3
--- /dev/null
+++ b/content/diferenca-operadores.md
@@ -0,0 +1,109 @@
+Title: Diferença entre == e is
+Slug: diferenca-operadores
+Date: 2015-11-27 14:00
+Tags: python,dica,operadores
+Author: Gildásio Júnior
+Email: gjuniioor@protonmail.ch
+Github: gjuniioor
+Site: https://gjuniioor.github.io
+Category: Python
+Status: draft
+
+Diferença entre == e is
+-----------
+
+Olá, galera, tudo tranquilo?
+
+Estava conversando com um amigo que está estudando python e tudo mais, e então ele veio com a seguinte dúvida:
+
+> Qual a diferença entre == e is no python?
+
+Para quem não sabe, no python tem o operador *is* que "tem a mesma função do *==*. Veja:
+
+```python
+>>> x = 10
+>>> y = 10
+>>> x == y
+True
+>>> x is y
+True
+```
+
+Mas...
+
+```python
+>>> x = 1000
+>>> y = 1000
+>>> x == y
+True
+>>> x is y
+False
+```
+
+Viu só? Pois bem, o que que acontece então??
+
+O python tem um mecanismo interessante nesse ponto... Quando se tratam de *coisas pequenas* ele utiliza de ponteiros para apontar outros rótulos para um mesmo endereço de memória. Quando o que é armazenado na variável já começa a crescer, fica maior e tal, ele já não usa disso, para não pesar, mas sim de outro endereço ...
+
+Seria um cache que ele faz de alguns tipos de objetos, entre eles estão int e string, por exemplo. Float e dicionário já não são assim.
+
+Para ter uma ideia melhor disso, vamos ver os endereços que as variáveis ocupam e o resultado da comparação:
+
+```python
+>>> x = 10
+>>> y = 10
+>>> hex(id(x))
+'0x98a1844'
+>>> hex(id(y))
+'0x98a1844'
+>>> x == y
+True
+>>> x is y
+True
+```
+
+Perceba que ele pega o mesmo endereço ... Agora, se colocarmos valores maiores:
+
+```python
+>>> x = 1000
+>>> y = 1000
+>>> hex(id(x))
+'0x98e5520'
+>>> hex(id(y))
+'0x98e5508'
+>>> x == y
+True
+>>> x is y
+False
+```
+
+Ou seja, o *is* (como a tradução mostra) vai verificar se algo é aquilo a que a comparação está se referindo, ou seja, se são a mesma coisa. Já o *==* vai analisar se são iguais, assim como o esperado.
+
+Mais algumas demonstrações:
+
+* Float:
+
+```python
+>>> x = 1.0
+>>> y = 1.0
+>>> x == y
+True
+>>> x is y
+False
+```
+
+* Dict:
+
+```python
+>>> x = [1]
+>>> y = [1]
+>>> x == y
+True
+>>> x is y
+False
+```
+
+> Não há motivo para me aprofundar tanto aqui, é apenas uma dica rápida. Espero que resolva os problemas de dúvidas de quem necessitar ... Tem mais algum caso como esse? Quer tirar alguma dúvida do tipo? Comenta ai ou entra em contato (olha no topo da página).
+
+Vlw pessoal, até mais ver!!
+
+> Texto originalmente postado aqui: [https://gjuniioor.github.io/blog/python-diferenca-operadores/](https://gjuniioor.github.io/blog/python-diferenca-operadores/).
diff --git a/content/django-ci-github-actions.rst b/content/django-ci-github-actions.rst
new file mode 100644
index 000000000..fb4bbdbab
--- /dev/null
+++ b/content/django-ci-github-actions.rst
@@ -0,0 +1,21 @@
+Criando um CI de uma aplicação Django usando Github Actions
+###########################################################
+
+:date: 2020-01-24 12:10
+:tags: python, django, github actions
+:category: Python
+:slug: django-ci-github-actions
+:author: Lucas Magnum
+:email: lucasmagnumlopes@gmail.com
+:github: lucasmagnum
+:linkedin: lucasmagnum
+
+Fala pessoal, tudo bom?
+
+Nos vídeo abaixo vou mostrar como podemos configurar um CI de uma aplicação Django usando Github Actions.
+
+`https://www.youtube.com/watch?v=KpSlY8leYFY `_.
+
+
+.. youtube:: KpSlY8leYFY
+
diff --git a/content/django-rest-framework-class-based-views.md b/content/django-rest-framework-class-based-views.md
new file mode 100644
index 000000000..c5dac7958
--- /dev/null
+++ b/content/django-rest-framework-class-based-views.md
@@ -0,0 +1,155 @@
+title: Django Rest Framework - #3 Class Based Views
+Slug: django-rest-framework-class-based-views
+Date: 2018-02-15 23:00
+Tags: Python, Django, REST
+Author: Regis da Silva
+Email: regis.santos.100@gmail.com
+Github: rg3915
+Twitter: rg3915
+Category: Python, Django, REST
+
+* 0 - [Quickstart][10]
+* 1 - [Serialization][11]
+* 2 - [Requests & Responses][12]
+* 3 - **Class based views**
+
+Este post é continuação do post [Django Rest Framework Requests & Responses][12].
+
+Finalmente chegamos as views baseadas em classes. A grande vantagem é que com poucas linhas de código já temos nossa API pronta.
+
+Veja como fica a [views.py](https://github.com/rg3915/drf/blob/b0aa989ffc756e6dc5f65e172dfb43d47127d743/core/views.py):
+
+```python
+from django.http import Http404
+from rest_framework.views import APIView
+from rest_framework.response import Response
+from rest_framework import status
+from core.models import Person
+from core.serializers import PersonSerializer
+
+
+class PersonList(APIView):
+ """
+ List all persons, or create a new person.
+ """
+
+ def get(self, request, format=None):
+ persons = Person.objects.all()
+ serializer = PersonSerializer(persons, many=True)
+ return Response(serializer.data)
+
+ def post(self, request, format=None):
+ serializer = PersonSerializer(data=request.data)
+ if serializer.is_valid():
+ serializer.save()
+ return Response(serializer.data, status=status.HTTP_201_CREATED)
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+
+class PersonDetail(APIView):
+ """
+ Retrieve, update or delete a person instance.
+ """
+
+ def get_object(self, pk):
+ try:
+ return Person.objects.get(pk=pk)
+ except Person.DoesNotExist:
+ raise Http404
+
+ def get(self, request, pk, format=None):
+ person = self.get_object(pk)
+ serializer = PersonSerializer(person)
+ return Response(serializer.data)
+
+ def put(self, request, pk, format=None):
+ person = self.get_object(pk)
+ serializer = PersonSerializer(person, data=request.data)
+ if serializer.is_valid():
+ serializer.save()
+ return Response(serializer.data)
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+ def delete(self, request, pk, format=None):
+ person = self.get_object(pk)
+ person.delete()
+ return Response(status=status.HTTP_204_NO_CONTENT)
+```
+
+E `urls.py`:
+
+```python
+urlpatterns = [
+ path('persons/', views.PersonList.as_view()),
+ path('persons//', views.PersonDetail.as_view()),
+]
+```
+
+## Usando Mixins
+
+Repare que no exemplo anterior tivemos que definir os métodos `get()`, `post()`, `put()` e `delete()`. Podemos reduzir ainda mais esse código com o uso de mixins.
+
+```python
+from rest_framework import mixins
+from rest_framework import generics
+from core.models import Person
+from core.serializers import PersonSerializer
+
+
+class PersonList(mixins.ListModelMixin,
+ mixins.CreateModelMixin,
+ generics.GenericAPIView):
+ queryset = Person.objects.all()
+ serializer_class = PersonSerializer
+
+ def get(self, request, *args, **kwargs):
+ return self.list(request, *args, **kwargs)
+
+ def post(self, request, *args, **kwargs):
+ return self.create(request, *args, **kwargs)
+
+
+class PersonDetail(mixins.RetrieveModelMixin,
+ mixins.UpdateModelMixin,
+ mixins.DestroyModelMixin,
+ generics.GenericAPIView):
+ queryset = Person.objects.all()
+ serializer_class = PersonSerializer
+
+ def get(self, request, *args, **kwargs):
+ return self.retrieve(request, *args, **kwargs)
+
+ def put(self, request, *args, **kwargs):
+ return self.update(request, *args, **kwargs)
+
+ def delete(self, request, *args, **kwargs):
+ return self.destroy(request, *args, **kwargs)
+```
+
+## Usando generic class-based views
+
+E para finalizar usamos `ListCreateAPIView` e `RetrieveUpdateDestroyAPIView` que já tem todos os métodos embutidos.
+
+```python
+from rest_framework import generics
+from core.models import Person
+from core.serializers import PersonSerializer
+
+
+class PersonList(generics.ListCreateAPIView):
+ queryset = Person.objects.all()
+ serializer_class = PersonSerializer
+
+
+class PersonDetail(generics.RetrieveUpdateDestroyAPIView):
+ queryset = Person.objects.all()
+ serializer_class = PersonSerializer
+```
+
+Versão final de [views.py](https://github.com/rg3915/drf/blob/263ca63e5c8e2dd2e0f5d6c88c5733fcfdab4f74/core/views.py).
+
+Abraços.
+
+[10]: http://pythonclub.com.br/django-rest-framework-quickstart.html
+[11]: http://pythonclub.com.br/django-rest-framework-serialization.html
+[12]: http://pythonclub.com.br/django-rest-framework-requests-responses.html
\ No newline at end of file
diff --git a/content/django-rest-framework-quickstart.md b/content/django-rest-framework-quickstart.md
new file mode 100644
index 000000000..f1218a4e0
--- /dev/null
+++ b/content/django-rest-framework-quickstart.md
@@ -0,0 +1,213 @@
+title: Django Rest Framework Quickstart
+Slug: django-rest-framework-quickstart
+Date: 2015-11-17 14:00
+Tags: Python, Django, REST
+Author: Regis da Silva
+Email: regis.santos.100@gmail.com
+Github: rg3915
+Twitter: rg3915
+Category: Python, Django, REST
+
+Veremos aqui uma forma rápida de criar uma API REST com [Django Rest Framework][0].
+
+> Este artigo foi atualizado em 14 de Fevereiro de 2018.
+
+Este artigo está usando:
+
+* Python 3.5.2
+* Django 2.0.2
+* djangorestframework 3.7.7
+
+Favor clonar o projeto do [GitHub](https://github.com/rg3915/drf#clonando-o-projeto), favor ler o README para instalação.
+
+Repare nas alterações das urls na nova versão do Django.
+
+```python
+urls.py
+from django.urls import include, path
+from django.contrib import admin
+
+urlpatterns = [
+ path('', include('core.urls')),
+ path('admin/', admin.site.urls),
+]
+```
+
+```python
+# core/urls.py
+from django.urls import path
+from core import views
+
+urlpatterns = [
+ path('persons/', views.person_list),
+ path('persons//', views.person_detail),
+]
+```
+
+Além disso, tivemos alterações significativas em [settings.py](https://github.com/rg3915/drf/blob/master/myproject/settings.py).
+
+**Obs**: *Tem coisas que é melhor nem traduzir. ;)*
+
+* 0 - **Quickstart**
+* 1 - [Serialization][11]
+* 2 - [Requests & Responses][12]
+* 3 - [Class based views][13]
+
+> **Obs**: se você não sabe [Django][3] sugiro que leia este [tutorial][4] antes.
+
+## Começando
+
+```bash
+$ python3 -m venv .venv
+$ source env/bin/activate
+$ mkdir drf-quickstart
+$ cd drf-quickstart
+$ pip install django djangorestframework
+$ pip freeze > requirements.txt
+$ django-admin.py startproject myproject . # tem um ponto '.' aqui
+$ python manage.py startapp core
+$ python manage.py migrate
+$ python manage.py createsuperuser --username='admin' --email=''
+```
+
+Veja o meu requirements.txt
+
+```bash
+dj-database-url==0.4.2
+Django==2.0.2
+django-extensions==1.9.9
+django-filter==1.1.0
+djangorestframework==3.7.7
+drf-nested-routers==0.90.0
+python-decouple==3.1
+```
+
+## Editando `settings.py`
+
+Abra o arquivo `settings.py` e em `INSTALLED_APPS` acrescente
+
+```python
+INSTALLED_APPS = (
+ ...
+ 'rest_framework',
+)
+
+REST_FRAMEWORK = {
+ 'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAdminUser',),
+ 'PAGE_SIZE': 10
+}
+```
+
+## Editando `serializers.py`
+
+Crie o arquivo
+
+```bash
+$ cd core/
+$ touch serializers.py
+```
+
+Edite
+
+```python
+from django.contrib.auth.models import User, Group
+from rest_framework import serializers
+
+
+class UserSerializer(serializers.HyperlinkedModelSerializer):
+
+ class Meta:
+ model = User
+ fields = ('url', 'username', 'email', 'groups')
+
+
+class GroupSerializer(serializers.HyperlinkedModelSerializer):
+
+ class Meta:
+ model = Group
+ fields = ('url', 'name')
+```
+
+## Editando `views.py`
+
+```python
+from django.contrib.auth.models import User, Group
+from rest_framework import viewsets
+from core.serializers import UserSerializer, GroupSerializer
+
+
+class UserViewSet(viewsets.ModelViewSet):
+ """
+ API endpoint that allows users to be viewed or edited.
+ """
+ queryset = User.objects.all().order_by('-date_joined')
+ serializer_class = UserSerializer
+
+
+class GroupViewSet(viewsets.ModelViewSet):
+ """
+ API endpoint that allows groups to be viewed or edited.
+ """
+ queryset = Group.objects.all()
+ serializer_class = GroupSerializer
+```
+
+
+## Editando `urls.py`
+
+
+```python
+from django.conf.urls import url, include
+from rest_framework import routers
+from core import views
+
+router = routers.DefaultRouter()
+router.register(r'users', views.UserViewSet)
+router.register(r'groups', views.GroupViewSet)
+
+urlpatterns = [
+ url(http://github.com/r'%5E',%20include(router.urls)),
+ url(http://github.com/r'%5Eapi-auth/',%20include('rest_framework.urls',%20namespace='rest_framework'))
+]
+```
+
+## Rodando a API
+
+Abra duas abas no terminal, numa rode a aplicação.
+
+```bash
+$ python manage.py runserver
+```
+
+
+
+Na outra teste a API.
+
+```bash
+curl -H 'Accept: application/json; indent=4' -u admin:admin http://127.0.0.1:8000/users/
+```
+
+onde `admin:admin` equivale a `username:password`.
+
+Experimente com [httpie][7]
+
+```bash
+http -a admin:admin http://127.0.0.1:8000/users/
+```
+
+> **Atenção**: se você receber erro 301, muito provavelmente é porque você esqueceu da barra `/` no final da url.
+
+
+
+Veja o código no [GitHub][8].
+
+> Haverá continuação ...
+
+[0]: http://www.django-rest-framework.org/
+[3]: https://www.djangoproject.com/
+[4]: http://pythonclub.com.br/tutorial-django-17.html
+[7]: https://github.com/jakubroztocil/httpie#installation
+[8]: https://github.com/rg3915/drf-quickstart.git
+[11]: http://pythonclub.com.br/django-rest-framework-serialization.html
+[12]: http://pythonclub.com.br/django-rest-framework-requests-responses.html
+[13]: http://pythonclub.com.br/django-rest-framework-class-based-views.html
\ No newline at end of file
diff --git a/content/django-rest-framework-requests-responses.md b/content/django-rest-framework-requests-responses.md
new file mode 100644
index 000000000..0f8172c4c
--- /dev/null
+++ b/content/django-rest-framework-requests-responses.md
@@ -0,0 +1,121 @@
+title: Django Rest Framework - #2 Requests and Responses
+Slug: django-rest-framework-requests-responses
+Date: 2018-02-14 23:00
+Tags: Python, Django, REST
+Author: Regis da Silva
+Email: regis.santos.100@gmail.com
+Github: rg3915
+Twitter: rg3915
+Category: Python, Django, REST
+
+* 0 - [Quickstart][10]
+* 1 - [Serialization][11]
+* 2 - **Requests & Responses**
+* 3 - [Class based views][12]
+
+Este post é continuação do post [Django Rest Framework Serialization][11].
+
+O uso de *requests* e *responses* torna nossa api mais flexível. A funcionalidade principal do objeto **Request** é o atributo `request.data`, que é semelhante ao `request.POST`, mas é mais útil para trabalhar com APIs.
+
+## Objeto Response
+
+Introduzimos aqui um objeto `Response`, que é um tipo de `TemplateResponse` que leva conteúdo não renderizado e usa a negociação de conteúdo para determinar o tipo de conteúdo correto para retornar ao cliente.
+
+```python
+return Response(data) # Renderiza para o tipo de conteúdo conforme solicitado pelo cliente.
+```
+
+Repare também no uso de *status code* pré definidos, exemplo: `status.HTTP_400_BAD_REQUEST`.
+
+E usamos o decorador `@api_view` para trabalhar com funções. Ou `APIView` para classes.
+
+Nosso código ficou assim:
+
+```python
+# views.py
+from rest_framework import status
+from rest_framework.decorators import api_view
+from rest_framework.response import Response
+from core.models import Person
+from core.serializers import PersonSerializer
+
+
+@api_view(['GET', 'POST'])
+def person_list(request):
+ """
+ List all persons, or create a new person.
+ """
+ if request.method == 'GET':
+ persons = Person.objects.all()
+ serializer = PersonSerializer(persons, many=True)
+ return Response(serializer.data)
+
+ elif request.method == 'POST':
+ serializer = PersonSerializer(data=request.data)
+ if serializer.is_valid():
+ serializer.save()
+ return Response(serializer.data, status=status.HTTP_201_CREATED)
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+
+@api_view(['GET', 'PUT', 'DELETE'])
+def person_detail(request, pk):
+ """
+ Retrieve, update or delete a person instance.
+ """
+ try:
+ person = Person.objects.get(pk=pk)
+ except Person.DoesNotExist:
+ return Response(status=status.HTTP_404_NOT_FOUND)
+
+ if request.method == 'GET':
+ serializer = PersonSerializer(person)
+ return Response(serializer.data)
+
+ elif request.method == 'PUT':
+ serializer = PersonSerializer(person, data=request.data)
+ if serializer.is_valid():
+ serializer.save()
+ return Response(serializer.data)
+ return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
+
+ elif request.method == 'DELETE':
+ person.delete()
+ return Response(status=status.HTTP_204_NO_CONTENT)
+```
+
+Veja no [GitHub](https://github.com/rg3915/drf/commit/69205da9262415eaf83ff04f22a635e912880a60).
+
+
+## Usando sufixo opcional
+
+Em `core/urls.py` acrescente
+
+```python
+from rest_framework.urlpatterns import format_suffix_patterns
+
+...
+
+urlpatterns = format_suffix_patterns(urlpatterns)
+```
+
+E em `views.py` acrescente `format=None` como parâmetro das funções a seguir:
+
+```python
+def person_list(request, format=None):
+
+def person_detail(request, pk, format=None):
+```
+
+Com isso você pode chamar a api da seguinte forma:
+
+```bash
+http http://127.0.0.1:8000/persons.json # ou
+http http://127.0.0.1:8000/persons.api
+```
+
+Até a próxima.
+
+[10]: http://pythonclub.com.br/django-rest-framework-quickstart.html
+[11]: http://pythonclub.com.br/django-rest-framework-serialization.html
+[12]: http://pythonclub.com.br/django-rest-framework-class-based-views.html
\ No newline at end of file
diff --git a/content/django-rest-framework-serialization.md b/content/django-rest-framework-serialization.md
new file mode 100644
index 000000000..9b313d0cc
--- /dev/null
+++ b/content/django-rest-framework-serialization.md
@@ -0,0 +1,450 @@
+title: Django Rest Framework Serialization
+Slug: django-rest-framework-serialization
+Date: 2016-04-24 18:00
+Tags: Python, Django, REST
+Author: Regis da Silva
+Email: regis.santos.100@gmail.com
+Github: rg3915
+Twitter: rg3915
+Category: Python, Django, REST
+
+Eu resolvi estudar um pouco mais de [DRF][0] depois do tutorial do [Hugo Brilhante][1] na [Python Brasil 11][2].
+
+> Este artigo foi atualizado em 14 de Fevereiro de 2018.
+
+Este artigo está usando:
+
+* Python 3.5.2
+* Django 2.0.2
+* djangorestframework 3.7.7
+
+Favor clonar o projeto do [GitHub](https://github.com/rg3915/drf#clonando-o-projeto), favor ler o README para instalação.
+
+> **Obs**: se você não sabe Django sugiro que leia este [tutorial][4] antes.
+
+**Obs**: *Tem coisas que é melhor nem traduzir. ;)*
+
+* 0 - [Quickstart][10]
+* 1 - **Serialization**
+* 2 - [Requests & Responses][11]
+* 3 - [Class based views][12]
+
+Pra quem não sabe, para usar API Web usamos REST, no caso, [Django Rest Framework][0], framework web do [Django][3].
+
+> **Nota:** este tutorial não é exatamente igual ao do Hugo, é baseado nele. E baseado também em [Tutorial 1: Serialization][9].
+
+Então para criar a API, no meu caso, eu usei:
+
+* Ambiente: .venv
+* Projeto: myproject
+* App: core
+* Model: Person
+* Fields: first_name, last_name, email, active (boolean), created
+
+
+
+
+## Configurando um novo ambiente
+
+```bash
+$ python3 -m venv .venv
+$ source .venv/bin/activate
+$ mkdir drf; cd drf
+$ pip install django==2.0.2 djangorestframework==3.7.7
+$ pip install django-filter drf-nested-routers
+$ pip freeze > requirements.txt
+$ django-admin.py startproject myproject .
+$ python manage.py startapp core
+```
+
+Veja o meu requirements.txt
+
+```bash
+dj-database-url==0.4.2
+Django==2.0.2
+django-extensions==1.9.9
+django-filter==1.1.0
+djangorestframework==3.7.7
+drf-nested-routers==0.90.0
+python-decouple==3.1
+```
+
+## Step-0 Projeto inicial
+
+Abra o arquivo `settings.py` e em `INSTALLED_APPS` acrescente
+
+```python
+INSTALLED_APPS = (
+ ...
+ 'rest_framework',
+ 'core',
+)
+```
+
+## Step-1 Serializer
+
+### `models.py`: Criando o modelo `Person`
+
+```python
+from django.db import models
+
+class Person(models.Model):
+ first_name = models.CharField(max_length=30)
+ last_name = models.CharField(max_length=30)
+ email = models.EmailField(null=True, blank=True)
+ active = models.BooleanField(default=True)
+ created = models.DateTimeField(auto_now_add=True, auto_now=False)
+
+ class Meta:
+ ordering = ['first_name']
+ verbose_name = u'pessoa'
+ verbose_name_plural = u'pessoas'
+
+ def __str__(self):
+ return self.first_name + " " + self.last_name
+
+ full_name = property(__str__)
+```
+
+
+
+### `serializers.py`: Criando `PersonSerializer`
+
+Precisamos proporcionar uma forma de [serialização][5] e desserialização das instâncias de `person` em uma representação JSON.
+
+```bash
+$ cd core/
+$ touch serializers.py
+```
+
+Edite
+
+```python
+from rest_framework import serializers
+from core.models import Person
+
+class PersonSerializer(serializers.Serializer):
+ pk = serializers.IntegerField(read_only=True)
+ first_name = serializers.CharField(max_length=30)
+ last_name = serializers.CharField(max_length=30)
+ email = serializers.EmailField()
+ active = serializers.BooleanField(default=True)
+ created = serializers.DateTimeField()
+
+ def create(self, validated_data):
+ """
+ Create and return a new `Person` instance, given the validated data.
+ :param validated_data:
+ """
+ return Person.objects.create(**validated_data)
+
+ def update(self, instance, validated_data):
+ """
+ Update and return an existing `Person` instance, given the validated data.
+ """
+
+ instance.first_name = validated_data.get(
+ 'first_name', instance.first_name)
+ instance.last_name = validated_data.get(
+ 'last_name', instance.last_name)
+ instance.email = validated_data.get('email', instance.email)
+ instance.save()
+ return instance
+```
+
+A primeira parte da classe define os campos que serão serializados. Os métodos `create()` e `update()` criam e atualizam as instâncias, respectivamente, quando chamados.
+
+Uma classe de serialização é similar a uma classe `Form` do Django, e inclui validações similares para os campos, tais como `required`, `max_length` e `default`.
+
+
+### Fazendo a migração
+
+```bash
+$ ./manage.py makemigrations core
+$ ./manage.py migrate
+```
+
+### Trabalhando com a serialização
+
+Abra o `shell` do Django.
+
+```bash
+$ ./manage.py shell
+```
+
+Primeiro vamos criar uma pessoa.
+
+```python
+>>> from core.models import Person
+>>> from core.serializers import PersonSerializer
+>>> from rest_framework.renderers import JSONRenderer
+>>> from rest_framework.parsers import JSONParser
+
+>>> person = Person(first_name='Paul', last_name='Van Dyke', email='paul@email.com')
+>>> person.save()
+
+>>> person = Person(first_name='Regis', last_name='Santos', email='regis@email.com')
+>>> person.save()
+```
+
+Agora que já temos alguns dados podemos ver a serialização da última instância.
+
+```python
+>>> serializer = PersonSerializer(person)
+>>> serializer.data
+# {'pk': 2, 'first_name': 'Regis', 'created': '2015-11-15T03:20:25.084990Z', 'last_name': 'Santos', 'email': 'regis@email.com', 'active': True}
+```
+
+Neste ponto nós traduzimos a instância do modelo em tipos de dados nativos do Python. Para finalizar o processo de serialização nós vamos renderizar os dados em `json`.
+
+```python
+>>> content = JSONRenderer().render(serializer.data)
+>>> content
+# b'{"pk":2,"first_name":"Regis","last_name":"Santos","email":"regis@email.com","active":true,"created":"2015-11-15T03:20:25.084990Z"}'
+```
+
+
+A desserialização é similar.
+
+```python
+>>> from core.models import Person
+>>> from core.serializers import PersonSerializer
+>>> from rest_framework.renderers import JSONRenderer
+>>> from rest_framework.parsers import JSONParser
+>>> from django.utils.six import BytesIO
+
+>>> person = Person.objects.get(pk=1)
+>>> serializer = PersonSerializer(person)
+>>> content = JSONRenderer().render(serializer.data)
+>>> stream = BytesIO(content)
+>>> data = JSONParser().parse(stream)
+>>> serializer = PersonSerializer(data=data)
+>>> serializer.is_valid()
+# True
+>>> serializer.validated_data
+# OrderedDict([('first_name', 'Paul'), ('last_name', 'Van Dyke'), ('email', 'paul@email.com'), ('active', True)])
+```
+
+## Step-2 ModelSerializer
+
+Nossa classe `PersonSerializer` está replicando um monte de informações que está contido no modelo `Person`.
+
+Da mesma forma que o Django fornece `Form` e `ModelForm`, REST framework inclui as classes `Serializer` e `ModelSerializer`.
+
+Vamos refatorar nosso arquivo `serializers.py`, que agora ficará assim:
+
+```python
+from rest_framework import serializers
+from core.models import Person
+
+class PersonSerializer(serializers.ModelSerializer):
+
+ class Meta:
+ model = Person
+ fields = ('pk', 'first_name', 'last_name','email', 'active', 'created')
+```
+
+Uma propriedade legal que a serialização tem é que você pode inspecionar todos os campos em uma instância serializer, imprimindo sua representação. Abra o `shell` do Django.
+
+```bash
+$ ./manage.py shell
+```
+
+```python
+>>> from core.serializers import PersonSerializer
+>>> serializer = PersonSerializer()
+>>> print(repr(serializer))
+# PersonSerializer():
+# pk = IntegerField(label='ID', read_only=True)
+# first_name = CharField(max_length=30)
+# last_name = CharField(max_length=30)
+# email = EmailField(allow_blank=True, allow_null=True, max_length=254, required=False)
+# active = BooleanField(required=False)
+# created = DateTimeField(read_only=True)
+```
+
+É importante lembrar que as classes `ModelSerializer` não faz nenhuma mágica, são simplesmente um atalho para a criação das classes de serialização:
+
+* Os campos são definidos automaticamente.
+* Os métodos `create()` e `update()` são implementados por padrão de uma forma simplificada.
+
+
+
+### `views.py`: Criando *views* regulares usando nosso *Serializer*
+
+Vamos criar uma *view* simples para visualizar os dados em `json`.
+
+Edite o arquivo `views.py`
+
+```python
+from django.http import HttpResponse
+from django.views.decorators.csrf import csrf_exempt
+from rest_framework.renderers import JSONRenderer
+from rest_framework.parsers import JSONParser
+from core.models import Person
+from core.serializers import PersonSerializer
+
+class JSONResponse(HttpResponse):
+ """
+ An HttpResponse that renders its content into JSON.
+ """
+
+ def __init__(self, data, **kwargs):
+ content = JSONRenderer().render(data)
+ kwargs['content_type'] = 'application/json'
+ super(JSONResponse, self).__init__(content, **kwargs)
+```
+
+A raiz da nossa API será uma lista de todas as pessoas, ou podemos criar uma pessoa nova.
+
+
+```python
+@csrf_exempt
+def person_list(request):
+ """
+ List all persons, or create a new person.
+ """
+ if request.method == 'GET':
+ persons = Person.objects.all()
+ serializer = PersonSerializer(persons, many=True)
+ return JSONResponse(serializer.data)
+
+ elif request.method == 'POST':
+ data = JSONParser().parse(request)
+ serializer = PersonSerializer(data=data)
+ if serializer.is_valid():
+ serializer.save()
+ return JSONResponse(serializer.data, status=201)
+ return JSONResponse(serializer.errors, status=400)
+```
+
+Note que nós queremos usar o POST mas não temos o CSRF Token, por isso usamos o `@csrf_exempt`.
+
+Também vamos precisar visualizar os detalhes de cada pessoa. Assim podemos recuperar, atualizar ou deletar cada registro.
+
+```python
+@csrf_exempt
+def person_detail(request, pk):
+ """
+ Retrieve, update or delete a person.
+ """
+ try:
+ person = Person.objects.get(pk=pk)
+ except Person.DoesNotExist:
+ return HttpResponse(status=404)
+
+ if request.method == 'GET':
+ serializer = PersonSerializer(person)
+ return JSONResponse(serializer.data)
+
+ elif request.method == 'PUT':
+ data = JSONParser().parse(request)
+ serializer = PersonSerializer(person, data=data)
+ if serializer.is_valid():
+ serializer.save()
+ return JSONResponse(serializer.data)
+ return JSONResponse(serializer.errors, status=400)
+
+ elif request.method == 'DELETE':
+ person.delete()
+ return HttpResponse(status=204)
+```
+
+Agora, vamos criar as urls. Crie um novo arquivo `core/urls.py`.
+
+```python
+from django.urls import path
+from core import views
+
+urlpatterns = [
+ path('persons/', views.PersonList.as_view()),
+ path('persons//', views.PersonDetail.as_view()),
+]
+```
+
+E acrescente a seguinte linha em `myproject/urls.py`.
+
+```python
+from django.urls import include, path
+from django.contrib import admin
+
+urlpatterns = [
+ path('', include('core.urls')),
+ path('admin/', admin.site.urls),
+]
+```
+
+## Instalando `httpie`
+
+Podemos usar o [curl][6], mas o [httpie][7] é mais amigável, e escrito em Python.
+
+```bash
+$ sudo pip install httpie
+```
+
+Vamos usar o `httpie` digitando
+
+```bash
+$ http http://127.0.0.1:8000/persons/
+
+HTTP/1.0 200 OK
+Content-Type: application/json
+Date: Sun, 15 Nov 2015 03:24:44 GMT
+Server: WSGIServer/0.2 CPython/3.4.3
+X-Frame-Options: SAMEORIGIN
+
+[
+ {
+ "active": true,
+ "created": "2015-11-15T03:20:24.938378Z",
+ "email": "paul@email.com",
+ "first_name": "Paul",
+ "last_name": "Van Dyke",
+ "pk": 1
+ },
+ {
+ "active": true,
+ "created": "2015-11-15T03:20:25.084990Z",
+ "email": "regis@email.com",
+ "first_name": "Regis",
+ "last_name": "Santos",
+ "pk": 2
+ }
+]
+```
+
+Veja os detalhes
+
+```bash
+$ http http://127.0.0.1:8000/persons/1/
+```
+
+> **Atenção**: se você receber erro 301, muito provavelmente é porque você esqueceu da barra `/` no final da url.
+
+
+### Como seria em curl?
+
+Assim
+
+```bash
+$ curl http://127.0.0.1:8000/persons/
+
+[{"pk":1,"first_name":"Paul","last_name":"Van Dyke","email":"paul@email.com","active":true,"created":"2015-11-15T03:20:24.938378Z"},{"pk":2,"first_name":"Regis","last_name":"Santos","email":"regis@email.com","active":true,"created":"2015-11-15T03:20:25.084990Z"}]
+```
+
+
+GitHub: Se você quiser pode olhar meu [GitHub][8], mas terá que ver os *commits* para ver os passos.
+
+[0]: http://www.django-rest-framework.org/
+[1]: https://github.com/hugobrilhante/drf-tutorial-pybr11
+[2]: http://pythonbrasil.github.io/pythonbrasil11-site/
+[3]: https://www.djangoproject.com/
+[4]: http://pythonclub.com.br/tutorial-django-17.html
+[5]: https://pt.wikipedia.org/wiki/Serializa%C3%A7%C3%A3o
+[6]: http://curl.haxx.se/
+[7]: https://github.com/jakubroztocil/httpie#installation
+[8]: https://github.com/rg3915/drf.git
+[9]: http://www.django-rest-framework.org/tutorial/1-serialization/
+[10]: http://pythonclub.com.br/django-rest-framework-quickstart.html
+[11]: http://pythonclub.com.br/django-rest-framework-requests-responses.html
+[12]: http://pythonclub.com.br/django-rest-framework-class-based-views.html
\ No newline at end of file
diff --git a/content/django_na_pratica_parte_1.rst b/content/django_na_pratica_parte_1.rst
new file mode 100644
index 000000000..d09caf94d
--- /dev/null
+++ b/content/django_na_pratica_parte_1.rst
@@ -0,0 +1,276 @@
+Django na prática - Hello World
+#############################################
+
+:date: 2015-10-26 09:25
+:tags: python, django, django-na-pratica
+:category: Python
+:slug: django-na-pratica-aula-01
+:author: Lucas Magnum
+:email: lucasmagnumlopes@gmail.com
+:github: lucasmagnum
+:linkedin: lucasmagnum
+
+
+Esse é o primeiro post do Curso **django na prática** onde você aprenderá tudo que precisa para criar um sistema web :D
+
+Utilizaremos a versão 1.8 do Django com Python 3!
+
+
+==========
+Requisitos
+==========
+
+* Linux (Ubuntu)
+* Python 3.X
+* Virtualenv
+* pip
+
+
+Se precisar de ajuda para instalar o pip, você pode utilizar esse `tutorial `_.
+
+
+----------------
+Convenções
+----------------
+
+.. code-block:: bash
+
+ $ indica que comando deve ser executado no terminal do Linux
+ >>> indica que comando deve ser executado pelo interpretador Python em modo interativo
+
+
+===========
+Instalação
+===========
+
+Ative seu virtualenv e instale o Django na versão 1.8:
+
+.. code-block:: bash
+
+ $ pip install "django>=1.8,<1.9"
+
+
+Se tiver alguma duvida, você pode olhar na `documentação `_ como instalar o framework.
+
+
+Para verificar se está tudo certo, abra o interpretador python e verifique a versão do Django:
+
+.. code-block:: python
+
+ >>> import django
+ >>> print(django.get_version())
+ 1.8.5
+
+
+Isso é tudo que precisamos para começar =)
+
+
+===============
+django-admin.py
+===============
+
+o ``django-admin.py`` é um script de linha de comando do Django que nos oferece vários comandos administrativos.
+
+Existem várias opções, para visualizar todas basta executar:
+
+.. code-block:: bash
+
+ $ django-admin.py help
+
+
+Alguns parâmetros importantes são ``--pythonpath`` e ``--settings``.
+
+ * Como vamos criar nosso projeto do zero, precisamos informar onde nossos módulos estarão localizados e para isso utilizaremos o parâmetro ``--pythonpath``.
+ * Precisamos informar ao Django onde encontrar nossas configurações e para isso utilizaremos o parâmetro ``--settings``.
+
+
+=======================
+Configurando o ambiente
+=======================
+
+Crie um arquivo chamado ``helloworld.py``:
+
+.. code-block:: bash
+
+ $ touch helloworld.py
+
+Criamos nosso arquivo e agora vamos rodar o `ambiente de desenvolvimento `_ do Django :D
+
+.. code-block:: bash
+
+ $ django-admin.py runserver --pythonpath=. --settings=helloworld
+
+
+Dessa forma, estamos dizendo ao Django que nossos arquivos estão no diretório atual e que nossas configurações estão no arquivo ``heloworld`` (não devemos informar a extensão do arquivo no parâmetro).
+
+**Puts**, ocorreu um erro!
+
+.. code-block:: bash
+
+ django.core.exceptions.ImproperlyConfigured: The SECRET_KEY setting must not be empty.
+
+Atualmente o Django não inicia sem a `SECRET_KEY `_ configurada. Precisamos adiciona-la ao nosso arquivo.
+
+Abra o arquivo ``helloworld.py`` e insira uma SECRET_KEY qualquer:
+
+.. code-block:: python
+
+ SECRET_KEY='helloworld'
+
+
+Para ambiente de teste não existe nenhum problema em deixar nossa SECRET_KEY com esse valor, porém para o ambiente de produção é necessário que seja um valor randômico. A SECRET_KEY é utilizada em diversas partes do Django para criar hashes e encriptar chaves. Por esse fato, você NUNCA DEVE deixar pública o valor de SECRET_KEY utilizado em ambientes de produção.
+Mais informações `aqui `_.
+
+Executando novamente nosso ambiente, teremos o seguinte erro:
+
+.. code-block:: bash
+
+ $ django-admin.py runserver --pythonpath=. --settings=helloworld
+
+ CommandError: You must set settings.ALLOWED_HOSTS if DEBUG is False.
+
+Como estamos em ambiente de desenvolvimento, precisamos utilizar o ``DEBUG`` como ``True``, somente para produção que ele será desativado.
+
+Nosso arquivo ``helloworld.py`` agora está assim:
+
+.. code-block:: python
+
+ SECRET_KEY='helloworld'
+ DEBUG = True
+
+Com isso já é possível subir o ambiente de desenvolvimento.
+
+.. code-block:: bash
+
+ $ django-admin.py runserver --pythonpath=. --settings=helloworld
+
+ Performing system checks...
+
+ System check identified some issues:
+
+ WARNINGS:
+ ?: (1_7.W001) MIDDLEWARE_CLASSES is not set.
+ HINT: Django 1.7 changed the global defaults for the MIDDLEWARE_CLASSES. django.contrib.sessions.middleware.SessionMiddleware, django.contrib.auth.middleware.AuthenticationMiddleware, and django.contrib.messages.middleware.MessageMiddleware were removed from the defaults. If your project needs these middleware then you should configure this setting.
+
+ System check identified 1 issue (0 silenced).
+ September 26, 2015 - 08:50:09
+ Django version 1.8.5, using settings 'helloworld'
+ Starting development server at http://127.0.0.1:8000/
+ Quit the server with CONTROL-C.
+
+
+Pronto! Nosso ambiente já está rodando na porta 8000, abra seu navegador e digite ``http://127.0.0.1:8000/``.
+
+============
+Hello World
+============
+
+
+Ok, nosso ambiente está rodando, porém ainda temos erros. O que aconteceu?
+
+Se você visualizar no terminal onde o ambiente está sendo executado, verá a seguinte mensagem:
+
+.. code-block:: bash
+
+ AttributeError: 'Settings' object has no attribute 'ROOT_URLCONF'
+
+
+Para encontrar as *views* que serão renderizadas no projeto, o Django procura primeiro as configurações no
+arquivo apontado pelo ``ROOT_URLCONF``.
+
+* uma view é uma função responsável por retornar algo para ser renderizado no browser, pode ser um html, um arquivo, um json e etc.
+
+
+Como toda nossa aplicação ficará por enquanto no arquivo ``helloworld.py``, vamos apontar nosso ``ROOT_URLCONF`` para ele.
+
+Abra o arquivo ``helloworld.py`` e insira o seguinte código:
+
+.. code-block:: python
+
+ SECRET_KEY = 'helloworld'
+ DEBUG = True
+ ROOT_URLCONF = __name__
+
+Estamos dizendo ao Django que nossas `urls` estão nesse arquivo, para reconhecer as urls, o django procura
+pela variável ``urlpatterns``.
+
+Logo, nosso arquivo deve ficar assim:
+
+.. code-block:: python
+
+
+ SECRET_KEY = 'helloworld'
+ DEBUG = True
+ ROOT_URLCONF = __name__
+
+ urlpatterns = []
+
+
+Agora, se abrirmos nosso navegador no endereço ``http://127.0.0.1:8000/`` já recebemos a página de bem vindo do Django =DDD
+
+
+.. image:: images/lucasmagnum/itworked.png
+ :alt: itworked
+
+
+---------------------
+Nossa primeira view
+---------------------
+
+Agora sim, tudo está pronto para criarmos nossa primeira view!
+
+Vamos criar nossa view chamada ``hello_world``, toda view recebe como primeiro o ``request``,
+e precisa retornar alguma resposta para o navegador, vamos retornar um ``HttpResponse`` com o texto
+*Django na prática - Hello World!*
+
+Modifique seu ``helloworld.py`` para que fique assim:
+
+.. code-block:: python
+
+ from django.http import HttpResponse
+
+
+ SECRET_KEY = 'helloworld'
+ DEBUG = True
+ ROOT_URLCONF = __name__
+
+ def hello_world(request):
+ return HttpResponse('Django na prática - Hello World!')
+
+ urlpatterns = []
+
+
+Pronto! Temos nossa view criada, porém ainda não conseguimos acessá-la.
+Precisamos dizer ao framework como essa view pode ser encontrada e para qual ``url`` ela deve responder.
+
+Façamos dessa forma:
+
+.. code-block:: python
+
+ from django.conf.urls import url
+ from django.http import HttpResponse
+
+
+ SECRET_KEY = 'helloworld'
+ DEBUG = True
+ ROOT_URLCONF = __name__
+
+ def hello_world(request):
+ return HttpResponse('Django na prática - Hello World!')
+
+ urlpatterns = [
+ url(http://github.com/r'%5E),%20hello_world)
+ ]
+
+Dentro do ``urlpatterns`` nós informamos quais são as urls disponíveis no nosso projeto.
+Fazemos isso usando utilizado uma expressão regular associada à uma função, que no nosso caso é o ``hello_world``.
+
+Agora, se abrirmos o navegador, iremos nos deparar com o seguinte resultado:
+
+.. image:: images/lucasmagnum/helloworld.png
+ :alt: hello world
+
+
+Por hoje é isso!!! Guarde o arquivo criado hoje, pois ele será utilizado nas próximas aulas!
+
+Até a próxima =)
diff --git a/content/django_releases_em_10_minutos.rst b/content/django_releases_em_10_minutos.rst
new file mode 100644
index 000000000..7650ca630
--- /dev/null
+++ b/content/django_releases_em_10_minutos.rst
@@ -0,0 +1,118 @@
+Django - 3 anos em 10 minutos
+#############################################
+
+:date: 2015-04-06 11:55
+:tags: python, django
+:category: Python
+:slug: django-overview-10-minutos
+:author: Lucas Magnum
+:email: lucasmagnumlopes@gmail.com
+:github: lucasmagnum
+:linkedin: lucasmagnum
+
+
+Em março de 2012 foi lançada a versão 1.4 e por aqui que nossa jornada começa, o objetivo não é entrar em detalhes em todas as features que foram implementadas e sim um `overview` sobre as que considero mais importantes para nosso dia a dia.
+
+
+Versão `1.4 `_ (Há 3 anos)
+--------------------------------------------------------------------------------
+
+Lançada em Março de 2012, suporta Python 2.5 >= 2.7.
+
+Foi a primeira versão LTS (long-term support), ou seja, ela recebeu correções e atualizações de segurança por pelo menos 3 anos após à data de lançamento.
+
+É a primeira versão a permitir o uso de `custom project template `_ e tornar o arquivo `manage.py` *o* que conhecemos hoje.
+
+Foi a primeira versão a suportar o `testes `_ feitos com frameworks que utilizam o browser como o `Selenium `_.
+
+
+Algumas outras coisas legais foram:
+
+ * `bulk_create `_ para criar vários objetos de uma só vez.
+ * `prefetch_related `_ para realizar `joins` em tabelas que possuem relações `many-to-many`.
+ * `reverse_lazy `_ que permite fazer `reverse` antes das configurações de URL serem carregadas.
+
+
+Versão `1.5 `_ (Há 2 anos)
+--------------------------------------------------------------------------------
+
+Lançada em Fevereiro de 2013, suporta Python 2.6.5 >= 2.7 (Python 3 - Experimental).
+
+A versão 1.5 ficou bem conhecida pela reformulação na aplicação de autenticação, é possível `customizar um usuário `_ conforme nossas necessidades.
+
+É possivel também passar os campos que você `gostaria de atualizar `_ quando for salvar um objeto.
+
+Algumas outras coisas legais foram:
+
+ * `verbatim `_ template tag para casos onde você deseja ignorar a sintaxe do template.
+ * `novos tutoriais `_ foram inseridos para ajudar iniciantes e existe também uma seção `Tutoriais Avançados`
+
+
+Versão `1.6 `_ (Há 1 ano e meio)
+--------------------------------------------------------------------------------------
+
+Lançada em Novembro de 2013, suporta Python 2.6.5 >= 3.3. (Python 3!!!!)
+
+
+A versão 1.6 alterou um pouco do `layout` padrão dos projetos que estávamos acostumados, para nossa alegria o `admin.py` é adicionado por padrão na aplicação :)
+
+Transações possuem `autocommit` habilitado por padrão!
+
+Os testes são localizados em qualquer arquivo que possua o nome começando com **test_** (Antigamente os testes só eram encontrados nos arquivos models.py e tests.py)
+
+Algumas outras coisas legais foram:
+
+ * `check `_ comando para validar se suas configurações estão compatíveis com a versão do django.
+
+
+Versão `1.7 `_ (Há 8 meses)
+---------------------------------------------------------------------------------
+
+Lançada em Setembro de 2014, suporta Python 2.7 >= 3.4.
+
+Um novo conceito de `migrações `_ foi implementado, até o momento a maioria utilizava o `South `_, nessa versão tudo é `built-in`.
+
+Para criar uma aplicação não é necessário mais conter o arquivo `models.py`.
+
+O `conceito de aplicação `_ foi atualizado e existem vários novas maneiras de se customizar seus dados :)
+
+O comando `check` foi evoluído e agora realiza uma varredura quase completa no seu código para identificar possíveis problemas.
+
+
+Versão `1.8 `_
+--------------------------------------------------------------------
+
+Lançada em Abril de 2015, a versão 1.8 introduz várias features legais!
+
+É a segunda versão LTS (long-term support), ou seja, vai receber correções e atualizações de segurança por pelo menos 3 anos após à data de lançamento.
+
+Agora há a possibilidade de utilizar `várias linguagens(engines) `_ de templates, ou seja, você pode usar simuntaneamente (não no mesmo arquivo) a `Django Template Language `_ ou `Jinja2 `_. Há uma API padronizada para quem quiser adicionar suporte para outras linguagens de template no futuro. Há um `guia de migração `_ .
+
+Novos campos foram introduzidos como `UUIDField `_ e `DurationField `_ e ainda tem mais!
+
+O `Model._meta API `_ foi totalmente refatorado e padronizado. O `Model._meta` API foi incluida no Django 0.96 e serve para obter informações sobre campos do Model, contudo, essa api era considerada de uso privado, e não tinha qualquer documentação ou comentários. Agora com tudo padronizado, abre uma gama de novas opções para criar apps plugaveis que fazem coisas com Models arbitrarios.
+
+Algumas outras coisas legais foram:
+
+ * O comando de gerenciamento `inspectdb `_ agora suporta fazer engenharia reversa tambem de Database Views (nas versões anteriores, o inspectdb inspecionava somente tabelas, mas não conseguia "ver" as views).
+ * Adição/Melhoria de `Query Expressions, Conditional Expressions, and Database Functions `_ . Isso adiciona muito mais flexibilidade para fazer pesquisas mais complexas no banco de dados.
+
+
+
+Há várias outras ótimas melhorias que omiti, veja o `release note `_ completo.
+
+O Futuro
+----------
+
+Versão 1.9 (Com lançamento previsto para Outubro de 2016)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+O Django 1.8 mal foi lançado, e já há algumas novidades que talvez venham no Django 1.9.
+
+Muito provavelmete, o Django 1.9 vai adicionar os tão esperados `Campos Compostos `_ . Isso vai permitir fazer coisas mais complexas, como ter um campo Dinheiro, que "sabe" como fazer conversões de moeda (ex. Real para Dolar).
+
+Tambem existe uma expectativa que o tema `django-flat-admin `_ para o `Admin `_ seja integrado no Django 1.9, virando o tema padrão. O `django-flat-admin `_ somente modifica o CSS, nenhuma tag HTML é alterada em relação ao HTML original do Admin, então ele é relativemente compativel (desde que você não tenha incluido customizações no CSS do Admin). Os core developers do Django estão tratando desse assunto `neste tópico `_
+
+
+Veja o `Roadmap `_ do que vem por ai no Django 1.9
+
diff --git a/content/encapsulamento-da-logica-do-algoritmo.md b/content/encapsulamento-da-logica-do-algoritmo.md
new file mode 100644
index 000000000..34a5104c0
--- /dev/null
+++ b/content/encapsulamento-da-logica-do-algoritmo.md
@@ -0,0 +1,178 @@
+Title: Encapsulamento da lógica do algoritmo
+Slug: encapsulamento-da-logica-do-algoritmo
+Date: 2021-03-02 15:00
+Category: Python
+Tags: python, poo
+Author: Eduardo Klosowski
+Email: eduardo_klosowski@yahoo.com
+Github: eduardoklosowski
+Twitter: eduklosowski
+Site: https://dev.to/eduardoklosowski
+About_author: Programador, formado em redes de computadores, estuda DevOps
+
+Muitas listas de exercícios de lógica de programação pedem em algum momento que um valor seja lido do teclado, e caso esse valor seja inválido, deve-se avisar, e repetir a leitura até que um valor válido seja informado. Utilizando a ideia de [otimização do algoritmo passo a passo](https://dev.to/acaverna/otimizando-o-algoritmo-passo-a-passo-4co0), começando com uma solução simples, pretendo estudar como reduzir a duplicação de código alterando o algoritmo, encapsulando a lógica em funções, e encapsulando em classes.
+
+## Exercício
+
+Um exemplo de exercício que pede esse tipo de validação é a leitura de notas, que devem estar entre 0 e 10. A solução mais simples, consiste em ler um valor, e enquanto esse valor for inválido, dar o aviso e ler outro valor. Exemplo:
+
+```python
+nota = float(input('Digite a nota: '))
+while nota < 0 or nota > 10:
+ print('Nota inválida')
+ nota = float(input('Digite a nota: '))
+```
+
+Esse algoritmo funciona, porém existe uma duplicação no código que faz a leitura da nota (uma antes do *loop* e outra dentro). Caso seja necessário uma alteração, como a mudança da nota para um valor inteiro entre 0 e 100, deve-se alterar os dois lugares, e se feito em apenas um lugar, o algoritmo poderia processar valores inválidos.
+
+### Alterando o algoritmo
+
+Visando remover a repetição de código, é possível unificar a leitura do valor dentro do *loop*, uma vez que é necessário repetir essa instrução até que o valor válido seja obtido. Exemplo:
+
+```python
+while True:
+ nota = float(input('Digite a nota: '))
+ if 0 <= nota <= 10:
+ break
+ print('Nota inválida!')
+```
+
+Dessa forma, não existe mais a repetição de código. A condição de parada, que antes verificava se o valor era inválido (o que pode ter uma leitura não tão intuitiva), agora verifica se é um valor válido (que é geralmente é mais fácil de ler e escrever a condição). E a ordem dos comandos dentro do *loop*, que agora estão em uma ordem que facilita a leitura, visto que no algoritmo anterior era necessário tem em mente o que era executado antes do *loop*.
+
+Porém esses algoritmos validam apenas o valor lido, apresentando erro caso seja informado um valor com formato inválido, como letras em vez de números. Isso pode ser resolvido tratando as exceções lançadas. Exemplo:
+
+```python
+while True:
+ try:
+ nota = float(input('Digite a nota: '))
+ if 0 <= nota <= 10:
+ break
+ except ValueError:
+ ...
+ print('Nota inválida!')
+```
+
+### Encapsulamento da lógica em função
+
+Caso fosse necessário ler várias notas, com os algoritmos apresentados até então, seria necessário repetir todo esse trecho de código, ou utilizá-lo dentro de uma estrutura de repetição. Para facilitar sua reutilização, evitando a duplicação de código, é possível encapsular esse algoritmo dentro de uma função. Exemplo:
+
+```python
+def nota_input(prompt):
+ while True:
+ try:
+ nota = float(input(prompt))
+ if 0 <= nota <= 10:
+ break
+ except ValueError:
+ ...
+ print('Nota inválida!')
+ return nota
+
+
+nota1 = nota_input('Digite a primeira nota: ')
+nota2 = nota_input('Digite a segunda nota: ')
+```
+
+### Encapsulamento da lógica em classes
+
+Em vez de encapsular essa lógica em uma função, é possível encapsulá-la em uma classe, o que permitiria separar cada etapa do algoritmo em métodos, assim como ter um método responsável por controlar qual etapa deveria ser chamada em qual momento. Exemplo:
+
+```python
+class ValidaNotaInput:
+ mensagem_valor_invalido = 'Nota inválida!'
+
+ def ler_entrada(self, prompt):
+ return input(prompt)
+
+ def transformar_entrada(self, entrada):
+ return float(entrada)
+
+ def validar_nota(self, nota):
+ return 0 <= nota <= 10
+
+ def __call__(self, prompt):
+ while True:
+ try:
+ nota = self.transformar_entrada(self.ler_entrada(prompt))
+ if self.validar_nota(nota):
+ break
+ except ValueError:
+ ...
+ print(self.mensagem_valor_invalido)
+ return nota
+
+
+nota_input = ValidaNotaInput()
+
+
+nota = nota_input('Digite a nota: ')
+```
+
+Vale observar que o método `__call__` permite que o objeto criado a partir dessa classe seja chamado como se fosse uma função. Nesse caso ele é o responsável por chamar cada etapa do algoritmo, como: `ler_entrada` que é responsável por ler o que foi digitado no teclado, `transformar_entrada` que é responsável por converter o texto lido para o tipo desejado (converter de `str` para `float`), e `validar_nota` que é responsável por dizer se o valor é válido ou não. Vale observar que ao dividir o algoritmo em métodos diferentes, seu código principal virou uma espécie de código comentado, descrevendo o que está sendo feito e onde está sendo feito.
+
+Outra vantagem de encapsular a lógica em classe, em vez de uma função, é a possibilidade de generalizá-la. Se fosse necessário validar outro tipo de entrada, encapsulando em uma função, seria necessário criar outra função repetindo todo o algoritmo, alterando apenas a parte referente a transformação do valor lido, e validação, o que gera uma espécie de repetição de código. Ao encapsular em classes, é possível se aproveitar dos mecanismos de herança para evitar essa repetição. Exemplo:
+
+```python
+class ValidaInput:
+ mensagem_valor_invalido = 'Valor inválido!'
+
+ def ler_entrada(self, prompt):
+ return input(prompt)
+
+ def transformar_entrada(self, entrada):
+ raise NotImplementedError
+
+ def validar_valor(self, valor):
+ raise NotImplementedError
+
+ def __call__(self, prompt):
+ while True:
+ try:
+ valor = self.transformar_entrada(self.ler_entrada(prompt))
+ if self.validar_valor(valor):
+ break
+ except ValueError:
+ ...
+ print(self.mensagem_valor_invalido)
+ return valor
+
+
+class ValidaNomeInput(ValidaInput):
+ mensagem_valor_invalido = 'Nome inválido!'
+
+ def transformar_entrada(self, entrada):
+ return entrada.strip().title()
+
+ def validar_valor(self, valor):
+ return valor != ''
+
+
+class ValidaNotaInput(ValidaInput):
+ mensagem_valor_invalido = 'Nota inválida!'
+
+ def transformar_entrada(self, entrada):
+ return float(entrada)
+
+ def validar_valor(self, valor):
+ return 0 <= valor <= 10
+
+
+nome_input = ValidaNomeInput()
+nota_input = ValidaNotaInput()
+
+
+nome = nome_input('Digite o nome: ')
+nota = nota_input('Digite a nota: ')
+```
+
+Dessa forma, é possível reutilizar o código já existente para criar outras validações, sendo necessário implementar apenas como converter a `str` lida do teclado para o tipo desejado, e como esse valor deve ser validado. Não é necessário entender e repetir a lógica de ler o valor, validá-lo, imprimir a mensagem de erro, e repetir até que seja informado um valor válido.
+
+## Considerações
+
+É possível encapsular a lógica de um algoritmo em funções ou em classes. Embora para fazê-lo em uma classe exija conhecimentos de programação orientada a objetos, o seu reaproveitamento é facilitado, abstraindo toda a complexidade do algoritmo, que pode ser disponibilizado através de uma biblioteca, exigindo apenas a implementações de métodos simples por quem for a utilizar.
+
+Ainda poderia ser discutido outras formas de fazer essa implementação, como passar funções como parâmetro e a utilização de [corrotinas](https://docs.python.org/pt-br/3/library/asyncio-task.html) no encapsulamento do algoritmo em função, assim como a utilização de [classmethod](https://docs.python.org/pt-br/3/library/functions.html#classmethod), [staticmethod](https://docs.python.org/pt-br/3/library/functions.html#staticmethod) e [ABC](https://docs.python.org/pt-br/3/library/abc.html) no encapsulamento do algoritmo em classes.
+
+---
+
+Esse artigo foi publicado originalmente no [meu blog](https://eduardoklosowski.github.io/blog/), passe por lá, ou siga-me no [DEV](https://dev.to/eduardoklosowski) para ver mais artigos que eu escrevi.
diff --git a/content/explicit_is_better_than_implicit.md b/content/explicit_is_better_than_implicit.md
new file mode 100644
index 000000000..0b45b9587
--- /dev/null
+++ b/content/explicit_is_better_than_implicit.md
@@ -0,0 +1,108 @@
+Title: Explicit is better than implicit
+Date: 2016-04-22 23:00
+Tags: python, zen of python, experências, decisões de desenvolvimento
+Category: Experiências
+Slug: explicit-is-better-than-implicit
+Author: Ivan Neto
+About_author: Desenvolvedor Python, esposo, pai, escritor nas horas vagas
+Email: ivan.cr.neto@gmail.com
+Github: ivancrneto
+Twitter: ivancrneto
+Linkedin: ivanrocha
+
+Esse post não é diretamente relacionado a desenvolvimento com Python, mas conta a história de uma das muitas experiências que passamos desenvolvendo e mostra como a filosofia e o _mindset_ __Python__ podem nos influenciar a tomar decisões melhores.
+
+## Contexto geral
+
+Atualmente trabalho remotamente pela Toptal, uma empresa de consultoria em _software_ com foco em trabalho remoto e que tem um processo seletivo bastante rígido para garantir uma qualidade acima da média para seus clientes ([saiba mais sobre a Toptal aqui](https://www.toptal.com/#book-tested-programmers)).
+
+No time em que faço parte os papéis são bem definidos entre desenvolvedores _front-end_ e _back-end_ e faço parte da equipe de _back-end_, que usa principalmente __Django__ nas aplicações. À medida que evoluímos e nos tornamos mais maduros como time, buscamos soluções que pudessem otimizar nosso processo de desenvolvimento.
+
+Atualmente utilizamos _CircleCI_ -- uma plataforma para integração e entrega contínuas -- para tarefas como rodar nossa suíte de testes, fazer a integração de nosso código, instanciar uma nova versão de nossos sistemas em um ambiente de _staging_ e criar imagens __Docker__ posteriormente colocadas em produção.
+
+## Melhorias
+
+Nosso time constantemente reavalia processos, ferramentas e o resultado são discussões interessantes sobre como tornar nosso trabalho mais rápido e produtivo.
+
+Recentemente começamos a utilizar um servidor __NPM__ -- um dos mais usados gerenciadores de pacotes para __Javascript__ -- privado para uma melhor separação de pacotes _front-end_, otimizando o tempo de _build_ de _assets_ de 47 para 25 segundos.
+
+Na raiz do nosso projeto temos um _package.json_ com o seguinte conteúdo:
+
+``` json
+{
+ // [ ... ]
+ "dependencies": {
+ "cat": "^1.0.0",
+ "front": "^1.0.0",
+ "core": "^1.0.0",
+ },
+ // [ ... ]
+}
+```
+
+Sendo que __cat__, __front__ e __core__ (renomeados para exemplificar) são pacotes mantidos por nós mesmos no __NPM__ privado. Por padrão, se você lista o pacote com `“^”` (como por exemplo acima `“^1.0.0”`), o _npm_ considera apenas o número que representa a _major version_, no caso o número 1, e fará o _download_ da última versão que começa com 1.
+
+Essa abordagem tem quatro pontos fracos:
+
+ 1. Ela pode quebrar seu código. Se pacote de terceiro atualizar, seu código pode não estar preparado para lidar com as novas funcionalidades adicionadas, principalmente porque _libs_ evoluem tão rapidamente que se torna fácil acontecer uma atualização sem _backwards compatibility_.
+ 2. Você não sabe exatamente qual versão do pacote seu sistema está usando em produção. Para saber, você teria que acessar os servidores remotamente e executar o comando `npm list`, por exemplo (poderia fazer localmente também mas existe a possibilidade de que no momento em que ocorreu o _deploy_, aquele pacote estava em uma versão anterior à sua versão local).
+ 3. Você perde o controle de quando quer que seu sistema utilize a nova versão do pacote.
+ 4. Se você precisar fazer um _rollback_ ou usar uma imagem antiga de seu sistema em produção, ainda assim ela vai utilizar a última versão do pacote, o que pode levar a mais dores de cabeça.
+
+# Problema
+
+Recentemente tivemos um _bug_ em produção, e uma mudança no pacote _core_ resolveria. __O que fazer com o sistema principal?__ Nada, não era necessária nenhuma alteração. Só precisaríamos gerar uma nova imagem _Docker_ que ela seria montada do zero e no momento de instalar os pacotes _npm_, baixaria a última versão.
+
+Bastava realizar _rebuild_ na branch _master_ no __CircleCI__, que assim que terminado ele trataria de enviar um _webhook_ para o nossa ferramenta que cria imagens _Docker_. Nós utilizamos o seguinte padrão de nomenclatura dessas imagens:
+```
+myapp-production--
+```
+Como não fizemos nenhuma alteração no sistema principal, o _branch_ e o _sha_ continuaram os mesmos.
+
+Resumindo, nosso _Docker_ recebeu um pedido de _build_ para aquela _branch_ e _sha_ e, por padrão, primeiro procurou em seu _cache_ de imagens se já existia alguma imagem pronta com aquele nome. O resultado foi que a mesma imagem, sem o _hotfix_, foi para produção (pois ela havia sido criada antes e no momento em que baixou os pacotes _npm_ ainda não havia alterações no _core_).
+
+Demoramos um pouco para perceber o problema, mas o suficiente para resolvê-lo sem que _stakeholders_ percebessem.
+
+## Solução
+
+Algum tempo depois discutimos e nós desenvolvedores _back-end_ sugerimos a seguinte solução:
+
+``` json
+{
+ // [ ... ]
+ "dependencies": {
+ "cat": "1.0.5",
+ "front": "1.0.7",
+ "core": "1.0.10",
+ },
+ // [ ... ]
+}
+```
+
+Com essa abordagem:
+
+1. Você pode fazer _rollback_ do seu código sem problemas pois o código antigo vai usar a versão antiga do pacote.
+2. Você tem controle sobre quando quer que seu sistema utilize a nova versão do pacote.
+3. Você sabe exatamente quais versões de pacotes seu sistema está utilizando, bastando abrir o _packages.json_.
+4. Caso uma nova versão quebre seu código, você pode voltar uma versão rapidamente até que o problema seja resolvido.
+
+O problema que tivemos em produção não aconteceria caso tivéssemos utilizado a abordagem acima. Assim que os pacotes fossem atualizados, criaríamos uma _pull request_ no repositório do sistema principal com as seguintes alterações:
+
+``` diff
+diff --git i/package.json w/package.json
+index eaae10d..5aa773b 100644
+--- i/package.json
++++ w/package.json
+@@ -9,7 +9,7 @@
+ "dependencies": {
+ "cat": "1.0.5",
+ "front": "1.0.7",
+- "core": "1.0.10",
++ "core": "1.0.11",
+ },
+```
+Após o _merge_, um novo _build_ aconteceria no __CircleCI__, e um novo _sha_ seria enviado via _webhook_. O _Docker_ não encontraria nenhuma imagem com essa combinação de _branch_ e _sha_ e criaria uma nova do zero. Produção teria o _hotfix_ e não haveria constrangimento.
+
+Os desenvolvedores _front-end_ não gostaram da ideia de ter que atualizar o arquivo toda vez que alguma dependência subisse de versão. Discutimos bastante e a última coisa que eu disse foi: __“from the Zen of Python: explicit is better than implicit”__.
+
+Lição aprendida.
diff --git a/content/extraindo_texto_de_imagens_com_python.md b/content/extraindo_texto_de_imagens_com_python.md
new file mode 100644
index 000000000..30fe41f79
--- /dev/null
+++ b/content/extraindo_texto_de_imagens_com_python.md
@@ -0,0 +1,73 @@
+Title: Extraindo Texto de Imagens com Python
+Date: 2015-11-22 17:00
+Tags: imagens,ocr,pytesseract,extrair texto
+Category: Manipulação de imagens
+Slug: extraindo-texto-de-imagens-com-python
+Author: André Ramos
+Email: andrel.ramos97@gmail.com
+Github: andrelramos
+About_author: Programador web/desktop/mobile. Apaixonado por tecnologia, programação e python.
+
+Introdução
+-----------
+
+Já precisou extrair texto de imagens mas não sabia como? aprenda como fazer isso com apenas 3 linhas de código (Por isso amo python!). Antes de começarmos, vamos ver um pouco de teoria.
+
+### O que é OCR?
+
+Segundo o Wikipedia, OCR é um acrónimo para o inglês Optical Character Recognition, é uma tecnologia para reconhecer caracteres a partir de um arquivo de imagem ou mapa de bits sejam eles escaneados, escritos a mão, datilografados ou impressos. Dessa forma, através do OCR é possível obter um arquivo de texto editável por um computador. A engine OCR que vamos utilizar é a **Tesseract**, a mesma foi inicialmente desenvolvida nos laboratórios da HP e tem seu projeto hospedado em: [https://github.com/tesseract-ocr/tesseract](https://github.com/tesseract-ocr/tesseract). Texto adaptado de: [https://pt.wikipedia.org/wiki/Reconhecimento_ótico_de_caracteres](https://pt.wikipedia.org/wiki/Reconhecimento_%C3%B3tico_de_caracteres)
+
+Como descrito acima, já existe uma tecnologia para realizar essa função, então apenas precisamos utilizá-la em nosso script python e assim desenvolvermos o que a imaginação permitir.
+
+### Instalando Dependências (Ubuntu)
+
+Primeiro vamos começar pela instalação do Tesseract OCR. Abra o terminal e digite o seguinte comando:
+
+ :::bash
+ $ sudo apt-get install tesseract-ocr tesseract-ocr-por
+
+Também precisamos instalar a biblioteca Pillow e suas dependências. Ela será necessária para carregar a imagem para nosso script:
+
+Ubuntu 12.04/14.04:
+
+ :::bash
+ $ sudo apt-get install python-dev python3-dev build-essential liblcms1-dev zlib1g-dev libtiff4-dev libjpeg8-dev libfreetype6-dev libwebp-dev
+ $ sudo -H pip install Pillow
+
+Ubuntu 15.04/15.10/16.04:
+
+ :::bash
+ $ sudo apt-get install python-dev python3-dev build-essential liblcms2-dev zlib1g-dev libtiff5-dev libjpeg8-dev libfreetype6-dev libwebp-dev
+ $ sudo -H pip install Pillow
+
+
+Agora partiremos para a instalação do wrapper que irá permitir a utilização do Tesseract através do python:
+
+ :::bash
+ $ sudo -H pip install pytesseract
+
+
+### Mão Na Massa!
+
+Finalmente chegamos a parte prática desse artigo. Como dito anteriormente, são apenas 3 linhas de código, mas antes de começar baixe a seguinte imagem para realizar seus testes:
+
+
+
+Agora vamos ao código:
+
+ :::python
+
+ from PIL import Image # Importando o módulo Pillow para abrir a imagem no script
+
+ import pytesseract # Módulo para a utilização da tecnologia OCR
+
+ print( pytesseract.image_to_string( Image.open('nome_da_imagem.jpg') ) ) # Extraindo o texto da imagem
+
+Simples né? Mas nem sempre o texto sai 100% correto, depende muito da qualidade da imagem e da quantidade de detalhes que a mesma possui, porem existe algumas técnicas usadas para fazer melhorias na imagem diminuindo a chance de erros na hora da extração.
+
+**Alguns links que podem te ajudar a aproveitar ao maximo da tecnologia OCR:**
+
+* [http://pt.scribd.com/doc/88203318/Como-escanear-livros-com-qualidade-e-produzir-textos-por-OCR#scribd](http://pt.scribd.com/doc/88203318/Como-escanear-livros-com-qualidade-e-produzir-textos-por-OCR#scribd)
+
+* [http://profs.if.uff.br/tjpp/blog/entradas/ocr-de-qualidade-no-linux](http://profs.if.uff.br/tjpp/blog/entradas/ocr-de-qualidade-no-linux)
+
diff --git a/content/fazendo-backup-do-banco-de-dados-no-django.md b/content/fazendo-backup-do-banco-de-dados-no-django.md
new file mode 100644
index 000000000..c8de95af4
--- /dev/null
+++ b/content/fazendo-backup-do-banco-de-dados-no-django.md
@@ -0,0 +1,122 @@
+Title: Fazendo backup do banco de dados no Django
+Date: 2020-10-30 10:40
+Tags: Python,Django,backup
+Category: Python
+Slug: fazendo-backup-do-banco-de-dados-no-django
+Author: Jackson Osvaldo
+Email: jacksonosvaldo@live.com
+Github: JacksonOsvaldo
+About_author: Um curioso apaixonado por livros, tecnologia e programação.
+
+## Apresentação
+
+Em algum momento, durante o seu processo de desenvolvimento com Django, pode ser que surja a necessidade de criar e restaurar o banco de dados da aplicação. Pensando nisso, resolvi fazer um pequeno tutorial, básico, de como realizar essa operação.
+
+Nesse tutorial, usaremos o [django-dbbackup](https://github.com/django-dbbackup/django-dbbackup), um pacote desenvolvido especificamente para isso.
+
+## Configurando nosso ambiente
+
+Primeiro, partindo do início, vamos criar uma pasta para o nosso projeto e, nela, isolar o nosso ambiente de desenvolvimento usando uma [virtualenv](https://virtualenv.pypa.io/en/latest/index.html):
+
+```shell
+mkdir projeto_db && cd projeto_db #criando a pasta do nosso projeto
+
+virtualenv -p python3.8 env && source env/bin/activate #criando e ativando a nossa virtualenv
+```
+
+Depois disso e com o nosso ambiente já ativo, vamos realizar os seguintes procedimentos:
+
+```shell
+pip install -U pip #com isso, atualizamos a verão do pip instalado
+```
+
+## Instalando as dependências
+
+Agora, vamos instalar o [Django](https://www.djangoproject.com/) e o pacote que usaremos para fazer nossos backups.
+
+```shell
+pip install Django==3.1.2 #instalando o Django
+
+pip install django-dbbackup #instalando o django-dbbackup
+```
+
+## Criando e configurando projeto
+
+Depois de instaladas nossas dependências, vamos criar o nosso projeto e configurar o nosso pacote nas configurações do Django.
+
+```shell
+django-admin startproject django_db . #dentro da nossa pasta projeto_db, criamos um projeto Django com o nome de django_db.
+```
+
+Depois de criado nosso projeto, vamos criar e popular o nosso banco de dados.
+
+```shell
+python manage.py migrate #com isso, sincronizamos o estado do banco de dados com o conjunto atual de modelos e migrações.
+```
+
+Criado nosso banco de dados, vamos criar um superusuário para podemos o painel admin do nosso projeto.
+
+```shell
+python manage.py createsuperuser
+```
+
+Perfeito. Já temos tudo que precisamos para executar nosso projeto. Para execução dele, é só fazermos:
+
+```shell
+python manage.py runserver
+```
+
+Você terá uma imagem assim do seu projeto:
+
+
+
+## Configurando o django-dbbackup
+
+Dentro do seu projeto, vamos acessar o arquivo settings.py, como expresso abaixo:
+
+```shell
+django_db/
+├── settings.py
+```
+
+Dentro desse arquivos iremos, primeiro, adiconar o django-dbbackup às apps do projeto:
+
+```python
+INSTALLED_APPS = (
+ ...
+ 'dbbackup', # adicionando django-dbbackup
+)
+```
+
+Depois de adicionado às apps, vamos dizer para o Django o que vamos salvar no backup e, depois, indicar a pasta para onde será encaminhado esse arquivo. Essa inserção deve ou pode ser feita no final do arquivo _settings.py_:
+
+```python
+DBBACKUP_STORAGE = 'django.core.files.storage.FileSystemStorage' #o que salvar
+DBBACKUP_STORAGE_OPTIONS = {'location': 'backups/'} # onde salvar
+```
+
+Percebam que dissemos para o Django salvar o backup na pasta _backups_, mas essa pasta ainda não existe no nosso projeto. Por isso, precisamos criá-la [fora da pasta do projeto]:
+
+```shell
+mkdir backups
+```
+
+## Criando e restaurando nosso backup
+
+Já temos tudo pronto. Agora, vamos criar o nosso primeiro backup:
+
+```shell
+python manage.py dbbackup
+```
+
+Depois de exetudado, será criado um arquivo -- no nosso exemplo, esse arquivo terá uma extensão .dump --, salvo na pasta _backups_. Esse arquivo contem todo backup do nosso banco de dados.
+
+Para recuperarmos nosso banco, vamos supor que migramos nosso sistema de um servidor antigo para um novo e, por algum motivo, nossa base de dados foi corrompida, inviabilizando seu uso. Ou seja, estamos com o sistema/projeto sem banco de dados -- ou seja, exlua ou mova a a sua base dados .sqlite3 para que esse exemplo seja útil --, mas temos os backups. Com isso, vamos restaurar o banco:
+
+```shell
+python manage.py dbrestore
+```
+
+Prontinho, restauramos nosso banco de dados. O interessante do django-dbbackup, dentre outras coisas, é que ele gera os backups com datas e horários específicos, facilitando o processo de recuperação das informações mais recentes.
+
+Por hoje é isso, pessoal. Até a próxima. ;)
diff --git a/content/fork_clone_push_pull-request.rst b/content/fork_clone_push_pull-request.rst
index 1e6566a24..b2e34cb0d 100644
--- a/content/fork_clone_push_pull-request.rst
+++ b/content/fork_clone_push_pull-request.rst
@@ -54,7 +54,7 @@ Arquivo .gitignore:
O que é o arquivo .gitignore: http://pt-br.gitready.com/iniciante/2009/01/19/ignoring-files.html
-Gerador de arquivo .gitignore: http://www.gitignore.io/
+Gerador de arquivo .gitignore: https://www.toptal.com/developers/gitignore
Exemplos de arquivo .gitignore para diversas linguagens: https://github.com/github/gitignore
diff --git a/content/funcao-inplace-ou-copia-de-valor.md b/content/funcao-inplace-ou-copia-de-valor.md
new file mode 100644
index 000000000..fda5844c7
--- /dev/null
+++ b/content/funcao-inplace-ou-copia-de-valor.md
@@ -0,0 +1,177 @@
+Title: Funções in place ou cópia de valor
+Slug: funcao-inplace-ou-copia-de-valor
+Date: 2021-03-29 12:00
+Category: Python
+Tags: python, funções
+Author: Eduardo Klosowski
+Email: eduardo_klosowski@yahoo.com
+Github: eduardoklosowski
+Twitter: eduklosowski
+Site: https://dev.to/eduardoklosowski
+About_author: Programador, formado em redes de computadores e estuda DevOps
+
+Eventualmente observo dificuldades de algumas pessoas em usar corretamente alguma função, seja porque a função deveria ser executada isoladamente, e utilizado a própria variável que foi passada como argumento posteriormente, seja porque deveria se atribuir o retorno da função a alguma variável, e utilizar essa nova variável. No Python, essa diferença pode ser observada nos métodos das listas `sort` e `reverse` para as funções `sorted` e `reversed`, que são implementadas com padrões diferentes, *in place* e cópia de valor respectivamente. Assim pretendo discutir esses dois padrões de funções, comentando qual a diferença e o melhor caso de aplicação de cada padrão.
+
+## Função de exemplo
+
+Para demonstrar como esses padrões funcionam, será implementado uma função que recebe uma lista e calcula o dobro dos valores dessa lista. Exemplo:
+
+```python
+entrada = [5, 2, 8, 6, 4]
+
+# Execução da função
+
+resultado = [10, 4, 16, 12, 8]
+```
+
+### Função com in place
+
+A ideia do padrão *in place* é alterar a própria variável recebida pela função (ou o próprio objeto, caso esteja lidando com orientação a objetos). Neste caso, bastaria calcular o dobro do valor de cada posição da lista, e sobrescrever a posição com seu resultado. Exemplo:
+
+```python
+from typing import List
+
+
+def dobro_inplace(lista: List[int]) -> None:
+ for i in range(len(lista)):
+ lista[i] = 2 * lista[i]
+
+
+valores = [5, 2, 8, 6, 4]
+retorno = dobro_inplace(valores)
+
+print(f'Variável: valores | Tipo: {type(valores)} | Valor: {valores}')
+print(f'Variável: retorno | Tipo: {type(retorno)} | Valor: {retorno}')
+```
+
+Resultado da execução:
+
+```
+Variável: valores | Tipo: | Valor: [10, 4, 16, 12, 8]
+Variável: retorno | Tipo: | Valor: None
+```
+
+Com essa execução é possível observar que os valores da lista foram alterados, e que o retorno da função é nulo (`None`), ou seja, a função alterou a própria lista passada como argumento. Outro ponto importante a ser observado é a assinatura da função (tipo dos argumentos e do retorno da função), que recebe uma lista de inteiros e não tem retorno ou é nulo (`None`). Dessa forma embora seja possível chamar essa função diretamente quando está se informando os argumentos de outra função, como `print(dobro_inplace(valores))`, a função `print` receberia `None` e não a lista como argumento.
+
+### Função com cópia de valor
+
+A ideia do padrão cópia de valor é criar uma cópia do valor passado como argumento e retornar essa cópia, sem alterar a variável recebida (ou criando um novo objeto, no caso de orientação a objetos). Neste caso, é necessário criar uma nova lista e adicionar nela os valores calculados. Exemplo:
+
+```python
+from typing import List
+
+
+def dobro_copia(lista: List[int]) -> List[int]:
+ nova_lista = []
+
+ for i in range(len(lista)):
+ nova_lista.append(2 * lista[i])
+
+ return nova_lista
+
+
+valores = [5, 2, 8, 6, 4]
+retorno = dobro_copia(valores)
+
+print(f'Variável: valores | Tipo: {type(valores)} | Valor: {valores}')
+print(f'Variável: retorno | Tipo: {type(retorno)} | Valor: {retorno}')
+```
+
+Resultado da execução:
+
+```
+Variável: valores | Tipo: | Valor: [5, 2, 8, 6, 4]
+Variável: retorno | Tipo: | Valor: [10, 4, 16, 12, 8]
+```
+
+Com essa execução é possível observar que a variável `valores` continua com os valores que tinha antes da execução da função, e a variável retorno apresenta uma lista com os dobros, ou seja, a função não altera a lista passada como argumento e retorna uma nova lista com os valores calculados. Observado a assinatura da função, ela recebe uma lista de inteiros e retorna uma lista de inteiros. Isso permite chamar essa função diretamente nos argumentos para outra função, como `print(dobro_copia(valores))`, nesse caso a função `print` receberia a lista de dobros como argumento. Porém caso o retorno da função não seja armazenado, parecerá que a função não fez nada, ou não funcionou. Então em alguns casos, quando o valor anterior não é mais necessário, pode-se reatribuir o retorno da função a própria variável passada como argumento:
+
+```python
+valores = dobro_copia(valores)
+```
+
+### Função híbrida
+
+Ainda é possível mesclar os dois padrões de função, alterando o valor passado e retornando-o. Exemplo:
+
+```python
+from typing import List
+
+
+def dobro_hibrido(lista: List[int]) -> List[int]:
+ for i in range(len(lista)):
+ lista[i] = 2 * lista[i]
+
+ return lista
+
+
+valores = [5, 2, 8, 6, 4]
+retorno = dobro_hibrido(valores)
+
+print(f'Variável: valores | Tipo: {type(valores)} | Valor: {valores}')
+print(f'Variável: retorno | Tipo: {type(retorno)} | Valor: {retorno}')
+```
+
+Resultado da execução:
+
+```
+Variável: valores | Tipo: | Valor: [10, 4, 16, 12, 8]
+Variável: retorno | Tipo: | Valor: [10, 4, 16, 12, 8]
+```
+
+Nesse caso, pode-se apenas chamar a função, como também utilizá-la nos argumentos de outras funções. Porém para se ter os valores originais, deve-se fazer uma cópia manualmente antes de executar a função.
+
+## Exemplo na biblioteca padrão
+
+Na biblioteca padrão do Python, existem os métodos `sort` e `reverse` que seguem o padrão *in place*, e as funções `sorted` e `reversed` que seguem o padrão cópia de valor, podendo ser utilizados para ordenar e inverter os valores de uma lista, por exemplo. Quando não é mais necessário uma cópia da lista com a ordem original, é preferível utilizar funções *in place*, que alteram a própria lista, e como não criam uma cópia da lista, utilizam menos memória. Exemplo:
+
+```python
+valores = [5, 2, 8, 6, 4]
+valores.sort()
+valores.reverse()
+print(valores)
+```
+
+Se for necessário manter uma cópia da lista inalterada, deve-se optar pelas funções de cópia de valor. Exemplo:
+
+```python
+valores = [5, 2, 8, 6, 4]
+novos_valores = reversed(sorted(valores))
+print(novos_valores)
+```
+
+Porém esse exemplo cria duas cópias da lista, uma em cada função. Para criar apenas uma cópia, pode-se misturar funções *in place* com cópia de valor. Exemplo:
+
+```python
+valores = [5, 2, 8, 6, 4]
+novos_valores = sorted(valores)
+novos_valores.reverse()
+print(novos_valores)
+```
+
+Também vale observar que algumas utilizações dessas funções podem dar a impressão de que elas não funcionaram, como:
+
+```python
+valores = [5, 2, 8, 6, 4]
+
+sorted(valores)
+print(valores) # Imprime a lista original, e não a ordenada
+
+print(valores.sort()) # Imprime None e não a lista
+```
+
+## Considerações
+
+Nem sempre é possível utilizar o padrão desejado, *strings* no Python (`str`) são imutáveis, logo todas as funções que manipulam elas seguiram o padrão cópia de valor, e para outros tipos, pode ocorrer de só existir funções *in place*, sendo necessário fazer uma cópia manualmente antes de chamar a função, caso necessário. Para saber qual padrão a função implementa, é necessário consultar sua documentação, ou verificando sua assinatura, embora ainda possa existir uma dúvida entre cópia de valor e híbrida, visto que a assinatura dos dois padrões são iguais.
+
+Os exemplos aqui dados são didáticos. Caso deseja-se ordenar de forma reversa, tanto o método `sort`, quanto a função `sorted` podem receber como argumento `reverse=True`, e assim já fazer a ordenação reversa. Assim como é possível criar uma nova lista já com os valores, sem precisar adicionar manualmente item por item, como os exemplos:
+
+```python
+valores = [5, 2, 8, 6, 4]
+partes_dos_valores = valores[2:]
+novos_valores = [2 * valor for valor in valores]
+```
+
+---
+
+Esse artigo foi publicado originalmente no [meu blog](https://eduardoklosowski.github.io/blog/), passe por lá, ou siga-me no [DEV](https://dev.to/eduardoklosowski) para ver mais artigos que eu escrevi.
diff --git a/content/gerando-relatorios-de-teste-com-coveralls.md b/content/gerando-relatorios-de-teste-com-coveralls.md
new file mode 100644
index 000000000..569baf09a
--- /dev/null
+++ b/content/gerando-relatorios-de-teste-com-coveralls.md
@@ -0,0 +1,146 @@
+Title: Relatórios de testes com Coveralls
+Slug: gerando-relatorios-de-testes-com-coveralls
+Date: 2016-06-03 11:28:55
+Category: Python
+Tags: python, coveralls, coverage, relatório, test
+Author: Michell Stuttgart
+Email: michellstut@gmail.com
+Github: mstuttgart
+Linkedin: mstuttgart
+Site: https://mstuttgart.github.io/
+
+Na [terceira parte](https://mstuttgart.github.io/2016/04/29/2016-04-29-python-com-unittest-travis-ci-coveralls-e-landscape-parte-3-de-4/) do tutorial sobre *unittest*, vimos como utilizar o serviço [Coveralls](https://coveralls.io/) para gerar relatórios sobre o testes do nosso projeto. Entretanto, uma "desvantagem" do serviço é que o processo de análise é iniciado apenas quando executarmos um *push* ou um *pull request*. Sendo assim, não seria interessante termos a liberdade de executar esses testes localmente?
+
+Felizmente, os desenvolvedores do [Coveralls](https://coveralls.io/) pensaram nisso e criaram um conjunto de comandos que nos permite executá-lo pelo terminal.
+
+### Instalação
+
+Então, antes de iniciarmos, vamos a instalação do módulo, que pode ser feito pelo comando a seguir:
+
+```bash
+pip install coveralls
+```
+
+Quando você instala o módulo, um *script* de linha de comando chamado `coverage` é adicionado ao diretório de *scripts* python no seu sistema. Para suportar diferentes versões do Python, o módulo vem com um conjunto de *scripts*. Então, para a versão 2.7 do Python, você pode utilizar o comando `coverage` ou `coverage2`. Para a versão 3, utilize `coverage3`.
+
+### Gerando relatórios
+
+O comando usado para obtermos um relatório sobre os testes do nosso projeto é simples. No diretório do projeto, basta executar:
+
+```bash
+coverage run --source=nomedopacote setup.py test
+```
+o comando `run` irá coletar dados sobre nosso código fonte. No nosso caso, usaremos o repositorio que criamos para o tutorial anterior: [codigo-avulso-test-tutorial](https://github.com/mstuttgart/codigo-avulso-test-tutorial). Assim, o comando seria:
+
+```bash
+coverage run --source=codigo_avulso_test_tutorial setup.py test
+```
+
+Se você executar o comando `ls -la` no terminal, verá que um arquivo chamando `.coverage` foi criado. Esse arquivo contém algumas informações sobre o seu código. Vale alertar que para gerar os relatórios precisamos, obrigatoriamente, executar o comando acima, quando formos gerar o relatórios pela primeira vez ou quando o código sofrer alguma modificação.
+
+Uma vez que o arquivo `.coverage` foi gerado, execute o seguinte comando:
+
+```bash
+coverage report
+```
+
+Um relatório com a porcentagem de cobertura de testes (entre outras informações) de cada arquivo de código fonte será exibido no terminal.
+
+```bash
+Name Stmts Miss Cover
+----------------------------------------------------------------------
+codigo_avulso_test_tutorial/__init__.py 0 0 100%
+codigo_avulso_test_tutorial/circulo.py 9 0 100%
+codigo_avulso_test_tutorial/figura_geometrica.py 5 0 100%
+codigo_avulso_test_tutorial/quadrado.py 8 0 100%
+----------------------------------------------------------------------
+TOTAL 22 0 100%
+```
+
+As colunas exibidas no relatório possuem informações interessantes. São elas:
+
+* Stmts: indica o total de trechos do código que, segundo o Coveralls, devem ser testados.
+* Miss: coluna que indica quantos trechos do código ainda não estão sob testes.
+* Cover: talvez a coluna mais importante, ela indica a porcentagem de cobertura de testes do arquivo fonte.
+
+Em `TOTAL` temos a cobertura total de testes do nosso projeto. Nesse projeto em especial, temos 100% porque o mesmo possui pouco código e os códigos existentes são simples de testar. Entretanto, em projeto mais complexos, nem sempre é possível chegar nessa porcentagem, então vale a pena se focar em escrever testes para as partes mais críticas do seu código e conseguir uma porcentagem perto dos 80%, considerado pelo `Coveralls` como satisfatório.
+
+#### Gerando relatório em HTML
+
+Uma opção interessante é gerar o relatório em formato `html` com o comando:
+
+```bash
+coverage html
+```
+
+Um diretório chamado `htmlcov` será criado no diretório do projeto. Dentro desse diretório existe um arquivo de nome `index.html`, que pode ser aberto no navegador.
+
+Para o Google Chrome, usamos:
+
+```bash
+google-chrome htmlcov/index.html
+```
+ou com o Firefox
+
+```bash
+firefox htmlcov/index.html
+```
+
+Abaixo temos o `index.html` aberto. Nele podemos ver a lista dos arquivos que estão cobertos pelo `Coveralls`.
+
+
+
+
+
+Vamos analisar os dados do arquivo `circulo.py`. Assim, temos as seguintes colunas:
+
+* `statements`: indica o total de trechos do código que, segundo o Coveralls, devem ser testadas. No caso do arquivo `circulo.py`, o valor da coluna é 9, indicando que existem 9 trechos do código quem devem estar sob teste.
+* `missing`: coluna que indica quantos trechos do código ainda não estão sob testes.
+* `excluded`: coluna que indica quantos trechos do código foram ignorados pelo Coveralls. Algumas vezes pode ser necessário excluir alguns trechos de código do relatório devido ao tipo de código nele contido ou porque você simplesmente não deseja que aquele trecho seja incluido no relatorio. Isso é feito através de um arquivo de configuração, visto mais adiante.
+* `coverage`: indica a porcentagem de cobertura de testes do nosso código.
+
+Na imagem abaixo, logo após clicarmos em `codigo_avulso_test_tutorial/circulo.py`, podemos ver os pontos do código que devem ser testados.
+
+
+
+
+
+Ao clicarmos nos três botões no cabeçalho da página:
+
+
+
+
+
+A página irá destacar, respectivamente, os trechos cobertos por testes, trechos sem testes ou que foram excluídos do `Coveralls`.
+
+#### Gerando relatório em XML
+Os relatórios em XML podem ser facilmente gerados por:
+
+```bash
+coverage xml
+```
+Um arquivo chamado `coverage.xml` será criado.
+
+#### Criando o arquivo coveragerc
+
+O arquivo `.coveragesrc` é usado para determinar parâmetros de funcionamento do comando `coverage`. Nele podemos incluir e excluir aquivos da analise do `Coveralls` entre outras configurações. Abaixo temos um exemplo de arquivo de configuração.
+
+```
+[run]
+source = codigo_avulso_test_tutorial
+omit =
+ codigo_avulso_test_tutorial/__init__.py
+ codigo_avulso_test_tutorial/main.py
+```
+
+Na configuração acima, vamos omitir da análise o arquivo `__init__.py` e um arquivo `main.py`. Indicamos o *source* que é onde o `Coveralls` deve executar a análise.
+
+O arquivo de configuração ainda pode receber várias informações. Você pode ver mais delas [aqui](http://coverage.readthedocs.io/en/latest/source.html#source).
+
+### Conclusão
+
+Neste tutorial vimos um pouco mais sobre o `Coveralls`. Evitei colocar as informações deste tutorial nos tutoriais anteriores a fim de simplificá-los. Você pode aprender mais sobre o módulo consultando sua documentação [aqui](http://coverage.readthedocs.io/en/latest/index.html).
+
+É isso pessoal, obrigado pela leitura e até o próximo tutorial.
+
+**Publicado originalmente:** [gerando-relatorios-de-testes-com-coveralls](https://mstuttgart.github.io/2016/05/18/2016-05-18-gerando-relatorios-de-teste-com-coveralls/)
diff --git a/content/github-pages-com-pelican-e-travis-ci.rst b/content/github-pages-com-pelican-e-travis-ci.rst
new file mode 100644
index 000000000..22fe6c91a
--- /dev/null
+++ b/content/github-pages-com-pelican-e-travis-ci.rst
@@ -0,0 +1,261 @@
+GitHub Pages com Pelican e Travis-CI
+====================================
+
+:slug: github-pages-com-pelican-e-travis-ci
+:date: 2016-05-04 21:46
+:tags: tutorial,gh-pages,pelican,travis-ci
+:category: Pelican
+:author: Humberto Rocha
+:email: humrochagf@gmail.com
+:github: humrochagf
+:twitter: humrochagf
+:facebook: humrochagf
+:linkedin: humrochagf
+
+**Publicado originalmente em:** `df.python.org.br/blog/github-pages-com-pelican-e-travis-ci`_
+
+Olá pessoal!
+
+Estou fazendo esta postagem para ajudar quem quer criar seu site no `GitHub Pages`_ usando `Pelican`_ para a criação das páginas e o `Travis-CI`_ para automatizar a tarefa de geração e publicação.
+
+Este guia assume que o leitor possua conta no `GitHub`_ e no `Travis-CI`_ e tenha familiaridade com o ambiente python. A versão do pelican utilizada ao elaborar esta publicação foi a 3.6.
+
+O GitHub Pages
+--------------
+
+O GitHub Pages é uma funcionalidade gratuita do GitHub para hospedar conteúdo estático (html, css, js e imagens) e publicar através de um sub-domínio de **github.io** ou até mesmo de um domínio customizado. É baseado em seu funcionamento que iremos guiar nossos próximos passos.
+
+Resumidamente existem duas formas de se criar uma página pelo gh pages:
+
+**1 - Página de usuário/organização**
+
+Para este tipo de página crie um repositório com o nome ``usuario.github.io``, onde ``usuario`` é o nome de usuário ou organização da conta em que o repositório será criado:
+
+.. figure:: /images/humrochagf/gh-pelican-travis/pagina-usuario.png
+ :alt: Repositório da página de usuário
+ :align: center
+
+O conteúdo a ser publicado deve ser colocado na **branch master** e os arquivos do pelican na **branch pelican**.
+
+**2 - Página de projeto**
+
+Para este tipo de página crie um repositório com o nome ``meuprojeto``, onde ``meuprojeto`` é o nome desejado para o projeto que será publicado em ``usuario.github.io``:
+
+.. figure:: /images/humrochagf/gh-pelican-travis/pagina-projeto.png
+ :alt: Repositório da página de projeto
+ :align: center
+
+O conteúdo a ser publicado deve ser colocado na **branch gh-pages** e os arquivos do pelican na **branch pelican**.
+
+Para mais informações acesse o site oficial do `GitHub Pages`_.
+
+Pelican
+-------
+
+O pelican é um gerador de site estático otimizado por padrão para criação de blogs. Utilizaremos aqui, para fins de demonstração, o modelo padrão do de blog seguindo o caminho de criação de página de usuário/organização, qualquer diferença do caminho de página de projeto será descrita quando necessário.
+
+Para instalar o pelican basta rodar o comando:
+
+.. code-block:: bash
+
+ $ pip install pelican==3.6
+
+Para criar um projeto faça:
+
+.. code-block:: bash
+
+ $ mkdir humrochagf.github.io
+ $ cd humrochagf.github.io
+ $ pelican-quickstart
+ Welcome to pelican-quickstart v3.6.3.
+
+ This script will help you create a new Pelican-based website.
+
+ Please answer the following questions so this script can generate the files
+ needed by Pelican.
+
+
+ > Where do you want to create your new web site? [.]
+ > What will be the title of this web site? Meu Blog
+ > Who will be the author of this web site? Humberto Rocha
+ > What will be the default language of this web site? [en] pt
+ > Do you want to specify a URL prefix? e.g., http://example.com (Y/n) n
+ > Do you want to enable article pagination? (Y/n) y
+ > How many articles per page do you want? [10]
+ > What is your time zone? [Europe/Paris] America/Sao_Paulo
+ > Do you want to generate a Fabfile/Makefile to automate generation and publishing? (Y/n) y
+ > Do you want an auto-reload & simpleHTTP script to assist with theme and site development? (Y/n) y
+ > Do you want to upload your website using FTP? (y/N) n
+ > Do you want to upload your website using SSH? (y/N) n
+ > Do you want to upload your website using Dropbox? (y/N) n
+ > Do you want to upload your website using S3? (y/N) n
+ > Do you want to upload your website using Rackspace Cloud Files? (y/N) n
+ > Do you want to upload your website using GitHub Pages? (y/N) y
+ > Is this your personal page (username.github.io)? (y/N) y
+ Done. Your new project is available at /caminho/para/humrochagf.github.io
+
+Inicialize um repositório neste diretório e suba os dados para a **branch pelican**:
+
+.. code-block:: bash
+
+ $ git init
+ $ git remote add origin git@github.com:humrochagf/humrochagf.github.io.git
+ $ git checkout -b pelican
+ $ git add .
+ $ git commit -m 'iniciando branch pelican'
+ $ git push origin pelican
+
+Para publicar o conteúdo na **branch master** é necessário o módulo ghp-import:
+
+.. code-block:: bash
+
+ $ pip install ghp-import
+ $ echo 'pelican==3.6\nghp-import' > requirements.txt
+ $ git add requirements.txt
+ $ git commit -m 'adicionando requirements'
+ $ git push origin pelican
+
+
+Publicando o blog:
+
+.. code-block:: bash
+
+ $ make github
+
+.. figure:: /images/humrochagf/gh-pelican-travis/blog.png
+ :alt: Primeira publicação do blog
+ :align: center
+
+Para publicar no caso da página de projeto altere o conteúdo da variável ``GITHUB_PAGES_BRANCH`` do makefile de ``master`` para ``gh-pages``.
+
+Agora que o nosso blog está rodando no gh pages vamos automatizar a tarefa de geração das páginas para poder alterar o conteúdo do blog e fazer novas postagens sem precisar estar um uma máquina com o ambiente do pelican configurado.
+
+Travis-CI
+---------
+
+O Travis-CI é uma plataforma de Integração Contínua que monta e testa projetos hospedados no github e será nossa ferramenta para automatizar a montagem das páginas do blog.
+
+A Primeira coisa a ser feita é ir ao `Travis-CI`_ e habilitar seu repositório.
+
+.. figure:: /images/humrochagf/gh-pelican-travis/travis-repo1.png
+ :alt: Habilitando repositório no travis
+ :align: center
+
+Em seguida vá nas configurações do repositório no travis e desabilite a opção **Build pull requests** para seu blog não ser atualizado quando alguém abrir um pull request e habilite o **Build only if .travis.yml is present** para que somente a branch que possuir o arquivo .travis.yml gerar atualização no blog.
+
+.. figure:: /images/humrochagf/gh-pelican-travis/travis-repo2.png
+ :alt: Configurando remositório no travis
+ :align: center
+
+O próximo passo é criar uma **Deploy Key** para que o travis possa publicar conteúdo no github. Para isso gere uma chave ssh na raiz do repositório local:
+
+.. code-block:: bash
+
+ $ ssh-keygen -f publish-key
+ Generating public/private rsa key pair.
+ Enter passphrase (empty for no passphrase):
+ Enter same passphrase again:
+ Your identification has been saved in publish-key.
+ Your public key has been saved in publish-key.pub.
+
+Criada a chave vamos cifrar usando a ferramenta `Travis-CLI`_ (certifique-se de que esteja instalada em sua máquina) para poder publicar em nosso repositório sem expor o conteúdo da chave privada:
+
+.. code-block:: bash
+
+ $ travis encrypt-file publish-key
+ Detected repository as humrochagf/humrochagf.github.io, is this correct? |yes| yes
+ encrypting publish-key for humrochagf/humrochagf.github.io
+ storing result as publish-key.enc
+ storing secure env variables for decryption
+
+ Please add the following to your build script (before_install stage in your .travis.yml, for instance):
+
+ openssl aes-256-cbc -K $encrypted_591fe46d4973_key -iv $encrypted_591fe46d4973_iv -in publish-key.enc -out publish-key -d
+
+ Pro Tip: You can add it automatically by running with --add.
+
+ Make sure to add publish-key.enc to the git repository.
+ Make sure not to add publish-key to the git repository.
+ Commit all changes to your .travis.yml.
+
+Como dito no resultado do comando podemos adicionar a opção `--add` para já adicionar as informações no `.travis.yml`, porém, para evitar de sobrescrever algum comando que venha existir no seu arquivo é recomendado editar manualmente.
+
+Em nosso caso iremos criar o arquivo:
+
+.. code-block:: bash
+
+ $ touch .travis.yml
+
+E adicionar o seguinte conteúdo:
+
+.. code-block:: yaml
+
+ sudo: false
+ branches:
+ only:
+ - pelican
+ language: python
+ before_install:
+ # troque a linha abaixo pelo resultado do comando:
+ # travis encrypt-file publish-key
+ # porém mantenha o final:
+ # -out ~/.ssh/publish-key -d
+ - openssl aes-256-cbc -K $encrypted_591fe46d4973_key -iv $encrypted_591fe46d4973_iv -in publish-key.enc -out ~/.ssh/publish-key -d
+ - chmod u=rw,og= ~/.ssh/publish-key
+ - echo "Host github.com" >> ~/.ssh/config
+ - echo " IdentityFile ~/.ssh/publish-key" >> ~/.ssh/config
+ # substitua git@github.com:humrochagf/humrochagf.github.io.git
+ # pelo endereço de acesso ssh do seu repositório
+ - git remote set-url origin git@github.com:humrochagf/humrochagf.github.io.git
+ # Caso esteja montando a página de projeto troque master:master
+ # por gh-pages:gh-pages
+ - git fetch origin -f master:master
+ install:
+ - pip install --upgrade pip
+ - pip install -r requirements.txt
+ script:
+ - make github
+
+Removemos em seguida a chave privada não cifrada para não correr o risco de publicar no repositório:
+
+.. code-block:: bash
+
+ $ rm publish-key
+
+**ATENÇÃO**: Em hipótese alguma adicione o arquivo **publish-key** em seu repositório, pois ele contém a chave privada não cifrada que tem poder de commit em seu repositório, e não deve ser publicada. Adicione somente o arquivo **publish-key.enc**. Se você adicionou por engano refaça os passos de geração da chave e cifração para gerar uma chave nova.
+
+Agora adicionaremos os arquivos no repositório:
+
+.. code-block:: bash
+
+ $ git add .travis.yml publish-key.enc
+ $ git commit -m 'adicionando arquivos do travis'
+ $ git push origin pelican
+
+Para liberar o acesso do travis adicionaremos a deploy key no github com o conteúdo da chave pública **publish-key.pub**:
+
+.. figure:: /images/humrochagf/gh-pelican-travis/deploy-key.png
+ :alt: Adicionando a deploy key no github
+ :align: center
+
+Pronto, agora podemos publicar conteúdo em nosso blog sem a necessidade de ter o pelican instalado na máquina:
+
+.. figure:: /images/humrochagf/gh-pelican-travis/primeira-postagem1.png
+ :alt: Fazendo a primeira postagem
+ :align: center
+
+Que o travis irá publicar para você:
+
+.. figure:: /images/humrochagf/gh-pelican-travis/primeira-postagem2.png
+ :alt: Blog com a primeira postagem
+ :align: center
+
+Caso você tenha animado de criar seu blog pessoal e quer saber mais sobre pelican você pode acompanhar a série do `Mind Bending`_ sobre o assunto.
+
+.. _df.python.org.br/blog/github-pages-com-pelican-e-travis-ci: http://df.python.org.br/blog/github-pages-com-pelican-e-travis-ci
+.. _GitHub Pages: http://pages.github.com
+.. _Pelican: http://blog.getpelican.com
+.. _Travis-CI: https://travis-ci.org
+.. _GitHub: http://github.com
+.. _Travis-CLI: https://github.com/travis-ci/travis.rb
+.. _Mind Bending: http://mindbending.org/pt/series/migrando-para-o-pelican
diff --git a/content/images/andrealmar/Generator_FooFighters.jpg b/content/images/andrealmar/Generator_FooFighters.jpg
new file mode 100644
index 000000000..139ecea50
Binary files /dev/null and b/content/images/andrealmar/Generator_FooFighters.jpg differ
diff --git a/content/images/andrelramos/ocr2.png b/content/images/andrelramos/ocr2.png
new file mode 100644
index 000000000..112b855fe
Binary files /dev/null and b/content/images/andrelramos/ocr2.png differ
diff --git a/content/images/drgarcia1986/is_a_trap.png b/content/images/drgarcia1986/is_a_trap.png
new file mode 100644
index 000000000..1540dd275
Binary files /dev/null and b/content/images/drgarcia1986/is_a_trap.png differ
diff --git a/content/images/eduardoklosowski/oo-de-outra-forma-4/mro.png b/content/images/eduardoklosowski/oo-de-outra-forma-4/mro.png
new file mode 100644
index 000000000..9a126c665
Binary files /dev/null and b/content/images/eduardoklosowski/oo-de-outra-forma-4/mro.png differ
diff --git a/content/images/eduardoklosowski/questoes-para-estudo-de-algoritmos/fatorial.png b/content/images/eduardoklosowski/questoes-para-estudo-de-algoritmos/fatorial.png
new file mode 100644
index 000000000..b999023c7
Binary files /dev/null and b/content/images/eduardoklosowski/questoes-para-estudo-de-algoritmos/fatorial.png differ
diff --git a/content/images/eduardoklosowski/questoes-para-estudo-de-algoritmos/fibonacci.png b/content/images/eduardoklosowski/questoes-para-estudo-de-algoritmos/fibonacci.png
new file mode 100644
index 000000000..4fa54d20f
Binary files /dev/null and b/content/images/eduardoklosowski/questoes-para-estudo-de-algoritmos/fibonacci.png differ
diff --git a/content/images/ehriq/ConfigPyCharm.png b/content/images/ehriq/ConfigPyCharm.png
new file mode 100644
index 000000000..601b78636
Binary files /dev/null and b/content/images/ehriq/ConfigPyCharm.png differ
diff --git a/content/images/erichideki/dreampie.jpg b/content/images/erichideki/dreampie.jpg
new file mode 100644
index 000000000..9073bd5b5
Binary files /dev/null and b/content/images/erichideki/dreampie.jpg differ
diff --git a/content/images/humrochagf/gh-pelican-travis/banner.png b/content/images/humrochagf/gh-pelican-travis/banner.png
new file mode 100644
index 000000000..559f0bbbf
Binary files /dev/null and b/content/images/humrochagf/gh-pelican-travis/banner.png differ
diff --git a/content/images/humrochagf/gh-pelican-travis/blog.png b/content/images/humrochagf/gh-pelican-travis/blog.png
new file mode 100644
index 000000000..1935a7aac
Binary files /dev/null and b/content/images/humrochagf/gh-pelican-travis/blog.png differ
diff --git a/content/images/humrochagf/gh-pelican-travis/deploy-key.png b/content/images/humrochagf/gh-pelican-travis/deploy-key.png
new file mode 100644
index 000000000..55a55f222
Binary files /dev/null and b/content/images/humrochagf/gh-pelican-travis/deploy-key.png differ
diff --git a/content/images/humrochagf/gh-pelican-travis/pagina-projeto.png b/content/images/humrochagf/gh-pelican-travis/pagina-projeto.png
new file mode 100644
index 000000000..d7ea9061d
Binary files /dev/null and b/content/images/humrochagf/gh-pelican-travis/pagina-projeto.png differ
diff --git a/content/images/humrochagf/gh-pelican-travis/pagina-usuario.png b/content/images/humrochagf/gh-pelican-travis/pagina-usuario.png
new file mode 100644
index 000000000..0dfd1dc13
Binary files /dev/null and b/content/images/humrochagf/gh-pelican-travis/pagina-usuario.png differ
diff --git a/content/images/humrochagf/gh-pelican-travis/primeira-postagem1.png b/content/images/humrochagf/gh-pelican-travis/primeira-postagem1.png
new file mode 100644
index 000000000..37621788f
Binary files /dev/null and b/content/images/humrochagf/gh-pelican-travis/primeira-postagem1.png differ
diff --git a/content/images/humrochagf/gh-pelican-travis/primeira-postagem2.png b/content/images/humrochagf/gh-pelican-travis/primeira-postagem2.png
new file mode 100644
index 000000000..01097f831
Binary files /dev/null and b/content/images/humrochagf/gh-pelican-travis/primeira-postagem2.png differ
diff --git a/content/images/humrochagf/gh-pelican-travis/travis-repo1.png b/content/images/humrochagf/gh-pelican-travis/travis-repo1.png
new file mode 100644
index 000000000..86cb10d92
Binary files /dev/null and b/content/images/humrochagf/gh-pelican-travis/travis-repo1.png differ
diff --git a/content/images/humrochagf/gh-pelican-travis/travis-repo2.png b/content/images/humrochagf/gh-pelican-travis/travis-repo2.png
new file mode 100644
index 000000000..aedb5a429
Binary files /dev/null and b/content/images/humrochagf/gh-pelican-travis/travis-repo2.png differ
diff --git a/content/images/humrochagf/meusite-admin.png b/content/images/humrochagf/meusite-admin.png
new file mode 100644
index 000000000..6c16b22b5
Binary files /dev/null and b/content/images/humrochagf/meusite-admin.png differ
diff --git a/content/images/humrochagf/meusite.png b/content/images/humrochagf/meusite.png
new file mode 100644
index 000000000..98cfc3eb4
Binary files /dev/null and b/content/images/humrochagf/meusite.png differ
diff --git a/content/images/juniorcarvalho/dokku-setup.jpg b/content/images/juniorcarvalho/dokku-setup.jpg
new file mode 100644
index 000000000..bfa4ad726
Binary files /dev/null and b/content/images/juniorcarvalho/dokku-setup.jpg differ
diff --git a/content/images/lucasmagnum/helloworld.png b/content/images/lucasmagnum/helloworld.png
new file mode 100644
index 000000000..363e2eecd
Binary files /dev/null and b/content/images/lucasmagnum/helloworld.png differ
diff --git a/content/images/lucasmagnum/itworked.png b/content/images/lucasmagnum/itworked.png
new file mode 100644
index 000000000..4692f857d
Binary files /dev/null and b/content/images/lucasmagnum/itworked.png differ
diff --git a/content/images/mstuttgart/snapshot_17.png b/content/images/mstuttgart/snapshot_17.png
new file mode 100644
index 000000000..34199b251
Binary files /dev/null and b/content/images/mstuttgart/snapshot_17.png differ
diff --git a/content/images/mstuttgart/snapshot_18.png b/content/images/mstuttgart/snapshot_18.png
new file mode 100644
index 000000000..eaaf3e097
Binary files /dev/null and b/content/images/mstuttgart/snapshot_18.png differ
diff --git a/content/images/mstuttgart/snapshot_19.png b/content/images/mstuttgart/snapshot_19.png
new file mode 100644
index 000000000..6e0a3e827
Binary files /dev/null and b/content/images/mstuttgart/snapshot_19.png differ
diff --git a/content/images/mstuttgart/snapshot_20.png b/content/images/mstuttgart/snapshot_20.png
new file mode 100644
index 000000000..4df153ce0
Binary files /dev/null and b/content/images/mstuttgart/snapshot_20.png differ
diff --git a/content/images/mstuttgart/snapshot_21.png b/content/images/mstuttgart/snapshot_21.png
new file mode 100644
index 000000000..fc87e91e8
Binary files /dev/null and b/content/images/mstuttgart/snapshot_21.png differ
diff --git a/content/images/mstuttgart/snapshot_22.png b/content/images/mstuttgart/snapshot_22.png
new file mode 100644
index 000000000..dae0f5472
Binary files /dev/null and b/content/images/mstuttgart/snapshot_22.png differ
diff --git a/content/images/mstuttgart/snapshot_23.png b/content/images/mstuttgart/snapshot_23.png
new file mode 100644
index 000000000..8c1820168
Binary files /dev/null and b/content/images/mstuttgart/snapshot_23.png differ
diff --git a/content/images/mstuttgart/snapshot_24.png b/content/images/mstuttgart/snapshot_24.png
new file mode 100644
index 000000000..4de56ec0c
Binary files /dev/null and b/content/images/mstuttgart/snapshot_24.png differ
diff --git a/content/images/mstuttgart/snapshot_25.png b/content/images/mstuttgart/snapshot_25.png
new file mode 100644
index 000000000..b8c1257ea
Binary files /dev/null and b/content/images/mstuttgart/snapshot_25.png differ
diff --git a/content/images/mstuttgart/snapshot_26.png b/content/images/mstuttgart/snapshot_26.png
new file mode 100644
index 000000000..79aa775f8
Binary files /dev/null and b/content/images/mstuttgart/snapshot_26.png differ
diff --git a/content/images/mstuttgart/snapshot_27.png b/content/images/mstuttgart/snapshot_27.png
new file mode 100644
index 000000000..0fd89c3c3
Binary files /dev/null and b/content/images/mstuttgart/snapshot_27.png differ
diff --git a/content/images/mstuttgart/snapshot_28.png b/content/images/mstuttgart/snapshot_28.png
new file mode 100644
index 000000000..6273a117e
Binary files /dev/null and b/content/images/mstuttgart/snapshot_28.png differ
diff --git a/content/images/mstuttgart/snapshot_29.png b/content/images/mstuttgart/snapshot_29.png
new file mode 100644
index 000000000..6273a117e
Binary files /dev/null and b/content/images/mstuttgart/snapshot_29.png differ
diff --git a/content/images/mstuttgart/snapshot_30.png b/content/images/mstuttgart/snapshot_30.png
new file mode 100644
index 000000000..4cc306018
Binary files /dev/null and b/content/images/mstuttgart/snapshot_30.png differ
diff --git a/content/images/mstuttgart/snapshot_31.png b/content/images/mstuttgart/snapshot_31.png
new file mode 100644
index 000000000..0546a5b27
Binary files /dev/null and b/content/images/mstuttgart/snapshot_31.png differ
diff --git a/content/images/mstuttgart/snapshot_32.png b/content/images/mstuttgart/snapshot_32.png
new file mode 100644
index 000000000..2676a2360
Binary files /dev/null and b/content/images/mstuttgart/snapshot_32.png differ
diff --git a/content/images/mstuttgart/snapshot_33.png b/content/images/mstuttgart/snapshot_33.png
new file mode 100644
index 000000000..5aef9e56e
Binary files /dev/null and b/content/images/mstuttgart/snapshot_33.png differ
diff --git a/content/images/mstuttgart/snapshot_34.png b/content/images/mstuttgart/snapshot_34.png
new file mode 100644
index 000000000..c21cfea8e
Binary files /dev/null and b/content/images/mstuttgart/snapshot_34.png differ
diff --git a/content/images/mstuttgart/snapshot_35.png b/content/images/mstuttgart/snapshot_35.png
new file mode 100644
index 000000000..eae50680f
Binary files /dev/null and b/content/images/mstuttgart/snapshot_35.png differ
diff --git a/content/images/mstuttgart/snapshot_36.png b/content/images/mstuttgart/snapshot_36.png
new file mode 100644
index 000000000..fa6d59b81
Binary files /dev/null and b/content/images/mstuttgart/snapshot_36.png differ
diff --git a/content/images/mstuttgart/snapshot_37.png b/content/images/mstuttgart/snapshot_37.png
new file mode 100644
index 000000000..539859f30
Binary files /dev/null and b/content/images/mstuttgart/snapshot_37.png differ
diff --git a/content/images/mstuttgart/snapshot_38.png b/content/images/mstuttgart/snapshot_38.png
new file mode 100644
index 000000000..b5f6372b9
Binary files /dev/null and b/content/images/mstuttgart/snapshot_38.png differ
diff --git a/content/images/mstuttgart/snapshot_39.png b/content/images/mstuttgart/snapshot_39.png
new file mode 100644
index 000000000..4d7488eaa
Binary files /dev/null and b/content/images/mstuttgart/snapshot_39.png differ
diff --git a/content/images/mstuttgart/snapshot_40.png b/content/images/mstuttgart/snapshot_40.png
new file mode 100644
index 000000000..6279fed38
Binary files /dev/null and b/content/images/mstuttgart/snapshot_40.png differ
diff --git a/content/images/mstuttgart/snapshot_41.png b/content/images/mstuttgart/snapshot_41.png
new file mode 100644
index 000000000..a32a23568
Binary files /dev/null and b/content/images/mstuttgart/snapshot_41.png differ
diff --git a/content/images/mstuttgart/snapshot_42.png b/content/images/mstuttgart/snapshot_42.png
new file mode 100644
index 000000000..02d9789f4
Binary files /dev/null and b/content/images/mstuttgart/snapshot_42.png differ
diff --git a/content/images/mstuttgart/snapshot_43.png b/content/images/mstuttgart/snapshot_43.png
new file mode 100644
index 000000000..e70cf29ad
Binary files /dev/null and b/content/images/mstuttgart/snapshot_43.png differ
diff --git a/content/images/mstuttgart/snapshot_44.png b/content/images/mstuttgart/snapshot_44.png
new file mode 100644
index 000000000..e9df7ac60
Binary files /dev/null and b/content/images/mstuttgart/snapshot_44.png differ
diff --git a/content/images/othonalberto/2016-01-24_gitshot_othonalberto.png b/content/images/othonalberto/2016-01-24_gitshot_othonalberto.png
new file mode 100644
index 000000000..ea3f26071
Binary files /dev/null and b/content/images/othonalberto/2016-01-24_gitshot_othonalberto.png differ
diff --git a/content/images/rafpyprog/pybr-banner.jpeg b/content/images/rafpyprog/pybr-banner.jpeg
new file mode 100644
index 000000000..1008c6d7c
Binary files /dev/null and b/content/images/rafpyprog/pybr-banner.jpeg differ
diff --git a/content/images/regisdasilva/drf01.png b/content/images/regisdasilva/drf01.png
new file mode 100644
index 000000000..120526207
Binary files /dev/null and b/content/images/regisdasilva/drf01.png differ
diff --git a/content/images/regisdasilva/drf02.png b/content/images/regisdasilva/drf02.png
new file mode 100644
index 000000000..07958dbb8
Binary files /dev/null and b/content/images/regisdasilva/drf02.png differ
diff --git a/content/images/regisdasilva/person.jpg b/content/images/regisdasilva/person.jpg
new file mode 100644
index 000000000..12617f010
Binary files /dev/null and b/content/images/regisdasilva/person.jpg differ
diff --git a/content/images/rochacbruno/access.png b/content/images/rochacbruno/access.png
new file mode 100644
index 000000000..a7c86f5c6
Binary files /dev/null and b/content/images/rochacbruno/access.png differ
diff --git a/content/images/rochacbruno/admin.jpg b/content/images/rochacbruno/admin.jpg
new file mode 100644
index 000000000..b508f0b11
Binary files /dev/null and b/content/images/rochacbruno/admin.jpg differ
diff --git a/content/images/rochacbruno/admin_index.png b/content/images/rochacbruno/admin_index.png
new file mode 100644
index 000000000..6785e1b67
Binary files /dev/null and b/content/images/rochacbruno/admin_index.png differ
diff --git a/content/images/rochacbruno/admin_index_login.png b/content/images/rochacbruno/admin_index_login.png
new file mode 100644
index 000000000..99ddd0b45
Binary files /dev/null and b/content/images/rochacbruno/admin_index_login.png differ
diff --git a/content/images/rochacbruno/admin_noticia.png b/content/images/rochacbruno/admin_noticia.png
new file mode 100644
index 000000000..7d69c6614
Binary files /dev/null and b/content/images/rochacbruno/admin_noticia.png differ
diff --git a/content/images/rochacbruno/admin_user_columns.png b/content/images/rochacbruno/admin_user_columns.png
new file mode 100644
index 000000000..c33cc1ec9
Binary files /dev/null and b/content/images/rochacbruno/admin_user_columns.png differ
diff --git a/content/images/rochacbruno/admin_user_full.png b/content/images/rochacbruno/admin_user_full.png
new file mode 100644
index 000000000..eeb5b91be
Binary files /dev/null and b/content/images/rochacbruno/admin_user_full.png differ
diff --git a/content/images/rochacbruno/bootstrap.png b/content/images/rochacbruno/bootstrap.png
new file mode 100644
index 000000000..34aceded9
Binary files /dev/null and b/content/images/rochacbruno/bootstrap.png differ
diff --git a/content/images/rochacbruno/cached.png b/content/images/rochacbruno/cached.png
new file mode 100644
index 000000000..226fddd63
Binary files /dev/null and b/content/images/rochacbruno/cached.png differ
diff --git a/content/images/rochacbruno/cms_index.png b/content/images/rochacbruno/cms_index.png
new file mode 100644
index 000000000..73567ac48
Binary files /dev/null and b/content/images/rochacbruno/cms_index.png differ
diff --git a/content/images/rochacbruno/code.png b/content/images/rochacbruno/code.png
new file mode 100644
index 000000000..1fe7d8532
Binary files /dev/null and b/content/images/rochacbruno/code.png differ
diff --git a/content/images/rochacbruno/deboas.jpg b/content/images/rochacbruno/deboas.jpg
new file mode 100644
index 000000000..ce7719f0a
Binary files /dev/null and b/content/images/rochacbruno/deboas.jpg differ
diff --git a/content/images/rochacbruno/debug_toolbar.png b/content/images/rochacbruno/debug_toolbar.png
new file mode 100644
index 000000000..d00eb3a25
Binary files /dev/null and b/content/images/rochacbruno/debug_toolbar.png differ
diff --git a/content/images/rochacbruno/lego_snake.jpg b/content/images/rochacbruno/lego_snake.jpg
new file mode 100644
index 000000000..5916aa579
Binary files /dev/null and b/content/images/rochacbruno/lego_snake.jpg differ
diff --git a/content/images/rochacbruno/login.png b/content/images/rochacbruno/login.png
new file mode 100644
index 000000000..1a6de79f4
Binary files /dev/null and b/content/images/rochacbruno/login.png differ
diff --git a/content/images/rochacbruno/mongo.jpg b/content/images/rochacbruno/mongo.jpg
new file mode 100644
index 000000000..48480caa4
Binary files /dev/null and b/content/images/rochacbruno/mongo.jpg differ
diff --git a/content/images/rochacbruno/profiler.png b/content/images/rochacbruno/profiler.png
new file mode 100644
index 000000000..c11f7cc79
Binary files /dev/null and b/content/images/rochacbruno/profiler.png differ
diff --git a/content/images/rochacbruno/register.png b/content/images/rochacbruno/register.png
new file mode 100644
index 000000000..8502d3c1f
Binary files /dev/null and b/content/images/rochacbruno/register.png differ
diff --git a/content/images/rochacbruno/robomongo.png b/content/images/rochacbruno/robomongo.png
new file mode 100644
index 000000000..2b3e06010
Binary files /dev/null and b/content/images/rochacbruno/robomongo.png differ
diff --git a/content/images/rochacbruno/security.jpg b/content/images/rochacbruno/security.jpg
new file mode 100644
index 000000000..717f2ba0a
Binary files /dev/null and b/content/images/rochacbruno/security.jpg differ
diff --git a/content/images/rochacbruno/wtf_index.png b/content/images/rochacbruno/wtf_index.png
new file mode 100644
index 000000000..f0ec82fe9
Binary files /dev/null and b/content/images/rochacbruno/wtf_index.png differ
diff --git a/content/images/z4r4tu5tr4/lambda.jpg b/content/images/z4r4tu5tr4/lambda.jpg
new file mode 100644
index 000000000..1b818652f
Binary files /dev/null and b/content/images/z4r4tu5tr4/lambda.jpg differ
diff --git a/content/images/z4r4tu5tr4/lambda.png b/content/images/z4r4tu5tr4/lambda.png
new file mode 100644
index 000000000..426069c2d
Binary files /dev/null and b/content/images/z4r4tu5tr4/lambda.png differ
diff --git "a/content/instalando-o-python-vers\303\243o-3.7.0-alpha-1-no-ubuntu-16.04.md" "b/content/instalando-o-python-vers\303\243o-3.7.0-alpha-1-no-ubuntu-16.04.md"
new file mode 100644
index 000000000..02be88830
--- /dev/null
+++ "b/content/instalando-o-python-vers\303\243o-3.7.0-alpha-1-no-ubuntu-16.04.md"
@@ -0,0 +1,64 @@
+Title: Instalando o Python versão 3.7.0 alpha 1 no Ubuntu 16.04
+Slug: instalando-o-python-versão-3.7.0-alpha-1-no-ubuntu-16.04.md
+Date: 2017-01-16 20:37:39
+Category: Python
+Tags: python,tutorial,install
+Author: Welton Vaz
+Email: weltonvaz@gmail.com
+Github: weltonvaz
+Linkedin: welton-vaz-de-souza
+Facebook: weltonv
+Site: http://www.weltonvaz.com/
+
+
+Instalando o Python versão 3.7.0 alpha 1 no Ubuntu 16.04
+
+A versão mais recente do Python, a 3.7.0 alfa 1, pode agora ser baixada ou clonada do GitHub facilmente. Uma das linguagens mais fáceis de usar e aprender, o Python foi criado nos anos 90 e é elogiado por sua fácil leitura de código e necessidade de poucas linhas de código, comparada a outras linguagens. Agora mais próxima da comunidade no Github!
+
+Depois disso os caminhos mudaram e conheci a profissão de Analista de Suporte e me ocupo disso desde então. Atualmente voltei a aprender uma linguagem, antes de mais nada, dei uma atualizada em lógica de programação, por sinal existem muitas boas apostilas e cursos gratuitos na Internet, dois caminhos muito bons.
+
+Sobre linguagem de programação, existem várias. Neste quesito comecei a conhecer a linguagem Python e logo me apaixonei pela simplicidade, beleza e eficiência.
+
+Depois disso tudo, você tem que instalar a linguagem em sua máquina. Por padrão, o Ubuntu 16.04 instala a versão 3.4, mas se você quiser, pode usar a versão 3.7.0a0
+
+Obs.: Execute os comandos como root, ou usando o comando sudo no terminal.
+
+```shell
+git clone https://github.com/python/cpython
+cd cpython
+apt-get install build-essential libssl-dev libffi-dev python3-dev
+./configure
+make
+make test
+make install
+
+# Se você quiser usar várias versões do Python 2.7, 3.6 e 3.7 use o comando abaixo
+make altinstall
+```
+
+Observação: via apt instalei as dependências do python, no caso o openssl, porque o pip apresenta vários problemas com certificados na instalação dos módulos, mas, isso é para outro artigo
+
+Depois disso é só entrar no interpretador:
+
+```shell
+python3.7
+```
+
+Tela do interpretador Python
+
+```shell
+Python 3.7.0a0 (default, Feb 16 2017, 18:59:44)
+[GCC 5.4.0 20160609] on linux
+Type "help", "copyright", "credits" or "license" for more information.
+>>>
+
+```
+### Referências
+Para ler mais sobre a linguagem:
+* [Python] - Site oficial da Linguagem Python!
+* [This is Python version 3.7.0 alpha 1] - Git da próxima versão do Python, hospedado no Github!
+* [Python-Brasil] - A comunidade Python Brasil reune grupos de usuários em todo o Brasil interessados em difundir e divulgar a linguagem de programação.
+
+[Python]:
+[Python-Brasil]:
+[This is Python version 3.7.0 alpha 1]:
\ No newline at end of file
diff --git a/content/instalando-pycharm.md b/content/instalando-pycharm.md
new file mode 100644
index 000000000..19938702e
--- /dev/null
+++ b/content/instalando-pycharm.md
@@ -0,0 +1,88 @@
+Title: Instalando o PyCharm no Ubuntu (e irmãos)
+Slug: instalando-pycharm-ubuntu
+Date: 2015-06-14 12:58
+Tags: python,blog,tutorial,pycharm
+Author: Erick Müller
+Email: erick.muller@gmail.com
+Github: ehriq
+Bitbucket: ehriq
+Twitter: ehriq
+Category: Python
+
+
+O objetivo aqui é instalar o PyCharm no Ubuntu e distribuições "irmãs" (como o Mint); estou instalando a versão **Community Edition**, que acredito que é a que muita gente que começa com essa poderosa IDE vai instalar pra dar os primeiros passos, experimentar.
+
+(aliás, bom avisar antes de começar: fiz o guia baseado no Ubuntu 14.04 e no Linux Mint 17.1; mas já fiz o mesmo procedimento em versões anteriores tanto do PyCharm quanto do Ubuntu, e com a versão "Professional" do PyCharm, e funcionou bem.)
+
+
+## Parte 1 - instalar o Java
+
+As aplicações da JetBrains não são exatamente compatíveis com a versão do Java que vem por padrão no Ubuntu. Por isso, precisamos atualizar.
+
+Abra o terminal e execute os comandos abaixo:
+
+```
+sudo add-apt-repository ppa:webupd8team/java
+sudo apt-get update
+echo oracle-java8-installer shared/accepted-oracle-license-v1-1 select true | sudo /usr/bin/debconf-set-selections
+sudo apt-get install oracle-java8-installer -y
+sudo apt-get install oracle-java8-set-default -y
+```
+
+Após os comandos acima, veja se a instalação está correta, executando no console:
+
+```bash
+java -version
+```
+
+a saída esperada é algo como:
+
+```
+java version "1.8.0_45"
+Java(TM) SE Runtime Environment (build 1.8.0_45-b14)
+Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, mixed mode)
+```
+
+## Parte 2 - pip e virtualenv
+
+O PyCharm usa o **pip** para baixar módulos/bibliotecas/extensões (como quiser chamar) do python, e o **virtualenv** para criar os queridos ambientes virtuais que mantém a sanidade dos programadores python. Então, para tirar proveito dessas funcionalidades, é bom garantir que estejam instalados também.
+
+Para isto, abra o console e:
+
+```
+cd ~/Downloads
+wget -c https://bootstrap.pypa.io/get-pip.py
+sudo -H python2 get-pip.py
+sudo -H python3 get-pip.py
+sudo -H pip2 install virtualenv
+```
+
+
+## Parte 3 - copiar o PyCharm
+
+- clique no link ao lado para ir à página de [Download do PyCharm](https://www.jetbrains.com/pycharm/download/)
+- clique em "Download Community"
+- grave o arquivo no diretório que quiser
+
+
+
+
+## Parte 4 - instalar o PyCharm
+
+Com os pré-requisitos prontos e instalados, vamos ao prato principal:
+
+```
+sudo tar -C /opt/ -xzf /pycharm-community-4.5.1.tar.gz
+```
+
+- Abra o navegador de arquivos e vá ao diretório */opt/pycharm-community-4.5.1*
+- Entre no diretório 'bin' e, com dois cliques sobre, execute o script *'pycharm.sh'*
+- Se aparecer uma janela perguntando como rodar o programa, clique no último botão (*'Executar'* ou *'Run'*)
+- Dê "OK" na janela que abrir
+- E na próxima janela, deixe todas as últimas opções selecionadas. Ao clicar em *'OK'* o PyCharm vai pedir a senha de 'root' para criar as entradas no menu.
+
+
+
+
+
+Pronto, é isso. O software está instalado, e pronto para uso.
diff --git a/content/introducao-classes-metodos-python-basico.rst b/content/introducao-classes-metodos-python-basico.rst
index bc89c55eb..1182cfbf0 100644
--- a/content/introducao-classes-metodos-python-basico.rst
+++ b/content/introducao-classes-metodos-python-basico.rst
@@ -10,17 +10,21 @@ Introdução a Classes e Métodos em Python (básico)
:github: rg3915
:summary: Abordaremos aqui o básico sobre o uso de classes e métodos e a manipulação de dados em Python.
-Eu não sou a melhor pessoa do mundo para explicar este assunto, mas eu escrevi este post para introduzir um tópico sobre *manipulação de banco de dados em SQLite3 com Python*, porém mais informações sobre classes e métodos podem ser encontradas nos links abaixo. Veja os exemplos em `https://github.com/rg3915/pythonDesktopApp `_.
+Eu não sou a melhor pessoa do mundo para explicar este assunto, mas vou tentar fazer uma breve introdução a classes e métodos em Python.
+
+Mais informações sobre classes e métodos podem ser encontradas nos links abaixo. Veja os exemplos em `https://github.com/rg3915/python-classes-metodos `_.
+
+> Este artigo foi atualizado em 26 de Maio de 2018.
PS: *Considere a sintaxe para Python 3*.
-Segundo a documentação do `Python `_ e o video `Python para Zumbis `_, uma **classe** associa dados (**atributos**) e operações (**métodos**) numa só estrutura. Um **objeto** é uma variável cujo tipo é uma classe, ou seja, um **objeto é uma instância** de uma classe.
+Segundo a documentação do `Python `_ e o video `Python para Zumbis `_, uma **classe** associa dados (**atributos**) e operações (**métodos**) numa só estrutura. Um **objeto é uma instância** de uma classe. Ou seja, uma representação da classe. Por exemplo, Regis é uma instância de uma classe chamada Pessoa, mas a Pessoa é a classe que o representa de uma forma genérica. Se você criar um outro objeto chamado Fabio, esse objeto também será uma instancia da classe Pessoa.
Na sua sintaxe mais elementar definimos uma classe conforme abaixo:
.. code-block:: python
- class NomeDaClasse(object):
+ class NomeDaClasse:
pass
E um método (função) como:
@@ -42,15 +46,60 @@ Juntando os dois temos:
.. code-block:: python
- class NomeDaClasse(object):
- atributo1 = None
+ class NomeDaClasse:
+
+ def metodo(self, args):
+ pass
+
+
+
+A primeira pergunta que você vai ter é o porque do ``self`` em ``metodo``. A resposta curta é, todo metodo criado dentro de uma classe deve definir como primeiro parametro o ``self``. Para a resposta longa, por favor, leia a excelente explicação que o Pedro Werneck fez: `O porquê do self explícito em Python `_
+
+A segunda pergunta é: para que serve o ``pass``?
+
+A resposta é que, em Python, ao contrario de várias outras liguagens de programação, os blocos de código **NÃO** são definidos com os caracteres ``{`` e ``}``, mas sim com indentação e o caractere ``:``. Devido a esse fato, python necessitava de algo para explicitar quando se quer definir um bloco vazio. O ``pass`` foi criado exatamente para explicitar essa situação.
+
+Um exemplo de uma função vazia feita em linguagem C e a mesma função vazia feita em Python:
+
+.. code-block:: C
+
+ void metodo(int num){
+
+ }
+
+.. code-block:: python
+
+ def metodo(num):
+ pass
+
+Importante: Note que para nome de **classes** usamos *PalavrasComeçandoPorMaiúscula* (isso tambem é conhecido como "`CapitalizeWords `_") e para nome de **métodos (funções)** usamos *minúsculas_separadas_por_underscore*. Esta é uma convenção adotada pelos *Pythonistas* segundo o `Guia de Estilo `_ **PEP 8** - `Style Guide for Python Code `_ escrito por `Guido Van Rossum `_.
+
+
+Exemplo 0 - Pessoa
+------------------
- def metodo(args):
- pass
+No exemplo mencionado no começo desse post o código mais simples seria o seguinte:
-``pass`` significa que você pode escrever o seu código no lugar. E ``atributo1`` é um atributo com valor inicial ``None`` (nada). Poderia ser ``atributo1 = 0``, por exemplo.
+.. code-block:: python
+
+ class Pessoa:
+
+ def __init__(self, nome):
+ self.nome = nome
+
+ def __str__(self):
+ return self.nome
+
+
+ regis = Pessoa('Regis')
+ print(regis)
+ fabio = Pessoa('Fabio')
+ print(fabio)
+
+Note que ``regis`` é uma instância da classe ``Pessoa``, e ``fabio`` é uma outra instância. Ou seja, temos dois **objetos**: ``regis`` e ``fabio``.
+
+Os dois métodos serão explicados no próximo exemplo.
-Importante: Note que para nome de **classes** usamos *PalavrasComeçandoPorMaiúscula* (isso tambem é conhecido como "CamelCase") e para nome de **métodos (funções)** usamos *minúsculas_separadas_por_underscore*. Esta é uma convenção adotada pelos *Pythonistas* segundo o `Guia de Estilo `_ **PEP 8** - `Style Guide for Python Code `_ escrito por `Guido Van Rossum `_.
Exemplo 1 - Calculadora simples
-------------------------------
@@ -60,7 +109,7 @@ Existem pelo menos duas formas diferentes de trabalhar com os parâmetros de ent
.. code-block:: python
#calculadora.py
- class Calculadora(object):
+ class Calculadora:
def __init__(self, a, b):
self.a = a
@@ -143,7 +192,7 @@ Agora faremos uma classe sem valor inicial e com **dois parâmetros** *para todo
.. code-block:: python
#calculadora2.py
- class Calculadora(object):
+ class Calculadora:
def soma(self, a, b):
return a + b
@@ -165,23 +214,28 @@ Usando o **terminal no modo interativo** façamos:
>>> from calculadora2 import Calculadora
>>> c = Calculadora()
>>> print('Soma:', c.soma(2,3))
+ Soma: 5
>>> print('Subtração:', c.subtrai(2,10))
+ Subtração: -8
>>> print('Multiplicação:', c.multiplica(3,3))
+ Multiplicação: 9
>>> print('Divisão:', c.divide(128,2))
+ Divisão: 64.0
+ >>>
A vantagem de colocar os parâmetros em cada método, é que podemos calcular qualquer valor sem ter que instanciar uma nova classe para cada valor diferente.
Exemplo 3 - Classe Pedido
-------------------------
-Agora veremos um exemplo que mais se aproxima do que iremos fazer em banco de dados, mas aqui iremos apenas instanciar os objetos e armazená-los em memória numa lista.
+Agora veremos um outro exemplo, mas aqui iremos apenas instanciar os objetos e armazená-los em memória numa lista.
Veremos o código na íntegra e depois os comentários.
.. code-block:: python
#user.py
- class User(object):
+ class User:
seq = 0
objects = []
@@ -224,6 +278,9 @@ Podemos rodar o Python no modo `modo interativo >> u2 = User('Fabio',20)
>>> u2.save()
>>> print(User.all())
+ [
+ ,
+ ]
Agora os comentários:
@@ -231,7 +288,7 @@ Definindo a classe
.. code-block:: python
- class User(object):
+ class User:
Define um atributo que servirá como contador inicial e um atributo ``objects`` (tupla vazia) que é uma lista de instâncias de ``User`` que foram salvos (que chamaram o método ``save``).
@@ -320,11 +377,63 @@ Note que nesse ``print`` a lista está vazia.
Após chamar o ``save`` para as duas instâncias elas são guardadas e o método ``User.all()`` retorna essa lista.
+Exemplo 4 - Televisão
+---------------------
+
+Escrevi mais um exemplo para fixar melhor o entendimento: `tv.py `_.
+
+.. code-block:: python
+
+ class Televisao():
+
+ def __init__(self):
+ self.ligada = False
+ self.canal = 2
+
+ def muda_canal_para_baixo(self):
+ self.canal -= 1
+
+ def muda_canal_para_cima(self):
+ self.canal += 1
+
+ if __name__ == '__main__':
+ tv = Televisao()
+ print('Canal inicial:', tv.canal)
+ print('Ligada:', tv.ligada)
+
+ tv.ligada = True
+ tv.canal = 5
+
+ print('Ligada:', tv.ligada)
+ print('Canal inicial:', tv.canal)
+ tv.muda_canal_para_cima()
+ print('Canal +', tv.canal)
+ tv.muda_canal_para_cima()
+ print('Canal +', tv.canal)
+ tv.muda_canal_para_baixo()
+ print('Canal -', tv.canal)
+
+Este programa está muito bem explicado no video `Python para Zumbis `_.
+
+A seguir o resultado do programa:
+
+.. code-block:: python
+
+ $ python tv.py
+ ('Canal inicial:', 2)
+ ('Ligada:', False)
+ ('Ligada:', True)
+ ('Canal inicial:', 5)
+ ('Canal +', 6)
+ ('Canal +', 7)
+ ('Canal -', 6)
+
+
Agradeço a colaboração de `Fabio Cerqueira `_.
Veja os exemplos em `https://github.com/rg3915/pythonDesktopApp `_.
-Mais informações em
+Mais informações em
`Classes Python `_
diff --git a/content/material-do-tutorial-web-scraping-na-nuvem.md b/content/material-do-tutorial-web-scraping-na-nuvem.md
new file mode 100644
index 000000000..381c565b0
--- /dev/null
+++ b/content/material-do-tutorial-web-scraping-na-nuvem.md
@@ -0,0 +1,933 @@
+Title: Web Scraping na Nuvem com Scrapy
+Date: 2015-11-13 10:04
+Author: Elias Dorneles
+Slug: material-do-tutorial-web-scraping-na-nuvem
+Tags: scrapy,spider,web-scraping,scraping,crawling,js2xml,extruct,web,scrapy-cloud,scrapinghub
+Email: eliasdorneles@gmail.com
+Github: eliasdorneles
+Twitter: eliasdorneles
+Site: http://eliasdorneles.github.io
+
+
+Este tutorial foi apresentado na Python Brasil 2015 em São José dos Campos.
+
+## Roteiro
+
+* Introdução a web scraping com [Scrapy](http://scrapy.org/)
+* Conceitos do Scrapy
+* Hands-on: crawler para versões diferentes dum site de citações
+* Rodando no [Scrapy Cloud](http://scrapinghub.com/platform/)
+
+O tutorial é 90% Scrapy e 10% Scrapy Cloud.
+
+> **Nota:** Scrapy Cloud é o serviço PaaS da Scrapinghub, a empresa em que
+> trabalho e que é responsável pelo desenvolvimento do Scrapy.
+
+### Precisa de ajuda?
+
+Pergunte no [Stackoverflow em Português usando a tag
+scrapy](http://pt.stackoverflow.com/tags/scrapy) ou pergunte em inglês no
+[Stackoverflow em inglês](http://stackoverflow.com/tags/scrapy) ou na [lista de
+e-mail scrapy-users](https://groups.google.com/forum/#!forum/scrapy-users).
+
+
+## Introdução a web scraping com Scrapy
+
+### O que é Scrapy?
+
+[Scrapy](http://scrapy.org/) é um framework para crawlear web sites e extrair dados estruturados que
+podem ser usados para uma gama de aplicações úteis (data mining, arquivamento,
+etc).
+
+*Scraping:*
+: extrair dados do conteúdo da página
+
+*Crawling:*
+: seguir links de uma página a outra
+
+Se você já fez extração de dados de páginas Web antes em Python, são grandes as
+chances de você ter usado algo como requests + beautifulsoup. Essas tecnologias
+ajudam a fazer *scraping*.
+
+A grande vantagem de usar Scrapy é que tem suporte de primeira classe a
+*crawling*.
+
+Por exemplo, ele permite configurar o tradeoff de **politeness vs speed** (sem
+precisar escrever código pra isso) e já vem com uma configuração útil de
+fábrica para crawling habilitada: suporte a cookies, redirecionamento tanto via
+HTTP header quanto via tag HTML `meta`, tenta de novo requisições que falham,
+evita requisições duplicadas, etc.
+
+Além disso, o framework é altamente extensível, permite seguir combinando
+componentes e crescer um projeto de maneira gerenciável.
+
+### Instalando o Scrapy
+
+Recomendamos usar virtualenv, e instalar o Scrapy com:
+
+ pip install scrapy
+
+A dependência chatinha é normalmente o [lxml](http://lxml.de/) (que precisa de
+algumas bibliotecas C instaladas). Caso tenha dificuldade, [consulte as
+instruções específicas por
+plataforma](http://doc.scrapy.org/en/latest/intro/install.html#intro-install-platform-notes)
+ou peça ajuda nos canais citados acima.
+
+Para verificar se o Scrapy está instalado corretamente, rode o comando:
+
+ scrapy version
+
+A saída que obtenho rodando este comando no meu computador é:
+
+ $ scrapy version
+ 2015-11-14 19:58:56 [scrapy] INFO: Scrapy 1.0.3 started (bot: scrapybot)
+ 2015-11-14 19:58:56 [scrapy] INFO: Optional features available: ssl, http11
+ 2015-11-14 19:58:56 [scrapy] INFO: Overridden settings: {}
+ Scrapy 1.0.3
+
+
+### Rodando um spider
+
+Para ter uma noção inicial de como usar o Scrapy, vamos começar rodando um
+spider de exemplo.
+
+Crie um arquivo **youtube_spider.py** com o seguinte conteúdo:
+
+
+ import scrapy
+
+
+ def first(sel, xpath):
+ return sel.xpath(xpath).extract_first()
+
+
+ class YoutubeChannelLister(scrapy.Spider):
+ name = 'channel-lister'
+ youtube_channel = 'portadosfundos'
+ start_urls = ['https://www.youtube.com/user/%s/videos' % youtube_channel]
+
+ def parse(self, response):
+ for sel in response.css("ul#channels-browse-content-grid > li"):
+ yield {
+ 'link': response.urljoin(first(sel, './/h3/a/@href')),
+ 'title': first(sel, './/h3/a/text()'),
+ 'views': first(sel, ".//ul/li[1]/text()"),
+ }
+
+
+Agora, rode o spider com o comando:
+
+ scrapy runspider youtube_spider.py -o portadosfundos.csv
+
+O scrapy vai procurar um spider no arquivo **youtube_spider.py** e
+escrever os dados no arquivo CSV **portadosfundos.csv**.
+
+Caso tudo deu certo, você verá o log da página sendo baixada, os dados sendo
+extraídos, e umas estatísticas resumindo o processo no final, algo como:
+
+ ...
+ 2015-11-14 20:14:21 [scrapy] DEBUG: Crawled (200) (referer: None)
+ 2015-11-14 20:14:22 [scrapy] DEBUG: Scraped from <200 https://www.youtube.com/user/portadosfundos/videos>
+ {'views': u'323,218 views', 'link': u'https://www.youtube.com/watch?v=qSqPkRi-UiE', 'title': u'GAR\xc7ONS'}
+ 2015-11-14 20:14:22 [scrapy] DEBUG: Scraped from <200 https://www.youtube.com/user/portadosfundos/videos>
+ {'views': u'1,295,054 views', 'link': u'https://www.youtube.com/watch?v=yXc8KCxyEyQ', 'title': u'SUCESSO'}
+ 2015-11-14 20:14:22 [scrapy] DEBUG: Scraped from <200 https://www.youtube.com/user/portadosfundos/videos>
+ {'views': u'1,324,448 views', 'link': u'https://www.youtube.com/watch?v=k9CbDcOT1e8', 'title': u'BIBLIOTECA'}
+ ...
+ {'downloader/request_bytes': 239,
+ 'downloader/request_count': 1,
+ 'downloader/request_method_count/GET': 1,
+ 'downloader/response_bytes': 27176,
+ 'downloader/response_count': 1,
+ 'downloader/response_status_count/200': 1,
+ 'item_scraped_count': 30,
+ ...
+ 2015-11-14 20:14:22 [scrapy] INFO: Spider closed (finished)
+
+Ao final, verifique os resultados abrindo o arquivo CSV no seu editor de
+planilhas favorito.
+
+Se você quiser os dados em JSON, basta mudar a extensão do arquivo de saída:
+
+ scrapy runspider youtube_spider.py -o portadosfundos.json
+
+Outro formato interessante que o Scrapy suporta é [JSON lines](http://jsonlines.org):
+
+ scrapy runspider youtube_spider.py -o portadosfundos.jl
+
+Esse formato usa um item JSON em cada linha -- isso é muito útil para arquivos
+grandes, porque fica fácil de concatenar dois arquivos ou acrescentar novas
+entradas a um arquivo já existente.
+
+
+## Conceitos do Scrapy
+
+## Spiders
+
+Conceito central no Scrapy,
+[spiders](http://doc.scrapy.org/en/latest/topics/spiders.html) são classes que
+herdam de
+[``scrapy.Spider``](http://doc.scrapy.org/en/latest/topics/spiders.html#scrapy-spider),
+definindo de alguma maneira as requisições iniciais do crawl e como proceder
+para tratar os resultados dessas requisições.
+
+Um exemplo simples de spider é:
+
+ import scrapy
+
+ class SpiderSimples(scrapy.Spider):
+ name = 'meuspider'
+
+ def start_requests(self):
+ return [scrapy.Request('http://example.com/')]
+
+ def parse(self, response):
+ self.log('Visitei o site: %s' % response.url)
+
+Se você rodar o spider acima com o comando ``scrapy runspider``, deverá ver no
+log as mensagens:
+
+ 2015-11-14 21:11:13 [scrapy] DEBUG: Crawled (200) (referer: None)
+ 2015-11-14 21:11:13 [meuspider] DEBUG: Visitei o site: http://example.com
+
+Como iniciar um crawl a partir de uma lista de URLs é uma tarefa comum,
+o Scrapy permite você usar o atribute de classe `start_urls` em vez de
+definir o método ``start_requests()`` a cada vez:
+
+ import scrapy
+
+ class SpiderSimples(scrapy.Spider):
+ name = 'meuspider'
+ start_urls = ['http://example.com/']
+
+ def parse(self, response):
+ self.log('Visitei o site: %s' % response.url)
+
+## Callbacks e próximas requisições
+
+Repare o método ``parse()``, ele recebe um objeto *response* que representa uma
+resposta HTTP, é o que chamamos de um **callback**. Os métodos **callbacks** no
+Scrapy são
+[generators](https://pythonhelp.wordpress.com/2012/09/03/generator-expressions/)
+(ou retornam uma lista ou iterável) de objetos que podem ser:
+
+* dados extraídos (dicionários Python ou objetos que herdam de scrapy.Item)
+* requisições a serem feitas a seguir (objetos scrapy.Request)
+
+O motor do Scrapy itera sobre os objetos resultantes dos callbacks e os
+encaminha para o pipeline de dados ou para a fila de próximas requisições a
+serem feitas.
+
+Exemplo:
+
+ import scrapy
+
+ class SpiderSimples(scrapy.Spider):
+ name = 'meuspider'
+ start_urls = ['http://example.com/']
+
+ def parse(self, response):
+ self.log('Visitei o site: %s' % response.url)
+ yield {'url': response.url, 'tamanho': len(response.body)}
+
+ proxima_url = 'http://www.google.com.br/'
+ self.log('Agora vou para: %s' % proxima_url)
+ yield scrapy.Request(proxima_url, self.handle_google)
+
+ def handle_google(self, response):
+ self.log('Visitei o google via URL: %s' % response.url)
+
+
+Antes de rodar o código acima, experimente ler o código e prever
+o que ele vai fazer. Depois, rode e verifique se ele fez mesmo
+o que você esperava.
+
+Você deverá ver no log algo como:
+
+ 2015-11-14 21:32:53 [scrapy] DEBUG: Crawled (200) (referer: None)
+ 2015-11-14 21:32:53 [meuspider] DEBUG: Visitei o site: http://example.com
+ 2015-11-14 21:32:53 [scrapy] DEBUG: Scraped from <200 http://example.com>
+ {'url': 'http://example.com/', 'tamanho': 1270}
+ 2015-11-14 21:32:53 [meuspider] DEBUG: Agora vou para: http://www.google.com.br
+ 2015-11-14 21:32:53 [scrapy] DEBUG: Crawled (200) (referer: http://example.com)
+ 2015-11-14 21:32:54 [meuspider] DEBUG: Visitei o google via URL: http://www.google.com.br
+ 2015-11-14 21:32:54 [scrapy] INFO: Closing spider (finished)
+
+
+### Settings
+
+Outro conceito importante do Scrapy são as **settings** (isto é, configurações).
+As **settings** oferecem uma maneira de configurar componentes do Scrapy, podendo
+ser setadas de várias maneiras, tanto via linha de comando, variáveis de ambiente
+em um arquivo **settings.py** no caso de você estar usando um projeto Scrapy ou ainda
+diretamente no spider definindo um atributo de classe `custom_settings`.
+
+Exemplo setando no código do spider um delay de 1.5 segundos entre cada
+requisição:
+
+ class MeuSpider(scrapy.Spider):
+ name = 'meuspider'
+
+ custom_settings = {
+ 'DOWNLOAD_DELAY': 1.5,
+ }
+
+Para setar uma setting diretamente na linha de comando com `scrapy runspider`,
+use opção `-s`:
+
+ scrapy runspider meuspider.py -s DOWNLOAD_DELAY=1.5
+
+Uma setting útil durante o desenvolvimento é a *HTTPCACHE_ENABLED*, que
+habilita uma cache das requisições HTTP -- útil para evitar baixar as
+mesmas páginas várias vezes enquanto você refina o código de extração.
+
+> **Dica:** na versão atual do Scrapy, a cache por padrão só funciona caso você
+> esteja dentro de um projeto, que é onde ele coloca um diretório
+> `.scrapy/httpcache` para os arquivos de cache. Caso você queira usar a cache
+> rodando o spider com `scrapy runspider`, você pode usar um truque "enganar" o
+> Scrapy criando um arquivo vazio com o nome `scrapy.cfg` no diretório atual, e
+> o Scrapy criará a estrutura de diretórios `.scrapy/httpcache` no diretório
+> atual.
+
+Bem, por ora você já deve estar familiarizado com os conceitos importantes do
+Scrapy, está na hora de partir para exemplos mais realistas.
+
+
+## Hands-on: crawler para versões diferentes dum site de citações
+
+Vamos agora criar um crawler para um site de frases e citações, feito
+para esse tutorial e disponível em:
+
+> *Nota:* O código-fonte do site está disponível em:
+>
+
+### Descrição dos objetivos:
+
+O site contém uma lista de citações com autor e tags, paginadas com 10 citações
+por páginas. Queremos obter todas as citações, juntamente com os respectivos
+autores e lista de tags.
+
+Existem 4 variações do site, com o mesmo conteúdo mas usando markup HTML diferente.
+
+* Versão com markup HTML semântico:
+* Versão com leiaute em tabelas:
+* Versão com os dados dentro do código Javascript:
+* Versão com AJAX e scroll infinito:
+
+Para ver as diferenças entre cada versão do site, acione a opção "Exibir
+código-fonte" (Ctrl-U) do menu de contexto do seu
+browser.
+
+> **Nota:** cuidado com a opção "Inspecionar elemento" do browser para inspecionar
+> a estrutura do markup. Diferentemente do resultado da opção "Exibir
+> código-fonte" Usando essa ferramenta, o código que você vê representa as
+> estruturas que o browser cria para a página, e nem sempre mapeiam diretamente
+> ao código HTML que veio na requisição HTTP (que é o que você obtém quando usa
+> o Scrapy), especialmente se a página estiver usando Javascript ou AJAX. Outro
+> exemplo é o elemento `` que é adicionado automaticamente pelos
+> browsers em todas as tabelas, mesmo quando não declarado no markup.
+
+
+### Spider para a versão com HTML semântico
+
+Para explorar a página (e a API de scraping do Scrapy), você pode usar
+o comando `scrapy shell URL`:
+
+ scrapy shell http://spidyquotes.herokuapp.com/
+
+Esse comando abre um shell Python (ou [IPython](http://ipython.org), se você o
+tiver instalado no mesmo virtualenv) com o objeto `response`, o mesmo que você
+obteria num método **callback**. Recomendo usar o IPython porque fica mais fácil
+de explorar as APIs sem precisar ter que abrir a documentação a cada vez.
+
+Exemplo de exploração com o shell:
+
+ >>> # olhando o fonte HTML, percebi que cada citação está num
+ >>> # vamos pegar o primeiro dele, e ver como extrair o texto:
+ >>> quote = response.css('.quote')[0]
+ >>> quote
+
+ “We accept the love we think we deserve.”
+ Stephen Chbosky
+