from django.conf import settings from django.db import models from django.utils.text import slugify from django.core.files.storage import get_storage_class import re import logging logger = logging.getLogger(__name__) try: library_storage = get_storage_class(settings.LIBRARY_STORAGE)() except (ImportError, AttributeError): library_storage = get_storage_class()() logger.info("Library storage: %s", library_storage.__class__.__name__) INSTRUMENTS = [ ('Score', 'Score'), ('S', 'Soprano'), ('A', 'Alto'), ('T', 'Tenor'), ('B', 'Bass'), ('V', 'Vocals'), ('Vln', 'Violin'), ('Vla', 'Viola'), ('Vc', 'Violoncello'), ('Cb', 'Contrabass'), ('Fl', 'Flute'), ('Picc', 'Piccolo'), ('Cl', 'Clarinet'), ('Ob', 'Oboe'), ('Hn', 'Horn'), ('Tpt', 'Trumpet'), ('Tbn', 'Trombone'), ('Tuba', 'Tuba'), ('Timp', 'Timpani'), ('Drum', 'Drumset'), ('Perc', 'Percussion'), ('Pno', 'Piano'), ('Hp', 'Harp'), ] ORCHESTRATIONS = { 'SATB': ('S', 'A', 'T', 'B'), 'String Quartet': ('Vln1', 'Vln2', 'Vla', 'Vc'), 'Chamber Orchestra': ('Vln1', 'Vln2', 'Vla', 'Vc', 'Cb', 'Fl1', 'Fl2', 'Cl1', 'Cl2', 'Hn1', 'Hn2', 'Tpt1', 'Tpt2', 'Tbn1', 'Tbn2', 'Tuba', 'Timp', 'Drum', 'Perc'), 'RWE': ('Fl1', 'Fl2', 'Cl', 'Tbn', 'Vln1', 'Vln2', 'Vla', 'Vc'), 'Custom': () } def tag_to_instrument(tag): m = re.match(r'([A-Za-z]+)(\d*)', tag) if not m: return tag l = m.groups() return "{0} {1}".format(dict(INSTRUMENTS).get(l[0],l[0]), l[1]).strip() class Orchestration(models.Model): ensemble = models.ForeignKey('interface.Ensemble', on_delete=models.CASCADE, related_name="orchestrations", null=True, blank=True) name = models.CharField(max_length=100) instruments = models.TextField() def as_list(self): tags = [ t.strip() for t in self.instruments.split(',') ] return [ (t, tag_to_instrument(t)) for t in tags if t ] def save(self): self.as_list() super(Orchestration, self).save() def __str__(self): return self.name class Work(models.Model): ensemble = models.ForeignKey('interface.Ensemble', on_delete=models.CASCADE, related_name="works") slug = models.SlugField(max_length=100, editable=False) name = models.CharField(max_length=255) composer = models.CharField(max_length=255, blank=True) orchestration = models.ForeignKey(Orchestration, null=True, on_delete=models.SET_NULL, related_name='works') running_time = models.IntegerField(null=True, blank=True) notes = models.TextField(blank=True) projects = models.ManyToManyField('interface.Project', through='Item', related_name="works") class Meta: unique_together = ['ensemble', 'slug'] @property def parts(self): return Part.objects.filter(doc__work=self.pk) @property def instruments(self): return self.orchestration.as_list() def save(self): if not self.slug: self.slug = slugify(self.name) super(Work, self).save() def __str__(self): return self.name class Item(models.Model): """ Item represents a Work in a Project e.g. item in set list or programme It also allows works to be shared from one ensemble to another on a per-project basis. """ project = models.ForeignKey('interface.Project', on_delete=models.CASCADE) work = models.ForeignKey('Work', on_delete=models.CASCADE) order = models.SmallIntegerField(default=0) class Meta: ordering = ['order', 'work'] def __str__(self): return f"<{self.project.slug}:{self.work.slug}>" def doc_upload_filename(doc, filename): storage = doc.work.ensemble.storage if not storage: raise RuntimeError("Storage not set") return f'{storage}:{doc.work.ensemble.slug}/works/{doc.work.slug}/{filename}' class Document(models.Model): work = models.ForeignKey('Work', on_delete=models.CASCADE, related_name="docs") upload = models.FileField(upload_to=doc_upload_filename, storage=library_storage) def __str__(self): return self.upload.name class Part(models.Model): doc = models.ForeignKey(Document, on_delete=models.CASCADE, related_name="parts") tag = models.SlugField(max_length=20) start = models.SmallIntegerField(null=True, blank=True) end = models.SmallIntegerField(null=True, blank=True) notes = models.TextField(blank=True) class Meta: ordering = ['doc', 'start', 'pk'] @property def instrument(self): return tag_to_instrument(self.tag) @property def filename(self): return slugify(f'{self.doc.work.name}_{self.instrument}') + '.pdf' @property def pagerange(self): if self.start: if self.end: return f"{self.start}-{self.end}" return str(self.start) return "all" def __str__(self): return f'{self.doc.upload} [{self.pagerange}]'