1. 程式人生 > >Django學習筆記6

Django學習筆記6

模型層:Object Relational Mapping(ORM)

ORM

  • 定義

ORM是Object Relational Mapping的簡稱,中文翻譯為物件關係模型,是一種為了解決面向物件與關係資料庫存在的互不匹配的現象的技術,ORM在業務邏輯層和資料庫層之間充當了橋樑的作用。

  • 由來

讓我們從O/R開始。字母O起源於"物件"(Object),而R則來自於"關係"(Relational)。

幾乎所有的軟體開發過程中都會涉及到物件和關係資料庫。在使用者層面和業務邏輯層面,我們是面向物件的。當物件的資訊發生變化的時候,我們就需要把物件的資訊儲存在關係資料庫中。

按照之前的方式來進行開發就會出現程式設計師會在自己的業務邏輯程式碼中夾雜很多SQL語句用來增加、讀取、修改、刪除相關資料,而這些程式碼通常都是重複的

  • 本質

每個模型都是一個Python類,它是django.db.models.Model的子類。

  • 優勢

ORM解決的主要問題是物件和關係的對映,按照規定的語法寫,自動翻譯成對應的SQL語句.

1.不用自己寫SQL語句
2. 開發效率高

  • 劣勢

ORM的缺點是會在一定程度上犧牲程式的執行效率。

ORM用多了SQL語句就不會寫了,關係資料庫相關技能退化...

  • ORM能做的事兒:

1. 操作資料表 ===> 建立表/刪除表/修改表
操作models.py裡面的類
2. 操作資料行 ===> 資料的增刪改查
不能建立資料庫,自己動手建立資料庫

Django中的ORM

Django專案如何使用ORM連線MySQL

1. 手動建立資料庫
2. 在settings.py裡面,配置資料庫的連線資訊

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'booksystem',
        'HOST': '127.0.0.1',
        'PORT': 3306,
        'USER': 'root',
        'PASSWORD': 'root',
    }
}

3. 在專案/__init__.py告訴Django用pymysql模組代替MySQLdb來連線MySQL資料庫

import pymysql
pymysql.install_as_MySQLdb()

4. 在app/models.py裡面定義類

# 作者類
class Author(models.Model):
    id = models.AutoField(primary_key=True)  # 自增的ID主鍵
    name = models.CharField(max_length=16, null=False, unique=True)
    book = models.ManyToManyField(to="Book")  # 建立作者表和書籍表多對多的關係
    # 多對多的關係會在資料庫中另建立一個新的對應關係表,只存放id的對應關係

    def __str__(self):
        return "<Author object>: {}".format(self.name)

5. 執行兩個命令

在哪兒執行?
在專案的根目錄(有manage.py檔案的那個目錄)

  • python3 manage.py makemigrations --> 把models.py裡面的更改記錄到小本本上
  • python3 manage.py migrate --> 把更改翻譯成SQL語句,去資料庫執行

簡單使用

下面這個例子定義了一個 Person 模型,包含 first_name 和 last_name

from django.db import models

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

first_name 和 last_name 是模型的欄位。每個欄位被指定為一個類屬性,每個屬性對映到一個數據庫列。

上面的 Person 模型將會像這樣建立一個數據庫表:

CREATE TABLE myapp_person (
    "id" serial NOT NULL PRIMARY KEY,
    "first_name" varchar(30) NOT NULL,
    "last_name" varchar(30) NOT NULL
);

一些說明:

  • 表myapp_person的名稱是自動生成的,如果你要自定義表名,需要在model的Meta類中指定 db_table 引數,強烈建議使用小寫表名,特別是使用MySQL作為後端資料庫時。
  • id欄位是自動新增的,如果你想要指定自定義主鍵,只需在其中一個欄位中指定 primary_key=True 即可。如果Django發現你已經明確地設定了Field.primary_key,它將不會新增自動ID列
  • 本示例中的CREATE TABLE SQL使用PostgreSQL語法進行格式化,但值得注意的是,Django會根據配置檔案中指定的資料庫後端型別來生成相應的SQL語句。
  • Django支援MySQL5.5及更高版本。

欄位型別

單表字段型別

  • 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_now = True 每次修改資料行(物件)時會把當前時間新增到資料庫中
  • TimeField(DateTimeCheckMixin, Field)

- 時間格式 HH:MM[:ss[.uuuuuu]]

  • DurationField(Field)

- 長整數,時間間隔,資料庫中按照bigint儲存,ORM中獲取的值為datetime.timedelta型別

  • FloatField(Field)

- 浮點型

  • DecimalField(Field)

- 10進位制小數
- 引數:

max_digits,小數總長度
decimal_places,小數位長度
  • BinaryField(Field)

- 二進位制型別

關聯關係欄位

Django還定義一系列欄位來描述資料庫之間的關聯。

ForeignKey(多對一

class  ForeignKey(toon_delete**options)

多對一關係。要求兩個位置引數:模型相關的類和on_delete選項。 on_delete實際上並不是必需的,但不提供它會給出已廢棄的警告。 在Django 2.0中將需要它。)

若要建立遞迴關聯關係 ——————— 一個物件與自己具有多對一關聯關係 — 請使用models.ForeignKey('self', on_delete=models.CASCADE)

  • 引用關係(三種情況)

如果你需要關聯到一個還沒有定義的模型,你可以使用模型的名字而不用模型物件本身:

from django.db import models

class Car(models.Model):
    manufacturer = models.ForeignKey(
        'Manufacturer',
        on_delete=models.CASCADE,
    )
    # ...

class Manufacturer(models.Model):
    # ...
    pass

抽象模型上定義的這種關聯關係在模型子類化為具體模型時解析,並且不相對於抽象模型的app_label

products/models.py
from django.db import models

class AbstractCar(models.Model):
    manufacturer = models.ForeignKey('Manufacturer', on_delete=models.CASCADE)

    class Meta:
        abstract = True
production/models.py
from django.db import models
from products.models import AbstractCar

class Manufacturer(models.Model):
    pass

class Car(AbstractCar):
    pass

# Car.manufacturer將指向這裡的`production.Manufacturer`。

若要引用在其它應用中定義的模型,你可以用帶有完整標籤名的模型來顯式指定。 例如,如果上面的Manufacturer模型是在一個名為production的應用中定義的,你應該這樣使用它:

class Car(models.Model):
    manufacturer = models.ForeignKey(
        'production.Manufacturer',
        on_delete=models.CASCADE,
    )

這種稱為懶惰關係的引用在解析兩個應用程式之間的迴圈匯入依賴關係時可能很有用。

  • 資料庫表示

在幕後,Django 會在欄位名上新增"_id" 來建立資料庫中的列名。 在上面的例子中,Car 模型的資料庫表將會擁有一個manufacturer_id 列。 (你可以通過指定db_column來顯式更改)但是,除非你編寫自定義SQL,否則程式碼不應該處理資料庫列名。 你應該永遠只處理你的模型物件中的欄位名稱。

ForeignKey 會自動建立資料庫索引。 你可以通過設定db_index 為False 來取消。 如果你建立外來鍵是為了一致性而不是用來Join,或者如果你將建立其它索引例如部分或多列索引,你也許想要避免索引的開銷。

  • 引數

ForeignKey.to
  要進行關聯的表名,to=“User”和to=User,to本質是和類做關聯,當User類在當前模組被匯入時,才可以不加雙引號,否則要加雙引號
ForeignKey.on_delete
   當刪除由ForeignKey引用的物件時,Django將模擬由on_delete引數指定的SQL約束的行為。  自1.9版以來已棄用 on_delete將成為Django 2.0中必需的引數。 在舊版本中,預設為CASCADE。
  • CASCADE
    級聯刪除,刪除關聯資料,與之關聯也刪除,Django模擬SQL約束ON DELETE CASCADE的行為,並刪除包含ForeignKey的物件。
  • PROTECT
    丟擲 ProtectedError 以阻止被引用物件的刪除,它是 django.db.IntegrityError 的一個子類。
  • SET_NULL

    刪除關聯資料,與之關聯的值設定為null(前提FK欄位需要設定為可空)

  • SET_DEFAULT
    刪除關聯資料,與之關聯的值設定為預設值(前提FK欄位需要設定預設值)
  • SET()
    設定 ForeignKey 為傳遞給 SET() 的值,如果傳遞的是一個可呼叫物件,則為呼叫後的結果。  在大部分情形下,傳遞一個可呼叫物件用於避免models.py 在匯入時執行查詢:
    a. 與之關聯的值設定為指定值,設定:
    models.SET(值)
    b. 與之關聯的值設定為可執行物件的返回值,設定:
    class MyModel(models.Model):
        user = models.ForeignKey(
            to="User",
            to_field="id",
            on_delete=models.SET(func), 
        )
  • DO_NOTHING

    不採取任何動作。 如果您的資料庫後端強制引用完整性,除非手動新增SQL ON DELETE約束,否則將導致IntegrityError到資料庫欄位。

ForeignKey.limit_choices_to

當這個欄位使用ModelForm或者Admin 渲染時(預設情況下,查詢集中的所有物件都可以使用),為這個欄位設定一個可用的選項。 它可以是一個字典、一個Q 物件或者一個返回字典或Q物件的可呼叫物件。

# 在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) | 
ForeignKey.related_name

反向操作時,使用的欄位名,用於代替 【表名_set】 如: obj.表名_set.all()。它還是related_query_name 的預設值。

如果你不想讓Django 建立一個反向關聯,請設定related_name 為 '+' 或者以'+' 結尾。 例如,下面這行將確定User 模型將不會有到這個模型的返回關聯:

user = models.ForeignKey(
    User,
    on_delete=models.CASCADE,
    related_name='+',
)
ForeignKey.related_query_name

反向操作時,使用的連線字首,用於替換【表名】 如:

models.UserGroup.objects.filter(表名__欄位名=1).values('表名__欄位名')

  用於過濾器或者value,不直接用於.後面

ForeignKey.to_field

關聯到的關聯物件的欄位名稱。 預設地,Django 使用關聯物件的主鍵。 如果引用其他欄位,該欄位必須具有unique=True

ForeignKey. db_constraint

控制是否在資料庫中為這個外來鍵建立約束。 預設值為True,這幾乎是你想要的;將此設定為False可能對資料完整性非常不利。 即便如此,有一些場景你也許想要這麼設定:

  • 你有遺留的無效資料。
  • 你正在對資料庫縮容。

如果被設定成False,訪問一個不存在的關聯物件將丟擲 DoesNotExist 異常。

ManyToManyField(多對多)

class  ManyToManyField(to**options)[source]

一個多對多關聯。關聯的物件可以通過欄位的RelatedManager 新增、刪除和建立。

資料庫表示

在幕後,Django 建立一箇中間表來表示多對多關係。 預設情況下,這張中間表的名稱使用多對多欄位的名稱和包含這張表的模型的名稱生成。 因為某些資料庫支援的表的名字的長度有限制,這些表的名稱將自動截短到64 個字元並加上一個唯一性的雜湊值。 這意味著你可能會看到像author_books_9cdf4這樣的表名;這是完全正常的。 你可以使用db_table選項手工提供中間表的名稱。

引數

  • ManyToManyField.to
  • ManyToManyField.to_filed
  • ManyToManyField.on_delete
  • ManyToManyField.related_name
  • ManyToManyField.related_query_name

上面幾個屬性參考foreignerkey的引數即可相同。

  • ManyToManyField.limit_choices_to

ForeignKey.limit_choices_to 相同。

ManyToManyField 對於使用through 引數自定義中間表的limit_choices_to 不生效。

  • ManyToManyField.symmetrical

只用於與自身進行關聯的ManyToManyField。 例如下面的模型:

from django.db import models

class Person(models.Model):
    friends = models.ManyToManyField("self")

當Django 處理這個模型的時候,它定義該模型具有一個與自身具有多對多關聯的ManyToManyField,因此它不會向person_set 類新增Person 屬性。 Django 將假定這個ManyToManyField 欄位是對稱的 —— 如果我是你的朋友,那麼你也是我的朋友。

如果你希望與self 進行多對多關聯的關係不具有對稱性,可以設定symmetrical 為False。 這會強制讓Django 新增一個描述器給反向的關聯關係,以使得ManyToManyField 的關聯關係不是對稱的。

  • ManyToManyField.through    (使用自己指定的第三方表)

Django 會自動建立一個表來管理多對多關係。 不過,如果你希望手動指定中介表,可以使用through 選項來指定Django 模型來表示你想要使用的中介表。

這個選項最常見的使用場景是當你想要關聯更多的資料到關聯關係的時候。

如果你沒有顯式指定through 的模型,仍然會有一個隱式的through 模型類,你可以用它來直接訪問對應的表示關聯關係的資料庫表。 它由三個欄位來連結模型。

如果源模型和目標不同,則生成以下欄位:

  • id:關係的主鍵。
  • <containing_model>_id:聲明瞭ManyToManyField的模型的id
  • <other_model>_id: 被ManyToManyField所指向的模型的id

如果ManyToManyField 的源模型和目標模型相同,則生成以下欄位:

  • id:關係的主鍵。
  • from_<model>_id:源模型例項的id
  • to_<model>_id:目標模型例項的id

這個類可以讓一個給定的模型像普通的模型那樣查詢與之相關聯的記錄。

  • ManyToManyField.through_fields

只能在指定了自定義中間模型的時候使用。 Django 一般情況會自動決定使用中間模型的哪些欄位來建立多對多關聯。 但是,考慮如下模型:

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)

Membership兩個 foreign keys指向 Person (person and inviter), 這樣會導致關係不清晰,Django不知道使用哪一個外來鍵。 在這種情況下,你必須使用through_fields 明確指定Django 應該使用哪些外來鍵,就像上面例子一樣。

through_fields 接受一個2元陣列 ('field1', 'field2'), 其中 field1是指向定義了ManyToManyField的那個model的 foreign key的名字(在本例中就是group,它自己定義了M2M欄位,同時也在中間模型中被ForeignKey所指向 ), and field2就是目標模型的foreign key 的名字 (person in this case).

當中間模型具有多個外來鍵指向多對多關聯關係模型中的任何一個(或兩個),你必須 指定through_fields。 這通用適用於recursive relationships,當用到中間模型而有多個外來鍵指向該模型時,或當你想顯式指定Django 應該用到的兩個欄位時。

遞迴的關聯關係使用的中間模型始終定義為非對稱的,也就是symmetrical=False —— 所以具有源和目標的概念。 這種情況下,'field1' 將作為管理關係的源,而'field2' 作為目標。

  • ManyToManyField.db_table

為儲存多對多資料而建立的表的名稱。 如果沒有提供,Django 將基於定義關聯關係的模型和欄位假設一個預設的名稱。

  • ManyToManyField.db_constraint

控制中間表中的外來鍵是否建立約束。 預設值為True,這幾乎是你想要的;將此設定為False可能對資料完整性非常不利。 即便如此,有一些場景你也許想要這麼設定:

  • 你有遺留的無效資料。
  • 你正在對資料庫縮容。

不可以同時傳遞db_constraint 和 through

OneToOneField(一對一)

class  OneToOneField(toon_deleteparent_link=False**options)[source]

一對一關聯關係。 概念上講,這個欄位類似ForeignKey設定了unique=True,不同的是關聯關係的另一邊會直接返回單個物件。選定了以後別的不能選了,不像多對一,別的還能選

什麼時候用一對一?
當 一張表的某一些欄位查詢的比較頻繁,另外一些欄位查詢的不是特別頻繁,把不怎麼常用的欄位,單獨拿出來做成一張表,然後用過一對一關聯起來。

一對一關係使用案例

# 作者
class Author(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()
    phone = models.IntegerField()
    books = models.ManyToManyField(to="Book", related_name="authors")
    detail = models.OneToOneField(to="AuthorDetail")

    def __str__(self):
        return self.name


# 作者詳情
class AuthorDetail(models.Model):
    # 愛好
    hobby = models.CharField(max_length=32)
    # 地址
    addr = models.CharField(max_length=128)

  注意:一對一關係時,預設值會不好使,以為這列不能有相同的,全為none也不行

如果你沒有指定OneToOneField 的related_name 引數,Django 將使用當前模型的小寫的名稱作為預設值。

例如下面的例子:

from django.conf import settings
from django.db import models

class MySpecialUser(models.Model):
    user = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
    )
    supervisor = models.OneToOneField(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name='supervisor_of',
    )

你將使得User 模型具有以下屬性:

>>> user = User.objects.get(pk=1)
>>> hasattr(user, 'myspecialuser')
True
>>> hasattr(user, 'supervisor_of')
True

自定義欄位

class UnsignedIntegerField(models.IntegerField):

    def db_type(self, connection):

        return 'integer UNSIGNED'



PS: 返回值為欄位在資料庫中的屬性,Django欄位預設的值為:

    'AutoField': 'integer AUTO_INCREMENT',

    'BigAutoField': 'bigint AUTO_INCREMENT',

    'BinaryField': 'longblob',

    'BooleanField': 'bool',

    'CharField': 'varchar(%(max_length)s)',

    'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',

    'DateField': 'date',

    'DateTimeField': 'datetime',

    'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',

    'DurationField': 'bigint',

    'FileField': 'varchar(%(max_length)s)',

    'FilePathField': 'varchar(%(max_length)s)',

    'FloatField': 'double precision',

    'IntegerField': 'integer',

    'BigIntegerField': 'bigint',

    'IPAddressField': 'char(15)',

    'GenericIPAddressField': 'char(39)',

    'NullBooleanField': 'bool',

    'OneToOneField': 'integer',

    'PositiveIntegerField': 'integer UNSIGNED',

    'PositiveSmallIntegerField': 'smallint UNSIGNED',

    'SlugField': 'varchar(%(max_length)s)',

    'SmallIntegerField': 'smallint',

    'TextField': 'longtext',

    'TimeField': 'time',

    'UUIDField': 'char(32)',

  自定義char型別欄位:

class FixedCharField(models.Field):
    """
    自定義的char型別的欄位類
    """
    def __init__(self, max_length, *args, **kwargs):
        self.max_length = max_length
        super(FixedCharField, self).__init__(max_length=max_length, *args, **kwargs)

    def db_type(self, connection):
        """
        限定生成資料庫表的欄位型別為char,長度為max_length指定的值
        """
        return 'char(%s)' % self.max_length


class Class(models.Model):
    id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=25)
    # 使用自定義的char型別的欄位
    cname = FixedCharField(max_length=25)

  建立的表結構:

欄位通用引數

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': '格式錯誤'}