Django Model


1. ORM 簡介

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

我們不可能自己手動去寫大量的 SQL 語句,因為我們也不是專業 DBA 人員,那么我們就只能奢求有一種能夠使用 Python 程序對數據庫操作的方法了。這就是 ORM(Object Relation Mapping)對象關系映射,以面向對象的方式去操作數據庫。

其實現模式大致是這樣的:

Django 本身給我們提供了強大的 ORM 系統,不需要再額外的
安裝,當然還有一些其他的 ORM ,如:SQLAlch 等。

2. 字段

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

2.1 常用字段類型

Django 內置了很多字段類型,都位於 django.db.models 中,以下為常用字段類型:

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,即唯一且不能為空,你也可以自己將其他字段設置為空,若有必要。 

示例

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

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

使用方式

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 總結

一對一、一對多、多對多,最好都設置好 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 一對一、一對多、多對多操作、常用方法

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)      # 17、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')		# 將 q1、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)


免責聲明!

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



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