第十八章: 集成已有的数据库和应用
Django最适合于所谓的green-field开发,即从头开始的一个项目,正如你在一块还长着青草的未开垦的土地上从零开始建造一栋建筑一般。 然而,尽管Django偏爱从头开始的项目,将这个框架和以前遗留的数据库和应用相整合仍然是可能的。 本章就将介绍一些整合的技巧。
与遗留数据库整合
Django的数据库层从Python代码生成SQL schemas—但是对于遗留数据库,你已经拥有SQL schemas. 这种情况,你需要为已经存在的数据表创建model. 为此,Django自带了一个可以通过读取您的数据表结构来生成model的工具. 该辅助工具称为inspectdb,你可以通过执行manage.py inspectdb
来调用它.
使用 inspectdb
inspectdb
工具自省你配置文件指向的数据库,针对每一个表生成一个Django模型,然后将这些Python模型的代码显示在系统的标准输出里面。
下面是一个从头开始的针对一个典型的遗留数据库的整合过程。 两个前提条件是安装了Django和一个传统数据库。
通过运行django-admin.py startproject mysite (这里
mysite
是你的项目的名字)建立一个Django项目。 好的,那我们在这个例子中就用这个mysite
作为项目的名字。编辑项目中的配置文件,
mysite/settings.py
,告诉Django你的数据库连接参数和数据库名。 具体的说,要提供DATABASE_NAME
,DATABASE_ENGINE
,DATABASE_USER
,DATABASE_PASSWORD
,DATABASE_HOST
, 和DATABASE_PORT
这些配置信息.。 (请注意其中的一些设置是可选的。 更多信息参见第5章)通过运行
python mysite/manage.py startapp myapp
(这里myapp
是你的应用的名字)创建一个Django应用。 这里我们使用myapp
做为应用名。运行命令
python mysite/manage.py inspectdb
。这将检查DATABASE_NAME
数据库中所有的表并打印出为每张表生成的模型类。 看一看输出结果以了解inspectdb能做些什么。将标准shell的输出重定向,保存输出到你的应用的
models.py
文件里:
python mysite/manage.py inspectdb > mysite/myapp/models.py
编辑
mysite/myapp/models.py
文件以清理生成的 models 并且做一些必要的自定义。 针对这个,下一个节有些好的建议。
清理生成的Models
如你可能会预料到的,数据库自省不是完美的,你需要对产生的模型代码做些许清理。 这里提醒一点关于处理生成 models 的要点:
数据库的每一个表都会被转化为一个model类 (也就是说,数据库的表和model 类之间是一对一的映射)。 这意味着你需要为多对多连接的表,重构其models 为
ManyToManyField
的对象。所生成的每一个model中的每个字段都拥有自己的属性,包括id主键字段。 但是,请注意,如果某个model没有主键的话,那么Django会自动为其增加一个id主键字段。 这样一来,你也许希望移除这样的代码行。
id = models.IntegerField(primary_key=True)
这样做并不是仅仅因为这些行是冗余的,而且如果当你的应用需要向这些表中增加新记录时,这些行会导致某些问题。
每一个字段类型,如CharField、DateField, 是通过查找数据库列类型如VARCHAR,DATE来确定的。如果inspectdb无法把某个数据库字段映射到model字段上,它会使用TextField字段进行代替,并且会在所生成model字段后面加入Python注释“该字段类型是猜的”。 对这要当心,如果必要的话,更改字段类型。
如果你的数据库中的某个字段在Django中找不到合适的对应物,你可以放心的略过它。 Django模型层不要求必须导入你数据库表中的每个列。
如果数据库中某个列的名字是Python的保留字(比如pass、class或者for等),inspectdb会在每个属性名后附加上_field,并将db_column属性设置为真实的字段名(也就是pass,class或者for等)。
例如,某张表中包含一个INT类型的列,其列名为for,那么所生成的model将会包含如下所示的一个字段:
for_field = models.IntegerField(db_column='for')
inspectdb
会在该字段后加注‘字段重命名,因为它是一个Python保留字’
。如果数据库中某张表引用了其他表(正如大多数数据库系统所做的那样),你需要适当的修改所生成model的顺序,以使得这种引用能够正确映射。 例如,model Book拥有一个针对于model Author的外键,那么后者应该先于前者被定义。如果你想创建一个指向尚未定义的model的关系,那么可以使用包含model名的字符串,而不是model对象本身。
对于PostgreSQL,MySQL和SQLite数据库系统,inspectdb能够自动检测出主键关系。 也就是说,它会在合适的位置插入primary_key=True。 而对于其他数据库系统,你必须为每一个model中至少一个字段插入这样的语句,因为Django的model要求必须拥有一个primary_key=True的字段。
外键检测仅对PostgreSQL,还有MySQL表中的某些特定类型生效。 至于其他数据库,外键字段将在假定其为INT列的情况下被自动生成为IntegerField。
与认证系统的整合
将Django与其他现有认证系统的用户名和密码或者认证方法进行整合是可以办到的。
例如,你所在的公司也许已经安装了LDAP,并且为每一个员工都存储了相应的用户名和密码。 如果用户在LDAP和基于Django的应用上拥有独立的账号,那么这时无论对于网络管理员还是用户自己来说,都是一件很令人头痛的事儿。
为了解决这样的问题,Django认证系统能让您以插件方式与其他认证资源进行交互。 您可以覆盖Diango默认的基于数据库的模式,您还可以使用默认的系统与其他系统进行交互。
指定认证后台
在后台,Django维护了一个用于检查认证的后台列表。 当某个人调用 django.contrib.auth.authenticate()
(如14章中所述)时,Django会尝试对其认证后台进行遍历认证。 如果第一个认证方法失败,Django会尝试认证第二个,以此类推,一直到尝试完。
认证后台列表在AUTHENTICATION_BACKENDS设置中进行指定。 它应该是指向知道如何认证的Python类的Python路径的名字数组。 这些类可以在你Python路径的任何位置。
默认情况下,AUTHENTICATION_BACKENDS被设置为如下:
('django.contrib.auth.backends.ModelBackend',)
那就是检测Django用户数据库的基本认证模式。
AUTHENTICATION_BACKENDS的顺序很重要,如果用户名和密码在多个后台中都是有效的,那么Django将会在第一个正确匹配后停止进一步的处理。
编写认证后台
一个认证后台其实就是一个实现了如下两个方法的类: get_user(id)
和 authenticate(**credentials)
。
方法 get_user
需要一个参数 id
,这个 id
可以是用户名,数据库ID或者其他任何数值,该方法会返回一个 User
对象。
方法 authenticate
使用证书作为关键参数。 大多数情况下,该方法看起来如下:
class MyBackend(object):
def authenticate(self, username=None, password=None):
# Check the username/password and return a User.
但是有时候它也可以认证某个短语,例如:
class MyBackend(object):
def authenticate(self, token=None):
# Check the token and return a User.
每一个方法中, authenticate
都应该检测它所获取的证书,并且当证书有效时,返回一个匹配于该证书的 User
对象,如果证书无效那么返回 None
。 如果它们不合法,就返回None
。
如14章中所述,Django管理系统紧密连接于其自己后台数据库的 User
对象。 实现这个功能的最好办法就是为您的后台数据库(如LDAP目录,外部SQL数据库等)中的每个用户都创建一个对应的Django User对象。 您可以提前写一个脚本来完成这个工作,也可以在某个用户第一次登陆的时候在 authenticate
方法中进行实现。
以下是一个示例后台程序,该后台用于认证定义在 setting.py
文件中的username和password变量,并且在该用户第一次认证的时候创建一个相应的Django User
对象。
from django.conf import settings
from django.contrib.auth.models import User, check_password
class SettingsBackend(object):
"""
Authenticate against the settings ADMIN_LOGIN and ADMIN_PASSWORD.
Use the login name, and a hash of the password. For example:
ADMIN_LOGIN = 'admin'
ADMIN_PASSWORD = 'sha1$4e987$afbcf42e21bd417fb71db8c66b321e9fc33051de'
"""
def authenticate(self, username=None, password=None):
login_valid = (settings.ADMIN_LOGIN == username)
pwd_valid = check_password(password, settings.ADMIN_PASSWORD)
if login_valid and pwd_valid:
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
# Create a new user. Note that we can set password
# to anything, because it won't be checked; the password
# from settings.py will.
user = User(username=username, password='get from settings.py')
user.is_staff = True
user.is_superuser = True
user.save()
return user
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
更多认证模块的后台, 参考Django文档。
和遗留Web应用集成
同由其他技术驱动的应用一样,在相同的Web服务器上运行Django应用也是可行的。 最简单直接的办法就是利用Apaches配置文件httpd.conf,将不同的URL类型分发至不同的技术。 (请注意,第12章包含了在Apache/mod_python上配置Django的相关内容,因此在尝试本章集成之前花些时间去仔细阅读第12章或许是值得的。)
关键在于只有在您的httpd.conf文件中进行了相关定义,Django对某个特定的URL类型的驱动才会被激活。 在第12章中解释的缺省部署方案假定您需要Django去驱动某个特定域上的每一个页面。
<Location "/">
SetHandler python-program
PythonHandler django.core.handlers.modpython
SetEnv DJANGO_SETTINGS_MODULE mysite.settings
PythonDebug On
</Location>
这里, <Location "/">
这一行表示用Django处理每个以根开头的URL.
精妙之处在于Django将<location>指令值限定于一个特定的目录树上。 举个例子,比如说您有一个在某个域中驱动大多数页面的遗留PHP应用,并且您希望不中断PHP代码的运行而在../admin/位置安装一个Django域。 要做到这一点,您只需将<location>值设置为/admin/即可。
<Location "/admin/">
SetHandler python-program
PythonHandler django.core.handlers.modpython
SetEnv DJANGO_SETTINGS_MODULE mysite.settings
PythonDebug On
</Location>
有了这样的设置,只有那些以/admin/开头的URL地址才会触发Django去进行处理。 其他页面会使用已存在的设置。
请注意,把Diango绑定到的合格的URL(比如在本章例子中的 /admin/
)并不会影响其对URL的解析。 绝对路径对Django才是有效的(例如 /admin/people/person/add/
),而非截断后的URL(例如 /people/person/add/
)。这意味着你的根URLconf必须包含前缀 /admin/
。
下一章
如果你的母语是英语, 你可能就不会注意到许多Django admin网站中最酷的特性功能。 它支持超过50种语言! Django 的国际化框架使其成为可能( 还有Django志愿翻译者的努力 )。下一章介绍如何使用这个框架来提供本地化的Django网站。