Phil Grütter
Phil Grütter

Reputation: 47

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>

Upvotes: 0

Views: 2573

Answers (3)

Conor
Conor

Reputation: 395

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) 

Upvotes: 0

Dakshesh Jain
Dakshesh Jain

Reputation: 359

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

Upvotes: 2

JSRB
JSRB

Reputation: 2623

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.

Upvotes: 1

Related Questions