Web框架之Django进阶篇
Django路由映射FBV 和 CBV
django中请求处理方式有2种:FBV(function base views) 和 CBV(class base views),换言之就是一种用函数处理请求,一种用类处理请求。
FBV
# url.py
from django.conf.urls import url, include
from mytest import views
urlpatterns = [
url(r‘^index/‘, views.index),
]
# views.py
from django.shortcuts import render
def index(req):
if req.method == ‘POST‘:
print(‘method is :‘ + req.method)
elif req.method == ‘GET‘:
print(‘method is :‘ + req.method)
return render(req, ‘index.html‘)
CBV
# urls.py
from mytest import views
urlpatterns = [
# url(r‘^index/‘, views.index),
url(r‘^index/‘, views.Index.as_view()),
]
# views.py
from django.views import View
class Index(View):
def get(self, req):
print(‘method is :‘ + req.method)
return render(req, ‘index.html‘)
def post(self, req):
print(‘method is :‘ + req.method)
return render(req, ‘index.html‘)
# 注:类要继承 View ,类中函数名必须小写。
关于CBV模式下的一个拓展
# cbv 模式下继承了django的view类
# 在请求来临的时候,会调用继承类的 dispatch 方法
# 通过反射的方法它会去调用自己写的视图函数, 那么这便是一个切入点,可以在自己的 cbv 视图中,重写这个方法。
class View(object):
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
分页
一、Django内置分页
from django.shortcuts import render
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
L = []
for i in range(999):
L.append(i) # 模拟数据库 生成数据
def index(request):
current_page = request.GET.get('page') # 通过get请求得到当前请求的页数
paginator = Paginator(L, 10) # 实例化传入俩个参数(所有数据,当页显示条数)
# per_page: 每页显示条目数量
# count: 数据总个数
# num_pages:总页数
# page_range:总页数的索引范围,如: (1,10),(1,200)
# page: page对象
try:
posts = paginator.page(current_page)# 传入当前页码,观源码可得实例化了一个Page对象
# has_next 是否有下一页
# next_page_number 下一页页码
# has_previous 是否有上一页
# previous_page_number 上一页页码
# object_list 分页之后的数据列表
# number 当前页
# paginator paginator对象
except PageNotAnInteger: # 不是数字
posts = paginator.page(1)
except EmptyPage: # 超出页码范围
posts = paginator.page(paginator.num_pages)
return render(request, 'index.html', {'posts': posts}) # posts封装了一些方法
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
</head>
<body>
<ul>
{% for item in posts %}
<li>{{ item }}</li>
{% endfor %}
</ul>
<div class="pagination">
<span class="step-links">
{% if posts.has_previous %}
<a href="?p={{ posts.previous_page_number }}">上一页</a>
{% endif %}
<span class="current">
Page {{ posts.number }} of {{ posts.paginator.num_pages }}.
</span>
{% if posts.has_next %}
<a href="?p={{ posts.next_page_number }}">下一页</a>
{% endif %}
</span>
</div>
</body>
</html>
那么、Django的内置分页基本俩个类实现、并封装了一些方法来使用、此时并不能满足有一些的需求
二、Django内置分页的拓展
from django.shortcuts import render
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
user = []
for i in range(1, 1001):
dic = {'name': 'root' + str(i), 'pwd': i}
user.append(dic)
class DiyPaginator(Paginator):
def __init__(self, current_page,max_pager_num, *args, **kwargs):
"""
:param current_page: 当前页码
:param max_pager_num: 显示页码个数的最大值
:param args:
:param kwargs:
"""
self.current_page = int(current_page)
self.max_pager_num = int(max_pager_num)
super(DiyPaginator,self).__init__(*args,**kwargs)
def pager_num_range(self):
# 需要的参数
# 当前页码 self.current_page
# 页码数量 self.max_pager_num
# 总页数 self.num_pages
# 如果总页数小于页码个数最大值的情况
if self.num_pages < self.max_pager_num:
return range(1,self.num_pages+1) # 返回 从 1 到 总页数
# 如果总页数大于页码数量且当前所选页码小于页码数量的一半
part = self.max_pager_num//2
if self.current_page <= part:
return range(1,self.max_pager_num+1) # 返回 从 1 到 页码个数最大值
# 如果当前页码加一半的页码 大于 总页数
if (self.current_page+part) > self.num_pages:
# 返回 从总页数-最大页码数 到 总页数 range的用法在此不作解释
# 例如 96页+5页 超出总页数 则返回的范围是 从 总页数-最大页码数量+1 到 总页数+1
return range(self.num_pages-self.max_pager_num+1,self.num_pages+1)
# 其余情况从 当前页码减去显示页码的平均值开始 到 当前页码加显示页码的平均值(并加一)结束
return range(self.current_page-part,self.current_page+part+1)
def index(request):
p = request.GET.get('page')
start = (int(p)-1)*10
end = int(p)*10
data = user[start:end]
return render(request,'index.html',{'data':data,'user':user})
def index1(request):
current_page = request.GET.get('page')
paginator = DiyPaginator(current_page, 9, user, 10)
# Paginator所封装的方法
# per_page: 每页显示条目数量
# count: 数据总个数
# num_pages:总页数
# page_range:总页数的索引范围,如: (1,10),(1,200)
# page: page对象
try:
posts = paginator.page(current_page)
# has_next 是否有下一页
# next_page_number 下一页页码
# has_previous 是否有上一页
# previous_page_number 上一页页码
# object_list 分页之后的数据列表
# number 当前页
# paginator paginator对象
except PageNotAnInteger: # 不是整形数字
posts = paginator.page(1)
except EmptyPage: # 如果是空值
posts = paginator.page(paginator.num_pages)
return render(request,'index1.html',{'posts':posts})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul>
{% for row in posts.object_list %}
<li>{{ row.name }}-{{ row.pwd }}</li>
{% endfor %}
</ul>
{% include 'include/pager.html' %}
</body>
</html>
##################################
include 组件代码
{% if posts.has_previous %}
<a href="/index1?page={{ posts.previous_page_number }}">上一页</a>
{% endif %}
{% for num in posts.paginator.pager_num_range %}
{% if num == posts.number %}
<a style="color: red;font-size: 20px" href="/index1?page={{ num }}">{{ num }}</a>
{% else %}
<a href="/index1?page={{ num }}">{{ num }}</a>
{% endif %}
{% endfor %}
{% if posts.has_next %}
<a href="/index1?page={{ posts.next_page_number }}">下一页</a>
{% endif %}
<span>
当前页:{{ posts.number }} 总页数:{{ posts.paginator.num_pages }}
</span>
三、自定义分页(适用于任何地方)
- 创建处理分页数据的类
- 根据分页数据获取数据
- 输出分页HTML,即:[上一页][1][2][3][4][5][下一页]或者额外的作出一些拓展也可以
创建处理分页数据的类时,大致也需要四个参数(详情观看类构造方法)
1、为了减少服务器内存的负载,不再获取所有数据的,而是获得所有数据的总个数,然后再根据索引查数据库的内容
2、当前页码
3、每页显示的行数
4、页码显示的数量
对于页数的显示大致也可以归类为三种情况(详情观看类中page_num_range函数)
1、计算的总页数小于页码显示的数量
2、计算的总页数大于页码显示的数量
A、当前页数小于页码数量的一半
B、当前页数加页码数量的一半超出总页数的范围
3、正常情况
从 当前页数 减 一半页码数量 到 当前页数 加 一半页码数量
class Pagination(object):
def __init__(self,totalCount,currentPage,perPageItemNum=10,maxPageNum=9):
"""
:param totalCount: 所有数据总个数
:param currentPage: 当前页数
:param perPageItemNum: 每页显示行数
:param maxPageNum: 最多显示页码个数
"""
self.total_count = totalCount
# 对当前的页码进行一次异常捕获
try:
currentPage = int(currentPage)
if currentPage <= 0:
currentPage = 1
self.current_page = currentPage
except Exception:
self.current_page = 1
self.per_page_item_num = perPageItemNum
self.max_page_num = maxPageNum
@property
def start(self):
# 数据索引开始的值
return (self.current_page-1) * self.per_page_item_num
@property
def end(self):
# 数据索引结束的值
return self.current_page * self.per_page_item_num
@property
def num_pages(self):
"""
总页数
:return:
"""
# 得商取余得内置函数
x, o = divmod(self.total_count,self.per_page_item_num)
if o == 0:
return x
return x + 1
@property
def page_num_range(self):
if self.num_pages < self.max_page_num:
return range(1, self.num_pages+1)
part = self.max_page_num//2
if self.current_page <= part:
return range(1,self.max_page_num+1)
if (self.current_page+part) > self.num_pages:
return range(self.num_pages-self.max_page_num+1, self.num_pages+1)
return range(self.current_page-part, self.current_page+part+1)
def page_str(self):
page_list = []
first = "<li><a href='/index2/?page=1'>首页</a></li>"
page_list.append(first)
if self.current_page == 1:
prev_page = "<li><a href='#'>上一页</a></li>"
else:
prev_page = "<li><a href='/index2/?page=%s'>上一页</a></li>" %(self.current_page-1)
page_list.append(prev_page)
for i in self.page_num_range:
if i == self.current_page:
temp = "<li class='active'><a href='/index2/?page=%s'>%s</a></li>" %(i,i)
else:
temp = "<li><a href='/index2/?page=%s'>%s</a></li>" % (i, i)
page_list.append(temp)
if self.current_page == self.num_pages:
next_page = "<li><a href='#'>下一页</a></li>"
else:
next_page = "<li><a href='/index2/?page=%s'>下一页</a></li>" %(self.current_page+1)
page_list.append(next_page)
last = "<li><a href='/index2/?page=%s'>尾页</a></li>" %self.num_pages
page_list.append(last)
return ''.join(page_list)
def index2(request):
from page.diypage import Pagination
current_page = request.GET.get('page')
page_obj = Pagination(1000,current_page)
data_list = user[page_obj.start:page_obj.end]
return render(request,'index2.html',{
'data' : data_list,
'page_obj' : page_obj
})
# 本页面引用了bootstrap样式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.css" />
</head>
<body>
<ul>
{% for row in data %}
<li>{{ row.name }}-{{ row.pwd }}</li>
{% endfor %}
</ul>
{% for i in page_obj.pager_num_range %}
<a href="/index2/?page={{ i }}">{{ i }}</a>
{% endfor %}
<hr/>
<ul class="pagination pagination-sm">
{{ page_obj.page_str|safe }}
</ul>
<div style="height: 300px;"></div>
</body>
</html>
序列化
序列化是将对象状态转换为可保持或传输的格式的过程
反序列化是指将存储在存储媒体中的对象状态装换成对象的过程
例如游戏都有存档的功能、再次开始的时候只需读档即可(这即是一个序列化与反序列的过程)
序列化也可以将一个对象传递到另一个地方的
关于Django中的序列化主要应用在将数据库中检索的数据返回给客户端用户,特别的Ajax请求一般返回的为Json格式。
1、serializers
from django.core import serializers
ret = models.BookType.objects.all()
data = serializers.serialize("json", ret)
2、json.dumps
import json
#ret = models.BookType.objects.all().values('caption')
ret = models.BookType.objects.all().values_list('caption')
ret=list(ret)
result = json.dumps(ret)
3、from django.http import JsonResponse
django JsonResponse不支持返回列表形式的序列化。例如看看这个类的构造方法是怎么样执行的…
class JsonResponse(HttpResponse):
"""
将数据序列化成为JSON的Http响应类
:param data: Data to be dumped into json. By default only ``dict`` objects
are allowed to be passed due to a security flaw before EcmaScript 5. See
the ``safe`` parameter for more information.
:param encoder: Should be an json encoder class. Defaults to
``django.core.serializers.json.DjangoJSONEncoder``.
:param safe: Controls if only ``dict`` objects may be serialized. Defaults
to ``True``.
:param json_dumps_params: A dictionary of kwargs passed to json.dumps().
"""
def __init__(self, data, encoder=DjangoJSONEncoder, safe=True,
json_dumps_params=None, **kwargs):
# 如果safe为True 和 data 不是 dict的实例对象 则抛出异常
if safe and not isinstance(data, dict):
raise TypeError(
'In order to allow non-dict objects to be serialized set the '
'safe parameter to False.'
)
if json_dumps_params is None:
json_dumps_params = {}
kwargs.setdefault('content_type', 'application/json')
data = json.dumps(data, cls=encoder, **json_dumps_params)
super(JsonResponse, self).__init__(content=data, **kwargs)
Cookices
Cookie是存储在用户浏览器上的一个键值对
A、获取Cookies
request.COOKIES[ 'key']
request.get_signed_cookie(key, default=RAISE_ERROR, salt='', max_age=None)
参数:
default: 默认值
salt: 加密盐
max_age: 后台控制过期时间
B、设置Cookies
rep = HttpResponse(...) 或 rep = render(request, ...)
rep.set_cookie(key,value,...)
rep.set_signed_cookie(key,value,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获取(不是绝对,底层抓包可以获取到也可以被覆盖)
C、由于Cookies保存在客户端的电脑上,所以,JavaScript和jquery也可以操作cookie.
更多详情更新中…
Session
Session是存储在服务器的一组键值对,且它依赖于Cookie,且安全系数比Cookie高
Django中默认支持Session,其内部提供了5种类型的Session供开发者使用:
- 数据库(默认)
- 缓存
- 文件
- 缓存+数据库
- 加密cookie
A、数据库Session
Django默认支持Session,并且默认是将Session数据存储在数据库中,即:django_session 表中。
a. 配置 settings.py
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默认)
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,默认修改之后才保存(默认)
b. 使用
def index(request):
# 获取、设置、删除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的随机字符串
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失效策略。
跨站请求伪造
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
Django中间件
django 中的中间件(middleware),在django中,中间件其实就是一个类,在请求到来和结束后,django会根据自己的规则在合适的时机执行中间件中相应的方法。
在django项目的settings模块中,有一个 MIDDLEWARE_CLASSES 变量,其中每一个元素就是一个中间件,如下图。
例如,可以在项目中的任何地方创建一个middleware.py的文件(可随意创建, 注册中间件的时候配置好路径即可)
中间件中可以定义四个方法,分别是:
- 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)
以上方法的返回值可以是None和HttpResonse对象,如果是None,则继续按照django定义的规则向下执行,如果是HttpResonse对象,则直接将该对象返回给用户。
自定义中间件
1、创建中间件
class CustomMiddleware(object):
def process_request(self,request):
pass
def process_view(self, request, callback, callback_args, callback_kwargs):
i =1
def process_exception(self, request, exception):
pass
def process_response(self, request, response):
return response
2、注册中间件
如第一图所示最后一条,可根据自身业务去自定义一些中间件操作。
Django信号
1,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 # 创建数据库连接时,自动触发
调用内置信号的俩种形式
2,django自定义信号
1 )、定义信号
所有的信号都是 django.dispatch.Signal
的实例、那么在项目某个app下创建 名为 custom_signals.py
的文件,如下这么写
# filename: custom_singals.py
import django.dispatch
# 声明了一个 `pizza_done` 信号,它将为接收者提供 `toppings`, `size` 参数
# 可以在任何时候更改这个参数列表
pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])
并配置信号回调函数
在项目某个app下创建 名为 register.py
的文件,如下这么写
# filename: register.py
# 注册信号使用
from app01.custom_singals import pizza_done
def pizza_done_func(sender, **kwargs):
print("msg:", sender, kwargs)
pizza_done.connect(pizza_done_func)
2 )、注册信号
项目启动的时候要将这些信号注册,那么在对应 app
的 apps.py
文件中如下
# filename: app_name/apps.py
from django.apps import AppConfig
# `django` 工具包中封装好的动态导入模块的方法
from django.utils.module_loading import import_module
class App01Config(AppConfig):
name = 'app01'
def ready(self):
# 项目启动所有 `app` 的 `ready` 方法下的代码均会执行
import_module("app01.register") # 注册信号
3 )、发送信号
事先定义 一组 url
映射关系,在视图业务操作中发送我们的信号( 有俩种发送信号的方法 )
from django.shortcuts import HttpResponse
from app01.custom_singals import pizza_done
# Create your views here.
class PizzaStore:
def send_pizza(self, toppings, size):
# 发送信号
pizza_done.send(sender=self.__class__, toppings=toppings, size=size)
# "OR" 上下俩个方法是等价的, 区别 ~ ~
pizza_done.send_robust(sender=self.__class__, toppings=toppings, size=size)
def index(request, ):
# 业务操作, 执行信号发送操作
PizzaStore().send_pizza("chicken", "40")
return HttpResponse("ok")
Django自定义manage.py命令
正如之前,已经所知一些 Django
的一些命令, 例如 runserver、migrate…,那么它们执行的机制是如何的呢,是否可以做一些其它的命令满足我们的一些需求呢,请看下文
在 APP 创建相关目录路径,closepoll.py
便是自定义的命令文件
polls/
__init__.py
models.py
management/
__init__.py
commands/
__init__.py
closepoll.py
tests.py
views.py
文件里面必须继承基类,并实现抽象方法 handle
from django.core.management.base import BaseCommand, CommandError
from polls.models import Question as Poll
class Command(BaseCommand):
help = 'Closes the specified poll for voting'
def add_arguments(self, parser):
# 添加自定义参数,例如 python manage.py runserver -h 0.0.0.0 -p 8080 这样子 ~~
parser.add_argument('poll_id', nargs='+', type=int)
def handle(self, *args, **options):
for poll_id in options['poll_id']:
try:
poll = Poll.objects.get(pk=poll_id)
except Poll.DoesNotExist:
raise CommandError('Poll "%s" does not exist' % poll_id)
poll.opened = False
poll.save()
self.stdout.write(self.style.SUCCESS('Successfully closed poll "%s"' % poll_id))
运行自定义命令
python3 manage.py closepoll -poll_id 10
# 上述是在命令行运行文件,那么在代码中如何运行,
from django.core import management
management.call_command('closepoll', args=()) # 参数详解待更新