MongoEngine是MongoDB的一个ODM(Object-Document Mapper)框架,它提供了类似Django的语法来操作MongoDB数据库。

MongoEngine是一个对象文档映射器(ODM),相当于一个基于SQL的对象关系映射器(ORM)

pymongo来操作MongoDB数据库,但是直接把对于数据库的操作代码都写在脚本中,这会让应用的代码耦合性太强,而且不利于代码的优化管理 

一般应用都是使用MVC框架来设计的,为了更好地维持MVC结构,需要把数据库操作部分作为model抽离出来,这就需要借助MongoEngine,MongoEngine提供的抽象是基于类的,创建的所有模型都是类
我们可以跟关系型数据库的Python客户端MySQLdb,以及ORM SQLAlchemy/Django ORM比较一下,PyMongo相当于MySQLdb,MongoEngine相当于SQLAlchemy,SQLAlchemy是基于MySQLdb之上的,MongoEngine是基于PyMongo的.

官方英文文档为:http://docs.mongoengine.org/tutorial.html

文档类型

document

document是静态文档,这种文档定义好了之后,是不能添加新的字段的


from mongoengine import Document, StringField
class People(Document):
    title = StringField(max_length=20)
    meta = {
        'db_alias': "yusu",  # 数据库的名字
        'collection': 'people'  # 集合的名字,如果你不定义就用class的小写
    }

DynamicDocument

但是dynamicdocument可以实现随意添加字段的功能

from mongoengine import *

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

# Create a new page and add tags
>>> page = Page(title='Using MongoEngine')
>>> page.tags = ['mongodb', 'mongoengine']
>>> page.save()

>>> Page.objects(tags='mongoengine').count()
>>> 1

注意:动态文档中的字段不能以_开头

EmbeddedDocument

EmbeddedDocument是嵌入文档,MongoDB能够在文档中嵌入文档。嵌入文档模式并不是在数据库中真正形成一个集合,而是作为某个集合的字段出现

class Comment(EmbeddedDocument):  # EmbeddedDocument嵌入文档模式并不是在数据库中真正形成一个集合,而是作为某个集合的字段出现
    content = StringField()
    name = StringField(max_length=120)


class Article(Document):
    title = StringField(max_length=120, required=True)
    author = ReferenceField(User,
                            reverse_delete_rule=CASCADE)  # 外键,reverse_delete_rule=CASCADE,传统数据库中的级联删除。MapFields and DictFields currently don’t support automatic handling of deleted references
    tags = ListField(StringField(max_length=30))  # 列表的类型,listfield内第一个参数就是限制列表中元素的类型
    comments = ListField(EmbeddedDocumentField(Comment))  # 存储一个评论列表,

    meta = {'allow_inheritance': True}  # allow_inheritance=True允许继承默认是不允许的,当以后需要添加新的字段时,直接继承这个类就行了

嵌入文档添加数据的方式:

article2 = LinkPost(title='MongoEngine Documentation', author=ross)
article2.link_url = 'http://docs.mongoengine.com/'
article2.tags = ['mongoengine']
comment1 = Comment(name='小明', content="真的好棒")
comment2 = Comment(name='小红', content="太好了")
article2.comments = [comment1, comment2]  # 要这样添加
article2.save()

 

结果:

{
    "_id" : ObjectId("5d620a7c23641b07eb85bcd2"),
    "_cls" : "Article.LinkPost",
    "title" : "MongoEngine Documentation",
    "author" : ObjectId("5d620a7c23641b07eb85bccf"),
    "tags" : [ 
        "mongoengine"
    ],
    "comments" : [ 
        {
            "content" : "真的好棒",
            "name" : "小明"
        }, 
        {
            "content" : "太好了",
            "name" : "小红"
        }
    ],
    "link_url" : "http://docs.mongoengine.com/"
}

 

 

Field

ListField

支持一个列表,它的第一个参数用来指定列表中元素的类型

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

ReferenceField

ReferenceField相当于传统数据库中的外键,用于关联其他集合,

一对一

class Employee(Document):
    name = StringField()
    boss = ReferenceField('self')#如果要引用的类名没有被定义,则使用self
    profile_page = ReferenceField('ProfilePage') # 参数为要引用的类名

class ProfilePage(Document):
    content = StringField()

一对多需要用listfield,其实我感觉一对多,不好理解,不如改成一引多,把ReferenceField放在一的集合中

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

需要级联删除:reverse_delete_rule =Cascade

举个例子:

class ProfilePage(Document):
    ...
    employee = ReferenceField('Employee', reverse_delete_rule=mongoengine.CASCADE)
本例中的声明意味着,当删除雇员对象时,引用该雇员的ProfilePage配置文件页也将被删除。如果删除了整批员工,则链接的所有配置文件页也将被删除。

默认删除是这个选项:mongoengine.DO_NOTHING。不做任何操作。

GenericReferenceField

该字段的作用是设置某个集合用来引用任何的集合。随便引用

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

结果:

/* 1 */
{
    "_id" : ObjectId("5d6213aa23641b08c1a29c64"),
    "bookmark_object" : {
        "_cls" : "Link",#要引用的类
        "_ref" : {
            "$ref" : "link",
            "$id" : ObjectId("5d6213aa23641b08c1a29c62")
        }
    }
}

/* 2 */
{
    "_id" : ObjectId("5d6213ab23641b08c1a29c65"),
    "bookmark_object" : {
        "_cls" : "Post",
        "_ref" : {
            "$ref" : "post",
            "$id" : ObjectId("5d6213aa23641b08c1a29c63")
        }
    }
}

注意:使用genericreferencefields的效率比标准referencefields稍低,因此,如果您只引用一种文档类型,则首选标准referencefield。

Unique

class User(Document):
    username = StringField(unique=True) #唯一
    first_name = StringField()
    last_name = StringField(unique_with='first_name') #联合唯一

 Skipping Document validation on save

我们知道MongoDBengine中的字段默认有检查的功能,也就是valid,如果你要保存数据的时候不需要他检验可以这样设置

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

recipient = Recipient(name='admin', email='root@localhost')
recipient.save()               # will raise a ValidationError while
recipient.save(validate=False) # won't

改变数据库的名字:

默认集合的名字就是当前类名的小写,如果你要更改集合的名字的话这样操作

class Page(Document):
    title = StringField(max_length=200, required=True)
    meta = {'collection': 'cmsPage'}

对集合中的文档的个数和大小限制可以这样做,默认为10M,以字节显示

class Log(Document):
    ip_address = StringField()
    meta = {'max_documents': 1000, 'max_size': 2000000}

LazyReferenceField

这个字段是懒惰关联字段,相当于懒加载,和迭代器的作用差不多,什么时候调用fetch方法我什么时候产生数据,它不想referenceField字段那样直接得出数据

# models.py
from mongoengine import LazyReferenceField, DynamicDocument, StringField, connect

connect(host="mongodb://127.0.0.1:27017/test1011")


class Address(DynamicDocument):
    add_name = StringField(max_length=22)


class People(DynamicDocument):
    name = StringField(max_length=22)
    adds = LazyReferenceField(Address)

 

数据库信息为:

{
    "_id" : ObjectId("5da048b823641b655f9049eb"),
    "name" : "小红",
    "adds" : ObjectId("5da0485e23641b6513319660")
}

我们要查询小红的地址

p1 = People.objects.filter(name="小红").first()
print('>>>', p1.adds)  # 这样是获取不到地址对象的
print("使用了fetch", p1.adds.fetch())  # 我们要通过fetch方法才能获取到小红对应的地址对象
print("获取小红的地址为", p1.adds.fetch().add_name) # 获取地址

结果:

>>> <LazyReference(<class 'MongoDBengine_text.model1011.Address'>, ObjectId('5da0485e23641b6513319660'))>
使用了fetch Address object
获取小红的地址为 上海

注意使用了lazyReference字段后,获取引用的对象时要用fetch()方法来获取,但是可以直接获取它的主键值:

比如这样

p1 = People.objects.filter(name="小红").first()
print('>>>', p1.adds.id)  


p2 = People.objects.filter(name="小红").first()
print('>>>', p2.adds.pk)  

#结果都是
5da0485e23641b6513319660

 

排序

mongo的orm中排序有三种方式,

方式一:写在模型里

from datetime import datetime

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

    meta = {
        'ordering': ['-published_date']
    }

方式二:写在查询语句中用order_by方法,这个只能用在单表查询,不能用在aggregate中

users = Users.objects(**query).order_by("-role")

方式三在aggregate中使用sort来排序

 pipeline_list = [
                {"$sort": {'user_role': -1}},
                {'$lookup': {
                    'from': 'shops',
                    'localField': 'shop',
                    'foreignField': '_id',
                    'as': 'staff_shop'
                }},
                {'$lookup': {
                    'from': 'users',
                    'localField': 'user',
                    'foreignField': '_id',
                    'as': 'staff_user'
                }},
                {'$match': query_data},
                {'$unwind': "$staff_shop"},
                {'$unwind': "$staff_user"},
                {'$project': {'_id': True, 'created_timestamp': True, 'updated_timestamp': True, 'name': True,
                              'user_role': True, 'replenish_goods_permission': True, 'staff_shop.name': True,
                              'staff_shop.rates': True, 'staff_shop._id': True, 'staff_user.phone_number': True,
                              'staff_user.profile': True

                              }}
            ]

 

继承

继承后的类并不会在数据库中创建新的集合,而是作为基类的属性存在

from mongoengine import *
from datetime import datetime

connect('app')


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


DatedPage(title='你好',date=datetime.now()).save()

 

结果:

{
    "_id" : ObjectId("5d621c8e23641b092a2107e2"),
    "_cls" : "Page.DatedPage",
    "title" : "你好",
    "date" : ISODate("2019-08-25T13:28:46.794Z")
}

对于继承的集合我们要注意:查询的时候既可以用基类查又可以用子类查,但是添加的时候只能用子类添加,不能用基类添加

DatedPage(title='你好', date=datetime.datetime.now()).save()  #子类添加
p = Page.objects.filter(title='你好').first()  # 基类查询
p = DatedPage.objects.filter(title='你好').first() #子类查询
print(p.date)

 

自定义验证规则

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

    def clean(self):
        """Ensures that only published essays have a `pub_date` and
        automatically sets `pub_date` if essay is published and `pub_date`
        is not set"""
        if self.status == 'Draft' and self.pub_date is not None:
            msg = 'Draft entries should not have a publication date.'
            raise ValidationError(msg)
        # Set the pub_date for published items if not set.
        if self.status == 'Published' and self.pub_date is None:
            self.pub_date = datetime.now()

 

posted on 2019-08-25 14:03  程序员一学徒  阅读(1325)  评论(0编辑  收藏  举报