From 60084cfe04a32bfeec1d972b8d38310373d1d5f1 Mon Sep 17 00:00:00 2001 From: Daan Sprenkels Date: Thu, 16 Aug 2018 16:58:19 +0200 Subject: [PATCH] Cache the stats page --- marietje/marietje/settings.py | 7 ++ .../management/commands/recache_stats.py | 9 +++ marietje/stats/templates/stats/stats.html | 39 ++++++---- marietje/stats/utils.py | 73 +++++++++++++++++++ marietje/stats/views.py | 60 ++++++--------- 5 files changed, 135 insertions(+), 53 deletions(-) create mode 100644 marietje/stats/management/commands/recache_stats.py create mode 100644 marietje/stats/utils.py diff --git a/marietje/marietje/settings.py b/marietje/marietje/settings.py index a47421c..64fb370 100644 --- a/marietje/marietje/settings.py +++ b/marietje/marietje/settings.py @@ -91,6 +91,13 @@ PASSWORD_HASHERS = [ 'django.contrib.auth.hashers.UnsaltedMD5PasswordHasher', ] +CACHES = { + 'default': { + 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', + 'LOCATION': '/var/tmp/MarietjeDjango_cache', + } +} + AUTH_USER_MODEL = 'marietje.User' LANGUAGE_CODE = 'en-us' TIME_ZONE = 'Europe/Amsterdam' diff --git a/marietje/stats/management/commands/recache_stats.py b/marietje/stats/management/commands/recache_stats.py new file mode 100644 index 0000000..13acb5f --- /dev/null +++ b/marietje/stats/management/commands/recache_stats.py @@ -0,0 +1,9 @@ +from django.core.management.base import BaseCommand + +from stats.utils import recache_stats + +class Command(BaseCommand): + help = 'Update the statistics cache' + + def handle(self, *args, **options): + recache_stats() diff --git a/marietje/stats/templates/stats/stats.html b/marietje/stats/templates/stats/stats.html index 8189cd8..0317a25 100644 --- a/marietje/stats/templates/stats/stats.html +++ b/marietje/stats/templates/stats/stats.html @@ -6,10 +6,20 @@ {% block content %}

Statistics

+ {% if not stats %} +
+ Stats unavailable :( +
+ {% else %} + {% if current_age_text %} +
+ {{ current_age_text }} + {% endif %} +

Uploads

-

Total: {{ total_uploads }}

-

Top {{ stats_top_count }}:

+

Total: {{ stats.total_uploads }}

+

Top {{ stats.stats_top_count }}:

@@ -20,7 +30,7 @@ - {% for stat in upload_stats %} + {% for stat in stats.upload_stats %} @@ -33,8 +43,8 @@

Requests

-

Total: {{ total_requests }}

-

Top {{ stats_top_count }}:

+

Total: {{ stats.total_requests }}

+

Top {{ stats.stats_top_count }}:

{{ forloop.counter }} {{ stat.user__name }}
@@ -45,11 +55,11 @@ - {% for stat in request_stats %} + {% for stat in stats.request_stats %} - + {% endfor %} @@ -58,7 +68,7 @@

Unique requests

-

Top {{ stats_top_count }}:

+

Top {{ stats.stats_top_count }}:

{{ forloop.counter }} {{ stat.user__name }}{{ stat.total }} ({% widthratio stat.total total_requests 100 %}%){{ stat.total }} ({% widthratio stat.total stats.total_requests 100 %}%)
@@ -69,11 +79,11 @@ - {% for stat in unique_request_stats %} + {% for stat in stats.unique_request_stats %} - + {% endfor %} @@ -82,7 +92,7 @@

Most played songs

-

Top {{ stats_top_count }}:

+

Top {{ stats.stats_top_count }}:

{{ forloop.counter }} {{ stat.user__name }}{{ stat.total }} ({{stat.ratio }}%){{ stat.total }} ({{ stat.ratio }}%)
@@ -94,7 +104,7 @@ - {% for stat in most_played_songs %} + {% for stat in stats.most_played_songs %} @@ -108,7 +118,7 @@

Most played songs last 14 days

-

Top {{ stats_top_count }}:

+

Top {{ stats.stats_top_count }}:

{{ forloop.counter }} {{ stat.song__artist }}
@@ -120,7 +130,7 @@ - {% for stat in most_played_songs_14_days %} + {% for stat in stats.most_played_songs_14_days %} @@ -132,5 +142,6 @@
{{ forloop.counter }} {{ stat.song__artist }}
+ {% endif %}
{% endblock %} diff --git a/marietje/stats/utils.py b/marietje/stats/utils.py new file mode 100644 index 0000000..d52710a --- /dev/null +++ b/marietje/stats/utils.py @@ -0,0 +1,73 @@ +from datetime import datetime, timedelta + +from django.core.cache import cache +from django.conf import settings +from django.db.models import Count, Q +from django.shortcuts import render +from django.utils import timezone + +from queues.models import PlaylistSong +from songs.models import Song + + +def recache_stats(): + new_stats = compute_stats() + cache.delete('stats') + cache.set('stats', new_stats) + return new_stats + +def compute_stats(): + # We want to grab the time now, because otherwise we would be reporting a minute too late + last_updated = datetime.now() + + total_uploads = Song.objects.filter(deleted=False).exclude( + user_id=None).count() + + upload_stats = Song.objects.filter( + deleted=False).exclude(user_id=None).values('user__name').annotate( + total=Count('id')).order_by( + '-total', 'user__name')[:settings.STATS_TOP_COUNT] + + total_requests = PlaylistSong.objects.filter(state=2).exclude( + Q(user_id=None) + | Q(user_id__in=settings.STATS_REQUEST_IGNORE_USER_IDS)).count() + + request_stats = PlaylistSong.objects.filter(state=2).exclude( + Q(user_id=None) + | Q(user_id__in=settings.STATS_REQUEST_IGNORE_USER_IDS)).values( + 'user__name').annotate(total=Count('id')).order_by( + '-total', 'user__name')[:settings.STATS_TOP_COUNT] + + unique_request_stats = PlaylistSong.objects.filter(state=2).exclude( + Q(user_id=None) + | Q(user_id__in=settings.STATS_REQUEST_IGNORE_USER_IDS)).values( + 'user__name', 'user__name').annotate( + total=Count('song_id', distinct=True), + ratio=Count('song_id', distinct=True) / Count('id') * + 100).order_by('-total')[:settings.STATS_TOP_COUNT] + + most_played_songs = PlaylistSong.objects.filter(state=2).exclude( + Q(user_id=None) + | Q(user_id__in=settings.STATS_REQUEST_IGNORE_USER_IDS)).values( + 'song__artist', + 'song__title').annotate(total=Count('id')).order_by( + '-total', 'song__artist')[:settings.STATS_TOP_COUNT] + + most_played_songs_14_days = PlaylistSong.objects.filter( + state=2, played_at__gte=timezone.now() - + timedelta(days=14)).exclude(user_id=None).values( + 'song__artist', + 'song__title').annotate(total=Count('id')).order_by( + '-total', 'song__artist')[:settings.STATS_TOP_COUNT] + + return { + 'last_updated': last_updated, + 'total_uploads': total_uploads, + 'upload_stats': upload_stats, + 'total_requests': total_requests, + 'request_stats': request_stats, + 'unique_request_stats': unique_request_stats, + 'most_played_songs': most_played_songs, + 'most_played_songs_14_days': most_played_songs_14_days, + 'stats_top_count': settings.STATS_TOP_COUNT, + } diff --git a/marietje/stats/views.py b/marietje/stats/views.py index caba1fa..16adcc5 100644 --- a/marietje/stats/views.py +++ b/marietje/stats/views.py @@ -1,46 +1,28 @@ -from datetime import timedelta +from datetime import datetime, timedelta -from django.conf import settings -from django.db.models import Count, Q +from django.core.cache import cache from django.shortcuts import render -from django.utils import timezone - -from queues.models import PlaylistSong -from songs.models import Song def stats(request): - total_uploads = Song.objects.filter(deleted=False).exclude(user_id=None).count() + stats = cache.get('stats') + status = 503 + current_age = None + current_age_text = None - upload_stats = Song.objects.filter(deleted=False).exclude(user_id=None).values('user__name').annotate( - total=Count('id')).order_by('-total', 'user__name')[:settings.STATS_TOP_COUNT] + if stats: + status = 200 + if 'last_updated' in stats: + current_age = datetime.now() - stats['last_updated'] + if current_age < timedelta(minutes=1): + current_age_text = 'Stats were updated less than a minute ago.' + elif current_age < timedelta(minutes=2): + current_age_text = 'Stats were updated one minute ago.' + elif current_age < timedelta(minutes=60): + minutes = current_age.seconds / 60 + current_age_text = 'Stats were updated {:.0f} minutes ago.'.format(minutes) + else: + current_age_text = 'Stats were updated more than an hour ago' - total_requests = PlaylistSong.objects.filter(state=2).exclude( - Q(user_id=None) | Q(user_id__in=settings.STATS_REQUEST_IGNORE_USER_IDS)).count() - - request_stats = PlaylistSong.objects.filter(state=2).exclude( - Q(user_id=None) | Q(user_id__in=settings.STATS_REQUEST_IGNORE_USER_IDS)).values('user__name').annotate( - total=Count('id')).order_by('-total', 'user__name')[:settings.STATS_TOP_COUNT] - - unique_request_stats = PlaylistSong.objects.filter(state=2).exclude( - Q(user_id=None) | Q(user_id__in=settings.STATS_REQUEST_IGNORE_USER_IDS)).values( - 'user__name', 'user__name').annotate( - total=Count('song_id', distinct=True), ratio=Count('song_id', distinct=True) / Count('id') * 100).order_by( - '-total')[:settings.STATS_TOP_COUNT] - - most_played_songs = PlaylistSong.objects.filter(state=2).exclude( - Q(user_id=None) | Q(user_id__in=settings.STATS_REQUEST_IGNORE_USER_IDS)).values( - 'song__artist', 'song__title').annotate(total=Count('id')).order_by( - '-total', 'song__artist')[:settings.STATS_TOP_COUNT] - - most_played_songs_14_days = PlaylistSong.objects.filter( - state=2, played_at__gte=timezone.now() - timedelta(days=14)).exclude(user_id=None).values( - 'song__artist', 'song__title').annotate(total=Count('id')).order_by( - '-total', 'song__artist')[:settings.STATS_TOP_COUNT] - - return render(request, 'stats/stats.html', {'total_uploads': total_uploads, 'upload_stats': upload_stats, - 'total_requests': total_requests, 'request_stats': request_stats, - 'unique_request_stats': unique_request_stats, - 'most_played_songs': most_played_songs, - 'most_played_songs_14_days': most_played_songs_14_days, - 'stats_top_count': settings.STATS_TOP_COUNT}) + data = {'stats': stats, 'current_age': current_age, 'current_age_text': current_age_text} + return render(request, 'stats/stats.html', data, status=status)