Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save amelieykw/855fedc3df9f0a664ae644f0c578aed2 to your computer and use it in GitHub Desktop.
Save amelieykw/855fedc3df9f0a664ae644f0c578aed2 to your computer and use it in GitHub Desktop.
[Django Ajax] #Python #Django #Ajax

Initial Setup

# base.html

{% load static %}<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>{% block title %}Default Title{% endblock %}</title>
    <link rel="stylesheet" type="text/css" href="{% static 'css/app.css' %}">
    {% block stylesheet %}{% endblock %}
  </head>
  <body>
    <header>
      ...
    </header>
    <main>
      {% block content %}
      {% endblock %}
    </main>
    <footer>
      ...
    </footer>
    <script src="https://code.jquery.com/jquery-3.1.0.min.js"></script>
    <script src="{% static 'js/app.js' %}"></script>
    {% block javascript %}{% endblock %}
  </body>
</html>

The jQuery library and all the JavaScript resources stays in the end of the HTML page for two reasons:

  1. to guarantee the DOM will be loaded when the script is executed
  2. to avoid inline scripts (at least scripts that uses jQuery).

All the extra or page specific JavaScript goes inside the {% block javascript %}{% endblock %} block.

Sample Scenario

Let’s say you want to validate the username field in a sign up view, as soon as the user finish typing the desired username. You want just to do a simple check, if the username is already taken or not.

views.py

from django.contrib.auth.forms import UserCreationForm
from django.views.generic.edit import CreateView

class SignUpView(CreateView):
    template_name = 'core/signup.html'
    form_class = UserCreationForm

urls.py

from django.conf.urls import url
from core import views

urlpatterns = [
    url(r'^signup/$', views.SignUpView.as_view(), name='signup'),
]

signup.html

{% extends 'base.html' %}

{% block content %}
  <form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Sign up</button>
  </form>
{% endblock %}

The view looks like that:

Ajax Request

Let’s implement an asynchronous request to validate if the username is already taken or not.

First

we gotta have a look on the HTML generated by the {{ form.as_p }}.

We want to inspect the username field, which looks like that:

<input type="text" required="" name="username" maxlength="150" id="id_username" autofocus="">

What we need here is its ID, which is id_username.

Let’s create a listener for the username’s field change event:

signup.html

{% extends 'base.html' %}

{% block javascript %}
  <script>
    $("#id_username").change(function () {
      console.log( $(this).val() );
    });
  </script>
{% endblock %}

{% block content %}
  <form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Sign up</button>
  </form>
{% endblock %}

In this case, as the name suggests, the change event will occur every time the value of the username field changes.

You can check the output of the console.log() function in your web browser console. Generally right clicking in the page and clicking on a menu that says Inspect or something similar.

Let’s create a view that checks if a given username is taken, and return a response as JSON.

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()
    }
    return JsonResponse(data)

Add a route to this view:

urls.py

from django.conf.urls import url
from core import views

urlpatterns = [
    url(r'^signup/$', views.SignUpView.as_view(), name='signup'),
    url(r'^ajax/validate_username/$', views.validate_username, name='validate_username'),
]

And then the simpliest implementation would be:

signup.html

{% extends 'base.html' %}

{% block javascript %}
  <script>
    $("#id_username").change(function () {
      var username = $(this).val();

      $.ajax({
        url: '/ajax/validate_username/',
        data: {
          'username': username
        },
        dataType: 'json',
        success: function (data) {
          if (data.is_taken) {
            alert("A user with this username already exists.");
          }
        }
      });

    });
  </script>
{% endblock %}

{% block content %}
  <form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Sign up</button>
  </form>
{% endblock %}

Improving the Example

The example is working, and that’s great. But there are a few details we can improve:

  • Hardcoded URL inside the Ajax function
  • User message embeded in the JavaScript

Since we are inside a Django template, we could do something like that:

$.ajax({
  url: '{% url "validate_username" %}',
  ...
});

It is already better, but, I don’t really like this strategy. Two reasons: You won’t be able to extract this JavaScript code to a external file. We have an external script modifying our JavaScript. If you find yourself writing code to write a JavaScript code, give it another thought. There might be a better solution.

Now, what I usually like to do:

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)

Extract the user message to the Python code, this way is easier to work with translations.

signup.html

{% extends 'base.html' %}

{% block javascript %}
  <script>
    $("#id_username").change(function () {
      var form = $(this).closest("form");
      $.ajax({
        url: form.attr("data-validate-username-url"),
        data: form.serialize(),
        dataType: 'json',
        success: function (data) {
          if (data.is_taken) {
            alert(data.error_message);
          }
        }
      });

    });
  </script>
{% endblock %}

{% block content %}
  <form method="post" data-validate-username-url="{% url 'validate_username' %}">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Sign up</button>
  </form>
{% endblock %}

Ideally I would put the data-validate-username-url attribute directly in the username field. But here we would need to expose all the fields in the {{ form.as_p }}. So in this case I can live with the attribute in the form tag.

Generally speaking: try to avoid modifying JavaScript code with Python code, keep the URL references in the HTML and manage user messages in the Python code.

Another tip: if you have direct access to the HTML field, prefer adding a class name like this:

<input type="text" name="username" id="id_username" class="js-validate-username">

And then you hook the change event to the class js-validate-username instead. The js- prefix suggests that there is a JavaScript code that interacts with this element. And then use this prefix for JavaScript only, never use it for styling the component as well via css.

有时候我们需要在不刷新的情况下载入一些内容,在网页的基本知识中我们介绍了 ajax 技术。

在本文中讲解如何用 Django 来实现 不刷新网页的情况下加载一些内容。

由于用 jQuery 实现 ajax 比较简单,所以我们用 jQuery库来实现,想用原生的 javascript 的同学可以参考:ajax 教程,下面也有例子提供下载

第一节

这里用 Django 表单 第一节 中的一个例子,我们要实现的是在不刷新的情况下显示计算结果到页面上。

# index.html


<!DOCTYPE html>
<html>
<body>
<p>请输入两个数字</p>
<form action="/add/" method="get">
    a: <input type="text" id="a" name="a"> <br>
    b: <input type="text" id="b" name="b"> <br>
    <p>result: <span id='result'></span></p>
    <button type="button" id='sum'>提交</button>
</form>
 
<script src="http://apps.bdimg.com/libs/jquery/1.11.1/jquery.min.js"></script>
<script>
    $(document).ready(function(){
      $("#sum").click(function(){
        var a = $("#a").val();
        var b = $("#b").val();
 
        $.get("/add/",{'a':a,'b':b}, function(ret){
            $('#result').html(ret)
        })
      });
    });
</script>
</body>
</html>

在原来的基础上,在一些元素上加了 id, 以便于获取值和绑定数据,然后我们用了jQuery.get() 方法,并用 $(selector).html() 方法将结果显示在页面上,如下图:

备注:关于请求头和 request.is_ajax() 方法使用

views.py 中可以用 request.is_ajax() 方法判断是否是 ajax 请求,需要添加一个 HTTP 请求头:

原生javascript:

xmlhttp.setRequestHeader("X-Requested-With", "XMLHttpRequest");

用 jQuery:

用 $.ajax 方法代替 $.get,因为 $.get 在 IE 中不会发送 ajax header

服务器端会将请求头的值全部大写,中划线改成下划线,并在非标准的头前面加上 HTTP_,这个过程可以认为相当于以下Python代码:

STANDARD_HEADERS = ['REFER', 'HOST', ...] # just for example
 
def handle_header(value):
    value = value.replace('-', '_').upper()
 
    if value in STANDARD_HEADERS:
        return value
 
    return 'HTTP_' + value

第二节

更复杂的例子,传递一个数组或字典到网页,由JS处理,再显示出来。

# views.py

from django.http import JsonResponse
 
def ajax_list(request):
    a = range(100)
    return JsonResponse(a, safe=False)
 
def ajax_dict(request):
    name_dict = {'twz': 'Love python and Django', 'zqxt': 'I am teaching Django'}
    return JsonResponse(name_dict)

写好后,我们在 urls.py 中添加以下两行:

url(r'^ajax_list/$', 'tools.views.ajax_list', name='ajax-list'),
url(r'^ajax_dict/$', 'tools.views.ajax_dict', name='ajax-dict'),

打开开发服务器 python manage.py runserver

我们访问对应的网址会看到输出值:

下一步就是在无刷新的情况下把内容加载到网页了,我们修改一下首页的模板 index.html

<!DOCTYPE html>
<html>
<body>
<p>请输入两个数字</p>
<form action="/add/" method="get">
    a: <input type="text" id="a" name="a"> <br>
    b: <input type="text" id="b" name="b"> <br>
    <p>result: <span id='result'></span></p>
    <button type="button" id='sum'>提交</button>
</form>
 
 
<div id="dict">Ajax 加载字典</div>
<p id="dict_result"></p>
 
<div id="list">Ajax 加载列表</div>
<p id="list_result"></p>
 
 
<script src="http://apps.bdimg.com/libs/jquery/1.11.1/jquery.min.js"></script>
<script>
    $(document).ready(function(){
      // 求和 a + b
      $("#sum").click(function(){
        var a = $("#a").val();
        var b = $("#b").val();
 
        $.get("/add/",{'a':a,'b':b}, function(ret){
            $('#result').html(ret);
        })
      });
 
      // 列表 list
      $('#list').click(function(){
          $.getJSON('/ajax_list/',function(ret){
            //返回值 ret 在这里是一个列表
            for (var i = ret.length - 1; i >= 0; i--) {
              // 把 ret 的每一项显示在网页上
              $('#list_result').append(' ' + ret[i])
            };
          })
      })
 
      // 字典 dict
      $('#dict').click(function(){
          $.getJSON('/ajax_dict/',function(ret){
              //返回值 ret 在这里是一个字典
              $('#dict_result').append(ret.twz + '<br>');
              // 也可以用 ret['twz']
          })
      })
    });
</script>
</body>
</html>

技能提升:getJSON中的写的对应网址,用 urls.py 中的 name 来获取是一个更好的方法!

标签:{% url 'name' %}

<script>
    $(document).ready(function(){
      // 求和 a + b
      $("#sum").click(function(){
        var a = $("#a").val();
        var b = $("#b").val();
 
        $.get("{% url 'add' %}",{'a':a,'b':b}, function(ret){
            $('#result').html(ret);
        })
      });
 
      // 列表 list
      $('#list').click(function(){
          $.getJSON("{% url 'ajax-list' %}",function(ret){
            //返回值 ret 在这里是一个列表
            for (var i = ret.length - 1; i >= 0; i--) {
              // 把 ret 的每一项显示在网页上
              $('#list_result').append(' ' + ret[i])
            };
          })
      })
 
      // 字典 dict
      $('#dict').click(function(){
          $.getJSON("{% url 'ajax-dict' %}",function(ret){
              //返回值 ret 在这里是一个字典
              $('#dict_result').append(ret.twz + '<br>');
              // 也可以用 ret['twz']
          })
      })
    });
</script>

这样做最大的好处就是在修改 urls.py 中的网址后,不用改模板中对应的网址。

补充:如果是一个复杂的 列表 或 字典,因为比如如下信息:

person_info_dict = [
    {"name":"xiaoming", "age":20},
    {"name":"tuweizhong", "age":24},
    {"name":"xiaoli", "age":33},
]

这样我们遍历列表的时候,每次遍历得到一个字典,再用字典的方法去处理,当然有更简单的遍历方法:

$.each() 方法代替 for 循环,html 代码(jQuery)

$.getJSON('ajax-url-to-json', function(ret) {
    $.each(ret, function(i,item){
        // i 为索引,item为遍历值
    });
});

补充:如果 ret 是一个字典,$.each 的参数有所不同,详见:http://api.jquery.com/jquery.each/

$.getJSON('ajax-get-a-dict', function(ret) {
    $.each(ret, function(key, value){
        // key 为字典的 key,value 为对应的值
    });
});

最后,附上一个返回图片并显示的ajax实例:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment