Skip to content

O mais famoso tutorial de Django com DRF do IFC. Amado por uns, odiado por outros, respeitado por todos.

Notifications You must be signed in to change notification settings

marrcandre/django-drf-tutorial

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Django com DRF (2024)

Tutorial para desenvolvimento de APIs REST usando o Django com DRF (Django Rest Framework). Esse tutorial foi construído a partir do curso em vídeo Django com DRF do Eduardo da Silva.

Este tutorial está em constante desenvolvimento. Envie sugestões e correções para meu email. Se preferir, faça uma solicitação de contribuição ao projeto.

Trilha do Curso

Esse curso é parte de uma trilha de aprendizado. Siga os links abaixo para acessar os outros cursos da trilha:

Bons estudos!


1. Preparação do ambiente

A preparação do ambiente será feita apenas uma vez em cada computador. Ela consiste em instalar e configurar o VS Code, o PDM e o Python.

2. Criação do projeto

2.1 O projeto Livraria

Este projeto consiste em uma API REST para uma livraria. Ele terá as seguintes classes:

  • Categoria: representa a categoria de um livro.
  • Editora: representa a editora de um livro.
  • Autor: representa o autor de um livro.
  • Livro: representa um livro.
  • User: representa um usuário do sistema.
  • Compra: representa uma compra de livros.
  • ItemCompra: representa um item de uma compra.

Modelo Entidade Relacionamento

O modelo entidade relacionamento (MER) do projeto é o seguinte:

Modelo ER

Diagrama de Classes

O diagrama de classes do projeto é o seguinte:

Diagrama de Classes

Modelo de Dados do Django

O modelo de dados do Django é o seguinte:

Modelo de Dados do Django

2.2 Criação do projeto a partir de um template

IMPORTANTE: Vamos criar o projeto livraria a partir de um repositório de template. Se você quiser criar aprender a criar um projeto do zero, acesse o tutorial de 2023.

  • Acesse o template em https://github.com/marrcandre/template_django_pdm.
  • Clique no botão Use this template em Create a new repository.
  • Preencha as informações solicitadas:
    • Owner: <seu usuário no GitHub>
    • Repository name: livraria
  • Click no botão Create repository.

Feito isso, o repositório livraria será criado no seu GitHub.

2.3 Clonando o projeto

Você pode clonar o projeto de duas formas:

2.3.1 Usando o VS Code

  • Abra o VS Code.
  • Clique no ícone de Source Control na barra lateral esquerda.
    • Clique no botão Clone Repository.
    • Vocẽ também pode teclar Control+Shift+P e digitar Clone Repository.
  • Digite a URL do repositório do projeto (ou procure na lista de repositórios disponíveis).
  • Escolha a pasta onde o projeto será clonado.
  • Clique no botão Clone.

2.3.2 Usando o terminal

  • Abra o terminal.
  • Vá para a pasta onde o projeto será clonado.
  • Digite o comando:
git clone <URL do repositório>
  • Abra o projeto no VS Code, digitando:
code .

O projeto criado ficará assim:

Projeto inicial

2.4 Instalando as dependências

  • Abra o terminal no VS Code (Ctrl+Shift+´).
  • Instale as dependências do projeto:
pdm install

2.5 Criando o arquivo .env

  • Crie o arquivo .env, a partir do arquivo .env.exemplo:
  • Abra o arquivo .env.exemplo.
  • Escolha a opção Salvar como... (Ctrl+Shift+S).
  • Salve o arquivo como .env.

Opcionalmente, você pode criar o arquivo .env a partir do terminal, digitando:

cp .env.exemplo .env

2.4 Rodando o servidor de desenvolvimento

  • Para executar o projeto, digite no terminal:
pdm run dev

2.5 Acessando o projeto

  • Acesse o projeto no navegador:

    http://0.0.0.0:19003/admin

  • Os dados de acesso são:

    • Usuário: a@a.com
    • Senha: teste.123
  • Após acessar, você pode o nome do usuário e a senha.

IMPORTANTE: O servidor de desenvolvimento deve estar sempre rodando para que o projeto funcione.

É isso! Seu projeto está inicializado e rodando!!!

2.6 Exercício

  • Apague o projeto e crie novamente, seguindo as instruções acima.
  • Verifique se o projeto está rodando e se o Admin está em execução.
  • Observe que configurações precisam ser feitas novamente e quais não foram mais necessárias.

3. Criação de uma aplicação

3.1 Compreendendo uma aplicação

Uma aplicação no Django é um conjunto de arquivos e pastas que contém o código de uma funcionalidade específica do seu site.

Uma aplicação pode ser criada dentro de um projeto ou importada de outro projeto.

Em nosso projeto, temos uma aplicação criada, chamada core, conforme a imagem abaixo:

App core

Todas as aplicações precisam ser adicionadas ao arquivo settings.py do projeto, na seção INSTALLED_APPS.

Dentro da pasta core temos alguns arquivos e pastas, mas os mais importantes são:

  • migrations: é a pasta de migrações de banco de dados da aplicação.
  • models: é a pasta onde ficam as models (tabelas) da aplicação.
  • serializers: é a pasta onde ficam os serializadores da aplicação.
  • views: é a pasta onde ficam as views da aplicação.
  • admin.py: é o arquivo de configuração do Admin, uma ferramenta que permite que você gerencie os dados do seu site.

O arquivo __init__.py é um arquivo que indica que a pasta é um pacote Python. Ele vai aparecer em todas as pastas que contêm código Python. Muitas vezes, ele é um arquivo vazio.

Posteriormente, iremos modificar esses arquivos, bem como incluir alguns arquivos novos.

3.2 Model User

Um modelo (model) no Django é uma classe que representa uma tabela no banco de dados. Cada atributo (variável) dessa classe representa um campo da tabela.

Para maiores informações consulte a documentação do Django sobre models.

Você pode observar que a pasta models já contém um modelo de dados, dentro do arquivo user.py, chamado User. Esse modelo modifica o usuário padrão fornecido pelo Django e representa um usuário do sistema.

3.3 Criação da model de Categoria

  • Vamos começar criando o modelo de dados Categoria, que representa uma categoria de livro, como por exemplo: Ficção, Terror, Romance, etc.

  • Dentro da pasta models da aplicação core crie um arquivo chamado categoria.py.

  • Adicione o seguinte código no arquivo categoria.py:

from django.db import models

class Categoria(models.Model):
    descricao = models.CharField(max_length=100)

Nesse código, você:

  • Importou o pacote necessário para criar a model;

  • Criou a classe Categoria;

  • Incluiu o campo descricao, que é uma string de no máximo 100 caracteres. Esse campo é obrigatório.

  • IMPORTANTE:

    • O nome da classe deve ser sempre no singular e com a primeira letra maiúscula.
    • O nome dos campos deve ser sempre no singular e com a primeira letra minúscula.

3.4 Inclusão da model no arquivo __init__.py

  • Precisamos ainda incluir a model no arquivo __init__.py da pasta models:
from .categoria import Categoria

3.5 Efetivando a criação da tabela

Precisamos ainda efetivar a criação da tabela no banco de dados.

  • Abra um novo terminal, deixando o terminal antigo executando o servidor do projeto.

  • Crie as migrações:

pdm run migrate

Esse comando executará 3 comandos em sequência:

  • makemigrations: cria as migrações de banco de dados.
  • migrate: efetiva as migrações no banco de dados.
  • graph_models: cria/atualiza um diagrama de classes do modelo de dados.
  • Acesse o arquivo do banco de dados (db.sqlite3) e verifique se a tabela core_categoria foi criada.
  • Para ver o diagrama de classes atualizado, acesse o arquivo core.png na pasta raiz do projeto.
  • Acesse o Admin do projeto e verifique se a nova tabela aparece lá.

3.6 Inclusão no Admin

A tabela ainda não apareceu, certo? Isso acontece poque ainda não incluímos a model no Admin.

  • Vamos incluir a model no Admin. Abra o arquivo admin.py da aplicação core e adicione o seguinte código no final do arquivo:
admin.site.register(models.Categoria)

3.7 Exercício

  • Acesse novamente o Admin e inclua algumas categorias no banco de dados.

3.8 O campo id

O campo id é criado automaticamente pelo Django. Ele é o identificador único de cada registro da tabela.

3.9 Mudando a forma de exibição dos registros criados

  • Inclua algumas categorias no banco de dados.
  • Você perceberá que a descrição dos informações que você inclui está meio estranha, algo como Categoria object (1) e assim por diante.
  • Para resolver, isso, vamos fazer uma pequena modificação na model Categoria.

3.10 O método __str__

O método __str__ é um método especial que é chamado quando você tenta imprimir um objeto. Ele é utilizado no Admin e em outros locais para definir como o objeto será exibido.

  • Vamos incluir o método __str__ na model Categoria:
...
    def __str__(self):
        return self.descricao

Isso fará com que a descrição da categoria seja exibida no lugar de Categoria object (1). O método __str__ é um método especial do Python e deve sempre retornar uma string.

Volte ao Admin verifique o que mudou na apresentação dos objetos da model Categoria.

3.11 Hora de fazer um commit

  • Verifique antes se seu computador está configurado corretamente para o git com as suas credenciais. Veja como fazer isso aqui.
  • Faça um commit com a mensagem Criação da model de Categoria.

IMPORTANTE: Escrevendo uma boa mensagem de commit

  • Escreva uma mensagem de commit que descreva o que foi feito.
  • Dessa forma fica mais fácil identificar as mudanças sem precisar ver o código.
  • Não escreva mensagens como Alteração 1, Alteração 2, Alteração 3, etc.
  • Escreva mensagens como:
    • Modificação do arquivo models.py
    • Inclusão da Categoria de Veículos
    • Alteração do Marca do Veículo

4. Criando uma API REST

Nesta aula, vamos criar uma API REST para o projeto livraria. Ao final, teremos uma API completa, que permite criar, listar, atualizar e deletar categorias.

4.1 Instalação e configuração do Django Rest Framework (DRF)

  • Observe que o DRF já está instalado no projeto, conforme os arquivos pyproject.toml e requirements.txt.
  • Além disso, o DRF já está configurado no arquivo settings.py, na seção INSTALLED_APPS.

Essas configurações já foram feitas no template que utilizamos para criar o projeto. Se você estiver criando um projeto do zero, terá que fazer essas configurações manualmente.

4.2 Criação do serializer

Um serializer é um objeto que transforma um objeto do banco de dados em um objeto JSON.

  • Crie o arquivo categoria.py na pasta serializers da aplicação core, e adicione o seguinte código, para criar a CategoriaSerializer:
from rest_framework.serializers import ModelSerializer

from core.models import Categoria

class CategoriaSerializer(ModelSerializer):
    class Meta:
        model = Categoria
        fields = "__all__"

4.2.1 Explicando o código

  • model = Categoria: define o model que será serializado.
  • fields = "__all__": define que todos os campos serão serializados.

4.2.2 Inclusão do serializer no init.py

  • Inclua o serializer no arquivo __init__.py da pasta serializers:
from .categoria import CategoriaSerializer

4.3 Criação da view

Uma view é um objeto que recebe uma requisição HTTP e retorna uma resposta HTTP.

  • Crie a view CategoriaViewSet na pasta views da aplicação core, no arquivo categoria.py:
from rest_framework.viewsets import ModelViewSet

from core.models import Categoria
from core.serializers import CategoriaSerializer

class CategoriaViewSet(ModelViewSet):
    queryset = Categoria.objects.all()
    serializer_class = CategoriaSerializer

4.3.1 Explicando o código

  • queryset = Categoria.objects.all(): define o conjunto de objetos que será retornado pela view.
  • serializer_class = CategoriaSerializer: define o serializer que será utilizado para serializar os objetos.

4.3.2 Inclusão da view no init.py

  • Inclua a view no arquivo __init__.py da pasta views:
from .categoria import CategoriaViewSet

4.4 Criação das rotas (urls)

As rotas são responsáveis por mapear as URLs para as views.

  • Para criar as rotas da Categoria, edite o arquivo urls.py na pasta app e adicione as linhas indicadas:
...
from core.views import UserViewSet
from core.views import CategoriaViewSet # nova linha

router = DefaultRouter()
router.register(r"categorias", CategoriaViewSet) # nova linha
router.register(r"users", UserViewSet, basename="users")
...

IMPORTANTE: as nomes das rotas serão sempre nomes únicos, no plural e em minúsculas. Na maiorias das vezes, os colocamos em ordem alfabética.

4.5 Testando a API

Se tudo correu bem, você deve ver a interface do DRF.

Isso deve trazer todas as categorias do banco, no formato JSON.

Nesse caso, 1 é o id do registro no banco de dados.

4.6 Opções de manipulação do banco de dados

As opções disponíveis para manipulação dos dados são:

4.7 Outras ferramentas para testar a API

A interface do DRF é funcional, porém simples e limitada. Algumas opções de ferramentas para o teste da API são:

4.8 Utilizando o Swagger

O Swagger é uma ferramenta que permite a documentação e teste de APIs.

4.9 Exercícios: testando a API e as ferramentas

Instale uma ou mais das ferramentas sugeridas.

  • Experimente as seguintes tarefas:
    • Criar uma ou mais categorias;
    • Listar todas as categorias;
    • Alterar uma ou mais categorias, utilizando PUT e PATCH;
    • Listar a categoria alterada;
    • Remover uma categoria;
    • Incluir outra categoria;
    • Listar todas as categorias.

4.10 Fazendo um commit

  • Faça um commit com a mensagem Criação da API para Categoria.

5. Aplicação frontend Vuejs

Agora que temos uma API REST completa, vamos criar uma aplicação frontend em Vuejs para consumir essa API da Categoria.

    npm install
    npm run dev

Se tudo correu bem, execute a aplicação:

Se os dados não aparecerem, entre na opção Inspecionar do seu navegador (F12)

Para maiores detalhes sobre a instalação do npm, acesse o tutorial de Instalação da versão LTS do NodeJS do Prof. Eduardo da Silva.

6. Incluindo a Editora no projeto Livraria

Vamos continuar a criação da API REST para o projeto livraria, criando a model Editora e a API para ela.

6.1 Criação da API para a classe Editora

  • Os passos para a criação da API para a classe Editora são os mesmos que fizemos para a classe Categoria:
    1. Criar a model Editora na pasta models.
    2. Incluir a model no __init__.py da pasta models.
    3. Fazer a migração e efetivar a migração.
    4. Incluir a model no Admin.
    5. Criar o serializador na pasta serializers.
    6. Incluir o serializador no __init__.py da pasta serializers.
    7. Criar a viewset na pasta views.
    8. Incluir a viewset no __init__.py da pasta views.
    9. Incluir a nova rota em urls.py.

6.2 Criação e modificação dos arquivos

  • Os arquivos ficarão assim:

models/editora.py

from django.db import models

class Editora(models.Model):
    nome = models.CharField(max_length=100)
    site = models.URLField(max_length=200, blank=True, null=True)

    def __str__(self):
        return self.nome

models/init.py

...
from .editora import Editora

admin.py

...
admin.site.register(models.Editora)

serializers/editora.py

from rest_framework.serializers import ModelSerializer

from core.models import Categoria, Editora
...
class EditoraSerializer(ModelSerializer):
    class Meta:
        model = Editora
        fields = "__all__"

serializers/__init__.py

...
from .editora import EditoraSerializer

views/editora.py

from rest_framework.viewsets import ModelViewSet

from core.models import Categoria, Editora
from core.serializers import CategoriaSerializer, EditoraSerializer

...
class EditoraViewSet(ModelViewSet):
    queryset = Editora.objects.all()
    serializer_class = EditoraSerializer

views/__init__.py

...
from .editora import EditoraViewSet

urls.py

...
from core.views import UserViewSet, CategoriaViewSet, EditoraViewSet
...
router.register(r"categorias", CategoriaViewSet)
router.register(r"editoras", EditoraViewSet)
...

6.3 Fazendo a migração e efetivando a migração

  • Faça a migração e efetive a migração:
pdm run migrate
  • Verifique se a tabela core_editora foi criada no banco de dados.

6.4 Exercícios: testando da API da Editora

  • Teste todas as operações da Editora.
  • Verifique se é possível incluir novas editoras sem incluir todos os campos.
  • Tente utilizar o PUT e o PATCH sem informar todos os campos.

6.5 Fazendo um commit

  • Faça um commit com a mensagem Criação da API para Editora.

7. Criação da API para Autor

Vamos continuar a criação da API REST para o projeto livraria, criando a model Autor e a API para ela. Os passos são os mesmos que fizemos para as classes Categoria e Editora.

  • Crie a API para a classe Autor.

O autor terá os seguintes atributos:

  • nome: string de no máximo 100 caracteres.

  • email: campo do tipo email de no máximo 100 caracteres, que pode ser nulo.

  • Teste a API.

  • Faça o commit, com a mensagem Criação da API para Autor.

Exercícios:

  • Crie no Vuejs a tela para listar, incluir, alterar e excluir autores.

8. Criação da API para Livro

Vamos continuar a criação da API REST para o projeto livraria, criando a model Livro e a API para ela. Os passos iniciais são os mesmos que fizemos para as classes Categoria, Editora e Autor.

8.1 Criação dos arquivos necessários

Utilizando um comando no terminal, é possível criar todos os arquivos necessários para a criação da API para a classe Livro.

touch core/models/livro.py core/serializers/livro.py core/views/livro.py

É possivel também abrir todos os arquivos de uma vez, utilizando o comando:

code core/models/livro.py core/models/__init__.py core/serializers/livro.py core/serializers/__init__.py core/views/livro.py core/views/__init__.py app/urls.py core/admin.py

Se você preferir, pode criar os arquivos utilizando o VS Code, como já fizemos anteriormente.

Você deve estar se perguntando: posso criar um comando para fazer isso automaticamente? Sim, pergunte-me como. :)

8.2 Criando o modelo de dados Livro

  • Vamos criar o modelo de dados Livro, no arquivo models.py:
class Livro(models.Model):
    titulo = models.CharField(max_length=255)
    isbn = models.CharField(max_length=32, null=True, blank=True)
    quantidade = models.IntegerField(default=0,  null=True, blank=True)
    preco = models.DecimalField(max_digits=7, decimal_places=2, default=0, null=True, blank=True)

    def __str__(self):
        return f"{self.titulo} ({self.quantidade})"

Inclua o modelo no arquivo __init__.py da pasta models:

from .livro import Livro
  • Faça as migrações e veja o resultado no banco de dados.

Seu projeto deve ficar assim:

Projeto com a model Livro

8.3 Criando a API para a classe Livro

Da mesma forma que fizemos para as classes Categoria, Editora e Autor, vamos criar a API para a classe Livro.

Siga os passos conforme já definimos.

  • Após a criação da API, teste todas as operações de CRUD para a classe Livro.
  • Faça um commit com a mensagem Criação da API para Livro.

9. Incluindo chaves estrangeiras no modelo Livro

Nosso livro terá uma categoria e uma editora. Para isso, vamos incluir campos que serão chaves estrageiras, referenciando os modelos Categoria e Editora. Esse relacionamento é do tipo n para 1. Posteriormente, vamos incluir um relacionamento n para n entre Livro e Autor.

9.1 Campo categoria no Livro

  • Inclua a linha a seguir no modelo Livro, logo após o atributo preco:
from .categoria import Categoria
...
    categoria = models.ForeignKey(
        Categoria, on_delete=models.PROTECT, related_name="livros", null=True, blank=True
    )
...
  • Vamos entender cada parte:
    • models.ForeignKey: define o campo como sendo uma chave estrangeira.
    • Categoria: o model que será associado a esse campo.
    • on_delete=models.PROTECT: impede de apagar uma categoria que possua livros associados.
    • related_name="livros": cria um atributo livros na classe Categoria, permitindo acessar todos os livros de uma categoria.
    • null=True, blank=True: permite que o campo seja nulo e em branco. Isso é útil para evitar problemas na migração.

9.2 Campo editora no Livro

  • De forma semelhante, vamos associar o livro a uma editora, incluindo logo em seguida à categoria, a seguinte linha:
from .editora import Editora
...
editora = models.ForeignKey(Editora, on_delete=models.PROTECT, related_name="livros", null=True, blank=True)

Observe que os atributos null=True e blank=True não foram utilizados, pois os campos categoria e editora são obrigatórios.

  • Faça a migração dos dados.

Observe que os campos categoria_id e editora_id foram criados no banco de dados, na tabela core_livro. Eles são os campos que fazem referência às tabelas core_categoria e core_editora.

A model Livro ficará assim:

Projeto com a model Livro

9.3 Testando o atributo on_delete

Feito isso, verifique se tudo funcionou.

No Admin:

  • Cadastre algumas categorias, editoras, autores e livros.
  • Note como os livros acessam as categorias e editoras já cadastradas.
  • Tente apagar uma editora ou categoria com livros associados.
    • O que aconteceu?
    • Por que isso aconteceu?
  • Tente apagar uma editora ou categoria sem livros associados.
    • O que aconteceu?
    • Por que isso aconteceu?

9.4 Testando o atributo related_name no Django Shell

No Django Shell (que iremos estudar em mais detalhes em uma aula mais adiante), é possível testar o acesso a todos os livros de uma categoria usando algo parecido com isso:

  • Abra o Django shell:
pdm run shellp
  • Acesse os livros da categoria com id 1:
>>> Categoria.objects.get(id=1).livros.all()

O comando pdm run shellp é utilizado para abrir o Django Shell Plus com o ambiente virtual do projeto.

  • Faça um commit com a mensagem Adiciona relacionamento de Livro com Categoria e Editora.

10. Incluindo relacionamento n para n no modelo do Livro

10.1 Model com ManyToManyField - Livros com vários autores

Um livro pode ter vários autores, por isso criaremos agora um relacionamento n para n entre Livro e Autor. Para isso utilizaremos um campo do tipo ManyToManyField.

  • Inclua o campo autores no modelo Livro:
from .autor import Autor
...
autores = models.ManyToManyField(Autor, related_name="livros")
...
  • Execute as migrações.

Observe que o campo autores não foi criado na tabela core_livro. Ao invés disso, uma tabela associativa foi criada, com o nome core_livro_autores, contendo os campos livro_id e autor_id. É assim que é feito um relacionamento n para n no Django.

Nesse caso, não é necessário usar o atributo null=True e blank=True, pois um campo do tipo ManyToManyField cria uma tabela associativa.

  • A model Livro ficará assim:

Projeto com a model Livro

Note que na ligação entre Livro e Autor existem uma "bolinha" em cada lado, indicando que o relacionamento é n para n.

Observe as alterações no banco de dados, no Admin e na API.

10.2 Exercícios

  • Teste a API REST de livros e autores.

11. Modificação da API para Livro

Observou que no Livro, aparecem apenas os campos id da categoria, da editora e dos autores e não as descrições?

  • Vamos resolver isso.

Criação de múltiplos serializadores

Podemos criar múltiplos serializadores para um mesmo modelo, de forma a apresentar as informações de diferentes formas, dependendo da operação.

Apresentação das informações detalhadas no Livro

Uma forma de mostrar essas informações é essa, em serializers.py:

class LivroSerializer(ModelSerializer):
    class Meta:
        model = Livro
        fields = "__all__"
        depth = 1

Teste e você verá que isso resolve a listagem (GET), mas gera problema no criação e alteração (POST, PUT e PATCH).

  • Para resolver isso, vamos criar dois (ou mais) serializadores, sendo um para a listagem e outro para a recuperação de um único livro:
class LivroSerializer(ModelSerializer):
    class Meta:
        model = Livro
        fields = "__all__"


class LivroDetailSerializer(ModelSerializer):
    class Meta:
        model = Livro
        fields = "__all__"
        depth = 1
  • Inclua o serializador LivroDetailSerializer no arquivo serializers/__init__.py:
from .livro import LivroDetailSerializer, LivroSerializer

Observe que no LivroDetailSerializer foi incluído o atributo depth = 1, que permite a apresentação dos dados relacionados.

  • Na viewset, escolhemos o serializador conforme a operação:
...
from core.serializers import LivroDetailSerializer, LivroSerializer


class LivroViewSet(ModelViewSet):
    queryset = Livro.objects.all()
    serializer_class = LivroSerializer

    def get_serializer_class(self):
        if self.action in ["list", "retrieve"]:
            return LivroDetailSerializer
        return LivroSerializer

Nesse caso, o serializador LivroDetailSerializer é utilizado para a listagem e recuperação de um único livro, enquanto o LivroSerializer é utilizado para as demais operações, ou seja, criação e alteração.

  • Teste a API.
  • Faça um commit com a mensagem Dois serializadores para Livro.

Criação de um serializador para a listagem de livros

Podemos criar um serializador para a listagem de livros, que mostre apenas o id, o título e o preço. Isso pode ser útil, pois traz menos informações, o que pode tornar a listagem mais rápida.

  • Inclua um serializador para a listagem de livros, que mostre apenas o id, o título e o preço:
class LivroListSerializer(ModelSerializer):
    class Meta:
        model = Livro
        fields = ["id", "titulo", "preco"]
  • Altere a viewset para utilizar esse serializador na listagem:
    def get_serializer_class(self):
        if self.action == "list":
            return LivroListSerializer
        elif self.action == "retrieve":
            return LivroDetailSerializer
        return LivroSerializer

Observe que o serializador LivroListSerializer é utilizado apenas na listagem, enquanto o LivroDetailSerializer é utilizado na recuperação de um único livro e o LivroSerializer é utilizado nas demais operações.

  • Teste a API. Observe que a listagem de vários livros está diferente da recuperação de um único livro.
  • Faça um commit com a mensagem Múltiplos serializadores para Livro.

12. Upload e associação de imagens

Vamos instalar uma aplicação para gerenciar o upload de imagens e sua associação ao nosso modelos. Com isso poderemos associar imagens aos livros, ao perfil do usuário, etc.

Essa aplicação não será instalada através do comando pdm add <pacote>, pois é uma aplicação que não está disponível no PyPI. Ela será instalada manualmente, baixando e descopactando um arquivo compactado.

Baixando o pacote

Baixe e descompacte o arquivo com a app pronta para ser utilizada.

  • No Linux, execute o seguinte comando no terminal:
wget https://github.com/marrcandre/django-drf-tutorial/raw/main/apps/uploader.zip -O uploader.zip && unzip uploader.zip && rm uploader.zip
  • No Windows, execute os seguintes comandos no PowerShell:
Invoke-WebRequest -Uri https://github.com/marrcandre/django-drf-tutorial/raw/main/apps/uploader.zip -OutFile uploader.zip
Expand-Archive -Path uploader.zip -DestinationPath .
Remove-Item -Force uploader.zip

O projeto ficará com uma estrutura parecida com essa:

App Uploader

Instalando as dependências

  • Instale os pacotes Pillow e python-magic:
pdm add Pillow
pdm add "python-magic; sys_platform=='linux'"
pdm add "python-magic-bin; sys_platform=='win32, darwin'"

O pacote python-magic é utilizado para identificar o tipo de arquivo, enquanto o Pillow é utilizado para manipulação de imagens.

O pacote python-magic-bin é utilizado no Windows e MacOS, enquanto o python-magic é utilizado no Linux.

Registro da app

  • Adicione o pacote uploader na lista de INSTALLED_APPS, no settings.py:
INSTALLED_APPS = [
    ...
    "uploader", # nova linha
    "core",
    ...
]

IMPORTANTE: Não esqueça da vírgula no final da linha.

Configuração no settings.py

  • Ainda no settings.py faça as seguintes configurações, logo após a configuração do STATIC_URL:
# App Uploader settings
MEDIA_ENDPOINT = "/media/"
MEDIA_ROOT = os.path.join(BASE_DIR, "media/")
FILE_UPLOAD_PERMISSIONS = 0o640

Configuração no urls.py

  • Inclua o seguinte conteúdo no arquivo urls.py:
from django.conf import settings
from django.conf.urls.static import static
...
from uploader.router import router as uploader_router
...
path("api/media/", include(uploader_router.urls)),
...
urlpatterns += static(settings.MEDIA_ENDPOINT, document_root=settings.MEDIA_ROOT)
...

Migração do banco de dados

  • Faça a migração do banco de dados:
pdm run migrate
  • Se o seu projeto já foi publicado, não esqueça de fazer a migração também no servidor.

Uso em modelos

Agora que a aplicação uploader está configurada, vamos utilizá-la para associar imagens aos livros.

  • Edite o arquivo models/livro.py da aplicação livraria e inclua o seguinte conteúdo:
...
from uploader.models import Image


class Livro(models.Model):
...
    capa = models.ForeignKey(
        Image,
        related_name="+",
        on_delete=models.CASCADE,
        null=True,
        blank=True,
        default=None,
    )

O campo capa é uma chave estrangeira para a tabela uploader_image.

  • Faça novamente a migração do banco de dados:
pdm run migrate

O modelo Livro ficará assim:

Projeto com a model Livro com capa

Observe que o campo capa_id foi criado na tabela core_livro, fazendo referência à tabela uploader_image.

Uso no serializer

  • Edite o arquivo serializers/livro.py da aplicação core e inclua o seguinte conteúdo:
...
from rest_framework.serializers import ModelSerializer, SlugRelatedField

from uploader.models import Image
from uploader.serializers import ImageSerializer
...
class LivroSerializer(ModelSerializer):
    capa_attachment_key = SlugRelatedField(
        source="capa",
        queryset=Image.objects.all(),
        slug_field="attachment_key",
        required=False,
        write_only=True,
    )
    capa = ImageSerializer(required=False, read_only=True)

...
class LivroDetailSerializer(ModelSerializer):
...
    capa = ImageSerializer(required=False)

Alteramos dois serializadores: um para a gravação e outro para a recuperação de um único livro.

Teste de upload e associação com o livro

  • Acesse a API de media:

    http://0.0.0.0:19003/api/media/images/

  • Faça o upload de uma imagem.

  • Observe que o campo capa_attachment_key foi preenchido com o valor attachment_key da imagem.

  • Guarde o valor do campo capa_attachment_key.

  • Crie um novo livro, preenchendo o campo capa_attachment_key com o valor guardado anteriormente.

  • Acesse o endpoint http://0.0.0.0:19003/api/media/images/ e observe que a imagem foi associada ao livro.

13. Dump e Load de dados

O dump dos dados permite que você salve os dados do banco de dados em um arquivo. O load dos dados permite que você carregue os dados de um arquivo para o banco de dados. Isso é útil para fazer cópias de segurança, para transferir dados entre bancos de dados, para carregar dados iniciais, etc.

Carga inicial de dados

  • Acesse o seguinte link:

    • Link: http://191.52.55.156:19005/admin
    • Usuário: a@a.com
    • Senha: teste.123
  • Cadastre pelos menos 10 livros, com autor e editora

  • Verifique se o livro, autor ou editora já estão cadastrados, para evitar duplicidade.

  • NÃO USE CAIXA ALTA!!!

  • Use o formato de nomes de livros, como no exemplo: O Senhor dos Anéis - A Sociedade do Anel

Cópia de segurança dos dados

  • Execute o comando dumpdata:
pdm run dumpdata > core_bkp.json
  • Observe que o arquivo core_bkp.json foi criado:
code core_bkp.json

Arquivo exemplo

  • Baixe o arquivo core.json:

No Linux:

wget https://github.com/marrcandre/django-drf-tutorial/raw/main/scripts/core.json

No Windows:

Invoke-WebRequest -Uri "https://github.com/marrcandre/django-drf-tutorial/raw/main/scripts/core.json" -OutFile core.json

Carga dos dados

  • Execute o comando loaddata:
pdm run loaddata
  • Utilizando o Django Shell Plus, observe que os dados foram carregados:
pdm run shellp

E dentro dele, execute:

>>> Livro.objects.all()

Você também pode acessar o Django Admin ou o Swagger e verificar que os dados foram carregados.

14. Uso do Django Shell e do Django Shell Plus

O Django Shell é uma ferramenta para interagir com o banco de dados. O Django Shell Plus é uma extensão do Django Shell que inclui alguns recursos adicionais, como a inclusão automática dos modelos.

  • Acesse o shell:
pdm run shellp
  • Crie um objeto:
>>> categoria = Categoria.objects.create(descricao="Desenvolvimento Web")
  • Observe que o objeto foi criado:
>>> categoria
<Categoria: Desenvolvimento Web>
  • Liste os objetos:
>>> Categoria.objects.all()
<QuerySet [<Categoria: Desenvolvimento Web>]>
  • Obtenha o objeto:
>>> categoria = Categoria.objects.get(descricao="Desenvolvimento Web")
  • Observe que o objeto foi obtido:
>>> categoria
<Categoria: Desenvolvimento Web>
  • Atualize o objeto:
>>> categoria.descricao = "Desenvolvimento Web com Django"
>>> categoria.save()
  • Observe que o objeto foi atualizado:
>>> categoria
<Categoria: Desenvolvimento Web com Django>
  • Remova o objeto:
>>> categoria.delete()
(1, {'core.Categoria': 1})
  • Observe que o objeto foi removido:
>>> Categoria.objects.all()
<QuerySet []>

Useando o atributo related_name

  • Acesso a todos os livros de um autor:
Autor.objects.get(id=1).livros.all()
  • Acesso a todos os livros de uma categoria:
Categoria.objects.get(id=1).livros.all()
  • Acesso a todos os livros de uma editora:
Editora.objects.get(id=1).livros.all()
  • Encerre o shell:
>>> exit()

15. Customização do Admin

O Admin é uma ferramenta para gerenciar os dados do banco de dados. Ele pode ser customizado para melhorar a experiência do usuário.

  • Edite o arquivo core/admin.py:

Importação das models

Vamos importar as models de forma explícita:

from core.models import Autor, Categoria, Editora, Livro, User

Registro das models através do decorator @admin.register

Vamos registrar as models através do decorator @admin.register:

@admin.register(User)
class UserAdmin(UserAdmin):
...

Customização do Admin

...
@admin.register(Autor)
class AutorAdmin(admin.ModelAdmin):
    list_display = ('nome', 'email')
    search_fields = ('nome', 'email')
    list_filter = ('nome',)
    ordering = ('nome', 'email')

@admin.register(Categoria)
class CategoriaAdmin(admin.ModelAdmin):
    list_display = ('descricao',)
    search_fields = ('descricao',)
    list_filter = ('descricao',)
    ordering = ('descricao',)

@admin.register(Editora)
class EditoraAdmin(admin.ModelAdmin):
    list_display = ('nome',)
    search_fields = ('nome',)
    list_filter = ('nome',)
    ordering = ('nome',)

@admin.register(Livro)
class LivroAdmin(admin.ModelAdmin):
    list_display = ('titulo', 'editora', 'categoria')
    search_fields = ('titulo', 'editora__nome', 'categoria__descricao')
    list_filter = ('editora', 'categoria')
    ordering = ('titulo', 'editora', 'categoria')
    list_per_page = 25
  • As linhas com admin.site.register() devem ser removidas.

O atributo list_display é uma tupla que define os campos que serão exibidos na listagem.

O atributo search_fields é uma tupla que define os campos que serão utilizados na busca.

O atributo list_filter é uma tupla que define os campos que serão utilizados para filtrar os registros.

O atributo ordering é uma tupla que define a ordem de exibição default dos registros.

16. Autenticação e autorização

Introdução

Vamos trabalhar agora os conceitos de segurança relacionados a autenticação (login) e autorização (permissão). Utilizaremos aquilo que o Django já oferece, em termos de usuários e grupos.

Uma estratégia muito utilizada para a definição de permissões de acesso é:

  • Criar grupos para perfis de usuários específicos.
  • Definir as permissões que esse grupo de usuários terá.
  • Criar um usuário para cada pessoa que utilizará a aplicação.
  • Incluir os usuários nos grupos, dando assim as permissões.
  • No caso de mudanças nas permissões, elas são sempre feitas nos grupos, refletindo nos usuários.
  • Se um usuário possui mais do que um perfil de permissões, ele deve ser incluído em vários grupos.
  • Quando um usuário sai de uma função ou deve perder seus privilégios, ele é removido do grupo específico.

Resumindo: toda a estratégia de permissões parte da criação de grupos e inclusão ou remoção de usuários desses grupos.

Observe no Admin, para cada usuário em Usuários (Users), as opções de Permissões do usuário.

Relação entre nomes das ações

Podemos perceber uma relação as ações que compôem o CRUD, os termos utilizados no Admin e os verbos HTTP:

Aqui está a tabela com a terceira coluna movida para o lugar da quarta:

Ação CRUD Admin HTTP
Criar Create add POST
Ler Read view GET
Atualizar Update change PUT (PATCH)
Deletar Delete delete DELETE

Exercício:

No Admin, crie os seguintes usuários e grupos e dê as permissões necessárias:

a. Criando grupos e dando permissões

Vamos começar criando 2 grupos e dando a eles permissões distintas:

  • Crie um grupo chamado Administradores, com as seguintes as permissões:
    • Adicionar, editar, visualizar e remover: autor, categoria, editora e livro.
  • Crie um grupo chamado Compradores, com as seguintes permissões:
    • Visualizar: autor, categoria e editora.
    • Adicionar, editar e visualizar: livro.

b. Criando usuários e adicionando aos grupos

  • Crie um usuário admin1@a.com e o inclua no grupo Administradores.
  • Crie um usuário comprador1@a.com e o inclua no grupo Compradores.

17. Usando as permissões do DRF

Autenticação e permissão

A autenticação ou identificação por si só geralmente não é suficiente para obter acesso à informação ou código. Para isso, a entidade que solicita o acesso deve ter autorização. (Permissões no DRF)

Autenticação significa que um usuário foi identificado em um sistema, portanto ele é conhecido. Isso se dá, normamente por um sistema de login.

Permissão (autorização) se dá por um esquema de conceder privilégios, seja a usuários ou grupos.

Por padrão, qualquer usuário, mesmo sem autenticação, tem acesso irrestrito e permissão de fazer qualquer coisa em uma aplicação.

As permissões podem ser definidas:

  1. a nível de objeto (nas views ou viewsets, por exemplo);
  2. de forma global, no arquivo settings.py;
  3. com o uso de classes de permissão do Django REST Framework.

Vamos analisar cada uma dessas formas.

a. Exemplo de uso de permisssão na viewset

Vamos ver um exemplo de uso de permissão em uma viewset. No exemplo, vamos permitir acesso apenas a usuários autenticados na model Categoria.

Como ilustração, modifique o arquivo views/categoria.py, da seguinte forma.

  • Importe a seguinte função:
from rest_framework.permissions import IsAuthenticated
  • Inclua também a seguinte linha na CategoriaViewSet:
permission_classes = [IsAuthenticated]

Para testar:

  • Encerre a sessão do Admin.
  • Tente acessar as categorias pelo DRF.
  • Você deve receber o seguinte erro: "As credenciais de autenticação não foram fornecidas."
  • Agora entre novamente pelo Admin.
  • Tente acessar as categorias pelo DRF.
  • Você deve conseguir acessar novamente.

Resumindo, utilizamos a classe IsAuthenticated para permitir acesso apenas a usuários autenticados.

b. Exemplo de uso de permisssão no settings.py

Outra forma de gerenciamento de permissões é feita no arquivo settings.py.

IMPORTANTE: Para utilizá-la, comente as últimas alterações feitas no arquivo views.py.

Uma forma de conseguir o mesmo resultado de forma padrão para todo o projeto, isto é, permitir acesso aos endpoints apenas para usuários autenticados, é configurar desse modo o arquivo settings.py:

REST_FRAMEWORK = {
    "DEFAULT_PERMISSION_CLASSES": [
        "rest_framework.permissions.IsAuthenticated",
    ]
}

Para testar:

  • Inclua o código acima e teste novamente o acesso aos endpoints do DRF (categorias, editoras, etc.) com e sem uma sessão autenticada.

Resumindo, utilizamos a classe IsAuthenticated no settings.py para permitir acesso apenas a usuários autenticados.

c. Permissões com o DjangoModelPermissionsOrAnonReadOnly

Apesar de ser possível definir a autorização das formas que vimos anteriormente, adotaremos uma outra forma. Essa forma que iremos adotar para o gerenciamento de permissões será com o uso do DjangoModelPermissions.

Esta classe de permissão está ligada às permissões do modelo django.contrib.auth padrão do Django. Essa permissão deve ser aplicada apenas a visualizações que tenham uma propriedade .queryset ou método get_queryset() (exatamente o que temos).

A autorização só será concedida se o usuário estiver autenticado e tiver as permissões de modelo relevantes atribuídas, da seguinte forma:

  • As solicitações POST exigem que o usuário tenha a permissão de adição (add) no modelo.
  • As solicitações PUT e PATCH exigem que o usuário tenha a permissão de alteração (change) no modelo.
  • As solicitações DELETE exigem que o usuário tenha a permissão de exclusão (remove) no modelo. - Se o usuário não estiver autenticado, ele terá acesso somente leitura à API.

Para isso, teremos que alterar a classe de autenticação, substituindo o que colocamos anteriormente:

REST_FRAMEWORK = {
    ...
    "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly", ),  # autorização de acesso
    ...
}

Resumindo, utilizaremos a estrutura de usuários, grupos e permissões que o próprio Django já nos fornece. Para isso, utilizaremos o DjangoModelPermissionsOrAnonReadOnly para gerenciar as permissões.

Para utilizar essa estrutura de permissões corretamente, precisaremos de um sistema de autenticação (login) no nosso projeto, de forma a enviar essas informações via a URL. Para isso, utilizaremos o Passage.

18. Autenticação com Passage

Configuração do Passage

Para configurar o Passage, acesse essa página (no tutorial do Prof. Eduardo da Silva) e siga as instruções.

Configuração do Passage no backend Django

  • Descomente (ou inclua) as seguintes linhas no arquivo settings.py:
REST_FRAMEWORK = {
    ...
    "DEFAULT_AUTHENTICATION_CLASSES": ("core.authentication.TokenAuthentication",), # Autenticação no passage.id
    "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated"), # Permissão total para usuários autenticados
    ...
}

No arquivo .env, preencha as seguintes variáveis com os valores da sua aplicação:

PASSAGE_APP_ID=sua_app_id
PASSAGE_APP_SECRET=sua_app_secret

Configuração do Passage no frontend Vue

  • No arquivo src/views/Login.vue, inclua o seguinte código:
    <passage-auth app-id="seu_app_id"></passage-auth>

Substitua o valor de app-id pelo valor do seu app_id, no Passage.

19. Inclusão da foto de perfil no usuário

Vamos aproveitar a aplicação uploader para incluir a foto de perfil no usuário.

Criação do campo de foto de perfil

  • No arquivo models/user.py, inclua o campo foto:
...
from uploader.models import Image
...
class User(AbstractUser):
    foto = models.ForeignKey(
        Image,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        default=None,
    )

O campo foto é uma chave estrangeira para a tabela uploader_image.

A foto será opcional, por isso utilizamos null=True e blank=True.

O campo foto será null por padrão, por isso utilizamos default=None.

Se a foto for deletada, o campo foto será null, por isso utilizamos on_delete=models.SET_NULL.

  • Faça as migrações:

Seu projeto deve ficar assim:

Projeto com a model Livro

Observe a ligação entre a model Usuario e a model Image, através da chave estrangeira foto.

Inclusão da foto no Admin

  • No arquivo admin.py, inclua o campo foto:
...
class UserAdmin(UserAdmin):
    ...
        (_("Personal Info"), {"fields": ("name","foto")}), # inclua a foto aqui

    ...
  • Teste a inclusão da foto de um usuário pelo Admin.

Inclusão da foto no Serializer

  • Modifique o serializador para o usuário, em serializers/user.py:
from rest_framework.serializers import ModelSerializer, SlugRelatedField

from core.models import Usuario
from uploader.models import Image
from uploader.serializers import ImageSerializer


class UsuarioSerializer(ModelSerializer):
    foto_attachment_key = SlugRelatedField(
        source="foto",
        queryset=Image.objects.all(),
        slug_field="attachment_key",
        required=False,
        write_only=True,
    )
    foto = ImageSerializer(required=False, read_only=True)

    class Meta:
        model = Usuario
        fields = "__all__"

O atributo write_only=True indica que o campo foto_attachment_key é apenas para escrita. Isso significa que ele não será exibido na resposta da API.

O atributo read_only=True indica que o campo foto é apenas para leitura. Isso significa que ele não será aceito na requisição da API.

Testando

  • Inclua uma foto de perfil em um usuário, através da API.

Finalizando

  • Faça as alterações no sistema publicado.
  • Faça um commit com a mensagem Inclusão da foto de perfil no usuário.

20. Criação da entidade Compra integrada ao usuário do projeto

Nessa aula, vamos criar um entidade de compras integrada à entidade do usuário do projeto.

Criando o model de compras

  • Crie um novo arquivo compra.py dentro da pasta models do app core, digitando no terminal:
touch core/models/compra.py
  • Inclua o seguinte conteúdo no arquivo compra.py recém criado:
from django.db import models

from .user import User

class Compra(models.Model):
    class StatusCompra(models.IntegerChoices):
        CARRINHO = 1, "Carrinho"
        REALIZADO = 2, "Realizado"
        PAGO = 3, "Pago"
        ENTREGUE = 4, "Entregue"

    usuario = models.ForeignKey(Usuario, on_delete=models.PROTECT, related_name="compras")
    status = models.IntegerField(choices=StatusCompra.choices,  default=StatusCompra.CARRINHO)

Note que estamos utilizando a model User como ForeignKey para o model Compra.

StatusCompra é do tipo IntegerChoices, que é uma forma de criar um campo choices com valores inteiros.

status é um campo IntegerField que utiliza o choices StatusCompra.choices e tem o valor padrão StatusCompra.CARRINHO.

  • Inclua a nova model no arquivo core/models/__init__.py:
from .compra import Compra

Adicionando a model Compra ao Admin

  • Adicione o model Compra ao admin.py do app core:
...
from core.models import Compra

admin.site.register(Compra)

Executando as migrações

  • Execute as migrações.

O seu projeto deve ficar assim:

Projeto com a model Compra

Testando a model Compra

  • Teste o model Compra no admin do Django.

Finalizando

  • Faça um commit com a mensagem Criação da entidade Compra integrada ao usuário do projeto.

21. Criando os itens da compra

No caso dos itens da compra, não vamos utilizar um campo livro do tipo ManyToManyField no model Compra, pois queremos ter a possibilidade de adicionar mais informações ao item da compra, como a quantidade, por exemplo. Desta forna, vamos criar "manualmente" a tabela associativa, que será chamada de ItensCompra.

  • Vamos adicionar um nova entidade ItensCompra ao arquivo core/models/compra.py:
...
from .livro import Livro
...
class ItensCompra(models.Model):
    compra = models.ForeignKey(Compra, on_delete=models.CASCADE, related_name="itens")
    livro = models.ForeignKey(Livro, on_delete=models.PROTECT, related_name="+")
    quantidade = models.IntegerField(default=1)

No atributo compra, utilizamos models.CASCADE, pois queremos que, ao deletar uma compra, todos os itens da compra sejam deletados também.

No atributo livro, utilizamos models.PROTECT, pois queremos impedir que um livro seja deletado se ele estiver associado a um item de compra.

Ainda no livro, utilizamos related_name="+", pois não queremos que o ItensCompra tenha um atributo livro.

  • Inclua o novo model no arquivo __init__.py dos models:
from .compra import Compra, ItensCompra
  • Execute as migrações (você já sabe como fazer, certo?)

O seu projeto deve ficar assim:

Projeto com a model Compra

  • Verifique que a tabela core_itenscompra foi criada no banco de dados.
  • Inclua o model ItensCompra no Admin do Django.
  • Faça um commit com a mensagem Criação dos itens da compra.

22. Uso de TabularInline no Admin para Itens da Compra

Da forma que configuramos o Admin para a model ItensCompra, não é possível adicionar itens da compra diretamente na tela de edição da compra. Isso é pouco natural, pois há uma relação direta entre a compra e seus itens.

Vamos mostrar os itens da compra no admin do Django, utilizando o TabularInline. Desta forma, podemos adicionar os itens da compra diretamente na tela de edição da compra.

  • No arquivo admin.py do app core, modifique o código das models Compra e ItensCompra da seguinte forma:
class ItensCompraInline(admin.TabularInline):
    model = ItensCompra
    extra = 1 # Quantidade de itens adicionais


@admin.register(Compra)
class CompraAdmin(admin.ModelAdmin):
    list_display = ("usuario", "status")
    search_fields = ("usuario", "status")
    list_filter = ("usuario", "status")
    ordering = ("usuario", "status")
    list_per_page = 25
    inlines = [ItensCompraInline]

Desta forma, quando você editar uma compra no admin do Django, você verá os itens da compra logo abaixo do formulário de edição da compra.

  • Teste no admin do Django.
  • Faça um commit com a mensagem Uso de TabularInline no Admin para Itens da Compra.

23. Endpoint para a listagem básica de compras

Vamos começar a criar os endpoints para a entidade Compra, começando pela listagem básica de compras. Posteriormente, vamos incluir os itens da compra e criar os endpoints para adicionar, editar e excluir compras.

Criação do serializer de Compra

  • Crie um novo arquivo compra.py dentro da pasta serializers do app core:
touch core/serializers/compra.py
  • Inclua o seguinte conteúdo no arquivo compra.py recém criado:
from rest_framework.serializers import ModelSerializer

from core.models import Compra

class CompraSerializer(ModelSerializer):
    class Meta:
        model = Compra
        fields = "__all__"
  • Inclua o novo CompraSerializer no arquivo __init__.py dos serializers:
from .compra import CompraSerializer

Criação da Viewset de Compra

  • Crie um novo arquivo compra.py dentro da pasta views do app core:
touch core/views/compra.py
  • Inclua o seguinte conteúdo no arquivo compra.py recém criado:
from rest_framework.viewsets import ModelViewSet

from core.models import Compra
from core.serializers import CompraSerializer


class CompraViewSet(ModelViewSet):
    queryset = Compra.objects.all()
    serializer_class = CompraSerializer
  • Inclua o novo CompraViewSet no arquivo __init__.py das views:
from .compra import CompraViewSet

URL para listagem de compras

  • Inclua o endpoint no arquivo urls.py do app core:
...
from core.views import AutorViewSet, CategoriaViewSet, CompraViewSet, EditoraViewSet, LivroViewSet
...
router.register(r"compras", CompraViewSet)
...
  • Teste o endpoint no navegador.
  • Faça o commit com a mensagem Endpoint para a listagem básica de compras.

Inclusão do email do usuário na listagem da compra

Nesse momento, a listagem de compras mostra apenas o id do usuário. Vamos substituir o id pelo email do usuário.

  • No serializer de Compra, inclua o seguinte código:
...
from rest_framework.serializers import CharField, ModelSerializer
...
class CompraSerializer(ModelSerializer):
    usuario = CharField(source="user.email", read_only=True) # inclua essa linha
...

O parâmetro source indica qual campo do model Compra será utilizado para preencher o campo usuario do serializer.

O parâmetro read_only indica que o campo usuario não será utilizado para atualizar o model Compra.

  • Teste o endpoint no navegador.
  • Faça o commit com a mensagem Inclusão do email do usuário na listagem da compra.

Inclusão do status da compra na listagem da compra

De forma semelhante ao email do usuário, vamos incluir o status da compra na listagem da compra.

  • No serializer de Compra, inclua o seguinte código:
...
class CompraSerializer(ModelSerializer):
    status = CharField(source="get_status_display", read_only=True) # inclua essa linha
...

O parâmetro source indica qual método do model Compra será utilizado para preencher o campo status do serializer. Sempre que utilizamos um campo do tipo IntegerChoices, podemos utilizar o método get_<nome_do_campo>_display para obter a descrição do campo.

O parâmetro read_only indica que o campo status não será utilizado para atualizar o model Compra.

  • Teste o endpoint no navegador.
  • Faça o commit com a mensagem Inclusão do status da compra na listagem da compra.

Estes são apenas dois exemplos de como podemos modificar a listagem de compras. Você pode incluir outros campos, como o total da compra, por exemplo.

24. Visualização dos itens da compra no endpoint da listagem de compras

De forma semelhante ao que fizemos no Admin, vamos incluir os itens da compra na listagem de compras.

  • Crie um serializer para ItensCompra, no arquivo serializers/compra.py:
```python
...
from core.models import Compra, ItensCompra
...

class ItensCompraSerializer(ModelSerializer):
    class Meta:
        model = ItensCompra
        fields = "__all__"

No ComprasSerializer, inclua o seguinte código:

...
itens = ItensCompraSerializer(many=True, read_only=True)
...

O parâmetro many=True indica que o campo itens é uma lista de itens.

O parâmetro read_only=True indica que o campo itens não será utilizado para atualizar o model Compra.

  • Teste o endpoint no navegador.
  • Faça o commit com a mensagem Visualização dos itens da compra na listagem da compra.

Mostrando os detalhes dos itens da compra na listagem de compras

  • No serializer de ItensCompra, modifique o código:
class ItensCompraSerializer(ModelSerializer):
    class Meta:
        model = ItensCompra
        fields = "__all__"
        depth = 1

O parâmetro depth=1 indica que o serializer deve mostrar os detalhes do model ItensCompra. O valor 1 indica que o serializer deve mostrar os detalhes do model ItensCompra e dos models relacionados a ele (nesse caso, o livro). Se o valor fosse 2, o serializer mostraria os detalhes do model ItensCompra, dos models relacionados a ele e dos models relacionados aos models relacionados a ele (nesse caso, a categoria, a editora e o autor).

  • Experimente alterar o valor de depth e veja o resultado no navegador.

Mostrando apenas os campos necessários dos itens da compra na listagem de compras

Você deve ter percebido que o serializer de ItensCompra está mostrando todos os seus campos, incluindo o campo compra. Vamos modificar o serializer para mostrar apenas os campos necessários. Nesse exemplo, vamos mostrar apenas os camposlivro e quantidade.

  • No ItensCompraSerializer, modifique a linha fields:
fields = ("livro", "quantidade")

O parâmetro fields indica quais campos do model ItensCompra serão mostrados no serializer. Se o valor for __all__, todos os campos serão mostrados. Se o valor for uma sequência de campos, apenas esses campos serão mostrados.

  • Teste o endpoint no navegador.
  • Faça o commit com a mensagem Limitando os campos dos itens da compra na listagem de compras.

Mostrando o total do item na listagem de compras

O total do item é calculado pelo preço do livro multiplicado pela quantidade. Esse é um campo calculado, que não existe no model ItensCompra. Vamos incluir esse campo na listagem de compras.

  • Primeiro, importe o SerializerMethodField no arquivo serializers/compra.py:
from rest_framework.serializers import CharField, ModelSerializer, SerializerMethodField
  • Depois, modifique o ItensCompraSerializer, para que fique desse jeito:
class ItensCompraSerializer(ModelSerializer):
    total = SerializerMethodField()

    def get_total(self, instance):
        return instance.livro.preco * instance.quantidade

    class Meta:
        model = ItensCompra
        fields = ("livro", "quantidade", "total")
        depth = 1

O parâmetro SerializerMethodField indica que o campo total não existe no model ItensCompra. Ele será calculado pelo método get_total.

O método get_total recebe como parâmetro o objeto instance, que representa o item da compra. A partir dele, podemos acessar os campos do item da compra, como quantidade e livro.preco.

O método get_total retorna o valor do campo total, que é calculado pelo preço do livro multiplicado pela quantidade.

O método get_<nome_do_campo> é um método especial do serializer que é chamado para calcular o valor do campo <nome_do_campo>.

Incluimos o campo total no fields do serializer.

  • Teste o endpoint no navegador.
  • Faça o commit com a mensagem Mostrando o total do item na listagem de compras.

25. Inclusão do total da compra na listagem de compras

Vamos incluir o total da compra na listagem de compras. O total da compra é calculado pela soma dos totais dos itens da compra. Esse é um campo calculado, que não existe no model Compra. Vamos incluir esse campo na listagem de compras.

  • Ao final da model Compra, inclua o seguinte código:
...
    @property
    def total(self):
        # total = 0
        # for item in self.itens.all():
        #     total += item.livro.preco * item.quantidade
        # return total
        return sum(item.livro.preco * item.quantidade for item in self.itens.all())

No código acima, temos duas formas de calcular o total da compra. A primeira forma está comentada. A segunda forma está descomentada. A segunda forma é mais simples e mais eficiente, e utiliza uma compreensão de lista (list comprehension).

O método property indica que o campo total não existe no model Compra. Ele será calculado pelo método total.

O método total retorna o valor do campo total, que é calculado pela soma dos totais dos itens da compra, que é calculado pelo preço do livro multiplicado pela quantidade do item da compra.

  • Precisamos incluir o campo total no serializer de Compra. No ComprasSerializer, inclua o seguinte código:
...
        fields = ("id", "usuario", "status", "total", "itens")
...

O parâmetro fields indica quais campos do model Compra serão mostrados no serializer. Se o valor for __all__, todos os campos serão mostrados. Se o valor for uma lista de campos, apenas os campos da lista serão mostrados, na ordem da lista.

  • Teste o endpoint no navegador.
  • Faça o commit com a mensagem Inclusão do total da compra na listagem de compras.

26. Criação de um endpoint para criar novas compras

Vamos primeiro definir o que é necessário para criar uma nova compra. Para criar uma nova compra, precisamos informar o usuário e os itens da compra. Os itens da compra são compostos pelo livro e pela quantidade. Essas são as informações necessárias para criar uma nova compra.

O formato dos dados para criar uma nova compra é o seguinte:

{
    "usuario": 1,
    "itens": [
        {
            "livro": 1,
            "quantidade": 1
        },
        {
            "livro": 2,
            "quantidade": 2
        }
    ]
}

Criando um serializer para os itens da compra

Tendo definido o formato dos dados, vamos criar um novo serializer, que será usado para criar uma nova compra ou editar uma compra já existente.

  • No arquivo serializer/compra.py , inclua o seguinte serializer no final do arquivo:
...
class CriarEditarCompraSerializer(ModelSerializer):
    itens = ItensCompraSerializer(many=True)

    class Meta:
        model = Compra
        fields = ("usuario", "itens")
...

O parâmetro many=True indica que o campo itens é uma lista de itens de compra.

  • Inclua também o serializer no arquivo __init__.py dos serializers:
from .compra import CompraSerializer, CriarEditarCompraSerializer, ItensCompraSerializer

Alterando o viewset de Compra para usar o novo serializer

Vamos alterar o viewset de Compra para usar o novo serializer, nas operações de criação e edição.

  • No arquivo views/compra.py altere o viewset de Compra para usar o novo serializer:
...
from core.serializers import CompraSerializer, CriarEditarCompraSerializer
...
class CompraViewSet(ModelViewSet):
    queryset = Compra.objects.all()
    serializer_class = CompraSerializer

    def get_serializer_class(self):
        if self.action in ("create", "update"):
            return CriarEditarCompraSerializer
        return CompraSerializer
...
  • Tente criar uma nova compra no endpoint compras/ no ThunderClient, utilizando o método POST:
{
    "usuario": 1,
    "itens": [
        {
            "livro": 1,
            "quantidade": 1
        }
    ]
}

Você receberá o seguinte erro:

AssertionError at /api/compras/
The `.create()` method does not support writable nested fields by default.
Write an explicit `.create()` method for serializer `core.serializers.compra.CriarEditarCompraSerializer`, or set `read_only=True` on nested serializer fields.

Traduzindo, chegamos no seguinte:

Erro de afirmação em /api/compras/
O método `.create()` não suporta campos aninhados graváveis por padrão.
Escreva um método `.create()` explícito para o serializer `core.serializers.compra.CriarEditarCompraSerializer`, ou defina `read_only=True` nos campos do serializer aninhado.

O erro ocorre por que os itens da compra vêm de outra tabela, a tabela ItemCompra, através de uma chave estangeira. O serializer de Compra não sabe como criar os itens da compra. Precisamos alterar o método create do serializer de Compra para criar os itens da compra.

Alterando o método create do serializer de Compra

  • No arquivo serializers/compra.py , altere o serializer de Compra para suportar campos aninhados:
...

class CriarEditarCompraSerializer(ModelSerializer):
    itens = CriarEditarItensCompraSerializer(many=True) # Aqui mudou

    class Meta:
        model = Compra
        fields = ("usuario", "itens")

    def create(self, validated_data):
        itens_data = validated_data.pop("itens")
        compra = Compra.objects.create(**validated_data)
        for item_data in itens_data:
            ItensCompra.objects.create(compra=compra, **item_data)
        compra.save()
        return compra

O método create é chamado quando uma nova compra é criada. Ele recebe os dados validados e cria a compra e os itens da compra.

  • Precisamos criar também o novo serializer CriarEditarItensCompraSerializer para os itens da compra. No serializers/compra.py, inclua o seguinte código, após o ItensCompraSerializer:
...
class CriarEditarItensCompraSerializer(ModelSerializer):
    class Meta:
        model = ItensCompra
        fields = ("livro", "quantidade")
...

O serializer de ItensCompra é bem simples, pois ele recebe apenas o livro e a quantidade.

  • Teste o endpoint no `ThunderClient.
  • Faça o commit com a mensagem Criação de um endpoint para criar novas compras.

27. Criação de um endpoint para atualizar compras

  • Vamos tentar alterar uma compra existente no endpoint compras/1/ (ou aquela que você preferir) no ThunderClient, utilizando o método PUT:
{
    "usuario": 2,
    "itens": [
        {
            "livro": 2,
            "quantidade": 2
        }
    ]
}

Você receberá o seguinte erro:

AssertionError at /api/compras/1/
The `.update()` method does not support writable nested fields by default.
Write an explicit `.update()` method for serializer `core.serializers.compra.CriarEditarCompraSerializer`, or set `read_only=True` on nested serializer fields.

Traduzindo, chegamos no seguinte:

Erro de afirmação em /api/compras/1/
O método `.update()` não suporta campos aninhados graváveis por padrão.
Escreva um método `.update()` explícito para o serializer `core.serializers.compra.CriarEditarCompraSerializer`, ou defina `read_only=True` nos campos do serializer aninhado.

O erro ocorre por que os itens da compra vêm de outra tabela, a tabela ItensCompra, através de uma chave estangeira. O serializer de Compra não sabe como atualizar os itens da compra. Precisamos alterar o método update do serializer de Compra para atualizar os itens da compra.

  • No arquivo serializers/compra.py, redefina o método update do serializer de CriarEditarCompraSerializer:
...
    def update(self, instance, validated_data):
        itens = validated_data.pop("itens")
        if itens:
            instance.itens.all().delete()
            for item in itens:
                ItensCompra.objects.create(compra=instance, **item)
        instance.save()
        return super().update(instance, validated_data)
...

O método update é chamado quando uma compra é atualizada. Ele recebe os dados validados e atualiza a compra e os itens da compra.

O método update recebe dois parâmetros: instance e validated_data. O instance é a compra que está sendo atualizada. O validated_data são os dados validados que estão sendo atualizados.

O método update remove todos os itens da compra (se houverem) e cria novos itens com os dados validados.

O método update salva a compra e retornamos a instância.

O comando super().update(instance, validated_data) chama o método update da classe pai, que é o método padrão de atualização.

  • Teste o endpoint no ThunderClient:
    • use o método PUT, para atualizar a compra de forma completa;
    • use o método PATCH, para atualizar a compra de forma parcial.
      • Experimente mudar apenas o usuário;
      • Experimente mudar apenas a quantidade de um item da compra;
      • Experimente mudar o livro de um item da compra;
  • Faça o commit com a mensagem Criação de um endpoint para atualizar compras.

28. Criação de uma compra a partir do usuário autenticado

Ao invés de passar o usuário no corpo da requisição, podemos pegar o usuário autenticado e criar a compra a partir dele. O Django Rest Framework nos dá uma forma de fazer isso.

  • Primeiro, vamos importar todos os tipos de campos necessários no arquivo serializers/compra.py:
from rest_framework.serializers import (
    CharField,
    CurrentUserDefault, # novo
    HiddenField, # novo
    ModelSerializer,
    SerializerMethodField,
)
  • Agora, vamos definir o usuário como um campo oculto, cujo valor padrão é o usuário autenticado, na criação ou edição de uma compra.
class CriarEditarCompraSerializer(ModelSerializer):
    usuario = HiddenField(default=CurrentUserDefault())
...

O campo usuario é um campo oculto, pois foi definido como serializers.HiddenField. Ele não é exibido no serializer.

O valor padrão do campo é o usuário autenticado.

O CurrentUserDefault é um campo padrão que retorna o usuário autenticado.

Para testar, vamos criar uma nova compra no endpoint compras/ no ThunderClient, utilizando o método POST:

{
    "itens": [
        {
            "livro": 2,
            "quantidade": 2
        }
    ]
}

Observe que não precisamos mais passar o usuário no corpo da requisição, pois ele pega o usuário autenticado.

  • Faça o commit com a mensagem Criação de uma compra a partir do usuário autenticado.

29. Filtrando apenas as compras do usuário autenticado

Nesse momento, qualquer usuário pode ver todas as compras. Vamos filtrar da seguinte forma: se o usuário for um usuário normal, ele só pode ver as suas compras. Se o usuário for um administrador, ele pode ver todas as compras.

  • No views.py, vamos alterar o viewset de Compra para filtrar apenas as compras do usuário autenticado:
...
class CompraViewSet(ModelViewSet):
    queryset = Compra.objects.all()

    def get_queryset(self):
        usuario = self.request.user
        if usuario.is_superuser:
            return Compra.objects.all()
        if usuario.groups.filter(name="Administradores"):
            return Compra.objects.all()
        return Compra.objects.filter(usuario=usuario)
...

O método get_queryset é chamado quando uma compra é listada.

O request é o objeto que representa a requisição. O request.user é o usuário autenticado.

Se o usuário for um superusuário ou for membro do grupo "Administradores", retorna todas as compras. Caso contrário, retorna apenas as compras do usuário autenticado.

  • Para testar, autentique-se com um usuário normal e depois com um que seja administrador. Você verá que o administrador consegue ver todas as compras, enquanto o usuário normal só consegue ver as suas compras.
  • Faça o commit com a mensagem Filtrando apenas as compras do usuário autenticado.

30. Validando a quantidade de itens em estoque

Nesse momento, é possível criar uma compra com uma quantidade de itens maior do que a quantidade em estoque. Vamos validar isso.

  • No serializers/compra.py, vamos alterar o serializer CriarEditarItensCompraSerializer para validar a quantidade de itens em estoque, de forma a não permitir que a quantidade de itens solicitada seja maior do que a quantidade em estoque:
...
from rest_framework.serializers import (
    CharField,
    CurrentUserDefault,
    HiddenField,
    ModelSerializer,
    SerializerMethodField,
    ValidationError, # novo
)
...
    def validate(self, data):
        if data["quantidade"] > data["livro"].quantidade:
            raise ValidationError("Quantidade de itens maior do que a quantidade em estoque.")
        return data
...

O método validate é chamado quando os dados são validados. Ele recebe os dados e retorna os dados validados.

  • Para testar, tente criar uma compra com uma quantidade de itens maior do que a quantidade em estoque. Você verá que a compra não é criada e é exibida uma mensagem de erro.
  • Faça o commit com a mensagem Validando a quantidade de itens em estoque.

31. Gravando o preço do livro no item da compra

Nesse momento, o preço do livro não é gravado no item da compra. Vamos gravar o preço do livro no item da compra, uma vez que o preço do livro pode mudar e queremos manter o registro do preço do livro no momento da compra.

Inclui o campo preco na entidade ItensCompra

  • Primeiro, precisamos incluir o campo preco na entidade ItensCompra, em models/compra.py:
...
class ItensCompra(models.Model):
...
    preco = models.DecimalField(max_digits=10, decimal_places=2, default=0)
...
  • Execute as migrações.
  • Lembre-se de migrar também o banco de dados publicado, caso você esteja utilizando.

Gravando o preço do livro na criação do item da compra

  • No serializers/compra.py, vamos alterar a função create do serializer CriarEditarCompraSerializer para gravar o preço do livro no item da compra:
...
    def create(self, validated_data):
        itens = validated_data.pop("itens")
        compra = Compra.objects.create(**validated_data)
        for item in itens:
            item["preco"] = item["livro"].preco # nova linha
            ItensCompra.objects.create(compra=compra, **item)
        compra.save()
        return compra
...

O método create é chamado quando uma nova compra é criada. Ele recebe os dados validados e cria a compra e os itens da compra.

Alterando o campo total da compra para considerar o preço do item da compra

  • Para finalizar, precisamos alterar o campo total da compra para considerar o preço do item da compra, e não o preço do livro. No models/compra.py, altere o método total da model Compra:
...
    @property
    def total(self):
        return sum(item.preco * item.quantidade for item in self.itens.all())
...

Estamos utilizando o campo preco para calcular o total da compra, ao invés do campo livro.preco.

  • Para testar, crie uma nova compra e verifique que o preço do livro foi gravado no item da compra.

Gravando o preço do livro na atualização do item da compra

Da mesma forma, podemos alterar o método update do serializer CriarEditarCompraSerializer para gravar o preço do livro no item da compra:

...
    def update(self, instance, validated_data):
        itens = validated_data.pop("itens")
        if itens:
            instance.itens.all().delete()
            for item in itens:
                item["preco"] = item["livro"].preco # nova linha
                ItensCompra.objects.create(compra=instance, **item)
        instance.save()
        return super().update(instance, validated_data)
...
  • Para testar, altere uma compra e verifique que o preço do livro foi gravado no item da compra.
  • Faça o commit com a mensagem Gravando o preço do livro no item da compra.

32. Acrescentando a data da compra

No momento, não existe nenhum registro da data da compra. Vamos incluir a data da compra, que será definida automaticamente no momento da criação da compra.

  • Vamos incluir o campo data na entidade Compra, em models/compra.py:
...
class Compra(models.Model):
...
    usuario = models.ForeignKey(User, on_delete=models.PROTECT, related_name="compras")
    status = models.IntegerField(choices=StatusCompra.choices, default=StatusCompra.CARRINHO)
    data = models.DateTimeField(auto_now_add=True) # campo novo
...

O campo data é um campo do tipo DateTimeField, que armazena a data e a hora da compra.

O parâmetro auto_now_add=True indica que o campo será preenchido automaticamente com a data e hora atual, quando a compra for criada.

  • Execute as migrações.

Vocẽ receberá um erro na migration, pois o campo data não pode ser nulo.

  • Escolha a opção 2, que é a opção de preencher o campo com a data atual (timezone.now).

  • Execute as migrações no banco de dados publicado, caso você esteja utilizando.

Modificando o serializer de compra para mostrar a data da compra

Para que a data da compra seja mostrada no endpoint, precisamos modificar o serializer de Compra para incluir o campo data.

  • No serializers/compra.py, vamos incluir o campo data no serializer de Compra:
from rest_framework.serializers import (
    CharField,
    CurrentUserDefault,
    DateTimeField, # novo
    HiddenField,
    ModelSerializer,
    SerializerMethodField,
    ValidationError,
)
...
class ComprasSerializer(ModelSerializer):
    usuario = CharField(source="usuario.email", read_only=True)
    status = CharField(source="get_status_display", read_only=True)
    data = serializers.DateTimeField(read_only=True) # novo campo
    itens = ItensCompraSerializer(many=True, read_only=True)

    class Meta:
        model = Compra
        fields = ("id", "usuario", "status", "total", "data", "itens") # modificado
...
  • Para testar, crie uma nova compra e verifique que a data da compra foi gravada.
  • Faça o commit com a mensagem Acrescentando a data da compra.

33. Adicionando tipo de pagamento à entidade de Compra

Vamos adicionar o tipo de pagamento à compra. O tipo de pagamento pode ser cartão de crédito, cartão de débito, pix, boleto, transferência bancária, dinheiro ou outro.

  • No models\compra.py, vamos incluir o campo tipo_pagamento no model Compra:
...
class Compra(models.Model):
    class TipoPagamento(models.IntegerChoices):
        CARTAO_CREDITO = 1, "Cartão de Crédito"
        CARTAO_DEBITO = 2, "Cartão de Débito"
        PIX = 3, "PIX"
        BOLETO = 4, "Boleto"
        TRANSFERENCIA_BANCARIA = 5, "Transferência Bancária"
        DINHEIRO = 6, "Dinheiro"
        OUTRO = 7, "Outro"
...
    tipo_pagamento = models.IntegerField(choices=TipoPagamento.choices, default=TipoPagamento.CARTAO_CREDITO)
...

O campo tipo_pagamento é um campo do tipo IntegerField, que armazena o tipo de pagamento da compra. O parâmetro choices indica as opções de pagamento. O parâmetro default indica o tipo de pagamento padrão.

  • Execute as migrações.
  • Para testar, crie uma nova compra e verifique que o tipo de pagamento foi gravado.
  • Faça o commit com a mensagem Adicionando tipo de pagamento à entidade de Compra.

34. Adicionando o tipo de usuário à model de Usuário

Inicialmente, utilizamos os grupos do Django para diferenciar os usuários. Uma outra forma de diferenciar os usuários é através de um campo tipo de usuário. Vamos adicionar o tipo de usuário na entidade Usuario.

  • Em models/user.py, vamos incluir o campo tipo_usuario na entidade User:
...
class User(AbstractBaseUser, PermissionsMixin):
    class TipoUsuario(models.IntegerChoices):
        CLIENTE = 1, "Cliente"
        VENDEDOR = 2, "Vendedor"
        GERENTE = 3, "Gerente"
...
    tipo_usuario = models.IntegerField(_("User Type"), choices=TipoUsuario.choices, default=TipoUsuario.CLIENTE)
...

O campo tipo_usuario é um campo do tipo IntegerField, que armazena o tipo de usuário. O parâmetro choices indica as opções de usuário. O parâmetro default indica o tipo de usuário padrão.

  • Execute as migrações.
  • Para testar, crie um novo usuário e verifique que o tipo de usuário foi gravado.

Adicionando o campo ao Admin

Vamos adicionar o campo tipo_usuario ao Admin.

  • Em admin/user.py, vamos incluir o campo tipo_usuario no UserAdmin:
...
class UserAdmin(BaseUserAdmin):
    fieldsets = (
...
        (_("Personal Info"), {"fields": ("name", "foto", "tipo_usuario")}),
    )
...

O campo tipo_usuario foi incluído no campo Personal info.

Utilizando o tipo de usuário

Uma forma de utilizar o tipo de usuário é verificando se o usuário é GERENTE e então permitir que ele tenha acesso a todas as compras. Vamos ver como fazer isso.

  • No views/compra.py, vamos alterar o método get_queryset para permitir que o usuário GERENTE tenha acesso a todas as compras:
...
class CompraViewSet(ModelViewSet):
...
    def get_queryset(self):
        usuario = self.request.user
...
        if usuario.tipo == Usuario.Tipos.GERENTE:
            return Compra.objects.all()
        return Compra.objects.filter(usuario=usuario)
...

O método get_queryset é chamado quando uma compra é listada. Ele retorna apenas as compras do usuário autenticado, exceto se o usuário for GERENTE, que retorna todas as compras.

  • Para testar, autentique-se com um usuário normal e depois com um que seja GERENTE. Você verá que o GERENTE consegue ver todas as compras, enquanto o usuário normal só consegue ver as suas compras.
  • Faça o commit com a mensagem Adicionando o tipo de usuário à model de Usuário.

35. Finalizando a compra e atualizando a quantidade de itens em estoque

Nesse momento, a compra é criada com o status CARRINHO. Vamos criar um endpoint para finalizar a compra, alterando o status da compra para REALIZADO. No momento que a compra é finalizada, a quantidade de itens em estoque deve ser atualizada, isto é, a quantidade de itens em estoque deve ser reduzida pela quantidade de itens comprados.

  • No models/compra.py, vamos criar um método finalizar na model Compra:
from django.db import transaction

from rest_framework import status
from rest_framework.decorators import action
from rest_framework.response import Response
...
class Compra(models.Model):
...
    @action(detail=True, methods=["post"])
    def finalizar(self, request, pk=None):
        compra = self.get_object()
        if compra.status != Compra.StatusCompra.CARRINHO:
            return Response(
                status=status.HTTP_400_BAD_REQUEST,
                data={"status": "Compra já finalizada"},
            )
        with transaction.atomic():
            for item in compra.itens.all():
                if item.quantidade > item.livro.quantidade:
                    return Response(
                        status=status.HTTP_400_BAD_REQUEST,
                        data={
                            "status": "Quantidade insuficiente",
                            "livro": item.livro.titulo,
                        },
                    )
                item.livro.quantidade -= item.quantidade
                item.livro.save()
            compra.status = Compra.StatusCompra.REALIZADO
            compra.save()
        return Response(status=status.HTTP_200_OK, data={"status": "Compra finalizada"})

O decorador @action cria um endpoint para a ação finalizar, no formato api/compras/{id}/finalizar.

O método finalizar é um método de ação que finaliza a compra. Ele recebe a compra que está sendo finalizada.

Se a compra já foi finalizada, retorna um erro.

Se a quantidade de itens em estoque for menor do que a quantidade de itens comprados, retorna um erro.

Se a quantidade de itens em estoque for maior ou igual à quantidade de itens comprados, atualiza a quantidade de itens em estoque e finaliza a compra.

O comando with transaction.atomic() garante que todas as operações dentro do bloco with sejam executadas ou nenhuma seja executada.

O método save é chamado para salvar a compra e o livro.

O método Response retorna uma resposta HTTP.

O status HTTP_200_OK indica que a requisição foi bem sucedida.

O status HTTP_400_BAD_REQUEST indica que a requisição não foi bem sucedida.

  • Para testar:

    • Tente finalizar uma compra que não foi finalizada.
    • Tente finalizar uma compra que já foi finalizada.
    • Tente finalizar uma compra com quantidade de itens maior do que a quantidade em estoque.
    • Tente finalizar uma compra com quantidade de itens menor ou igual à quantidade em estoque.
  • Faça o commit com a mensagem Finalizando a compra e atualizando a quantidade de itens em estoque.

36. Utilizando filtros

Nesse momento, é possível apenas listar todos os livros. Vamos ver como podemos filtrar os livros por seus atributos, como categoria, editora e autores.

Para isso, vamos utilizar o pacote django-filter, que nos permite filtrar os resultados de uma consulta. Ele já está instalado no projeto.

Filtrando os livros por categoria

Vamos começar filtrando os livros por categoria.

  • No views/livro.py, vamos alterar o viewset de Livro para filtrar os livros por categoria:
...
from django_filters.rest_framework import DjangoFilterBackend
...
class LivroViewSet(viewsets.ModelViewSet):
    queryset = Livro.objects.all()
    serializer_class = LivroSerializer
    filter_backends = [DjangoFilterBackend]
    filterset_fields = ["categoria__descricao"]
...

O DjangoFilterBackend é o filtro do django-filter.

O filterset_fields indica quais campos serão filtrados. Nesse caso, estamos filtrando apenas pelo campo categoria__descricao.

  • Para testar no Swagger, clique no endpoint livros/ e depois em Try it out. Você verá que apareceu um campo categoria para filtrar os livros por categoria. Informe a descrição da categoria e clique em Execute. Você verá que apenas os livros da categoria informada foram listados.
  • Para testar no ThunderClient, utilize a url com o seguinte formato: http://0.0.0.0:19003/api/livros/?categoria__descricao=Python. Você verá que apenas os livros da categoria informada foram listados.

Acrescentando outros filtros na listagem de livros

Vamos acrescentar outros filtros na listagem de livros.

  • No views/livro.py, vamos alterar o atributo filterset_fields, na viewset de Livro para filtrar os livros por categoria__descricao e editora__nome:
...
    filterset_fields = ["categoria__descricao", "editora__nome"]
...

O filterset_fields indica quais campos serão filtrados. Nesse caso, estamos filtrando pelos campos categoria__descricao e editora__nome.

Da mesma forma, por outros campos.

Exercício

  • Acrescente filtros nas models Autor, Categoria, Editora, Livro e Compra.

  • Faça o commit com a mensagem Utilizando filtros.

37. Busca textual

A busca textual serve para adicionar a funcionalidade de realizar buscas dentro de determinados valores de texto armazenados na base de dados.

Contudo a busca só funciona para campos de texto, como CharField e TextField.

  • Para utilizar a busca textual nos livros, devemos promover duas alterações em nossa viewset:

  • Novamente alterar o atributo filter_backends, adicionando o Backend SearchFilter que irá processar a busca; e

  • Adicionar o atributo search_fields, contendo os campos que permitirão a busca.

  • A LivroViewSet ficará assim:

...
from rest_framework.filters import SearchFilter
...

class LivroViewSet(viewsets.ModelViewSet):
...
    filter_backends = [DjangoFilterBackend, SearchFilter]
    filterset_fields = ["categoria__descricao", "editora__nome"]
    search_fields = ["titulo"]
...
  • Para pesquisar por um livro, basta adicionar o parâmetro search na URL, com o valor a ser pesquisado. Por exemplo, para pesquisar por livros que contenham a palavra python no título, a URL ficaria assim:

Exercício

  • Acrescente a busca textual nas models Autor, Categoria, Editora e Compra.

  • Faça o commit com a mensagem Adicionando busca textual.

38. Ordenação dos resultados

Toda viewset possui um atributo chamado ordering_fields, que é uma lista de campos que podem ser utilizados para ordenar os resultados. Além disso, o atributo ordering é utilizado para definir o campo padrão de ordenação. Se você ainda quiser permitir a ordenação reversa, basta adicionar um sinal de menos (-) na frente do campo.

Independentemente dessa ordenação padrão, o usuário pode ordenar os resultados de acordo com o campo desejado, passando o nome do campo como parâmetro na URL.

A ordenação serve para adicionar a funcionalidade de ordenar os resultados de uma consulta.

  • Para utilizar a ordenação nos livros, devemos promover três alterações em nossa ViewSet:
  • Novamente alterar o atributo filter_backends, adicionando o Backend OrderingFilter que irá processar a ordenação; e
  • Adicionar o atributo ordering_fields, contendo os campos que permitirão a ordenação.
  • Adicionar o atributo ordering com o campo que será utilizado como padrão para ordenação.
  • A LivroViewSet ficará assim:
...
from rest_framework.filters import SearchFilter, OrderingFilter
...

class LivroViewSet(viewsets.ModelViewSet):
...
    filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
    filterset_fields = ["categoria__descricao", "editora__nome"]
    search_fields = ["titulo"]
    ordering_fields = ["titulo", "preco"]
    ordering = ["titulo"]
...

Esses são apenas alguns exemplos de como utilizar os filtros, a pesquisa textual e a ordenação. Você pode combinar esses recursos da forma que desejar.

Acrescentando filtro e ordenação por data

Vamos ver ainda um último exemplo de como adicionar filtro e ordenação.

  • No views/compra.py, vamos alterar o atributo filterset_fields, na viewset de Compra para filtrar as compras por data.
  • Vamos também alterar o atributo ordering_fields, na viewset de Compra para ordenar as compras por data.
...
    filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]
    filterset_fields = ["usuario__email", "status", "data"]
    search_fields = ["usuario__email"]
    ordering_fields = ["usuario__email", "status", "data"]
    ordering = ["-data"]
...

Exercício

  • Acrescente a ordenação nas models Autor, Categoria, Editora e Compra.
  • Faça o commit com a mensagem Adicionando ordenação.

Exercícios Garagem

O projeto Garagem é um projeto de uma garagem de carros. O objetivo é praticar aquilo que foi visto nesse tutorial, no projeto core.

E1. Crie o projeto Garagem

Seguindo aquilo que você já aprendeu na criação do projeto da Livraria, crie um novo projeto, a partir do template.

  1. O projeto será chamado Garagem.
  2. Nomeie o commit como sendo Criação do projeto.
  3. Siga esses passos para criar a API.
  4. Crie as seguintes APIs, fazendo um commit para cada uma:
    • Acessorio:
      • descricao (string, máximo 100 caracteres).
      • __str__ (retorna a descrição e o id).
      • Exemplos: Ar condicionado, Direção hidráulica, Vidros elétricos, Travas elétricas, Alarme, Airbag, Freios ABS.
    • Categoria:
      • descricao (string, máximo 100 caracteres).
      • __str__ (retorna a descrição e o id.
      • Exemplos: Sedan, Hatch, SUV, Picape, Caminhonete, Conversível, Esportivo, Utilitário.
    • Cor:
      • nome (string, máximo 100 caracteres).
      • __str__ (retorna o nome e o id).
      • Exemplo: Preto, Branco, Prata, Vermelho, Cinza, Grafite.
    • Marca:
      • nome (string, máximo 50 caracteres).
      • nacionalidade (string, máximo 50 caracteres, permite nulo).
      • __str__ (retorna o nome em caixa alta e o id).
      • Exemplo: FORD, CHEVROLET, VOLKSWAGEN, FIAT, RENAULT, TOYOTA, HONDA, HYUNDAI, KIA, NISSAN, PEUGEOT, CITROEN, JEEP, MITSUBISHI, MERCEDES-BENZ, BMW, AUDI, VOLVO.
  5. Crie a aplicação frontend com Vuejs para consumir a API REST do projeto Garagem. Pode utilizar o template do projeto da livraria-vue3 como base.

E2. Crie o modelo Modelo

Vamos incluir o modelo Modelo no projeto Garagem.

  • Crie o modelo Modelo, com os seguintes atributos:
    • nome (string, máximo 100 caracteres).
    • marca (chave estrangeira para Marca).
    • categoria (chave estrangeira para Categoria).
    • __str__ (retorna o nome do modelo e a marca).
    • Exemplo: KA, FIESTA, ECOSPORT, RANGER, ONIX, PRISMA, TRACKER, S10, GOL, POLO, TAOS, AMAROK, ARGO, TORO, UNO, CRONOS, COMPASS, CIVIC, HR-V, FIT, CITY, HB20, CRETA, TUCSON, KICKS, FRONTIER, 208, 3008, C3, C4, COMPASS, A3, A4, Q3, Q5, XC40, XC60.
  • Crie a API REST para o modelo Modelo.
  • Crie a aplicação frontend com Vuejs para consumir a API REST do modelo Modelo.
  • Faça um commit para cada etapa.

E3. Crie o modelo Veiculo

Vamos incluir o modelo Veiculo no projeto Garagem.

  • Crie o modelo Veiculo, com os seguintes atributos:
    • modelo (chave estrangeira para Modelo).
    • cor (chave estrangeira para Cor).
    • ano (inteiro, permite nulo, default 0).
    • preco (decimal, máximo 10 dígitos, 2 casas decimais, permite nulo, default 0).
    • acessorios (chave estrangeira para Acessorio, muitos para muitos).
    • __str__ (retorna o modelo, ano e cor do carro).
  • Crie a API REST para o modelo Veiculo.
  • Crie a aplicação frontend com Vuejs para consumir a API REST do modelo Veiculo.
  • Faça um commit para cada etapa.

Apêndices

A1. Instalação e atualização do VS Code

Para instalar ou atualizar o VS Code, siga as seguintes instruções:

No Ubuntu/Mint e derivados:

sudo apt install code

No Manjaro:

yay -Syu visual-studio-code-bin

No Windows:

  • Clique no ícone de engrenagem no canto inferior esquerdo da tela do VS Code e clique em Check for Updates.

A2. Instalação e sincronização de extensões do VS Code

Instalação de extensões no VS Code

Instale as extensoẽs do VS Code de sua preferência. Você pode instalar as extensões clicando no ícone de extensões no canto esquerdo da tela do VS Code e pesquisando pelo nome da extensão.

Eu recomendo as seguintes:

Extensão Vue.js devtools no Google Chrome

Sinconização de extensões no VS Code

Você pode configurar a sincronização das extensões entre os computadores. Para isso:

  • Faça login com a conta do GitHub ou da Microsoft no VS Code.
  • Clique no ícone de engrenagem no canto inferior esquerdo da tela do VS Code e clique em Ativar a Sincronização de Configurações.

A3. Instalação e configuração do PDM

Instalação do PDM no Linux

As instruções a seguir são para o Linux Manjaro e Ubuntu. Se você estiver usando outra distribuição ou quiser mais informações, consulte a documentação do PDM.

  • Abra um terminal:

    Ctrl + Alt + T

  • Verifique se o PDM está instalado:

pdm -V
  • Se não estiver instalado, instale a versão mais recente:
curl -sSL https://raw.githubusercontent.com/pdm-project/pdm/main/install-pdm.py | python3 -
  • Após a instalação, feche o terminal (Ctrl + D) e abra um novo terminal (Ctrl + Alt + T).

IMPORTANTE: Após a instalação do PDM, você precisa rodar o script de configuração, conforme descrito abaixo.

Configuração do PDM no bash (Ubuntu e derivados)

  • Execute o seguinte comando:
curl -sSL https://github.com/marrcandre/django-drf-tutorial/raw/main/scripts/pdm_config_bash.sh | bash

Configuração do PDM no zsh com o Oh! My Zsh (Manjaro e derivados)

  • Execute o seguinte comando:
curl -sSL https://github.com/marrcandre/django-drf-tutorial/raw/main/scripts/pdm_config_ohmyzsh.sh | zsh

Instalação do PDM no Windows

Execute o comando abaixo no PowerShell (pode ser no Terminal do VS Code):

(Invoke-WebRequest -Uri https://raw.githubusercontent.com/pdm-project/pdm/main/install-pdm.py -UseBasicParsing).Content | python -

Verifique se o PDM está configurado para não usar virtualenv:

pdm config

IMPORTANTE: Se você não fizer essa configuração, o PDM irá criar uma pasta .venv no diretório do projeto. Para resolver isso, você deve apagar a pasta .venv e executar o comando pdm config python.use_venv false e então executar o comando pdm install.

Se precisar instalar o Python:

sudo apt install python-is-python3 python3.10-venv

Voltar para a preparação do ambiente

A4. Publicando o projeto no Seenode

O Seenode é uma plataforma de hospedagem que permite publicar aplicações web, bancos de dados e outros serviços. No site existe um link para o tutorial oficial.

Criando um Banco de Dados no Seenode

Para criar o banco de dados no Seenode, siga as instruções a seguir, ou leia a documentação oficial:

  • Acesse o site do Seenode.

  • Crie uma conta ou conecte-se no Seenode.

  • Clique na opção Create - Database.

    • Escolha o tipo MySQL.
    • Escolha uma região onde criar o banco de dados.
    • Escolha o tipo de pacote Free.
    • Se preferir, mude o nome do banco de dados.
    • Clique em Create database.

    Obtendo as informações do banco de dados:

  • Acesse o banco de dados criado.

  • Em Connection Details, clique em show password.

  • Em Connection Parameters, clique em URI.

  • Copie a URI do banco de dados, sem o "db:" inicial.

  • Armazene a URI do banco de dados no arquivo .env do projeto, como no exemplo:

# Seenode
DATABASE_URL=mysql://user:password@host:port/database

Com essa informação no arquivo .env, o Django vai usar o banco de dados do Seenode. Você já pode executar o projeto, migrar a base de dados, criar um superuser ou fazer o upload dos dados e testar a aplicação executando localmente mas acessando o banco de dados remotamente.

Incluindo suporte ao MySQL no projeto

Instale o MySQL no sistema:

  • No Ubuntu:
sudo apt install mysql-server
  • No Manjaro:
sudo pacman -S mysql

Instale o pacote mysqlclient no projeto:

pdm add mysqlclient

Criando uma aplicação no Seenode

Vamos criar uma aplicação no Seenode para publicar o projeto. Para isso, siga as instruções a seguir ou leia a documentação oficial:

  • Escolha a opção Create - Web.
  • Escolha um repositorio do GitHub e clique em Continue.
  • Escolha a branch main e clique em Continue.
  • Escolha a versão do Python de acordo com o seu projeto.
  • Em Build Command, coloque o comando pip install -r requirements.txt
  • Em Run Command, coloque o comando gunicorn app.wsgi --workers 2 --bind :80 --access-logfile -
  • Escolha uma região para hospedar a aplicação.
  • Escolha o tipo de pacote Free.
  • Clique em Create service.

Adicionando variáveis de ambiente

  • Adicione as variáveis de ambiente do arquivo .env no Seenode, em Variables.

Testando a aplicação

  • Acesse a URL da aplicação no Seenode e verifique se a aplicação está funcionando corretamente. Ela aparece na aba Domains.

A5. Criação do Banco de Dados no Supabase

Escolhendo uma plataforma de hospedagem

Além da publicação do projeto no Seenode, podemos publicar em outras plataformas. Uma das vantagem do Seenode é que ele oferece tanto um banco de dados PostgreSQL gratuito quanto hospedagem de aplicações web.

Se não formos utilizar o banco de dados do Seenode, podemos utilizar o banco de dados do Supabase para armazenar os dados da aplicação, enquanto a aplicação é publicada em outra plataforma, como o Render, que utilizarmos nesse tutorial.

IMPORTANTE: Você não precisa seguir esse passo e o próximo, se for utilizar o banco de dados e a aplicaçãoi web do Seenode.

Criando um banco de dados no Supabase

Para evitar a perda dos dados a cada nova publicação do projeto, vamos criar um banco de dados externamente no Supabase. O banco de dados SQLite local será utilizado apenas para desenvolvimento.

Criando um projeto no Supabase

Para criar o banco de dados no Supabase, siga as instruções a seguir:

  • Acesse o site do Supabase.
  • Crie uma conta ou conecte-se no Supabase.
  • Clique na opção Start your project.
  • Dẽ um nome ao projeto.
  • Selecione a opção Create a new organization.
  • Dẽ um nome à organização.
  • Dê um nome ao banco de dados.
  • Escolha uma senha e guarde-a (você vai precisar dela).
  • Selecione a região South America (São Paulo).

Configurando o banco de dados no projeto

  • Entre no Dashboard do projeto, e escolha o projeto criado.
  • Escolha a opção Project settings e depois Database.
  • Copia a linha de conexão do banco de dados (URI).
    • Ela deve ser parecida com isso: postgres://postgres:[YOUR-PASSWORD]@site.supabase.co:5432/postgres.
  • Coloque as informações da sua conta.
  • Copie a linha de conexão e cole no arquivo .env do projeto, como no exemplo:
# Supabase
DATABASE_URL=postgres://postgres:teste.123@!@db.vqcprcexhnwvyvewgrin.supabase.co:5432/postgres

Instalando o pacote dj_database_url

O pacote dj_database_url facilita a configuração do banco de dados no Django, pois ele converte a URL do banco de dados para o formato que o Django entende.

  • Instale o pacote dj_database_url:
pdm add dj-database-url
  • Adicione o pacote dj_database_url ao arquivo settings.py:
import dj_database_url
  • Substitua a configuração do banco de dados no arquivo settings.py:
DATABASES = {
    'default': dj_database_url.config(
        default='sqlite:///db.sqlite3',
        conn_max_age=600,
        conn_health_checks=True,
    )
}

Essa configuração permite que o Django use o banco de dados local em desenvolvimento e o banco de dados do Supabase em produção, definindo a variável de ambiente DATABASE_URL.

Migrando o banco de dados

  • No arquivo .env:
    • Altere o valor da variável MODE para MIGRATE.
    • Descomente a linha DATABASE_URL.
  • Faça a migracão do banco de dados:
pdm run python migrate

Observe que o banco de dados foi migrado no Supabase.

Para testar, crie alguns registros no banco de dados. Depois volte a configuração local e perceba que os dados são diferentes na base local e na base do Supabase.

  • No site do Supabase, acesse o Table Editor e verifique que as tabelas foram criadas.
  • Você também pode ver o esquema das tabelas, em Database, Schema Visualizer.

Utilizando o banco de dados local

  • Para voltar a usar o banco de dados local, no arquivo .env:
    • Altere o valor da variável MODE para DEVELOPMENT.
    • Comente a linha DATABASE_URL.

IMPORTANTE: A cada nova alteração no banco de dados, você deve repetir esse processo de migração, tanto no banco de dados local quanto no banco de dados do Supabase.

A6. Publicando o projeto no Render

O Render é uma plataforma de hospedagem que permite publicar aplicações web, bancos de dados e outros serviços. No site existe um link para o tutorial oficial: https://render.com/docs/deploy-django

Criando um script de Build

Precisamos executar uma série de comandos para construir nosso aplicativo. Podemos fazer isso com um script de construção (build script).

  • Crie um arquivo chamado build.sh na raiz do projeto com o seguinte conteúdo:
#!/usr/bin/env bash
# Sai do script se houver algum erro
set -o errexit

# Atualiza o pip
pip install --upgrade pip

# Instala as dependências
pip install -r requirements.txt

# Coleta os arquivos estáticos
python manage.py collectstatic --no-input

# Aplica as migrações
python manage.py migrate
  • Torne o script executável:
chmod a+x build.sh
  • Adicione os pacotes Uvicorn e Gunicorn ao projeto:
pdm add uvicorn gunicorn

Testando a execução localmente

  • Execute a seguinte linha de comando para testar a execução localmente:
pdm run python -m gunicorn app.asgi:application -k uvicorn.workers.UvicornWorker
  • Acesse o endereço http://localhost:8000 no navegador para verificar se a aplicação está funcionando.

O que fizemos foi substituir o servidor de desenvolvimento do Django pelo servidor Uvicorn e Gunicorn.

Configurando o Render

  • Acesse o site do Render

  • Crie uma conta ou conecte-se a uma conta existente.

  • Crie um novo serviço (Web Service).

  • Escolha a opção Build and deploy from a Git repository (Construir e implantar a partir de um repositório Git).

  • Escolha o repositório do projeto.

  • Preencha as informações necessárias:

    • Name: nome-do-projeto.
    • Region: Ohio (US East).
    • Branch: main.
    • Runtime: Python.
    • Build command: ./build.sh.
    • Start command: python -m gunicorn app.asgi:application -k uvicorn.workers.UvicornWorker.
    • Instance Type: Free.
  • Environment Variables: clique em Add from .env e adicione as informações do seu arquivo .env:

MODE=PRODUCTION
DEBUG=False
SECRET_KEY=[sua_secret_key]
WEB_CONCURRENCY=4
DATABASE_URL=[sua_database_url]
CLOUDINARY_URL=cloudinary://your_api_key:your_api_secret@your_cloud_name

Crie uma SECRET_KEY nova. Veja como aqui. Coloque essa chave no lugar de [sua_secret_key].

Coloque a URL do banco de dados do Supabase no lugar de [sua_database_url].

  • Clique em Create Web Service.

Se tudo estiver correto, o projeto será implantado no Render.

A7. Armazenando arquivos estáticos no Cloudinary

Vamos utilizar o Cloudinary para armazenar os arquivos estáticos, como as imagens dos livros. Detsa forma, os arquivos não serão perdidos a cada nova implantação.

Criando uma conta no Cloudinary

Configurando o Cloudinary

  • Edite o arquivo .env, incluindo a seguinte variável:
# Cloudinary
CLOUDINARY_URL=cloudinary://your_api_key:your_api_secret@your_cloud_name

Altere as informações de acordo com o seu projeto, acessando o Cloudinary Console na opção Dashboard.

  • Inclua essa mesma variável no Render (ou no serviço de hospedagem que você estiver utilizando), na opção Environment variables.

Testando

  • Coloque a variável MODE com o valor MIGRATE no arquivo .env.
  • Faça o upload de uma imagem pelo Admin do Django e verifique se ela foi salva no Cloudinary, na opção Media Explorer.
  • Se deu certo, sua aplicação deve estar funcionando normalmente, utilizando o Cloudinary para armazenar os arquivos estáticos.
  • Faça o commit com a mensagem Adicionando Cloudinary.

A8. Resolução de erros

Liberando uma porta em uso

  • Ao tentar executar o comando:
pdm run python manage.py runserver
  • Se você receber o seguinte erro:
Error: That port is already in use.
  • Execute o seguinte comando:
fuser -k 19003/tcp

Este comando vai matar o processo que está rodando na porta 19003. Mude o número da porta conforme necessário.

Removendo temporários, migrations e o banco de dados

find . -name "__pycache__" -type d -exec rm -r {} +
find . -path "*/migrations/*.pyc" -delete
find . -path "*/migrations/*.py" -not -name "__init__.py" -delete
rm -rf __pypackages__ pdm.lock
rm db.sqlite3

Pasta .venv criada no projeto

  • Se seu projeto tiver a pasta .venv, e não a pasta __pypackages__, remova a pasta .venv:
rm -rf .venv
  • Depois, execute novamente o script de configuração do pdm, da aula 1.
  • Opcionalmente, rode o seguinte comando, para configurar o projeto para não usar ambiente virtual:
pdm config python.use_venv false
  • Feito isso, execute o pdm install novamente.
  • Por fim, execute o pdm run dev novamente.

Geração da SECRET_KEY

A SECRET_KEY é uma chave secreta usada pelo Django para criptografar dados sensíveis. Ela é usada, por exemplo, para criptografar as senhas dos usuários. Em sistemas em produção ela deve ser mantida em segredo.

  • Para gerar uma nova SECRET_KEY (chave secreta), a ser colocada no arquivo .env, execute o comando:
python -c "import secrets; print(secrets.token_urlsafe())"
  • No Django, o comando é:
pdm run python manage.py shell -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"

Para saber mais sobre a chave secreta, acesse a documentação do Django.

Não esqueça de substituir a chave secreta pelo valor gerado.

Abrindo um arquivo sqlite3 na web

Aumentando o tempo de vida do token de autenticação JWT

  • Adicione as seguintes linhas ao arquivo settings.py:
from datetime import timedelta
...
SIMPLE_JWT = {
    "ACCESS_TOKEN_LIFETIME": timedelta(minutes=180),
    "REFRESH_TOKEN_LIFETIME":timedelta(days=1),
}

A9. Configurando o git

Um aviso importante

Antes de mais nada, seguem 3 regras a serem consideradas ao seguir as instruções:

  • Antes de clicar ou responder, leia atentamente as instruções.
  • Leia atentamente as instruções antes de clicar ou responder.
  • Nunca clique ou responda sem antes ler atentamente as instruções.

As 3 regras falam a mesma coisa? Sim, você entendeu o recado. ;-)

Configurando o projeto git

  • Se o computador estiver configurado com contas individuais, você precisará fazer isso apenas uma vez. Ainda assim, é bom verificar se está tudo certo.
  • Verifique se já não existe uma conta conectada ao GitHub no VS Code, clicando no ícone Contas na barra lateral esquerda. Deve ser o penúltimo ícone da baixo pra cima. Se houver, desconecte primeiro.
  • Inicialize o repositório git. Clique no ícone do git no painel lateral esquerdo. Deve ser o segundo ícone, de cima pra baixo. Opcionalmente, tecle (Control+Shift+G). Depois, clique no botão Initialize repository.
  • Se aparecer uma bolinha azul no ícone do git com um número, o repositório foi ativado. Esse número indica o número de arquivos que foram criados ou alterados.
  • Se aparecem muitos arquivos alterados (10 mil, por exemplo), é provável que exista um repositório git criado na pasta raiz do usuário. Apague esse repositório assim:
rm -Rf ~/.git
  • Recarregue a janela do VS Code:
Control + Shift + P + "Recarregar a Janela"
  • Verifique se o número mudou para algo mais razoável (em torno de 100 arquivos).

Configurando as variáveis do git

  • Informe seu nome e email no git. Para isso, abra o terminal do VS Code e digite:
git config --global user.name "Seu Nome"
git config --global user.email "seuEmailNoGitHub@gmail.com"
  • Para verificar se as variáveis foram configuradas corretamente, digite:
git config -l
  • Se aparecer outro nome de usuário ou outras informações estranhas, remova o arquivo com as configurações globais do git:
rm ~/.gitconfig

Repita o processo de configuração de nome e email.

A10. Usando curl para testar a API via linha de comando

  • Liste todas as categorias:
curl -X GET http://0.0.0.0:19003/api/categorias/
  • Liste uma categoria específica:
curl -X GET http://0.0.0.0:19003/api/categorias/1/
  • Crie uma nova categoria:
curl -X POST http://0.0.0.0:19003/api/categorias/ -d "descricao=Teste"
  • Atualize uma categoria:
curl -X PUT http://0.0.0.0:19003/api/categorias/1/ -d "descricao=Teste 2"
  • Delete uma categoria:
curl -X DELETE http://0.0.0.0:19003/api/categorias/1/

Contribua

Para contriburi com esse projeto:

  • Criar um fork do projeto.
  • Clonar o fork
  • Criar um branch para a sua contribuição.
  • Fazer as alterações no seu branch.
  • Enviar um pull request para o projeto original.

Marco André Mendes <marcoandre@gmail.com>

About

O mais famoso tutorial de Django com DRF do IFC. Amado por uns, odiado por outros, respeitado por todos.

Resources

Stars

Watchers

Forks

Packages

No packages published