[videos] Fix channel templates
All checks were successful
build / test (push) Successful in 2m0s

This commit is contained in:
2026-06-17 10:59:42 -04:00
parent d3b9ec815b
commit b26470c279
5 changed files with 108 additions and 5 deletions

View File

@ -88,7 +88,7 @@ fetching and simple saving.
*** Metadata sources *** Metadata sources
**** Scraper **** 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: ** TODO [#C] Create small utility to clean up tracks scrobbled with wonky playback times :bug:music:scrobbles:
:PROPERTIES: :PROPERTIES:
:ID: 702462cf-d54b-48c6-8a7c-78b8de751deb :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 a helper method to create board game scrobbles given a json blob. It's
independent of the email flow it was originally creatdd for 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: ** DONE [#A] Concurrent reading trend does not consolidate on single book :trends:reading:
:PROPERTIES: :PROPERTIES:
:ID: fe220f55-7e0d-2a17-2477-a5aa7c4a1f2c :ID: fe220f55-7e0d-2a17-2477-a5aa7c4a1f2c

View File

@ -80,6 +80,28 @@ class Channel(ScrobblableMixin):
def title(self): def title(self):
return self.name 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): def save_image_from_url(self, url: str, force_update: bool = False):
if not self.cover_image or (force_update and url): if not self.cover_image or (force_update and url):
r = requests.get(url) r = requests.get(url)
@ -95,7 +117,7 @@ class Channel(ScrobblableMixin):
played_query = models.Q() played_query = models.Q()
return Scrobble.objects.filter( return Scrobble.objects.filter(
played_query, played_query,
channel=self, models.Q(channel=self) | models.Q(video__channel=self),
user=user_id, user=user_id,
).order_by("-timestamp") ).order_by("-timestamp")

View File

@ -1,5 +1,7 @@
import datetime import datetime
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from django.utils import timezone from django.utils import timezone
from django.views import generic from django.views import generic
from scrobbles.models import Scrobble from scrobbles.models import Scrobble
@ -44,15 +46,31 @@ class SeriesDetailView(LoginRequiredMixin, ChartContextMixin, generic.DetailView
return context_data return context_data
class ChannelDetailView(LoginRequiredMixin, generic.DetailView): class ChannelDetailView(LoginRequiredMixin, ChartContextMixin, generic.DetailView):
model = Channel model = Channel
slug_field = "uuid" slug_field = "uuid"
template_name = "videos/channel_detail.html" template_name = "videos/channel_detail.html"
paginate_by = 50
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
user_id = self.request.user.id user_id = self.request.user.id
context_data = super().get_context_data(**kwargs) 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 return context_data

View File

@ -19,6 +19,26 @@
color:white; color:white;
background:rgba(0,0,0,0.4); 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> </style>
{% endblock %} {% endblock %}
@ -29,9 +49,30 @@
<img src="{{ object.safe_cover_image_url }}" width="400px" /> <img src="{{ object.safe_cover_image_url }}" width="400px" />
</div> </div>
<div class="summary"> <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>
</div> </div>
{% if charts %}
<div class="row">
<div class="col-md">
{% include "scrobbles/_chart_links.html" %}
</div>
</div>
{% endif %}
<div class="row"> <div class="row">
<div class="col-md"> <div class="col-md">
<h3>Last scrobbles</h3> <h3>Last scrobbles</h3>
@ -41,6 +82,8 @@
<tr> <tr>
<th scope="col">Date</th> <th scope="col">Date</th>
<th scope="col">Title</th> <th scope="col">Title</th>
<th scope="col">With</th>
<th scope="col">Rated</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -48,11 +91,26 @@
<tr> <tr>
<td><a href={{scrobble.get_absolute_url}}>{{scrobble.local_timestamp}}</a></td> <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><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> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div> </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>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -63,6 +63,7 @@ dd {
</div> </div>
<div class="summary"> <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.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.overview %}<p><em>{{object.overview}}</em></p>{% endif %}
{% if object.plot%}<p>{{object.plot|safe|linebreaks|truncatewords:160}}</p>{% endif %} {% if object.plot%}<p>{{object.plot|safe|linebreaks|truncatewords:160}}</p>{% endif %}
<hr /> <hr />