As interfaces gráficas do usuário (GUI – Graphic User Interface) são bastante populares no uso de softwares em geral, e os programadores devem estar aptos a trabalhar com a criação de interfaces, já que torna o uso mais fácil além de aumentar a produtividade.
Para quem trabalha com desenvolvimento em Python, existem diversos frameworks e ferramentas que permitem a criação interfaces gráficas.
A seguir podemos ver alguns frameworks gráficos que permitem desenvolver interfaces em Python:
Saiba mais: Curso de Python
- WxWidgets;
- Tkinter;
- Kivy;
- PyGTK;
- PySide;
- QT.
Estes frameworks listados são apenas alguns que podem ser usados para criar interfaces em Python. Ao longo deste artigo você verá como criar interfaces usando a biblioteca Tkinter.
O que é Tkinter?
Tkinter é uma biblioteca da linguagem Python que acompanha a instalação padrão e permite desenvolver interfaces gráficas. Isso significa que qualquer computador que tenha o interpretador Python instalado é capaz de criar interfaces gráficas usando o Tkinter, com exceção de algumas distribuições Linux, exigindo que seja feita o download do módulo separadamente
Por que usar Tkinter?
Um dos motivos de estarmos usando o Tkinter como exemplo é a sua facilidade de uso e recursos disponíveis. Outra vantagem é que é nativo da linguagem Python, tudo o que precisamos fazer é importá-lo no momento do uso, ou seja, estará sempre disponível.
Instalação e primeiro uso do Tkinter
Para que você possa trabalhar com Tkinter, é necessário que tenha pelo menos algum conhecimento em Python, então, não abordaremos a instalação padrão do interpretador e seus recursos.
A instalação padrão do Python acompanha além do interpretador, um ambiente de desenvolvimento, o IDLE, que está incluso no pacote Python na maioria das versões, com exceção de algumas distribuições Linux.
Na Figura 1 vemos o ambiente de desenvolvimento incluso no pacote Python, que por sinal, foi feito com o próprio Tkinter.
Durante todos os exemplos do artigo estaremos usando o IDLE na versão 3.5 do Python.
O Tkinter já vem por padrão na maioria das instalações Python, então tudo que temos que fazer é importar a biblioteca.
Para importar todo o conteúdo do módulo usamos o seguinte comando:
From Tkinter import *
Conceitos de GUI (Graphic User Interface)
Uma GUI aborda muitos conceitos, dos quais os mais comuns são:
- Container – É uma analogia a um container físico e tem como objetivo organizar e guardar objetos. Da mesma forma este conceito serve para um container em interface. Nesse caso, os objetos que estamos armazenando são os widgets;
- Widget – É um componente qualquer na tela, que pode ser um botão, um ícone, uma caixa de texto, etc.;
- Event Handler – São tratadores de eventos. Por exemplo, ao clicarmos em um botão para executar uma ação, uma rotina é executada. Essa rotina é chamada de event handler;
- Event Loop – O event loop verifica constantemente se outro evento foi acionado. Caso a hipótese seja verdadeira, ele irá executar a rotina correspondente.
Montando a interface
Vamos começar a montar a interface. Começaremos a escrever nosso primeiro código usando a Listagem 1.
class Application:
def __init__(self, master=None):
pass
root = Tk()
Application(root)
root.mainloop()
Vamos entender o código: primeiro importamos todos os componentes do módulo Tkinter. Logo após, criamos uma classe chamada Application, que no momento ainda não possui nenhuma informação. É nela que criaremos os controles que serão exibidos na tela.
Depois instanciamos a classe TK() através da variável root, que foi criada no final do código. Essa classe permite que os widgets possam ser utilizados na aplicação.
Em Application(root) passamos a variável root como parâmetro do método construtor da classe Application. E para finalizar, chamamos o método root.mainloop() para exibirmos a tela. Sem o event loop, a interface não será exibida.
O código foi salvo em um arquivo .py (extensão Python) e depois executado. O resultado obtido é o mesmo da Figura 2.
Adicionando Widgets e montando a interface
Para poder trabalhar com widgets é necessário entender o conceito de container, que é uma estrutura onde os widgets são colocados. Por questão de organização e para sua correta criação, definimos os containeres, e dentro de cada container, um ou mais widgets que o compõe.
Com a Figura 3 entenderemos melhor os conceitos de container e widget.
Sempre que um container for criado dentro de outro, devemos, no momento de sua criação, definir qual é o container pai. A mesma questão de hierarquia serve para a criação de widgets, devendo ser definido na sua criação qual o container pai, ou seja, em que container ele será incluído.
Logo após definirmos a hierarquia de containeres e widgets, podemos posicionar os elementos na tela, indicando a posição em que o elemento irá aparecer.
O módulo Tkinter oferece três formas de trabalharmos com geometria e posicionamento:
- Pack;
- Grid;
- Place.
Neste artigo abordaremos o gerenciador de posicionamento Pack. Caso um widget não seja aplicado a nenhum gerenciador geométrico, ele continua existindo, mas invisível ao usuário.
Vamos criar nosso primeiro widget, como uma caixa de texto dentro do container principal, como mostra a Listagem 2.
from tkinter import *
class Application:
def __init__(self, master=None):
self.widget1 = Frame(master)
self.widget1.pack()
self.msg = Label(self.widget1, text="Primeiro widget")
self.msg.pack ()
root = Tk()
Application(root)
root.mainloop()
O código resultará na interface da Figura 4.
Comparando com a primeira figura, quando não tínhamos definido nenhum widget, a tela diminuiu de tamanho. Isso aconteceu porque como não tinha nenhum conteúdo definido e a tela assumiu o tamanho padrão, mas a partir do momento que definimos um widget dentro da janela top-level (principal), a mesma assumiu o tamanho deste widget.
Veja na linha 4 do código que criamos o primeiro container chamado widget1 e passamos como referência o container pai.
O frame master é o nosso top level, que é o elemento máximo da hierarquia, ou seja, é a nossa janela de aplicação, que contém o título, e botões de maximizar, minimizar e fechar.
Na linha 5 informamos o gerenciador de geometria pack e usamos um widget label para imprimir na tela as palavras "Primeiro widget" e informamos que o container pai é o widget1, que foi passado como parâmetro, conforme a linha 6.
Por fim, exibimos na tela mais uma vez, usamos o gerenciador pack (linha 7).
Agora, vamos adicionar outros widgets à nossa interface, conforme mostra a Listagem 3.
class Application:
def __init__(self, master=None):
self.widget1 = Frame(master)
self.widget1.pack()
self.msg = Label(self.widget1, text="Primeiro widget")
self.msg["font"] = ("Verdana", "10", "italic", "bold")
self.msg.pack ()
self.sair = Button(self.widget1)
self.sair["text"] = "Sair"
self.sair["font"] = ("Calibri", "10")
self.sair["width"] = 5
self.sair["command"] = self.widget1.quit
self.sair.pack ()
root = Tk()
Application(root)
root.mainloop()
O código resultará na interface da Figura 5.
Veja que adicionamos um widget do tipo Button e depois atribuímos os valores e estilização, como altura, largura e tipo da fonte a ser exibida.
Existem muitas outras configurações que podem ser usadas. Vamos ver mais algumas delas quando estivermos adicionando elementos à nossa interface.
Posicionando e atribuindo valores a elementos da tela
Quem já trabalha ou já trabalhou com CSS já está familiarizado com a maioria das configurações de estilo.
Vamos ver algumas configurações de estilo mais comuns que podemos definir:
- Width – Largura do widget;
- Height – Altura do widget;
- Text – Texto a ser exibido no widget;
- Font – Família da fonte do texto;
- Fg – Cor do texto do widget;
- Bg – Cor de fundo do widget;
- Side – Define em que lado o widget se posicionará (Left, Right, Top, Bottom).
Por padrão, o side vem definido como Top, então vamos usá-lo para ver o que acontece.
Vamos modificar o último código, conforme mostra a Listagem 4.
from tkinter import *
class Application:
def __init__(self, master=None):
self.widget1 = Frame(master)
self.widget1.pack(side=RIGHT)
self.msg = Label(self.widget1, text="Primeiro widget")
self.msg["font"] = ("Verdana", "10", "italic", "bold")
self.msg.pack ()
self.sair = Button(self.widget1)
self.sair["text"] = "Sair"
self.sair["font"] = ("Verdana", "10")
self.sair["width"] = 5
self.sair["command"] = self.widget1.quit
self.sair.pack (side=RIGHT)
root = Tk()
Application(root)
root.mainloop()
O resultado é o mesmo da Figura 6.
Adicionamos a configuração pack(side=RIGHT) no elemento de texto e ele se alinhou à direita, colocando o restante do conteúdo na área restante.
Aplicando o event handler
Os event handlers são ações que executadas como resposta a determinado evento. Sempre que um evento ocorre, o event loop o interpreta como uma string. Por exemplo, ao clicar com o botão esquerdo do mouse, o event loop interpreta esta ação pela string "": o botão ENTER é representado pela string "" e o botão direito do mouse pela string "".
Vamos começar a usar esses conceitos. Para adicionar um evento de clique devemos primeiro criar o método event handler e fazer o binding no botão, conforme mostra a Listagem 5.
from tkinter import *
class Application:
def __init__(self, master=None):
self.widget1 = Frame(master)
self.widget1.pack()
self.msg = Label(self.widget1, text="Primeiro widget")
self.msg["font"] = ("Calibri", "9", "italic")
self.msg.pack ()
self.sair = Button(self.widget1)
self.sair["text"] = "Clique aqui"
self.sair["font"] = ("Calibri", "9")
self.sair["width"] = 10
self.sair.bind("<Button-1>", self.mudarTexto)
self.sair.pack ()
def mudarTexto(self, event):
if self.msg["text"] == "Primeiro widget":
self.msg["text"] = "O botão recebeu um clique"
else:
self.msg["text"] = "Primeiro widget"
root = Tk()
Application(root)
root.mainloop()
Quando executamos este código e o botão que foi criado recebe um clique, o texto é modificado na tela, graças ao event handler associado ao bind, como mostra a Figura 7.
Perceba que quando criamos o método mudarTexto() adicionamos dois parâmetros: o self e o event. Nesse caso eles são obrigatórios para o funcionamento e devem ser sempre os dois primeiros parâmetros do método.
Outra opção que temos é passar diretamente o comando como atributo do widget, conforme mostra a Listagem 6.
from tkinter import *
class Application:
def __init__(self, master=None):
self.widget1 = Frame(master)
self.widget1.pack()
self.msg = Label(self.widget1, text="Primeiro widget")
self.msg["font"] = ("Calibri", "9", "italic")
self.msg.pack ()
self.sair = Button(self.widget1)
self.sair["text"] = "Clique aqui"
self.sair["font"] = ("Calibri", "9")
self.sair["width"] = 10
self.sair["command"] = self.mudarTexto
self.sair.pack ()
def mudarTexto(self):
if self.msg["text"] == "Primeiro widget":
self.msg["text"] = "O botão recebeu um clique"
else:
self.msg["text"] = "Primeiro widget"
root = Tk()
Application(root)
root.mainloop()
No código não foi preciso passar o argumento event como parâmetro do método, já que passamos o evento como atributo de um widget e assim ele estará sempre associado ao clique do mouse, não sendo necessário passar a string .
Recebendo dados do usuário
Para receber dados do usuário vamos usar o widget Entry, onde os mesmos são capturados como string, de forma semelhante ao método input.
Vamos criar uma entrada de dados simples utilizando o Entry, como mostra a Listagem 7.
from tkinter import *
class Application:
def __init__(self, master=None):
self.fontePadrao = ("Arial", "10")
self.primeiroContainer = Frame(master)
self.primeiroContainer["pady"] = 10
self.primeiroContainer.pack()
self.segundoContainer = Frame(master)
self.segundoContainer["padx"] = 20
self.segundoContainer.pack()
self.terceiroContainer = Frame(master)
self.terceiroContainer["padx"] = 20
self.terceiroContainer.pack()
self.quartoContainer = Frame(master)
self.quartoContainer["pady"] = 20
self.quartoContainer.pack()
self.titulo = Label(self.primeiroContainer, text="Dados do usuário")
self.titulo["font"] = ("Arial", "10", "bold")
self.titulo.pack()
self.nomeLabel = Label(self.segundoContainer,text="Nome", font=self.fontePadrao)
self.nomeLabel.pack(side=LEFT)
self.nome = Entry(self.segundoContainer)
self.nome["width"] = 30
self.nome["font"] = self.fontePadrao
self.nome.pack(side=LEFT)
self.senhaLabel = Label(self.terceiroContainer, text="Senha", font=self.fontePadrao)
self.senhaLabel.pack(side=LEFT)
self.senha = Entry(self.terceiroContainer)
self.senha["width"] = 30
self.senha["font"] = self.fontePadrao
self.senha["show"] = "*"
self.senha.pack(side=LEFT)
self.autenticar = Button(self.quartoContainer)
self.autenticar["text"] = "Autenticar"
self.autenticar["font"] = ("Calibri", "8")
self.autenticar["width"] = 12
self.autenticar["command"] = self.verificaSenha
self.autenticar.pack()
self.mensagem = Label(self.quartoContainer, text="", font=self.fontePadrao)
self.mensagem.pack()
#Método verificar senha
def verificaSenha(self):
usuario = self.nome.get()
senha = self.senha.get()
if usuario == "usuariodevmedia" and senha == "dev":
self.mensagem["text"] = "Autenticado"
else:
self.mensagem["text"] = "Erro na autenticação"
root = Tk()
Application(root)
root.mainloop()
A execução do código resultará na Figura 8.
Veja no código que tudo que precisamos fazer para receber a entrada foi usar o widget Entry e informar o container em que foi aplicado.
Usamos também o método get() para recuperar o texto que foi digitado pelo usuário e atribuímos as variáveis usuario e senha.
Criando uma pequena aplicação com Banco de dados
Agora vamos criar uma pequena aplicação com os comandos já vistos até agora e mais alguns que veremos durante o desenvolvimento de nossa aplicação com acesso ao banco de dados.
Para acessarmos o mesmo usaremos o módulo DBI (DataBase Interface), que usa uma API para se comunicar com o banco de dados.
No exemplo usaremos o SQLite, que já vem incorporado ao módulo DBI. Então podemos acessá-lo sem necessidade de instalar módulos externos.
O SQLite cria um arquivo com extensão .db em disco, contendo todas as tabelas da aplicação.
Vamos à estrutura da nossa aplicação, onde criaremos três arquivos:
- App.py (Conterá os containeres da interface e executará a aplicação);
- Usuarios.py (Classe de modelo para usuários);
- Banco.py (Classe de banco de dados).
Começaremos pelo nosso arquivo Banco.py. Primeiro devemos importar o módulo do SQLite:
#importando módulo do SQlite
Import sqlite3
Após isso, seguimos com o restante do código do arquivo, conforme mostra a Listagem 8.
class Banco():
def __init__(self):
self.conexao = sqlite3.connect('banco.db')
self.createTable()
def createTable(self):
c = self.conexao.cursor()
c.execute("""create table if not exists usuarios (
idusuario integer primary key autoincrement ,
nome text,
telefone text,
email text,
usuario text,
senha text)""")
self.conexao.commit()
c.close()
Já criamos nosso arquivo de conexão com o banco de dados e agora criaremos o arquivo usuarios.py, conforme mostra a Listagem 9.
from Banco import Banco
class Usuarios(object):
def __init__(self, idusuario = 0, nome = "", telefone = "",
email = "", usuario = "", senha = ""):
self.info = {}
self.idusuario = idusuario
self.nome = nome
self.telefone = telefone
self.email = email
self.usuario = usuario
self.senha = senha
def insertUser(self):
banco = Banco()
try:
c = banco.conexao.cursor()
c.execute("insert into usuarios (nome, telefone, email,
usuario, senha) values ('" + self.nome + "', '" +
self.telefone + "', '" + self.email + "', '" +
self.usuario + "', '" + self.senha + "' )")
banco.conexao.commit()
c.close()
return "Usuário cadastrado com sucesso!"
except:
return "Ocorreu um erro na inserção do usuário"
def updateUser(self):
banco = Banco()
try:
c = banco.conexao.cursor()
c.execute("update usuarios set nome = '" + self.nome + "',
telefone = '" + self.telefone + "', email = '" + self.email +
"', usuario = '" + self.usuario + "', senha = '" + self.senha +
"' where idusuario = " + self.idusuario + " ")
banco.conexao.commit()
c.close()
return "Usuário atualizado com sucesso!"
except:
return "Ocorreu um erro na alteração do usuário"
def deleteUser(self):
banco = Banco()
try:
c = banco.conexao.cursor()
c.execute("delete from usuarios where idusuario = " + self.idusuario + " ")
banco.conexao.commit()
c.close()
return "Usuário excluído com sucesso!"
except:
return "Ocorreu um erro na exclusão do usuário"
def selectUser(self, idusuario):
banco = Banco()
try:
c = banco.conexao.cursor()
c.execute("select * from usuarios where idusuario = " + idusuario + " ")
for linha in c:
self.idusuario = linha[0]
self.nome = linha[1]
self.telefone = linha[2]
self.email = linha[3]
self.usuario = linha[4]
self.senha = linha[5]
c.close()
return "Busca feita com sucesso!"
except:
return "Ocorreu um erro na busca do usuário"
Já temos a classe de conexão com o banco de dados e a classe que modela o usuário.
O próximo passo é utilizar os dados dessas duas classes no nosso arquivo de execução App.py, que terá a nossa interface, como mostra a Listagem 10.
from Usuarios import Usuarios
from tkinter import *
class Application:
def __init__(self, master=None):
self.fonte = ("Verdana", "8")
self.container1 = Frame(master)
self.container1["pady"] = 10
self.container1.pack()
self.container2 = Frame(master)
self.container2["padx"] = 20
self.container2["pady"] = 5
self.container2.pack()
self.container3 = Frame(master)
self.container3["padx"] = 20
self.container3["pady"] = 5
self.container3.pack()
self.container4 = Frame(master)
self.container4["padx"] = 20
self.container4["pady"] = 5
self.container4.pack()
self.container5 = Frame(master)
self.container5["padx"] = 20
self.container5["pady"] = 5
self.container5.pack()
self.container6 = Frame(master)
self.container6["padx"] = 20
self.container6["pady"] = 5
self.container6.pack()
self.container7 = Frame(master)
self.container7["padx"] = 20
self.container7["pady"] = 5
self.container7.pack()
self.container8 = Frame(master)
self.container8["padx"] = 20
self.container8["pady"] = 10
self.container8.pack()
self.container9 = Frame(master)
self.container9["pady"] = 15
self.container9.pack()
self.titulo = Label(self.container1, text="Informe os dados :")
self.titulo["font"] = ("Calibri", "9", "bold")
self.titulo.pack ()
self.lblidusuario = Label(self.container2,
text="idUsuario:", font=self.fonte, width=10)
self.lblidusuario.pack(side=LEFT)
self.txtidusuario = Entry(self.container2)
self.txtidusuario["width"] = 10
self.txtidusuario["font"] = self.fonte
self.txtidusuario.pack(side=LEFT)
self.btnBuscar = Button(self.container2, text="Buscar",
font=self.fonte, width=10)
self.btnBuscar["command"] = self.buscarUsuario
self.btnBuscar.pack(side=RIGHT)
self.lblnome = Label(self.container3, text="Nome:",
font=self.fonte, width=10)
self.lblnome.pack(side=LEFT)
self.txtnome = Entry(self.container3)
self.txtnome["width"] = 25
self.txtnome["font"] = self.fonte
self.txtnome.pack(side=LEFT)
self.lbltelefone = Label(self.container4, text="Telefone:",
font=self.fonte, width=10)
self.lbltelefone.pack(side=LEFT)
self.txttelefone = Entry(self.container4)
self.txttelefone["width"] = 25
self.txttelefone["font"] = self.fonte
self.txttelefone.pack(side=LEFT)
self.lblemail= Label(self.container5, text="E-mail:",
font=self.fonte, width=10)
self.lblemail.pack(side=LEFT)
self.txtemail = Entry(self.container5)
self.txtemail["width"] = 25
self.txtemail["font"] = self.fonte
self.txtemail.pack(side=LEFT)
self.lblusuario= Label(self.container6, text="Usuário:",
font=self.fonte, width=10)
self.lblusuario.pack(side=LEFT)
self.txtusuario = Entry(self.container6)
self.txtusuario["width"] = 25
self.txtusuario["font"] = self.fonte
self.txtusuario.pack(side=LEFT)
self.lblsenha= Label(self.container7, text="Senha:",
font=self.fonte, width=10)
self.lblsenha.pack(side=LEFT)
self.txtsenha = Entry(self.container7)
self.txtsenha["width"] = 25
self.txtsenha["show"] = "*"
self.txtsenha["font"] = self.fonte
self.txtsenha.pack(side=LEFT)
self.bntInsert = Button(self.container8, text="Inserir",
font=self.fonte, width=12
self.bntInsert["command"] = self.inserirUsuario
self.bntInsert.pack (side=LEFT)
self.bntAlterar = Button(self.container8, text="Alterar",
font=self.fonte, width=12)
self.bntAlterar["command"] = self.alterarUsuario
self.bntAlterar.pack (side=LEFT)
self.bntExcluir = Button(self.container8, text="Excluir",
font=self.fonte, width=12)
self.bntExcluir["command"] = self.excluirUsuario
self.bntExcluir.pack(side=LEFT)
self.lblmsg = Label(self.container9, text="")
self.lblmsg["font"] = ("Verdana", "9", "italic")
self.lblmsg.pack()
def inserirUsuario(self):
user = Usuarios()
user.nome = self.txtnome.get()
user.telefone = self.txttelefone.get()
user.email = self.txtemail.get()
user.usuario = self.txtusuario.get()
user.senha = self.txtsenha.get()
self.lblmsg["text"] = user.insertUser()
self.txtidusuario.delete(0, END)
self.txtnome.delete(0, END)
self.txttelefone.delete(0, END)
self.txtemail.delete(0, END)
self.txtusuario.delete(0, END)
self.txtsenha.delete(0, END)
def alterarUsuario(self):
user = Usuarios()
user.idusuario = self.txtidusuario.get()
user.nome = self.txtnome.get()
user.telefone = self.txttelefone.get()
user.email = self.txtemail.get()
user.usuario = self.txtusuario.get()
user.senha = self.txtsenha.get()
self.lblmsg["text"] = user.updateUser()
self.txtidusuario.delete(0, END)
self.txtnome.delete(0, END)
self.txttelefone.delete(0, END)
self.txtemail.delete(0, END)
self.txtusuario.delete(0, END)
self.txtsenha.delete(0, END)
def excluirUsuario(self):
user = Usuarios()
user.idusuario = self.txtidusuario.get()
self.lblmsg["text"] = user.deleteUser()
self.txtidusuario.delete(0, END)
self.txtnome.delete(0, END)
self.txttelefone.delete(0, END)
self.txtemail.delete(0, END)
self.txtusuario.delete(0, END)
self.txtsenha.delete(0, END)
def buscarUsuario(self):
user = Usuarios()
idusuario = self.txtidusuario.get()
self.lblmsg["text"] = user.selectUser(idusuario)
self.txtidusuario.delete(0, END)
self.txtidusuario.insert(INSERT, user.idusuario)
self.txtnome.delete(0, END)
self.txtnome.insert(INSERT, user.nome)
self.txttelefone.delete(0, END)
self.txttelefone.insert(INSERT,user.telefone)
self.txtemail.delete(0, END)
self.txtemail.insert(INSERT, user.email)
self.txtusuario.delete(0, END)
self.txtusuario.insert(INSERT, user.usuario)
self.txtsenha.delete(0, END)
self.txtsenha.insert(INSERT,user.senha)
root = Tk()
Application(root)
root.mainloop()
O resultado será a interface da Figura 9, que mostra basicamente um CRUD (Create – Read – Update - Delete).
No artigo foi mostrado uma introdução ao Tkinter, que foi escolhido justamente pela quantidade de recursos que oferece, por ser relativamente simples e por ser nativo no pacote de instalação do Python.
Links Úteis
- Como implementar um CRUD em PHP com MVC: Neste curso vamos aprender a implementar um CRUD em PHP, utilizando arquitetura MVC.
- O que é Git?: Neste curso você aprenderá o que é o Git, ferramenta para controle de versão mais utilizada por desenvolvedores de software.
- Entity Framework: Como monitorar os comandos SQL gerados: Aprenda neste microexemplo a monitorar as instruções SQL que são executadas pelo Entity Framework quando efetuamos operações de acesso ao banco de dados em nossas aplicações.
Saiba mais sobre Python ;)
- Python: Neste Guia de Referência você encontrará todo o conteúdo que precisa para começar a programar com a linguagem Python e a desenvolver aplicações.
- Curso de Python: Afinal, o que é Python? O Python é uma linguagem que permite criar bons códigos e de fácil manutenção.
- Cursos de Python: Aqui você encontra os cursos para você se tornar um programador Python completo. Aprenda a desenvolver aplicações web com o framework Django e o Django Admin. Confira!