Bem que você tentou, releu o código diversas vezes, escreveu um monte de prints, mas não conseguiu descobrir o que está causando aquele bug indesejado. É chegada a hora de debugar seu código, a seguir darei algumas dicas de como debugar com pdb seu projeto em Django[1].
Primeiramente, debugar um projeto em Python[2] e debugar um projeto em Django não tem muita diferença, a coisa toda é muito parecida, você só precisa do pontapé inicial para se virar dali pra frente.
Para aprender como utilizar o pdb[3] (vulgo Python Debugger) crie um novo projeto chamado djdebug, logo em seguida crie um arquivo vazio chamado views.py na própria raiz do projeto, ficando com a seguinte estrutura de arquivos:
djdebug
- init.py
- manage.py
- settings.py
- urls.py
- views.py
No arquivo views.py cole o seguinte código:
from django.shortcuts import HttpResponse</p>
<p>def soma(a,b):
arg1 = a
arg2 = b
soma = arg1 + arg2
return soma</p>
<p>def hello(request):
a = 2
b = 3
c = [1,2,3]
c.pop()
d = soma(3,4)
response = HttpResponse("Hello World")
return response
Como podemos ver o arquivo contém uma função soma, que não faz nada de muito complexo e uma função hello que retorna uma response com o conteúdo "Hello World".
No arquivo urls.py importe a view hello e adicione uma URL para podemos acessá-la:
from views import hello</p>
<p>urlpatterns = patterns('',
...
(r'^hello', hello),
)
Execute o servidor de testes, navegue até http://localhost:8000/hello e verifique que está tudo funcionando:
./manage runserver
Suponha que há um bug em alguma parte da view hello e então adicione o código para possibilitar o debug:
def hello(request):
a = 2
import pdb
pdb.set_trace()
...
Apenas para esclarecer, o import pdb importa o Python debugger, já o pdb.set_trace() serve pra colocar um breakpoint no código, há maneiras de se colocar um breakpoint no código sem alterá-lo através do próprio pdb[3].
Execute o servidor de testes mas desta vez adicione o parâmetro necessário para evitar recarregamento automático do código-fonte:
./manage runserver --noreload
Abra novamente a view hello no seu navegador, ela ficará parada e no terminal que você iniciou o servidor o pdb[3] estará parado esperando por comandos:
> /home/.../djdebug/views.py(13)hello()
-> b = 3
(Pdb)
Vamos começar, temos um comando interessante que é o l (de list, lista o código na região do debugger) que lista onde se encontra o debugger:
(Pdb) l
8<br />
9 def hello(request):
10 a = 2
11 import pdb
12 pdb.set_trace()
13 -> b = 3
14 c = [1,2,3]
15 c.pop()
16 d = soma(3,4)
17 response = HttpResponse("Hello World")
18 return response
É importante notar que o debugger se encontra no exato momento antes de executar a linha apontada pela seta, isto é, se executarmos p (de print, imprime a expressão passada como argumento) para inspecionarmos a variável b, ela não estará definida:
(Pdb) p a
2
(Pdb) p b
*** NameError: NameError("name 'b' is not defined",)
Vamos para próxima linha com n (de next, segue para a próxima linha):
(Pdb) n
> /home/.../djdebug/views.py(14)hello()
-> c = [1,2,3]
(Pdb) p b
3
Agora sim b já possui valor, vamos avançar até a chamada da função somar:
(Pdb) n
> /home/.../djdebug/views.py(15)hello()
-> c.pop()
(Pdb) <enter>
> /home/.../djdebug/views.py(16)hello()
-> d = soma(3,4)
A partir do primeiro n, enter executará n novamente. Como nós não sabemos se o problema da view hello é a função somar, vamos entrar na função somar com s (de step into, entra na função, ou seja, passo a dentro) e verificar se está tudo certo:
(Pdb) s
--Call--
> /home/.../djdebug/views.py(3)soma()
-> def soma(a,b):
(Pdb) l
1 from django.shortcuts import HttpResponse
2<br />
3 -> def soma(a,b):
4 arg1 = a
5 arg2 = b
6 soma = arg1 + arg2
7 return soma
8<br />
9 def hello(request):
10 a = 2
11 import pdb
Como podemos ver o debugger se encontra na entrada da função soma, damos uma “olhada por cima” e saímos da função com up (de up, sobe uma chamada na pilha):
> /home/.../djdebug/views.py(4)soma()
-> arg1 = a
(Pdb) n
> /home/.../djdebug/views.py(5)soma()
-> arg2 = b
(Pdb) n
> /home/.../djdebug/views.py(6)soma()
-> soma = arg1 + arg2
(Pdb) u
> /home/.../djdebug/views.py(16)hello()
-> d = soma(3,4)
Como podemos ver, voltamos a view hello, vamos avançar até antes de retornar a resposta da requisição e atestar umas coisinhas:
(Pdb) n
> /home/.../djdebug/views.py(17)hello()
-> response = HttpResponse("Hello World")
(Pdb) n
> /home/.../djdebug/views.py(18)hello()
-> return response
(Pdb) dir(response)
['<strong>class</strong>', '<strong>contains</strong>', '<strong>delattr</strong>', '<strong>delitem</strong>', '<strong>dict</strong>', '<strong>doc</strong>', '<strong>format</strong>', '<strong>getattribute</strong>', '<strong>getitem</strong>', '<strong>hash</strong>', '<strong>init</strong>', '<strong>iter</strong>', '<strong>module</strong>', '<strong>new</strong>', '<strong>reduce</strong>', '<strong>reduce_ex</strong>', '<strong>repr</strong>', '<strong>setattr</strong>', '<strong>setitem</strong>', '<strong>sizeof</strong>', '<strong>str</strong>', '<strong>subclasshook</strong>', '<strong>weakref</strong>', '_charset', '_container', '_convert_to_ascii', '_get_content', '_headers', '_is_string', '_set_content', 'close', 'content', 'cookies', 'delete_cookie', 'flush', 'get', 'has_header', 'items', 'next', 'set_cookie', 'status_code', 'tell', 'write']
(Pdb) response.content
'Hello World'
(Pdb) response.content = 'Django debugado'
(Pdb) c
Como podemos verificar, o pdb[3] fornece um shell do Python[2] você pode executar dir, verificar valores de variáveis e até mudar conteúdo de variáveis (poder e perigo andando juntos), por fim executamos c (de continue, para continuar a execução), veja o resultado no seu navegador, a mensagem "Hello World" foi trocada por "Django debugado".
Recomendo a leitura do paper “Debugging in Python”[4] para mais alguns detalhes do pdb[3]. É muito interessante saber “brincar” com o shell do Python e do Django, com essas ferramentas fica mais fácil rastrear o chato do bug.
[1] - http://www.djangoproject.com/
[2] – http://www.python.org/
[3] – http://docs.python.org/library/pdb.html
[4] – http://www.ferg.org/papers/debugging_in_python.html