s10_part5_d71_分组查询_分页_cookie_session_装饰器
分组查询补充
例如:以部门为单位,查询员工平均工资
models.Employee.objects.values("dept").annotate(avg=AVG("salary")).values("dept","avg")
查询结果分页展示
reference: http://www.cnblogs.com/liwenzhou/p/8343243.html
准备工作:脚本批量创建一些测试数据
将下面的代码保存到bulk_create.py文件中放到Django项目的根目录,直接执行即可。
django_library/bulk_create.py
import os
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "about_orm.settings")
import django
django.setup()
from app01 import models
bulk_obj = (models.Publisher(name='沙河第{}出版社'.format(i)) for i in range(300))
models.Publisher.objects.bulk_create(bulk_obj)
查检settings.py中的static设置
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, "static")
]
下载bootstrap文件:https://github.com/twbs/bootstrap/releases/download/v4.1.3/bootstrap-4.1.3-dist.zip
解压到static目录
static/
└── bootstrap
└── css
├── bootstrap.css
├── bootstrap.css.map
├── bootstrap-grid.css
├── bootstrap-grid.css.map
├── bootstrap-grid.min.css
├── bootstrap-grid.min.css.map
├── bootstrap.min.css
├── bootstrap.min.css.map
├── bootstrap-reboot.css
├── bootstrap-reboot.css.map
├── bootstrap-reboot.min.css
└── bootstrap-reboot.min.css.map
html模板中导入bootstrap样式
从官网中选一个页数展示条
https://getbootstrap.com/docs/4.0/components/pagination/
从官网中选一个tables样式
https://getbootstrap.com/docs/4.0/content/tables/
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>出版社列表</title>
<!--导入bootstrap样式-->
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<a href="/add_publisher/">添加新的出版社</a>
<table class="table table-striped">
<thead>
<tr>
<th>序号</th>
<th>ID</th>
<th>出版社名称</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for publisher in publisher_list %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ publisher.id }}</td>
<td>{{ publisher.name }}</td>
<td>
<a href="/delete_publisher/?id={{ publisher.id }}">删除</a>
<a href="/edit_publisher/?id={{ publisher.id }}">编辑</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<!--增加一个显示页数的导航条-->
<nav aria-label="Page navigation example">
<ul class="pagination">
{{ page_html|safe }}
</ul>
</nav>
</nav>
</div>
</body>
</html>
编写后端代码:
django_library/app01/views.py
from app01 import models
def publisher_list(request):
page_num = request.GET.get("page", None)
# 总共有多少数据
total_count = models.Publisher.objects.all().count()
# 每页展示10条数据
per_page = 10
# 总共需要多少页码来展示
total_page, m = divmod(total_count, per_page)
if m:
total_page += 1
# 处理用户输入的页数
try:
page_num = int(page_num)
# 如果输入的页码数超过了最大的页码数或者小于1,默认返回最后一页
if page_num > total_page or page_num < 1:
page_num = total_page
except Exception as e:
# 当输入的页码不是正经数字的时候 默认返回第一页的数据
print(str(e))
page_num = 1
# 数据结尾数
data_end = page_num * per_page
if data_end > total_count:
data_end = total_count
# 数据开始数
data_start = (page_num - 1) * per_page
# 每页显示多少个页码
max_page = 20
half_max_page = max_page // 2
# 页面上展示页码开始数
if page_num <= half_max_page:
page_start = 1
else:
page_start = page_num - half_max_page
# 页面上展示页码结束数
page_end = page_num + half_max_page
if page_end > total_page:
page_end = total_page
# 去数据库查出需要展示的数据
# all_publisher = models.Publisher.objects.all()[data_start, data_end].order_by("id")
print([data_start, data_end])
all_publisher = models.Publisher.objects.all()[data_start:data_end]
html_str_list = []
for i in range(page_start, page_end + 1):
# 如果是当前页就加一个active样式类
if page_num == i:
tmp = '<li class="page-item active"><a class="page-link" href="/publisher_list/?page={0}">{0}</a></li>'.
format(i)
else:
tmp = '<li class="page-item"><a class="page-link" href="/publisher_list/?page={0}">{0}</a></li>'.format(i)
html_str_list.append(tmp)
# 上一页的html
if page_num == 1:
previous_str = '<li class="page-item"><a class ="page-link" href="#" >上一页</a></li>'
else:
previous_str = '<li class="page-item"><a class ="page-link" href="/publisher_list/?page={}" >上一页</a></li>'.
format(page_num - 1)
# 下一页的html
if page_end == page_num:
next_str = '<li class="page-item"><a class ="page-link" href="#" >下一页</a></li>'
else:
next_str = '<li class="page-item"><a class ="page-link" href="/publisher_list/?page={}" >下一页</a></li>'.
format(page_num + 1, )
#将上一页的html插入到页数html的最前面
html_str_list.insert(0, previous_str)
#将下一页的html追加到页数html的最后面
html_str_list.append(next_str)
page_html = "".join(html_str_list)
return render(request, "publisher_list.html", {"publisher_list": all_publisher, "page_html": page_html})
ps:更好的做法是封装到一个类中,
例如:
django_library/utils/mypage.py
class Page():
def __init__(self, page_num, total_count, url_prefix, per_page=10, max_page=11):
"""
:param page_num: 当前页码数
:param total_count: 数据总数
:param url_prefix: a标签href的前缀
:param per_page: 每页显示多少条数据
:param max_page: 页面上最多显示几个页码
"""
self.url_prefix = url_prefix
self.max_page = max_page
# 每一页显示多少条数据
# 总共需要多少页码来展示
total_page, m = divmod(total_count, per_page)
if m:
total_page += 1
self.total_page = total_page
try:
page_num = int(page_num)
# 如果输入的页码数超过了最大的页码数,默认返回最后一页
if page_num > total_page:
page_num = total_page
except Exception as e:
# 当输入的页码不是正经数字的时候 默认返回第一页的数据
page_num = 1
self.page_num = page_num
# 定义两个变量保存数据从哪儿取到哪儿
self.data_start = (page_num - 1) * 10
self.data_end = page_num * 10
# 页面上总共展示多少页码
if total_page < self.max_page:
self.max_page = total_page
half_max_page = self.max_page // 2
# 页面上展示的页码从哪儿开始
page_start = page_num - half_max_page
# 页面上展示的页码到哪儿结束
page_end = page_num + half_max_page
# 如果当前页减一半 比1还小
if page_start <= 1:
page_start = 1
page_end = self.max_page
# 如果 当前页 加 一半 比总页码数还大
if page_end >= total_page:
page_end = total_page
page_start = total_page - self.max_page + 1
self.page_start = page_start
self.page_end = page_end
@property
def start(self):
return self.data_start
@property
def end(self):
return self.data_end
def page_html(self):
# 自己拼接分页的HTML代码
html_str_list = []
# 加上第一页
html_str_list.append('<li><a href="{}?page=1">首页</a></li>'.format( self.url_prefix))
# 判断一下 如果是第一页,就没有上一页
if self.page_num <= 1:
html_str_list.append('<li class="disabled"><a href="#"><span aria-hidden="true">«</span></a></li>'.format(self.page_num-1))
else:
# 加一个上一页的标签
html_str_list.append('<li><a href="{}?page={}"><span aria-hidden="true">«</span></a></li>'.format( self.url_prefix, self.page_num-1))
for i in range(self.page_start, self.page_end+1):
# 如果是当前页就加一个active样式类
if i == self.page_num:
tmp = '<li class="active"><a href="{0}?page={1}">{1}</a></li>'.format(self.url_prefix, i)
else:
tmp = '<li><a href="{0}?page={1}">{1}</a></li>'.format( self.url_prefix, i)
html_str_list.append(tmp)
# 加一个下一页的按钮
# 判断,如果是最后一页,就没有下一页
if self.page_num >= self.total_page:
html_str_list.append('<li class="disabled"><a href="#"><span aria-hidden="true">»</span></a></li>')
else:
html_str_list.append('<li><a href="{}?page={}"><span aria-hidden="true">»</span></a></li>'.format( self.url_prefix, self.page_num+1))
# 加最后一页
html_str_list.append('<li><a href="{}?page={}">尾页</a></li>'.format( self.url_prefix, self.total_page))
page_html = "".join(html_str_list)
return page_html
调用实例化类的后端代码:
django_library/app01/views.py
from utils.mypage import Page
def books(request):
# 从URL取参数
page_num = request.GET.get("page")
# 总数据是多少
total_count = models.Book.objects.all().count()
page_obj = Page(page_num, total_count, per_page=10, url_prefix="/books/", max_page=9,)
ret = models.Book.objects.all()[page_obj.start:page_obj.end]
page_html = page_obj.page_html()
return render(request, "books.html", {"books": ret, "page_html": page_html})
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('p')
paginator = Paginator(L, 10)
# 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, 'index.html', {'posts': posts})
返回顶部
内置分页HTML部分
<!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 }}">Previous</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 }}">Next</a>
{% endif %}
</span>
</div>
</body>
</html>
cookie and session
yx_fab_sys_django/yx_fab_sys/urls.py
url(r'^login/$', views.login, name="login"),
yx_fab_sys_django/fab_wx_webar/views.py
登陆的逻辑处理
# 登陆的逻辑处理
def login(request):
error_msg = ""
print(request.get_full_path()) # 获取当前请求的路径和参数
print(request.path_info) # 取当前请求的路径
print("-" * 120)
if request.method == 'POST':
username = request.POST.get("user", None)
pwd = request.POST.get("pwd", None)
# 从URL里面取到 next 参数
next_url = request.GET.get("next")
if username == 'test' and pwd == 'test':
if next_url:
rep = redirect(next_url)
else:
rep = redirect('/')
# 设置cookie
# rep.set_signed_cookie("is_login", "1", salt="mysaltno1", max_age=3600) # 单位是秒
# 设置session
request.session["is_login"] = "1"
request.session["user"] = username
request.session.set_expiry(3600) # 3600秒钟之后失效
return rep
else:
error_msg = "用户名或密码不正确,请重试!"
return render(request, "login.html", {"error_msg": error_msg})
检查登陆的装饰器
# 检查登陆的装饰器
def check_login(func):
@wraps(func) # 装饰器修复技术
def inner(request, *args, **kwargs):
ret = request.session.get("is_login")
# 1. 获取cookie中的随机字符串
# 2. 根据随机字符串去数据库取 session_data --> 解密 --> 反序列化成字典
# 3. 在字典里面 根据 is_login 取具体的数据
if ret == "1":
# 已经登陆过的 继续执行
return func(request, *args, **kwargs)
# 没有登录过的 跳转到登录页面
else:
# 获取当前访问的URL
next_url = request.path_info
print(next_url)
return redirect("/login/?next={}".format(next_url))
return inner
返回顶部
Session介绍:
Session保存在服务端的键值对
Session依赖于Cookie
dsadasdsadsafsjkndf: {"is_login": 1, "name": "xiaohei", "age":18}
dsaasdaknfgreryywdf: {"is_login": 1, "name": "xiaobai", "age":20}
wqrqrteknfgzddasqfg: {"is_login": 0, "name": "xiaohui", "age":48}
给浏览器写入Cookie:
sessionid:wqrqrteknfgzddasqfg
- 从用户发来的请求的Cookie中 根据 sessionid 取值, 取到 wqrqrteknfgzddasqfg
- 根据特殊字符串找到对应的 Session 数据 --> {"is_login": 0, "name": "xiaohui", "age":48}
- request.session.get("is_login") --> 从Session取值
Session版登陆验证
@check_login
def logout(request):
# 删除所有当前请求相关的session
request.session.delete()
return redirect("/login/")
@check_login
def index(request):
current_user = request.session.get("user", None)
return render(request, "index.html", {"user": current_user})
Django中Session相关方法
# 获取、设置、删除Session中数据
request.session['k1']
request.session.get('k1',None)
request.session['k1'] = 123
request.session.setdefault('k1',123) # 存在则不设置
del request.session['k1']
# 所有 键、值、键值对
request.session.keys()
request.session.values()
request.session.items()
request.session.iterkeys()
request.session.itervalues()
request.session.iteritems()
# 会话session的key
request.session.session_key
# 将所有Session失效日期小于当前日期的数据删除
request.session.clear_expired()
# 检查会话session的key在数据库中是否存在
request.session.exists("session_key")
# 删除当前会话的所有Session数据
request.session.delete()
# 删除当前的会话数据并删除会话的Cookie。
request.session.flush()
这用于确保前面的会话数据不可以再次被用户的浏览器访问
例如,django.contrib.auth.logout() 函数中就会调用它。
# 设置会话Session和Cookie的超时时间
request.session.set_expiry(value)
* 如果value是个整数,session会在些秒数后失效。
* 如果value是个datatime或timedelta,session就会在这个时间后失效。
* 如果value是0,用户关闭浏览器session就会失效。
* 如果value是None,session会依赖全局session失效策略。
返回顶部
Django中的Session配置
1. 数据库Session
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默认)
2. 缓存Session
SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 引擎
SESSION_CACHE_ALIAS = 'default' # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置
3. 文件Session
SESSION_ENGINE = 'django.contrib.sessions.backends.file' # 引擎
SESSION_FILE_PATH = None # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir()
4. 缓存+数据库
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' # 引擎
5. 加密Cookie Session
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies' # 引擎
其他公用设置项:
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相关设置
CBV中加装饰器相关
方法一:加在CBV视图的get或post方法上
方法二:加在dispatch方法上
方法三:直接加在视图类上,但method_decorator必须传 name 关键字参数
# Django提供的工具,把函数装饰器转变成方法装饰器
from django.utils.decorators import method_decorator
# @method_decorator(check_login, name="get")
# @method_decorator(check_login, name="post")
class UserInfo(views.View):
# @method_decorator(check_login)
# def dispatch(self.request, *args, **kwargs):
# return super(UserInfo, self).dispatch(request, *args, **kwargs)
@method_decorator(check_login)
def get(self, request):
return render(request, "app02/userinfo.html")
@method_decorator(check_login)
def post(self,request):
pass