1. 程式人生 > >Django - ORM操作

Django - ORM操作

目錄

ORM介紹

ORM的兩種方式

db first    先連線資料庫    -> ...
code first  先建立類        -> sqlachemy、Django、大多數都是

Django ORM

ORM:Object Relational Mapping(關係物件對映)

類名             ->>   資料庫中的表名

類屬性           ->>   資料庫裡的欄位

類例項           ->>   資料庫表裡的一行資料

obj.name.....    ->>   類例項物件的屬性

Django orm的優勢:Django的orm操作本質上會根據對接的資料庫引擎,翻譯成對應的sql語句;所有使用Django開發的專案無需關心程式底層使用的是MySQL、Oracle、sqlite....,如果資料庫遷移,只需要更換Django的資料庫引擎即可

QuerySet資料型別介紹

QuerySet特點:

  • 可迭代的

  • 可切片

  • 惰性計算:等於一個生成器,.objects.all()或者.filter()等都只是返回了一個QuerySet的查詢結果集物件,它並不會馬上執行sql,而是當呼叫QuerySet的時候才執行。

  • 快取機制:每一次資料庫查詢結果QuerySet都會對應一塊快取,再次使用該QuerySet時,不會發生新的SQL操作

這樣減小了頻繁操作資料庫給資料庫帶來的壓力

但是有時候取出來的資料量太大會撐爆快取,可以使用迭代器解決這個問題:

models.Publish.objects.all().iterator()

建立ORM類

1. 在models裡建立表的類

/app/models.py

from django.db import models
# 表名為app01_userinfo
class UserInfo(models.Model):
    # 自動建立id列,自增,主鍵
    # 列名,字串型別,指定長度
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)
    email = models.EmailField(max_length=19)

類的欄位和引數詳見[欄位和引數]

2. 註冊APP

/./settings.py

INSTALLED_APPS = [
    ...,
    'app01',
]

3. 執行命令,每次更改表結構都要重複一遍

python manage.py  makemigrations    ->  生成表結構的快取
python manage.py  migrate           ->  建立表結構

4. 預設使用sqlite3資料庫,可修改為mysql

/./settings.py      ->  DATABASES

********** 注意 ***********
Django預設使用MySQLdb模組連結MySQL
主動修改為pymysql,在project同名資料夾下的__init__檔案中新增如下程式碼即可:
    import pymysql
    pymysql.install_as_MySQLdb()

增刪改查

1.增

/app/views.py
from app01 import models
def orm(request):
    # 直接傳入引數
    models.UserInfo.objects.create(username='root',password='123')
    # 傳入字典
    dic = {'username': 'eric', 'password': '666'}
    models.UserInfo.objects.create(**dic)
    # 另一種增加方式
    obj = models.UserInfo(username='alex',password='123')
    obj.save()

2.查

result = models.UserInfo.objects.all()
result = models.UserInfo.objects.filter(user='root',psd='123') -> filter傳入字典也可 **dic
        => QuerySet, Django的一種列表, [], 內部元素是.obj => [obj(id,username),obj]
    
# 轉化為字典輸出                
    .all().values('id','caption')       -> [{'id:1,'username':'alex'},{},{}]
# 轉化為tuple輸出            
    .all().values_list('id','caption')  -> [(1,'alex'),(),()]
# 取第一個obj
    .filter(xxx).first()                -> 不存在返回None
            => 用get取單條資料,如果不存在,直接報錯
            => models.UserInfo.objects.get(id=nid)
# 計數
    .filter(name='seven').count()

# 切片
    .all()[10:20]
    .all()[::2]
    .all()[6]    # 索引

# 去重
    .distinct()

# 排序
    .filter(name='seven').order_by('id')    ->  asc
    .filter(name='seven').order_by('-id')   ->  desc

3.刪

models.UserInfo.objects.filter(username="alex").delete()

4.改

models.UserInfo.objects.filter(id=3).update(password="69")  # 可新增**kwargs形式
# 或者先查詢物件再修改儲存
    obj = models.tb.objects.get(id=1)
    obj.c1 = '111'
    obj.save()                                              # 修改單條資料

特殊的判斷語句(神奇的雙下劃線1)

# 大於小於
    .filter(id__gt=1)              ->      > 1
    .filter(id=1)                  ->      = 1
    .filter(id__lt=1)              ->      < 1
    .filter(id__lte=1)             ->      <= 1
    .filter(id__gte=1)             ->      >= 1
    .exclude(id__gt=1)             ->      != 1  exclude 除了...與filter相反
    .filter(id__gt=1, id__lt=10)   ->      1< x <10

# 範圍range
    .filter(id__range=[1,3])       ->      [1~3]   bettwen + and

# 範圍in
    .filter(id__in=[1,2,3])        ->      in [1,2,3]
    .exclude(id__in=[1,2,3])       ->      in [1,2,3]

# 是否為空
    .filter(name__isnull=True)

# 包含、開頭、結尾 __startswith, istartswith, endswith, iendswith
    .filter(name__contains="ven")
    .filter(name__icontains="ven")    # i 忽略大小寫

# regex正則匹配,iregex 不區分大小寫
    .get(title__regex=r'^(An?|The) +')
    .get(title__iregex=r'^(an?|the) +')

# date
    .filter(pub_date__date=datetime.date(2005, 1, 1))
    .filter(pub_date__date__gt=datetime.date(2005, 1, 1))

# year、month、day、week_day
    .filter(pub_date__year=2005)
    .filter(pub_date__year__gte=2005)

# hour、minute、second
    .filter(timestamp__hour=23)
    .filter(time__hour=5)
    .filter(timestamp__hour__gte=12)

進階查詢

  • F模組,用於獲取物件中的某一欄位(列)的值,並且對其進行操作;

      from django.db.models import F      # 首先匯入F模組
      models.Book.objects.all().update(price=F('price')+1)   # 每一本書的價格上調1塊錢
  • Q模組,用於構造複雜的查詢條件,使用邏輯關係(&與、|或、~非)組合進行多條件查詢;

    雖然filter中可以使用 , 隔開表示關係與,但沒法表示或非的關係

      from django.db.models import Q      # 匯入Q模組
      # 方式一:
          .filter( Q(id__gt=10) )                 -> 
          .filter( Q(id=8) | Q(id__gt=10) )       -> or
          .filter( Q( Q(id=8) | Q(id__gt=10) ) & Q(caption='root') )  -> and, or
      # 方式二:
      # 可以組合巢狀
          # q1裡面的條件都是or的關係
          q1 = Q()
          q1.connector = 'OR'
          q1.children.append(('id', 1))
          q1.children.append(('id', 10))
          q1.children.append(('id', 9))
          # q2裡面的條件都是or的關係
          q2 = Q()
          q2.connector = 'OR'
          q2.children.append(('c1', 1))
          q2.children.append(('c1', 10))
          q2.children.append(('c1', 9))
          # con通過and的條件把q1和q2聯絡到一塊
          con  = Q()
          con.add(q1, 'AND')
          con.add(q2, 'AND')
          models.tb.objects.filter(con)

    例項:查詢作者姓名中包含 方/少/偉/3字,書名不包含偉,並且出版社地址以山西開頭的書

      book=models.Book.objects.filter(
                                      Q(
                                          Q(author__name__contains='方') |
                                          Q(author__name__contains='少') |
                                          Q(author__name__contains='偉') |
                                          Q(title__icontains='偉')
                                      ) & 
                                      Q(publish__addr__contains='山西')
                                  ).values('title')

    注意:Q查詢和非Q查詢混合使用,非Q查詢一定要放在Q查詢後面

  • extra方法

    對不同的資料庫引擎可能存在移植問題(因為你在顯式的書寫SQL語句),儘量避免使用extra

      a.對映
          - select={'new_id':select count(1) from app01_usertype where id>%s'}
          - select_params=[1,]
          # 例:
              models.UserInfo.objects.all().extra(
                  select={
                      'n':"select count(1) from app01_utype WHERE id=%s or id=%s",
                      'm':"select count(1) from app01_uinfo WHERE id=%s or id=%s",
                  },
                  select_params=[1,2,3,4]
              )
    
      b.條件
          - where=["foo='a' OR bar = 'a'", "baz = '%s'"],
          - params=['Lennon',]
    
      c.表
          - tables=["app01_usertype"]
    
      d.排序
          - order_by = ['-id']
    
      # 例1:
          models.UserInfo.objects.extra(
              select={'new_id':select count(1) from app01_usertype where id>%s'},
              select_params=[1,],
              where=['age>%s'],
              params=[18,],
              order_by=['-age'],
              tables=["app01_usertype']
          )
          -> 相當於:
              '''
              select
                  app01_userinfo.id,
                  (select count(1) from app01_usertype where id>1) as new_id
              from
                  app01_userinfo,
                  app01_usertype
              where 
                  app01_userinfo.age>18
              order by 
                  app01_userinfo.age desc
              '''
    
      # 例2:
          current_user = models.UserInfo.objects.filter(username=username).first()   # 當前使用者
    
          1、models.Article.objects.all()      # 查出每一篇文章
          2、models.Article.objects.all().filter(user=current_user)  # 查出當前使用者的所有文章
          3、models.Article.objects.all().filter(user=current_user).extra(select={"filter_create_date":"strftime(‘%%Y/%%m‘,create_time)"}).values_list("filter_create_date")
              # 查出當前使用者的所有文章的create_time,並且只取出年份和月份
  • 執行原生SQL的三種方式

    • 1.使用extra方法

        結果集修改器,一種提供額外查詢引數的機制  
        依賴model模型
    • 2.使用raw方法

        執行原始sql並返回模型  
        依賴model多用於查詢
      
        book = Book.objects.raw("select * from hello_book")
        for item in book:
            print(item.title)
    • 3.使用cursor遊標

        不依賴model
      
        from django.db import connection, connections
        cursor = connection.cursor()  
        # 或cursor = connections['default'].cursor() 
        # 其中'default'是django資料庫配置的default,也可取別的值
        cursor.execute("""SELECT * from auth_user where id = %s""", [1])
        row = cursor.fetchone()

類的欄位和引數

欄位:字串、數字、時間、二進位制

AutoField(Field)        ->  自定義自增列(必須加primary_key=True)
IntegerField(Field)     ->  整數列
BooleanField(Field)     ->  布林
GenericIPAddressField(Field)    ->  IP驗證(僅限django admin)
URLField(CharField)     ->  url驗證(僅限django admin)
# Django裡有很多的欄位型別在資料庫中都是Char型別,只是用於django admin便於區分

更多詳見:武沛齊的部落格 - Django

欄位的引數:

null                -> db中是否可以為空
default=''          -> 預設值
primary_key         -> 是否主鍵
db_column           -> 列名

db_index            -> 是否可索引
unique              -> 是否可唯一索引
unique_for_date     -> 【日期】部分是否可索引
unique_for_month    -> 【月】部分是否可索引
unique_for_year     -> 【年】部分是否可索引

auto_now_add        -> 建立時,自動生成時間
auto_now            -> 更新時,自動更新為當前時間
        # update方式不生效,先獲取再更改才生效
        ctime = models.DateTimeField(auto_now_add=True)
        UserGroup.objects.filter(id=1).update(caption='CEO')    -> 不生效
        obj = UserGroup.objects.filter(id=1).first()
        obj.caption = "CEO"             -> 生效,自動更新更改時間
        obj.save()

# django admin中才生效的欄位
blank               -> django admin是否可以為空
verbose_name=''     -> django admin顯示欄位中文
editable            -> django admin是否可以被編輯
help_text           -> django admin幫助提示
choices=[]          -> django admin中顯示下拉框
        # 可避免連表查詢,提高效率,一般用於基本不變的選項
        user_type_choices = (
            (1, '超級使用者'),
            (2, '普通使用者'),
            (3, '普普通使用者'),
        )
        user_type_id = models.IntegerField(choices=user_type_choices,default=1)

error_messages      -> 自定義錯誤資訊(字典型別)
        # 字典的鍵:null, blank, invalid, invalid_choice, unique, unique_for_date
        # 例:error_messages = {'null': "不能為空", 'invalid': '格式錯誤'}

validators          -> django form ,自定義錯誤資訊(列表型別)
        # 例:
        from django.core.validators import RegexValidator
        from django.core.validators import EmailValidator,URLValidator,DecimalValidator,\
                MaxLengthValidator,MinLengthValidator,MaxValueValidator,MinValueValidator
        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'), ]

更多錯誤資訊的使用方法參考武沛齊 - FORM

建立 Django admin使用者: python manage.py createsuperuser

Meta元資訊

class UserInfo(models.Model):
    ...
    class Meta:
        # 定義資料庫中生成的表名稱 預設 app名稱 + 下劃線 + 類名
        db_table = "table_name"

        # 聯合索引
        index_together = [("pub_date", "deadline"),]

        # 聯合唯一索引,一旦三者都相同,則會被Django拒絕建立。
        可以同時設定多組約束。為了方便,對於只有一組約束的情況下,可以簡單地使用一維元素
        unique_together = (("driver", "restaurant"),)

        # admin後臺中顯示的表名稱
        verbose_name = '使用者資訊'

        # verbose_name加s,複數形式,不指定自動加s
        verbose_name_plural = 

        # 預設排序
        ordering=['-order_date'] # 按訂單降序排列,-表示降序,不加升序,加?表示隨機
        ordering=['-pub_date','author'] # 以pub_date為降序,再以author升序排列

更多:https://docs.djangoproject.com/en/1.10/ref/models/options/

Admin拓展知識

  1. 觸發Model中的驗證和錯誤提示有兩種方式:

     a. Django Admin中的錯誤資訊會優先根據Admin內部的ModelForm錯誤資訊提示,如果都成功,才來檢查Model的欄位並顯示指定錯誤資訊
     b. 呼叫Model物件的 clean_fields 方法,如:
         # models.py
         class UserInfo(models.Model):
             username = models.CharField(max_length=32)
             email = models.EmailField(error_messages={'invalid': '格式錯了.'})
    
         # views.py
         def index(request):
             obj = models.UserInfo(username='11234', email='uu')
             try:
                 print(obj.clean_fields())
             except Exception as e:
                 print(e)
             return HttpResponse('ok')
    
        # Model的clean方法是一個鉤子,可用於定製操作,如:上述的異常處理。
  2. Admin中修改錯誤提示

     # admin.py
     from django.contrib import admin
     from model_club import models
     from django import forms
    
     class UserInfoForm(forms.ModelForm):
         username = forms.CharField(error_messages={'required': '使用者名稱不能為空.'})
         email = forms.EmailField(error_messages={'invalid': '郵箱格式錯誤.'})
         age = forms.IntegerField(initial=1, error_messages={'required': '請輸入數值.', 'invalid': '年齡必須為數值.'})
    
         class Meta:
             model = models.UserInfo
             # fields = ('username',)
             fields = "__all__"
    
     class UserInfoAdmin(admin.ModelAdmin):
         form = UserInfoForm
    
     admin.site.register(models.UserInfo, UserInfoAdmin)

ORM連表的幾種型別

ORM一對多

當一張表中建立一行資料時,有一個單選的下拉框(可以被重複選擇)

  • 建立表結構時關聯外來鍵

      user_group = models.ForeignKey("UserGroup",to_field='uid')  ->>   obj(UserGroup)
      # 自動建立user_group_id列,存的是數字(關聯主鍵)
  • 新增資料時關聯id或物件

      方式一:建立資料時新增id關聯
          models.UserInfo.object.create(name='root', user_group_id=1)
    
      方式二:查詢obj物件進行關聯
          user_group = models.UserGroup.objects.filter(id=1).first()

一對多自關聯

由原來的2張表,變成一張表!

# 例:回覆評論
class Comment(models.Model):
    news_id = models.IntegerField()                 -> 新聞ID
    content = models.CharField(max_length=32)       -> 評論內容
    user = models.CharField(max_length=32)          -> 評論者
    reply = models.ForeignKey('Comment',null=True,blank=True,related_name='xxxx') -> 回覆ID
# 注意:回覆的id必須是已經存在的評論的id

ORM多對多

在某表中建立一行資料是,有一個可以多選的下拉框

  • 兩種建立方式

    以下兩種建立方式建議都用,自動建立的只能關聯兩個表,自定義的可以不斷關聯

    • 方式一:自定義關係表

      可以直接操作第三張表,但無法通過欄位跨表查詢,查詢麻煩

        class UserInfo(models.Model):
            ...
        class UserGroup(models.Model):
            ...
        # 建立中間表
        class UserInfoToUserGroup(models.Model):
            user_info_obj = models.ForeignKey(to='UserInfo',to_field='nid')
            group_obj = models.ForeignKey(to='UserGroup',to_field='id')
      
        # 新增關聯資料:  
        UserInfoToApp.objects.create(user_info_obj_id=1,group_obj_id=2)
    • 方式二:Django自動建立關係表

      可以使用欄位跨表查詢,但無法直接操作第三張表

        class UserInfo(models.Model):
            ...
        # ManyToManyField欄位
        class UserGroup(models.Model):
            user_info = models.ManyToManyField("UserInfo")
    • 方式三:既自定義第三張關係表 也使用ManyToManyField欄位(雜交型別)

      既可以使用欄位跨表查詢,也可以直接操作第3張關係表

      注意:obj.m.all() 只有查詢和清空方法

        # 例:博主粉絲關係
        class UserInfo(AbstractUser):
            ...
            fans = models.ManyToManyField(to='UserInfo',
                                          through='UserFans',                   -> 指定關係表表名
                                          through_fields=('user', 'follower'))  -> 指定關係表字段
        class UserFans(models.Model):
            ...
            user = models.ForeignKey(to='UserInfo', to_field='nid', related_name='users')
            follower = models.ForeignKey(to='UserInfo', to_field='nid', related_name='followers')
            class Meta:
                unique_together = [('user', 'follower'),]

多對多自關聯

(由原來的3張表,變成只有2張表)
把兩張表通過 choices 欄位合併為一張表
使用ManyToManyField欄位

1、查詢第三張關係表前面那一列:obj.m

2、查詢第三張關係表後面那一列:obj.userinfo_set

class Userinfo(models.Model):
    sex=((1,'男'),(2,'女'))
    gender=models.IntegerField(choices=sex)
    m=models.ManyToManyField('Userinfo')

# 通過男士查詢女生
    boy_obj=models.Userinfo.objects.filter(id=4).first()
    res=boy_obj.m.all()
# 通過女士查詢男生
    girl_obj=models.Userinfo.objects.filter(id=4).first()
    res=girl_obj.userinfo_set.all()

ORM一對一

在某表中建立一行資料時,有一個單選的下拉框(下拉框中的內容被用過一次就消失了)

例如:原有含10列資料的一張表儲存相關資訊,經過一段時間之後,10列無法滿足需求,需要為原來的表再新增5列資料

r = models.OneToOneField(...)

# 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)

ORM連表操作

欄位引數

  • 一對多ForeignKey()

      to                          ->  要關聯的表名
      to_field='uid',             ->  要關聯的欄位,不寫預設關聯主鍵
      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(func)
                      def func():
                          return 10
                      class MyModel(models.Model):
                          user = models.ForeignKey(...,on_delete=models.SET(func))
    
      related_name=None,          ->  反向操作時,使用的欄位名,用於替換【表名_set】
                                      如: obj.表名_set.all()
      related_query_name=None,    ->  反向操作時,使用的連線字首,用於替換【表名】
                          如: ...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中是否顯示關聯資料
  • 多對多ManyToManyField()

      symmetrical=None,       -> 僅用於多對多自關聯時,指定內部是否建立反向操作的欄位
          => 做如下操作時,不同的symmetrical會有不同的可選欄位
              models.BB.objects.filter(...)
    
          => 可選欄位有:code, id, m1
              class BB(models.Model):
                  code = models.CharField(max_length=12)
                  m1 = models.ManyToManyField('self',symmetrical=True)
    
          => 可選欄位有: code, id, m1, bb
              class BB(models.Model):
                  code = models.CharField(max_length=12)
                  m1 = models.ManyToManyField('self',symmetrical=False)
    
      through=None,           -> 自定義第三張表時,用於指定關係表
      through_fields=None,    -> 自定義第三張表時,用於指定關係表中哪些欄位做多對多關係表
    
      db_constraint=True,         -> 是否在資料庫中建立外來鍵約束
      db_table=None,              -> 預設建立第三張表時,資料庫中表的名稱
  • 一對一OneToOneField()

      to                          ->  要關聯的表名
      to_field='uid',             ->  要關聯的欄位,不寫預設關聯主鍵
      on_delete=None,             ->  刪除關聯表中的資料時,當前表與其關聯的行的行為

跨表查詢(神奇的雙下劃線2)

  • 獲取值時使用 . 連線

      group_obj = models.UserGroup.objects.filter(id=1).first()   # orm連表必須取單個物件
      # 增
      group_obj.user_info.add(1)                -> 新增一個
      group_obj.user_info.add(2,3,4)            -> 新增多個
      group_obj.user_info.add(*[1,2,3,4])       -> 新增*列表
      # 刪
      group_obj.user_info.remove(1)
      group_obj.user_info.remove(2,4)
      group_obj.user_info.remove(*[1,2,3])
      group_obj.user_info.clear()           -> 清除當前物件關聯的多對多資料
      # 改
      group_obj.user_info.set([3,5,7])      -> (不加*)只保留1-3,1-5,1-7,其它刪除
      # 查
      group_obj.user_info.all()             -> 獲取所有相關的主機obj 的QuerySet
      group_obj.user_info.filter()
      ......
  • 搜尋條件使用 __ 連線 (value、value_list、fifter)

      obj = models.UserGroup.objects.filter(id=1).value('name','user_info__name').first()
      在html裡也用obj.user_group__name
  • 反查
    # . 操作,獲取物件的QuerySet,表名小寫_set
    user_info_obj.usergroup_set.add(group_obj)
    user_info_obj.usergroup_set.remove(group_obj)
    user_info_obj.usergroup_set.all()
    user_info_obj.usergroup_set.filter()
    ......
    # __操作,搜尋屬性,表名小寫__屬性
    obj = models.UserInfo.objects.filter('usergruop__name').first()

設定反向查詢別名

related_query_name      -> 反向查詢時用 obj.別名_set.all(),保留了_set
relatedname             -> 反向查詢時用 obj.別名.all()  


# 例如:
'''把男女表混合在一起,在程式碼層面控制第三張關係表的外來鍵關係'''

    # models.py
    class UserInfo(models.Model):
        ...
        sex=((1,'男'),(2,'女'))
        gender=models.IntegerField(choices=sex)
    class U2U(models.Model):
        b=models.ForeignKey(Userinfo,related_name='boy')
        g=models.ForeignKey(Userinfo,related_name='girl')

       # 寫到此處問題就來了,原來兩個外來鍵 對應2張表 2個主鍵,可以識別男女
       # 現在兩個外來鍵對應1張表,反向查詢,無法區分男女了了
       # object物件女.U2U.Userinfo.set  object物件男.U2U.Userinfo.set
       # 所以要加related_name設定反向查詢命名 對錶中主鍵加以區分
       # 查詢方法
       # 男:obj.a.all()
       # 女:obj.b.all()

    # views.py
    def index(request):
       #查詢 ID為1男孩 相關的女孩
       boy_obj=models.UserInfo.objects.filter(id=1).first()
       res = boy_obj.boy.all()   # 得到U2U的物件再正向跨表           
       for obj in res:
           print(obj.girl.name)
       return HttpResponse('OK')

分組和聚合查詢

  1. aggregate() 聚合函式

    通過對QuerySet進行計算,返回一個聚合值的字典。
    aggregate()中每一個引數都指定一個包含在字典中的返回值。即在查詢集上生成聚合。

     from django.db.models import Avg,Sum,Max,Min
    
     # 求書籍的平均價
     ret = models.Book.objects.all().aggregate(Avg('price'))
     # {'price__avg': 145.23076923076923}
    
     # 參與西遊記著作的作者中最老的一位作者
     ret = models.Book.objects.filter(title__icontains='西遊記').values('author__age').aggregate(Max('author__age'))
     # {'author__age__max': 518}
  2. annotate() 分組函式

     # 檢視每一位作者出過的書中最貴的一本  
     # (按作者名分組 values(),然後 annotate() 分別取每人出過的書價格最高的)
     ret=models.Book.objects.values('author__name').annotate(Max('price'))
     # < QuerySet[
     # {'author__name': '吳承恩', 'price__max': Decimal('234.000')},
     # {'author__name': '呂不韋','price__max': Decimal('234.000')},
     # {'author__name': '姜子牙', 'price__max': Decimal('123.000')},
     # ] >

淺談ORM查詢效能

  1. 普通跨表查詢

     obj_list=models.Love.objects.all() 
     for row in obj_list:           # for迴圈10次傳送10次資料庫查詢請求
         print(row.b.name)

    原理:第一次傳送查詢請求,每for迴圈一次也會發送查詢請求

  2. select_related

    結果為物件,query_set型別的物件都有該方法

    原理:select_related查詢時主動完成連表形成一張大表,for迴圈時不用額外發請求

    試用場景:節省硬碟空間,資料量少的時候適用,相當於做了一次資料庫查詢;

     obj_list=models.Love.objects.all().select_related('b')      # 查詢時關聯b表
         for row in obj_list:
             print(row.b.name)
  3. prefetch_related:

    結果為物件

    原理:select_related雖好,但是做連表操作依然會影響查詢效能,prefetch_related不做連表,多次單表查詢外來鍵表,去重之後顯示,2次單表查詢(有N個外來鍵做1+N次單表查詢)

    適用場景:效率高,資料量大的時候使用

     obj_list=models.Love.objects.all().prefetch_related('b')
         for obj in obj_list:
             print(obj.b.name)
  4. update()和物件.save()修改方式的效能PK

     # 方式1
         models.Book.objects.filter(id=1).update(price=3)
         # 執行結果
             (0.000) BEGIN; args=None
             (0.000) UPDATE "app01_book" SET "price" = '3.000' WHERE "app01_book"."id" = 1; args=('3.000', 1)
    
     # 方式2
         book_obj=models.Book.objects.get(id=1)
         book_obj.price=5
         book_obj.save()
         # 執行結果
             (0.000) SELECT "app01_book"."id", "app01_book"."title", "app01_book"."price", "app01_book"."date", "app01_book"."publish_id", "app01_book"."classify_id" FROM "app01_book" WHERE "app01_book"."id" = 1; args=(1,)
             (0.000) BEGIN; args=None
             (0.000) UPDATE "app01_book" SET "title" = '我的奮鬥', "price" = '5.000', "date" = '1370-09-09', "publish_id" = 4, "classify_id" = 3 WHERE "app01_book"."id" = 1; args=('我的奮鬥', '5.000', '1370-09-09', 4, 3, 1)
    
     # 結論:
         update() 比 obj.save()效能好

Django自帶ContentType表

Django程式啟動後自帶的一張表,記錄了Django程式的所有APP下model中的表名和所在app的名稱

  1. 通過ContentType中的app名和表名,查詢到Django model中所有表;

     from django.contrib.contenttypes.models import ContentType
     def test(request):
         c = ContentType.objects.get(app_label='app01',model='boy')
         print(c)                    -> boy
         print(c.model_class())      -> app01.models.Boy
  2. 解決 1張表 同時與其他N張表建立外來鍵,並且多個外來鍵中只能選擇1個的複雜問題

    場景1:現有N種優惠券,每1種優惠券分別對應N門課程中的一門課程,怎麼設計表結構呢?
    場景2:學生的學習成績如何獎懲、 作業如何獎懲、學習進度如何獎懲...

     # 例:場景1
         from django.db import models
         from django.contrib.contenttypes.models import ContentType
         from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
    
         class DegreeCourse(models.Model):
             name = models.CharField(max_length=128, unique=True)
             # GenericRelation 自動連表查詢
             xxx = GenericRelation('Coupon')
    
         class Course(models.Model):
             name = models.CharField(max_length=128, unique=True)
    
         class Coupon(models.Model):
             """優惠券生成規則
                 ID     優惠券名稱         content_type_id(表)         object_id(表中資料ID)
                  1       通用                 null                    null
                  2       滿100-10               8                      1
                  3       滿200-30               8                      2
                  4       滿200-30               9                      1
             """
             name = models.CharField(max_length=64, verbose_name="活動名稱")
             # course_type 代指哪張表 注意該欄位必須為 content_type
             content_type = models.ForeignKey(ContentType,blank=True,null=True)
             # 代指物件ID 該欄位必須為 object_id
             object_id = models.PositiveIntegerField(blank=True, null=True, help_text="可以把優惠券跟課程繫結")
             # GenericForeignKey 通過 content_type 直接建立外來鍵關係,不會生成額外的列
             content_object = GenericForeignKey('content_type','object_id')
    
     # 給學位課1,建立優惠券100
     # 方式1:
     # 1、在學位課表中 ,找到學位課1
         d1 = models.DegreeCourse.objects.get(id=1)
     # 2、在ContentType找到學位課表
         c1 = ContentType.objects.get(app_label='app01',model='degreecourse')
     # 3、給學位課1,建立優惠券100
         models.Coupon.objects.create(name='優惠券',brief='100',content_type=c1,object_id=d1.id)
    
     # 方式2:
         d1 = models.DegreeCourse.objects.get(id=1)
         models.Coupon.objects.create(name='優惠券',brief='100',content_object=d1)
    
     # 查詢關聯的所有優惠券
         d1 = models.DegreeCourse.objects.get(id=1)
         print(d1.xxx.all())
         v = models.DegreeCourse.objects.values('name','xxx__brief','xxx__name')
         print(v)

其他小技巧

資料庫表刪除重建:

  1. 先到資料庫把表刪掉:drop table

  2. 註釋django中對應的Model

  3. 執行以下命令:

     python manage.py makemigrations   
     python manage.py migrate --fake     ->  只記錄變化,不提交資料庫操作
  4. 去掉註釋重新遷移

     python manage.py makemigrations   
     python manage.py migrate

字典key替換

# 把value傳給新key並同時刪除舊key
row['delivery'] = [row.pop('投遞')]

獲取欄位名和verbose_name

fields_data = Group._meta.fields
for key in data:
    # 這裡是將當前的資料轉換成資料字典,方便後面修改後提交
    data_dict = Group.__dict__
    for field in fields_data:
        # 這樣或輸出這條記錄的所有欄位名,需要的話還可以輸出verbose_name
        print(field.name)
        if field.name == key:
            #進行匹配,將前端傳來的欄位匹配到,然後修改資料庫裡面的資料
            data_dict[key] = data[key]
# 儲存資料到資料庫,這樣的好處就是提高效率,避免過多重複操作

參考部落格

ORM詳細講解
武沛齊的部落格 - Django