Dario de Python (18 Part Series)
1 Diario de Python | #1. Bajada a tierra
2 Diario de Python | #2. Mi propio plan Full Stack
… 14 more parts…
3 Diario de Python | #3. Pycharm, el mejor IDE para Python
4 Diario de Python | #4. Probando FastAPI para desarrollar APIs
5 Diario de Python | #5. Proyecto: Color Choice
6 Diario de Python | #6. Primeros pasos con Color Choice
7 Diario de Python | #7. Color Choice: armando las rutas
8 Diario de Python | #8. Issuu PDF Downloader
9 Diario de Python | #9. Color Choice: Volantazo
10 Diario de Python | #10. Color Choice: Los colores
11 Diario de Python | #11. Color Choice: Flujo de la API
12 Diario de Python | #12. Color Choice: Autenticación por Token
13 Diario de Python | #13. Color Choice: emitir votos
14 Diario de Python | #14. Color Choice: día productivo
15 Diario de Python | #15. Combinando Django y React
16 Diario de Python | #16. Patrón Builder
17 Diario de Python | #17. un paseo por Flake8
18 Diario de Python | #18. Un curso de Automatizar cosas aburridas
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.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.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.
Enter fullscreen mode Exit fullscreen mode
Para resolver esto, tal como dice el error, agregué “fields = ‘all‘” a mis serializadores:
<span>class</span> <span>ColorSerializer</span><span>(</span><span>ModelSerializer</span><span>):</span><span>class</span> <span>Meta</span><span>:</span><span>model</span> <span>=</span> <span>Color</span><span>fields</span> <span>=</span> <span>'</span><span>__all__</span><span>'</span><span>class</span> <span>VoteSerializer</span><span>(</span><span>ModelSerializer</span><span>):</span><span>class</span> <span>Meta</span><span>:</span><span>model</span> <span>=</span> <span>Vote</span><span>fields</span> <span>=</span> <span>'</span><span>__all__</span><span>'</span><span>depth</span> <span>=</span> <span>1</span><span>class</span> <span>ColorSerializer</span><span>(</span><span>ModelSerializer</span><span>):</span> <span>class</span> <span>Meta</span><span>:</span> <span>model</span> <span>=</span> <span>Color</span> <span>fields</span> <span>=</span> <span>'</span><span>__all__</span><span>'</span> <span>class</span> <span>VoteSerializer</span><span>(</span><span>ModelSerializer</span><span>):</span> <span>class</span> <span>Meta</span><span>:</span> <span>model</span> <span>=</span> <span>Vote</span> <span>fields</span> <span>=</span> <span>'</span><span>__all__</span><span>'</span> <span>depth</span> <span>=</span> <span>1</span>class ColorSerializer(ModelSerializer): class Meta: model = Color fields = '__all__' class VoteSerializer(ModelSerializer): class Meta: model = Vote fields = '__all__' depth = 1
Enter fullscreen mode Exit fullscreen mode
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:
<span>class</span> <span>ModelViewSet</span><span>(</span><span>mixins</span><span>.</span><span>CreateModelMixin</span><span>,</span><span>mixins</span><span>.</span><span>RetrieveModelMixin</span><span>,</span><span>mixins</span><span>.</span><span>UpdateModelMixin</span><span>,</span><span>mixins</span><span>.</span><span>DestroyModelMixin</span><span>,</span><span>mixins</span><span>.</span><span>ListModelMixin</span><span>,</span><span>GenericViewSet</span><span>):</span><span>"""</span><span> A viewset that provides default `create()`, `retrieve()`, `update()`, `partial_update()`, `destroy()` and `list()` actions. </span><span>"""</span><span>pass</span><span>class</span> <span>ModelViewSet</span><span>(</span><span>mixins</span><span>.</span><span>CreateModelMixin</span><span>,</span> <span>mixins</span><span>.</span><span>RetrieveModelMixin</span><span>,</span> <span>mixins</span><span>.</span><span>UpdateModelMixin</span><span>,</span> <span>mixins</span><span>.</span><span>DestroyModelMixin</span><span>,</span> <span>mixins</span><span>.</span><span>ListModelMixin</span><span>,</span> <span>GenericViewSet</span><span>):</span> <span>"""</span><span> A viewset that provides default `create()`, `retrieve()`, `update()`, `partial_update()`, `destroy()` and `list()` actions. </span><span>"""</span> <span>pass</span>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
Enter fullscreen mode Exit fullscreen mode
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:
<span>class</span> <span>VoteViewSet</span><span>(</span><span>viewsets</span><span>.</span><span>ModelViewSet</span><span>):</span><span>(...)</span><span>authentication_classes</span> <span>=</span> <span>[</span><span>TokenAuthentication</span><span>]</span><span>class</span> <span>VoteViewSet</span><span>(</span><span>viewsets</span><span>.</span><span>ModelViewSet</span><span>):</span> <span>(...)</span> <span>authentication_classes</span> <span>=</span> <span>[</span><span>TokenAuthentication</span><span>]</span>class VoteViewSet(viewsets.ModelViewSet): (...) authentication_classes = [TokenAuthentication]
Enter fullscreen mode Exit fullscreen mode
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:
<span>class</span> <span>VoteSerializer</span><span>(</span><span>ModelSerializer</span><span>):</span><span>color</span> <span>=</span> <span>ColorSerializer</span><span>(</span><span>many</span><span>=</span><span>False</span><span>,</span> <span>read_only</span><span>=</span><span>True</span><span>)</span><span>class</span> <span>VoteSerializer</span><span>(</span><span>ModelSerializer</span><span>):</span> <span>color</span> <span>=</span> <span>ColorSerializer</span><span>(</span><span>many</span><span>=</span><span>False</span><span>,</span> <span>read_only</span><span>=</span><span>True</span><span>)</span>class VoteSerializer(ModelSerializer): color = ColorSerializer(many=False, read_only=True)
Enter fullscreen mode Exit fullscreen mode
Luego debemos redefinir nuestro método create de VoteViewSet:
<span>def</span> <span>create</span><span>(</span><span>self</span><span>,</span> <span>request</span><span>,</span> <span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span><span>serializer</span> <span>=</span> <span>VoteSerializer</span><span>(</span><span>data</span><span>=</span><span>request</span><span>.</span><span>data</span><span>)</span><span>if</span> <span>serializer</span><span>.</span><span>is_valid</span><span>():</span><span>color</span> <span>=</span> <span>Color</span><span>(</span><span>**</span><span>request</span><span>.</span><span>data</span><span>[</span><span>'</span><span>color</span><span>'</span><span>])</span><span>serializer</span><span>.</span><span>save</span><span>(</span><span>user</span><span>=</span><span>request</span><span>.</span><span>user</span><span>,</span> <span>color</span><span>=</span><span>color</span><span>)</span><span>return</span> <span>Response</span><span>({</span><span>"</span><span>success</span><span>"</span><span>:</span> <span>True</span><span>,</span><span>"</span><span>message</span><span>"</span><span>:</span> <span>"</span><span>Voto emitido exitosamente.</span><span>"</span><span>,</span><span>"</span><span>vote</span><span>"</span><span>:</span> <span>serializer</span><span>.</span><span>data</span><span>},</span> <span>status</span><span>=</span><span>status</span><span>.</span><span>HTTP_201_CREATED</span><span>)</span><span>else</span><span>:</span><span>return</span> <span>Response</span><span>({</span><span>"</span><span>success</span><span>"</span><span>:</span> <span>False</span><span>,</span><span>"</span><span>message</span><span>"</span><span>:</span> <span>"</span><span>Error al emitir Voto!</span><span>"</span><span>,</span><span>"</span><span>vote</span><span>"</span><span>:</span> <span>serializer</span><span>.</span><span>errors</span><span>},</span> <span>status</span><span>=</span><span>status</span><span>.</span><span>HTTP_400_BAD_REQUEST</span><span>)</span><span>def</span> <span>create</span><span>(</span><span>self</span><span>,</span> <span>request</span><span>,</span> <span>*</span><span>args</span><span>,</span> <span>**</span><span>kwargs</span><span>):</span> <span>serializer</span> <span>=</span> <span>VoteSerializer</span><span>(</span><span>data</span><span>=</span><span>request</span><span>.</span><span>data</span><span>)</span> <span>if</span> <span>serializer</span><span>.</span><span>is_valid</span><span>():</span> <span>color</span> <span>=</span> <span>Color</span><span>(</span><span>**</span><span>request</span><span>.</span><span>data</span><span>[</span><span>'</span><span>color</span><span>'</span><span>])</span> <span>serializer</span><span>.</span><span>save</span><span>(</span><span>user</span><span>=</span><span>request</span><span>.</span><span>user</span><span>,</span> <span>color</span><span>=</span><span>color</span><span>)</span> <span>return</span> <span>Response</span><span>({</span> <span>"</span><span>success</span><span>"</span><span>:</span> <span>True</span><span>,</span> <span>"</span><span>message</span><span>"</span><span>:</span> <span>"</span><span>Voto emitido exitosamente.</span><span>"</span><span>,</span> <span>"</span><span>vote</span><span>"</span><span>:</span> <span>serializer</span><span>.</span><span>data</span> <span>},</span> <span>status</span><span>=</span><span>status</span><span>.</span><span>HTTP_201_CREATED</span><span>)</span> <span>else</span><span>:</span> <span>return</span> <span>Response</span><span>({</span> <span>"</span><span>success</span><span>"</span><span>:</span> <span>False</span><span>,</span> <span>"</span><span>message</span><span>"</span><span>:</span> <span>"</span><span>Error al emitir Voto!</span><span>"</span><span>,</span> <span>"</span><span>vote</span><span>"</span><span>:</span> <span>serializer</span><span>.</span><span>errors</span> <span>},</span> <span>status</span><span>=</span><span>status</span><span>.</span><span>HTTP_400_BAD_REQUEST</span><span>)</span>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)
Enter fullscreen mode Exit fullscreen mode
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:
<span>{</span><span> </span><span>"color"</span><span>:</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span> </span><span>1</span><span>,</span><span> </span><span>"name"</span><span>:</span><span> </span><span>"azul"</span><span>,</span><span> </span><span>"hexa"</span><span>:</span><span> </span><span>"#360FFF"</span><span> </span><span>}</span><span> </span><span>}</span><span> </span><span>{</span><span> </span><span>"color"</span><span>:</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span> </span><span>1</span><span>,</span><span> </span><span>"name"</span><span>:</span><span> </span><span>"azul"</span><span>,</span><span> </span><span>"hexa"</span><span>:</span><span> </span><span>"#360FFF"</span><span> </span><span>}</span><span> </span><span>}</span><span> </span>{ "color": { "id": 1, "name": "azul", "hexa": "#360FFF" } }
Enter fullscreen mode Exit fullscreen mode
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:
<span>class</span> <span>ColorSerializer</span><span>(</span><span>ModelSerializer</span><span>):</span><span>id</span> <span>=</span> <span>IntegerField</span><span>()</span><span>class</span> <span>ColorSerializer</span><span>(</span><span>ModelSerializer</span><span>):</span> <span>id</span> <span>=</span> <span>IntegerField</span><span>()</span>class ColorSerializer(ModelSerializer): id = IntegerField()
Enter fullscreen mode Exit fullscreen mode
Una vez emitimos el voto, obtendremos una respuesta como esta:
<span>{</span><span> </span><span>"success"</span><span>:</span><span> </span><span>true</span><span>,</span><span> </span><span>"message"</span><span>:</span><span> </span><span>"Voto emitido exitosamente."</span><span>,</span><span> </span><span>"vote"</span><span>:</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span> </span><span>1</span><span>,</span><span> </span><span>"color"</span><span>:</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span> </span><span>1</span><span>,</span><span> </span><span>"name"</span><span>:</span><span> </span><span>"azul"</span><span>,</span><span> </span><span>"hexa"</span><span>:</span><span> </span><span>"#360FFF"</span><span> </span><span>},</span><span> </span><span>"user"</span><span>:</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span> </span><span>1</span><span>,</span><span> </span><span>"password"</span><span>:</span><span> </span><span>"pbkdf2_sha256$600000$MTt324mR10bsmpz5WVycFV$B0Lj6QFYWgUKI2txcsxdnu4sW9T0ZSU/OB67JThJfdk="</span><span>,</span><span> </span><span>"last_login"</span><span>:</span><span> </span><span>"2023-04-12T14:13:56.147422Z"</span><span>,</span><span> </span><span>"is_superuser"</span><span>:</span><span> </span><span>true</span><span>,</span><span> </span><span>"username"</span><span>:</span><span> </span><span>"admin"</span><span>,</span><span> </span><span>"first_name"</span><span>:</span><span> </span><span>""</span><span>,</span><span> </span><span>"last_name"</span><span>:</span><span> </span><span>""</span><span>,</span><span> </span><span>"email"</span><span>:</span><span> </span><span>""</span><span>,</span><span> </span><span>"is_staff"</span><span>:</span><span> </span><span>true</span><span>,</span><span> </span><span>"is_active"</span><span>:</span><span> </span><span>true</span><span>,</span><span> </span><span>"date_joined"</span><span>:</span><span> </span><span>"2023-04-12T14:13:42.051901Z"</span><span>,</span><span> </span><span>"groups"</span><span>:</span><span> </span><span>[],</span><span> </span><span>"user_permissions"</span><span>:</span><span> </span><span>[]</span><span> </span><span>}</span><span> </span><span>}</span><span> </span><span>}</span><span> </span><span>{</span><span> </span><span>"success"</span><span>:</span><span> </span><span>true</span><span>,</span><span> </span><span>"message"</span><span>:</span><span> </span><span>"Voto emitido exitosamente."</span><span>,</span><span> </span><span>"vote"</span><span>:</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span> </span><span>1</span><span>,</span><span> </span><span>"color"</span><span>:</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span> </span><span>1</span><span>,</span><span> </span><span>"name"</span><span>:</span><span> </span><span>"azul"</span><span>,</span><span> </span><span>"hexa"</span><span>:</span><span> </span><span>"#360FFF"</span><span> </span><span>},</span><span> </span><span>"user"</span><span>:</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span> </span><span>1</span><span>,</span><span> </span><span>"password"</span><span>:</span><span> </span><span>"pbkdf2_sha256$600000$MTt324mR10bsmpz5WVycFV$B0Lj6QFYWgUKI2txcsxdnu4sW9T0ZSU/OB67JThJfdk="</span><span>,</span><span> </span><span>"last_login"</span><span>:</span><span> </span><span>"2023-04-12T14:13:56.147422Z"</span><span>,</span><span> </span><span>"is_superuser"</span><span>:</span><span> </span><span>true</span><span>,</span><span> </span><span>"username"</span><span>:</span><span> </span><span>"admin"</span><span>,</span><span> </span><span>"first_name"</span><span>:</span><span> </span><span>""</span><span>,</span><span> </span><span>"last_name"</span><span>:</span><span> </span><span>""</span><span>,</span><span> </span><span>"email"</span><span>:</span><span> </span><span>""</span><span>,</span><span> </span><span>"is_staff"</span><span>:</span><span> </span><span>true</span><span>,</span><span> </span><span>"is_active"</span><span>:</span><span> </span><span>true</span><span>,</span><span> </span><span>"date_joined"</span><span>:</span><span> </span><span>"2023-04-12T14:13:42.051901Z"</span><span>,</span><span> </span><span>"groups"</span><span>:</span><span> </span><span>[],</span><span> </span><span>"user_permissions"</span><span>:</span><span> </span><span>[]</span><span> </span><span>}</span><span> </span><span>}</span><span> </span><span>}</span><span> </span>{ "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": [] } } }
Enter fullscreen mode Exit fullscreen mode
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:
<span>class</span> <span>UserSerializer</span><span>(</span><span>ModelSerializer</span><span>):</span><span>class</span> <span>Meta</span><span>:</span><span>model</span> <span>=</span> <span>User</span><span>fields</span> <span>=</span> <span>[</span><span>'</span><span>username</span><span>'</span><span>,</span> <span>'</span><span>first_name</span><span>'</span><span>,</span> <span>'</span><span>last_name</span><span>'</span><span>]</span><span>class</span> <span>UserSerializer</span><span>(</span><span>ModelSerializer</span><span>):</span> <span>class</span> <span>Meta</span><span>:</span> <span>model</span> <span>=</span> <span>User</span> <span>fields</span> <span>=</span> <span>[</span><span>'</span><span>username</span><span>'</span><span>,</span> <span>'</span><span>first_name</span><span>'</span><span>,</span> <span>'</span><span>last_name</span><span>'</span><span>]</span>class UserSerializer(ModelSerializer): class Meta: model = User fields = ['username', 'first_name', 'last_name']
Enter fullscreen mode Exit fullscreen mode
También debemos incluirlo en VoteSerializer:
<span>user</span> <span>=</span> <span>UserSerializer</span><span>(</span><span>many</span><span>=</span><span>False</span><span>,</span> <span>read_only</span><span>=</span><span>True</span><span>)</span><span>user</span> <span>=</span> <span>UserSerializer</span><span>(</span><span>many</span><span>=</span><span>False</span><span>,</span> <span>read_only</span><span>=</span><span>True</span><span>)</span>user = UserSerializer(many=False, read_only=True)
Enter fullscreen mode Exit fullscreen mode
Ahora, si emitimos un voto (eliminé el anterior por django-admin, recuerden que solo se puede emitir uno por usuario), nos devolverá esto:
<span>{</span><span> </span><span>"success"</span><span>:</span><span> </span><span>true</span><span>,</span><span> </span><span>"message"</span><span>:</span><span> </span><span>"Voto emitido exitosamente."</span><span>,</span><span> </span><span>"vote"</span><span>:</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span> </span><span>2</span><span>,</span><span> </span><span>"color"</span><span>:</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span> </span><span>1</span><span>,</span><span> </span><span>"name"</span><span>:</span><span> </span><span>"azul"</span><span>,</span><span> </span><span>"hexa"</span><span>:</span><span> </span><span>"#360FFF"</span><span> </span><span>},</span><span> </span><span>"user"</span><span>:</span><span> </span><span>{</span><span> </span><span>"username"</span><span>:</span><span> </span><span>"admin"</span><span>,</span><span> </span><span>"first_name"</span><span>:</span><span> </span><span>""</span><span>,</span><span> </span><span>"last_name"</span><span>:</span><span> </span><span>""</span><span> </span><span>}</span><span> </span><span>}</span><span> </span><span>}</span><span> </span><span>{</span><span> </span><span>"success"</span><span>:</span><span> </span><span>true</span><span>,</span><span> </span><span>"message"</span><span>:</span><span> </span><span>"Voto emitido exitosamente."</span><span>,</span><span> </span><span>"vote"</span><span>:</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span> </span><span>2</span><span>,</span><span> </span><span>"color"</span><span>:</span><span> </span><span>{</span><span> </span><span>"id"</span><span>:</span><span> </span><span>1</span><span>,</span><span> </span><span>"name"</span><span>:</span><span> </span><span>"azul"</span><span>,</span><span> </span><span>"hexa"</span><span>:</span><span> </span><span>"#360FFF"</span><span> </span><span>},</span><span> </span><span>"user"</span><span>:</span><span> </span><span>{</span><span> </span><span>"username"</span><span>:</span><span> </span><span>"admin"</span><span>,</span><span> </span><span>"first_name"</span><span>:</span><span> </span><span>""</span><span>,</span><span> </span><span>"last_name"</span><span>:</span><span> </span><span>""</span><span> </span><span>}</span><span> </span><span>}</span><span> </span><span>}</span><span> </span>{ "success": true, "message": "Voto emitido exitosamente.", "vote": { "id": 2, "color": { "id": 1, "name": "azul", "hexa": "#360FFF" }, "user": { "username": "admin", "first_name": "", "last_name": "" } } }
Enter fullscreen mode Exit fullscreen mode
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.
Dario de Python (18 Part Series)
1 Diario de Python | #1. Bajada a tierra
2 Diario de Python | #2. Mi propio plan Full Stack
… 14 more parts…
3 Diario de Python | #3. Pycharm, el mejor IDE para Python
4 Diario de Python | #4. Probando FastAPI para desarrollar APIs
5 Diario de Python | #5. Proyecto: Color Choice
6 Diario de Python | #6. Primeros pasos con Color Choice
7 Diario de Python | #7. Color Choice: armando las rutas
8 Diario de Python | #8. Issuu PDF Downloader
9 Diario de Python | #9. Color Choice: Volantazo
10 Diario de Python | #10. Color Choice: Los colores
11 Diario de Python | #11. Color Choice: Flujo de la API
12 Diario de Python | #12. Color Choice: Autenticación por Token
13 Diario de Python | #13. Color Choice: emitir votos
14 Diario de Python | #14. Color Choice: día productivo
15 Diario de Python | #15. Combinando Django y React
16 Diario de Python | #16. Patrón Builder
17 Diario de Python | #17. un paseo por Flake8
18 Diario de Python | #18. Un curso de Automatizar cosas aburridas
暂无评论内容