Django框架

Django框架

基本配置

1.安裝

pip3 install django

2.建立django project

經過終端建立

1.建立django程序html

django-admin startproject <proecjt名>
例如:
    django-admin startproject mysite

2.建立project中的app文件目錄前端

python manage.py startapp <app名>
例如:
    python manage.py startapp app01

3.進入程序目錄python

cd mysite

4.啓動socket服務端,等待用戶發送請求(不寫端口默認8000)mysql

python manage.py runserver 127.0.0.1:8080

5.其餘命令jquery

python manage.py createsuperuser  # 用於django admin建立超級用戶
python manage.py makemigrations  # 記錄對models.py文件的改動
python manage.py migrate  # 將改動映射到數據庫

經過IDE建立

經過IDE(pycharm等)建立django project,以上命令均自動執行git

3.project目錄結構

目錄結構介紹web

- mysite(~/PycharmProjects/mysite)
    |- app01/ 
        |- admin.py  # Django自帶管理配置後臺
        |- models.py  # 寫類,根據類建立數據庫表
        |- test.py  # 單元測試
        |- views.py  # 視圖函數
        |- apps.py  # 配置文件 
    |- mysite/
        |- __init__.py
        |- setting.py
        |- urls.py
        |- wsgi.py 
    |- static/
    |- templates/
    |- utils/
    |- manage.py

4.setting.py配置文件

指定template目錄

在settings.py 文件中,指定template文件路徑ajax

TEMPLATES = [
    {
        ...
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        ...
     },

指定static目錄

在settings.py 文件中,添加STATICFILES_DIRS,注意要加逗號正則表達式

STATIC_URL = '/static/'
STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'static'),  # 逗號
)

配置數據庫

  • Django默認使用的數據庫是sqllite3,咱們要使用mysql就須要利用第三方工具鏈接數據庫
  • Django內部鏈接mysql的第三方工具是MySQLDB,可是python3再也不支持MySQLDB
  • 因此得修改django默認鏈接mysql方式爲pymysql

1.建立mysql數據庫
2.修改配置settings.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'dbname',
        'USER': 'root',
        'PASSWORD': '',
        'HOST': 'localhost',
        'PORT': 3306,
    }
}

3.修改項目同名文件下__init__.py文件
pymsql替換內部的myqldb,修改django默認鏈接mysql第三方工具

import pymysql
pymysql.install_as_MySQLdb()

路由系統

路由系統簡介

路由就是在urls.py文件中的url和函數的對應關係

# urls.py文件
from django.conf.urls import url
from django.contrib import admin

# 對應關係
urlpatterns = [
    # url(r'^admin/', admin.site.urls),
    url(r'^login/', login),  # 對應關係
]

其中,

  • 對應關係:/login/, login
  • 請求參數:request
  • 返回方式:HttpResponse\render\redirect

案例

from django.conf.urls import url
from django.contrib import admin

from django.shortcuts import HttpResponse,render,redirect
def login(request):
    print(request.GET)
    if request.method == "GET":
        return render(request,'login.html')
    else:
        # 用戶POST提交的數據(請求體)
        u = request.POST.get('user')
        p = request.POST.get('pwd')
        if u == 'root' and p == '123':
            # 登陸成功
            return redirect('/index/')
        else:
            # 登陸失敗
            return render(request,'login.html',{'msg': '用戶名或密碼錯誤'})
            
urlpatterns = [
# url(r'^admin/', admin.site.urls),
url(r'^login/', login),
url(r'^index/', index),
]

1.單一路由對應

url(r'^index$', views.index),

2.基於正則的路由

基於正則的路由能夠傳遞參數,根據傳遞方式能夠分爲,靜態路由和動態路由兩種

靜態路由

咱們都知道url能夠經過get方式傳參數,這種方式被稱做靜態路由

url(r'^index/?id=12', views.index),  

# 對應views.py中的def index(request),能夠根據request.GET.get('id')獲取參數值

動態路由

路由中的url部分其實就是正則表達式,因此可使用正則表達式替代get傳參數,傳遞方式有兩種 傳遞位置參數 和 傳遞命名參數

位置參數

位置參數對參數順序有要求

url(r'^index/(\w+)/', views.index),  # (\w+)能夠匹配任意字母或數字或下劃線或漢字做爲位置參數,對參數順序有要求

# 匹配的參數會被def index(request,a)中的參數a接收
命名參數

參數經過參數名對應,因此對順序沒要求

url(r'^index/(?P<a2>\d+)/(?P<a1>\d+)/', views.index),   # (?P<a1>\d+)能夠指定匹配任意數字而且參數名爲a1

# 匹配的參數被def index(request,a1,a2)中的參數a1接收,對參數順序沒有要求

注意:位置傳遞和命名傳遞不可混用,因此使用要統一

終止符

由於url就是爲正則表達式,因此url前部匹配成功後就會調用函數,因此爲了完美匹配能夠在url結尾跟/或者$

url(r'^index$', views.index),  # 只會匹配index
url(r'^index', views.index),  # 會所有匹配index開頭字符串

僞靜態

SEO(Search Engine Optimization,搜索引擎優化)靜態網頁的搜索權重高於動態網頁,因此經過僞造動態網頁爲靜態網頁後綴達到權重增長的目的

url(r'^index/(/w+).html$', views.index),  # 表示頁面以.html結尾

因此,url正則表達式方式 + 僞靜態 = 動態路由
不出現‘?id=..’get傳參的寫法,能夠提升SEO的搜索權重

默認頁面

若是用戶輸入url不存在,能夠制定到默認頁面

url(r'^', <指定函數名>),

3.添加額外的參數

url(r'^manage/(?P<title>\w*)', views.manage,{'name':'kirs'}),

4.路由分發

根據據app對路由規則進行分類

一個Django項目中可能會有衆多業務線(衆多app),若是一都使用同一個路由系統可能會致使url重名問題,因此能夠根據每一個app創建本身的urls.py文件,經過項目的路由系統來找到各自app的路由對應關係

1.在app01中新建urls.py,並import本身的views

from django.conf.urls import url
from app01 import views
urlpatterns = [
    url(r'^index.html$', views.index),
]

2.在項目的urls.py中import包include,
在項目的urls.py中寫入app01url和include內容

from django.conf.urls import url,include
urlpatterns = [
    url(r'^app01/', include('app01.urls')), 
]

這樣,url來了,項目路由會先找對應app,找到後,去對應app中的urls中找對應關係

5.url關係命名

給url與函數關係命名

視圖函數應用

未來在視圖函數中能夠根據別名反生成url

- 1.首先,urls.py中
    url(r'^index/', views.index, name='name1'),
    
- 2.在views.py中import包reverse
    from django.urls import reverse
    url = reverse('name1')  # 獲取到/index/

反生成帶特定 位置參數的url

- 1.urls.py中
    url(r'^index/(\w+)/', views.index, name='name1'),
- 2.在views.py中
    from django.urls import reverse
    url = reverse('name1',args=(abc,))  # 獲取到/index/abc/

反生成帶特定 命名參數的url

- 1.urls.py中
    url(r'^index/(?P<b1>\w+)/', views.index, name='name1'),
- 2.在views.py中
    from django.urls import reverse
    url = reverse('name1',kwargs={'b1':abcd,})  # 獲取到/index/abcd/

html應用

未來在html頁面中能夠根據別名取得url地址

- 1.urls.py中
    url(r'^index/', views.index, name='name1'),

- 2.a標籤取url 
    - 原來寫法
        <a href="/index/"></a>
    
    - 如今寫法
        <a href="{% url "name1" %}"></a>  # 效果就是/index/

動態傳遞參數怎麼辦?

- 1.urls.py中
    url(r'^index/(\w+)/(\w+)/', views.index, name='name1'),

- 2.a標籤取url
    - 原來寫法: 
        <a href="/index/(\w+)/(\w+)/"></a>  
    - 如今寫法:
        <a href="{% url "name1" 參數1 參數2 %}"></a>  # 效果就是/index/參數1/參數2
        # <a href="{% url "name1" x y %}"></a>  # 效果就是/index/peter/alex

PS:url與函數關係命名能夠用於權限管理簡化存儲內容:數據庫存儲別名便可,就不用存儲很長的url地址


模版引擎

模版的執行

模版的建立過程,對於模版,其實就是讀取模版(其中嵌套着模版標籤),而後將 Model 中獲取的數據插入到模版中,最後將信息返回給用戶。

其中,返回方式:HttpResponse\render\redirect

#views.py
from django.shortcuts import HttpResponse, render, redirect

# 視圖函數
def login(request): 
    # return HttpResponse('massage')  # 直接獲取內容返回用戶
    return render(request, 'login.html', {'key':'value'})  # 自動找到templates路徑下的login.html文件,讀取內容並返回給用戶
    # return redirect('/login/')  # 重定向文件

render 返回參數

返回參數能夠是

字符串:'name': 'alex',
列表:'users':['tom','kirs'],
字典:'user_dict':{'k1': '123','k2':'234'},
列表套字典:
'user_list_dict': [
                {'id':1, 'name': 'alex', 'email': 'alex3714@163.com'},
                {'id':2, 'name': 'alex2', 'email': 'alex3714@1632.com'},
                {'id':3, 'name': 'alex3', 'email': 'alex3713@1632.com'},
            ]

html 中取值,直接經過.取值

{{users.0}}  {{user.1}}
{{user_dict.k1}}  {{user_dict.k2}}

案例

def index(request):
    # return HttpResponse('Index')
    return render(
        request,
        'index.html',
        {
            'name': 'alex',
            'users':['李志','李傑'],
            'user_dict':{'k1': 'v1','k2':'v2'},
            'user_list_dict': [
                {'id':1, 'name': 'alex', 'email': 'alex3714@163.com'},
                {'id':2, 'name': 'alex2', 'email': 'alex3714@1632.com'},
                {'id':3, 'name': 'alex3', 'email': 'alex3713@1632.com'},
            ]
        }
    )

模版語言

html 渲染方式

{{ }}

{% for i in [列表|字典] %}
    {{i.列1}}
    {{i.列2}}
    ...
{% endfor%}

{% if 條件%}
    ...
{% else %}
    ...
{% endif %}

母版

以前說過的母版就是將公用內容存到html,其餘子板extends便可

{% extends "base.html" %}

include

相似於python中的import
能夠自定義html小組件,將小組件倒入其餘頁面應用,而且一個頁面呢能夠屢次導入

{% include 'pub.html' %}  # pub.html爲組件
#pub.html
<div>
<div class='...'>{{ name }}<>
<div>

PS:而且組件中能夠接受後臺參數

模版引擎

在模版引擎中傳入函數名,自動執行函數,不用加括號
經過後臺傳來的字典具備方法
keys

- 遍歷key
    {% for i in user_list.keys%}
        {{i}}
    {% endfor%}

values

- 遍歷value
    {% for i in user_list.values%}
        {{i}}
    {% endfor%}

items

- 遍歷key和value
    {% for k,v in user_list.items%}
        {{k}}
        {{v}}
    {% endfor%}

內置方法

內置方法經過|方法名執行

- 將字符串大寫
    {{ name|upper }}

模版自定義函數

建立模版自定義函數simple_filter

1.在app建立一個名爲templatetags文件(名稱不可改)
2.建立任意templatetags/xx.py文件,函數增長裝飾器@register.filter

#xx.py
from django import template
        
register = temlate.Library()
    
@register.filter
def my_upper(value):
    return value.upper()

3.導入文件:在html文件中導入以前建立的 xx.py 文件名

{% load xxx %}

4.使用:

{{name|my_upper}}

5.settings.py註冊app

INSTALLED_APPS = (
...
'app',
)

建立模版自定義函數simple_filter最多隻能有兩個參數,可是能夠作條件判斷

- 最多兩個參數,方式: {{第一個參數|函數名稱:"第二個參數"}}
    def my_upper(value,arg):
        return value+arg
        
    {{name|my_upper:'abc'}}
- 能夠作條件判斷

建立模版自定義函數simple_tag

在以上建立過程當中,若是函數增長裝飾器@register.simple_tag

@register.simple_tag
    def my_lower(value):
        return value.lower()

使用時參數個數無限制

- 參數無限制:{% 函數名 參數 參數 ...%}
    def my_lower(value,arg1,arg2,...):
        return value + arg1 + arg2 +...
        
    {% my_lower name 'abc' 'bcd' ...%}

視圖函數

request 請求信息

request.method - 用戶請求方法,GET或者POST
request.GET - 獲取GET方式提交的數據
request.POST — 獲取POST方式提交的數據

request.GET.get()
request.POST.get()
request.POST.getlist() - 獲取列表

其中,客戶端POST方式提交,request一樣能夠獲取GET數據,反之不能夠

視圖函數 CBV FBV

路由系統經過反射方式匹配method(post、get、put等)。

用戶發來請求,url匹配到類,將method看成參數傳入類,經過getattr找到對應方法。

FBV (Function-based views)

路由系統中,url對應函數就是FBV

urls.py

- 直接跟方法名
    url(r'^login.html$',views.login),

views.py

- 類名須要繼承View,能夠定義get、post等方法
    def login(request):
        return render(request, "login.html")

CBV (Class-based views)

一樣,url也能夠對應類,那麼這就是CBV

urls.py

- 類名後跟.as_view()
    url(r'^login.html$',views.Login.as_view()),

views.py

- 類名須要繼承View,能夠定義get、post等方法
    from django.views import View
    
    class Login(View):
        def get(self, request):
            return render(request, "login.html")
    
        def post(self, request):
            print(request.POST.get("user"))
            return HttpResponse("login.post")

from表單只有get、post方法,ajax不只有post和get方法,還有不少其它方法,如put、delete等
PS:約定俗成:get查 post建立 put更新 delete刪除

dispatch 方法

在CBV的get、post方法執行以前View內部會先執行dispatch方法

def dispatch(self, request, *args, **kwargs):
   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)

因此咱們能夠重寫dispatch方法,起到相似裝飾器功能

裝飾get、post等方法

def dispatch(self,request,*args,**kwargs):
    #執行get、post前代碼
    func = super(Login,self).dispatch(request,*args,**kwargs)
    #執行get、post後代碼
    return func

中間件

原理介紹

django 中的中間件(middleware),在django中,中間件其實就是一個類,在請求到來和結束後,django會根據本身的規則在合適的時機執行中間件中相應的方法。

請求-->
        中間件類1方法1-->
                        中間件類2方法1-->
                                       ...-->
                                             路由繫系統and視圖函數 
                                       ...<--
                        中間件類2方法2<--
        中間件類1方法2<--             
響應<--   
    
其中,
方法1都是 process_request(),
方法二都是 process_response()

在django項目的settings模塊中,有一個 MIDDLEWARE_CLASSES 變量,其中每個元素就是一箇中間件

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

請求按照MIDDLEWARE中的中間件順序執行,若是其中一箇中間件在process_request()出現錯誤,就會直接執行這個中間件的process_response(),請求中止繼續執行,返回錯誤信息

請求-->
        中間件類1方法1-->
                        中間件類2方法1-出錯
                                       |
                        中間件類2方法2<--
        中間件類1方法2<--             
響應<--

自定義中間件

應用環境:於用戶認證,替代auth裝飾器,省去給所有視圖函數添加裝飾器

1.新建py文件,倒入入MiddlewareMixin
2.類中函數:process_request、process_response
3.註冊中間件py文件

# m1.py
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse
class M1(MiddlewareMixin):
    def process_request(self,request):
        print('m1.process_request')

    def process_view(self, request, callback, callback_args, callback_kwargs):
        print('m1.process_view')
        # response = callback(request,*callback_args,**callback_kwargs)
        # return response

    def process_response(self,request,response):
        print('m1.process_response')
        return response

    def process_exception(self, request, exception):
        print('m1.process_exception')

    def process_template_response(self,request,response):
        """
        視圖函數的返回值中,若是有render方法,才被調用
        :param request:
        :param response:
        :return:
        """
        print('m1.process_template_response')
        return response

其中,
1.process_request 不能有return,若是return,中間件再也不繼續執行,直接返回
2.process_response 必須須要return 返回值
3.proccess_view 匹配到對應函數,並不會執行,
4.proccess_exception 異常處理,只有視圖函數錯誤纔會執行,中間件會從最後一個類中的proccess_exception開始向前執行,proccess_exception所有執行結束後,再從最後一個類中的process_response開始向前返回
5.proccess_template_view 視圖函數有render,才執行,對全部請求,或一部分請求作批量處理,應用:記錄日誌功能

# settings.py
MIDDLEWARE = [
    ...
    m1.Middle1,
]

Ajax請求返回須要後臺經過HttpResponse返回json.dumps字典數據,這個功能能夠封裝成另外一個類調用render,利用中間件中的proccess_template_view實現

# views.py
from django.shortcuts import render,HttpResponse,redirect

class JSONResponse:
    def __init__(self,req,status,msg):
        self.req = req
        self.status = status
        self.msg = msg
    def render(self):
        import json
        ret = {
            'status': self.status,
            'msg':self.msg
        }
        return HttpResponse(json.dumps(ret))

def test(request):
    # print('test')
    # return HttpResponse('...')
    ret = {}
    return JSONResponse(request,True,"錯誤信息")

PS:在django1.10中,若是process_request中有異常或者return,中間件會從當前類的process_response開始返回

在django1.7或者1.8中,若是process_request中有異常或者return,中間件會從最後一個類中的process_response開始向前返回


admin

Model

Django對數據庫操做使用關係對象映射(Object Relational Mapping,簡稱ORM),通常不使用原生sql(除非遇到複雜sql)

ORM操做

Django中經過ORM操做數據庫

ORM操做表:能夠建立表、修改表、刪除表(SQLAlchemy卻沒法修改表)

ORM操做行:增、刪、改、查

Django的ORM默認使用的數據庫是sqllite3,咱們要使用mysql就須要利用第三方工具鏈接數據庫
Django默認鏈接mysql的第三方工具是MySQLDB,可是python3再也不支持MySQLDB
因此得修改django默認鏈接mysql方式爲pymysql

建立表

建表前準備:配置文件

1.建立數據庫
2.修改配置settings.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'dbname',
        'USER': 'root',
        'PASSWORD': '',
        'HOST': 'localhost',
        'PORT': 3306,
    }
}

3.修改項目同名文件下__init__.py文件
pymsql替換內部的myqldb,修改django默認鏈接mysql第三方工具

import pymysql
pymysql.install_as_MySQLdb()

1.基本結構

a.建表流程

1.寫model:
models.py文件

from diango.db import models

class UserInfo(models.Model):
uid = models.BigAutoField(primary_key=True) # int自增主鍵,默認不寫django自動增長
username = models.CharField(max_length=32) #char(32)
password = models.CharField(max_length=64)
age = models.IntegerField(default=1) # int,默認值爲1,也能夠null=True

2.app寫入配置
settings.py,將app寫入

INSTALLED_APPS = [
    ...
    'app01',
]

3.執行命令

python3 manage.py makemigrations
python3 manage.py migrate

PS:每次修改表結構和表結構後經過命令更新便可

4.更新表
models.py文件

class UserGroup(Models.Model):
    title = models.CharField(max_length=32)

class UserInfo(models.Model):
    ...
    ug = models.ForeignKey("UserGroup", null=True)  # 制定外鍵表名,能夠爲空,建立外鍵
b.數據類型字段
字符串
CharField(Field)
    - 字符類型
    - 必須提供max_length參數, max_length表示字符長度
字符串(Django Admin中使用數據類型的而且帶有對應類型驗證)
EmailField(CharField)
    - email
IPAddressField(Field)
    - ip
URLField(CharField)
    - url
SlugField(CharField)
    - url並提供驗證支持 字母、數字、下劃線、鏈接符(減號)
UUIDField(Field)
    - 提供對UUID格式的驗證
FilePathField(Field)
    - file並提供讀取文件夾下文件的功能
    - 參數:
          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   上傳圖片的寬度保存的數據庫字段名(字符串)
CommaSeparatedIntegerField(CharField)
    - 字符串類型,格式必須爲逗號分割的數字
時間
DateTimeField(DateField)
    - 日期+時間格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]
DateField(DateTimeCheckMixin, Field)
    - 日期格式      YYYY-MM-DD
數字
IntegerField()
    - 整數列(有符號的) -2147483648 ~ 2147483647
SmallIntegerField(IntegerField)
    - 小整數 -32768 ~ 32767
BigIntegerField(IntegerField)
    - 長整型(有符號的) -9223372036854775808 ~ 9223372036854775807
FloatField(Field)
    - 浮點型
DecimalField(Field)
   - 10進制小數
   - 參數:
       max_digits,小數總長度
       decimal_places,小數位長度
布爾
BooleanField(Field)
    - 布爾值類型
NullBooleanField(Field):
    - 能夠爲空的布爾值
枚舉(django)

枚舉沒有本身的字段,是經過利用其它字段實現枚舉功能

color_list = (
    (1,'黑色'),
    (2,'白色'),
    (3,'藍色')
)
color = models.IntegerField(choices=color_list)
c.字段參數
null = True 
    - 爲空
default = '123' 
    - 默認值
primary_key = True 
    - 主鍵
max_length = 12
    - 最大長度
unique_for_date時間建立索引

db_index = True 
    - 單列索引 
unique = True 
    - 單列惟一索引 

class Meta:
    # 聯合惟一索引
    unique_together = ( 
        ('email','ctime'),
    )
    # 聯合索引
    index_together = ( 
        ('email','ctime'),
    )

2.連表結構

  • 一對多:models.ForeignKey(其餘表)
  • 多對多:models.ManyToManyField(其餘表)
  • 一對一:models.OneToOneField(其餘表)
多對多

多對多以及如何創建聯合惟一索引

方式一:ForeignKey

1.關係表Love中寫Class Meta

Class Love(models.Model):
    b = models.ForeignKey('Boy')
    # b = models.ForeignKey(to='Boy',to_field='id') #關聯哪一個表哪一個字段
    g = models.ForeignKey('Girl')
    
    Class Meta:  # 創建惟一索引
        unique_together = [
            ('b','g'),
        ]
方式二:models.ManyToManyField

2.經過在Boy或Girl表中寫 ManyToManyField(另外一張表名)

class Boy(models.Model):
    name = models.CharField(max_length=32)
     m = models.ManyToManyField('Girl')

這樣django會自動生成兩表的關係表boy_m,
可是boy_m表中只有三個字段:id、boy_id、girl_id,由於關係表boy_m是自動生成,沒有對應類,因此不能直接經過獲取關係表對象操做,那麼要如何操做呢?

對關係表boy_m的操做須要先獲取一個Boy對象或者Girl對象

- 獲取對象 
    obj = models.Boy.objects.filter(name='alex').first()
- 增長 
    obj.m.add(2)  # 給alex增長girl_id=2的數據
- 批量增長
    obj.m.add(1,2,...)
- 列表增長
    l = [1,2,...]
    obj.m.add(*l)
    
- 刪除
    obj.m.remove(1)  # 給alex刪除girl_id=1這條數據
- 批量刪除,同添加
    obj.m.remove(1,2,...)
- 列表刪除,同添加
    l = [1,2,...]
    obj.m.remove(*l)
    
- 重製 
    obj.m.set([1,2,...])  # 將本來的關係所有刪除,插入心的關係
    
- 查詢 
    girl_list = obj.m.all()  # 查詢到的是 QuerySet列,其中是Girl object
- 條件查詢
    girl_list = obj.m.filter(nick='alice')
    
- 清空
    obj.m.clear()  # 清空與alex有關所有數據

那麼對於Girl對象如何操做關係表呢?經過 小寫表名_set實現

- 獲取對象 
    obj = models.Girl.objects.filter(nick='alice').first()
- 查詢
    boy_list = obj.boy_set.all()
方式三:models.ManyToManyField + ForeignKey

方式1、方式二能夠連用,ManyToManyField中須要though和through_fields,可是方式二中的用法只有查詢和清空有效

#models.py
class Boy(models.Model):
    name = models.CharField(max_length=32)
    m = models.ManyToManyField('Girl',through="Love",through_fields=('b','g',)) 
    # 查詢和清空

class Girl(models.Model):
    nick = models.CharField(max_length=32)
    # m = models.ManyToManyField('Boy')

class Love(models.Model):
    b = models.ForeignKey('Boy')
    g = models.ForeignKey('Girl')

    class Meta:
        unique_together = [
            ('b','g'),
        ]
單表自關聯

數據庫中一個表中能夠根據id兩兩關聯
例如,有UserInfo表,讓其中id進行關聯,如何實現自關聯?

新建關係表 + ForeignKey

新建關係表 + ForeignKey

# models.py文件
class U2U(models.Model): # 關係表
    b = models.ForeignKey('UserInfo', related_name='boys')
    g = models.ForeignKey('UserInfo', related_name='girls')
    # b = models.ForeignKey('UserInfo', related_query_name='boys')
    # g = models.ForeignKey('UserInfo', related_query_name='girls')

其中,要用到反向查找別名替換,如何讓設置別名?這裏涉及知識點

related_query_name = 'b'
related_name = 'a'

這樣從關係表中查詢UserInfo表中的信息時,使用別名便可

obj.a_set.all()  # 經過related_query_name起名
obj.a.all()  # 經過related_name起名

以上,經過ForeignKey + 關係表實現了 自關聯

查詢到與id爲1的男生有關係的女生nickname

obj = models.UserInfo.objects.filter(id=1).first()  # 找到id爲1的UserInfo對象obj
    result = obj.boys.all()  # 與對象obj有關的全部信息:Querset列表[U2Ud對象]
    for i in result:
        print(i.g.nickname)  # 從U2U對象正向查找g的nickname
ManyToManyField

經過ManyToManyField實現

#models.py
class UserInfo(models.Model):
    ...
    m = models.ManyToManyField('UserInfo')

建立後會生成關係表,其中字段:id、from_userinfo_id、to_userinfo_id,其中,from_userinfo_id存放男生id,to_userinfo_id存放女生id

查詢與id=1的男生有關係的女生姓名

obj = models.UserInfo.objects.filter(id=1).first()
    result = obj.m.all()
    for i in result:
        print(i.nickname)

其中,obj.m是將obj做爲from_userinfo_id,查詢所有的to_userinfo_id

查詢到與id爲4的女生有關係的男生nickname

obj = models.UserInfo.objects.filter(id=4).first()
    result = obj.userinfo_set.all()
    for i in result:
        print(i.nickname)

其中,userinfo_set是將obj做爲to_userinfo_id,查詢所有的from_userinfo_id

FK自關聯 (評論信息存儲表)

應用於評論信息存儲表

id news_id conent user reply_id
1 1 什麼鬼 alex null
2 1 什麼什麼鬼 egon 1
3 1 sb eric 2
#models.py
class Comment(models.Model):
    news_id = models.IntegerField()
    conent = models.CharField(max_length=100)
    user = models.CharField(max_length=32)
    reply_id = models.ForeignKey('Comment', null=True, blank=True)

操做表

1.基本操做

views.py

def sql(request):
    from app01 import models

    # 增長數據
    models.UserGroup.objects.create(title="銷售部")
    models.UserInfo.objects.create(username='root', password='123', age="20", ug_id=1)

    # 查詢
    user_list = models.UserInfo.objects.all()  # 拿到QuerySet列表,其中存儲的對像(一行就是一個對象)
    #models.UserInfo.objects.all().first()  # 取到QuerySet中的第一個對象
    group_list = models.UserGroup.objects.filter(id=1, title="銷售部")  # 條件查詢
    group_list = models.UserGroup.objects.filter(id__gt=1)  # id>1(_lt小於)
    print(user_list, group_list)
    for i in user_list:  # 顯示字段
        print(i.username, i.age)

    # 刪除
    models.UserInfo.objects.filter(id=2).delete()  # 刪除

    # 更新
    models.UserInfo.objects.filter(id=1).update(title="人事部")  # 更新

    return HttpResponse("...")

2.進階操做

獲取行數 .count()
models.UserInfo.objects.all().count()
比較 __gt
models.UserInfo.objects.filter(id__gt=1)  # id>1
models.UserInfo.objects.filter(id__gte=1)  # id>=1
models.UserInfo.objects.filter(id__lt=2)  # id<2
models.UserInfo.objects.filter(id__lte=2)  # id<=2
models.UserInfo.objects.filter(id__gte=1, id__lt=2)  # id>=1,id<2
in 和 not in
models.UserInfo.objects.filter(id__in=[1, 2, 4]) # 獲取id在1,2,4中數據
models.UserInfo.objects.exclude(id__in=[1, 2, 4]) # 獲取除id在1,2,4中的數據
isnull 爲空

查詢字段爲空的數據 ... where ut_id IS NULL

models.UserInfo.objects.filter(ut__isnull=True)
contains 字段內容包含/不包含

字段是否包含內容 ... where name like '%a%' /... where not (name like '%a%' )

models.UserInfo.objects.filter(name__contains="a")
models.UserInfo.objects.filter(name__icontains="a")  # 大小寫不敏感
models.UserInfo.objects.exclude(name__icontains="a")
range 範圍

bettwen ... and ...

models.UserInfo.objects.filter(id_range=[1, 3])
startswith/istartswith/endswith/iendswith

... where name like 'a%'

models.UserInfo.objects.filter(name__startswith='a')
models.UserInfo.objects.filter(name__istartswith='a')
排序 order_by()
- 篩選結果按照id ASC排序
    models.UserInfo.objects.filter(id__gt=1).order_by("id")
- DESC排序
    models.UserInfo.objects.filter(id__gt=1).order_by("-id")

PS:order_by("id","name") 先按照id再按照name ASC排序

分組 annotate()
- import聚合函數
    from django.db.models import Count, Sum, Max, Min, Avg
- annotate()必須配合values(),values()中是要分組的列
    models.UserInfo.objects.values("ut_id").annotate(x=Count('id'))
    對應sql:
    "SELECT ut_id, COUNT(id) AS x FROM UserInfo GROUP BY ut_id"

PS:分組後結果再filter(),相似於group by後的having操做

models.UserInfo.objects.values("ut_id").annotate(x=Count('id')).filter(ut_id__gt=1)

查詢時,主動作連表操做,相似於inner join操做

- select_related(要鏈接表的外鍵)
    q = models.UserInfo.objects.all().select_related('ut')
- 執行原理
    " select * from UserInfo inner join UserType on UserInfo.ut = UserType.id "

- 取值
    for row in q:
        print(row.name,row.ut.title)

查詢時,不作連表,做屢次單表查詢,實現連表操做目的

- prefetch_related(外鍵)
    q = models.UserInfo.objects.all().perfetch_related('ut')
- 執行原理
    " select * from UserInfo "
    獲取UserInfo所有ut組成列表[1,2,...]
    " select * from UserType where id in [1,2,...] "
    
- 取值
    for row in q:
        print(row.id,row.ut.title)

PS:單表查詢比連表操做查詢性能高

sql語句 .query

經過.query 能夠查看sql語句

result = models.UserInfo.objects.all()
print(result.query)
all()
獲取全部的數據對象
filter()
條件查詢
 條件能夠是:參數,字典,Q
exclude()
條件查詢
條件能夠是:參數,字典,Q
annotate()
實現聚合group by 查詢
distinct()
去重複
models.UserInfo.objects.values('nid').distinct()
order_by()
排序
extra()
構造額外查詢條件或者映射
reverse()
倒序
models.UserInfo.objects.all().order_by('-id').reverse()

PS:若是存在order_by,reverse則是倒序,若是多個排序則一一倒序

defer()
映射中排除某些列
models.UserInfo.objects.defer('name','id')
或者
models.UserInfo.objects.filter(...).defer('name','id')
only()
僅取某個表中的數據
models.UserInfo.objects.only('name','id')
或
models.UserInfo.objects.filter(...).only('name','id')

PS:使用only效率比all高,可是使用only也能夠點出未包括在only中的列,可是效率會比all第,因此,only哪些列就使用哪些列

using()
制定使用的數據庫,參數爲別名,setting.py中設置的
raw()
執行原生sql
models.UserInfo.objects.raw('select * from userinfo')
若是SQL是其餘表時,必須將名字設置爲當前UserInfo對象的主鍵列名
models.UserInfo.objects.raw('select pid as id from 其餘表')
爲原生SQL設置參數
models.UserInfo.objects.raw('select id as nid from userinfo where nid>%s', params=[12,])
將獲取的到列名轉換爲指定列名
name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'}
Person.objects.raw('SELECT * FROM some_other_table', translations=name_map)
指定數據庫
models.UserInfo.objects.raw('select * from userinfo', using="default")
values()
獲取每行數據爲字典格式
values_list()
獲取每行數據爲元組
dates()
def dates(self, field_name, kind, order='ASC'):
# 根據時間進行某一部分進行去重查找並截取指定內容
# kind只能是:"year"(年), "month"(年-月), "day"(年-月-日)
# order只能是:"ASC"  "DESC"
# 並獲取轉換後的時間
    - year : 年-01-01
    - month: 年-月-01
    - day  : 年-月-日

models.DatePlus.objects.dates('ctime','day','DESC')
datetimes()
def datetimes(self, field_name, kind, order='ASC', tzinfo=None):
# 根據時間進行某一部分進行去重查找並截取指定內容,將時間轉換爲指定時區時間
# kind只能是 "year", "month", "day", "hour", "minute", "second"
# order只能是:"ASC"  "DESC"
# tzinfo時區對象
models.UserInfo.objects.datetimes('ctime','hour',tzinfo=pytz.UTC)
models.UserInfo.objects.datetimes('ctime','hour',tzinfo=pytz.timezone('Asia/Shanghai'))

"""
pip3 install pytz
import pytz
pytz.all_timezones
pytz.timezone(‘Asia/Shanghai’)
"""
none()
def none(self):
# 空QuerySet對象
aggregate()
def aggregate(self, *args, **kwargs):
   # 聚合函數,獲取字典類型聚合結果
   from django.db.models import Count, Avg, Max, Min, Sum
   result = models.UserInfo.objects.aggregate(k=Count('u_id', distinct=True), n=Count('nid'))
   ===> {'k': 3, 'n': 4}
count()
def count(self):
   # 獲取個數
get()
def get(self, *args, **kwargs):
   # 獲取單個對象
create()
def create(self, **kwargs):
   # 建立對象
bulk_create()
def bulk_create(self, objs, batch_size=None):
    # 批量插入
    # batch_size表示一次插入的個數
    objs = [
        models.DDD(name='r11'),
        models.DDD(name='r22')
    ]
    models.DDD.objects.bulk_create(objs, 10)
get_or_create()
def get_or_create(self, defaults=None, **kwargs):
    # 若是存在,則獲取,不然,建立
    # defaults 指定建立時,其餘字段的值
    obj, created = models.UserInfo.objects.get_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 2})
update_or_create()
def update_or_create(self, defaults=None, **kwargs):
    # 若是存在,則更新,不然,建立
    # defaults 指定建立時或更新時的其餘字段
    obj, created = models.UserInfo.objects.update_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 1})
first()
def first(self):
   # 獲取第一個
last()
def last(self):
   # 獲取最後一個
in_bulk
def in_bulk(self, id_list=None):
   # 根據主鍵ID進行查找
   id_list = [11,21,31]
   models.DDD.objects.in_bulk(id_list)
delete()
def delete(self):
   # 刪除
update()
def update(self, **kwargs):
    # 更新
exists()
def exists(self):
   # 是否有結果

3.其餘操做

F

獲取數據表中列字段進行操做

- import F
    from django.db.models import F
- F("age")+1 age列自加1,更新時取到原來的值
    v = models.UserInfo.objects.update(age=F('age') + 1)

PS:v取到被修改的行數

Q 字典傳參數

用於構造複雜的查詢條件,兩種用法,對象和方法

- import Q
    from django.db.models import Q

對象 方式多用於通常條件查詢,相似filter,因此不經常使用

- Q對象,表示一個條件
models.UserInfo.objects.filter(Q(id=1)) 
- 多條件查詢
    models.UserInfo.objects.filter(Q(id=1) | Q(id=2))

方法 方式多用於組合條件查詢,動態查詢

同條件進行or,不一樣條件進行and,循環拼接查詢條件

q1 = Q()
    q1.connector = "OR"
    q1.children.append(("id", 1))
    q1.children.append(("id", 2))
    q1.children.append(("id", 3))

    q2 = Q()
    q2.connector = "OR"
    q2.children.append(("name", "alex"))
    q2.children.append(("name", "tom"))
    q2.children.append(("name", "peter"))

    con = Q()
    con.add(q1, "AND")
    con.add(q2, "AND")

    result = models.UserInfo.objects.filter(con)
    print(result)
    for i in result:
        print(i.name, i.ut.title)

相似查詢:

select * from UserInfo where  (id=1 or id=2 or id =3)AND (name="alex" or name ="tom" or name = "peter")
extra 額外查詢條件

在model基礎上進行額外的查詢條件以及相關表,排序

extra的參數有select,select_params,where,params,tables,order_by

- select能夠做爲臨時表的查詢結果放在select映射部分,其中x爲別名,select_params配合select使用
    models.UserInfo.objects.extra(select={"x": "select count(id) from app01_usertype where id >%s"},select_params=[1, ])
- where能夠做爲where條件部分,params配合where使用
    models.UserInfo.objects.extra(where=['id>%s'], params=[1, ])
- tables能夠做爲原錶鏈接的新表,組成笛卡爾積
    models.UserInfo.objects.extra(tables=['app01_usertype'])
- order_by能夠對原表排序
    models.UserInfo.objects.extra(order_by=['-id'])
原生sql語句

django原生sql已幫咱們鏈接數據庫,獲取遊標

- 導入connection利用現成連接
from django.db import connection, connections
- 經過鏈接獲取遊標
    cursor = connection.cursor()  # 默認鏈接setting.py中的default DATABASES
- 能夠選擇其它數據庫
    # cursor = connections['default'].cursor()  # default是選擇setting.py中的DATABASES,能夠根據須要變換數據庫
- sql語句
    cursor.execute("select * from app01_userinfo where id = %s", [1, ])
- fetchone或者fetchall
v = cursor.fetchone()

4.連表操做

一對多

對象如何實現連表查詢?
答:經過外鍵實現

經過操做表數據的介紹可知,通常查詢方法有all(),filter(),如此這般查詢到的結果是QuerySet列表,列表中存放的是對象(一行數據就是一個對象)

經過values()查詢到的結果是QuerySet列表,列表中存放的是字典

經過values_list()查詢到的結果是QuerySet列表,列表中存放的是元組

對象
- 正向操做
本表中有外鍵的,直接經過 外鍵列名.字段便可

    result = models.UserInfo.objects.all()  # 獲取UserInfo表中所有QuerySet列表
    for i in result:
        print(i.id, i.name, i.age, i.ut_id, i.ut.title)  # 外鍵ut.title
        
- 反向操做
本表主鍵是其它表的外鍵,直接經過 對象.小寫被外鍵的表名_set.all() 便可 

    obj = models.UserType.objects.all().first()  # 獲取UserType表中的第一行QuerySet對象
    list = obj.userinfo_set.all()  # 小寫被外鍵的表名_set.all()
    print('用戶類型', obj.id, obj.title)
    for i in list:  
        print(i.name, i.age)
字典

字典能夠是直接經過values()獲得,也能夠是all().values()或者filter().values()獲得

- 正向操做
    - 獲取字典
    
        result = models.UserInfo.objects.all().values('id', 'name', 'ut__title')
        或者
        result = models.UserInfo.objects.values('id', 'name', 'ut__title')
        
    - 經過鍵取值
    
        print(result)
        for obj in result:
            print(obj['id'], obj['name'], obj['ut__title'])
            
    其中,ut__title經過外鍵加雙下劃線列名,能夠實現字典跨表查詢

- 反向操做
想要經過字典反向跨表操做,經過 小寫表名__列名 便可(只寫小寫表名爲id)

    # 字典反向操做
    result = models.UserType.objects.values("id", "title", "userinfo", "userinfo__age", "userinfo__name")  
    print(result)
    for obj in result:
        print(obj["id"], obj["title"], obj["userinfo"], obj["userinfo__age"], obj["userinfo__name"])
元組

元組能夠是直接經過values_list()獲得,也能夠是all().values_list()或者filter().values_list()獲得

- 正向操做
    - 獲取元組 
    
        obj = models.UserInfo.objects.all().values_list('id','name','ut_title') 
        或者
        obj = models.UserInfo.objects.values_list('id','name','ut_title') 
        
    - 經過索引取值 
    
        obj[0],obj[1],obj[3]
其中,ut__title經過外鍵加雙下劃線列名,能夠實現元組的跨表查詢

- 反向操做
想要經過元組反向跨表操做,經過 小寫表名__列名 便可(只寫小寫表名爲id)

    # 元組反向操做
    result = models.UserType.objects.values_list("id", "title", "userinfo", "userinfo__age", "userinfo__name")  
    print(result)
    for obj in result:
        print(obj[0], obj[1], obj[2], obj[3], obj[4])

PS:

  • filter().values()或者filter().values_list()能夠增長篩選條件,這樣將篩選結果再跨表操做
  • 正向反向都是left join效果
多對多

已知:表Boy、表Girl、關係表Love
要求:查詢到與alex有關係的girl

通常查詢

有四種查詢方式

# 方式一:從Boy表獲取alex對象,反向操做Love表獲得所有與alex有關係list,循環list正向取得girl名字,查詢2n+2次
obj = models.Boy.objects.filter(name="alex").first() # 查詢1次
    love_list = obj.love_set.all() # 查詢1次
    for i in love_list: # 查詢 2n次
        print(i.g.nick)
        
# 方式二:Love表字典正向操做取得有alex的list,循環list正向取得girl名字,查詢2n+1次
    love_list = models.Love.objects.filter(b__name="alex") # 查詢 1次
    for obj in love_list: # 查詢 2n次
        print(obj.g.nick)

# 方式三:Love表left join Girl表正向操做取的有alex的字典list,循環list取得girl名字,查詢n+1次
    love_list = models.Love.objects.filter(b__name='alex').values("g__nick") # 查詢1次
    for item in love_list: # 查詢n次
        print(item["g__nick"])
        
# 方式四:Love表inner join Girl表正向操做取的有alex的字典list,循環list取得girl名字,查詢n+1次
    obj = models.Love.objects.filter(b__name='alex').select_related("g") # 查詢 1次
    for i in obj: # 查詢n次
        print(i.g.nick)

PS:推薦後兩種,查詢效率稍高

Form組件

Django中Form組件主要功能:

  • 生成HTML標籤
  • 驗證用戶數據(顯示錯誤信息)
  • HTML Form提交保留上次提交數據
  • 初始化頁面顯示內容

Form組件簡單示例

1.建立Form類(肯定字段約束)

from django.forms import Form,fields

class LoginForm(Form):

     username = fields.CharField(  # html標籤的name屬性 就是 Form類字段名
        max_length=18,
        min_length=6,
        required=True,
        error_messages={
            'required': '用戶名不能爲空',
            'min_length': '過短了',
            'max_length': '太長了',
        }
    )

其中,
字段不可爲空: required=True
字段長度: min_length~max_length
錯誤信息:error_messages

2.view函數(使用提交的數據)

# 獲取POST對象
obj = LoginForm(request.POST)  

# 是否校驗成功 True或False
v = obj.is_valid()  
    
# 全部錯誤信息 , 對象
obj.errors

# 獲取字典格式數據,經過**dic能夠直接操做數據表的增改查詢
obj.cleaned_data

3.html(前端生成標籤)

{{ obj.username }} {{ obj.errors.username.0 }}

PS:

1.cleaned_data字典
對數據表中的某行數據的增、改、查操做可使用字典做爲傳入參數,精簡代碼,因此對數據表的操做能夠直接將obj.cleaned_data字典數據看成參數傳入

dic = {'name':'alex','age':12,...}
    
- 增長
    models.Table.objects.create(**dic)
- 更新
    models.Table.objects.filter(**dic)
- 查詢 查詢條件間是and關係
    models.Table.objects.filter(**dic)

2.is_valid()原理

1.實例化 LoginForm

self.fields{
    'user':正則表達式,
    'pwd':正則表達式,
    ...
}

2.循環 self.fields

flag = True
for k, v self.fields.items():  # k:user,v:正則表達式
    if request.POST.get(k)不符合v的正則表達式:
        flag = False
return flag

Form類

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)
    ...

RegexField(CharField)
    regex,                      自定製正則表達式
    max_length=None,            最大長度
    min_length=None,            最小長度
    error_message=None,         忽略,錯誤信息使用 error_messages={'invalid': '...'}
 
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
    ...
 
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類型
    ...

補充

檢測內置字段自己錯誤信息,例如,EmailField檢測的就是字段不符合郵箱地址的格式

fields.CharField(error_messages={'inalid','格式錯誤'})

針對IntegerField字段,字段範圍

number = fields.IntegerField(max_value=100,min_value=15)

生成標籤有關字段

t1 = fields.CharField(
    label='用戶名',  # 標籤的label命名
    lable_suffix=':'  # label內容後綴
    help_text=‘請入電話號碼’,  # 標籤幫助信息
    initital='username',  # input輸入框顯示默認值
    disabled=True,  # 輸入框是否爲能夠編輯
    
    import django.forms import widgets
    widget=widgets.Select,  # 選擇字段標籤爲select,默認是input
)  

html 調用

{{ obj.t1.label }}{{ obj.t1.lable_suffix }}
{{ obj.t1 }}{{ obj.t1.help_text }}

其實,html 調用 只須要寫以下就能夠了

{{ obj.as_p }}

PS:

有些瀏覽器會自動幫咱們增長校驗,卻掉瀏覽器的校驗給標籤加 novalidate 屬性

<form action='...' method='...' movalidate >...</form>

Form生成HTML標籤、驗證、不刷新、初始值全套

爲了實現form提交不會刷新頁面,咱們以前使用ajax方法,一樣的咱們能夠經過使用Django的form組件實現

ajax提交示例

經過ajax不刷新頁面驗證form,而且保留上次輸入內容

views.py

def ajax_login(request):
    import json
    ret = {'status': True, 'message': None}
    obj = loginForm(request.POST)
    if obj.is_valid():
        print(obj.cleaned_data)
    else:
        ret['status'] = False
        ret['message'] = obj.errors
    return HttpResponse(json.dumps(ret))

login.html

<script src="/static/jquery-3.2.1.js"></script>
<script>
    function ajaxForm() {
        $('.c1').remove();
        $.ajax({
            url: '{% url "ajax_login" %}',
            type: 'POST',
            data: $('#f1').serialize(),
            dataType: 'JSON',
            success: function (arg) {
                if (arg.status) {
                    location.href = '{% url index %}'
                } else {
                    $.each(arg.message, function (index, value) {
                        var tag = document.createElement('span');
                        tag.innerHTML = value[0];
                        tag.className = 'c1';
                        $('#f1').find("input[name='" + index + "']").after(tag)
                    })
                }
            }
        })
    }
</script>

login.html

<form method="post" action="{% url 'login' %}" id="f1">
    ...
    <a onclick="ajaxForm();" class="btn btn-default">ajax提交</a>
</form>

PS:

1.serialize()提交表單所有
ajax提交form表單時,能夠將form表單內所有數據提交,使用serialize()方法

data: $('#f1').serialize(),  # 其中<form id='f1'>

提交原理

user=alex&pwd=123456&scrftoken=...

form提交示例

不刷新頁面驗證form,而且保留上次輸入內容

1.建立form類

from django.shortcuts import render, HttpResponse, redirect
from django.forms import Form, fields, widgets
from app01 import models

class loginfForm(Form):
    username = fields.CharField(
        label='用戶名',
        widget=widgets.Input(attrs={'class': 'form-control'})
    )
    password = fields.CharField(
        label='密碼',
        widget=widgets.Input(attrs={'class': 'form-control', 'type': 'password'})
    )

2.view函數處理

def loginf(request):
    if request.method == 'GET':
        obj = loginfForm()
        return render(request, 'loginf.html', {'obj': obj})
    else:
        obj = loginfForm(request.POST)
        if obj.is_valid():
            row = models.User.objects.filter(username=obj.cleaned_data['username'],
                                             password=obj.cleaned_data['password']).first()
            if row:
                return redirect('/index.html')
        return render(request, 'loginf.html', {'obj': obj})

3.loginf.html

<form method="post" action="{% url 'loginf' %}" novalidate>
    {% csrf_token %}
    {{ obj.username.label }}{{ obj.username }} {{ obj.errors.username.0 }}
    {{ obj.password.label }} {{ obj.password }} {{ obj.errors.password.0 }}
    <input type="submit" value="登陸">
</form>

PS:

單選:生成select字段,參數需是元組

widget=widgets.Select(choices=[(1,'北京'),(2,'上海'),...])
widget=widgets.Select(choices=models.City.objects.values_list('id','title'))

生成字段加css樣式

widget=widgets.TextInput(attrs={'class':'form-control'})

初始值(data和initial)

生成html標籤,並含有錯誤信息

obj = funcForm(request.POST)
省略了data,本質就是
obj = funcForm(data = request.POST)

只生成html標籤,而且能夠附帶默認值

obj = funcForm({'username':'alex','cls_id':[1,2,3]})
省略了initial,本質是
obj = funcForm(initial = {'username':'alex','cls_id':[1,2,3]})

Form驗證

1.首先,字段驗證
2.而後,單獨字段方法 clean__參數
3.最後,clean 方法

1正則校驗

a.字段自己正則

自定義正則表達式的字段有兩種方式

- 1.自定義驗證規則,做爲內置字段正則的補充
    fields.CharField( validators=[] )               

- 2.自定義正則字段
    fields.RegexField('010\d+')  # 010開頭的數字

b.額外的正則

from django.forms import Form,widgets,fields
from django.core.validators import RegexValidator
 
class MyForm(Form):
    user = fields.CharField(
        validators=[RegexValidator(r'^[0-9]+$', '請輸入數字'), RegexValidator(r'^159[0-9]+$', '數字必須以159開頭')],
    )
2驗證字段是否已存在在數據庫中

關鍵字:clean_字段 和 clean

class funcForm(Form):
    user = fields.CharField()
    pwd = fields.CharField()
    email = fields.CharField()
    
    # 單個字段校驗
    def clean_user(self):
        v = self.cleand_data['user']
        if models.Student.objects.filter(name=v).count():
            # 主動異常
            raise ValidationError('用戶名已存在')
        return self.cleaned_data['user']  # 必須返回,不然obj.cleaned_data['user']就被清空了
    
    def clean_pwd(self):)
        return self.cleaned_data['pwd']  # 必須返回
        
    # 聯合校驗
    def clean(self):
        user = self.cleand_data['user']
        email = self.cleand_data['email']
        if models.Student.objects.filter(name=user,email=email).count():
            raise ValidationError('用戶名和郵箱聯合已經存在')
        return self.cleaned_data  # 必須返回,不然obj.cleaned_data就被清空了

製做插件

單選下拉菜單

以前使用的IntegerField

cls_id = fields.IntegerField(
    # widget=widgets.Select(choices=[(1,'上海'),(2,'北京')])
    widget=widgets.Select(choices=models.Classes.objects.values_list('id','title'),attrs={'class': 'form-control'})
)

使用ChoiceField

cls_id = fields.ChoiceField(
    choices = models.Classes.objects.values_list('id','title'),
    widget = widgets.Select()
)
可多選下拉菜單

首先嚐試使用字段CharField

cls_id = fields.CharField(
    widget = widgets.SelectMultiple(choices = models.Classes.objects.values_list('id','title'))
)
這樣獲得的cls_id結果是
{ 'cls_id': "['1','2']" }
其中,列表是SelectMultiple結果,可是列表又變成了字符串,那是由於CharField的做用,因此不能使用CharField

因此真正的單選是這樣的,使用ChoiceField,而且要把其中的choices參數提取出來

cls_id = fields.MultipleChoiceField(
    choices = models.Classes.objects.values_list('id','title'),
    widget = widgets.SelectMultiple
)
這樣獲得的cls_id結果是
{ 'cls_id': ['1','2']}

下拉列表中的數據在程序啓動時加入內存,沒法動態顯示數據庫中的變化,爲了修復這個Bug,刷新沒法動態顯示數據庫內容:

方式一:

class TeacherForm(Form):
    tname = fields.CharField(min_length=2)
    xx = form_model.ModelMultipleChoiceField(queryset=models.Classes.objects.all())
    # xx = form_model.ModelChoiceField(queryset=models.Classes.objects.all())

方式二:

class TeacherForm(Form):
    tname = fields.CharField(min_length=2)
    xx = fields.MultipleChoiceField(
        # choices = models.Classes.objects.values_list('id','title') # 這句可註釋
        widget=widgets.SelectMultiple
    )
    def __init__(self,*args,**kwargs):
        super(TeacherForm,self).__init__(*args,**kwargs)
        self.fields['xx'].choices = models.Classes.objects.values_list('id','title')

編輯界面如何將信息加載到頁面

def edit_teacher(request, nid):
    if request.method == 'GET':
        row = models.Teacher.object.filter(id=nid).first()
        class_ids = row.c2t.values_list(id)
        id_list = list(zip(*class_ids))[0] if list(zip(*class_ids)) else []
        obj = TeacherForm(initial={'tname':row.tname,'xx':id_list})
        return render(request,'edit_teacher.html',{'obj':obj})
checkbox

一個

cb = fields.CharField(
    widget = widgets.CheckboxInput
)

多個

cbs = fields.MultipleChoiceField(
    choices = [(1,'籃球'),(2,'足球'),(3,'排球')],
    widget = widgets.CheckboxSelectMultiple
)
radio
r = fields.ChoiceField(
    choices = [(1,'籃球'),(2,'足球'),(3,'排球')],
    widget = widgets.RadioSelect
)
文件上傳

f1.html
須要在form標籤增長 enctype="multipart/form-data"

<form method="post" action="/f1/" enctype="multipart/form-data">
        {% csrf_token %}
        <input type="file" name="fafafa">
        <input type="submit" value="提交">
    </form>

普通上傳

def f1(request):
    if request.method == "GET":
        return render(request, 'f1.html')
    else:
        import os
        file_obj = request.FILES.get('fafafa')
        f = open(os.path.join('static', file_obj.name), 'wb')
        for chunk in file_obj.chunks():
            f.write(chunk)
        f.close()
        return render(request, 'f1.html')

基於form實現文件上傳

class f2Form(Form):
    fafafa = fields.FileField()


def f2(request):
    if request.method == "GET":
        obj = f2Form()
        return render(request, 'f2.html', {'obj': obj})
    else:
        import os
        obj = f2Form(files=request.FILES)

        if obj.is_valid():
            print(obj.cleaned_data.get('fafafa').name)
            print(obj.cleaned_data.get('fafafa').size)
            f = open(os.path.join('static', obj.cleaned_data.get('fafafa').name), 'wb')
            for chunk in obj.cleaned_data.get('fafafa').chunks():
                f.write(chunk)
            f.close()

        return render(request, 'f2.html', {'obj': obj})

XSS攻擊

惡意web用戶將代碼植入到提供給其它用戶使用的頁面中

經過頁面評論等功能,插入js代碼,獲取cookie等信息,叫作跨站腳本攻擊(Cross Site Scripting)

<script>alert(123)</script>

django默認阻止傳入頁面帶有標籤的字符串,想要瀏覽器不阻止傳入字符串的解析須要標註safe關鍵字

- 前端
    <div>{{ msg | safe }}</div>
- 後臺
    from django.utils.safestring import mark_safe
    msg = "<a href='http://www.sdf.com'>sdf</a>"
    safe_msg = mark_safe(msg)
    return render(request,'conmment.html',{'msg':safe_msg})

因此,前端不要輕易加safe

可是,非要加safe,又想要避免被xss攻擊,能夠在後臺接收字段時,判斷是否有標籤關鍵字

v = request.POST.get('content')
if 'script' in v:
    return render(request,"conmment.html",{'error':'輸入內容存在安全隱患'})

PS:

  • 慎用 safe和mark_safe
  • 必須得用的話,必定要過濾關鍵字

跨站請求僞造

CSRF攻擊

CSRF(Cross-site request forgery)跨站請求僞造,經過假裝來自受信任用戶的請求來利用受信任的網站。

一個網站用戶Bob可能正在瀏覽聊天論壇,而同時另外一個用戶Alice也在此論壇中,而且後者剛剛發佈了一個具備Bob銀行連接的圖片消息。設想一下,Alice編寫了一個在Bob的銀行站點上進行取款的form提交的連接,並將此連接做爲圖片src。若是Bob的銀行在cookie中保存他的受權信息,而且此cookie沒有過時,那麼當Bob的瀏覽器嘗試裝載圖片時將提交這個取款form和他的cookie,這樣在沒經Bob贊成的狀況下便受權了此次事務。

主要利用受害者恰好也在操做銀行頁面,cookie沒有過時,攻擊者利用圖片連接中的隱藏的form提交POST請求給銀行,銀行驗證cookie沒問題,會執行轉帳操做。

這樣經過其它網站直接向銀行發送請求的操做應該被禁止,因此在用戶登陸銀行系統時,銀行會給用戶一個隨機字符串,在接下來的POST請求中,必需要有這個隨機字符串,用戶才能夠正常操做,不然,銀行認爲操做操做違法。

啓用CSRF驗證

Diango中帶有自動生成字符串的功能來避免被CSRF攻擊

- 啓用CSRF驗證
    settings.py中MIDDLEWAR的'django.middleware.csrf.CsrfViewMiddleware'表示啓用驗證
- form表單中增長
    {% csrf_token %}
- 頁面加載後會有隱藏的input標籤
    <input type="hidden" name="csrfmiddlewaretoken" value="dguniTLGtMB3xCoq3YFuyTzBtgOaCjdYygEf5QvzTwGqQuAyetpnCdvh6o1bIqNm">
- 瀏覽器中的cookie將生成
    ![](media/14980919010345/14986489795952.jpg)

禁用CSRF驗證

- 全站禁用
將settings.py中'django.middleware.csrf.CsrfViewMiddleware',註釋

- 局部禁用 FBV 給函數加裝飾器
    from django.views.decorators.csrf import csrf_exempt
    @csrf_exempt
    def func(request):
    ...
    
- 局部使用
    # 'django.middleware.csrf.CsrfViewMiddleware',  # 註釋
        
    from django.views.decorators.csrf import csrf_protect
    @csrf_protect
    def func(request):
        ...

- 局部使用 CBV csrf_protect不可給類中的方法加裝飾器
    from django.views import View
    from django.views.decorators.csrf import csrf_protect
    from django.utils.decorators import method_decorator
    
    # @method_decorator(csrf_protect,name = 'dispatch')  # 方法二:給所有方法被裝飾
    # @method_decorator(csrf_protect,name = 'post')  # 方法三:制定給某個方法加裝飾器
    @method_decorator(csrf_protect)  # 方法一:給類加,所有方法被裝飾
    class Login(View):
        def get(self, request):
            return render(request, "login.html")
            
        def post(self, request):
            print(request.POST.get("user"))
            return HttpResponse("login.post")

PS:CBV 如何加裝飾器

Django中CBV不可直接加裝飾器,須要經過method_decorator(裝飾器)

from django.views import View
    from django.utils.decorators import method_decorator
    
    # @method_decorator(auth,name = 'dispatch')  # 方法二:給所有方法被裝飾
    # @method_decorator(auth,name = 'fun2')  # 方法三:制定給某個方法加裝飾器
    @method_decorator(auth)  # 方法一:給類加,所有方法被裝飾
    class Login(View):
        def func1(self, request):
            ...
            
        # @method_decorator(auth)  # 方法四:給方法加,僅該方法被裝飾
        def fun2(self, request):
            ...

Ajax提交數據時候,攜帶CSRF

放在data中攜帶

若是網站中帶有csrftoken,發送ajax應該將csrftoken帶進去

<script src="/static/jquery-3.2.1.js"></script>
<script>
   function submitForm() {
       {#從頁面中獲取csrf值#}
       var csrf = $('input[name="csrfmiddlewaretoken"]').val();
       $.ajax({
           url:"/login.html",
           type:"POST",
           data:{'csrfmiddlewaretoken':csrf},
           success:function(arg){
               console.log(arg)
           }
       })
   }
</script>

放在請求頭

從cookie中獲取csrf值

獲取cookie的插件:jquery.cookie.js
獲取token:$.cookie('csrftoken')
加入請求頭:headers:{'X-CSRFToken':token}

<script src="/static/jquery-3.2.1.js"></script>
    <script src="/static/jquery.cookie.js"></script>
    <script>
        function submitForm() {
            var token = $.cookie('csrftoken');
            $.ajax({
                url:"/login.html",
                type:"POST",
                data:{},
                {#加入請求頭#}
                headers:{'X-CSRFToken':token},
                success:function(arg){
                    console.log(arg)
                }
            })
        }
    </script>

1.保存在瀏覽器端的「鍵值對」
2.服務端能夠向用戶瀏覽器端寫cookie
3.客戶端每次請求,會攜帶coolie

- 發送cookie
    obj = redirect("/index/")
    obj.set_cookie("ticket","1qazxsw23edc",max_age = 10)  
    return obj
    
- set_cookie方法:
    max_age = 10  //超時時間
    path = '/'  //指定url才能夠後去cookie
    domain = None  //域名等級劃分,多點登陸,統一認證登陸
    httponly = True  //只有http請求中才可用,js代碼沒法獲取cookie
    secure = True  //只有https請求才可用,用戶修改cookie沒用
    
- 接收cookie
    tk = request.COOKIES.get("ticket")

- 加簽名
set_signed_cookie("ticket","1qazxsw23edc",salt='abc')  
    salt = '' //加鹽

- 接收cookie+解密
    tk = request.get_signed_cookie("ticket",salt='abc')

Session

原理

cookie是保存在客戶端瀏覽器上的鍵值對
Session是保存在服務端的數據(本質也是鍵值對)

http請求特色是短連接,無狀態
session與cookie具備一樣的功能:用於服務端驗證客戶端信息,啓到保持會話做用,
而且session應用須要依賴cookie
session與cookie具備不一樣的是:session不會將敏感信息直接給客戶端

django實現

發送session步驟:
1.生成 隨機字符串
2.經過cookie將 隨機字符串 發送給客戶端
3.服務端將 隨機字符串 對應 客戶端信息 鍵值對{ 隨機字符串:{‘username’:‘alex’,...} }保存到某個地方

- 語句
    def func(request):
        request.session['username']='alex'
        # request.session['user_info']={'username':'alex',...} #能夠寫成字典,做爲session隨機字符串的value
        
        return ...

獲取session步驟:
1.獲取客戶端cookie中的 隨機字符串
2.去session中查詢有沒有對應 隨機字符串
3.去session對應的key的value中查看是否有username

- 語句
    def func2(request):
        v = request.session.get('username')
        # v = request.session.get('user_info').get('username')
        if v:
            登陸成功
        else:
            失敗

前端:

- 前端直接經過request.session獲取字典
    {{ request.session.user_info.username }}

session其餘操做:

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的隨機字符串
        s_k = request.session.session_key # 獲取隨機字符串自己
 
        # 將全部Session失效日期小於當前日期的數據刪除
        request.session.clear_expired()
 
        # 檢查 用戶session的隨機字符串 在數據庫中是否存在
        request.session.exists(s_k)
 
        # 刪除 當前用戶的全部Session數據
        request.session.delete(s_k) # 能夠用做登出loginout
        
        # session超時
        request.session.clear() # 能夠用做登出loginout
            
         # 修改 session超時時間   
        request.session.set_expiry(value)
            * 若是value是個整數,session會在些秒數後失效。
            * 若是value是個datatime或timedelta,session就會在這個時間後失效。
            * 若是value是0,用戶關閉瀏覽器session就會失效。
            * 若是value是None,session會依賴全局session失效策略。

配置文件設置session settings.py

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,默認修改以後才保存(默認)只要用戶從新操做頁面,session超時時間就會重置,能夠設置爲True

session保存設置

session默認保存在數據庫,django可讓session放在內存、緩存、數據庫(默認)、文件、cookie等等
想要session存放於不一樣位置,只須要修改配置文件settings.py

數據庫:

SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默認)

緩存:(緩存至關於另一臺服務器的內存)

SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 引擎
SESSION_CACHE_ALIAS = 'default'                            # 使用的緩存別名(默認內存緩存,也能夠是memcache),此處別名依賴緩存的設置

文件:

SESSION_ENGINE = 'django.contrib.sessions.backends.file'    # 引擎
SESSION_FILE_PATH = None                                    # 緩存文件路徑,若是爲None,則使用tempfile模塊獲取一個臨時地址tempfile.gettempdir()                                                            # 如:/var/folders/d3/j9tj0gz93dg06bmwxmhh6_xm0000gn/T

緩存+數據庫(優先去緩存拿,緩存沒有去數據庫拿)

SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'        # 引擎

加密cookie(至關於又放到coolie,沒什麼用)

SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'   # 引擎

數據分頁

數據查詢到後顯示到頁面,如何實現分頁,Django自帶分頁功能,可是有些缺陷,因此咱們自定義一個分頁組件

分頁核心功能

ORM經過相似python中的切片功能實現分批獲取數據

models.UserInfo.objects.all()[0:10]  # 從0開始取到9,取頭不取尾
models.UserInfo.objects.all()[10:20]  # 從10開始取到19

Django自帶分頁

Django自帶分頁適用於只有上一頁,下一頁的狀況

views.py

def index(request):
    '''
    分頁
    :param request: 
    :return: 
    '''
    current_page = request.GET.get('page')
    user_list = models.UserInfo.objects.all()
    paginator = Paginator(user_list, 10) # 用戶列表,每頁顯示10條數據
    # ---paginator方法---
    # page:     page對象
    # per_page: 每頁顯示條目數量
    # count:    數據總個數
    # num_pages:總頁數
    # page_range:總頁數的索引範圍,如: (1,10),(1,200)
    try:
        posts = paginator.page(current_page)  # 新建page對象
        # ---posts方法---
        # has_next              是否有下一頁
        # next_page_number      下一頁頁碼
        # has_previous          是否有上一頁
        # previous_page_number  上一頁頁碼
        # object_list           分頁以後的數據列表
        # number                當前頁
        # paginator             paginator對象
    except PageNotAnInteger as e:  # 頁碼不是整型
        posts = paginator.page(1)
    except EmptyPage as e:  # 頁碼爲空
        posts = paginator.page(1)
        return render(request, 'index.html', {'posts': posts})

index.html

<ul>
    {% for i in posts.object_list %}
        <li>{{ i.name }}</li>
    {% endfor %}
</ul>
<div>
    {% if posts.has_previous %}
        <a href="/index.html?page={{ posts.previous_page_number }}">上一頁</a>
    {% endif %}
</div>
<div>
    {% if posts.has_next %}
        <a href="/index.html?page={{ posts.next_page_number }}">下一頁</a>
    {% endif %}
</div>

自定義一個分頁組件

views.py

from utils.pager import PageInfo

def custom(request):
    total_row = models.UserInfo.objects.all().count()  # 所有數據行數
    page_info = PageInfo(request.GET.get('page'), total_row, 10, '/custom.html', 11)
    user_list = models.UserInfo.objects.all()[page_info.start():page_info.end()]
    return render(request, 'custom.html', {"userlist": user_list, "page_info": page_info})

utils.pager.py

class PageInfo(object):
    def __init__(self, current_page, total_row, per_page, base_url, show_page=11):
        '''

        :param current_page: 當前頁
        :param total_row: 總數據行數
        :param per_page: 每頁顯示幾條數據
        :param base_url: url地址
        :param show_page: 顯示幾頁
        '''
        try:
            self.current_page = int(current_page)
        except Exception as e:
            self.current_page = 1
        self.per_page = per_page
        # 求頁碼數
        x, y = divmod(total_row, per_page)
        if y:  # 若是餘數不爲0,頁碼+1
            x = x + 1
        self.total_page = x
        self.show_page = show_page
        self.base_url = base_url

    def start(self):
        return (self.current_page - 1) * self.per_page

    def end(self):
        return self.current_page * self.per_page

    def pager(self):
        page_list = []
        half_page = int((self.show_page - 1) / 2)  # 1/2顯示幾頁

        # 總頁數小於顯示幾頁 <11
        if self.total_page < self.show_page:
            begin = 1
            stop = self.total_page + 1
        # 總頁數大於顯示幾頁 >11
        else:
            # 當前頁小於一半
            if self.current_page <= half_page:
                begin = 1
                stop = self.show_page + 1
            # 當前頁大於一半
            else:
                # 當前頁+一半大於總頁碼
                if self.current_page + half_page > self.total_page:
                    begin = self.total_page - self.show_page + 1
                    stop = self.total_page + 1
                # 當前頁+一半小於總頁碼
                else:
                    begin = self.current_page - half_page
                    stop = self.current_page + half_page + 1
        if self.current_page <= 1:
            prev = '<li><a href="#">上一頁</a></li>'
        else:
            prev = '<li><a href="%s?page=%s">上一頁</a></li>' % (self.base_url, self.current_page - 1,)
        page_list.append(prev)

        for i in range(begin, stop):
            if i == self.current_page:
                temp = '<li class="active"><a href="%s?page=%s">%s</a></li>' % (self.base_url, i, i)
            else:
                temp = '<li><a href="%s?page=%s">%s</a></li>' % (self.base_url, i, i)
            page_list.append(temp)

        if self.current_page >= self.total_page:
            nex = '<li><a href="#">下</a></li>'
        else:
            nex = '<li><a href="%s?page=%s">下一頁</a></li>' % (self.base_url, self.current_page + 1,)
        page_list.append(nex)

        return ''.join(page_list)

custom.html

<ul>
    {% for i in userlist %}
        <li>{{ i.name }}</li>
    {% endfor %}
</ul>
<nav aria-label="Page navigation">
    <ul class="pagination">
        {{ page_info.pager|safe }}
        {#        html調用函數不須要加括號pager(),瀏覽器信任插入字符串safe#}
    </ul>
</nav>

緩存

序列化

信號

相關文章
相關標籤/搜索