diff --git a/AUTHORS b/AUTHORS index 747c3bc6a8..7be78035a1 100644 --- a/AUTHORS +++ b/AUTHORS @@ -147,6 +147,7 @@ answer newbie questions, and generally made Django that much better: Ricardo Javier Cárdenes Medina Jeremy Carbaugh Graham Carlyle + Eric Palakovich Carr Juan Catalano Antonio Cavedoni cedric@terramater.net diff --git a/django/contrib/gis/tests/migrations/__init__.py b/django/contrib/gis/tests/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django/contrib/gis/tests/migrations/migrations/0001_initial.py b/django/contrib/gis/tests/migrations/migrations/0001_initial.py new file mode 100644 index 0000000000..165a21700c --- /dev/null +++ b/django/contrib/gis/tests/migrations/migrations/0001_initial.py @@ -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,), + ) + ] diff --git a/django/contrib/gis/tests/migrations/migrations/__init__.py b/django/contrib/gis/tests/migrations/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django/contrib/gis/tests/migrations/test_commands.py b/django/contrib/gis/tests/migrations/test_commands.py new file mode 100644 index 0000000000..5454042100 --- /dev/null +++ b/django/contrib/gis/tests/migrations/test_commands.py @@ -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") diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index dd88471d78..8dfe7e79e5 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -511,7 +511,7 @@ class Field(RegisterLookupMixin): connection. """ # 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". # # 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 # can implement db_type() instead of get_internal_type() to specify # exactly which wacky database column type you want to use. - params = self.db_parameters(connection) - if params['type']: - if params['check']: - return "%s CHECK (%s)" % (params['type'], params['check']) - else: - return params['type'] - return None + data = DictWrapper(self.__dict__, connection.ops.quote_name, "qn_") + try: + return connection.creation.data_types[self.get_internal_type()] % data + except KeyError: + return None def db_parameters(self, connection): """ - Replacement for db_type, providing a range of different return - values (type, checks) + Extension of db_type(), providing a range of different return + 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_") - try: - type_string = connection.creation.data_types[self.get_internal_type()] % data - except KeyError: - type_string = None + type_string = self.db_type(connection) try: check_string = connection.creation.data_type_check_constraints[self.get_internal_type()] % data except KeyError: diff --git a/tests/field_subclassing/fields.py b/tests/field_subclassing/fields.py index 43ece22e82..d96ce8d873 100644 --- a/tests/field_subclassing/fields.py +++ b/tests/field_subclassing/fields.py @@ -74,3 +74,8 @@ class JSONField(six.with_metaclass(models.SubfieldBase, models.TextField)): if value is None: return None return json.dumps(value) + + +class CustomTypedField(models.TextField): + def db_type(self, connection): + return 'custom_field' diff --git a/tests/field_subclassing/tests.py b/tests/field_subclassing/tests.py index 6a3d1cad20..5f1dbac8a3 100644 --- a/tests/field_subclassing/tests.py +++ b/tests/field_subclassing/tests.py @@ -3,9 +3,10 @@ from __future__ import unicode_literals import inspect from django.core import serializers +from django.db import connection from django.test import TestCase -from .fields import Small +from .fields import Small, CustomTypedField from .models import DataModel, MyModel, OtherModel @@ -104,3 +105,10 @@ class CustomField(TestCase): data = dict(inspect.getmembers(MyModel)) self.assertIn('__module__', data) 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')