Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

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
  • Loading branch information...
commit a1fbc0dc3696500cdc2391da3fbc6694a2a4841e 1 parent 58440c0
Justin Bronn jbronn authored
2  django/contrib/gis/db/backends/mysql/base.py
View
@@ -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 @@ def __init__(self, *args, **kwargs):
super(DatabaseWrapper, self).__init__(*args, **kwargs)
self.creation = MySQLCreation(self)
self.ops = MySQLOperations()
+ self.introspection = MySQLIntrospection(self)
32 django/contrib/gis/db/backends/mysql/introspection.py
View
@@ -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
4 django/contrib/gis/db/backends/oracle/base.py
View
@@ -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)
39 django/contrib/gis/db/backends/oracle/introspection.py
View
@@ -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
2  django/contrib/gis/db/backends/postgis/base.py
View
@@ -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 @@ def __init__(self, *args, **kwargs):
super(DatabaseWrapper, self).__init__(*args, **kwargs)
self.creation = PostGISCreation(self)
self.ops = PostGISOperations(self)
+ self.introspection = PostGISIntrospection(self)
95 django/contrib/gis/db/backends/postgis/introspection.py
View
@@ -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
2  django/contrib/gis/db/backends/spatialite/base.py
View
@@ -7,6 +7,7 @@
_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 @@ def __init__(self, *args, **kwargs):
self.ops = SpatiaLiteOperations(self)
self.client = SpatiaLiteClient(self)
self.creation = SpatiaLiteCreation(self)
+ self.introspection = SpatiaLiteIntrospection(self)
def _cursor(self):
if self.connection is None:
51 django/contrib/gis/db/backends/spatialite/introspection.py
View
@@ -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
218 django/contrib/gis/management/commands/inspectdb.py
View
@@ -1,188 +1,32 @@
-"""
- This overrides the traditional `inspectdb` command so that geographic databases
- may be introspected.
-"""
-
-from django.core.management.commands.inspectdb import Command as InspectCommand
-from django.contrib.gis.db.backend import SpatialBackend
-
-class Command(InspectCommand):
-
- # 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
+from optparse import make_option
+
+from django.core.management.base import CommandError
+from django.core.management.commands.inspectdb import Command as InspectDBCommand
+
+class Command(InspectDBCommand):
+ db_module = 'django.contrib.gis.db'
+ gis_tables = {}
+
+ 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 }
-
- 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 ''
+ self.gis_tables[table_name] = [geo_col]
+ return field_type, field_params, field_notes
+
+ 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
74 django/core/management/commands/inspectdb.py
View
@@ -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 @@ def handle_inspection(self, options):
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 @@ def handle_inspection(self, options):
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 @@ def handle_inspection(self, options):
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,
+ '']
Please sign in to comment.
Something went wrong with that request. Please try again.