Submissions working nicely
This commit is contained in:
parent
0609eafc7e
commit
23b7f492b0
27
interface/migrations/0009_auto_20200907_0103.py
Normal file
27
interface/migrations/0009_auto_20200907_0103.py
Normal 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),
|
||||
),
|
||||
]
|
||||
18
interface/migrations/0010_auto_20200907_0148.py
Normal file
18
interface/migrations/0010_auto_20200907_0148.py
Normal 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',
|
||||
),
|
||||
]
|
||||
18
interface/migrations/0011_auto_20200907_0234.py
Normal file
18
interface/migrations/0011_auto_20200907_0234.py
Normal 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),
|
||||
),
|
||||
]
|
||||
@ -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)
|
||||
)
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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 %}
|
||||
|
||||
@ -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>
|
||||
|
||||
<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 %}
|
||||
@ -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 %}
|
||||
@ -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 %}
|
||||
|
||||
@ -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 %}
|
||||
20
interface/templates/interface/submission_create.html
Normal file
20
interface/templates/interface/submission_create.html
Normal 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 %}
|
||||
17
interface/templates/interface/submission_detail.html
Normal file
17
interface/templates/interface/submission_detail.html
Normal 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 %}
|
||||
@ -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>
|
||||
@ -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"),
|
||||
]
|
||||
@ -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)
|
||||
@ -106,7 +106,7 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
TIME_ZONE = 'Australia/Melbourne'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user