Fix chart display
This commit is contained in:
@ -1,6 +1,8 @@
|
|||||||
from django.db.models import Count
|
from datetime import timedelta
|
||||||
|
from django.utils import timezone
|
||||||
from django.views import generic
|
from django.views import generic
|
||||||
from music.models import Track, Artist, Album
|
from music.models import Album, Artist, Track
|
||||||
|
from scrobbles.models import ChartRecord
|
||||||
from scrobbles.stats import get_scrobble_count_qs
|
from scrobbles.stats import get_scrobble_count_qs
|
||||||
|
|
||||||
|
|
||||||
@ -17,6 +19,14 @@ class TrackDetailView(generic.DetailView):
|
|||||||
model = Track
|
model = Track
|
||||||
slug_field = 'uuid'
|
slug_field = 'uuid'
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context_data = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
context_data['charts'] = ChartRecord.objects.filter(
|
||||||
|
track=self.object, rank__in=[1, 2, 3]
|
||||||
|
)
|
||||||
|
return context_data
|
||||||
|
|
||||||
|
|
||||||
class ArtistListView(generic.ListView):
|
class ArtistListView(generic.ListView):
|
||||||
model = Artist
|
model = Artist
|
||||||
@ -29,6 +39,13 @@ class ArtistDetailView(generic.DetailView):
|
|||||||
model = Artist
|
model = Artist
|
||||||
slug_field = 'uuid'
|
slug_field = 'uuid'
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context_data = super().get_context_data(**kwargs)
|
||||||
|
context_data['charts'] = ChartRecord.objects.filter(
|
||||||
|
artist=self.object, rank__in=[1, 2, 3]
|
||||||
|
)
|
||||||
|
return context_data
|
||||||
|
|
||||||
|
|
||||||
class AlbumListView(generic.ListView):
|
class AlbumListView(generic.ListView):
|
||||||
model = Album
|
model = Album
|
||||||
|
|||||||
@ -312,7 +312,22 @@ class ChartRecord(TimeStampedModel):
|
|||||||
return period
|
return period
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"#{self.rank} in {self.period} - {self.media_obj}"
|
title = f"#{self.rank} in {self.period}"
|
||||||
|
if self.day or self.week:
|
||||||
|
title = f"#{self.rank} on {self.period}"
|
||||||
|
return title
|
||||||
|
|
||||||
|
def link(self):
|
||||||
|
get_params = f"?date={self.year}"
|
||||||
|
if self.week:
|
||||||
|
get_params = get_params = get_params + f"-W{self.week}"
|
||||||
|
if self.month:
|
||||||
|
get_params = get_params = get_params + f"-{self.month}"
|
||||||
|
if self.day:
|
||||||
|
get_params = get_params = get_params + f"-{self.day}"
|
||||||
|
if self.artist:
|
||||||
|
get_params = get_params + "&media=Artist"
|
||||||
|
return reverse('scrobbles:charts-home') + get_params
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def build(cls, user, **kwargs):
|
def build(cls, user, **kwargs):
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import calendar
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from django.db.models.query import QuerySet
|
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -10,6 +9,7 @@ from django.contrib import messages
|
|||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.db.models.fields import timezone
|
from django.db.models.fields import timezone
|
||||||
|
from django.db.models.query import QuerySet
|
||||||
from django.http import FileResponse, HttpResponseRedirect, JsonResponse
|
from django.http import FileResponse, HttpResponseRedirect, JsonResponse
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
@ -17,12 +17,7 @@ from django.views.decorators.csrf import csrf_exempt
|
|||||||
from django.views.generic import DetailView, FormView, TemplateView
|
from django.views.generic import DetailView, FormView, TemplateView
|
||||||
from django.views.generic.edit import CreateView
|
from django.views.generic.edit import CreateView
|
||||||
from django.views.generic.list import ListView
|
from django.views.generic.list import ListView
|
||||||
from music.aggregators import (
|
from music.aggregators import scrobble_counts, week_of_scrobbles
|
||||||
scrobble_counts,
|
|
||||||
top_artists,
|
|
||||||
top_tracks,
|
|
||||||
week_of_scrobbles,
|
|
||||||
)
|
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.decorators import (
|
from rest_framework.decorators import (
|
||||||
api_view,
|
api_view,
|
||||||
@ -62,6 +57,8 @@ from scrobbles.tasks import (
|
|||||||
)
|
)
|
||||||
from scrobbles.thesportsdb import lookup_event_from_thesportsdb
|
from scrobbles.thesportsdb import lookup_event_from_thesportsdb
|
||||||
|
|
||||||
|
from vrobbler.apps.music.aggregators import live_charts
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@ -406,19 +403,17 @@ class ChartRecordView(TemplateView):
|
|||||||
template_name = 'scrobbles/chart_index.html'
|
template_name = 'scrobbles/chart_index.html'
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_media_filter(media_type: str = "Track"):
|
def get_media_filter(media_type: str = "") -> Q:
|
||||||
media_filter = Q()
|
filters = {
|
||||||
if media_type == 'Track':
|
"Track": Q(track__isnull=False),
|
||||||
media_filter = Q(track__isnull=False)
|
"Artist": Q(artist__isnull=False),
|
||||||
if media_type == 'Artist':
|
"Series": Q(series__isnull=False),
|
||||||
media_filter = Q(artist__isnull=False)
|
"Video": Q(video__isnull=False),
|
||||||
if media_type == 'Series':
|
"": Q(),
|
||||||
media_filter = Q(series__isnull=False)
|
}
|
||||||
if media_type == 'Video':
|
return filters[media_type]
|
||||||
media_filter = Q(video__isnull=False)
|
|
||||||
return media_filter
|
|
||||||
|
|
||||||
def get_chart_records(self, media_type: str = "Track", **kwargs):
|
def get_chart_records(self, media_type: str = "", **kwargs):
|
||||||
media_filter = self.get_media_filter(media_type)
|
media_filter = self.get_media_filter(media_type)
|
||||||
charts = ChartRecord.objects.filter(
|
charts = ChartRecord.objects.filter(
|
||||||
media_filter, user=self.request.user, **kwargs
|
media_filter, user=self.request.user, **kwargs
|
||||||
@ -434,37 +429,24 @@ class ChartRecordView(TemplateView):
|
|||||||
return charts
|
return charts
|
||||||
|
|
||||||
def get_chart(
|
def get_chart(
|
||||||
self, period: str = "all_time", limit=15, media: str = "Track"
|
self, period: str = "all_time", limit=15, media: str = ""
|
||||||
) -> QuerySet:
|
) -> QuerySet:
|
||||||
chart = QuerySet()
|
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
if period == "all_time":
|
params = {}
|
||||||
chart = self.get_chart_records(media_type=media)
|
params['media_type'] = media
|
||||||
if period == "today":
|
if period == "today":
|
||||||
chart = self.get_chart_records(
|
params['day'] = now.day
|
||||||
media_type=media,
|
params['month'] = now.month
|
||||||
day=now.day,
|
params['year'] = now.year
|
||||||
month=now.month,
|
|
||||||
year=now.year,
|
|
||||||
)
|
|
||||||
if period == "week":
|
if period == "week":
|
||||||
chart = self.get_chart_records(
|
params['week'] = now.ioscalendar()[1]
|
||||||
media_type=media,
|
params['year'] = now.year
|
||||||
year=now.year,
|
|
||||||
week=now.isocalendar()[1],
|
|
||||||
)
|
|
||||||
if period == "month":
|
if period == "month":
|
||||||
chart = self.get_chart_records(
|
params['month'] = now.month
|
||||||
media_type=media,
|
params['year'] = now.year
|
||||||
year=now.year,
|
|
||||||
month=now.month,
|
|
||||||
)
|
|
||||||
if period == "year":
|
if period == "year":
|
||||||
chart = self.get_chart_records(
|
params['year'] = now.year
|
||||||
media_type=media,
|
return self.get_chart_records(**params)[:limit]
|
||||||
year=now.year,
|
|
||||||
)
|
|
||||||
return chart[:limit]
|
|
||||||
|
|
||||||
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)
|
||||||
@ -475,21 +457,24 @@ class ChartRecordView(TemplateView):
|
|||||||
context_data["artist_charts"] = {}
|
context_data["artist_charts"] = {}
|
||||||
|
|
||||||
if not date:
|
if not date:
|
||||||
|
artist_params = {'user': user, 'media_type': 'Artist'}
|
||||||
context_data['artist_charts'] = {
|
context_data['artist_charts'] = {
|
||||||
"today": top_artists(user, filter="today")[:30],
|
"today": live_charts(**artist_params, chart_period="today"),
|
||||||
"week": top_artists(user, filter="week")[:30],
|
"week": live_charts(**artist_params, chart_period="week"),
|
||||||
"month": top_artists(user, filter="month")[:30],
|
"month": live_charts(**artist_params, chart_period="month"),
|
||||||
"all": top_artists(user),
|
"all": live_charts(**artist_params),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
track_params = {'user': user, 'media_type': 'Track'}
|
||||||
context_data['track_charts'] = {
|
context_data['track_charts'] = {
|
||||||
"today": top_tracks(user, filter="today")[:30],
|
"today": live_charts(**track_params, chart_period="today"),
|
||||||
"week": top_tracks(user, filter="week")[:30],
|
"week": live_charts(**track_params, chart_period="week"),
|
||||||
"month": top_tracks(user, filter="month")[:30],
|
"month": live_charts(**track_params, chart_period="month"),
|
||||||
"all": top_tracks(user),
|
"all": live_charts(**track_params),
|
||||||
}
|
}
|
||||||
return context_data
|
return context_data
|
||||||
|
|
||||||
|
# Date provided, lookup past charts, returning nothing if it's now or in the future.
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
year = now.year
|
year = now.year
|
||||||
params = {'year': year}
|
params = {'year': year}
|
||||||
@ -531,7 +516,7 @@ class ChartRecordView(TemplateView):
|
|||||||
media_filter, user=self.request.user, **params
|
media_filter, user=self.request.user, **params
|
||||||
).order_by("rank")
|
).order_by("rank")
|
||||||
|
|
||||||
if charts.count() == 0:
|
if charts.count() == 0 and not in_progress:
|
||||||
ChartRecord.build(
|
ChartRecord.build(
|
||||||
user=self.request.user, model_str=media_type, **params
|
user=self.request.user, model_str=media_type, **params
|
||||||
)
|
)
|
||||||
@ -539,11 +524,8 @@ class ChartRecordView(TemplateView):
|
|||||||
media_filter, user=self.request.user, **params
|
media_filter, user=self.request.user, **params
|
||||||
).order_by("rank")
|
).order_by("rank")
|
||||||
|
|
||||||
if in_progress:
|
context_data['media_type'] = media_type
|
||||||
# TODO recalculate
|
|
||||||
...
|
|
||||||
|
|
||||||
context_data['charts'] = charts
|
context_data['charts'] = charts
|
||||||
context_data['name'] = name
|
context_data['name'] = " ".join(["Top", media_type, "for", name])
|
||||||
context_data['in_progress'] = in_progress
|
context_data['in_progress'] = in_progress
|
||||||
return context_data
|
return context_data
|
||||||
|
|||||||
@ -14,6 +14,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<p>{{artist.scrobbles.count}} scrobbles</p>
|
<p>{{artist.scrobbles.count}} scrobbles</p>
|
||||||
|
{% if charts %}
|
||||||
|
<p>{% for chart in charts %}<em><a href="{{chart.link}}">{{chart}}</a></em>{% if forloop.last %}{% else %} | {% endif %}{% endfor %}</p>
|
||||||
|
{% endif %}
|
||||||
<div class="col-md">
|
<div class="col-md">
|
||||||
<h3>Top tracks</h3>
|
<h3>Top tracks</h3>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
|
|||||||
@ -1,13 +1,41 @@
|
|||||||
{% extends "base_detail.html" %}
|
{% extends "base_list.html" %}
|
||||||
|
|
||||||
{% block title %}{{object.title}}{% endblock %}
|
{% block title %}{{object.title}}{% endblock %}
|
||||||
|
|
||||||
{% block details %}
|
{% block lists %}
|
||||||
<h2>Last scrobbles</h2>
|
<div class="row">
|
||||||
{% for scrobble in object.scrobble_set.all %}
|
{% if track.album.cover_image %}
|
||||||
<ul>
|
<p style="width:150px; float:left;"><img src="{{track.album.cover_image.url}}" width=150 height=150 /></p>
|
||||||
<li>{{scrobble.timestamp|date:"d M Y h:m"}} - <img src="{{object.album.cover_image.url}}" width=25 height=25 /> - {{object}}</li>
|
{% endif %}
|
||||||
</ul>
|
</div>
|
||||||
{% endfor %}
|
<div class="row">
|
||||||
|
<p>{{object.scrobble_set.count}} scrobbles</p>
|
||||||
|
{% if charts %}
|
||||||
|
<p>{% for chart in charts %}<em><a href="{{chart.link}}">{{chart}}</a></em>{% if forloop.last %}{% else %} | {% endif %}{% endfor %}</p>
|
||||||
|
{% endif %}
|
||||||
|
<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">Track</th>
|
||||||
|
<th scope="col">Album</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for scrobble in object.scrobble_set.all %}
|
||||||
|
<tr>
|
||||||
|
<td>{{scrobble.timestamp}}</td>
|
||||||
|
<td>{{scrobble.track.title}}</td>
|
||||||
|
<td>{{scrobble.track.album.name}}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -5,6 +5,30 @@
|
|||||||
{% block lists %}
|
{% block lists %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{% if charts %}
|
{% if charts %}
|
||||||
|
{% if "Artist" in name %}
|
||||||
|
<div class="tab-content" id="artistTabContent">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped table-sm">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Rank</th>
|
||||||
|
<th scope="col">Artist</th>
|
||||||
|
<th scope="col">Scrobbles</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for chart in charts %}
|
||||||
|
<tr>
|
||||||
|
<td>{{chart.rank}}</td>
|
||||||
|
<td><a href="{{chart.media_obj.get_absolute_url}}">{{chart.media_obj}}</a></td>
|
||||||
|
<td>{{chart.count}}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% elif media_type == "Track" %}
|
||||||
<div class="tab-content" id="artistTabContent">
|
<div class="tab-content" id="artistTabContent">
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-striped table-sm">
|
<table class="table table-striped table-sm">
|
||||||
@ -29,93 +53,96 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% elif "Video" in name %}
|
||||||
|
{% elif "Series" in name %}
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if artist_charts %}
|
{% if artist_charts %}
|
||||||
<h2>Top Artists</h2>
|
<div class="col-md">
|
||||||
|
<h2>Top Artists</h2>
|
||||||
|
|
||||||
<ul class="nav nav-tabs" id="artistTab" role="tablist">
|
<ul class="nav nav-tabs" id="artistTab" role="tablist">
|
||||||
{% for chart_name in artist_charts.keys %}
|
{% for chart_name in artist_charts.keys %}
|
||||||
<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}}
|
{{chart_name}}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="tab-content" id="artistTabContent">
|
<div class="tab-content" id="artistTabContent">
|
||||||
{% for chart_name, artists in artist_charts.items %}
|
{% for chart_name, artists in artist_charts.items %}
|
||||||
<div class="tab-pane fade {% if forloop.first %}show active{% endif %}" id="artist-{{chart_name}}" role="tabpanel"
|
<div class="tab-pane fade {% if forloop.first %}show active{% endif %}" id="artist-{{chart_name}}" role="tabpanel"
|
||||||
aria-labelledby="artist-{[chart}}-tab">
|
aria-labelledby="artist-{[chart}}-tab">
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<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">Artist</th>
|
||||||
<th scope="col">Scrobbles</th>
|
<th scope="col">Scrobbles</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for artist in artists %}
|
{% for artist in artists %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="{{artist.get_absolute_url}}">{{artist}}</a></td>
|
<td><a href="{{artist.get_absolute_url}}">{{artist}}</a></td>
|
||||||
<td>{{artist.num_scrobbles}}</td>
|
<td>{{artist.num_scrobbles}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
{% if track_charts %}
|
{% if track_charts %}
|
||||||
<h2>Top Tracks</h2>
|
<div class="col-md">
|
||||||
|
<h2>Top Tracks</h2>
|
||||||
|
|
||||||
<ul class="nav nav-tabs" id="artistTab" role="tablist">
|
<ul class="nav nav-tabs" id="artistTab" role="tablist">
|
||||||
{% for chart_name in track_charts.keys %}
|
{% for chart_name in track_charts.keys %}
|
||||||
<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}}
|
{{chart_name}}
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="tab-content" id="trackTabContent">
|
<div class="tab-content" id="trackTabContent">
|
||||||
{% for chart_name, tracks in track_charts.items %}
|
{% for chart_name, tracks in track_charts.items %}
|
||||||
<div class="tab-pane fade {% if forloop.first %}show active{% endif %}" id="track-{{chart_name}}" role="tabpanel"
|
<div class="tab-pane fade {% if forloop.first %}show active{% endif %}" id="track-{{chart_name}}" role="tabpanel"
|
||||||
aria-labelledby="track-{[chart_name}}-tab">
|
aria-labelledby="track-{[chart_name}}-tab">
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-striped table-sm">
|
<table class="table table-striped table-sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">Rank</th>
|
<th scope="col">Track</th>
|
||||||
<th scope="col">Artist</th>
|
<th scope="col">Artist</th>
|
||||||
<th scope="col">Track</th>
|
<th scope="col">Scrobbles</th>
|
||||||
<th scope="col">Scrobbles</th>
|
</tr>
|
||||||
</tr>
|
</thead>
|
||||||
</thead>
|
<tbody>
|
||||||
<tbody>
|
{% for track in tracks %}
|
||||||
{% for track in tracks %}
|
<tr>
|
||||||
<tr>
|
<td><a href="{{track.get_absolute_url}}">{{track.title}}</a></td>
|
||||||
<td>{{track.rank}}</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.num_scrobbles}}</td>
|
||||||
<td><a href="{{track.get_absolute_url}}">{{track.title}}</a></td>
|
</tr>
|
||||||
<td>{{track.num_scrobbles}}</td>
|
{% endfor %}
|
||||||
</tr>
|
</tbody>
|
||||||
{% endfor %}
|
</table>
|
||||||
</tbody>
|
</div>
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user