Background: I have two models: SellingItem and SellingItemImages. SellingItemImages has a custom FileField that can take multiple files. By putting two forms(itemform and imageform) under single element (enctype="multipart/form-data"), I was able to allow users to upload multiple images. Now, I want to incorporate client side image optimization and better UI. I tried out filepond but am facing some challenges. I organized this post by
- showing django code without filepond
- showing code with filepond what
- I accomplished with filepond so far
- questions on what to do next
** 1)django code without filepond.** models.py
# models.py
class SellingItem(models.Model):
seller = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
name = models.CharField(max_length=200)
description = models.CharField(max_length= 500, null=True, blank=True)
price = models.IntegerField(default=0)
class SellingItemImages(models.Model):
sellingitem = models.ForeignKey(SellingItem, default = None, on_delete=models.CASCADE, related_name='images')
image = ContentTypeRestrictedFileField(content_types=['image/png', 'image/jpeg','image/jpg'],blank=True,
max_upload_size=5242880)
#ContentTypeRestrictedFileField is a custom FileField.
Here is forms.py
class SellingItemForm(forms.ModelForm):
class Meta:
model = SellingItem
fields = ('name', 'description', 'price')
class SellingItemImagesForm(forms.ModelForm):
class Meta:
model = SellingItemImages
fields= ('image',)
widgets = {
'image': forms.FileInput(attrs={'multiple':True,}),
}
Here is views.py
@login_required
def post_newitem(request):
if request.method == 'POST':
itemform = SellingItemForm(request.POST)
imageform = SellingItemImagesForm(request.POST, request.FILES)
if '_cancel' in request.POST:
itemform = SellingItemForm()
imageform = SellingItemImagesForm()
return render(request, 'market/post_newitem.html',
{'itemform': itemform, 'imageform': imageform})
else:
if '_publish' in request.POST:
print('hit publish')
if itemform.is_valid() and imageform.is_valid():
print('two forms are valid')
sellingitem = itemform.save(commit=False)
sellingitem.seller = request.user
sellingitem.published_date = timezone.now()
sellingitem.save()
files = request.FILES.getlist('image')
for f in files:
photo = SellingItemImages(sellingitem=sellingitem, image=f)
photo.save()
return redirect('market_home')
else:
print(itemform.errors, imageform.errors)
else:
itemform = SellingItemForm()
imageform = SellingItemImagesForm(request.POST)
return render(request, 'market/post_newitem.html',
{'itemform': itemform, 'imageform': imageform})
Here is template post_newitem.html. Here I put two forms under single element.
{% extends 'market/marketbase.html' %}
{% block content %}
<form id="post_form" method="post" action="" enctype="multipart/form-data">
{% csrf_token %}
{% for hidden in itemform.hidden_fields %}
{{ hidden }}
{% endfor %}
{% load widget_tweaks %}
<table>
<tr>
<td>{{ itemform.name |add_class:"name_form_field"}}</td>
</tr>
<tr>
<td>{{ itemform.description |add_class:"description_form_field" }}</td>
</tr>
<tr>
<td>{{ itemform.price |add_class:"price_form_field" }}</td>
</tr>
{% for hidden in imageform.hidden_fields %}
{{ hidden }}
{% endfor %}
<tr>
<td>
{{ imageform.image |add_class:"image_form_field" }}
</td>
</tr>
</table>
<input class='edit-delete-buttons' type="submit" name="_publish">
<input class='edit-delete-buttons' type="submit" name="_cancel">
</form>
{% endblock %}
The above code works in allowing users to upload multiple images. As mentioned earlier, to get better UI and client-side image optimization, I turned to this nice javascript library, filepond.
2) code with filepond
<script>
document.addEventListener('DOMContentLoaded', function() {
// Create FilePond object
const inputElement = document.querySelector('input[type="file"]');
const pond = FilePond.create(inputElement, {
// track addfile event
onaddfile: (err, fileItem) => {
console.log(err, fileItem.getMetadata('resize'));
},
// to see whether all files are processed
onprocessfiles: () => {
console.log('process finished for all files');
},
// show when a file is reverted
onprocessfilerevert: (fileItem) => {
console.log(fileItem + 'is reverted');
},
});
});
FilePond.registerPlugin(
FilePondPluginImagePreview,
FilePondPluginImageCrop,
FilePondPluginImageTransform,
FilePondPluginFileValidateType,
FilePondPluginImageResize);
var csrf_token="{{ csrf_token }}";
FilePond.setOptions({
imagePreviewHeight: 100,
allowMultiple: true,
imageCropAspectRatio: 1,
imageResizeTargetWidth: 256,
imageResizeMode: 'contain',
imageTransformOutputQuality: 80,
maxFiles: 4,
server: {
// url, none, because endpoints located on the same server
process: {
headers: {"X-CSRFToken":csrf_token,},
url: '/media/',
method: 'POST',
},
revert: {
headers: {
"X-CSRFToken":csrf_token,
},
url: '/media/',
method: 'DELETE',
},
fetch: null,
load: null,
}
});
</script>
3) what I accomplished with filepond so far
With the above code, I was able to a. show filepond drop-drag area b. show image preview c. filepond showing upload completed as shown in the following image d. in Chrome Develop Tools console, showing "process finished for all files"
image showing filepond drop area after selecting two files
4) questions on what to do next
a: server related:I understand that the green highlight with "upload finished" is for users. This does not necessarily mean that the file is uploaded to server.
Were the files uploaded to the server? Is my server config. correct? How can one know if the files are uploaded to server (using console)?
b: django related: once the files are uploaded to server, how can one retrieve these files and point to the right django models (in my case, SellingItemsImages)?
I tried files=request.FILES.getlist('filepond') as shown in this post, but files returned empty list. I do not know if this is because this snippet does not work, or it is because I do not have any files uploaded to start with.
c: django form related: as mentioned in the background, I have two forms, one is regular form with name, price, etc; another one for uploading images. Without filepond, I was sending both forms with one submit button in one post_newitem view. With filepond, I guess I have a couple of options: - option 1: send regular form with submit button, while send filepond files asynchronously. - option 2: let filepond optimize images (via transformplugin), and send images and other form areas(name, price, etc) as FormData.
I hope to get some input on pros and cons about these two options, and how to proceed with these two options.