Django前后端通信

Posted on 2022-11-27 21:37  呱呱呱呱叽里呱啦  阅读(104)  评论(0编辑  收藏  举报

Django前后端通信

Ajax

# 异步提交、局部刷新
'''
向后端发送请求的方式:
1.浏览器地址栏(get)
2.a标签href(get)
3.form表单(get/post)
4.ajax(get/post)
'''
# 优点:在不重新加载整个页面的情况下与服务器交换数据并更新部分网页内容


'''
<body>
<input type="text" id="d1">
<input type="text" id="d2">
<input type="text" id="d3">
<p>
    <button id="btn">hahah</button>
</p>
<script>
    $('#btn').click(function (){
        $.ajax({
            url:'', //目的
            type:'post', //方法
            data:{'d1':$('#d1').val(),'d2':$('#d2').val()},
            dataType:true, //自动反序列化
            success:function (args){
                {#alert(args) //后端返回结果时会自动触发,args是结果#}
                $('#d3').val(args)
            }


        })
    })
</script>
</body>
'''

def test_ajax(request):
    if request.method == 'POST':
        d1 = request.POST.get('d1')
        d2 = request.POST.get('d2')
        d3 = int(d1) + int(d2)
        return JsonResponse({1:'1',2:'2'}) # JsonResponse返回的数据前段会自动反序列化
    return render(request,'test_ajax.html')

前后端传输数据的编码格式(contentType)

# form表单默认的编码格式是urlencoded,数据格式:username=george&password=123,django针对符合urlencoded编码格式的数据都会自动解析封装到request.POST
# 如果编码格式改成formdata,那么针对普通的键值对,还是解析到request.POST中而将文件解析到requeat.FILES中
# form表单是没办法发送json格式数据的
# ajax默认的编码格式也是urlencoded

ajax发送json格式数据

'''
$('#btn').click(function (){
        $.ajax({
            url:'', 
            type:'post', 
            data:JSON.stringify({'d1':$('#d1').val(),'d2':$('#d2').val()}),
            contentType:'application/json',
            success:function (args){
                // alert(args)
                $('#d3').val(args)
            }


        })
    })
'''
# request.is_ajax():判断当前请求是否是ajax请求

# 针对前段发来的json格式数据,需要手动处理
json_bytes = request.body
json_str =json_bytes.decode('utf-8')
dict = json.loads(json_str)


json_bytes = request.body
dict = json.loads(json_str) #json.loads()可以自动解码二进制再反序列化

# ajax发送json格式数据需要注意:1.contentType需要指定为application/json 2.确保数据已经序列化 3.Django不会自动反序列化,需要自己在request.body中获取并处理

ajax发送文件

# ajax发送文件需要借助js内置对象FormData
'''
$('#d4').on('click',function(){
    let formDataObj = new FormData();
    formDataObj.append(key1,value1); 添加普通键值对
    formDataObj.append(key2,value2);
    formDataObj.append('filename',$('#d3')[0].files[0]) 添加文件对象

    $.ajax({
        url:'',
        type:'post',
        data:formDataObj,
        contentType:false, 不使用任何编码
        processData:false, 不许浏览器处理数据

        success:function(args) {

        }

    })
})
'''

# ajax发送文件:1.需要借助内置对象FormData 2.需要指定关键性参数contentType、processData 3.django能够识别formdata对象并自动解析封装到POST或者FILES中

django自带的序列化组件

from django.core import serializers
res = serializers.serializers('json',user_queryset) # 将数据变成json格式的字符串
return HtttpResopnse(res)

ajax结合sweetalert

# window.location.reload() 重新加载当前页面

批量插入数据

models.Book.objects.bulk_create(book_list)

分页器

# 当我们需要使用到非django框架内置的功能或者代码时,我们一般会创建一个名为uils的文件夹,在该文件夹内对代码进行功能性划分

# 新建文件夹utils
# 文件夹内新建py文件
# py文件内定义类
# 视图函数导入类
# 生成Pagination对象,使用Pagination对象的start,end对所有传给页面的对象列表进行切片
# 在前段进行循环渲染切片后的对象列表
# 使用Pagination对象html方法与safe过滤器渲染分页按钮
models.Book.objects.bulk_create(book_list)

cat_queryset = models.CatzA.objects.all()
cur_page = request.GET.get('page', 1)
all_count = cat_queryset.count()

page_obj = Pagination(current_page=cur_page,all_count=all_count)
page_queryset = cat_queryset[page_obj.start:page_obj.end]

return render(request,'cat.html',locals())

forms组件

# 基本使用
from django import forms
class MyForm(forms.Form):
    username = forms.CharField(min_length=3,max_length=8)
    password = forms.CharField(min_length=3,max_length=8)
    email = forms.EmailField()
# 测试环境-pycharm 校验数据
from app01 import views
form_obj = views.MyForm({'username':'george','password':'12wewew','email':'123'})
form_obj.is_valid() # False
form_obj.cleaned_data # {'username': 'george', 'password': '12wewew'} 校验通过
form_obj.errors # {'email': ['Enter a valid email address.']}

form_obj = views.MyForm({'username':'george','password':'12wewew','email':'123','anotherone':'anothervalue'})
form_obj.is_valid() # True 多传字段直接忽略

form_obj = views.MyForm({'username':'george','password':'12wewew'})
form_obj.is_valid() # False 少传字段错

# 默认情况下,已经定义的字段都需要传值

# 渲染HTML标签
# 第一种 封装程度高,不便于拓展,一般不用
def test_form(request):
    form_obj = MyForm()
    return render(request,'test_form.html',locals())
'''
<form action="" method="post">
    {{ form_obj.as_p }}
</form>
'''
'''
<form action="" method="post">
{#    {{ form_obj.as_p }}#}
    {{ form_obj.as_ul }}
</form>
'''
# 第二种 拓展性强但代码繁杂
'''
<form action="" method="post">
{#    {{ form_obj.as_p }}#}
{#    {{ form_obj.as_ul }}#}
    <p>{{ form_obj.username.label }}:{{ form_obj.username }}</p>
</form>
{# label值可以在类定义时直接指定
class MyForm(forms.Form):
    username = forms.CharField(min_length=3,max_length=8,label='用户名')
#}
'''
# 第三种 推荐 label字段默认展示类的属性首字母大写的形式,可以自己定义label值
'''
<form action="" method="post">
{#    {{ form_obj.as_p }}#}
{#    {{ form_obj.as_ul }}#}
{#    <p>{{ form_obj.username.label }}:{{ form_obj.username }}</p>#}
    {% for form in form_obj %}
        <p>{{ form.label }}{{ form }}</p>
    {% endfor %}


</form>
'''

# 展示提示信息
form_obj['email'].errors # ['Enter a valid email address.'] 使用自定义类创建的form_obj对象的key是有.errors属性的,这个属性的值就是这个key对应的value

# 必须将get请求和post请求传给html的对象变量名设置成相同的,这样可以在POST数据不合法时将基于收到的数据创建的对象传递回html页面,让页面刷新后能同时看到错误提示信息和之前的输入

#自定义错误提示 <form action="" method="post" novalidate>
class MyForm(forms.Form):
username = forms.CharField(min_length=3,max_length=8,label='用户名',error_messages={'min_length':'用户名最少3位',
                                                                                     'max_length':'用户名最多8位',
                                                                                     'required':'用户名不能为空'})
password = forms.CharField(min_length=3,max_length=8,label='密码',error_messages={'min_length':'密码最少3位',
                                                                                    'max_length':'密码最多8位',
                                                                                    'required':'密码不能为空'})
email = forms.EmailField(label='邮箱',error_messages={'invalid':'邮箱格式不正确','required':'邮箱不能为空'})

钩子函数(HOOK)

# 即在特殊节点自动触发完成相应操作,可以自定义新增校验规则
# form组件有两类钩子,即:1.局部钩子(给单个字段增加校验规则) 2.全局钩子(给多个字段增加校验规则)

class MyForm(forms.Form):
    username = forms.CharField(min_length=3,max_length=8,label='用户名',error_messages={'min_length':'用户名最少3位',
                                                                                     'max_length':'用户名最多8位',
                                                                                     'required':'用户名不能为空'})
    password = forms.CharField(min_length=3,max_length=8,label='密码',error_messages={'min_length':'密码最少3位',
                                                                                     'max_length':'密码最多8位',
                                                                                     'required':'密码不能为空'})
    confirm_password = forms.CharField(min_length=3, max_length=8, label='确认密码', error_messages={'min_length': '确认密码最少3位',
                                                                                       'max_length': '确认密码最多8位',
                                                                                       'required': '确认密码不能为空'})
    email = forms.EmailField(label='邮箱',error_messages={'invalid':'邮箱格式不正确','required':'邮箱不能为空'})

    # 局部钩子
    def clean_username(self):
        username = self.cleaned_data.get('username')
        if 'sb' in username:
            self.add_error('username','用户名含有不当词汇')
        return username
    # 全局钩子
    def clean(self):
        password = self.cleaned_data.get('password')
        confirm_password = self.cleaned_data.get('confirm_password')
        if not password == confirm_password:
            self.add_error('confirm_password','两次输入的密码不一致')
        return self.cleaned_data

forms组件其他参数

'''
label 标签名
error_messages 自定义错误提示
initial 默认值
required 是否必填
widget 设置input标签类型(password/select等),并且可以通过传参设置标签属性:
widget=forms.widgets.PasswordInput(attrs={'class':'formcontrol c1 c2','me':'george'})
RegexValidator
更多input标签类型的渲染:https://www.cnblogs.com/Dominic-Ji/p/9240365.html
'''
# RegexValidator校验
from django.core.validators import RegexValidator
email = forms.EmailField(label='邮箱',error_messages={'invalid':'邮箱格式不正确','required':'邮箱不能为空'})
# 仍然属于第一道校验,在钩子函数之前

forms源码


cookie与session

# cookie 服务端保存在客户端浏览器上的信息都可以称之为cookie,形式一般是键值对
# session 保存在服务端上的信息
# session是基于cookie工作的
# token 将第一次登录时提交信息的一部分用自有算法加密,将提交信息和加密结果整体返回浏览器,下次访问时将携带的信息切割,再次加密用户信息与提交的加密结果比对

cookie操作

# 虽然cookie是服务端告诉客户端浏览器保存信息,但浏览器可以拒绝保存
obj = HttpResponse()
# 设置cookie
obj.set_cookie(key,value,max_age=超时s) expires=超时s,针对IE浏览器
# 获取cookie
request.COOKIES.get(key)

def login_auth(func):
    def inner(request, *args, **kwargs):
        target_url = request.get_full_path()
        # print(target_url)
        if request.COOKIES.get('username') == 'confirm':
            return func(request, *args, **kwargs)
        else:
            return redirect(f'/app01/login/?next={target_url}')

    return inner


def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        if username == 'george' and password == '123':
            target_url = request.GET.get('next')
            if target_url:
                obj = redirect(target_url)

            else:
                obj = redirect('home')
            obj.set_cookie('username', 'confirm',max_age=300) # 设置cookie
            return obj
    return render(request, 'login.html')


@login_auth
def home(request):
    return render(request, 'home.html')

# 主动删除cookie

session操作

# 设置session,设置多个session时仅生成一个ID和对应的字符串,同一台计算机上同一个浏览器只会生成表中的
request.session['key'] = value
# 获取session
request.session.get('key')
# session数据是保存在服务端的,给客户端返回的是一个随机字符串,客户端sessionid:随机字符串
# 在默认操作下操作session需要django默认的django_session表,
# django默认session的过期时间是14天
def test_session(request):
    request.session['password'] = 'confirm' # 1.django内部自动生成一个随机字符串,2.并将其与对应的数据存储到django_session表中(先在内存中产生操作的缓存,在响应经过django中间件时真正操作数据库),3.将产生的随机字符串返回给客户端浏览器保存(不需要手动操作)
    return HttpResponse('123')

def test_session1(request):
    request.session.get('password') # 1.自动从浏览器请求中获取对应的随机字符串,2.使用此字符串在django_session表中比对,3.如果能够找到,则将对应的数据取出并封装到request.session中4.如果不能找到,则request.session.get()返回None
    return HttpResponse('321')
# 设置过期时间
request.session.set_expiry() # 参数:1.整数,即秒数 2.日期对象,即过期日期 3.0,即浏览器窗口关闭即失效 4.不写,即默认14天

# 清除session
request.session.delete() # 只删服务端
request.session.flush() # 浏览器和服务端都清空

# session是保存在服务端的,但是session的保存位置可以有多种选择

CBV添加装饰器

# CBV中django不建议直接给类的方法加装饰器,无论该装饰器能否正常
# 方式一
from django.utils.decorators import method_decorator

class MyLogin(View):
    @method_decorator(login_auth)
    def get(self,request):
        return HttpResponse('get')
    def post(self,request):
        return HttpResponse('post')

# 方式二
from django.utils.decorators import method_decorator


@method_decorator(login_auth, name='get') # 可以对不同的方法添加不同的装饰器
@method_decorator(login_auth, name='post')
class MyLogin(View):
    # @method_decorator(login_auth)
    def get(self,request):
        return HttpResponse('get')
    def post(self,request):
        return HttpResponse('post')

# 方式三 给类内所有暴露方法加装饰器
from django.utils.decorators import method_decorator

class MyLogin(View):
    @method_decorator(login_auth)
    def dispatch(self, request, *args, **kwargs):
        pass

    # @method_decorator(login_auth)
    def get(self,request):
        return HttpResponse('get')
    def post(self,request):
        return HttpResponse('post')

django中间件

# django中间件是django的门户
# 请求来的时候需要先经过中间件才能到达真正的dajango后端
# 响应走的时候也需要经过

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

django支持开发人员自定义中间件并且暴露给开发人员五个可以自定义的方法

# process_request
# 请求来的时候需要经过每一个中间件里面的process_request方法,经过的顺序是按照配置文件中的注册顺序,如果某一中间件中没有定义process_request,则直接执行下一个,如果该方法返回了HttpResponse对象,那么请求不再继续往后执行,该方法就是用来校验全局相关的所有限制功能

# process_response
def process_response(self,request,response):
    pass
# 响应走的时候需要经过每一个中间件里面的process_response方法,该方法有两个额外参数:request、resopnse,该方法必须返回HttpResponse对象,默认返回形参response,可以自定义,执行顺序是按照配置文件注册顺序倒序执行,如果某一个中间件没有定义该方法,则直接跳过

# 如果在第N个中间件的process_request方法中就直接返回HttpResponse对象,那么,会直接从当前中间件的process_response方法走,返回响应

# process_view
def process_view(self,request,view_name,*args,**kwargs):
    pass
# 路由匹配成功之后,执行视图函数之前会触发该方法

# process_template_response
def process_template_response(self,request,response):
    return response
# 返回的对象中有render方法才会触发,执行顺序中间件注册顺序倒序

# process_exception
def process_exception(self,request,exception):
    pass
# 当视图函数执行出现异常的情况下触发,顺序是中间件注册顺序倒序

自定义中间件

# 在项目名或者应用名下创建一个任意名称的文件夹
# 在该文件夹内创建一个任意名称的py文件
# py文件内书写类,继承MiddlewareMixin
# 在类内自定义方法
# 将类的路径以字符串的形式注册到配置文件中才能生效

CSRF跨站请求伪造

# 自个儿写吧

CSRF跨站请求伪造校验

# 网站在给用户返回一个具有提交数据功能的页面时会给这个页面加一个唯一标识,当这个页面向后端发送post请求的时候,后端先校验唯一标识,如果校验不通过,就会直接拒绝(403 forbbiden)
<form>
{% csrf_token %}
</form>

# ajax 第一种
'''
$('#d1').click(function(){
    $.ajax({
        url:'',
        type:'post',
        data:{'csrfmiddlewaretoken':$('[name=csrfmiddlewaretoken]').val()},
    })
})
'''
# ajax 第二种
'''
$('#d1').click(function(){
    $.ajax({
        url:'',
        type:'post',
        data:{'csrfmiddlewaretoken':{{csrf_tiken}},
    })
})
'''
# ajax 第三种
{% load static %}
<script src='{% static 'js/mysetup.js'%}'></script>

CSRF相关装饰器

from django.views.decorators.csrf import csrf_protect,csrf_exempt
# csrf_protect 需要校验(直接注释CSRF中间件,整个网站处此装饰器修饰的函数外都不校验)

# csrf_exempt 忽略校验(整个网站除此装饰器修饰的函数都校验)

# CBV与CSRF装饰器
# csrf_protect直接给类内方法装饰,可以;直接在类上装饰,可以;给类内dispatch方法加装饰器,可以
# csrf_exempt直接给类内方法装饰,不可以;直接在类上装饰,不可以;给类内dispatch方法加装饰器,可以

importlib

import importlib
dir = 'mydir.my_python_file'
for path_str in settings.notify_list:
    module_path,class_name = path_str.rsplit('.',maxsplit=1)
    module = importlib.import_module(module_path)
    cls = getattr(module,class_name)
    obj = cls()
    obj.send(content)