From c49f2d21ea055ae3c9b66cfd62a9f527a2f6d3b5 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Mon, 20 Aug 2007 05:00:52 +0000 Subject: [PATCH] newforms-admin: Merged to [5983] git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@5984 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- AUTHORS | 2 + django/conf/global_settings.py | 3 +- django/conf/locale/es/LC_MESSAGES/django.mo | Bin 53852 -> 53873 bytes django/conf/locale/es/LC_MESSAGES/django.po | 9 +- django/conf/project_template/settings.py | 4 +- .../contrib/admin/templates/admin/index.html | 2 +- django/contrib/auth/models.py | 17 +- django/contrib/contenttypes/generic.py | 10 +- django/contrib/databrowse/datastructures.py | 23 +- .../contrib/databrowse/plugins/calendars.py | 10 +- .../databrowse/templates/databrowse/base.html | 5 +- .../templates/databrowse/base_site.html | 1 + .../templates/databrowse/calendar_day.html | 4 +- .../databrowse/calendar_homepage.html | 2 +- .../templates/databrowse/calendar_main.html | 2 +- .../templates/databrowse/calendar_month.html | 4 +- .../templates/databrowse/calendar_year.html | 2 +- .../templates/databrowse/choice_detail.html | 2 +- .../templates/databrowse/choice_list.html | 2 +- .../databrowse/fieldchoice_detail.html | 4 +- .../databrowse/fieldchoice_homepage.html | 2 +- .../databrowse/fieldchoice_list.html | 2 +- .../templates/databrowse/homepage.html | 2 +- .../templates/databrowse/model_detail.html | 4 +- .../templates/databrowse/object_detail.html | 2 +- django/contrib/localflavor/pl/__init__.py | 0 django/contrib/localflavor/pl/forms.py | 84 ++ .../localflavor/pl/pl_administrativeunits.py | 385 +++++++++ .../contrib/localflavor/pl/pl_voivodeships.py | 24 + django/core/management/base.py | 6 +- .../management/commands/createcachetable.py | 11 +- django/core/management/commands/loaddata.py | 4 +- .../management/commands/sqlsequencereset.py | 4 +- django/core/management/commands/syncdb.py | 8 +- django/core/management/sql.py | 134 +-- django/db/backends/__init__.py | 218 +++++ django/db/backends/ado_mssql/base.py | 191 ++--- django/db/backends/dummy/base.py | 35 +- django/db/backends/mysql/base.py | 252 ++---- django/db/backends/mysql/introspection.py | 3 +- django/db/backends/mysql_old/base.py | 252 +++--- django/db/backends/mysql_old/introspection.py | 3 +- django/db/backends/oracle/base.py | 806 +++++++++--------- django/db/backends/oracle/creation.py | 19 +- django/db/backends/oracle/introspection.py | 4 +- django/db/backends/postgresql/base.py | 242 +----- .../db/backends/postgresql/introspection.py | 4 +- django/db/backends/postgresql/operations.py | 109 +++ .../db/backends/postgresql_psycopg2/base.py | 228 +---- .../postgresql_psycopg2/introspection.py | 4 +- django/db/backends/sqlite3/base.py | 206 ++--- django/db/backends/sqlite3/introspection.py | 4 +- django/db/backends/util.py | 27 - django/db/models/__init__.py | 2 +- django/db/models/base.py | 59 +- django/db/models/fields/related.py | 9 +- django/db/models/loading.py | 279 +++--- django/db/models/options.py | 10 +- django/db/models/query.py | 110 +-- django/template/defaulttags.py | 12 +- django/template/loader_tags.py | 2 +- django/test/utils.py | 16 +- docs/contributing.txt | 2 +- docs/install.txt | 4 +- docs/man/compile-messages.1 | 40 + docs/man/daily_cleanup.1 | 34 + docs/man/gather_profile_stats.1 | 26 + docs/man/make-messages.1 | 62 ++ docs/newforms.txt | 4 +- docs/templates.txt | 11 + docs/templates_python.txt | 2 +- docs/tutorial04.txt | 13 +- docs/url_dispatch.txt | 16 +- tests/modeltests/test_client/models.py | 1 + tests/regressiontests/forms/localflavor.py | 57 +- tests/regressiontests/string_lookup/models.py | 16 + 76 files changed, 2325 insertions(+), 1849 deletions(-) create mode 100644 django/contrib/databrowse/templates/databrowse/base_site.html create mode 100644 django/contrib/localflavor/pl/__init__.py create mode 100644 django/contrib/localflavor/pl/forms.py create mode 100644 django/contrib/localflavor/pl/pl_administrativeunits.py create mode 100644 django/contrib/localflavor/pl/pl_voivodeships.py create mode 100644 django/db/backends/postgresql/operations.py create mode 100644 docs/man/compile-messages.1 create mode 100644 docs/man/daily_cleanup.1 create mode 100644 docs/man/gather_profile_stats.1 create mode 100644 docs/man/make-messages.1 diff --git a/AUTHORS b/AUTHORS index 3ce1875d33..d640ebb2c9 100644 --- a/AUTHORS +++ b/AUTHORS @@ -103,6 +103,7 @@ answer newbie questions, and generally made Django that much better: dusk@woofle.net Andy Dustman Clint Ecker + Nick Efford eibaan@gmail.com enlight Enrico @@ -197,6 +198,7 @@ answer newbie questions, and generally made Django that much better: mccutchen@gmail.com michael.mcewan@gmail.com mikko@sorl.net + Slawek Mikula mitakummaa@gmail.com mmarshall Andreas Mock diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index f1ef70dbca..e11ea7531b 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -25,7 +25,8 @@ ADMINS = () INTERNAL_IPS = () # Local time zone for this installation. All choices can be found here: -# http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name (although not all +# systems may support all possibilities). TIME_ZONE = 'America/Chicago' # Language code for this installation. All choices can be found here: diff --git a/django/conf/locale/es/LC_MESSAGES/django.mo b/django/conf/locale/es/LC_MESSAGES/django.mo index 7beda77032a97bfb01ec42baca0729e40f8ba8b8..be8be1916278cabb0ec53316fa5b8cdc2ac31f94 100644 GIT binary patch delta 12209 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 delta 12206 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\n" "Language-Team: Castellano \n" @@ -22,6 +22,7 @@ msgstr "%(object)s de este %(type)s ya existen en este %(field)s." #: db/models/manipulators.py:310 contrib/admin/views/main.py:342 #: contrib/admin/views/main.py:344 contrib/admin/views/main.py:346 +#: core/validators.py:275 msgid "and" msgstr "y" @@ -510,6 +511,10 @@ msgstr "%(number)d %(type)s" msgid ", %(number)d %(type)s" msgstr ", %(number)d %(type)s" +#: utils/text.py:127 +msgid "or" +msgstr "o" + #: utils/dateformat.py:41 msgid "p.m." msgstr "p.m" @@ -765,7 +770,7 @@ msgstr "ocho" msgid "nine" msgstr "nueve" -#: contrib/auth/views.py:41 +#: contrib/auth/views.py:47 msgid "Logged out" msgstr "Sesión terminada" diff --git a/django/conf/project_template/settings.py b/django/conf/project_template/settings.py index c1e296cbe4..ecd716b50d 100644 --- a/django/conf/project_template/settings.py +++ b/django/conf/project_template/settings.py @@ -17,8 +17,8 @@ DATABASE_HOST = '' # Set to empty string for localhost. Not used wit DATABASE_PORT = '' # Set to empty string for default. Not used with sqlite3. # Local time zone for this installation. Choices can be found here: -# http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE -# although not all variations may be possible on all operating systems. +# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name +# although not all choices may be avilable on all operating systems. # If running in a Windows environment this must be set to the same as your # system time zone. TIME_ZONE = 'America/Chicago' diff --git a/django/contrib/admin/templates/admin/index.html b/django/contrib/admin/templates/admin/index.html index f1a5e499a4..76b3bcc838 100644 --- a/django/contrib/admin/templates/admin/index.html +++ b/django/contrib/admin/templates/admin/index.html @@ -59,7 +59,7 @@ {% else %} {% endif %} diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py index 183d48642e..0b29311dbc 100644 --- a/django/contrib/auth/models.py +++ b/django/contrib/auth/models.py @@ -1,6 +1,6 @@ from django.core import validators from django.core.exceptions import ImproperlyConfigured -from django.db import backend, connection, models +from django.db import connection, models from django.contrib.contenttypes.models import ContentType from django.utils.encoding import smart_str from django.utils.translation import ugettext_lazy as _ @@ -188,6 +188,7 @@ class User(models.Model): # AND gp."group_id" = ug."group_id" # AND ct."id" = p."content_type_id" # AND ug."user_id" = %s, [self.id]) + qn = connection.ops.quote_name sql = """ SELECT ct.%s, p.%s FROM %s p, %s gp, %s ug, %s ct @@ -195,13 +196,13 @@ class User(models.Model): AND gp.%s = ug.%s AND ct.%s = p.%s AND ug.%s = %%s""" % ( - backend.quote_name('app_label'), backend.quote_name('codename'), - backend.quote_name('auth_permission'), backend.quote_name('auth_group_permissions'), - backend.quote_name('auth_user_groups'), backend.quote_name('django_content_type'), - backend.quote_name('id'), backend.quote_name('permission_id'), - backend.quote_name('group_id'), backend.quote_name('group_id'), - backend.quote_name('id'), backend.quote_name('content_type_id'), - backend.quote_name('user_id'),) + qn('app_label'), qn('codename'), + qn('auth_permission'), qn('auth_group_permissions'), + qn('auth_user_groups'), qn('django_content_type'), + qn('id'), qn('permission_id'), + qn('group_id'), qn('group_id'), + qn('id'), qn('content_type_id'), + qn('user_id'),) cursor.execute(sql, [self.id]) self._group_perm_cache = set(["%s.%s" % (row[0], row[1]) for row in cursor.fetchall()]) return self._group_perm_cache diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py index efa49edf73..b738a269ec 100644 --- a/django/contrib/contenttypes/generic.py +++ b/django/contrib/contenttypes/generic.py @@ -4,7 +4,7 @@ Classes allowing "generic" relations through ContentType and object-id fields. from django import oldforms from django.core.exceptions import ObjectDoesNotExist -from django.db import backend +from django.db import connection from django.db.models import signals from django.db.models.fields.related import RelatedField, Field, ManyToManyRel from django.db.models.loading import get_model @@ -163,13 +163,15 @@ class ReverseGenericRelatedObjectsDescriptor(object): superclass = rel_model._default_manager.__class__ RelatedManager = create_generic_related_manager(superclass) + qn = connection.ops.quote_name + manager = RelatedManager( model = rel_model, instance = instance, symmetrical = (self.field.rel.symmetrical and instance.__class__ == rel_model), - join_table = backend.quote_name(self.field.m2m_db_table()), - source_col_name = backend.quote_name(self.field.m2m_column_name()), - target_col_name = backend.quote_name(self.field.m2m_reverse_name()), + join_table = qn(self.field.m2m_db_table()), + source_col_name = qn(self.field.m2m_column_name()), + target_col_name = qn(self.field.m2m_reverse_name()), content_type = ContentType.objects.get_for_model(self.field.model), content_type_field_name = self.field.content_type_field_name, object_id_field_name = self.field.object_id_field_name diff --git a/django/contrib/databrowse/datastructures.py b/django/contrib/databrowse/datastructures.py index fa4fb76714..0a26db18b1 100644 --- a/django/contrib/databrowse/datastructures.py +++ b/django/contrib/databrowse/datastructures.py @@ -8,6 +8,7 @@ from django.utils import dateformat from django.utils.text import capfirst from django.utils.translation import get_date_formats from django.utils.encoding import smart_unicode, smart_str, iri_to_uri +from django.db.models.query import QuerySet EMPTY_VALUE = '(None)' @@ -30,8 +31,12 @@ class EasyModel(object): return '%s%s/%s/' % (self.site.root_url, self.model._meta.app_label, self.model._meta.module_name) def objects(self, **kwargs): - for obj in self.model._default_manager.filter(**kwargs): - yield EasyInstance(self, obj) + return self.get_query_set().filter(**kwargs) + + def get_query_set(self): + easy_qs = self.model._default_manager.get_query_set()._clone(klass=EasyQuerySet) + easy_qs._easymodel = self + return easy_qs def object_by_pk(self, pk): return EasyInstance(self, self.model._default_manager.get(pk=pk)) @@ -194,3 +199,17 @@ class EasyInstanceField(object): else: lst = [(self.values()[0], None)] return lst + +class EasyQuerySet(QuerySet): + """ + When creating (or cloning to) an `EasyQuerySet`, make sure to set the + `_easymodel` variable to the related `EasyModel`. + """ + def iterator(self, *args, **kwargs): + for obj in super(EasyQuerySet, self).iterator(*args, **kwargs): + yield EasyInstance(self._easymodel, obj) + + def _clone(self, *args, **kwargs): + c = super(EasyQuerySet, self)._clone(*args, **kwargs) + c._easymodel = self._easymodel + return c diff --git a/django/contrib/databrowse/plugins/calendars.py b/django/contrib/databrowse/plugins/calendars.py index 651abd7695..1b1a197290 100644 --- a/django/contrib/databrowse/plugins/calendars.py +++ b/django/contrib/databrowse/plugins/calendars.py @@ -64,22 +64,22 @@ class CalendarPlugin(DatabrowsePlugin): def calendar_view(self, request, field, year=None, month=None, day=None): easy_model = EasyModel(self.site, self.model) + queryset = easy_model.get_query_set() extra_context = {'root_url': self.site.root_url, 'model': easy_model, 'field': field} if day is not None: - # TODO: The objects in this template should be EasyInstances - return date_based.archive_day(request, year, month, day, self.model.objects.all(), field.name, + return date_based.archive_day(request, year, month, day, queryset, field.name, template_name='databrowse/calendar_day.html', allow_empty=False, allow_future=True, extra_context=extra_context) elif month is not None: - return date_based.archive_month(request, year, month, self.model.objects.all(), field.name, + return date_based.archive_month(request, year, month, queryset, field.name, template_name='databrowse/calendar_month.html', allow_empty=False, allow_future=True, extra_context=extra_context) elif year is not None: - return date_based.archive_year(request, year, self.model.objects.all(), field.name, + return date_based.archive_year(request, year, queryset, field.name, template_name='databrowse/calendar_year.html', allow_empty=False, allow_future=True, extra_context=extra_context) else: - return date_based.archive_index(request, self.model.objects.all(), field.name, + return date_based.archive_index(request, queryset, field.name, template_name='databrowse/calendar_main.html', allow_empty=True, allow_future=True, extra_context=extra_context) assert False, ('%s, %s, %s, %s' % (field, year, month, day)) diff --git a/django/contrib/databrowse/templates/databrowse/base.html b/django/contrib/databrowse/templates/databrowse/base.html index 30ba5bb2fc..a3419851c4 100644 --- a/django/contrib/databrowse/templates/databrowse/base.html +++ b/django/contrib/databrowse/templates/databrowse/base.html @@ -2,6 +2,7 @@ {% block title %}{% endblock %} +{% block style %} +{% endblock %} +{% block extrahead %}{% endblock %} - +
{% block content %}{% endblock %}
diff --git a/django/contrib/databrowse/templates/databrowse/base_site.html b/django/contrib/databrowse/templates/databrowse/base_site.html new file mode 100644 index 0000000000..b577ab8427 --- /dev/null +++ b/django/contrib/databrowse/templates/databrowse/base_site.html @@ -0,0 +1 @@ +{% extends "databrowse/base.html" %} diff --git a/django/contrib/databrowse/templates/databrowse/calendar_day.html b/django/contrib/databrowse/templates/databrowse/calendar_day.html index 4e8a8dabbd..b0f57e1eae 100644 --- a/django/contrib/databrowse/templates/databrowse/calendar_day.html +++ b/django/contrib/databrowse/templates/databrowse/calendar_day.html @@ -1,4 +1,4 @@ -{% extends "databrowse/base.html" %} +{% extends "databrowse/base_site.html" %} {% block title %}{{ model.verbose_name_plural|capfirst }} with {{ field.verbose_name }} {{ day|date:"F j, Y" }}{% endblock %} @@ -6,7 +6,7 @@ -

{{ model.verbose_name_plural|capfirst }} with {{ field.verbose_name }} on {{ day|date:"F j, Y" }}

+

{{ object_list.count }} {% if object_list.count|pluralize %}{{ model.verbose_name_plural|escape }}{% else %}{{ model.verbose_name|escape }}{% endif %} with {{ field.verbose_name }} on {{ day|date:"F j, Y" }}

    {% for object in object_list %} diff --git a/django/contrib/databrowse/templates/databrowse/calendar_homepage.html b/django/contrib/databrowse/templates/databrowse/calendar_homepage.html index 5bbc42e353..daf45dfdeb 100644 --- a/django/contrib/databrowse/templates/databrowse/calendar_homepage.html +++ b/django/contrib/databrowse/templates/databrowse/calendar_homepage.html @@ -1,4 +1,4 @@ -{% extends "databrowse/base.html" %} +{% extends "databrowse/base_site.html" %} {% block title %}Calendars{% endblock %} diff --git a/django/contrib/databrowse/templates/databrowse/calendar_main.html b/django/contrib/databrowse/templates/databrowse/calendar_main.html index 7f9ba03bbe..18ee5bdc8b 100644 --- a/django/contrib/databrowse/templates/databrowse/calendar_main.html +++ b/django/contrib/databrowse/templates/databrowse/calendar_main.html @@ -1,4 +1,4 @@ -{% extends "databrowse/base.html" %} +{% extends "databrowse/base_site.html" %} {% block title %}{{ field.verbose_name|capfirst }} calendar{% endblock %} diff --git a/django/contrib/databrowse/templates/databrowse/calendar_month.html b/django/contrib/databrowse/templates/databrowse/calendar_month.html index 9ff0cae08f..67506f452f 100644 --- a/django/contrib/databrowse/templates/databrowse/calendar_month.html +++ b/django/contrib/databrowse/templates/databrowse/calendar_month.html @@ -1,4 +1,4 @@ -{% extends "databrowse/base.html" %} +{% extends "databrowse/base_site.html" %} {% block title %}{{ model.verbose_name_plural|capfirst }} with {{ field.verbose_name }} in {{ month|date:"F Y" }}{% endblock %} @@ -6,7 +6,7 @@ -

    {{ model.verbose_name_plural|capfirst }} with {{ field.verbose_name }} in {{ month|date:"F Y" }}

    +

    {{ object_list.count }} {% if object_list.count|pluralize %}{{ model.verbose_name_plural|escape }}{% else %}{{ model.verbose_name|escape }}{% endif %} with {{ field.verbose_name }} on {{ day|date:"F Y" }}

      {% for object in object_list %} diff --git a/django/contrib/databrowse/templates/databrowse/calendar_year.html b/django/contrib/databrowse/templates/databrowse/calendar_year.html index 676ae88e27..729c2ddf3b 100644 --- a/django/contrib/databrowse/templates/databrowse/calendar_year.html +++ b/django/contrib/databrowse/templates/databrowse/calendar_year.html @@ -1,4 +1,4 @@ -{% extends "databrowse/base.html" %} +{% extends "databrowse/base_site.html" %} {% block title %}{{ model.verbose_name_plural|capfirst }} with {{ field.verbose_name }} in {{ year }}{% endblock %} diff --git a/django/contrib/databrowse/templates/databrowse/choice_detail.html b/django/contrib/databrowse/templates/databrowse/choice_detail.html index 35a67f4528..e6add55021 100644 --- a/django/contrib/databrowse/templates/databrowse/choice_detail.html +++ b/django/contrib/databrowse/templates/databrowse/choice_detail.html @@ -1,4 +1,4 @@ -{% extends "databrowse/base.html" %} +{% extends "databrowse/base_site.html" %} {% block title %}{{ model.verbose_name_plural|capfirst }} by {{ field.field.verbose_name }}: {{ value|escape }}{% endblock %} diff --git a/django/contrib/databrowse/templates/databrowse/choice_list.html b/django/contrib/databrowse/templates/databrowse/choice_list.html index 3122330635..07a1319459 100644 --- a/django/contrib/databrowse/templates/databrowse/choice_list.html +++ b/django/contrib/databrowse/templates/databrowse/choice_list.html @@ -1,4 +1,4 @@ -{% extends "databrowse/base.html" %} +{% extends "databrowse/base_site.html" %} {% block title %}{{ model.verbose_name_plural|capfirst }} by {{ field.field.verbose_name }}{% endblock %} diff --git a/django/contrib/databrowse/templates/databrowse/fieldchoice_detail.html b/django/contrib/databrowse/templates/databrowse/fieldchoice_detail.html index a620ec931c..d1c8b877cb 100644 --- a/django/contrib/databrowse/templates/databrowse/fieldchoice_detail.html +++ b/django/contrib/databrowse/templates/databrowse/fieldchoice_detail.html @@ -1,4 +1,4 @@ -{% extends "databrowse/base.html" %} +{% extends "databrowse/base_site.html" %} {% block title %}{{ model.verbose_name_plural|capfirst|escape }} with {{ field.field.verbose_name|escape }} {{ value|escape }}{% endblock %} @@ -6,7 +6,7 @@ -

      {{ model.verbose_name_plural|capfirst|escape }} with {{ field.field.verbose_name|escape }} {{ value|escape }}

      +

      {{ object_list.count }} {% if object_list.count|pluralize %}{{ model.verbose_name_plural|escape }}{% else %}{{ model.verbose_name|escape }}{% endif %} with {{ field.field.verbose_name|escape }} {{ value|escape }}

        {% for object in object_list %} diff --git a/django/contrib/databrowse/templates/databrowse/fieldchoice_homepage.html b/django/contrib/databrowse/templates/databrowse/fieldchoice_homepage.html index ad842c1e6d..8760bd4ad1 100644 --- a/django/contrib/databrowse/templates/databrowse/fieldchoice_homepage.html +++ b/django/contrib/databrowse/templates/databrowse/fieldchoice_homepage.html @@ -1,4 +1,4 @@ -{% extends "databrowse/base.html" %} +{% extends "databrowse/base_site.html" %} {% block title %}Browsable fields in {{ model.verbose_name_plural|escape }}{% endblock %} diff --git a/django/contrib/databrowse/templates/databrowse/fieldchoice_list.html b/django/contrib/databrowse/templates/databrowse/fieldchoice_list.html index 952e771af6..9f41213a6f 100644 --- a/django/contrib/databrowse/templates/databrowse/fieldchoice_list.html +++ b/django/contrib/databrowse/templates/databrowse/fieldchoice_list.html @@ -1,4 +1,4 @@ -{% extends "databrowse/base.html" %} +{% extends "databrowse/base_site.html" %} {% block title %}{{ model.verbose_name_plural|capfirst|escape }} by {{ field.field.verbose_name|escape }}{% endblock %} diff --git a/django/contrib/databrowse/templates/databrowse/homepage.html b/django/contrib/databrowse/templates/databrowse/homepage.html index e4f3d60b16..104a804253 100644 --- a/django/contrib/databrowse/templates/databrowse/homepage.html +++ b/django/contrib/databrowse/templates/databrowse/homepage.html @@ -1,4 +1,4 @@ -{% extends "databrowse/base.html" %} +{% extends "databrowse/base_site.html" %} {% block title %}Databrowse{% endblock %} diff --git a/django/contrib/databrowse/templates/databrowse/model_detail.html b/django/contrib/databrowse/templates/databrowse/model_detail.html index 69e02af975..2084b18ca7 100644 --- a/django/contrib/databrowse/templates/databrowse/model_detail.html +++ b/django/contrib/databrowse/templates/databrowse/model_detail.html @@ -1,4 +1,4 @@ -{% extends "databrowse/base.html" %} +{% extends "databrowse/base_site.html" %} {% block title %}{{ model.verbose_name_plural|capfirst }}{% endblock %} @@ -6,7 +6,7 @@ -

        {{ model.verbose_name_plural|capfirst }}

        +

        {{ model.objects.count }} {% if model.objects.count|pluralize %}{{ model.verbose_name_plural }}{% else %}{{ model.verbose_name }}{% endif %}

        {{ plugin_html }} diff --git a/django/contrib/databrowse/templates/databrowse/object_detail.html b/django/contrib/databrowse/templates/databrowse/object_detail.html index af5b937985..e9977743fd 100644 --- a/django/contrib/databrowse/templates/databrowse/object_detail.html +++ b/django/contrib/databrowse/templates/databrowse/object_detail.html @@ -1,4 +1,4 @@ -{% extends "databrowse/base.html" %} +{% extends "databrowse/base_site.html" %} {% block title %}{{ object.model.verbose_name|capfirst }}: {{ object }}{% endblock %} diff --git a/django/contrib/localflavor/pl/__init__.py b/django/contrib/localflavor/pl/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django/contrib/localflavor/pl/forms.py b/django/contrib/localflavor/pl/forms.py new file mode 100644 index 0000000000..62806362dc --- /dev/null +++ b/django/contrib/localflavor/pl/forms.py @@ -0,0 +1,84 @@ +""" +Polish-specific form helpers +""" + +from django.newforms import ValidationError +from django.newforms.fields import Select, RegexField +from django.utils.translation import ugettext as _ + +class PLVoivodeshipSelect(Select): + """ + A select widget with list of Polish voivodeships (administrative provinces) + as choices. + """ + def __init__(self, attrs=None): + from pl_voivodeships import VOIVODESHIP_CHOICES + super(PLVoivodeshipSelect, self).__init__(attrs, choices=VOIVODESHIP_CHOICES) + +class PLAdministrativeUnitSelect(Select): + """ + A select widget with list of Polish administrative units as choices. + """ + def __init__(self, attrs=None): + from pl_administrativeunits import ADMINISTRATIVE_UNIT_CHOICES + super(PLAdministrativeUnitSelect, self).__init__(attrs, choices=ADMINISTRATIVE_UNIT_CHOICES) + +class PLNationalIdentificationNumberField(RegexField): + """ + A form field that validates as Polish Identification Number (PESEL). + + Checks the following rules: + * the length consist of 11 digits + * has a valid checksum + + The algorithm is documented at http://en.wikipedia.org/wiki/PESEL. + """ + + def has_valid_checksum(self, number): + """ + Calculates a checksum with the provided algorithm. + """ + multiple_table = (1, 3, 7, 9, 1, 3, 7, 9, 1, 3, 1) + result = 0 + for i in range(len(number)): + result += int(number[i])*multiple_table[i] + + if result % 10 == 0: + return True + else: + return False + + def __init__(self, *args, **kwargs): + super(PLNationalIdentificationNumberField, self).__init__(r'^\d{11}$', + max_length=None, min_length=None, error_message=_(u'National Identification Number consists of 11 digits.'), + *args, **kwargs) + + def clean(self,value): + super(PLNationalIdentificationNumberField, self).clean(value) + if not self.has_valid_checksum(value): + raise ValidationError(_(u'Wrong checksum for the National Identification Number.')) + return u'%s' % value + + +class PLTaxNumberField(RegexField): + """ + A form field that validates as Polish Tax Number (NIP). + Valid forms are: XXX-XXX-YY-YY or XX-XX-YYY-YYY. + """ + def __init__(self, *args, **kwargs): + super(PLTaxNumberField, self).__init__(r'^\d{3}-\d{3}-\d{2}-\d{2}$|^\d{2}-\d{2}-\d{3}-\d{3}$', + max_length=None, min_length=None, + error_message=_(u'Enter a tax number field (NIP) in the format XXX-XXX-XX-XX or XX-XX-XXX-XXX.'), *args, **kwargs) + + +class PLPostalCodeField(RegexField): + """ + A form field that validates as Polish postal code. + Valid code is XX-XXX where X is digit. + """ + def __init__(self, *args, **kwargs): + super(PLPostalCodeField, self).__init__(r'^\d{2}-\d{3}$', + max_length=None, min_length=None, + error_message=_(u'Enter a postal code in the format XX-XXX.'), + *args, **kwargs) + diff --git a/django/contrib/localflavor/pl/pl_administrativeunits.py b/django/contrib/localflavor/pl/pl_administrativeunits.py new file mode 100644 index 0000000000..9777ea2b61 --- /dev/null +++ b/django/contrib/localflavor/pl/pl_administrativeunits.py @@ -0,0 +1,385 @@ +# -*- coding: utf-8 -*- +""" +Polish administrative units as in http://pl.wikipedia.org/wiki/Podzia%C5%82_administracyjny_Polski +""" + + +ADMINISTRATIVE_UNIT_CHOICES = ( + ('wroclaw', u'Wrocław'), + ('jeleniagora', u'Jelenia Góra'), + ('legnica', u'Legnica'), + ('boleslawiecki', u'bolesławiecki'), + ('dzierzoniowski', u'dzierżoniowski'), + ('glogowski', u'głogowski'), + ('gorowski', u'górowski'), + ('jaworski', u'jaworski'), + ('jeleniogorski', u'jeleniogórski'), + ('kamiennogorski', u'kamiennogórski'), + ('klodzki', u'kłodzki'), + ('legnicki', u'legnicki'), + ('lubanski', u'lubański'), + ('lubinski', u'lubiński'), + ('lwowecki', u'lwówecki'), + ('milicki', u'milicki'), + ('olesnicki', u'oleśnicki'), + ('olawski', u'oławski'), + ('polkowicki', u'polkowicki'), + ('strzelinski', u'strzeliński'), + ('sredzki', u'średzki'), + ('swidnicki', u'świdnicki'), + ('trzebnicki', u'trzebnicki'), + ('walbrzyski', u'wałbrzyski'), + ('wolowski', u'wołowski'), + ('wroclawski', u'wrocławski'), + ('zabkowicki', u'ząbkowicki'), + ('zgorzelecki', u'zgorzelecki'), + ('zlotoryjski', u'złotoryjski'), + ('bydgoszcz', u'Bydgoszcz'), + ('torun', u'Toruń'), + ('wloclawek', u'Włocławek'), + ('grudziadz', u'Grudziądz'), + ('aleksandrowski', u'aleksandrowski'), + ('brodnicki', u'brodnicki'), + ('bydgoski', u'bydgoski'), + ('chelminski', u'chełmiński'), + ('golubsko-dobrzynski', u'golubsko-dobrzyński'), + ('grudziadzki', u'grudziądzki'), + ('inowroclawski', u'inowrocławski'), + ('lipnowski', u'lipnowski'), + ('mogilenski', u'mogileński'), + ('nakielski', u'nakielski'), + ('radziejowski', u'radziejowski'), + ('rypinski', u'rypiński'), + ('sepolenski', u'sępoleński'), + ('swiecki', u'świecki'), + ('torunski', u'toruński'), + ('tucholski', u'tucholski'), + ('wabrzeski', u'wąbrzeski'), + ('wloclawski', u'wrocławski'), + ('zninski', u'źniński'), + ('lublin', u'Lublin'), + ('biala-podlaska', u'Biała Podlaska'), + ('chelm', u'Chełm'), + ('zamosc', u'Zamość'), + ('bialski', u'bialski'), + ('bilgorajski', u'biłgorajski'), + ('chelmski', u'chełmski'), + ('hrubieszowski', u'hrubieszowski'), + ('janowski', u'janowski'), + ('krasnostawski', u'krasnostawski'), + ('krasnicki', u'kraśnicki'), + ('lubartowski', u'lubartowski'), + ('lubelski', u'lubelski'), + ('leczynski', u'łęczyński'), + ('lukowski', u'łukowski'), + ('opolski', u'opolski'), + ('parczewski', u'parczewski'), + ('pulawski', u'puławski'), + ('radzynski', u'radzyński'), + ('rycki', u'rycki'), + ('swidnicki', u'świdnicki'), + ('tomaszowski', u'tomaszowski'), + ('wlodawski', u'włodawski'), + ('zamojski', u'zamojski'), + ('gorzow-wielkopolski', u'Gorzów Wielkopolski'), + ('zielona-gora', u'Zielona Góra'), + ('gorzowski', u'gorzowski'), + ('krosnienski', u'krośnieński'), + ('miedzyrzecki', u'międzyrzecki'), + ('nowosolski', u'nowosolski'), + ('slubicki', u'słubicki'), + ('strzelecko-drezdenecki', u'strzelecko-drezdenecki'), + ('sulecinski', u'suleńciński'), + ('swiebodzinski', u'świebodziński'), + ('wschowski', u'wschowski'), + ('zielonogorski', u'zielonogórski'), + ('zaganski', u'żagański'), + ('zarski', u'żarski'), + ('lodz', u'Łódź'), + ('piotrkow-trybunalski', u'Piotrków Trybunalski'), + ('skierniewice', u'Skierniewice'), + ('belchatowski', u'bełchatowski'), + ('brzezinski', u'brzeziński'), + ('kutnowski', u'kutnowski'), + ('laski', u'łaski'), + ('leczycki', u'łęczycki'), + ('lowicki', u'łowicki'), + ('lodzki wschodni', u'łódzki wschodni'), + ('opoczynski', u'opoczyński'), + ('pabianicki', u'pabianicki'), + ('pajeczanski', u'pajęczański'), + ('piotrkowski', u'piotrkowski'), + ('poddebicki', u'poddębicki'), + ('radomszczanski', u'radomszczański'), + ('rawski', u'rawski'), + ('sieradzki', u'sieradzki'), + ('skierniewicki', u'skierniewicki'), + ('tomaszowski', u'tomaszowski'), + ('wielunski', u'wieluński'), + ('wieruszowski', u'wieruszowski'), + ('zdunskowolski', u'zduńskowolski'), + ('zgierski', u'zgierski'), + ('krakow', u'Kraków'), + ('tarnow', u'Tarnów'), + ('nowy-sacz', u'Nowy Sącz'), + ('bochenski', u'bocheński'), + ('brzeski', u'brzeski'), + ('chrzanowski', u'chrzanowski'), + ('dabrowski', u'dąbrowski'), + ('gorlicki', u'gorlicki'), + ('krakowski', u'krakowski'), + ('limanowski', u'limanowski'), + ('miechowski', u'miechowski'), + ('myslenicki', u'myślenicki'), + ('nowosadecki', u'nowosądecki'), + ('nowotarski', u'nowotarski'), + ('olkuski', u'olkuski'), + ('oswiecimski', u'oświęcimski'), + ('proszowicki', u'proszowicki'), + ('suski', u'suski'), + ('tarnowski', u'tarnowski'), + ('tatrzanski', u'tatrzański'), + ('wadowicki', u'wadowicki'), + ('wielicki', u'wielicki'), + ('warszawa', u'Warszawa'), + ('ostroleka', u'Ostrołęka'), + ('plock', u'Płock'), + ('radom', u'Radom'), + ('siedlce', u'Siedlce'), + ('bialobrzeski', u'białobrzeski'), + ('ciechanowski', u'ciechanowski'), + ('garwolinski', u'garwoliński'), + ('gostyninski', u'gostyniński'), + ('grodziski', u'grodziski'), + ('grojecki', u'grójecki'), + ('kozienicki', u'kozenicki'), + ('legionowski', u'legionowski'), + ('lipski', u'lipski'), + ('losicki', u'łosicki'), + ('makowski', u'makowski'), + ('minski', u'miński'), + ('mlawski', u'mławski'), + ('nowodworski', u'nowodworski'), + ('ostrolecki', u'ostrołęcki'), + ('ostrowski', u'ostrowski'), + ('otwocki', u'otwocki'), + ('piaseczynski', u'piaseczyński'), + ('plocki', u'płocki'), + ('plonski', u'płoński'), + ('pruszkowski', u'pruszkowski'), + ('przasnyski', u'przasnyski'), + ('przysuski', u'przysuski'), + ('pultuski', u'pułtuski'), + ('radomski', u'radomski'), + ('siedlecki', u'siedlecki'), + ('sierpecki', u'sierpecki'), + ('sochaczewski', u'sochaczewski'), + ('sokolowski', u'sokołowski'), + ('szydlowiecki', u'szydłowiecki'), + ('warszawski-zachodni', u'warszawski zachodni'), + ('wegrowski', u'węgrowski'), + ('wolominski', u'wołomiński'), + ('wyszkowski', u'wyszkowski'), + ('zwolenski', u'zwoleński'), + ('zurominski', u'żuromiński'), + ('zyrardowski', u'żyrardowski'), + ('opole', u'Opole'), + ('brzeski', u'brzeski'), + ('glubczycki', u'głubczyski'), + ('kedzierzynsko-kozielski', u'kędzierzyński-kozielski'), + ('kluczborski', u'kluczborski'), + ('krapkowicki', u'krapkowicki'), + ('namyslowski', u'namysłowski'), + ('nyski', u'nyski'), + ('oleski', u'oleski'), + ('opolski', u'opolski'), + ('prudnicki', u'prudnicki'), + ('strzelecki', u'strzelecki'), + ('rzeszow', u'Rzeszów'), + ('krosno', u'Krosno'), + ('przemysl', u'Przemyśl'), + ('tarnobrzeg', u'Tarnobrzeg'), + ('bieszczadzki', u'bieszczadzki'), + ('brzozowski', u'brzozowski'), + ('debicki', u'dębicki'), + ('jaroslawski', u'jarosławski'), + ('jasielski', u'jasielski'), + ('kolbuszowski', u'kolbuszowski'), + ('krosnienski', u'krośnieński'), + ('leski', u'leski'), + ('lezajski', u'leżajski'), + ('lubaczowski', u'lubaczowski'), + ('lancucki', u'łańcucki'), + ('mielecki', u'mielecki'), + ('nizanski', u'niżański'), + ('przemyski', u'przemyski'), + ('przeworski', u'przeworski'), + ('ropczycko-sedziszowski', u'ropczycko-sędziszowski'), + ('rzeszowski', u'rzeszowski'), + ('sanocki', u'sanocki'), + ('stalowowolski', u'stalowowolski'), + ('strzyzowski', u'strzyżowski'), + ('tarnobrzeski', u'tarnobrzeski'), + ('bialystok', u'Białystok'), + ('lomza', u'Łomża'), + ('suwalki', u'Suwałki'), + ('augustowski', u'augustowski'), + ('bialostocki', u'białostocki'), + ('bielski', u'bielski'), + ('grajewski', u'grajewski'), + ('hajnowski', u'hajnowski'), + ('kolnenski', u'kolneński'), + ('łomzynski', u'łomżyński'), + ('moniecki', u'moniecki'), + ('sejnenski', u'sejneński'), + ('siemiatycki', u'siematycki'), + ('sokolski', u'sokólski'), + ('suwalski', u'suwalski'), + ('wysokomazowiecki', u'wysokomazowiecki'), + ('zambrowski', u'zambrowski'), + ('gdansk', u'Gdańsk'), + ('gdynia', u'Gdynia'), + ('slupsk', u'Słupsk'), + ('sopot', u'Sopot'), + ('bytowski', u'bytowski'), + ('chojnicki', u'chojnicki'), + ('czluchowski', u'człuchowski'), + ('kartuski', u'kartuski'), + ('koscierski', u'kościerski'), + ('kwidzynski', u'kwidzyński'), + ('leborski', u'lęborski'), + ('malborski', u'malborski'), + ('nowodworski', u'nowodworski'), + ('gdanski', u'gdański'), + ('pucki', u'pucki'), + ('slupski', u'słupski'), + ('starogardzki', u'starogardzki'), + ('sztumski', u'sztumski'), + ('tczewski', u'tczewski'), + ('wejherowski', u'wejcherowski'), + ('katowice', u'Katowice'), + ('bielsko-biala', u'Bielsko-Biała'), + ('bytom', u'Bytom'), + ('chorzow', u'Chorzów'), + ('czestochowa', u'Częstochowa'), + ('dabrowa-gornicza', u'Dąbrowa Górnicza'), + ('gliwice', u'Gliwice'), + ('jastrzebie-zdroj', u'Jastrzębie Zdrój'), + ('jaworzno', u'Jaworzno'), + ('myslowice', u'Mysłowice'), + ('piekary-slaskie', u'Piekary Śląskie'), + ('ruda-slaska', u'Ruda Śląska'), + ('rybnik', u'Rybnik'), + ('siemianowice-slaskie', u'Siemianowice Śląskie'), + ('sosnowiec', u'Sosnowiec'), + ('swietochlowice', u'Świętochłowice'), + ('tychy', u'Tychy'), + ('zabrze', u'Zabrze'), + ('zory', u'Żory'), + ('bedzinski', u'będziński'), + ('bielski', u'bielski'), + ('bierunsko-ledzinski', u'bieruńsko-lędziński'), + ('cieszynski', u'cieszyński'), + ('czestochowski', u'częstochowski'), + ('gliwicki', u'gliwicki'), + ('klobucki', u'kłobucki'), + ('lubliniecki', u'lubliniecki'), + ('mikolowski', u'mikołowski'), + ('myszkowski', u'myszkowski'), + ('pszczynski', u'pszczyński'), + ('raciborski', u'raciborski'), + ('rybnicki', u'rybnicki'), + ('tarnogorski', u'tarnogórski'), + ('wodzislawski', u'wodzisławski'), + ('zawiercianski', u'zawierciański'), + ('zywiecki', u'żywiecki'), + ('kielce', u'Kielce'), + ('buski', u'buski'), + ('jedrzejowski', u'jędrzejowski'), + ('kazimierski', u'kazimierski'), + ('kielecki', u'kielecki'), + ('konecki', u'konecki'), + ('opatowski', u'opatowski'), + ('ostrowiecki', u'ostrowiecki'), + ('pinczowski', u'pińczowski'), + ('sandomierski', u'sandomierski'), + ('skarzyski', u'skarżyski'), + ('starachowicki', u'starachowicki'), + ('staszowski', u'staszowski'), + ('wloszczowski', u'włoszczowski'), + ('olsztyn', u'Olsztyn'), + ('elblag', u'Elbląg'), + ('bartoszycki', u'bartoszycki'), + ('braniewski', u'braniewski'), + ('dzialdowski', u'działdowski'), + ('elblaski', u'elbląski'), + ('elcki', u'ełcki'), + ('gizycki', u'giżycki'), + ('goldapski', u'gołdapski'), + ('ilawski', u'iławski'), + ('ketrzynski', u'kętrzyński'), + ('lidzbarski', u'lidzbarski'), + ('mragowski', u'mrągowski'), + ('nidzicki', u'nidzicki'), + ('nowomiejski', u'nowomiejski'), + ('olecki', u'olecki'), + ('olsztynski', u'olsztyński'), + ('ostrodzki', u'ostródzki'), + ('piski', u'piski'), + ('szczycienski', u'szczycieński'), + ('wegorzewski', u'węgorzewski'), + ('poznan', u'Poznań'), + ('kalisz', u'Kalisz'), + ('konin', u'Konin'), + ('leszno', u'Leszno'), + ('chodzieski', u'chodziejski'), + ('czarnkowsko-trzcianecki', u'czarnkowsko-trzcianecki'), + ('gnieznienski', u'gnieźnieński'), + ('gostynski', u'gostyński'), + ('grodziski', u'grodziski'), + ('jarocinski', u'jarociński'), + ('kaliski', u'kaliski'), + ('kepinski', u'kępiński'), + ('kolski', u'kolski'), + ('koninski', u'koniński'), + ('koscianski', u'kościański'), + ('krotoszynski', u'krotoszyński'), + ('leszczynski', u'leszczyński'), + ('miedzychodzki', u'międzychodzki'), + ('nowotomyski', u'nowotomyski'), + ('obornicki', u'obornicki'), + ('ostrowski', u'ostrowski'), + ('ostrzeszowski', u'ostrzeszowski'), + ('pilski', u'pilski'), + ('pleszewski', u'pleszewski'), + ('poznanski', u'poznański'), + ('rawicki', u'rawicki'), + ('slupecki', u'słupecki'), + ('szamotulski', u'szamotulski'), + ('sredzki', u'średzki'), + ('sremski', u'śremski'), + ('turecki', u'turecki'), + ('wagrowiecki', u'wągrowiecki'), + ('wolsztynski', u'wolsztyński'), + ('wrzesinski', u'wrzesiński'), + ('zlotowski', u'złotowski'), + ('bialogardzki', u'białogardzki'), + ('choszczenski', u'choszczeński'), + ('drawski', u'drawski'), + ('goleniowski', u'goleniowski'), + ('gryficki', u'gryficki'), + ('gryfinski', u'gryfiński'), + ('kamienski', u'kamieński'), + ('kolobrzeski', u'kołobrzeski'), + ('koszalinski', u'koszaliński'), + ('lobeski', u'łobeski'), + ('mysliborski', u'myśliborski'), + ('policki', u'policki'), + ('pyrzycki', u'pyrzycki'), + ('slawienski', u'sławieński'), + ('stargardzki', u'stargardzki'), + ('szczecinecki', u'szczecinecki'), + ('swidwinski', u'świdwiński'), + ('walecki', u'wałecki'), +) + diff --git a/django/contrib/localflavor/pl/pl_voivodeships.py b/django/contrib/localflavor/pl/pl_voivodeships.py new file mode 100644 index 0000000000..d8caede3c8 --- /dev/null +++ b/django/contrib/localflavor/pl/pl_voivodeships.py @@ -0,0 +1,24 @@ +""" +Polish voivodeship as in http://en.wikipedia.org/wiki/Poland#Administrative_division +""" + +from django.utils.translation import ugettext_lazy as _ + +VOIVODESHIP_CHOICES = ( + ('lower_silesia', _('Lower Silesia')), + ('kuyavia-pomerania', _('Kuyavia-Pomerania')), + ('lublin', _('Lublin')), + ('lubusz', _('Lubusz')), + ('lodz', _('Lodz')), + ('lesser_poland', _('Lesser Poland')), + ('masovia', _('Masovia')), + ('opole', _('Opole')), + ('subcarpatia', _('Subcarpatia')), + ('podlasie', _('Podlasie')), + ('pomerania', _('Pomerania')), + ('silesia', _('Silesia')), + ('swietokrzyskie', _('Swietokrzyskie')), + ('warmia-masuria', _('Warmia-Masuria')), + ('greater_poland', _('Greater Poland')), + ('west_pomerania', _('West Pomerania')), +) diff --git a/django/core/management/base.py b/django/core/management/base.py index 3b88234a94..866dec4407 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -34,9 +34,9 @@ class BaseCommand(object): if output: if self.output_transaction: # This needs to be imported here, because it relies on settings. - from django.db import backend - if backend.get_start_transaction_sql(): - print self.style.SQL_KEYWORD(backend.get_start_transaction_sql()) + from django.db import connection + if connection.ops.start_transaction_sql(): + print self.style.SQL_KEYWORD(connection.ops.start_transaction_sql()) print output if self.output_transaction: print self.style.SQL_KEYWORD("COMMIT;") diff --git a/django/core/management/commands/createcachetable.py b/django/core/management/commands/createcachetable.py index dc8b1b7854..c1fa1a26a5 100644 --- a/django/core/management/commands/createcachetable.py +++ b/django/core/management/commands/createcachetable.py @@ -8,7 +8,7 @@ class Command(LabelCommand): requires_model_validation = False def handle_label(self, tablename, **options): - from django.db import backend, connection, transaction, models + from django.db import connection, transaction, models fields = ( # "key" is a reserved word in MySQL, so use "cache_key" instead. models.CharField(name='cache_key', max_length=255, unique=True, primary_key=True), @@ -17,8 +17,9 @@ class Command(LabelCommand): ) table_output = [] index_output = [] + qn = connection.ops.quote_name for f in fields: - field_output = [backend.quote_name(f.name), f.db_type()] + field_output = [qn(f.name), f.db_type()] field_output.append("%sNULL" % (not f.null and "NOT " or "")) if f.unique: field_output.append("UNIQUE") @@ -27,10 +28,10 @@ class Command(LabelCommand): if f.db_index: unique = f.unique and "UNIQUE " or "" index_output.append("CREATE %sINDEX %s_%s ON %s (%s);" % \ - (unique, tablename, f.name, backend.quote_name(tablename), - backend.quote_name(f.name))) + (unique, tablename, f.name, qn(tablename), + qn(f.name))) table_output.append(" ".join(field_output)) - full_statement = ["CREATE TABLE %s (" % backend.quote_name(tablename)] + full_statement = ["CREATE TABLE %s (" % qn(tablename)] for i, line in enumerate(table_output): full_statement.append(' %s%s' % (line, i < len(table_output)-1 and ',' or '')) full_statement.append(');') diff --git a/django/core/management/commands/loaddata.py b/django/core/management/commands/loaddata.py index 82b8968c61..4cbdb55c67 100644 --- a/django/core/management/commands/loaddata.py +++ b/django/core/management/commands/loaddata.py @@ -15,7 +15,7 @@ class Command(BaseCommand): def handle(self, *fixture_labels, **options): from django.db.models import get_apps from django.core import serializers - from django.db import connection, transaction, backend + from django.db import connection, transaction from django.conf import settings self.style = no_style() @@ -105,7 +105,7 @@ class Command(BaseCommand): (format, fixture_name, humanize(fixture_dir)) if count[0] > 0: - sequence_sql = backend.get_sql_sequence_reset(self.style, models) + sequence_sql = connection.ops.sequence_reset_sql(self.style, models) if sequence_sql: if verbosity > 1: print "Resetting sequences" diff --git a/django/core/management/commands/sqlsequencereset.py b/django/core/management/commands/sqlsequencereset.py index 8ed2fbaf97..6b12d83843 100644 --- a/django/core/management/commands/sqlsequencereset.py +++ b/django/core/management/commands/sqlsequencereset.py @@ -5,5 +5,5 @@ class Command(AppCommand): output_transaction = True def handle_app(self, app, **options): - from django.db import backend, models - return '\n'.join(backend.get_sql_sequence_reset(self.style, models.get_models(app))) + from django.db import connection, models + return '\n'.join(connection.ops.sequence_reset_sql(self.style, models.get_models(app))) diff --git a/django/core/management/commands/syncdb.py b/django/core/management/commands/syncdb.py index d4f430c069..f7b694d6e7 100644 --- a/django/core/management/commands/syncdb.py +++ b/django/core/management/commands/syncdb.py @@ -12,7 +12,7 @@ class Command(NoArgsCommand): args = '[--verbosity] [--noinput]' def handle_noargs(self, **options): - from django.db import backend, connection, transaction, models + from django.db import connection, transaction, models from django.conf import settings from django.core.management.sql import table_list, installed_models, sql_model_create, sql_for_pending_references, many_to_many_sql_for_model, custom_sql_for_model, sql_indexes_for_model, emit_post_sync_signal @@ -34,7 +34,7 @@ class Command(NoArgsCommand): # Get a list of all existing database tables, # so we know what needs to be added. table_list = table_list() - if backend.uses_case_insensitive_names: + if connection.features.uses_case_insensitive_names: table_name_converter = str.upper else: table_name_converter = lambda x: x @@ -125,6 +125,6 @@ class Command(NoArgsCommand): else: transaction.commit_unless_managed() - # Install the 'initialdata' fixture, using format discovery + # Install the 'initial_data' fixture, using format discovery from django.core.management import call_command - call_command('loaddata', 'initial_data', **options) + call_command('loaddata', 'initial_data', verbosity=verbosity) diff --git a/django/core/management/sql.py b/django/core/management/sql.py index d9736262a1..11056bbf3b 100644 --- a/django/core/management/sql.py +++ b/django/core/management/sql.py @@ -15,12 +15,12 @@ def table_list(): def installed_models(table_list): "Returns a set of all models that are installed, given a list of existing table names." - from django.db import backend, models + from django.db import connection, models all_models = [] for app in models.get_apps(): for model in models.get_models(app): all_models.append(model) - if backend.uses_case_insensitive_names: + if connection.features.uses_case_insensitive_names: converter = lambda x: x.upper() else: converter = lambda x: x @@ -95,7 +95,7 @@ def sql_create(app, style): def sql_delete(app, style): "Returns a list of the DROP TABLE SQL statements for the given app." - from django.db import backend, connection, models, get_introspection_module + from django.db import connection, models, get_introspection_module from django.db.backends.util import truncate_name introspection = get_introspection_module() @@ -110,12 +110,13 @@ def sql_delete(app, style): table_names = introspection.get_table_list(cursor) else: table_names = [] - if backend.uses_case_insensitive_names: + if connection.features.uses_case_insensitive_names: table_name_converter = str.upper else: table_name_converter = lambda x: x output = [] + qn = connection.ops.quote_name # Output DROP TABLE statements for standard application tables. to_delete = set() @@ -136,8 +137,8 @@ def sql_delete(app, style): if cursor and table_name_converter(model._meta.db_table) in table_names: # Drop the table now output.append('%s %s;' % (style.SQL_KEYWORD('DROP TABLE'), - style.SQL_TABLE(backend.quote_name(model._meta.db_table)))) - if backend.supports_constraints and model in references_to_delete: + style.SQL_TABLE(qn(model._meta.db_table)))) + if connection.features.supports_constraints and model in references_to_delete: for rel_class, f in references_to_delete[model]: table = rel_class._meta.db_table col = f.column @@ -146,12 +147,14 @@ def sql_delete(app, style): r_name = '%s_refs_%s_%x' % (col, r_col, abs(hash((table, r_table)))) output.append('%s %s %s %s;' % \ (style.SQL_KEYWORD('ALTER TABLE'), - style.SQL_TABLE(backend.quote_name(table)), - style.SQL_KEYWORD(backend.get_drop_foreignkey_sql()), - style.SQL_FIELD(truncate_name(r_name, backend.get_max_name_length())))) + style.SQL_TABLE(qn(table)), + style.SQL_KEYWORD(connection.ops.drop_foreignkey_sql()), + style.SQL_FIELD(truncate_name(r_name, connection.ops.max_name_length())))) del references_to_delete[model] - if model._meta.has_auto_field and hasattr(backend, 'get_drop_sequence'): - output.append(backend.get_drop_sequence(model._meta.db_table)) + if model._meta.has_auto_field: + ds = connection.ops.drop_sequence_sql(model._meta.db_table) + if ds: + output.append(ds) # Output DROP TABLE statements for many-to-many tables. for model in app_models: @@ -159,9 +162,10 @@ def sql_delete(app, style): for f in opts.many_to_many: if cursor and table_name_converter(f.m2m_db_table()) in table_names: output.append("%s %s;" % (style.SQL_KEYWORD('DROP TABLE'), - style.SQL_TABLE(backend.quote_name(f.m2m_db_table())))) - if hasattr(backend, 'get_drop_sequence'): - output.append(backend.get_drop_sequence("%s_%s" % (model._meta.db_table, f.column))) + style.SQL_TABLE(qn(f.m2m_db_table())))) + ds = connection.ops.drop_sequence_sql("%s_%s" % (model._meta.db_table, f.column)) + if ds: + output.append(ds) app_label = app_models[0]._meta.app_label @@ -178,9 +182,9 @@ def sql_reset(app, style): return sql_delete(app, style) + sql_all(app, style) def sql_flush(style): - "Returns a list of the SQL statements used to flush the database" - from django.db import backend - statements = backend.get_sql_flush(style, table_list(), sequence_list()) + "Returns a list of the SQL statements used to flush the database." + from django.db import connection + statements = connection.ops.sql_flush(style, table_list(), sequence_list()) return statements def sql_custom(app): @@ -213,12 +217,13 @@ def sql_model_create(model, style, known_models=set()): Returns the SQL required to create a single model, as a tuple of: (list_of_sql, pending_references_dict) """ - from django.db import backend, models + from django.db import connection, models opts = model._meta final_output = [] table_output = [] pending_references = {} + qn = connection.ops.quote_name for f in opts.fields: col_type = f.db_type() tablespace = f.db_tablespace or opts.db_tablespace @@ -227,23 +232,23 @@ def sql_model_create(model, style, known_models=set()): # database columns in this table. continue # Make the definition (e.g. 'foo VARCHAR(30)') for this field. - field_output = [style.SQL_FIELD(backend.quote_name(f.column)), + field_output = [style.SQL_FIELD(qn(f.column)), style.SQL_COLTYPE(col_type)] field_output.append(style.SQL_KEYWORD('%sNULL' % (not f.null and 'NOT ' or ''))) - if f.unique and (not f.primary_key or backend.allows_unique_and_pk): + if f.unique and (not f.primary_key or connection.features.allows_unique_and_pk): field_output.append(style.SQL_KEYWORD('UNIQUE')) if f.primary_key: field_output.append(style.SQL_KEYWORD('PRIMARY KEY')) - if tablespace and backend.supports_tablespaces and (f.unique or f.primary_key) and backend.autoindexes_primary_keys: + if tablespace and connection.features.supports_tablespaces and (f.unique or f.primary_key) and connection.features.autoindexes_primary_keys: # We must specify the index tablespace inline, because we # won't be generating a CREATE INDEX statement for this field. - field_output.append(backend.get_tablespace_sql(tablespace, inline=True)) + field_output.append(connection.ops.tablespace_sql(tablespace, inline=True)) if f.rel: if f.rel.to in known_models: field_output.append(style.SQL_KEYWORD('REFERENCES') + ' ' + \ - style.SQL_TABLE(backend.quote_name(f.rel.to._meta.db_table)) + ' (' + \ - style.SQL_FIELD(backend.quote_name(f.rel.to._meta.get_field(f.rel.field_name).column)) + ')' + - backend.get_deferrable_sql() + style.SQL_TABLE(qn(f.rel.to._meta.db_table)) + ' (' + \ + style.SQL_FIELD(qn(f.rel.to._meta.get_field(f.rel.field_name).column)) + ')' + + connection.ops.deferrable_sql() ) else: # We haven't yet created the table to which this field @@ -251,25 +256,25 @@ def sql_model_create(model, style, known_models=set()): pr = pending_references.setdefault(f.rel.to, []).append((model, f)) table_output.append(' '.join(field_output)) if opts.order_with_respect_to: - table_output.append(style.SQL_FIELD(backend.quote_name('_order')) + ' ' + \ + table_output.append(style.SQL_FIELD(qn('_order')) + ' ' + \ style.SQL_COLTYPE(models.IntegerField().db_type()) + ' ' + \ style.SQL_KEYWORD('NULL')) for field_constraints in opts.unique_together: table_output.append(style.SQL_KEYWORD('UNIQUE') + ' (%s)' % \ - ", ".join([backend.quote_name(style.SQL_FIELD(opts.get_field(f).column)) for f in field_constraints])) + ", ".join([qn(style.SQL_FIELD(opts.get_field(f).column)) for f in field_constraints])) - full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(backend.quote_name(opts.db_table)) + ' ('] + full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(qn(opts.db_table)) + ' ('] for i, line in enumerate(table_output): # Combine and add commas. full_statement.append(' %s%s' % (line, i < len(table_output)-1 and ',' or '')) full_statement.append(')') - if opts.db_tablespace and backend.supports_tablespaces: - full_statement.append(backend.get_tablespace_sql(opts.db_tablespace)) + if opts.db_tablespace and connection.features.supports_tablespaces: + full_statement.append(connection.ops.tablespace_sql(opts.db_tablespace)) full_statement.append(';') final_output.append('\n'.join(full_statement)) - if opts.has_auto_field and hasattr(backend, 'get_autoinc_sql'): - # Add any extra SQL needed to support auto-incrementing primary keys - autoinc_sql = backend.get_autoinc_sql(opts.db_table) + if opts.has_auto_field: + # Add any extra SQL needed to support auto-incrementing primary keys. + autoinc_sql = connection.ops.autoinc_sql(opts.db_table) if autoinc_sql: for stmt in autoinc_sql: final_output.append(stmt) @@ -280,11 +285,12 @@ def sql_for_pending_references(model, style, pending_references): """ Returns any ALTER TABLE statements to add constraints after the fact. """ - from django.db import backend + from django.db import connection from django.db.backends.util import truncate_name + qn = connection.ops.quote_name final_output = [] - if backend.supports_constraints: + if connection.features.supports_constraints: opts = model._meta if model in pending_references: for rel_class, f in pending_references[model]: @@ -297,60 +303,61 @@ def sql_for_pending_references(model, style, pending_references): # So we are careful with character usage here. r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table)))) final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \ - (backend.quote_name(r_table), truncate_name(r_name, backend.get_max_name_length()), - backend.quote_name(r_col), backend.quote_name(table), backend.quote_name(col), - backend.get_deferrable_sql())) + (qn(r_table), truncate_name(r_name, connection.ops.max_name_length()), + qn(r_col), qn(table), qn(col), + connection.ops.deferrable_sql())) del pending_references[model] return final_output def many_to_many_sql_for_model(model, style): - from django.db import backend, models + from django.db import connection, models from django.contrib.contenttypes import generic opts = model._meta final_output = [] + qn = connection.ops.quote_name for f in opts.many_to_many: if not isinstance(f.rel, generic.GenericRel): tablespace = f.db_tablespace or opts.db_tablespace - if tablespace and backend.supports_tablespaces and backend.autoindexes_primary_keys: - tablespace_sql = ' ' + backend.get_tablespace_sql(tablespace, inline=True) + if tablespace and connection.features.supports_tablespaces and connection.features.autoindexes_primary_keys: + tablespace_sql = ' ' + connection.ops.tablespace_sql(tablespace, inline=True) else: tablespace_sql = '' table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \ - style.SQL_TABLE(backend.quote_name(f.m2m_db_table())) + ' ('] + style.SQL_TABLE(qn(f.m2m_db_table())) + ' ('] table_output.append(' %s %s %s%s,' % \ - (style.SQL_FIELD(backend.quote_name('id')), + (style.SQL_FIELD(qn('id')), style.SQL_COLTYPE(models.AutoField(primary_key=True).db_type()), style.SQL_KEYWORD('NOT NULL PRIMARY KEY'), tablespace_sql)) table_output.append(' %s %s %s %s (%s)%s,' % \ - (style.SQL_FIELD(backend.quote_name(f.m2m_column_name())), + (style.SQL_FIELD(qn(f.m2m_column_name())), style.SQL_COLTYPE(models.ForeignKey(model).db_type()), style.SQL_KEYWORD('NOT NULL REFERENCES'), - style.SQL_TABLE(backend.quote_name(opts.db_table)), - style.SQL_FIELD(backend.quote_name(opts.pk.column)), - backend.get_deferrable_sql())) + style.SQL_TABLE(qn(opts.db_table)), + style.SQL_FIELD(qn(opts.pk.column)), + connection.ops.deferrable_sql())) table_output.append(' %s %s %s %s (%s)%s,' % \ - (style.SQL_FIELD(backend.quote_name(f.m2m_reverse_name())), + (style.SQL_FIELD(qn(f.m2m_reverse_name())), style.SQL_COLTYPE(models.ForeignKey(f.rel.to).db_type()), style.SQL_KEYWORD('NOT NULL REFERENCES'), - style.SQL_TABLE(backend.quote_name(f.rel.to._meta.db_table)), - style.SQL_FIELD(backend.quote_name(f.rel.to._meta.pk.column)), - backend.get_deferrable_sql())) + style.SQL_TABLE(qn(f.rel.to._meta.db_table)), + style.SQL_FIELD(qn(f.rel.to._meta.pk.column)), + connection.ops.deferrable_sql())) table_output.append(' %s (%s, %s)%s' % \ (style.SQL_KEYWORD('UNIQUE'), - style.SQL_FIELD(backend.quote_name(f.m2m_column_name())), - style.SQL_FIELD(backend.quote_name(f.m2m_reverse_name())), + style.SQL_FIELD(qn(f.m2m_column_name())), + style.SQL_FIELD(qn(f.m2m_reverse_name())), tablespace_sql)) table_output.append(')') - if opts.db_tablespace and backend.supports_tablespaces: + if opts.db_tablespace and connection.features.supports_tablespaces: # f.db_tablespace is only for indices, so ignore its value here. - table_output.append(backend.get_tablespace_sql(opts.db_tablespace)) + table_output.append(connection.ops.tablespace_sql(opts.db_tablespace)) table_output.append(';') final_output.append('\n'.join(table_output)) # Add any extra SQL needed to support auto-incrementing PKs - autoinc_sql = backend.get_autoinc_sql(f.m2m_db_table()) + autoinc_sql = connection.ops.autoinc_sql(f.m2m_db_table()) if autoinc_sql: for stmt in autoinc_sql: final_output.append(stmt) @@ -386,23 +393,24 @@ def custom_sql_for_model(model): def sql_indexes_for_model(model, style): "Returns the CREATE INDEX SQL statements for a single model" - from django.db import backend + from django.db import connection output = [] + qn = connection.ops.quote_name for f in model._meta.fields: - if f.db_index and not ((f.primary_key or f.unique) and backend.autoindexes_primary_keys): + if f.db_index and not ((f.primary_key or f.unique) and connection.features.autoindexes_primary_keys): unique = f.unique and 'UNIQUE ' or '' tablespace = f.db_tablespace or model._meta.db_tablespace - if tablespace and backend.supports_tablespaces: - tablespace_sql = ' ' + backend.get_tablespace_sql(tablespace) + if tablespace and connection.features.supports_tablespaces: + tablespace_sql = ' ' + connection.ops.tablespace_sql(tablespace) else: tablespace_sql = '' output.append( style.SQL_KEYWORD('CREATE %sINDEX' % unique) + ' ' + \ - style.SQL_TABLE(backend.quote_name('%s_%s' % (model._meta.db_table, f.column))) + ' ' + \ + style.SQL_TABLE(qn('%s_%s' % (model._meta.db_table, f.column))) + ' ' + \ style.SQL_KEYWORD('ON') + ' ' + \ - style.SQL_TABLE(backend.quote_name(model._meta.db_table)) + ' ' + \ - "(%s)" % style.SQL_FIELD(backend.quote_name(f.column)) + \ + style.SQL_TABLE(qn(model._meta.db_table)) + ' ' + \ + "(%s)" % style.SQL_FIELD(qn(f.column)) + \ "%s;" % tablespace_sql ) return output diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py index e69de29bb2..f3c6f59258 100644 --- a/django/db/backends/__init__.py +++ b/django/db/backends/__init__.py @@ -0,0 +1,218 @@ +try: + # Only exists in Python 2.4+ + from threading import local +except ImportError: + # Import copy of _thread_local.py from Python 2.4 + from django.utils._threading_local import local + +class BaseDatabaseWrapper(local): + """ + Represents a database connection. + """ + ops = None + def __init__(self, **kwargs): + self.connection = None + self.queries = [] + self.options = kwargs + + def _commit(self): + if self.connection is not None: + return self.connection.commit() + + def _rollback(self): + if self.connection is not None: + return self.connection.rollback() + + def close(self): + if self.connection is not None: + self.connection.close() + self.connection = None + + def cursor(self): + from django.conf import settings + cursor = self._cursor(settings) + if settings.DEBUG: + return self.make_debug_cursor(cursor) + return cursor + + def make_debug_cursor(self, cursor): + from django.db.backends import util + return util.CursorDebugWrapper(cursor, self) + +class BaseDatabaseFeatures(object): + allows_group_by_ordinal = True + allows_unique_and_pk = True + autoindexes_primary_keys = True + needs_datetime_string_cast = True + needs_upper_for_iops = False + supports_constraints = True + supports_tablespaces = False + uses_case_insensitive_names = False + uses_custom_queryset = False + +class BaseDatabaseOperations(object): + """ + This class encapsulates all backend-specific differences, such as the way + a backend performs ordering or calculates the ID of a recently-inserted + row. + """ + def autoinc_sql(self, table): + """ + Returns any SQL needed to support auto-incrementing primary keys, or + None if no SQL is necessary. + + This SQL is executed when a table is created. + """ + return None + + def date_extract_sql(self, lookup_type, field_name): + """ + Given a lookup_type of 'year', 'month' or 'day', returns the SQL that + extracts a value from the given date field field_name. + """ + raise NotImplementedError() + + def date_trunc_sql(self, lookup_type, field_name): + """ + Given a lookup_type of 'year', 'month' or 'day', returns the SQL that + truncates the given date field field_name to a DATE object with only + the given specificity. + """ + raise NotImplementedError() + + def datetime_cast_sql(self): + """ + Returns the SQL necessary to cast a datetime value so that it will be + retrieved as a Python datetime object instead of a string. + + This SQL should include a '%s' in place of the field's name. This + method should return None if no casting is necessary. + """ + return None + + def deferrable_sql(self): + """ + Returns the SQL necessary to make a constraint "initially deferred" + during a CREATE TABLE statement. + """ + return '' + + def drop_foreignkey_sql(self): + """ + Returns the SQL command that drops a foreign key. + """ + return "DROP CONSTRAINT" + + def drop_sequence_sql(self, table): + """ + Returns any SQL necessary to drop the sequence for the given table. + Returns None if no SQL is necessary. + """ + return None + + def field_cast_sql(self, db_type): + """ + Given a column type (e.g. 'BLOB', 'VARCHAR'), returns the SQL necessary + to cast it before using it in a WHERE statement. Note that the + resulting string should contain a '%s' placeholder for the column being + searched against. + """ + return '%s' + + def fulltext_search_sql(self, field_name): + """ + Returns the SQL WHERE clause to use in order to perform a full-text + search of the given field_name. Note that the resulting string should + contain a '%s' placeholder for the value being searched against. + """ + raise NotImplementedError('Full-text search is not implemented for this database backend') + + def last_insert_id(self, cursor, table_name, pk_name): + """ + Given a cursor object that has just performed an INSERT statement into + a table that has an auto-incrementing ID, returns the newly created ID. + + This method also receives the table name and the name of the primary-key + column. + """ + return cursor.lastrowid + + def limit_offset_sql(self, limit, offset=None): + """ + Returns a LIMIT/OFFSET SQL clause, given a limit and optional offset. + """ + # 'LIMIT 40 OFFSET 20' + sql = "LIMIT %s" % limit + if offset and offset != 0: + sql += " OFFSET %s" % offset + return sql + + def max_name_length(self): + """ + Returns the maximum length of table and column names, or None if there + is no limit. + """ + return None + + def pk_default_value(self): + """ + Returns the value to use during an INSERT statement to specify that + the field should use its default value. + """ + return 'DEFAULT' + + def query_set_class(self, DefaultQuerySet): + """ + Given the default QuerySet class, returns a custom QuerySet class + to use for this backend. Returns None if a custom QuerySet isn't used. + See also BaseDatabaseFeatures.uses_custom_queryset, which regulates + whether this method is called at all. + """ + return None + + def quote_name(self, name): + """ + Returns a quoted version of the given table, index or column name. Does + not quote the given name if it's already been quoted. + """ + raise NotImplementedError() + + def random_function_sql(self): + """ + Returns a SQL expression that returns a random value. + """ + return 'RANDOM()' + + def sql_flush(self, style, tables, sequences): + """ + Returns a list of SQL statements required to remove all data from + the given database tables (without actually removing the tables + themselves). + + The `style` argument is a Style object as returned by either + color_style() or no_style() in django.core.management.color. + """ + raise NotImplementedError() + + def sequence_reset_sql(self, style, model_list): + """ + Returns a list of the SQL statements required to reset sequences for + the given models. + + The `style` argument is a Style object as returned by either + color_style() or no_style() in django.core.management.color. + """ + return [] # No sequence reset required by default. + + def start_transaction_sql(self): + """ + Returns the SQL statement required to start a transaction. + """ + return "BEGIN;" + + def tablespace_sql(self, tablespace, inline=False): + """ + Returns the tablespace SQL, or None if the backend doesn't use + tablespaces. + """ + return None diff --git a/django/db/backends/ado_mssql/base.py b/django/db/backends/ado_mssql/base.py index 0deb6aae64..07ce02d591 100644 --- a/django/db/backends/ado_mssql/base.py +++ b/django/db/backends/ado_mssql/base.py @@ -4,12 +4,12 @@ ADO MSSQL database backend for Django. Requires adodbapi 2.0.1: http://adodbapi.sourceforge.net/ """ -from django.db.backends import util +from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util try: import adodbapi as Database except ImportError, e: from django.core.exceptions import ImproperlyConfigured - raise ImproperlyConfigured, "Error loading adodbapi module: %s" % e + raise ImproperlyConfigured("Error loading adodbapi module: %s" % e) import datetime try: import mx @@ -48,148 +48,65 @@ def variantToPython(variant, adType): return res Database.convertVariantToPython = variantToPython -try: - # Only exists in Python 2.4+ - from threading import local -except ImportError: - # Import copy of _thread_local.py from Python 2.4 - from django.utils._threading_local import local +class DatabaseFeatures(BaseDatabaseFeatures): + supports_tablespaces = True -class DatabaseWrapper(local): - def __init__(self, **kwargs): - self.connection = None - self.queries = [] +class DatabaseOperations(BaseDatabaseOperations): + def date_extract_sql(self, lookup_type, field_name): + return "DATEPART(%s, %s)" % (lookup_type, field_name) - def cursor(self): - from django.conf import settings + def date_trunc_sql(self, lookup_type, field_name): + if lookup_type == 'year': + return "Convert(datetime, Convert(varchar, DATEPART(year, %s)) + '/01/01')" % field_name + if lookup_type == 'month': + return "Convert(datetime, Convert(varchar, DATEPART(year, %s)) + '/' + Convert(varchar, DATEPART(month, %s)) + '/01')" % (field_name, field_name) + if lookup_type == 'day': + return "Convert(datetime, Convert(varchar(12), %s))" % field_name + + def deferrable_sql(self): + return " DEFERRABLE INITIALLY DEFERRED" + + def last_insert_id(self, cursor, table_name, pk_name): + cursor.execute("SELECT %s FROM %s WHERE %s = @@IDENTITY" % (pk_name, table_name, pk_name)) + return cursor.fetchone()[0] + + def quote_name(self, name): + if name.startswith('[') and name.endswith(']'): + return name # Quoting once is enough. + return '[%s]' % name + + def random_function_sql(self): + return 'RAND()' + + def tablespace_sql(self, tablespace, inline=False): + return "ON %s" % self.quote_name(tablespace) + +class DatabaseWrapper(BaseDatabaseWrapper): + features = DatabaseFeatures() + ops = DatabaseOperations() + operators = { + 'exact': '= %s', + 'iexact': 'LIKE %s', + 'contains': 'LIKE %s', + 'icontains': 'LIKE %s', + 'gt': '> %s', + 'gte': '>= %s', + 'lt': '< %s', + 'lte': '<= %s', + 'startswith': 'LIKE %s', + 'endswith': 'LIKE %s', + 'istartswith': 'LIKE %s', + 'iendswith': 'LIKE %s', + } + + def _cursor(self, settings): if self.connection is None: if settings.DATABASE_NAME == '' or settings.DATABASE_USER == '': from django.core.exceptions import ImproperlyConfigured - raise ImproperlyConfigured, "You need to specify both DATABASE_NAME and DATABASE_USER in your Django settings file." + raise ImproperlyConfigured("You need to specify both DATABASE_NAME and DATABASE_USER in your Django settings file.") if not settings.DATABASE_HOST: settings.DATABASE_HOST = "127.0.0.1" # TODO: Handle DATABASE_PORT. conn_string = "PROVIDER=SQLOLEDB;DATA SOURCE=%s;UID=%s;PWD=%s;DATABASE=%s" % (settings.DATABASE_HOST, settings.DATABASE_USER, settings.DATABASE_PASSWORD, settings.DATABASE_NAME) self.connection = Database.connect(conn_string) - cursor = self.connection.cursor() - if settings.DEBUG: - return util.CursorDebugWrapper(cursor, self) - return cursor - - def _commit(self): - if self.connection is not None: - return self.connection.commit() - - def _rollback(self): - if self.connection is not None: - return self.connection.rollback() - - def close(self): - if self.connection is not None: - self.connection.close() - self.connection = None - -allows_group_by_ordinal = True -allows_unique_and_pk = True -autoindexes_primary_keys = True -needs_datetime_string_cast = True -needs_upper_for_iops = False -supports_constraints = True -supports_tablespaces = True -uses_case_insensitive_names = False - -def quote_name(name): - if name.startswith('[') and name.endswith(']'): - return name # Quoting once is enough. - return '[%s]' % name - -dictfetchone = util.dictfetchone -dictfetchmany = util.dictfetchmany -dictfetchall = util.dictfetchall - -def get_last_insert_id(cursor, table_name, pk_name): - cursor.execute("SELECT %s FROM %s WHERE %s = @@IDENTITY" % (pk_name, table_name, pk_name)) - return cursor.fetchone()[0] - -def get_date_extract_sql(lookup_type, table_name): - # lookup_type is 'year', 'month', 'day' - return "DATEPART(%s, %s)" % (lookup_type, table_name) - -def get_date_trunc_sql(lookup_type, field_name): - # lookup_type is 'year', 'month', 'day' - if lookup_type=='year': - return "Convert(datetime, Convert(varchar, DATEPART(year, %s)) + '/01/01')" % field_name - if lookup_type=='month': - return "Convert(datetime, Convert(varchar, DATEPART(year, %s)) + '/' + Convert(varchar, DATEPART(month, %s)) + '/01')" % (field_name, field_name) - if lookup_type=='day': - return "Convert(datetime, Convert(varchar(12), %s))" % field_name - -def get_datetime_cast_sql(): - return None - -def get_limit_offset_sql(limit, offset=None): - # TODO: This is a guess. Make sure this is correct. - sql = "LIMIT %s" % limit - if offset and offset != 0: - sql += " OFFSET %s" % offset - return sql - -def get_random_function_sql(): - return "RAND()" - -def get_deferrable_sql(): - return " DEFERRABLE INITIALLY DEFERRED" - -def get_fulltext_search_sql(field_name): - raise NotImplementedError - -def get_drop_foreignkey_sql(): - return "DROP CONSTRAINT" - -def get_pk_default_value(): - return "DEFAULT" - -def get_max_name_length(): - return None - -def get_start_transaction_sql(): - return "BEGIN;" - -def get_tablespace_sql(tablespace, inline=False): - return "ON %s" % quote_name(tablespace) - -def get_autoinc_sql(table): - return None - -def get_sql_flush(style, tables, sequences): - """Return a list of SQL statements required to remove all data from - all tables in the database (without actually removing the tables - themselves) and put the database in an empty 'initial' state - """ - # Return a list of 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements - # TODO - SQL not actually tested against ADO MSSQL yet! - # TODO - autoincrement indices reset required? See other get_sql_flush() implementations - sql_list = ['%s %s;' % \ - (style.SQL_KEYWORD('TRUNCATE'), - style.SQL_FIELD(quote_name(table)) - ) for table in tables] - -def get_sql_sequence_reset(style, model_list): - "Returns a list of the SQL statements to reset sequences for the given models." - # No sequence reset required - return [] - -OPERATOR_MAPPING = { - 'exact': '= %s', - 'iexact': 'LIKE %s', - 'contains': 'LIKE %s', - 'icontains': 'LIKE %s', - 'gt': '> %s', - 'gte': '>= %s', - 'lt': '< %s', - 'lte': '<= %s', - 'startswith': 'LIKE %s', - 'endswith': 'LIKE %s', - 'istartswith': 'LIKE %s', - 'iendswith': 'LIKE %s', -} + return self.connection.cursor() diff --git a/django/db/backends/dummy/base.py b/django/db/backends/dummy/base.py index f47cbdf3d4..50191f88fe 100644 --- a/django/db/backends/dummy/base.py +++ b/django/db/backends/dummy/base.py @@ -21,7 +21,14 @@ class DatabaseError(Exception): class IntegrityError(DatabaseError): pass -class DatabaseWrapper: +class ComplainOnGetattr(object): + def __getattr__(self, *args, **kwargs): + complain() + +class DatabaseWrapper(object): + features = ComplainOnGetattr() + ops = ComplainOnGetattr() + operators = {} cursor = complain _commit = complain _rollback = ignore @@ -30,28 +37,4 @@ class DatabaseWrapper: pass def close(self): - pass # close() - -supports_constraints = False -supports_tablespaces = False -quote_name = complain -dictfetchone = complain -dictfetchmany = complain -dictfetchall = complain -get_last_insert_id = complain -get_date_extract_sql = complain -get_date_trunc_sql = complain -get_datetime_cast_sql = complain -get_limit_offset_sql = complain -get_random_function_sql = complain -get_deferrable_sql = complain -get_fulltext_search_sql = complain -get_drop_foreignkey_sql = complain -get_pk_default_value = complain -get_max_name_length = ignore -get_start_transaction_sql = complain -get_autoinc_sql = complain -get_sql_flush = complain -get_sql_sequence_reset = complain - -OPERATOR_MAPPING = {} + pass diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index 25ae2fa84f..f913b88709 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -4,12 +4,12 @@ MySQL database backend for Django. Requires MySQLdb: http://sourceforge.net/projects/mysql-python """ -from django.db.backends import util +from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util try: import MySQLdb as Database except ImportError, e: from django.core.exceptions import ImproperlyConfigured - raise ImproperlyConfigured, "Error loading MySQLdb module: %s" % e + raise ImproperlyConfigured("Error loading MySQLdb module: %s" % e) # We want version (1, 2, 1, 'final', 2) or later. We can't just use # lexicographic ordering in this check because then (1, 2, 1, 'gamma') @@ -17,7 +17,7 @@ except ImportError, e: version = Database.version_info if (version < (1,2,1) or (version[:3] == (1, 2, 1) and (len(version) < 5 or version[3] != 'final' or version[4] < 2))): - raise ImportError, "MySQLdb-1.2.1p2 or newer is required; you have %s" % Database.__version__ + raise ImportError("MySQLdb-1.2.1p2 or newer is required; you have %s" % Database.__version__) from MySQLdb.converters import conversions from MySQLdb.constants import FIELD_TYPE @@ -53,19 +53,94 @@ server_version_re = re.compile(r'(\d{1,2})\.(\d{1,2})\.(\d{1,2})') # standard util.CursorDebugWrapper can be used. Also, using sql_mode # TRADITIONAL will automatically cause most warnings to be treated as errors. -try: - # Only exists in Python 2.4+ - from threading import local -except ImportError: - # Import copy of _thread_local.py from Python 2.4 - from django.utils._threading_local import local +class DatabaseFeatures(BaseDatabaseFeatures): + autoindexes_primary_keys = False + +class DatabaseOperations(BaseDatabaseOperations): + def date_extract_sql(self, lookup_type, field_name): + # http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html + return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), field_name) + + def date_trunc_sql(self, lookup_type, field_name): + fields = ['year', 'month', 'day', 'hour', 'minute', 'second'] + format = ('%%Y-', '%%m', '-%%d', ' %%H:', '%%i', ':%%s') # Use double percents to escape. + format_def = ('0000-', '01', '-01', ' 00:', '00', ':00') + try: + i = fields.index(lookup_type) + 1 + except ValueError: + sql = field_name + else: + format_str = ''.join([f for f in format[:i]] + [f for f in format_def[i:]]) + sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str) + return sql + + def drop_foreignkey_sql(self): + return "DROP FOREIGN KEY" + + def fulltext_search_sql(self, field_name): + return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name + + def limit_offset_sql(self, limit, offset=None): + # 'LIMIT 20,40' + sql = "LIMIT " + if offset and offset != 0: + sql += "%s," % offset + return sql + str(limit) + + def quote_name(self, name): + if name.startswith("`") and name.endswith("`"): + return name # Quoting once is enough. + return "`%s`" % name + + def random_function_sql(self): + return 'RAND()' + + def sql_flush(self, style, tables, sequences): + # NB: The generated SQL below is specific to MySQL + # 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements + # to clear all tables of all data + if tables: + sql = ['SET FOREIGN_KEY_CHECKS = 0;'] + for table in tables: + sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.quote_name(table)))) + sql.append('SET FOREIGN_KEY_CHECKS = 1;') + + # 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements + # to reset sequence indices + sql.extend(["%s %s %s %s %s;" % \ + (style.SQL_KEYWORD('ALTER'), + style.SQL_KEYWORD('TABLE'), + style.SQL_TABLE(self.quote_name(sequence['table'])), + style.SQL_KEYWORD('AUTO_INCREMENT'), + style.SQL_FIELD('= 1'), + ) for sequence in sequences]) + return sql + else: + return [] + +class DatabaseWrapper(BaseDatabaseWrapper): + features = DatabaseFeatures() + ops = DatabaseOperations() + operators = { + 'exact': '= %s', + 'iexact': 'LIKE %s', + 'contains': 'LIKE BINARY %s', + 'icontains': 'LIKE %s', + 'regex': 'REGEXP BINARY %s', + 'iregex': 'REGEXP %s', + 'gt': '> %s', + 'gte': '>= %s', + 'lt': '< %s', + 'lte': '<= %s', + 'startswith': 'LIKE BINARY %s', + 'endswith': 'LIKE BINARY %s', + 'istartswith': 'LIKE %s', + 'iendswith': 'LIKE %s', + } -class DatabaseWrapper(local): def __init__(self, **kwargs): - self.connection = None - self.queries = [] + super(DatabaseWrapper, self).__init__(**kwargs) self.server_version = None - self.options = kwargs def _valid_connection(self): if self.connection is not None: @@ -77,8 +152,7 @@ class DatabaseWrapper(local): self.connection = None return False - def cursor(self): - from django.conf import settings + def _cursor(self, settings): from warnings import filterwarnings if not self._valid_connection(): kwargs = { @@ -100,29 +174,16 @@ class DatabaseWrapper(local): kwargs['port'] = int(settings.DATABASE_PORT) kwargs.update(self.options) self.connection = Database.connect(**kwargs) - cursor = self.connection.cursor() - else: - cursor = self.connection.cursor() + cursor = self.connection.cursor() if settings.DEBUG: filterwarnings("error", category=Database.Warning) - return util.CursorDebugWrapper(cursor, self) return cursor - def _commit(self): - if self.connection is not None: - self.connection.commit() - def _rollback(self): - if self.connection is not None: - try: - self.connection.rollback() - except Database.NotSupportedError: - pass - - def close(self): - if self.connection is not None: - self.connection.close() - self.connection = None + try: + BaseDatabaseWrapper._rollback(self) + except Database.NotSupportedError: + pass def get_server_version(self): if not self.server_version: @@ -133,128 +194,3 @@ class DatabaseWrapper(local): raise Exception('Unable to determine MySQL version from version string %r' % self.connection.get_server_info()) self.server_version = tuple([int(x) for x in m.groups()]) return self.server_version - -allows_group_by_ordinal = True -allows_unique_and_pk = True -autoindexes_primary_keys = False -needs_datetime_string_cast = True # MySQLdb requires a typecast for dates -needs_upper_for_iops = False -supports_constraints = True -supports_tablespaces = False -uses_case_insensitive_names = False - -def quote_name(name): - if name.startswith("`") and name.endswith("`"): - return name # Quoting once is enough. - return "`%s`" % name - -dictfetchone = util.dictfetchone -dictfetchmany = util.dictfetchmany -dictfetchall = util.dictfetchall - -def get_last_insert_id(cursor, table_name, pk_name): - return cursor.lastrowid - -def get_date_extract_sql(lookup_type, table_name): - # lookup_type is 'year', 'month', 'day' - # http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html - return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), table_name) - -def get_date_trunc_sql(lookup_type, field_name): - # lookup_type is 'year', 'month', 'day' - fields = ['year', 'month', 'day', 'hour', 'minute', 'second'] - format = ('%%Y-', '%%m', '-%%d', ' %%H:', '%%i', ':%%s') # Use double percents to escape. - format_def = ('0000-', '01', '-01', ' 00:', '00', ':00') - try: - i = fields.index(lookup_type) + 1 - except ValueError: - sql = field_name - else: - format_str = ''.join([f for f in format[:i]] + [f for f in format_def[i:]]) - sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str) - return sql - -def get_datetime_cast_sql(): - return None - -def get_limit_offset_sql(limit, offset=None): - sql = "LIMIT " - if offset and offset != 0: - sql += "%s," % offset - return sql + str(limit) - -def get_random_function_sql(): - return "RAND()" - -def get_deferrable_sql(): - return "" - -def get_fulltext_search_sql(field_name): - return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name - -def get_drop_foreignkey_sql(): - return "DROP FOREIGN KEY" - -def get_pk_default_value(): - return "DEFAULT" - -def get_max_name_length(): - return None; - -def get_start_transaction_sql(): - return "BEGIN;" - -def get_autoinc_sql(table): - return None - -def get_sql_flush(style, tables, sequences): - """Return a list of SQL statements required to remove all data from - all tables in the database (without actually removing the tables - themselves) and put the database in an empty 'initial' state - - """ - # NB: The generated SQL below is specific to MySQL - # 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements - # to clear all tables of all data - if tables: - sql = ['SET FOREIGN_KEY_CHECKS = 0;'] + \ - ['%s %s;' % \ - (style.SQL_KEYWORD('TRUNCATE'), - style.SQL_FIELD(quote_name(table)) - ) for table in tables] + \ - ['SET FOREIGN_KEY_CHECKS = 1;'] - - # 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements - # to reset sequence indices - sql.extend(["%s %s %s %s %s;" % \ - (style.SQL_KEYWORD('ALTER'), - style.SQL_KEYWORD('TABLE'), - style.SQL_TABLE(quote_name(sequence['table'])), - style.SQL_KEYWORD('AUTO_INCREMENT'), - style.SQL_FIELD('= 1'), - ) for sequence in sequences]) - return sql - else: - return [] - -def get_sql_sequence_reset(style, model_list): - "Returns a list of the SQL statements to reset sequences for the given models." - # No sequence reset required - return [] - -OPERATOR_MAPPING = { - 'exact': '= %s', - 'iexact': 'LIKE %s', - 'contains': 'LIKE BINARY %s', - 'icontains': 'LIKE %s', - 'regex': 'REGEXP BINARY %s', - 'iregex': 'REGEXP %s', - 'gt': '> %s', - 'gte': '>= %s', - 'lt': '< %s', - 'lte': '<= %s', - 'startswith': 'LIKE BINARY %s', - 'endswith': 'LIKE BINARY %s', - 'istartswith': 'LIKE %s', - 'iendswith': 'LIKE %s', -} diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py index 39733311c5..68fe5d99e6 100644 --- a/django/db/backends/mysql/introspection.py +++ b/django/db/backends/mysql/introspection.py @@ -1,8 +1,9 @@ -from django.db.backends.mysql.base import quote_name +from django.db.backends.mysql.base import DatabaseOperations from MySQLdb import ProgrammingError, OperationalError from MySQLdb.constants import FIELD_TYPE import re +quote_name = DatabaseOperations().quote_name foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)` \(`([^`]*)`\)") def get_table_list(cursor): diff --git a/django/db/backends/mysql_old/base.py b/django/db/backends/mysql_old/base.py index 0116af53ea..0777c1dc93 100644 --- a/django/db/backends/mysql_old/base.py +++ b/django/db/backends/mysql_old/base.py @@ -4,13 +4,13 @@ MySQL database backend for Django. Requires MySQLdb: http://sourceforge.net/projects/mysql-python """ -from django.db.backends import util +from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util from django.utils.encoding import force_unicode try: import MySQLdb as Database except ImportError, e: from django.core.exceptions import ImproperlyConfigured - raise ImproperlyConfigured, "Error loading MySQLdb module: %s" % e + raise ImproperlyConfigured("Error loading MySQLdb module: %s" % e) from MySQLdb.converters import conversions from MySQLdb.constants import FIELD_TYPE import types @@ -48,14 +48,14 @@ class MysqlDebugWrapper: return self.cursor.execute(sql, params) except Database.Warning, w: self.cursor.execute("SHOW WARNINGS") - raise Database.Warning, "%s: %s" % (w, self.cursor.fetchall()) + raise Database.Warning("%s: %s" % (w, self.cursor.fetchall())) def executemany(self, sql, param_list): try: return self.cursor.executemany(sql, param_list) except Database.Warning, w: self.cursor.execute("SHOW WARNINGS") - raise Database.Warning, "%s: %s" % (w, self.cursor.fetchall()) + raise Database.Warning("%s: %s" % (w, self.cursor.fetchall())) def __getattr__(self, attr): if attr in self.__dict__: @@ -63,19 +63,94 @@ class MysqlDebugWrapper: else: return getattr(self.cursor, attr) -try: - # Only exists in Python 2.4+ - from threading import local -except ImportError: - # Import copy of _thread_local.py from Python 2.4 - from django.utils._threading_local import local +class DatabaseFeatures(BaseDatabaseFeatures): + autoindexes_primary_keys = False + +class DatabaseOperations(BaseDatabaseOperations): + def date_extract_sql(self, lookup_type, field_name): + # http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html + return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), field_name) + + def date_trunc_sql(self, lookup_type, field_name): + fields = ['year', 'month', 'day', 'hour', 'minute', 'second'] + format = ('%%Y-', '%%m', '-%%d', ' %%H:', '%%i', ':%%s') # Use double percents to escape. + format_def = ('0000-', '01', '-01', ' 00:', '00', ':00') + try: + i = fields.index(lookup_type) + 1 + except ValueError: + sql = field_name + else: + format_str = ''.join([f for f in format[:i]] + [f for f in format_def[i:]]) + sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str) + return sql + + def drop_foreignkey_sql(self): + return "DROP FOREIGN KEY" + + def fulltext_search_sql(self, field_name): + return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name + + def limit_offset_sql(self, limit, offset=None): + # 'LIMIT 20,40' + sql = "LIMIT " + if offset and offset != 0: + sql += "%s," % offset + return sql + str(limit) + + def quote_name(self, name): + if name.startswith("`") and name.endswith("`"): + return name # Quoting once is enough. + return "`%s`" % name + + def random_function_sql(self): + return 'RAND()' + + def sql_flush(self, style, tables, sequences): + # NB: The generated SQL below is specific to MySQL + # 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements + # to clear all tables of all data + if tables: + sql = ['SET FOREIGN_KEY_CHECKS = 0;'] + for table in tables: + sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.quote_name(table)))) + sql.append('SET FOREIGN_KEY_CHECKS = 1;') + + # 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements + # to reset sequence indices + sql.extend(["%s %s %s %s %s;" % \ + (style.SQL_KEYWORD('ALTER'), + style.SQL_KEYWORD('TABLE'), + style.SQL_TABLE(self.quote_name(sequence['table'])), + style.SQL_KEYWORD('AUTO_INCREMENT'), + style.SQL_FIELD('= 1'), + ) for sequence in sequences]) + return sql + else: + return [] + +class DatabaseWrapper(BaseDatabaseWrapper): + features = DatabaseFeatures() + ops = DatabaseOperations() + operators = { + 'exact': '= %s', + 'iexact': 'LIKE %s', + 'contains': 'LIKE BINARY %s', + 'icontains': 'LIKE %s', + 'regex': 'REGEXP BINARY %s', + 'iregex': 'REGEXP %s', + 'gt': '> %s', + 'gte': '>= %s', + 'lt': '< %s', + 'lte': '<= %s', + 'startswith': 'LIKE BINARY %s', + 'endswith': 'LIKE BINARY %s', + 'istartswith': 'LIKE %s', + 'iendswith': 'LIKE %s', + } -class DatabaseWrapper(local): def __init__(self, **kwargs): - self.connection = None - self.queries = [] + super(DatabaseWrapper, self).__init__(**kwargs) self.server_version = None - self.options = kwargs def _valid_connection(self): if self.connection is not None: @@ -87,8 +162,7 @@ class DatabaseWrapper(local): self.connection = None return False - def cursor(self): - from django.conf import settings + def _cursor(self, settings): if not self._valid_connection(): kwargs = { # Note: use_unicode intentonally not set to work around some @@ -119,25 +193,16 @@ class DatabaseWrapper(local): self.connection.set_character_set('utf8') else: cursor = self.connection.cursor() - if settings.DEBUG: - return util.CursorDebugWrapper(MysqlDebugWrapper(cursor), self) return cursor - def _commit(self): - if self.connection is not None: - self.connection.commit() + def make_debug_cursor(self, cursor): + return BaseDatabaseWrapper.make_debug_cursor(self, MysqlDebugWrapper(cursor)) def _rollback(self): - if self.connection is not None: - try: - self.connection.rollback() - except Database.NotSupportedError: - pass - - def close(self): - if self.connection is not None: - self.connection.close() - self.connection = None + try: + BaseDatabaseWrapper._rollback(self) + except Database.NotSupportedError: + pass def get_server_version(self): if not self.server_version: @@ -148,128 +213,3 @@ class DatabaseWrapper(local): raise Exception('Unable to determine MySQL version from version string %r' % self.connection.get_server_info()) self.server_version = tuple([int(x) for x in m.groups()]) return self.server_version - -allows_group_by_ordinal = True -allows_unique_and_pk = True -autoindexes_primary_keys = False -needs_datetime_string_cast = True # MySQLdb requires a typecast for dates -needs_upper_for_iops = False -supports_constraints = True -supports_tablespaces = False -uses_case_insensitive_names = False - -def quote_name(name): - if name.startswith("`") and name.endswith("`"): - return name # Quoting once is enough. - return "`%s`" % name - -dictfetchone = util.dictfetchone -dictfetchmany = util.dictfetchmany -dictfetchall = util.dictfetchall - -def get_last_insert_id(cursor, table_name, pk_name): - return cursor.lastrowid - -def get_date_extract_sql(lookup_type, table_name): - # lookup_type is 'year', 'month', 'day' - # http://dev.mysql.com/doc/mysql/en/date-and-time-functions.html - return "EXTRACT(%s FROM %s)" % (lookup_type.upper(), table_name) - -def get_date_trunc_sql(lookup_type, field_name): - # lookup_type is 'year', 'month', 'day' - fields = ['year', 'month', 'day', 'hour', 'minute', 'second'] - format = ('%%Y-', '%%m', '-%%d', ' %%H:', '%%i', ':%%s') # Use double percents to escape. - format_def = ('0000-', '01', '-01', ' 00:', '00', ':00') - try: - i = fields.index(lookup_type) + 1 - except ValueError: - sql = field_name - else: - format_str = ''.join([f for f in format[:i]] + [f for f in format_def[i:]]) - sql = "CAST(DATE_FORMAT(%s, '%s') AS DATETIME)" % (field_name, format_str) - return sql - -def get_datetime_cast_sql(): - return None - -def get_limit_offset_sql(limit, offset=None): - sql = "LIMIT " - if offset and offset != 0: - sql += "%s," % offset - return sql + str(limit) - -def get_random_function_sql(): - return "RAND()" - -def get_deferrable_sql(): - return "" - -def get_fulltext_search_sql(field_name): - return 'MATCH (%s) AGAINST (%%s IN BOOLEAN MODE)' % field_name - -def get_drop_foreignkey_sql(): - return "DROP FOREIGN KEY" - -def get_pk_default_value(): - return "DEFAULT" - -def get_max_name_length(): - return None; - -def get_start_transaction_sql(): - return "BEGIN;" - -def get_autoinc_sql(table): - return None - -def get_sql_flush(style, tables, sequences): - """Return a list of SQL statements required to remove all data from - all tables in the database (without actually removing the tables - themselves) and put the database in an empty 'initial' state - - """ - # NB: The generated SQL below is specific to MySQL - # 'TRUNCATE x;', 'TRUNCATE y;', 'TRUNCATE z;'... style SQL statements - # to clear all tables of all data - if tables: - sql = ['SET FOREIGN_KEY_CHECKS = 0;'] + \ - ['%s %s;' % \ - (style.SQL_KEYWORD('TRUNCATE'), - style.SQL_FIELD(quote_name(table)) - ) for table in tables] + \ - ['SET FOREIGN_KEY_CHECKS = 1;'] - - # 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements - # to reset sequence indices - sql.extend(["%s %s %s %s %s;" % \ - (style.SQL_KEYWORD('ALTER'), - style.SQL_KEYWORD('TABLE'), - style.SQL_TABLE(quote_name(sequence['table'])), - style.SQL_KEYWORD('AUTO_INCREMENT'), - style.SQL_FIELD('= 1'), - ) for sequence in sequences]) - return sql - else: - return [] - -def get_sql_sequence_reset(style, model_list): - "Returns a list of the SQL statements to reset sequences for the given models." - # No sequence reset required - return [] - -OPERATOR_MAPPING = { - 'exact': '= %s', - 'iexact': 'LIKE %s', - 'contains': 'LIKE BINARY %s', - 'icontains': 'LIKE %s', - 'regex': 'REGEXP BINARY %s', - 'iregex': 'REGEXP %s', - 'gt': '> %s', - 'gte': '>= %s', - 'lt': '< %s', - 'lte': '<= %s', - 'startswith': 'LIKE BINARY %s', - 'endswith': 'LIKE BINARY %s', - 'istartswith': 'LIKE %s', - 'iendswith': 'LIKE %s', -} diff --git a/django/db/backends/mysql_old/introspection.py b/django/db/backends/mysql_old/introspection.py index cb5b8320d9..b910774b95 100644 --- a/django/db/backends/mysql_old/introspection.py +++ b/django/db/backends/mysql_old/introspection.py @@ -1,8 +1,9 @@ -from django.db.backends.mysql_old.base import quote_name +from django.db.backends.mysql_old.base import DatabaseOperations from MySQLdb import ProgrammingError, OperationalError from MySQLdb.constants import FIELD_TYPE import re +quote_name = DatabaseOperations().quote_name foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)` \(`([^`]*)`\)") def get_table_list(cursor): diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index f442e3ecdd..37cfd85282 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -4,8 +4,7 @@ Oracle database backend for Django. Requires cx_Oracle: http://www.python.net/crew/atuining/cx_Oracle/ """ -from django.conf import settings -from django.db.backends import util +from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util from django.utils.datastructures import SortedDict from django.utils.encoding import smart_str, force_unicode import datetime @@ -17,29 +16,392 @@ try: import cx_Oracle as Database except ImportError, e: from django.core.exceptions import ImproperlyConfigured - raise ImproperlyConfigured, "Error loading cx_Oracle module: %s" % e - + raise ImproperlyConfigured("Error loading cx_Oracle module: %s" % e) DatabaseError = Database.Error IntegrityError = Database.IntegrityError -try: - # Only exists in Python 2.4+ - from threading import local -except ImportError: - # Import copy of _thread_local.py from Python 2.4 - from django.utils._threading_local import local +class DatabaseFeatures(BaseDatabaseFeatures): + allows_group_by_ordinal = False + allows_unique_and_pk = False # Suppress UNIQUE/PK for Oracle (ORA-02259) + needs_datetime_string_cast = False + needs_upper_for_iops = True + supports_tablespaces = True + uses_case_insensitive_names = True + uses_custom_queryset = True -class DatabaseWrapper(local): - def __init__(self, **kwargs): - self.connection = None - self.queries = [] - self.options = kwargs +class DatabaseOperations(BaseDatabaseOperations): + def autoinc_sql(self, table): + # To simulate auto-incrementing primary keys in Oracle, we have to + # create a sequence and a trigger. + sq_name = get_sequence_name(table) + tr_name = get_trigger_name(table) + sequence_sql = 'CREATE SEQUENCE %s;' % sq_name + trigger_sql = """ + CREATE OR REPLACE TRIGGER %s + BEFORE INSERT ON %s + FOR EACH ROW + WHEN (new.id IS NULL) + BEGIN + SELECT %s.nextval INTO :new.id FROM dual; + END;/""" % (tr_name, self.quote_name(table), sq_name) + return sequence_sql, trigger_sql + + def date_extract_sql(self, lookup_type, field_name): + # http://download-east.oracle.com/docs/cd/B10501_01/server.920/a96540/functions42a.htm#1017163 + return "EXTRACT(%s FROM %s)" % (lookup_type, field_name) + + def date_trunc_sql(self, lookup_type, field_name): + # Oracle uses TRUNC() for both dates and numbers. + # http://download-east.oracle.com/docs/cd/B10501_01/server.920/a96540/functions155a.htm#SQLRF06151 + if lookup_type == 'day': + sql = 'TRUNC(%s)' % field_name + else: + sql = "TRUNC(%s, '%s')" % (field_name, lookup_type) + return sql + + def datetime_cast_sql(self): + return "TO_TIMESTAMP(%s, 'YYYY-MM-DD HH24:MI:SS.FF')" + + def deferrable_sql(self): + return " DEFERRABLE INITIALLY DEFERRED" + + def drop_sequence_sql(self, table): + return "DROP SEQUENCE %s;" % self.quote_name(get_sequence_name(table)) + + def field_cast_sql(self, db_type): + if db_type.endswith('LOB'): + return "DBMS_LOB.SUBSTR(%s)" + else: + return "%s" + + def last_insert_id(self, cursor, table_name, pk_name): + sq_name = util.truncate_name(table_name, self.max_name_length() - 3) + cursor.execute('SELECT %s_sq.currval FROM dual' % sq_name) + return cursor.fetchone()[0] + + def limit_offset_sql(self, limit, offset=None): + # Limits and offset are too complicated to be handled here. + # Instead, they are handled in django/db/backends/oracle/query.py. + return "" + + def max_name_length(self): + return 30 + + def query_set_class(self, DefaultQuerySet): + from django.db import connection + from django.db.models.query import EmptyResultSet, GET_ITERATOR_CHUNK_SIZE, quote_only_if_word + + class OracleQuerySet(DefaultQuerySet): + + def iterator(self): + "Performs the SELECT database lookup of this QuerySet." + + from django.db.models.query import get_cached_row + + # self._select is a dictionary, and dictionaries' key order is + # undefined, so we convert it to a list of tuples. + extra_select = self._select.items() + + full_query = None + + try: + try: + select, sql, params, full_query = self._get_sql_clause(get_full_query=True) + except TypeError: + select, sql, params = self._get_sql_clause() + except EmptyResultSet: + raise StopIteration + if not full_query: + full_query = "SELECT %s%s\n%s" % ((self._distinct and "DISTINCT " or ""), ', '.join(select), sql) + + cursor = connection.cursor() + cursor.execute(full_query, params) + + fill_cache = self._select_related + fields = self.model._meta.fields + index_end = len(fields) + + # so here's the logic; + # 1. retrieve each row in turn + # 2. convert NCLOBs + + while 1: + rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE) + if not rows: + raise StopIteration + for row in rows: + row = self.resolve_columns(row, fields) + if fill_cache: + obj, index_end = get_cached_row(klass=self.model, row=row, + index_start=0, max_depth=self._max_related_depth) + else: + obj = self.model(*row[:index_end]) + for i, k in enumerate(extra_select): + setattr(obj, k[0], row[index_end+i]) + yield obj + + + def _get_sql_clause(self, get_full_query=False): + from django.db.models.query import fill_table_cache, \ + handle_legacy_orderlist, orderfield2column + + opts = self.model._meta + qn = connection.ops.quote_name + + # Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z. + select = ["%s.%s" % (qn(opts.db_table), qn(f.column)) for f in opts.fields] + tables = [quote_only_if_word(t) for t in self._tables] + joins = SortedDict() + where = self._where[:] + params = self._params[:] + + # Convert self._filters into SQL. + joins2, where2, params2 = self._filters.get_sql(opts) + joins.update(joins2) + where.extend(where2) + params.extend(params2) + + # Add additional tables and WHERE clauses based on select_related. + if self._select_related: + fill_table_cache(opts, select, tables, where, opts.db_table, [opts.db_table]) + + # Add any additional SELECTs. + if self._select: + select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), qn(s[0])) for s in self._select.items()]) + + # Start composing the body of the SQL statement. + sql = [" FROM", qn(opts.db_table)] + + # Compose the join dictionary into SQL describing the joins. + if joins: + sql.append(" ".join(["%s %s %s ON %s" % (join_type, table, alias, condition) + for (alias, (table, join_type, condition)) in joins.items()])) + + # Compose the tables clause into SQL. + if tables: + sql.append(", " + ", ".join(tables)) + + # Compose the where clause into SQL. + if where: + sql.append(where and "WHERE " + " AND ".join(where)) + + # ORDER BY clause + order_by = [] + if self._order_by is not None: + ordering_to_use = self._order_by + else: + ordering_to_use = opts.ordering + for f in handle_legacy_orderlist(ordering_to_use): + if f == '?': # Special case. + order_by.append(DatabaseOperations().random_function_sql()) + else: + if f.startswith('-'): + col_name = f[1:] + order = "DESC" + else: + col_name = f + order = "ASC" + if "." in col_name: + table_prefix, col_name = col_name.split('.', 1) + table_prefix = qn(table_prefix) + '.' + else: + # Use the database table as a column prefix if it wasn't given, + # and if the requested column isn't a custom SELECT. + if "." not in col_name and col_name not in (self._select or ()): + table_prefix = qn(opts.db_table) + '.' + else: + table_prefix = '' + order_by.append('%s%s %s' % (table_prefix, qn(orderfield2column(col_name, opts)), order)) + if order_by: + sql.append("ORDER BY " + ", ".join(order_by)) + + # Look for column name collisions in the select elements + # and fix them with an AS alias. This allows us to do a + # SELECT * later in the paging query. + cols = [clause.split('.')[-1] for clause in select] + for index, col in enumerate(cols): + if cols.count(col) > 1: + col = '%s%d' % (col.replace('"', ''), index) + cols[index] = col + select[index] = '%s AS %s' % (select[index], col) + + # LIMIT and OFFSET clauses + # To support limits and offsets, Oracle requires some funky rewriting of an otherwise normal looking query. + select_clause = ",".join(select) + distinct = (self._distinct and "DISTINCT " or "") + + if order_by: + order_by_clause = " OVER (ORDER BY %s )" % (", ".join(order_by)) + else: + #Oracle's row_number() function always requires an order-by clause. + #So we need to define a default order-by, since none was provided. + order_by_clause = " OVER (ORDER BY %s.%s)" % \ + (qn(opts.db_table), qn(opts.fields[0].db_column or opts.fields[0].column)) + # limit_and_offset_clause + if self._limit is None: + assert self._offset is None, "'offset' is not allowed without 'limit'" + + if self._offset is not None: + offset = int(self._offset) + else: + offset = 0 + if self._limit is not None: + limit = int(self._limit) + else: + limit = None + + limit_and_offset_clause = '' + if limit is not None: + limit_and_offset_clause = "WHERE rn > %s AND rn <= %s" % (offset, limit+offset) + elif offset: + limit_and_offset_clause = "WHERE rn > %s" % (offset) + + if len(limit_and_offset_clause) > 0: + fmt = \ + """SELECT * FROM + (SELECT %s%s, + ROW_NUMBER()%s AS rn + %s) + %s""" + full_query = fmt % (distinct, select_clause, + order_by_clause, ' '.join(sql).strip(), + limit_and_offset_clause) + else: + full_query = None + + if get_full_query: + return select, " ".join(sql), params, full_query + else: + return select, " ".join(sql), params + + def resolve_columns(self, row, fields=()): + from django.db.models.fields import DateField, DateTimeField, \ + TimeField, BooleanField, NullBooleanField, DecimalField, Field + values = [] + for value, field in map(None, row, fields): + if isinstance(value, Database.LOB): + value = value.read() + # Oracle stores empty strings as null. We need to undo this in + # order to adhere to the Django convention of using the empty + # string instead of null, but only if the field accepts the + # empty string. + if value is None and isinstance(field, Field) and field.empty_strings_allowed: + value = '' + # Convert 1 or 0 to True or False + elif value in (1, 0) and isinstance(field, (BooleanField, NullBooleanField)): + value = bool(value) + # Convert floats to decimals + elif value is not None and isinstance(field, DecimalField): + value = util.typecast_decimal(field.format_number(value)) + # cx_Oracle always returns datetime.datetime objects for + # DATE and TIMESTAMP columns, but Django wants to see a + # python datetime.date, .time, or .datetime. We use the type + # of the Field to determine which to cast to, but it's not + # always available. + # As a workaround, we cast to date if all the time-related + # values are 0, or to time if the date is 1/1/1900. + # This could be cleaned a bit by adding a method to the Field + # classes to normalize values from the database (the to_python + # method is used for validation and isn't what we want here). + elif isinstance(value, Database.Timestamp): + # In Python 2.3, the cx_Oracle driver returns its own + # Timestamp object that we must convert to a datetime class. + if not isinstance(value, datetime.datetime): + value = datetime.datetime(value.year, value.month, value.day, value.hour, + value.minute, value.second, value.fsecond) + if isinstance(field, DateTimeField): + pass # DateTimeField subclasses DateField so must be checked first. + elif isinstance(field, DateField): + value = value.date() + elif isinstance(field, TimeField) or (value.year == 1900 and value.month == value.day == 1): + value = value.time() + elif value.hour == value.minute == value.second == value.microsecond == 0: + value = value.date() + values.append(value) + return values + + return OracleQuerySet + + def quote_name(self, name): + # SQL92 requires delimited (quoted) names to be case-sensitive. When + # not quoted, Oracle has case-insensitive behavior for identifiers, but + # always defaults to uppercase. + # We simplify things by making Oracle identifiers always uppercase. + if not name.startswith('"') and not name.endswith('"'): + name = '"%s"' % util.truncate_name(name.upper(), self.max_name_length()) + return name.upper() + + def random_function_sql(self): + return "DBMS_RANDOM.RANDOM" + + def sql_flush(self, style, tables, sequences): + # Return a list of 'TRUNCATE x;', 'TRUNCATE y;', + # 'TRUNCATE z;'... style SQL statements + if tables: + # Oracle does support TRUNCATE, but it seems to get us into + # FK referential trouble, whereas DELETE FROM table works. + sql = ['%s %s %s;' % \ + (style.SQL_KEYWORD('DELETE'), + style.SQL_KEYWORD('FROM'), + style.SQL_FIELD(self.quote_name(table)) + ) for table in tables] + # Since we've just deleted all the rows, running our sequence + # ALTER code will reset the sequence to 0. + for sequence_info in sequences: + table_name = sequence_info['table'] + seq_name = get_sequence_name(table_name) + query = _get_sequence_reset_sql() % {'sequence': seq_name, 'table': self.quote_name(table_name)} + sql.append(query) + return sql + else: + return [] + + def sequence_reset_sql(self, style, model_list): + from django.db import models + output = [] + query = _get_sequence_reset_sql() + for model in model_list: + for f in model._meta.fields: + if isinstance(f, models.AutoField): + sequence_name = get_sequence_name(model._meta.db_table) + output.append(query % {'sequence':sequence_name, + 'table':model._meta.db_table}) + break # Only one AutoField is allowed per model, so don't bother continuing. + for f in model._meta.many_to_many: + sequence_name = get_sequence_name(f.m2m_db_table()) + output.append(query % {'sequence':sequence_name, + 'table':f.m2m_db_table()}) + return output + + def start_transaction_sql(self): + return '' + + def tablespace_sql(self, tablespace, inline=False): + return "%sTABLESPACE %s" % ((inline and "USING INDEX " or ""), self.quote_name(tablespace)) + +class DatabaseWrapper(BaseDatabaseWrapper): + features = DatabaseFeatures() + ops = DatabaseOperations() + operators = { + 'exact': '= %s', + 'iexact': '= UPPER(%s)', + 'contains': "LIKE %s ESCAPE '\\'", + 'icontains': "LIKE UPPER(%s) ESCAPE '\\'", + 'gt': '> %s', + 'gte': '>= %s', + 'lt': '< %s', + 'lte': '<= %s', + 'startswith': "LIKE %s ESCAPE '\\'", + 'endswith': "LIKE %s ESCAPE '\\'", + 'istartswith': "LIKE UPPER(%s) ESCAPE '\\'", + 'iendswith': "LIKE UPPER(%s) ESCAPE '\\'", + } def _valid_connection(self): return self.connection is not None - def cursor(self): + def _cursor(self, settings): if not self._valid_connection(): if len(settings.DATABASE_HOST.strip()) == 0: settings.DATABASE_HOST = 'localhost' @@ -55,32 +417,8 @@ class DatabaseWrapper(local): # Set oracle date to ansi date format. cursor.execute("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD'") cursor.execute("ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS.FF'") - if settings.DEBUG: - return util.CursorDebugWrapper(cursor, self) return cursor - def _commit(self): - if self.connection is not None: - return self.connection.commit() - - def _rollback(self): - if self.connection is not None: - return self.connection.rollback() - - def close(self): - if self.connection is not None: - self.connection.close() - self.connection = None - -allows_group_by_ordinal = False -allows_unique_and_pk = False # Suppress UNIQUE/PK for Oracle (ORA-02259) -autoindexes_primary_keys = True -needs_datetime_string_cast = False -needs_upper_for_iops = True -supports_constraints = True -supports_tablespaces = True -uses_case_insensitive_names = True - class FormatStylePlaceholderCursor(Database.Cursor): """ Django uses "format" (e.g. '%s') style placeholders, but Oracle uses ":var" @@ -145,90 +483,6 @@ def to_unicode(s): return force_unicode(s) return s -def quote_name(name): - # SQL92 requires delimited (quoted) names to be case-sensitive. When - # not quoted, Oracle has case-insensitive behavior for identifiers, but - # always defaults to uppercase. - # We simplify things by making Oracle identifiers always uppercase. - if not name.startswith('"') and not name.endswith('"'): - name = '"%s"' % util.truncate_name(name.upper(), get_max_name_length()) - return name.upper() - -dictfetchone = util.dictfetchone -dictfetchmany = util.dictfetchmany -dictfetchall = util.dictfetchall - -def get_last_insert_id(cursor, table_name, pk_name): - sq_name = util.truncate_name(table_name, get_max_name_length()-3) - cursor.execute('SELECT %s_sq.currval FROM dual' % sq_name) - return cursor.fetchone()[0] - -def get_date_extract_sql(lookup_type, table_name): - # lookup_type is 'year', 'month', 'day' - # http://download-east.oracle.com/docs/cd/B10501_01/server.920/a96540/functions42a.htm#1017163 - return "EXTRACT(%s FROM %s)" % (lookup_type, table_name) - -def get_date_trunc_sql(lookup_type, field_name): - # lookup_type is 'year', 'month', 'day' - # Oracle uses TRUNC() for both dates and numbers. - # http://download-east.oracle.com/docs/cd/B10501_01/server.920/a96540/functions155a.htm#SQLRF06151 - if lookup_type == 'day': - sql = 'TRUNC(%s)' % (field_name,) - else: - sql = "TRUNC(%s, '%s')" % (field_name, lookup_type) - return sql - -def get_datetime_cast_sql(): - return "TO_TIMESTAMP(%s, 'YYYY-MM-DD HH24:MI:SS.FF')" - -def get_limit_offset_sql(limit, offset=None): - # Limits and offset are too complicated to be handled here. - # Instead, they are handled in django/db/backends/oracle/query.py. - return "" - -def get_random_function_sql(): - return "DBMS_RANDOM.RANDOM" - -def get_deferrable_sql(): - return " DEFERRABLE INITIALLY DEFERRED" - -def get_fulltext_search_sql(field_name): - raise NotImplementedError - -def get_drop_foreignkey_sql(): - return "DROP CONSTRAINT" - -def get_pk_default_value(): - return "DEFAULT" - -def get_max_name_length(): - return 30 - -def get_start_transaction_sql(): - return None - -def get_tablespace_sql(tablespace, inline=False): - return "%sTABLESPACE %s" % ((inline and "USING INDEX " or ""), quote_name(tablespace)) - -def get_autoinc_sql(table): - # To simulate auto-incrementing primary keys in Oracle, we have to - # create a sequence and a trigger. - sq_name = get_sequence_name(table) - tr_name = get_trigger_name(table) - sequence_sql = 'CREATE SEQUENCE %s;' % sq_name - trigger_sql = """CREATE OR REPLACE TRIGGER %s - BEFORE INSERT ON %s - FOR EACH ROW - WHEN (new.id IS NULL) - BEGIN - SELECT %s.nextval INTO :new.id FROM dual; - END; - /""" % (tr_name, quote_name(table), sq_name) - return sequence_sql, trigger_sql - -def get_drop_sequence(table): - return "DROP SEQUENCE %s;" % quote_name(get_sequence_name(table)) - def _get_sequence_reset_sql(): # TODO: colorize this SQL code with style.SQL_KEYWORD(), etc. return """ @@ -249,310 +503,10 @@ def _get_sequence_reset_sql(): END; /""" -def get_sql_flush(style, tables, sequences): - """Return a list of SQL statements required to remove all data from - all tables in the database (without actually removing the tables - themselves) and put the database in an empty 'initial' state - """ - # Return a list of 'TRUNCATE x;', 'TRUNCATE y;', - # 'TRUNCATE z;'... style SQL statements - if tables: - # Oracle does support TRUNCATE, but it seems to get us into - # FK referential trouble, whereas DELETE FROM table works. - sql = ['%s %s %s;' % \ - (style.SQL_KEYWORD('DELETE'), - style.SQL_KEYWORD('FROM'), - style.SQL_FIELD(quote_name(table)) - ) for table in tables] - # Since we've just deleted all the rows, running our sequence - # ALTER code will reset the sequence to 0. - for sequence_info in sequences: - table_name = sequence_info['table'] - seq_name = get_sequence_name(table_name) - query = _get_sequence_reset_sql() % {'sequence':seq_name, - 'table':quote_name(table_name)} - sql.append(query) - return sql - else: - return [] - def get_sequence_name(table): - name_length = get_max_name_length() - 3 + name_length = DatabaseOperations().max_name_length() - 3 return '%s_SQ' % util.truncate_name(table, name_length).upper() -def get_sql_sequence_reset(style, model_list): - "Returns a list of the SQL statements to reset sequences for the given models." - from django.db import models - output = [] - query = _get_sequence_reset_sql() - for model in model_list: - for f in model._meta.fields: - if isinstance(f, models.AutoField): - sequence_name = get_sequence_name(model._meta.db_table) - output.append(query % {'sequence':sequence_name, - 'table':model._meta.db_table}) - break # Only one AutoField is allowed per model, so don't bother continuing. - for f in model._meta.many_to_many: - sequence_name = get_sequence_name(f.m2m_db_table()) - output.append(query % {'sequence':sequence_name, - 'table':f.m2m_db_table()}) - return output - def get_trigger_name(table): - name_length = get_max_name_length() - 3 + name_length = DatabaseOperations().max_name_length() - 3 return '%s_TR' % util.truncate_name(table, name_length).upper() - -def get_query_set_class(DefaultQuerySet): - "Create a custom QuerySet class for Oracle." - - from django.db import backend, connection - from django.db.models.query import EmptyResultSet, GET_ITERATOR_CHUNK_SIZE, quote_only_if_word - - class OracleQuerySet(DefaultQuerySet): - - def iterator(self): - "Performs the SELECT database lookup of this QuerySet." - - from django.db.models.query import get_cached_row - - # self._select is a dictionary, and dictionaries' key order is - # undefined, so we convert it to a list of tuples. - extra_select = self._select.items() - - full_query = None - - try: - try: - select, sql, params, full_query = self._get_sql_clause(get_full_query=True) - except TypeError: - select, sql, params = self._get_sql_clause() - except EmptyResultSet: - raise StopIteration - if not full_query: - full_query = "SELECT %s%s\n%s" % \ - ((self._distinct and "DISTINCT " or ""), - ', '.join(select), sql) - - cursor = connection.cursor() - cursor.execute(full_query, params) - - fill_cache = self._select_related - fields = self.model._meta.fields - index_end = len(fields) - - # so here's the logic; - # 1. retrieve each row in turn - # 2. convert NCLOBs - - while 1: - rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE) - if not rows: - raise StopIteration - for row in rows: - row = self.resolve_columns(row, fields) - if fill_cache: - obj, index_end = get_cached_row(klass=self.model, row=row, - index_start=0, max_depth=self._max_related_depth) - else: - obj = self.model(*row[:index_end]) - for i, k in enumerate(extra_select): - setattr(obj, k[0], row[index_end+i]) - yield obj - - - def _get_sql_clause(self, get_full_query=False): - from django.db.models.query import fill_table_cache, \ - handle_legacy_orderlist, orderfield2column - - opts = self.model._meta - - # Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z. - select = ["%s.%s" % (backend.quote_name(opts.db_table), backend.quote_name(f.column)) for f in opts.fields] - tables = [quote_only_if_word(t) for t in self._tables] - joins = SortedDict() - where = self._where[:] - params = self._params[:] - - # Convert self._filters into SQL. - joins2, where2, params2 = self._filters.get_sql(opts) - joins.update(joins2) - where.extend(where2) - params.extend(params2) - - # Add additional tables and WHERE clauses based on select_related. - if self._select_related: - fill_table_cache(opts, select, tables, where, opts.db_table, [opts.db_table]) - - # Add any additional SELECTs. - if self._select: - select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), backend.quote_name(s[0])) for s in self._select.items()]) - - # Start composing the body of the SQL statement. - sql = [" FROM", backend.quote_name(opts.db_table)] - - # Compose the join dictionary into SQL describing the joins. - if joins: - sql.append(" ".join(["%s %s %s ON %s" % (join_type, table, alias, condition) - for (alias, (table, join_type, condition)) in joins.items()])) - - # Compose the tables clause into SQL. - if tables: - sql.append(", " + ", ".join(tables)) - - # Compose the where clause into SQL. - if where: - sql.append(where and "WHERE " + " AND ".join(where)) - - # ORDER BY clause - order_by = [] - if self._order_by is not None: - ordering_to_use = self._order_by - else: - ordering_to_use = opts.ordering - for f in handle_legacy_orderlist(ordering_to_use): - if f == '?': # Special case. - order_by.append(backend.get_random_function_sql()) - else: - if f.startswith('-'): - col_name = f[1:] - order = "DESC" - else: - col_name = f - order = "ASC" - if "." in col_name: - table_prefix, col_name = col_name.split('.', 1) - table_prefix = backend.quote_name(table_prefix) + '.' - else: - # Use the database table as a column prefix if it wasn't given, - # and if the requested column isn't a custom SELECT. - if "." not in col_name and col_name not in (self._select or ()): - table_prefix = backend.quote_name(opts.db_table) + '.' - else: - table_prefix = '' - order_by.append('%s%s %s' % (table_prefix, backend.quote_name(orderfield2column(col_name, opts)), order)) - if order_by: - sql.append("ORDER BY " + ", ".join(order_by)) - - # Look for column name collisions in the select elements - # and fix them with an AS alias. This allows us to do a - # SELECT * later in the paging query. - cols = [clause.split('.')[-1] for clause in select] - for index, col in enumerate(cols): - if cols.count(col) > 1: - col = '%s%d' % (col.replace('"', ''), index) - cols[index] = col - select[index] = '%s AS %s' % (select[index], col) - - # LIMIT and OFFSET clauses - # To support limits and offsets, Oracle requires some funky rewriting of an otherwise normal looking query. - select_clause = ",".join(select) - distinct = (self._distinct and "DISTINCT " or "") - - if order_by: - order_by_clause = " OVER (ORDER BY %s )" % (", ".join(order_by)) - else: - #Oracle's row_number() function always requires an order-by clause. - #So we need to define a default order-by, since none was provided. - order_by_clause = " OVER (ORDER BY %s.%s)" % \ - (backend.quote_name(opts.db_table), - backend.quote_name(opts.fields[0].db_column or opts.fields[0].column)) - # limit_and_offset_clause - if self._limit is None: - assert self._offset is None, "'offset' is not allowed without 'limit'" - - if self._offset is not None: - offset = int(self._offset) - else: - offset = 0 - if self._limit is not None: - limit = int(self._limit) - else: - limit = None - - limit_and_offset_clause = '' - if limit is not None: - limit_and_offset_clause = "WHERE rn > %s AND rn <= %s" % (offset, limit+offset) - elif offset: - limit_and_offset_clause = "WHERE rn > %s" % (offset) - - if len(limit_and_offset_clause) > 0: - fmt = \ -"""SELECT * FROM - (SELECT %s%s, - ROW_NUMBER()%s AS rn - %s) -%s""" - full_query = fmt % (distinct, select_clause, - order_by_clause, ' '.join(sql).strip(), - limit_and_offset_clause) - else: - full_query = None - - if get_full_query: - return select, " ".join(sql), params, full_query - else: - return select, " ".join(sql), params - - def resolve_columns(self, row, fields=()): - from django.db.models.fields import DateField, DateTimeField, \ - TimeField, BooleanField, NullBooleanField, DecimalField, Field - values = [] - for value, field in map(None, row, fields): - if isinstance(value, Database.LOB): - value = value.read() - # Oracle stores empty strings as null. We need to undo this in - # order to adhere to the Django convention of using the empty - # string instead of null, but only if the field accepts the - # empty string. - if value is None and isinstance(field, Field) and field.empty_strings_allowed: - value = '' - # Convert 1 or 0 to True or False - elif value in (1, 0) and isinstance(field, (BooleanField, NullBooleanField)): - value = bool(value) - # Convert floats to decimals - elif value is not None and isinstance(field, DecimalField): - value = util.typecast_decimal(field.format_number(value)) - # cx_Oracle always returns datetime.datetime objects for - # DATE and TIMESTAMP columns, but Django wants to see a - # python datetime.date, .time, or .datetime. We use the type - # of the Field to determine which to cast to, but it's not - # always available. - # As a workaround, we cast to date if all the time-related - # values are 0, or to time if the date is 1/1/1900. - # This could be cleaned a bit by adding a method to the Field - # classes to normalize values from the database (the to_python - # method is used for validation and isn't what we want here). - elif isinstance(value, Database.Timestamp): - # In Python 2.3, the cx_Oracle driver returns its own - # Timestamp object that we must convert to a datetime class. - if not isinstance(value, datetime.datetime): - value = datetime.datetime(value.year, value.month, value.day, value.hour, - value.minute, value.second, value.fsecond) - if isinstance(field, DateTimeField): - pass # DateTimeField subclasses DateField so must be checked first. - elif isinstance(field, DateField): - value = value.date() - elif isinstance(field, TimeField) or (value.year == 1900 and value.month == value.day == 1): - value = value.time() - elif value.hour == value.minute == value.second == value.microsecond == 0: - value = value.date() - values.append(value) - return values - - return OracleQuerySet - - -OPERATOR_MAPPING = { - 'exact': '= %s', - 'iexact': '= UPPER(%s)', - 'contains': "LIKE %s ESCAPE '\\'", - 'icontains': "LIKE UPPER(%s) ESCAPE '\\'", - 'gt': '> %s', - 'gte': '>= %s', - 'lt': '< %s', - 'lte': '<= %s', - 'startswith': "LIKE %s ESCAPE '\\'", - 'endswith': "LIKE %s ESCAPE '\\'", - 'istartswith': "LIKE UPPER(%s) ESCAPE '\\'", - 'iendswith': "LIKE UPPER(%s) ESCAPE '\\'", -} diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py index b1b32dd23e..d080b5d283 100644 --- a/django/db/backends/oracle/creation.py +++ b/django/db/backends/oracle/creation.py @@ -36,9 +36,7 @@ TEST_DATABASE_PREFIX = 'test_' PASSWORD = 'Im_a_lumberjack' REMEMBER = {} - -def create_test_db(settings, connection, backend, verbosity=1, autoclobber=False): - +def create_test_db(settings, connection, verbosity=1, autoclobber=False): TEST_DATABASE_NAME = _test_database_name(settings) TEST_DATABASE_USER = _test_database_user(settings) TEST_DATABASE_PASSWD = _test_database_passwd(settings) @@ -115,8 +113,7 @@ def create_test_db(settings, connection, backend, verbosity=1, autoclobber=False # the side effect of initializing the test database. cursor = connection.cursor() - -def destroy_test_db(settings, connection, backend, old_database_name, verbosity=1): +def destroy_test_db(settings, connection, old_database_name, verbosity=1): connection.close() TEST_DATABASE_NAME = _test_database_name(settings) @@ -152,7 +149,6 @@ def destroy_test_db(settings, connection, backend, old_database_name, verbosity= _destroy_test_db(cursor, parameters, verbosity) connection.close() - def _create_test_db(cursor, parameters, verbosity): if verbosity >= 2: print "_create_test_db(): dbname = %s" % parameters['dbname'] @@ -168,7 +164,6 @@ def _create_test_db(cursor, parameters, verbosity): ] _execute_statements(cursor, statements, parameters, verbosity) - def _create_test_user(cursor, parameters, verbosity): if verbosity >= 2: print "_create_test_user(): username = %s" % parameters['user'] @@ -182,7 +177,6 @@ def _create_test_user(cursor, parameters, verbosity): ] _execute_statements(cursor, statements, parameters, verbosity) - def _destroy_test_db(cursor, parameters, verbosity): if verbosity >= 2: print "_destroy_test_db(): dbname=%s" % parameters['dbname'] @@ -192,7 +186,6 @@ def _destroy_test_db(cursor, parameters, verbosity): ] _execute_statements(cursor, statements, parameters, verbosity) - def _destroy_test_user(cursor, parameters, verbosity): if verbosity >= 2: print "_destroy_test_user(): user=%s" % parameters['user'] @@ -202,7 +195,6 @@ def _destroy_test_user(cursor, parameters, verbosity): ] _execute_statements(cursor, statements, parameters, verbosity) - def _execute_statements(cursor, statements, parameters, verbosity): for template in statements: stmt = template % parameters @@ -214,7 +206,6 @@ def _execute_statements(cursor, statements, parameters, verbosity): sys.stderr.write("Failed (%s)\n" % (err)) raise - def _test_database_name(settings): name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME try: @@ -226,7 +217,6 @@ def _test_database_name(settings): raise return name - def _test_database_create(settings): name = True try: @@ -240,7 +230,6 @@ def _test_database_create(settings): raise return name - def _test_user_create(settings): name = True try: @@ -254,7 +243,6 @@ def _test_user_create(settings): raise return name - def _test_database_user(settings): name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME try: @@ -266,7 +254,6 @@ def _test_database_user(settings): raise return name - def _test_database_passwd(settings): name = PASSWORD try: @@ -278,7 +265,6 @@ def _test_database_passwd(settings): raise return name - def _test_database_tblspace(settings): name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME try: @@ -290,7 +276,6 @@ def _test_database_tblspace(settings): raise return name - def _test_database_tblspace_tmp(settings): name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME + '_temp' try: diff --git a/django/db/backends/oracle/introspection.py b/django/db/backends/oracle/introspection.py index 44430a0029..6f800c8bb6 100644 --- a/django/db/backends/oracle/introspection.py +++ b/django/db/backends/oracle/introspection.py @@ -1,8 +1,8 @@ -from django.db.backends.oracle.base import quote_name +from django.db.backends.oracle.base import DatabaseOperations import re import cx_Oracle - +quote_name = DatabaseOperations().quote_name foreign_key_re = re.compile(r"\sCONSTRAINT `[^`]*` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)` \(`([^`]*)`\)") def get_table_list(cursor): diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py index 9d0967fa2b..ca07ae21d9 100644 --- a/django/db/backends/postgresql/base.py +++ b/django/db/backends/postgresql/base.py @@ -5,23 +5,17 @@ Requires psycopg 1: http://initd.org/projects/psycopg1 """ from django.utils.encoding import smart_str, smart_unicode -from django.db.backends import util +from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, util +from django.db.backends.postgresql.operations import DatabaseOperations try: import psycopg as Database except ImportError, e: from django.core.exceptions import ImproperlyConfigured - raise ImproperlyConfigured, "Error loading psycopg module: %s" % e + raise ImproperlyConfigured("Error loading psycopg module: %s" % e) DatabaseError = Database.DatabaseError IntegrityError = Database.IntegrityError -try: - # Only exists in Python 2.4+ - from threading import local -except ImportError: - # Import copy of _thread_local.py from Python 2.4 - from django.utils._threading_local import local - class UnicodeCursorWrapper(object): """ A thin wrapper around psycopg cursors that allows them to accept Unicode @@ -62,22 +56,36 @@ class UnicodeCursorWrapper(object): else: return getattr(self.cursor, attr) -postgres_version = None +class DatabaseFeatures(BaseDatabaseFeatures): + pass # This backend uses all the defaults. -class DatabaseWrapper(local): - def __init__(self, **kwargs): - self.connection = None - self.queries = [] - self.options = kwargs +class DatabaseWrapper(BaseDatabaseWrapper): + features = DatabaseFeatures() + ops = DatabaseOperations() + operators = { + 'exact': '= %s', + 'iexact': 'ILIKE %s', + 'contains': 'LIKE %s', + 'icontains': 'ILIKE %s', + 'regex': '~ %s', + 'iregex': '~* %s', + 'gt': '> %s', + 'gte': '>= %s', + 'lt': '< %s', + 'lte': '<= %s', + 'startswith': 'LIKE %s', + 'endswith': 'LIKE %s', + 'istartswith': 'ILIKE %s', + 'iendswith': 'ILIKE %s', + } - def cursor(self): - from django.conf import settings + def _cursor(self, settings): set_tz = False if self.connection is None: set_tz = True if settings.DATABASE_NAME == '': from django.core.exceptions import ImproperlyConfigured - raise ImproperlyConfigured, "You need to specify DATABASE_NAME in your Django settings file." + raise ImproperlyConfigured("You need to specify DATABASE_NAME in your Django settings file.") conn_string = "dbname=%s" % settings.DATABASE_NAME if settings.DATABASE_USER: conn_string = "user=%s %s" % (settings.DATABASE_USER, conn_string) @@ -94,186 +102,11 @@ class DatabaseWrapper(local): cursor.execute("SET TIME ZONE %s", [settings.TIME_ZONE]) cursor.execute("SET client_encoding to 'UNICODE'") cursor = UnicodeCursorWrapper(cursor, 'utf-8') - global postgres_version - if not postgres_version: + if self.ops.postgres_version is None: cursor.execute("SELECT version()") - postgres_version = [int(val) for val in cursor.fetchone()[0].split()[1].split('.')] - if settings.DEBUG: - return util.CursorDebugWrapper(cursor, self) + self.ops.postgres_version = [int(val) for val in cursor.fetchone()[0].split()[1].split('.')] return cursor - def _commit(self): - if self.connection is not None: - return self.connection.commit() - - def _rollback(self): - if self.connection is not None: - return self.connection.rollback() - - def close(self): - if self.connection is not None: - self.connection.close() - self.connection = None - -allows_group_by_ordinal = True -allows_unique_and_pk = True -autoindexes_primary_keys = True -needs_datetime_string_cast = True -needs_upper_for_iops = False -supports_constraints = True -supports_tablespaces = False -uses_case_insensitive_names = False - -def quote_name(name): - if name.startswith('"') and name.endswith('"'): - return name # Quoting once is enough. - return '"%s"' % name - -def dictfetchone(cursor): - "Returns a row from the cursor as a dict" - return cursor.dictfetchone() - -def dictfetchmany(cursor, number): - "Returns a certain number of rows from a cursor as a dict" - return cursor.dictfetchmany(number) - -def dictfetchall(cursor): - "Returns all rows from a cursor as a dict" - return cursor.dictfetchall() - -def get_last_insert_id(cursor, table_name, pk_name): - cursor.execute("SELECT CURRVAL('\"%s_%s_seq\"')" % (table_name, pk_name)) - return cursor.fetchone()[0] - -def get_date_extract_sql(lookup_type, table_name): - # lookup_type is 'year', 'month', 'day' - # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-EXTRACT - return "EXTRACT('%s' FROM %s)" % (lookup_type, table_name) - -def get_date_trunc_sql(lookup_type, field_name): - # lookup_type is 'year', 'month', 'day' - # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC - return "DATE_TRUNC('%s', %s)" % (lookup_type, field_name) - -def get_datetime_cast_sql(): - return None - -def get_limit_offset_sql(limit, offset=None): - sql = "LIMIT %s" % limit - if offset and offset != 0: - sql += " OFFSET %s" % offset - return sql - -def get_random_function_sql(): - return "RANDOM()" - -def get_deferrable_sql(): - return " DEFERRABLE INITIALLY DEFERRED" - -def get_fulltext_search_sql(field_name): - raise NotImplementedError - -def get_drop_foreignkey_sql(): - return "DROP CONSTRAINT" - -def get_pk_default_value(): - return "DEFAULT" - -def get_max_name_length(): - return None - -def get_start_transaction_sql(): - return "BEGIN;" - -def get_autoinc_sql(table): - return None - -def get_sql_flush(style, tables, sequences): - """Return a list of SQL statements required to remove all data from - all tables in the database (without actually removing the tables - themselves) and put the database in an empty 'initial' state - - """ - if tables: - if postgres_version[0] >= 8 and postgres_version[1] >= 1: - # Postgres 8.1+ can do 'TRUNCATE x, y, z...;'. In fact, it *has to* - # in order to be able to truncate tables referenced by a foreign - # key in any other table. The result is a single SQL TRUNCATE - # statement. - sql = ['%s %s;' % \ - (style.SQL_KEYWORD('TRUNCATE'), - style.SQL_FIELD(', '.join([quote_name(table) for table in tables])) - )] - else: - # Older versions of Postgres can't do TRUNCATE in a single call, so - # they must use a simple delete. - sql = ['%s %s %s;' % \ - (style.SQL_KEYWORD('DELETE'), - style.SQL_KEYWORD('FROM'), - style.SQL_FIELD(quote_name(table)) - ) for table in tables] - - # 'ALTER SEQUENCE sequence_name RESTART WITH 1;'... style SQL statements - # to reset sequence indices - for sequence_info in sequences: - table_name = sequence_info['table'] - column_name = sequence_info['column'] - if column_name and len(column_name)>0: - # sequence name in this case will be __seq - sql.append("%s %s %s %s %s %s;" % \ - (style.SQL_KEYWORD('ALTER'), - style.SQL_KEYWORD('SEQUENCE'), - style.SQL_FIELD(quote_name('%s_%s_seq' % (table_name, column_name))), - style.SQL_KEYWORD('RESTART'), - style.SQL_KEYWORD('WITH'), - style.SQL_FIELD('1') - ) - ) - else: - # sequence name in this case will be
        _id_seq - sql.append("%s %s %s %s %s %s;" % \ - (style.SQL_KEYWORD('ALTER'), - style.SQL_KEYWORD('SEQUENCE'), - style.SQL_FIELD(quote_name('%s_id_seq' % table_name)), - style.SQL_KEYWORD('RESTART'), - style.SQL_KEYWORD('WITH'), - style.SQL_FIELD('1') - ) - ) - return sql - else: - return [] - -def get_sql_sequence_reset(style, model_list): - "Returns a list of the SQL statements to reset sequences for the given models." - from django.db import models - output = [] - for model in model_list: - # Use `coalesce` to set the sequence for each model to the max pk value if there are records, - # or 1 if there are none. Set the `is_called` property (the third argument to `setval`) to true - # if there are records (as the max pk value is already in use), otherwise set it to false. - for f in model._meta.fields: - if isinstance(f, models.AutoField): - output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \ - (style.SQL_KEYWORD('SELECT'), - style.SQL_FIELD(quote_name('%s_%s_seq' % (model._meta.db_table, f.column))), - style.SQL_FIELD(quote_name(f.column)), - style.SQL_FIELD(quote_name(f.column)), - style.SQL_KEYWORD('IS NOT'), - style.SQL_KEYWORD('FROM'), - style.SQL_TABLE(quote_name(model._meta.db_table)))) - break # Only one AutoField is allowed per model, so don't bother continuing. - for f in model._meta.many_to_many: - output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \ - (style.SQL_KEYWORD('SELECT'), - style.SQL_FIELD(quote_name('%s_id_seq' % f.m2m_db_table())), - style.SQL_FIELD(quote_name('id')), - style.SQL_FIELD(quote_name('id')), - style.SQL_KEYWORD('IS NOT'), - style.SQL_KEYWORD('FROM'), - style.SQL_TABLE(f.m2m_db_table()))) - return output - def typecast_string(s): """ Cast all returned strings to unicode strings. @@ -288,26 +121,9 @@ def typecast_string(s): try: Database.register_type(Database.new_type((1082,), "DATE", util.typecast_date)) except AttributeError: - raise Exception, "You appear to be using psycopg version 2. Set your DATABASE_ENGINE to 'postgresql_psycopg2' instead of 'postgresql'." + raise Exception("You appear to be using psycopg version 2. Set your DATABASE_ENGINE to 'postgresql_psycopg2' instead of 'postgresql'.") Database.register_type(Database.new_type((1083,1266), "TIME", util.typecast_time)) Database.register_type(Database.new_type((1114,1184), "TIMESTAMP", util.typecast_timestamp)) Database.register_type(Database.new_type((16,), "BOOLEAN", util.typecast_boolean)) Database.register_type(Database.new_type((1700,), "NUMERIC", util.typecast_decimal)) Database.register_type(Database.new_type(Database.types[1043].values, 'STRING', typecast_string)) - -OPERATOR_MAPPING = { - 'exact': '= %s', - 'iexact': 'ILIKE %s', - 'contains': 'LIKE %s', - 'icontains': 'ILIKE %s', - 'regex': '~ %s', - 'iregex': '~* %s', - 'gt': '> %s', - 'gte': '>= %s', - 'lt': '< %s', - 'lte': '<= %s', - 'startswith': 'LIKE %s', - 'endswith': 'LIKE %s', - 'istartswith': 'ILIKE %s', - 'iendswith': 'ILIKE %s', -} diff --git a/django/db/backends/postgresql/introspection.py b/django/db/backends/postgresql/introspection.py index 2605490afd..982c004569 100644 --- a/django/db/backends/postgresql/introspection.py +++ b/django/db/backends/postgresql/introspection.py @@ -1,4 +1,6 @@ -from django.db.backends.postgresql.base import quote_name +from django.db.backends.postgresql.base import DatabaseOperations + +quote_name = DatabaseOperations().quote_name def get_table_list(cursor): "Returns a list of table names in the current database." diff --git a/django/db/backends/postgresql/operations.py b/django/db/backends/postgresql/operations.py new file mode 100644 index 0000000000..21c017038f --- /dev/null +++ b/django/db/backends/postgresql/operations.py @@ -0,0 +1,109 @@ +from django.db.backends import BaseDatabaseOperations + +# This DatabaseOperations class lives in here instead of base.py because it's +# used by both the 'postgresql' and 'postgresql_psycopg2' backends. + +class DatabaseOperations(BaseDatabaseOperations): + def __init__(self, postgres_version=None): + self.postgres_version = postgres_version + + def date_extract_sql(self, lookup_type, field_name): + # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-EXTRACT + return "EXTRACT('%s' FROM %s)" % (lookup_type, field_name) + + def date_trunc_sql(self, lookup_type, field_name): + # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC + return "DATE_TRUNC('%s', %s)" % (lookup_type, field_name) + + def deferrable_sql(self): + return " DEFERRABLE INITIALLY DEFERRED" + + def last_insert_id(self, cursor, table_name, pk_name): + cursor.execute("SELECT CURRVAL('\"%s_%s_seq\"')" % (table_name, pk_name)) + return cursor.fetchone()[0] + + def quote_name(self, name): + if name.startswith('"') and name.endswith('"'): + return name # Quoting once is enough. + return '"%s"' % name + + def sql_flush(self, style, tables, sequences): + if tables: + if self.postgres_version[0] >= 8 and self.postgres_version[1] >= 1: + # Postgres 8.1+ can do 'TRUNCATE x, y, z...;'. In fact, it *has to* + # in order to be able to truncate tables referenced by a foreign + # key in any other table. The result is a single SQL TRUNCATE + # statement. + sql = ['%s %s;' % \ + (style.SQL_KEYWORD('TRUNCATE'), + style.SQL_FIELD(', '.join([self.quote_name(table) for table in tables])) + )] + else: + # Older versions of Postgres can't do TRUNCATE in a single call, so + # they must use a simple delete. + sql = ['%s %s %s;' % \ + (style.SQL_KEYWORD('DELETE'), + style.SQL_KEYWORD('FROM'), + style.SQL_FIELD(self.quote_name(table)) + ) for table in tables] + + # 'ALTER SEQUENCE sequence_name RESTART WITH 1;'... style SQL statements + # to reset sequence indices + for sequence_info in sequences: + table_name = sequence_info['table'] + column_name = sequence_info['column'] + if column_name and len(column_name)>0: + # sequence name in this case will be
        __seq + sql.append("%s %s %s %s %s %s;" % \ + (style.SQL_KEYWORD('ALTER'), + style.SQL_KEYWORD('SEQUENCE'), + style.SQL_FIELD(self.quote_name('%s_%s_seq' % (table_name, column_name))), + style.SQL_KEYWORD('RESTART'), + style.SQL_KEYWORD('WITH'), + style.SQL_FIELD('1') + ) + ) + else: + # sequence name in this case will be
        _id_seq + sql.append("%s %s %s %s %s %s;" % \ + (style.SQL_KEYWORD('ALTER'), + style.SQL_KEYWORD('SEQUENCE'), + style.SQL_FIELD(self.quote_name('%s_id_seq' % table_name)), + style.SQL_KEYWORD('RESTART'), + style.SQL_KEYWORD('WITH'), + style.SQL_FIELD('1') + ) + ) + return sql + else: + return [] + + def sequence_reset_sql(self, style, model_list): + from django.db import models + output = [] + qn = self.quote_name + for model in model_list: + # Use `coalesce` to set the sequence for each model to the max pk value if there are records, + # or 1 if there are none. Set the `is_called` property (the third argument to `setval`) to true + # if there are records (as the max pk value is already in use), otherwise set it to false. + for f in model._meta.fields: + if isinstance(f, models.AutoField): + output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \ + (style.SQL_KEYWORD('SELECT'), + style.SQL_FIELD(qn('%s_%s_seq' % (model._meta.db_table, f.column))), + style.SQL_FIELD(qn(f.column)), + style.SQL_FIELD(qn(f.column)), + style.SQL_KEYWORD('IS NOT'), + style.SQL_KEYWORD('FROM'), + style.SQL_TABLE(qn(model._meta.db_table)))) + break # Only one AutoField is allowed per model, so don't bother continuing. + for f in model._meta.many_to_many: + output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \ + (style.SQL_KEYWORD('SELECT'), + style.SQL_FIELD(qn('%s_id_seq' % f.m2m_db_table())), + style.SQL_FIELD(qn('id')), + style.SQL_FIELD(qn('id')), + style.SQL_KEYWORD('IS NOT'), + style.SQL_KEYWORD('FROM'), + style.SQL_TABLE(f.m2m_db_table()))) + return output \ No newline at end of file diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index c0ecbf80e9..43ca7a1ec5 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -4,42 +4,50 @@ PostgreSQL database backend for Django. Requires psycopg 2: http://initd.org/projects/psycopg2 """ -from django.db.backends import util +from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures +from django.db.backends.postgresql.operations import DatabaseOperations try: import psycopg2 as Database import psycopg2.extensions except ImportError, e: from django.core.exceptions import ImproperlyConfigured - raise ImproperlyConfigured, "Error loading psycopg2 module: %s" % e + raise ImproperlyConfigured("Error loading psycopg2 module: %s" % e) DatabaseError = Database.DatabaseError IntegrityError = Database.IntegrityError -try: - # Only exists in Python 2.4+ - from threading import local -except ImportError: - # Import copy of _thread_local.py from Python 2.4 - from django.utils._threading_local import local - psycopg2.extensions.register_type(psycopg2.extensions.UNICODE) -postgres_version = None +class DatabaseFeatures(BaseDatabaseFeatures): + needs_datetime_string_cast = False -class DatabaseWrapper(local): - def __init__(self, **kwargs): - self.connection = None - self.queries = [] - self.options = kwargs +class DatabaseWrapper(BaseDatabaseWrapper): + features = DatabaseFeatures() + ops = DatabaseOperations() + operators = { + 'exact': '= %s', + 'iexact': 'ILIKE %s', + 'contains': 'LIKE %s', + 'icontains': 'ILIKE %s', + 'regex': '~ %s', + 'iregex': '~* %s', + 'gt': '> %s', + 'gte': '>= %s', + 'lt': '< %s', + 'lte': '<= %s', + 'startswith': 'LIKE %s', + 'endswith': 'LIKE %s', + 'istartswith': 'ILIKE %s', + 'iendswith': 'ILIKE %s', + } - def cursor(self): - from django.conf import settings + def _cursor(self, settings): set_tz = False if self.connection is None: set_tz = True if settings.DATABASE_NAME == '': from django.core.exceptions import ImproperlyConfigured - raise ImproperlyConfigured, "You need to specify DATABASE_NAME in your Django settings file." + raise ImproperlyConfigured("You need to specify DATABASE_NAME in your Django settings file.") conn_string = "dbname=%s" % settings.DATABASE_NAME if settings.DATABASE_USER: conn_string = "user=%s %s" % (settings.DATABASE_USER, conn_string) @@ -56,187 +64,7 @@ class DatabaseWrapper(local): cursor.tzinfo_factory = None if set_tz: cursor.execute("SET TIME ZONE %s", [settings.TIME_ZONE]) - global postgres_version - if not postgres_version: + if self.ops.postgres_version is None: cursor.execute("SELECT version()") - postgres_version = [int(val) for val in cursor.fetchone()[0].split()[1].split('.')] - if settings.DEBUG: - return util.CursorDebugWrapper(cursor, self) + self.ops.postgres_version = [int(val) for val in cursor.fetchone()[0].split()[1].split('.')] return cursor - - def _commit(self): - if self.connection is not None: - return self.connection.commit() - - def _rollback(self): - if self.connection is not None: - return self.connection.rollback() - - def close(self): - if self.connection is not None: - self.connection.close() - self.connection = None - -allows_group_by_ordinal = True -allows_unique_and_pk = True -autoindexes_primary_keys = True -needs_datetime_string_cast = False -needs_upper_for_iops = False -supports_constraints = True -supports_tablespaces = False -uses_case_insensitive_names = False - -def quote_name(name): - if name.startswith('"') and name.endswith('"'): - return name # Quoting once is enough. - return '"%s"' % name - -dictfetchone = util.dictfetchone -dictfetchmany = util.dictfetchmany -dictfetchall = util.dictfetchall - -def get_last_insert_id(cursor, table_name, pk_name): - cursor.execute("SELECT CURRVAL('\"%s_%s_seq\"')" % (table_name, pk_name)) - return cursor.fetchone()[0] - -def get_date_extract_sql(lookup_type, table_name): - # lookup_type is 'year', 'month', 'day' - # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-EXTRACT - return "EXTRACT('%s' FROM %s)" % (lookup_type, table_name) - -def get_date_trunc_sql(lookup_type, field_name): - # lookup_type is 'year', 'month', 'day' - # http://www.postgresql.org/docs/8.0/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC - return "DATE_TRUNC('%s', %s)" % (lookup_type, field_name) - -def get_datetime_cast_sql(): - return None - -def get_limit_offset_sql(limit, offset=None): - sql = "LIMIT %s" % limit - if offset and offset != 0: - sql += " OFFSET %s" % offset - return sql - -def get_random_function_sql(): - return "RANDOM()" - -def get_deferrable_sql(): - return " DEFERRABLE INITIALLY DEFERRED" - -def get_fulltext_search_sql(field_name): - raise NotImplementedError - -def get_drop_foreignkey_sql(): - return "DROP CONSTRAINT" - -def get_pk_default_value(): - return "DEFAULT" - -def get_max_name_length(): - return None - -def get_start_transaction_sql(): - return "BEGIN;" - -def get_autoinc_sql(table): - return None - -def get_sql_flush(style, tables, sequences): - """Return a list of SQL statements required to remove all data from - all tables in the database (without actually removing the tables - themselves) and put the database in an empty 'initial' state - """ - if tables: - if postgres_version[0] >= 8 and postgres_version[1] >= 1: - # Postgres 8.1+ can do 'TRUNCATE x, y, z...;'. In fact, it *has to* in order to be able to - # truncate tables referenced by a foreign key in any other table. The result is a - # single SQL TRUNCATE statement - sql = ['%s %s;' % \ - (style.SQL_KEYWORD('TRUNCATE'), - style.SQL_FIELD(', '.join([quote_name(table) for table in tables])) - )] - else: - sql = ['%s %s %s;' % \ - (style.SQL_KEYWORD('DELETE'), - style.SQL_KEYWORD('FROM'), - style.SQL_FIELD(quote_name(table)) - ) for table in tables] - - # 'ALTER SEQUENCE sequence_name RESTART WITH 1;'... style SQL statements - # to reset sequence indices - for sequence in sequences: - table_name = sequence['table'] - column_name = sequence['column'] - if column_name and len(column_name) > 0: - # sequence name in this case will be
        __seq - sql.append("%s %s %s %s %s %s;" % \ - (style.SQL_KEYWORD('ALTER'), - style.SQL_KEYWORD('SEQUENCE'), - style.SQL_FIELD(quote_name('%s_%s_seq' % (table_name, column_name))), - style.SQL_KEYWORD('RESTART'), - style.SQL_KEYWORD('WITH'), - style.SQL_FIELD('1') - ) - ) - else: - # sequence name in this case will be
        _id_seq - sql.append("%s %s %s %s %s %s;" % \ - (style.SQL_KEYWORD('ALTER'), - style.SQL_KEYWORD('SEQUENCE'), - style.SQL_FIELD(quote_name('%s_id_seq' % table_name)), - style.SQL_KEYWORD('RESTART'), - style.SQL_KEYWORD('WITH'), - style.SQL_FIELD('1') - ) - ) - return sql - else: - return [] - -def get_sql_sequence_reset(style, model_list): - "Returns a list of the SQL statements to reset sequences for the given models." - from django.db import models - output = [] - for model in model_list: - # Use `coalesce` to set the sequence for each model to the max pk value if there are records, - # or 1 if there are none. Set the `is_called` property (the third argument to `setval`) to true - # if there are records (as the max pk value is already in use), otherwise set it to false. - for f in model._meta.fields: - if isinstance(f, models.AutoField): - output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \ - (style.SQL_KEYWORD('SELECT'), - style.SQL_FIELD(quote_name('%s_%s_seq' % (model._meta.db_table, f.column))), - style.SQL_FIELD(quote_name(f.column)), - style.SQL_FIELD(quote_name(f.column)), - style.SQL_KEYWORD('IS NOT'), - style.SQL_KEYWORD('FROM'), - style.SQL_TABLE(quote_name(model._meta.db_table)))) - break # Only one AutoField is allowed per model, so don't bother continuing. - for f in model._meta.many_to_many: - output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \ - (style.SQL_KEYWORD('SELECT'), - style.SQL_FIELD(quote_name('%s_id_seq' % f.m2m_db_table())), - style.SQL_FIELD(quote_name('id')), - style.SQL_FIELD(quote_name('id')), - style.SQL_KEYWORD('IS NOT'), - style.SQL_KEYWORD('FROM'), - style.SQL_TABLE(f.m2m_db_table()))) - return output - -OPERATOR_MAPPING = { - 'exact': '= %s', - 'iexact': 'ILIKE %s', - 'contains': 'LIKE %s', - 'icontains': 'ILIKE %s', - 'regex': '~ %s', - 'iregex': '~* %s', - 'gt': '> %s', - 'gte': '>= %s', - 'lt': '< %s', - 'lte': '<= %s', - 'startswith': 'LIKE %s', - 'endswith': 'LIKE %s', - 'istartswith': 'ILIKE %s', - 'iendswith': 'ILIKE %s', -} diff --git a/django/db/backends/postgresql_psycopg2/introspection.py b/django/db/backends/postgresql_psycopg2/introspection.py index a953368991..bf839c3e95 100644 --- a/django/db/backends/postgresql_psycopg2/introspection.py +++ b/django/db/backends/postgresql_psycopg2/introspection.py @@ -1,4 +1,6 @@ -from django.db.backends.postgresql_psycopg2.base import quote_name +from django.db.backends.postgresql_psycopg2.base import DatabaseOperations + +quote_name = DatabaseOperations().quote_name def get_table_list(cursor): "Returns a list of table names in the current database." diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index a0b1341b53..7919e1cc50 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -2,7 +2,7 @@ SQLite3 backend for django. Requires pysqlite2 (http://pysqlite.org/). """ -from django.db.backends import util +from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util try: try: from sqlite3 import dbapi2 as Database @@ -34,21 +34,69 @@ Database.register_converter("TIMESTAMP", util.typecast_timestamp) Database.register_converter("decimal", util.typecast_decimal) Database.register_adapter(decimal.Decimal, util.rev_typecast_decimal) -try: - # Only exists in Python 2.4+ - from threading import local -except ImportError: - # Import copy of _thread_local.py from Python 2.4 - from django.utils._threading_local import local +class DatabaseFeatures(BaseDatabaseFeatures): + supports_constraints = False -class DatabaseWrapper(local): - def __init__(self, **kwargs): - self.connection = None - self.queries = [] - self.options = kwargs +class DatabaseOperations(BaseDatabaseOperations): + def date_extract_sql(self, lookup_type, field_name): + # sqlite doesn't support extract, so we fake it with the user-defined + # function django_extract that's registered in connect(). + return 'django_extract("%s", %s)' % (lookup_type.lower(), field_name) - def cursor(self): - from django.conf import settings + def date_trunc_sql(self, lookup_type, field_name): + # sqlite doesn't support DATE_TRUNC, so we fake it with a user-defined + # function django_date_trunc that's registered in connect(). + return 'django_date_trunc("%s", %s)' % (lookup_type.lower(), field_name) + + def drop_foreignkey_sql(self): + return "" + + def pk_default_value(self): + return 'NULL' + + def quote_name(self, name): + if name.startswith('"') and name.endswith('"'): + return name # Quoting once is enough. + return '"%s"' % name + + def sql_flush(self, style, tables, sequences): + # NB: The generated SQL below is specific to SQLite + # Note: The DELETE FROM... SQL generated below works for SQLite databases + # because constraints don't exist + sql = ['%s %s %s;' % \ + (style.SQL_KEYWORD('DELETE'), + style.SQL_KEYWORD('FROM'), + style.SQL_FIELD(self.quote_name(table)) + ) for table in tables] + # Note: No requirement for reset of auto-incremented indices (cf. other + # sql_flush() implementations). Just return SQL at this point + return sql + +class DatabaseWrapper(BaseDatabaseWrapper): + features = DatabaseFeatures() + ops = DatabaseOperations() + + # SQLite requires LIKE statements to include an ESCAPE clause if the value + # being escaped has a percent or underscore in it. + # See http://www.sqlite.org/lang_expr.html for an explanation. + operators = { + 'exact': '= %s', + 'iexact': "LIKE %s ESCAPE '\\'", + 'contains': "LIKE %s ESCAPE '\\'", + 'icontains': "LIKE %s ESCAPE '\\'", + 'regex': 'REGEXP %s', + 'iregex': "REGEXP '(?i)' || %s", + 'gt': '> %s', + 'gte': '>= %s', + 'lt': '< %s', + 'lte': '<= %s', + 'startswith': "LIKE %s ESCAPE '\\'", + 'endswith': "LIKE %s ESCAPE '\\'", + 'istartswith': "LIKE %s ESCAPE '\\'", + 'iendswith': "LIKE %s ESCAPE '\\'", + } + + def _cursor(self, settings): if self.connection is None: kwargs = { 'database': settings.DATABASE_NAME, @@ -60,28 +108,15 @@ class DatabaseWrapper(local): self.connection.create_function("django_extract", 2, _sqlite_extract) self.connection.create_function("django_date_trunc", 2, _sqlite_date_trunc) self.connection.create_function("regexp", 2, _sqlite_regexp) - cursor = self.connection.cursor(factory=SQLiteCursorWrapper) - if settings.DEBUG: - return util.CursorDebugWrapper(cursor, self) - else: - return cursor - - def _commit(self): - if self.connection is not None: - self.connection.commit() - - def _rollback(self): - if self.connection is not None: - self.connection.rollback() + return self.connection.cursor(factory=SQLiteCursorWrapper) def close(self): from django.conf import settings # If database is in memory, closing the connection destroys the - # database. To prevent accidental data loss, ignore close requests on + # database. To prevent accidental data loss, ignore close requests on # an in-memory db. - if self.connection is not None and settings.DATABASE_NAME != ":memory:": - self.connection.close() - self.connection = None + if settings.DATABASE_NAME != ":memory:": + BaseDatabaseWrapper.close(self) class SQLiteCursorWrapper(Database.Cursor): """ @@ -100,33 +135,6 @@ class SQLiteCursorWrapper(Database.Cursor): def convert_query(self, query, num_params): return query % tuple("?" * num_params) -allows_group_by_ordinal = True -allows_unique_and_pk = True -autoindexes_primary_keys = True -needs_datetime_string_cast = True -needs_upper_for_iops = False -supports_constraints = False -supports_tablespaces = False -uses_case_insensitive_names = False - -def quote_name(name): - if name.startswith('"') and name.endswith('"'): - return name # Quoting once is enough. - return '"%s"' % name - -dictfetchone = util.dictfetchone -dictfetchmany = util.dictfetchmany -dictfetchall = util.dictfetchall - -def get_last_insert_id(cursor, table_name, pk_name): - return cursor.lastrowid - -def get_date_extract_sql(lookup_type, table_name): - # lookup_type is 'year', 'month', 'day' - # sqlite doesn't support extract, so we fake it with the user-defined - # function _sqlite_extract that's registered in connect(), above. - return 'django_extract("%s", %s)' % (lookup_type.lower(), table_name) - def _sqlite_extract(lookup_type, dt): try: dt = util.typecast_timestamp(dt) @@ -134,67 +142,6 @@ def _sqlite_extract(lookup_type, dt): return None return str(getattr(dt, lookup_type)) -def get_date_trunc_sql(lookup_type, field_name): - # lookup_type is 'year', 'month', 'day' - # sqlite doesn't support DATE_TRUNC, so we fake it as above. - return 'django_date_trunc("%s", %s)' % (lookup_type.lower(), field_name) - -def get_datetime_cast_sql(): - return None - -def get_limit_offset_sql(limit, offset=None): - sql = "LIMIT %s" % limit - if offset and offset != 0: - sql += " OFFSET %s" % offset - return sql - -def get_random_function_sql(): - return "RANDOM()" - -def get_deferrable_sql(): - return "" - -def get_fulltext_search_sql(field_name): - raise NotImplementedError - -def get_drop_foreignkey_sql(): - return "" - -def get_pk_default_value(): - return "NULL" - -def get_max_name_length(): - return None - -def get_start_transaction_sql(): - return "BEGIN;" - -def get_autoinc_sql(table): - return None - -def get_sql_flush(style, tables, sequences): - """ - Return a list of SQL statements required to remove all data from - all tables in the database (without actually removing the tables - themselves) and put the database in an empty 'initial' state - """ - # NB: The generated SQL below is specific to SQLite - # Note: The DELETE FROM... SQL generated below works for SQLite databases - # because constraints don't exist - sql = ['%s %s %s;' % \ - (style.SQL_KEYWORD('DELETE'), - style.SQL_KEYWORD('FROM'), - style.SQL_FIELD(quote_name(table)) - ) for table in tables] - # Note: No requirement for reset of auto-incremented indices (cf. other - # get_sql_flush() implementations). Just return SQL at this point - return sql - -def get_sql_sequence_reset(style, model_list): - "Returns a list of the SQL statements to reset sequences for the given models." - # No sequence reset required - return [] - def _sqlite_date_trunc(lookup_type, dt): try: dt = util.typecast_timestamp(dt) @@ -213,24 +160,3 @@ def _sqlite_regexp(re_pattern, re_string): return bool(re.search(re_pattern, re_string)) except: return False - -# SQLite requires LIKE statements to include an ESCAPE clause if the value -# being escaped has a percent or underscore in it. -# See http://www.sqlite.org/lang_expr.html for an explanation. -OPERATOR_MAPPING = { - 'exact': '= %s', - 'iexact': "LIKE %s ESCAPE '\\'", - 'contains': "LIKE %s ESCAPE '\\'", - 'icontains': "LIKE %s ESCAPE '\\'", - 'regex': 'REGEXP %s', - 'iregex': "REGEXP '(?i)' || %s", - 'gt': '> %s', - 'gte': '>= %s', - 'lt': '< %s', - 'lte': '<= %s', - 'startswith': "LIKE %s ESCAPE '\\'", - 'endswith': "LIKE %s ESCAPE '\\'", - 'istartswith': "LIKE %s ESCAPE '\\'", - 'iendswith': "LIKE %s ESCAPE '\\'", -} - diff --git a/django/db/backends/sqlite3/introspection.py b/django/db/backends/sqlite3/introspection.py index cb2fbb8ee0..52b880aac2 100644 --- a/django/db/backends/sqlite3/introspection.py +++ b/django/db/backends/sqlite3/introspection.py @@ -1,4 +1,6 @@ -from django.db.backends.sqlite3.base import quote_name +from django.db.backends.sqlite3.base import DatabaseOperations + +quote_name = DatabaseOperations().quote_name def get_table_list(cursor): "Returns a list of table names in the current database." diff --git a/django/db/backends/util.py b/django/db/backends/util.py index 4de7c517d6..fa2df22927 100644 --- a/django/db/backends/util.py +++ b/django/db/backends/util.py @@ -124,30 +124,3 @@ def truncate_name(name, length=None): hash = md5.md5(name).hexdigest()[:4] return '%s%s' % (name[:length-4], hash) - -################################################################################## -# Helper functions for dictfetch* for databases that don't natively support them # -################################################################################## - -def _dict_helper(desc, row): - "Returns a dictionary for the given cursor.description and result row." - return dict(zip([col[0] for col in desc], row)) - -def dictfetchone(cursor): - "Returns a row from the cursor as a dict" - row = cursor.fetchone() - if not row: - return None - return _dict_helper(cursor.description, row) - -def dictfetchmany(cursor, number): - "Returns a certain number of rows from a cursor as a dict" - desc = cursor.description - for row in cursor.fetchmany(number): - yield _dict_helper(desc, row) - -def dictfetchall(cursor): - "Returns all rows from a cursor as a dict" - desc = cursor.description - for row in cursor.fetchall(): - yield _dict_helper(desc, row) diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py index fb93d36b0f..9556459d44 100644 --- a/django/db/models/__init__.py +++ b/django/db/models/__init__.py @@ -1,7 +1,7 @@ from django.conf import settings from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured from django.core import validators -from django.db import backend, connection +from django.db import connection from django.db.models.loading import get_apps, get_app, get_models, get_model, register_models from django.db.models.query import Q from django.db.models.manager import Manager diff --git a/django/db/models/base.py b/django/db/models/base.py index 85ddb79fff..92eb64f3d3 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -6,7 +6,7 @@ from django.db.models.fields import AutoField, ImageField, FieldDoesNotExist from django.db.models.fields.related import OneToOneRel, ManyToOneRel from django.db.models.query import delete_objects from django.db.models.options import Options -from django.db import connection, backend, transaction +from django.db import connection, transaction from django.db.models import signals from django.db.models.loading import register_models, get_model from django.dispatch import dispatcher @@ -212,28 +212,32 @@ class Model(object): non_pks = [f for f in self._meta.fields if not f.primary_key] cursor = connection.cursor() + qn = connection.ops.quote_name + # First, try an UPDATE. If that doesn't update anything, do an INSERT. pk_val = self._get_pk_val() - pk_set = bool(pk_val) + # Note: the comparison with '' is required for compatibility with + # oldforms-style model creation. + pk_set = pk_val is not None and pk_val != u'' record_exists = True if pk_set: # Determine whether a record with the primary key already exists. cursor.execute("SELECT 1 FROM %s WHERE %s=%%s" % \ - (backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.pk.column)), + (qn(self._meta.db_table), qn(self._meta.pk.column)), self._meta.pk.get_db_prep_lookup('exact', pk_val)) # If it does already exist, do an UPDATE. if cursor.fetchone(): db_values = [f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, False)) for f in non_pks] if db_values: cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % \ - (backend.quote_name(self._meta.db_table), - ','.join(['%s=%%s' % backend.quote_name(f.column) for f in non_pks]), - backend.quote_name(self._meta.pk.column)), + (qn(self._meta.db_table), + ','.join(['%s=%%s' % qn(f.column) for f in non_pks]), + qn(self._meta.pk.column)), db_values + self._meta.pk.get_db_prep_lookup('exact', pk_val)) else: record_exists = False if not pk_set or not record_exists: - field_names = [backend.quote_name(f.column) for f in self._meta.fields if not isinstance(f, AutoField)] + field_names = [qn(f.column) for f in self._meta.fields if not isinstance(f, AutoField)] db_values = [f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True)) for f in self._meta.fields if not isinstance(f, AutoField)] # If the PK has been manually set, respect that. if pk_set: @@ -241,23 +245,22 @@ class Model(object): db_values += [f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True)) for f in self._meta.fields if isinstance(f, AutoField)] placeholders = ['%s'] * len(field_names) if self._meta.order_with_respect_to: - field_names.append(backend.quote_name('_order')) + field_names.append(qn('_order')) # TODO: This assumes the database supports subqueries. placeholders.append('(SELECT COUNT(*) FROM %s WHERE %s = %%s)' % \ - (backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.order_with_respect_to.column))) + (qn(self._meta.db_table), qn(self._meta.order_with_respect_to.column))) db_values.append(getattr(self, self._meta.order_with_respect_to.attname)) if db_values: cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % \ - (backend.quote_name(self._meta.db_table), ','.join(field_names), + (qn(self._meta.db_table), ','.join(field_names), ','.join(placeholders)), db_values) else: # Create a new record with defaults for everything. cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % - (backend.quote_name(self._meta.db_table), - backend.quote_name(self._meta.pk.column), - backend.get_pk_default_value())) + (qn(self._meta.db_table), qn(self._meta.pk.column), + connection.ops.pk_default_value())) if self._meta.has_auto_field and not pk_set: - setattr(self, self._meta.pk.attname, backend.get_last_insert_id(cursor, self._meta.db_table, self._meta.pk.column)) + setattr(self, self._meta.pk.attname, connection.ops.last_insert_id(cursor, self._meta.db_table, self._meta.pk.column)) transaction.commit_unless_managed() # Run any post-save hooks. @@ -329,10 +332,11 @@ class Model(object): return force_unicode(dict(field.choices).get(value, value), strings_only=True) def _get_next_or_previous_by_FIELD(self, field, is_next, **kwargs): + qn = connection.ops.quote_name op = is_next and '>' or '<' where = '(%s %s %%s OR (%s = %%s AND %s.%s %s %%s))' % \ - (backend.quote_name(field.column), op, backend.quote_name(field.column), - backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.pk.column), op) + (qn(field.column), op, qn(field.column), + qn(self._meta.db_table), qn(self._meta.pk.column), op) param = smart_str(getattr(self, field.attname)) q = self.__class__._default_manager.filter(**kwargs).order_by((not is_next and '-' or '') + field.name, (not is_next and '-' or '') + self._meta.pk.name) q._where.append(where) @@ -343,14 +347,15 @@ class Model(object): raise self.DoesNotExist, "%s matching query does not exist." % self.__class__._meta.object_name def _get_next_or_previous_in_order(self, is_next): + qn = connection.ops.quote_name cachename = "__%s_order_cache" % is_next if not hasattr(self, cachename): op = is_next and '>' or '<' order_field = self._meta.order_with_respect_to where = ['%s %s (SELECT %s FROM %s WHERE %s=%%s)' % \ - (backend.quote_name('_order'), op, backend.quote_name('_order'), - backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.pk.column)), - '%s=%%s' % backend.quote_name(order_field.column)] + (qn('_order'), op, qn('_order'), + qn(self._meta.db_table), qn(self._meta.pk.column)), + '%s=%%s' % qn(order_field.column)] params = [self._get_pk_val(), getattr(self, order_field.attname)] obj = self._default_manager.order_by('_order').extra(where=where, params=params)[:1].get() setattr(self, cachename, obj) @@ -432,24 +437,26 @@ class Model(object): # ORDERING METHODS ######################### def method_set_order(ordered_obj, self, id_list): + qn = connection.ops.quote_name cursor = connection.cursor() # Example: "UPDATE poll_choices SET _order = %s WHERE poll_id = %s AND id = %s" sql = "UPDATE %s SET %s = %%s WHERE %s = %%s AND %s = %%s" % \ - (backend.quote_name(ordered_obj._meta.db_table), backend.quote_name('_order'), - backend.quote_name(ordered_obj._meta.order_with_respect_to.column), - backend.quote_name(ordered_obj._meta.pk.column)) + (qn(ordered_obj._meta.db_table), qn('_order'), + qn(ordered_obj._meta.order_with_respect_to.column), + qn(ordered_obj._meta.pk.column)) rel_val = getattr(self, ordered_obj._meta.order_with_respect_to.rel.field_name) cursor.executemany(sql, [(i, rel_val, j) for i, j in enumerate(id_list)]) transaction.commit_unless_managed() def method_get_order(ordered_obj, self): + qn = connection.ops.quote_name cursor = connection.cursor() # Example: "SELECT id FROM poll_choices WHERE poll_id = %s ORDER BY _order" sql = "SELECT %s FROM %s WHERE %s = %%s ORDER BY %s" % \ - (backend.quote_name(ordered_obj._meta.pk.column), - backend.quote_name(ordered_obj._meta.db_table), - backend.quote_name(ordered_obj._meta.order_with_respect_to.column), - backend.quote_name('_order')) + (qn(ordered_obj._meta.pk.column), + qn(ordered_obj._meta.db_table), + qn(ordered_obj._meta.order_with_respect_to.column), + qn('_order')) rel_val = getattr(self, ordered_obj._meta.order_with_respect_to.rel.field_name) cursor.execute(sql, [rel_val]) return [r[0] for r in cursor.fetchall()] diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 4024e87f2f..2bd31c982e 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -1,4 +1,4 @@ -from django.db import backend, transaction +from django.db import connection, transaction from django.db.models import signals, get_model from django.db.models.fields import AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField, get_ul_class from django.db.models.related import RelatedObject @@ -319,7 +319,6 @@ def create_many_related_manager(superclass): # source_col_name: the PK colname in join_table for the source object # target_col_name: the PK colname in join_table for the target object # *objs - objects to add. Either object instances, or primary keys of object instances. - from django.db import connection # If there aren't any objects, there is nothing to do. if objs: @@ -350,7 +349,6 @@ def create_many_related_manager(superclass): # source_col_name: the PK colname in join_table for the source object # target_col_name: the PK colname in join_table for the target object # *objs - objects to remove - from django.db import connection # If there aren't any objects, there is nothing to do. if objs: @@ -371,7 +369,6 @@ def create_many_related_manager(superclass): def _clear_items(self, source_col_name): # source_col_name: the PK colname in join_table for the source object - from django.db import connection cursor = connection.cursor() cursor.execute("DELETE FROM %s WHERE %s = %%s" % \ (self.join_table, source_col_name), @@ -400,7 +397,7 @@ class ManyRelatedObjectsDescriptor(object): superclass = rel_model._default_manager.__class__ RelatedManager = create_many_related_manager(superclass) - qn = backend.quote_name + qn = connection.ops.quote_name manager = RelatedManager( model=rel_model, core_filters={'%s__pk' % self.related.field.name: instance._get_pk_val()}, @@ -441,7 +438,7 @@ class ReverseManyRelatedObjectsDescriptor(object): superclass = rel_model._default_manager.__class__ RelatedManager = create_many_related_manager(superclass) - qn = backend.quote_name + qn = connection.ops.quote_name manager = RelatedManager( model=rel_model, core_filters={'%s__pk' % self.field.related_query_name(): instance._get_pk_val()}, diff --git a/django/db/models/loading.py b/django/db/models/loading.py index 224f5e8451..090390aa9e 100644 --- a/django/db/models/loading.py +++ b/django/db/models/loading.py @@ -4,113 +4,188 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured import sys import os +import threading -__all__ = ('get_apps', 'get_app', 'get_models', 'get_model', 'register_models') +__all__ = ('get_apps', 'get_app', 'get_models', 'get_model', 'register_models', + 'load_app', 'app_cache_ready') -_app_list = [] # Cache of installed apps. - # Entry is not placed in app_list cache until entire app is loaded. -_app_models = {} # Dictionary of models against app label - # Each value is a dictionary of model name: model class - # Applabel and Model entry exists in cache when individual model is loaded. -_app_errors = {} # Dictionary of errors that were experienced when loading the INSTALLED_APPS - # Key is the app_name of the model, value is the exception that was raised - # during model loading. -_loaded = False # Has the contents of settings.INSTALLED_APPS been loaded? - # i.e., has get_apps() been called? - -def get_apps(): - "Returns a list of all installed modules that contain models." - global _app_list - global _loaded - if not _loaded: - _loaded = True - for app_name in settings.INSTALLED_APPS: - try: - load_app(app_name) - except Exception, e: - # Problem importing the app - _app_errors[app_name] = e - return _app_list - -def get_app(app_label, emptyOK=False): - "Returns the module containing the models for the given app_label. If the app has no models in it and 'emptyOK' is True, returns None." - get_apps() # Run get_apps() to populate the _app_list cache. Slightly hackish. - for app_name in settings.INSTALLED_APPS: - if app_label == app_name.split('.')[-1]: - mod = load_app(app_name) - if mod is None: - if emptyOK: - return None - else: - return mod - raise ImproperlyConfigured, "App with label %s could not be found" % app_label - -def load_app(app_name): - "Loads the app with the provided fully qualified name, and returns the model module." - global _app_list - mod = __import__(app_name, {}, {}, ['models']) - if not hasattr(mod, 'models'): - return None - if mod.models not in _app_list: - _app_list.append(mod.models) - return mod.models - -def get_app_errors(): - "Returns the map of known problems with the INSTALLED_APPS" - global _app_errors - get_apps() # Run get_apps() to populate the _app_list cache. Slightly hackish. - return _app_errors - -def get_models(app_mod=None): +class AppCache(object): """ - Given a module containing models, returns a list of the models. Otherwise - returns a list of all installed models. + A cache that stores installed applications and their models. Used to + provide reverse-relations and for app introspection (e.g. admin). """ - app_list = get_apps() # Run get_apps() to populate the _app_list cache. Slightly hackish. - if app_mod: - return _app_models.get(app_mod.__name__.split('.')[-2], {}).values() - else: - model_list = [] - for app_mod in app_list: - model_list.extend(get_models(app_mod)) - return model_list + # Use the Borg pattern to share state between all instances. Details at + # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66531. + __shared_state = dict( + # Keys of app_store are the model modules for each application. + app_store = {}, -def get_model(app_label, model_name, seed_cache=True): - """ - Returns the model matching the given app_label and case-insensitive - model_name. + # Mapping of app_labels to a dictionary of model names to model code. + app_models = {}, - Returns None if no model is found. - """ - if seed_cache: - get_apps() - try: - model_dict = _app_models[app_label] - except KeyError: - return None + # Mapping of app_labels to errors raised when trying to import the app. + app_errors = {}, - try: - return model_dict[model_name.lower()] - except KeyError: - return None + # -- Everything below here is only used when populating the cache -- + loaded = False, + handled = {}, + postponed = [], + nesting_level = 0, + write_lock = threading.RLock(), + ) -def register_models(app_label, *models): - """ - Register a set of models as belonging to an app. - """ - for model in models: - # Store as 'name: model' pair in a dictionary - # in the _app_models dictionary - model_name = model._meta.object_name.lower() - model_dict = _app_models.setdefault(app_label, {}) - if model_name in model_dict: - # The same model may be imported via different paths (e.g. - # appname.models and project.appname.models). We use the source - # filename as a means to detect identity. - fname1 = os.path.abspath(sys.modules[model.__module__].__file__) - fname2 = os.path.abspath(sys.modules[model_dict[model_name].__module__].__file__) - # Since the filename extension could be .py the first time and .pyc - # or .pyo the second time, ignore the extension when comparing. - if os.path.splitext(fname1)[0] == os.path.splitext(fname2)[0]: - continue - model_dict[model_name] = model + def __init__(self): + self.__dict__ = self.__shared_state + + def _populate(self): + """ + Fill in all the cache information. This method is threadsafe, in the + sense that every caller will see the same state upon return, and if the + cache is already initialised, it does no work. + """ + if self.loaded: + return + self.write_lock.acquire() + try: + if self.loaded: + return + for app_name in settings.INSTALLED_APPS: + if app_name in self.handled: + continue + try: + self.load_app(app_name, True) + except Exception, e: + # Problem importing the app + self.app_errors[app_name] = e + if not self.nesting_level: + for app_name in self.postponed: + self.load_app(app_name) + self.loaded = True + finally: + self.write_lock.release() + + def load_app(self, app_name, can_postpone=False): + """ + Loads the app with the provided fully qualified name, and returns the + model module. + """ + self.handled[app_name] = None + self.nesting_level += 1 + mod = __import__(app_name, {}, {}, ['models']) + self.nesting_level -= 1 + if not hasattr(mod, 'models'): + if can_postpone: + # Either the app has no models, or the package is still being + # imported by Python and the model module isn't available yet. + # We will check again once all the recursion has finished (in + # populate). + self.postponed.append(app_name) + return None + if mod.models not in self.app_store: + self.app_store[mod.models] = len(self.app_store) + return mod.models + + def app_cache_ready(self): + """ + Returns true if the model cache is fully populated. + + Useful for code that wants to cache the results of get_models() for + themselves once it is safe to do so. + """ + return self.loaded + + def get_apps(self): + "Returns a list of all installed modules that contain models." + self._populate() + + # Ensure the returned list is always in the same order (with new apps + # added at the end). This avoids unstable ordering on the admin app + # list page, for example. + apps = [(v, k) for k, v in self.app_store.items()] + apps.sort() + return [elt[1] for elt in apps] + + def get_app(self, app_label, emptyOK=False): + """ + Returns the module containing the models for the given app_label. If + the app has no models in it and 'emptyOK' is True, returns None. + """ + self._populate() + self.write_lock.acquire() + try: + for app_name in settings.INSTALLED_APPS: + if app_label == app_name.split('.')[-1]: + mod = self.load_app(app_name, False) + if mod is None: + if emptyOK: + return None + else: + return mod + raise ImproperlyConfigured, "App with label %s could not be found" % app_label + finally: + self.write_lock.release() + + def get_app_errors(self): + "Returns the map of known problems with the INSTALLED_APPS." + self._populate() + return self.app_errors + + def get_models(self, app_mod=None): + """ + Given a module containing models, returns a list of the models. + Otherwise returns a list of all installed models. + """ + self._populate() + if app_mod: + return self.app_models.get(app_mod.__name__.split('.')[-2], {}).values() + else: + model_list = [] + for app_entry in self.app_models.itervalues(): + model_list.extend(app_entry.values()) + return model_list + + def get_model(self, app_label, model_name, seed_cache=True): + """ + Returns the model matching the given app_label and case-insensitive + model_name. + + Returns None if no model is found. + """ + if seed_cache: + self._populate() + return self.app_models.get(app_label, {}).get(model_name.lower()) + + def register_models(self, app_label, *models): + """ + Register a set of models as belonging to an app. + """ + for model in models: + # Store as 'name: model' pair in a dictionary + # in the _app_models dictionary + model_name = model._meta.object_name.lower() + model_dict = self.app_models.setdefault(app_label, {}) + if model_name in model_dict: + # The same model may be imported via different paths (e.g. + # appname.models and project.appname.models). We use the source + # filename as a means to detect identity. + fname1 = os.path.abspath(sys.modules[model.__module__].__file__) + fname2 = os.path.abspath(sys.modules[model_dict[model_name].__module__].__file__) + # Since the filename extension could be .py the first time and + # .pyc or .pyo the second time, ignore the extension when + # comparing. + if os.path.splitext(fname1)[0] == os.path.splitext(fname2)[0]: + continue + model_dict[model_name] = model + +cache = AppCache() + +# These methods were always module level, so are kept that way for backwards +# compatibility. +get_apps = cache.get_apps +get_app = cache.get_app +get_app_errors = cache.get_app_errors +get_models = cache.get_models +get_model = cache.get_model +register_models = cache.register_models +load_app = cache.load_app +app_cache_ready = cache.app_cache_ready diff --git a/django/db/models/options.py b/django/db/models/options.py index 9483cabe9b..7a10829b1c 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -2,7 +2,7 @@ from django.conf import settings from django.db.models.related import RelatedObject from django.db.models.fields.related import ManyToManyRel from django.db.models.fields import AutoField, FieldDoesNotExist -from django.db.models.loading import get_models +from django.db.models.loading import get_models, app_cache_ready from django.db.models.query import orderlist2sql from django.utils.translation import activate, deactivate_all, get_language, string_concat from django.utils.encoding import force_unicode, smart_str @@ -62,7 +62,7 @@ class Options(object): del self.meta def _prepare(self, model): - from django.db import backend + from django.db import connection from django.db.backends.util import truncate_name if self.order_with_respect_to: self.order_with_respect_to = self.get_field(self.order_with_respect_to) @@ -78,8 +78,7 @@ class Options(object): # If the db_table wasn't provided, use the app_label + module_name. if not self.db_table: self.db_table = "%s_%s" % (self.app_label, self.module_name) - self.db_table = truncate_name(self.db_table, - backend.get_max_name_length()) + self.db_table = truncate_name(self.db_table, connection.ops.max_name_length()) def add_field(self, field): # Insert the given field in the order in which it was created, using @@ -178,7 +177,8 @@ class Options(object): for f in klass._meta.many_to_many: if f.rel and self == f.rel.to._meta: rel_objs.append(RelatedObject(f.rel.to, klass, f)) - self._all_related_many_to_many_objects = rel_objs + if app_cache_ready(): + self._all_related_many_to_many_objects = rel_objs return rel_objs def get_ordered_objects(self): diff --git a/django/db/models/query.py b/django/db/models/query.py index 7620c366ce..a750ef5550 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -1,5 +1,5 @@ from django.conf import settings -from django.db import backend, connection, transaction +from django.db import connection, transaction from django.db.models.fields import DateField, FieldDoesNotExist from django.db.models import signals, loading from django.dispatch import dispatcher @@ -64,23 +64,24 @@ def orderfield2column(f, opts): return f def orderlist2sql(order_list, opts, prefix=''): + qn = connection.ops.quote_name if prefix.endswith('.'): - prefix = backend.quote_name(prefix[:-1]) + '.' + prefix = qn(prefix[:-1]) + '.' output = [] for f in handle_legacy_orderlist(order_list): if f.startswith('-'): - output.append('%s%s DESC' % (prefix, backend.quote_name(orderfield2column(f[1:], opts)))) + output.append('%s%s DESC' % (prefix, qn(orderfield2column(f[1:], opts)))) elif f == '?': - output.append(backend.get_random_function_sql()) + output.append(connection.ops.random_function_sql()) else: - output.append('%s%s ASC' % (prefix, backend.quote_name(orderfield2column(f, opts)))) + output.append('%s%s ASC' % (prefix, qn(orderfield2column(f, opts)))) return ', '.join(output) def quote_only_if_word(word): if re.search('\W', word): # Don't quote if there are spaces or non-word chars. return word else: - return backend.quote_name(word) + return connection.ops.quote_name(word) class _QuerySet(object): "Represents a lazy database lookup for a set of objects" @@ -235,8 +236,8 @@ class _QuerySet(object): cursor = connection.cursor() if self._distinct: - id_col = "%s.%s" % (backend.quote_name(self.model._meta.db_table), - backend.quote_name(self.model._meta.pk.column)) + id_col = "%s.%s" % (connection.ops.quote_name(self.model._meta.db_table), + connection.ops.quote_name(self.model._meta.pk.column)) cursor.execute("SELECT COUNT(DISTINCT(%s))" % id_col + sql, params) else: cursor.execute("SELECT COUNT(*)" + sql, params) @@ -308,11 +309,12 @@ class _QuerySet(object): assert self._limit is None and self._offset is None, \ "Cannot use 'limit' or 'offset' with in_bulk" assert isinstance(id_list, (tuple, list)), "in_bulk() must be provided with a list of IDs." + qn = connection.ops.quote_name id_list = list(id_list) if id_list == []: return {} qs = self._clone() - qs._where.append("%s.%s IN (%s)" % (backend.quote_name(self.model._meta.db_table), backend.quote_name(self.model._meta.pk.column), ",".join(['%s'] * len(id_list)))) + qs._where.append("%s.%s IN (%s)" % (qn(self.model._meta.db_table), qn(self.model._meta.pk.column), ",".join(['%s'] * len(id_list)))) qs._params.extend(id_list) return dict([(obj._get_pk_val(), obj) for obj in qs.iterator()]) @@ -481,10 +483,11 @@ class _QuerySet(object): return self._result_cache def _get_sql_clause(self): + qn = connection.ops.quote_name opts = self.model._meta # Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z. - select = ["%s.%s" % (backend.quote_name(opts.db_table), backend.quote_name(f.column)) for f in opts.fields] + select = ["%s.%s" % (qn(opts.db_table), qn(f.column)) for f in opts.fields] tables = [quote_only_if_word(t) for t in self._tables] joins = SortedDict() where = self._where[:] @@ -505,10 +508,10 @@ class _QuerySet(object): # Add any additional SELECTs. if self._select: - select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), backend.quote_name(s[0])) for s in self._select.items()]) + select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), qn(s[0])) for s in self._select.items()]) # Start composing the body of the SQL statement. - sql = [" FROM", backend.quote_name(opts.db_table)] + sql = [" FROM", qn(opts.db_table)] # Compose the join dictionary into SQL describing the joins. if joins: @@ -531,7 +534,7 @@ class _QuerySet(object): ordering_to_use = opts.ordering for f in handle_legacy_orderlist(ordering_to_use): if f == '?': # Special case. - order_by.append(backend.get_random_function_sql()) + order_by.append(connection.ops.random_function_sql()) else: if f.startswith('-'): col_name = f[1:] @@ -541,29 +544,29 @@ class _QuerySet(object): order = "ASC" if "." in col_name: table_prefix, col_name = col_name.split('.', 1) - table_prefix = backend.quote_name(table_prefix) + '.' + table_prefix = qn(table_prefix) + '.' else: # Use the database table as a column prefix if it wasn't given, # and if the requested column isn't a custom SELECT. if "." not in col_name and col_name not in (self._select or ()): - table_prefix = backend.quote_name(opts.db_table) + '.' + table_prefix = qn(opts.db_table) + '.' else: table_prefix = '' - order_by.append('%s%s %s' % (table_prefix, backend.quote_name(orderfield2column(col_name, opts)), order)) + order_by.append('%s%s %s' % (table_prefix, qn(orderfield2column(col_name, opts)), order)) if order_by: sql.append("ORDER BY " + ", ".join(order_by)) # LIMIT and OFFSET clauses if self._limit is not None: - sql.append("%s " % backend.get_limit_offset_sql(self._limit, self._offset)) + sql.append("%s " % connection.ops.limit_offset_sql(self._limit, self._offset)) else: assert self._offset is None, "'offset' is not allowed without 'limit'" return select, " ".join(sql), params -# Use the backend's QuerySet class if it defines one, otherwise use _QuerySet. -if hasattr(backend, 'get_query_set_class'): - QuerySet = backend.get_query_set_class(_QuerySet) +# Use the backend's QuerySet class if it defines one. Otherwise, use _QuerySet. +if connection.features.uses_custom_queryset: + QuerySet = connection.ops.query_set_class(_QuerySet) else: QuerySet = _QuerySet @@ -579,6 +582,8 @@ class ValuesQuerySet(QuerySet): except EmptyResultSet: raise StopIteration + qn = connection.ops.quote_name + # self._select is a dictionary, and dictionaries' key order is # undefined, so we convert it to a list of tuples. extra_select = self._select.items() @@ -605,9 +610,9 @@ class ValuesQuerySet(QuerySet): field_names = [f.attname for f in fields] columns = [f.column for f in fields] - select = ['%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(c)) for c in columns] + select = ['%s.%s' % (qn(self.model._meta.db_table), qn(c)) for c in columns] if extra_select: - select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), backend.quote_name(s[0])) for s in extra_select]) + select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), qn(s[0])) for s in extra_select]) field_names.extend([f[0] for f in extra_select]) cursor = connection.cursor() @@ -632,32 +637,33 @@ class DateQuerySet(QuerySet): def iterator(self): from django.db.backends.util import typecast_timestamp from django.db.models.fields import DateTimeField + + qn = connection.ops.quote_name self._order_by = () # Clear this because it'll mess things up otherwise. if self._field.null: self._where.append('%s.%s IS NOT NULL' % \ - (backend.quote_name(self.model._meta.db_table), backend.quote_name(self._field.column))) + (qn(self.model._meta.db_table), qn(self._field.column))) try: select, sql, params = self._get_sql_clause() except EmptyResultSet: raise StopIteration - table_name = backend.quote_name(self.model._meta.db_table) - field_name = backend.quote_name(self._field.column) + table_name = qn(self.model._meta.db_table) + field_name = qn(self._field.column) - if backend.allows_group_by_ordinal: + if connection.features.allows_group_by_ordinal: group_by = '1' else: - group_by = backend.get_date_trunc_sql(self._kind, - '%s.%s' % (table_name, field_name)) + group_by = connection.ops.date_trunc_sql(self._kind, '%s.%s' % (table_name, field_name)) sql = 'SELECT %s %s GROUP BY %s ORDER BY 1 %s' % \ - (backend.get_date_trunc_sql(self._kind, '%s.%s' % (backend.quote_name(self.model._meta.db_table), - backend.quote_name(self._field.column))), sql, group_by, self._order) + (connection.ops.date_trunc_sql(self._kind, '%s.%s' % (qn(self.model._meta.db_table), + qn(self._field.column))), sql, group_by, self._order) cursor = connection.cursor() cursor.execute(sql, params) has_resolve_columns = hasattr(self, 'resolve_columns') - needs_datetime_string_cast = backend.needs_datetime_string_cast + needs_datetime_string_cast = connection.features.needs_datetime_string_cast dates = [] # It would be better to use self._field here instead of DateTimeField(), # but in Oracle that will result in a list of datetime.date instead of @@ -777,44 +783,44 @@ class QNot(Q): return SortedDict(), [], [] return joins, where2, params -def get_where_clause(lookup_type, table_prefix, field_name, value): +def get_where_clause(lookup_type, table_prefix, field_name, value, db_type): if table_prefix.endswith('.'): - table_prefix = backend.quote_name(table_prefix[:-1])+'.' - field_name = backend.quote_name(field_name) - if type(value) == datetime.datetime and backend.get_datetime_cast_sql(): - cast_sql = backend.get_datetime_cast_sql() + table_prefix = connection.ops.quote_name(table_prefix[:-1])+'.' + field_name = connection.ops.quote_name(field_name) + if type(value) == datetime.datetime and connection.ops.datetime_cast_sql(): + cast_sql = connection.ops.datetime_cast_sql() else: cast_sql = '%s' - if lookup_type in ('iexact', 'icontains', 'istartswith', 'iendswith') and backend.needs_upper_for_iops: - format = 'UPPER(%s%s) %s' + field_sql = connection.ops.field_cast_sql(db_type) % (table_prefix + field_name) + if lookup_type in ('iexact', 'icontains', 'istartswith', 'iendswith') and connection.features.needs_upper_for_iops: + format = 'UPPER(%s) %s' else: - format = '%s%s %s' + format = '%s %s' try: - return format % (table_prefix, field_name, - backend.OPERATOR_MAPPING[lookup_type] % cast_sql) + return format % (field_sql, connection.operators[lookup_type] % cast_sql) except KeyError: pass if lookup_type == 'in': in_string = ','.join(['%s' for id in value]) if in_string: - return '%s%s IN (%s)' % (table_prefix, field_name, in_string) + return '%s IN (%s)' % (field_sql, in_string) else: raise EmptyResultSet elif lookup_type in ('range', 'year'): - return '%s%s BETWEEN %%s AND %%s' % (table_prefix, field_name) + return '%s BETWEEN %%s AND %%s' % field_sql elif lookup_type in ('month', 'day'): - return "%s = %%s" % backend.get_date_extract_sql(lookup_type, table_prefix + field_name) + return "%s = %%s" % connection.ops.date_extract_sql(lookup_type, field_sql) elif lookup_type == 'isnull': - return "%s%s IS %sNULL" % (table_prefix, field_name, (not value and 'NOT ' or '')) + return "%s IS %sNULL" % (field_sql, (not value and 'NOT ' or '')) elif lookup_type == 'search': - return backend.get_fulltext_search_sql(table_prefix + field_name) + return connection.ops.fulltext_search_sql(field_sql) elif lookup_type in ('regex', 'iregex'): if settings.DATABASE_ENGINE == 'oracle': if lookup_type == 'regex': match_option = 'c' else: match_option = 'i' - return "REGEXP_LIKE(%s%s, %s, '%s')" % (table_prefix, field_name, cast_sql, match_option) + return "REGEXP_LIKE(%s, %s, '%s')" % (field_sql, cast_sql, match_option) else: raise NotImplementedError raise TypeError, "Got invalid lookup_type: %s" % repr(lookup_type) @@ -846,7 +852,7 @@ def fill_table_cache(opts, select, tables, where, old_prefix, cache_tables_seen, if max_depth and cur_depth > max_depth: return None - qn = backend.quote_name + qn = connection.ops.quote_name for f in opts.fields: if f.rel and not f.null: db_table = f.rel.to._meta.db_table @@ -944,7 +950,7 @@ def field_choices(field_list, related_query): return choices def lookup_inner(path, lookup_type, value, opts, table, column): - qn = backend.quote_name + qn = connection.ops.quote_name joins, where, params = SortedDict(), [], [] current_opts = opts current_table = table @@ -1071,6 +1077,7 @@ def lookup_inner(path, lookup_type, value, opts, table, column): else: # No elements left in path. Current element is the element on which # the search is being performed. + db_type = None if join_required: # Last query term is a RelatedObject @@ -1100,15 +1107,16 @@ def lookup_inner(path, lookup_type, value, opts, table, column): else: # Last query term was a normal field. column = field.column + db_type = field.db_type() - where.append(get_where_clause(lookup_type, current_table + '.', column, value)) + where.append(get_where_clause(lookup_type, current_table + '.', column, value, db_type)) params.extend(field.get_db_prep_lookup(lookup_type, value)) return joins, where, params def delete_objects(seen_objs): "Iterate through a list of seen classes, and remove any instances that are referred to" - qn = backend.quote_name + qn = connection.ops.quote_name ordered_classes = seen_objs.keys() ordered_classes.reverse() diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index bd21212ff6..11cd04653f 100644 --- a/django/template/defaulttags.py +++ b/django/template/defaulttags.py @@ -676,16 +676,16 @@ def do_if(parser, token): tag, because the order of logic would be ambigous. For example, this is invalid:: - {% if athlete_list and coach_list or cheerleader_list %} + {% if athlete_list and coach_list or cheerleader_list %} - If you need to combine and and or to do advanced logic, just use + If you need to combine ``and`` and ``or`` to do advanced logic, just use nested if tags. For example: - {% if athlete_list %} - {% if coach_list or cheerleader_list %} - We have athletes, and either coaches or cheerleaders! + {% if athlete_list %} + {% if coach_list or cheerleader_list %} + We have athletes, and either coaches or cheerleaders! + {% endif %} {% endif %} - {% endif %} """ bits = token.contents.split() del bits[0] diff --git a/django/template/loader_tags.py b/django/template/loader_tags.py index 4439e0b010..19f368711c 100644 --- a/django/template/loader_tags.py +++ b/django/template/loader_tags.py @@ -140,7 +140,7 @@ def do_extends(parser, token): This tag may be used in two ways: ``{% extends "base" %}`` (with quotes) uses the literal value "base" as the name of the parent template to extend, or ``{% extends variable %}`` uses the value of ``variable`` as either the - name of the parent template to extend (if it evaluates to a string,) or as + name of the parent template to extend (if it evaluates to a string) or as the parent tempate itelf (if it evaluates to a Template object). """ bits = token.contents.split() diff --git a/django/test/utils.py b/django/test/utils.py index bc53ef4bfb..6edd186b54 100644 --- a/django/test/utils.py +++ b/django/test/utils.py @@ -1,6 +1,6 @@ import sys, time from django.conf import settings -from django.db import connection, backend, get_creation_module +from django.db import connection, get_creation_module from django.core import mail from django.core.management import call_command from django.dispatch import dispatcher @@ -97,7 +97,7 @@ def create_test_db(verbosity=1, autoclobber=False): # If the database backend wants to create the test DB itself, let it creation_module = get_creation_module() if hasattr(creation_module, "create_test_db"): - creation_module.create_test_db(settings, connection, backend, verbosity, autoclobber) + creation_module.create_test_db(settings, connection, verbosity, autoclobber) return if verbosity >= 1: @@ -118,13 +118,15 @@ def create_test_db(verbosity=1, autoclobber=False): else: TEST_DATABASE_NAME = TEST_DATABASE_PREFIX + settings.DATABASE_NAME + qn = connection.ops.quote_name + # Create the test database and connect to it. We need to autocommit # if the database supports it because PostgreSQL doesn't allow # CREATE/DROP DATABASE statements within transactions. cursor = connection.cursor() _set_autocommit(connection) try: - cursor.execute("CREATE DATABASE %s %s" % (backend.quote_name(TEST_DATABASE_NAME), suffix)) + cursor.execute("CREATE DATABASE %s %s" % (qn(TEST_DATABASE_NAME), suffix)) except Exception, e: sys.stderr.write("Got an error creating the test database: %s\n" % e) if not autoclobber: @@ -133,10 +135,10 @@ def create_test_db(verbosity=1, autoclobber=False): try: if verbosity >= 1: print "Destroying old test database..." - cursor.execute("DROP DATABASE %s" % backend.quote_name(TEST_DATABASE_NAME)) + cursor.execute("DROP DATABASE %s" % qn(TEST_DATABASE_NAME)) if verbosity >= 1: print "Creating test database..." - cursor.execute("CREATE DATABASE %s %s" % (backend.quote_name(TEST_DATABASE_NAME), suffix)) + cursor.execute("CREATE DATABASE %s %s" % (qn(TEST_DATABASE_NAME), suffix)) except Exception, e: sys.stderr.write("Got an error recreating the test database: %s\n" % e) sys.exit(2) @@ -163,7 +165,7 @@ def destroy_test_db(old_database_name, verbosity=1): # If the database wants to drop the test DB itself, let it creation_module = get_creation_module() if hasattr(creation_module, "destroy_test_db"): - creation_module.destroy_test_db(settings, connection, backend, old_database_name, verbosity) + creation_module.destroy_test_db(settings, connection, old_database_name, verbosity) return # Unless we're using SQLite, remove the test database to clean up after @@ -180,5 +182,5 @@ def destroy_test_db(old_database_name, verbosity=1): cursor = connection.cursor() _set_autocommit(connection) time.sleep(1) # To avoid "database is being accessed by other users" errors. - cursor.execute("DROP DATABASE %s" % backend.quote_name(TEST_DATABASE_NAME)) + cursor.execute("DROP DATABASE %s" % connection.ops.quote_name(TEST_DATABASE_NAME)) connection.close() diff --git a/docs/contributing.txt b/docs/contributing.txt index cce54fa485..b0df62fe99 100644 --- a/docs/contributing.txt +++ b/docs/contributing.txt @@ -286,7 +286,7 @@ Please follow these coding standards when writing code for inclusion in Django: * Mark all strings for internationalization; see the `i18n documentation`_ for details. - * In docstrings, use "action words," like so:: + * In docstrings, use "action words" such as:: def foo(): """ diff --git a/docs/install.txt b/docs/install.txt index 1b2919f1bc..082000149f 100644 --- a/docs/install.txt +++ b/docs/install.txt @@ -48,8 +48,8 @@ Get your database running If you plan to use Django's database API functionality, you'll need to make sure a database server is running. Django works with PostgreSQL_, -MySQL_, Oracle_ and SQLite_ (the latter doesn't require a separate server to -be running). +MySQL_, Oracle_ and SQLite_ (although SQLite doesn't require a separate server +to be running). Additionally, you'll need to make sure your Python database bindings are installed. diff --git a/docs/man/compile-messages.1 b/docs/man/compile-messages.1 new file mode 100644 index 0000000000..d26a94aca7 --- /dev/null +++ b/docs/man/compile-messages.1 @@ -0,0 +1,40 @@ +.TH "compile-messages.py" "1" "August 2007" "Django Project" "" +.SH "NAME" +compile-messages.py \- Internationalization utility for the Django +web framework +.SH "SYNOPSIS" +.B compile-messages.py \fR[-l ] + +.SH "DESCRIPTION" +A Django-customised wrapper around gettext's \fBmsgfmt\fR command. Generates +binary message catalogs (.mo files) from textual translation descriptions (.po +files). +.sp +The script should be invoked after running +.BI make-messages.py, +in the same directory from which +.BI make-messages.py +was invoked. + +.SH "OPTIONS" +.TP +.I \-l +Compile the message catalogs for a specific locale. If this option is omitted, +all message catalogs are (re-)compiled. + +.SH "SEE ALSO" +The man page for +.BI msgfmt +from the GNU gettext utilities, and the internationalization documentation +for Django: +.sp +.I http://www.djangoproject.com/documentation/i18n/ + +.SH "AUTHORS/CREDITS" +Originally developed at World Online in Lawrence, Kansas, USA. Refer to the +AUTHORS file in the Django distribution for contributors. + +.SH "LICENSE" +New BSD license. For the full license text refer to the LICENSE file in the +Django distribution. + diff --git a/docs/man/daily_cleanup.1 b/docs/man/daily_cleanup.1 new file mode 100644 index 0000000000..9186dd67d6 --- /dev/null +++ b/docs/man/daily_cleanup.1 @@ -0,0 +1,34 @@ +.TH "daily_cleanup.py" "1" "August 2007" "Django Project" "" +.SH "NAME" +daily_cleanup.py \- Database clean-up for the Django web framework +.SH "SYNOPSIS" +.B daily_cleanup.py + +.SH "DESCRIPTION" +Removes stale session data from a Django database. This means, any session data +which has an expiry date prior to the date the script is run. +.sp +The script can be run manually or can be scheduled to run at regular +intervals as a +.BI cron +job. + +.SH "ENVIRONMENT" +.TP +.I DJANGO_SETTINGS_MODULE +This environment variable defines the settings module to be read. +It should be in Python-import form, e.g. "myproject.settings". + +.SH "SEE ALSO" +The sessions documentation: +.sp +.I http://www.djangoproject.com/documentation/sessions/ + +.SH "AUTHORS/CREDITS" +Originally developed at World Online in Lawrence, Kansas, USA. Refer to the +AUTHORS file in the Django distribution for contributors. + +.SH "LICENSE" +New BSD license. For the full license text refer to the LICENSE file in the +Django distribution. + diff --git a/docs/man/gather_profile_stats.1 b/docs/man/gather_profile_stats.1 new file mode 100644 index 0000000000..5ff13d8e69 --- /dev/null +++ b/docs/man/gather_profile_stats.1 @@ -0,0 +1,26 @@ +.TH "gather_profile_stats.py" "1" "August 2007" "Django Project" "" +.SH "NAME" +gather_profile_stats.py \- Performance analysis tool for the Django web +framework +.SH "SYNOPSIS" +.B python gather_profile_stats.py +.I + +.SH "DESCRIPTION" +This utility script aggregates profiling logs generated using Python's +hotshot profiler. The sole command-line argument is the full path to the +directory containing the profiling logfiles. + +.SH "SEE ALSO" +Discussion of profiling Django applications on the Django project's wiki: +.sp +.I http://www.djangoproject.com/wiki/ProfilingDjango + +.SH "AUTHORS/CREDITS" +Originally developed at World Online in Lawrence, Kansas, USA. Refer to the +AUTHORS file in the Django distribution for contributors. + +.SH "LICENSE" +New BSD license. For the full license text refer to the LICENSE file in the +Django distribution. + diff --git a/docs/man/make-messages.1 b/docs/man/make-messages.1 new file mode 100644 index 0000000000..b8c83dcff5 --- /dev/null +++ b/docs/man/make-messages.1 @@ -0,0 +1,62 @@ +.TH "make-messages.py" "1" "August 2007" "Django Project" "" +.SH "NAME" +make-messages.py \- Internationalization utility for the Django +web framework +.SH "SYNOPSIS" +.B make-messages.py\fR [\-a] [\-v] [\-l ] [\-d ] + +.SH "DESCRIPTION" +This script creates or updates one or more message files for a Django app, +a Django project or the Django framework itself. It should be run from one +of three places: the root directory of a Django app; the root directory +of a Django project; or the root django directory (the one in your PYTHONPATH, +not the root of a Subversion checkout). +.sp +The script will run over the source tree of an application, project or Django +itself (depending on where it is invoked), pulling out all strings marked for +translation and creating or updating a standard PO-format message file for the +specified language. Refer to Django's internationalization documentation for +details of where this file is created. +.sp +The \fI\-a\fR and \fI\-l\fR options are used to control whether message +catalogs are created for all locales, or just a single one. + +.SH "OPTIONS" +.TP +.I \-a +Run make-messages for all locales specified in the Django settings file. Cannot +be used in conjuntion with \fI\-l\fR. +.TP +.I \-d +Specifies the translation domain to use. Valid domains are \fIdjango\fR or +\fIdjangojs\fR, depending on whether you wish to generate translation strings +for the Python or JavaScript components of your app, your project or the +framework itself. The default domain is \fIdjango\fR. +.TP +.I \-l +Extract messages for a particular locale. +.TP +.I \-v +Run verbosely. + +.SH "ENVIRONMENT" +.TP +.I DJANGO_SETTINGS_MODULE +This environment variable defines the settings module to be read. +It should be in Python-import form, e.g. "myproject.settings". + +.SH "SEE ALSO" +The Django internationalization documentation: +.sp +.I http://www.djangoproject.com/documentation/i18n/ +.sp +The PO file format is documented in the GNU gettext documentation. + +.SH "AUTHORS/CREDITS" +Originally developed at World Online in Lawrence, Kansas, USA. Refer to the +AUTHORS file in the Django distribution for contributors. + +.SH "LICENSE" +New BSD license. For the full license text refer to the LICENSE file in the +Django distribution. + diff --git a/docs/newforms.txt b/docs/newforms.txt index db4e6596e4..19820bc5eb 100644 --- a/docs/newforms.txt +++ b/docs/newforms.txt @@ -1422,12 +1422,12 @@ keep it simple and assume e-mail validation is contained in a function called class MultiEmailField(forms.Field): def clean(self, value): + if not value: + raise forms.ValidationError('Enter at least one e-mail address.') emails = value.split(',') for email in emails: if not is_valid_email(email): raise forms.ValidationError('%s is not a valid e-mail address.' % email) - if not emails: - raise forms.ValidationError('Enter at least one e-mail address.') return emails Let's alter the ongoing ``ContactForm`` example to demonstrate how you'd use diff --git a/docs/templates.txt b/docs/templates.txt index 0d53c281d1..6cebd3b7bd 100644 --- a/docs/templates.txt +++ b/docs/templates.txt @@ -937,6 +937,12 @@ such as this:: The template tag will output the string ``/clients/client/123/``. +**New in development version:** If you're using `named URL patterns`_, +you can refer to the name of the pattern in the ``url`` tag instead of +using the path to the view. + +.. _named URL patterns: ../url_dispatch/#naming-url-patterns + widthratio ~~~~~~~~~~ @@ -1326,12 +1332,17 @@ urlize Converts URLs in plain text into clickable links. +Note that if ``urlize`` is applied to text that already contains HTML markup, +things won't work as expected. Apply this filter only to *plain* text. + urlizetrunc ~~~~~~~~~~~ Converts URLs into clickable links, truncating URLs longer than the given character limit. +As with urlize_, this filter should only be applied to *plain* text. + **Argument:** Length to truncate URLs to wordcount diff --git a/docs/templates_python.txt b/docs/templates_python.txt index 117656762f..261eaedf74 100644 --- a/docs/templates_python.txt +++ b/docs/templates_python.txt @@ -277,7 +277,7 @@ Subclassing Context: RequestContext Django comes with a special ``Context`` class, ``django.template.RequestContext``, that acts slightly differently than -the normal ``django.template.Context``. The first difference is that takes +the normal ``django.template.Context``. The first difference is that it takes an `HttpRequest object`_ as its first argument. For example:: c = RequestContext(request, { diff --git a/docs/tutorial04.txt b/docs/tutorial04.txt index 553a76e9b1..bd16fa2924 100644 --- a/docs/tutorial04.txt +++ b/docs/tutorial04.txt @@ -193,7 +193,7 @@ Change it like so:: urlpatterns = patterns('', (r'^$', 'django.views.generic.list_detail.object_list', info_dict), (r'^(?P\d+)/$', 'django.views.generic.list_detail.object_detail', info_dict), - (r'^(?P\d+)/results/$', 'django.views.generic.list_detail.object_detail', dict(info_dict, template_name='polls/results.html'), 'poll_results'), + url(r'^(?P\d+)/results/$', 'django.views.generic.list_detail.object_detail', dict(info_dict, template_name='polls/results.html'), 'poll_results'), (r'^(?P\d+)/vote/$', 'mysite.polls.views.vote'), ) @@ -209,11 +209,14 @@ objects" and "display a detail page for a particular type of object." from the URL to be called ``"object_id"``, so we've changed ``poll_id`` to ``object_id`` for the generic views. - * We've added a name, ``poll_results``, to the results view so that we have - a way to refer to its URL later on (see `naming URL patterns`_ for more on - named patterns). - + * We've added a name, ``poll_results``, to the results view so that we + have a way to refer to its URL later on (see the documentation about + `naming URL patterns`_ for information). We're also using the `url()`_ + function from ``django.conf.urls.defaults`` here. It's a good habit to + use ``url()`` when you are providing a pattern name like this. + .. _naming URL patterns: ../url_dispatch/#naming-url-patterns +.. _url(): ../url_dispatch/#url By default, the ``object_detail`` generic view uses a template called ``/_detail.html``. In our case, it'll use the template diff --git a/docs/url_dispatch.txt b/docs/url_dispatch.txt index f9723a6a89..76a4e1b5f0 100644 --- a/docs/url_dispatch.txt +++ b/docs/url_dispatch.txt @@ -204,8 +204,16 @@ optional extra arguments dictionary. For example:: ... ) +This function takes five arguments, most of which are optional:: + + url(regex, view, kwargs=None, name=None, prefix='') + See `Naming URL patterns`_ for why the ``name`` parameter is useful. +The ``prefix`` parameter has the same meaning as the first argument to +``patterns()`` and is only relevant when you're passing a string as the +``view`` parameter. + handler404 ---------- @@ -512,7 +520,7 @@ view:: This is completely valid, but it leads to problems when you try to do reverse URL matching (through the ``permalink()`` decorator or the ``{% url %}`` -template tag). Continuing this example, if you wanted to retrieve the URL for +`template tag`_). Continuing this example, if you wanted to retrieve the URL for the ``archive`` view, Django's reverse URL matcher would get confused, because *two* URLpatterns point at that view. @@ -552,14 +560,16 @@ not restricted to valid Python names. name, will decrease the chances of collision. We recommend something like ``myapp-comment`` instead of ``comment``. +.. _template tag: ../templates/#url + Utility methods =============== reverse() --------- -If you need to use something similar to the ``{% url %}`` template tag in your -code, Django provides the ``django.core.urlresolvers.reverse()``. The +If you need to use something similar to the ``{% url %}`` `template tag`_ in +your code, Django provides the ``django.core.urlresolvers.reverse()``. The ``reverse()`` function has the following signature:: reverse(viewname, urlconf=None, args=None, kwargs=None) diff --git a/tests/modeltests/test_client/models.py b/tests/modeltests/test_client/models.py index 98b6a808a1..e1f3987847 100644 --- a/tests/modeltests/test_client/models.py +++ b/tests/modeltests/test_client/models.py @@ -247,6 +247,7 @@ class ClientTest(TestCase): self.failIf(login) def test_logout(self): + "Request a logout after logging in" # Log in self.client.login(username='testclient', password='password') diff --git a/tests/regressiontests/forms/localflavor.py b/tests/regressiontests/forms/localflavor.py index 132a61c30d..a896013b0d 100644 --- a/tests/regressiontests/forms/localflavor.py +++ b/tests/regressiontests/forms/localflavor.py @@ -1390,5 +1390,60 @@ u'\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n' -""" +# PLVoivodeshipSelect ########################################################## +>>> from django.contrib.localflavor.pl.forms import PLVoivodeshipSelect +>>> f = PLVoivodeshipSelect() +>>> f.render('voivodeships','pomerania') +u'' + +# PLAdministrativeUnitSelect ########################################################## + +>>> from django.contrib.localflavor.pl.forms import PLAdministrativeUnitSelect +>>> f = PLAdministrativeUnitSelect() +>>> f.render('administrativeunit','katowice') +u'' + +# PLPostalCodeField ############################################################## + +>>> from django.contrib.localflavor.pl.forms import PLPostalCodeField +>>> f = PLPostalCodeField() +>>> f.clean('43--434') +Traceback (most recent call last): +... +ValidationError: [u'Enter a postal code in the format XX-XXX.'] +>>> f.clean('41-403') +u'41-403' + +# PLTaxNumberField ############################################################### + +>>> from django.contrib.localflavor.pl.forms import PLTaxNumberField +>>> f = PLTaxNumberField() +>>> f.clean('43-343-234-323') +Traceback (most recent call last): +... +ValidationError: [u'Enter a tax number field (NIP) in the format XXX-XXX-XX-XX or XX-XX-XXX-XXX.'] +>>> f.clean('43-34-234-323') +u'43-34-234-323' +>>> f.clean('433-344-24-23') +u'433-344-24-23' + +# PLNationalIdentificationNumberField ############################################ + +>>> from django.contrib.localflavor.pl.forms import PLNationalIdentificationNumberField +>>> f = PLNationalIdentificationNumberField() +>>> f.clean('80071610614') +u'80071610614' +>>> f.clean('80071610610') +Traceback (most recent call last): +... +ValidationError: [u'Wrong checksum for the National Identification Number.'] +>>> f.clean('80') +Traceback (most recent call last): +... +ValidationError: [u'National Identification Number consists of 11 digits.'] +>>> f.clean('800716106AA') +Traceback (most recent call last): +... +ValidationError: [u'National Identification Number consists of 11 digits.'] +""" diff --git a/tests/regressiontests/string_lookup/models.py b/tests/regressiontests/string_lookup/models.py index 6a341070a4..12ebd0cf07 100644 --- a/tests/regressiontests/string_lookup/models.py +++ b/tests/regressiontests/string_lookup/models.py @@ -36,6 +36,13 @@ class Base(models.Model): def __unicode__(self): return "Base %s" % self.name +class Article(models.Model): + name = models.CharField(maxlength = 50) + text = models.TextField() + + def __str__(self): + return "Article %s" % self.name + __test__ = {'API_TESTS': ur""" # Regression test for #1661 and #1662: Check that string form referencing of # models works, both as pre and post reference, on all RelatedField types. @@ -82,4 +89,13 @@ __test__ = {'API_TESTS': ur""" # We can also do the above query using UTF-8 strings. >>> Foo.objects.get(friend__contains='\xc3\xa7') + +# Regression tests for #5087: make sure we can perform queries on TextFields. +>>> a = Article(name='Test', text='The quick brown fox jumps over the lazy dog.') +>>> a.save() +>>> Article.objects.get(text__exact='The quick brown fox jumps over the lazy dog.') + + +>>> Article.objects.get(text__contains='quick brown fox') + """}