Marietje 4.1: Addition of Django REST framework, Swagger, Dark mode and updates to Django and Bootstrap

This commit is contained in:
Lars van Rhijn
2023-09-14 19:55:51 +02:00
parent 379ababcc0
commit d1a1be7e2e
124 changed files with 4835 additions and 3490 deletions

View File

@ -1,58 +1,218 @@
{% extends 'base.html' %}
{% extends 'marietje/base.html' %}
{% load static %}
{% block title %}Upload{% endblock %}
{% block content %}
<div class="row">
<div class="col-xs-12 col-sm-10 col-md-6 col-sm-offset-1 col-md-offset-3">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Upload</h3>
</div>
<div class="panel-body ">
<div class="forms-container">
<div class="panel panel-default uploadform">
<div class="panel-body">
<form action="/api/upload" method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="fileupload fileupload-new" data-provides="fileupload">
<span class="btn btn-primary btn-file"><span class="fileupload-new">Select files</span>
<span class="fileupload-exists">Change</span>
<input class="filefield" type="file" name="file[]" accept="audio/*" multiple />
</span>
<br>
</div>
<div class="song-container panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Song</h3>
</div>
<div class="panel-body ">
<div class="form-group">
<input type="text" name="artist[]" class="form-control input-sm artist" placeholder="Artist">
</div>
<div class="form-group">
<input type="text" name="title[]" class="form-control input-sm title" placeholder="Title">
</div>
</div>
</div>
</form>
<div class="container">
<div class="row mt-5">
<div class="col-lg-6 mx-auto">
<div class="card uploadform" id="uploadform">
<form action="{% url "songs:upload" %}" method="POST" enctype="multipart/form-data">
<div class="card-header">
<h3>Upload</h3>
</div>
</div>
<div class="card-body">
{% csrf_token %}
<div class="fileupload fileupload-new" data-provides="fileupload">
<span class="btn btn-primary btn-file">
<span v-if="fileObjects.length === 0">
Select files
</span>
<span v-else>
Change
</span>
<input class="filefield" id="filefield" type="file" name="file[]" accept="audio/*"
multiple @change="set_new_files"/>
</span>
</div>
<div class="songs">
<div v-for="fileObject in fileObjects" class="song-container card mb-3">
<div class="card-header">
<h3><% fileObject.name %></h3>
</div>
<div class="card-body">
<div class="form-group mb-3">
<div v-if="fileObject.artist === '' || fileObject.artist === null" class="alert alert-danger">Please enter an artist for this song.</div>
<input v-if="upload_in_progress || uploaded" type="text" name="artist[]" class="form-control input-sm artist" disabled
placeholder="Artist" v-model="fileObject.artist"/>
<input v-else type="text" name="artist[]" class="form-control input-sm artist"
placeholder="Artist" v-model="fileObject.artist"/>
</div>
<div class="form-group mb-3">
<div v-if="fileObject.title === '' || fileObject.title === null" class="alert alert-danger">Please enter a title for this song.</div>
<input v-if="upload_in_progress || uploaded" type="text" name="title[]" class="form-control input-sm title" disabled
placeholder="Title" v-model="fileObject.title"/>
<input v-else type="text" name="title[]" class="form-control input-sm title"
placeholder="Title" v-model="fileObject.title"/>
</div>
<template v-if="fileObject.upload_finished === true">
<div v-if="fileObject.success === true" class="alert alert-success">Upload finished successfully.</div>
<div v-else class="alert alert-danger"><% fileObject.error_message %></div>
</template>
</div>
</div>
</div>
</div>
<div class="card-footer">
<div class="progress mt-2 mb-3">
<div :class="{ 'progress-bar-animated': (upload_in_progress), 'bg-success': (uploaded && everything_successfully_uploaded), 'bg-danger': (uploaded && !everything_successfully_uploaded) }" class="progress-bar progress-bar-striped" role="progressbar" :style="{ width: (progress_bar_width + '%') }" aria-valuenow="50" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<template v-if="upload_in_progress || uploaded">
<button v-if="uploaded" class="btn btn-primary btn-block w-100" v-on:click="clear">Clear</button>
<button v-else class="btn btn-primary btn-block w-100 disabled">Clear</button>
</template>
<template v-else>
<input v-if="ready_for_upload" id="upload" class="btn btn-primary btn-block w-100" type="submit" value="Upload" v-on:click="upload"/>
<button v-else class="btn btn-primary btn-block w-100 disabled">Upload</button>
</template>
</div>
</form>
</div>
<span class="result-message"></span>
<div class="progress">
<div class="progress-bar progress-bar-info progress-bar-striped" role="progressbar" style="width: 0%;">
</div>
</div>
<button id="upload" class="btn btn-primary btn-block">Upload</button>
</div>
</div>
</div>
</div>
<link rel="stylesheet" href="{% static 'songs/css/upload.css' %}"/>
<script type="module">
import * as id3 from '//unpkg.com/id3js@^2/lib/id3.js';
<link rel="stylesheet" href="{% static 'css/upload.css' %}" />
<script type="text/javascript" src="{% static 'js/upload.js' %}"></script>
<script type="text/javascript" src="{% static 'js/id3.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/jquery.form.min.js' %}"></script>
let upload_vue = new Vue({
el: '#uploadform',
delimiters: ['<%', '%>'],
data: {
files: [],
fileObjects: [],
uploaded: false,
upload_in_progress: false,
},
computed: {
ready_for_upload: function() {
if (this.uploaded !== false || this.upload_in_progress !== false || this.fileObjects.length === 0) {
return false;
} else {
for (let i = 0; i < this.fileObjects.length; i++) {
if (this.fileObjects[i].artist === null || this.fileObjects[i].artist === '' || this.fileObjects[i].title === null || this.fileObjects[i].title === '') {
return false;
}
}
return true;
}
},
everything_successfully_uploaded: function() {
return this.fileObjects.map((fileObject) => {
return fileObject.upload_finished === true && fileObject.success === true;
}).reduce((previousValue, currentValue) => {
return previousValue && currentValue;
}, true);
},
progress_bar_width: function() {
if (this.fileObjects.length === 0) {
return 0;
}
const files_uploaded_successfully = this.fileObjects.map((fileObject) => {
if (fileObject.upload_finished === true && fileObject.success === true) {
return 1;
} else {
return 0;
}
}).reduce((previousValue, currentValue) => {
return previousValue + currentValue;
}, 0);
return Math.round((files_uploaded_successfully / this.fileObjects.length) * 100);
}
},
methods: {
clear(event) {
event.preventDefault();
this.files = [];
this.fileObjects = [];
this.uploaded = false;
this.upload_in_progress = false;
},
upload(event) {
this.upload_in_progress = true;
event.preventDefault();
let allPromises = [];
for (let i = 0; i < this.fileObjects.length; i++) {
const current_file = this.fileObjects[i].file;
const current_artist = this.fileObjects[i].artist;
const current_title = this.fileObjects[i].title;
let data = new FormData();
data.append('file', current_file);
data.append('artist', current_artist);
data.append('title', current_title);
allPromises.push(fetch('/api/v1/songs/upload/', {
method: 'POST',
headers: {
"X-CSRFToken": CSRF_TOKEN,
},
body: data,
}).then(result => {
if (result.status === 200) {
return result.json();
} else {
throw result;
}
}).then(() => {
this.fileObjects[i].success = true;
}).catch(e => {
if (e instanceof Response) {
e.json().then(data => {
this.fileObjects.error_message = data.errorMessage;
this.fileObjects.success = false;
});
} else {
this.fileObjects.error_message = "An exception occurred while uploading this file, please try again.";
this.fileObjects.success = false;
}
}).finally(() => {
this.fileObjects[i].upload_finished = true;
}));
}
Promise.all(allPromises).finally(() => {
this.upload_in_progress = false;
this.uploaded = true;
});
},
async set_new_files(event) {
this.files = event.target.files;
let newFileObjects = [];
for (let i = 0; i < this.files.length; i++) {
try {
const tags = await this.parseSong(this.files[i]);
newFileObjects.push(
{
"file": this.files[i],
"name": this.files[i].name,
"artist": tags.artist,
"title": tags.title,
"success": null,
"error_message": null,
"upload_finished": false,
}
);
} catch {
newFileObjects.push(
{
"file": this.files[i],
"name": this.files[i].name,
"artist": "",
"title": "",
"success": null,
"error_message": null,
"upload_finished": false,
}
)
}
}
this.fileObjects = newFileObjects;
},
parseSong(file) {
return id3.fromFile(file);
}
}
});
</script>
{% endblock %}