Django之ORM

 

ORM概念html

對象關係映射(Object Relational Mapping,簡稱ORM)模式是一種爲了解決面向對象與關係數據庫存在的互不匹配的現象的技術。python

簡單的說,ORM是經過使用描述對象和數據庫之間映射的元數據,將程序中的對象自動持久化到關係數據庫中。mysql

ORM在業務邏輯層和數據庫層之間充當了橋樑的做用。git

 

ORM由來程序員

讓咱們從O/R開始。字母O起源於"對象"(Object),而R則來自於"關係"(Relational)。sql

幾乎全部的軟件開發過程當中都會涉及到對象和關係數據庫。在用戶層面和業務邏輯層面,咱們是面向對象的。當對象的信息發生變化的時候,咱們就須要把對象的信息保存在關係數據庫中。數據庫

按照以前的方式來進行開發就會出現程序員會在本身的業務邏輯代碼中夾雜不少SQL語句用來增長、讀取、修改、刪除相關數據,而這些代碼一般都是極其類似或者重複的。django

 

ORM的優點瀏覽器

ORM解決的主要問題是對象和關係的映射。它一般將一個類和一張表一一對應,類的每一個實例對應表中的一條記錄,類的每一個屬性對應表中的每一個字段。 緩存

ORM提供了對數據庫的映射,不用直接編寫SQL代碼,只需操做對象就能對數據庫操做數據。

讓軟件開發人員專一於業務邏輯的處理,提升了開發效率。

 

ORM的劣勢

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

ORM的操做是有限的,也就是ORM定義好的操做是能夠完成的,一些複雜的查詢操做是完成不了。

 

Django項目使用MySQL數據庫

在Django項目的settings.py文件中,配置數據庫鏈接信息:

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.mysql",
        "NAME": "你的數據庫名稱",  # 須要本身手動建立數據庫
        "USER": "數據庫用戶名",
        "PASSWORD": "數據庫密碼",
        "HOST": "數據庫IP",
        "POST": 3306
    }
}

 

在與Django項目同名的目錄下的__init__.py文件中寫以下代碼,告訴Django使用pymysql模塊鏈接MySQL數據庫:

import pymysql
 
pymysql.install_as_MySQLdb()

  注意:數據庫遷移的時候出現一個警告 

WARNINGS: 
?: (mysql.W002) MySQL Strict Mode is not set for database connection 'default'
HINT: MySQL's Strict Mode fixes many data integrity problems in MySQL, such as
data truncation upon insertion, by escalating warnings into errors. It is strongly
recommended you activate it.

  在配置中多加一個OPTIONS參數:

 'OPTIONS': {
    'init_command': "SET sql_mode='STRICT_TRANS_TABLES'"},

 

Model

在Django中model是你數據的單1、明確的信息來源。它包含了你存儲的數據的重要字段和行爲。一般,一個模型(model)映射到一個數據庫表。

基本狀況:

  • 每一個模型都是一個Python類,它是django.db.models.Model的子類。
  • 模型的每一個屬性都表明一個數據庫字段。
  • 綜上所述,Django爲您提供了一個自動生成的數據庫訪問API

  

 

事例說明

下面這個例子定義了一個 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及更高版本。

  

   注意: 流程>>  建立Django項目---在setting中設置(告知Django建立的項目;模板中配置文件位置;數據庫配置;靜態文件配置)---手動建立數據庫---建立表結構(注意配置好項目中__init__文件).

 

字段

經常使用字段 

AutoField

自增的整形字段,必填參數primary_key=True,則成爲數據庫的主鍵。無該字段時,django自動建立。

一個model不能有兩個AutoField字段。

IntegerField

一個整數類型。數值的範圍是 -2147483648 ~ 2147483647。

CharField

字符類型,必須提供max_length參數。max_length表示字符的長度。

DateField

日期類型,日期格式爲YYYY-MM-DD,至關於Python中的datetime.date的實例。

參數:

  • auto_now:每次修改時修改成當前日期時間。(auto_now=True)
  • auto_now_add:新建立對象時自動添加當前日期時間。(auto_now_add=True)

auto_now和auto_now_add和default參數是互斥的,不能同時設置。

注意: auto_now和auto_now_add 都是記錄時間的參數,可是數據庫不提供記錄時間功能,

只有Django纔會提供記錄時間,它倆相互更改時,只須要從models記錄下來便可(Python manag.py makemigrations).

DatetimeField

日期時間字段,格式爲YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ],至關於Python中的datetime.datetime的實例。

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

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

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

    FloatField(Field)
        - 浮點型

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

    BinaryField(Field)
        - 二進制類型

  注意: 這些字段好似一個類名,括號內的是繼承其餘的類方法,如: AutoField(Field) AutoField能夠看作一個類名,Field是其繼承的類,而後經過models調用. 

 

自定義字段

自定義一個二進制字段,94

 

 以及Django字段與數據庫字段類型的對應關係。

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 MyCharField(models.Field):  # 繼承Field 類
    """
    自定義的char類型的字段類
    """
    def __init__(self, max_length, *args, **kwargs):  # 要接收max_length參數
        self.max_length = max_length          
        super(MyCharField, self).__init__(max_length=max_length, *args, **kwargs)
 
    def db_type(self, connection):
        """
        限定生成數據庫表的字段類型爲char,長度爲max_length指定的值
        """
        return 'char(%s)' % self.max_length  # 返回給數據庫char類型,並指定長度max_length

 

  使用自定義char類型字段: 

class Class(models.Model):
    id = models.AutoField(primary_key=True)
    title = models.CharField(max_length=25)
    # 使用自定義的char類型的字段
    cname = MyCharField(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中顯示選擇框的內容,用不變更的數據放在內存中從而避免跨表操做
                        如:sex= 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'), ]
                            )

  

Model Meta參數

class UserInfo(models.Model):
    nid = models.AutoField(primary_key=True)
    username = models.CharField(max_length=32)
 
    class Meta:  #(該類是爲當前表作的配置)
        # 數據庫中生成的表名稱 默認 app名稱 + 下劃線 + 類名
        db_table = "table_name"
 
        # admin中顯示的表名稱
        verbose_name = '我的信息'
 
        # verbose_name加s
        verbose_name_plural = '全部用戶信息'
 
        # 聯合索引 
        index_together = [
            ("pub_date", "deadline"),   # 應爲兩個存在的字段
        ]
 
        # 聯合惟一索引
        unique_together = (("driver", "restaurant"),)   # 應爲兩個存在的字段

 

  必知必會13條

  

<1> all():                 查詢全部結果

<2> get(**kwargs):         返回與所給篩選條件相匹配的對象,返回結果有且只有一個,若是符合篩選條件的對象超過一個或者沒有都會拋出錯誤。
 
<3> filter(**kwargs):      它包含了與所給篩選條件相匹配的對象
 
<4> exclude(**kwargs):     它包含了與所給篩選條件不匹配的對象
 
<5> values(*field):        返回一個ValueQuerySet——一個特殊的QuerySet,運行後獲得的並非一系列model的實例化對象,而是一個可迭代的字典序列
   # 沒有指定參數,獲取全部字段數據(字段和他的值);指定參數就獲取指定參數數據 , 如:id,name..
<6> values_list(*field):   它與values()很是類似,它返回的是一個元組序列,values返回的是一個字典序列
 
<7> order_by(*field):      對查詢結果排序
   例: ...order_by('id')升序  ...order_by('-id')降序  
<8> reverse():         對查詢結果反向排序,請注意reverse()一般只能在具備已定義順序的
QuerySet上調用(在model類的Meta中指定ordering或調用order_by()方法,先對指定字段進行排序)。
<9> distinct(): 從返回結果中剔除重複紀錄(對象並不是字段)(若是你查詢跨越多個表,可能在計算QuerySet
時獲得重複的結果。此時可使用distinct(),注意只有在PostgreSQL中支持按字段去重。)
<10> count(): 返回數據庫中匹配查詢(QuerySet)的對象數量。 <11> first(): 返回第一條記錄 <12> last(): 返回最後一條記錄 <13> exists(): 若是QuerySet包含數據,就返回True,不然返回False

 

 

單表查詢(雙下劃線)

models.Tb1.objects.filter(id__lt=10, id__gt=1)   # 獲取id大於1 且 小於10的值(大於greater than 小於 less than)
models.Tb1.objects.filter(id__gte=1) # 獲取id大於等於1的數據
models.Tb1.objects.filter(id__in=[11, 22, 33])   # 獲取id等於十一、2二、33的數據
models.Tb1.objects.exclude(id__in=[11, 22, 33])  # not in
 
models.Tb1.objects.filter(name__contains="ven")  # 獲取name字段包含"ven"的(區分大小寫)
models.Tb1.objects.filter(name__icontains="ven") # icontains大小寫不敏感(忽略大小寫)
 
models.Tb1.objects.filter(id__range=[1, 3])      # id範圍是1到3的(顧頭顧尾),等價於SQL的bettwen and
 
相似的還有:startswith(以×爲開頭),istartswith(同前且忽略大小寫),
      endswith(以×結尾), iendswith  date字段還能夠: models.Class.objects.filter(first_day__year
=2017)

 

 

 外鍵方法

(利用外鍵(關聯字段)從當前表拿到另外一個表的對象,而後再在該對象的基礎上查找)

 

 正向查找(從多到一的查詢,通常會把外鍵設在'多'的一方)

  對象查找(跨表)

  語法: 對象.關聯字段.字段 

  示例:

book_obj = models.Book.objects.first()  # 第一本書對象
print(book_obj.publisher)  # 獲得這本書關聯的出版社對象(經過獲得的書的對象,查到本表中該對象的publisher字段,自己
  又是外鍵,因此該字段便是關聯的那個對象)
print(book_obj.publisher.name) # 獲得出版社對象的名稱(經過獲得的書的對象,查到本表中該對象的publisher字段,
  因爲又是外鍵,因此獲得關聯的對象,從該關聯對象中獲取name字段)

 

  字段查找(跨表)

  語法: 關聯字段_字段

  示例:                                                                    

print(models.Book.objects.values_list("publisher__name")) # 查找關聯字段所關聯的表中的全部 name
      字段的值

 

 反向操做(從一到多的查詢)

  對象查找

  語法: obj.表名_set

  示例:

publisher_obj = models.Publisher.objects.first()  # 找到第一個出版社對象
books = publisher_obj.book_set.all()  # 找到第一個出版社出版的全部書 
          注意:book_set(默認 表名小寫_set ,拿到的是關係管理對象)是Django封裝的
          可是若只指定releted_name後,就須要改成 指定後的名字_set; 同時也指定
          releted_query_name,則後面的字段查詢,就要首先按照 此指定的名字_字段 命名
          若沒有指定releted_query_name,字段查詢可按照默認或者指定的releted_name命名
titles = books.values_list("title") # 找到第一個出版社出版的全部書的書名

 

  字段查找

  語法: 表名_字段

  示例 

titles = models.Publisher.objects.values_list("books__title")
# 在倆張或多張表中'__'雙下劃線表示跨表

 

多對多方法

  

  方法:

  create()  建立一個新的對象,保存對象,並將它添加到關聯對象之中,返回新建立的對象.

 

>>> import datetime
>>> models.Author.objects.first().book_set.create(title="番茄物語", publish_date=datetime.date.today())

 

  add()  把指定的model對象添加到關聯對象集中添加對象

>>> author_objs = models.Author.objects.filter(id__lt=3)
>>> models.Book.objects.first().authors.add(*author_objs)

 

    添加id 

>>> models.Book.objects.first().authors.add(*[1, 2])

 

  set()  更新model對象的關聯對象

>>> book_obj = models.Book.objects.first() # 取到第一個書籍對象
>>> book_obj.authors.set([2, 3]) # 爲第一個書籍對象設置做者的關聯id 2和3.

 

  remove()  從關聯對象集中移除執行的model對象

>>> book_obj = models.Book.objects.first()
>>> book_obj.authors.remove(3)

 

  clear()  從關聯對象集中移除一切對象

>>> book_obj = models.Book.objects.first()
>>> book_obj.authors.clear()

  注意: 對於ForeignKey對象, clear()和remove()方法僅在null=True時存在

  例子:  ForeignKey字段沒設置null=True時,

class Book(models.Model):
    title = models.CharField(max_length=32)
    publisher = models.ForeignKey(to=Publisher)

  沒有clear()和remove()方法:

>>> models.Publisher.objects.first().book_set.clear()
Traceback (most recent call last):
  File "<input>", line 1, in <module>
AttributeError: 'RelatedManager' object has no attribute 'clear'

 

  當ForeignKey字段設置null=True時 

class Book(models.Model):
    name = models.CharField(max_length=32)
    publisher = models.ForeignKey(to=Class, null=True)

  此時就有clear()和remove()方法:

>>> models.Publisher.objects.first().book_set.clear()

  注意: 對於全部類型的關聯字段,add(), create(), remove(), 和clear(), set()都會立刻更新數據庫. 換句話說在關聯的任何一端,都不須要在調用save()方法.

 

 

聚合查詢和分組

  

  聚合

 

aggregate()QuerySet 的一個終止子句,意思是說,它返回一個包含一些鍵值對的字典。 

鍵的名稱是聚合值的標識符,值是計算出來的聚合值。鍵的名稱是按照字段和聚合函數的名稱自動生成出來的。

 

   用到的內置函數  >> from django.db.models import Avg, Sum, Max,Min, Count

  例子:  

>>> from django.db.models import Avg, Sum, Max, Min, Count
>>> models.Book.objects.all().aggregate(Avg("price"))
{'price__avg': 13.233333}

 

  若是你想要爲聚合值指定一個名稱,能夠向聚合子句提供它。

>>> models.Book.objects.aggregate(average_price=Avg('price'))
{'average_price': 13.233333}

 

  若是你但願生成不止一個聚合,你能夠向aggregate()子句中添加另外一個參數。因此,若是你也想知道全部圖書價格的最大值和最小值,能夠這樣查詢:

 

>>> models.Book.objects.all().aggregate(Avg("price"), Max("price"), Min("price"))
{'price__avg': 13.233333, 'price__max': Decimal('19.90'), 'price__min': Decimal('9.90')}

 

  分組

  假設有一張公司職員表:

  

 

  按照分組求平均工資(原生sql語句)

select dept,AVG(salary) from employee group by dept;

 

  ORM查詢

from django.db.models import Avg
Employee.objects.values("dept").annotate(avg=Avg("salary").values("dept", "avg")

 

  連表查詢的分組:

 

 

  SQL查詢:

select dept.name,AVG(salary)
from employee inner join dept on (employee.dept_id=dept.id) group by dept_id;

 

  ORM查詢:

from django.db.models import Avg
models.Dept.objects.annotate(avg=Avg("employee__salary")).values("name", "avg")
# 以部門爲分組條件,查詢工人姓名和平均工資

 

  示例1:  統計每一本書的做者個數

>>> book_list = models.Book.objects.all().annotate(author_num=Count("author"))
  # annotate查詢 會把查詢結果放到對象之中,做爲對象屬性存放.
>>> for obj in book_list: ... print(obj.author_num) ...
2
1
1

 

  示例2:  統計出每一個出版社買的最便宜的書價格

>>> publisher_list = models.Publisher.objects.annotate(min_price=Min("book__price"))
>>> for obj in publisher_list:
...     print(obj.min_price)
...     
9.90
19.90

 

  方法二:

>>> models.Book.objects.values("publisher__name").annotate(min_price=Min("price"))

<QuerySet [{'publisher__name': '沙河出版社', 'min_price': Decimal('9.90')},
{'publisher__name': '人民出版社', 'min_price': Decimal('19.90')}]>

 

  示例3: 統計不止一個做者的圖書

>>> models.Book.objects.annotate(author_num=Count("author")).filter(author_num__gt=1)
<QuerySet [<Book: 番茄物語>]>  # 拿到的是一個對象,想要拿到整本書(對象)的信息,就要 .values()

 

  示例4: 根據一本圖書做者數量的多少對查詢集QuerySet進行排序

>>> models.Book.objects.annotate(author_num=Count("author")).order_by("author_num")
<QuerySet [<Book: 香蕉物語>, <Book: 橘子物語>, <Book: 番茄物語>]> 

 

  示例5: 查詢各個做者出的書的總價格

>>> models.Author.objects.annotate(sum_price=Sum("book__price")).values("name", "sum_price")
<QuerySet [{'name': '小精靈', 'sum_price': Decimal('9.90')},
{'name': '小仙女', 'sum_price': Decimal('29.80')}, {'name': '小魔女', 'sum_price': Decimal('9.90')}]>

 

  F查詢

Django 提供 F() 來作這樣的比較。F() 的實例能夠在查詢中引用字段,來比較同一個 model 實例中兩個不一樣字段的值。

示例1:

查詢評論數大於收藏數的書籍

from django.db.models import F
models.Book.objects.filter(commnet_num__gt=F('keep_num'))
# 查詢出的是對象,若要對象的信息須要 .values()

 

Django支持F()對象之間以及F()對象和常數之間的加減乘除和取模的操做.

models.Book.objects.filter(commnet_num__lt=F('keep_num')*2)

 

修改操做也可使用F函數,好比將每一本書的價格提升30元

models.Book.objects.all().update(price=F("price")+30)
# update 表示更新指定字段  若是要用set,則會把全部字段都會更新一遍,效率低.

 

  引伸:  若是要修改char字段呢?

  例 把全部書名後面加上(老潘)

>>> from django.db.models.functions import Concat
>>> from django.db.models import Value
>>> models.Book.objects.all().update(title=Concat(F("title"), Value("("), Value("老潘"), Value(")")))

 

  Q查詢

filter() 等方法中的關鍵字參數查詢都是一塊兒進行「AND」 的。 若是你須要執行更復雜的查詢(例如OR語句),你可使用Q對象

 

 

  示例1:  查詢做者名是皮皮蝦或大志

models.Book.objects.filter(Q(authors__name="皮皮蝦")|Q(authors__name="大志"))

你能夠組合&(and) 和|(或)  操做符以及使用括號進行分組來編寫任意複雜的Q 對象。同時,Q 對象可使用~(非)操做符取反,這容許組合正常的查詢和取反(NOT) 查詢。

 

  示例: 查詢做者名字是皮皮蝦而且不是2018年出版的書的書名.

>>> models.Book.objects.filter(Q(author__name="皮皮蝦")
& ~Q(publish_date__year=2018)).values_list("title") <QuerySet [('番茄物語',)]>

 

查詢函數能夠混合使用Q 對象和關鍵字參數。全部提供給查詢函數的參數(關鍵字參數或Q 對象)都將"AND」在一塊兒。可是,若是出現Q 對象,它必須位於全部關鍵字參數的前面。

  

  例如: 查詢出版社年份是 2017或2018,書名帶物語的全部書.

>>> models.Book.objects.filter(Q(publish_date__year=2018) | 
Q(publish_date__year=2017), title__icontains="物語") <QuerySet [<Book: 番茄物語>, <Book: 香蕉物語>, <Book: 橘子物語>]>

 

事物

整個操做過程,一處失敗都失敗,以前的操做都是無效的,全部操做都回退.所有成功才能成功.

好比:銀行轉帳,一步操做失誤,轉帳過程失敗,錢也會回退.

示例:

import os

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

    import datetime
    from app01 import models

    try:
        from django.db import transaction # 導入事物
        with transaction.atomic(): # 其中的操做要麼都成功,要麼都失敗
            new_publisher = models.Publisher.objects.create(name="火星出版社")
            models.Book.objects.create(title="橘子物語", publish_date=datetime.date.today(), publisher_id=10)  # 指定一個不存在的出版社id
    except Exception as e:
        print(str(e))

 

Cookie

 cookie的由來  

你們都知道HTTP協議是無狀態的。

無狀態的意思是每次請求都是獨立的,它的執行狀況和結果與前面的請求和以後的請求都無直接關係,它不會受前面的請求響應狀況的直接影響,也不會直接影響後面的請求響應狀況。

一句有意思的話來描述就是,對服務器來講,每次的請求都是全新的。

狀態能夠理解爲客戶端和服務器在某次會話中產生的數據,那無狀態的就覺得這些數據不會被保留。會話中產生的數據又是咱們須要保存的,也就是說要「保持狀態」。所以Cookie就是在這樣一個場景下誕生。

  

 什麼是Cookie

  Cookie具體指的是一段小信息,它是服務器發送出來存儲在瀏覽器上的一組組鍵值對,下次訪問服務器時瀏覽器會自動攜帶這些鍵值對,以便服務器提取有用信息。瀏覽器有權利是否進行保存.

 

 cookie的原理

cookie的工做原理是:由服務器產生內容,瀏覽器收到請求後保存在本地;當瀏覽器再次訪問時,瀏覽器會自動帶上Cookie,這樣服務器就能經過Cookie的內容來判斷這個是「誰」了。

 

 查看Cookie

  咱們使用Chrome瀏覽器,打開開發者工具.

 

 

 Django中操做Cookie

  獲取Cookie

request.COOKIES['key']
request.get_signed_cookie('key', default=RAISE_ERROR, salt='', max_age=None)

 

get_signed_cookie方法的參數:

  • default: 默認值
  • salt: 加密鹽
  • max_age: 後臺控制過時時間

 

   設置Cookie

rep = HttpResponse(...) # HttpResponse對象
rep = render(request, ...) 

rep.set_cookie(key,value,...)  # 設置
rep.set_signed_cookie(key,value,salt='加密鹽',...)

 

參數:

  • key, 鍵
  • value='', 值
  • max_age=None, 超時時間
  • expires=None, 超時時間(IE requires expires, so set it if hasn't been already.)
  • path='/', Cookie生效的路徑,/ 表示根路徑,特殊的:根路徑的cookie能夠被任何url的頁面訪問
  • domain=None, Cookie生效的域名
  • secure=False, https傳輸
  • httponly=False 只能http協議傳輸,沒法被JavaScript獲取(不是絕對,底層抓包能夠獲取到也能夠被覆蓋)

 

  刪除Cookie

def logout(request):
    rep = redirect("/login/")
    rep.delete_cookie("user")  # 刪除用戶瀏覽器上以前設置的user的cookie值
    return rep
# Cookie已經封裝在request中, 刪除須要用瀏覽器去刪除,即拿HttpResponse對象去刪.

 

   Cookie版登陸校驗

def check_login(func):
    @wraps(func)
    def inner(request, *args, **kwargs):
      # 獲取到url中 full_path路徑 next_url
= request.get_full_path() if request.get_signed_cookie("login", salt="SSS", default=None) == "yes": # 已經登陸的用戶... return func(request, *args, **kwargs) else: # 沒有登陸的用戶,跳轉剛到登陸頁面 return redirect("/login/?next={}".format(next_url)) return inner def login(request): if request.method == "POST":
    # 獲取到填寫的姓名與密碼 username
= request.POST.get("username") passwd = request.POST.get("password") if username == "xxx" and passwd == "dashabi": next_url = request.GET.get("next")
        # 當存在next_url,而且不是logout,則跳轉到 next_url表示的網頁
if next_url and next_url != "/logout/": response = redirect(next_url) else: response = redirect("/class_list/")
        # 設置cookie, response.set_signed_cookie(
"login", "yes", salt="SSS") return response return render(request, "login.html")

 

 Session

 Session

Cookie雖然在必定程度上解決了「保持狀態」的需求,可是因爲Cookie自己最大支持4096字節,以及Cookie自己保存在客戶端,可能被攔截或竊取,所以就須要有一種新的東西,它能支持更多的字節,而且他保存在服務器,有較高的安全性。這就是Session。

問題來了,基於HTTP協議的無狀態特徵,服務器根本就不知道訪問者是「誰」。那麼上述的Cookie就起到橋接的做用。

咱們能夠給每一個客戶端的Cookie分配一個惟一的id,這樣用戶在訪問時,經過Cookie,服務器就知道來的人是「誰」。而後咱們再根據不一樣的Cookie的id,在服務器上保存一段時間的私密資料,如「帳號密碼」等等。

總結而言:Cookie彌補了HTTP無狀態的不足,讓服務器知道來的人是「誰」;可是Cookie以文本的形式保存在本地,自身安全性較差;因此咱們就經過Cookie識別不一樣的用戶,對應的在Session裏保存私密的信息以及超過4096字節的文本。

另外,上述所說的Cookie和Session實際上是共通性的東西,不限於語言和框架。

 session是存放在服務器上的一組組鍵值對,cookie是存放在瀏覽器上的一組組鍵值對.session與cookie要配合使用.

 

 Django中Session相關方法

# 設置、獲取、刪除Session中數據
request.session['k1'] # 取不到k1時會報錯
request.session.get('k1',None) # 取不到會返回None
request.session['k1'] = 123  # 設置k1位123
request.session.setdefault('k1',123) # 存在則不設置
del request.session['k1']


# 全部 鍵、值、鍵值對
request.session.keys() # 拿到全部key
request.session.values() # 拿到全部的值
request.session.items()  # 拿到每個鍵值對
request.session.iterkeys()
request.session.itervalues()
request.session.iteritems()

# 拿到會話session的key
request.session.session_key

# 將全部Session失效日期小於當前日期的數據刪除
request.session.clear_expired()

# 檢查會話session的key在數據庫中是否存在
request.session.exists("session_key")

# 刪除當前會話的全部Session數據,可是不會刪除cookie
request.session.delete()
  
# 刪除當前的會話數據並刪除會話的Cookie。
request.session.flush() 
    這用於確保前面的會話數據不能夠再次被用戶的瀏覽器訪問
    例如,django.contrib.auth.logout() 函數中就會調用它。

# 設置會話Session和Cookie的超時時間
request.session.set_expiry(value)
    * 若是value是個整數,session會在些秒數後失效。
    * 若是value是個datatime或timedelta,session就會在這個時間後失效。
    * 若是value是0,用戶關閉瀏覽器session就會失效。
    * 若是value是None,session會依賴全局session失效策略。

 

 Session流程解析

首次登錄沒有cookie和session,瀏覽器上發送請求並生成一串字符串(鑰匙), 服務器生成鑰匙空間,存放對應數據,返回cookie(響應頭),瀏覽器接收到響應後,把cookie存到本地,再次訪問時,會帶着以前存放的cookie拿到存放在服務器的數據.

 

 

 

Session版本登陸驗證

from functools import wraps


def check_login(func):
    @wraps(func)
    def inner(request, *args, **kwargs):
        next_url = request.get_full_path()
        if request.session.get("user"):
            return func(request, *args, **kwargs)
        else:
            return redirect("/login/?next={}".format(next_url))
    return inner


def login(request):
    if request.method == "POST":
        user = request.POST.get("user")
        pwd = request.POST.get("pwd")

        if user == "alex" and pwd == "alex1234":
            # 設置session
            request.session["user"] = user
            # 獲取跳到登錄頁面以前的URL
            next_url = request.GET.get("next")
            # 若是有,就跳轉回登錄以前的URL
            if next_url:
                return redirect(next_url)
            # 不然默認跳轉到index頁面
            else:
                return redirect("/index/")
    return render(request, "login.html")


@check_login
def logout(request):
    # 刪除全部當前請求相關的session
    request.session.delete()
    return redirect("/login/")


@check_login
def index(request):
    current_user = request.session.get("user", None)
    return render(request, "index.html", {"user": current_user})

 

 Django中的Session配置

  Django中默認支持Session,其內部提供了5中類型的Session供開發者使用.

1. 數據庫Session
SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默認)

2. 緩存Session
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # 引擎
SESSION_CACHE_ALIAS = 'default'                            # 使用的緩存別名(默認內存緩存,也能夠是memcache),此處別名依賴緩存的設置

3. 文件Session
SESSION_ENGINE = 'django.contrib.sessions.backends.file'    # 引擎
SESSION_FILE_PATH = None                                    # 緩存文件路徑,若是爲None,則使用tempfile模塊獲取一個臨時地址tempfile.gettempdir() 

4. 緩存+數據庫
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'        # 引擎

5. 加密Cookie Session
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'   # 引擎

其餘公用設置項:
SESSION_COOKIE_NAME = "sessionid"         # Session的cookie保存在瀏覽器上時的key,即:sessionid=隨機字符串(默認)
SESSION_COOKIE_PATH = "/"                 # Session的cookie保存的路徑(默認)
SESSION_COOKIE_DOMAIN = None               # Session的cookie保存的域名(默認)
SESSION_COOKIE_SECURE = False              # 是否Https傳輸cookie(默認)
SESSION_COOKIE_HTTPONLY = True             # 是否Session的cookie只支持http傳輸(默認)
SESSION_COOKIE_AGE = 1209600               # Session的cookie失效日期(2周)(默認)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False    # 是否關閉瀏覽器使得Session過時(默認)
SESSION_SAVE_EVERY_REQUEST = False         # 是否每次請求都保存Session,默認修改以後才保存(默認)

 

注意:用sessions時,要明確setting設置中的設置操做和建立數據庫(記得下載驅動).

相關文章
相關標籤/搜索