how to upload multiple images to a blog post in django how to upload multiple images to a blog post in django django django

how to upload multiple images to a blog post in django


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 modelsfrom django.contrib.auth.models import Userfrom django.template.defaultfilters import slugifyclass 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 formsfrom .models import Post, Imagesclass 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 renderfrom django.forms import modelformset_factoryfrom django.contrib.auth.decorators import login_requiredfrom django.contrib import messagesfrom django.http import HttpResponseRedirectfrom .forms import ImageForm, PostFormfrom .models import Images@login_requireddef 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>


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>


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