内容


结合使用 MongoDB 和 Django

添加一个面向文档的数据库

Comments

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;这里提供几个有用的概念:

  • 您可以使用对象字面量语法 (object literal syntax) 创建对象,换句话说,会使用两个花括号(如 var myCollection = {};)。
  • 您可以使用方括号([])来创建数组。
  • 除了数字、布尔值、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 具有无模式特性,所以 person1person2 不必具有相同的列类型,甚至不必拥有相同的列编号。并且,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 使用的修饰符
修饰符描述
$gt大于
$lt小于
$gte大于或等于
$lte小于或等于
$in检查某个数组是否存在,类似于 'in' SQL 操作符。

当然,您也可以使用 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。在本示例中,使用了两种字段类型:StringFieldIntField。与 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 视图拥有三个方法:indexupdatedelete

层叠样式表 (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)

数据库名称是源自设置文件,用于分隔关注点。每个博客帖子都包含三个必要的字段:titlecontentlast_update。如果您将此清单与您通常在 Django 获得的清单进行对比,就会发现它们差别并不大。此清单使用了 mongoengine.Document 类,而非从 django.db.models.Model 中继承的类。在这里,我并不打算深入探讨数据类型之间的差别,但您可以随意查看 MongoEngine 文档(参见 参考资料)。

表 2 列出了 MongoEngine 字段类型,并显示了对等的 Django ORM 字段类型(如果存在的话)。

表 2. MongoEngine 字段类型和 Django ORM 对等物
MongoEngine 字段类型Django ORM 对等物
StringFieldCharField
URLFieldURLField
EmailFieldEmailField
IntFieldIntegerField
FloatFieldFloatField
DecimalFieldDecimalField
BooleanFieldBooleanField
DateTimeFieldDateTimeField
EmbeddedDocumentFieldNone
DictFieldNone
ListFieldNone
SortedListFieldNone
BinaryFieldNone
ObjectIdFieldNone
FileFieldFileField

最后,您可以创建自己的视图。在这里,您拥有三个视图方法:indexupdatedelete。要执行想要执行的操作,则必须向特定 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 操作。虽然这只是第一步,但是希望您现在能够了解如何将此设置应用到您自己的项目中。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source
ArticleID=810210
ArticleTitle=结合使用 MongoDB 和 Django
publish-date=04162012