How to upload image in django without using django forms

Question:

Model

class Property(models.Model):
    property_id = models.BigAutoField(primary_key=True)
    author = models.ForeignKey(User, on_delete=models.CASCADE)
    status = models.CharField(max_length=30, default='Public')
    title = models.CharField(max_length=30, default='')
    image1 = models.ImageField(upload_to="properties/images", default='', null=True)
    image2 = models.ImageField(upload_to="properties/images", default='', null=True)

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

        if self.image1:
           img1 = Image.open(self.image1.path)
           if img1.height > 1500 or img1.width > 1500:
              output_size = (1500, 1500)
              img1.thumbnail(output_size)
              img1.save(self.image1.path)
    
       if self.image2:
           img2 = Image.open(self.image2.path)
           if img2.height > 1500 or img2.width > 1500:
              output_size = (1500, 1500)
              img2.thumbnail(output_size)
              img2.save(self.image2.path)

In this model i to uoload Images in image1 and image2. I used PIL to create thumbnail

View

def post(self, request):
    status = request.POST.get('status')
    title = request.POST.get('title')
    image1 = request.FILES.get('image-1')
    image2 = request.FILES.get('image-2')

    if Property.objects.all():
        pid = Property.objects.all().last().property_id + 1
    else:
        pid = 0

    Property(property_id = pid, author = request.user, status = status, title = title).save()

    p = Property.objects.all().filter(property_id = pid)[0]

    if image1:
        p.image1 = image1
    
    if image2:
        p.image2 = image2

    p.save()

    return redirect('add_item')

HTML

<form method="POST" class="row gx-3 gy-4">
    {% csrf_token %}
    <!-- Status -->
    <label for="inputStatus">Status</label>
    <select name="status" id="inputStatus" required>
        <option disabled selected>Choose an option</option>
        {% for status in all_status %}
            <option value="Public">{{status}}</option>
         {% endfor %}
    </select>
    <!-- Title -->
    <label for="inputTitle">Title</label>
    <input name="title" type="text" required>
    <!-- Images -->
    <label for="inputTitle">Images</label>
    <input name="image-1" type="file">
    <input name="image-2" type="file">
</form>

With this is code i can add Product_id, author, status but image1 and image2 is not uploading to properties/images when i open to this model in django-admin then its look like a blank image field

Asked By: Abhay Pratap Rana

||

Answers:

If you submit files you need to specify how to encode these with the enctype="multipart/form-data" attribute [mozilla-dev]:

<form method="POST" class="row gx-3 gy-4" enctype="multipart/form-data">
    {% csrf_token %}
    <!-- … -->
</form>
Answered By: Willem Van Onsem

Saving Files in general with Django without using Django form can seem to be quite twisted.
However, here is a little walk through of how the process should look like:

You first have your HTML form:

<form method="POST" class="row gx-3 gy-4">
    {% csrf_token %}
    <!-- Status -->
    <label for="inputStatus">Status</label>
    <select name="status" id="inputStatus" required>
        <option disabled selected>Choose an option</option>
        {% for status in all_status %}
            <option value="Public">{{status}}</option>
        {% endfor %}
    </select>
    <!-- Title -->
    <label for="inputTitle">Title</label>
    <input name="title" id="idTitle" type="text" required>
    <!-- Images -->
    <label for="inputTitle">Images</label>
    <input name="image-1" id="idImage1" type="file">
    <input name="image-2" id="idImage2" type="file">
    <input type="submit" id="submit_btn" class="btn btn-success center_btn" value="Save Data">
</form>

After specifying your HTML Structure, time to dive into Javascript:

But Wait Hold on…There are 2 ways to send the data:

  1. Using FileReader object precisely its readAsArrayBuffer method
  2. Using just formData object (Which we’ll use below)

NOTE: It is preferable to use the formData object of the Form element to send
the data to the server, because it already handles everything concerning files for you

// selecting the input elements in JS 
const idImage1 = document.getElementById("idImage1")
const idImage2 = document.getElementById("idImage2")
const idTitle = document.getElementById("idTitle")
const submit_btn = document.getElementById("submit_btn")
// Selecting the csrf token from a django form
const csrftoken_elmt = document.querySelector('[name=csrfmiddlewaretoken]'); 

// Preparing the function that will send the data
const sendData = async (data, link) => {
    try {
        // ${baseURL}registration/enregistrement_pro/
        let response = await fetch(`${link}`, 
            {
                method: 'POST', 
                body: data, 
                headers: {
                    // 'Content-Type': 'multipart/form-data', 
                    'X-CSRFToken': csrftoken_elmt.value,
                }, 
            }
        );
        return await response.json();
    } catch(err) {
        console.error(err);
    }
}

// Building the submit button code:
submit_btn.addEventListener("click", (evt) => {
    evt.preventDefault();
    
    const formData = new FormData()

    formData.append("title", idTitle.value);
    formData.append("Image1", idImage1.files[0]);
    formData.append("Image2", idImage2.files[0]);

    // Using the data-sending method declared above to send the data to the server
    sendData(formData, 'Your Link to you view.py')
    .then (json => {
        // Place codes After the response from the server here
        console.log("The Json: ", json)
    })
})

Moving on, we handle the request at the server side now, with django:

def post(self, request):
    title = request.POST.get('title')
    image1 = request.FILES.get('Image1')
    image2 = request.FILES.get('Image2')

    if Property.objects.all():
        pid = Property.objects.all().last().property_id + 1
    else:
        pid = 0

    Property(property_id = pid, author = request.user, status = status, title = title).save()

    p = Property.objects.all().filter(property_id = pid)[0]

    if image1:
        p.image1 = image1

    if image2:
        p.image2 = image2

    p.save()

    return redirect('add_item')

This way you ensure that the file will actually be really uploaded.

There are many other articles that can help you have further understanding about how to make this process better:

How do i receive image file in django view from fetch post without using django forms

Sending data to server using javascript file streaming object without using formData

Further reading from this Django doc article: File Uploads

Answered By: vially