Submissions working nicely

This commit is contained in:
Tris Forster 2020-09-07 14:25:06 +10:00
parent 0609eafc7e
commit 23b7f492b0
16 changed files with 222 additions and 75 deletions

View File

@ -0,0 +1,27 @@
# 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),
),
]

View File

@ -0,0 +1,18 @@
# 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',
),
]

View File

@ -0,0 +1,18 @@
# 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),
),
]

View File

@ -1,5 +1,6 @@
from django.db import models from django.db import models
from django.utils.text import slugify from django.utils.text import slugify
from django.utils import timezone
import random import random
@ -18,7 +19,7 @@ class Ensemble(models.Model):
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
code = models.CharField(max_length=9, default=generate_code) code = models.CharField(max_length=9, default=generate_code)
passphrase = models.CharField(max_length=100) passphrase = models.CharField(max_length=100)
bucket = models.CharField(max_length=100) bucket = models.CharField(max_length=255)
def active_projects(self): def active_projects(self):
return self.projects.filter(active=True) return self.projects.filter(active=True)
@ -37,7 +38,7 @@ class Project(models.Model):
deadline =models.DateField(null=True, blank=True) deadline =models.DateField(null=True, blank=True)
def submissions(self): def submissions(self):
return self.all_submissions.filter(complete=True) return self.all_submissions.filter(complete=True).order_by('-pk')
def presigned_post(self, object_name, fields=None, conditions=None, expires=3600): def presigned_post(self, object_name, fields=None, conditions=None, expires=3600):
key = os.path.join(slugify(self.name), object_name) key = os.path.join(slugify(self.name), object_name)
@ -61,16 +62,20 @@ class WikiPage(models.Model):
class Submission(models.Model): class Submission(models.Model):
project = models.ForeignKey(Project, related_name='all_submissions', on_delete=models.CASCADE) project = models.ForeignKey(Project, related_name='all_submissions', on_delete=models.CASCADE)
date = models.DateField(auto_now_add=True) date = models.DateTimeField(auto_now_add=True, )
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
instrument = models.CharField(max_length=100) instrument = models.CharField(max_length=100)
notes = models.TextField(blank=True) notes = models.TextField(blank=True)
complete = models.BooleanField(default=False) complete = models.BooleanField(default=False)
key = models.CharField(max_length=255, blank=True) key = models.CharField(max_length=512, blank=True)
def generate_key(self): def presigned_url(self):
params = {'Bucket': self.project.ensemble.bucket, 'Key': self.key}
return s3client.generate_presigned_url('get_object', Params=params, ExpiresIn=3600)
def key_template(self):
return "{}_{}_{}_${{filename}}".format( return "{}_{}_{}_${{filename}}".format(
datetime.now().isoformat(timespec='seconds').replace(':', ''), timezone.localtime(self.date).isoformat(timespec='seconds').replace(':', ''),
slugify(self.name), slugify(self.name),
slugify(self.instrument) slugify(self.instrument)
) )

View File

@ -37,6 +37,7 @@ BODY {
.content { .content {
margin: 20px; margin: 20px;
flex-direction: column;
} }
.narrow { .narrow {
@ -178,7 +179,7 @@ TEXTAREA {
} }
.progress-bar { .progress-bar {
width: 5%; width: 0%;
height: 1.5em; height: 1.5em;
background-color: var(--light-blue); background-color: var(--light-blue);
border-radius: 5px; border-radius: 5px;
@ -198,3 +199,16 @@ A:hover {
H1 { H1 {
text-align: center; text-align: center;
} }
TABLE {
width: 100%;
}
TABLE.horizontal TH {
text-align: right;
}
TABLE.horizontal TD,
TABLE.horizontal TH {
padding: 5px;
}

View File

@ -25,13 +25,18 @@
Projects</span></a> Projects</span></a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url 'register' %}"><i class="fas fa-users"></i> <span class="">Change <a class="nav-link" href="{% url 'register' %}"><i class="fas fa-users"></i> <span class="">My
Ensemble</span></a> Ensembles</span></a>
</li> </li>
{% endif %} {% endif %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href=""><i class="fas fa-question"></i> <span class="">About</span></a> <a class="nav-link" href=""><i class="fas fa-question"></i> <span class="">About</span></a>
</li> </li>
{% if request.user.is_authenticated %}
<li class="nav-item">
<a href="/admin"><i class="fas fa-key"></i></a>
</li>
{% endif %}
</ul> </ul>
</nav> </nav>
{% endblock %} {% endblock %}

View File

@ -1,15 +1,31 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<h1>{{ project.name }}</h1> <h1>{{ project.name }}</h1>
<div>
{% block page %} {% block page %}
<div class="narrow"> <div class="narrow">
<h3 class="text-center">Due in {{ project.deadline|timeuntil }}!</h3> <h3 class="text-center">Due in {{ project.deadline|timeuntil }}!</h3>
<p>There have been {{ project.submissions.count }} submissions so far...</p> <p>There have been {{ project.submissions.count }} submissions so far...</p>
<table>
<tbody>
{% for submission in project.submissions %}
<tr>
<td>{{ submission.date|timesince }} ago</td>
<td>{{ submission.name }} ({{ submission.instrument }})</td>
{% if request.user.is_authenticated %}
<td>
<a href="{% url 'submission' project_id=project.pk submission_id=submission.pk %}"><i class="fas fa-info-circle"></i></a>
&nbsp;
<a href="{{ submission.presigned_url }}"><i class="fas fa-download"></i></a>
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div> </div>
{% endblock %} {% endblock %}
</div>
<div class="project-links"> <div class="project-links">
<div class="pills" role="tablist"> <div class="pills" role="tablist">
<a role=tab" href="{% url 'project' project_id=project.id %}">Project info</a> <a role=tab" href="{% url 'project' project_id=project.id %}">Project info</a>
@ -17,8 +33,8 @@
<a class="nav-link {% if page.id == wiki_id %}active{% endif %}" <a class="nav-link {% if page.id == wiki_id %}active{% endif %}"
href="{% url 'wiki' project_id=project.id wiki_id=page.id %}">{{ page.title }}</a> href="{% url 'wiki' project_id=project.id wiki_id=page.id %}">{{ page.title }}</a>
{% endfor %} {% endfor %}
<a role="tab" href="">Record a submission</a> <!--a role="tab" href="">Record a submission</a-->
<a role="tab" href="{% url 'submission' project_id=project.id %}">Send a file</a> <a role="tab" href="{% url 'create_submission' project_id=project.id %}">Send a file</a>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,7 +1,7 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<div style="flex-grow: 1">
<h1>Projects for {{ ensemble.name }}</h1> <h1>Projects for {{ ensemble.name }}</h1>
<div class="list-group narrow"> <div class="list-group narrow">
{% for project in ensemble.active_projects %} {% for project in ensemble.active_projects %}
@ -11,4 +11,5 @@
</a> </a>
{% endfor %} {% endfor %}
</div> </div>
</div>
{% endblock %} {% endblock %}

View File

@ -1,7 +1,8 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<div class="narrow"> <div style="display: flex">
{% if current %}
<div> <div>
<h3>My Ensembles</h3> <h3>My Ensembles</h3>
<ul> <ul>
@ -9,8 +10,9 @@
<li><a href="/?code={{ ensemble.ensemble_code}}">{{ ensemble.name }}</a></li> <li><a href="/?code={{ ensemble.ensemble_code}}">{{ ensemble.name }}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div
<div> {% endif %}
<div style="flex-grow: 4;">
<form action="" method="POST"> <form action="" method="POST">
<h3>Add a new ensemble</h3> <h3>Add a new ensemble</h3>
{% csrf_token %} {% csrf_token %}

View File

@ -1,23 +0,0 @@
{% extends "interface/project.html" %}
{% block page %}
<div class="card">
<div class="card-header">Make a submission</div>
<div class="card-body">
<p>
Some instructions about how to submit the file
{{ url }}
</p>
<div class="">
<form action="" method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form }}
<div class="form-actions">
<button type="submit" class="btn-primary">Continue</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,20 @@
{% extends "interface/project.html" %}
{% block page %}
<div class="narrow">
<h3>Excellent, you are ready to make a submission!</h3>
<p>
Please enter some basic information so we can identify your submission and
note anything that might be relevant.
</p>
</div>
<div>
<form action="" method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form }}
<div class="form-actions">
<button type="submit" class="btn-primary">Continue</button>
</div>
</form>
</div>
{% endblock %}

View File

@ -0,0 +1,17 @@
{% extends "interface/project.html" %}
{% block page %}
<div class="narrow">
<h3>Thankyou for your submission!</h3>
<table class="horizontal">
<tbody>
<tr><th>From:</th><td>{{ submission.name }}</td></tr>
<tr><th>Instrument:</th><td>{{ submission.instrument }}</td></tr>
<tr><th>Notes:</th><td>{{ submission.notes }}</td></tr>
{% if download %}
<tr><th>Download:</th><td><a href="{{ download }}">{{ submission.key }}</a></td></tr>
{% endif %}
</tbody>
</table>
</div>
{% endblock %}

View File

@ -35,7 +35,7 @@
<script src="http://malsup.github.com/jquery.form.js"></script> <script src="http://malsup.github.com/jquery.form.js"></script>
<script> <script>
function enableUpload(e) { function uploadReady(e) {
$('#upload').attr('disabled', false); $('#upload').attr('disabled', false);
$('#status').html('Ready to upload'); $('#status').html('Ready to upload');
} }
@ -47,17 +47,20 @@ function startUpload(e) {
var status = $('#status'); var status = $('#status');
console.log("STARTING"); console.log("STARTING");
e.target.disabled = true;
// disable file select and submit
e.target.disabled = true;
//$('input[name="file"]').attr('disabled', true); //$('input[name="file"]').attr('disabled', true);
// patch form // patch form to use our ajax policy
$('input[name="policy"]').val("{{ ajax_post.fields.policy }}"); $('input[name="policy"]').val("{{ ajax_post.fields.policy }}");
$('input[name="signature"]').val("{{ ajax_post.fields.signature }}"); $('input[name="signature"]').val("{{ ajax_post.fields.signature }}");
$('input[name="success_action_redirect"]').remove(); $('input[name="success_action_redirect"]').remove();
$('form').ajaxForm({ $('form').ajaxForm({
beforeSend: function() { beforeSend: function() {
status.html("Starting upload"); status.html("Starting upload");
var percentVal = '0%'; var percentVal = '0%';
bar.width(percentVal) bar.width(percentVal)
@ -77,8 +80,8 @@ function startUpload(e) {
complete: function(xhr) { complete: function(xhr) {
if (xhr.status == 204) { if (xhr.status == 204) {
status.html("Finished upload"); status.html("Finished upload");
console.log(xhr); let s3_location = xhr.getResponseHeader("Location");
//location.href = "{{ upload.fields.success_action_redirect }}"; location.href += "/complete?location=" + s3_location;
} else { } else {
status.html("Something went wrong"); status.html("Something went wrong");
console.log(xhr.responseText); console.log(xhr.responseText);
@ -87,7 +90,8 @@ function startUpload(e) {
}); });
} }
$('input[name="file"]').on('change', enableUpload); // enable ajax upload
$('input[name="file"]').on('change', uploadReady);
$('#upload').on('click', startUpload).attr('disabled', true); $('#upload').on('click', startUpload).attr('disabled', true);
</script> </script>

View File

@ -7,7 +7,8 @@ urlpatterns = [
path('register', views.register, name="register"), path('register', views.register, name="register"),
path('projects/<int:project_id>', views.project_page, name="project"), 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>/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', views.create_submission, name="create_submission"),
path('projects/<int:project_id>/submission/<int:submission_id>', 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>/complete', views.complete_submission, name="complete_submission"),
path('projects/<int:project_id>/submission/<int:submission_id>/cancel', views.cancel_submission, name="cancel_submission"), path('projects/<int:project_id>/submission/<int:submission_id>/cancel', views.cancel_submission, name="cancel_submission"),
] ]

View File

@ -2,6 +2,7 @@ from django.shortcuts import render, get_object_or_404, redirect, resolve_url
from markdown2 import markdown from markdown2 import markdown
from datetime import datetime from datetime import datetime
from urllib.parse import urlparse
from . import models, forms from . import models, forms
from .decorators import check_allowed from .decorators import check_allowed
@ -25,14 +26,17 @@ def register(request):
if form.is_valid(): if form.is_valid():
data = form.cleaned_data; data = form.cleaned_data
ensemble = models.Ensemble.objects.get(code=data['code'].replace('-', '')) try:
ensemble = models.Ensemble.objects.get(code=data['code'].replace('-', ''))
if ensemble.passphrase == data['passphrase']:
request.session['ensemble'] = ensemble.pk
registered[ensemble.code] = ensemble.pk
return redirect('my_projects')
except models.Ensemble.DoesNotExist:
form.add_error(None, "Incorrect code or passphrase")
if ensemble.passphrase == data['passphrase']:
request.session['ensemble'] = ensemble.pk
registered[ensemble.code] = ensemble.pk
return redirect('my_projects')
else: else:
form = forms.CodeForm(initial=request.GET) form = forms.CodeForm(initial=request.GET)
@ -61,7 +65,7 @@ def wiki_page(request, project_id, wiki_id):
return render(request, 'interface/wiki.html', context) return render(request, 'interface/wiki.html', context)
@check_allowed @check_allowed
def submission(request, project_id): def create_submission(request, project_id):
project = get_object_or_404(models.Project, pk=project_id, ensemble=request.ensemble_id) project = get_object_or_404(models.Project, pk=project_id, ensemble=request.ensemble_id)
if request.method == 'POST': if request.method == 'POST':
@ -72,28 +76,43 @@ def submission(request, project_id):
s.project_id = project_id s.project_id = project_id
s.save() s.save()
data = form.cleaned_data # cache details for next time
request.session['name'] = data['name'] request.session['name'] = s.name
request.session['instrument'] = data['instrument'] request.session['instrument'] = s.instrument
redirect = request.build_absolute_uri(resolve_url('complete_submission', project_id=project.pk, submission_id=s.pk)) return redirect('submission', project_id=project_id, submission_id=s.pk)
key = s.generate_key()
#print("KEY:", key)
upload = project.presigned_post(key,
fields={'success_action_redirect': redirect},
conditions=[["starts-with", "$success_action_redirect", ""]])
ajax_post = project.presigned_post(key)
#print(b64decode(ajax_post['fields']['policy']))
context = {'upload': upload, 'ajax_post': ajax_post, 'project': project, 'submission': s}
return render(request, 'interface/upload.html', context)
else: else:
initial = { k: request.session.get(k) for k in ('name', 'instrument') } initial = { k: request.session.get(k) for k in ('name', 'instrument') }
form = forms.SubmissionForm(initial=initial) form = forms.SubmissionForm(initial=initial)
context = {'project': project, 'form': form} context = {'project': project, 'form': form}
return render(request, 'interface/submission.html', context) return render(request, 'interface/submission_create.html', context)
@check_allowed
def submission(request, project_id, submission_id):
project = get_object_or_404(models.Project, pk=project_id, ensemble=request.ensemble_id)
submission = project.all_submissions.get(pk=submission_id)
if submission.complete:
context = {'project': project, 'submission': submission}
if request.user.is_authenticated:
context['download'] = submission.presigned_url()
return render(request, 'interface/submission_detail.html', context)
# Need to do an upload
redirect = request.build_absolute_uri(resolve_url('complete_submission', project_id=project.pk, submission_id=submission.pk))
key = submission.key_template()
upload = project.presigned_post(key,
fields={'success_action_redirect': redirect},
conditions=[["starts-with", "$success_action_redirect", ""]])
# need an additional presigned without the redirect for ajax submission
ajax_post = project.presigned_post(key)
context = {'upload': upload, 'ajax_post': ajax_post, 'project': project, 'submission': submission}
return render(request, 'interface/submission_upload.html', context)
@check_allowed @check_allowed
def cancel_submission(request, project_id, submission_id): def cancel_submission(request, project_id, submission_id):
@ -107,6 +126,9 @@ def complete_submission(request, project_id, submission_id):
project = get_object_or_404(models.Project, pk=project_id, ensemble=request.ensemble_id) project = get_object_or_404(models.Project, pk=project_id, ensemble=request.ensemble_id)
s = project.all_submissions.get(pk=submission_id) s = project.all_submissions.get(pk=submission_id)
s.complete = True s.complete = True
s.key = request.GET['key']
uri = urlparse(request.GET['location'])
s.key = uri.path[1:]
s.save() s.save()
return redirect('project', project_id=project_id) return redirect('submission', project_id=project_id, submission_id=submission_id)

View File

@ -106,7 +106,7 @@ AUTH_PASSWORD_VALIDATORS = [
LANGUAGE_CODE = 'en-us' LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC' TIME_ZONE = 'Australia/Melbourne'
USE_I18N = True USE_I18N = True