Search code examples
djangodjango-formsdjango-templatesmultiple-file-upload

Django multiple image upload


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>

Solution

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