[charts] Add youtube aggregator
This commit is contained in:
@ -16,9 +16,7 @@ def scrobble_counts(user=None):
|
|||||||
now = now_user_timezone(user.profile)
|
now = now_user_timezone(user.profile)
|
||||||
user_filter = Q(user=user)
|
user_filter = Q(user=user)
|
||||||
|
|
||||||
start_of_today = datetime.combine(
|
start_of_today = datetime.combine(now.date(), datetime.min.time(), now.tzinfo)
|
||||||
now.date(), datetime.min.time(), now.tzinfo
|
|
||||||
)
|
|
||||||
starting_day_of_current_week = now.date() - timedelta(
|
starting_day_of_current_week = now.date() - timedelta(
|
||||||
days=now.today().isoweekday() % 7
|
days=now.today().isoweekday() % 7
|
||||||
)
|
)
|
||||||
@ -31,9 +29,7 @@ def scrobble_counts(user=None):
|
|||||||
media_type=Scrobble.MediaType.TRACK,
|
media_type=Scrobble.MediaType.TRACK,
|
||||||
)
|
)
|
||||||
data = {}
|
data = {}
|
||||||
data["today"] = finished_scrobbles_qs.filter(
|
data["today"] = finished_scrobbles_qs.filter(timestamp__gte=start_of_today).count()
|
||||||
timestamp__gte=start_of_today
|
|
||||||
).count()
|
|
||||||
data["week"] = finished_scrobbles_qs.filter(
|
data["week"] = finished_scrobbles_qs.filter(
|
||||||
timestamp__gte=starting_day_of_current_week
|
timestamp__gte=starting_day_of_current_week
|
||||||
).count()
|
).count()
|
||||||
@ -47,9 +43,7 @@ def scrobble_counts(user=None):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
def week_of_scrobbles(
|
def week_of_scrobbles(user=None, start=None, media: str = "tracks") -> dict[str, int]:
|
||||||
user=None, start=None, media: str = "tracks"
|
|
||||||
) -> dict[str, int]:
|
|
||||||
|
|
||||||
now = timezone.now()
|
now = timezone.now()
|
||||||
user_filter = Q()
|
user_filter = Q()
|
||||||
@ -101,9 +95,7 @@ def live_charts(
|
|||||||
seven_days_ago = now - timedelta(days=7)
|
seven_days_ago = now - timedelta(days=7)
|
||||||
thirty_days_ago = now - timedelta(days=30)
|
thirty_days_ago = now - timedelta(days=30)
|
||||||
start_of_today = datetime.combine(now, datetime.min.time(), tzinfo)
|
start_of_today = datetime.combine(now, datetime.min.time(), tzinfo)
|
||||||
start_day_of_week = start_of_today - timedelta(
|
start_day_of_week = start_of_today - timedelta(days=now.today().isoweekday() % 7)
|
||||||
days=now.today().isoweekday() % 7
|
|
||||||
)
|
|
||||||
start_day_of_month = now.replace(day=1)
|
start_day_of_month = now.replace(day=1)
|
||||||
start_day_of_year = now.replace(month=1, day=1)
|
start_day_of_year = now.replace(month=1, day=1)
|
||||||
|
|
||||||
@ -120,17 +112,13 @@ def live_charts(
|
|||||||
}
|
}
|
||||||
|
|
||||||
time_filter = Q()
|
time_filter = Q()
|
||||||
completion_filter = Q(
|
completion_filter = Q(scrobble__user=user, scrobble__played_to_completion=True)
|
||||||
scrobble__user=user, scrobble__played_to_completion=True
|
|
||||||
)
|
|
||||||
user_filter = Q(scrobble__user=user)
|
user_filter = Q(scrobble__user=user)
|
||||||
count_field = "scrobble"
|
count_field = "scrobble"
|
||||||
|
|
||||||
if media_type == "Artist":
|
if media_type == "Artist":
|
||||||
for period, query_dict in period_queries.items():
|
for period, query_dict in period_queries.items():
|
||||||
period_queries[period] = {
|
period_queries[period] = {"track__" + k: v for k, v in query_dict.items()}
|
||||||
"track__" + k: v for k, v in query_dict.items()
|
|
||||||
}
|
|
||||||
completion_filter = Q(
|
completion_filter = Q(
|
||||||
track__scrobble__user=user,
|
track__scrobble__user=user,
|
||||||
track__scrobble__played_to_completion=True,
|
track__scrobble__played_to_completion=True,
|
||||||
@ -175,9 +163,7 @@ def live_tv_charts(
|
|||||||
seven_days_ago = now - timedelta(days=7)
|
seven_days_ago = now - timedelta(days=7)
|
||||||
thirty_days_ago = now - timedelta(days=30)
|
thirty_days_ago = now - timedelta(days=30)
|
||||||
start_of_today = datetime.combine(now, datetime.min.time(), tzinfo)
|
start_of_today = datetime.combine(now, datetime.min.time(), tzinfo)
|
||||||
start_day_of_week = start_of_today - timedelta(
|
start_day_of_week = start_of_today - timedelta(days=now.today().isoweekday() % 7)
|
||||||
days=now.today().isoweekday() % 7
|
|
||||||
)
|
|
||||||
start_day_of_month = now.replace(day=1)
|
start_day_of_month = now.replace(day=1)
|
||||||
start_day_of_year = now.replace(month=1, day=1)
|
start_day_of_year = now.replace(month=1, day=1)
|
||||||
|
|
||||||
@ -214,3 +200,56 @@ def live_tv_charts(
|
|||||||
.filter(num_scrobbles__gt=0)
|
.filter(num_scrobbles__gt=0)
|
||||||
.order_by("-num_scrobbles")[:limit]
|
.order_by("-num_scrobbles")[:limit]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def live_youtube_channel_charts(
|
||||||
|
user: "User",
|
||||||
|
chart_period: str = "all",
|
||||||
|
limit: int = 15,
|
||||||
|
) -> QuerySet:
|
||||||
|
from django.db.models import OuterRef, Subquery
|
||||||
|
from videos.models import Video, Channel
|
||||||
|
|
||||||
|
now = timezone.now()
|
||||||
|
tzinfo = now.tzinfo
|
||||||
|
now = now_user_timezone(user.profile)
|
||||||
|
tzinfo = now.tzinfo
|
||||||
|
|
||||||
|
seven_days_ago = now - timedelta(days=7)
|
||||||
|
thirty_days_ago = now - timedelta(days=30)
|
||||||
|
start_of_today = datetime.combine(now, datetime.min.time(), tzinfo)
|
||||||
|
start_day_of_week = start_of_today - timedelta(days=now.today().isoweekday() % 7)
|
||||||
|
start_day_of_year = now.replace(month=1, day=1)
|
||||||
|
|
||||||
|
period_queries = {
|
||||||
|
"today": Q(timestamp__gte=start_of_today),
|
||||||
|
"week": Q(timestamp__gte=start_day_of_week),
|
||||||
|
"last7": Q(timestamp__gte=seven_days_ago),
|
||||||
|
"last30": Q(timestamp__gte=thirty_days_ago),
|
||||||
|
"year": Q(timestamp__gte=start_day_of_year),
|
||||||
|
"all": Q(),
|
||||||
|
}
|
||||||
|
|
||||||
|
time_filter = period_queries[chart_period]
|
||||||
|
completion_filter = Q(
|
||||||
|
user=user,
|
||||||
|
played_to_completion=True,
|
||||||
|
video__video_type=Video.VideoType.YOUTUBE,
|
||||||
|
)
|
||||||
|
|
||||||
|
scrobble_counts = (
|
||||||
|
Scrobble.objects.filter(
|
||||||
|
completion_filter,
|
||||||
|
time_filter,
|
||||||
|
video__channel=OuterRef("pk"),
|
||||||
|
)
|
||||||
|
.values("video__channel")
|
||||||
|
.annotate(c=Count("id"))
|
||||||
|
.values("c")
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
Channel.objects.annotate(num_scrobbles=Subquery(scrobble_counts))
|
||||||
|
.filter(num_scrobbles__gt=0)
|
||||||
|
.order_by("-num_scrobbles")[:limit]
|
||||||
|
)
|
||||||
|
|||||||
@ -22,10 +22,10 @@ from django.views.generic.edit import CreateView
|
|||||||
from django.views.generic.list import ListView
|
from django.views.generic.list import ListView
|
||||||
from moods.models import Mood
|
from moods.models import Mood
|
||||||
from music.aggregators import (
|
from music.aggregators import (
|
||||||
|
artist_scrobble_count,
|
||||||
live_charts,
|
live_charts,
|
||||||
live_tv_charts,
|
live_tv_charts,
|
||||||
scrobble_counts,
|
live_youtube_channel_charts,
|
||||||
week_of_scrobbles,
|
|
||||||
)
|
)
|
||||||
from pendulum.parsing.exceptions import ParserError
|
from pendulum.parsing.exceptions import ParserError
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
@ -161,25 +161,17 @@ class RecentScrobbleList(ListView):
|
|||||||
next_date = date + timedelta(weeks=+2)
|
next_date = date + timedelta(weeks=+2)
|
||||||
prev_date = date + timedelta(weeks=-1)
|
prev_date = date + timedelta(weeks=-1)
|
||||||
if date.isocalendar()[1] < today.isocalendar()[1]:
|
if date.isocalendar()[1] < today.isocalendar()[1]:
|
||||||
data[
|
data["next_link"] = f"?date={next_date.strftime('%Y-W%W')}"
|
||||||
"next_link"
|
|
||||||
] = f"?date={next_date.strftime('%Y-W%W')}"
|
|
||||||
data["prev_link"] = f"?date={prev_date.strftime('%Y-W%W')}"
|
data["prev_link"] = f"?date={prev_date.strftime('%Y-W%W')}"
|
||||||
data[
|
data["title"] = f"Week {date.strftime('%-W')} of {date.year}"
|
||||||
"title"
|
|
||||||
] = f"Week {date.strftime('%-W')} of {date.year}"
|
|
||||||
data = data | Scrobble.as_dict_by_type(
|
data = data | Scrobble.as_dict_by_type(
|
||||||
Scrobble.for_week(
|
Scrobble.for_week(user_id, date.year, date.isocalendar()[1])
|
||||||
user_id, date.year, date.isocalendar()[1]
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
elif date_str == "this_month" or date_str.count("-") == 1:
|
elif date_str == "this_month" or date_str.count("-") == 1:
|
||||||
next_date = date + relativedelta(months=1)
|
next_date = date + relativedelta(months=1)
|
||||||
prev_date = date + relativedelta(months=-1)
|
prev_date = date + relativedelta(months=-1)
|
||||||
if date.month < today.month:
|
if date.month < today.month:
|
||||||
data[
|
data["next_link"] = f"?date={next_date.strftime('%Y-%m')}"
|
||||||
"next_link"
|
|
||||||
] = f"?date={next_date.strftime('%Y-%m')}"
|
|
||||||
data["title"] = f"{date.strftime('%B %Y')}"
|
data["title"] = f"{date.strftime('%B %Y')}"
|
||||||
data["prev_link"] = f"?date={prev_date.strftime('%Y-%m')}"
|
data["prev_link"] = f"?date={prev_date.strftime('%Y-%m')}"
|
||||||
data = data | Scrobble.as_dict_by_type(
|
data = data | Scrobble.as_dict_by_type(
|
||||||
@ -188,24 +180,16 @@ class RecentScrobbleList(ListView):
|
|||||||
elif date_str == "today" or date_str.count("-") == 2:
|
elif date_str == "today" or date_str.count("-") == 2:
|
||||||
next_date = date + timedelta(days=1)
|
next_date = date + timedelta(days=1)
|
||||||
prev_date = date - timedelta(days=1)
|
prev_date = date - timedelta(days=1)
|
||||||
data[
|
data["prev_link"] = f"?date={prev_date.strftime('%Y-%m-%d')}"
|
||||||
"prev_link"
|
|
||||||
] = f"?date={prev_date.strftime('%Y-%m-%d')}"
|
|
||||||
if date < today:
|
if date < today:
|
||||||
data[
|
data["next_link"] = f"?date={next_date.strftime('%Y-%m-%d')}"
|
||||||
"next_link"
|
|
||||||
] = f"?date={next_date.strftime('%Y-%m-%d')}"
|
|
||||||
if date == today:
|
if date == today:
|
||||||
data["title"] = "Today"
|
data["title"] = "Today"
|
||||||
else:
|
else:
|
||||||
data["title"] = f"{date.strftime('%Y-%m-%d')}"
|
data["title"] = f"{date.strftime('%Y-%m-%d')}"
|
||||||
data[
|
data["today_link"] = f"?date={today.strftime('%Y-%m-%d')}"
|
||||||
"today_link"
|
|
||||||
] = f"?date={today.strftime('%Y-%m-%d')}"
|
|
||||||
data = data | Scrobble.as_dict_by_type(
|
data = data | Scrobble.as_dict_by_type(
|
||||||
Scrobble.for_day(
|
Scrobble.for_day(user_id, date.year, date.month, date.day)
|
||||||
user_id, date.year, date.month, date.day
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
elif date_str == "this_year" or date_str.count("-") == 0:
|
elif date_str == "this_year" or date_str.count("-") == 0:
|
||||||
next_date = date + relativedelta(years=+1)
|
next_date = date + relativedelta(years=+1)
|
||||||
@ -228,9 +212,7 @@ class RecentScrobbleList(ListView):
|
|||||||
data = data | Scrobble.as_dict_by_type(
|
data = data | Scrobble.as_dict_by_type(
|
||||||
Scrobble.for_day(user_id, date.year, date.month, date.day)
|
Scrobble.for_day(user_id, date.year, date.month, date.day)
|
||||||
)
|
)
|
||||||
data[
|
data["today_link"] = "" # f"?date={today.strftime('%Y-%m-%d')}"
|
||||||
"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,
|
||||||
@ -259,9 +241,7 @@ class ScrobbleLongPlaysView(TemplateView):
|
|||||||
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["view"] = self.request.GET.get("view", "grid")
|
context_data["view"] = self.request.GET.get("view", "grid")
|
||||||
context_data["in_progress"] = get_long_plays_in_progress(
|
context_data["in_progress"] = get_long_plays_in_progress(self.request.user)
|
||||||
self.request.user
|
|
||||||
)
|
|
||||||
context_data["completed"] = get_long_plays_completed(self.request.user)
|
context_data["completed"] = get_long_plays_completed(self.request.user)
|
||||||
return context_data
|
return context_data
|
||||||
|
|
||||||
@ -338,9 +318,7 @@ class ManualScrobbleView(FormView):
|
|||||||
scrobble_fn = MANUAL_SCROBBLE_FNS[key]
|
scrobble_fn = MANUAL_SCROBBLE_FNS[key]
|
||||||
scrobble = eval(scrobble_fn)(item_id, self.request.user.id)
|
scrobble = eval(scrobble_fn)(item_id, self.request.user.id)
|
||||||
|
|
||||||
return HttpResponseRedirect(
|
return HttpResponseRedirect(scrobble.redirect_url(self.request.user.id))
|
||||||
scrobble.redirect_url(self.request.user.id)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class JsonableResponseMixin:
|
class JsonableResponseMixin:
|
||||||
@ -386,9 +364,7 @@ class AudioScrobblerImportCreateView(
|
|||||||
return HttpResponseRedirect(self.request.META.get("HTTP_REFERER"))
|
return HttpResponseRedirect(self.request.META.get("HTTP_REFERER"))
|
||||||
|
|
||||||
|
|
||||||
class KoReaderImportCreateView(
|
class KoReaderImportCreateView(LoginRequiredMixin, JsonableResponseMixin, CreateView):
|
||||||
LoginRequiredMixin, JsonableResponseMixin, CreateView
|
|
||||||
):
|
|
||||||
model = KoReaderImport
|
model = KoReaderImport
|
||||||
fields = ["sqlite_file"]
|
fields = ["sqlite_file"]
|
||||||
template_name = "scrobbles/upload_form.html"
|
template_name = "scrobbles/upload_form.html"
|
||||||
@ -447,9 +423,7 @@ def web_scrobbler_webhook(request):
|
|||||||
playing_state = "started"
|
playing_state = "started"
|
||||||
if request.POST.get("isPlaying") == "false":
|
if request.POST.get("isPlaying") == "false":
|
||||||
playing_state = "stopped"
|
playing_state = "stopped"
|
||||||
scrobble = web_scrobbler_scrobble_media(
|
scrobble = web_scrobbler_scrobble_media(youtube_id, user_id, status=playing_state)
|
||||||
youtube_id, user_id, status=playing_state
|
|
||||||
)
|
|
||||||
if not scrobble:
|
if not scrobble:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"[web_scrobbler_webhook] failed",
|
"[web_scrobbler_webhook] failed",
|
||||||
@ -467,9 +441,7 @@ def web_scrobbler_webhook(request):
|
|||||||
"[web_scrobbler_webhook] finished",
|
"[web_scrobbler_webhook] finished",
|
||||||
extra={"scrobble_id": scrobble.id},
|
extra={"scrobble_id": scrobble.id},
|
||||||
)
|
)
|
||||||
return JsonResponse(
|
return JsonResponse({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK)
|
||||||
{"scrobble_id": scrobble.id}, status=status.HTTP_200_OK
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
@ -574,18 +546,12 @@ def import_audioscrobbler_file(request):
|
|||||||
scrobbles_created = []
|
scrobbles_created = []
|
||||||
# tsv_file = request.FILES[0]
|
# tsv_file = request.FILES[0]
|
||||||
|
|
||||||
file_serializer = serializers.AudioScrobblerTSVImportSerializer(
|
file_serializer = serializers.AudioScrobblerTSVImportSerializer(data=request.data)
|
||||||
data=request.data
|
|
||||||
)
|
|
||||||
if file_serializer.is_valid():
|
if file_serializer.is_valid():
|
||||||
import_file = file_serializer.save()
|
import_file = file_serializer.save()
|
||||||
return Response(
|
return Response({"scrobble_ids": scrobbles_created}, status=status.HTTP_200_OK)
|
||||||
{"scrobble_ids": scrobbles_created}, status=status.HTTP_200_OK
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
return Response(
|
return Response(file_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
file_serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@permission_classes([IsAuthenticated])
|
@permission_classes([IsAuthenticated])
|
||||||
@ -762,9 +728,7 @@ class ChartRecordView(TemplateView):
|
|||||||
).order_by("rank")
|
).order_by("rank")
|
||||||
|
|
||||||
if charts.count() == 0:
|
if charts.count() == 0:
|
||||||
ChartRecord.build(
|
ChartRecord.build(user=self.request.user, model_str=media_type, **kwargs)
|
||||||
user=self.request.user, model_str=media_type, **kwargs
|
|
||||||
)
|
|
||||||
charts = ChartRecord.objects.filter(
|
charts = ChartRecord.objects.filter(
|
||||||
media_filter, user=self.request.user, **kwargs
|
media_filter, user=self.request.user, **kwargs
|
||||||
).order_by("rank")
|
).order_by("rank")
|
||||||
@ -796,9 +760,7 @@ 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(
|
context_data["chart_type"] = self.request.GET.get("chart_type", "maloja")
|
||||||
"chart_type", "maloja"
|
|
||||||
)
|
|
||||||
context_data["artist_charts"] = {}
|
context_data["artist_charts"] = {}
|
||||||
|
|
||||||
if not date:
|
if not date:
|
||||||
@ -836,9 +798,7 @@ class ChartRecordView(TemplateView):
|
|||||||
tzinfo = now.tzinfo
|
tzinfo = now.tzinfo
|
||||||
now = now.date()
|
now = now.date()
|
||||||
start_of_today = datetime.combine(now, datetime.min.time(), tzinfo)
|
start_of_today = datetime.combine(now, datetime.min.time(), tzinfo)
|
||||||
start_day_of_week = start_of_today - timedelta(
|
start_day_of_week = start_of_today - timedelta(days=now.isoweekday() % 7)
|
||||||
days=now.isoweekday() % 7
|
|
||||||
)
|
|
||||||
start_day_of_month = now.replace(day=1)
|
start_day_of_month = now.replace(day=1)
|
||||||
|
|
||||||
# TV Series Scrobbles
|
# TV Series Scrobbles
|
||||||
@ -883,13 +843,27 @@ class ChartRecordView(TemplateView):
|
|||||||
context_data["tv_show_charts"] = {
|
context_data["tv_show_charts"] = {
|
||||||
"today": list(live_tv_charts(user=user, chart_period="today")),
|
"today": list(live_tv_charts(user=user, chart_period="today")),
|
||||||
"last7": list(live_tv_charts(user=user, chart_period="last7")),
|
"last7": list(live_tv_charts(user=user, chart_period="last7")),
|
||||||
"last30": list(
|
"last30": list(live_tv_charts(user=user, chart_period="last30")),
|
||||||
live_tv_charts(user=user, chart_period="last30")
|
|
||||||
),
|
|
||||||
"year": list(live_tv_charts(user=user, chart_period="year")),
|
"year": list(live_tv_charts(user=user, chart_period="year")),
|
||||||
"all": list(live_tv_charts(user=user, chart_period="all")),
|
"all": list(live_tv_charts(user=user, chart_period="all")),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context_data["youtube_channel_charts"] = {
|
||||||
|
"today": list(
|
||||||
|
live_youtube_channel_charts(user=user, chart_period="today")
|
||||||
|
),
|
||||||
|
"last7": list(
|
||||||
|
live_youtube_channel_charts(user=user, chart_period="last7")
|
||||||
|
),
|
||||||
|
"last30": list(
|
||||||
|
live_youtube_channel_charts(user=user, chart_period="last30")
|
||||||
|
),
|
||||||
|
"year": list(
|
||||||
|
live_youtube_channel_charts(user=user, chart_period="year")
|
||||||
|
),
|
||||||
|
"all": list(live_youtube_channel_charts(user=user, chart_period="all")),
|
||||||
|
}
|
||||||
|
|
||||||
return context_data
|
return context_data
|
||||||
|
|
||||||
# Date provided, lookup past charts, returning nothing if it's now or in the future.
|
# Date provided, lookup past charts, returning nothing if it's now or in the future.
|
||||||
@ -925,9 +899,7 @@ class ChartRecordView(TemplateView):
|
|||||||
params["day"] = day
|
params["day"] = day
|
||||||
month_str = calendar.month_name[month]
|
month_str = calendar.month_name[month]
|
||||||
name = f"Chart for {month_str} {day}, {year}"
|
name = f"Chart for {month_str} {day}, {year}"
|
||||||
in_progress = (
|
in_progress = now.month == month and now.year == year and now.day == day
|
||||||
now.month == month and now.year == year and now.day == day
|
|
||||||
)
|
|
||||||
|
|
||||||
media_filter = self.get_media_filter("Track")
|
media_filter = self.get_media_filter("Track")
|
||||||
track_charts = ChartRecord.objects.filter(
|
track_charts = ChartRecord.objects.filter(
|
||||||
@ -939,17 +911,13 @@ class ChartRecordView(TemplateView):
|
|||||||
).order_by("rank")
|
).order_by("rank")
|
||||||
|
|
||||||
if track_charts.count() == 0 and not in_progress:
|
if track_charts.count() == 0 and not in_progress:
|
||||||
ChartRecord.build(
|
ChartRecord.build(user=self.request.user, model_str="Track", **params)
|
||||||
user=self.request.user, model_str="Track", **params
|
|
||||||
)
|
|
||||||
media_filter = self.get_media_filter("Track")
|
media_filter = self.get_media_filter("Track")
|
||||||
track_charts = ChartRecord.objects.filter(
|
track_charts = ChartRecord.objects.filter(
|
||||||
media_filter, user=self.request.user, **params
|
media_filter, user=self.request.user, **params
|
||||||
).order_by("rank")
|
).order_by("rank")
|
||||||
if artist_charts.count() == 0 and not in_progress:
|
if artist_charts.count() == 0 and not in_progress:
|
||||||
ChartRecord.build(
|
ChartRecord.build(user=self.request.user, model_str="Artist", **params)
|
||||||
user=self.request.user, model_str="Artist", **params
|
|
||||||
)
|
|
||||||
media_filter = self.get_media_filter("Artist")
|
media_filter = self.get_media_filter("Artist")
|
||||||
artist_charts = ChartRecord.objects.filter(
|
artist_charts = ChartRecord.objects.filter(
|
||||||
media_filter, user=self.request.user, **params
|
media_filter, user=self.request.user, **params
|
||||||
@ -970,38 +938,24 @@ class ScrobbleStatusView(LoginRequiredMixin, TemplateView):
|
|||||||
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_scrobble_qs = Scrobble.objects.filter().order_by("-timestamp")
|
user_scrobble_qs = Scrobble.objects.filter().order_by("-timestamp")
|
||||||
progress_plays = user_scrobble_qs.filter(
|
progress_plays = user_scrobble_qs.filter(in_progress=True, is_paused=False)
|
||||||
in_progress=True, is_paused=False
|
|
||||||
)
|
|
||||||
|
|
||||||
data["listening"] = progress_plays.filter(
|
data["listening"] = progress_plays.filter(
|
||||||
Q(track__isnull=False) | Q(podcast_episode__isnull=False)
|
Q(track__isnull=False) | Q(podcast_episode__isnull=False)
|
||||||
).first()
|
).first()
|
||||||
data["watching"] = progress_plays.filter(video__isnull=False).first()
|
data["watching"] = progress_plays.filter(video__isnull=False).first()
|
||||||
data["going"] = progress_plays.filter(
|
data["going"] = progress_plays.filter(geo_location__isnull=False).first()
|
||||||
geo_location__isnull=False
|
data["playing"] = progress_plays.filter(board_game__isnull=False).first()
|
||||||
).first()
|
data["sporting"] = progress_plays.filter(sport_event__isnull=False).first()
|
||||||
data["playing"] = progress_plays.filter(
|
data["browsing"] = progress_plays.filter(web_page__isnull=False).first()
|
||||||
board_game__isnull=False
|
data["participating"] = progress_plays.filter(life_event__isnull=False).first()
|
||||||
).first()
|
|
||||||
data["sporting"] = progress_plays.filter(
|
|
||||||
sport_event__isnull=False
|
|
||||||
).first()
|
|
||||||
data["browsing"] = progress_plays.filter(
|
|
||||||
web_page__isnull=False
|
|
||||||
).first()
|
|
||||||
data["participating"] = progress_plays.filter(
|
|
||||||
life_event__isnull=False
|
|
||||||
).first()
|
|
||||||
data["working"] = progress_plays.filter(task__isnull=False).first()
|
data["working"] = progress_plays.filter(task__isnull=False).first()
|
||||||
|
|
||||||
long_plays = user_scrobble_qs.filter(
|
long_plays = user_scrobble_qs.filter(
|
||||||
long_play_complete=False, played_to_completion=True
|
long_play_complete=False, played_to_completion=True
|
||||||
)
|
)
|
||||||
data["reading"] = long_plays.filter(book__isnull=False).first()
|
data["reading"] = long_plays.filter(book__isnull=False).first()
|
||||||
data["sessioning"] = long_plays.filter(
|
data["sessioning"] = long_plays.filter(video_game__isnull=False).first()
|
||||||
video_game__isnull=False
|
|
||||||
).first()
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@ -1036,9 +990,7 @@ class ScrobbleDetailView(DetailView):
|
|||||||
data[field_name] = original_value
|
data[field_name] = original_value
|
||||||
|
|
||||||
if data.get("with_people_ids", False):
|
if data.get("with_people_ids", False):
|
||||||
data["with_people_ids"] = [
|
data["with_people_ids"] = [p.id for p in data["with_people_ids"]]
|
||||||
p.id for p in data["with_people_ids"]
|
|
||||||
]
|
|
||||||
|
|
||||||
if data.get("platform_id", False):
|
if data.get("platform_id", False):
|
||||||
data["platform_id"] = data["platform_id"].id
|
data["platform_id"] = data["platform_id"].id
|
||||||
|
|||||||
@ -12,6 +12,11 @@ urlpatterns = [
|
|||||||
views.SeriesDetailView.as_view(),
|
views.SeriesDetailView.as_view(),
|
||||||
name="series_detail",
|
name="series_detail",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"channels/<slug:slug>/",
|
||||||
|
views.ChannelDetailView.as_view(),
|
||||||
|
name="channel_detail",
|
||||||
|
),
|
||||||
path("videos/", views.VideoListView.as_view(), name="video_list"),
|
path("videos/", views.VideoListView.as_view(), name="video_list"),
|
||||||
path(
|
path(
|
||||||
"video/<slug:slug>/",
|
"video/<slug:slug>/",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.views import generic
|
from django.views import generic
|
||||||
from videos.models import Series, Video
|
from videos.models import Channel, Series, Video
|
||||||
from scrobbles.views import ScrobbleableListView, ScrobbleableDetailView
|
from scrobbles.views import ScrobbleableListView, ScrobbleableDetailView
|
||||||
|
|
||||||
|
|
||||||
@ -25,9 +25,7 @@ class SeriesDetailView(LoginRequiredMixin, generic.DetailView):
|
|||||||
context_data = super().get_context_data(**kwargs)
|
context_data = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
context_data["scrobbles"] = self.object.scrobbles_for_user(user_id)
|
context_data["scrobbles"] = self.object.scrobbles_for_user(user_id)
|
||||||
next_episode_id = (
|
next_episode_id = self.object.last_scrobbled_episode(user_id).next_imdb_id or ""
|
||||||
self.object.last_scrobbled_episode(user_id).next_imdb_id or ""
|
|
||||||
)
|
|
||||||
if self.object.is_episode_playing(user_id):
|
if self.object.is_episode_playing(user_id):
|
||||||
next_episode_id = ""
|
next_episode_id = ""
|
||||||
if next_episode_id:
|
if next_episode_id:
|
||||||
@ -35,6 +33,18 @@ class SeriesDetailView(LoginRequiredMixin, generic.DetailView):
|
|||||||
return context_data
|
return context_data
|
||||||
|
|
||||||
|
|
||||||
|
class ChannelDetailView(LoginRequiredMixin, generic.DetailView):
|
||||||
|
model = Channel
|
||||||
|
slug_field = "uuid"
|
||||||
|
template_name = "videos/channel_detail.html"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
user_id = self.request.user.id
|
||||||
|
context_data = super().get_context_data(**kwargs)
|
||||||
|
context_data["scrobbles"] = self.object.scrobbles_for_user(user_id)
|
||||||
|
return context_data
|
||||||
|
|
||||||
|
|
||||||
class VideoListView(ScrobbleableListView):
|
class VideoListView(ScrobbleableListView):
|
||||||
model = Video
|
model = Video
|
||||||
|
|
||||||
|
|||||||
@ -400,45 +400,60 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md">
|
<h2>Top YouTube Channels</h2>
|
||||||
<h2>TV Shows Watched</h2>
|
<ul class="nav nav-tabs" id="youtubeChannelTab" 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="youtubeChannel-{{key}}-tab" data-bs-toggle="tab" data-bs-target="#youtubeChannel-{{key}}"
|
||||||
|
type="button" role="tab" aria-controls="home" aria-selected="true">{{name}}</button>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
{% if series_this_week %}
|
<div class="tab-content" id="youtubeChannelTabContent" class="maloja-chart">
|
||||||
<h3>This Week</h3>
|
{% for key, channels in youtube_channel_charts.items %}
|
||||||
<div class="table-responsive">
|
<div class="tab-pane fade {% if forloop.counter == 2 %}show active{% endif %}" id="youtubeChannel-{{key}}" role="tabpanel" aria-labelledby="youtubeChannel-{{key}}-tab">
|
||||||
<table class="table table-striped table-sm">
|
<div style="display:block">
|
||||||
<tbody>
|
{% if channels.0 %}
|
||||||
{% for scrobble in series_this_week %}
|
<div style="float:left;">
|
||||||
<tr>
|
<div class="image-wrapper" style="display:flex; flex-wrap: wrap; margin:0">
|
||||||
<td><a href="{{scrobble.video.get_absolute_url}}">{{scrobble.video}}</a></td>
|
<div class="caption">#1 {{channels.0.name}} ({{channels.0.num_scrobbles}} videos)</div>
|
||||||
<td>{{scrobble.timestamp|date:"N d"}}</td>
|
{% if channels.0.cover_image %}
|
||||||
</tr>
|
<a href="{{channels.0.get_absolute_url}}"><img lt="{{channels.0.name}}" src="{{channels.0.cover_medium.url}}" width="300px"></a>
|
||||||
{% empty %}
|
{% else %}
|
||||||
<tr><td>No episodes watched this week</td></tr>
|
<a href="{{channels.0.get_absolute_url}}"><img lt="{{channels.0.name}}" src="{% static "images/not-found.jpg" %}" width="300px"></a>
|
||||||
{% endfor %}
|
{% endif %}
|
||||||
</tbody>
|
</div>
|
||||||
</table>
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if channels.1 %}
|
||||||
|
<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 {{channels.1.name}} ({{channels.1.num_scrobbles}})</div>
|
||||||
|
{% if channels.1.cover_image %}
|
||||||
|
<a href="{{channels.1.get_absolute_url}}"><img lt="{{channels.1.name}}" src="{{channels.1.cover_small.url}}" width="150px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{channels.1.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="150px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="image-wrapper" style="width:50%">
|
||||||
|
<div class="caption-medium">#3 {{channels.2.name}} ({{channels.2.num_scrobbles}})</div>
|
||||||
|
{% if channels.2.cover_image %}
|
||||||
|
<a href="{{channels.2.get_absolute_url}}"><img lt="{{channels.2.name}}" src="{{channels.2.cover_small.url}}" width="150px"></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="{{channels.2.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="150px"></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endfor %}
|
||||||
|
|
||||||
{% if series_this_month %}
|
|
||||||
<h3>This Month</h3>
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-striped table-sm">
|
|
||||||
<tbody>
|
|
||||||
{% for scrobble in series_this_month %}
|
|
||||||
<tr>
|
|
||||||
<td><a href="{{scrobble.video.get_absolute_url}}">{{scrobble.video}}</a></td>
|
|
||||||
<td>{{scrobble.timestamp|date:"N d"}}</td>
|
|
||||||
</tr>
|
|
||||||
{% empty %}
|
|
||||||
<tr><td>No episodes watched this month</td></tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% if videos_this_week or videos_this_month %}
|
{% if videos_this_week or videos_this_month %}
|
||||||
<div class="col-md">
|
<div class="col-md">
|
||||||
@ -482,47 +497,4 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if youtue_this_week or youtube_this_month %}
|
|
||||||
<div class="col-md">
|
|
||||||
<h2>YouTube Watched</h2>
|
|
||||||
|
|
||||||
{% if youtube_this_week %}
|
|
||||||
<h3>This Week</h3>
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-striped table-sm">
|
|
||||||
<tbody>
|
|
||||||
{% for scrobble in youtube_this_week %}
|
|
||||||
<tr>
|
|
||||||
<td><a href="{{scrobble.video.get_absolute_url}}">{{scrobble.video}}</a></td>
|
|
||||||
<td>{{scrobble.timestamp|date:"N d"}}</td>
|
|
||||||
</tr>
|
|
||||||
{% empty %}
|
|
||||||
<tr><td>No youtube watched this week</td></tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if youtube_this_month %}
|
|
||||||
<h3>This Month</h3>
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-striped table-sm">
|
|
||||||
<tbody>
|
|
||||||
{% for scrobble in youtube_this_month %}
|
|
||||||
<tr>
|
|
||||||
<td><a href="{{scrobble.video.get_absolute_url}}">{{scrobble.video}}</a></td>
|
|
||||||
<td>{{scrobble.timestamp|date:"N d"}}</td>
|
|
||||||
</tr>
|
|
||||||
{% empty %}
|
|
||||||
<tr><td>No youtube watched this month</td></tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
58
vrobbler/templates/videos/channel_detail.html
Normal file
58
vrobbler/templates/videos/channel_detail.html
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
{% extends "base_list.html" %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block title %}{{object.name}}{% endblock %}
|
||||||
|
|
||||||
|
{% block head_extra %}
|
||||||
|
<style>
|
||||||
|
.cover {float:left; width:412px; margin-right:10px; padding-bottom:15px;}
|
||||||
|
.summary {float:left; width:600px; margin-left:10px;}
|
||||||
|
.header {padding-bottom:15px;}
|
||||||
|
.image-wrapper { contain: content; }
|
||||||
|
.caption {
|
||||||
|
position: fixed;
|
||||||
|
top: 15px;
|
||||||
|
left: 30px;
|
||||||
|
padding: 5px;
|
||||||
|
font-size: 110%;
|
||||||
|
font-weight: bold;
|
||||||
|
color:white;
|
||||||
|
background:rgba(0,0,0,0.4);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block lists %}
|
||||||
|
|
||||||
|
<div class="row header">
|
||||||
|
<div class="cover image-wrapper">
|
||||||
|
<img src="{% if object.cover_image %}{{object.cover_image.url}}{% else %}{% static 'images/no-video-cover.jpg' %}{% endif %}" width="400px" />
|
||||||
|
</div>
|
||||||
|
<div class="summary">
|
||||||
|
{% if object.youtube_id %}<p><a href="{{object.youtube_url}}" target="_blank">View on YouTube</a></p>{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<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">Title</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for scrobble in scrobbles %}
|
||||||
|
<tr>
|
||||||
|
<td><a href={{scrobble.get_absolute_url}}>{{scrobble.local_timestamp}}</a></td>
|
||||||
|
<td><a href="{{scrobble.media_obj.get_absolute_url}}">{{scrobble.media_obj.title}}</a></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user