Start implementing listening sessions

main
Luca 2 years ago
parent 1df7cdcf40
commit 0199584f92

@ -0,0 +1,35 @@
# Generated by Django 4.1.4 on 2022-12-16 20:49
from django.db import migrations, models
import django.db.models.deletion
import musicrate.core.models
class Migration(migrations.Migration):
dependencies = [
("core", "0001_initial"),
]
operations = [
migrations.CreateModel(
name="Session",
fields=[
(
"token",
models.CharField(
default=musicrate.core.models.session_token,
max_length=43,
primary_key=True,
serialize=False,
),
),
(
"playlist",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to="core.playlist"
),
),
],
),
]

@ -1,4 +1,5 @@
from django.db import models from django.db import models
from secrets import token_urlsafe
# Create your models here. # Create your models here.
@ -14,3 +15,10 @@ class Artist(models.Model):
origin = models.TextField(blank=True) origin = models.TextField(blank=True)
comment = models.TextField(blank=True) comment = models.TextField(blank=True)
playlist = models.ForeignKey(Playlist, on_delete=models.CASCADE) playlist = models.ForeignKey(Playlist, on_delete=models.CASCADE)
def session_token():
return token_urlsafe(32)
class Session(models.Model):
token = models.CharField(max_length=43, default=session_token, primary_key=True)
playlist = models.ForeignKey(Playlist, on_delete=models.CASCADE)

@ -9,13 +9,22 @@
padding: 0; padding: 0;
} }
a, a:link, a:visited {
color: currentColor;
text-decoration: none;
}
a:active, a:hover {
text-decoration: underline;
}
body { body {
align-items: center; align-items: center;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
font-family: "Maven Pro", sans-serif; font-family: "Maven Pro", sans-serif;
margin: 0 auto; margin: 0 auto;
max-width: 800px; max-width: 1200px;
min-height: 100vh; min-height: 100vh;
padding: 1em; padding: 1em;
} }
@ -24,6 +33,24 @@ body > * {
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
body > p, body > .links {
margin-bottom: 1em;
}
button {
font-family: "Maven Pro", sans-serif;
padding: 0 0.5em;
width: max-content;
}
svg {
max-width: 100%;
}
.center {
justify-content: center;
}
.error { .error {
background: #c00; background: #c00;
border-radius: 0.5em; border-radius: 0.5em;
@ -36,18 +63,76 @@ body > * {
width: 100%; width: 100%;
} }
.form * { .form > * {
font-family: "Maven Pro", sans-serif; font-family: "Maven Pro", sans-serif;
grid-column: 2 / span 1; grid-column: 2 / span 1;
margin: 0.125em 0.25em; margin: 0.125em 0.25em;
} }
.form button { .form > label {
padding: 0 0.5em; grid-column: 1 / span 1;
width: max-content; text-align: right;
} }
.form label { .links {
grid-column: 1 / span 1; display: flex;
flex-wrap: wrap;
max-width: 100%;
}
.links > * {
flex: 1 0;
}
.links > p {
align-self: center;
text-align: center;
width: 560px;
}
.modal {
align-items: center;
background: rgba(224, 224, 224, 0.95);
display: none;
flex-direction: column;
height: 100vh;
justify-content: center;
left: 0;
padding: 1em;
position: fixed;
top: 0;
width: 100vw;
}
.modal:target {
display: flex;
}
.modal > a {
height: 100%;
position: absolute;
width: 100%;
z-index: -1;
}
.modal > svg {
margin-bottom: 0.5em;
}
.pagination {
display: flex;
max-width: 800px;
width: 100%;
}
.pagination > * {
flex-basis: 0;
}
.pagination > a {
flex-grow: 1;
}
.pagination > a:last-child {
text-align: right; text-align: right;
} }

@ -8,7 +8,7 @@
<link rel="icon" href="{% static 'kontakt.svg' %}" sizes="any" type="image/svg+xml"> <link rel="icon" href="{% static 'kontakt.svg' %}" sizes="any" type="image/svg+xml">
<link rel="stylesheet" href="{% static 'style.css' %}"> <link rel="stylesheet" href="{% static 'style.css' %}">
</head> </head>
<body> <body class="{% block body_class %}{% endblock %}">
{% block body %} {% block body %}
{% endblock %} {% endblock %}
</body> </body>

@ -0,0 +1,10 @@
from django import template
from django.utils.safestring import mark_safe
from qrcode import make
from qrcode.image.svg import SvgPathFillImage
register = template.Library()
@register.simple_tag
def qrcode(data):
return mark_safe(make(data, box_size=20, image_factory=SvgPathFillImage).to_string().decode('utf-8'))

@ -0,0 +1,8 @@
from django import template
from django.utils.html import format_html
register = template.Library()
@register.simple_tag
def youtube(v):
return format_html('<iframe allow="autoplay; encrypted-media" allowfullscreen="" src="https://www.youtube-nocookie.com/embed/{}" width="560" height="315" frameborder="0"></iframe>', v)

@ -2,3 +2,6 @@ from django import forms
class CreatePlaylistForm(forms.Form): class CreatePlaylistForm(forms.Form):
spreadsheet_url = forms.URLField(label='Link zur Tabelle') spreadsheet_url = forms.URLField(label='Link zur Tabelle')
class StartSessionForm(forms.Form):
pass

@ -2,4 +2,9 @@
{% block body %} {% block body %}
<p>Diese Playlist enthält <b>{{ num_artists }} Künstler*innen</b>.</p> <p>Diese Playlist enthält <b>{{ num_artists }} Künstler*innen</b>.</p>
<form action="start" method="post">
{% csrf_token %}
<button type="submit">Anhörabend starten</button>
</form>
{% endblock %} {% endblock %}

@ -0,0 +1,11 @@
{% extends "core/base.html" %}
{% load qrcode %}
{% block body_class %}center{% endblock %}
{% block body %}
{% qrcode session_url %}
<p>{{ session_url }}</p>
<a href="{% url 'view_artist' session.playlist.pk 0 %}">Starten</a>
{% endblock %}

@ -0,0 +1,43 @@
{% extends "core/base.html" %}
{% load qrcode %}
{% load youtube %}
{% block body_class %}center{% endblock %}
{% block body %}
<h1>{{ artist.name }}</h1>
<p>{{ artist.genre }} &middot; {{ artist.origin }}<br>{{ artist.comment }}</p>
<div class="links">
{% for type, link in links %}
{% if type == 'youtube' %}
{% youtube link %}
{% else %}
<p>{{ link | urlize }}</p>
{% endif %}
{% endfor %}
</div>
<div class="pagination">
{% if offset > 0 %}
<a href="{% url 'view_artist' playlist offset|add:-1 %}">&lt; Zurück</a>
{% else %}
<a></a>
{% endif %}
{{ offset|add:1 }}/{{ count }}
{% if offset < count|add:-1 %}
<a href="{% url 'view_artist' playlist offset|add:1 %}">Weiter &gt;</a>
{% else %}
<a></a>
{% endif %}
</div>
{% if session_url %}
<a href="#session">QR-Code anzeigen</a>
<div class="modal" id="session">
<a href="#"></a>
{% qrcode session_url %}
{{ session_url }}
</div>
{% endif %}
{% endblock %}

@ -1,8 +1,13 @@
from django.urls import path from django.urls import include, path
from . import views from . import views
urlpatterns = [ urlpatterns = [
path('', views.create_playlist, name='create_playlist'), path('', views.create_playlist, name='create_playlist'),
path('<slug:playlist>', views.playlist, name='playlist'), path('session/', views.session, name='session'),
path('<slug:playlist>/', include([
path('', views.playlist, name='playlist'),
path('start', views.start_session, name='start_session'),
path('<int:offset>/', views.view_artist, name='view_artist'),
])),
] ]

@ -1,10 +1,14 @@
from django.conf import settings from django.conf import settings
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from re import match from re import match
from .forms import CreatePlaylistForm from .forms import CreatePlaylistForm, StartSessionForm
from .spreadsheet import get_sheet_data from .spreadsheet import get_sheet_data
from ..core.models import Artist, Playlist from ..core.models import Artist, Playlist, Session
def get_absolute_join_session_url(request, session):
return request.build_absolute_uri(reverse('join_session', args=(session.pk,)))
# Create your views here. # Create your views here.
@ -79,6 +83,51 @@ def create_playlist(request):
return render(request, 'host/create_playlist.html', {'error': None, 'form': form}) return render(request, 'host/create_playlist.html', {'error': None, 'form': form})
def session(request):
if 'session' in request.session:
try:
session = Session.objects.get(pk=request.session['session'])
return render(request, 'host/session.html', {'session': session, 'session_url': get_absolute_join_session_url(request, session)})
except Session.DoesNotExist:
del request.session['session']
return redirect('create_playlist')
def playlist(request, playlist): def playlist(request, playlist):
form = StartSessionForm()
playlist = get_object_or_404(Playlist, pk=playlist)
return render(request, 'host/playlist.html', {'form': form, 'num_artists': playlist.artist_set.count()})
def start_session(request, playlist):
playlist = get_object_or_404(Playlist, pk=playlist) playlist = get_object_or_404(Playlist, pk=playlist)
return render(request, 'host/playlist.html', {'num_artists': playlist.artist_set.count()})
if request.method == 'POST':
form = StartSessionForm(request.POST)
if not form.is_valid():
return redirect('playlist', playlist=playlist.pk)
if 'session' in request.session:
return redirect('session')
session = Session(playlist=playlist)
session.save()
request.session['session'] = session.token
return redirect('session')
return redirect('playlist', playlist=playlist.pk)
def view_artist(request, playlist, offset):
artists = get_object_or_404(Playlist, pk=playlist).artist_set
artist = artists.order_by('id')[offset]
links = (('youtube', m[1]) if (m := match(settings.YOUTUBE_RE, link)) else ('other', link) for link in (artist.link_1, artist.link_2) if link)
session_url = None
if 'session' in request.session:
try:
session = Session.objects.get(pk=request.session['session'])
session_url = get_absolute_join_session_url(request, session)
except Session.DoesNotExist:
del request.session['session']
return render(request, 'host/view_artist.html', {'artist': artist, 'count': artists.count(), 'links': links, 'offset': offset, 'playlist': playlist, 'session_url': session_url})

@ -141,3 +141,5 @@ SHEET_TO_MODEL = {
'Herkunft': ('origin', True), 'Herkunft': ('origin', True),
'Anmerkung': ('comment', True), 'Anmerkung': ('comment', True),
} }
YOUTUBE_RE = r'(?:|(?:|(?:http(?:|s):))//)(?:youtu\.be/|(?:|(?:m|www)\.)youtube\.com/watch\?v=)([-0-9A-Z_a-z]{11})'

@ -18,5 +18,6 @@ from django.urls import include, path
urlpatterns = [ urlpatterns = [
path('', include('musicrate.host.urls')), path('', include('musicrate.host.urls')),
path('vote/', include('musicrate.vote.urls')),
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
] ]

@ -0,0 +1,4 @@
from django import forms
class JoinSessionForm(forms.Form):
pass

@ -0,0 +1,4 @@
{% extends "core/base.html" %}
{% block body %}
{% endblock %}

@ -0,0 +1,8 @@
{% extends "core/base.html" %}
{% block body %}
<form action="" method="post">
{% csrf_token %}
<button type="submit">Anhörabend beitreten</button>
</form>
{% endblock %}

@ -1,7 +1,11 @@
from django.urls import path from django.urls import include, path
from . import views from . import views
urlpatterns = [ urlpatterns = [
path('', views.vote, name='vote'), path('join/<slug:session>/', views.join_session, name='join_session'),
path('<slug:playlist>/', include([
path('', views.artist, name='current_artist'),
path('<int:offset>/', views.artist, name='artist'),
])),
] ]

@ -1,6 +1,20 @@
from django.shortcuts import render from django.shortcuts import get_object_or_404, render
from .forms import JoinSessionForm
from ..core.models import Session
# Create your views here. # Create your views here.
def vote(request): def join_session(request, session):
return render(request, 'vote/vote.html', {}) session = get_object_or_404(Session, pk=session)
form = JoinSessionForm()
if request.method == 'POST':
form = JoinSessionForm(request.POST)
if not form.is_valid():
return render(request, 'vote/join_session.html', {'form': JoinSessionForm()})
return render(request, 'vote/join_session.html', {'form': form})
def artist(request, playlist, offset=None):
return render(request, 'vote/artist.html', {})

@ -14,6 +14,7 @@ protobuf==4.21.12
pyasn1==0.4.8 pyasn1==0.4.8
pyasn1-modules==0.2.8 pyasn1-modules==0.2.8
pyparsing==3.0.9 pyparsing==3.0.9
qrcode==7.3.1
requests==2.28.1 requests==2.28.1
rsa==4.9 rsa==4.9
six==1.16.0 six==1.16.0

Loading…
Cancel
Save