models.py
1 import datetime 2 3 from dirtyfields import DirtyFieldsMixin 4 from django.db import models 5 from django_prometheus.models import ExportModelOperationsMixin 6 from model_utils.models import TimeStampedModel 7 from picklefield.fields import PickledObjectField 8 9 10 class Driver(ExportModelOperationsMixin("driver"), TimeStampedModel): 11 class Meta: 12 ordering = [ 13 "name", 14 ] 15 16 name = models.CharField(max_length=200, unique=True) 17 18 def __str__(self): 19 return self.name 20 21 22 class Game(TimeStampedModel): 23 class Meta: 24 ordering = [ 25 "name", 26 ] 27 28 name = models.CharField(max_length=200, unique=True) 29 30 def __str__(self): 31 return self.name 32 33 34 class Track(TimeStampedModel): 35 class Meta: 36 ordering = [ 37 "name", 38 ] 39 40 name = models.CharField(max_length=200) 41 length = models.IntegerField(default=0) 42 43 game = models.ForeignKey(Game, on_delete=models.CASCADE, related_name="tracks") 44 45 def __str__(self): 46 return self.name 47 48 49 class Car(TimeStampedModel): 50 class Meta: 51 ordering = [ 52 "name", 53 ] 54 55 name = models.CharField(max_length=200) 56 57 game = models.ForeignKey(Game, on_delete=models.CASCADE, related_name="cars") 58 car_class = models.ForeignKey("CarClass", on_delete=models.CASCADE, related_name="cars", null=True) 59 60 def __str__(self): 61 return self.name 62 63 64 class CarClass(TimeStampedModel): 65 class Meta: 66 ordering = [ 67 "name", 68 ] 69 70 name = models.CharField(max_length=200) 71 72 game = models.ForeignKey(Game, on_delete=models.CASCADE, related_name="car_classes") 73 74 def __str__(self): 75 return self.name 76 77 78 class SessionType(TimeStampedModel): 79 type = models.CharField(max_length=200, unique=True) 80 81 def __str__(self): 82 return self.type 83 84 85 class Session(ExportModelOperationsMixin("session"), DirtyFieldsMixin, TimeStampedModel): 86 session_id = models.CharField(max_length=200) 87 start = models.DateTimeField(default=datetime.datetime.now) 88 end = models.DateTimeField(default=datetime.datetime.now) 89 90 driver = models.ForeignKey(Driver, on_delete=models.CASCADE, related_name="sessions") 91 session_type = models.ForeignKey(SessionType, on_delete=models.CASCADE, related_name="sessions") 92 game = models.ForeignKey(Game, on_delete=models.CASCADE, related_name="sessions") 93 94 class Meta: 95 unique_together = ( 96 "driver", 97 "session_id", 98 "session_type", 99 "game", 100 ) 101 102 def __str__(self): 103 return self.session_id 104 105 106 class FastLap(ExportModelOperationsMixin("fastlap"), TimeStampedModel): 107 game = models.ForeignKey(Game, on_delete=models.CASCADE, related_name="fast_laps") 108 car = models.ForeignKey(Car, on_delete=models.CASCADE, related_name="fast_laps") 109 track = models.ForeignKey(Track, on_delete=models.CASCADE, related_name="fast_laps") 110 driver = models.ForeignKey(Driver, on_delete=models.CASCADE, related_name="fast_laps", null=True) 111 # add binary field to hold arbitrary data 112 data = PickledObjectField(null=True) 113 114 class Meta: 115 ordering = ["game", "car", "track"] 116 117 def __str__(self): 118 return f"{self.game} {self.car} {self.track}" 119 120 121 class Lap(ExportModelOperationsMixin("lap"), DirtyFieldsMixin, TimeStampedModel): 122 number = models.IntegerField() 123 start = models.DateTimeField(default=datetime.datetime.now) 124 end = models.DateTimeField(default=datetime.datetime.now) 125 time = models.FloatField(default=0) 126 length = models.IntegerField(default=0) 127 valid = models.BooleanField(default=False) 128 129 session = models.ForeignKey(Session, on_delete=models.CASCADE, related_name="laps") 130 track = models.ForeignKey(Track, on_delete=models.CASCADE, related_name="laps") 131 car = models.ForeignKey(Car, on_delete=models.CASCADE, related_name="laps") 132 fast_lap = models.ForeignKey(FastLap, on_delete=models.CASCADE, related_name="laps", null=True) 133 134 class Meta: 135 ordering = [ 136 "number", 137 ] 138 unique_together = ("session", "start") 139 140 def __str__(self): 141 return ( 142 f"{self.number}: {self.start.strftime('%H:%M:%S')} - {self.end.strftime('%H:%M:%S')} " 143 + f"{self.time}s {self.length}m valid: {self.valid}" 144 ) 145 146 def time_human(self): 147 minutes = int(self.time // 60) 148 seconds = round(self.time % 60, 2) 149 # milliseconds = int((coach_lap_time % 1) * 1000) 150 time_string = "" 151 if minutes > 1: 152 time_string += f"{minutes} minutes " 153 elif minutes == 1: 154 time_string += f"{minutes} minute " 155 156 time_string += f"{seconds:.2f} seconds " 157 158 return time_string 159 160 161 class FastLapSegment(TimeStampedModel): 162 turn = models.CharField(max_length=200) 163 start = models.IntegerField(default=0) 164 end = models.IntegerField(default=0) 165 brake = models.IntegerField(default=0) 166 turn_in = models.IntegerField(default=0) 167 force = models.IntegerField(default=0) 168 gear = models.IntegerField(default=0) 169 # stop is the time the brake force starts to decrease 170 stop = models.IntegerField(default=0) 171 accelerate = models.IntegerField(default=0) 172 speed = models.IntegerField(default=0) 173 mark = models.CharField(max_length=256, default="") 174 175 fast_lap = models.ForeignKey(FastLap, on_delete=models.CASCADE, related_name="fast_lap_segments") 176 177 def __str__(self): 178 repr = f"{self.turn}: {self.start} - {self.end} brake: {self.brake} " 179 repr += f"turn_in: {self.turn_in} force: {self.force} gear: {self.gear} stop: {self.stop} " 180 repr += f"acc: {self.accelerate} speed: {self.speed} mark: {self.mark}" 181 return repr 182 183 184 class Coach(ExportModelOperationsMixin("coach"), TimeStampedModel): 185 driver = models.OneToOneField( 186 Driver, 187 on_delete=models.CASCADE, 188 primary_key=True, 189 ) 190 191 error = models.TextField(default="") 192 status = models.TextField(default="") 193 enabled = models.BooleanField(default=False) 194 195 fast_lap = models.ForeignKey(FastLap, on_delete=models.SET_NULL, related_name="coaches", null=True) 196 197 MODE_DEFAULT = "default" 198 MODE_TRACK_GUIDE = "track_guide" 199 MODE_TRACK_GUIDE_APP = "track_guide_app" 200 MODE_DEBUG_APP = "debug_app" 201 MODE_DEBUG = "debug" 202 MODE_ONLY_BRAKE = "only_brake" 203 MODE_ONLY_BRAKE_DEBUG = "only_brake_debug" 204 MODE_COPILOTS = "copilots" 205 MODE_CHOICES = [ 206 (MODE_DEFAULT, "Default"), 207 (MODE_COPILOTS, "Copilots"), 208 (MODE_TRACK_GUIDE, "Track Guide"), 209 (MODE_TRACK_GUIDE_APP, "Track Guide Application"), 210 (MODE_DEBUG_APP, "Debug Application"), 211 (MODE_DEBUG, "Debug"), 212 (MODE_ONLY_BRAKE, "Only Brakepoints"), 213 (MODE_ONLY_BRAKE_DEBUG, "Only Brakepoints (Debug))"), 214 ] 215 mode = models.CharField(max_length=64, default=MODE_DEFAULT, choices=MODE_CHOICES) 216 217 def __str__(self): 218 return self.driver.name 219 220 221 class Landmark(TimeStampedModel): 222 name = models.CharField(max_length=200) 223 start = models.IntegerField(null=True) 224 end = models.IntegerField(null=True) 225 is_overtaking_spot = models.BooleanField(null=True) 226 from_cc = models.BooleanField(default=False) 227 228 KIND_MISC = "misc" 229 KIND_SEGMENT = "segment" 230 KIND_TURN = "turn" 231 KIND_CHOICES = [ 232 (KIND_MISC, "Misc"), 233 (KIND_SEGMENT, "Segment"), 234 (KIND_TURN, "Turn"), 235 ] 236 237 kind = models.CharField(max_length=64, default=KIND_MISC, choices=KIND_CHOICES) 238 239 track = models.ForeignKey(Track, on_delete=models.CASCADE, related_name="landmarks") 240 241 def __str__(self): 242 return self.name 243 244 245 class TrackGuide(TimeStampedModel): 246 name = models.CharField(max_length=200) 247 description = models.TextField(default="") 248 car = models.ForeignKey(Car, on_delete=models.CASCADE) 249 track = models.ForeignKey(Track, on_delete=models.CASCADE) 250 251 def car_game(self): 252 return self.car.game 253 254 def __str__(self): 255 return f"{self.name}" 256 257 258 class TrackGuideNote(TimeStampedModel): 259 track_guide = models.ForeignKey(TrackGuide, on_delete=models.CASCADE, related_name="notes") 260 261 landmark = models.ForeignKey(Landmark, on_delete=models.CASCADE, related_name="notes", null=True) 262 segment = models.IntegerField(default=0) 263 finish_at = models.TextField(null=True) 264 at = models.CharField(max_length=64, default="") 265 266 priority = models.IntegerField(default=0) 267 ref_id = models.CharField(max_length=64, default="") 268 ref_eval = models.CharField(max_length=64, default="") 269 270 sort_key = models.CharField(max_length=64, default="") 271 mode = models.CharField(max_length=64, default="") 272 273 message = models.TextField(default="") 274 eval = models.TextField(default="") 275 notes = models.TextField(default="") 276 score = models.CharField(max_length=64, default="") 277 278 def __str__(self): 279 repr = f"{self.segment or self.landmark} - at {self.at or self.finish_at}: {self.message}" 280 return repr