Es increíble que un proyecto tan simple como éste, tenga tantas posibilidades de probar cosas técnicas. De hecho, lo elegí justamente con eso: menos vueltas del lado del negocio, más jugo que le podemos sacar a nivel técnico.
Por supuesto, esto aplica a mi proyecto de una forma muy particular: hay otros casos donde el negocio implica desafíos técnicos, y esto está muy bien, pero yo me conformo con lo que estamos haciendo ahora.
Antes de continuar
Haciendo unas pruebas, dado que estoy utilizando la última versión de DRF, cuando hice un GET a "api/colors" me devolvió lo siguiente:
Creating a ModelSerializer without either the 'fields' attribute or the 'exclude' attribute has been deprecated since 3.3.0, and is now disallowed. Add an explicit fields = '__all__' to the NestedSerializer serializer.
Para resolver esto, tal como dice el error, agregué "fields = 'all'" a mis serializadores:
class ColorSerializer(ModelSerializer):
class Meta:
model = Color
fields = '__all__'
class VoteSerializer(ModelSerializer):
class Meta:
model = Vote
fields = '__all__'
depth = 1
Seguimos con la transmisión habitual 😊
Métodos del ModelViewSet
Para poder modificar a VotesViewSet, necesitamos entender cómo cambiar el comportamiento de los métodos de su padre, ModelViewSet:
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
"""
A viewset that provides default `create()`, `retrieve()`, `update()`,
`partial_update()`, `destroy()` and `list()` actions.
"""
pass
De esta parte nos interesan los comentarios:
- create(): crea un nuevo voto.
- retrieve(): trae un voto por su id.
- update() / partial_update(): modifica un voto total o parcialmente.
- destroy(): elimina un voto.
- list(): trae todos los votos.
Esto se parece los endpoints que vimos hace un par de artículos, justamente porque son estos quieren apuntan a dichos métodos.
Generar un Vote
Siguiendo el flujo, ahora deberíamos poder crear un voto nuevo basado en el usuario de la sesión. Primero, restringimos el acceso a los endpoints de Vote mediante Auth Token:
class VoteViewSet(viewsets.ModelViewSet):
(...)
authentication_classes = [TokenAuthentication]
Ahora cualquier método relacionado con Vote, va a requerir del token que generamos anteriormente. Una vez lo tenemos, lo agregamos como header Auth:
Un Vote tiene asociado un Color dentro, por lo cual debemos especificarlo en VoteSerializer:
class VoteSerializer(ModelSerializer):
color = ColorSerializer(many=False, read_only=True)
Luego debemos redefinir nuestro método create de VoteViewSet:
def create(self, request, *args, **kwargs):
serializer = VoteSerializer(data=request.data)
if serializer.is_valid():
color = Color(**request.data['color'])
serializer.save(user=request.user, color=color)
return Response({
"success": True,
"message": "Voto emitido exitosamente.",
"vote": serializer.data
}, status=status.HTTP_201_CREATED)
else:
return Response({
"success": False,
"message": "Error al emitir Voto!",
"vote": serializer.errors
}, status=status.HTTP_400_BAD_REQUEST)
Pueden notar que en request recibo data y user. El atributo user se genera como un objeto interno que ya resolvió nuestro autenticador con token internamente.
En el caso de data, debemos enviarlo por JSON Body de la siguiente manera:
{
"color": {
"id": 1,
"name": "azul",
"hexa": "#360FFF"
}
}
El objeto color que estamos indicando lo podemos obtener mediante el endpoint "GET api/colors".
Otro detalle importante: para obtener y llevar el campo id, debemos declararlo en nuestro serializador:
class ColorSerializer(ModelSerializer):
id = IntegerField()
Una vez emitimos el voto, obtendremos una respuesta como esta:
{
"success": true,
"message": "Voto emitido exitosamente.",
"vote": {
"id": 1,
"color": {
"id": 1,
"name": "azul",
"hexa": "#360FFF"
},
"user": {
"id": 1,
"password": "pbkdf2_sha256$600000$MTt324mR10bsmpz5WVycFV$B0Lj6QFYWgUKI2txcsxdnu4sW9T0ZSU/OB67JThJfdk=",
"last_login": "2023-04-12T14:13:56.147422Z",
"is_superuser": true,
"username": "admin",
"first_name": "",
"last_name": "",
"email": "",
"is_staff": true,
"is_active": true,
"date_joined": "2023-04-12T14:13:42.051901Z",
"groups": [],
"user_permissions": []
}
}
}
Serializador para User
La respuesta anterior es correcta, pero nos está devolviendo demasiada información del usuario; incluso la sensible. Necesitamos arreglar esto mediante su propio serializer:
class UserSerializer(ModelSerializer):
class Meta:
model = User
fields = ['username', 'first_name', 'last_name']
También debemos incluirlo en VoteSerializer:
user = UserSerializer(many=False, read_only=True)
Ahora, si emitimos un voto (eliminé el anterior por django-admin, recuerden que solo se puede emitir uno por usuario), nos devolverá esto:
{
"success": true,
"message": "Voto emitido exitosamente.",
"vote": {
"id": 2,
"color": {
"id": 1,
"name": "azul",
"hexa": "#360FFF"
},
"user": {
"username": "admin",
"first_name": "",
"last_name": ""
}
}
}
Conclusiones
No se va a notar en el artículo, pero estuve peleando muy fuerte con los NestedSerializers. Estos son los serializadores que contienen otros, como pasa en el caso de Color dentro de Vote.
No obstante, con la posibilidad de votar implementada, ahora solo queda validar que dichos votos existan para un determinado usuario.
Pero eso será, queridos lectores, en el siguiente capítulo. 😁
Top comments (0)