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:
parent
b82f30785f
commit
72d3889db4
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
# encoding: utf8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
# encoding: utf8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
# encoding: utf8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
# encoding: utf8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
# encoding: utf8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
# encoding: utf8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
# encoding: utf8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
# encoding: utf8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
# encoding: utf8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
# encoding: utf8
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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 = [
|
||||||
|
|
Loading…
Reference in New Issue