= 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.96 release. For changes prior to 0.96, see the OlderBackwardsIncompatibleChanges page. == Table of Contents == Changes made after Django [source:/django/tags/releases/0.96 0.96]: * [4885] March 31, 2007: [http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#Changedspacelesstemplatetagtoremoveallspaces Changed 'spaceless' template tag to remove all spaces] * [4954] April 8, 2007: [http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#Renamedlocalflavor.usatolocalflavor.us Renamed `localflavour.usa` to `localflavor.us`] * [4985] April 9, 2007: [http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#RemovedLazyDate Removed `LazyDate`] * [5042] April 20, 2007: [http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#MySQLIntrospectionChange MySQL Introspection change] * [5072] April 25, 2007: [http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#LOGIN_URLisnowasetting Login URL is now a setting] * [5152] May 5, 2007: [http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#TestClientloginmethodchanged Test Client `login()` method changed] * [5172] May 8, 2007: [http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#Genericrelationshavemoved Generic relations have moved] * [5237] May 14, 2007: [http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#Newforms:clean_datachangedtocleaned_data Newforms: clean_data changed to cleaned_data] * [5302] May 20, 2007: [http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#RenamedFloatFieldtoDecimalField Renamed FloatField to DecimalField] * [5465] June 11, 2007: [http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#Testclientremovedspecialcasingforfileuploads Test client removed special casing for file uploads] * [5516] June 22, 2007: [http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#Urlpatternsnowcached Urlpatterns now cached] * [5609] July 4, 2007: [http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#Unicodemerge Unicode merge] * [5654] July 12, 2007: [http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#Changed__init__parametersinsyndicationframeworksFeedclass Changed __init__() parameters in syndication framework's Feed class] * [5708] July 15, 2007: [http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#POfilesmustbeUTF-8encoded PO files must be UTF-8 encoded] * [5752] July 23, 2007: [http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#Addedinteractiveargumenttorun_tests Added `interactive` argument to `run_tests`] * [5769] July 28, 2007: [http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#Changedformatforfirstargumenttorun_tests Changed format for first argument to `run_tests` ] * [5819] Aug. 6, 2007: [http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#Minorchangetonewformsargumentsanddatadictionaryhandling Minor change to newforms arguments and data dictionary handling ] * [5898] Aug. 16, 2007: [http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#Changestomanagement.pycommands Changes to management.py commands ] * (Multiple) Aug. 19, 2007: [http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#Refactoreddatabasebackends Refactored database backends] * [6057] Sept. 6, 2007: [http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#archive_yeargenericviewnolongerperformsanorder_byonthepassedqueryset archive_year generic view no longer performs an order_by() on the passed queryset] * [6075] Sept. 9, 2007: [http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#django-admin.pyandmanage.pynowrequiresubcommandstoprecedeoptions django-admin.py and manage.py now require subcommands to precede options] * [6177] Sept. 14, 2007: [http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#django.views.i18n.set_languagerequiresaPOSTrequest django.views.i18n.set-language requires a POST request] * [6212] Sept. 14, 2007: [http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#django.http.HttpResponsenowhascase-insensitiveheaders HttpResponse now has case-insensitive headers, and response.headers is no longer directly accessible] * [6289] Sept. 15, 2007: [http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#Templatetagloadingrespectsdottednotation Template tag loading respects dotted notation] * [6582] Oct. 21, 2007: [http://code.djangoproject.com/wiki/BackwardsIncompatibleChanges#_nolongerinbuiltins _() no longer in builtins] * [6668] Nov. 10, 2007: [#django.newforms.forms.SortedDictFromListclassremoved django.newforms.forms.SortedDictFromList class removed] * [6671] Nov. 15, 2007: [#Auto-escapingintemplates Auto-escaping in templates] * [6796] Dec 1, 2007: [#Removedunusedsessionbackendfunctions Removed unused session backend functions] * [6832] Dec 2, 2007: [#Settingsexceptiontypeschanged Settings exception types changed] * [6833] Dec 2, 2007: [#Genericviewsemptydefaults Generic views' empty defaults] * [6838] Dec. 2, 2007: [#MultipleObjectsReturnedexceptioninsteadofAssertionError MultipleObjectsReturned exception instead of AssertionError] * [6852] Dec. 2, 2007: [#ChangetoAPPEND_SLASHbehaviour Change to APPEND_SLASH behaviour] * [6915] Dec 12, 2007 [#ModelFormsconstructornowmatchesForms ModelForm's constructor now matches Form's] * [7082] Feb 3, 2008 [#Raiseerrorsifextendsisnotthefirsttagofatemplate Raise errors if extends is not the first tag of a template] * [7091] Feb 5, 2008 [#Changees_ARtoes-arinLANGUAGES Change es_AR to es-ar in LANGUAGES] * [7133] Feb 18, 2008 [#ChangedField.get_internal_typedefault Changed Field.get_internal_type() default] * [7153] Feb 25, 2008 [#Changeddecoratorstoinheritattributesofthefunctiontheywrap Changed decorators to inherit attributes of the function they wrap] * [7364] March 26, 2008 [#Removedado_mssql Removed ado_mssql] * [7477] April 27, 2008 [#Queryset-refactormerge Queryset-refactor merge] * [7574] June 4, 2008 [#TightenedupForeignKeyassignment Tightened up ForeignKey assignment] * [7794] June 30, 2008 [#RemovedOrderingField Removed OrderingField] * [7798] June 30, 2008 [#ExactComparisonsRespectCaseInMySQL Exact Comparisons Respect Case In MySQL] * [7799] June 30, 2008 [#BooleanFieldsinnewformsenforcerequired BooleanFields in newforms enforce "required"] * [7806] June 30, 2008 [#DefaultUserorderingremoved Default User ordering removed] * [7814] July 1, 2008 [#Uploadedfilechanges Uploaded file changes] * [7844] July 6, 2008 [#Translationtoolsnowpartofdjango-admin.py Translation tools now part of `django-admin.py`] * [7859] July 7, 2008 [#Uploadedfilechanges More uploaded file changes]. * [7923] July 14, 2008 [#ChangestoModelAdminsave_addsave_changeandrender_change_formmethodsignatures Changes to ModelAdmin save_add, save_change and render_change_form method signatures]. * [7949] July 18, 2008 [#MySQL_oldbackendremoved MySQL_old backend removed] * [7952] July 18, 2008 [#Genericviewsusenewforms Create/update generic views use newforms] * [7967] July 18, 2008 [#Mergednewforms-adminintotrunk Merged newforms-admin into trunk] * [7971] July 18, 2008 [#Movednewformstoforms Moved django.newforms to django.forms] * [8015] July 19, 2008 [#ChangedthewayURLpathsaredetermined Changed the way URL paths are determined] * [8131] July 29, 2008 [#intfield__infilterlookupsnolongerwork intfield__in=('',) filter lookups no longer work] * [8143] July 30, 2008 [#DecimalFieldconversiontightened DecimalField conversion tightened] * [8162] July 30, 2008 [#Passwordresetsystemchangedtoimprovesecurityandusability Password reset system changed to improve security and usability ] * [8191] Aug. 1, 2008 [#Removedseveraldeprecatedfeaturesfor1.0 Removed several deprecated features for 1.0] * [8202] Aug. 3, 2008 [#Removeddictionaryaccesstorequestobject Removed dictionary access to request object] * [8211] Aug. 5, 2008 [#urltagnowallowsNoReverseMatchexceptionstopropagate url tag now allows !NoReverseMatch exceptions to propagate] * [8223] Aug 6, 2008 [#Signalrefactoring Signal/dispatch refactoring] == 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 %}
{% endspaceless %} }}} The old output was this: {{{ }}} The new output is this: {{{ }}} 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`: {{{ #!python class Article(models.Model): title = models.CharField(maxlength=100) published = models.DateField(default=LazyDate()) }}} can be redefined as: {{{ #!python 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 {{{ #!python 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.: {{{ #!python c = Client() c.login('/path/to/login','myuser','mypassword') }}} should be modified to read: {{{ #!python 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 {{{ #!python generic_field = models.GenericRelation(SomeOtherModel) }}} to using {{{ #!python 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 {{{ #!python class MyModel(models.Model): ... field_name = models.FloatField(max_digits=10, decimal_places=3) # old code! }}} to {{{ #!python 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 [http://code.djangoproject.com/wiki/UnicodeBranch#PortingApplicationsTheQuickChecklist 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_cast}}}||{{{django.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 [http://www.djangoproject.com/documentation/i18n/#in-python-code this section] of the [http://www.djangoproject.com/documentation/i18n/ 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: {{{ #!python 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`. 1. 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 [http://www.djangoproject.com/documentation/templates/#automatic-html-escaping template author documentation] and the [http://www.djangoproject.com/documentation/templates_python/#filters-and-auto-escaping 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, and if all your templates are supposed to output HTML, 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. Finally, change all calls to the 'escape' function to use 'force_escape' instead. The drawback of this method in templates that are destined for HTML output is that you receive none of the benefits of auto-escaping. 2. '''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 [http://www.djangoproject.com/documentation/templates_python/#filters-and-auto-escaping 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: {{{ #!python try: Model.objects.get(...) except AssertionError: ... }}} After: {{{ #!python try: Model.objects.get(...) except Model.MultipleObjectsReturned: ... }}} == Change to APPEND_SLASH behaviour == Prior to [6852], if a URL didn't end in a slash or have a period in the final component of it's path, and {{{APPEND_SLASH}}} was True, Django would redirect to the same URL, but with a slash appended to the end. Now (from [6852] onwards), Django checks to see if 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: {{{ #!python 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 {{{ #!python r'/some_prefix/(.*/)$' }}} Now {{{/some_prefix/foo}}} will not match the pattern and will be redirected to {{{/some_prefix/foo/}}}, which will match. Your view function will be passed {{{foo/}}}, just as it was prior to [6852]. == !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: {{{ #!python # for add forms obj = MyObj() form = MyForm(obj) # for change forms obj = MyObj.objects.get(pk=1) form = MyForm(obj) }}} After: {{{ #!python # for add forms form = MyForm() # for change forms obj = MyObj.objects.get(pk=1) form = MyForm(instance=obj) }}} == Raise errors if extends is not the first tag of a template == In [7082] we removed the possibility of putting extends below other tags (in [7089]). You may still put it below comment tags or whitespace. Otherwise it will raise a !TemplateSyntaxError. This is not technically a backward incompatible change because documentation has always specified that [http://www.djangoproject.com/documentation/templates/#template-inheritance extends tags should be placed first in your templates], but it may break your code if you haven't followed those instructions. == Change es_AR to es-ar in LANGUAGES == In [7091] we fixed a bug that was inadvertently introduced recently with regards to parsing language preferences. The LANGUAGES list in the configuration file should use a hyphen to separate the locale main name and the variant (if any). The only slight consequence here is that the Argentinean Spanish was incorrectly specified as es_AR. If you are using Django's default language list, there is no change required, since the central list has been updated and specifying es-ar as the preference in your browser will still work. However, if you have created a custom LANGUAGES list in your settings file, overriding the list in global_settings, you will need to change "es_AR" to read "es-ar", otherwise your Argentinean Spanish readers will see English text, not Spanish. == Changed Field.get_internal_type() default == If you were subclassing an existing model field and were relying on the fact that the {{{Field.get_internal_type()}}} method would return your class name by default, you will need to alter your code to explicitly say that (i.e. to return the string you want). It seemed that most subclassing of existing fields (even in Django core) were using the parent's column type, so we've now set things up to exploit that. Thus, inheriting from a {{{CharField}}} results in {{{get_internal_type()}}} returning ''!CharField'', etc. == Changed decorators to inherit attributes of the function they wrap == If you were using one of Django's decorators and relying on decorated functions/methods to have the name (`__name__`), docstring (`__doc__`), or attributes (`__dict__`) of the decorator, then you need to change your code to work off the name, docstring, or attributes of the decorated function instead. If you are using Python 2.3, you don't need to worry about the changing of the decorated function's name since Python 2.3 does not support assignment to `__name__`. == Removed ado_mssql == The unmaintained `ado_mssql` backend was removed in [7364]. Since external database backends can be used with Django, people wishing to contribute to or utilise Django support on Microsoft's SQL Server should have a look at external projects such as: * http://code.google.com/p/django-mssql/ * http://code.google.com/p/django-pyodbc/ Django's maintainers do not make any recommendations as to which of these (or alternate projects) may be better at this time, since none of us regularly develop on Windows or with SQL Server. == Queryset-refactor merge == In [7477] the [QuerysetRefactorBranch queryset-refactor branch] was merged into trunk. This is mostly backwards compatible, but there are a few points to note. Rather than list them here, refer to the [QuerysetRefactorBranch#Backwardsincompatiblechanges backwards incompatible changes] section on the branch's wiki page. == Tightened up !ForeignKey assignment == [7574] tightened up restrictions on assigning objects to {{{ForeignKey}}} and {{{OneToOne}}} fields (i.e. assignments of the form {{{book.author = someinstance}}}). Specifically, Django will now: * Raise a {{{ValueError}}} if you try to assign the wrong type of object. Previously things like {{{book.author = Vegetable(...)}}} worked, which was insane. * Raise a {{{ValueError}}} if you try to assign {{{None}}} to a field not specified with {{{null=True}}}. * Cache the set value at set time instead of just at lookup time. This avoids race conditions when saving related objects along with their parents and was the original impetus for the change (see #6886). == Removed !OrderingField == In [7794], a model field that had no use was removed (!OrderingField). It was introduced in the magic-removal branch to support a feature that was never completed, so the code isn't useful in Django any longer. == Exact Comparisons Respect Case In MySQL == The documented behaviour of the `__exact` comparison in Django's ORM is that it is case-sensitive. For case-insensitive searches, there is `__iexact`. On some MySQL installations (which includes most default installations), `__exact` was using case-''in''sensitive matching. This was changed in [7798] so that using `__exact` will respect the case of the argument, bringing it into line with all the other database backends. == !BooleanFields in newforms enforce "required" == In [7799], a bug was fixed that meant `required=True` is now enforced by the `newforms.BooleanField`. In this case, "required" means "must be checked" (i.e. must be True). This has been documented for quite a while, but was inadvertently not enforced by the validation code. Since `required=True` is the default behaviour for all form fields, if your code does not set `required=False` on a `BooleanField` and you do not require it to be checked in the HTML form, you will need to change your form definition. == Default User ordering removed == Since the `django.contrib.auth.models.User` class frequently has a lot of rows in the database and is queried often, we have removed the default ordering on this class in [7806]. If your code requires users to be ordered by their name, you will need to manually specify `order_by('name')` in your querysets. Note that the admin interface will still behave the same as prior to this change, since it automatically orders the users by their name, just as before. So no change will be noticable there. == Uploaded file changes == [7814] introduced changes to the way Django handles uploaded files. Most of the changes are in the form of added flexibility and performance (for details, see the [http://www.djangoproject.com/documentation/upload_handing/ upload handling documentation]). However, as part of the change, the representation of uploaded files has changed. Previous, uploaded files -- that is, entries in {{{request.FILES}}} -- were represented by simple dictionaries with a few well-known keys. Uploaded files are now represented by an [http://www.djangoproject.com/documentation/upload_handling/#uploaded-file-objects UploadedFile object], and thus the file's information is accessible through object attributes. Thus, given {{{f = request.FILES['some_field_name']}}}: || '''Deprecated''' || '''Replacement''' || || {{{f['content']}}} || {{{f.read()}}} || || {{{f['filename']}}} || {{{f.name}}} || || {{{f['content-type']}}} || {{{f.content_type}}} || The old dictionary-style key access is still supported, but will raise a {{{DeprecationWarning}}}. [7859] further cleaned up and clarified this interface. It unfortunatly had to deprecate a couple of things added in [7814]: || '''Deprecated''' || '''Replacement''' || || {{{f.file_name}}} || {{{f.name}}} || || {{{f.file_size}}} || {{{f.size}}} || || {{{f.chunk()}}} || {{{f.chunks()}}} (note the plural) || The old attributes will still work, but will issue deprecation warnings. Finally, [7859] removed the {{{django.newforms.fields.UploadedFile}}} class, unifying the use of {{{django.core.files.uploadedfile.UploadedFile}}} everywhere in Django. This means a few API changes when using a form with a {{{FileField}}}. || '''Deprecated''' || '''Replacement''' || || {{{form.cleaned_data['file_field'].filename}}} || {{{form.cleaned_data['file_field'].name}}} || || {{{form.cleaned_data['file_field'].data}}} || {{{form.cleaned_data['file_field'].read()}}} || == Translation tools now part of `django-admin.py` == In [7844] the tools to extract strings for translations (formerly `make-messages.py`) and compile the PO files (formerly `compile-messages.py`) were moved into `django-admin.py`. The old executables still exist, but raise errors and point the caller towards the correct command to run (both of these files are designed to be run from the command line, not as part of other programs). || '''Old command''' || ''' New ''' || || {{{make-messages.py -l xx}}} || {{{django-admin.py makemessages -l xx}}} || || {{{compile-messages.py -l xx}}} || {{{django-admin.py compilemessages -l xx}}} || The options accepted by the new form are the same as the options accepted by the old commands, only the calling convention has changed (this reduced the number of executables Django has to install). == MySQL_old backend removed == In [7949] we removed the `mysql_old` backend. It was only available for backwards compatibility with a version of `MySQLdb` that was in popular use in some ISPs at the time. Since `MySQLdb-1.2.1p2` was released over 2 years ago and that version, or anything later, works with the `mysql` backend, we have now removed this formerly deprecated backend. == Changes to !ModelAdmin save_add, save_change and render_change_form method signatures == [7923] removed some places where model was being passed around for no reason - this will only affect you if you have custom ModelAdmin subclasses that over-ride those methods. == Generic views use newforms == [7952] makes the create/update generic views use newforms instead of the old deprecated "oldforms" package. You'll probably need to update any templates used by these views. == Merged newforms-admin into trunk == As of [7967] we have merged newforms-admin into trunk. Here is list of things that have changed: A lot has changed in this branch. Let's start with the syntax for URLconfs: {{{ #!python # OLD: from django.conf.urls.defaults import * urlpatterns = patterns('', (r'^admin/', include('django.contrib.admin.urls')), ) # NEW: from django.conf.urls.defaults import * from django.contrib import admin admin.autodiscover() urlpatterns = patterns('', (r'^admin/(.*)', admin.site.root), ) }}} Note that, in this above URLconf example, we're dealing with the object {{{django.contrib.admin.site}}}. This is an instance of {{{django.contrib.admin.AdminSite}}}, which is a class that lets you specify admin-site functionality. The object {{{django.contrib.admin.site}}} is a default {{{AdminSite}}} instance that is created for you automatically, but you can also create other instances as you see fit. We use the {{{admin.autodiscover()}}} call above to force the import of the {{{admin.py}}} module of each {{{INSTALLED_APPS}}} entry. This won't be needed if you use your own {{{AdminSite}}} instance since you will likely be importing those modules explicily in a project-level {{{admin.py}}}. This was added in [7872]. Previously, there was one "global" version of the admin site, which used all models that contained a {{{class Admin}}}. This new scheme allows for much more fine-grained control over your admin sites, allowing you to have multiple admin sites in the same Django instance. === Changed Admin.manager option to more flexible hook === The {{{manager}}} option to {{{class Admin}}} no longer exists. This option was undocumented, but we're mentioning the change here in case you used it. In favor of this option, a {{{ModelAdmin}}} class may now define a ```queryset``` method: {{{ #!python class BookAdmin(admin.ModelAdmin): def queryset(self, request): """ Filter based on the current user. """ return self.model._default_manager.filter(user=request.user) }}} === Changed prepopulate_from to be defined in the Admin class, not database field classes === The {{{prepopulate_from}}} option to database fields no longer exists. It's been discontinued in favor of the new {{{prepopulated_fields}}} option in a {{{ModelAdmin}}}. The new {{{prepopulated_fields}}} option, if given, should be a dictionary mapping field names to lists/tuples of field names. This change was made in an effort to remove admin-specific options from the model itself. Here's an example comparing old syntax and new syntax: {{{ #!python # OLD: class MyModel(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30) slug = models.CharField(max_length=60, prepopulate_from=('first_name', 'last_name')) class Admin: pass # NEW: class MyModel(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30) slug = models.CharField(max_length=60) from django.contrib import admin class MyModelAdmin(admin.ModelAdmin): prepopulated_fields = {'slug': ('first_name', 'last_name')} admin.site.register(MyModel, MyModelAdmin) }}} === Moved admin doc views into django.contrib.admindocs === The documentation views for the Django admin site were moved into a new package, {{{django.contrib.admindocs}}}. The admin docs, which aren't documented very well, were located at {{{docs/}}} in the admin site. They're also linked-to by the "Documentation" link in the upper right of default admin templates. Because we've moved the doc views, you now have to activate admin docs explicitly. Do this by adding the following line to your URLconf: {{{ #!python (r'^admin/doc/', include('django.contrib.admindocs.urls')), }}} You have to add this line before {{{r'^admin/(.*)'}}} otherwise it won't work. === Renamed 'fields' to 'fieldsets', and changed type of 'classes' value === 'Fields' is used to order and group fields in the change form layout.[[BR]] It is still available in the new admin, but it accepts only a list of fields. [[BR]] In case one uses fieldsets to organize the fields, one needs to use 'fieldsets' instead. [[BR]] Also, if 'classes' is specified in a field specification, then the type of its value needs to be changed from a string to a tuple of strings when migrating to the new 'fieldsets' specification. An example: {{{ #!python # OLD: class MyModelA(models.Model): class Admin: fields = ('field1','field2','field3','field4') class MyModelB(models.Model): class Admin: fields = ( ('group1', {'fields': ('field1','field2'), 'classes': 'collapse'}), ('group2', {'fields': ('field3','field4'), 'classes': 'collapse wide'}), ) # NEW: class MyModelAdmin(admin.ModelAdmin): fields = ('field1', 'field2', 'field3', 'field4') # Renaming is optional class AnotherModelAdmin(admin.ModelAdmin): fieldsets = ( ('group1', {'fields': ('field1','field2'), 'classes': ('collapse',)}), ('group2', {'fields': ('field3','field4'), 'classes': ('collapse', 'wide')}), ) }}} === Inline editing === We have significantly improved working with inlines. Here is an example: {{{ #!python # OLD: class Author(models.Model): name = models.CharField(max_length=100) class Book(models.Model): author = models.ForeignKey(Author, edit_inline=models.TABULAR) title = models.CharField(max_length=100) # NEW: # no longer need the edit_inline database field option above. just remove it. from django.contrib import admin class BookInline(admin.TabularInline): model = Book extra = 3 class AuthorAdmin(admin.ModelAdmin): inlines = [BookInline] admin.site.register(Author, AuthorAdmin) }}} Before you would define an {{{edit_inline}}} database field option and give it the value of the type of inline you wanted, either {{{models.TABULAR}}} or {{{models.STACKED}}}. This is now done with classes and gives you much more flexibility over the options of a specific inline. See [http://www.djangoproject.com/documentation/admin/#inlinemodeladmin-objects the inlines documentation] to learn more. === Refactored inner Admin ```js``` option to media definitions === In [5926] a new method of dealing with media definitions was added. It is now much more flexible and allows media on more than just a {{{ModelAdmin}}} classes. An example: {{{ #!python # OLD: class MyModel(models.Model): # not relavent, but here for show field1 = models.CharField(max_length=100) class Admin: js = ( "/static/my_code.js", ) # NEW: # in admin.py class MyModelAdmin(admin.ModelAdmin): class Media: js = ( "/static/my_code.js", ) }}} One very subtle thing to note is previously with trunk the documentation stated: If you use relative URLs — URLs that don’t start with {{{http://}}} or {{{/}}} — then the admin site will automatically prefix these links with {{{settings.ADMIN_MEDIA_PREFIX}}}. Which is still partially true with newforms-admin, but now when using relative URLs {{{settings.MEDIA_URL}}} is prepended and '''not''' {{{settings.ADMIN_MEDIA_PREFIX}}}. If you are still looking for the old behavior you can accomplish it by doing the following: {{{ #!python from django.conf import settings class MyModelAdmin(admin.ModelAdmin): class Media: js = ( settings.ADMIN_MEDIA_PREFIX + "some_file.js", ) }}} Make sure the value of {{{settings.ADMIN_MEDIA_PREFIX}}} is a proper absolute URL otherwise it will be treated the same as a relative URL. === Moved raw_id_admin from the model definition === The syntax is now separated from the definition of your models. An example: {{{ #!python # OLD: class MyModel(models.Model): field1 = models.ForeignKey(AnotherModel, raw_id_admin=True) class Admin: pass # NEW: class MyModelAdmin(admin.ModelAdmin): model = MyModel raw_id_fields = ('field1',) }}} === ```django.contrib.auth``` is now using newforms === ```django.contrib.auth``` has been converted to use newforms as opposed to using oldforms. If you are relying on the oldforms, you will need to modify your code/templates to work with newforms. === Moved radio_admin from the model definition === An example: {{{ #!python # OLD: class MyModel(models.Model): field1 = models.ForeignKey(AnotherModel, radio_admin=models.VERTICAL) class Admin: pass # NEW: class MyModelAdmin(admin.ModelAdmin): model = MyModel radio_fields = {'field1': admin.VERTICAL} }}} === Moved filter_interface from the model definition === An example: {{{ #!python # OLD: class MyModel(models.Model): field1 = models.ManyToManyField(AnotherModel, filter_interface=models.VERTICAL) field2 = models.ManyToManyField(YetAnotherModel, filter_interface=models.HORIZONTAL) # NEW: class MyModelAdmin(admin.ModelAdmin): filter_vertical = ('field1',) filter_horizontal = ('field2',) }}} == Moved newforms to forms == In [7971] Django's {{{newforms}}} library was finally renamed to just plain old {{{forms}}}. Imports of {{{django.newforms}}} will still work but will throw a {{{DeprecationWarning}}}. Deeper imports throw {{{ImportError}}}s. In general, you'll just need to change {{{from django import newforms as forms}}} to {{{from django import forms}}}. {{{django.oldforms}}} is still available, but really you shouldn't be using it. == Changed the way URL paths are determined == A long-awaited change was made in [8015] to the way Django works with HTTP URL strings. Web servers separate request URLs into two pieces, the `SCRIPT_NAME` and the `PATH_INFO` (there is also the `QUERY_STRING` for query parameters, but that is not relevant here). Prior to this change, Django was not using the `SCRIPT_NAME` at all, which caused a number of problems, including some portability issues. The main goal of this change is to make it possible to move a Django project from being served under, say, URLs of the form `/mysite/*` to `/my_other_site/*` without needing any changes in the ``URLConf`` file. The prefix (`/mysite/` and `/my_other_site/`) is the `SCRIPT_NAME` portion and Django only really needs to act on the `PATH_INFO` portion (the remainder of the URL). If your Django project handles all the URLs under `'/'`, you shouldn't have to make any changes in most cases. In all other cases, some alterations will be required. This change will have different effects for different web server setups. In all cases, you should remove the `SCRIPT_NAME` portion of the URLs from your `URLConf` file (they currently need to be included). === mod_python === If you are using mod_python and change nothing (not even `URLConf`), your code will still work and will be as non-portable as before. You may wish to leave things this way. However, if you wish to take advantage of the portability improvements, remove the script prefix from `URLConf` and set the prefix using the `django.root` option in mod_python's configuration. A configuration section that used to look like this: {{{