14 Django -- ModelForm组件以及同源与跨域
目录
ModelForm操作
class Meta下常用参数:
model = models.Book # 对应的Model中的类
fields = "__all__" # 字段,如果是__all__,就是表示列出所有的字段
exclude = None # 排除的字段
labels = None # 提示信息
help_texts = None # 帮助提示信息
widgets = None # 自定义插件
error_messages = None # 自定义错误信息
error_messages = {
'title':{'required':'不能为空',...} #每个字段的所有的错误都可以写}
ModelForm的验证
与普通的Form表单验证类型类似,ModelForm表单的验证在调用is_valid() 或访问errors 属性时隐式调用。
可以像使用Form类一样自定义局部钩子方法和全局钩子方法来实现自定义的校验规则。
如果我们不重写具体字段并设置validators属性的话,ModelForm是按照模型中字段的validators来校验的.
save()方法
每个ModelForm还具有一个save()方法。 这个方法根据表单绑定的数据创建并保存数据库对象。
ModelForm的子类可以接受现有的模型实例作为关键字参数instance;如果提供此功能,则save()将更新(update)该实例。 如果没有提供,save() 将创建(create)模型的一个新实例:
添加/编辑书籍 示例
models.py
需要定义str双下方法。
from django.db import models
# Create your models here.
class Author(models.Model):
"""
作者表
"""
name = models.CharField(max_length=32)
age = models.IntegerField()
auth = models.OneToOneField(to='AuthorDetail',on_delete=models.CASCADE)
def __str__(self):
return self.name
class AuthorDetail(models.Model):
"""
作者详细信息表
"""
birthday = models.DateField()
telephone = models.CharField(max_length=11)
addr = models.CharField(max_length=64)
class Publish(models.Model):
"""
出版社表
"""
name = models.CharField(max_length=32)
city = models.CharField(max_length=32)
def __str__(self):
return self.name
class Book(models.Model):
"""
书籍表
"""
title = models.CharField(max_length=32)
publishdate = models.DateField()
price = models.DecimalField(max_digits=5, decimal_places=2)
pub = models.ForeignKey(to='Publish', on_delete=models.CASCADE)
authors=models.ManyToManyField('Author',)
def __str__(self):
return self.title
views.py
from django.shortcuts import render,HttpResponse,redirect
from app01 import models
from django import forms
class BookModelForm(forms.ModelForm):
"""使用modelform组件创建"""
class Meta:
model = models.Book # 会将Book表所有的字段生成form字段,并且可以读取所有的数据(关联的其他表也会被读取)
fileds = '__all__' # 全部
# fileds = ['title', 'publishs'] # 指定部分字段
# exclude = ['title',] # 排除部分字段
# 设置标签别名
labels={
'title': '书名',
'publishdate': '出版日期',
'price': '价格',
'pub': '出版社',
'authors': '作者',
}
# 设置插件
widgets={
'publishdate':forms.DateInput(attrs={'type':'date'}),
}
# 设置错误
error_messages={
'publishdate': {'required': '不能为空!'},
'price': {'required': '不能为空!'},
'pub': {'required': '不能为空!'},
'authors': {'required': '不能为空!'},
}
#def clean_title(self):
"""局部钩子"""
# pass
#def clean(self):
"""全局钩子"""
#pass
# 批量添加属性样式
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
for field_name, field in self.fields.items(): #orderdict(('username',charfield对象))
field.widget.attrs.update({'class':'form-control'})
def addbook(request):
if request.method == 'GET':
book_model_obj = BookModelForm()
return render(request,'addbook.html',{'book_model_obj':book_model_obj})
else:
book_model_obj = BookModelForm(request.POST) # 将数据传入对象中
print(request.POST)
if book_model_obj.is_valid():
# 校验成功
print(book_model_obj.cleaned_data)
book_model_obj.save() # 将所有提交的数据保存到Book表
return redirect('showbooks')
else:
# 没有通过校验,会将错误信息传入对象的errors中
return render(request,'addbook.html',{'book_model_obj':book_model_obj})
def editbook(request,id):
old_obj = models.Book.objects.filter(id = id)
if request.method == 'GET':
# 传model对象,不能传queryset对象
book_model_obj = BookModelForm(instance=old_obj.first())
else:
book_model_obj = BookModelForm(request.POST,instance=old_obj.first()) # 将数据传入对象中
print(request.POST)
if book_model_obj.is_valid():
# 校验成功
print(book_model_obj.cleaned_data)
book_model_obj.save() # 将修改提交的数据保存到Book表;有instance,是update;没有是create
return redirect('showbooks')
else:
# 没有通过校验,会将错误信息传入对象的errors中
return render(request,'editbook.html',{'book_model_obj':book_model_obj})
editbook.html/addbook.html
<form action="" method="post" novalidate>
{% csrf_token %}
{% for field in book_model_obj %}
<label for='{{ field.id_for_label }}'>{{ field.lable }}</label>
{{ field }}
<span class=''>{{ field.errors.0 }} </span>
{% endfor %}
<div class="modal-footer">
<button type="submit" class="btn btn-primary">提交</button>
</div>
</form>
同源和跨域
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。
同源策略,它是由Netscape提出的一个著名的安全策略。现在所有支持JavaScript 的浏览器都会使用这个策略。
当一个浏览器的两个tab页中分别打开来 百度和谷歌的页面当浏览器的百度tab页执行一个脚本的时候会检查这个脚本是属于哪个页面的,即检查是否同源,只有和百度同源的脚本才会被执行。如果非同源,那么在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问。
同源: 同一协议、ip地址、端口号
示例:简单的同源跨域
1.创建一个pro1的Django,端口号8000
views.py文件
def index(request):
"""
同源跨域
:param request:
:return:
"""
if request.method == 'GET':
return render(request, 'index.html')
else:
ret = {'name': 'liu', 'age': 20}
return JsonResponse(ret)
index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>同源</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css">
</head>
<body>
<div>
<form action="" >
<div>
<label for="username">用户名:</label>
<input type="text" id="username" name="username" class="form-group">
</div>
<div>
<label for="password">密码:</label>
<input type="password" id="password" name="password" class="form-group">
</div>
<button type="button" class="btn btn-success" id="submit">提交</button>
</form>
</div>
</body>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
<script>
$('#submit').click(function () {
var username = $(':text').val();
var password = $(':password').val();
console.log(username);
$.ajax({
url:'http://127.0.0.1:8001/index/',
type:'post',
success:function (res) {
console.log(res)
}
})
})
</script>
</html>
2.创建另一个pro2 的Django,端口号8001
views.py
def index(request):
ret = {'username':'yan','password':123}
res = JsonResponse(ret)
res['Access-Control-Allows-Origin']='http://http://127.0.0.1:8000' # 允许这个源拿取我的数据
res['Access-Control-Allows-Origin']='*' # 允许全部源
return res
CORS:HTTP访问控制(跨站资源共享)
CORS需要浏览器和服务器同时支持。 目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
浏览器将CORS请求分成两类:**简单请求**(simple request)和**非简单请求**(not-so-simple request)
简单请求:
ajax请求需同时满足下面两大条件:
(1) 请求方法是以下三种方法之一:(也就是说如果你的请求方法是什么put、delete等肯定是非简单请求)
HEAD
GET
POST
(2)HTTP的头信息不超出以下几种字段:(如果比这些请求头多,那么一定是非简单请求)
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain,也就是说,如果你发送的application/json格式的数据,那么肯定是非简单请求,vue的axios默认的请求体信息格式是json的,ajax默认是urlencoded的。
简单请求设置:
def index(request):
ret = {'username':'yan','password':123}
res = JsonResponse(ret)
res['Access-Control-Allows-Origin']='http://127.0.0.1:8000' # 允许这个源拿取我的数据
res['Access-Control-Allows-Origin']='*' # 允许全部
非简单请求:
不满足上面的条件,就属于非简单请求。
例如:前端用put方式发送请求
pro1 的index.html
<script>
$('#submit').click(function () {
var username = $(':text').val();
var password = $(':password').val();
console.log(username);
$.ajax({
url:'http://127.0.0.1:8001/index/',
type:'put',
success:function (res) {
console.log(res)
}
})
})
</script>
pro2 --- views.py
def index(request):
ret = {'username':'yan','password':123}
res = JsonResponse(ret)
res['Access-Control-Allows-Origin']='http://127.0.0.1:8000' # 允许这个源拿取我的数据
res['Access-Control-Allows-Methods']='PUT' # 允许put方式请求
return res
例如:前端用Json发送数据
pro1 --- index.html
<script>
$('#submit').click(function () {
var username = $(':text').val();
var password = $(':password').val();
console.log(username);
$.ajax({
url:'http://127.0.0.1:8001/index2/',
type:'post',
contentType:'application/json',
data:JSON.stringify({username:username, password:password}),
success:function (res) {
console.log(res)
}
})
})
</script>
pro2 --- views.py 文件
def index(request):
if request.method == 'POST':
data = json.loads(request.body)
# bytes数据类型可以直接反序列化
print(data, type(data))
ret = {'username':'yan','password':123}
res = JsonResponse(ret)
res['Access-Control-Allows-Origin']='http://127.0.0.1:8000' # 允许这个源拿取我的数据
res['Access-Control-Allow-Headers']='content-type'
#发送来的请求里面的请求头里面的内容可以定义多个,后端需要将头配置上才能访问,允许content-type所有的方式
return res
两种请求方式的处理:
区别:
简单请求:一次请求
非简单请求:两次请求,在发送数据之前会先发一次options请求用于做“预检”,只有“预检”通过后才再发送一次请求用于数据传输。
OPTIONS请求方式:
“预检”其实做检查,检查如果通过则允许传输数据,检查不通过则不再发送真正想要发送的消息。
如何预检:
1. 如果复杂请求是PUT等请求,则服务端需要设置允许某请求,否则“预检”不通过,
Access-Control-Request-Method
2. 如果复杂请求设置了请求头,则服务端需要设置允许某请求头,否则“预检”不通过,
Access-Control-Request-Headers
总结:
支持跨域,简单请求
服务器设置响应头:Access-Control-Allow-Origin = '域名' 或 '*'
支持跨域,复杂请求
由于复杂请求时,首先会发送options“预检”请求,如果“预检”成功,则发送真实数据。
“预检”请求时,允许请求方式则需服务器设置响应头:Access-Control-Request-Method
“预检”请求时,允许请求头则需服务器设置响应头:Access-Control-Request-Headers