Issues with saving POST data to database using JavaScript Fetch API

Hello!

I am trying to save POST data to the database using JavaScript Fetch API but encounter issues with saving the data to my model instance.

Could someone please take a look at my codes and guide/explain what I am doing wrong?

The POST data is sent to the view and I can see a dictionary in the console containing the POST data. However, the POST data is not saved. In the browser console, I can see that the fields are empty, and therefore rejected by the database.

Currently I am trying to validate the form at the database level, not client side.

Appreciate all help!

View:

@login_required(redirect_field_name="authapp:signin")
def customerinfo(request, user_id, first_name):
    if request.headers.get("X-Requested-With") == "XMLHttpRequest":
        try:
            post_data = json.loads(request.body)
            print(post_data)
        except json.JSONDecodeError:
            return JsonResponse({"error": "Invalid JSON data"}, status=400)

        if request.method == "POST":
            form_data = CustomerInfoUpdateForm(post_data)
            if form_data.is_valid():
                print("Form is valid")
                user = request.user
                user.first_name = post_data["firstName"]
                user.last_name = post_data["lastName"]
                user.mobile_number = post_data["phoneNumber"]
                user.email = post_data["emailAddress"]
                user.save()
        else:
            form_data = CustomerInfoUpdateForm()

        return JsonResponse({"form_data": form_data}, safe=True)

    context = {}
    return render(request, "jewstore_main_app/customerinfo.html", context)

Template code:

<div class="d-flex flex-column">
                            <form action="#" method = "POST" id="customerInfoForm">
                                {% csrf_token %}
                                <fieldset>
                                    <div class="kaycee-MyAccount-content">
                                        <div class="kaycee-notices-wrapper"></div>
                                            <div class="p-3 d-flex flex-column">
                                                <p class="kaycee-form-row kaycee-form-row--wide form-row form-row-wide">
                                                    <label for="first_name">Förnamn&nbsp;<span
                                                            class="required">*</span></label>
                                                    <input type="text" class="kaycee-Input kaycee-Input--text input-text"
                                                        name="first_name" id="account_first_name" autocomplete="given-name"
                                                        value="">
                                                </p>
                                                <p class="kaycee-form-row kaycee-form-row--wide form-row form-row-widet">
                                                    <label for="last_name">Efternamn&nbsp;<span
                                                            class="required">*</span></label>
                                                    <input type="text" class="kaycee-Input kaycee-Input--text input-text"
                                                        name="last_name" id="account_last_name" autocomplete="family-name"
                                                        value="">
                                                </p>
                                                <div class="clear"></div>
                                                <p class="kaycee-form-row kaycee-form-row--wide form-row form-row-wide">
                                                    <label for="phone">Telefonnummer&nbsp;<span
                                                            class="required">*</span></label>
                                                    <input type="tel" class="kaycee-Input kaycee-Input--email input-text"
                                                        name="phone" id="account_phone" autocomplete="email"
                                                        value="">
                                                </p>
                                                <p class="kaycee-form-row kaycee-form-row--wide form-row form-row-wide">
                                                    <label for="email">Mail&nbsp;<span
                                                            class="required">*</span></label>
                                                    <input type="email" class="kaycee-Input kaycee-Input--email input-text"
                                                        name="email" id="account_email" autocomplete="email"
                                                        value="">
                                                </p>
                                            </div>

                                            <div class="clear"></div>
                                            <p>
                                                <button type="submit" class="kaycee-Button button" name="save_account_details"
                                                value="" id="customerInfoUpdateButton">Uppdatera
                                                </button>
                                            </p>
                                        </fieldset>
                            </form>
                        </div>

JavaScript code:

document.addEventListener("DOMContentLoaded", () => {
    const customerInfoForm = document.getElementById("customerInfoForm");

    // EVENTLISTENERS
    customerInfoForm.addEventListener('submit', validateCustomerInfo);

    // FUNCTIONS

    // Form submit function
    function getToken(name) {
        let cookieValue = null;
        if (document.cookie && document.cookie != '') {
            const cookies = document.cookie.split(';');
            for (let i = 0; i < cookies.length; i++) {
                const cookie = cookies[i].trim();
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) === (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }
    var csrftoken = getToken('csrftoken')

    function validateCustomerInfo(e) {
        e.preventDefault();

        // Constants related to customer info page
        let firstName = document.getElementById("account_first_name").value;
        let lastName = document.getElementById("account_last_name").value;
        let phoneNumber = document.getElementById("account_phone").value;
        let emailAddress = document.getElementById("account_email").value;

        fetch("", {
            method: 'POST',
            headers: {
                "Accept": "application/json",
                "X-Requested-With": "XMLHttpRequest",
                "X-CSRFToken": csrftoken
            },
            body: JSON.stringify({ "firstName": firstName, "lastName": lastName, "phoneNumber": phoneNumber, "emailAddress": emailAddress }),
        })
            .then(response => {
                if (!response.ok) {
                    return console.log("Invalid response")
                }
                else {
                    console.log("Valid response")
                    return response.json();
                }
            })
            .then(data => {
                if (data) {
                    console.log(data.error)
                }

            })
            .catch(error => {
                console.log(error)
            });
    }
})

The form:

class CustomerInfoUpdateForm(forms.ModelForm):
    class Meta:
        model = CustomUser
        fields = ["first_name", "last_name", "mobile_number", "email"]

        widgets = {
            "first_name": TextInput(attrs={"class": "form-control", "id": "account_first_name"}),
            "last_name": TextInput(attrs={"class": "form-control", "id": "account_last_name"}),
            "mobile_number": NumberInput(attrs={"class": "form-control", "id": "account_phone"}),
            "email": EmailInput(attrs={"class": "form-control", "id": "account_email"}),
        }

And here is the response from the console:

console response

You haven’t posted the code for CustomerInfoUpdateForm, so this is a bit of a guess.

Notice the error messages being returned. It’s looking for fields named email, first_name, and last_name. However, the JSON dict that you are creating in your Javascript has keys firstName, lastName, and emailAddress`. These apparently don’t match what your form has defined.

1 Like

Hello!

I’ve now added the form.

The field names are the same in the model and the form.

I have different field names in the JS code when I am targeting the input fields, but that shouldn’t matter since I only need the dictionary values in the form validation.

Here is the dictionary body:

{‘firstName’: ‘Abbas’, ‘lastName’: ‘XXXX’, ‘phoneNumber’: ‘+46739647388’, ‘emailAddress’: ‘abbas@outlook.com’}.

Any ideas?

That’s the problem. That’s what’s not matching the form, and so the form fields are not being populated.

Since the form fields aren’t populated, the is_valid test fails.

1 Like

I don’t understand. How is that a problem?

My understanding is that the is_valid() method checks whether the posted data, using the form, complies with the way the models are defined. For example, if the first_name model field is not consisting of characters, the form is not valid.

In my case, I make sure to post the right data type in the form.

class CustomUser(AbstractBaseUser, PermissionsMixin):
    first_name = models.CharField(_("Förnamn"), max_length=150, blank=False)
    last_name = models.CharField(_("Efternamn"), max_length=150, blank=False)
    email = models.EmailField(_("Email"), unique=True)  # Identifier
    mobile_number = PhoneNumberField(null=True, blank=True)

Also, for example the
user.first_name = post_data["firstName"]

should equal to

user.first_name = "Abbas"

Why is then the post data not saved?

When is this statement being executed:

relative to:

???

You’re trying to do this:

but post_data can not populate form_data, because the field names don’t match.

Review the docs at Working with forms | Django documentation | Django to refresh your understanding of how forms work.

1 Like

Thank you!

I understand my mistake now. And the code works :smiley:

In the JS code, I change the value for the body attribute to:

  body: JSON.stringify({ "first_name": firstName, "last_name": lastName, "mobile_number": phoneNumber, "email": emailAddress }),

And therefore also change the view to:

@login_required(redirect_field_name="authapp:signin")
def customerinfo(request, user_id, first_name):
    if request.headers.get("X-Requested-With") == "XMLHttpRequest":
        try:
            post_data = json.loads(request.body)
            print(f"The posted data is: \n {post_data}")
        except json.JSONDecodeError:
            return JsonResponse({"error": "Invalid JSON data"}, status=400)

        if request.method == "POST":
            form_data = CustomerInfoUpdateForm(post_data)
            if form_data.is_valid():
                print("Form is valid")
                user = CustomUser.objects.get(pk=request.user.id)
                user.first_name = post_data["first_name"]
                user.last_name = post_data["last_name"]
                user.mobile_number = post_data["mobile_number"]
                user.email = post_data["email"]
                user.save()
        else:
            form_data = CustomerInfoUpdateForm()

        return JsonResponse({"error": "Form is invalid", "error": form_data.errors}, safe=False)

    updated_data = {
        "first_name": request.user.first_name,
        "last_name": request.user.last_name,
        "mobile_number": request.user.mobile_number,
        "email": request.user.email,
    }

    context = {"updated_data": updated_data}
    return render(request, "jewstore_main_app/customerinfo.html", context)

There’s a lot of this that you don’t need to do.

You’ve created a model form, which means it can work with the instance of the model being updated directly.

Instead of this:

It can be simplified to this:

        if request.method == "POST":
            form = CustomerInfoUpdateForm(post_data, instance=request.user)
            if form.is_valid():
                print("Form is valid")
                form.save()

Review the docs at Creating forms from models | Django documentation | Django

But I want to update the information for the authenticated user, which means that there already exist an CustomUser instance for the authenticated user that I want to update. I tried using only form_data.save() and it created a new instance in the database, which to me seem unnecessary.

I am wrong?

You also need to create the instance of the form for the GET using the same instance parameter.

Omg, of course, the instance attribute. I totally forgot it.

Her is my view again:

@login_required(redirect_field_name="authapp:signin")
def customerinfo(request, user_id, first_name):
    if request.headers.get("X-Requested-With") == "XMLHttpRequest":
        try:
            post_data = json.loads(request.body)
        except json.JSONDecodeError:
            return JsonResponse({"error": "Invalid JSON data"}, status=400)

        if request.method == "POST":
            form_data = CustomerInfoUpdateForm(post_data, instance=request.user)
            if form_data.is_valid():
                form_data.save()
        else:
            form_data = CustomerInfoUpdateForm()

        return JsonResponse({"error": "Form is invalid", "error": form_data.errors}, safe=False)

    updated_data = {
        "first_name": request.user.first_name,
        "last_name": request.user.last_name,
        "mobile_number": request.user.mobile_number,
        "email": request.user.email,
    }

    context = {"updated_data": updated_data}
    return render(request, "jewstore_main_app/customerinfo.html", context)

Thank you soo much!