django终章篇

从 django终章篇

1.Cookie和Session

1.1 基于cookie的登录

  • cookie设置在响应头上,获取在请求头上
  • 设置cookie时要先获得一个响应对象(HttpResponse),然后在这个响应对象上设置cookie
  • 获取从request上直接获取
  • 不明白可以跟着代码看浏览器
from django.shortcuts import render, HttpResponse, redirect


# Create your views here.

def deco(func):
    def inner(request, *args, **kwargs):
        if not request.COOKIES.get('coo') == '1':
            path = request.path_info
            return redirect('/login/?path={}'.format(path))
        ret = func(request, *args, **kwargs)
        return ret

    return inner


def login(request):
    if request.method == "POST":
        if request.POST.get('user') == '123456' and request.POST.get('pwd') == '123456':
            path = request.GET.get('path')
            if path:
                ret = redirect(path)
            else:
                ret = redirect('/index/')
            ret.set_cookie('coo', '1')
            return ret
    return render(request, 'login.html')


@deco
def home(request):
    return HttpResponse("这是home页面")


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


def dle(request):
    ret = redirect('/login/')
    ret.delete_cookie('coo')
    return ret

1.2 Django中操作Cookie

1.2.1 设置Cookie

rep = HttpResponse(...)
rep = render(request, ...)
rep = redirect(....)

rep.set_cookie(key,value,...)
rep.set_signed_cookie(key,value,salt='加密盐', max_age=None, ...)

参数:
	key, 键
    value='', 值
    max_age=None, 超时时间
    expires=None, 超时时间(IE requires expires, so set it if hasn't been already.)
    path='/', Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问
    domain=None, Cookie生效的域名
    secure=False, https传输
    httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)

1.2.2 获取Cookie

request.COOKIES['key']
request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)

参数:
    default: 默认值
    salt: 加密盐
    max_age: 后台控制过期时间

1.2.3 删除Cookie

def dle(request):
    ret = redirect('/login/')
    ret.delete_cookie('coo')
    return ret

1.3 Session

1.3.1 Django中Session相关方法

# 获取、设置、删除Session中数据
request.session['k1']
request.session.get('k1',None)
request.session['k1'] = 123
request.session.setdefault('k1',123) # 存在则不设置
del request.session['k1']


# 所有 键、值、键值对
request.session.keys()
request.session.values()
request.session.items()
request.session.iterkeys()
request.session.itervalues()
request.session.iteritems()

# 会话session的key
request.session.session_key

# 将所有Session失效日期小于当前日期的数据删除
request.session.clear_expired()

# 检查会话session的key在数据库中是否存在
request.session.exists("session_key")

# 删除当前会话的所有Session数据
request.session.delete()
  
# 删除当前的会话数据并删除会话的Cookie。
request.session.flush() 
    这用于确保前面的会话数据不可以再次被用户的浏览器访问
    例如,django.contrib.auth.logout() 函数中就会调用它。

# 设置会话Session和Cookie的超时时间
request.session.set_expiry(value)
    * 如果value是个整数,session会在些秒数后失效。
    * 如果value是个datatime或timedelta,session就会在这个时间后失效。
    * 如果value是0,用户关闭浏览器session就会失效。
    * 如果value是None,session会依赖全局session失效策略。

1.3.2 基于Session的登录

from django.shortcuts import render, HttpResponse, redirect


# Create your views here.

def deco(func):
    def inner(request, *args, **kwargs):
        print(request.session)
        if not request.session['coo'] == '1':
            path = request.path_info
            return redirect('/login/?path={}'.format(path))
        ret = func(request, *args, **kwargs)
        return ret

    return inner


def login(request):
    if request.method == "POST":
        if request.POST.get('user') == '123456' and request.POST.get('pwd') == '123456':
            path = request.GET.get('path')
            if path:
                ret = redirect(path)
            else:
                ret = redirect('/index/')
            request.session['coo'] = '1'
            return ret
    return render(request, 'login.html')


@deco
def home(request):
    return HttpResponse("这是home页面")


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


def dle(request):
    request.session.delete()
    return redirect('/login/')

1.3.3 Session流程解析

  • 好好看,好好学。下次来看不要急,肯定能理解

image-20210806160919823

2.django中间件

  • 地址1

  • 地址2

  • 中间件是一个python类,用来在全局范围内处理请求和响应的一个钩子。

1. process_request

process_request:
    1.执行顺序:按照注册顺序执行
    2.参数:request
    3.返回值:
    	1.None ---> 无返回值默认为None,继续向下执行
        2.HttpResponse对象 
        	当前中间件之后的process_request和process_response方法和视图函数都不执行
    4.执行时间:视图函数之前
settings.py:
    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',
    'app01.my_middleware.MD1',
    'app01.my_middleware.MD2'
]
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse


class MD1(MiddlewareMixin):

    def process_request(self, request):
        print("这是MD1中的中间件")
        # 若该方法返回一个response对象,则页面会返回MD1,因为之后的中间件也不会执行,直接会返回
	   # return HttpResponse('MD1')  

class MD2(MiddlewareMixin):

    def process_request(self, request):
        print("这是MD2中的中间件")
views.py:
    def index(request):
        print('index')
        return HttpResponse('ok')

image-20210808202406886

2. process_response

process_response:
    1.执行顺序:按照注册顺序倒序执行
    2.参数:request,response
    3.返回值:必须返回一个response对象
    4.执行时间:视图函数执行之后
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse


class MD1(MiddlewareMixin):

    def process_request(self, request):
        print("这是MD1中的process_request")
    
    def process_response(self, request, response):
        print("这是MD1中的process_response")
        return response


class MD2(MiddlewareMixin):

    def process_request(self, request):
        print("这是MD2中的process_request")
    
    def process_response(self, request, response):
        print("这是MD2中的process_response")
        return response

image-20210808203734397

img

3. process_view

process_view:
    1.执行顺序:按照注册顺序执行
    2.参数:request, view_func, view_args, view_kwargs
    	 request 是 HttpRequest 对象。
         view_func 是 Django 即将使用的视图函数。
         view_args 是将传递给视图的位置参数的列表。
         view_kwargs 是将传递给视图的关键字参数的字典。
    3.返回值:None  正常走
    		返回一个对象 直接执行中间件中所有的process_response方法,直到返回到页面
    4.执行时间:路由匹配后寻找执行的视图函数,执行视图函数前执行该中间件
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse


class MD1(MiddlewareMixin):

    def process_request(self, request):
        print("这是MD1中的process_request")
        # return HttpResponse('MD1')

    def process_response(self, request, response):
        print("这是MD1中的process_response")
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        print('这是MD1中的process_view')
        # 若返回response对象,则其它中间件中本正常执行的process_view方法不再执行,而是倒序执行所有的process_response
        # return HttpResponse('MD1') 

class MD2(MiddlewareMixin):

    def process_request(self, request):
        print("这是MD2中的process_request")

    def process_response(self, request, response):
        print("这是MD2中的process_response")
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        print('这是MD2中的process_view')

img

4. process_exception

1. 执行时间(触发条件:有异常才执行)
	在视图函数之后,在process_response之前
2. 参数
	exception ——》 错误信息对象

3. 返回值
	None  正常走
	HttpResponse对象  
    注册顺序之前的中间件的process_exception方法不走了。
    执行所有中间件的process_response方法

4. 执行顺序
	按照注册顺序倒叙执行
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse


class MD1(MiddlewareMixin):

    def process_request(self, request):
        print("这是MD1中的process_request")

    def process_response(self, request, response):
        print("这是MD1中的process_response")
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        print('这是MD1中的process_view')

    def process_exception(self, request, exception):
        print('这是MD1中的process_exception')
        print(exception)

class MD2(MiddlewareMixin):

    def process_request(self, request):
        print("这是MD2中的process_request")

    def process_response(self, request, response):
        print("这是MD2中的process_response")
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        print('这是MD2中的process_view')

    def process_exception(self, request, exception):
        print('这是MD2中的process_exception')
        print(exception)
    # 若返回response对象,则其它中间件中本正常执行的process_exception方法不再执行,而是倒序执行所有的process_response
        # return HttpResponse('MD2') 
        

image-20210808210252083

5. process_template_response

3.django请求流程图

img

4.csrf源码

image-20210809210845792

  • ----------⬇—--————说错了,是中间件会生成cookie,{% csrf_token %}会生成标签

{% csrf_token %}: <input type="hidden" name="csrfmiddlewaretoken" value="NlisZvyWY4oKZOWjZmwkpU2VeK8CSkN5izw1Jf3CP1KIbIJWWasyCHRHyG2CDK5I">

image-20210809212058903

image-20210809214900395

# 12.之后就是正常的走视图函数了,然后再走response

# 13.所以发送post请求需要带上csrf_token值
	- 一处是中间件给的cookie值
    - 一处是{% csrf_token %}所生成的校验值/或设置请求头时带上校验值(第10步),语言ajax的第二种

补充:

补充两个装饰器
from django.views.decorators.csrf import csrf_exempt, csrf_protect
csrf_exempt   给单个视图排除校验
csrf_protect  给单个视图必须校验


from django.views.decorators.csrf import ensure_csrf_cookie
# 确保有cookie

5.Ajax

  • Ajax是一个与服务器交互的技术,JS技术。
  • 特点: 异步 不刷新页面 数据量小
$.ajax({
    url: 发送的地址,
    type: 'post/get',
    data:{
        k1:v1,
        k2:v2,
    },
    success:function(res){   # res  返回的响应的响应体 

    },
    error:function(res){   # res  返回的响应的响应体 

    }
})

5.1 通过ajax上传文件

views.py:
    def upload(request):
    if request.method == 'POST':
        file = request.FILES.get('f1')
        with open(file.name, 'wb') as f:
            for line in file.chunks():
                f.write(line)
        return HttpResponse('上传成功')
    return render(request, 'upload.html')
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src="/static/ajax_setup.js"></script>
</head>
<body>
{% csrf_token %}
<input type="file" id="f1">

<button id="b1">上传</button>
<script>
    $('#b1').click(function () {
        let form_data = new FormData();
        console.log($('#f1')[0].files[0]);
        form_data.append('f1', $('#f1')[0].files[0]);
        $.ajax({
            url: '/upload/',
            data: form_data,
            type:"post",
            processData: false,  // 告诉jQuery不要去处理发送的数据
            contentType: false,  // 告诉jQuery不要去设置Content-Type请求头
            success: function (res) {
                console.log(res)
            }
        })
    })

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

5.2 ajax如何通过django的CSRF验证

5.2.1 页面中使用

$('#b1').click(function () {
    $.ajax({
        url: '/csrf_test/',
        type: 'post',
        data: {
            # 从{% csrf_token %}生成的标签中取值,该key固定为csrfmiddlewaretoken,可查看源码
            csrfmiddlewaretoken: $('[name="csrfmiddlewaretoken"]').val(),  
            name: 'alex',
            age: '73',
        },
        success: function (res) {
            console.log(res);
        },
        error:functiom(err){
            console.log(err)
        }
    })
});

5.2.2 在头中设置值,(可查看csrf源码)

$('#b1').click(function () {
    $.ajax({
        url: '/csrf_test/',
        type: 'post',
        headers: {"X-CSRFToken": $('[name="csrfmiddlewaretoken"]').val()},
        data: {
            name: 'alex',
            age: '73',

        },
        success: function (res) {
            console.log(res);
        },
         error:functiom(err){
            console.log(err)
        }
    })
});

5.2.3 全局的设置(官方推荐)

  • 写一个js文件,文件中写入如下内容
  • 将文件导入需要使用ajax的html页面
  • 导入该文件使用时需要用到cookie,该js文件会从cookie中取得校验值
    1. 使用
    2. 不使用{% csrf_token %}
      from django.views.decorators.csrf import ensure_csrf_cookie
      将ensure_csrf_cookie加在视图上 保证返回的响应有cookie
function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = jQuery.trim(cookies[i]);
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}
var csrftoken = getCookie('csrftoken');

function csrfSafeMethod(method) {
  // these HTTP methods do not require CSRF protection
  return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}

$.ajaxSetup({
  beforeSend: function (xhr, settings) {
    if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
      xhr.setRequestHeader("X-CSRFToken", csrftoken);
    }
  }
});

6.Form

6.1 使用form实现注册案例

app01/forms.py:
    
    from django import forms
    # 按照Django form组件的要求自己写一个类
    class RegForm(forms.Form):
        name = forms.CharField(label="用户名")
        pwd = forms.CharField(label="密码")
    
   
views.py:
    # 使用form组件实现注册方式
    def register2(request):
        form_obj = RegForm()
        if request.method == "POST":
            # 实例化form对象的时候,把post提交过来的数据直接传进去
            form_obj = RegForm(request.POST)
            # 调用form_obj校验数据的方法
            if form_obj.is_valid():  # 待会看源码
                return HttpResponse("注册成功")
        return render(request, "register2.html", {"form_obj": form_obj})
register2.html:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>注册2</title>
    </head>
    <body>
        <form action="" method="post" novalidate autocomplete="off">
           	{{ form_obj.as_p }} <---!>可以一次性将字段通过p包含input标签显示在页面上,具体看效果</---!>
            <p>
                {{ from_obj.name.label }}  <---!>取出lable标签,默认是字段</---!>
                {{ from_obj.name }}   <---!>生成input标签,name属性就是字段名</---!>
                {{ from_obj.name.errors }}  <--!>显示该字段所校验产生的错误</--!>
                {{ from_obj.name.errors.0 }}  <--!>显示该字段所校验产生的第一个错误</--!>
            </p>
            <p>
                {{ from_obj.pwd.label }}
                {{ from_obj.pwd }}
            </p>
            <p>
                {{ from_obj.errors }}  <--!>显示所有字段所校验产生的错误</--!>
            </p>
                <input type="submit" class="btn btn-success" value="注册">
        </form>
    </body>
    </html>

6.2 常用字段与参数

6.2.1 initial

  • 初始值,input框里面的初始值。
class LoginForm(forms.Form):
    username = forms.CharField(
        min_length=8,  # 设置最小长度,若小于该值,这errors将显示该错误
        label="用户名",
        initial="张三"  # 设置默认值
    )
    pwd = forms.CharField(min_length=6, label="密码")  # 此处密码是明文的,下面会引出插件

6.2.2 error_messages

  • 重写错误信息。
class LoginForm(forms.Form):
    username = forms.CharField(
        min_length=8,
        label="用户名",
        initial="张三",
        error_messages={
            "required": "不能为空",
            "invalid": "格式错误",
            "min_length": "用户名最短8位"
        }
    )

6.2.3 password

  • 引出插件
from django.forms import widgets
class LoginForm(forms.Form):
    ...
    pwd = forms.CharField(
        min_length=6,
        label="密码",
        # 此时该字段可以显示不出明文,并且增加了一个属性  class='c1'.下面会引出给所有字段加属性
        widget=widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True)  
    )

6.2.4 radioSelect

  • 单radio值为字符串
class LoginForm(forms.Form):
    username = forms.CharField(
        min_length=8,
        label="用户名",
        initial="张三",
        error_messages={
            "required": "不能为空",
            "invalid": "格式错误",
            "min_length": "用户名最短8位"
        }
    )
    pwd = forms.CharField(min_length=6, label="密码")
    gender = forms.fields.ChoiceField(
        choices=((1, "男"), (2, "女"), (3, "保密")),
        label="性别",
        initial=3,  # 设置初始选择为3:保密
        widget=forms.widgets.RadioSelect(attr={k1:v1})  # 设置插件,表明该字段为单选radio类型
    )

6.2.5 单选Select

class LoginForm(forms.Form):
    ...
    hobby = forms.fields.ChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
        label="爱好",
        initial=3,
        widget=forms.widgets.Select()  # 设置插件,表明该字段为单选select类型
    )

6.2.6 多选Select

class LoginForm(forms.Form):
    ...
    hobby = forms.fields.MultipleChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
        label="爱好",
        initial=[1, 3],
        widget=forms.widgets.SelectMultiple()
    )

6.2.7 单选checkbox

class LoginForm(forms.Form):
    ...
    keep = forms.fields.ChoiceField(
        label="是否记住密码",
        initial="checked",
        widget=forms.widgets.CheckboxInput()
    )

6.2.8 多选checkbox

class LoginForm(forms.Form):
    ...
    hobby = forms.fields.MultipleChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好",
        initial=[1, 3],
        widget=forms.widgets.CheckboxSelectMultiple()
    )

6.2.9 choice字段注意事项

  • 在使用选择标签时,需要注意choices的选项可以配置从数据库中获取,但是由于是静态字段 获取的值无法实时更新,需要重写构造方法从而实现choice实时更新。
  1. 方法一:

    class LoginForm(forms.Form):
        ...
        hobby = forms.fields.MultipleChoiceField(
            choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
            label="爱好",
            initial=[1, 3],
            widget=forms.widgets.CheckboxSelectMultiple()
        )
    
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.fields['hobby'].choices = models.User.objects.all().values_list('id', 'hobby')
    
  2. 方法二:

    from django import forms
    from django.forms import fields
    from django.forms import models as form_model
    
     
    class FInfo(forms.Form):
        hobby = form_model.ModelMultipleChoiceField(queryset=models.User.objects.all().values_list('id', 'hobby'))
        
        authors = form_model.ModelMultipleChoiceField(queryset=models.Author.objects.all())  # 多选
        # authors = form_model.ModelChoiceField(queryset=models.Author.objects.all())  # 单选
    

6.3 Form所有内置字段

Field
    required=True,               是否允许为空
    widget=None,                 HTML插件
    label=None,                  用于生成Label标签或显示内容
    initial=None,                初始值
    help_text='',                帮助信息(在标签旁边显示)
    error_messages=None,         错误信息 {'required': '不能为空', 'invalid': '格式错误'}
    validators=[],               自定义验证规则
    localize=False,              是否支持本地化
    disabled=False,              是否可以编辑
    label_suffix=None            Label内容后缀
 
 
CharField(Field)
    max_length=None,             最大长度
    min_length=None,             最小长度
    strip=True                   是否移除用户输入空白
 
IntegerField(Field)
    max_value=None,              最大值
    min_value=None,              最小值
 
FloatField(IntegerField)
    ...
 
DecimalField(IntegerField)
    max_value=None,              最大值
    min_value=None,              最小值
    max_digits=None,             总长度
    decimal_places=None,         小数位长度
 
BaseTemporalField(Field)
    input_formats=None          时间格式化   
 
DateField(BaseTemporalField)    格式:2015-09-01
TimeField(BaseTemporalField)    格式:11:12
DateTimeField(BaseTemporalField)格式:2015-09-01 11:12
 
DurationField(Field)            时间间隔:%d %H:%M:%S.%f
    ...
 
RegexField(CharField)
    regex,                      自定制正则表达式
    max_length=None,            最大长度
    min_length=None,            最小长度
    error_message=None,         忽略,错误信息使用 error_messages={'invalid': '...'}
 
EmailField(CharField)      
    ...
 
FileField(Field)
    allow_empty_file=False     是否允许空文件
 
ImageField(FileField)      
    ...
    注:需要PIL模块,pip3 install Pillow
    以上两个字典使用时,需要注意两点:
        - form表单中 enctype="multipart/form-data"
        - view函数中 obj = MyForm(request.POST, request.FILES)
 
URLField(Field)
    ...
 
 
BooleanField(Field)  
    ...
 
NullBooleanField(BooleanField)
    ...
 
ChoiceField(Field)
    ...
    choices=(),                选项,如:choices = ((0,'上海'),(1,'北京'),)
    required=True,             是否必填
    widget=None,               插件,默认select插件
    label=None,                Label内容
    initial=None,              初始值
    help_text='',              帮助提示
 
 
ModelChoiceField(ChoiceField)
    ...                        django.forms.models.ModelChoiceField
    queryset,                  # 查询数据库中的数据
    empty_label="---------",   # 默认空显示内容
    to_field_name=None,        # HTML中value的值对应的字段
    limit_choices_to=None      # ModelForm中对queryset二次筛选
     
ModelMultipleChoiceField(ModelChoiceField)
    ...                        django.forms.models.ModelMultipleChoiceField
 
 
     
TypedChoiceField(ChoiceField)
    coerce = lambda val: val   对选中的值进行一次转换
    empty_value= ''            空值的默认值
 
MultipleChoiceField(ChoiceField)
    ...
 
TypedMultipleChoiceField(MultipleChoiceField)
    coerce = lambda val: val   对选中的每一个值进行一次转换
    empty_value= ''            空值的默认值
 
ComboField(Field)
    fields=()                  使用多个验证,如下:即验证最大长度20,又验证邮箱格式
                               fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])
 
MultiValueField(Field)
    PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用
 
SplitDateTimeField(MultiValueField)
    input_date_formats=None,   格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
    input_time_formats=None    格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
 
FilePathField(ChoiceField)     文件选项,目录下文件显示在页面中
    path,                      文件夹路径
    match=None,                正则匹配
    recursive=False,           递归下面的文件夹
    allow_files=True,          允许文件
    allow_folders=False,       允许文件夹
    required=True,
    widget=None,
    label=None,
    initial=None,
    help_text=''
 
GenericIPAddressField
    protocol='both',           both,ipv4,ipv6支持的IP格式
    unpack_ipv4=False          解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用
 
SlugField(CharField)           数字,字母,下划线,减号(连字符)
    ...
 
UUIDField(CharField)           uuid类型

6.4 字段校验

6.4.1 RegexValidator验证器

class LoginForm(forms.Form):
    	......
    phone = forms.CharField(
            label='手机号',
            validators=[RegexValidator(r'^1[3-9]\d{9}$', '手机号不正经')]
        )

6.4.2 自定义校验器

def check(value):
    if 'alex' in value:
        raise ValidationError('这不符合社会主义核心价值观')
        
class Reg(forms.Form):
    user = forms.CharField(label='用户名',
                           # min_length=6,
                           # initial='张三',
                           validators=[check],  # 这个校验里面可以接收函数
                           error_messages={
                               'min_length': '你太短了'
                           },
                           widget=widgets.TextInput(attrs={'id': 'd1', 'placeholder': '用户名'}))

6.5 钩子函数

6.5.1 了解源码

def login(request):
    from_obj = Reg()
    if request.method == 'POST':
        # print(from_obj.fields.get('user').label)  # 实例化的对象含有所有字段,可以通过它拿到属性值,这里拿到label值
        from_obj = Reg(request.POST)
        if from_obj.is_valid():  # 从这个is_valid()方法了解源码
            print(from_obj.cleaned_data)
            return HttpResponse('注册成功')
        # else:
        #     return HttpResponse('注册失败')
    return render(request, 'login.html', {'from_obj': from_obj})
  • 你可以按照图片然后结合源码一起看
  • 到这个份上,总不能连我当时都能看懂的现在看不懂吧-----> (静心)

image-20210810213920264

image-20210810214418084

image-20210810215431432

image-20210810215749337

image-20210810220427199

6.5.2 局部钩子

phone = forms.CharField(
        label='手机号',
    )

    def clean_phone(self):
        value = self.cleaned_data.get('phone')
        if re.match(r'^1[3-9]\d{9}$', value):
            return value  # 将值返回
        raise ValidationError('手机号不正经')  # 若校验不成功,则返回异常

6.5.3 全局钩子

class LoginForm(forms.Form):
    ...
    pwd = forms.CharField(
        min_length=6,
        label="密码",
        widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True)
    )
    re_pwd = forms.CharField(
        min_length=6,
        label="确认密码",
        widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True)
    )   
    def clean(self):
        pwd = self.cleaned_data.get('pwd')
        re_pwd = self.cleaned_data.get('re_pwd')
        if re_pwd == pwd:
            return self.cleaned_data  # 校验成功,将cleaned_data返回
        self.add_error('re_pwd','两次输入的密码不一致')
        raise ValidationError('两次输入的密码不一致')

补充:字段添加属性

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field in self.fields:  # 循环每一个字段,给每个字段的attrs设置属性键值
            self.fields[field].widget.attrs.update({'class': 'form-control'})

7.auth模块

7.1 创建超级用户命令

  • python manage.py createsuperuser

  • 再执行 python manage.py migrate 将生成系统自带的表

image-20210811083028217

7.2 认证--authenticate()

提供了用户认证功能,即验证用户名以及密码是否正确,一般需要username 、password两个关键字参数。

如果认证成功(用户名和密码正确有效),便会返回一个 User 对象。

authenticate()会在该 User 对象上设置一个属性来标识后端已经认证了该用户,且该信息在后续的登录过程中是需要的。
views.py:
	from django.contrib import auth
	def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        obj = auth.authenticate(request, username=username, password=password)  # 返回User对象

7.3 记住状态--login(HttpRequest, user)

  • 该函数接受一个HttpRequest对象,以及一个经过认证的User对象。
  • 该函数实现一个用户登录的功能。它本质上会在后端为该用户生成相关session数据。
def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        obj = auth.authenticate(request, username=username, password=password)
        if obj:
            auth.login(request, obj)  # 记录登录状态 会生成session存储在session表中

7.4 注销--logout(request)

  • 该函数接受一个HttpRequest对象,无返回值。
  • 当调用该函数时,当前请求的session信息会全部清除。该用户即使没有登录,使用该函数也不会报错。
def logout(request):
    auth.logout(request)  # 可以点进去查看源码,会执行request.session.flush()清除session
    return redirect('/login/')

7.5 判断是否通过认证-- is_authenticated()

  • 用来判断当前请求是否通过了认证。
def my_view(request):
  if not request.user.is_authenticated():
    return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))

'注意':需将settings.LOGIN_URL设置为自己的登录路由,让django配合我们,要不然默认的路由会是  /accounts/login/(大概是这个)

7.6 login_requierd()

  • auth 给我们提供的一个装饰器工具,用来快捷的给某个视图添加登录校验
  • 与我们写的登录装饰器一样的道理,可以查看上面的基于cookie/session的登录
from django.contrib.auth.decorators import login_required
      
@login_required  # 当装饰器装饰此函数,说明走某个路由访问该函数需要先登录(与当时我们自己的装饰器一个道理)
def my_view(request):
   ...
若用户没有登录,则会跳转到django默认的 登录URL '/accounts/login/ ' 并传递当前访问url的绝对路径 (登陆成功后,会重定向到该路径)。

如果需要自定义登录的URL,则需要在settings.py文件中通过LOGIN_URL进行修改。
LOGIN_URL = '/login/'  # 这里配置成你项目登录页面的路由

7.7 create_user()

  • auth 提供的一个创建新用户的方法,需要提供必要参数(username、password)等。
from django.contrib.auth.models import User
def reg(request):
    reg2_obj = RegForm()
    if request.method == 'POST':
        reg2_obj = RegForm(request.POST)
        if reg2_obj.is_valid():
            username = reg2_obj.cleaned_data.get('username')
            password = reg2_obj.cleaned_data.get('password')
            # 用普通的方式创建用户会不可登录,且密码是明文
            # User.objects.create(username=username, password=password)  
            reg2_obj.cleaned_data.pop('re_password')
            # 用django提供的方式创建密文的用户,可登录,但没有权限
            User.objects.create_user(username=username, password=password, is_staff=1)
            # User.objects.create_user(**reg2_obj.cleaned_data)

            return redirect('/login/')
    return render(request, 'reg.html', {'reg2_obj': reg2_obj})

7.8 create_superuser()

  • auth 提供的一个创建新的超级用户的方法,需要提供必要参数(username、password)等。
from django.contrib.auth.models import User
user_obj = User.objects.create_superuser(username='用户名',password='密码',email='邮箱',...)

7.9 check_password(raw_password) 和 set_password(raw_password)

  • check_password(raw_password)
    1. auth 提供的一个检查密码是否正确的方法,需要提供当前请求用户的密码。
    2. 密码正确返回True,否则返回False。
  • set_password(raw_password)
    1. auth 提供的一个修改密码的方法,接收 要设置的新密码 作为参数。
    2. 注意:设置完一定要调用用户对象的save方法!!!
@login_required
def index(request):
    print(request.user.is_authenticated)  # 判断登录状态
    if request.user.check_password('root1234'):
        request.user.set_password('1234root')
        request.user.save()
    return render(request, 'index.html')

补充:修改密码案例

@login_required
def set_password(request):
    user = request.user
    err_msg = ''
    if request.method == 'POST':
        old_password = request.POST.get('old_password', '')
        new_password = request.POST.get('new_password', '')
        repeat_password = request.POST.get('repeat_password', '')
        # 检查旧密码是否正确
        if user.check_password(old_password):
            if not new_password:
                err_msg = '新密码不能为空'
            elif new_password != repeat_password:
                err_msg = '两次密码不一致'
            else:
                user.set_password(new_password)
                user.save()
                return redirect("/login/")
        else:
            err_msg = '原密码输入错误'
    content = {
        'err_msg': err_msg,
    }
    return render(request, 'set_password.html', content)

扩展默认的auth_user表

  • 通过继承来扩展表
  • 通过下图我们可以通过继承AbstractUser来扩展表

image-20210811091016017

app01/models.py:
    from django.db import models
    from django.contrib.auth.models import AbstractUser


    class User(AbstractUser):
        phone = models.CharField(max_length=32)
  • 按上面的方式扩展了内置的auth_user表之后,一定要在settings.py中告诉Django,我现在使用我新定义的UserInfo表来做用户认证。写法如下:`

    # 引用Django自带的User表,继承使用时需要设置
    AUTH_USER_MODEL = "app名.User"
    
    • 之后进行数据库迁移 python manage.py makemigrations app01python manage.py migrate app01

image-20210811091703577

  • 之后使用创建用户的方法也是一样的

8. ModelForm

8.1 modelForm定义

  • form与model的终极结合。
  • 用还是和form一样
  • 也有钩子函数
from django import forms
from crm import models
from django.core.exceptions import ValidationError


class RegForm(forms.ModelForm):
    # 写一个新的字段(但数据库不需要,只是为了业务逻辑)
    re_password = forms.CharField(
        label='确认密码',
        widget=forms.widgets.PasswordInput()
    )
    # 下面的[widgets,labels,error_messages]等对于上面的字段不生效, 只对model内生成的字段有效
    # 即若re_password的label写在下面的labels中不生效,所以上面的字段的插件等只能写在自己那
    class Meta:
        model = models.UserProfile  # 关联哪个model
        # fields = '__all__'  # 生成所有字段
        fields = ['username', 'password', 're_password', 'name', 'department']  # 生成指定字段
        # 插件,为一个对象,哪个字段需要使用什么插件可在这里面写入  
        widgets = {
            'password': forms.widgets.PasswordInput(),
        }
		# 标签 反正记得与form一样
        labels = {
            'username': '用户名',
            'password': '密码',
            'department': '部门',
            'name': '姓名',
        }
        error_messages = {
            'password': {
                'required': '密码不能为空',
            }
        }

8.2 class Meta下常用参数:

model = models.Book  # 对应的Model中的类
fields = "__all__"  # 字段,如果是__all__,就是表示列出所有的字段
exclude = None  # 排除的字段
labels = None  # 提示信息
help_texts = None  # 帮助提示信息
widgets = None  # 自定义插件
error_messages = None  # 自定义错误信息

8.3 ModelForm的验证

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

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

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

8.4 save()方法

每个ModelForm还具有一个save()方法。 这个方法根据表单绑定的数据创建并保存数据库对象。 ModelForm的子类可以接受现有的模型实例作为关键字参数instance;如果提供此功能,则save()将更新该实例。 如果没有提供,save() 将创建模型的一个新实例:
obj = models.Customer.objects.filter(id=edit_id).first()
form_obj = CustomerForm(instance=obj)
form_obj带着原有的数据,根据数据生成input的值

form_obj = CustomerForm(request.POST,instance=obj)
将提交的数据和要修改的实例交给form对象
form_obj.save()  对要修改的实例进行修改
# 编辑客户
def edit_customer(request, edit_id):
    # 根据ID查出所需要编辑的客户对象
    obj = models.Customer.objects.filter(id=edit_id).first()
    # 将该用户实例交给Form,Form将会带着该实例对象的信息
    form_obj = CustomerForm(instance=obj)
    if request.method == 'POST':
        # 将提交的数据和要修改的实例交给form对象
        form_obj = CustomerForm(request.POST, instance=obj)
        if form_obj.is_valid():
            form_obj.save()
            return redirect(reverse('customer'))
    
    return render(request, 'crm/edit_customer.html', {"form_obj": form_obj})
posted @ 2023-03-04 22:23  w随风w  阅读(20)  评论(0编辑  收藏  举报