Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 299 lines (232 sloc) 13.782 kb
acc918f Jacob Kaplan-Moss Initial import of djangobook from private SVN repo.
jacobian authored
1 ==============================================================
2 Chapter 16: Integrating with Legacy Databases and Applications
3 ==============================================================
4
5 Django is best suited for so-called green-field development -- that is, starting
6 projects from scratch, as if you were constructing a building on a fresh field
7 of green grass. But despite the fact that Django favors from-scratch projects,
8 it's possible to integrate the framework into legacy databases and
9 applications. This chapter explains a few integration strategies.
10
11 Integrating with a Legacy Database
12 ==================================
13
14 Django's database layer generates SQL schemas from Python code -- but with
15 a legacy database, you already have the SQL schemas. In such a case,
16 you'll need to create models for your existing database tables. For this
17 purpose, Django comes with a tool that can generate model code by reading your
18 database table layouts. This tool is called ``inspectdb``, and you can call it
19 by executing the command ``manage.py inspectdb``.
20
21 Using ``inspectdb``
22 -------------------
23
24 The ``inspectdb`` utility introspects the database pointed to by your settings
25 file, determines a Django model representation for each of your tables, and
26 prints the Python model code to standard output.
27
28 Here's a walk-through of a typical legacy database integration process from
29 scratch. The only assumptions are that Django is installed and that you have a
30 legacy database.
31
32 1. Create a Django project by running
33 ``django-admin.py startproject mysite`` (where ``mysite`` is your
34 project's name). We'll use ``mysite`` as the project name in this
35 example.
36
37 2. Edit the settings file in that project, ``mysite/settings.py``,
38 to tell Django what your database connection parameters are and what
39 the name of the database is. Specifically, provide the
40 ``DATABASE_NAME``, ``DATABASE_ENGINE``, ``DATABASE_USER``,
41 ``DATABASE_PASSWORD``, ``DATABASE_HOST``, and ``DATABASE_PORT`` settings.
42 (Note that some of these settings are optional. Refer to Chapter 5 for
43 more information.)
44
45 3. Create a Django application within your project by running
46 ``python mysite/manage.py startapp myapp`` (where ``myapp`` is your
47 application's name). We'll use ``myapp`` as the application name here.
48
49 4. Run the command ``python mysite/manage.py inspectdb``. This will
50 examine the tables in the ``DATABASE_NAME`` database and print the
51 generated model class for each table. Take a look at the output to get
52 an idea of what ``inspectdb`` can do.
53
54 5. Save the output to the ``models.py`` file within your application by using
55 standard shell output redirection::
56
57 python mysite/manage.py inspectdb > mysite/myapp/models.py
58
59 6. Edit the ``mysite/myapp/models.py`` file to clean up the generated
60 models and make any necessary customizations. We'll give
61 some hints for this in the next section.
62
63 Cleaning Up Generated Models
64 ----------------------------
65
66 As you might expect, the database introspection isn't perfect, and you'll need
67 to do some light cleanup of the resulting model code. Here are a few pointers
68 for dealing with the generated models:
69
70 1. Each database table is converted to a model class (i.e., there is a
71 one-to-one mapping between database tables and model classes). This means
72 that you'll need to refactor the models for any many-to-many join tables
73 into ``ManyToManyField`` objects.
74
75 2. Each generated model has an attribute for every field, including
76 ``id`` primary key fields. However, recall that Django automatically
77 adds an ``id`` primary key field if a model doesn't have a primary key.
78 Thus, you'll want to remove any lines that look like this::
79
80 id = models.IntegerField(primary_key=True)
81
82 Not only are these lines redundant, but also they can cause problems if your
83 application will be adding *new* records to these tables. The
84 ``inspectdb`` command cannot detect whether a field is autoincremented,
85 so it's up to you to change this to ``AutoField``, if necessary.
86
87 3. Each field's type (e.g., ``CharField``, ``DateField``) is determined by
88 looking at the database column type (e.g., ``VARCHAR``, ``DATE``). If
89 ``inspectdb`` cannot map a column's type to a model field type, it will
90 use ``TextField`` and will insert the Python comment
91 ``'This field type is a guess.'`` next to the field in the generated
92 model. Keep an eye out for that, and change the field type accordingly
93 if needed.
94
95 If a field in your database has no good Django equivalent, you can
96 safely leave it off. The Django model layer is not required to include
97 every field in your table(s).
98
99 4. If a database column name is a Python reserved word (such as ``pass``,
100 ``class``, or ``for``), ``inspectdb`` will append ``'_field'`` to the
101 attribute name and set the ``db_column`` attribute to the real field
102 name (e.g., ``pass``, ``class``, or ``for``).
103
104 For example, if a table has an ``INT`` column called ``for``, the generated
105 model will have a field like this::
106
107 for_field = models.IntegerField(db_column='for')
108
109 ``inspectdb`` will insert the Python comment
110 ``'Field renamed because it was a Python reserved word.'`` next to the
111 field.
112
113 5. If your database contains tables that refer to other tables (as most
114 databases do), you might need to rearrange the order of the generated
115 models so that models that refer to other models are ordered properly.
116 For example, if model ``Book`` has a ``ForeignKey`` to model ``Author``,
117 model ``Author`` should be defined before model ``Book``. If you need
118 to create a relationship on a model that has not yet been defined, you
119 can use the name of the model, rather than the model object itself.
120
121 6. ``inspectdb`` detects primary keys for PostgreSQL, MySQL, and SQLite.
122 That is, it inserts ``primary_key=True`` where appropriate. For other
123 databases, you'll need to insert ``primary_key=True`` for at least one
124 field in each model, because Django models are required to have a
125 ``primary_key=True`` field.
126
127 7. Foreign-key detection only works with PostgreSQL and with certain types
128 of MySQL tables. In other cases, foreign-key fields will be generated as
129 ``IntegerField``s, assuming the foreign-key column was an ``INT``
130 column.
131
132 Integrating with an Authentication System
133 =========================================
134
135 It's possible to integrate Django with an existing authentication system --
136 another source of usernames and passwords or authentication methods.
137
138 For example, your company may already have an LDAP setup that stores a username
139 and password for every employee. It would be a hassle for both the network
140 administrator and the users themselves if users had separate accounts in LDAP
141 and the Django-based applications.
142
143 To handle situations like this, the Django authentication system lets you
144 plug in other authentication sources. You can override Django's default
145 database-based scheme, or you can use the default system in tandem with other
146 systems.
147
148 Specifying Authentication Back-ends
149 -----------------------------------
150
151 Behind the scenes, Django maintains a list of "authentication back-ends" that it
152 checks for authentication. When somebody calls
153 ``django.contrib.auth.authenticate()`` (as described in Chapter 12), Django
154 tries authenticating across all of its authentication back-ends. If the first
155 authentication method fails, Django tries the second one, and so on, until all
156 back-ends have been attempted.
157
158 The list of authentication back-ends to use is specified in the
159 ``AUTHENTICATION_BACKENDS`` setting. This should be a tuple of Python path
160 names that point to Python classes that know how to authenticate. These classes
161 can be anywhere on your Python path.
162
163 By default, ``AUTHENTICATION_BACKENDS`` is set to the following::
164
165 ('django.contrib.auth.backends.ModelBackend',)
166
167 That's the basic authentication scheme that checks the Django users database.
168
169 The order of ``AUTHENTICATION_BACKENDS`` matters, so if the same username and
170 password are valid in multiple back-ends, Django will stop processing at the
171 first positive match.
172
173 Writing an Authentication Back-end
174 ----------------------------------
175
176 An authentication back-end is a class that implements two methods:
177 ``get_user(id)`` and ``authenticate(**credentials)``.
178
179 The ``get_user`` method takes an ``id`` -- which could be a username, database
180 ID, or whatever -- and returns a ``User`` object.
181
182 The ``authenticate`` method takes credentials as keyword arguments. Most of
183 the time it looks like this::
184
185 class MyBackend(object):
186 def authenticate(self, username=None, password=None):
187 # Check the username/password and return a User.
188
189 But it could also authenticate a token, like so::
190
191 class MyBackend(object):
192 def authenticate(self, token=None):
193 # Check the token and return a User.
194
195 Either way, ``authenticate`` should check the credentials it gets, and it
196 should return a ``User`` object that matches those credentials, if the
197 credentials are valid. If they're not valid, it should return ``None``.
198
199 The Django admin system is tightly coupled to Django's own database-backed
200 ``User`` object described in Chapter 12. The best way to deal with this is to
201 create a Django ``User`` object for each user that exists for your back-end
202 (e.g., in your LDAP directory, your external SQL database, etc.). Either you can
203 write a script to do this in advance or your ``authenticate`` method can do it
204 the first time a user logs in.
205
206 Here's an example back-end that authenticates against a username and password
207 variable defined in your ``settings.py`` file and creates a Django ``User``
208 object the first time a user authenticates::
209
210 from django.conf import settings
211 from django.contrib.auth.models import User, check_password
212
213 class SettingsBackend(object):
214 """
215 Authenticate against the settings ADMIN_LOGIN and ADMIN_PASSWORD.
216
217 Use the login name, and a hash of the password. For example:
218
219 ADMIN_LOGIN = 'admin'
220 ADMIN_PASSWORD = 'sha1$4e987$afbcf42e21bd417fb71db8c66b321e9fc33051de'
221 """
222 def authenticate(self, username=None, password=None):
223 login_valid = (settings.ADMIN_LOGIN == username)
224 pwd_valid = check_password(password, settings.ADMIN_PASSWORD)
225 if login_valid and pwd_valid:
226 try:
227 user = User.objects.get(username=username)
228 except User.DoesNotExist:
229 # Create a new user. Note that we can set password
230 # to anything, because it won't be checked; the password
231 # from settings.py will.
232 user = User(username=username, password='get from settings.py')
233 user.is_staff = True
234 user.is_superuser = True
235 user.save()
236 return user
237 return None
238
239 def get_user(self, user_id):
240 try:
241 return User.objects.get(pk=user_id)
242 except User.DoesNotExist:
243 return None
244
245 Integrating with Legacy Web Applications
246 ========================================
247
248 It's possible to run a Django application on the same Web server as an
249 application powered by another technology. The most straightforward way of
250 doing this is to use Apache's configuration file, ``httpd.conf``, to delegate
251 different URL patterns to different technologies. (Note that Chapter 20 covers
252 Django deployment on Apache/mod_python, so it might be worth reading that
253 chapter first before attempting this integration.)
254
255 The key is that Django will be activated for a particular URL pattern only if
256 your ``httpd.conf`` file says so. The default deployment explained in Chapter
257 20 assumes you want Django to power every page on a particular domain::
258
259 <Location "/">
260 SetHandler python-program
261 PythonHandler django.core.handlers.modpython
262 SetEnv DJANGO_SETTINGS_MODULE mysite.settings
263 PythonDebug On
264 </Location>
265
266 Here, the ``<Location "/">`` line means "handle every URL, starting at the
267 root," with Django.
268
269 It's perfectly fine to limit this ``<Location>`` directive to a certain
270 directory tree. For example, say you have a legacy PHP application that powers
271 most pages on a domain and you want to install a Django admin site at
272 ``/admin/`` without disrupting the PHP code. To do this, just set the
273 ``<Location>`` directive to ``/admin/``::
274
275 <Location "/admin/">
276 SetHandler python-program
277 PythonHandler django.core.handlers.modpython
278 SetEnv DJANGO_SETTINGS_MODULE mysite.settings
279 PythonDebug On
280 </Location>
281
282 With this in place, only the URLs that start with ``/admin/`` will activate
283 Django. Any other page will use whatever infrastructure already existed.
284
285 Note that attaching Django to a qualified URL (such as ``/admin/`` in this
286 section's example) does not affect the Django URL parsing. Django works with the
287 absolute URL (e.g., ``/admin/people/person/add/``), not a "stripped" version of
288 the URL (e.g., ``/people/person/add/``). This means that your root URLconf
289 should include the leading ``/admin/``.
290
291 What's Next?
292 ============
293
294 Speaking of the Django admin site and bending the framework to fit legacy needs,
295 another common task is to customize the Django admin site. The `next chapter`_
296 focuses on such customization.
297
298 .. _next chapter: ../chapter17/
Something went wrong with that request. Please try again.