Diario de Python | #13. Color Choice: emitir votos
Tags: devjournal, django, python, spanish
Published: 2023-04-12 17:47:38

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:

```python
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:

```python
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](https://dev.to/maxwellnewage/diario-de-python-10-color-choice-flujo-de-la-api-4lm), 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:

```python
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:

![Postman Header Auth](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7q1t59rjtgrh3ch6a0g6.png)

Un Vote tiene asociado un Color dentro, por lo cual debemos especificarlo en VoteSerializer:

```python
class VoteSerializer(ModelSerializer):
    color = ColorSerializer(many=False, read_only=True)
```

Luego debemos redefinir nuestro método create de VoteViewSet:

```python
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:

```json
{
    "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:

```python
class ColorSerializer(ModelSerializer):
    id = IntegerField()
```

Una vez emitimos el voto, obtendremos una respuesta como esta:

```json
{
    "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:

```python
class UserSerializer(ModelSerializer):
    class Meta:
        model = User
        fields = ['username', 'first_name', 'last_name']
```

También debemos incluirlo en VoteSerializer:

```python
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:

```json
{
    "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. 😁