I am trying to allow each user to upload multiple pictures to a single blog post. I have been trying to work out the best way to do this for a few days now. What is the best practice to do this?
From what I have read, I should make a seperate image model from the blog post model and use a foreign key. Is that right?
Then there is the matter of how to allow them to upload multiple pictures at the same time. Am I right in assuming I should use something like drop zone?
Any advice on best practices for storing the photos is also welcome. I have looked at Amazon s3 and cloudinary. I want to create something which is scalable.
Any help would be much appreciated!
You'll just need two models. One for the Post and the other would be for the Images. Your image model would have a foreignkey to the Post model:
from django.db import models
from django.contrib.auth.models import User
from django.template.defaultfilters import slugify
class Post(models.Model):
user = models.ForeignKey(User)
title = models.CharField(max_length=128)
body = models.CharField(max_length=400)
def get_image_filename(instance, filename):
title = instance.post.title
slug = slugify(title)
return "post_images/%s-%s" % (slug, filename)
class Images(models.Model):
post = models.ForeignKey(Post, default=None)
image = models.ImageField(upload_to=get_image_filename,
verbose_name='Image')
You need to create a form for each model, but they will be related to each other, as in when the user is filling out the form post he has to complete the image form too for the post to successfully be posted, and we shall do that in the views, but for now your form can look something like this
from django import forms
from .models import Post, Images
class PostForm(forms.ModelForm):
title = forms.CharField(max_length=128)
body = forms.CharField(max_length=245, label="Item Description.")
class Meta:
model = Post
fields = ('title', 'body', )
class ImageForm(forms.ModelForm):
image = forms.ImageField(label='Image')
class Meta:
model = Images
fields = ('image', )
Now this is the most important part of everything, the views, because this is where uploading multiple images to a single magic happens. For us to be able to upload multiple images at once, we need multiple image fields right? That's where you fall in love with Django formsets. We will ned django formsets to make this happen, you can read about formsets in the Django documentation, which I have linked :) But here is how your view should look like:
*Very important the imports
from django.shortcuts import render
from django.forms import modelformset_factory
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.http import HttpResponseRedirect
from .forms import ImageForm, PostForm
@login_required
def post(request):
ImageFormSet = modelformset_factory(Images,
form=ImageForm, extra=3)
#'extra' means the number of photos that you can upload ^
if request.method == 'POST':
postForm = PostForm(request.POST)
formset = ImageFormSet(request.POST, request.FILES,
queryset=Images.objects.none())
if postForm.is_valid() and formset.is_valid():
post_form = postForm.save(commit=False)
post_form.user = request.user
post_form.save()
for form in formset.cleaned_data:
#this helps to not crash if the user
#do not upload all the photos
if form:
image = form['image']
photo = Images(post=post_form, image=image)
photo.save()
messages.success(request,
"Yeeew, check it out on the home page!")
return HttpResponseRedirect("/")
else:
print(postForm.errors, formset.errors)
else:
postForm = PostForm()
formset = ImageFormSet(queryset=Images.objects.none())
return render(request, 'index.html',
{'postForm': postForm, 'formset': formset})
In the view the we are getting both of our forms, and the view will check both forms if they are valid. In that way the user has to fill the form AND upload all the images which in this case are 3 extra=3
. Only then will the post successfully get created.
You template should look like this then:
<form id="post_form" method="post" action="" enctype="multipart/form-data">
{% csrf_token %}
{% for hidden in postForm.hidden_fields %}
{{ hidden }}
{% endfor %}
{% for field in postForm %}
{{ field }} <br />
{% endfor %}
{{ formset.management_form }}
{% for form in formset %}
{{ form }}
{% endfor %}
<input type="submit" name="submit" value="Submit" />
</form>
If you have some inputs name=image1 name=image2 name=image3 etc in your template try this
for i in xrange(1,4):
if ('image'+str(i)) in request.FILES:
image = request.FILES.get('image'+str(i), None)
if image.content_type == 'image/jpeg'
image._set_name(str(description.id)+'_image'+str(i)+'.jpg')
photo = Images(description=description, image=image)
photo.save()