From 8f18b9ab9d5ee4142e7b803e037500368bbc6fb2 Mon Sep 17 00:00:00 2001 From: Tris Forster Date: Sat, 19 Nov 2022 21:30:59 +1100 Subject: [PATCH] Removing old files --- dev-requirements.txt | 1 + interface/admin.py | 12 +- interface/forms.py | 51 +- interface/migrations/0001_initial.py | 52 +- .../0001_squashed_0021_project_description.py | 93 ---- .../migrations/0002_auto_20200904_1004.py | 31 -- .../migrations/0003_auto_20200905_0118.py | 33 -- .../migrations/0004_auto_20200905_0127.py | 34 -- .../migrations/0005_auto_20200905_0638.py | 23 - interface/migrations/0006_submission_key.py | 18 - .../migrations/0007_auto_20200906_1009.py | 35 -- .../migrations/0008_auto_20200906_1122.py | 24 - .../migrations/0009_auto_20200907_0103.py | 27 - .../migrations/0010_auto_20200907_0148.py | 18 - .../migrations/0011_auto_20200907_0234.py | 18 - .../migrations/0012_remove_ensemble_bucket.py | 17 - .../migrations/0013_auto_20200907_1455.py | 18 - .../migrations/0014_auto_20200909_1016.py | 23 - .../migrations/0015_resource_media_type.py | 18 - .../migrations/0016_auto_20200910_2025.py | 18 - .../migrations/0017_auto_20200914_0943.py | 25 - .../migrations/0018_auto_20200914_1009.py | 25 - interface/migrations/0019_project_owner.py | 18 - .../migrations/0020_auto_20201003_2103.py | 23 - .../migrations/0021_project_description.py | 18 - .../migrations/0022_auto_20210303_2043.py | 34 -- interface/migrations/0023_ensemble_storage.py | 19 - .../migrations/0024_auto_20210312_1712.py | 23 - .../migrations/0025_auto_20210312_1713.py | 23 - .../migrations/0026_auto_20210313_0926.py | 18 - .../migrations/0027_auto_20210322_1154.py | 18 - interface/migrations/0028_ensemble_details.py | 18 - interface/models.py | 104 ++-- interface/static/interface/css/polyphonic.css | 373 ++------------ interface/templates/base.html | 70 +-- .../templates/interface/default_form.html | 19 +- .../interface/ensemble_project_list.html | 81 ++- .../templates/interface/project_base.html | 125 ++++- .../templates/interface/project_detail.html | 66 ++- .../templates/interface/project_form.html | 23 +- .../interface/project_submissions.mk | 11 - interface/templates/interface/register.html | 35 +- .../templates/interface/resource_list.html | 67 ++- .../interface/submission_create.html | 24 - .../interface/submission_detail.html | 17 - .../templates/interface/submission_link.html | 18 - .../templates/interface/submission_list.html | 36 -- .../interface/submission_preview.html | 17 - interface/templates/interface/wiki.html | 8 +- .../templates/interface/wikipage_form.html | 6 +- interface/templates/registration/login.html | 18 +- interface/urls.py | 21 +- interface/views.py | 334 +++++------- library/admin.py | 31 +- library/forms.py | 8 +- library/migrations/0001_initial.py | 75 +-- library/migrations/0002_auto_20210504_1830.py | 34 -- library/migrations/0003_auto_20210504_1845.py | 24 - library/migrations/0004_auto_20210505_0927.py | 44 -- library/migrations/0005_work_parts.py | 18 - library/migrations/0006_auto_20210902_1355.py | 48 -- library/migrations/0007_auto_20210902_1407.py | 33 -- library/migrations/0008_work_orchestration.py | 18 - library/models.py | 314 ++++-------- .../templates/library/document_annotate.html | 478 ++++++++++++------ library/templates/library/item_list.html | 121 ++++- .../templates/library/item_list_manage.html | 10 +- library/templates/library/project_detail.html | 13 +- library/templates/library/project_menu.html | 9 +- library/templates/library/work_detail.html | 276 +++++++--- library/templates/library/work_list.html | 78 +-- library/templates/library/work_partset.html | 52 +- library/tests.py | 28 + library/urls.py | 11 +- library/views.py | 187 ++++--- polyphonic/settings_default.py | 15 +- polyphonic/urls.py | 3 +- 77 files changed, 1734 insertions(+), 2493 deletions(-) delete mode 100644 interface/migrations/0001_squashed_0021_project_description.py delete mode 100644 interface/migrations/0002_auto_20200904_1004.py delete mode 100644 interface/migrations/0003_auto_20200905_0118.py delete mode 100644 interface/migrations/0004_auto_20200905_0127.py delete mode 100644 interface/migrations/0005_auto_20200905_0638.py delete mode 100644 interface/migrations/0006_submission_key.py delete mode 100644 interface/migrations/0007_auto_20200906_1009.py delete mode 100644 interface/migrations/0008_auto_20200906_1122.py delete mode 100644 interface/migrations/0009_auto_20200907_0103.py delete mode 100644 interface/migrations/0010_auto_20200907_0148.py delete mode 100644 interface/migrations/0011_auto_20200907_0234.py delete mode 100644 interface/migrations/0012_remove_ensemble_bucket.py delete mode 100644 interface/migrations/0013_auto_20200907_1455.py delete mode 100644 interface/migrations/0014_auto_20200909_1016.py delete mode 100644 interface/migrations/0015_resource_media_type.py delete mode 100644 interface/migrations/0016_auto_20200910_2025.py delete mode 100644 interface/migrations/0017_auto_20200914_0943.py delete mode 100644 interface/migrations/0018_auto_20200914_1009.py delete mode 100644 interface/migrations/0019_project_owner.py delete mode 100644 interface/migrations/0020_auto_20201003_2103.py delete mode 100644 interface/migrations/0021_project_description.py delete mode 100644 interface/migrations/0022_auto_20210303_2043.py delete mode 100644 interface/migrations/0023_ensemble_storage.py delete mode 100644 interface/migrations/0024_auto_20210312_1712.py delete mode 100644 interface/migrations/0025_auto_20210312_1713.py delete mode 100644 interface/migrations/0026_auto_20210313_0926.py delete mode 100644 interface/migrations/0027_auto_20210322_1154.py delete mode 100644 interface/migrations/0028_ensemble_details.py delete mode 100644 interface/templates/interface/project_submissions.mk delete mode 100644 interface/templates/interface/submission_create.html delete mode 100644 interface/templates/interface/submission_detail.html delete mode 100644 interface/templates/interface/submission_link.html delete mode 100644 interface/templates/interface/submission_list.html delete mode 100644 interface/templates/interface/submission_preview.html delete mode 100644 library/migrations/0002_auto_20210504_1830.py delete mode 100644 library/migrations/0003_auto_20210504_1845.py delete mode 100644 library/migrations/0004_auto_20210505_0927.py delete mode 100644 library/migrations/0005_work_parts.py delete mode 100644 library/migrations/0006_auto_20210902_1355.py delete mode 100644 library/migrations/0007_auto_20210902_1407.py delete mode 100644 library/migrations/0008_work_orchestration.py diff --git a/dev-requirements.txt b/dev-requirements.txt index b064f29..255813d 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1 +1,2 @@ pylint==2.6.0 +django-debug-toolbar \ No newline at end of file diff --git a/interface/admin.py b/interface/admin.py index d97d5a4..53bcdc7 100644 --- a/interface/admin.py +++ b/interface/admin.py @@ -5,14 +5,15 @@ from . import models class EnsembleAdmin(admin.ModelAdmin): list_display = ['name', 'ensemble_code', 'slug'] +class ModuleInline(admin.StackedInline): + model = models.Module + extra = 0 + class ProjectAdmin(admin.ModelAdmin): - list_display = ['name', 'ensemble', 'event_date', 'active', 'slug'] + list_display = ['name', 'ensemble', 'event_date', 'active'] list_filter = ['ensemble', 'active'] - -class SubmissionAdmin(admin.ModelAdmin): - list_display = ['name', 'instrument', 'date', 'complete'] - list_filter = ['project', 'complete'] + inlines = [ModuleInline] class ResourceAdmin(admin.ModelAdmin): list_display = ['name', 'media_type', 'project'] @@ -24,6 +25,5 @@ class WikiPageAdmin(admin.ModelAdmin): admin.site.register(models.Ensemble, EnsembleAdmin) admin.site.register(models.Project, ProjectAdmin) -admin.site.register(models.Submission, SubmissionAdmin) admin.site.register(models.Resource, ResourceAdmin) admin.site.register(models.WikiPage, WikiPageAdmin) \ No newline at end of file diff --git a/interface/forms.py b/interface/forms.py index bd4cb94..9e9e541 100644 --- a/interface/forms.py +++ b/interface/forms.py @@ -1,17 +1,46 @@ from django import forms -from .models import Submission +from crispy_forms.helper import FormHelper +from crispy_forms.layout import Submit -class CodeForm(forms.Form): +from . import models, fields + +class BaseForm(forms.Form): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.helper = self.get_form_helper() + + def get_form_helper(self): + helper = FormHelper(self) + helper.add_input(Submit('submit', 'Submit', css_class='button is-link')) + return helper + + +class ProjectForm(forms.ModelForm, BaseForm): + + class Meta: + model = models.Project + fields = ['name', 'description', 'event_date'] + widgets = { + 'event_date': forms.DateTimeInput(attrs={'type': 'date'}) + } + +class ResourceForm(forms.ModelForm, BaseForm): + + class Meta: + model = models.Resource + fields = ['name', 'media_type', 'description', 'file'] + + def get_form_helper(self): + helper = super().get_form_helper() + helper[3].wrap(fields.BulmaFileUpload) + return helper + +class CodeForm(BaseForm): code = forms.CharField(max_length=14, widget=forms.TextInput(attrs={'placeholder': 'xxx-xxx-xxx', 'inputmode': 'numeric'})) passphrase = forms.CharField(max_length=32) -class SubmissionForm(forms.ModelForm): - method = forms.ChoiceField(choices=( - ('upload', 'I need to upload a file'), - ('link', 'I have a link from my own cloud storage provider') - ), initial='upload') - - class Meta: - model = Submission - fields = ['name', 'instrument', 'method', 'notes'] \ No newline at end of file +class ResourceUploadForm(forms.Form): + pass +# file = S3UploadField() \ No newline at end of file diff --git a/interface/migrations/0001_initial.py b/interface/migrations/0001_initial.py index b8da9cb..c50c935 100644 --- a/interface/migrations/0001_initial.py +++ b/interface/migrations/0001_initial.py @@ -1,7 +1,10 @@ -# Generated by Django 3.1.1 on 2020-09-04 09:59 +# Generated by Django 3.2.7 on 2022-11-18 09:54 +import byostorage.user +from django.conf import settings from django.db import migrations, models import django.db.models.deletion +import interface.models class Migration(migrations.Migration): @@ -9,32 +12,69 @@ class Migration(migrations.Migration): initial = True dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('byostorage', '0004_alter_userstorage_storage'), ] operations = [ + migrations.CreateModel( + name='Ensemble', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(help_text='Display name', max_length=100)), + ('slug', models.SlugField(editable=False, help_text='Short name for the ensemble - used for folders', max_length=100)), + ('code', models.CharField(default=interface.models.generate_code, help_text='Ensemble registration code', max_length=9)), + ('passphrase', models.CharField(help_text='Used to register ensembles', max_length=100)), + ('details', models.TextField(blank=True, help_text='Description of the ensemble (markdown)')), + ('admins', models.ManyToManyField(related_name='ensembles', to=settings.AUTH_USER_MODEL)), + ('storage', models.ForeignKey(help_text='Default storage for this ensemble', null=True, on_delete=django.db.models.deletion.SET_NULL, to='byostorage.userstorage')), + ], + ), migrations.CreateModel( name='Project', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=100)), + ('description', models.TextField(blank=True, help_text='Markdown format')), + ('active', models.BooleanField(default=True)), + ('event_date', models.DateField(blank=True, null=True)), + ('owner', models.CharField(blank=True, max_length=255)), + ('ensemble', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='projects', to='interface.ensemble')), ], + options={ + 'ordering': ['active', '-pk'], + }, ), migrations.CreateModel( name='WikiPage', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=255)), ('markdown', models.TextField()), ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='wiki_pages', to='interface.project')), ], ), migrations.CreateModel( - name='Submission', + name='Resource', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255)), - ('instrument', models.CharField(max_length=100)), - ('notes', models.TextField()), - ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='submissions', to='interface.project')), + ('name', models.CharField(max_length=100)), + ('description', models.TextField(blank=True)), + ('file', models.FileField(storage=byostorage.user.BYOStorage(), upload_to=interface.models.resource_key)), + ('media_type', models.CharField(choices=[('audio', 'Audio'), ('video', 'Video'), ('general', 'General')], default='*', max_length=10)), + ('visible', models.BooleanField(default=True)), + ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='resources', to='interface.project')), + ], + options={ + 'ordering': ['-visible', '-pk'], + }, + ), + migrations.CreateModel( + name='Module', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.SlugField(max_length=20)), + ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='modules', to='interface.project')), ], ), ] diff --git a/interface/migrations/0001_squashed_0021_project_description.py b/interface/migrations/0001_squashed_0021_project_description.py deleted file mode 100644 index 51bab54..0000000 --- a/interface/migrations/0001_squashed_0021_project_description.py +++ /dev/null @@ -1,93 +0,0 @@ -# Generated by Django 3.1.1 on 2021-03-21 23:10 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import interface.models - - -class Migration(migrations.Migration): - - replaces = [('interface', '0001_initial'), ('interface', '0002_auto_20200904_1004'), ('interface', '0003_auto_20200905_0118'), ('interface', '0004_auto_20200905_0127'), ('interface', '0005_auto_20200905_0638'), ('interface', '0006_submission_key'), ('interface', '0007_auto_20200906_1009'), ('interface', '0008_auto_20200906_1122'), ('interface', '0009_auto_20200907_0103'), ('interface', '0010_auto_20200907_0148'), ('interface', '0011_auto_20200907_0234'), ('interface', '0012_remove_ensemble_bucket'), ('interface', '0013_auto_20200907_1455'), ('interface', '0014_auto_20200909_1016'), ('interface', '0015_resource_media_type'), ('interface', '0016_auto_20200910_2025'), ('interface', '0017_auto_20200914_0943'), ('interface', '0018_auto_20200914_1009'), ('interface', '0019_project_owner'), ('interface', '0020_auto_20201003_2103'), ('interface', '0021_project_description')] - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='Project', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100)), - ('active', models.BooleanField(default=True)), - ], - ), - migrations.CreateModel( - name='WikiPage', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('markdown', models.TextField()), - ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='wiki_pages', to='interface.project')), - ('title', models.CharField(default='', max_length=255)), - ], - ), - migrations.CreateModel( - name='Ensemble', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100)), - ('code', models.CharField(default=interface.models.generate_code, max_length=9)), - ('passphrase', models.CharField(max_length=100)), - ('admins', models.ManyToManyField(related_name='ensembles', to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.AddField( - model_name='project', - name='ensemble', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='projects', to='interface.ensemble'), - ), - migrations.AddField( - model_name='project', - name='deadline', - field=models.DateField(blank=True, null=True), - ), - migrations.CreateModel( - name='Resource', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100)), - ('key', models.CharField(blank=True, max_length=255)), - ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='resources', to='interface.project')), - ('description', models.TextField(blank=True)), - ('media_type', models.CharField(choices=[('audio', 'Audio'), ('video', 'Video'), ('general', 'General')], default='*', max_length=10)), - ('visible', models.BooleanField(default=True)), - ], - ), - migrations.AddField( - model_name='project', - name='owner', - field=models.CharField(blank=True, max_length=255), - ), - migrations.CreateModel( - name='Submission', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255)), - ('instrument', models.CharField(max_length=100, verbose_name='Instrument / Voice')), - ('notes', models.TextField(blank=True)), - ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='all_submissions', to='interface.project')), - ('date', models.DateTimeField(auto_now_add=True)), - ('complete', models.BooleanField(default=False)), - ('url', models.CharField(blank=True, max_length=512)), - ('private', models.BooleanField(default=False)), - ], - ), - migrations.AddField( - model_name='project', - name='description', - field=models.TextField(blank=True), - ), - ] diff --git a/interface/migrations/0002_auto_20200904_1004.py b/interface/migrations/0002_auto_20200904_1004.py deleted file mode 100644 index 1aa1682..0000000 --- a/interface/migrations/0002_auto_20200904_1004.py +++ /dev/null @@ -1,31 +0,0 @@ -# Generated by Django 3.1.1 on 2020-09-04 10:04 - -from django.db import migrations, models -import django.utils.timezone - - -class Migration(migrations.Migration): - - dependencies = [ - ('interface', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='submission', - name='date', - field=models.DateField(auto_created=True, default=django.utils.timezone.now), - preserve_default=False, - ), - migrations.AddField( - model_name='wikipage', - name='title', - field=models.CharField(default='', max_length=255), - preserve_default=False, - ), - migrations.AlterField( - model_name='submission', - name='notes', - field=models.TextField(blank=True), - ), - ] diff --git a/interface/migrations/0003_auto_20200905_0118.py b/interface/migrations/0003_auto_20200905_0118.py deleted file mode 100644 index ba5f49a..0000000 --- a/interface/migrations/0003_auto_20200905_0118.py +++ /dev/null @@ -1,33 +0,0 @@ -# Generated by Django 3.1.1 on 2020-09-05 01:18 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('interface', '0002_auto_20200904_1004'), - ] - - operations = [ - migrations.CreateModel( - name='Ensemble', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100)), - ('code', models.CharField(max_length=12)), - ('password', models.CharField(max_length=100)), - ], - ), - migrations.AddField( - model_name='project', - name='active', - field=models.BooleanField(default=True), - ), - migrations.AddField( - model_name='project', - name='ensemble', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='projects', to='interface.ensemble'), - ), - ] diff --git a/interface/migrations/0004_auto_20200905_0127.py b/interface/migrations/0004_auto_20200905_0127.py deleted file mode 100644 index bcc9612..0000000 --- a/interface/migrations/0004_auto_20200905_0127.py +++ /dev/null @@ -1,34 +0,0 @@ -# Generated by Django 3.1.1 on 2020-09-05 01:27 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('interface', '0003_auto_20200905_0118'), - ] - - operations = [ - migrations.AddField( - model_name='project', - name='bucket', - field=models.CharField(default='', max_length=100), - preserve_default=False, - ), - migrations.AddField( - model_name='project', - name='deadline', - field=models.DateField(blank=True, null=True), - ), - migrations.CreateModel( - name='Resource', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100)), - ('uri', models.CharField(max_length=255)), - ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='resources', to='interface.project')), - ], - ), - ] diff --git a/interface/migrations/0005_auto_20200905_0638.py b/interface/migrations/0005_auto_20200905_0638.py deleted file mode 100644 index 9b38f63..0000000 --- a/interface/migrations/0005_auto_20200905_0638.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.1.1 on 2020-09-05 06:38 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('interface', '0004_auto_20200905_0127'), - ] - - operations = [ - migrations.AddField( - model_name='submission', - name='complete', - field=models.BooleanField(default=False), - ), - migrations.AlterField( - model_name='submission', - name='date', - field=models.DateField(auto_now_add=True), - ), - ] diff --git a/interface/migrations/0006_submission_key.py b/interface/migrations/0006_submission_key.py deleted file mode 100644 index 4c9479f..0000000 --- a/interface/migrations/0006_submission_key.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.1.1 on 2020-09-05 09:32 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('interface', '0005_auto_20200905_0638'), - ] - - operations = [ - migrations.AddField( - model_name='submission', - name='key', - field=models.CharField(blank=True, max_length=255), - ), - ] diff --git a/interface/migrations/0007_auto_20200906_1009.py b/interface/migrations/0007_auto_20200906_1009.py deleted file mode 100644 index 87f9acd..0000000 --- a/interface/migrations/0007_auto_20200906_1009.py +++ /dev/null @@ -1,35 +0,0 @@ -# Generated by Django 3.1.1 on 2020-09-06 10:09 - -from django.db import migrations, models -import django.db.models.deletion -import interface.models - - -class Migration(migrations.Migration): - - dependencies = [ - ('interface', '0006_submission_key'), - ] - - operations = [ - migrations.RemoveField( - model_name='project', - name='bucket', - ), - migrations.AddField( - model_name='ensemble', - name='bucket', - field=models.CharField(default='', max_length=100), - preserve_default=False, - ), - migrations.AlterField( - model_name='ensemble', - name='code', - field=models.CharField(default=interface.models.generate_code, max_length=12), - ), - migrations.AlterField( - model_name='submission', - name='project', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='all_submissions', to='interface.project'), - ), - ] diff --git a/interface/migrations/0008_auto_20200906_1122.py b/interface/migrations/0008_auto_20200906_1122.py deleted file mode 100644 index e9f6a97..0000000 --- a/interface/migrations/0008_auto_20200906_1122.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 3.1.1 on 2020-09-06 11:22 - -from django.db import migrations, models -import interface.models - - -class Migration(migrations.Migration): - - dependencies = [ - ('interface', '0007_auto_20200906_1009'), - ] - - operations = [ - migrations.RenameField( - model_name='ensemble', - old_name='password', - new_name='passphrase', - ), - migrations.AlterField( - model_name='ensemble', - name='code', - field=models.CharField(default=interface.models.generate_code, max_length=9), - ), - ] diff --git a/interface/migrations/0009_auto_20200907_0103.py b/interface/migrations/0009_auto_20200907_0103.py deleted file mode 100644 index 027531f..0000000 --- a/interface/migrations/0009_auto_20200907_0103.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 3.1.1 on 2020-09-07 01:03 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('interface', '0008_auto_20200906_1122'), - ] - - operations = [ - migrations.RemoveField( - model_name='submission', - name='key', - ), - migrations.AddField( - model_name='submission', - name='location', - field=models.CharField(blank=True, max_length=512), - ), - migrations.AlterField( - model_name='ensemble', - name='bucket', - field=models.CharField(max_length=255), - ), - ] diff --git a/interface/migrations/0010_auto_20200907_0148.py b/interface/migrations/0010_auto_20200907_0148.py deleted file mode 100644 index fb00411..0000000 --- a/interface/migrations/0010_auto_20200907_0148.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.1.1 on 2020-09-07 01:48 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('interface', '0009_auto_20200907_0103'), - ] - - operations = [ - migrations.RenameField( - model_name='submission', - old_name='location', - new_name='key', - ), - ] diff --git a/interface/migrations/0011_auto_20200907_0234.py b/interface/migrations/0011_auto_20200907_0234.py deleted file mode 100644 index 1738cc9..0000000 --- a/interface/migrations/0011_auto_20200907_0234.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.1.1 on 2020-09-07 02:34 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('interface', '0010_auto_20200907_0148'), - ] - - operations = [ - migrations.AlterField( - model_name='submission', - name='date', - field=models.DateTimeField(auto_now_add=True), - ), - ] diff --git a/interface/migrations/0012_remove_ensemble_bucket.py b/interface/migrations/0012_remove_ensemble_bucket.py deleted file mode 100644 index 6073ea5..0000000 --- a/interface/migrations/0012_remove_ensemble_bucket.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 3.1.1 on 2020-09-07 04:53 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('interface', '0011_auto_20200907_0234'), - ] - - operations = [ - migrations.RemoveField( - model_name='ensemble', - name='bucket', - ), - ] diff --git a/interface/migrations/0013_auto_20200907_1455.py b/interface/migrations/0013_auto_20200907_1455.py deleted file mode 100644 index 2afe8a8..0000000 --- a/interface/migrations/0013_auto_20200907_1455.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.1.1 on 2020-09-07 04:55 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('interface', '0012_remove_ensemble_bucket'), - ] - - operations = [ - migrations.RenameField( - model_name='resource', - old_name='uri', - new_name='key', - ), - ] diff --git a/interface/migrations/0014_auto_20200909_1016.py b/interface/migrations/0014_auto_20200909_1016.py deleted file mode 100644 index bf2e489..0000000 --- a/interface/migrations/0014_auto_20200909_1016.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.1.1 on 2020-09-09 00:16 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('interface', '0013_auto_20200907_1455'), - ] - - operations = [ - migrations.AddField( - model_name='resource', - name='description', - field=models.TextField(blank=True), - ), - migrations.AlterField( - model_name='resource', - name='key', - field=models.CharField(blank=True, max_length=255), - ), - ] diff --git a/interface/migrations/0015_resource_media_type.py b/interface/migrations/0015_resource_media_type.py deleted file mode 100644 index 269c3e0..0000000 --- a/interface/migrations/0015_resource_media_type.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.1.1 on 2020-09-09 01:46 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('interface', '0014_auto_20200909_1016'), - ] - - operations = [ - migrations.AddField( - model_name='resource', - name='media_type', - field=models.CharField(choices=[('audio', 'Audio'), ('video', 'Video'), ('*', 'General')], default='*', max_length=10), - ), - ] diff --git a/interface/migrations/0016_auto_20200910_2025.py b/interface/migrations/0016_auto_20200910_2025.py deleted file mode 100644 index 0ee32f9..0000000 --- a/interface/migrations/0016_auto_20200910_2025.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.1.1 on 2020-09-10 10:25 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('interface', '0015_resource_media_type'), - ] - - operations = [ - migrations.AlterField( - model_name='resource', - name='media_type', - field=models.CharField(choices=[('audio', 'Audio'), ('video', 'Video'), ('general', 'General')], default='*', max_length=10), - ), - ] diff --git a/interface/migrations/0017_auto_20200914_0943.py b/interface/migrations/0017_auto_20200914_0943.py deleted file mode 100644 index bb2e88a..0000000 --- a/interface/migrations/0017_auto_20200914_0943.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 3.1.1 on 2020-09-13 23:43 - -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('interface', '0016_auto_20200910_2025'), - ] - - operations = [ - migrations.AddField( - model_name='ensemble', - name='admins', - field=models.ManyToManyField(to=settings.AUTH_USER_MODEL), - ), - migrations.AlterField( - model_name='submission', - name='instrument', - field=models.CharField(max_length=100, verbose_name='Instrument / Voice'), - ), - ] diff --git a/interface/migrations/0018_auto_20200914_1009.py b/interface/migrations/0018_auto_20200914_1009.py deleted file mode 100644 index cf7a09e..0000000 --- a/interface/migrations/0018_auto_20200914_1009.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 3.1.1 on 2020-09-14 00:09 - -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('interface', '0017_auto_20200914_0943'), - ] - - operations = [ - migrations.AddField( - model_name='resource', - name='visible', - field=models.BooleanField(default=True), - ), - migrations.AlterField( - model_name='ensemble', - name='admins', - field=models.ManyToManyField(related_name='ensembles', to=settings.AUTH_USER_MODEL), - ), - ] diff --git a/interface/migrations/0019_project_owner.py b/interface/migrations/0019_project_owner.py deleted file mode 100644 index 14ed726..0000000 --- a/interface/migrations/0019_project_owner.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.1.1 on 2020-10-03 09:28 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('interface', '0018_auto_20200914_1009'), - ] - - operations = [ - migrations.AddField( - model_name='project', - name='owner', - field=models.CharField(blank=True, max_length=255), - ), - ] diff --git a/interface/migrations/0020_auto_20201003_2103.py b/interface/migrations/0020_auto_20201003_2103.py deleted file mode 100644 index 7d594fd..0000000 --- a/interface/migrations/0020_auto_20201003_2103.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.1.1 on 2020-10-03 11:03 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('interface', '0019_project_owner'), - ] - - operations = [ - migrations.RenameField( - model_name='submission', - old_name='key', - new_name='url', - ), - migrations.AddField( - model_name='submission', - name='private', - field=models.BooleanField(default=False), - ), - ] diff --git a/interface/migrations/0021_project_description.py b/interface/migrations/0021_project_description.py deleted file mode 100644 index b2a9a1c..0000000 --- a/interface/migrations/0021_project_description.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.1.1 on 2020-10-05 03:41 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('interface', '0020_auto_20201003_2103'), - ] - - operations = [ - migrations.AddField( - model_name='project', - name='description', - field=models.TextField(blank=True), - ), - ] diff --git a/interface/migrations/0022_auto_20210303_2043.py b/interface/migrations/0022_auto_20210303_2043.py deleted file mode 100644 index 3f77f10..0000000 --- a/interface/migrations/0022_auto_20210303_2043.py +++ /dev/null @@ -1,34 +0,0 @@ -# Generated by Django 3.1.1 on 2021-03-03 09:43 - -from django.db import migrations, models -from django.utils.text import slugify - -def create_slugs(apps, schema_editor): - for model in ('Ensemble', 'Project'): - M = apps.get_model('interface', model) - for instance in M.objects.all(): - if instance.slug == '': - instance.slug = slugify(instance.name) - instance.save() - -class Migration(migrations.Migration): - - dependencies = [ - ('interface', '0021_project_description'), - ] - - operations = [ - migrations.AddField( - model_name='ensemble', - name='slug', - field=models.SlugField(default='', editable=False, max_length=100), - preserve_default=False, - ), - migrations.AddField( - model_name='project', - name='slug', - field=models.SlugField(default='', editable=False, max_length=100), - preserve_default=False, - ), - migrations.RunPython(create_slugs) - ] diff --git a/interface/migrations/0023_ensemble_storage.py b/interface/migrations/0023_ensemble_storage.py deleted file mode 100644 index ae04499..0000000 --- a/interface/migrations/0023_ensemble_storage.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 3.1.1 on 2021-03-12 02:56 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('interface', '0022_auto_20210303_2043'), - ] - - operations = [ - migrations.AddField( - model_name='ensemble', - name='storage', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='byostorage.userstorage'), - ), - ] diff --git a/interface/migrations/0024_auto_20210312_1712.py b/interface/migrations/0024_auto_20210312_1712.py deleted file mode 100644 index 19daf29..0000000 --- a/interface/migrations/0024_auto_20210312_1712.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.1.1 on 2021-03-12 06:12 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('interface', '0023_ensemble_storage'), - ] - - operations = [ - migrations.AddField( - model_name='project', - name='accept_submissions', - field=models.BooleanField(default=False, help_text='Allow media submissions from participants'), - ), - migrations.AddField( - model_name='project', - name='has_items', - field=models.BooleanField(default=True, help_text='Enable items to be added from the library'), - ), - ] diff --git a/interface/migrations/0025_auto_20210312_1713.py b/interface/migrations/0025_auto_20210312_1713.py deleted file mode 100644 index 5eadfaa..0000000 --- a/interface/migrations/0025_auto_20210312_1713.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.1.1 on 2021-03-12 06:13 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('interface', '0024_auto_20210312_1712'), - ] - - operations = [ - migrations.RenameField( - model_name='project', - old_name='has_items', - new_name='enable_library', - ), - migrations.RenameField( - model_name='project', - old_name='accept_submissions', - new_name='enable_submissions', - ), - ] diff --git a/interface/migrations/0026_auto_20210313_0926.py b/interface/migrations/0026_auto_20210313_0926.py deleted file mode 100644 index e8c6b19..0000000 --- a/interface/migrations/0026_auto_20210313_0926.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.1.1 on 2021-03-12 22:26 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('interface', '0025_auto_20210312_1713'), - ] - - operations = [ - migrations.RenameField( - model_name='project', - old_name='deadline', - new_name='event_date', - ), - ] diff --git a/interface/migrations/0027_auto_20210322_1154.py b/interface/migrations/0027_auto_20210322_1154.py deleted file mode 100644 index e4c8439..0000000 --- a/interface/migrations/0027_auto_20210322_1154.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.1.1 on 2021-03-22 00:54 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('interface', '0026_auto_20210313_0926'), - ] - - operations = [ - migrations.AlterField( - model_name='wikipage', - name='title', - field=models.CharField(max_length=255), - ), - ] diff --git a/interface/migrations/0028_ensemble_details.py b/interface/migrations/0028_ensemble_details.py deleted file mode 100644 index 0aa6ec2..0000000 --- a/interface/migrations/0028_ensemble_details.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.1.1 on 2021-05-05 05:10 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('interface', '0027_auto_20210322_1154'), - ] - - operations = [ - migrations.AddField( - model_name='ensemble', - name='details', - field=models.TextField(blank=True), - ), - ] diff --git a/interface/models.py b/interface/models.py index 1804c87..d7810c6 100644 --- a/interface/models.py +++ b/interface/models.py @@ -4,19 +4,16 @@ from django.utils import timezone from django.conf import settings from django.shortcuts import resolve_url +from byostorage.user import BYOStorage + import random -import boto3 from datetime import datetime from urllib.parse import urlparse import os.path -s3client = boto3.client('s3', **getattr(settings, 'S3_CREDENTIALS', {})) - -BUCKET = settings.AWS_BUCKET - MEDIA_TYPES = [ ('audio', "Audio"), ('video', "Video"), @@ -24,14 +21,14 @@ MEDIA_TYPES = [ ] def rough_date(d): - days = (self.event_date - timezone.now().date()).days + days = (d - timezone.now()).days in_past = days < 0 if in_past: days = abs(days) if days ==0: return "today!" if days >= 7: - return in_past, "{0:d} weeks, {1:d} days".format(days / 7, days % 7) + return in_past, "{0:d} weeks, {1:d} days".format(int(days / 7), int(days % 7)) return in_past, f"{days} days" @@ -39,16 +36,25 @@ def generate_code(length=9): return "".join([ random.choice('0123456789') for _ in range(length) ]) class Ensemble(models.Model): - name = models.CharField(max_length=100) - code = models.CharField(max_length=9, default=generate_code) - passphrase = models.CharField(max_length=100) + ''' A group that plays together + + ''' + name = models.CharField(max_length=100, + help_text="Display name") + slug = models.SlugField(max_length=100, editable=False, unique=True, + help_text="Short name for the ensemble - used for folders") + code = models.CharField(max_length=9, default=generate_code, + help_text="Ensemble registration code") + passphrase = models.CharField(max_length=100, + help_text="Used to register ensembles") admins = models.ManyToManyField('auth.User', related_name='ensembles') - slug = models.SlugField(max_length=100, editable=False) - details = models.TextField(blank=True) - storage = models.ForeignKey('byostorage.UserStorage', null=True, on_delete=models.SET_NULL) + details = models.TextField(blank=True, + help_text="Description of the ensemble (markdown)") + storage = models.ForeignKey('byostorage.UserStorage', null=True, on_delete=models.SET_NULL, + help_text="Default storage for this ensemble") def active_projects(self): - return self.projects.filter(active=True) + return self.projects.filter(active=True).order_by('event_date') def ensemble_code(self): code = str(self.code) @@ -62,24 +68,24 @@ class Ensemble(models.Model): def __str__(self): return self.name + class Project(models.Model): + ''' A Project linked to an ensemble + ''' name = models.CharField(max_length=100) ensemble = models.ForeignKey(Ensemble, related_name='projects', on_delete=models.CASCADE, null=True) - description = models.TextField(blank=True) + description = models.TextField(blank=True, + help_text="Markdown format") active = models.BooleanField(default=True) - event_date =models.DateField(null=True, blank=True) + event_date =models.DateTimeField(null=True, blank=True) owner = models.CharField(max_length=255, blank=True) - slug = models.SlugField(max_length=100, editable=False) - enable_library = models.BooleanField(default=True, help_text="Enable items to be added from the library") - enable_submissions = models.BooleanField(default=False, help_text="Allow media submissions from participants") + + class Meta: + ordering = ['active', '-pk'] @property def submissions(self): - return self.all_submissions.filter(complete=True).order_by('-pk') - - def presigned_post(self, object_name, fields=None, conditions=None, expires=3600): - key = os.path.join(self.slug, object_name) - return s3client.generate_presigned_post(BUCKET, key, Fields=fields or {}, Conditions=conditions or [], ExpiresIn=expires) + return self.all_submissions.order_by('-pk') @property def days(self): @@ -87,42 +93,59 @@ class Project(models.Model): @property def has_happened(self): - return self.event_date < timezone.now().date() + return self.event_date < timezone.now() - def save(self): - if not self.slug: - self.slug = slugify(self.name) - super(Project, self).save() + @property + def rough_date(self): + in_past, s = rough_date(self.event_date) + if in_past: + return f"{s} ago" + return f"In {s}" + + @property + def folder(self): + print(f"{self.ensemble.storage_id}:{self.ensemble.slug}/{self.slug}") + return f"{self.ensemble.storage_id}:{self.ensemble.slug}/{self.slug}" def __str__(self): return self.name +class Module(models.Model): + ''' Enable modules on a oriject + ''' + name = models.SlugField(max_length=20, choices=[ (x, x.title()) for x in settings.POLYPHONIC_MODULES ]) + project = models.ForeignKey(Project, related_name="modules", on_delete=models.CASCADE) + +def resource_key(resource, filename): + return f'{resource.project.folder}/resources/{filename}' + class Resource(models.Model): + ''' A viewable file resource attached to a project + + e.g PDF instructions, MP3 backing track + ''' project = models.ForeignKey(Project, related_name='resources', on_delete=models.CASCADE) name = models.CharField(max_length=100) description = models.TextField(blank=True) - key = models.CharField(max_length=255, blank=True) + file = models.FileField(storage=BYOStorage(), upload_to=resource_key) media_type = models.CharField(max_length=10, choices=MEDIA_TYPES, default='*') visible = models.BooleanField(default=True) - def key_template(self): - return "{}/${{filename}}".format(slugify(self.name)) + class Meta: + ordering = ['-visible', '-pk'] def accept(self): if self.media_type == 'general': return ".*" return f"{self.media_type}/*" - def presigned_url(self): - if not self.key: - return "" - params = {'Bucket': BUCKET, 'Key': self.key} - return s3client.generate_presigned_url('get_object', Params=params, ExpiresIn=3600*24) - def __str__(self): return self.name class WikiPage(models.Model): + ''' An editable wiki page for the project in markdown format + + ''' project = models.ForeignKey(Project, related_name='wiki_pages', on_delete=models.CASCADE) title = models.CharField(max_length=255) markdown = models.TextField() @@ -132,9 +155,9 @@ class WikiPage(models.Model): def __str__(self): return self.title - +""" class Submission(models.Model): - project = models.ForeignKey(Project, related_name='all_submissions', on_delete=models.CASCADE) + project = models.ForeignKey(Project, related_name='old_submissions', on_delete=models.CASCADE) date = models.DateTimeField(auto_now_add=True, ) name = models.CharField(max_length=255) instrument = models.CharField(max_length=100, verbose_name="Instrument / Voice") @@ -180,3 +203,4 @@ class Submission(models.Model): def __str__(self): return f"{self.name}: {self.date}" +""" \ No newline at end of file diff --git a/interface/static/interface/css/polyphonic.css b/interface/static/interface/css/polyphonic.css index ea8351e..828ab97 100644 --- a/interface/static/interface/css/polyphonic.css +++ b/interface/static/interface/css/polyphonic.css @@ -1,366 +1,63 @@ +@font-face { + font-family: MartinHand; + src: url('/static/fonts/Martinhand3.ttf'); +} :root { - --border-color: #292929; - --gray-blue: #667788; - --light-blue: #c5eff7; - --light-grey: #EEEEEE; + --primary: #485fc7; } -@font-face { - font-family: 'Quicksand'; - src: url('../../fonts/Quicksand_Book.otf'); +.fancy { + font-family: MartinHand; + color: var(--primary); } -@font-face { - font-family: 'QuicksandBold'; - src: url('../../fonts/Quicksand_Bold_Oblique.otf'); +.has-text-shadow { + text-shadow: 1px 1px 2px #000; } -.debug DIV { - border: 1px dashed #DDD; +.is-form-group { + max-width: 600px; + align-self: center; } -HTML { - height: 100%; +.is-centered { + margin: auto; } -BODY { - background-image: url('../background.png'); - background-position: center top; - background-size: 100%; - background-repeat: no-repeat; - margin: 0px; - height: 100%; - display: flex; - align-items: flex-start; - justify-content: center; +.is-action { + cursor: pointer; } -.main { - max-width: 1280px; - margin: 10px auto; - border: 1px solid var(--border-color); - border-radius: 5px; - font-family: 'Quicksand', Arial, Helvetica, sans-serif; - font-size: 14pt; - background-color: white; +.menu-label { + color: var(--primary); } -.content { - margin: 20px; - flex-direction: column; +.button.is-primary, .button.is-primary:hover { + background-color: var(--primary); } -.narrow { - max-width: 500px; - margin: 0px auto; +A.admin-link:after { + content: "*"; } -.wide { - width: 1200px; +TEXTAREA.input { + height: 400px; } -.collapse { - display: flex; - flex-direction: row; - justify-content: space-around; -} - -@media all and (max-width: 1200px) { - .wide { - width: 900px; - } -} - -@media all and (max-width: 900px) { - .mdhide { - display: none; - } - - .wide { - width: auto; - } -} - -@media all and (max-width: 700px) { - .smhide { - display: none; - } - .collapse { - flex-direction: column; - } - .wide { - width: auto; - } -} - - -/* HEADER BAR */ - -.navigation { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: 50px; - background-color: var(--gray-blue); - color: var(--light-blue) !important; -} - -.navigation > * { - white-space: nowrap; -} - -.navigation A, -.navigation A:visited { - color: var(--light-blue); - text-decoration: none; -} - -.navigation .brand { - font-family: 'QuicksandBold', 'Quicksand', Arial, Helvetica, sans-serif; - font-size: 1.5rem; - margin: auto 20px; -} - -UL.nav-buttons { - display: flex; - list-style: none; -} -UL.nav-buttons > LI { - margin: 2px 10px; -} - -/* FORMS */ - -FORM.vertical { - display: flex; - flex-direction: column; - max-width: 400px; - margin: 0px auto; -} - -LABEL { - margin-top: 20px; -} - -TEXTAREA { - height: 50px; -} - -INPUT[type=checkbox] { - margin-right: auto; -} - -.form-actions { - text-align: right; - margin-top: 20px; -} - -.badge { - display: inline-block; - border: 1px solid var(--gray-blue); - font-weight: bold; - font-size: 0.9em; - border-radius: 10px; - padding: 4px 10px 2px; - margin: 2px; -} - -.btn { - background-color: var(--gray-blue); - display: inline-block; +.control INPUT[type='file'] { border: none; - color: var(--light-blue); - text-decoration: none; - border-radius: 1em; - font-size: 1em; -} - -.btn:hover { - cursor: pointer; - color: white; -} - -.clickable { - cursor: pointer; -} - -.action { - margin: 0px 10px 0px 5px; -} - -.pills { - display: flex; - flex-wrap: wrap; - justify-content: center; - margin-top: 40px; -} - -.pills A { - border: 1px solid var(--gray-blue); - padding: 4px 10px 2px 10px; - margin: 10px 5px; - border-radius: 10px; - white-space: nowrap; -} - -.pills A:hover { - background-color: var(--light-blue); - text-decoration: none -} - -.list-group { - display: flex; - flex-direction: column; -} - -.list-group > * { - border: 1px solid var(--gray-blue); - border-radius: 10px; - padding: 2px 20px; - margin-top: 20px; -} - -.list-group > A:hover { - background-color: var(--light-blue); - text-decoration: none; -} - -/* PROGRESS BAR */ - -.progress { - display: relative; - border: 1px solid var(--border-color); - border-radius: 5px; - margin: 20px 10px; -} - -.progress-bar { - width: 0%; - height: 1.5em; - background-color: var(--light-blue); - border-radius: 5px; -} - -.text-center { + width: 80%; text-align: center; + margin: auto 10%; } -A, A:visited { - text-decoration: none; - color: var(--gray-blue); - font-weight: bold; -} - -A:hover { - text-decoration: underline; -} - -H1 { - text-align: center; -} - -TABLE { - border-spacing: 0px; -} - -TD, TH { - padding: 3px 6px; - text-align: left; -} - -TABLE THEAD TR { - background-color: var(--gray-blue); - color: var(--light-blue); - font-weight: bolder; -} - -TABLE THEAD TH { - padding: 5px 6px; -} - -TABLE.zebra TR:nth-child(even) { - background-color: var(--light-grey); -} - -TABLE.horizontal TH { - text-align: right; -} - -TABLE.horizontal TD, -TABLE.horizontal TH { - padding: 5px; -} - -TABLE SELECT { - width: 100%; -} - -.resource-player { - width: 100%; - border-radius: 10px; - max-width: 640px; - max-height: 640px; - margin: 5px; - border: 1px solid var(--border-color); -} - -.dz-clickable { - text-align: center; -} -.scrollable { - max-height: 200px; - overflow: auto; +.project-footer { + position: fixed; + bottom: 0px; + right: 0px; background-color: #EEE; - border: 1px solid var(--border-color); - border-radius: 10px; - padding: 5px; -} - -.admin-tools { - float: right; - padding: 10pt; -} - -.disabled { - background-color: #DDD; -} - -.dz-image { - width: 240px !important; -} - -.dz-progress { - width: 200px !important; - margin-left: -100px !important; - margin-top: 24px !important; -} - -.item-table { - border-collapse: collapse; -} - -.item-table TD { - border: 1px solid #999; -} -TD.select-cell { - padding: 0px !important; -} - -.select-cell SELECT { - border: none; - background-color: transparent; - height: 30px; - font-family: inherit; - font-size: inherit; -} -#tag-list DIV { - padding: 5px; -} - -DIV.selected { - background-color: var(--gray-blue); - color: white; - border: 1px solid #999; - border-radius: 5px; + padding: 10px 10px; + margin-top: 30px; + border-top-left-radius: 6px; } \ No newline at end of file diff --git a/interface/templates/base.html b/interface/templates/base.html index e12acb2..5434f38 100644 --- a/interface/templates/base.html +++ b/interface/templates/base.html @@ -1,50 +1,50 @@ {% load static %} - + - + - + + {% block title %}Polyphonic{% endblock %} + {% block media %}{% endblock %} -
- {% block navigation %} - - {% endblock %} - -
- {% block content %} -

No content!

- {% endblock %} + {% block navigation %} +
+ + + + + {% endblock %} + +{% block content %} +

No content!

+{% endblock %} diff --git a/interface/templates/interface/default_form.html b/interface/templates/interface/default_form.html index 837c798..0e762e3 100644 --- a/interface/templates/interface/default_form.html +++ b/interface/templates/interface/default_form.html @@ -1,14 +1,15 @@ {% extends "interface/project_base.html" %} +{% load crispy_forms_tags %} + +{% block media %} +{{ form.media }} +{% endblock %} {% block page %} -
-

{% firstof title view.title %}

-
- {% csrf_token %} - {{ form }} -
- -
-
+

{% firstof title view.title %}

+
+
+ {% crispy form %} +
{% endblock %} \ No newline at end of file diff --git a/interface/templates/interface/ensemble_project_list.html b/interface/templates/interface/ensemble_project_list.html index 326a6d7..cbe4b15 100644 --- a/interface/templates/interface/ensemble_project_list.html +++ b/interface/templates/interface/ensemble_project_list.html @@ -1,22 +1,73 @@ {% extends "interface/project_base.html" %} +{% load md2 %} + +{% block admin %} + + + Add new + +{% endblock %} + + {% block page %} -
-

Projects for {{ ensemble.name }}

-
- {% for project in ensemble.active_projects %} - -

{{ project.name }}

-

- {% if project.deadline %}In {{ project.deadline|timeuntil }}
{% endif %} - {% if project.works.count %}{{ project.works.count }} works
{% endif %} - {% if project.submissions.count %}{{ project.submissions.count }} submissions
{% endif %} -

-
- {% endfor %} +

Projects for {{ ensemble.name }}

+ +
+{% for project in ensemble.active_projects %} +
+
+ + +
+

{{ project.name }}

+

{{ project.rough_date }}

+
+
+
+
+ {{ project.description | markdown }} +
+

+ {% if project.deadline %}In {{ project.deadline|timeuntil }}
{% endif %} + {% if project.works.count %} + + {{ project.works.count }} works + +
+ {% endif %} + {% if project.submissions.count %}{{ project.submissions.count }} submissions
{% endif %} +

+
+
-
- {{ ensemble.ensemble_code }} +{% empty %} +
+
+

No projects currently planned

+

Go put your feet up!

+
+
+{% endfor %} +
+ +{% if request.is_admin %} +
+
+
+

Admin Details

+
+
+ +

+ Joining instructions for participants

+ URL: {{ ensemble_url }}
+ Code: {{ ensemble.ensemble_code }}
+ Passphrase: {{ ensemble.passphrase }} +

+
+{% endif %} + {% endblock %} \ No newline at end of file diff --git a/interface/templates/interface/project_base.html b/interface/templates/interface/project_base.html index efc5742..08f7733 100644 --- a/interface/templates/interface/project_base.html +++ b/interface/templates/interface/project_base.html @@ -2,40 +2,109 @@ {% block content %} -{% if request.is_admin %} -
-{% block admin %} -{% endblock %} -
-{% endif %} -

{{ project.name }}

-{% block page %} -No content -{% endblock %} +
+
+
+ +
+
+ +
{% if project %} - Project info - {% for page in project.wiki_pages.all %} - {{ page.title }} - {% endfor %} - {% endif %} +
+
    +
  • Info
  • + {% if project.wiki_pages.count %} +
  • Pages
  • + {% endif %} + {% if project.resources.count %} +
  • Resources
  • + {% endif %} + {% if project.enable_library %} +
  • My Music
  • + {% endif %} + {% if project.enable_submissions %} +
  • Send File
  • + {% endif %} +
+
- {% if project and project.enable_submissions%} - Resources - - {% if request.is_admin %} - Submissions {% endif %} - Send a file - {% endif %} - - {% include "library/project_menu.html" %} +
+ {% if request.is_admin %} +
+ {% block admin %} + {% endblock %} +
+ {% endif %} + {% if project %}

{{ project.name }}

{% endif %} + {% block page %} + No content + {% endblock %} + +
+ +{% if ensemble %} + +{% endif %} {% endblock %} diff --git a/interface/templates/interface/project_detail.html b/interface/templates/interface/project_detail.html index a75b1e3..8fc3659 100644 --- a/interface/templates/interface/project_detail.html +++ b/interface/templates/interface/project_detail.html @@ -1,32 +1,62 @@ {% extends "interface/project_base.html" %} {% load md2 %} +{% load polyphonic %} +{% block admin %} + + + Add Page + + + + Edit + +{% endblock %} {% block page %} -
+
{% if project.event_date %} -

+

+ {{ project.event_date|date:"l jS F Y, g:i A" }} {% if project.has_happened %} - {{ project.event_date|timesince }} ago. + ({{ project.event_date|roughtimesince }} ago) {% else %} - In {{ project.event_date|timeuntil }}. + (in {{ project.event_date|roughtimeuntil }}...) {% endif %}

{% endif %} -

{{ project.description|markdown }}

- - {% if project.owner %} -

Project email: {{ project.owner }}

- {% endif %} - - {% if project.enable_submissions %} - {% include 'submissions/project_detail.html' %} - {% endif %} - - {% if project.enable_library %} - {% include 'library/project_detail.html' %} - {% endif %} - +
+ {% if project.description %} +

{{ project.description|markdown }}

+ {% else %} +

No description

+ {% endif %}
+ + + + {% if 'library' in modules %} +
+ {% include 'library/project_detail.html' %} +
+ {% endif %} + + {% if 'submission' in modules %} +
+ {% include 'submissions/project_detail.html' %} +
+ {% endif %} + + {% if project.owner %} +
+ {% if project.owner.email %} + The project owner is {{ project.owner }} + {% else %} + The project owner is {{ project.owner }}. + {% endif %} +
+ {% endif %} + +
{% endblock %} diff --git a/interface/templates/interface/project_form.html b/interface/templates/interface/project_form.html index a65ec70..0238b1c 100644 --- a/interface/templates/interface/project_form.html +++ b/interface/templates/interface/project_form.html @@ -1,15 +1,18 @@ {% extends "interface/project_base.html" %} +{% load crispy_forms_tags %} + +{% block media %} +{{ form.media }} +{% endblock %} {% block page %} -
-

{{ title }}

-

{{ instructions }}

-
- {% csrf_token %} - {{ form }} -
- -
-
+

{% firstof title view.title %}

+
+
+ {% if instructions %} +

{{ instructions }}

+ {% endif %} + {% crispy form %} +
{% endblock %} \ No newline at end of file diff --git a/interface/templates/interface/project_submissions.mk b/interface/templates/interface/project_submissions.mk deleted file mode 100644 index 7bf95c4..0000000 --- a/interface/templates/interface/project_submissions.mk +++ /dev/null @@ -1,11 +0,0 @@ - -ALL = {{ targets|join:" " }} - --include "local.mk" - -all: ${ALL} - -{% for s in submissions %} -{{ s.name }}: - curl -o $@ -L {{ s.url }} -{% endfor %} \ No newline at end of file diff --git a/interface/templates/interface/register.html b/interface/templates/interface/register.html index 26a20b0..8217796 100644 --- a/interface/templates/interface/register.html +++ b/interface/templates/interface/register.html @@ -1,29 +1,24 @@ {% extends "base.html" %} +{% load crispy_forms_tags %} {% block content %} +
{% if not request.user.is_authenticated %} {% endif %} -
- {% if current %} -
-

My Ensembles

- -
- {% endif %} -
-
-

Join an ensemble

- {% csrf_token %} - {{ form }} -
- +
+
+

Join an ensemble

+ + {% csrf_token %} + {{ form | crispy }} +
+
+
- -
+
+
+
+
{% endblock %} diff --git a/interface/templates/interface/resource_list.html b/interface/templates/interface/resource_list.html index 600b2b1..56102d4 100644 --- a/interface/templates/interface/resource_list.html +++ b/interface/templates/interface/resource_list.html @@ -2,34 +2,40 @@ {% load md2 %} {% block admin %} - Add new + + + Add new + {% endblock %} - {% block page %} -
-

Resources

-
- {% for resource in object_list %} - {% with download=resource.presigned_url %} -
- {% if request.is_admin %} -
- - - - - - +{% block page %} +
+{% for resource in object_list %} + {% with download=resource.file.url %} +
+
+
+ +
+ {% if download %} + {{ resource.name }} + {% else %} + {{ resource.name }} + {% endif %} +
+ +
+ {% if request.is_admin %} + + + + + + + {% endif %} +
- {% endif %} -

- {{ resource.name }} - {% if download %} - - Download - - {% endif %} -

+

{{ resource.description|markdown }} {% if not resource.visible %} @@ -37,11 +43,16 @@ {% endif %}

{% if download and resource.media_type == 'audio' %} - + {% endif %} +
- {% endwith %} - {% endfor %}
+ {% endwith %} +{% empty %} +
+

There are resources for this project

+
+{% endfor %}
{% endblock %} diff --git a/interface/templates/interface/submission_create.html b/interface/templates/interface/submission_create.html deleted file mode 100644 index bd2a4a7..0000000 --- a/interface/templates/interface/submission_create.html +++ /dev/null @@ -1,24 +0,0 @@ -{% extends "interface/project_base.html" %} - -{% block page %} -
-

Excellent, you are ready to make a submission!

-

- Please enter some basic information so we can identify your submission and - note anything that might be relevant.
- Most people will want to upload their - file directly but if you have your own cloud storage provider you can send a - public link to your submission instead. -

-
-
-
- {% csrf_token %} - {{ form }} -
- Cancel - -
-
-
-{% endblock %} \ No newline at end of file diff --git a/interface/templates/interface/submission_detail.html b/interface/templates/interface/submission_detail.html deleted file mode 100644 index a9ce0d3..0000000 --- a/interface/templates/interface/submission_detail.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends "interface/project_base.html" %} - -{% block page %} -
-

Thankyou for your submission!

- - - - - - {% if can_download %} - - {% endif %} - -
From:{{ submission.name }}
Instrument:{{ submission.instrument }}
Notes:{{ submission.notes }}
Download:{{ submission.download_name }}
-
-{% endblock %} \ No newline at end of file diff --git a/interface/templates/interface/submission_link.html b/interface/templates/interface/submission_link.html deleted file mode 100644 index 339a7ef..0000000 --- a/interface/templates/interface/submission_link.html +++ /dev/null @@ -1,18 +0,0 @@ -{% extends "interface/project_base.html" %} - -{% block page %} -
-

Link to cloud storage

-

Please paste the full link from your storage provider

-
-
-
- {% csrf_token %} - {{ form }} -
- Cancel - -
-
-
-{% endblock %} \ No newline at end of file diff --git a/interface/templates/interface/submission_list.html b/interface/templates/interface/submission_list.html deleted file mode 100644 index 8af3f4e..0000000 --- a/interface/templates/interface/submission_list.html +++ /dev/null @@ -1,36 +0,0 @@ -{% extends "interface/project_base.html" %} - -{% block page %} - - - - - - - - - - {% for submission in object_list %} - - - - - - - - {% endfor %} - -
DateTimeNameInstrument
{{ submission.date.date }}{{ submission.date.time }}{{ submission.name }}{{ submission.instrument }} - - {% if submission.private %} - - {% else %} - - {% endif %} - -
-{% endblock %} \ No newline at end of file diff --git a/interface/templates/interface/submission_preview.html b/interface/templates/interface/submission_preview.html deleted file mode 100644 index 4e574e3..0000000 --- a/interface/templates/interface/submission_preview.html +++ /dev/null @@ -1,17 +0,0 @@ -{% extends "interface/project_base.html" %} - -{% block page %} -
-
- Back -
- {% with object.download_url as url %} - -

- {{ object.name }} ({{ object.instrument }}) - {{ object.date }} - Download -

- {% endwith %} -
-{% endblock %} \ No newline at end of file diff --git a/interface/templates/interface/wiki.html b/interface/templates/interface/wiki.html index 1fde2fd..93d76fd 100644 --- a/interface/templates/interface/wiki.html +++ b/interface/templates/interface/wiki.html @@ -1,11 +1,15 @@ {% extends "interface/project_base.html" %} {% block admin %} - + + + Edit + {% endblock %} {% block page %} -
+

{{ wikipage.title }}

+
{{ wiki_html|safe }}
{% endblock %} \ No newline at end of file diff --git a/interface/templates/interface/wikipage_form.html b/interface/templates/interface/wikipage_form.html index 038b83e..ec8f08a 100644 --- a/interface/templates/interface/wikipage_form.html +++ b/interface/templates/interface/wikipage_form.html @@ -1,4 +1,5 @@ {% extends "interface/project_base.html" %} +{% load crispy_forms_tags %} {% block page %} {% endblock %} {% block page %} -

Works / {{ document.work.name }}

-
-
- -
-
-
-

Page: /

-
- - -
-
-
No tag
-
Score
- {% for instrument in document.work.instruments %} -
{{ instrument.1 }}
- {% endfor %} -
+

{{ document.work.name }}

+
+
+
+
+
+
+

Page - / -

+
+
+
+
+ + + + {% for inst in json_data.instruments.values %} + + + + + + + + +
+
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+ +

{{ document.upload.name }}

{% endblock %} {% block scripts %} - + {{ json_data|json_script:"data" }} {% endblock %} \ No newline at end of file diff --git a/library/templates/library/item_list.html b/library/templates/library/item_list.html index f4e4421..e8033af 100644 --- a/library/templates/library/item_list.html +++ b/library/templates/library/item_list.html @@ -1,34 +1,60 @@ {% extends "interface/project_base.html" %} {% block admin %} - Change items + + + Change items + {% endblock %} {% block page %} -

- This page lets you download a complete set of music for your part by selecting your instrument - and optionally a part (e.g. Flute 1 or 2).
- You can also tweak which parts you get for each piece, or click on a piece for more download options. -

-
+ {% csrf_token %} -
- -

- -

- +
+
+
+ + + Your Instrument + + + + + + {% for inst in instruments %} + + + + + + + +
+ + + +
- + +
+ + @@ -36,33 +62,79 @@ + + {% endfor %} + + + + + +
PieceRunning time Part
{{ forloop.counter }}. + {% if request.is_admin %} {{ item.work.name }} + {% else %} + {{ item.work.name }} + {% endif %} {% firstof item.work.running_time "------" %} + + + +
+ + {{ running_time }} + + + Individual files (zipped) +
+

+ This page lets you download a complete set of music for your part by selecting your instrument + and optionally a part (e.g. Flute 1 or 2).
+ You can also tweak which parts you get for each piece, or click on a piece for more download options. +

{% endblock %} {% block scripts %} +{{ instruments|json_script:'instruments' }} {% endblock %} \ No newline at end of file diff --git a/library/templates/library/item_list_manage.html b/library/templates/library/item_list_manage.html index 37e07e7..97e16d9 100644 --- a/library/templates/library/item_list_manage.html +++ b/library/templates/library/item_list_manage.html @@ -1,8 +1,14 @@ {% extends "interface/project_base.html" %} {% block admin %} -Save -Add + + + Add + + + + Save + {% endblock %} {% block page %} diff --git a/library/templates/library/project_detail.html b/library/templates/library/project_detail.html index b0046a2..3b7d3f6 100644 --- a/library/templates/library/project_detail.html +++ b/library/templates/library/project_detail.html @@ -1 +1,12 @@ -

Click the 'My Music' button below to access your parts...

\ No newline at end of file +{% with project.items.count as items %} +{% if items %} +

+There {{ items|pluralize:"is,are" }} currently {{ items}} item{{ items|pluralize }} for this project.
+Click the 'My Music' button below to access your parts... +

+{% else %} +

+There are no items listed yet for this project - please check back later... +

+{% endif %} +{% endwith %} \ No newline at end of file diff --git a/library/templates/library/project_menu.html b/library/templates/library/project_menu.html index 0d2a90f..bbfffcd 100644 --- a/library/templates/library/project_menu.html +++ b/library/templates/library/project_menu.html @@ -1,6 +1,9 @@ + + \ No newline at end of file diff --git a/library/templates/library/work_detail.html b/library/templates/library/work_detail.html index 6652f84..fdc9c41 100644 --- a/library/templates/library/work_detail.html +++ b/library/templates/library/work_detail.html @@ -1,93 +1,211 @@ {% extends 'interface/project_base.html' %} {% load path_filters %} +{% block media %} + + +{% endblock %} + {% block admin %} - Upload file + + + Edit + + + + Add to project + {% endblock %} {% block page %} -

{{ work.name }} {% if work.running_time %}({{ work.duration }}){% endif %} [{{ work.identifier }}]

-

{{ work.composer }}{% if work.version %} - {{ work.version }}{% endif %}

-

{{ work.notes }}

-{% if work.collection %} -

Location: {{ work.collection }} [{{ work.collection_index }}]

-{% endif %} - -{% if work.parent %} -

From {{ work.parent.name }} - {{ work.parent.composer }}

-{% endif %} - -{% if work.related_works.count %} -

Related

- -{% endif %} + +

{% firstof work.composer "Unattributed" %}{% if work.edition %} - {{ work.edition }}{% endif %}

+
+

{{ work.notes }}

-

Loans +

+ Location: {{ work.collection }} [{{ work.identifier }}]
+ Running time: {{ work.duration }}
+ Licence: {{ work.get_licence_display }}
+ {% for meta in work.meta %} + {{ meta.get_name_display }}: {{ meta.value }}
+ {% endfor %} +

+ + {% if work.parent %} +

From {{ work.parent.name }} - {{ work.parent.composer }} +

+ {% endif %} + + {% if work.related_works.count %} +

Related

+ + {% endif %} +
+
+ +
+
+
+

+ + Printed Parts +

+
+ {% for inst, c in work.physical_parts %} + {{ inst }} ({{ c }}) + {% empty %} +

No printed parts listed

+ {% endfor %} +
+
+
+
+
+

+ + Digital Parts +

+
+ {% if work.digital_parts %} + Full Set + {% endif %} + {% for part in work.digital_parts %} + {{ part.instrument }} + {% empty %} +

No digital parts available

+ {% endfor %} +
+
+
+
+
+
+
+
+

+ + Files +

+
+
+
+ + + + + + + + + {% for doc in work.docs.all %} + + + + + + {% endfor %} + +
FilenameSections +
+ {{ doc.upload.name|basename }} + {% for part in doc.sections.all %} + {{ part.instrument }} + {% endfor %} + + {% if request.is_admin %} + + + {% endif %} +
+
+ {% if request.is_admin %} +
+

Upload files

+
+ {% csrf_token %} +
+
+ {% endif %} +
+
+
{% if request.is_admin %} - +
+
+
+

+ + Loans +

+ + Checkout + +
+ + + + + + + + + + + {% for item in work.current_loans %} + + + + + + + {% empty %} + + + + {% endfor %} + +
EnsembleProjectChecked OutDue Back
+ {{ item.project.ensemble.name }} + {{ item.project.name }}{{ item.checkout.date|date:"d/m/Y" }}{{ item.due.date|date:"d/m/Y" }}
No current loans
+
+
{% endif %} - - - - - - - - - - - - {% for item in work.current_loans %} - - - - - - - {% empty %} - - {% endfor %} - -
EnsembleProjectChecked OutDue Back
{{ item.project.ensemble.name }}{{ item.project.name }}{{ item.checkout.date|date:"d/m/Y" }}{{ item.due.date|date:"d/m/Y" }}
No current loans
-

Printed Parts

-

-{% for inst, c in work.physical_parts %} - {{ inst }} ({{ c }}) -{% empty %} - No physical parts available -{% endfor %} -

+{% block scripts %} + +{% endblock %} -

Digital Parts - -

-

-{% for part in work.digital_parts %} - {{ part.instrument }} -{% empty %} - No digital parts available -{% endfor %} -

- -

Documents

-
    -{% for doc in work.docs.all %} -
  • - {{ doc.upload.name|basename }} -   - {% with parts=doc.parts.count %} - {% if parts %}[{{ parts }} parts]{% endif %} - {% endwith %} - {% if request.is_admin %} - - {% endif %} -
  • -{% endfor %} -
{% endblock %} \ No newline at end of file diff --git a/library/templates/library/work_list.html b/library/templates/library/work_list.html index b735158..c729c74 100644 --- a/library/templates/library/work_list.html +++ b/library/templates/library/work_list.html @@ -2,26 +2,37 @@ {% load url_tools %} {% block admin %} - Add new +{% if collection_id %} + + + Add a work + +{% endif %} {% endblock %} {% block page %} -

Library for {{ view.ensemble }}

-
-
- - Clear -
-
- +

{{ title }}

+ +
+
+ +
+
+ +
+
+ + +
- - - - + + {% if request.is_admin %} + + + {% endif %} @@ -29,10 +40,11 @@ - - - - + + {% if request.is_admin %} + + + {% endif %} {% empty %} @@ -40,22 +52,20 @@
Work ComposerEditionOrchestrationCollectionCopiesEditionCollectionCopies
{{ work.name }} {{ work.composer|truncatewords:3 }}{{ work.edition|truncatewords:2 }}{{ work.orchestration|truncatewords:2}}{{ work.collection.name }}{{ work.available }}{{ work.edition|truncatewords:2 }}{{ work.collection.name }}{{ work.available }}
No works found
- + {% endblock %} \ No newline at end of file diff --git a/library/templates/library/work_partset.html b/library/templates/library/work_partset.html index 33785a7..834e719 100644 --- a/library/templates/library/work_partset.html +++ b/library/templates/library/work_partset.html @@ -1,31 +1,37 @@ {% extends "interface/project_base.html" %} + + {% block page %} -

Works / {{ work.name }}

{% csrf_token %} - - - - - - - - - {% for part in work.parts %} - - - - - {% endfor %} - - - - -
PartCopies
{{ part.instrument }} - - -
+ +
+ + + Cancel + +
+ + +

{{ work.name }}

+ +

+ You can generate a custom partset for printing - select the number of copies of each you want... +

+ +
+ {% for part in work.digital_parts %} +
+ {{ part.instrument }} + + +
+ {% endfor %} +
{% endblock %} \ No newline at end of file diff --git a/library/tests.py b/library/tests.py index 57c2498..731b8c8 100644 --- a/library/tests.py +++ b/library/tests.py @@ -20,3 +20,31 @@ class IntegrationTestCase(TestCase): def test_integration(self): pass + + def test_movement_from_large_work(self): + ''' + Will be common to store a work which has several movements, but the project is only going to play one. + This also should give us the ability to store an anthology as one Work have Project reference 'no:23' + ''' + + work = self.sel.works.create(name="Some Quartet", composer="Beethoven") + for g in ('vl1', 'vl2', 'vla', 'vc'): + doc = work.docs.create(upload=f'sel/beethoven/some_quartet/some_quartet_{g}.pdf') + doc.sections.create(tag='mvmt:1', start=1, end=3) + doc.sections.create(tag='mvmt:2', start=4, end=8) + doc.sections.create(tag='mvmt:3', start=9, end=12) + doc.sections.create(tag=f'inst:{g}') + + # no tags - get nothing (should it be everything?) + self.assertEqual(work.extract(), []) + + # single tag - should get just that range + self.assertEqual(work.extract('inst:vl1'), [('sel/beethoven/some_quartet/some_quartet_vl1.pdf', None, None)]) + + # single tag - returns all documents with that range + result = work.extract('mvmt:2') + self.assertEqual(len(result), 4) + + # multiple tags - returns the overlapping portion of all documents that have all tags + self.assertEqual(work.extract('inst:vl1', 'mvmt:2'), [('sel/beethoven/some_quartet/some_quartet_vl1.pdf', 4, 8)]) + self.assertEqual(work.extract('inst:vl1', 'inst:vl2'), []) \ No newline at end of file diff --git a/library/urls.py b/library/urls.py index 625ec42..8adb10d 100644 --- a/library/urls.py +++ b/library/urls.py @@ -8,17 +8,22 @@ urlpatterns = [ path('projects//items', views.ProjectItemListView.as_view(), name="item_list"), path('projects//items/manage', views.ProjectItemManageView.as_view(), name="item_list_manage"), path('projects//items/append', views.ProjectItemAddView.as_view(), name="item_list_append"), + + path('library/collections', views.CollectionListView.as_view(), name="collection_list"), + path('library/collections/', views.CollectionWorkListView.as_view(), name="collection_work_list"), + path('library/collection//create', views.WorkAddView.as_view(), name="work_add"), path('library/works', views.WorkListView.as_view(), name="work_list"), - path('library/works/create', views.WorkAddView.as_view(), name="work_add"), path('library/works/', views.WorkDetailView.as_view(), name="work_detail"), + path('library/works//edit', views.WorkUpdateView.as_view(), name="work_edit"), path('library/works//partset', views.WorkPartSetView.as_view(), name="work_partset"), path('library/works//add_to_project', views.WorkAddToProject.as_view(), name="work_add_to_project"), - path('library/documents//upload', views.DocumentAddView.as_view(), name="document_add"), + path('library/works//upload', views.WorkAddDocumentView.as_view(), name="document_add"), + path('library/documents//download', views.DocumentDownloadView.as_view(), name="document_download"), path('library/documents//annotate', views.DocumentAnnotateView.as_view(), name="document_annotate"), path('library/parts//', views.PartDownloadView.as_view(), name="part_download"), ] from django.views.static import serve -urlpatterns.append(path('localstorage/', serve, {'document_root': 'local_storage'})) \ No newline at end of file +urlpatterns.append(path('docs/', serve, {'document_root': 'local_storage'})) \ No newline at end of file diff --git a/library/views.py b/library/views.py index a348608..f0aa72b 100644 --- a/library/views.py +++ b/library/views.py @@ -2,22 +2,25 @@ 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 +from django.http import FileResponse, HttpResponse, JsonResponse from django.db import IntegrityError -from django.db.models import Q, Count +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 Work, Document, Part, INSTRUMENTS +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.Item + model = models.ProjectItem def post(self, request, **kwargs): @@ -44,7 +47,7 @@ class ProjectItemListView(ProjectMixin, ListView): if tag == '-': continue - part = Part.objects.filter(tag=tag, doc__work=pk).select_related('doc').get() + 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) @@ -64,11 +67,16 @@ class ProjectItemListView(ProjectMixin, ListView): 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.Item + model = models.ProjectItem def post(self, request, **kwargs): self.request = request @@ -99,38 +107,61 @@ class ProjectItemAddView(ProjectMixin, UpdateView): 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_queryset(self): - #works = Work.objects.filter(collection__ensembles=self.request.ensemble_id).order_by('name').select_related('collection') - works = Work.objects.filter(collection__allowed_ensembles__ensemble=self.request.ensemble_id).order_by('name').select_related('collection') + 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 - loan_count = Count('project_items', Q(project_items__checkout__lte=now(), project_items__returned=None)) - works = works.annotate(loan_count=loan_count) + def get_queryset(self): + works = self.get_works().order_by('name') q = self.request.GET.get('filter') if q: - works = works.filter(Q(name__contains=q) | Q(composer__contains=q) | Q(tag_list__contains=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 - def get_form(self): - f = super(WorkAddView, self).get_form() - #qs = f.fields['orchestration'].queryset - #f.fields['orchestration'].queryset = qs.filter(ensemble_id__isnull=True) | qs.filter(ensemble_id=self.request.ensemble_id) - qs = f.fields['collection'].queryset - qs = qs.filter(administrators=self.request.user) - f.fields['collection'].queryset = qs - return f + 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 @@ -144,12 +175,26 @@ class WorkAddView(EnsembleMixin, FormView): else: return redirect('work_detail', pk=work.pk) -class WorkDetailView(EnsembleMixin, DetailView): +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" @@ -188,7 +233,7 @@ class WorkPartSetView(EnsembleMixin, DetailView): sections = [] for i, tag in enumerate(parts): - part = work.parts.select_related('doc').get(tag=tag) + 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) @@ -200,25 +245,59 @@ class WorkPartSetView(EnsembleMixin, DetailView): return response def get_queryset(self): - return Version.objects.filter(work__ensemble_id=self.request.ensemble_id) + works = Work.objects.all() -class DocumentDetailView(EnsembleMixin, DetailView): + if not self.request.is_admin: + works = works.filter(collection__allowed_ensembles__ensemble=self.request.ensemble_id) + return works - def get_queryset(self): - return Document.objects.filter(work__ensemble=self.request.ensemble_id).select_related('work') - -class DocumentAddView(EnsembleMixin, CreateView): +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): - self.object = form.save(commit=False) - self.object.work_id = self.kwargs['pk'] - self.object.save() - return redirect('document_annotate', self.object.pk) + 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""" +{filename} + + + + + + """ + }, status=201) -class DocumentDownloadView(EnsembleMixin, SingleObjectMixin, View): + 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 @@ -229,10 +308,7 @@ class DocumentDownloadView(EnsembleMixin, SingleObjectMixin, View): #return response return redirect(self.object.upload.url) - def get_queryset(self): - return Document.objects.filter(work__collection__allowed_ensembles__ensemble=self.request.ensemble_id) - -class DocumentAnnotateView(EnsembleMixin, DetailView): +class DocumentAnnotateView(EnsembleMixin, DocumentMixin, DetailView): template_name = 'library/document_annotate.html' def post(self, request, **args): @@ -242,36 +318,24 @@ class DocumentAnnotateView(EnsembleMixin, DetailView): data = json.loads(request.body) - tags = {} - for page, tag in data.items(): - tags.setdefault(tag, []).append(int(page)) - try: - del(tags['None']) - except KeyError: - pass - - self.object.parts.all().delete() - for tag, pages in tags.items(): - pages.sort() - end = pages[-1] if len(pages) > 1 else None - o = self.object.parts.create(tag=tag, start=pages[0], end=end) + 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'].parts.all(): - for i in range(part.start, (part.end or part.start)+1): - pages[i] = part.tag + pages = [] + for part in data['document'].sections.all(): + pages.append((part.tag, part.start, part.end)) - data['json_data'] = {'pageTags': pages, 'instruments': data['document'].work.orchestration} + data['json_data'] = {'pageTags': pages, 'instruments': dict(INSTRUMENTS)} return data - def get_queryset(self): - return Document.objects.filter(work__collection__allowed_ensembles__ensemble=self.request.ensemble_id).select_related('work') - class PartDownloadView(EnsembleMixin, SingleObjectMixin, View): @@ -289,4 +353,9 @@ class PartDownloadView(EnsembleMixin, SingleObjectMixin, View): return response def get_queryset(self): - return Part.objects.filter(doc__work__collection__allowed_ensembles__ensemble=self.request.ensemble_id).select_related('doc', 'doc__work') \ No newline at end of file + + 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') \ No newline at end of file diff --git a/polyphonic/settings_default.py b/polyphonic/settings_default.py index 7212ebd..7e9cd63 100644 --- a/polyphonic/settings_default.py +++ b/polyphonic/settings_default.py @@ -23,13 +23,18 @@ BASE_DIR = Path(__file__).resolve().parent.parent SECRET_KEY = None # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = True +DEBUG = False ALLOWED_HOSTS = ['localhost'] # Application definition +POLYPHONIC_MODULES = [ + 'library', + 'submissions' +] + INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', @@ -38,10 +43,14 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', 'django_markdown2', + 'crispy_forms', + 'crispy_bulma', 'byostorage', 'interface', - 'library', -] +] + POLYPHONIC_MODULES + +CRISPY_ALLOWED_TEMPLATE_PACKS = ("bulma",) +CRISPY_TEMPLATE_PACK = "bulma" MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', diff --git a/polyphonic/urls.py b/polyphonic/urls.py index b922aa3..9ae6b5f 100644 --- a/polyphonic/urls.py +++ b/polyphonic/urls.py @@ -14,11 +14,12 @@ Including another URLconf 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin -from django.urls import path, include +from django.urls import path, re_path, include urlpatterns = [ path('admin/', admin.site.urls), path('', include('interface.urls')), + path('', include('submissions.urls')), path('', include('library.urls')), ]