之前有在用Django寫一些小網站,現在暑假想說再來複習一下之前買的這本書
於是我就把它寫成一系列的文章,也方便查語法
而且因為這本書大概是2014年出的,如今Django也已經出到2.多版
有些內容也變得不再支援或適用,而且語法或許也改變了
所以我會以最新版的Python和Django來修正這本書的內容跟程式碼
執行指令
python manage.py startapp restaurants
mysite/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'restaurants', # <-加入app
]
在restaurants底下新增目錄templates
並將menu.html移到這裡
我們不需要把新的模板目錄添加到settings.py裡
因為django預設應用目錄下的templates目錄為可用的模板路徑
接著把原本的views.py移到restaurants
restaurants/views.py
from django.shortcuts import render_to_response
def menu(request):
food1 = {'name': '番茄炒蛋', 'price': 60, 'comment': '好吃', 'is_spicy': False}
food2 = {'name': '蒜泥白肉', 'price': 100, 'comment': '人氣推薦', 'is_spicy': True}
foods = [food1, food2]
return render_to_response('menu.html', locals())
mysite/urls.py
from django.contrib import admin
from django.urls import path
from restaurants.views import menu # <-修改這裡
urlpatterns = [
path('admin/', admin.site.urls),
path('menu/', menu),
]
django預設使用SQLite資料庫
Python的模型是一種ORM(object-relational mapping,物件關聯映射)的機制
我們只需要操作python的類別與物件就能建立資料表和存取資料
而不需要親自去寫SQL資料庫語言
建立模型檔
restaurants/models.py
from django.db import models
class Restaurant(models.Model):
name = models.CharField(max_length=20)
phone_number = models.CharField(max_length=15)
address = models.CharField(max_length=50, blank=True)
class Food(models.Model):
name = models.CharField(max_length=20)
price=models.DecimalField(max_digits=3,decimal_places=0)
comment = models.CharField(max_length=50, blank=True)
is_spicy = models.BooleanField(default=False)
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
所有的模型繼承自models.Model這個類別
操作模型時,會將一個模型類別對應到資料庫中的一張資料表
實體化自此類別的物件就是資料表中的一筆一筆資料
上面的name、phone_number、address變數會成為資料表中的欄位
而這三個變數都是models中的欄位物件(field object)
CharField是文字資料型態的欄位物件
max_length參數可以設定文字最大長度
blank參數可以允許欄位為空
DecimalField為浮點數的資料
max_digits參數設定最大位數
decimal_places參數設定小數點位數
BooleanField為布林真假值資料
而這一行
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
包含了重要的資料表關聯性的概念
一家餐廳有多種食物
或是一本書有許多編者或作者
這屬於一種Many-to-one的關係
這時候就要用ForeignKey(外鍵)來描述
在建置資料表前,先檢查看看之前設計的模型有沒有問題
python manage.py check
如果沒錯的話會顯示
System check identified no issues (0 silenced).
在建立資料表前,必須先建立migration資料檔,以確保資料表的架構和版本有被記錄下來
python manage.py makemigrations restaurants
這會對指定的應用做migration的檢查,如果模型有異動,會產生新的migration檔
放置在app目錄底下的migration資料夾
如果makemigrations後面不指定app名稱
則django會對所有安裝好的app做migration的檢查
當模型有更動時,再次makemigrations,會生成新的migration檔
從檔名以及裡頭的dependencies和operations串列
可以知道之前做了哪些變動
而dependencies描述了migration檔案的相依性,也就是這次的migration是基於哪一個migration在更動的
使用下列指令可以知道模型是如何被翻譯成SQL語言的
python manage.py sqlmigrate restaurants(應用名稱) 0001(版本編號)
接下來將模型同步到資料庫,真正產生資料表
python manage.py migrate restaurants 0001
migrate會根據指定的migration記錄(利用編號指定),將模型同步到資料庫,只要使用了migrate指令,就可以根據模型異動,將現行的資料庫調整到與記錄檔一樣
若不指定編號,則自動更新到最新版本
另外,與makemigrations相同,如果不指定應用名稱,預設將會對所有的app進行同步
使用migrate的好處是我們可以任意地更動資料庫結構並作成各種版本的記錄,且隨時可以指定任一版本進行同步
進入django shell
python manage.py shell
>>> from restaurants.models import Restaurant, Food
>>> r1 = Restaurant(name='派森家常小館', phone_number='02-12345678', address='天龍國天龍區
天龍路1號')
>>> r1
<Restaurant: Restaurant object (None)>
生成一筆資料了,不過還尚未被寫入資料庫中
>>> r1.save()
若想要一次完成模型資料的建立與寫入資料庫
>>> r2 = Restaurant.objects.create(name='古意得餐廳', phone_number='02-7654321', address='天龍國天龍區天龍路100號')
>>> r2
<Restaurant: Restaurant object (2)>
r1在r2在shell中顯示出來的資訊都一樣
我們可以更改顯示出來的資訊,讓使用者好分辨
修改models.py
from django.db import models
class Restaurant(models.Model):
name = models.CharField(max_length=20)
phone_number = models.CharField(max_length=15)
address = models.CharField(max_length=50, blank=True)
def __str__(self):
return self.name
class Food(models.Model):
name = models.CharField(max_length=20)
price=models.DecimalField(max_digits=3,decimal_places=0)
comment = models.CharField(max_length=50, blank=True)
is_spicy = models.BooleanField(default=False)
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
def __str__(self):
return self.name
接著先離開shell(按Ctrl+Z)
這是為了讓我們剛剛修改過的模型代碼可以被載入直譯器
再進入shell一次
>>> from restaurants.models import Restaurant, Food
>>> restaurants = Restaurant.objects.all()
>>> restaurants
<QuerySet [<Restaurant: 派森家常小館>, <Restaurant: 古意得餐廳>]>
我們會發現因為之前__str__
函式的緣故
顯示出來的資訊改變了
只需要利用python的方式思考就行
>>> r1 = restaurants[0]
>>> r1.phone_number
'02-12345678'
>>> r1.id
1
取出一筆資料:
>>> r = Restaurant.objects.get(name='古意得餐廳')
>>> r
<Restaurant: 古意得餐廳>
objects.get方法會回傳一個模型物件,也就是一筆資料,如果回傳的是多筆或是查詢結果失敗(空),都會引發例外,這可以利用try/expect來捕獲並處理
>>> restaurants = Restaurant.objects.all()
>>> restaurants
<QuerySet [<Restaurant: 派森家常小館>, <Restaurant: 古意得餐廳>]>
objects稱之為管理器,每個模型都有一個管理器,他包含了關於查詢該模型資料的種種方法
而回傳出來的這個資料清單(嚴格來說不是我們所熟知的那個清單),被稱為QuerySet:查詢集,他是一個類似於清單的物件,把它想像成是儲存著藉由管理器查詢回來的資料集合
對於查詢集我們可以使用類似清單的方式來取值或做slicing(切片):
>>> restaurants[0]
<Restaurant: 派森家常小館>
>>> restaurants[0:2]
<QuerySet [<Restaurant: 派森家常小館>, <Restaurant: 古意得餐廳>]>
>>> restaurants[-1]
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "D:\vs code\python\demo\lib\site-packages\django\db\models\query.py", line 288, in
__getitem__
"Negative indexing is not supported."
AssertionError: Negative indexing is not supported.
可惜並不支援負向索引
利用objects管理器的filter方法,可以對資料進行過濾
>>> restaurants = Restaurant.objects.filter(name='古意得餐廳')
>>> restaurants
<QuerySet [<Restaurant: 古意得餐廳>]>
多重過濾
>>> restaurants = Restaurant.objects.filter(name='古意得餐廳', phone_number='02-7654321')
>>> restaurants
<QuerySet [<Restaurant: 古意得餐廳>]>
包含過濾
>>> restaurants = Restaurant.objects.filter(name__contains='餐廳')
>>> restaurants
<QuerySet [<Restaurant: 古意得餐廳>]>
利用屬性名+__contains=搜尋值
可以搜尋該屬性包含值的資料
先產生幾筆資料
>>> r = Restaurant.objects.get(name='古意得餐廳')
>>> f1 = Food(name='宮保雞丁', price=120, comment='超級辣', is_spicy=True, restaurant = r)
>>> f1.save()
>>> f2 = Food(name='炒青菜', price=85, comment='每日不同', is_spicy=False, restaurant = r)
>>> f2.save()
>>> Food.objects.all()
<QuerySet [<Food: 宮保雞丁>, <Food: 炒青菜>]>
查詢出來的QuerySet預設是按照id進行排序的
利用order_by方法可以自行指定依照哪個資料進行排序
>>> Food.objects.all()
<QuerySet [<Food: 宮保雞丁>, <Food: 炒青菜>]>
>>> Food.objects.order_by('price')
<QuerySet [<Food: 炒青菜>, <Food: 宮保雞丁>]>
注意參數要放的是字串喔!
>>> Food.objects.order_by('price','name') # 先排price再排name
<QuerySet [<Food: 炒青菜>, <Food: 宮保雞丁>]>
>>> Food.objects.order_by('name','price') # 先排name再排price
<QuerySet [<Food: 宮保雞丁>, <Food: 炒青菜>]>
>>> Food.objects.order_by('-price') # 反向排序
<QuerySet [<Food: 宮保雞丁>, <Food: 炒青菜>]>
如果決定之後想讓資料都照某項特性排列,但卻不想每次這麼麻煩地打這些
在Food類別中加入Meta
class Food(models.Model):
name = models.CharField(max_length=20)
price=models.DecimalField(max_digits=3,decimal_places=0)
comment = models.CharField(max_length=50, blank=True)
is_spicy = models.BooleanField(default=False)
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
def __str__(self):
return self.name
class Meta:
ordering=['price']
記得更動Meta時也需做migration
>>> foods = Food.objects.order_by('price')
>>> foods
<QuerySet [<Food: 炒青菜>, <Food: 宮保雞丁>]>
>>> foods = foods.filter(is_spicy=True)
>>> foods
<QuerySet [<Food: 宮保雞丁>]>
>>> food = foods.get(name__contains='宮保')
>>> foods
<QuerySet [<Food: 宮保雞丁>]>
上述操作可做一個串連:
>>> Food.objects.order_by('price').filter(is_spicy=True).get(name__contains='宮保')
<Food: 宮保雞丁>
找出食物對應的餐廳
>>> food = Food.objects.get(name='宮保雞丁')
>>> food.restaurant
<Restaurant: 古意得餐廳>
查出一家餐廳中擁有的食物
>>> r = Restaurant.objects.get(name='古意得餐廳')
>>> r.food_set
<django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager.<locals>.RelatedManager object at 0x03E520D0>
>>> r.food_set.all()
<QuerySet [<Food: 炒青菜>, <Food: 宮保雞丁>]>
利用小寫模型名稱_set
就可得到一個關係管理器,類似於objects,我們可以對他使用各種查詢的方法
接下來介紹另外一種關係
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=30)
def __str__(self):
return self.name
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
一本書有多個作者,一個作者也有多本著作
這時候是多對多關係
>>> book = Book.objects.get(id=1)
>>> book.authors.all()
<QuerySet [<Author: 金庸>, <Author: 古龍>]>
>>> author = Author.objects.get(id=1)
>>> a.book_set.all()
[<Book: 便秘的魔法石>, <Book: 考盃的考驗>]
如果有一筆資料需要修改
>>> food = Food.objects.get(name='宮保雞丁')
>>> food.price = 200
>>> food.save()
不過save()方法會將一筆資料的所有欄位重新輸入
為了避免一些錯誤發生
使用查詢集的方法update
>>> Food.objects.filter(name='宮保雞丁').update(price=200)
1
注意update是QuerySet的方法,無法作用在單獨物件上,所以我們要偷偷用點技巧,使用filter來過濾出只含有宮保雞丁的QuerySet
另外,此方法會回傳一個整數代表有幾筆資料被更新了
因為它是查詢集的方法,所以update是可以更動所有在查詢集中的物件的,比如說今天古意得餐廳改變營業模式成為熱炒100了,那麼我們可以這麼做
>>> Restaurant.objects.get(name='古意得餐廳').food_set.update(price=100)
2
刪除資料的方法
>>> f = Food.objects.get(name='宮保雞丁')
>>> f.delete()
(1, {'restaurants.Food': 1})
>>> Food.objects.all()
<QuerySet [<Food: 炒青菜>]>
>>> Food.objects.all().delete()
(1, {'restaurants.Food': 1})
>>> Food.objects.all()
<QuerySet []>
不論是查詢集還是單獨的模型物件都是適用的
前面的資料操作試完後
記得把資料改回去
接著
views.py
from django.shortcuts import render_to_response
from restaurants.models import Restaurant, Food
def menu(request):
restaurants = Restaurant.objects.all()
return render_to_response('menu.html', locals())
改完後會發現出現這個錯誤
原因是因為如果只安裝pylint,vs code會不知道那是你定義的ORM
所以要安裝 pylint_django 修正他
pip install pylint_django
裝好後,在settings.json中加入
"python.linting.pylintArgs": [
"--load-plugins=pylint_django",
]
錯誤訊息就會消失了
menu.html
<!doctype html>
<html>
<head>
<title> Menu </title>
<meta charset='UTF-8'>
<style>
table, th, td{
border: 1px solid black;
}
</style>
</head>
<body>
{% for r in restaurants %}
<h2>{{ r.name }}</h2>
{% if r.food_set.all %}
<p>本餐廳共有{{ r.food_set.all|length }}道菜</p>
<table>
<tr>
<th>菜名</th>
<th>價格</th>
<th>註解</th>
<th>辣不辣</th>
</tr>
{% for food in r.food_set.all %}
<tr>
<td> {{ food.name }} </td>
<td> {{ food.price }} </td>
<td> {{ food.comment }} </td>
<td> {% if food.is_spicy %} 辣 {% else %} 不辣 {% endif %} </td>
</tr>
{% endfor %}
</table>
{% else %}
<p>本餐廳啥都沒賣</p>
{% endif %}
{% endfor %}
</body>
</html
結果: