django models 模型 從入門到進階
Model 進階學習
簡介
Django經常被用於一些創業團隊,乃是因為其非常適合敏捷開發,開發效率非常之高。Model 作為Django重要組成部分也是亮點之一,著實需要我們花時間好好梳理一遍。
ORM需要好好學習一下,運用得當可以大大的提升程式碼的簡潔性。Django的model模組,遵循了DRY(don’t repeat yourself)原則,也會使得程式碼更加容易維護,只需修改一次,肯定會大大提高程式的健壯性以及可維護性。
而且ORM也使得該框架更加靈活且鬆解耦。例如有些碼農不喜歡django的模版,更偏愛其他的模版渲染引擎。django檢視方法並未強制一定要使用自身的模版引擎,你一可以嘗試實用jinjia2等等。同時ORM遮蔽了底層資料庫的語法,你可以執行非常多的資料庫型別(mysql,mongo?don’t care,改一下engine配置即可),當然效能肯定會降低一些,畢竟多封裝了一層。
一路走來,踩過無數的大坑,發現網上很多網友的博文是誤導性的,所以想在這裡寫一片文章梳理一下model的全貌,以備複習,也供給大家一個參考,如果發現錯誤希望可以幫忙指正,謝謝。
本文結構:
- model常用欄位定義
- model的初級用法
- model的高階用法
- model的一些坑
1.model常用欄位定義
V=models.CharField(max_length=None[, **options]) #varchar
V=models.EmailField([max_length=75, **options]) #varchar
V=models.URLField([verify_exists=True , max_length=200, **options]) #varchar
V=models.FileField(upload_to=None[, max_length=100, **options]) #varchar
#upload_to指定儲存目錄可帶格式,
V=models.ImageField(upload_to=None[, height_field=None, width_field=None, max_length=100, **options])
V=models.IPAddressField([**options]) #varchar
V=models.FilePathField(path=None[, match=None, recursive=False, max_length=100, **options]) #varchar
V=models.SlugField([max_length=50, **options]) #varchar,標籤,內含索引
V=models.CommaSeparatedIntegerField(max_length=None[, **options]) #varchar
V=models.IntegerField([**options]) #int
V=models.PositiveIntegerField([**options]) #int 正整數
V=models.SmallIntegerField([**options]) #smallint
V=models.PositiveSmallIntegerField([**options]) #smallint 正整數
V=models.AutoField(**options) #int;在Django程式碼內是自增
V=models.DecimalField(max_digits=None, decimal_places=None[, **options]) #decimal
V=models.FloatField([**options]) #real
V=models.BooleanField(**options) #boolean或bit
V=models.NullBooleanField([**options]) #bit欄位上可以設定上null值
V=models.DateField([auto_now=False, auto_now_add=False, **options]) #date
#auto_now最後修改記錄的日期;auto_now_add新增記錄的日期
V=models.DateTimeField([auto_now=False, auto_now_add=False, **options]) #datetime
V=models.TimeField([auto_now=False, auto_now_add=False, **options]) #time
V=models.TextField([**options]) #text
V=models.XMLField(schema_path=None[, **options]) #text
——————————————————————————–
V=models.ForeignKey(othermodel[, **options]) #外來鍵,關聯其它模型,建立關聯索引
V=models.ManyToManyField(othermodel[, **options]) #多對多,關聯其它模型,建立關聯表
V=models.OneToOneField(othermodel[, parent_link=False, **options]) #一對一,欄位關聯表屬性
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
2.model初級用法
首先我們拿官網的例子作為示範,這個例子非常的經典,被用在django book以及其他很多相關的書籍當中。
我們首先假定如下的概念:
- 一個作者有姓,名,email地址。
- 出版商有名稱,地址,所在的city,province,country,website.
- 書籍有書名和出版日期。它有一個或者多個作者[many-2-many]。但是隻有一個出版商([one 2 many]),被稱為外來鍵[foreign key]。
在models.py中新增如下內容:
from django.db import models
class Publisher(models.Model):
name = models.CharField(max_length=30)
address = models.CharField(max_length=50)
city = models.CharField(max_length=60)
state_province = models.CharField(max_length=30)
country = models.CharField(max_length=50)
website = models.URLField()
def __unicode__(self):
return self.name
class Author(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=40)
email = models.EmailField()
def __unicode__(self):
return u'%s %s' % (self.first_name, self.last_name)
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher)
publication_date = models.DateField()
def __unicode__(self):
return self.title
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
並且記得在配置裡面加入app model註冊,
之後可以用python manage.py validate
校驗模型的正確性。
也可以用python mange.py sqlall books
來檢視sql生成的語句。
後面還需要執行 python manage.py syncdb
基本的新增資料
基本的選擇物件
- 獲取全體物件:
Publisher.objects.all()
可以用來獲取所有的物件. - 篩選部分物件:
Publisher.objects.filter(name='Apress')
可以用來篩選所有name是Apress的publisher.(獲取的是一個物件列表) - 獲取單個物件
Publisher.objects.get(name='Apress')
獲取一個名字是Apress的開發商(只獲取單個物件)。 filter的條件會在高階部分列出。 - 排序:
publisher.objects.order_by("state_province","-address")
.這句程式碼幾乎囊括了排序的精華:).我們可以看到是多欄位排序,同時,address前面有一個-
代表的是逆排序。
當然,一般情況下,只需要按照某個欄位進行排序,這種情況下可以指定模型的預設排序方式: - 連查:
除了單個查詢,我們還可以用下面這種方式來查詢Publisher.objects.filter(name='xxoo').order_by('-name')[0]
這段程式並不會執行兩次,而是最後轉化為一句sql語句來執行(考慮的太周到). - 更新多個物件:
我們知道,之前我們用p.save()來更新物件,如果涉及到很多條資料需要一次性更新,這個時候該如何呢?Django也考慮到了這一點,可以用如下的方式來更新:Publisher.objects.all().update(country='China')
,一次性將所有的Publisher的國家更新為china。 - 刪除物件:
前面講了新增、更新,這裡補充一下刪除,刪除主要是篩選出物件後執行delete()
方法.Publisher.objects.filter(country='USA').delete()
3.model的高階用法
訪問外來鍵
>>>b=Book.objects.get(id=1)
>>>b=p.publisher
<publisher:Apress Publishing>
>>>b.publisher.website
u'http://www.xuancan.net.cn'
- 1
- 2
- 3
- 4
- 5
- 6
對於Foreignkey 來說,關係的另外一端,也可以追溯回來,不過由於不是對稱關係,所以有一些區別,獲得的是一個list,而非單一物件:
>>>p=Publisher.objects.get(name="Apress Publishing")
>>>p.book_set.all()
[<Book:The Chinese english>,<Book:the good good study>,...]
- 1
- 2
- 3
訪問多對多值
多對多和外來鍵工作方式類似,不過我們處理的是QuerySet而非模型例項。例如,檢視書籍的作者:
>>>b = Book.objects.get(id=1)
>>>b.authors.all()
[<Author:liushuchun>,<Author:nobb>]
>>>b.authors.filter(fisrt_name="liushuchun")
[<Author:liushuchun>]
- 1
- 2
- 3
- 4
- 5
我們可以看到這就是類似objects來用了。
更改資料庫結構
Django有一個不完善的地方是,一旦model確定下來後,再想通過命令來更新是無法更新的,會報錯,這個時候就要學會用手動的方式更改資料庫結構。
具體的過程:
1. 先在models.py檔案中,找到你要新增欄位的模型,新增(也可以是刪除修改,只是sql語句有一些區別)上該欄位,如下所示(抱歉,截圖多了個等號)
2. 在cmd
下,通過cd命令進入應用目錄,也就是manage.py檔案所在的目錄
3. 然後使用python manage.py sqlall[app_name]命令,打印出app中包括的所有模型的sql語言表示
3. 找到你想要新增欄位的表,找到你已經新增過的欄位test,記下sql語句
4. 進入 manage.py shell 建立一個cursor例項,用於執行sql語句
5. 執行該sql語句,看好了,這個sql語句是剛才我讓你記下的sql語句,執行這個命令就可以完成向資料庫新增欄位.
6. 最後,我們要驗證新增欄位是否成功,仍然在manage.py shell中,通過呼叫模型來檢查是否成功。
這裡我們只說了新增欄位,其他刪除或者修改欄位類似。
manager管理器新增自定義方法
管理器是Django查詢資料庫時會使用到的一個特別的物件,在Book.objects.all()語法中,objects就是管理器,在django中,每一個model至少有一個管理器,而且,你也可以建立自己的管理器來自定義你的資料庫訪問操作。一方面可以增加額外的管理器方法,另一方面可以根據你的需求來修改管理器返回的QuerySet。
這是一種”表級別”的操作,下面我們給之前的Book類增加一個方法title_count(),它根據關鍵字,
返回標題中包括這個關鍵字的書的個數。
class BookManager(models.Manager):
def title_count(self, keyword):
return self.filter(title__icontains=keyword).count()
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher)
publication_date = models.DateField()
num_pages = models.IntegerField(blank=True, null=True)
#可以直接賦值替換掉預設的objects管理器,也可以定義一個新的管理器變數
#呼叫時,直接使用這個新變數就可以了,一旦定義了新的管理器,預設管理器
#需要顯示宣告出來才可以使用
# objects = models.Manger()
objects = BookManager()
def __unicode__(self):
return self.title
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
結果為:
>>>Book.objects.title_count('python')
2
- 1
- 2
上面的程式碼可以看到,建立自定義的Manager的步驟:
1. 繼承models.Manager,定義新的管理器方法,在方法中使用self,也就是manager本身來
進行操作
2. 把自定義的管理器物件賦值給objects屬性來代替預設的管理器。
那為什麼不直接建立個title_count函式來實現這個功能呢?
因為建立管理器類,可以更好地進行封裝功能和重用程式碼。
修改返回的QuerySet
Book.objects.all()返回的是所有記錄物件,可以重寫Manager.get_query_set()方法,它返回的是你自定義的QuerySet,你之後的filter,slice等操作都是基於這個自定義的QuerySet。
from django.db import models
class RogerBookManager(models.Manager):
def get_query_set(self):
#呼叫父類的方法,在原來返回的QuerySet的基礎上返回新的QuerySet
return super(RogerBookManager, self).get_query_set().filter(title__icontains='python')
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=50)
#objects預設管理器需要顯示宣告,才能使用
objects = models.Manager() # 預設的管理器
roger_objects = RogerBookManager() # 自定義的管理器,用新變數
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
結果為:
>>>Book.objects.all()
[<Book:Coplier Theroy>,<Book:python>,...]
>>>Book.roger_objects.all()
[<Book:Python Tutorial>,<Book:Python tools>]
- 1
- 2
- 3
- 4
你可以為model定義多個不同的管理器來返回不同的QuerySet,不過要注意一點的是Django
會把你第一個定義的管理器當作是預設的管理器,也就是程式碼行中最上面定義的管理器。Django
有些其它的功能會使用到預設的管理器,為了能讓它正常的工作,一種比較好的做法就是把原始預設
的管理器放在第一個定義。
Model新增方法(這部分是抄書)
和管理器的”表級別”操作相比,model的方法更像是”記錄級別”的操作,不過,model的主要設計是用來
用”表級別”操作的,”記錄級別”的操作往往是用來表示記錄的狀態的,是那些沒有放在資料庫表中,但是也
有意義的資料。舉例說明:
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
birth_date = models.DateField()
address = models.CharField(max_length=100)
city = models.CharField(max_length=50)
# 用來判讀是否在baby boomer出生,可以不用放在資料庫表中
def baby_boomer_status(self):
"Returns the person's baby-boomer status."
import datetime
if datetime.date(1945, 8, 1) <= self.birth_date <= datetime.date(1964, 12, 31):
return "Baby boomer"
if self.birth_date < datetime.date(1945, 8, 1):
return "Pre-boomer"
return "Post-boomer"
# 用來返回全名,這個可以不被插入到資料庫表中
def get_full_name(self):
"Returns the person's full name."
return u'%s %s' % (self.first_name, self.last_name)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
執行結果:
>>> p = Person.objects.get(first_name='Barack', last_name='Obama')
>>> p.birth_date
datetime.date(1961, 8, 4)
>>> p.baby_boomer_status()
'Baby boomer'
>>> p.get_full_name()
u'Barack Obama'
- 1
- 2
- 3
- 4
- 5
- 6
- 7
執行自定義SQL語句
如果你想執行自定義的SQL語句查詢,可以使用django.db.connection物件:
可以使用SQL對資料庫中所有的表進行操作,而不用引用特定的model類。
需要注意的是execute()函式使用的SQL語句需要使用到%s這樣的格式符,而
不是直接寫在裡面。
這樣的操作比較自由,比較好的做法是把它放在自定義管理器中:
from django.db import connection, models
class PythonBookManager(models.Manager):
def books_titles_after_publication(self, date_string):
cursor = connection.cursor()
cursor.execute("""
SELECT title
FROM books_book
WHERE publication_date > %s""", [date_string])
#fetchall()返回的是元組的列表
return [row[0] for row in cursor.fetchall()]
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher)
publication_date = models.DateField(blank=True, null=True)
num_pages = models.IntegerField(blank=True, null=True)
objects = models.Manager()
python_objects = PythonBookManager()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
一些django奇技淫巧
還在蒐集中,後面會加上.
- 1
- 2
一些過濾欄位方法
1.多表連線查詢:當我知道這點的時候頓時覺得django太NX了。
class A(models.Model):
name = models.CharField(u'名稱')
class B(models.Model):
aa = models.ForeignKey(A)
B.objects.filter(aa__name__contains='searchtitle')
- 1
- 2
- 3
- 4
- 5
1.5 我叫它反向查詢,後來插入記錄1.5,當我知道的時候瞬間就覺得django太太太NX了。
class A(models.Model):
name = models.CharField(u'名稱')
class B(models.Model):
aa = models.ForeignKey(A,related_name="FAN")
bb = models.CharField(u'名稱')
查A: A.objects.filter(FAN=’XXXX’),都知道related_name的作用,A.FAN.all()是一組以A為外來鍵的B例項,可前面這樣的用法是查詢出所有(B.aa=A且B.bb=XXXX)的A例項,然後還可以通過__各種關係查詢,真赤激!!!
**2.條件選取querySet的時候,filter表示=,exclude表示!=querySet.distinct() 去重複
__exact 精確等於 like 'aaa'
__iexact 精確等於 忽略大小寫 ilike 'aaa'
__contains 包含 like '%aaa%'
__icontains 包含 忽略大小寫 ilike '%aaa%',但是對於sqlite來說,contains的作用效果等同於icontains。
__gt 大於
__gte 大於等於
__lt 小於
__lte 小於等於
__in 存在於一個list範圍內
__startswith 以...開頭
__istartswith 以...開頭 忽略大小寫
__endswith 以...結尾
__iendswith 以...結尾,忽略大小寫
__range 在...範圍內
__year 日期欄位的年份
__month 日期欄位的月份
__day 日期欄位的日
__isnull=True/False
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
>> q1 = Entry.objects.filter(headline__startswith="What")
>> q2 = q1.exclude(pub_date__gte=datetime.date.today())
>> q3 = q1.filter(pub_date__gte=datetime.date.today())
>>> q = q.filter(pub_date__lte=datetime.date.today())
>>> q = q.exclude(body_text__icontains="food")
- 1
- 2
- 3
- 4
- 5
即q1.filter(pub_date__gte=datetime.date.today())表示為時間>=now,q1.exclude(pub_date__gte=datetime.date.today())表示為<=now
關於快取:
queryset是有快取的,a = A.objects.all(),print [i for i in a].第一次執行列印會查詢資料庫,然後結果會被儲存在queryset內建的cache中,再執行print的時候就會取自快取。
DJANGO or 查詢
Q查詢——對物件的複雜查詢
F查詢——專門取物件中某列值的操作
Q查詢
1、Q物件(django.db.models.Q)可以對關鍵字引數進行封裝,從而更好地應用多個查詢,例如:
from django.db.models import Q
from login.models import New #models物件
news=New.objects.filter(Q(question__startswith='What'))
- 1
- 2
- 3
- 4
2、可以組合使用&,|操作符,當一個操作符是用於兩個Q的物件,它產生一個新的Q物件。
Q(question__startswith='Who') | Q(question__startswith='What')
- 1
3、Q物件可以用~操作符放在前面表示否定,也可允許否定與不否定形式的組合
Q(question__startswith='Who') | ~Q(pub_date__year=2005)
- 1
4、應用範圍
Poll.objects.