PageRenderTime 128ms CodeModel.GetById 114ms app.highlight 6ms RepoModel.GetById 1ms app.codeStats 0ms

/docs/releases/1.0-porting-guide.txt

https://code.google.com/p/mango-py/
Plain Text | 772 lines | 544 code | 228 blank | 0 comment | 0 complexity | d9ffd3147df0e7e2dd56cefb422c22ab MD5 | raw file
  1=========================================
  2Porting your apps from Django 0.96 to 1.0
  3=========================================
  4
  5.. highlight:: python
  6
  7Django 1.0 breaks compatibility with 0.96 in some areas.
  8
  9This guide will help you port 0.96 projects and apps to 1.0. The first part of
 10this document includes the common changes needed to run with 1.0. If after going
 11through the first part your code still breaks, check the section `Less-common
 12Changes`_ for a list of a bunch of less-common compatibility issues.
 13
 14.. seealso::
 15
 16    The :doc:`1.0 release notes </releases/1.0>`. That document explains the new
 17    features in 1.0 more deeply; the porting guide is more concerned with
 18    helping you quickly update your code.
 19
 20Common changes
 21==============
 22
 23This section describes the changes between 0.96 and 1.0 that most users will
 24need to make.
 25
 26Use Unicode
 27-----------
 28
 29Change string literals (``'foo'``) into Unicode literals (``u'foo'``). Django
 30now uses Unicode strings throughout. In most places, raw strings will continue
 31to work, but updating to use Unicode literals will prevent some obscure
 32problems.
 33
 34See :doc:`/ref/unicode` for full details.
 35
 36Models
 37------
 38
 39Common changes to your models file:
 40
 41Rename ``maxlength`` to ``max_length``
 42~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 43
 44Rename your ``maxlength`` argument to ``max_length`` (this was changed to be
 45consistent with form fields):
 46
 47Replace ``__str__`` with ``__unicode__``
 48~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 49
 50Replace your model's ``__str__`` function with a ``__unicode__`` method, and
 51make sure you `use Unicode`_ (``u'foo'``) in that method.
 52
 53Remove ``prepopulated_from``
 54~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 55
 56Remove the ``prepopulated_from`` argument on model fields. It's no longer valid
 57and has been moved to the ``ModelAdmin`` class in ``admin.py``. See `the
 58admin`_, below, for more details about changes to the admin.
 59
 60Remove ``core``
 61~~~~~~~~~~~~~~~
 62
 63Remove the ``core`` argument from your model fields. It is no longer
 64necessary, since the equivalent functionality (part of :ref:`inline editing
 65<admin-inlines>`) is handled differently by the admin interface now. You don't
 66have to worry about inline editing until you get to `the admin`_ section,
 67below. For now, remove all references to ``core``.
 68
 69Replace ``class Admin:`` with ``admin.py``
 70~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 71
 72Remove all your inner ``class Admin`` declarations from your models. They won't
 73break anything if you leave them, but they also won't do anything. To register
 74apps with the admin you'll move those declarations to an ``admin.py`` file;
 75see `the admin`_ below for more details.
 76
 77.. seealso::
 78
 79    A contributor to djangosnippets__ has written a script that'll `scan your
 80    models.py and generate a corresponding admin.py`__.
 81
 82    __ http://www.djangosnippets.org/
 83    __ http://www.djangosnippets.org/snippets/603/
 84
 85Example
 86~~~~~~~
 87
 88Below is an example ``models.py`` file with all the changes you'll need to make:
 89
 90Old (0.96) ``models.py``::
 91
 92    class Author(models.Model):
 93        first_name = models.CharField(maxlength=30)
 94        last_name = models.CharField(maxlength=30)
 95        slug = models.CharField(maxlength=60, prepopulate_from=('first_name', 'last_name'))
 96
 97        class Admin:
 98            list_display = ['first_name', 'last_name']
 99
100        def __str__(self):
101            return '%s %s' % (self.first_name, self.last_name)
102
103New (1.0) ``models.py``::
104
105    class Author(models.Model):
106        first_name = models.CharField(max_length=30)
107        last_name = models.CharField(max_length=30)
108        slug = models.CharField(max_length=60)
109
110        def __unicode__(self):
111            return u'%s %s' % (self.first_name, self.last_name)
112
113New (1.0) ``admin.py``::
114
115    from django.contrib import admin
116    from models import Author
117
118    class AuthorAdmin(admin.ModelAdmin):
119        list_display = ['first_name', 'last_name']
120        prepopulated_fields = {
121            'slug': ('first_name', 'last_name')
122        }
123
124    admin.site.register(Author, AuthorAdmin)
125
126The Admin
127---------
128
129One of the biggest changes in 1.0 is the new admin. The Django administrative
130interface (``django.contrib.admin``) has been completely refactored; admin
131definitions are now completely decoupled from model definitions, the framework
132has been rewritten to use Django's new form-handling library and redesigned with
133extensibility and customization in mind.
134
135Practically, this means you'll need to rewrite all of your ``class Admin``
136declarations. You've already seen in `models`_ above how to replace your ``class
137Admin`` with a ``admin.site.register()`` call in an ``admin.py`` file. Below are
138some more details on how to rewrite that ``Admin`` declaration into the new
139syntax.
140
141Use new inline syntax
142~~~~~~~~~~~~~~~~~~~~~
143
144The new ``edit_inline`` options have all been moved to ``admin.py``. Here's an
145example:
146
147Old (0.96)::
148
149    class Parent(models.Model):
150        ...
151
152    class Child(models.Model):
153        parent = models.ForeignKey(Parent, edit_inline=models.STACKED, num_in_admin=3)
154
155
156New (1.0)::
157
158    class ChildInline(admin.StackedInline):
159        model = Child
160        extra = 3
161
162    class ParentAdmin(admin.ModelAdmin):
163        model = Parent
164        inlines = [ChildInline]
165
166    admin.site.register(Parent, ParentAdmin)
167
168See :ref:`admin-inlines` for more details.
169
170Simplify ``fields``, or use ``fieldsets``
171~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
172
173The old ``fields`` syntax was quite confusing, and has been simplified. The old
174syntax still works, but you'll need to use ``fieldsets`` instead.
175
176Old (0.96)::
177
178    class ModelOne(models.Model):
179        ...
180
181        class Admin:
182            fields = (
183                (None, {'fields': ('foo','bar')}),
184            )
185
186    class ModelTwo(models.Model):
187        ...
188
189        class Admin:
190            fields = (
191                ('group1', {'fields': ('foo','bar'),   'classes': 'collapse'}),
192                ('group2', {'fields': ('spam','eggs'), 'classes': 'collapse wide'}),
193            )
194
195
196New (1.0)::
197
198    class ModelOneAdmin(admin.ModelAdmin):
199        fields = ('foo', 'bar')
200
201    class ModelTwoAdmin(admin.ModelAdmin):
202        fieldsets = (
203            ('group1', {'fields': ('foo','bar'),   'classes': 'collapse'}),
204            ('group2', {'fields': ('spam','eggs'), 'classes': 'collapse wide'}),
205        )
206
207
208.. seealso::
209
210    * More detailed information about the changes and the reasons behind them
211      can be found on the `NewformsAdminBranch wiki page`__
212
213    * 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    __ http://code.djangoproject.com/wiki/NewformsAdminBranch
217
218URLs
219----
220
221Update your root ``urls.py``
222~~~~~~~~~~~~~~~~~~~~~~~~~~~~
223
224If you're using the admin site, you need to update your root ``urls.py``.
225
226Old (0.96) ``urls.py``::
227
228    from django.conf.urls.defaults import *
229
230    urlpatterns = patterns('',
231        (r'^admin/', include('django.contrib.admin.urls')),
232
233        # ... the rest of your URLs here ...
234    )
235
236New (1.0) ``urls.py``::
237
238    from django.conf.urls.defaults import *
239
240    # The next two lines enable the admin and load each admin.py file:
241    from django.contrib import admin
242    admin.autodiscover()
243
244    urlpatterns = patterns('',
245        (r'^admin/(.*)', admin.site.root),
246
247        # ... the rest of your URLs here ...
248    )
249
250Views
251-----
252
253Use ``django.forms`` instead of ``newforms``
254~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
255
256Replace ``django.newforms`` with ``django.forms`` -- Django 1.0 renamed the
257``newforms`` module (introduced in 0.96) to plain old ``forms``. The
258``oldforms`` module was also removed.
259
260If you're already using the ``newforms`` library, and you used our recommended
261``import`` statement syntax, all you have to do is change your import
262statements.
263
264Old::
265
266    from django import newforms as forms
267
268New::
269
270    from django import forms
271
272If you're using the old forms system (formerly known as ``django.forms`` and
273``django.oldforms``), you'll have to rewrite your forms. A good place to start
274is the :doc:`forms documentation </topics/forms/index>`
275
276Handle uploaded files using the new API
277~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
278
279Replace use of uploaded files -- that is, entries in ``request.FILES`` -- as
280simple dictionaries with the new :class:`~django.core.files.UploadedFile`. The
281old dictionary syntax no longer works.
282
283Thus, in a view like::
284
285      def my_view(request):
286          f = request.FILES['file_field_name']
287          ...
288
289...you'd need to make the following changes:
290
291===================== =====================
292Old (0.96)            New (1.0)
293===================== =====================
294``f['content']``      ``f.read()``
295``f['filename']``     ``f.name``
296``f['content-type']`` ``f.content_type``
297===================== =====================
298
299Work with file fields using the new API
300~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
301
302The internal implementation of :class:`django.db.models.FileField` have changed.
303A visible result of this is that the way you access special attributes (URL,
304filename, image size, etc) of these model fields has changed. You will need to
305make the following changes, assuming your model's
306:class:`~django.db.models.FileField` is called ``myfile``:
307
308=================================== ========================
309Old (0.96)                           New (1.0)
310=================================== ========================
311``myfile.get_content_filename()``   ``myfile.content.path``
312``myfile.get_content_url()``        ``myfile.content.url``
313``myfile.get_content_size()``       ``myfile.content.size``
314``myfile.save_content_file()``      ``myfile.content.save()``
315``myfile.get_content_width()``      ``myfile.content.width``
316``myfile.get_content_height()``     ``myfile.content.height``
317=================================== ========================
318
319Note that the ``width`` and ``height`` attributes only make sense for
320:class:`~django.db.models.ImageField` fields. More details can be found in the
321:doc:`model API </ref/models/fields>` documentation.
322
323Use ``Paginator`` instead of ``ObjectPaginator``
324~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
325
326The ``ObjectPaginator`` in 0.96 has been removed and replaced with an improved
327version, :class:`django.core.paginator.Paginator`.
328
329Templates
330---------
331
332Learn to love autoescaping
333~~~~~~~~~~~~~~~~~~~~~~~~~~
334
335By default, the template system now automatically HTML-escapes the output of
336every variable. To learn more, see :ref:`automatic-html-escaping`.
337
338To disable auto-escaping for an individual variable, use the :tfilter:`safe`
339filter:
340
341.. code-block:: html+django
342
343      This will be escaped: {{ data }}
344      This will not be escaped: {{ data|safe }}
345
346To disable auto-escaping for an entire template, wrap the template (or just a
347particular section of the template) in the :ttag:`autoescape` tag:
348
349.. code-block:: html+django
350
351      {% autoescape off %}
352         ... unescaped template content here ...
353      {% endautoescape %}
354
355Less-common changes
356===================
357
358The following changes are smaller, more localized changes. They should only
359affect more advanced users, but it's probably worth reading through the list and
360checking your code for these things.
361
362Signals
363-------
364
365* Add ``**kwargs`` to any registered signal handlers.
366
367* Connect, disconnect, and send signals via methods on the
368  :class:`~django.dispatch.Signal` object instead of through module methods in
369  ``django.dispatch.dispatcher``.
370
371* Remove any use of the ``Anonymous`` and ``Any`` sender options; they no longer
372  exist. You can still receive signals sent by any sender by using
373  ``sender=None``
374
375* Make any custom signals you've declared into instances of
376  :class:`django.dispatch.Signal` instead of anonymous objects.
377
378Here's quick summary of the code changes you'll need to make:
379
380=================================================  ======================================
381Old (0.96)                                         New (1.0)
382=================================================  ======================================
383``def callback(sender)``                           ``def callback(sender, **kwargs)``
384``sig = object()``                                 ``sig = django.dispatch.Signal()``
385``dispatcher.connect(callback, sig)``              ``sig.connect(callback)``
386``dispatcher.send(sig, sender)``                   ``sig.send(sender)``
387``dispatcher.connect(callback, sig, sender=Any)``  ``sig.connect(callback, sender=None)``
388=================================================  ======================================
389
390Comments
391--------
392
393If you were using Django 0.96's ``django.contrib.comments`` app, you'll need to
394upgrade to the new comments app introduced in 1.0. See
395:doc:`/ref/contrib/comments/upgrade` for details.
396
397Template tags
398-------------
399
400:ttag:`spaceless` tag
401~~~~~~~~~~~~~~~~~~~~~
402
403The spaceless template tag now removes *all* spaces between HTML tags, instead
404of preserving a single space.
405
406Local flavors
407-------------
408
409U.S. local flavor
410~~~~~~~~~~~~~~~~~
411
412``django.contrib.localflavor.usa`` has been renamed to
413:mod:`django.contrib.localflavor.us`. This change was made to match the naming
414scheme of other local flavors. To migrate your code, all you need to do is
415change the imports.
416
417Sessions
418--------
419
420Getting a new session key
421~~~~~~~~~~~~~~~~~~~~~~~~~
422
423``SessionBase.get_new_session_key()`` has been renamed to
424``_get_new_session_key()``. ``get_new_session_object()`` no longer exists.
425
426Fixtures
427--------
428
429Loading a row no longer calls ``save()``
430~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
431
432Previously, loading a row automatically ran the model's ``save()`` method. This
433is no longer the case, so any fields (for example: timestamps) that were
434auto-populated by a ``save()`` now need explicit values in any fixture.
435
436Settings
437--------
438
439Better exceptions
440~~~~~~~~~~~~~~~~~
441
442The old :exc:`EnvironmentError` has split into an :exc:`ImportError` when
443Django fails to find the settings module and a :exc:`RuntimeError` when you try
444to reconfigure settings after having already used them
445
446:setting:`LOGIN_URL` has moved
447~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
448
449The :setting:`LOGIN_URL` constant moved from ``django.contrib.auth`` into the
450``settings`` module. Instead of using ``from django.contrib.auth import
451LOGIN_URL`` refer to :setting:`settings.LOGIN_URL <LOGIN_URL>`.
452
453:setting:`APPEND_SLASH` behavior has been updated
454~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
455
456In 0.96, if a URL didn't end in a slash or have a period in the final
457component of its path, and :setting:`APPEND_SLASH` was True, Django would
458redirect to the same URL, but with a slash appended to the end. Now, Django
459checks to see whether the pattern without the trailing slash would be matched
460by something in your URL patterns. If so, no redirection takes place, because
461it is assumed you deliberately wanted to catch that pattern.
462
463For most people, this won't require any changes. Some people, though, have URL
464patterns that look like this::
465
466    r'/some_prefix/(.*)$'
467
468Previously, those patterns would have been redirected to have a trailing
469slash. If you always want a slash on such URLs, rewrite the pattern as::
470
471    r'/some_prefix/(.*/)$'
472
473Smaller model changes
474---------------------
475
476Different exception from ``get()``
477~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
478
479Managers now return a :exc:`MultipleObjectsReturned` exception
480instead of :exc:`AssertionError`:
481
482Old (0.96)::
483
484    try:
485        Model.objects.get(...)
486    except AssertionError:
487        handle_the_error()
488
489New (1.0)::
490
491  try:
492      Model.objects.get(...)
493  except Model.MultipleObjectsReturned:
494      handle_the_error()
495
496``LazyDate`` has been fired
497~~~~~~~~~~~~~~~~~~~~~~~~~~~
498
499The ``LazyDate`` helper class no longer exists.
500
501Default field values and query arguments can both be callable objects, so
502instances of ``LazyDate`` can be replaced with a reference to ``datetime.datetime.now``:
503
504Old (0.96)::
505
506    class Article(models.Model):
507        title = models.CharField(maxlength=100)
508        published = models.DateField(default=LazyDate())
509
510New (1.0)::
511
512    import datetime
513
514    class Article(models.Model):
515        title = models.CharField(max_length=100)
516        published = models.DateField(default=datetime.datetime.now)
517
518``DecimalField`` is new, and ``FloatField`` is now a proper float
519~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
520
521Old (0.96)::
522
523    class MyModel(models.Model):
524        field_name = models.FloatField(max_digits=10, decimal_places=3)
525        ...
526
527New (1.0)::
528
529    class MyModel(models.Model):
530        field_name = models.DecimalField(max_digits=10, decimal_places=3)
531        ...
532
533If you forget to make this change, you will see errors about ``FloatField``
534not taking a ``max_digits`` attribute in ``__init__``, because the new
535``FloatField`` takes no precision-related arguments.
536
537If you're using MySQL or PostgreSQL, no further changes are needed. The
538database column types for ``DecimalField`` are the same as for the old
539``FloatField``.
540
541If you're using SQLite, you need to force the database to view the
542appropriate columns as decimal types, rather than floats. To do this, you'll
543need to reload your data. Do this after you have made the change to using
544``DecimalField`` in your code and updated the Django code.
545
546.. warning::
547
548  **Back up your database first!**
549
550  For SQLite, this means making a copy of the single file that stores the
551  database (the name of that file is the :setting:`DATABASE_NAME` in your
552  settings.py file).
553
554To upgrade each application to use a ``DecimalField``, you can do the
555following, replacing ``<app>`` in the code below with each app's name:
556
557.. code-block:: bash
558
559      $ ./manage.py dumpdata --format=xml <app> > data-dump.xml
560      $ ./manage.py reset <app>
561      $ ./manage.py loaddata data-dump.xml
562
563Notes:
564
565  1. It's important that you remember to use XML format in the first step of
566     this process. We are exploiting a feature of the XML data dumps that makes
567     porting floats to decimals with SQLite possible.
568
569  2. In the second step you will be asked to confirm that you are prepared to
570     lose the data for the application(s) in question. Say yes; we'll restore
571     this data in the third step, of course.
572
573  3. ``DecimalField`` is not used in any of the apps shipped with Django prior
574     to this change being made, so you do not need to worry about performing
575     this procedure for any of the standard Django models.
576
577If something goes wrong in the above process, just copy your backed up
578database file over the original file and start again.
579
580Internationalization
581--------------------
582
583:func:`django.views.i18n.set_language` now requires a POST request
584~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
585
586Previously, a GET request was used. The old behavior meant that state (the
587locale used to display the site) could be changed by a GET request, which is
588against the HTTP specification's recommendations. Code calling this view must
589ensure that a POST request is now made, instead of a GET. This means you can
590no longer use a link to access the view, but must use a form submission of
591some kind (e.g. a button).
592
593``_()`` is no longer in builtins
594~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
595
596``_()`` (the callable object whose name is a single underscore) is no longer
597monkeypatched into builtins -- that is, it's no longer available magically in
598every module.
599
600If you were previously relying on ``_()`` always being present, you should now
601explicitly import ``ugettext`` or ``ugettext_lazy``, if appropriate, and alias
602it to ``_`` yourself::
603
604    from django.utils.translation import ugettext as _
605
606HTTP request/response objects
607-----------------------------
608
609Dictionary access to ``HttpRequest``
610~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
611
612``HttpRequest`` objects no longer directly support dictionary-style
613access; previously, both ``GET`` and ``POST`` data were directly
614available on the ``HttpRequest`` object (e.g., you could check for a
615piece of form data by using ``if 'some_form_key' in request`` or by
616reading ``request['some_form_key']``. This is no longer supported; if
617you need access to the combined ``GET`` and ``POST`` data, use
618``request.REQUEST`` instead.
619
620It is strongly suggested, however, that you always explicitly look in
621the appropriate dictionary for the type of request you expect to
622receive (``request.GET`` or ``request.POST``); relying on the combined
623``request.REQUEST`` dictionary can mask the origin of incoming data.
624
625Accessing ``HTTPResponse`` headers
626~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
627
628``django.http.HttpResponse.headers`` has been renamed to ``_headers`` and
629:class:`~django.http.HttpResponse` now supports containment checking directly.
630So use ``if header in response:`` instead of ``if header in response.headers:``.
631
632Generic relations
633-----------------
634
635Generic relations have been moved out of core
636~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
637
638The generic relation classes -- ``GenericForeignKey`` and ``GenericRelation``
639-- have moved into the :mod:`django.contrib.contenttypes` module.
640
641Testing
642-------
643
644:meth:`django.test.Client.login` has changed
645~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
646
647Old (0.96)::
648
649    from django.test import Client
650    c = Client()
651    c.login('/path/to/login','myuser','mypassword')
652
653New (1.0)::
654
655    # ... same as above, but then:
656    c.login(username='myuser', password='mypassword')
657
658Management commands
659-------------------
660
661Running management commands from your code
662~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
663
664:mod:`django.core.management` has been greatly refactored.
665
666Calls to management services in your code now need to use
667``call_command``. For example, if you have some test code that calls flush and
668load_data::
669
670      from django.core import management
671      management.flush(verbosity=0, interactive=False)
672      management.load_data(['test_data'], verbosity=0)
673
674...you'll need to change this code to read::
675
676      from django.core import management
677      management.call_command('flush', verbosity=0, interactive=False)
678      management.call_command('loaddata', 'test_data', verbosity=0)
679
680Subcommands must now precede options
681~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
682
683``django-admin.py`` and ``manage.py`` now require subcommands to precede
684options. So:
685
686.. code-block:: bash
687
688      $ django-admin.py --settings=foo.bar runserver
689
690...no longer works and should be changed to:
691
692.. code-block:: bash
693
694      $ django-admin.py runserver --settings=foo.bar
695
696Syndication
697-----------
698
699``Feed.__init__`` has changed
700~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
701
702The ``__init__()`` method of the syndication framework's ``Feed`` class now
703takes an ``HttpRequest`` object as its second parameter, instead of the feed's
704URL. This allows the syndication framework to work without requiring the sites
705framework. This only affects code that subclasses ``Feed`` and overrides the
706``__init__()`` method, and code that calls ``Feed.__init__()`` directly.
707
708Data structures
709---------------
710
711``SortedDictFromList`` is gone
712~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
713
714``django.newforms.forms.SortedDictFromList`` was removed.
715:class:`django.utils.datastructures.SortedDict` can now be instantiated with
716a sequence of tuples.
717
718To update your code:
719
720     1. Use :class:`django.utils.datastructures.SortedDict` wherever you were
721        using ``django.newforms.forms.SortedDictFromList``.
722
723     2. Because :meth:`django.utils.datastructures.SortedDict.copy` doesn't
724        return a deepcopy as ``SortedDictFromList.copy()`` did, you will need
725        to update your code if you were relying on a deepcopy. Do this by using
726        ``copy.deepcopy`` directly.
727
728Database backend functions
729--------------------------
730
731Database backend functions have been renamed
732~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
733
734Almost *all* of the database backend-level functions have been renamed and/or
735relocated. None of these were documented, but you'll need to change your code
736if you're using any of these functions, all of which are in :mod:`django.db`:
737
738=======================================  ===================================================
739Old (0.96)                               New (1.0)
740=======================================  ===================================================
741``backend.get_autoinc_sql``              ``connection.ops.autoinc_sql``
742``backend.get_date_extract_sql``         ``connection.ops.date_extract_sql``
743``backend.get_date_trunc_sql``           ``connection.ops.date_trunc_sql``
744``backend.get_datetime_cast_sql``        ``connection.ops.datetime_cast_sql``
745``backend.get_deferrable_sql``           ``connection.ops.deferrable_sql``
746``backend.get_drop_foreignkey_sql``      ``connection.ops.drop_foreignkey_sql``
747``backend.get_fulltext_search_sql``      ``connection.ops.fulltext_search_sql``
748``backend.get_last_insert_id``           ``connection.ops.last_insert_id``
749``backend.get_limit_offset_sql``         ``connection.ops.limit_offset_sql``
750``backend.get_max_name_length``          ``connection.ops.max_name_length``
751``backend.get_pk_default_value``         ``connection.ops.pk_default_value``
752``backend.get_random_function_sql``      ``connection.ops.random_function_sql``
753``backend.get_sql_flush``                ``connection.ops.sql_flush``
754``backend.get_sql_sequence_reset``       ``connection.ops.sequence_reset_sql``
755``backend.get_start_transaction_sql``    ``connection.ops.start_transaction_sql``
756``backend.get_tablespace_sql``           ``connection.ops.tablespace_sql``
757``backend.quote_name``                   ``connection.ops.quote_name``
758``backend.get_query_set_class``          ``connection.ops.query_set_class``
759``backend.get_field_cast_sql``           ``connection.ops.field_cast_sql``
760``backend.get_drop_sequence``            ``connection.ops.drop_sequence_sql``
761``backend.OPERATOR_MAPPING``             ``connection.operators``
762``backend.allows_group_by_ordinal``      ``connection.features.allows_group_by_ordinal``
763``backend.allows_unique_and_pk``         ``connection.features.allows_unique_and_pk``
764``backend.autoindexes_primary_keys``     ``connection.features.autoindexes_primary_keys``
765``backend.needs_datetime_string_cast``   ``connection.features.needs_datetime_string_cast``
766``backend.needs_upper_for_iops``         ``connection.features.needs_upper_for_iops``
767``backend.supports_constraints``         ``connection.features.supports_constraints``
768``backend.supports_tablespaces``         ``connection.features.supports_tablespaces``
769``backend.uses_case_insensitive_names``  ``connection.features.uses_case_insensitive_names``
770``backend.uses_custom_queryset``         ``connection.features.uses_custom_queryset``
771=======================================  ===================================================
772