summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdrian Holovaty <adrian@holovaty.com>2007-08-20 05:00:52 +0000
committerAdrian Holovaty <adrian@holovaty.com>2007-08-20 05:00:52 +0000
commitc49f2d21ea055ae3c9b66cfd62a9f527a2f6d3b5 (patch)
tree66a8709e51ae865b2a115a5f3753f2460d9b281c
parent0ae4e102dc60de49d8f1457373458082f6639086 (diff)
newforms-admin: Merged to [5983]
git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@5984 bcc190cf-cafb-0310-a4f2-bffc1f526a37
-rw-r--r--AUTHORS2
-rw-r--r--django/conf/global_settings.py3
-rw-r--r--django/conf/locale/es/LC_MESSAGES/django.mobin53852 -> 53873 bytes
-rw-r--r--django/conf/locale/es/LC_MESSAGES/django.po9
-rw-r--r--django/conf/project_template/settings.py4
-rw-r--r--django/contrib/admin/templates/admin/index.html2
-rw-r--r--django/contrib/auth/models.py17
-rw-r--r--django/contrib/contenttypes/generic.py10
-rw-r--r--django/contrib/databrowse/datastructures.py23
-rw-r--r--django/contrib/databrowse/plugins/calendars.py10
-rw-r--r--django/contrib/databrowse/templates/databrowse/base.html5
-rw-r--r--django/contrib/databrowse/templates/databrowse/base_site.html1
-rw-r--r--django/contrib/databrowse/templates/databrowse/calendar_day.html4
-rw-r--r--django/contrib/databrowse/templates/databrowse/calendar_homepage.html2
-rw-r--r--django/contrib/databrowse/templates/databrowse/calendar_main.html2
-rw-r--r--django/contrib/databrowse/templates/databrowse/calendar_month.html4
-rw-r--r--django/contrib/databrowse/templates/databrowse/calendar_year.html2
-rw-r--r--django/contrib/databrowse/templates/databrowse/choice_detail.html2
-rw-r--r--django/contrib/databrowse/templates/databrowse/choice_list.html2
-rw-r--r--django/contrib/databrowse/templates/databrowse/fieldchoice_detail.html4
-rw-r--r--django/contrib/databrowse/templates/databrowse/fieldchoice_homepage.html2
-rw-r--r--django/contrib/databrowse/templates/databrowse/fieldchoice_list.html2
-rw-r--r--django/contrib/databrowse/templates/databrowse/homepage.html2
-rw-r--r--django/contrib/databrowse/templates/databrowse/model_detail.html4
-rw-r--r--django/contrib/databrowse/templates/databrowse/object_detail.html2
-rw-r--r--django/contrib/localflavor/pl/__init__.py0
-rw-r--r--django/contrib/localflavor/pl/forms.py84
-rw-r--r--django/contrib/localflavor/pl/pl_administrativeunits.py385
-rw-r--r--django/contrib/localflavor/pl/pl_voivodeships.py24
-rw-r--r--django/core/management/base.py6
-rw-r--r--django/core/management/commands/createcachetable.py11
-rw-r--r--django/core/management/commands/loaddata.py4
-rw-r--r--django/core/management/commands/sqlsequencereset.py4
-rw-r--r--django/core/management/commands/syncdb.py8
-rw-r--r--django/core/management/sql.py134
-rw-r--r--django/db/backends/__init__.py218
-rw-r--r--django/db/backends/ado_mssql/base.py191
-rw-r--r--django/db/backends/dummy/base.py35
-rw-r--r--django/db/backends/mysql/base.py252
-rw-r--r--django/db/backends/mysql/introspection.py3
-rw-r--r--django/db/backends/mysql_old/base.py252
-rw-r--r--django/db/backends/mysql_old/introspection.py3
-rw-r--r--django/db/backends/oracle/base.py806
-rw-r--r--django/db/backends/oracle/creation.py19
-rw-r--r--django/db/backends/oracle/introspection.py4
-rw-r--r--django/db/backends/postgresql/base.py242
-rw-r--r--django/db/backends/postgresql/introspection.py4
-rw-r--r--django/db/backends/postgresql/operations.py109
-rw-r--r--django/db/backends/postgresql_psycopg2/base.py228
-rw-r--r--django/db/backends/postgresql_psycopg2/introspection.py4
-rw-r--r--django/db/backends/sqlite3/base.py206
-rw-r--r--django/db/backends/sqlite3/introspection.py4
-rw-r--r--django/db/backends/util.py27
-rw-r--r--django/db/models/__init__.py2
-rw-r--r--django/db/models/base.py59
-rw-r--r--django/db/models/fields/related.py9
-rw-r--r--django/db/models/loading.py273
-rw-r--r--django/db/models/options.py10
-rw-r--r--django/db/models/query.py110
-rw-r--r--django/template/defaulttags.py12
-rw-r--r--django/template/loader_tags.py2
-rw-r--r--django/test/utils.py16
-rw-r--r--docs/contributing.txt2
-rw-r--r--docs/install.txt4
-rw-r--r--docs/man/compile-messages.140
-rw-r--r--docs/man/daily_cleanup.134
-rw-r--r--docs/man/gather_profile_stats.126
-rw-r--r--docs/man/make-messages.162
-rw-r--r--docs/newforms.txt4
-rw-r--r--docs/templates.txt11
-rw-r--r--docs/templates_python.txt2
-rw-r--r--docs/tutorial04.txt13
-rw-r--r--docs/url_dispatch.txt16
-rw-r--r--tests/modeltests/test_client/models.py1
-rw-r--r--tests/regressiontests/forms/localflavor.py57
-rw-r--r--tests/regressiontests/string_lookup/models.py16
76 files changed, 2322 insertions, 1846 deletions
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 <farcepest@gmail.com>
Clint Ecker
+ Nick Efford <nick@efford.org>
eibaan@gmail.com
enlight
Enrico <rico.bl@gmail.com>
@@ -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 <slawek dot mikula at gmail dot com>
mitakummaa@gmail.com
mmarshall
Andreas Mock <andreas.mock@web.de>
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 7beda77032..be8be19162 100644
--- a/django/conf/locale/es/LC_MESSAGES/django.mo
+++ b/django/conf/locale/es/LC_MESSAGES/django.mo
Binary files differ
diff --git a/django/conf/locale/es/LC_MESSAGES/django.po b/django/conf/locale/es/LC_MESSAGES/django.po
index 424cce900d..5f11644cfa 100644
--- a/django/conf/locale/es/LC_MESSAGES/django.po
+++ b/django/conf/locale/es/LC_MESSAGES/django.po
@@ -6,7 +6,7 @@ msgid ""
msgstr ""
"Project-Id-Version: django\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2007-08-13 11:29-0400\n"
+"POT-Creation-Date: 2007-08-17 15:35-0400\n"
"PO-Revision-Date: 2007-07-14 13:00-0500\n"
"Last-Translator: Mario Gonzalez <gonzalemario @t gmail.com>\n"
"Language-Team: Castellano <Django-I18N@googlegroups.com>\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 %}
<ul class="actionlist">
{% for entry in admin_log %}
- <li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">{% if not entry.is_deletion %}<a href="{{ entry.get_admin_url }}">{% endif %}{{ entry.object_repr|escape }}{% if not entry.is_deletion %}</a>{% endif %}<br /><span class="mini quiet">{{ entry.content_type.name|capfirst|escape }}</span></li>
+ <li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">{% if not entry.is_deletion %}<a href="{{ entry.get_admin_url }}">{% endif %}{{ entry.object_repr|escape }}{% if not entry.is_deletion %}</a>{% endif %}<br /><span class="mini quiet">{% filter capfirst|escape %}{% trans entry.content_type.name %}{% endfilter %}</span></li>
{% endfor %}
</ul>
{% 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 @@
<html xmlns="http://www.w3.org/1999/xhtml" lang="{{ LANGUAGE_CODE }}" xml:lang="{{ LANGUAGE_CODE }}" {% if LANGUAGE_BIDI %}dir="rtl"{% endif %}>
<head>
<title>{% block title %}{% endblock %}</title>
+{% block style %}
<style type="text/css">
* { margin:0; padding:0; }
body { background:#eee; color:#333; font:76%/1.6 "Lucida Grande","Bitstream Vera Sans",Verdana,sans-serif; }
@@ -48,9 +49,11 @@ p { margin:0.5em 0 1em 0; }
/* CONTENT */
#content { background:#fff; border-bottom:1px solid #ddd; padding:0 20px; }
</style>
+{% endblock %}
+{% block extrahead %}{% endblock %}
</head>
<body id="{% block bodyid %}page{% endblock %}">
-<div id="header"><a href="{{ root_url }}">Databrowse</a></div>
+<div id="header"><a href="{{ root_url }}">{% block databrowse_title %}Databrowse{% endblock %}</a></div>
<div id="content">
{% block content %}{% endblock %}
</div>
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 @@
<div id="breadcrumbs"><a href="{{ root_url }}">Home</a> / <a href="{{ model.url }}">{{ model.verbose_name_plural|capfirst }}</a> / <a href="../../../../">Calendars</a> / <a href="../../../">By {{ field.verbose_name }}</a> / <a href="../../">{{ day.year }}</a> / <a href="../">{{ day|date:"F" }}</a> / {{ day.day }}</div>
-<h1>{{ model.verbose_name_plural|capfirst }} with {{ field.verbose_name }} on {{ day|date:"F j, Y" }}</h1>
+<h1>{{ 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" }}</h1>
<ul class="objectlist">
{% 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 @@
<div id="breadcrumbs"><a href="{{ root_url }}">Home</a> / <a href="{{ model.url }}">{{ model.verbose_name_plural|capfirst }}</a> / <a href="../../../">Calendars</a> / <a href="../../">By {{ field.verbose_name }}</a> / <a href="../">{{ month.year }}</a> / {{ month|date:"F" }}</div>
-<h1>{{ model.verbose_name_plural|capfirst }} with {{ field.verbose_name }} in {{ month|date:"F Y" }}</h1>
+<h1>{{ 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" }}</h1>
<ul class="objectlist">
{% 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 @@
<div id="breadcrumbs"><a href="{{ root_url }}">Home</a> / <a href="{{ model.url }}">{{ model.verbose_name_plural|capfirst }}</a> / <a href="../../">Fields</a> / <a href="../">By {{ field.field.verbose_name|escape }}</a> / {{ value|escape }}</div>
-<h1>{{ model.verbose_name_plural|capfirst|escape }} with {{ field.field.verbose_name|escape }} {{ value|escape }}</h1>
+<h1>{{ 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 }}</h1>
<ul class="objectlist">
{% 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 @@
<div id="breadcrumbs"><a href="{{ root_url }}">Home</a> / {{ model.verbose_name_plural|capfirst }}</div>
-<h1>{{ model.verbose_name_plural|capfirst }}</h1>
+<h1>{{ model.objects.count }} {% if model.objects.count|pluralize %}{{ model.verbose_name_plural }}{% else %}{{ model.verbose_name }}{% endif %}</h1>
{{ 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
--- /dev/null
+++ b/django/contrib/localflavor/pl/__init__.py
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 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])
-class DatabaseWrapper(local):
- def __init__(self, **kwargs):
- self.connection = None
- self.queries = []
- self.options = kwargs
+ # 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 <table>_<column>_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 <table>_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 <table>_<column>_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 <table>_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 <table>_<column>_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 <table>_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?
+class AppCache(object):
+ """
+ A cache that stores installed applications and their models. Used to
+ provide reverse-relations and for app introspection (e.g. admin).
+ """
+ # 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_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
+ # Mapping of app_labels to a dictionary of model names to model code.
+ app_models = {},
-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
+ # Mapping of app_labels to errors raised when trying to import the app.
+ app_errors = {},
-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
+ # -- Everything below here is only used when populating the cache --
+ loaded = False,
+ handled = {},
+ postponed = [],
+ nesting_level = 0,
+ write_lock = threading.RLock(),
+ )
-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 __init__(self):
+ self.__dict__ = self.__shared_state
-def get_models(app_mod=None):
- """
- Given a module containing models, returns a list of the models. Otherwise
- returns a list of all installed models.
- """
- 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
+ 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 get_model(app_label, model_name, seed_cache=True):
- """
- Returns the model matching the given app_label and case-insensitive
- model_name.
+ 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
- Returns None if no model is found.
- """
- if seed_cache:
- get_apps()
- try:
- model_dict = _app_models[app_label]
- except KeyError:
- return None
+ def app_cache_ready(self):
+ """
+ Returns true if the model cache is fully populated.
- try:
- return model_dict[model_name.lower()]
- except KeyError:
- return None
+ 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 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 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 <locale>]
+
+.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 <locale>
+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 <path>
+
+.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 <locale>] [\-d <domain>]
+
+.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 <domain>
+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 <locale>
+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<object_id>\d+)/$', 'django.views.generic.list_detail.object_detail', info_dict),
- (r'^(?P<object_id>\d+)/results/$', 'django.views.generic.list_detail.object_detail', dict(info_dict, template_name='polls/results.html'), 'poll_results'),
+ url(r'^(?P<object_id>\d+)/results/$', 'django.views.generic.list_detail.object_detail', dict(info_dict, template_name='polls/results.html'), 'poll_results'),
(r'^(?P<poll_id>\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
``<app name>/<model name>_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'<select name="regions">\n<option value="BB">Banska Bystrica region</option>\n<
>>> w.render('Districts', 'RK')
u'<select name="Districts">\n<option value="BB">Banska Bystrica</option>\n<option value="BS">Banska Stiavnica</option>\n<option value="BJ">Bardejov</option>\n<option value="BN">Banovce nad Bebravou</option>\n<option value="BR">Brezno</option>\n<option value="BA1">Bratislava I</option>\n<option value="BA2">Bratislava II</option>\n<option value="BA3">Bratislava III</option>\n<option value="BA4">Bratislava IV</option>\n<option value="BA5">Bratislava V</option>\n<option value="BY">Bytca</option>\n<option value="CA">Cadca</option>\n<option value="DT">Detva</option>\n<option value="DK">Dolny Kubin</option>\n<option value="DS">Dunajska Streda</option>\n<option value="GA">Galanta</option>\n<option value="GL">Gelnica</option>\n<option value="HC">Hlohovec</option>\n<option value="HE">Humenne</option>\n<option value="IL">Ilava</option>\n<option value="KK">Kezmarok</option>\n<option value="KN">Komarno</option>\n<option value="KE1">Kosice I</option>\n<option value="KE2">Kosice II</option>\n<option value="KE3">Kosice III</option>\n<option value="KE4">Kosice IV</option>\n<option value="KEO">Kosice - okolie</option>\n<option value="KA">Krupina</option>\n<option value="KM">Kysucke Nove Mesto</option>\n<option value="LV">Levice</option>\n<option value="LE">Levoca</option>\n<option value="LM">Liptovsky Mikulas</option>\n<option value="LC">Lucenec</option>\n<option value="MA">Malacky</option>\n<option value="MT">Martin</option>\n<option value="ML">Medzilaborce</option>\n<option value="MI">Michalovce</option>\n<option value="MY">Myjava</option>\n<option value="NO">Namestovo</option>\n<option value="NR">Nitra</option>\n<option value="NM">Nove Mesto nad Vahom</option>\n<option value="NZ">Nove Zamky</option>\n<option value="PE">Partizanske</option>\n<option value="PK">Pezinok</option>\n<option value="PN">Piestany</option>\n<option value="PT">Poltar</option>\n<option value="PP">Poprad</option>\n<option value="PB">Povazska Bystrica</option>\n<option value="PO">Presov</option>\n<option value="PD">Prievidza</option>\n<option value="PU">Puchov</option>\n<option value="RA">Revuca</option>\n<option value="RS">Rimavska Sobota</option>\n<option value="RV">Roznava</option>\n<option value="RK" selected="selected">Ruzomberok</option>\n<option value="SB">Sabinov</option>\n<option value="SC">Senec</option>\n<option value="SE">Senica</option>\n<option value="SI">Skalica</option>\n<option value="SV">Snina</option>\n<option value="SO">Sobrance</option>\n<option value="SN">Spisska Nova Ves</option>\n<option value="SL">Stara Lubovna</option>\n<option value="SP">Stropkov</option>\n<option value="SK">Svidnik</option>\n<option value="SA">Sala</option>\n<option value="TO">Topolcany</option>\n<option value="TV">Trebisov</option>\n<option value="TN">Trencin</option>\n<option value="TT">Trnava</option>\n<option value="TR">Turcianske Teplice</option>\n<option value="TS">Tvrdosin</option>\n<option value="VK">Velky Krtis</option>\n<option value="VT">Vranov nad Toplou</option>\n<option value="ZM">Zlate Moravce</option>\n<option value="ZV">Zvolen</option>\n<option value="ZC">Zarnovica</option>\n<option value="ZH">Ziar nad Hronom</option>\n<option value="ZA">Zilina</option>\n</select>'
-"""
+# PLVoivodeshipSelect ##########################################################
+
+>>> from django.contrib.localflavor.pl.forms import PLVoivodeshipSelect
+>>> f = PLVoivodeshipSelect()
+>>> f.render('voivodeships','pomerania')
+u'<select name="voivodeships">\n<option value="lower_silesia">Lower Silesia</option>\n<option value="kuyavia-pomerania">Kuyavia-Pomerania</option>\n<option value="lublin">Lublin</option>\n<option value="lubusz">Lubusz</option>\n<option value="lodz">Lodz</option>\n<option value="lesser_poland">Lesser Poland</option>\n<option value="masovia">Masovia</option>\n<option value="opole">Opole</option>\n<option value="subcarpatia">Subcarpatia</option>\n<option value="podlasie">Podlasie</option>\n<option value="pomerania" selected="selected">Pomerania</option>\n<option value="silesia">Silesia</option>\n<option value="swietokrzyskie">Swietokrzyskie</option>\n<option value="warmia-masuria">Warmia-Masuria</option>\n<option value="greater_poland">Greater Poland</option>\n<option value="west_pomerania">West Pomerania</option>\n</select>'
+
+# PLAdministrativeUnitSelect ##########################################################
+
+>>> from django.contrib.localflavor.pl.forms import PLAdministrativeUnitSelect
+>>> f = PLAdministrativeUnitSelect()
+>>> f.render('administrativeunit','katowice')
+u'<select name="administrativeunit">\n<option value="wroclaw">Wroc\u0142aw</option>\n<option value="jeleniagora">Jelenia G\xf3ra</option>\n<option value="legnica">Legnica</option>\n<option value="boleslawiecki">boles\u0142awiecki</option>\n<option value="dzierzoniowski">dzier\u017coniowski</option>\n<option value="glogowski">g\u0142ogowski</option>\n<option value="gorowski">g\xf3rowski</option>\n<option value="jaworski">jaworski</option>\n<option value="jeleniogorski">jeleniog\xf3rski</option>\n<option value="kamiennogorski">kamiennog\xf3rski</option>\n<option value="klodzki">k\u0142odzki</option>\n<option value="legnicki">legnicki</option>\n<option value="lubanski">luba\u0144ski</option>\n<option value="lubinski">lubi\u0144ski</option>\n<option value="lwowecki">lw\xf3wecki</option>\n<option value="milicki">milicki</option>\n<option value="olesnicki">ole\u015bnicki</option>\n<option value="olawski">o\u0142awski</option>\n<option value="polkowicki">polkowicki</option>\n<option value="strzelinski">strzeli\u0144ski</option>\n<option value="sredzki">\u015bredzki</option>\n<option value="swidnicki">\u015bwidnicki</option>\n<option value="trzebnicki">trzebnicki</option>\n<option value="walbrzyski">wa\u0142brzyski</option>\n<option value="wolowski">wo\u0142owski</option>\n<option value="wroclawski">wroc\u0142awski</option>\n<option value="zabkowicki">z\u0105bkowicki</option>\n<option value="zgorzelecki">zgorzelecki</option>\n<option value="zlotoryjski">z\u0142otoryjski</option>\n<option value="bydgoszcz">Bydgoszcz</option>\n<option value="torun">Toru\u0144</option>\n<option value="wloclawek">W\u0142oc\u0142awek</option>\n<option value="grudziadz">Grudzi\u0105dz</option>\n<option value="aleksandrowski">aleksandrowski</option>\n<option value="brodnicki">brodnicki</option>\n<option value="bydgoski">bydgoski</option>\n<option value="chelminski">che\u0142mi\u0144ski</option>\n<option value="golubsko-dobrzynski">golubsko-dobrzy\u0144ski</option>\n<option value="grudziadzki">grudzi\u0105dzki</option>\n<option value="inowroclawski">inowroc\u0142awski</option>\n<option value="lipnowski">lipnowski</option>\n<option value="mogilenski">mogile\u0144ski</option>\n<option value="nakielski">nakielski</option>\n<option value="radziejowski">radziejowski</option>\n<option value="rypinski">rypi\u0144ski</option>\n<option value="sepolenski">s\u0119pole\u0144ski</option>\n<option value="swiecki">\u015bwiecki</option>\n<option value="torunski">toru\u0144ski</option>\n<option value="tucholski">tucholski</option>\n<option value="wabrzeski">w\u0105brzeski</option>\n<option value="wloclawski">wroc\u0142awski</option>\n<option value="zninski">\u017ani\u0144ski</option>\n<option value="lublin">Lublin</option>\n<option value="biala-podlaska">Bia\u0142a Podlaska</option>\n<option value="chelm">Che\u0142m</option>\n<option value="zamosc">Zamo\u015b\u0107</option>\n<option value="bialski">bialski</option>\n<option value="bilgorajski">bi\u0142gorajski</option>\n<option value="chelmski">che\u0142mski</option>\n<option value="hrubieszowski">hrubieszowski</option>\n<option value="janowski">janowski</option>\n<option value="krasnostawski">krasnostawski</option>\n<option value="krasnicki">kra\u015bnicki</option>\n<option value="lubartowski">lubartowski</option>\n<option value="lubelski">lubelski</option>\n<option value="leczynski">\u0142\u0119czy\u0144ski</option>\n<option value="lukowski">\u0142ukowski</option>\n<option value="opolski">opolski</option>\n<option value="parczewski">parczewski</option>\n<option value="pulawski">pu\u0142awski</option>\n<option value="radzynski">radzy\u0144ski</option>\n<option value="rycki">rycki</option>\n<option value="swidnicki">\u015bwidnicki</option>\n<option value="tomaszowski">tomaszowski</option>\n<option value="wlodawski">w\u0142odawski</option>\n<option value="zamojski">zamojski</option>\n<option value="gorzow-wielkopolski">Gorz\xf3w Wielkopolski</option>\n<option value="zielona-gora">Zielona G\xf3ra</option>\n<option value="gorzowski">gorzowski</option>\n<option value="krosnienski">kro\u015bnie\u0144ski</option>\n<option value="miedzyrzecki">mi\u0119dzyrzecki</option>\n<option value="nowosolski">nowosolski</option>\n<option value="slubicki">s\u0142ubicki</option>\n<option value="strzelecko-drezdenecki">strzelecko-drezdenecki</option>\n<option value="sulecinski">sule\u0144ci\u0144ski</option>\n<option value="swiebodzinski">\u015bwiebodzi\u0144ski</option>\n<option value="wschowski">wschowski</option>\n<option value="zielonogorski">zielonog\xf3rski</option>\n<option value="zaganski">\u017caga\u0144ski</option>\n<option value="zarski">\u017carski</option>\n<option value="lodz">\u0141\xf3d\u017a</option>\n<option value="piotrkow-trybunalski">Piotrk\xf3w Trybunalski</option>\n<option value="skierniewice">Skierniewice</option>\n<option value="belchatowski">be\u0142chatowski</option>\n<option value="brzezinski">brzezi\u0144ski</option>\n<option value="kutnowski">kutnowski</option>\n<option value="laski">\u0142aski</option>\n<option value="leczycki">\u0142\u0119czycki</option>\n<option value="lowicki">\u0142owicki</option>\n<option value="lodzki wschodni">\u0142\xf3dzki wschodni</option>\n<option value="opoczynski">opoczy\u0144ski</option>\n<option value="pabianicki">pabianicki</option>\n<option value="pajeczanski">paj\u0119cza\u0144ski</option>\n<option value="piotrkowski">piotrkowski</option>\n<option value="poddebicki">podd\u0119bicki</option>\n<option value="radomszczanski">radomszcza\u0144ski</option>\n<option value="rawski">rawski</option>\n<option value="sieradzki">sieradzki</option>\n<option value="skierniewicki">skierniewicki</option>\n<option value="tomaszowski">tomaszowski</option>\n<option value="wielunski">wielu\u0144ski</option>\n<option value="wieruszowski">wieruszowski</option>\n<option value="zdunskowolski">zdu\u0144skowolski</option>\n<option value="zgierski">zgierski</option>\n<option value="krakow">Krak\xf3w</option>\n<option value="tarnow">Tarn\xf3w</option>\n<option value="nowy-sacz">Nowy S\u0105cz</option>\n<option value="bochenski">boche\u0144ski</option>\n<option value="brzeski">brzeski</option>\n<option value="chrzanowski">chrzanowski</option>\n<option value="dabrowski">d\u0105browski</option>\n<option value="gorlicki">gorlicki</option>\n<option value="krakowski">krakowski</option>\n<option value="limanowski">limanowski</option>\n<option value="miechowski">miechowski</option>\n<option value="myslenicki">my\u015blenicki</option>\n<option value="nowosadecki">nowos\u0105decki</option>\n<option value="nowotarski">nowotarski</option>\n<option value="olkuski">olkuski</option>\n<option value="oswiecimski">o\u015bwi\u0119cimski</option>\n<option value="proszowicki">proszowicki</option>\n<option value="suski">suski</option>\n<option value="tarnowski">tarnowski</option>\n<option value="tatrzanski">tatrza\u0144ski</option>\n<option value="wadowicki">wadowicki</option>\n<option value="wielicki">wielicki</option>\n<option value="warszawa">Warszawa</option>\n<option value="ostroleka">Ostro\u0142\u0119ka</option>\n<option value="plock">P\u0142ock</option>\n<option value="radom">Radom</option>\n<option value="siedlce">Siedlce</option>\n<option value="bialobrzeski">bia\u0142obrzeski</option>\n<option value="ciechanowski">ciechanowski</option>\n<option value="garwolinski">garwoli\u0144ski</option>\n<option value="gostyninski">gostyni\u0144ski</option>\n<option value="grodziski">grodziski</option>\n<option value="grojecki">gr\xf3jecki</option>\n<option value="kozienicki">kozenicki</option>\n<option value="legionowski">legionowski</option>\n<option value="lipski">lipski</option>\n<option value="losicki">\u0142osicki</option>\n<option value="makowski">makowski</option>\n<option value="minski">mi\u0144ski</option>\n<option value="mlawski">m\u0142awski</option>\n<option value="nowodworski">nowodworski</option>\n<option value="ostrolecki">ostro\u0142\u0119cki</option>\n<option value="ostrowski">ostrowski</option>\n<option value="otwocki">otwocki</option>\n<option value="piaseczynski">piaseczy\u0144ski</option>\n<option value="plocki">p\u0142ocki</option>\n<option value="plonski">p\u0142o\u0144ski</option>\n<option value="pruszkowski">pruszkowski</option>\n<option value="przasnyski">przasnyski</option>\n<option value="przysuski">przysuski</option>\n<option value="pultuski">pu\u0142tuski</option>\n<option value="radomski">radomski</option>\n<option value="siedlecki">siedlecki</option>\n<option value="sierpecki">sierpecki</option>\n<option value="sochaczewski">sochaczewski</option>\n<option value="sokolowski">soko\u0142owski</option>\n<option value="szydlowiecki">szyd\u0142owiecki</option>\n<option value="warszawski-zachodni">warszawski zachodni</option>\n<option value="wegrowski">w\u0119growski</option>\n<option value="wolominski">wo\u0142omi\u0144ski</option>\n<option value="wyszkowski">wyszkowski</option>\n<option value="zwolenski">zwole\u0144ski</option>\n<option value="zurominski">\u017curomi\u0144ski</option>\n<option value="zyrardowski">\u017cyrardowski</option>\n<option value="opole">Opole</option>\n<option value="brzeski">brzeski</option>\n<option value="glubczycki">g\u0142ubczyski</option>\n<option value="kedzierzynsko-kozielski">k\u0119dzierzy\u0144ski-kozielski</option>\n<option value="kluczborski">kluczborski</option>\n<option value="krapkowicki">krapkowicki</option>\n<option value="namyslowski">namys\u0142owski</option>\n<option value="nyski">nyski</option>\n<option value="oleski">oleski</option>\n<option value="opolski">opolski</option>\n<option value="prudnicki">prudnicki</option>\n<option value="strzelecki">strzelecki</option>\n<option value="rzeszow">Rzesz\xf3w</option>\n<option value="krosno">Krosno</option>\n<option value="przemysl">Przemy\u015bl</option>\n<option value="tarnobrzeg">Tarnobrzeg</option>\n<option value="bieszczadzki">bieszczadzki</option>\n<option value="brzozowski">brzozowski</option>\n<option value="debicki">d\u0119bicki</option>\n<option value="jaroslawski">jaros\u0142awski</option>\n<option value="jasielski">jasielski</option>\n<option value="kolbuszowski">kolbuszowski</option>\n<option value="krosnienski">kro\u015bnie\u0144ski</option>\n<option value="leski">leski</option>\n<option value="lezajski">le\u017cajski</option>\n<option value="lubaczowski">lubaczowski</option>\n<option value="lancucki">\u0142a\u0144cucki</option>\n<option value="mielecki">mielecki</option>\n<option value="nizanski">ni\u017ca\u0144ski</option>\n<option value="przemyski">przemyski</option>\n<option value="przeworski">przeworski</option>\n<option value="ropczycko-sedziszowski">ropczycko-s\u0119dziszowski</option>\n<option value="rzeszowski">rzeszowski</option>\n<option value="sanocki">sanocki</option>\n<option value="stalowowolski">stalowowolski</option>\n<option value="strzyzowski">strzy\u017cowski</option>\n<option value="tarnobrzeski">tarnobrzeski</option>\n<option value="bialystok">Bia\u0142ystok</option>\n<option value="lomza">\u0141om\u017ca</option>\n<option value="suwalki">Suwa\u0142ki</option>\n<option value="augustowski">augustowski</option>\n<option value="bialostocki">bia\u0142ostocki</option>\n<option value="bielski">bielski</option>\n<option value="grajewski">grajewski</option>\n<option value="hajnowski">hajnowski</option>\n<option value="kolnenski">kolne\u0144ski</option>\n<option value="\u0142omzynski">\u0142om\u017cy\u0144ski</option>\n<option value="moniecki">moniecki</option>\n<option value="sejnenski">sejne\u0144ski</option>\n<option value="siemiatycki">siematycki</option>\n<option value="sokolski">sok\xf3lski</option>\n<option value="suwalski">suwalski</option>\n<option value="wysokomazowiecki">wysokomazowiecki</option>\n<option value="zambrowski">zambrowski</option>\n<option value="gdansk">Gda\u0144sk</option>\n<option value="gdynia">Gdynia</option>\n<option value="slupsk">S\u0142upsk</option>\n<option value="sopot">Sopot</option>\n<option value="bytowski">bytowski</option>\n<option value="chojnicki">chojnicki</option>\n<option value="czluchowski">cz\u0142uchowski</option>\n<option value="kartuski">kartuski</option>\n<option value="koscierski">ko\u015bcierski</option>\n<option value="kwidzynski">kwidzy\u0144ski</option>\n<option value="leborski">l\u0119borski</option>\n<option value="malborski">malborski</option>\n<option value="nowodworski">nowodworski</option>\n<option value="gdanski">gda\u0144ski</option>\n<option value="pucki">pucki</option>\n<option value="slupski">s\u0142upski</option>\n<option value="starogardzki">starogardzki</option>\n<option value="sztumski">sztumski</option>\n<option value="tczewski">tczewski</option>\n<option value="wejherowski">wejcherowski</option>\n<option value="katowice" selected="selected">Katowice</option>\n<option value="bielsko-biala">Bielsko-Bia\u0142a</option>\n<option value="bytom">Bytom</option>\n<option value="chorzow">Chorz\xf3w</option>\n<option value="czestochowa">Cz\u0119stochowa</option>\n<option value="dabrowa-gornicza">D\u0105browa G\xf3rnicza</option>\n<option value="gliwice">Gliwice</option>\n<option value="jastrzebie-zdroj">Jastrz\u0119bie Zdr\xf3j</option>\n<option value="jaworzno">Jaworzno</option>\n<option value="myslowice">Mys\u0142owice</option>\n<option value="piekary-slaskie">Piekary \u015al\u0105skie</option>\n<option value="ruda-slaska">Ruda \u015al\u0105ska</option>\n<option value="rybnik">Rybnik</option>\n<option value="siemianowice-slaskie">Siemianowice \u015al\u0105skie</option>\n<option value="sosnowiec">Sosnowiec</option>\n<option value="swietochlowice">\u015awi\u0119toch\u0142owice</option>\n<option value="tychy">Tychy</option>\n<option value="zabrze">Zabrze</option>\n<option value="zory">\u017bory</option>\n<option value="bedzinski">b\u0119dzi\u0144ski</option>\n<option value="bielski">bielski</option>\n<option value="bierunsko-ledzinski">bieru\u0144sko-l\u0119dzi\u0144ski</option>\n<option value="cieszynski">cieszy\u0144ski</option>\n<option value="czestochowski">cz\u0119stochowski</option>\n<option value="gliwicki">gliwicki</option>\n<option value="klobucki">k\u0142obucki</option>\n<option value="lubliniecki">lubliniecki</option>\n<option value="mikolowski">miko\u0142owski</option>\n<option value="myszkowski">myszkowski</option>\n<option value="pszczynski">pszczy\u0144ski</option>\n<option value="raciborski">raciborski</option>\n<option value="rybnicki">rybnicki</option>\n<option value="tarnogorski">tarnog\xf3rski</option>\n<option value="wodzislawski">wodzis\u0142awski</option>\n<option value="zawiercianski">zawiercia\u0144ski</option>\n<option value="zywiecki">\u017cywiecki</option>\n<option value="kielce">Kielce</option>\n<option value="buski">buski</option>\n<option value="jedrzejowski">j\u0119drzejowski</option>\n<option value="kazimierski">kazimierski</option>\n<option value="kielecki">kielecki</option>\n<option value="konecki">konecki</option>\n<option value="opatowski">opatowski</option>\n<option value="ostrowiecki">ostrowiecki</option>\n<option value="pinczowski">pi\u0144czowski</option>\n<option value="sandomierski">sandomierski</option>\n<option value="skarzyski">skar\u017cyski</option>\n<option value="starachowicki">starachowicki</option>\n<option value="staszowski">staszowski</option>\n<option value="wloszczowski">w\u0142oszczowski</option>\n<option value="olsztyn">Olsztyn</option>\n<option value="elblag">Elbl\u0105g</option>\n<option value="bartoszycki">bartoszycki</option>\n<option value="braniewski">braniewski</option>\n<option value="dzialdowski">dzia\u0142dowski</option>\n<option value="elblaski">elbl\u0105ski</option>\n<option value="elcki">e\u0142cki</option>\n<option value="gizycki">gi\u017cycki</option>\n<option value="goldapski">go\u0142dapski</option>\n<option value="ilawski">i\u0142awski</option>\n<option value="ketrzynski">k\u0119trzy\u0144ski</option>\n<option value="lidzbarski">lidzbarski</option>\n<option value="mragowski">mr\u0105gowski</option>\n<option value="nidzicki">nidzicki</option>\n<option value="nowomiejski">nowomiejski</option>\n<option value="olecki">olecki</option>\n<option value="olsztynski">olszty\u0144ski</option>\n<option value="ostrodzki">ostr\xf3dzki</option>\n<option value="piski">piski</option>\n<option value="szczycienski">szczycie\u0144ski</option>\n<option value="wegorzewski">w\u0119gorzewski</option>\n<option value="poznan">Pozna\u0144</option>\n<option value="kalisz">Kalisz</option>\n<option value="konin">Konin</option>\n<option value="leszno">Leszno</option>\n<option value="chodzieski">chodziejski</option>\n<option value="czarnkowsko-trzcianecki">czarnkowsko-trzcianecki</option>\n<option value="gnieznienski">gnie\u017anie\u0144ski</option>\n<option value="gostynski">gosty\u0144ski</option>\n<option value="grodziski">grodziski</option>\n<option value="jarocinski">jaroci\u0144ski</option>\n<option value="kaliski">kaliski</option>\n<option value="kepinski">k\u0119pi\u0144ski</option>\n<option value="kolski">kolski</option>\n<option value="koninski">koni\u0144ski</option>\n<option value="koscianski">ko\u015bcia\u0144ski</option>\n<option value="krotoszynski">krotoszy\u0144ski</option>\n<option value="leszczynski">leszczy\u0144ski</option>\n<option value="miedzychodzki">mi\u0119dzychodzki</option>\n<option value="nowotomyski">nowotomyski</option>\n<option value="obornicki">obornicki</option>\n<option value="ostrowski">ostrowski</option>\n<option value="ostrzeszowski">ostrzeszowski</option>\n<option value="pilski">pilski</option>\n<option value="pleszewski">pleszewski</option>\n<option value="poznanski">pozna\u0144ski</option>\n<option value="rawicki">rawicki</option>\n<option value="slupecki">s\u0142upecki</option>\n<option value="szamotulski">szamotulski</option>\n<option value="sredzki">\u015bredzki</option>\n<option value="sremski">\u015bremski</option>\n<option value="turecki">turecki</option>\n<option value="wagrowiecki">w\u0105growiecki</option>\n<option value="wolsztynski">wolszty\u0144ski</option>\n<option value="wrzesinski">wrzesi\u0144ski</option>\n<option value="zlotowski">z\u0142otowski</option>\n<option value="bialogardzki">bia\u0142ogardzki</option>\n<option value="choszczenski">choszcze\u0144ski</option>\n<option value="drawski">drawski</option>\n<option value="goleniowski">goleniowski</option>\n<option value="gryficki">gryficki</option>\n<option value="gryfinski">gryfi\u0144ski</option>\n<option value="kamienski">kamie\u0144ski</option>\n<option value="kolobrzeski">ko\u0142obrzeski</option>\n<option value="koszalinski">koszali\u0144ski</option>\n<option value="lobeski">\u0142obeski</option>\n<option value="mysliborski">my\u015bliborski</option>\n<option value="policki">policki</option>\n<option value="pyrzycki">pyrzycki</option>\n<option value="slawienski">s\u0142awie\u0144ski</option>\n<option value="stargardzki">stargardzki</option>\n<option value="szczecinecki">szczecinecki</option>\n<option value="swidwinski">\u015bwidwi\u0144ski</option>\n<option value="walecki">wa\u0142ecki</option>\n</select>'
+
+# 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')
<Foo: Foo Bjorn>
+
+# 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: Article Test>
+
+>>> Article.objects.get(text__contains='quick brown fox')
+<Article: Article Test>
"""}