Fonctionalités de base
This commit is contained in:
parent
762d736625
commit
5c30be2c65
12 changed files with 385 additions and 5 deletions
|
@ -14,8 +14,9 @@ Including another URLconf
|
||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path
|
from django.urls import path, include
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
|
path('', include('player.urls')),
|
||||||
]
|
]
|
||||||
|
|
24
player/forms.py
Normal file
24
player/forms.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
|
||||||
|
from player.models import Playlist, Link
|
||||||
|
|
||||||
|
class PlaylistForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Playlist
|
||||||
|
fields = ['name']
|
||||||
|
|
||||||
|
def is_valid(self):
|
||||||
|
self.instance.date = datetime.datetime.now()
|
||||||
|
return super().is_valid()
|
||||||
|
|
||||||
|
class LinkForm(forms.Form):
|
||||||
|
url = forms.URLField(label="URL de la piste à ajouter")
|
||||||
|
|
||||||
|
def get_token(self):
|
||||||
|
p=urlparse(self.cleaned_data['url'])
|
||||||
|
print(p.query)
|
||||||
|
return [i for i in p.query.split('&') if i and i[0]=='v'][0].split('=')[-1]
|
||||||
|
|
29
player/migrations/0002_auto_20180324_2340.py
Normal file
29
player/migrations/0002_auto_20180324_2340.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
# Generated by Django 2.0.3 on 2018-03-24 22:40
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('player', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='link',
|
||||||
|
name='url',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='link',
|
||||||
|
name='token',
|
||||||
|
field=models.CharField(default='', max_length=200, verbose_name='Token'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='playlist',
|
||||||
|
name='name',
|
||||||
|
field=models.CharField(default='Nom', max_length=255, verbose_name='Nom de la playlist'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,9 +1,14 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.shortcuts import reverse
|
||||||
|
|
||||||
PK_LENGTH = 23
|
PK_LENGTH = 23
|
||||||
|
|
||||||
class Playlist(models.Model):
|
class Playlist(models.Model):
|
||||||
date = models.DateTimeField(verbose_name="date")
|
date = models.DateTimeField(verbose_name="date")
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
verbose_name="Nom de la playlist"
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def reverse_token(token):
|
def reverse_token(token):
|
||||||
|
@ -13,14 +18,17 @@ class Playlist(models.Model):
|
||||||
return int(self.date.timestamp()) << PK_LENGTH | self.pk
|
return int(self.date.timestamp()) << PK_LENGTH | self.pk
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
pass
|
return reverse('player:playlist', kwargs={'token':self.get_token()})
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Playlist " + str(self.get_token())
|
return "Playlist " + str(self.get_token())
|
||||||
|
|
||||||
|
|
||||||
class Link(models.Model):
|
class Link(models.Model):
|
||||||
url = models.URLField(verbose_name="Lien")
|
token = models.CharField(
|
||||||
|
max_length=200,
|
||||||
|
verbose_name="Token",
|
||||||
|
)
|
||||||
playlist = models.ForeignKey(
|
playlist = models.ForeignKey(
|
||||||
Playlist,
|
Playlist,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
|
|
30
player/templates/player/all_list.html
Normal file
30
player/templates/player/all_list.html
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 text-justify">
|
||||||
|
<h1>Live Share</h1>
|
||||||
|
<br/>
|
||||||
|
<p>Bienvenue sur Live Share. Vous pouvez créer des playlist youtube anonymes, collaboratives et éphémères !</p>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
<a class="btn btn-success btn-lg" type='button' href="{% url 'player:new' %}">
|
||||||
|
<i class="fa fa-plus"></i> Nouvelle playlist
|
||||||
|
</a>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6 text-justify">
|
||||||
|
<h1>Diffusions en cours</h1>
|
||||||
|
<br/>
|
||||||
|
{% for l in lists %}
|
||||||
|
<a class="card bg-secondary text-white" href="{{l.get_absolute_url}}">
|
||||||
|
<div class="card-body">
|
||||||
|
<i class="far fa-play-circle"></i>
|
||||||
|
{{l.name}}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<br/>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
164
player/templates/player/playlist.html
Normal file
164
player/templates/player/playlist.html
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
{% load bootstrap4 %}
|
||||||
|
|
||||||
|
{% block title %} - {{playlist.name}}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<h1>{{playlist.name}}</h1>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<a class="btn btn-success btn-sm" type='button' href="">
|
||||||
|
<i class="fa fa-edit"></i> Changer le nom
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
Lien de partage : <input class="input-monospace" value="{{request.get_host}}{{playlist.get_absolute_url}}" type="text" readonly="">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row" style="height:100%">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<h2>Lecture</h2>
|
||||||
|
<!-- 1. The <iframe> (and video player) will replace this <div> tag. -->
|
||||||
|
<div id="player" style="max-width:100%"></div>
|
||||||
|
<form onSubmit="return addLink();" class="form" id="add_link_form">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form form %}
|
||||||
|
<button class="btn btn-outline-primary"><i class="fa fa-plus"></i> Ajouter</button>
|
||||||
|
</form>
|
||||||
|
<script>
|
||||||
|
var tracks = [
|
||||||
|
{% for link in playlist.link_set.all %}
|
||||||
|
"{{link.token}}",
|
||||||
|
{% endfor %}
|
||||||
|
];
|
||||||
|
// 2. This code loads the IFrame Player API code asynchronously.
|
||||||
|
var tag = document.createElement('script');
|
||||||
|
var current_link = -1;
|
||||||
|
tag.src = "https://www.youtube.com/iframe_api";
|
||||||
|
var firstScriptTag = document.getElementsByTagName('script')[0];
|
||||||
|
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
|
||||||
|
|
||||||
|
// 3. This function creates an <iframe> (and YouTube player)
|
||||||
|
// after the API code downloads.
|
||||||
|
var player;
|
||||||
|
function onYouTubeIframeAPIReady() {
|
||||||
|
player = new YT.Player('player', {
|
||||||
|
height: '390',
|
||||||
|
width: '640',
|
||||||
|
events: {
|
||||||
|
'onReady': onPlayerReady,
|
||||||
|
'onStateChange': onPlayerStateChange
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. The API will call this function when the video player is ready.
|
||||||
|
function onPlayerReady(event) {
|
||||||
|
console.log(current_link);
|
||||||
|
console.log(tracks.length);
|
||||||
|
if(tracks.length > 0 && current_link < (tracks.length - 1)) {
|
||||||
|
current_link += 1
|
||||||
|
player.loadVideoById(tracks[current_link]);
|
||||||
|
event.target.playVideo();
|
||||||
|
}
|
||||||
|
setInterval(loadLinks, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onPlayerStateChange(event) {
|
||||||
|
if (event.data == YT.PlayerState.ENDED) {
|
||||||
|
onPlayerReady();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function stopVideo() {
|
||||||
|
player.stopVideo();
|
||||||
|
}
|
||||||
|
function addLink() {
|
||||||
|
var form = $('#add_link_form');
|
||||||
|
$.ajax({
|
||||||
|
type: "post",
|
||||||
|
url: "{% url 'player:add' playlist.get_token %}",
|
||||||
|
data: form.serialize(),
|
||||||
|
async: true,
|
||||||
|
success: loadLinks,
|
||||||
|
})
|
||||||
|
$('#id_url').val('');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
function updateLinks(data) {
|
||||||
|
var links = document.getElementById("links");
|
||||||
|
while (links.firstChild) {
|
||||||
|
links.removeChild(links.firstChild);
|
||||||
|
}
|
||||||
|
var model = document.getElementById('link_template');
|
||||||
|
tracks = [];
|
||||||
|
for (var i=0; i<data.length; i++)
|
||||||
|
{
|
||||||
|
tracks.push(data[i].fields.token);
|
||||||
|
var card = model.cloneNode(true);
|
||||||
|
card.style.display = 'block';
|
||||||
|
card.id = 'link_' + data[i].fields.token;
|
||||||
|
card.getElementsByClassName('link_name')[0].innerHTML = data[i].fields.token;
|
||||||
|
links.appendChild(card);
|
||||||
|
links.append(document.createElement('br'));
|
||||||
|
}
|
||||||
|
if (current_link < 0) {
|
||||||
|
onPlayerReady();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function loadLinks() {
|
||||||
|
$.ajax({
|
||||||
|
type: "get",
|
||||||
|
url: "{% url 'player:list' playlist.get_token %}",
|
||||||
|
async: true,
|
||||||
|
success: updateLinks
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6" style="height:100%; overflow-y:scroll;">
|
||||||
|
<h2>Pistes à venir</h2>
|
||||||
|
<div id="links">
|
||||||
|
{% for link in playlist.link_set.all %}
|
||||||
|
<div class="card bg-secondary text-white" href="{{l.get_absolute_url}}" id="link_{{link.token}}">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row container">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<i class="far fa-play-circle"></i>
|
||||||
|
{{link.token}}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3 btn-group">
|
||||||
|
<a class="btn btn-outline-light" href="#"><i class="fas fa-thumbs-up"></i></a>
|
||||||
|
<a class="btn btn-outline-light" href="#"><i class="fas fa-thumbs-down"></i></a>
|
||||||
|
<a class="btn btn-outline-light" href="#"><i class="fas fa-trash"></i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display:none;" class="card bg-secondary text-white" href="{{l.get_absolute_url}}" id="link_template">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row container">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<i class="far fa-play-circle"></i>
|
||||||
|
<span class="link_name"></span>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3 btn-group">
|
||||||
|
<a class="btn btn-outline-light" href="#"><i class="fas fa-thumbs-up"></i></a>
|
||||||
|
<a class="btn btn-outline-light" href="#"><i class="fas fa-thumbs-down"></i></a>
|
||||||
|
<a class="btn btn-outline-light" href="#"><i class="fas fa-trash"></i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
12
player/urls.py
Normal file
12
player/urls.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
from django.urls import path
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
app_name = "player"
|
||||||
|
urlpatterns = [
|
||||||
|
path('<int:token>', views.playlist, name='playlist'),
|
||||||
|
path('remove/<int:pk>', views.remove_link, name='remove-link'),
|
||||||
|
path('<int:token>/add', views.add_link, name='add'),
|
||||||
|
path('<int:token>/list', views.get_list, name='list'),
|
||||||
|
path('new', views.new_playlist, name='new'),
|
||||||
|
path('', views.all_playlist, name='all'),
|
||||||
|
]
|
|
@ -1,3 +1,65 @@
|
||||||
from django.shortcuts import render
|
import datetime
|
||||||
|
|
||||||
# Create your views here.
|
from django.shortcuts import render, get_object_or_404, redirect
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
from django.core.serializers import serialize
|
||||||
|
|
||||||
|
from player.models import Playlist, Link
|
||||||
|
from player.forms import PlaylistForm, LinkForm
|
||||||
|
|
||||||
|
def new_playlist(request):
|
||||||
|
p = PlaylistForm(request.POST or None)
|
||||||
|
if p.is_valid():
|
||||||
|
playlist = p.save()
|
||||||
|
return redirect(playlist.get_absolute_url())
|
||||||
|
return render(request, 'form.html', {
|
||||||
|
'form':p,
|
||||||
|
'validate':'Créer',
|
||||||
|
'title':'Nouvelle playlist'
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
|
def get_list(request, token):
|
||||||
|
p = get_object_or_404(Playlist, pk=Playlist.reverse_token(token))
|
||||||
|
|
||||||
|
s = serialize('json', p.link_set.all(), fields=('token',))
|
||||||
|
return HttpResponse(s, content_type='application/json')
|
||||||
|
|
||||||
|
|
||||||
|
@csrf_exempt
|
||||||
|
def add_link(request, token):
|
||||||
|
p = get_object_or_404(Playlist, pk=Playlist.reverse_token(token))
|
||||||
|
l = LinkForm(request.POST or None)
|
||||||
|
if l.is_valid():
|
||||||
|
yt_token = l.get_token()
|
||||||
|
link = Link()
|
||||||
|
link.token = yt_token
|
||||||
|
link.playlist = p
|
||||||
|
link.save()
|
||||||
|
return HttpResponse('Ok')
|
||||||
|
return render(request, 'form_inline.html', {
|
||||||
|
'form':l,
|
||||||
|
'validate':'Ajouter'
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def remove_link(request, pk):
|
||||||
|
l = get_object_or_404(Link, pk=pk)
|
||||||
|
l.delete()
|
||||||
|
return HttpResponse('Ok')
|
||||||
|
|
||||||
|
|
||||||
|
def playlist(request, token):
|
||||||
|
p = get_object_or_404(Playlist, pk=Playlist.reverse_token(token))
|
||||||
|
add_link_form = LinkForm()
|
||||||
|
return render(request, 'player/playlist.html', {
|
||||||
|
'playlist':p,
|
||||||
|
'form':add_link_form
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def all_playlist(request):
|
||||||
|
p = Playlist.objects.all().order_by('date')
|
||||||
|
return render(request, 'player/all_list.html', {'lists':p})
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
{% load bootstrap4 %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>LSY{% block title %}{% endblock %}</title>
|
||||||
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
|
||||||
|
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>
|
||||||
|
<script defer src="https://use.fontawesome.com/releases/v5.0.6/js/all.js"></script>
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% include 'navbar.html' %}
|
||||||
|
<main role="main" class="container">
|
||||||
|
<br/>
|
||||||
|
{% bootstrap_messages %}
|
||||||
|
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</main>
|
||||||
|
<hr/>
|
||||||
|
<footer class="footer container text-center text-muted">
|
||||||
|
LSY by Klafyvel
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
14
templates/form.html
Normal file
14
templates/form.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load bootstrap4 %}
|
||||||
|
{% block title %} - {{title}}{%endblock%}
|
||||||
|
{% block content %}
|
||||||
|
<h3>{{title}}</h3>
|
||||||
|
<form action="" method="post" class="form">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form form %}
|
||||||
|
<button class="btn btn-outline-primary"><i class="fa fa-star"></i> {{validate}}</button>
|
||||||
|
</form>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
{% endblock %}
|
6
templates/form_inline.html
Normal file
6
templates/form_inline.html
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{% load bootstrap4 %}
|
||||||
|
<form action="" method="post" class="form" id="{{id}}">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form form %}
|
||||||
|
<button class="btn btn-outline-primary"><i class="fa fa-star"></i> {{validate}}</button>
|
||||||
|
</form>
|
|
@ -0,0 +1,3 @@
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||||
|
<a class="navbar-brand" href="/"><i class="fa fa-users"></i> LSY</a>
|
||||||
|
</nav>
|
Loading…
Reference in a new issue