summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdrian Holovaty <adrian@holovaty.com>2005-11-14 01:44:35 +0000
committerAdrian Holovaty <adrian@holovaty.com>2005-11-14 01:44:35 +0000
commitf6bf41e59ac032c253e3b4e1b267010c6d456a26 (patch)
treea2b7755ee377b3cd99ff301154d9ae9a35991b1c
parent6e40d8c29f2b6ed926cf764f5c9cdce07bc9a069 (diff)
downloaddjango-f6bf41e59ac032c253e3b4e1b267010c6d456a26.tar.gz
Fixed #121 -- Django now quotes all names in SQL queries. Also added unit tests to confirm. Thanks, Robin Munn and Sune.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@1224 bcc190cf-cafb-0310-a4f2-bffc1f526a37
-rw-r--r--django/bin/daily_cleanup.py6
-rw-r--r--django/core/db/backends/ado_mssql.py4
-rw-r--r--django/core/db/backends/postgresql.py4
-rw-r--r--django/core/management.py126
-rw-r--r--django/core/meta/__init__.py168
-rw-r--r--django/models/auth.py25
-rw-r--r--docs/model-api.txt29
-rw-r--r--tests/testapp/models/__init__.py2
-rw-r--r--tests/testapp/models/reserved_names.py47
9 files changed, 295 insertions, 116 deletions
diff --git a/django/bin/daily_cleanup.py b/django/bin/daily_cleanup.py
index 6f2e8da4c0..52e2ef43fd 100644
--- a/django/bin/daily_cleanup.py
+++ b/django/bin/daily_cleanup.py
@@ -7,8 +7,10 @@ DOCUMENTATION_DIRECTORY = '/home/html/documentation/'
def clean_up():
# Clean up old database records
cursor = db.cursor()
- cursor.execute("DELETE FROM core_sessions WHERE expire_date < NOW()")
- cursor.execute("DELETE FROM registration_challenges WHERE request_date < NOW() - INTERVAL '1 week'")
+ cursor.execute("DELETE FROM %s WHERE %s < NOW()" % \
+ (db.quote_name('core_sessions'), db.quote_name('expire_date')))
+ cursor.execute("DELETE FROM %s WHERE %s < NOW() - INTERVAL '1 week'" % \
+ (db.quote_name('registration_challenges'), db.quote_name('request_date')))
db.commit()
if __name__ == "__main__":
diff --git a/django/core/db/backends/ado_mssql.py b/django/core/db/backends/ado_mssql.py
index d4f2a359c5..cc2f861ea7 100644
--- a/django/core/db/backends/ado_mssql.py
+++ b/django/core/db/backends/ado_mssql.py
@@ -149,8 +149,8 @@ DATA_TYPES = {
'NullBooleanField': 'bit',
'OneToOneField': 'int',
'PhoneNumberField': 'varchar(20)',
- 'PositiveIntegerField': 'int CONSTRAINT [CK_int_pos_%(name)s] CHECK ([%(name)s] > 0)',
- 'PositiveSmallIntegerField': 'smallint CONSTRAINT [CK_smallint_pos_%(name)s] CHECK ([%(name)s] > 0)',
+ 'PositiveIntegerField': 'int CONSTRAINT [CK_int_pos_%(column)s] CHECK ([%(column)s] > 0)',
+ 'PositiveSmallIntegerField': 'smallint CONSTRAINT [CK_smallint_pos_%(column)s] CHECK ([%(column)s] > 0)',
'SlugField': 'varchar(50)',
'SmallIntegerField': 'smallint',
'TextField': 'text',
diff --git a/django/core/db/backends/postgresql.py b/django/core/db/backends/postgresql.py
index b6d34fc814..1db6fe40c3 100644
--- a/django/core/db/backends/postgresql.py
+++ b/django/core/db/backends/postgresql.py
@@ -170,8 +170,8 @@ DATA_TYPES = {
'NullBooleanField': 'boolean',
'OneToOneField': 'integer',
'PhoneNumberField': 'varchar(20)',
- 'PositiveIntegerField': 'integer CHECK (%(name)s >= 0)',
- 'PositiveSmallIntegerField': 'smallint CHECK (%(name)s >= 0)',
+ 'PositiveIntegerField': 'integer CHECK (%(column)s >= 0)',
+ 'PositiveSmallIntegerField': 'smallint CHECK (%(column)s >= 0)',
'SlugField': 'varchar(50)',
'SmallIntegerField': 'smallint',
'TextField': 'text',
diff --git a/django/core/management.py b/django/core/management.py
index 8a9fd4020d..405371ebce 100644
--- a/django/core/management.py
+++ b/django/core/management.py
@@ -19,7 +19,10 @@ APP_ARGS = '[modelmodule ...]'
PROJECT_TEMPLATE_DIR = os.path.join(django.__path__[0], 'conf', '%s_template')
def _get_packages_insert(app_label):
- return "INSERT INTO packages (label, name) VALUES ('%s', '%s');" % (app_label, app_label)
+ from django.core.db import db
+ return "INSERT INTO %s (%s, %s) VALUES ('%s', '%s');" % \
+ (db.quote_name('packages'), db.quote_name('label'), db.quote_name('name'),
+ app_label, app_label)
def _get_permission_codename(action, opts):
return '%s_%s' % (action, opts.object_name.lower())
@@ -33,12 +36,16 @@ def _get_all_permissions(opts):
return perms + list(opts.permissions)
def _get_permission_insert(name, codename, opts):
- return "INSERT INTO auth_permissions (name, package, codename) VALUES ('%s', '%s', '%s');" % \
- (name.replace("'", "''"), opts.app_label, codename)
+ from django.core.db import db
+ return "INSERT INTO %s (%s, %s, %s) VALUES ('%s', '%s', '%s');" % \
+ (db.quote_name('auth_permissions'), db.quote_name('name'), db.quote_name('package'),
+ db.quote_name('codename'), name.replace("'", "''"), opts.app_label, codename)
def _get_contenttype_insert(opts):
- return "INSERT INTO content_types (name, package, python_module_name) VALUES ('%s', '%s', '%s');" % \
- (opts.verbose_name, opts.app_label, opts.module_name)
+ from django.core.db import db
+ return "INSERT INTO %s (%s, %s, %s) VALUES ('%s', '%s', '%s');" % \
+ (db.quote_name('content_types'), db.quote_name('name'), db.quote_name('package'),
+ db.quote_name('python_module_name'), opts.verbose_name, opts.app_label, opts.module_name)
def _is_valid_dir_name(s):
return bool(re.search(r'^\w+$', s))
@@ -64,7 +71,7 @@ def get_sql_create(mod):
data_type = f.get_internal_type()
col_type = db.DATA_TYPES[data_type]
if col_type is not None:
- field_output = [f.column, col_type % rel_field.__dict__]
+ field_output = [db.db.quote_name(f.column), col_type % rel_field.__dict__]
field_output.append('%sNULL' % (not f.null and 'NOT ' or ''))
if f.unique:
field_output.append('UNIQUE')
@@ -72,14 +79,16 @@ def get_sql_create(mod):
field_output.append('PRIMARY KEY')
if f.rel:
field_output.append('REFERENCES %s (%s)' % \
- (f.rel.to.db_table, f.rel.to.get_field(f.rel.field_name).column))
+ (db.db.quote_name(f.rel.to.db_table),
+ db.db.quote_name(f.rel.to.get_field(f.rel.field_name).column)))
table_output.append(' '.join(field_output))
if opts.order_with_respect_to:
- table_output.append('_order %s NULL' % db.DATA_TYPES['IntegerField'])
+ table_output.append('%s %s NULL' % (db.db.quote_name('_order'), db.DATA_TYPES['IntegerField']))
for field_constraints in opts.unique_together:
- table_output.append('UNIQUE (%s)' % ", ".join([opts.get_field(f).column for f in field_constraints]))
+ table_output.append('UNIQUE (%s)' % \
+ ", ".join([db.db.quote_name(opts.get_field(f).column) for f in field_constraints]))
- full_statement = ['CREATE TABLE %s (' % opts.db_table]
+ full_statement = ['CREATE TABLE %s (' % db.db.quote_name(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(');')
@@ -88,13 +97,21 @@ def get_sql_create(mod):
for klass in mod._MODELS:
opts = klass._meta
for f in opts.many_to_many:
- table_output = ['CREATE TABLE %s (' % f.get_m2m_db_table(opts)]
- table_output.append(' id %s NOT NULL PRIMARY KEY,' % db.DATA_TYPES['AutoField'])
- table_output.append(' %s_id %s NOT NULL REFERENCES %s (%s),' % \
- (opts.object_name.lower(), db.DATA_TYPES[get_rel_data_type(opts.pk)] % opts.pk.__dict__, opts.db_table, opts.pk.column))
- table_output.append(' %s_id %s NOT NULL REFERENCES %s (%s),' % \
- (f.rel.to.object_name.lower(), db.DATA_TYPES[get_rel_data_type(f.rel.to.pk)] % f.rel.to.pk.__dict__, f.rel.to.db_table, f.rel.to.pk.column))
- table_output.append(' UNIQUE (%s_id, %s_id)' % (opts.object_name.lower(), f.rel.to.object_name.lower()))
+ table_output = ['CREATE TABLE %s (' % db.db.quote_name(f.get_m2m_db_table(opts))]
+ table_output.append(' %s %s NOT NULL PRIMARY KEY,' % (db.db.quote_name('id'), db.DATA_TYPES['AutoField']))
+ table_output.append(' %s %s NOT NULL REFERENCES %s (%s),' % \
+ (db.db.quote_name(opts.object_name.lower() + '_id'),
+ db.DATA_TYPES[get_rel_data_type(opts.pk)] % opts.pk.__dict__,
+ db.db.quote_name(opts.db_table),
+ db.db.quote_name(opts.pk.column)))
+ table_output.append(' %s %s NOT NULL REFERENCES %s (%s),' % \
+ (db.db.quote_name(f.rel.to.object_name.lower() + '_id'),
+ db.DATA_TYPES[get_rel_data_type(f.rel.to.pk)] % f.rel.to.pk.__dict__,
+ db.db.quote_name(f.rel.to.db_table),
+ db.db.quote_name(f.rel.to.pk.column)))
+ table_output.append(' UNIQUE (%s, %s)' % \
+ (db.db.quote_name(opts.object_name.lower() + '_id'),
+ db.db.quote_name(f.rel.to.object_name.lower() + '_id')))
table_output.append(');')
final_output.append('\n'.join(table_output))
return final_output
@@ -114,7 +131,7 @@ def get_sql_delete(mod):
try:
if cursor is not None:
# Check whether the table exists.
- cursor.execute("SELECT 1 FROM django_admin_log LIMIT 1")
+ cursor.execute("SELECT 1 FROM %s LIMIT 1" % db.db.quote_name('django_admin_log'))
except:
# The table doesn't exist, so it doesn't need to be dropped.
db.db.rollback()
@@ -129,12 +146,12 @@ def get_sql_delete(mod):
try:
if cursor is not None:
# Check whether the table exists.
- cursor.execute("SELECT 1 FROM %s LIMIT 1" % klass._meta.db_table)
+ cursor.execute("SELECT 1 FROM %s LIMIT 1" % db.db.quote_name(klass._meta.db_table))
except:
# The table doesn't exist, so it doesn't need to be dropped.
db.db.rollback()
else:
- output.append("DROP TABLE %s;" % klass._meta.db_table)
+ output.append("DROP TABLE %s;" % db.db.quote_name(klass._meta.db_table))
# Output DROP TABLE statements for many-to-many tables.
for klass in mod._MODELS:
@@ -142,25 +159,31 @@ def get_sql_delete(mod):
for f in opts.many_to_many:
try:
if cursor is not None:
- cursor.execute("SELECT 1 FROM %s LIMIT 1" % f.get_m2m_db_table(opts))
+ cursor.execute("SELECT 1 FROM %s LIMIT 1" % db.db.quote_name(f.get_m2m_db_table(opts)))
except:
db.db.rollback()
else:
- output.append("DROP TABLE %s;" % f.get_m2m_db_table(opts))
+ output.append("DROP TABLE %s;" % db.db.quote_name(f.get_m2m_db_table(opts)))
app_label = mod._MODELS[0]._meta.app_label
# Delete from packages, auth_permissions, content_types.
- output.append("DELETE FROM packages WHERE label = '%s';" % app_label)
- output.append("DELETE FROM auth_permissions WHERE package = '%s';" % app_label)
- output.append("DELETE FROM content_types WHERE package = '%s';" % app_label)
+ output.append("DELETE FROM %s WHERE %s = '%s';" % \
+ (db.db.quote_name('packages'), db.db.quote_name('label'), app_label))
+ output.append("DELETE FROM %s WHERE %s = '%s';" % \
+ (db.db.quote_name('auth_permissions'), db.db.quote_name('package'), app_label))
+ output.append("DELETE FROM %s WHERE %s = '%s';" % \
+ (db.db.quote_name('content_types'), db.db.quote_name('package'), app_label))
# Delete from the admin log.
if cursor is not None:
- cursor.execute("SELECT id FROM content_types WHERE package = %s", [app_label])
+ cursor.execute("SELECT %s FROM %s WHERE %s = %%s" % \
+ (db.db.quote_name('id'), db.db.quote_name('content_types'),
+ db.db.quote_name('package')), [app_label])
if admin_log_exists:
for row in cursor.fetchall():
- output.append("DELETE FROM django_admin_log WHERE content_type_id = %s;" % row[0])
+ output.append("DELETE FROM %s WHERE %s = %s;" % \
+ (db.db.quote_name('django_admin_log'), db.db.quote_name('content_type_id'), row[0]))
# Close database connection explicitly, in case this output is being piped
# directly into a database client, to avoid locking issues.
@@ -206,25 +229,29 @@ get_sql_initial_data.args = APP_ARGS
def get_sql_sequence_reset(mod):
"Returns a list of the SQL statements to reset PostgreSQL sequences for the given module."
- from django.core import meta
+ from django.core import db, meta
output = []
for klass in mod._MODELS:
for f in klass._meta.fields:
if isinstance(f, meta.AutoField):
- output.append("SELECT setval('%s_%s_seq', (SELECT max(%s) FROM %s));" % (klass._meta.db_table, f.column, f.column, klass._meta.db_table))
+ output.append("SELECT setval('%s_%s_seq', (SELECT max(%s) FROM %s));" % \
+ (klass._meta.db_table, f.column, db.db.quote_name(f.column),
+ db.db.quote_name(klass._meta.db_table)))
return output
get_sql_sequence_reset.help_doc = "Prints the SQL statements for resetting PostgreSQL sequences for the given model module name(s)."
get_sql_sequence_reset.args = APP_ARGS
def get_sql_indexes(mod):
"Returns a list of the CREATE INDEX SQL statements for the given module."
+ from django.core.db import db
output = []
for klass in mod._MODELS:
for f in klass._meta.fields:
if f.db_index:
unique = f.unique and "UNIQUE " or ""
output.append("CREATE %sINDEX %s_%s ON %s (%s);" % \
- (unique, klass._meta.db_table, f.column, klass._meta.db_table, f.column))
+ (unique, klass._meta.db_table, f.column,
+ db.quote_name(klass._meta.db_table), db.quote_name(f.column)))
return output
get_sql_indexes.help_doc = "Prints the CREATE INDEX SQL statements for the given model module name(s)."
get_sql_indexes.args = APP_ARGS
@@ -242,7 +269,8 @@ def database_check(mod):
app_label = mod._MODELS[0]._meta.app_label
# Check that the package exists in the database.
- cursor.execute("SELECT 1 FROM packages WHERE label = %s", [app_label])
+ cursor.execute("SELECT 1 FROM %s WHERE %s = %%s" % \
+ (db.db.quote_name('packages'), db.db.quote_name('label')), [app_label])
if cursor.rowcount < 1:
# sys.stderr.write("The '%s' package isn't installed.\n" % app_label)
print _get_packages_insert(app_label)
@@ -256,34 +284,46 @@ def database_check(mod):
perms_seen.update(dict(perms))
contenttypes_seen[opts.module_name] = 1
for codename, name in perms:
- cursor.execute("SELECT 1 FROM auth_permissions WHERE package = %s AND codename = %s", (app_label, codename))
+ cursor.execute("SELECT 1 FROM %s WHERE %s = %%s AND %s = %%s" % \
+ (db.db.quote_name('auth_permissions'), db.db.quote_name('package'),
+ db.db.quote_name('codename')), (app_label, codename))
if cursor.rowcount < 1:
# sys.stderr.write("The '%s.%s' permission doesn't exist.\n" % (app_label, codename))
print _get_permission_insert(name, codename, opts)
- cursor.execute("SELECT 1 FROM content_types WHERE package = %s AND python_module_name = %s", (app_label, opts.module_name))
+ cursor.execute("SELECT 1 FROM %s WHERE %s = %%s AND %s = %%s" % \
+ (db.db.quote_name('content_types'), db.db.quote_name('package'),
+ db.db.quote_name('python_module_name')), (app_label, opts.module_name))
if cursor.rowcount < 1:
# sys.stderr.write("The '%s.%s' content type doesn't exist.\n" % (app_label, opts.module_name))
print _get_contenttype_insert(opts)
# Check that there aren't any *extra* permissions in the DB that the model
# doesn't know about.
- cursor.execute("SELECT codename FROM auth_permissions WHERE package = %s", (app_label,))
+ cursor.execute("SELECT %s FROM %s WHERE %s = %%s" % \
+ (db.db.quote_name('codename'), db.db.quote_name('auth_permissions'),
+ db.db.quote_name('package')), (app_label,))
for row in cursor.fetchall():
try:
perms_seen[row[0]]
except KeyError:
# sys.stderr.write("A permission called '%s.%s' was found in the database but not in the model.\n" % (app_label, row[0]))
- print "DELETE FROM auth_permissions WHERE package='%s' AND codename = '%s';" % (app_label, row[0])
+ print "DELETE FROM %s WHERE %s='%s' AND %s = '%s';" % \
+ (db.db.quote_name('auth_permissions'), db.db.quote_name('package'),
+ app_label, db.db.quote_name('codename'), row[0])
# Check that there aren't any *extra* content types in the DB that the
# model doesn't know about.
- cursor.execute("SELECT python_module_name FROM content_types WHERE package = %s", (app_label,))
+ cursor.execute("SELECT %s FROM %s WHERE %s = %%s" % \
+ (db.db.quote_name('python_module_name'), db.db.quote_name('content_types'),
+ db.db.quote_name('package')), (app_label,))
for row in cursor.fetchall():
try:
contenttypes_seen[row[0]]
except KeyError:
# sys.stderr.write("A content type called '%s.%s' was found in the database but not in the model.\n" % (app_label, row[0]))
- print "DELETE FROM content_types WHERE package='%s' AND python_module_name = '%s';" % (app_label, row[0])
+ print "DELETE FROM %s WHERE %s='%s' AND %s = '%s';" % \
+ (db.db.quote_name('content_types'), db.db.quote_name('package'),
+ app_label, db.db.quote_name('python_module_name'), row[0])
database_check.help_doc = "Checks that everything is installed in the database for the given model module name(s) and prints SQL statements if needed."
database_check.args = APP_ARGS
@@ -318,7 +358,9 @@ def init():
cursor = db.db.cursor()
for sql in get_sql_create(core) + get_sql_create(auth) + get_sql_initial_data(core) + get_sql_initial_data(auth):
cursor.execute(sql)
- cursor.execute("INSERT INTO %s (domain, name) VALUES ('example.com', 'Example site')" % core.Site._meta.db_table)
+ cursor.execute("INSERT INTO %s (%s, %s) VALUES ('example.com', 'Example site')" % \
+ (db.db.quote_name(core.Site._meta.db_table), db.db.quote_name('domain'),
+ db.db.quote_name('name')))
except Exception, e:
sys.stderr.write("Error: The database couldn't be initialized.\n%s\n" % e)
try:
@@ -687,7 +729,7 @@ def createcachetable(tablename):
table_output = []
index_output = []
for f in fields:
- field_output = [f.column, db.DATA_TYPES[f.get_internal_type()] % f.__dict__]
+ field_output = [db.db.quote_name(f.column), db.DATA_TYPES[f.get_internal_type()] % f.__dict__]
field_output.append("%sNULL" % (not f.null and "NOT " or ""))
if f.unique:
field_output.append("UNIQUE")
@@ -695,9 +737,11 @@ def createcachetable(tablename):
field_output.append("PRIMARY KEY")
if f.db_index:
unique = f.unique and "UNIQUE " or ""
- index_output.append("CREATE %sINDEX %s_%s ON %s (%s);" % (unique, tablename, f.column, tablename, f.column))
+ index_output.append("CREATE %sINDEX %s_%s ON %s (%s);" % \
+ (unique, tablename, f.column, db.db.quote_name(tablename),
+ db.db.quote_name(f.column)))
table_output.append(" ".join(field_output))
- full_statement = ["CREATE TABLE %s (" % tablename]
+ full_statement = ["CREATE TABLE %s (" % db.db.quote_name(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/meta/__init__.py b/django/core/meta/__init__.py
index 22ce51d0a5..328a2f449c 100644
--- a/django/core/meta/__init__.py
+++ b/django/core/meta/__init__.py
@@ -57,14 +57,16 @@ def orderfield2column(f, opts):
return f
def orderlist2sql(order_list, opts, prefix=''):
+ if prefix.endswith('.'):
+ prefix = db.db.quote_name(prefix[:-1]) + '.'
output = []
for f in handle_legacy_orderlist(order_list):
if f.startswith('-'):
- output.append('%s%s DESC' % (prefix, orderfield2column(f[1:], opts)))
+ output.append('%s%s DESC' % (prefix, db.db.quote_name(orderfield2column(f[1:], opts))))
elif f == '?':
output.append(db.get_random_function_sql())
else:
- output.append('%s%s ASC' % (prefix, orderfield2column(f, opts)))
+ output.append('%s%s ASC' % (prefix, db.db.quote_name(orderfield2column(f, opts))))
return ', '.join(output)
def get_module(app_label, module_name):
@@ -785,27 +787,31 @@ def method_save(opts, self):
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 LIMIT 1" % (opts.db_table, opts.pk.column), [pk_val])
+ cursor.execute("SELECT 1 FROM %s WHERE %s=%%s LIMIT 1" % \
+ (db.db.quote_name(opts.db_table), db.db.quote_name(opts.pk.column)), [pk_val])
# If it does already exist, do an UPDATE.
if cursor.fetchone():
db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.attname), False)) for f in non_pks]
- cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % (opts.db_table,
- ','.join(['%s=%%s' % f.column for f in non_pks]), opts.pk.attname),
+ cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % \
+ (db.db.quote_name(opts.db_table),
+ ','.join(['%s=%%s' % db.db.quote_name(f.column) for f in non_pks]),
+ db.db.quote_name(opts.pk.attname)),
db_values + [pk_val])
else:
record_exists = False
if not pk_set or not record_exists:
- field_names = [f.column for f in opts.fields if not isinstance(f, AutoField)]
+ field_names = [db.db.quote_name(f.column) for f in opts.fields if not isinstance(f, AutoField)]
placeholders = ['%s'] * len(field_names)
db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.attname), True)) for f in opts.fields if not isinstance(f, AutoField)]
if opts.order_with_respect_to:
- field_names.append('_order')
+ field_names.append(db.db.quote_name('_order'))
# TODO: This assumes the database supports subqueries.
placeholders.append('(SELECT COUNT(*) FROM %s WHERE %s = %%s)' % \
- (opts.db_table, opts.order_with_respect_to.column))
+ (db.db.quote_name(opts.db_table), db.db.quote_name(opts.order_with_respect_to.column)))
db_values.append(getattr(self, opts.order_with_respect_to.attname))
- cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % (opts.db_table,
- ','.join(field_names), ','.join(placeholders)), db_values)
+ cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % \
+ (db.db.quote_name(opts.db_table), ','.join(field_names),
+ ','.join(placeholders)), db_values)
if opts.has_auto_field:
setattr(self, opts.pk.attname, db.get_last_insert_id(cursor, opts.db_table, opts.pk.column))
db.db.commit()
@@ -832,12 +838,17 @@ def method_delete(opts, self):
for sub_obj in getattr(self, 'get_%s_list' % rel_opts_name)():
sub_obj.delete()
for rel_opts, rel_field in opts.get_all_related_many_to_many_objects():
- cursor.execute("DELETE FROM %s WHERE %s_id=%%s" % (rel_field.get_m2m_db_table(rel_opts),
- self._meta.object_name.lower()), [getattr(self, opts.pk.attname)])
+ cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
+ (db.db.quote_name(rel_field.get_m2m_db_table(rel_opts)),
+ db.db.quote_name(self._meta.object_name.lower() + '_id')), [getattr(self, opts.pk.attname)])
for f in opts.many_to_many:
- cursor.execute("DELETE FROM %s WHERE %s_id=%%s" % (f.get_m2m_db_table(opts), self._meta.object_name.lower()),
+ cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
+ (db.db.quote_name(f.get_m2m_db_table(opts)),
+ db.db.quote_name(self._meta.object_name.lower() + '_id')),
[getattr(self, opts.pk.attname)])
- cursor.execute("DELETE FROM %s WHERE %s=%%s" % (opts.db_table, opts.pk.column), [getattr(self, opts.pk.attname)])
+ cursor.execute("DELETE FROM %s WHERE %s=%%s" % \
+ (db.db.quote_name(opts.db_table), db.db.quote_name(opts.pk.column)),
+ [getattr(self, opts.pk.attname)])
db.db.commit()
setattr(self, opts.pk.attname, None)
for f in opts.fields:
@@ -854,16 +865,20 @@ def method_delete(opts, self):
def method_get_next_in_order(opts, order_field, self):
if not hasattr(self, '_next_in_order_cache'):
self._next_in_order_cache = opts.get_model_module().get_object(order_by=('_order',),
- where=['_order > (SELECT _order FROM %s WHERE %s=%%s)' % (opts.db_table, opts.pk.column),
- '%s=%%s' % order_field.column], limit=1,
+ where=['%s > (SELECT %s FROM %s WHERE %s=%%s)' % \
+ (db.db.quote_name('_order'), db.db.quote_name('_order'),
+ db.db.quote_name(opts.db_table), db.db.quote_name(opts.pk.column)),
+ '%s=%%s' % db.db.quote_name(order_field.column)], limit=1,
params=[getattr(self, opts.pk.attname), getattr(self, order_field.attname)])
return self._next_in_order_cache
def method_get_previous_in_order(opts, order_field, self):
if not hasattr(self, '_previous_in_order_cache'):
self._previous_in_order_cache = opts.get_model_module().get_object(order_by=('-_order',),
- where=['_order < (SELECT _order FROM %s WHERE %s=%%s)' % (opts.db_table, opts.pk.column),
- '%s=%%s' % order_field.column], limit=1,
+ where=['%s < (SELECT %s FROM %s WHERE %s=%%s)' % \
+ (db.db.quote_name('_order'), db.db.quote_name('_order'),
+ db.db.quote_name(opts.db_table), db.db.quote_name(opts.pk.column)),
+ '%s=%%s' % db.db.quote_name(order_field.column)], limit=1,
params=[getattr(self, opts.pk.attname), getattr(self, order_field.attname)])
return self._previous_in_order_cache
@@ -888,10 +903,13 @@ def method_get_many_to_many(field_with_rel, self):
cache_var = '_%s_cache' % field_with_rel.name
if not hasattr(self, cache_var):
mod = rel.get_model_module()
- sql = "SELECT %s FROM %s a, %s b WHERE a.%s = b.%s_id AND b.%s_id = %%s %s" % \
- (','.join(['a.%s' % f.column for f in rel.fields]), rel.db_table,
- field_with_rel.get_m2m_db_table(self._meta), rel.pk.column,
- rel.object_name.lower(), self._meta.object_name.lower(), rel.get_order_sql('a'))
+ sql = "SELECT %s FROM %s a, %s b WHERE a.%s = b.%s AND b.%s = %%s %s" % \
+ (','.join(['a.%s' % db.db.quote_name(f.column) for f in rel.fields]),
+ db.db.quote_name(rel.db_table),
+ db.db.quote_name(field_with_rel.get_m2m_db_table(self._meta)),
+ db.db.quote_name(rel.pk.column),
+ db.db.quote_name(rel.object_name.lower() + '_id'),
+ db.db.quote_name(self._meta.object_name.lower() + '_id'), rel.get_order_sql('a'))
cursor = db.db.cursor()
cursor.execute(sql, [getattr(self, self._meta.pk.attname)])
setattr(self, cache_var, [getattr(mod, rel.object_name)(*row) for row in cursor.fetchall()])
@@ -916,10 +934,16 @@ def method_set_many_to_many(rel_field, self, id_list):
cursor = db.db.cursor()
this_id = getattr(self, self._meta.pk.attname)
if ids_to_delete:
- sql = "DELETE FROM %s WHERE %s_id = %%s AND %s_id IN (%s)" % (m2m_table, self._meta.object_name.lower(), rel.object_name.lower(), ','.join(map(str, ids_to_delete)))
+ sql = "DELETE FROM %s WHERE %s = %%s AND %s IN (%s)" % \
+ (db.db.quote_name(m2m_table),
+ db.db.quote_name(self._meta.object_name.lower() + '_id'),
+ db.db.quote_name(rel.object_name.lower() + '_id'), ','.join(map(str, ids_to_delete)))
cursor.execute(sql, [this_id])
if ids_to_add:
- sql = "INSERT INTO %s (%s_id, %s_id) VALUES (%%s, %%s)" % (m2m_table, self._meta.object_name.lower(), rel.object_name.lower())
+ sql = "INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
+ (db.db.quote_name(m2m_table),
+ db.db.quote_name(self._meta.object_name.lower() + '_id'),
+ db.db.quote_name(rel.object_name.lower() + '_id'))
cursor.executemany(sql, [(this_id, i) for i in ids_to_add])
db.db.commit()
try:
@@ -965,8 +989,13 @@ def method_set_related_many_to_many(rel_opts, rel_field, self, id_list):
m2m_table = rel_field.get_m2m_db_table(rel_opts)
this_id = getattr(self, self._meta.pk.attname)
cursor = db.db.cursor()
- cursor.execute("DELETE FROM %s WHERE %s_id = %%s" % (m2m_table, rel.object_name.lower()), [this_id])
- sql = "INSERT INTO %s (%s_id, %s_id) VALUES (%%s, %%s)" % (m2m_table, rel.object_name.lower(), rel_opts.object_name.lower())
+ cursor.execute("DELETE FROM %s WHERE %s = %%s" % \
+ (db.db.quote_name(m2m_table),
+ db.db.quote_name(rel.object_name.lower() + '_id')), [this_id])
+ sql = "INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \
+ (db.db.quote_name(m2m_table),
+ db.db.quote_name(rel.object_name.lower() + '_id'),
+ db.db.quote_name(rel_opts.object_name.lower() + '_id'))
cursor.executemany(sql, [(this_id, i) for i in id_list])
db.db.commit()
@@ -975,7 +1004,10 @@ def method_set_related_many_to_many(rel_opts, rel_field, self, id_list):
def method_set_order(ordered_obj, self, id_list):
cursor = db.db.cursor()
# Example: "UPDATE poll_choices SET _order = %s WHERE poll_id = %s AND id = %s"
- sql = "UPDATE %s SET _order = %%s WHERE %s = %%s AND %s = %%s" % (ordered_obj.db_table, ordered_obj.order_with_respect_to.column, ordered_obj.pk.column)
+ sql = "UPDATE %s SET %s = %%s WHERE %s = %%s AND %s = %%s" % \
+ (db.db.quote_name(ordered_obj.db_table), db.db.quote_name('_order'),
+ db.db.quote_name(ordered_obj.order_with_respect_to.column),
+ db.db.quote_name(ordered_obj.pk.column))
rel_val = getattr(self, ordered_obj.order_with_respect_to.rel.field_name)
cursor.executemany(sql, [(i, rel_val, j) for i, j in enumerate(id_list)])
db.db.commit()
@@ -983,7 +1015,11 @@ def method_set_order(ordered_obj, self, id_list):
def method_get_order(ordered_obj, self):
cursor = db.db.cursor()
# Example: "SELECT id FROM poll_choices WHERE poll_id = %s ORDER BY _order"
- sql = "SELECT %s FROM %s WHERE %s = %%s ORDER BY _order" % (ordered_obj.pk.column, ordered_obj.db_table, ordered_obj.order_with_respect_to.column)
+ sql = "SELECT %s FROM %s WHERE %s = %%s ORDER BY %s" % \
+ (db.db.quote_name(ordered_obj.pk.column),
+ db.db.quote_name(ordered_obj.db_table),
+ db.db.quote_name(ordered_obj.order_with_respect_to.column),
+ db.db.quote_name('_order'))
rel_val = getattr(self, ordered_obj.order_with_respect_to.rel.field_name)
cursor.execute(sql, [rel_val])
return [r[0] for r in cursor.fetchall()]
@@ -993,7 +1029,8 @@ def method_get_order(ordered_obj, self):
def method_get_next_or_previous(get_object_func, opts, field, is_next, self, **kwargs):
op = is_next and '>' or '<'
kwargs.setdefault('where', []).append('(%s %s %%s OR (%s = %%s AND %s %s %%s))' % \
- (field.column, op, field.column, opts.pk.column, op))
+ (db.db.quote_name(field.column), op, db.db.quote_name(field.column),
+ db.db.quote_name(opts.pk.column), op))
param = str(getattr(self, field.attname))
kwargs.setdefault('params', []).extend([param, param, getattr(self, opts.pk.attname)])
kwargs['order_by'] = [(not is_next and '-' or '') + field.name, (not is_next and '-' or '') + opts.pk.name]
@@ -1080,6 +1117,9 @@ def get_absolute_url(opts, func, self):
return settings.ABSOLUTE_URL_OVERRIDES.get('%s.%s' % (opts.app_label, opts.module_name), func)(self)
def _get_where_clause(lookup_type, table_prefix, field_name, value):
+ if table_prefix.endswith('.'):
+ table_prefix = db.db.quote_name(table_prefix[:-1])+'.'
+ field_name = db.db.quote_name(field_name)
try:
return '%s%s %s %%s' % (table_prefix, field_name, db.OPERATOR_MAPPING[lookup_type])
except KeyError:
@@ -1160,7 +1200,7 @@ def function_get_values_iterator(opts, klass, **kwargs):
cursor = db.db.cursor()
_, sql, params = function_get_sql_clause(opts, **kwargs)
- select = ['%s.%s' % (opts.db_table, f) for f in fields]
+ select = ['%s.%s' % (db.db.quote_name(opts.db_table), db.db.quote_name(f)) for f in fields]
cursor.execute("SELECT " + (kwargs.get('distinct') and "DISTINCT " or "") + ",".join(select) + sql, params)
while 1:
rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
@@ -1181,14 +1221,16 @@ def _fill_table_cache(opts, select, tables, where, old_prefix, cache_tables_seen
if f.rel and not f.null:
db_table = f.rel.to.db_table
if db_table not in cache_tables_seen:
- tables.append(db_table)
+ tables.append(db.db.quote_name(db_table))
else: # The table was already seen, so give it a table alias.
new_prefix = '%s%s' % (db_table, len(cache_tables_seen))
- tables.append('%s %s' % (db_table, new_prefix))
+ tables.append('%s %s' % (db.db.quote_name(db_table), db.db.quote_name(new_prefix)))
db_table = new_prefix
cache_tables_seen.append(db_table)
- where.append('%s.%s = %s.%s' % (old_prefix, f.column, db_table, f.rel.get_related_field().column))
- select.extend(['%s.%s' % (db_table, f2.column) for f2 in f.rel.to.fields])
+ where.append('%s.%s = %s.%s' % \
+ (db.db.quote_name(old_prefix), db.db.quote_name(f.column),
+ db.db.quote_name(db_table), db.db.quote_name(f.rel.get_related_field().column)))
+ select.extend(['%s.%s' % (db.db.quote_name(db_table), db.db.quote_name(f2.column)) for f2 in f.rel.to.fields])
_fill_table_cache(f.rel.to, select, tables, where, db_table, cache_tables_seen)
def _throw_bad_kwarg_error(kwarg):
@@ -1247,11 +1289,15 @@ def _parse_lookup(kwarg_items, opts, table_count=0):
# Try many-to-many relationships first...
for f in current_opts.many_to_many:
if f.name == current:
- rel_table_alias = 't%s' % table_count
+ rel_table_alias = db.db.quote_name('t%s' % table_count)
table_count += 1
- tables.append('%s %s' % (f.get_m2m_db_table(current_opts), rel_table_alias))
- join_where.append('%s.%s = %s.%s_id' % (current_table_alias, current_opts.pk.column,
- rel_table_alias, current_opts.object_name.lower()))
+ tables.append('%s %s' % \
+ (db.db.quote_name(f.get_m2m_db_table(current_opts)), rel_table_alias))
+ join_where.append('%s.%s = %s.%s' % \
+ (db.db.quote_name(current_table_alias),
+ db.db.quote_name(current_opts.pk.column),
+ rel_table_alias,
+ db.db.quote_name(current_opts.object_name.lower() + '_id')))
# Optimization: In the case of primary-key lookups, we
# don't have to do an extra join.
if lookup_list and lookup_list[0] == f.rel.to.pk.name and lookup_type == 'exact':
@@ -1262,9 +1308,13 @@ def _parse_lookup(kwarg_items, opts, table_count=0):
param_required = False
else:
new_table_alias = 't%s' % table_count
- tables.append('%s %s' % (f.rel.to.db_table, new_table_alias))
- join_where.append('%s.%s_id = %s.%s' % (rel_table_alias, f.rel.to.object_name.lower(),
- new_table_alias, f.rel.to.pk.column))
+ tables.append('%s %s' % (db.db.quote_name(f.rel.to.db_table),
+ db.db.quote_name(new_table_alias)))
+ join_where.append('%s.%s = %s.%s' % \
+ (db.db.quote_name(rel_table_alias),
+ db.db.quote_name(f.rel.to.object_name.lower() + '_id'),
+ db.db.quote_name(new_table_alias),
+ db.db.quote_name(f.rel.to.pk.column)))
current_table_alias = new_table_alias
param_required = True
current_opts = f.rel.to
@@ -1287,9 +1337,11 @@ def _parse_lookup(kwarg_items, opts, table_count=0):
params.extend(f.get_db_prep_lookup(lookup_type, kwarg_value))
else:
new_table_alias = 't%s' % table_count
- tables.append('%s %s' % (f.rel.to.db_table, new_table_alias))
- join_where.append('%s.%s = %s.%s' % (current_table_alias, f.column, \
- new_table_alias, f.rel.to.pk.column))
+ tables.append('%s %s' % \
+ (db.db.quote_name(f.rel.to.db_table), db.db.quote_name(new_table_alias)))
+ join_where.append('%s.%s = %s.%s' % \
+ (db.db.quote_name(current_table_alias), db.db.quote_name(f.column),
+ db.db.quote_name(new_table_alias), db.db.quote_name(f.rel.to.pk.column)))
current_table_alias = new_table_alias
param_required = True
current_opts = f.rel.to
@@ -1308,8 +1360,9 @@ def _parse_lookup(kwarg_items, opts, table_count=0):
return tables, join_where, where, params, table_count
def function_get_sql_clause(opts, **kwargs):
- select = ["%s.%s" % (opts.db_table, f.column) for f in opts.fields]
+ select = ["%s.%s" % (db.db.quote_name(opts.db_table), db.db.quote_name(f.column)) for f in opts.fields]
tables = [opts.db_table] + (kwargs.get('tables') and kwargs['tables'][:] or [])
+ tables = [db.db.quote_name(t) for t in tables]
where = kwargs.get('where') and kwargs['where'][:] or []
params = kwargs.get('params') and kwargs['params'][:] or []
@@ -1328,7 +1381,7 @@ def function_get_sql_clause(opts, **kwargs):
# Add any additional SELECTs passed in via kwargs.
if kwargs.get('select'):
- select.extend(['(%s) AS %s' % (s[1], s[0]) for s in kwargs['select']])
+ select.extend(['(%s) AS %s' % (db.db.quote_name(s[1]), db.db.quote_name(s[0])) for s in kwargs['select']])
# ORDER BY clause
order_by = []
@@ -1342,13 +1395,17 @@ def function_get_sql_clause(opts, **kwargs):
else:
col_name = f
order = "ASC"
- # 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 [k[0] for k in kwargs.get('select', [])]:
- table_prefix = opts.db_table + '.'
+ if "." in col_name:
+ table_prefix, col_name = col_name.split('.', 1)
+ table_prefix = db.db.quote_name(table_prefix) + '.'
else:
- table_prefix = ''
- order_by.append('%s%s %s' % (table_prefix, orderfield2column(col_name, opts), order))
+ # 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 [k[0] for k in kwargs.get('select', [])]:
+ table_prefix = db.db.quote_name(opts.db_table) + '.'
+ else:
+ table_prefix = ''
+ order_by.append('%s%s %s' % (table_prefix, db.db.quote_name(orderfield2column(col_name, opts)), order))
order_by = ", ".join(order_by)
# LIMIT and OFFSET clauses
@@ -1363,7 +1420,7 @@ def function_get_sql_clause(opts, **kwargs):
def function_get_in_bulk(opts, klass, *args, **kwargs):
id_list = args and args[0] or kwargs['id_list']
assert id_list != [], "get_in_bulk() cannot be passed an empty list."
- kwargs['where'] = ["%s.%s IN (%s)" % (opts.db_table, opts.pk.column, ",".join(['%s'] * len(id_list)))]
+ kwargs['where'] = ["%s.%s IN (%s)" % (db.db.quote_name(opts.db_table), db.db.quote_name(opts.pk.column), ",".join(['%s'] * len(id_list)))]
kwargs['params'] = id_list
obj_list = function_get_list(opts, klass, **kwargs)
return dict([(getattr(o, opts.pk.attname), o) for o in obj_list])
@@ -1384,9 +1441,10 @@ def function_get_date_list(opts, field, *args, **kwargs):
assert order in ('ASC', 'DESC'), "'order' must be either 'ASC' or 'DESC'"
kwargs['order_by'] = [] # Clear this because it'll mess things up otherwise.
if field.null:
- kwargs.setdefault('where', []).append('%s.%s IS NOT NULL' % (opts.db_table, field.column))
+ kwargs.setdefault('where', []).append('%s.%s IS NOT NULL' % \
+ (db.db.quote_name(opts.db_table), db.db.quote_name(field.column)))
select, sql, params = function_get_sql_clause(opts, **kwargs)
- sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1' % (db.get_date_trunc_sql(kind, '%s.%s' % (opts.db_table, field.column)), sql)
+ sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1' % (db.get_date_trunc_sql(kind, '%s.%s' % (db.db.quote_name(opts.db_table), db.db.quote_name(field.column))), sql)
cursor = db.db.cursor()
cursor.execute(sql, params)
# We have to manually run typecast_timestamp(str()) on the results, because
diff --git a/django/models/auth.py b/django/models/auth.py
index ef13fbea69..d0c13f66ce 100644
--- a/django/models/auth.py
+++ b/django/models/auth.py
@@ -91,12 +91,25 @@ class User(meta.Model):
if not hasattr(self, '_group_perm_cache'):
import sets
cursor = db.cursor()
- cursor.execute("""
- SELECT p.package, p.codename
- FROM auth_permissions p, auth_groups_permissions gp, auth_users_groups ug
- WHERE p.id = gp.permission_id
- AND gp.group_id = ug.group_id
- AND ug.user_id = %s""", [self.id])
+ # The SQL below works out to the following, after DB quoting:
+ # cursor.execute("""
+ # SELECT p.package, p.codename
+ # FROM auth_permissions p, auth_groups_permissions gp, auth_users_groups ug
+ # WHERE p.id = gp.permission_id
+ # AND gp.group_id = ug.group_id
+ # AND ug.user_id = %s""", [self.id])
+ sql = """
+ SELECT p.%s, p.%s
+ FROM %s p, %s gp, %s ug
+ WHERE p.%s = gp.%s
+ AND gp.%s = ug.%s
+ AND ug.%s = %%s""" % (
+ db.quote_name('package'), db.quote_name('codename'),
+ db.quote_name('auth_permissions'), db.quote_name('auth_groups_permissions'),
+ db.quote_name('auth_users_groups'), db.quote_name('id'),
+ db.quote_name('permission_id'), db.quote_name('group_id'),
+ db.quote_name('group_id'), db.quote_name('user_id'))
+ cursor.execute(sql, [self.id])
self._group_perm_cache = sets.Set(["%s.%s" % (row[0], row[1]) for row in cursor.fetchall()])
return self._group_perm_cache
diff --git a/docs/model-api.txt b/docs/model-api.txt
index 51086e426e..1864de7e6a 100644
--- a/docs/model-api.txt
+++ b/docs/model-api.txt
@@ -34,15 +34,21 @@ Django will use ``first_name`` and ``last_name`` as the database column names.
Each field type, except for ``ForeignKey``, ``ManyToManyField`` and
``OneToOneField``, takes an optional first positional argument -- a
-human-readable name. If the human-readable name isn't given, Django will use
-the machine-readable name, converting underscores to spaces.
+human-readable name. If the human-readable name isn't given, Django will
+automatically create the human-readable name by using the machine-readable
+name, converting underscores to spaces.
-Example::
+In this example, the human-readable name is ``"Person's first name"``::
first_name = meta.CharField("Person's first name", maxlength=30)
-For ``ForeignKey``, ``ManyToManyField`` and ``OneToOneField``, use the
-``verbose_name`` keyword argument::
+In this example, the human-readable name is ``"first name"``::
+
+ first_name = meta.CharField(maxlength=30)
+
+``ForeignKey``, ``ManyToManyField`` and ``OneToOneField`` require the first
+argument to be a model class, so use the ``verbose_name`` keyword argument to
+specify the human-readable name::
poll = meta.ForeignKey(Poll, verbose_name="the related poll")
sites = meta.ManyToManyField(Site, verbose_name="list of sites")
@@ -111,6 +117,11 @@ The following arguments are available to all field types. All are optional.
The name of the database column to use for this field. If this isn't given,
Django will use the field's name.
+ If your database column name is an SQL reserved word, or contains
+ characters that aren't allowed in Python variable names -- notably, the
+ hyphen -- that's OK. Django quotes column and table names behind the
+ scenes.
+
``db_index``
If ``True``, ``django-admin.py sqlindexes`` will output a ``CREATE INDEX``
statement for this field.
@@ -700,6 +711,10 @@ Here's a list of all possible ``META`` options. No options are required. Adding
If this isn't given, Django will use ``app_label + '_' + module_name``.
+ If your database table name is an SQL reserved word, or contains characters
+ that aren't allowed in Python variable names -- notably, the hyphen --
+ that's OK. Django quotes column and table names behind the scenes.
+
``exceptions``
Names of extra exception subclasses to include in the generated module.
These exceptions are available from instance methods and from module-level
@@ -732,8 +747,8 @@ Here's a list of all possible ``META`` options. No options are required. Adding
module_name = "pizza_orders"
If this isn't given, Django will use a lowercased version of the class
- name, plus "s". This "poor man's pluralization" is intentional: Any other
- level of magic pluralization would get confusing.
+ name, plus ``"s"``. This "poor man's pluralization" is intentional: Any
+ other level of magic pluralization would get confusing.
``order_with_respect_to``
Marks this object as "orderable" with respect to the given field. This is
diff --git a/tests/testapp/models/__init__.py b/tests/testapp/models/__init__.py
index baa048d123..c0e39fae7d 100644
--- a/tests/testapp/models/__init__.py
+++ b/tests/testapp/models/__init__.py
@@ -1,4 +1,4 @@
__all__ = ['basic', 'repr', 'custom_methods', 'many_to_one', 'many_to_many',
'ordering', 'lookup', 'get_latest', 'm2m_intermediary', 'one_to_one',
'm2o_recursive', 'm2o_recursive2', 'save_delete_hooks', 'custom_pk',
- 'subclassing', 'many_to_one_null', 'custom_columns']
+ 'subclassing', 'many_to_one_null', 'custom_columns', 'reserved_names']
diff --git a/tests/testapp/models/reserved_names.py b/tests/testapp/models/reserved_names.py
new file mode 100644
index 0000000000..eabe41e5bd
--- /dev/null
+++ b/tests/testapp/models/reserved_names.py
@@ -0,0 +1,47 @@
+"""
+18. Using SQL reserved names
+
+Need to use a reserved SQL name as a column name or table name? Need to include
+a hyphen in a column or table name? No problem. Django quotes names
+appropriately behind the scenes, so your database won't complain about
+reserved-name usage.
+"""
+
+from django.core import meta
+
+class Thing(meta.Model):
+ when = meta.CharField(maxlength=1, primary_key=True)
+ join = meta.CharField(maxlength=1)
+ like = meta.CharField(maxlength=1)
+ drop = meta.CharField(maxlength=1)
+ alter = meta.CharField(maxlength=1)
+ having = meta.CharField(maxlength=1)
+ where = meta.CharField(maxlength=1)
+ has_hyphen = meta.CharField(maxlength=1, db_column='has-hyphen')
+ class META:
+ db_table = 'select'
+
+ def __repr__(self):
+ return self.when
+
+API_TESTS = """
+>>> t = things.Thing(when='a', join='b', like='c', drop='d', alter='e', having='f', where='g', has_hyphen='h')
+>>> t.save()
+>>> print t.when
+a
+
+>>> u = things.Thing(when='h', join='i', like='j', drop='k', alter='l', having='m', where='n')
+>>> u.save()
+>>> print u.when
+h
+
+>>> things.get_list(order_by=['when'])
+[a, h]
+>>> v = things.get_object(pk='a')
+>>> print v.join
+b
+>>> print v.where
+g
+>>> things.get_list(order_by=['select.when'])
+[a, h]
+"""