python使用ODM控制Mongodb(MongoEngine)

python使用ODM控制Mongodb(MongoEngine) 1.安装pip install mongoengine2.连接数据库要连接一个 mongod实例, 需要用到connect()函数。分不同情况需提供不同的连接参数。2.1 默认情况指mongod运行在localhost且端口为27017只需要提供需要连接的数据库名即可:frommongoengineimportconnect connect(project1)2.2 mongod运行在其他服务器或者端口不为默认27107端口frommongoengineimportconnect connect(project1,host192.168.1.35,port12345)2.3 需要用户登陆验证frommongoengineimportconnect connect(project1,usernamewebapp,passwordpwd123,authentication_sourceadmin)2.4 URI连接方式将URI给host参数赋值frommongoengineimportconnect connect(project1,hostmongodb://localhost/database_name)注意如URI包含数据库名用户名或者密码将会覆盖相应参数的传值。如以下例子连接的数据库将会是production用户名为admin密码为qwerty。frommongoengineimportconnect connect(dbtest,usernameuser,password12345,hostmongodb://admin:qwertylocalhost/production)2.5 连接副本集frommongoengineimportconnect# Regular connectconnect(dbname,replicasetrs-name)# MongoDB URI-style connectconnect(hostmongodb://localhost/dbname?replicaSetrs-name)2.6 多数据库连接连接多个数据库可以使用 connect() 并设置别名参数alias如没有设置别名则默认为 “default” 。connect(dbtest01,aliastestdbA)connect(dbtest02,aliastestdbB)connect(dbtest03,aliastestdbC)也可以使用 register_connection() 进行多个数据库连接信息的存储。register_connection(aliasdefault,dbtest)当单独Document类需要调用特定数据库时只需要在meta的 db_alias 设置对应的数据库别名即可。classUser(Document):nameStringField()meta{db_alias:user-db}classBook(Document):nameStringField()meta{db_alias:book-db}classAuthorBooks(Document):authorReferenceField(User)bookReferenceField(Book)meta{db_alias:users-books-db}3. 上下文管理3.1 切换数据库使用switch_db提供需要切换数据库的类与所需切换到的数据库别名即可进行切换数据库。frommongoengine.context_managersimportswitch_dbclassUser(Document):nameStringField()meta{db_alias:user-db}withswitch_db(User,archive-user-db)asUser:User(nameRoss).save()# Saves the archive-user-db3.2 切换集合数据表使用switch_collection提供需要切换集合的类与所需切换到的集合名即可进行切换集合。frommongoengine.context_managersimportswitch_collectionclassGroup(Document):nameStringField()Group(nametest).save()# Saves in the default dbwithswitch_collection(Group,group2000)asGroup:Group(namehello Group 2000 collection!).save()# Saves in group2000 collection注意使用上下文管理之前需使用register_connection()或connect()登记需要使用到的数据库信息。4. 定义文件MongoEngine允许您为文档定义模式因为这有助于减少编码错误并允许在可能存在的字段上定义实用方法下面来一个给一个例子,要为文档定义模式请创建一个继承自其的类 Document。通过将字段对象作为类属性添加到文档类来指定字段frommongoengineimport*importdatetimeclassPage(Document):titleStringField(max_length200,requiredTrue)date_modifiedDateTimeField(defaultdatetime.datetime.utcnow)5.字段默认情况下字段不是必需的。要使字段成为必填字段请将字段的required关键字参数设置 为True。字段也可能有可用的验证约束max_length例如上面的示例中。字段也可以采用默认值如果没有提供值将使用默认值。默认值可以可选地是可调用的将被调用来检索该值例如在上面的示例中。可用的字段类型如下所示BinaryField 二进制数据字段 BooleanField ComplexDateTimeField DateTimeField DecimalField DictField DynamicField EmailField EmbeddedDocumentField EmbeddedDocumentListField FileField GridFS存储字段 FloatField GenericEmbeddedDocumentField GenericReferenceField GenericLazyReferenceField GeoPointField ImageField 图像文件存储区域 IntField ListField MapField ObjectIdField ReferenceField LazyReferenceField SequenceField SortedListField StringField URLField UUIDField PointField LineStringField PolygonField MultiPointField MultiLineStringField MultiPolygonField6.字段参数每个字段类型可以通过关键字参数进行定制。以下关键字参数可以在所有字段上设置db_field 默认无MongoDB字段名称。required 默认False如果设置为True并且该字段未在文档实例上设置ValidationError则在验证文档时将引发a 。default 默认无未为此字段设置值时使用的值。默认参数的定义遵循Python的一般规则这意味着在处理默认的可变对象时应该小心一些如in ListField或DictFieldclassExampleFirst(Document):# 默认是一个空列表valuesListField(IntField(),defaultlist)classExampleSecond(Document):valuesListField(IntField(),defaultlambda:[1,2,3])classExampleDangerous(Document):valuesListField(IntField(),default[1,2,3])unique 默认False如果为True则该集合中的任何文档都不会具有该字段的相同值。unique_with 默认无与此字段一起使用的字段名称或字段名称列表将不会具有相同值的集合中的两个文档。primary_key 默认False如果为True则使用此字段作为集合的主键。 DictField 和EmbeddedDocuments都支持作为文档的主键。如果设置该字段也可通过PK字段访问choices 默认无一个可迭代的例如列表元组或集合选项这个字段的值应该被限制到这个选项。#可以是值的嵌套元组存储在mongo中和人类可读的密钥SIZESSmall MMedium LLarge XLExtra Large XXL特大号classShirtDocument sizeStringField max_length3 choicesSIZE#或者是一个只包含值的平坦迭代器SIZESMLXLXXLclassShirtDocument sizeStringField max_length3 choicesSIZE **kwargs 可选的您可以提供额外的元数据作为任意附加关键字参数 但是您无法覆盖现有的属性。常用选项包括help_text和verbose_name通常由窗体和小部件库使用7.列表字段MongoDB允许存储项目列表。要想添加列表类型到 Document请使用ListField字段类型。ListField将另一个字段对象作为其第一个参数该参数指定列表中可以存储哪些类型元素classPage(Document):tagsListField(StringField(max_length50))8. 嵌入式文档MongoDB能够在其他文档中嵌入文档。可以为这些嵌入式文档定义概要就像它们可能用于常规文档一样。要创建嵌入式文档只需像往常一样定义一个文档但继承EmbeddedDocument而不是 DocumentclassComment(EmbeddedDocument):contentStringField()要将文档嵌入到另一个文档中请使用 EmbeddedDocumentField字段类型将嵌入的文档类作为第一个参数提供classPageDocument commentsListField EmbeddedDocumentField Comment comment1Comment contentGood work comment2Comment contentNice article pagePage comments[comment1 comment2]9.字典字段通常可以使用嵌入式文档而不是字典 - 通常建议使用嵌入式文档因为字典不支持验证或自定义字段类型。但是有时你不会知道你想要存储的结构; 在这种情况下DictField是合适的classSurveyResponse(Document):dateDateTimeField()userReferenceField(User)answersDictField()survey_responseSurveyResponse(datedatetime.utcnow(),userrequest.user)response_formResponseForm(request.POST)survey_response.answersresponse_form.cleaned_data()survey_response.save()字典可以存储复杂的数据其他字典列表对其他对象的引用所以是最灵活的可用字段类型10.引用字段要使引用字段可以存储到数据库中的其他文档可以使用 ReferenceField。传入另一个文档类作为构造函数的第一个参数然后将文档对象分配给该字段classUser(Document):nameStringField()classPage(Document):contentStringField()authorReferenceField(User)johnUser(nameJohn Smith)john.save()postPage(contentTest Page)post.authorjohn post.save()该User对象在幕后自动变为引用并在Page检索对象时解除引用。要添加一个ReferenceField引用正在定义的文档的引用请使用该字符串’self’代替文档类作为ReferenceField构造函数的参数。要引用尚未定义的文档请使用未定义文档的名称作为构造函数的参数classEmployee(Document):nameStringField()bossReferenceField(self)profile_pageReferenceField(ProfilePage)classProfilePage(Document):contentStringField()11.一对多与ListField如果通过引用列表实现一对多关系则引用将存储为DBRefs并且需要将对象的实例传递给查询classUser(Document):nameStringField()classPage(Document):contentStringField()authorsListField(ReferenceField(User))bobUser(nameBob Jones).save()johnUser(nameJohn Smith).save()Page(contentTest Page,authors[bob,john]).save()Page(contentAnother Page,authors[john]).save()# 查找所有由bob编写的网页此处用到了高级查询后面会介绍Page.objects(authors__in[bob])# 查找所有由bob和john编写的网页Page.objects(authors__all[bob,john])# 删除由bob编写的某篇文章根据某个id查找到的.Page.objects(id...).update_one(pull__authorsbob)# 把john添加到某篇文章根据某个id查找到的.Page.objects(id...).update_one(push__authorsjohn)12.处理引用文档的删除默认情况下MongoDB不检查数据的完整性因此删除其他文档仍然存在引用的文档将导致一致性问题。Mongoengine 的ReferenceField增加了一些功能来防范这些类型的数据库完整性问题为每个引用提供删除规则规范。通过reverse_delete_rule在ReferenceField定义上提供属性 来指定删除规则如下所示classProfilePage(Document):...employeeReferenceField(Employee,reverse_delete_rulemongoengine.CASCADE)#这个例子中的声明意味着当一个Employee对象被移除时ProfilePage引用该雇员的那个也被移除。如果删除了整批员工则所有链接的配置文件页面也会被删除它的值可以采用以下任何常量mongoengine.DO_NOTHING这是默认设置不会执行任何操作。删除速度很快但可能会导致数据库不一致或悬空引用。mongoengine.DENY如果仍存在对被删除对象的引用则拒绝删除。mongoengine.NULLIFY删除任何仍然指向被删除对象的对象字段使用MongoDB的“未设置”操作从而有效地消除关系。即删除了被引用的字段对引用它的字段无影响举个例子假如文章的作者字段采用的是引用字段那么作者一旦被删除那么由他写的文章仅仅是没有了作者他的文章都还在。mongoengine.CASCADE引用字段被删除则引用此字段的文档也会被删除举个例子假如文章的作者字段采用的是引用字段那么作者一旦被删除那么由他写的文章也都被删除。mongoengine.PULL从ListFieldReferenceField的任何对象的字段中删除对该对象的引用使用MongoDB的“拉”操作 。13. 通用参考字段第二种场景也存在 GenericReferenceField。这允许您引用任何类型的Document因此不会将 Document子类作为构造函数参数classLink(Document):urlStringField()classPost(Document):titleStringField()classBookmark(Document):bookmark_objectGenericReferenceField()linkLink(urlhttp://hmarr.com/mongoengine/)link.save()postPost(titleUsing MongoEngine)post.save()Bookmark(bookmark_objectlink).save()Bookmark(bookmark_objectpost).save()使用GenericReferenceFields的效率稍低于标准ReferenceFields所以如果只引用一种文档类型则更喜欢标准 ReferenceField14. 唯一性约束MongoEngine允许你通过提供uniqueTrue给Field构造函数来指定一个字段在集合中是唯一的。如果您尝试将与唯一字段具有相同值的文档保存为数据库的文档 NotUniqueError则会引发。您也可以使用下列方法指定多字段唯一性约束unique_with可以是单个字段名称也可以是字段名称的列表或元组classUser(Document):usernameStringField(uniqueTrue)first_nameStringField()last_nameStringField(unique_withfirst_name)跳过文档验证保存您也可以通过设置validateFalse调用save() 方法时跳过整个文档验证过程classRecipient(Document):nameStringField()emailEmailField()recipientRecipient(nameadmin,emailrootlocalhost)recipient.save()# 会产生一个 ValidationError 错误recipient.save(validateFalse)# 不会15.文档集合通过在文档中添加meta类字典属性我们可以更改数据集合的名称例如下方的例子我们把page改为了cmspageclassPage(Document):titleStringField(max_length200,requiredTrue)meta{collection:cmsPage}五). 索引您可以在集合上指定索引以加快查询速度。这是通过创建indexes在 meta字典中调用的索引规范列表完成的其中索引规范可以是单个字段名称包含多个字段名称的元组或包含完整索引定义的字典。顺序可以通过在字段名前加上 用于升序或-号用于降序来指定。请注意方向只对多字段索引很重要。文本索引可以通过在字段名前添加一个$来指定。散列索引可以通过在字段名前添加来指定classPage(Document):categoryIntField()titleStringField()ratingStringField()createdDateTimeField()meta{indexes:[title,$title,# 文本索引#title,# 散列索引(title,-rating),(category,_cls),{fields:[created],expireAfterSeconds:3600}]}如果采用字典那么以下选项可用fields 默认无 要索引的字段。与上述相同的格式指定。 cls 默认值True 如果您有多态模型可以继承并 allow_inheritance打开则可以配置索引是否应该将该_cls字段自动添加到索引的开头。 sparse 默认False 索引是否应该是稀疏的。 unique 默认False 索引是否应该是唯一的。 expireAfterSeconds 可选的 允许您通过设置以秒为单位的时间过期来自动将某个字段中的数据过期。六). 排序可以为您QuerySet使用的ordering属性 指定默认排序。排序将在QuerySet创建时应用 并且可以通过随后的order_by()覆盖fromdatetimeimportdatetimeclassBlogPost(Document):titleStringField()published_dateDateTimeField()meta{ordering:[-published_date]}blog_post_1BlogPost(titleBlog Post #1)blog_post_1.published_datedatetime(2010,1,5,0,0,0)blog_post_2BlogPost(titleBlog Post #2)blog_post_2.published_datedatetime(2010,1,6,0,0,0)blog_post_3BlogPost(titleBlog Post #3)blog_post_3.published_datedatetime(2010,1,7,0,0,0)blog_post_1.save()blog_post_2.save()blog_post_3.save()# 通过默认的排序降序获取第一篇文章# from BlogPost.meta.orderinglatest_postBlogPost.objects.first()assertlatest_post.titleBlog Post #3# 覆盖原来的默认排序规则采用升序first_postBlogPost.objects.order_by(published_date).first()assertfirst_post.titleBlog Post #1七). 文档继承要创建您定义的文档的专用类型您可以对其进行子类化并添加任何可能需要的额外字段或方法。 由于这是新类它不是Document的直接子类因此它不会存储在它自己的集合中; 它将使用与超类使用相同的集合。 这样可以更方便更高效地检索相关文档 - 您只需在元数据中将allow_inheritance设置为True即可。# Stored in a collection named pageclassPage(Document):titleStringField(max_length200,requiredTrue)meta{allow_inheritance:True}#这里要设置为True默认是False# Also stored in the collection named pageclassDatedPage(Page):dateDateTimeField()八). 抽象类如果您想为一组Document类添加一些额外的功能但您不需要或不需要继承的开销则可以使用该 abstract属性meta。这不会打开文档继承但可以让您保持代码干燥classBaseDocument(Document):meta{abstract:True,}defcheck_permissions(self):...classUser(BaseDocument):...现在User类将有权访问继承的check_permissions方法并且不会存储任何额外的_cls信息 这个特性在实际的web开发中作用很大