瀏覽代碼

Add timezone support and an authenticated view

Colin Powell 2 年之前
父節點
當前提交
e75c22d583

+ 82 - 34
vrobbler/apps/music/aggregators.py

@@ -1,12 +1,13 @@
-from django.db.models import Q, Count, Sum
+from datetime import datetime, timedelta
 from typing import List, Optional
-from scrobbles.models import Scrobble
-from music.models import Track, Artist
-from videos.models import Video
 
+import pytz
+from django.db.models import Count, Q, Sum
 from django.utils import timezone
-from datetime import datetime, timedelta
-
+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(

+ 10 - 2
vrobbler/apps/profiles/utils.py

@@ -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))

+ 36 - 22
vrobbler/apps/scrobbles/views.py

@@ -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,
+            )
+
+            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['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['top_daily_artists'] = top_artists()
-        data['top_weekly_artists'] = top_artists(filter='week')
-        data['top_monthly_artists'] = top_artists(filter='month')
+        data["weekly_data"] = week_of_scrobbles(user=user)
 
-        data["weekly_data"] = week_of_scrobbles()
-        data['counts'] = scrobble_counts()
+        data['counts'] = scrobble_counts(user)
         data['imdb_form'] = ScrobbleForm
         return data
 

+ 1 - 8
vrobbler/settings.py

@@ -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",

+ 8 - 5
vrobbler/templates/base.html

@@ -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 %}

+ 47 - 9
vrobbler/templates/scrobbles/scrobble_list.html

@@ -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 %}