title: How to add multiple images to a django form asynchronously before form submit
body: <p><strong>Intro:</strong> I have a python Django web app where users are allowed to create posts. Each post has 1 main image and followed by extra images (max 12 &amp; min 2) that are associated with that post. I want to let users add a total of 13 images. 1 main image and 12 extra images. </p>
  
  <p><strong>The issue:</strong> Usually users take photos with their smart phones. which makes image size upto 10MB . with 13 images that can become 130MB form. My django server can accept a max of 10MB form. So I cannot reduce the images ServerSide</p>
  
  <p><strong>What I want to do:</strong> I want such that when the user uploads each image to a form. The size of that image is reduced on client side and it is asynchronously saved in a temporary place on my server using Ajax. When the post is created all these images are linked to the post. So basically when the user hits submit on the post create form. Its a super light form with no images. Sounds too ambitious.. ha ha maybe</p>
  
  <p><strong>What I have so far:</strong> </p>
  
  <ol>
  <li>I have the models/views (all django parts that create a post) without the asynchronous part. As in, if the form after all images are added is less than 10MB. My post is created with how many ever extra images</li>
  <li>I have the Javascript code that reduces the size of the images on the client side and asynchronously adds it to my server. All I need to do is give it a endpoint, which is a simple url  </li>
  <li>I have a rough idea of how I plan to achieve this </li>
  </ol>
  
  <h1> Now to show you my code </h1>
  
  <blockquote>
    <p><strong>My Models</strong> (Just the django part no asynchronous part added as yet) </p>
  </blockquote>
  
  <pre><code>class Post(models.Model):
      user = models.ForeignKey(User, related_name='posts')
      title = models.CharField(max_length=250, unique=True)
      slug = models.SlugField(allow_unicode=True, unique=True, max_length=500)
      message = models.TextField()
      post_image = models.ImageField()
  
  class Extra (models.Model): #(Images)
      post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='post_extra')
      image = models.ImageField(upload_to='images/', blank=True, null=True, default='')
      image_title = models.CharField(max_length=100, default='')
      image_description = models.CharField(max_length=250, default='')
      sequence = models.SmallIntegerField(validators=[MaxValueValidator(12), MinValueValidator(1)])
  </code></pre>
  
  <blockquote>
    <p><strong>My views</strong> (Just the django part no asynchronous part added as yet) </p>
  </blockquote>
  
  <pre><code>@login_required
  def post_create(request):
      ImageFormSet = modelformset_factory(Extra, fields=('image', 'image_title', 'image_description'), extra=12, max_num=12,
                                          min_num=2)
      if request.method == "POST":
          form = PostForm(request.POST or None, request.FILES or None)
          formset = ImageFormSet(request.POST or None, request.FILES or None)
          if form.is_valid() and formset.is_valid():
              instance = form.save(commit=False)
              instance.user = request.user
              instance.save()
              for index, f in enumerate(formset.cleaned_data):
                  try:
                      photo = Extra(sequence=index+1, post=instance, image=f['image'],
                                   image_title=f['image_title'], image_description=f['image_description'])
                      photo.save()
                  except Exception as e:
                      break   
  
              return redirect('posts:single', username=instance.user.username, slug=instance.slug)
  </code></pre>
  
  <blockquote>
    <p>Now Just to keep things simple I will not add any Javascript in this question. Adding the below script tag to my form makes the image saved asynchronously to the server. You can read more about <a href="https://pqina.nl/filepond/docs/patterns/api/server/#configuration" rel="noreferrer">Filepond</a> if you wish </p>
  </blockquote>
  
  <pre><code>'''See the urls below to see where the **new_image** is coming from'''
      FilePond.setOptions({ server: "new_image/",
                            headers: {"X-CSRF-Token": "{% csrf_token %}"}}
      }); #I need to figure how to pass the csrf to this request Currently this is throwing error
  </code></pre>
  
  <blockquote>
    <p><strong>My plan to make it work</strong></p>
  </blockquote>
  
  <p><em>Add a new model below the existing 2 models</em></p>
  
  <pre><code>class ReducedImages(models.Model):
      image = models.ImageField()
      post = models.ForeignKey(Post, blank=True, null=True, upload_to='reduced_post_images/')
  </code></pre>
  
  <p><em>Change the view as below (only working on the main image for now. Not sure how to get the Extra images )</em></p>
  
  <pre><code>''' This could be my asynchronous code  '''
  @login_required
  def post_image_create(request, post):
      image = ReducedImages.objects.create(image=request.FILES)
      image.save()
      if post:
          post.post_image = image
  
  
  @login_required
  def post_create(request):
      ImageFormSet = modelformset_factory(Extra, fields=('image', 'image_title', 'image_description'), extra=12, max_num=12,
                                          min_num=2)
      if request.method == "POST":
          form = PostForm(request.POST or None)
          formset = ImageFormSet(request.POST or None, request.FILES or None)
          if form.is_valid() and formset.is_valid():
              instance = form.save(commit=False)
              instance.user = request.user
              post_image_create(request=request, post=instance) #This function is defined above
              instance.save()
              for index, f in enumerate(formset.cleaned_data):
                  try:
                      photo = Extra(sequence=index+1, post=instance, image=f['image'],
                                   image_title=f['image_title'], image_description=f['image_description'])
                      photo.save()
  
                  except Exception as e:
                      break
              return redirect('posts:single', username=instance.user.username, slug=instance.slug)
      else:
          form = PostForm()
          formset = ImageFormSet(queryset=Extra.objects.none())
      context = {
          'form': form,
          'formset': formset,
      }
      return render(request, 'posts/post_form.html', context)
  </code></pre>
  
  <blockquote>
    <p><strong>my urls.py</strong></p>
  </blockquote>
  
  <pre><code>url(r'^new_image/$', views.post_image_create, name='new_image'),
  </code></pre>
  
  <h2>Any suggestions on how I can make this work</h2>
  
  <blockquote>
    <p><strong>My Templates</strong></p>
  </blockquote>
  
  <pre><code>{% extends 'posts/post_base.html' %}
  {% load bootstrap3 %}
  {% load staticfiles %}
  
  {% block postcontent %}
  &lt;head&gt;
  
      &lt;meta http-equiv="X-UA-Compatible" content="ie=edge"&gt;
      &lt;link href="https://unpkg.com/filepond/dist/filepond.css" rel="stylesheet" type="text/css"/&gt;
      &lt;link href="https://unpkg.com/filepond-plugin-image-edit/dist/filepond-plugin-image-edit.css" rel="stylesheet" type="text/css"/&gt;
      &lt;link href="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css" rel="stylesheet" type="text/css"/&gt;
      &lt;link href="{% static 'doka.min.css' %}" rel="stylesheet" type="text/css"/&gt;
      &lt;style&gt;
      html {
          font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;
          font-size: 1em;
      }
  
      body {
          padding: 2em;
          max-width: 30em;
      }
      &lt;/style&gt;
  &lt;/head&gt;
  &lt;body&gt;
  &lt;div class="container"&gt;
      &lt;h2&gt; Add a new Recipe&lt;/h2&gt;
      &lt;form action="" method="post" enctype="multipart/form-data" id="form"&gt;
          {% csrf_token %}
          {% bootstrap_form form %}
          &lt;img alt="" id="preview" src="" width="100" /&gt;
          &lt;img alt="" id="new_image" src="" style="display: none;"  /&gt;
          {{formset.management_form}}
            &lt;h3 class="text-danger"&gt;You must be present in at least 1 image making the dish. With your face clearly visible and
              matching your profile picture
          &lt;/h3&gt;
          &lt;h5&gt;(Remember a picture is worth a thousand words) try to add as many extra images as possible
              &lt;span class="text-danger"&gt;&lt;b&gt;(Minimum 2)&lt;/b&gt;&lt;/span&gt;.
              People love to see how its made. Try not to add terms/language which only a few people understand.
  
           Please add your own images. The ones you took while making the dish. Do not copy images&lt;/h5&gt;
          {% for f in formset %}
              &lt;div style="border-style: inset; padding:20px; display: none;" id="form{{forloop.counter}}" &gt;
                  &lt;p class="text-warning"&gt;Extra Image {{forloop.counter}}&lt;/p&gt;
                  {% bootstrap_form f %}
  
                  &lt;img alt="" src="" width="60" id="extra_image{{forloop.counter}}"  /&gt;
              &lt;/div&gt;
          {% endfor %}
  
          &lt;br/&gt;&lt;button type="button" id="add_more" onclick="myFunction()"&gt;Add more images&lt;/button&gt;
  
          &lt;input type="submit" class="btn btn-primary" value="Post" style="float:right;"/&gt;
  
      &lt;/form&gt;
  
  &lt;/div&gt;
  &lt;script&gt;
      [
          {supported: 'Promise' in window, fill: 'https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js'},
          {supported: 'fetch' in window, fill: 'https://cdn.jsdelivr.net/npm/fetch-polyfill@0.8.2/fetch.min.js'},
          {supported: 'CustomEvent' in window &amp;&amp; 'log10' in Math &amp;&amp; 'sign' in Math &amp;&amp;  'assign' in Object &amp;&amp;  'from' in Array &amp;&amp;
                      ['find', 'findIndex', 'includes'].reduce(function(previous, prop) { return (prop in Array.prototype) ? previous : false; }, true), fill: 'doka.polyfill.min.js'}
      ].forEach(function(p) {
          if (p.supported) return;
          document.write('&lt;script src="' + p.fill + '"&gt;&lt;\/script&gt;');
      });
      &lt;/script&gt;
  
      &lt;script src="https://unpkg.com/filepond-plugin-image-edit"&gt;&lt;/script&gt;
      &lt;script src="https://unpkg.com/filepond-plugin-image-preview"&gt;&lt;/script&gt;
      &lt;script src="https://unpkg.com/filepond-plugin-image-exif-orientation"&gt;&lt;/script&gt;
      &lt;script src="https://unpkg.com/filepond-plugin-image-crop"&gt;&lt;/script&gt;
      &lt;script src="https://unpkg.com/filepond-plugin-image-resize"&gt;&lt;/script&gt;
      &lt;script src="https://unpkg.com/filepond-plugin-image-transform"&gt;&lt;/script&gt;
      &lt;script src="https://unpkg.com/filepond"&gt;&lt;/script&gt;
  
      &lt;script src="{% static 'doka.min.js' %}"&gt;&lt;/script&gt;
  
      &lt;script&gt;
  
      FilePond.registerPlugin(
          FilePondPluginImageExifOrientation,
          FilePondPluginImagePreview,
          FilePondPluginImageCrop,
          FilePondPluginImageResize,
          FilePondPluginImageTransform,
          FilePondPluginImageEdit
      );
  
  // Below is my failed attempt to tackle the csrf issue
  
  const csrftoken = $("[name=csrfmiddlewaretoken]").val();
  
  
  FilePond.setOptions({
      server: {
          url: 'http://127.0.0.1:8000',
          process: {
              url: 'new_image/',
              method: 'POST',
              withCredentials: false,
              headers: {
                  headers:{
          "X-CSRFToken": csrftoken
              },
              timeout: 7000,
              onload: null,
              onerror: null,
              ondata: null
          }
      }
  }});
  
  
  // This is the expanded version of the Javascript code that uploads the image
  
  
      FilePond.create(document.querySelector('input[type="file"]'), {
  
          // configure Doka
          imageEditEditor: Doka.create({
              cropAspectRatioOptions: [
                  {
                      label: 'Free',
                      value: null
                  }                   
              ]
          })
  
      });
  
  The below codes are exacty like the one above. I have just minimised it
  
  FilePond.create(document.querySelector('input[type="file"]'), {...});
  FilePond.create(document.querySelector('input[type="file"]'), {...});
  FilePond.create(document.querySelector('input[type="file"]'), {...});
  FilePond.create(document.querySelector('input[type="file"]'), {...});
  FilePond.create(document.querySelector('input[type="file"]'), {...});
  FilePond.create(document.querySelector('input[type="file"]'), {...});
  FilePond.create(document.querySelector('input[type="file"]'), {...});
  FilePond.create(document.querySelector('input[type="file"]'), {...});
  FilePond.create(document.querySelector('input[type="file"]'), {...});
  FilePond.create(document.querySelector('input[type="file"]'), {...});
  FilePond.create(document.querySelector('input[type="file"]'), {...});
  FilePond.create(document.querySelector('input[type="file"]'), {...});
  
  
  // ignore this part This is just to have a new form appear when the add more image button is pressed. Default is 3 images
  
  
  &lt;script&gt;
      document.getElementById("form1").style.display = "block";
      document.getElementById("form2").style.display = "block";
      document.getElementById("form3").style.display = "block";   
  
      let x = 0;
      let i = 4;
      function myFunction() {
  
            if( x &lt; 13) {
              x = i ++
            }
        document.getElementById("form"+x+"").style.display = "block";
      }
  &lt;/script&gt;
  &lt;/body&gt;
  
  
  {% endblock %}
  </code></pre>
  
  <p><strong>I have not added the forms.py as they were not relevant</strong></p>