From 72d3889db4eb3a14acb94f613edd79f0f27d26e3 Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Mon, 31 Mar 2014 15:25:08 -0400 Subject: [PATCH] Fixed #22350 -- Consistently serialize bytes and text in migrations. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks to @treyhunner and Loïc for their suggestions and review. --- django/db/migrations/state.py | 2 +- django/db/migrations/writer.py | 33 +++++++++++++++---- .../test_migrations/0001_initial.py | 3 ++ .../migrations/test_migrations/0002_second.py | 3 ++ .../test_migrations_2/0001_initial.py | 3 ++ .../test_migrations_conflict/0001_initial.py | 3 ++ .../0002_conflicting_second.py | 3 ++ .../test_migrations_conflict/0002_second.py | 3 ++ .../test_migrations_squashed/0001_initial.py | 3 ++ .../0001_squashed_0002.py | 3 ++ .../test_migrations_squashed/0002_second.py | 3 ++ .../test_migrations_unmigdep/0001_initial.py | 3 ++ tests/migrations/test_writer.py | 23 ++++++++++--- 13 files changed, 76 insertions(+), 12 deletions(-) diff --git a/django/db/migrations/state.py b/django/db/migrations/state.py index ab2bd8e6d8..d146bfd8bc 100644 --- a/django/db/migrations/state.py +++ b/django/db/migrations/state.py @@ -227,7 +227,7 @@ class ModelState(object): body['__module__'] = "__fake__" # Then, make a Model object return type( - self.name, + str(self.name), bases, body, ) diff --git a/django/db/migrations/writer.py b/django/db/migrations/writer.py index d9f9c734a2..7696aaf339 100644 --- a/django/db/migrations/writer.py +++ b/django/db/migrations/writer.py @@ -53,8 +53,10 @@ class OperationWriter(object): self.feed('%s={' % arg_name) self.indent() for key, value in arg_value.items(): + key_string, key_imports = MigrationWriter.serialize(key) arg_string, arg_imports = MigrationWriter.serialize(value) - self.feed('%s: %s,' % (repr(key), arg_string)) + self.feed('%s: %s,' % (key_string, arg_string)) + imports.update(key_imports) imports.update(arg_imports) self.unindent() self.feed('},') @@ -122,7 +124,7 @@ class MigrationWriter(object): dependencies.append(" migrations.swappable_dependency(settings.%s)," % dependency[1]) imports.add("from django.conf import settings") else: - dependencies.append(" %s," % repr(dependency)) + dependencies.append(" %s," % self.serialize(dependency)[0]) items["dependencies"] = "\n".join(dependencies) + "\n" if dependencies else "" # Format imports nicely @@ -131,7 +133,7 @@ class MigrationWriter(object): # If there's a replaces, make a string for it if self.migration.replaces: - items['replaces_str'] = "\n replaces = %s\n" % repr(self.migration.replaces) + items['replaces_str'] = "\n replaces = %s\n" % self.serialize(self.migration.replaces)[0] return (MIGRATION_TEMPLATE % items).encode("utf8") @@ -185,6 +187,12 @@ class MigrationWriter(object): More advanced than repr() as it can encode things like datetime.datetime.now. """ + # FIXME: Ideally Promise would be reconstructible, but for now we + # use force_text on them and defer to the normal string serialization + # process. + if isinstance(value, Promise): + value = force_text(value) + # Sequences if isinstance(value, (list, set, tuple)): imports = set() @@ -229,11 +237,20 @@ class MigrationWriter(object): elif isinstance(value, SettingsReference): return "settings.%s" % value.setting_name, set(["from django.conf import settings"]) # Simple types - elif isinstance(value, six.integer_types + (float, six.binary_type, six.text_type, bool, type(None))): + elif isinstance(value, six.integer_types + (float, bool, type(None))): return repr(value), set() - # Promise - elif isinstance(value, Promise): - return repr(force_text(value)), set() + elif isinstance(value, six.binary_type): + value_repr = repr(value) + if six.PY2: + # Prepend the `b` prefix since we're importing unicode_literals + value_repr = 'b' + value_repr + return value_repr, set() + elif isinstance(value, six.text_type): + value_repr = repr(value) + if six.PY2: + # Strip the `u` prefix since we're importing unicode_literals + value_repr = value_repr[1:] + return value_repr, set() # Decimal elif isinstance(value, decimal.Decimal): return repr(value), set(["from decimal import Decimal"]) @@ -286,6 +303,8 @@ class MigrationWriter(object): MIGRATION_TEMPLATE = """\ # encoding: utf8 +from __future__ import unicode_literals + from django.db import models, migrations %(imports)s diff --git a/tests/migrations/test_migrations/0001_initial.py b/tests/migrations/test_migrations/0001_initial.py index 344bebdfe3..3544610eb4 100644 --- a/tests/migrations/test_migrations/0001_initial.py +++ b/tests/migrations/test_migrations/0001_initial.py @@ -1,3 +1,6 @@ +# encoding: utf8 +from __future__ import unicode_literals + from django.db import migrations, models diff --git a/tests/migrations/test_migrations/0002_second.py b/tests/migrations/test_migrations/0002_second.py index 736e844825..5da381677c 100644 --- a/tests/migrations/test_migrations/0002_second.py +++ b/tests/migrations/test_migrations/0002_second.py @@ -1,3 +1,6 @@ +# encoding: utf8 +from __future__ import unicode_literals + from django.db import migrations, models diff --git a/tests/migrations/test_migrations_2/0001_initial.py b/tests/migrations/test_migrations_2/0001_initial.py index 6172f011e8..cb15a04956 100644 --- a/tests/migrations/test_migrations_2/0001_initial.py +++ b/tests/migrations/test_migrations_2/0001_initial.py @@ -1,3 +1,6 @@ +# encoding: utf8 +from __future__ import unicode_literals + from django.db import migrations, models diff --git a/tests/migrations/test_migrations_conflict/0001_initial.py b/tests/migrations/test_migrations_conflict/0001_initial.py index 344bebdfe3..3544610eb4 100644 --- a/tests/migrations/test_migrations_conflict/0001_initial.py +++ b/tests/migrations/test_migrations_conflict/0001_initial.py @@ -1,3 +1,6 @@ +# encoding: utf8 +from __future__ import unicode_literals + from django.db import migrations, models diff --git a/tests/migrations/test_migrations_conflict/0002_conflicting_second.py b/tests/migrations/test_migrations_conflict/0002_conflicting_second.py index 15ea1f063a..cf83d7b036 100644 --- a/tests/migrations/test_migrations_conflict/0002_conflicting_second.py +++ b/tests/migrations/test_migrations_conflict/0002_conflicting_second.py @@ -1,3 +1,6 @@ +# encoding: utf8 +from __future__ import unicode_literals + from django.db import migrations, models diff --git a/tests/migrations/test_migrations_conflict/0002_second.py b/tests/migrations/test_migrations_conflict/0002_second.py index ace9a83347..0595de83f0 100644 --- a/tests/migrations/test_migrations_conflict/0002_second.py +++ b/tests/migrations/test_migrations_conflict/0002_second.py @@ -1,3 +1,6 @@ +# encoding: utf8 +from __future__ import unicode_literals + from django.db import migrations, models diff --git a/tests/migrations/test_migrations_squashed/0001_initial.py b/tests/migrations/test_migrations_squashed/0001_initial.py index 344bebdfe3..3544610eb4 100644 --- a/tests/migrations/test_migrations_squashed/0001_initial.py +++ b/tests/migrations/test_migrations_squashed/0001_initial.py @@ -1,3 +1,6 @@ +# encoding: utf8 +from __future__ import unicode_literals + from django.db import migrations, models diff --git a/tests/migrations/test_migrations_squashed/0001_squashed_0002.py b/tests/migrations/test_migrations_squashed/0001_squashed_0002.py index 742be641aa..b357e3f6f8 100644 --- a/tests/migrations/test_migrations_squashed/0001_squashed_0002.py +++ b/tests/migrations/test_migrations_squashed/0001_squashed_0002.py @@ -1,3 +1,6 @@ +# encoding: utf8 +from __future__ import unicode_literals + from django.db import migrations, models diff --git a/tests/migrations/test_migrations_squashed/0002_second.py b/tests/migrations/test_migrations_squashed/0002_second.py index ace9a83347..0595de83f0 100644 --- a/tests/migrations/test_migrations_squashed/0002_second.py +++ b/tests/migrations/test_migrations_squashed/0002_second.py @@ -1,3 +1,6 @@ +# encoding: utf8 +from __future__ import unicode_literals + from django.db import migrations, models diff --git a/tests/migrations/test_migrations_unmigdep/0001_initial.py b/tests/migrations/test_migrations_unmigdep/0001_initial.py index f4c11b4657..ff19085563 100644 --- a/tests/migrations/test_migrations_unmigdep/0001_initial.py +++ b/tests/migrations/test_migrations_unmigdep/0001_initial.py @@ -1,3 +1,6 @@ +# encoding: utf8 +from __future__ import unicode_literals + from django.db import migrations, models diff --git a/tests/migrations/test_writer.py b/tests/migrations/test_writer.py index c3889df0e7..4d552532d4 100644 --- a/tests/migrations/test_writer.py +++ b/tests/migrations/test_writer.py @@ -1,9 +1,9 @@ # encoding: utf8 - from __future__ import unicode_literals import datetime import os +import tokenize from django.core.validators import RegexValidator, EmailValidator from django.db import models, migrations @@ -59,7 +59,11 @@ class WriterTests(TestCase): self.assertSerializedEqual(1) self.assertSerializedEqual(None) self.assertSerializedEqual(b"foobar") + string, imports = MigrationWriter.serialize(b"foobar") + self.assertEqual(string, "b'foobar'") self.assertSerializedEqual("föobár") + string, imports = MigrationWriter.serialize("foobar") + self.assertEqual(string, "'foobar'") self.assertSerializedEqual({1: 2}) self.assertSerializedEqual(["a", 2, True, None]) self.assertSerializedEqual(set([2, 3, "eighty"])) @@ -92,15 +96,15 @@ class WriterTests(TestCase): # Classes validator = RegexValidator(message="hello") string, imports = MigrationWriter.serialize(validator) - self.assertEqual(string, "django.core.validators.RegexValidator(message=%s)" % repr("hello")) + self.assertEqual(string, "django.core.validators.RegexValidator(message='hello')") self.serialize_round_trip(validator) validator = EmailValidator(message="hello") # Test with a subclass. string, imports = MigrationWriter.serialize(validator) - self.assertEqual(string, "django.core.validators.EmailValidator(message=%s)" % repr("hello")) + self.assertEqual(string, "django.core.validators.EmailValidator(message='hello')") self.serialize_round_trip(validator) validator = deconstructible(path="custom.EmailValidator")(EmailValidator)(message="hello") string, imports = MigrationWriter.serialize(validator) - self.assertEqual(string, "custom.EmailValidator(message=%s)" % repr("hello")) + self.assertEqual(string, "custom.EmailValidator(message='hello')") # Django fields self.assertSerializedFieldEqual(models.CharField(max_length=255)) self.assertSerializedFieldEqual(models.TextField(null=True, blank=True)) @@ -153,6 +157,17 @@ class WriterTests(TestCase): # Just make sure it runs for now, and that things look alright. result = self.safe_exec(output) self.assertIn("Migration", result) + # In order to preserve compatibility with Python 3.2 unicode literals + # prefix shouldn't be added to strings. + tokens = tokenize.generate_tokens(six.StringIO(str(output)).readline) + for token_type, token_source, (srow, scol), _, line in tokens: + if token_type == tokenize.STRING: + self.assertFalse( + token_source.startswith('u'), + "Unicode literal prefix found at %d:%d: %r" % ( + srow, scol, line.strip() + ) + ) def test_migration_path(self): test_apps = [