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
« 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
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 )
18 code = models.CharField(max_length=8, unique=True)
19 description = models.CharField(max_length=50)
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 ]
31 def __str__(self):
32 return self.description
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()
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 )
98 def clean(self):
99 errors = {}
101 if self.is_periodic and self.periodic_interval is None:
102 errors['periodic_interval'] = 'Periodic interval required when travel is periodic.'
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.'
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.'
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.'
113 if errors:
114 raise ValidationError(errors)
116 def save(self, *args, **kwargs):
117 self.full_clean()
118 super().save(*args, **kwargs)
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 ]
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)
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 ]
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 )
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'
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)
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 ]
211 def __str__(self):
212 return self.description
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 )
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 ]