Fixed #22350 -- Consistently serialize bytes and text in migrations.

Thanks to @treyhunner and Loïc for their suggestions and review.
This commit is contained in:
Simon Charette 2014-03-31 15:25:08 -04:00
parent b82f30785f
commit 72d3889db4
13 changed files with 76 additions and 12 deletions

View File

@ -227,7 +227,7 @@ class ModelState(object):
body['__module__'] = "__fake__" body['__module__'] = "__fake__"
# Then, make a Model object # Then, make a Model object
return type( return type(
self.name, str(self.name),
bases, bases,
body, body,
) )

View File

@ -53,8 +53,10 @@ class OperationWriter(object):
self.feed('%s={' % arg_name) self.feed('%s={' % arg_name)
self.indent() self.indent()
for key, value in arg_value.items(): for key, value in arg_value.items():
key_string, key_imports = MigrationWriter.serialize(key)
arg_string, arg_imports = MigrationWriter.serialize(value) 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) imports.update(arg_imports)
self.unindent() self.unindent()
self.feed('},') self.feed('},')
@ -122,7 +124,7 @@ class MigrationWriter(object):
dependencies.append(" migrations.swappable_dependency(settings.%s)," % dependency[1]) dependencies.append(" migrations.swappable_dependency(settings.%s)," % dependency[1])
imports.add("from django.conf import settings") imports.add("from django.conf import settings")
else: else:
dependencies.append(" %s," % repr(dependency)) dependencies.append(" %s," % self.serialize(dependency)[0])
items["dependencies"] = "\n".join(dependencies) + "\n" if dependencies else "" items["dependencies"] = "\n".join(dependencies) + "\n" if dependencies else ""
# Format imports nicely # Format imports nicely
@ -131,7 +133,7 @@ class MigrationWriter(object):
# If there's a replaces, make a string for it # If there's a replaces, make a string for it
if self.migration.replaces: 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") return (MIGRATION_TEMPLATE % items).encode("utf8")
@ -185,6 +187,12 @@ class MigrationWriter(object):
More advanced than repr() as it can encode things More advanced than repr() as it can encode things
like datetime.datetime.now. 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 # Sequences
if isinstance(value, (list, set, tuple)): if isinstance(value, (list, set, tuple)):
imports = set() imports = set()
@ -229,11 +237,20 @@ class MigrationWriter(object):
elif isinstance(value, SettingsReference): elif isinstance(value, SettingsReference):
return "settings.%s" % value.setting_name, set(["from django.conf import settings"]) return "settings.%s" % value.setting_name, set(["from django.conf import settings"])
# Simple types # 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() return repr(value), set()
# Promise elif isinstance(value, six.binary_type):
elif isinstance(value, Promise): value_repr = repr(value)
return repr(force_text(value)), set() 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 # Decimal
elif isinstance(value, decimal.Decimal): elif isinstance(value, decimal.Decimal):
return repr(value), set(["from decimal import Decimal"]) return repr(value), set(["from decimal import Decimal"])
@ -286,6 +303,8 @@ class MigrationWriter(object):
MIGRATION_TEMPLATE = """\ MIGRATION_TEMPLATE = """\
# encoding: utf8 # encoding: utf8
from __future__ import unicode_literals
from django.db import models, migrations from django.db import models, migrations
%(imports)s %(imports)s

View File

@ -1,3 +1,6 @@
# encoding: utf8
from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models

View File

@ -1,3 +1,6 @@
# encoding: utf8
from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models

View File

@ -1,3 +1,6 @@
# encoding: utf8
from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models

View File

@ -1,3 +1,6 @@
# encoding: utf8
from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models

View File

@ -1,3 +1,6 @@
# encoding: utf8
from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models

View File

@ -1,3 +1,6 @@
# encoding: utf8
from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models

View File

@ -1,3 +1,6 @@
# encoding: utf8
from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models

View File

@ -1,3 +1,6 @@
# encoding: utf8
from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models

View File

@ -1,3 +1,6 @@
# encoding: utf8
from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models

View File

@ -1,3 +1,6 @@
# encoding: utf8
from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations, models

View File

@ -1,9 +1,9 @@
# encoding: utf8 # encoding: utf8
from __future__ import unicode_literals from __future__ import unicode_literals
import datetime import datetime
import os import os
import tokenize
from django.core.validators import RegexValidator, EmailValidator from django.core.validators import RegexValidator, EmailValidator
from django.db import models, migrations from django.db import models, migrations
@ -59,7 +59,11 @@ class WriterTests(TestCase):
self.assertSerializedEqual(1) self.assertSerializedEqual(1)
self.assertSerializedEqual(None) self.assertSerializedEqual(None)
self.assertSerializedEqual(b"foobar") self.assertSerializedEqual(b"foobar")
string, imports = MigrationWriter.serialize(b"foobar")
self.assertEqual(string, "b'foobar'")
self.assertSerializedEqual("föobár") self.assertSerializedEqual("föobár")
string, imports = MigrationWriter.serialize("foobar")
self.assertEqual(string, "'foobar'")
self.assertSerializedEqual({1: 2}) self.assertSerializedEqual({1: 2})
self.assertSerializedEqual(["a", 2, True, None]) self.assertSerializedEqual(["a", 2, True, None])
self.assertSerializedEqual(set([2, 3, "eighty"])) self.assertSerializedEqual(set([2, 3, "eighty"]))
@ -92,15 +96,15 @@ class WriterTests(TestCase):
# Classes # Classes
validator = RegexValidator(message="hello") validator = RegexValidator(message="hello")
string, imports = MigrationWriter.serialize(validator) 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) self.serialize_round_trip(validator)
validator = EmailValidator(message="hello") # Test with a subclass. validator = EmailValidator(message="hello") # Test with a subclass.
string, imports = MigrationWriter.serialize(validator) 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) self.serialize_round_trip(validator)
validator = deconstructible(path="custom.EmailValidator")(EmailValidator)(message="hello") validator = deconstructible(path="custom.EmailValidator")(EmailValidator)(message="hello")
string, imports = MigrationWriter.serialize(validator) string, imports = MigrationWriter.serialize(validator)
self.assertEqual(string, "custom.EmailValidator(message=%s)" % repr("hello")) self.assertEqual(string, "custom.EmailValidator(message='hello')")
# Django fields # Django fields
self.assertSerializedFieldEqual(models.CharField(max_length=255)) self.assertSerializedFieldEqual(models.CharField(max_length=255))
self.assertSerializedFieldEqual(models.TextField(null=True, blank=True)) 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. # Just make sure it runs for now, and that things look alright.
result = self.safe_exec(output) result = self.safe_exec(output)
self.assertIn("Migration", result) 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): def test_migration_path(self):
test_apps = [ test_apps = [