Django dynamic filtering with Ajax

Hello,

I want to dynamically filter my querySet using the value of my input field with Ajax.

Right now I’m trying something like this:

Template:

<div class="card mt-3">
        <div class="card-body">
        <form action="" form="get">
            <input data-url="{% url 'klantbeheer' %}" class="zoekklanten" name="q" type="text" placeholder="Zoek op contactnaam...">
        </form>
        {% if object_list %}
                <div class="single-table">
                    <div class="table-responsive">
                        <table class="table text-center">
                            <thead class="text-uppercase bg-dark">
                                <tr class="text-white">
                                    <th scope="col">Bedrijfsnaam</th>
                                    <th scope="col">Contactnaam</th>
                                    <th scope="col">Locatie</th>
                                    <th scope="col">Actie</th>
                                </tr>
                            </thead>
                            <tbody>
                                {% for user in object_list %}
                                <tr>
                                    <td>{{ user.bedrijfsNaam }}</td>
                                    <td>{{ user.contactNaam }}</td>
                                    <td>{{ user.adres }}</td>
                                    <td>
                                        <a href="{% url 'klantupdaten' user.id  %}"><i class="fas fa-edit"></i></a>
                                        <a href="#" data-url="{% url 'klantverwijderen' user.id %}" class="deletegebruiker" data-bs-toggle="modal" data-bs-target="#dynamic-modal"><i class="fas fa-trash-alt"></i></a>
                                    </td>
                                </tr>
                                {% endfor %}
                            </tbody>
                        </table>
                    </div>
                </div>
            {% else %}
            <p>Geen gebruikers gevonden</p>
                <p>
                    <a href="{% url 'klantaanmaken' user.id %}" class="btn btn-primary">Maak klant aan</a>
                </p>
            {% endif %}
        </div>
    </div>

View:

class ManageUserView(SuperUserRequired, ListView):
    model = User
    template_name = 'webapp/klant/beheerklant.html'


    #Query based upon contactName
    def get_queryset(self, query):
        

        #Contactnaam bevat QUERY EN superuser is vals.
        object_list = self.model.objects.filter(contactNaam__icontains=query).exclude(is_superuser=True)
        
        return object_list

    def post(self, request, *args, **kwargs):
        query = self.request.POST.get('query', '')
        print(query)
        self.get_queryset(query)

Jquery:

$('.zoekklanten').on('keyup', function(){
    var url = $(this).data('url')
    var query = $(this).val();

    $.ajax({
        url:url,
        type:'POST',
        data:{
            'query':query
        },
        dataType: 'json',
            beforeSend: function(xhr, settings){
                if(!csrfSafeMethod(settings.type) && !this.crossDomain){
                    xhr.setRequestHeader("X-CSRFToken", csrftoken);
                }
            },
            success: function(result){
            },
            error: function(data){
            }

     })
})

I’m getting "get_queryset() missing 1 required positional argument: ‘query’ " error. I assume calling the get_queryset function inside a post request function isn’t the way to go about this.

What would be the best way to filter dynamically using Ajax in Django?

The normal calling sequence for get_queryset doesn’t take any parameters. Also keep in mind that ListView by default doesn’t take a POST request, so any change you make to get_queryset needs to be able to handle both cases (GET and POST).

You’ve got a couple of different options:

  • Create a separate function for this (don’t override get_queryset)
  • Make the query parameter optional, then check for it being passed in the call
  • Have get_queryset get the query from self.request.POST, again checking to see if query exists.
  • Have post save query on self, and have get_queryset reference self.query, again with checking to see that self.query exists.

There are a couple other options but all appear to be basic variations on one or more of these.

Hello, thanks again for your response.

I’m trying this right now:

#Query based upon contactName
    def get_queryset(self):
        query = self.request.POST.get('query', '')  
        object_list = self.model.objects.filter(contactNaam__icontains=query).exclude(is_superuser=True)
        return object_list

Which is working, but when I try and enter a value I’m getting a POST 405 Error (Method Not Allowed).

Correct, because there’s no post function defined by default within the ListView CBV. You still need that function for the CBV to know it can handle a POST request.

Something like this returns nothing. I believe this is your third option or am I doing something wrong?

class ManageUserView(SuperUserRequired, ListView):
    model = User
    template_name = 'webapp/klant/beheerklant.html'

    #Query based upon contactName
    def get_queryset(self, query=None):
        if not query:
            return super().get_queryset()

        object_list = self.model.objects.filter(contactNaam__icontains=query).exclude(is_superuser=True)
        return object_list
    
    def post(self, request, *args, **kwargs):
        query = self.request.POST.get('query', '')
        self.get_queryset(self, query)
        return JsonResponse('OK', safe=False)

How are you debugging this, and what are you seeing? (Actually, that’s the second option I presented, but that’s an unimportant side note.)

haha oops

I’m debugging this with Developer Tools in Chrome. Right now it’s returning the entire list of users, not even excluding the admins. When typing something in the input it doesn’t do anything.

Ah - I just spotted something I should have recognized earlier. You’re posting JSON and not an HTML form.

See: request.POST and request.body

(You would be able to see the problem if you logged / printed / looked at your query on the server side.)

I’ve changed the dataType from JSON to HTML in the Ajax, when I print the data on the server side. It correctly prints the value from the input. Still not filtering though.

Interesting, I wouldn’t have thought to approach it from that direction, but that’s ok. What exactly are you getting on the server from self.request.POST.get(‘query’)? (Or, phrasing the same question in a different way, in your post method, what is the value of query being supplied to get_queryset?)

It’s exactly printing the query I have typed in the input. :confused:

You made a change from earlier.

First you had:

Now you have:

You were right the first time. Within methods in a class, Python passes self as the first parameter of a call - you don’t pass it explicitly.

I still have

self.get_queryset(query)

right now inside my post function.

Please post your current view then, because that’s not what you had in your most recent post showing that view (Django dynamic filtering with Ajax - #5 by DarknessNacho)

My current view:

class ManageUserView(SuperUserRequired, ListView):
    model = User
    template_name = 'webapp/klant/beheerklant.html'

    #Query based upon contactName
    def get_queryset(self, query=None):
        if not query:
            return super().get_queryset()
        
        print(query)

        object_list = self.model.objects.filter(contactNaam__icontains=query).exclude(is_superuser=True)
        return object_list
    
    def post(self, request, *args, **kwargs):
        query = self.request.POST.get('query', '')
        self.get_queryset(query)

        return JsonResponse({ 'status' : 'NOK'})

Couple things:

  • move your print to before the if statement. That way if the if not test is True, you’ll still see what’s being passed.
  • Your get_queryset is returning a value, but your call to get_queryset isn’t capturing that value
  • Your JsonResponse isn’t returning any data

I think you want to solve it in another way.

I want to simply fill in my input, capture the value each time the input changes than pass it to the view using AJAX and change the queryset based upon that value. So the results get filtered based upon that value.

The mechanics or frequency doesn’t matter. How / when you invoke the view is a client-side issue and has nothing to do with the Django implementation.

I think you may have a wrong impression of how a CBV works.

The issue is that each time you invoke that view, you need to return the new / filtered queryset. Each call to a CBV creates a new instance of that class. It’s not like your page is repeatedly invoking that method on the same instance of the view. When the view returns a response, that instance of the view class is disposed. Next call, new instance is created. There’s no “memory” or “persistence” of data between views.

But what is the use of passing the new queryset to the success function in Ajax? It won’t change anything, right? Because I won’t do anything with that data inside my success function.

You want to return it back to the calling JavaScript function so that it can update your page. Otherwise, there’s nothing that is going to update the HTML of your list.