Library schema semi-fixed
This commit is contained in:
parent
8f18b9ab9d
commit
f7aaa98000
2
.gitignore
vendored
2
.gitignore
vendored
@ -8,3 +8,5 @@ test.*
|
|||||||
static
|
static
|
||||||
teststore
|
teststore
|
||||||
cache
|
cache
|
||||||
|
local_storage
|
||||||
|
media
|
||||||
4
interface/fields.py
Normal file
4
interface/fields.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
from crispy_forms.layout import Field
|
||||||
|
|
||||||
|
class BulmaFileUpload(Field):
|
||||||
|
template = 'bulma/file_upload.html'
|
||||||
@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 3.2.7 on 2022-11-18 09:54
|
# Generated by Django 3.2.7 on 2022-11-19 10:25
|
||||||
|
|
||||||
import byostorage.user
|
import byostorage.user
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -9,11 +9,13 @@ import interface.models
|
|||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
replaces = [('interface', '0001_initial'), ('interface', '0002_alter_module_name'), ('interface', '0003_alter_ensemble_slug'), ('interface', '0004_alter_project_event_date')]
|
||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
('byostorage', '0004_alter_userstorage_storage'),
|
('byostorage', '0004_alter_userstorage_storage'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
@ -22,7 +24,7 @@ class Migration(migrations.Migration):
|
|||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('name', models.CharField(help_text='Display name', max_length=100)),
|
('name', models.CharField(help_text='Display name', max_length=100)),
|
||||||
('slug', models.SlugField(editable=False, help_text='Short name for the ensemble - used for folders', max_length=100)),
|
('slug', models.SlugField(editable=False, help_text='Short name for the ensemble - used for folders', max_length=100, unique=True)),
|
||||||
('code', models.CharField(default=interface.models.generate_code, help_text='Ensemble registration code', max_length=9)),
|
('code', models.CharField(default=interface.models.generate_code, help_text='Ensemble registration code', max_length=9)),
|
||||||
('passphrase', models.CharField(help_text='Used to register ensembles', max_length=100)),
|
('passphrase', models.CharField(help_text='Used to register ensembles', max_length=100)),
|
||||||
('details', models.TextField(blank=True, help_text='Description of the ensemble (markdown)')),
|
('details', models.TextField(blank=True, help_text='Description of the ensemble (markdown)')),
|
||||||
@ -37,7 +39,7 @@ class Migration(migrations.Migration):
|
|||||||
('name', models.CharField(max_length=100)),
|
('name', models.CharField(max_length=100)),
|
||||||
('description', models.TextField(blank=True, help_text='Markdown format')),
|
('description', models.TextField(blank=True, help_text='Markdown format')),
|
||||||
('active', models.BooleanField(default=True)),
|
('active', models.BooleanField(default=True)),
|
||||||
('event_date', models.DateField(blank=True, null=True)),
|
('event_date', models.DateTimeField(blank=True, null=True)),
|
||||||
('owner', models.CharField(blank=True, max_length=255)),
|
('owner', models.CharField(blank=True, max_length=255)),
|
||||||
('ensemble', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='projects', to='interface.ensemble')),
|
('ensemble', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='projects', to='interface.ensemble')),
|
||||||
],
|
],
|
||||||
@ -73,7 +75,7 @@ class Migration(migrations.Migration):
|
|||||||
name='Module',
|
name='Module',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('name', models.SlugField(max_length=20)),
|
('name', models.SlugField(choices=[('library', 'Library'), ('submissions', 'Submissions')], max_length=20)),
|
||||||
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='modules', to='interface.project')),
|
('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='modules', to='interface.project')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -154,53 +154,4 @@ class WikiPage(models.Model):
|
|||||||
return resolve_url('wiki', project=self.project_id, pk=self.pk)
|
return resolve_url('wiki', project=self.project_id, pk=self.pk)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
"""
|
|
||||||
class Submission(models.Model):
|
|
||||||
project = models.ForeignKey(Project, related_name='old_submissions', on_delete=models.CASCADE)
|
|
||||||
date = models.DateTimeField(auto_now_add=True, )
|
|
||||||
name = models.CharField(max_length=255)
|
|
||||||
instrument = models.CharField(max_length=100, verbose_name="Instrument / Voice")
|
|
||||||
notes = models.TextField(blank=True)
|
|
||||||
complete = models.BooleanField(default=False)
|
|
||||||
url = models.CharField(max_length=512, blank=True)
|
|
||||||
private = models.BooleanField(default=False)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def download_url(self):
|
|
||||||
if not self.complete:
|
|
||||||
raise RuntimeError("Submission not complete")
|
|
||||||
|
|
||||||
if self.private:
|
|
||||||
return self.url
|
|
||||||
|
|
||||||
params = {'Bucket': BUCKET, 'Key': self.url}
|
|
||||||
return s3client.generate_presigned_url('get_object', Params=params, ExpiresIn=3600)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def download_name(self):
|
|
||||||
uri = urlparse(self.download_url)
|
|
||||||
_, name = os.path.split(uri.path)
|
|
||||||
return name or "<Unknown>"
|
|
||||||
|
|
||||||
def key_template(self):
|
|
||||||
return "submissions/{}_{}_{}_${{filename}}".format(
|
|
||||||
timezone.localtime(self.date).isoformat(timespec='seconds').replace(':', '')[:17],
|
|
||||||
slugify(self.name),
|
|
||||||
slugify(self.instrument)
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def short_name(self):
|
|
||||||
_, ext = os.path.splitext(self.download_name)
|
|
||||||
return "{}_{}_{}{}".format(
|
|
||||||
#timezone.localtime(self.date).strftime("%Y%m%d%H%M%S"),
|
|
||||||
slugify(self.name),
|
|
||||||
slugify(self.instrument),
|
|
||||||
self.pk,
|
|
||||||
ext
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.name}: {self.date}"
|
|
||||||
"""
|
|
||||||
28
interface/templates/bulma/file_upload.html
Normal file
28
interface/templates/bulma/file_upload.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{% load crispy_forms_field %}
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<div id="div_id_{{ field.name }}" class="file has-name is-fullwidth">
|
||||||
|
<label class="file-label">
|
||||||
|
{% crispy_field field 'class' 'file-input'%}
|
||||||
|
<span class="file-cta">
|
||||||
|
<span class="file-icon">
|
||||||
|
<i class="fas fa-upload"></i>
|
||||||
|
</span>
|
||||||
|
<span class="file-label">
|
||||||
|
Choose a file…
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<span class="file-name"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const fileInput = document.querySelector('#div_id_{{ field.name }} input[type=file]');
|
||||||
|
fileInput.onchange = () => {
|
||||||
|
if (fileInput.files.length > 0) {
|
||||||
|
const fileName = document.querySelector('#div_id_{{ field.name }} .file-name');
|
||||||
|
fileName.textContent = fileInput.files[0].name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
41
interface/templates/interface/ensemble_list.html
Normal file
41
interface/templates/interface/ensemble_list.html
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
{% extends "interface/project_base.html" %}
|
||||||
|
{% load md2 %}
|
||||||
|
|
||||||
|
{% block page %}
|
||||||
|
<div class="admin-tools is-pulled-right">
|
||||||
|
<a class="button is-link" href="{% url 'register' %}">
|
||||||
|
<span class="icon"><i class="fas fa-plus-circle"></i></span>
|
||||||
|
<span>Register another</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 class="title">My Ensembles</h3>
|
||||||
|
|
||||||
|
<div class="columns is-multiline">
|
||||||
|
{% for ensemble in object_list %}
|
||||||
|
<div class="column is-half">
|
||||||
|
<div class="card">
|
||||||
|
<header class="card-header{% if ensemble.pk == ensemble_id %} has-background-link-light{% endif %}">
|
||||||
|
<a class="card-header-title" href="{% url 'register' %}?code={{ ensemble.code }}">
|
||||||
|
{{ ensemble.name }}
|
||||||
|
</a>
|
||||||
|
<a class="card-header-icon" href="{% url 'ensemble_forget' pk=ensemble.id %}">
|
||||||
|
<span class="delete"></span>
|
||||||
|
</a>
|
||||||
|
</header>
|
||||||
|
<div class="card-content">
|
||||||
|
{% if ensemble.details %}
|
||||||
|
<div class="content">
|
||||||
|
{{ ensemble.details | markdown }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div>
|
||||||
|
{% with projects=ensemble.active_projects.count %}
|
||||||
|
<p>{{ projects }} active project{{ projects|pluralize }}</p>
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
14
interface/templatetags/polyphonic.py
Normal file
14
interface/templatetags/polyphonic.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
from django import template
|
||||||
|
from django.utils import timesince
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
|
||||||
|
def roughtimesince(value):
|
||||||
|
return timesince.timesince(value, depth=1)
|
||||||
|
|
||||||
|
register.filter('roughtimesince', roughtimesince)
|
||||||
|
|
||||||
|
def roughtimeuntil(value):
|
||||||
|
return timesince.timeuntil(value, depth=1)
|
||||||
|
|
||||||
|
register.filter('roughtimeuntil', roughtimeuntil)
|
||||||
@ -1,53 +0,0 @@
|
|||||||
from django.test import TestCase, Client
|
|
||||||
|
|
||||||
from interface import models
|
|
||||||
|
|
||||||
class SubmissionTestCase(TestCase):
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def setUpTestData():
|
|
||||||
e1 = models.Ensemble.objects.create(name='The Be Sharps', code="1234", passphrase='Homer')
|
|
||||||
e1.projects.create(name='Baby on Board')
|
|
||||||
e2 = models.Ensemble.objects.create(name='Lisa and the Bleeding Gums', code="2345", passphrase="Maggie")
|
|
||||||
e2.projects.create(name='Baker St')
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.client = Client()
|
|
||||||
|
|
||||||
def test_submission_upload(self):
|
|
||||||
response = self.client.post('/register', {'code': '12-34', 'passphrase': 'Homer'})
|
|
||||||
self.assertRedirects(response, '/')
|
|
||||||
|
|
||||||
response = self.client.post(f"/projects/1/submission", {'name': 'Ned', 'instrument': 'Harp', 'method': 'upload'})
|
|
||||||
self.assertRedirects(response, '/projects/1/submission/1/upload')
|
|
||||||
|
|
||||||
response = self.client.get(response.url)
|
|
||||||
upload = response.context['upload']
|
|
||||||
self.assertEqual(upload['url'], f"http://localhost:9000/{models.BUCKET}")
|
|
||||||
self.assertRegex(upload['fields']['key'], r'^baby-on-board\/submissions\/[0-9T\-]+_ned_harp_\$\{filename\}$')
|
|
||||||
self.assertEqual(upload['fields']['success_action_redirect'], 'http://testserver/projects/1/submission/1/complete')
|
|
||||||
|
|
||||||
self.assertEqual(models.Submission.objects.count(), 1)
|
|
||||||
self.assertRedirects(self.client.get(f"/projects/1/submission/1/cancel"), '/projects/1')
|
|
||||||
self.assertEqual(models.Submission.objects.count(), 0)
|
|
||||||
|
|
||||||
def test_submission_link(self):
|
|
||||||
response = self.client.post('/register', {'code': '12-34', 'passphrase': 'Homer'})
|
|
||||||
self.assertRedirects(response, '/')
|
|
||||||
|
|
||||||
response = self.client.post(f"/projects/1/submission", {'name': 'Ned', 'instrument': 'Harp', 'method': 'link'})
|
|
||||||
self.assertRedirects(response, '/projects/1/submission/1/link')
|
|
||||||
|
|
||||||
url = 'https://drive.google.com/a/path/to/a/video.mp4#g6e6e4a23'
|
|
||||||
|
|
||||||
response = self.client.post(f"/projects/1/submission/1/link", {'url': url})
|
|
||||||
self.assertRedirects(response, '/projects/1/submission/1')
|
|
||||||
|
|
||||||
response = self.client.get('/projects/1/submission/1')
|
|
||||||
self.assertContains(response, "Thankyou for your submission")
|
|
||||||
|
|
||||||
response = self.client.get('/projects/1')
|
|
||||||
self.assertContains(response, 'Ned')
|
|
||||||
|
|
||||||
s = models.Submission.objects.get(pk=1)
|
|
||||||
self.assertEqual(s.download_url, url)
|
|
||||||
197
library/imslp.py
Normal file
197
library/imslp.py
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
|
||||||
|
from collections import namedtuple
|
||||||
|
|
||||||
|
# taken from https://imslp.org/wiki/IMSLP:Abbreviations_for_Instruments
|
||||||
|
|
||||||
|
ABBREVIATIONS = """
|
||||||
|
score Score
|
||||||
|
acc Accordion
|
||||||
|
afl Alto flute
|
||||||
|
alt Alto (voice) (contralto)
|
||||||
|
arp Arpeggione
|
||||||
|
bag Bagpipe
|
||||||
|
bar Baritone (voice)
|
||||||
|
bass Bass (voice)
|
||||||
|
bbar Bass baritone (voice)
|
||||||
|
bc Continuo (Basso continuo)
|
||||||
|
bcl Bass clarinet
|
||||||
|
bell Bell (Chimes)
|
||||||
|
bfl Bass flute
|
||||||
|
bgtr Bass guitar
|
||||||
|
bjo Banjo
|
||||||
|
bn Bassoon
|
||||||
|
bob Bass oboe (Baritone oboe)
|
||||||
|
br Brass instruments
|
||||||
|
bryt Baryton
|
||||||
|
bstcl Basset clarinet
|
||||||
|
bsthn Basset horn
|
||||||
|
bug Bugle
|
||||||
|
cbcl Contrabass clarinet
|
||||||
|
cbn Contrabassoon
|
||||||
|
cch Children's chorus
|
||||||
|
cel Celesta
|
||||||
|
ch Mixed chorus
|
||||||
|
cimb Cimbalom
|
||||||
|
cit Cittern
|
||||||
|
cl Clarinet
|
||||||
|
clvd Clavichord
|
||||||
|
cm Chalumeau
|
||||||
|
conc Concertina
|
||||||
|
crh Crumhorn
|
||||||
|
crt Cornet
|
||||||
|
crtt Cornett (Zink)
|
||||||
|
cv Child's voice
|
||||||
|
db Double Bass
|
||||||
|
dlcn Dulcian
|
||||||
|
dom Domra
|
||||||
|
dulc Dulcimer
|
||||||
|
egtr Electric guitar
|
||||||
|
eh English horn (Cor anglais)
|
||||||
|
elec Electronic Instruments
|
||||||
|
epf Electric piano
|
||||||
|
eq Equal voices
|
||||||
|
erhu Erhu
|
||||||
|
euph Euphonium
|
||||||
|
fch Female chorus
|
||||||
|
fda Flute d'amore (Tenor flute)
|
||||||
|
fgh Flugelhorn
|
||||||
|
fife Fife
|
||||||
|
fl Flute
|
||||||
|
flag Flageolet
|
||||||
|
ghca Glass harmonica (Bowl organ)
|
||||||
|
gl Glockenspiel
|
||||||
|
gtr Guitar
|
||||||
|
harm Harmonium
|
||||||
|
hca Harmonica (Mouth Organ)
|
||||||
|
heck Heckelphone
|
||||||
|
hn Horn
|
||||||
|
hp Harp
|
||||||
|
hpd Harpsichord
|
||||||
|
kbd Keyboard instrument
|
||||||
|
lute Lute
|
||||||
|
lyre Lyre
|
||||||
|
mand Mandolin
|
||||||
|
mar Marimba
|
||||||
|
mch Male chorus
|
||||||
|
mez Mezzo-soprano
|
||||||
|
mus Musette
|
||||||
|
nar Narrator (Reciter)
|
||||||
|
ob Oboe
|
||||||
|
oca Ocarina
|
||||||
|
oda Oboe d'amore
|
||||||
|
om Ondes Martenot
|
||||||
|
oph Ophicleide
|
||||||
|
orch Orchestra
|
||||||
|
org Organ
|
||||||
|
oud Oud
|
||||||
|
pan Pan flute (Pan-pipes)
|
||||||
|
perc Percussion
|
||||||
|
pf Piano
|
||||||
|
pf3h Piano 3 hands
|
||||||
|
pf4h Piano 4 hands
|
||||||
|
pf5h Piano 5 hands
|
||||||
|
pf6h Piano 6 hands
|
||||||
|
pflh Piano left hand
|
||||||
|
pfped Pedal piano
|
||||||
|
pfrh Piano right hand
|
||||||
|
picc Piccolo
|
||||||
|
pipa Pipa
|
||||||
|
pk Timpani
|
||||||
|
ptpt Piccolo trumpet
|
||||||
|
reb Rebec
|
||||||
|
rec Recorder
|
||||||
|
sar Sarrusophone
|
||||||
|
sax Saxophone
|
||||||
|
sheng Sheng
|
||||||
|
shw Shawm
|
||||||
|
sit Sitar
|
||||||
|
skbt Sackbut
|
||||||
|
sop Soprano (voice)
|
||||||
|
srp Serpent
|
||||||
|
stpt Slide trumpet
|
||||||
|
str String instruments
|
||||||
|
sxh Saxhorn
|
||||||
|
syn Synthesizer
|
||||||
|
tba Tuba
|
||||||
|
tbn Trombone
|
||||||
|
ten Tenor
|
||||||
|
thrm Theremin
|
||||||
|
timp Timpani
|
||||||
|
tpt Trumpet
|
||||||
|
uch Unison chorus
|
||||||
|
uke Ukelele (Ukulele)
|
||||||
|
v Voice (solo)
|
||||||
|
va Viola
|
||||||
|
vap Viola pomposa
|
||||||
|
vc Cello
|
||||||
|
vda Viola d'amore
|
||||||
|
vib Vibraphone
|
||||||
|
vie Vielle (Hurdy-Gurdy)
|
||||||
|
viol Viol (Viola da gamba)
|
||||||
|
vlne Violone
|
||||||
|
vn Violin
|
||||||
|
vuv Vuvuzela
|
||||||
|
vv Voices (multiple soloists)
|
||||||
|
wag Wagner tuba
|
||||||
|
ww Woodwind instruments
|
||||||
|
xiao Xiao
|
||||||
|
xyl Xylophone
|
||||||
|
zith Zither
|
||||||
|
"""
|
||||||
|
|
||||||
|
INSTRUMENTS = []
|
||||||
|
for line in ABBREVIATIONS.split('\n'):
|
||||||
|
parts = line.strip().split(maxsplit=1)
|
||||||
|
if len(parts) < 2: continue
|
||||||
|
name, _, _ = parts[1].partition('(')
|
||||||
|
INSTRUMENTS.append((parts[0], name))
|
||||||
|
|
||||||
|
INSTRUMENT_LOOKUP = dict(INSTRUMENTS)
|
||||||
|
TAG_LOOKUP = dict( ( (x[1].lower(), x[0]) for x in INSTRUMENTS ) )
|
||||||
|
|
||||||
|
class Instrument(namedtuple('Instrument', ('name', 'variant'), defaults=[None])):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_tag(cls, tag):
|
||||||
|
"""
|
||||||
|
>>> Instrument.from_tag('vn-1')
|
||||||
|
Instrument(name='Violin', variant='1')
|
||||||
|
>>> Instrument.from_tag('db')
|
||||||
|
Instrument(name='Double Bass', variant=None)
|
||||||
|
>>> Instrument.from_tag('Jaws Harp')
|
||||||
|
Instrument(name='Jaws Harp', variant=None)
|
||||||
|
"""
|
||||||
|
abbr, _, variant = tag.partition('-')
|
||||||
|
name = INSTRUMENT_LOOKUP.get(abbr.lower(), abbr)
|
||||||
|
|
||||||
|
if variant:
|
||||||
|
return cls(name, variant)
|
||||||
|
return cls(name, None)
|
||||||
|
|
||||||
|
|
||||||
|
def abbreviate(self):
|
||||||
|
"""
|
||||||
|
>>> Instrument('Violin', 1).abbreviate()
|
||||||
|
'vn-1'
|
||||||
|
>>> Instrument('Double Bass').abbreviate()
|
||||||
|
'db'
|
||||||
|
"""
|
||||||
|
tag = TAG_LOOKUP.get(self.name.lower())
|
||||||
|
if self.variant:
|
||||||
|
tag = f"{tag}-{self.variant}"
|
||||||
|
return tag
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""
|
||||||
|
>>> str(Instrument('Violin', 1))
|
||||||
|
'Violin 1'
|
||||||
|
>>> str(Instrument('Double Bass'))
|
||||||
|
'Double Bass'
|
||||||
|
"""
|
||||||
|
if self.variant:
|
||||||
|
return f"{self.name} {self.variant}"
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import doctest
|
||||||
|
print(doctest.testmod())
|
||||||
@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 3.2.7 on 2022-11-18 09:54
|
# Generated by Django 3.2.7 on 2022-11-19 10:24
|
||||||
|
|
||||||
import byostorage.user
|
import byostorage.user
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
@ -9,12 +9,14 @@ import library.models
|
|||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
replaces = [('library', '0001_initial'), ('library', '0002_auto_20221118_2208'), ('library', '0003_work_composer'), ('library', '0004_auto_20221118_2223'), ('library', '0005_auto_20221118_2253'), ('library', '0006_auto_20221119_2121')]
|
||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
('byostorage', '0004_alter_userstorage_storage'),
|
('byostorage', '0004_alter_userstorage_storage'),
|
||||||
('interface', '0001_initial'),
|
('interface', '0001_initial'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
@ -23,21 +25,11 @@ class Migration(migrations.Migration):
|
|||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('name', models.CharField(help_text='Name of the collection', max_length=255)),
|
('name', models.CharField(help_text='Name of the collection', max_length=255)),
|
||||||
('prefix', models.SlugField(default='default', max_length=30)),
|
('prefix', models.SlugField(default='default', help_text='Folder to store works in', max_length=30)),
|
||||||
('location', models.CharField(help_text='Physical location (institution, town...)', max_length=100)),
|
('location', models.CharField(blank=True, help_text='Physical location (institution, town...)', max_length=100)),
|
||||||
('notes', models.TextField(blank=True, help_text='Publicly visible notes about collection and loans policy')),
|
('notes', models.TextField(blank=True, help_text='Publicly visible notes about collection and loans policy (markdown format)')),
|
||||||
('administrators', models.ManyToManyField(help_text='Administrators for this collection', related_name='collections', to=settings.AUTH_USER_MODEL)),
|
('administrators', models.ManyToManyField(help_text='Administrators for this collection', related_name='collections', to=settings.AUTH_USER_MODEL)),
|
||||||
('storage', models.ForeignKey(blank=True, help_text='Storage for documents', null=True, on_delete=django.db.models.deletion.CASCADE, to='byostorage.userstorage')),
|
('storage', models.ForeignKey(blank=True, help_text='User storage for documents', null=True, on_delete=django.db.models.deletion.CASCADE, to='byostorage.userstorage')),
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Document',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('doctype', models.PositiveSmallIntegerField(choices=[(1, 'PDF'), (2, 'Audio'), (3, 'Video'), (4, 'Source')], default=1)),
|
|
||||||
('upload', models.FileField(storage=byostorage.user.BYOStorage(), upload_to=library.models.doc_upload_filename)),
|
|
||||||
('created', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('version', models.CharField(blank=True, max_length=30)),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
@ -59,45 +51,21 @@ class Migration(migrations.Migration):
|
|||||||
name='Work',
|
name='Work',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('slug', models.SlugField(editable=False, max_length=100)),
|
('slug', models.SlugField(editable=False, help_text='Used as folder name', max_length=100)),
|
||||||
('name', models.CharField(max_length=255)),
|
('name', models.CharField(help_text='Original name of the work', max_length=255)),
|
||||||
('edition', models.CharField(blank=True, help_text='Edition details', max_length=255)),
|
('original_parts', models.JSONField(blank=True, default=dict, help_text='Original printed parts (IMSLP format)')),
|
||||||
('composer', models.CharField(blank=True, max_length=255)),
|
|
||||||
('orchestration', models.CharField(blank=True, help_text='IMDB format instrumentation', max_length=255)),
|
|
||||||
('original_parts', models.JSONField(blank=True, help_text='Original printed parts', null=True)),
|
|
||||||
('code', models.CharField(blank=True, help_text='Collection specific code or number', max_length=100)),
|
('code', models.CharField(blank=True, help_text='Collection specific code or number', max_length=100)),
|
||||||
('licence', models.PositiveSmallIntegerField(choices=[(2, 'Public Domain'), (4, 'Copyright Expired'), (6, 'Copyrighted'), (10, 'Internal use only')], default=6, help_text='Copyright status')),
|
('licence', models.PositiveSmallIntegerField(choices=[(2, 'Public Domain'), (4, 'Copyright Expired'), (6, 'Copyrighted'), (10, 'Internal use only')], default=6, help_text='Copyright status')),
|
||||||
('max_projects', models.IntegerField(default=1, help_text='How many projects can this work be attached to')),
|
('max_projects', models.IntegerField(default=1, help_text='How many active projects can this work be attached to')),
|
||||||
('running_time', models.DurationField(blank=True, help_text='Running time in seconds', null=True)),
|
('running_time', models.DurationField(blank=True, help_text='Running time in seconds', null=True)),
|
||||||
('notes', models.TextField(blank=True)),
|
('notes', models.TextField(blank=True)),
|
||||||
('collection', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='works', to='library.collection')),
|
('collection', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='works', to='library.collection')),
|
||||||
('parent', models.ForeignKey(blank=True, help_text='Arrangement of another work or part of an anthology', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='related_works', to='library.work')),
|
('parent', models.ForeignKey(blank=True, help_text='Arrangement of another work or part of an anthology', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='related_works', to='library.work')),
|
||||||
('projects', models.ManyToManyField(help_text='Current usage', related_name='works', through='library.ProjectItem', to='interface.Project')),
|
('projects', models.ManyToManyField(help_text='Current usage', related_name='works', through='library.ProjectItem', to='interface.Project')),
|
||||||
|
('composer', models.CharField(blank=True, help_text='Surname, First Name/Initials', max_length=255)),
|
||||||
|
('edition', models.CharField(blank=True, help_text='Edition details to distinguish multiple versions', max_length=255)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
|
||||||
name='WorkMeta',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.SlugField(choices=[('tag', 'Tag'), ('arr', 'Arranger'), ('lyrics', 'Lyracist'), ('genre', 'Genre'), ('style', 'Style')], max_length=20)),
|
|
||||||
('value', models.CharField(max_length=255)),
|
|
||||||
('work', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='meta_info', to='library.work')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Section',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('tag', models.SlugField(max_length=20)),
|
|
||||||
('start', models.SmallIntegerField(blank=True, null=True)),
|
|
||||||
('end', models.SmallIntegerField(blank=True, null=True)),
|
|
||||||
('notes', models.TextField(blank=True)),
|
|
||||||
('doc', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sections', to='library.document')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'ordering': ['doc', 'start', 'pk'],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='projectitem',
|
model_name='projectitem',
|
||||||
name='work',
|
name='work',
|
||||||
@ -115,9 +83,37 @@ class Migration(migrations.Migration):
|
|||||||
'verbose_name_plural': 'Ensemble access',
|
'verbose_name_plural': 'Ensemble access',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.CreateModel(
|
||||||
model_name='document',
|
name='Document',
|
||||||
name='work',
|
fields=[
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='docs', to='library.work'),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('doctype', models.PositiveSmallIntegerField(choices=[(1, 'PDF'), (2, 'Audio'), (3, 'Video'), (4, 'Source')], default=1)),
|
||||||
|
('upload', models.FileField(storage=byostorage.user.BYOStorage(), upload_to=library.models.doc_upload_filename)),
|
||||||
|
('created', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('version', models.CharField(blank=True, max_length=30)),
|
||||||
|
('work', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='docs', to='library.work')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='WorkMeta',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.SlugField(choices=[('tag', 'Tag'), ('arr', 'Arranger'), ('lyrics', 'Lyracist'), ('genre', 'Genre'), ('style', 'Style'), ('orchestration', 'Orchestration')], max_length=20)),
|
||||||
|
('value', models.CharField(max_length=255)),
|
||||||
|
('work', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='meta_info', to='library.work')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Section',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('tag', models.CharField(max_length=50)),
|
||||||
|
('start', models.SmallIntegerField(blank=True, null=True)),
|
||||||
|
('end', models.SmallIntegerField(blank=True, null=True)),
|
||||||
|
('doc', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sections', to='library.document')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ['doc', 'start', 'pk'],
|
||||||
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@ -90,7 +90,7 @@ class ProjectItem(models.Model):
|
|||||||
returned = models.DateTimeField(null=True, blank=True)
|
returned = models.DateTimeField(null=True, blank=True)
|
||||||
approved_by = models.ForeignKey('auth.User', on_delete=models.CASCADE)
|
approved_by = models.ForeignKey('auth.User', on_delete=models.CASCADE)
|
||||||
order = models.SmallIntegerField(default=0)
|
order = models.SmallIntegerField(default=0)
|
||||||
section = models.SlugField
|
section = models.CharField(max_length=100, blank=True)
|
||||||
#version = models.CharField(max_length=30, blank=True, help_text="Limited to specific version tag")
|
#version = models.CharField(max_length=30, blank=True, help_text="Limited to specific version tag")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|||||||
26
library/templates/library/collection_list.html
Normal file
26
library/templates/library/collection_list.html
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{% extends "interface/project_base.html" %}
|
||||||
|
|
||||||
|
{% block page %}
|
||||||
|
<h3 class="title">Library collections for {{ request.user }}</h3>
|
||||||
|
|
||||||
|
<div class="columns is-multiline">
|
||||||
|
{% for collection in object_list %}
|
||||||
|
<div class="column is-half">
|
||||||
|
<div class="card">
|
||||||
|
<header class="card-header">
|
||||||
|
<a class="" href="{% url 'collection_work_list' pk=collection.id %}">
|
||||||
|
<p class="card-header-title">{{ collection.name }}</p>
|
||||||
|
</a>
|
||||||
|
</header>
|
||||||
|
<div class="card-content">
|
||||||
|
<p>{{ collection.location }}, {{ collection.works.count }} items.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<small>{{ ensemble.ensemble_code }}</small>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@ -31,8 +31,7 @@ ALLOWED_HOSTS = ['localhost']
|
|||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
POLYPHONIC_MODULES = [
|
POLYPHONIC_MODULES = [
|
||||||
'library',
|
'library'
|
||||||
'submissions'
|
|
||||||
]
|
]
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
|
|||||||
@ -19,7 +19,7 @@ from django.urls import path, re_path, include
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path('', include('interface.urls')),
|
path('', include('interface.urls')),
|
||||||
path('', include('submissions.urls')),
|
#path('', include('submissions.urls')),
|
||||||
path('', include('library.urls')),
|
path('', include('library.urls')),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user