Changes between Version 161 and Version 162 of RemovingTheMagic


Ignore:
Timestamp:
Dec 10, 2006, 1:30:47 PM (17 years ago)
Author:
JayK
Comment:

restoring page

Legend:

Unmodified
Added
Removed
Modified
  • RemovingTheMagic

    v161 v162  
    1 [http://www.sj83.com/google.htm google排名]
    2 [http://www.global-jipiao.cn/ 特价机票]
    3 [http://www.beijingyizheng.com/zhongkongban/ 塑料周转箱]
    4 [http://www.njjghg.com/user/jghg/index_cn.asp 偶联剂]
    5 [http://www.bawwgt.com/ powerleveling]
     1= Removing the magic =
     2
     3The "magic-removal" branch made several sweeping changes to the Django codebase, removing warts that Django had accumulated over the years. Most changes involved the database API and removing some of its unneeded magic, and other changes involved improving the framework's simplicity and usability.
     4
     5As of May 1, 2006, these changes have been integrated into the Django development version (Subversion's trunk), and were released officially in the following Django release, 0.95.
     6
     7This document explains all changes.
     8
     9Additional resource: the [wiki:MagicRemovalCheatSheet "magic-removal" cheat sheet], which can be used as a reference for conversion to the "magic-removal" branch. It contains links to this document organized by functional areas.
     10
     11[[TOC(inline, RemovingTheMagic)]]
     12
     13== How to get the branch ==
     14
     15If you're running Django 0.91 at the moment and want to start playing with the new, magic-removal version, just check out Django's development version using this command:
     16
     17{{{
     18svn co http://code.djangoproject.com/svn/django/trunk/
     19}}}
     20
     21If you have been working from a checkout of the magic-removal branch and want to switch your checkout over to the new trunk, cd to your magic-removal directory and then:
     22
     23{{{
     24svn switch http://code.djangoproject.com/svn/django/trunk/
     25}}}
     26
     27'''Note to Windows users:''' There have been confirmed reports of a 0.95 SVN checkout not correctly overwriting older versions. To be safe, remove your old trunk folder before checking out.
     28
     29=== Using two versions of Django side-by-side ===
     30
     31Here's one way to use Django 0.91 and the magic-removal trunk versions on the same machine. This assumes a release such as 0.90 or 0.91 is installed:
     32
     33{{{
     34# Get the development/trunk code somewhere on your filesystem. In this example, we use /home/python/django.
     35$ cd /home/python/django
     36$ svn co http://code.djangoproject.com/svn/django/trunk
     37
     38# This will have created a "trunk" directory.
     39
     40# Whenever you want to use trunk, set the environment variable {{{PYTHONPATH}}} to the directory containing trunk.
     41export PYTHONPATH=/home/python/django/trunk
     42}}}
     43
     44== Overview ==
     45
     46The biggest changes are:
     47
     48 * The magic package {{{django.models}}} no longer exists. To use models, just import the model class from wherever it lives on the Python path. Similarly, the magic modules (such as {{{django.models.polls}}} in the tutorial) no longer exist; now, you interact directly with the model class.
     49 * All automatic pluralization is gone.
     50 * The database API has changed in several ways.
     51 * Various packages, such as the Django template system (previously in {{{django.core.template}}}), have been moved around to make importing less verbose and easier to remember.
     52
     53== Database changes you'll need to make ==
     54
     55To upgrade from a previous Django installation, you'll need to make some database changes. Obviously, this doesn't apply if you're starting from scratch.
     56
     57=== Rename core database tables ===
     58
     59We've renamed a bunch of the core Django tables. Due to functionality differences between the various database backends, the SQL to perform the necessary tasks varies slightly between engines.
     60
     61To upgrade in SQLite, execute this SQL in your database (some steps are more involved because SQLite has only limited ALTER TABLE functionality. We have to instead create working tables, move the data, and then replace the old tables with the new ones:
     62
     63{{{
     64ALTER TABLE auth_groups RENAME TO auth_group;
     65ALTER TABLE auth_groups_permissions RENAME TO auth_group_permissions;
     66ALTER TABLE auth_messages RENAME TO auth_message;
     67ALTER TABLE auth_permissions RENAME TO auth_permission;
     68ALTER TABLE auth_users RENAME TO auth_user;
     69ALTER TABLE auth_users_groups RENAME TO auth_user_groups;
     70ALTER TABLE auth_users_user_permissions RENAME TO auth_user_user_permissions;
     71ALTER TABLE content_types RENAME TO django_content_type;
     72ALTER TABLE core_sessions RENAME TO django_session;
     73ALTER TABLE django_flatpages RENAME TO django_flatpage;
     74ALTER TABLE django_flatpages_sites RENAME TO django_flatpage_sites;
     75ALTER TABLE django_redirects RENAME TO django_redirect;
     76ALTER TABLE sites RENAME TO django_site;
     77
     78CREATE TABLE "django_content_type_new"  (
     79"id" integer NOT NULL PRIMARY KEY,
     80"name" varchar(100) NOT NULL,
     81"app_label" varchar(100) NOT NULL,
     82"model" varchar(100) NOT NULL,
     83UNIQUE ("app_label", "model")
     84);
     85
     86INSERT INTO django_content_type_new
     87(id,name,app_label,model)
     88SELECT id,name,package,python_module_name
     89FROM django_content_type;
     90
     91
     92ALTER TABLE django_content_type rename to django_content_type_old;
     93ALTER TABLE django_content_type_new rename to django_content_type;
     94
     95DROP TABLE django_content_type_old;
     96
     97DROP TABLE packages;
     98
     99
     100ALTER TABLE auth_permission ADD COLUMN content_type_id INTEGER;
     101}}}
     102
     103To upgrade in MySQL , execute this SQL in your database:
     104
     105{{{
     106ALTER TABLE auth_groups RENAME TO auth_group;
     107ALTER TABLE auth_groups_permissions RENAME TO auth_group_permissions;
     108ALTER TABLE auth_messages RENAME TO auth_message;
     109ALTER TABLE auth_permissions RENAME TO auth_permission;
     110ALTER TABLE auth_users RENAME TO auth_user;
     111ALTER TABLE auth_users_groups RENAME TO auth_user_groups;
     112ALTER TABLE auth_users_user_permissions RENAME TO auth_user_user_permissions;
     113ALTER TABLE content_types RENAME TO django_content_type;
     114ALTER TABLE core_sessions RENAME TO django_session;
     115ALTER TABLE django_flatpages RENAME TO django_flatpage;
     116ALTER TABLE django_flatpages_sites RENAME TO django_flatpage_sites;
     117ALTER TABLE django_redirects RENAME TO django_redirect;
     118ALTER TABLE sites RENAME TO django_site;
     119DROP TABLE packages;
     120ALTER TABLE django_content_type CHANGE package app_label VARCHAR(100) NOT NULL,
     121  CHANGE python_module_name model VARCHAR(100) NOT NULL;
     122ALTER TABLE auth_permission ADD COLUMN content_type_id INTEGER;
     123}}}
     124
     125PostgreSQL is different, because you have to rename sequences, too. To upgrade in PostgreSQL, execute this SQL in your database:
     126
     127{{{
     128BEGIN;
     129
     130ALTER TABLE auth_groups RENAME TO auth_group;
     131ALTER TABLE auth_groups_id_seq RENAME TO auth_group_id_seq;
     132ALTER TABLE auth_group ALTER COLUMN id DROP DEFAULT;
     133ALTER TABLE auth_group ALTER COLUMN id SET DEFAULT nextval('public.auth_group_id_seq'::text);
     134
     135ALTER TABLE auth_groups_permissions RENAME TO auth_group_permissions;
     136ALTER TABLE auth_groups_permissions_id_seq RENAME TO auth_group_permissions_id_seq;
     137ALTER TABLE auth_group_permissions ALTER COLUMN id DROP DEFAULT;
     138ALTER TABLE auth_group_permissions ALTER COLUMN id SET DEFAULT nextval('public.auth_group_permissions_id_seq'::text);
     139
     140ALTER TABLE auth_messages RENAME TO auth_message;
     141ALTER TABLE auth_messages_id_seq RENAME TO auth_message_id_seq;
     142ALTER TABLE auth_message ALTER COLUMN id DROP DEFAULT;
     143ALTER TABLE auth_message ALTER COLUMN id SET DEFAULT nextval('public.auth_message_id_seq'::text);
     144
     145ALTER TABLE auth_permissions RENAME TO auth_permission;
     146ALTER TABLE auth_permissions_id_seq RENAME TO auth_permission_id_seq;
     147ALTER TABLE auth_permission ALTER COLUMN id DROP DEFAULT;
     148ALTER TABLE auth_permission ALTER COLUMN id SET DEFAULT nextval('public.auth_permission_id_seq'::text);
     149
     150ALTER TABLE auth_users RENAME TO auth_user;
     151ALTER TABLE auth_users_id_seq RENAME TO auth_user_id_seq;
     152ALTER TABLE auth_user ALTER COLUMN id DROP DEFAULT;
     153ALTER TABLE auth_user ALTER COLUMN id SET DEFAULT nextval('public.auth_user_id_seq'::text);
     154
     155ALTER TABLE auth_users_groups RENAME TO auth_user_groups;
     156ALTER TABLE auth_users_groups_id_seq RENAME TO auth_user_groups_id_seq;
     157ALTER TABLE auth_user_groups ALTER COLUMN id DROP DEFAULT;
     158ALTER TABLE auth_user_groups ALTER COLUMN id SET DEFAULT nextval('public.auth_user_groups_id_seq'::text);
     159
     160ALTER TABLE auth_users_user_permissions RENAME TO auth_user_user_permissions;
     161ALTER TABLE auth_users_user_permissions_id_seq RENAME TO auth_user_user_permissions_id_seq;
     162ALTER TABLE auth_user_user_permissions ALTER COLUMN id DROP DEFAULT;
     163ALTER TABLE auth_user_user_permissions ALTER COLUMN id SET DEFAULT nextval('public.auth_user_user_permissions_id_seq'::text);
     164
     165ALTER TABLE content_types RENAME TO django_content_type;
     166ALTER TABLE content_types_id_seq RENAME TO django_content_type_id_seq;
     167ALTER TABLE django_content_type ALTER COLUMN id DROP DEFAULT;
     168ALTER TABLE django_content_type ALTER COLUMN id SET DEFAULT nextval('public.django_content_type_id_seq'::text);
     169
     170ALTER TABLE core_sessions RENAME TO django_session;
     171
     172ALTER TABLE django_flatpages RENAME TO django_flatpage;
     173ALTER TABLE django_flatpages_id_seq RENAME TO django_flatpage_id_seq;
     174ALTER TABLE django_flatpage ALTER COLUMN id DROP DEFAULT;
     175ALTER TABLE django_flatpage ALTER COLUMN id SET DEFAULT nextval('public.django_flatpage_id_seq'::text);
     176
     177ALTER TABLE django_flatpages_sites RENAME TO django_flatpage_sites;
     178ALTER TABLE django_flatpages_sites_id_seq RENAME TO django_flatpage_sites_id_seq;
     179ALTER TABLE django_flatpage_sites ALTER COLUMN id DROP DEFAULT;
     180ALTER TABLE django_flatpage_sites ALTER COLUMN id SET DEFAULT nextval('public.django_flatpage_sites_id_seq'::text);
     181
     182ALTER TABLE django_redirects RENAME TO django_redirect;
     183ALTER TABLE django_redirects_id_seq RENAME TO django_redirect_id_seq;
     184ALTER TABLE django_redirect ALTER COLUMN id DROP DEFAULT;
     185ALTER TABLE django_redirect ALTER COLUMN id SET DEFAULT nextval('public.django_redirect_id_seq'::text);
     186
     187ALTER TABLE auth_permission DROP COLUMN package;
     188
     189-- First try this:
     190ALTER TABLE django_content_type DROP CONSTRAINT "content_types_package_fkey";
     191-- If that didn't work, do this:
     192ALTER TABLE django_content_type DROP CONSTRAINT "$1";
     193
     194ALTER TABLE django_content_type ADD COLUMN app_label varchar(100);
     195UPDATE django_content_type SET app_label=package;
     196ALTER TABLE django_content_type ALTER COLUMN app_label SET NOT NULL;
     197ALTER TABLE django_content_type DROP COLUMN package;
     198
     199DROP TABLE packages;
     200
     201ALTER TABLE sites RENAME TO django_site;
     202ALTER TABLE sites_id_seq RENAME TO django_site_id_seq;
     203ALTER TABLE django_site ALTER COLUMN id DROP DEFAULT;
     204ALTER TABLE django_site ALTER COLUMN id SET DEFAULT nextval('public.django_site_id_seq'::text);
     205
     206ALTER TABLE django_content_type rename python_module_name to model;
     207
     208ALTER TABLE auth_permission ADD COLUMN content_type_id INTEGER references django_content_type(id);
     209
     210COMMIT;
     211}}}
     212
     213=== Changes to the SQL for the comments app ===
     214
     215The comments app, still undocumented, also requires some database changes.
     216
     217In PostgreSQL:
     218{{{
     219BEGIN;
     220ALTER TABLE comments RENAME TO comments_comment;
     221ALTER TABLE comments_id_seq RENAME TO comments_comment_id_seq;
     222
     223ALTER TABLE comments_karma RENAME TO comments_karmascore;
     224ALTER TABLE comments_karma_id_seq RENAME TO comments_karmascore_id_seq;
     225
     226ALTER TABLE comments_free RENAME TO comments_freecomment;
     227ALTER TABLE comments_free_id_seq RENAME TO comments_freecomment_id_seq;
     228
     229ALTER TABLE comments_moderator_deletions RENAME TO comments_moderatordeletion;
     230ALTER TABLE comments_moderator_deletions_id_seq RENAME TO comments_moderatordeletion_id_seq;
     231
     232ALTER TABLE comments_user_flags RENAME TO comments_userflag;
     233ALTER TABLE comments_user_flags_id_seq RENAME TO comments_userflag_id_seq;
     234
     235COMMIT;
     236}}}
     237
     238In other databases:
     239{{{
     240ALTER TABLE comments RENAME TO comments_comment;
     241ALTER TABLE comments_karma RENAME TO comments_karmascore;
     242ALTER TABLE comments_free RENAME TO comments_freecomment;
     243ALTER TABLE comments_moderator_deletions RENAME TO comments_moderatordeletion;
     244ALTER TABLE comments_user_flags RENAME TO comments_userflag;
     245}}}
     246
     247=== Auth_permissions case changes ===
     248
     249Changeset [2745] changed the {{{verbose_name}}}s of two models in the {{{django.contrib.auth}}} app -- {{{Group}}} and {{{User}}} -- to be lowercased instead of uppercased ({{{"users"}}} instead of {{{"Users"}}}). This breaks the {{{syncdb}}} function of manage.py due to the disparity in existing installations of the magic-removal branch. To fix your {{{auth_permissions}}} table, execute these SQL statements:
     250
     251{{{
     252BEGIN;
     253UPDATE auth_permission SET name='Can add group' WHERE codename='add_group';
     254UPDATE auth_permission SET name='Can change group' WHERE codename='change_group';
     255UPDATE auth_permission SET name='Can delete group' WHERE codename='delete_group';
     256UPDATE auth_permission SET name='Can add user' WHERE codename='add_user';
     257UPDATE auth_permission SET name='Can change user' WHERE codename='change_user';
     258UPDATE auth_permission SET name='Can delete user' WHERE codename='delete_user';
     259
     260UPDATE django_content_type SET name='group' WHERE model='groups';
     261UPDATE django_content_type SET name='user' WHERE model='users';
     262COMMIT;
     263}}}
     264
     265=== django_content_type table changes ===
     266
     267For every record in the {{{django_content_type}}} table (formerly {{{content_types}}}), rename the {{{model}}} field so that it's a singular version of the word. For example:
     268
     269{{{
     270UPDATE django_content_type SET model='group' WHERE model='groups';
     271UPDATE django_content_type SET model='user' WHERE model='users';
     272UPDATE django_content_type SET model='flatpage' WHERE model='flatpagess';
     273}}}
     274
     275=== SQLite: Drop Auth_permission.Package column ===
     276
     277'''User 'Rajesh Dhawan' notes that, in SQLite, the Foreign Key reference on column {{{package}}} from the {{{auth_permissions}}} table still causes problems after following all the above database conversions. Executing the following conversion routine eliminates the {{{package}}} column (SQLite doesn't support dropping of columns):'''
     278
     279{{{
     280CREATE TABLE "auth_permission_new" (
     281    "id" integer NOT NULL PRIMARY KEY,
     282    "name" varchar(50) NOT NULL,
     283    "content_type_id" integer,
     284    "codename" varchar(100) NOT NULL,
     285    UNIQUE ("content_type_id", "codename")
     286);
     287
     288
     289insert into auth_permission_new
     290(id, name, codename)
     291select id, name, codename
     292from auth_permission;
     293
     294alter table auth_permission rename to auth_permission_old;
     295alter table auth_permission_new rename to auth_permission;
     296
     297drop table auth_permission_old;
     298}}}
     299
     300=== Entries in your Content Types table will now be wrong ===
     301
     302If you've been using the comments system, your comments will be related back to the objects via the content types table. These won't work after your upgrade because of the pluralization discrepancy between the new and the old: you'll get failures in {{{ContentType.get_object_for_this_type}}} complaining that {{{None}}} doesn't have a {{{_default_manager}}} attribute, and when you fix the arguments to {{{comment_form}}} and {{{get_comment_list}}} arguments you'll get more breakage because the {{{django_content_type}}} table is now out of whack. If you can't be bothered writing your own SQL to fix this, try [http://django.pastebin.com/762068 fix_content_types.py]; it seems to work for me. - [mailto:garth@deadlybloodyserious.com Garth].
     303
     304=== Database table-naming scheme has been changed ===
     305
     306Database table names formerly were created by joining the {{{app_label}}} and {{{module_name}}}. Example: {{{polls_polls}}}.
     307
     308Because there's no longer any concept of {{{module_name}}}, database table names are now formed by joining the {{{app_label}}} and model class name (lower case). Example: {{{polls_poll}}}.
     309
     310As always, this behavior can be overridden on a per-model basis by specifying the {{{db_table}}} attribute in {{{class Meta}}} in your model.
     311
     312To upgrade, you'll either have to explicitly set {{{db_table}}} in your models or rename your database tables to fit the new naming scheme Django expects. We'd recommend setting {{{db_table}}}, because it's easier.
     313
     314== Code changes you'll need to make ==
     315
     316=== Model class and Field classes renamed/relocated ===
     317
     318Change your models to import from {{{django.db.models}}} instead of {{{django.core.meta}}}.
     319
     320{{{
     321#!python
     322from django.db import models
     323
     324class Person(models.Model):
     325    first_name = models.CharField(maxlength=30)
     326    last_name = models.CharField(maxlength=30)
     327}}}
     328
     329=== Interact directly with model classes, not with magic modules ===
     330
     331Import the model class directly from the module in which it was defined. No more {{{django.models.*}}} magic.
     332
     333{{{
     334#!python
     335from myproject.people.models import Person
     336p = Person(first_name='John', last_name='Smith')
     337p.save()
     338}}}
     339
     340=== Include template extension explicitly ===
     341
     342Now, whenever referencing templates by name, you now pass the template's *full name* -- including its extension -- instead of the file's name without an extension.
     343
     344Given a template {{{polls/poll_detail.html}}}...
     345
     346Old:
     347{{{
     348get_template('polls/poll_detail')
     349select_template(['polls/poll_detail', 'polls/poll_detail2'])
     350render_to_response('polls/poll_detail', {'poll': p})
     351}}}
     352
     353New:
     354
     355{{{
     356get_template('polls/poll_detail.html')
     357select_template(['polls/poll_detail.html', 'polls/poll_detail2.html'])
     358render_to_response('polls/poll_detail.html', {'poll': p})
     359}}}
     360
     361This is changed in templates themselves, too -- notably in the {{{ {% extends %} }}} and {{{ {% include %} }}} template tags:
     362
     363Old:
     364{{{
     365{% extends "base" %}
     366{% include "some_snippet" %}
     367}}}
     368
     369New:
     370{{{
     371{% extends "base.html" %}
     372{% include "some_snippet.html" %}
     373}}}
     374
     375As a result of this change, the {{{TEMPLATE_FILE_EXTENSION}}} setting is gone, because there's no need for it.
     376
     377Clearly, this also means you can start putting whatever extension you want on templates. So if you'd like your templates to have a {{{.txt}}} extension, go for it. If you'd like to keep using {{{.html}}}, that's fine too. If you want to invent your own extension, or not even *use* an extension, be our guest.
     378
     379All the templates for the {{{django.contrib}}} apps -- most notably, the admin site -- still use {{{.html}}} extensions.
     380
     381Custom template tags created via {{{inclusion_tag()}}} should note the explicit template name (with the extension). For example, use {{{inclusion_tag('foo/bar.html')}}} instead of {{{inclusion_tag('foo/bar')}}}.
     382
     383If you're using custom {{{template_name}}} arguments to generic views, don't forget to change those calls in your URLconf, too.
     384
     385Finally, note the syndication framework, which looks for templates with the name of the slug of each feed, now requires an '.html' extension on those templates. For example, if your feed has a slug {{{"hello"}}}, the syndication framework will look for the templates {{{feeds/hello_title.html}}} and {{{feeds/hello_description.html}}}. This is backwards-compatible.
     386
     387=== Namespace simplification ===
     388
     389{{{django.utils.httpwrappers}}} has moved to {{{django.http}}}.
     390
     391{{{django.core.exceptions.Http404}}} has moved to {{{django.http.Http404}}}.
     392
     393{{{django.core.template}}} has moved to {{{django.template}}}.
     394
     395{{{django.core.formfields}}} has moved to {{{django.forms}}}.
     396
     397{{{django.core.extensions}}} has moved to {{{django.shortcuts}}}.
     398
     399{{{django.core.extensions.DjangoContext}}} has been renamed to {{{RequestContext}}} and moved to {{{django.template.RequestContext}}}.
     400
     401You'll need to remove ".core." from your {{{TEMPLATE_LOADERS}}} settings values. Old:
     402{{{
     403#!python
     404TEMPLATE_LOADERS = (
     405    'django.core.template.loaders.filesystem.load_template_source',
     406    'django.core.template.loaders.app_directories.load_template_source',
     407#    'django.core.template.loaders.eggs.load_template_source',
     408)
     409}}}
     410
     411New:
     412
     413{{{
     414#!python
     415TEMPLATE_LOADERS = (
     416    'django.template.loaders.filesystem.load_template_source',
     417    'django.template.loaders.app_directories.load_template_source',
     418#    'django.template.loaders.eggs.load_template_source',
     419)
     420}}}
     421
     422Custom template tag definitions need a similar change. Old:
     423{{{
     424#!python
     425from django.core import template
     426register = template.Library()
     427}}}
     428
     429New:
     430{{{
     431#!python
     432from django.template import Library
     433register = Library()
     434}}}
     435
     436The "auth" and "core" models have been split and moved to {{{django.contrib}}} as follows:
     437
     438    * {{{django.models.auth}}} has moved to {{{django.contrib.auth.models}}}.
     439    * {{{django.models.core.sites}}} has moved to {{{django.contrib.sites.models}}}.
     440    * {{{django.models.core.contenttypes}}} has moved to {{{django.contrib.contenttypes.models}}}.
     441    * {{{django.models.core.packages}}} has moved to {{{django.contrib.contenttypes.models}}}. (Note that "packages" are going away before magic-removal is done.)
     442
     443Session middleware has moved from {{{django.middleware.sessions.SessionMiddleware}}} to {{{django.contrib.sessions.middleware.SessionMiddleware}}}. Make sure to update your {{{MIDDLEWARE_CLASSES}}} setting, if you're using sessions.
     444
     445If you get the following errors upon starting the webserver:
     446{{{
     447admin.logentry: 'user' has relation with model User, which has not been installed
     448admin.logentry: 'content_type' has relation with model ContentType, which has not been installed
     449}}}
     450you need to add the apps {{{'django.contrib.contenttypes'}}} and {{{'django.contrib.auth'}}} to your {{{INSTALLED_APPS}}}.
     451
     452
     453Also, the {{{Session}}} model has moved from django/models/core.py to django/contrib/sessions/models.py. If you're accessing the {{{Session}}} model for some reason, note that location change.
     454
     455=== The 'packages' module is no more ===
     456
     457Packages no longer exist (they were redundant).  If you've done lookups against content-types or permissions you'll need to modify your code slightly:
     458
     459|| Old                                                      || New                                                    ||
     460|| {{{contenttypes.get_list(package__label__exact='foo')}}} || {{{ContentType.objects.filter(package__exact='foo')}}} ||
     461|| {{{permissions.get_list(package__label__exact='foo')}}}  || {{{Permission.objects.filter(package__exact='foo')}}}  ||
     462   
     463=== Model location changed ===
     464
     465In previous Django versions, models lived in a {{{models/}}} subdirectory of your app package. Now, they should live in a file {{{models.py}}} directly within your app package.
     466
     467=== Changes to model syntax ===
     468
     469 * {{{class META}}} should now be {{{class Meta}}}. The latter is easier on the eyes.
     470
     471 * The following are no longer valid parameters to {{{class Meta}}} and should be removed:
     472    * {{{module_name}}}
     473    * {{{admin}}} (See "Moved admin options to 'class Admin'" below.)
     474    * {{{exceptions}}} (Just put your exceptions in the module that contains the models and access them normally.)
     475    * {{{module_constants}}} (Just put your constants in the module that contains the models and access them normally.)
     476    * {{{where_constraints}}} (Just use a custom manager. See "Custom managers, and multiple managers" below.)
     477
     478=== Moved admin options to 'class Admin' ===
     479
     480Instead of {{{admin=meta.Admin}}} in the {{{class META}}}, all admin options are in an inner {{{class Admin}}}.
     481
     482Old:
     483{{{
     484#!python
     485class Person(meta.Model):
     486    first_name = meta.CharField(maxlength=30)
     487    last_name = meta.CharField(maxlength=30)
     488    class META:
     489        admin = meta.Admin(
     490            list_display = ('first_name', 'last_name')
     491        )
     492}}}
     493
     494New:
     495{{{
     496#!python
     497class Person(models.Model):
     498    first_name = models.CharField(maxlength=30)
     499    last_name = models.CharField(maxlength=30)
     500    class Admin:
     501        list_display = ('first_name', 'last_name')
     502}}}
     503
     504If you're using the admin interface but aren't specifying any admin options, just put a {{{pass}}} in that inner class.
     505
     506Old:
     507{{{
     508#!python
     509class Person(meta.Model):
     510    first_name = meta.CharField(maxlength=30)
     511    last_name = meta.CharField(maxlength=30)
     512    class META:
     513        admin = meta.Admin()
     514}}}
     515
     516New:
     517{{{
     518#!python
     519class Person(models.Model):
     520    first_name = models.CharField(maxlength=30)
     521    last_name = models.CharField(maxlength=30)
     522    class Admin:
     523        pass
     524}}}
     525
     526=== `__repr__()` replaced by `__str__()` ===
     527
     528You should use `__str__()` where `__repr__()` was formerly used, i.e., as a string representation of the model.
     529
     530`__repr__()` returns to its standard Python purpose of returning a string suitable for debugging purposes (e.g., `<ObjectName more_info>`); unless you want to customize the returned string, explicitly overriding `__repr__` in a model class is unnecessary.
     531
     532For example::
     533
     534{{{
     535class BlogPost(models.Model):
     536
     537    [...]
     538
     539    def __repr__(self):
     540        return '''<BlogPost title="%s" comments_enabled=%s>''' % (self.title, self.comments_enabled)
     541
     542    def __str__(self):
     543        return self.title
     544}}}
     545
     546
     547=== Database connection relocated/renamed ===
     548
     549For any code that uses the raw database connection, use {{{django.db.connection}}} instead of {{{django.core.db.db}}}.
     550
     551Old:
     552{{{
     553#!python
     554from django.core.db import db
     555cursor = db.cursor()
     556}}}
     557
     558New:
     559{{{
     560#!python
     561from django.db import connection
     562cursor = connection.cursor()
     563
     564
     565}}}
     566
     567Backend-specific functions, if you should need them, are available at {{{django.db.backend}}}.
     568
     569Old:
     570{{{
     571#!python
     572from django.core import db
     573db.quote_name('foo')
     574}}}
     575
     576New:
     577{{{
     578#!python
     579from django.db import backend
     580backend.quote_name('foo')
     581}}}
     582
     583Also, the various backend functionality has been split into three separate modules for each backend -- {{{base.py}}}, {{{creation.py}}} and {{{introspection.py}}}. This is purely for performance and memory savings, so that basic, everyday Django usage doesn't have to load the introspective functionality into memory.
     584
     585=== `datetime` and `db` modules must be imported explicitly ===
     586
     587Formerly, each model method magically had access to the {{{datetime}}} module and to the variable {{{db}}}, which represents the current database connection. Now, those have to be imported explicitly.
     588
     589Old:
     590{{{
     591#!python
     592    def some_method(self):
     593        print datetime.datetime.now()
     594        cursor = db.cursor()
     595        cursor.execute("UPDATE something;")
     596}}}
     597
     598New:
     599{{{
     600#!python
     601import datetime
     602from django.db import connection
     603
     604# ...
     605
     606    def some_method(self):
     607        print datetime.datetime.now()
     608        cursor = connection.cursor()
     609        cursor.execute("UPDATE something;")
     610}}}
     611
     612=== Descriptor fields ===
     613
     614All "table-level" functions -- ways of retrieving records tablewide rather than performing instance-specific tasks -- are now accessed via a model class's {{{objects}}} attribute. They aren't direct methods of a model instance object because we want to keep the "table-wide" and "row-specific" namespaces separate.
     615
     616A model class's {{{objects}}} attribute is an instance of {{{django.db.models.manager.Manager}}}. A manager has the following methods, all of which return a {{{QuerySet}}} instance.
     617
     618    * {{{all()}}} -- Returns a {{{QuerySet}}} of all objects in the database. This is like the old {{{get_list()}}}. Takes no arguments.
     619    * {{{filter(**kwargs)}}} -- Returns a {{{QuerySet}}}, filtered by the given keyword arguments. Lookup arguments are in the same style as previously, e.g. {{{pubdate__year=2005}}}, except you can leave off {{{__exact}}} as a convenience. For example, {{{name='John'}}} and {{{name__exact='John'}}} are equivalent. Note that for lookups between applications you can't omit {{{__exact}}}.
     620    * {{{exclude(**kwargs)}}} is the same as {{{filter()}}}, but returns objects where the given arguments are not true.
     621    * {{{order_by(*fieldnames)}}} -- Returns a {{{QuerySet}}}
     622    * {{{count()}}} -- Returns the count of all objects in the database.
     623    * {{{dates(field_name, kind)}}} -- Like the old {{{get_FIELD_list()}}} for date fields. For example, old-school {{{get_pubdate_list('year')}}} is now {{{dates('pubdate', 'year')}}}.
     624    * {{{delete()}}} -- Deletes all objects.
     625    * {{{distinct()}}} -- Returns a {{{QuerySet}}} with DISTINCT set.
     626    * {{{extra(select=None, where=None, params=None, tables=None)}}} -- Sets the {{{select}}}, {{{where}}}, {{{params}}} and {{{tables}}} arguments, which are in the same format as before.
     627    * {{{get(**kwargs)}}} -- Like the old {{{get_object()}}}. Returns an object or raises {{{DoesNotExist}}} on error.
     628    * {{{in_bulk(id_list)}}} -- Like the old {{{get_in_bulk()}}}.
     629    * {{{iterator()}}} -- Returns a generator that iterators over results.
     630    * {{{select_related()}}} -- Returns a {{{QuerySet}}} with the "select related" option (which acts the same as before) set.
     631    * {{{values(*fieldnames)}}} -- Like the old {{{get_values()}}}.
     632
     633Each {{{QuerySet}}} has the following methods, which return a clone of the query set with the appropriate changes made:
     634
     635    * {{{filter(**kwargs)}}}
     636    * {{{order_by(*fieldnames)}}}
     637    * {{{iterator()}}}
     638    * {{{count()}}}
     639    * {{{get(**kwargs)}}}
     640    * {{{delete()}}}
     641    * {{{filter(**kwargs)}}}
     642    * {{{select_related()}}}
     643    * {{{order_by(*fieldnames)}}}
     644    * {{{distinct()}}}
     645    * {{{extra(select=None, where=None, params=None, tables=None)}}}
     646
     647Here are some examples, which use the following models:
     648
     649{{{
     650#!python
     651class Reporter(models.Model):
     652    fname = models.CharField(maxlength=30)
     653    lname = models.CharField(maxlength=30)
     654
     655class Site(models.Model):
     656    name = models.CharField(maxlength=20)
     657
     658class Article(models.Model):
     659    headline = models.CharField(maxlength=50)
     660    reporter = models.ForeignKey(Reporter)
     661    pub_date = models.DateField()
     662
     663
     664    sites = models.ManyToManyField(Site)
     665}}}
     666
     667|| '''Old syntax'''                                       || '''New syntax'''                             ||
     668|| {{{reporters.get_list()}}}                             || {{{Reporter.objects.all()}}}                       ||
     669|| {{{reporters.get_list(fname__exact='John')}}}          || {{{Reporter.objects.filter(fname='John')}}}        ||
     670|| {{{reporters.get_list(order_by=('-lname', 'fname'))}}} || {{{Reporter.objects.order_by('-lname', 'fname')}}} ||
     671|| {{{reporters.get_list(fname__exact='John', order_by=('lname',))}}} || {{{Reporter.objects.filter(fname='John').order_by('lname')}}} ||
     672|| {{{reporters.get_object(pk=3)}}}                       || {{{Reporter.objects.get(pk=3)}}}                   ||
     673|| {{{reporters.get_object(complex=(Q(...)|Q(...)))}}}    || {{{Reporter.objects.get(Q(...)|Q(...))}}}
     674||                     
     675|| {{{reporters.get_object(fname__contains='John')}}}     || {{{Reporter.objects.get(fname__contains='John')}}} ||
     676|| {{{reporters.get_list(fname__ne='John')}}}             || {{{Reporter.objects.exclude(fname='John')}}} (note that {{{ne}}} is no longer a valid lookup type) ||
     677|| (not previously possible)             || {{{Reporter.objects.exclude(fname__contains='n')}}} ||
     678|| {{{reporters.get_list(distinct=True)}}}                || {{{Reporter.objects.distinct()}}} ||
     679|| {{{reporters.get_list(offset=10, limit=5)}}}           || {{{Reporter.objects.all()[10:15]}}}||
     680|| {{{reporters.get_values()}}}                           || {{{Reporter.objects.values()}}} ||
     681|| {{{reporters.get_in_bulk([1, 2])}}}                    || {{{Reporter.objects.in_bulk([1, 2])}}} ||
     682|| {{{reporters.get_in_bulk([1, 2], fname__exact='John')}}} || {{{Reporter.objects.filter(fname='John').in_bulk([1, 2])}}} ||
     683|| '''Date lookup'''                                        ||                                              ||
     684|| {{{articles.get_pub_date_list('year')}}}                 || {{{Article.objects.dates('pub_date', 'year')}}} ||
     685|| '''Latest-object lookup'''                               ||                                               ||
     686|| {{{articles.get_latest()}}} (required {{{get_latest_by}}} in model) || {{{Article.objects.latest()}}} (with {{{get_latest_by}}} in model) ||
     687|| (Not previously possible)                                     || {{{Article.objects.latest('pub_date')}}} # Latest by pub_date (overrides {{{get_latest_by}}} field in model) ||
     688|| '''Many-to-one related lookup'''                        ||                                              ||
     689|| {{{article_obj.reporter_id}}}                                 || {{{article_obj.reporter.id}}} ||
     690|| {{{article_obj.get_reporter()}}}                              || {{{article_obj.reporter}}}    ||
     691|| {{{reporter_obj.get_article_list()}}}                         || {{{reporter_obj.article_set.all()}}} ||
     692|| {{{reporter_obj.get_article_list(headline__exact='Hello')}}}  || {{{reporter_obj.article_set.filter(headline='Hello')}}} ||
     693|| {{{reporter_obj.get_article_count()}}}                        || {{{reporter_obj.article_set.count()}}} ||
     694|| {{{reporter_obj.add_article(headline='Foo')}}}                || {{{reporter_obj.article_set.create(headline='Foo')}}} ||
     695|| (Alternate syntax)                                      || {{{reporter_obj.article_set.add(article_obj)}}} ||
     696|| ("values" lookup, etc., not previously possible)        || {{{reporter_obj.article_set.values()}}} ||
     697|| '''Many-to-many related lookup'''                       ||                         ||
     698|| {{{article_obj.get_site_list()}}}                            || {{{article_obj.sites.all()}}} ||
     699|| {{{article_obj.set_sites([s1.id, s2.id])}}}                   || {{{article_obj.sites.clear(); article_obj.sites.add(s1); article_obj.sites.add(s2)}}} ||
     700|| {{{article_obj.set_sites([s1.id]) # deletion}}}               || {{{article_obj.sites.remove(s2)}}} ||
     701|| {{{site_obj.get_article_list()}}}                            || {{{site_obj.article_set.all()}}} ||
     702
     703Note that related-object lookup uses the default manager of the related object, which means the API for accessing related objects is completely consistent with the API for accessing objects via a manager.
     704
     705Also note that managers can't be accessed from instances:
     706
     707{{{
     708#!python
     709p = Person.objects.get(pk=1)
     710p.objects.all() # Raises AttributeError
     711}}}
     712
     713=== Override default manager name ('objects') ===
     714
     715If a model already has an {{{objects}}} attribute, you'll need to specify an alternate name for the {{{objects}}} manager.
     716
     717{{{
     718#!python
     719class Person(models.Model):
     720    first_name = models.CharField(maxlength=30)
     721    last_name = models.CharField(maxlength=30)
     722    objects = models.TextField()
     723    people = models.Manager()
     724
     725p = Person(first_name='Mary', last_name='Jones', objects='Hello there.')
     726p.save()
     727p.objects == 'Hello there.'
     728Person.people.all()
     729}}}
     730
     731=== Custom managers, and multiple managers ===
     732
     733You can create as many managers as you want. When necessary (such as on the admin), Django will use the first one defined, in order.
     734
     735If you define at least one custom manager, it will not get the default "objects" manager.
     736
     737{{{
     738#!python
     739class Person(models.Model):
     740    first_name = models.CharField(maxlength=30)
     741    last_name = models.CharField(maxlength=30)
     742    people = models.Manager()
     743    fun_people = SomeOtherManager()
     744}}}
     745
     746If a manager needs to access its associated model class, it should use {{{self.model}}}. Example:
     747
     748{{{
     749#!python
     750class PersonManager(models.Manager):
     751    def get_fun_person(self):
     752        try:
     753            return self.get(fun=True)
     754        except self.model.DoesNotExist:
     755            print "Doesn't exist."
     756}}}
     757
     758==== Using a custom manager for the admin ====
     759
     760Sometimes you'll want to use a different manager for the admin displays (e.g. to display only objects matching some criteria in the admin).  You can do this by defining the {{{manager}}} option in your {{{Admin}}} declaration:
     761
     762{{{
     763#!python
     764
     765
     766class LivingPeopleManager(models.Manager):
     767    def get_query_set(self):
     768        return super(LivingPeopleManager, self).get_query_set().filter(is_alive=True)
     769
     770class Person(models.Model):
     771    name = models.CharField(maxlength=50)
     772    is_alive = models.BooleanField()
     773   
     774    class Admin:
     775        manager = LivingPeopleManager()
     776
     777}}}
     778
     779(see "You can override default QuerySets" for more on QuerySets)
     780
     781=== Overriding save() and delete() model methods ===
     782
     783Proper subclassing of methods now works, so you can subclass the automatic {{{save()}}} and {{{delete()}}} methods. This removes the need for the {{{_pre_save()}}}, {{{_post_save()}}}, {{{_pre_delete()}}} and {{{_post_delete()}}} hooks -- all of which have been removed. Example:
     784
     785{{{
     786#!python
     787class Person(models.Model):
     788    first_name = models.CharField(maxlength=30)
     789    last_name = models.CharField(maxlength=30)
     790
     791    def save(self):
     792        self.do_something()
     793        super(Person, self).save() # Call the "real" save() method.
     794        self.do_something_else()
     795}}}
     796
     797You can even skip saving (as requested in #1014).
     798
     799{{{
     800#!python
     801class Person(models.Model):
     802    first_name = models.CharField(maxlength=30)
     803    last_name = models.CharField(maxlength=30)
     804
     805    def save(self):
     806        if datetime.date.today() > datetime.date(2005, 1, 1):
     807            super(Person, self).save() # Call the "real" save() method.
     808        else:
     809            # Don't save.
     810            pass
     811}}}
     812
     813=== Renamed !DoesNotExist exception ===
     814
     815Instead of {{{people.PersonDoesNotExist}}}, it's {{{Person.DoesNotExist}}}.
     816
     817Old:
     818{{{
     819#!python
     820from django.models.myapp import people
     821try:
     822    people.get_object(pk=1)
     823except people.PersonDoesNotExist:
     824    print "Not there"
     825}}}
     826
     827New:
     828{{{
     829#!python
     830from path.to.myapp.models import Person
     831try:
     832    Person.objects.get(pk=1)
     833except Person.DoesNotExist:
     834    print "Not there"
     835}}}
     836
     837=== Moved admin URLconf to shorten its path ===
     838
     839You'll need to change your URLconf to {{{include}}} the new location.
     840
     841 * Old: {{{django.contrib.admin.urls.admin}}}
     842 * New: {{{django.contrib.admin.urls}}}
     843
     844=== get_object_or_404 and get_list_or_404 take model classes ===
     845
     846Formerly, these helper methods took a model module as their first positional argument. Now, they expect a model class.
     847
     848Old:
     849{{{
     850#!python
     851get_object_or_404(polls, pk=1)
     852}}}
     853
     854New:
     855{{{
     856#!python
     857get_object_or_404(Poll, pk=1)
     858}}}
     859
     860
     861=== Changed the parameters you pass to generic views ===
     862
     863Because there's no longer a concept of {{{module_name}}}, the "info_dicts" passed to [http://www.djangoproject.com/documentation/generic_views/ generic views] no longer accept {{{"app_label"}}} and {{{"module_name"}}}. Instead, pass the parameter {{{"queryset"}}}, which should be a {{{QuerySet}}} instance.
     864
     865Old:
     866{{{
     867#!python
     868info_dict = {
     869    'app_label': 'blog',
     870    'module_name': 'entries'
     871}
     872}}}
     873
     874New:
     875{{{
     876#!python
     877from myproject.blog.models import Entry
     878info_dict = {'queryset': Entry.objects.all()}
     879}}}
     880
     881=== Changed template names in generic views ===
     882
     883Because there's no longer a concept of {{{module_name}}}, [http://www.djangoproject.com/documentation/generic_views/ generic views] no longer create templates based on the {{{module_name}}}. Wherever they used {{{module_name}}}, they now use {{{model_name}}}, a lowercase version of the model name.
     884
     885Note that {{{app_label}}} remains the same.
     886
     887These examples assume models live in {{{myproject/blog/models.py}}}.
     888
     889 * Old: {{{blog/entries_archive.html}}}
     890 * New: {{{blog/entry_archive.html}}}
     891
     892=== Moved settings into an instance ===
     893
     894Settings have moved out of a dedicated module {{{django.conf.settings}}} into an instance in the {{{django.conf}}} module. So now you need to import the {{{settings}}} object and reference settings as attributes of that instance.
     895
     896 * Old: {{{from django.conf.settings import LANGUAGE_CODE}}}
     897 * New: {{{from django.conf import settings}}}
     898
     899Wrappers around the Django machinery can make use of this by exchanging the settings instance with a proxy instance that delegates attribute access to a per-thread or per-location global.
     900
     901=== Removed !SilentVariableFailure exception ===
     902
     903Old behavior: Any exception that subclasses {{{django.core.template.SilentVariableFailure}}} fails silently in the template system.
     904
     905New behavior: Any exception that has a {{{silent_variable_failure}}} attribute fails silently in the template system. {{{django.core.template.SilentVariableFailure}}} no longer exists.
     906
     907=== request.user is now set via middleware ===
     908
     909It used to be set in the mod_python and wsgi handlers. You will need to add {{{"django.contrib.auth.middleware.AuthenticationMiddleware"}}} somewhere '''after''' {{{"django.contrib.sessions.middleware.SessionMiddleware"}}} in {{{MIDDLEWARE_CLASSES}}} in your settings.py file. Otherwise accessing {{{request.user}}} will raise an {{{AttributeError}}}.
     910
     911=== Authentication has been consolidated ===
     912
     913Previously, pieces of the authentication system resided in at least 4 different places. Everything has now been consolidated into {{{django.contrib.auth}}} as follows:
     914
     915 * {{{django.parts.auth.formfields.AuthenticationForm}}} has moved to {{{django.contrib.auth.forms}}}
     916 * {{{django.parts.auth.anonymoususers.AnonymousUser}}} has moved to {{{django.contrib.auth.models}}}
     917
     918 * {{{django.views.auth.login.*}}} has moved to {{{django.contrib.auth.views}}}
     919 * {{{django.views.decorators.auth.*}}} has moved to {{{django.contrib.auth.decorators}}}
     920
     921 * {{{django.views.registration.passwords.PasswordResetForm}}} has moved to {{{django.contrib.auth.forms}}}
     922 * {{{django.views.registration.passwords.PasswordChangeForm}}} has moved to {{{django.contrib.auth.forms}}}
     923
     924 * {{{django.views.registration.passwords.password_reset}}} has moved to {{{django.contrib.auth.views}}}
     925 * {{{django.views.registration.passwords.password_reset_done}}} has moved to {{{django.contrib.auth.views}}}
     926 * {{{django.views.registration.passwords.password_change}}} has moved to {{{django.contrib.auth.views}}}
     927 * {{{django.views.registration.passwords.password_change_done}}} has moved to {{{django.contrib.auth.views}}}
     928
     929If you are using any of these classes or functions, you will need to update your code accordingly.
     930
     931=== Changed interface to manipulators ===
     932
     933Old:
     934{{{
     935#!python
     936from django.core import formfields
     937from django.models.PROJECT import MODELMODULE
     938...
     939manipulator = MODELMODULE.AddManipulator()
     940...
     941form = formfields.FormWrapper(manipulator, new_data, errors)
     942}}}
     943
     944New:
     945{{{
     946#!python
     947from django.forms import FormWrapper
     948
     949from PROJECT.APP.models import MODELNAME
     950...
     951manipulator = MODELNAME.AddManipulator()
     952...
     953form = FormWrapper(manipulator, new_data, errors)
     954}}}
     955
     956
     957=== Slightly changed django.VERSION ===
     958
     959The variable {{{django.VERSION}}} has changed from a tuple of four elements to a tuple of three elements.
     960
     961Old: {{{VERSION = (0, 9, 1, 'magic-removal')}}}
     962
     963New: {{{VERSION = (0, 95, 'post-magic-removal')}}}
     964
     965
     966== New functionality you can start using ==
     967
     968
     969=== Models support properties ===
     970
     971Unlike before, properties are supported on models.
     972
     973{{{
     974#!python
     975from django.db import models
     976
     977class Person(models.Model):
     978    first_name = models.CharField(maxlength=30)
     979    last_name = models.CharField(maxlength=30)
     980
     981    def _get_full_name(self):
     982        return "%s %s" % (self.first_name, self.last_name)
     983    full_name = property(_get_full_name)
     984}}}
     985
     986=== You can override default !QuerySets ===
     987
     988You can specify the default !QuerySet (see "Descriptor fields" above) that a manager uses. For example:
     989
     990{{{
     991#!python
     992class PublishedBookManager(models.Manager):
     993    def get_query_set(self):
     994        return super(PublishedBookManager, self).get_query_set().filter(is_published=True)
     995
     996class Book(models.Model):
     997    title = models.CharField(maxlength=50)
     998    author = models.CharField(maxlength=30)
     999    is_published = models.BooleanField()
     1000    published_objects = PublishedBookManager()
     1001}}}
     1002
     1003== Documentation status ==
     1004
     1005The documentation on djangoproject.com has been updated to focus on trunk. [http://www.djangoproject.com/documentation/0_91/ Documentation for 0.91] has been archived.
     1006
     1007A few improvements still need to be made to the new documentation, however. Adrian is proofreading each document to make sure all is well. Here's the status.
     1008
     1009||Document||Edited by Adrian||
     1010||add_ons.txt||Yes||
     1011||admin_css.txt||Yes||
     1012||apache_auth.txt||Yes||
     1013||authentication.txt||Yes||
     1014||cache.txt||Yes||
     1015||contributing.txt||Yes||
     1016
     1017||db-api.txt||Yes||
     1018||design_philosophies.txt||Yes||
     1019||django-admin.txt||Yes||
     1020||email.txt||Yes||
     1021||faq.txt||Yes||
     1022||flatpages.txt||Yes||
     1023||forms.txt||Yes||
     1024||generic_views.txt||Yes||
     1025||i18n.txt||Yes||
     1026||install.txt||Yes||
     1027||legacy_databases.txt||Yes||
     1028||middleware.txt||Yes||
     1029||model-api.txt||Yes||
     1030||modpython.txt||Yes||
     1031||outputting_csv.txt||Yes||
     1032||outputting_pdf.txt||Yes||
     1033||overview.txt||Yes||
     1034||redirects.txt||Yes||
     1035||request_response.txt||Yes||
     1036||sessions.txt||Yes||
     1037||settings.txt||Yes||
     1038||static_files.txt||Yes||
     1039||syndication_feeds.txt||Yes||
     1040||templates_python.txt||Yes||
     1041||templates.txt||Yes||
     1042||transactions.txt||Yes||
     1043||tutorial01.txt||'''No'''||
     1044||tutorial02.txt||'''No'''||
     1045||tutorial03.txt||'''No'''||
     1046||tutorial04.txt||'''No'''||
     1047||url_dispatch.txt||Yes||
     1048
     1049The [http://www.djangoproject.com/documentation/models/ model examples], which are generated from Django's unit tests, also need to be updated. This is just a matter of updating the script that generates the examples from the tests.
Back to Top