Tweeking section handling
This commit is contained in:
@@ -0,0 +1,379 @@
|
||||
from django.shortcuts import render, redirect, resolve_url
|
||||
from django.views.generic.detail import DetailView, SingleObjectMixin, View
|
||||
from django.views.generic.list import ListView, MultipleObjectMixin
|
||||
from django.views.generic.edit import CreateView, FormView, UpdateView, DeleteView
|
||||
from django.http import FileResponse, HttpResponse, JsonResponse
|
||||
from django.db import IntegrityError
|
||||
from django.db.models import Q, Count, Sum
|
||||
from django.utils.timezone import now
|
||||
from django.urls import reverse
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
import json
|
||||
import os.path
|
||||
import re
|
||||
|
||||
from interface.views import EnsembleMixin, ProjectMixin
|
||||
from interface.models import Project
|
||||
from library.models import Collection, Work, Document, Section
|
||||
from library.imslp import INSTRUMENT_TAGS, INSTRUMENTS
|
||||
from library import forms, models
|
||||
from library.pdf_utils import extract_pages, extract_and_concat
|
||||
|
||||
class ProjectItemListView(ProjectMixin, ListView):
|
||||
template_name = "library/item_list.html"
|
||||
model = models.ProjectItem
|
||||
|
||||
def post(self, request, **kwargs):
|
||||
|
||||
project = self.get_project()
|
||||
|
||||
project_works = project.works.all()
|
||||
|
||||
instruments = request.POST.getlist('instruments')
|
||||
works = request.POST.getlist('works')
|
||||
|
||||
self.request.session['part'] = request.POST.get('part', '')
|
||||
self.request.session['instrument'] = request.POST.get('instrument')
|
||||
|
||||
valid_pks = [ x.pk for x in project_works ]
|
||||
|
||||
sections = []
|
||||
|
||||
for i, pk in enumerate(works):
|
||||
|
||||
if int(pk) not in valid_pks:
|
||||
raise Exception(f"Not a valid work pk: {pk}")
|
||||
|
||||
tag = instruments[i]
|
||||
if tag == '-':
|
||||
continue
|
||||
|
||||
part = Section.objects.filter(tag=tag, doc__work=pk).select_related('doc').get()
|
||||
sections.append((part.doc.upload.path, part.doc.work.name, part.start, part.end, 1))
|
||||
|
||||
result = extract_and_concat(sections)
|
||||
|
||||
download_name = f'{project.name}.pdf'
|
||||
|
||||
response = FileResponse(result, content_type="application/pdf")
|
||||
response['Content-Disposition'] = f'inline; filename="{download_name}"'
|
||||
return response
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
return super(ProjectItemListView, self).get_queryset().select_related('project', 'work')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
data = super(ProjectItemListView, self).get_context_data(**kwargs)
|
||||
data['instruments'] = INSTRUMENTS
|
||||
data['instrument'] = self.request.session.get('instrument', 'Score')
|
||||
data['part'] = self.request.session.get('part', '0')
|
||||
data['running_time'] = self.get_queryset().aggregate(Sum('work__running_time'))['work__running_time__sum']
|
||||
#if running_time:
|
||||
# data['running_time'] = "{0:d}:{1:02d}".format(int(running_time / 60), running_time % 60)
|
||||
#else:
|
||||
# data['running_time'] = "-:--"
|
||||
return data
|
||||
|
||||
class ProjectItemManageView(ProjectMixin, ListView):
|
||||
template_name = "library/item_list_manage.html"
|
||||
model = models.ProjectItem
|
||||
|
||||
def post(self, request, **kwargs):
|
||||
self.request = request
|
||||
self.kwargs = kwargs
|
||||
|
||||
data = json.loads(request.body)
|
||||
|
||||
q = self.get_queryset()
|
||||
for pk, order in data.items():
|
||||
order = int(order)
|
||||
if order == -1:
|
||||
q.filter(pk=pk).delete()
|
||||
else:
|
||||
i = q.filter(pk=pk).update(order=order)
|
||||
|
||||
return HttpResponse(status=204)
|
||||
|
||||
def get_queryset(self):
|
||||
return super(ProjectItemManageView, self).get_queryset().select_related('project', 'work')
|
||||
|
||||
class ProjectItemAddView(ProjectMixin, UpdateView):
|
||||
form_class = forms.PlaylistAddForm
|
||||
template_name = "interface/default_form.html"
|
||||
|
||||
def get_success_url(self):
|
||||
return resolve_url('item_list_manage', project=self.kwargs['project'])
|
||||
|
||||
def get_object(self):
|
||||
return self.get_project()
|
||||
|
||||
class CollectionListView(EnsembleMixin, ListView):
|
||||
|
||||
def get_queryset(self):
|
||||
return Collection.objects.filter(administrators=self.request.user)
|
||||
|
||||
|
||||
class WorkListView(EnsembleMixin, ListView):
|
||||
paginate_by = 20
|
||||
|
||||
def get_works(self):
|
||||
return Work.objects.filter(collection__allowed_ensembles__ensemble=self.request.ensemble_id).order_by('name').distinct().select_related('collection')
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
data = super(WorkListView, self).get_context_data(*args, **kwargs)
|
||||
data['title'] = f'Music available to {self.ensemble.name}'
|
||||
return data
|
||||
|
||||
def get_queryset(self):
|
||||
works = self.get_works().order_by('name')
|
||||
|
||||
q = self.request.GET.get('filter')
|
||||
if q:
|
||||
if ":" in q:
|
||||
name, _, value = q.partition(":")
|
||||
works = works.filter(meta_info__name=name, meta_info__value__contains=value)
|
||||
else:
|
||||
works = works.filter(Q(name__contains=q) | Q(composer__contains=q) | Q(meta_info__value__contains=q))
|
||||
|
||||
return works
|
||||
|
||||
class CollectionWorkListView(WorkListView):
|
||||
|
||||
def get_works(self):
|
||||
works = Work.objects.filter(collection=self.kwargs['pk']).distinct()
|
||||
|
||||
loan_count = Count('project_items', Q(project_items__checkout__lte=now(), project_items__returned=None))
|
||||
works = works.annotate(loan_count=loan_count)
|
||||
return works
|
||||
|
||||
def get_context_data(self, *args, **kwargs):
|
||||
data = super(CollectionWorkListView, self).get_context_data(*args, **kwargs)
|
||||
data['title'] = Collection.objects.get(pk=self.kwargs['pk']).name
|
||||
data['collection_id'] = self.kwargs['pk']
|
||||
return data
|
||||
|
||||
class WorkAddView(EnsembleMixin, FormView):
|
||||
template_name = "interface/default_form.html"
|
||||
form_class = forms.WorkCreateForm
|
||||
|
||||
title = "Add a new work"
|
||||
|
||||
def form_valid(self, form):
|
||||
work = form.save(commit=False)
|
||||
work.ensemble_id = self.request.ensemble_id
|
||||
work.collection_id = self.kwargs['pk']
|
||||
work.save()
|
||||
|
||||
# handle the files
|
||||
uploads = self.request.FILES.getlist('uploads')
|
||||
docs = []
|
||||
for f in uploads:
|
||||
docs.append(work.docs.create(upload=f).pk)
|
||||
|
||||
if len(docs) == 1:
|
||||
return redirect('document_annotate', docs[0])
|
||||
else:
|
||||
return redirect('work_detail', pk=work.pk)
|
||||
|
||||
class WorkMixin(object):
|
||||
|
||||
def get_queryset(self):
|
||||
if self.request.is_admin:
|
||||
return Work.objects.all()
|
||||
|
||||
return Work.objects.filter(collection__allowed_ensembles__ensemble=self.request.ensemble_id)
|
||||
|
||||
class WorkDetailView(EnsembleMixin, WorkMixin, DetailView):
|
||||
pass
|
||||
|
||||
class WorkUpdateView(EnsembleMixin, WorkMixin, UpdateView):
|
||||
fields = ['name', 'composer', 'edition', 'code', 'licence', 'max_projects', 'running_time', 'notes']
|
||||
template_name = 'interface/default_form.html'
|
||||
|
||||
def get_success_url(self):
|
||||
return resolve_url('work_detail', self.kwargs['pk'])
|
||||
|
||||
|
||||
|
||||
class WorkAddToProject(EnsembleMixin, FormView):
|
||||
admin_required = True
|
||||
form_class = forms.ProjectSelectForm
|
||||
template_name = "interface/default_form.html"
|
||||
title = "Select project to add work to"
|
||||
|
||||
def get_object(self):
|
||||
return Work.objects.get(pk=self.kwargs['pk'])
|
||||
|
||||
def get_form(self):
|
||||
f = super(WorkAddToProject, self).get_form()
|
||||
qs = f.fields['project'].queryset
|
||||
|
||||
work = self.get_object()
|
||||
qs = qs.filter(ensemble_id=self.request.ensemble_id).exclude(pk__in=work.projects.all())
|
||||
|
||||
f.fields['project'].queryset = qs
|
||||
return f
|
||||
|
||||
def form_valid(self, form):
|
||||
work = self.get_object()
|
||||
project = form.cleaned_data['project']
|
||||
work.project_items.create(project=project, approved_by=self.request.user, checkout=now())
|
||||
return redirect('item_list', project=project.pk)
|
||||
|
||||
|
||||
|
||||
class WorkPartSetView(EnsembleMixin, DetailView):
|
||||
template_name = "library/work_partset.html"
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
|
||||
work = self.get_object()
|
||||
|
||||
parts = request.POST.getlist('parts')
|
||||
copies = request.POST.getlist('copies')
|
||||
|
||||
sections = []
|
||||
|
||||
for i, tag in enumerate(parts):
|
||||
part = work.digital_parts.select_related('doc').get(tag=tag)
|
||||
sections.append((part.doc.upload.path, part.instrument, part.start, part.end, int(copies[i])))
|
||||
|
||||
result = extract_and_concat(sections)
|
||||
|
||||
download_name = f'{work.name}.pdf'
|
||||
|
||||
response = FileResponse(result, content_type="application/pdf")
|
||||
response['Content-Disposition'] = f'inline; filename="{download_name}"'
|
||||
return response
|
||||
|
||||
def get_queryset(self):
|
||||
works = Work.objects.all()
|
||||
|
||||
if not self.request.is_admin:
|
||||
works = works.filter(collection__allowed_ensembles__ensemble=self.request.ensemble_id)
|
||||
return works
|
||||
|
||||
class WorkAddDocumentView(EnsembleMixin, CreateView):
|
||||
template_name = "interface/default_form.html"
|
||||
model = Document
|
||||
fields = ['upload']
|
||||
|
||||
def title(self):
|
||||
work = Work.objects.get(pk=self.kwargs['pk'])
|
||||
return f"Add a document to {work.name}"
|
||||
|
||||
def form_invalid(self, form):
|
||||
if self.request.headers['Accept'] == 'application/json':
|
||||
return HttpResponse(status=400)
|
||||
return super().form_invalid(form)
|
||||
|
||||
def form_valid(self, form):
|
||||
doc = form.save(commit=False)
|
||||
doc.work_id = self.kwargs['pk']
|
||||
doc.save()
|
||||
|
||||
# auto tag the document
|
||||
|
||||
name, _ = os.path.splitext(os.path.basename(doc.upload.name))
|
||||
parts = re.split(r'[^A-Za-z]+', name)
|
||||
parts.reverse()
|
||||
for word in parts:
|
||||
try:
|
||||
tag = INSTRUMENT_TAGS[word.lower()]
|
||||
doc.sections.create(tag=tag)
|
||||
break
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
if self.request.headers['Accept'] == 'application/json':
|
||||
filename = os.path.basename(doc.upload.name)
|
||||
return JsonResponse({
|
||||
"message": "created",
|
||||
"id": doc.pk,
|
||||
"entry": render_to_string('library/document_entry.html', {'doc': doc, 'request': self.request})
|
||||
}, status=201)
|
||||
|
||||
return redirect('document_annotate', doc.pk)
|
||||
|
||||
class DocumentMixin(object):
|
||||
|
||||
def get_queryset(self):
|
||||
if self.request.is_admin:
|
||||
return Document.objects.select_related('work')
|
||||
return Document.objects.filter(work__ensemble=self.request.ensemble_id).select_related('work')
|
||||
|
||||
class DocumentDetailView(EnsembleMixin, DocumentMixin, DetailView):
|
||||
pass
|
||||
|
||||
class DocumentDownloadView(EnsembleMixin, DocumentMixin, SingleObjectMixin, View):
|
||||
|
||||
def get(self, request, **args):
|
||||
self.request = request
|
||||
self.args = args
|
||||
self.object = self.get_object()
|
||||
|
||||
#response = FileResponse(self.object.upload, content_type="application/pdf")
|
||||
#return response
|
||||
return redirect(self.object.upload.url)
|
||||
|
||||
class DocumentAnnotateView(EnsembleMixin, DocumentMixin, DetailView):
|
||||
template_name = 'library/document_annotate.html'
|
||||
|
||||
def post(self, request, **args):
|
||||
self.request = request
|
||||
self.args = args
|
||||
self.object = self.get_object()
|
||||
|
||||
data = json.loads(request.body)
|
||||
|
||||
self.object.sections.all().delete()
|
||||
for tag, start, end in data:
|
||||
#pages.sort()
|
||||
#end = pages[-1] if len(pages) > 1 else None
|
||||
o = self.object.sections.create(tag=tag, start=start, end=end)
|
||||
|
||||
return HttpResponse(status=204)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
data = super(DocumentAnnotateView, self).get_context_data(**kwargs)
|
||||
|
||||
pages = []
|
||||
for part in data['document'].sections.all():
|
||||
pages.append((part.tag, part.start, part.end))
|
||||
|
||||
data['json_data'] = {'pageTags': pages, 'instruments': dict(INSTRUMENTS)}
|
||||
return data
|
||||
|
||||
class DocumentDeleteView(EnsembleMixin, DocumentMixin, DeleteView):
|
||||
|
||||
#def get_template_names(self):
|
||||
# return ["interface/default_form.html"]
|
||||
|
||||
def get_success_url(self):
|
||||
return resolve_url('work_detail', self.object.work.pk)
|
||||
|
||||
class PartDownloadView(EnsembleMixin, SingleObjectMixin, View):
|
||||
|
||||
def get(self, request, **args):
|
||||
self.request = request
|
||||
self.args = args
|
||||
self.object = self.get_object()
|
||||
|
||||
result = extract_pages(self.object.doc.upload.path, self.object.doc.work.name, self.object.start, self.object.end)
|
||||
|
||||
#download_name = f'{self.object.doc.work.name}_{self.object.instrument}.pdf'
|
||||
|
||||
response = FileResponse(result, content_type="application/pdf")
|
||||
response['Content-Disposition'] = f'inline; filename="{self.args["filename"]}"'
|
||||
return response
|
||||
|
||||
def get_queryset(self):
|
||||
|
||||
if self.request.is_admin:
|
||||
parts = Section.objects.all()
|
||||
else:
|
||||
parts = Section.objects.filter(doc__work__collection__allowed_ensembles__ensemble=self.request.ensemble_id)
|
||||
return parts.select_related('doc', 'doc__work')
|
||||
@@ -0,0 +1,161 @@
|
||||
"""
|
||||
Views relating to importing and exporting collection items
|
||||
"""
|
||||
"""
|
||||
from interface.views import EnsembleMixin
|
||||
from library.views import WorkMixin
|
||||
from django.views.generic import View
|
||||
from django.http import JsonResponse
|
||||
|
||||
from djantic import ModelSchema
|
||||
from library.models import Work, Document, Section
|
||||
|
||||
class DocumentSchema(ModelSchema):
|
||||
class Config:
|
||||
model = Document
|
||||
|
||||
|
||||
class WorkSchema(ModelSchema):
|
||||
|
||||
docs: DocumentSchema
|
||||
|
||||
class Config:
|
||||
model = Work
|
||||
exclude = ['licence']
|
||||
|
||||
class WorkExportView(EnsembleMixin, WorkMixin, View):
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
obj = self.get_queryset().get(pk=kwargs['pk'])
|
||||
schema = WorkSchema.from_orm(obj)
|
||||
return JsonResponse(schema.dict())
|
||||
|
||||
"""
|
||||
|
||||
from library.views import WorkMixin
|
||||
from interface.views import EnsembleMixin
|
||||
|
||||
from rest_framework import routers, serializers, viewsets
|
||||
|
||||
from library.models import Collection, Work, Document, Section
|
||||
|
||||
|
||||
import requests
|
||||
from io import BytesIO
|
||||
import tempfile
|
||||
import shutil
|
||||
|
||||
from django.db import transaction
|
||||
from django.core.files.uploadedfile import TemporaryUploadedFile
|
||||
|
||||
|
||||
class SectionSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Section
|
||||
exclude = ['id', 'doc']
|
||||
|
||||
def to_representation(self, instance):
|
||||
start = instance.start or 0
|
||||
end = instance.end or 0
|
||||
return f"{instance.tag}:{instance.type}:{start}:{end}"
|
||||
|
||||
def to_internal_value(self, data):
|
||||
tag, section_type, start, end = data.split(":")
|
||||
try:
|
||||
start = int(start)
|
||||
except:
|
||||
start = 0
|
||||
try:
|
||||
end = int(end)
|
||||
except:
|
||||
end = 0
|
||||
return super().to_internal_value({'tag': tag, 'type': int(section_type), 'start': start, 'end': end})
|
||||
|
||||
class DocumentSerializer(serializers.ModelSerializer):
|
||||
|
||||
upload = serializers.URLField()
|
||||
sections = SectionSerializer(many=True)
|
||||
#doctype = serializers.CharField(source='get_doctype_display')
|
||||
|
||||
#def to_internal_value(self, data):
|
||||
# r = requests.get(data['upload'], stream=True)
|
||||
# with tempfile.NamedTemporaryFile('wb') as f:
|
||||
# shutil.copyfileobj(r.raw, f)
|
||||
# data['upload'] = f.name
|
||||
# print(repr(data))
|
||||
# return super().to_internal_value(data)
|
||||
|
||||
def to_representation(self, instance):
|
||||
data = super().to_representation(instance)
|
||||
if data['upload'][0] == '/':
|
||||
data['upload'] = 'http://localhost:8000' + (data['upload'])
|
||||
return data
|
||||
|
||||
def create(self, validated_data):
|
||||
print("CREATE", validated_data)
|
||||
return super().create(validated_data)
|
||||
|
||||
def validate(self, data):
|
||||
print("VALIDATE", data)
|
||||
return super().validate(data)
|
||||
|
||||
def validate_upload(self, value):
|
||||
print("VALIDATE", value)
|
||||
return value
|
||||
|
||||
class Meta:
|
||||
model = Document
|
||||
exclude = ["id", "work", "version", "created"]
|
||||
|
||||
# Serializers define the API representation.
|
||||
class WorkSerializer(serializers.ModelSerializer):
|
||||
|
||||
docs = DocumentSerializer(many=True)
|
||||
|
||||
class Meta:
|
||||
model = Work
|
||||
exclude = ['id', 'collection', 'projects', 'parent']
|
||||
|
||||
def create(self, validated):
|
||||
with transaction.atomic():
|
||||
docs = validated.pop('docs', [])
|
||||
work = Work.objects.create(**validated)
|
||||
|
||||
for d in docs:
|
||||
sections = d.pop('sections', [])
|
||||
|
||||
r = requests.get(d['upload'], stream=True)
|
||||
f = TemporaryUploadedFile(d['upload'], r.headers['content-type'], r.headers['content-length'], r.encoding)
|
||||
shutil.copyfileobj(r.raw, f.file)
|
||||
r.close()
|
||||
d['upload'] = f
|
||||
doc = Document.objects.create(work_id=work.pk, **d)
|
||||
|
||||
for s in sections:
|
||||
Section.objects.create(doc_id=doc.pk, **s)
|
||||
|
||||
return work
|
||||
|
||||
class CollectionSerializer(serializers.Serializer):
|
||||
works = WorkSerializer(many=True)
|
||||
|
||||
from rest_framework import generics
|
||||
|
||||
class CollectionExportView(generics.RetrieveAPIView):
|
||||
serializer_class = CollectionSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
return Collection.objects.filter(administrators=self.request.user)
|
||||
|
||||
class WorkExportView(generics.RetrieveAPIView):
|
||||
serializer_class = WorkSerializer
|
||||
|
||||
def get_queryset(self):
|
||||
return Work.objects.filter(collection__administrators=self.request.user)
|
||||
|
||||
class WorkImportView(generics.CreateAPIView):
|
||||
serializer_class = WorkSerializer
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(collection_id=self.kwargs['pk'])
|
||||
|
||||
Reference in New Issue
Block a user