0

I already found a solution to this problem, but I only know that it works. I don't know how or why and that's the purpose of this question.

I have a Vanilla JavaScript file that controls a dependent drop-down list of countries-regions-cities triplets.

Here is the code:

{% load i18n %}
{% load static %}

<script>
    /* This function retrieve the csrf token from the cookies */
    function getCookie(name) {
        const value = `; ${document.cookie}`;
        const parts = value.split(`; ${name}=`);
        if (parts.length === 2) return parts.pop().split(';').shift();
    }

    /* This function just sorts the list of regions or cities */
        function sortList(dataList){
        return dataList.sort(function(a, b){
            if (a[1] < b[1]) return -1;
            if (a[1] > b[1]) return 1;
            return 0;
        });
    }

    /**
    Creates a XmlReq request and send it. 
    @param function func_name - it's the function in the server that will be called asynchronously. 
    @param string token - it's the csrf token necessary to validate django forms. 
    @param data json object - it's the data to send to the server
    @param callback callback - it's the callback function to be called on success. 
    **/
    function createXmlReq(func_name, token, data, callback){
        var xhr = new XMLHttpRequest();
        xhr.addEventListener("load", callback);
        xhr.open("POST", func_name);
        xhr.setRequestHeader("X-CSRFToken", token);
        /* The following two setRequestHeaders are not mandatory, the code works without them. */
        xhr.setRequestHeader("Accept", "application/json");
        xhr.setRequestHeader("Content-Type", "application/json");
        xhr.send(JSON.stringify(data));
    }

    document.getElementById("divProvinces").style.display="none";
    document.getElementById("divCities").style.display="none";
    
    /* Fetch the regions by the country when a country is selected from the drop-down list */
    document.getElementById("id_country").onchange = function(){
        var country_name = this.value;
        var token = getCookie('csrftoken');
        /* if country changes reset city list, no need to hide id_province because it will change anyway */
        document.getElementById("id_city").innerHTML = "";
        document.getElementById("divCities").style.display="none";
        var data = {'country': country_name};
        var xhr = createXmlReq("get-province", token, data, transferCompleteProvinces);

        function transferCompleteProvinces(event){
            data = JSON.parse(event.srcElement.response);
            data.provinces = sortList(data.provinces);
            let html_data = '';
            if (data.provinces.length !== 0) {
                html_data = '<option value="-----">{% trans "-- Select State --" %}</option>';
                data.provinces.forEach(function(data){
                    html_data += `<option value="${data[0]}">${data[1]}</option>`
                });
            }else{
                html_data = '<option value="-----">{% trans "No states available for this country" %}</option>';
            }
            document.getElementById("divProvinces").style.display="block";
            document.getElementById("id_province").innerHTML=html_data
        }
    }

    /* Fetch the cities by the region when a region is selected from the drop-down list */
    document.getElementById("id_province").onchange = function(){
        var province_name = this.value;
        var token = getCookie('csrftoken');
        var data = {'province': province_name};
        var xhr = createXmlReq("get-cities", token, data, transferCompleteCities);
        
        function transferCompleteCities(event){
            data = JSON.parse(event.srcElement.response);
            data.cities = sortList(data.cities);
            let html_data = '';
            if (data.cities.length !== 0) {
                html_data = '<option value="-----">{% trans "-- Select City --" %}</option>';
                data.cities.forEach(function(data){
                    html_data += `<option value="${data[0]}">${data[1]}</option>`
                });
            }else{
                html_data = '<option value="-----">{% trans "No cities available for this region" %}</option>';
            }
            document.getElementById("divCities").style.display="block";
            document.getElementById("id_city").innerHTML=html_data;
        }
    }
</script>

This code will send a request to functions defined in views.py, I'll only show the first one because there is no need to show both to understand the question. Here's the code:

# views.py
def getProvince(request):
    # country = request.POST.get('country')
    #from django.http import HttpResponse
    #print(HttpResponse(request.POST.items()))
    country = json.load(request)['country']
    af = AddressForm()
    provinces = af.get_state_by_country(country, get_language())
    return JsonResponse({'provinces': provinces})

If I use request.POST.get('country'), I receive nothing (None if I make a print statement to country).

It will only work with

country = json.load(request)['country']

And my question is: WHY? But that's not all. If I write the same code in JQuery, then request.POST.get('country') will work!

Here is the JQuery:

{% load i18n %}
{% load static %}
<script src="{% static 'js/jquery/jquery-3.3.1.min.js' %}"></script>
    <script>
        $("#divProvinces").hide();
        $("#divCities").hide();
        $("#id_country").change(function(){
            var countryId = $(this).val();
            /* if country changes reset city list */
            /* no need to hide id_province because it will be changed anyway */
            $("#id_city").html("");
            $("#divCities").hide();

            $.ajax({
                type: "POST",
                url: "{% url 'get-province' %}",
                data: {
                    'csrfmiddlewaretoken': '{{ csrf_token }}',
                    'country': countryId
                },
                success: function (data){
                    console.log(data.provinces);
                    data.provinces = data.provinces.sort(function(a, b){
                        if (a[1] < b[1]) return -1;
                        if (a[1] > b[1]) return 1;
                        return 0;
                    });
                    let html_data = '';
                    if (data.provinces.length !== 0) {
                        html_data = '<option value="-----">{% trans "-- Select State --" %}</option>';
                        data.provinces.forEach(function(data){
                        html_data += `<option value="${data[0]}">${data[1]}</option>`
                    });
                    }else{
                        html_data = '<option value="-----">{% trans "No states available for this country" %}</option>';
                    }
                    $("#divProvinces").show();
                    $("#id_province").html(html_data);
                }
            });
        });
        $("#id_province").change(function(){
            var provinceId = $(this).val();      
            $.ajax({
                type: "POST",
                url: "{% url 'get-cities' %}",
                data: {
                    'csrfmiddlewaretoken': '{{ csrf_token }}',
                    'province': provinceId
                },
                success: function (data){
                    console.log(data.cities);
                    data.cities = data.cities.sort(function(a, b){
                        if (a[1] < b[1]) return -1;
                        if (a[1] > b[1]) return 1;
                        return 0;
                    });
                    let html_data = '';
                    if (data.cities.length !== 0) {
                        html_data = '<option value="-----">{% trans "-- Select City --" %}</option>';
                        data.cities.forEach(function(data){
                        html_data += `<option value="${data[0]}">${data[1]}</option>`
                        });
                    }else{
                        html_data = '<option value="-----">{% trans "No city available for this region" %}</option>';
                    }
                    $("#divCities").show();
                    $("#id_city").html(html_data);
                }
            });
        });
    </script>

So the real question is: why request.POST.get('country') works with JQuery (despite I'm sending a stringified JSon object), but it doesn't with Vanilla JavaScript? Why does the View only accept json.load(request)['country'], and even if I send a string directly it doesn't work the other way?

Sure I could just go with the fact that the code works this way and just be happy with it. But I'm not satisfied. I would like to know why the same thing does work in JQuery but not in Vanilla JavaScript and why I have to use another mechanism in normal JS in order to make it work.

Danilo
  • 43
  • 4
  • 1
    Does this answer your question? [Django POST data in request.body but can not store in variable](https://stackoverflow.com/questions/43178061/django-post-data-in-request-body-but-can-not-store-in-variable) – Abdul Aziz Barkat Sep 30 '21 at 03:45
  • `request.POST` is only filled with _form data_. You can pass form data with XHR too but you are instead JSON encoding your data and sending it in the request body. See [Using FormData Objects - MDN Web Docs](https://developer.mozilla.org/en-US/docs/Web/API/FormData/Using_FormData_Objects) – Abdul Aziz Barkat Sep 30 '21 at 03:47
  • Yes, yes, I understand that request.POST only receives data from forms and not from JSON encoded data but... does the JQuery version use a form? The real question is, why the JQuery version works if it doesn't use a form neither (as far as I know)? – Danilo Sep 30 '21 at 05:05
  • An html form and form data are not the same thing. When you pass data as an object JQuery is automatically sending it as form data since its default content type is `application/x-www-form-urlencoded`. Again you can also send form data with XHR as I said above. – Abdul Aziz Barkat Sep 30 '21 at 05:18

0 Answers0