目錄
子類HttpResponseRedirect(注意namespace的配置)
前言
該備忘錄是基於以下版本開發
Mac OS High sierra 10.13.6
mintoudeMacBook-Pro-2:~ mintou$ python3 -m django --version
2.2.1
mintoudeMacBook-Pro-2:~ mintou$ ipython3
Python 3.7.3 (default, Mar 27 2019, 09:23:39)
[Clang 10.0.0 (clang-1000.11.45.5)] on darwin
安裝
pip3 install Django
pip3 install ipython3
FVFXGM44HV29:mysite mikejing191$ ipython3
Python 3.7.3 (default, Mar 27 2019, 09:23:39)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.4.0 -- An enhanced Interactive Python. Type '?' for help.
In [1]: import django
In [2]: django.get_version()
Out[2]: '2.2.1'
In [3]:
創建項目(模型映射表)
cd到指定目錄(例如桌面),mkdir一個文件夾放項目
然後執行
django-admin startproject test1
- manage.py:一個命令行工具,可以使你用多種方式對Django項目進行交互
- 內層的目錄:項目的真正的Python包
- _init _.py:一個空文件,它告訴Python這個目錄應該被看做一個Python包
- settings.py:項目的配置
- urls.py:項目的URL聲明
- wsgi.py:項目與WSGI兼容的Web服務器入口
創建應用(默認方式)
1.cd到manage.py下的目錄,然後執行創建一個booktest的應用
mintoudeMacBook-Pro-2:DJangoProject mintou$ pwd
/Users/mintou/Desktop/DJangoProject
mintoudeMacBook-Pro-2:DJangoProject mintou$ ls
test1
mintoudeMacBook-Pro-2:DJangoProject mintou$ cd test1/
mintoudeMacBook-Pro-2:test1 mintou$ ls -a
. booktest manage.py
.. db.sqlite3 test1
mintoudeMacBook-Pro-2:test1 mintou$ python3 manage.py startapp booktest
從這也可以看到Django是默認用的sqlite3編寫的
看下應用的目錄
2.定義模型類
- 有一個數據表,就有一個模型類與之對應
- 打開models.py文件,定義模型類
- 引入包from django.db import models
- 模型類繼承自models.Model類
- 說明:不需要定義主鍵列,在生成時會自動添加,並且值爲自動增長
- 當輸出對象時,會調用對象的str方法
from django.db import models
class BookInfo(models.Model):
book_title = models.CharField(max_length=20)
book_publish_date = models.DateTimeField()
def __str__(self):
# 2.1最新版本不需要encode
return self.book_title
class HeroInfo(models.Model):
hero_name = models.CharField(max_length=20)
hero_gender = models.BooleanField()
hero_content = models.CharField(max_length=100)
# 這裏和1.x版本不同的是需要增加on_delete CASCADE代表級聯操作。主表刪除之後和這個關聯的都會跟隨刪除
hero_book = models.ForeignKey('BookInfo', on_delete=models.CASCADE)
def __str__(self):
return self.hero_name
3.生成數據表遷移
- 激活模型:編輯settings.py文件,將booktest應用加入到installed_apps中
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'booktest',
]
- 生成遷移文件:根據模型類生成sql語句
python3 manage.py makemigrations
- 遷移文件被生成到應用的migrations目錄
- 執行遷移:執行sql語句生成數據表
python3 manage.py migrate
這個 migrate
命令選中所有還沒有執行過的遷移(Django 通過在數據庫中創建一個特殊的表 django_migrations
來跟蹤執行過哪些遷移)並應用在數據庫上 - 也就是將你對模型的更改同步到數據庫結構上。
遷移是非常強大的功能,它能讓你在開發過程中持續的改變數據庫結構而不需要重新刪除和創建表 - 它專注於使數據庫平滑升級而不會丟失數據。我們會在後面的教程中更加深入的學習這部分內容,現在,你只需要記住,改變模型需要這三步:
- 編輯
models.py
文件,改變模型。 - 運行
python manage.py makemigrations
爲模型的改變生成遷移文件。 - 運行
python manage.py migrate
來應用數據庫遷移。
數據庫遷移被分解成生成和應用兩個命令是爲了讓你能夠在代碼控制系統上提交遷移數據並使其能在多個應用裏使用;這不僅僅會讓開發更加簡單,也給別的開發者和生產環境中的使用帶來方便。
4.測試數據操作
回到manage.py的目錄進入shell,導入包然後執行代碼查看所有BookInfo的信息
mintoudeMacBook-Pro-2:test1 mintou$ pwd
/Users/mintou/Desktop/DJangoProject/test1
mintoudeMacBook-Pro-2:test1 mintou$ ls
booktest db.sqlite3 manage.py test1
mintoudeMacBook-Pro-2:test1 mintou$ python3 manage.py shell
Python 3.6.3 (v3.6.3:2c5fed86e0, Oct 3 2017, 00:32:08)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.3.1 -- An enhanced Interactive Python. Type '?' for help.
In [1]: from booktest.models import BookInfo, HeroInfo
In [2]: from django.utils import timezone
In [3]: from datetime import *
In [4]: BookInfo.objects.all()
Out[4]: <QuerySet [<BookInfo: 射鵰銀熊轉>, <BookInfo: 水滸傳>]>
- 新建圖書信息:
In [5]: book1 = BookInfo()
In [6]: book1.book_title = "三國演義"
In [8]: book1.book_publish_date = datetime(year=2018,month=1,day=30)
In [9]: book1.save()
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1421: RuntimeWarning: DateTimeField BookInfo.book_publish_date received a naive datetime (2018-01-30 00:00:00) while time zone support is active.
RuntimeWarning)
In [10]: BookInfo.objects.all()
Out[10]: <QuerySet [<BookInfo: 射鵰銀熊轉>, <BookInfo: 水滸傳>, <BookInfo: 三國演義>]>
- 查找圖書信息:
In [11]: BookInfo.objects.get(pk=3)
Out[11]: <BookInfo: 三國演義>
- 修改圖書信息:
In [12]: book2 = BookInfo.objects.get(pk=1)
In [13]: book2.book_title
Out[13]: '射鵰銀熊轉'
In [14]: book2.book_title = "射鵰轉"
In [15]: book2.save()
- 刪除圖書信息:
book2.delete()
關聯對象的操作
- 對於HeroInfo可以按照上面的操作方式進行
- 添加,注意添加關聯對象
In [18]: HeroInfo.objects.all()
Out[18]: <QuerySet [<HeroInfo: 郭靖>, <HeroInfo: 黃蓉>, <HeroInfo: 浪裏白條>, <HeroInfo: 小李廣>, <HeroInfo: 花和尚>]>
In [19]: h=HeroInfo()
In [20]: h.hero_name = "霹靂火"
In [21]: h.hero_gender = True
In [22]: h.hero_content = "晴明"
In [23]: BookInfo.objects.all()
Out[23]: <QuerySet [<BookInfo: 射鵰轉>, <BookInfo: 水滸傳>, <BookInfo: 三國演義>]>
In [24]: b2=BookInfo.objects.get(pk=2)
In [25]: h.hero_book=b2
In [26]: h.save()
In [27]: HeroInfo.objects.all()
Out[27]: <QuerySet [<HeroInfo: 郭靖>, <HeroInfo: 黃蓉>, <HeroInfo: 浪裏白條>, <HeroInfo: 小李廣>, <HeroInfo: 花和尚>, <HeroInfo: 霹靂火>]>
- 獲得關聯集合:返回當前book對象的所有hero
In [28]: b2.heroinfo_set.all()
Out[28]: <QuerySet [<HeroInfo: 浪裏白條>, <HeroInfo: 小李廣>, <HeroInfo: 花和尚>, <HeroInfo: 霹靂火>]>
- 有一個HeroInfo存在,必須要有一個BookInfo對象,提供了創建關聯的數據:
In [29]: h = b2.heroinfo_set.create(hero_name='豹子頭',hero_gender=True,hero_con
...: tent="靈寵")
In [30]: HeroInfo.objects.all()
Out[30]: <QuerySet [<HeroInfo: 郭靖>, <HeroInfo: 黃蓉>, <HeroInfo: 浪裏白條>, <HeroInfo: 小李廣>, <HeroInfo: 花和尚>, <HeroInfo: 霹靂火>, <HeroInfo: 豹子頭>]>
Django後臺管理系統
服務器
- 運行如下命令可以開啓服務器
python3 manage.py runserver
- 可以不寫ip,默認端口爲8000
- 這是一個純python編寫的輕量級web服務器,僅在開發階段使用
- 服務器成功啓動後,提示如下信息
mintoudeMacBook-Pro-2:test1 mintou$ python3 manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
August 16, 2018 - 10:07:53
Django version 2.1, using settings 'test1.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
- 默認端口是8000,可以修改端口
python3 manage.py runserver 8080
- 打開瀏覽器,輸入網址“127.0.0.1:8000”可以打開默認頁面
- 如果修改文件不需要重啓服務器,如果增刪文件需要重啓服務器
- 通過ctrl+c停止服務器
管理操作
-
站點分爲“內容發佈”和“公共訪問”兩部分
- “內容發佈”的部分負責添加、修改、刪除內容,開發這些重複的功能是一件單調乏味、缺乏創造力的工作。爲此,Django會根據定義的模型類完全自動地生成管理模塊
使用django的管理
- 創建一個管理員用戶
python3 manage.py createsuperuser
按提示輸入用戶名、郵箱、密碼
- 啓動服務器,通過“127.0.0.1:8000/admin”訪問,輸入上面創建的用戶名、密碼完成登錄
- 進入管理站點,默認可以對groups、users進行管理
管理界面本地化
- 編輯settings.py文件,設置編碼、時區
LANGUAGE_CODE = 'zh-Hans'
TIME_ZONE = 'Asia/Shanghai'
向admin註冊模型
- 打開booktest/admin.py文件,註冊模型
from django.contrib import admin
# 這裏Python3編寫就不能和2一樣直接寫models,需要在寫一個.
from .models import BookInfo, HeroInfo
admin.site.register(BookInfo)
admin.site.register(HeroInfo)
自定義管理頁面
- Django提供了admin.ModelAdmin類
- 通過定義ModelAdmin的子類,來定義模型在Admin界面的顯示方式
class QuestionAdmin(admin.ModelAdmin):
...
admin.site.register(Question, QuestionAdmin)
列表頁屬性
- list_display:顯示字段,可以點擊列頭進行排序
list_display = ['pk', 'book_title', 'book_publish_date']
- list_filter:過濾字段,過濾框會出現在右側
list_filter = ['book_title']
- search_fields:搜索字段,搜索框會出現在上側
search_fields = ['book_title']
- list_per_page:分頁,分頁框會出現在下側
list_per_page = 10
添加、修改頁屬性
- fields:屬性的先後順序
fields = ['book_publish_date', 'book_title']
- fieldsets:屬性分組
fieldsets = [
('basic',{'fields': ['book_title']}),
('more', {'fields': ['book_publish_date']}),
]
關聯對象
-
對於HeroInfo模型類,有兩種註冊方式
- 方式一:與BookInfo模型類相同
- 方式二:關聯註冊
-
按照BookInfor的註冊方式完成HeroInfo的註冊
- 接下來實現關聯註冊
from django.contrib import admin
# Register your models here.
from .models import BookInfo, HeroInfo
# 插入的Book的時候插入Hero 指定三條
class HeroInfoInline(admin.StackedInline):
model = HeroInfo
extra = 3
class BookAdmin(admin.ModelAdmin):
# 展示方式 UI
list_display = ['pk', 'book_title', 'book_publish_date']
# 搜索UI
search_fields = ['book_title']
# 插入BookInfo的時候可以帶上Hero子類插入,具體信息看HeroInfoInline的屬性
inlines = [HeroInfoInline]
class HeroAdmin(admin.ModelAdmin):
list_display = ['pk', 'hero_name', 'hero_gender', 'hero_content', 'hero_book']
search_fields = ['hero_name']
# 這裏把需要的models註冊進行 例如 BookInfo 和 HeroInfo 最基本的UI展示
# 如果需要自定義UI就需要寫一個繼承於admin.ModelAdmin來指定字段編寫展示
admin.site.register(BookInfo, BookAdmin)
admin.site.register(HeroInfo, HeroAdmin)
插入BookInfo的時候可以看到底部會附帶三個HeroInfo讓我們填寫
布爾值的顯示
- 發佈性別的顯示不是一個直觀的結果,可以使用方法進行封裝 在對應的Model裏面編寫
class HeroInfo(models.Model):
hero_name = models.CharField(max_length=20)
hero_gender = models.BooleanField()
hero_content = models.CharField(max_length=100)
# 這裏和1.x版本不同的是需要增加on_delete CASCADE代表級聯操作。主表刪除之後和這個關聯的都會跟隨刪除
hero_book = models.ForeignKey('BookInfo', on_delete=models.CASCADE)
def __str__(self):
return self.hero_name
def gender(self):
if self.hero_gender:
return "男"
else:
return "女"
gender.short_description = '性別'
- 在admin註冊中使用gender代替hero_gender
class HeroAdmin(admin.ModelAdmin):
list_display = ['pk', 'hero_name', 'gender', 'hero_content', 'hero_book']
search_fields = ['hero_name']
視圖
- 在django中,視圖對WEB請求進行迴應
- 視圖接收reqeust對象作爲第一個參數,包含了請求的信息
- 視圖就是一個Python函數,被定義在views.py中
from django.shortcuts import render
from django.http import HttpResponse
def index(request):
return HttpResponse('<h1>Hello Mikejing<h1>')
def detail(request,id):
return HttpResponse('detail page--->%s'%id)
URLconf(注意namespace的配置)
- 在Django中,定義URLconf包括正則表達式、視圖兩部分
- Django使用正則表達式匹配請求的URL,一旦匹配成功,則調用應用的視圖
- 注意:只匹配路徑部分,即除去域名、參數後的字符串
- 在test1/urls.py插入booktest,使主urlconf連接到booktest.urls模塊
from django.contrib import admin
from django.urls import path, include, re_path
urlpatterns = [
path('admin/', admin.site.urls),
re_path(r'^', include('booktest.urls'))
]
這裏需要注意兩點
1.這個是主入口,需要例如有個應用模塊是booktest,我們需要另外再建一個urls,那麼根部就需要把它include進來,需要把include包導入
2.2x版本之前是默認支持正則的,Django 2.x之後需要把path和re_path兩個包都導入,後者對應正則匹配路徑
- 在booktest中的urls.py中添加urlconf
from django.urls import path, re_path
from . import views
urlpatterns = [
re_path(r'^$', views.index, name='index'),
re_path(r'^book/([0-9]+)$', views.detail, name='detail'),
]
模板
- 模板是html頁面,可以根據視圖中傳遞的數據填充值
- 創建模板的目錄如下圖:
- 修改settings.py文件,設置TEMPLATES的DIRS值
'DIRS': [os.path.join(BASE_DIR, 'templates')],
- 在模板中訪問視圖傳遞的數據
{{輸出值,可以是變量,也可以是對象.屬性}}
{%執行代碼段%}
index.html模板
<body>
<ul>
{%for book in lists%}
<li><a href="/book/{{book.id}}">{{book.book_title}}</a></li>
{%endfor%}
</ul>
</body>
注意:
這裏a標籤裏面的href中寫的路徑有兩個區別如下圖,首先明確一點,無論哪種都是從頭根部urls開啓重新匹配
帶/和不帶的區別,一般寫全路徑都需要帶上/
detail.html模板
<body>
<ul>
{%for hero in lists%}
<li>{{hero.hero_name}} {{hero.hero_gender}} {{hero.hero_content}}</li>
{%endfor%}
</ul>
</body>
應用urls
from django.urls import path, re_path
from . import views
urlpatterns = [
re_path(r'^$', views.index, name='index'),
re_path(r'^book/([0-9]+)$', views.detail, name='detail'),
]
這裏說的不是根目錄下的urls,是每個創建的應用對應的urls,裏面如果用了re_path正則來匹配,那麼正則裏面獲取值的方式就是(),加了就能傳遞到views.detail方法裏面作爲第二個參數,沒有默認還是一個
views裏面的id就是正則()裏面匹配到的id,根據id查找對應的heros進行模板渲染
from django.shortcuts import render
from django.http import HttpResponse
from .models import *
def index(request):
books = BookInfo.objects.all()
context = {'lists': books}
return render(request, 'booktest/index.html', context)
def detail(request, id):
book = BookInfo.objects.get(pk=id)
heros = book.heroinfo_set.all()
return render(request, 'booktest/detail.html', {'lists': heros})
模型Model詳細介紹
- MVC框架中包括一個重要的部分,就是ORM,它實現了數據模型與數據庫的解耦,即數據模型的設計不需要依賴於特定的數據庫,通過簡單的配置就可以輕鬆更換數據庫
- ORM是“對象-關係-映射”的簡稱,主要任務是:
- 根據對象的類型生成表結構
- 將對象、列表的操作,轉換爲sql語句
- 將sql查詢到的結果轉換爲對象、列表
- 這極大的減輕了開發人員的工作量,不需要面對因數據庫變更而導致的無效勞動
- Django中的模型包含存儲數據的字段和約束,對應着數據庫中唯一的表
- 在模型中定義屬性,會生成表中的字段
- django根據屬性的類型確定以下信息:
- 當前選擇的數據庫支持字段的類型
- 渲染管理表單時使用的默認html控件
- 在管理站點最低限度的驗證
- django會爲表增加自動增長的主鍵列,每個模型只能有一個主鍵列,如果使用選項設置某屬性爲主鍵列後,則django不會再生成默認的主鍵列
- 屬性命名限制
- 不能是python的保留關鍵字
- 由於django的查詢方式,不允許使用連續的下劃線
定義屬性
- 定義屬性時,需要字段類型
- 字段類型被定義在django.db.models.fields目錄下,爲了方便使用,被導入到django.db.models中
- 使用方式
- 導入from django.db import models
- 通過models.Field創建字段類型的對象,賦值給屬性
- 對於重要數據都做邏輯刪除,不做物理刪除,實現方法是定義isDelete屬性,類型爲BooleanField,默認值爲False
字段類型
- AutoField:一個根據實際ID自動增長的IntegerField,通常不指定
- 如果不指定,一個主鍵字段將自動添加到模型中
- BooleanField:true/false 字段,此字段的默認表單控制是CheckboxInput
- NullBooleanField:支持null、true、false三種值
- CharField(max_length=字符長度):字符串,默認的表單樣式是 TextInput
- TextField:大文本字段,一般超過4000使用,默認的表單控件是Textarea
- IntegerField:整數
- DecimalField(max_digits=None, decimal_places=None):使用python的Decimal實例表示的十進制浮點數
- DecimalField.max_digits:位數總數
- DecimalField.decimal_places:小數點後的數字位數
- FloatField:用Python的float實例來表示的浮點數
- DateField[auto_now=False, auto_now_add=False]):使用Python的datetime.date實例表示的日期
- 參數DateField.auto_now:每次保存對象時,自動設置該字段爲當前時間,用於"最後一次修改"的時間戳,它總是使用當前日期,默認爲false
- 參數DateField.auto_now_add:當對象第一次被創建時自動設置當前時間,用於創建的時間戳,它總是使用當前日期,默認爲false
- 該字段默認對應的表單控件是一個TextInput. 在管理員站點添加了一個JavaScript寫的日曆控件,和一個“Today"的快捷按鈕,包含了一個額外的invalid_date錯誤消息鍵
- auto_now_add, auto_now, and default 這些設置是相互排斥的,他們之間的任何組合將會發生錯誤的結果
- TimeField:使用Python的datetime.time實例表示的時間,參數同DateField
- DateTimeField:使用Python的datetime.datetime實例表示的日期和時間,參數同DateField
- FileField:一個上傳文件的字段
- ImageField:繼承了FileField的所有屬性和方法,但對上傳的對象進行校驗,確保它是個有效的image
字段選項
- 通過字段選項,可以實現對字段的約束
- 在字段對象時通過關鍵字參數指定
- null:如果爲True,Django 將空值以NULL 存儲到數據庫中,默認值是 False
- blank:如果爲True,則該字段允許爲空白,默認值是 False
- 對比:null是數據庫範疇的概念,blank是表單驗證證範疇的
- db_column:字段的名稱,如果未指定,則使用屬性的名稱
- db_index:若值爲 True, 則在表中會爲此字段創建索引
- default:默認值
- primary_key:若爲 True, 則該字段會成爲模型的主鍵字段
- unique:如果爲 True, 這個字段在表中必須有唯一值
關係
- 關係的類型包括
- ForeignKey:一對多,將字段定義在多的端中
- ManyToManyField:多對多,將字段定義在兩端中
- OneToOneField:一對一,將字段定義在任意一端中
- 可以維護遞歸的關聯關係,使用'self'指定,詳見“自關聯”
- 用一訪問多:對象.模型類小寫_set
bookinfo.heroinfo_set
- 用一訪問一:對象.模型類小寫
heroinfo.bookinfo
- 訪問id:對象.屬性_id
heroinfo.book_id
元選項
- 在模型類中定義類Meta,用於設置元信息
- 元信息db_table:定義數據表名稱,推薦使用小寫字母,數據表的默認名稱
<app_name>_<model_name>
- ordering:對象的默認排序字段,獲取對象的列表時使用,接收屬性構成的列表
class BookInfo(models.Model):
...
class Meta():
ordering = ['id']
- 字符串前加-表示倒序,不加-表示正序
class BookInfo(models.Model):
...
class Meta():
ordering = ['-id']
- 排序會增加數據庫的開銷
1.創建項目(mysql服務)
cd 到指定目錄
django-admin startproject test2
打開配置文件默認用sqlite3引擎,name表示工程中sqlite3的路徑
# Database
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
這裏可以看到默認的數據庫引擎是sqlite3,我們現在用Mysql,打開backends路徑下的查看如下
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6
這是我Python 3.6的安裝路徑
到該路徑下有很多python文件,這些都是默認的包,我們安裝的第三方包都在site-packages裏面
2.修改默認sqlite3爲mysql
可以看到,除了sqlite3,還有mysql和oracle等,我們現在用mysql,因此就把上面的配置文件修改成mysql引擎
# Database
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'test2',
'USER': 'root',
'PASSWORD': 'mikejing',
'HOST': 'localhost',
'PORT': '3306'
}
}
這裏的修改是要到mysql服務器上創建一個名字爲test2的database ,mysql沒有test2數據庫,因此後續需要生成遷移,如果已經有了,就不需要進行遷移,直接跑服務就好
3.修改後在創建的項目中創建App
mintou$ python3 manage.py startapp booktest
注意:當你修改成Mysql後之後,由於不在是2.7 python,我們用的是3.6 python針對Mysql的包名都不同,直接執行直接報錯
Did you install mysqlclient or MySQL-python?
我們在booktest--->test2---->__init__.py根目錄下的__init__中添加如下
import pymysql
pymysql.install_as_MySQLdb()
4.編寫好Model
from django.db import models
class BookInfo(models.Model):
btitle = models.CharField(max_length=20)
bpub_data = models.DateTimeField(db_column= 'book_publish_data')
bread = models.IntegerField(default=0)
bcommet = models.IntegerField(default=0)
isDelete = models.BooleanField(default=False)
# 用來修改數據庫信息 比如表名
class Meta():
db_table = 'bookinfo'
class HeroInfo(models.Model):
hname = models.CharField(max_length=20)
hgender = models.BooleanField(default=True)
isDelete = models.BooleanField(default=False)
hcontent = models.CharField(max_length=100)
# 注意2.1一定要加上on_delete
hbook = models.ForeignKey('BookInfo',on_delete=models.CASCADE)
把mode加入到setting文件中的INSTALLED_APPS選項中
5.生成遷移
python3 manage.py makemigrations
再次執行makemigrations即可
mintoudeMacBook-Pro-2:test2 mintou$ python3 manage.py makemigrations
Migrations for 'booktest':
booktest/migrations/0001_initial.py
- Create model BookInfo
- Create model HeroInfo
mintoudeMacBook-Pro-2:test2 mintou$ python3 manage.py migrate
最後執行migrate執行遷移,把我們做的Model數據類型全部遷移到數據庫上生成對應的表信息
6.查看Mysql數據
打開終端,鏈接數據庫
mysql -uroot -p
輸入密碼
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| test2 |
+--------------------+
5 rows in set (0.00 sec)
mysql> use test2
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> show tables;
+----------------------------+
| Tables_in_test2 |
+----------------------------+
| auth_group |
| auth_group_permissions |
| auth_permission |
| auth_user |
| auth_user_groups |
| auth_user_user_permissions |
| bookinfo |
| booktest_heroinfo |
| django_admin_log |
| django_content_type |
| django_migrations |
| django_session |
+----------------------------+
12 rows in set (0.00 sec)
mysql> desc bookinfo
-> ;
+-------------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| btitle | varchar(20) | NO | | NULL | |
| book_publish_data | datetime(6) | NO | | NULL | |
| bread | int(11) | NO | | NULL | |
| bcommet | int(11) | NO | | NULL | |
| isDelete | tinyint(1) | NO | | NULL | |
+-------------------+-------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)
mysql> desc booktest_heroinfo;
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| hname | varchar(20) | NO | | NULL | |
| hgender | tinyint(1) | NO | | NULL | |
| isDelete | tinyint(1) | NO | | NULL | |
| hcontent | varchar(100) | NO | | NULL | |
| hbook_id | int(11) | NO | MUL | NULL | |
+----------+--------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)
可以看到我們剛纔的model信息變成了mysql表,Django幫我們生成了很多其他的標,主要看
booktest_heroinfo 和 bookinfo 前者是默認表名,後者是通過Meta元類修改自定義的表名
7.類的屬性objects
- objects:是Manager類型的對象,用於與數據庫進行交互
- 當定義模型類時沒有指定管理器,則Django會爲模型類提供一個名爲objects的管理器
- 支持明確指定模型類的管理器
class BookInfo(models.Model):
...
books = models.Manager()
- 當爲模型類指定管理器後,django不再爲模型類生成名爲objects的默認管理器
管理器Manager
- 管理器是Django的模型進行數據庫的查詢操作的接口,Django應用的每個模型都擁有至少一個管理器
- 自定義管理器類主要用於兩種情況
- 情況一:向管理器類中添加額外的方法:見下面“創建對象”中的方式二
- 情況二:修改管理器返回的原始查詢集:重寫get_queryset()方法
class BookInfoManager(models.Manager):
def get_queryset(self):
return super(BookInfoManager, self).get_queryset().filter(isDelete=False)
class BookInfo(models.Model):
...
books = BookInfoManager()
該類繼承了models.Manager,重寫的get_queryset的方法,當我們重新進入
In [3]: BookInfo.objects.all()
Out[3]: <QuerySet [<BookInfo: BookInfo object (1)>, <BookInfo: BookInfo object (2)>, <BookInfo: BookInfo object (3)>, <BookInfo: BookInfo object (4)>]>
In [4]: exit
上半段,默認查出所有,沒有重寫之前
下半段是重寫之後filter了,查出來只有三個了,而且不再是默認的objects,而是我們自己定義的類名books
mintoudeMacBook-Pro-2:test2 mintou$ python3 manage.py shell
Python 3.6.3 (v3.6.3:2c5fed86e0, Oct 3 2017, 00:32:08)
Type 'copyright', 'credits' or 'license' for more information
IPython 6.3.1 -- An enhanced Interactive Python. Type '?' for help.
In [1]: from booktest.models import BookInfo, HeroInfo
In [2]: BookInfo.objects.all()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-2-182abd3174ec> in <module>()
----> 1 BookInfo.objects.all()
AttributeError: type object 'BookInfo' has no attribute 'objects'
In [3]: BookInfo.books.all()
Out[3]: <QuerySet [<BookInfo: BookInfo object (1)>, <BookInfo: BookInfo object (2)>, <BookInfo: BookInfo object (3)>]>
8.創建對象
- 當創建對象時,django不會對數據庫進行讀寫操作
- 調用save()方法才與數據庫交互,將對象保存到數據庫中
- 使用關鍵字參數構造模型對象很麻煩,推薦使用下面的兩種之式
- 說明: _init _方法已經在基類models.Model中使用,在自定義模型中無法使用,
- 方式一:在模型類中增加一個類方法
class BookInfo(models.Model):
...
@classmethod
def create(cls, title, pub_date):
book = cls(btitle=title, bpub_date=pub_date)
book.bread=0
book.bcommet=0
book.isDelete = False
return book
引入時間包:from datetime import *
調用:book=BookInfo.create("hello",datetime(1980,10,11));
保存:book.save()
- 方式二:在自定義管理器中添加一個方法
- 在管理器的方法中,可以通過self.model來得到它所屬的模型類
class BookInfoManager(models.Manager):
def create_book(self, title, pub_date):
book = self.model()
book.btitle = title
book.bpub_date = pub_date
book.bread=0
book.bcommet=0
book.isDelete = False
return book
class BookInfo(models.Model):
...
books = BookInfoManager()
調用:book=BookInfo.books.create_book("abc",datetime(1980,1,1))
保存:book.save()
- 在方式二中,可以調用self.create()創建並保存對象,不需要再手動save()
class BookInfoManager(models.Manager):
def create_book(self, title, pub_date):
book = self.create(btitle = title,bpub_date = pub_date,bread=0,bcommet=0,isDelete = False)
return book
class BookInfo(models.Model):
...
books = BookInfoManager()
調用:book=Book.books.create_book("abc",datetime(1980,1,1))
查看:book.pk
實例的屬性
- DoesNotExist:在進行單個查詢時,模型的對象不存在時會引發此異常,結合try/except使用
實例的方法
- str (self):重寫object方法,此方法在將對象轉換成字符串時會被調用
- save():將模型對象保存到數據表中
- delete():將模型對象從數據表中刪除
查詢集
- 在管理器上調用過濾器方法會返回查詢集
- 查詢集經過過濾器篩選後返回新的查詢集,因此可以寫成鏈式過濾
- 惰性執行:創建查詢集不會帶來任何數據庫的訪問,直到調用數據時,纔會訪問數據庫
- 何時對查詢集求值:迭代,序列化,與if合用
- 返回查詢集的方法,稱爲過濾器
- all()
- filter()
- exclude()
- order_by()
- values():一個對象構成一個字典,然後構成一個列表返回
- 寫法:
filter(鍵1=值1,鍵2=值2)
等價於
filter(鍵1=值1).filter(鍵2=值2)
- 返回單個值的方法
- get():返回單個滿足條件的對象
- 如果未找到會引發"模型類.DoesNotExist"異常
- 如果多條被返回,會引發"模型類.MultipleObjectsReturned"異常
- count():返回當前查詢的總條數
- first():返回第一個對象
- last():返回最後一個對象
- exists():判斷查詢集中是否有數據,如果有則返回True
- get():返回單個滿足條件的對象
限制查詢集
- 查詢集返回列表,可以使用下標的方式進行限制,等同於sql中的limit和offset子句
- 注意:不支持負數索引
- 使用下標後返回一個新的查詢集,不會立即執行查詢
- 如果獲取一個對象,直接使用[0],等同於[0:1].get(),但是如果沒有數據,[0]引發IndexError異常,[0:1].get()引發DoesNotExist異常
查詢集的緩存
- 每個查詢集都包含一個緩存來最小化對數據庫的訪問
- 在新建的查詢集中,緩存爲空,首次對查詢集求值時,會發生數據庫查詢,django會將查詢的結果存在查詢集的緩存中,並返回請求的結果,接下來對查詢集求值將重用緩存的結果
- 情況一:這構成了兩個查詢集,無法重用緩存,每次查詢都會與數據庫進行一次交互,增加了數據庫的負載
print([e.title for e in Entry.objects.all()])
print([e.title for e in Entry.objects.all()])
- 情況二:兩次循環使用同一個查詢集,第二次使用緩存中的數據
querylist=Entry.objects.all()
print([e.title for e in querylist])
print([e.title for e in querylist])
- 何時查詢集不會被緩存:當只對查詢集的部分進行求值時會檢查緩存,但是如果這部分不在緩存中,那麼接下來查詢返回的記錄將不會被緩存,這意味着使用索引來限制查詢集將不會填充緩存,如果這部分數據已經被緩存,則直接使用緩存中的數據
字段查詢
- 實現where子名,作爲方法filter()、exclude()、get()的參數
- 語法:屬性名稱__比較運算符=值
- 表示兩個下劃線,左側是屬性名稱,右側是比較類型
- 對於外鍵,使用“屬性名_id”表示外鍵的原始值
- 轉義:like語句中使用了%與,匹配數據中的%與,在過濾器中直接寫,例如:filter(title__contains="%")=>where title like '%\%%',表示查找標題中包含%的
比較運算符
- exact:表示判等,大小寫敏感;如果沒有寫“ 比較運算符”,表示判等
filter(isDelete=False)
- contains:是否包含,大小寫敏感
exclude(btitle__contains='傳')
- startswith、endswith:以value開頭或結尾,大小寫敏感
exclude(btitle__endswith='傳')
- isnull、isnotnull:是否爲null 這個字段很有用,可以查詢類下面所有子類,而不需要對象去查詢
In [13]: BookInfo.books.filter(heroinfo__isnull=False)
Out[13]: <QuerySet [<BookInfo: 射鵰英雄傳>, <BookInfo: 射鵰英雄傳>, <BookInfo: 射鵰英雄傳>, <BookInfo: 射鵰英雄傳>, <BookInfo: 射鵰英雄傳>, <BookInfo: 天龍八部>, <BookInfo: 天龍八部>, <BookInfo: 天龍八部>, <BookInfo: 天龍八部>, <BookInfo: 笑傲江湖>, <BookInfo: 笑傲江湖>, <BookInfo: 笑傲江湖>, <BookInfo: 笑傲江湖>]>
這裏我們第四種書雪山飛狐由於isDelete字段是True,所以沒出來,一般我們要查詢一個book下面的所有hero,需要拿到book對象,也可以通過這方法來查詢所有書的hero
filter(btitle__isnull=False)
- 在前面加個i表示不區分大小寫,如iexact、icontains、istarswith、iendswith
- in:是否包含在範圍內
filter(pk__in=[1, 2, 3, 4, 5])
- gt、gte、lt、lte:大於、大於等於、小於、小於等於
filter(id__gt=3)
- year、month、day、week_day、hour、minute、second:對日期間類型的屬性進行運算
filter(bpub_date__year=1980)
filter(bpub_date__gt=date(1980, 12, 31))
- 跨關聯關係的查詢:處理join查詢
- 語法:模型類名 <屬性名> <比較>
- 注:可以沒有__<比較>部分,表示等於,結果同inner join
- 可返向使用,即在關聯的兩個模型中都可以使用
filter(heroinfo_ _hcontent_ _contains='八')
- 查詢的快捷方式:pk,pk表示primary key,默認的主鍵是id
filter(pk__lt=6)
聚合函數
- 使用aggregate()函數返回聚合函數的值
- 函數:Avg,Count,Max,Min,Sum
from django.db.models import Max
maxDate = list.aggregate(Max('bpub_date'))
- count的一般用法:
count = list.count()
F對象
-
可以使用模型的字段A與字段B進行比較,如果A寫在了等號的左邊,則B出現在等號的右邊,需要通過F對象構造
list.filter(bread__gte=F('bcommet'))
- django支持對F()對象使用算數運算
list.filter(bread__gte=F('bcommet') * 2)
- F()對象中還可以寫作“模型類__列名”進行關聯查詢
list.filter(isDelete=F('heroinfo__isDelete'))
- 對於date/time字段,可與timedelta()進行運算
list.filter(bpub_date__lt=F('bpub_date') + timedelta(days=1))
Q對象
- 過濾器的方法中關鍵字參數查詢,會合併爲And進行
- 需要進行or查詢,使用Q()對象
- Q對象(django.db.models.Q)用於封裝一組關鍵字參數,這些關鍵字參數與“比較運算符”中的相同
from django.db.models import Q
list.filter(Q(pk_ _lt=6))
- Q對象可以使用&(and)、|(or)操作符組合起來
- 當操作符應用在兩個Q對象時,會產生一個新的Q對象
list.filter(pk_ _lt=6).filter(bcommet_ _gt=10)
list.filter(Q(pk_ _lt=6) | Q(bcommet_ _gt=10))
- 使用~(not)操作符在Q對象前表示取反
list.filter(~Q(pk__lt=6))
- 可以使用&|~結合括號進行分組,構造做生意複雜的Q對象
- 過濾器函數可以傳遞一個或多個Q對象作爲位置參數,如果有多個Q對象,這些參數的邏輯爲and
- 過濾器函數可以混合使用Q對象和關鍵字參數,所有參數都將and在一起,Q對象必須位於關鍵字參數的前面
視圖詳細介紹
- 視圖接受Web請求並且返回Web響應
- 視圖就是一個python函數,被定義在views.py中
- 響應可以是一張網頁的HTML內容,一個重定向,一個404錯誤等等
- 響應處理過程如下圖:
瀏覽器輸入host+port+path---->DJango獲取到地址除去host+port解析path---->匹配urls------>傳遞給views接收request返回response
urlconf
- 在settings.py文件中通過ROOT_URLCONF指定根級url的配置
- urlpatterns是一個url()實例的列表
- 一個url()對象包括:
- 正則表達式
- 視圖函數
- 名稱name
- 編寫URLconf的注意:
- 若要從url中捕獲一個值,需要在它周圍設置一對圓括號
- 不需要添加一個前導的反斜槓,如應該寫作'test/',而不應該寫作'/test/'
- 每個正則表達式前面的r表示字符串不轉義
- 請求的url被看做是一個普通的python字符串,進行匹配時不包括get或post請求的參數及域名
http://www.google.com/python/1/?i=1&p=new,只匹配“/python/1/”部分
- 正則表達式非命名組,通過位置參數傳遞給視圖
re_path(r'^([0-9]+)/$', views.detail, name='detail'),
- 正則表達式命名組,通過關鍵字參數傳遞給視圖,本例中關鍵字參數爲id
re_path(r'^(?P<id>[0-9]+)/$', views.detail, name='detail'),
- 參數匹配規則:優先使用命名參數,如果沒有命名參數則使用位置參數
- 每個捕獲的參數都作爲一個普通的python字符串傳遞給視圖
- 性能:urlpatterns中的每個正則表達式在第一次訪問它們時被編譯,這使得系統相當快
解析流程
新建的項目會有一個和項目同名的文件夾,下面有對應的urls文件,如下
根的urls
"""
from django.contrib import admin
from django.urls import path, re_path, include
urlpatterns = [
path('admin/', admin.site.urls),
re_path(r'^booktest/', include('booktest.urls'))
]
app urls
from django.urls import path, re_path
from . import views
urlpatterns = [
re_path(r'^$', views.index, name='index'),
re_path(r'^book/([0-9]+)$', views.detail, name='detail'),
]
- 匹配過程:先與主URLconf匹配,成功後再用剩餘的部分與應用中的URLconf匹配
請求http://www.google.com/booktest/1/
在sesstings.py中的配置:
re_path(r'^booktest/', include('booktest.urls', namespace='booktest')),
在booktest應用urls.py中的配置
re_path(r'^([0-9]+)/$', views.detail, name='detail'),
匹配部分是:/booktest/1/
匹配過程:在settings.py中與“booktest/”成功,再用“1/”與booktest應用的urls匹配
- 使用include可以去除urlconf的冗餘
- 參數:視圖會收到來自父URLconf、當前URLconf捕獲的所有參數
- 在include中通過namespace定義命名空間,用於反解析 後面response部分會有講到
- 注意使用namespace的操作,重定向需要指定
根部
from django.contrib import admin
from django.urls import path, re_path, include
urlpatterns = [
path('admin/', admin.site.urls),
re_path(r'^booktest/', include('booktest.urls', namespace='booktest'))
]
app
from django.urls import path, re_path
from . import views
app_name = 'booktest'
urlpatterns = [
]
這裏和上面的配置區別就是需要加上namespace參數,然後在appurls加入app_name參數
錯誤頁面
404 (page not found) 視圖
- defaults.page_not_found(request, template_name='404.html')
- 默認的404視圖將傳遞一個變量給模板:request_path,它是導致錯誤的URL
- 如果Django在檢測URLconf中的每個正則表達式後沒有找到匹配的內容也將調用404視圖
- 如果在settings中DEBUG設置爲True,那麼將永遠不會調用404視圖,而是顯示URLconf 並帶有一些調試信息
- 在templates中創建404.html
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
找不到了
<hr/>
{{request_path}}
</body>
</html>
- 在settings.py中修改調試
DEBUG = False
ALLOWED_HOSTS = ['*', ]
- 請求一個不存在的地址
http://127.0.0.1:8000/test/
500 (server error) 視圖
- defaults.server_error(request, template_name='500.html')
- 在視圖代碼中出現運行時錯誤
- 默認的500視圖不會傳遞變量給500.html模板
- 如果在settings中DEBUG設置爲True,那麼將永遠不會調用505視圖,而是顯示URLconf 並帶有一些調試信息
400 (bad request) 視圖
- defaults.bad_request(request, template_name='400.html')
- 錯誤來自客戶端的操作
- 當用戶進行的操作在安全方面可疑的時候,例如篡改會話cookie
Request對象
- 服務器接收到http協議的請求後,會根據報文創建HttpRequest對象
- 視圖函數的第一個參數是HttpRequest對象
- 在django.http模塊中定義了HttpRequest對象的API
屬性
- 下面除非特別說明,屬性都是隻讀的
- path:一個字符串,表示請求的頁面的完整路徑,不包含域名
- method:一個字符串,表示請求使用的HTTP方法,常用值包括:'GET'、'POST'
- encoding:一個字符串,表示提交的數據的編碼方式
- 如果爲None則表示使用瀏覽器的默認設置,一般爲utf-8
- 這個屬性是可寫的,可以通過修改它來修改訪問表單數據使用的編碼,接下來對屬性的任何訪問將使用新的encoding值
- GET:一個類似於字典的對象,包含get請求方式的所有參數
- POST:一個類似於字典的對象,包含post請求方式的所有參數
- FILES:一個類似於字典的對象,包含所有的上傳文件
- COOKIES:一個標準的Python字典,包含所有的cookie,鍵和值都爲字符串
- session:一個既可讀又可寫的類似於字典的對象,表示當前的會話,只有當Django 啓用會話的支持時纔可用,詳細內容見“狀態保持”
方法
- is_ajax():如果請求是通過XMLHttpRequest發起的,則返回True
QueryDict對象
- 定義在django.http.QueryDict
- request對象的屬性GET、POST都是QueryDict類型的對象
- 與python字典不同,QueryDict類型的對象用來處理同一個鍵帶有多個值的情況
- 方法get():根據鍵獲取值
- 只能獲取鍵的一個值
- 如果一個鍵同時擁有多個值,獲取最後一個值
dict.get('鍵',default)
或簡寫爲
dict['鍵']
- 方法getlist():根據鍵獲取值
- 將鍵的值以列表返回,可以獲取一個鍵的多個值
dict.getlist('鍵',default)
GET
- QueryDict類型的對象
- 包含get請求方式的所有參數
- 與url請求地址中的參數對應,位於?後面
- 參數的格式是鍵值對,如key1=value1
- 多個參數之間,使用&連接,如key1=value1&key2=value2
- 鍵是開發人員定下來的,值是可變的
- 示例如下
- 創建視圖getTest1用於定義鏈接,getTest2用於接收一鍵一值,getTest3用於接收一鍵多值
def getTest1(request):
return render(request,'booktest/getTest1.html')
def getTest2(request):
return render(request,'booktest/getTest2.html')
def getTest3(request):
return render(request,'booktest/getTest3.html')
- 配置url
url(r'^getTest1/$', views.getTest1),
url(r'^getTest2/$', views.getTest2),
url(r'^getTest3/$', views.getTest3),
- 創建getTest1.html,定義鏈接
<html>
<head>
<title>Title</title>
</head>
<body>
鏈接1:一個鍵傳遞一個值
<a href="/getTest2/?a=1&b=2">gettest2</a><br>
鏈接2:一個鍵傳遞多個值
<a href="/getTest3/?a=1&a=2&b=3">gettest3</a>
</body>
</html>
- 完善視圖getTest2的代碼
def getTest2(request):
a=request.GET['a']
b=request.GET['b']
context={'a':a,'b':b}
return render(request,'booktest/getTest2.html',context)
- 創建getTest2.html,顯示接收結果
<html>
<head>
<title>Title</title>
</head>
<body>
a:{{ a }}<br>
b:{{ b }}
</body>
</html>
- 完善視圖getTest3的代碼
def getTest3(request):
a=request.GET.getlist('a')
b=request.GET['b']
context={'a':a,'b':b}
return render(request,'booktest/getTest3.html',context)
- 創建getTest3.html,顯示接收結果
<html>
<head>
<title>Title</title>
</head>
<body>
a:{% for item in a %}
{{ item }}
{% endfor %}
<br>
b:{{ b }}
</body>
</html>
POST
- QueryDict類型的對象
- 包含post請求方式的所有參數
- 與form表單中的控件對應
- 問:表單中哪些控件會被提交?
- 答:控件要有name屬性,則name屬性的值爲鍵,value屬性的值爲鍵,構成鍵值對提交
- 對於checkbox控件,name屬性一樣爲一組,當控件被選中後會被提交,存在一鍵多值的情況
- 鍵是開發人員定下來的,值是可變的
- 示例如下
- 定義視圖postTest1
def postTest1(request):
return render(request,'booktest/postTest1.html')
- 配置url
url(r'^postTest1$',views.postTest1)
- 創建模板postTest1.html
<html>
<head>
<title>Title</title>
</head>
<body>
<form method="post" action="/postTest2/">
姓名:<input type="text" name="uname"/><br>
密碼:<input type="password" name="upwd"/><br>
性別:<input type="radio" name="ugender" value="1"/>男
<input type="radio" name="ugender" value="0"/>女<br>
愛好:<input type="checkbox" name="uhobby" value="胸口碎大石"/>胸口碎大石
<input type="checkbox" name="uhobby" value="跳樓"/>跳樓
<input type="checkbox" name="uhobby" value="喝酒"/>喝酒
<input type="checkbox" name="uhobby" value="爬山"/>爬山<br>
<input type="submit" value="提交"/>
</form>
</body>
</html>
- 創建視圖postTest2接收請求的數據
def postTest2(request):
uname=request.POST['uname']
upwd=request.POST['upwd']
ugender=request.POST['ugender']
uhobby=request.POST.getlist('uhobby')
context={'uname':uname,'upwd':upwd,'ugender':ugender,'uhobby':uhobby}
return render(request,'booktest/postTest2.html',context)
- 配置url
url(r'^postTest2$',views.postTest2)
- 創建模板postTest2.html
<html>
<head>
<title>Title</title>
</head>
<body>
{{ uname }}<br>
{{ upwd }}<br>
{{ ugender }}<br>
{{ uhobby }}
</body>
</html>
- 注意:使用表單提交,註釋掉settings.py中的中間件crsf
Reponse對象
- 在django.http模塊中定義了HttpResponse對象的API
- HttpRequest對象由Django自動創建,HttpResponse對象由程序員創建
- 不調用模板,直接返回數據
#coding=utf-8
from django.http import HttpResponse
def index(request):
return HttpResponse('你好')
- 調用模板
from django.http import HttpResponse
from django.template import RequestContext, loader
def index(request):
t1 = loader.get_template('polls/index.html')
context = RequestContext(request, {'h1': 'hello'})
return HttpResponse(t1.render(context))
屬性
- content:表示返回的內容,字符串類型
- charset:表示response採用的編碼字符集,字符串類型
- status_code:響應的HTTP響應狀態碼
- content-type:指定輸出的MIME類型
cookies知識點詳解
- init :使用頁內容實例化HttpResponse對象
- write(content):以文件的方式寫
- flush():以文件的方式輸出緩存區
- set_cookie(key, value='', max_age=None, expires=None):設置Cookie
- key、value都是字符串類型
- max_age是一個整數,表示在指定秒數後過期
- expires是一個datetime或timedelta對象,會話將在這個指定的日期/時間過期,注意datetime和timedelta值只有在使用PickleSerializer時纔可序列化
- max_age與expires二選一
- 如果不指定過期時間,則兩個星期後過期
def getCookies(request):
# 後面再來的時候都會攜帶
response = HttpResponse()
if 'name' in request.COOKIES:
response.write(request.COOKIES['name'])
# 第一次的時候選擇注入cookies
# response.set_cookie('name', 'mikjeing')
return response
- delete_cookie(key):刪除指定的key的Cookie,如果key不存在則什麼也不發生
分析:
http是無狀態的,我們需要記錄用戶的信息,多次訪問需要攜帶上一次的用戶信息,有服務器發送cookies從resonse中給客戶端保存在本地,但是服務器不存儲這些信息,在有效時間內,同一域名下瀏覽器會默認帶上cookies給服務器
簡單的例子,當我們訪問本地服務例如 127.0.0.7:8000/booktest/getCookies的時候是第一次,requestheader裏面是找不到本地存儲的cookies,因此不會有攜帶,但是服務器寫了set_cookies,就會有respon裏面帶有cookies返回給客戶端存儲,刷新頁面再次請求的時候,客戶端帶上cookies給服務端,就會在請求頭帶過去給服務器
上面的是最簡單的設置例子,看下正常註冊成功的時候把cookies的值回寫
http://blog.51cto.com/suhaozhi/2063468
def login(request):
c_user = request.COOKIES.get('username')
if not c_user:
return redirect('/login/')
#如果沒有從瀏覽器響應頭中得到username對應的value,那麼直接跳轉回登錄頁面。
2.cookie回寫。
if request.method == "GET":
return render(request,'login.html')
else:
user = request.POST.get('username')
pwd = request.POST.get('password')
if user == 'admin' and pwd =='admin':
obj = redirect('/admin/')
obj.set_cookie('username','xxxx') ###爲瀏覽器回寫cookie!!key爲username 對應的value爲 xxx。
return obj
else:
return render(request,'login.html')
[28/Aug/2018 09:00:13] "GET /user/login/ HTTP/1.1" 200 6
{'csrftoken': 'fy3O8YWLMnlQwi7GfOR55aFvyhKJvzOBviQD4Phe3eMitk4Dd6OP5OYpUKIMPDM7', 'username': 'tiantian'}
[28/Aug/2018 09:00:22] "GET /user/login/ HTTP/1.1" 200 6
[28/Aug/2018 09:00:25] "GET /user/register/ HTTP/1.1" 200 3262
[28/Aug/2018 09:00:36] "GET /user/register/ HTTP/1.1" 200 3262
[28/Aug/2018 09:00:49] "GET /user/register_exit/?uname=wuliao HTTP/1.1" 200 12
[28/Aug/2018 09:00:56] "GET /user/register_exit/?uname=wuliao HTTP/1.1" 200 12
[28/Aug/2018 09:00:56] "POST /user/register_handle/ HTTP/1.1" 302 0
{'csrftoken': 'fy3O8YWLMnlQwi7GfOR55aFvyhKJvzOBviQD4Phe3eMitk4Dd6OP5OYpUKIMPDM7', 'username': 'wuliao'}
例如這兩次註冊的username的更改
1、設置cookie聲明週期。
如果想在回寫cookie時,可以給cookie加一個超時時間,就可以使用max_age參數。
例如:
obj.set_cookie('username','xxxx',max_age=10) ###爲瀏覽器回寫cookie!!key爲username 對應的value爲xxx,並且cookie的聲明週期爲10秒,10秒後自動消失。
2、設置cookie作用域。
如果需要設置cookie的作用域,可以通過response的set_cookie中的path參數去進行設置。
path='/' #代表對整個站點生效。
path='/p1' #代表對www.xxxx.com/p1/*站點生效。
還可以通過domain參數來設置,這個cookie對哪個域名生效。
默認爲當前域名。
3、安全相關參數。
secure= False #默認值爲False ,也就是關閉狀態,當使用https時,需要開啓。
httponly = False #默認值爲False ,默認也是關閉狀態,如果開啓了httponly,那麼這個cookie只能在http請求傳輸的時候可以被讀取,
js是無法讀取這個cookie的。
4、cookie的簡單簽名。
通過response回寫cookie時。
obj.set_signed_cookie('kkk','vvv',salt='123456') #通過加鹽的方式爲cookie簽名。
request.get_signed_cookie('kkk',salt='123456') #獲取經過簽名後的cookie值。
子類HttpResponseRedirect(注意namespace的配置)
- 重定向,服務器端跳轉
- 構造函數的第一個參數用來指定重定向的地址
def redTest1(request):
return HttpResponseRedirect('/booktest/redTest2')
def redTest2(request):
return HttpResponse('我是轉向後的頁面資源')
app
from django.urls import path, re_path
from . import views
app_name = 'booktest'
urlpatterns = [
re_path(r'^redTest1$', views.redTest1, name='redTest1'),
re_path(r'^redTest2$', views.redTest2, name='redTest2'),
]
根
from django.contrib import admin
from django.urls import path, re_path, include
urlpatterns = [
path('admin/', admin.site.urls),
re_path(r'^booktest/', include('booktest.urls', namespace='booktest'))
]
當我們輸入以下的時候,會重定向
http://127.0.0.1:8000/booktest/redTest1
重定向
http://127.0.0.1:8000/booktest/redTest2
重定向推薦使用反向解析(需要namespace配置)
from django.urls import reverse
def redTest1(request):
# return HttpResponseRedirect('/booktest/redTest2')
# 重定向的兩個方法
# return HttpResponseRedirect(reverse('booktest:redTest2'))
return redirect(reverse('booktest:redTest2'))
def redTest2(request):
return HttpResponse('我是轉向後的頁面資源')
子類JsonResponse
- 返回json數據,一般用於異步請求
- _init _(data)
- 幫助用戶創建JSON編碼的響應
- 參數data是字典對象
- JsonResponse的默認Content-Type爲application/json
from django.http import JsonResponse
def index2(requeset):
return JsonResponse({'list': 'abc'})
簡寫函數render
render
- render(request, template_name[, context])
- 結合一個給定的模板和一個給定的上下文字典,並返回一個渲染後的HttpResponse對象
- request:該request用於生成response
- template_name:要使用的模板的完整名稱
- context:添加到模板上下文的一個字典,視圖將在渲染模板之前調用它
from django.shortcuts import render
def index(request):
return render(request, 'booktest/index.html', {'h1': 'hello'})
重定向(這裏可以寫全路徑)
- redirect(to)
- 爲傳遞進來的參數返回HttpResponseRedirect
- to推薦使用反向解析
from django.shortcuts import redirect
from django.urls import reverse
def index(request):
return redirect(reverse('booktest:index2'))
def redTest1(request):
return redirect(reverse('booktest:redTest2'))
[21/Aug/2018 03:39:37] "GET /booktest/redTest1 HTTP/1.1" 302 0
[21/Aug/2018 03:39:37] "GET /booktest/redTest2 HTTP/1.1" 200 30
注:
任何簡寫的地方都可以寫全路徑例如 /booktest/redTest2
但是如果用reverse反向解析,需要用到命名空間namespace 兩個都可以選擇,後者更容易維護,不需要要在更改URL的情況下更改很多地方,直接自動反向解析url
經常看到網站有淘寶的商品cookies如何解釋?
正常情況cookies是域名安全的,一開始我們用瀏覽器訪問淘寶,打開很多商品,會被服務端包裝到cookies傳給客戶端保存起來,那麼如果繼續訪問淘寶,cookie會回傳回去,就能知道對應的瀏覽記錄然後進行分析和推薦,那爲什麼比如你訪問博客網站的時候會有淘寶的廣告,那其實是一個iframe,然後iframe裏面嵌入了淘寶的域名下的鏈接,然後cookies就傳過去了,拿出cookies信息解析展示推薦商品即可
Session
- http協議是無狀態的:每次請求都是一次新的請求,不會記得之前通信的狀態
- 客戶端與服務器端的一次通信,就是一次會話
- 實現狀態保持的方式:在客戶端或服務器端存儲與會話有關的數據
- 存儲方式包括cookie、session,會話一般指session對象
- 使用cookie,所有數據存儲在客戶端,注意不要存儲敏感信息
- 推薦使用sesison方式,所有數據存儲在服務器端,在客戶端cookie中存儲session_id
- 狀態保持的目的是在一段時間內跟蹤請求者的狀態,可以實現跨頁面訪問當前請求者的數據
- 注意:不同的請求者之間不會共享這個數據,與請求者一一對應
啓用session
- 使用django-admin startproject創建的項目默認啓用
- 在settings.py文件中
項INSTALLED_APPS列表中添加:
'django.contrib.sessions',
項MIDDLEWARE_CLASSES列表中添加:
'django.contrib.sessions.middleware.SessionMiddleware',
- 禁用會話:刪除上面指定的兩個值,禁用會話將節省一些性能消耗
使用session
- 啓用會話後,每個HttpRequest對象將具有一個session屬性,它是一個類字典對象
- get(key, default=None):根據鍵獲取會話的值
- clear():清除所有會話
- flush():刪除當前的會話數據並刪除會話的Cookie
- del request.session['member_id']:刪除會話
urls.py
re_path(r'^mainTest$', views.mainTest, name='mainTest'),
re_path(r'^loginTest$', views.loginTest, name='login'),
re_path(r'^login_handle/$', views.login_handle, name='login_handle'),
re_path(r'^logoutTest/$', views.logoutTest, name='logoutTest'),
views.py
def mainTest(request):
username = request.session.get('name')
return render(request, 'booktest/mainTest.html', {'uname':username})
def loginTest(request):
return render(request, 'booktest/loginTest.html')
def login_handle(request):
request.session['name'] = request.POST['username']
request.session.set_expiry(0)
return redirect(reverse('booktest:mainTest'))
def logoutTest(request):
request.session.flush()
return redirect(reverse('booktest:mainTest'))
會話過期時間
set_expiry(value):設置會話的超時時間
如果沒有指定,則兩個星期後過期
如果value是一個整數,會話將在values秒沒有活動後過期
若果value是一個imedelta對象,會話將在當前時間加上這個指定的日期/時間過期
如果value爲0,那麼用戶會話的Cookie將在用戶的瀏覽器關閉時過期
如果value爲None,那麼會話永不過期
修改視圖中login_handle函數,查看效果
首先來擼一下邏輯
1.當我們一個頁面請求的時候,服務端會通過request對象獲取對應的session,如果沒有,就會創建一個session,有的話就讀,這就很好解釋爲什麼通過request來獲取,因爲有可能帶過來了,就不需要創建了,內部邏輯會判斷
2.登錄成功的時候一樣直接獲取session,這個就是OC裏面的懶加載,每次都直接讀,沒有創建新的,這裏的內部邏輯DJango做了,獲取到之後賦值,然後返回登錄頁面,這個時候已經能從session獲取到信息,就渲染登錄之後的信息
3.第一次創建session後,服務器通過cookies返回給客戶端,然後客戶端再次訪問的時候會攜帶cookie,服務端就能從cookies拿到對應的sessionid,進行資源查找
4.服務器退出登錄的時候例如執行flush,會清楚session,在返回的cookies裏面不返回sessionid,下次訪問就需要重新創建分配了
退出登錄的時候報文截圖
session在DJango默認存儲在配置的數據庫中,我們配置的Mysql,看下具體表中的存儲
mysql> show tables;
+----------------------------+
| Tables_in_test3 |
+----------------------------+
| auth_group |
| auth_group_permissions |
| auth_permission |
| auth_user |
| auth_user_groups |
| auth_user_user_permissions |
| bookinfo |
| booktest_heroinfo |
| django_admin_log |
| django_content_type |
| django_migrations |
| django_session |
+----------------------------+
12 rows in set (0.00 sec)
mysql> select *from django_session;
+----------------------------------+------------------------------------------------------------------------------------------------------+----------------------------+
| session_key | session_data | expire_date |
+----------------------------------+------------------------------------------------------------------------------------------------------+----------------------------+
| 1tj570h8vwlm7yynu0z0hwe2kz0r9pil | Y2ZmMzJlNzUzYjI4MjNkNmEwZTM2NTNhMzM4MzQyODRhMDJkYzZlYTp7Im5hbWUiOiIzMzMiLCJfc2Vzc2lvbl9leHBpcnkiOjB9 | 2018-09-04 07:07:14.852882 |
+----------------------------------+------------------------------------------------------------------------------------------------------+----------------------------+
1 row in set (0.00 sec)
配置Mysql情況下DJango是會把session數據存儲到數據庫裏面,我們可以自己配置redis來提高性能
使用Redis緩存session
存儲session
- 使用存儲會話的方式,可以使用settings.py的SESSION_ENGINE項指定
- 基於數據庫的會話:這是django默認的會話存儲方式,需要添加django.contrib.sessions到的INSTALLED_APPS設置中,運行manage.py migrate在數據庫中安裝會話表,可顯示指定爲
SESSION_ENGINE='django.contrib.sessions.backends.db'
- 基於緩存的會話:只存在本地內在中,如果丟失則不能找回,比數據庫的方式讀寫更快
SESSION_ENGINE='django.contrib.sessions.backends.cache'
- 可以將緩存和數據庫同時使用:優先從本地緩存中獲取,如果沒有則從數據庫中獲取
SESSION_ENGINE='django.contrib.sessions.backends.cached_db'
redis存儲
- 會話還支持文件、純cookie、Memcached、Redis等方式存儲,下面演示使用redis存儲
- 安裝包
pip3 install django-redis-sessions
- 修改settings中的配置,增加如下項
SESSION_ENGINE = 'redis_sessions.session'
SESSION_REDIS_HOST = 'localhost'
SESSION_REDIS_PORT = 6379
SESSION_REDIS_DB = 0
SESSION_REDIS_PASSWORD = ''
SESSION_REDIS_PREFIX = 'session'
- 管理redis的命令
Ubuntu
啓動:sudo redis-server /etc/redis/redis.conf
停止:sudo redis-server stop
重啓:sudo redis-server restart
Mac
啓動:
brew services start redis
redis-server /usr/local/etc/redis.conf
停止:brew services stop redis
重啓:brew services restart redis
redis-cli:使用客戶端連接服務器
keys *:查看所有的鍵
get name:獲取指定鍵的值
del name:刪除指定名稱的鍵
INFO keyspace 查看數據庫
select index 選擇數據庫
CONFIG GET databases 查看數據庫數量
配置好之後,我們還是執行上面的session登錄操作,登錄之後,報文中還是一樣,可以下mysql和redis
可以看到session被存到的內存高性能redis裏面去了,而Mysql不再存儲。
模板詳細介紹
模板配置
- 作爲Web框架,Django提供了模板,可以很便利的動態生成HTML
- 模版系統致力於表達外觀,而不是程序邏輯
- 模板的設計實現了業務邏輯(view)與顯示內容(template)的分離,一個視圖可以使用任意一個模板,一個模板可以供多個視圖使用
- 模板包含
- HTML的靜態部分
- 動態插入內容部分
- Django模板語言,簡寫DTL,定義在django.template包中
- 由startproject命令生成的settings.py定義關於模板的值:
- DIRS定義了一個目錄列表,模板引擎按列表順序搜索這些目錄以查找模板源文件
- APP_DIRS告訴模板引擎是否應該在每個已安裝的應用中查找模板
- 常用方式:在項目的根目錄下創建templates目錄,設置DIRS值
DIRS=[os.path.join(BASE_DIR,"templates")]
模板處理
- Django處理模板分爲兩個階段
- Step1 加載:根據給定的標識找到模板然後預處理,通常會將它編譯好放在內存中
loader.get_template(template_name),返回一個Template對象
- Step2 渲染:使用Context數據對模板插值並返回生成的字符串
Template對象的render(RequestContext)方法,使用context渲染模板
- 加載渲染完整代碼:
from django.template import loader, RequestContext
from django.http import HttpResponse
def index(request):
tem = loader.get_template('temtest/index.html')
context = RequestContext(request, {})
return HttpResponse(tem.render(context))
快捷函數
- 爲了減少加載模板、渲染模板的重複代碼,django提供了快捷函數
- render_to_string("")
- render(request,'模板',context)
from django.shortcuts import render
def index(request):
return render(request, 'temtest/index.html')
模板定義
- 模板語言包括
- 變量
- 標籤 { % 代碼塊 % }
- 過濾器
- 註釋{# 代碼或html #}
變量
- 語法:
{{ variable }}
- 當模版引擎遇到一個變量,將計算這個變量,然後將結果輸出
- 變量名必須由字母、數字、下劃線(不能以下劃線開頭)和點組成
- 當模版引擎遇到點("."),會按照下列順序查詢:
- 字典查詢,例如:foo["bar"]
- 屬性或方法查詢,例如:foo.bar 如果是方法就不能帶參
- 數字索引查詢,例如:foo[bar]
- 如果變量不存在, 模版系統將插入'' (空字符串)
- 在模板中調用方法時不能傳遞參數
在模板中調用對象的方法
- 在models.py中定義類HeroInfo
from django.db import models
class HeroInfo(models.Model):
...
def showName(self):
return self.hname
- 在views.py中傳遞HeroInfo對象
from django.shortcuts import render
from models import *
def index(request):
hero = HeroInfo(hname='abc')
context = {'hero': hero}
return render(request, 'temtest/detail.html', context)
- 在模板detail.html中調用
{{hero.showName}}
標籤
- 語法:{ % tag % }
- 作用
- 在輸出中創建文本
- 控制循環或邏輯
- 加載外部信息到模板中供以後的變量使用
- for標籤
{ %for ... in ...%}
循環邏輯
{{forloop.counter}}表示當前是第幾次循環
{ %empty%}
給出的列表爲或列表不存在時,執行此處
{ %endfor%}
- if標籤
{ %if ...%}
邏輯1
{ %elif ...%}
邏輯2
{ %else%}
邏輯3
{ %endif%}
- comment標籤
{ % comment % }
多行註釋
{ % endcomment % }
- include:加載模板並以標籤內的參數渲染
{ %include "foo/bar.html" % }
- url:反向解析
{ % url 'name' p1 p2 %}
- csrf_token:這個標籤用於跨站請求僞造保護
{ % csrf_token %}
- 布爾標籤:and、or,and比or的優先級高
- block、extends:詳見“模板繼承”
- autoescape:詳見“HTML轉義”
過濾器
https://blog.csdn.net/xyp84/article/details/7945094
一、形式:小寫
{{ name | lower }}
二、過濾器是可以嵌套的,字符串經過三個過濾器,第一個過濾器轉換爲小寫,第二個過濾器輸出首字母,第三個過濾器將首字母轉換成大寫
標籤
{{ str|lower|first|upper }}
顯示前30個字
{{ bio | truncatewords:"30" }}
格式化
{{ pub_date | date:"F j, Y" }}
過濾器列表
{{ 123|add:"5" }} 給value加上一個數值
{{ "AB'CD"|addslashes }} 單引號加上轉義號,一般用於輸出到javascript中
{{ "abcd"|capfirst }} 第一個字母大寫
{{ "abcd"|center:"50" }} 輸出指定長度的字符串,並把值對中
{{ "123spam456spam789"|cut:"spam" }} 查找刪除指定字符串
{{ value|date:"F j, Y" }} 格式化日期
{{ value|default:"(N/A)" }} 值不存在,使用指定值
{{ value|default_if_none:"(N/A)" }} 值是None,使用指定值
{{ 列表變量|dictsort:"數字" }} 排序從小到大
{{ 列表變量|dictsortreversed:"數字" }} 排序從大到小
{% if 92|pisibleby:"2" %} 判斷是否整除指定數字
{{ string|escape }} 轉換爲html實體
{{ 21984124|filesizeformat }} 以1024爲基數,計算最大值,保留1位小數,增加可讀性
{{ list|first }} 返回列表第一個元素
{{ "ik23hr&jqwh"|fix_ampersands }} &轉爲&
{{ 13.414121241|floatformat }} 保留1位小數,可爲負數,幾種形式
{{ 13.414121241|floatformat:"2" }} 保留2位小數
{{ 23456 |get_digit:"1" }} 從個位數開始截取指定位置的1個數字
{{ list|join:", " }} 用指定分隔符連接列表
{{ list|length }} 返回列表個數
{% if 列表|length_is:"3" %} 列表個數是否指定數值
{{ "ABCD"|linebreaks }} 用新行用
{% forloop.counter|divisibleby:"2" %} 表示是否被某個數整除
、
標記包裹
{{ "ABCD"|linebreaksbr }} 用新行用
標記包裹
{{ 變量|linenumbers }} 爲變量中每一行加上行號
{{ "abcd"|ljust:"50" }} 把字符串在指定寬度中對左,其它用空格填充
{{ "ABCD"|lower }} 小寫
{% for i in "1abc1"|make_list %}ABCDE,{% endfor %} 把字符串或數字的字符個數作爲一個列表
{{ "abcdefghijklmnopqrstuvwxyz"|phone2numeric }} 把字符轉爲可以對應的數字??
{{ 列表或數字|pluralize }} 單詞的複數形式,如列表字符串個數大於1,返回s,否則返回空串
{{ 列表或數字|pluralize:"es" }} 指定es
{{ 列表或數字|pluralize:"y,ies" }} 指定ies替換爲y
{{ object|pprint }} 顯示一個對象的值
{{ 列表|random }} 返回列表的隨機一項
{{ string|removetags:"br p p" }} 刪除字符串中指定html標記
{{ string|rjust:"50" }} 把字符串在指定寬度中對右,其它用空格填充
{{ 列表|slice:":2" }} 切片
{{ string|slugify }} 字符串中留下減號和下劃線,其它符號刪除,空格用減號替換
{{ 3|stringformat:"02i" }} 字符串格式,使用Python的字符串格式語法
{{ "EABCD"|striptags }} 剝去[X]HTML語法標記
{{ 時間變量|time:"P" }} 日期的時間部分格式
{{ datetime|timesince }} 給定日期到現在過去了多少時間
{{ datetime|timesince:"other_datetime" }} 兩日期間過去了多少時間
{{ datetime|timeuntil }} 給定日期到現在過去了多少時間,與上面的區別在於2日期的前後位置。
{{ datetime|timeuntil:"other_datetime" }} 兩日期間過去了多少時間
{{ "abdsadf"|title }} 首字母大寫
{{ "A B C D E F"|truncatewords:"3" }} 截取指定個數的單詞
{{ "111221"|truncatewords_html:"2" }} 截取指定個數的html標記,並補完整
{{ list|unordered_list }}
多重嵌套列表展現爲html的無序列表
{{ string|upper }} 全部大寫
linkageurl編碼
{{ string|urlize }} 將URLs由純文本變爲可點擊的鏈接。
{{ string|urlizetrunc:"30" }} 同上,多個截取字符數。
{{ "B C D E F"|wordcount }} 單詞數
{{ "a b c d e f g h i j k"|wordwrap:"5" }} 每指定數量的字符就插入回車符
{{ boolean|yesno:"Yes,No,Perhaps" }} 對三種值的返回字符串,對應是 非空,空,None。
三、過濾器的參數
- 語法:{ { 變量|過濾器 }},例如{ { name|lower }},表示將變量name的值變爲小寫輸出
- 使用管道符號 (|)來應用過濾器
- 通過使用過濾器來改變變量的計算結果
- 可以在if標籤中使用過濾器結合運算符
if list1|length > 1
- 過濾器能夠被“串聯”,構成過濾器鏈
name|lower|upper
- 過濾器可以傳遞參數,參數使用引號包起來
list|join:", "
- default:如果一個變量沒有被提供,或者值爲false或空,則使用默認值,否則使用變量的值
value|default:"什麼也沒有"
- date:根據給定格式對一個date變量格式化
value|date:'Y-m-d'
- escape:詳見“HTML轉義”
- 點擊查看詳細的過濾器
註釋
- 單行註釋
{#...#}
- 註釋可以包含任何模版代碼,有效的或者無效的都可以
{# { % if foo % }bar{ % else % } #}
- 使用comment標籤註釋模版中的多行內容
反向解析(動態生成跳轉url)
DJango模板語言中
{ % url 'namespace:name' p1 p2 %}
重定向中
def login_handle(request):
request.session['name'] = request.POST['username']
request.session.set_expiry(0)
return redirect(reverse('booktest:mainTest'))
爲什麼要反向解析?
首先看個簡單的例子
根url匹配
from django.contrib import admin
from django.urls import path, re_path, include
urlpatterns = [
path('admin/', admin.site.urls),
re_path(r'^booktest/', include('booktest.urls', namespace='booktest'))
]
booktestapp裏面url匹配
from django.urls import path, re_path
from . import views
app_name = 'booktest'
urlpatterns = [
re_path(r'^$', views.index, name='index'),
re_path(r'^(\d+)/$', views.show, name='show'),
]
views.py
index對應的文件
<body>
<h1>Index</h1>
<a href="/booktest/123">展示</a>
</body>
</html>
這裏面兩個頁面index頁面展示一個跳轉a標籤,a標籤寫的路徑是根路徑,會替換url裏面的路徑,這麼看來簡單的跳轉就沒問題。但是如果咱們按下面改一下
urlpatterns = [
path('admin/', admin.site.urls),
re_path(r'^', include('booktest.urls', namespace='booktest'))
]
當我們進入index頁面的時候再點擊a標籤,路徑還是帶有booktest前綴,因此直接404了,外面改了,我們裏面還是需要跟着改,這就非常的浪費時間了,而且維護起來很麻煩,因此就有了反向解析。
我們在根的urls下面都會有定義namespace,在app目錄下的urls裏面會給匹配的name,除此之外還需要在app的urls下面指定app_name
根部urls
urlpatterns = [
path('admin/', admin.site.urls),
re_path(r'^booktest/', include('booktest.urls', namespace='booktest'))
]
app裏面urls
app_name = 'booktest'
urlpatterns = [
re_path(r'^$', views.index, name='index'),
re_path(r'^(\d+)/$', views.show, name='show'),
]
views
def index(request):
return render(request, 'booktest/index.html')
def show(request, p1):
return render(request,'booktest/show.html', {'p1':p1})
index頁面信息
<h1>Index</h1>
# 這裏的第一個參數是namespace 第二個參數是show正則裏面()匹配的參數
<a href="{% url 'booktest:show' '100000' %}">展示</a>
</body>
</html>
這裏可以看到通過上面a標籤的寫法來指定反向解析,前提是booktest和show要先指定namespace和name,然後就可以通過語法進行反向url解析,不需要我們寫死跳轉路徑了,views裏面的重定向也是一樣可以反向解析出路徑,這樣外部更改url,我們內部也無需再改
模板繼承
- 模板繼承可以減少頁面內容的重複定義,實現頁面內容的重用
- 典型應用:網站的頭部、尾部是一樣的,這些內容可以定義在父模板中,子模板不需要重複定義
- block標籤:在父模板中預留區域,在子模板中填充
- extends繼承:繼承,寫在模板文件的第一行
- 定義父模板base.html
{ %block block_name%}
這裏可以定義默認值
如果不定義默認值,則表示空字符串
{ %endblock%}
- 定義子模板index.html
{ % extends "base.html" %}
- 在子模板中使用block填充預留區域
{ %block block_name%}
實際填充內容
{ %endblock%}
說明
- 如果在模版中使用extends標籤,它必須是模版中的第一個標籤
- 不能在一個模版中定義多個相同名字的block標籤
- 子模版不必定義全部父模版中的blocks,如果子模版沒有定義block,則使用了父模版中的默認值
- 如果發現在模板中大量的複製內容,那就應該把內容移動到父模板中
- 使用可以獲取父模板中block的內容
- 爲了更好的可讀性,可以給endblock標籤一個名字
{ % block block_name %}
區域內容
{ % endblock block_name %}
三層繼承結構
- 三層繼承結構使代碼得到最大程度的複用,並且使得添加內容更加簡單
- 如下圖爲常見的電商頁面
1.創建根級模板
- 名稱爲“base.html”
- 存放整個站點共用的內容
<!DOCTYPE html>
<html>
<head>
<title>{%block title%}{%endblock%} 水果超市</title>
</head>
<body>
top--{{logo}}
<hr/>
{%block left%}{%endblock%}
{%block content%}{%endblock%}
<hr/>
bottom
</body>
</html>
2.創建分支模版
- 繼承自base.html
- 名爲“base_*.html”
- 定義特定分支共用的內容
- 定義base_goods.html
{%extends 'temtest/base.html'%}
{%block title%}商品{%endblock%}
{%block left%}
<h1>goods left</h1>
{%endblock%}
- 定義base_user.html
{%extends 'temtest/base.html'%}
{%block title%}用戶中心{%endblock%}
{%block left%}
<font color='blue'>user left</font>
{%endblock%}
- 定義index.html,繼承自base.html,不需要寫left塊
{%extends 'temtest/base.html'%}
{%block content%}
首頁內容
{%endblock content%}
3.爲具體頁面創建模板,繼承自分支模板
- 定義商品列表頁goodslist.html
{%extends 'temtest/base_goods.html'%}
{%block content%}
商品正文列表
{%endblock content%}
- 定義用戶密碼頁userpwd.html
{%extends 'temtest/base_user.html'%}
{%block content%}
用戶密碼修改
{%endblock content%}
4.視圖調用具體頁面,並傳遞模板中需要的數據
- 首頁視圖index
logo='welcome to itcast'
def index(request):
return render(request, 'temtest/index.html', {'logo': logo})
- 商品列表視圖goodslist
def goodslist(request):
return render(request, 'temtest/goodslist.html', {'logo': logo})
- 用戶密碼視圖userpwd
def userpwd(request):
return render(request, 'temtest/userpwd.html', {'logo': logo})
5.配置url
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'^list/$', views.goodslist, name='list'),
url(r'^pwd/$', views.userpwd, name='pwd'),
]
HTML轉義
- Django對字符串進行自動HTML轉義,如在模板中輸出如下值:
視圖代碼:
def index(request):
return render(request, 'temtest/index2.html',
{
't1': '<h1>hello</h1>'
})
模板代碼:
{{t1}}
- 顯示效果如下圖:
會被自動轉義的字符
- html轉義,就是將包含的html標籤輸出,而不被解釋執行,原因是當顯示用戶提交字符串時,可能包含一些攻擊性的代碼,如js腳本
- Django會將如下字符自動轉義:
< 會轉換爲<
> 會轉換爲>
' (單引號) 會轉換爲'
" (雙引號)會轉換爲 "
& 會轉換爲 &
- 當顯示不被信任的變量時使用escape過濾器,一般省略,因爲Django自動轉義
{{t1|escape}}
關閉轉義
- 對於變量使用safe過濾器
{{ data|safe }}
- 對於代碼塊使用autoescape標籤
{% autoescape off %}
{{ body }}
{% endautoescape %}
- 標籤autoescape接受on或者off參數
- 自動轉義標籤在base模板中關閉,在child模板中也是關閉的
字符串字面值
- 手動轉義
{{ data|default:"<b>123</b>" }}
- 應寫爲
{{ data|default:"<b>123</b>" }}
CSRF(跨站請求僞造)
- 全稱Cross Site Request Forgery,跨站請求僞造
- 某些惡意網站上包含鏈接、表單按鈕或者JavaScript,它們會利用登錄過的用戶在瀏覽器中的認證信息試圖在你的網站上完成某些操作,這就是跨站攻擊
- 演示csrf如下
- 創建視圖csrf1用於展示表單,csrf2用於接收post請求
def csrf1(request):
return render(request,'booktest/csrf1.html')
def csrf2(request):
uname=request.POST['uname']
return render(request,'booktest/csrf2.html',{'uname':uname})
- 配置url
url(r'^csrf1/$', views.csrf1),
url(r'^csrf2/$', views.csrf2),
- 創建模板csrf1.html用於展示表單
<html>
<head>
<title>Title</title>
</head>
<body>
<form method="post" action="/crsf2/">
<input name="uname"><br>
<input type="submit" value="提交"/>
</form>
</body>
</html>
- 創建模板csrf2用於展示接收的結果
<html>
<head>
<title>Title</title>
</head>
<body>
{{ uname }}
</body>
</html>
- 在瀏覽器中訪問,查看效果,報403
- 將settings.py中的中間件代碼'django.middleware.csrf.CsrfViewMiddleware'註釋
- 查看csrf1的源代碼,複製,在自己的網站內建一個html文件,粘貼源碼,訪問查看效果
防csrf的使用
- 在django的模板中,提供了防止跨站攻擊的方法,使用步驟如下:
- step1:在settings.py中啓用'django.middleware.csrf.CsrfViewMiddleware'中間件,此項在創建項目時,默認被啓用
- step2:在csrf1.html中添加標籤
<form>
{% csrf_token %}
...
</form>
- step3:測試剛纔的兩個請求,發現跨站的請求被拒絕了,效果如下圖
取消保護
- 如果某些視圖不需要保護,可以使用裝飾器csrf_exempt,模板中也不需要寫標籤,修改csrf2的視圖如下
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def csrf2(request):
uname=request.POST['uname']
return render(request,'booktest/csrf2.html',{'uname':uname})
- 運行上面的兩個請求,發現都可以請求
保護原理
- 加入標籤後,可以查看源代碼,發現多瞭如下代碼
<input type='hidden' name='csrfmiddlewaretoken' value='nGjAB3Md9ZSb4NmG1sXDolPmh3bR2g59' />
- 在瀏覽器的調試工具中,通過network標籤可以查看cookie信息
- 本站中自動添加了cookie信息,如下圖
- 查看跨站的信息,並沒有cookie信息,即使加入上面的隱藏域代碼,發現又可以訪問了
- 結論:django的csrf不是完全的安全
- 當提交請求時,中間件'django.middleware.csrf.CsrfViewMiddleware'會對提交的cookie及隱藏域的內容進行驗證,如果失敗則返回403錯誤
自己的域名下自己訪問加了{% csrf_token%}表單隱藏添加了cookies中的csrftoken信息,由於開啓了middleware,服務器會做一個token讓cookie帶回來,如果表單沒有開啓token驗證,表單不會攜帶csrftoken,那就會返回403,如果加了token驗證,那麼表單裏面會把token帶過去,服務器拿到表單和cookies進行token比較就可以了。但是有個問題,其他域名下訪問的時候,也是僞造了表單,然後把cookies裏面的token值抓下來帶過去了,這個時候就能跨域訪問了,感覺DJango沒有判斷cookies裏面有沒有值,直接拿服務器的和表單的比較了,這樣cookies被扒下來就能僞造了,因此驗證碼模式就出來了,更完善的模式
驗證碼
- 在用戶註冊、登錄頁面,爲了防止暴力請求,可以加入驗證碼功能,如果驗證碼錯誤,則不需要繼續處理,可以減輕一些服務器的壓力
- 使用驗證碼也是一種有效的防止crsf的方法
- 驗證碼效果如下圖:
- 定義函數verify
- 此段代碼用到了PIL中的Image、ImageDraw、ImageFont模塊,pip3 install pillow
- Image表示畫布對象
- ImageDraw表示畫筆對象
-
ImageFont表示字體對象,Mac的字體路徑爲”/System/Library/Fonts/“
from PIL import Image, ImageDraw, ImageFont
from io import BytesIO
def verify(request):
# 引入繪圖模塊
from PIL import Image, ImageDraw, ImageFont
# 引入隨機函數模塊
import random
# 定義變量,用於畫面的背景色、寬、高
bgcolor = (random.randrange(20, 100), random.randrange(
20, 100), 255)
width = 100
height = 25
# 創建畫面對象
im = Image.new('RGB', (width, height), bgcolor)
# 創建畫筆對象
draw = ImageDraw.Draw(im)
# 調用畫筆的point()函數繪製噪點
for i in range(0, 100):
xy = (random.randrange(0, width), random.randrange(0, height))
fill = (random.randrange(0, 255), 255, random.randrange(0, 255))
draw.point(xy, fill=fill)
# 定義驗證碼的備選值
str1 = 'ABCD123EFGHIJK456LMNOPQRS789TUVWXYZ0'
# 隨機選取4個值作爲驗證碼
rand_str = ''
for i in range(0, 4):
rand_str += str1[random.randrange(0, len(str1))]
# 構造字體對象 這裏的路徑是Mac的
font = ImageFont.truetype('/System/Library/Fonts/AquaKana.ttc', 23)
# 構造字體顏色
fontcolor = (255, random.randrange(0, 255), random.randrange(0, 255))
# 繪製4個字
draw.text((5, 2), rand_str[0], font=font, fill=fontcolor)
draw.text((25, 2), rand_str[1], font=font, fill=fontcolor)
draw.text((50, 2), rand_str[2], font=font, fill=fontcolor)
draw.text((75, 2), rand_str[3], font=font, fill=fontcolor)
# 釋放畫筆
del draw
# 存入session,用於做進一步驗證
request.session['verifycode'] = rand_str
# 內存文件操作 python2和3有很大區別,這裏用的3
buf = BytesIO()
# 將圖片保存在內存中,文件類型爲png 二進制 所以用BytesIO
im.save(buf, 'png')
# 將內存中的圖片數據返回給客戶端,MIME類型爲圖片png
return HttpResponse(buf.getvalue(), 'image/png')
1.注意Python3和2的區別,這裏我們導入的BytesIO,而不在是StringsIO了
2.Mac的路徑注意填寫
配置url
- 在urls.py中定義請求驗證碼視圖的url
from django.urls import path, re_path
from . import views
app_name = 'booktest'
urlpatterns = [
re_path(r'^$', views.index, name='index'),
re_path(r'^verify$', views.verify, name='verify'),
re_path(r'^verifycode$', views.verifycode, name='verifycode'),
]
顯示驗證碼
- 在模板中使用img標籤,src指向驗證碼視圖
<h1>Index</h1>
<a href="">{{random}}</a>
<a href="{% url 'booktest:show' '100000' %}">展示</a>
<br>
<br>
<br>
<tr>
<form action="{% url 'booktest:verifycode' %}" method="post">
<input type="text" name="name">
<img src="{% url 'booktest:verify' %}" alt="驗證碼" id="verify">
<span id='verifycodeChange' onclick="changecode()">看不清,換一個</span>
<input type="submit" value="提交">
</form>
</body>
<script type="text/javascript">
function changecode() {
var i = Math.floor(Math.random() * (1000 - 1) + 1)
document.getElementById("verify").src = "{% url 'booktest:verify' %}".concat('?a=', i)
}
</script>
</html>
驗證
- 接收請求的信息,與session中的內容對比
def verifycode(request):
poststr = request.POST['name']
vcodestr = request.session.get('verifycode', '')
if poststr.upper() == vcodestr:
return HttpResponse('OK')
else:
return HttpResponse('NO')
高級特性
靜態文件
- 項目中的CSS、圖片、js都是靜態文件
配置靜態文件
- 在settings 文件中定義靜態內容
這個值是可變的,例如給別人起一個外號,如果path裏面路徑由這個開頭,就會從下面路徑查找
127.0.0.1:8000/static/booktest/a1.jpg
匹配到static了,因此後面的/booktest/a1.jpg就會去STATICFILES_DIRS路徑下查找
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]
如果改爲
STATIC_URL = '/mkj/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]
src=127.0.0.1:8000/static/booktest/a1.jpg這個時候,匹配不上mkj路徑,因此顯示不了
我們要寫成src=127.0.0.1:8000/mkj/booktest/a1.jpg就好了,避免通過查看源代碼知道物理結構,就弄了一個別名
- 在項目根目錄下創建static目錄,再創建當前應用名稱的目錄
test3(project名稱)/static/booktest/
- 在模板中可以使用硬編碼
結構
/static/my_app/myexample.jpg
src下面寫相對路徑,第一個路徑標識STATIC_URL,和setting中匹配上,說明讀的靜態文件,然後再去
STATICFILES_DIRS中查找後面路徑的值/booktest/a1.jpg 找到就顯示
<img src="/static/booktest/a1.jpg" alt="美女" width="100" height="100">
- 在模板中可以使用static編碼 和反向解析一樣,可以和STATIC_URL一樣同步
導入static標籤
{ % load static from staticfiles %}
<img src="{ % static "my_app/myexample.jpg" %}" alt="My image"/>
固定static寫法,然後導入配置路徑下的具體路徑即可
<img src="{ % static "booktest/a1.jpg" %}" alt="My image"/>
中間件
- 是一個輕量級、底層的插件系統,可以介入Django的請求和響應處理過程,修改Django的輸入或輸出
- 激活:添加到Django配置文件中的MIDDLEWARE_CLASSES元組中
- 每個中間件組件是一個獨立的Python類,可以定義下面方法中的一個或多個
- _init _:無需任何參數,服務器響應第一個請求的時候調用一次,用於確定是否啓用當前中間件
- process_request(request):執行視圖之前被調用,在每個請求上調用,返回None或HttpResponse對象
- process_view(request, view_func, view_args, view_kwargs):調用視圖之前被調用,在每個請求上調用,返回None或HttpResponse對象
- process_template_response(request, response):在視圖剛好執行完畢之後被調用,在每個請求上調用,返回實現了render方法的響應對象
- process_response(request, response):所有響應返回瀏覽器之前被調用,在每個請求上調用,返回HttpResponse對象
- process_exception(request,response,exception):當視圖拋出異常時調用,在每個請求上調用,返回一個HttpResponse對象
- 使用中間件,可以干擾整個處理過程,每次請求中都會執行中間件的這個方法
- 示例:自定義異常處理
- 與settings.py同級目錄下創建myexception.py文件,定義類MyException,實現process_exception方法
from django.http import HttpResponse
class MyException():
def process_exception(request,response, exception):
return HttpResponse(exception.message)
- 將類MyException註冊到settings.py中間件中
MIDDLEWARE_CLASSES = (
'test1.myexception.MyException',
...
)
- 定義視圖,併發生一個異常信息,則會運行自定義的異常處理
上傳圖片
- 當Django在處理文件上傳的時候,文件數據被保存在request.FILES
- FILES中的每個鍵爲<input type="file" name="" />中的name
- 注意:FILES只有在請求的方法爲POST 且提交的<form>帶有enctype="multipart/form-data" 的情況下才會包含數據。否則,FILES 將爲一個空的類似於字典的對象
- 使用模型處理上傳文件:將屬性定義成models.ImageField類型
pic=models.ImageField(upload_to='cars/')
- 注意:如果屬性類型爲ImageField需要安裝包Pilow
pip3 install Pillow
- 圖片存儲路徑
- 在項目根目錄下創建media文件夾
- 圖片上傳後,會被保存到“/static/media/cars/圖片文件”
- 打開settings.py文件,增加media_root項
MEDIA_ROOT=os.path.join(BASE_DIR,"static/media")
- 使用django後臺管理,遇到ImageField類型的屬性會出現一個file框,完成文件上傳
- 手動上傳的模板代碼
<body>
<h1>呵呵</h1>
<form action="{% url 'booktest:uploadfile2' %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<input type="file" name="icon">
<input type="submit" value="提交">
</form>
</body>
</html>
視圖代碼
def uploadFile1(request):
return render(request, 'booktest/upload.html')
def uploadFile2(request):
# 注意是FILES
pic = request.FILES['icon']
# 全路徑
storepath = os.path.join(settings.MEDIA_ROOT, pic.name)
# with是不需要手動釋放內存 ft指針
with open(storepath, 'wb') as ft:
# 內存中圖片讀取
for ct in pic.chunks():
# 寫入
ft.write(ct)
# 這裏的staitc是標識setting文件下的路徑,會自動拼接那個路徑,你然後後面跟media/xxx.jpg即可
return HttpResponse('<img src="/static/media/%s" alt="">'%pic.name)
Admin站點
- 通過使用startproject創建的項目模版中,默認Admin被啓用
- 1.創建管理員的用戶名和密碼
python manage.py createsuperuser
然後按提示填寫用戶名、郵箱、密碼
- 2.在應用內admin.py文件完成註冊,就可以在後臺管理中維護模型的數據
from django.contrib import admin
from models import *
admin.site.register(HeroInfo)
- 查找admin文件:在INSTALLED_APPS項中加入django.contrib.admin,Django就會自動搜索每個應用的admin模塊並將其導入
ModelAdmin對象
- ModelAdmin類是模型在Admin界面中的表示形式
- 定義:定義一個類,繼承於admin.ModelAdmin,註冊模型時使用這個類
class HeroAdmin(admin.ModelAdmin):
...
- 通常定義在應用的admin.py文件裏
- 使用方式一:註冊參數
admin.site.register(HeroInfo,HeroAdmin)
- 使用方式二:註冊裝飾器
@admin.register(HeroInfo)
class HeroAdmin(admin.ModelAdmin):
- 通過重寫admin.ModelAdmin的屬性規定顯示效果,屬性主要分爲列表頁、增加修改頁兩部分
列表頁選項
“操作選項”的位置
- actions_on_top、actions_on_bottom:默認顯示在頁面的頂部
class HeroAdmin(admin.ModelAdmin):
actions_on_top = True
actions_on_bottom = True
list_display
- 出現列表中顯示的字段
- 列表類型
- 在列表中,可以是字段名稱,也可以是方法名稱,但是方法名稱默認不能排序
- 在方法中可以使用format_html()輸出html內容
在models.py文件中
from django.db import models
from tinymce.models import HTMLField
from django.utils.html import format_html
class HeroInfo(models.Model):
hname = models.CharField(max_length=10)
hcontent = HTMLField()
isDelete = models.BooleanField()
def hContent(self):
return format_html(self.hcontent)
在admin.py文件中
class HeroAdmin(admin.ModelAdmin):
list_display = ['hname', 'hContent']
- 讓方法排序,爲方法指定admin_order_field屬性
在models.py中HeroInfo類的代碼改爲如下:
def hContent(self):
return format_html(self.hcontent)
hContent.admin_order_field = 'hname'
- 標題欄名稱:將字段封裝成方法,爲方法設置short_description屬性
在models.py中爲HeroInfo類增加方法hName:
def hName(self):
return self.hname
hName.short_description = '姓名'
hContent.short_description = '內容'
在admin.py頁中註冊
class HeroAdmin(admin.ModelAdmin):
list_display = ['hName', 'hContent']
list_filter
- 右側欄過濾器,對哪些屬性的值進行過濾
- 列表類型
- 只能接收字段
class HeroAdmin(admin.ModelAdmin):
...
list_filter = ['hname', 'hcontent']
list_per_page
- 每頁中顯示多少項,默認設置爲100
class HeroAdmin(admin.ModelAdmin):
...
list_per_page = 10
search_fields
- 搜索框
- 列表類型,表示在這些字段上進行搜索
- 只能接收字段
class HeroAdmin(admin.ModelAdmin):
...
search_fields = ['hname']
增加與修改頁選項
- fields:顯示字段的順序,如果使用元組表示顯示到一行上
class HeroAdmin(admin.ModelAdmin):
...
fields = [('hname', 'hcontent')]
- fieldsets:分組顯示
class HeroAdmin(admin.ModelAdmin):
...
fieldsets = (
('base', {'fields': ('hname')}),
('other', {'fields': ('hcontent')})
)
- fields與fieldsets兩者選一
InlineModelAdmin對象
- 類型InlineModelAdmin:表示在模型的添加或修改頁面嵌入關聯模型的添加或修改
- 子類TabularInline:以表格的形式嵌入
- 子類StackedInline:以塊的形式嵌入
class HeroInline(admin.TabularInline):
model = HeroInfo
class BookAdmin(admin.ModelAdmin):
inlines = [
HeroInline,
]
路徑相關疑問
1、路徑前面的/問題
首先我們看下/開頭的路徑,以一個 127.0.0.1:8000/booktest/path1路徑爲例,當我們進入前面這個路徑的views後,點擊a標籤,這個時候a標籤如果不開啓命名空間,正常情況下如果用href='path2',那麼這個路徑會變成
127.0.0.1:8000/booktest/path1/path2
如果href='/path2',路徑會變成
127.0.0.1:8000/path2
如果是img標籤,路徑是指定靜態資源的,首先看下配置
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]
如果這個時候img標籤裏面的src是 /static/booktest/a1.jpg的意思是,第一個路徑的參數是匹配上面的STATIC_URL的,如果匹配上了,就去STATICFILES_DIRS下查找,查找內容就是booktest/a1.jpg,這就是靜態資源查找的規則
2、路徑後面的/問題
Django中的處理方法是提供了一個設置項,APPEND_SLASH
,這個設置的默認值是True。當APPEND_SLASH
爲True的時候,如果一個URL沒有匹配成功,那麼Django會在這個URL尾部加上一個'/'並重新redirect(301)回來。如果設置爲False,那沒有匹配成功的時候就什麼都不會做。
需要注意的是,由於採用了redirect的方式,所以POST數據可能會丟失。
例如兩個請求
127.0.0.1:8000/booktest/hello
127.0.0.1:8000/booktest/hello/
在APPEND_SLASH默認爲True的情況下,如果
如果設置了'hello',那麼只有'hello'會匹配,'hello/'不會匹配。
如果設置了'hello/',那麼'hello'和'hello/'都會匹配。hello沒匹配到,自動加上尾斜槓,然後301繼續匹配,這也是DJango的推薦方式
在APPEND_SLASH默認爲False的情況下,寫了上面就匹配什麼,匹配不到也不會繼續301重定向,
可以看到如果按着DJango的寫法,一般匹配都會寫上尾斜槓來匹配用戶輸入寫或者沒寫的url
https://www.bbsmax.com/A/kjdwABD6JN/
分頁
- Django提供了一些類實現管理數據分頁,這些類位於django/core/paginator.py中
Paginator對象
- Paginator(列表,int):返回分頁對象,參數爲列表數據,每面數據的條數
屬性
- count:對象總數
- num_pages:頁面總數
- page_range:頁碼列表,從1開始,例如[1, 2, 3, 4]
方法
- page(num):下標以1開始,如果提供的頁碼不存在,拋出InvalidPage異常
異常exception
- InvalidPage:當向page()傳入一個無效的頁碼時拋出
- PageNotAnInteger:當向page()傳入一個不是整數的值時拋出
- EmptyPage:當向page()提供一個有效值,但是那個頁面上沒有任何對象時拋出
Page對象
創建對象
- Paginator對象的page()方法返回Page對象,不需要手動構造
屬性
- object_list:當前頁上所有對象的列表
- number:當前頁的序號,從1開始
- paginator:當前page對象相關的Paginator對象
方法
- has_next():如果有下一頁返回True
- has_previous():如果有上一頁返回True
- has_other_pages():如果有上一頁或下一頁返回True
- next_page_number():返回下一頁的頁碼,如果下一頁不存在,拋出InvalidPage異常
- previous_page_number():返回上一頁的頁碼,如果上一頁不存在,拋出InvalidPage異常
- len():返回當前頁面對象的個數
- 迭代頁面對象:訪問當前頁面中的每個對象
視圖
def herolist(request,index):
if index == '':
index = 1
list1 = HeroInfo.objects.all()
paginator = Paginator(list1, 5)
page = paginator.page(index)
return render(request, 'booktest/hero.html', {'page': page,'lists':list1})
url
urlpatterns = [
re_path(r'^$', views.index, name='index'),
re_path(r'^hero/(\d*)$', views.herolist, name='hero'),
]
模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul>
{% for hero in page.object_list %}
<li>{{hero.hname}}</li>
{% endfor %}
<br>
{% for index in page.paginator.page_range %}
{% if page.number == index%}
{{index}}
{% else %}
<a href="/booktest/hero/{{index}}">{{index}}</a>
{% endif %}
{% endfor %}
<!--總個數 {{page.count}}-->
<br>
<!--分了幾頁 {{page.num_pages}}-->
<br>
<!--頁數數組 {{page.page_range}}-->
</ul>
</body>
</html>
Ajax請求和JsonReponse數據
- 使用視圖通過上下文向模板中傳遞數據,需要先加載完成模板的靜態頁面,再執行模型代碼,生成最張的html,返回給瀏覽器,這個過程將頁面與數據集成到了一起,擴展性差
- 改進方案:通過ajax的方式獲取數據,通過dom操作將數據呈現到界面上
- 推薦使用框架的ajax相關方法,不要使用XMLHttpRequest對象,因爲操作麻煩且不容易查錯
- jquery框架中提供了$.ajax、$.get、$.post方法,用於進行異步交互
- 由於csrf的約束,推薦使用$.get
- 示例:實現省市區的選擇
引入js文件
- js文件屬於靜態文件,創建目錄結構如圖:
修改settings.py關於靜態文件的設置
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.1/howto/static-files/
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]
在models.py中定義模型
class AreaInfo(models.Model):
title = models.CharField(max_length=20)
parentid = models.ForeignKey('self', null=True, blank=True,on_delete=models.CASCADE)
生成遷移
python manage.py makemigrations
python manage.py migrate
網上找一個sql腳本,然後用Navicat連接,然後把腳本跑入對應的數據庫即可
views.py中編寫視圖
# 選擇首頁
def area(request):
return render(request,'booktest/area.html')
# 省請求json信息
def areainfo(request, pid):
data = AreaInfo.objects.filter(parentid__isnull=True).values('id', 'title')
return JsonResponse({'result': list(data)})
# 市和區請求json信息
def cityInfo(request, cid):
data = AreaInfo.objects.filter(parentid=cid).values('id', 'title')
return JsonResponse({'result': list(data)})
如果配置了mysql,就是mysql,配置了redis就從redis,獲取到的fiter數據是QuerySet的,不能直接給JsonResponse轉換,下面是StackoverFlow的答案
https://stackoverflow.com/questions/30243101/return-queryset-as-json
Simplest solution without any additional framework:
results = PCT.objects.filter(code__startswith='a').values('id', 'name')
return JsonResponse({'results': list(results)})
returns
{'results': [{'id': 1, 'name': 'foo'}, ...]}
or if you only need the values:
results = PCT.objects.filter(code__startswith='a').values_list('id', 'name')
return JsonResponse({'results': list(results)})
returns
{'results': [[1, 'foo'], ...]}
在urls.py中配置urlconf
re_path(r'^area/$', views.area, name='area'),
re_path(r'^area/(\d+)/$', views.areainfo, name='areainfo'),
re_path(r'^city/(\d+)/$', views.cityInfo, name='cityinfo'),
主urls.py中包含此應用的url
urlpatterns = [
path('admin/', admin.site.urls),
re_path(r'^booktest/', include('booktest.urls', namespace='booktest'))
]
定義模板index.html
- 在項目中的目錄結構如圖:
- 修改settings.py的TEMPLATES項,設置DIRS值
'DIRS': [os.path.join(BASE_DIR, 'templates')],
定義模板文件:包含三個select標籤,分別存放省市區的信息
<body>
<!--選擇器-->
<select name="" id="pro">
<option value="">請選擇省</option>
</select>
<select name="" id="city">
<option value="">請選擇市</option>
</select>
<select name="" id="dis">
<option value="">請選擇區</option>
</select>
</body>
js代碼
1.一進來請求省信息,get,直接執行
2.點擊之後觸發請求下一級
<script src="/static/booktest/jquery-3.3.1.min.js"></script>
<script>
$(function () {
//獲取省
pro = $('#pro')
city = $('#city')
dis = $('#dis')
// get請求
$.get('/booktest/area/0/', function (data) {
$.each(data.result, function (index, item) {
// console.log(index)
pro.append('<option value="' + item.id + '">' + item.title + '</option>')
})
})
// 獲取市
$('#pro').change(function () {
// 選擇當前城市的id
console.log('/booktest/city/' + ($(this).val() || "0") + '/')
$.get('/booktest/city/' + ($(this).val() || "0") + '/', function (data) {
city.empty().append('<option value="">請選擇市</option>')
dis.empty().append('<option value="">請選擇市</option>')
$.each(data.result, function (index, item) {
city.append('<option value="' + item.id + '">' + item.title + '</option>')
})
})
})
$('#city').change(function () {
$.get('/booktest/city/' + ($(this).val() || "0") + '/', function (data) {
dis.empty().append('<option value="">請選擇區</option>')
$.each(data.result, function (index, item) {
dis.append('<option value="' + item.id + '">' + item.title + '</option>')
})
})
})
})
</script>
創建正常項目簡單流程梳理(mysql服務)
1.找個目錄新建個文件夾放項目(例如桌面)
2.打開終端,cd到目錄下,執行
django-admin startproject taobao
cd taobao/
.
├── manage.py
└── taobao
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
1 directory, 5 files
3.配置靜態文件
先創建靜態文件夾,static和templates,都在根級,和manage.py同級,在static中放靜態文件
添加templates路徑和static路徑在根級的setting.py裏面
TEMPLATES = [
{
'DIRS': [os.path.join(BASE_DIR,'templates')], // 添加路徑
......
},
]
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.1/howto/static-files/
STATIC_URL = '/static/'
STATICFILES_DIRS=[
os.path.join(BASE_DIR, 'static')
]
修改默認的sqllite爲mysql
# Database
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'taobao',
'USER': 'root',
'PASSWORD': 'mikejing',
'HOST': 'localhost',
'PORT': '3306'
}
}
我們指定名字NAME爲taobao的databases沒有的話就需要自己先創建好,不然後面生成的models找不到數據庫無法生成表
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| test2 |
| test3 |
+--------------------+
6 rows in set (0.00 sec)
mysql> create database taobao charset=utf8;
Query OK, 1 row affected, 1 warning (0.05 sec)
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| taobao |
| test2 |
| test3 |
+--------------------+
7 rows in set (0.00 sec)
4.在taobao項目中創建app模塊(用戶模塊爲例)以及遷移
python3 manage.py startapp userinfo
在models.py裏面創建模型類
from django.db import models
class UserInfo(models.Model):
username = models.CharField(max_length=20)
userpwd = models.CharField(max_length=40)
useremail = models.CharField(max_length=30)
usershow = models.CharField(max_length=20, default='')
useraddress = models.CharField(max_length=100, default='')
useryoubian = models.CharField(max_length=6, default='')
userphone = models.CharField(max_length=11, default='')
在根級settings裏面添加apps
INSTALLED_APPS = [
......
'userinfo',
]
注意:當你修改成Mysql爲數據庫之後,由於不在是2.7 python,我們用的是3.6 python針對Mysql的包名都不同,直接執行直接報錯,因此需要在根級taobao__init__文件中加入
import pymysql
pymysql.install_as_MySQLdb()
然後生成遷移腳本(sql語句而已 userinfo/migrations/0001_initial.py文件中),執行遷移 (執行)
mintoudeMacBook-Pro-2:taobao mintou$ python3 manage.py makemigrations
Migrations for 'userinfo':
userinfo/migrations/0001_initial.py
- Create model UserInfo
mintoudeMacBook-Pro-2:taobao mintou$ python3 manage.py migrate
成功之後打開mysql查看即可
mysql> desc userinfo_userinfo;
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| username | varchar(20) | NO | | NULL | |
| userpwd | varchar(40) | NO | | NULL | |
| useremail | varchar(30) | NO | | NULL | |
| usershow | varchar(20) | NO | | NULL | |
| useraddress | varchar(100) | NO | | NULL | |
| useryoubian | varchar(6) | NO | | NULL | |
| userphone | varchar(11) | NO | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
8 rows in set (0.00 sec)
mysql>
5.根據模塊導入templates模板文件
首先根據模塊在templates裏面新建app對應的目錄,然後html文件導入
6.編寫views.py
from django.shortcuts import render
# Create your views here.
def register(request):
return render(request, 'userinfo/register.html')
7.配置urls即可訪問
根級
from django.contrib import admin
from django.urls import path, re_path, include
urlpatterns = [
path('admin/', admin.site.urls),
re_path(r'^user/', include('userinfo.urls', namespace='userinfo'))
]
模塊級
from django.urls import path, re_path
from . import views
app_name = 'userinfo'
urlpatterns = [
re_path(r'^register/', views.register, name='register')
]
Python服務器和Java服務器通信類比
Python的WSGI和Java的Servlet API
Python的WSGI
最近在學習使用Python進行WebServer的編程,發現WSGI(Web Server Gateway Interface)的概念。PythonWeb服務器網關接口(Python Web Server Gateway Interface,縮寫爲WSGI)是Python應用程序或框架和Web服務器之間的一種接口,已經被廣泛接受,它已基本達成它的可移植性方面的目標。WSGI 沒有官方的實現,因爲WSGI更像一個協議。只要遵照這些協議,WSGI應用(Application)都可以在任何服務器(Server)上運行,反之亦然。
如果沒有WSGI,你選擇的Python網絡框架將會限制所能夠使用的 Web 服務器。
這就意味着,你基本上只能使用能夠正常運行的服務器與框架組合,而不能選擇你希望使用的服務器或框架。
那麼,你怎樣確保可以在不修改 Web 服務器代碼或網絡框架代碼的前提下,使用自己選擇的服務器,並且匹配多個不同的網絡框架呢?爲了解決這個問題,就出現了PythonWeb 服務器網關接口(Web Server Gateway Interface,WSGI)。
WSGI的出現,讓開發者可以將網絡框架與 Web 服務器的選擇分隔開來,不再相互限制。現在,你可以真正地將不同的 Web 服務器與網絡開發框架進行混合搭配,選擇滿足自己需求的組合。例如,你可以使用Gunicorn或Nginx/uWSGI或Waitress服務器來運行Django、Flask或Pyramid應用。正是由於服務器和框架均支持WSGI,才真正得以實現二者之間的自由混合搭配。
Java的Servlet API
下面將類比Java來說明一下:
如果沒有Java Servlet API,你選擇的Java Web容器(Java Socket編程框架實現)將會限制所能夠使用的Java Web框架(因爲沒有Java Servlet API,那麼SpringMVC可能會實現一套SpringMVCHttpRequest和SpringMVCHttpResponse標準,Struts2可能會實現一套Struts2HttpRequest和Struts2HttpResponse標準,如果Tomcat只支持SpringMVC的API,那麼選擇Tomcat服務器就只能使用SpringMVC的Web框架來寫服務端代碼)。
這就意味着,你基本上只能使用能夠正常運行的服務器(Tomcat)與框架(SpringMVC)組合,而不能選擇你希望使用的服務器或框架(比如:我要換成Tomcat + Struts2的組合)。
注意:這裏假設沒有Java Servlet API,這樣就相當於SpringMVC和Struts2可能都要自己實現一套Servlet封裝HttpRequest和HttpResponse,這樣從SpringMVC更換成Struts2就幾乎需要重寫服務器端的代碼。爲了解決這個問題,Java提出了Java Servlet API協議,讓所有的Web服務框架都實現此Java Servlet API協議來和Java Web服務器(例如:Tomcat)交互,而複雜的網絡連接控制等等都交由Java Web服務器來控制,Java Web服務器用Java Socket編程實現了複雜的網絡連接管理。
詳細說說Python的WSGI
Python Web 開發中,服務端程序可以分爲兩個部分,一是服務器程序,二是應用程序。前者負責把客戶端請求接收,整理,後者負責具體的邏輯處理。爲了方便應用程序的開發,我們把常用的功能封裝起來,成爲各種Web開發框架,例如 Django, Flask, Tornado。不同的框架有不同的開發方式,但是無論如何,開發出的應用程序都要和服務器程序配合,才能爲用戶提供服務。這樣,服務器程序就需要爲不同的框架提供不同的支持。這樣混亂的局面無論對於服務器還是框架,都是不好的。對服務器來說,需要支持各種不同框架,對框架來說,只有支持它的服務器才能被開發出的應用使用。
這時候,標準化就變得尤爲重要。我們可以設立一個標準,只要服務器程序支持這個標準,框架也支持這個標準,那麼他們就可以配合使用。一旦標準確定,雙方各自實現。這樣,服務器可以支持更多支持標準的框架,框架也可以使用更多支持標準的服務器。
- 服務器端:
服務器必須將可迭代對象的內容傳遞給客戶端,可迭代對象會產生bytestrings,必須完全完成每個bytestring後才能請求下一個。
- 應用程序:
服務器程序會在每次客戶端的請求傳來時,調用我們寫好的應用程序,並將處理好的結果返回給客戶端。
總結:
- Web Server Gateway Interface是Python編寫Web業務統一接口。
- 無論多麼複雜的Web應用程序,入口都是一個WSGI處理函數。
- Web應用程序就是寫一個WSGI的處理函數,主要功能在於交互式地瀏覽和修改數據,生成動態Web內容,針對每個HTTP請求進行響應。
實現Python的Web應用程序能被訪問的方式
要使 Python 寫的程序能在 Web 上被訪問,還需要搭建一個支持 Python 的 HTTP 服務器(也就是實現了WSGI server(WSGI協議)的Http服務器)。有如下幾種方式:
- 可以自己使用Python Socket編程實現一個Http服務器
- 使用支持Python的開源的Http服務器(如:uWSGI,wsgiref,Mod_WSGI等等)。如果是使用Nginx,Apache,Lighttpd等Http服務器需要單獨安裝支持WSGI server的模塊插件。
- 使用Python開源Web框架(如:Flask,Django等等)內置的Http服務器(Django自帶的WSGI Server,一般測試使用)
Python標準庫對WSGI的實現
wsgiref 是Python標準庫給出的 WSGI 的參考實現。simple_server 這一模塊實現了一個簡單的 HTTP 服務器。
Python源碼中的wsgiref的simple_server.py正好說明上面的分工情況,server的主要作用是接受client的請求,並把的收到的請求交給RequestHandlerClass處理,RequestHandlerClass處理完成後回傳結果給client
uWSGI服務器
uWSGI是一個Web服務器,它實現了WSGI協議、uwsgi、http等協議。注意uwsgi是一種通信協議,而uWSGI是實現uwsgi協議和WSGI協議的Web服務器。
Django框架內置的WSGI Server服務器
Django的WSGIServer繼承自wsgiref.simple_server.WSGIServer,而WSGIRequestHandler繼承自wsgiref.simple_server.WSGIRequestHandler
之前說到的application,在Django中一般是django.core.handlers.wsgi.WSGIHandler對象,WSGIHandler繼承自django.core.handlers.base.BaseHandler,這個是Django處理request的核心邏輯,它會創建一個WSGIRequest實例,而WSGIRequest是從http.HttpRequest繼承而來
Python和Java的類比
Python和Java的服務器結構
- 獨立WSGI server(實現了Http服務器功能) + Python Web應用程序
- 例如:Gunicorn,uWSGI + Django,Flask
- 獨立Servlet引擎(Java應用服務器)(實現了Http服務器功能) + Java Web應用程序
- 例如:Jetty,Tomcat + SpringMVC,Struts2
Python和Java服務器共同點
WSGI server(例如Gunicorn和uWSGI)
- WSGI server服務器內部都有組建來實現Socket連接的創建和管理。
- WSGI server服務器都實現了Http服務器功能,能接受Http請求,並且通過Python Web應用程序處理之後返回動態Web內容。
Java應用服務器(Jetty和Tomcat)
- Java應用服務器內部都有Connector組件來實現Socket連接的創建和管理。
- Java應用服務器都實現了Http服務器功能,能接受Http請求,並且通過Java Web應用程序處理之後返回動態Web內容。
以上這段參考鏈接
DJango部署到阿里雲
另外寫了一篇部署到阿里雲