Jquery call to URI/function not working

Cascading drop down list question. I have two select controls with the first one filtering the second (select a Clinic, then a Patient). I’m trying to do this with a Jquery ajax call:

    <script>
        
        $(document).ready(function(){
            $('select#ddlCriteriaClinic').change(function () {
             let optionSelected = $(this).find("option:selected");
             let valueSelected  = optionSelected.val();
             let clinic_name    = optionSelected.text();

             let _data = {'BOOKED_DATE' : valueSelected };
             $.ajax({
                url: 'CGUDefault/GetPatientsForClinic',
                data: _data,
                dataType: 'json',
                success: function (data) {
                    console.log(data);
                    alert(data);
             //    $("#ddlPatient option").remove();
             //    for (var i = data.length - 1; i >= 0; i--) {
             //        $("#ddlPatient").append('<option>' + data[i].FIRSTNAME + ' ' + data[i].LASTNAME + '</option>');
             //    };
                }
              });
            });        //ddlCriteriaClinic.change
        });             //function
    </script>

with the uri mapping set to:

urlpatterns = [
    path('', login_views.CGULogin, name='CGULogin'),
    path('CGUDefault/', default_view.CGUDefault.as_view(), name='CGUDefault'),
    path('CGUDefault/GetPatientsForClinic', default_view.CGUDefault.GetPatientsForClinic, name='GetPatientsForClinic'),
]

I’ve been broadly following this blog/tutorial.

CGUDefault.GetPatientsForClinic just doesn’t get called. Any ideas?

Paul

If default_view.CGUDefault is a ClassBasedView (based upon how you have it defined in your urls.py), then default_view.CGUDefault.GetPatientsForClinic doesn’t seem to make sense to me. Is that a method within the CBV? Or is that a class defined within your CBV?
Either way, what I think you probably want to do is make sure that GetPatientsForClinic is either an FBV or CBV within the default_view module, not defined within your existing CBV.
To be more precise, we’d need to see your default_view.py file.

Yes, I did have GetPatientsForClinic as a method of CGUDefault. That makes perfect OOP sense to me; the CGUDefault class (the controller) services the template (the view) and with the method being used to extend the functionality of the UI, it should belong to the servicing controller. In other languages (.NET, Delphi etc) it’s perfectly normal for a controller to have multiple get and post methods. Is Django limited in this way? I thought the whole idea of MVC routing was to avoid the limitations of HTTP is a fairly simple and coherent way.

Anyway, I tried making GetPatientsForClinic a standard view function, which is what you see below, but still no joy:

from datetime import datetime, timedelta

from django.http import HttpResponseRedirect, HttpResponse
from django.shortcuts import render

from django.contrib.auth import logout
from django.views.generic import TemplateView

from . import helper_classes
from .models import MasterActivityTable, STAFF

from django.core import serializers
from django.http import JsonResponse

class CGUDefault(TemplateView):
    template_name = 'PollsterSystem/CGUDefault.html'

    def get(self, request):
        try:
            if not request.user.is_authenticated:

                #TODO Add authorisation check

                context = { "errorMessage": "You must log in before you can access any system pages"}

                return render(request, 'PollsterSystem/CGULogin.html', context)
            else:

                #This GET has two primary states.  The initial state is when the system first opens the Default.html
                #page, where there are no search criteria in the request body.  The main state is when the user
                #submits a search and the request body does have the search criteria.

                testSessionObj = helper_classes.TestSession(None, None, request.session['TestSessionObject'])   # Pull the dictionary out of session

                _text_value = testSessionObj.text_value
                _number_value = testSessionObj.number_value

                # Extract the dates from the selection criteria

                _dateFrom = None
                _dateTo = None
                _bookedDate = None

                try:
                    for _key in request.GET:
                        if _key == "txtCriteriaDateFrom":
                            _dateFromStr = request.GET[_key]
                            _dateFrom = datetime.strptime(_dateFromStr, '%Y-%m-%d')     #IMPORTANT..The format string is different to a template filter and is specific!
                        if _key == "txtCriteriaDateTo":
                            _dateToStr = request.GET[_key]
                            _dateTo = datetime.strptime(_dateToStr, '%Y-%m-%d')

                        # if _key == "ddlCriteriaClinic":
                        #     _bookedDateStr = request.GET[_key]
                        #     _bookedDate = datetime.strptime(_bookedDateStr, '%Y-%m-%d')
                except Exception as ex:
                    #The most likely error at this point is conversion of the date strings to datetime objects!
                    context = {"errorMessage": "CGUDefault.Get (2) : " + str(ex)}

                    return render(request, self.template_name, context)

                #If the criteria are not provided, create the default criteria

                if _dateFrom == None:
                    _dateFrom = datetime.today() - timedelta(days=2)

                if _dateTo == None:
                    _dateTo = datetime.today() + timedelta(days=5)

                _staffObj = STAFF.objects.get(EMPLOYEE_NUMBER = request.user.username)

                #_clinics = MasterActivityTable.objects.filter(STAFF_CODE_1=_staffObj.STAFF_CODE, BOOKED_DATE__range=(_dateFrom, _dateTo))
                _clinics = MasterActivityTable.objects.filter(STAFF_CODE_1=_staffObj.STAFF_CODE, BOOKED_DATE__range=(_dateFrom, _dateTo))

                # if _bookedDate == None:
                #     _patients = None
                # else:
                #     _patients = MasterActivityTable.objects.filter(BOOKED_DATE=_bookedDate)

                context = {
                    "text_value" : _text_value,
                    "number_value" : _number_value,
                    "criteriaFor" : _staffObj.NAME,
                    "criteriaDateFrom" : _dateFrom,
                    "criteriaDateTo" : _dateTo,
                    "criteriaClinics" : _clinics,
                    #"criteriaPatients" : _patients,
                }

                return render(request, self.template_name, context)
        except Exception as ex:
            context = {"errorMessage": "CGUDefault.Get (1) : " + str(ex)}

            return render(request, self.template_name, context)



    def post(self, request):
        # At the moment the only post is to log out
        logout(request)

        return render(request, 'PollsterSystem/CGULogin.html', None)


def GetPatientsForClinic(self, request):
    try:
        _bookedDateStr = request.GET["BOOKED_DATE"]

        _bookedDate = datetime.strptime(_bookedDateStr, '%Y-%m-%d')

        _patients = MasterActivityTable.objects.filter(BOOKED_DATE=_bookedDate)

        _patientsJSON = serializers.serialize('JSON', _patients)

        return JsonResponse(_patientsJSON)

    except Exception as ex:
        context = {"errorMessage": "CGUDefault.GetPatientsForClinic : " + str(ex)}

        return render(request, self.template_name, context)

I tried using fetch as well as jquery and although the messaging is better it still gave me the same result. Even if I hard code the full URI in I don’t get anything.

I can do the cascading drop down lists using the primary “get” method, but want to understand how I can use javascript in this way. I have a project coming up which will require at least 4 separate get type calls and at least two forms on one page.

That’s not how Django dispatches to CBVs. If you need to extend the functionality of a CBV, you create a new class that inherits from the parent class, in the same manner as your CBV (probably) inherits from one of the standard generic CBVs.

A CBV is not “a controller” in that sense. You’re trying to match patterns and terminology from other systems that don’t really apply here. The URL routing is defined by the urls.py file. When you’re using CBVs, you’re routing to the class, not an instance of the class. (When the request is received, a new instance of the class is created to handle that request. When the request is completed, the instance is disposed.)

SInce GetPatientsForClinic is a function and not a method in a class -

  • It should more properly be named get_patients_for_clinic to match standard Python naming conventions
  • It does not receive an implicit “self” parameter. See the sample views in the tutorial as examples.

Thanks, duly noted. This is the first time I have read these anywhere in any documentation or book. It seems very verbose to me.

I’m not sure what the difference between a function and a method is in the context of a class (?) A function in a class is commonly called a method, no?

Anyway, it turns out that I am just being very rusty with the old HTTP standard. I rewrote the Javascript to native (I’m not fan of JQuery):

    <script>

        let _ddlCriteriaClinic = document.getElementById('ddlCriteriaClinic')

        _ddlCriteriaClinic.addEventListener('change', function () { SetPatientDDL() } )

        function SetPatientDDL() {
            let _uri = 'CGUDefault/GetPatientsForClinic?BookedDate=' + _ddlCriteriaClinic.value;

            fetch(_uri)
                .then(response => response.json)
                .then(data => {
                    console.log(data);
                    alert('partial success!')
                })
                .catch(error => { alert('Error : ' + error) } )
        }
    </script>

I forgot that the request has to be hard coded in the URI. As soon as I did that it all started to come together.

And yes, I had to remove the ‘self’ parameter in GetPatientsForClinic:

    def GetPatientsForClinic(request):
        try:
            _bookedDateStr = request.GET["BookedDate"]

            _bookedDate = datetime.strptime(_bookedDateStr, '%Y-%m-%d')

            _patients = MasterActivityTable.objects.filter(BOOKED_DATE=_bookedDate)

            _patientsJSON = serializers.serialize('json', _patients)

            return HttpResponse(_patientsJSON, content_type='application/json')

        except Exception as ex:
            context = {"errorMessage": "CGUDefault.GetPatientsForClinic : " + str(ex)}

            return render(request, _templateName, context)      

Have just got to make sense of the serialised query now.

Thanks again.

Yes. A function in a class is frequently referred to as a method of that class. However, functions do not have to be defined within the context of a class, and those are only referred to as functions. So in the way that I tend to use those terms, a “function” can refer to either a stand-alone function or a method. But when I use the term method, I’m only referring to a function within the context of a class.