Version 143 (modified by Manoj Govindan <egmanoj@…>, 16 years ago) ( diff )

I hope this will save time for developers whose tests fail like mine did.

Backwards-incompatible changes

As Django is still in pre-1.0 mode, we haven't yet committed to maintaining backwards compatibility in any APIs. Although we're keeping such changes to a minimum, Django developers should be acutely aware of these changes.

Of course, once we reach 1.0, we'll be strongly committed to backward compatibility.

This page lists all backwards-incompatible changes to Django since the 0.95 release. For changes prior to 0.95, see the OlderBackwardsIncompatibleChanges page.

Table of Contents

Changes made after Django [source:/django/tags/releases/0.95 0.95]:

Changes made after Django [source:/django/tags/releases/0.96 0.96]:

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.

Changed ModelForm's init signature to match Form's

As of [6915] the __init__ signature for ModelForm matches that of newforms.Form. ModelForm objects can now be instantiated by supplying the relevant form data without requiring a model instance to be passed in.

Before the change ModelForm objects used in views were instantiated as follows:

   form = MyModelForm(instance, request.POST.copy())

This now becomes

   form = MyModelForm(request.POST.copy())

Changed 'spaceless' template tag to remove all spaces

As of [4885], the spaceless template tag removes *all* spaces between HTML tags instead of preserving a single space.

For example, for this template code:

{% spaceless %}
<p>
    <a href="foo/">Foo</a>
</p>
{% endspaceless %}

The old output was this:

<p> <a href="foo/">Foo</a> </p>

The new output is this:

<p><a href="foo/">Foo</a></p>

As always, spaceless only affects space *between* HTML tags, not space within HTML tags or space in plain text.

Renamed localflavor.usa to localflavor.us

As of [4954], localflavor.usa has been renamed localflavor.us. This change was made to match the naming scheme of other local flavors.

Removed LazyDate

The LazyDate helper class was removed in [4985]. Default field values and query arguments can both be callable objects, so instances of LazyDate can be replaced with a reference to a callable that evaluates to the current date (i.e., datetime.now). For example, the following model using LazyDate:

class Article(models.Model):
    title = models.CharField(maxlength=100)
    published = models.DateField(default=LazyDate())

can be redefined as:

from datetime import datetime
class Article(models.Model):
    title = models.CharField(maxlength=100)
    published = models.DateField(default=datetime.now)

Note that the new model definition refers to the name of the datetime.now() function, but doesn't actually call the function. The call to datetime.now() is deferred until the value is actually required (i.e., when the model is saved).

MySQL Introspection Change

In [5042] a small change was made in the mysql backend in how it interpreted CHAR(n) columns in the database. They are now mapped to Django's CharField class, rather than the previous TextField. See ticket #4048 for details.

This will only be apparent if you introspect a database table using 0.96 and again using [5042] or later: you will have slightly different Django models generated if there are any CHAR(n) columns. However, no real code changes should be necessary.

LOGIN_URL is now a setting

In [5072], we moved the LOGIN_URL constant from django.contrib.auth into the settings module. This was part of a broader change to make these URLs (including logout and post-login redirect) configurable. Code that previously read

from django.contrib.auth import LOGIN_URL

should be changed to refer to settings.LOGIN_URL instead.

Test Client login() method changed

The implementation of django.test.Client.login() operated as a wrapper around a series of GET and POST calls accessing a nominated login URL. This approach was fragile, and tightly bound to specific templates and login mechanims.

In [5152], we changed the implementation of the login() method on the test Client. login() now accepts a list of credentials, and exercises these credentials directly on the cookies and Session objects of a site, without accessing or using a login page. This breaks the dependence on specific template formatting, and enables the login mechanism to work with any authentication backend, and any login decorator.

Existing uses of login(), e.g.:

c = Client()
c.login('/path/to/login','myuser','mypassword')

should be modified to read:

c = Client()
c.login(username='myuser', password='mypassword')

The keyword arguments username and password *must* be given explicitly. If an alternate authentication scheme is in use, the credentials required by that scheme can be provided instead of username and password.

Generic relations have moved

In [5172], the various generic relation classes (GenericForeignKey and GenericRelation) have moved into the django.contrib.contenttypes module. This is because they will not work if contenttypes is not an installed app. Making the import path reflect the dependency should help remind people of this.

Code using generic relations should change from saying things like

generic_field = models.GenericRelation(SomeOtherModel)

to using

from django.contrib.contenttypes import generic
...
generic_field = generic.GenericRelation(SomeOtherModel)

No database changes or other model changes are required.

Newforms: clean_data changed to cleaned_data

If you are accessing form data that has been cleaned in newforms, you could previously use the clean_data attribute on the form. In [5237], this was changed to cleaned_data to avoid a name-clash (see [5231] for why the change was necessary).

You will need to do a search-and-replace in your form code and replace clean_data with cleaned_data everywhere.

Test client removed special casing for file uploads

Before [5465], Django's client in the testing infrastructure had special casing for file uploads that matched the implementation of the FileField datatype, making it simpler to use for this case. To allow for different methods of uploading files (e.g. newforms and oldforms), this has been removed--you will have to update your tests if you depended on this behaviour (look at the HTML source to work out what the field names for posted data should be).

Renamed FloatField to DecimalField

In [5302], we fixed a slight inconsistency in Django's model field system. Previously, we had a FloatField that had a maximum number of digits and decimal places. However, although it was stored in the database as a fixed precision value, it was stored in Python as a float, so precision was lost due to rounding problems.

We now have a DecimalField class that is the same as the old FloatField, except that it is represented in Python by a Decimal type (and still stored as a fixed precision value in the database). We have also introduced a proper FloatField that is represented as a float in Python and stored as a "double precision" field in the database.

To make your code compatible with these changes, you need to change all occurrences of FloatField in your code to DecimalField. You only need to rename the field type -- all the parameters are the same. So change

class MyModel(models.Model):
    ...
    field_name = models.FloatField(max_digits=10, decimal_places=3)    # old code!

to

class MyModel(models.Model):
    ...
    field_name = models.DecimalField(max_digits=10, decimal_places=3)    # new code!

If you forget to make this change, you will see errors about FloatField not taking a max_digits attribute in __init__, since the new FloatField takes no precision-related arguments.

If you are using MySQL or PostgreSQL, there are no further changes needed. The database column types for DecimalField are the same as for the old FloatField.

If you are using SQLite, you need to force the database to view the appropriate columns as decimal types, rather than floats. To do this, follow this procedure for every application you have that contains a DecimalField. 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).

For every application using a DecimalField, do the following. We will use applications called some_app and another_app in this example

./manage.py dumpdata --format=xml some_app another_app > data-dump.xml
./manage.py reset some_app another_app
./manage.py loaddata data-dump.xml

Notes:

  1. It is 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.
  2. In the second step you will be asked to confirm that you are prepared to lose the data for the application(s) in question. We restore this data in the third step, of course.
  3. 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 applications.

If something goes wrong in the above process, just copy your backed up database file over the top of the original file and start again.

Urlpatterns now cached

In [5516], a speed improvement was made for reverse URL lookups, particularly. Part of this involved caching information that was unlikely to change: the urlpatterns() contents.

If you were somehow relying on the fact that you could change your urls.py files and not have to restart Django, you can no longer do that. Edits require a restart.

Unicode merge

In [5609], the UnicodeBranch was merged into trunk. For any installations using ASCII data, this is fully backwards compatible.

If you were using non-ASCII data, Django would have behaved unreliably in some cases previously and so backwards-compatibility was neither completely possible nor desirable. However, some people may have been able to get by with non-ASCII data successfully. They might now experience some different errors to previously. Porting code to work correctly with non-ASCII data is fairly simple. Follow this checklist for fastest results.

Changed __init__() parameters in syndication framework's Feed class

In [5654], we changed the Feed class' __init__() method to take an HttpRequest object as its second parameter, instead of the feed's URL. This allows the syndication framework to work without requiring the sites framework.

This only affects people who have subclassed Feed and overridden the __init__() method, and people who have called Feed.__init__() directly from their own code. Feed.__init__() had not been documented.

PO files must be UTF-8 encoded

In [5708] we added the ability to use non-ASCII strings as input to the translation framework. This was a frequently requested feature by developers whose original strings were not in English. A consequence of this change is that we have to specify the encoding of the PO files (the files used by translators to create translations). Following common practice in other projects, we have chosen UTF-8 as the encoding.

This change only affects third-party developers who are using Django's make-messages.py script to extract their strings for their own PO files. The PO files will need to be saved and edited as UTF-8 files and the charset set correctly in the header of the file. All of Django's core translation files already satisfy this requirement.

Note that existing code and message catalogs will not break even if you update your code to [5708] or later. This change only takes effect the next time you run make-messages.py.

Added interactive argument to run_tests

In [5752] the parameter interactive was added to the run_tests method. This parameter allows tests to be added to automated build scripts where interactive questions (such as being asked if it is ok to delete an existing test database) cannot be asked.

If you are using the default Django test runner, no change is required.

However, if you have defined a custom test runner, you must be able to accept (and handle appropriately) the interactive argument to the test runner. For most test runners, the only change required will be to change the call to create_test_db to use the keyword argument autoclobber=not interactive.

Changed format for first argument to run_tests

In [5769], the first argument to run_tests was changed to allow end-users to invoke individual tests, rather than requiring that entire applications be tested.

This change has no effect on most end users. However, it does require a significant change to the prototype of run_test, which will be backwards incompatible for anyone with a customized test runner. Previously, the first argument to run_tests was a list of application modules. These modules would then be searched for test cases. After [5769], the first argument to run_tests is a list of test labels. Whereas run_tests was previously given a list of already-loaded applications, it is now the responsibility of the test runner to load the appropriate applications. Anyone with a customized test runner will need to incorporate the appropriate calls to get_app() as part of their test runner.

Minor change to newforms arguments and data dictionary handling

In [5819], FileField and ImageField were added to newforms. Implementing these fields required some subtle changes to the way data binding occurs in newforms.

This change will have no effect on most end users. The simple cases for binding forms do not change. However:

  • The second argument to a form instance is now taken by file data. As a result, if you implicitly relied upon the order of the keyword arguments auto_id, prefix and initial when instantiating forms, your forms will no longer interpret these options correctly. To overcome this problem, explicitly name these keyword arguments - for example, use:
f = ContactForm(data, auto_id=True)

rather than:

f = ContactForm(data, True)
  • A files argument was added to the prototype for value_from_datadict() to allow for the processing of file data. If you have written any custom widgets that provide their own data dictionary handling, you will need to modify those widgets to accomodate the new argument. If your custom widget has no need to handle file data, all you will need to do is change the method definition from:
def value_from_datadict(self, data, name):

to:

def value_from_datadict(self, data, files, name):

Changes to management.py commands

In [5898], we refactored management.py. This was partially to clean up a 1700-line file, but also to allow user applications to register commands for use in management.py.

As a result, any calls to management services in your code will need to be adjusted. 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 will 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)

Refactored database backends

In a series of commits from [5949] to [5982], we refactored the database backends (the code in django.db.backends) to remove quite a bit of duplication, make it easier to maintain and extend, and to lay the groundwork for advanced features such as database connection pooling.

As a result, almost *all* of the 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:

Old name/location New name/location
django.db.backend.get_autoinc_sql django.db.connection.ops.autoinc_sql
django.db.backend.get_date_extract_sql django.db.connection.ops.date_extract_sql
django.db.backend.get_date_trunc_sql django.db.connection.ops.date_trunc_sql
django.db.backend.get_datetime_cast_sql django.db.connection.ops.datetime_cast_sql
django.db.backend.get_deferrable_sql django.db.connection.ops.deferrable_sql
django.db.backend.get_drop_foreignkey_sql django.db.connection.ops.drop_foreignkey_sql
django.db.backend.get_fulltext_search_sql django.db.connection.ops.fulltext_search_sql
django.db.backend.get_last_insert_id django.db.connection.ops.last_insert_id
django.db.backend.get_limit_offset_sql django.db.connection.ops.limit_offset_sql
django.db.backend.get_max_name_length django.db.connection.ops.max_name_length
django.db.backend.get_pk_default_value django.db.connection.ops.pk_default_value
django.db.backend.get_random_function_sql django.db.connection.ops.random_function_sql
django.db.backend.get_sql_flush django.db.connection.ops.sql_flush
django.db.backend.get_sql_sequence_reset django.db.connection.ops.sequence_reset_sql
django.db.backend.get_start_transaction_sql django.db.connection.ops.start_transaction_sql
django.db.backend.get_tablespace_sql django.db.connection.ops.tablespace_sql
django.db.backend.quote_name django.db.connection.ops.quote_name
django.db.backend.get_query_set_class django.db.connection.ops.query_set_class
django.db.backend.get_field_cast_sql django.db.connection.ops.field_cast_sql
django.db.backend.get_drop_sequence django.db.connection.ops.drop_sequence_sql
django.db.backend.OPERATOR_MAPPING django.db.connection.operators
django.db.backend.allows_group_by_ordinal django.db.connection.features.allows_group_by_ordinal
django.db.backend.allows_unique_and_pk django.db.connection.features.allows_unique_and_pk
django.db.backend.autoindexes_primary_keys django.db.connection.features.autoindexes_primary_keys
django.db.backend.needs_datetime_string_castdjango.db.connection.features.needs_datetime_string_cast
django.db.backend.needs_upper_for_iops django.db.connection.features.needs_upper_for_iops
django.db.backend.supports_constraints django.db.connection.features.supports_constraints
django.db.backend.supports_tablespaces django.db.connection.features.supports_tablespaces
django.db.backend.uses_case_insensitive_names django.db.connection.features.uses_case_insensitive_names
django.db.backend.uses_custom_queryset django.db.connection.features.uses_custom_queryset

archive_year generic view no longer performs an order_by() on the passed queryset

In [6057], archive_year was changed NOT to perform an order_by() on the passed queryset. This is for consistancy with the other date-based generic views.

Update your code by performing an explicit order_by() on the QuerySet you pass to archive_year or add an ordering option to your model's class Meta.

django-admin.py and manage.py now require subcommands to precede options

In [6075], django-admin.py and manage.py were changed so that any options (e.g., --settings) must be specified *after* the subcommand (e.g., runserver).

This used to be accepted: django-admin.py --settings=foo.bar runserver

Now, options must come after the subcommand: django-admin.py runserver --settings=foo.bar

django.views.i18n.set_language requires a POST request

In [6177], the set_language() view was changed to only change the caller's locale if called as a POST request. Previously, a GET request was used. The old behaviour 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).

django.http.HttpResponse now has case-insensitive headers

In a number of changes starting with [6212], HttpResponse headers were made case-insensitive.

As a side-effect, could break code that accesses response.headers directly, and specifically broke the idiom if header in response.headers:.

So response.headers has been renamed to response._headers, and HttpResponse now supports containment checking directly. You should now use if header in response instead of if header in response.headers.

Template tag loading respects dotted notation

In [6289], template tag loading was altered to respect dotted paths, just like Python imports. So

{% load foo.bar %}

will now look for a file foo/bar.py under one of your templatetag directories.

Previously, anything before the final dot was ignored. If you were using this for any decorative purpose (at least one package was, it turns out), you have to remove any unwanted prefix.

_() no longer in builtins

As of [6582], we no longer unconditionally install _() as an alias for gettext() in Python's builtins module. The reason for this is explained in the initial note in this section of the internationalization documentation.

If you were previously relying on _() always being present, you should now explicitly import ugettext or ugettext_lazy or even gettext, if appropriate, and alias it to _ yourself:

from django.utils.translation import ugettext as _

django.newforms.forms.SortedDictFromList class removed

In [6668], the SortedDictFromList class was removed. Due to django.utils.datastructures.SortedDict gaining the ability to be instantiated with a sequence of tuples (#5744), SortedDictFromList was no longer needed. Two things need to be done to fix your code:

  1. Use django.utils.datastructures.SortedDict wherever you were using django.newforms.forms.SortedDictFromList.
  2. Since SortedDict's copy method doesn't return a deepcopy as SortedDictFromList's copy method did, you will need to update your code if you were relying on SortedDictFromList.copy to return a deepcopy. Do this by using copy.deepcopy instead of SortedDict's copy method.

Auto-escaping in templates

In [6671] a long-awaited feature was committed to make default HTML template usage a bit safe from some forms of cross-site scripting attacks. For full details, read the template author documentation and the template filter documentation.

Automatic HTML escaping (henceforth auto-escaping) affects any variables in templates. It is only applied to variables and not to template tags.

{{ a_variable }}   -- This will be auto-escaped.
{{ variable|filter1|filter1 }} -- so will this
{% a_template_tag %} -- This will not be; it's a tag

Also note that the escape filter's behaviour has changed slightly (it is only applied once and only as the last thing done to the output) and new filters force_escape and safe have been introduced if you need them.

If you never intentionally insert raw HTML into your context variables, no changes will be needed.

If you have any variables in your templates that do insert raw HTML output and you do not want this output automatically escaped, you will need to make some changes. There are two approaches here, the first one being the easiest (but not the safest) and can act as a temporary step whilst you do the more detailed second step.

  1. Simple backwards compatibility: In every root template (such as your base.html template), wrap the entire contents in tags to disable auto-escaping:
{% autoescape off %}
   ... normal template content here ...
{% endautoescape %}

If one template extends from another template, the auto-escaping behaviour from the root template (the one in the {% extends ... %} tag) will be inherited. So you only need to add these tags to your root templates.

This is also the recommended approach for templates that produce, say, email messages or other non-HTML output. The autoescape template tag exists for this reason.

The drawback of this method in templates that are destined for HTML output is that you receive none of the benefits of auto-escaping.

  1. More detailed porting to take advantage of auto-escaping: There are two approaches to inserting raw HTML via variables into templates. One option is to pass the variable content through the safe filter. This tells the renderer not to auto-escape the contents:

{{ variable_with_html|safe }} -- will not be auto-escaped.

The second and probably more robust way is to use mark_safe() where appropriate (see the documentation for details) and go through each of your custom filters attaching is_safe and needs_autoescape attributes where necessary (again, the details are in the above documentation).

Have a look at Django's default filters (in django/template/defaultfilters.py) for examples of how mixed filters (those which behave differently depending upon whether auto-escaping is in effect or not) can be written.

The attributes on filter functions and the new escape behaviour mean that you can write templates and filters that will operate correctly in both auto-escaping and non-auto-escaping environments, so you're not forcing your decision onto users of your filters, if you distribute the code.

Removed unused session backend functions

In [6796] a couple of session backend functions that are no longer needed were removed. In the unlikely event your code was using get_new_session_key() or get_new_session_object(), you will need to switch to using SessionBase._get_new_session_key().

Settings exception types changed

In [6832] we changed the possible exceptions raised when initializing Django's settings in order not to interfere with Python's help() function. If there is a problem such as not being able to find the settings module (via the DJANGO_SETTINGS_MODULE environment variable, for example), an ImportError is now raised (previously it was EnvironmentError). If you try to reconfigure settings after having already used them, a RuntimeError is raised (rather than the previous EnvironmentError). See #5743 for the details of this change.

Generic views' empty defaults

In [6833] the allow_empty parameter to the archive_index() and object_list() generic views were changed to be True by default. So viewing a list of objects that is empty will not raise a 404 HTTP error. Things like date-based views still raise a 404 when there is nothing to view (by default), since viewing a non-existent date is usually going to be an error. See #685 for details.

MultipleObjectsReturned exception instead of AssertionError

In [6838], QuerySet.get() was changed to raise a MultipleObjectsReturned exception rather than an assertion error when multiple objects are returned.

Before:

try:
    Model.objects.get(...)
except AssertionError:
    ...

After:

try:
    Model.objects.get(...)
except Model.MultipleObjectsReturned:
    ...

ModelForm's constructor now matches Form's

instance was moved from the first argument in ModelForm's __init__ method to the last. Also, ModelForm will instantiate a new model object if you don't give it an instance. The model it generates is specified by the ModelForm's inner Meta classes model attribute. See #6162 for details.

Before:

# for add forms
obj = MyObj()
form = MyForm(obj)

# for change forms
obj = MyObj.objects.get(pk=1)
form = MyForm(obj)

After:

# for add forms
form = MyForm()

# for change forms
obj = MyObj.objects.get(pk=1)
form = MyForm(instance=obj)
Note: See TracWiki for help on using the wiki.
Back to Top