[templates] Adding some aggregation widgets
All checks were successful
build & deploy / test (push) Successful in 1m45s
build & deploy / deploy (push) Successful in 26s

This commit is contained in:
2026-03-18 10:23:30 -04:00
parent a8dc336950
commit 2e7d6364a2
8 changed files with 288 additions and 46 deletions

View File

@ -1,5 +1,8 @@
import datetime
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.utils import timezone
from django.views import generic from django.views import generic
from scrobbles.models import Scrobble
from videos.models import Channel, Series, Video from videos.models import Channel, Series, Video
from scrobbles.views import ScrobbleableListView, ScrobbleableDetailView from scrobbles.views import ScrobbleableListView, ScrobbleableDetailView
@ -50,6 +53,74 @@ class ChannelDetailView(LoginRequiredMixin, generic.DetailView):
class VideoListView(ScrobbleableListView): class VideoListView(ScrobbleableListView):
model = Video model = Video
def get_context_data(self, **kwargs):
context_data = super().get_context_data(**kwargs)
user = self.request.user
now = timezone.now()
start_day_of_week = now - datetime.timedelta(days=now.weekday())
start_day_of_month = now.replace(day=1)
movie_scrobbles = Scrobble.objects.filter(
user=user,
video__video_type=Video.VideoType.MOVIE,
played_to_completion=True,
)
context_data["videos_this_week"] = movie_scrobbles.filter(
timestamp__gte=start_day_of_week
).order_by("-timestamp")
context_data["videos_this_month"] = movie_scrobbles.filter(
timestamp__gte=start_day_of_month
).order_by("-timestamp")
youtube_scrobbles_this_week = Scrobble.objects.filter(
user=user,
video__video_type=Video.VideoType.YOUTUBE,
video__channel__isnull=False,
timestamp__gte=start_day_of_week,
).select_related("video", "video__channel")
youtube_scrobbles_this_month = Scrobble.objects.filter(
user=user,
video__video_type=Video.VideoType.YOUTUBE,
video__channel__isnull=False,
timestamp__gte=start_day_of_month,
).select_related("video", "video__channel")
channels_this_week = {}
for scrobble in youtube_scrobbles_this_week:
channel = scrobble.video.channel
if channel:
channels_this_week[channel.id] = {
"channel": channel,
"count": channels_this_week.get(channel.id, {}).get(
"count", 0
)
+ 1,
}
channels_this_month = {}
for scrobble in youtube_scrobbles_this_month:
channel = scrobble.video.channel
if channel:
channels_this_month[channel.id] = {
"channel": channel,
"count": channels_this_month.get(channel.id, {}).get(
"count", 0
)
+ 1,
}
context_data["channels_this_week"] = sorted(
channels_this_week.values(), key=lambda x: x["count"], reverse=True
)
context_data["channels_this_month"] = sorted(
channels_this_month.values(),
key=lambda x: x["count"],
reverse=True,
)
return context_data
class VideoDetailView(ScrobbleableDetailView): class VideoDetailView(ScrobbleableDetailView):
model = Video model = Video

View File

@ -1,17 +1,72 @@
import datetime
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.shortcuts import redirect from django.shortcuts import redirect
from django.urls import reverse from django.urls import reverse
from django.utils import timezone
from django.views import generic from django.views import generic
from webpages.forms import WebPageReadForm from webpages.forms import WebPageReadForm
from webpages.models import WebPage from webpages.models import WebPage
from scrobbles.models import Scrobble
from scrobbles.views import ScrobbleableListView, ScrobbleableDetailView from scrobbles.views import ScrobbleableListView, ScrobbleableDetailView
class WebPageListView(ScrobbleableListView): class WebPageListView(ScrobbleableListView):
model = WebPage model = WebPage
def get_context_data(self, **kwargs):
context_data = super().get_context_data(**kwargs)
user = self.request.user
now = timezone.now()
start_day_of_week = now - datetime.timedelta(days=now.weekday())
start_day_of_month = now.replace(day=1)
scrobbles_this_week = Scrobble.objects.filter(
user=user,
web_page__isnull=False,
timestamp__gte=start_day_of_week,
).select_related("web_page", "web_page__domain")
scrobbles_this_month = Scrobble.objects.filter(
user=user,
web_page__isnull=False,
timestamp__gte=start_day_of_month,
).select_related("web_page", "web_page__domain")
domains_this_week = {}
for scrobble in scrobbles_this_week:
domain = scrobble.web_page.domain
if domain:
domains_this_week[domain.id] = {
"domain": domain,
"count": domains_this_week.get(domain.id, {}).get(
"count", 0
)
+ 1,
}
domains_this_month = {}
for scrobble in scrobbles_this_month:
domain = scrobble.web_page.domain
if domain:
domains_this_month[domain.id] = {
"domain": domain,
"count": domains_this_month.get(domain.id, {}).get(
"count", 0
)
+ 1,
}
context_data["domains_this_week"] = sorted(
domains_this_week.values(), key=lambda x: x["count"], reverse=True
)
context_data["domains_this_month"] = sorted(
domains_this_month.values(), key=lambda x: x["count"], reverse=True
)
return context_data
class WebPageDetailView(ScrobbleableDetailView): class WebPageDetailView(ScrobbleableDetailView):
model = WebPage model = WebPage

View File

@ -5,9 +5,21 @@
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"> <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">{% block title %}{% endblock %} </h1> <h1 class="h2">{% block title %}{% endblock %} </h1>
<div class="btn-toolbar mb-2 mb-md-0"> <div class="btn-toolbar mb-2 mb-md-0">
{% if user.is_authenticated %}
<div class="btn-group me-2"> <div class="btn-group me-2">
<button type="button" class="btn btn-sm btn-outline-secondary">Export</button> <form action="/admin/" method="get">
<button type="submit" class="btn btn-sm btn-outline-secondary">
<span data-feather="key"></span>
Admin
</button>
</form>
</div> </div>
<div class="btn-group me-2">
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="modal"
data-bs-target="#exportModal">Export</button>
{% block charts_button %}{% endblock %}
</div>
{% endif %}
</div> </div>
</div> </div>
@ -16,3 +28,6 @@
</div> </div>
</main> </main>
{% endblock %} {% endblock %}
{% block extra_modals %}
{% endblock %}

View File

@ -7,14 +7,24 @@
class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom"> class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">{% block title %}{% endblock %} </h1> <h1 class="h2">{% block title %}{% endblock %} </h1>
<div class="btn-toolbar mb-2 mb-md-0"> <div class="btn-toolbar mb-2 mb-md-0">
{% if user.is_authenticated %}
<div class="btn-group me-2">
<form action="/admin/" method="get">
<button type="submit" class="btn btn-sm btn-outline-secondary">
<span data-feather="key"></span>
Admin
</button>
</form>
</div>
{% endif %}
<div class="btn-group me-2"> <div class="btn-group me-2">
{% if view == 'grid' %} {% if view == 'grid' %}
<button type="button" class="btn btn-sm btn-outline-secondary"><a href="?{% urlreplace view='list' %}">List View</a> <button type="button" class="btn btn-sm btn-outline-secondary"><a href="?{% urlreplace view='list' %}">List View</a>
{% else %} {% else %}
<button type="button" class="btn btn-sm btn-outline-secondary"><a href="?{% urlreplace view='grid' %}">Grid View</a> <button type="button" class="btn btn-sm btn-outline-secondary"><a href="?{% urlreplace view='grid' %}">Grid View</a>
{% endif %} {% endif %}
<button type="button" class="btn btn-sm btn-outline-secondary">Export</button>
</div> </div>
{% block charts_button %}{% endblock %}
</div> </div>
</div> </div>

View File

@ -642,47 +642,3 @@
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
{% if videos_this_week or videos_this_month %}
<div class="col-md">
<h2>Movies Watched</h2>
{% if videos_this_week %}
<h3>This Week</h3>
<div class="table-responsive">
<table class="table table-striped table-sm">
<tbody>
{% for scrobble in videos_this_week %}
<tr>
<td><a href="{{scrobble.video.get_absolute_url}}">{{scrobble.video}}</a></td>
<td>{{scrobble.timestamp|date:"N d"}}</td>
</tr>
{% empty %}
<tr><td>No movies watched this week</td></tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% if videos_this_month %}
<h3>This Month</h3>
<div class="table-responsive">
<table class="table table-striped table-sm">
<tbody>
{% for scrobble in videos_this_month %}
<tr>
<td><a href="{{scrobble.video.get_absolute_url}}">{{scrobble.video}}</a></td>
<td>{{scrobble.timestamp|date:"N d"}}</td>
</tr>
{% empty %}
<tr><td>No movies watched this month</td></tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
{% endif %}
</div>

View File

@ -95,7 +95,9 @@
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="modal" <button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="modal"
data-bs-target="#exportModal">Export</button> data-bs-target="#exportModal">Export</button>
</div> </div>
{% block charts_button %}
<a href="{% url 'scrobbles:charts-home' %}" class="btn btn-sm btn-outline-secondary">Charts</a> <a href="{% url 'scrobbles:charts-home' %}" class="btn btn-sm btn-outline-secondary">Charts</a>
{% endblock %}
{% endif %} {% endif %}
<div class="dropdown"> <div class="dropdown">
<button type="button" class="btn btn-sm btn-outline-secondary dropdown-toggle" id="graphDateButton" <button type="button" class="btn btn-sm btn-outline-secondary dropdown-toggle" id="graphDateButton"

View File

@ -24,6 +24,94 @@
{% endblock %} {% endblock %}
{% block lists %} {% block lists %}
{% if videos_this_week or videos_this_month or channels_this_week or channels_this_month %}
<div class="row">
{% if videos_this_week or videos_this_month %}
<div class="col-md">
<h2>Movies Watched</h2>
{% if videos_this_week %}
<h3>This Week</h3>
<div class="table-responsive">
<table class="table table-striped table-sm">
<tbody>
{% for scrobble in videos_this_week %}
<tr>
<td><a href="{{scrobble.video.get_absolute_url}}">{{scrobble.video}}</a></td>
<td>{{scrobble.timestamp|date:"N d"}}</td>
</tr>
{% empty %}
<tr><td>No movies watched this week</td></tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% if videos_this_month %}
<h3>This Month</h3>
<div class="table-responsive">
<table class="table table-striped table-sm">
<tbody>
{% for scrobble in videos_this_month %}
<tr>
<td><a href="{{scrobble.video.get_absolute_url}}">{{scrobble.video}}</a></td>
<td>{{scrobble.timestamp|date:"N d"}}</td>
</tr>
{% empty %}
<tr><td>No movies watched this month</td></tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
{% endif %}
{% if channels_this_week or channels_this_month %}
<div class="col-md">
<h2>YouTube Channels</h2>
{% if channels_this_week %}
<h3>This Week</h3>
<div class="table-responsive">
<table class="table table-striped table-sm">
<tbody>
{% for item in channels_this_week %}
<tr>
<td>{{item.channel}}</td>
<td>{{item.count}} videos</td>
</tr>
{% empty %}
<tr><td>No YouTube videos watched this week</td></tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% if channels_this_month %}
<h3>This Month</h3>
<div class="table-responsive">
<table class="table table-striped table-sm">
<tbody>
{% for item in channels_this_month %}
<tr>
<td>{{item.channel}}</td>
<td>{{item.count}} videos</td>
</tr>
{% empty %}
<tr><td>No YouTube videos watched this month</td></tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
{% endif %}
</div>
{% endif %}
<div class="row"> <div class="row">
<div class="col-md"> <div class="col-md">
<div class="table-responsive">{% include "_scrobblable_list.html" %}</div> <div class="table-responsive">{% include "_scrobblable_list.html" %}</div>

View File

@ -4,6 +4,51 @@
{% block title %}Webpages{% endblock %} {% block title %}Webpages{% endblock %}
{% block lists %} {% block lists %}
{% if domains_this_week or domains_this_month %}
<div class="row">
<div class="col-md">
<h2>Domains Read</h2>
{% if domains_this_week %}
<h3>This Week</h3>
<div class="table-responsive">
<table class="table table-striped table-sm">
<tbody>
{% for item in domains_this_week %}
<tr>
<td>{{item.domain}}</td>
<td>{{item.count}} pages</td>
</tr>
{% empty %}
<tr><td>No pages read this week</td></tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% if domains_this_month %}
<h3>This Month</h3>
<div class="table-responsive">
<table class="table table-striped table-sm">
<tbody>
{% for item in domains_this_month %}
<tr>
<td>{{item.domain}}</td>
<td>{{item.count}} pages</td>
</tr>
{% empty %}
<tr><td>No pages read this month</td></tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div>
</div>
{% endif %}
<div class="row"> <div class="row">
<p class="pagination"> <p class="pagination">
<span class="page-links"> <span class="page-links">