Django框架
- Django框架
- 基本配置
- 路由系統
- 模版引擎
- 視圖函數
- 中間件
- admin
- Model
- ORM操作
- 創建表
- 操作表
- 1.基本操作
- 2.進階操作
- 獲取行數 .count()
- 比較 __gt
- in 和 not in
- isnull 為空
- contains 字段內容包含/不包含
- range 范圍
- startswith/istartswith/endswith/iendswith
- 排序 order_by()
- 分組 annotate()
- select_related()
- prefetch_related()
- sql語句 .query
- all()
- filter()
- exclude()
- annotate()
- distinct()
- order_by()
- extra()
- reverse()
- defer()
- only()
- using()
- raw()
- values()
- values_list()
- dates()
- datetimes()
- none()
- aggregate()
- count()
- get()
- create()
- bulk_create()
- get_or_create()
- update_or_create()
- first()
- last()
- in_bulk
- delete()
- update()
- exists()
- 3.其他操作
- 4.連表操作
- Form組件
- XSS攻擊
- 跨站請求偽造
- Cookie
- Session
- 數據分頁
- 緩存
- 序列化
- 信號
基本配置
1.安裝
pip3 install django
2.創建django project
通過終端創建
1.創建django程序
django-admin startproject <proecjt名>
例如:
django-admin startproject mysite
2.創建project中的app文件目錄
python manage.py startapp <app名>
例如:
python manage.py startapp app01
3.進入程序目錄
cd mysite
4.啟動socket服務端,等待用戶發送請求(不寫端口默認8000)
python manage.py runserver 127.0.0.1:8080
5.其他命令
python manage.py createsuperuser # 用於django admin創建超級用戶
python manage.py makemigrations # 記錄對models.py文件的改動
python manage.py migrate # 將改動映射到數據庫
通過IDE創建
通過IDE(pycharm等)創建django project,以上命令均自動執行
3.project目錄結構
目錄結構介紹
- 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文件路徑
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
方式一、方式二可以連用,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)
select_related()
查詢時,主動做連表操作,類似於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()
查詢時,不做連表,作多次單表查詢,實現連表操作目的
- 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將生成

禁用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>
Cookie
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>