Python之路(三十):Django(进阶)

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 }}&nbsp;&nbsp;总页数:{{ 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=())  # 参数详解待更新

 

posted @ 2018-11-16 12:43  __Miracle  阅读(335)  评论(0编辑  收藏  举报