From 39949562c14adba163ebccfe6c34fc5ab841c8e4 Mon Sep 17 00:00:00 2001 From: Carl van Tonder Date: Tue, 29 May 2018 23:14:36 -0400 Subject: [PATCH] Use multi-file upload for "images" field Closes #62 --- apps/files/forms.py | 7 +++- .../migrations/0003_auto_20180526_1547.py | 36 +++++++++++++++++++ .../migrations/0004_auto_20180530_0308.py | 25 +++++++++++++ apps/files/models.py | 21 ++++++++++- apps/files/static/files/upload.js | 3 ++ apps/files/urls.py | 3 +- apps/files/views.py | 10 ++++-- apps/map/forms.py | 23 ++++++++---- apps/map/migrations/0065_casestudy_images.py | 21 +++++++++++ .../0066_copy_images_to_imagefiles.py | 36 +++++++++++++++++++ apps/map/migrations/0067_remove_old_images.py | 27 ++++++++++++++ apps/map/models.py | 25 ++++--------- apps/map/views.py | 1 + 13 files changed, 207 insertions(+), 31 deletions(-) create mode 100644 apps/files/migrations/0003_auto_20180526_1547.py create mode 100644 apps/files/migrations/0004_auto_20180530_0308.py create mode 100644 apps/map/migrations/0065_casestudy_images.py create mode 100644 apps/map/migrations/0066_copy_images_to_imagefiles.py create mode 100644 apps/map/migrations/0067_remove_old_images.py diff --git a/apps/files/forms.py b/apps/files/forms.py index 9f0ada2..6cf3056 100644 --- a/apps/files/forms.py +++ b/apps/files/forms.py @@ -1,9 +1,14 @@ from django import forms -from .models import File +from .models import File, ImageFile class FileForm(forms.ModelForm): class Meta: model = File exclude = ['user',] + +class ImageFileForm(forms.ModelForm): + class Meta: + model = ImageFile + exclude = ['user',] diff --git a/apps/files/migrations/0003_auto_20180526_1547.py b/apps/files/migrations/0003_auto_20180526_1547.py new file mode 100644 index 0000000..9e61436 --- /dev/null +++ b/apps/files/migrations/0003_auto_20180526_1547.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.6 on 2018-05-26 15:47 +from __future__ import unicode_literals + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('files', '0002_file_user'), + ] + + operations = [ + migrations.CreateModel( + name='ImageFile', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('file', models.FileField(upload_to='.')), + ('caption', models.CharField(default=None, max_length=240, null=True, verbose_name='Image caption')), + ('credit', models.CharField(default=None, max_length=240, null=True, verbose_name='Image credit')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='imagefile', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + migrations.AlterField( + model_name='file', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='file', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/apps/files/migrations/0004_auto_20180530_0308.py b/apps/files/migrations/0004_auto_20180530_0308.py new file mode 100644 index 0000000..8e22c52 --- /dev/null +++ b/apps/files/migrations/0004_auto_20180530_0308.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.6 on 2018-05-30 03:08 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('files', '0003_auto_20180526_1547'), + ] + + operations = [ + migrations.AlterField( + model_name='imagefile', + name='caption', + field=models.CharField(blank=True, default=None, max_length=240, null=True, verbose_name='Image caption'), + ), + migrations.AlterField( + model_name='imagefile', + name='credit', + field=models.CharField(blank=True, default=None, max_length=240, null=True, verbose_name='Image credit'), + ), + ] diff --git a/apps/files/models.py b/apps/files/models.py index 57a123d..b2b8f1d 100644 --- a/apps/files/models.py +++ b/apps/files/models.py @@ -1,5 +1,6 @@ from django.contrib.auth.models import User from django.db import models +from django.utils.translation import ugettext as _ from apps.map.models import CaseStudy, CaseStudyDraft @@ -9,7 +10,7 @@ class BaseFile(models.Model): upload_to='.', ) user = models.ForeignKey( - User, related_name='files' + User, related_name='%(class)s' ) class Meta: @@ -21,3 +22,21 @@ class BaseFile(models.Model): class File(BaseFile): pass + + +class ImageFile(BaseFile): + caption = models.CharField( + verbose_name=_("Image caption"), + max_length=240, + default=None, + null=True, + blank=True, + ) + + credit = models.CharField( + verbose_name=_("Image credit"), + max_length=240, + default=None, + null=True, + blank=True, + ) diff --git a/apps/files/static/files/upload.js b/apps/files/static/files/upload.js index db9596d..fdcf716 100644 --- a/apps/files/static/files/upload.js +++ b/apps/files/static/files/upload.js @@ -213,6 +213,9 @@ class MultipleFilesWidget { } $(function() { + window.images = new MultipleFilesWidget( + document.querySelector('[data-field=images_files]') + ) window.official_project_documents = new MultipleFilesWidget( document.querySelector('[data-field=official_project_documents_files]') ) diff --git a/apps/files/urls.py b/apps/files/urls.py index efae6c9..aeb0607 100644 --- a/apps/files/urls.py +++ b/apps/files/urls.py @@ -1,10 +1,11 @@ from django.conf.urls import url -from .views import FileUploadView, FileDeleteView +from .views import FileUploadView, FileDeleteView, ImageFileUploadView app_name = 'files' urlpatterns = [ url(r'^upload/$', FileUploadView.as_view(), name='upload'), + url(r'^upload/image/$', ImageFileUploadView.as_view(), name='upload'), url(r'^delete/(?P\d+)/$', FileDeleteView.as_view(), name='delete'), ] diff --git a/apps/files/views.py b/apps/files/views.py index b1b38b0..cbb36ce 100644 --- a/apps/files/views.py +++ b/apps/files/views.py @@ -4,8 +4,9 @@ from django.http import JsonResponse from django.shortcuts import render from django.views.generic import FormView, DetailView -from .forms import FileForm -from .models import File +from .forms import ImageFileForm, FileForm +from .models import ImageFile, File + class FileUploadView(LoginRequiredMixin, FormView): model = File @@ -26,6 +27,11 @@ class FileUploadView(LoginRequiredMixin, FormView): return JsonResponse({'is_valid': False, 'errors': form.errors}) +class ImageFileUploadView(FileUploadView): + model = ImageFile + form_class = ImageFileForm + + class FileDeleteView(LoginRequiredMixin, DetailView): model = File diff --git a/apps/map/forms.py b/apps/map/forms.py index 67efffe..edc2cbe 100644 --- a/apps/map/forms.py +++ b/apps/map/forms.py @@ -9,7 +9,7 @@ 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 apps.files.models import File, ImageFile from .models import CaseStudy, SpatialRefSys from .widgets import CommaSeparatedTextInput @@ -71,9 +71,6 @@ class ShortCaseStudyForm(BaseCaseStudyForm): 'project_status', 'synopsis', 'full_description', - 'image', - 'image_caption', - 'image_credit', 'video', 'media_coverage_mainstream', 'media_coverage_independent', @@ -88,6 +85,19 @@ class BootstrapClearableFileInput(forms.ClearableFileInput): class LongCaseStudyForm(BaseCaseStudyForm): """Long version of the CaseStudy form.""" + images = forms.FileField( + widget=BootstrapClearableFileInput(attrs={ + 'url': reverse_lazy('files:upload'), + 'field': 'images_files', + }), required=False + ) + + images_files = forms.ModelMultipleChoiceField( + queryset=ImageFile.objects.all(), + widget=CommaSeparatedTextInput(), + required=True + ) + official_project_documents = forms.FileField( widget=BootstrapClearableFileInput(attrs={ 'url': reverse_lazy('files:upload'), @@ -201,9 +211,8 @@ class LongCaseStudyForm(BaseCaseStudyForm): 'project_status', 'synopsis', 'full_description', - 'image', - 'image_caption', - 'image_credit', + 'images', + 'images_files', 'video', 'video_caption', 'video_credit', diff --git a/apps/map/migrations/0065_casestudy_images.py b/apps/map/migrations/0065_casestudy_images.py new file mode 100644 index 0000000..4051a88 --- /dev/null +++ b/apps/map/migrations/0065_casestudy_images.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.6 on 2018-05-26 15:47 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('files', '0003_auto_20180526_1547'), + ('map', '0064_auto_20180526_1536'), + ] + + operations = [ + migrations.AddField( + model_name='casestudy', + name='images', + field=models.ManyToManyField(blank=True, related_name='image_for', to='files.ImageFile', verbose_name='Images'), + ), + ] diff --git a/apps/map/migrations/0066_copy_images_to_imagefiles.py b/apps/map/migrations/0066_copy_images_to_imagefiles.py new file mode 100644 index 0000000..a896083 --- /dev/null +++ b/apps/map/migrations/0066_copy_images_to_imagefiles.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.6 on 2018-05-26 15:48 +from __future__ import unicode_literals + +from django.db import migrations + + +def copy_images(apps, schema_editor): + CaseStudy = apps.get_model('map', 'CaseStudy') + ImageFile = apps.get_model('files', 'ImageFile') + User = apps.get_model('auth', 'User') + + for case_study in CaseStudy.objects.all(): + author = case_study.author + if author is None: + author = User.objects.get(username='root') + + imagefile = ImageFile( + file=case_study.image, + caption=case_study.image_caption, + credit=case_study.image_credit, + user=author + ) + imagefile.save() + case_study.images.add(imagefile) + + +class Migration(migrations.Migration): + + dependencies = [ + ('map', '0065_casestudy_images'), + ] + + operations = [ + migrations.RunPython(copy_images, migrations.RunPython.noop), + ] diff --git a/apps/map/migrations/0067_remove_old_images.py b/apps/map/migrations/0067_remove_old_images.py new file mode 100644 index 0000000..3789202 --- /dev/null +++ b/apps/map/migrations/0067_remove_old_images.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.6 on 2018-05-29 05:20 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('map', '0066_copy_images_to_imagefiles'), + ] + + operations = [ + migrations.RemoveField( + model_name='casestudy', + name='image', + ), + migrations.RemoveField( + model_name='casestudy', + name='image_caption', + ), + migrations.RemoveField( + model_name='casestudy', + name='image_credit', + ), + ] diff --git a/apps/map/models.py b/apps/map/models.py index ebb0277..5a6eb2e 100644 --- a/apps/map/models.py +++ b/apps/map/models.py @@ -442,25 +442,12 @@ class CaseStudy(models.Model): blank=True ) - # 1.15.1 - image = models.ImageField( - verbose_name=_("Image") - ) - - # 1.15.2 - image_caption = models.CharField( - verbose_name=_("Image caption"), - max_length=240, - default=None, - null=True, - ) - - # 1.15.3 - image_credit = models.CharField( - verbose_name=_("Image credit(s)"), - max_length=240, - default=None, - null=True, + # 1.15.1, 1.15.2, 1.15.3 + images = models.ManyToManyField( + 'files.ImageFile', + related_name='image_for', + verbose_name=_("Images"), + blank=True ) # 1.16.1 diff --git a/apps/map/views.py b/apps/map/views.py index 764fd7f..1c9fa7f 100644 --- a/apps/map/views.py +++ b/apps/map/views.py @@ -63,6 +63,7 @@ class BaseForm(LoginRequiredMixin, CreateView): form.cleaned_data.pop('official_project_documents', None) form.cleaned_data.pop('other_documents', None) form.cleaned_data.pop('shapefiles', None) + form.cleaned_data.pop('images', None) self.object = form.save()