1. 程式人生 > 實用技巧 >Django模型層之單表操作

Django模型層之單表操作

ORM簡介

查詢資料層次圖解:如果操作mysql,ORM是在pymysql之上又進行了一層封裝

  • MVC或者MTV框架中一個重要組成部分,就是ORM,它實現了資料模型與資料庫的解耦,即資料型別的設計不需要依賴於特定的資料庫通過簡單的配置就可以輕鬆更換資料庫,這極大的減輕了開發人員的工作量,不需要面對因資料庫變更而導致的無效勞動
  • ORM是“物件-關係-對映”的簡稱

例子

#sql中的表                                                      

 #建立表:
     CREATE TABLE employee(                                     
                id INT PRIMARY KEY auto_increment ,                    
                name VARCHAR (20),                                      
                gender BIT default 1,                                  
                birthday DATA ,                                         
                department VARCHAR (20),                                
                salary DECIMAL (8,2) unsigned,                          
              );


  #sql中的表紀錄                                                  

  #新增一條表紀錄:                                                          
      INSERT employee (name,gender,birthday,salary,department)            
             VALUES   ("alex",1,"1985-12-12",8000,"保潔部");               

  #查詢一條表紀錄:                                                           
      SELECT * FROM employee WHERE age=24;                               

  #更新一條表紀錄:                                                           
      UPDATE employee SET birthday="1989-10-24" WHERE id=1;              

  #刪除一條表紀錄:                                                          
      DELETE FROM employee WHERE name="alex"                             





#python的類
class Employee(models.Model):
     id=models.AutoField(primary_key=True)
     name=models.CharField(max_length=32)
     gender=models.BooleanField()
     birthday=models.DateField()
     department=models.CharField(max_length=32)
     salary=models.DecimalField(max_digits=8,decimal_places=2)


 #python的類物件
      #新增一條表紀錄:
          emp=Employee(name="alex",gender=True,birthday="1985-12-12",epartment="保潔部")
          emp.save()
      #查詢一條表紀錄:
          Employee.objects.filter(age=24)
      #更新一條表紀錄:
          Employee.objects.filter(id=1).update(birthday="1989-10-24")
      #刪除一條表紀錄:
          Employee.objects.filter(name="alex").delete()

1 更多的欄位和引數

每個欄位有一些特有的引數,例如,CharField需要max_length引數來指定VARCHAR資料庫欄位的大小。還有一些適用於所有欄位的通用引數。 這些引數在文件中有詳細定義,這裡我們只簡單介紹一些最常用的:

欄位

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

    自定義無符號整數字段

        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)',

    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)
        - 字串,路徑儲存在資料庫,檔案上傳到指定目錄,本質是varchar
        - 引數:
            upload_to = ""      上傳檔案的儲存路徑
            storage = None      儲存元件,預設django.core.files.storage.FileSystemStorage

    ImageField(FileField)
        - 字串,路徑儲存在資料庫,檔案上傳到指定目錄,本質是varchar繼承了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

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

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

    FloatField(Field)
        - 浮點型

    DecimalField(Field)
        - 10進位制小數
        - 引數:
            max_digits,小數總長度
            decimal_places,小數位長度

    BinaryField(Field)
        - 二進位制型別

引數

(1)null
 
如果為True,Django 將用NULL 來在資料庫中儲存空值。 預設值是 False.
 
(1)blank
 
如果為True,該欄位允許不填。預設為False。
要注意,這與 null 不同。null純粹是資料庫範疇的,而 blank 是資料驗證範疇的。
如果一個欄位的blank=True,表單的驗證將允許該欄位是空值。如果欄位的blank=False,該欄位就是必填的。
 
(2)default
 
欄位的預設值。可以是一個值或者可呼叫物件。如果可呼叫 ,每有新物件被建立它都會被呼叫。
 
(3)primary_key
 
如果為True,那麼這個欄位就是模型的主鍵。如果你沒有指定任何一個欄位的primary_key=True,
Django 就會自動新增一個IntegerField欄位做為主鍵,所以除非你想覆蓋預設的主鍵行為,
否則沒必要設定任何一個欄位的primary_key=True。
 
(4)unique
 
如果該值設定為 True, 這個資料欄位的值在整張表中必須是唯一的
 
(5)choices
由二元組組成的一個可迭代物件(例如,列表或元組),用來給欄位提供選擇項。 如果設定了choices ,預設的表單將是一個選擇框而不是標準的文字框,<br>而且這個選擇框的選項就是choices 中的選項。

(6)auto_now 和 auto_now_add
# 相同:
兩者都是預設傳入當前的時間,即進行原生新增記錄的資料庫操作,即當資料物件呼叫save()方法時即使是沒有輸入時間,也會自動記錄下當前的時間傳入。

# 不同:
auto_now建立物件記錄下當前時間後,如果該物件呼叫update()方法進行記錄的更新,那麼即使沒有特意去更換當前的時間,該欄位也會自動記錄下當前的時間。
auto_now_add則是建立物件記錄當前時間後,不會隨著呼叫update()方法實時更新自己欄位下的記錄,除非是強行更改。

# 注意:
 res = models.Work.objects.filter(pk=72).first()
    res.ip = '1111.111.111'
    res.save()
用這種方式將物件單獨取出來,進行更改,而不是取出querset物件。這樣才能觸發實時更新時間的效果

# models.Work.objects.filter(pk=72).update(ip='111.111.111')
可以修改但是不能更新時間

(7)db_index
設定子索引

元資料

class Meta:    
  # 表名
  	db_table = 'book'
  # 聯合索引
  	index_together = [
      	('name','publish'),
    ]
  
  # 聯合唯一索引:聯合欄位下的記錄不能出現同時重複的物件(一條資料)
   unique_together = (("name", "publish"),)
    
# 瞭解

	      # admin中顯示的表名稱,會自動加上s表示為複數
        verbose_name='圖書表'
        # verbose_name取消s
        verbose_name_plural='圖書表'

  
    

2 settings配置

若想將模型轉為Mysql資料庫中的表,需要在settings中配置:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'lqz',
        'USER': 'root',
        'PASSWORD': '123456',
        'HOST': '127.0.0.1',
        'PORT': 3306,
        'ATOMIC_REQUEST': True,
        'OPTIONS': {
            "init_command": "SET storage_engine=MyISAM",
        }
    }
}

'''
NAME':要連線的資料庫,連線前需要建立好
'USER':連線資料庫的使用者名稱
'PASSWORD':連線資料庫的密碼
'HOST':連線主機,預設本機
'PORT':埠 預設3306
'ATOMIC_REQUEST': True,
設定為True統一個http請求對應的所有sql都放在一個事務中執行(要麼所有都成功,要麼所有都失敗)。
是全域性性的配置, 如果要對某個http請求放水(然後自定義事務),可以用non_atomic_requests修飾器 
'OPTIONS': {
             "init_command": "SET storage_engine=MyISAM",
            }
設定建立表的儲存引擎為MyISAM,INNODB
'''

注意1:NAME即資料庫的名字,在mysql連線前該資料庫必須已經建立,而上面的sqlite資料庫下的db.sqlite3則是專案自動建立 USER和PASSWORD分別是資料庫的使用者名稱和密碼。設定完後,再啟動我們的Django專案前,我們需要啟用我們的mysql。然後,啟動專案,會報錯:no module named MySQLdb 。這是因為django預設你匯入的驅動是MySQLdb,可是MySQLdb 對於py3有很大問題,所以我們需要的驅動是PyMySQL 所以,我們只需要找到專案名(app)檔案下的__init__,在裡面寫入:

import pymysql
pymysql.install_as_MySQLdb()

最後通過兩條資料庫遷移命令即可在指定的資料庫中建立表 :

python manage.py makemigrations # 產生資料相關操作的日誌
python manage.py migrate				# 真正將資料遷移到資料庫中

注意2:確保配置檔案中的INSTALLED_APPS中寫入我們建立的app名稱

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    "book"
]

增加刪除欄位

​ 刪除,直接註釋掉欄位,執行資料庫遷移命令即可

  新增欄位,在類裡直接新增欄位,直接執行資料庫遷移命令會提示輸入預設值,此時需要設定

publish = models.CharField(max_length=12,default='人民出版社',null=True) 

# 需要匹配之前已經輸入過的欄位,保證每條資料即每個物件的欄位下記錄的完整性,所以設定預設值為空

3 新增表記錄

方式1

# create方法的返回值book_obj就是插入book表中的python葵花寶典這本書籍紀錄物件
book_obj=Book.objects.create(title="python葵花寶典",state=True,price=100,publish="蘋果出版社",pub_date="2012-12-12")

方式2

book_obj=Book(title="python葵花寶典",state=True,price=100,publish="蘋果出版社",pub_date="2012-12-12")
book_obj.save()

注意

'''
當建立一對多,或多對多,一對一,存在外來鍵時
在 Django 刪除物件時,會模仿 SQL 約束 ON DELETE CASCADE 的行為,換句話說,刪除一個物件時也會刪除與它相關聯的外來鍵物件。例如:
'''
b = Blog.objects.get(pk=1)
# This will delete the Blog and all of its Entry objects.
b.delete()

# 如果不想聯級刪除,可以設定為:

pubHouse = models.ForeignKey(to='Publisher', on_delete=models.SET_NULL, blank=True, null=True)

'''
要注意的是: delete() 方法是 QuerySet 上的方法,但並不適用於 Manager 本身。這是一種保護機制,是為了避免意外地呼叫 Entry.objects.delete() 方法導致 所有的 記錄被誤刪除。如果你確認要刪除所有的物件,那麼你必須顯式地呼叫:

'''
Entry.objects.all().delete() 

4 刪除表記錄

# 刪除的兩種方式

# 第一種:queryset的delete方法
	book = models.Book.filter(pk=2).delete()
  print(type(book)) # queryset 物件
  
# 第二種:物件自己的delete方法
	book = models.Book.objects.all().filter(name='嘻哈猴').first()
  print(type(book))  # python中的dict型別
	book.delete()

5 修改表記錄

# 修改記錄

# 第一種:queryset的update方法 
	 res=models.Book.objects.filter(pk=30).update(ip='111.111')
   print(res)
# 對應的原生sql
UPDATE `app10_work` SET `ip` = '111.111' WHERE `app10_work`.`id` = 30; args=('111.111', 30)
# 只改變了ip的值,沒有觸發 auto_now 的屬性,時間沒有隨之更改
    
# 第二種:物件自己的
# 注意:如果要觸發auto_now的修改物件則自動更新當前時間的屬性用此種方式修改記錄
	 book = models.Book.objects.filter(pk=12).first()
   book.name='asdfasd'
   book.save()
# 原生sql
UPDATE `app10_work` SET `ip` = '1111.111.111', `date` = '2020-10-10', `addr` = '/app10/index', `date_update` = '2020-10-13 15:47:26.951295' WHERE `app10_work`.`id` = 12; args=('1111.111.111', '2020-10-10', '/app10/index', '2020-10-13 15:47:26.951295', 12)
# 整體進行重新賦值
# date不變 auto_now_add 引數
# update隨之改變為當前時間 auto_now 引數


6 在Python指令碼中呼叫Django環境

# tests.py 在專案下

import os
if __name__ == '__main__':
    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "untitled15.settings")
    import django
    django.setup()

    from app01 import models

    books = models.Book.objects.all()
    print(books)

7 Django終端列印SQL語句

如果想列印orm轉換過程中的sql,需要在settings中進行如下配置:

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

8 查詢表記錄API

1.all():							查詢所有的結果,每一條資料(ORM中的每一個數據物件)
2.filter(**kwargs)		它包含了與所給篩選條件相匹配的物件

3.get(**kwargs)				返回與所給篩選條件相匹配的物件,返回結果有且只有一個,如果符合篩選條件的											物件超過一個或者沒有都會丟擲錯誤
4.exclude(**kwargs)		它包含了與所給篩選條件不匹配的物件如exclude(pk=2)返回除了此主鍵的資料
5.order_by()					對查詢結果排序預設從小到大,('-id')反序
6.reverse()						對查詢結果反向排序
7.count()							返回資料庫中匹配查詢(QuerySet)的物件數量
8.first()							返回第一條記錄,或第一個物件
9.last()							返回最後一條記錄,或最後一個物件
10.exists()						如果QuerySet包含資料,就返回True,否則返回False
11.values(*field)			返回一個ValueQuerySet——一個特殊的QuerySet,執行後得到的並不是一系列                       model的例項化物件,而是一個可迭代的字典序列(在mysql中只顯示括號內中字											段的記錄)
12.values_list(*field):   它與values()非常相似,它返回的是一個元組序列,values返回的是一個													 字典序列
13.distinct():				 從返回結果中剔除重複紀錄

# 注意
'''
類似於
 res = models.Book.objects.get(pk=1)
 res =models.Book.objects.first()
 res =models.Book.objects.filter(pk=1).first()
 print(type(res))
 
這樣單獨拿出來的資料物件
資料型別都為:
<class 'app10.models.Book'>  # 就是定義此欄位的類

其他:
<class 'django.db.models.query.QuerySet'> # 都是QuerySet物件

其本質是生成器,也就是具備在某個節點能夠暫停迴圈並儲存狀態,以開始下一次的迴圈。
比如設定了 limited 21  代表迴圈21次從硬碟讀取資料到記憶體21條就通過生成式yield掛起狀態,
如果此21條有用則被引用儲存在記憶體中,沒有則進行垃圾回收。然後再讀入下個21條資料,再重複操作。
這樣時刻保持了記憶體的資源不會被過多消耗。
不然一下從硬碟中迴圈讀出百萬條資料到記憶體,那麼記憶體的負荷會被撐爆,無法進行後續操作

只有QuerySet物件可以匯入到html檔案中進行模板的渲染

'''

9 基於雙下劃線的模糊查詢

#1  價格在[100,200,300]這個範圍內
Book.objects.filter(price__in=[100,200,300])
# 2 大於,小於,大於等於,小於等於
Book.objects.filter(price__gt=100)
Book.objects.filter(price__lt=100)
Book.objects.filter(price__gte=100)
Book.objects.filter(price__lte=100)
# 3 範圍 BETWEEN 100 AND 200
Book.objects.filter(price__range=[100,200])
# 包含 
Book.objects.filter(title__contains="python")
# 4 忽略大小寫包含
Book.objects.filter(title__icontains="python")
# 5 以xx開頭
Book.objects.filter(title__startswith="py")
# 6 時間型別
Book.objects.filter(pub_date__year=2011) # 匹配年份2011
Book.objects.filter(pub_date__month=11)  # 匹配月份11月
Book.objects.filter(pub_date__day=11)		 # 匹配日曆11日

補充

1 時區和國際化問題

setting.py中
1 後臺管理漢語問題
	LANGUAGE_CODE = 'zh-hans'  # 管理後臺看到的就是中文
2 時區問題(使用東八區)
	TIME_ZONE = 'Asia/Shanghai'
    USE_TZ = False

2 django admin(管理後臺的簡單使用)

0 管理後臺是django提供的可以快速對錶進行增刪查改操作

1 建立一個後臺管理賬號
    python3 manage.py createsuperuser
    輸入使用者名稱
    輸入郵箱(可以不填,敲回車)
    輸入密碼
    確認密碼
    # 超級使用者創建出來了,可以登入管理後臺了
2 admin中表中一行一行的資料顯示我們定製的樣子
	重寫模型類的__str__方法

3 blank引數作用

1 需要把book表註冊到admin中
	在app下的admin.py中寫
    from app01 import models
	# 把book表註冊一些,管理後臺就能看到了
	admin.site.register(models.Book)
2 可以快速的對book表進行增刪查改操作
	
    

練習

'''
1 查詢老男孩出版社出版過的價格大於200的書籍
 
2 查詢2017年8月出版的所有以py開頭的書籍名稱
 
3 查詢價格為50,100或者150的所有書籍名稱及其出版社名稱
 
4 查詢價格在100到200之間的所有書籍名稱及其價格
 
5 查詢所有人民出版社出版的書籍的價格(從高到低排序,去重)
'''
def book(request):
    # 1 查詢老男孩出版社出版過的價格大於200的書籍
    all_obj = models.Book.objects.filter(publish_name='老男孩').filter(price__gt=200).values('name')

    # 2 查詢2017年8月出版的所有以py開頭的書籍名稱(區分大小寫)
    all_obj = models.Book.objects.filter(create_time__year='2017').filter(create_time__month=8).filter(name__startswith='Py').values('name')

    # 3 查詢價格為50,100或者150的所有書籍名稱及其出版社名稱
    all_obj = models.Book.objects.filter(price__in=[50, 100, 150]).values('name','publish_name')

    # 4 查詢價格在100到200之間的所有書籍名稱及其價格
    all_obj = models.Book.objects.filter(price__range=[100, 200]).values('name','price')

    # 5 查詢所有人民出版社出版的書籍的價格(從高到低排序,去重)
    all_obj = models.Book.objects.filter(publish_name='人民').order_by('-price').distinct()

    return render(request, 'book.html', locals())

# 鏈式呼叫(queryset就是鏈式呼叫的使用),實現一個可以支援鏈式呼叫的類


'''
鏈式呼叫就是在物件呼叫一個方法的基礎上還可以通過.的方式進行方法的呼叫
那麼只要在每個類中的函式定義下返回物件本身,那麼相當於還是原物件通過.呼叫方法

'''


class Base:
    def name(self, name):
        print(name)
        return self

    def age(self, age):
        print(age)
        return self

    def func(self):
        print(self.name, self.age)


obj = Base()
obj.name('arther').age('age').func()
# 呼叫返回了self相當於 obj.age又返回self 相當於obj.func
# 驗證:queryset物件,for迴圈的時候,其本質每列印完21條,又會執行一句查詢sql
# 在 test.py 測試
import os

if __name__ == '__main__':
    # 1  引入django配置檔案
    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'Django_learn.settings')
    # 2 讓djagno啟動
    import django

    django.setup()
    # 3 使用表模型
    from app10 import models

    res = models.Book.objects.all()
    print(res)
    for i in res:
        print(i)

# 終端顯示的原生sql語句
# 取出每個物件 但是LIMIT 21 限制了只能取頭21條資料 採用了生成式的原理 防止讀取到記憶體的資料量過大
(0.001) SELECT `app10_book`.`id`, `app10_book`.`name`, `app10_book`.`price`, `app10_book`.`create_time`, `app10_book`.`update_time`, `app10_book`.`publish_name` FROM `app10_book` LIMIT 21; args=()

# 然後再執行完整的取出所有物件的sql語句
(0.007) SELECT `app10_book`.`id`, `app10_book`.`name`, `app10_book`.`price`, `app10_book`.`create_time`, `app10_book`.`update_time`, `app10_book`.`publish_name` FROM `app10_book`; args=()