[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 datetime
import json import json
import logging import logging
from collections import defaultdict
from typing import Optional from typing import Optional
from uuid import uuid4 from uuid import uuid4
@ -514,6 +515,10 @@ class Scrobble(TimeStampedModel):
MOOD = "Mood", "Mood" MOOD = "Mood", "Mood"
BRICKSET = "BrickSet", "Brick set" BRICKSET = "BrickSet", "Brick set"
@classmethod
def list(cls):
return list(map(lambda c: c.value, cls))
uuid = models.UUIDField(editable=False, **BNULL) uuid = models.UUIDField(editable=False, **BNULL)
video = models.ForeignKey(Video, on_delete=models.DO_NOTHING, **BNULL) video = models.ForeignKey(Video, on_delete=models.DO_NOTHING, **BNULL)
track = models.ForeignKey(Track, 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_seconds = models.BigIntegerField(**BNULL)
long_play_complete = models.BooleanField(**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 @property
def last_serial_scrobble(self) -> Optional["Scrobble"]: def last_serial_scrobble(self) -> Optional["Scrobble"]:
from scrobbles.models import Scrobble from scrobbles.models import Scrobble

View File

@ -3,9 +3,9 @@ import json
import logging import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta
import pendulum
import pytz import pytz
from django.apps import apps from django.apps import apps
from django.conf import settings
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Count, Q from django.db.models import Count, Q
@ -52,7 +52,6 @@ from scrobbles.tasks import (
from scrobbles.utils import ( from scrobbles.utils import (
get_long_plays_completed, get_long_plays_completed,
get_long_plays_in_progress, get_long_plays_in_progress,
get_recently_played_board_games,
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -107,32 +106,19 @@ class RecentScrobbleList(ListView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs) data = super().get_context_data(**kwargs)
user = self.request.user user = self.request.user
data["date"] = ""
if user.is_authenticated: 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( data = data | Scrobble.for_day_dict(user_id, year, month, day)
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["active_imports"] = AudioScrobblerTSVImport.objects.filter( data["active_imports"] = AudioScrobblerTSVImport.objects.filter(
processing_started__isnull=False, processing_started__isnull=False,
@ -698,45 +684,13 @@ class ChartRecordView(TemplateView):
media_type = self.request.GET.get("media", "Track") media_type = self.request.GET.get("media", "Track")
user = self.request.user user = self.request.user
params = {} params = {}
context_data["chart_type"] = self.request.GET.get(
"chart_type", "maloja"
)
context_data["artist_charts"] = {} context_data["artist_charts"] = {}
if not date: if not date:
limit = 20 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} artist = {"user": user, "media_type": "Artist", "limit": limit}
# This is weird. They don't display properly as QuerySets, so we cast to lists # This is weird. They don't display properly as QuerySets, so we cast to lists
context_data["chart_keys"] = { context_data["chart_keys"] = {

View File

@ -1,7 +1,6 @@
{% load humanize %} {% load humanize %}
{% load naturalduration %} {% load naturalduration %}
<div> <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> <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>
<div class="row"> <div class="row">
@ -30,6 +29,22 @@
<button class="nav-link" id="profile-tab" data-bs-toggle="tab" data-bs-target="#latest-boardgames" <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> type="button" role="tab" aria-controls="profile" aria-selected="false">Board Games</button>
</li> </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> </ul>
<div class="tab-content" id="myTabContent2"> <div class="tab-content" id="myTabContent2">
@ -46,9 +61,10 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for scrobble in object_list %} {{Track|length}}
{% for scrobble in Track %}
<tr> <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 %} {% 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> <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 %} {% else %}
@ -65,7 +81,6 @@
<div class="tab-pane fade show" id="latest-watched" role="tabpanel" <div class="tab-pane fade show" id="latest-watched" role="tabpanel"
aria-labelledby="latest-watched-tab"> aria-labelledby="latest-watched-tab">
<h2>Latest watched</h2>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped table-sm"> <table class="table table-striped table-sm">
<thead> <thead>
@ -77,9 +92,9 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for scrobble in video_scrobble_list %} {% for scrobble in Video %}
<tr> <tr>
<td>{{scrobble.timestamp|naturaltime}}</td> <td>{% if scrobble.in_progress %}Watching now{% else %}{{scrobble.timestamp|naturaltime}}{% endif %}</td>
{% if scrobble.video.cover_image %} {% 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> <td><img src="{{scrobble.media_obj.cover_image_medium.url}}" width=25 height=25 style="border:1px solid black;" /></td>
{% else %} {% else %}
@ -105,15 +120,17 @@
<th scope="col">Title</th> <th scope="col">Title</th>
<th scope="col">Round</th> <th scope="col">Round</th>
<th scope="col">League</th> <th scope="col">League</th>
<th scope="col">Time watched</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for scrobble in sport_scrobble_list %} {% for scrobble in SportEvent %}
<tr> <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.title}}</td>
<td>{{scrobble.sport_event.round.name}}</td> <td>{{scrobble.sport_event.round.name}}</td>
<td>{{scrobble.sport_event.round.season.league}}</td> <td>{{scrobble.sport_event.round.season.league}}</td>
<td>{{scrobble.playback_position_seconds|natural_duration}}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@ -123,7 +140,6 @@
<div class="tab-pane fade show" id="latest-podcasted" role="tabpanel" <div class="tab-pane fade show" id="latest-podcasted" role="tabpanel"
aria-labelledby="latest-podcasted-tab"> aria-labelledby="latest-podcasted-tab">
<h2>Latest Podcasted</h2>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped table-sm"> <table class="table table-striped table-sm">
<thead> <thead>
@ -134,8 +150,9 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for scrobble in podcast_scrobble_list %} {% for scrobble in PodcastEpisode %}
<tr> <tr>
<td>{% if scrobble.in_progress %}Listening now{% else %}{{scrobble.timestamp|naturaltime}}{% endif %}</td>
<td>{{scrobble.timestamp|naturaltime}}</td> <td>{{scrobble.timestamp|naturaltime}}</td>
<td>{{scrobble.podcast_episode.title}}</td> <td>{{scrobble.podcast_episode.title}}</td>
<td>{{scrobble.podcast_episode.podcast}}</td> <td>{{scrobble.podcast_episode.podcast}}</td>
@ -148,7 +165,6 @@
<div class="tab-pane fade show" id="latest-videogames" role="tabpanel" <div class="tab-pane fade show" id="latest-videogames" role="tabpanel"
aria-labelledby="latest-videogames-tab"> aria-labelledby="latest-videogames-tab">
<h2>Latest Video Games</h2>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped table-sm"> <table class="table table-striped table-sm">
<thead> <thead>
@ -156,14 +172,14 @@
<th scope="col">Date</th> <th scope="col">Date</th>
<th scope="col">Cover/Screenshot</th> <th scope="col">Cover/Screenshot</th>
<th scope="col">Title</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> <th scope="col">Percent complete</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for scrobble in videogame_scrobble_list %} {% for scrobble in VideoGame %}
<tr> <tr>
<td>{{scrobble.timestamp|naturaltime}}</td> <td>{% if scrobble.in_progress %}Sessioning now{% else %}{{scrobble.timestamp|naturaltime}}{% endif %}</td>
{% if scrobble.screenshot %} {% if scrobble.screenshot %}
<td><img src="{{scrobble.screenshot_medium.url}}" width=25 height=25 style="border:1px solid black;" /></td> <td><img src="{{scrobble.screenshot_medium.url}}" width=25 height=25 style="border:1px solid black;" /></td>
{% else %} {% else %}
@ -184,7 +200,6 @@
<div class="tab-pane fade show" id="latest-boardgames" role="tabpanel" <div class="tab-pane fade show" id="latest-boardgames" role="tabpanel"
aria-labelledby="latest-boardgames-tab"> aria-labelledby="latest-boardgames-tab">
<h2>Latest Board Games</h2>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped table-sm"> <table class="table table-striped table-sm">
<thead> <thead>
@ -192,13 +207,13 @@
<th scope="col">Date</th> <th scope="col">Date</th>
<th scope="col">Cover</th> <th scope="col">Cover</th>
<th scope="col">Title</th> <th scope="col">Title</th>
<th scope="col">Time played (mins)</th> <th scope="col">Time played</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for scrobble in boardgame_scrobble_list %} {% for scrobble in BoardGame %}
<tr> <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><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><a href="{{scrobble.media_obj.get_absolute_url}}">{{scrobble.media_obj.title}}</a></td>
<td>{{scrobble.playback_position_seconds|natural_duration}}</td> <td>{{scrobble.playback_position_seconds|natural_duration}}</td>
@ -208,5 +223,108 @@
</table> </table>
</div> </div>
</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>
</div> </div>