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')