Coverage for travels\models.py: 91.8%

88 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-08-30 20:58 +0200

1import uuid 

2from django.db import models 

3from api.base_models import BaseModel 

4from django.core.validators import MinValueValidator, MaxValueValidator 

5from django.core.exceptions import ValidationError 

6from users.models import Users, UserType, Vehicles 

7from django.db.models import Q 

8from django.contrib.gis.db import models as gis_models 

9 

10# Modelo de estados de un viaje 

11class TravelStates(BaseModel): 

12 id_state = models.AutoField( 

13 primary_key=True, 

14 editable=False, 

15 db_column='id_state' 

16 ) 

17 

18 code = models.CharField(max_length=8, unique=True) 

19 description = models.CharField(max_length=50) 

20 

21 class Meta: 

22 db_table = 'travel_states' 

23 constraints = [ 

24 models.UniqueConstraint( # El codigo no se peude repetir solo si no esta eliminado 

25 fields=['code'], 

26 condition=Q(is_deleted=False), 

27 name='unique_active_travel_state' 

28 ) 

29 ] 

30 

31 def __str__(self): 

32 return self.description 

33 

34# Modelo para los viajes 

35class Travel(BaseModel): 

36 id_travel = models.UUIDField( 

37 primary_key=True, 

38 default=uuid.uuid4, 

39 editable=False, 

40 db_column='id_travel' 

41 ) 

42 periodic_interval = models.SmallIntegerField( 

43 null=True, 

44 blank=True, 

45 validators=[ 

46 MinValueValidator(1), 

47 MaxValueValidator(31) 

48 ] 

49 ) 

50 is_periodic = models.BooleanField(default=False) 

51 end_periodic_date = models.DateField(null=True, blank=True) 

52 origin = models.CharField(max_length=175) 

53 destination = models.CharField(max_length=175) 

54 origin_point = gis_models.PointField(srid=4326, null=True, blank=True) 

55 destination_point = gis_models.PointField(srid=4326, null=True, blank=True) 

56 duration_minutes = models.PositiveIntegerField() 

57 # Se debe validar que el numero de asientos sea entre 1 y 10 

58 num_seats = models.SmallIntegerField( 

59 validators=[ 

60 MinValueValidator(1), 

61 MaxValueValidator(10) 

62 ] 

63 ) 

64 # Se debe validar que no haya asientos restantes negativos ni mayores que el maximo de asientos 

65 remaining_seats = models.SmallIntegerField( 

66 validators=[ 

67 MinValueValidator(0), 

68 MaxValueValidator(10) 

69 ] 

70 ) 

71 travel_date = models.DateTimeField() 

72 

73 creation_user = models.ForeignKey( 

74 'users.Users', 

75 on_delete=models.CASCADE, 

76 db_column='creation_user', 

77 related_name='created_travels' 

78 ) 

79 state = models.ForeignKey( 

80 'travels.TravelStates', 

81 to_field='code', 

82 on_delete=models.PROTECT, # No se debe borrar si travelSatates se borra 

83 db_column='state' 

84 ) 

85 id_origin_travel = models.ForeignKey( 

86 'self', 

87 on_delete=models.SET_NULL, # Si el viaje original se borra, no se borran los viajes futuros 

88 db_column='id_origin_travel', 

89 null=True, 

90 blank=True 

91 ) 

92 vehicle = models.ForeignKey( 

93 'users.Vehicles', 

94 on_delete=models.PROTECT, 

95 db_column='vehicle' 

96 ) 

97 

98 def clean(self): 

99 errors = {} 

100 

101 if self.is_periodic and self.periodic_interval is None: 

102 errors['periodic_interval'] = 'Periodic interval required when travel is periodic.' 

103 

104 if not self.is_periodic and self.periodic_interval is not None: 104 ↛ 105line 104 didn't jump to line 105 because the condition on line 104 was never true

105 errors['periodic_interval'] = 'Periodic interval must be empty if travel is not periodic.' 

106 

107 if self.remaining_seats > self.num_seats: 107 ↛ 108line 107 didn't jump to line 108 because the condition on line 107 was never true

108 errors['remaining_seats'] = 'Remaining seats cannot exceed total seats.' 

109 

110 if self.remaining_seats < 0: 110 ↛ 111line 110 didn't jump to line 111 because the condition on line 110 was never true

111 errors['remaining_seats'] = 'Remaining seats cannot be negative.' 

112 

113 if errors: 

114 raise ValidationError(errors) 

115 

116 def save(self, *args, **kwargs): 

117 self.full_clean() 

118 super().save(*args, **kwargs) 

119 

120 

121 class Meta: 

122 db_table = 'travel' 

123 indexes = [ # Se crean indices para optimizar las consultas por fecha y por origen destino 

124 models.Index(fields=['travel_date']), 

125 models.Index(fields=['origin', 'destination']), 

126 ] 

127 

128# Modelo para los puntos de recogida 

129class PickUpPoints(BaseModel): 

130 id_point = models.UUIDField( 

131 primary_key=True, 

132 default=uuid.uuid4, 

133 editable=False, 

134 db_column='id_point' 

135 ) 

136 direction = models.CharField(max_length=250) 

137 date = models.DateTimeField(null=True, blank=True) 

138 point = gis_models.PointField(srid=4326, null=True, blank=True) 

139 order_in_travel = models.SmallIntegerField( 

140 # Estabece el orden que se va a seguir dentro del viaje, no puede ser negativo ni mayor que 100 

141 validators=[ 

142 MinValueValidator(0), 

143 MaxValueValidator(100) 

144 ] 

145 ) 

146 is_reached = models.BooleanField(default=False) 

147 

148 id_travel = models.ForeignKey( 

149 'travels.Travel', 

150 on_delete=models.CASCADE, 

151 db_column='id_travel', 

152 related_name='pickup_points' 

153 ) 

154 class Meta: 

155 db_table = 'pickup_points' 

156 constraints = [ 

157 models.UniqueConstraint( # Dentro de un mismo viaje, no puede haber dos puntos con el mismo orden 

158 fields=['id_travel', 'order_in_travel'], 

159 condition=Q(is_deleted=False), 

160 name='unique_order_per_travel' 

161 ) 

162 ] 

163 indexes = [ 

164 models.Index(fields=['id_travel']) 

165 ] 

166 

167# Modelo para los usuarios denegados de un viaje 

168class UsersDenied(BaseModel): 

169 id_deny = models.UUIDField( 

170 primary_key=True, 

171 default=uuid.uuid4, 

172 editable=False, 

173 db_column='id_deny' 

174 ) 

175 

176 id_travel = models.ForeignKey( 

177 'travels.Travel', 

178 on_delete=models.CASCADE, 

179 db_column='id_travel', 

180 related_name='users_denied' 

181 ) 

182 user_type = models.ForeignKey( 

183 'users.UserType', 

184 to_field='code', 

185 on_delete=models.CASCADE, 

186 db_column='user_type' 

187 ) 

188 class Meta: 

189 db_table = 'users_denied' 

190 

191# Modelo para los estados de las solicitudes de un viaje 

192class RequestStates(BaseModel): 

193 id_state = models.AutoField( 

194 primary_key=True, 

195 editable=False, 

196 db_column='id_state' 

197 ) 

198 code = models.CharField(max_length=15, unique=True) 

199 description = models.CharField(max_length=50) 

200 

201 class Meta: 

202 db_table = 'request_states' 

203 constraints = [ 

204 models.UniqueConstraint( # El codigo no se puede repetir si no esta borrado 

205 fields=['code'], 

206 condition=Q(is_deleted=False), 

207 name='unique_active_request_state' 

208 ) 

209 ] 

210 

211 def __str__(self): 

212 return self.description 

213 

214# Modelo para las solicitudes de viajes 

215class RequestTravels(BaseModel): 

216 id = models.UUIDField( 

217 primary_key=True, 

218 default=uuid.uuid4, 

219 editable=False, 

220 db_column='id_request' 

221 ) 

222 validation_code = models.CharField(max_length=20, null=True, blank=True) 

223 id_travel = models.ForeignKey( 

224 'travels.Travel', 

225 on_delete=models.CASCADE, 

226 db_column='id_travel', 

227 related_name='requested_travels' 

228 ) 

229 user = models.ForeignKey( 

230 'users.Users', 

231 on_delete=models.CASCADE, 

232 db_column='id_user', 

233 related_name='requested_travels' 

234 ) 

235 status = models.ForeignKey( 

236 'travels.RequestStates', 

237 to_field='code', 

238 on_delete=models.PROTECT, 

239 db_column='status' 

240 ) 

241 

242 class Meta: 

243 db_table = 'request_travels' 

244 constraints = [ 

245 models.UniqueConstraint( 

246 fields=['id_travel', 'validation_code'], # El codigo es unico por viaje, no en general 

247 condition=Q(is_deleted=False) & Q(validation_code__isnull=False), 

248 name='unique_code_per_travel' 

249 ) 

250 ]