diff -r 5ff1fc726848 -r c6bca38c1cbf parts/django/docs/releases/1.0-porting-guide.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/parts/django/docs/releases/1.0-porting-guide.txt Sat Jan 08 11:20:57 2011 +0530 @@ -0,0 +1,772 @@ +========================================= +Porting your apps from Django 0.96 to 1.0 +========================================= + +.. highlight:: python + +Django 1.0 breaks compatibility with 0.96 in some areas. + +This guide will help you port 0.96 projects and apps to 1.0. The first part of +this document includes the common changes needed to run with 1.0. If after going +through the first part your code still breaks, check the section `Less-common +Changes`_ for a list of a bunch of less-common compatibility issues. + +.. seealso:: + + The :doc:`1.0 release notes `. That document explains the new + features in 1.0 more deeply; the porting guide is more concerned with + helping you quickly update your code. + +Common changes +============== + +This section describes the changes between 0.96 and 1.0 that most users will +need to make. + +Use Unicode +----------- + +Change string literals (``'foo'``) into Unicode literals (``u'foo'``). Django +now uses Unicode strings throughout. In most places, raw strings will continue +to work, but updating to use Unicode literals will prevent some obscure +problems. + +See :doc:`/ref/unicode` for full details. + +Models +------ + +Common changes to your models file: + +Rename ``maxlength`` to ``max_length`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Rename your ``maxlength`` argument to ``max_length`` (this was changed to be +consistent with form fields): + +Replace ``__str__`` with ``__unicode__`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Replace your model's ``__str__`` function with a ``__unicode__`` method, and +make sure you `use Unicode`_ (``u'foo'``) in that method. + +Remove ``prepopulated_from`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Remove the ``prepopulated_from`` argument on model fields. It's no longer valid +and has been moved to the ``ModelAdmin`` class in ``admin.py``. See `the +admin`_, below, for more details about changes to the admin. + +Remove ``core`` +~~~~~~~~~~~~~~~ + +Remove the ``core`` argument from your model fields. It is no longer +necessary, since the equivalent functionality (part of :ref:`inline editing +`) is handled differently by the admin interface now. You don't +have to worry about inline editing until you get to `the admin`_ section, +below. For now, remove all references to ``core``. + +Replace ``class Admin:`` with ``admin.py`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Remove all your inner ``class Admin`` declarations from your models. They won't +break anything if you leave them, but they also won't do anything. To register +apps with the admin you'll move those declarations to an ``admin.py`` file; +see `the admin`_ below for more details. + +.. seealso:: + + A contributor to djangosnippets__ has written a script that'll `scan your + models.py and generate a corresponding admin.py`__. + + __ http://www.djangosnippets.org/ + __ http://www.djangosnippets.org/snippets/603/ + +Example +~~~~~~~ + +Below is an example ``models.py`` file with all the changes you'll need to make: + +Old (0.96) ``models.py``:: + + class Author(models.Model): + first_name = models.CharField(maxlength=30) + last_name = models.CharField(maxlength=30) + slug = models.CharField(maxlength=60, prepopulate_from=('first_name', 'last_name')) + + class Admin: + list_display = ['first_name', 'last_name'] + + def __str__(self): + return '%s %s' % (self.first_name, self.last_name) + +New (1.0) ``models.py``:: + + class Author(models.Model): + first_name = models.CharField(max_length=30) + last_name = models.CharField(max_length=30) + slug = models.CharField(max_length=60) + + def __unicode__(self): + return u'%s %s' % (self.first_name, self.last_name) + +New (1.0) ``admin.py``:: + + from django.contrib import admin + from models import Author + + class AuthorAdmin(admin.ModelAdmin): + list_display = ['first_name', 'last_name'] + prepopulated_fields = { + 'slug': ('first_name', 'last_name') + } + + admin.site.register(Author, AuthorAdmin) + +The Admin +--------- + +One of the biggest changes in 1.0 is the new admin. The Django administrative +interface (``django.contrib.admin``) has been completely refactored; admin +definitions are now completely decoupled from model definitions, the framework +has been rewritten to use Django's new form-handling library and redesigned with +extensibility and customization in mind. + +Practically, this means you'll need to rewrite all of your ``class Admin`` +declarations. You've already seen in `models`_ above how to replace your ``class +Admin`` with 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): + ... + + class Child(models.Model): + parent = models.ForeignKey(Parent, edit_inline=models.STACKED, num_in_admin=3) + + +New (1.0):: + + class ChildInline(admin.StackedInline): + model = Child + extra = 3 + + class ParentAdmin(admin.ModelAdmin): + model = Parent + inlines = [ChildInline] + + admin.site.register(Parent, ParentAdmin) + +See :ref:`admin-inlines` for more details. + +Simplify ``fields``, or use ``fieldsets`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The old ``fields`` syntax was quite confusing, and has been simplified. The old +syntax still works, but you'll need to use ``fieldsets`` instead. + +Old (0.96):: + + class ModelOne(models.Model): + ... + + class Admin: + fields = ( + (None, {'fields': ('foo','bar')}), + ) + + class ModelTwo(models.Model): + ... + + class Admin: + fields = ( + ('group1', {'fields': ('foo','bar'), 'classes': 'collapse'}), + ('group2', {'fields': ('spam','eggs'), 'classes': 'collapse wide'}), + ) + + +New (1.0):: + + class ModelOneAdmin(admin.ModelAdmin): + fields = ('foo', 'bar') + + class ModelTwoAdmin(admin.ModelAdmin): + fieldsets = ( + ('group1', {'fields': ('foo','bar'), 'classes': 'collapse'}), + ('group2', {'fields': ('spam','eggs'), 'classes': 'collapse wide'}), + ) + + +.. 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 + the :doc:`admin documentation `. + + __ http://code.djangoproject.com/wiki/NewformsAdminBranch + +URLs +---- + +Update your root ``urls.py`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you're using the admin site, you need to update your root ``urls.py``. + +Old (0.96) ``urls.py``:: + + from django.conf.urls.defaults import * + + urlpatterns = patterns('', + (r'^admin/', include('django.contrib.admin.urls')), + + # ... the rest of your URLs here ... + ) + +New (1.0) ``urls.py``:: + + from django.conf.urls.defaults import * + + # The next two lines enable the admin and load each admin.py file: + from django.contrib import admin + admin.autodiscover() + + urlpatterns = patterns('', + (r'^admin/(.*)', admin.site.root), + + # ... the rest of your URLs here ... + ) + +Views +----- + +Use ``django.forms`` instead of ``newforms`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Replace ``django.newforms`` with ``django.forms`` -- Django 1.0 renamed the +``newforms`` module (introduced in 0.96) to plain old ``forms``. The +``oldforms`` module was also removed. + +If you're already using the ``newforms`` library, and you used our recommended +``import`` statement syntax, all you have to do is change your import +statements. + +Old:: + + from django import newforms as forms + +New:: + + from django import forms + +If you're using the old forms system (formerly known as ``django.forms`` and +``django.oldforms``), you'll have to rewrite your forms. A good place to start +is the :doc:`forms documentation ` + +Handle uploaded files using the new API +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Replace use of uploaded files -- that is, entries in ``request.FILES`` -- as +simple dictionaries with the new :class:`~django.core.files.UploadedFile`. The +old dictionary syntax no longer works. + +Thus, in a view like:: + + def my_view(request): + f = request.FILES['file_field_name'] + ... + +...you'd need to make the following changes: + +===================== ===================== +Old (0.96) New (1.0) +===================== ===================== +``f['content']`` ``f.read()`` +``f['filename']`` ``f.name`` +``f['content-type']`` ``f.content_type`` +===================== ===================== + +Work with file fields using the new API +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The internal implementation of :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 +:doc:`model API ` documentation. + +Use ``Paginator`` instead of ``ObjectPaginator`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``ObjectPaginator`` in 0.96 has been removed and replaced with an improved +version, :class:`django.core.paginator.Paginator`. + +Templates +--------- + +Learn to love autoescaping +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default, the template system now automatically HTML-escapes the output of +every variable. To learn more, see :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 + :class:`django.dispatch.Signal` instead of anonymous objects. + +Here's quick summary of the code changes you'll need to make: + +================================================= ====================================== +Old (0.96) New (1.0) +================================================= ====================================== +``def callback(sender)`` ``def callback(sender, **kwargs)`` +``sig = object()`` ``sig = django.dispatch.Signal()`` +``dispatcher.connect(callback, sig)`` ``sig.connect(callback)`` +``dispatcher.send(sig, sender)`` ``sig.send(sender)`` +``dispatcher.connect(callback, sig, sender=Any)`` ``sig.connect(callback, sender=None)`` +================================================= ====================================== + +Comments +-------- + +If you were using Django 0.96's ``django.contrib.comments`` app, you'll need to +upgrade to the new comments app introduced in 1.0. See +:doc:`/ref/contrib/comments/upgrade` for details. + +Template tags +------------- + +:ttag:`spaceless` tag +~~~~~~~~~~~~~~~~~~~~~ + +The spaceless template tag now removes *all* spaces between HTML tags, instead +of preserving a single space. + +Local flavors +------------- + +U.S. local flavor +~~~~~~~~~~~~~~~~~ + +``django.contrib.localflavor.usa`` has been renamed to +:mod:`django.contrib.localflavor.us`. This change was made to match the naming +scheme of other local flavors. To migrate your code, all you need to do is +change the imports. + +Sessions +-------- + +Getting a new session key +~~~~~~~~~~~~~~~~~~~~~~~~~ + +``SessionBase.get_new_session_key()`` has been renamed to +``_get_new_session_key()``. ``get_new_session_object()`` no longer exists. + +Fixtures +-------- + +Loading a row no longer calls ``save()`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Previously, loading a row automatically ran the model's ``save()`` method. This +is no longer the case, so any fields (for example: timestamps) that were +auto-populated by a ``save()`` now need explicit values in any fixture. + +Settings +-------- + +Better exceptions +~~~~~~~~~~~~~~~~~ + +The old :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 after having already used them + +``LOGIN_URL`` has moved +~~~~~~~~~~~~~~~~~~~~~~~ + +The ``LOGIN_URL`` constant moved from ``django.contrib.auth`` into the +``settings`` module. Instead of using ``from django.contrib.auth import +LOGIN_URL`` refer to :setting:`settings.LOGIN_URL `. + +:setting:`APPEND_SLASH` behavior has been updated +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In 0.96, if a URL didn't end in a slash or have a period in the final +component of its path, and ``APPEND_SLASH`` was True, Django would redirect +to the same URL, but with a slash appended to the end. Now, Django checks to +see whether the pattern without the trailing slash would be matched by +something in your URL patterns. If so, no redirection takes place, because it +is assumed you deliberately wanted to catch that pattern. + +For most people, this won't require any changes. Some people, though, have URL +patterns that look like this:: + + r'/some_prefix/(.*)$' + +Previously, those patterns would have been redirected to have a trailing +slash. If you always want a slash on such URLs, rewrite the pattern as:: + + r'/some_prefix/(.*/)$' + +Smaller model changes +--------------------- + +Different exception from ``get()`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Managers now return a :exc:`MultipleObjectsReturned` exception +instead of :exc:`AssertionError`: + +Old (0.96):: + + try: + Model.objects.get(...) + except AssertionError: + handle_the_error() + +New (1.0):: + + try: + Model.objects.get(...) + except Model.MultipleObjectsReturned: + handle_the_error() + +``LazyDate`` has been fired +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``LazyDate`` helper class no longer exists. + +Default field values and query arguments can both be callable objects, so +instances of ``LazyDate`` can be replaced with a reference to ``datetime.datetime.now``: + +Old (0.96):: + + class Article(models.Model): + title = models.CharField(maxlength=100) + published = models.DateField(default=LazyDate()) + +New (1.0):: + + import datetime + + class Article(models.Model): + title = models.CharField(max_length=100) + published = models.DateField(default=datetime.datetime.now) + +``DecimalField`` is new, and ``FloatField`` is now a proper float +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Old (0.96):: + + class MyModel(models.Model): + field_name = models.FloatField(max_digits=10, decimal_places=3) + ... + +New (1.0):: + + class MyModel(models.Model): + field_name = models.DecimalField(max_digits=10, decimal_places=3) + ... + +If you forget to make this change, you will see errors about ``FloatField`` +not taking a ``max_digits`` attribute in ``__init__``, because the new +``FloatField`` takes no precision-related arguments. + +If you're using MySQL or PostgreSQL, no further changes are needed. The +database column types for ``DecimalField`` are the same as for the old +``FloatField``. + +If you're using SQLite, you need to force the database to view the +appropriate columns as decimal types, rather than floats. To do this, you'll +need to reload your data. Do this after you have made the change to using +``DecimalField`` in your code and updated the Django code. + +.. warning:: + + **Back up your database first!** + + For SQLite, this means making a copy of the single file that stores the + database (the name of that file is the ``DATABASE_NAME`` in your settings.py + file). + +To upgrade each application to use a ``DecimalField``, you can do the +following, replacing ```` in the code below with each app's name: + +.. code-block:: bash + + $ ./manage.py dumpdata --format=xml > data-dump.xml + $ ./manage.py reset + $ ./manage.py loaddata data-dump.xml + +Notes: + + 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. + + 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. + + 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. + +If something goes wrong in the above process, just copy your backed up +database file over the original file and start again. + +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 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``_()`` (the callable object whose name is a single underscore) is no longer +monkeypatched into builtins -- that is, it's no longer available magically in +every module. + +If you were previously relying on ``_()`` always being present, you should now +explicitly import ``ugettext`` or ``ugettext_lazy``, if appropriate, and alias +it to ``_`` yourself:: + + from django.utils.translation import ugettext as _ + +HTTP request/response objects +----------------------------- + +Dictionary access to ``HttpRequest`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``HttpRequest`` objects no longer directly support dictionary-style +access; previously, both ``GET`` and ``POST`` data were directly +available on the ``HttpRequest`` object (e.g., you could check for a +piece of form data by using ``if 'some_form_key' in request`` or by +reading ``request['some_form_key']``. This is no longer supported; if +you need access to the combined ``GET`` and ``POST`` data, use +``request.REQUEST`` instead. + +It is strongly suggested, however, that you always explicitly look in +the appropriate dictionary for the type of request you expect to +receive (``request.GET`` or ``request.POST``); relying on the combined +``request.REQUEST`` dictionary can mask the origin of incoming data. + +Accessing ``HTTPResponse`` headers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``django.http.HttpResponse.headers`` has been renamed to ``_headers`` and +:class:`~django.http.HttpResponse` now supports containment checking directly. +So use ``if header in response:`` instead of ``if header in response.headers:``. + +Generic relations +----------------- + +Generic relations have been moved out of core +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The generic relation classes -- ``GenericForeignKey`` and ``GenericRelation`` +-- have moved into the :mod:`django.contrib.contenttypes` module. + +Testing +------- + +:meth:`django.test.Client.login` has changed +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Old (0.96):: + + from django.test import Client + c = Client() + c.login('/path/to/login','myuser','mypassword') + +New (1.0):: + + # ... same as above, but then: + c.login(username='myuser', password='mypassword') + +Management commands +------------------- + +Running management commands from your code +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:mod:`django.core.management` has been greatly refactored. + +Calls to management services in your code now need to use +``call_command``. For example, if you have some test code that calls flush and +load_data:: + + from django.core import management + management.flush(verbosity=0, interactive=False) + management.load_data(['test_data'], verbosity=0) + +...you'll need to change this code to read:: + + from django.core import management + management.call_command('flush', verbosity=0, interactive=False) + management.call_command('loaddata', 'test_data', verbosity=0) + +Subcommands must now precede options +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``django-admin.py`` and ``manage.py`` now require subcommands to precede +options. So: + +.. code-block:: bash + + $ django-admin.py --settings=foo.bar runserver + +...no longer works and should be changed to: + +.. code-block:: bash + + $ django-admin.py runserver --settings=foo.bar + +Syndication +----------- + +``Feed.__init__`` has changed +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``__init__()`` method of the syndication framework's ``Feed`` class now +takes 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 code that subclasses ``Feed`` and overrides the +``__init__()`` method, and code that calls ``Feed.__init__()`` directly. + +Data structures +--------------- + +``SortedDictFromList`` is gone +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``django.newforms.forms.SortedDictFromList`` was removed. +:class:`django.utils.datastructures.SortedDict` can now be instantiated with +a sequence of tuples. + +To update your code: + + 1. Use :class:`django.utils.datastructures.SortedDict` wherever you were + using ``django.newforms.forms.SortedDictFromList``. + + 2. Because :meth:`django.utils.datastructures.SortedDict.copy` doesn't + 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. + +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`` +======================================= =================================================== +