Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| db36329011 | |||
| 69aa80e6c1 | |||
| 99a6e5107b | |||
| 39e2fdce27 | |||
| b2f98d780b | |||
| 07b5dc6a2c | |||
| 7207ca385e | |||
| 2a254e28d0 | |||
| d4377e49ac | |||
| 9dc0a818ff | |||
| 5e672fc9ed |
15
poetry.lock
generated
15
poetry.lock
generated
@ -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"
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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}")
|
||||||
|
|||||||
28
vrobbler/apps/scrobbles/middleware.py
Normal file
28
vrobbler/apps/scrobbles/middleware.py
Normal 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))
|
||||||
@ -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
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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"] = {
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
7
vrobbler/templates/scrobbles/_row.html
Normal file
7
vrobbler/templates/scrobbles/_row.html
Normal 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>
|
||||||
22
vrobbler/templates/scrobbles/_scrobble_table.html
Normal file
22
vrobbler/templates/scrobbles/_scrobble_table.html
Normal 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>
|
||||||
@ -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">
|
||||||
|
|||||||
@ -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 %}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user