Cache the stats page

This commit is contained in:
Daan Sprenkels
2018-08-16 16:58:19 +02:00
parent 3f22056157
commit 60084cfe04
5 changed files with 135 additions and 53 deletions

View File

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

View File

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

View File

@ -6,10 +6,20 @@
{% block content %}
<h1>Statistics</h1>
<div class="row">
{% if not stats %}
<div class="alert alert-danger">
<strong>Stats unavailable :(</strong>
</div>
{% else %}
{% if current_age_text %}
<div class="alert alert-info">
<strong>{{ current_age_text }}</strong>
{% endif %}
</div>
<div class="col-md-6">
<h2>Uploads</h2>
<h4>Total: {{ total_uploads }}</h4>
<h4>Top {{ stats_top_count }}:</h4>
<h4>Total: {{ stats.total_uploads }}</h4>
<h4>Top {{ stats.stats_top_count }}:</h4>
<div class="table-responsive">
<table class="table table-striped">
<thead>
@ -20,7 +30,7 @@
</tr>
</thead>
<tbody>
{% for stat in upload_stats %}
{% for stat in stats.upload_stats %}
<tr>
<th>{{ forloop.counter }}</th>
<td>{{ stat.user__name }}</td>
@ -33,8 +43,8 @@
</div>
<div class="col-md-6">
<h2>Requests</h2>
<h4>Total: {{ total_requests }}</h4>
<h4>Top {{ stats_top_count }}:</h4>
<h4>Total: {{ stats.total_requests }}</h4>
<h4>Top {{ stats.stats_top_count }}:</h4>
<div class="table-responsive">
<table class="table table-striped">
<thead>
@ -45,11 +55,11 @@
</tr>
</thead>
<tbody>
{% for stat in request_stats %}
{% for stat in stats.request_stats %}
<tr>
<th>{{ forloop.counter }}</th>
<td>{{ stat.user__name }}</td>
<td>{{ stat.total }} ({% widthratio stat.total total_requests 100 %}%)</td>
<td>{{ stat.total }} ({% widthratio stat.total stats.total_requests 100 %}%)</td>
</tr>
{% endfor %}
</tbody>
@ -58,7 +68,7 @@
</div>
<div class="col-md-6">
<h2>Unique requests</h2>
<h4>Top {{ stats_top_count }}:</h4>
<h4>Top {{ stats.stats_top_count }}:</h4>
<div class="table-responsive">
<table class="table table-striped">
<thead>
@ -69,11 +79,11 @@
</tr>
</thead>
<tbody>
{% for stat in unique_request_stats %}
{% for stat in stats.unique_request_stats %}
<tr>
<th>{{ forloop.counter }}</th>
<td>{{ stat.user__name }}</td>
<td>{{ stat.total }} ({{stat.ratio }}%)</td>
<td>{{ stat.total }} ({{ stat.ratio }}%)</td>
</tr>
{% endfor %}
</tbody>
@ -82,7 +92,7 @@
</div>
<div class="col-md-6">
<h2>Most played songs</h2>
<h4>Top {{ stats_top_count }}:</h4>
<h4>Top {{ stats.stats_top_count }}:</h4>
<div class="table-responsive">
<table class="table table-striped">
<thead>
@ -94,7 +104,7 @@
</tr>
</thead>
<tbody>
{% for stat in most_played_songs %}
{% for stat in stats.most_played_songs %}
<tr>
<th>{{ forloop.counter }}</th>
<td>{{ stat.song__artist }}</td>
@ -108,7 +118,7 @@
</div>
<div class="col-md-6">
<h2>Most played songs last 14 days</h2>
<h4>Top {{ stats_top_count }}:</h4>
<h4>Top {{ stats.stats_top_count }}:</h4>
<div class="table-responsive">
<table class="table table-striped">
<thead>
@ -120,7 +130,7 @@
</tr>
</thead>
<tbody>
{% for stat in most_played_songs_14_days %}
{% for stat in stats.most_played_songs_14_days %}
<tr>
<th>{{ forloop.counter }}</th>
<td>{{ stat.song__artist }}</td>
@ -132,5 +142,6 @@
</table>
</div>
</div>
{% endif %}
</div>
{% endblock %}

73
marietje/stats/utils.py Normal file
View File

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

View File

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