[templates] Start redesigning the dashboard

This commit is contained in:
2025-05-18 23:20:47 -04:00
parent d4377e49ac
commit 2a254e28d0
3 changed files with 207 additions and 79 deletions

View File

@ -2,6 +2,7 @@ import calendar
import datetime
import json
import logging
from collections import defaultdict
from typing import Optional
from uuid import uuid4
@ -514,6 +515,10 @@ class Scrobble(TimeStampedModel):
MOOD = "Mood", "Mood"
BRICKSET = "BrickSet", "Brick set"
@classmethod
def list(cls):
return list(map(lambda c: c.value, cls))
uuid = models.UUIDField(editable=False, **BNULL)
video = models.ForeignKey(Video, on_delete=models.DO_NOTHING, **BNULL)
track = models.ForeignKey(Track, on_delete=models.DO_NOTHING, **BNULL)
@ -597,6 +602,57 @@ class Scrobble(TimeStampedModel):
long_play_seconds = models.BigIntegerField(**BNULL)
long_play_complete = models.BooleanField(**BNULL)
@classmethod
def for_year(cls, user, year):
return cls.objects.filter(timestamp__year=year, user=user)
@classmethod
def for_month(cls, user, year, month):
return cls.objects.filter(
timestamp__year=year, timestamp__month=month, user=user
)
@classmethod
def for_day(cls, user, year, day, month):
return cls.objects.filter(
timestamp__year=year,
timestamp__month=month,
timestamp__day=day,
user=user,
)
@classmethod
def for_day_dict(
cls, user_id: int, year: int, month: int, day: int
) -> dict:
scrobbles_by_type = defaultdict(list)
scrobbles = cls.objects.filter(
timestamp__year=year,
timestamp__month=month,
timestamp__day=day,
user_id=user_id,
).order_by("-timestamp")
for scrobble in scrobbles:
scrobbles_by_type[scrobble.media_type].append(scrobble)
return scrobbles_by_type
@classmethod
def for_week(cls, user, year, week):
return cls.objects.filter(
timestamp__year=year, timestamp__week=week, user=user
)
@classmethod
def in_progress_for_user(cls, user_id: int) -> models.QuerySet:
return cls.objects.filter(
user=user_id,
in_progress=True,
played_to_completion=False,
is_paused=False,
)
@property
def last_serial_scrobble(self) -> Optional["Scrobble"]:
from scrobbles.models import Scrobble

View File

@ -3,9 +3,9 @@ import json
import logging
from datetime import datetime, timedelta
import pendulum
import pytz
from django.apps import apps
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Count, Q
@ -52,7 +52,6 @@ from scrobbles.tasks import (
from scrobbles.utils import (
get_long_plays_completed,
get_long_plays_in_progress,
get_recently_played_board_games,
)
logger = logging.getLogger(__name__)
@ -107,32 +106,19 @@ class RecentScrobbleList(ListView):
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
user = self.request.user
data["date"] = ""
if user.is_authenticated:
date = self.request.GET.get("date", "")
today = timezone.localtime(timezone.now())
user_id = self.request.user.id
year = today.year
month = today.month
day = today.day
if date:
year, month, day = date.split("-")
data["date"] = pendulum.parse(date)
completed_for_user = Scrobble.objects.filter(
played_to_completion=True, user=user
)
data["long_play_in_progress"] = get_long_plays_in_progress(user)
data["play_again"] = get_recently_played_board_games(user)
data["video_scrobble_list"] = completed_for_user.filter(
video__isnull=False
).order_by("-timestamp")[:15]
data["podcast_scrobble_list"] = completed_for_user.filter(
podcast_episode__isnull=False
).order_by("-timestamp")[:15]
data["sport_scrobble_list"] = completed_for_user.filter(
sport_event__isnull=False
).order_by("-timestamp")[:15]
data["videogame_scrobble_list"] = completed_for_user.filter(
video_game__isnull=False
).order_by("-timestamp")[:15]
data["boardgame_scrobble_list"] = completed_for_user.filter(
board_game__isnull=False
).order_by("-timestamp")[:15]
data = data | Scrobble.for_day_dict(user_id, year, month, day)
data["active_imports"] = AudioScrobblerTSVImport.objects.filter(
processing_started__isnull=False,
@ -698,45 +684,13 @@ class ChartRecordView(TemplateView):
media_type = self.request.GET.get("media", "Track")
user = self.request.user
params = {}
context_data["chart_type"] = self.request.GET.get(
"chart_type", "maloja"
)
context_data["artist_charts"] = {}
if not date:
limit = 20
artist_params = {"user": user, "media_type": "Artist"}
context_data["current_artist_charts"] = {
"today": live_charts(
**artist_params, chart_period="today", limit=limit
),
"week": live_charts(
**artist_params, chart_period="week", limit=limit
),
"month": live_charts(
**artist_params, chart_period="month", limit=limit
),
"year": live_charts(
**artist_params, chart_period="year", limit=limit
),
"all": live_charts(**artist_params, limit=limit),
}
track_params = {"user": user, "media_type": "Track"}
context_data["current_track_charts"] = {
"today": live_charts(
**track_params, chart_period="today", limit=limit
),
"week": live_charts(
**track_params, chart_period="week", limit=limit
),
"month": live_charts(
**track_params, chart_period="month", limit=limit
),
"year": live_charts(
**track_params, chart_period="year", limit=limit
),
"all": live_charts(**track_params, limit=limit),
}
limit = 14
artist = {"user": user, "media_type": "Artist", "limit": limit}
# This is weird. They don't display properly as QuerySets, so we cast to lists
context_data["chart_keys"] = {

View File

@ -1,7 +1,6 @@
{% load humanize %}
{% load naturalduration %}
<div>
<h2>Last Scrobbles</h2>
<p>Today <b>{{counts.today}}</b> | This Week <b>{{counts.week}}</b> | This Month <b>{{counts.month}}</b> | This Year <b>{{counts.year}}</b> | All Time <b>{{counts.alltime}}</b></p>
</div>
<div class="row">
@ -30,6 +29,22 @@
<button class="nav-link" id="profile-tab" data-bs-toggle="tab" data-bs-target="#latest-boardgames"
type="button" role="tab" aria-controls="profile" aria-selected="false">Board Games</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="profile-tab" data-bs-toggle="tab" data-bs-target="#latest-webpages"
type="button" role="tab" aria-controls="profile" aria-selected="false">Web Pages</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="profile-tab" data-bs-toggle="tab" data-bs-target="#latest-beers"
type="button" role="tab" aria-controls="profile" aria-selected="false">Beers</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="profile-tab" data-bs-toggle="tab" data-bs-target="#latest-books"
type="button" role="tab" aria-controls="profile" aria-selected="false">Books</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="profile-tab" data-bs-toggle="tab" data-bs-target="#latest-tasks"
type="button" role="tab" aria-controls="profile" aria-selected="false">Tasks</button>
</li>
</ul>
<div class="tab-content" id="myTabContent2">
@ -46,9 +61,10 @@
</tr>
</thead>
<tbody>
{% for scrobble in object_list %}
{{Track|length}}
{% for scrobble in Track %}
<tr>
<td>{{scrobble.timestamp|naturaltime}}</td>
<td>{% if scrobble.in_progress %}Listening now{% else %}{{scrobble.timestamp|naturaltime}}{% endif %}</td>
{% if scrobble.track.album.cover_image %}
<td><a href="{{scrobble.track.album.get_absolute_url}}"><img src="{{scrobble.track.album.cover_image_small.url}}" width=25 height=25 style="border:1px solid black;" /></aa></td>
{% else %}
@ -65,7 +81,6 @@
<div class="tab-pane fade show" id="latest-watched" role="tabpanel"
aria-labelledby="latest-watched-tab">
<h2>Latest watched</h2>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
@ -77,9 +92,9 @@
</tr>
</thead>
<tbody>
{% for scrobble in video_scrobble_list %}
{% for scrobble in Video %}
<tr>
<td>{{scrobble.timestamp|naturaltime}}</td>
<td>{% if scrobble.in_progress %}Watching now{% else %}{{scrobble.timestamp|naturaltime}}{% endif %}</td>
{% if scrobble.video.cover_image %}
<td><img src="{{scrobble.media_obj.cover_image_medium.url}}" width=25 height=25 style="border:1px solid black;" /></td>
{% else %}
@ -105,15 +120,17 @@
<th scope="col">Title</th>
<th scope="col">Round</th>
<th scope="col">League</th>
<th scope="col">Time watched</th>
</tr>
</thead>
<tbody>
{% for scrobble in sport_scrobble_list %}
{% for scrobble in SportEvent %}
<tr>
<td>{{scrobble.timestamp|naturaltime}}</td>
<td>{% if scrobble.in_progress %}Watching now{% else %}{{scrobble.timestamp|naturaltime}}{% endif %}</td>
<td>{{scrobble.sport_event.title}}</td>
<td>{{scrobble.sport_event.round.name}}</td>
<td>{{scrobble.sport_event.round.season.league}}</td>
<td>{{scrobble.playback_position_seconds|natural_duration}}</td>
</tr>
{% endfor %}
</tbody>
@ -123,7 +140,6 @@
<div class="tab-pane fade show" id="latest-podcasted" role="tabpanel"
aria-labelledby="latest-podcasted-tab">
<h2>Latest Podcasted</h2>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
@ -134,8 +150,9 @@
</tr>
</thead>
<tbody>
{% for scrobble in podcast_scrobble_list %}
{% for scrobble in PodcastEpisode %}
<tr>
<td>{% if scrobble.in_progress %}Listening now{% else %}{{scrobble.timestamp|naturaltime}}{% endif %}</td>
<td>{{scrobble.timestamp|naturaltime}}</td>
<td>{{scrobble.podcast_episode.title}}</td>
<td>{{scrobble.podcast_episode.podcast}}</td>
@ -148,7 +165,6 @@
<div class="tab-pane fade show" id="latest-videogames" role="tabpanel"
aria-labelledby="latest-videogames-tab">
<h2>Latest Video Games</h2>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
@ -156,14 +172,14 @@
<th scope="col">Date</th>
<th scope="col">Cover/Screenshot</th>
<th scope="col">Title</th>
<th scope="col">Time played (mins)</th>
<th scope="col">Time played</th>
<th scope="col">Percent complete</th>
</tr>
</thead>
<tbody>
{% for scrobble in videogame_scrobble_list %}
{% for scrobble in VideoGame %}
<tr>
<td>{{scrobble.timestamp|naturaltime}}</td>
<td>{% if scrobble.in_progress %}Sessioning now{% else %}{{scrobble.timestamp|naturaltime}}{% endif %}</td>
{% if scrobble.screenshot %}
<td><img src="{{scrobble.screenshot_medium.url}}" width=25 height=25 style="border:1px solid black;" /></td>
{% else %}
@ -184,7 +200,6 @@
<div class="tab-pane fade show" id="latest-boardgames" role="tabpanel"
aria-labelledby="latest-boardgames-tab">
<h2>Latest Board Games</h2>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
@ -192,13 +207,13 @@
<th scope="col">Date</th>
<th scope="col">Cover</th>
<th scope="col">Title</th>
<th scope="col">Time played (mins)</th>
<th scope="col">Time played</th>
</tr>
</thead>
<tbody>
{% for scrobble in boardgame_scrobble_list %}
{% for scrobble in BoardGame %}
<tr>
<td>{{scrobble.timestamp|naturaltime}}</td>
<td>{% if scrobble.in_progress %}Tabling now{% else %}{{scrobble.timestamp|naturaltime}}{% endif %}</td>
<td><img src="{{scrobble.media_obj.cover_medium.url}}" width=25 height=25 style="border:1px solid black;" /></td>
<td><a href="{{scrobble.media_obj.get_absolute_url}}">{{scrobble.media_obj.title}}</a></td>
<td>{{scrobble.playback_position_seconds|natural_duration}}</td>
@ -208,5 +223,108 @@
</table>
</div>
</div>
<div class="tab-pane fade show" id="latest-webpages" role="tabpanel"
aria-labelledby="latest-webpages-tab">
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">Date</th>
<th scope="col">Cover</th>
<th scope="col">Title</th>
<th scope="col">Time browsing</th>
</tr>
</thead>
<tbody>
{% for scrobble in WebPage %}
<tr>
<td>{% if scrobble.in_progress %}Browsing now{% else %}{{scrobble.timestamp|naturaltime}}{% endif %}</td>
<td><img src="{{scrobble.media_obj.cover_medium.url}}" width=25 height=25 style="border:1px solid black;" /></td>
<td><a href="{{scrobble.media_obj.get_absolute_url}}">{{scrobble.media_obj.title}}</a></td>
<td>{{scrobble.playback_position_seconds|natural_duration}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="tab-pane fade show" id="latest-beers" role="tabpanel"
aria-labelledby="latest-beers-tab">
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">Date</th>
<th scope="col">Cover</th>
<th scope="col">Title</th>
<th scope="col">Time drinking</th>
</tr>
</thead>
<tbody>
{% for scrobble in Beer %}
<tr>
<td>{% if scrobble.in_progress %}Drinking now{% else %}{{scrobble.timestamp|naturaltime}}{% endif %}</td>
<td><img src="{{scrobble.media_obj.cover_medium.url}}" width=25 height=25 style="border:1px solid black;" /></td>
<td><a href="{{scrobble.media_obj.get_absolute_url}}">{{scrobble.media_obj.title}}</a></td>
<td>{{scrobble.playback_position_seconds|natural_duration}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="tab-pane fade show" id="latest-books" role="tabpanel"
aria-labelledby="latest-books-tab">
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">Date</th>
<th scope="col">Cover</th>
<th scope="col">Title</th>
<th scope="col">Time reading</th>
</tr>
</thead>
<tbody>
{% for scrobble in Book %}
<tr>
<td>{% if scrobble.in_progress %}Reading now{% else %}{{scrobble.timestamp|naturaltime}}{% endif %}</td>
<td><img src="{scrobble.media_obj.cover_medium.url}}" width=25 height=25 style="border:1px solid black;" /></td>
<td><a href="{{scrobble.media_obj.get_absolute_url}}">{{scrobble.media_obj.title}}</a></td>
<td>{{scrobble.playback_position_seconds|natural_duration}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="tab-pane fade show" id="latest-tasks" role="tabpanel"
aria-labelledby="latest-tasks-tab">
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">Date</th>
<th scope="col">Title</th>
<th scope="col">Time doing</th>
</tr>
</thead>
<tbody>
{% for scrobble in Task %}
<tr>
<td>{% if scrobble.in_progress %}Doing now{% else %}{{scrobble.timestamp|naturaltime}}{% endif %}</td>
<td><a href="{{scrobble.media_obj.get_absolute_url}}">{{scrobble.media_obj.title}}</a></td>
<td>{{scrobble.playback_position_seconds|natural_duration}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>