Uma API RESTful dinâmica baseada em Django Rest Framework que permite operações CRUD em qualquer modelo Django sem a necessidade de criar viewsets específicos para cada modelo.
⚠️ Beta: Este pacote está em fase beta. Use em ambiente de produção por sua conta e risco.
pip install drf-builder- Visão Geral
- Estrutura do Projeto
- Endpoints
- Autenticação
- Operações HTTP
- Recursos Avançados
- Tratamento de Erros
- Exemplos de Uso
- Documentação da API
A API Dinâmica é uma solução para interação com modelos Django de forma genérica e flexível. Ela foi projetada para:
- Criar endpoints RESTful para qualquer modelo Django sem código adicional
- Suportar todas as operações CRUD básicas (Create, Read, Update, Delete)
- Permitir filtragem dinâmica de resultados
- Facilitar operações com relacionamentos aninhados
- Fornecer uma estrutura consistente para tratamento de erros
- Permitir seleção de campos específicos para retorno
- Oferecer controle de profundidade para serialização de relacionamentos
- Suportar operações em lote e exclusão em cascata
A API é composta pelos seguintes componentes principais:
- DynamicModelViewSet: ViewSet central que gerencia todas as operações CRUD
- DynamicSerializer: Serializer que se adapta dinamicamente a qualquer modelo
- Middleware de Exceções: Sistema para tratamento padronizado de erros
- Utilitários: Funções auxiliares para operações comuns
api/
├── viewsets.py # Contém DynamicModelViewSet
├── serializers.py # Contém DynamicSerializer
├── exceptions.py # Define exceções personalizadas
├── middleware.py # Implementa tratamento padronizado de erros
├── utils.py # Funções de utilidade
└── routers.py # Configuração de rotasA API segue um padrão de URL consistente:
- Lista/Criação:
/api/v1/<app>/<model>/ - Recuperar/Atualizar/Excluir:
/api/v1/<app>/<model>/<pk>/
Onde:
<app>é o nome do aplicativo Django<model>é o nome do modelo<pk>é o ID do registro
A API usa autenticação JWT (JSON Web Token) através do pacote rest_framework_simplejwt. Os seguintes endpoints estão disponíveis para autenticação:
- Obter Token:
POST /api/v1/token/ - Atualizar Token:
POST /api/v1/token/refresh/ - Verificar Token:
POST /api/v1/token/verify/
Exemplo de obtenção de token:
POST /api/v1/token/
{
"username": "usuario",
"password": "senha"
}Resposta de exemplo:
{
"refresh": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"access": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}Para autenticar solicitações subsequentes, inclua o token de acesso no cabeçalho HTTP:
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
Retorna uma lista de todos os objetos de um modelo específico.
Endpoint:
GET /api/v1/<app>/<model>/
Parâmetros de consulta:
depth: Nível de profundidade para serializar relacionamentos (0-10)filter_<campo>: Filtrar resultados por campo específicogroup_by: Agrupar resultados por um ou mais camposfields: Seleção de campos específicos para retornar, separados por vírgula
Exemplo de requisição:
GET /api/v1/blog/post/?filter_status=published&depth=1
Resposta de exemplo:
[
{
"id": 1,
"title": "Primeiro Post",
"content": "Conteúdo do post...",
"status": "published",
"author": {
"id": 1,
"name": "João Silva"
},
"_meta": {
"model": "post",
"app": "blog"
}
},
{
"id": 2,
"title": "Segundo Post",
"content": "Mais conteúdo...",
"status": "published",
"author": {
"id": 2,
"name": "Maria Souza"
},
"_meta": {
"model": "post",
"app": "blog"
}
}
]Retorna um objeto específico pelo seu ID.
Endpoint:
GET /api/v1/<app>/<model>/<pk>/
Parâmetros de consulta:
depth: Nível de profundidade para serializar relacionamentos (0-10)fields: Seleção de campos específicos para retornar, separados por vírgula
Exemplo de requisição:
GET /api/v1/blog/post/1/?depth=2
Resposta de exemplo:
{
"id": 1,
"title": "Primeiro Post",
"content": "Conteúdo do post...",
"status": "published",
"author": {
"id": 1,
"name": "João Silva",
"email": "joao@exemplo.com"
},
"comment_set": [
{
"id": 1,
"text": "Ótimo post!",
"created_at": "2023-01-15T14:30:00Z"
},
{
"id": 2,
"text": "Muito interessante.",
"created_at": "2023-01-16T10:15:00Z"
}
],
"_meta": {
"model": "post",
"app": "blog"
}
}Cria um novo objeto do modelo especificado.
Endpoint:
POST /api/v1/<app>/<model>/
Exemplo de requisição:
POST /api/v1/blog/post/
Payload de exemplo:
{
"title": "Novo Post",
"content": "Este é um novo post",
"status": "draft",
"author": 1,
"comment_set": [
{
"text": "Um comentário inicial",
"author_name": "Leitor Assíduo"
}
]
}Resposta de exemplo:
{
"id": 3,
"title": "Novo Post",
"content": "Este é um novo post",
"status": "draft",
"author": 1,
"created_at": "2023-07-10T08:45:12Z",
"_meta": {
"model": "post",
"app": "blog"
}
}Atualiza parcialmente um objeto existente.
Endpoint:
PATCH /api/v1/<app>/<model>/<pk>/
Exemplo de requisição:
PATCH /api/v1/blog/post/3/
Payload de exemplo:
{
"status": "published",
"comment_set": [
{
"id": 5,
"text": "Comentário atualizado"
},
{
"text": "Novo comentário"
}
]
}Resposta de exemplo:
{
"id": 3,
"title": "Novo Post",
"content": "Este é um novo post",
"status": "published",
"author": 1,
"created_at": "2023-07-10T08:45:12Z",
"_meta": {
"model": "post",
"app": "blog"
}
}Atualiza completamente um objeto existente, substituindo todos os campos.
Endpoint:
PUT /api/v1/<app>/<model>/<pk>/
Exemplo de requisição:
PUT /api/v1/blog/post/3/
Payload de exemplo:
{
"title": "Título Completamente Novo",
"content": "Conteúdo totalmente atualizado",
"status": "published",
"author": 1,
"comment_set": [
{
"id": 5,
"text": "Comentário atualizado"
},
{
"text": "Novo comentário"
}
]
}Resposta de exemplo:
{
"id": 3,
"title": "Título Completamente Novo",
"content": "Conteúdo totalmente atualizado",
"status": "published",
"author": 1,
"created_at": "2023-07-10T08:45:12Z",
"updated_at": "2023-07-11T10:22:45Z",
"_meta": {
"model": "post",
"app": "blog"
}
}Observação: Diferente do PATCH, o PUT requer que todos os campos obrigatórios sejam fornecidos, mesmo que não estejam sendo alterados.
Remove um objeto específico pelo seu ID.
Endpoint:
DELETE /api/v1/<app>/<model>/<pk>/
Exemplo de requisição:
DELETE /api/v1/blog/post/3/
Payload opcional para exclusão em cascata:
{
"comment_set": [
{
"id": 5
},
{
"id": 6
}
]
}Resposta: Status HTTP 204 No Content
A API suporta filtragem dinâmica usando o prefixo filter_ seguido pelo nome do campo.
Exemplos:
Filtrar posts por autor:
GET /api/v1/blog/post/?filter_author=1
Filtrar posts por status:
GET /api/v1/blog/post/?filter_status=published
Combinando múltiplos filtros:
GET /api/v1/blog/post/?filter_status=published&filter_author=1
O parâmetro group_by permite agrupar resultados por um ou mais campos, retornando contagens.
Exemplo:
GET /api/v1/blog/post/?group_by=status
Resposta de exemplo:
[
{
"status": "published",
"count": 15
},
{
"status": "draft",
"count": 7
}
]Agrupamento por múltiplos campos:
GET /api/v1/blog/post/?group_by=status,author
Resposta de exemplo:
[
{
"status": "published",
"children": [
{
"author": 1,
"count": 8
},
{
"author": 2,
"count": 7
}
]
},
{
"status": "draft",
"children": [
{
"author": 1,
"count": 3
},
{
"author": 2,
"count": 4
}
]
}
]A API suporta a criação e atualização de objetos relacionados em uma única operação.
Exemplo de criação com objetos aninhados:
{
"title": "Post com comentários",
"content": "Conteúdo...",
"author": 1,
"comment_set": [
{
"text": "Primeiro comentário",
"author_name": "João"
},
{
"text": "Segundo comentário",
"author_name": "Maria"
}
]
}Exemplo de atualização com objetos aninhados:
{
"title": "Título atualizado",
"comment_set": [
{
"id": 1,
"text": "Comentário atualizado"
},
{
"text": "Novo comentário"
}
]
}A API valida automaticamente os objetos aninhados, garantindo que:
- Todos os campos obrigatórios estejam presentes
- Os tipos de dados estejam corretos
- Os objetos relacionados referenciados existam
- As permissões sejam respeitadas
Se alguma validação falhar, a operação inteira é revertida (transação atômica) e um erro detalhado é retornado, indicando qual objeto aninhado apresentou o problema.
O parâmetro depth controla quantos níveis de objetos relacionados são incluídos na resposta.
GET /api/v1/blog/post/1/?depth=0 # Apenas o objeto principal
GET /api/v1/blog/post/1/?depth=1 # Inclui objetos diretamente relacionados
GET /api/v1/blog/post/1/?depth=2 # Inclui também os objetos relacionados aos relacionados
Exemplo com depth=0:
{
"id": 1,
"title": "Primeiro Post",
"content": "Conteúdo...",
"author": 1
}Exemplo com depth=1:
{
"id": 1,
"title": "Primeiro Post",
"content": "Conteúdo...",
"author": {
"id": 1,
"name": "João Silva",
"email": "joao@exemplo.com"
}
}Limitações de Profundidade:
- A profundidade máxima permitida é 10 (para evitar problemas de desempenho)
- Valores negativos resultarão em erro
O parâmetro fields permite selecionar apenas campos específicos para retornar na resposta.
GET /api/v1/blog/post/?fields=id,title,status # Retorna apenas id, título e status
GET /api/v1/blog/post/1/?fields=title,content,author # Retorna apenas título, conteúdo e autor
Exemplo com seleção de campos:
{
"id": 1,
"title": "Primeiro Post",
"status": "published",
"_meta": {
"model": "post",
"app": "blog",
"selected_fields": ["id", "title", "status"]
}
}A seleção de campos também funciona em combinação com outros parâmetros:
GET /api/v1/blog/post/?fields=title,author&depth=1 # Campos específicos com relacionamentos
GET /api/v1/blog/post/?fields=title,status&group_by=status # Campos específicos com agrupamento
Observações:
- O campo
idé sempre incluído, mesmo se não for explicitamente solicitado - Se um campo solicitado não existir no modelo, ele será ignorado
- Para operações de agrupamento, os campos de agrupamento são automaticamente incluídos
A API suporta exclusão em cascata controlada de objetos relacionados.
Exemplo de exclusão em cascata:
DELETE /api/v1/blog/post/1/
Payload para exclusão em cascata:
{
"comment_set": [
{
"id": 1
},
{
"id": 2
}
],
"tag_set": [] // Exclui todos os tags relacionados
}Observações:
- Um array vazio (
[]) indica que todos os objetos relacionados devem ser excluídos - Um array com objetos contendo IDs especifica quais objetos relacionados devem ser excluídos
- Se nenhum payload for fornecido, apenas o objeto principal é excluído
A API implementa um sistema abrangente de tratamento de erros, fornecendo respostas consistentes e informativas quando ocorrem problemas.
| Exceção | Código HTTP | Descrição |
|---|---|---|
| ModelNotFoundError | 404 | Modelo especificado não encontrado |
| RelatedObjectNotFoundError | 404 | Objeto relacionado não encontrado |
| InvalidRelationshipError | 400 | Relacionamento inválido especificado |
| NestedObjectError | 400 | Erro ao processar objeto aninhado |
| InvalidFilterError | 400 | Filtro inválido especificado |
| InvalidDepthError | 400 | Valor de profundidade inválido |
| OperationError | 400 | Erro genérico durante a operação |
| ValidationError | 400 | Erro de validação nos dados fornecidos |
Modelo não encontrado:
{
"message": "Modelo 'article' não encontrado no app 'blog' nem no app 'core'.",
"type": "ModelNotFoundError"
}Objeto relacionado não encontrado:
{
"message": "Não foi possível encontrar Author com ID 99 para o campo 'author'.",
"type": "RelatedObjectNotFoundError",
"details": {
"detail": "Não foi possível encontrar Author com ID 99 para o campo 'author'.",
"context": {
"model": "Author",
"id": "99",
"code": "related_object_not_found",
"field": "author"
}
}
}Erro de validação:
{
"message": "Erro de validação: title: Este campo é obrigatório.",
"type": "ValidationError",
"details": {
"title": "Este campo é obrigatório."
},
"fields": {
"title": "Este campo é obrigatório."
}
}Erro em objeto aninhado:
{
"comment_set": {
"message": "Campos obrigatórios faltando em comment: text",
"path": "comment_set[0]",
"context": {
"path": "comment_set[0]",
"relation_field": "comment_set",
"code": "nested_object_error",
"index": 0,
"missing_fields": ["text"]
}
}
}Filtro inválido:
{
"filter": "Filtro inválido 'invalid_field': O campo não existe no modelo Post.",
"type": "InvalidFilterError"
}Profundidade inválida:
{
"depth": "Valor de profundidade inválido 'xyz'. Deve ser um número inteiro válido.",
"type": "InvalidDepthError"
}Criar um autor:
POST /api/v1/blog/author/
{
"name": "João Silva",
"email": "joao@exemplo.com",
"bio": "Escritor de tecnologia"
}Resposta:
{
"id": 1,
"name": "João Silva",
"email": "joao@exemplo.com",
"bio": "Escritor de tecnologia",
"created_at": "2023-07-01T14:30:00Z",
"_meta": {
"model": "author",
"app": "blog"
}
}Recuperar o autor:
GET /api/v1/blog/author/1/
Atualizar o autor:
PATCH /api/v1/blog/author/1/
{
"bio": "Escritor de tecnologia e entusiasta de IA"
}Excluir o autor:
DELETE /api/v1/blog/author/1/
Criar um post com comentários:
POST /api/v1/blog/post/
{
"title": "API Dinâmica",
"content": "Como criar uma API dinâmica com Django",
"status": "published",
"author": 2,
"tag_set": [
{ "name": "django" },
{ "name": "api" },
{ "name": "python" }
],
"comment_set": [
{
"text": "Excelente tutorial!",
"author_name": "Leitor"
}
]
}Atualizar um post e seus comentários:
PATCH /api/v1/blog/post/1/
{
"status": "featured",
"comment_set": [
{
"id": 1,
"text": "Comentário atualizado"
},
{
"text": "Novo comentário"
}
]
}Filtrar posts publicados:
GET /api/v1/blog/post/?filter_status=published
Agrupar posts por status:
GET /api/v1/blog/post/?group_by=status
Filtrar e agrupar:
GET /api/v1/blog/post/?filter_author=2&group_by=status
Relacionamentos profundos:
GET /api/v1/blog/post/1/?depth=2
Resposta:
{
"id": 1,
"title": "API Dinâmica",
"content": "Como criar uma API dinâmica com Django",
"status": "featured",
"author": {
"id": 2,
"name": "Maria Souza",
"email": "maria@exemplo.com",
"bio": "Desenvolvedora Python"
},
"tag_set": [
{
"id": 1,
"name": "django",
"post": 1
},
{
"id": 2,
"name": "api",
"post": 1
},
{
"id": 3,
"name": "python",
"post": 1
}
],
"comment_set": [
{
"id": 1,
"text": "Comentário atualizado",
"author_name": "Leitor",
"post": 1
},
{
"id": 2,
"text": "Novo comentário",
"post": 1
}
],
"_meta": {
"model": "post",
"app": "blog"
}
}Selecionar campos específicos:
GET /api/v1/blog/post/?fields=title,status,created_at
Resposta:
[
{
"id": 1,
"title": "API Dinâmica",
"status": "featured",
"created_at": "2023-07-01T14:30:00Z",
"_meta": {
"model": "post",
"app": "blog",
"selected_fields": ["id", "title", "status", "created_at"]
}
},
{
"id": 2,
"title": "Segundo Post",
"status": "published",
"created_at": "2023-07-05T09:15:00Z",
"_meta": {
"model": "post",
"app": "blog",
"selected_fields": ["id", "title", "status", "created_at"]
}
}
]Combinar seleção de campos com profundidade:
GET /api/v1/blog/post/1/?fields=title,author,comment_set&depth=1
Resposta:
{
"id": 1,
"title": "API Dinâmica",
"author": {
"id": 2,
"name": "Maria Souza",
"email": "maria@exemplo.com"
},
"comment_set": [
{
"id": 1,
"text": "Comentário atualizado",
"author_name": "Leitor"
},
{
"id": 2,
"text": "Novo comentário"
}
],
"_meta": {
"model": "post",
"app": "blog",
"selected_fields": ["id", "title", "author", "comment_set"]
}
}Selecionar campos em uma operação de agrupamento:
GET /api/v1/blog/post/?fields=status,author&group_by=status
Resposta:
[
{
"status": "published",
"count": 15
},
{
"status": "draft",
"count": 7
}
]Note que ao usar seleção de campos com agrupamento, os campos de agrupamento têm prioridade e sempre serão incluídos na resposta.
Excluir um post e alguns comentários específicos:
DELETE /api/v1/blog/post/1/
Payload:
{
"comment_set": [
{ "id": 1 },
{ "id": 3 }
]
}Excluir um post e todos os seus comentários:
DELETE /api/v1/blog/post/2/
Payload:
{
"comment_set": []
}A API inclui documentação interativa através do Swagger UI e ReDoc. Se a documentação estiver habilitada nas configurações (API_DOCS_ENABLED = True), os seguintes endpoints estarão disponíveis:
- Swagger UI:
/api/v1/swagger/ - ReDoc:
/api/v1/redoc/ - Arquivo JSON da API:
/api/v1/docs.json - Arquivo YAML da API:
/api/v1/docs.yaml
A documentação interativa permite:
- Visualizar todos os endpoints disponíveis
- Testar chamadas diretamente da interface
- Ver modelos de dados e exemplos de requisição/resposta
- Verificar parâmetros e opções de cada endpoint
A documentação da API é atualizada automaticamente e reflete a estrutura atual da API, incluindo modelos dinâmicos e endpoints.