Older (pre-0.96) backwards-incompatible changes
This page lists all backwards-incompatible changes to Django prior to the 0.96 release. For backwards-incompatible changes since 0.96 was released, see the BackwardsIncompatibleChanges page.
Table of Contents
Old-school Django (Pre-0.90)
- Moved mod_python handler
- Changed ordering syntax
- Refactored meta.py
- Changed edit_inline and edit_inline_type behavior
- Changed admin log to store primary keys as TEXT fields, not INTEGER fields
- Added support for anonymous sessions
- Change your settings files
- Create the core_sessions database table
- Remove old, unneeded things
- Changed model syntax
- Moved template loader module
- Refactored the admin app not to require its own settings file
- Separated flatpages and redirects into standalone, optional apps
- Refactored RSS framework
Changes made after Django [source:/django/tags/releases/0.90 0.90]
- Nov. 20: Changed field name and length for auth.User password_md5 field
- Nov. 26: Template system changes
- Nov. 27: Added support for non-named groups in URLconf regular expressions
- Nov. 29: Changed behavior of MultiValueDict.items()
- Nov. 30: Removed undocumented '_or' database API parameter
Changes made after Django [source:/django/tags/releases/0.91 0.91]
- May 1, 2006: Removed the magic
- June 28, 2006: Auth session format change
- Added by jdunck, May 29, 2007
Changes made after Django [source:/django/tags/releases/0.95 0.95]:
- [3512] August 2, 2006: Database constraint names changed
- [3552] August 11, 2006: Backslash escaping changed
- [3877] September 27, 2006: Removed ENABLE_PSYCO setting
- [4724] + [4767] March 14, 2007: Enforcing MySQLdb version
Moved mod_python handler
As of [169], using django.core.handler
as a mod_python handler is deprecated. Use django.core.handlers.modpython
instead. We will be removing django.core.handler
for 1.0.
Changed ordering syntax
As of [292], syntax used for order_by
(in the database API) and ordering
(in models) has changed.
Example of old ordering syntax:
order_by=[('foo', 'ASC'), ('bar', 'DESC')]
Example of new ordering syntax:
order_by=['foo', '-bar']
The old syntax is deprecated, and we'll stop supporting it for 1.0.
Refactored meta.py
As of [378], django/core/meta.py
has been converted to a package, django/core/meta/
. If you're using a version of Django from before [378], make sure to delete django/core/meta.pyc
and django/core/meta.pyo
, if they exist. The existence of those files doesn't pose any known problems, but it's best to clean things up.
Changed edit_inline and edit_inline_type behavior
As of [440], using edit_inline_type
in your models is deprecated, in favor of a less-redundant approach that uses edit_inline
itself.
Example of old syntax:
edit_inline=True, edit_inline_type=meta.TABULAR
Example of new syntax:
edit_inline=meta.TABULAR
We'll stop supporting the old syntax for 1.0.
Changed admin log to store primary keys as TEXT fields, not INTEGER fields
As of [469], the object_id
field in django.models.auth.LogEntry
is a TextField
instead of an IntegerField
. We made this change to accomodate non-integer primary keys.
If you're using a Django database installation from before [469] and you want to use non-integer primary keys on an object you edit in the admin site, you'll need to do an ALTER TABLE
in your database.
In PostgreSQL:
BEGIN; ALTER TABLE auth_admin_log RENAME object_id TO object_id_old; ALTER TABLE auth_admin_log ADD COLUMN object_id TEXT; UPDATE auth_admin_log SET object_id = object_id_old; ALTER TABLE auth_admin_log DROP COLUMN object_id_old; COMMIT;
In MySQL:
ALTER TABLE auth_admin_log MODIFY object_id TEXT;
Added support for anonymous sessions
As of [518], Django has support for anonymous sessions. If you're using a Django database installation from before [518] and you want to use the Django admin, anonymous sessions or auth-based sessions, you'll need to make a few updates to your database and settings files.
Change your settings files
Add "django.middleware.sessions.SessionMiddleware"
to the MIDDLEWARE_CLASSES
tuple in your admin settings file. Make sure it appears before "django.middleware.admin.AdminUserRequired"
. (The middleware classes are applied in order, and the admin middleware requires that the session middleware come first.)
If you want session support any other (i.e., non-admin) Django installation, change the MIDDLEWARE_CLASSES
setting accordingly. The order (i.e., whether it comes before or after the other installed middleware classes) doesn't matter.
Create the core_sessions database table
In PostgreSQL, use this:
CREATE TABLE core_sessions ( session_key varchar(40) NOT NULL PRIMARY KEY, session_data text NOT NULL, expire_date timestamp with time zone NOT NULL ); INSERT INTO content_types (name, package, python_module_name) VALUES ('session', 'core', 'sessions');
In MySQL and SQLite, use this:
CREATE TABLE core_sessions ( session_key varchar(40) NOT NULL PRIMARY KEY, session_data text NOT NULL, expire_date datetime NOT NULL ); INSERT INTO content_types (name, package, python_module_name) VALUES ('session', 'core', 'sessions');
Remove old, unneeded things
Execute this SQL in your database:
DROP TABLE auth_sessions; DELETE FROM content_types WHERE package = 'auth' AND python_module_name = 'sessions';
Edit your settings file(s) to remove AUTH_SESSION_COOKIE
and REGISTRATION_COOKIE_DOMAIN
, if they exist.
Changed model syntax
As of [549], Django's model syntax has changed. If you're using models that use old (pre-[549]) syntax, you'll need to convert them according to the instructions on ModelSyntaxChangeInstructions.
Moved template loader module
As of [867], django.core.template_loader
is deprecated. Use django.core.template.loader
instead.
Refactored the admin app not to require its own settings file
As of [948], the admin has been refactored, to make things simpler and tighter -- both conceptually and in code layout.
What changed
- The admin no longer requires its own settings file. The "main" site and admin site can run on the same Django installation.
- All the admin code moved to
django/contrib/admin
. - The admin requires
"django.contrib.admin"
inINSTALLED_APPS
, and it requires theapp_directories
template loader. - The admin database table isn't installed unless you explicitly have the admin installed (
django-admin.py install admin
). - Renamed the admin log database table to give it a
"django"
prefix.
How to update your code
If you're using a Django installation from before this changeset, do the following to restore your admin site:
- Execute this SQL command:
ALTER TABLE auth_admin_log RENAME TO django_admin_log;
- If you're using an SQLite version older than 3.2.0 (no ALTER TABLE support), execute these SQL commands (following this pattern):
BEGIN TRANSACTION; CREATE TEMPORARY TABLE auth_backup ( id integer NOT NULL PRIMARY KEY, action_time datetime NOT NULL, user_id integer NOT NULL REFERENCES auth_users (id), content_type_id integer NULL REFERENCES content_types (id), object_id text NULL, object_repr varchar(200) NOT NULL, action_flag smallint unsigned NOT NULL, change_message text NOT NULL ); INSERT INTO auth_backup SELECT id, action_time, user_id, content_type_id, object_id, object_repr, action_flag, change_message FROM auth_admin_log; DROP TABLE auth_admin_log; CREATE TABLE django_admin_log ( id integer NOT NULL PRIMARY KEY, action_time datetime NOT NULL, user_id integer NOT NULL REFERENCES auth_users (id), content_type_id integer NULL REFERENCES content_types (id), object_id text NULL, object_repr varchar(200) NOT NULL, action_flag smallint unsigned NOT NULL, change_message text NOT NULL ); INSERT INTO django_admin_log SELECT id, action_time, user_id, content_type_id, object_id, object_repr, action_flag, change_message FROM auth_backup; DROP TABLE auth_backup; COMMIT;
- If you're using PostgreSQL, execute these SQL commands:
ALTER TABLE auth_admin_log_id_seq RENAME TO django_admin_log_id_seq; ALTER TABLE django_admin_log ALTER COLUMN id DROP DEFAULT; ALTER TABLE django_admin_log ALTER COLUMN id SET DEFAULT nextval('public.django_admin_log_id_seq'::text);
- Edit your Django settings file (probably called
settings/main.py
) to make the following changes:- Add
"django.contrib.admin"
toINSTALLED_APPS
. Order doesn't matter; it can be the first, last, whatever. - Remove
"django.middleware.admin.AdminUserRequired"
fromMIDDLEWARE_CLASSES
, if it's in there. - Add
"django.middleware.sessions.SessionMiddleware"
toMIDDLEWARE_CLASSES
, if it's not already in there. - If you've created any custom admin templates, add the appropriate line from the admin
TEMPLATE_DIRS
setting to your "main"TEMPLATE_DIRS
.- (Note that Django looks for "404.html" and "500.html" templates in your
TEMPLATE_DIRS
. Make sure you have these templates available.)
- (Note that Django looks for "404.html" and "500.html" templates in your
- If you've created any custom admin templates, note that the template inheritance structure has changed. All admin templates are now within an
admin
subdirectory of the template directory. The following admin templates are directly affected by this change:- 404 --> admin/404
- 500 --> admin/500
- base --> admin/base
- base_site --> admin/base_site
- admin_object_history --> admin/object_history
- delete_confirmation_generic --> admin/delete_confirmation
- index --> admin/index
- login --> admin/login
- template_validator --> admin/template_validator
- Add
"django.core.template.loaders.app_directories.load_template_source"
toTEMPLATE_LOADERS
, after"django.core.template.loaders.filesystem.load_template_source"
. If you don't have theTEMPLATE_LOADERS
setting, set it to this:TEMPLATE_LOADERS = ( 'django.core.template.loaders.filesystem.load_template_source', 'django.core.template.loaders.app_directories.load_template_source', )
- Add
- Remove your admin settings file (probably called
settings/admin.py
) and admin URLconf (probably calledsettings/urls/admin.py
). - Delete
django/templatetags/*.pyc
anddjango/templatetags/*.pyo
, just to be safe. - Edit your main URLconf (probably called
settings/urls/main.py
) and add this line:
(r'^admin/', include('django.contrib.admin.urls.admin')),
Change that
"admin"
to whatever URL you were using for the admin site.
If you use external mapping to files and directories (e.g., using mod_rewrite), do the following:
- Make sure you're not redirecting
/admin
to another instance of Django with different settings (e.g., using settings/admin.py). - If you had a mapping of admin media files, make sure that it points to new directory, which is
/your/path/to/django/contrib/admin/media
.
The following steps are optional but will tighten your code up. All assume your project is called myproject
.
- Move
myproject/settings/urls/main.py
tomyproject/urls.py
. - Delete
myproject/settings/urls/admin.py
(unless you had custom things in it, of course). - Move
myproject/settings/main.py
tomyproject/settings.py
. - Edit
myproject/settings.py
to changeROOT_URLCONF
from"myproject.settings.urls.main"
to"myproject.urls"
. - If you use
myproject/settings/main_rss.py
to describe your RSS feeds, move it tomyproject/settings_rss.py
. - Change
DJANGO_SETTINGS_MODULE
in Apache configuration from"myproject.settings.main"
to"myproject.settings"
. - Having done all this, delete the directory
myproject/settings
.
Separated flatpages and redirects into standalone, optional apps
As of [1166], flatpages and redirects, previously installed by default, are now optional add-ons. Now they must be installed manually.
What changed
- The flatpages and redirects database files are no longer installed by default.
- Renamed all references to "flatfiles" -- a previous name for flatpages -- to "flatpages", to be unambiguous.
- Renamed the flatpages and redirects database tables.
- Moved all flatpages and redirects logic from throughout the Django code to
django.contrib
. - To use flatpages and redirects, you now need to specify them in
INSTALLED_APPS
-- use"django.contrib.flatpages"
and"django.contrib.redirects"
.
How to update your code
If you're using a Django installation from before this changeset, do the following to restore flatpages and redirects functionality:
If you don't want to use flatfiles
Execute the following SQL:
DROP TABLE flatfiles; DROP TABLE flatfiles_sites; DELETE FROM auth_permissions WHERE package = 'core' AND codename IN ('add_flatfile', 'change_flatfile', 'delete_flatfile'); DELETE FROM content_types WHERE package = 'core' AND python_module_name = 'flatfiles';
If you don't want to use redirects
Execute the following SQL:
DROP TABLE redirects; DELETE FROM auth_permissions WHERE package = 'core' AND codename IN ('add_redirect', 'change_redirect', 'delete_redirect'); DELETE FROM content_types WHERE package = 'core' AND python_module_name = 'redirects';
If you want to use flatfiles
- If you're using PostgreSQL, execute the following:
ALTER TABLE flatfiles RENAME TO django_flatpages; ALTER TABLE flatfiles_sites RENAME TO django_flatpages_sites; ALTER TABLE django_flatpages_sites RENAME flatfile_id TO flatpage_id; INSERT INTO packages (label, name) VALUES ('flatpages', 'flatpages'); UPDATE content_types SET package = 'flatpages', python_module_name = 'flatpages' WHERE package = 'core' AND python_module_name = 'flatfiles'; UPDATE auth_permissions SET package = 'flatpages' WHERE package = 'core' AND codename IN ('add_flatfile', 'change_flatfile', 'delete_flatfile'); ALTER TABLE flatfiles_id_seq RENAME TO django_flatpages_id_seq; ALTER TABLE django_flatpages ALTER COLUMN id DROP DEFAULT; ALTER TABLE django_flatpages ALTER COLUMN id SET DEFAULT nextval('public.django_flatpages_id_seq'::text);
- If you're using MySQL, execute the following:
ALTER TABLE flatfiles RENAME TO django_flatpages; ALTER TABLE flatfiles_sites RENAME TO django_flatpages_sites; ALTER TABLE django_flatpages_sites CHANGE flatfile_id flatpage_id INTEGER; INSERT INTO packages (label, name) VALUES ('flatpages', 'flatpages'); UPDATE content_types SET package = 'flatpages', python_module_name = 'flatpages' WHERE package = 'core' AND python_module_name = 'flatfiles'; UPDATE auth_permissions SET package = 'flatpages' WHERE package = 'core' AND codename IN ('add_flatfile', 'change_flatfile', 'delete_flatfile');
- If you have a
flatfiles/default.html
template, rename it toflatpages/default.html
. - In every one of your flatpage templates, change the variable
flatfile
toflatpage
. - If you use the URLconf
django.conf.urls.flatfiles
, now point todjango.contrib.flatpages.urls
. - If you have a
USE_FLAT_PAGES
setting, remove it. - Add
"django.contrib.flatpages"
to yourINSTALLED_APPS
. - Add
"django.contrib.flatpages.middleware.FlatpageFallbackMiddleware"
to yourMIDDLEWARE_CLASSES
.
If you want to use redirects
- Execute the following SQL:
ALTER TABLE redirects RENAME TO django_redirects; INSERT INTO packages (label, name) VALUES ('redirects', 'redirects'); UPDATE content_types SET package = 'redirects', python_module_name = 'redirects' WHERE package = 'core' AND python_module_name = 'redirects'; UPDATE auth_permissions SET package = 'redirects' WHERE package = 'core' AND codename IN ('add_redirect', 'change_redirect', 'delete_redirect');
- If you're using PostgreSQL, execute this additional SQL:
ALTER TABLE redirects_id_seq RENAME TO django_redirects_id_seq; ALTER TABLE django_redirects ALTER COLUMN id DROP DEFAULT; ALTER TABLE django_redirects ALTER COLUMN id SET DEFAULT nextval('public.django_redirects_id_seq'::text);
- Add
"django.contrib.redirects"
to yourINSTALLED_APPS
. - Add
"django.contrib.redirects.middleware.RedirectFallbackMiddleware"
to yourMIDDLEWARE_CLASSES
.
Refactored RSS framework
As of [1194], Django's RSS framework was refactored. This change should not affect most Django users, because the RSS framework was completely undocumented. The only users affected are people who reverse-engineered the framework, and WorldOnline.
See the new syndication docs. For completeness, here's what changed:
- Created
django/contrib/syndication
. - Removed
django/conf/urls/rss.py
. The syndication system doesn't require its own URLconf anymore. - Moved
django/views/rss/rss.py
todjango/contrib/syndication/views.py
and refactored it so thatfeed()
takesurl
andfeed_dict
instead ofslug
andparam
. - Renamed
DefaultRssFeed
toDefaultFeed
indjango/utils/feedgenerator.py
. - RSS feeds are now specified as subclasses of
django.contrib.syndication.feeds.Feed
instead ofdjango.core.rss.FeedConfiguration
. Syntax is completely different. - RSS feeds are now registered in URLconfs rather than in "magic" settings modules whose names end with "_rss".
- Templates for RSS titles and descriptions now live in a
feeds
directory, not anrss
directory.
Changed field name and length for auth.User password_md5 field
As of [1327], the password_md5
field in the auth.User
model, which is used for authentication in the Django admin, was renamed to password
, and its length was changed from 32 to 128 to accomodate longer hashes and password metadata, such as which hash algorithm to use. This affects everybody who uses the Django authentication system -- including users of the Django admin.
Execute the following SQL to restore auth functionality:
- PostgreSQL:
BEGIN; ALTER TABLE auth_users ADD COLUMN password varchar(128); UPDATE auth_users SET password = password_md5; ALTER TABLE auth_users ALTER COLUMN password SET NOT NULL; ALTER TABLE auth_users DROP COLUMN password_md5; COMMIT;
- MySQL:
BEGIN; ALTER TABLE `auth_users` CHANGE `password_md5` `password` VARCHAR(128) NOT NULL; COMMIT;
- SQLite:
BEGIN; ALTER TABLE auth_users RENAME TO auth_users_old; CREATE TABLE auth_users ( id integer NOT NULL PRIMARY KEY, username varchar(30) NOT NULL UNIQUE, first_name varchar(30) NOT NULL, last_name varchar(30) NOT NULL, email varchar(75) NOT NULL, password varchar(128) NOT NULL, is_staff bool NOT NULL, is_active bool NOT NULL, is_superuser bool NOT NULL, last_login datetime NOT NULL, date_joined datetime NOT NULL ); INSERT INTO auth_users ( id, username, first_name, last_name, email, password, is_staff, is_active, is_superuser, last_login, date_joined ) SELECT * FROM auth_users_old; DROP TABLE auth_users_old; COMMIT;
Template system changes
As of [1443], we changed the way custom template tags and filters are registered. If you've written custom template tags or filters, you'll need to make a couple of changes:
Filter arguments
For any filters that don't take an argument, remove the second argument to the filter function.
Old:
def lower(param, _): return param.lower()
New:
def lower(param): return param.lower()
The system now introspects the function's arguments to find out whether a filter argument is required.
Change the way your tags/filters are registered
Old way:
from django.core import template template.register_filter('lower', lower, False) template.register_tag('current_time', do_current_time)
New way:
from django.core import template register = template.Library() register.filter('lower', lower) register.tag('current_time', do_current_time)
Change template decorator calls
If you're using the undocumented template decorators (simple_tag and inclusion_tag), change your calls to be members of the library class.
Old way:
simple_tag(func) inclusion_tag('path/to/template')(func)
New way:
register.simple_tag(func) register.inclusion_tag('path/to/template')(func)
Change template tags that use filters
Filters are compiled at compile time now.
Old way:
class SomethingNode(Node): def __init__(self, filter_string): self.filter_string = filter_string def render(self, context): var = resolve_variable_with_filters(self.filter_string, context) return var def do_something(parser, token): bits = token.split() filter_string = bits[1] return SomethingNode(filter_string) register_tag('something', do_something)
New way:
class SomethingNode(Node): def __init__(self, filter_expr): self.filter_expr = filter_expr def render(self, context): var = self.filter_expr.resolve(context) return var def do_something(parser, token): bits = token.split() filter_string = bits[1] filter_expr = parser.compile_filter(filter_string) return SomethingNode(filter_expr) register.tag('something', do_something)
See Writing custom template filters and Writing custom template tags for full documentation.
Templates
Templates that are included in another template using {% include %
} or {% ssi %
}, or extend a template using {% extends %
}, must explicitly {% load %
} all libraries they need for their tags and filters. Some templates may have worked in the past due to previous requests registering tags : this will no longer work.
Added support for non-named groups in URLconf regular expressions
As of [1470], Django URLconfs support non-named groups in URLconf regular expressions. This means you no longer have to specify the name of each group.
For example, this old syntax:
(r'^(?P<item_id>\d{1,3})/$', 'foo.item_detail'),
Can be shortened to this syntax:
(r'^(\d{1,3})/$', 'foo.item_detail'),
The algorithm it follows is: If there are any named groups, it will use those, ignoring all non-named groups. Otherwise, it will pass all non-named groups as positional arguments. In both cases, it will pass any extra_kwargs
as keyword arguments.
The old syntax (named groups) still works. Use that in cases where you can't (or don't want to) couple the order of URL parameters with the order of your view function arguments.
There are two small backwards-incompatibilities about this change:
- If you've written custom middleware that implements
process_view()
, you'll need to change it so it takesview_args
andview_kwargs
arguments instead ofparam_dict
:
def process_view(self, request, view_func, view_args, view_kwargs)
- On the off chance you have legacy URLconfs that have NO captured items but DO use capturing parenthesis (which until now would have had no effect), you'll need to either change your view code to accept the captured values, or uncapture them.
Changed behavior of MultiValueDict.items()
In [1504], the MultiValueDict
(and QueryDict
) items()
method was changed to return the *last* member of each list rather than the full list.
This probably doesn't affect anybody, but it's listed here for completeness.
Old behavior:
>>> q = QueryDict('a=1&a=2') >>> q.items() [('a', ['1', 2'])]
New behavior:
>>> q = QueryDict('a=1&a=2') >>> q.items() [('a', '2')]
To emulate the old behavior, use the new lists()
method:
>>> q = QueryDict('a=1&a=2') >>> q.lists() [('a', ['1', 2'])]
Removed undocumented '_or' database API parameter
As of [1508], the _or
parameter no longer works. Use the new complex
parameter. This likely doesn't affect anybody except World Online, because _or
has been undocumented and vehemently discouraged on the mailing list.
Removed the magic
As of [2809], the magic-removal branch has been merged. There's a LONG list of backwards-incompatible changes, and they're all documented on the RemovingTheMagic wiki page.
Database constraint names changed
As of [3512], the format of the constraint names Django generates for foreign key references changed slightly. These names are only used sometimes, when it is not possible to put the reference directly on the affected column, so this is not always visible.
The effect of this change is that manage.py reset app_name
and similar commands may generate SQL with invalid constraint names and thus generate an error when run against the database (the database server will complain about the constraint not existing). To fix this, you will need to tweak the output of manage.py sqlreset app_name
to match the correct constraint names and pass the results to the database server manually.
Backslash escaping changed
As of [3552], the Django database API now escapes backslashes given as query parameters. If you have any database API code that match backslashes, and it was working before (despite the broken escaping), you'll have to change your code to "unescape" the slashes one level.
For example, this used to work:
# Code that matches a single backslash MyModel.objects.filter(text__contains='\\\\')
But it should be rewritten as this:
# Code that matches a single backslash MyModel.objects.filter(text__contains='\\')
Removed ENABLE_PSYCO setting
As of [3877], the ENABLE_PSYCO
setting no longer exists. If your settings file includes ENABLE_PSYCO
, nothing will break per se, but it just won't do anything. If you want to use Psyco with Django, write some custom middleware that activates Psyco.
Enforcing MySQLdb version
As of [4724], Django raises an error if you try to use the MySQL backend with a MySQLdb
(MySQL Python module) version earlier than 1.2.1p2. There were significant, production-related bugs in earlier versions, so we have upgraded the minimum requirement.
In [4767], we added a mysql_old
backend, which is identical to the mysql
backend prior to the change in [4724]. You can use this backend if upgrading the MySQLdb
module is not immediately possible. However, the mysql_old
backend is deprecated, and we will not continue developing it.