Просмотр исходного кода

Copmletely rejigger sports to accomodate tennis

Colin Powell 2 лет назад
Родитель
Сommit
49bf57dd58

+ 1 - 1
vrobbler/apps/scrobbles/scrobblers.py

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

+ 14 - 8
vrobbler/apps/scrobbles/thesportsdb.py

@@ -4,7 +4,7 @@ from dateutil.parser import parse
 from django.conf import settings
 from django.utils import timezone
 from pysportsdb import TheSportsDbClient
-from sports.models import SportEvent
+from sports.models import Sport
 
 logger = logging.getLogger(__name__)
 
@@ -15,18 +15,23 @@ client = TheSportsDbClient(api_key=API_KEY)
 def lookup_event_from_thesportsdb(event_id: str) -> dict:
 
     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'))
-    event_type = SportEvent.Type.GAME
-    run_time_seconds = 11700
-    run_time_ticks = run_time_seconds * 1000
+    event_type = "Game"
+    sport = Sport.objects.filter(thesportsdb_id=event.get('strSport')).first()
 
+    logger.debug(event)
     data_dict = {
-        "ItemType": event_type,
-        "Name": event.get('strEventAlternate'),
+        "ItemType": sport.default_event_type,
+        "Name": event.get('strEvent'),
+        "AltName": event.get('strEventAlternate'),
         "Start": parse(event.get('strTimestamp')),
         "Provider_thesportsdb": event.get('idEvent'),
-        "RunTime": run_time_seconds,
-        "RunTimeTicks": run_time_ticks,
+        "RunTime": sport.default_event_run_time,
+        "RunTimeTicks": sport.default_event_run_time_ticks,
+        "Sport": event.get('strSport'),
+        "Season": event.get('strSeason'),
         "LeagueId": event.get('idLeague'),
         "LeagueName": event.get('strLeague'),
         "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'),
         "IsPaused": False,
         "PlayedToCompletion": False,
+        "Source": "Vrobbler",
     }
 
     return data_dict

+ 45 - 5
vrobbler/apps/sports/admin.py

@@ -1,10 +1,24 @@
 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
 
 
+@admin.register(Sport)
+class SportAdmin(admin.ModelAdmin):
+    date_hierarchy = "created"
+    ordering = ("name",)
+
+
 @admin.register(League)
 class LeagueAdmin(admin.ModelAdmin):
     date_hierarchy = "created"
@@ -12,6 +26,27 @@ class LeagueAdmin(admin.ModelAdmin):
     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)
 class TeamAdmin(admin.ModelAdmin):
     date_hierarchy = "created"
@@ -26,12 +61,17 @@ class SportEventAdmin(admin.ModelAdmin):
         "title",
         "event_type",
         "start",
-        "home_team",
-        "away_team",
-        "season",
+        "comp_str",
+        "round",
     )
-    list_filter = ("season", "home_team", "away_team")
+    list_filter = ("round__season", "home_team", "away_team")
     ordering = ("-created",)
     inlines = [
         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}'

+ 71 - 0
vrobbler/apps/sports/migrations/0003_sport_league_sport.py

@@ -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',
+            ),
+        ),
+    ]

+ 254 - 0
vrobbler/apps/sports/migrations/0004_alter_league_options_alter_sport_options_and_more.py

@@ -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',
+            ),
+        ),
+    ]

+ 17 - 0
vrobbler/apps/sports/migrations/0005_remove_sportevent_season.py

@@ -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',
+        ),
+    ]

+ 22 - 0
vrobbler/apps/sports/migrations/0006_alter_sportevent_event_type.py

@@ -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,
+            ),
+        ),
+    ]

+ 22 - 0
vrobbler/apps/sports/migrations/0007_sport_default_event_type.py

@@ -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,
+            ),
+        ),
+    ]

+ 148 - 41
vrobbler/apps/sports/models.py

@@ -7,52 +7,93 @@ 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 sqlalchemy import update
 from scrobbles.mixins import ScrobblableMixin
+from vrobbler.apps.sports.utils import (
+    get_players_from_event,
+    get_round_name_from_event,
+)
 
 logger = logging.getLogger(__name__)
 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)
     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)
+    thesportsdb_id = models.CharField(max_length=255, **BNULL)
+
+    class Meta:
+        abstract = True
 
     def __str__(self):
         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
     def abbreviation(self):
         return self.abbreviation_str
 
 
-class Team(TimeStampedModel):
-    name = models.CharField(max_length=255)
-    uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
+class Season(TheSportsDbMixin):
     league = models.ForeignKey(League, on_delete=models.DO_NOTHING, **BNULL)
-    thesportsdb_id = models.IntegerField(**BNULL)
 
     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):
     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(
         max_length=2,
-        choices=Type.choices,
-        default=Type.UNKNOWN,
+        choices=SportEventType.choices,
+        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)
     home_team = models.ForeignKey(
         Team,
@@ -66,10 +107,21 @@ class SportEvent(ScrobblableMixin):
         related_name='away_event_set',
         **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):
-        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):
         return reverse("sports:event_detail", kwargs={'slug': self.uuid})
@@ -81,33 +133,88 @@ class SportEvent(ScrobblableMixin):
         exist.
 
         """
-        league_dict = {
-            "abbreviation_str": data_dict.get("LeagueName", ""),
-            "thesportsdb_id": data_dict.get("LeagueId", ""),
-        }
-        league, _created = League.objects.get_or_create(**league_dict)
-
-        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)
+        # Find or create our Sport
+        sid = data_dict.get("Sport")
+        sport, s_created = Sport.objects.get_or_create(thesportsdb_id=sid)
+        if s_created:
+            sport.name = sid
+            sport.save(update_fields=['name'])
+
+        # Find or create our League
+        lid = data_dict.get("LeagueId")
+        league, l_created = League.objects.get_or_create(
+            thesportsdb_id=lid, sport=sport
+        )
+        if l_created:
+            league.sport = sport
+            league.name = data_dict.get("LeagueName", "")
+            league.save(update_fields=['sport', 'name'])
+
+        # Find or create our Season
+        seid = data_dict.get('Season')
+        season, se_created = Season.objects.get_or_create(
+            thesportsdb_id=seid, league=league
+        )
+        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 = {
             "title": data_dict.get("Name"),
-            "event_type": data_dict.get("ItemType"),
+            "event_type": sport.default_event_type,
             "home_team": home_team,
             "away_team": away_team,
+            "player_one": player_one,
+            "player_two": player_two,
             "start": data_dict['Start'],
-            "league": league,
+            "round": round,
             "run_time_ticks": data_dict.get("RunTimeTicks"),
             "run_time": data_dict.get("RunTime", ""),
         }

+ 11 - 0
vrobbler/apps/sports/utils.py

@@ -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

+ 1 - 1
vrobbler/templates/base.html

@@ -206,7 +206,7 @@
                                 {% if scrobble.track %}<em>{{scrobble.track.artist}}</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.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/>
                                     from {{scrobble.source}}</small>
                                 <div class="progress-bar" style="margin-right:5px;">