mirror of
https://gitlab.science.ru.nl/technicie/MarietjeDjango.git
synced 2025-12-15 04:02:22 +01:00
Compare commits
1 Commits
tosti-info
...
search
| Author | SHA1 | Date | |
|---|---|---|---|
| b9fa1747c4 |
141
.gitignore
vendored
141
.gitignore
vendored
@ -1,138 +1,3 @@
|
|||||||
# Byte-compiled / optimized / DLL files
|
/venv/
|
||||||
__pycache__/
|
*.pyc
|
||||||
*.py[cod]
|
*.pyo
|
||||||
*$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
|
- pip install -r requirements.txt pylint
|
||||||
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,10 +39,4 @@ 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,5 +19,4 @@ 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, HttpResponse
|
from django.http import JsonResponse, HttpResponseForbidden
|
||||||
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,7 +20,6 @@ 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')
|
||||||
@ -95,7 +94,10 @@ def songs(request):
|
|||||||
|
|
||||||
def search_songs():
|
def search_songs():
|
||||||
queries = [Q(deleted=False)]
|
queries = [Q(deleted=False)]
|
||||||
queries.extend([Q(Q(artist__icontains=word) | Q(title__icontains=word)) for word in request.POST.get('all', '').split()])
|
|
||||||
|
for word in request.POST.get('all', '').split():
|
||||||
|
regexword = r"".join('[\W]?[' + letter + ']' for letter in word)
|
||||||
|
queries.extend([Q(Q(artist__iregex=regexword) | Q(title__iregex=regexword))])
|
||||||
queries.extend([Q(user__name__icontains=word) for word in request.POST.get('uploader', '').split()])
|
queries.extend([Q(user__name__icontains=word) for word in request.POST.get('uploader', '').split()])
|
||||||
|
|
||||||
filter_query = queries.pop()
|
filter_query = queries.pop()
|
||||||
@ -169,7 +171,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, 'max_length': MAX_MINUTES_IN_A_ROW}
|
infobar = {"start_personal_queue": 0, "length_personal_queue": 0, "length_total_queue": 0, "end_personal_queue": 0}
|
||||||
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:
|
||||||
@ -178,6 +180,7 @@ 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,7 +192,6 @@ 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()
|
||||||
@ -349,26 +351,3 @@ 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 UnicodeUsernameValidator
|
from django.contrib.auth.validators import ASCIIUsernameValidator, 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 timezone
|
from django.utils import six, 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()
|
username_validator = UnicodeUsernameValidator() if six.PY3 else ASCIIUsernameValidator()
|
||||||
|
|
||||||
username = models.CharField(
|
username = models.CharField(
|
||||||
_('username'),
|
_('username'),
|
||||||
|
|||||||
@ -40,8 +40,8 @@ footer {
|
|||||||
border-bottom: 1px solid #DDDDDD;
|
border-bottom: 1px solid #DDDDDD;
|
||||||
}
|
}
|
||||||
|
|
||||||
tr.requested_song{
|
.requested_song .plays-at, .requested_song .requested-by {
|
||||||
border-left: 1px solid #777777;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-header-style td, .table-header-style{
|
.table-header-style td, .table-header-style{
|
||||||
@ -49,13 +49,3 @@ tr.requested_song{
|
|||||||
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;
|
|
||||||
}
|
|
||||||
|
|||||||
Binary file not shown.
@ -183,11 +183,6 @@ 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,7 +37,6 @@
|
|||||||
<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>
|
||||||
|
|||||||
@ -1,97 +0,0 @@
|
|||||||
<!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,5 +40,4 @@ 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,10 +12,8 @@ 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',)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
# 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,7 +64,6 @@ 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()
|
||||||
@ -112,7 +111,7 @@ class Queue(models.Model):
|
|||||||
return songs[1:]
|
return songs[1:]
|
||||||
|
|
||||||
def request(self, song, user):
|
def request(self, song, user):
|
||||||
if not user.has_perm('queues.unlimited_queue_length'):
|
if not user.is_superuser:
|
||||||
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)
|
||||||
@ -150,7 +149,7 @@ class Queue(models.Model):
|
|||||||
song_count += 1
|
song_count += 1
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.name)
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class QueueCommand(models.Model):
|
class QueueCommand(models.Model):
|
||||||
@ -162,4 +161,4 @@ class QueueCommand(models.Model):
|
|||||||
command = models.TextField()
|
command = models.TextField()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return str(self.command)
|
return self.command
|
||||||
|
|||||||
@ -36,11 +36,9 @@
|
|||||||
<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 hidden-sm hidden-xs"></p>
|
<p class="navbar-text start-queue"></p>
|
||||||
<p class="navbar-text end-queue"></p>
|
<p class="navbar-text end-queue"></p>
|
||||||
<div class="navbar-text">
|
<p class="navbar-text duration-queue"></p>
|
||||||
<p class="duration-queue"></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -95,9 +93,7 @@
|
|||||||
<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 class="col-md-1 hidden-xs text-info" style="cursor: pointer;">
|
<td id="timeswitch" class="col-md-1 hidden-xs text-info" >Plays In</td>
|
||||||
<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,7 +1,4 @@
|
|||||||
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
|
||||||
|
|
||||||
@ -9,40 +6,16 @@ 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):
|
||||||
# num_reports is annotated by SongHasReportNoteFilter
|
return ReportNote.objects.filter(song=song).count()
|
||||||
return song.num_reports
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def user_name(song):
|
def user_name(song):
|
||||||
@ -57,15 +30,6 @@ 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')
|
||||||
autocomplete_fields = ('user',)
|
readonly_fields = ('song',)
|
||||||
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 display-flex">
|
<div class="row">
|
||||||
{% if not stats %}
|
{% if not stats %}
|
||||||
<div class="col-xs-12 alert alert-danger">
|
<div class="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="col-xs-12 alert alert-info">
|
<div class="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,34 +148,11 @@
|
|||||||
</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>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>
|
<p>The left column shows the {{ stats.stats_top_count }} people whose songs are requested most often by other people
|
||||||
|
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,17 +7,18 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>User Statistics</h1>
|
<h1>User Statistics</h1>
|
||||||
|
|
||||||
<div class="row display-flex">
|
<div class="row">
|
||||||
{% if not stats %}
|
{% if not stats %}
|
||||||
<div class="col-xs-12 alert alert-danger">
|
<div class="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="col-xs-12 alert alert-info">
|
<div class="alert alert-info">
|
||||||
<strong>{{ current_age_text }}</strong>
|
<strong>{{ current_age_text }}</strong>
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h2>Most played songs</h2>
|
<h2>Most played songs</h2>
|
||||||
<p>You have requested <strong> {{ stats.unique_requests }} </strong> different
|
<p>You have requested <strong> {{ stats.unique_requests }} </strong> different
|
||||||
@ -47,30 +48,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="row">
|
||||||
<h2>Most played Artists</h2>
|
|
||||||
<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;"># 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 class="col-md-6">
|
<div class="col-md-6">
|
||||||
<h2>Uploads requested</h2>
|
<h2>Uploads requested</h2>
|
||||||
<p> You have uploaded a total of <strong> {{stats.total_uploads }} </strong> songs. The left column
|
<p> You have uploaded a total of <strong> {{stats.total_uploads }} </strong> songs. The left column
|
||||||
@ -104,34 +82,6 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="col-md-6">
|
||||||
<h2>Most played uploaders</h2>
|
<h2>Most played uploaders</h2>
|
||||||
<p> The people whose songs you have queued the most are:</p>
|
<p> The people whose songs you have queued the most are:</p>
|
||||||
@ -157,29 +107,6 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -10,12 +10,10 @@ 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, CACHE_TTL)
|
caches['default'].set('stats', new_stats, 2 * 3600)
|
||||||
return new_stats
|
return new_stats
|
||||||
|
|
||||||
|
|
||||||
@ -27,7 +25,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, CACHE_TTL)
|
caches['userstats'].set(cacheloc, new_stats, 48 * 3600)
|
||||||
return new_stats
|
return new_stats
|
||||||
|
|
||||||
|
|
||||||
@ -106,12 +104,6 @@ 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(
|
||||||
@ -171,7 +163,6 @@ 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(
|
||||||
@ -204,12 +195,6 @@ 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)
|
||||||
@ -218,50 +203,31 @@ 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', 'song__title')
|
'-total', 'song__artist',
|
||||||
|
'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 = {}
|
total_played_uploads = 0
|
||||||
total_played['uploads'] = 0
|
total_played_user_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,7 +150,6 @@ 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.2,<2.3
|
django==2.1
|
||||||
mysqlclient
|
mysqlclient
|
||||||
https://projects.unbit.it/downloads/uwsgi-lts.tar.gz
|
https://projects.unbit.it/downloads/uwsgi-lts.tar.gz
|
||||||
mutagen
|
mutagen
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
django>=2.2,<2.3
|
django
|
||||||
mutagen
|
mutagen
|
||||||
argon2-cffi
|
argon2-cffi
|
||||||
prometheus_client
|
prometheus_client
|
||||||
pylint==2.5.3
|
|
||||||
pillow
|
|
||||||
|
|||||||
Reference in New Issue
Block a user