在Flask中使用MongoEngine,需要通過Flask-MongoEngine包來對MongoEngine進行配置。Flask-MongoEngine是MongoEngine的Flask封裝,針對Flask對MongoEngine做出了一些拓展,而MongoEngine則是在PyMongo的基礎上構建的一個類似於SQLAlchemy的對象文檔映射器(Object-Document Mapper, ODM),為用戶提供基本的數據模型以及類型約束,並對PyMongo的數據查詢做了進一步的封裝,避免直接書寫MongoDB查詢語句,簡化數據查詢。
PyMongo是MongoDB官方提供的Python庫,將MongoDB CLI中JS風格的查詢語句在Python中進行了封裝,用戶仍然可以在Python中采用和MongoDB CLI中相同的方法操作MongoDB數據庫。但PyMongo本身缺乏類型約束、值校驗、數據模型等特性支持,每一次的查詢操作都需要直接和字典進行交互。
Flask-MongoEngine對MongoEngine做了以下拓展:
- 從
MONGODB_XXX
格式變量中讀取MongoEngine的配置信息,並自動建立連接。 - 對MongoEngine的查詢
QuerySet
進行了拓展,以支持get_or_404
,first_or_404
,paginate
,paginate_field
。 - 支持直接從MongoEngine模型中生成WTForms表單。
- 支持將MongoEngine作為session存儲后端。
- 為
flask_debugtoolbar
提供MongoEngine查詢跟蹤。 - 通過
app.json_encoder
對Flask默認的JSON編碼器進行拓展,添加了BaseDocument
和QuerySet
兩個類型的序列化支持。
資源
Item | Repository | Documents |
---|---|---|
MongoEngine | https://github.com/MongoEngine/mongoengine | https://mongoengine-odm.readthedocs.io/ |
Flask-MongoEngine | https://github.com/MongoEngine/flask-mongoengine | https://flask-mongoengine.readthedocs.io |
快速開始
from flask import Flask
from flask_mongoengine import MongoEngine
app = Flask(__name__)
# 通過MONGODB_SETTINGS配置MongoEngine
app.config.from_mapping({
MONGODB_SETTINGS = {
'db': 'test',
'host': 'localhost',
'port': 27017,
'connect': True,
'username': 'test',
'password': '123456',
'authentication_source': 'admin'
}
})
# 初始化 MongoEngine
db = MongoEngine(app)
# 定義一個文檔 User,MongoEngine 會自動在數據庫中創建一個名為 user 的集合
class User(db.Document):
email = db.StringField(required=True)
username = db.StringField(required=True, max_length=128, unique=True)
def __repr__(self):
return 'User(email="{}", username="{}")'.format(self.username, self.password)
# 創建一個User文檔實例並存儲
user = User(email="xxx@hotmail.com", username="kikyou", password="123456")
user.save()
# 通過 objects 屬性訪問集合中的所有文檔
# objects 是一個 QuerySet 實例
for user in User.objects[: 5]:
print(user)
# 通過 username 查找用戶,並更新 email
user = User.objects(username='kikyou').first()
user.email = '_kikyou_@kikyou.com'
user.save()
Flask-MongonEngine配置
Flask-MongonEngine通過MONGODB_XXX
格式的配置變量來配置MongoEngine。其支持通過MONGODB_SETTINGS
來設置MongoEngine的整個配置,也支持通過MONGODB_DB
等來直接設置MONGODB_SETTINGS
下的每個子屬性。需要注意的是,只要設置了MONGODB_SETTINGS
變量,其余的MONGODB_XXX
變量將被忽略。
通過MONGODB_SETTINGS配置MongoEngine
MONGODB_SETTINGS變量是一個字典,其對應着mongoengine中connect函數支持的所有關鍵字。一旦該變量被設置,其他基於MONGODB_XXX格式指定的mongoengine配置信息將被忽略。
MONGODB_SETTINGS = {
'db': 'appdb',
'host': 'localhost',
'port': 27017,
'connect': True,
'username': 'test',
'password': '123456',
}
完整的參數列表參考mongoengine.connection.register_connection
以及pymongo.mongo_client.MongoClient
。
alias
建立的數據庫連接的別名,默認為default
,通過alias機制可以同時訪問多個MongoDB數據庫。db
將要訪問的數據庫名稱,默認為test
host
MongoDB服務器地址,默認為localhost
port
MongoDB服務器端口,默認為27017
username
用戶名password
用戶密碼authentication_source
認證源,創建該用戶的數據庫authentication_mechanism
認證機制,不需要設置is_mock
是否使用 mongomockconnect
是否直接連接服務器,如果為false,則直到第一次操作時才會連接服務器tz_aware
是否自動識別時區,如果為false,則直接使用本地時間,忽略datetime的時區配置
通過MONGODB_XXX配置MongoEngine
除了通過MONGODB_SETTTINGS直接配置connect的參數外,還可以通過MONGODB_XXX的形式直接指定參數的值,其中XXX對應MONGODB_SETTTINGS中關鍵字的大寫形式,但目前只支持有限關鍵字:
MONGODB_ALIAS
MONGODB_DB
MONGODB_HOST
MONGODB_IS_MOCK
MONGODB_PASSWORD
MONGODB_PORT
MONGODB_USERNAME
MONGODB_CONNECT
MONGODB_TZ_AWARE
Flask-MongoEngine初始化
除了直接使用db = MongoEngine(app)
進行初始化外,Flask-MongoEngine也像其他Flask擴展一樣支持將定義和初始化分離。
# project_dir/__init__.py
from flask import Flask
from flask_mongoengine import MongoEngine
# 定義 MongoEngine
db = MongoEngine()
def create_app():
app = Flask(__name__)
# 通過MONGODB_SETTINGS配置MongoEngine
app.config.from_mapping({
MONGODB_SETTINGS = {
'db': 'test',
'host': 'localhost',
'port': 27017,
'connect': True,
'username': 'test',
'password': '123456',
'authentication_source': 'admin'
}
})
db.init(app)
import .models
return app
# project_dir/models.py
class User(db.Document):
email = db.StringField(required=True)
username = db.StringField(required=True, max_length=128, unique=True)
def __repr__(self):
return 'User(email="{}", username="{}")'.format(self.username, self.password)
Flask-MongoEngine QeurySet 擴展
Flask-MongoEngine 對 MongoEngine的查詢QuerySet
進行了拓展,以支持get_or_404
,first_or_404
,paginate
,paginate_field
。
get_or_404
與QuerySet.get
類似,如果不存在或有兩個及以上的匹配項,則會拋出404異常first_or_404
與QuerySet.first
類似,如果不存在,則會拋出404異常paginate
對QuerySet結果進行分頁,接受兩個參數:page
和per_page
,返回一個Pagination對象。paginate_field(field_name, doc_id, page, per_page, total=None)
與paginate
類似,對文檔的數組類型字段進行分頁。這個方法會首先嘗試獲取“字段名+count”形式的值,如posts_count
,作為posts的總數,如果不存在則會獲取整個文檔,然后計算數組類型字段的長度。不建議使用!
paged = User.objects.paginate(page=1, per_page=10)
Pagination
支持的屬性包括:
total
文檔總數pages
總頁數page
當前頁數has_prev
是否有前一頁has_next
是否有后一頁pre_num
上一頁頁碼next_num
下一頁頁碼items
可迭代文檔集合
此外,Pagination
還支持一個iter_pages
方法,用於為分頁器生成編號,被跳過的頁碼使用None
表示。
class Pagination(object):
...
def iter_pages(self, left_edge=2, left_current=2, right_current=5, right_edge=2):
...
...
{% raw %}
{# Display a page of todos #}
<ul>
{% for todo in paginated_todos.items %}
<li>{{ todo.title }}</li>
{% endfor %}
</ul>
{# Macro for creating navigation links #}
{% macro render_navigation(pagination, endpoint) %}
<div class=pagination>
{% for page in pagination.iter_pages() %}
{% if page %}
{% if page != pagination.page %}
<a href="{{ url_for(endpoint, page=page) }}">{{ page }}</a>
{% else %}
<strong>{{ page }}</strong>
{% endif %}
{% else %}
<span class=ellipsis>…</span>
{% endif %}
{% endfor %}
</div>
{% endmacro %}
{{ render_navigation(paginated_todos, 'view_todos') }}
{% endraw %}
額外的拓展
為Flask-MongoEngine添加額外的JSON序列化類型支持
Flask-MongoEngine默認為Flask添加了BaseDocument
和QuerySet
類型的序列化支持,但對於常見的ObjectId
以及Datetime
數據類型缺乏支持。
from flask import Flask
from flask_mongoengine import MongoEngine
def override_json_encoder(app: Flask):
from bson import ObjectId
from datetime import date
superclass = app.json_encoder
class _JsonEncoder(superclass):
def default(self, o):
if isinstance(o, ObjectId):
return str(o)
if isinstance(o, date):
return o.isoformat()
return superclass.default(self, o)
app.json_encoder = _JsonEncoder
def create_app()
app = Flask(__name__)
# 通過MONGODB_SETTINGS配置MongoEngine
app.config.from_mapping({
MONGODB_SETTINGS = {
'db': 'test',
'host': 'localhost',
'port': 27017,
'connect': True,
'username': 'test',
'password': '123456',
'authentication_source': 'admin'
}
})
override_json_encoder(app)
db.init(app)
import .models
return app
注:datetime
是date
的子類。
Q&A
編輯器/pylint提示“Instance of 'MongoEngine' has no 'StringField' member”
Flask-MongoEngine在初始化時,為MongoEngine類通過_include_mongoengine
方法動態注入mongoengine
和mongoengine.fields
的所有屬性,但pylint在進行靜態檢測時,無法處理動態注入的屬性。
def _include_mongoengine(obj):
"""
Copy all of the attributes from mongoengine and mongoengine.fields
onto obj (which should be an instance of the MongoEngine class).
"""
for module in (mongoengine, mongoengine.fields):
for attr_name in module.__all__:
if not hasattr(obj, attr_name):
setattr(obj, attr_name, getattr(module, attr_name))
# patch BaseField if available
_patch_base_field(obj, attr_name)
class MongoEngine(object):
"""Main class used for initialization of Flask-MongoEngine."""
def __init__(self, app=None, config=None):
_include_mongoengine(self)
...
解決方法1:直接通過mongoengine.fields.XXXField
引用。
import mongoengine.fields as fields
class User(db.Document):
email = fields.StringField(required=True)
username = fields.StringField(required=True, max_length=128, unique=True)
password = fields.StringField(max_length=256)
解決方法2:禁用pylint錯誤報告。
# pylint: disable=no-member
value = db.StringField(max_length=200) # no error
參考:
數據庫連接提示認證失敗(認證數據庫未配置)
MongoDB支持在不同的數據庫上創建不同的用戶,即使這些用戶的用戶名相同。如果將要訪問的數據庫與用戶所在的數據庫不一致,而在連接時只配置將要訪問的數據庫,沒有配置認證數據庫,將產生認證錯誤。
需要通過MONGODB_SETTINGS
變量配置authentication_source
參數,指定用戶所在的數據庫。
MONGODB_SETTINGS = {
'db': 'appdb',
'host': 'localhost',
'port': 27017,
'connect': True,
'username': 'test',
'password': '123456',
'authentication_source': 'admin'
}