2022-12-02 13:24:31 +11:00

377 lines
13 KiB
Python

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 .models import Collection, Work, Document, Section
from .imslp import INSTRUMENTS, TAG_LOOKUP
from . import forms, models
from .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.lower())
parts.reverse()
for word in parts:
print(word)
if word in TAG_LOOKUP:
doc.sections.create(tag=TAG_LOOKUP[word])
break
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="foo.pdf"'
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')