models.py 7.2 KB

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