Fixed #12637 -- GeoDjango's `inspectdb` command is now a subclass of Django's, and works with all spatial backends (Oracle and SpatiaLite did work before). This changeset introduces new introspection modules for all of the spatial backends and adds hooks to the original `inspectdb.Command` class to enable reuse.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@12257 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
58440c0b66
commit
a1fbc0dc36
|
@ -1,6 +1,7 @@
|
|||
from django.db.backends.mysql.base import *
|
||||
from django.db.backends.mysql.base import DatabaseWrapper as MySQLDatabaseWrapper
|
||||
from django.contrib.gis.db.backends.mysql.creation import MySQLCreation
|
||||
from django.contrib.gis.db.backends.mysql.introspection import MySQLIntrospection
|
||||
from django.contrib.gis.db.backends.mysql.operations import MySQLOperations
|
||||
|
||||
class DatabaseWrapper(MySQLDatabaseWrapper):
|
||||
|
@ -9,3 +10,4 @@ class DatabaseWrapper(MySQLDatabaseWrapper):
|
|||
super(DatabaseWrapper, self).__init__(*args, **kwargs)
|
||||
self.creation = MySQLCreation(self)
|
||||
self.ops = MySQLOperations()
|
||||
self.introspection = MySQLIntrospection(self)
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
from MySQLdb.constants import FIELD_TYPE
|
||||
|
||||
from django.contrib.gis.gdal import OGRGeomType
|
||||
from django.db.backends.mysql.introspection import DatabaseIntrospection
|
||||
|
||||
class MySQLIntrospection(DatabaseIntrospection):
|
||||
# Updating the data_types_reverse dictionary with the appropriate
|
||||
# type for Geometry fields.
|
||||
data_types_reverse = DatabaseIntrospection.data_types_reverse.copy()
|
||||
data_types_reverse[FIELD_TYPE.GEOMETRY] = 'GeometryField'
|
||||
|
||||
def get_geometry_type(self, table_name, geo_col):
|
||||
cursor = self.connection.cursor()
|
||||
try:
|
||||
# In order to get the specific geometry type of the field,
|
||||
# we introspect on the table definition using `DESCRIBE`.
|
||||
cursor.execute('DESCRIBE %s' %
|
||||
self.connection.ops.quote_name(table_name))
|
||||
# Increment over description info until we get to the geometry
|
||||
# column.
|
||||
for column, typ, null, key, default, extra in cursor.fetchall():
|
||||
if column == geo_col:
|
||||
# Using OGRGeomType to convert from OGC name to Django field.
|
||||
# MySQL does not support 3D or SRIDs, so the field params
|
||||
# are empty.
|
||||
field_type = OGRGeomType(typ).django
|
||||
field_params = {}
|
||||
break
|
||||
finally:
|
||||
cursor.close()
|
||||
|
||||
return field_type, field_params
|
|
@ -1,10 +1,12 @@
|
|||
from django.db.backends.oracle.base import *
|
||||
from django.db.backends.oracle.base import DatabaseWrapper as OracleDatabaseWrapper
|
||||
from django.contrib.gis.db.backends.oracle.creation import OracleCreation
|
||||
from django.contrib.gis.db.backends.oracle.introspection import OracleIntrospection
|
||||
from django.contrib.gis.db.backends.oracle.operations import OracleOperations
|
||||
|
||||
class DatabaseWrapper(OracleDatabaseWrapper):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DatabaseWrapper, self).__init__(*args, **kwargs)
|
||||
self.creation = OracleCreation(self)
|
||||
self.ops = OracleOperations(self)
|
||||
self.creation = OracleCreation(self)
|
||||
self.introspection = OracleIntrospection(self)
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
import cx_Oracle
|
||||
from django.db.backends.oracle.introspection import DatabaseIntrospection
|
||||
|
||||
class OracleIntrospection(DatabaseIntrospection):
|
||||
# Associating any OBJECTVAR instances with GeometryField. Of course,
|
||||
# this won't work right on Oracle objects that aren't MDSYS.SDO_GEOMETRY,
|
||||
# but it is the only object type supported within Django anyways.
|
||||
data_types_reverse = DatabaseIntrospection.data_types_reverse.copy()
|
||||
data_types_reverse[cx_Oracle.OBJECT] = 'GeometryField'
|
||||
|
||||
def get_geometry_type(self, table_name, geo_col):
|
||||
cursor = self.connection.cursor()
|
||||
try:
|
||||
# Querying USER_SDO_GEOM_METADATA to get the SRID and dimension information.
|
||||
try:
|
||||
cursor.execute('SELECT "DIMINFO", "SRID" FROM "USER_SDO_GEOM_METADATA" WHERE "TABLE_NAME"=%s AND "COLUMN_NAME"=%s',
|
||||
(table_name.upper(), geo_col.upper()))
|
||||
row = cursor.fetchone()
|
||||
except Exception, msg:
|
||||
raise Exception('Could not find entry in USER_SDO_GEOM_METADATA corresponding to "%s"."%s"\n'
|
||||
'Error message: %s.' % (table_name, geo_col, msg))
|
||||
|
||||
# TODO: Research way to find a more specific geometry field type for
|
||||
# the column's contents.
|
||||
field_type = 'GeometryField'
|
||||
|
||||
# Getting the field parameters.
|
||||
field_params = {}
|
||||
dim, srid = row
|
||||
if srid != 4326:
|
||||
field_params['srid'] = srid
|
||||
# Length of object array ( SDO_DIM_ARRAY ) is number of dimensions.
|
||||
dim = len(dim)
|
||||
if dim != 2:
|
||||
field_params['dim'] = dim
|
||||
finally:
|
||||
cursor.close()
|
||||
|
||||
return field_type, field_params
|
|
@ -1,6 +1,7 @@
|
|||
from django.db.backends.postgresql_psycopg2.base import *
|
||||
from django.db.backends.postgresql_psycopg2.base import DatabaseWrapper as Psycopg2DatabaseWrapper
|
||||
from django.contrib.gis.db.backends.postgis.creation import PostGISCreation
|
||||
from django.contrib.gis.db.backends.postgis.introspection import PostGISIntrospection
|
||||
from django.contrib.gis.db.backends.postgis.operations import PostGISOperations
|
||||
|
||||
class DatabaseWrapper(Psycopg2DatabaseWrapper):
|
||||
|
@ -8,3 +9,4 @@ class DatabaseWrapper(Psycopg2DatabaseWrapper):
|
|||
super(DatabaseWrapper, self).__init__(*args, **kwargs)
|
||||
self.creation = PostGISCreation(self)
|
||||
self.ops = PostGISOperations(self)
|
||||
self.introspection = PostGISIntrospection(self)
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
from django.db.backends.postgresql_psycopg2.introspection import DatabaseIntrospection
|
||||
from django.contrib.gis.gdal import OGRGeomType
|
||||
|
||||
class GeoIntrospectionError(Exception):
|
||||
pass
|
||||
|
||||
class PostGISIntrospection(DatabaseIntrospection):
|
||||
# Reverse dictionary for PostGIS geometry types not populated until
|
||||
# introspection is actually performed.
|
||||
postgis_types_reverse = {}
|
||||
|
||||
def get_postgis_types(self):
|
||||
"""
|
||||
Returns a dictionary with keys that are the PostgreSQL object
|
||||
identification integers for the PostGIS geometry and/or
|
||||
geography types (if supported).
|
||||
"""
|
||||
cursor = self.connection.cursor()
|
||||
# The OID integers associated with the geometry type may
|
||||
# be different across versions; hence, this is why we have
|
||||
# to query the PostgreSQL pg_type table corresponding to the
|
||||
# PostGIS custom data types.
|
||||
oid_sql = 'SELECT "oid" FROM "pg_type" WHERE "typname" = %s'
|
||||
try:
|
||||
cursor.execute(oid_sql, ('geometry',))
|
||||
GEOM_TYPE = cursor.fetchone()[0]
|
||||
postgis_types = { GEOM_TYPE : 'GeometryField' }
|
||||
if self.connection.ops.geography:
|
||||
cursor.execute(oid_sql, ('geography',))
|
||||
GEOG_TYPE = cursor.fetchone()[0]
|
||||
# The value for the geography type is actually a tuple
|
||||
# to pass in the `geography=True` keyword to the field
|
||||
# definition.
|
||||
postgis_types[GEOG_TYPE] = ('GeometryField', {'geography' : True})
|
||||
finally:
|
||||
cursor.close()
|
||||
|
||||
return postgis_types
|
||||
|
||||
def get_field_type(self, data_type, description):
|
||||
if not self.postgis_types_reverse:
|
||||
# If the PostGIS types reverse dictionary is not populated, do so
|
||||
# now. In order to prevent unnecessary requests upon connection
|
||||
# intialization, the `data_types_reverse` dictionary is not updated
|
||||
# with the PostGIS custom types until introspection is actually
|
||||
# performed -- in other words, when this function is called.
|
||||
self.postgis_types_reverse = self.get_postgis_types()
|
||||
self.data_types_reverse.update(self.postgis_types_reverse)
|
||||
return super(PostGISIntrospection, self).get_field_type(data_type, description)
|
||||
|
||||
def get_geometry_type(self, table_name, geo_col):
|
||||
"""
|
||||
The geometry type OID used by PostGIS does not indicate the particular
|
||||
type of field that a geometry column is (e.g., whether it's a
|
||||
PointField or a PolygonField). Thus, this routine queries the PostGIS
|
||||
metadata tables to determine the geometry type,
|
||||
"""
|
||||
cursor = self.connection.cursor()
|
||||
try:
|
||||
try:
|
||||
# First seeing if this geometry column is in the `geometry_columns`
|
||||
cursor.execute('SELECT "coord_dimension", "srid", "type" '
|
||||
'FROM "geometry_columns" '
|
||||
'WHERE "f_table_name"=%s AND "f_geometry_column"=%s',
|
||||
(table_name, geo_col))
|
||||
row = cursor.fetchone()
|
||||
if not row: raise GeoIntrospectionError
|
||||
except GeoIntrospectionError:
|
||||
if self.connection.ops.geography:
|
||||
cursor.execute('SELECT "coord_dimension", "srid", "type" '
|
||||
'FROM "geography_columns" '
|
||||
'WHERE "f_table_name"=%s AND "f_geography_column"=%s',
|
||||
(table_name, geo_col))
|
||||
row = cursor.fetchone()
|
||||
|
||||
if not row:
|
||||
raise Exception('Could not find a geometry or geography column for "%s"."%s"' %
|
||||
(table_name, geo_col))
|
||||
|
||||
# OGRGeomType does not require GDAL and makes it easy to convert
|
||||
# from OGC geom type name to Django field.
|
||||
field_type = OGRGeomType(row[2]).django
|
||||
|
||||
# Getting any GeometryField keyword arguments that are not the default.
|
||||
dim = row[0]
|
||||
srid = row[1]
|
||||
field_params = {}
|
||||
if srid != 4326:
|
||||
field_params['srid'] = srid
|
||||
if dim != 2:
|
||||
field_params['dim'] = dim
|
||||
finally:
|
||||
cursor.close()
|
||||
|
||||
return field_type, field_params
|
|
@ -7,6 +7,7 @@ from django.db.backends.sqlite3.base import DatabaseWrapper as SqliteDatabaseWra
|
|||
_sqlite_extract, _sqlite_date_trunc, _sqlite_regexp
|
||||
from django.contrib.gis.db.backends.spatialite.client import SpatiaLiteClient
|
||||
from django.contrib.gis.db.backends.spatialite.creation import SpatiaLiteCreation
|
||||
from django.contrib.gis.db.backends.spatialite.introspection import SpatiaLiteIntrospection
|
||||
from django.contrib.gis.db.backends.spatialite.operations import SpatiaLiteOperations
|
||||
|
||||
class DatabaseWrapper(SqliteDatabaseWrapper):
|
||||
|
@ -32,6 +33,7 @@ class DatabaseWrapper(SqliteDatabaseWrapper):
|
|||
self.ops = SpatiaLiteOperations(self)
|
||||
self.client = SpatiaLiteClient(self)
|
||||
self.creation = SpatiaLiteCreation(self)
|
||||
self.introspection = SpatiaLiteIntrospection(self)
|
||||
|
||||
def _cursor(self):
|
||||
if self.connection is None:
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
from django.contrib.gis.gdal import OGRGeomType
|
||||
from django.db.backends.sqlite3.introspection import DatabaseIntrospection, FlexibleFieldLookupDict
|
||||
|
||||
class GeoFlexibleFieldLookupDict(FlexibleFieldLookupDict):
|
||||
"""
|
||||
Sublcass that includes updates the `base_data_types_reverse` dict
|
||||
for geometry field types.
|
||||
"""
|
||||
base_data_types_reverse = FlexibleFieldLookupDict.base_data_types_reverse.copy()
|
||||
base_data_types_reverse.update(
|
||||
{'point' : 'GeometryField',
|
||||
'linestring' : 'GeometryField',
|
||||
'polygon' : 'GeometryField',
|
||||
'multipoint' : 'GeometryField',
|
||||
'multilinestring' : 'GeometryField',
|
||||
'multipolygon' : 'GeometryField',
|
||||
'geometrycollection' : 'GeometryField',
|
||||
})
|
||||
|
||||
class SpatiaLiteIntrospection(DatabaseIntrospection):
|
||||
data_types_reverse = GeoFlexibleFieldLookupDict()
|
||||
|
||||
def get_geometry_type(self, table_name, geo_col):
|
||||
cursor = self.connection.cursor()
|
||||
try:
|
||||
# Querying the `geometry_columns` table to get additional metadata.
|
||||
cursor.execute('SELECT "coord_dimension", "srid", "type" '
|
||||
'FROM "geometry_columns" '
|
||||
'WHERE "f_table_name"=%s AND "f_geometry_column"=%s',
|
||||
(table_name, geo_col))
|
||||
row = cursor.fetchone()
|
||||
if not row:
|
||||
raise Exception('Could not find a geometry column for "%s"."%s"' %
|
||||
(table_name, geo_col))
|
||||
|
||||
# OGRGeomType does not require GDAL and makes it easy to convert
|
||||
# from OGC geom type name to Django field.
|
||||
field_type = OGRGeomType(row[2]).django
|
||||
|
||||
# Getting any GeometryField keyword arguments that are not the default.
|
||||
dim = row[0]
|
||||
srid = row[1]
|
||||
field_params = {}
|
||||
if srid != 4326:
|
||||
field_params['srid'] = srid
|
||||
if isinstance(dim, basestring) and 'Z' in dim:
|
||||
field_params['dim'] = 3
|
||||
finally:
|
||||
cursor.close()
|
||||
|
||||
return field_type, field_params
|
|
@ -1,188 +1,32 @@
|
|||
"""
|
||||
This overrides the traditional `inspectdb` command so that geographic databases
|
||||
may be introspected.
|
||||
"""
|
||||
from optparse import make_option
|
||||
|
||||
from django.core.management.commands.inspectdb import Command as InspectCommand
|
||||
from django.contrib.gis.db.backend import SpatialBackend
|
||||
from django.core.management.base import CommandError
|
||||
from django.core.management.commands.inspectdb import Command as InspectDBCommand
|
||||
|
||||
class Command(InspectCommand):
|
||||
class Command(InspectDBCommand):
|
||||
db_module = 'django.contrib.gis.db'
|
||||
gis_tables = {}
|
||||
|
||||
# Mapping from lower-case OGC type to the corresponding GeoDjango field.
|
||||
geofield_mapping = {'point' : 'PointField',
|
||||
'linestring' : 'LineStringField',
|
||||
'polygon' : 'PolygonField',
|
||||
'multipoint' : 'MultiPointField',
|
||||
'multilinestring' : 'MultiLineStringField',
|
||||
'multipolygon' : 'MultiPolygonField',
|
||||
'geometrycollection' : 'GeometryCollectionField',
|
||||
'geometry' : 'GeometryField',
|
||||
}
|
||||
|
||||
def geometry_columns(self):
|
||||
"""
|
||||
Returns a datastructure of metadata information associated with the
|
||||
`geometry_columns` (or equivalent) table.
|
||||
"""
|
||||
# The `geo_cols` is a dictionary data structure that holds information
|
||||
# about any geographic columns in the database.
|
||||
geo_cols = {}
|
||||
def add_col(table, column, coldata):
|
||||
if table in geo_cols:
|
||||
# If table already has a geometry column.
|
||||
geo_cols[table][column] = coldata
|
||||
def get_field_type(self, connection, table_name, row):
|
||||
field_type, field_params, field_notes = super(Command, self).get_field_type(connection, table_name, row)
|
||||
if field_type == 'GeometryField':
|
||||
geo_col = row[0]
|
||||
# Getting a more specific field type and any additional parameters
|
||||
# from the `get_geometry_type` routine for the spatial backend.
|
||||
field_type, geo_params = connection.introspection.get_geometry_type(table_name, geo_col)
|
||||
field_params.update(geo_params)
|
||||
# Adding the table name and column to the `gis_tables` dictionary, this
|
||||
# allows us to track which tables need a GeoManager.
|
||||
if table_name in self.gis_tables:
|
||||
self.gis_tables[table_name].append(geo_col)
|
||||
else:
|
||||
# Otherwise, create a dictionary indexed by column.
|
||||
geo_cols[table] = { column : coldata }
|
||||
self.gis_tables[table_name] = [geo_col]
|
||||
return field_type, field_params, field_notes
|
||||
|
||||
if SpatialBackend.name == 'postgis':
|
||||
# PostGIS holds all geographic column information in the `geometry_columns` table.
|
||||
from django.contrib.gis.models import GeometryColumns
|
||||
for geo_col in GeometryColumns.objects.all():
|
||||
table = geo_col.f_table_name
|
||||
column = geo_col.f_geometry_column
|
||||
coldata = {'type' : geo_col.type, 'srid' : geo_col.srid, 'dim' : geo_col.coord_dimension}
|
||||
add_col(table, column, coldata)
|
||||
return geo_cols
|
||||
elif SpatialBackend.name == 'mysql':
|
||||
# On MySQL have to get all table metadata before hand; this means walking through
|
||||
# each table and seeing if any column types are spatial. Can't detect this with
|
||||
# `cursor.description` (what the introspection module does) because all spatial types
|
||||
# have the same integer type (255 for GEOMETRY).
|
||||
from django.db import connection
|
||||
cursor = connection.cursor()
|
||||
cursor.execute('SHOW TABLES')
|
||||
tables = cursor.fetchall();
|
||||
for table_tup in tables:
|
||||
table = table_tup[0]
|
||||
table_desc = cursor.execute('DESCRIBE `%s`' % table)
|
||||
col_info = cursor.fetchall()
|
||||
for column, typ, null, key, default, extra in col_info:
|
||||
if typ in self.geofield_mapping: add_col(table, column, {'type' : typ})
|
||||
return geo_cols
|
||||
else:
|
||||
# TODO: Oracle (has incomplete `geometry_columns` -- have to parse
|
||||
# SDO SQL to get specific type, SRID, and other information).
|
||||
raise NotImplementedError('Geographic database inspection not available.')
|
||||
|
||||
def handle_inspection(self):
|
||||
"Overloaded from Django's version to handle geographic database tables."
|
||||
from django.db import connection
|
||||
import keyword
|
||||
|
||||
geo_cols = self.geometry_columns()
|
||||
|
||||
table2model = lambda table_name: table_name.title().replace('_', '')
|
||||
|
||||
cursor = connection.cursor()
|
||||
yield "# This is an auto-generated Django model module."
|
||||
yield "# You'll have to do the following manually to clean this up:"
|
||||
yield "# * Rearrange models' order"
|
||||
yield "# * Make sure each model has one field with primary_key=True"
|
||||
yield "# Feel free to rename the models, but don't rename db_table values or field names."
|
||||
yield "#"
|
||||
yield "# Also note: You'll have to insert the output of 'django-admin.py sqlcustom [appname]'"
|
||||
yield "# into your database."
|
||||
yield ''
|
||||
yield 'from django.contrib.gis.db import models'
|
||||
yield ''
|
||||
for table_name in connection.introspection.get_table_list(cursor):
|
||||
# Getting the geographic table dictionary.
|
||||
geo_table = geo_cols.get(table_name, {})
|
||||
|
||||
yield 'class %s(models.Model):' % table2model(table_name)
|
||||
try:
|
||||
relations = connection.introspection.get_relations(cursor, table_name)
|
||||
except NotImplementedError:
|
||||
relations = {}
|
||||
try:
|
||||
indexes = connection.introspection.get_indexes(cursor, table_name)
|
||||
except NotImplementedError:
|
||||
indexes = {}
|
||||
for i, row in enumerate(connection.introspection.get_table_description(cursor, table_name)):
|
||||
att_name, iatt_name = row[0].lower(), row[0]
|
||||
comment_notes = [] # Holds Field notes, to be displayed in a Python comment.
|
||||
extra_params = {} # Holds Field parameters such as 'db_column'.
|
||||
|
||||
if ' ' in att_name:
|
||||
extra_params['db_column'] = att_name
|
||||
att_name = att_name.replace(' ', '')
|
||||
comment_notes.append('Field renamed to remove spaces.')
|
||||
if keyword.iskeyword(att_name):
|
||||
extra_params['db_column'] = att_name
|
||||
att_name += '_field'
|
||||
comment_notes.append('Field renamed because it was a Python reserved word.')
|
||||
|
||||
if i in relations:
|
||||
rel_to = relations[i][1] == table_name and "'self'" or table2model(relations[i][1])
|
||||
field_type = 'ForeignKey(%s' % rel_to
|
||||
if att_name.endswith('_id'):
|
||||
att_name = att_name[:-3]
|
||||
else:
|
||||
extra_params['db_column'] = att_name
|
||||
else:
|
||||
if iatt_name in geo_table:
|
||||
## Customization for Geographic Columns ##
|
||||
geo_col = geo_table[iatt_name]
|
||||
field_type = self.geofield_mapping[geo_col['type'].lower()]
|
||||
# Adding extra keyword arguments for the SRID and dimension (if not defaults).
|
||||
dim, srid = geo_col.get('dim', 2), geo_col.get('srid', 4326)
|
||||
if dim != 2: extra_params['dim'] = dim
|
||||
if srid != 4326: extra_params['srid'] = srid
|
||||
else:
|
||||
try:
|
||||
field_type = connection.introspection.get_field_type(row[1], row)
|
||||
except KeyError:
|
||||
field_type = 'TextField'
|
||||
comment_notes.append('This field type is a guess.')
|
||||
|
||||
# This is a hook for data_types_reverse to return a tuple of
|
||||
# (field_type, extra_params_dict).
|
||||
if type(field_type) is tuple:
|
||||
field_type, new_params = field_type
|
||||
extra_params.update(new_params)
|
||||
|
||||
# Add max_length for all CharFields.
|
||||
if field_type == 'CharField' and row[3]:
|
||||
extra_params['max_length'] = row[3]
|
||||
|
||||
if field_type == 'DecimalField':
|
||||
extra_params['max_digits'] = row[4]
|
||||
extra_params['decimal_places'] = row[5]
|
||||
|
||||
# Add primary_key and unique, if necessary.
|
||||
column_name = extra_params.get('db_column', att_name)
|
||||
if column_name in indexes:
|
||||
if indexes[column_name]['primary_key']:
|
||||
extra_params['primary_key'] = True
|
||||
elif indexes[column_name]['unique']:
|
||||
extra_params['unique'] = True
|
||||
|
||||
field_type += '('
|
||||
|
||||
# Don't output 'id = meta.AutoField(primary_key=True)', because
|
||||
# that's assumed if it doesn't exist.
|
||||
if att_name == 'id' and field_type == 'AutoField(' and extra_params == {'primary_key': True}:
|
||||
continue
|
||||
|
||||
# Add 'null' and 'blank', if the 'null_ok' flag was present in the
|
||||
# table description.
|
||||
if row[6]: # If it's NULL...
|
||||
extra_params['blank'] = True
|
||||
if not field_type in ('TextField(', 'CharField('):
|
||||
extra_params['null'] = True
|
||||
|
||||
field_desc = '%s = models.%s' % (att_name, field_type)
|
||||
if extra_params:
|
||||
if not field_desc.endswith('('):
|
||||
field_desc += ', '
|
||||
field_desc += ', '.join(['%s=%r' % (k, v) for k, v in extra_params.items()])
|
||||
field_desc += ')'
|
||||
if comment_notes:
|
||||
field_desc += ' # ' + ' '.join(comment_notes)
|
||||
yield ' %s' % field_desc
|
||||
if table_name in geo_cols:
|
||||
yield ' objects = models.GeoManager()'
|
||||
yield ' class Meta:'
|
||||
yield ' db_table = %r' % table_name
|
||||
yield ''
|
||||
def get_meta(self, table_name):
|
||||
meta_lines = super(Command, self).get_meta(table_name)
|
||||
if table_name in self.gis_tables:
|
||||
# If the table is a geographic one, then we need make
|
||||
# GeoManager the default manager for the model.
|
||||
meta_lines.insert(0, ' objects = models.GeoManager()')
|
||||
return meta_lines
|
||||
|
|
|
@ -15,6 +15,8 @@ class Command(NoArgsCommand):
|
|||
|
||||
requires_model_validation = False
|
||||
|
||||
db_module = 'django.db'
|
||||
|
||||
def handle_noargs(self, **options):
|
||||
try:
|
||||
for line in self.handle_inspection(options):
|
||||
|
@ -37,7 +39,7 @@ class Command(NoArgsCommand):
|
|||
yield "# Also note: You'll have to insert the output of 'django-admin.py sqlcustom [appname]'"
|
||||
yield "# into your database."
|
||||
yield ''
|
||||
yield 'from django.db import models'
|
||||
yield 'from %s import models' % self.db_module
|
||||
yield ''
|
||||
for table_name in connection.introspection.get_table_list(cursor):
|
||||
yield 'class %s(models.Model):' % table2model(table_name)
|
||||
|
@ -81,25 +83,11 @@ class Command(NoArgsCommand):
|
|||
else:
|
||||
extra_params['db_column'] = column_name
|
||||
else:
|
||||
try:
|
||||
field_type = connection.introspection.get_field_type(row[1], row)
|
||||
except KeyError:
|
||||
field_type = 'TextField'
|
||||
comment_notes.append('This field type is a guess.')
|
||||
|
||||
# This is a hook for DATA_TYPES_REVERSE to return a tuple of
|
||||
# (field_type, extra_params_dict).
|
||||
if type(field_type) is tuple:
|
||||
field_type, new_params = field_type
|
||||
extra_params.update(new_params)
|
||||
|
||||
# Add max_length for all CharFields.
|
||||
if field_type == 'CharField' and row[3]:
|
||||
extra_params['max_length'] = row[3]
|
||||
|
||||
if field_type == 'DecimalField':
|
||||
extra_params['max_digits'] = row[4]
|
||||
extra_params['decimal_places'] = row[5]
|
||||
# Calling `get_field_type` to get the field type string and any
|
||||
# additional paramters and notes.
|
||||
field_type, field_params, field_notes = self.get_field_type(connection, table_name, row)
|
||||
extra_params.update(field_params)
|
||||
comment_notes.extend(field_notes)
|
||||
|
||||
# Add primary_key and unique, if necessary.
|
||||
if column_name in indexes:
|
||||
|
@ -131,6 +119,46 @@ class Command(NoArgsCommand):
|
|||
if comment_notes:
|
||||
field_desc += ' # ' + ' '.join(comment_notes)
|
||||
yield ' %s' % field_desc
|
||||
yield ' class Meta:'
|
||||
yield ' db_table = %r' % table_name
|
||||
yield ''
|
||||
for meta_line in self.get_meta(table_name):
|
||||
yield meta_line
|
||||
|
||||
def get_field_type(self, connection, table_name, row):
|
||||
"""
|
||||
Given the database connection, the table name, and the cursor row
|
||||
description, this routine will return the given field type name, as
|
||||
well as any additional keyword parameters and notes for the field.
|
||||
"""
|
||||
field_params = {}
|
||||
field_notes = []
|
||||
|
||||
try:
|
||||
field_type = connection.introspection.get_field_type(row[1], row)
|
||||
except KeyError:
|
||||
field_type = 'TextField'
|
||||
field_notes.append('This field type is a guess.')
|
||||
|
||||
# This is a hook for DATA_TYPES_REVERSE to return a tuple of
|
||||
# (field_type, field_params_dict).
|
||||
if type(field_type) is tuple:
|
||||
field_type, new_params = field_type
|
||||
field_params.update(new_params)
|
||||
|
||||
# Add max_length for all CharFields.
|
||||
if field_type == 'CharField' and row[3]:
|
||||
field_params['max_length'] = row[3]
|
||||
|
||||
if field_type == 'DecimalField':
|
||||
field_params['max_digits'] = row[4]
|
||||
field_params['decimal_places'] = row[5]
|
||||
|
||||
return field_type, field_params, field_notes
|
||||
|
||||
def get_meta(self, table_name):
|
||||
"""
|
||||
Return a sequence comprising the lines of code necessary
|
||||
to construct the inner Meta class for the model corresponding
|
||||
to the given database table name.
|
||||
"""
|
||||
return [' class Meta:',
|
||||
' db_table = %r' % table_name,
|
||||
'']
|
||||
|
|
Loading…
Reference in New Issue