day33-Django3.2(二)

死不悔改奇男子發表於2024-07-02

四、檢視

django的檢視主要有2種,分別是函式檢視類檢視.現在剛開始學習django,我們先學習函式檢視(FBV),後面再學習類檢視[CBV].

4.1、請求方式

web專案執行在http協議下,預設肯定也支援使用者透過不同的http請求傳送資料來。django支援讓客戶端只能透過指定的Http請求來訪問到專案的檢視

home/views.py,程式碼:

# 讓使用者傳送POST才能訪問的內容
from django.views.decorators.http import require_http_methods
@require_http_methods(["POST"])
def login(request):
    return HttpResponse("登入成功!")

路由繫結,demo/urls.py,程式碼:

from django.contrib import admin
from django.urls import path
from home.views import index
urlpatterns = [
    path('admin/', admin.site.urls),
    path("index", index),
    path("login", login),
]

透過瀏覽器,訪問效果http://127.0.0.1:8090/login:
image

4.2、請求物件

django將請求報文中的請求行、首部資訊、內容主體封裝成 HttpRequest 類中的屬性。 除了特殊說明的之外,其他均為只讀的。

(1)請求方式

print(request.method)

(2)請求資料

# 1.HttpRequest.GET:一個類似於字典的物件,包含 HTTP GET 的所有引數。詳情請參考 QueryDict 物件。

# 2.HttpRequest.POST:一個類似於字典的物件,如果請求中包含表單資料,則將這些資料封裝成 QueryDict 物件。
   # 注意:鍵值對的值是多個的時候,比如checkbox型別的input標籤,select標籤,需要用:         		 request.POST.getlist("hobby")
    
# 3.HttpRequest.body:一個字串,代表請求報文的請求體的原資料。

(3)請求路徑

# HttpRequest.path:表示請求的路徑元件(不含get引數)
# HttpRequest.get_full_path():含引數路徑

(4)請求頭

# HttpRequest.META:一個標準的Python 字典,包含所有的HTTP 首部。具體的頭部資訊取決於客戶端和伺服器

4.3、響應物件

響應物件主要有三種形式:

  • HttpResponse()
  • render()
  • redirect()

(1)HttpResponse()

Django伺服器接收到客戶端傳送過來的請求後,會將提交上來的這些資料封裝成一個 HttpRequest 物件傳給檢視函式。那麼檢視函式在處理完相關的邏輯後,也需要返回一個響應給瀏覽器。而這個響應,我們必須返回 HttpResponseBase 或者他的子類的物件。而 HttpResponse 則是 HttpResponseBase 用得最多的子類。

常用屬性:

  1. content:返回的內容。
  2. status:返回的HTTP響應狀態碼。
  3. content_type:返回的資料的MIME型別,預設為 text/html 。瀏覽器會根據這個屬性,來顯示資料。如果是 text/html ,那麼就會解析這個字串,如果 text/plain ,那麼就會顯示一個純文字。
  4. 設定響應頭: response['X-Access-Token'] = 'xxxx' 。
return HttpResponse("ok")
return HttpResponse("您訪問的資源不存在", status=404)
return HttpResponse("<h1>ok</h1>", content_type="text/plain")
res = HttpResponse("ok")
res["user"] = "yuan"
return res

JsonResponse類:

用來物件 dump 成 json 字串,然後返回將 json 字串封裝成 Response 物件返回給瀏覽器。並且他的 Content-Type 是 application/json 。示例程式碼如下:

from django.http import JsonResponse

def index(request):
    
    return JsonResponse({"title":"三國演義","price":199})

預設情況下 JsonResponse 只能對字典進行 dump ,如果想要對非字典的資料進行 dump ,那麼需要給 JsonResponse 傳遞一個 safe=False 引數。示例程式碼如下:

books = [{"title": "西遊記", "price": 99}, {"title": "水滸傳", "price": 199}]
return JsonResponse(books, safe=False)

(2)render()

render(request, template_name[, context])
#結合一個給定的模板和一個給定的上下文字典,並返回一個渲染後的 HttpResponse 物件。

引數:

 /*
 request: 用於生成響應的請求物件。
 template_name:要使用的模板的完整名稱,可選的引數
 context:新增到模板上下文的一個字典,
          預設是一個空字典。如果字典中的某個值是可呼叫的,檢視將在渲染模板之前呼叫它。
          */

render方法就是將一個模板頁面中的模板語法進行渲染,最終渲染成一個html頁面作為響應體。

(3)redirect方法

當您使用Django框架構建Python Web應用程式時,您在某些時候必須將使用者從一個URL重定向到另一個URL,

透過redirect方法實現重定向。

引數可以是:

  • 一個絕對的或相對的URL, 將原封不動的作為重定向的位置.
  • 一個url的別名: 可以使用reverse來反向解析url

傳遞要重定向到的一個具體的網址

def my_view(request):
    ...
    return redirect("/some/url/")

當然也可以是一個完整的網址

def my_view(request):
    ...
    return redirect("http://www.baidu.com")

傳遞一個檢視的名稱

def my_view(request):
    ...
    return redirect(reverse("url的別名")) 

image

APPEND_SLASH的實現就是基於redirect

4.4、登入驗證案例

image

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

from users.views import index,login,auth
urlpatterns = [
    path("",index),
    path("login/",login),
    path("auth/",auth),
]


def login(request):

    return render(request,"users/login.html")


def auth(request):

    #  獲取資料
    print("request.POST:",request.POST)

    user = request.POST.get("user")
    pwd = request.POST.get("pwd")

    # 模擬資料校驗
    if user == "rain" and pwd == "123":
        # return HttpResponse("驗證透過")
        return redirect("/users/")
    else:
        # return HttpResponse("使用者名稱或者密碼錯誤")
        # return redirect("/users/login")
        msg = "使用者名稱或者密碼錯誤"
        return render(request,"users/login.html",{"msg":msg})
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>


<form action="/users/auth" method="post">
    使用者名稱<input type="text" name="user">
    密碼 <input type="password" name="pwd">
    <input type="submit"> <span style="color: red">{{ msg }}</span>
</form>
</body>
</html>

五、模板語法

模板引擎是一種可以讓開發者把服務端資料填充到html網頁中完成渲染效果的技術。它實現了把前端程式碼和服務端程式碼分離的作用,讓專案中的業務邏輯程式碼和資料表現程式碼分離,讓前端開發者和服務端開發者可以更好的完成協同開發。

靜態網頁:頁面上的資料都是寫死的,萬年不變

動態網頁:頁面上的資料是從後端動態獲取的(比如後端獲取當前時間;後端獲取資料庫資料然後傳遞給前端頁面)

Django框架中內建了web開發領域非常出名的一個DjangoTemplate模板引擎(DTL)。DTL官方文件

要在django框架中使用模板引擎把檢視中的資料更好的展示給客戶端,需要完成3個步驟:

  1. 在專案配置檔案中指定儲存模板檔案的模板目錄。一般模板目錄都是設定在專案根目錄或者主應用目錄下。

  2. 在檢視中基於django提供的渲染函式繫結模板檔案和需要展示的資料變數

  3. 在模板目錄下建立對應的模板檔案,並根據模板引擎內建的模板語法,填寫輸出檢視傳遞過來的資料。

配置模板目錄:在當前專案根目錄下建立了模板目錄templates. 然後在settings.py, 模板相關配置,找到TEMPLATES配置項,填寫DIRS設定模板目錄。

# 模板引擎配置
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            BASE_DIR / "templates",  # 路徑拼接
        ],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

5.1、簡單案例

為了方便接下里的演示內容,我這裡建立建立一個新的子應用tem

python manage.py startapp tem

settings.py,註冊子應用,程式碼:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'tem',   # 開發者建立的子應用,這填寫就是子應用的導包路徑
]

總路由載入子應用路由,urls.py,程式碼:

from django.contrib import admin
from django.urls import path,include
urlpatterns = [
    path('admin/', admin.site.urls),
    # path("路由字首/", include("子應用目錄名.路由模組"))
    path("users/", include("users.urls")),
    path("tem/", include("tem.urls")),
]

在子應用目錄下建立urls.py子路由檔案,程式碼如下:

"""子應用路由"""
from django.urls import path, re_path
from . import views

urlpatterns = [
    path("index", views.index),
]

tem.views.index,程式碼:

from django.shortcuts import render
def index(request):
    # 要顯示到客戶端的資料
	name = "hello DTL!"
    # return render(request, "模板檔案路徑",context={字典格式:要在客戶端中展示的資料})
	return render(request, "index.html",context={"name":name})

templates.index.html,程式碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>來自模板的內容</h1>
    <p>輸出變數name的值:{{ name }}</p>
</body>
</html>

5.2、render函式內部本質

image

from django.shortcuts import render
from django.template.loader import get_template
from django.http.response import HttpResponse

def index(request):
    name = "hello world!"
    # 1. 初始化模板,讀取模板內容,例項化模板物件
    # get_template會從專案配置中找到模板目錄,我們需要填寫的引數就是補全模板檔案的路徑
    template = get_template("index.html")
    # 2. 識別context內容, 和模板內容裡面的標記[標籤]替換,針對複雜的內容,進行正則的替換
    context = {"name": name}
    content = template.render(context, request)  # render中完成了變數替換成變數值的過程,這個過程使用了正則。
    print(content)
    # 3. 透過response響應物件,把替換了資料的模板內容返回給客戶端
    return HttpResponse(content)  # 上面程式碼的簡寫,直接使用 django.shortcuts.render
    # return render(request, "index.html",context={"name":name})
    # return render(request,"index3.html", locals())
    # data = {}
    # data["name"] = "xiaoming"
    # data["message"] = "你好!"
    # return render(request,"index3.html", data)
  1. DTL模板檔案與普通html檔案的區別在哪裡?

DTL模板檔案是一種帶有特殊語法的HTML檔案,這個HTML檔案可以被Django編譯,可以傳遞引數進去,實現資料動態化。在編譯完成後,生成一個普通的HTML檔案,然後傳送給客戶端。

  1. 開發中,我們一般把開發中的檔案分2種,分別是靜態檔案和動態檔案。
* 靜態檔案,資料儲存在當前檔案,不需要經過任何處理就可以展示出去。普通html檔案,圖片,影片,音訊等這一類檔案叫靜態檔案。
* 動態檔案,資料並不在當前檔案,而是要經過服務端或其他程式進行編譯轉換才可以展示出去。 編譯轉換的過程往往就是使用正則或其他技術把檔案內部具有特殊格式的變數轉換成真實資料。 動態檔案,一般資料會儲存在第三方儲存裝置,如資料庫中。django的模板檔案,就屬於動態檔案。

5.3、模板語法

  1. 變數渲染(深度查詢、過濾器)

    {{val}}
    {{val|filter_name:引數}}
    
  2. 標籤

    {% tag_name %}
    
  3. 巢狀和繼承

5.3.1、變數渲染之深度查詢

def index(request):
    name = "root"
    age = 13
    sex = True
    lve = ["swimming", "shopping", "coding", "game"]
    bookinfo = {"id": 1, "price": 9.90, "name": "python3天入門到掙扎", }
    book_list = [
        {"id": 10, "price": 9.90, "name": "python3天入門到掙扎", },
        {"id": 11, "price": 19.90, "name": "python7天入門到垂死掙扎", },
    ]
    return render(request, 'index.html', locals())

模板程式碼,templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <p>name={{ name }}</p>
    <p>{{ age }}</p>
    <p>{{ sex }}</p>
    <p>列表成員</p>
    <p>{{ lve }}</p>
    <p>{{ lve.0 }}</p>
    <p>{{ lve | last }}</p>

    <p>字典成員</p>
    <p>id={{ bookinfo.id }}</p>
    <p>price={{ bookinfo.price }}</p>
    <p>name={{ bookinfo.name }}</p>

    <p>複雜列表</p>
    <p>{{ book_list.0.name }}</p>
    <p>{{ book_list.1.name }}</p>

</body>
</html>

透過句點符號深度查詢

tem.urls,程式碼:

"""子應用路由"""
from django.urls import path, re_path
from . import views

urlpatterns = [
	# ....
    path("index", views.index),
]

5.3.2、變數渲染之內建過濾器

語法:

{{obj|過濾器名稱:過濾器引數}}

內建過濾器

過濾器 用法 程式碼
last 獲取列表/元組的最後一個成員 {{liast | last}}
first 獲取列表/元組的第一個成員 {{list|first}}
length 獲取資料的長度 {{list | length}}
defualt 當變數沒有值的情況下, 系統輸出預設值, {{str|default="預設值"}}
safe 讓系統不要對內容中的html程式碼進行實體轉義 {{htmlcontent| safe}}
upper 字母轉換成大寫 {{str | upper}}
lower 字母轉換成小寫 {{str | lower}}
title 每個單詞首字母轉換成大寫 {{str | title}}
date 日期時間格式轉換 `{{ value
cut 從內容中擷取掉同樣字元的內容 {{content | cut:"hello"}}
list 把內容轉換成列表格式 {{content | list}}
add 加法 {{num| add}}
filesizeformat 把檔案大小的數值轉換成單位表示 {{filesize | filesizeformat}}
join 按指定字元拼接內容 {{list| join("-")}}
random 隨機提取某個成員 {list | random}}
slice 按切片提取成員 {{list | slice:":-2"}}
truncatechars 按字元長度擷取內容 {{content | truncatechars:30}}
truncatewords 按單詞長度擷取內容 同上

過濾器的使用

檢視程式碼 home.views.py;

def index(request):
	"""過濾器 filters"""
	content = "<a href='http://www.luffycity.com'>路飛學城</a>"
	# content1 = '<script>alert(1);</script>'
	from datetime import datetime
	now = datetime.now()
	content2= "hello wrold!"
	return render(request,"index.html",locals())

# 模板程式碼,templates/index.html:
 
    {{ content | safe }}
    {{ content1 | safe }}

    {# 過濾器本質就是函式,但是模板語法不支援小括號呼叫,所以需要使用:號分割引數 #}
    <p>{{ now | date:"Y-m-d H:i:s" }}</p>
    <p>{{ conten1 | default:"預設值" }}</p>
    {# 一個資料可以連續呼叫多個過濾器 #}
    <p>{{ content2 | truncatechars:6 | upper }}</p>

5.3.3、自定義過濾器

雖然官方已經提供了許多內建的過濾器給開發者,但是很明顯,還是會有存在不足的時候。例如:希望輸出使用者的手機號碼時, 13912345678 ---->> 139*****678,這時我們就需要自定義過濾器。要宣告自定義過濾器並且能在模板中正常使用,需要完成2個前置的工作:

# 1. 當前使用和宣告過濾器的子應用必須在setting.py配置檔案中的INSTALLED_APPS中註冊了!!!
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'home',
]


# 2. 自定義過濾器函式必須被 template.register進行裝飾使用.
#    而且過濾器函式所在的模組必須在templatetags包裡面儲存
   
# 在home子應用下建立templatetags包[必須包含__init__.py], 在包目錄下建立任意py檔案
# home.templatetags.my_filters.py程式碼:

from django import template
register = template.Library()

# 自定義過濾器
@register.filter("mobile")
def mobile(content):
	return content[:3]+"*****"+content[-3:]

# 3. 在需要使用的模板檔案中頂部使用load標籤載入過濾器檔案my_filters.py並呼叫自定義過濾器
# home.views.py,程式碼:

def index(request):
	"""自定義過濾器 filters"""
    
	moblie_number = "13312345678"
	return render(request,"index2.html",locals())


# templates/index2.html,程式碼:

{% load my_filters %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    {{ moblie_number| mobile }}
</body>
</html>

5.3.4、標籤

(1)if 標籤

檢視程式碼,tem.views.py:

def index(request):
    name = "xiaoming"
    age = 19
    sex = True
    lve = ["swimming", "shopping", "coding", "game"]
    user_lve = "sleep"
    bookinfo = {"id": 1, "price": 9.90, "name": "python3天入門到掙扎", }
    book_list = [
        {"id": 10, "price": 9.90, "name": "python3天入門到掙扎", },
        {"id": 11, "price": 19.90, "name": "python7天入門到垂死掙扎", },
    ]
    return render(request, 'index.html', locals())

模板程式碼,templates/index.html,程式碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
{# 來自django模板引擎的註釋~~~~ #}
{% comment %}
多行註釋,comment中的所有內容全部都不會被顯示出去
{% endcomment %}

{#    {% if age < 18 %}#}
{#        <p>你還沒成年,不能訪問我的網站!</p>#}
{#    {% endif %}#}
{##}
{#    {% if name == "root" %}#}
{#        <p>超級使用者,歡迎回家!</p>#}
{#    {% else %}#}
{#        <p>{{ name }},你好,歡迎來到xx網站!</p>#}
{#    {% endif %}#}


    {% if user_lve == lve.0 %}
        <p>那麼巧,你喜歡游泳,海里也能見到你~</p>
    {% elif user_lve == lve.1 %}
        <p>那麼巧,你也來收快遞呀?~</p>
    {% elif user_lve == lve.2 %}
        <p>那麼巧,你也在老男孩?</p>
    {% else %}
        <p>看來我們沒有緣分~</p>
    {% endif %}
</body>
</html>

路由程式碼:

"""子應用路由"""
from django.urls import path, re_path
from . import views

urlpatterns = [
	# ....
    path("index", views.index),
]

(2)for標籤

檢視程式碼, home.views.py:

def index7(request):
    book_list1 = [
        {"id": 11, "name": "python基礎入門", "price": 130.00},
        {"id": 17, "name": "Go基礎入門", "price": 230.00},
        {"id": 23, "name": "PHP基礎入門", "price": 330.00},
        {"id": 44, "name": "Java基礎入門", "price": 730.00},
        {"id": 51, "name": "C++基礎入門", "price": 300.00},
        {"id": 56, "name": "C#基礎入門", "price": 100.00},
        {"id": 57, "name": "前端基礎入門", "price": 380.00},
    ]
    return render(request, 'index.html', locals())

template/index.html,程式碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <table width="800" align="center" border="1">
        <tr>
            <td>序號</td>
            <td>id</td>
            <td>標題</td>
            <td>價格</td>
        </tr>
        {# 多行編輯,alt+滑鼠鍵,alt不要鬆開,左鍵點選要編輯的每一行 #}
{#        {% for book in book_list1 %}#}
{#            <tr>#}
{#                <td>{{ book.id }}</td>#}
{#                <td>{{ book.name }}</td>#}
{#                <td>{{ book.price }}</td>#}
{#            </tr>#}
{#        {% endfor %}#}

{# 建議不要直接使用for迴圈一維字典,此處使用僅僅展示for巢狀for而已 #}
{#        {% for book in book_list1 %}#}
{#            <tr>#}
{#                {% for field,value in book.items %}#}
{#                <td>{{ field }} == {{ value }}</td>#}
{#                {% endfor %}#}
{#            </tr>#}
{#        {% endfor %}#}

{#        {% for book in book_list1 %}#}
{#            <tr>#}
{#                <td>{{ book.id }}</td>#}
{#                <td>{{ book.name }}</td>#}
{#                {% if book.price > 200 %}#}
{#                    <td bgcolor="#ff7f50">{{ book.price }}</td>#}
{#                {% else %}#}
{#                    <td>{{ book.price }}</td>#}
{#                {% endif %}#}
{#            </tr>#}
{#        {% endfor %}#}

        {# 逆向迴圈資料 #}
{#        {% for book in book_list1 reversed %}#}
{#            <tr>#}
{#                <td>{{ book.id }}</td>#}
{#                <td>{{ book.name }}</td>#}
{#                {% if book.price > 200 %}#}
{#                    <td bgcolor="#ff7f50">{{ book.price }}</td>#}
{#                {% else %}#}
{#                    <td>{{ book.price }}</td>#}
{#                {% endif %}#}
{#            </tr>#}
{#        {% endfor %}#}

        {% for book in book_list1 %}
            <tr>
{#                <td>{{ forloop.counter }}</td>#}
{#                <td>{{ forloop.counter0 }}</td>#}
{#                <td>{{ forloop.revcounter }}</td>#}
{#                <td>{{ forloop.revcounter0 }}</td>#}
{#                <td>{{ forloop.first }}</td>#}
                <td>{{ forloop.last }}</td>
                <td>{{ book.id }}</td>
                <td>{{ book.name }}</td>
                {% if book.price > 200 %}
                    <td bgcolor="#ff7f50">{{ book.price }}</td>
                {% else %}
                    <td>{{ book.price }}</td>
                {% endif %}
            </tr>
        {% endfor %}

    </table>
</body>
</html>

路由程式碼:

"""子應用路由"""
from django.urls import path, re_path
from . import views

urlpatterns = [
	# ....
    path("index", views.index),
]

迴圈中, 模板引擎提供的forloop物件,用於給開發者獲取迴圈次數或者判斷迴圈過程的.

屬性 描述
forloop.counter 顯示迴圈的次數,從1開始
forloop.counter0 顯示迴圈的次數,從0開始
forloop.revcounter0 倒數顯示迴圈的次數,從0開始
forloop.revcounter 倒數顯示迴圈的次數,從1開始
forloop.first 判斷如果本次是迴圈的第一次,則結果為True
forloop.last 判斷如果本次是迴圈的最後一次,則結果為True
forloop.parentloop 在巢狀迴圈中,指向當前迴圈的上級迴圈

5.3.5、模板巢狀繼承

傳統的模板分離技術,依靠{% include "模板檔名"%}實現,這種方式,雖然達到了頁面程式碼複用的效果,但是由此也會帶來大量的碎片化模板,導致維護模板的成本上升.因此, Django框架中除了提供這種模板分離技術以外,還並行的提供了 模板繼承給開發者.

{% include "模板檔名"%}  # 模板嵌入
{% extends "base.html" %} # 模板繼承 

(1) 繼承父模板的公共內容

檢視, home.views.py程式碼:

def index(request):
	"""模板繼承"""
	return render(request,"index.html",locals())

子模板, templates/index.html

{% extends "base.html" %}

父模板, templates/base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>base.html的頭部</h1>
    <h1>base.html的內容</h1>
    <h1>base.html的腳部</h1>
</body>
</html>

(2) 個性展示不同於父模板的內容

{%block %} 獨立內容 {%endblock%}

{{block.super}}

檢視home.views.py, 程式碼:

def index(request):
	"""模板繼承"""

	return render(request,"index.html",locals())

def home(request):
	"""模板繼承"""
	return render(request,"home.html",locals())

路由 home.urls.py,程式碼:

from django.urls import path
from . import views
urlpatterns = [
	path("", views.index),
	path("home/", views.home),
]

子模板index.html,程式碼:

{% extends "base.html" %}
{% block title %}index3的標題{% endblock  %}
{% block content %}
    {{ block.super }} {# 父級模板同名block標籤的內容 #}
    <h1>index3.html的獨立內容</h1>
    {{ block.super }}
{% endblock %}

子模板home.html,程式碼:

{% extends "base.html" %}
{% block title %}home的標題{% endblock %}

父模板base.html,程式碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}{% endblock  %}</title>
</head>
<body>
    <h1>base.html的頭部</h1>
    {% block content %}
    <h1>base.html的內容</h1>
    {% endblock %}
    <h1>base.html的腳部</h1>
</body>
</html>
  • 如果你在模版中使用 {% extends %} 標籤,它必須是模版中的第一個標籤。其他的任何情況下,模版繼承都將無法工作。
  • 在base模版中設定越多的 {% block %} 標籤越好。請記住,子模版不必定義全部父模版中的blocks,所以,你可以在大多數blocks中填充合理的預設內容,然後,只定義你需要的那一個。多一點鉤子總比少一點好。
  • 為了更好的可讀性,你也可以給你的 {% endblock %} 標籤一個 名字 。例如:{``% block content``%``}``...``{``% endblock content``%``},在大型模版中,這個方法幫你清楚的看到哪一個  {% block %} 標籤被關閉了。
  • 不能在一個模版中定義多個相同名字的 block 標籤。

5.4、靜態檔案

開發中在開啟了debug模式時,django可以透過配置,允許使用者透過對應的url地址訪問django的靜態檔案。

setting.py,程式碼:

STATIC_ROOT = BASE_DIR / 'static'
STATIC_URL = '/static/'   # django模板中,可以引用{{STATIC_URL}}變數避免把路徑寫死。

總路由,urls.py,程式碼:

from django.views.static import serve as serve_static
urlpatterns = [
    path('admin/', admin.site.urls), 
    # 對外提供訪問靜態檔案的路由,serve_static 是django提供靜態訪問支援的對映類。依靠它,客戶端才能訪問到django的靜態檔案。
    path(r'static/<path:path>', serve_static, {'document_root': settings.STATIC_ROOT},),
]

注意:專案上線以後,關閉debug模式時,django預設是不提供靜態檔案的訪問支援,專案部署的時候,我們會透過收集靜態檔案使用nginx這種web伺服器來提供靜態檔案的訪問支援。

六、模型層(ORM)

Django中內嵌了ORM框架,不需要直接編寫SQL語句進行資料庫操作,而是透過定義模型類,操作模型類來完成對資料庫中表的增刪改查和建立等操作。
image

O是object,也就類物件的意思。

R是relation,翻譯成中文是關係,也就是關聯式資料庫中資料表的意思。

M是mapping,是對映的意思。

對映:

類:sql語句table表

類成員變數:table表中的欄位、型別和約束

類物件:sql表的表記錄

ORM的優點

  • 資料模型類都在一個地方定義,更容易更新和維護,也利於重用程式碼。

  • ORM 有現成的工具,很多功能都可以自動完成,比如資料消除、預處理、事務等等。

  • 它迫使你使用 MVC 架構,ORM 就是天然的 Model,最終使程式碼更清晰。

  • 基於 ORM 的業務程式碼比較簡單,程式碼量少,語義性好,容易理解。

  • 新手對於複雜業務容易寫出效能不佳的 SQL,有了ORM不必編寫複雜的SQL語句, 只需要透過操作模型物件即可同步修改資料表中的資料.

  • 開發中應用ORM將來如果要切換資料庫.只需要切換ORM底層對接資料庫的驅動【修改配置檔案的連線地址即可】

ORM 也有缺點

  • ORM 庫不是輕量級工具,需要花很多精力學習和設定,甚至不同的框架,會存在不同操作的ORM。
  • 對於複雜的業務查詢,ORM表達起來比原生的SQL要更加困難和複雜。
  • ORM運算元據庫的效能要比使用原生的SQL差。
  • ORM 抽象掉了資料庫層,開發者無法瞭解底層的資料庫操作,也無法定製一些特殊的 SQL。【自己使用pymysql另外操作即可,用了ORM並不表示當前專案不能使用別的資料庫操作工具了。】

我們可以透過以下步驟來使用django的資料庫操作

1. 配置資料庫連線資訊
2. 在models.py中定義模型類
3. 生成資料庫遷移檔案並執行遷移檔案[注意:資料遷移是一個獨立的功能,這個功能在其他web框架未必和ORM一塊的]
4. 透過模型類物件提供的方法或屬性完成資料表的增刪改查操作

6.1、配置資料庫連線

在settings.py中儲存了資料庫的連線配置資訊,Django預設初始配置使用sqlite資料庫。

  1. 使用MySQL資料庫首先需要安裝驅動程式

    pip install PyMySQL
    
  2. 在Django的工程同名子目錄的__init__.py檔案中新增如下語句

    from pymysql import install_as_MySQLdb
    install_as_MySQLdb() # 讓pymysql以MySQLDB的執行模式和Django的ORM對接執行
    

    作用是讓Django的ORM能以mysqldb的方式來呼叫PyMySQL。

  3. 修改DATABASES配置資訊

    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'HOST': '127.0.0.1',  # 資料庫主機
            'PORT': 3306,  # 資料庫埠
            'USER': 'root',  # 資料庫使用者名稱
            'PASSWORD': '123',  # 資料庫使用者密碼
            'NAME': 'student'  # 資料庫名字
        }
    }
    
  4. 在MySQL中建立資料庫

    create database student; # mysql8.0預設就是utf8mb4;
    create database student default charset=utf8mb4; # mysql8.0之前的版本
    
  5. 注意3: 如果想列印orm轉換過程中的sql,需要在settings中進行如下配置:

    LOGGING = {
        'version': 1,
        'disable_existing_loggers': False,
        'handlers': {
            'console':{
                'level':'DEBUG',
                'class':'logging.StreamHandler',
            },
        },
        'loggers': {
            'django.db.backends': {
                'handlers': ['console'],
                'propagate': True,
                'level':'DEBUG',
            },
        }
    }  
    

6.2、定義模型類

定義模型類

  • 模型類被定義在"子應用/models.py"檔案中。
  • 模型類必須直接或者間接繼承自django.db.models.Model類。

接下來以學生管理為例進行演示。[系統大概3-4表,學生資訊,課程資訊,老師資訊],建立子應用student,註冊子應用並引入子應用路由.

settings.py,程式碼:

INSTALLED_APPS = [
	# ...
    'student',
]

urls.py,總路由程式碼:

urlpatterns = [
    # 省略,如果前面有重複的路由,改動以下。
    path("student/", include("student.urls")),
]

在models.py 檔案中定義模型類。

from django.db import models
from datetime import datetime

# 模型類必須要直接或者間接繼承於 models.Model
class BaseModel(models.Model):
	"""公共模型[公共方法和公共欄位]"""
	# created_time = models.IntegerField(default=0, verbose_name="建立時間")
	created_time = models.DateTimeField(auto_now_add=True, verbose_name="建立時間")
	# auto_now_add 當資料新增時設定當前時間為預設值
	# auto_now= 當資料新增/更新時, 設定當前時間為預設值
	updated_time = models.DateTimeField(auto_now=True)
	class Meta(object):
		abstract = True # 設定當前模型為抽象模型, 當系統執行時, 不會認為這是一個資料表對應的模型.

class Student(BaseModel):
	"""Student模型類"""
	#1. 欄位[資料庫表欄位對應]
	SEX_CHOICES = (
		(0,"女"),
		(1,"男"),
		(2,"保密"),
	)

	# 欄位名 = models.資料型別(約束選項1,約束選項2, verbose_name="註釋")
	# SQL: id bigint primary_key auto_increment not null comment="主鍵",
    # id = models.AutoField(primary_key=True, null=False, verbose_name="主鍵") # django會自動在建立資料表的時候生成id主鍵/還設定了一個呼叫別名 pk

    # SQL: name varchar(20) not null comment="姓名"
    # SQL: key(name),
    name = models.CharField(max_length=20, db_index=True, verbose_name="姓名" )

    # SQL: age smallint not null comment="年齡"
    age = models.SmallIntegerField(verbose_name="年齡")

    # SQL: sex tinyint not null comment="性別"
    # sex = models.BooleanField(verbose_name="性別")
    sex = models.SmallIntegerField(choices=SEX_CHOICES, default=2)

    # SQL: class varchar(5) not null comment="班級"
    # SQL: key(class)
    classmate = models.CharField(db_column="class", max_length=5, db_index=True, verbose_name="班級")
    # SQL: description longtext default "" not null comment="個性簽名"
    description = models.TextField(default="", verbose_name="個性簽名")

	#2. 資料表結構資訊
	class Meta:
		db_table = 'tb_student'  # 指明資料庫表名,如果沒有指定表明,則預設為子應用目錄名_模型名稱,例如: users_student
		verbose_name = '學生資訊表'  # 在admin站點中顯示的名稱
		verbose_name_plural = verbose_name  # 顯示的複數名稱

	#3. 自定義資料庫操作方法
	def __str__(self):
		"""定義每個資料物件的顯示資訊"""
		return "<User %s>" % self.name

(1) 資料庫表名

模型類如果未指明表名db_table,Django預設以 小寫app應用名_小寫模型類名 為資料庫表名。

可透過db_table 指明資料庫表名。

(2) 關於主鍵

django會為表建立自動增長的主鍵列,每個模型只能有一個主鍵列。

如果使用選項設定某個欄位的約束屬性為主鍵列(primary_key)後,django不會再建立自動增長的主鍵列。

class Student(models.Model):
    # django會自動在建立資料表的時候生成id主鍵/還設定了一個呼叫別名 pk
    id = models.AutoField(primary_key=True, null=False, verbose_name="主鍵") # 設定主鍵

預設建立的主鍵列屬性為id,可以使用pk代替,pk全拼為primary key

(3) 屬性命名限制

  • 不能是python的保留關鍵字。

  • 不允許使用連續的2個下劃線,這是由django的查詢方式決定的。__ 是關鍵字來的,不能使用!!!

  • 定義屬性時需要指定欄位型別,透過欄位型別的引數指定選項,語法如下:

    屬性名 = models.欄位型別(約束選項, verbose_name="註釋")
    

(4)欄位型別

型別 說明
AutoField 自動增長的IntegerField,通常不用指定,不指定時Django會自動建立屬性名為id的自動增長屬性
BooleanField 布林欄位,值為True或False
NullBooleanField 支援Null、True、False三種值
CharField 字串,引數max_length表示最大字元個數,對應mysql中的varchar
TextField 大文字欄位,一般大段文字(超過4000個字元)才使用。
IntegerField 整數
DecimalField 十進位制浮點數, 引數max_digits表示總位數, 引數decimal_places表示小數位數,常用於表示分數和價格 Decimal(max_digits=7, decimal_places=2) ==> 99999.99~ 0.00
FloatField 浮點數
DateField 日期
引數auto_now表示每次儲存物件時,自動設定該欄位為當前時間。
引數auto_now_add表示當物件第一次被建立時自動設定當前。
引數auto_now_add和auto_now是相互排斥的,一起使用會發生錯誤。
TimeField 時間,引數同DateField
DateTimeField 日期時間,引數同DateField
FileField 上傳檔案欄位,django在檔案欄位中內建了檔案上傳儲存類, django可以透過模型的欄位儲存自動儲存上傳檔案, 但是, 在資料庫中本質上儲存的僅僅是檔案在專案中的儲存路徑!!
ImageField 繼承於FileField,對上傳的內容進行校驗,確保是有效的圖片

(5)約束選項

選項 說明
null 如果為True,表示允許為空,預設值是False。相當於python的None
blank 如果為True,則該欄位允許為空白,預設值是False。 相當於python的空字串,“”
db_column 欄位的名稱,如果未指定,則使用屬性的名稱。
db_index 若值為True, 則在表中會為此欄位建立索引,預設值是False。 相當於SQL語句中的key
default 預設值,當不填寫資料時,使用該選項的值作為資料的預設值。
primary_key 如果為True,則該欄位會成為模型的主鍵,預設值是False,一般不用設定,系統預設設定。
unique 如果為True,則該欄位在表中必須有唯一值,預設值是False。相當於SQL語句中的unique

注意:null是資料庫範疇的概念,blank是表單驗證範疇的

(6) 外來鍵

在設定外來鍵時,需要透過on_delete選項指明主表刪除資料時,對於外來鍵引用表資料如何處理,在django.db.models中包含了可選常量:

  • CASCADE 級聯,刪除主表資料時連通一起刪除外來鍵表中資料

  • PROTECT 保護,透過丟擲ProtectedError異常,來阻止刪除主表中被外來鍵應用的資料

  • SET_NULL 設定為NULL,僅在該欄位null=True允許為null時可用

  • SET_DEFAULT 設定為預設值,僅在該欄位設定了預設值時可用

  • SET() 設定為特定值或者呼叫特定方法,例如:

    from django.conf import settings
    from django.contrib.auth import get_user_model
    from django.db import models
    
    def get_sentinel_user():
        return get_user_model().objects.get_or_create(username='deleted')[0]
    
    class UserModel(models.Model):
        user = models.ForeignKey(
            settings.AUTH_USER_MODEL,
            on_delete=models.SET(get_sentinel_user),
        )
    
  • DO_NOTHING 不做任何操作,如果資料庫前置指明級聯性,此選項會丟擲IntegrityError異常

商品分類表

id category
1 蔬菜
2 電腦

商品資訊表

id goods_name cid
1 冬瓜 1
2 華為筆記本A1 2
3 茄子 1
  1. 當模型欄位的on_delete=CASCADE, 刪除蔬菜(id=1),則在外來鍵cid=1的商品id1和3就被刪除。

  2. 當模型欄位的on_delete=PROTECT,刪除蔬菜,mysql自動檢查商品資訊表,有沒有cid=1的記錄,有則提示必須先移除掉商品資訊表中,id=1的所有記錄以後才能刪除蔬菜。

  3. 當模型欄位的on_delete=SET_NULL,刪除蔬菜以後,對應商品資訊表,cid=1的資料的cid全部被改成cid=null

  4. 當模型欄位的on_delete=SET_DEFAULT,刪除蔬菜以後,對應商品資訊表,cid=1的資料記錄的cid被被設定預設值。

6.3、資料遷移

將模型類定義表架構的程式碼轉換成SQL同步到資料庫中,這個過程就是資料遷移。django中的資料遷移,就是一個類,這個類提供了一系列的終端命令,幫我們完成資料遷移的工作。

(1)生成遷移檔案

所謂的遷移檔案, 是類似模型類的遷移類,主要是描述了資料表結構的類檔案.

python manage.py makemigrations

(2)同步到資料庫中

python manage.py migrate

補充:在django內部提供了一系列的功能,這些功能也會使用到資料庫,所以在專案搭建以後第一次資料遷移的時候,會看到django專案中其他的資料表被建立了。其中就有一個django內建的admin站點管理。

# admin站點預設是開啟狀態的,我們可以透過http://127.0.0.1:8000/admin
# 這個站點必須有個管理員賬號登入,所以我們可以在第一次資料遷移,有了資料表以後,就可以透過以下終端命令來建立一個超級管理員賬號。
python manage.py createsuperuser

image
image

(3)新增測試資料

INSERT INTO `db_student`  
(`id`,`name`,`sex`,`class`,`age`,`description`,`created_time`,`updated_time`) 
VALUES
(1,'趙華',1,307,22,'對於勤奮的人來說,成功不是偶然;對於懶惰的人來說,失敗卻是必然。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(2,'程星雲',1,301,20,'人生應該如蠟燭一樣,從頂燃到底,一直都是光明的。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(3,'陳峰',1,504,21,'在不瘋狂,我們就老了,沒有記憶怎麼祭奠呢?','2020-11-20 10:00:00','2020-11-20 10:00:00'),(4,'蘇禮就',1,502,20,'不要為舊的悲傷,浪費新的眼淚。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(5,'張小玉',2,306,18,'沒有血和汗水就沒有成功的淚水。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(6,'吳傑',1,307,19,'以大多數人的努力程度之低,根本輪不到去拼天賦','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(7,'張小辰',2,405,19,'人生的道路有成千上萬條, 每一條路上都有它獨自的風景。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(8,'王丹丹',2,502,22,'平凡的人聽從命運,堅強的人主宰命運。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(9,'苗俊偉',1,503,22,'外事找谷歌,內事找百度。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(10,'婁鎮明',1,301,22,'不經三思不求教,不動筆墨不讀書。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(11,'周夢琪',2,306,19,'學習與坐禪相似,須有一顆恆心。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(12,'歐陽博',1,503,23,'春去秋來,又一年。What did you get ?','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(13,'顏敏莉',2,306,20,'Knowledge makes humble, ignorance makes proud.','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(14,'柳宗仁',1,301,20,'有志者事竟成。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(15,'謝海龍',1,402,22,'這世界誰也不欠誰,且行且珍惜。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(16,'鄧士鵬',1,508,22,'青,取之於藍而青於藍;冰,水為之而寒於水。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(17,'寧靜',2,502,23,'一息若存 希望不滅','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(18,'上官屏兒',2,502,21,'美不自美,因人而彰。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(19,'孫曉靜',2,503,20,'人生本過客,何必千千結;無所謂得失,淡看風和雨。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(20,'劉承志',1,306,20,'good good study,day day up! ^-^','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(21,'王浩',1,503,21,'積土而為山,積水而為海。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(22,'鍾無豔',2,303,19,'真者,精誠之至也,不精不誠,不能動人。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(23,'莫榮軒',1,409,22,'不管發生什麼事,都請安靜且愉快地接受人生,勇敢地、大膽地,而且永遠地微笑著。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(24,'張裕民',1,303,21,'偉大的目標形成偉大的人物。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(25,'江宸軒',1,407,22,'用最少的悔恨面對過去。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(26,'譚季同',1,305,21,'人總是珍惜未得到的,而遺忘了所擁有的。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(27,'李松風',1,504,19,'明天的希望,讓我們忘了今天的痛苦。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(28,'葉宗政',1,407,20,'因害怕失敗而不敢放手一搏,永遠不會成功。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(29,'魏雪寧',2,306,20,'成功與失敗只有一紙之隔','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(30,'徐秋菱',2,404,19,'年輕是我們唯一擁有權利去編織夢想的時光。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(31,'曾嘉慧',2,301,19,'有一分熱,發一分光。就令螢火一般,也可以在黑暗裡發一點光,不必等候炬火。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(32,'歐陽鎮安',1,408,23,'青春虛度無所成,白首銜悲補何及!','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(33,'周子涵',2,309,19,'青春是一個普通的名稱,它是幸福美好的,但它也是充滿著艱苦的磨鍊。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(34,'宋應諾',2,501,23,'涓滴之水終可以磨損大石,不是由於它力量強大,而是由於晝夜不捨的滴墜。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(35,'白瀚文',1,305,19,'一個人假如不腳踏實地去做,那麼所希望的一切就會落空。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(36,'陳匡怡',2,505,19,'一份耕耘,一份收穫。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(37,'邵星芸',2,503,22,'冰凍三尺非一日之寒。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(38,'王天歌',2,302,21,'任何的限制,都是從自己的內心開始的。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(39,'王天龍',1,302,22,'再長的路,一步步也能走完,再短的路,不邁開雙腳也無法到達。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(40,'方怡',2,509,23,'智者不做不可能的事情。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(41,'李偉',1,505,19,'人之所以能,是相信能。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(42,'李思玥',2,503,22,'人的一生可能燃燒也可能腐朽,我不能腐朽,我願意燃燒起來。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(43,'趙思成',1,401,18,'合抱之木,生於毫末;九層之臺,起於累土。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(44,'蔣小媛',2,308,22,'不積跬步無以至千里,不積細流無以成江河。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(45,'龍華',1,510,19,'只要持續地努力,不懈地奮鬥,就沒有徵服不了的東西。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(46,'牧婧白夜',2,501,21,'讀不在三更五鼓,功只怕一曝十寒。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(47,'江俊文',1,304,19,'立志不堅,終不濟事。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(48,'李亞容',2,304,18,'Keep on going never give up.','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(49,'王紫伊',2,301,22,'最可怕的敵人,就是沒有堅強的信念。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(50,'毛小寧',1,501,19,'要從容地著手去做一件事,但一旦開始,就要堅持到底。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(51,'董 晴',2,507,19,'常常是最後一把鑰匙開啟了門。貴在堅持','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(52,'嚴語',2,405,18,'逆水行舟,不進則退。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(53,'陳都靈',2,503,19,'無論什麼時候,不管遇到什麼情況,我絕不允許自己有一點點灰心喪氣。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(54,'黃威',1,301,23,'我的字典裡面沒有“放棄”兩個字','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(55,'林佳欣',2,308,23,'夢想就是一種讓你感到堅持,就是幸福的東西。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(56,'翁心穎',2,303,19,'有目標的人才能成功,因為他們知道自己的目標在哪裡。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(57,'蒙毅',1,502,22,'所謂天才,就是努力的力量。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(58,'李小琳',2,509,22,'每天早上對自己微笑一下。這就是我的生活態度。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(59,'伍小龍',1,406,19,'一路上的點點滴滴才是我們的財富。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(60,'晁然',2,305,23,'人的價值是由自己決定的。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(61,'端木浩然',1,507,18,'摔倒了爬起來再哭。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(62,'姜沛佩',2,309,21,'Believe in yourself.','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(63,'李棟明',1,306,19,'雖然過去不能改變,但是未來可以。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(64,'柴柳依',2,508,23,'沒有實踐就沒有發言權。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(65,'吳傑',1,401,22,'人生有兩出悲劇。一是萬念俱灰;另一是躊躇滿志','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(66,'杜文華',1,507,19,'有智者立長志,無志者長立志。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(67,'鄧珊珊',2,510,18,'Action is the proper fruit of knowledge.','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(68,'杜俊峰',1,507,23,'世上無難事,只要肯登攀。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(69,'莊信傑',1,301,22,'知識就是力量。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(70,'宇文軒',1,402,23,'如果你想要某樣東西,別等著有人某天會送給你。生命太短,等不得。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(71,'黃佳懌',2,510,19,'Learn and live.','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(72,'衛然',1,510,18,'神於天,聖於地。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(73,'耶律齊',1,307,23,'如果不是在海市蜃樓中求勝,那就必須腳踏實地去跋涉。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(74,'白素欣',2,305,18,'慾望以提升熱忱,毅力以磨平高山。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(75,'徐鴻',1,403,23,'最美的不是生如夏花,而是在時間的長河裡,波瀾不驚。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(76,'上官傑',1,409,19,'生活之所以耀眼,是因為磨難與輝煌會同時出現。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(77,'吳興國',1,406,18,'生活的道路一旦選定,就要勇敢地走到底,決不回頭。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(78,'莊曉敏',2,305,18,'Never say die.','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(79,'吳鎮升',1,509,18,'Judge not from appearances.','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(80,'朱文豐',1,304,19,'每個人都比自己想象的要強大,但同時也比自己想象的要普通。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(81,'苟興妍',2,508,18,'Experience is the best teacher.','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(82,'祝華生',1,302,21,'淺學誤人。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(83,'張美琪',2,404,23,'最淡的墨水,也勝過最強的記性。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(84,'周永麟',1,308,21,'All work and no play makes Jack a dull boy.','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(85,'鄭心',2,404,21,'人生就像一杯茶,不會苦一輩子,但總會苦一陣子。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(86,'公孫龍馨',1,510,21,'Experience is the father of wisdom and memory the mother.','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(87,'葉靈瓏',2,401,19,'讀一書,增一智。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(88,'上官龍',1,501,21,'別人能做到的事,自己也可以做到。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(89,'顏振超',1,303,19,'如果要飛得高,就該把地平線忘掉。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(90,'瑪詩琪',2,409,22,'每天進步一點點,成功不會遠。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(91,'李哲生',1,309,22,'這不是偶然的失誤,是必然的結果。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(92,'羅文華',2,408,22,'好走的都是下坡路。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(93,'李康',1,509,19,'Deliberate slowly, promptly.','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(94,'鍾華強',1,405,19,'混日子很簡單,討生活比較難。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(95,'張今菁',2,403,23,'不經一翻徹骨寒,怎得梅花撲鼻香。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(96,'黃偉麟',1,407,19,'與其詛咒黑暗,不如燃起蠟燭。沒有人能給你光明,除了你自己。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(97,'程榮泰',1,406,22,'明天不一定更好,。但更好的明天一定會來。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(98,'範偉傑',1,508,19,'水至清則無魚,人至察則無徒。凡事不能太執著。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(99,'王俊凱',1,407,21,'我欲將心向明月,奈何明月照溝渠。','2020-11-20 10:00:00','2020-11-20 10:00:00'),
(100,'白楊 ',1,406,19,'閃電從不打在相同的地方.人不該被相同的方式傷害兩次。','2020-11-20 10:00:00','2020-11-20 10:00:00');
-- 作業:
-- 1. 完成學生的建立,匯入上面的資料。
-- 2. 使用原來學過的SQL語句,然後對上面匯入的學生資訊,完成增刪查改的操作。
   -- 2.0 查詢所有的學生資訊(name,age)
       SELECT name,age FROM db_student
   -- 2.1 查詢年齡在18-20之間的學生資訊[name,age,sex]
       select name,age,sex from db_student where age >=18 and age <=20;
   -- 2.2 查詢年齡在18-20之間的男生資訊[name,age,sex]
       select name,age,if(sex=1,'男','女') as sex from db_student where age >=18 and age <=20 and sex=1;
   -- 2.3 查詢401-406之間所有班級的學生資訊[name,age,sex,class]
       select name,age,sex,class from db_student where class between 401 and 406;
   -- 2.4 查詢401-406之間所有班級的總人數
       select count(1) as c from db_student where class between 401 and 406;
   -- 2.5 新增一個學生,男,劉德華,302班,17歲,"給我一杯水就行了。",'2020-11-20 10:00:00','2020-11-20 10:00:00'
       insert into db_student (name,sex,class, age, description, created_time,updated_time) values ('劉德華',1,'302', 17, "給我一杯水就行了。",'2020-11-20 10:00:00','2020-11-20 10:00:00');
   -- 2.6 修改劉德華的年齡,為19歲。
       update db_student set age=19 where name='劉德華';
   -- 2.7 劉德華畢業了,把他從學生表中刪除
       delete  from db_student where name='劉德華';
   -- 2.8 找到所有學生中,年齡最小的5位同學和年齡最大的5位同學
       select * from db_student order by age asc limit 5;
       select * from db_student order by age desc limit 5;
   -- 2.9 【進階】找到所有班級中人數超過4個人班級出來
       select class,count(id) as total from db_student group by class having total >= 4;
   -- 2.10【進階】把上面2.8的要求重新做一遍,改成一條資料實現
       (select * from db_student order by age asc limit 5) union all (select * from db_student order by age desc limit 5);

6.4、資料庫基本操作

6.4.1、新增記錄

(1)save方法

透過建立模型類物件,執行物件的save()方法儲存到資料庫中。

student = Student(
    name="劉德華",
    age=17,
    sex=True,
    classmate=301,
    description="一手忘情水"
)
student.save()
print(student.id) # 判斷是否新增有ID

(2)create方法

透過模型類.objects.create()儲存,返回生成的模型類物件。

student = Student.objects.create(
    name="趙本山",
    age=50,
    sex=True,
    class_number=301,
    description="一段小品"
)
print(student.id)

6.4.2、基礎查詢

ORM中針對查詢結果的限制,提供了一個查詢集[QuerySet].這個QuerySet,是ORM中針對查詢結果進行儲存資料的一個型別,我們可以透過了解這個QuerySet進行使用,達到查詢最佳化,或者限制查詢結果數量的作用。

(1)all()

查詢所有物件,返回queryset物件。查詢集,也稱查詢結果集、QuerySet,表示從資料庫中獲取的物件集合。

students = Student.objects.all()
print("students:",students)

(2)filter()

篩選條件相匹配的物件,返回queryset物件。

# 查詢所有的女生
students = Student.objects.filter(sex=0)
print(students)

(3)get()

返回與所給篩選條件相匹配的物件,返回結果有且只有一個, 如果符合篩選條件的物件超過一個或者沒有都會丟擲錯誤。

student = Student.objects.get(pk=1)
print(student)
print(student.description)
get使用過程中的注意點:get是根據條件返回多個結果或者沒有結果,都會報錯
try:
    student = Student.objects.get(name="劉德華")
    print(student)
    print(student.description)
except Student.MultipleObjectsReturned:
    print("查詢得到多個結果!")
except Student.DoesNotExist:
    print("查詢結果不存在!")

(4)first()、last()

分別為查詢集的第一條記錄和最後一條記錄

# 沒有結果返回none,如果有多個結果,則返回模型物件
students = Student.objects.all()
# print(students.name)
print(students[0].name)
stu01 = Student.objects.first()
stu02 = Student.objects.last()

print(stu01.name)
print(stu02.name)

(5)exclude()

篩選條件不匹配的物件,返回queryset物件。

# 查詢張三以外的所有的學生
students = Student.objects.exclude(name="張三")
print(students)

(6)order_by()

對查詢結果排序

# order_by("欄位")  # 按指定欄位正序顯示,相當於 asc  從小到大
# order_by("-欄位") # 按欄位倒序排列,相當於 desc 從大到小
# order_by("第一排序","第二排序",...)

# 查詢所有的男學生按年齡從高到低展示
# students = Student.objects.all().order_by("-age","-id")
students = Student.objects.filter(sex=1).order_by("-age", "-id")
print(students)

(7)count()

查詢集中物件的個數

# 查詢所有男生的個數
count = Student.objects.filter(sex=1).count()
print(count)

(8)exists()

判斷查詢集中是否有資料,如果有則返回True,沒有則返回False

# 查詢Student表中是否存在學生
print(Student.objects.exists())

(9)values()、values_list()

  • value()把結果集中的模型物件轉換成字典,並可以設定轉換的欄位列表,達到減少記憶體損耗,提高效能

  • values_list(): 把結果集中的模型物件轉換成列表,並可以設定轉換的欄位列表(元祖),達到減少記憶體損耗,提高效能

# values 把查詢結果中模型物件轉換成字典
student_list = Student.objects.filter(classmate="301")
student_list = student_list.order_by("-age")
student_list = student_list.filter(sex=1)
ret1 = student_list.values() # 預設把所有欄位全部轉換並返回
ret2 = student_list.values("id","name","age") # 可以透過引數設定要轉換的欄位並返回
ret3 = student_list.values_list() # 預設把所有欄位全部轉換並返回
ret4 = student_list.values_list("id","name","age") # 可以透過引數設定要轉換的欄位並返回
print(ret4)
return JsonResponse({},safe=False)

(10)distinct()

從返回結果中剔除重複紀錄。返回queryset。

# 查詢所有學生出現過的年齡
print(Student.objects.values("age").distinct())

6.4.3、模糊查詢

(1)模糊查詢之contains

說明:如果要包含%無需轉義,直接寫即可。

例:查詢姓名包含'華'的學生。

Student.objects.filter(name__contains='華')

(2)模糊查詢之startswith、endswith

例:查詢姓名以'文'結尾的學生

Student.objects.filter(name__endswith='文')

以上運算子都區分大小寫,在這些運算子前加上i表示不區分大小寫,如iexact、icontains、istartswith、iendswith.

(3)模糊查詢之isnull

例:查詢個性簽名不為空的學生。

# 修改Student模型description屬性允許設定為null,然後資料遷移
description = models.TextField(default=None, null=True, verbose_name="個性簽名")
# 新增測試資料
NSERT INTO student.db_student (name, age, sex, class, description, created_time, updated_time) VALUES ('劉德華', 17, 1, '407', null, '2020-11-20 10:00:00.000000', '2020-11-20 10:00:00.000000');
# 程式碼操作
tudent_list = Student.objects.filter(description__isnull=True)

(4)模糊查詢之in

例:查詢編號為1或3或5的學生

Student.objects.filter(id__in=[1, 3, 5])

(5)模糊查詢之比較查詢

  • gt 大於 (greater then)
  • gte 大於等於 (greater then equal)
  • lt 小於 (less then)
  • lte 小於等於 (less then equal)

例:查詢編號大於3的學生

Student.objects.filter(id__gt=3)

(6)模糊查詢之日期查詢

year、month、day、week_day、hour、minute、second:對日期時間型別的屬性進行運算。

例:查詢2010年被新增到資料中的學生。

Student.objects.filter(born_date__year=1980)

例:查詢2016年6月20日後新增的學生資訊。

from django.utils import timezone as datetime
student_list = Student.objects.filter(created_time__gte=datetime.datetime(2016,6,20),created_time__lt=datetime.datetime(2016,6,21)).all()
print(student_list)

6.4.4、進階查詢

(1) F查詢

之前的查詢都是物件的屬性與常量值比較,兩個屬性怎麼比較呢? 答:使用F物件,被定義在django.db.models中。

語法如下:

"""F物件:2個欄位的值比較"""
# 獲取從新增資料以後被改動過資料的學生
from django.db.models import F
# SQL: select * from db_student where created_time=updated_time;
student_list = Student.objects.exclude(created_time=F("updated_time"))
print(student_list)

(2) Q查詢

多個過濾器逐個呼叫表示邏輯與關係,同sql語句中where部分的and關鍵字。

例:查詢年齡大於20,並且編號小於30的學生。

Student.objects.filter(age__gt=20,id__lt=30)
或
Student.filter(age__gt=20).filter(id__lt=30)

如果需要實現邏輯或or的查詢,需要使用Q()物件結合|運算子,Q物件被義在django.db.models中。

語法如下:

Q(屬性名__運算子=值)
Q(屬性名__運算子=值) | Q(屬性名__運算子=值)

例:查詢年齡小於19或者大於20的學生,使用Q物件如下。

from django.db.models import Q
student_list = Student.objects.filter( Q(age__lt=19) | Q(age__gt=20) ).all()

Q物件可以使用&、|連線,&表示邏輯與,|表示邏輯或**

例:查詢年齡大於20,或編號小於30的學生,只能使用Q物件實現

Student.objects.filter(Q(age__gt=20) | Q(pk__lt=30))

Q物件左邊可以使用~運算子,表示非not。但是工作中,我們只會使用Q物件進行或者的操作,只有多種巢狀複雜的查詢條件才會使用&和~進行與和非得操作

例:查詢編號不等於30的學生。

Student.objects.filter(~Q(pk=30))

(3)聚合查詢

使用aggregate()過濾器呼叫聚合函式。聚合函式包括:Avg 平均,Count 數量,Max 最大,Min 最小,Sum 求和,被定義在django.db.models中。

例:查詢學生的平均年齡。

from django.db.models import Sum,Count,Avg,Max,Min

Student.objects.aggregate(Avg('age'))

注意:aggregate的返回值是一個字典型別,格式如下:

  {'屬性名__聚合類小寫':值}

使用count時一般不使用aggregate()過濾器。

例:查詢學生總數。

Student.objects.count() # count函式的返回值是一個數字。

(4)分組查詢

QuerySet物件.annotate()
# annotate() 進行分組統計,按前面select 的欄位進行 group by
# annotate() 返回值依然是 queryset物件,增加了分組統計後的鍵值對
模型物件.objects.values("id").annotate(course=Count('course__sid')).values('id','course')
# 查詢指定模型, 按id分組 , 將course下的sid欄位計數,返回結果是 name欄位 和 course計數結果 

# SQL原生語句中分組之後可以使用having過濾,在django中並沒有提供having對應的方法,但是可以使用filter對分組結果進行過濾
# 所以filter在annotate之前,表示where,在annotate之後代表having
# 同理,values在annotate之前,代表分組的欄位,在annotate之後代表資料查詢結果返回的欄位

(5)原生查詢

執行原生SQL語句,也可以直接跳過模型,才通用原生pymysql.

 ret = Student.objects.raw("SELECT id,name,age FROM db_student")  # student 可以是任意一個模型
 # 這樣執行獲取的結果無法透過QuerySet進行操作讀取,只能迴圈提取
 print(ret,type(ret))
 for item in ret:
    print(item,type(item))

6.4.5、修改記錄

(1) 使用save更新資料

student = Student.objects.filter(name='劉德華').first()
print(student)
student.age = 19
student.classmate = "303"
# save之所以能提供給我們新增資料的同時,還可以更新資料的原因?
# save會找到模型的欄位的主鍵id的值,
# 主鍵id的值如果是none,則表示當前資料沒有被資料庫,所以save會自動變成新增操作
# 主鍵id有值,則表示當前資料在資料庫中已經存在,所以save會自動變成更新資料操作
student.save()

(2)update更新(推薦)

使用模型類.objects.filter().update(),會返回受影響的行數

# update是全域性更新,只要符合更新的條件,則全部更新,因此強烈建議加上條件!!!
student = Student.objects.filter(name="趙華",age=22).update(name="劉芙蓉",sex=True)
print(student)

6.4.6、刪除記錄

刪除有兩種方法

(1)模型類物件.delete

student = Student.objects.get(id=13)
student.delete()

(2)模型類.objects.filter().delete()

Student.objects.filter(id=14).delete() 

程式碼:

# 1. 先查詢到資料模型物件。透過模型物件進行刪除
# student = Student.objects.filter(pk=13).first()
# student.delete()

# 2. 直接刪除
ret = Student.objects.filter(pk=100).delete()
print(ret)
# 務必寫上條件,否則變成了清空表了。ret = Student.objects.filter().delete()

6.5、建立關聯模型

-- 版本1
-- student
id  name  age class_name  class_tutor  class_num
1   rain  22  s12         zhangsan     78 
2   alvin 24  s12         zhangsan     78 
3   eric  23  s12         zhangsan     78 


-- 版本2: 一對多是透過在多的表中建立關聯欄位
-- student
id  name  age  class_id  
1   rain  22      1 
2   alvin 24      1 
3   eric  23      1  


-- class
id  class_name  class_tutor  class_num
1    s12         zhangsan     78 
2    s13         lisi         88

-- 查詢學生rain所在班級名稱
select class_id from student where name = 'rain'  -- 1
select class_name from class where id = 1



-- 多對多的關係的確立是透過建立第三張關係表來完成的

-- course(選秀課程)
 id  name  
 
 1   python
 2   java
 3   php
 
 
 -- student2course
 
 id  student_id  course_id
  1      1          1      -- id為1的學生選修了id為1的課程
  2      1          2      -- id為1的學生選修了id為2的課程
  3      2          2      -- id為2的學生選修了id為2的課程
 

-- 一對一 ,類似一對多,在兩張關係表中的任何一張都可以建立一個關聯欄位
-- student
id  name  age  class_id  student_detail_id(unique)  
1   rain  22      1            1
2   alvin 24      1            3 
3   eric  23      1            2


-- student_detail
id  addr   email   tel   
1   bj     123     110 
2   bj     234     911
3   nj     456     112


例項:我們來假定下面這些概念,欄位和關係

  • 班級模型: 班級名稱、導員。
  • 課程模型:課程名稱、講師等。
  • 學生模型: 學生有姓名,年齡,只有一個班級,所以和班級表是一對多的關係(one-to-many);選修了多個課程,所以和課程表是多對多的關係(many-to-many)
  • 學生詳情:學生的家庭地址,手機號,郵箱等詳細資訊,和學生模型應該是一對一的關係(one-to-one)

模型建立如下:

from django.db import models


# Create your models here.


class Clas(models.Model):
    name = models.CharField(max_length=32, unique=True, verbose_name="班級名稱")

    class Meta:
        db_table = "db_class"


class Course(models.Model):
    name = models.CharField(max_length=32, unique=True, verbose_name="課程名稱")

    class Meta:
        db_table = "db_course"


class Student(models.Model):
    sex_choices = (
        (0, "女"),
        (1, "男"),
        (2, "保密"),
    )

    name = models.CharField(max_length=32, unique=True, verbose_name="姓名")
    age = models.SmallIntegerField(verbose_name="年齡", default=18)  # 年齡
    sex = models.SmallIntegerField(choices=sex_choices)
    birthday = models.DateField()

    # 一對多
    # on_delete= 關聯關係的設定
    # models.CASCADE    刪除主鍵以後, 對應的外來鍵所在資料也被刪除
    # models.DO_NOTHING 刪除主鍵以後, 對應的外來鍵不做任何修改
    # 反向查詢欄位 related_name
    clas = models.ForeignKey("Clas", on_delete=models.CASCADE)

    # 多對多
    # 建立多對多的關係,ManyToManyField可以建在兩個模型中的任意一個,自動建立第三張表
    courses = models.ManyToManyField("Course", db_table="db_student2course")

    # 一對一,使用同一對多
    stu_detail = models.OneToOneField("StudentDetail", on_delete=models.CASCADE)

    class Meta:
        db_table = "db_student"

    def __str__(self):
        return self.name


class StudentDetail(models.Model):
    tel = models.CharField(max_length=32)
    email = models.EmailField()
    description = models.TextField(null=True, verbose_name="個性簽名")

    class Meta:
        db_table = "db_student_detail"

6.6、關聯新增

(1)一對多與一對一

 stu = Student.objects.create(name="王五", age=23, sex=1, birthday="1991-11-12", clas_id=9, stu_detail_id=6)
  print(stu.name)
  print(stu.age)
  print(stu.sex)
  print(stu.clas_id)  # 6
  print(stu.stu_detail_id)  # 5
  print(stu.clas)  # 模型類物件
  print(stu.stu_detail)  # 模型類物件

  # 查詢stu這個學生的班級名稱
  print(stu.clas.name)
  # 查詢stu這個學生的手機號
  print(stu.stu_detail.tel)

(2)多對多

 # stu = Student.objects.create(name="rain", age=33, sex=1, birthday="1996-11-12", clas_id=9, stu_detail_id=7)

    # (1) 新增多對多的資料

    # 新增多對多方式1
    c1 = Course.objects.get(title="思修")
    c2 = Course.objects.get(title="邏輯學")
    stu.courses.add(c1,c2)

    # 新增多對多方式2
    stu = Student.objects.get(name="張三")
    stu.courses.add(5,7)

    # 新增多對多方式3
    stu = Student.objects.get(name="李四")
    stu.courses.add(*[6,7])

    # (2) 刪除多對多記錄

   stu = Student.objects.get(name="李四")
   stu.courses.remove(7)

    # (3) 清空多對多記錄:clear方法

    stu = Student.objects.get(name="rain")
    stu.courses.clear()

    # (4) 重置多對多記錄:set方法

    stu = Student.objects.get(name="李四")
    stu.courses.set([5,8])

    # (5) 多對多記錄查詢: all
    # 查詢李四所報課程的名稱
    stu = Student.objects.get(name="李四")
    courses = stu.courses.all()
    courses = stu.courses.all().values("title")
    print(courses)  # <QuerySet [<Course: Course object (5)>, <Course: Course object (8)>]>

6.7、關聯查詢

6.7.1、基於物件查詢(子查詢)

    # **********************************  一對多查詢
    # 查詢張三所在班級的名稱
    # stu = Student.objects.get(name="張三")
    # print(stu.clas.name)

    # 查詢電腦科學與技術2班有哪些學生

    # clas = Clas.objects.get(name="電腦科學與技術2班")
    # 反向查詢方式1:
    # ret = clas.student_set.all()  # 反向查詢按表名小寫_set
    # print(ret) # <QuerySet [<Student: 張三>, <Student: 李四>]>
    # 反向查詢方2:

    # print(clas.student_list.all())  # <QuerySet [<Student: 張三>, <Student: 李四>]>

    # **********************************  一對一查詢

    # 查詢李四的手機號
    # stu = Student.objects.get(name="李四")
    # print(stu.stu_detail.tel)

    # 查詢110手機號的學生姓名和年齡

    # stu_detail = StudentDetail.objects.get(tel="110")
    # 反向查詢方式1: 表名小寫
    # print(stu_detail.student.name)
    # print(stu_detail.student.age)
    # 反向查詢方式2: related_name
    # print(stu_detail.stu.name)
    # print(stu_detail.stu.age)

    # **********************************  多對多查詢

    # 查詢張三所報課程的名稱

    # stu = Student.objects.get(name="張三")
    # print(stu.courses.all())  # QuerySet [<Course: 近代史>, <Course: 籃球>]>

    # 查詢選修了近代史這門課程學生的姓名和年齡
    # course = Course.objects.get(title="近代史")
    # 反向查詢方式1: 表名小寫_set
    # print(course.student_set.all()) # <QuerySet [<Student: 張三>, <Student: 李四>]>

    # 反向查詢方式2:related_name
    # print(course.students.all())
    # print(course.students.all().values("name","age")) # <QuerySet [{'name': '張三', 'age': 22}, {'name': '李四', 'age': 24}]>

(1)正向查詢按欄位

(2)反向查詢按表名小寫或者related_name

6.7.2、基於雙下劃線查詢(join查詢)

    # 查詢張三的年齡
    ret = Student.objects.filter(name="張三").values("age")
    print(ret) # <QuerySet [{'age': 22}]>

    # (1) 查詢年齡大於22的學生的姓名以及所在名稱班級
    # select db_student.name,db_class.name from db_student inner join db_class on db_student.clas_id = db_class.id where db_student.age>22;

    # 方式1 : Student作為基表
    ret = Student.objects.filter(age__gt=22).values("name","clas__name")
    print(ret)
    # 方式2 :Clas表作為基表
    ret = Clas.objects.filter(student_list__age__gt=22).values("student_list__name","name")
    print(ret)

    # (2) 查詢電腦科學與技術2班有哪些學生

    ret = Clas.objects.filter(name="電腦科學與技術2班").values("student_list__name")
    print(ret)  #<QuerySet [{'student_list__name': '張三'}, {'student_list__name': '李四'}]>

    # (3) 查詢張三所報課程的名稱

    ret = Student.objects.filter(name="張三").values("courses__title")
    print(ret) # <QuerySet [{'courses__title': '近代史'}, {'courses__title': '籃球'}]>

    # (4) 查詢選修了近代史這門課程學生的姓名和年齡

    ret = Course.objects.filter(title="近代史").values("students__name","students__age")
    print(ret) # <QuerySet [{'students__name': '張三', 'students__age': 22}, {'students__name': '李四', 'students__age': 24}]>

    # (5) 查詢李四的手機號

    ret = Student.objects.filter(name='李四').values("stu_detail__tel")
    print(ret)  # <QuerySet [{'stu_detail__tel': '911'}]>


    # (6) 查詢手機號是110的學生的姓名和所在班級名稱

    # 方式1
    ret = StudentDetail.objects.filter(tel="110").values("stu__name","stu__clas__name")
    print(ret) # <QuerySet [{'stu__name': '張三', 'stu__clas__name': '電腦科學與技術2班'}]>

    # 方式2:
    ret = Student.objects.filter(stu_detail__tel="110").values("name","clas__name")
    print(ret) # <QuerySet [{'name': '張三', 'clas__name': '電腦科學與技術2班'}]>

(1)正向關聯按關聯欄位

(2)反向按表名小寫或related_name

6.7.3、關聯分組查詢

# from django.db.models import Avg, Count, Max, Min
ret = Student.objects.values("sex").annotate(c = Count("name"))
print(ret) # <QuerySet [{'sex': 0, 'c': 1}, {'sex': 1, 'c': 3}]>

# (1)查詢每一個班級的名稱以及學生個數

ret = Clas.objects.values("name").annotate(c = Count("student_list__name"))
print(ret) # <QuerySet [{'name': '網路工程1班', 'c': 0}, {'name': '網路工程2班', 'c': 0}, {'name': '電腦科學與技術1班', 'c': 0}, {'name': '電腦科學與技術2班', 'c': 1}, {'name': '軟體1班', 'c': 3}]>

# (2)查詢每一個學生的姓名,年齡以及選修課程的個數

ret = Student.objects.values("name","age").annotate(c=Count("courses__title"))
print(ret) # <QuerySet [{'name': 'rain', 'c': 0}, {'name': '張三', 'c': 2}, {'name': '李四', 'c': 2}, {'name': '王五', 'c': 0}]>

ret = Student.objects.all().annotate(c=Count("courses__title")).values("name","age","sex","c")
# print(ret)

# (3) 每一個課程名稱以及選修學生的個數
ret = Course.objects.all().annotate(c = Count("students__name")).values("title","c")
print(ret) # <QuerySet [{'title': '近代史', 'c': 2}, {'title': '思修', 'c': 0}, {'title': '籃球', 'c': 1}, {'title': '邏輯學', 'c': 1}, {'title': '輪滑', 'c': 0}]>

# (4)  查詢選修課程個數大於1的學生姓名以及選修課程個數
ret = Student.objects.all().annotate(c=Count("courses__title")).filter(c__gt=1).values("name","c")
print(ret) # <QuerySet [{'name': '張三', 'c': 2}, {'name': '李四', 'c': 2}]>

# (5) 查詢每一個學生的姓名以及選修課程個數並按著選修的課程個數進行從低到高排序
ret = Student.objects.all().annotate(c=Count("courses__title")).order_by("c").values("name","c")
print(ret)

6.8、專案練習