mongoengine簡單使用


創建model

class User(Document):
    email = StringField(required=True)
    first_name = StringField(max_length=50)
    last_name = StringField(max_length=50)

文檔都是繼承Document類。
預留:
字段類型:

  1. StringField,字符串。
  2. ListField,列表。列表里還可以傳入字段規定列表內的字段類型,例如ListField(StringField(max_length=30))
  3. ReferenceField, 這是一個保存相關文檔的filed
  4. StringFiled(regex=None,max_length=None,min_lenght=None) #字符串類型
  5. IntField(min_value=None,max_value=None) #整數類型
  6. FloatField(min_value=None,max_value=None) #字符串類型
  7. BooleanField() #布爾類型
  8. DateTimeField() #時間類型
  9. listField() #可以插入列表的
  10. DictField() #字典類型
  11. ReferenceField() #參照類型
  12. SequenceField() #自動產生一個數列、 遞增的

字段限制:

  1. required,必須的。
  2. max_length,最大長度。
  3. default #默認值 也可以是一個函數 可調用類型
  4. primary_key #插入數據是否重復
  5. null #賦值是否可以為空
  6. choices #列表的范圍
  7. unique #當前field只能是唯一的

繼承model

class Post(Document):
    title = StringField(max_length=120, required=True)
    author = ReferenceField(User)

    meta = {'allow_inheritance': True}

class TextPost(Post):
    content = StringField()

class ImagePost(Post):
    image_path = StringField()

class LinkPost(Post):
    link_url = StringField()

只需要再父類的meta的 'allow_inheritance' 設置為True

內嵌文檔

一篇文章post內的所有評論comments直接嵌入這篇文章的內部是mongodb常見的思路,首先創建一個comments的model:

class Comment(EmbeddedDocument):
    content = StringField()
    name = StringField(max_length=120)

這個model繼承了EmbeddedDocument類,聲明了他就是一個內嵌的文檔,它不會在數據庫里有個專門的文檔保存,例如post這樣的文檔。post的model應該這樣寫:

class Post(Document):
    title = StringField(max_length=120, required=True)
    author = ReferenceField(User)
    tags = ListField(StringField(max_length=30))
    comments = ListField(EmbeddedDocumentField(Comment))

關聯刪除

ReferenceField這個字段似乎是一個外鍵,創建model是指定reverse_delete_rule的值,可以設定關聯刪除的規則,如:

class Post(Document):
    title = StringField(max_length=120, required=True)
    author = ReferenceField(User, reverse_delete_rule=CASCADE)
    tags = ListField(StringField(max_length=30))
    comments = ListField(EmbeddedDocumentField(Comment))

此例中reverse_delete_rule的值設定為CASCADE,這樣如果刪除了一個用戶,用戶下所有的關聯的post都會被刪除。更多字段查詢ReferenceField API

添加數據

現在我們可以添加數據了,寫法有兩種。如:

ross = User(email='ross@example.com', first_name='Ross', last_name='Lawley').save()

再如:

ross = User(email='ross@example.com')
ross.first_name = 'Ross'
ross.last_name = 'Lawley'
ross.save()

以上兩種都是可以的。注意它可以用屬性賦值的語法。如果save()了之后,又修改了屬性,再save()就可以了,非常方便。

使用數據

添加幾個數據之后,我們可以來使用這些數據了。所有的model,例如上面的Post,User,都有一個objects屬性,可以檢索出此類下所有的文檔。例如我們用繼承自Post類的TextPost做個最簡單的示范。

for post in TextPost.objects:
    print post.content

這樣會print出所有屬於TextPost的文檔的content。如果用Post.objects,可以得到所有屬於Post或者繼承自Post的model實例化的文檔,這是一個QuerySet對象,(類似一個游標對象,不知道對不對)。如:

for post in Post.objects:
    print post.title
    print '=' * len(post.title)

    if isinstance(post, TextPost):
        print post.content

    if isinstance(post, LinkPost):
        print 'Link:', post.link_url

可見objects的類是不一樣的,只是父類都是Post。
檢索指定字段的值的文檔也很方便,例:

for post in Post.objects(tags='mongodb'):
    print post.title

這樣就能檢索出tags里有'mongodb'的文檔。我覺得很方便,普通的查詢條件如name='foo'這樣的寫法很正常,但tags是一個列表,用這樣的寫法,直接給個值他就能自己遍歷判斷是否是列表里的一個值,隱去了列表這個細節,很贊。
objects對象和sqlAlchemy的游標一樣,也可以調用first()或者count(),意思都是一樣。如:

num_posts = Post.objects(tags='mongodb').count()
print 'Found %d posts with tag "mongodb"' % num_posts

連接數據庫

mongoengine連接數據可也很方便,最簡單的傳入數據庫名如:

from mongoengine import connect connect('project1') 

connect還接收, name(數據庫名),username,password,host等參數。

配置多個數據庫

新建model的時候可以指定model可以屬於哪個數據庫,可以都添加進來例:

class User(Document):
    name = StringField()

    meta = {"db_alias": "user-db"}  # 根據元屬性確認哪一個數據庫

class Book(Document):
    name = StringField()

    meta = {"db_alias": "book-db"}# 根據元屬性確認哪一個數據庫
class AuthorBooks(Document): 
  author
= ReferenceField(User)
  book
= ReferenceField(Book)
  
meta
= {"db_alias": "users-books-db"} # 根據元屬性確認哪一個數據庫

不過這些數據庫先要用connet連接。

臨時轉換數據庫database(未知,沒用過)

model表雖然設計了存儲的數據庫,但是有些數據需要存儲到另一個數據庫怎么辦。可以轉換:

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()  # Saves the 'archive-user-db'

這樣,應該默認保存在“user-db”這個數據庫里的User,現在是作為“archive-user-db”的User保存了。這是跨db的保存。

臨時轉換集合collection

同樣mongoegnine也支持collection的轉換, 如:

from mongoengine.context_managers import switch_collection

class Group(Document):
    name = StringField()

Group(name="test").save()  # Saves in the default db

with switch_collection(Group, 'group2000') as Group:
    Group(name="hello Group 2000 collection!").save()  # Saves in group2000 collection

保存到里一個集合,第一個參數為需要轉換的model,第二個為目標集合名,最后的model名可以不變。

Reference fields

這是一個設置相關document的fields,如:

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

這樣就可以引用了。

一對多關系

可以用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()

# Find all pages Bob authored
Page.objects(authors__in=[bob])

# Find all pages that both Bob and John have authored
Page.objects(authors__all=[bob, john])

# Remove Bob from the authors for a page.
Page.objects(id='...').update_one(pull__authors=bob)

# Add John to the authors for a page.
Page.objects(id='...').update_one(push__authors=john)

一次插入和刪除一條關聯可見最后兩行,多個的方法還不知道。

如果關聯的文檔發生了變化,如何同步改變呢?例如ReferenceField在子級的多對一,一對一關系,如何能刪除了關聯的父級,子級也一並刪除呢?

class Parent(Document):
    name = StringField()
    
class Child(Document):
    name = StringField()
    parent = ReferenceField(Parent, reverse_delete_rule=CASCADE)

定義一個反向刪除的規則,CASCADE,就能一並刪除了。

其實用這樣的多對一的方式做到一對多並不方便,這個應用場景更多是一對一的關聯。那如何方便的一對多呢。ListField:

class Child(Document):
    name = StringField()

class Parent(Document):
    name = StringField()
    child = ListField(ReferenceField(Child, reverse_delete_rule=PULL))

首先子級要先定義,應為父級的ReferenceField要傳入子級的類。父級定義一個列表保存所有子級ReferenceField。反向刪除規則為PULL,這樣刪除一個child,他的關聯就會自動從父級的ListField中刪掉了。

另外還有DO_NOTHING,DENY,NULLIFY。

另外GenericReferenceField不需要傳入關聯類,可以任意指定。

文檔名

這個庫默認保存的文檔是定義的類的縮寫加下划線,可以用meta = {'collection': 'YourDocument'}來設置你想要的文檔名稱

限制文檔數量和大小

如果需要限制一個文檔的數量或者大小,可以用meta = {'max_documents': 1000, 'max_size': 2000000}

排序

再meta中放入ordering,可以指定文檔的默認排序方式,例如meta = {'ordering': ['-published_date']},不過order_by()方法的優先級更高。

clean方法

文檔調用save()時,如果定義了clean,會先調用clean,如:

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 the pub_date if published and 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()

檢索

操作符

ne – not equal to,不等於
lt – less than,小於
lte – less than or equal to,小於或等於
gt – greater than,大於
gte – greater than or equal to,大於或等於
not – negate a standard check, may be used before other operators (e.g. Q(age__not__mod=5)),否,與其他操作符一起用
in – value is in list (a list of values should be provided),提供一個列表,數據庫值在列表內
nin – value is not in list (a list of values should be provided),提供一個列表,數據庫值不在列表內
mod – value % x == y, where x and y are two provided values,整除
all – every item in list of values provided is in array,提供的列表元素都在數據庫數組內
size – the size of the array is,數據庫數組的大小
exists – value for field exists,field存在

字符串檢索

exact – string field exactly matches value,完全匹配
iexact – string field exactly matches value (case insensitive),完全匹配,不區分大小寫
contains – string field contains value,包含
icontains – string field contains value (case insensitive),包含,不區分大小寫
startswith – string field starts with value,開始於
istartswith – string field starts with value (case insensitive),開始於,不區分大小寫
endswith – string field ends with value,結束於
iendswith – string field ends with value (case insensitive),結束於,不區分大小寫
match – performs an $elemMatch so you can match an entire document within an array,匹配?

列表檢索

如果一個域是個列表,同樣可以傳入一個元素,這樣做的意思是檢索所有這個列表域中包含這個元素的文檔,例:

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

# This will match all pages that have the word 'coding' as an item in the
# 'tags' list
Page.objects(tags='coding')

當然也可以指定這個列表域的序號來匹配,如:

Page.objects(tags__0='db')

這樣即是匹配列表域第一個元素為‘db’的文檔

如果對列表檢索的內的結果需要分割,可如:

# comments - skip 5, limit 10
Page.objects.fields(slice__comments=[5, 10])

檢索結果的分割和限制

可以使用limit()和skip()方法,但是比較好的是:

# Only the first 5 people
users = User.objects[:5]

# All except for the first 5 people
users = User.objects[5:]

# 5 users, starting from the 11th user found
users = User.objects[10:15]

定義默認的搜索條件

可以定義自己的object方法:

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

聚合

計算一個域的總和,使用sum():

yearly_expense = Employee.objects.sum('salary')

計算一個域的平均值,使用average():

mean_age = User.objects.average('age')

使用only()提高檢索效率

使用only()只會得到文檔中的某個域,以及是默認值的域,如:

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

exclude()是和它相反的方法。
另外,如果用了only(),之后有需要其他的值了,可以使用reload()

原子更新

可以使用update_one(), update() 和 modify() 方法對 QuerySet 進行更新,和使用modify() 和 save() 對一個文檔進行更新。下面是一些搭配使用的修改符:

setset a particular value,設置某個值
unset – delete a particular value (since MongoDB v1.3),刪除某個值
inc – increment a value by a given amount,增加
dec – decrement a value by a given amount,減少
push – append a value to a list,增加列表一個值
push_all – append several values to a list,增加列表多個值(傳入一個列表)
pop – remove the first or last element of a list depending on the value,取出一個列表最前或最后的值
pull – remove a value from a list,刪除一個列表的值
pull_all – remove several values from a list,刪除一個列表多個值
add_to_set – add value to a list only if its not in the list already,加入列表一個值,如果它之前不在其中。

這些修改符和檢索操作符用法差不多,只是他們在域名之前:

post = BlogPost(title='Test', page_views=0, tags=['database'])
post.save()
BlogPost.objects(id=post.id).update_one(inc__page_views=1)
post.reload()  # the document has been changed, so we need to reload it
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')
常用查詢方法
https://blog.csdn.net/weixin_42042680/article/details/87909424
個人實際示例
https://www.cnblogs.com/clbao/p/11428794.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM