Home > database >  Adding a multiple file field to a Django ModelForm
Adding a multiple file field to a Django ModelForm

Time:06-18

I'm wanting to create a form that allows multiple image uploads. I've got a Listing model that looks like this:

class Listing(models.Model):
    location = models.CharField("Address/Neighborhood", max_length=250)


class ListingImage(models.Model):
    listing = models.ForeignKey(
        Listing,
        related_name="images",
        on_delete=models.SET_NULL,
        null=True,
    )
    image = models.ImageField()

I'm using django-crispy-forms to create the form on the page but I cannot figure out how to get the listing field onto the page.

class ListingModelForm(forms.ModelForm):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.helper = FormHelper()
        self.helper.layout = Layout(
            Fieldset(
                "Location",
                Div("location", css_)
            )
        )

        class Meta:
            model = Listing
            fields = "__all__"

And this is my view:

class ListingCreateView(LoginRequiredMixin, CreateView):
    model = Listing
    form_class = ListingModelForm

    def form_valid(self, form):
        form.instance.user = self.request.user
        return super().form_valid(form)

CodePudding user response:

If I'm understanding correctly, you want to have a field where you'll be able to add multiple images and then save them all. If I'm correct, then here's an approach you could take.

Add an extra field on the form that you'll set to accept multiple images... for example:

class ListingModelForm(forms.ModelForm):
     # Extra field to collect multiple images
     listing_images = forms.ImageField(
          required=False,
          widget=forms.ClearableFileInput(attrs={'name': 'listing_images', 'id': 'listing_images', 'multiple': True})
     )

     # other portion of your form below...

You can now access the field in the form: {{ form.listing_images }}, adding multiple images...

Now within the function's post request you have handling that form... You can do:

if request.method == 'POST':
     form = ListingModelForm(data=request.POST, files=request.FILES)

     if form.is_valid():
          # listing = form.save(commit=False)
          # listing.location = 'blah blah'
          # listing.save()

          # or just use
          listing = form.save()

          # Looping over the list of images and create for each iteration
          for file in request.FILES.getlist('listing_images'):
              ListingImage.objects.create(listing=listing, image=file)

          # Also note that you could override the save method on the form to include the above forloop
          # if you don't want it out here...

Assuming that your image field on the ListingImage have a path to upload the images and have configured your MEDIA_URL and MEDIA_ROOT settings...

UPDATES

Posted an answer before knowing you were using the class-based view.

class ListingCreateView(LoginRequiredMixin, CreateView):
     model = Listing
     form_class = ListingModelForm

     def form_valid(self, form):
          # form.instance.user = self.request.user

          listing = form.save()  # Listing must be created first to use as the image reference

          for file in self.request.FILES.getlist('listing_images'):
               ListingImage.objects.create(listing=listing, image=file)

          return super(ListingCreateView, self).form_valid(form)
     
     # or using the post method
     def post(self, *args, *kwargs):
          form = ListingModelForm(data=request.POST, files=request.FILES)
          
          if form.is_valid():
               for file in self.request.FILES.getlist('listing_images'):
                    ListingImage.objects.create(listing=listing, image=file)
          
          return redirect('to a path...')
          

So you could just use one of the methods highlighted above on the class-based view.

  • Related