一文搞懂 MongoEngine 的使用

  • 原创
  • Madman
  • /
  • /
  • 5
  • 17275 次阅读

Synopsis: Flask-MongoEngine 是集成了 MongoEngine 的一个 Flask 插件,并且它可以从 MongoEngine 定义的数据模型中快速生成 WTForms。而 MongoEngine 是一个 Document-Object Mapper (非常类似于 ORM),是 Python 语言的、用来操作 MongoDB 数据库的 DOM 架构

代码已上传到 https://github.com/wangy8961/flask-mongoengine-tutorial ,欢迎 star

1. 安装

如果你在使用 Flask 框架:

Microsoft Windows [版本 10.0.14393]
(c) 2016 Microsoft Corporation。保留所有权利。

C:\Users\wangy>D:

D:\>cd python-code\flask-mongoengine-tutorial

D:\python-code\flask-mongoengine-tutorial>python -m venv venv3

D:\python-code\flask-mongoengine-tutorial>venv3\Scripts\activate
(venv3) D:\python-code\flask-mongoengine-tutorial>pip install flask

然后再直接安装 Flask-MongoEngine 这个插件:

(venv3) D:\python-code\flask-mongoengine-tutorial>pip install flask-mongoengine

它会自动安装如下依赖包:

  • flask-mongoengine
  • mongoengine: DOM 框架
  • pymongo: 连接 MongoDB 的数据库驱动
  • Flask-WTF: 集成了 WTForms 包的 Flask 插件,能保护所有表单免受 跨站请求伪造(Cross-Site Request Forgery,CSRF) 的攻击
  • WTForms: 适用于 Python 的灵活的 表单验证Web表单生成
  • six: Python 2 and 3 compatibility utilities

2. 配置

2.1 连接数据库

from flask import Flask
...

app = Flask(__name__)

# 配置项
app.config['MONGODB_DB'] = 'test'
app.config['MONGODB_HOST'] = '127.0.0.1'
app.config['MONGODB_PORT'] = 27017
app.config['MONGODB_USERNAME'] = None
app.config['MONGODB_PASSWORD'] = None

表明要连接本机上的 MongoDB(默认端口: 27017),数据库名为 test,且数据库没有开启用户认证

2.2 初始化 Flask-MongoEngine 插件

from flask_mongoengine import MongoEngine
...

# 初始化插件
db = MongoEngine(app)

3. 定义数据模型

创建数据模型文件 models.py

from datetime import datetime
import random
from mongoengine.queryset.manager import queryset_manager
from slugify import slugify
from app import db


class User(db.DynamicDocument):
    # required=True 表明此字段必须填写
    email = db.StringField(required=True)
    # max_length 表明字符串字段的最大长度
    first_name = db.StringField(max_length=50)
    last_name = db.StringField(max_length=50)

    def __str__(self):  # 建议: 给每个模型增加 __str__ 方法,它返回一个具有可读性的字符串表示模型,可在调试和测试时使用
        return self.first_name + ' ' + self.last_name


class Category(db.Document):
    name = db.StringField(max_length=64, required=True)
    # unique=True 表明此字段的值必须在整个 collection 中唯一
    slug = db.StringField(max_length=64, unique=True)
    caption = db.StringField()
    # 自引用
    parent = db.ReferenceField('self', reverse_delete_rule=db.DENY)

    meta = {
        'collection': 'classes',  # 更改数据库中默认的 collection 名称
        'indexes': ['parent'],
        'ordering': ['slug']
    }

    def __str__(self):
        return self.name


class Tag(db.Document):
    name = db.StringField(max_length=64, required=True)
    slug = db.StringField(max_length=64, unique=True)

    def __str__(self):
        return self.name


class Comment(db.EmbeddedDocument):
    '''EmbeddedDocument 表明它是一个可被嵌入其它文档的对象,比如嵌套到下面的 Post 中'''
    content = db.StringField()
    name = db.StringField(max_length=120)

    def __str__(self):
        return self.name


class Post(db.Document):
    title = db.StringField(max_length=120, required=True)
    slug = db.StringField(max_length=64, unique=True)
    # ReferenceField 是引用字段,在数据库中真正存储的是 ObjectID
    # reverse_delete_rule=db.CASCADE 定义了,author 这个引用字段当被引用的对象删除时,它如何处理
    # 比如 author 引用了用户 ross,当我们删除 ross 时,由于定义了 db.CASCADE,所以会 [级联删除] ross 用户所发表过的所有 Post 文章
    author = db.ReferenceField(User, reverse_delete_rule=db.CASCADE)
    category = db.ReferenceField(Category, reverse_delete_rule=db.NULLIFY)
    # ListField 表明它是一个列表,可以保存多个其它类型的字段值,比如 StringField、ReferenceField、EmbeddedDocumentField 都可以
    tags = db.ListField(db.ReferenceField(Tag, reverse_delete_rule=db.PULL))
    comments = db.ListField(db.EmbeddedDocumentField(Comment))
    # 创建的时间,建议在数据库中全部存储 UTC 时间
    # default=datetime.utcnow 表明不指定此字段的值时,它会默认保存当前的时间
    created_at = db.DateTimeField(default=datetime.utcnow)
    # 价格。需要数学计算时,请使用 DecimalField,不要用 FloatField (计算结果不对)
    price = db.DecimalField(default='0.00')
    # 是否发布
    published = db.BooleanField(default=True)

    meta = {
        'allow_inheritance': True,  # 允许被继承,比如下面的 TextPost 就继承自 Post
        'indexes': ['title', 'author'],  # 索引字段,后续按这两个字段值查询时可以加快速度
        'ordering': ['-created_at']  # 表示按 created_at 降序排列,没有减号表示升序排列
    }

    @queryset_manager
    def live_posts(doc_cls, queryset):
        '''非默认的objects查询集,此查询集只返回发布状态为True的博客文章'''
        return queryset.filter(published=True)

    def clean(self):
        '''
        MongoEngine allows you to create custom cleaning rules for your documents when calling save().
        By providing a custom clean() method you can do any pre validation / data cleaning.
        '''
        # 如果创建Post对象时没有提供slug,则根据title自动生成;如果提供了slug,用slugify再清理
        if self.slug:
            self.slug = slugify(self.slug)
        else:
            self.slug = slugify(self.title)
        # 判断slug是否唯一
        filters = dict(slug=self.slug)
        if self.id:
            filters['id__ne'] = self.id
        # 不能用 exist = self.__class__.objects(**filters),因为可能创建 TextPost 对象时,其它子类有相同的 slug
        exist = Post.objects(**filters)
        if exist.count():
            self.slug = "{0}-{1}".format(self.slug, random.getrandbits(32))

    def __str__(self):
        return self.title


class TextPost(Post):  # 继承自 Post 模型,所以它也有 title、author 等字段
    content = db.StringField()


class ImagePost(Post):
    image_path = db.StringField()


class LinkPost(Post):
    link_url = db.StringField()

3.1 文档模型

每个 数据模型类 一般会继承自 db.Document,类里面再定义一些类似 db.StringField()db.DateTimeField()db.DecimalField()db.ReferenceField()db.ListField() 等字段

(1)嵌入型

如果 数据模型类 继承自 db.EmbeddedDocument,那么这个模型类就可以被嵌入到其它模型中,同时这个嵌入型文档不会在数据库中有 collection,且它的对象没有 save() 方法

class Comment(db.EmbeddedDocument):
    content = db.StringField()
    ...

class Post(db.Document):
    ...
    comments = db.ListField(db.EmbeddedDocumentField(Comment))

现在让我们插入一些数据,使用方法见后续的第 4 节 [插入数据]

(venv3) D:\python-code\flask-mongoengine-tutorial>flask shell
Python 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 17:54:52) [MSC v.1900 32 bit (Intel)] on win32
App: app [development]
Instance: D:\python-code\flask-mongoengine-tutorial\instance
>>> comment1 = Comment(content='Good work!')  # 如果调用 comment1.save() 会报错
>>> comment2 = Comment(content='Nice article!')
>>>
>>> post1 = Post(title='The post has many comments', slug='test-post')
>>> post1.comments = [comment1, comment2]
>>> post1.save()
<Post: The post has many comments>

此时,数据库中只有 post 集合

1 嵌入型文档

说明: 像文章下面的评论只属于该文章,就非常适合将评论定义为 嵌入型文档,而文章的标签或分类,因为标签或分类可以多篇文章共用,所以适合用下面将要描述的 引用字段(db.ReferenceField)

(2)动态型

模型继承 db.Document 的话,如果在模型定义中没有指定的字段,在程序运行中动态添加额外的字段和值,却无法保存到数据库中,比如下面 User 模型没有 age 字段,就算给它赋值了,在数据库中 alice 对象还是看不到 age 字段和值

(venv3) D:\python-code\flask-mongoengine-tutorial>flask shell
Python 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 17:54:52) [MSC v.1900 32 bit (Intel)] on win32
App: app [development]
Instance: D:\python-code\flask-mongoengine-tutorial\instance
>>> alice = User.objects.get(first_name='Alice')
>>> alice.age
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'User' object has no attribute 'age'
>>> alice.age = 18
>>> alice.save()
<User: Alice Haby>
>>> alice.age
18
>>> exit()

(venv3) D:\python-code\flask-mongoengine-tutorial>flask shell
Python 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 17:54:52) [MSC v.1900 32 bit (Intel)] on win32
App: app [development]
Instance: D:\python-code\flask-mongoengine-tutorial\instance
>>> alice = User.objects.get(first_name='Alice')
>>> alice.age
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'User' object has no attribute 'age'

如果我们使用 db.DynamicDocument 替代 db.Document 的话,模型的对象可以动态增加没有定义过的字段,比如现在 User 模型如下

class User(db.DynamicDocument):
    # required=True 表明此字段必须填写
    email = db.StringField(required=True)
    # max_length 表明字符串字段的最大长度
    first_name = db.StringField(max_length=50)
    last_name = db.StringField(max_length=50)

虽然还是没有定义 age 字段,但重复上面 Flask Shell 的执行过程后,数据库中 alice 对象有 age = 18

(venv3) D:\python-code\flask-mongoengine-tutorial>flask shell
Python 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 17:54:52) [MSC v.1900 32 bit (Intel)] on win32
App: app [development]
Instance: D:\python-code\flask-mongoengine-tutorial\instance
>>> alice = User.objects.get(first_name='Alice')
>>> alice.age
Traceback (most recent call last):
  File "<console>", line 1, in <module>
AttributeError: 'User' object has no attribute 'age'
>>> alice.age = 18
>>> alice.save()
<User: Alice Haby>
>>> alice.age = 18
>>> exit()

(venv3) D:\python-code\flask-mongoengine-tutorial>flask shell
Python 3.6.1 (v3.6.1:69c0db5, Mar 21 2017, 17:54:52) [MSC v.1900 32 bit (Intel)] on win32
App: app [development]
Instance: D:\python-code\flask-mongoengine-tutorial\instance
>>> alice = User.objects.get(first_name='Alice')
>>> alice.age
18

3.2 字段

请参考: http://docs.mongoengine.org/guide/defining-documents.html#fields

需要注意的是,每种字段类型都会有一些共同的参数:

  • db_field: (Default: None) The MongoDB field name.
  • primary_key: (Default: False) 如果设置为 True,则该字段是文档的 主键,还可以通过 pk 来访问此字段
  • required: (Default: False) 字段是否必填,如果指定了 required=True 时,而创建文档对象时没有给该字段赋值的话,会抛出 ValidationError
  • default: (Default: None) A value to use when no value is set for this field.
  • unique: (Default: False) When True, no documents in the collection will have the same value for this field.
  • choices: (Default: None) An iterable (e.g. list, tuple or set) of choices to which the value of this field should be limited.
class Payment(db.Document):
    ...
    pay_type = db.StringField(
        choices=(('alipay', '支付宝'), ('wechat', '微信'), ('qqpay', 'QQ钱包')),
        default=
                                
                            
  • a-xian-shi-fu-a
  • 96323260
  • 79523156
  • ruoxin-xv
  • 13390379
  • 5436817641
  • frewgf
  • 5886496374
  • 26949268
  • 5491464061
  • 17488434
  • 1629041425
  • loucst
  • 1994427592
  • guidetheorient
  • 9570529
  • bhb603
  • 6780231
  • 1789247363
  • 8663884
  • yellowrui
  • 1366427322
  • denghua
  • 44791452
未经允许不得转载: LIFE & SHARE - 王颜公子 » 一文搞懂 MongoEngine 的使用

分享

作者

作者头像

Madman

如需 Linux / Python 相关问题付费解答,请按如下方式联系我

5 条评论

321坤-TOO
321坤-TOO

666666666666666666666666,请问作者,文章买了会过期吗?永久会员在哪买,咋没看到咧

Madman
Madman 321坤-TOO Author

不会过期,只要我还运营此网站就能看,当然读者的支持是我继续下去的动力!会员的权限可以联系我微信 wangy8961 了解详情

321坤-TOO
321坤-TOO

666666,请问作者,文章买了有期限的吗?还有永久会员在哪啊。没找到咧~

zhouboboya
zhouboboya

'indexes': ['title', 'author'], # 索引字段,后续按这两个字段值查询时可以加快速度 这个索引是使用的过程中自建的索引吗?我是数据库小白

Madman
Madman zhouboboya Author

不需要你自己建,由 mongoengine 完成

专题系列