MongoEngine中文文檔


簡介:

MongoEngine是一個基於pymongo開發的ODM庫,對應與SQLAlchemy。同時,在MongoEngine基礎上封裝了Flask-MongoEngine,用於支持flask框架。

官方地址:http://docs.mongoengine.org/index.html

入門教程

1.安裝MongoEngine

pip3 install mongoengine

2.連接至MongoDB

2.1 連接數據庫

-2.11 方法一 連接本地數據庫

from mongoengine import connect
connect('dbname', alias='別名')

-2.12 方法二 連接遠程數據庫

from mongoengine import connect
connect('dbname', host='遠程服務器IP地址', post=開放的端口號)

-2.13 方法三 連接帶有驗證的遠程數據庫

from mongoengine import connect
connect('dbname', username='用戶名', password='密碼', authentication_source='admin',  host='遠程服務器IP地址', post=開放的端口號)

2.2 連接至副本

from mongoengine import connect

# Regular connect
connect('dbname', replicaset='rs-name')

# URI風格連接
connect(host='mongodb://localhost/dbname?replicaSet=rs-name')

2.3 連接至多個數據庫

要使用多個數據庫,您可以使用connect()並為連接提供別名(alisa),默認使用“default”。
在后台,這用於register_connection()存儲數據,如果需要,您可以預先注冊所有別名。

-2.31 在不同數據庫中定義的文檔

通過在元數據中提供db_alias,可以將各個文檔附加到不同的數據庫 。這允許DBRef 對象指向數據庫和集合。下面是一個示例模式,使用3個不同的數據庫來存儲數據

connect (alias = 'user-db-alias' , db = 'user-db' )
connect (alias = 'book-db-alias' , db = 'book-db' )
connect (alias = 'users-books-db -alias' , db = 'users-books-db' )

class  User (Document ):
    name  =  StringField ()
    meta  =  { 'db_alias' : 'user-db-alias' } 

class  Book(Document ):
    name  =  StringField ()
    meta  =  { 'db_alias' : 'book-db-alias' } 

class  AuthorBooks (Document ):
    author  =  ReferenceField (User )
    book  =  ReferenceField (Book )
    meta  =  { 'db_alias' : ' users-books-db-alias' }

-2.32 斷開現有連接

該功能 disconnect() 可用於斷開特定連接。這可用於全局更改連接:

from mongoengine import connect, disconnect
connect('a_db', alias='db1')    # ==》 建立別名為“db1”的連接

class User(Document):
    name = StringField()
    meta = {'db_alias': 'db1'}

disconnect(alias='db1')   # ==》 斷開 別名為“db1”的連接

connect('another_db', alias='db1')      # ==》 由於上一步斷開了別名為db1的連接,現在連接其它數據庫時又可以使用別名“db1”作為連接名

2.4 上下文管理器 context_managers

有時您可能希望切換數據庫或集合以進行查詢

- 2.4.1 切換數據庫 switch_db()

switch_db上允許更改數據庫別名給定類,允許快速和方便地跨數據庫訪問:
⚠️注:切換數據庫,必須預先注冊別名(使用已注冊的別名)

from mongoengine.context_managers import switch_db

class User(Document):
    name = StringField()
    meta = {'db_alias': 'user-db'}

with switch_db(User, 'archive-user-db') as User:
    User(name='Ross').save()  #  ===》 這時會將數據保存至 'archive-user-db'

2.4.2 切換文檔 switch_collection()

switch_collection()上下文管理器允許更改集合,允許快速和方便地跨集合訪問:

from mongoengine.context_managers import switch_collection

class Group(Document):
    name = StringField()

Group(name='test').save()  # 保存至默認數據庫

with switch_collection(Group, 'group2000') as Group:
    Group(name='hello Group 2000 collection!').save()  # 將數據保存至 group2000 集合

3.【定義文檔 Defining Documents】

3.1、定義文檔模型 Defining a document’s schema

MongoEngine允許為文檔定義模式,因為這有助於減少編碼錯誤,並允許在可能存在的字段上定義方法。
為文檔定義模式,需創建一個繼承自Document的類,將字段對象作為類屬性添加到文檔類:

from mongoengine import *
import datetime

class Page(Document):
    title = StringField(max_length=200, required=True)      # ===》 創建一個String型的字段title,最大長度為200字節且為必填項
    date_modified = DateTimeField(default=datetime.datetime.utcnow)      # ===》 創建一個時間類型的字段(utcnow是世界時間,now是本地計算機時間)

3.2、定義“動態”文檔模型 Defining a dynamic document’s schema

動態文檔(Dynamic Document)跟非動態文檔(document)的區別是,動態文檔可以在模型基礎上新增字段,但非動態文檔不允許新增。
⚠️注意:動態文檔有一點需要注意:字段不能以_開頭

from mongoengine import *

class Page(DynamicDocument):
    title = StringField(max_length=200, required=True)

# 創建一個page實例,並新增tags字段
>>> page = Page(title='Using MongoEngine')
>>> page.tags = ['mongodb', 'mongoengine']
>>> page.save()      =====》# 不會報錯,可以被保存至數據庫

>>> Page.objects(tags='mongoengine').count()      =====》# 統計 tags=‘mongengine’的文檔數
>>> 1

3.3 字段 Fields

字段類型包含:(以“》”開頭的是常用類型,“》”僅用於標注)

》BinaryField    #  二進制字段
》BooleanField     # 布爾型字段
》DateTimeField    # 后六位精確到毫妙的時間類型字段
ComplexDateTimeField   # 后六位精確到微妙的時間類型字段
DecimalField    # 
》DictField    # 字典類型字段
》DynamicField   # 動態類型字段,能夠處理不同類型的數據
》EmailField     # 郵件類型字段
》EmbeddedDocumentField   # 嵌入式文檔類型
》StringField    # 字符串類型字段
》URLField    # URL類型字段
》SequenceField     # 順序計數器字段,自增長
》ListField       # 列表類型字段
》ReferenceField    # 引用類型字段
LazyReferenceField
》IntField     # 整數類型字段,存儲大小為32字節
LongField      # 長整型字段,存儲大小為64字節
EmbeddedDocumentListField
FileField  #  列表類型字段
FloatField   # 浮點數類型字段
GenericEmbeddedDocumentField   # 
GenericReferenceField
GenericLazyReferenceField
GeoPointField
ImageField
MapField
ObjectIdField
SortedListField  
UUIDField
PointField
LineStringField
PolygonField
MultiPointField
MultiLineStringField
MultiPolygonField

- 3.3.1 字段通用參數

db_field (默認值:無) # MongoDB字段名稱
required (默認值:False) # 是否必須填寫,如果設置為True且未在文檔實例上設置字段,則在ValidationError驗證文檔時將引發
default (默認值:無) # 默認值
unique (默認值:False) # 是否唯一,如果為True,則集合中的任何文檔都不具有此字段的相同值
unique_with (默認值:無) # 唯一 字段列表
primary_key (默認值:False) # 主鍵
choices (默認值:無) # 限制​​該字段的值(例如列表,元組或集合)
validation (可選的) # 可調用以驗證字段的值。callable將值作為參數,如果驗證失敗,則應引發ValidationError
「舉例」

def _not_empty(val):
    if not val:
        raise ValidationError('value can not be empty')

class Person(Document):
    name = StringField(validation=_not_empty)

- 3.3.2 類標字段 ListField

使用ListField字段類型可以向 Document添加項目列表。ListField將另一個字段對象作為其第一個參數,該參數指定可以在列表中存儲哪些類型元素:
「舉例」

class  Page (Document ):
    tags  =  ListField (StringField (max_length = 50 ))   # ===》 ListField中存放字符串字段
# 應該可以存放任意類型字段

- 3.3.3 內嵌文檔 Embedded Document

MongoDB能夠將文檔嵌入到其他文檔中。要創建嵌入式文檔模型,需要繼承EmbeddedDocument:
「舉例」

# 創建嵌入式文檔模型
class Comment(EmbeddedDocument):
    content = StringField()

# 創建文檔模型,且將 Comment 嵌入Post.comments列表字段中
class Page(Document):
    comments = ListField(EmbeddedDocumentField(Comment))

comment1 = Comment(content='Good work!')
comment2 = Comment(content='Nice article!')
page = Page(comments=[comment1, comment2])

- 3.3.4 字典字段 Dictionary Fields

不知道想要存儲什么結構時,可以使用字典字段(字典字段不支持驗證),字典可以存儲復雜數據,其他字典,列表,對其他對象的引用,因此是最靈活的字段類型:
「舉例」

class SurveyResponse(Document):
    date = DateTimeField()
    user = ReferenceField(User)     # ===》 引用字段,引用User類
    answers = DictField()

survey_response = SurveyResponse(date=datetime.utcnow(), user=request.user)
response_form = ResponseForm(request.POST)      # ===》 這段和下一段代碼,我沒看明白
survey_response.answers = response_form.cleaned_data()        # ===》 這段和上一段代碼,我沒看明白
survey_response.save()

- 3.3.5 引用字段 Reference fields

引用文檔,類似關系型數據庫中的外鍵,如果想引用自身這個類時,使用self關鍵字:
「舉例」

class User(Document):
    name = StringField()

class Page(Document):
    content = StringField()
    author = ReferenceField(User)     # ===》引用User,即Page文檔與User文檔建立外鍵關系

john = User(name="John Smith")
john.save()

post = Page(content="Test Page")
post.author = john
post.save()

引用自身「舉例」

class Employee(Document):
    name = StringField()
    boss = ReferenceField('self')    #  ====》引用自身
    profile_page = ReferenceField('ProfilePage')

class ProfilePage(Document):
    content = StringField()

3.3.5.1. 用ListField建立一對多關系引用 One to Many with ListFields

「舉例」

class User(Document):
    name = StringField()

class Page(Document):
    content = StringField()
    authors = ListField(ReferenceField(User))

bob = User(name="Bob Jones").save()
john = User(name="John Smith").save()

Page(content="Test Page", authors=[bob, john]).save()
Page(content="Another Page", authors=[john]).save()

# 查找authored中包含  Bob 的文檔
Page.objects(authors__in=[bob])

# 查找authored中包含  Bob 和 john 的文檔
Page.objects(authors__all=[bob, john])

# 從 被引用的文檔中刪除作者 bob
Page.objects(id='...').update_one(pull__authors=bob)

# 將John添加到被引用文檔的作者列表中
Page.objects(id='...').update_one(push__authors=john)

3.3.5.2 刪除引用文檔

「舉例」
例子意味着,如果刪除Employee時也會刪除對應的關聯文檔ProfilePage

class ProfilePage(Document):
    ...
    employee = ReferenceField('Employee', reverse_delete_rule=mongoengine.CASCADE)

reverse_delete_rule有以下可選值:

DO_NOTHING
這是默認設置,不會執行任何操作。刪除速度很快,但可能導致數據庫不一致或懸空引用。
DENY
如果仍存在對要刪除的對象的引用,則拒絕刪除。
NULLIFY
刪除任何仍然引用被刪除對象的對象字段(使用MongoDB的“未設置”操作),有效地使關系無效。
CASCADE
將首先刪除包含引用要刪除的對象的字段的任何對象。
PULL
從ListField(ReferenceField)的任何對象的字段中刪除對象的引用(使用MongoDB的“拉”操作 )。

3.3.5.3 通用引用文檔

GenericReferenceField允許您引用任何類型Document,因此不需要將 Document子類作為構造函數參數(也可以使用選項參數來限制可接受的文檔類型):
「舉例」

class Link(Document):
    url = StringField()

class Post(Document):
    title = StringField()

class Bookmark(Document):
    bookmark_object = GenericReferenceField()

link = Link(url='http://hmarr.com/mongoengine/')
link.save()

post = Post(title='Using MongoEngine')
post.save()

Bookmark(bookmark_object=link).save()
Bookmark(bookmark_object=post).save()

⚠️注:使用GenericReferenceFields的效率略低於標准ReferenceFields,因此如果只引用一種文檔類型,則更推薦使用 ReferenceField

- 3.3.6 唯一性約束 Uniqueness constraints

MongoEngine提供unique=True給Field構造函數來指定字段在集合中應該是唯一的。插入相同文檔時,則將引發NotUniqueError。您還可以使用指定多字段(列表或元組)唯一性約束unique_with:

「舉例」

class  User (Document ):
    username  =  StringField (unique = True )
    first_name  =  StringField ()
    last_name  =  StringField (unique_with = 'first_name' )   # ====》也可以是列表或元組

- 3.3.7 保存時跳過文檔驗證 Skipping Document validation on save

可以通過設置validate=False,調用save() 方法時跳過整個文檔驗證過程 :
「舉例」

class Recipient(Document):
    name = StringField()
    email = EmailField()

recipient = Recipient(name='admin', email='root@localhost')
recipient.save()               # 會拋出 ValidationError 錯誤
recipient.save(validate=False)     #  不會拋出錯誤並會持久化數據

3.4 文檔集

默認情況下,集合的名稱是類的名稱,轉換為小寫。如果您需要更改集合的名稱(例如,將MongoEngine與現有數據庫一起使用),則創建一個meta在文檔上調用的類字典屬性,並用collection設置集合的名稱:
「舉例」

class  Page (Document ):
    title  =  StringField (max_length = 200 , required = True )
    meta  =  { 'collection' : 'cmsPage' }   # ====》 自定義集合名稱

- 3.4.1 集合上限

一個集合可存儲大小的上限默認為10m,但可通過max_size來設置集合可存儲大小,同時也可使用max_documents來設置最大文檔數量:
「舉例」

class Log(Document):
    ip_address = StringField()
    meta = {'max_documents': 1000, 'max_size': 2000000}    # ====》 最大文檔數為1000個,存儲大小為200萬字節

3.5 索引 Indexes

為了能在數據庫中更快查找數據,需要創建索引。索引可以在模型文檔中的meta中指定索引字段以及索引方向。
可以通過在字段名前加上$來指定文本索引。可以通過在字段名前加上#來指定散列索引
「舉例]

class Page(Document):
    category = IntField()
    title = StringField()
    rating = StringField()
    created = DateTimeField()
    meta = {
        'indexes': [
            'title',
            '$title',  #   ====》  文本索引
            '#title',  #     =====》  哈希索引
            ('title', '-rating'),
            ('category', '_cls'),       #   ====》  沒看明白, 有緣的小伙伴望留言指導一下
            {
                'fields': ['created'],           #   ====》  沒看明白, 有緣的小伙伴望留言指導一下
                'expireAfterSeconds': 3600       #   ====》  設置文檔在3600秒后過期,數據庫會在3600秒后刪除過期文檔
            }
        ]
    }

「參數說明」
如果傳遞了字典,則可以使用其他選項。有效選項包括但不限於:

fields (默認值:無)
要索引的字段。以與上述相同的格式指定。
cls (默認值:True)
如果您具有繼承並已allow_inheritance打開的多態模型,則 可以配置索引是否應將該_cls字段自動添加到索引的開頭。
sparse (默認值:False)
索引是否應該稀疏。
unique (默認值:False)
索引是否應該是唯一的。
expireAfterSeconds (可選)
允許您通過設置使字段到期的時間(以秒為單位)自動使集合中的數據到期。
name (可選)
允許您指定索引的名稱
collation (可選)
允許創建不區分大小寫的索引(僅限MongoDB v3.4 +)

⚠️注意:需要刪除索引時,去除模型中索引字段的同時需要從數據庫層使用pymongo或者shell手動刪除索引

- 3.5.1 全局索引默認選項

class Page(Document):
    title = StringField()
    rating = StringField()
    meta = {
        'index_opts': {},
        'index_background': True,
        'index_cls': False,
        'auto_create_index': True,
        'index_drop_dups': True,
    }

「參數說明」
index_opts (可選)
設置默認索引選項
index_background (可選)
index_background=True時,在后台創建索引
index_cls (可選)
一種關閉_cls的特定索引的方法。
auto_create_index (可選)
當這是True(默認)時,MongoEngine將確保每次運行命令時MongoDB中都存在正確的索引。可以在單獨管理索引的系統中禁用此功能。禁用此功能可以提高性能。

- 3.5.2 復合索引和索引子文檔

mongodb的最佳地理索引是新的“2dsphere”,它有改進的球形模型,在查詢時提供更好的性能和更多選項。以下字段將明確添加“2dsphere”索引:

  • PointField # 存儲經度和緯度坐標的GeoJSON字段
{'type' : 'Point' ,
 'coordinates' : [x, y]}


# ⚠️ 注意:參數	auto_index (bool) – 會自動創建 ‘2dsphere’ 索引
  • LineStringField # 存儲經度和緯度坐標線的GeoJSON字段
{'type' : 'LineString' ,
 'coordinates' : [[x1, y1], [x2, y2] ... [xn, yn]]}
  • PolygonField # 存儲經度和緯度坐標多邊形的GeoJSON字段
{'type' : 'Polygon' ,
 'coordinates' : [[[x1, y1], [x1, y1] ... [xn, yn]],
                  [[x1, y1], [x1, y1] ... [xn, yn]]}

# ⚠️ 注意:參數	auto_index (bool) – 會自動創建 ‘2dsphere’ 索引
  • MultiPointField # 存儲點列表的GeoJSON字段
{'type' : 'MultiPoint' ,
 'coordinates' : [[x1, y1], [x2, y2]]}

# ⚠️ 注意:參數	auto_index (bool) – 會自動創建 ‘2dsphere’ 索引
  • MultiLineStringField # 存儲LineStrings列表的GeoJSON字段
{'type' : 'MultiLineString' ,
 'coordinates' : [[[x1, y1], [x1, y1] ... [xn, yn]],
                  [[x1, y1], [x1, y1] ... [xn, yn]]]}
                  
# ⚠️ 注意:參數	auto_index (bool) – 會自動創建 ‘2dsphere’ 索引
  • MultiPolygonField # 存儲多邊形列表的GeoJSON字段。
{'type' : 'MultiPolygon' ,
 'coordinates' : [[
       [[x1, y1], [x1, y1] ... [xn, yn]],
       [[x1, y1], [x1, y1] ... [xn, yn]]
   ], [
       [[x1, y1], [x1, y1] ... [xn, yn]],
       [[x1, y1], [x1, y1] ... [xn, yn]]
   ]
} 

# ⚠️ 注意:參數	auto_index (bool) – 會自動創建 ‘2dsphere’ 索引

- 3.5.3.1 Pre MongoDB 2.4 Geo ===>這個沒看明白,官網資料如下

原文:Geospatial indexes will be automatically created for all GeoPointFields
譯文:將自動為所有GeoPointFields 創建地理空間索引

原文:It is also possible to explicitly define geospatial indexes. This is useful if you need to define a geospatial index on a subfield of a DictField or a custom field that contains a point. To create a geospatial index you must prefix the field with the * sign.
譯文:也可以明確定義地理空間索引。如果您需要在DictField包含點的自定義字段的子字段上定義地理空間索引,這將非常有用 。要創建地理空間索引,必須在字段前加上 * 符號。

class  Place (Document ):
    location  =  DictField ()
    meta  =  { 
        'indexes' : [ 
            '* location.point' ,
        ],
    }

- 3.5.4. 生存時間索引 Time To Live indexes

一種特殊的索引類型,允許您在給定時間段后自動使集合中的數據到期。有關 更多信息,請參閱官方 ttl文檔。常見的用例可能是會話數據:

class Session(Document):
    created = DateTimeField(default=datetime.utcnow)
    meta = {
        'indexes': [
            {'fields': ['created'], 'expireAfterSeconds': 3600}
        ]
    }

⚠️注意:TTL索引發生在MongoDB服務器上而不是應用程序代碼中,因此在刪除文檔時不會觸發任何信號。如果您需要在刪除時觸發信號,則必須在應用程序代碼中處理刪除文檔。

- 3.5.5 比較索引 compare_indexes ===>沒看明白

原文:Use mongoengine.Document.compare_indexes() to compare actual indexes in the database to those that your document definitions define. This is useful for maintenance purposes and ensuring you have the correct indexes for your schema.
譯文:
用mongoengine.Document.compare_indexes()實際索引在數據庫中的那些文檔定義進行比較。這對於維護目的很有用,並確保您擁有正確的架構索引。
將MongoEngine中定義的索引與數據庫中存在的索引進行比較。返回任何缺失/額外索引。

3.6 排序 Ordering

from datetime import datetime

class BlogPost(Document):
    title = StringField()
    published_date = DateTimeField()

    meta = {
        'ordering': ['-published_date']     # 按發布時間倒序
    }

blog_post_1 = BlogPost(title="Blog Post #1")
blog_post_1.published_date = datetime(2010, 1, 5, 0, 0 ,0)

blog_post_2 = BlogPost(title="Blog Post #2")
blog_post_2.published_date = datetime(2010, 1, 6, 0, 0 ,0)

blog_post_3 = BlogPost(title="Blog Post #3")
blog_post_3.published_date = datetime(2010, 1, 7, 0, 0 ,0)

blog_post_1.save()
blog_post_2.save()
blog_post_3.save()

# 使用BlogPost.meta.ordering設置的排序規則,獲取第一條BlogPost文檔
latest_post = BlogPost.objects.first()
assert latest_post.title == "Blog Post #3"

# 覆蓋模型中的設置,按在調用時的需求排序查詢數據
first_post = BlogPost.objects.order_by("+published_date").first()
assert first_post.title == "Blog Post #1"

3.7 分片鍵 Shard keys

原文:If your collection is sharded by multiple keys, then you can improve shard routing (and thus the performance of your application) by specifying the shard key, using the shard_key attribute of meta. The shard key should be defined as a tuple.

This ensures that the full shard key is sent with the query when calling methods such as save(), update(), modify(), or delete() on an existing Document instance:

如果集合是通過多個鍵進行分片,那么可以通過使用shard_key屬性 來指定分片鍵來改進分片路由(以及應用程序的性能)meta。分片鍵應定義為元組。

class LogEntry(Document):
    machine = StringField()
    app = StringField()
    timestamp = DateTimeField()
    data = StringField()

    meta = {
        'shard_key': ('machine', 'timestamp'),
        'indexes': ('machine', 'timestamp'),
    }

3.8 文檔繼承 Document inheritance

原文:To create a specialised type of a Document you have defined, you may subclass it and add any extra fields or methods you may need. As this is new class is not a direct subclass of Document, it will not be stored in its own collection; it will use the same collection as its superclass uses. This allows for more convenient and efficient retrieval of related documents – all you need do is set allow_inheritance to True in the meta data for a document.:

譯文:要創建Document已定義的特定類型,可以將其子類化並添加您可能需要的任何額外字段或方法。由於這是新類不是直接的子類 Document,因此不會存儲在自己的集合中; 它將使用與其超類使用相同的集合。這樣可以更方便,更有效地檢索相關文檔 - 您需要做的只是allow_inheritance在meta文檔數據中設置為True :

# Stored in a collection named 'page'
class Page(Document):
    title = StringField(max_length=200, required=True)

    meta = {'allow_inheritance': True}

# Also stored in the collection named 'page'
class DatedPage(Page):
    date = DateTimeField()

⚠️ 注意:從0.8版本以后繼承默認是Fasle,如希望允許被繼承需要在meta中手動設置 allow_inheritance:True

- 3.8.1 使用現有數據

由於MongoEngine不再默認需要_cls,您可以快速輕松地使用現有數據。只需定義文檔以匹配數據庫中的預期模式即可:

# 這個模型將在集合名為 'cmsPage'工作
class Page(Document):
    title = StringField(max_length=200, required=True)
    meta = {
        'collection': 'cmsPage'
    }

原文:If you have wildly varying schemas then using a DynamicDocument might be more appropriate, instead of defining all possible field types.
譯文:如果現有數據庫中存在着各式各樣的數據模型,建議使用動態文檔 DynamicDocument
If you use Document and the database contains data that isn’t defined then that data will be stored in the document._data dictionary.

3.9 抽象類

原文:If you want to add some extra functionality to a group of Document classes but you don’t need or want the overhead of inheritance you can use the abstract attribute of meta. This won’t turn on Document inheritance but will allow you to keep your code DRY:

譯文:在不繼承的情況下,擴展屬性時可以在meta中使用 abstract:True:

class  BaseDocument (Document ):
    meta  =  { 
        'abstract' : True ,
    } 
    def  check_permissions (self ):
        ... 

class  User (BaseDocument ):
   ...

4. 【 文檔實例 】Documents instances

實例化一個對象,並提供參數給對象即可創建一個對象:

>>> page = Page(title="Test Page")
>>> page.title
'Test Page'

4.1 持久化和刪除文檔

使用save()方法即可持久化數據:

>>> page = Page(title="Test Page")
>>> page.save()  # 保存
>>> page.title = "My Page"
>>> page.save()  # 修改title值后 再次保存

- 4.1.1. 預先保存數據驗證和清理 Pre save data validation and cleaning

class Essay(Document):
    status = StringField(choices=('Published', 'Draft'), required=True)
    pub_date = DateTimeField()

    def clean(self):
        """確保只發布的論文有'pub_date`並
        自動設置`如果pub_date`論文發表和'pub_date` 
        未設置"""
        if self.status == 'Draft' and self.pub_date is not None:
            msg = 'Draft entries should not have a publication date.'
            raise ValidationError(msg)
        #  設置已發布項目的pub_date(如果未設置).
        if self.status == 'Published' and self.pub_date is None:
            self.pub_date = datetime.now()

- 4.1.2 級聯保存 Cascading Saves

原文:If your document contains ReferenceField or GenericReferenceField objects, then by default the save() method will not save any changes to those objects. If you want all references to be saved also, noting each save is a separate query, then passing cascade as True to the save method will cascade any saves.

譯文:希望保存文檔中ReferenceField或GenericReferenceField字段時,需要將cascade設置為True。也可以通過在文檔__meta__中設置“cascade”來設置默認值

- 4.1.3 刪除文件 Deleting documents

delete(signal_kwargs=None, **write_concern)

「參數說明」
signal_kwargs - (可選)要傳遞給信號調用的kwargs字典。
write_concern - 向下傳遞額外的關鍵字參數,這些參數將用作結果getLastError命令的選項。例如,將等到至少兩個服務器已記錄寫入並將強制主服務器上的fsync。save(…, w: 2, fsync: True)
「舉例」

p=Post.objects(title='test_title').first()
p.delete()

4.2 文檔ID Document IDs

每保存一個文檔,數據庫都會自動創建一個ID,並且可通過ID來查找文檔。 也可以用pk來訪問主鍵

>>> page = Page(title="Test Page")
>>> page.id      #   ===》 未保存到數據庫,無法獲得ID號
>>> page.save()   #   ===》 保存到數據庫后,可以獲得ID號
>>> page.id
ObjectId('123456789abcdef000000000')
>>> page.pk
ObjectId('123456789abcdef000000000')

也可以用primary_key=True的方式指定主鍵:

>>> class User(Document):
...     email = StringField(primary_key=True)     ===⇒ 創建模型時,指定主鍵
...     name = StringField()
...
>>> bob = User(email='bob@example.com', name='Bob')
>>> bob.save()
>>> bob.id == bob.email == 'bob@example.com'
True

5、【查詢數據庫】query database

Document類具有一個objects屬性,用於訪問與類關聯的數據庫中的對象。該objects屬性實際上是一個 QuerySetManager,QuerySet在訪問時創建並返回一個新 對象。QuerySet可以迭代該 對象以從數據庫中獲取文檔

# 打印出所有User集合中所有文檔的用戶名
for user in User.objects:
    print(user.name)

5.1 過濾查詢 Filtering queries

可以通過QuerySet使用字段查找關鍵字參數調用對象來過濾 查詢。關鍵字參數中的鍵對應於Document您要查詢的字段 :

# 查詢出國家是英國的所有用戶
uk_users = User.objects(country='uk')

嵌入文檔中的字段可以通過使用雙下划線代替對象屬性查詢:

# auther是一個嵌入文檔字段,country是嵌入文檔的field,通過auther__country的方式訪問值
uk_pages = Page.objects(author__country='uk')

5.2 查詢運算符 Query operators

除了相等運算符外,其它運算符也可以在查詢中使用 :

# 查詢18歲以下的用戶
young_users = Users.objects(age__lte=18)

可用的運算符如下:

ne - 不等於
lt - 少於
lte - 小於或等於
gt - 大於
gte - 大於或等於
not - negate a standard check, may be used before other operators (e.g. Q(age__not__mod=(5, 0)))
in - 值在列表中(應提供值列表)
nin - 值不在列表中(應提供值列表)
mod - value % x == y, where x and y are two provided values
all - 提供的值列表中的每個項目都在數組中
size - 數組的大小是多少
exists - 字段值存在

5.2.1. 字符串查詢 String queries

以下運算符可用作使用正則表達式查詢:
exact - 字符串字段與值完全匹配
iexact - 字符串字段與值完全匹配(不區分大小寫)
contains - 字符串字段包含值
icontains - 字符串字段包含值(不區分大小寫)
startswith - 字符串字段以值開頭
istartswith - 字符串字段以值開頭(不區分大小寫)
endswith - 字符串字段以值結尾
iendswith - 字符串字段以值結尾(不區分大小寫)
match - 執行$ elemMatch,以便您可以匹配數組中的整個文檔

- 5.2.2. 地理查詢 Geo queries ====》目前我還沒使用地理信息,以下提出原文信息,您可以自己翻譯學習

There are a few special operators for performing geographical queries. The following were added in MongoEngine 0.8 for PointField, LineStringField and PolygonField:

  • geo_within – check if a geometry is within a polygon. For ease of use it accepts either a geojson geometry or just the polygon coordinates eg:

「舉例」

loc.objects(point__geo_within=[[[40, 5], [40, 6], [41, 6], [40, 5]]])
loc.objects(point__geo_within={"type": "Polygon",
                         "coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]]})
  • geo_within_box – simplified geo_within searching with a box eg:
loc.objects(point__geo_within_box=[(-125.0, 35.0), (-100.0, 40.0)])
loc.objects(point__geo_within_box=[<bottom left coordinates>, <upper right coordinates>])
  • geo_within_polygon – simplified geo_within searching within a simple polygon eg:
loc.objects(point__geo_within_polygon=[[40, 5], [40, 6], [41, 6], [40, 5]])
loc.objects(point__geo_within_polygon=[ [ <x1> , <y1> ] ,
                                        [ <x2> , <y2> ] ,
                                        [ <x3> , <y3> ] ])
  • geo_within_center – simplified geo_within the flat circle radius of a point eg:
loc.objects(point__geo_within_center=[(-125.0, 35.0), 1])
loc.objects(point__geo_within_center=[ [ <x>, <y> ] , <radius> ])
  • geo_within_sphere – simplified geo_within the spherical circle radius of a point eg:
loc.objects(point__geo_within_sphere=[(-125.0, 35.0), 1])
loc.objects(point__geo_within_sphere=[ [ <x>, <y> ] , <radius> ])
  • geo_intersects – selects all locations that intersect with a geometry eg:
# Inferred from provided points lists:
loc.objects(poly__geo_intersects=[40, 6])
loc.objects(poly__geo_intersects=[[40, 5], [40, 6]])
loc.objects(poly__geo_intersects=[[[40, 5], [40, 6], [41, 6], [41, 5], [40, 5]]])

# With geoJson style objects
loc.objects(poly__geo_intersects={"type": "Point", "coordinates": [40, 6]})
loc.objects(poly__geo_intersects={"type": "LineString",
                                  "coordinates": [[40, 5], [40, 6]]})
loc.objects(poly__geo_intersects={"type": "Polygon",
                                  "coordinates": [[[40, 5], [40, 6], [41, 6], [41, 5], [40, 5]]]})
  • near – find all the locations near a given point:
loc.objects(point__near=[40, 5])
loc.objects(point__near={"type": "Point", "coordinates": [40, 5]})
  • You can also set the maximum and/or the minimum distance in meters as well:
loc.objects(point__near=[40, 5], point__max_distance=1000)
loc.objects(point__near=[40, 5], point__min_distance=100)

- 5.2.3. 列表查詢 Querying lists

class Page(Document):
    tags = ListField(StringField())

# 從所有tags列表中匹配有coding的文檔
Page.objects(tags='coding')

也可以定位列表中某個項

Page.objects(tags__0='db')

如果您只想獲取列表的一部分,例如:您想要對列表進行分頁,則需要切片運算符:

# comments - skip 5, limit 10
Page.objects.fields(slice__comments=[5, 10])

如果不知道列表中的具體位置,可以使用S操作符:

Post.objects(comments__by="joe").update(inc__comments__S__votes=1)

- 5.2.4 原始查詢 raw query

如果希望使用pymongo操作數據庫,可以使用__raw__關鍵字:
「例子」

Page.objects(__raw__={'tags': 'coding'})

⚠️注意:此時需要您學習pymongo https://api.mongodb.com/python/current/

5.3. 限制與跳過查詢 Limiting and skipping results

# 查詢前五條文檔
users = User.objects[:5]

# 查詢第六條以后的所有文檔
users = User.objects[5:]

# 查詢第十一到第十五條文檔
users = User.objects[10:15]
>>> # 確認數據庫中不存在文檔
>>> User.drop_collection()
>>> User.objects[0]
IndexError: list index out of range   ===》 報IndexError的錯
>>> User.objects.first() == None
True
>>> User(name='Test User').save()
>>> User.objects[0] == User.objects.first()
True

- 5.3.1. 檢索唯一結果 Retrieving unique results ===》沒太明白意思

原文:
To retrieve a result that should be unique in the collection, use get(). This will raise DoesNotExist if no document matches the query, and MultipleObjectsReturned if more than one document matched the query. These exceptions are merged into your document definitions eg: MyDoc.DoesNotExist

A variation of this method, get_or_create() existed, but it was unsafe. It could not be made safe, because there are no transactions in mongoDB. Other approaches should be investigated, to ensure you don’t accidentally duplicate data when using something similar to this method. Therefore it was deprecated in 0.8 and removed in 0.10.

5.4. 默認文檔查詢 Default Document queries

默認情況下,查詢文檔是不會過濾文檔的,但如果希望直接查詢出過濾文檔,可以在定義類模型時寫一個方法並給它加上裝飾器:

原文:By default, the objects objects attribute on a document returns a QuerySet that doesn’t filter the collection – it returns all objects. This may be changed by defining a method on a document that modifies a queryset. The method should accept two arguments – doc_cls and queryset. The first argument is the Document class that the method is defined on (in this sense, the method is more like a classmethod() than a regular method), and the second argument is the initial queryset. The method needs to be decorated with queryset_manager() in order for it to be recognised.

譯文:默認情況下,objects文檔上的objects 屬性返回QuerySet不過濾集合的對象 - 它返回所有對象。可以通過在修改查詢集的文檔上定義方法來更改此方法。該方法應該接受兩個參數 - doc_cls和queryset。第一個參數是Document定義方法的 類(在這個意義上,該方法更像是classmethod()一個常規方法),第二個參數是初始查詢集。該方法需要進行修飾queryset_manager()才能被識別。

「例子」

class BlogPost(Document):
    title = StringField()
    date = DateTimeField()

    @queryset_manager
    def objects(doc_cls, queryset):
        # This may actually also be done by defining a default ordering for
        # the document, but this illustrates the use of manager methods
        return queryset.order_by('-date')

可以根據自己需要定義更多方法:
「例子」

class BlogPost(Document):
    title = StringField()
    published = BooleanField()

    @queryset_manager
    def live_posts(doc_cls, queryset):
        return queryset.filter(published=True)

BlogPost(title='test1', published=False).save()
BlogPost(title='test2', published=True).save()
assert len(BlogPost.objects) == 2
assert len(BlogPost.live_posts()) == 1

5.5. 自定義QuerySets Custom QuerySets

如果要添加自定義方法交互或過濾文檔,可以繼續擴展類QuerySet。要使用自定義的QuerySet文檔類,需要在Document模型中的meta詞典設置queryset_class:
「例子」

class AwesomerQuerySet(QuerySet):

    def get_awesome(self):
        return self.filter(awesome=True)

class Page(Document):
    meta = {'queryset_class': AwesomerQuerySet}      ======》設置 queryset_class

# 調用:
Page.objects.get_awesome()

5.6 聚合 Aggregation

MongoDB提供了一些開箱即用的聚合方法,但是沒有像RDBMS那樣多的方法。MongoEngine提供了一個圍繞內置方法的包裝器,並提供了一些自己的方法,它們被實現為在數據庫服務器上執行的Javascript代碼。

5.6.1 統計 counting results

就像限制和跳過結果一樣,QuerySet對象上有一個方法 - count():
⚠️注意:雖然.count()跟len(列表對象)計算結果一樣,但是count()函數性能優於len()

num_users = User.objects.count()

- 5.6.2 進一步聚合 Further aggregation

  • sum() 求和
yearly_expense = Employee.objects.sum('salary')
  • average() 求平均值
mean_age = User.objects.average('age')
  • item_frequencies() 統計頻率
    item_frequencies(field,normalize = False,map_reduce = True )
    「參數」
    field - the field to use 要使用的字段
    normalize - normalize the results so they add to 1.0 規范化結果,使它們加到1.0
    map_reduce - Use map_reduce over exec_js 在exec_js上使用map_reduce
    「舉例」
class Article(Document):
    tag = ListField(StringField())

# After adding some tagged articles...
tag_freqs = Article.objects.item_frequencies('tag', normalize=True)

from operator import itemgetter
top_tags = sorted(tag_freqs.items(), key=itemgetter(1), reverse=True)[:10]

5.7 查詢效率和性能 Query efficiency and performance

- 5.7.1 搜索文檔子集(例如引用文檔或者嵌入式文檔) Retrieving a subset of fields

原文:Sometimes a subset of fields on a Document is required, and for efficiency only these should be retrieved from the database. This issue is especially important for MongoDB, as fields may often be extremely large (e.g. a ListField of EmbeddedDocuments, which represent the comments on a blog post. To select only a subset of fields, use only(), specifying the fields you want to retrieve as its arguments. Note that if fields that are not downloaded are accessed, their default value (or None if no default value is provided) will be given:

>>> class Film(Document):
...     title = StringField()
...     year = IntField()
...     rating = IntField(default=3)
...
>>> Film(title='The Shawshank Redemption', year=1994, rating=5).save()
>>> f = Film.objects.only('title').first()
>>> f.title
'The Shawshank Redemption'
>>> f.year   # None
>>> f.rating # default value
3

note:
The exclude() is the opposite of only() if you want to exclude a field

if you later need the missing fields,hust call reload() on your document.

  • reload(字段,* kwargs )
    reload是重新加載被持久化的話的數據(即重新將數據從數據庫讀到內存)

「參數」
fields - (可選)args要重新加載的字段列表
max_depth - (可選)要取消引用的深度

「舉例」

  • only()
post = BlogPost.objects(...).only('title', 'author.name')
# 查詢出title和auther.name的值
  • exclude()
    「舉例」
post = BlogPost.objects.exclude('title').exclude('author.name')
# 查詢出除了title和auther.name字段的內容

「原文」When iterating the results of ListField or DictField we automatically dereference any DBRef objects as efficiently as possible, reducing the number the queries to mongo.

There are times when that efficiency is not enough, documents that have ReferenceField objects or GenericReferenceField objects at the top level are expensive as the number of queries to MongoDB can quickly rise.

To limit the number of queries use select_related() which converts the QuerySet to a list and dereferences as efficiently as possible. By default select_related() only dereferences any references to the depth of 1 level. If you have more complicated documents and want to dereference more of the object at once then increasing the max_depth will dereference more levels of the document.

- 5.7.3 Turning off dereferencing

有時出於性能原因,不希望自動取消引用數據。要關閉查詢結果的解除引用,請使用查詢集 no_dereference(),如下所示:

post = Post.objects.no_dereference().first()
assert(isinstance(post.author, DBRef))

可以使用no_dereference上下文管理器關閉固定時間段內的所有解除引用 :

with no_dereference(Post) as Post:
    post = Post.objects.first()
    assert(isinstance(post.author, DBRef))

# Outside the context manager dereferencing occurs.
assert(isinstance(post.author, User))

5.8 高級查詢

如果希望通過 or 或者 and 來多條件查詢時,需要使用 Q(條件語句1) | Q(條件語句2) Q(條件語句1) | Q(條件語句2)
「舉例」

from mongoengine.queryset.visitor import Q

# 獲取已發布的文檔
Post.objects(Q(published=True) | Q(publish_date__lte=datetime.now()))

# 獲取 featured為真 同時 hits大於等於1000或大於等於5000  的文檔
Post.objects((Q(featured=True) & Q(hits__gte=1000)) | Q(hits__gte=5000))

5.9 原子更新 Atomic updates

更新方法有:update(), update_one(), modify()
更新修飾符有:
set - 重新設置一個值
unset - 刪除
inc - 加
dec - 減
push - 將新值添加到列表中
push_all - 將多個值添加到列表中
pop - 根據值刪除列表的第一個或最后一個元素
pull - 從列表中刪除值
pull_all - 從列表中刪除多個值
add_to_set - 僅當列表中的值不在列表中時才為其添加值

>>> post = BlogPost(title='Test', page_views=0, tags=['database'])
>>> post.save()
>>> BlogPost.objects(id=post.id).update_one(inc__page_views=1)
>>> post.reload()  # 值已被修改,重新加載數據
>>> post.page_views
1
>>> BlogPost.objects(id=post.id).update_one(set__title='Example Post')
>>> post.reload()
>>> post.title
'Example Post'
>>> BlogPost.objects(id=post.id).update_one(push__tags='nosql')
>>> post.reload()
>>> post.tags
['database', 'nosql']

⚠️注意:如果未寫 set

>>> BlogPost.objects(id=post.id).update(title='Example Post')
>>> BlogPost.objects(id=post.id).update(set__title='Example Post')
>>> post = BlogPost(title='Test', page_views=0, tags=['database', 'mongo'])
>>> post.save()
>>> BlogPost.objects(id=post.id, tags='mongo').update(set__tags__S='mongodb')
>>> post.reload()
>>> post.tags
['database', 'mongodb']

5.10 執行JS代碼

「原文」
Javascript functions may be written and sent to the server for execution. The result of this is the return value of the Javascript function. This functionality is accessed through the exec_js() method on QuerySet() objects. Pass in a string containing a Javascript function as the first argument.

The remaining positional arguments are names of fields that will be passed into you Javascript function as its arguments. This allows functions to be written that may be executed on any field in a collection (e.g. the sum() method, which accepts the name of the field to sum over as its argument). Note that field names passed in in this manner are automatically translated to the names used on the database (set using the name keyword argument to a field constructor).

Keyword arguments to exec_js() are combined into an object called options, which is available in the Javascript function. This may be used for defining specific parameters for your function.

Some variables are made available in the scope of the Javascript function:

  • collection – the name of the collection that corresponds to the Document class that is being used; this should be used to get the Collection object from db in Javascript code
  • query – the query that has been generated by the QuerySet object; this may be passed into the find() method on a Collection object in the Javascript function
  • options – an object containing the keyword arguments passed into exec_js()

The following example demonstrates the intended usage of exec_js() by defining a function that sums over a field on a document (this functionality is already available through sum() but is shown here for sake of example):
「舉例」

def sum_field(document, field_name, include_negatives=True):
    code = """
    function(sumField) {
        var total = 0.0;
        db[collection].find(query).forEach(function(doc) {
            var val = doc[sumField];
            if (val >= 0.0 || options.includeNegatives) {
                total += val;
            }
        });
        return total;
    }
    """
    options = {'includeNegatives': include_negatives}
    return document.objects.exec_js(code, field_name, **options)

「原文」
As fields in MongoEngine may use different names in the database (set using the db_field keyword argument to a Field constructor), a mechanism exists for replacing MongoEngine field names with the database field names in Javascript code. When accessing a field on a collection object, use square-bracket notation, and prefix the MongoEngine field name with a tilde. The field name that follows the tilde will be translated to the name used in the database. Note that when referring to fields on embedded documents, the name of the EmbeddedDocumentField, followed by a dot, should be used before the name of the field on the embedded document. The following example shows how the substitutions are made:

class Comment(EmbeddedDocument):
    content = StringField(db_field='body')

class BlogPost(Document):
    title = StringField(db_field='doctitle')
    comments = ListField(EmbeddedDocumentField(Comment), name='cs')

# Returns a list of dictionaries. Each dictionary contains a value named
# "document", which corresponds to the "title" field on a BlogPost, and
# "comment", which corresponds to an individual comment. The substitutions
# made are shown in the comments.
BlogPost.objects.exec_js("""
function() {
    var comments = [];
    db[collection].find(query).forEach(function(doc) {
        // doc[~comments] -> doc["cs"]
        var docComments = doc[~comments];

        for (var i = 0; i < docComments.length; i++) {
            // doc[~comments][i] -> doc["cs"][i]
            var comment = doc[~comments][i];

            comments.push({
                // doc[~title] -> doc["doctitle"]
                'document': doc[~title],

                // comment[~comments.content] -> comment["body"]
                'comment': comment[~comments.content]
            });
        }
    });
    return comments;
}
""")

6、 GridFS

地址:http://docs.mongoengine.org/guide/gridfs.html

7、 信號 Signals

該類是在數據庫發生變化時或者符合訂閱標准時出發發送數據的行為
原文地址:http://docs.mongoengine.org/guide/signals.html

第三方庫:https://pypi.org/project/blinker/ 第三方庫文檔:https://pythonhosted.org/blinker/

8、【搜索文本 Text Search】

8.1 使用文本索引定義文檔 Defining a Document with text index

使用$前綴設置文本索引,查看聲明:

class News(Document):
    title = StringField()
    content = StringField()
    is_active = BooleanField()

    meta = {'indexes': [
        {'fields': ['$title', "$content"],
         'default_language': 'english',
         'weights': {'title': 10, 'content': 2}
        }
    ]}

8.2 查詢 Querying

先新建並保存以下文檔

News(title="Using mongodb text search",
     content="Testing text search").save()

News(title="MongoEngine 0.9 released",
     content="Various improvements").save()

Next, start a text search using QuerySet.search_text method:
接下來,使用QuerySet.search_text方法開始文本搜索:

>>>  document = News.objects.search_text('testing').first()
>>>  document.title 
>>>  返回結果:Using mongodb text search

>>>   document = News.objects.search_text('released').first()
>>>   document.title 
>>>   返回結果   MongoEngine 0.9 released

8.3 按分數排序 Ordering by text score

objects = News.objects.search_text('mongo').order_by('$text_score')
#  搜索到的文檔按 text_score 排序

9、 使用mongomock進行測試

地址: http://docs.mongoengine.org/guide/mongomock.html

二、API參考

這里有比較詳細的API說明和參數說明,還有一些例子
地址: http://docs.mongoengine.org/apireference.html


免責聲明!

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



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