Django Model

1. ORM 簡介

MTV 設計模式中,模型(M)就是對數據庫的操做,在 Web 開發中,使用最頻繁的也是對數據庫的操做,那麼該怎麼樣去實現呢?javascript

咱們不可能本身手動去寫大量的 SQL 語句,由於咱們也不是專業 DBA 人員,那麼咱們就只能奢求有一種可以使用 Python 程序對數據庫操做的方法了。這就是 ORM(Object Relation Mapping)對象關係映射,以面向對象的方式去操做數據庫。html

其實現模式大體是這樣的:java

Django 自己給咱們提供了強大的 ORM 系統,不須要再額外的
安裝,固然還有一些其餘的 ORM ,如:SQLAlch 等。python

2. 字段

Model 中的字段 fileds 即數據表中的列,用來存儲數據(記錄),字段類型對應列的數據類型。mysql

2.1 經常使用字段類型

Django 內置了不少字段類型,都位於 django.db.models 中,如下爲經常使用字段類型:jquery

AutoField(Field)
    - int自增列,必須填入參數 primary_key=True

BigAutoField(AutoField)
    - bigint自增列,必須填入參數 primary_key=True

    注:當model中若是沒有自增列,則自動會建立一個列名爲id的列
    from django.db import models

    class UserInfo(models.Model):
        # 自動建立一個列名爲id的且爲自增的整數列
        username = models.CharField(max_length=32)

    class Group(models.Model):
        # 自定義自增列
        nid = models.AutoField(primary_key=True)
        name = models.CharField(max_length=32)

SmallIntegerField(IntegerField):
    - 小整數 -32768 ~ 32767

PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
    - 正小整數 0 ~ 32767

IntegerField(Field)
    - 整數列(有符號的) -2147483648 ~ 2147483647

PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField)
    - 正整數 0 ~ 2147483647

BigIntegerField(IntegerField):
    - 長整型(有符號的) -9223372036854775808 ~ 9223372036854775807

BooleanField(Field)
    - 布爾值類型

NullBooleanField(Field):
    - 能夠爲空的布爾值

CharField(Field)
    - 字符類型
    - 必須提供max_length參數, max_length表示字符長度

TextField(Field)
    - 文本類型

EmailField(CharField):
    - 字符串類型,Django Admin以及ModelForm中提供驗證機制

IPAddressField(Field)
    - 字符串類型,Django Admin以及ModelForm中提供驗證 IPV4 機制

GenericIPAddressField(Field)
    - 字符串類型,Django Admin以及ModelForm中提供驗證 Ipv4和Ipv6
    - 參數:
        protocol,用於指定Ipv4或Ipv6, 'both',"ipv4","ipv6"
        unpack_ipv4, 若是指定爲True,則輸入::ffff:192.0.2.1時候,可解析爲192.0.2.1,開啓刺功能,須要protocol="both"

URLField(CharField)
    - 字符串類型,Django Admin以及ModelForm中提供驗證 URL

SlugField(CharField)
    - 字符串類型,Django Admin以及ModelForm中提供驗證支持 字母、數字、下劃線、鏈接符(減號)

CommaSeparatedIntegerField(CharField)
    - 字符串類型,格式必須爲逗號分割的數字

UUIDField(Field)
    - 字符串類型,Django Admin以及ModelForm中提供對UUID格式的驗證

FilePathField(Field)
    - 字符串,Django Admin以及ModelForm中提供讀取文件夾下文件的功能
    - 參數:
            path,                      文件夾路徑
            match=None,                正則匹配
            recursive=False,           遞歸下面的文件夾
            allow_files=True,          容許文件
            allow_folders=False,       容許文件夾

FileField(Field)
    - 字符串,路徑保存在數據庫,文件上傳到指定目錄
    - 參數:
        upload_to = ""      上傳文件的保存路徑
        storage = None      存儲組件,默認django.core.files.storage.FileSystemStorage

ImageField(FileField)
    - 字符串,路徑保存在數據庫,文件上傳到指定目錄
    - 參數:
        upload_to = ""      上傳文件的保存路徑
        storage = None      存儲組件,默認django.core.files.storage.FileSystemStorage
        width_field=None,   上傳圖片的高度保存的數據庫字段名(字符串)
        height_field=None   上傳圖片的寬度保存的數據庫字段名(字符串)

DateTimeField(DateField)
    - 日期+時間格式 YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]

DateField(DateTimeCheckMixin, Field)
    - 日期格式      YYYY-MM-DD
    - auto_now_add=True     當在首次建立對象時自動將字段設置爲如今。用於建立時間戳
    - auto_add=True         每次保存對象時,自動將字段設置爲如今。用於「最後修改」的時間戳

TimeField(DateTimeCheckMixin, Field)
    - 時間格式      HH:MM[:ss[.uuuuuu]]

DurationField(Field)
    - 長整數,時間間隔,數據庫中按照bigint存儲,ORM中獲取的值爲datetime.timedelta類型

FloatField(Field)   
    - 浮點型,精確的數不能用 FloatField

DecimalField(Field)
    - 10進制小數,對於比較精確的數能夠用 DecimalField
    - 參數:
        max_digits,小數總長度
        decimal_places,小數位長度

BinaryField(Field)
    - 二進制類型

2.2 字段參數

null                數據庫中字段是否能夠爲空
db_column           數據庫中字段的列名
default             數據庫中字段的默認值
primary_key         數據庫中字段是否爲主鍵
db_index            數據庫中字段是否能夠創建索引
unique              數據庫中字段是否能夠創建惟一索引
unique_for_date     數據庫中字段【日期】部分是否能夠創建惟一索引
unique_for_month    數據庫中字段【月】部分是否能夠創建惟一索引
unique_for_year     數據庫中字段【年】部分是否能夠創建惟一索引

verbose_name        Admin中顯示的字段名稱
blank               Admin中是否容許用戶輸入爲空
editable            Admin中是否能夠編輯
help_text           Admin中該字段的提示信息
choices             Admin中顯示選擇框的內容,用不變更的數據放在內存中從而避免跨表操做
                    如:gf = models.IntegerField(choices=[(0, '何穗'),(1, '大表姐'),],default=1)

error_messages      自定義錯誤信息(字典類型),從而定製想要顯示的錯誤信息;
                    字典健:null, blank, invalid, invalid_choice, unique, and unique_for_date
                    如:{'null': "不能爲空.", 'invalid': '格式錯誤'}

validators          自定義錯誤驗證(列表類型),從而定製想要的驗證規則
                    from django.core.validators import RegexValidator
                    from django.core.validators import EmailValidator,URLValidator,DecimalValidator,\
                    MaxLengthValidator,MinLengthValidator,MaxValueValidator,MinValueValidator
                    如:
                        test = models.CharField(
                            max_length=32,
                            error_messages={
                                'c1': '優先錯信息1',
                                'c2': '優先錯信息2',
                                'c3': '優先錯信息3',
                            },
                            validators=[
                                RegexValidator(regex='root_\d+', message='錯誤了', code='c1'),
                                RegexValidator(regex='root_112233\d+', message='又錯誤了', code='c2'),
                                EmailValidator(message='又錯誤了', code='c3'), ]
                        )
1. null
用來保存空值,默認值 false。對於保存字符串的字段,儘可能避免將其設置爲 true,不然可能會致使出現是 null 或空字符串

2. blank
表示字段能夠爲空,默認 false。經常使用語表單輸入驗證是否爲空,與數據庫無關。

3. choices
頁面上選擇框標籤,二維元組格式。兩個參數,第一個爲存儲在數據庫中,第二個顯示在頁面上內容。
數據庫存儲的是0 、1 、2,而頁面上顯示的是籃球、足球、羽毛球
hobby_choices = [(0, '籃球'), (1, '足球'), (2, '羽毛球'),]

hobby = models.IntegerField(
    choices=hobby_choices
)

4. primary_key 
主鍵,若是沒有指定主鍵,那麼 Django會自動建立一個 AutoField的自增字段,名爲 id,並將其設置爲主鍵。

primary_key 至關於 null=False和unique=True,即惟一且不能爲空,你也能夠本身將其餘字段設置爲空,如有必要。

示例git

from django.db import models

class UserInfo(models.Model):
    username = models.CharField(
        null=True,
        db_column='user',
        max_length=32,
        db_index=True,
        verbose_name='用戶名',
        help_text='幫助信息',
        default='',
    )

    hobby_choices = [(0, '籃球'), (1, '足球'), (2, '羽毛球'),]

    hobby = models.IntegerField(
        choices=hobby_choices
    )

    def __str__(self):
        return self.username

2.3 元類 Meta

模型中的元類是指除了字段外的其餘非必須內容,如修改數據表的名字,設置代理等。ajax

使用方式sql

class User(models.Model):
    ...

    class Meta:
        verbose_name = '用戶'
1. abstract
爲 true 時,模型會被認爲是一個抽象類,長用來做爲其餘模型的父類被繼承。

2. app_label
若是沒有在 settings 中註冊 app,那麼必須在元類中聲名是屬於哪一個 app
app_label = 'polls'

3. base_manager_name
自定義模型的 _base_manager 管理器的名字,模型管理器是 Django 爲模型提供的 API

4. db_table
指定模型生成數據表時,數據表的表名
db_table = 'user'

5. db_tablespace
自定義數據庫表空間的名字。默認值是工程的DEFAULT_TABLESPACE設置

6. default_manager_name
自定義模型的_default_manager管理器的名字

7. default_related_name
反向查詢時,默認咱們使用的 `<model_name>_set` 也就是源模型名字+下劃線+set 方法查詢,當咱們定義了 default_related_name時,就可使用它來反向查詢。

8. ordering
指定該模型生成的全部對象的排序方式,接收一個字段名組成的元組或列表。默認按升序排列,若是在字段名前加上字符「-」則表示按降序排列,若是使用字符問號「?」表示隨機排列。

ordering = ['pub_date']             # 表示按'pub_date'字段進行升序排列
ordering = ['-pub_date']            # 表示按'pub_date'字段進行降序排列
ordering = ['-pub_date', 'author']  # 表示先按'pub_date'字段進行降序排列,再按`author`字段進行升序排列。

9. permissions
該元數據用於當建立對象時增長額外的權限。它接收一個全部元素都是二元元組的列表或元組,每一個元素都是(權限代碼, 直觀的權限名稱)的格式。好比下面的例子:

permissions = (("can_deliver_pizzas", "能夠送披薩"),)

10. default_permissions
Django默認給全部的模型設置('add', 'change', 'delete')的權限,也就是增刪改。你能夠自定義這個選項,好比設置爲一個空列表,表示你不須要默認的權限,可是這一操做必須在執行migrate命令以前

11. proxy

若爲 true,表示使用代理模式的模型繼承方式。

12. required_db_vendor

聲明模型支持的數據庫。Django默認支持sqlite, postgresql, mysql, oracle

13. indexes

接收一個應用在當前模型上的索引列表

from django.db import models

class Customer(models.Model):
    first_name = models.CharField(max_length=100)
    last_name = models.CharField(max_length=100)

    class Meta:
        indexes = [
            models.Index(fields=['last_name', 'first_name']),
            models.Index(fields=['first_name'], name='first_name_idx'),
        ]

14. unique_together

等同於數據庫的聯合約束,沒法應用多對多字段。
好比一張用戶表,保存用戶姓名、出生日期、地址等,如今有兩個叫張偉的人,那麼就可使用聯合惟一。

# 表示 name、birth_day、address 聯合惟一,即不能在同一地方、同一時刻出生且都叫張偉
unique_together = (('name', 'birth_day', 'address'),)

14. verbose_name
給 Admin 提供人性化的名稱,支持中文,如:
verbose_name = '用戶'   # 那麼 Admin 中顯示的就是 用戶

15. label
只讀元數據,不可修改,至關於 polls.Question

3. 多表關係及參數

  • 一對一:Foreignkey 基礎上加一個惟一索引 unique
  • 一對多:ForeignKey
  • 多對多:ManyToMany(兩個一對多,兩個 ForeignKey 相連)

3.1 一對多模型

一對多模型,如:員工部門表,一個部門能夠有多個員工。那麼 Django 怎麼創建這種關係呢?數據庫

其實就是經過外鍵 ForeignKey 進行關聯,在多的一方,字段指定外鍵便可

ForeignKey 字段參數

ForeignKey(ForeignObject) # ForeignObject(RelatedField)
        to,                         # 要進行關聯的表名
        to_field=None,              # 要關聯的表中的字段名稱
        on_delete=None,             # 當刪除關聯表中的數據時,當前表與其關聯的行的行爲
                - models.CASCADE,刪除關聯數據,與之關聯也刪除
                - models.DO_NOTHING,刪除關聯數據,引起錯誤IntegrityError
                - models.PROTECT,刪除關聯數據,引起錯誤ProtectedError
                - models.SET_NULL,刪除關聯數據,與之關聯的值設置爲null(前提FK字段須要設置爲可空)
                - models.SET_DEFAULT,刪除關聯數據,與之關聯的值設置爲默認值(前提FK字段須要設置默認值)
                - models.SET,刪除關聯數據,
                                a. 與之關聯的值設置爲指定值,設置:models.SET(值)
                                b. 與之關聯的值設置爲可執行對象的返回值,設置:models.SET(可執行對象)

                        def func():
                            return 10

                        class MyModel(models.Model):
                            user = models.ForeignKey(
                                to="User",      # 關聯 User 表
                                to_field="id"   # 關聯 User 表 的  id 字段
                                on_delete=models.SET(func),)

        related_name=None,          # 反向操做時,使用的字段名,用於代替 【表名_set】 如: obj.表名_set.all()
        related_query_name=None,    # 反向操做時,使用的鏈接前綴,用於替換【表名】     如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
        
        limit_choices_to=None,      # 在Admin或ModelForm中顯示關聯數據時,提供的條件:
                # 如:
                - limit_choices_to={'nid__gt': 5}
                - limit_choices_to=lambda : {'nid__gt': 5}

                from django.db.models import Q
                - limit_choices_to=Q(nid__gt=10)
                - limit_choices_to=Q(nid=8) | Q(nid__gt=10)
                - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
                
        db_constraint=True          # 是否在數據庫中建立外鍵約束
        parent_link=False           # 在Admin中是否顯示關聯數據

示例:

class Department(models.Model):
    """部門表"""
    name = models.CharField(max_length=32)
    create_data = models.DateField(auto_now_add=True)   # 建立時間
    id_delete = models.BooleanField(default=False)      # 是否可刪除
    
    class Meta:
        db_table = 'department'
    
    
class Employee(models.Model):
    """員工表"""
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    gender = models.IntegerField(default=0)
    salary = models.DecimalField(max_digits=8, decimal_places=2)    # max_digits 表示八個數字,包括兩位小數,decimal_places 表示保留兩位小數
    
    # null=True 表示能夠爲空, blank=True 表示 Django admin 後臺能夠輸入空
    comment = models.CharField(max_length=300, null=True, blank=True)   
    hire_data = models.DateField(auto_now_add=True)
    department = models.ForeignKey('Department')        # 外鍵字段
    
    class Meta:
        db_table = 'employee'       # 數據表名字

Tips

在設置外鍵時,須要給子表(有外鍵的表)指定當主表(被鏈接的表)刪除數據時,從表該如何處理。Django 經過設置 on_delete 屬性來控制,它有三種值:

  • 刪除時報錯:DO_NOTHING/PROTECT
  • 級聯刪除:主表刪除數據,從表與之關聯的數據也將被刪除:CASCADE
  • 設置默認值:SET_NULL/SET_DEFAULTset_null 僅在字段能夠是 null 時才能使用

3.2 一對一模型

OneToOneField(ForeignKey)
    to,                         # 要進行關聯的表名
    to_field=None               # 要關聯的表中的字段名稱
    on_delete=None,             # 當刪除關聯表中的數據時,當前表與其關聯的行的行爲
###### 對於一對一 ######
# 1. 一對一其實就是 一對多 + 惟一索引
# 2.當兩個類之間有繼承關係時,默認會建立一個一對一字段
# 以下會在A表中額外增長一個c_ptr_id列且惟一:
class C(models.Model):
    nid = models.AutoField(primary_key=True)
    part = models.CharField(max_length=12)

class A(C):
    id = models.AutoField(primary_key=True)
    code = models.CharField(max_length=1)
    
# 使用 OneToOneField 字段建立一對一模型
class Person(models.Model):
    name = models.CharField(max_length=32)
    o2o = models.OneToOneField(to='Person_detail', to_field='id', on_delete=models.CASCADE)
    
class Person_detal(models.Model):
    age = models.IntegerField()
    gender_choices = [(0, '男'), (1, '女')]
    gender = models.IntegerField(
        choices=gender_choices,
        default=0,
    )
    height = models.PositiveIntegerField()      # 正整數
    email = models.EmailField(max_length=64)

3.3 多對多模型

多對多其實就是兩個一對多,經過兩個外鍵將三張表相連,其中第三張表存儲了前面兩張表的對應關係。例如:名字和愛好表,一我的能夠有多個愛好,一個愛好也能夠是多我的有。

如何建立三張表:

  • 自動建立:ManyToMangField 字段自動建立,不能新增列
  • 手動建立:元 Meta,能夠新增列
  • 手動加自動:元 Meta + ManyToMangField

多對多 ManyToManyField 字段參數:

ManyToManyField(RelatedField)
        to,                         # 要進行關聯的表名
        related_name=None,          # 反向操做時,使用的字段名,用於代替 【表名_set】 如: obj.表名_set.all()
        related_query_name=None,    # 反向操做時,使用的鏈接前綴,用於替換【表名】     如: models.UserGroup.objects.filter(表名__字段名=1).values('表名__字段名')
        limit_choices_to=None,      # 在Admin或ModelForm中顯示關聯數據時,提供的條件:
                                    # 如:
                                            - limit_choices_to={'nid__gt': 5}
                                            - limit_choices_to=lambda : {'nid__gt': 5}

                                            from django.db.models import Q
                                            - limit_choices_to=Q(nid__gt=10)
                                            - limit_choices_to=Q(nid=8) | Q(nid__gt=10)
                                            - limit_choices_to=lambda : Q(Q(nid=8) | Q(nid__gt=10)) & Q(caption='root')
                                    
        symmetrical=None,           # 僅用於多對多自關聯時,symmetrical用於指定內部是否建立反向操做的字段
                                    # 作以下操做時,不一樣的symmetrical會有不一樣的可選字段
                                        models.BB.objects.filter(...)

                                        # 可選字段有:code, id, m1
                                            class BB(models.Model):

                                            code = models.CharField(max_length=12)
                                            m1 = models.ManyToManyField('self',symmetrical=True)

                                        # 可選字段有: bb, code, id, m1
                                            class BB(models.Model):

                                            code = models.CharField(max_length=12)
                                            m1 = models.ManyToManyField('self',symmetrical=False)

        through=None,               # 自定義第三張表時,使用字段用於指定關係表
        through_fields=None,        # 自定義第三張表時,使用字段用於指定關係表中那些字段作多對多關係表
                                        from django.db import models

                                        class Person(models.Model):
                                            name = models.CharField(max_length=50)

                                        class Group(models.Model):
                                            name = models.CharField(max_length=128)
                                            members = models.ManyToManyField(
                                                Person,
                                                through='Membership',
                                                through_fields=('group', 'person'),
                                            )

                                        class Membership(models.Model):
                                            group = models.ForeignKey(Group, on_delete=models.CASCADE)
                                            person = models.ForeignKey(Person, on_delete=models.CASCADE)
                                            inviter = models.ForeignKey(
                                                Person,
                                                on_delete=models.CASCADE,
                                                related_name="membership_invites",
                                            )
                                            invite_reason = models.CharField(max_length=64)
        db_constraint=True,         # 是否在數據庫中建立外鍵約束
        db_table=None,              # 默認建立第三張表時,數據庫中表的名稱

自動建立

class UserInfo(models.Model):
    username = models.CharField(max_length=32, verbose_name='用戶名')
    m = models.ManyToManyField(
        to = 'Tag',
        related_name = 'bb',
    )
    
class Tag(models.Model):
    title = models.CharField(max_length=32)

手動建立

class UserInfo(models.Model):
    username = models.CharField(max_length=32, verbose_name='用戶名')
    
class Tag(models.Model):
    title = models.CharField(max_length=32)
    
# 手動建立第三張表
class UserToTag(models.Model):
    tid = models.AutoField(primary_key=True)
    u = models.ForeignKey('UserInfo', on_delete=models.CASCADE)     # 經過外鍵相連
    t = models.ForeignKey('Tag', on_delete=models.CASCADE)

    #  聯合惟一索引
    class Meta:
        unique_together = [
            ('u', 't'),
        ]

手動加自動

class UserInfo(models.Model):
    username = models.CharField(max_length=32, verbose_name='用戶名')
    m = models.ManyToManyField(
        through = 'UserToTag',      # 指定第三張表,不自動建立
        through_fields = ['u', 't']     # 指定第三張表的字段
    )
    
class Tag(models.Model):
    title = models.CharField(max_length=32)
    
# 手動建立第三張表
class UserToTag(models.Model):
    tid = models.AutoField(primary_key=True)
    u = models.ForeignKey('UserInfo', on_delete=models.CASCADE)     # 經過外鍵相連
    t = models.ForeignKey('Tag', on_delete=models.CASCADE)

    #  聯合惟一索引
    class Meta:
        unique_together = [
            ('u', 't'),
        ]

3.4 自關聯模型

自關聯模型,即表中的某列關聯表中另外一列。最典型的自關聯模型就是地區表(省市縣三級聯動)省的 pid 爲 null,市的 pid 爲省的 id,縣的 pid 爲市的 id。具體以下:

自關聯表現形式

  • 省的 parent_id 爲 null
  • 市的 parent_id 爲對應省的 id
  • 區的 parent_id 爲對應市的 id
  1. 數據庫設計 models.py
class Area(models.Model):
    name = models.CharField(max_length=32, verbose_name='名稱')
    parent = models.ForeignKey('self',          # 自關聯字段的外鍵指向自身,也能夠是 Area
                               verbose_name='上級行政區劃',
                               on_delete=models.SET_NULL,
                               related_name='subs',
                               null=True,
                               blank=True
                               )

    class Meta:
        db_table = 'tb_areas'       # 自定義表名
        verbose_name = '行政區劃'   # admin 中顯示的名稱
        verbose_name_plural = '行政區劃'

    def __str__(self):
        return self.name

自關聯模型,最核心的地方就是本身關聯本身 self/Area,用一張表作兩張表才能作的事。

  1. 路由系統 app/urls.py
from django.urls import path
from app import views

urlpatterns = [
    path('area/', views.area, name='area'),
    path('getPro/', views.getPro, name='getArea'),
    path('getCity/', views.getCity, name='getCity'),
    path('getDis/', views.getDis, name='getDis'),
]
  1. 視圖函數 views.py
from django.shortcuts import render, HttpResponse
from app import models
from django.http import JsonResponse

# 訪問 http://127.0.0.1:8000/app/area/,返回 area.html
def area(request):
    return render(request, 'area.html')


def getPro(request):
    """獲取省份信息"""
    pro_list = models.Area.objects.filter(parent_id=None)       # 省份的 parent_id 爲 None
    res = []
    for i in pro_list:
        res.append([i.id, i.name])
    print(res)      # [[1, '廣東省'], [7, '湖南省']]
    return JsonResponse({'pro_list': res})      # JsonResponse 打包成 json 格式字符串


def getCity(request):
    """獲取市信息"""
    city_id = request.GET.get('city_id')
    city_list = models.Area.objects.filter(parent_id=int(city_id))
    res = []
    for i in city_list:
        res.append([i.id, i.name])

    print('city', res)      #  [[2, '深圳市'], [3, '廣州市'], [6, '湛江市']]
    return JsonResponse({'city_list': res})


def getDis(request):
    """獲取區信息"""
    dis_id = request.GET.get('dis_id')
    dis_list = models.Area.objects.filter(parent_id=int(dis_id))
    res = []
    for i in dis_list:
        res.append([i.id, i.name])
    return JsonResponse({'dis_list': res})
  1. 模板 area.html
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <select id="pro">
        <option value="">請選擇省份</option>
    </select>

    <select id="city">
        <option value="">請選擇城市</option>
    </select>

    <select id="dis">
        <option value="">請選擇區</option>
    </select>

<script src="{% static 'jquery-3.1.1.js' %}"></script>
<script>
    $(function () {
        var pro = $('#pro');
        var city = $('#city');
        var dis = $('#dis');

        // 查詢省信息,$.get() 爲 $.ajax 的簡化版
        // 向 /app/getPro/ 發送 ajax 請求,arg 爲請求成功返回的信息
        $.get('/app/getPro/', function (arg) {          
            console.log(arg);   // {'pro_list': [[1, '廣東省'],[7, '湖南省']]}

            $.each(arg.pro_list, function (index, item) {
                console.log(item);      // [1, '廣東省'] 、[7, '湖南省']
                console.log(index);     // 0、1
                
                // 將從數據庫中取出的數據添加到 option 標籤中,其中 value 爲 id,文本值爲 name
                var $new = $("<option value="+ item[0] +">" + item[1] + "</option>");
                pro.append($new);
            });
            
{# 或者使用 JavaScript for 循環#}
{#            console.log(arg.pro_list);#}
{#            for (var i = 0, len=arg.pro_list.length; i<len; i++){#}
{#                var $new = $('<option value='+ arg.pro_list[i][0] +'>' + arg.pro_list[i][1] + '</option>')#}
{#                pro.append($new);#}
{#            }#}

        });

        // 根據省的變化查詢市,改變省時,清空市和區
        pro.change(function () {
            city.empty().append('<option value="">請選擇市</option>');
            dis.empty().append('<option value="">請選擇市</option>');
            $.ajax({
                url: '/app/getCity/',
                type: 'get',
                data: {'city_id': $(this).val()},
                success: function (arg) {
                    $.each(arg.city_list, function (index, item) {
                        var $new = $('<option value='+ item[0]+'>' + item[1] + '</value>');
                        city.append($new);
                    })
                }
            })
        });

        // 根據市變化查詢區,改變市,清空區
        city.change(function () {
            dis.empty().append('<option value="">請選擇市</option>');
            $.ajax({
                url: '/app/getDis/',
                type: 'get',
                data: {'dis_id': $(this).val()},
                success: function (arg) {
                    console.log('區', arg.dis_list);     // [[4, '寶安區'], ]
                        $.each(arg.dis_list, function (index, item) {
                            console.log(item[1]);
                            var $new = $('<option value='+ item[0] +'>' + item[1] + '</value>');
                            dis.append($new);
                    })
                }
            })
        })

    })
</script>
</body>
</html>

當訪問 http://127.0.0.1:8000/app/area/ 時,會向 /app/getPro/ 發送 ajax 請求,請求成功得到包含省份 id、name 的信息 {'pro_list': [[1, '廣東省'],[7, '湖南省']]}。取出數據遍歷循環,添加到 option 標籤中,從而得到省份信息。

當咱們改變省份時,將上一次省份的市、區信息清空,避免添加劇復。獲取當前省份的 id,並向 /app/getDis/ 發送 ajax 請求,從而得到相應市的信息,同理區也是同樣。

  1. 添加數據 views.py

添加數據,能夠用 pycharm 自帶的數據庫添加,也能夠手動添加:

def add(request):
    models.Area.objects.create(id=7, name='湖南省', parent_id=None)
    models.Area.objects.create(id=8, name='長沙市', parent_id=7)
    models.Area.objects.create(id=9, name='雨花區', parent_id=8)

    return HttpResponse('添加成功')

參考博客:

3.5 知識點補充

Ajax 的 get() 方法,它能夠向後臺發送 ajax 請求,請求成功可調用回調函數。若是須要在出錯時執行函數,請使用 $.ajax,它是 $.ajax 簡化版本。

語法

$(selector).get(url, data, success(response, status, xhr), dataType);   // 除了 url 必需,其餘均可選,status 爲請求狀態,xhr - 包含 XMLHttpRequest 對象
$('button').click(function(){
    get('/app/getdata/', function(arg){});
});

3.6 Model 經常使用設計模型示例及方法

3.6.1 Model 設計

總共四張表,分別是班級、學生、學生我的信息以及老師表,之間的關係以下:

class Class(models.Model):
    """班級表"""
    class_name = models.CharField(max_length=32)
    create_data = models.DateField()

    def __str__(self):
        return self.class_name

    class Meta:
        verbose_name = '班級'


class Student(models.Model):
    """
    學生表
    一每一個學生都有對應的我的信息(一對一),一個班級能夠有多個學生(一對多)
    """
    student_name = models.CharField(max_length=32)

    # 一對多,與班級關聯
    sc = models.ForeignKey(to='Class', to_field='id', related_name='stu', on_delete=models.CASCADE)

    # 一對一,與學生我的信息關聯
    detail = models.OneToOneField('StudentDetail', to_field='id', on_delete=models.CASCADE)

    class Meta:
        verbose_name = '學生'

    def __str__(self):
        return self.student_name


class StudentDetail(models.Model):
    """學生我的信息表"""
    age = models.IntegerField()
    gender_choices = [(0, '男'), (1, '女')]
    gender = models.IntegerField(
        choices=gender_choices,
        default=0,
    )
    height = models.PositiveIntegerField()      # 正整數
    email = models.EmailField(max_length=64)


class Teacher(models.Model):
    """
    老師表
    一個老師能夠教多個班,一個班也能夠有多個老師
    """
    teacher_name = models.CharField(max_length=32)
    tc = models.ManyToManyField(to='Class', related_name='b')

    class Meta:
        verbose_name = '老師'

    def __str__(self):
        return self.teacher_name
  1. 老師表 app_teacher

  1. 學生我的信息表 app_student_detail

  1. 學生表 app_student

  1. 班級表 app_class

  1. 老師、班級關係表(第三張表)app_teacher_tc

3.6.2 查詢操做

def query(request):
    ########### 一對一 #####################
    # 正向查詢(根據外鍵字段)
    # 根據學生名字查詢其我的信息
    # obj = models.Student.objects.filter(student_name='rose')[0]
    # print(obj)
    # print(obj.detail.age, obj.detail.gender, obj.detail.height, obj.detail.email)
    # # 17 1 170 456@qq.com


    # 反向查詢(根據要查詢的數據表名)
    # 根據郵箱查詢學生名字
    # obj2 = models.StudentDetail.objects.filter(email='456@qq.com')[0]
    # print(obj2)         # <QuerySet [<StudentDetail: StudentDetail object (2)>]>
    #
    # print(obj2.student.student_name)        # rose


    ############## 一對多(班級表和學生表) ##################
    # 正向查詢
     # 根據學生名查詢所屬班級
    # obj3 = models.Student.objects.get(student_name='rose')
    # print(obj3.sc_id)   # 1
    # print(type(obj3.sc))      # <class 'app.models.Class'>
    # print(obj3.sc.class_name)   # 一班


    # 反向查詢
    # 二班有哪些學生
    # obj4 = models.Class.objects.filter(class_name='二班')[0]
    # print(obj4)
    # # res = obj4.student_set.all()     # 若是外鍵字段沒有設置 related_name 就用 表名_set
    # res = obj4.stu.all()
    # print(res)       # <QuerySet [<Student: john>, <Student: lila>]>
    # for i in res:
    #     print(i.student_name)   # john、lila

    # 方法二
    # ret = models.Student.objects.filter(sc=obj4).values('student_name')   # 字典形式
    # print(ret)      # <QuerySet [{'student_name': 'john'}, {'student_name': 'lila'}]>
    # for i in ret:
    #     print(i['student_name'])    # john、lila

    # # 雙下劃線
    # 正向
    obj4 = models.Class.objects.filter(class_name='二班').values('stu__student_name')

    # 反向
    obj5 = models.Student.objects.filter(sc__class_name='二班').values('student_name')



############################### 多對多(老師表與班級表) ############################################
    # 查看 孫老師教哪幾個班
    # 正向查詢(經過多對多字段)

    # obj5 = models.Teacher.objects.filter(teacher_name='孫老師')[0]
    # ret = obj5.tc.all()
    # print(ret)      # <QuerySet [<Class: 一班>, <Class: 二班>]>
    # for i in ret:
    #     print(i.class_name)

    # 查看一班有哪幾個老師
    # 反向查詢
    # obj5 = models.Class.objects.filter(class_name='一班')[0]
    # ret = obj5.b.all()
    # print(ret)          # < QuerySet[ < Teacher: 孫老師 >, < Teacher: 劉老師 >] >
    # for i in ret:
    #     print(i.teacher_name)       # 孫老師、劉老師

    # # 雙下劃線
    # # 若是沒有設置 related_name = b,那麼就是 values('teacher__name`) 即要查的表的表名__要查詢的字段
    # obj6 = models.Class.objects.filter(class_name='一班').values('b__teacher_name')
    # print(obj6)     # <QuerySet [{'b__teacher_name': '孫老師'}, {'b__teacher_name': '劉老師'}]>

    # # 正向  tc = models.ManyToManyField(to='Class', related_name='b')
    # obj6 = models.Teacher.objects.filter(tc__class_name='一班').values('teacher_name')

    return HttpResponse('查詢成功!')

3.6.3 總結

一對1、一對多、多對多,最好都設置好 related_name 參數,沒有設置時,反向查找用 **要查詢的命名.查詢字段 或 _set.all()**。

查詢分爲:

  • 基於對象查詢(至關於 SQL 中子查詢)
    • 先獲取相關對象:models.Models.objects.filter(過濾字段)
    • 再經過對象(正向、反向)查詢相關字段
  • 基於 QuerySet 和雙下劃線查詢(至關於 SQL 中連表查詢)
    • 前面是過濾條件,後面跟要顯示(查詢)的字段
    • models.Model.objects.filter(過濾字段).values(要顯示的字段)

一對一

  • 正向查詢:根據 OneToOneField 字段查詢
  • 反向查詢:根據 要查詢的命名.查詢字段查詢
# detail = models.OneToOneField('StudentDetail', to_field='id', on_delete=models.CASCADE)
# 正向
obj = models.Student.objects.filter(student_name='rose')[0]
print(obj.detail.age)

# 反向
obj2 = models.StudentDetail.objects.filter(email='456@qq.com')[0]
print(obj2.student.student_name)    # 這裏沒設置 related_name 所以用表名

一對多

  • 正向查詢:根據外鍵字段查詢
  • 反向查詢
    • 設置了related_name,就用這個名字查詢
    • 沒有設置,用表名_set 方式查詢
  • 雙下劃線查詢
    • 正向:filter(條件).values(要查詢的表名__要查詢字段)
    • 反向:filter(外鍵字段__條件).values(要查詢字段)
# sc = models.ForeignKey(to='Class', to_field='id', related_name='stu', on_delete=models.CASCADE)
# 正向
obj3 = models.Student.objects.get(student_name='rose')
print(obj3.sc.class_name)   # 根據外鍵字段 sc 查詢

# 反向
obj4 = models.Class.objects.filter(class_name='二班')[0]
# res = obj4.student_set.all()  # 沒設置 related_name,用表名_set
res = obj4.stu.all()        # 用 related_name 名字

# 雙下劃線
# 正向
obj5 = models.Class.objects.filter(class_name='二班').values('stu__student_name')

# 反向
obj6 = models.Student.objects.filter(sc__class_name='二班').values('student_name')

多對多

  • 正向查詢:ManyToManyField 字段查詢
  • 反向查詢:表名_set、related_name名字查詢
  • 雙下劃線與一對多同樣
# tc = models.ManyToManyField(to='Class', related_name='b')
# 正向
obj5 = models.Teacher.objects.filter(teacher_name='孫老師')[0]
print(obj5.tc.all())

# 反向
obj5 = models.Class.objects.filter(class_name='一班')[0]
print(obj5.b.all())

參考博客:django 一對1、一對多、多對多操做、經常使用方法

4. 模型繼承

Django中全部的模型都必須繼承 django.db.models.Model 模型,一樣地咱們也能夠本身建立一個父模型用來保存公共部分,子模型繼承父模型。這樣的好處就是有時會省去不少重複代碼。

一樣地 Django 也支持繼承多個父類。

Django 中三種繼承方式:

  • 抽象基類:被用來繼承的模型被稱爲 Abstract base classes,將子類共同的數據抽離,供子類複用,它不會建立實際的數據表
  • 多表繼承: Multi-table inheritance,每個模型都有本身的數據庫表
  • 代理模型:若是你只想修改模型的Python層面的行爲,並不想改動模型的字段,可使用代理模型

4.1 抽象基類

只需在模型中元類添加 abstract=True,便可將模型轉換爲抽象基類。可是它不會建立實際數據庫表,只能用來被繼承。

from django.db import models

class CommonInfo(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)

    class Meta:
        abstract = True


class User(CommonInfo):
    email = models.EmailField(max_length=60)

模型 User 將會有 username、password、email 三個字段。

抽象基類元類

若是子類沒有元類,那麼它將繼承基類的元類,一樣地子類也能夠對基類的元類進行拓展:

class CommonInfo(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)

    class Meta:
        abstract = True
        ordering = ['username']


class User(CommonInfo):
    email = models.EmailField(max_length=60)

    # 繼承基類的元類,並進行拓展
    class Meta(CommonInfo.Meta):
        db_table = 'username'
  • 基類有元類,子類也沒有的話,直接繼承
  • 基類有元類,子類也有,直接覆蓋
  • 子類能夠添加額外元數據
  • 基類的 abstract=true 不會被繼承
  • 基類的 db_table 元數據無效,由於抽象基類不會建立數據表

related_name 和 related_query_name

若抽象基類中存在 ForeignkManyToManyField 字段,且設置了 related_namerelated_query_name 參數,其子類也將繼承這兩個參數。可是在查詢時就會出現錯誤。

例如,對於 app01/models.py 中:

class Base(models.Model):
    m2m = models.ManyToManyField(User, related_name="%(app_label)s_%(class)s_related", related_query_name="%(app_label)s_%(class)ss")
    class Meta:
        abstract = True
        
class ChildA(Base):
    pass

class ChildB(Base):
    pass
  • app01.ChildA.m2m 字段:反向關係名(reverse name)爲 app01_childa_related;反向查詢名(reverse query name)爲 app01_childas
  • app01.ChildB.m2m 字段:分別爲: app01_childb_relatedapp01_childbs
  • 若是沒有設置 related_name 或 related_query_name 就沒有上述狀況

4.2 多表繼承

父類和子類都是可正常使用的模型,且都有本身的數據表,其本質爲 一對一 關係。

class UserInfo(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    
    
class PersonalDetail(UserInfo):
    email = models.EmailField()
    description = models.CharField(max_length=4000)

PersonalDetail 繼承 UserInfo,它會繼承父類全部的字段,兩個模型內部是一對一關係,以下所示:

>>> from polls.models import UserInfo, PersonalDetail
>>> q = PersonalDetail.objects.all()

>>> for i in q:
...     i.name
...
'rose'
'lina'

多表繼承之元類

多表繼承中子類不會繼承父類的元類,可是有兩個元類數據除外: ordering、get_latest_by,所以若但願不繼承父類的元類的全部元數據,就須要指定或重寫這兩個元數據:

class PersonalDetail(UserInfo):
    email = models.EmailField()
    description = models.CharField(max_length=4000)

    class Meta:
    # 重寫 ordering,移除父類元類的影響
        ordering = []

4.3 多重繼承

與 Python 同樣,模型也能夠繼承多個父類模型,當有多個父類都含有 Meta 類時,那麼只有第一個父類的會被繼承,其他將被忽略掉。

Tips

  • 儘可能避免不用多重繼承
  • 當父類有相同 id 主鍵字段時,將會報錯,所以須要給父類顯示地添加 AutoField 字段。
class Base1(models.Model):
    base1_id = models.AutoField(primary_key=True)
    pass

class Base2(models.Model):
    base2_id = models.AutoField(primary_key=True)
    pass

class Child(Base1, Base2):
    pass

4.4 代理模型

代理模型就是對源模型的一種代理,能夠建立、刪除、更新代理模型的實例,與源模型用的是同一張數據表,可是 它對源模型數據沒有影響

要想將一個模型設置爲代理模型,只需在 Meta 類中設置 proxy=True

class A(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()


class B(A):
    # 代理模型
    class Meta:
        proxy = True
        ordering = ['age']

    def do_something(self):
        pass

只會建立一張數據表,同時代理模型也能夠操做數據表:

from .models import A, B


>>> A.objects.create(name='rose', age=18)

>>> q = B.objects.get(name='rose')
>>> q.age
18

# 對代理模型排序,按照年齡從小到大排列,被代理的模型,查詢不會排序

obj_list = B.objects.all()
for obj in obj_list:
    print(obj.age)      # 1七、18 、20
    print(obj.name)     # lila、rose、tom

Tips

  • 代理模型必須繼承自一個非抽象的基類,而且不能同時繼承多個非抽象基類;
  • 代理模型能夠同時繼承任意多個抽象基類,前提是這些抽象基類沒有定義任何模型字段。
  • 代理模型能夠同時繼承多個別的代理模型,前提是這些代理模型繼承同一個非抽象基類。

4.5 總結

  • 抽象基類不會建立數據表,直接設置 abstract=True 便可設置爲抽象基類
  • 多表繼承實質是一對一關係,不會繼承基類 Meta,除 ordering 和 get_latest_by
  • 多表繼承最好不要用
  • 代理模型與被代理模型通用一張數據表,能夠對代理數據進行建立、刪除、修改等操做,但對源模型無影響
  • 若基類是非抽象基類,基類與子類有相同名字的字段,那麼將會覆蓋子類(抽象基類除外)
# A 中的 name 將會覆蓋 B 中 的 name,除非 A 是抽象基類
class A(models.Model):
    name = models.CharField(max_length=32)

    # class Meta:
    #     abstract = True

class B(A):
    name = models.CharField(max_length=32)

組織模型

當模型有不少時,咱們最好將模型分割開來,分類存儲,這樣有利於組織。咱們能夠利用包來組織模型。

在應用中建立一個名爲 models 的包(pycharm 能夠直接建立包,不須要手動建立 init.py文件),而後將模型分類到各個 .py 文件中,最後將其導入 __init__.py 文件中:

# app/models/__init__.py
from polls.models.modelone import User

5. 聚合分組

5.1 聚合 aggregate

aggregate() 返回一個字典,鍵爲聚合值標識符,值爲計算處理的聚合值。

使用時須要先導入內置函數:

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

示例:

from django.db.models import Avg, Sum, Max, Min, Count
>>> models.UserInfo.objects.all.aggregate(avg_age=Avg('age'))   # 指定鍵爲 avg_age,也能夠用默認的
{'avg_age': 18}

多個聚合:

from django.db.models import Avg, Sum, Max, Min, Count
>>> models.UserInfo.objects.all.aggregate(avg_age=Avg('age'), max_length=Max('height')) 
{'avg_age': 18, 'max_length': 182}

5.2 分組 annotate

ORM 中 values()values_list() 選字段,就至關於原生 SQL 中 select 什麼字段:

ret = models.Employee.objects.all()    # 至關於 select * from employee
"""
SELECT `employee`.`id`, `employee`.`name`, `employee`.`age`, `employee`.`salary`, `employee`.`province`, `employee`.`dept` FROM `employee` LIMIT 21; args=()
"""

ret = models.Employee.objects.all().values("dept", "age")
"""
SELECT `employee`.`dept`, `employee`.`age` FROM `employee` LIMIT 21; args=()
"""

select選中什麼字段,group by 就只能是什麼字段:

mysql> select name, Avg(salary) from app_employee group by name;
+------+-------------+
| name | Avg(salary) |
+------+-------------+
| rose |      2000.1 |
| lila |     3000.45 |
| john |     4000.56 |
| tom  |     5005.78 |
+------+-------------+

  1. 連表分組查詢

    按照部門分組,查詢每一個部門的平均薪水:

# ORM 操做
# 前一個 values() 表示按照什麼分組,後一個表示要顯示的字段
>>> employee_list = models.Employee.objects.values('dept__name').annotate(avg=Avg('salary')).values('dept__name', 'avg')
>>> print(employee_list)    
    
<QuerySet [{'dept__name': '財務部', 'avg': 2000.1}, {'dept__name': '事業部', 'avg': 4003.115}, {'dept__name': '人事部', 'avg': 4000.56}]>
# 對應 SQL 語句
SELECT `app_department`.`name`, AVG(`app_employee`.`salary`) AS `avg` FROM `app_employee` INNER JOIN `app_department` ON (`app_employee`.`dept_id` = `app_department`.`id`) GROUP BY `app_employee`.`name`, `app_department`.`name` ORDER BY NULL  LIMIT 21; args=()
  1. ORM 分組查詢

按照國家分組,查詢每一個國家的平均薪水:

>>> employee_list = models.Employee.objects.values('addr').annotate(avg=Avg('salary')).values('addr', 'avg')
>>> print(employee_list)    

 <QuerySet [{'addr': '美國', 'avg': 3502.9399999999996}, {'addr': '英國', 'avg': 3000.45}, {'addr': '德國', 'avg': 4000.56}]>
SELECT `app_employee`.`addr`, AVG(`app_employee`.`salary`) AS `avg` FROM `app_employee` GROUP BY `app_employee`.`addr` ORDER BY NULL  LIMIT 21; args=()

Tips

  • ORM 中的連表查詢(跨表)至關於 SQL 中的連表(inner john)查詢
  • ORM 查詢,第一個 values(fields) 爲分組字段,第二個 values(fileds1, fields2) 爲要顯示的字段
  • select選中什麼字段,group by 就只能是什麼字段

5. F 查詢和 Q 查詢

5.1 F 查詢

上面咱們都是在比較字段與某個常亮,要想兩個字段進行比較,那就要用到 F 查詢

  1. 查詢書的價格大於評論數的書籍
from django.db.models import F
models.Book.objects.filter(price__gt=F('comment_count'))
  1. 將書的價格總體提高 5 元
models.Book.objects.update(price=F('price')+5)
  1. F 對象和 F 對象以及常量之間能夠進行加減乘除、取模等
models.Book.objects.update(price=F('price')*5)

5.2 Q 查詢

Q 構建搜索條件(在這裏解決了或、與的問題)

  1. Q 對象能夠對關鍵字參數進行封裝,從而更好地應用多個查詢
obj1 = models.StudentDetail.objects.filter(Q(age=18))       # 構建過濾條件
  1. 組合使用 &、| 、~ 操做符
obj2 = models.StudentDetail.objects.filter(Q(age=18) | Q(age=19))   # 解決了 Django API 中沒有或的問題
obj3 = models.StudentDetail.objects.filter(Q(age=18) & Q(age=19))   # 解決了 Django API 中沒有與的問題
obj3 = models.StudentDetail.objects.filter(~ Q(age=19)) # 解決了 Django API 中沒有非的問題

手動建立邏輯關係

q1 = Q()        # 建立 Q 對象
q1.connector = 'OR'     # q1 內部爲 or 關係,即 age=18 或 age =19
q1.children.append(('age', 18))
q1.children.append(('age', 19))

q2 = Q()        # 建立 Q 對象
q2.connector = 'AND'    # q2 內部爲 and 關係
q2.children.append(('gender', 0))

q3 = Q()            # 建立 Q 對象
q3.add(q1, 'AND')       # 將 q一、q2 都添加到 q3 中,建立總的過濾條件
q3.add(q2, 'AND')
obj = models.StudentDetail.objects.filter(q3)
print(obj)  # <QuerySet [<StudentDetail: StudentDetail object (1)>]>

# 原生 SQL
SELECT "app_studentdetail"."id", "app_studentdetail"."age", "app_studentdetail"."gender", "app_studentdetail"."height", "app_studentdetail"."email" FROM "app_studentdetail" WHERE (("app_studentdetail"."age" = 18 OR "app_studentdetail"."age" = 19) AND "app_studentdetail"."gender" = 0)  LIMIT 21;
  1. Q 對象 能夠和關鍵字參數一塊兒查詢使用,無論必定要在關鍵字參數前面
models.StudentDetail.filter(Q(age=18), email__startswith='123')
  1. 應用
import datetime
obj = models.Class.objects.filter(Q(create_data=datetime.date(2019,2,15)))
print(obj)
相關文章
相關標籤/搜索