Fixed #22001 -- Ensure db_type is respected.

db_parameters should respect an already existing db_type method and
return that as its type string. In particular, this was causing some
fields from gis to not be generated.

Thanks to @bigsassy and @blueyed for their work on the patch.

Also fixed #22260
This commit is contained in:
Marc Tamlyn 2014-03-14 22:18:20 +00:00
parent 37f7f233f5
commit d22b291890
8 changed files with 105 additions and 15 deletions

View File

@ -147,6 +147,7 @@ answer newbie questions, and generally made Django that much better:
Ricardo Javier Cárdenes Medina <ricardo.cardenes@gmail.com> Ricardo Javier Cárdenes Medina <ricardo.cardenes@gmail.com>
Jeremy Carbaugh <jcarbaugh@gmail.com> Jeremy Carbaugh <jcarbaugh@gmail.com>
Graham Carlyle <graham.carlyle@maplecroft.net> Graham Carlyle <graham.carlyle@maplecroft.net>
Eric Palakovich Carr <carreric@gmail.com>
Juan Catalano <catalanojuan@gmail.com> Juan Catalano <catalanojuan@gmail.com>
Antonio Cavedoni <http://cavedoni.com/> Antonio Cavedoni <http://cavedoni.com/>
cedric@terramater.net cedric@terramater.net

View File

@ -0,0 +1,33 @@
from django.db import models, migrations
import django.contrib.gis.db.models.fields
# Used for regression test of ticket #22001: https://code.djangoproject.com/ticket/22001
class Migration(migrations.Migration):
operations = [
migrations.CreateModel(
name='Neighborhood',
fields=[
(u'id', models.AutoField(verbose_name=u'ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.TextField(unique=True)),
('geom', django.contrib.gis.db.models.fields.MultiPolygonField(srid=4326, null=True)),
],
options={
},
bases=(models.Model,),
),
migrations.CreateModel(
name='Household',
fields=[
(u'id', models.AutoField(verbose_name=u'ID', serialize=False, auto_created=True, primary_key=True)),
('neighborhood', models.ForeignKey(to='gis.Neighborhood', to_field=u'id', null=True)),
('address', models.TextField()),
('zip_code', models.IntegerField(null=True, blank=True)),
('geom', django.contrib.gis.db.models.fields.PointField(srid=4326, null=True, geography=True)),
],
options={
},
bases=(models.Model,),
)
]

View File

@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.core.management import call_command
from django.db import connection
from django.test import override_settings, override_system_checks, TransactionTestCase
class MigrateTests(TransactionTestCase):
"""
Tests running the migrate command in Geodjango.
"""
available_apps = ["django.contrib.gis"]
def get_table_description(self, table):
with connection.cursor() as cursor:
return connection.introspection.get_table_description(cursor, table)
def assertTableExists(self, table):
with connection.cursor() as cursor:
self.assertIn(table, connection.introspection.get_table_list(cursor))
def assertTableNotExists(self, table):
with connection.cursor() as cursor:
self.assertNotIn(table, connection.introspection.get_table_list(cursor))
@override_system_checks([])
@override_settings(MIGRATION_MODULES={"gis": "django.contrib.gis.tests.migrations.migrations"})
def test_migrate_gis(self):
"""
Tests basic usage of the migrate command when a model uses Geodjango
fields. Regression test for ticket #22001:
https://code.djangoproject.com/ticket/22001
"""
# Make sure no tables are created
self.assertTableNotExists("migrations_neighborhood")
self.assertTableNotExists("migrations_household")
# Run the migrations to 0001 only
call_command("migrate", "gis", "0001", verbosity=0)
# Make sure the right tables exist
self.assertTableExists("gis_neighborhood")
self.assertTableExists("gis_household")
# Unmigrate everything
call_command("migrate", "gis", "zero", verbosity=0)
# Make sure it's all gone
self.assertTableNotExists("gis_neighborhood")
self.assertTableNotExists("gis_household")

View File

@ -511,7 +511,7 @@ class Field(RegisterLookupMixin):
connection. connection.
""" """
# The default implementation of this method looks at the # The default implementation of this method looks at the
# backend-specific DATA_TYPES dictionary, looking up the field by its # backend-specific data_types dictionary, looking up the field by its
# "internal type". # "internal type".
# #
# A Field class can implement the get_internal_type() method to specify # A Field class can implement the get_internal_type() method to specify
@ -525,24 +525,20 @@ class Field(RegisterLookupMixin):
# mapped to one of the built-in Django field types. In this case, you # 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 # can implement db_type() instead of get_internal_type() to specify
# exactly which wacky database column type you want to use. # exactly which wacky database column type you want to use.
params = self.db_parameters(connection) data = DictWrapper(self.__dict__, connection.ops.quote_name, "qn_")
if params['type']: try:
if params['check']: return connection.creation.data_types[self.get_internal_type()] % data
return "%s CHECK (%s)" % (params['type'], params['check']) except KeyError:
else: return None
return params['type']
return None
def db_parameters(self, connection): def db_parameters(self, connection):
""" """
Replacement for db_type, providing a range of different return Extension of db_type(), providing a range of different return
values (type, checks) values (type, checks).
This will look at db_type(), allowing custom model fields to override it.
""" """
data = DictWrapper(self.__dict__, connection.ops.quote_name, "qn_") data = DictWrapper(self.__dict__, connection.ops.quote_name, "qn_")
try: type_string = self.db_type(connection)
type_string = connection.creation.data_types[self.get_internal_type()] % data
except KeyError:
type_string = None
try: try:
check_string = connection.creation.data_type_check_constraints[self.get_internal_type()] % data check_string = connection.creation.data_type_check_constraints[self.get_internal_type()] % data
except KeyError: except KeyError:

View File

@ -74,3 +74,8 @@ class JSONField(six.with_metaclass(models.SubfieldBase, models.TextField)):
if value is None: if value is None:
return None return None
return json.dumps(value) return json.dumps(value)
class CustomTypedField(models.TextField):
def db_type(self, connection):
return 'custom_field'

View File

@ -3,9 +3,10 @@ from __future__ import unicode_literals
import inspect import inspect
from django.core import serializers from django.core import serializers
from django.db import connection
from django.test import TestCase from django.test import TestCase
from .fields import Small from .fields import Small, CustomTypedField
from .models import DataModel, MyModel, OtherModel from .models import DataModel, MyModel, OtherModel
@ -104,3 +105,10 @@ class CustomField(TestCase):
data = dict(inspect.getmembers(MyModel)) data = dict(inspect.getmembers(MyModel))
self.assertIn('__module__', data) self.assertIn('__module__', data)
self.assertEqual(data['__module__'], 'field_subclassing.models') self.assertEqual(data['__module__'], 'field_subclassing.models')
class TestDbType(TestCase):
def test_db_parameters_respects_db_type(self):
f = CustomTypedField()
self.assertEqual(f.db_parameters(connection)['type'], 'custom_field')