Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Added a db_type() method to the database Field class. This is a hook …

…for calculating the database column type for a given Field. Also converted all management.py CREATE TABLE statements to use db_type(), which made that code cleaner. The Field.get_internal_type() hook still exists, but we should consider removing it at some point, because db_type() is more general. Also added docs -- the beginnings of docs on how to create custom database Field classes. This is backwards-compatible.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@5725 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit ac2b9f2a3fd2a6c8dd3f87a6302b7d3f2ef73a0a 1 parent f5ef3be
Adrian Holovaty authored July 20, 2007
52  django/core/management.py
@@ -95,19 +95,12 @@ def _get_sequence_list():
95 95
 
96 96
     return sequence_list
97 97
 
98  
-# If the foreign key points to an AutoField, a PositiveIntegerField or a
99  
-# PositiveSmallIntegerField, the foreign key should be an IntegerField, not the
100  
-# referred field type. Otherwise, the foreign key should be the same type of
101  
-# field as the field to which it points.
102  
-get_rel_data_type = lambda f: (f.get_internal_type() in ('AutoField', 'PositiveIntegerField', 'PositiveSmallIntegerField')) and 'IntegerField' or f.get_internal_type()
103  
-
104 98
 def get_sql_create(app):
105 99
     "Returns a list of the CREATE TABLE SQL statements for the given app."
106  
-    from django.db import get_creation_module, models
107  
-
108  
-    data_types = get_creation_module().DATA_TYPES
  100
+    from django.db import models
  101
+    from django.conf import settings
109 102
 
110  
-    if not data_types:
  103
+    if settings.DATABASE_ENGINE == 'dummy':
111 104
         # This must be the "dummy" database backend, which means the user
112 105
         # hasn't set DATABASE_ENGINE.
113 106
         sys.stderr.write(style.ERROR("Error: Django doesn't know which syntax to use for your SQL statements,\n" +
@@ -159,28 +152,19 @@ def _get_sql_model_create(model, known_models=set()):
159 152
 
160 153
     Returns list_of_sql, pending_references_dict
161 154
     """
162  
-    from django.db import backend, get_creation_module, models
163  
-    data_types = get_creation_module().DATA_TYPES
  155
+    from django.db import backend, models
164 156
 
165 157
     opts = model._meta
166 158
     final_output = []
167 159
     table_output = []
168 160
     pending_references = {}
169 161
     for f in opts.fields:
170  
-        if isinstance(f, (models.ForeignKey, models.OneToOneField)):
171  
-            rel_field = f.rel.get_related_field()
172  
-            while isinstance(rel_field, (models.ForeignKey, models.OneToOneField)):
173  
-                rel_field = rel_field.rel.get_related_field()
174  
-            data_type = get_rel_data_type(rel_field)
175  
-        else:
176  
-            rel_field = f
177  
-            data_type = f.get_internal_type()
178  
-        col_type = data_types[data_type]
  162
+        col_type = f.db_type()
179 163
         tablespace = f.db_tablespace or opts.db_tablespace
180 164
         if col_type is not None:
181 165
             # Make the definition (e.g. 'foo VARCHAR(30)') for this field.
182 166
             field_output = [style.SQL_FIELD(backend.quote_name(f.column)),
183  
-                style.SQL_COLTYPE(col_type % rel_field.__dict__)]
  167
+                style.SQL_COLTYPE(col_type)]
184 168
             field_output.append(style.SQL_KEYWORD('%sNULL' % (not f.null and 'NOT ' or '')))
185 169
             if f.unique and (not f.primary_key or backend.allows_unique_and_pk):
186 170
                 field_output.append(style.SQL_KEYWORD('UNIQUE'))
@@ -204,7 +188,7 @@ def _get_sql_model_create(model, known_models=set()):
204 188
             table_output.append(' '.join(field_output))
205 189
     if opts.order_with_respect_to:
206 190
         table_output.append(style.SQL_FIELD(backend.quote_name('_order')) + ' ' + \
207  
-            style.SQL_COLTYPE(data_types['IntegerField']) + ' ' + \
  191
+            style.SQL_COLTYPE(models.IntegerField().db_type()) + ' ' + \
208 192
             style.SQL_KEYWORD('NULL'))
209 193
     for field_constraints in opts.unique_together:
210 194
         table_output.append(style.SQL_KEYWORD('UNIQUE') + ' (%s)' % \
@@ -232,9 +216,8 @@ def _get_sql_for_pending_references(model, pending_references):
232 216
     """
233 217
     Get any ALTER TABLE statements to add constraints after the fact.
234 218
     """
235  
-    from django.db import backend, get_creation_module
  219
+    from django.db import backend
236 220
     from django.db.backends.util import truncate_name
237  
-    data_types = get_creation_module().DATA_TYPES
238 221
 
239 222
     final_output = []
240 223
     if backend.supports_constraints:
@@ -257,11 +240,9 @@ def _get_sql_for_pending_references(model, pending_references):
257 240
     return final_output
258 241
 
259 242
 def _get_many_to_many_sql_for_model(model):
260  
-    from django.db import backend, get_creation_module
  243
+    from django.db import backend, models
261 244
     from django.contrib.contenttypes import generic
262 245
 
263  
-    data_types = get_creation_module().DATA_TYPES
264  
-
265 246
     opts = model._meta
266 247
     final_output = []
267 248
     for f in opts.many_to_many:
@@ -275,19 +256,19 @@ def _get_many_to_many_sql_for_model(model):
275 256
                 style.SQL_TABLE(backend.quote_name(f.m2m_db_table())) + ' (']
276 257
             table_output.append('    %s %s %s%s,' % \
277 258
                 (style.SQL_FIELD(backend.quote_name('id')),
278  
-                style.SQL_COLTYPE(data_types['AutoField']),
  259
+                style.SQL_COLTYPE(models.AutoField(primary_key=True).db_type()),
279 260
                 style.SQL_KEYWORD('NOT NULL PRIMARY KEY'),
280 261
                 tablespace_sql))
281 262
             table_output.append('    %s %s %s %s (%s)%s,' % \
282 263
                 (style.SQL_FIELD(backend.quote_name(f.m2m_column_name())),
283  
-                style.SQL_COLTYPE(data_types[get_rel_data_type(opts.pk)] % opts.pk.__dict__),
  264
+                style.SQL_COLTYPE(models.ForeignKey(model).db_type()),
284 265
                 style.SQL_KEYWORD('NOT NULL REFERENCES'),
285 266
                 style.SQL_TABLE(backend.quote_name(opts.db_table)),
286 267
                 style.SQL_FIELD(backend.quote_name(opts.pk.column)),
287 268
                 backend.get_deferrable_sql()))
288 269
             table_output.append('    %s %s %s %s (%s)%s,' % \
289 270
                 (style.SQL_FIELD(backend.quote_name(f.m2m_reverse_name())),
290  
-                style.SQL_COLTYPE(data_types[get_rel_data_type(f.rel.to._meta.pk)] % f.rel.to._meta.pk.__dict__),
  271
+                style.SQL_COLTYPE(models.ForeignKey(f.rel.to).db_type()),
291 272
                 style.SQL_KEYWORD('NOT NULL REFERENCES'),
292 273
                 style.SQL_TABLE(backend.quote_name(f.rel.to._meta.db_table)),
293 274
                 style.SQL_FIELD(backend.quote_name(f.rel.to._meta.pk.column)),
@@ -517,7 +498,7 @@ def _emit_post_sync_signal(created_models, verbosity, interactive):
517 498
 
518 499
 def syncdb(verbosity=1, interactive=True):
519 500
     "Creates the database tables for all apps in INSTALLED_APPS whose tables haven't already been created."
520  
-    from django.db import backend, connection, transaction, models, get_creation_module
  501
+    from django.db import backend, connection, transaction, models
521 502
     from django.conf import settings
522 503
 
523 504
     disable_termcolors()
@@ -533,8 +514,6 @@ def syncdb(verbosity=1, interactive=True):
533 514
         except ImportError:
534 515
             pass
535 516
 
536  
-    data_types = get_creation_module().DATA_TYPES
537  
-
538 517
     cursor = connection.cursor()
539 518
 
540 519
     # Get a list of all existing database tables,
@@ -1266,8 +1245,7 @@ def inner_run():
1266 1245
 
1267 1246
 def createcachetable(tablename):
1268 1247
     "Creates the table needed to use the SQL cache backend"
1269  
-    from django.db import backend, connection, transaction, get_creation_module, models
1270  
-    data_types = get_creation_module().DATA_TYPES
  1248
+    from django.db import backend, connection, transaction, models
1271 1249
     fields = (
1272 1250
         # "key" is a reserved word in MySQL, so use "cache_key" instead.
1273 1251
         models.CharField(name='cache_key', maxlength=255, unique=True, primary_key=True),
@@ -1277,7 +1255,7 @@ def createcachetable(tablename):
1277 1255
     table_output = []
1278 1256
     index_output = []
1279 1257
     for f in fields:
1280  
-        field_output = [backend.quote_name(f.name), data_types[f.get_internal_type()] % f.__dict__]
  1258
+        field_output = [backend.quote_name(f.name), f.db_type()]
1281 1259
         field_output.append("%sNULL" % (not f.null and "NOT " or ""))
1282 1260
         if f.unique:
1283 1261
             field_output.append("UNIQUE")
1  django/db/backends/ado_mssql/creation.py
@@ -12,7 +12,6 @@
12 12
     'ImageField':        'varchar(100)',
13 13
     'IntegerField':      'int',
14 14
     'IPAddressField':    'char(15)',
15  
-    'ManyToManyField':   None,
16 15
     'NullBooleanField':  'bit',
17 16
     'OneToOneField':     'int',
18 17
     'PhoneNumberField':  'varchar(20)',
1  django/db/backends/mysql/creation.py
@@ -16,7 +16,6 @@
16 16
     'ImageField':        'varchar(100)',
17 17
     'IntegerField':      'integer',
18 18
     'IPAddressField':    'char(15)',
19  
-    'ManyToManyField':   None,
20 19
     'NullBooleanField':  'bool',
21 20
     'OneToOneField':     'integer',
22 21
     'PhoneNumberField':  'varchar(20)',
1  django/db/backends/mysql_old/creation.py
@@ -16,7 +16,6 @@
16 16
     'ImageField':        'varchar(100)',
17 17
     'IntegerField':      'integer',
18 18
     'IPAddressField':    'char(15)',
19  
-    'ManyToManyField':   None,
20 19
     'NullBooleanField':  'bool',
21 20
     'OneToOneField':     'integer',
22 21
     'PhoneNumberField':  'varchar(20)',
1  django/db/backends/oracle/creation.py
@@ -19,7 +19,6 @@
19 19
     'ImageField':                   'NVARCHAR2(100)',
20 20
     'IntegerField':                 'NUMBER(11)',
21 21
     'IPAddressField':               'VARCHAR2(15)',
22  
-    'ManyToManyField':              None,
23 22
     'NullBooleanField':             'NUMBER(1) CHECK ((%(column)s IN (0,1)) OR (%(column)s IS NULL))',
24 23
     'OneToOneField':                'NUMBER(11)',
25 24
     'PhoneNumberField':             'VARCHAR2(20)',
1  django/db/backends/postgresql/creation.py
@@ -16,7 +16,6 @@
16 16
     'ImageField':        'varchar(100)',
17 17
     'IntegerField':      'integer',
18 18
     'IPAddressField':    'inet',
19  
-    'ManyToManyField':   None,
20 19
     'NullBooleanField':  'boolean',
21 20
     'OneToOneField':     'integer',
22 21
     'PhoneNumberField':  'varchar(20)',
1  django/db/backends/sqlite3/creation.py
@@ -15,7 +15,6 @@
15 15
     'ImageField':                   'varchar(100)',
16 16
     'IntegerField':                 'integer',
17 17
     'IPAddressField':               'char(15)',
18  
-    'ManyToManyField':              None,
19 18
     'NullBooleanField':             'bool',
20 19
     'OneToOneField':                'integer',
21 20
     'PhoneNumberField':             'varchar(20)',
25  django/db/models/fields/__init__.py
... ...
@@ -1,3 +1,4 @@
  1
+from django.db import get_creation_module
1 2
 from django.db.models import signals
2 3
 from django.dispatch import dispatcher
3 4
 from django.conf import settings
@@ -117,6 +118,30 @@ def to_python(self, value):
117 118
         """
118 119
         return value
119 120
 
  121
+    def db_type(self):
  122
+        """
  123
+        Returns the database column data type for this field, taking into
  124
+        account the DATABASE_ENGINE setting.
  125
+        """
  126
+        # The default implementation of this method looks at the
  127
+        # backend-specific DATA_TYPES dictionary, looking up the field by its
  128
+        # "internal type".
  129
+        #
  130
+        # A Field class can implement the get_internal_type() method to specify
  131
+        # which *preexisting* Django Field class it's most similar to -- i.e.,
  132
+        # an XMLField is represented by a TEXT column type, which is the same
  133
+        # as the TextField Django field type, which means XMLField's
  134
+        # get_internal_type() returns 'TextField'.
  135
+        #
  136
+        # But the limitation of the get_internal_type() / DATA_TYPES approach
  137
+        # is that it cannot handle database column types that aren't already
  138
+        # mapped to one of the built-in Django field types. In this case, you
  139
+        # can implement db_type() instead of get_internal_type() to specify
  140
+        # exactly which wacky database column type you want to use.
  141
+        data_types = get_creation_module().DATA_TYPES
  142
+        internal_type = self.get_internal_type()
  143
+        return data_types[internal_type] % self.__dict__
  144
+
120 145
     def validate_full(self, field_data, all_data):
121 146
         """
122 147
         Returns a list of errors for this field. This is the main interface,
27  django/db/models/fields/related.py
... ...
@@ -1,6 +1,6 @@
@@ -556,6 +556,16 @@ def formfield(self, **kwargs):
@@ -622,6 +632,16 @@ def formfield(self, **kwargs):
@@ -745,6 +765,11 @@ def formfield(self, **kwargs):
71  docs/model-api.txt
@@ -981,6 +981,77 @@ See the `One-to-one relationship model example`_ for a full example.
981 981
 
982 982
 .. _One-to-one relationship model example: http://www.djangoproject.com/documentation/models/one_to_one/
983 983
 
  984
+Custom field types
  985
+------------------
  986
+
  987
+**New in Django development version**
  988
+
  989
+Django's built-in field types don't cover every possible database column type --
  990
+only the common types, such as ``VARCHAR`` and ``INTEGER``. For more obscure
  991
+column types, such as geographic polygons or even user-created types such as
  992
+`PostgreSQL custom types`_, you can define your own Django ``Field`` subclasses.
  993
+
  994
+.. _PostgreSQL custom types: http://www.postgresql.org/docs/8.2/interactive/sql-createtype.html
  995
+
  996
+.. admonition:: Experimental territory
  997
+
  998
+    This is an area of Django that traditionally has not been documented, but
  999
+    we're starting to include bits of documentation, one feature at a time.
  1000
+    Please forgive the sparseness of this section.
  1001
+
  1002
+    If you like living on the edge and are comfortable with the risk of
  1003
+    unstable, undocumented APIs, see the code for the core ``Field`` class
  1004
+    in ``django/db/models/fields/__init__.py`` -- but if/when the innards
  1005
+    change, don't say we didn't warn you.
  1006
+
  1007
+To create a custom field type, simply subclass ``django.db.models.Field``.
  1008
+Here is an incomplete list of the methods you should implement:
  1009
+
  1010
+``db_type()``
  1011
+~~~~~~~~~~~~~
  1012
+
  1013
+Returns the database column data type for the ``Field``, taking into account
  1014
+the current ``DATABASE_ENGINE`` setting.
  1015
+
  1016
+Say you've created a PostgreSQL custom type called ``mytype``. You can use this
  1017
+field with Django by subclassing ``Field`` and implementing the ``db_type()``
  1018
+method, like so::
  1019
+
  1020
+    from django.db import models
  1021
+
  1022
+    class MytypeField(models.Field):
  1023
+        def db_type(self):
  1024
+            return 'mytype'
  1025
+
  1026
+Once you have ``MytypeField``, you can use it in any model, just like any other
  1027
+``Field`` type::
  1028
+
  1029
+    class Person(models.Model):
  1030
+        name = models.CharField(maxlength=80)
  1031
+        gender = models.CharField(maxlength=1)
  1032
+        something_else = MytypeField()
  1033
+
  1034
+If you aim to build a database-agnostic application, you should account for
  1035
+differences in database column types. For example, the date/time column type
  1036
+in PostgreSQL is called ``timestamp``, while the same column in MySQL is called
  1037
+``datetime``. The simplest way to handle this in a ``db_type()`` method is to
  1038
+import the Django settings module and check the ``DATABASE_ENGINE`` setting.
  1039
+For example::
  1040
+
  1041
+    class MyDateField(models.Field):
  1042
+        def db_type(self):
  1043
+            from django.conf import settings
  1044
+            if settings.DATABASE_ENGINE == 'mysql':
  1045
+                return 'datetime'
  1046
+            else:
  1047
+                return 'timestamp'
  1048
+
  1049
+The ``db_type()`` method is only called by Django when the framework constructs
  1050
+the ``CREATE TABLE`` statements for your application -- that is, when you first
  1051
+create your tables. It's not called at any other time, so it can afford to
  1052
+execute slightly complex code, such as the ``DATABASE_ENGINE`` check in the
  1053
+above example.
  1054
+
984 1055
 Meta options
985 1056
 ============
986 1057
 

0 notes on commit ac2b9f2

Please sign in to comment.
Something went wrong with that request. Please try again.