how to upload multiple images to a blog post in django

50,591

Solution 1

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 need 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
from .models import Images

@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()
            # use django messages framework
            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, we are getting both of our forms, and it will check both forms whether they are valid or not. In that way, 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.

Your 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>

Solution 2

Step by step solution => Even, I was stuck too. So this is how I successfully do.

Finally, implementing below code, I achieved this

  • 1 Note model with many Images
  • Multiple Uploads(at the same time, with same Choose File button, & all save together as like in Gmail file upload)

Here are my Note and Image Model- (or see full code)

class Note(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE)
    title = models.CharField(max_length=30)
    text = models.TextField(null=True,blank=True)
    created_date = models.DateTimeField(auto_now_add=True)
    last_modified = models.DateTimeField(auto_now=True)

class Images(models.Model):
    note = models.ForeignKey(Note,on_delete=models.CASCADE)
    image = models.ImageField(upload_to=user_directory_path,null=True,blank=True)

Here is my form (Link of doc. on multiple upload)- (or see full code)

class NoteForm(forms.ModelForm):
    class Meta:
        model = Note
        fields = ['title','text'] #make sure to mention field here, if nothing is mentioned then all will be required.

class NoteFullForm(NoteForm): #extending form
    images = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}))

    class Meta(NoteForm.Meta):
        fields = NoteForm.Meta.fields + ['images',]

Here is my View file- (or see full code)

def addNoteView(request):
if request.method == "POST":
    #images will be in request.FILES
    form = NoteFullForm(request.POST or None, request.FILES or None)
    files = request.FILES.getlist('images')
    if form.is_valid():
        user = request.user
        title = form.cleaned_data['title']
        text = form.cleaned_data['text']
        note_obj = Note.objects.create(user=user,title=title,text=text) #create will create as well as save too in db.
        for f in files:
            Images.objects.create(note=note_obj,image=f)
    else:
        print("Form invalid")

And, finally my Html file (be sure to bind files as said in docs)- (or see full code)

<form action="{% url 'note:add_note' %}" method="post" class="note-form" enctype="multipart/form-data">{% csrf_token %}
  <div class="form-group">
    <label for="note-title">Title</label>
    <input type="text" name="title" class="form-control" id="note-title" placeholder="Enter Title" required>
  </div>
  <div class="form-group">
    <label for="note-description">Description</label>
    <textarea type="text" name="text" class="form-control" id="note-description" placeholder="Description here"></textarea>
  </div>
  <div class="form-group">
    <label for="note-image">Images</label>
    <input type="file" name="images" class="form-control-file" id="note-image" multiple>
  </div>
  <button type="submit" class="btn btn-primary">Submit</button>
</form>

Solution 3

The answer is very simple. So much of code is not required.

HTML File

<input type="file" name = "files" multiple />

VIEWS.PY

files = request.FILES.getlist('files')

for f in files:
    a = Image(image=f)
    a.save()

Solution 4

I had a similar problem. In my model, an article must have a thumbnail(image), then I set five more fields for optional images. The problem came in when after applying the safe filter, the image source would not render because it was not HTML anymore,

<img src="{{article.image1.url}}" class="some class">

would not work. A temporary solution I used was to name the images according to the title of the article. If I am writing on "django filters", I would name my additional images djangofiltersimage1.png or djangofiltersimage2.png, this helps since in my model each article has a unique title. I then changed the image source to:

<img src="/media/djangofiltersimage1.png" class="some class">

The only issue is the strict naming of images. You can upload more images by creating a model for Blog Images. I am still looking for another solution in the meantime. You can check out my blog here

Solution 5

getlist name and html input name fiels should be same

for models.py

class studentImage(models.Model):
        image = models.ImageField(upload_to='media/')
    
        def __str__(self):
            return self.image

for views.py

def studentImageView(request):
    if request.method == "POST":
        images = request.FILES.getlist('images')
        for image in images:
            photo = studentImage.objects.create(image=image,)
            photo.save()

    return render(request, 'image.html')

for template

<form method="post" enctype="multipart/form-data" >
  {% csrf_token %}
   <input required name="images" type="file" multiple >
  <button class="btn btn-primary" type="submit">Upload</button>
</form>
Share:
50,591
ollysmall
Author by

ollysmall

Updated on July 09, 2022

Comments

  • ollysmall
    ollysmall almost 2 years

    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!

  • ollysmall
    ollysmall over 8 years
    thanks so much for this! how would you incorporate a drag and drop system like dropzone js into it? Do you need formsets if you want a drag and drop method?
  • qasimalbaqali
    qasimalbaqali over 8 years
    @ollysmall sorry I can't help you with that, I am oblivious with Javascript so I can't tell you how would you do it. But check this out github.com/sigurdga/django-jquery-file-upload I tried to do exactly what you are asking for and I found django jquery file upload, but I had no idea how to play with it and for it to work with formsets. Give it a try maybe?
  • dungu
    dungu almost 7 years
    @ qasimalbaqali I'm late for the party, but I have a slightly different issue here my question here. hope you could help me to fix it
  • Chris
    Chris over 6 years
    What should the admin look like with this?
  • bgarcial
    bgarcial over 6 years
    @qasimalbaqali, I am using Django 1.10 and I replace context_instance by context in the render function. However, I get the error: Exception Value: render() got multiple values for argument 'context' My traceback is: dpaste.com/3W0HB73 Any orientation will be highly appreciated
  • qasimalbaqali
    qasimalbaqali over 6 years
    @bgarcial sorry, I haven't touched django for a bit of time, I can't help you with that. But maybe this would help stackoverflow.com/a/38739423/4708186
  • fpaekoaij
    fpaekoaij over 4 years
    @qasimalbaqali Can you update your answer for class based forms also please?
  • Lars
    Lars about 3 years
    Can i do it into only One model ?
  • qasimalbaqali
    qasimalbaqali about 3 years
    @Program depends on the data storage technology you have I guess. Since the example is based on using a relational database, and to relate multiple images to one post, we use two tables, hence two different models. But, if you're using a NoSQL database such as MongoDB that would be possible.