Dica 37 - Faker
Versões usadas no vídeo: Django 2.2, Faker 8.7.0, Python 3.8 e Bootstrap 4.
Importante: nas versões antigas desta página as tags de template apareciam com uma \ no meio ({%). Se copiar código de lá, remova a \.
Documentação: https://faker.readthedocs.io/en/master/
O Faker é uma biblioteca para popular o seu banco de dados com dados aleatórios: nomes, sobrenomes, e-mails, textos, datas, endereços e muito mais. É ótimo para ter uma base de desenvolvimento com volume de dados parecido com o real, para testar listagens, paginação e buscas.
Neste tutorial vamos:
- acrescentar dois campos ao modelo
Person; - criar um comando personalizado
create_dataque usa o Faker para cadastrar 100 pessoas de uma vez, combulk_createe uma barra de progresso; - trocar a view da lista de pessoas por uma
ListViewe montar a tabela no template.
Pré-requisitos
O vídeo continua o projeto do repositório dicas-de-django: um projeto myproject com a app myproject/core, um base.html com Bootstrap 4 e o modelo Person. Alguns assuntos usados aqui já foram vistos em outras dicas:
- Dica 17 - Criando comandos personalizados;
- Dica 18 - bulk_create e bulk_update;
- Dica 24 - Barra de progresso.
O modelo Person
Este é o modelo Person do projeto. Ele herda de UuidModel, uma classe abstrata que acrescenta um slug do tipo UUID. Vamos acrescentar os campos bio (biografia) e birthday (data de nascimento), os dois opcionais:
# myproject/core/models.py
import uuid
from django.db import models
class UuidModel(models.Model):
slug = models.UUIDField(unique=True, editable=False, default=uuid.uuid4)
class Meta:
abstract = True
class Person(UuidModel):
first_name = models.CharField('nome', max_length=50)
last_name = models.CharField('sobrenome', max_length=50, null=True, blank=True) # noqa E501
email = models.EmailField(null=True, blank=True)
bio = models.TextField('biografia', null=True, blank=True)
birthday = models.DateField('nascimento', null=True, blank=True)
class Meta:
ordering = ('first_name',)
verbose_name = 'pessoa'
verbose_name_plural = 'pessoas'
@property
def full_name(self):
return f'{self.first_name} {self.last_name or ""}'.strip()
def __str__(self):
return self.full_name
Gere e aplique a migração:
python manage.py makemigrations
python manage.py migrate
Instalando o Faker
pip install faker
E acrescente no requirements.txt (no vídeo, a versão instalada foi a 8.7.0):
Faker==8.7.0
Conhecendo os providers
Os dados do Faker são organizados em providers. Na documentação, em Standard Providers, você encontra a lista completa. Os que vamos usar:
- faker.providers.person:
first_name(),last_name(),name(),first_name_male()etc.; - faker.providers.lorem:
paragraph(nb_sentences=5)gera um parágrafo com aproximadamente 5 frases; - faker.providers.date_time:
date()gera uma data aleatória no formato'AAAA-MM-DD'.
O uso é sempre o mesmo: crie uma instância de Faker e chame o método do provider.
>>> from faker import Faker
>>> fake = Faker()
>>> fake.first_name()
'Alexis'
>>> fake.last_name()
'Aguilar'
>>> fake.date()
'2011-07-23'
Os valores mudam a cada chamada. Por padrão o Faker usa o idioma en_US, por isso os nomes e textos saem em inglês.
A barra de progresso
Para acompanhar a criação dos registros vamos reaproveitar a barra de progresso da Dica 24. Crie a pasta utils dentro de myproject e o arquivo progress_bar.py:
mkdir myproject/utils
touch myproject/utils/progress_bar.py
# myproject/utils/progress_bar.py
import sys
def progressbar(it, prefix="", size=60, file=sys.stdout):
count = len(it)
def show(j):
x = int(size * j / count)
file.write("%s[%s%s] %i/%i\r" %
(prefix, "#" * x, "." * (size - x), j, count))
file.flush()
show(0)
for i, item in enumerate(it):
yield item
show(i + 1)
file.write("\n")
file.flush()
A função progressbar é um gerador: ela devolve cada item do iterável it e, a cada item, reescreve a mesma linha do terminal (por causa do \r) com a quantidade de # proporcional ao progresso.
O comando create_data
Crie o comando dentro de management/commands da app core (as pastas management e commands, com seus __init__.py, já existem no projeto desde a Dica 17):
touch myproject/core/management/commands/create_data.py
# myproject/core/management/commands/create_data.py
from django.core.management.base import BaseCommand
from django.utils.text import slugify
from faker import Faker
from myproject.core.models import Person
from myproject.utils.progress_bar import progressbar
fake = Faker()
def gen_email(first_name: str, last_name: str):
first_name = slugify(first_name)
last_name = slugify(last_name)
email = f'{first_name}.{last_name}@email.com'
return email
def get_person():
first_name = fake.first_name()
last_name = fake.last_name()
email = gen_email(first_name, last_name)
bio = fake.paragraph(nb_sentences=5)
birthday = fake.date()
data = dict(
first_name=first_name,
last_name=last_name,
email=email,
bio=bio,
birthday=birthday,
)
return data
def create_persons():
aux_list = []
for _ in progressbar(range(100), 'Persons'):
data = get_person()
obj = Person(**data)
aux_list.append(obj)
Person.objects.bulk_create(aux_list)
class Command(BaseCommand):
help = 'Create data.'
def handle(self, *args, **options):
create_persons()
Entendendo cada parte:
fake = Faker(): uma única instância, criada no nível do módulo, usada por todas as funções.gen_email(): em vez de usarfake.email(), montamos o e-mail a partir do nome e do sobrenome da própria pessoa, assim os dados ficam coerentes (Alexis Aguilar →alexis.aguilar@email.com). Oslugifypassa para minúsculas, tira acentos e espaços. As anotações: strsão só type hints.get_person(): gera os dados de uma pessoa e devolve um dicionário cujas chaves são os nomes dos campos do modelo.create_persons(): repete 100 vezes, criando objetosPerson(**data)sem salvar, e guarda numa lista auxiliar. No fim,bulk_creategrava todos de uma vez, numa única query de insert, em vez de 100save(). Oprogressbar(range(100), 'Persons')envolve orangepara mostrar o progresso.Command.handle(): é o que roda quando você chamapython manage.py create_data.
Registrando o Person no Admin
Para conferir os dados (e apagá-los quando quiser gerar de novo), registre o modelo no Admin. No admin.py da app, acrescente o Person no import e o registro no final do arquivo:
# myproject/core/admin.py
from .models import Article, Category, Person
...
admin.site.register(Person)
A view, a URL e o template
A lista de pessoas era uma view de função que só renderizava o template. Vamos trocá-la por uma ListView (vamos precisar dela na próxima dica, para a paginação):
# myproject/core/views.py
from django.views.generic import ListView
from .models import Article, Person
class PersonListView(ListView):
model = Person
template_name = 'core/person_list.html'
A ListView busca Person.objects.all() e entrega a lista no contexto como object_list (e também como person_list).
Em urls.py, troque v.person_list por v.PersonListView.as_view():
# myproject/core/urls.py
from django.urls import path
from myproject.core import views as v
app_name = 'core'
urlpatterns = [
path('', v.index, name='index'),
path('persons/', v.PersonListView.as_view(), name='person_list'),
path('persons/create/', v.person_create, name='person_create'),
path('articles/', v.article_list, name='article_list'),
path('articles/filter/', v.article_filter_list, name='article_filter_list'),
path('articles/json/', v.article_json, name='article_json'),
]
E o template, com um formulário de busca (que vamos fazer funcionar na Dica 38) e a tabela com os dados:
<!-- myproject/core/templates/core/person_list.html -->
{% extends "base.html" %}
{% block content %}
<h1>Lista de pessoas</h1>
<div class="row">
<div class="col">
<form action="." method="GET">
<div class="row">
<div class="col">
<input name="search" class="form-control mb-2" type="text" placeholder="Buscar...">
</div>
<div class="col-auto">
<button class="btn btn-success mb-2" type="submit">OK</button>
<button class="btn btn-link mb-2">Limpar</button>
</div>
</div>
</form>
</div>
</div>
<table class="table">
<thead>
<tr>
<th>Nome</th>
<th>Sobrenome</th>
<th>E-mail</th>
<th>Biografia</th>
<th>Nascimento</th>
</tr>
</thead>
<tbody>
{% for object in object_list %}
<tr>
<td>{{ object.first_name }}</td>
<td>{{ object.last_name }}</td>
<td>{{ object.email }}</td>
<td>{{ object.bio }}</td>
<td>{{ object.birthday|date:"d/m/Y" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock content %}
O filtro date:"d/m/Y" mostra a data de nascimento no formato brasileiro.
Gerando os dados
Rode o comando:
python manage.py create_data
A barra de progresso vai enchendo até o fim:
Persons[############################################################] 100/100
Depois suba o servidor e acesse http://localhost:8000/persons/:
python manage.py runserver
A tabela mostra as 100 pessoas, em ordem de nome, com e-mails no formato nome.sobrenome@email.com, um parágrafo de biografia e a data de nascimento, por exemplo:
| Nome | Sobrenome | Nascimento | |
|---|---|---|---|
| Alexis | Aguilar | alexis.aguilar@email.com | 23/07/2011 |
| Alfred | Mills | alfred.mills@email.com | 06/07/2003 |
| Andrea | Brown | andrea.brown@email.com | 20/02/2008 |
No vídeo, os registros foram apagados pelo Admin (ação "Delete selected pessoas", já que o projeto está com LANGUAGE_CODE = 'en-us') e o comando foi rodado de novo, gerando outras 100 pessoas diferentes. Cada execução do create_data acrescenta mais 100 registros.
Conclusão
Com o Faker e um comando personalizado você popula o banco em segundos, com dados de aparência real. Na próxima dica, Dica 38 - Paginação + Filtros, vamos paginar essa lista e fazer a busca funcionar.
Observação: para gerar dados em português, passe o idioma na criação da instância: Faker('pt_BR').