MongoEngine中文文档

一、简介:

MongoEngine是一个基于pymongo开发的ODM库,对应与SQLAlchemy。同时,在MongoEngine基础上封装了Flask-MongoEngine,用于支持flask框架。

⚠️注:本文内容来自官方文档(地址:http://docs.mongoengine.org/index.html )

二、入门教程

1、【安装MongoEngine】

pip3 install mongoengine

⚠️注:安装python3.x时自动安装pip3工具

2、【链接至MongoDB】

2.1、连接数据库

- 2.1.1、方法一 连接本地数据库

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

⚠️注:默认链接至本地数据库127.0.0.1:27017,每次创建连接可以给这几个连接取一个别名,用于区别其它的连接,默认情况下别名为default

- 2.1.2、方法二 连接远程数据库

from mongoengine import connect
connect('dbname', host='远程服务器IP地址', post=开放的端口号)

- 2.1.3、方法三 连接带有验证的远程数据库

from mongoengine import connect
connect('dbname', username='用户名', password='密码', authentication_source='admin',  host='远程服务器IP地址', post=开放的端口号)

- 2.1.4、方法四 URI方式连接数据库

from mongoengine import connect
connect('dbname', host='mongodb://用户名:密码@服务器IP地址database_name')

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.3.1、在不同数据库中定义的文档

通过在元数据中提供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.3.2、断开现有连接

该功能 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)

**kwargs (可选的)

- 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 复合索引和索引子文档

通过将嵌入字段或字典字段名称添加到索引定义来创建复合索引。
也可以使用’点’表示法来标识要索引的值,例如:rank.title

- 3.5.3 地理空间索引

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='[email protected]', name='Bob')
>>> bob.save()
>>> bob.id == bob.email == '[email protected]'
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 官方文档

- 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

在这里插入图片描述

  • 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字段的内容

- 5.7.2. 获取相关数据 Getting related data (没看太明白)

「原文」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

发布了10 篇原创文章 · 获赞 1 · 访问量 2041

猜你喜欢

转载自blog.csdn.net/xinxianren007/article/details/100183982
今日推荐