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

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 

11 

12logger = logging.getLogger(__name__) 

13 

14# Serializer para el modelo User 

15class UserSerializer(serializers.ModelSerializer): 

16 

17 profile_picture_url = serializers.SerializerMethodField() 

18 

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 } 

39 

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') 

44 

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 ) 

54 

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 ) 

61 

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 ) 

68 

69 return attrs 

70 

71 

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 

77 

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 = {} 

89 

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) 

95 

96 class Meta: 

97 model = Users 

98 

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': []}, 

112 

113 } 

114 

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']) 

124 

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 ) 

153 

154 return attrs 

155 

156 def create(self, validated_data): 

157 validated_data.pop('password_confirm') 

158 password = validated_data.pop('password') 

159 

160 user = Users(**validated_data) 

161 user.set_password(password) 

162 user.save() 

163 return user 

164 

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 } 

174 

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__") 

181 

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__" 

188 

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",) 

195 

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__" 

202 

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__" 

209 

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__" 

216 

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__" 

223 

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__" 

230 

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 } 

248 

249 

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') 

256 

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 

260 

261 # Se busca un vehiculo con la misma matricula 

262 queryset = Vehicles.objects.filter(license_plate=value, is_deleted=False) 

263 

264 # Se excluye el propio vehiculo 

265 if self.instance: 

266 queryset = queryset.exclude(pk=self.instance.pk) 

267 

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 

278