Python3基础之学习笔记(十四)-Django补充-cookie-session-CSRF-ModelForm

文章目录

  • 1. Django
    • 1.1 url补充
      • 1.1.1 url默认值
      • 1.1.2 命名空间
    • 1.2 views获取其他信息
    • 1.3 自定义simple_tag和filter
    • 1.4 自定义分页
      • 1.4.1 防止xss
      • 1.4.2 分页函数
    • 1.5 cookie
      • 1.5.1 简单cookie
    • 1.6 CBV和FBV用户认证装饰器
    • 1.7 Session
      • 1.7.1 Session简介
      • 1.7.2 Session简单使用
      • 1.7.3 Session操作
    • 1.8 CSRF
      • 1.8.1 简介
      • 1.8.2 原理
    • 1.9 中间件
    • 1.10 缓存
      • 1.10.1 缓存简介
      • 1.10.2 缓存的应用
    • 1.11 信号
      • 1.11.1 信号简介
      • 1.11.2 自定义信号
    • 1.12 Form组件验证
    • 1.13 ModelForm
      • 1.13.1 ModelForm操作及验证
      • 1.13.2 ModelForm组件

1. Django

1.1 url补充

1.1.1 url默认值

path('index',views.index,{'name':'root'}),#设置name默认值为root
def index(request,name):#有默认值时函数必须有参数
    pass

1.1.2 命名空间

project urls.py

from django.urls import path,include
urlpatterns=[
    path('a/',include('app01.urls',namespace='author-polls')),
    path('b/',include('app01.urls',namespace='publisher-polls')),
]

app01 urls.py

from django.urls import path,include
from . import views
app_name='app01'
urlpatterns=[
    path('detail',views.detail,name='detail'),
]

app01 views.py

def detail(request,pk):
    print(request.resolver_match)
    return HttpResponse(pk)

以上定义带命名空间的url之后,使用name生成url时候必须加上namespace和name,应该如下:

  • v=reverse(‘author-polls:detail’,kwarrgs={‘pk’:11})
  • {% url ‘author-polls:detail’ pk=12 pp=99 %}

1.2 views获取其他信息

def index(request):
    #封装了所有用户请求信息
    print(request.environ)
    #获取cookies
    print(request.COOKIES)
    #获取useragent
    print(request.environ['HTTP_USER_AGENT'])

1.3 自定义simple_tag和filter

步骤:

  1. 在app中创建templatetags模块

  2. 创建任意.py文件,如果:xx.py

    from django import template
    from django.utils.safestring import mark_safe
    
    register = template.Library()
    
    @register.simple_tag
    def my_simple_time(a1,a2,a3):
        return a1 + a2+a3
    
    @register.simple_tag
    def my_input(id,arg):
        result=""%(id,arg,)
        return mark_safe(result)
    @register.filter
    def aa(a1.a2):#最多只能传2个参数
        return a1+a2
    
  3. 在使用自定义simple_tag的html文件中导入之前创建的xx.py文件名

    {% load xx %}
    
  4. 使用simple_tag和filter

    {% my_simple_time 1 2 3 %}
    {% my_input 'id_username' 'hide' %}
    #使用filter
    {{ '参数1'| aa:'参数2'}}#结果为'参数1参数2'
    {% if {{ '参数1'| aa:'参数2'}} %}
    {% endif %}
    
  5. 在settings中注册App

1.4 自定义分页

1.4.1 防止xss

#前端代码
page_str=""
{{page_str|safe}}
#后端代码
from django.utils.safestring import mark_safe
page_str=mark_safe(page_str)

1.4.2 分页函数

#!/usr/bin/python
#coding:utf-8
 
"""
@author: GoldenKitten
@contact: [email protected]
@software: PyCharm
@file: page.py
@time: 2018/9/17 14:29
"""
class Page(object):
    def __init__(self,
                 datas_count,
                 current_page=1,
                 per_page_data_count=10,
                 page_count=7):
        '''
        data_count:数据总共有多少条
        current_page:当前页码,默认从第一页开始
        per_page_data_count:每一页显示多少条数据
        page_count:显示多少个页码
        '''
        self.datas_count=datas_count
        self.current_page=current_page
        self.per_page_data_count=per_page_data_count
        self.page_count=page_count
    @property
    def total_page_count(self):
        # 获取总页数
        total_count, y = divmod(self.datas_count, self.per_page_data_count)
        if y:
            total_count += 1
        return total_count
    @property
    def start_page(self):
        #获取开始的页码
        start=None
        if self.total_page_count<self.page_count:
            start= 1
        else:
            if self.current_page<=(self.page_count-1)/2:
                start= 1
            elif self.current_page>=(self.total_page_count-(self.page_count-1)/2):
                start=self.total_page_count-self.page_count+1
            else:
                start=self.current_page-(self.page_count-1)/2
        return int(start)
    @property
    def end_page(self):
        #获取结尾时页码
        end=None
        if self.total_page_count<self.page_count:
            end=self.total_page_count
        else:
            if self.current_page<=(self.page_count-1)/2:
                end= self.page_count
            elif self.current_page>=(self.total_page_count-(self.page_count-1)/2):
                end= self.total_page_count
            else:
                end= self.current_page+(self.page_count-1)/2
        return int(end)
    def page(self):
        page_list=[]
        for i in range(self.start_page,self.end_page+1):
            page_list.append(i)
        return page_list
if __name__ == '__main__':
    p=Page(134,1)
    print(p.start_page)
    print(p.end_page)
    print(p.page())

1.5 cookie

1.5.1 简单cookie

def login(req):
    if req.method=='GET':
        pass
    if req.method=='POST':
        u=req.POST.get('username')
        p=req.POST.get('password')
        res=HttpResponseRedirect('app2/host')
        res.set_cookie('username',u)#设置cookie
         #设置cookie5秒后失效
         #res.set_cookie('username',u,max_age=5)
        #设置超时时间
         #custom_datetime=datetime.datetime.utcnow()+datetime.timedelta(seconds=5)
         #res.set_cookie('username',u,expires=custom_datetime)
        return res
def index(req):
    if req.COOKIES.get('username'):#获取cookie
        return HttpResponse('你已经登录过了')

设置cookie

rep = HttpResponse(...) 或 rep = render(request, ...)
 
rep.set_cookie(key,value,...)
#加密的cookie
rep.set_signed_cookie(key,value,salt='加密盐',...)
req.get_signed_cookie(key,salt='加密盐')
    参数:
        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.6 CBV和FBV用户认证装饰器

FBV装饰器

def auth(func):
    def inner(request,*args,**kwargs):
        u=request.COOKIES.get('username')
        if not u:
            return HttpResponseRedirect('/login')
        return func(request,*args,**kwargs)
    return inner

CBV装饰器

from django.utils.decorators import method_decorator
def auth(func):
    def inner(request,*args,**kwargs):
        u=request.COOKIES.get('username')
        if not u:
            return HttpResponseRedirect('/login')
        return func(request,*args,**kwargs)
    return inner
class Order(views.View):
    @method_decorator(auth)#只装饰get方法
    def get(self,request):
        pass
    def post(self,request):
        pass
#装饰全部方法
@method_decorator(auth,name='dispatch')#只装饰get方法
class Order(views.View):
    def get(self,request):
        pass
    def post(self,request):
        pass

1.7 Session

1.7.1 Session简介

基于Cookie做用户验证时:敏感信息不适合放在cookie中

Session原理:

  • Cookie是保存在用户浏览器端的键值对
  • Session是保存在服务端的键值对

session的工作过程

  1. 生成随机字符串
  2. 写到用户浏览器的cookie中
  3. 保存到session中
  4. 在随机字符串对应的字典中设置相关内容

而上述过程在Django中的体现为:

request.session[“username”]=user

这里的username为通过request.POST.get(“username”)从前端html页面中获取到的用户名信息

注意:

在Django中要用session中一定要先执行:

python manage.py makemigrations

python manage.py migrate

当用户登录的时候的就会在数据库的django_session表中记录session信息

同样的通过request.session[“username”]也可以获取相应的值

在这个过程中:

1、 首先获取当前用户的随机字符串

2、 根据随机字符串获取对应的内容

1.7.2 Session简单使用

def login(request):
    if request.method== 'GET':
        return render(request, 'app2/login.html')
    if request.method== 'POST':
        u=request.POST.get('user')
        p=request.POST.get('pwd')
        if u=='root' and p=='123':
            request.session['username']=u
            request.session['is_login']=True
            return HttpResponseRedirect('index')
        else:
            return render(request, 'app2/login.html')
def index(request):
    if request.session['is_login']:
        return HttpResponse('你已经登录过了')
    else:
        return HttpResponse('你没有登录过')

1.7.3 Session操作

request.session["k1"]  如果不存在则会报错
 
request.session.get["k1"],如果不存在则会报错,为了防止出错可以request.session.get('k1',None)
 
request.session['k1'] = 123 设置session值
 
request.session.setdefault('k1',123)  存在则不设置
 
del request.session['k1']  删除
 
request.session.clear()    删除
 
 
 
所有 键、值、键值对
 
request.session.keys()
 
request.session.values()
 
request.session.items()
 
request.session.iterkeys()
 
request.session.itervalues()
 
request.session.iteritems()
 
 
 
用户session的随机字符串
 
request.session.session_key
 
 
将所有Session失效日期小于当前日期的数据删除
 
request.session.clear_expired()
 
 
检查 用户session的随机字符串 在数据库中是否
 
request.session.exists("session_key")
 
 
 
删除当前用户的所有Session数据
 
request.session.delete("session_key")
 
 
 
request.session.set_expiry(value)
 
默认的过期时间是两周,如果自己设置了过期时间,这样自己设定的优先级就会高于默认的
 
如果value是个整数,session会在些秒数后失效。
 
如果value是个datatime或timedelta,session就会在这个时间后失效。
 
如果value是0,用户关闭浏览器session就会失效。
 
如果value是None,session会依赖全局session失效策略。

**配置setting.py **

SESSION_COOKIE_NAME = "sessionid"      # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
 
SESSION_COOKIE_PATH = "/"              # Session的cookie保存的路径(默认)
 
SESSION_COOKIE_DOMAIN = None             # Session的cookie保存的域名(默认)
 
SESSION_COOKIE_SECURE = False          # 是否Https传输cookie(默认)
 
SESSION_COOKIE_HTTPONLY = True         # 是否Session的cookie只支持http传输(默认)
 
SESSION_COOKIE_AGE = 1209600             # Session的cookie失效日期(2周)(默认)
 
SESSION_EXPIRE_AT_BROWSER_CLOSE = False    # 是否关闭浏览器使得Session过期(默认)
 
SESSION_SAVE_EVERY_REQUEST = False        # 是否每次请求都保存Session,默认修改之后才保存(默认)

Django中对于session的存储方式

Django中支持session,其中内部提供了5种类型的session供开发者使用:

数据库(默认)

缓存

文件

缓存+数据库

加密cookie

1、如果是数据库,需要在settings.py中配置如下:

SESSION_ENGINE = ‘django.contrib.sessions.backends.db’ (引擎(默认))

2、如果是缓存session,需要在settings.py中配置如下:

SESSION_ENGINE = ‘django.contrib.sessions.backends.cache’(引擎)

SESSION_CACHE_ALIAS= ‘default’ 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置

1、 如果是文件session, 需要在settings.py中配置如下:

SESSION_ENGINE = ‘django.contrib.sessions.backends.file’ (引擎)

SESSION_FILE_PATH=None 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir()

2、 如果是缓存+数据库session,需要在settings.py中配置如下:

SESSION_ENGINE=‘django.contrib.sessions.backends.cached_db’ (引擎)

1.8 CSRF

1.8.1 简介

django为用户实现防止跨站请求伪造的功能,通过中间件 django.middleware.csrf.CsrfViewMiddleware 来完成。而对于django中设置防跨站请求伪造功能有分为全局和局部。

全局:

中间件 django.middleware.csrf.CsrfViewMiddleware

局部:

@csrf_protect,为当前函数强制设置防跨站请求伪造功能,即便settings中没有设置全局中间件。

@csrf_exempt,取消当前函数防跨站请求伪造功能,即便settings中设置了全局中间件。

注意:from django.views.decorators.csrf import csrf_exempt,csrf_protect

1.8.2 原理

当用post提交数据的时候,django会去检查是否有一个csrf的随机字符串,如果没有就会报错,这也是之前我们一直将其注释的原因 ,在django内部支持生成这个随机字符串 。

通过form提交

在form表单里面需要添加{%csrf_token%}

这样当你查看页面源码的时候,可以看到form中有一个input是隐藏的

<form action="/login/" method="POST">
        {% csrf_token %}
        <input type="text" name="user" />
        <input type="text" name="pwd" />
        <input type="checkbox" name="rmb" value="1" /> 10秒免登录
        <input type="submit" value="提交" />
        <input id="btn1" type="button" value="按钮" />
        <input id="btn2" type="button" value="按钮" />
    </form>

总结原理:当用户访问login页面的时候,会生成一个csrf的随机字符串,,并且cookie中也存放了这个随机字符串,当用户再次提交数据的时候会带着这个随机字符串提交,如果没有这个随机字符串则无法提交成功

通过ajax提交

因为cookie中同样存在csrftoken,所以可以在js中通过:

$.cooke(“cstftoken”)获取

如果通过ajax进行提交数据,这里提交的csrftoken是通过请求头中存放,需要提交一个字典类型的数据,即这个时候需要一个key。

在views中的login函数中:from django.conf import settings,然后打印print(settings.CSRF_HEADER_NAME)

这里需要注意一个问题,这里导入的settings并不是我们在项目文件下看到的settings.py文件,这里是是一个全局的settings配置,而当我们在项目目录下的settings.py中配置的时候,我们添加的配置则会覆盖全局settings中的配置

print(settings.CSRF_HEADER_NAME)打印的内容为:HTTP_X_CSRFTOKEN

这里的HTTP_X_CSRFTOKEN是django在X_CSRF的前面添加了HTTP_,所以实际传递的是就是X_CSRFtoken,而在前端页面的ajax传递的时候由于不能使用下划线所以传递的是X_CSRFtoken

下面是在前端ajax中写的具体内容:

$("#btn1").click(function () {
        $.ajax({
            url:"/login/",
            type:"POST",
            data:{"usr":"root","pwd":"123"},
            headers:{ "X-CSRFtoken":$.cookie("csrftoken")},
            success:function (arg) {
 
            }
        })
    })

但是如果页面中有多个ajax请求的话就在每个ajax中添加headers信息,所以可以通过下面方式在所有的ajax中都添加

$.ajaxSetup({
            beforeSend:function (xhr,settings) {
                xhr.setRequestHeader("X-CSRFtoken",$.cookie("csrftoken"))
            }
        });

这样就会在提交ajax之前执行这个方法,从而在所有的ajax里都加上这个csrftoken

这里的xhr是XMLHttpRequest的简写,ajax调用的就是这个方法

如果想要实现在当get方式的时候不需要提交csrftoken,当post的时候需要,实现这种效果的代码如下:

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);
                }
            }
        });

**总结 **

  1. csrf在ajax提交的时候通过请求头传递的给后台的
  2. csrf在前端的key为:X-CSRFtoken,到后端的时候django会自动添加HTTP_,并且最后为HTTP_X_CSRFtoken
  3. csrf在form中提交的时需要在前端form中添加{%csrftoken%}

1.9 中间件

中间件顾名思义,是介于request与response处理之间的一道处理过程,相对比较轻量级,并且在全局上改变django的输入与输出。因为改变的是全局,所以需要谨慎实用,用不好会影响到性能。  每个中间件都会负责一个功能,例如,AuthenticationMiddleware,与sessions处理相关。

一般我们我们从浏览器发出一个请求 Request,得到一个响应后的内容 HttpResponse ,这个请求传递到 Django的过程 也就是说,每一个请求都是先通过中间件中的 process_request 函数,这个函数返回 None 或者 HttpResponse 对象,如果返回前者,继续处理其它中间件,如果返回一个 HttpResponse,就处理中止,返回到网页上。

中间件写法

try:
    from django.utils.deprecation import MiddlewareMixin  # Django 1.10.x
except ImportError:
    MiddlewareMixin = object  # Django 1.4.x - Django 1.9.x
 
class SimpleMiddleware(MiddlewareMixin):
    def process_request(self, request):
        pass
     def process_view(self,request,view_func,view_func_args,view_func_kwargs):
        pass
    def process_response(self,request,response):
        return response

中间件中可以定义五个方法 :

process_request(self,request)
process_view(self, request, callback, callback_args, callback_kwargs)
process_template_response(self,request,response)
process_exception(self, request, exception)
process_response(self, request, response)
#前二个方法是从前往后执行的,后三个方法是从后往前执行的

简单识别手机的中间件

MOBILE_USERAGENTS = ("2.0 MMP","240x320","400X240","AvantGo","BlackBerry",
    "Blazer","Cellphone","Danger","DoCoMo","Elaine/3.0","EudoraWeb",
    "Googlebot-Mobile","hiptop","IEMobile","KYOCERA/WX310K","LG/U990",
    "MIDP-2.","MMEF20","MOT-V","NetFront","Newt","Nintendo Wii","Nitro",
    "Nokia","Opera Mini","Palm","PlayStation Portable","portalmmm","Proxinet",
    "ProxiNet","SHARP-TQ-GX10","SHG-i900","Small","SonyEricsson","Symbian OS",
    "SymbianOS","TS21i-10","UP.Browser","UP.Link","webOS","Windows CE",
    "WinWAP","YahooSeeker/M1A1-R2D2","iPhone","iPod","Android",
    "BlackBerry9530","LG-TU915 Obigo","LGE VX","webOS","Nokia5800")
 
class MobileTemplate(object):
    """
    If a mobile user agent is detected, inspect the default args for the view
    func, and if a template name is found assume it is the template arg and
    attempt to load a mobile template based on the original template name.
    """
 
    def process_view(self, request, view_func, view_args, view_kwargs):
        if any(ua for ua in MOBILE_USERAGENTS if ua in
            request.META["HTTP_USER_AGENT"]):
            template = view_kwargs.get("template")
            if template is None:
                for default in view_func.func_defaults:
                    if str(default).endswith(".html"):
                        template = default
            if template is not None:
                template = template.rsplit(".html", 1)[0] + ".mobile.html"
                try:
                    get_template(template)
                except TemplateDoesNotExist:
                    pass
                else:
                    view_kwargs["template"] = template
                    return view_func(request, *view_args, **view_kwargs)
        return None

1.10 缓存

1.10.1 缓存简介

由于Django是动态网站,所有每次请求均会去数据进行相应的操作,当程序访问量大时,耗时必然会更加明显,最简单解决方式是使用:缓存,缓存将一个某个views的返回值保存至内存或者memcache中,5分钟内再有人来访问时,则不再去执行view中的操作,而是直接从内存或者Redis中之前缓存的内容拿到,并返回。

Django提供了6种缓存方式:

1、 开发调试

2、 内存

3、 文件

4、 数据库

5、 Memcache缓存(python-memcached模块)

6、 Memcache缓存(pylibmc模块)

通用配置

'TIMEOUT': 300,                       # 缓存超时时间(默认300,None表示永不过期,0表示立即过期)
  'OPTIONS':{
     'MAX_ENTRIES': 300,       # 最大缓存个数(默认300)
     'CULL_FREQUENCY': 3, # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
                },
     'KEY_PREFIX': '',   # 缓存key的前缀(默认空)
     'VERSION': 1,       # 缓存key的版本(默认1)
     'KEY_FUNCTION' 函数名     # 生成key的函数(默认函数会生成为:【前缀:版本:key】)

开发调试

# 此为开始调试用,实际内部不做任何操作
    # 配置:
        CACHES = {
            'default': {
                'BACKEND': 'django.core.cache.backends.dummy.DummyCache',     # 引擎
              通用配置
            }
        }

内存

# 此缓存将内容保存至内存的变量中
    # 配置:
        CACHES = {
            'default': {
                'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
                'LOCATION': 'unique-snowflake',
              通用配置
            }
        }
 
    # 注:其他配置同开发调试版本

文件

# 此缓存将内容保存至文件
    # 配置:
 
        CACHES = {
            'default': {
                'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
                'LOCATION': '/var/tmp/django_cache',
                 通用配置
            }
        }
    # 注:其他配置同开发调试版本

数据库

# 此缓存将内容保存至数据库
 
    # 配置:
        CACHES = {
            'default': {
                'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
                'LOCATION': 'my_cache_table', # 数据库表
              通用配置
            }
        }
 
    # 注:执行创建表命令 python manage.py createcachetable
复制代码

Memcache缓存(python-memcached模块)

# 此缓存使用python-memcached模块连接memcache
 
    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
            'LOCATION': '127.0.0.1:11211',
        }
    }
 
    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
            'LOCATION': 'unix:/tmp/memcached.sock',
        }
    } 
 
    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
            'LOCATION': [
                '172.19.26.240:11211',
                '172.19.26.242:11211',
            ]
        }
    }

Memcache缓存(pylibmc模块)

# 此缓存使用pylibmc模块连接memcache
 
    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
            'LOCATION': '127.0.0.1:11211',
        }
    }
 
    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
            'LOCATION': '/tmp/memcached.sock',
        }
    } 
 
    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
            'LOCATION': [
                '172.19.26.240:11211',
                '172.19.26.242:11211',
            ]
        }
    }

1.10.2 缓存的应用

单独视图缓存

通过装饰器的方式实现,导入模块之后,在需要缓存的函数前加@cache_page(60 * 15) 60*15表示缓存时间是15分钟

from django.views.decorators.cache import cache_page
@cache_page(10)
def cache(request):
    import time
    ctime = time.time()
    return  render(request,"cache.html",{"ctime":ctime})

这样在前端页面在获取的ctime的时候就会被缓存10秒钟,10秒钟之后才会变化,但是这样的话就相当月所有的调用ctime的地方都被缓存了

局部缓存

引入TemplateTag
 
{% load cache %}
 
使用缓存
 
{% cache 5000 缓存key %}
缓存内容
{% endcache %}
{% load cache %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>{{ ctime }}</h1>
    <h1>{{ ctime }}</h1>
    {% cache 10 c1 %}
    <h1>{{ ctime }}</h1>
    {% endcache %}
</body>
</html>

这样就实现了最后一个ctime缓存,其他两个不缓存 。

全站缓存

全站缓存的时候,需要在中间件的最上面添加:

‘django.middleware.cache.UpdateCacheMiddleware’,

在中间件的最下面添加:

‘django.middleware.cache.FetchFromCacheMiddleware’,

其中’django.middleware.cache.UpdateCacheMiddleware’里面只有process_response方法,在’django.middleware.cache.FetchFromCacheMiddleware’中只有process_request方法,所以最开始是直接跳过UpdateCacheMiddleware,然后从第一个到最后一个中间件的resquest,第一次没有缓存座椅匹配urls路由关系依次进过中间件的process_view,到达views函数,再经过process_exception最后经过response,到达FetchFromCacheMiddleware。

1.11 信号

1.11.1 信号简介

Django中提供了“信号调度”,用于在框架执行操作时解耦。通俗来讲,就是一些动作发生的时候,信号允许特定的发送者去提醒一些接受者。

Django内置信号

Model signals
    pre_init                    # django的modal执行其构造方法前,自动触发
    post_init                   # django的modal执行其构造方法后,自动触发
    pre_save                    # django的modal对象保存前,自动触发
    post_save                   # django的modal对象保存后,自动触发
    pre_delete                  # django的modal对象删除前,自动触发
    post_delete                 # django的modal对象删除后,自动触发
    m2m_changed                 # django的modal中使用m2m字段操作第三张表(add,remove,clear)前后,自动触发
    class_prepared              # 程序启动时,检测已注册的app中modal类,对于每一个类,自动触发
Management signals
    pre_migrate                 # 执行migrate命令前,自动触发
    post_migrate                # 执行migrate命令后,自动触发
Request/response signals
    request_started             # 请求到来前,自动触发
    request_finished            # 请求结束后,自动触发
    got_request_exception       # 请求异常后,自动触发
Test signals
    setting_changed             # 使用test测试修改配置文件时,自动触发
    template_rendered           # 使用test测试渲染模板时,自动触发
Database Wrappers
    connection_created          # 创建数据库连接时,自动触发

因为这些信号中并没有注册函数,所以运行时并没有调用触发这些信号

对于Django内置的信号,仅需注册指定信号,当程序执行相应操作时,自动触发注册函数:

from django.core.signals import request_finished
    from django.core.signals import request_started
    from django.core.signals import got_request_exception
 
    from django.db.models.signals import class_prepared
    from django.db.models.signals import pre_init, post_init
    from django.db.models.signals import pre_save, post_save
    from django.db.models.signals import pre_delete, post_delete
    from django.db.models.signals import m2m_changed
    from django.db.models.signals import pre_migrate, post_migrate
 
    from django.test.signals import setting_changed
    from django.test.signals import template_rendered
 
    from django.db.backends.signals import connection_created
 
#注册函数
    def callback(sender, **kwargs):
        print("request_started_callback")
        print(sender,kwargs)
 
    request_started.connect(callback)

如果我们把导入信号以及将注册函数都写到一个单独的文件里,为了在程序启动的时候执行信号中的注册函数,可以在于项目同名的文件中的init文件中导入该文件即可

1.11.2 自定义信号

自定义信号一共需要三步骤 :

  1. 定义信号
  2. 注册信号
  3. 触发信号

定义信号

import django.dispatch
pizza_done=django.dispatch.Signal(providing_args=["toppings", "size"])

注册信号

def callback(sender, **kwargs):
 
    print("callback")
 
    print(sender,kwargs)
pizza_done.connect(callback)

触发信号

from 路径 import pizza_done
 
pizza_done.send(sender='seven',toppings=123, size=456)

1.12 Form组件验证

Form表单的功能

  • 准备数据、重构数据,以便下一步提交。
  • 为数据创建HTML 表单
  • 接收并处理客户端提交的表单和数据
from django import forms
from django.forms import widgets
from django.forms import fields
class FM(forms.Form):
    #前端页面的name值必须和这个变量一致,字段本身只做验证
    user=fields.CharField(
        error_messages={'required':'用户名不能为空'},
        widget=widgets.Textarea(attrs={'class':c1})#定制样式,
        label='用户名'
 
    )
    pwd=fields.CharField(
        max_length=12,
        min_length=6,
        error_messages={
            'required':'用户名不能为空',
            'min_length':'密码长度不能小于6',
            'max_length':'密码长度不能大于12'
        },
        widget=widgets.PasswordInput(attrs={'class':'c3'})      
    )
    email=fields.EmailField(error_messages={'required':'邮箱不能为空','invalid':'邮箱格式错误'})
def fm(request):
    if request.method=='GET':
        obj=FM()
        return render(request,'fm.html',{'obj':obj})
    if request.method=='POST':
        obj=FM(request.POST)
        r1=obj.is_valid()#返回校验是否正确
        if r1:
            print(obj.cleaned_data)
            models.UserInfo.objects.crreate(**obj.cleaned_data)
        else:
            print(obj.errors)
            print(obj.errors.as_json())
            print(obj.errors['user'][0])
            return render(request,'fm.html',{'obj':obj})
        return redirect('/fm/')

前端代码:


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>title>
head>
<body>
    <form action="/fm/" method="POST">
        {% csrf_token %}
        
        <p>{{ obj.user.label }} {{ obj.user }} {{ obj.errors.user.0 }}p>
        <p>{{ obj.pwd }} {{ obj.errors.pwd.0 }}p>
        <p>{{ obj.email }}{{ obj.errors.email.0 }}p>
        <p>{{ obj.f }}{{ obj.errors.f.0 }}p>
        {{ obj.city1 }}
        {{ obj.city2 }}
        <input type="submit" value="提交" />
    form>
body>
html>

Django内置字段

Field
    required=True,               是否允许为空
    widget=None,                 HTML插件
    label=None,                  用于生成Label标签或显示内容
    initial=None,                初始值
    help_text='',                帮助信息(在标签旁边显示)
    error_messages=None,         错误信息 {'required': '不能为空', 'invalid': '格式错误'}
    show_hidden_initial=False,   是否在当前插件后面再加一个隐藏的且具有默认值的插件(可用于检验两次输入是否一直)
    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类型
    ...

Django内置插件

TextInput(Input)
NumberInput(TextInput)
EmailInput(TextInput)
URLInput(TextInput)
PasswordInput(TextInput)
HiddenInput(TextInput)
Textarea(Widget)
DateInput(DateTimeBaseInput)
DateTimeInput(DateTimeBaseInput)
TimeInput(DateTimeBaseInput)
CheckboxInput
Select
NullBooleanSelect
SelectMultiple
RadioSelect
CheckboxSelectMultiple
FileInput
ClearableFileInput
MultipleHiddenInput
SplitDateTimeWidget
SplitHiddenDateTimeWidget
SelectDateWidget

**Django常用插件 **

# 单radio,值为字符串
# user = fields.CharField(
#     initial=2,
#     widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),))
# )
 
# 单radio,值为字符串
# user = fields.ChoiceField(
#     choices=((1, '上海'), (2, '北京'),),
#     initial=2,
#     widget=widgets.RadioSelect
# )
 
# 单select,值为字符串
# user = fields.CharField(
#     initial=2,
#     widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))
# )
 
# 单select,值为字符串
# user = fields.ChoiceField(
#     choices=((1, '上海'), (2, '北京'),),
#     initial=2,
#     widget=widgets.Select
# )
 
# 多选select,值为列表
# user = fields.MultipleChoiceField(
#     choices=((1,'上海'),(2,'北京'),),
#     initial=[1,],
#     widget=widgets.SelectMultiple
# )
 
 
# 单checkbox
# user = fields.CharField(
#     widget=widgets.CheckboxInput()
# )
 
 
# 多选checkbox,值为列表
# user = fields.MultipleChoiceField(
#     initial=[2, ],
#     choices=((1, '上海'), (2, '北京'),),
#     widget=widgets.CheckboxSelectMultiple
# )

在使用选择标签时,需要注意choices的选项可以从数据库中获取,但是由于是静态字段 获取的值无法实时更新,那么需要自定义构造方法从而达到此目的。

方式一:

from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.validators import RegexValidator
 
class MyForm(Form):
 
    user = fields.ChoiceField(
        # choices=((1, '上海'), (2, '北京'),),
        initial=2,
        widget=widgets.Select
    )
 
    def __init__(self, *args, **kwargs):
        super(MyForm,self).__init__(*args, **kwargs)
        # self.fields['user'].widget.choices = ((1, '上海'), (2, '北京'),)
        # 或
        self.fields['user'].widget.choices = models.Classes.objects.all().value_list('id','caption')

方式二 :

使用django提供的ModelChoiceField和ModelMultipleChoiceField字段来实现

from django import forms
from django.forms import fields
from django.forms import widgets
from django.forms import models as form_model
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
 
class FInfo(forms.Form):
    authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all())
    # authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all())

1.13 ModelForm

Model + Form ==> ModelForm。model和form的结合体,所以有以下功能:

  • 验证
  • 数据库操作

model有操作数据库的字段,form验证也有那几个字段,虽然耦合度降低,但是代码是有重复的。如果利用model里的字段,那是不是form里的字段就不用写了。

1.13.1 ModelForm操作及验证

**未使用ModelForm之前 **

models.py

from django.db import models
 
# Create your models here.
class UserType(models.Model):
    caption=models.CharField(max_length=32)
class UserInfo(models.Model):
    username=models.CharField(verbose_name='用户名',max_length=32)
    email=models.EmailField()
    user_type=models.ForeignKey(to='UserType',to_field='id',on_delete=models.CASCADE)

views.py

#coding:utf-8
from django.shortcuts import render,HttpResponse
from django import forms
from django.forms import fields
from .  import models
# Create your views here.
class UserInfoForm(forms.Form):
    username=fields.CharField(max_length=32)
    email=fields.EmailField()
    user_type=fields.ChoiceField(
        choices=models.UserType.objects.values_list('id','caption')
    )
    #让数据在网页实时更新
    def __init__(self,*args,**kwargs):
        super(UserInfoForm,self).__init__(*args,**kwargs)
        self.fields['user_type'].choices=models.UserType.objects.values_list('id','caption')
def index(request):
    if request.method=='GET':
        obj=UserInfoForm()
        return render(request,'index.html',{'obj',obj})
    if request.method=='POST':
        obj=UserInfoForm(request.POST)
        if obj.is_valid():
            models.UserInfo.objects.create(**obj.cleaned_data)
            return HttpResponse('上传成功')
        else:
            return render(request, 'index.html', {'obj', obj})

index.html




    
    index


{% csrf_token %} {{ obj.as_p }}

novalidate 注: HTML5输入类型和浏览器验证

如果表单中包含URLField、EmailField和其他整数字段类似,Django将使用url、email和number这样的HTML5输入类型。默认情况下,浏览器可能会对这些字段进行他们自身的验证,这些验证可能比Django的验证更严格。如果你想禁用这个行为,请设置form标签的novalidate属性,或者制定一个不同的字段,如TextInput。

使用ModelForm之后

只需修改views.py

#coding:utf-8
from django.shortcuts import render,HttpResponse
from django import forms
from django.forms import fields
from .  import models
# Create your views here.
class UserInfoModelForm(forms.ModelForm):
    class Meta:
        model=models.UserInfo
        fields='__all__'#展示所有字段
        #fields=['username']#只展示username
        #exclude=['username']
def index(request):
    if request.method=='GET':
        obj=UserInfoModelForm()
        return render(request,'index.html',{'obj':obj})
    if request.method=='POST':
        obj=UserInfoModelForm(request.POST)
        print(obj.is_valid())
        print(obj.cleaned_data)
        print(obj.errors)
        return HttpResponse('提交成功')

1.13.2 ModelForm组件

ModelForm
    a.  class Meta:
            model,                           # 对应Model的
            fields=None,                     # 字段
            exclude=None,                    # 排除字段
            labels=None,                     # 提示信息
            help_texts=None,                 # 帮助提示信息
            widgets=None,                    # 自定义插件
            error_messages=None,             # 自定义错误信息(整体错误信息from django.core.exceptions import NON_FIELD_ERRORS)
            field_classes=None               # 自定义字段类 (也可以自定义字段)
            localized_fields=('birth_date',) # 本地化,如:根据不同时区显示数据
            如:
                数据库中
                    2016-12-27 04:10:57
                setting中的配置
                    TIME_ZONE = 'Asia/Shanghai'
                    USE_TZ = True
                则显示:
                    2016-12-27 12:10:57
    b. 验证执行过程
        is_valid -> full_clean -> 钩子 -> 整体错误
 
    c. 字典字段验证
        def clean_字段名(self):
            # 可以抛出异常
            # from django.core.exceptions import ValidationError
            return "新值"
    d. 用于验证
        model_form_obj = XXOOModelForm()
        model_form_obj.is_valid()
        model_form_obj.errors.as_json()
        model_form_obj.clean()
        model_form_obj.cleaned_data
    e. 用于创建
        model_form_obj = XXOOModelForm(request.POST)
        #### 页面显示,并提交 #####
        # 默认保存多对多
            obj = form.save(commit=True)
        # 不做任何操作,内部定义 save_m2m(用于保存多对多)
            obj = form.save(commit=False)
            obj.save()      # 保存单表信息
            obj.save_m2m()  # 保存关联多对多信息
 
    f. 用于更新和初始化
        obj = model.tb.objects.get(id=1)
        model_form_obj = XXOOModelForm(request.POST,instance=obj)
        ...
 
        PS: 单纯初始化
            model_form_obj = XXOOModelForm(initial={...})

**注意:**导入模块名(fields、widgets)和字段名重复,所以导入时要起个别名。

from django import forms
from django.forms import fields as Ffields
from django.forms import widgets as Fwidgets
class UserInfoModelForm(forms.ModelForm):
 
    is_rmb = Ffields.CharField(widget=Fwidgets.CheckboxInput())
 
    class Meta:
        model = models.UserInfo
        fields = '__all__'
        # fields =  ['username','email']
        # exclude = ['username']
        labels = {
            'username': '用户名',
            'email': '邮箱',
        }
        help_texts = {
            'username': '...'
        }
        widgets = {
            'username': Fwidgets.Textarea(attrs={'class': 'c1'})
        }
        error_messages = {
            '__all__':{    # 整体错误信息
 
            },
            'email': {
                'required': '邮箱不能为空',
                'invalid': '邮箱格式错误..',
            }
        }
        field_classes = {  # 定义字段的类是什么
            # 'email': Ffields.URLField  # 这里只能填类,加上括号就是对象了。
        }
 
        # localized_fields=('ctime',)  # 哪些字段做本地化

你可能感兴趣的:(Python)