The dynamically rendered inputs are not being pulled through to the post request?

Code is used to render the input fields based on the different expense types to the edit form

// Clear previous fields
                    dynamicFieldsContainer.innerHTML = '';

                    // Inject fields dynamically
                    for (const [key, value] of Object.entries(data.fields)) {
                        // Skip fields that are null or undefined
                        if (value === null || value === undefined) continue;

                        const field = `
                            <div class="mb-3">
                                <label for="${key}" class="form-label">${key.replace('_', ' ').toUpperCase()}</label>
                                <input type="text" class="form-control" id="${key}" name="${key}" value="${value}" required>
                            </div>`;
                        dynamicFieldsContainer.insertAdjacentHTML('beforeend', field);
                    }

These are the hidden fields that are already present on the form and are populated by the JS script these, are the only fields present in the POST request

                // Set hidden ID field
                document.getElementById('expense-id').value = data.id;
                // Set hidden expense-type field
                document.getElementById('expense-type').value = data.type;

The current html structure which shows the form and the post action as well as the hidden and dynamically render html tags

 <form id="editExpenseForm" name="editExpenseForm" method="post" action="{% url 'edit_expense' %}">
                                        {% csrf_token %}
                                        <div class="modal-header">
                                            <h5 class="modal-title" id="editExpenseModalLabel">Edit Expense</h5>
                                            <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                                        </div>
                                        <div class="modal-body">
                                            <input type="hidden" name="expense-id" id="expense-id">
                                            <input type="hidden" name="expense-type" id="expense-type">
                                            <div id="dynamic-fields">
                                                <!-- Fields will be injected dynamically -->
                                            </div>
                                        </div>
                                        <div class="modal-footer">
                                            <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
                                            <button type="submit" class="btn btn-primary">Save Changes</button>
                                        </div>
                                    </form>

Your javascript code just needed a little tweaking. It should look something like this:

    // your previous code
    const formData = new FormData(form);
    const searchParams = new URLSearchParams();
    for (let [key, value] of formData.entries()) {
      searchParams.append(key.toString(), value.toString());
    }
    
    fetch(form.action, {
      method: 'POST',
      body: searchParams.toString(),
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
    })
    // your other code

I just checked with myself, it should work, request.POST should now contain the form data. Check it out and let me know if it works for you?

p.s. I did not use your update_expense controller code.

UPDATED

I'm adding what I tested for just in case:

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
<form action="{% url 'foo_bar' %}" method="POST" id="my-form">
  {% csrf_token %}
  <div class="modal-body">
    <input type="hidden" name="expense-id" id="expense-id">
    <input type="hidden" name="expense-type" id="expense-type">
    <div id="dynamic-fields"></div>
    <button type="submit">CLICK ME</button>
  </div>
</form>

<script>
  const element = document.querySelector('#dynamic-fields');
  for (const [key, value] of Object.entries({ field1: 1 })) {
    // Skip fields that are null or undefined
    if (value === null || value === undefined) continue;

    const field = `
      <div class="mb-3">
        <label for="${key}" class="form-label">${key.replace('_', ' ').toUpperCase()}</label>
        <input type="text" class="form-control" id="${key}" name="${key}" value="${value}" required>
      </div>`;
    element.insertAdjacentHTML('beforeend', field);
  }

  const form = document.querySelector('#my-form');
  form.addEventListener('submit', function (event) {
    event.preventDefault();
    const formData = new FormData(form);
    const searchParams = new URLSearchParams();
    for (let [key, value] of formData.entries()) {
      searchParams.append(key, value.toString());
    }

    fetch(form.action, {
      method: 'POST',
      body: searchParams.toString(),
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
    })
  })
</script>
</body>
</html>
#  views

def spam(request):
    return render(request=request, template_name='test.html')

Console output of request.body and request.POST: console_output

// Solution to the answer.

document.addEventListener('DOMContentLoaded', function () {
    const editModal = document.getElementById('editExpenseModal');
    const dynamicFieldsContainer = document.getElementById('dynamic-fields');
    const form = document.getElementById('editExpenseForm');
    const confirmationModal = new bootstrap.Modal(document.getElementById('confirmationModal'));
    const cancelButton = document.getElementById('cancelEdit'); // Ensure this ID exists in your cancel button

    document.querySelectorAll('.edit-expense-btn').forEach(button => {
        button.addEventListener('click', () => {
            const url = button.getAttribute('data-url');

            fetch(url)
                .then(response => {
                    if (!response.ok) {
                        throw new Error(`HTTP error! Status: ${response.status}`);
                    }
                    return response.json();
                })
                .then(data => {
                    if (data.error) {
                        alert('Error fetching expense details: ' + data.error);
                        return;
                    }

                    // Clear previous fields inside the form
                    dynamicFieldsContainer.innerHTML = '';

                    // Inject fields dynamically inside the form
                    for (const [key, value] of Object.entries(data.fields)) {
                        if (value === null || value === undefined) continue;

                        const field = document.createElement('div');
                        field.classList.add('mb-3');
                        field.innerHTML = `
                            <label for="${key}" class="form-label">${key.replace('_', ' ').toUpperCase()}</label>
                            <input type="text" class="form-control dynamic-input" id="${key}" name="${key}" value="${value}" required>
                        `;
                        dynamicFieldsContainer.appendChild(field);
                    }

                    // Ensure hidden input fields retain their values
                    document.getElementById('expense-id').value = data.id;
                    document.getElementById('expense-type').value = data.type;

                    // Open modal properly
                    editModal.setAttribute('aria-hidden', 'false');
                    const modalInstance = new bootstrap.Modal(editModal);
                    modalInstance.show();
                })
                .catch(error => console.error('Fetch Error:', error));
        });
    });

    // Handle form submission
    form.addEventListener('submit', function (event) {
        event.preventDefault();

        // Ensure dynamically generated inputs are captured
        document.querySelectorAll('.dynamic-input').forEach(input => {
            if (!form.contains(input)) {
                form.appendChild(input);
            }
        });

        const formData = new FormData(form);

        // Get the CSRF token from the form
        const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;

        fetch(form.action, {
            method: 'POST',
            body: formData,
            headers: {
                'X-CSRFToken': csrfToken,
            }
        })
        .then(response => {
            const contentType = response.headers.get("content-type");
            if (contentType && contentType.includes("application/json")) {
                return response.json();
            } else {
                return response.text();
            }
        })
        .then(data => {
            console.log(data)
            if (typeof data === "object" && data.success) {
                // Hide edit modal properly
                const editModalInstance = bootstrap.Modal.getInstance(editModal);
                if (editModalInstance) {
                    editModalInstance.hide();
                }

                // Ensure modal cleanup
                cleanupModals();
        
                // Show confirmation modal
                confirmationModal.show();


                // Auto-hide confirmation modal after 1.5s
                setTimeout(() => {
                    confirmationModal.hide();
                    window.location.reload();
                    cleanupModals();
                }, 1500);

            } else {
                console.log("Non-JSON response received:", data);
            }
        })
        .catch(error => console.warn('⚠️ Fetch Warning:', error));
    });

    // Handle Cancel button click
    cancelButton.addEventListener('click', function () {
        const editModalInstance = bootstrap.Modal.getInstance(editModal);
        if (editModalInstance) {
            editModalInstance.hide();
        }
        cleanupModals();
    });

    // Ensure modal cleanup when closed
    editModal.addEventListener('hidden.bs.modal', cleanupModals);

    // Function to remove lingering backdrops and reset page state
    function cleanupModals() {
        document.body.classList.remove('modal-open');
        document.querySelectorAll('.modal-backdrop').forEach(backdrop => backdrop.remove());
        editModal.setAttribute('aria-hidden', 'true');
    }
});
Back to Top