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.utils.text import slugify
from django.utils import timezone
import random
@ -18,7 +19,7 @@ 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)
bucket = models.CharField(max_length=100)
bucket = models.CharField(max_length=255)
def active_projects(self):
return self.projects.filter(active=True)
@ -37,7 +38,7 @@ class Project(models.Model):
deadline =models.DateField(null=True, blank=True)
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):
key = os.path.join(slugify(self.name), object_name)
@ -61,16 +62,20 @@ class WikiPage(models.Model):
class Submission(models.Model):
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)
instrument = models.CharField(max_length=100)
notes = models.TextField(blank=True)
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(
datetime.now().isoformat(timespec='seconds').replace(':', ''),
timezone.localtime(self.date).isoformat(timespec='seconds').replace(':', ''),
slugify(self.name),
slugify(self.instrument)
)

View File

@ -37,6 +37,7 @@ BODY {
.content {
margin: 20px;
flex-direction: column;
}
.narrow {
@ -178,7 +179,7 @@ TEXTAREA {
}
.progress-bar {
width: 5%;
width: 0%;
height: 1.5em;
background-color: var(--light-blue);
border-radius: 5px;
@ -197,4 +198,17 @@ A:hover {
H1 {
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>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'register' %}"><i class="fas fa-users"></i> <span class="">Change
Ensemble</span></a>
<a class="nav-link" href="{% url 'register' %}"><i class="fas fa-users"></i> <span class="">My
Ensembles</span></a>
</li>
{% endif %}
<li class="nav-item">
<a class="nav-link" href=""><i class="fas fa-question"></i> <span class="">About</span></a>
</li>
{% if request.user.is_authenticated %}
<li class="nav-item">
<a href="/admin"><i class="fas fa-key"></i></a>
</li>
{% endif %}
</ul>
</nav>
{% endblock %}

View File

@ -1,15 +1,31 @@
{% extends "base.html" %}
{% block content %}
<h1>{{ project.name }}</h1>
<div>
{% block page %}
<div class="narrow">
<h3 class="text-center">Due in {{ project.deadline|timeuntil }}!</h3>
<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>
{% endblock %}
</div>
<div class="project-links">
<div class="pills" role="tablist">
<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 %}"
href="{% url 'wiki' project_id=project.id wiki_id=page.id %}">{{ page.title }}</a>
{% endfor %}
<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="">Record a submission</a-->
<a role="tab" href="{% url 'create_submission' project_id=project.id %}">Send a file</a>
</div>
</div>
{% endblock %}

View File

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

View File

@ -1,7 +1,8 @@
{% extends "base.html" %}
{% block content %}
<div class="narrow">
<div style="display: flex">
{% if current %}
<div>
<h3>My Ensembles</h3>
<ul>
@ -9,8 +10,9 @@
<li><a href="/?code={{ ensemble.ensemble_code}}">{{ ensemble.name }}</a></li>
{% endfor %}
</ul>
</div>
<div>
</div
{% endif %}
<div style="flex-grow: 4;">
<form action="" method="POST">
<h3>Add a new ensemble</h3>
{% 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>
function enableUpload(e) {
function uploadReady(e) {
$('#upload').attr('disabled', false);
$('#status').html('Ready to upload');
}
@ -47,17 +47,20 @@ function startUpload(e) {
var status = $('#status');
console.log("STARTING");
// disable file select and submit
e.target.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="signature"]').val("{{ ajax_post.fields.signature }}");
$('input[name="success_action_redirect"]').remove();
$('form').ajaxForm({
beforeSend: function() {
status.html("Starting upload");
var percentVal = '0%';
bar.width(percentVal)
@ -77,8 +80,8 @@ function startUpload(e) {
complete: function(xhr) {
if (xhr.status == 204) {
status.html("Finished upload");
console.log(xhr);
//location.href = "{{ upload.fields.success_action_redirect }}";
let s3_location = xhr.getResponseHeader("Location");
location.href += "/complete?location=" + s3_location;
} else {
status.html("Something went wrong");
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);
</script>

View File

@ -7,7 +7,8 @@ urlpatterns = [
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', 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>/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 datetime import datetime
from urllib.parse import urlparse
from . import models, forms
from .decorators import check_allowed
@ -25,14 +26,17 @@ def register(request):
if form.is_valid():
data = form.cleaned_data;
ensemble = models.Ensemble.objects.get(code=data['code'].replace('-', ''))
data = form.cleaned_data
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:
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)
@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)
if request.method == 'POST':
@ -72,28 +76,43 @@ def submission(request, project_id):
s.project_id = project_id
s.save()
data = form.cleaned_data
request.session['name'] = data['name']
request.session['instrument'] = data['instrument']
# cache details for next time
request.session['name'] = s.name
request.session['instrument'] = s.instrument
redirect = request.build_absolute_uri(resolve_url('complete_submission', project_id=project.pk, 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)
return redirect('submission', project_id=project_id, submission_id=s.pk)
else:
initial = { k: request.session.get(k) for k in ('name', 'instrument') }
form = forms.SubmissionForm(initial=initial)
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
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)
s = project.all_submissions.get(pk=submission_id)
s.complete = True
s.key = request.GET['key']
uri = urlparse(request.GET['location'])
s.key = uri.path[1:]
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'
TIME_ZONE = 'UTC'
TIME_ZONE = 'Australia/Melbourne'
USE_I18N = True