Coverage for api\serializers\user_serializer.py: 95.1%
126 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-30 21:28 +0200
« prev ^ index » next coverage.py v7.15.2, created at 2026-08-30 21:28 +0200
1from rest_framework import serializers
2from users.models import *
3from api.serializers.base import AuditFieldsMixin
4from users.services.auth_service import EMAIL_DOMAIN_ERROR_MESSAGE, AuthService
5from users.services.exceptions import (EmailAlreadyRegisteredError, EmailDomainNotAllowedError, InvalidUsernameError,
6 UsernameAlreadyTakenError)
7from api.exceptions import CustomAPIException
8from api.errors import ErrorCodes
9import os
10import logging
12logger = logging.getLogger(__name__)
14# Serializer para el modelo User
15class UserSerializer(serializers.ModelSerializer):
17 profile_picture_url = serializers.SerializerMethodField()
19 class Meta:
20 model = Users
21 # Se restringen los campos para evitar mostrar informacion sensible como la contraseña
22 fields = [
23 "id",
24 "username",
25 "email",
26 "name",
27 "surname1",
28 "surname2",
29 "user_type",
30 "profile_picture_url",
31 "has_2FA"
32 ]
33 # EL email y el 2FA deben ser de solo lectura ya que si no, con un patch se podria cambiar el email y robar la cuenta
34 read_only_fields = ["email", "has_2FA"]
35 extra_kwargs = {
36 'username': {'validators': []},
37 'email': {'validators': []},
38 }
40 # Validacion pra asegurarse de que el nombre de usuario y el correo electronico sean unicos, ignorando el propio usuario en caso de actualización
41 def validate(self, attrs):
42 username = attrs.get('username')
43 email = attrs.get('email')
45 if username:
46 try:
47 AuthService.assert_username_is_acceptable(username)
48 except InvalidUsernameError:
49 raise CustomAPIException(
50 code=ErrorCodes.INVALID_USERNAME,
51 message=USERNAME_ERROR_MESSAGE,
52 status_code=400
53 )
55 if username and AuthService.username_is_taken(username, excluding=self.instance):
56 raise CustomAPIException(
57 code=ErrorCodes.USERNAME_ALREADY_EXISTS,
58 message="El nombre de usuario ya esta en uso",
59 status_code=409
60 )
62 if email and AuthService.email_is_taken(email, excluding=self.instance): 62 ↛ 63line 62 didn't jump to line 63 because the condition on line 62 was never true
63 raise CustomAPIException(
64 code=ErrorCodes.EMAIL_ALREADY_EXISTS,
65 message="El correo electrónico ya esta en uso",
66 status_code=409
67 )
69 return attrs
72 def get_profile_picture_url(self, obj):
73 if obj.profile_picture: 73 ↛ 74line 73 didn't jump to line 74 because the condition on line 73 was never true
74 filename = os.path.basename(obj.profile_picture.name)
75 return f"/api/v1/media/profile_pics/{filename}"
76 return None
78# Vista publica de un usuario, la que se manda cuando aparece dentro de otra cosa
79class PublicUserSerializer(UserSerializer):
80 class Meta(UserSerializer.Meta):
81 fields = [
82 "id",
83 "username",
84 "user_type",
85 "profile_picture_url",
86 ]
87 read_only_fields = []
88 extra_kwargs = {}
90# Serializer para el resgistro de usuarios, incluye validacion de la contraseña y su confirmacion, que le email y nombre de usuario sean
91# unicos, y que este validado
92class UserRegistrationSerializer(serializers.ModelSerializer):
93 password = serializers.CharField(write_only=True, required=True, min_length=6)
94 password_confirm = serializers.CharField(write_only=True, required=True, min_length=6)
96 class Meta:
97 model = Users
99 fields = [
100 "username",
101 "email",
102 "password",
103 "password_confirm",
104 "name",
105 "surname1",
106 "surname2",
107 "user_type"
108 ]
109 extra_kwargs = {
110 'username': {'validators': []},
111 'email': {'validators': []},
113 }
115 def validate(self, attrs):
116 # La contraseña y la confirmacin deben ser la misma
117 if attrs['password'] != attrs['password_confirm']:
118 logger.warning("Password and confirmation password are not the same")
119 raise CustomAPIException(
120 code=ErrorCodes.PASSWORD_MISMATCH,
121 message="La contraseña y la confirmación no coinciden"
122 )
123 AuthService.assert_password_is_acceptable(attrs['password'])
125 try:
126 AuthService.prepare_registration(attrs['username'], attrs['email'])
127 except InvalidUsernameError:
128 # El nombre de usuario no cumple el formato admitido
129 raise CustomAPIException(
130 code=ErrorCodes.INVALID_USERNAME,
131 message=USERNAME_ERROR_MESSAGE,
132 status_code=400
133 )
134 except EmailDomainNotAllowedError:
135 # El correo no es de un dominio admitido
136 raise CustomAPIException(
137 code=ErrorCodes.EMAIL_DOMAIN_NOT_ALLOWED,
138 message=EMAIL_DOMAIN_ERROR_MESSAGE,
139 status_code=400
140 )
141 except UsernameAlreadyTakenError:
142 raise CustomAPIException(
143 code=ErrorCodes.USERNAME_ALREADY_EXISTS,
144 message="El nombre de usuario ya esta en uso",
145 status_code=409
146 )
147 except EmailAlreadyRegisteredError:
148 raise CustomAPIException(
149 code=ErrorCodes.EMAIL_ALREADY_EXISTS,
150 message="El correo electrónico ya esta en uso",
151 status_code=409
152 )
154 return attrs
156 def create(self, validated_data):
157 validated_data.pop('password_confirm')
158 password = validated_data.pop('password')
160 user = Users(**validated_data)
161 user.set_password(password)
162 user.save()
163 return user
165# Serializer para la foto de perfil
166class ProfilePicSerializer(serializers.ModelSerializer):
167 class Meta:
168 model = Users
169 # Añade el campo de foto de perfil, que contiene la ruta de la imagen, que es necesario proporcionar
170 fields = ["profile_picture"]
171 extra_kwargs = {
172 'profile_picture': {'required': True}
173 }
175# Serializer para el modelo UserType, que contiene los tipos de usuario disponibles
176class UserTypeSerializer(AuditFieldsMixin, serializers.ModelSerializer):
177 class Meta:
178 model = UserType
179 # Se incluyen todos los campos ya que no hay informacion sensible
180 fields = ("__all__")
182# Serializer para el modelo Notifications, que contiene las notificaciones de los usuarios
183class NotificationsSerializer(AuditFieldsMixin, serializers.ModelSerializer):
184 class Meta:
185 model = Notifications
186 # Se incluyen todos los campos ya que no hay informacion sensible
187 fields = "__all__"
189# Serializer para el modelo Device, que contiene los dispositivos registrados para notificaciones push
190class DeviceSerializer(AuditFieldsMixin, serializers.ModelSerializer):
191 class Meta:
192 model = Device
193 fields = "__all__"
194 read_only_fields = ("id_user",)
196# Serializer para el modelo PrefTypes, que contiene los tipos de preferencias disponibles
197class PrefTypesSerializer(AuditFieldsMixin, serializers.ModelSerializer):
198 class Meta:
199 model = PrefTypes
200 # Se incluyen todos los campos ya que no hay informacion sensible
201 fields = "__all__"
203# Serializer para el modelo Preferences, que contiene las preferencias de los usuarios
204class PreferencesSerializer(AuditFieldsMixin, serializers.ModelSerializer):
205 class Meta:
206 model = Preferences
207 # Se incluyen todos los campos ya que no hay informacion sensible
208 fields = "__all__"
210# Serializer para el modelo Criteria, que contiene los criterios de valoración de los conductores
211class CriteriaSerializer(AuditFieldsMixin, serializers.ModelSerializer):
212 class Meta:
213 model = Criteria
214 # Se incluyen todos los campos ya que no hay informacion sensible
215 fields = "__all__"
217# Serializer para el modelo DriverRatings, que contiene las valoraciones de los conductores por parte de los usuarios
218class DriverRatingsSerializer(AuditFieldsMixin, serializers.ModelSerializer):
219 class Meta:
220 model = DriverRatings
221 # Se incluyen todos los campos ya que no hay informacion sensible
222 fields = "__all__"
224# Serializer para el modelo EnvTypes, que contiene los tipos de distintivos ambientales disponibles
225class EnvTypesSerializer(AuditFieldsMixin, serializers.ModelSerializer):
226 class Meta:
227 model = EnvTypes
228 # Se incluyen todos los campos ya que no hay informacion sensible
229 fields = "__all__"
231# Serializer para el modelo Vehicles, que contiene los vehículos de los usuarios, se valida que no haya dos vehiculos con la misma matricula
232class VehicleSerializer(serializers.ModelSerializer):
233 class Meta:
234 model = Vehicles
235 fields = [
236 "id_vehicle",
237 "brand",
238 "model",
239 "license_plate",
240 "color",
241 "seats",
242 "env_sticker",
243 "id_user"
244 ]
245 extra_kwargs = {
246 'license_plate': {'validators': []}
247 }
250 def validate_license_plate(self, value):
251 """
252 Valida que la matricula no este en uso por otro vehiculo, no se tiene en cuenta el propio vehiculo si se esta actualizando.
253 """
254 logger.info(f"Checking license_plate: {value}")
255 request = self.context.get('request')
257 # Si no hay request o no es un metodo de creacion/modificacion, devolvemos el valor tal cual
258 if not request or request.method not in ['POST', 'PUT', 'PATCH']:
259 return value
261 # Se busca un vehiculo con la misma matricula
262 queryset = Vehicles.objects.filter(license_plate=value, is_deleted=False)
264 # Se excluye el propio vehiculo
265 if self.instance:
266 queryset = queryset.exclude(pk=self.instance.pk)
268 # Si existe, es que hay un vehiculo con la misma matricula por lo que se lanza excepcion
269 if queryset.exists():
270 logger.warning(f"License_plate: {value} already in use")
271 raise CustomAPIException(
272 code=ErrorCodes.LICENSE_PLATE_ALREADY_EXISTS,
273 message="License plate is already associated with another vehicle",
274 status_code=409
275 )
276 logger.info(f"License_plate: {value} is not used")
277 return value