Multiple Image upload like ecommerce application

I want to let the user upload multiple images per model as Instagram or Amazon does. Till now I have title and featured_images fields in my model but the “multiple” attribute seems not to work. Thanks for your answers.

models.py

class Project(models.Model):
    title = models.CharField(max_length=200)
    featured_images = models.ImageField(null=True, blank=True)

forms.py

class ProjectForm(ModelForm):
    class Meta:
        featured_images = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}))
        model = Project
        fields = ['title', 'featured_images']

views.py

def createProject(request):
    form = ProjectForm()

    if request.method == 'POST':
        form = ProjectForm(request.POST, request.FILES.getlist('featured_images'))
        if form.is_valid():
            project = form.save(commit=False)
            project.save()

    context = {'form':form}
    return render(request, 'projects/project_form.html', context)

project_form.html

<form class="form" method="POST" enctype="multipart/form-data">
    {% csrf_token %}
    {% for field in form %}
    <div class="form__field">
        <label for="formInput#text">{{field.label}}</label>
        {{field}}
    </div>
    {% endfor %}
    <input type="submit" name="" id="">
</form>

You only have a single ImageField defined in your model. You can’t store multiple images in the same field.

The plan is to let the user upload as much images per model as he wants. So I don´t want to have different imageuploadfields. There should be just on image-upload field in which the user can upload multiple images. I saw a solution in which 2 models where created and connected with a ForeignKey relationship, but unfortunately didn´t worked out. One for the productin-formations and one for the images. Is this the way to go?

That is correct, that is the appropriate structure for that type of relationship.

Something like this ?
models.py

class Project(models.Model):
    title = models.CharField(max_length=200)
    describtion = models.TextField(null=True, blank=True)

class ProjectImage(models.Model):
    project = models.ForeignKey(Project, on_delete=models.CASCADE)
    image = models.FileField()

forms.py

class CommentForm(forms.ModelForm):
    class Meta:
        model = ProjectImage
        fields = ['title', 'describtion', 'images']
        widgets = {
            'images': forms.ClearableFileInput(attrs={'multiple': True}),
        }

But how would the views.py and the template look like in such a case. Do I have to define the two models seperatly?

One way to do this (there are others) is that you have a ModelForm for Project, and the images are just an extra field in that form.
When the form is submitted, you need to create the instances of ProjectImage from that extra field, assigning the relationship in that instance to the new instance of Project that you’ve just created.

So forms.py should look like this? But where does the reference to image come from? Do I have to make an import like this
from .models import ProjectImage in forms.py

forms.py

class CommentForm(forms.ModelForm):
    class Meta:
        model = Project
        fields = ['title', 'describtion', 'image']
        widgets = {
            'image': forms.ClearableFileInput(attrs={'multiple': True}),
        }

views.py

def createProject(request):
    form = ProjectForm(instance=ProjectImage)

    if request.method == 'POST':
        form = ProjectForm(request.POST, request.FILES.getlist('image'))
        if form.is_valid():
            project = form.save(commit=False)
            project.save()
    context = {'form':form}
    return render(request, 'projects/project_form.html', context)

Instances are pretty new to me, so sorry for what I just send to you :slight_smile:

A ModelForm is a form - where Django creates some (most, or all) of the fields for you. Beyond that, they work like any other form.

That means you can define your images field in the form in the same way you create any other field in a form.

Since image is not a field in Project, it would not be listed in the fields attribute of the Meta class. It’s not part of Project, and so isn’t referenced there. Likewise, the widget needs to be defined for that (non-model) form field, not part of the ModelForm.

Your view is on the right track. After you do the form.save(commit=False), you would create all the instances of the ProjectImage model, one for each image.

Thanks for your answer. So I would send you all my code from which I think it should be right. First I create two seperated models.
models.py

class Project(models.Model):
    title = models.CharField(max_length=200)
    describtion = models.TextField(null=True, blank=True)

class ProjectImage(models.Model):
    project = models.ForeignKey(Project, on_delete=models.CASCADE)
    image = models.FileField()

Now to add the image field to forms.py I would do something like this:
forms.py

class ProjectForm(ModelForm):
    image = forms.ImageField()
    class Meta:
        model = Project
        fields = ['title', 'describtion', 'image']
        widgets = {
            'image': forms.ClearableFileInput(attrs={'multiple': True}),
        }

And for the views
views.py

def createProject(request):
    form = ProjectForm(instance=ProjectImage)

    if request.method == 'POST':
        form = ProjectForm(request.POST, request.FILES.getlist('image'))
        if form.is_valid():
            project = form.save(commit=False)
            for i in image:
                i = ProjectForm(instance=form)
            project.save()
    context = {'form':form}
    return render(request, 'projects/project_form.html', context)

You’re really close, except the Form doesn’t much help you here for the images.

So you’re starting from what is shown in the second example at File Uploads | Django documentation | Django, what you have as for i in image:. (Good!)

What I believe “i” would be in this loop is a file object. Taking inspiration from the second example at File Uploads | Django documentation | Django, then you should be able to do this:

project_image = ProjectImage(image=i)
project_image.project = project
project_image.save()

I wrote it this way to make the steps explicitly clear, but I would more likely write this as:
ProjectImage(project=project, image=i).save().

Side note: Since with this structure you’re not modifying project, you do not need to use the save with commit=False before the loop. You can remove that parameter from form.save() and remove the project.save().

(Disclaimer: I’ve never provided a facility for uploading multiple files in a field - this is just my understanding from what I read in the docs. There’s a good chance I have something wrong here.)

If the Form doesn´t help with images. Do I have to change forms.py a bit or is it already fine?

My views.py file looks like this now

def createProject(request):
    form = ProjectForm(instance=ProjectImage)

    if request.method == 'POST':
        form = ProjectForm(request.POST, request.FILES.getlist('image'))
        if form.is_valid():
            project = form.save()
            for i in image:
                i = ProjectForm(instance=form)
            ProjectImage(project=project, image=i).save()
    context = {'form':form}
    return render(request, 'projects/project_form.html', context)

but image in the for loop isn´t defined.

I thought that uploading multiple images in one imput field would be a common thing because all the ecommerce websites are doing that.

You do not use the form inside the loop. The ProjectImage statement replaces that line.

More accurately, I would describe it as this form doesn’t help you with the images in the way you are working with them.

There may be a third-party library that facilitates this, you can check djangopackages.org to see if you can find something appropriate. However, if we’re on the right track here, it’s probably not necessary.

Also, you need to retrieve the files independantly from the form.
Again, see the second example at File Uploads | Django documentation | Django.
e.g.

if request...:
    form = ProjectForm(request.POST)
    images = request.FILES.getlist('image')
    ...

Ok but could you tell me what is wrong with the way I have it now and what I have to fix to make it work?

My for loop in views.py looks like this now and I don´t get any errors related to this.

            for i in form.image:
                i = ProjectForm(instance=form)
            ProjectImage(project=project, image=i).save()

Unfortunately I don´t really know what you mean by that.

Also, you need to retrieve the files independantly from the form

Do I just have to add this similar to the example?

def createProject(request, *args, **kwargs):

I showed you how to fix it.

  • Delete the line i = Project...
  • Replace it with ProjectImage...
  • Add the images = ... line after the form = ... line as shown above.
  • Do not add, change, or alter anything else (yet).

Sorry I forgot to send to all the changes. So it should look like this right

def createProject(request):
    form = ProjectForm()

    if request.method == 'POST':
        form = ProjectForm(request.POST)
        images = request.FILES.getlist('image')
        if form.is_valid():
            project = form.save()
            for i in images:
                ProjectImage(project=project, image=i).save()
    context = {'form':form}
    return render(request, 'projects/project_form.html', context)

It should be for i in images:, but yea, this basically looks right.

(You don’t need the instance= parameter in your first ProjectForm call. It’s not doing anything for you.)

Ok I just edited it. So could you tell me which changes are left in forms.py?

You need to add the widget you want to use for the ImageField directly to the form field. Since it’s not part of the model, the settings in Meta don’t apply.

Perfect thank you so much for helping me out since now two days :slight_smile:
that´s my solution in forms.py

class ProjectForm(ModelForm):
    image = forms.ImageField(widget=ClearableFileInput(attrs={'multiple':True}))
    class Meta:
        model = Project
        fields = ['title', 'describtion', 'image']

You can take the field name ‘image’ out of the fields list - again, it’s not part of the Project model.