Tutorial Django Experience 2022
Doc: Serializers
Basicamente, serialização é quando você transforma uma estrutura de dados num formato que possa ser transmitido pela rede e reconstruído posteriormente no mesmo ou em outro ambiente.
Ou seja, transforma objetos Python (JSON) em string.
A desserialização transforma a string em objeto Python novamente.
Continuando com nossa app movie
, temos:
from rest_framework import serializers
from backend.movie.models import Movie
class MovieSerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
title = serializers.CharField(max_length=30)
sinopse = serializers.CharField(max_length=255)
rating = serializers.IntegerField()
like = serializers.BooleanField(default=False)
Abra o shell
do Django.
python manage.py shell_plus
Primeiro vamos criar um filme.
from backend.movie.api.serializers import MovieSerializer
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from backend.movie.models import Movie
movie = Movie(
title='Matrix',
sinopse='Filme de ficção',
rating=5,
like=True
)
Agora podemos ver a serialização da última instância.
In [5]: serializer = MovieSerializer(movie)
In [6]: serializer.data
Out[6]: {'title': 'Matrix', 'sinopse': 'Filme de ficção', 'rating': 5, 'like': True}
Neste ponto nós traduzimos a instância do modelo em tipos de dados nativos do Python. Para finalizar o processo de serialização nós vamos renderizar os dados em uma string no formato json
.
In [7]: content = JSONRenderer().render(serializer.data)
In [8]: content
Out[8]: b'{"title":"Matrix","sinopse":"Filme de fic\xc3\xa7\xc3\xa3o","rating":5,"like":true}'
A desserialização é similar.
import io
from rest_framework.parsers import JSONParser
# content foi definido acima
stream = io.BytesIO(content)
data = JSONParser().parse(stream)
serializer = MovieSerializer(data=data)
In [11]: serializer.is_valid()
Out[11]: True
In [12]: serializer.validated_data
Out[12]:
OrderedDict([('title', 'Matrix'),
('sinopse', 'Filme de ficção'),
('rating', 5),
('like', True)])
class MovieSerializer(serializers.Serializer):
...
def create(self, validated_data):
"""
Create and return a new `Movie` instance, given the validated data.
Cria e retorna uma nova instância `Movie`, de acordo com os dados validados.
:param validated_data:
"""
return Movie.objects.create(**validated_data)
def update(self, instance, validated_data):
"""
Update and return an existing `Movie` instance, given the validated data.
Atualiza e retorna uma instância `Movie` existente, de acordo com os dados validados.
"""
instance.title = validated_data.get('title', instance.title)
instance.sinopse = validated_data.get('sinopse', instance.sinopse)
instance.rating = validated_data.get('rating', instance.rating)
instance.like = validated_data.get('like', instance.like)
instance.save()
return instance
Agora podemos fazer
python manage.py shell_plus
Agora podemos chamar o método save()
para retornar a instância do objeto, baseado no validated_data
.
movie = serializer.save()
E ainda:
from backend.movie.api.serializers import MovieSerializer
from backend.movie.models import Movie
data = {'title': 'Vingadores Ultimato', 'sinopse': 'Filme de super-heróis', 'rating': 5, 'like': True}
serializer = MovieSerializer(data=data)
serializer.data
Erro
AssertionError: When a serializer is passed a `data` keyword argument you must call `.is_valid()` before attempting to access the serialized `.data` representation.
You should either call `.is_valid()` first, or access `.initial_data` instead.
serializer.is_valid()
serializer.data
# agora tente salvar
serializer.save()
Erro
AssertionError: You cannot call `.save()` after accessing `serializer.data`. If you need to access data before committing to the database then inspect 'serializer.validated_data' instead.
from backend.movie.api.serializers import MovieSerializer
from backend.movie.models import Movie
data = {'title': 'Vingadores Ultimato', 'sinopse': 'Filme de super-heróis', 'rating': 5, 'like': True}
serializer = MovieSerializer(data=data)
# AssertionError: You must call `.is_valid()` before calling `.save()`.
serializer.is_valid()
serializer.save()
Agora vamos atualizar os dados.
from backend.movie.api.serializers import MovieSerializer
from backend.movie.models import Movie
movie = Movie.objects.get(title='Vingadores Ultimato')
data = {'sinopse': 'Após Thanos eliminar metade das criaturas vivas, os Vingadores têm de lidar com a perda de amigos e entes queridos. Com Tony Stark vagando perdido no espaço sem água e comida, Steve Rogers e Natasha Romanov lideram a resistência contra o titã louco.'}
serializer = MovieSerializer(movie, data=data)
serializer.is_valid() # False
serializer.errors
Out[11]: {'title': [ErrorDetail(string='Este campo é obrigatório.', code='required')], 'rating': [ErrorDetail(string='Este campo é obrigatório.', code='required')]}
serializer = MovieSerializer(movie, data=data, partial=True)
serializer.is_valid() # True
serializer.save()
Doc: Dealing with nested objects
Acrescente uma chave estrangeira no models Movie
:
class Movie(models.Model):
...
category = models.ForeignKey(
'Category',
on_delete=models.SET_NULL,
verbose_name='categoria',
related_name='movies',
null=True,
blank=True
)
from backend.movie.api.serializers import MovieSerializer
from backend.movie.models import Movie
movie = Movie.objects.last()
serializer = MovieSerializer(movie)
serializer.data
python manage.py shell_plus
from backend.movie.api.serializers import MovieSerializer
from backend.movie.models import Movie
data={'category': {'title': 'Ação'}, 'title': 'Vingadores Guerra Infinita', 'sinopse': 'Filme de super-heróis', 'rating': 5, 'like': True}
serializer = MovieSerializer(data=data)
serializer.is_valid()
serializer.save()
Erro
ValueError: Cannot assign "OrderedDict([('title', 'Ação')])": "Movie.category" must be a "Category" instance.
Então vamos editar o serializers.py
.
class CategorySerializer(serializers.Serializer):
id = serializers.IntegerField(read_only=True)
title = serializers.CharField(max_length=30)
class MovieSerializer(serializers.Serializer):
...
category = CategorySerializer()
def create(self, validated_data):
"""
Create and return a new `Movie` instance, given the validated data.
Cria e retorna uma nova instância `Movie`, de acordo com os dados validados.
:param validated_data:
"""
category_data = {}
if 'category' in validated_data:
category_data = validated_data.pop('category')
if category_data:
category = Category.objects.create(**category_data)
movie = Movie.objects.create(category=category, **validated_data)
else:
movie = Movie.objects.create(**validated_data)
return movie
Agora podemos tentar novamente
python manage.py shell_plus
from backend.movie.api.serializers import MovieSerializer
from backend.movie.models import Movie
data={'category': {'title': 'Ação'}, 'title': 'Vingadores Guerra Infinita', 'sinopse': 'Filme de super-heróis', 'rating': 5, 'like': True}
serializer = MovieSerializer(data=data)
serializer.is_valid()
serializer.save()
Agora vamos editar o update
.
# serializers.py
class MovieSerializer(serializers.Serializer):
...
def update(self, instance, validated_data):
"""
Update and return an existing `Movie` instance, given the validated data.
Atualiza e retorna uma instância `Movie` existente, de acordo com os dados validados.
"""
if 'category' in validated_data:
category_data = validated_data.pop('category')
title = category_data.get('title')
category, _ = Category.objects.get_or_create(title=title)
# Atualiza a categoria
instance.category = category
# Atualiza a instância
instance.title = validated_data.get('title', instance.title)
instance.sinopse = validated_data.get('sinopse', instance.sinopse)
instance.rating = validated_data.get('rating', instance.rating)
instance.like = validated_data.get('like', instance.like)
instance.save()
return instance
Agora podemos editar
python manage.py shell_plus
from backend.movie.api.serializers import MovieSerializer
from backend.movie.models import Movie
movie = Movie.objects.get(title='Vingadores Guerra Infinita')
data={'category': {'title': 'Ação'}, 'title': 'Vingadores Guerra Infinita', 'sinopse': 'Filme de super-heróis', 'rating': 6, 'like': True}
serializer = MovieSerializer(movie, data=data, partial=True)
serializer.is_valid()
serializer.save()
A esse ponto nós já esquecemos do jeito mais simples, então vamos escrever o create()
e o update()
de CategorySerializer
.
# serializers.py
class CategorySerializer(serializers.Serializer):
title = serializers.CharField(max_length=30)
def create(self, validated_data):
"""
Create and return a new `Category` instance, given the validated data.
Cria e retorna uma nova instância `Category`, de acordo com os dados validados.
:param validated_data:
"""
return Category.objects.create(**validated_data)
def update(self, instance, validated_data):
"""
Update and return an existing `Category` instance, given the validated data.
Atualiza e retorna uma instância `Category` existente, de acordo com os dados validados.
"""
instance.title = validated_data.get('title', instance.title)
instance.save()
return instance
from backend.movie.api.serializers import MovieSerializer
from backend.movie.models import Movie
queryset = Movie.objects.all()
serializer = MovieSerializer(queryset, many=True)
serializer.data
from rest_framework.renderers import JSONRenderer
from pprint import pprint
json = JSONRenderer().render(serializer.data)
pprint(json)
Mais dois exemplos:
from backend.movie.api.serializers import MovieSerializer
from backend.movie.models import Movie
movie = Movie.objects.get(title='Matrix')
data={'rating': 5}
serializer = MovieSerializer(movie, data=data, partial=True)
serializer.is_valid()
serializer.save()
serializer.data
from backend.movie.api.serializers import MovieSerializer
from backend.movie.models import Movie
movie = Movie.objects.get(title='Matrix')
data={'category': {'title': 'Drama'}, 'rating': 2}
serializer = MovieSerializer(movie, data=data, partial=True)
serializer.is_valid()
serializer.save()
serializer.data
Doc: ModelSerializer
# movie/serializers.py
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('id', 'title')
>>> from backend.movie.api.serializers import CategorySerializer
>>> serializer = CategorySerializer()
>>> print(repr(serializer))
CategorySerializer():
id = IntegerField(label='ID', read_only=True)
title = CharField(max_length=30, validators=[<UniqueValidator(queryset=Category.objects.all())>])
Rode os testes.
# movie/serializers.py
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = ('id', 'title', 'sinopse', 'rating', 'like', 'category')
python manage.py shell_plus
from backend.movie.api.serializers import MovieSerializer
from backend.movie.models import Movie
In [2]: data={'category': {'title': 'Ação'}, 'title': 'Vingadores Guerra Infinita', 'sinopse': 'Filme de super-heróis', '
...: rating': 5, 'like': True}
...:
In [3]: serializer = MovieSerializer(data=data)
...:
In [4]: serializer.is_valid()
...:
Out[4]: False
In [5]: serializer.errors
Out[5]: {'category': [ErrorDetail(string='Tipo incorreto. Esperado valor pk, recebeu dict.', code='incorrect_type')]}
Então acrescente depth = 1
em MovieSerializer
.
# movie/serializers.py
class MovieSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
fields = ('id', 'title', 'sinopse', 'rating', 'like', 'category')
depth = 1
python manage.py shell_plus
from backend.movie.api.serializers import MovieSerializer
from backend.movie.models import Movie
data={'category': {'title': 'Ação'}, 'title': 'Vingadores Guerra Infinita', 'sinopse': 'Filme de super-heróis', 'rating': 5, 'like': True}
serializer = MovieSerializer(data=data)
serializer.is_valid()
serializer.save()
serializer.data
Só que ele não salvou a categoria. Então
# movie/serializers.py
class MovieSerializer(serializers.ModelSerializer):
category = CategorySerializer(required=False)
class Meta:
model = Movie
fields = ('id', 'title', 'sinopse', 'rating', 'like', 'category')
def create(self, validated_data):
"""
Create and return a new `Movie` instance, given the validated data.
Cria e retorna uma nova instância `Movie`, de acordo com os dados validados.
:param validated_data:
"""
category_data = {}
if 'category' in validated_data:
category_data = validated_data.pop('category')
if category_data:
category, _ = Category.objects.get_or_create(**category_data)
movie = Movie.objects.create(category=category, **validated_data)
else:
movie = Movie.objects.create(**validated_data)
return movie
def update(self, instance, validated_data):
"""
Update and return an existing `Movie` instance, given the validated data.
Atualiza e retorna uma instância `Movie` existente, de acordo com os dados validados.
"""
if 'category' in validated_data:
category_data = validated_data.pop('category')
title = category_data.get('title')
category, _ = Category.objects.get_or_create(title=title)
# Atualiza a categoria
instance.category = category
# Atualiza a instância
instance.title = validated_data.get('title', instance.title)
instance.sinopse = validated_data.get('sinopse', instance.sinopse)
instance.rating = validated_data.get('rating', instance.rating)
instance.like = validated_data.get('like', instance.like)
instance.save()
return instance
python manage.py shell_plus
from backend.movie.api.serializers import MovieSerializer
from backend.movie.models import Movie
data={'category': {'title': 'Ação'}, 'title': 'Matrix Reloaded', 'sinopse': 'Filme de ação', 'rating': 5, 'like': True}
serializer = MovieSerializer(data=data)
serializer.is_valid()
# serializer.errors
serializer.save()
serializer.data
Rode os testes.
Doc: https://www.django-rest-framework.org/api-guide/serializers/#listserializer
Vamos criar uma nova app chamado school
python manage.py dr_scaffold school Student \
registration:charfield \
first_name:charfield \
last_name:charfield
python manage.py dr_scaffold school Classroom \
title:charfield \
students:ManyToMany:Student
# school/models.py
from django.db import models
class Student(models.Model):
registration = models.CharField(max_length=7)
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
def __str__(self):
return f"{self.first_name} {self.last_name}"
class Meta:
verbose_name = "Aluno"
verbose_name_plural = "Alunos"
class Classroom(models.Model):
title = models.CharField(max_length=30)
students = models.ManyToManyField(Student)
def __str__(self):
return f"{self.title}"
class Meta:
verbose_name = "Sala de aula"
verbose_name_plural = "Salas de aula"
# school/serializers.py
from rest_framework import serializers
from backend.school.models import Classroom, Student
class StudentSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = '__all__'
class ClassroomSerializer(serializers.ModelSerializer):
students = serializers.ListSerializer(child=StudentSerializer())
class Meta:
model = Classroom
fields = '__all__'
Ou simplesmente many=True
.
class ClassroomSerializer(serializers.ModelSerializer):
students = StudentSerializer(many=True)
class Meta:
model = Classroom
fields = '__all__'
Ou simplesmente depth = 1
.
class ClassroomSerializer(serializers.ModelSerializer):
class Meta:
model = Classroom
fields = '__all__'
depth = 1
Doc: https://www.django-rest-framework.org/api-guide/serializers/#baseserializer
# school/serializers.py
class StudentRegistrationSerializer(serializers.BaseSerializer):
class Meta:
model = Student
def to_representation(self, instance):
return {
'registration': instance.registration.zfill(7),
'full_name': instance.__str__()
}
# school/views.py
from rest_framework.decorators import action
from rest_framework.response import Response
class StudentViewSet(viewsets.ModelViewSet):
queryset = Student.objects.all()
serializer_class = StudentSerializer
@action(detail=False, methods=['get'])
def all_students(self, request, pk=None):
queryset = Student.objects.all()
serializer = StudentRegistrationSerializer(queryset, many=True)
return Response(serializer.data)