添加一个面向文档的数据库
Django 是一种 Python Web 框架,由对象关系映射器 (ORM)、后端控制器和模板系统组成。MongoDB
是一种面向文档的数据库(也称为 NoSQL 数据库),能有效地进行扩展并提供高性能。在本文中,我们将学习如何从
Python 调用 MongoDB(使用 MongoEngine),以及如何将它集成到 Django 项目中以代替内置的
ORM。本文还包括用来为 MongoDB 后端创建、读取和更新数据的样例 Web 界面。
Django 是在一个极为出色的模块化样式中得到应用的;替换一个基于 Django
的 Web 应用程序的不同组件非常简单。由于 NoSQL 目前较为常见,您可能想要尝试运行一个具有不同后端而非具有标准关系数据库(如
MySQL?)的应用程序。在本文中,您将初体验 MongoDB,包括如何使用 PyMongo 或 MongoEngine
在您的 Python 项目中调用它。接着,您将使用 Django 和 MongoEngine 创建一个可执行创建、读取、更新、删除
(CRUD) 操作的简单博客。
关于 NoSQL 数据库
根据 nosql-database.org,NoSQL 数据库是 “下一代数据库,主要具有以下几个要点:非关系型、分布式、开放源码和可水平伸缩”。面向文档的数据库
MongoDB 就是这种类型的数据库。
从一种新的角度来看,Django 1.3 包含了对 SQLite、MySQL、PostgreSQL
和 Oracle 的支持,但是并不包含对 MongoDB 的支持。不过,要添加对 MongoDB 的支持非常容易,但要以失去自动管理面板为代价。因此,您必须根据您的需要进行权衡。
MongoDB 简介
MongoDB 可以充当 JavaScript 解释器,因此数据库操作是通过
JavaScript 命令完成的。在您的机器上本地安装 MongoDB 后(参见 参考资料),可以尝试使用
清单 1 中列出的一些命令。
清单 1. 您可以尝试对 MongoDB 使用的样例 JavaScript
命令
var x = "0"; x === 0; typeof({}); |
您不必成为一名 JavaScript 专家之后才开始使用 MongoDB;这里提供几个有用的概念:
1.您可以使用对象字面量语法 (object literal syntax) 创建对象,换句话说,会使用两个花括号(如
var myCollection = {};)。
2.您可以使用方括号([])来创建数组。
3.除了数字、布尔值、null 和未定义值以外,JavaScript 中的其他所有值都是一个对象。
如果您想要了解关于 JavaScript 其他特性的更多信息,比如原型的面向对象的编程 (OOP)、范围规则,及其函数式编程特性,请参阅
参考资料。
MongoDB 是一种无模式数据库,与关系型数据库完全相反。无模式数据库没有使用表格,而是使用由文档组成的集合。这些文档是使用对象字面量语法创建的,如
清单 2 所示。
清单 2. 文档创建示例
var person1 = {name:"John Doe", age:25}; var person2 = {name:"Jane Doe", age:26, dept: 115}; |
现在,请执行 清单 3 中所示的命令来创建一个新集合。
清单 3. 创建集合
db.employees.save(person1); db.employees.save(person2); |
由于 MongoDB 具有无模式特性,所以 person1 和 person2 不必具有相同的列类型,甚至不必拥有相同的列编号。并且,MongoDB
本身具有动态特性,所以它会创建 employees 而不是抛出一个错误。您可以通过 find() 方法检索文档。要获取
employees 中的所有文档,可以直接调用 find(),无需使用任何参数,如 清单 4 所示。
清单 4. 一个简单的 MongoDB 查询
> db.employees.find();
// returns
[
{ "_id" : { "$oid" : "4e363c4dcc93747e68055fa1" },
"name" : "John Doe", "age" : 25 },
{ "_id" : { "$oid" : "4e363c53cc93747e68055fa2" },
"name" : "Jane Doe", "dept" : 115, "age" : 26 }
] |
注意,_id 等效于一个主键。要运行具体的查询,则需要使用键/值对传递另一个对象来指示您所要查询的内容,如
清单 5 所示。
清单 5. 通过一个搜索参数进行查询
> db.employees.find({name: "John Doe"});
// returns
[
{ "_id" : { "$oid" : "4e363c4dcc93747e68055fa1" },
"name" : "John Doe", "age" : 25 }
]
|
要查询年龄大于 25 岁的员工,请执行 清单 6 中的命令。
清单 6. 查询年龄大于 25 岁的员工
> db.employees.find({age:{'$gt':25}});
// returns
[
{ "_id" : { "$oid" : "4e363c53cc93747e68055fa2" },
"name" : "Jane Doe", "dept" : 115, "age" : 26 }
]
|
$gt 是一个特殊操作符,表示大于。表 1 列出了其他修饰符。
表 1. 可以对 MongoDB 使用的修饰符
当然,您也可以使用 update() 方法更新记录。您可以更新整条记录,如 清单 7 所示。
清单 7. 更新整条记录
> db.employees.update({ name:"John Doe", // Document to update {name:"John Doe", age:27} // updated document }); |
此外,您还可以使用 $set 操作符仅更新一个值,如 清单 8 所示。
清单 8. 仅更新记录中的一个值
> db.employees.update({name:"John Doe", { '$set': {age:27} } }); |
要清空集合,可以调用 remove() 方法,无需使用任何参数。例如,如果您想要从 employees
集合中移除 John Doe,那么您可以执行 清单 9 所示的操作。
清单 9. 从 employees 集合中移除 John Doe
> db.employees.remove({"name":"John Doe"}); > db.employees.find(); // returns [ { "_id" : { "$oid" : "4e363c53cc93747e68055fa2" }, "name" : "Jane Doe", "dept" : 115, "age" : 26 } ] |
此内容对于刚开始使用 MongoDB 的您来说已经足够了。当然,您可以继续浏览官方网站,该网站提供了简洁的、基于
Web 的交互式 mongodb 命令提示符,以及指南和官方文档。请参阅 参考资料。
将 Django 与 MongoDB 集成
有几个从 Python 或 Django 访问 MongoDB 的选项。第一个选项是使用 Python
模块,即 PyMongo。清单 10 是一个简单的 PyMongo 会话,假设您已经安装了 MongoDB,并且已经拥有一个在端口上运行的实例。
清单 10. 样例 PyMongo 会话
from pymongo import Connection
databaseName = "sample_database"
connection = Connection()
db = connection[databaseName]
employees = db['employees']
person1 = { "name" : "John Doe",
"age" : 25, "dept": 101, "languages":["English","German","Japanese"] }
person2 = { "name" : "Jane Doe",
"age" : 27, "languages":["English","Spanish","French"] }
print "clearing"
employees.remove()
print "saving"
employees.save(person1)
employees.save(person2)
print "searching"
for e in employees.find():
print e["name"] + " " + unicode(e["languages"])
|
PyMongo 允许您同时运行多个数据库。要定义一个连接,只需将数据库名字传递给连接实例。在本例中,Python
词典代替了 JavaScript 对象字面量来创建新文档定义,而 Python 列表代替了 JavaScript
数组。find 方法会返回一个您可以进行迭代的数据库游标对象。
语法的简易性使 MongoDB 命令行与包含 PyMongo 的运行命令之间的切换变得容易。例如,清单
11 展示了如何使用 PyMongo 运行一个查询。
清单 11. 使用 PyMongo 运行一个查询
for e in employees.find({"name":"John Doe"}): print e |
您从 Python 调用 MongoDB 的另一个选项是 MongoEngine,如果您使用过 Django
的内置 ORM,那么您对此应该感到非常熟悉。MongoEngine 是一个文档到对象的映射器,从概念上说,它与映射到
ORM 的映射器类似。清单 12 显示了一个使用 MongoEngine 的示例会话。
清单 12. MongoEngine 示例会话
from mongoengine import *
connect('employeeDB')
class Employee(Document):
name = StringField(max_length=50)
age = IntField(required=False)
john = Employee(name="John Doe", age=25)
john.save()
jane = Employee(name="Jane Doe", age=27)
jane.save()
for e in Employee.objects.all():
print e["id"], e["name"], e["age"]
|
Employee 对象继承自 mongoengine.Document。在本示例中,使用了两种字段类型:StringField
和 IntField。与 Django 的 ORM 相似,要通过查询获得集合中的所有文档,请调用 Employee.objects.all()。请注意,要访问惟一的对象
ID,请使用 "id" 而非 "_id"
一个样例博客
现在要创建一个名为 Blongo 的简单博客。您将使用 Python 1.7、Django 1.3、MongoDB
1.8.2、MongoEngine 0.4 和 Hypertext Markup Language (HTML)
5。如果您想要重现我的精确设置,我习惯使用 Ubuntu Linux 和 FireFox。Blongo
在网页加载后就会显示所输入的所有博客条目,并且允许对任何条目执行更新和删除操作,换句话说,允许进行所有的标准
CRUD 操作。Django 视图拥有三个方法:index、update 和 delete。
层叠样式表 (CSS) 定义被引入一个单独的静态文件。相关内容我不想在此处赘述,您可以随意浏览 下载
部分中包含的源代码。
假设已安装好所需的所有工具,并且这些组件都能良好运行,那么请创建一个新的 Django 项目和必要的组件,如
清单 13 所示。
清单 13. 创建 Django 博客项目的命令
$ django-admin.py startproject blongo $ cd blongo $ django-admin.py startapp blogapp $ mkdir templates $ cd blogapp $ mkdir static |
Django 1.3 的新增内容是一个已包含在 Django 中的贡献型应用程序,可使用它来改进静态文件的处理。通过向任何应用程序目录添加一个静态目录(例如,本例中的
blogapp)并确保 django.contrib.staticfiles 包含在已安装应用程序中,Django
可以查找到诸如 .css 和 .js 的静态文件,而无需使用其他任何方法。清单 14 显示了设置文件的代码行,这些代码行已经进行了相应修改(来自默认的
settings.py 文件),使博客应用程序能够正常运行。
清单 14. 已改变的设置文件代码行(来自默认的 settings.py 文件)
# Django settings for blog project.
import os
APP_DIR = os.path.dirname( globals()['__file__'] )
DBNAME = 'blog'
TEMPLATE_DIRS = (
os.path.join( APP_DIR, 'templates' )
)
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog.blogapp',
)
|
本项目中也有三个模板:index.html、update.html 和 delete.html。清单
15 显示了这三个模板文件的代码。
清单 15. index.html、update.html 和 delete.html 模板文件的代码
<!-- index.html --> <!DOCTYPE html> <html> <head> <link href="{{STATIC_URL}}blog.css" rel="stylesheet" type="text/css"> </head> <body> <h1>Blongo</h1> <form method="post" action="http://127.0.0.1:8000/"> {% csrf_token %} <ul> <li> <input type="text" name="title" placeholder="Post Title" required> </li> <li> <textarea name="content" placeholder="Enter Content" rows=5 cols=50 required> </textarea> </li> <li> <input type="submit" value="Add Post"> </li> </ul> </form> <!-- Cycle through entries --> {% for post in Posts %} <h2> {{ post.title }} </h2> <p>{{ post.last_update }}</p> <p>{{ post.content }}</p> <form method="get" action="http://127.0.0.1:8000/update"> <input type="hidden" name="id" value="{{ post.id }}"> <input type="hidden" name="title" value="{{ post.title }}"> <input type="hidden" name="last_update" value="{{ post.last_update }}"> <input type="hidden" name="content" value="{{ post.content }}"> <input type="submit" name="" value="update"> </form> <form method="get" action="http://127.0.0.1:8000/delete"> <input type="hidden" name="id" value="{{post.id}}"> <input type="submit" value="delete"> </form> {% endfor %} </body> </html>
<!-- update.html -->
<!DOCTYPE html>
<html>
<head>
<link href="{{STATIC_URL}}blog.css"
rel="stylesheet" type="text/css">
</head>
<body>
<h1>Blongo - Update Entry</h1>
<form method="post" action="http://127.0.0.1:8000/update/">
{% csrf_token %}
<ul>
<li><input type="hidden" name="id"
value="{{post.id}}"></li>
<li>
<input type="text" name="title"
placeholder="Post Title"
value="{{post.title}}" required>
<input type="text" name="last_update"
value="{{post.last_update}}" required>
</li>
<li>
<textarea name="content" placeholder="Enter
Content"
rows=5 cols=50 required>
{{post.content}}
</textarea>
</li>
<li>
<input type="submit" value="Save
Changes">
</li>
</ul>
</form>
</body>
</html>
<!-- delete.html -->
<!DOCTYPE html>
<html>
<head>
<link href="{{STATIC_URL}}blog.css"
rel="stylesheet" type="text/css">
</head>
<body>
<h1>Blongo - Delete Entry</h1>
<form method="post" action="http://127.0.0.1:8000/delete/">
{% csrf_token %}
<input type="hidden" name="id"
value="{{id}}">
<p>Are you sure you want to delete this
post?</p>
<input type="submit" value="Delete">
</form>
</body>
</html> |
接着,更改 清单 16 中所示代码的 URL 映射,该映射指向索引、更新和删除的视图。您可能想让样例博客创建新的博客条目(在索引中)、更新现有博客帖子,并在想要删除这些帖子的时候删除它们。每个操作都是通过向特定
URL 发送一个请求来完成的。
清单 16. 索引、更新和删除的映射
from django.conf.urls.defaults import patterns, include, url
urlpatterns = patterns('',
url(r'^$', 'blog.blogapp.views.index'),
url(r'^update/', 'blog.blogapp.views.update'),
url(r'^delete/', 'blog.blogapp.views.delete'),
)
|
注意,您无 需运行 syncdb Django 命令。要将 MongoDB 集成到您的应用程序中,只需使用
MongoEngine 即可。在 blogapp 目录的 models.py 文件中,添加 清单 17
中所示的代码。
清单 17. 在数据层中包含 MongoEngine
from mongoengine import *
from blog.settings import DBNAME
connect(DBNAME)
class Post(Document):
title = StringField(max_length=120, required=True)
content = StringField(max_length=500, required=True)
last_update = DateTimeField(required=True)
|
数据库名称是源自设置文件,用于分隔关注点。每个博客帖子都包含三个必要的字段:title、content
和 last_update。如果您将此清单与您通常在 Django 获得的清单进行对比,就会发现它们差别并不大。此清单使用了
mongoengine.Document 类,而非从 django.db.models.Model 中继承的类。在这里,我并不打算深入探讨数据类型之间的差别,但您可以随意查看
MongoEngine 文档(参见 参考资料)。
表 2 列出了 MongoEngine 字段类型,并显示了对等的 Django ORM 字段类型(如果存在的话)。
表 2. MongoEngine 字段类型和 Django ORM 对等物
最后,您可以创建自己的视图。在这里,您拥有三个视图方法:index、update 和 delete。要执行想要执行的操作,则必须向特定
URL 发送一个请求。例如,要更新一个文档,则必须向 localhost:8000/update 发送一个请求。执行一个
http 'GET' 请求不会执行保存、更新等操作。新博客帖子是从索引视图插入的。清单 18 显示了索引、更新和删除视图的实现。
清单 18. Django 视图
from django.shortcuts import render_to_response from django.template import RequestContext from models import Post import datetime
def index(request):
if request.method == 'POST':
# save new post
title = request.POST['title']
content = request.POST['content']
post = Post(title=title)
post.last_update = datetime.datetime.now()
post.content = content
post.save()
# Get all posts from DB
posts = Post.objects
return render_to_response('index.html', {'Posts':
posts},
context_instance=RequestContext(request))
def update(request):
id = eval("request." + request.method
+ "['id']")
post = Post.objects(id=id)[0]
if request.method == 'POST':
# update field values and save to mongo
post.title = request.POST['title']
post.last_update = datetime.datetime.now()
post.content = request.POST['content']
post.save()
template = 'index.html'
params = {'Posts': Post.objects}
elif request.method == 'GET':
template = 'update.html'
params = {'post':post}
return render_to_response(template, params, context_instance=RequestContext(request))
def delete(request):
id = eval("request." + request.method
+ "['id']")
if request.method == 'POST':
post = Post.objects(id=id)[0]
post.delete()
template = 'index.html'
params = {'Posts': Post.objects}
elif request.method == 'GET':
template = 'delete.html'
params = { 'id': id }
return render_to_response(template, params,
context_instance=RequestContext(request)) |
您可能已经注意到用来索引文档 ID 的 eval 语句。使用该语句是为了避免编写 清单 19 中所示的
if 语句。
清单 19. 检索文档 ID 的替代方法
if request.method == 'POST': id = request.POST['id'] elif request.method == 'GET': id = request.GET['id'] |
您也可以这样编写代码。以上就是创建并运行一个简单博客需要执行的所有操作。显然,最终产品中还会添加许多组件,比如用户、登录、标签等。
结束语
正如您所看到的,从 Django 调用 MongoDB 真的不是很复杂。在本文中,我简单介绍了一下 MongoDB,并解释了如何访问它,以及如何通过
PyMongo 包装器和 MongoEngine 的 “对象至文档” 映射器,从 Python 操作 MongoDB
的集合和文档。最后,我快速演示了如何使用 Django 执行基础 CRUD 操作。虽然这只是第一步,但是希望您现在能够了解如何将此设置应用到您自己的项目中。
|