Django是Python中的一个模块,用于做网站后端管理的。在学Djang之前,了解一下Web框架
Web框架
Web框架可分为两类,MVC和MTV,本质上是同一个Web框架,只是名称不同。
1、MVC:Modal(数据库)、View(模板文件)、Controller(业务处理)
2、MTV:Modal(数据库),Template(模板文件)、View(业务处理)
Django使用的Web框架是MTV。
安装
windows系统:
pip3 install django
创建工程流程
方法一、
1、添加python、django-admin到环境变量(不是必需的)
2、django-admin startproject <project_name>
3、测试:cd <project_name>
python manage.py runserver
4、网页浏览测试
方法二、
在pycharm(professional)中创建project
Django初次使用
初次感受Djang的流程图:
1、添加一个视图函数
2、在urls.py中添加路径和其对应的视图函数。
目录结构
<project_name>
<project_name>
__init__.py
settings.py # 配置文件
urls.py # url与view的对应关系
wsgi.py # 遵循wsgi规范,上线后建议用uwsgi+nginx
manage.py # 管理Django程序:
python manage.py runserver 127.0,0.1:8000
python manage.py startapp <app_name>
python manage.py makemigrations
python manage.py migrate
APP相关
一、创建APP
python manage.py start app <app_name>
创建新的APP,就是方便把视图函数放到APP中。
二、APP目录结构
migrations # 数据表结构操作记录
__init__.py
admin.py # 为用户提供后台管理,主要是管理数据库
apps.py # 当前appr的配置文件
models.py # 表结构类
tests.py # 单元测试
views.py # 业务处理
初试登录页面
步骤一、在Django工作中创建templates文件夹,按如下新建一个html页面
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 <style> 7 .content{ 8 width:80px; 9 text-align: right; 10 display: inline-block; 11 } 12 </style> 13 </head> 14 <body> 15 <form action="/login" method="post"> 16 <p> 17 <label class="content" for="username">用户名:</label> 18 <input type="text" id="username"\> 19 </p> 20 <p> 21 <label class="content" for="password">密码:</label> 22 <input type="password" id="password" \> 23 <input type="submit" value="提交" \> 24 </p> 25 </form> 26 </body> 27 </html>
步骤二、新建一个app
步骤三、在工程urls中建立对应关系。
步骤四、在app的views中定义处理函数,有两种方法
方法1、利用HttpResponse模块
方法2、利用render模块(推荐)
神奇的render:需要传入两个参数,request和模板。但模板好像不用加路径就会找到?那是因为在工程的settings.py中已经配置了模板的路径,如下:
静态文件配置
静态文件如js、css等文件需新件一个static文件夹,放到此文件夹。然后在settings中最后加上
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static'),
)
注意:逗号不能省略。
CSRF
什么是CSRF?
CSRF(Cross-site request forgery),中文名称是跨站请求伪造。攻击情况如下:
A电脑登陆了B网站,而同时又没有关闭浏览器,则cookie是会存在的。A电脑又登陆了C网站,而此网站可能具有攻击性。此时C返回A一个网页,有form请求,如果是提交带着cookie提交给C,则C则获取A的cookie,而可以访问其他访问。
如何防CSRF攻击
一般情况下,POST表单提交就有CSRF攻击的风险。而要防止CSRF攻击可以这样,在第一次GET请求时,会返回加密的字符串给客户端,此字符串只能服务器才能解密。下一次提交POST表单时,则用户在善带着此字符串提交。
在FORM表单中防止CSRF攻击
如果不带csrf_token进行post请求,则会出现以下情况
解决办法:
在模板的form表单中添加{% csrf_token %}
在Ajax提交中防止CSRF攻击
方式一、在每个Ajax请求中添加请求头
步骤一、获取csrf_token
步骤二、在请求头中添加csrf_token键值对。
方式二、在Ajaxsetup中添加请求头,这样就不用每个ajax请求都需要添加头部信息
最好加上判断是否是post请求。
CSRF的禁用与启用
默认情况下,django是全局开启csrf防攻击的功能。
全局禁用方法:
局部的禁用与启用
导入:from django.views.decorators.csrf import csrf_exempt, csrf_protect
局部禁用:在view函数前加入@csrf_exempt,配合全局启用使用
局部启用:在view函数前加入@csrf_protect,配合全局禁用使用
VIEWS
响应方式
一、HttpResponse:
导入:from django.http import HttpResponse
使用方法:HttpResponse(<html_code>) # 用html_code响应
设置响应头
res = HttpResponse(...)
res['name']='alex'
二、render
导入:from django.shortcut import render
使用方法:render(request,<template_name>,[<传递给前端的变量字典>]) # 读取模板文件,返回客户端,并可传递变量给前端
三、redirect
导入:from django.shortcut import redirect
以GET方式请求
使用方法:redirect(<url>) # 重定向
redirect(<urlpattern>)
客户请求信息
会自动传入一个客户请求信息给视图函数,其中包含头部信息、参数内容等,一般有以下操作
一、request.method # 获取请求方式
二、request.POST.get(<key>,None) # 获取客户发过来的键值为key的值
三、request.GET.get(<key>, None) # 与上类似
四、obj = request.FILES.get(<key>, None) # 用于类型为file的input标签,上传文件所使用
obj.name:获取文件名称
obj.chunks():一个生成器,通过循环可获取其内容,然后写到文件里
案例如下:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <form action="/index/" method="post" enctype="multipart/form-data"> 9 <p> 10 篮球<input type="checkbox" name="favor" value="basketball"> 11 足球<input type="checkbox" name="favor" value="football"> 12 网球<input type="checkbox" name="favor" value="tennis"> 13 台球<input type="checkbox" name="favor" value="snooker"> 14 </p> 15 <p> 16 <select name="city" multiple> 17 <option value="bj">北京</option> 18 <option value="sh">上海</option> 19 <option value="gz">广州</option> 20 <option value="gl">桂林</option> 21 </select> 22 </p> 23 <p> 24 <input type="file" name="filetransfer"> 25 </p> 26 <p> 27 <input type="submit"> 28 </p> 29 </form> 30 </body> 31 </html>
1 from django.shortcuts import render 2 from django.http import HttpResponse 3 import os 4 5 # Create your views here. 6 7 8 def index(request): 9 # request.POST.getlist测试 10 # if request.method == 'POST': 11 # favors = request.POST.getlist('favor', None) 12 # cities = request.POST.getlist('city', None) 13 # print(favors, cities) 14 # 上传文件 15 obj = request.FILES.get('filetransfer', None) 16 file = os.path.join('upload', obj.name) 17 f = open(file, mode='wb') 18 for item in obj.chunks(): 19 f.write(item) 20 f.close() 21 return render(request, 'index.html')
五、request.POST.getlist(<key>,None):主要是针对checkbox、select(可多选)等这些会返回多个值的标签而使用的,可参考上面的views。
六、request.URL:获取当前URl
七、request.COOKIES
八、request.environ:获取请求的所有信息,是不个字典
九、request.body:由于django只帮我们封装了get、post的请求,但put、delete请求并没有封装。如果需要处理这一些请求,请在此处处理。是徐了请求头的所有源生数据
十、request.Meta:请求头的所有源生数据
FBV和CBV
一、FBV:是function base views的简称,意思即是视图以函数的方式编写。
二、CBV:是class base views的简称,视图以类的方式编写。这里重点说明这种方式的使用步骤
1、
1 from django.contrib import admin 2 from django.urls import path 3 from app01 import views 4 5 urlpatterns = [ 6 path('admin/', admin.site.urls), 7 path('index/', views.index), 8 path('home/', views.Home.as_view()), 9 ]
2、
1 from django.shortcuts import render 2 from django.http import HttpResponse 3 from django.views import View 4 import os 5 6 # Create your views here. 7 8 9 def index(request): 10 # request.POST.getlist测试 11 # if request.method == 'POST': 12 # favors = request.POST.getlist('favor', None) 13 # cities = request.POST.getlist('city', None) 14 # print(favors, cities) 15 # 上传文件 16 # obj = request.FILES.get('filetransfer', None) 17 # file = os.path.join('upload', obj.name) 18 # f = open(file, mode='wb') 19 # for item in obj.chunks(): 20 # f.write(item) 21 # f.close() 22 return render(request, 'index.html') 23 24 25 class Home(View): 26 def post(self, request): 27 return HttpResponse('POST产生的页面') 28 29 def get(self, request): 30 return HttpResponse('Get产生的页面')
导入模块
创建类
关于View类中的dispatch方法
这个方法是所有请求的入口,也是请求的出口。这是个分配器,会找到子类中定义的处理请求方法,如get、post等,然后把请求发到对应的方法。当相应的方法处理完成后,就又会返回给dispatch函数,再返回给客户端。所以我们如果需要定义一些在请求前和请求后的自定义功能,可重写此类。如下:
Cookies
什么是Cookies?
Cookies主要用于需要登录的网站。当用户名、密码正确时,则返回一个cookies给客户端并且记录在服务器上。cookies实际上是键值对。此时以后客户端需要登录时,如果还没失效则拿着此cookie去服务器验证。
认证通过时Cookies从服务器端发送到客户端
<HttpResponse对象>.set_cookie(<key>,<value>......)
此cookies会在响应头处返回给用户。
默认情况下,关闭浏览器则cookie失效。
rep
=
HttpResponse(...) 或 rep = render(request, ...)
rep.set_cookie(key,value,...)
rep.set_signed_cookie(key,value,salt
=
'加密盐'
,...)
参数:
key, 键
value
=
'', 值
max_age
=
None
, 超时时间(秒为单位)
expires
=
None
, 设置超时的日期
path
=
'/'
, Cookie生效的路径,
/
表示根路径,特殊的:跟路径的cookie可以被任何url的页面访问
domain
=
None
, Cookie生效的域名
secure
=
False
, https传输
httponly
=
False
只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)
获取客户端cookie
一、非加密cookie
request.COOKIES.get(<key>)
二、加密cookie
request.get_signed_cookie(<key>,salt=<salt>)
session
由于cookie是保存在客户端浏览器的,所以cookies这种认证方式不适用于敏感信息的认证。这时候可以用session认证了
什么是session
其实session也是依赖于cookie的,因为如果登陆成功,session流程是这样的:
使用步骤
一、由于默认情况下,session保存在本地的数据库,所以需要在本地创建数据库
python manage.py makemigrations
python manage.py migrate
Django中默认支持Session,其内部提供了5种类型的Session供开发者使用:
- 数据库(默认)
- 缓存
- 文件
- 缓存+数据库
- 加密cookie
1、数据库Session(默认情况下)
2、缓存session
a. 配置 settings.py SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 引擎 SESSION_CACHE_ALIAS = 'default' # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置 CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': [ '172.19.26.240:11211', '172.19.26.242:11211', ] } }
3、文件session
1 SESSION_ENGINE = 'django.contrib.sessions.backends.file' # 引擎 2 SESSION_FILE_PATH = None # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir()
4、缓存+数据库Session
1 数据库用于做持久化,缓存用于提高效率 2 3 a. 配置 settings.py 4 5 SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' # 引擎
二、Views中的使用
1 from django.shortcuts import render, redirect 2 from django.http import HttpResponse 3 4 # Create your views here. 5 6 7 def login(request): 8 if request.method == 'GET': 9 return render(request, 'login.html') 10 else: 11 username = request.POST.get('username') 12 password = request.POST.get('pwd') 13 if username == 'root' and password == 'abc123': 14 # 如果用户密码输入正确,会做以下事情 15 # 在数据库生成随机字符串,以作为键 16 # 以随机字符串为键的值中,写入用户信息到数据库 17 # 把随机字符串写入到客户端浏览器的cookie中 18 # django只需要以下的语句就可以完成 19 request.session['username'] = 'root' 20 request.session['is_login'] = True 21 if request.POST.get('excess_time', None) == '10': 22 request.session.set_expiry(10) 23 return redirect('/index/') 24 else: 25 return redirect('/login/') 26 27 28 def index(request): 29 auth_res = request.session.get('is_login', None) 30 if auth_res: 31 return render(request, 'index.html') 32 else: 33 return HttpResponse('滚') 34 35 36 def logout(request): 37 request.session.clear() 38 return redirect('/login/')
注意session的配置和操作。
1 a. 配置 settings.py 2 3 SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默认) 4 5 SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认) 6 SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径(默认) 7 SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名(默认) 8 SESSION_COOKIE_SECURE = False # 是否Https传输cookie(默认) 9 SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输(默认) 10 SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周)(默认) 11 SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期(默认) 12 SESSION_SAVE_EVERY_REQUEST = False # 是否每次请求都保存Session,默认修改之后才保存(默认)。如果为True,则每次有session操作,则会更新过期时间为此操作后的SESSION_COOKIE_AGE时间。。 13 14 15 16 b. 使用 17 18 def index(request): 19 # 获取、设置、删除Session中数据 20 request.session['k1'] 21 request.session.get('k1',None) 22 request.session['k1'] = 123 23 request.session.setdefault('k1',123) # 存在则不设置 24 del request.session['k1'] 25 26 # 所有 键、值、键值对 27 request.session.keys() 28 request.session.values() 29 request.session.items() 30 request.session.iterkeys() 31 request.session.itervalues() 32 request.session.iteritems() 33 34 35 # 用户session的随机字符串 36 request.session.session_key 37 38 # 将所有Session失效日期小于当前日期的数据删除 39 request.session.clear_expired() 40 41 # 检查 用户session的随机字符串 在数据库中是否 42 request.session.exists("session_key") 43 44 # 删除当前用户的所有Session数据 45 request.session.delete("session_key") 46 47 request.session.set_expiry(value) 48 * 如果value是个整数,session会在些秒数后失效。 49 * 如果value是个datatime或timedelta,session就会在这个时间后失效。 50 * 如果value是0,用户关闭浏览器session就会失效。 51 * 如果value是None,session会依赖全局session失效策略,即配置文件
FBV和CBV的装饰器
FBV装饰器
1 from django.shortcuts import render, redirect 2 from django.http import HttpResponse 3 from django.urls import reverse 4 from django.utils.safestring import mark_safe 5 import datetime 6 7 # Create your views here. 8 player_list = ['Scholes', 'Keane', 'Beckham', 'Giggs'] 9 10 11 def auth(func): 12 def inner(request, *args, **kwargs): 13 username = request.COOKIES.get('authcode') 14 if not username: 15 return redirect('/login/') 16 return func(request, *args, **kwargs) 17 return inner 18 19 20 class Pagination(object): 21 def __init__(self, itemcount_per_page, item_count, current_page=1, pagination_show_count=11): 22 self.itemcount_per_page = itemcount_per_page 23 self.item_count = item_count 24 self.current_page = current_page 25 self.pagination_show_count = pagination_show_count 26 27 @property 28 def start(self): 29 """计算条目起始索引""" 30 return (self.current_page - 1) * self.itemcount_per_page 31 32 @property 33 def end(self): 34 """计算条目结束索引""" 35 return self.current_page * self.itemcount_per_page 36 37 @property 38 def page_count(self): 39 """计算总页数""" 40 total_count, remainder = divmod(self.item_count, self.itemcount_per_page) 41 if remainder: 42 total_count += 1 43 return total_count 44 45 def make_page_str(self): 46 """生成页码html""" 47 # 如果总页数少于要显示的页数 48 page_str_list = [] 49 if self.page_count <= self.pagination_show_count: 50 pagination_start = 1 51 pagination_end = self.page_count + 1 52 else: 53 # 如果是页码在中间的情况 54 pagination_start = self.current_page - (self.pagination_show_count - 1)/2 55 pagination_end = self.current_page + (self.pagination_show_count + 1)/2 56 # 页码在头 57 if self.current_page <= (self.pagination_show_count + 1)/2: 58 pagination_start = 1 59 pagination_end = self.pagination_show_count + 1 60 # 页码在后面 61 if self.current_page + (self.pagination_show_count - 1)/2 >= self.page_count: 62 pagination_end = self.page_count + 1 63 pagination_start = self.page_count - self.pagination_show_count + 1 64 if self.current_page != 1: 65 page_str_list.append('<a class="pagination" href="/user_list/?p=%s">上一页</a>' % (self.current_page - 1)) 66 else: 67 page_str_list.append('<a class="pagination">上一页</a>') 68 for i in range(int(pagination_start), int(pagination_end)): 69 if i == self.current_page: 70 page_str_list.append('<a class="pagination active" href="/user_list/?p=%s">%s</a>' % (i, i)) 71 else: 72 page_str_list.append('<a class="pagination" href="/user_list/?p=%s">%s</a>' % (i, i)) 73 if self.current_page != self.page_count: 74 page_str_list.append('<a class="pagination" href="/user_list/?p=%s">下一页</a>' % (self.current_page + 1)) 75 else: 76 page_str_list.append('<a class="pagination">下一页</a>') 77 page_str = ''.join(page_str_list) 78 page_str = mark_safe(page_str) 79 return page_str 80 81 82 items_list = [] 83 for i in range(200): 84 items_list.append(i) 85 86 87 def user_list(request): 88 # 当前页码 89 current_page = request.GET.get('p', 1) 90 # 由于获取的是字符串,所以需要转换成数字 91 current_page = int(current_page) 92 # 每页显示的条目数 93 itemcount_per_page = request.COOKIES.get('items_count_per_page', 10) 94 itemcount_per_page = int(itemcount_per_page) 95 # 显示的页码数 96 pagination_show_count = 11 97 page_obj = Pagination(itemcount_per_page, len(items_list), current_page, pagination_show_count) 98 html_str = page_obj.make_page_str() 99 items_show_list = items_list[page_obj.start:page_obj.end] 100 return render(request, 'user_list.html', {'items_show_list': items_show_list, 'page_str': html_str}) 101 102 103 auth_db = {'Treelight': 'abc123'} 104 105 106 def login(request): 107 # 处理get请求 108 if request.method == 'GET': 109 return render(request, 'login.html') 110 # 处理post请求 111 else: 112 username = request.POST.get('username', None) 113 password = request.POST.get('pwd', None) 114 # 获取用户名是否存在的结果 115 db_pwd = auth_db.get(username) 116 # 用户名不存在 117 if not db_pwd: 118 return redirect('/login/') 119 # 用户存在 120 else: 121 # 密码正确 122 if password == db_pwd: 123 res = redirect('/index/') 124 now = datetime.datetime.utcnow() 125 expire = now + datetime.timedelta(seconds=10) 126 res.set_cookie('auth_code', username, expires=expire) 127 return res 128 # 密码错误 129 else: 130 return redirect('/login/') 131 132 133 @auth 134 def index(request): 135 username = request.COOKIES.get('auth_code') 136 # if not username: 137 # return redirect('/login/') 138 return render(request, 'index.html', {'username': username})
CBV装饰器
使用方式有三种,但都需要导入一个模块
from django.utils.decorators import method_decorator
方式一、直接在类里面的函数添加
1 from django.shortcuts import render, redirect 2 from django.utils.safestring import mark_safe 3 from django.views import View 4 from django.utils.decorators import method_decorator 5 import datetime 6 7 # Create your views here. 8 player_list = ['Scholes', 'Keane', 'Beckham', 'Giggs'] 9 10 11 def auth(func): 12 def inner(request, *args, **kwargs): 13 username = request.COOKIES.get('authcode') 14 if not username: 15 return redirect('/login/') 16 return func(request, *args, **kwargs) 17 return inner 18 19 20 class Pagination(object): 21 def __init__(self, itemcount_per_page, item_count, current_page=1, pagination_show_count=11): 22 self.itemcount_per_page = itemcount_per_page 23 self.item_count = item_count 24 self.current_page = current_page 25 self.pagination_show_count = pagination_show_count 26 27 @property 28 def start(self): 29 """计算条目起始索引""" 30 return (self.current_page - 1) * self.itemcount_per_page 31 32 @property 33 def end(self): 34 """计算条目结束索引""" 35 return self.current_page * self.itemcount_per_page 36 37 @property 38 def page_count(self): 39 """计算总页数""" 40 total_count, remainder = divmod(self.item_count, self.itemcount_per_page) 41 if remainder: 42 total_count += 1 43 return total_count 44 45 def make_page_str(self): 46 """生成页码html""" 47 # 如果总页数少于要显示的页数 48 page_str_list = [] 49 if self.page_count <= self.pagination_show_count: 50 pagination_start = 1 51 pagination_end = self.page_count + 1 52 else: 53 # 如果是页码在中间的情况 54 pagination_start = self.current_page - (self.pagination_show_count - 1)/2 55 pagination_end = self.current_page + (self.pagination_show_count + 1)/2 56 # 页码在头 57 if self.current_page <= (self.pagination_show_count + 1)/2: 58 pagination_start = 1 59 pagination_end = self.pagination_show_count + 1 60 # 页码在后面 61 if self.current_page + (self.pagination_show_count - 1)/2 >= self.page_count: 62 pagination_end = self.page_count + 1 63 pagination_start = self.page_count - self.pagination_show_count + 1 64 if self.current_page != 1: 65 page_str_list.append('<a class="pagination" href="/user_list/?p=%s">上一页</a>' % (self.current_page - 1)) 66 else: 67 page_str_list.append('<a class="pagination">上一页</a>') 68 for i in range(int(pagination_start), int(pagination_end)): 69 if i == self.current_page: 70 page_str_list.append('<a class="pagination active" href="/user_list/?p=%s">%s</a>' % (i, i)) 71 else: 72 page_str_list.append('<a class="pagination" href="/user_list/?p=%s">%s</a>' % (i, i)) 73 if self.current_page != self.page_count: 74 page_str_list.append('<a class="pagination" href="/user_list/?p=%s">下一页</a>' % (self.current_page + 1)) 75 else: 76 page_str_list.append('<a class="pagination">下一页</a>') 77 page_str = ''.join(page_str_list) 78 page_str = mark_safe(page_str) 79 return page_str 80 81 82 items_list = [] 83 for i in range(200): 84 items_list.append(i) 85 86 87 def user_list(request): 88 # 当前页码 89 current_page = request.GET.get('p', 1) 90 # 由于获取的是字符串,所以需要转换成数字 91 current_page = int(current_page) 92 # 每页显示的条目数 93 itemcount_per_page = request.COOKIES.get('items_count_per_page', 10) 94 itemcount_per_page = int(itemcount_per_page) 95 # 显示的页码数 96 pagination_show_count = 11 97 page_obj = Pagination(itemcount_per_page, len(items_list), current_page, pagination_show_count) 98 html_str = page_obj.make_page_str() 99 items_show_list = items_list[page_obj.start:page_obj.end] 100 return render(request, 'user_list.html', {'items_show_list': items_show_list, 'page_str': html_str}) 101 102 103 auth_db = {'Treelight': 'abc123'} 104 105 106 def login(request): 107 # 处理get请求 108 if request.method == 'GET': 109 return render(request, 'login.html') 110 # 处理post请求 111 else: 112 username = request.POST.get('username', None) 113 password = request.POST.get('pwd', None) 114 # 获取用户名是否存在的结果 115 db_pwd = auth_db.get(username) 116 # 用户名不存在 117 if not db_pwd: 118 return redirect('/login/') 119 # 用户存在 120 else: 121 # 密码正确 122 if password == db_pwd: 123 res = redirect('/index/') 124 now = datetime.datetime.utcnow() 125 expire = now + datetime.timedelta(seconds=10) 126 res.set_cookie('auth_code', username, expires=expire) 127 return res 128 # 密码错误 129 else: 130 return redirect('/login/') 131 132 133 @auth 134 def index(request): 135 username = request.COOKIES.get('auth_code') 136 return render(request, 'index.html', {'username': username}) 137 138 139 class Order(View): 140 @method_decorator(auth) 141 def get(self, request): 142 username = request.COOKIES.get('auth_code') 143 return render(request, 'index.html', {'username': username}) 144 145 def post(self, request): 146 pass
方式二、在dispatch方法中装饰,如此一来,此类中的所有方法都会使用此装饰器
class Order(View): @method_decorator(auth) def dispatch(self, request, *args, **kwargs): return super(Order, self).dispatch(request, *args, **kwargs) def get(self, request): username = request.COOKIES.get('auth_code') return render(request, 'index.html', {'username': username}) def post(self, request): pass
方式三、把整个类都装饰了,效果和方式二是一样的
再试登录页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="/static/commons.css"> <style> .content{ width:80px; text-align: right; display: inline-block; } .error{ color:red; } </style> </head> <body> <form action="/login/" method="post"> <p> <label class="content" for="username">用户名:</label> <input type="text" id="username" name="user" \> </p> <p> <label class="content" for="password">密码:</label> <input type="password" id="password" name="pwd" \> <input type="submit" value="提交" \> <span class="error">{{ error_msg }}</span> </p> </form> <script src="/static/jquery-1.12.4.js"></script> </body> </html>
from django.shortcuts import render from django.shortcuts import redirect from django.http import HttpResponse # Create your views here. def login(request): # request参数是客户端改过来的数据,包括头数据 # request.method获取请求方法 error_msg = '' if request.method == 'POST': # 从客户的请求中找到name为user的标签的值,如果没有则返回None username = request.POST.get('user', None) pwd = request.POST.get('pwd', None) if username == 'Treelight' and pwd == 'dczx5501': # 跳转到百度 return redirect('https://www.baidu.com') else: error_msg = '用户名或密码错误' return render(request, 'login.html', {'error_msg': error_msg})
关键点:
Views
Html:
案例:登录页面与后台交互,并且能添加数据
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> .header { background-color: #eeeeee; height: 40px; } </style> </head> <body style="margin: 0 auto"> <div class="header"></div> <form action="/home/" method="post"> <input type="text" name="username" placeholder="用户名"> <input type="text" name="gender" placeholder="性别"> <input type="text" name="email" placeholder="邮箱"> <input type="submit" value="添加"> </form> <table border="1"> <thead> <tr> <th>用户名</th> <th>性别</th> <th>邮箱</th> </tr> </thead> <tbody> {% for row in user_list %} <tr> <td>{{ row.username }}</td> <td>{{ row.gender }}</td> <td>{{ row.email }}</td> </tr> {% endfor %} </tbody> </table> </body> </html>
from django.shortcuts import render from django.shortcuts import redirect from django.http import HttpResponse # Create your views here. def login(request): # request参数是客户端改过来的数据,包括头数据 # request.method获取请求方法 error_msg = '' if request.method == 'POST': # 从客户的请求中找到name为user的标签的值,如果没有则返回None username = request.POST.get('user', None) pwd = request.POST.get('pwd', None) if username == 'Treelight' and pwd == 'dczx5501': # 跳转到百度 return redirect('/home/') else: error_msg = '用户名或密码错误' return render(request, 'login.html', {'error_msg': error_msg}) user_list = [ {'username': 'Scholes', 'gender': 'male', 'email': 'Scholes@MU.com'}, {'username': 'Keane', 'gender': 'male', 'email': 'Keane@MU.com'}, {'username': 'Beckham', 'gender': 'male', 'email': 'Beckham@MU.com'}, {'username': 'Giggs', 'gender': 'female', 'email': 'Scholes@MU.com'}, ] def home(request): if request.method == 'POST': username = request.POST.get('username', None) gender = request.POST.get('gender', None) email = request.POST.get('email', None) user_list.append( {'username': username, 'gender': gender, 'email': email}, ) return render(request, 'home.html', {'user_list': user_list})
新知识点:
一、在Djang模板中
二、
Django模板语言
一、单体变量:用两个大括号
二、for循环:{% for %}
.....
{% endfor %}
注意:在for循环中,有5个特殊的变量,
{{ forloop.counter }}:序号计数器,从1开始
{{ forloop.counter0 }}:序号计数器,从0开始
{{ forloop.revcounter }}:序号计数器,倒序,从大到小,最后一个是1
{{ forloop.revcounter0 }}:序号计数器,倒序,从大到小,最后一个是0
{{ forloop.last }}:判断是否是最后一个
{{ forloop.first }}:判断是否是第一个
{{ forloop.parentloop }}:不知道怎么表达。。。
三、获取字典的值。与python不同,通过<dict>.<key>获取值
四、循环字典
模板的继承
你应该会经常发现,在多个网页中,这些网页都使用到类似的html,但有个别位置的内容又不同。比如在一个网站的后台管理页面中,网页的头部和网页的左边菜单是一样的,但网页的右边的内容是不同的。这时候为了代码的简化,可以使用继承和引入。比如以上例子,网页的头部和网页的左边菜单可以做成一个模板,如下:
继承
引入
模板中的内置函数
{{ item.event_start|date:"Y-m-d H:i:s"}} # 转换日期格式
{{ bio|truncatewords:"30" }} # 取前30个字符
{{ my_list|first|upper }} # 首字母大写
{{ name|lower }} # 转换成小写
模板语言中自定义函数
但是由于在模板语言中可使用的函数方法太少了,所以django就提供了一个机制,可以在python中自定义函数,然后这些函数可在模板语言中使用。牛!有两种方式,各有优缺点
simple_tag
步骤如下:
一、在<app_name>中新建一个templatetags文件夹,注意此文件夹名称是固定的,不能更改。
二、在templatetags文件夹中新建一个py文件,名字可自己起
三、在新建的py文件中需按以下步骤做:
四、注册APP?(好像在新版本的django中不用)
五、使用方法:
1、在html顶部中添加{% load <py_name> %}
2、在需要使用的地方添加{% <fun_name> arg1 arg2 %}
filter
这个的步骤和simple_tag中的一、二、四一样的,这里说说不同的步骤
步骤三、函数的定义
步骤五、使用
simple_tag和filter中的优缺点
一、simple_tag
优点:可传多个参数,多个参数之间可有多个空格分隔
缺点:不能作为if的条件判断
二、filter
优点:作为if的条件判断
缺点:只能传两个参数,中间不能有空格
路由系统URL
1、可建立一个url对应一个视图的关系,不使用正则表达式即可。
2、可建立多个url对应一个视图的关系,使用正则表达式。使用步骤如下:
(1)导入模块
(2)、写正则表达式的URL
(3)、此时django会把正则表达式的分组作为参数传进视图函数。如有多个正则分组,则会按顺序传多个参数。类似于python中的位置参数
request为请求的数据,n1、n2为url正则匹配的值
3、也可使用正则表达式,然后分组加名称,则在视图函数中需要有对应的正则分组名称。如下:
必须要对应起来。
路由名称
可在每一个路由中添加一个name属性,如下
这样做有什么作用?就是无论你的url怎么改变,都是使用此对应的视图函数,就是起到绑定路由到指定函数。
但这还没完,还要在模板中做设置,如果是form表单可如下:
但这样之会出现一个问题,就是以上模板只会匹配前缀,而后面的正则表达式就会忽略掉。这个问题如何解决?如下:
如需获取当前URL:可request.path_info
路由名称的作用
上面讲了这么多路由名称,他的主要作用就是在django的模板语言和view视图中重新构建一个新的url,如下:
views视图:
URL不带名称的正则分组:
URL带名称的正则分组
模板语言
在模板中使用格式有所不同
对于不带名称正则分组的模板语言
对于带有名称的正则分组的模板语言
URL分发器
到目前为此,我们的路由系统条目都写在<project_name>.urls.py文件中。但这样不太好,为什么这样说呢?
1、一个django项目可以有多个app,这就要求里面的条目URL不能重复
2、每个app开发人员都要编辑url,这样就会编辑同一个urls.py文件。
解决办法:每个app中也有自己的路由系统,这样就可以只编辑自己的路由系统,不会产生冲突了。具体做法如下:
在路由系统中传递默认值
步骤如下:
1、在urls.py的path参数中添加一个字典,则此字典会把参数传递给对应的视图函数
2、views需添加参数以接收此参数
命名空间
有可能出现以下情况:
有多个app,在<project_name>.urls.py中,使用路由分发(即使用include),urlpattern中前缀不同,但指向的视图函数相同,如果需要获取url,则需要在<project_name>.urls.py中使用命名空间(namespace),以此区分app前缀的不同。案例如下:
父urls.py
子urls.py
模板语言
{% url '<namespace>:<name' args... %}'
数据库
创建步骤
一、在<project_name>.settings.py中的INSTALL_APPS中添加app名称,这个的意义就是要告诉django去哪里创建数据库
二、在app中的models中添加数据类。
null=True可加在字段类型里
三、运行命令:python manage.py makemigrations
python manage.py migrate
此时数据库已经创建完毕。
使用mysql
由于django默认使用的是sqlite数据库引擎,在配置文件有说明
可把此段更改为用mysql引擎,如下:
DATABASES
=
{
'default'
: {
'ENGINE'
:
'django.db.backends.mysql'
,
'NAME'
:
'dbname'
,
'USER'
:
'root'
,
'PASSWORD'
:
'xxx'
,
'HOST'
: '',
'PORT'
: '',
}
}
Django默认使用MySQLdb模块链接MySQL
主动修改为pymysql,在project同名文件夹下的__init__文件中添加如下代码即可:
import pymysql
pymysql.install_as_MySQLdb()
数据库的操作
数据库的操作主要有增删改查,这些操作都要在views.py中导入models文件,如下
一、插入:
方法一、格式如下:models.<class_name>.objects.create(<col>=<val>...)
同时会返回创建的对象
方法二、
先创建一个对象:obj = models.<class_name>(<col>=<val...)
再使用此对象的save方法:obj.save()
方法三、以字典的形式添加,是方法一的变种
格式:models.<class_name>.objects.create(**<dict>)
二、删除
格式:models.<class_name>.objects.<filter(<cond1>...)>.delete()
三、查询
1、格式:models.<class_name>.objects.filter(<cond1>, <cond2>) 有多个条件,则用逗号分隔。返回的是QuerySet类型,相当于列表。
2、models.<class_name>.objects.exclude() 排除不符合条件的
3、models.<class_name>.objects.all().order_by('')
4、models.<class_name>.objects.distinct()
其中cond中的条件有以下例子:
id__gt=1 # id大于1
id__lt=1 # id小于1
id__gte=1 # id大于等于1
id__lte=1 # id小于等于1
id__range=[1,3] # 查找范围
id__in=[1,2,3,4]
2、QuerySet对象的方法
(1)、first():取第一个对象,没有则返回None
(2)、count():个数
(3)、get():取第一个对象,但如果没有则会报错
(4)、all():取所有,返回queryset列表
(5)、query属性:查看select语句
(6)、 values(<col>...):以字典的方式获取数据
(7)、values_list(<col>...):以元组的方式获取数据
(8)、models.<class_name>.objects.filter(id__range=[1,3])
字段类型
AutoField(Field)
- int自增列,必须填入参数 primary_key=True
BigAutoField(AutoField)
- bigint自增列,必须填入参数 primary_key=True
注:当model中如果没有自增列,则自动会创建一个列名为id的列
from django.db import models
class UserInfo(models.Model):
# 自动创建一个列名为id的且为自增的整数列
username = models.CharField(max_length=32)
class Group(models.Model):
# 自定义自增列
nid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
SmallIntegerField(IntegerField):
- 小整数 -32768 ~ 32767
PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
- 正小整数 0 ~ 32767
IntegerField(Field)
- 整数列(有符号的) -2147483648 ~ 2147483647
PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
- 正整数 0 ~ 2147483647
BigIntegerField(IntegerField):
- 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807
自定义无符号整数字段
class UnsignedIntegerField(models.IntegerField):
def db_type(self, connection):
return 'integer UNSIGNED'
PS: 返回值为字段在数据库中的属性,Django字段默认的值为:
'AutoField': 'integer AUTO_INCREMENT',
'BigAutoField': 'bigint AUTO_INCREMENT',
'BinaryField': 'longblob',
'BooleanField': 'bool',
'CharField': 'varchar(%(max_length)s)',
'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
'DateField': 'date',
'DateTimeField': 'datetime',
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'DurationField': 'bigint',
'FileField': 'varchar(%(max_length)s)',
'FilePathField': 'varchar(%(max_length)s)',
'FloatField': 'double precision',
'IntegerField': 'integer',
'BigIntegerField': 'bigint',
'IPAddressField': 'char(15)',
'GenericIPAddressField': 'char(39)',
'NullBooleanField': 'bool',
'OneToOneField': 'integer',
'PositiveIntegerField': 'integer UNSIGNED',
'PositiveSmallIntegerField': 'smallint UNSIGNED',
'SlugField': 'varchar(%(max_length)s)',
'SmallIntegerField': 'smallint',
'TextField': 'longtext',
'TimeField': 'time',
'UUIDField': 'char(32)',
BooleanField(Field)
- 布尔值类型
NullBooleanField(Field):
- 可以为空的布尔值
CharField(Field)
- 字符类型
- 必须提供max_length参数, max_length表示字符长度
TextField(Field)
- 文本类型
EmailField(CharField):
- 字符串类型,Django Admin以及ModelForm中提供验证机制
IPAddressField(Field)
- 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制
GenericIPAddressField(Field)
- 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6
- 参数:
protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6"
unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启刺功能,需要protocol="both"
URLField(CharField)
- 字符串类型,Django Admin以及ModelForm中提供验证 URL
SlugField(CharField)
- 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号)
CommaSeparatedIntegerField(CharField)
- 字符串类型,格式必须为逗号分割的数字
UUIDField(Field)
- 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证
FilePathField(Field)
- 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能
- 参数:
path, 文件夹路径
match=None, 正则匹配
recursive=False, 递归下面的文件夹
allow_files=True, 允许文件
allow_folders=False, 允许文件夹
FileField(Field)
- 字符串,路径保存在数据库,文件上传到指定目录
- 参数:
upload_to = "" 上传文件的保存路径
storage = None 存储组件,默认django.core.files.storage.FileSystemStorage
ImageField(FileField)
- 字符串,路径保存在数据库,文件上传到指定目录
- 参数:
upload_to = "" 上传文件的保存路径
storage = None 存储组件,默认django.core.files.storage.FileSystemStorage
width_field=None, 上传图片的高度保存的数据库字段名(字符串)
height_field=None 上传图片的宽度保存的数据库字段名(字符串)
DateTimeField(DateField)
- 日期+时间格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]
DateField(DateTimeCheckMixin, Field)
- 日期格式 YYYY-MM-DD
TimeField(DateTimeCheckMixin, Field)
- 时间格式 HH:MM[:ss[.uuuuuu]]
DurationField(Field)
- 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型
FloatField(Field)
- 浮点型
DecimalField(Field)
- 10进制小数
- 参数:
max_digits,小数总长度
decimal_places,小数位长度
BinaryField(Field)
- 二进制类型
字段参数
null:是否允许为空
default:设置默认值
primary_key:是否主键
db_column:设置列名,这为了区分列名和类中的属性
db_index:创建索引
unique:创建唯一索引
unique_for_date:创建时间唯一索引
unique_for_month:创建月唯一索引
unique_for_year:创建年唯一索引
auto_now_add:记录数据记录创建时间
auto_now:记录数据更新时间,但只支持以下方式的更新,不支持直接update
obj = models.<class_name>.filter(<cond1>...).first()
obj.<property> = <val>
obj.save
choices:django admin中显示下拉框,避免连表查询
blank:django admin是否能够为空
verbose_name:django admin中显示字段名称
editable:在django admin中能否编辑
error_messages:错误信息
help_text:帮助信息
外键
一对多
一、创建外键
在表结构中,也即models.py的类中,添加语句,格式如下:
<col_name>=models.ForeignKey(<foreign_tb_name>, <to_field>=<foreign_tb_primary_col>, default=1, on_delete=models.CASCADE)
其实这样在此表中添加了一个字段,名为user_group_id,记录的是关联表的主键。
二、添加表结构中带有外键的记录,有两种方法
方法一、
models.<class_name>.objects.create(<col1>=val1...,<for_key>=<关联表的对象>)
方法二、models.<class_name>.objects.create(<col1>=val1...,<for_key>_id=<关联表的对象>)
三、通过外键访问关联表,即跨表访问
方法一、
<本地对象>.<foreignkey>.<关联表字段>
访问关联表主键可<本表对象>.<foreignkey>_id即可。
方法二、如果使用的是QuerySet类中的values或values_list方法,则在其参数中加入以下格式的字符串:
'<foreignkey>__<col>'
四、反查
比如一个主机模型(Host)中有一个外键(department),关联department表,则如何通过department表查询主机?此时需要反查
正查:
host_obj.department
反查
department_obj.host_set.all()
多对多
创建方式一、手动创建
此种方式创建的多对多可这样创建数据记录,HostToApp.objects.create(a_id=<application_id>,h_id=<host_id>)
方式二、自动创建,使用models.ManyToManyField方法。但这种方法不灵活,只会在中间表创建三列。
这种方式创建多对多,有三种方法创建中间表记录
obj = Application.objects.filter(<cond>).first()
1、obj.r.add(1) # 在第三张表中创建obj_id为obj和host_id为1 的记录
2、obj.r.add(2,3,4)
3、obj.r.add(*[2,3,4])
删除
obj.r.remove(1)
obj.r.remove(2,3,4)
obj.r.remove(*[2,3,4])
清除
obj.r.cleare() # 把与obj相关的记录删除
更新
obj.r.set([3,5,6]) # 把原来的删除,再与3 5 6绑定
查询
obj.r.all() # 查询所有的host表的数据
中间件
什么是Django的中间件
完整django请求的生命周期
如果有中间件其中的请求不通过会这样:
所以说中间件其实是在路由系统前就会发生的,在django中是一个类,用其中的process_request函数处理;如果views中函数处理完了以后,则也是要经过中间件来处理,通过相关类中的procee_response处理;其实也可以用process_view对视图函数处理,process_exception对view中函数出现异常处理,process_template_response是在视图中返回的对象中含有render方法才执行。
作用:例如可以对于所有的客户请求,在到达路由系统前,作一些验证的工作。也可以作一些视图函数处理后的后期工作。其实就是对请求的事前事后的统一处理。
关于process_view
process_exception
process_template_response
执行条件:就是views中返回的对象中含有render方法
自定义中间件
步骤一、创建文件夹
步骤二、写中间件的类
(1)、导入:from django.utils.deprecation import MiddlewareMixin
(2)、写类,需继承MiddlewareMixin,但注意写以下方法:process_request和process_response、process_view、process_exception、process_template_response
1 from django.utils.deprecation import MiddlewareMixin 2 3 4 class route1(MiddlewareMixin): 5 def process_request(self, request): 6 print('路由1') 7 8 def process_response(self, request, response): 9 print('返回1') 10 return response 11 12 13 class route2(MiddlewareMixin): 14 def process_request(self, request): 15 print('路由2') 16 17 def process_response(self, request, response): 18 print('返回2') 19 return response 20 21 class route3(MiddlewareMixin): 22 def process_request(self, request): 23 print('路由3') 24 25 def process_response(self, request, response): 26 print('返回3') 27 return response
1 from django.utils.deprecation import MiddlewareMixin 2 from django.http import HttpResponse 3 4 5 class route1(MiddlewareMixin): 6 def process_request(self, request): 7 print('路由1') 8 9 def process_response(self, request, response): 10 print('返回1') 11 return response 12 13 14 class route2(MiddlewareMixin): 15 def process_request(self, request): 16 print('路由2') 17 return HttpResponse('走') 18 19 def process_response(self, request, response): 20 print('返回2') 21 return response 22 23 class route3(MiddlewareMixin): 24 def process_request(self, request): 25 print('路由3') 26 27 def process_response(self, request, response): 28 print('返回3') 29 return response
步骤三、在settings.py中加入路径
缓存
现在网络上基本上都是动态网页,而如果所有数据都要经过view函数处理渲染,这必然要耗费内存、CPU资源。而如果有缓存,则不需要函数处理,直接从缓存中返回数据给客户端,如此一来,则会大大减轻了计算机的负担。
6种缓存引擎
一、开发调试
1 # 此为开始调试用,实际内部不做任何操作 2 # 配置: 3 CACHES = { 4 'default': { 5 'BACKEND': 'django.core.cache.backends.dummy.DummyCache', # 引擎 6 'TIMEOUT': 300, # 缓存超时时间(默认300,None表示永不过期,0表示立即过期) 7 'OPTIONS':{ 8 'MAX_ENTRIES': 300, # 最大缓存个数(默认300) 9 'CULL_FREQUENCY': 3, # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3) 10 }, 11 'KEY_PREFIX': '', # 缓存key的前缀(默认空) 12 'VERSION': 1, # 缓存key的版本(默认1) 13 'KEY_FUNCTION' 函数名 # 生成key的函数(默认函数会生成为:【前缀:版本:key】) 14 } 15 } 16 17 18 # 自定义key 19 def default_key_func(key, key_prefix, version): 20 """ 21 Default function to generate keys. 22 23 Constructs the key used by all other methods. By default it prepends 24 the `key_prefix'. KEY_FUNCTION can be used to specify an alternate 25 function with custom key making behavior. 26 """ 27 return '%s:%s:%s' % (key_prefix, version, key) 28 29 def get_key_func(key_func): 30 """ 31 Function to decide which key function to use. 32 33 Defaults to ``default_key_func``. 34 """ 35 if key_func is not None: 36 if callable(key_func): 37 return key_func 38 else: 39 return import_string(key_func) 40 return default_key_func
注意此引擎的配置均可用于以下5种
二、内存
# 此缓存将内容保存至内存的变量中 # 配置: CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', 'LOCATION': 'unique-snowflake', } } # 注:其他配置同开发调试版本
三、文件
1 # 此缓存将内容保存至文件 2 # 配置: 3 4 CACHES = { 5 'default': { 6 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', 7 'LOCATION': '/var/tmp/django_cache', 8 } 9 } 10 # 注:其他配置同开发调试版本
四、数据库
1 # 此缓存将内容保存至数据库 2 3 # 配置: 4 CACHES = { 5 'default': { 6 'BACKEND': 'django.core.cache.backends.db.DatabaseCache', 7 'LOCATION': 'my_cache_table', # 数据库表 8 } 9 } 10 11 # 注:执行创建表命令 python manage.py createcachetable
五、Memcache缓存(python-memcached模块)
1 # 此缓存使用python-memcached模块连接memcache 2 3 CACHES = { 4 'default': { 5 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 6 'LOCATION': '127.0.0.1:11211', 7 } 8 } 9 10 CACHES = { 11 'default': { 12 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 13 'LOCATION': 'unix:/tmp/memcached.sock', 14 } 15 } 16 17 CACHES = { 18 'default': { 19 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 20 'LOCATION': [ 21 '172.19.26.240:11211', 22 '172.19.26.242:11211', 23 ] 24 } 25 }
六、Memcache缓存(pylibmc模块)
1 # 此缓存使用pylibmc模块连接memcache 2 3 CACHES = { 4 'default': { 5 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', 6 'LOCATION': '127.0.0.1:11211', 7 } 8 } 9 10 CACHES = { 11 'default': { 12 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', 13 'LOCATION': '/tmp/memcached.sock', 14 } 15 } 16 17 CACHES = { 18 'default': { 19 'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache', 20 'LOCATION': [ 21 '172.19.26.240:11211', 22 '172.19.26.242:11211', 23 ] 24 } 25 }
七、Redis缓存(依赖:pip3 install django-redis)
1 CACHES = { 2 "default": { 3 "BACKEND": "django_redis.cache.RedisCache", 4 "LOCATION": "redis://127.0.0.1:6379", 5 "OPTIONS": { 6 "CLIENT_CLASS": "django_redis.client.DefaultClient", 7 "CONNECTION_POOL_KWARGS": {"max_connections": 100} 8 # "PASSWORD": "密码", 9 } 10 } 11 }
from django_redis import get_redis_connection conn = get_redis_connection("default")
应用方法(以文件为例说明)
步骤一、在settings.py中导入缓存引擎
CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache', 'LOCATION': os.path.join(BASE_DIR, 'cache'), } }
步骤二、缓存的应用
根据范围的不同,缓存的应用分了三个级别
一、全网站应用
在setting.py中的中间件的头尾添加中间件
1 MIDDLEWARE = [ 2 'django.middleware.cache.UpdateCacheMiddleware' 3 'django.middleware.security.SecurityMiddleware', 4 'django.contrib.sessions.middleware.SessionMiddleware', 5 'django.middleware.common.CommonMiddleware', 6 'django.middleware.csrf.CsrfViewMiddleware', 7 'django.contrib.auth.middleware.AuthenticationMiddleware', 8 'django.contrib.messages.middleware.MessageMiddleware', 9 'django.middleware.clickjacking.XFrameOptionsMiddleware', 10 'middle.m1.route1', 11 'middle.m1.route2', 12 'middle.m1.route3', 13 'django.middleware.cache.FetchFromCacheMiddleware' 14 ]
二、单视图
1、先导入模块from django.views.decorators.cache import cache_page
2、在对应的view函数中加上加上装饰器@cache_page(seconds)
三、模板中的使用
1、在模板中引入模块{% load cache %}
2、然后在需要缓存的地方加上{% cache <seconds> <key> %}
....
{% endcache %}
信号
什么是信号
信号其实很简单。想想,在体育课上,老师发出口令“向左转”,学生就会根据口令做动作。其实你可以这样理解,口令即是个信号,指令即是个信号。而在django里面,其实就内置了大量的信号供我们使用,只要触发这些信号,就可以做“动作”,在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 # 创建数据库连接时,自动触发
以信号post_init说明信号量的使用
步骤一、创建一个python文件,注册信号绑定的事件,具体步骤
1、导入模块,相关的信号模块如下:
2、定义函数
3、绑定信号和函数
1 from django.db.models.signals import post_init 2 3 4 def callback(sender, **kwargs): 5 print('in the signal callback func') 6 7 8 post_init.connect(callback)
步骤二、在<project_name>.__init__.py中导入步骤一创建的py文件。为什么在这里导入?那是因为每次django运行时都会运行此文件。
这样每一次创建数据时都会触发callback函数
自定义信号
一、定义信号
1 import django.dispatch 2 pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])
b. 注册信号
def callback(sender, **kwargs): print("callback") print(sender,kwargs) pizza_done.connect(callback)
c. 触发信号
在view中触发信号
1 from 路径 import pizza_done 2 3 pizza_done.send(sender='seven',toppings=123, size=456)
由于内置信号的触发者已经集成到Django中,所以其会自动调用,而对于自定义信号则需要开发者在任意位置触发。
表单验证
表单验证是在views函数中执行的,如下:
from django.shortcuts import render, redirect from django import forms from app01 import models from django.views.decorators.cache import cache_page from django.http import HttpResponse import time class FM(forms.Form): """验证表单""" # 此名称必须要与需要验证的html中的name属性对应 user = forms.CharField(error_messages={'required': '用户名不能为空'}) pwd = forms.CharField( min_length=6, max_length=12, error_messages={'required': '密码不能为空', 'min_length': '密码至少需要6位', 'max_length': "密码不多于12位"} ) email = forms.EmailField(error_messages={'required': '邮箱不能为空'}) def fm(request): if request.method == 'GET': return render(request, 'fm.html') elif request.method == 'POST': # 获取验证对象 obj = FM(request.POST) res = obj.is_valid() # 验证通过 if res: print(obj.cleaned_data) # 验证不通过 else: # 以下加上as_json()就作为字典呈现,如果需要以字符串呈现则需要删除as_json() print(obj.errors.as_json()) return redirect('/fm/')
关键点:
1、导入:from django import forms
2、创建类,需继承forms.Form
3、类中的属性名需与表单提交的标签name属性一致
4、获取名单验证结果对象obj = FM(request.POST)
5、obj.is_valid():表单验证结果,True或False
6、obj.cleaned_data:表单通过的数据
7、obj.errors:表单不通过的所有信息
obj.errors['user'][0] 获取user不能通过的第一条信息
8、obj.errors.as_json():表单不通过的信息用json格式显示
9、用了表单验证,在HTML中不用单独写input标签了
在Form表单生成的HTML写类
这个就需要用到widget,这个插件可以帮我们生成HTML,几乎写所有的input系列的标签都靠这个插件完成,具体步骤如下:
一、导入:from django.forms import widgets, fields
二、在form表单类中,在写表单属性时添加widget参数,如下:
1 from django.shortcuts import render, redirect 2 from django import forms 3 from django.forms import widgets, fields 4 class FM(forms.Form): 5 """验证表单""" 6 # 此名称必须要与需要验证的html中的name属性对应 7 user = fields.CharField(error_messages={'required': '用户名不能为空'}, 8 widget=widgets.Textarea(attrs={'class': 'c1'})) 9 pwd = fields.CharField( 10 min_length=6, 11 max_length=12, 12 error_messages={'required': '密码不能为空', 13 'min_length': '密码至少需要6位', 14 'max_length': "密码不多于12位"} 15 ) 16 email = fields.EmailField(error_messages={'required': '邮箱不能为空'})
其实总的来说,widget生成的是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类型 ...
1 TextInput(Input) 2 NumberInput(TextInput) 3 EmailInput(TextInput) 4 URLInput(TextInput) 5 PasswordInput(TextInput) 6 HiddenInput(TextInput) 7 Textarea(Widget) 8 DateInput(DateTimeBaseInput) 9 DateTimeInput(DateTimeBaseInput) 10 TimeInput(DateTimeBaseInput) 11 CheckboxInput 12 Select 13 NullBooleanSelect 14 SelectMultiple 15 RadioSelect 16 CheckboxSelectMultiple 17 FileInput 18 ClearableFileInput 19 MultipleHiddenInput 20 SplitDateTimeWidget 21 SplitHiddenDateTimeWidget 22 SelectDateWidget
常用的选择插件
1 # 单radio,值为字符串 2 # user = fields.CharField( 3 # initial=2, 4 # widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),)) 5 # ) 6 7 # 单radio,值为字符串 8 # user = fields.ChoiceField( 9 # choices=((1, '上海'), (2, '北京'),), 10 # initial=2, 11 # widget=widgets.RadioSelect 12 # ) 13 14 # 单select,值为字符串 15 # user = fields.CharField( 16 # initial=2, 17 # widget=widgets.Select(choices=((1,'上海'),(2,'北京'),)) 18 # ) 19 20 # 单select,值为字符串 21 # user = fields.ChoiceField( 22 # choices=((1, '上海'), (2, '北京'),), 23 # initial=2, 24 # widget=widgets.Select 25 # ) 26 27 # 多选select,值为列表 28 # user = fields.MultipleChoiceField( 29 # choices=((1,'上海'),(2,'北京'),), 30 # initial=[1,], 31 # widget=widgets.SelectMultiple 32 # ) 33 34 35 # 单checkbox 36 # user = fields.CharField( 37 # widget=widgets.CheckboxInput() 38 # ) 39 40 41 # 多选checkbox,值为列表 42 # user = fields.MultipleChoiceField( 43 # initial=[2, ], 44 # choices=((1, '上海'), (2, '北京'),), 45 # widget=widgets.CheckboxSelectMultiple 46 # )
自定义验证规则
1 from django.forms import Form 2 from django.forms import widgets 3 from django.forms import fields 4 from django.core.validators import RegexValidator 5 6 class MyForm(Form): 7 user = fields.CharField( 8 validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')], 9 )
1 import re 2 from django.forms import Form 3 from django.forms import widgets 4 from django.forms import fields 5 from django.core.exceptions import ValidationError 6 7 8 # 自定义验证规则 9 def mobile_validate(value): 10 mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$') 11 if not mobile_re.match(value): 12 raise ValidationError('手机号码格式错误') 13 14 15 class PublishForm(Form): 16 17 18 title = fields.CharField(max_length=20, 19 min_length=5, 20 error_messages={'required': '标题不能为空', 21 'min_length': '标题最少为5个字符', 22 'max_length': '标题最多为20个字符'}, 23 widget=widgets.TextInput(attrs={'class': "form-control", 24 'placeholder': '标题5-20个字符'})) 25 26 27 # 使用自定义验证规则 28 phone = fields.CharField(validators=[mobile_validate, ], 29 error_messages={'required': '手机不能为空'}, 30 widget=widgets.TextInput(attrs={'class': "form-control", 31 'placeholder': u'手机号码'})) 32 33 email = fields.EmailField(required=False, 34 error_messages={'required': u'邮箱不能为空','invalid': u'邮箱格式错误'}, 35 widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'邮箱'}))
1 from django import forms 2 from django.forms import fields 3 from django.forms import widgets 4 from django.core.exceptions import ValidationError 5 from django.core.validators import RegexValidator 6 7 class FInfo(forms.Form): 8 username = fields.CharField(max_length=5, 9 validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid extension.', 'invalid')], ) 10 email = fields.EmailField() 11 12 def clean_username(self): 13 """ 14 Form中字段中定义的格式匹配完之后,执行此方法进行验证 15 :return: 16 """ 17 value = self.cleaned_data['username'] 18 if "666" in value: 19 raise ValidationError('666已经被玩烂了...', 'invalid') 20 return value
1 from django.forms import Form 2 from django.forms import widgets 3 from django.forms import fields 4 5 from django.core.validators import RegexValidator 6 7 8 ############## 自定义字段 ############## 9 class PhoneField(fields.MultiValueField): 10 def __init__(self, *args, **kwargs): 11 # Define one message for all fields. 12 error_messages = { 13 'incomplete': 'Enter a country calling code and a phone number.', 14 } 15 # Or define a different message for each field. 16 f = ( 17 fields.CharField( 18 error_messages={'incomplete': 'Enter a country calling code.'}, 19 validators=[ 20 RegexValidator(r'^[0-9]+$', 'Enter a valid country calling code.'), 21 ], 22 ), 23 fields.CharField( 24 error_messages={'incomplete': 'Enter a phone number.'}, 25 validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid phone number.')], 26 ), 27 fields.CharField( 28 validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid extension.')], 29 required=False, 30 ), 31 ) 32 super(PhoneField, self).__init__(error_messages=error_messages, fields=f, require_all_fields=False, *args, 33 **kwargs) 34 35 def compress(self, data_list): 36 """ 37 当用户验证都通过后,该值返回给用户 38 :param data_list: 39 :return: 40 """ 41 return data_list 42 43 ############## 自定义插件 ############## 44 class SplitPhoneWidget(widgets.MultiWidget): 45 def __init__(self): 46 ws = ( 47 widgets.TextInput(), 48 widgets.TextInput(), 49 widgets.TextInput(), 50 ) 51 super(SplitPhoneWidget, self).__init__(ws) 52 53 def decompress(self, value): 54 """ 55 处理初始值,当初始值initial不是列表时,调用该方法 56 :param value: 57 :return: 58 """ 59 if value: 60 return value.split(',') 61 return [None, None, None]