Django 之进阶篇
一、分页
1.Django内置分页
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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封装了一些方法 Views.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!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> HTML
Django的内置分页基本俩个类实现、并封装了一些方法来使用、此时并不能满足有一些的需求。。
2.Django内置分页的拓展
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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}) 扩展内置分页:views.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
<!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
3.自定义分页(适用于任何地方)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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) 自定义分页
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }) 自定义分页:views.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# 本页面引用了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>
*************************************************************
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#!/usr/bin/env python # _*_coding:utf-8_*_ from django.utils.safestring import mark_safe class PageInfo(object): def __init__(self,current,totalItem,peritems=5): self.__current=current self.__peritems=peritems self.__totalItem=totalItem def From(self): return (self.__current-1)*self.__peritems def To(self): return self.__current*self.__peritems def TotalPage(self): #总页数 result=divmod(self.__totalItem,self.__peritems) if result[1]==0: return result[0] else: return result[0]+1 def Custompager(baseurl,currentPage,totalpage): #基础页,当前页,总页数 perPager=11 #总页数<11 #0 -- totalpage #总页数>11 #当前页大于5 currentPage-5 -- currentPage+5 #currentPage+5是否超过总页数,超过总页数,end就是总页数 #当前页小于5 0 -- 11 begin=0 end=0 if totalpage <= 11: begin=0 end=totalpage else: if currentPage>5: begin=currentPage-5 end=currentPage+5 if end > totalpage: end=totalpage else: begin=0 end=11 pager_list=[] if currentPage<=1: first="<a href=''>首页</a>" else: first="<a href='%s%d'>首页</a>" % (baseurl,1) pager_list.append(first) if currentPage<=1: prev="<a href=''>上一页</a>" else: prev="<a href='%s%d'>上一页</a>" % (baseurl,currentPage-1) pager_list.append(prev) for i in range(begin+1,end+1): if i == currentPage: temp="<a href='%s%d' class='selected'>%d</a>" % (baseurl,i,i) else: temp="<a href='%s%d'>%d</a>" % (baseurl,i,i) pager_list.append(temp) if currentPage>=totalpage: next="<a href='#'>下一页</a>" else: next="<a href='%s%d'>下一页</a>" % (baseurl,currentPage+1) pager_list.append(next) if currentPage>=totalpage: last="<a href=''>末页</a>" else: last="<a href='%s%d'>末页</a>" % (baseurl,totalpage) pager_list.append(last) result=''.join(pager_list) return mark_safe(result) #把字符串转成html语言 分页实例
二、序列化
关于Django中的序列化主要应用在将数据库中检索的数据返回给客户端用户,特别的Ajax请求一般返回的为Json格式。
1、serializers
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
from django.core import serializers ret = models.BookType.objects.all() data = serializers.serialize("json", ret)
2、json.dumps
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import json #ret = models.BookType.objects.all().values('caption') ret = models.BookType.objects.all().values_list('caption') ret=list(ret) result = json.dumps(ret)
由于json.dumps时无法处理datetime日期,所以可以通过自定义处理器来做扩展,如:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import json from datetime import date from datetime import datetime class JsonCustomEncoder(json.JSONEncoder): def default(self, field): if isinstance(field, datetime): return o.strftime('%Y-%m-%d %H:%M:%S') elif isinstance(field, date): return o.strftime('%Y-%m-%d') else: return json.JSONEncoder.default(self, field) # ds = json.dumps(d, cls=JsonCustomEncoder)
3、from django.http import JsonResponse(自定制序列化json 不支持的转换类型)
django JsonResponse不支持返回列表形式的序列化。例如看看这个类的构造方法是怎么样执行的...
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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
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.
<script src='/static/js/jquery.cookie.js'></script> $.cookie("list_pager_num", 30,{ path: '/' });
四、Session
Session是存储在服务器的一组键值对,且它依赖于Cookie,且安全系数比Cookie高
Django中默认支持Session,其内部提供了5种类型的Session供开发者使用:
- 数据库(默认)
- 缓存
- 文件
- 缓存+数据库
- 加密cookie
1、数据库Session
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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") ...
2、缓存Session
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
a. 配置 settings.py SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 引擎 SESSION_CACHE_ALIAS = 'default' # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置 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. 使用 同上
3、文件Session
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
a. 配置 settings.py SESSION_ENGINE = 'django.contrib.sessions.backends.file' # 引擎 SESSION_FILE_PATH = None # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir() # 如:/var/folders/d3/j9tj0gz93dg06bmwxmhh6_xm0000gn/T 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. 使用 同上
4、缓存+数据库Session
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
数据库用于做持久化,缓存用于提高效率 a. 配置 settings.py SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' # 引擎 b. 使用 同上
5、加密cookie Session
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
a. 配置 settings.py SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies' # 引擎 b. 使用 同上
6、Session用户验证
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
def login(func): def wrap(request, *args, **kwargs): # 如果未登陆,跳转到指定页面 if request.path == '/test/': return redirect('http://www.baidu.com') return func(request, *args, **kwargs) return wrap
五、跨站请求伪造
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、普通表单
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
veiw中设置返回值: return render_to_response('Account/Login.html',data,context_instance=RequestContext(request)) 或者 return render(request, 'xxx.html', data) html中设置Token: {% csrf_token %}
2、Ajax
对于传统的form,可以通过表单的方式将token再次发送到服务端,而对于ajax的话,使用如下方式。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# view.py from django.template.context import RequestContext # Create your views here. def test(request): if request.method == 'POST': print request.POST return HttpResponse('ok') return render_to_response('app01/test.html',context_instance=RequestContext(request)) view.py
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
# text.html <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> {% csrf_token %} <input type="button" onclick="Do();" value="Do it"/> <script src="/static/plugin/jquery/jquery-1.8.0.js"></script> <script src="/static/plugin/jquery/jquery.cookie.js"></script> <script type="text/javascript"> var csrftoken = $.cookie('csrftoken'); function csrfSafeMethod(method) { // these HTTP methods do not require CSRF protection return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); } $.ajaxSetup({ beforeSend: function(xhr, settings) { if (!csrfSafeMethod(settings.type) && !this.crossDomain) { xhr.setRequestHeader("X-CSRFToken", csrftoken); } } }); function Do(){ $.ajax({ url:"/app01/test/", data:{id:1}, type:'POST', success:function(data){ console.log(data); } }); } </script> </body> </html> text.html
六、认证系统auth
auth模块是Django提供的标准权限管理系统,可以提供用户身份认证, 用户组管理,并且可以和admin模块配合使用.
在INSTALLED_APPS中添加'django.contrib.auth'使用该APP, auth模块默认启用.
model
from django.contrib.auth.models import User # 数据库中该表名为auth_user. CREATE TABLE "auth_user" ( "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "password" varchar(128) NOT NULL, "last_login" datetime NULL, "is_superuser" bool NOT NULL, "first_name" varchar(30) NOT NULL, "last_name" varchar(30) NOT NULL, "email" varchar(254) NOT NULL, "is_staff" bool NOT NULL, "is_active" bool NOT NULL, "date_joined" datetime NOT NULL, "username" varchar(30) NOT NULL UNIQUE )
新建用户
user = User.objects.create_user(username, email, password) user.save() # 不存储用户密码明文而是存储一个Hash值
认证用户
from django.contrib.auth import authenticate user = authenticate(username=username, password=password) # 认证用户的密码是否有效, 若有效则返回代表该用户的user对象, 若无效则返回None. # 该方法不检查is_active标志位.
修改密码
user.set_password(new_password) # 以下实例为先认证通过后才可以修改密码 user = auth.authenticate(username=username, password=old_password) if user is not None: user.set_password(new_password) user.save()
登录
from django.contrib.auth import login # login向session中添加SESSION_KEY, 便于对用户进行跟踪: 'login(request, user)' # login不进行认证,也不检查is_active标志位 # 实例 user = authenticate(username=username, password=password) if user is not None: if user.is_active: login(request, user)
退出登录
# logout会移除request中的user信息, 并刷新session from django.contrib.auth import logout def logout_view(request): logout(request)
只允许登录的用户访问
@login_required
修饰器修饰的view函数会先通过session key检查是否登录, 已登录用户可以正常的执行操作, 未登录用户将被重定向到login_url
指定的位置.
若未指定login_url参数, 则重定向到settings.LOGIN_URL
from django.contrib.auth.decorators import login_required @login_required(login_url='/accounts/login/') def userinfo(request): ... # settings 配置 LOGIN_URL = '/index/' # views @login_required def userinfo(request): ...
。。。。