Further work on multi-file-upload
This commit is contained in:
parent
bb326bfed8
commit
935af1355b
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -1,9 +1,40 @@
|
||||
$(function () {
|
||||
/* 2. INITIALIZE THE FILE UPLOAD COMPONENT */
|
||||
$("input[type=file]").fileupload({
|
||||
dataType: 'json',
|
||||
done: function (e, data) { /* 3. PROCESS THE RESPONSE FROM THE SERVER */
|
||||
console.log(data);
|
||||
}
|
||||
});
|
||||
// un-set "name" attributes to avoid submitting to server
|
||||
$(".fileupload").removeAttr('name');
|
||||
// set up all file inputs for jQuery-fileUpload
|
||||
$(".fileupload").fileupload({
|
||||
dataType: 'json',
|
||||
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'))
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -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'),
|
||||
]
|
||||
|
@ -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
|
||||
})
|
||||
|
@ -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',
|
||||
)
|
||||
|
43
apps/map/migrations/0057_auto_20180423_0220.py
Normal file
43
apps/map/migrations/0057_auto_20180423_0220.py
Normal 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'),
|
||||
),
|
||||
]
|
@ -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()
|
||||
|
@ -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 => {
|
||||
|
@ -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
21
apps/map/widgets.py
Normal 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
|
@ -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"),
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user