test_writer.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. # encoding: utf8
  2. from __future__ import unicode_literals
  3. import datetime
  4. import os
  5. import tokenize
  6. from django.core.validators import RegexValidator, EmailValidator
  7. from django.db import models, migrations
  8. from django.db.migrations.writer import MigrationWriter, SettingsReference
  9. from django.test import TestCase
  10. from django.conf import settings
  11. from django.utils import datetime_safe, six
  12. from django.utils.deconstruct import deconstructible
  13. from django.utils.translation import ugettext_lazy as _
  14. from django.utils.timezone import get_default_timezone
  15. class WriterTests(TestCase):
  16. """
  17. Tests the migration writer (makes migration files from Migration instances)
  18. """
  19. def safe_exec(self, string, value=None):
  20. l = {}
  21. try:
  22. exec(string, globals(), l)
  23. except Exception as e:
  24. if value:
  25. self.fail("Could not exec %r (from value %r): %s" % (string.strip(), value, e))
  26. else:
  27. self.fail("Could not exec %r: %s" % (string.strip(), e))
  28. return l
  29. def serialize_round_trip(self, value):
  30. string, imports = MigrationWriter.serialize(value)
  31. return self.safe_exec("%s\ntest_value_result = %s" % ("\n".join(imports), string), value)['test_value_result']
  32. def assertSerializedEqual(self, value):
  33. self.assertEqual(self.serialize_round_trip(value), value)
  34. def assertSerializedResultEqual(self, value, target):
  35. self.assertEqual(MigrationWriter.serialize(value), target)
  36. def assertSerializedFieldEqual(self, value):
  37. new_value = self.serialize_round_trip(value)
  38. self.assertEqual(value.__class__, new_value.__class__)
  39. self.assertEqual(value.max_length, new_value.max_length)
  40. self.assertEqual(value.null, new_value.null)
  41. self.assertEqual(value.unique, new_value.unique)
  42. def test_serialize(self):
  43. """
  44. Tests various different forms of the serializer.
  45. This does not care about formatting, just that the parsed result is
  46. correct, so we always exec() the result and check that.
  47. """
  48. # Basic values
  49. self.assertSerializedEqual(1)
  50. self.assertSerializedEqual(None)
  51. self.assertSerializedEqual(b"foobar")
  52. string, imports = MigrationWriter.serialize(b"foobar")
  53. self.assertEqual(string, "b'foobar'")
  54. self.assertSerializedEqual("föobár")
  55. string, imports = MigrationWriter.serialize("foobar")
  56. self.assertEqual(string, "'foobar'")
  57. self.assertSerializedEqual({1: 2})
  58. self.assertSerializedEqual(["a", 2, True, None])
  59. self.assertSerializedEqual(set([2, 3, "eighty"]))
  60. self.assertSerializedEqual({"lalalala": ["yeah", "no", "maybe"]})
  61. self.assertSerializedEqual(_('Hello'))
  62. # Functions
  63. with six.assertRaisesRegex(self, ValueError, 'Cannot serialize function: lambda'):
  64. self.assertSerializedEqual(lambda x: 42)
  65. self.assertSerializedEqual(models.SET_NULL)
  66. string, imports = MigrationWriter.serialize(models.SET(42))
  67. self.assertEqual(string, 'models.SET(42)')
  68. self.serialize_round_trip(models.SET(42))
  69. # Datetime stuff
  70. self.assertSerializedEqual(datetime.datetime.utcnow())
  71. self.assertSerializedEqual(datetime.datetime.utcnow)
  72. self.assertSerializedEqual(datetime.datetime.today())
  73. self.assertSerializedEqual(datetime.datetime.today)
  74. self.assertSerializedEqual(datetime.date.today())
  75. self.assertSerializedEqual(datetime.date.today)
  76. with self.assertRaises(ValueError):
  77. self.assertSerializedEqual(datetime.datetime(2012, 1, 1, 1, 1, tzinfo=get_default_timezone()))
  78. safe_date = datetime_safe.date(2014, 3, 31)
  79. string, imports = MigrationWriter.serialize(safe_date)
  80. self.assertEqual(string, repr(datetime.date(2014, 3, 31)))
  81. self.assertEqual(imports, {'import datetime'})
  82. safe_datetime = datetime_safe.datetime(2014, 3, 31, 16, 4, 31)
  83. string, imports = MigrationWriter.serialize(safe_datetime)
  84. self.assertEqual(string, repr(datetime.datetime(2014, 3, 31, 16, 4, 31)))
  85. self.assertEqual(imports, {'import datetime'})
  86. # Classes
  87. validator = RegexValidator(message="hello")
  88. string, imports = MigrationWriter.serialize(validator)
  89. self.assertEqual(string, "django.core.validators.RegexValidator(message='hello')")
  90. self.serialize_round_trip(validator)
  91. validator = EmailValidator(message="hello") # Test with a subclass.
  92. string, imports = MigrationWriter.serialize(validator)
  93. self.assertEqual(string, "django.core.validators.EmailValidator(message='hello')")
  94. self.serialize_round_trip(validator)
  95. validator = deconstructible(path="custom.EmailValidator")(EmailValidator)(message="hello")
  96. string, imports = MigrationWriter.serialize(validator)
  97. self.assertEqual(string, "custom.EmailValidator(message='hello')")
  98. # Django fields
  99. self.assertSerializedFieldEqual(models.CharField(max_length=255))
  100. self.assertSerializedFieldEqual(models.TextField(null=True, blank=True))
  101. # Setting references
  102. self.assertSerializedEqual(SettingsReference(settings.AUTH_USER_MODEL, "AUTH_USER_MODEL"))
  103. self.assertSerializedResultEqual(
  104. SettingsReference("someapp.model", "AUTH_USER_MODEL"),
  105. (
  106. "settings.AUTH_USER_MODEL",
  107. set(["from django.conf import settings"]),
  108. )
  109. )
  110. self.assertSerializedResultEqual(
  111. ((x, x * x) for x in range(3)),
  112. (
  113. "((0, 0), (1, 1), (2, 4))",
  114. set(),
  115. )
  116. )
  117. def test_simple_migration(self):
  118. """
  119. Tests serializing a simple migration.
  120. """
  121. fields = {
  122. 'charfield': models.DateTimeField(default=datetime.datetime.utcnow),
  123. 'datetimefield': models.DateTimeField(default=datetime.datetime.utcnow),
  124. }
  125. options = {
  126. 'verbose_name': 'My model',
  127. 'verbose_name_plural': 'My models',
  128. }
  129. migration = type(str("Migration"), (migrations.Migration,), {
  130. "operations": [
  131. migrations.CreateModel("MyModel", tuple(fields.items()), options, (models.Model,)),
  132. migrations.CreateModel("MyModel2", tuple(fields.items()), bases=(models.Model,)),
  133. migrations.CreateModel(name="MyModel3", fields=tuple(fields.items()), options=options, bases=(models.Model,)),
  134. migrations.DeleteModel("MyModel"),
  135. migrations.AddField("OtherModel", "datetimefield", fields["datetimefield"]),
  136. ],
  137. "dependencies": [("testapp", "some_other_one")],
  138. })
  139. writer = MigrationWriter(migration)
  140. output = writer.as_string()
  141. # It should NOT be unicode.
  142. self.assertIsInstance(output, six.binary_type, "Migration as_string returned unicode")
  143. # We don't test the output formatting - that's too fragile.
  144. # Just make sure it runs for now, and that things look alright.
  145. result = self.safe_exec(output)
  146. self.assertIn("Migration", result)
  147. # In order to preserve compatibility with Python 3.2 unicode literals
  148. # prefix shouldn't be added to strings.
  149. tokens = tokenize.generate_tokens(six.StringIO(str(output)).readline)
  150. for token_type, token_source, (srow, scol), __, line in tokens:
  151. if token_type == tokenize.STRING:
  152. self.assertFalse(
  153. token_source.startswith('u'),
  154. "Unicode literal prefix found at %d:%d: %r" % (
  155. srow, scol, line.strip()
  156. )
  157. )
  158. def test_migration_path(self):
  159. test_apps = [
  160. 'migrations.migrations_test_apps.normal',
  161. 'migrations.migrations_test_apps.with_package_model',
  162. ]
  163. base_dir = os.path.dirname(os.path.dirname(__file__))
  164. for app in test_apps:
  165. with self.modify_settings(INSTALLED_APPS={'append': app}):
  166. migration = migrations.Migration('0001_initial', app.split('.')[-1])
  167. expected_path = os.path.join(base_dir, *(app.split('.') + ['migrations', '0001_initial.py']))
  168. writer = MigrationWriter(migration)
  169. self.assertEqual(writer.path, expected_path)