Further work on multi-file-upload

This commit is contained in:
Carl van Tonder 2018-04-23 01:15:33 -04:00
parent bb326bfed8
commit 935af1355b
13 changed files with 235 additions and 58 deletions

View File

@ -34,13 +34,13 @@ $ export DJANGO_SETTINGS_MODULE=ojusomap.settings
## Install the Python Dependencies
```bash
$ pip3 install -r requirements.txt
$ pip3 install -r requirements-devel.txt
```
If you run into issues with `psycopg2` you may need to run the following:
```bash
$ pip uninstall psycopg2 && pip install --no-binary :all: psycopg2
$ pip3 uninstall psycopg2 && pip3 install --no-binary :all: psycopg2
```
## Run The Migrations

View File

@ -1,9 +1,8 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-04-19 19:19
# Generated by Django 1.11.6 on 2018-04-23 02:20
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
@ -11,7 +10,6 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('map', '0056_delete_shapefile'),
]
operations = [
@ -20,8 +18,6 @@ class Migration(migrations.Migration):
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('file', models.FileField(upload_to='.')),
('case_study', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='files', to='map.CaseStudy')),
('case_study_draft', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='files', to='map.CaseStudyDraft')),
],
options={
'abstract': False,

View File

@ -7,16 +7,13 @@ class BaseFile(models.Model):
file = models.FileField(
upload_to='.',
)
case_study = models.ForeignKey(
CaseStudy, related_name='files', blank=True, null=True,
)
case_study_draft = models.ForeignKey(
CaseStudyDraft, related_name='files', blank=True, null=True
)
class Meta:
abstract = True
def __str__(self):
return self.file.name
class File(BaseFile):
pass

View File

@ -1,9 +1,40 @@
$(function () {
/* 2. INITIALIZE THE FILE UPLOAD COMPONENT */
$("input[type=file]").fileupload({
// un-set "name" attributes to avoid submitting to server
$(".fileupload").removeAttr('name');
// set up all file inputs for jQuery-fileUpload
$(".fileupload").fileupload({
dataType: 'json',
done: function (e, data) { /* 3. PROCESS THE RESPONSE FROM THE SERVER */
console.log(data);
paramName: 'file',
done: function (e, data) {
// process server response
if (data.result.is_valid) {
var $field = $('#id_' + $(this).attr('data-field')),
$ul = $(this).siblings('ul'),
$li = $("<li>"),
$remove = $('<a title="remove"></a>'),
ids = $field.val().split(",").filter(function (v) {
return v != '';
});
ids.push(data.result.id);
if (!$ul.length) {
$ul = $("<ul>").insertAfter(this);
}
$li.text(data.result.name);
// FIXME AJAXify
//$remove.attr('href', '/files/' + data.result.id + '/delete/');
//$remove.append($('<i class="glyphicon glyphicon-remove"/>'));
//$li.append($remove);
$ul.append($li);
$field.val(ids.toString());
document.getElementById('case-study-form').dispatchEvent(new Event('dirty'))
}
}
});
});

View File

@ -1,9 +1,10 @@
from django.conf.urls import url
from .views import FileUploadView
from .views import FileUploadView, FileDeleteView
app_name = 'files'
urlpatterns = [
url(r'^upload/?$', FileUploadView.as_view(), name='upload'),
url(r'^upload/$', FileUploadView.as_view(), name='upload'),
url(r'^delete/(?P<pk>\d+)/$', FileDeleteView.as_view(), name='delete'),
]

View File

@ -1,19 +1,45 @@
from django.shortcuts import render
from django.http import JsonResponse
from django.views.generic import CreateView
from django.views.generic import FormView, DetailView
from .forms import FileForm
from .models import File
class FileUploadView(CreateView):
class FileUploadView(FormView):
# FIXME require login
model = File
form_class = FileForm
def form_valid(self, form):
# save the File to the database
super().form_valid(form)
self.object = form.save()
return JsonResponse({'is_valid': True, 'url': self.object.file.url})
# FIXME set File owner
return JsonResponse({
'is_valid': True, 'url': self.object.file.url,
'name': self.object.file.name,
'id': self.object.pk
})
def form_invalid(self, form):
return JsonResponse({'is_valid': False, 'errors': form.errors})
class FileDeleteView(DetailView):
# FIXME require login
model = File
def get(self, request, *args, **kwargs):
return self.post(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
# FIXME check file ownership
self.object = self.get_object()
self.object.delete()
return JsonResponse({
'success': True
})

View File

@ -9,7 +9,10 @@ from crispy_forms.bootstrap import Tab, TabHolder, PrependedText, FormActions
from dal import autocomplete
from leaflet.forms.widgets import LeafletWidget
from apps.files.models import File
from .models import CaseStudy, SpatialRefSys
from .widgets import CommaSeparatedTextInput
class MinimumZoomWidget(LeafletWidget):
@ -19,13 +22,6 @@ class MinimumZoomWidget(LeafletWidget):
class BaseCaseStudyForm(forms.models.ModelForm):
"""Base form class for the CaseStudy model."""
official_project_documents = forms.FileField(
widget=forms.ClearableFileInput(attrs={
'multiple': True,
'data-url': reverse_lazy('files:upload'),
})
)
def __init__(self, *args, **kwargs):
super(BaseCaseStudyForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
@ -48,13 +44,6 @@ class BaseCaseStudyForm(forms.models.ModelForm):
}),
}
class Media:
js = (
'files/jquery.ui.widget.js',
'files/jquery.iframe-transport.js',
'files/jquery.fileupload.js'
)
class ShortCaseStudyForm(BaseCaseStudyForm):
"""Short version of the CaseStudy form."""
@ -95,6 +84,51 @@ class ShortCaseStudyForm(BaseCaseStudyForm):
class LongCaseStudyForm(BaseCaseStudyForm):
"""Long version of the CaseStudy form."""
official_project_documents = forms.FileField(
widget=forms.ClearableFileInput(attrs={
'multiple': True,
'data-url': reverse_lazy('files:upload'),
'data-field': 'official_project_documents_files',
'class': 'fileupload',
}), required=False
)
official_project_documents_files = forms.ModelMultipleChoiceField(
queryset=File.objects.all(),
widget=CommaSeparatedTextInput(),
required=False
)
other_documents = forms.FileField(
widget=forms.ClearableFileInput(attrs={
'multiple': True,
'data-url': reverse_lazy('files:upload'),
'data-field': 'other_documents_files',
'class': 'fileupload',
}), required=False
)
other_documents_files = forms.ModelMultipleChoiceField(
queryset=File.objects.all(),
widget=CommaSeparatedTextInput(),
required=False
)
shapefiles = forms.FileField(
widget=forms.ClearableFileInput(attrs={
'multiple': True,
'data-url': reverse_lazy('files:upload'),
'data-field': 'shapefiles_files',
'class': 'fileupload',
}), required=False
)
shapefiles_files = forms.ModelMultipleChoiceField(
queryset=File.objects.all(),
widget=CommaSeparatedTextInput(),
required=False
)
coordinate_reference_system = forms.ModelChoiceField(
queryset=SpatialRefSys.objects.all(),
widget=autocomplete.ModelSelect2(url='srs-autocomplete'),
@ -288,8 +322,11 @@ class LongCaseStudyForm(BaseCaseStudyForm):
Tab(
_("Uploads"),
'official_project_documents',
'official_project_documents_files',
'other_documents',
'other_documents_files',
'shapefiles',
'shapefiles_files',
'coordinate_reference_system',
'name_of_territory_or_area',
'shown_on_other_platforms',
@ -302,3 +339,11 @@ class LongCaseStudyForm(BaseCaseStudyForm):
class Meta(BaseCaseStudyForm.Meta):
exclude = ('approved',)
class Media:
js = (
'files/jquery.ui.widget.js',
'files/jquery.iframe-transport.js',
'files/jquery.fileupload.js',
'files/upload.js',
)

View File

@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-04-23 02:20
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('files', '0001_initial'),
('map', '0056_delete_shapefile'),
]
operations = [
migrations.RemoveField(
model_name='casestudy',
name='official_project_documents',
),
migrations.AddField(
model_name='casestudy',
name='official_project_documents',
field=models.ManyToManyField(blank=True, help_text='Attach any legal or official documents that relate to the project.', null=True, related_name='official_project_document_for', to='files.File', verbose_name='Official project documents'),
),
migrations.RemoveField(
model_name='casestudy',
name='other_documents',
),
migrations.AddField(
model_name='casestudy',
name='other_documents',
field=models.ManyToManyField(blank=True, help_text='Attach any other documents that relate to the project.', null=True, related_name='other_document_for', to='files.File', verbose_name='Other documents'),
),
migrations.RemoveField(
model_name='casestudy',
name='shapefiles',
),
migrations.AddField(
model_name='casestudy',
name='shapefiles',
field=models.ManyToManyField(blank=True, help_text='If you have territory that you would like to show in relation to this project - e.g. Bienes Comunales de Ixtepec etc. This is a set of 3 or more (often 5-6) files with file extensions like .cpg, .dbf, .prj, .qpj, .shp, .shx', null=True, related_name='shapefile_for', to='files.File', verbose_name='Shapefiles'),
),
]

View File

@ -17,8 +17,7 @@ from . import validators
class CaseStudyDraft(models.Model):
author = models.ForeignKey(
User,
on_delete=models.CASCADE
User, on_delete=models.CASCADE
)
data = models.TextField()
@ -1001,31 +1000,31 @@ class CaseStudy(models.Model):
##
# 4.1
official_project_documents = models.FileField(
official_project_documents = models.ManyToManyField(
'files.File',
related_name='official_project_document_for',
verbose_name=_("Official project documents"),
help_text=_("Attach any legal or official documents that relate to the project."),
default=None,
null=True,
blank=True,
)
# 4.2
other_documents = models.FileField(
other_documents = models.ManyToManyField(
'files.File',
related_name='other_document_for',
verbose_name=_("Other documents"),
help_text=_("Attach any other documents that relate to the project."),
default=None,
null=True,
blank=True,
)
# 4.3.1
shapefiles = models.FileField(
shapefiles = models.ManyToManyField(
'files.File',
related_name='shapefile_for',
verbose_name=_("Shapefiles"),
help_text=_("If you have territory that you would like to show in relation to this project - e.g. Bienes \
Comunales de Ixtepec etc. This is a set of 3 or more (often 5-6) files with file extensions like \
.cpg, .dbf, .prj, .qpj, .shp, .shx"),
default=None,
null=True,
blank=True
)
@ -1078,7 +1077,6 @@ class CaseStudy(models.Model):
# Continue normal save method by calling original save method.
super(CaseStudy, self).save(*args, **kwargs)
def is_video_youtube(self):
return self.video.count("youtube.com") > 0
@ -1086,7 +1084,6 @@ class CaseStudy(models.Model):
"""Gets the 11 character YouTube video ID from the video field."""
return parse.parse_qs(parse.urlparse(self.video).query)["v"][0]
def is_video_vimeo(self):
return self.video.count("vimeo.com") > 0
@ -1094,7 +1091,6 @@ class CaseStudy(models.Model):
"""Gets the 11 number video ID from the video field."""
return parse.urlparse(self.video).path
def get_negative_case_reasons_no_other(self):
"""Return a list of negative case reasons, minus the 'other' choice (if selected)"""
choices = self.get_negative_case_reasons_list()

View File

@ -509,10 +509,10 @@ function initDrafts() {
var button = new SaveButton(document.querySelector('.savebutton'))
// Dirty forms set up
$('#case-study-form').dirtyForms()
$('#case-study-form').dirtyForms();
$('#case-study-form').on('dirty.dirtyforms', ev => {
button.switchStateUnsaved()
})
});
// Save button
button.element.button.addEventListener('click', evt => {

View File

@ -55,8 +55,25 @@ class BaseForm(LoginRequiredMixin, CreateView):
fail_silently=False,
)
def form_invalid(self, form):
print(form.errors)
return super().form_invalid(form)
def form_valid(self, form):
from pdb import set_trace; set_trace()
self.object = form.save()
self.object.official_project_documents = form.cleaned_data.get(
'official_project_document_files', []
)
self.object.other_documents = form.cleaned_data.get(
'other_documents_files', []
)
self.object.shapefiles = form.cleaned_data.get(
'shapefiles_files', []
)
self.send_email()
# Delete the corresponding draft
@ -140,6 +157,10 @@ class Drafts(View):
return HttpResponse(status=403) # Forbidden
draft = self.get_object(request)
if draft != None:
draft.delete()
from pdb import set_trace; set_trace()
return HttpResponse(status=204)

21
apps/map/widgets.py Normal file
View File

@ -0,0 +1,21 @@
from django.forms import widgets
class CommaSeparatedTextInput(widgets.HiddenInput):
def format_value(self, value):
try:
value = ','.join(value)
except TypeError:
value = ''
return super().format_value(value)
def value_from_datadict(self, data, files, name):
value = super().value_from_datadict(data, files, name)
if value == '':
return None
try:
return value.split(',')
except AttributeError:
return None

View File

@ -68,12 +68,12 @@ urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^avatar/', include('avatar.urls')),
url(r'^cas/', include('cas_server.urls', namespace='cas_server')),
url(r'^files/', include('apps.files.urls')),
# url(r'^contact/', include('apps.contact.urls'), name="contact"),
]
urlpatterns += i18n_patterns(
url(r'^accounts/profile/', include('apps.profiles.urls', namespace="profile")),
url(r'^accounts/', include('registration.backends.default.urls')),
url(r'^files/', include('apps.files.urls')),
url(r'', include('apps.map.urls'), name="map"),
)