Add sports as a preliminary scobble type

This commit is contained in:
2023-01-14 16:42:48 -05:00
parent e206a7fbf3
commit bd3a381346
14 changed files with 433 additions and 14 deletions

View File

@ -23,7 +23,7 @@ class ScrobbleAdmin(admin.ModelAdmin):
"is_paused",
"played_to_completion",
)
raw_id_fields = ('video', 'podcast_episode', 'track')
raw_id_fields = ('video', 'podcast_episode', 'track', 'sport_event')
list_filter = ("is_paused", "in_progress", "source", "track__artist")
ordering = ("-timestamp",)
@ -34,6 +34,8 @@ class ScrobbleAdmin(admin.ModelAdmin):
return obj.track
if obj.podcast_episode:
return obj.podcast_episode
if obj.sport_event:
return obj.sport_event
def media_type(self, obj):
if obj.video:
@ -42,6 +44,8 @@ class ScrobbleAdmin(admin.ModelAdmin):
return "Track"
if obj.podcast_episode:
return "Podcast"
if obj.sport_event:
return "Sport Event"
def playback_percent(self, obj):
return obj.percent_played

View File

@ -1,8 +1,8 @@
from django import forms
class ImdbScrobbleForm(forms.Form):
imdb_id = forms.CharField(
class ScrobbleForm(forms.Form):
item_id = forms.CharField(
label="",
widget=forms.TextInput(
attrs={

View File

@ -0,0 +1,25 @@
# Generated by Django 4.1.5 on 2023-01-14 21:23
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('sports', '0002_rename_start_utc_sportevent_start'),
('scrobbles', '0007_scrobble_podcast_episode'),
]
operations = [
migrations.AddField(
model_name='scrobble',
name='sport_event',
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
to='sports.sportevent',
),
),
]

View File

@ -11,6 +11,7 @@ from music.models import Track
from podcasts.models import Episode
from scrobbles.utils import check_scrobble_for_finish
from videos.models import Video
from sports.models import SportEvent
logger = logging.getLogger(__name__)
User = get_user_model()
@ -27,6 +28,9 @@ class Scrobble(TimeStampedModel):
podcast_episode = models.ForeignKey(
Episode, on_delete=models.DO_NOTHING, **BNULL
)
sport_event = models.ForeignKey(
SportEvent, on_delete=models.DO_NOTHING, **BNULL
)
user = models.ForeignKey(
User, blank=True, null=True, on_delete=models.DO_NOTHING
)
@ -73,6 +77,8 @@ class Scrobble(TimeStampedModel):
media_obj = self.track
if self.podcast_episode:
media_obj = self.podcast_episode
if self.sport_event:
media_obj = self.sport_event
return media_obj
def __str__(self):
@ -179,14 +185,14 @@ class Scrobble(TimeStampedModel):
return scrobble.resume()
# We're not changing the scrobble, but we don't want to walk over an existing one
scrobble_is_finished = (
not scrobble.in_progress and scrobble.modified < backoff
)
if scrobble_is_finished:
logger.info(
'Found a very recent scrobble for this item, holding off scrobbling again'
)
return
# scrobble_is_finished = (
# not scrobble.in_progress and scrobble.modified < backoff
# )
# if scrobble_is_finished:
# logger.info(
# 'Found a very recent scrobble for this item, holding off scrobbling again'
# )
# return
if not scrobble:
# If we default this to "" we can probably remove this

View File

@ -33,7 +33,7 @@ from vrobbler.apps.music.aggregators import (
top_tracks,
week_of_scrobbles,
)
from scrobbles.forms import ImdbScrobbleForm
from scrobbles.forms import ScrobbleForm
logger = logging.getLogger(__name__)
@ -77,7 +77,7 @@ class RecentScrobbleList(ListView):
data["weekly_data"] = week_of_scrobbles()
data['counts'] = scrobble_counts()
data['imdb_form'] = ImdbScrobbleForm
data['imdb_form'] = ScrobbleForm
return data
def get_queryset(self):

View File

View File

@ -0,0 +1,37 @@
from django.contrib import admin
from sports.models import League, SportEvent, Team
from scrobbles.admin import ScrobbleInline
@admin.register(League)
class LeagueAdmin(admin.ModelAdmin):
date_hierarchy = "created"
list_display = ("name", "abbreviation_str")
ordering = ("name",)
@admin.register(Team)
class TeamAdmin(admin.ModelAdmin):
date_hierarchy = "created"
list_display = ("name", "league")
ordering = ("name",)
@admin.register(SportEvent)
class SportEventAdmin(admin.ModelAdmin):
date_hierarchy = "created"
list_display = (
"title",
"event_type",
"start",
"home_team",
"away_team",
"season",
)
list_filter = ("season", "home_team", "away_team")
ordering = ("-created",)
inlines = [
ScrobbleInline,
]

View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class SportsConfig(AppConfig):
name = "sports"

View File

@ -0,0 +1,214 @@
# Generated by Django 4.1.5 on 2023-01-14 21:14
from django.db import migrations, models
import django.db.models.deletion
import django_extensions.db.fields
import uuid
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name='League',
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,
),
),
(
'logo',
models.ImageField(
blank=True, null=True, upload_to='sports/league-logos/'
),
),
(
'abbreviation_str',
models.CharField(blank=True, max_length=10, null=True),
),
('thesportsdb_id', models.IntegerField(blank=True, null=True)),
],
options={
'get_latest_by': 'modified',
'abstract': False,
},
),
migrations.CreateModel(
name='Team',
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)),
(
'league',
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
to='sports.league',
),
),
],
options={
'get_latest_by': 'modified',
'abstract': False,
},
),
migrations.CreateModel(
name='SportEvent',
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'
),
),
(
'uuid',
models.UUIDField(
blank=True,
default=uuid.uuid4,
editable=False,
null=True,
),
),
(
'title',
models.CharField(blank=True, max_length=255, null=True),
),
(
'run_time',
models.CharField(blank=True, max_length=8, null=True),
),
(
'run_time_ticks',
models.PositiveBigIntegerField(blank=True, null=True),
),
(
'event_type',
models.CharField(
choices=[
('UK', 'Unknown'),
('GA', 'Game'),
('MA', 'Match'),
('ME', 'Meet'),
],
default='UK',
max_length=2,
),
),
('start_utc', models.DateTimeField(blank=True, null=True)),
(
'season',
models.CharField(blank=True, max_length=255, null=True),
),
(
'away_team',
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name='away_event_set',
to='sports.team',
),
),
(
'home_team',
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
related_name='home_event_set',
to='sports.team',
),
),
(
'league',
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.DO_NOTHING,
to='sports.league',
),
),
],
options={
'abstract': False,
},
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 4.1.5 on 2023-01-14 21:17
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('sports', '0001_initial'),
]
operations = [
migrations.RenameField(
model_name='sportevent',
old_name='start_utc',
new_name='start',
),
]

View File

@ -0,0 +1,105 @@
import logging
from typing import Dict
from uuid import uuid4
from django.db import models
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django_extensions.db.models import TimeStampedModel
from scrobbles.mixins import ScrobblableMixin
logger = logging.getLogger(__name__)
BNULL = {"blank": True, "null": True}
class League(TimeStampedModel):
name = models.CharField(max_length=255)
uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
logo = models.ImageField(upload_to="sports/league-logos/", **BNULL)
abbreviation_str = models.CharField(max_length=10, **BNULL)
thesportsdb_id = models.IntegerField(**BNULL)
def __str__(self):
return self.name
@property
def abbreviation(self):
return self.abbreviation_str
class Team(TimeStampedModel):
name = models.CharField(max_length=255)
uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
league = models.ForeignKey(League, on_delete=models.DO_NOTHING, **BNULL)
thesportsdb_id = models.IntegerField(**BNULL)
def __str__(self):
return self.name
class SportEvent(ScrobblableMixin):
class Type(models.TextChoices):
UNKNOWN = 'UK', _('Unknown')
GAME = 'GA', _('Game')
MATCH = 'MA', _('Match')
MEET = 'ME', _('Meet')
event_type = models.CharField(
max_length=2,
choices=Type.choices,
default=Type.UNKNOWN,
)
league = models.ForeignKey(League, on_delete=models.DO_NOTHING, **BNULL)
start = models.DateTimeField(**BNULL)
home_team = models.ForeignKey(
Team,
on_delete=models.DO_NOTHING,
related_name='home_event_set',
**BNULL,
)
away_team = models.ForeignKey(
Team,
on_delete=models.DO_NOTHING,
related_name='away_event_set',
**BNULL,
)
season = models.CharField(max_length=255, **BNULL)
def __str__(self):
return f"{self.start.date()} - {self.league.abbreviation} - {self.home_team} v {self.away_team}"
def get_absolute_url(self):
return reverse("sports:event_detail", kwargs={'slug': self.uuid})
@classmethod
def find_or_create(cls, data_dict: Dict) -> "Event":
"""Given a data dict from Jellyfin, does the heavy lifting of looking up
the video and, if need, TV Series, creating both if they don't yet
exist.
"""
league_dict = {"name": data_dict.get("LeagueName", "")}
league, _created = League.objects.get_or_create(**league_dict)
home_team_dict = {
"name": data_dict.get("HomeTeamName", ""),
"league": league,
}
home_team, _created = Team.objects.get_or_create(**home_team_dict)
away_team_dict = {
"name": data_dict.get("AwayTeamName", ""),
"league": league,
}
away_team, _created = Team.objects.get_or_create(**away_team_dict)
event_dict = {
"event_type": SportEvent.Type.GAME,
"home_team": home_team,
"away_team": away_team,
"start_utc": data_dict['SportEventStart'],
"league": league,
}
event, _created = cls.objects.get_or_create(**event_dict)
return event

View File

@ -58,6 +58,10 @@ MUSIC_BACKOFF_SECONDS = os.getenv("VROBBLER_VIDEO_BACKOFF_SECONDS", 1)
VIDEO_WAIT_PERIOD_DAYS = os.getenv("VROBBLER_VIDEO_WAIT_PERIOD_DAYS", 1)
MUSIC_WAIT_PERIOD_MINUTES = os.getenv("VROBBLER_VIDEO_BACKOFF_MINUTES", 1)
THESPORTSDB_API_KEY = os.getenv("VROBBLER_THESPORTSDB_API_KEY", "2")
THESPORTSDB_BASE_URL = os.getenv(
"VROBBLER_THESPORTSDB_BASE_URL", "https://www.thesportsdb.com/api/v1/json/"
)
TMDB_API_KEY = os.getenv("VROBBLER_TMDB_API_KEY", "")
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
@ -94,6 +98,7 @@ INSTALLED_APPS = [
"videos",
"music",
"podcasts",
"sports",
"rest_framework",
"allauth",
"allauth.account",

View File

@ -17,7 +17,7 @@ urlpatterns = [
path("api/v1/scrobbles/", include(scrobble_urls, namespace="scrobbles")),
path(
'manual/imdb/',
scrobbles_views.ManualImdbScrobbleView.as_view(),
scrobbles_views.ManualScrobbleView.as_view(),
name='imdb-manual-scrobble',
),
path("", include(video_urls, namespace="videos")),