Porting your apps from Django 0.96 to 1.0 — Django documentation
Porting your apps from Django 0.96 to 1.0
Django 1.0 breaks compatibility with 0.96 in some areas.
This guide will help you port 0.96 projects and apps to 1.0. The first part of this document includes the common changes needed to run with 1.0. If after going through the first part your code still breaks, check the section Less-common Changes for a list of a bunch of less-common compatibility issues.
See also
The 1.0 release notes. That document explains the new features in 1.0 more deeply; the porting guide is more concerned with helping you quickly update your code.
Common changes
This section describes the changes between 0.96 and 1.0 that most users will need to make.
Use Unicode
Change string literals ('foo'
) into Unicode literals (u'foo'
). Django now uses Unicode strings throughout. In most places, raw strings will continue to work, but updating to use Unicode literals will prevent some obscure problems.
See Unicode data for full details.
Models
Common changes to your models file:
Rename maxlength to max_length
Rename your maxlength
argument to max_length
(this was changed to be consistent with form fields):
Replace __str__ with __unicode__
Replace your model’s __str__
function with a __unicode__
method, and make sure you use Unicode (u'foo'
) in that method.
Remove prepopulated_from
Remove the prepopulated_from
argument on model fields. It’s no longer valid and has been moved to the ModelAdmin
class in admin.py
. See the admin, below, for more details about changes to the admin.
Remove core
Remove the core
argument from your model fields. It is no longer necessary, since the equivalent functionality (part of inline editing) is handled differently by the admin interface now. You don’t have to worry about inline editing until you get to the admin section, below. For now, remove all references to core
.
Replace class Admin: with admin.py
Remove all your inner class Admin
declarations from your models. They won’t break anything if you leave them, but they also won’t do anything. To register apps with the admin you’ll move those declarations to an admin.py
file; see the admin below for more details.
See also
A contributor to djangosnippets has written a script that’ll scan your models.py and generate a corresponding admin.py.
Example
Below is an example models.py
file with all the changes you’ll need to make:
Old (0.96) models.py
:
class Author(models.Model):
first_name = models.CharField(maxlength=30)
last_name = models.CharField(maxlength=30)
slug = models.CharField(maxlength=60, prepopulate_from=('first_name', 'last_name'))
class Admin:
list_display = ['first_name', 'last_name']
def __str__(self):
return '%s %s' % (self.first_name, self.last_name)
New (1.0) models.py
:
class Author(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
slug = models.CharField(max_length=60)
def __unicode__(self):
return u'%s %s' % (self.first_name, self.last_name)
New (1.0) admin.py
:
from django.contrib import admin
from models import Author
class AuthorAdmin(admin.ModelAdmin):
list_display = ['first_name', 'last_name']
prepopulated_fields = {
'slug': ('first_name', 'last_name')
}
admin.site.register(Author, AuthorAdmin)
The Admin
One of the biggest changes in 1.0 is the new admin. The Django administrative interface (django.contrib.admin
) has been completely refactored; admin definitions are now completely decoupled from model definitions, the framework has been rewritten to use Django’s new form-handling library and redesigned with extensibility and customization in mind.
Practically, this means you’ll need to rewrite all of your class Admin
declarations. You’ve already seen in models above how to replace your class Admin
with an admin.site.register()
call in an admin.py
file. Below are some more details on how to rewrite that Admin
declaration into the new syntax.
Use new inline syntax
The new edit_inline
options have all been moved to admin.py
. Here’s an example:
Old (0.96):
class Parent(models.Model):
...
class Child(models.Model):
parent = models.ForeignKey(Parent, edit_inline=models.STACKED, num_in_admin=3)
New (1.0):
class ChildInline(admin.StackedInline):
model = Child
extra = 3
class ParentAdmin(admin.ModelAdmin):
model = Parent
inlines = [ChildInline]
admin.site.register(Parent, ParentAdmin)
See InlineModelAdmin objects for more details.
Simplify fields, or use fieldsets
The old fields
syntax was quite confusing, and has been simplified. The old syntax still works, but you’ll need to use fieldsets
instead.
Old (0.96):
class ModelOne(models.Model):
...
class Admin:
fields = (
(None, {'fields': ('foo','bar')}),
)
class ModelTwo(models.Model):
...
class Admin:
fields = (
('group1', {'fields': ('foo','bar'), 'classes': 'collapse'}),
('group2', {'fields': ('spam','eggs'), 'classes': 'collapse wide'}),
)
New (1.0):
class ModelOneAdmin(admin.ModelAdmin):
fields = ('foo', 'bar')
class ModelTwoAdmin(admin.ModelAdmin):
fieldsets = (
('group1', {'fields': ('foo','bar'), 'classes': 'collapse'}),
('group2', {'fields': ('spam','eggs'), 'classes': 'collapse wide'}),
)
See also
- More detailed information about the changes and the reasons behind them can be found on the NewformsAdminBranch wiki page
- The new admin comes with a ton of new features; you can read about them in the admin documentation.
URLs
Update your root urls.py
If you’re using the admin site, you need to update your root urls.py
.
Old (0.96) urls.py
:
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^admin/', include('django.contrib.admin.urls')),
# ... the rest of your URLs here ...
)
New (1.0) urls.py
:
from django.conf.urls.defaults import *
# The next two lines enable the admin and load each admin.py file:
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
(r'^admin/(.*)', admin.site.root),
# ... the rest of your URLs here ...
)
Views
Use django.forms instead of newforms
Replace django.newforms
with django.forms
– Django 1.0 renamed the newforms
module (introduced in 0.96) to plain old forms
. The oldforms
module was also removed.
If you’re already using the newforms
library, and you used our recommended import
statement syntax, all you have to do is change your import statements.
Old:
from django import newforms as forms
New:
from django import forms
If you’re using the old forms system (formerly known as django.forms
and django.oldforms
), you’ll have to rewrite your forms. A good place to start is the forms documentation
Handle uploaded files using the new API
Replace use of uploaded files – that is, entries in request.FILES
– as simple dictionaries with the new UploadedFile. The old dictionary syntax no longer works.
Thus, in a view like:
def my_view(request):
f = request.FILES['file_field_name']
...
…you’d need to make the following changes:
Old (0.96) | New (1.0) |
---|---|
f['content']
|
f.read()
|
f['filename']
|
f.name
|
f['content-type']
|
f.content_type
|
Work with file fields using the new API
The internal implementation of django.db.models.FileField have changed. A visible result of this is that the way you access special attributes (URL, filename, image size, etc.) of these model fields has changed. You will need to make the following changes, assuming your model’s FileField is called myfile
:
Old (0.96) | New (1.0) |
---|---|
myfile.get_content_filename()
|
myfile.content.path
|
myfile.get_content_url()
|
myfile.content.url
|
myfile.get_content_size()
|
myfile.content.size
|
myfile.save_content_file()
|
myfile.content.save()
|
myfile.get_content_width()
|
myfile.content.width
|
myfile.get_content_height()
|
myfile.content.height
|
Note that the width
and height
attributes only make sense for ImageField fields. More details can be found in the model API documentation.
Use Paginator instead of ObjectPaginator
The ObjectPaginator
in 0.96 has been removed and replaced with an improved version, django.core.paginator.Paginator.
Templates
Learn to love autoescaping
By default, the template system now automatically HTML-escapes the output of every variable. To learn more, see Automatic HTML escaping.
To disable auto-escaping for an individual variable, use the :tfilter:`safe` filter:
This will be escaped: {{ data }}
This will not be escaped: {{ data|safe }}
To disable auto-escaping for an entire template, wrap the template (or just a particular section of the template) in the :ttag:`autoescape` tag:
{% autoescape off %}
... unescaped template content here ...
{% endautoescape %}
Less-common changes
The following changes are smaller, more localized changes. They should only affect more advanced users, but it’s probably worth reading through the list and checking your code for these things.
Signals
- Add
**kwargs
to any registered signal handlers. - Connect, disconnect, and send signals via methods on the Signal object instead of through module methods in
django.dispatch.dispatcher
. - Remove any use of the
Anonymous
andAny
sender options; they no longer exist. You can still receive signals sent by any sender by usingsender=None
- Make any custom signals you’ve declared into instances of django.dispatch.Signal instead of anonymous objects.
Here’s quick summary of the code changes you’ll need to make:
Old (0.96) | New (1.0) |
---|---|
def callback(sender)
|
def callback(sender, **kwargs)
|
sig = object()
|
sig = django.dispatch.Signal()
|
dispatcher.connect(callback, sig)
|
sig.connect(callback)
|
dispatcher.send(sig, sender)
|
sig.send(sender)
|
dispatcher.connect(callback, sig, sender=Any)
|
sig.connect(callback, sender=None)
|
Local flavors
U.S. local flavor
django.contrib.localflavor.usa
has been renamed to django.contrib.localflavor.us
. This change was made to match the naming scheme of other local flavors. To migrate your code, all you need to do is change the imports.
Sessions
Getting a new session key
SessionBase.get_new_session_key()
has been renamed to _get_new_session_key()
. get_new_session_object()
no longer exists.
Fixtures
Loading a row no longer calls save()
Previously, loading a row automatically ran the model’s save()
method. This is no longer the case, so any fields (for example: timestamps) that were auto-populated by a save()
now need explicit values in any fixture.
Settings
Better exceptions
The old EnvironmentError
has split into an ImportError
when Django fails to find the settings module and a RuntimeError
when you try to reconfigure settings after having already used them.
:setting:`LOGIN_URL` has moved
The :setting:`LOGIN_URL` constant moved from django.contrib.auth
into the settings
module. Instead of using from django.contrib.auth import LOGIN_URL
refer to :setting:`settings.LOGIN_URL `.
:setting:`APPEND_SLASH` behavior has been updated
In 0.96, if a URL didn’t end in a slash or have a period in the final component of its path, and :setting:`APPEND_SLASH` was True, Django would redirect to the same URL, but with a slash appended to the end. Now, Django checks to see whether the pattern without the trailing slash would be matched by something in your URL patterns. If so, no redirection takes place, because it is assumed you deliberately wanted to catch that pattern.
For most people, this won’t require any changes. Some people, though, have URL patterns that look like this:
r'/some_prefix/(.*)$'
Previously, those patterns would have been redirected to have a trailing slash. If you always want a slash on such URLs, rewrite the pattern as:
r'/some_prefix/(.*/)$'
Smaller model changes
Different exception from get()
Managers now return a MultipleObjectsReturned exception instead of AssertionError
:
Old (0.96):
try:
Model.objects.get(...)
except AssertionError:
handle_the_error()
New (1.0):
try:
Model.objects.get(...)
except Model.MultipleObjectsReturned:
handle_the_error()
LazyDate has been fired
The LazyDate
helper class no longer exists.
Default field values and query arguments can both be callable objects, so instances of LazyDate
can be replaced with a reference to datetime.datetime.now
:
Old (0.96):
class Article(models.Model):
title = models.CharField(maxlength=100)
published = models.DateField(default=LazyDate())
New (1.0):
import datetime
class Article(models.Model):
title = models.CharField(max_length=100)
published = models.DateField(default=datetime.datetime.now)
DecimalField is new, and FloatField is now a proper float
Old (0.96):
class MyModel(models.Model):
field_name = models.FloatField(max_digits=10, decimal_places=3)
...
New (1.0):
class MyModel(models.Model):
field_name = models.DecimalField(max_digits=10, decimal_places=3)
...
If you forget to make this change, you will see errors about FloatField
not taking a max_digits
attribute in __init__
, because the new FloatField
takes no precision-related arguments.
If you’re using MySQL or PostgreSQL, no further changes are needed. The database column types for DecimalField
are the same as for the old FloatField
.
If you’re using SQLite, you need to force the database to view the appropriate columns as decimal types, rather than floats. To do this, you’ll need to reload your data. Do this after you have made the change to using DecimalField
in your code and updated the Django code.
Warning
Back up your database first!
For SQLite, this means making a copy of the single file that stores the database (the name of that file is the DATABASE_NAME
in your settings.py file).
To upgrade each application to use a DecimalField
, you can do the following, replacing <app>
in the code below with each app’s name:
$ ./manage.py dumpdata --format=xml <app> > data-dump.xml
$ ./manage.py reset <app>
$ ./manage.py loaddata data-dump.xml
Notes:
- It’s important that you remember to use XML format in the first step of this process. We are exploiting a feature of the XML data dumps that makes porting floats to decimals with SQLite possible.
- In the second step you will be asked to confirm that you are prepared to lose the data for the application(s) in question. Say yes; we’ll restore this data in the third step.
DecimalField
is not used in any of the apps shipped with Django prior to this change being made, so you do not need to worry about performing this procedure for any of the standard Django models.
If something goes wrong in the above process, just copy your backed up database file over the original file and start again.
Internationalization
django.views.i18n.set_language() now requires a POST request
Previously, a GET request was used. The old behavior meant that state (the locale used to display the site) could be changed by a GET request, which is against the HTTP specification’s recommendations. Code calling this view must ensure that a POST request is now made, instead of a GET. This means you can no longer use a link to access the view, but must use a form submission of some kind (e.g. a button).
_() is no longer in builtins
_()
(the callable object whose name is a single underscore) is no longer monkeypatched into builtins – that is, it’s no longer available magically in every module.
If you were previously relying on _()
always being present, you should now explicitly import ugettext
or ugettext_lazy
, if appropriate, and alias it to _
yourself:
from django.utils.translation import ugettext as _
HTTP request/response objects
Dictionary access to HttpRequest
HttpRequest
objects no longer directly support dictionary-style access; previously, both GET
and POST
data were directly available on the HttpRequest
object (e.g., you could check for a piece of form data by using if 'some_form_key' in request
or by reading request['some_form_key']
. This is no longer supported; if you need access to the combined GET
and POST
data, use request.REQUEST
instead.
It is strongly suggested, however, that you always explicitly look in the appropriate dictionary for the type of request you expect to receive (request.GET
or request.POST
); relying on the combined request.REQUEST
dictionary can mask the origin of incoming data.
Accessing HTTPResponse headers
django.http.HttpResponse.headers
has been renamed to _headers
and HttpResponse now supports containment checking directly. So use if header in response:
instead of if header in response.headers:
.
Generic relations
Generic relations have been moved out of core
The generic relation classes – GenericForeignKey
and GenericRelation
– have moved into the django.contrib.contenttypes module.
Testing
django.test.Client.login() has changed
Old (0.96):
from django.test import Client
c = Client()
c.login('/path/to/login','myuser','mypassword')
New (1.0):
# ... same as above, but then:
c.login(username='myuser', password='mypassword')
Management commands
Running management commands from your code
django.core.management has been greatly refactored.
Calls to management services in your code now need to use call_command
. For example, if you have some test code that calls flush and load_data:
from django.core import management
management.flush(verbosity=0, interactive=False)
management.load_data(['test_data'], verbosity=0)
…you’ll need to change this code to read:
from django.core import management
management.call_command('flush', verbosity=0, interactive=False)
management.call_command('loaddata', 'test_data', verbosity=0)
Subcommands must now precede options
django-admin.py
and manage.py
now require subcommands to precede options. So:
$ django-admin.py --settings=foo.bar runserver
…no longer works and should be changed to:
$ django-admin.py runserver --settings=foo.bar
Data structures
SortedDictFromList is gone
django.newforms.forms.SortedDictFromList
was removed. django.utils.datastructures.SortedDict
can now be instantiated with a sequence of tuples.
To update your code:
- Use
django.utils.datastructures.SortedDict
wherever you were usingdjango.newforms.forms.SortedDictFromList
. - Because
django.utils.datastructures.SortedDict.copy
doesn’t return a deepcopy asSortedDictFromList.copy()
did, you will need to update your code if you were relying on a deepcopy. Do this by usingcopy.deepcopy
directly.
Database backend functions
Database backend functions have been renamed
Almost all of the database backend-level functions have been renamed and/or relocated. None of these were documented, but you’ll need to change your code if you’re using any of these functions, all of which are in django.db:
Old (0.96) | New (1.0) |
---|---|
backend.get_autoinc_sql
|
connection.ops.autoinc_sql
|
backend.get_date_extract_sql
|
connection.ops.date_extract_sql
|
backend.get_date_trunc_sql
|
connection.ops.date_trunc_sql
|
backend.get_datetime_cast_sql
|
connection.ops.datetime_cast_sql
|
backend.get_deferrable_sql
|
connection.ops.deferrable_sql
|
backend.get_drop_foreignkey_sql
|
connection.ops.drop_foreignkey_sql
|
backend.get_fulltext_search_sql
|
connection.ops.fulltext_search_sql
|
backend.get_last_insert_id
|
connection.ops.last_insert_id
|
backend.get_limit_offset_sql
|
connection.ops.limit_offset_sql
|
backend.get_max_name_length
|
connection.ops.max_name_length
|
backend.get_pk_default_value
|
connection.ops.pk_default_value
|
backend.get_random_function_sql
|
connection.ops.random_function_sql
|
backend.get_sql_flush
|
connection.ops.sql_flush
|
backend.get_sql_sequence_reset
|
connection.ops.sequence_reset_sql
|
backend.get_start_transaction_sql
|
connection.ops.start_transaction_sql
|
backend.get_tablespace_sql
|
connection.ops.tablespace_sql
|
backend.quote_name
|
connection.ops.quote_name
|
backend.get_query_set_class
|
connection.ops.query_set_class
|
backend.get_field_cast_sql
|
connection.ops.field_cast_sql
|
backend.get_drop_sequence
|
connection.ops.drop_sequence_sql
|
backend.OPERATOR_MAPPING
|
connection.operators
|
backend.allows_group_by_ordinal
|
connection.features.allows_group_by_ordinal
|
backend.allows_unique_and_pk
|
connection.features.allows_unique_and_pk
|
backend.autoindexes_primary_keys
|
connection.features.autoindexes_primary_keys
|
backend.needs_datetime_string_cast
|
connection.features.needs_datetime_string_cast
|
backend.needs_upper_for_iops
|
connection.features.needs_upper_for_iops
|
backend.supports_constraints
|
connection.features.supports_constraints
|
backend.supports_tablespaces
|
connection.features.supports_tablespaces
|
backend.uses_case_insensitive_names
|
connection.features.uses_case_insensitive_names
|
backend.uses_custom_queryset
|
connection.features.uses_custom_queryset
|
Comments
If you were using Django 0.96’s
django.contrib.comments
app, you’ll need to upgrade to the new comments app introduced in 1.0. See the upgrade guide for details.