From c7339fbe312a2113360a111191283d28e44b7c90 Mon Sep 17 00:00:00 2001 From: Colin Powell Date: Sun, 7 Jun 2026 11:06:30 -0400 Subject: [PATCH] [templates] Clean up how str and subtitles work --- PROJECT.org | 15 +++++++++- vrobbler/apps/birds/models.py | 22 ++++---------- vrobbler/apps/boardgames/models.py | 5 ++-- vrobbler/apps/books/models.py | 13 +++++---- vrobbler/apps/scrobbles/admin.py | 29 +++++++------------ vrobbler/apps/scrobbles/mixins.py | 3 ++ vrobbler/apps/scrobbles/views.py | 8 ++--- vrobbler/apps/sports/models.py | 10 +++---- vrobbler/apps/videogames/models.py | 5 +--- .../templates/scrobbles/scrobble_detail.html | 14 ++++++++- .../templates/sports/sportevent_detail.html | 9 ++++-- 11 files changed, 72 insertions(+), 61 deletions(-) diff --git a/PROJECT.org b/PROJECT.org index b01cc97..9bb8462 100644 --- a/PROJECT.org +++ b/PROJECT.org @@ -86,7 +86,7 @@ fetching and simple saving. **** Bookmarklet *** Metadata sources **** Scraper -* Backlog [1/13] :vrobbler:project:personal: +* Backlog [2/14] :vrobbler:project:personal: ** TODO [#C] Create small utility to clean up tracks scrobbled with wonky playback times :vrobbler:personal:bug:music:scrobbles: :PROPERTIES: :ID: 702462cf-d54b-48c6-8a7c-78b8de751deb @@ -498,6 +498,19 @@ needed import celery task. This is how the WebDAV celery task currently works. This would also be an opporunity to clean up the code around WebDAV imports and make them more re-usable for other import services. +** DONE [#B] Show team or player images on sport detail and scrobble detail :sports:templates: +:PROPERTIES: +:ID: 68c17383-ee6e-4b5f-b3f5-1b637a0a3ea8 +:END: + +*** Description + +On the sport event detail page, we should show the images of the teams or +players invovled. + +Also, those images for the sport event should be shown on the scrobble detail +page for sport event scrobble details. + ** DONE [#B] Add fix_metadta method to Video instances :videos:metadata: :PROPERTIES: :ID: 9df5404d-1b60-4eee-b7cf-1f7e6dfade65 diff --git a/vrobbler/apps/birds/models.py b/vrobbler/apps/birds/models.py index 9a4a26d..e74ef5a 100644 --- a/vrobbler/apps/birds/models.py +++ b/vrobbler/apps/birds/models.py @@ -61,9 +61,7 @@ class BirdSightingLogData(BaseLogData, WithPeopleLogData): @cached_property def bird_list(self) -> str: if self.birds: - return ", ".join( - [BirdSightingEntry(**b).__str__() for b in self.birds] - ) + return ", ".join([BirdSightingEntry(**b).__str__() for b in self.birds]) return "" def as_html(self) -> str: @@ -80,9 +78,7 @@ class BirdSightingLogData(BaseLogData, WithPeopleLogData): ) if self.area: - html_parts.append( - f'
Area: {self.area}
' - ) + html_parts.append(f'
Area: {self.area}
') if self.party_size: html_parts.append( @@ -105,9 +101,7 @@ class BirdSightingLogData(BaseLogData, WithPeopleLogData): ) if self.guide: - html_parts.append( - f'
Guide: {self.guide}
' - ) + html_parts.append(f'
Guide: {self.guide}
') if self.duration_minutes: html_parts.append( @@ -183,9 +177,7 @@ class Bird(TimeStampedModel): class BirdingLocation(ScrobblableMixin): description = models.TextField(**BNULL) - geo_location = models.ForeignKey( - GeoLocation, **BNULL, on_delete=models.DO_NOTHING - ) + geo_location = models.ForeignKey(GeoLocation, **BNULL, on_delete=models.DO_NOTHING) ebird_hotspot_id = models.CharField(max_length=255, **BNULL) def get_absolute_url(self): @@ -193,7 +185,7 @@ class BirdingLocation(ScrobblableMixin): @property def subtitle(self): - return "" + return self.geo_location @property def strings(self) -> ScrobblableConstants: @@ -269,9 +261,7 @@ class BirdingCSVImport(TimeStampedModel): self.save(update_fields=["process_log", "process_count"]) return for count, scrobble in enumerate(scrobbles): - scrobble_str = ( - f"{scrobble.id}\t{scrobble.timestamp}\t{scrobble.media_obj}" - ) + scrobble_str = f"{scrobble.id}\t{scrobble.timestamp}\t{scrobble.media_obj}" log_line = f"{scrobble_str}" if count > 0: log_line = "\n" + log_line diff --git a/vrobbler/apps/boardgames/models.py b/vrobbler/apps/boardgames/models.py index 89a984b..251b732 100644 --- a/vrobbler/apps/boardgames/models.py +++ b/vrobbler/apps/boardgames/models.py @@ -266,8 +266,9 @@ class BoardGame(ScrobblableMixin): "self", **BNULL, on_delete=models.DO_NOTHING ) - def __str__(self): - return self.title + @property + def subtitle(self) -> str: + return self.publisher def get_absolute_url(self): return reverse("boardgames:boardgame_detail", kwargs={"slug": self.uuid}) diff --git a/vrobbler/apps/books/models.py b/vrobbler/apps/books/models.py index 04135ed..09e9e1c 100644 --- a/vrobbler/apps/books/models.py +++ b/vrobbler/apps/books/models.py @@ -173,11 +173,7 @@ class Book(LongPlayScrobblableMixin): genre = TaggableManager(through=ObjectWithGenres, blank=True, verbose_name="Genre") def __str__(self) -> str: - if self.issue_number and "Issue" not in str(self.title): - return f"{self.title} - Issue {self.issue_number}" - if self.volume_number and "Volume" not in str(self.title): - return f"{self.title} - Volume {self.volume_number}" - return f"{self.title}" + return f"{self.title} - {self.subtitle}" def save(self, *args, **kwargs): if self.pages: @@ -188,7 +184,12 @@ class Book(LongPlayScrobblableMixin): @property def subtitle(self): - return f" by {self.author}" + subtitle = self.author + if self.issue_number and "Issue" not in str(self.title): + subtitle += " - Issue {self.issue_number}" + if self.volume_number and "Volume" not in str(self.title): + subtitle += " - Volume {self.volume_number}" + return subtitle @property def strings(self) -> ScrobblableConstants: diff --git a/vrobbler/apps/scrobbles/admin.py b/vrobbler/apps/scrobbles/admin.py index 1e4fd4b..453180c 100644 --- a/vrobbler/apps/scrobbles/admin.py +++ b/vrobbler/apps/scrobbles/admin.py @@ -20,6 +20,7 @@ class ScrobbleInline(admin.TabularInline): extra = 0 raw_id_fields = ( "video", + "channel", "podcast_episode", "track", "video_game", @@ -30,6 +31,7 @@ class ScrobbleInline(admin.TabularInline): "board_game", "geo_location", "task", + "puzzle", "mood", "brick_set", "trail", @@ -59,47 +61,38 @@ class ImportBaseAdmin(admin.ModelAdmin): @admin.register(AudioScrobblerTSVImport) -class AudioScrobblerTSVImportAdmin(ImportBaseAdmin): - ... +class AudioScrobblerTSVImportAdmin(ImportBaseAdmin): ... @admin.register(LastFmImport) -class LastFmImportAdmin(ImportBaseAdmin): - ... +class LastFmImportAdmin(ImportBaseAdmin): ... @admin.register(KoReaderImport) -class KoReaderImportAdmin(ImportBaseAdmin): - ... +class KoReaderImportAdmin(ImportBaseAdmin): ... @admin.register(RetroarchImport) -class RetroarchImportAdmin(ImportBaseAdmin): - ... +class RetroarchImportAdmin(ImportBaseAdmin): ... -class RetroarchImportAdmin(ImportBaseAdmin): - ... +class RetroarchImportAdmin(ImportBaseAdmin): ... @admin.register(BGStatsImport) -class BGStatsImportAdmin(ImportBaseAdmin): - ... +class BGStatsImportAdmin(ImportBaseAdmin): ... @admin.register(EBirdCSVImport) -class EBirdCSVImportAdmin(ImportBaseAdmin): - ... +class EBirdCSVImportAdmin(ImportBaseAdmin): ... @admin.register(ScaleCSVImport) -class ScaleCSVImportAdmin(ImportBaseAdmin): - ... +class ScaleCSVImportAdmin(ImportBaseAdmin): ... @admin.register(TrailGPXImport) -class TrailGPXImportAdmin(ImportBaseAdmin): - ... +class TrailGPXImportAdmin(ImportBaseAdmin): ... @admin.register(Genre) diff --git a/vrobbler/apps/scrobbles/mixins.py b/vrobbler/apps/scrobbles/mixins.py index 47cf2e4..136ac9d 100644 --- a/vrobbler/apps/scrobbles/mixins.py +++ b/vrobbler/apps/scrobbles/mixins.py @@ -65,6 +65,9 @@ class ScrobblableMixin(TimeStampedModel): class Meta: abstract = True + def __str__(self) -> str: + return f"{self.title} - {self.subtitle}" + @property def run_time_seconds(self) -> int: run_time = 900 diff --git a/vrobbler/apps/scrobbles/views.py b/vrobbler/apps/scrobbles/views.py index 420e650..28f5120 100644 --- a/vrobbler/apps/scrobbles/views.py +++ b/vrobbler/apps/scrobbles/views.py @@ -184,7 +184,7 @@ class ScrobbleableDetailView(ChartContextMixin, DetailView): def get_context_data(self, **kwargs): context_data = super().get_context_data(**kwargs) scrobbles = [] - if not self.request.user.is_anonymous: + if not self.request.user.is_anonymous and hasattr(self.object, "scrobble_set"): scrobbles = self.object.scrobble_set.filter( user=self.request.user ).order_by("-timestamp") @@ -439,8 +439,7 @@ class ScrobbleListView(LoginRequiredMixin, ListView): full_qs = getattr(self, "_full_queryset", None) if full_qs is not None and getattr(self, "tag_list", []): total = ( - full_qs.aggregate(total=Sum("playback_position_seconds"))["total"] - or 0 + full_qs.aggregate(total=Sum("playback_position_seconds"))["total"] or 0 ) ctx["total_time_seconds"] = total return ctx @@ -1245,8 +1244,7 @@ class ScrobbleDetailView(DetailView): track=media_obj, user=self.object.user ).order_by("-timestamp")[:20] context["has_mopidy_uri"] = any( - (s.log or {}).get("raw_data", {}).get("mopidy_uri") - for s in scrobbles + (s.log or {}).get("raw_data", {}).get("mopidy_uri") for s in scrobbles ) else: context["has_mopidy_uri"] = False diff --git a/vrobbler/apps/sports/models.py b/vrobbler/apps/sports/models.py index 0b625c7..6401eb8 100644 --- a/vrobbler/apps/sports/models.py +++ b/vrobbler/apps/sports/models.py @@ -123,17 +123,17 @@ class SportEvent(ScrobblableMixin): super(SportEvent, self).save(*args, **kwargs) def __str__(self) -> str: - league = self.league - if self.league and self.league.abbreviation_str: - league = self.league.abbreviation_str - return f"{self.title} - {league}" + return f"{self.title} - {self.subtitle}" def get_absolute_url(self): return reverse("sports:event_detail", kwargs={"slug": self.uuid}) @property def subtitle(self) -> str: - return self.comp_str + league = self.league + if self.league and self.league.abbreviation_str: + league = self.league.abbreviation_str + return f"{league} {self.get_event_type_display()}" @property def comp_str(self) -> str: diff --git a/vrobbler/apps/videogames/models.py b/vrobbler/apps/videogames/models.py index df65e6d..66e1896 100644 --- a/vrobbler/apps/videogames/models.py +++ b/vrobbler/apps/videogames/models.py @@ -163,12 +163,9 @@ class VideoGame(LongPlayScrobblableMixin): platforms = models.ManyToManyField(VideoGamePlatform) retroarch_name = models.CharField(max_length=255, **BNULL) - def __str__(self): - return self.title - @property def subtitle(self): - return f" On {self.platforms.first()}" + return f"{self.platforms.first()}" @property def strings(self) -> ScrobblableConstants: diff --git a/vrobbler/templates/scrobbles/scrobble_detail.html b/vrobbler/templates/scrobbles/scrobble_detail.html index 86747c4..f1c6797 100644 --- a/vrobbler/templates/scrobbles/scrobble_detail.html +++ b/vrobbler/templates/scrobbles/scrobble_detail.html @@ -50,7 +50,11 @@

{% if object.media_type == "Video" %}🎬{% elif object.media_type == "Track" %}🎵{% elif object.media_type == "PodcastEpisode" %}🎙️{% elif object.media_type == "SportEvent" %}⚽{% elif object.media_type == "Book" %}📚{% elif object.media_type == "Paper" %}📄{% elif object.media_type == "VideoGame" %}🎮{% elif object.media_type == "BoardGame" %}🎲{% elif object.media_type == "GeoLocation" %}📍{% elif object.media_type == "Trail" %}🥾{% elif object.media_type == "Beer" %}🍺{% elif object.media_type == "Puzzle" %}🧩{% elif object.media_type == "Food" %}🍔{% elif object.media_type == "Task" %}✅{% elif object.media_type == "WebPage" %}🌐{% elif object.media_type == "LifeEvent" %}🎉{% elif object.media_type == "Mood" %}😊{% elif object.media_type == "BrickSet" %}🧱{% elif object.media_type == "Channel" %}📺{% endif %} -{% if object.media_obj.get_absolute_url %}{% endif %}{{ object.media_obj }}{% if object.media_obj.get_absolute_url %}{% endif %} +{% if object.media_obj.get_absolute_url %} + {% endif %} + {{ object.media_obj.title }} + {% if object.media_obj.get_absolute_url %} + {% endif %} {% if user.is_authenticated and object.media_obj %}

+

{{ object.media_obj.subtitle }}

+

+{% if object.media_type == "SportEvent" %} +{% for team in object.media_obj.teams.all %} + +{% endfor %} +{% endif %} {% if object.media_type == "Task" and object.logdata.title %} +

{{ object.logdata.title }}

{% endif %}

{{ object.local_timestamp }}

diff --git a/vrobbler/templates/sports/sportevent_detail.html b/vrobbler/templates/sports/sportevent_detail.html index a63ed0c..5777240 100644 --- a/vrobbler/templates/sports/sportevent_detail.html +++ b/vrobbler/templates/sports/sportevent_detail.html @@ -1,13 +1,16 @@ {% extends "base_detail.html" %} -{% block title %}{{object}}{% endblock %} +{% block title %}{{object.title}}{% endblock %} {% block details %} -

{{object.subtitle}}

+

{{object.league}} {{object.get_event_type_display}}

-

{{object.tv_series}}

+ {% for team in object.teams.all %} + + {% endfor %} +

Last scrobbles