Minor UI Tweaks
This commit is contained in:
parent
04bf1ab1f3
commit
78789c02ed
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,6 +1,7 @@
|
|||||||
__pycache__
|
__pycache__
|
||||||
*.pyc
|
*.pyc
|
||||||
*.sqlite3
|
*.sqlite3
|
||||||
|
*.swp
|
||||||
credentials.json
|
credentials.json
|
||||||
credentials
|
credentials
|
||||||
local_settings.py
|
local_settings.py
|
||||||
|
|||||||
19
TODO.md
Normal file
19
TODO.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
## Polyphonic TODO
|
||||||
|
|
||||||
|
## Core interface
|
||||||
|
|
||||||
|
* Shift from crispy forms to native component templates
|
||||||
|
* Make long running calls async (Django 5)
|
||||||
|
* Deprecate Django 4 portions
|
||||||
|
|
||||||
|
### Library App
|
||||||
|
|
||||||
|
* Remove music tags and replace with strings vn1 -> 'Violin 1'
|
||||||
|
* GDrive selector
|
||||||
|
* Move upload to modal from 'Upload' button
|
||||||
|
* Tagging app - migrate to AlpineJS
|
||||||
|
* Allow other tags (movements, sections, pieces)
|
||||||
|
|
||||||
|
### Submissions App
|
||||||
|
|
||||||
|
* None currently pending
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.0.6 on 2024-06-18 05:57
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('library', '0014_auto_20230223_1422'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='collection',
|
||||||
|
name='settings',
|
||||||
|
field=models.JSONField(default=dict, help_text='Storage specific settings'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='document',
|
||||||
|
name='doctype',
|
||||||
|
field=models.PositiveSmallIntegerField(choices=[(1, 'PDF'), (2, 'Audio'), (3, 'Video'), (4, 'Misc')], default=1),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -86,14 +86,14 @@ class ProjectItem(models.Model):
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"<{self.project_id}:{slugify(self.work.name)}>"
|
return f"<{self.project_id}:{slugify(self.work.name)}>"
|
||||||
|
|
||||||
class Collection(models.Model):
|
class Collection(models.Model):
|
||||||
"""
|
"""
|
||||||
A logical collection of works, typically owned by an organisation or person (physical or virtual)
|
A logical collection of works, typically owned by an organisation or person (physical or virtual)
|
||||||
"""
|
"""
|
||||||
name = models.CharField(max_length=255,
|
name = models.CharField(max_length=255,
|
||||||
help_text="Name of the collection")
|
help_text="Name of the collection")
|
||||||
prefix = models.SlugField(max_length=30, default="default",
|
prefix = models.CharField(max_length=255, default="default",
|
||||||
help_text="Folder to store works in")
|
help_text="Folder to store works in")
|
||||||
administrators = models.ManyToManyField('auth.User', related_name="collections",
|
administrators = models.ManyToManyField('auth.User', related_name="collections",
|
||||||
help_text="Administrators for this collection")
|
help_text="Administrators for this collection")
|
||||||
@ -103,6 +103,8 @@ class Collection(models.Model):
|
|||||||
help_text="User storage for documents")
|
help_text="User storage for documents")
|
||||||
notes = models.TextField(blank=True,
|
notes = models.TextField(blank=True,
|
||||||
help_text="Publicly visible notes about collection and loans policy (markdown format)")
|
help_text="Publicly visible notes about collection and loans policy (markdown format)")
|
||||||
|
settings = models.JSONField(default=dict, blank=True,
|
||||||
|
help_text="Storage specific settings")
|
||||||
nonce = models.SmallIntegerField(default=1,
|
nonce = models.SmallIntegerField(default=1,
|
||||||
help_text="Increment this to reset the authentication links")
|
help_text="Increment this to reset the authentication links")
|
||||||
|
|
||||||
@ -405,4 +407,4 @@ class Section(models.Model):
|
|||||||
return "all"
|
return "all"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|||||||
@ -139,7 +139,7 @@
|
|||||||
crossorigin="anonymous"></script>
|
crossorigin="anonymous"></script>
|
||||||
{{ json_data|json_script:"data" }}
|
{{ json_data|json_script:"data" }}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
let url = "{{ document.upload.url|safe }}";
|
let url = "{% url 'document_download' collection.pk document.pk %}";
|
||||||
|
|
||||||
// Loaded via <script> tag, create shortcut to access PDF.js exports.
|
// Loaded via <script> tag, create shortcut to access PDF.js exports.
|
||||||
let pdfjsLib = window['pdfjs-dist/build/pdf'];
|
let pdfjsLib = window['pdfjs-dist/build/pdf'];
|
||||||
@ -447,4 +447,4 @@
|
|||||||
window.addEventListener('beforeunload', checkSaved);
|
window.addEventListener('beforeunload', checkSaved);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
12
app/library/templates/library/storage_browser.html
Normal file
12
app/library/templates/library/storage_browser.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{% load path_filters %}
|
||||||
|
<div>
|
||||||
|
<h3>Listing for {{ collection }}</h3>
|
||||||
|
<ul>
|
||||||
|
{% for item in folders %}
|
||||||
|
<li><a href="{% url 'storage_browser_folder' collection=collection.pk folder=item %}">{{ item|basename }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
{% for item in files %}
|
||||||
|
<li>{{ item|basename }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
@ -25,46 +25,46 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="subtitle">{% firstof work.composer "Unattributed" %}{% if work.edition %} - {{ work.edition }}{% endif %}</p>
|
<p class="subtitle">{% firstof work.composer "Unattributed" %}{% if work.edition %} - {{ work.edition }}{% endif %}</p>
|
||||||
<section class="block">
|
|
||||||
<p class="block">{{ work.notes }}</p>
|
|
||||||
|
|
||||||
<p class="block">
|
|
||||||
<table class="table">
|
|
||||||
<tr>
|
|
||||||
<th>Location:</th><td><a href="{% url 'collection_work_list' work.collection.pk %}">{{ work.collection }}</a> [{{ work.identifier }}]</td>
|
|
||||||
<th>Orchestration:</th><td>{{ work.orchestration }}</td>
|
|
||||||
</tr><tr>
|
|
||||||
<th>Running time:</th><td>{% firstof work.duration 'Unknown' %}</td>
|
|
||||||
<th>Licence:</th><td>{{ work.get_licence_display }}</td>
|
|
||||||
</tr><tr>
|
|
||||||
<td colspan="4">
|
|
||||||
{% for meta in work.meta %}
|
|
||||||
<a href="{% url 'collection_work_list' work.collection.pk %}?filter={{ meta.name}}:{{ meta.value }}" class="tag" >
|
|
||||||
{{ meta.get_name_display }}:
|
|
||||||
{{ meta.value }}
|
|
||||||
</a>
|
|
||||||
{% endfor %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
{% if work.parent %}
|
|
||||||
<p>From <a href="{% url 'work_detail' work.parent.collection.pk work.parent.pk %}">{{ work.parent.name }} - {{ work.parent.composer }}</a>
|
|
||||||
</p>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if work.related_works.count %}
|
|
||||||
<h3>Related</h3>
|
|
||||||
<ul>
|
|
||||||
{% for related in work.related_works.all %}
|
|
||||||
<li><a href="{% url 'work_detail' related.collection.pk related.pk %}">{{ related.name }} - {{ related.composer }}</a></li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
</section>
|
|
||||||
<section class="block">
|
<section class="block">
|
||||||
|
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
|
<div class="column is-half">
|
||||||
|
<p class="block">{{ work.notes }}</p>
|
||||||
|
|
||||||
|
<p class="block">
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<th>Location:</th><td><a href="{% url 'collection_work_list' work.collection.pk %}">{{ work.collection }}</a> [{{ work.identifier }}]</td>
|
||||||
|
<th>Orchestration:</th><td>{{ work.orchestration }}</td>
|
||||||
|
</tr><tr>
|
||||||
|
<th>Running time:</th><td>{% firstof work.duration 'Unknown' %}</td>
|
||||||
|
<th>Licence:</th><td>{{ work.get_licence_display }}</td>
|
||||||
|
</tr><tr>
|
||||||
|
<td colspan="4">
|
||||||
|
{% for meta in work.meta %}
|
||||||
|
<a href="{% url 'collection_work_list' work.collection.pk %}?filter={{ meta.name}}:{{ meta.value }}" class="tag" >
|
||||||
|
{{ meta.get_name_display }}:
|
||||||
|
{{ meta.value }}
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% if work.parent %}
|
||||||
|
<p>From <a href="{% url 'work_detail' work.parent.collection.pk work.parent.pk %}">{{ work.parent.name }} - {{ work.parent.composer }}</a>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if work.related_works.count %}
|
||||||
|
<h3>Related</h3>
|
||||||
|
<ul>
|
||||||
|
{% for related in work.related_works.all %}
|
||||||
|
<li><a href="{% url 'work_detail' related.collection.pk related.pk %}">{{ related.name }} - {{ related.composer }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
<div class="column is-half">
|
<div class="column is-half">
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<h4 class="subtitle is-size-4">
|
<h4 class="subtitle is-size-4">
|
||||||
@ -79,8 +79,6 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="column is-half">
|
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<h4 class="subtitle is-size-4">
|
<h4 class="subtitle is-size-4">
|
||||||
<span class="icon"><i class="fas fa-print"></i></span>
|
<span class="icon"><i class="fas fa-print"></i></span>
|
||||||
@ -206,4 +204,4 @@
|
|||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
<h3 class="subtitle">{{ work.name }}</h3>
|
<h3 class="subtitle">{{ work.name }}</h3>
|
||||||
<div class="block">
|
<div class="block tags">
|
||||||
{% for tag, name in work.digital_parts %}
|
{% for tag, name in work.digital_parts %}
|
||||||
<a class="tag is-info" href="{% url 'work_download' work.collection_id work.pk %}?tag={{ tag }}">{{ name }}</a>
|
<a class="tag is-info" href="{% url 'work_download' work.collection_id work.pk %}?tag={{ tag }}" target="polyphonic_parts">{{ name }}</a>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!--
|
||||||
<h3 class="subtitle">Files</h3>
|
<h3 class="subtitle">Files</h3>
|
||||||
<table class="table is-narrow is-fullwidth">
|
<table class="table is-narrow is-fullwidth">
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -19,6 +20,7 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
-->
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<a href="{% url 'work_detail' work.collection_id work.pk %}">More details...</a>
|
<a href="{% url 'work_detail' work.collection_id work.pk %}">More details...</a>
|
||||||
|
|||||||
@ -35,10 +35,13 @@ urlpatterns = [
|
|||||||
path('collections/<int:collection>/docs/<int:pk>/annotate', views.DocumentAnnotateView.as_view(), name="document_annotate"),
|
path('collections/<int:collection>/docs/<int:pk>/annotate', views.DocumentAnnotateView.as_view(), name="document_annotate"),
|
||||||
|
|
||||||
path('collections/<int:collection>/download/<int:section>/<str:filename>', views.PartDownloadView.as_view(), name="part_download"),
|
path('collections/<int:collection>/download/<int:section>/<str:filename>', views.PartDownloadView.as_view(), name="part_download"),
|
||||||
|
path('collections/<int:collection>/browse', views.StorageBrowserView.as_view(), name="storage_browser"),
|
||||||
|
path('collections/<int:collection>/browse/<path:folder>', views.StorageBrowserView.as_view(), name="storage_browser_folder"),
|
||||||
|
|
||||||
#path('api/', include(router.urls))
|
#path('api/', include(router.urls))
|
||||||
path('api/collections/<int:pk>', api.CollectionExportView.as_view(), name="collection_export"),
|
path('api/collections/<int:pk>', api.CollectionExportView.as_view(), name="collection_export"),
|
||||||
path('api/collections/<int:collection>/works/<int:pk>', api.WorkExportView.as_view(), name="work_export"),
|
path('api/collections/<int:collection>/works/<int:pk>', api.WorkExportView.as_view(), name="work_export"),
|
||||||
path('api/collections/<int:collection>/import', api.WorkImportView.as_view(), name="work_import"),
|
path('api/collections/<int:collection>/import', api.WorkImportView.as_view(), name="work_import"),
|
||||||
path('api/collections/<int:collection>/bulk_import', api.CollectionImportView.as_view(), name="collection_import"),
|
path('api/collections/<int:collection>/bulk_import', api.CollectionImportView.as_view(), name="collection_import"),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
from django.shortcuts import get_object_or_404, redirect, resolve_url
|
from django.shortcuts import get_object_or_404, redirect, resolve_url
|
||||||
|
from django.views.generic import TemplateView
|
||||||
from django.views.generic.detail import DetailView, SingleObjectMixin, View
|
from django.views.generic.detail import DetailView, SingleObjectMixin, View
|
||||||
from django.views.generic.list import ListView, MultipleObjectMixin
|
from django.views.generic.list import ListView, MultipleObjectMixin
|
||||||
from django.views.generic.edit import CreateView, FormView, UpdateView, DeleteView
|
from django.views.generic.edit import CreateView, FormView, UpdateView, DeleteView
|
||||||
@ -18,6 +19,7 @@ import re
|
|||||||
|
|
||||||
from interface.views import EnsembleMixin, ProjectMixin, AuthorizedResourceMixin
|
from interface.views import EnsembleMixin, ProjectMixin, AuthorizedResourceMixin
|
||||||
from interface.models import Project
|
from interface.models import Project
|
||||||
|
from interface.utils import signed_url
|
||||||
from library.models import Collection, Work, Document, Section
|
from library.models import Collection, Work, Document, Section
|
||||||
from library.music_tags import MUSIC_TAGS, MusicTag, auto_tag
|
from library.music_tags import MUSIC_TAGS, MusicTag, auto_tag
|
||||||
from library import forms, models
|
from library import forms, models
|
||||||
@ -416,9 +418,11 @@ class DocumentDownloadView(DocumentMixin, SingleObjectMixin, View):
|
|||||||
self.args = args
|
self.args = args
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
|
|
||||||
#response = FileResponse(self.object.upload, content_type="application/pdf")
|
if request.GET.get('method') == 'direct':
|
||||||
#return response
|
return redirect(self.object.upload.url)
|
||||||
return redirect(self.object.upload.url)
|
|
||||||
|
response = FileResponse(self.object.upload.open('rb'), content_type="application/pdf")
|
||||||
|
return response
|
||||||
|
|
||||||
class DocumentAnnotateView(DocumentMixin, DetailView):
|
class DocumentAnnotateView(DocumentMixin, DetailView):
|
||||||
template_name = 'library/document_annotate.html'
|
template_name = 'library/document_annotate.html'
|
||||||
@ -446,6 +450,8 @@ class DocumentAnnotateView(DocumentMixin, DetailView):
|
|||||||
for part in data['document'].sections.all():
|
for part in data['document'].sections.all():
|
||||||
pages.append((part.tag, part.start, part.end))
|
pages.append((part.tag, part.start, part.end))
|
||||||
|
|
||||||
|
data['url'] = signed_url('document_download', collection=data['collection'].pk, pk=data['document'].pk)
|
||||||
|
|
||||||
data['json_data'] = {'pageTags': pages, 'instruments': dict(MUSIC_TAGS)}
|
data['json_data'] = {'pageTags': pages, 'instruments': dict(MUSIC_TAGS)}
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@ -479,4 +485,15 @@ class PartDownloadView(CollectionMixin, SingleObjectMixin, View):
|
|||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
|
|
||||||
return Section.objects.filter(doc__work__collection=self.collection).select_related('doc', 'doc__work').get(pk=self.kwargs['section'])
|
return Section.objects.filter(doc__work__collection=self.collection).select_related('doc', 'doc__work').get(pk=self.kwargs['section'])
|
||||||
|
|
||||||
|
|
||||||
|
class StorageBrowserView(CollectionMixin, TemplateView):
|
||||||
|
template_name = "library/storage_browser.html"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
data = super().get_context_data(**kwargs)
|
||||||
|
folder = self.kwargs.get('folder') or data['collection'].prefix
|
||||||
|
data['folders'], data['files'] = data['collection'].storage.instance().listdir(folder)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user