mirror of
https://gitlab.science.ru.nl/technicie/MarietjeDjango.git
synced 2025-12-11 12:32:21 +01:00
Compare commits
51 Commits
search
...
tosti-info
| Author | SHA1 | Date | |
|---|---|---|---|
| d17c6d8b18 | |||
| ef627318ba | |||
| 4491fc234b | |||
| b1a080799c | |||
| e67bc8dc5a | |||
| e5fe2aa1cf | |||
| 9529ae245a | |||
| a325ebbe82 | |||
| a422e6d4f5 | |||
| b4a6530204 | |||
| 1292694c4a | |||
| 64b26d03a1 | |||
| 2ade1a7dfa | |||
| 1a797a5d98 | |||
| b604ac9955 | |||
| bb6166c1db | |||
| 3aa876e223 | |||
| 61fa646353 | |||
| 416fb3e5a9 | |||
| 1b5b5106ba | |||
| 4a1df11b40 | |||
| f4ab85106d | |||
| 23f651bbd1 | |||
| 3724b94e4a | |||
| 4fdf25ac43 | |||
| 83406ec0ab | |||
| e447a7c210 | |||
| 91d3b0cf35 | |||
| 228d0208f2 | |||
| 62ba17ef67 | |||
| 6a549fbd7b | |||
| 371334326b | |||
| 40e2245d39 | |||
| c9ba37d291 | |||
| 6ec3d57a39 | |||
| 59993aa1c0 | |||
| a1116ca4cd | |||
| 95bd47c46a | |||
| af6031c3a5 | |||
| a3f501273f | |||
| 7af7127612 | |||
| 4c9a431f8a | |||
| 53d3c6e5c4 | |||
| 581e14f7ef | |||
| d0ed0a0a62 | |||
| ce3bfb02d5 | |||
| 065f29fe55 | |||
| 3bdac86bfb | |||
| a864e8f535 | |||
| 48dd3bd0df | |||
| 2fcd827b85 |
141
.gitignore
vendored
141
.gitignore
vendored
@ -1,3 +1,138 @@
|
|||||||
/venv/
|
# Byte-compiled / optimized / DLL files
|
||||||
*.pyc
|
__pycache__/
|
||||||
*.pyo
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|||||||
@ -9,7 +9,7 @@ pylint:
|
|||||||
- apt-get -qq install -y python3 python3-venv python3-pip
|
- apt-get -qq install -y python3 python3-venv python3-pip
|
||||||
- python3 -m venv venv
|
- python3 -m venv venv
|
||||||
- source venv/bin/activate
|
- source venv/bin/activate
|
||||||
- pip install -r requirements.txt pylint
|
- pip install -r requirements.txt
|
||||||
script:
|
script:
|
||||||
- pylint marietje/marietje marietje/metrics marietje/playerapi marietje/queues marietje/songs marietje/stats
|
- pylint marietje/marietje marietje/metrics marietje/playerapi marietje/queues marietje/songs marietje/stats
|
||||||
|
|
||||||
@ -39,4 +39,10 @@ deploy:
|
|||||||
"\$PYTHON" "\$MANAGE" migrate --noinput
|
"\$PYTHON" "\$MANAGE" migrate --noinput
|
||||||
"\$PYTHON" "\$MANAGE" collectstatic --noinput
|
"\$PYTHON" "\$MANAGE" collectstatic --noinput
|
||||||
systemctl start MarietjeDjango.service
|
systemctl start MarietjeDjango.service
|
||||||
|
|
||||||
|
# Regenerate caches
|
||||||
|
(
|
||||||
|
sudo -u www-data /srv/MarietjeDjango/django_env/bin/python /srv/MarietjeDjango/marietje/manage.py recache_stats
|
||||||
|
sudo -u www-data /srv/MarietjeDjango/django_env/bin/python /srv/MarietjeDjango/marietje/manage.py recache_user_stats
|
||||||
|
) &
|
||||||
EOF
|
EOF
|
||||||
|
|||||||
@ -19,4 +19,5 @@ urlpatterns = [
|
|||||||
url(r'^volumedown', views.volume_down),
|
url(r'^volumedown', views.volume_down),
|
||||||
url(r'^volumeup', views.volume_up),
|
url(r'^volumeup', views.volume_up),
|
||||||
url(r'^mute', views.mute),
|
url(r'^mute', views.mute),
|
||||||
|
url(r'^hier-heb-je-je-endpoint-voor-tosti.png$', views.queue_png),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -8,7 +8,7 @@ from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
|||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Q, Sum, Value
|
from django.db.models import Q, Sum, Value
|
||||||
from django.db.models.functions import Coalesce
|
from django.db.models.functions import Coalesce
|
||||||
from django.http import JsonResponse, HttpResponseForbidden
|
from django.http import JsonResponse, HttpResponseForbidden, HttpResponse
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.views.decorators.cache import cache_page
|
from django.views.decorators.cache import cache_page
|
||||||
from django.views.decorators.http import require_http_methods
|
from django.views.decorators.http import require_http_methods
|
||||||
@ -20,6 +20,7 @@ from prometheus_client import Counter
|
|||||||
from marietje.utils import song_to_dict, playlist_song_to_dict, send_to_bertha
|
from marietje.utils import song_to_dict, playlist_song_to_dict, send_to_bertha
|
||||||
from queues.models import PlaylistSong, QueueCommand
|
from queues.models import PlaylistSong, QueueCommand
|
||||||
from songs.models import Song
|
from songs.models import Song
|
||||||
|
from marietje.settings import MAX_MINUTES_IN_A_ROW
|
||||||
|
|
||||||
request_counter = Counter('marietje_requests', 'Queue requests on marietje', ['queue'])
|
request_counter = Counter('marietje_requests', 'Queue requests on marietje', ['queue'])
|
||||||
upload_counter = Counter('marietje_uploads', 'Songs uploaded to marietje')
|
upload_counter = Counter('marietje_uploads', 'Songs uploaded to marietje')
|
||||||
@ -168,7 +169,7 @@ def managesongs(request):
|
|||||||
@api_auth_required
|
@api_auth_required
|
||||||
def queue(request):
|
def queue(request):
|
||||||
queue = request.user.queue
|
queue = request.user.queue
|
||||||
infobar = {"start_personal_queue": 0, "length_personal_queue": 0, "length_total_queue": 0, "end_personal_queue": 0}
|
infobar = {"start_personal_queue": 0, "length_personal_queue": 0, "length_total_queue": 0, "end_personal_queue": 0, 'max_length': MAX_MINUTES_IN_A_ROW}
|
||||||
for song in queue.queue():
|
for song in queue.queue():
|
||||||
infobar["length_total_queue"] += song.song.duration
|
infobar["length_total_queue"] += song.song.duration
|
||||||
if song.user == request.user:
|
if song.user == request.user:
|
||||||
@ -177,7 +178,6 @@ def queue(request):
|
|||||||
if infobar["start_personal_queue"] == 0:
|
if infobar["start_personal_queue"] == 0:
|
||||||
infobar["start_personal_queue"] = infobar["length_total_queue"] - song.song.duration
|
infobar["start_personal_queue"] = infobar["length_total_queue"] - song.song.duration
|
||||||
|
|
||||||
|
|
||||||
json = {
|
json = {
|
||||||
'current_song': playlist_song_to_dict(queue.current_song()),
|
'current_song': playlist_song_to_dict(queue.current_song()),
|
||||||
'queue': [playlist_song_to_dict(playlist_song, user=request.user) for playlist_song in queue.queue()],
|
'queue': [playlist_song_to_dict(playlist_song, user=request.user) for playlist_song in queue.queue()],
|
||||||
@ -189,6 +189,7 @@ def queue(request):
|
|||||||
return JsonResponse(json)
|
return JsonResponse(json)
|
||||||
|
|
||||||
|
|
||||||
|
@require_http_methods(["POST"])
|
||||||
@api_auth_required
|
@api_auth_required
|
||||||
def skip(request):
|
def skip(request):
|
||||||
playlist_song = request.user.queue.current_song()
|
playlist_song = request.user.queue.current_song()
|
||||||
@ -348,3 +349,26 @@ def _request_weight(ps):
|
|||||||
return float(ps.song.duration)
|
return float(ps.song.duration)
|
||||||
# Count other requests for 10%
|
# Count other requests for 10%
|
||||||
return 0.10 * float(ps.song.duration)
|
return 0.10 * float(ps.song.duration)
|
||||||
|
|
||||||
|
|
||||||
|
def queue_png(request):
|
||||||
|
current_song = request.user.queue.current_song()
|
||||||
|
|
||||||
|
requestor = 'privacy™' if current_song.user else 'Marietje'
|
||||||
|
artist, title = current_song.song.artist, current_song.song.title
|
||||||
|
|
||||||
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
|
width, height = 640, 480
|
||||||
|
ttf = 'marietje/static/fonts/comic-serif.tff'
|
||||||
|
zuidSerifRequestor = ImageFont.truetype(ttf, 64)
|
||||||
|
zuidSerifArtist = ImageFont.truetype(ttf, 80)
|
||||||
|
zuidSerifTitle = ImageFont.truetype(ttf, 64)
|
||||||
|
img = Image.new('RGB', (width, height), color='#BE311A')
|
||||||
|
imgDraw = ImageDraw.Draw(img)
|
||||||
|
imgDraw.text((10, 110), requestor, fill='#FFFFFF', font=zuidSerifRequestor)
|
||||||
|
imgDraw.text((10, 200), artist, fill='#FFFFFF', font=zuidSerifArtist)
|
||||||
|
imgDraw.text((10, 280), title, fill='#FFFFFF', font=zuidSerifTitle)
|
||||||
|
|
||||||
|
response = HttpResponse(content_type='image/png')
|
||||||
|
img.save(response, 'png')
|
||||||
|
return response
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
|
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
|
||||||
from django.contrib.auth.hashers import make_password
|
from django.contrib.auth.hashers import make_password
|
||||||
from django.contrib.auth.models import PermissionsMixin
|
from django.contrib.auth.models import PermissionsMixin
|
||||||
from django.contrib.auth.validators import ASCIIUsernameValidator, UnicodeUsernameValidator
|
from django.contrib.auth.validators import UnicodeUsernameValidator
|
||||||
from django.core.mail import send_mail
|
from django.core.mail import send_mail
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils import six, timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from marietje.utils import get_first_queue
|
from marietje.utils import get_first_queue
|
||||||
@ -46,7 +46,7 @@ class UserManager(BaseUserManager):
|
|||||||
|
|
||||||
|
|
||||||
class User(AbstractBaseUser, PermissionsMixin):
|
class User(AbstractBaseUser, PermissionsMixin):
|
||||||
username_validator = UnicodeUsernameValidator() if six.PY3 else ASCIIUsernameValidator()
|
username_validator = UnicodeUsernameValidator()
|
||||||
|
|
||||||
username = models.CharField(
|
username = models.CharField(
|
||||||
_('username'),
|
_('username'),
|
||||||
|
|||||||
@ -40,8 +40,8 @@ footer {
|
|||||||
border-bottom: 1px solid #DDDDDD;
|
border-bottom: 1px solid #DDDDDD;
|
||||||
}
|
}
|
||||||
|
|
||||||
.requested_song .plays-at, .requested_song .requested-by {
|
tr.requested_song{
|
||||||
font-weight: bold;
|
border-left: 1px solid #777777;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-header-style td, .table-header-style{
|
.table-header-style td, .table-header-style{
|
||||||
@ -49,3 +49,13 @@ footer {
|
|||||||
background-color: #f9f9f9;
|
background-color: #f9f9f9;
|
||||||
border-bottom: 2px solid #777777;
|
border-bottom: 2px solid #777777;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Bootstrap 3 doesn't support equal height columns, hack via <https://medium.com/wdstack/bootstrap-equal-height-columns-d07bc934eb27#892f> */
|
||||||
|
.row.display-flex {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.row.display-flex > [class*='col-'] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|||||||
BIN
marietje/marietje/static/fonts/comic-serif.tff
Normal file
BIN
marietje/marietje/static/fonts/comic-serif.tff
Normal file
Binary file not shown.
@ -183,6 +183,11 @@ function updateTime() {
|
|||||||
}
|
}
|
||||||
if (infobar['end_personal_queue'] !== 0){
|
if (infobar['end_personal_queue'] !== 0){
|
||||||
$('.start-queue').text("First song starts " + showExactOrRelative(infobar['start_personal_queue']));
|
$('.start-queue').text("First song starts " + showExactOrRelative(infobar['start_personal_queue']));
|
||||||
|
if (infobar['length_personal_queue'] > infobar['max_length'] * 60) {
|
||||||
|
$('.duration-queue').addClass('text-danger');
|
||||||
|
} else {
|
||||||
|
$('.duration-queue').removeClass('text-danger');
|
||||||
|
}
|
||||||
$('.duration-queue').text( " (" + (infobar['length_personal_queue']).secondsToMMSS() + ")");
|
$('.duration-queue').text( " (" + (infobar['length_personal_queue']).secondsToMMSS() + ")");
|
||||||
$('.end-queue').text("Last song ends " + showExactOrRelative(infobar['end_personal_queue']));
|
$('.end-queue').text("Last song ends " + showExactOrRelative(infobar['end_personal_queue']));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,6 +37,7 @@
|
|||||||
<li{% if request.path == url %} class="active"{% endif %}><a href="{{ url }}">Stats</a></li>
|
<li{% if request.path == url %} class="active"{% endif %}><a href="{{ url }}">Stats</a></li>
|
||||||
{% url 'stats:user_stats' as url %}
|
{% url 'stats:user_stats' as url %}
|
||||||
<li{% if request.path == url %} class="active"{% endif %}><a href="{{ url }}">User Stats</a></li>
|
<li{% if request.path == url %} class="active"{% endif %}><a href="{{ url }}">User Stats</a></li>
|
||||||
|
<li><a href="/beeldscherm/">Beeldscherm</a></li>
|
||||||
{% if user.is_staff %}
|
{% if user.is_staff %}
|
||||||
{% url 'admin:index' as url %}
|
{% url 'admin:index' as url %}
|
||||||
<li><a href="{{ url }}">Admin</a></li>
|
<li><a href="{{ url }}">Admin</a></li>
|
||||||
|
|||||||
97
marietje/marietje/templates/beeldscherm.html
Normal file
97
marietje/marietje/templates/beeldscherm.html
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
|
<!-- marietje-zuid -->
|
||||||
|
<title>Marietje-Zuid beeldscherm</title>
|
||||||
|
<style>
|
||||||
|
@font-face {
|
||||||
|
font-family: zuidSerif;
|
||||||
|
src: url('/static/fonts/comic-serif.tff') format('truetype');
|
||||||
|
}
|
||||||
|
html, body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #BE311A;
|
||||||
|
color: #FFFFFF;
|
||||||
|
font-family: zuidSerif;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.artist {
|
||||||
|
font-size: 80pt;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-size: 64pt;
|
||||||
|
}
|
||||||
|
.requestedBy {
|
||||||
|
font-size: 64pt;
|
||||||
|
}
|
||||||
|
#content {
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
padding-left: 25px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
function init() {
|
||||||
|
fetchCurrentQueue();
|
||||||
|
}
|
||||||
|
function fetchCurrentQueue() {
|
||||||
|
fetch('/api/queue')
|
||||||
|
.then(response => {
|
||||||
|
switch(response.status) {
|
||||||
|
case 200:
|
||||||
|
break;
|
||||||
|
case 401:
|
||||||
|
throw { name: 'NotLoggedIn', message: 'Fblwurp!' };
|
||||||
|
default:
|
||||||
|
throw new Error('unexpected response: '+ response);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(json => {
|
||||||
|
updateScreen(json.current_song.requested_by, json.current_song.song.artist, json.current_song.song.title);
|
||||||
|
setTimeout(fetchCurrentQueue, 1000);
|
||||||
|
}).catch(err => {
|
||||||
|
if(err.name == "NotLoggedIn") {
|
||||||
|
setTimeout(function() {
|
||||||
|
window.location.assign('/login/?next=/beeldscherm/');
|
||||||
|
}, 5000);
|
||||||
|
updateScreen('Error Handler', 'Not Logged In', 'Redirecting You In Five');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("error: "+ err);
|
||||||
|
updateScreen('Error Handler', 'Faulty Request', 'Help!');
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function updateScreen(requestor, artist, title) {
|
||||||
|
var r = document.getElementById('requestor');
|
||||||
|
var a = document.getElementById('song_artist');
|
||||||
|
var t = document.getElementById('song_title');
|
||||||
|
r.textContent = requestor;
|
||||||
|
a.textContent = artist;
|
||||||
|
t.textContent = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
init();
|
||||||
|
}, false);
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="content">
|
||||||
|
<p class="requestedBy" id="requestor">?</p>
|
||||||
|
<p class="artist" id="song_artist">?</p>
|
||||||
|
<p class="title" id="song_title">?</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -40,4 +40,5 @@ urlpatterns = [
|
|||||||
url(r'^playerapi/', include('playerapi.urls')),
|
url(r'^playerapi/', include('playerapi.urls')),
|
||||||
url(r'^stats/', include('stats.urls')),
|
url(r'^stats/', include('stats.urls')),
|
||||||
url(r'^metrics', metrics, name='metrics'),
|
url(r'^metrics', metrics, name='metrics'),
|
||||||
|
url(r'^beeldscherm/$', partial(render, template_name='beeldscherm.html'), name='beeldscherm'),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -12,8 +12,10 @@ class OrderAdmin(admin.ModelAdmin):
|
|||||||
@admin.register(PlaylistSong)
|
@admin.register(PlaylistSong)
|
||||||
class PlaylistSongAdmin(admin.ModelAdmin):
|
class PlaylistSongAdmin(admin.ModelAdmin):
|
||||||
list_display = ('playlist', 'song', 'user', 'state', 'played_at')
|
list_display = ('playlist', 'song', 'user', 'state', 'played_at')
|
||||||
|
list_display_links = ('song',)
|
||||||
list_filter = ('playlist', 'state', 'user')
|
list_filter = ('playlist', 'state', 'user')
|
||||||
search_fields = ('song__title', 'song__artist', 'user__name')
|
search_fields = ('song__title', 'song__artist', 'user__name')
|
||||||
|
autocomplete_fields = ('user',)
|
||||||
readonly_fields = ('song',)
|
readonly_fields = ('song',)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 2.2.13 on 2020-06-09 15:24
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('queues', '0008_remove_queuecommand_executed'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='queue',
|
||||||
|
options={'permissions': (('can_skip', 'Can skip the currently playing song'), ('can_move', 'Can move all songs in the queue'), ('can_cancel', 'Can cancel all songs in the queue'), ('can_control_volume', 'Can control the volume of Marietje'), ('unlimited_queue_length', 'Is unlimited by maximum queue length'))},
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -64,6 +64,7 @@ class Queue(models.Model):
|
|||||||
('can_move', 'Can move all songs in the queue'),
|
('can_move', 'Can move all songs in the queue'),
|
||||||
('can_cancel', 'Can cancel all songs in the queue'),
|
('can_cancel', 'Can cancel all songs in the queue'),
|
||||||
('can_control_volume', 'Can control the volume of Marietje'),
|
('can_control_volume', 'Can control the volume of Marietje'),
|
||||||
|
('unlimited_queue_length', 'Is unlimited by maximum queue length'),
|
||||||
)
|
)
|
||||||
|
|
||||||
name = models.TextField()
|
name = models.TextField()
|
||||||
@ -111,7 +112,7 @@ class Queue(models.Model):
|
|||||||
return songs[1:]
|
return songs[1:]
|
||||||
|
|
||||||
def request(self, song, user):
|
def request(self, song, user):
|
||||||
if not user.is_superuser:
|
if not user.has_perm('queues.unlimited_queue_length'):
|
||||||
playlist_songs = PlaylistSong.objects.filter(playlist=self.playlist, state=0).order_by('id')
|
playlist_songs = PlaylistSong.objects.filter(playlist=self.playlist, state=0).order_by('id')
|
||||||
|
|
||||||
seconds_in_a_row = sum(ps.song.duration for ps in playlist_songs if ps.user == user)
|
seconds_in_a_row = sum(ps.song.duration for ps in playlist_songs if ps.user == user)
|
||||||
@ -149,7 +150,7 @@ class Queue(models.Model):
|
|||||||
song_count += 1
|
song_count += 1
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return str(self.name)
|
||||||
|
|
||||||
|
|
||||||
class QueueCommand(models.Model):
|
class QueueCommand(models.Model):
|
||||||
@ -161,4 +162,4 @@ class QueueCommand(models.Model):
|
|||||||
command = models.TextField()
|
command = models.TextField()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.command
|
return str(self.command)
|
||||||
|
|||||||
@ -36,9 +36,11 @@
|
|||||||
<ul class="nav navbar-nav navbar-right hidden-xs">
|
<ul class="nav navbar-nav navbar-right hidden-xs">
|
||||||
<li>
|
<li>
|
||||||
<div class="infobar">
|
<div class="infobar">
|
||||||
<p class="navbar-text start-queue"></p>
|
<p class="navbar-text start-queue hidden-sm hidden-xs"></p>
|
||||||
<p class="navbar-text end-queue"></p>
|
<p class="navbar-text end-queue"></p>
|
||||||
<p class="navbar-text duration-queue"></p>
|
<div class="navbar-text">
|
||||||
|
<p class="duration-queue"></p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -93,7 +95,9 @@
|
|||||||
<td class="col-md-4">Artist</td>
|
<td class="col-md-4">Artist</td>
|
||||||
<td class="col-md-4">Title</td>
|
<td class="col-md-4">Title</td>
|
||||||
<td class="col-md-2 hidden-xs">Requested By</td>
|
<td class="col-md-2 hidden-xs">Requested By</td>
|
||||||
<td id="timeswitch" class="col-md-1 hidden-xs text-info" >Plays In</td>
|
<td class="col-md-1 hidden-xs text-info" style="cursor: pointer;">
|
||||||
|
<span id="timeswitch" class="btn-link" >Plays In</span>
|
||||||
|
</td>
|
||||||
<td class="col-md-1 control-icons">Control</td>
|
<td class="col-md-1 control-icons">Control</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="currentsong" style="font-weight: bold">
|
<tr class="currentsong" style="font-weight: bold">
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.db.models import Count
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.html import format_html
|
||||||
|
|
||||||
from .models import ReportNote, Song
|
from .models import ReportNote, Song
|
||||||
|
|
||||||
@ -6,16 +9,40 @@ from .models import ReportNote, Song
|
|||||||
class ReportNoteInline(admin.StackedInline):
|
class ReportNoteInline(admin.StackedInline):
|
||||||
model = ReportNote
|
model = ReportNote
|
||||||
extra = 0
|
extra = 0
|
||||||
|
autocomplete_fields = ('user',)
|
||||||
|
|
||||||
|
class SongHasReportNoteFilter(admin.SimpleListFilter):
|
||||||
|
title = 'report notes'
|
||||||
|
parameter_name = 'reportnotes'
|
||||||
|
|
||||||
|
def lookups(self, request, model_admin):
|
||||||
|
return (
|
||||||
|
('yes', 'yes'),
|
||||||
|
('no', 'no'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def queryset(self, request, queryset):
|
||||||
|
queryset = queryset.annotate(num_reports=Count('reportnote'))
|
||||||
|
if self.value() == 'yes':
|
||||||
|
return queryset.exclude(num_reports=0)
|
||||||
|
if self.value() == 'no':
|
||||||
|
return queryset.filter(num_reports=0)
|
||||||
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Song)
|
@admin.register(Song)
|
||||||
class SongAdmin(admin.ModelAdmin):
|
class SongAdmin(admin.ModelAdmin):
|
||||||
list_display = ('artist', 'title', 'user_name', 'reports')
|
list_display = ('artist', 'title', 'user_name', 'reports')
|
||||||
|
list_display_links = ('artist', 'title')
|
||||||
|
list_filter = (SongHasReportNoteFilter,)
|
||||||
search_fields = ('artist', 'title', 'user__name')
|
search_fields = ('artist', 'title', 'user__name')
|
||||||
inlines = [ReportNoteInline]
|
inlines = [ReportNoteInline]
|
||||||
|
autocomplete_fields = ('user',)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def reports(song):
|
def reports(song):
|
||||||
return ReportNote.objects.filter(song=song).count()
|
# num_reports is annotated by SongHasReportNoteFilter
|
||||||
|
return song.num_reports
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def user_name(song):
|
def user_name(song):
|
||||||
@ -30,6 +57,15 @@ class SongAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
@admin.register(ReportNote)
|
@admin.register(ReportNote)
|
||||||
class ReportNoteAdmin(admin.ModelAdmin):
|
class ReportNoteAdmin(admin.ModelAdmin):
|
||||||
|
exclude = ('song',)
|
||||||
list_display = ('song', 'note', 'user')
|
list_display = ('song', 'note', 'user')
|
||||||
search_fields = ('song__artist', 'song__title', 'user__name')
|
search_fields = ('song__artist', 'song__title', 'user__name')
|
||||||
readonly_fields = ('song',)
|
autocomplete_fields = ('user',)
|
||||||
|
readonly_fields = ('song_link',)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def song_link(note):
|
||||||
|
url = reverse("admin:songs_song_change", args=(note.song.id,))
|
||||||
|
return format_html("<a href='{url}'>{song}</a>", url=url, song=note.song)
|
||||||
|
|
||||||
|
song_link.short_description = "Song link"
|
||||||
|
|||||||
@ -5,14 +5,14 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Statistics</h1>
|
<h1>Statistics</h1>
|
||||||
<div class="row">
|
<div class="row display-flex">
|
||||||
{% if not stats %}
|
{% if not stats %}
|
||||||
<div class="alert alert-danger">
|
<div class="col-xs-12 alert alert-danger">
|
||||||
<strong>Stats unavailable :(</strong>
|
<strong>Stats unavailable :(</strong>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if current_age_text %}
|
{% if current_age_text %}
|
||||||
<div class="alert alert-info">
|
<div class="col-xs-12 alert alert-info">
|
||||||
<strong>{{ current_age_text }}</strong>
|
<strong>{{ current_age_text }}</strong>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@ -72,7 +72,7 @@
|
|||||||
<h2>Time requested</h2>
|
<h2>Time requested</h2>
|
||||||
<p>In total <strong> {{ stats.total_time_requested }} </strong> of music have been requested, with an
|
<p>In total <strong> {{ stats.total_time_requested }} </strong> of music have been requested, with an
|
||||||
average song length of <strong>{{ stats.total_average }}</strong>.
|
average song length of <strong>{{ stats.total_average }}</strong>.
|
||||||
These are the {{ stats.stats_top_count }} people with the longest total time queued</p>
|
These are the {{ stats.stats_top_count }} people with the longest total time queued.</p>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
@ -148,11 +148,34 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h2>Most played Artists</h2>
|
||||||
|
<p>These are the {{ stats.stats_top_count }} most played artists ever.</p>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Artist</th>
|
||||||
|
<th style="text-align: right;"># Requests</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for stat in stats.most_played_artists %}
|
||||||
|
<tr>
|
||||||
|
<th>{{ forloop.counter }}</th>
|
||||||
|
<td>{{ stat.song__artist }}</td>
|
||||||
|
<td style="text-align: right;">{{ stat.total }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h2>Most played uploaders</h2>
|
<h2>Most played uploaders</h2>
|
||||||
<p>The left column shows the {{ stats.stats_top_count }} people whose songs are requested most often by other people
|
<p>These are the {{ stats.stats_top_count }} people whose songs are requested most often by other people, as shown in the left column. The right column shows how many times that person has queued his own songs.</p>
|
||||||
people. The right column shows how many times that person has queued his own songs.</p>
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-striped">
|
<table class="table table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@ -7,107 +7,180 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>User Statistics</h1>
|
<h1>User Statistics</h1>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row display-flex">
|
||||||
{% if not stats %}
|
{% if not stats %}
|
||||||
<div class="alert alert-danger">
|
<div class="col-xs-12 alert alert-danger">
|
||||||
<strong>Stats unavailable :(</strong>
|
<strong>Stats unavailable :(</strong>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if current_age_text %}
|
{% if current_age_text %}
|
||||||
<div class="alert alert-info">
|
<div class="col-xs-12 alert alert-info">
|
||||||
<strong>{{ current_age_text }}</strong>
|
<strong>{{ current_age_text }}</strong>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
<div class="col-md-6">
|
||||||
</div>
|
<h2>Most played songs</h2>
|
||||||
<div class="col-md-6">
|
<p>You have requested <strong> {{ stats.unique_requests }} </strong> different
|
||||||
<h2>Most played songs</h2>
|
songs a total of <strong> {{ stats.total_requests }} </strong> times. This
|
||||||
<p>You have requested <strong> {{ stats.unique_requests }} </strong> different
|
means <strong> {% widthratio stats.unique_requests stats.total_requests 100 %}% </strong> of your requests have been unique. </p>
|
||||||
songs a total of <strong> {{ stats.total_requests }} </strong> times. This
|
<h4>Top {{ stats.stats_top_count }}:</h4>
|
||||||
means <strong> {% widthratio stats.unique_requests stats.total_requests 100 %}% </strong> of your requests have been unique. </p>
|
<div class="table-responsive">
|
||||||
<h4>Top {{ stats.stats_top_count }}:</h4>
|
<table class="table table-striped">
|
||||||
<div class="table-responsive">
|
<thead>
|
||||||
<table class="table table-striped">
|
<tr>
|
||||||
<thead>
|
<th>#</th>
|
||||||
<tr>
|
<th>Artist</th>
|
||||||
<th>#</th>
|
<th>Title</th>
|
||||||
<th>Artist</th>
|
<th># Requests</th>
|
||||||
<th>Title</th>
|
</tr>
|
||||||
<th># Requests</th>
|
</thead>
|
||||||
</tr>
|
<tbody>
|
||||||
</thead>
|
{% for stat in stats.most_played_songs %}
|
||||||
<tbody>
|
<tr>
|
||||||
{% for stat in stats.most_played_songs %}
|
<th>{{ forloop.counter }}</th>
|
||||||
<tr>
|
<td>{{ stat.song__artist }}</td>
|
||||||
<th>{{ forloop.counter }}</th>
|
<td>{{ stat.song__title }}</td>
|
||||||
<td>{{ stat.song__artist }}</td>
|
<td style="text-align: middle;">{{ stat.total }}</td>
|
||||||
<td>{{ stat.song__title }}</td>
|
</tr>
|
||||||
<td style="text-align: middle;">{{ stat.total }}</td>
|
{% endfor %}
|
||||||
</tr>
|
</tbody>
|
||||||
{% endfor %}
|
</table>
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h2>Uploads requested</h2>
|
<h2>Most played Artists</h2>
|
||||||
<p> You have uploaded a total of <strong> {{stats.total_uploads }} </strong> songs. The left column
|
<h4>Top {{ stats.stats_top_count }}:</h4>
|
||||||
shows how many times these have been requested by other people. The right column shows
|
<div class="table-responsive">
|
||||||
how many times you requested your own songs. In total your songs
|
<table class="table table-striped">
|
||||||
have been queued <strong> {{stats.total_played_uploads }} </strong> times by others and
|
<thead>
|
||||||
<strong> {{stats.total_played_user_uploads }} </strong> by yourself.
|
<tr>
|
||||||
<h4>Top {{ stats.stats_top_count }}:</h4>
|
<th>#</th>
|
||||||
<div class="table-responsive">
|
<th>Artist</th>
|
||||||
<table class="table table-striped">
|
<th style="text-align: right;"># Requests</th>
|
||||||
<thead>
|
</tr>
|
||||||
<tr>
|
</thead>
|
||||||
<th>#</th>
|
<tbody>
|
||||||
<th>Artist</th>
|
{% for stat in stats.most_played_artists %}
|
||||||
<th>Title</th>
|
<tr>
|
||||||
<th style="text-align: right;">Others</th>
|
<th>{{ forloop.counter }}</th>
|
||||||
<th>You</th>
|
<td>{{ stat.song__artist }}</td>
|
||||||
</tr>
|
<td style="text-align: right;">{{ stat.total }}</td>
|
||||||
</thead>
|
</tr>
|
||||||
<tbody>
|
{% endfor %}
|
||||||
{% for stat in stats.most_played_uploads %}
|
</tbody>
|
||||||
<tr>
|
</table>
|
||||||
<th>{{ forloop.counter }}</th>
|
|
||||||
<td>{{ stat.song__artist }}</td>
|
|
||||||
<td>{{ stat.song__title }}</td>
|
|
||||||
<td style="text-align: right;">{{ stat.total }}</td>
|
|
||||||
<td>{{ stat.user_total }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
</div>
|
||||||
<h2>Most played uploaders</h2>
|
<div class="col-md-6">
|
||||||
<p> The people whose songs you have queued the most are:</p>
|
<h2>Uploads requested</h2>
|
||||||
<div class="table-responsive">
|
<p> You have uploaded a total of <strong> {{stats.total_uploads }} </strong> songs. The left column
|
||||||
<table class="table table-striped">
|
shows how many times these have been requested by other people. The right column shows
|
||||||
<thead>
|
how many times you requested your own songs. In total your songs
|
||||||
<tr>
|
have been queued <strong> {{stats.total_played_uploads }} </strong> times by others and
|
||||||
<th>#</th>
|
<strong> {{stats.total_played_user_uploads }} </strong> by yourself.
|
||||||
<th>Uploader</th>
|
<h4>Top {{ stats.stats_top_count }}:</h4>
|
||||||
<th style="text-align: right;"># Requests</th>
|
<div class="table-responsive">
|
||||||
</tr>
|
<table class="table table-striped">
|
||||||
</thead>
|
<thead>
|
||||||
<tbody>
|
<tr>
|
||||||
{% for stat in stats.most_played_uploaders %}
|
<th>#</th>
|
||||||
<tr>
|
<th>Artist</th>
|
||||||
<th>{{ forloop.counter }}</th>
|
<th>Title</th>
|
||||||
<td>{{ stat.song__user__name }}</td>
|
<th style="text-align: right;">Others</th>
|
||||||
<td style="text-align: right;">{{ stat.total }}</td>
|
<th>You</th>
|
||||||
<td>({% widthratio stat.total stats.total_requests 100 %}%)</td>
|
</tr>
|
||||||
</tr>
|
</thead>
|
||||||
{% endfor %}
|
<tbody>
|
||||||
</tbody>
|
{% for stat in stats.most_played_uploads %}
|
||||||
</table>
|
<tr>
|
||||||
</div>
|
<th>{{ forloop.counter }}</th>
|
||||||
|
<td>{{ stat.song__artist }}</td>
|
||||||
|
<td>{{ stat.song__title }}</td>
|
||||||
|
<td style="text-align: right;">{{ stat.total }}</td>
|
||||||
|
<td>{{ stat.user_total }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h2>Upload artists requested</h2>
|
||||||
|
<p> The left column shows how many times songs from artists uploaded by you have been requested by
|
||||||
|
other people. The right column shows how many times you requested those songs.
|
||||||
|
<h4>Top {{ stats.stats_top_count }}:</h4>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Artist</th>
|
||||||
|
<th style="text-align: right;">Others</th>
|
||||||
|
<th>You</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for stat in stats.most_played_uploaded_artists %}
|
||||||
|
<tr>
|
||||||
|
<th>{{ forloop.counter }}</th>
|
||||||
|
<td>{{ stat.song__artist }}</td>
|
||||||
|
<td style="text-align: right;">{{ stat.total }}</td>
|
||||||
|
<td>{{ stat.user_total }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h2>Most played uploaders</h2>
|
||||||
|
<p> The people whose songs you have queued the most are:</p>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Uploader</th>
|
||||||
|
<th style="text-align: right;"># Requests</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for stat in stats.most_played_uploaders %}
|
||||||
|
<tr>
|
||||||
|
<th>{{ forloop.counter }}</th>
|
||||||
|
<td>{{ stat.song__user__name }}</td>
|
||||||
|
<td style="text-align: right;">{{ stat.total }}</td>
|
||||||
|
<td>({% widthratio stat.total stats.total_requests 100 %}%)</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h2>Biggest fans</h2>
|
||||||
|
<p> The people that queued your songs the most are:</p>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>User</th>
|
||||||
|
<th style="text-align: right;"># Requests</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for stat in stats.biggest_fans %}
|
||||||
|
<tr>
|
||||||
|
<th>{{ forloop.counter }}</th>
|
||||||
|
<td>{{ stat.user__name }}</td>
|
||||||
|
<td style="text-align: right;">{{ stat.total }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -10,10 +10,12 @@ from songs.models import Song
|
|||||||
from marietje.models import User
|
from marietje.models import User
|
||||||
|
|
||||||
|
|
||||||
|
CACHE_TTL = 2 * 24 * 3600
|
||||||
|
|
||||||
def recache_stats():
|
def recache_stats():
|
||||||
new_stats = compute_stats()
|
new_stats = compute_stats()
|
||||||
caches['default'].delete('stats')
|
caches['default'].delete('stats')
|
||||||
caches['default'].set('stats', new_stats, 2 * 3600)
|
caches['default'].set('stats', new_stats, CACHE_TTL)
|
||||||
return new_stats
|
return new_stats
|
||||||
|
|
||||||
|
|
||||||
@ -25,7 +27,7 @@ def recache_user_stats():
|
|||||||
new_stats = user_stats(user['id'])
|
new_stats = user_stats(user['id'])
|
||||||
cacheloc = 'userstats_{}'.format(user['id'])
|
cacheloc = 'userstats_{}'.format(user['id'])
|
||||||
caches['userstats'].delete(cacheloc)
|
caches['userstats'].delete(cacheloc)
|
||||||
caches['userstats'].set(cacheloc, new_stats, 48 * 3600)
|
caches['userstats'].set(cacheloc, new_stats, CACHE_TTL)
|
||||||
return new_stats
|
return new_stats
|
||||||
|
|
||||||
|
|
||||||
@ -104,6 +106,12 @@ def compute_stats():
|
|||||||
'song__title').annotate(total=Count('id')).order_by(
|
'song__title').annotate(total=Count('id')).order_by(
|
||||||
'-total', 'song__artist')[:settings.STATS_TOP_COUNT]
|
'-total', 'song__artist')[:settings.STATS_TOP_COUNT]
|
||||||
|
|
||||||
|
stats['most_played_artists'] = PlaylistSong.objects.filter(state=2).exclude(
|
||||||
|
Q(user_id=None)
|
||||||
|
| Q(user_id__in=settings.STATS_REQUEST_IGNORE_USER_IDS)).values(
|
||||||
|
'song__artist').annotate(total=Count('song__artist')).order_by(
|
||||||
|
'-total', 'song__artist')[:settings.STATS_TOP_COUNT]
|
||||||
|
|
||||||
stats['most_played_songs_14_days'] = PlaylistSong.objects.filter(
|
stats['most_played_songs_14_days'] = PlaylistSong.objects.filter(
|
||||||
state=2, played_at__gte=timezone.now() -
|
state=2, played_at__gte=timezone.now() -
|
||||||
timedelta(days=14)).exclude(user_id=None).values(
|
timedelta(days=14)).exclude(user_id=None).values(
|
||||||
@ -163,6 +171,7 @@ def compute_stats():
|
|||||||
'unique_request_stats': list(stats['unique_request_stats']),
|
'unique_request_stats': list(stats['unique_request_stats']),
|
||||||
'total_unique_requests': "{0:,.0f}".format(stats['total_unique_requests']['total']),
|
'total_unique_requests': "{0:,.0f}".format(stats['total_unique_requests']['total']),
|
||||||
'most_played_songs': list(stats['most_played_songs']),
|
'most_played_songs': list(stats['most_played_songs']),
|
||||||
|
'most_played_artists': list(stats['most_played_artists']),
|
||||||
'most_played_songs_14_days': list(stats['most_played_songs_14_days']),
|
'most_played_songs_14_days': list(stats['most_played_songs_14_days']),
|
||||||
'time_requested': stats['time_requested'],
|
'time_requested': stats['time_requested'],
|
||||||
'total_time_requested': str(round(float(
|
'total_time_requested': str(round(float(
|
||||||
@ -195,6 +204,12 @@ def user_stats(request):
|
|||||||
'-total', 'song__artist',
|
'-total', 'song__artist',
|
||||||
'song__title')[:settings.STATS_TOP_COUNT]
|
'song__title')[:settings.STATS_TOP_COUNT]
|
||||||
|
|
||||||
|
most_played_artists = PlaylistSong.objects.filter(
|
||||||
|
user__id=request, state=2,
|
||||||
|
song_id__isnull=False).values('song__artist').annotate(
|
||||||
|
total=Count('song__artist')).order_by(
|
||||||
|
'-total', 'song__artist')[:settings.STATS_TOP_COUNT]
|
||||||
|
|
||||||
most_played_uploaders = PlaylistSong.objects.filter(
|
most_played_uploaders = PlaylistSong.objects.filter(
|
||||||
user__id=request, state=2).exclude(
|
user__id=request, state=2).exclude(
|
||||||
Q(user_id=None)
|
Q(user_id=None)
|
||||||
@ -203,31 +218,50 @@ def user_stats(request):
|
|||||||
total=Count('song__user__id')).order_by(
|
total=Count('song__user__id')).order_by(
|
||||||
'-total')[:settings.STATS_TOP_COUNT]
|
'-total')[:settings.STATS_TOP_COUNT]
|
||||||
|
|
||||||
|
biggest_fans = PlaylistSong.objects.filter(
|
||||||
|
state=2, song_id__in=Song.objects.filter(user__id=request)).exclude(
|
||||||
|
Q(user_id=None)
|
||||||
|
| Q(user_id__in=settings.STATS_REQUEST_IGNORE_USER_IDS)).values(
|
||||||
|
'user__id', 'user__name').annotate(
|
||||||
|
total=Count('user__id')).order_by(
|
||||||
|
'-total')[:settings.STATS_TOP_COUNT]
|
||||||
|
|
||||||
most_played_uploads = PlaylistSong.objects.filter(
|
most_played_uploads = PlaylistSong.objects.filter(
|
||||||
state=2, song_id__in=Song.objects.filter(user__id=request)).exclude(
|
state=2, song_id__in=Song.objects.filter(user__id=request)).exclude(
|
||||||
user__id=None).values('song__artist', 'song__title').annotate(
|
user__id=None).values('song__artist', 'song__title').annotate(
|
||||||
total=Count('id', filter=~Q(user__id=request)),
|
total=Count('id', filter=~Q(user__id=request)),
|
||||||
user_total=Count('id', filter=Q(user__id=request))).order_by(
|
user_total=Count('id', filter=Q(user__id=request))).order_by(
|
||||||
'-total', 'song__artist',
|
'-total', 'song__artist', 'song__title')
|
||||||
'song__title')
|
|
||||||
|
most_played_uploaded_artists = PlaylistSong.objects.filter(
|
||||||
|
state=2, song_id__in=Song.objects.filter(user__id=request)).exclude(
|
||||||
|
user__id=None).values('song__artist').annotate(total=Count(
|
||||||
|
'song__artist', filter=~Q(user__id=request)), user_total=Count(
|
||||||
|
'id', filter=Q(user__id=request))).order_by(
|
||||||
|
'-total', 'song__artist')
|
||||||
|
|
||||||
most_played = list(most_played_uploads)
|
most_played = list(most_played_uploads)
|
||||||
total_played_uploads = 0
|
total_played = {}
|
||||||
total_played_user_uploads = 0
|
total_played['uploads'] = 0
|
||||||
|
total_played['user_uploads'] = 0
|
||||||
for x in most_played:
|
for x in most_played:
|
||||||
total_played_uploads += x['total']
|
total_played['uploads'] += x['total']
|
||||||
total_played_user_uploads += x['user_total']
|
total_played['user_uploads'] += x['user_total']
|
||||||
most_played_uploads_list = sorted(most_played_uploads, key=lambda x: (x['song__artist'], x['song__title']))
|
most_played_uploads_list = sorted(most_played_uploads, key=lambda x: (x['song__artist'], x['song__title']))
|
||||||
most_played_uploads_list = sorted(most_played_uploads_list, key=lambda x: x["total"], reverse=True)[:settings.STATS_TOP_COUNT]
|
most_played_uploads_list = sorted(most_played_uploads_list, key=lambda x: x["total"], reverse=True)[:settings.STATS_TOP_COUNT]
|
||||||
|
most_played_uploaded_artists = sorted(list(most_played_uploaded_artists), key=lambda x: x["total"], reverse=True)[:settings.STATS_TOP_COUNT]
|
||||||
return {
|
return {
|
||||||
'last_updated': last_updated,
|
'last_updated': last_updated,
|
||||||
'total_uploads': total_uploads,
|
'total_uploads': total_uploads,
|
||||||
'total_requests': total_requests,
|
'total_requests': total_requests,
|
||||||
'unique_requests': unique_requests,
|
'unique_requests': unique_requests,
|
||||||
'most_played_songs': list(most_played_songs),
|
'most_played_songs': list(most_played_songs),
|
||||||
|
'most_played_artists': list(most_played_artists),
|
||||||
'most_played_uploaders': list(most_played_uploaders),
|
'most_played_uploaders': list(most_played_uploaders),
|
||||||
'most_played_uploads': most_played_uploads_list,
|
'most_played_uploads': most_played_uploads_list,
|
||||||
|
'most_played_uploaded_artists': most_played_uploaded_artists,
|
||||||
'stats_top_count': settings.STATS_TOP_COUNT,
|
'stats_top_count': settings.STATS_TOP_COUNT,
|
||||||
'total_played_uploads': total_played_uploads,
|
'total_played_uploads': total_played['uploads'],
|
||||||
'total_played_user_uploads': total_played_user_uploads,
|
'total_played_user_uploads': total_played['user_uploads'],
|
||||||
|
'biggest_fans': list(biggest_fans),
|
||||||
}
|
}
|
||||||
|
|||||||
1
pylintrc
1
pylintrc
@ -150,6 +150,7 @@ disable=missing-docstring,
|
|||||||
missing-format-attribute,
|
missing-format-attribute,
|
||||||
too-few-public-methods,
|
too-few-public-methods,
|
||||||
unused-argument,
|
unused-argument,
|
||||||
|
signature-differs,
|
||||||
|
|
||||||
# Enable the message, report, category or checker with the given id(s). You can
|
# Enable the message, report, category or checker with the given id(s). You can
|
||||||
# either give multiple identifier separated by comma (,) or put this option
|
# either give multiple identifier separated by comma (,) or put this option
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
setuptools
|
setuptools
|
||||||
django==2.1
|
django>=2.2,<2.3
|
||||||
mysqlclient
|
mysqlclient
|
||||||
https://projects.unbit.it/downloads/uwsgi-lts.tar.gz
|
https://projects.unbit.it/downloads/uwsgi-lts.tar.gz
|
||||||
mutagen
|
mutagen
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
django
|
django>=2.2,<2.3
|
||||||
mutagen
|
mutagen
|
||||||
argon2-cffi
|
argon2-cffi
|
||||||
prometheus_client
|
prometheus_client
|
||||||
|
pylint==2.5.3
|
||||||
|
pillow
|
||||||
|
|||||||
Reference in New Issue
Block a user