Copmletely rejigger sports to accomodate tennis

This commit is contained in:
2023-01-22 18:27:33 -05:00
parent 506de848d7
commit 49bf57dd58
11 changed files with 604 additions and 54 deletions

View File

@ -119,7 +119,7 @@ def create_jellyfin_scrobble_dict(data_dict: dict, user_id: int) -> dict:
"timestamp": parse(data_dict.get("UtcTimestamp")), "timestamp": parse(data_dict.get("UtcTimestamp")),
"playback_position_ticks": playback_position_ticks, "playback_position_ticks": playback_position_ticks,
"playback_position": playback_position, "playback_position": playback_position,
"source": data_dict.get("ClientName", "Jellyfin"), "source": data_dict.get("ClientName", "Vrobbler"),
"source_id": data_dict.get('MediaSourceId'), "source_id": data_dict.get('MediaSourceId'),
"jellyfin_status": jellyfin_status, "jellyfin_status": jellyfin_status,
} }

View File

@ -4,7 +4,7 @@ from dateutil.parser import parse
from django.conf import settings from django.conf import settings
from django.utils import timezone from django.utils import timezone
from pysportsdb import TheSportsDbClient from pysportsdb import TheSportsDbClient
from sports.models import SportEvent from sports.models import Sport
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -15,18 +15,23 @@ client = TheSportsDbClient(api_key=API_KEY)
def lookup_event_from_thesportsdb(event_id: str) -> dict: def lookup_event_from_thesportsdb(event_id: str) -> dict:
event = client.lookup_event(event_id)['events'][0] event = client.lookup_event(event_id)['events'][0]
if not event or type(event) != dict:
return {}
league = {} # client.lookup_league(league_id=event.get('idLeague')) league = {} # client.lookup_league(league_id=event.get('idLeague'))
event_type = SportEvent.Type.GAME event_type = "Game"
run_time_seconds = 11700 sport = Sport.objects.filter(thesportsdb_id=event.get('strSport')).first()
run_time_ticks = run_time_seconds * 1000
logger.debug(event)
data_dict = { data_dict = {
"ItemType": event_type, "ItemType": sport.default_event_type,
"Name": event.get('strEventAlternate'), "Name": event.get('strEvent'),
"AltName": event.get('strEventAlternate'),
"Start": parse(event.get('strTimestamp')), "Start": parse(event.get('strTimestamp')),
"Provider_thesportsdb": event.get('idEvent'), "Provider_thesportsdb": event.get('idEvent'),
"RunTime": run_time_seconds, "RunTime": sport.default_event_run_time,
"RunTimeTicks": run_time_ticks, "RunTimeTicks": sport.default_event_run_time_ticks,
"Sport": event.get('strSport'),
"Season": event.get('strSeason'),
"LeagueId": event.get('idLeague'), "LeagueId": event.get('idLeague'),
"LeagueName": event.get('strLeague'), "LeagueName": event.get('strLeague'),
"HomeTeamId": event.get('idHomeTeam'), "HomeTeamId": event.get('idHomeTeam'),
@ -39,6 +44,7 @@ def lookup_event_from_thesportsdb(event_id: str) -> dict:
"UtcTimestamp": timezone.now().strftime('%Y-%m-%d %H:%M:%S.%f%z'), "UtcTimestamp": timezone.now().strftime('%Y-%m-%d %H:%M:%S.%f%z'),
"IsPaused": False, "IsPaused": False,
"PlayedToCompletion": False, "PlayedToCompletion": False,
"Source": "Vrobbler",
} }
return data_dict return data_dict

View File

@ -1,10 +1,24 @@
from django.contrib import admin from django.contrib import admin
from sports.models import League, SportEvent, Team from sports.models import (
League,
Player,
Round,
Season,
Sport,
SportEvent,
Team,
)
from scrobbles.admin import ScrobbleInline from scrobbles.admin import ScrobbleInline
@admin.register(Sport)
class SportAdmin(admin.ModelAdmin):
date_hierarchy = "created"
ordering = ("name",)
@admin.register(League) @admin.register(League)
class LeagueAdmin(admin.ModelAdmin): class LeagueAdmin(admin.ModelAdmin):
date_hierarchy = "created" date_hierarchy = "created"
@ -12,6 +26,27 @@ class LeagueAdmin(admin.ModelAdmin):
ordering = ("name",) ordering = ("name",)
@admin.register(Player)
class PlayerAdmin(admin.ModelAdmin):
date_hierarchy = "created"
list_display = ("name", "league", "team")
ordering = ("name",)
@admin.register(Season)
class SeasonAdmin(admin.ModelAdmin):
date_hierarchy = "created"
list_display = ("name", "league")
ordering = ("name",)
@admin.register(Round)
class RoundAdmin(admin.ModelAdmin):
date_hierarchy = "created"
list_display = ("name", "season")
ordering = ("name",)
@admin.register(Team) @admin.register(Team)
class TeamAdmin(admin.ModelAdmin): class TeamAdmin(admin.ModelAdmin):
date_hierarchy = "created" date_hierarchy = "created"
@ -26,12 +61,17 @@ class SportEventAdmin(admin.ModelAdmin):
"title", "title",
"event_type", "event_type",
"start", "start",
"home_team", "comp_str",
"away_team", "round",
"season",
) )
list_filter = ("season", "home_team", "away_team") list_filter = ("round__season", "home_team", "away_team")
ordering = ("-created",) ordering = ("-created",)
inlines = [ inlines = [
ScrobbleInline, ScrobbleInline,
] ]
def comp_str(self, obj):
if obj.home_team:
return f'{obj.away_team} @ {obj.home_team}'
if obj.player_one:
return f'{obj.player_one} v {obj.player_two}'

View File

@ -0,0 +1,71 @@
# Generated by Django 4.1.5 on 2023-01-21 04:21
from django.db import migrations, models
import django.db.models.deletion
import django_extensions.db.fields
import uuid
class Migration(migrations.Migration):
dependencies = [
('sports', '0002_rename_start_utc_sportevent_start'),
]
operations = [
migrations.CreateModel(
name='Sport',
fields=[
(
'id',
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name='ID',
),
),
(
'created',
django_extensions.db.fields.CreationDateTimeField(
auto_now_add=True, verbose_name='created'
),
),
(
'modified',
django_extensions.db.fields.ModificationDateTimeField(
auto_now=True, verbose_name='modified'
),
),
('name', models.CharField(max_length=255)),
(
'uuid',
models.UUIDField(
blank=True,
default=uuid.uuid4,
editable=False,
null=True,
),
),
('thesportsdb_id', models.IntegerField(blank=True, null=True)),
(
'default_event_run_time',
models.IntegerField(blank=True, null=True),
),
],
options={
'get_latest_by': 'modified',
'abstract': False,
},
),
migrations.AddField(
model_name='league',
name='sport',
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
to='sports.sport',
),
),
]

View File

@ -0,0 +1,254 @@
# Generated by Django 4.1.5 on 2023-01-22 20:20
from django.db import migrations, models
import django.db.models.deletion
import django_extensions.db.fields
import uuid
class Migration(migrations.Migration):
dependencies = [
('sports', '0003_sport_league_sport'),
]
operations = [
migrations.AlterModelOptions(
name='league',
options={},
),
migrations.AlterModelOptions(
name='sport',
options={},
),
migrations.AlterModelOptions(
name='team',
options={},
),
migrations.RemoveField(
model_name='sportevent',
name='league',
),
migrations.AlterField(
model_name='league',
name='thesportsdb_id',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AlterField(
model_name='sport',
name='thesportsdb_id',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AlterField(
model_name='team',
name='thesportsdb_id',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.CreateModel(
name='Season',
fields=[
(
'id',
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name='ID',
),
),
(
'created',
django_extensions.db.fields.CreationDateTimeField(
auto_now_add=True, verbose_name='created'
),
),
(
'modified',
django_extensions.db.fields.ModificationDateTimeField(
auto_now=True, verbose_name='modified'
),
),
('name', models.CharField(max_length=255)),
(
'uuid',
models.UUIDField(
blank=True,
default=uuid.uuid4,
editable=False,
null=True,
),
),
(
'thesportsdb_id',
models.CharField(blank=True, max_length=255, null=True),
),
(
'league',
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
to='sports.league',
),
),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Round',
fields=[
(
'id',
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name='ID',
),
),
(
'created',
django_extensions.db.fields.CreationDateTimeField(
auto_now_add=True, verbose_name='created'
),
),
(
'modified',
django_extensions.db.fields.ModificationDateTimeField(
auto_now=True, verbose_name='modified'
),
),
('name', models.CharField(max_length=255)),
(
'uuid',
models.UUIDField(
blank=True,
default=uuid.uuid4,
editable=False,
null=True,
),
),
(
'thesportsdb_id',
models.CharField(blank=True, max_length=255, null=True),
),
(
'season',
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
to='sports.season',
),
),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Player',
fields=[
(
'id',
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name='ID',
),
),
(
'created',
django_extensions.db.fields.CreationDateTimeField(
auto_now_add=True, verbose_name='created'
),
),
(
'modified',
django_extensions.db.fields.ModificationDateTimeField(
auto_now=True, verbose_name='modified'
),
),
('name', models.CharField(max_length=255)),
(
'uuid',
models.UUIDField(
blank=True,
default=uuid.uuid4,
editable=False,
null=True,
),
),
(
'thesportsdb_id',
models.CharField(blank=True, max_length=255, null=True),
),
(
'league',
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
to='sports.league',
),
),
(
'team',
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
to='sports.team',
),
),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='sportevent',
name='player_one',
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name='player_one_set',
to='sports.player',
),
),
migrations.AddField(
model_name='sportevent',
name='player_two',
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name='player_two_set',
to='sports.player',
),
),
migrations.AddField(
model_name='sportevent',
name='round',
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
to='sports.round',
),
),
migrations.AlterField(
model_name='sportevent',
name='season',
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
to='sports.season',
),
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 4.1.5 on 2023-01-22 20:34
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('sports', '0004_alter_league_options_alter_sport_options_and_more'),
]
operations = [
migrations.RemoveField(
model_name='sportevent',
name='season',
),
]

View File

@ -0,0 +1,22 @@
# Generated by Django 4.1.5 on 2023-01-22 21:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sports', '0005_remove_sportevent_season'),
]
operations = [
migrations.AlterField(
model_name='sportevent',
name='event_type',
field=models.CharField(
choices=[('UK', 'Unknown'), ('GA', 'Game'), ('MA', 'Match')],
default='UK',
max_length=2,
),
),
]

View File

@ -0,0 +1,22 @@
# Generated by Django 4.1.5 on 2023-01-22 22:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('sports', '0006_alter_sportevent_event_type'),
]
operations = [
migrations.AddField(
model_name='sport',
name='default_event_type',
field=models.CharField(
choices=[('UK', 'Unknown'), ('GA', 'Game'), ('MA', 'Match')],
default='UK',
max_length=2,
),
),
]

View File

@ -7,52 +7,93 @@ from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django_extensions.db.models import TimeStampedModel from django_extensions.db.models import TimeStampedModel
from sqlalchemy import update
from scrobbles.mixins import ScrobblableMixin from scrobbles.mixins import ScrobblableMixin
from vrobbler.apps.sports.utils import (
get_players_from_event,
get_round_name_from_event,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
BNULL = {"blank": True, "null": True} BNULL = {"blank": True, "null": True}
class League(TimeStampedModel): class SportEventType(models.TextChoices):
UNKNOWN = 'UK', _('Unknown')
GAME = 'GA', _('Game')
MATCH = 'MA', _('Match')
class TheSportsDbMixin(TimeStampedModel):
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
uuid = models.UUIDField(default=uuid4, editable=False, **BNULL) uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
logo = models.ImageField(upload_to="sports/league-logos/", **BNULL) thesportsdb_id = models.CharField(max_length=255, **BNULL)
abbreviation_str = models.CharField(max_length=10, **BNULL)
thesportsdb_id = models.IntegerField(**BNULL) class Meta:
abstract = True
def __str__(self): def __str__(self):
return self.name return self.name
class Sport(TheSportsDbMixin):
default_event_run_time = models.IntegerField(**BNULL)
default_event_type = models.CharField(
max_length=2,
choices=SportEventType.choices,
default=SportEventType.UNKNOWN,
)
# TODO Add these to the default run_time for Football
# run_time_seconds = 11700
# run_time_ticks = run_time_seconds * 1000
@property
def default_event_run_time_ticks(self):
return self.default_event_run_time * 1000
class League(TheSportsDbMixin):
logo = models.ImageField(upload_to="sports/league-logos/", **BNULL)
abbreviation_str = models.CharField(max_length=10, **BNULL)
sport = models.ForeignKey(Sport, on_delete=models.DO_NOTHING, **BNULL)
@property @property
def abbreviation(self): def abbreviation(self):
return self.abbreviation_str return self.abbreviation_str
class Team(TimeStampedModel): class Season(TheSportsDbMixin):
name = models.CharField(max_length=255)
uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
league = models.ForeignKey(League, on_delete=models.DO_NOTHING, **BNULL) league = models.ForeignKey(League, on_delete=models.DO_NOTHING, **BNULL)
thesportsdb_id = models.IntegerField(**BNULL)
def __str__(self): def __str__(self):
return self.name return f'{self.name} season of {self.league}'
class Team(TheSportsDbMixin):
league = models.ForeignKey(League, on_delete=models.DO_NOTHING, **BNULL)
class Player(TheSportsDbMixin):
league = models.ForeignKey(League, on_delete=models.DO_NOTHING, **BNULL)
team = models.ForeignKey(Team, on_delete=models.DO_NOTHING, **BNULL)
class Round(TheSportsDbMixin):
season = models.ForeignKey(Season, on_delete=models.DO_NOTHING, **BNULL)
def __str__(self):
return f'{self.name} of {self.season}'
class SportEvent(ScrobblableMixin): class SportEvent(ScrobblableMixin):
COMPLETION_PERCENT = getattr(settings, 'SPORT_COMPLETION_PERCENT', 90) COMPLETION_PERCENT = getattr(settings, 'SPORT_COMPLETION_PERCENT', 90)
class Type(models.TextChoices):
UNKNOWN = 'UK', _('Unknown')
GAME = 'GA', _('Game')
MATCH = 'MA', _('Match')
MEET = 'ME', _('Meet')
event_type = models.CharField( event_type = models.CharField(
max_length=2, max_length=2,
choices=Type.choices, choices=SportEventType.choices,
default=Type.UNKNOWN, default=SportEventType.UNKNOWN,
) )
league = models.ForeignKey(League, on_delete=models.DO_NOTHING, **BNULL) round = models.ForeignKey(Round, on_delete=models.DO_NOTHING, **BNULL)
start = models.DateTimeField(**BNULL) start = models.DateTimeField(**BNULL)
home_team = models.ForeignKey( home_team = models.ForeignKey(
Team, Team,
@ -66,10 +107,21 @@ class SportEvent(ScrobblableMixin):
related_name='away_event_set', related_name='away_event_set',
**BNULL, **BNULL,
) )
season = models.CharField(max_length=255, **BNULL) player_one = models.ForeignKey(
Player,
on_delete=models.DO_NOTHING,
related_name='player_one_set',
**BNULL,
)
player_two = models.ForeignKey(
Player,
on_delete=models.DO_NOTHING,
related_name='player_two_set',
**BNULL,
)
def __str__(self): def __str__(self):
return f"{self.start.date()} - {self.league.abbreviation} - {self.home_team} v {self.away_team}" return f"{self.start.date()} - {self.round} - {self.home_team} v {self.away_team}"
def get_absolute_url(self): def get_absolute_url(self):
return reverse("sports:event_detail", kwargs={'slug': self.uuid}) return reverse("sports:event_detail", kwargs={'slug': self.uuid})
@ -81,33 +133,88 @@ class SportEvent(ScrobblableMixin):
exist. exist.
""" """
league_dict = { # Find or create our Sport
"abbreviation_str": data_dict.get("LeagueName", ""), sid = data_dict.get("Sport")
"thesportsdb_id": data_dict.get("LeagueId", ""), sport, s_created = Sport.objects.get_or_create(thesportsdb_id=sid)
} if s_created:
league, _created = League.objects.get_or_create(**league_dict) sport.name = sid
sport.save(update_fields=['name'])
home_team_dict = { # Find or create our League
"name": data_dict.get("HomeTeamName", ""), lid = data_dict.get("LeagueId")
"thesportsdb_id": data_dict.get("HomeTeamId", ""), league, l_created = League.objects.get_or_create(
"league": league, thesportsdb_id=lid, sport=sport
} )
home_team, _created = Team.objects.get_or_create(**home_team_dict) if l_created:
league.sport = sport
league.name = data_dict.get("LeagueName", "")
league.save(update_fields=['sport', 'name'])
away_team_dict = { # Find or create our Season
"name": data_dict.get("AwayTeamName", ""), seid = data_dict.get('Season')
"thesportsdb_id": data_dict.get("AwayTeamId", ""), season, se_created = Season.objects.get_or_create(
"league": league, thesportsdb_id=seid, league=league
} )
away_team, _created = Team.objects.get_or_create(**away_team_dict) if se_created:
season.name = seid
season.save(update_fields['name'])
# Find or create our Round
rid = data_dict.get('RoundId')
round, r_created = Round.objects.get_or_create(
thesportsdb_id=rid, season=season
)
if r_created:
round.season = season
round.save(update_fields=['season'])
# Set some special data for Tennis
player_one = None
player_two = None
if data_dict.get('Sport') == 'Tennis':
event_name = data_dict.get('Name', '')
if not round.name:
round.name = get_round_name_from_event(event_name)
round.save(update_fields=['name'])
players_list = get_players_from_event(event_name)
player_one = Player.objects.filter(
name__icontains=players_list[0]
).first()
if not player_one:
player_one = Player.objects.create(name=players_list[0])
player_two = Player.objects.filter(
name__icontains=players_list[1]
).first()
if not player_two:
player_two = Player.objects.create(name=players_list[1])
home_team = None
away_team = None
if data_dict.get("HomeTeamName"):
home_team_dict = {
"name": data_dict.get("HomeTeamName", ""),
"thesportsdb_id": data_dict.get("HomeTeamId", ""),
"league": league,
}
home_team, _created = Team.objects.get_or_create(**home_team_dict)
away_team_dict = {
"name": data_dict.get("AwayTeamName", ""),
"thesportsdb_id": data_dict.get("AwayTeamId", ""),
"league": league,
}
away_team, _created = Team.objects.get_or_create(**away_team_dict)
event_dict = { event_dict = {
"title": data_dict.get("Name"), "title": data_dict.get("Name"),
"event_type": data_dict.get("ItemType"), "event_type": sport.default_event_type,
"home_team": home_team, "home_team": home_team,
"away_team": away_team, "away_team": away_team,
"player_one": player_one,
"player_two": player_two,
"start": data_dict['Start'], "start": data_dict['Start'],
"league": league, "round": round,
"run_time_ticks": data_dict.get("RunTimeTicks"), "run_time_ticks": data_dict.get("RunTimeTicks"),
"run_time": data_dict.get("RunTime", ""), "run_time": data_dict.get("RunTime", ""),
} }

View File

@ -0,0 +1,11 @@
def get_round_name_from_event(event: str) -> str:
return ' '.join(event.split(' ')[:2])
def get_players_from_event(event: str) -> list[str]:
players = []
event_name = get_round_name_from_event(event)
players_list = event.split(event_name)[1:][0].split('vs')
players.append(players_list[0].strip())
players.append(players_list[1].strip())
return players

View File

@ -206,7 +206,7 @@
{% if scrobble.track %}<em>{{scrobble.track.artist}}</em><br/>{% endif %} {% if scrobble.track %}<em>{{scrobble.track.artist}}</em><br/>{% endif %}
{% if scrobble.podcast_episode%}<em>{{scrobble.podcast_episode.podcast}}</em><br/>{% endif %} {% if scrobble.podcast_episode%}<em>{{scrobble.podcast_episode.podcast}}</em><br/>{% endif %}
{% if scrobble.video.tv_series %}<em>{{scrobble.video.tv_series }}</em><br/>{% endif %} {% if scrobble.video.tv_series %}<em>{{scrobble.video.tv_series }}</em><br/>{% endif %}
{% if scrobble.sport_event %}<em>{{scrobble.sport_event.league.abbreviation}}</em><br/>{% endif %} {% if scrobble.sport_event %}<em>{{scrobble.sport_event.round.season.league}}</em><br/>{% endif %}
<small>{{scrobble.timestamp|naturaltime}}<br/> <small>{{scrobble.timestamp|naturaltime}}<br/>
from {{scrobble.source}}</small> from {{scrobble.source}}</small>
<div class="progress-bar" style="margin-right:5px;"> <div class="progress-bar" style="margin-right:5px;">