1. 程式人生 > >MongoEngine 中文文件(一)

MongoEngine 中文文件(一)

標籤(空格分隔): Mongodb

近來用Flask做了一個小小的Demo(目前還在做),用的是MongoDB,ORM採用的是時Flask-MongoEngine,雖然是叫做Flask-MongoEngine,但其實只是對MongoEngine的一種封裝,讓其成為了Flask的擴充套件,所以在查詢資料的時候直接找MongoEngine的資料就可以了,然而在開發的過程中,突然發現有關的MongoEngine的中文資料實在是太少了,作為當前比較流行NoSQL資料庫,大部分的Python Web開發者其實都是比較鍾愛NoSQL的,所以今天打算翻譯一下MongoEngine的官方文件

這裡我只會翻譯我個人認為比較重要的部分 ,翻譯不好還請原諒

(一). 安裝

$ pip install mongoengine

(二). 連線資料庫

flask-mongoengine

我們先來看一下flask-mongoengine中的原始碼,其實它就是呼叫的mongoengine中的connect方法
#flask-mongoengine/flask_mongoengine/connection.py
def _connect(conn_settings):

    """Given a dict of connection settings, create a connection to
    MongoDB by calling mongoengine.connect and return its result.
        """
db_name = conn_settings.pop('name') return mongoengine.connect(db_name, **conn_settings)

關於flask-mongoengine的連線資料庫的方式,我就不一一列舉了,可以去看它的文件,很簡單

mongoengine

  1. 第一種方式
    from mongoengine import connect
    connect('project1')  #project1為你要連線的資料庫的名稱
  1. 第二種方式
    #如果mongo服務是在其他地方執行,可以使用這種方法
from mongoengine import connect connect ('project1' , host = '192.168.1.35' , port = 12345
  1. 第三種方式
    #如果資料庫需要身份驗證,username並password 應提供的引數
    from mongoengine import connect
    connect ('project1' , username = 'webapp' , password = 'pwd123'
  1. 第四種方式(推薦)
    #URI樣式連線也被支援 - 只需提供URI作為host:
    from mongoengine import connect
    connect ('project1' , host = 'mongodb:// localhost / database_name'

(三). 定義檔案

MongoEngine允許您為文件定義模式,因為這有助於減少編碼錯誤,並允許在可能存在的欄位上定義實用方法
下面來一個給一個例子,要為文件定義模式,請建立一個繼承自其的類 Document。通過將欄位物件作為類屬性新增到文件類來指定欄位

from mongoengine import *
import datetime

class Page(Document):
    title = StringField(max_length=200, required=True)
    date_modified = DateTimeField(default=datetime.datetime.utcnow)

3.1. 欄位

預設情況下,欄位不是必需的。要使欄位成為必填欄位,請將欄位的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
MultiPolygonField

3.2. 欄位引數

每個欄位型別可以通過關鍵字引數進行定製。以下關鍵字引數可以在所有欄位上設定:

  • db_field (預設:無)
    MongoDB欄位名稱。
  • required (預設:False)
    如果設定為True,並且該欄位未在文件例項上設定,ValidationError則在驗證文件時將引發a 。
  • default (預設:無)
    未為此欄位設定值時使用的值。

預設引數的定義遵循Python的一般規則,這意味著在處理預設的可變物件時應該小心一些(如in ListFieldDictField

class ExampleFirst(Document):
    # 預設是一個空列表
    values = ListField(IntField(), default=list)

class ExampleSecond(Document):
    values = ListField(IntField(), default=lambda: [1,2,3])

class ExampleDangerous(Document):
    values = ListField(IntField(), default=[1,2,3])
  • unique (預設:False)
    如果為True,則該集合中的任何文件都不會具有該欄位的相同值。
  • unique_with (預設:無)
    與此欄位一起使用的欄位名稱(或欄位名稱列表)將不會具有相同值的集合中的兩個文件。
  • primary_key (預設:False)
    如果為True,則使用此欄位作為集合的主鍵。 DictFieldEmbeddedDocuments都支援作為文件的主鍵。
    如果設定,該欄位也可通過PK欄位訪問
  • choices (預設:無)
    一個可迭代的(例如列表,元組或集合)選項,這個欄位的值應該被限制到這個選項。
#可以是值的巢狀元組(儲存在mongo中)和人類可讀的金鑰
SIZE  =  (('S''Small' ),
        ('M''Medium' ),
        ('L''Large' ),
        ('XL''Extra Large' ),
        ('XXL''特大號' ))
class ShirtDocument ):
    size  =  StringField (max_length = 3 , choices = SIZE 
#或者是一個只包含值的平坦迭代器
SIZE  =  ('S''M''L''XL''XXL'class  ShirtDocument ):
    size  =  StringField (max_length = 3 , choices = SIZE
  • **kwargs (可選的)
    您可以提供額外的元資料作為任意附加關鍵字引數 但是,您無法覆蓋現有的屬性。常用選項包括help_text和verbose_name,通常由窗體和小部件庫使用

3.2. 列表欄位

MongoDB允許儲存專案列表。要想新增列表型別到 Document,請使用ListField欄位型別。ListField將另一個欄位物件作為其第一個引數,該引數指定列表中可以儲存哪些型別元素

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

3.2. 嵌入式文件

MongoDB能夠在其他文件中嵌入文件。可以為這些嵌入式文件定義概要,就像它們可能用於常規文件一樣。要建立嵌入式文件,只需像往常一樣定義一個文件,但繼承EmbeddedDocument而不是 Document

class Comment(EmbeddedDocument):
    content = StringField()

要將文件嵌入到另一個文件中,請使用 EmbeddedDocumentField欄位型別,將嵌入的文件類作為第一個引數提供:

class  PageDocument ):
    comments  =  ListFieldEmbeddedDocumentFieldComment ))

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

3.3. 字典欄位

通常,可以使用嵌入式文件而不是字典 - 通常建議使用嵌入式文件,因為字典不支援驗證或自定義欄位型別。但是,有時你不會知道你想要儲存的結構; 在這種情況下DictField是合適的:

class SurveyResponse(Document):
    date = DateTimeField()
    user = ReferenceField(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.4. 引用欄位

要使引用欄位可以儲存到資料庫中的其他文件,可以使用 ReferenceField。傳入另一個文件類作為建構函式第一個引數,然後將文件物件分配給該欄位

class User(Document):
    name = StringField()

class Page(Document):
    content = StringField()
    author = ReferenceField(User)

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

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

該User物件在幕後自動變為引用,並在Page檢索物件時解除引用。

要新增一個ReferenceField引用正在定義的文件的引用,請使用該字串’self’代替文件類作為ReferenceField建構函式的引數。要引用尚未定義的文件,請使用未定義文件的名稱作為建構函式的引數

class Employee(Document):
    name = StringField()
    boss = ReferenceField('self')
    profile_page = ReferenceField('ProfilePage')

class ProfilePage(Document):
    content = StringField()

3.4.1 一對多與ListField 

如果通過引用列表實現一對多關係,則引用將儲存為DBRefs,並且需要將物件的例項傳遞給查詢

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

# 查詢所有由bob編寫的網頁,此處用到了高階查詢,後面會介紹
Page.objects(authors__in=[bob])

# 查詢所有由bob和john編寫的網頁
Page.objects(authors__all=[bob, john])

# 刪除由bob編寫的某篇文章(根據某個id查詢到的).
Page.objects(id='...').update_one(pull__authors=bob)

# 把john新增到某篇文章(根據某個id查詢到的).
Page.objects(id='...').update_one(push__authors=john)

3.4.2 處理引用文件的刪除

預設情況下,MongoDB不檢查資料的完整性,因此刪除其他文件仍然存在引用的文件將導致一致性問題MongoengineReferenceField增加了一些功能來防範這些型別的資料庫完整性問題,為每個引用提供刪除規則規範。通過reverse_delete_ruleReferenceField定義上提供屬性 來指定刪除規則,如下所示

class ProfilePage(Document):
    ...
    employee = ReferenceField('Employee', reverse_delete_rule=mongoengine.CASCADE)
    #這個例子中的宣告意味著當一個Employee物件被移除時,ProfilePage引用該僱員的那個也被移除。如果刪除了整批員工,則所有連結的配置檔案頁面也會被刪除

它的值可以採用以下任何常量:

  • mongoengine.DO_NOTHING
    這是預設設定,不會執行任何操作。刪除速度很快,但可能會導致資料庫不一致或懸空引用。
  • mongoengine.DENY
    如果仍存在對被刪除物件的引用,則拒絕刪除。
  • mongoengine.NULLIFY
    刪除任何仍然指向被刪除物件的物件欄位(使用MongoDB的“未設定”操作),從而有效地消除關係。
    即刪除了被引用的欄位,對引用它的欄位無影響,舉個例子,假如,文章的作者欄位採用的是引用欄位,那麼作者一旦被刪除,那麼,由他寫的文章僅僅是沒有了作者,他的文章都還在。
  • mongoengine.CASCADE
    引用欄位被刪除,則引用此欄位的文件也會被刪除,
    舉個例子,假如,文章的作者欄位採用的是引用欄位,那麼作者一旦被刪除,那麼,由他寫的文章也都被刪除。
  • mongoengine.PULL
    從ListField(ReferenceField)的任何物件的欄位中刪除對該物件的引用(使用MongoDB的“拉”操作 )。

3.4.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()
Note

使用GenericReferenceFields的效率稍低於標準ReferenceFields,所以如果只引用一種文件型別,則更喜歡標準 ReferenceField

3.4. 唯一性約束

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.5 跳過文件驗證儲存

您也可以通過設定validate=False呼叫save() 方法時跳過整個文件驗證過程

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

recipient = Recipient(name='admin', email='root@localhost')
recipient.save()               # 會產生一個 ValidationError 錯誤
recipient.save(validate=False) # 不會

(四). 文件集合

通過在文件中新增meta類字典屬性,我們可以更改資料集合的名稱,例如下方的例子,我們把page改為了cmspage

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

(五). 索引

您可以在集合上指定索引以加快查詢速度。這是通過建立indexesmeta字典中呼叫的索引規範列表完成的,其中索引規範可以是單個欄位名稱,包含多個欄位名稱的元組或包含完整索引定義的字典。

順序可以通過在欄位名前加上 +(用於升序)或-號(用於降序)來指定。請注意,方向只對多欄位索引很重要。文字索引可以通過在欄位名前新增一個$來指定。雜湊索引可以通過在欄位名前新增#來指定:

class Page(Document):
    category = IntField()
    title = StringField()
    rating = StringField()
    created = DateTimeField()
    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()覆蓋:

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

# 通過預設的排序(降序),獲取第一篇文章
# from BlogPost.meta.ordering
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"

(七). 文件繼承

要建立您定義的文件的專用型別,您可以對其進行子類化並新增任何可能需要的額外欄位或方法。 由於這是新類,它不是Document的直接子類,因此它不會儲存在它自己的集合中; 它將使用與超類使用相同的集合。 這樣可以更方便,更高效地檢索相關文件 - 您只需在元資料中將allow_inheritance設定為True即可。

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

    meta = {'allow_inheritance': True}  #這裡要設定為True,預設是False

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

(八). 抽象類

如果您想為一組Document類新增一些額外的功能,但您不需要或不需要繼承的開銷,則可以使用該 abstract屬性meta。這不會開啟文件繼承,但可以讓您保持程式碼乾燥:

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

class User(BaseDocument):
   ...

現在,User類將有權訪問繼承的check_permissions方法,並且不會儲存任何額外的_cls資訊
這個特性在實際的web開發中作用很大