This commit has no recorded session.
diff
commit b26470c2792792c960cf9a468ad0e14568bf148aAuthor: Colin Powell <colin@unbl.ink>Date: Wed Jun 17 10:59:42 2026 -0400 [videos] Fix channel templates@@ -88,7 +88,7 @@ fetching and simple saving. *** Metadata sources **** Scraper -* Backlog [2/22] :vrobbler:project:personal:+* Backlog [3/23] :vrobbler:project:personal: ** TODO [#C] Create small utility to clean up tracks scrobbled with wonky playback times :bug:music:scrobbles: :PROPERTIES: :ID: 702462cf-d54b-48c6-8a7c-78b8de751deb@@ -590,6 +590,10 @@ We should rename `email_scrobble_board_game` to reflect the fact that it's just a helper method to create board game scrobbles given a json blob. It's independent of the email flow it was originally creatdd for +** DONE [#A] Update youtube video detail pages with links to channel :videos:templates:+:PROPERTIES:+:ID: 8b87cb42-09e5-a3f5-136f-182f967fa81f+:END: ** DONE [#A] Concurrent reading trend does not consolidate on single book :trends:reading: :PROPERTIES: :ID: fe220f55-7e0d-2a17-2477-a5aa7c4a1f2c@@ -80,6 +80,28 @@ class Channel(ScrobblableMixin): def title(self): return self.name + @property+ def safe_cover_image_url(self) -> str:+ if self.cover_image:+ try:+ if self.cover_image.storage.exists(self.cover_image.name):+ return self.cover_medium.url+ except Exception:+ pass+ return "/static/images/not-found.jpg"++ @property+ def youtube_url(self) -> str:+ if self.youtube_id:+ return YOUTUBE_CHANNEL_URL + self.youtube_id+ return ""++ @property+ def twitch_url(self) -> str:+ if self.twitch_id:+ return f"https://www.twitch.tv/{self.twitch_id}"+ return ""+ def save_image_from_url(self, url: str, force_update: bool = False): if not self.cover_image or (force_update and url): r = requests.get(url)@@ -95,7 +117,7 @@ class Channel(ScrobblableMixin): played_query = models.Q() return Scrobble.objects.filter( played_query,- channel=self,+ models.Q(channel=self) | models.Q(video__channel=self), user=user_id, ).order_by("-timestamp") @@ -1,5 +1,7 @@ import datetime+ from django.contrib.auth.mixins import LoginRequiredMixin+from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator from django.utils import timezone from django.views import generic from scrobbles.models import Scrobble@@ -44,15 +46,31 @@ class SeriesDetailView(LoginRequiredMixin, ChartContextMixin, generic.DetailView return context_data -class ChannelDetailView(LoginRequiredMixin, generic.DetailView):+class ChannelDetailView(LoginRequiredMixin, ChartContextMixin, generic.DetailView): model = Channel slug_field = "uuid" template_name = "videos/channel_detail.html"+ paginate_by = 50 def get_context_data(self, **kwargs): user_id = self.request.user.id context_data = super().get_context_data(**kwargs)- context_data["scrobbles"] = self.object.scrobbles_for_user(user_id)++ scrobbles = self.object.scrobbles_for_user(user_id)+ paginator = Paginator(scrobbles, self.paginate_by)+ page_number = self.request.GET.get("page")++ try:+ page_obj = paginator.page(page_number)+ except PageNotAnInteger:+ page_obj = paginator.page(1)+ except EmptyPage:+ page_obj = paginator.page(paginator.num_pages)++ context_data["page_obj"] = page_obj+ context_data["scrobbles"] = page_obj.object_list+ context_data["is_paginated"] = paginator.num_pages > 1+ return context_data @@ -19,6 +19,26 @@ color:white; background:rgba(0,0,0,0.4); }+dl {+ display: flex;+ flex-flow: row wrap;+ padding-right:20px;+ border:none;+}+dt {+ flex-basis: 20%;+ padding: 5px;+ background: #3cf;+ text-align: right;+ color: #fff;+}+dd {+ flex-basis: 70%;+ flex-grow: 1;+ margin: 0;+ padding: 5px;+ border:none;+} </style> {% endblock %} @@ -29,9 +49,30 @@ <img src="{{ object.safe_cover_image_url }}" width="400px" /> </div> <div class="summary">- {% if object.youtube_id %}<p><a href="{{object.youtube_url}}" target="_blank">View on YouTube</a></p>{% endif %}+ {% if object.description %}<p><em>{{object.description}}</em></p>{% endif %}+ {% if object.genre.all %}+ <p>Genres: {% for tag in object.genre.all %}<span class="badge bg-secondary">{{tag.name}}</span> {% endfor %}</p>+ {% endif %}+ <hr />+ {% if object.youtube_id %}+ <p style="float:right;">+ <a href="{{object.youtube_url}}" target="_blank"><img src="{% static "images/youtube_logo.png" %}" width=35></a>+ </p>+ {% endif %}+ {% if object.twitch_id %}+ <p style="float:right;">+ <a href="{{object.twitch_url}}" target="_blank">View on Twitch</a>+ </p>+ {% endif %}+ </div>+</div>+{% if charts %}+<div class="row">+ <div class="col-md">+ {% include "scrobbles/_chart_links.html" %} </div> </div>+{% endif %} <div class="row"> <div class="col-md"> <h3>Last scrobbles</h3>@@ -41,6 +82,8 @@ <tr> <th scope="col">Date</th> <th scope="col">Title</th>+ <th scope="col">With</th>+ <th scope="col">Rated</th> </tr> </thead> <tbody>@@ -48,11 +91,26 @@ <tr> <td><a href={{scrobble.get_absolute_url}}>{{scrobble.local_timestamp}}</a></td> <td><a href="{{scrobble.media_obj.get_absolute_url}}">{{scrobble.media_obj.title}}</a></td>+ <td>{% firstof scrobble.logdata.with_people|join:", " "Solo" %}</td>+ <td>{% firstof scrobble.logdata.rating "Unrated" %}</td> </tr> {% endfor %} </tbody> </table> </div>+ {% if is_paginated %}+ <nav>+ <ul class="pagination">+ {% if page_obj.has_previous %}+ <li class="page-item"><a class="page-link" href="?page={{ page_obj.previous_page_number }}">Previous</a></li>+ {% endif %}+ <li class="page-item disabled"><span class="page-link">Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span></li>+ {% if page_obj.has_next %}+ <li class="page-item"><a class="page-link" href="?page={{ page_obj.next_page_number }}">Next</a></li>+ {% endif %}+ </ul>+ </nav>+ {% endif %} </div> </div> {% endblock %}@@ -63,6 +63,7 @@ dd { </div> <div class="summary"> {% if object.tv_series %}<h4><a href="{{object.tv_series.get_absolute_url}}">{{object.tv_series}}</a> - S{{object.season_number}}E{{object.episode_number}}</h4>{% endif %}+ {% if object.channel %}<h5><a href="{{object.channel.get_absolute_url}}">{{object.channel.name}}</a></h5>{% endif %} {% if object.overview %}<p><em>{{object.overview}}</em></p>{% endif %} {% if object.plot%}<p>{{object.plot|safe|linebreaks|truncatewords:160}}</p>{% endif %} <hr />