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 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 import json import os.path from interface.views import EnsembleMixin, ProjectMixin from interface.models import Project from .models import Collection, Work, Document, Section from .imslp import INSTRUMENTS 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() if self.request.headers['Accept'] == 'application/json': filename = os.path.basename(doc.upload.name) return JsonResponse({ "message": "created", "id": doc.pk, "entry": f"""