1.0-porting-guide.txt 26.1 KB
Newer Older
1 2 3 4 5 6
=========================================
Porting your apps from Django 0.96 to 1.0
=========================================

.. highlight:: python

7
Django 1.0 breaks compatibility with 0.96 in some areas.
8 9 10 11 12 13 14 15

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.

.. seealso::

16
    The :doc:`1.0 release notes </releases/1.0>`. That document explains the new
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
    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.

34
See :doc:`/ref/unicode` for full details.
35 36 37 38 39 40 41 42 43 44

Models
------

Common changes to your models file:

Rename ``maxlength`` to ``max_length``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Rename your ``maxlength`` argument to ``max_length`` (this was changed to be
45
consistent with form fields):
46 47 48 49 50 51 52 53 54 55 56

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
57
and has been moved to the ``ModelAdmin`` class in ``admin.py``. See `the
58 59
admin`_, below, for more details about changes to the admin.

60 61 62 63 64 65 66 67 68
Remove ``core``
~~~~~~~~~~~~~~~

Remove the ``core`` argument from your model fields. It is no longer
necessary, since the equivalent functionality (part of :ref:`inline editing
<admin-inlines>`) 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``.

69 70 71 72 73 74 75 76
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.

77 78 79 80 81
.. seealso::

    A contributor to djangosnippets__ has written a script that'll `scan your
    models.py and generate a corresponding admin.py`__.

82 83
    __ https://www.djangosnippets.org/
    __ https://www.djangosnippets.org/snippets/603/
84

85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
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']

100 101 102
        def __str__(self):
            return '%s %s' % (self.first_name, self.last_name)

103 104 105 106 107 108 109
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)

110 111 112
        def __unicode__(self):
            return u'%s %s' % (self.first_name, self.last_name)

113 114 115 116
New (1.0) ``admin.py``::

    from django.contrib import admin
    from models import Author
117

118
    class AuthorAdmin(admin.ModelAdmin):
119
        list_display = ['first_name', 'last_name']
120 121 122 123 124 125 126 127 128 129 130 131
        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
132
has been rewritten to use Django's new form-handling library and redesigned with
133 134
extensibility and customization in mind.

135
Practically, this means you'll need to rewrite all of your ``class Admin``
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
declarations. You've already seen in `models`_ above how to replace your ``class
Admin`` with a ``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):
        ...
151

152 153 154 155 156 157 158 159 160
    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
161

162
    class ParentAdmin(admin.ModelAdmin):
163 164
        model = Parent
        inlines = [ChildInline]
165

166
    admin.site.register(Parent, ParentAdmin)
167

168 169
See :ref:`admin-inlines` for more details.

170 171
Simplify ``fields``, or use ``fieldsets``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
172 173 174 175

The old ``fields`` syntax was quite confusing, and has been simplified. The old
syntax still works, but you'll need to use ``fieldsets`` instead.

176
Old (0.96)::
177 178 179

    class ModelOne(models.Model):
        ...
180

181 182 183 184
        class Admin:
            fields = (
                (None, {'fields': ('foo','bar')}),
            )
185

186 187
    class ModelTwo(models.Model):
        ...
188

189 190 191 192 193
        class Admin:
            fields = (
                ('group1', {'fields': ('foo','bar'),   'classes': 'collapse'}),
                ('group2', {'fields': ('spam','eggs'), 'classes': 'collapse wide'}),
            )
194

195 196 197 198 199

New (1.0)::

    class ModelOneAdmin(admin.ModelAdmin):
        fields = ('foo', 'bar')
200

201 202 203 204 205
    class ModelTwoAdmin(admin.ModelAdmin):
        fieldsets = (
            ('group1', {'fields': ('foo','bar'),   'classes': 'collapse'}),
            ('group2', {'fields': ('spam','eggs'), 'classes': 'collapse wide'}),
        )
206

207 208 209 210 211 212 213

.. seealso::

    * 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
214
      the :doc:`admin documentation </ref/contrib/admin/index>`.
215

216
    __ https://code.djangoproject.com/wiki/NewformsAdminBranch
217

218
URLs
219 220 221 222 223
----

Update your root ``urls.py``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

224
If you're using the admin site, you need to update your root ``urls.py``.
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257

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
258
``oldforms`` module was also removed.
259

260 261 262
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.
263

264 265 266 267 268 269 270 271 272 273
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
274
is the :doc:`forms documentation </topics/forms/index>`
275 276 277 278 279

Handle uploaded files using the new API
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Replace use of uploaded files -- that is, entries in ``request.FILES`` -- as
280 281 282
simple dictionaries with the new
:class:`~django.core.files.uploadedfile.UploadedFile`. The old dictionary
syntax no longer works.
283 284 285 286 287 288 289

Thus, in a view like::

      def my_view(request):
          f = request.FILES['file_field_name']
          ...

290
...you'd need to make the following changes:
291 292 293 294 295 296 297 298 299

===================== =====================
Old (0.96)            New (1.0)
===================== =====================
``f['content']``      ``f.read()``
``f['filename']``     ``f.name``
``f['content-type']`` ``f.content_type``
===================== =====================

300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
Work with file fields using the new API
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The internal implementation of :class:`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
:class:`~django.db.models.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
:class:`~django.db.models.ImageField` fields. More details can be found in the
322
:doc:`model API </ref/models/fields>` documentation.
323

324 325 326 327 328
Use ``Paginator`` instead of ``ObjectPaginator``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The ``ObjectPaginator`` in 0.96 has been removed and replaced with an improved
version, :class:`django.core.paginator.Paginator`.
329

330 331 332 333 334 335
Templates
---------

Learn to love autoescaping
~~~~~~~~~~~~~~~~~~~~~~~~~~

336
By default, the template system now automatically HTML-escapes the output of
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376
every variable. To learn more, see :ref:`automatic-html-escaping`.

To disable auto-escaping for an individual variable, use the :tfilter:`safe`
filter:

.. code-block:: html+django

      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:

.. code-block:: html+django

      {% 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
  :class:`~django.dispatch.Signal` object instead of through module methods in
  ``django.dispatch.dispatcher``.

* Remove any use of the ``Anonymous`` and ``Any`` sender options; they no longer
  exist. You can still receive signals sent by any sender by using
  ``sender=None``

* Make any custom signals you've declared into instances of
377
  :class:`django.dispatch.Signal` instead of anonymous objects.
378 379 380 381 382 383 384 385 386 387 388 389 390

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)``
=================================================  ======================================

391 392 393 394
Comments
--------

If you were using Django 0.96's ``django.contrib.comments`` app, you'll need to
395 396
upgrade to the new comments app introduced in 1.0. See the upgrade guide
for details.
397

398 399 400 401 402 403
Template tags
-------------

:ttag:`spaceless` tag
~~~~~~~~~~~~~~~~~~~~~

404 405
The spaceless template tag now removes *all* spaces between HTML tags, instead
of preserving a single space.
406

407 408
Local flavors
-------------
409

410 411
U.S. local flavor
~~~~~~~~~~~~~~~~~
412

413
``django.contrib.localflavor.usa`` has been renamed to
414
``django.contrib.localflavor.us``. This change was made to match the naming
415 416
scheme of other local flavors. To migrate your code, all you need to do is
change the imports.
417 418 419 420 421 422 423

Sessions
--------

Getting a new session key
~~~~~~~~~~~~~~~~~~~~~~~~~

424
``SessionBase.get_new_session_key()`` has been renamed to
425
``_get_new_session_key()``. ``get_new_session_object()`` no longer exists.
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442

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
~~~~~~~~~~~~~~~~~

443 444 445
The old :exc:`EnvironmentError` has split into an
:exc:`ImportError` when Django fails to find the settings module
and a :exc:`RuntimeError` when you try to reconfigure settings
446
after having already used them.
447

448 449
:setting:`LOGIN_URL` has moved
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
450

451
The :setting:`LOGIN_URL` constant moved from ``django.contrib.auth`` into the
452 453 454
``settings`` module. Instead of using ``from django.contrib.auth import
LOGIN_URL`` refer to :setting:`settings.LOGIN_URL <LOGIN_URL>`.

455 456
:setting:`APPEND_SLASH` behavior has been updated
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
457 458

In 0.96, if a URL didn't end in a slash or have a period in the final
459 460 461 462 463
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.
464 465 466 467 468 469 470 471 472 473 474

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/(.*/)$'

475
Smaller model changes
476 477 478 479 480
---------------------

Different exception from ``get()``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

481
Managers now return a :exc:`~django.core.exceptions.MultipleObjectsReturned`
482
exception instead of :exc:`AssertionError`:
483 484 485 486 487 488 489 490 491 492 493 494 495 496

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()
497

498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516
``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):
517
        title = models.CharField(max_length=100)
518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535
        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``
536
not taking a ``max_digits`` attribute in ``__init__``, because the new
537 538
``FloatField`` takes no precision-related arguments.

539
If you're using MySQL or PostgreSQL, no further changes are needed. The
540 541 542
database column types for ``DecimalField`` are the same as for the old
``FloatField``.

543
If you're using SQLite, you need to force the database to view the
544 545 546 547 548 549
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::

550 551
  **Back up your database first!**

552
  For SQLite, this means making a copy of the single file that stores the
553
  database (the name of that file is the ``DATABASE_NAME`` in your
554
  settings.py file).
555

556 557
To upgrade each application to use a ``DecimalField``, you can do the
following, replacing ``<app>`` in the code below with each app's name:
558

559
.. code-block:: console
560 561 562 563 564 565 566

      $ ./manage.py dumpdata --format=xml <app> > data-dump.xml
      $ ./manage.py reset <app>
      $ ./manage.py loaddata data-dump.xml

Notes:

567 568 569
1. 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.
570

571 572 573
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. Say yes; we'll restore
   this data in the third step, of course.
574

575 576 577
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 models.
578 579

If something goes wrong in the above process, just copy your backed up
580
database file over the original file and start again.
581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597

Internationalization
--------------------

:func:`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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

598 599 600 601 602 603 604
``_()`` (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::
605 606 607 608 609 610

    from django.utils.translation import ugettext as _

HTTP request/response objects
-----------------------------

611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626
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.

627 628 629 630
Accessing ``HTTPResponse`` headers
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

``django.http.HttpResponse.headers`` has been renamed to ``_headers`` and
631 632
:class:`~django.http.HttpResponse` now supports containment checking directly.
So use ``if header in response:`` instead of ``if header in response.headers:``.
633 634 635 636 637 638 639 640 641 642 643 644 645

Generic relations
-----------------

Generic relations have been moved out of core
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The generic relation classes -- ``GenericForeignKey`` and ``GenericRelation``
-- have moved into the :mod:`django.contrib.contenttypes` module.

Testing
-------

646
:meth:`django.test.Client.login` has changed
647
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665

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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

666
:mod:`django.core.management` has been greatly refactored.
667

668
Calls to management services in your code now need to use
669 670 671 672 673 674 675
``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)

676
...you'll need to change this code to read::
677 678 679 680 681

      from django.core import management
      management.call_command('flush', verbosity=0, interactive=False)
      management.call_command('loaddata', 'test_data', verbosity=0)

682
Subcommands must now precede options
683 684 685 686 687
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

688
.. code-block:: console
689 690 691

      $ django-admin.py --settings=foo.bar runserver

692
...no longer works and should be changed to:
693

694
.. code-block:: console
695 696 697 698 699 700 701 702 703

      $ django-admin.py runserver --settings=foo.bar

Syndication
-----------

``Feed.__init__`` has changed
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

704 705
The ``__init__()`` method of the syndication framework's ``Feed`` class now
takes an ``HttpRequest`` object as its second parameter, instead of the feed's
706
URL. This allows the syndication framework to work without requiring the sites
707
framework. This only affects code that subclasses ``Feed`` and overrides the
708 709 710 711 712 713 714 715
``__init__()`` method, and code that calls ``Feed.__init__()`` directly.

Data structures
---------------

``SortedDictFromList`` is gone
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

716
``django.newforms.forms.SortedDictFromList`` was removed.
717
``django.utils.datastructures.SortedDict`` can now be instantiated with
718 719 720 721
a sequence of tuples.

To update your code:

722
1. Use ``django.utils.datastructures.SortedDict`` wherever you were
723
   using ``django.newforms.forms.SortedDictFromList``.
724

725
2. Because ``django.utils.datastructures.SortedDict.copy`` doesn't
726 727 728
   return a deepcopy as ``SortedDictFromList.copy()`` did, you will need
   to update your code if you were relying on a deepcopy. Do this by using
   ``copy.deepcopy`` directly.
729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773

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 :mod:`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``
=======================================  ===================================================