mirror of
				https://github.com/django/django.git
				synced 2025-10-31 01:25:32 +00:00 
			
		
		
		
	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
This commit is contained in:
		| @@ -95,19 +95,12 @@ def _get_sequence_list(): | ||||
|  | ||||
|     return sequence_list | ||||
|  | ||||
| # If the foreign key points to an AutoField, a PositiveIntegerField or a | ||||
| # PositiveSmallIntegerField, the foreign key should be an IntegerField, not the | ||||
| # referred field type. Otherwise, the foreign key should be the same type of | ||||
| # field as the field to which it points. | ||||
| get_rel_data_type = lambda f: (f.get_internal_type() in ('AutoField', 'PositiveIntegerField', 'PositiveSmallIntegerField')) and 'IntegerField' or f.get_internal_type() | ||||
|  | ||||
| def get_sql_create(app): | ||||
|     "Returns a list of the CREATE TABLE SQL statements for the given app." | ||||
|     from django.db import get_creation_module, models | ||||
|     from django.db import models | ||||
|     from django.conf import settings | ||||
|  | ||||
|     data_types = get_creation_module().DATA_TYPES | ||||
|  | ||||
|     if not data_types: | ||||
|     if settings.DATABASE_ENGINE == 'dummy': | ||||
|         # This must be the "dummy" database backend, which means the user | ||||
|         # hasn't set DATABASE_ENGINE. | ||||
|         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()): | ||||
|  | ||||
|     Returns list_of_sql, pending_references_dict | ||||
|     """ | ||||
|     from django.db import backend, get_creation_module, models | ||||
|     data_types = get_creation_module().DATA_TYPES | ||||
|     from django.db import backend, models | ||||
|  | ||||
|     opts = model._meta | ||||
|     final_output = [] | ||||
|     table_output = [] | ||||
|     pending_references = {} | ||||
|     for f in opts.fields: | ||||
|         if isinstance(f, (models.ForeignKey, models.OneToOneField)): | ||||
|             rel_field = f.rel.get_related_field() | ||||
|             while isinstance(rel_field, (models.ForeignKey, models.OneToOneField)): | ||||
|                 rel_field = rel_field.rel.get_related_field() | ||||
|             data_type = get_rel_data_type(rel_field) | ||||
|         else: | ||||
|             rel_field = f | ||||
|             data_type = f.get_internal_type() | ||||
|         col_type = data_types[data_type] | ||||
|         col_type = f.db_type() | ||||
|         tablespace = f.db_tablespace or opts.db_tablespace | ||||
|         if col_type is not None: | ||||
|             # Make the definition (e.g. 'foo VARCHAR(30)') for this field. | ||||
|             field_output = [style.SQL_FIELD(backend.quote_name(f.column)), | ||||
|                 style.SQL_COLTYPE(col_type % rel_field.__dict__)] | ||||
|                 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): | ||||
|                 field_output.append(style.SQL_KEYWORD('UNIQUE')) | ||||
| @@ -204,7 +188,7 @@ def _get_sql_model_create(model, known_models=set()): | ||||
|             table_output.append(' '.join(field_output)) | ||||
|     if opts.order_with_respect_to: | ||||
|         table_output.append(style.SQL_FIELD(backend.quote_name('_order')) + ' ' + \ | ||||
|             style.SQL_COLTYPE(data_types['IntegerField']) + ' ' + \ | ||||
|             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)' % \ | ||||
| @@ -232,9 +216,8 @@ def _get_sql_for_pending_references(model, pending_references): | ||||
|     """ | ||||
|     Get any ALTER TABLE statements to add constraints after the fact. | ||||
|     """ | ||||
|     from django.db import backend, get_creation_module | ||||
|     from django.db import backend | ||||
|     from django.db.backends.util import truncate_name | ||||
|     data_types = get_creation_module().DATA_TYPES | ||||
|  | ||||
|     final_output = [] | ||||
|     if backend.supports_constraints: | ||||
| @@ -257,11 +240,9 @@ def _get_sql_for_pending_references(model, pending_references): | ||||
|     return final_output | ||||
|  | ||||
| def _get_many_to_many_sql_for_model(model): | ||||
|     from django.db import backend, get_creation_module | ||||
|     from django.db import backend, models | ||||
|     from django.contrib.contenttypes import generic | ||||
|  | ||||
|     data_types = get_creation_module().DATA_TYPES | ||||
|  | ||||
|     opts = model._meta | ||||
|     final_output = [] | ||||
|     for f in opts.many_to_many: | ||||
| @@ -275,19 +256,19 @@ def _get_many_to_many_sql_for_model(model): | ||||
|                 style.SQL_TABLE(backend.quote_name(f.m2m_db_table())) + ' ('] | ||||
|             table_output.append('    %s %s %s%s,' % \ | ||||
|                 (style.SQL_FIELD(backend.quote_name('id')), | ||||
|                 style.SQL_COLTYPE(data_types['AutoField']), | ||||
|                 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_COLTYPE(data_types[get_rel_data_type(opts.pk)] % opts.pk.__dict__), | ||||
|                 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())) | ||||
|             table_output.append('    %s %s %s %s (%s)%s,' % \ | ||||
|                 (style.SQL_FIELD(backend.quote_name(f.m2m_reverse_name())), | ||||
|                 style.SQL_COLTYPE(data_types[get_rel_data_type(f.rel.to._meta.pk)] % f.rel.to._meta.pk.__dict__), | ||||
|                 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)), | ||||
| @@ -517,7 +498,7 @@ def _emit_post_sync_signal(created_models, verbosity, interactive): | ||||
|  | ||||
| def syncdb(verbosity=1, interactive=True): | ||||
|     "Creates the database tables for all apps in INSTALLED_APPS whose tables haven't already been created." | ||||
|     from django.db import backend, connection, transaction, models, get_creation_module | ||||
|     from django.db import backend, connection, transaction, models | ||||
|     from django.conf import settings | ||||
|  | ||||
|     disable_termcolors() | ||||
| @@ -533,8 +514,6 @@ def syncdb(verbosity=1, interactive=True): | ||||
|         except ImportError: | ||||
|             pass | ||||
|  | ||||
|     data_types = get_creation_module().DATA_TYPES | ||||
|  | ||||
|     cursor = connection.cursor() | ||||
|  | ||||
|     # Get a list of all existing database tables, | ||||
| @@ -1266,8 +1245,7 @@ runserver.args = '[--noreload] [--adminmedia=ADMIN_MEDIA_PATH] [optional port nu | ||||
|  | ||||
| def createcachetable(tablename): | ||||
|     "Creates the table needed to use the SQL cache backend" | ||||
|     from django.db import backend, connection, transaction, get_creation_module, models | ||||
|     data_types = get_creation_module().DATA_TYPES | ||||
|     from django.db import backend, connection, transaction, models | ||||
|     fields = ( | ||||
|         # "key" is a reserved word in MySQL, so use "cache_key" instead. | ||||
|         models.CharField(name='cache_key', maxlength=255, unique=True, primary_key=True), | ||||
| @@ -1277,7 +1255,7 @@ def createcachetable(tablename): | ||||
|     table_output = [] | ||||
|     index_output = [] | ||||
|     for f in fields: | ||||
|         field_output = [backend.quote_name(f.name), data_types[f.get_internal_type()] % f.__dict__] | ||||
|         field_output = [backend.quote_name(f.name), f.db_type()] | ||||
|         field_output.append("%sNULL" % (not f.null and "NOT " or "")) | ||||
|         if f.unique: | ||||
|             field_output.append("UNIQUE") | ||||
|   | ||||
| @@ -12,7 +12,6 @@ DATA_TYPES = { | ||||
|     'ImageField':        'varchar(100)', | ||||
|     'IntegerField':      'int', | ||||
|     'IPAddressField':    'char(15)', | ||||
|     'ManyToManyField':   None, | ||||
|     'NullBooleanField':  'bit', | ||||
|     'OneToOneField':     'int', | ||||
|     'PhoneNumberField':  'varchar(20)', | ||||
|   | ||||
| @@ -16,7 +16,6 @@ DATA_TYPES = { | ||||
|     'ImageField':        'varchar(100)', | ||||
|     'IntegerField':      'integer', | ||||
|     'IPAddressField':    'char(15)', | ||||
|     'ManyToManyField':   None, | ||||
|     'NullBooleanField':  'bool', | ||||
|     'OneToOneField':     'integer', | ||||
|     'PhoneNumberField':  'varchar(20)', | ||||
|   | ||||
| @@ -16,7 +16,6 @@ DATA_TYPES = { | ||||
|     'ImageField':        'varchar(100)', | ||||
|     'IntegerField':      'integer', | ||||
|     'IPAddressField':    'char(15)', | ||||
|     'ManyToManyField':   None, | ||||
|     'NullBooleanField':  'bool', | ||||
|     'OneToOneField':     'integer', | ||||
|     'PhoneNumberField':  'varchar(20)', | ||||
|   | ||||
| @@ -19,7 +19,6 @@ DATA_TYPES = { | ||||
|     'ImageField':                   'NVARCHAR2(100)', | ||||
|     'IntegerField':                 'NUMBER(11)', | ||||
|     'IPAddressField':               'VARCHAR2(15)', | ||||
|     'ManyToManyField':              None, | ||||
|     'NullBooleanField':             'NUMBER(1) CHECK ((%(column)s IN (0,1)) OR (%(column)s IS NULL))', | ||||
|     'OneToOneField':                'NUMBER(11)', | ||||
|     'PhoneNumberField':             'VARCHAR2(20)', | ||||
|   | ||||
| @@ -16,7 +16,6 @@ DATA_TYPES = { | ||||
|     'ImageField':        'varchar(100)', | ||||
|     'IntegerField':      'integer', | ||||
|     'IPAddressField':    'inet', | ||||
|     'ManyToManyField':   None, | ||||
|     'NullBooleanField':  'boolean', | ||||
|     'OneToOneField':     'integer', | ||||
|     'PhoneNumberField':  'varchar(20)', | ||||
|   | ||||
| @@ -15,7 +15,6 @@ DATA_TYPES = { | ||||
|     'ImageField':                   'varchar(100)', | ||||
|     'IntegerField':                 'integer', | ||||
|     'IPAddressField':               'char(15)', | ||||
|     'ManyToManyField':              None, | ||||
|     'NullBooleanField':             'bool', | ||||
|     'OneToOneField':                'integer', | ||||
|     'PhoneNumberField':             'varchar(20)', | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| from django.db import get_creation_module | ||||
| from django.db.models import signals | ||||
| from django.dispatch import dispatcher | ||||
| from django.conf import settings | ||||
| @@ -117,6 +118,30 @@ class Field(object): | ||||
|         """ | ||||
|         return value | ||||
|  | ||||
|     def db_type(self): | ||||
|         """ | ||||
|         Returns the database column data type for this field, taking into | ||||
|         account the DATABASE_ENGINE setting. | ||||
|         """ | ||||
|         # The default implementation of this method looks at the | ||||
|         # backend-specific DATA_TYPES dictionary, looking up the field by its | ||||
|         # "internal type". | ||||
|         # | ||||
|         # A Field class can implement the get_internal_type() method to specify | ||||
|         # which *preexisting* Django Field class it's most similar to -- i.e., | ||||
|         # an XMLField is represented by a TEXT column type, which is the same | ||||
|         # as the TextField Django field type, which means XMLField's | ||||
|         # get_internal_type() returns 'TextField'. | ||||
|         # | ||||
|         # But the limitation of the get_internal_type() / DATA_TYPES approach | ||||
|         # is that it cannot handle database column types that aren't already | ||||
|         # mapped to one of the built-in Django field types. In this case, you | ||||
|         # can implement db_type() instead of get_internal_type() to specify | ||||
|         # exactly which wacky database column type you want to use. | ||||
|         data_types = get_creation_module().DATA_TYPES | ||||
|         internal_type = self.get_internal_type() | ||||
|         return data_types[internal_type] % self.__dict__ | ||||
|  | ||||
|     def validate_full(self, field_data, all_data): | ||||
|         """ | ||||
|         Returns a list of errors for this field. This is the main interface, | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| from django.db import backend, transaction | ||||
| from django.db.models import signals, get_model | ||||
| from django.db.models.fields import AutoField, Field, IntegerField, get_ul_class | ||||
| from django.db.models.fields import AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField, get_ul_class | ||||
| from django.db.models.related import RelatedObject | ||||
| from django.utils.text import capfirst | ||||
| from django.utils.translation import ugettext_lazy, string_concat, ungettext, ugettext as _ | ||||
| @@ -556,6 +556,16 @@ class ForeignKey(RelatedField, Field): | ||||
|         defaults.update(kwargs) | ||||
|         return super(ForeignKey, self).formfield(**defaults) | ||||
|  | ||||
|     def db_type(self): | ||||
|         # The database column type of a ForeignKey is the column type | ||||
|         # of the field to which it points. An exception is if the ForeignKey | ||||
|         # points to an AutoField/PositiveIntegerField/PositiveSmallIntegerField, | ||||
|         # in which case the column type is simply that of an IntegerField. | ||||
|         rel_field = self.rel.get_related_field() | ||||
|         if isinstance(rel_field, (AutoField, PositiveIntegerField, PositiveSmallIntegerField)): | ||||
|             return IntegerField().db_type() | ||||
|         return rel_field.db_type() | ||||
|  | ||||
| class OneToOneField(RelatedField, IntegerField): | ||||
|     def __init__(self, to, to_field=None, **kwargs): | ||||
|         try: | ||||
| @@ -622,6 +632,16 @@ class OneToOneField(RelatedField, IntegerField): | ||||
|         defaults.update(kwargs) | ||||
|         return super(OneToOneField, self).formfield(**defaults) | ||||
|  | ||||
|     def db_type(self): | ||||
|         # The database column type of a OneToOneField is the column type | ||||
|         # of the field to which it points. An exception is if the OneToOneField | ||||
|         # points to an AutoField/PositiveIntegerField/PositiveSmallIntegerField, | ||||
|         # in which case the column type is simply that of an IntegerField. | ||||
|         rel_field = self.rel.get_related_field() | ||||
|         if isinstance(rel_field, (AutoField, PositiveIntegerField, PositiveSmallIntegerField)): | ||||
|             return IntegerField().db_type() | ||||
|         return rel_field.db_type() | ||||
|  | ||||
| class ManyToManyField(RelatedField, Field): | ||||
|     def __init__(self, to, **kwargs): | ||||
|         kwargs['verbose_name'] = kwargs.get('verbose_name', None) | ||||
| @@ -745,6 +765,11 @@ class ManyToManyField(RelatedField, Field): | ||||
|             defaults['initial'] = [i._get_pk_val() for i in defaults['initial']] | ||||
|         return super(ManyToManyField, self).formfield(**defaults) | ||||
|  | ||||
|     def db_type(self): | ||||
|         # A ManyToManyField is not represented by a single column, | ||||
|         # so return None. | ||||
|         return None | ||||
|  | ||||
| class ManyToOneRel(object): | ||||
|     def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None, | ||||
|         max_num_in_admin=None, num_extra_on_change=1, edit_inline=False, | ||||
|   | ||||
| @@ -981,6 +981,77 @@ See the `One-to-one relationship model example`_ for a full example. | ||||
|  | ||||
| .. _One-to-one relationship model example: http://www.djangoproject.com/documentation/models/one_to_one/ | ||||
|  | ||||
| Custom field types | ||||
| ------------------ | ||||
|  | ||||
| **New in Django development version** | ||||
|  | ||||
| Django's built-in field types don't cover every possible database column type -- | ||||
| only the common types, such as ``VARCHAR`` and ``INTEGER``. For more obscure | ||||
| column types, such as geographic polygons or even user-created types such as | ||||
| `PostgreSQL custom types`_, you can define your own Django ``Field`` subclasses. | ||||
|  | ||||
| .. _PostgreSQL custom types: http://www.postgresql.org/docs/8.2/interactive/sql-createtype.html | ||||
|  | ||||
| .. admonition:: Experimental territory | ||||
|  | ||||
|     This is an area of Django that traditionally has not been documented, but | ||||
|     we're starting to include bits of documentation, one feature at a time. | ||||
|     Please forgive the sparseness of this section. | ||||
|  | ||||
|     If you like living on the edge and are comfortable with the risk of | ||||
|     unstable, undocumented APIs, see the code for the core ``Field`` class | ||||
|     in ``django/db/models/fields/__init__.py`` -- but if/when the innards | ||||
|     change, don't say we didn't warn you. | ||||
|  | ||||
| To create a custom field type, simply subclass ``django.db.models.Field``. | ||||
| Here is an incomplete list of the methods you should implement: | ||||
|  | ||||
| ``db_type()`` | ||||
| ~~~~~~~~~~~~~ | ||||
|  | ||||
| Returns the database column data type for the ``Field``, taking into account | ||||
| the current ``DATABASE_ENGINE`` setting. | ||||
|  | ||||
| Say you've created a PostgreSQL custom type called ``mytype``. You can use this | ||||
| field with Django by subclassing ``Field`` and implementing the ``db_type()`` | ||||
| method, like so:: | ||||
|  | ||||
|     from django.db import models | ||||
|  | ||||
|     class MytypeField(models.Field): | ||||
|         def db_type(self): | ||||
|             return 'mytype' | ||||
|  | ||||
| Once you have ``MytypeField``, you can use it in any model, just like any other | ||||
| ``Field`` type:: | ||||
|  | ||||
|     class Person(models.Model): | ||||
|         name = models.CharField(maxlength=80) | ||||
|         gender = models.CharField(maxlength=1) | ||||
|         something_else = MytypeField() | ||||
|  | ||||
| If you aim to build a database-agnostic application, you should account for | ||||
| differences in database column types. For example, the date/time column type | ||||
| in PostgreSQL is called ``timestamp``, while the same column in MySQL is called | ||||
| ``datetime``. The simplest way to handle this in a ``db_type()`` method is to | ||||
| import the Django settings module and check the ``DATABASE_ENGINE`` setting. | ||||
| For example:: | ||||
|  | ||||
|     class MyDateField(models.Field): | ||||
|         def db_type(self): | ||||
|             from django.conf import settings | ||||
|             if settings.DATABASE_ENGINE == 'mysql': | ||||
|                 return 'datetime' | ||||
|             else: | ||||
|                 return 'timestamp' | ||||
|  | ||||
| The ``db_type()`` method is only called by Django when the framework constructs | ||||
| the ``CREATE TABLE`` statements for your application -- that is, when you first | ||||
| create your tables. It's not called at any other time, so it can afford to | ||||
| execute slightly complex code, such as the ``DATABASE_ENGINE`` check in the | ||||
| above example. | ||||
|  | ||||
| Meta options | ||||
| ============ | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user