第十六章 集成已有的数据库和应用

优质
小牛编辑
128浏览
2023-12-01

Django 最适合于所谓的 green-field 开发,即从头开始的一个项目,正如你在一块还长着青 草 的未开垦的土地上从零开始建造一栋建筑一般。然而,尽管 Django 偏爱从头开始的项目, 将这个框架和以前遗留的数据库和应用相整合仍然是可能的。本章就 将介绍一些整合的技巧。

与遗留数据库整合

Django 的数据库层从 Python 代码生成 SQL schemas—但是对于遗留数据库,你已经拥有 SQL schemas,这种情况下你需要为你 已经存在的数据库表写模型(由于性能的原因,Django 的数据库层不支持通过运行时自省数据库的不工作的对象-关系映射, 为了使用数据库 API,你需要写模型代码),幸运的是,Django 带有通过阅读你的数据库表规划来生成模型代码的辅助工具 该辅助工具称为 manage.py inspectdb

使用 inspectdb

The inspectdb 工具内省检查你的配置文件(setting file)指向的数据库,针对你的每一个表生成一个 Django model 的表现,然后将这些 Python model 的代码显示在系统的标准输出里面。

下面是一个从头开始的针对一个典型的遗留数据库的整合过程

通过运行 django-admin.py startproject mysite (这里 mysite 是你的项目的名字)建立一个 Django 项目。好的,那我们在这个例子中就用这个 mysite 作为项目的名字。

编辑项目中的配置文件, mysite/settings.py ,告诉 Django 你的数据库连接参数和数据库名。具体的说,要提供 DATABASE_NAME , DATABASE_ENGINE , DATABASE_USER , DATABASE_PASSWORD , DATABASE_HOST , 和 DATABASE_PORT 这些配置信息. (注意,这里面有

些配置项是可选的,更多信息参考第五章)

通过运行 python mysite/manage.py startapp myapp (这里 myapp 是你的应用的名字)创建一个 Django 应用.那么,我们就以 myapp 做为这个应用的名字.

运行命令 python mysite/manage.py inspectdb . 这将在 DATABASE_NAME 数据库中检查所有的表和打印出为每张表生成的 model class. 看一看输出结果想一下 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)

这样做并不是仅仅因为这些行是冗余的,而且如果当你的应用需要向这些表中增加新记录时,这些行会导致某些问题。而 inspectdb 命令并不能检测出一个字段是否自增长的,因此必要的时候,你必须将他们修改为 AutoField.

每一个字段类型,如 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 认证系统能让您以插件方式与其他认证资源进行交互。您可以覆盖 Diangos 的默认基于数据库模式,您还可以使用默认的系统与其他系统进行交互。

指定认证后台

在后台,Django 维护了一个用于检查认证的后台列表。当某个人调用 django.contrib.auth.authenticate() (如 12 章中所述)时,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 。

如 12 章中所述,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

和遗留 Web 应用集成

同由其他技术驱动的应用一样,在相同的 Web 服务器上运行 Django 应用也是可行的。最简单直接的办 法就是利用 Apaches 配置文件 httpd.conf,将不同的 URL 类型代理至不同的技术。

(请注意,第 20 章包含了在 Apache/mod_python 上配置 Django 的相关内容,因此在尝试本章集成之前花些时间去仔细阅读第 20 章或许是值得的。)

关键在于只有在您的 httpd.conf 文件中进行了相关定义,Django 对某个特定的 URL 类型的驱动才会被激活。在第 20 章中解释的缺省部署方案假定您需要 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 管理站点和让整个框架服从于遗留 Web 集成需求时,另一个常见任务就是定制

Django 管理站点。我们会在下一章中聚焦类似的定制。