Compare commits

...

11 Commits

15 changed files with 595 additions and 442 deletions

15
poetry.lock generated
View File

@ -2228,6 +2228,7 @@ python-versions = "*"
groups = ["main"] groups = ["main"]
files = [ files = [
{file = "jusText-3.0.1-py2.py3-none-any.whl", hash = "sha256:e0fb882dd7285415709f4b7466aed23d6b98b7b89404c36e8a2e730facfed02b"}, {file = "jusText-3.0.1-py2.py3-none-any.whl", hash = "sha256:e0fb882dd7285415709f4b7466aed23d6b98b7b89404c36e8a2e730facfed02b"},
{file = "justext-3.0.1-py2.py3-none-any.whl", hash = "sha256:0a5225c5cd7c5a124fec7bfa9a55110a73135e8b58ce784470af67d051ac9fd3"},
{file = "justext-3.0.1.tar.gz", hash = "sha256:b6ed2fb6c5d21618e2e34b2295c4edfc0bcece3bd549ed5c8ef5a8d20f0b3451"}, {file = "justext-3.0.1.tar.gz", hash = "sha256:b6ed2fb6c5d21618e2e34b2295c4edfc0bcece3bd549ed5c8ef5a8d20f0b3451"},
] ]
@ -2891,6 +2892,18 @@ rsa = ["cryptography (>=3.0.0)"]
signals = ["blinker (>=1.4.0)"] signals = ["blinker (>=1.4.0)"]
signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"]
[[package]]
name = "orgparse"
version = "0.4.20250520"
description = "orgparse - Emacs org-mode parser in Python"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
{file = "orgparse-0.4.20250520-py3-none-any.whl", hash = "sha256:24d454432385016ae91c3518c8357a3a31fdd4cebfbb0c5926cf31247bf4c7e3"},
{file = "orgparse-0.4.20250520.tar.gz", hash = "sha256:6472fd16ddcabb523918505c865263abfa05ac80b593e668a089cda291b1a2de"},
]
[[package]] [[package]]
name = "packaging" name = "packaging"
version = "24.2" version = "24.2"
@ -5439,4 +5452,4 @@ cffi = ["cffi (>=1.11)"]
[metadata] [metadata]
lock-version = "2.1" lock-version = "2.1"
python-versions = ">=3.9,<3.12" python-versions = ">=3.9,<3.12"
content-hash = "17358679b06dd15b7f119307013ed578ef81dbf31c592bc2ca11d13787a81215" content-hash = "cdd7f577fe3a4c5c8cc960e0070d93b7ddbb2a7968fab63d72bb039afaa05bbe"

View File

@ -53,6 +53,7 @@ django-oauth-toolkit = "^3.0.1"
meta-yt = "^0.1.9" meta-yt = "^0.1.9"
berserk = "^0.13.2" berserk = "^0.13.2"
poetry-bumpversion = "^0.3.3" poetry-bumpversion = "^0.3.3"
orgparse = "^0.4.20250520"
[tool.poetry.group.test] [tool.poetry.group.test]
optional = true optional = true

View File

@ -307,8 +307,15 @@ class Album(TimeStampedModel):
self.save(update_fields=["album_artist"]) self.save(update_fields=["album_artist"])
def scrape_allmusic(self, force=False) -> None: def scrape_allmusic(self, force=False) -> None:
if not self.name:
logger.warning(
"Album without a name cannot be scraped",
extra={"album_id": self.id},
)
return
if not self.allmusic_id or force: if not self.allmusic_id or force:
slug = get_allmusic_slug(self.name, self.album_artist.name) slug = get_allmusic_slug(self.album_artist.name, self.name)
if not slug: if not slug:
logger.info( logger.info(
f"No allmsuic link for {self} by {self.album_artist}" f"No allmsuic link for {self} by {self.album_artist}"
@ -317,7 +324,9 @@ class Album(TimeStampedModel):
self.allmusic_id = slug self.allmusic_id = slug
self.save(update_fields=["allmusic_id"]) self.save(update_fields=["allmusic_id"])
allmusic_data = scrape_data_from_allmusic(self.allmusic_link) allmusic_data = None
if self.allmusic_link:
allmusic_data = scrape_data_from_allmusic(self.allmusic_link)
if not allmusic_data: if not allmusic_data:
logger.info(f"No allmsuic data for {self} by {self.album_artist}") logger.info(f"No allmsuic data for {self} by {self.album_artist}")

View File

@ -0,0 +1,28 @@
import logging
from django.conf import settings
from django.utils import timezone
logger = logging.getLogger(__name__)
class TimezoneMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def __getUserTimeZone(self, request):
# info = IPResolver(request).getGeoInfo()
# return pytz.country_timezones[info["country_code"]][0]
if not request.user.is_anonymous:
return request.user.profile.timezone
def process_request(self, request):
try:
tz = self.__getUserTimeZone(request) or settings.TIME_ZONE
timezone.activate(tz)
logger.info('Time zone "%s" activated' % str(tz))
except Exception as e:
logger.error("Unable to set timezone: %s" % str(e))

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,51 @@ 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).order_by(
"-timestamp"
)
@classmethod
def for_month(cls, user, year, month):
return cls.objects.filter(
timestamp__year=year, timestamp__month=month, user=user
).order_by("-timestamp")
@classmethod
def for_day(cls, user, year, month, day):
return cls.objects.filter(
timestamp__year=year,
timestamp__month=month,
timestamp__day=day,
user=user,
).order_by("-timestamp")
@classmethod
def for_week(cls, user, year, week):
return cls.objects.filter(
timestamp__year=year, timestamp__week=week, user=user
).order_by("-timestamp")
@classmethod
def as_dict_by_type(cls, scrobble_qs: models.QuerySet) -> dict:
scrobbles_by_type = defaultdict(list)
for scrobble in scrobble_qs:
scrobbles_by_type[scrobble.media_type].append(scrobble)
return scrobbles_by_type
@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
@ -788,6 +838,15 @@ class Scrobble(TimeStampedModel):
def is_long_play(self) -> bool: def is_long_play(self) -> bool:
return self.media_obj.__class__.__name__ in LONG_PLAY_MEDIA.values() return self.media_obj.__class__.__name__ in LONG_PLAY_MEDIA.values()
@property
def elapsed_time(self) -> int | None:
if self.played_to_completion:
if self.playback_position_seconds:
return self.playback_position_seconds
if self.media_obj.run_time_seconds:
return self.media_obj.run_time_seconds
return (timezone.now() - self.timestamp).seconds
@property @property
def percent_played(self) -> int: def percent_played(self) -> int:
if not self.media_obj: if not self.media_obj:
@ -801,7 +860,7 @@ class Scrobble(TimeStampedModel):
playback_seconds = self.playback_position_seconds playback_seconds = self.playback_position_seconds
if not playback_seconds: if not playback_seconds:
playback_seconds = (timezone.now() - self.timestamp).seconds playback_seconds = self.elapsed_time
run_time_secs = self.media_obj.run_time_seconds run_time_secs = self.media_obj.run_time_seconds
percent = int((playback_seconds / run_time_secs) * 100) percent = int((playback_seconds / run_time_secs) * 100)
@ -917,6 +976,16 @@ class Scrobble(TimeStampedModel):
) )
return True return True
@classmethod
def by_date(cls, media_type: str = "Track"):
cls.objects.filter(media_type=media_type).values(
"timestamp__date"
).annotate(count=models.Count("id")).values(
"timestamp__date", "count"
).order_by(
"-count",
)
@property @property
def media_obj(self): def media_obj(self):
media_obj = None media_obj = None

View File

@ -36,6 +36,11 @@ urlpatterns = [
views.web_scrobbler_webhook, views.web_scrobbler_webhook,
name="web-scrobbler-webhook", name="web-scrobbler-webhook",
), ),
path(
"webhook/emacs/",
views.emacs_webhook,
name="emacs-webhook",
),
path( path(
"webhook/gps/", "webhook/gps/",
views.gps_webhook, views.gps_webhook,

View File

@ -2,10 +2,11 @@ import calendar
import json import json
import logging import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
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 +53,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 +107,94 @@ 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:
self.queryset = self.get_queryset().filter(user=user)
user_id = self.request.user.id
completed_for_user = Scrobble.objects.filter( today = timezone.localtime(timezone.now())
played_to_completion=True, user=user date_str = self.request.GET.get("date", "")
) date = today
data["long_play_in_progress"] = get_long_plays_in_progress(user) if date_str:
data["play_again"] = get_recently_played_board_games(user) try:
data["video_scrobble_list"] = completed_for_user.filter( date = pendulum.parse(date_str)
video__isnull=False except:
).order_by("-timestamp")[:15] pass
if date_str:
data["podcast_scrobble_list"] = completed_for_user.filter( if date_str == "this_week" or "-W" in date_str:
podcast_episode__isnull=False next_date = date + timedelta(weeks=+2)
).order_by("-timestamp")[:15] prev_date = date + timedelta(weeks=-1)
if date.isocalendar()[1] < today.isocalendar()[1]:
data["sport_scrobble_list"] = completed_for_user.filter( data[
sport_event__isnull=False "next_link"
).order_by("-timestamp")[:15] ] = f"?date={next_date.strftime('%Y-W%W')}"
data["prev_link"] = f"?date={prev_date.strftime('%Y-W%W')}"
data["videogame_scrobble_list"] = completed_for_user.filter( data[
video_game__isnull=False "title"
).order_by("-timestamp")[:15] ] = f"Week {date.strftime('%-W')} of {date.year}"
data = data | Scrobble.as_dict_by_type(
data["boardgame_scrobble_list"] = completed_for_user.filter( Scrobble.for_week(
board_game__isnull=False user_id, date.year, date.isocalendar()[1]
).order_by("-timestamp")[:15] )
)
elif date_str == "this_month" or date_str.count("-") == 1:
next_date = date + relativedelta(months=1)
prev_date = date + relativedelta(months=-1)
if date.month < today.month:
data[
"next_link"
] = f"?date={next_date.strftime('%Y-%m')}"
data["title"] = f"{date.strftime('%B %Y')}"
data["prev_link"] = f"?date={prev_date.strftime('%Y-%m')}"
data = data | Scrobble.as_dict_by_type(
Scrobble.for_month(user_id, date.year, date.month)
)
elif date_str == "today" or date_str.count("-") == 2:
next_date = date + timedelta(days=1)
prev_date = date - timedelta(days=1)
data[
"prev_link"
] = f"?date={prev_date.strftime('%Y-%m-%d')}"
if date < today:
data[
"next_link"
] = f"?date={next_date.strftime('%Y-%m-%d')}"
if date == today:
data["title"] = "Today"
else:
data["title"] = f"{date.strftime('%Y-%m-%d')}"
data[
"today_link"
] = f"?date={today.strftime('%Y-%m-%d')}"
data = data | Scrobble.as_dict_by_type(
Scrobble.for_day(
user_id, date.year, date.month, date.day
)
)
elif date_str == "this_year" or date_str.count("-") == 0:
next_date = date + relativedelta(years=+1)
prev_date = date + relativedelta(years=-1)
data["next_link"] = ""
if date.year < today.year:
data["title"] = f"{date.strftime('%Y')}"
data["next_link"] = f"?date={next_date.year}"
data["title"] = f"{date.year}"
data["prev_link"] = f"?date={prev_date.year}"
data = data | Scrobble.as_dict_by_type(
Scrobble.for_year(user_id, date.year)
)
else:
next_date = today - timedelta(days=1)
prev_date = today - timedelta(days=1)
data["title"] = "Today"
data["next_link"] = ""
data["prev_link"] = f"?date={prev_date.strftime('%Y-%m-%d')}"
data = data | Scrobble.as_dict_by_type(
Scrobble.for_day(user_id, date.year, date.month, date.day)
)
data[
"today_link"
] = "" # f"?date={today.strftime('%Y-%m-%d')}"
data["active_imports"] = AudioScrobblerTSVImport.objects.filter( data["active_imports"] = AudioScrobblerTSVImport.objects.filter(
processing_started__isnull=False, processing_started__isnull=False,
@ -149,9 +211,7 @@ class RecentScrobbleList(ListView):
return data return data
def get_queryset(self): def get_queryset(self):
return Scrobble.objects.filter( return Scrobble.objects.all().order_by("-timestamp")
track__isnull=False, in_progress=False
).order_by("-timestamp")[:15]
class ScrobbleLongPlaysView(TemplateView): class ScrobbleLongPlaysView(TemplateView):
@ -464,6 +524,35 @@ def gps_webhook(request):
return Response({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK) return Response({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK)
@csrf_exempt
@api_view(["POST"])
def emacs_webhook(request):
try:
data_dict = json.loads(request.data)
except TypeError:
data_dict = request.data
logger.info(
"[emacs_webhook] called",
extra={
"post_data": data_dict,
"user_id": 1,
},
)
# TODO Fix this so we have to authenticate!
user_id = 1
if request.user.id:
user_id = request.user.id
# scrobble = gpslogger_scrobble_location(data_dict, user_id)
# if not scrobble:
# return Response({}, status=status.HTTP_200_OK)
return Response({"post_data": post_data}, status=status.HTTP_200_OK)
@csrf_exempt @csrf_exempt
@permission_classes([IsAuthenticated]) @permission_classes([IsAuthenticated])
@api_view(["POST"]) @api_view(["POST"])
@ -698,45 +787,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

@ -163,6 +163,7 @@ SITE_ID = 1
MIDDLEWARE = [ MIDDLEWARE = [
"vrobbler.health_check.HealthCheckMiddleware", "vrobbler.health_check.HealthCheckMiddleware",
"vrobbler.apps.scrobbles.middleware.TimezoneMiddleware",
"django.middleware.security.SecurityMiddleware", "django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware", "django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware", "django.middleware.common.CommonMiddleware",

View File

@ -1,212 +1,83 @@
{% load humanize %} {% load humanize %}
{% load naturalduration %} {% 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"> <div class="row">
<ul class="nav nav-tabs" id="myTab" role="tablist"> <div>
<li class="nav-item" role="presentation"> <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>
<button class="nav-link active" id="home-tab" data-bs-toggle="tab" data-bs-target="#latest-listened" </div>
type="button" role="tab" aria-controls="home" aria-selected="true">Tracks</button> </div>
</li> <div class="row">
<li class="nav-item" role="presentation"> <div class="col-md">
<button class="nav-link" id="profile-tab" data-bs-toggle="tab" data-bs-target="#latest-watched" {% if Track %}
type="button" role="tab" aria-controls="profile" aria-selected="false">Videos</button> <h2>Music</h2>
</li> {% with scrobbles=Track %}
<li class="nav-item" role="presentation"> {% include "scrobbles/_scrobble_table.html" %}
<button class="nav-link" id="profile-tab" data-bs-toggle="tab" data-bs-target="#latest-podcasted" {% endwith %}
type="button" role="tab" aria-controls="profile" aria-selected="false">Podcasts</button> {% endif %}
</li> </div>
<li class="nav-item" role="presentation"> <div class="col-md">
<button class="nav-link" id="profile-tab" data-bs-toggle="tab" data-bs-target="#latest-sports"
type="button" role="tab" aria-controls="profile" aria-selected="false">Sports</button> {% if Task %}
</li> <h2>Latest tasks</h2>
<li class="nav-item" role="presentation"> {% with scrobbles=Task %}
<button class="nav-link" id="profile-tab" data-bs-toggle="tab" data-bs-target="#latest-videogames" {% include "scrobbles/_scrobble_table.html" %}
type="button" role="tab" aria-controls="profile" aria-selected="false">Video Games</button> {% endwith %}
</li> {% endif %}
<li class="nav-item" role="presentation">
<button class="nav-link" id="profile-tab" data-bs-toggle="tab" data-bs-target="#latest-boardgames" {% if Video %}
type="button" role="tab" aria-controls="profile" aria-selected="false">Board Games</button> <h2>Videos</h2>
</li> {% with scrobbles=Video %}
</ul> {% include "scrobbles/_scrobble_table.html" %}
{% endwith %}
<div class="tab-content" id="myTabContent2"> {% endif %}
<div class="tab-pane fade show active" id="latest-listened" role="tabpanel"
aria-labelledby="latest-listened-tab"> {% if WebPage %}
<div class="table-responsive"> <h4>Web pages</h4>
<table class="table table-striped table-sm"> {% with scrobbles=WebPage %}
<thead> {% include "scrobbles/_scrobble_table.html" %}
<tr> {% endwith %}
<th scope="col">Time</th> {% endif %}
<th scope="col">Album</th>
<th scope="col">Track</th> {% if SportEvent %}
<th scope="col">Artist</th> <h2>Sports</h2>
</tr> {% with scrobbles=SportEvent %}
</thead> {% include "scrobbles/_scrobble_table.html" %}
<tbody> {% endwith %}
{% for scrobble in object_list %} {% endif %}
<tr>
<td>{{scrobble.timestamp|naturaltime}}</td> {% if PodcastEpisode %}
{% if scrobble.track.album.cover_image %} <h2>Latest podcasts</h2>
<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> {% with scrobbles=PodcastEpisode %}
{% else %} {% include "scrobbles/_scrobble_table.html" %}
<td><a href="{{scrobble.track.album.get_absolute_url}}">{{scrobble.track.album.name}}</a></td> {% endwith %}
{% endif %} {% endif %}
<td><a href="{{scrobble.track.get_absolute_url }}">{{scrobble.track.title}}</a></td>
<td><a href="{{scrobble.track.artist.get_absolute_url }}">{{scrobble.track.artist.name}}</aa></td> {% if VideoGame %}
</tr> <h4>Video games</h4>
{% endfor %} {% with scrobbles=VideoGame %}
</tbody> {% include "scrobbles/_scrobble_table.html" %}
</table> {% endwith %}
</div> {% endif %}
</div>
{% if BoardGame %}
<div class="tab-pane fade show" id="latest-watched" role="tabpanel" <h4>Board games</h4>
aria-labelledby="latest-watched-tab"> {% with scrobbles=BoardGame %}
<h2>Latest watched</h2> {% include "scrobbles/_scrobble_table.html" %}
<div class="table-responsive"> {% endwith %}
<table class="table table-striped table-sm"> {% endif %}
<thead>
<tr> {% if Beer %}
<th scope="col">Time</th> <h4>Beers</h4>
<th scope="col">Cover</th> {% with scrobbles=Beer %}
<th scope="col">Title</th> {% include "scrobbles/_scrobble_table.html" %}
<th scope="col">Series</th> {% endwith %}
</tr> {% endif %}
</thead>
<tbody> {% if Book %}
{% for scrobble in video_scrobble_list %} <h4>Books</h4>
<tr> {% with scrobbles=Book %}
<td>{{scrobble.timestamp|naturaltime}}</td> {% include "scrobbles/_scrobble_table.html" %}
{% if scrobble.video.cover_image %} {% endwith %}
<td><img src="{{scrobble.media_obj.cover_image_medium.url}}" width=25 height=25 style="border:1px solid black;" /></td> {% endif %}
{% else %}
<td></td>
{% endif %}
<td><a href="{{scrobble.video.get_absolute_url }}">{% if scrobble.video.tv_series%}S{{scrobble.video.season_number}}E{{scrobble.video.episode_number}} -{%endif %} {{scrobble.video.title}}</a></td>
<td><a href="{{scrobble.video.tv_series.get_absolute_url }}">{% if scrobble.video.tv_series %}{{scrobble.video.tv_series}}</a>{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="tab-pane fade show" id="latest-sports" role="tabpanel" aria-labelledby="latest-sports-tab">
<h2>Latest Sports</h2>
<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">Round</th>
<th scope="col">League</th>
</tr>
</thead>
<tbody>
{% for scrobble in sport_scrobble_list %}
<tr>
<td>{{scrobble.timestamp|naturaltime}}</td>
<td>{{scrobble.sport_event.title}}</td>
<td>{{scrobble.sport_event.round.name}}</td>
<td>{{scrobble.sport_event.round.season.league}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<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>
<tr>
<th scope="col">Date</th>
<th scope="col">Title</th>
<th scope="col">Podcast</th>
</tr>
</thead>
<tbody>
{% for scrobble in podcast_scrobble_list %}
<tr>
<td>{{scrobble.timestamp|naturaltime}}</td>
<td>{{scrobble.podcast_episode.title}}</td>
<td>{{scrobble.podcast_episode.podcast}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<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>
<tr>
<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">Percent complete</th>
</tr>
</thead>
<tbody>
{% for scrobble in videogame_scrobble_list %}
<tr>
<td>{{scrobble.timestamp|naturaltime}}</td>
{% if scrobble.screenshot %}
<td><img src="{{scrobble.screenshot_medium.url}}" width=25 height=25 style="border:1px solid black;" /></td>
{% else %}
{% if scrobble.media_obj.hltb_cover %}
<td><img src="{{scrobble.media_obj.hltb_cover_medium.url}}" width=25 height=25 style="border:1px solid black;" /></td>
{% endif %}
{% endif %}
<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.percent_played}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<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>
<tr>
<th scope="col">Date</th>
<th scope="col">Cover</th>
<th scope="col">Title</th>
<th scope="col">Time played (mins)</th>
</tr>
</thead>
<tbody>
{% for scrobble in boardgame_scrobble_list %}
<tr>
<td>{{scrobble.timestamp|naturaltime}}</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> </div>
</div> </div>

View File

@ -0,0 +1,7 @@
{% load humanize %}
{% load naturalduration %}
<tr>
<td>{% if scrobble.in_progress %}{{scrobble.media_obj.strings.verb}} now | <a class="right" href="{% url "scrobbles:finish" scrobble.uuid %}">Finish</a>{% else %}{{scrobble.timestamp|naturaltime}}{% endif %}</td>
<td><a href="{{scrobble.media_obj.get_absolute_url}}">{{scrobble.media_obj|truncatechars_html:50}}</a></td>
<td>{{scrobble.elapsed_time|natural_duration}}</td>
</tr>

View File

@ -0,0 +1,22 @@
{% load humanize %}
{% load naturalduration %}
<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">Title</th>
<th scope="col">Time</th>
</tr>
</thead>
<tbody>
{% for scrobble in scrobbles %}
{% include "scrobbles/_row.html" %}
{% endfor %}
</tbody>
</table>
</div>
</div>

View File

@ -1,173 +1,176 @@
{% load static %} {% load static %}
<h2>Top Artist</h2>
<ul class="nav nav-tabs" id="artistTab" role="tablist">
{% for key, name in chart_keys.items %}
<li class="nav-item" role="presentation">
<button class="nav-link {% if forloop.counter == 2 %}active{% endif %}"
id="artist-{{key}}-tab" data-bs-toggle="tab" data-bs-target="#artist-{{key}}"
type="button" role="tab" aria-controls="home" aria-selected="true">{{name}}</button>
</li>
{% endfor %}
</ul>
<div class="tab-content" id="artistTabContent" class="maloja-chart">
{% for key, artists in current_artist_charts.items %} <div class="row">
<div class="tab-pane fade {% if forloop.counter == 2 %}show active{% endif %}" id="artist-{{key}}" role="tabpanel" aria-labelledby="artist-{{key}}-tab"> <h2>Top Artist</h2>
<div style="display:block"> <ul class="nav nav-tabs" id="artistTab" role="tablist">
<div style="float:left;"> {% for key, name in chart_keys.items %}
<div class="image-wrapper" style="display:flex; flex-wrap: wrap; margin:0"> <li class="nav-item" role="presentation">
<div class="caption">#1 {{artists.0.name}}</div> <button class="nav-link {% if forloop.counter == 2 %}active{% endif %}"
{% if artists.0 %} id="artist-{{key}}-tab" data-bs-toggle="tab" data-bs-target="#artist-{{key}}"
{% if artists.0.thumbnail %} type="button" role="tab" aria-controls="home" aria-selected="true">{{name}}</button>
<a href="{{artists.0.get_absolute_url}}"><img lt="{{artists.0.name}}" src="{{artists.0.thumbnail_medium.url}}" width="300px"></a> </li>
{% else %} {% endfor %}
<a href="{{artists.0.get_absolute_url}}"><img lt="{{artists.0.name}}" src="{% static "images/not-found.jpg" %}" width="300px"></a> </ul>
{% endif %}
{% endif %} <div class="tab-content" id="artistTabContent" class="maloja-chart">
</div> {% for key, artists in current_artist_charts.items %}
</div> <div class="tab-pane fade {% if forloop.counter == 2 %}show active{% endif %}" id="artist-{{key}}" role="tabpanel" aria-labelledby="artist-{{key}}-tab">
<div style="float:left; width:300px;"> <div style="display:block">
<div style="display:flex; flex-wrap: wrap;"> <div style="float:left;">
<div class="image-wrapper" class="image-wrapper" style="width:50%"> <div class="image-wrapper" style="display:flex; flex-wrap: wrap; margin:0">
<div class="caption-medium">#2 {{artists.1.name}}</div> <div class="caption">#1 {{artists.0.name}}</div>
{% if artists.1 %} {% if artists.0 %}
{% if artists.1.thumbnail %} {% if artists.0.thumbnail %}
<a href="{{artists.1.get_absolute_url}}"><img lt="{{artists.1.name}}" src="{{artists.1.thumbnail_medium.url}}" width="150px"></a> <a href="{{artists.0.get_absolute_url}}"><img lt="{{artists.0.name}}" src="{{artists.0.thumbnail_medium.url}}" width="300px"></a>
{% else %} {% else %}
<a href="{{artists.1.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="150px"></a> <a href="{{artists.0.get_absolute_url}}"><img lt="{{artists.0.name}}" src="{% static "images/not-found.jpg" %}" width="300px"></a>
{% endif %}
{% endif %}
</div>
<div class="image-wrapper" class="image-wrapper" style="width:50%">
<div class="caption-medium">#3 {{artists.2.name}}</div>
{% if artists.2 %}
{% if artists.2.thumbnail %}
<a href="{{artists.2.get_absolute_url}}"><img src="{{artists.2.thumbnail_medium.url}}" width="150px"></a>
{% else %}
<a href="{{artists.2.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="150px"></a>
{% endif %}
{% endif %}
</div>
<div class="image-wrapper" class="image-wrapper" style="width:50%">
<div class="caption-medium">#4 {{artists.3.name}}</div>
{% if artists.3 %}
{% if artists.3.thumbnail %}
<a href="{{artists.3.get_absolute_url}}"><img src="{{artists.3.thumbnail_medium.url}}" width="150px"></a>
{% else %}
<a href="{{artists.3.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="150px"></a>
{% endif %}
{% endif %}
</div>
<div class="image-wrapper" class="image-wrapper" style="width:50%">
<div class="caption-medium">#5 {{artists.4.name}}</div>
{% if artists.4 %}
{% if artists.4.thumbnail %}
<a href="{{artists.4.get_absolute_url}}"><img src="{{artists.4.thumbnail_medium.url}}" width="150px"></a>
{% else %}
<a href="{{artists.4.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="150px"></a>
{% endif %} {% endif %}
{% endif %} {% endif %}
</div> </div>
</div> </div>
</div> <div style="float:left; width:300px;">
<div style="float:left; width:300px;"> <div style="display:flex; flex-wrap: wrap;">
<div style="display:flex; flex-wrap: wrap;"> <div class="image-wrapper" class="image-wrapper" style="width:50%">
<div class="image-wrapper" class="image-wrapper" class="image-wrapper" style="width:33;"> <div class="caption-medium">#2 {{artists.1.name}}</div>
<div class="caption-small">#6 {{artists.5.name}}</div> {% if artists.1 %}
{% if artists.5 %} {% if artists.1.thumbnail %}
{% if artists.5.thumbnail %} <a href="{{artists.1.get_absolute_url}}"><img lt="{{artists.1.name}}" src="{{artists.1.thumbnail_medium.url}}" width="150px"></a>
<a href="{{artists.5.get_absolute_url}}"><img src="{{artists.5.thumbnail_medium.url}}" width="100px"></a> {% else %}
{% else %} <a href="{{artists.1.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="150px"></a>
<a href="{{artists.5.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="100px"></a> {% endif %}
{% endif %} {% endif %}
{% endif %} </div>
<div class="image-wrapper" class="image-wrapper" style="width:50%">
<div class="caption-medium">#3 {{artists.2.name}}</div>
{% if artists.2 %}
{% if artists.2.thumbnail %}
<a href="{{artists.2.get_absolute_url}}"><img src="{{artists.2.thumbnail_medium.url}}" width="150px"></a>
{% else %}
<a href="{{artists.2.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="150px"></a>
{% endif %}
{% endif %}
</div>
<div class="image-wrapper" class="image-wrapper" style="width:50%">
<div class="caption-medium">#4 {{artists.3.name}}</div>
{% if artists.3 %}
{% if artists.3.thumbnail %}
<a href="{{artists.3.get_absolute_url}}"><img src="{{artists.3.thumbnail_medium.url}}" width="150px"></a>
{% else %}
<a href="{{artists.3.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="150px"></a>
{% endif %}
{% endif %}
</div>
<div class="image-wrapper" class="image-wrapper" style="width:50%">
<div class="caption-medium">#5 {{artists.4.name}}</div>
{% if artists.4 %}
{% if artists.4.thumbnail %}
<a href="{{artists.4.get_absolute_url}}"><img src="{{artists.4.thumbnail_medium.url}}" width="150px"></a>
{% else %}
<a href="{{artists.4.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="150px"></a>
{% endif %}
{% endif %}
</div>
</div> </div>
<div class="image-wrapper" class="image-wrapper" style="width:33;"> </div>
<div class="caption-small">#7 {{artists.6.name}}</div> <div style="float:left; width:300px;">
{% if artists.6 %} <div style="display:flex; flex-wrap: wrap;">
{% if artists.6.thumbnail %} <div class="image-wrapper" class="image-wrapper" class="image-wrapper" style="width:33;">
<a href="{{artists.6.get_absolute_url}}"><img src="{{artists.6.thumbnail_medium.url}}" width="100px"></a> <div class="caption-small">#6 {{artists.5.name}}</div>
{% else %} {% if artists.5 %}
<a href="{{artists.6.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="100px"></a> {% if artists.5.thumbnail %}
{% endif %} <a href="{{artists.5.get_absolute_url}}"><img src="{{artists.5.thumbnail_medium.url}}" width="100px"></a>
{% endif %} {% else %}
</div> <a href="{{artists.5.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="100px"></a>
<div class="image-wrapper" class="image-wrapper" style="width:33;"> {% endif %}
<div class="caption-small">#8 {{artists.7.name}}</div> {% endif %}
{% if artists.7 %} </div>
{% if artists.7.thumbnail %} <div class="image-wrapper" class="image-wrapper" style="width:33;">
<a href="{{artists.7.get_absolute_url}}"><img src="{{artists.7.thumbnail_medium.url}}" width="100px"></a> <div class="caption-small">#7 {{artists.6.name}}</div>
{% else %} {% if artists.6 %}
<a href="{{artists.7.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="100px"></a> {% if artists.6.thumbnail %}
{% endif %} <a href="{{artists.6.get_absolute_url}}"><img src="{{artists.6.thumbnail_medium.url}}" width="100px"></a>
{% endif %} {% else %}
</div> <a href="{{artists.6.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="100px"></a>
<div class="image-wrapper" class="image-wrapper" style="width:33;"> {% endif %}
<div class="caption-small">#9 {{artists.8.name}}</div> {% endif %}
{% if artists.8 %} </div>
{% if artists.8.thumbnail %} <div class="image-wrapper" class="image-wrapper" style="width:33;">
<a href="{{artists.8.get_absolute_url}}"><img src="{{artists.8.thumbnail_medium.url}}" width="100px"></a> <div class="caption-small">#8 {{artists.7.name}}</div>
{% else %} {% if artists.7 %}
<a href="{{artists.8.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="100px"></a> {% if artists.7.thumbnail %}
{% endif %} <a href="{{artists.7.get_absolute_url}}"><img src="{{artists.7.thumbnail_medium.url}}" width="100px"></a>
{% endif %} {% else %}
</div> <a href="{{artists.7.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="100px"></a>
<div class="image-wrapper" class="image-wrapper" style="width:33;"> {% endif %}
<div class="caption-small">#10 {{artists.9.name}}</div> {% endif %}
{% if artists.9 %} </div>
{% if artists.9.thumbnail %} <div class="image-wrapper" class="image-wrapper" style="width:33;">
<a href="{{artists.9.get_absolute_url}}"><img src="{{artists.9.thumbnail_medium.url}}" width="100px"></a> <div class="caption-small">#9 {{artists.8.name}}</div>
{% else %} {% if artists.8 %}
<a href="{{artists.9.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="100px"></a> {% if artists.8.thumbnail %}
{% endif %} <a href="{{artists.8.get_absolute_url}}"><img src="{{artists.8.thumbnail_medium.url}}" width="100px"></a>
{% endif %} {% else %}
</div> <a href="{{artists.8.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="100px"></a>
<div class="image-wrapper" class="image-wrapper" style="width:33;"> {% endif %}
<div class="caption-small">#11 {{artists.10.name}}</div> {% endif %}
{% if artists.10 %} </div>
{% if artists.10.thumbnail %} <div class="image-wrapper" class="image-wrapper" style="width:33;">
<a href="{{artists.10.get_absolute_url}}"><img src="{{artists.10.thumbnail_medium.url}}" width="100px"></a> <div class="caption-small">#10 {{artists.9.name}}</div>
{% else %} {% if artists.9 %}
<a href="{{artists.10.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="100px"></a> {% if artists.9.thumbnail %}
{% endif %} <a href="{{artists.9.get_absolute_url}}"><img src="{{artists.9.thumbnail_medium.url}}" width="100px"></a>
{% endif %} {% else %}
</div> <a href="{{artists.9.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="100px"></a>
<div class="image-wrapper" class="image-wrapper" style="width:33;"> {% endif %}
<div class="caption-small">#12 {{artists.11.name}}</div> {% endif %}
{% if artists.11 %} </div>
{% if artists.11.thumbnail %} <div class="image-wrapper" class="image-wrapper" style="width:33;">
<a href="{{artists.11.get_absolute_url}}"><img src="{{artists.11.thumbnail_medium.url}}" width="100px"></a> <div class="caption-small">#11 {{artists.10.name}}</div>
{% else %} {% if artists.10 %}
<a href="{{artists.11.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="100px"></a> {% if artists.10.thumbnail %}
{% endif %} <a href="{{artists.10.get_absolute_url}}"><img src="{{artists.10.thumbnail_medium.url}}" width="100px"></a>
{% endif %} {% else %}
</div> <a href="{{artists.10.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="100px"></a>
<div class="image-wrapper" class="image-wrapper" style="width:33;"> {% endif %}
<div class="caption-small">#13 {{artists.12.name}}</div> {% endif %}
{% if artists.12 %} </div>
{% if artists.12.thumbnail %} <div class="image-wrapper" class="image-wrapper" style="width:33;">
<a href="{{artists.12.get_absolute_url}}"><img src="{{artists.12.thumbnail_medium.url}}" width="100px"></a> <div class="caption-small">#12 {{artists.11.name}}</div>
{% else %} {% if artists.11 %}
<a href="{{artists.12.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="100px"></a> {% if artists.11.thumbnail %}
{% endif %} <a href="{{artists.11.get_absolute_url}}"><img src="{{artists.11.thumbnail_medium.url}}" width="100px"></a>
{% endif %} {% else %}
</div> <a href="{{artists.11.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="100px"></a>
<div class="image-wrapper" class="image-wrapper" style="width:33;"> {% endif %}
<div class="caption-small">#14 {{artists.13.name}}</div> {% endif %}
{% if artists.13 %} </div>
{% if artists.13.thumbnail %} <div class="image-wrapper" class="image-wrapper" style="width:33;">
<a href="{{artists.13.get_absolute_url}}"><img src="{{artists.13.thumbnail_medium.url}}" width="100px"></a> <div class="caption-small">#13 {{artists.12.name}}</div>
{% else %} {% if artists.12 %}
<a href="{{artists.13.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="100px"></a> {% if artists.12.thumbnail %}
{% endif %} <a href="{{artists.12.get_absolute_url}}"><img src="{{artists.12.thumbnail_medium.url}}" width="100px"></a>
{% endif %} {% else %}
<a href="{{artists.12.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="100px"></a>
{% endif %}
{% endif %}
</div>
<div class="image-wrapper" class="image-wrapper" style="width:33;">
<div class="caption-small">#14 {{artists.13.name}}</div>
{% if artists.13 %}
{% if artists.13.thumbnail %}
<a href="{{artists.13.get_absolute_url}}"><img src="{{artists.13.thumbnail_medium.url}}" width="100px"></a>
{% else %}
<a href="{{artists.13.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="100px"></a>
{% endif %}
{% endif %}
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endfor %}
</div> </div>
{% endfor %}
</div>
</div> </div>
<div class="row"> <div class="row">

View File

@ -2,12 +2,54 @@
{% block title %}{{name}}{% endblock %} {% block title %}{{name}}{% endblock %}
{% block head_extra %}
<style>
.container { margin-bottom:100px; }
h2 { padding-top:20px; }
.image-wrapper {
contain: content;
}
.image-wrapper :hover {
background:rgba(0,0,0,0.3);
}
.caption {
position: fixed;
top: 5px;
left: 5px;
padding: 3px;
font-size: 90%;
color:white;
background:rgba(0,0,0,0.4);
}
.caption-medium {
position: fixed;
top: 5px;
left: 5px;
padding: 3px;
font-size: 75%;
color:white;
background:rgba(0,0,0,0.4);
}
.caption-small {
position: fixed;
top: 5px;
left: 5px;
padding: 3px;
font-size: 60%;
color:white;
background:rgba(0,0,0,0.4);
}
</style>
{% endblock %}
{% block lists %} {% block lists %}
<div "calss="row> {% if chart_type == "maloja" %}
{% include "scrobbles/_top_charts.html" %} {% include "scrobbles/_top_charts.html" %}
</div> {% else %}
<div class="row"> <div class="row">
{% if artist_charts %} {% if artist_charts %}
<div class="col-md"> <div class="col-md">
@ -151,5 +193,6 @@
</div> </div>
{% endif %} {% endif %}
</div> </div>
{% endif %}
{% endblock %} {% endblock %}

View File

@ -49,9 +49,28 @@
<main class="col-md-9 ms-sm-auto col-lg-10 px-md-4"> <main class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
<div <div
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">Dashboard</h1> <h1 class="h2">{% if date %}{{date|naturaltime}}{% else %}{{title}}{% endif %}</h1>
<div class="btn-toolbar mb-2 mb-md-0"> <div class="btn-toolbar mb-2 mb-md-0">
{% if user.is_authenticated %} {% if user.is_authenticated %}
<div class="btn-group me-2">
{% if user.profile.lastfm_username and not user.profile.lastfm_auto_import %}
<form action="{% url 'scrobbles:lastfm-import' %}" method="get">
<button type="submit" class="btn btn-sm btn-outline-secondary">Last.fm Sync</button>
</form>
{% endif %}
{% if prev_link %}
<a type="button" class="btn btn-sm btn-outline-secondary" href="{{prev_link}}"
data-bs-target="#">Previous</a>
{% endif %}
{% if today_link %}
<a type="button" class="btn btn-sm btn-outline-secondary" href="{{today_link}}"
data-bs-target="#">Today</a>
{% endif %}
{% if next_link %}
<a type="button" class="btn btn-sm btn-outline-secondary" href="{{next_link}}"
data-bs-target="#">Next</a>
{% endif %}
</div>
<div class="btn-group me-2"> <div class="btn-group me-2">
{% if user.profile.lastfm_username and not user.profile.lastfm_auto_import %} {% if user.profile.lastfm_username and not user.profile.lastfm_auto_import %}
@ -69,11 +88,13 @@
<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"
data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<div data-feather="calendar"></div> <div data-feather="calendar"></div>
This week {{title}}
</button> </button>
<div class="dropdown-menu" data-bs-toggle="#graphDataChange" aria-labelledby="graphDateButton"> <div class="dropdown-menu" data-bs-toggle="#graphDataChange" aria-labelledby="graphDateButton">
<a class="dropdown-item" href="#">This month</a> <a class="dropdown-item" href="?date=today">Today</a>
<a class="dropdown-item" href="#">This year</a> <a class="dropdown-item" href="?date=this_week">This week</a>
<a class="dropdown-item" href="?date=this_month">This month</a>
<a class="dropdown-item" href="?date=this_year">This year</a>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,5 +1,8 @@
<html> <html>
<style>label { display:none; } textarea {height:8em; width:100%}</style> <head>
<style>label { display:none; } textarea {height:8em; width:100%}</style>
<title>Reading {{object.title}}</title>
</head>
<body> <body>
<form method=post>{% csrf_token %} <form method=post>{% csrf_token %}
{{form.as_p}} {{form.as_p}}
@ -7,7 +10,7 @@
<input type="submit" value="Finish" /> <input type="submit" value="Finish" />
</form> </form>
<a href="{{object.url}}" target="_blank">Open in new window</a> <a href="{{object.url}}" target="_blank">Open in new window</a>
<iframe style="height:78%; width:100%" src="{{object.url}}" title="{{object.url}}" allowfullscreen sandbox> <iframe style="height:84%; width:100%" src="{{object.url}}" title="{{object.url}}" allowfullscreen sandbox>
<p> <p>
Page could not be opened, use link above to read. Page could not be opened, use link above to read.
</p> </p>