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