Django: Como criar um sistema web completo

Configuração inicial, controle de acesso, criação de apps, utilização do ORM

Publicado por Lohan Bodevan 21 de Setembro de 2016 às 11:35

Introdução

Este post deveria se chamar “Admin em 15 minutos”. Pois “levantar” um admin com interface amigável, protegido por login, com controle de níveis de acesso, seja por grupo, seja por usuário e pronto para iniciar a configuração dos módulos, não leva nem isso de tempo.

Mas aqui vamos além do admin, falaremos sobre criação de apps, versionamento de banco de dados, testes unitários (claro), como fazer uma query com o ORM do Django, dentre outras coisas.

Django é um framework fullstack escrito em Python e um dos mais populares da comunidade. Ele nos oferece diversos recursos prontos, que no dia a dia, perdemos muito tempo programando. O objetivo do Django é permitir que possamos criar aplicações web de forma rápida, porém de uma maneira elegante.

Resumindo, se você tem um prazo apertado, não quer reinventar a roda e precisa de uma solução completa para um sistema web (site + admin), você deveria estar utilizando o Django.

 

O que você precisa saber antes de começar?

Para esse post, por incrível que pareça você não precisa ter experiência com Python. Aliás, o Django é para muitos programadores, a porta de entrada para essa linguagem.

 

Preparando o ambiente

Vamos utilizar a versão 3.x do Python. No Ubuntu 14.04 o Python 3.4 já vem instalado nativamente. Ou  você pode fazer o download. Recomenda-se a versão 3.5.

Para quem usa OSX, a instalação via brew é a mais indicada.

brew install python3

Iremos instalar também os seguintes pacotes:

  • python3-dev

  • libmysqlclient-dev

Para quem usa Ubuntu, esses pacotes são facilmente instalados via apt-get. 

Virtualenv

O virtualenv é uma biblioteca utilizada para criar ambientes isolados de versões Python, além de nos ajudar a manter isoladas as dependências dos projetos.

Vamos utilizá-lo para criar nossos ambientes e instalaremos ele com o PIP.

pip install virtualenv

O PIP é um gerenciador de pacotes para Python, assim como temos o NPM para Node.js e o Composer para PHP. Se você tem a versão 3 do Python então você já tem o PIP.

Recapitulando: Vamos utilizar o Python 3.x, o virtualenv para criar nossos ambientes de versões Python e o PIP para instalar nossas dependências.

Ação

Antes de mais nada precisamos criar o nosso ambiente.

virtualenv --python=python3 venv

Esse comando irá criar uma pasta chamada venv, ela pode ter qualquer nome, porém é uma convenção na comunidade Python utilizar o nome “venv”.

Para iniciar o ambiente criado, executaremos o seguinte comando:

. venv/bin/activate

Isso fará com que, a partir de agora, todas as dependências que instalemos fiquem isoladas nesta pasta. Você saberá que o ambiente foi inicializado pois haverá  o nome da pasta que você escolheu para ser seu ambiente virtual, entre parênteses, no início da linha do cursor:

Ativando virtualenv

 

Instalando o Django

pip install django

Só isso! Após o término do download e da instalação já podemos começar a configurar nossa aplicação.

django-admin startproject django_init .

O parâmetro django_init é o nome que escolhi para o projeto, você poderá utilizar outro se preferir.

Versionamento do banco de dados

O Django possui um sistema de versionamento de banco de dados. Como o framework cria por conta própria as tabelas da nossa base, para cada alteração que fazemos nos nossos models, o Django cria uma nova versão do banco e as aplica.

Antes de rodarmos a versão inicial do banco de dados, vamos ver como configurar a conexão.

No arquivo django_init/settings.py existe uma configuração chamada DATABASES, ele tem esse formato:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

 

Nós iremos modificá-la para utilizar o MySQL:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': os.getenv('DB_NAME', ‘django_init’),
        'USER': os.getenv('DB_USER', 'root'),
        'PASSWORD': os.getenv('DB_PASSWORD', ''),
        'HOST': os.getenv('DB_HOST', 'localhost'),
        'PORT': '3306'
    }
}

A sentença os.getenv('DB_NAME', ‘django_init’) significa que o Python tentará encontrar uma variável de ambiente chamada DB_NAME, caso não encontre usará o valor ‘django_init’ como default. Ele será usado para o nome da base de dados. O mesmo percebemos para USER e HOST.

Agora instalaremos a lib que possibilitará a conexão:

pip install mysqlclient

Com isso, nossa aplicação está preparada para se conectar ao MySQL

O Django cria por conta própria a estrutura da nossa base mas, para isso é preciso que você crie o schema. Então no seu client MySQL preferido execute:

create database django_init;

Lembrando que o schema deve ter o nome que você definiu em NAME na configuração DATABASES do seu arquivo django_init/settings.py

Agora estamos prontos para executar o migrate inicial do Django:

python manage.py migrate

Isso fará com que a estrutura básica do nosso projeto seja criado, o resultado deve ser como na imagem abaixo:

Migrate inicial 

Rodando o projeto

python manage.py runserver 0.0.0.0:8022

Este comando irá iniciar nossa aplicação que está disponível na porta 8022, se tudo der certo você tem que ver a seguinte tela ao acessar http://localhost:8022

Django Admin

 

Se acessarmos a url http://localhost:8022/admin veremos uma tela de login:

Django Admin

 

Incrível não é? Você programou essa tela de login? nem eu.

Super usuário

Para acessar o admin, precisamos configurar um super usuário, ele será responsável por criar outros usuários e setar as permissões, então vamos parar (Ctrl + c) o nosso projeto que está rodando no terminal e executar o comando:

python manage.py createsuperuser

Após criar o super usuário e iniciar novamente o projeto, voltaremos ao browser para entrar com as credenciais no admin. Ao logar, você deve ver a seguinte tela:

Acessando o Django Admin

 

É possível criar outros usuários clicando em "+ Addna linha Users, porém ele só poderá acessar o admin se, após inserir, for marcada a opção “Staff status” e salvar novamente.

Usuário criado

 

 

Nesta mesma tela você encontrará um box para administrar as permissões deste usuário, você pode fazer isso para cada usuário ou criar um grupo com as permissões desejadas e associar usuários a este grupo. Esta parte vou deixar para que você se divirta sozinho depois! Se tiver qualquer dúvida diga nos comentários.

Tradução

Ainda falando da configuração, você deve ter percebido que o Admin está todo em inglês. Para mudar isso vamos editar a configuração LANGUAGE_CODE no arquivo django_init/settings.py. Por default ela vem definida como “en-us”. Para traduzir o admin, incluindo mensagens de sucesso e falha para o português, basta mudar para “pt-br” e salvar o arquivo. Dê um refresh na página e verá as mudanças.

Até aqui, criamos nosso projeto com o framework Django, configuramos nossa base de dados, criamos a estrutura principal com o migrate, definimos um super usuário e traduzimos nosso admin. Vimos também que é possível dar permissões aos usuários, e que podemos definir que alguns só possam ler um registro, ou só editar, ou não permitir que ele faça deleções, ou só faça inserção. Tudo isso através de uma interface amigável e auto explicativa, de bandeja. E nem começamos a codar.

Criando apps

O Django separa cada unidade de negócio em apps. No nosso exemplo nós iremos criar uma app Django para controlar um cadastro de fornecedores chamada “supplier”. Ela terá os campos: nome, telefone e email.

Paramos novamente nosso projeto e executaremos o comando abaixo:

python manage.py startapp supplier

Veja que uma pasta de mesmo nome da app será criada na raiz do projeto.

Models

Os models representam nosso banco de dados, conforme descrito acima vamos criar nosso cadastro de fornecedores. Para tal, o arquivo supplier/models.py deverá ser da seguinte maneira:

from django.db import models

class Supplier(models.Model):
    name = models.CharField(max_length=50, blank=False)
    phone = models.CharField(max_length=20, blank=True)
    email = models.CharField(max_length=100, blank=True)

    def __str__(self):
        return self.name

Acredito que código seja auto explicativo mas, para não ficar dúvidas estamos discriminando como será a configuração de cada coluna para tabela supplier, quantidade máxima de caracteres, se permite ou não nulo. Existe uma longa documentação que você pode consultar com relação aos model fields. Talvez o que possa causar confusão no primeiro momento é o método __str__. Por agora, entenda apenas que esse método será executado sempre que um objeto dessa classe for invocado.

Feito isso precisamos habilitar nossa nova app no projeto principal. Voltaremos então ao arquivo django_init/settings.py e adicionamos “supplier” ao inicio configuração INSTALLED_APPS ficando ela assim:

INSTALLED_APPS = [
   'supplier',
      'django.contrib.admin',
      'django.contrib.auth',
      'django.contrib.contenttypes',
      'django.contrib.sessions',
      'django.contrib.messages',
      'django.contrib.staticfiles',
]

 Criando versão do banco de dados para nossa app

Agora vamos dizer ao nosso projeto que temos uma nova versão do banco de dados, para que o migrate crie a nova tabela.

python manage.py makemigrations
python manage.py migrate

Você deverá ver as seguintes mensagens de retorno:

Aplicando migrate

 

Adicionando apps no Admin

Para que uma app seja administrada pelo Django Admin, é preciso registrá-la no arquivo admin.py de sua app, no caso supplier/admin.py:

from django.contrib import admin
from .models import Supplier

admin.site.register(Supplier)

Feito isso, voltamos a iniciar nosso projeto:

python manage.py runserver 0.0.0.0:8022

Acessamos http://localhost:8022/admin e Voilá, temos o CRUD completo de fornecedores, com validação para campos não nulo:

Nova app

 

Espero que você tenha percebido a esta altura o quão simples é criar apps do Django.

Modificando uma app 

Para falar um pouco mais sobre o versionamento do nosso banco, imagine que agora nosso cadastro de fornecedor, necessite de um novo campo, chamado endereço.

Vamos editar então o nosso model Supplier:

from django.db import models

class Supplier(models.Model):
    name = models.CharField(max_length=50, blank=False)
    phone = models.CharField(max_length=20, blank=True)
    email = models.CharField(max_length=100, blank=True)
address = models.CharField(max_length=150, blank=True)

    def __str__(self):
        return self.name

Com o projeto parado no terminal, criamos uma nova versão para o banco de dados e executamos o migrate:

python manage.py makemigrations
python manage.py migrate

Pronto, podemos rodar novamente o projeto e verificar o novo campo no formulário do admin:

Novo campo no app

 

Utilizando as Views do Django

O nosso projeto não é apenas um admin, nós queremos listar nossos fornecedores no site. Então vamos fazer um exemplo bem simples de listagem.

Na raiz da pasta da nossa app (supplier) iremos criar uma pasta templates que por sua vez conterá uma pasta com o nome da app, ficando assim: supplier/templates/supplier. E então criaremos um arquivo chamado list.html com o seguinte conteúdo:

<!DOCTYPE html>
<html lang="pt-BR">
    <head>
      <title>Django Init</title>
   </head>
   <body>
      <p>Fornecedores cadastrados</p>
      <ul>
         {% for item in supplier_list %}
            <li>
               <b>Nome:</b> {{ item.name }} - <b>Telefone:</b> {{ item.phone }}
            </li>
         {% endfor %}
      </ul>
   </body>
</html>

Agora iremos criar nossa view. Uma view no Django é responsável por receber o request, processar e responder adequadamente, em sua maioria renderizando um template.

Na raiz da pasta de nossa app existe o arquivo views.py. Nós o modificaremos para ficar da seguinte forma:

from django.shortcuts import render
from .models import Supplier

def supplier_list(request):
    suppliers = Supplier.objects.all()
   context = {
      'supplier_list': suppliers
   }
   return render(request, 'supplier/list.html', context)

Aqui já vemos um pouco de como funciona o ORM do Django, a linha abaixo é uma consulta ao banco de dados.

suppliers = Supplier.objects.all()

Ela é equivalente a seguinte query:

SELECT * FROM `supplier`;

Com a nossa view pronta, precisamos dizer a app, qual URL nossa ela irá responder. Para isso vamos criar o arquivo supplier/urls.py

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^$', views.supplier_list, name='list'),
]

Os urlpatters podem ser definidos com expressões regulares. Se quiséssemos por exemplo, passar um ID na url, ela seria algo dessa forma:

url(r'^(?P<supplier_id>[0-9]+)$', views.supplier_detail, name='detail')

Ok! Agora vamos incluir as URLs de nossa app no projeto principal, para isso editamos o arquivo django_init/urls.py

from django.conf.urls import url, include
from django.contrib import admin

urlpatterns = [
    url(r'^fornecedores/', include('supplier.urls')),
    url(r'^admin/', admin.site.urls),
]

Você reparou que a adicionamos a função include no imports e adicionamos a linha:

url(r'^fornecedores/', include('supplier.urls')),

Isso significa que ao acessar a URL http://localhost:8022/fornecedores, as configurações de URLs da nossa APP será invocada. Vamos testar!

Listagem de fornecedores

 

Fácil, não?

Filtrando dados com o ORM do Django 

Suponhamos que nosso sistema deva mostrar apenas fornecedores que possuam telefone. Moleza! No arquivo supplier/view.py trocaremos nossa query atual pela seguinte:

suppliers = Supplier.objects.exclude(phone__isnull=True).exclude(phone__exact='')

Esta expressão irá resultar na seguinte query:

SELECT * FROM `supplier` WHERE phone IS NOT NULL AND phone != ""

Acessando nossa página de fornecedores novamente e veremos a listagem filtrada:

Filtrando fornecedores sem telefone

 

Testes automatizados

Como todo software de qualidade o Django oferece um suporte muito bom para criar testes automatizados. Pode-se fazer mock do banco de dados, simular requisições, dentre outras coisas.

Vamos criar um teste simples que tem como objetivo, simular uma requisição HTTP de método GET na url /fornecedores e ela deve retornar apenas fornecedores que contenham telefones, assim como definimos anteriormente.

Fixtures

Sempre que executamos testes com Django, o framework cria em tempo de execução uma base de dados de testes para que possamos aplicar testes sem prejudicar nossa base oficial. Porém apenas levantar uma base de testes não é suficiente, é preciso imputar dados nela para o teste fazer sentido. Aí que entram as fixtures e é bem fácil utilizá-las.

Crie a pasta fixtures na raiz da pasta da app, ficando supplier/fixturesEntão podemos gerar fixtures a partir de nossa base oficial da seguinte maneira:

python manage.py dumpdata supplier >> supplier/fixtures/supplier.json

Este comando irá fazer um dump da app supplier em um arquivo JSON chamado supplier.json

Vamos ao teste, edite o arquivo supplier/test.py

from django.test import TestCase
from django.test.client import Client

from supplier.models import Supplier


class SupplierTestCase(TestCase):
    fixtures = ['supplier.json']

   def setUp(self):
      self.client = Client()

   def test_list_supplier_no_phone(self):
      expected = Supplier.objects.exclude(phone__isnull=True).exclude(phone__exact='')

      response = self.client.get('/fornecedores/')
      self.failUnlessEqual(response.status_code, 200)
      self.failUnlessEqual(len(response.context['supplier_list']), len(expected))

Este teste é bem simples, nós criamos um classe chamada SupplierTestCase que é uma subclasse de TestCase.

O método setUp é invocado sempre antes de cada teste. Nesse caso, para cada teste estamos iniciando um client HTTP que simulará um request.

O test_list_supplier_no_phone(self) é o nosso método de testes, os métodos de teste precisam começar com o prefixo “test_.

expected = Supplier.objects.exclude(phone__isnull=True).exclude(phone__exact='')

Esta linha está buscando no banco de dados de testes todos os fornecedores que possuem telefone, assim como fizemos lá na nossa view.py

response = self.client.get('/fornecedores/')

Aqui estamos fazendo o request para o endpoint /fornecedores. E a partir desse response conseguimos aplicar testes:

self.failUnlessEqual(response.status_code, 200)
self.failUnlessEqual(len(response.context['supplier_list']), len(expected))

A primeira linha testa o HTTP Status Code, ou seja, nossa página /fornecedores precisa responder 200. Na segunda testamos se a quantidade de fornecedores retornados no request, corresponde a quantidade esperada. Ou seja, se o response retornou apenas fornecedores com telefone.

Agora para o rodar o teste executaremos o seguinte comando no terminal:

python manage.py test

E o resultado deverá ser o seguinte:

Execução dos testes

 

Repare que o Django avisa que criou uma base de testes para nosso testes e a destruiu ao final.

 

Resultado

Assim como no post anterior, este tutorial vem acompanhado de um projeto no Github, para que você possa consultar, fazer fork, sugerir melhorias e etc. De cara, tem uma melhoria que deveria ser feita. Nossa regra de négocio, que é mostrar apenas fornecedores com telefone, está na view e isso não é bom. O correto seria movê-la para o model e nosso teste deve continuar funcionando.

Eu deixei um teste extra no Github, que prova que os dados que utilizamos nos testes são dados da base de dados de testes e não da base de dados real.

 

Conclusão

O Django tem muito mais a oferecer, infelizmente não dá para abordamos tanto, senão o post fica enorme sendo difícil absorver o conteúdo. Vou tentar abordar outros tópicos sobre Django nos próximos posts.


Grande abraço!


Comentários