Add timezone support and an authenticated view

This commit is contained in:
2023-01-18 00:39:12 -05:00
parent a0af0bce05
commit e75c22d583
6 changed files with 185 additions and 81 deletions

View File

@ -1,12 +1,13 @@
from django.db.models import Q, Count, Sum
from typing import List, Optional
from scrobbles.models import Scrobble
from music.models import Track, Artist
from videos.models import Video
from django.utils import timezone
from datetime import datetime, timedelta
from typing import List, Optional
import pytz
from django.db.models import Count, Q, Sum
from django.utils import timezone
from music.models import Artist, Track
from scrobbles.models import Scrobble
from videos.models import Video
from vrobbler.apps.profiles.utils import now_user_timezone
NOW = timezone.now()
START_OF_TODAY = datetime.combine(NOW.date(), datetime.min.time(), NOW.tzinfo)
@ -17,58 +18,103 @@ STARTING_DAY_OF_CURRENT_MONTH = NOW.date().replace(day=1)
STARTING_DAY_OF_CURRENT_YEAR = NOW.date().replace(month=1, day=1)
def scrobble_counts():
finished_scrobbles_qs = Scrobble.objects.filter(played_to_completion=True)
def scrobble_counts(user):
now = timezone.now()
user_filter = Q()
if user.is_authenticated:
now = now_user_timezone(user.profile)
user_filter = Q(user=user)
start_of_today = datetime.combine(
now.date(), datetime.min.time(), now.tzinfo
)
starting_day_of_current_week = now.date() - timedelta(
days=now.today().isoweekday() % 7
)
starting_day_of_current_month = now.date().replace(day=1)
starting_day_of_current_year = now.date().replace(month=1, day=1)
finished_scrobbles_qs = Scrobble.objects.filter(
user_filter, played_to_completion=True
)
data = {}
data['today'] = finished_scrobbles_qs.filter(
timestamp__gte=START_OF_TODAY
timestamp__gte=start_of_today
).count()
data['week'] = finished_scrobbles_qs.filter(
timestamp__gte=STARTING_DAY_OF_CURRENT_WEEK
timestamp__gte=starting_day_of_current_week
).count()
data['month'] = finished_scrobbles_qs.filter(
timestamp__gte=STARTING_DAY_OF_CURRENT_MONTH
timestamp__gte=starting_day_of_current_month
).count()
data['year'] = finished_scrobbles_qs.filter(
timestamp__gte=STARTING_DAY_OF_CURRENT_YEAR
timestamp__gte=starting_day_of_current_year
).count()
data['alltime'] = finished_scrobbles_qs.count()
return data
def week_of_scrobbles(media: str = 'tracks') -> dict[str, int]:
def week_of_scrobbles(user=None, media: str = 'tracks') -> dict[str, int]:
now = timezone.now()
user_filter = Q()
if user.is_authenticated:
now = now_user_timezone(user.profile)
user_filter = Q(user=user)
start_of_today = datetime.combine(
now.date(), datetime.min.time(), now.tzinfo
)
scrobble_day_dict = {}
base_qs = Scrobble.objects.filter(user_filter, played_to_completion=True)
media_filter = Q(track__isnull=False)
if media == 'movies':
media_filter = Q(video__video_type=Video.VideoType.MOVIE)
if media == 'series':
media_filter = Q(video__video_type=Video.VideoType.TV_EPISODE)
for day in range(6, -1, -1):
start = START_OF_TODAY - timedelta(days=day)
end = datetime.combine(start, datetime.max.time(), NOW.tzinfo)
start = start_of_today - timedelta(days=day)
end = datetime.combine(start, datetime.max.time(), now.tzinfo)
day_of_week = start.strftime('%A')
if media == 'movies':
media_filter = Q(video__videotype=Video.VideoType.MOVIE)
if media == 'series':
media_filter = Q(video__videotype=Video.VideoType.TV_EPISODE)
scrobble_day_dict[day_of_week] = (
Scrobble.objects.filter(media_filter)
.filter(
timestamp__gte=start,
timestamp__lte=end,
played_to_completion=True,
)
.count()
)
scrobble_day_dict[day_of_week] = base_qs.filter(
media_filter,
timestamp__gte=start,
timestamp__lte=end,
played_to_completion=True,
).count()
return scrobble_day_dict
def top_tracks(filter: str = "today", limit: int = 15) -> List["Track"]:
time_filter = Q(scrobble__timestamp__gte=START_OF_TODAY)
def top_tracks(
user: "User", filter: str = "today", limit: int = 15
) -> List["Track"]:
now = timezone.now()
if user.is_authenticated:
now = now_user_timezone(user.profile)
start_of_today = datetime.combine(
now.date(), datetime.min.time(), now.tzinfo
)
starting_day_of_current_week = now.date() - timedelta(
days=now.today().isoweekday() % 7
)
starting_day_of_current_month = now.date().replace(day=1)
starting_day_of_current_year = now.date().replace(month=1, day=1)
time_filter = Q(scrobble__timestamp__gte=start_of_today)
if filter == "week":
time_filter = Q(scrobble__timestamp__gte=STARTING_DAY_OF_CURRENT_WEEK)
time_filter = Q(scrobble__timestamp__gte=starting_day_of_current_week)
if filter == "month":
time_filter = Q(scrobble__timestamp__gte=STARTING_DAY_OF_CURRENT_MONTH)
time_filter = Q(scrobble__timestamp__gte=starting_day_of_current_month)
if filter == "year":
time_filter = Q(scrobble__timestamp__gte=STARTING_DAY_OF_CURRENT_YEAR)
time_filter = Q(scrobble__timestamp__gte=starting_day_of_current_year)
return (
Track.objects.filter(time_filter)
@ -77,7 +123,9 @@ def top_tracks(filter: str = "today", limit: int = 15) -> List["Track"]:
)
def top_artists(filter: str = "today", limit: int = 15) -> List["Artist"]:
def top_artists(
user: "User", filter: str = "today", limit: int = 15
) -> List["Artist"]:
time_filter = Q(track__scrobble__timestamp__gte=START_OF_TODAY)
if filter == "week":
time_filter = Q(

View File

@ -1,6 +1,9 @@
import datetime
import settings
import pytz
from django.conf import settings
from django.utils import timezone
# 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):
@ -17,7 +20,12 @@ def to_system_timezone(date, profile):
)
def now_timezone():
def now_user_timezone(profile):
timezone.activate(pytz.timezone(profile.timezone))
return timezone.localtime(timezone.now())
def now_system_timezone():
return (
datetime.datetime.now()
.replace(tzinfo=pytz.timezone(settings.TIME_ZONE))

View File

@ -9,6 +9,7 @@ from django.utils import timezone
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import FormView
from django.views.generic.list import ListView
import pytz
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
@ -45,31 +46,44 @@ class RecentScrobbleList(ListView):
def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)
user = self.request.user
now = timezone.now()
data['now_playing_list'] = Scrobble.objects.filter(
in_progress=True,
is_paused=False,
timestamp__lte=now,
)
data['video_scrobble_list'] = Scrobble.objects.filter(
video__isnull=False, played_to_completion=True
).order_by('-timestamp')[:15]
data['podcast_scrobble_list'] = Scrobble.objects.filter(
podcast_episode__isnull=False, played_to_completion=True
).order_by('-timestamp')[:15]
data['sport_scrobble_list'] = Scrobble.objects.filter(
sport_event__isnull=False, played_to_completion=True
).order_by('-timestamp')[:15]
# data['top_daily_tracks'] = top_tracks()
# data['top_weekly_tracks'] = top_tracks(filter='week')
data['top_monthly_tracks'] = top_tracks(filter='month')
if self.request.user.is_authenticated:
timezone.activate(pytz.timezone(user.profile.timezone))
now = timezone.localtime(timezone.now())
data['now_playing_list'] = Scrobble.objects.filter(
in_progress=True,
is_paused=False,
timestamp__lte=now,
user=user,
)
# data['top_daily_artists'] = top_artists()
data['top_weekly_artists'] = top_artists(filter='week')
data['top_monthly_artists'] = top_artists(filter='month')
completed_for_user = Scrobble.objects.filter(
played_to_completion=True, user=user
)
data['video_scrobble_list'] = completed_for_user.filter(
video__isnull=False
).order_by('-timestamp')[:15]
data["weekly_data"] = week_of_scrobbles()
data['counts'] = scrobble_counts()
data['podcast_scrobble_list'] = completed_for_user.filter(
podcast_episode__isnull=False
).order_by('-timestamp')[:15]
data['sport_scrobble_list'] = completed_for_user.filter(
sport_event__isnull=False
).order_by('-timestamp')[:15]
# data['top_daily_tracks'] = top_tracks()
data['top_weekly_tracks'] = top_tracks(user, filter='week')
data['top_monthly_tracks'] = top_tracks(user, filter='month')
# data['top_daily_artists'] = top_artists()
data['top_weekly_artists'] = top_artists(user, filter='week')
data['top_monthly_artists'] = top_artists(user, filter='month')
data["weekly_data"] = week_of_scrobbles(user=user)
data['counts'] = scrobble_counts(user)
data['imdb_form'] = ScrobbleForm
return data

View File

@ -53,7 +53,7 @@ TMDB_API_KEY = os.getenv("VROBBLER_TMDB_API_KEY", "")
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
TIME_ZONE = os.getenv("VROBBLER_TIME_ZONE", "EST")
TIME_ZONE = os.getenv("VROBBLER_TIME_ZONE", "US/Eastern")
ALLOWED_HOSTS = ["*"]
CSRF_TRUSTED_ORIGINS = [
@ -61,16 +61,9 @@ CSRF_TRUSTED_ORIGINS = [
]
X_FRAME_OPTIONS = "SAMEORIGIN"
CACHALOT_TIMEOUT = os.getenv("VROBBLER_CACHALOT_TIMEOUT", 3600)
REDIS_URL = os.getenv("VROBBLER_REDIS_URL", None)
CELERY_TASK_ALWAYS_EAGER = os.getenv("VROBBLER_SKIP_CELERY", False)
CELERY_BROKER_URL = REDIS_URL if REDIS_URL else "memory://localhost/"
CELERY_RESULT_BACKEND = "django-db"
CELERY_TIMEZONE = os.getenv("VROBBLER_TIME_ZONE", "EST")
CELERY_TASK_TRACK_STARTED = True
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",

View File

@ -10,6 +10,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" type="image/png" href="{% static 'images/favicon.ico' %}"/>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.12.9/dist/umd/popper.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-3.3.1.js" integrity="sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.10/clipboard.min.js"></script>
@ -175,16 +176,18 @@
<span class="navbar-toggler-icon"></span>
</button>
{% if user.is_authenticated %}
<form id="scrobble-form" action="{% url 'imdb-manual-scrobble' %}" method="post">
{% csrf_token %}
{{ imdb_form }}
</form>
{% endif %}
<div class="navbar-nav">
<div class="nav-item text-nowrap">
{% if user.is_authenticated %}
<a class="nav-link px-3" href="{% url "account_logout" %}">Sign out</a>
{% else %}
<a class="nav-link px-3" href="{% url "account_login" %}">Sign in</a>
{% if user.is_authenticated %}
<a class="nav-link px-3" href="{% url "account_logout" %}">Sign out</a>
{% else %}
<a class="nav-link px-3" href="{% url "account_login" %}">Sign in</a>
{% endif %}
</div>
</div>
@ -194,7 +197,7 @@
<div class="row">
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
<div class="position-sticky pt-3">
{% if now_playing_list %}
{% if now_playing_list and user.is_authenticated %}
<ul style="padding-right:10px;">
<b>Now playing</b>
{% for scrobble in now_playing_list %}

View File

@ -7,15 +7,21 @@
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
<h1 class="h2">Dashboard</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group me-2">
<button type="button" class="btn btn-sm btn-outline-secondary">Share</button>
<button type="button" class="btn btn-sm btn-outline-secondary">Export</button>
</div>
<button type="button" class="btn btn-sm btn-outline-secondary dropdown-toggle">
<span data-feather="calendar"></span>
This week
</button>
<div class="btn-group me-2">
<button type="button" class="btn btn-sm btn-outline-secondary">Share</button>
<button type="button" class="btn btn-sm btn-outline-secondary">Export</button>
</div>
<div class="dropdown">
<button type="button" class="btn btn-sm btn-outline-secondary dropdown-toggle" id="graphDateButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span data-feather="calendar"></span>
This week
</button>
<div class="dropdown-menu" aria-labelledby="graphDateButton">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<a class="dropdown-item" href="#">Something else here</a>
</div>
</div>
</div>
</div>
@ -23,6 +29,7 @@
<div class="container">
{% if user.is_authenticated %}
<div class="row">
<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>
@ -35,6 +42,9 @@
<li class="nav-item" role="presentation">
<button class="nav-link" id="artist-month-tab" data-bs-toggle="tab" data-bs-target="#artists-month" type="button" role="tab" aria-controls="home" aria-selected="true">Monthly Artists</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="profile-tab" data-bs-toggle="tab" data-bs-target="#tracks-week" type="button" role="tab" aria-controls="profile" aria-selected="false">Weekly Tracks</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="profile-tab" data-bs-toggle="tab" data-bs-target="#tracks-month" type="button" role="tab" aria-controls="profile" aria-selected="false">Monthly Tracks</button>
</li>
@ -63,6 +73,31 @@
</div>
</div>
<div class="tab-pane fade show" id="tracks-week" role="tabpanel" aria-labelledby="tracks-week-tab">
<h2>Top tracks this week</h2>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Track</th>
<th scope="col">Artist</th>
</tr>
</thead>
<tbody>
{% for track in top_weekly_tracks %}
<tr>
<td>{{track.num_scrobbles}}</td>
<td>{{track.title}}</td>
<td>{{track.artist.name}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="tab-pane fade show" id="tracks-month" role="tabpanel" aria-labelledby="tracks-month-tab">
<h2>Top tracks this month</h2>
<div class="table-responsive">
@ -226,6 +261,9 @@
</div>
</div>
{% else %}
{% endif %}
</div>
</main>
{% endblock %}