Using the Django REST framework to serialize nested data and send nested JSON objects along with files via

What is the best method for sending a POST request with nested data, including files such as images, to django REST utilizing nested serializers?

If we have the following JavaScript object:

bookData: {
    title: 'Anne of Green Gables',
    coverImage: File(123456),
    pages: 123,
    author: {
        name: 'Lucy Maud Montgomery',
        born: 1874,
        profilepicture_set: [
            {file: File(234567), description: 'Young L. M. Montgomery'},
            {file: File(234568), description: 'Old L. M. Montgomery}
        ],
        quote_set: [
            {text: "I'm so glad I live in a world where there are Octobers."},
            {text: "True friends are always together in spirit."},
        ]
    },

}

The goal is to send this data via a POST request to our django REST API (note that VueJS is used on the front end).

# views.py
class CreateBookView(generics.CreateAPIView):
    serializer_class = CreateBookSerializer
    queryset = Book.objects.all()

# serializers.py
class CreateBookSerializer(serializers.ModelSerializer):
    author = CreateAuthorSerializer()

    class Meta:
        model = Book
        fields = ('title', 'pages', 'author')

    @transaction.atomic
    def create(self, validated_data):
        author_data = validated_data.pop('author')
        uploader = self.context['request'].user
        book = Book.objects.create(uploader=uploader, **validated_data)

        Author.objects.create(book=book, **author_data)

        return book


# models.py
class AuthorManager(models.Manager):

    def create(self, **author_data):
        quotes_data = author_data.pop('quote_set')
        photos_data = author_data.pop('profilepicture_set')

        author = Author(**author_data)
        author.save()

        for quote_data in quotes_data:
            Quote.objects.create(author=author, **quote_data)

        for photo_data in photos_data :
            ProfilePicture.objects.create(author=author, **photo_data)

        return author

## Omitting details on CreateAuthorSerializer, Quote and ProfilePicture Managers as they mirror the above logic.
## Assume each Author corresponds to only one Book (OneToOneField). 

<hr>

<p><strong>UPDATE</strong> </p>

<p>We can provide insight into how the data is sent from the front end using VueJS.</p>

<pre><code>sendData(){
    var fd = new FormData()
    var bookData = {
        title: this.$store.getters.title, # 'Anne of Green Gables"
        coverImage: this.$store.getters.coverImage, # File(somesize)
        pages: this.$store.getters.pages, # 123
        author: this.$store.getters.author, # nested object 
        ...
    }
    fd = objectToFormData(bookData, fd) # external function, see below
    this.$http.post('api/endpoint/create/', fd, headers: {
        'Content-Type': 'multipart/form-data',
        'X-CSRFToken': Cookies.get('csrftoken'),
        }
    }).then(...)
}

The sendData() function is executed upon button click. The objectToFormData refers to an external library obtained through npm, which flattens the nested object structure into a form format.

However, the issue arises when examining the structure of request.data, particularly its difference from the original bookData structure. This discrepancy causes problems with the REST serializer, most notably due to the absence of the author field (since the object has been flattened).

<QueryDict: {
'title': ['Anne of Green Gables'], 
'coverImage':[InMemoryUploadedFile: cover.jpg (image/jpeg)],
'pages': ['123'], 
'name': ['Lucy Maud Montgomery'], 
'born': ['1874'],
'profilepicture_set[][description]': ['Young L. M. Montgomery', 'Old L. M. Montgomery'], 
'profilepicture_set[][file]': [
    InMemoryUploadedFile: young.jpg (image/jpeg), 
    InMemoryUploadedFile: old.jpg (image/jpeg)
    ],
'quote_set[][text]': [
    "I'm so glad I live in a world where there are Octobers.",
    "True friends are always together in spirit."
    ]
}>

This leads us to explore potential solutions, such as overriding the create() function.

class CreateBookView(generics.CreateAPIView):
    (...)
    def create(self, request, *args, **kwargs):
        book_data = {}
        book_data['title'] = request.data['title']
        book_data['pages'] = request.data['pages']
        book_data['cover_image'] = request.data['coverImage']
        book_data['author'] = {}
        book_data['author']['name'] = request.data['name']
        book_data['author']['born'] = request.data['born']
        (...)
        serializer = self.get_serializer(data=book_data)
        (...)

While the above approach is functional, it may not be efficient for complex models with extensive fields and multiple layers of nesting, as shown in the simplified book/author example.

In light of this challenge, what would be the recommended course of action? Should the methodology of POSTing be altered to achieve a more favorable request.data format or is there a practical solution to address the current formatting complexities?

Answer №1

As per the information provided by DRF in their documentation,

It is mentioned that most parsers, such as JSON, do not support file uploads. Django's regular FILE_UPLOAD_HANDLERS are utilized for managing uploaded files.

An alternative method suggested is to utilize the Javascript FormData API for sending multipart data (particularly effective with VueJS/AngularJS)

var fd = new FormData();
fd.append('title', 'Anne of Green Gables')
fd.append('author_name', 'Lucy Maud Montgomery')
fd.append('author_profilepicture_set', files[])
$http.post('url', fd, {
   headers: {
       'Content-Type': 'multipart/form-data'
   }
});

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Creating divs that adapt to different screen sizes without relying on flexbox

Currently, I am in the process of developing a chat interface where the viewport height is set to 100vh. The layout consists of a header/navbar, a "main content" section, and a footer housing the input field (you can view the code here) The way I have str ...

Creating duplicates of a div element with each click

I am looking for a way to create a button (ADD) that, when clicked, generates a form with a field and a delete button. The form fields and the delete button should be erased when the button is clicked. <button type="button" class="btn btn ...

Pause and await the completion of an asynchronous function within a Vue composable before accessing its data

I've written various composables that fetch specific objects from an API and are used in a component. My challenge is to link objects retrieved by one composable with those from another, all while keeping the fetching process async to prevent any dela ...

What is the best way to display sub-categories within a category using data collapse feature in Django?

Help needed to display subcategories within categories in a collapsible format using Django. Being a novice developer, I'm having trouble understanding the required logic. The models for Category and Sub-Category are defined as follows: category.py ...

What issues can be found in this code snippet for AngularJS in the browser code editor that is interacting with Node.js

Greetings, I am currently working on developing a free in-browser code editor for high school students at the basic level to share knowledge. After extensive research, I came across this link. Following the instructions provided, I made some setups and mod ...

Displaying images in a React app using Node.js

I receive the image from the user as formdata Here is how I structured the Schema for the image The Image has been successfully stored in Monogodb This is my process of fetching image information using Axios I implement code for rendering the image on the ...

The property express.json() is not recognized

Why doesn't Typescript recognize the express.json() function, even though many tutorials claim it should compile without errors? Could I have overlooked something in my code? An example tutorial that suggests this code works: https://auth0.com/blog/n ...

The absence of a flickering flame is noticeable in the THREE.js environment

I have been working on creating a flame using THREE.js and spark.js. However, even after rendering the world, I am unable to see the flame and it seems like the world is empty. Although I checked the console for errors, there are no indications of what mig ...

The Datejs library is experiencing issues following an upgrade to jQuery 3.x

I'm currently working on a node.js project that utilizes the Datejs library. However, after updating our local jQuery file from version 1.9.1 to 3.6.0, this library has stopped functioning properly. Here is a snippet of the code: var today = Date ...

Mongoose: utilize populate for fetching records; if not, return an array of records

Learning MeanJS and encountering Mongoose issues. Two models are in question: CategorySchema: - name: String, default:'', required: 'Please fill Category name', trim: true - slug: String, default:'', trim: true, unique: tr ...

Exploring the world of Node.js with fs, path, and the

I am facing an issue with the readdirSync function in my application. I need to access a specific folder located at the root of my app. development --allure ----allure-result --myapproot ----myapp.js The folder I want to read is allure-results, and to d ...

Verifying the status following a promise execution in the initial promise function

Below is the function I am currently working with: startGame = () => { this.buildDeck() .then(this.shuffleDeck) .then(this.dealToPlayer) .then(setTimeout(this.dealToPlayer, 2000)) .then(setTimeout(this.dealToDealer, 4000)) } In ...

Develop a custom JavaScript code block in Selenium WebDriver using Java

Recently, I came across a JavaScript code snippet that I executed in the Chrome console to calculate the sum of values in a specific column of a web table: var iRow = document.getElementById("DataTable").rows.length var sum = 0 var column = 5 for (i=1; i& ...

Retrieve data from fields connected to a specific model through a foreign key relationship

class SenderInfo(models.Model): #to create unique id numbers sender = models.CharField(max_length=15) id_number = models.IntegerField(blank=True, null=True) class Messages(models.Model): message_sender = models.ForeignKey(SenderInfo, relat ...

Which is the better option for selecting DOM elements in a Vuejs 3 application: using raw js or jquery?

 I am currently working on developing an application using Node.js and Vue.js 3. One of the features I have implemented is a sidebar that dynamically fetches links from a routes file and displays them. The sidebar consists of a component that organize ...

Exploring Vue router and fetching data from the store

Can you assist me with a Vue app issue? I am utilizing Vuex store and vue-router. The challenge I am facing is that when I fetch data from API calls, the information is stored in the store. However, if I navigate to the blabla/edit-profile page from anot ...

Creating a React component in Typescript to utilize lodash helper functions

I have a component that looks like this: import React, { Component } from 'react'; import throttle from 'lodash.throttle'; interface Props { withScroll: boolean; } class Image extends Component<Props, {}> { throttledWindowS ...

Struggling to display my JSON file in an HTML element with AJAX

Having some trouble here. I can successfully print my JSON file content to the console using console.log, but now I'm trying to display it on the page. I want to display the 'Information' section from the JSON file using innerHTML like this ...

Request to api.upcitemdb.com endpoint encountering CORS issue

This code may seem simple, but for some reason, it's not working as expected. What I'm trying to achieve is to call the GET API at: I want to make this API call using either JavaScript or jQuery. I've attempted various approaches, but none ...

The JavaScript eval function is unable to carry out operations on two numbers

alert('The outcome of evaluating ${num1} ${operator} ${num2} is ${eval(`${num1} ${operator} ${num2}`)}'); Although I am using the eval() function to perform operations on two numbers (where num1 and num2 are numerical values and operator can be ...