Django開發:(3.2)ORM:多表操作


表關系總結:

  一對多:在多的表中建立關聯字段

  多對多:創建第三張表(關聯表):id 和 兩個關聯字段

  一對一:在兩張表中的任意一張表中建立關聯字段(關聯字段一定要加 unique 約束)

子查詢:一次查詢結果作為另一次查詢的查詢條件

 

創建模型:

from django.db import models

# Create your models here.
class AuthorDetail(models.Model):
    nid = models.AutoField(primary_key=True)
    birthday = models.DateField()
    telephone = models.BigIntegerField()
    addr = models.CharField(max_length=64)

class Author(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    # 一對一:在任意一張表中添加約束
    authordetail = models.OneToOneField(to="AuthorDetail",to_field="nid",on_delete=models.CASCADE) # authordetail也會被自動添加 _id
    # 對於Django2.0版本,一對多(models.ForeignKey)和一對一(models.OneToOneField)要加上 on_delete=models.CASCADE 這個屬性

class Publish(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    city = models.CharField(max_length=32)
    email = models.EmailField()



class Book(models.Model):

    nid = models.AutoField(primary_key=True)
    title = models.CharField( max_length=32)
    publishDate=models.DateField()
    price=models.DecimalField(max_digits=5,decimal_places=2)
    # 一對多:在多的表中添加關聯字段
    publish = models.ForeignKey(to="Publish",to_field="nid",on_delete=models.CASCADE)  # 這句代碼做了兩件事:1. 生成表的時候在 publish前面自動加了 _id,並在表中生成了一個字段 publish_id;2. 把 publish_id 作為外鍵關聯到了 Publish表的 nid 字段;具體作用用SQL語句表示如下:
    """
        publish_id int,
        foreign key(publish_id) references publish(id)
    """

    # 多對多:生成第三張表來存多對多對應關系
    authors = models.ManyToManyField(to="Author")  # 這句代碼的作用是創建第三張表來存多對多關系,而不是在該表中創建一個 authors_id 的字段;ManyToManyField可以建在兩個模型中的任意一個,自動創建第三張表;作用用SQL語句表示如下:
    """
    create table book_authors(
        id int primary key auto_increment,
        book_id int,
        author_id int,
        foreign key(book_id) references book(id),
        foreign key(author_id) references author(id)
        );
    """
    # ManyToManyField可以建在兩個模型中的任意一個,自動創建第三張表

注意:

  • 表的名稱myapp_modelName,是根據 模型中的元數據自動生成的,也可以覆寫為別的名稱
  • id 字段是自動添加的
  • 對於外鍵字段,Django 會在字段名上添加"_id" 來創建數據庫中的列名
  • Django 會根據settings 中指定的數據庫類型來使用相應的SQL 語句
  • 修改配置文件中的INSTALL_APPSZ中設置,在其中添加models.py所在應用的名稱
  • 外鍵字段 ForeignKey 有一個 null=True 的設置(它允許外鍵接受空值 NULL),你可以賦給它空值 None 。

 

添加表記錄

urls.py

from django.contrib import admin
from django.urls import path

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path(r"add/",views.add)
]

views.py

from django.shortcuts import render,HttpResponse

# Create your views here.

from app01.models import *

def add(request):
    # 單表插入記錄:(單表:沒有關聯字段的表)
    pub = Publish.objects.create(name="人民出版社",email="123@qq.com",city="北京")

    # #######################邦定一對多關系####################

    # 為一對多有關聯字段的表添加記錄
    # 為book添加出版社;book是多
    # 方式一:直接為 publish_id 這個字段添加 出版社的id
    book_obj1 = Book.objects.create(title="紅樓夢",price=200,publishDate="2016-8-8",publish_id=1)
    # Book中的 publish會在數據庫遷移的時候添加 _id 變成 publish_id 字段
    print(book_obj1.title)

    # 方式二:為 publish 賦值,此時賦的值為 Publish的實例模型對象
    pub_obj = Publish.objects.filter(nid=1).first()  # nid為1的出版社對象
    book_obj2 = Book.objects.create(title="西游記", price=200, publishDate="2016-8-8", publish=pub_obj)  # publish等於一個 Publish對象
    # create操作時,會把 publish翻譯成 publish_id,把 pub_obj的主鍵值取出來,並把 pub_obj的主鍵值賦值給 publish_id
    print(book_obj2.price)  # book_obj2的價格
    print(book_obj2.publish_id)  # book_obj2的出版社id
    print(book_obj2.publish)  # 不管是方式一還是方式二,book_obj2.publish 都表示 pub_obj 這個model對象(重點)
    # book_obj2.publish:與這本書籍關聯的出版社對象;所以 book_obj2.publish 后面還能用點語法,如:
    print(book_obj2.publish.email)

    # 一對多的查詢:查詢“西游記”對應出版社的郵箱
    book_obj3 = Book.objects.filter(title="西游記").first()  # 西游記這本書對象
    email = book_obj3.publish.email  # book_obj3.publish:西游記這本書對應的出版社對象
    print(email)

    # #################邦定多對多關系############################
    book_obj4 = Book.objects.create(title="python全棧開發",price=100,publishDate="2017-8-8",publish_id=1)

    alex = Author.objects.get(nid=1)
    egon = Author.objects.get(nid=2)
    # 為多對多關系表添加記錄的接口(添加的前提是先create一條記錄)
    book_obj4.authors.add(alex,egon)  # 給python全棧開發這本書籍對象添加作者,作者為alex和egon;邦定多對多關系的API
    """
    book_obj4.authors.add(alex,egon)執行的操作:
    找到 book_obj4的主鍵,找到alex這個作者對象的主鍵,然后在 Book和authors的關系表中添加一條記錄;
    找到 book_obj4的主鍵,找到egon這個作者對象的主鍵,然后在 Book和authors的關系表中添加一條記錄;
    所以book_obj4.authors.add(alex,egon)會在 app01_book_authors 這張表中添加兩條記錄
    """
    # 另外一種寫法:add中直接寫對應作者的主鍵(id),而不是寫作者的model對象
    book_obj4.authors.add(1,2)
    # 或者:
    book_obj4.authors.add(*[1,2])

    # 解除多對多關系(解除的前提是先查出來)
    book_obj5 = Book.objects.filter(nid=3).first()
    book_obj5.authors.remove(2)  # 把 book_id為book_obj4的主鍵、author_id為2的那條記錄從 app01_book_authors 這張關系表中刪除
    # book_obj5.authors.remove(1,2)
    # book_obj5.authors.remove(*[1,2])

    # 刪除所有: clear()
    book_obj5.authors.clear()

    # book_obj5.authors.all() :表示與這本書關聯的所有作者對象的集合(QuerySet)(重點)
    print(book_obj5.authors.all())
    # 查詢所有作者的名字
    ret = book_obj5.authors.all().values("name")

    return HttpResponse("ok")

 

基於對象的跨表查詢

還以上面的表為例: views.py

def add(request):
    # #################基於對象的跨表查詢(基於子查詢)############################
    # #########1. 一對多
    # 查詢主鍵為1的書籍的出版社所在的城市
    # 正向查詢;正向查詢,你需要先知道關聯字段在哪個表中,如這個例子中,publish在Book中,通過publish去查找Publish就是正向查詢;同理,通過Publish查找Book就是反向查詢
    # 正向查詢號按字段(如Book中的publish),反向查詢按表名(如下面的 book_set.all();book就是表名,set就是集合的意思;集合的形式:QuerySet)
    book_obj = Book.objects.filter(nid=1).first()  # 對應書籍對象
    press_city = book_obj.publish.city  # book_obj.publish:對應出版社對象
    print(press_city)
    # 查詢人民出版社出版的所有書籍的名稱
    publish_obj = Publish.objects.filter(name="人民出版社").first()  # 出版社對象
    book_objs = publish_obj.book_set.all()  # 該出版社對應的所有書籍對象;QuerySet;[book_obj1,book_obj2,....]
    print(book_objs)

    # 打印對應所有書籍的名稱
    for obj in publish_obj.book_set.all():
        print(obj.title)


    # #######2. 一對一
    # 正向查詢:查詢alex的手機號
    alex = Author.objects.filter(name="alex").first()
    phoneno = alex.authordetail.telephone  # 正向查詢按字段
    print(phoneno)

    # 反向查詢:查詢號手機號為911的作者名字
    authordetail_obj = AuthorDetail.objects.filter(telephone=911).first()
    authorname = authordetail_obj.author.name  # authordetail_obj.author為對應的作者對象
    print(authorname)


    # 查詢所有住址在北京的作者的姓名
    authorDetail_list = AuthorDetail.objects.filter(addr="北京")
    for obj in authorDetail_list:
        print(obj.author.name)

    # #######3. 多對多
    # 正向查詢:查詢“python全棧開發”所有作者的名字
    book = Book.objects.filter(title="python全棧開發").first()
    author_book = book.authors.all()  # 表示與這本書關聯的所有作者對象的集合(QuerySet)

    # 反向查詢:查詢alex出版過的所有書籍的名稱
    author_obj = Author.objects.filter(name="alex").first()
    books_set = author_obj.book_set.all()  # 所有書籍對象
    print(books_set)

    for obj in books_set:
        print(obj.title)
  
  return HttpResponse("ok")

""" # 一對多: 
   正向按字段:publish
book ------------------> publish
   <-----------------
   反向按表名:book_set.all()

# 一對一:(反向查詢直接按表名,因為本來就是一一對應的關系,不需要 _set.all())
    正向按字段:authordetail
author ------------------> authordetail
    <-----------------
     反向按表名:author  

# 多對多:
   正向按字段:authors.all()
book ------------------> author
   <-----------------
   反向按表名:book_set.all()

"""

字段對象 的 related_name 補充:

django 默認每個主表的對象都有一個是外鍵的屬性,可以通過它來查詢到所有屬於主表的子表的信息。這個屬性的名稱默認是以子表的名稱小寫加上_set()來表示(上面默認以 book_set 訪問),默認返回的是一個querydict對象。
  
#  related_name 可以給這個外鍵(如:Book表中的 publish 字段)定義好一個別的名稱(如 publish字段中: related_name = "publish_books"),這樣以后通過 Publish 對象(publish)查找 books相關信息時,就可通過: publish.publish_books.all()

可參考: https://blog.csdn.net/qq_42420425/article/details/81588306

 

基於雙下划線的跨表查詢:(正向查詢按字段,反向查詢按表名小寫用來告訴ORM引擎join哪張表)

def add(request):
    # #################基於雙下划線(QuerySet)的跨表查詢(基於join)############################
    # 關鍵點:正向查詢按字段,反向查詢按表名

    # 一對一查詢的查詢:查詢alex的手機號
    # 正向查詢:需求:通過Author表join與其關聯的AuthorDetail表,屬於正向查詢;按字段authordetail通知ORM引擎join AuthorDetail表
    Author.objects.filter(name="alex").values("authordetail__telephone")

    # 反向查詢:需求:通過AuthorDetail表join與其關聯的Author表,屬於反向查詢;按表名小寫author通知ORM引擎join AuthorD表
    AuthorDetail.objects.filter(author__name="alex").values("telephone")

    # 查詢人民出版社出版過的所有書籍的名字與價格(一對多)
    ret1 = Book.objects.filter(publish__name="人民出版社").values("title","price")  # 正向查詢:Book ----> Publish, publish是Book中的字段,publish__name是所關聯的Publish的name
    print(ret1)
    ret2 = Publish.objects.filter(name="人民出版社").values("book__title","book__price")  # 反向查詢:Publish ----> Book, book__title中book是表名,book__title是Book中的title字段
    print(ret2)
    """
    上述兩種方式實現的效果一樣,區別在於基表的不同;雙下划線的查詢方式能自動確認 SQL JOIN 聯系
    """

    # 查詢alex出版過的所有書籍的名字(多對多)(可以把雙下划線理解成“的”)
    alex_book1 = Author.objects.filter(name="alex").values("book__title")  # 反向查詢:Author---->Book 按表名 book__
    print(alex_book1)
    alex_book2 = Book.objects.filter(authors__name="alex").values("title")  # 正向查詢:Book---->Author 按字段 authors__
    print(alex_book2)

    ##### 混合使用
    # 查詢人民出版社出版過的所有書籍的名字以及作者的姓名
    mix1 = Book.objects.filter(publish__name="人民出版社").values("title","authors__name")  # 以book為基表
    print(mix1)
    mix2 = Publish.objects.filter(name="人民出版社").values("book__title","book__authors__name")  # 以 publish 為基表
    print(mix2)

    # 手機號以11開頭的作者出版過的所有書籍名稱以及出版社名稱
    mix3 = Book.objects.filter(authors__authordetail__telephone__startswith=11).values("title","publish__name")
    print("mix3",mix3)
    mix4 = Author.objects.filter(authordetail__telephone__startswith="11").values("book__title","book__publish__name")
    print(mix4)

  return HttpResponse("ok")

 

聚合查詢和分組查詢

def add(request):
    # ###################聚合查詢和分組查詢##################
    # 聚合函數:aggregate
    from django.db.models import Avg
    price_avg = Book.objects.all().aggregate(Avg("price"))  # 所有書籍的平均價格;字典的形式
    print(price_avg)
    # {'price__avg': 166.666667}
    price_avg2 = Book.objects.all().aggregate(c=Avg("price"))
    print(price_avg2)
    # {'c': 166.666667}

    # 分組函數:annotate()
    # 統計每本書的作者個數
    from django.db.models import Count
    author_num = Book.objects.all().annotate(c=Count("authors__name"))  # authors__name表示author表中的name字段(正向查詢);annotate的作用是給前面所有的對象添加一個獨立的“注釋”(相當於給前面的所有對象添加了一個新的屬性)
    print(author_num)
    for obj in author_num:
        print(obj.title,obj.c)

    # 統計每個作者出版書籍的最高價格
    from django.db.models import Max
    max_price = Author.objects.all().annotate(hp=Max("book__price")).values("name","hp")  # book__price:反向查詢;.values("name","hp") 鏈式操作
    print(max_price)
  
  return HttpResponse("ok")

aggregate(*args,**kwargs)QuerySet 的一個終止子句,意思是說,它返回一個包含一些鍵值對的字典。鍵的名稱是聚合值的標識符,值是計算出來的聚合值。鍵的名稱是按照字段和聚合函數的名稱自動生成出來的。如果你想要為聚合值指定一個名稱,可以向聚合子句提供它(kwargs的形式)。

而annotate()函數可以為QuerySet中的每個對象生成一個獨立的摘要,輸出的結果仍然是一個QuerySet對象,能夠調用filter()、order_by()甚至annotate()

總結 :跨表分組查詢本質就是將關聯表join成一張表,再按單表的思路進行分組查詢。

 

分組查詢:

1. 單表的分組查詢:

models.py

from django.db import models

# Create your models here.
class Emp(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    salary = models.DecimalField(max_digits=8,decimal_places=2)
    dep = models.CharField(max_length=32)
    province = models.CharField(max_length=32)

views.py

from django.shortcuts import render,HttpResponse

# Create your views here.
from app01.models import *
from django.db.models import Avg,Max,Min,Count

def query(request):
    # 單表分組查詢:
    # 查詢每個部門的名稱以及平均薪水
    ret1 = Emp.objects.values("dep")
    print(ret1)  # <QuerySet [{'dep': '教學部'}, {'dep': '保安部'}, {'dep': '教學部'}]>
    ret = Emp.objects.values("dep").annotate(avg_salary=Avg("salary"))  # sql: select dep,Avg(salary) from emp group by dep
    print(ret)  # <QuerySet [{'dep': '保安部', 'avg_salary': 5000.0}, {'dep': '教學部', 'avg_salary': 6000.0}]>

    # 單表分組查詢的ORM語法: 單表模型.objects.values("group by的字段").annotate(聚合函數("要統計的字段"))  # annotate()前面 select 的是哪個字段,就是以哪個字段分組
    """
    # 補充:
    Emp.objects,all() === SQL: select * from emp
    Emp.objects.all().values("name") === Emp.objects.values("name") === SQL: select name from emp
    所以 .values("xxx") 就相當於 SQL的 select xxx
    Emp.objects.annotate(avg_salary=Avg("salary")) 就是獲取所有salary的平均值
    
    在單表下,按照主鍵進行group by分組是沒有意義的;所以單表分組時不要加 .all() 
    """

    return HttpResponse("ok")

2. 多表的分組查詢:

views.py

from django.shortcuts import render,HttpResponse

# Create your views here.

from app01.models import *

def add(request):
    # ##############多表(跨表)分組查詢:##############
    # 查詢每個出版社的名稱以及出版的書籍個數(先join再進行分組)
    # sql:select publish.name,Count(book.id) from Publish inner join Book on book.publish_id=publish.id
    #           group by publish.id    # (與下面的聯表方式不等同)
    # 用ORM語法進行跨表分組查詢
    # 方式1. 跨表查詢時可利用主鍵進行 .values(主鍵)
    # ret = Publish.objects.values("nid").annotate(c=Count("book__title"))
    # print(ret)

    # 方式2
    # ret = Publish.objects.values("name").annotate(c=Count("book__title"))
    # print(ret)

    # 方式3(重點):Publish.objects.values("nid").annotate(c=Count("book__title")):這是一個特殊的QuerySet,里面只有兩對鍵值;如果這兩對鍵值不夠用,可以再通過 .values(字段)的方式去獲取想要的字段值,因為其基表是 Publish;不加 ,value()就是默認顯示默認的那兩個字段"nid"和"c"
    ret = Publish.objects.values("nid").annotate(c=Count("book__title")).values("name","c")  # 推薦這種方式
    print(ret)
    # < QuerySet[{'name': '人民出版社', 'c': 3}] >
    # 注意:ret = Publish.objects.values("nid").annotate(c=Count("book__title")):返回結果里面存的仍然是對象,只不過顯示的是字典的格式,所以其后面還能繼續 .values(Publish的其它字段或者 annotate的字段)

    # 查詢每一個作者的名字以及出版過的書籍的最高價格
    # sql: select app01_author.name,Max(app01_book.price) from app01_book inner join app01_book_authors
    #           on app01.book.nid = app01_book_authors.book_id
    #       inner join app01_author on app01_author.nid = app01_book_authors.author_id
    #       group by app01_author.nid
    #       按照作者表的主鍵nid進行 group by
    ret = Author.objects.values("pk").annotate(max_price=Max("book__price")).values("name","max_price")   # pk代表主鍵
    print(ret)

    # 示例: 查詢每一個書籍名稱以及對應的作者個數
    ret = Book.objects.values("pk").values(author_num=Count("authors__name")).values("title","author_num")
    print(ret)

    # #################### 跨表分組查詢擴展 #########################
    # 查詢每個出版社的名稱以及出版的書籍個數
    ret3 = Publish.objects.all().annotate(c=Count("book__title")).values("name","c")  # 這種方式也可以,因為join之后 group by publish.nid 和 group by publish.nid,publish.name,publish.city,publish.email沒有任何區別
    print(ret3)  # <QuerySet [{'name': '人民出版社', 'c': 3}]>
    ret4 = Publish.objects.all().annotate(c=Count("book__title"))
    print(ret4)  # <QuerySet [<Publish: Publish object (1)>]>
    ret5 = Publish.objects.annotate(c=Count("book__title"))
    print(ret5)  # <QuerySet [<Publish: Publish object (1)>]>

    """
    跨表的分組查詢模型總結:
        “每一個”的表模型.objects.values("pk").annotate(聚合函數(關聯表__統計的字段)).values(需要顯示的字段)
        “每一個”的表模型.objects.annotate(聚合函數(關聯表__統計的字段)).values(需要顯示的字段)
    """

    # #################練習#############
    # 1.統計每一本以py開頭的書籍的作者個數
    ret6 = Book.objects.filter(title__startswith="py").values("pk").annotate(author_num=Count("authors__name")).values("title","author_num")  # 先過濾出來符合條件的對象再進行分組 # 此 filter 相當於 sql語句的 where過濾方法
    print(ret6)

    # 2. 統計不只一個作者的書籍:這個過濾條件可分解為:統計每本書的作者個數,然后過濾出作者個數大於1的書籍
    ret7 = Book.objects.values("pk").annotate(author_num=Count("authors__name")).filter(author_num__gt=1).values("title","author_num")  # 先進行(分組)統計,再過濾  # 此filter相當於 sql語句的 having過濾方法(分組之后再過濾)
    print(ret7)

    return HttpResponse("ok")

 

F查詢和Q查詢

views.py

from django.shortcuts import render,HttpResponse

# Create your views here.

from app01.models import *

def add(request):
    # ###########F查詢和Q查詢#################
    # F查詢:更新數據庫字段
    # 查詢評論數大於閱讀數的書籍
    from django.db.models import F,Q
    ret8 = Book.objects.filter(comment_num__gt=F("read_num"))
    print(ret8)
    # 比較兩個字段時可利用F函數;如上面的示例中,.filter()中不能直接寫 comment_num__gt=read_num

    # 所有書籍的價格提升10元錢
    Book.objects.all().update(price=F("price")+10)
    # 當 = 右邊用到字段的值時,就利用 F("字段") 的方法

    # Q查詢:構造復雜條件
    # 查詢名字為紅樓夢且價格為210的書籍
    ret9 = Book.objects.filter(title="紅樓夢",price=210)  # 這句代碼的含義為: 名字為紅樓夢 且 價格為210; filter()里面的 逗號 是 “且”
    print(ret9)
    # 查詢名字為紅樓夢或價格為210的書籍
    ret10 = Book.objects.filter(Q(title="紅樓夢")|Q(price=210))  # 一個Q()就表示一個條件,Q和Q之間的“與或非”關系用 &、|、~ 來表示;多個Q可以放在一個括號 () 內當做一個整體,再去的其它的Q進行邏輯組合
    print(ret10)

    # 名字不為紅樓夢的書籍
    ret11 = Book.objects.filter(~Q(title="紅樓夢"))
    print(ret11)
    # 注意:如果 filter()中既有Q 又有鍵值對,那么一定要先在filter()中放入Q,再放入鍵值對

    return HttpResponse("ok")

 

基於多表的圖書管理系統

目錄結構圖:

urls.py

from django.contrib import admin
from django.urls import path,re_path

from app01 import views

urlpatterns = [
    path('admin/', admin.site.urls),
    re_path(r"book/add/$",views.add_book),
    re_path(r"books/$",views.books),
    re_path(r"books/(\d+)/edit/$",views.edit_book),
    re_path(r"books/(\d+)/delete/$",views.delete_book)
]

 

views.py

from django.shortcuts import render,HttpResponse,redirect

from app01.models import *

# Create your views here.
def add_book(request):

    if request.method == "POST":
        title = request.POST.get("title")
        price = request.POST.get("price")
        pub_date = request.POST.get("pub_date")
        publish_id = request.POST.get("publish_id")
        # 對於 type="checkbox"的<input> 和 多選的 <select multiple>,前端提交到后端的是一個列表;想要獲取到這個列表,需要用 request.POST.getlist() 的方法( getlist())
        authors_id_list = request.POST.getlist("authors_id_list")
        print(authors_id_list) # ['1', '2', '3']

        # 為Book表添加記錄
        book_obj = Book.objects.create(title=title,price=price,publishDate=pub_date,publish_id=publish_id)
        print(book_obj) # Book object (1)

        # 對於添加多對多表的數據,由於邦定多對多關系的表是Django為我們自動生成的(如:app01_book_author表),這個表我們不能直接用;所以我們需要調用一個接口去添加多對多的數據:多對多的基表的對象.多對多的字段.add(),add()中可以是一個個另外一張表(表2)中的對象,也可以是一個列表中包含的多個表2 中的對象(前面要加*),也可以是一個列表中包含表2的主鍵(前面也要加*)
        book_obj.authors.add(*authors_id_list)  # 通過這個接口可以在邦定多對多關系表(app01_book_author)中添加記錄

        return redirect("/books/")

    # 獲取Publish表和Author表中的全部對象列表;用於動態生成相應的 <option>
    publish_list = Publish.objects.all()
    author_list = Author.objects.all()

    return render(request,"addbook.html",{"publish_list":publish_list,"author_list":author_list})

def books(request):

    book_list = Book.objects.all()

    return render(request,"books.html",{"book_list":book_list})

def edit_book(request,edit_book_id):
    # 獲取到相應的書籍對象
    edit_book_obj = Book.objects.filter(pk=edit_book_id).first()
    if request.method == "POST":
        title = request.POST.get("title")
        price = request.POST.get("price")
        pub_date = request.POST.get("pub_date")
        publish_id = request.POST.get("publish_id")
        authors_id_list = request.POST.getlist("authors_id_list")

        # 先更新Book表(此處沒有更新 Book.authors 字段)
        Book.objects.filter(pk=edit_book_id).update(title=title,price=price,publishDate=pub_date,publish_id=publish_id)
        # 更新作者信息(app01_book_author表)
        """
        方式一:app01_book_author表 先clear()再add()
        # 先清除和這本書有關的作者記錄
        edit_book_obj.authors.clear()
        # 然后添加和這本書關聯的作者記錄
        edit_book_obj.authors.add(*authors_id_list)
        """
        # 方式二:set()方法:set()方法會先執行 clear()操作, 再執行add()操作  # .add()是綁定關系,.set()是先清空關系(.clear()),再綁定關系(.add())
        edit_book_obj.authors.set(authors_id_list)  # set()中的參數放一個列表即可,無需再加*

        return redirect("/books/")

    publish_list = Publish.objects.all()
    author_list = Author.objects.all()

    return render(request,"edit_book.html",{"edit_book_obj":edit_book_obj,"publish_list":publish_list,"author_list":author_list})

def delete_book(request,delete_book_id):

    Book.objects.filter(pk=delete_book_id).delete()

    return redirect("/books/")

 

models.py

from django.db import models

# Create your models here.
class Author(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    age = models.IntegerField()


class Publish(models.Model):
    nid = models.AutoField(primary_key=True)
    name = models.CharField(max_length=32)
    city = models.CharField(max_length=32)
    email = models.EmailField()

class Book(models.Model):

    nid = models.AutoField(primary_key=True)
    title = models.CharField( max_length=32)
    publishDate=models.DateField()
    price=models.DecimalField(max_digits=5,decimal_places=2)  # 最大為: 999.99

    publish = models.ForeignKey(to="Publish",to_field="nid",on_delete=models.CASCADE)
    authors = models.ManyToManyField(to="Author")

 

books.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bs/css/bootstrap.min.css">
</head>
<body>
<h3>添加書籍</h3>
<div class="container">
    <div class="row">
        <div class="col-md-8 col-md-offset-2">
            <a href="/book/add/" class="btn btn-primary">添加書籍</a>
            <table class="table table-bordered table-hover table-striped">
                <thead>
                    <tr>
                        <th>編號</th>
                        <th>書籍名稱</th>
                        <th>價格</th>
                        <th>出版時間</th>
                        <th>出版社</th>
                        <th>作者</th>
                        <th>編輯操作</th>
                        <th>刪除操作</th>
                    </tr>
                </thead>
                <tbody>
                    {% for book in book_list %}
                        <tr>
                            {# forloop.counter表示從1開始計數 #}
                            <td>{{ forloop.counter }}</td>
                            <td>{{ book.title }}</td>
                            <td>{{ book.price }}</td>
                            {# 獲取時間時需要利用date做一個時間過濾 #}
                            <td>{{ book.publishDate|date:"Y-m-d" }}</td>
                            {# 利用book查詢publish:正向查詢按字段 #}
                            <td>{{ book.publish.name }}</td>
                            <td> {# 通過 book.authors.all 能獲取到所有的多對多關系的 author對象 #} {# 但不能直接把對象顯示到前端,需要用 author.name的方式去顯示 #}
                                {% for author in book.authors.all %}
                                    {# forloop.last表示循環的最后一次 #}
                                    {% if forloop.last %}
                                    <span>{{ author.name }}</span>
                                        {% else %}
                                        <span>{{ author.name }}</span>,
                                    {% endif %}
                                {% endfor %}
                            </td>
                            <td>
                                {# 把每個book對象的主鍵添加到對應編輯、刪除鏈接的路徑中 #}
                                <a href="/books/{{ book.pk }}/edit/" class="btn btn-warning">編輯</a>
                            </td>
                            <td><a href="/books/{{ book.pk }}/delete/" class="btn btn-danger">刪除</a></td>
                        </tr>
                    {% endfor %}

                </tbody>
            </table>
        </div>
    </div>
</div>

</body>
</html>

 

addbook.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bs/css/bootstrap.min.css">
</head>
<body>
<h3>添加書籍</h3>
<div class="container">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <form action="" method="post">
                {% csrf_token %}
                <div class="form-group">
                    <label for="">書籍名稱</label>
                    {# 如果沒寫 value屬性,則按輸入框中的數據作為其 value值;如果寫了value屬性,value的值就是該value屬性的值 #}
                    <input type="text" name="title" class="form-control">
                </div>
                <div class="form-group">
                    <label for="">價格</label>
                    <input type="text" name="price" class="form-control">
                </div>
                <div class="form-group">
                    <label for="">出版日期</label>
                    <input type="date" name="pub_date" class="form-control">
                </div>
                <div class="form-group">
                    {# 出版社為單選下拉列表 #}
                    <label for="">出版社</label>
                    <select name="publish_id" id="" class="form-control">
                        {# 此處需要動態的去生成 <option>;根據 Publish表中有多少個對象,就生成多少個<option> #}
                        {% for publish in publish_list %}
                            {# 前端顯示為 Publish的name,但提交時為 Publish的主鍵 #} <option value="{{ publish.pk }}">{{ publish.name }}</option>
                        {% endfor %}

                    </select>
                </div>
                <div class="form-group">
                    <label for="">作者</label>
                    {# 作者為多選下拉列表 #}
                    <select name="authors_id_list" id="" class="form-control" multiple>
                        {% for author in author_list %}
                            <option value="{{ author.pk }}">{{ author.name }}</option>
                        {% endfor %}
                    </select>
                </div>
                <input type="submit" class="btn btn-default">
            </form>
        </div>
    </div>
</div>

</body>
</html>

edit_book.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="/static/bs/css/bootstrap.min.css">
</head>
<body>
<h3>添加書籍</h3>
<div class="container">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <form action="" method="post">
                {% csrf_token %}
                <div class="form-group">
                    <label for="">書籍名稱</label>
                    {# 把書籍對象的title賦值給 value屬性 #}
                    <input type="text" name="title" class="form-control" value="{{ edit_book_obj.title }}">
                </div>
                <div class="form-group">
                    <label for="">價格</label>
                    <input type="text" name="price" class="form-control" value="{{ edit_book_obj.price }}">
                </div>
                <div class="form-group">
                    <label for="">出版日期</label>
                    <input type="date" name="pub_date" class="form-control" value="{{ edit_book_obj.publishDate|date:"Y-m-d" }}">
                </div>
                <div class="form-group">
                    <label for="">出版社</label>
                    <select name="publish_id" id="" class="form-control"> {# publish為Publish表中的一個publish對象 #}
                        {% for publish in publish_list %}
                            {# 如果正在編輯的這本書籍對象的publish和循環到的publish對象相同,就為這個 <option>標簽添加 selected屬性 #}
                            {% if edit_book_obj.publish == publish %}
                                <option value="{{ publish.pk }}" selected>{{ publish.name }}</option>
                                {% else %}
                                <option value="{{ publish.pk }}">{{ publish.name }}</option>
                            {% endif %}
                        {% endfor %}

                    </select>
                </div>
                <div class="form-group">
                    <label for="">作者</label>
                    <select name="authors_id_list" id="" class="form-control" multiple>
                        {% for author in author_list %}
                            {# 如果循環到的這個author對象,在這本書籍關聯的所有作者對象的集合中(QuerySet),那就為這個 <option>添加 selected #}
                            {% if author in edit_book_obj.authors.all %}
                                <option value="{{ author.pk }}" selected>{{ author.name }}</option>
                                {% else %}
                                <option value="{{ author.pk }}">{{ author.name }}</option>
                            {% endif %}

                        {% endfor %}
                    </select>
                </div>
                <input type="submit" class="btn btn-default">
            </form>
        </div>
    </div>
</div>

</body>
</html>

 

ORM補充:

1. 只取某列/不取某列:

# only()用法 : 例如:
modelsUserInfo.objects.all().only("id","name")  # 返回 queryset, queryset中是一個個的記錄對象,如 [obj1,obj2,...] , 但 這些對象中並不是包含全部字段,而是只有 "id","name" 這兩列、這兩個字段(如果 obj.age也能獲取到,但性能會降低,所以不推薦這樣)

modelsUserInfo.objects.all().defer("id","name")  # 和 only() 相反, defer() 表示排除某幾列(字段)

 

2. select_related() 和 prefetch_related():

select_related():多次做連表;

prefetch_related():多次做單表查詢

# 需求: 打印所有用戶姓名以及部門名稱

# select_related():

class Depart:
    title = ...

class User:
    name = ...
    dp = ForeignKey(Depart)

# select_related() 是 主動創建關聯關系; 下面 result 獲取的SQL等價於: select * from User left join Depart on User.dp_id=Depart.id
result = User.objects.all().selected_related('dp')  # 在做聯表查詢的時候,如果有FK或者one2one,就可以利用 selected_related() 在性能上有所提高;只支持FK和one2one
for item in result:
    print(item.name,item.dp.title)


# prefetch_related():
# select * from User;
# 通過python代碼獲取: dp_id = [...]
# from * from depart where id in dp_id;
result = User.objects.all().selected_related('dp')  # 需要聯表特別多的時候,prefetch_related()的性能會比 selected_related()的高; 除了FK和one2one外,還支持m2m
for item in result:
    print(item.name,item.dp.title)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM