Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 34a2339b3b | |||
| 34abbe753b | |||
| 0fe00c3dd8 | |||
| 5a3eb7a8c8 | |||
| e63ca13d57 | |||
| b3d3098fe0 | |||
| 8f5a200526 | |||
| 411d2b42b0 | |||
| bce1322289 | |||
| 908819d24e | |||
| 6d21bb2e85 | |||
| 7df3fedc64 | |||
| b4e83b184e | |||
| 6e885df1dd | |||
| f153f831b3 | |||
| 66a90c87f1 | |||
| 6e17e4ce0d | |||
| 3c3e567573 | |||
| 2775851474 | |||
| 654a64e82d | |||
| 7dd7f369d8 | |||
| fb6110c71d |
@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "vrobbler"
|
name = "vrobbler"
|
||||||
version = "0.11.5"
|
version = "0.11.12"
|
||||||
description = ""
|
description = ""
|
||||||
authors = ["Colin Powell <colin@unbl.ink>"]
|
authors = ["Colin Powell <colin@unbl.ink>"]
|
||||||
|
|
||||||
|
|||||||
@ -129,7 +129,9 @@ class Album(TimeStampedModel):
|
|||||||
return self.artists.first()
|
return self.artists.first()
|
||||||
|
|
||||||
def scrape_theaudiodb(self) -> None:
|
def scrape_theaudiodb(self) -> None:
|
||||||
artist = self.primary_artist.name
|
artist = "Various Artists"
|
||||||
|
if self.primary_artist:
|
||||||
|
artist = self.primary_artist.name
|
||||||
album_data = lookup_album_from_tadb(self.name, artist)
|
album_data = lookup_album_from_tadb(self.name, artist)
|
||||||
if not album_data.get('theaudiodb_id'):
|
if not album_data.get('theaudiodb_id'):
|
||||||
logger.info(f"No data for {self} found in TheAudioDB")
|
logger.info(f"No data for {self} found in TheAudioDB")
|
||||||
|
|||||||
@ -43,38 +43,27 @@ def get_or_create_artist(name: str, mbid: str = None) -> Artist:
|
|||||||
|
|
||||||
def get_or_create_album(name: str, artist: Artist, mbid: str = None) -> Album:
|
def get_or_create_album(name: str, artist: Artist, mbid: str = None) -> Album:
|
||||||
album = None
|
album = None
|
||||||
album_created = False
|
album_dict = lookup_album_dict_from_mb(name, artist_name=artist.name)
|
||||||
albums = Album.objects.filter(name__iexact=name)
|
mbid = mbid or album_dict['mb_id']
|
||||||
if albums.count() == 1:
|
|
||||||
album = albums.first()
|
|
||||||
else:
|
|
||||||
for potential_album in albums:
|
|
||||||
if artist in album.artist_set.all():
|
|
||||||
album = potential_album
|
|
||||||
if not album:
|
|
||||||
album_created = True
|
|
||||||
album = Album.objects.create(name=name, musicbrainz_id=mbid)
|
|
||||||
album.save()
|
|
||||||
album.artists.add(artist)
|
|
||||||
|
|
||||||
if album_created or not mbid:
|
logger.debug(f'Looking up album {name} and mbid: {mbid}')
|
||||||
album_dict = lookup_album_dict_from_mb(
|
|
||||||
album.name, artist_name=artist.name
|
album = Album.objects.filter(musicbrainz_id=mbid).first()
|
||||||
)
|
if not album:
|
||||||
|
album = Album.objects.create(name=name, musicbrainz_id=mbid)
|
||||||
album.year = album_dict["year"]
|
album.year = album_dict["year"]
|
||||||
album.musicbrainz_id = album_dict["mb_id"]
|
|
||||||
album.musicbrainz_releasegroup_id = album_dict["mb_group_id"]
|
album.musicbrainz_releasegroup_id = album_dict["mb_group_id"]
|
||||||
album.musicbrainz_albumartist_id = artist.musicbrainz_id
|
album.musicbrainz_albumartist_id = artist.musicbrainz_id
|
||||||
album.save(
|
album.save(
|
||||||
update_fields=[
|
update_fields=[
|
||||||
"year",
|
"year",
|
||||||
"musicbrainz_id",
|
|
||||||
"musicbrainz_releasegroup_id",
|
"musicbrainz_releasegroup_id",
|
||||||
"musicbrainz_albumartist_id",
|
"musicbrainz_albumartist_id",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
album.artists.add(artist)
|
album.artists.add(artist)
|
||||||
album.fetch_artwork()
|
album.fetch_artwork()
|
||||||
|
|
||||||
return album
|
return album
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
from datetime import timedelta
|
from django.db.models import Count
|
||||||
from django.utils import timezone
|
|
||||||
from django.views import generic
|
from django.views import generic
|
||||||
from music.models import Album, Artist, Track
|
from music.models import Album, Artist, Track
|
||||||
from scrobbles.models import ChartRecord
|
from scrobbles.models import ChartRecord
|
||||||
@ -22,7 +21,6 @@ class TrackDetailView(generic.DetailView):
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context_data = super().get_context_data(**kwargs)
|
context_data = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
context_data['charts'] = ChartRecord.objects.filter(
|
context_data['charts'] = ChartRecord.objects.filter(
|
||||||
track=self.object, rank__in=[1, 2, 3]
|
track=self.object, rank__in=[1, 2, 3]
|
||||||
)
|
)
|
||||||
@ -34,7 +32,12 @@ class ArtistListView(generic.ListView):
|
|||||||
paginate_by = 100
|
paginate_by = 100
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return super().get_queryset().order_by("name")
|
return (
|
||||||
|
super()
|
||||||
|
.get_queryset()
|
||||||
|
.annotate(scrobble_count=Count('track__scrobble'))
|
||||||
|
.order_by("-scrobble_count")
|
||||||
|
)
|
||||||
|
|
||||||
def get_context_data(self, *, object_list=None, **kwargs):
|
def get_context_data(self, *, object_list=None, **kwargs):
|
||||||
context_data = super().get_context_data(
|
context_data = super().get_context_data(
|
||||||
@ -50,6 +53,17 @@ class ArtistDetailView(generic.DetailView):
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context_data = super().get_context_data(**kwargs)
|
context_data = super().get_context_data(**kwargs)
|
||||||
|
artist = context_data['object']
|
||||||
|
rank = 1
|
||||||
|
tracks_ranked = []
|
||||||
|
scrobbles = artist.tracks.first().scrobble_count
|
||||||
|
for track in artist.tracks:
|
||||||
|
if scrobbles > track.scrobble_count:
|
||||||
|
rank += 1
|
||||||
|
tracks_ranked.append((rank, track))
|
||||||
|
scrobbles = track.scrobble_count
|
||||||
|
|
||||||
|
context_data['tracks_ranked'] = tracks_ranked
|
||||||
context_data['charts'] = ChartRecord.objects.filter(
|
context_data['charts'] = ChartRecord.objects.filter(
|
||||||
artist=self.object, rank__in=[1, 2, 3]
|
artist=self.object, rank__in=[1, 2, 3]
|
||||||
)
|
)
|
||||||
@ -58,17 +72,14 @@ class ArtistDetailView(generic.DetailView):
|
|||||||
|
|
||||||
class AlbumListView(generic.ListView):
|
class AlbumListView(generic.ListView):
|
||||||
model = Album
|
model = Album
|
||||||
paginate_by = 50
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return super().get_queryset().order_by("name")
|
return (
|
||||||
|
super()
|
||||||
def get_context_data(self, *, object_list=None, **kwargs):
|
.get_queryset()
|
||||||
context_data = super().get_context_data(
|
.annotate(scrobble_count=Count('track__scrobble'))
|
||||||
object_list=object_list, **kwargs
|
.order_by("-scrobble_count")
|
||||||
)
|
)
|
||||||
context_data['view'] = self.request.GET.get('view')
|
|
||||||
return context_data
|
|
||||||
|
|
||||||
|
|
||||||
class AlbumDetailView(generic.DetailView):
|
class AlbumDetailView(generic.DetailView):
|
||||||
|
|||||||
@ -1,23 +1,18 @@
|
|||||||
import datetime
|
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
import calendar
|
||||||
|
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
# need to translate to a non-naive timezone, even if timezone == settings.TIME_ZONE, so we can compare two dates
|
# need to translate to a non-naive timezone, even if timezone == settings.TIME_ZONE, so we can compare two dates
|
||||||
def to_user_timezone(date, profile):
|
def to_user_timezone(date, profile):
|
||||||
timezone = profile.timezone if profile.timezone else settings.TIME_ZONE
|
timezone = profile.timezone if profile.timezone else settings.TIME_ZONE
|
||||||
return date.replace(tzinfo=pytz.timezone(settings.TIME_ZONE)).astimezone(
|
return date.astimezone(pytz.timezone(timezone))
|
||||||
pytz.timezone(timezone)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def to_system_timezone(date, profile):
|
def to_system_timezone(date):
|
||||||
timezone = profile.timezone if profile.timezone else settings.TIME_ZONE
|
return date.astimezone(pytz.timezone(settings.TIME_ZONE))
|
||||||
return date.replace(tzinfo=pytz.timezone(timezone)).astimezone(
|
|
||||||
pytz.timezone(settings.TIME_ZONE)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def now_user_timezone(profile):
|
def now_user_timezone(profile):
|
||||||
@ -25,9 +20,39 @@ def now_user_timezone(profile):
|
|||||||
return timezone.localtime(timezone.now())
|
return timezone.localtime(timezone.now())
|
||||||
|
|
||||||
|
|
||||||
def now_system_timezone():
|
def start_of_day(dt, profile) -> datetime:
|
||||||
return (
|
"""Get the start of the day in the profile's timezone"""
|
||||||
datetime.datetime.now()
|
timezone = profile.timezone if profile.timezone else settings.TIME_ZONE
|
||||||
.replace(tzinfo=pytz.timezone(settings.TIME_ZONE))
|
tzinfo = pytz.timezone(timezone)
|
||||||
.astimezone(pytz.timezone(settings.TIME_ZONE))
|
return datetime.combine(dt, datetime.min.time(), tzinfo)
|
||||||
)
|
|
||||||
|
|
||||||
|
def end_of_day(dt, profile) -> datetime:
|
||||||
|
"""Get the start of the day in the profile's timezone"""
|
||||||
|
timezone = profile.timezone if profile.timezone else settings.TIME_ZONE
|
||||||
|
tzinfo = pytz.timezone(timezone)
|
||||||
|
return datetime.combine(dt, datetime.max.time(), tzinfo)
|
||||||
|
|
||||||
|
|
||||||
|
def start_of_week(dt, profile) -> datetime:
|
||||||
|
# TODO allow profile to set start of week
|
||||||
|
return start_of_day(dt, profile) - timedelta(dt.weekday())
|
||||||
|
|
||||||
|
|
||||||
|
def end_of_week(dt, profile) -> datetime:
|
||||||
|
# TODO allow profile to set start of week
|
||||||
|
return start_of_week(dt, profile) + timedelta(days=6)
|
||||||
|
|
||||||
|
|
||||||
|
def start_of_month(dt, profile) -> datetime:
|
||||||
|
return start_of_day(dt, profile).replace(day=1)
|
||||||
|
|
||||||
|
|
||||||
|
def end_of_month(dt, profile) -> datetime:
|
||||||
|
next_month = end_of_day(dt, profile).replace(day=28) + timedelta(days=4)
|
||||||
|
# subtracting the number of the current day brings us back one month
|
||||||
|
return next_month - timedelta(days=next_month.day)
|
||||||
|
|
||||||
|
|
||||||
|
def start_of_year(dt, profile) -> datetime:
|
||||||
|
return start_of_day(dt, profile).replace(month=1, day=1)
|
||||||
|
|||||||
@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 4.1.5 on 2023-03-03 00:43
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('scrobbles', '0023_alter_audioscrobblertsvimport_options_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='chartrecord',
|
||||||
|
name='period_end',
|
||||||
|
field=models.DateTimeField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='chartrecord',
|
||||||
|
name='period_start',
|
||||||
|
field=models.DateTimeField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import calendar
|
import calendar
|
||||||
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
@ -15,6 +16,14 @@ from scrobbles.utils import check_scrobble_for_finish
|
|||||||
from sports.models import SportEvent
|
from sports.models import SportEvent
|
||||||
from videos.models import Series, Video
|
from videos.models import Series, Video
|
||||||
|
|
||||||
|
from vrobbler.apps.profiles.utils import (
|
||||||
|
end_of_day,
|
||||||
|
end_of_month,
|
||||||
|
end_of_week,
|
||||||
|
start_of_day,
|
||||||
|
start_of_month,
|
||||||
|
start_of_week,
|
||||||
|
)
|
||||||
from vrobbler.apps.scrobbles.stats import build_charts
|
from vrobbler.apps.scrobbles.stats import build_charts
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -62,6 +71,7 @@ class BaseFileImportMixin(TimeStampedModel):
|
|||||||
from scrobbles.models import Scrobble
|
from scrobbles.models import Scrobble
|
||||||
|
|
||||||
if not self.process_log:
|
if not self.process_log:
|
||||||
|
|
||||||
logger.warning("No lines in process log found to undo")
|
logger.warning("No lines in process log found to undo")
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -256,6 +266,26 @@ class ChartRecord(TimeStampedModel):
|
|||||||
series = models.ForeignKey(Series, on_delete=models.DO_NOTHING, **BNULL)
|
series = models.ForeignKey(Series, on_delete=models.DO_NOTHING, **BNULL)
|
||||||
artist = models.ForeignKey(Artist, on_delete=models.DO_NOTHING, **BNULL)
|
artist = models.ForeignKey(Artist, 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)
|
||||||
|
period_start = models.DateTimeField(**BNULL)
|
||||||
|
period_end = models.DateTimeField(**BNULL)
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
profile = self.user.profile
|
||||||
|
|
||||||
|
if self.week:
|
||||||
|
# set start and end to start and end of week
|
||||||
|
period = datetime.date.fromisocalendar(self.year, self.week, 1)
|
||||||
|
self.period_start = start_of_week(period, profile)
|
||||||
|
self.period_start = end_of_week(period, profile)
|
||||||
|
if self.day:
|
||||||
|
period = datetime.datetime(self.year, self.month, self.day)
|
||||||
|
self.period_start = start_of_day(period, profile)
|
||||||
|
self.period_end = end_of_day(period, profile)
|
||||||
|
if self.month and not self.day:
|
||||||
|
period = datetime.datetime(self.year, self.month, 1)
|
||||||
|
self.period_start = start_of_month(period, profile)
|
||||||
|
self.period_end = end_of_month(period, profile)
|
||||||
|
super(ChartRecord, self).save(*args, **kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def media_obj(self):
|
def media_obj(self):
|
||||||
|
|||||||
@ -6,8 +6,11 @@ from typing import Optional
|
|||||||
import pytz
|
import pytz
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models import Count, Q, ExpressionWrapper, OuterRef, Subquery
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.db.models import Count, Q
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -102,11 +105,11 @@ def get_scrobble_count_qs(
|
|||||||
|
|
||||||
|
|
||||||
def build_charts(
|
def build_charts(
|
||||||
|
user: "User",
|
||||||
year: Optional[int] = None,
|
year: Optional[int] = None,
|
||||||
month: Optional[int] = None,
|
month: Optional[int] = None,
|
||||||
week: Optional[int] = None,
|
week: Optional[int] = None,
|
||||||
day: Optional[int] = None,
|
day: Optional[int] = None,
|
||||||
user=None,
|
|
||||||
model_str="Track",
|
model_str="Track",
|
||||||
):
|
):
|
||||||
ChartRecord = apps.get_model(
|
ChartRecord = apps.get_model(
|
||||||
@ -140,3 +143,94 @@ def build_charts(
|
|||||||
ChartRecord.objects.bulk_create(
|
ChartRecord.objects.bulk_create(
|
||||||
chart_records, ignore_conflicts=True, batch_size=500
|
chart_records, ignore_conflicts=True, batch_size=500
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_yesterdays_charts_for_user(user: "User", model_str="Track") -> None:
|
||||||
|
"""Given a user calculate needed charts."""
|
||||||
|
ChartRecord = apps.get_model(
|
||||||
|
app_label='scrobbles', model_name='ChartRecord'
|
||||||
|
)
|
||||||
|
tz = pytz.timezone(settings.TIME_ZONE)
|
||||||
|
if user and user.is_authenticated:
|
||||||
|
tz = pytz.timezone(user.profile.timezone)
|
||||||
|
now = timezone.now().astimezone(tz)
|
||||||
|
yesterday = now - timedelta(days=1)
|
||||||
|
logger.info(
|
||||||
|
f"Generating charts for yesterday ({yesterday.date()}) for {user}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Always build yesterday's chart
|
||||||
|
ChartRecord.build(
|
||||||
|
user,
|
||||||
|
year=yesterday.year,
|
||||||
|
month=yesterday.month,
|
||||||
|
day=yesterday.day,
|
||||||
|
model_str=model_str,
|
||||||
|
)
|
||||||
|
now_week = now.isocalendar()[1]
|
||||||
|
yesterday_week = now.isocalendar()[1]
|
||||||
|
if now_week != yesterday_week:
|
||||||
|
logger.info(
|
||||||
|
f"New weekly charts for {yesterday.year}-{yesterday_week} for {user}"
|
||||||
|
)
|
||||||
|
ChartRecord.build(
|
||||||
|
user,
|
||||||
|
year=yesterday.year,
|
||||||
|
month=yesterday_week,
|
||||||
|
model_str=model_str,
|
||||||
|
)
|
||||||
|
# If the month has changed, build charts
|
||||||
|
if now.month != yesterday.month:
|
||||||
|
logger.info(
|
||||||
|
f"New monthly charts for {yesterday.year}-{yesterday.month} for {user}"
|
||||||
|
)
|
||||||
|
ChartRecord.build(
|
||||||
|
user,
|
||||||
|
year=yesterday.year,
|
||||||
|
month=yesterday.month,
|
||||||
|
model_str=model_str,
|
||||||
|
)
|
||||||
|
# If the year has changed, build charts
|
||||||
|
if now.year != yesterday.year:
|
||||||
|
logger.info(f"New annual charts for {yesterday.year} for {user}")
|
||||||
|
ChartRecord.build(user, year=yesterday.year, model_str=model_str)
|
||||||
|
|
||||||
|
|
||||||
|
def build_missing_charts_for_user(user: "User", model_str="Track") -> None:
|
||||||
|
""""""
|
||||||
|
ChartRecord = apps.get_model(
|
||||||
|
app_label='scrobbles', model_name='ChartRecord'
|
||||||
|
)
|
||||||
|
Scrobble = apps.get_model(app_label='scrobbles', model_name='Scrobble')
|
||||||
|
|
||||||
|
logger.info(f"Generating historical charts for {user}")
|
||||||
|
tz = pytz.timezone(settings.TIME_ZONE)
|
||||||
|
if user and user.is_authenticated:
|
||||||
|
tz = pytz.timezone(user.profile.timezone)
|
||||||
|
now = timezone.now().astimezone(tz)
|
||||||
|
|
||||||
|
first_scrobble = (
|
||||||
|
Scrobble.objects.filter(user=user, played_to_completion=True)
|
||||||
|
.order_by('created')
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
|
||||||
|
start_date = first_scrobble.timestamp
|
||||||
|
days_since = (now - start_date).days
|
||||||
|
|
||||||
|
for day_num in range(0, days_since):
|
||||||
|
build_date = start_date + timedelta(days=day_num)
|
||||||
|
logger.info(f"Generating chart batch for {build_date}")
|
||||||
|
ChartRecord.build(user=user, year=build_date.year)
|
||||||
|
ChartRecord.build(
|
||||||
|
user=user, year=build_date.year, week=build_date.isocalendar()[1]
|
||||||
|
)
|
||||||
|
ChartRecord.build(
|
||||||
|
user=user, year=build_date.year, month=build_date.month
|
||||||
|
)
|
||||||
|
ChartRecord.build(
|
||||||
|
user=user,
|
||||||
|
year=build_date.year,
|
||||||
|
month=build_date.month,
|
||||||
|
day=build_date.day,
|
||||||
|
)
|
||||||
|
|||||||
@ -1,13 +1,17 @@
|
|||||||
import logging
|
import logging
|
||||||
from celery import shared_task
|
|
||||||
|
|
||||||
|
from celery import shared_task
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
from scrobbles.models import (
|
from scrobbles.models import (
|
||||||
AudioScrobblerTSVImport,
|
AudioScrobblerTSVImport,
|
||||||
KoReaderImport,
|
KoReaderImport,
|
||||||
LastFmImport,
|
LastFmImport,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from vrobbler.apps.scrobbles.stats import build_yesterdays_charts_for_user
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
User = get_user_model()
|
||||||
|
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
@ -35,3 +39,9 @@ def process_koreader_import(import_id):
|
|||||||
logger.warn(f"KOReaderImport not found with id {import_id}")
|
logger.warn(f"KOReaderImport not found with id {import_id}")
|
||||||
|
|
||||||
koreader_import.process()
|
koreader_import.process()
|
||||||
|
|
||||||
|
|
||||||
|
@shared_task
|
||||||
|
def create_yesterdays_charts():
|
||||||
|
for user in User.objects.all():
|
||||||
|
build_yesterdays_charts_for_user(user)
|
||||||
|
|||||||
@ -58,13 +58,16 @@ def lookup_album_from_tadb(name: str, artist: str) -> dict:
|
|||||||
album_info['theaudiodb_mood'] = album.get('strMood')
|
album_info['theaudiodb_mood'] = album.get('strMood')
|
||||||
album_info['theaudiodb_speed'] = album.get('strSpeed')
|
album_info['theaudiodb_speed'] = album.get('strSpeed')
|
||||||
album_info['theaudiodb_theme'] = album.get('strTheme')
|
album_info['theaudiodb_theme'] = album.get('strTheme')
|
||||||
album_info['theaudiodb_year_released'] = album.get('intYearReleased')
|
|
||||||
album_info['allmusic_id'] = album.get('strAllMusicID')
|
album_info['allmusic_id'] = album.get('strAllMusicID')
|
||||||
album_info['wikipedia_slug'] = album.get('strWikipediaID')
|
album_info['wikipedia_slug'] = album.get('strWikipediaID')
|
||||||
album_info['discogs_id'] = album.get('strDiscogsID')
|
album_info['discogs_id'] = album.get('strDiscogsID')
|
||||||
album_info['wikidata_id'] = album.get('strWikidataID')
|
album_info['wikidata_id'] = album.get('strWikidataID')
|
||||||
album_info['rateyourmusic_id'] = album.get('strRateYourMusicID')
|
album_info['rateyourmusic_id'] = album.get('strRateYourMusicID')
|
||||||
|
|
||||||
|
if album.get('intYearReleased'):
|
||||||
|
album_info['theaudiodb_year_released'] = float(
|
||||||
|
album.get('intYearReleased')
|
||||||
|
)
|
||||||
if album.get('intScore'):
|
if album.get('intScore'):
|
||||||
album_info['theaudiodb_score'] = float(album.get('intScore'))
|
album_info['theaudiodb_score'] = float(album.get('intScore'))
|
||||||
if album.get('intScoreVotes'):
|
if album.get('intScoreVotes'):
|
||||||
|
|||||||
@ -69,7 +69,6 @@ class RecentScrobbleList(ListView):
|
|||||||
data = super().get_context_data(**kwargs)
|
data = super().get_context_data(**kwargs)
|
||||||
user = self.request.user
|
user = self.request.user
|
||||||
if user.is_authenticated:
|
if user.is_authenticated:
|
||||||
|
|
||||||
completed_for_user = Scrobble.objects.filter(
|
completed_for_user = Scrobble.objects.filter(
|
||||||
played_to_completion=True, user=user
|
played_to_completion=True, user=user
|
||||||
)
|
)
|
||||||
@ -84,14 +83,34 @@ class RecentScrobbleList(ListView):
|
|||||||
data['sport_scrobble_list'] = completed_for_user.filter(
|
data['sport_scrobble_list'] = completed_for_user.filter(
|
||||||
sport_event__isnull=False
|
sport_event__isnull=False
|
||||||
).order_by('-timestamp')[:15]
|
).order_by('-timestamp')[:15]
|
||||||
|
|
||||||
data['active_imports'] = AudioScrobblerTSVImport.objects.filter(
|
data['active_imports'] = AudioScrobblerTSVImport.objects.filter(
|
||||||
processing_started__isnull=False,
|
processing_started__isnull=False,
|
||||||
processed_finished__isnull=True,
|
processed_finished__isnull=True,
|
||||||
user=self.request.user,
|
user=self.request.user,
|
||||||
)
|
)
|
||||||
|
|
||||||
data["weekly_data"] = week_of_scrobbles(user=user)
|
limit = 14
|
||||||
|
artist = {'user': user, 'media_type': 'Artist', 'limit': limit}
|
||||||
|
# This is weird. They don't display properly as QuerySets, so we cast to lists
|
||||||
|
data['current_artist_charts'] = {
|
||||||
|
"today": list(live_charts(**artist, chart_period="today")),
|
||||||
|
"week": list(live_charts(**artist, chart_period="week")),
|
||||||
|
"month": list(live_charts(**artist, chart_period="month")),
|
||||||
|
"year": list(live_charts(**artist, chart_period="year")),
|
||||||
|
"all": list(live_charts(**artist)),
|
||||||
|
}
|
||||||
|
|
||||||
|
track = {'user': user, 'media_type': 'Track', 'limit': limit}
|
||||||
|
data['current_track_charts'] = {
|
||||||
|
"today": list(live_charts(**track, chart_period="today")),
|
||||||
|
"week": list(live_charts(**track, chart_period="week")),
|
||||||
|
"month": list(live_charts(**track, chart_period="month")),
|
||||||
|
"year": list(live_charts(**track, chart_period="year")),
|
||||||
|
"all": list(live_charts(**track)),
|
||||||
|
}
|
||||||
|
|
||||||
|
data["weekly_data"] = week_of_scrobbles(user=user)
|
||||||
data['counts'] = scrobble_counts(user)
|
data['counts'] = scrobble_counts(user)
|
||||||
data['imdb_form'] = ScrobbleForm
|
data['imdb_form'] = ScrobbleForm
|
||||||
data['export_form'] = ExportScrobbleForm
|
data['export_form'] = ExportScrobbleForm
|
||||||
@ -457,20 +476,39 @@ class ChartRecordView(TemplateView):
|
|||||||
context_data["artist_charts"] = {}
|
context_data["artist_charts"] = {}
|
||||||
|
|
||||||
if not date:
|
if not date:
|
||||||
|
limit = 20
|
||||||
artist_params = {'user': user, 'media_type': 'Artist'}
|
artist_params = {'user': user, 'media_type': 'Artist'}
|
||||||
context_data['current_artist_charts'] = {
|
context_data['current_artist_charts'] = {
|
||||||
"today": live_charts(**artist_params, chart_period="today"),
|
"today": live_charts(
|
||||||
"week": live_charts(**artist_params, chart_period="week"),
|
**artist_params, chart_period="today", limit=limit
|
||||||
"month": live_charts(**artist_params, chart_period="month"),
|
),
|
||||||
"all": live_charts(**artist_params),
|
"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'}
|
track_params = {'user': user, 'media_type': 'Track'}
|
||||||
context_data['current_track_charts'] = {
|
context_data['current_track_charts'] = {
|
||||||
"today": live_charts(**track_params, chart_period="today"),
|
"today": live_charts(
|
||||||
"week": live_charts(**track_params, chart_period="week"),
|
**track_params, chart_period="today", limit=limit
|
||||||
"month": live_charts(**track_params, chart_period="month"),
|
),
|
||||||
"all": live_charts(**track_params),
|
"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),
|
||||||
}
|
}
|
||||||
return context_data
|
return context_data
|
||||||
|
|
||||||
|
|||||||
18
vrobbler/apps/sports/urls.py
Normal file
18
vrobbler/apps/sports/urls.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from sports import views
|
||||||
|
|
||||||
|
app_name = 'sports'
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path(
|
||||||
|
'sport-events/',
|
||||||
|
views.SportEventListView.as_view(),
|
||||||
|
name='event_list',
|
||||||
|
),
|
||||||
|
path(
|
||||||
|
'sport-events/<slug:slug>/',
|
||||||
|
views.SportEventDetailView.as_view(),
|
||||||
|
name='event_detail',
|
||||||
|
),
|
||||||
|
]
|
||||||
12
vrobbler/apps/sports/views.py
Normal file
12
vrobbler/apps/sports/views.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
from django.views import generic
|
||||||
|
from sports.models import SportEvent
|
||||||
|
|
||||||
|
|
||||||
|
class SportEventListView(generic.ListView):
|
||||||
|
model = SportEvent
|
||||||
|
paginate_by = 50
|
||||||
|
|
||||||
|
|
||||||
|
class SportEventDetailView(generic.DetailView):
|
||||||
|
model = SportEvent
|
||||||
|
slug_field = 'uuid'
|
||||||
@ -302,54 +302,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/feather-icons@4.28.0/dist/feather.min.js" integrity="sha384-uO3SXW5IuS1ZpFPKugNNWqTZRRglnUJK6UAZ/gxOX80nxEkN9NcGZTftn6RzhGWE" crossorigin="anonymous"></script><script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js" integrity="sha384-zNy6FEbO50N+Cg5wap8IKA4M/ZnLJgzc6w2NqACZaK0u0FXfOWRRJOnQtpZun8ha" crossorigin="anonymous"></script>
|
|
||||||
<script><!-- comment ------------------------------------------------->
|
|
||||||
/* globals Chart:false, feather:false */
|
|
||||||
(function () {
|
|
||||||
'use strict'
|
|
||||||
|
|
||||||
feather.replace({ 'aria-hidden': 'true' })
|
|
||||||
|
|
||||||
// Graphs
|
|
||||||
var ctx = document.getElementById('myChart')
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
var myChart = new Chart(ctx, {
|
|
||||||
type: 'line',
|
|
||||||
data: {
|
|
||||||
labels: [
|
|
||||||
{% for day in weekly_data.keys %}
|
|
||||||
"{{day}}"{% if not forloop.last %},{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
],
|
|
||||||
datasets: [{
|
|
||||||
data: [
|
|
||||||
{% for count in weekly_data.values %}
|
|
||||||
{{count}}{% if not forloop.last %},{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
],
|
|
||||||
lineTension: 0,
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
borderColor: '#007bf0',
|
|
||||||
borderWidth: 4,
|
|
||||||
pointBackgroundColor: '#007bff'
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
scales: {
|
|
||||||
yAxes: [{
|
|
||||||
ticks: {
|
|
||||||
beginAtZero: false
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
display: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})()
|
|
||||||
|
|
||||||
</script>
|
|
||||||
{% block extra_js %}
|
{% block extra_js %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -46,17 +46,17 @@
|
|||||||
<table class="table table-striped table-sm">
|
<table class="table table-striped table-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th scope="col">Scrobbles</th>
|
||||||
<th scope="col">Album</th>
|
<th scope="col">Album</th>
|
||||||
<th scope="col">Artist</th>
|
<th scope="col">Artist</th>
|
||||||
<th scope="col">Scrobbles</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for album in object_list %}
|
{% for album in object_list %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{{album.get_absolute_url}}">{{album}}</a></td>
|
|
||||||
<td><a href="{{album.artist.get_absolute_url}}">{{album.artist}}</a></td>
|
|
||||||
<td>{{album.scrobbles.count}}</td>
|
<td>{{album.scrobbles.count}}</td>
|
||||||
|
<td><a href="{{album.get_absolute_url}}">{{album}}</a></td>
|
||||||
|
<td><a href="{{album.primary_artist.get_absolute_url}}">{{album.primary_artist}}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@ -44,15 +44,15 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for track in object.tracks %}
|
{% for track in tracks_ranked %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{rank}}#1</td>
|
<td>#{{track.0}}</td>
|
||||||
<td><a href="{{track.get_absolute_url}}">{{track.title}}</a></td>
|
<td><a href="{{track.1.get_absolute_url}}">{{track.1.title}}</a></td>
|
||||||
<td><a href="{{track.album.get_absolute_url}}">{{track.album}}</a></td>
|
<td><a href="{{track.1.album.get_absolute_url}}">{{track.1.album}}</a></td>
|
||||||
<td>{{track.scrobble_count}}</td>
|
<td>{{track.1.scrobble_count}}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="progress-bar" style="margin-right:5px;">
|
<div class="progress-bar" style="margin-right:5px;">
|
||||||
<span class="progress-bar-fill" style="width: {{track.scrobble_count|mul:10}}%;"></span>
|
<span class="progress-bar-fill" style="width: {{track.1.scrobble_count|mul:10}}%;"></span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@ -45,17 +45,15 @@
|
|||||||
<table class="table table-striped table-sm">
|
<table class="table table-striped table-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">Artist</th>
|
|
||||||
<th scope="col">Scrobbles</th>
|
<th scope="col">Scrobbles</th>
|
||||||
<th scope="col">All time</th>
|
<th scope="col">Artist</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for artist in object_list %}
|
{% for artist in object_list %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{{artist.get_absolute_url}}">{{artist}}</a></td>
|
|
||||||
<td>{{artist.scrobbles.count}}</td>
|
<td>{{artist.scrobbles.count}}</td>
|
||||||
<td></td>
|
<td><a href="{{artist.get_absolute_url}}">{{artist}}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@ -24,17 +24,17 @@
|
|||||||
<table class="table table-striped table-sm">
|
<table class="table table-striped table-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th scope="col">Scrobbles</th>
|
||||||
<th scope="col">Track</th>
|
<th scope="col">Track</th>
|
||||||
<th scope="col">Artist</th>
|
<th scope="col">Artist</th>
|
||||||
<th scope="col">Scrobbles</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for track in object_list %}
|
{% for track in object_list %}
|
||||||
<tr>
|
<tr>
|
||||||
|
<td>{{track.scrobble_set.count}}</td>
|
||||||
<td><a href="{{track.get_absolute_url}}">{{track}}</a></td>
|
<td><a href="{{track.get_absolute_url}}">{{track}}</a></td>
|
||||||
<td><a href="{{track.artist.get_absolute_url}}">{{track.artist}}</a></td>
|
<td><a href="{{track.artist.get_absolute_url}}">{{track.artist}}</a></td>
|
||||||
<td>{{track.scrobble_set.count}}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@ -67,7 +67,7 @@
|
|||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link {% if forloop.first %}active{% endif %}" id="artist-{{chart_name}}-tab" data-bs-toggle="tab" data-bs-target="#artist-{{chart_name}}"
|
<button class="nav-link {% if forloop.first %}active{% endif %}" id="artist-{{chart_name}}-tab" data-bs-toggle="tab" data-bs-target="#artist-{{chart_name}}"
|
||||||
type="button" role="tab" aria-controls="home" aria-selected="true">
|
type="button" role="tab" aria-controls="home" aria-selected="true">
|
||||||
{{chart_name}}
|
{% if chart_name == "all" %}All Time{% else %}{% if chart_name != "today" %}This {% endif %}{{chart_name|capfirst}}{% endif %}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -110,7 +110,7 @@
|
|||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link {% if forloop.first %}active{% endif %}" id="track-{{chart_name}}-tab" data-bs-toggle="tab" data-bs-target="#track-{{chart_name}}"
|
<button class="nav-link {% if forloop.first %}active{% endif %}" id="track-{{chart_name}}-tab" data-bs-toggle="tab" data-bs-target="#track-{{chart_name}}"
|
||||||
type="button" role="tab" aria-controls="home" aria-selected="true">
|
type="button" role="tab" aria-controls="home" aria-selected="true">
|
||||||
{{chart_name}}
|
{% if chart_name == "all" %}All Time{% else %}{% if chart_name != "today" %}This {% endif %}{{chart_name|capfirst}}{% endif %}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@ -1,7 +1,49 @@
|
|||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% load humanize %}
|
{% load humanize %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% 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 content %}
|
{% block content %}
|
||||||
<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
|
||||||
@ -25,7 +67,7 @@
|
|||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<button type="button" class="btn btn-sm btn-outline-secondary dropdown-toggle" id="graphDateButton"
|
<button type="button" class="btn btn-sm btn-outline-secondary dropdown-toggle" id="graphDateButton"
|
||||||
data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
<span data-feather="calendar"></span>
|
<div data-feather="calendar"></div>
|
||||||
This week
|
This week
|
||||||
</button>
|
</button>
|
||||||
<div class="dropdown-menu" data-bs-toggle="#graphDataChange" aria-labelledby="graphDateButton">
|
<div class="dropdown-menu" data-bs-toggle="#graphDataChange" aria-labelledby="graphDateButton">
|
||||||
@ -36,14 +78,306 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<canvas class="my-4 w-100" id="myChart" width="900" height="220"></canvas>
|
<canvas class="my-4 w-100" id="myChart" width="900" height="150"></canvas>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<p>Today <b>{{counts.today}}</b> | This Week <b>{{counts.week}}</b> | This Month <b>{{counts.month}}</b> |
|
<h2>Top Artist</h2>
|
||||||
This Year <b>{{counts.year}}</b> | All Time <b>{{counts.alltime}}</b></p>
|
<ul class="nav nav-tabs" id="artistTab" role="tablist">
|
||||||
|
{% for chart_name in current_artist_charts.keys %}
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link {% if forloop.counter == 2 %}active{% endif %}"
|
||||||
|
id="artist-{{chart_name}}-tab" data-bs-toggle="tab" data-bs-target="#artist-{{chart_name}}"
|
||||||
|
type="button" role="tab" aria-controls="home" aria-selected="true">
|
||||||
|
{% if chart_name == "all" %}All Time{% else %}{% if chart_name != "today" %}This {% endif %}{{chart_name|capfirst}}{% endif %}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="tab-content" id="artistTabContent" class="maloja-chart">
|
||||||
|
{% for chart_name, artists in current_artist_charts.items %}
|
||||||
|
<div class="tab-pane fade {% if forloop.counter == 2 %}show active{% endif %}" id="artist-{{chart_name}}" role="tabpanel" aria-labelledby="artist-{{chart_name}}-tab">
|
||||||
|
<div style="display:block">
|
||||||
|
<div style="float:left;">
|
||||||
|
<div class="image-wrapper" style="display:flex; flex-wrap: wrap; margin:0">
|
||||||
|
<div class="caption">#1 {{artists.0.name}}</div>
|
||||||
|
{% if artists.0.thumbnail %}
|
||||||
|
<a href="{{artists.0.get_absolute_url}}"><img lt="{{artists.0.name}}" src="{{artists.0.thumbnail.url}}" width="300px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{artists.0.get_absolute_url}}"><img lt="{{artists.0.name}}" src="{% static "images/artist-placeholder.jpg" %}" width="300px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="float:left; width:300px;">
|
||||||
|
<div style="display:flex; flex-wrap: wrap;">
|
||||||
|
<div class="image-wrapper" class="image-wrapper" style="width:50%">
|
||||||
|
<div class="caption-medium">#2 {{artists.1.name}}</div>
|
||||||
|
{% if artists.1.thumbnail %}
|
||||||
|
<a href="{{artists.1.get_absolute_url}}"><img lt="{{artists.1.name}}" src="{{artists.1.thumbnail.url}}" width="150px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{artists.1.get_absolute_url}}"><img src="{% static "images/artist-placeholder.jpg" %}" width="150px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="image-wrapper" class="image-wrapper" style="width:50%">
|
||||||
|
<div class="caption-medium">#3 {{artists.2.name}}</div>
|
||||||
|
{% if artists.2.thumbnail %}
|
||||||
|
<a href="{{artists.2.get_absolute_url}}"><img src="{{artists.2.thumbnail.url}}" width="150px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{artists.2.get_absolute_url}}"><img src="{% static "images/artist-placeholder.jpg" %}" width="150px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="image-wrapper" class="image-wrapper" style="width:50%">
|
||||||
|
<div class="caption-medium">#4 {{artists.3.name}}</div>
|
||||||
|
{% if artists.3.thumbnail %}
|
||||||
|
<a href="{{artists.3.get_absolute_url}}"><img src="{{artists.3.thumbnail.url}}" width="150px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{artists.3.get_absolute_url}}"><img src="{% static "images/artist-placeholder.jpg" %}" width="150px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="image-wrapper" class="image-wrapper" style="width:50%">
|
||||||
|
<div class="caption-medium">#5 {{artists.4.name}}</div>
|
||||||
|
{% if artists.4.thumbnail %}
|
||||||
|
<a href="{{artists.4.get_absolute_url}}"><img src="{{artists.4.thumbnail.url}}" width="150px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{artists.4.get_absolute_url}}"><img src="{% static "images/artist-placeholder.jpg" %}" width="150px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="float:left; width:300px;">
|
||||||
|
<div style="display:flex; flex-wrap: wrap;">
|
||||||
|
<div class="image-wrapper" class="image-wrapper" class="image-wrapper" style="width:33;">
|
||||||
|
<div class="caption-small">#6 {{artists.5.name}}</div>
|
||||||
|
{% if artists.5.thumbnail %}
|
||||||
|
<a href="{{artists.5.get_absolute_url}}"><img src="{{artists.5.thumbnail.url}}" width="100px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{artists.5.get_absolute_url}}"><img src="{% static "images/artist-placeholder.jpg" %}" width="100px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="image-wrapper" class="image-wrapper" style="width:33;">
|
||||||
|
<div class="caption-small">#7 {{artists.6.name}}</div>
|
||||||
|
{% if artists.6.thumbnail %}
|
||||||
|
<a href="{{artists.6.get_absolute_url}}"><img src="{{artists.6.thumbnail.url}}" width="100px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{artists.6.get_absolute_url}}"><img src="{% static "images/artist-placeholder.jpg" %}" width="100px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="image-wrapper" class="image-wrapper" style="width:33;">
|
||||||
|
<div class="caption-small">#8 {{artists.7.name}}</div>
|
||||||
|
{% if artists.7.thumbnail %}
|
||||||
|
<a href="{{artists.7.get_absolute_url}}"><img src="{{artists.7.thumbnail.url}}" width="100px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{artists.7.get_absolute_url}}"><img src="{% static "images/artist-placeholder.jpg" %}" width="100px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="image-wrapper" class="image-wrapper" style="width:33;">
|
||||||
|
<div class="caption-small">#9 {{artists.8.name}}</div>
|
||||||
|
{% if artists.8.thumbnail %}
|
||||||
|
<a href="{{artists.8.get_absolute_url}}"><img src="{{artists.8.thumbnail.url}}" width="100px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{artists.8.get_absolute_url}}"><img src="{% static "images/artist-placeholder.jpg" %}" width="100px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="image-wrapper" class="image-wrapper" style="width:33;">
|
||||||
|
<div class="caption-small">#10 {{artists.9.name}}</div>
|
||||||
|
{% if artists.9.thumbnail %}
|
||||||
|
<a href="{{artists.9.get_absolute_url}}"><img src="{{artists.9.thumbnail.url}}" width="100px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{artists.9.get_absolute_url}}"><img src="{% static "images/artist-placeholder.jpg" %}" width="100px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="image-wrapper" class="image-wrapper" style="width:33;">
|
||||||
|
<div class="caption-small">#11 {{artists.10.name}}</div>
|
||||||
|
{% if artists.10.thumbnail %}
|
||||||
|
<a href="{{artists.10.get_absolute_url}}"><img src="{{artists.10.thumbnail.url}}" width="100px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{artists.10.get_absolute_url}}"><img src="{% static "images/artist-placeholder.jpg" %}" width="100px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="image-wrapper" class="image-wrapper" style="width:33;">
|
||||||
|
<div class="caption-small">#12 {{artists.11.name}}</div>
|
||||||
|
{% if artists.11.thumbnail %}
|
||||||
|
<a href="{{artists.11.get_absolute_url}}"><img src="{{artists.11.thumbnail.url}}" width="100px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{artists.11.get_absolute_url}}"><img src="{% static "images/artist-placeholder.jpg" %}" width="100px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="image-wrapper" class="image-wrapper" style="width:33;">
|
||||||
|
<div class="caption-small">#13 {{artists.12.name}}</div>
|
||||||
|
{% if artists.12.thumbnail %}
|
||||||
|
<a href="{{artists.12.get_absolute_url}}"><img src="{{artists.12.thumbnail.url}}" width="100px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{artists.12.get_absolute_url}}"><img src="{% static "images/artist-placeholder.jpg" %}" width="100px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="image-wrapper" class="image-wrapper" style="width:33;">
|
||||||
|
<div class="caption-small">#14 {{artists.13.name}}</div>
|
||||||
|
{% if artists.13.thumbnail %}
|
||||||
|
<a href="{{artists.13.get_absolute_url}}"><img src="{{artists.13.thumbnail.url}}" width="100px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{artists.13.get_absolute_url}}"><img src="{% static "images/artist-placeholder.jpg" %}" width="100px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<h2>Top Tracks</h2>
|
||||||
|
<ul class="nav nav-tabs" id="trackTab" role="tablist">
|
||||||
|
{% for chart_name in current_track_charts.keys %}
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link {% if forloop.counter == 2 %}active{% endif %}" id="track-{{chart_name}}-tab" data-bs-toggle="tab"
|
||||||
|
data-bs-target="#track-{{chart_name}}" type="button" role="tab" aria-controls="home" aria-selected="true">
|
||||||
|
{% if chart_name == "all" %}All Time{% else %}{% if chart_name != "today" %}This {% endif %}{{chart_name|capfirst}}{% endif %}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div class="tab-content" id="trackTabContent" class="maloja-chart">
|
||||||
|
{% for chart_name, tracks in current_track_charts.items %}
|
||||||
|
<div class="tab-pane fade {% if forloop.counter == 2 %}show active{% endif %}" id="track-{{chart_name}}" role="tabpanel" aria-labelledby="track-{{chart_name}}-tab">
|
||||||
|
<div style="display:block">
|
||||||
|
<div style="float:left;">
|
||||||
|
<div class="image-wrapper" style="display:flex; flex-wrap: wrap; margin:0">
|
||||||
|
<div class="caption">#1 {{tracks.0.title}}</div>
|
||||||
|
{% if tracks.0.album.cover_image %}
|
||||||
|
<a href="{{tracks.0.get_absolute_url}}"><img src="{{tracks.0.album.cover_image.url}}" width="300px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{tracks.0.get_absolute_url}}"><img src="{% static 'images/track-placeholder.jpg' %}" width="300px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="float:left; width:300px;">
|
||||||
|
<div style="display:flex; flex-wrap: wrap;">
|
||||||
|
<div class="image-wrapper" style="width:50%">
|
||||||
|
<div class="caption-medium">#2 {{tracks.1.title}}</div>
|
||||||
|
{% if tracks.1.album.cover_image %}
|
||||||
|
<a href="{{tracks.1.get_absolute_url}}"><img src="{{tracks.1.album.cover_image.url}}" width="150px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{tracks.1.get_absolute_url}}"><img src="{% static 'images/track-placeholder.jpg' %}" width="150px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="image-wrapper" style="width:50%">
|
||||||
|
<div class="caption-medium">#3 {{tracks.2.title}}</div>
|
||||||
|
{% if tracks.2.album.cover_image %}
|
||||||
|
<a href="{{tracks.2.get_absolute_url}}"><img src="{{tracks.2.album.cover_image.url}}" width="150px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{tracks.2.get_absolute_url}}"><img src="{% static 'images/track-placeholder.jpg' %}" width="150px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="image-wrapper" style="width:50%">
|
||||||
|
<div class="caption-medium">#4 {{tracks.3.title}}</div>
|
||||||
|
{% if tracks.3.album.cover_image %}
|
||||||
|
<a href="{{tracks.3.get_absolute_url}}"><img src="{{tracks.3.album.cover_image.url}}" width="150px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{tracks.3.get_absolute_url}}"><img src="{% static 'images/track-placeholder.jpg' %}" width="150px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="image-wrapper" style="width:50%">
|
||||||
|
<div class="caption-medium">#5 {{tracks.4.title}}</div>
|
||||||
|
{% if tracks.4.album.cover_image %}
|
||||||
|
<a href="{{tracks.4.get_absolute_url}}"><img src="{{tracks.4.album.cover_image.url}}" width="150px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{tracks.4.get_absolute_url}}"><img src="{% static 'images/track-placeholder.jpg' %}" width="150px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="float:left; width:300px;">
|
||||||
|
<div style="display:flex; flex-wrap: wrap;">
|
||||||
|
<div class="image-wrapper" class="image-wrapper" style="width:33;">
|
||||||
|
<div class="caption-small">#6 {{tracks.5.title}}</div>
|
||||||
|
{% if tracks.5.album.cover_image %}
|
||||||
|
<a href="{{tracks.5.get_absolute_url}}"><img src="{{tracks.5.album.cover_image.url}}" width="100px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{tracks.5.get_absolute_url}}"><img src="{% static 'images/track-placeholder.jpg' %}" width="100px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="image-wrapper" class="image-wrapper" style="width:33;">
|
||||||
|
<div class="caption-small">#7 {{tracks.6.title}}</div>
|
||||||
|
{% if tracks.6.album.cover_image %}
|
||||||
|
<a href="{{tracks.6.get_absolute_url}}"><img src="{{tracks.6.album.cover_image.url}}" width="100px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{tracks.6.get_absolute_url}}"><img src="{% static 'images/track-placeholder.jpg' %}" width="100px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="image-wrapper" class="image-wrapper" style="width:33;">
|
||||||
|
<div class="caption-small">#8 {{tracks.7.title}}</div>
|
||||||
|
{% if tracks.7.album.cover_image %}
|
||||||
|
<a href="{{tracks.7.get_absolute_url}}"><img src="{{tracks.7.album.cover_image.url}}" width="100px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{tracks.7.get_absolute_url}}"><img src="{% static 'images/track-placeholder.jpg' %}" width="100px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="image-wrapper" class="image-wrapper" style="width:33;">
|
||||||
|
<div class="caption-small">#9 {{tracks.8.title}}</div>
|
||||||
|
{% if tracks.8.album.cover_image %}
|
||||||
|
<a href="{{tracks.8.get_absolute_url}}"><img src="{{tracks.8.album.cover_image.url}}" width="100px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{tracks.8.get_absolute_url}}"><img src="{% static 'images/track-placeholder.jpg' %}" width="100px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="image-wrapper" class="image-wrapper" style="width:33;">
|
||||||
|
<div class="caption-small">#10 {{tracks.9.title}}</div>
|
||||||
|
{% if tracks.9.album.cover_image %}
|
||||||
|
<a href="{{tracks.9.get_absolute_url}}"><img src="{{tracks.9.album.cover_image.url}}" width="100px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{tracks.9.get_absolute_url}}"><img src="{% static 'images/track-placeholder.jpg' %}" width="100px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="image-wrapper" class="image-wrapper" style="width:33;">
|
||||||
|
<div class="caption-small">#11 {{tracks.10.title}}</div>
|
||||||
|
{% if tracks.10.album.cover_image %}
|
||||||
|
<a href="{{tracks.10.get_absolute_url}}"><img src="{{tracks.10.album.cover_image.url}}" width="100px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{tracks.10.get_absolute_url}}"><img src="{% static 'images/track-placeholder.jpg' %}" width="100px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="image-wrapper" class="image-wrapper" style="width:33;">
|
||||||
|
<div class="caption-small">#12 {{tracks.11.title}}</div>
|
||||||
|
{% if tracks.11.album.cover_image %}
|
||||||
|
<a href="{{tracks.11.get_absolute_url}}"><img src="{{tracks.11.album.cover_image.url}}" width="100px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{tracks.11.get_absolute_url}}"><img src="{% static 'images/track-placeholder.jpg' %}" width="100px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="image-wrapper" class="image-wrapper" style="width:33;">
|
||||||
|
<div class="caption-small">#13 {{tracks.12.title}}</div>
|
||||||
|
{% if tracks.12.album.cover_image %}
|
||||||
|
<a href="{{tracks.12.get_absolute_url}}"><img src="{{tracks.12.album.cover_image.url}}" width="100px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{tracks.12.get_absolute_url}}"><img src="{% static 'images/track-placeholder.jpg' %}" width="100px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="image-wrapper" class="image-wrapper" style="width:33;">
|
||||||
|
<div class="caption-small">#14 {{tracks.13.title}}</div>
|
||||||
|
{% if tracks.13.album.cover_image %}
|
||||||
|
<a href="{{tracks.13.get_absolute_url}}"><img src="{{tracks.13.album.cover_image.url}}" width="100px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{tracks.13.get_absolute_url}}"><img src="{% static 'images/track-placeholder.jpg' %}" width="100px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<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>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||||
@ -68,7 +402,6 @@
|
|||||||
<div class="tab-content" id="myTabContent2">
|
<div class="tab-content" id="myTabContent2">
|
||||||
<div class="tab-pane fade show active" id="latest-listened" role="tabpanel"
|
<div class="tab-pane fade show active" id="latest-listened" role="tabpanel"
|
||||||
aria-labelledby="latest-listened-tab">
|
aria-labelledby="latest-listened-tab">
|
||||||
<h2>Latest listened</h2>
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-striped table-sm">
|
<table class="table table-striped table-sm">
|
||||||
<thead>
|
<thead>
|
||||||
@ -183,7 +516,7 @@
|
|||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title" id="importModalLabel">Import scrobbles</h5>
|
<h5 class="modal-title" id="importModalLabel">Import scrobbles</h5>
|
||||||
<button type="button" class="close" data-bs-dismiss="modal" aria-label="Close">
|
<button type="button" class="close" data-bs-dismiss="modal" aria-label="Close">
|
||||||
<span aria-hidden="true">×</span>
|
<div aria-hidden="true">×</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<form action="{% url 'scrobbles:audioscrobbler-file-upload' %}" method="post" enctype="multipart/form-data">
|
<form action="{% url 'scrobbles:audioscrobbler-file-upload' %}" method="post" enctype="multipart/form-data">
|
||||||
@ -221,7 +554,7 @@
|
|||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title" id="exportModalLabel">Export scrobbles</h5>
|
<h5 class="modal-title" id="exportModalLabel">Export scrobbles</h5>
|
||||||
<button type="button" class="close" data-bs-dismiss="modal" aria-label="Close">
|
<button type="button" class="close" data-bs-dismiss="modal" aria-label="Close">
|
||||||
<span aria-hidden="true">×</span>
|
<div aria-hidden="true">×</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<form action="{% url 'scrobbles:export' %}" method="get">
|
<form action="{% url 'scrobbles:export' %}" method="get">
|
||||||
@ -246,4 +579,53 @@
|
|||||||
$('#importModal').on('shown.bs.modal', function () { $('#importInput').trigger('focus') });
|
$('#importModal').on('shown.bs.modal', function () { $('#importInput').trigger('focus') });
|
||||||
$('#exportModal').on('shown.bs.modal', function () { $('#exportInput').trigger('focus') });
|
$('#exportModal').on('shown.bs.modal', function () { $('#exportInput').trigger('focus') });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/feather-icons@4.28.0/dist/feather.min.js" integrity="sha384-uO3SXW5IuS1ZpFPKugNNWqTZRRglnUJK6UAZ/gxOX80nxEkN9NcGZTftn6RzhGWE" crossorigin="anonymous"></script><script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js" integrity="sha384-zNy6FEbO50N+Cg5wap8IKA4M/ZnLJgzc6w2NqACZaK0u0FXfOWRRJOnQtpZun8ha" crossorigin="anonymous"></script>
|
||||||
|
<script><!-- comment ------------------------------------------------->
|
||||||
|
/* globals Chart:false, feather:false */
|
||||||
|
(function () {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
feather.replace({ 'aria-hidden': 'true' })
|
||||||
|
|
||||||
|
// Graphs
|
||||||
|
var ctx = document.getElementById('myChart')
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
var myChart = new Chart(ctx, {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: [
|
||||||
|
{% for day in weekly_data.keys %}
|
||||||
|
"{{day}}"{% if not forloop.last %},{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
],
|
||||||
|
datasets: [{
|
||||||
|
data: [
|
||||||
|
{% for count in weekly_data.values %}
|
||||||
|
{{count}}{% if not forloop.last %},{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
],
|
||||||
|
lineTension: 0,
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
borderColor: '#007bf0',
|
||||||
|
borderWidth: 4,
|
||||||
|
pointBackgroundColor: '#007bff'
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
scales: {
|
||||||
|
yAxes: [{
|
||||||
|
ticks: {
|
||||||
|
beginAtZero: false
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
display: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})()
|
||||||
|
|
||||||
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
33
vrobbler/templates/sports/sportevent_detail.html
Normal file
33
vrobbler/templates/sports/sportevent_detail.html
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{% extends "base_detail.html" %}
|
||||||
|
|
||||||
|
{% block title %}{{object.title}} - {{object.round.season.league}}{% endblock %}
|
||||||
|
|
||||||
|
{% block details %}
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<h2>{{object.tv_series}}</h2>
|
||||||
|
<div class="col-md">
|
||||||
|
<h3>Last scrobbles</h3>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped table-sm">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Date</th>
|
||||||
|
<th scope="col">Season</th>
|
||||||
|
<th scope="col">League</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for scrobble in object.scrobble_set.all %}
|
||||||
|
<tr>
|
||||||
|
<td>{{scrobble.timestamp}}</td>
|
||||||
|
<td>{{scrobble.media_obj.round.season.name}}</td>
|
||||||
|
<td>{{scrobble.media_obj.round.season.league}}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
36
vrobbler/templates/sports/sportevent_list.html
Normal file
36
vrobbler/templates/sports/sportevent_list.html
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{% extends "base_list.html" %}
|
||||||
|
|
||||||
|
{% block title %}Sport events{% endblock %}
|
||||||
|
|
||||||
|
{% block lists %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped table-sm">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Title</th>
|
||||||
|
<th scope="col">Date</th>
|
||||||
|
<th scope="col">Round</th>
|
||||||
|
<th scope="col">League</th>
|
||||||
|
<th scope="col">Scrobbles</th>
|
||||||
|
<th scope="col">All time</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for obj in object_list %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="{{obj.get_absolute_url}}">{{obj.title}}</a></td>
|
||||||
|
<td>{{obj.start}}</td>
|
||||||
|
<td>{{obj.round.name}}</td>
|
||||||
|
<td>{{obj.round.league}}</td>
|
||||||
|
<td>{{obj.scrobble_set.count}}</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@ -1,11 +1,9 @@
|
|||||||
{% extends "base_detail.html" %}
|
{% extends "base_detail.html" %}
|
||||||
|
|
||||||
{% block title %}{{object.name}}{% endblock %}
|
{% block title %}{{object.title}}{% if object.tv_series %} - {{object.tv_series}}{% endif %}{% endblock %}
|
||||||
|
|
||||||
{% block details %}
|
{% block details %}
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<h2>{{object.tv_series}}</h2>
|
|
||||||
<div class="col-md">
|
<div class="col-md">
|
||||||
<h3>Last scrobbles</h3>
|
<h3>Last scrobbles</h3>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
@ -14,9 +12,11 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th scope="col">Date</th>
|
<th scope="col">Date</th>
|
||||||
<th scope="col">Title</th>
|
<th scope="col">Title</th>
|
||||||
|
{% if object.tv_series %}
|
||||||
<th scope="col">Series</th>
|
<th scope="col">Series</th>
|
||||||
<th scope="col">Season</th>
|
<th scope="col">Season</th>
|
||||||
<th scope="col">Episode</th>
|
<th scope="col">Episode</th>
|
||||||
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -24,9 +24,11 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{{scrobble.timestamp}}</td>
|
<td>{{scrobble.timestamp}}</td>
|
||||||
<td>{{scrobble.video.title}}</td>
|
<td>{{scrobble.video.title}}</td>
|
||||||
|
{% if object.tv_series %}
|
||||||
<td>{{scrobble.video.tv_series}}</td>
|
<td>{{scrobble.video.tv_series}}</td>
|
||||||
<td>{{scrobble.video.season_number}}</td>
|
<td>{{scrobble.video.season_number}}</td>
|
||||||
<td>{{scrobble.video.episode_number}}</td>
|
<td>{{scrobble.video.episode_number}}</td>
|
||||||
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -34,4 +36,4 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ from rest_framework import routers
|
|||||||
import vrobbler.apps.scrobbles.views as scrobbles_views
|
import vrobbler.apps.scrobbles.views as scrobbles_views
|
||||||
from vrobbler.apps.books.api.views import AuthorViewSet, BookViewSet
|
from vrobbler.apps.books.api.views import AuthorViewSet, BookViewSet
|
||||||
from vrobbler.apps.music import urls as music_urls
|
from vrobbler.apps.music import urls as music_urls
|
||||||
|
from vrobbler.apps.sports import urls as sports_urls
|
||||||
from vrobbler.apps.music.api.views import (
|
from vrobbler.apps.music.api.views import (
|
||||||
AlbumViewSet,
|
AlbumViewSet,
|
||||||
ArtistViewSet,
|
ArtistViewSet,
|
||||||
@ -57,6 +58,7 @@ urlpatterns = [
|
|||||||
path("accounts/", include("allauth.urls")),
|
path("accounts/", include("allauth.urls")),
|
||||||
path("", include(music_urls, namespace="music")),
|
path("", include(music_urls, namespace="music")),
|
||||||
path("", include(video_urls, namespace="videos")),
|
path("", include(video_urls, namespace="videos")),
|
||||||
|
path("", include(sports_urls, namespace="sports")),
|
||||||
path("", include(scrobble_urls, namespace="scrobbles")),
|
path("", include(scrobble_urls, namespace="scrobbles")),
|
||||||
path(
|
path(
|
||||||
"", scrobbles_views.RecentScrobbleList.as_view(), name="vrobbler-home"
|
"", scrobbles_views.RecentScrobbleList.as_view(), name="vrobbler-home"
|
||||||
|
|||||||
Reference in New Issue
Block a user