S3 upload working
This commit is contained in:
parent
9ace9c3146
commit
83c011e32f
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
__pycache__
|
||||
*.pyc
|
||||
db.sqlite3
|
||||
db.sqlite3
|
||||
credentials
|
||||
|
||||
@ -4,6 +4,22 @@ from django.contrib import admin
|
||||
|
||||
from . import models
|
||||
|
||||
admin.site.register(models.Project)
|
||||
admin.site.register(models.Submission)
|
||||
admin.site.register(models.WikiPage)
|
||||
|
||||
class ProjectAdmin(admin.ModelAdmin):
|
||||
|
||||
list_display = ['name', 'ensemble', 'deadline', 'active']
|
||||
list_filter = ['ensemble', 'active']
|
||||
|
||||
class SubmissionAdmin(admin.ModelAdmin):
|
||||
list_display = ['name', 'instrument', 'date', 'complete']
|
||||
list_filter = ['project', 'complete']
|
||||
|
||||
class WikiPageAdmin(admin.ModelAdmin):
|
||||
list_display = ['title', 'project']
|
||||
list_filter = ['project']
|
||||
|
||||
admin.site.register(models.Ensemble)
|
||||
admin.site.register(models.Project, ProjectAdmin)
|
||||
admin.site.register(models.Submission, SubmissionAdmin)
|
||||
admin.site.register(models.Resource)
|
||||
admin.site.register(models.WikiPage, WikiPageAdmin)
|
||||
19
interface/decorators.py
Normal file
19
interface/decorators.py
Normal file
@ -0,0 +1,19 @@
|
||||
from django.http import HttpResponseRedirect
|
||||
|
||||
def check_allowed(view_func):
|
||||
|
||||
def _view(request, *args, **kwargs):
|
||||
|
||||
request.ensemble_id = request.session.get('ensemble')
|
||||
|
||||
if request.ensemble_id is None:
|
||||
return HttpResponseRedirect('/register')
|
||||
|
||||
return view_func(request, *args, **kwargs)
|
||||
|
||||
_view.__name__ = view_func.__name__
|
||||
_view.__dict__ = view_func.__dict__
|
||||
_view.__doc__ = view_func.__doc__
|
||||
|
||||
return _view
|
||||
|
||||
8
interface/forms.py
Normal file
8
interface/forms.py
Normal file
@ -0,0 +1,8 @@
|
||||
from django.forms import ModelForm
|
||||
|
||||
from .models import Submission
|
||||
|
||||
class SubmissionForm(ModelForm):
|
||||
class Meta:
|
||||
model = Submission
|
||||
fields = ['name', 'instrument', 'notes']
|
||||
33
interface/migrations/0003_auto_20200905_0118.py
Normal file
33
interface/migrations/0003_auto_20200905_0118.py
Normal file
@ -0,0 +1,33 @@
|
||||
# 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'),
|
||||
),
|
||||
]
|
||||
34
interface/migrations/0004_auto_20200905_0127.py
Normal file
34
interface/migrations/0004_auto_20200905_0127.py
Normal file
@ -0,0 +1,34 @@
|
||||
# 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')),
|
||||
],
|
||||
),
|
||||
]
|
||||
23
interface/migrations/0005_auto_20200905_0638.py
Normal file
23
interface/migrations/0005_auto_20200905_0638.py
Normal file
@ -0,0 +1,23 @@
|
||||
# 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),
|
||||
),
|
||||
]
|
||||
18
interface/migrations/0006_submission_key.py
Normal file
18
interface/migrations/0006_submission_key.py
Normal file
@ -0,0 +1,18 @@
|
||||
# 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),
|
||||
),
|
||||
]
|
||||
@ -1,12 +1,44 @@
|
||||
from django.db import models
|
||||
from django.utils.text import slugify
|
||||
|
||||
# Create your models here.
|
||||
class Project(models.Model):
|
||||
import boto3
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
import os.path
|
||||
|
||||
s3client = boto3.client('s3')
|
||||
|
||||
class Ensemble(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
code = models.CharField(max_length=12)
|
||||
password = models.CharField(max_length=100)
|
||||
|
||||
def active_projects(self):
|
||||
return self.projects.filter(active=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Project(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
ensemble = models.ForeignKey(Ensemble, related_name='projects', on_delete=models.CASCADE, null=True)
|
||||
active = models.BooleanField(default=True)
|
||||
deadline =models.DateField(null=True, blank=True)
|
||||
bucket = models.CharField(max_length=100)
|
||||
|
||||
def presigned_post(self, object_name, fields={}, conditions=[], expires=3600):
|
||||
key = os.path.join(slugify(self.name), object_name)
|
||||
return s3client.generate_presigned_post(self.bucket, key, Fields=fields, Conditions=conditions, ExpiresIn=expires)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Resource(models.Model):
|
||||
project = models.ForeignKey(Project, related_name='resources', on_delete=models.CASCADE)
|
||||
name = models.CharField(max_length=100)
|
||||
uri = models.CharField(max_length=255)
|
||||
|
||||
class WikiPage(models.Model):
|
||||
project = models.ForeignKey(Project, related_name='wiki_pages', on_delete=models.CASCADE)
|
||||
title = models.CharField(max_length=255)
|
||||
@ -17,10 +49,19 @@ class WikiPage(models.Model):
|
||||
|
||||
class Submission(models.Model):
|
||||
project = models.ForeignKey(Project, related_name='submissions', on_delete=models.CASCADE)
|
||||
date = models.DateField(auto_created=True)
|
||||
date = models.DateField(auto_now_add=True)
|
||||
name = models.CharField(max_length=255)
|
||||
instrument = models.CharField(max_length=100)
|
||||
notes = models.TextField(blank=True)
|
||||
complete = models.BooleanField(default=False)
|
||||
key = models.CharField(max_length=255, blank=True)
|
||||
|
||||
def generate_key(self):
|
||||
return "{}_{}_{}_${{filename}}".format(
|
||||
datetime.now().isoformat(timespec='seconds').replace(':', ''),
|
||||
slugify(self.name),
|
||||
slugify(self.instrument)
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name}: {self.date}"
|
||||
@ -17,16 +17,21 @@
|
||||
|
||||
{% block header %}
|
||||
<nav class="navbar navbar-expand-lg navbar-dark">
|
||||
<a class="navbar-brand" href="#"><i class="fas fa-random"></i> Polyphonic</a>
|
||||
<a class="navbar-brand" href="/"><i class="fas fa-random"></i> Polyphonic</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<span class="navbar-text">Virtual Ensemble Manager</span>
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent"></div>
|
||||
<ul class="navbar-nav ml-auto">
|
||||
{% if request.ensemble_id %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'myprojects' %}""><i class="fas fa-music"></i> My Projects</a>
|
||||
<a class="nav-link" href="{% url 'my_projects' %}"><i class="fas fa-music"></i> My Projects</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'register' %}"><i class="fas fa-users"></i> Change Ensemble</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href=""><i class="fas fa-question"></i> About</a>
|
||||
</li>
|
||||
|
||||
29
interface/templates/interface/bootstrap-form.html
Normal file
29
interface/templates/interface/bootstrap-form.html
Normal file
@ -0,0 +1,29 @@
|
||||
<form class="form-horizontal" method="post">{% csrf_token %}
|
||||
<fieldset>
|
||||
<legend>{{ title }}</legend>
|
||||
{% for field in form %}
|
||||
{% if field.errors %}
|
||||
<div class="control-group error">
|
||||
<label class="control-label">{{ field.label }}</label>
|
||||
<div class="controls">{{ field }}
|
||||
<span class="help-inline">
|
||||
{% for error in field.errors %}{{ error }}{% endfor %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="control-group">
|
||||
<label class="control-label">{{ field.label }}</label>
|
||||
<div class="controls">{{ field }}
|
||||
{% if field.help_text %}
|
||||
<p class="help-inline"><small>{{ field.help_text }}</small></p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</fieldset>
|
||||
<div class="form-actions text-right">
|
||||
<button type="submit" class="btn btn-primary" >Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
@ -7,6 +7,7 @@
|
||||
<div class="row">
|
||||
<div class="col-9">
|
||||
{% block page %}
|
||||
<h3 class="text-center">Due in {{ project.deadline|timeuntil }}</h3>
|
||||
<p>There have been {{ project.submissions.count }} submissions so far...</p>
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
||||
18
interface/templates/interface/project_list.html
Normal file
18
interface/templates/interface/project_list.html
Normal file
@ -0,0 +1,18 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="container">
|
||||
<h1>Projects for {{ ensemble.name }}</h1>
|
||||
<div class="col-md-8 offset-md-2">
|
||||
<div class="list-group">
|
||||
{% for project in ensemble.active_projects %}
|
||||
<a class="list-group-item list-group-item-action" href="{% url 'project' project_id=project.id %}">
|
||||
<h4>{{ project.name }}</h4>
|
||||
<p><small>Due in {{ project.deadline|timeuntil }}</small></p>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
25
interface/templates/interface/register.html
Normal file
25
interface/templates/interface/register.html
Normal file
@ -0,0 +1,25 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-md-4 offset-md-4">
|
||||
<div class="card">
|
||||
<div class="card-header">Enter the ensemble details given to you</div>
|
||||
<div class="card-body">
|
||||
<form action="" method="POST">
|
||||
{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<label for="code">Ensemble Code</label>
|
||||
<input type="text" class="form-control" placeholder="xxxx-xxxx-xxxx" name="code" value="{{ code }}"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="passphrase">Passphrase</label>
|
||||
<input type="password" class="form-control" name="passphrase"/>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<button class="btn btn-primary">Enter</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -3,29 +3,11 @@
|
||||
{% block page %}
|
||||
<p>
|
||||
Some instructions about how to submit the file
|
||||
{{ url }}
|
||||
</p>
|
||||
|
||||
<div class="col-md-6 offset-md-3">
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label for="name">Name</label>
|
||||
<input type="text" id="name" class="form-control"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="instrument">Instrument</label>
|
||||
<input type="text" id="instrument" class="form-control"/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="video-file">Video File</label>
|
||||
<input type="file" accept="video/*" class="" id="video-file" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="notes">Notes</label>
|
||||
<textarea class="form-control" id="notes"></textarea>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
</div>
|
||||
</form>
|
||||
{% include "interface/bootstrap-form.html" %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
19
interface/templates/interface/upload.html
Normal file
19
interface/templates/interface/upload.html
Normal file
@ -0,0 +1,19 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="col-md-4 offset-md-4">
|
||||
<form method="POST" action="{{ upload.url }}" enctype="multipart/form-data">
|
||||
{% for field, value in upload.fields.items %}
|
||||
<input type="hidden" name="{{ field }}" value="{{ value }}" />
|
||||
{% endfor %}
|
||||
File:
|
||||
<input name="file" type="file" accept="video/*"/>
|
||||
<div class="text-right">
|
||||
<a class="btn btn-secondary" href="{% url 'cancel_submission' project_id=project.pk submission_id=submission.pk %}">Cancel</a>
|
||||
<button class="btn btn-primary">Upload</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@ -3,8 +3,11 @@ from django.urls import path
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path('my_projects', views.myprojects, name='myprojects'),
|
||||
path('<int:project_id>', views.project_page, name="project"),
|
||||
path('<int:project_id>/page/<int:wiki_id>', views.wiki_page, name="wiki"),
|
||||
path('<int:project_id>/submission', views.submission, name="submission"),
|
||||
path('', views.my_projects, name='my_projects'),
|
||||
path('register', views.register, name="register"),
|
||||
path('projects/<int:project_id>', views.project_page, name="project"),
|
||||
path('projects/<int:project_id>/page/<int:wiki_id>', views.wiki_page, name="wiki"),
|
||||
path('projects/<int:project_id>/submission', views.submission, name="submission"),
|
||||
path('projects/<int:project_id>/submission/<int:submission_id>/complete', views.complete_submission, name="complete_submission"),
|
||||
path('projects/<int:project_id>/submission/<int:submission_id>/cancel', views.cancel_submission, name="cancel_submission"),
|
||||
]
|
||||
@ -1,23 +1,91 @@
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.shortcuts import render, get_object_or_404, redirect, resolve_url
|
||||
|
||||
from markdown2 import markdown
|
||||
from datetime import datetime
|
||||
|
||||
from . import models
|
||||
from . import models, forms
|
||||
from .decorators import check_allowed
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def forbidden(request):
|
||||
return render(request, 'interface/forbidden.html', {})
|
||||
|
||||
def register(request):
|
||||
code = ''
|
||||
try:
|
||||
code = request.POST.get('code', request.GET['code'])
|
||||
passphrase = request.POST.get('passphrase')
|
||||
|
||||
ensemble = models.Ensemble.objects.get(code=code)
|
||||
if ensemble.password != passphrase:
|
||||
raise ValueError("Incorrect passphase")
|
||||
|
||||
request.session['ensemble'] = ensemble.pk
|
||||
return redirect('my_projects')
|
||||
except:
|
||||
logger.exception("Registration failed")
|
||||
return render(request, 'interface/register.html', {'code': code})
|
||||
|
||||
|
||||
@check_allowed
|
||||
def my_projects(request):
|
||||
ensemble = get_object_or_404(models.Ensemble, pk=request.ensemble_id)
|
||||
context = {'ensemble': ensemble}
|
||||
return render(request, 'interface/project_list.html', context)
|
||||
|
||||
@check_allowed
|
||||
def project_page(request, project_id):
|
||||
project = get_object_or_404(models.Project, pk=project_id)
|
||||
project = get_object_or_404(models.Project, pk=project_id, ensemble_id=request.ensemble_id)
|
||||
context = {'project': project}
|
||||
return render(request, 'interface/project.html', context)
|
||||
|
||||
@check_allowed
|
||||
def wiki_page(request, project_id, wiki_id):
|
||||
wiki = get_object_or_404(models.WikiPage, pk=wiki_id, project=project_id)
|
||||
wiki = get_object_or_404(models.WikiPage, pk=wiki_id, project=project_id, project__ensemble=request.ensemble_id)
|
||||
context = {'project': wiki.project, 'wiki': wiki, 'wiki_html': markdown(wiki.markdown)}
|
||||
return render(request, 'interface/wiki.html', context)
|
||||
|
||||
@check_allowed
|
||||
def submission(request, project_id):
|
||||
project = get_object_or_404(models.Project, pk=project_id)
|
||||
context = {'project': project}
|
||||
project = get_object_or_404(models.Project, pk=project_id, ensemble=request.ensemble_id)
|
||||
|
||||
if request.method == 'POST':
|
||||
form = forms.SubmissionForm(request.POST)
|
||||
|
||||
if form.is_valid():
|
||||
s = form.save(commit=False)
|
||||
s.project_id = project_id
|
||||
s.save()
|
||||
|
||||
redirect = request.build_absolute_uri(resolve_url('complete_submission', project_id=project.pk, submission_id=s.pk))
|
||||
|
||||
upload = project.presigned_post(s.generate_key(),
|
||||
fields={'success_action_redirect': redirect},
|
||||
conditions=[["starts-with", "$success_action_redirect", ""]])
|
||||
context = {'upload': upload, 'project': project, 'submission': s}
|
||||
|
||||
return render(request, 'interface/upload.html', context)
|
||||
else:
|
||||
form = forms.SubmissionForm()
|
||||
|
||||
context = {'project': project, 'form': form}
|
||||
return render(request, 'interface/submission.html', context)
|
||||
|
||||
def myprojects(request):
|
||||
return render(request, 'interface/my_projects.html', {})
|
||||
@check_allowed
|
||||
def cancel_submission(request, project_id, submission_id):
|
||||
project = get_object_or_404(models.Project, pk=project_id, ensemble=request.ensemble_id)
|
||||
submission = project.submissions.get(pk=submission_id)
|
||||
submission.delete()
|
||||
return redirect('project', project_id=project_id)
|
||||
|
||||
@check_allowed
|
||||
def complete_submission(request, project_id, submission_id):
|
||||
project = get_object_or_404(models.Project, pk=project_id, ensemble=request.ensemble_id)
|
||||
s = project.submissions.get(pk=submission_id)
|
||||
s.complete = True
|
||||
s.key = request.GET['key']
|
||||
s.save()
|
||||
return redirect('project', project_id=project_id)
|
||||
@ -18,5 +18,5 @@ from django.urls import path, include
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
path('project/', include('interface.urls')),
|
||||
path('', include('interface.urls')),
|
||||
]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user