0

I am trying to let the user upload multiple images per project. The Django Documentation enter link description here shows how to do it in generell but I think I am rendering my form differently than they are doing it. So I don´t know how to add the 'multiple' attribute in my input field. In addition they have an extra class in their views.py and inside of that the function.

views.py

def createProject(request):
    form = ProjectForm()

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

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

models.py

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

forms.py

class ProjectForm(ModelForm):
    class Meta:
        model = Project
        fields = ['title', 'featured_images']

project_form.html or template file

            <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 %}
            </form>
Sunderam Dubey
  • 1
  • 11
  • 20
  • 40
Phil Grütter
  • 37
  • 1
  • 8

3 Answers3

1

First you have to slightly adjust your form to allow for multiple upload:

# forms.py

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

Then in the view you have to grab multiple entries:

# 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)

Please let me know if that works.

JSRB
  • 2,492
  • 1
  • 17
  • 48
1

There are multiple ways to associate multiple images to a single field in Django. One way I like to do is:

  1. First create two models, one for the thing you want(in this case Project) and the other for images.
class MyModel(models.Model):
   field = models.CharField(max_lenght=255)

class MyModelImage(models.Model):
   image = models.ImageField(upload_to="/where_you_want_to")
   field = models.ForeignKey(MyModel,related_name="images",on_delete=models.CASECADE)

  1. Then using Django inline formset you can add multiple images at once.

I hope this helps you out

Edit:

# this is how you model might look
class ParentModel(models.Model):
    # your model
    pass 

class ImageModel(models.Model):
    image = models.ImageField(upload_to="your_path/")
    parent = models.ForeignKey(ParentModel,on_delete=models.CASECADE,related_name="images")


# this is for view

# create your custom inlineforset form
# with first argument is your model with which you want to associate multiple images and second is your image model
CustomInlineForm = inlineformset_factory(ParentModel, ImageModel)

# use this custom inline form like this
formset = CustomInlineForm(request.FILES or None, instance=your_instance)

# optionally you can also get all your image using related name also like this:
parent_instance.images
Dakshesh Jain
  • 321
  • 1
  • 8
  • Could you specify what you mean with Django inline formset. Is it about the inline formset factory? – Phil Grütter Mar 20 '22 at 07:32
  • Yes, I meant inlineformset_factory. ```from django.forms import inlineformset_factory```. This is one video ref: https://www.youtube.com/watch?v=JIvJL1HizP4 – Dakshesh Jain Mar 20 '22 at 11:15
  • 1
    Ok so I just have to create 2 different models as you already did and then the views.py should look like the guy in the video did but instead of this: ```formset = LanguageFormset(request.POST, instance=programmer) ``` I write (request.POST, request.FILES.getlist('featured_images')) like the user 'Jonas' did, right? – Phil Grütter Mar 20 '22 at 12:29
  • Maybe [this answer](https://stackoverflow.com/a/73822733/12042940) could help. Which is without `inlineformset_factory` using CBV – Muhammmed Nihad Sep 23 '22 at 04:00
0

Based on the fact that you're using a function-based view, you are wise to follow the multiple image upload tutorial from "Very Academy", although he didn't explain a few things and left you to figure out how to implement it into a real-life project. I'll show what works now inspired by this tutorial:

forms.py (a script defined in the app folder)

from *APP*.models import DashboardModel, Image

#The main form in forms.py

class RecordForm(ModelForm):

    class Meta:
        model = DashboardModel # (.....whatever you named it)
        

        fields = (
        
          #...all fields you defined in your database model
        )
        
        labels = {
         
          #....labels including:
            "img_name" : "",
            "img_slug" : "",
            "img_description" : "",

            }
                
        widgets = {
           
            #....widgets including:

            "img_name" : forms.TextInput(attrs={'class':'form-control', 'placeholder':'Name'}),
            "img_slug" : forms.TextInput(attrs={'class':'form-control', 'placeholder':'Slug'}),
            "img_description" : forms.Textarea(attrs={'class':'form-control', 'placeholder':'Description'}), 

            }

class ImageForm(forms.ModelForm):

  class Meta:
      model = Image
      fields = ("image",)

      labels = {"image" : "Image upload"}

      widgets = {
        'image': forms.ClearableFileInput(attrs={'class': 'file-upload-input', 'id': 'file-selector',"multiple": True})
       }

models.py

class DashboardModel(models.Model):

   #...all your main database fields, NOT including any image field



#A new model for creation of image associations:

class Image(models.Model):
    project = models.ForeignKey(DashboardModel, on_delete=models.CASCADE)
    image = models.ImageField()

    def __str__(self):
        return self.image.url # return something meaningful instead of "object"
    

settings.py

import os

MEDIA_ROOT = os.path.join(BASE_DIR, "media")
MEDIA_URL = "/media/"

urls.py

urlpatterns = [
    
    ...your url paths
    
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
    

views.py

def create_project(request):


    if request.method == "POST":
        form = ProjectForm(request.POST)
        files = request.FILES.getlist("image") # get a list of images from the image association (Image model in models.py)
        if form.is_valid():
            f = form.save(commit=False)
            f.user = request.user
            f.save()
            for i in files:
                Image.objects.create(project=f, image=i)  #Check the Image model in models.py, this infers the fields "project" and "image"
            return HttpResponseRedirect(" *your database dashboard page* ")
        else:
            print(form.errors)
    else:
        form = ProjectForm()
        imageform = ImageForm()

    return render(request, "create_project.html", {"form": form, "imageform": imageform})

create_project.html in app/templates/app folder

You will need to define a html form (as you might have already):

<form id="post_form" method="post" action="" enctype="multipart/form-data">

Then within the form:

   {% csrf_token %}
     {% for hidden in form.hidden_fields %}
          {{ hidden }}
     {% endfor %}
    {{ imageform.label }}
    {{ form.img_name }} <br />
    {{ form.img_slug }} <br />
    {{ form.img_description }} <br />
    
    {{ imageform.management_form }}
      {% for form in imageform %}
        {{ form }}
      {% endfor %}

Image retrieval works by accessing the image set (getting the image URLS in this case):

_set is the queryset which accesses the images on the database record

record is the context name for my main model (DashboardModel)

{% for path in record.image_set.all %}
  {% if not forloop.first %}
  <br>
  {% endif %}

 <img src="{{ path }}" width='600'>      
{% endfor %}

Updating images with new images (a new "update" view function in views.py) is done by adding some extra logic (you can be creative with this):

            d = DashboardModel.objects.get(id=pk)

            if(d.image_set.all()): #if images are present in the record  
                if(files): #if new files are loaded
                    d.image_set.all().delete() #delete the previous images
                    for i in files:
                        Image.objects.update_or_create(project=f, image=i) #create the new images
                else:
                    pass               
            else: #no images already present in the record
                for i in files:
                    Image.objects.create(project=f, image=i) 
Conor
  • 327
  • 6
  • 15