Mike Slinn
Mike Slinn

Django-Oscar Templates and Ajax

Published 2021-03-19. Last modified 2021-03-25.
Time to read: about 3 minutes.

This article is categorized under AWS, Django, Django-Oscar, JavaScript.

This blog post builds on a previous blog post:

Acknowledgement

I found an excellent article by Vitor Freitas entitled How to Work With AJAX Request With Django that described how to create a user registration form in Django webapp, such that the form checked whether the currently typed userid was available, without the user having to submit the form.

The userid uniqueness test was performed on the server by the Django webapp. An Ajax GET request was used to communicate from the web browser to the Django webapp.

The article was written 5 years and many versions of Django ago, so it’s detailed instructions no longer can be followed.

I decided to follow that basic recipe, updated for current software, and apply the article to django-oscar, using the standard django-oscar user registration form. Because django-oscar is a more specialized framework than Django, it is more complete out of the box, so I had less work to do than Vitor Freitas described for Django. The remainder of the blog post are the updated instructions.

AuthenticationForm

Django-oscar uses email addresses for user ids.

As discussed in the Django-Oscar Startup & User Registration blog post, the HTML form for django-oscar user signin is subclassed from the standard Django AuthenticationForm. I used the web browser's view source to see what it looks when rendered:

Shell
<input type="email" name="login-username"
  maxlength="150" class="form-control"
  required id="id_login-username">

The most important detail here is the value of id, which is id_login-username. We need to know this so we can write JavaScript that triggers when a user types in their username during the registration process.

The Bootstrap 4 documentation discusses the form-control CSS class.

JavaScript

The List of Django-Oscar Page Template Blocks blog post lists out all of the Django template blocks defined by the base.html and layout.html Django templates.

“Hmm, which block should I store my JavaScript Ajax code in?”, I wondered. There are 3 reasonable Django template blocks to choose from: onbodyload, scripts, and extrascripts. Considering the code that I have in mind, I think selecting the onbodyload Django template block makes the most sense. I added the following to the end of $frobshop/templates/oscar/customer/login_registration.html:

$frobshop/templates/oscar/customer/login_registration.html
{% block onbodyload %}
  {{ block.super }}

  $("#id_login-username").change(function () {
    alert( $(this).val() );
  });
{% endblock %}

{{ block.super }} means that the rest of the block content is meant to enhance the default value of the block content, not replace it.

My browser's view source shows me that the web page was automatically regenerated with the additional JavaScript. I highlighted the default value, which was (hopefully) enhanced by the code I provided:

<script>
  $(function() {



oscar.init();


$("#id_login-username").change(function () {
alert( $(this).val() );
});

  });
  </script>

Now each time I type a value into the Email address field, and then the focus changes (perhaps by pressing the tab key), I get an alert that shows what I had typed.
Fantastic! 😁

Ajax Request

Now it is time to connect the JavaScript change handler to the Frobshop Django webapp that I've been working on (actually it is a django-oscar webapp, which is a subclass of Django webapps.)

I already had an app called main in my Frobshop webapp. No change was required to the recipe I am following with respect to the view. I merely added the following to $frobshop/main/views.py:

main/views.py
from django.contrib.auth.models import User
from django.http import JsonResponse

def validate_username(request):
    username = request.GET.get('username', None)
    data = {
        'is_taken': User.objects.filter(username__iexact=username).exists()
    }
    if data['is_taken']:
        data['error_message'] = 'A user with this username already exists.'
    return JsonResponse(data)

Django 2.0 changed url to re_path. I adjusted the recipe’s code example accordingly, and added the following to $frobshop/main/urls.py:

main/urls.py
from main.views import validate_username

urlpatterns = [ 
  re_path(r'^ajax/validate_username/$', validate_username, name='validate_username'),
] 

Now I pasted in the JavaScript changes from the recipe into $frobshop/templates/oscar/customer/login_registration.html. The changes are highlighted.

$frobshop/templates/oscar/customer/login_registration.html
{% block onbodyload %}
  {{ block.super }}

  $("#id_login-username").change(function () {
    var form = $(this).closest("form");
      $.ajax({
        url: form.attr("data-validate-username-url"),
        data: $(this).serialize(),
        dataType: 'json',
        success: function (data) {
          if (data.is_taken) {
            alert(data.error_message);
          } else {
            alert("New user");  // Delete this line once it works
          }
        }
      });
  });
{% endblock %}

The Django console logged the request.

[25/Mar/2021 12:11:29] "GET /accounts/login/?login-username=adminx HTTP/1.1" 200 19692

OMG, it worked perfectly! 😁