mirror of
				https://github.com/django/django.git
				synced 2025-11-04 05:35:37 +00:00 
			
		
		
		
	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
					
				
					 13 changed files with 76 additions and 12 deletions
				
			
		| 
						 | 
					@ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue