Home
/ Blog
/ Django REST Framework 2025: Guia Definitivo de APIs REST
Django REST Framework 2025: Guia Definitivo de APIs REST
Vinícius Ramos
• Atualizado em:
Cansado(a) de quebrar a cabeça para aprender a programar Python de verdade?
Conheça a melhor e mais completa formação de Python e Django e sinta-se
um programador verdadeiramente competente. Além de Python e Django, você também vai aprender Banco de Dados, SQL,
HTML, CSS, Javascript, Bootstrap e muito mais!
Projetos práticos voltados para o mercado de trabalho
Formação moderna com foco na prática profissional
Salve salve Pythonista!
Django REST Framework (DRF) é o padrão de facto para construir APIs REST profissionais com Python e Django.
Em 2025, com a versão 3.15+, o DRF continua sendo a escolha número 1 para desenvolvedores que precisam criar APIs robustas, escaláveis e bem documentadas.
Este é o guia definitivo de Django REST Framework: um artigo pilar que cobre desde a configuração inicial até técnicas avançadas de autenticação, filtros, paginação, versionamento e testes.
Se você quer dominar a criação de APIs REST profissionais com Django, este guia é para você! 🚀
# products/serializers.py
fromrest_frameworkimportserializersfrom.modelsimportProduct,CategoryclassCategorySerializer(serializers.ModelSerializer):products_count=serializers.SerializerMethodField()classMeta:model=Categoryfields=['id','name','slug','created_at','products_count']read_only_fields=['id','created_at']defget_products_count(self,obj):returnobj.products.count()classProductSerializer(serializers.ModelSerializer):category_name=serializers.CharField(source='category.name',read_only=True)created_by_username=serializers.CharField(source='created_by.username',read_only=True)classMeta:model=Productfields=['id','name','slug','description','price','stock','category','category_name','status','created_by','created_by_username','created_at','updated_at']read_only_fields=['id','created_at','updated_at','created_by']defvalidate_price(self,value):"""Validação customizada"""ifvalue<=0:raiseserializers.ValidationError("Preço deve ser maior que zero")ifvalue>999999.99:raiseserializers.ValidationError("Preço muito alto")returnvaluedefvalidate(self,data):"""Validação de múltiplos campos"""ifdata.get('status')=='published'anddata.get('stock',0)==0:raiseserializers.ValidationError("Não é possível publicar produto sem estoque")returndata
fromrest_framework.viewsimportAPIViewfromrest_framework.responseimportResponsefromrest_frameworkimportstatusclassProductListView(APIView):"""API para listar e criar produtos"""defget(self,request):products=Product.objects.select_related('category').all()serializer=ProductSerializer(products,many=True)returnResponse(serializer.data)defpost(self,request):serializer=ProductSerializer(data=request.data)ifserializer.is_valid():serializer.save(created_by=request.user)returnResponse(serializer.data,status=status.HTTP_201_CREATED)returnResponse(serializer.errors,status=status.HTTP_400_BAD_REQUEST)
fromrest_frameworkimportviewsetsfromrest_framework.decoratorsimportactionfromrest_framework.responseimportResponseclassProductViewSet(viewsets.ModelViewSet):"""
ViewSet completo para Product
list: GET /products/
create: POST /products/
retrieve: GET /products/{id}/
update: PUT /products/{id}/
partial_update: PATCH /products/{id}/
destroy: DELETE /products/{id}/
"""queryset=Product.objects.select_related('category','created_by')serializer_class=ProductSerializerlookup_field='slug'filterset_fields=['status','category']search_fields=['name','description']ordering_fields=['price','created_at','stock']defget_serializer_class(self):"""Usar serializer diferente para detail"""ifself.action=='retrieve':returnProductDetailSerializerreturnProductSerializerdefperform_create(self,serializer):serializer.save(created_by=self.request.user)@action(detail=True,methods=['post'])defpublish(self,request,slug=None):"""Custom action: /products/{slug}/publish/"""product=self.get_object()ifproduct.stock==0:returnResponse({'error':'Produto sem estoque'},status=status.HTTP_400_BAD_REQUEST)product.status='published'product.save()serializer=self.get_serializer(product)returnResponse(serializer.data)@action(detail=False,methods=['get'])deflow_stock(self,request):"""Custom action: /products/low_stock/"""products=self.queryset.filter(stock__lt=10,status='published')serializer=self.get_serializer(products,many=True)returnResponse(serializer.data)
# products/permissions.py
fromrest_frameworkimportpermissionsclassIsOwnerOrReadOnly(permissions.BasePermission):"""Apenas dono pode editar"""defhas_object_permission(self,request,view,obj):# Leitura permitida para todos
ifrequest.methodinpermissions.SAFE_METHODS:returnTrue# Escrita apenas para dono
returnobj.created_by==request.userclassIsAdminOrReadOnly(permissions.BasePermission):"""Apenas admin pode criar/editar/deletar"""defhas_permission(self,request,view):ifrequest.methodinpermissions.SAFE_METHODS:returnTruereturnrequest.userandrequest.user.is_staff
1
2
3
4
5
# Usar nas views
classProductViewSet(viewsets.ModelViewSet):permission_classes=[IsOwnerOrReadOnly]# ou
permission_classes=[IsAdminOrReadOnly]
GET /api/products/?status=published
GET /api/products/?min_price=50&max_price=500
GET /api/products/?search=notebook
GET /api/products/?ordering=-price
GET /api/products/?category=1&status=published&ordering=price
# products/tests.py
fromrest_framework.testimportAPITestCase,APIClientfromrest_frameworkimportstatusfromdjango.contrib.auth.modelsimportUserfrom.modelsimportProduct,CategoryclassProductAPITestCase(APITestCase):defsetUp(self):"""Executado antes de cada teste"""self.client=APIClient()self.user=User.objects.create_user(username='testuser',password='testpass123')self.category=Category.objects.create(name='Eletrônicos',slug='eletronicos')self.product=Product.objects.create(name='Notebook',slug='notebook',description='Notebook de teste',price=3000.00,stock=10,category=self.category,created_by=self.user,status='published')deftest_list_products(self):"""Teste GET /api/products/"""response=self.client.get('/api/products/')self.assertEqual(response.status_code,status.HTTP_200_OK)self.assertEqual(len(response.data['results']),1)deftest_create_product_authenticated(self):"""Teste POST /api/products/ (autenticado)"""self.client.force_authenticate(user=self.user)data={'name':'Mouse','slug':'mouse','description':'Mouse gamer','price':150.00,'stock':50,'category':self.category.id,'status':'draft'}response=self.client.post('/api/products/',data,format='json')self.assertEqual(response.status_code,status.HTTP_201_CREATED)self.assertEqual(Product.objects.count(),2)deftest_create_product_unauthenticated(self):"""Teste POST sem autenticação"""data={'name':'Test'}response=self.client.post('/api/products/',data)self.assertEqual(response.status_code,status.HTTP_401_UNAUTHORIZED)deftest_update_product(self):"""Teste PATCH /api/products/{slug}/"""self.client.force_authenticate(user=self.user)data={'price':2500.00}response=self.client.patch(f'/api/products/{self.product.slug}/',data,format='json')self.assertEqual(response.status_code,status.HTTP_200_OK)self.product.refresh_from_db()self.assertEqual(float(self.product.price),2500.00)deftest_delete_product(self):"""Teste DELETE /api/products/{slug}/"""self.client.force_authenticate(user=self.user)response=self.client.delete(f'/api/products/{self.product.slug}/')self.assertEqual(response.status_code,status.HTTP_204_NO_CONTENT)self.assertEqual(Product.objects.count(),0)deftest_filter_by_status(self):"""Teste filtros"""response=self.client.get('/api/products/?status=published')self.assertEqual(response.status_code,status.HTTP_200_OK)self.assertEqual(len(response.data['results']),1)deftest_search(self):"""Teste busca"""response=self.client.get('/api/products/?search=Notebook')self.assertEqual(response.status_code,status.HTTP_200_OK)self.assertEqual(len(response.data['results']),1)
Executar Testes
1
2
3
4
5
6
7
8
9
10
11
# Todos os testes
python manage.py test# Testes específicos
python manage.py test products.tests.ProductAPITestCase
# Com coverage
pip install coverage
coverage run --source='.' manage.py test
coverage report
coverage html # Gera relatório HTML
11. Documentação Automática (OpenAPI/Swagger)
1
pip install drf-spectacular
1
2
3
4
5
6
7
8
9
10
11
12
13
# settings.py
INSTALLED_APPS+=['drf_spectacular']REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS':'drf_spectacular.openapi.AutoSchema',}SPECTACULAR_SETTINGS={'TITLE':'Products API','DESCRIPTION':'API REST para gerenciamento de produtos','VERSION':'1.0.0','SERVE_INCLUDE_SCHEMA':False,}
# settings.py
INSTALLED_APPS+=['corsheaders']MIDDLEWARE=['corsheaders.middleware.CorsMiddleware','django.middleware.common.CommonMiddleware',# ...
]# Desenvolvimento
CORS_ALLOWED_ORIGINS=["http://localhost:3000","http://localhost:8080",]# Produção (mais restritivo)
CORS_ALLOWED_ORIGINS=["https://meusite.com",]# Ou permitir tudo (NÃO recomendado em produção)
CORS_ALLOW_ALL_ORIGINS=True