2022年 11月 6日

python自定义表单_Python编程(三十二):Django进阶(ModelForm类、Ajax操作)

有的人不管走到哪里,去到哪里,都能给别人带来幸福。

本节内容:

1、 ModelForm

ModelForm是Model和Form的结合体,使用它可以很方便的增删改查。其中要依赖很多的Model中的类,用于简单的小程序还好,要是用于大型的程序就有些不适用。Model用在定制Django Admin时用得比较多。

2、Ajax

原生的Ajax

通过jQuery操作的Ajax

伪Ajax操作

3、文件上传(预览)

– 基于Form提交,页面会刷新。要上传图片时,不能做预览。实在要做的话,需要浏览器的配合。

– Ajax上传文件

4、图片验证码,需要与Session来配合

5、CKEditor, UEEditor, TinyEditor, KindEditor(***)

– 基本使用

– 文件上传,多文件上传,文件空间管理

– 在博客系统防止XSS攻击(过滤的函数或类,将一些特殊的东西去掉)

上节内容回顾:

Model操作

– 数据库操作

– 验证

Form

– 做验证

– is_vaild -> 每一个字段进行正则(字段内置的正则)+clean_字段 -> clear(__all__) -> _post_clean

– cleand_data

– error

一、ModelForm

ModelForm 是 Model和Form的结合体。

Model + Form -> 数据库操作 + 验证

class LoginModelForm(xxxxx):

利用model.A 中的字段操作

下面来创建一个 Django 项目 michael_05,并创建一个app01,创建过程省略。在 settings.py 中注册 app01,并且添加静态文件路径代码:

STATICFILES_DIRS = (os.path.join(BASE_DIR, ‘static’),)

接下来在项目文件夹 michael_05 下创建静态文件夹 static。

接着在urls.py中添加一个URL:

from app01 import views

path(‘index/’, views.index),

在app01目录下的 models.py 文件写如下代码:

位置:michael_05\app01\models.py

from django.db import models

class UserType(models.Model):

captions = models.CharField(max_length=32)

class UserInfo(models.Model):

username = models.CharField(verbose_name=”用户名”,max_length=32)

email = models.EmailField(verbose_name=”邮箱”)

user_type = models.ForeignKey(to=”UserType”, to_field=”id”, on_delete=None)

接着在项目文件夹michael_05的命令行下执行下面的命令:

python manage.py makemigrations

python manage.py migrate

在 views.py 文件中写入下面的代码:

位置:app01\views.py

from django.shortcuts import render

from django import forms

from django.forms import fields

from app01 import models

class UserInfoForm(forms.Form):

username = fields.CharField(max_length=32)

email = fields.EmailField()

user_type = fields.ChoiceField(

choices = models.UserType.objects.values_list(“id”, “captions”), # 在 Form中做跨表查询是种方式

)

def __init__(self, *args, **kwargs):

“””自动执行更新”””

super(UserInfoForm, self).__init__(*args, **kwargs)

self.fields[‘user_type’].choices = models.UserType.objects.values_list(“id”, “captions”)

def index(request):

if request.method == “GET”:

obj = UserInfoForm()

return render(request, “index.html”, {‘obj’: obj})

elif request.method == “POST”:

obj = UserInfoForm(request.POST)

obj.is_valid()

obj.cleaned_data

obj.errors

# models.UserType.objects.create(**obj.cleaned_data) # 写入数据库

# models.UserType.objects.filter(id=1).update(**obj.cleaned_data) # 更新操作

return render(request, “index.html”, {‘obj’: obj})

在index() 函数中,根据GET请求还是POST请求进行相应的操作,如果是POST请求,可以将相应的值写入数据库或者更新操作。接下来还需要在templates目录下新建一个 index.html 文件,该文件代码片断是:

{% csrf_token %}

{{ obj.as_p }}

代码中 obj.as_p 表示生成 p 标签,p 标签内部是 label 和 input 标签,标签的数量由 views.py中 UserInfoForm() 字段数量决定。此时运行 michael_05,在浏览器地址栏访问 http://127.0.0.1:8000/index/,页面呈现出两个输入框和一个选择框。当输入内容不符合规范,就会提示错误,这样就简单的完成了 form 的验证功能。

下面使用 ModelForm来做验证功能。

只需要在Views.py 文件中增加一个ModelForm类,在 index() 函数实例化这个类,并将实例化对象传递到前端即可。

位置:app01\views.py

class UserInfoModelForm(forms.ModelForm):

class Meta:

model = models.UserInfo # model指向数据库的 UserInfo 类,两者建立依赖

fields = ‘__all__’ # 在页面上展示所有的字段

# fields = [“username”, “email”] # 还可以写成列表的形式,当有很多个字段时,想要显示某几个字段可用这种方式

# exclude = [‘username’] # 当想要某个字段不显示时,可使用exclued排除掉

此时 index() 函数代码的GET请求时,修改为实例化 UserInfoModelForm 这个类,如下所示:

def index(request):

if request.method == “GET”:

obj = UserInfoModelForm() # 注意修改了这个里的实例化对象

return render(request, “index.html”, {‘obj’: obj})

elif request.method == “POST”:

obj = UserInfoForm(request.POST)

return render(request, “index.html”, {‘obj’: obj})

这时刷新 http://127.0.0.1:8000/index/ 页面同样能看到之前的效果。这里的 UserInfoModelForm 类中,fields = ‘__all__’ 表示要在页面上显示所有的字段,如果要想显示特定的几个字段可使用 fields = [“username”, “email”] 的列表方式,当然还可以使用 exclude = [‘username’] 的列表方式排除掉要显示的字段。

在 ModelForm 中是怎样做的验证呢?验证不是由用户定义的类 UserInfo 做的,也不是forms类中定义的,是在 forms.Form 中Form基类 BaseForm 中做的验证,在这个 BaseForm 类中有定义很多的方法,比如 is_vaild() 方法。

所以在做Form验证时,UserInfoForm(自定义类)继承 Form,Form 再继承 BaseForm,BaseForm类中有验证方法,如 is_vaild()。那么 UserInfoModelForm(自定义类)继承的是 ModelForm,ModelForm又继承了 BaseModelForm,在 BaseModelForm 中有定义验证方法。

再一次修改 views.py 中的 index() 函数,这次修改POST请求,POST请求在实例化时也指向 UserInfoModelForm 类,修改后的index() 函数代码如下所示:

def index(request):

if request.method == “GET”:

obj = UserInfoModelForm()

return render(request, “index.html”, {‘obj’: obj})

elif request.method == “POST”:

obj = UserInfoModelForm(request.POST)

print(obj.is_valid())

print(obj.cleaned_data)

print(obj.errors)

return render(request, “index.html”, {‘obj’: obj})

现在刷新页面 http://127.0.0.1:8000/index/ ,输入对应字段的正确信息后点击提交后,在 index() 函数中的 3条print 语句的输出可知, obj.is_valid() 输出的是 True 或 False,obj.cleaned_data 输出的就是 POST提交是填写的表单内容。

二、 ModelForm组件

在自定义的ModelForm类中的 Meta 类中,除了前面说的3个参数 model,fields,exclude等参数外,其实还有其它参数,相应参数如下所示:

ModelForm

class Meta:

model, # 对应Model的类,如:model = models.UserInfo

fields=None, # 对应字段信息,如对应全部字段:fields = ‘__all__’

exclude=None, # 排除某个或者某些字段,如:exclude = [‘user_type’, ’email’]

labels=None, # input框左边的提示信息,字典形式,键是字段名,如:labels = {‘username’: ‘用户’}

help_texts=None, # 帮助提示信息,如:help_texts = {’email’:’比如:michael@root.com’}

widgets=None, # 自定义插件,用法介绍在后面

error_messages=None, # 自定义错误信息(整体错误信息from django.core.exceptions import NON_FIELD_ERRORS)

field_classes=None # 自定义字段类 (也可以自定义字段),用法介绍在后面

localized_fields=(‘ctime’,) # 本地化,如:根据不同时区显示数据,将UTC时间转化为本地时间,ctime 是字段名

如:

数据库中

2016-12-27 04:10:57

setting中的配置

TIME_ZONE = ‘Asia/Shanghai’

USE_TZ = True

则显示:

2016-12-27 12:10:57

Meta类中的 widgets 的用法,这个参数是用来修改字段类型的,比如在 UserInfo类中原来的 username 字段的类型是 CharField,现在要将其修改为 Textarea 类型。在使用这个功能前,还需要导入这个 widgets 插件,由于这个插件名与参数同名,所以在导入的时候,还需要指定别名,导入方法如下:

from django.forms import widgets as fwidgets

此时在 Meta 类中就可以使用 fwidgets 修改字段名,具体使用方法是这样:

widgets = {‘username’: fwidgets.Textarea(attrs={‘class’:’c1′})}

为了便于说明Meta类中的 error_messages 参数的使用,先将 views.py 中的 index() 函数中的POST请求下的第三条print语句:print(obj.errors),修改为 print(obj.errors.as_json()),就是以 json格式输出错误信息。这时刷新页面,进入到页面的源代码中将三个 input 标签中的 required 属性都去掉,并将邮箱input标签中的 type=”email” 改为 type=”text”,此时的错误信息在后台的输出如下所示:

{“username”: [{“message”: “This field is required.”, “code”: “required”}],

“email”: [{“message”: “This field is required.”, “code”: “required”}],

“user_type”: [{“message”: “This field is required.”, “code”: “required”}]}

从输出的错误信息可以看出,错误信息是一个字典,字典的键是字段名,值是列表,但是列表中又是字典,其中的键值对就对应相应的错误提示信息。但是要在 error_messages 参数中自定义错误提示信息,还得需要使用字典形式,例如下面这样定义:

error_messages = {

“email”: {“required”: “邮箱不能为空”, “invalid”: “邮箱格式不正确”,}

}

还可以在 error_messages 中定义整体的错误信息,在其中使用 __all__ 即可,如下所示:

error_messages = {

“__all__”: {‘required’: “不能为空”, ‘invalid’: ‘格式不正确’},

“email”: {“required”: “邮箱不能为空”,”invalid”: “邮箱格式不正确”,}

}

为了便于说明在 Meta 类中 field_classes 参数的使用,将之前导入的 fields 模块使用别名,因为在 field_classes 参数需要使用 fields 模块提供的功能。使用别名导入后,还要记得修改在 views.py 中使用到该模块的地方一并修改。

from django.forms import fields as ffields

这次将提交过来的邮箱使用正则匹配的方式转化成URL。具体实现方法如下代码所示:

field_classes = {’email’: ffields.URLField}

此时刷新页面 http://127.0.0.1:8000/index/ 后,在邮箱后面输入正确的邮箱地址点提交,这时提示输入网址,就不在是邮箱。这样可以不在models.py 中的类进行修改,在Meta 类中同样可以修改。要注意的是这里使用的是 ffields.URLField 类,后面没有括号。

在Meta类中的这些参数在生成页面时,只省了做那几个字段的事,也没有省多大的事。能够省事的是在 index() 函数中的 POST提交时做的验证、获取数据、以及 as_json() 等,这些是按照form方式提交时做的。在这里可以先判断 obj.is_vaild() 是否成功。

def index(request):

if request.method == “GET”:

obj = UserInfoModelForm()

return render(request, “index.html”, {‘obj’: obj})

elif request.method == “POST”:

obj = UserInfoModelForm(request.POST)

if obj.is_valid(): # 判断是否验证成功

obj.save() # 这一句就可以把form提交的全部正确数据保存到数据库

return render(request, “index.html”, {‘obj’: obj})

在上面的 index() 函数中,当POST提交时,通过 if obj.is_valid() 判断提交的数据是否合法,合法就执行 obj.save() 保存到数据库,只需要一条语句即可。要验证是否保存成功,先用navicat连接数据库,在 usertype 表中添加一条数据。此时再刷新http://127.0.0.1:8000/index/ 页面,填写正确数据后点击提交即可完成将全部正确的数据保存到数据,这在做更新操作时是方便的。

这里的数据库中只是做了一个简单的一对多情况,要是有多对多的情况,又该是何种用法呢?在此先在models.py 文件添加一个UserGroup 类,代码如下所示:

位置:app01\models.py

class UserGroup(models.Model):

name = models.CharField(max_length=32)

还需要在models.py 文件中的 UserInfo 类中添加下面这行代码:

u2g = models.ManyToManyField(UserGroup)

此时在命令行执行下面两条命令:

python manage.py makemigrations

python manage.py migrate

接下来在 usergroup 表中添加3条记录后,再次刷新 http://127.0.0.1:8000/index/ 页面,此时在 u2g 后面有多选框,将添加的3条记录全部选上,并填写正确的信息后点击提交。后台的 obj.save() 方法同样可以将多对多关系进行保存。这时刷新数据库连接可以看到 userinfo_u2g 的表上的相应记录。

通过查看 obj.save() 中的 save() 方法源代码可知,在这个方法中有一个参数 commit=True,当 commit=True时,save() 方法中的保存语句才会执行:

if commit:

# If committing, save the instance and the m2m data immediately.

self.instance.save() # 这一句只会保存当前对象,不会保存 ManyToMany

self._save_m2m() # 这一句才会保存 ManyToMany

else:

# If not committing, add a method to the form to allow deferred

# saving of m2m data.

self.save_m2m = self._save_m2m # commit=False时生成一个字段,字段是函数名 _save_m2m

当给save() 方法传递False 参数时,什么都不做,这时可以将save() 方法拆开来做。下面的 index() 函数中的 obj.save() 方法拆开成三步来完成,代码如下所示:

def index(request):

if request.method == “GET”:

obj = UserInfoModelForm()

return render(request, “index.html”, {‘obj’: obj})

elif request.method == “POST”:

obj = UserInfoModelForm(request.POST)

if obj.is_valid(): # 判断是否验证成功

# obj.save() # 这一句就可以把form提交的全部正确数据保存到数据库

instance = obj.save(False) # 传递 False 参数什么都不做,返回一个对象

instance.save() # 这一句只会保存当前对象,不会保存 ManyToMany

obj.save_m2m() # 这一句才会保存 ManyToMany

return render(request, “index.html”, {‘obj’: obj})

1、ModeForm使用实例

创建两个页面,一个是列出用户信息的页面 user_list,在这个用户信息的页面可以点击相应用户信息后面的编辑后,进入到另一个

编辑页面 edit-(\d+),在编辑页面可以修改用户名、邮箱、User Type、U2g等属性,点击提交后根据用户的id号更新到数据库中。

为此先在urls.py 中添加两个URL,代码如下:

位置:michael_05\ursl.py

from django.urls import path, re_path

path(‘user_list/’, views.user_list),

re_path(‘edit-(\d+)/’, views.user_edit),

接下来在views.py 中增加两个相应的处理函数user_list()和user_edit(),这里 user_list() 函数执行数据库查询操作,将查询结果返回给前端,由前端根据需要在页面上展示用户信息数据。当点击用户信息数据后面的编辑后就跳转到对应用户的编辑页面,在编辑页面首先要显示用户原来的信息,当修改完信息后,点击提交(此时是POST请求),后台首先验证数据是否规范,规范就执行保存动作。user_list()和user_edit() 函数的代码如下所示:

位置:app01\views.py

def user_list(request):

li = models.UserInfo.objects.all().select_related(‘user_type’) # 这里不能查询多对多的关系

return render(request, ‘user_list.html’, {“li”: li})

def user_edit(request, nid):

# 获取当前id 对应的用户信息

# 显示用户已经存在的数据

if request.method == “GET”:

user_obj = models.UserInfo.objects.filter(id=nid).first()

# 使用参数 instance,并将上一步的查询结果对象user_obj传递给instance即可,这时在编辑页面就有用户对应的原信息

mf = UserInfoModelForm(instance=user_obj)

return render(request, “user_edit.html”, {“mf”: mf, “nid”: nid})

elif request.method == “POST”:

user_obj = models.UserInfo.objects.filter(id=nid).first()

mf = UserInfoModelForm(request.POST, instance=user_obj) # 加上instance参数,下面的save()方法才会是更新,要不然是创建

if mf.is_valid(): # 判断提交的数据是否正确

mf.save() # 必须要将查询对象user_obj传递给mf,这里才是更新操作,否则是创建操作

else:

print(mf.errors.as_json())

return render(request, “user_edit.html”, {“mf”: mf, “nid”: nid})

在上面的 user_edit() 函数的代码中,要注意 instance参数的用法,instance 参数指向的是查询对象,在POST请求的时候,实例化UserInfoModelForm对象时,要将查询对象传递给instance参数,后面的save()方法才是更新操作,否则就是创建操作。

接下来的user_list.html和user_edit.html的代码如下所示:

位置:michael_05\templates\user_list.html

{% for row in li %}

{{ row.username }} – {{ row.user_type.captions }} –
编辑

{% endfor %}

位置:michael_05\templates\user_edit.html

{% csrf_token %}

{{ mf.as_p }}

2、ModeForm验证

前面做的ModeForm只是用来生成标签,对提交过来的数据通过 is_valid() 进行验证,通过 save() 方法进行保存。现在对用户提交过来的数据进行验证,就只用了一个 is_valid() 进行验证判定,在 ModeForm 中还可以定义方法进行验证。通过查看 is_valid()方法的源代码可知,这个方法是在 BaseForm 类中定义的。ModelForm 类也继承了 BaseForm 类,在 BaseForm 类中有定义clean方法,所以在用户自定义的 ModeForm类中也可以定义 clean 开头的方法对用户数据进行验证。例如在 UserInfoModelForm 自定义类中增加一个 clean_username() 验证方法,增加后的 UserInfoModelForm 类完整代码如下所示:

位置:app01\views.py中的 UserInfoModelForm 类

class UserInfoModelForm(forms.ModelForm):

class Meta:

model = models.UserInfo # model指向数据库的 UserInfo 类,两者建立依赖

fields = ‘__all__’ # 在页面上展示所有的字段

# fields = [“username”, “email”] # 还可以写成列表的形式,当有很多个字段时,想要显示某几个字段可用这种方式

# exclude = [‘user_type’] # 当想要某个字段不显示时,可使用exclued排除掉

labels = {‘username’: ‘用户’}

help_texts = {’email’:’比如:michael@root.com’}

widgets = {‘username’: fwidgets.Textarea(attrs={‘class’:’c1′})}

error_messages = {

“__all__”: {‘required’: “不能为空”, ‘invalid’: ‘格式不正确’},

“email”: {

“required”: “邮箱不能为空”,

“invalid”: “邮箱格式不正确”,

}

}

# field_classes = {

# ’email’: ffields.URLField

# }

def clean_username(self):

“””本次主要增加了 clean_username 验证方法”””

old = self.cleaned_data[‘username’] # 获取 username 原来的值

# 中间可以对原来的值进行各种操作,最后可根据情况返回原来的值还是操作后的新值,这样就提交过去了

return old

3、在自定义的ModeForm类中添加字段

添加字段的操作都是在 models.py 文件中的类中添加的,这些字段都是保存在数据库中,在用户自定义的 ModeForm 类也可以添加字段,这些字段同样可以在页面上显示。例如在自定义类 UserInfoModelForm 中添加一个单选标签字段 is_rmb,代码如下所示:

位置:app01\views.py 文件中的 UserInfoModelForm

class UserInfoModelForm(forms.ModelForm):

# 在 UserInfoModelForm 类中还可以定义字段,例如下面这样:

is_rmb = ffields.CharField(widget=fwidgets.CheckboxInput) # CheckboxInput 是单选标签

class Meta:…(这里的代码与前面的一样)

def clean_username(self):…(这里的代码与前面的一样)

这时访问 index页面或者 edit-(\d+) 页面都会有一个 Is rmb的单选框。这个新增字段与保存在数据库中的字段没有关系,这时也可以在 user_edit() 等函数中的 is_valid()方法后面做一些相应的操作。

4、ModeForm总结

(1)、可以生成HTML标签:class Meta: …

(2)、要在页面上显示默认值:mf = xxxModeForm(instance=Modelobj)

(3)、还可以加额外的标签,例如:

is_rmb = ffields.CharField(widget=fwidgets.CheckboxInput)

(4)、各种验证 is_valid() -> 各种钩子…

(5)、保存数据 mf.save(),可以拆开来做,例如:

instance = mf.save(False)

instance.save()

mf.save_m2m()

三、Ajax

1、 原生AJAX

Ajax主要就是使用 【XmlHttpRequest】对象来完成请求的操作,该对象在主流浏览器中均存在(除早期的IE),Ajax首次出现,IE5.5中存在(ActiveX控件)。

1.1、 XmlHttpRequest对象介绍

XmlHttpRequest对象的主要方法:

在浏览器的 Console 下面创建一个 XmlHttpRequest 对象方法:

xmlobj = new XMLHttpRequest(),创建对象后,可以对下面的方法进行调用

a. void open(String method,String url,Boolen async)

用于创建请求

参数:

method: 请求方式(字符串类型),如:POST、GET、DELETE…

url: 要请求的地址(字符串类型)

async: 是否异步(布尔类型)

xmlobj.open()

b. void send(String body)

用于发送请求

参数:

body: 要发送的数据(字符串类型)

c. void setRequestHeader(String header,String value)

用于设置请求头

参数:

header: 请求头的key(字符串类型)

vlaue: 请求头的value(字符串类型)

d. String getAllResponseHeaders()

获取所有响应头

返回值:

响应头数据(字符串类型)

e. String getResponseHeader(String header)

获取响应头中指定header的值

参数:

header: 响应头的key(字符串类型)

返回值:

响应头中指定的header对应的值

f. void abort()

终止请求

XmlHttpRequest对象的主要属性:

a. Number readyState

状态值(整数)

详细:

0-未初始化,尚未调用open()方法;

1-启动,调用了open()方法,未调用send()方法;

2-发送,已经调用了send()方法,未接收到响应;

3-接收,已经接收到部分响应数据;

4-完成,已经接收到全部响应数据;

b. Function onreadystatechange

当readyState的值改变时自动触发执行其对应的函数(回调函数)

c. String responseText

服务器返回的数据(字符串类型)

d. XmlDocument responseXML

服务器返回的数据是Xml对象,可以用这个对象,根据节点名称取值

e. Number states

状态码(整数),如:200、404…

f. String statesText

状态文本(字符串),如:OK、NotFound…

1.2、 XmlHttpRequest实例

为了对上面的 XmlHttpRequest 对象加深理解,下面用实例来做一做。首先在urls.py文件中添加两个URL,代码如下:

位置:michael_05\urls.py

re_path(‘ajax/$’, views.ajax),

re_path(‘ajax_json/$’, views.ajax_json),

接下来在 views.py 文件中创建两个函数 ajax() 和 ajax_json() ,这两个函数的代码如下所示:

位置:app01\views.py文件中 ajax() 函数和 ajax_json() 函数:

def ajax(request):

return render(request, ‘ajax.html’)

def ajax_json(request):

ret = {‘status’: True, ‘data’: None}

import json

return HttpResponse(json.dumps(ret))

#return HttpResponse(json.dumps(ret), status=404, reason=’Not Found’) # 在返回的时候还可以发送状态码

接着在templates文件夹下增加一个 ajax.html 文件,该文件的代码如下所示:

function Ajax1() {

var xhr = new XMLHttpRequest(); // 创建Ajax对象 xhr.open(‘GET’, ‘/index/’, true); // 使用open()方法以GET请求方式打开index页面 xhr.send(“name=root;pwd=123”); // 向打开的页面发送数据 }

此时访问 http://127.0.0.1:8000/ajax/ 页面,在页面上进入查看源代码模式,点击 NetWork 选项卡,这时在页面上的输入框中随便输入内容,点击 Ajax1 按钮,在Network选项卡下面的 Name 一栏可以看到请求的网址,在右边还有请求的头部(Headers)信息,请求页面的视图(Preview)信息,请求页面的源代码(Response)信息等。

为了更直观的说明请求页面的源代码信息,将ajax.html的代码修改如下:

function Ajax1() {

var xhr = new XMLHttpRequest(); // 创建Ajax对象 //xhr.open(‘GET’, ‘/index/’, true); // 使用open()方法以GET请求方式打开index页面 xhr.open(‘GET’, ‘/ajax_json/’, true); // 修改了这里 xhr.send(“name=root;pwd=123”); // 向打开的页面发送数据 }

再次刷新页面 http://127.0.0.1:8000/ajax/ 后,点击页面上的按钮,在Network选项卡下的 Response 子选项中,就可以看到ajax_json页面的源代码,该源代码正是在 views.py 文件中的 ajax_json() 函数中定义的 ret 字典内容。

现在已经能看到页面源代码的内容了,可以使用 XMLHttpRequest 对象的responseText属性获取源代码内容,例如下面修改后的ajax.html代码所示:

function Ajax1() {

var xhr = new XMLHttpRequest(); // 创建Ajax对象 //xhr.open(‘GET’, ‘/index/’, true); // 使用open()方法以GET请求方式打开index页面 xhr.onreadystatechange = function(){

// 这个函数的位置不重要,只要xhr的readyState属性值发生变化,就执行这个函数 if(xhr.readyState == 4){

// readyState等于4表示接收完毕 console.log(xhr.responseText); // 通过xhr的responseText属性获取返回值 // var xml = xhr.responseXML // responseXML 返回的是xml对象,可以用这个对象根据节点取值 };

};

xhr.open(‘GET’, ‘/ajax_json/’, true); // 使用open()方法以GET请求方式打开ajax_json页面 xhr.send(“name=root;pwd=123”); // 向打开的页面发送数据 }

还可以将获取的源代码转化为JSON格式,方法如下:

var obj = JSON.parse(xhr.responseText); // 将获取的源代码转化为JSON格式

console.log(obj);

还可以设置请求头,还可以拿到csrf_token的值来发送:

xhr.setRequestHeader(‘k1′,’v1’) // 还可以设置请求头

1.3、 发送数据

把xhr.open()方法的请求方式改为POST后,xhr.send()的数据可以发送到后台,这里需要在ajax请求中设置请求头才能发送过去。下面的 ajax.html 代码中的ajax请求改成了POST,并设置了请求头,如下面代码所示:

function Ajax1() {

var xhr = new XMLHttpRequest(); // 创建Ajax对象 xhr.onreadystatechange = function(){

// 这个函数的位置不重要,只要xhr的readyState属性值发生变化,就执行这个函数 if(xhr.readyState == 4){

// readyState等于4表示接收完毕 var obj = JSON.parse(xhr.responseText); // 将获取的源代码转化为JSON格式 console.log(obj);

};

};

xhr.open(‘POST’, ‘/ajax_json/’, true); // 这里使用 POST请求 xhr.setRequestHeader(‘k1′,’v1’); // 还可以设置请求头 // 设置下面的请求头后send方法发送的数据后端才能接收到 xhr.setRequestHeader(‘Content-Type’, ‘application/x-www-form-urlencoded; charset-UTF-8’);

xhr.send(“name=root;pwd=123”); // 向后台发送数据 }

要让xhr.send() 方法的数据能发送到后台,必须要在ajax请求中设置下面这个请求头信息,这个请求头信息由Django框架决定的:

xhr.setRequestHeader(‘Content-Type’, ‘application/x-www-form-urlencoded; charset-UTF-8’);

在views.py 文件中的 ajax_json()函数中增加一条输出语句,输出POST请求的数据,代码如下所示:

def ajax_json(request):

print(request.POST) # 主要添加这一条语句

ret = {‘status’: True, ‘data’: None}

import json

return HttpResponse(json.dumps(ret))

现在刷新 http://127.0.0.1:8000/ajax/ 页面,点击页面上的按钮,后台输出ajax请求中 xhr.send()方法发送的数据,如下所示:

在ajax.html 的代码中,var xhr = new XMLHttpRequest(); 是在创建ajax对象,其实还可以这样 var xhr = window.XMLHttpRequest();此外 alert 方法前面也可以加 window,例如 window.alert(“123”),在DOM中,对象都是在window下。要创建 ajax 对象还可以这样操作 var xhr = window[‘XMLHttpRequest’],当然也可以直接这样使用 aler方法 window[‘alert’](“hello”)。

ajax对低版本的浏览器要进行兼容,可在创建 ajax 对象前,先进行判断,以便于创建低版本的浏览器的 ajax 请求。可将 ajax.hmtl文件中的 script 标签中的代码修改如下:

function getXHR(){ // 根据不同的浏览器对象,创建不同的 ajax 对象 var xhr = null;

if(XMLHttpRequest){

xhr = new XMLHttpRequest();

}else{

xhr = new ActiveXObject(“Microsoft.XMLHTTP”);

}

return xhr;

}

function Ajax1() {

var xhr = getXHR();

xhr.onreadystatechange = function(){

// 这个函数的位置不重要,只要xhr的readyState属性值发生变化,就执行这个函数 if(xhr.readyState == 4){

// readyState等于4表示接收完毕 //console.log(xhr.responseText); // 通过xhr的responseText属性获取返回值 // var xml = xhr.responseXML // responseXML 返回的是xml对象,可以用这个对象根据节点取值 var obj = JSON.parse(xhr.responseText); // 将获取的源代码转化为JSON格式 console.log(obj);

};

};

xhr.open(‘POST’, ‘/ajax_json/’, true); // 使用open()方法以GET请求方式打开ajax_json页面 xhr.setRequestHeader(‘k1′,’v1’); // 还可以设置请求头 // 设置请求头后send方法发送的数据后端才能接收到 xhr.setRequestHeader(‘Content-Type’, ‘application/x-www-form-urlencoded; charset-UTF-8’);

xhr.send(“name=root;pwd=123”); // 向打开的页面发送数据 }

2、 jQuery 的 Ajax

jQuery 的 Ajax 与原生的 Ajax 没有多大的区别,调用 ajax 的方法是 $.ajax({})。在用 $.ajax({}) 发送请求时,内部有

success对应的函数,该函数有貌似有3个参数,其中第2还是第3个参数就是 XMLHttpRequest 对象,所以要用原生的 ajax 返回值,可以访问第2个还是第3个参数即可实现。例如下面这样:

$.ajax({

success: function (arg, a1, a2) {

}

})

3、 伪ajax(iframe标签)

iframe标签有一功能是,这个标签的src属性指向某个网站,当运行含有 iframe 标签的HTML代码后,在页面上就能看到 iframe 标签的src属性指向的网站页面。结合在 ajax 请求中就是,当在页面的输入框中输入一个网址,通过jQuery 的方法将这个输入框的内容传给 iframe 标签的 src 属性,再给提交按钮绑定一个事件,这样就能做到在我们的页面上访问输入框中的网址,但页面并没有刷新。例如下面的修改后的 ajax.html 代码所示,在这次的 ajax.html 代码要引入 jquery 文件。详细代码如下所示:

位置:michael_05\templates\ajax.html

function getXHR(){

var xhr = null;

if(XMLHttpRequest){

xhr = new XMLHttpRequest();

}else{

xhr = new ActiveXObject(“Microsoft.XMLHTTP”);

}

return xhr;

}

function Ajax1() {

var xhr = getXHR();

xhr.onreadystatechange = function(){

// 这个函数的位置不重要,只要xhr的readyState属性值发生变化,就执行这个函数 if(xhr.readyState == 4){

// readyState等于4表示接收完毕 //console.log(xhr.responseText); // 通过xhr的responseText属性获取返回值 // var xml = xhr.responseXML // responseXML 返回的是xml对象,可以用这个对象根据节点取值 var obj = JSON.parse(xhr.responseText); // 将获取的源代码转化为JSON格式 console.log(obj);

};

};

xhr.open(‘POST’, ‘/ajax_json/’, true); // 使用open()方法以GET请求方式打开ajax_json页面 xhr.setRequestHeader(‘k1′,’v1’); // 还可以设置请求头 // 设置请求头后send方法发送的数据后端才能接收到 xhr.setRequestHeader(‘Content-Type’, ‘application/x-www-form-urlencoded; charset-UTF-8’);

xhr.send(“name=root;pwd=123”); // 向打开的页面发送数据 }

function iframeRequest() {

// 更新 iframe 标签的 src 属性的值为输入框的值 var url = $(“#url”).val();

$(“#ifm”).attr(‘src’, url);

}

有了上面的 iframe 标签的功能介绍,怎样才能用 iframe 做伪Ajax请求呢?先来看下面这段在ajax.html文件中HTML代码:

在这段HTML代码中,body 内部有一个form标签,以 POST 方式提交请求,跳转的地址是 /ajax_json/ ,但是在这个 form 标签中有一个属性 target=”ifrm1″,这个 target属性的值与下面form标签内部的 iframe 标签内的 name属性值一样。这样写上后就表示提点“Form提交”时,不跳转到 /ajax_json/ 页面,而是直接在当前页面的 iframe 框内返回目标页面的内容。当在页面的两个 input框中输入相应内容点击“Form提交”时,页面上的 iframe 框返回目标页面的内容,后台同时也在输出两个输入框的内容。这里后台的代码仍然是前面写在 app01\views.py 文件中的 ajax()函数和ajax_json() 函数,这两个函数的代码在这里不在示出。后台的输出内容如下所示:

注意这里的输出内容,要取决于在页面上的两个输入框中填入的内容。

4、 原生Ajax、jQuery的Ajax、伪Ajax的使用时机

在做开发时,允许使用jQuery时,就使用jQuery的Ajax。

情景1:如果发送的是【普通数据(key-value)】 -> 推荐使用顺序:jQuery, XMLHttpRequest, iframe

前面的伪ajax中,使用的 iframe 标签,当点击请求提交按钮后,返回的数据是在页面的 iframe 标签框内,不能直接获取其内容,查看页面的源代码可知 iframe 标签下面嵌套了 document,相当于是嵌套了页面,要获取其下面的文本内容,还需要做一些特殊操作才行,具体代码如下所示:

位置:michael_05\templates\ajax.html

function submitForm() {

$(“#ifrm1”).load(function () {

//console.log($(“#ifrm1”).contents()); var s = $(“#ifrm1”).contents().find(“body”).text(); // 获取 iframe 标签的值 var obj = JSON.parse(s); // 拿到值后转化为 json 对象,就可以做各种操作 console.log(s)

})

}

/*function iframeLoad() {console.log(123);}*/

在这段代码中,iframe 标签本身有一个 onload 属性,可以给该属性绑定一个事件,这里假设绑定的是 iframeLoad() 事件,在每次刷新页面后,在 Console 可以看这个 iframeLoad未定义的提示,这是因为js代码写在下面的原因。可以对提交按钮绑定onclick事件submitForm(),在这个 submitForm() 事件中去执行 iframe 的事件。后台的ajax()函数和ajax_json()函数的代码可保持不变,运行上面的 HTML代码后,在输入框中填入相应的值点击提交,在 Console 端可以看到返回页面的文本信息。

(下一篇博文继续本节内容)