models.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  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. return self.default_event_run_time * 1000
  42. class League(TheSportsDbMixin):
  43. logo = models.ImageField(upload_to="sports/league-logos/", **BNULL)
  44. abbreviation_str = models.CharField(max_length=10, **BNULL)
  45. sport = models.ForeignKey(Sport, on_delete=models.DO_NOTHING, **BNULL)
  46. @property
  47. def abbreviation(self):
  48. return self.abbreviation_str
  49. class Season(TheSportsDbMixin):
  50. league = models.ForeignKey(League, on_delete=models.DO_NOTHING, **BNULL)
  51. def __str__(self):
  52. return f'{self.name} season of {self.league}'
  53. class Team(TheSportsDbMixin):
  54. league = models.ForeignKey(League, on_delete=models.DO_NOTHING, **BNULL)
  55. class Player(TheSportsDbMixin):
  56. league = models.ForeignKey(League, on_delete=models.DO_NOTHING, **BNULL)
  57. team = models.ForeignKey(Team, on_delete=models.DO_NOTHING, **BNULL)
  58. class Round(TheSportsDbMixin):
  59. season = models.ForeignKey(Season, on_delete=models.DO_NOTHING, **BNULL)
  60. def __str__(self):
  61. return f'{self.name} of {self.season}'
  62. class SportEvent(ScrobblableMixin):
  63. COMPLETION_PERCENT = getattr(settings, 'SPORT_COMPLETION_PERCENT', 90)
  64. event_type = models.CharField(
  65. max_length=2,
  66. choices=SportEventType.choices,
  67. default=SportEventType.UNKNOWN,
  68. )
  69. round = models.ForeignKey(Round, on_delete=models.DO_NOTHING, **BNULL)
  70. start = models.DateTimeField(**BNULL)
  71. home_team = models.ForeignKey(
  72. Team,
  73. on_delete=models.DO_NOTHING,
  74. related_name='home_event_set',
  75. **BNULL,
  76. )
  77. away_team = models.ForeignKey(
  78. Team,
  79. on_delete=models.DO_NOTHING,
  80. related_name='away_event_set',
  81. **BNULL,
  82. )
  83. player_one = models.ForeignKey(
  84. Player,
  85. on_delete=models.DO_NOTHING,
  86. related_name='player_one_set',
  87. **BNULL,
  88. )
  89. player_two = models.ForeignKey(
  90. Player,
  91. on_delete=models.DO_NOTHING,
  92. related_name='player_two_set',
  93. **BNULL,
  94. )
  95. def __str__(self):
  96. return f"{self.start.date()} - {self.round} - {self.home_team} v {self.away_team}"
  97. def get_absolute_url(self):
  98. return reverse("sports:event_detail", kwargs={'slug': self.uuid})
  99. @classmethod
  100. def find_or_create(cls, data_dict: Dict) -> "Event":
  101. """Given a data dict from Jellyfin, does the heavy lifting of looking up
  102. the video and, if need, TV Series, creating both if they don't yet
  103. exist.
  104. """
  105. # Find or create our Sport
  106. sid = data_dict.get("Sport")
  107. sport, s_created = Sport.objects.get_or_create(thesportsdb_id=sid)
  108. if s_created:
  109. sport.name = sid
  110. sport.save(update_fields=['name'])
  111. # Find or create our League
  112. lid = data_dict.get("LeagueId")
  113. league, l_created = League.objects.get_or_create(
  114. thesportsdb_id=lid, sport=sport
  115. )
  116. if l_created:
  117. league.sport = sport
  118. league.name = data_dict.get("LeagueName", "")
  119. league.save(update_fields=['sport', 'name'])
  120. # Find or create our Season
  121. seid = data_dict.get('Season')
  122. season, se_created = Season.objects.get_or_create(
  123. thesportsdb_id=seid, league=league
  124. )
  125. if se_created:
  126. season.name = seid
  127. season.save(update_fields['name'])
  128. # Find or create our Round
  129. rid = data_dict.get('RoundId')
  130. round, r_created = Round.objects.get_or_create(
  131. thesportsdb_id=rid, season=season
  132. )
  133. if r_created:
  134. round.season = season
  135. round.save(update_fields=['season'])
  136. # Set some special data for Tennis
  137. player_one = None
  138. player_two = None
  139. if data_dict.get('Sport') == 'Tennis':
  140. event_name = data_dict.get('Name', '')
  141. if not round.name:
  142. round.name = get_round_name_from_event(event_name)
  143. round.save(update_fields=['name'])
  144. players_list = get_players_from_event(event_name)
  145. player_one = Player.objects.filter(
  146. name__icontains=players_list[0]
  147. ).first()
  148. if not player_one:
  149. player_one = Player.objects.create(name=players_list[0])
  150. player_two = Player.objects.filter(
  151. name__icontains=players_list[1]
  152. ).first()
  153. if not player_two:
  154. player_two = Player.objects.create(name=players_list[1])
  155. home_team = None
  156. away_team = None
  157. if data_dict.get("HomeTeamName"):
  158. home_team_dict = {
  159. "name": data_dict.get("HomeTeamName", ""),
  160. "thesportsdb_id": data_dict.get("HomeTeamId", ""),
  161. "league": league,
  162. }
  163. home_team, _created = Team.objects.get_or_create(**home_team_dict)
  164. away_team_dict = {
  165. "name": data_dict.get("AwayTeamName", ""),
  166. "thesportsdb_id": data_dict.get("AwayTeamId", ""),
  167. "league": league,
  168. }
  169. away_team, _created = Team.objects.get_or_create(**away_team_dict)
  170. event_dict = {
  171. "title": data_dict.get("Name"),
  172. "event_type": sport.default_event_type,
  173. "home_team": home_team,
  174. "away_team": away_team,
  175. "player_one": player_one,
  176. "player_two": player_two,
  177. "start": data_dict['Start'],
  178. "round": round,
  179. "run_time_ticks": data_dict.get("RunTimeTicks"),
  180. "run_time": data_dict.get("RunTime", ""),
  181. }
  182. event, _created = cls.objects.get_or_create(**event_dict)
  183. return event