【Django】Django框架進階詳述(三)
- 2020 年 2 月 14 日
- 筆記
1、Model
Model用於定義數據模型,使用python的形式操作數據庫。
from django.db import models class Person(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30)
將上述Model轉換為SQL語句如下:
CREATE TABLE myapp_person ( "id" serial NOT NULL PRIMARY KEY, "first_name" varchar(30) NOT NULL, "last_name" varchar(30) NOT NULL );
2、Field
(1)Field類型確定字段類型:
- Field type
- CharField
- IntegerField
- DatetimeField
- AutoField
每個Field通常有一些參數,常見的有:
- Field option
- null
- black
- choices
- default
- help_text
- primary_key
- unique
- verbose_name
關係:
- Many-To-One ForeignKey
- Many-To-Many ManyToManyField
- One-To-One OneToOneField
class Manufacturer(models.Model): # ... pass class Car(models.Model): manufacturer = models.ForeignKey(Manufacturer) class Group(models.Model): #... pass Class User(models.Model): groups = ManyToManyField(Group) from django.db import models class Place(models.Model): name = models.CharField(max_length=50) address = models.CharField(max_length=80) class Restaurant(models.Model): place = models.OneToOneField(Place, primary_key=True) serves_hot_dogs = models.BooleanField(default=False) serves_pizza = models.BooleanField(default=False)
(2)內置Field 詳見:https://docs.djangoproject.com/en/1.10/ref/models/fields/ (3)Meta方法 詳見:https://docs.djangoproject.com/en/1.10/ref/models/options/ (4)重寫save方法
from django.db import models class Blog(models.Model): name = models.CharField(max_length=100) tagline = models.TextField() def save(self, *args, **kwargs): do_something() super(Blog, self).save(*args, **kwargs) # Call the "real" save() method. do_something_else()
- 一定要記得調用超類的save方法,否則不會保存到數據庫。
(5)執行SQL
>>> for p in Person.objects.raw('SELECT * FROM myapp_person'): ... print(p) John Smith Jane Jones
3、模型繼承
(1)抽象基類 你只想使用父類來持有一些信息,你不想在每個子模型中都敲一遍。這個類永遠不會單獨使用,使用abstract表明基類,數據庫不會建該表。
from django.db import models class CommonInfo(models.Model): name = models.CharField(max_length=100) age = models.PositiveIntegerField() class Meta: abstract = True ordering = ['name'] class Student(CommonInfo): home_group = models.CharField(max_length=5) class Meta(CommonInfo.Meta): db_table = 'student_info'
- Meta也可以繼承, 如果子類不實現Meta則默認會繼承。
(2)多表繼承 如果你繼承一個已經存在的模型且想讓每個模型具有它自己的數據庫表,那麼應該使用多表繼承。每一個層級下的每個 model 都是一個真正意義上完整的 model 。 每個 model 都有專屬的數據表,都可以查詢和創建數據表。 繼承關係在子 model 和它的每個父類之間都添加一個鏈接 (通過一個自動創建的 OneToOneField來實現)。
from django.db import models class Place(models.Model): name = models.CharField(max_length=50) address = models.CharField(max_length=80) class Restaurant(Place): serves_hot_dogs = models.BooleanField(default=False) serves_pizza = models.BooleanField(default=False) >>> Place.objects.create(name='Tsinghua', address='Zhongguancun') >>> Restaurant.objects.create(name='Bishengke', address='Beijing Wangjing', serves_hot_dogs=True, serves_pizza=True) <Restaurant: Restaurant object> >>> p0 = Place.objects.get(name='Tsinghua') >>> p1 = Place.objects.get(name='Bishengke') >>> p2 = Restaurant.objects.get(name='Bishengke') >>> p3 = Restaurant.objects.get(name='Tsinghua') ... DoesNotExist: Restaurant matching query does not exist. >>> p1.restaurant <Restaurant: Restaurant object> >>> p0.restaurant
(3)代理繼承 使用多表繼承時,model的每個子類都會創建一張新數據表,通常情況下,這正是我們想要的操作。這是因為子類需要一個空間來存儲不包含在基類中的字段數據。 但有時,你可能只想更改 model 在 Python 層的行為實現,比如:更改默認的 manager ,或是添加一個新方法。 而這正是代理繼承要做的:為原始模型創建一個代理。你可以創建、刪除、更新代理 model 的實例,而且所有的數據都可以像使用原始 model 一樣被保存。 不同之處在於:你可以在代理 model 中改變默認的排序設置和默認的 manager ,更不會對原始 model 產生影響。 聲明代理 model 和聲明普通 model 沒有什麼不同。 設置Meta類中 proxy 的值為 True,就完成了對代理 model 的聲明。
from django.db import models class Person(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30) class MyPerson(Person): class Meta: proxy = True def do_something(self): # ... pass
- MyPerson只是一個代理,數據庫中不會建立該表,MyPerson類和它的父類 Person 操作同一個數據表。特別的是,Person 的任何實例也可以通過 MyPerson訪問,反之亦然:
>>> p = Person.objects.create(first_name="foobar") >>> MyPerson.objects.get(first_name="foobar") <MyPerson: foobar>
(4)多重繼承 多重繼承和多表繼承沒什麼大的差別,會自動添加兩個OneToOneField, 需要指出,兩個基類的默認自增id要重新命名,否則添加OneToOneField會有問題。也不允許子類重寫父類字段。
class Article(models.Model): article_id = models.AutoField(primary_key=True) ... class Book(models.Model): book_id = models.AutoField(primary_key=True) ... class BookReview(Book, Article): pass >>> b = BookReview.objects.create(headline='', body='asdf', title='sdf')
4、Model查詢
- 增:
Model.objects.create(**kwargs) Model(*kwargs).save()
- 刪:
QuerySet.delete() object.delete()
- 改:
object.attr = value object.save() QuerySet.update(**kwargs)
- 查:
Model.objects.all(**kwargs) Model.objects.filter(**kwargs) Model.objects.get(**kwargs)
- 結果集:
object QuerySet
from django.db import models class Blog(models.Model): name = models.CharField(max_length=100) tagline = models.TextField() def __str__(self): # __unicode__ on Python 2 return self.name class Author(models.Model): name = models.CharField(max_length=50) email = models.EmailField() def __str__(self): # __unicode__ on Python 2 return self.name class Entry(models.Model): blog = models.ForeignKey(Blog) headline = models.CharField(max_length=255) body_text = models.TextField() pub_date = models.DateField() authors = models.ManyToManyField(Author) n_comments = models.IntegerField() n_pingbacks = models.IntegerField() rating = models.IntegerField() def __str__(self): # __unicode__ on Python 2 return self.headline
- exclude:
>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.') >>> b.save() >>> a1 = Author.objects.create(name='guang', email='[email protected]') >>> a2 = Author.objects.create(name='mage', email='[email protected]') >>> Author.objects.exclude(name='mage')
關聯對象查詢: 一對多,多對多,一對一都存在對象間的關係,Django提供了簡便的API來完成。
- 前向查詢:通過屬性訪問關聯的(外部)對象。
>>> e = Entry.objects.get(id=2) >>> e.blog # Returns the related Blog object. >>> e.blog = None >>> e.save()
- 反向查詢:如果模型有一個ForeignKey,那麼該ForeignKey 所指的模型實例可以通過一個管理器返回前一個模型的所有實例。默認情況下,這個管理器的名字為foo_set,其中foo 是源模型的小寫名稱。該管理器返回的查詢集可以用上一節提到的方式進行過濾和操作。
>>> b = Blog.objects.get(id=1) >>> b.entry_set.all() # Returns all Entry objects related to Blog. # b.entry_set is a Manager that returns QuerySets. >>> b.entry_set.filter(headline__contains='Lennon') >>> b.entry_set.count()
- 複雜查詢 Q:filter() 等方法中的關鍵字參數查詢都是一起進行「AND」 的。 如果你需要執行更複雜的查詢(例如OR語句),你可以使用Q對象。 Q 對象 (django.db.models.Q) 對象用於封裝一組關鍵字參數。這些關鍵字參數就是上文「字段查詢」 中所提及的那些。
>>> from django.db.models import Q >>> Author.objects.filter(Q(name='guang')|Q(name='mage'))
每個接受關鍵字參數的查詢函數(例如filter()、exclude()、get())都可以傳遞一個或多個Q 對象作為位置(不帶名的)參數。如果一個查詢函數有多個Q 對象參數,這些參數的邏輯關係為「AND"。 查詢函數可以混合使用Q 對象和關鍵字參數。所有提供給查詢函數的參數(關鍵字參數或Q 對象)都將"AND」在一起。但是,如果出現Q 對象,它必須位於所有關鍵字參數的前面。例如:
>>> Poll.objects.get( Q(question__startswith='Who'), Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)) ) >>> Poll.objects.get( Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)), question__startswith='Who')
- 保存ForignKey和ManyToManyField字段
>>> e = Entry.objects.create(blog=b,headline='Hello', body_text='Hello world', pub_date=timezone.now()) >>> e.blog = b >>> e.authors = [a1] >>> e.save() >>> e.authors.add(a1, a2) >>> e.save() >>> e.authors.create(name='comyn', email='[email protected]')
- QuerySet鏈式過濾
>>> Entry.objects.filter( ... headline__startswith='What' ... ).exclude( ... pub_date__gte=datetime.date.today() ... ).filter( ... pub_date__gte=datetime(2005, 1, 30) ... )
- 高級條件過濾 lookup
- 大於 gt
- 大於等於 gte
- 小於 lt
- 小於等於 lte
- 包含 contains
- 精確匹配 exact 也是默認方式
- 開始於 startswith
- 結束於 endswith
- regex 正則匹配
- 忽略大小寫 icontains iexact istartswith iendswith iregex
- 在列表中 in
- 範圍之內 range
- 時間日期類型 date year month day
這種形式django稱之為 lookup,使用__(雙下劃線)來查詢。
>>> Entry.objects.filter(pub_date__lte='2006-01-01') # SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01'; >>> Entry.objects.get(headline__exact="Man bites dog") >>> Blog.objects.get(name__iexact="beatles blog") >>> Blog.objects.filter(pk__in=[1,4,7]) >>> Entry.objects.filter(pub_date__range=(start_date, end_date)) >>> Entry.objects.get(title__regex=r'^(An?|The) +')
- 跨關聯關係查詢
>>> Entry.objects.filter(blog__name='Beatles Blog') >>> Blog.objects.filter(entry__headline__contains='Lennon') # 反向 >>> Blog.objects.filter(entry__authors__name__isnull=True) # 多層 >>> Blog.objects.filter(entry__authors__isnull=False, entry__authors__name__isnull=True) # 多個過濾條件
- 引用模型字段查詢 F 通常我們是將模型字段與常量比較,或者比較兩個字段值。
>>> from django.db.models import F >>> Entry.objects.filter(n_comments__gt=F('n_pingbacks')) >>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))
- 限制返回個數
>>> Entry.objects.all()[:5] # 返回前面5 個對象(LIMIT 5): >>> Entry.objects.all()[5:10] # 返回第6 至第10 個對象(OFFSET 5 LIMIT 5)
- 查詢緩存
查詢集不會永遠緩存它們的結果。當只對查詢集的部分進行求值時會檢查緩存, 但是如果這個部分不在緩存中,那麼接下來查詢返回的記錄都將不會被緩存。特別地,這意味着使用切片或索引來限制查詢集將不會填充緩存。例如,重複獲取查詢集對象中一個特定的索引將每次都查詢數據庫:
>>> queryset = Entry.objects.all() >>> print queryset[5] # Queries the database >>> print queryset[5] # Queries the database again
然而,如果已經對全部查詢集求值過,則將檢查緩存:
>>> queryset = Entry.objects.all() >>> [entry for entry in queryset] # Queries the database >>> print queryset[5] # Uses cache >>> print queryset[5] # Uses cache
- 查詢對象比較
為了比較兩個模型實例,只需要使用標準的Python 比較操作符,即雙等於符號:==。在後台,它會比較兩個模型主鍵的值。
>>> some_entry == other_entry # equal behind >>> some_entry.id == other_entry.id
- 所有查詢api,詳見:https://docs.djangoproject.com/en/1.10/ref/models/querysets/
- annotate:添加註釋屬性,與聚合函數一起使用返回聚合值。
>>> from django.db.models import Count >>> qs = Question.objects.annotate(Count('choices')) >>> for q in qs: ...: print(q.choices__count) >>> qs = Question.objects.annotate(choices_num=Count('choices')) >>> for q in qs: ...: print(q.choices_num)
- aggregate:聚合函數返回結果字典。
>>> from django.db.models import Count >>> q = Blog.objects.aggregate(Count('entry')) {'entry__count': 16} >>> q = Blog.objects.aggregate(number_of_entries=Count('entry')) {'number_of_entries': 16}
- 聚合類函數 聚合類函數配合 聚合函數 或 注釋函數使用,在 django.db.models模塊中。
- class Avg(expression, output_field=FloatField(), **extra)
- class Count(expression, distinct=False, **extra)
- class Max(expression, output_field=None, **extra)
- class Min(expression, output_field=None, **extra)
- class StdDev(expression, sample=False, **extra) # 標準除
- class Sum(expression, output_field=None, **extra)
- class Variance(expression, sample=False, **extra) # 方差
- distinct:去重
>>> Author.objects.distinct() >>> Entry.objects.order_by('pub_date').distinct('pub_date') >>> Entry.objects.order_by('blog').distinct('blog')
- values:返回ValuesQuerySet(類字典),而不是模型實例對象
# This list contains a Blog object. >>> Blog.objects.filter(name__startswith='Beatles') [<Blog: Beatles Blog>] # This list contains a dictionary. >>> Blog.objects.filter(name__startswith='Beatles').values() [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}] >>> Blog.objects.values() [{'id': 1, 'name': 'Beatles Blog', 'tagline': 'All the latest Beatles news.'}], >>> Blog.objects.values('id', 'name') [{'id': 1, 'name': 'Beatles Blog'}]
- values_list 與values類似,但返回元組而不是字典。
>>> Entry.objects.values_list('id').order_by('id') [(1,), (2,), (3,), ...] >>> Entry.objects.values_list('id', flat=True).order_by('id') [1, 2, 3, ...]
- defer和only 有時模型包含一些含有大量數據的類型,通常模型會將數據庫取出的數據轉換為python對象,有時我們不需要浪費這性能。 defer:排除;only:僅轉換。
>>> Entry.objects.defer("headline") >>> Entry.objects.only("headline")
- using:使用哪個數據庫
# queries the database with the 'default' alias. >>> Entry.objects.all() # queries the database with the 'backup' alias >>> Entry.objects.using('backup')
- select_for_update 返回一個 queryset ,會鎖定相關行直到事務結束。在支持的數據庫上面產生一個SELECT … FOR UPDATE 語句。
entries = Entry.objects.select_for_update().filter(author=request.user)
- raw:執行原始SQL
>>> for p in Person.objects.raw('SELECT * FROM myapp_person'): ... print(p) John Smith Jane Jones
- get_or_create
try: obj = Person.objects.get(first_name='John', last_name='Lennon') except Person.DoesNotExist: obj = Person(first_name='John', last_name='Lennon', birthday=date(1940, 10, 9)) obj.save() Equal obj, created = Person.objects.get_or_create( first_name='John', last_name='Lennon', defaults={'birthday': date(1940, 10, 9)}, )
- update_or_create
- bulk_create
>>> Entry.objects.bulk_create([ ... Entry(headline="Django 1.0 Released"), ... Entry(headline="Django 1.1 Announced"), ... Entry(headline="Breaking: Django is awesome") ... ])
- in_bulk
>>> Blog.objects.in_bulk([1]) {1: <Blog: Beatles Blog>} >>> Blog.objects.in_bulk([1, 2]) {1: <Blog: Beatles Blog>, 2: <Blog: Cheddar Talk>}
- latest earliest first last
>>> Entry.objects.latest('pub_date') # Or meta define get_latest_by()
5、事務
事務是綁在view中實現的。
from django.db import transaction @transaction.non_atomic_requests def my_view(request): do_stuff() @transaction.atomic def viewfunc(request): # This code executes inside a transaction. do_stuff()
6、自定義管理器
- 添加額外管理器方法
添加額外管理器方法是為你的類增加「表級」功能的首選方式。 (如果要添加行級功能,比如只對某個模型的實例起作用,應使用模型方法,而不是管理器方法。) 自定義的管理器方法可以返回你想要的任何數據,而不需要返回一個查詢集。 例如,下面這個自定義管理器提供一個with_counts() 方法,它返回所有OpinionPoll 對象的列表,列表的每項都有一額外num_responses 屬性,該屬性保存一個聚合查詢的結果(註:對應的應是SQL查詢語句中的COUNT(*)生成的項):
from django.db import models class PollManager(models.Manager): def with_counts(self): from django.db import connection cursor = connection.cursor() cursor.execute(""" SELECT p.id, p.question, p.poll_date, COUNT(*) FROM polls_opinionpoll p, polls_response r WHERE p.id = r.poll_id GROUP BY p.id, p.question, p.poll_date ORDER BY p.poll_date DESC""") result_list = [] for row in cursor.fetchall(): p = self.model(id=row[0], question=row[1], poll_date=row[2]) p.num_responses = row[3] result_list.append(p) return result_list class OpinionPoll(models.Model): question = models.CharField(max_length=200) poll_date = models.DateField() objects = PollManager() class Response(models.Model): poll = models.ForeignKey(OpinionPoll) person_name = models.CharField(max_length=50) response = models.TextField()
- 添加自定義manager
class AuthorManager(models.Manager): def get_queryset(self): return super(AuthorManager, self).get_queryset().filter(role='A') class EditorManager(models.Manager): def get_queryset(self): return super(EditorManager, self).get_queryset().filter(role='E') class Person(models.Model): first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) role = models.CharField(max_length=1, choices=(('A', _('Author')), ('E', _('Editor')))) people = models.Manager() authors = AuthorManager() editors = EditorManager() >>> Person.authors.all() >>> Person.editors.all() >>> Person.people.all()