models.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. import logging
  2. from typing import Dict
  3. from uuid import uuid4
  4. from django.conf import settings
  5. from django.db import models
  6. from django.urls import reverse
  7. from django.utils.translation import gettext_lazy as _
  8. from django_extensions.db.models import TimeStampedModel
  9. from scrobbles.mixins import ScrobblableMixin
  10. from sports.utils import get_players_from_event, get_round_name_from_event
  11. logger = logging.getLogger(__name__)
  12. BNULL = {"blank": True, "null": True}
  13. class SportEventType(models.TextChoices):
  14. UNKNOWN = 'UK', _('Event')
  15. GAME = 'GA', _('Game')
  16. RACE = 'RA', _('Race')
  17. MATCH = 'MA', _('Match')
  18. class TheSportsDbMixin(TimeStampedModel):
  19. name = models.CharField(max_length=255)
  20. uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
  21. thesportsdb_id = models.CharField(max_length=255, **BNULL)
  22. class Meta:
  23. abstract = True
  24. def __str__(self):
  25. return self.name
  26. class Sport(TheSportsDbMixin):
  27. default_event_run_time = models.IntegerField(**BNULL)
  28. default_event_type = models.CharField(
  29. max_length=2,
  30. choices=SportEventType.choices,
  31. default=SportEventType.UNKNOWN,
  32. )
  33. # TODO Add these to the default run_time for Football
  34. # run_time_seconds = 11700
  35. # run_time_ticks = run_time_seconds * 1000
  36. @property
  37. def default_event_run_time_ticks(self):
  38. default_run_time = getattr(
  39. settings, 'DEFAULT_EVENT_RUNTIME_SECONDS', 14400
  40. )
  41. if self.default_event_run_time:
  42. default_run_time = self.default_event_run_time
  43. return default_run_time * 1000
  44. class League(TheSportsDbMixin):
  45. logo = models.ImageField(upload_to="sports/league-logos/", **BNULL)
  46. abbreviation_str = models.CharField(max_length=10, **BNULL)
  47. sport = models.ForeignKey(Sport, on_delete=models.DO_NOTHING, **BNULL)
  48. @property
  49. def abbreviation(self):
  50. return self.abbreviation_str
  51. class Season(TheSportsDbMixin):
  52. league = models.ForeignKey(League, on_delete=models.DO_NOTHING, **BNULL)
  53. def __str__(self):
  54. return f'{self.name} season of {self.league}'
  55. class Team(TheSportsDbMixin):
  56. league = models.ForeignKey(League, on_delete=models.DO_NOTHING, **BNULL)
  57. class Player(TheSportsDbMixin):
  58. league = models.ForeignKey(League, on_delete=models.DO_NOTHING, **BNULL)
  59. team = models.ForeignKey(Team, on_delete=models.DO_NOTHING, **BNULL)
  60. class Round(TheSportsDbMixin):
  61. season = models.ForeignKey(Season, on_delete=models.DO_NOTHING, **BNULL)
  62. def __str__(self):
  63. return f'{self.name} of {self.season}'
  64. class SportEvent(ScrobblableMixin):
  65. COMPLETION_PERCENT = getattr(settings, 'SPORT_COMPLETION_PERCENT', 90)
  66. thesportsdb_id = models.CharField(max_length=255, **BNULL)
  67. event_type = models.CharField(
  68. max_length=2,
  69. choices=SportEventType.choices,
  70. default=SportEventType.UNKNOWN,
  71. )
  72. round = models.ForeignKey(Round, on_delete=models.DO_NOTHING, **BNULL)
  73. start = models.DateTimeField(**BNULL)
  74. home_team = models.ForeignKey(
  75. Team,
  76. on_delete=models.DO_NOTHING,
  77. related_name='home_event_set',
  78. **BNULL,
  79. )
  80. away_team = models.ForeignKey(
  81. Team,
  82. on_delete=models.DO_NOTHING,
  83. related_name='away_event_set',
  84. **BNULL,
  85. )
  86. player_one = models.ForeignKey(
  87. Player,
  88. on_delete=models.DO_NOTHING,
  89. related_name='player_one_set',
  90. **BNULL,
  91. )
  92. player_two = models.ForeignKey(
  93. Player,
  94. on_delete=models.DO_NOTHING,
  95. related_name='player_two_set',
  96. **BNULL,
  97. )
  98. def __str__(self):
  99. return f"{self.start.date()} - {self.round} - {self.home_team} v {self.away_team}"
  100. def get_absolute_url(self):
  101. return reverse("sports:event_detail", kwargs={'slug': self.uuid})
  102. @property
  103. def subtitle(self):
  104. return self.round.season.league
  105. @property
  106. def sportsdb_link(self):
  107. return f"https://thesportsdb.com/event/{self.thesportsdb_id}"
  108. @property
  109. def info_link(self):
  110. return self.sportsdb_link
  111. @classmethod
  112. def find_or_create(cls, data_dict: Dict) -> "Event":
  113. """Given a data dict from Jellyfin, does the heavy lifting of looking up
  114. the video and, if need, TV Series, creating both if they don't yet
  115. exist.
  116. """
  117. # Find or create our Sport
  118. sid = data_dict.get("Sport")
  119. sport, s_created = Sport.objects.get_or_create(thesportsdb_id=sid)
  120. if s_created:
  121. sport.name = sid
  122. sport.save(update_fields=['name'])
  123. # Find or create our League
  124. lid = data_dict.get("LeagueId")
  125. league, l_created = League.objects.get_or_create(
  126. thesportsdb_id=lid, sport=sport
  127. )
  128. if l_created:
  129. league.sport = sport
  130. league.name = data_dict.get("LeagueName", "")
  131. league.save(update_fields=['sport', 'name'])
  132. # Find or create our Season
  133. seid = data_dict.get('Season')
  134. season, se_created = Season.objects.get_or_create(
  135. thesportsdb_id=seid, league=league
  136. )
  137. if se_created:
  138. season.name = seid
  139. season.save(update_fields=['name'])
  140. # Find or create our Round
  141. rid = data_dict.get('RoundId')
  142. round, r_created = Round.objects.get_or_create(
  143. thesportsdb_id=rid, season=season
  144. )
  145. if r_created:
  146. round.season = season
  147. round.save(update_fields=['season'])
  148. # Set some special data for Tennis
  149. player_one = None
  150. player_two = None
  151. if data_dict.get('Sport') == 'Tennis':
  152. event_name = data_dict.get('Name', '')
  153. if not round.name:
  154. round.name = get_round_name_from_event(event_name)
  155. round.save(update_fields=['name'])
  156. players_list = get_players_from_event(event_name)
  157. player_one = Player.objects.filter(
  158. name__icontains=players_list[0]
  159. ).first()
  160. if not player_one:
  161. player_one = Player.objects.create(name=players_list[0])
  162. player_two = Player.objects.filter(
  163. name__icontains=players_list[1]
  164. ).first()
  165. if not player_two:
  166. player_two = Player.objects.create(name=players_list[1])
  167. home_team = None
  168. away_team = None
  169. if data_dict.get("HomeTeamName"):
  170. home_team_dict = {
  171. "name": data_dict.get("HomeTeamName", ""),
  172. "thesportsdb_id": data_dict.get("HomeTeamId", ""),
  173. "league": league,
  174. }
  175. home_team, _created = Team.objects.get_or_create(**home_team_dict)
  176. away_team_dict = {
  177. "name": data_dict.get("AwayTeamName", ""),
  178. "thesportsdb_id": data_dict.get("AwayTeamId", ""),
  179. "league": league,
  180. }
  181. away_team, _created = Team.objects.get_or_create(**away_team_dict)
  182. event_dict = {
  183. "thesportsdb_id": data_dict.get("EventId"),
  184. "title": data_dict.get("Name"),
  185. "event_type": sport.default_event_type,
  186. "home_team": home_team,
  187. "away_team": away_team,
  188. "player_one": player_one,
  189. "player_two": player_two,
  190. "start": data_dict['Start'],
  191. "round": round,
  192. "run_time_ticks": data_dict.get("RunTimeTicks"),
  193. "run_time": data_dict.get("RunTime", ""),
  194. }
  195. event, _created = cls.objects.get_or_create(**event_dict)
  196. return event