form与modelform 同源与跨域

form组件

1 生成html标签
2 保留原来的数据
3 校验用户提交的数据
如何给生成的标签加样式

利用Form自动生成HTML标签

第1步 在views.py文件中 导入from django import forms模块 定义一个类 继承与forms.Form  名字随意  继承forms.Form
第2步 修改forms.后面的  变更不同属性
第3步 修改forms.widgets.后面的 变更不同标签
第4步 实列化这个类 传给后端
第5步 写后端代码 
    {{对象.字段.label }}就是自定义名字 
    {{ 对象.字段 }} 就会生成标签
    {{ 对象.字段.errors.0 }}就会生成错误信息 第1个

form组件写法

from django import forms    
#生成用户名输入框 name对应username vluse对应用户输入的值
class UserInfo(forms.Form):
     username=forms.CharField(
        label='用户名:',
        initial="张三",#默认值
        widget=forms.widgets.TextInput,#变更不同标签
)
#生成密码输入框 name对应password vluse对应用户输入的值
    password=forms.CharField(
        label='密码:',
        widget=forms.widgets.PasswordInput(attrs={'class':'c1'}),---attrs里面可以添加属性
    )
#生成单选框 默认下拉框 name对应 sex vluse对应用户选择的值
    sex = forms.ChoiceField(
        choices=((1,'女'),(2,'男'),),#元祖套列表或元祖套元祖
        # widget=forms.RadioSelect,----修改成这样变成小圆点选择框
        # widget=forms.widgets.Select,---这是下拉框 默认就是
    )
#生成多选框 默认下拉框 name对应 hobby vluse对应用户选择的值
    hobby = forms.MultipleChoiceField(
        choices= ((1,'喝酒'),(2,'抽烟'),(3,'烫头')),
        # widget=forms.SelectMultiple,---这是下拉框 默认就是
        widget=forms.CheckboxSelectMultiple,---修改成小方块选择框
    )
#生成 选中框 name对应 remember_me 
    remember_me = forms.ChoiceField(
        label='记住我',#自定义标签名字
        widget=forms.CheckboxInput,
    )
#生成 日期选择框 name对应 bday 
    bday = forms.DateField(
        label='出生日期',
        # 日期类型必须加上日期属性
        widget=forms.DateInput(attrs={'type':'date'}),  


    )

view写法

    
def index(request):

    if request.method == 'GET':
        u_obj = UserInfo()#由form组件实列化出来的对象
        return render(request,'index.html',{'u_obj':u_obj})

    else:

        u_obj = UserInfo(request.POST)  #用户提交的数据给类 进行实例化 
        if u_obj.is_valid():#效验用户数据
            # {'username': 'a范德萨水电费', 'password': '1111'}
            print('正确数据',u_obj.cleaned_data)  #校验成功之后的数据cleaned_data

            return HttpResponse('ok')
        else:
            # print('错误信息',u_obj.errors)

            return render(request,'index.html',{'u_obj':u_obj})

html写法

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .c1{
            background-color: red;
        }
    </style>  
</head>
<body>

<h1>某某登录页面</h1>
{#{{ u_obj.errors }}#}全部报错信息

<form action="" method="post" novalidate>
    {% csrf_token %} 安全验证
{#    用户名: <input type="text" name="username">#}
{#    密码: <input type="password" name="password">#}

        {{ u_obj.username.label }} {{ u_obj.username }} {{ u_obj.username.errors.0 }}
    </div>
   <div>
        {{ u_obj.password.label }} {{ u_obj.password }} {{ u_obj.password.errors.0 }}
   </div>
    <div>
        {{ u_obj.r_password.label }} {{ u_obj.r_password }} {{ u_obj.r_password.errors.0 }}
   </div>


    <input type="submit">

</form>

利用form保留原来的数据

默认自动保留
password不会自动保留数据在widget里面添加render_value=True就可以强制留下
    r_password = forms.CharField(
        label='确认密码:',
        widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True),

    )
render_value=True强制留下

利用form校验用户提交的数据

简单效验用户数据

u_obj.password.errors.0 获得第一个错误数据

常见属性 
    min_length设置用户输入长度最小值
    max_length设置用户输入长度最大值
    required=True,  #不允许输入为空,值为False就可以为空
    
自定义错误信息后就不会显示英文的了
error_messages={
            'required':'不能为空',
            'min_length':'太短了!',
            'max_length':'太长了!',
        },

注意浏览器会自动帮你效验 要想浏览器不效验 在前端代码设置(novalidate)提示浏览器不要多管闲事
 <form action="" method="post" novalidate>

下面的代码实列

from组件

    username=forms.CharField(
        label='用户名:',
        # initial='张三'  #默认值
        # min_length=6, #最小值为6位
        # required=True,  #不允许为空,值为False就可以为空
        # widget=forms.widgets.TextInput,
        # max_length=8,

        error_messages={
            'required':'不能为空',
            'min_length':'太短了!',
            'max_length':'太长了!',
        },
        validators=[RegexValidator(r'^a','必须以a开头!'),RegexValidator(r'b$','必须以b结尾!')]
        # validators=[mobile_validate,]  #a范德萨水电费

    )
    

正则校验器RegexValidator验证器

第一步引入模块from django.core.validators import RegexValidator
第2步定义规则  可以定义多个规则
validators=[RegexValidator(r'^a','必须以a开头!'),RegexValidator(r'b$','必须以b结尾!')] 第一个参数是正则 第2个参数是错误信息

下面是代码实列

from django.core.validators import RegexValidator
username=forms.CharField(
        label='用户名:',
        initial='张三'  #默认值
        min_length=6, #最小值为6位
        required=True,  #不允许为空,值为False就可以为空
        widget=forms.widgets.TextInput,
        max_length=8,

        error_messages={
            'required':'不能为空',
            'min_length':'太短了!',
            'max_length':'太长了!',
        },
        validators=[RegexValidator(r'^a','必须以a开头!'),RegexValidator(r'b$','必须以b结尾!')]

自定义验证规则

当正则效验 与基础效验 满足不了 我们就需要  自定义验证规则了( ) 
第1步引入模块
    from django.core.exceptions import ValidationError
    import re
第2步定义一个函数 
    函数里面value是形参 传要验证的字符串
    mobile_re是利用re模块编译出来的正则规则
    利用ValidationError抛出错误
    
 def mobile_validate(value):#函数名随意
    mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
    if not mobile_re.match(value): 
        raise ValidationError('手机号码格式错误')
第3步 在类里面 验证类里面加入·
    validators=[定义的函数名,]

代码实列

form组件

from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError

import re
def mobile_validate(value):
    mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
    if not mobile_re.match(value): 
        raise ValidationError('手机号码格式错误') 
        
class UserInfo(forms.Form):

    username=forms.CharField(
        label='用户名:',
        # initial='张三'  #默认值
        # min_length=6, #最小值为6位
        # required=True,  #不允许为空,值为False就可以为空
        # widget=forms.widgets.TextInput,
        # max_length=8,

        error_messages={
            'required':'不能为空',
            'min_length':'太短了!',
            'max_length':'太长了!',
        },
        validators=[RegexValidator(r'^a','必须以a开头!'),RegexValidator(r'b$','必须以b结尾!')]
        validators=[mobile_validate,]  #a范德萨水电费

    )

    password=forms.CharField(
        label='密码:',
        widget=forms.widgets.PasswordInput(attrs={'class':'c1'},render_value=True),

    )

    r_password = forms.CharField(
        label='确认密码:',
        widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True),

    )

    # 局部钩子
    def clean_username(self):
        value = self.cleaned_data.get('username')
        if '666' in value:
            raise ValidationError('光喊666是不行的!')
        else:
            return value

    # 全局钩子
    def clean(self):
        password = self.cleaned_data.get('password')
        r_password = self.cleaned_data.get('r_password')

        if password == r_password:
            return self.cleaned_data
        else:
            # raise ValidationError('两次密码不一致!!!!')
            self.add_error('r_password','两次密码不一致~~~~') # 给单独的某个字段加错误信息


view

def index(request):
    if request.method == 'GET':
        u_obj = UserInfo()
        return render(request,'index.html',{'u_obj':u_obj})
    else:
效验
        u_obj = UserInfo(request.POST)  #{'username':'','password':'123'}
        print(u_obj.fields)
        # u_obj.is_valid()  #校验用户提交的数据,全部校验成功返回True,任意一个失败都返回False
        if u_obj.is_valid(): 
            # {'username': 'a范德萨水电费', 'password': '1111'}
            print('正确数据',u_obj.cleaned_data)  #校验成功之后的数据cleaned_data

            return HttpResponse('ok')
        else:
            # print('错误信息',u_obj.errors)

            return render(request,'index.html',{'u_obj':u_obj})

Hook钩子方法

效验顺序不同

局部钩子

第1步 我们在Fom类中定义 clean_字段() 方法,就能够实现对特定字段进行校验。
注意事项
    效验顺序是先效验字段里面的效验规则 然后在效验局部钩子 最后下一字段
    
实列写法(cleaned_data是效验以后得到的数据)
def clean_username(self):
        value = self.cleaned_data.get('username')
        if '666' in value:
            raise ValidationError('光喊666是不行的!')--错误信息
        else:
            return value--一定要返回不然会吧username对应的value剔除
钩子是写在类里面

代码实列

form组件

from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError

import re
def mobile_validate(value):
    mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
    if not mobile_re.match(value): 
        raise ValidationError('手机号码格式错误') 
        
class UserInfo(forms.Form):

    username=forms.CharField(
        label='用户名:',
        # initial='张三'  #默认值
        # min_length=6, #最小值为6位
        # required=True,  #不允许为空,值为False就可以为空
        # widget=forms.widgets.TextInput,
        # max_length=8,

        error_messages={
            'required':'不能为空',
            'min_length':'太短了!',
            'max_length':'太长了!',
        },
        validators=[RegexValidator(r'^a','必须以a开头!'),RegexValidator(r'b$','必须以b结尾!')]
        validators=[mobile_validate,]  #a范德萨水电费

    )

    password=forms.CharField(
        label='密码:',
        widget=forms.widgets.PasswordInput(attrs={'class':'c1'},render_value=True),

    )

    r_password = forms.CharField(
        label='确认密码:',
        widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True),

    )

    # 局部钩子 对某一字段效验
    def clean_username(self):#对username字段效验
        value = self.cleaned_data.get('username')
        if '666' in value:
            raise ValidationError('光喊666是不行的!')
        else:
            return value

    # 全局钩子 全部的字段 最后效验
    def clean(self):
        password = self.cleaned_data.get('password')
        r_password = self.cleaned_data.get('r_password')

        if password == r_password:
            return self.cleaned_data
        else:
            # raise ValidationError('两次密码不一致!!!!')
            self.add_error('r_password','两次密码不一致~~~~') # 给单独的某个字段加错误信息

view

def index(request):
    if request.method == 'GET':
        u_obj = UserInfo()
        return render(request,'index.html',{'u_obj':u_obj})
    else:
效验
        u_obj = UserInfo(request.POST)  #{'username':'','password':'123'}
        print(u_obj.fields)
        # u_obj.is_valid()  #校验用户提交的数据,全部校验成功返回True,任意一个失败都返回False
        if u_obj.is_valid(): 
            # {'username': 'a范德萨水电费', 'password': '1111'}
            print('正确数据',u_obj.cleaned_data)  #校验成功之后的数据cleaned_data

            return HttpResponse('ok')
        else:
            # print('错误信息',u_obj.errors)

            return render(request,'index.html',{'u_obj':u_obj})

全局钩子

句部钩子全部指行完 在执行全局  比如多个字段对比效验, 比如确认密码
函数名固定写法 
def clean(self):
    password = self.cleaned_data.get('password')
    r_password = self.cleaned_data.get('r_password')

    if password == r_password:#效验成功了L
        return self.cleaned_data---固定写法 cleaned_data源码里有
    else:
        # raise ValidationError('两次密码不一致!!!!')不用因为给全局了
        self.add_error('r_password','两次密码不一致~~~~') # 给单独的某个字段加错误信息

代码实列

from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError

import re
def mobile_validate(value):
    mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
    if not mobile_re.match(value): 
        raise ValidationError('手机号码格式错误') 
        
class UserInfo(forms.Form):

    username=forms.CharField(
        label='用户名:',
        # initial='张三'  #默认值
        # min_length=6, #最小值为6位
        # required=True,  #不允许为空,值为False就可以为空
        # widget=forms.widgets.TextInput,
        # max_length=8,

        error_messages={
            'required':'不能为空',
            'min_length':'太短了!',
            'max_length':'太长了!',
        },
        validators=[RegexValidator(r'^a','必须以a开头!'),RegexValidator(r'b$','必须以b结尾!')]
        validators=[mobile_validate,]  #a范德萨水电费

    )

    password=forms.CharField(
        label='密码:',
        widget=forms.widgets.PasswordInput(attrs={'class':'c1'},render_value=True),

    )

    r_password = forms.CharField(
        label='确认密码:',
        widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True),

    )

    # 局部钩子
    def clean_username(self):
        value = self.cleaned_data.get('username')
        if '666' in value:
            raise ValidationError('光喊666是不行的!')
        else:
            return value

    # 全局钩子
    def clean(self):
        password = self.cleaned_data.get('password')
        r_password = self.cleaned_data.get('r_password')

        if password == r_password:
            return self.cleaned_data
        else:
            # raise ValidationError('两次密码不一致!!!!')
            self.add_error('r_password','两次密码不一致~~~~') # 给单独的某个字段加错误信息



def index(request):
    if request.method == 'GET':
        u_obj = UserInfo()
        return render(request,'index.html',{'u_obj':u_obj})
    else:
效验
        u_obj = UserInfo(request.POST)  #{'username':'','password':'123'}
        print(u_obj.fields)
        # u_obj.is_valid()  #校验用户提交的数据,全部校验成功返回True,任意一个失败都返回False
        if u_obj.is_valid(): 
            # {'username': 'a范德萨水电费', 'password': '1111'}
            print('正确数据',u_obj.cleaned_data)  #校验成功之后的数据cleaned_data

            return HttpResponse('ok')
        else:
            # print('错误信息',u_obj.errors)

            return render(request,'index.html',{'u_obj':u_obj})

批量添加属性样式

   # 批量添加属性样式
    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'})

form组件总结

1.只要定义好了form组价 就可以快速生成HTML标签,还可以给标签加属性样式

2.form组价默认保留用户提交的数据

3.form组件可以效验用户提交的数据 支持以下
	自带的效验规则 不可以为空,最大最小长度
    正则效验
    自定义效验规则
    局部钩子(针对某字段)和全局钩子(最后的效验)

基于form的实战

modelform组件

  通常在Django项目中,我们编写的大部分都是与Django 的模型紧密映射的表单。 举个例子,你也许会有个Book 模型,并且你还想创建一个form表单用来添加和编辑书籍信息到这个模型中。 在这种情况下,在form表单中定义字段将是冗余的,因为我们已经在模型中定义了那些字段。

  基于这个原因,Django 提供一个辅助类来让我们可以从Django 的模型创建Form,这就是ModelForm。

  modelForm定义

    form与model的终极结合,会根据你model中的字段转换成对应的form字段,并且并你生成标签等操作。

class BookModelForm(forms.ModelForm):
    # 优先级高
    # title = forms.CharField(
    #     label='书名2',
    # )

    class Meta:
        model = models.Book
        #fields='__all__'
        fields=['title','publishs'] 
        labels={
            'title':'书名',
            'price':'价格',
            'publishDate':'出版日期',
            'publishs':'出版社',
            'authors':'作者',
        }

        widgets={
            'publishDate':forms.TextInput(attrs={'type':'date'}),
            # 'publishDate2':forms.TextInput(attrs={'type':'date'}),
        }

        error_messages={
            'title':{'required':'书名不能为空!',},
            'price':{'required':'不能为空!',},
            'publishDate':{'required':'不能为空!',},
            'publishs':{'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():
            field.widget.attrs.update({'class':'form-control'})

modelform组件怎么用

第一步定义一个类 名字随便取 最好有含义 列入BookModelForm
类里面定义
    class Meta:
            model = models.Book
            #fields='__all__' 拿到所有字段 生成from字段

modeform组件

class BookModelForm(forms.ModelForm):#类目随便取
    # 优先级高 自定义名字
    # title = forms.CharField(
    #     label='书名2',
    # )

    class Meta:
        model = models.Book#找到book表
        fields='__all__' #拿到book表所有字段 生成from字段
        #fields=['title','publishs']    
        labels={
            'title':'书名',
            'price':'价格',
            'publishDate':'出版日期',
            'publishs':'出版社',
            'authors':'作者',
        }

        
        
        widgets={
            'publishDate':forms.TextInput(attrs={'type':'date'}),
            # 'publishDate2':forms.TextInput(attrs={'type':'date'}),
        }

        error_messages={
            'title':{'required':'书名不能为空!',},
            'price':{'required':'不能为空!',},
            'publishDate':{'required':'不能为空!',},
            'publishs':{'required':'不能为空!',},
            'authors':{'required':'不能为空!',},
        }


    #
    # def clean_title(self): 钩子
    #     pass
    # def clean(self):
    #     pass

    def __init__(self,*args,**kwargs):#还是和from一样给所有字段添加样式
        super().__init__(*args,**kwargs)
        for field_name,field in self.fields.items():
            field.widget.attrs.update({'class':'form-control'})

view

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)  #{'title':'xx'}
        if book_model_obj.is_valid():#效验
            print(book_model_obj.cleaned_data)
            """
            data = book_obj.cleaned_data
            authors = data.pop('authors')
           
            new_book = models.Book.objects.create(
                **data
            )
            new_book.authors.add(*authors)
            """
            
            book_model_obj.save()#一个save带表上面3部

            return redirect('showbooks')
        else:
            return render(request,'addbook.html',{'book_model_obj':book_model_obj})

htm


ModelForm的验证

    与普通的Form表单验证类型类似,ModelForm表单的验证在调用is_valid() 或访问errors 属性时隐式调用。

    我们可以像使用Form类一样自定义局部钩子方法和全局钩子方法来实现自定义的校验规则。

    如果我们不重写具体字段并设置validators属性的话,ModelForm是按照模型中字段的validators来校验的。

save()方法

 每个ModelForm还具有一个save()方法。 这个方法根据表单绑定的数据创建并保存数据库对象。 ModelForm的子类可以接受现有的模型实例作为关键字参数instance;如果提供此功能,则save()将更新该实例。 如果没有提供,save() 将创建模型的一个新实例:

view

def edit_book(request,n):

    book_obj = models.Book.objects.filter(pk=n).first()
    if request.method == 'GET':
        # all_authors = models.Author.objects.all() #
        # all_publish = models.Publish.objects.all()

        form = BookForm(instance=book_obj)

        return render(request,'edit_book.html',{'form':form,'n':n}) #传递的这个n参数是给form表单提交数据的是的action的url用的,因为它需要一个参数来识别是更新的哪条记录

    else:
        form = BookForm(request.POST,instance=book_obj) #必须指定instance,不然我们调用save方法的是又变成了添加操作
        if form.is_valid():
            form.save()
            return redirect('show')
        else:
            return render(request,'edit_book.html',{'form':form,'n':n})

html文件

{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="{% static 'bootstrap-3.3.0-dist/dist/css/bootstrap.min.css' %}">
</head>
<body>

<h1>编辑页面</h1>
<div class="container-fluid">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <form action="{% url 'edit_book' n %}" novalidate method="post">
                {% csrf_token %}
                {% for field in form %}
                    <div class="form-group">
                        <label for="{{ field.id_for_label }}">{{ field.label }}</label>
                        {{ field }}
                        <span class="text-danger">{{ field.errors.0 }}</span>
                    </div>
                {% endfor %}

                <div class="form-group">
                    <input type="submit" class="btn btn-primary pull-right">
                </div>

            </form>

        </div>
    </div>
</div>


</body>
<script src="{% static 'bootstrap-3.3.0-dist/dist/jQuery/jquery-3.1.1.js' %}"></script>
<script src="{% static 'bootstrap-3.3.0-dist/dist/js/bootstrap.min.js' %}"></script>
</html>

modeform组件的总结

会根据你model中的字段转换成对应的form字段,并且并你生成标签

mode可以做的 modeform一样可以做

基于modeform的实战

同源和跨域

理解什么是同源,跨域

同源:域名、协议、端口完全相同。
跨域:域名、协议、端口有其中的一样不同。

什么是同源策略

  • 同协议、同domain(或ip)、同端口,视为同一个域,一个域内的脚本仅仅具有本域内的权限。
  • 理解:本域脚本只能读写本域内的资源,而无法访问其它域的资源。这种安全限制称为同源策略。  

前端解决跨域访问的常用方式

  1.jsonp

  2.iframe 元素会创建包含另外一个文档的内联框架(即行内框架)

  3.代理:如vue-cli项目中的config/index.js文件中的proxyTable设置所要跨域访问的地址

同源机制:域名、协议、端口号相同的同源

简单请求
(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的。
    

    

解决跨域

    #vue.js axios -- $.ajax
支持跨域,简单请求

    服务器设置响应头:Access-Control-Allow-Origin = '域名' 或 '*'

  支持跨域,复杂请求

    由于复杂请求时,首先会发送“预检”请求,如果“预检”成功,则发送真实数据。

      “预检”请求时,允许请求方式则需服务器设置响应头:Access-Control-Request-Method

      “预检”请求时,允许请求头则需服务器设置响应头:Access-Control-Request-Headers
    res['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8001'
    res['Access-Control-Allow-Headers'] = 'content-type' 所有的content-type
    # res['Access-Control-Allow-Methods'] = 'PUT'
    # res['Access-Control-Allow-Origin'] = '*'

简单请求跨域

同源机制:域名、协议、端口号相同的同源

简单请求
(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的。
    

    

我们创建两个django项目,第一个叫做s1,一个叫做s2,s1用8000端口启动,s2用8001端口启动

  s1项目的index.html文件内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h2>s1的首页</h2>
<button id="btn">Ajax请求</button>


<script src="https://cdn.bootcss.com/jquery/3.4.0/jquery.js"></script>
<script>
    $('#btn').click(function () {
        $.ajax({
            //url:'/books/', 访问自己服务器的路由,同源(ip地址、协议、端口都相同才是同源)
            url:'http://127.0.0.1:8001/books/', //访问其他服务器的路由,不同源,那么你可以访问到另外一个服务器,但是浏览器将响应内容给拦截了,并给你不同源的错误:Access to XMLHttpRequest at 'http://127.0.0.1:8001/books/' from origin 'http://127.0.0.1:8000' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header has a value 'http://127.0.0.1:8002' that is not equal to the supplied origin.
            #并且注意ip地址和端口后面是一个斜杠,如果s2的这个url没有^books的^符号,那么可以写两个//。
        type:'get',
            success:function (response) {
                console.log(response);
            }

        })
    })


</script>
</body>
</html>

url

from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^index/', views.index),
    url(r'^books/', views.books),

view

from django.shortcuts import render,HttpResponse
from django.http import JsonResponse
# Create your views here.

def index(request):
    return render(request,'index.html')

def books(request):

    # return JsonResponse(['西游记','三国演义','水浒传'],safe=False)
    obj = JsonResponse(['西游记','三国演义','水浒传'],safe=False)
    return obj

 s2项目的urls.py内容如下:

from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^books/', views.books),
]

view

from django.shortcuts import render
from django.http import JsonResponse
# Create your views here.
def books(request):

    # return JsonResponse(['西游记2','三国演义2','水浒传2'],safe=False)

    obj = JsonResponse(['西游记2','三国演义2','水浒传2'],safe=False)
    #下面这个响应头信息是告诉浏览器,不要拦着,我就给它,"*"的意思是谁来请求我,我都给
    # obj["Access-Control-Allow-Origin"] = "*"
    obj["Access-Control-Allow-Origin"] = "http://127.0.0.1:8000" #只有这个ip和端口来的请求,我才给他数据,其他你浏览器帮我拦着
    return obj

CORS通信实现跨域

  

  CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。

  整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

  因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

  浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。

  只要同时满足以下两大条件,就属于简单请求

(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的。

  凡是不同时满足上面两个条件,就属于非简单请求

  我们改一下上一节的s1项目的index.html文件中的ajax里面的内容:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h2>s1的首页</h2>
<button id="btn">Ajax请求</button>


<script src="https://cdn.bootcss.com/jquery/3.4.0/jquery.js"></script>
<script>
    $('#btn').click(function () {
        $.ajax({
            url:'http://127.0.0.1:8001/books/',
            type:'post',
            contentType:'application/json',//非简单请求,会报错:Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.
            data:JSON.stringify({
               a:'1'
            }),
            //headers:{b:2},
            success:function (response) {
                console.log(response);
            }

        })
    })


</script>
</body>
</html>

  img

  浏览器对这两种请求的处理,是不一样的。

* 简单请求和非简单请求的区别?

   简单请求:一次请求
   非简单请求:两次请求,在发送数据之前会先发一次请求用于做“预检”,只有“预检”通过后才再发送一次请求用于数据传输。
* 关于“预检”

- 请求方式:OPTIONS
- “预检”其实做检查,检查如果通过则允许传输数据,检查不通过则不再发送真正想要发送的消息
- 如何“预检”
     => 如果复杂请求是PUT等请求,则服务端需要设置允许某请求,否则“预检”不通过
        Access-Control-Request-Method
     => 如果复杂请求设置了请求头,则服务端需要设置允许某请求头,否则“预检”不通过
        Access-Control-Request-Headers

    看图:

    img

    

  s2项目的views.py内容如下:

from django.shortcuts import render
from django.http import JsonResponse
# Create your views here.
def books(request):

    # return JsonResponse(['西游记2','三国演义2','水浒传2'],safe=False)

    obj = JsonResponse(['西游记2','三国演义2','水浒传2'],safe=False)
    # obj["Access-Control-Allow-Origin"] = "*"
    obj["Access-Control-Allow-Origin"] = "http://127.0.0.1:8000"
    print(request.method)
    #处理预检的options请求,这个预检的响应,我们需要在响应头里面加上下面的内容
    if request.method == 'OPTIONS':
        # obj['Access-Control-Allow-Headers'] = "Content-Type" #"Content-Type",首字母小写也行
        # obj['Access-Control-Allow-Headers'] = "content-type" #"Content-Type",首字母小写也行。这个content-type的意思是,什么样的请求体类型数据都可以,我们前面说了content-type等于application/json时,是复杂请求,复杂请求先进行预检,预检的响应中我们加上这个,就是告诉浏览器,不要拦截
        obj['Access-Control-Allow-Headers'] = "content-type,b"  #发送来的请求里面的请求头里面的内容可以定义多个,后端需要将头配置上才能访问
    return obj

  支持跨域,简单请求

    服务器设置响应头:Access-Control-Allow-Origin = '域名' 或 '*'

  支持跨域,复杂请求

    由于复杂请求时,首先会发送“预检”请求,如果“预检”成功,则发送真实数据。

      “预检”请求时,允许请求方式则需服务器设置响应头:Access-Control-Request-Method

      “预检”请求时,允许请求头则需服务器设置响应头:Access-Control-Request-Headers

  

  关于请求方式的跨域问题及解决方法:

  s1的index.html文件内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<h2>s1的首页</h2>
<button id="btn">Ajax请求</button>


<script src="https://cdn.bootcss.com/jquery/3.4.0/jquery.js"></script>
<script>
    $('#btn').click(function () {
        $.ajax({
            url:'http://127.0.0.1:8001/books/',
            //type:'delete',
            //type:'post',
            type:'put',
            contentType:'application/json',//非简单请求,会报错:Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.
            data:JSON.stringify({
               a:'1'
            }),
            headers:{b:'2'},
            success:function (response) {
                console.log(response);
            }

        })
    })


</script>
</body>
</html>

  s2项目的views.py内容如下:

from django.shortcuts import render
from django.http import JsonResponse
# Create your views here.
def books(request):

    # return JsonResponse(['西游记2','三国演义2','水浒传2'],safe=False)

    obj = JsonResponse(['西游记2','三国演义2','水浒传2'],safe=False)
    # obj["Access-Control-Allow-Origin"] = "*"
    obj["Access-Control-Allow-Origin"] = "http://127.0.0.1:8000"
    print(request.method)
    #处理预检的options请求,这个预检的响应,我们需要在响应头里面加上下面的内容
    if request.method == 'OPTIONS':
       
        obj['Access-Control-Allow-Headers'] = "content-type,b"  #发送来的请求里面的请求头里面的内容可以定义多个,后端需要将头配置上才能访问
        obj['Access-Control-Allow-Methods'] = "DELETE,PUT"  #通过预检的请求方法设置

    return obj

Jsonp实现跨域

Jsonp

    jsonp是json用来跨域的一个东西。原理是通过script标签的跨域特性来绕过同源策略。

    思考:这算怎么回事?

<script src="http://code.jquery.com/jquery-latest.js"></script>

    script标签的src属性是可以直接实现跨域的,这是标签特性,所以我们可以借助它来实现跨域。

# =============================http://127.0.0.1:8001/index.html


<button>ajax</button>
{% csrf_token %}

<script>
    function func(name){ //func写在下面src请求的上面,不然找不到这个func函数,我们请求的另一个网站的后端返回的数据就是这个函数名称加参数的形式  // #HttpResponse("func('chao')")
        alert(name)
    }
</script>

<script src="http://127.0.0.1:8002/SendAjax/"></script>


# =============================http://127.0.0.1:8002/
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt  #如果跨域请求是post请求,别忘了csrftoken可能会产生的阻拦,get请求不需要加上它


def SendAjax(request):

    import json

    print("++++++++")
    # dic={"k1":"v1"}
    return HttpResponse("func('chao')")  # return HttpResponse("func('%s')"%json.dumps(dic))
# 注意,func这个名称是另外那个发送请求的网站的一个js代码函数名称

  

  这其实就是JSONP的简单实现模式,或者说是JSONP的原型:创建一个回调函数,然后在远程服务上调用这个函数并且将JSON 数据形式作为参数传递,完成回调。

  将JSON数据填充进回调函数,这就是JSONP的JSON+Padding的含义。

  一般情况下,我们希望这个script标签能够动态的调用,而不是像上面因为固定在html里面所以没等页面显示就执行了,很不灵活。我们可以通过javascript动态的创建script标签,这样我们就可以灵活调用远程服务了。

<button onclick="f()">sendAjax</button>

<script>
    function addScriptTag(src){
         var script = document.createElement('script');
         script.setAttribute("type","text/javascript");
         script.src = src;
         document.body.appendChild(script);
         document.body.removeChild(script);
    }


    function func(name){  #接受返回数据的回调函数
        alert("hello"+name)
    }

    function f(){
         addScriptTag("http://127.0.0.1:8002/SendAjax/")
    }
</script>

  

    为了更加灵活,现在将你自己在客户端定义的回调函数的函数名传送给服务端,服务端则会返回以你定义的回调函数名的方法,将获取的json数据传入这个方法完成回调,这样的话,后端服务器就不需要知道你的回调函数的名称是什么了,不然和上面似的,服务端必须知道你的函数名称,就不太灵活:

    将8001的f()改写为:

function f(){
         addScriptTag("http://127.0.0.1:8002/SendAjax/?callbacks=func") // callbacks这个名称不是固定的,和另一个网站的服务端商量好就行了,就是后端取值时的一个参数而已
    }

    8002的views改为:

def SendAjax(request):
 
    import json
 
    dic={"k1":"v1"}
 
    print("callbacks:",request.GET.get("callbacks"))
    callbacks=request.GET.get("callbacks")
 
    return HttpResponse("%s('%s')"%(callbacks,json.dumps(dic)))

jQuery对JSONP的实现

    getJSON

      jQuery框架也当然支持JSONP,可以使用$.getJSON(url,[data],[callback])方法

      8001的html改为:

<button onclick="f()">sendAjax</button>

<script>

    function f(){
          $.getJSON("http://127.0.0.1:8002/SendAjax/?callbacks=?",function(arg){
            alert("hello"+arg)
        });
    }
    
</script>

      8002的views不改动。

      结果是一样的,要注意的是在url的后面必须添加一个callbacks(callbacks名不是固定的)参数,这样getJSON方法才会知道是用JSONP方式去访问服务,callbacks后面的那个问号是内部自动生成的一个回调函数名。

​     此外,如果说我们想指定自己的回调函数名,或者说服务上规定了固定回调函数名该怎么办呢?我们可以使用$.ajax方法来实现

    $.ajax

      8001的html改为:

<script>

    function f(){
          $.ajax({  //大家会发现我的type请求方法没写,默认是get请求
                url:"http://127.0.0.1:8002/SendAjax/",
                dataType:"jsonp",
                jsonp: 'callbacks',
                jsonpCallback:"SayHi"
           });

       }

    function SayHi(arg){
                alert(arg.k1);  //k1是后端返回的数据中的键值对的键
            }

</script>

      8002的views不改动。

      当然,最简单的形式还是通过回调函数来处理:

<script>

    function f(){

            $.ajax({
               url:"http://127.0.0.1:8002/SendAjax/",
               dataType:"jsonp",            //必须有,告诉server,这次访问要的是一个jsonp的结果。
               jsonp: 'callbacks',          //jQuery帮助随机生成的:callbacks="wner"
               success:function(data){
                   alert("hi "+data)
              }
         });

       }

</script>

      jsonp: 'callbacks'就是定义一个存放回调函数的键,jsonpCallback是前端定义好的回调函数方法名'SayHi',server端接受callback键对应值后就可以在其中填充数据打包返回了;

      jsonpCallback参数可以不定义,jquery会自动定义一个随机名发过去,那前端就得用回调函数来处理对应数据了。利用jQuery可以很方便的实现JSONP来进行跨域访问。  

      注意 JSONP一定是GET请求

应用:

<input type="button" onclick="AjaxRequest()" value="跨域Ajax" />


<div id="container"></div>


    <script type="text/javascript">
        function AjaxRequest() {
            $.ajax({
                url: 'http://www.jxntv.cn/data/jmd-jxtv2.html?callback=list&_=1454376870403',
                type: 'GET',
                dataType: 'jsonp',
                jsonp: 'callback',
                jsonpCallback: 'list',
                success: function (data) {
                    
                    $.each(data.data,function(i){
                        var item = data.data[i];
                        var str = "<p>"+ item.week +"</p>";
                        $('#container').append(str);
                        $.each(item.list,function(j){
                            var temp = "<a href='" + item.list[j].link +"'>" + item.list[j].name +" </a><br/>";
                            $('#container').append(temp);
                        });
                        $('#container').append("<hr/>");
                    })

                }
            });
        }
</script>

同源可跨域总结

同源:域名、协议、端口完全相同。
跨域:域名、协议、端口有其中的一样不同。

解决方式 
简单请求设置一个响应头
非简单请求设置多个响应头
posted @ 2020-03-02 20:05  一起奥利给  阅读(513)  评论(0编辑  收藏  举报