test_writer.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. import datetime
  4. import functools
  5. import math
  6. import os
  7. import re
  8. import tokenize
  9. import unittest
  10. import custom_migration_operations.more_operations
  11. import custom_migration_operations.operations
  12. from django import get_version
  13. from django.conf import settings
  14. from django.core.validators import EmailValidator, RegexValidator
  15. from django.db import migrations, models
  16. from django.db.migrations.writer import (
  17. MigrationWriter, OperationWriter, SettingsReference,
  18. )
  19. from django.test import SimpleTestCase, ignore_warnings, mock
  20. from django.utils import datetime_safe, six
  21. from django.utils._os import upath
  22. from django.utils.deconstruct import deconstructible
  23. from django.utils.timezone import FixedOffset, get_default_timezone, utc
  24. from django.utils.translation import ugettext_lazy as _
  25. from .models import FoodManager, FoodQuerySet
  26. try:
  27. import enum
  28. except ImportError:
  29. enum = None
  30. class TestModel1(object):
  31. def upload_to(self):
  32. return "somewhere dynamic"
  33. thing = models.FileField(upload_to=upload_to)
  34. class OperationWriterTests(SimpleTestCase):
  35. def test_empty_signature(self):
  36. operation = custom_migration_operations.operations.TestOperation()
  37. buff, imports = OperationWriter(operation, indentation=0).serialize()
  38. self.assertEqual(imports, {'import custom_migration_operations.operations'})
  39. self.assertEqual(
  40. buff,
  41. 'custom_migration_operations.operations.TestOperation(\n'
  42. '),'
  43. )
  44. def test_args_signature(self):
  45. operation = custom_migration_operations.operations.ArgsOperation(1, 2)
  46. buff, imports = OperationWriter(operation, indentation=0).serialize()
  47. self.assertEqual(imports, {'import custom_migration_operations.operations'})
  48. self.assertEqual(
  49. buff,
  50. 'custom_migration_operations.operations.ArgsOperation(\n'
  51. ' arg1=1,\n'
  52. ' arg2=2,\n'
  53. '),'
  54. )
  55. def test_kwargs_signature(self):
  56. operation = custom_migration_operations.operations.KwargsOperation(kwarg1=1)
  57. buff, imports = OperationWriter(operation, indentation=0).serialize()
  58. self.assertEqual(imports, {'import custom_migration_operations.operations'})
  59. self.assertEqual(
  60. buff,
  61. 'custom_migration_operations.operations.KwargsOperation(\n'
  62. ' kwarg1=1,\n'
  63. '),'
  64. )
  65. def test_args_kwargs_signature(self):
  66. operation = custom_migration_operations.operations.ArgsKwargsOperation(1, 2, kwarg2=4)
  67. buff, imports = OperationWriter(operation, indentation=0).serialize()
  68. self.assertEqual(imports, {'import custom_migration_operations.operations'})
  69. self.assertEqual(
  70. buff,
  71. 'custom_migration_operations.operations.ArgsKwargsOperation(\n'
  72. ' arg1=1,\n'
  73. ' arg2=2,\n'
  74. ' kwarg2=4,\n'
  75. '),'
  76. )
  77. def test_nested_args_signature(self):
  78. operation = custom_migration_operations.operations.ArgsOperation(
  79. custom_migration_operations.operations.ArgsOperation(1, 2),
  80. custom_migration_operations.operations.KwargsOperation(kwarg1=3, kwarg2=4)
  81. )
  82. buff, imports = OperationWriter(operation, indentation=0).serialize()
  83. self.assertEqual(imports, {'import custom_migration_operations.operations'})
  84. self.assertEqual(
  85. buff,
  86. 'custom_migration_operations.operations.ArgsOperation(\n'
  87. ' arg1=custom_migration_operations.operations.ArgsOperation(\n'
  88. ' arg1=1,\n'
  89. ' arg2=2,\n'
  90. ' ),\n'
  91. ' arg2=custom_migration_operations.operations.KwargsOperation(\n'
  92. ' kwarg1=3,\n'
  93. ' kwarg2=4,\n'
  94. ' ),\n'
  95. '),'
  96. )
  97. def test_multiline_args_signature(self):
  98. operation = custom_migration_operations.operations.ArgsOperation("test\n arg1", "test\narg2")
  99. buff, imports = OperationWriter(operation, indentation=0).serialize()
  100. self.assertEqual(imports, {'import custom_migration_operations.operations'})
  101. self.assertEqual(
  102. buff,
  103. "custom_migration_operations.operations.ArgsOperation(\n"
  104. " arg1='test\\n arg1',\n"
  105. " arg2='test\\narg2',\n"
  106. "),"
  107. )
  108. def test_expand_args_signature(self):
  109. operation = custom_migration_operations.operations.ExpandArgsOperation([1, 2])
  110. buff, imports = OperationWriter(operation, indentation=0).serialize()
  111. self.assertEqual(imports, {'import custom_migration_operations.operations'})
  112. self.assertEqual(
  113. buff,
  114. 'custom_migration_operations.operations.ExpandArgsOperation(\n'
  115. ' arg=[\n'
  116. ' 1,\n'
  117. ' 2,\n'
  118. ' ],\n'
  119. '),'
  120. )
  121. def test_nested_operation_expand_args_signature(self):
  122. operation = custom_migration_operations.operations.ExpandArgsOperation(
  123. arg=[
  124. custom_migration_operations.operations.KwargsOperation(
  125. kwarg1=1,
  126. kwarg2=2,
  127. ),
  128. ]
  129. )
  130. buff, imports = OperationWriter(operation, indentation=0).serialize()
  131. self.assertEqual(imports, {'import custom_migration_operations.operations'})
  132. self.assertEqual(
  133. buff,
  134. 'custom_migration_operations.operations.ExpandArgsOperation(\n'
  135. ' arg=[\n'
  136. ' custom_migration_operations.operations.KwargsOperation(\n'
  137. ' kwarg1=1,\n'
  138. ' kwarg2=2,\n'
  139. ' ),\n'
  140. ' ],\n'
  141. '),'
  142. )
  143. class WriterTests(SimpleTestCase):
  144. """
  145. Tests the migration writer (makes migration files from Migration instances)
  146. """
  147. def safe_exec(self, string, value=None):
  148. l = {}
  149. try:
  150. exec(string, globals(), l)
  151. except Exception as e:
  152. if value:
  153. self.fail("Could not exec %r (from value %r): %s" % (string.strip(), value, e))
  154. else:
  155. self.fail("Could not exec %r: %s" % (string.strip(), e))
  156. return l
  157. def serialize_round_trip(self, value):
  158. string, imports = MigrationWriter.serialize(value)
  159. return self.safe_exec("%s\ntest_value_result = %s" % ("\n".join(imports), string), value)['test_value_result']
  160. def assertSerializedEqual(self, value):
  161. self.assertEqual(self.serialize_round_trip(value), value)
  162. def assertSerializedResultEqual(self, value, target):
  163. self.assertEqual(MigrationWriter.serialize(value), target)
  164. def assertSerializedFieldEqual(self, value):
  165. new_value = self.serialize_round_trip(value)
  166. self.assertEqual(value.__class__, new_value.__class__)
  167. self.assertEqual(value.max_length, new_value.max_length)
  168. self.assertEqual(value.null, new_value.null)
  169. self.assertEqual(value.unique, new_value.unique)
  170. def test_serialize_numbers(self):
  171. self.assertSerializedEqual(1)
  172. self.assertSerializedEqual(1.2)
  173. self.assertTrue(math.isinf(self.serialize_round_trip(float("inf"))))
  174. self.assertTrue(math.isinf(self.serialize_round_trip(float("-inf"))))
  175. self.assertTrue(math.isnan(self.serialize_round_trip(float("nan"))))
  176. def test_serialize_constants(self):
  177. self.assertSerializedEqual(None)
  178. self.assertSerializedEqual(True)
  179. self.assertSerializedEqual(False)
  180. def test_serialize_strings(self):
  181. self.assertSerializedEqual(b"foobar")
  182. string, imports = MigrationWriter.serialize(b"foobar")
  183. self.assertEqual(string, "b'foobar'")
  184. self.assertSerializedEqual("föobár")
  185. string, imports = MigrationWriter.serialize("foobar")
  186. self.assertEqual(string, "'foobar'")
  187. def test_serialize_multiline_strings(self):
  188. self.assertSerializedEqual(b"foo\nbar")
  189. string, imports = MigrationWriter.serialize(b"foo\nbar")
  190. self.assertEqual(string, "b'foo\\nbar'")
  191. self.assertSerializedEqual("föo\nbár")
  192. string, imports = MigrationWriter.serialize("foo\nbar")
  193. self.assertEqual(string, "'foo\\nbar'")
  194. def test_serialize_collections(self):
  195. self.assertSerializedEqual({1: 2})
  196. self.assertSerializedEqual(["a", 2, True, None])
  197. self.assertSerializedEqual({2, 3, "eighty"})
  198. self.assertSerializedEqual({"lalalala": ["yeah", "no", "maybe"]})
  199. self.assertSerializedEqual(_('Hello'))
  200. def test_serialize_builtin_types(self):
  201. self.assertSerializedEqual([list, tuple, dict, set, frozenset])
  202. self.assertSerializedResultEqual(
  203. [list, tuple, dict, set, frozenset],
  204. ("[list, tuple, dict, set, frozenset]", set())
  205. )
  206. @unittest.skipUnless(enum, "enum34 is required on Python 2")
  207. def test_serialize_enums(self):
  208. class TextEnum(enum.Enum):
  209. A = 'a-value'
  210. B = 'value-b'
  211. class BinaryEnum(enum.Enum):
  212. A = b'a-value'
  213. B = b'value-b'
  214. class IntEnum(enum.IntEnum):
  215. A = 1
  216. B = 2
  217. self.assertSerializedResultEqual(
  218. TextEnum.A,
  219. ("migrations.test_writer.TextEnum('a-value')", {'import migrations.test_writer'})
  220. )
  221. self.assertSerializedResultEqual(
  222. BinaryEnum.A,
  223. ("migrations.test_writer.BinaryEnum(b'a-value')", {'import migrations.test_writer'})
  224. )
  225. self.assertSerializedResultEqual(
  226. IntEnum.B,
  227. ("migrations.test_writer.IntEnum(2)", {'import migrations.test_writer'})
  228. )
  229. field = models.CharField(default=TextEnum.B, choices=[(m.value, m) for m in TextEnum])
  230. string = MigrationWriter.serialize(field)[0]
  231. self.assertEqual(
  232. string,
  233. "models.CharField(choices=["
  234. "('a-value', migrations.test_writer.TextEnum('a-value')), "
  235. "('value-b', migrations.test_writer.TextEnum('value-b'))], "
  236. "default=migrations.test_writer.TextEnum('value-b'))"
  237. )
  238. field = models.CharField(default=BinaryEnum.B, choices=[(m.value, m) for m in BinaryEnum])
  239. string = MigrationWriter.serialize(field)[0]
  240. self.assertEqual(
  241. string,
  242. "models.CharField(choices=["
  243. "(b'a-value', migrations.test_writer.BinaryEnum(b'a-value')), "
  244. "(b'value-b', migrations.test_writer.BinaryEnum(b'value-b'))], "
  245. "default=migrations.test_writer.BinaryEnum(b'value-b'))"
  246. )
  247. field = models.IntegerField(default=IntEnum.A, choices=[(m.value, m) for m in IntEnum])
  248. string = MigrationWriter.serialize(field)[0]
  249. self.assertEqual(
  250. string,
  251. "models.IntegerField(choices=["
  252. "(1, migrations.test_writer.IntEnum(1)), "
  253. "(2, migrations.test_writer.IntEnum(2))], "
  254. "default=migrations.test_writer.IntEnum(1))"
  255. )
  256. def test_serialize_functions(self):
  257. with six.assertRaisesRegex(self, ValueError, 'Cannot serialize function: lambda'):
  258. self.assertSerializedEqual(lambda x: 42)
  259. self.assertSerializedEqual(models.SET_NULL)
  260. string, imports = MigrationWriter.serialize(models.SET(42))
  261. self.assertEqual(string, 'models.SET(42)')
  262. self.serialize_round_trip(models.SET(42))
  263. def test_serialize_datetime(self):
  264. self.assertSerializedEqual(datetime.datetime.utcnow())
  265. self.assertSerializedEqual(datetime.datetime.utcnow)
  266. self.assertSerializedEqual(datetime.datetime.today())
  267. self.assertSerializedEqual(datetime.datetime.today)
  268. self.assertSerializedEqual(datetime.date.today())
  269. self.assertSerializedEqual(datetime.date.today)
  270. self.assertSerializedEqual(datetime.datetime.now().time())
  271. self.assertSerializedEqual(datetime.datetime(2014, 1, 1, 1, 1, tzinfo=get_default_timezone()))
  272. self.assertSerializedEqual(datetime.datetime(2013, 12, 31, 22, 1, tzinfo=FixedOffset(180)))
  273. self.assertSerializedResultEqual(
  274. datetime.datetime(2014, 1, 1, 1, 1),
  275. ("datetime.datetime(2014, 1, 1, 1, 1)", {'import datetime'})
  276. )
  277. self.assertSerializedResultEqual(
  278. datetime.datetime(2012, 1, 1, 1, 1, tzinfo=utc),
  279. (
  280. "datetime.datetime(2012, 1, 1, 1, 1, tzinfo=utc)",
  281. {'import datetime', 'from django.utils.timezone import utc'},
  282. )
  283. )
  284. def test_serialize_datetime_safe(self):
  285. self.assertSerializedResultEqual(
  286. datetime_safe.date(2014, 3, 31),
  287. ("datetime.date(2014, 3, 31)", {'import datetime'})
  288. )
  289. self.assertSerializedResultEqual(
  290. datetime_safe.time(10, 25),
  291. ("datetime.time(10, 25)", {'import datetime'})
  292. )
  293. self.assertSerializedResultEqual(
  294. datetime_safe.datetime(2014, 3, 31, 16, 4, 31),
  295. ("datetime.datetime(2014, 3, 31, 16, 4, 31)", {'import datetime'})
  296. )
  297. def test_serialize_fields(self):
  298. self.assertSerializedFieldEqual(models.CharField(max_length=255))
  299. self.assertSerializedResultEqual(
  300. models.CharField(max_length=255),
  301. ("models.CharField(max_length=255)", {"from django.db import models"})
  302. )
  303. self.assertSerializedFieldEqual(models.TextField(null=True, blank=True))
  304. self.assertSerializedResultEqual(
  305. models.TextField(null=True, blank=True),
  306. ("models.TextField(blank=True, null=True)", {'from django.db import models'})
  307. )
  308. def test_serialize_settings(self):
  309. self.assertSerializedEqual(SettingsReference(settings.AUTH_USER_MODEL, "AUTH_USER_MODEL"))
  310. self.assertSerializedResultEqual(
  311. SettingsReference("someapp.model", "AUTH_USER_MODEL"),
  312. ("settings.AUTH_USER_MODEL", {"from django.conf import settings"})
  313. )
  314. self.assertSerializedResultEqual(
  315. ((x, x * x) for x in range(3)),
  316. ("((0, 0), (1, 1), (2, 4))", set())
  317. )
  318. def test_serialize_compiled_regex(self):
  319. """
  320. Make sure compiled regex can be serialized.
  321. """
  322. regex = re.compile(r'^\w+$', re.U)
  323. self.assertSerializedEqual(regex)
  324. def test_serialize_class_based_validators(self):
  325. """
  326. Ticket #22943: Test serialization of class-based validators, including
  327. compiled regexes.
  328. """
  329. validator = RegexValidator(message="hello")
  330. string = MigrationWriter.serialize(validator)[0]
  331. self.assertEqual(string, "django.core.validators.RegexValidator(message='hello')")
  332. self.serialize_round_trip(validator)
  333. # Test with a compiled regex.
  334. validator = RegexValidator(regex=re.compile(r'^\w+$', re.U))
  335. string = MigrationWriter.serialize(validator)[0]
  336. self.assertEqual(string, "django.core.validators.RegexValidator(regex=re.compile('^\\\\w+$', 32))")
  337. self.serialize_round_trip(validator)
  338. # Test a string regex with flag
  339. validator = RegexValidator(r'^[0-9]+$', flags=re.U)
  340. string = MigrationWriter.serialize(validator)[0]
  341. self.assertEqual(string, "django.core.validators.RegexValidator('^[0-9]+$', flags=32)")
  342. self.serialize_round_trip(validator)
  343. # Test message and code
  344. validator = RegexValidator('^[-a-zA-Z0-9_]+$', 'Invalid', 'invalid')
  345. string = MigrationWriter.serialize(validator)[0]
  346. self.assertEqual(string, "django.core.validators.RegexValidator('^[-a-zA-Z0-9_]+$', 'Invalid', 'invalid')")
  347. self.serialize_round_trip(validator)
  348. # Test with a subclass.
  349. validator = EmailValidator(message="hello")
  350. string = MigrationWriter.serialize(validator)[0]
  351. self.assertEqual(string, "django.core.validators.EmailValidator(message='hello')")
  352. self.serialize_round_trip(validator)
  353. validator = deconstructible(path="migrations.test_writer.EmailValidator")(EmailValidator)(message="hello")
  354. string = MigrationWriter.serialize(validator)[0]
  355. self.assertEqual(string, "migrations.test_writer.EmailValidator(message='hello')")
  356. validator = deconstructible(path="custom.EmailValidator")(EmailValidator)(message="hello")
  357. with six.assertRaisesRegex(self, ImportError, "No module named '?custom'?"):
  358. MigrationWriter.serialize(validator)
  359. validator = deconstructible(path="django.core.validators.EmailValidator2")(EmailValidator)(message="hello")
  360. with self.assertRaisesMessage(ValueError, "Could not find object EmailValidator2 in django.core.validators."):
  361. MigrationWriter.serialize(validator)
  362. def test_serialize_empty_nonempty_tuple(self):
  363. """
  364. Ticket #22679: makemigrations generates invalid code for (an empty
  365. tuple) default_permissions = ()
  366. """
  367. empty_tuple = ()
  368. one_item_tuple = ('a',)
  369. many_items_tuple = ('a', 'b', 'c')
  370. self.assertSerializedEqual(empty_tuple)
  371. self.assertSerializedEqual(one_item_tuple)
  372. self.assertSerializedEqual(many_items_tuple)
  373. def test_serialize_builtins(self):
  374. string, imports = MigrationWriter.serialize(range)
  375. self.assertEqual(string, 'range')
  376. self.assertEqual(imports, set())
  377. @unittest.skipUnless(six.PY2, "Only applies on Python 2")
  378. def test_serialize_direct_function_reference(self):
  379. """
  380. Ticket #22436: You cannot use a function straight from its body
  381. (e.g. define the method and use it in the same body)
  382. """
  383. with self.assertRaises(ValueError):
  384. self.serialize_round_trip(TestModel1.thing)
  385. def test_serialize_local_function_reference(self):
  386. """
  387. Neither py2 or py3 can serialize a reference in a local scope.
  388. """
  389. class TestModel2(object):
  390. def upload_to(self):
  391. return "somewhere dynamic"
  392. thing = models.FileField(upload_to=upload_to)
  393. with self.assertRaises(ValueError):
  394. self.serialize_round_trip(TestModel2.thing)
  395. def test_serialize_local_function_reference_message(self):
  396. """
  397. Make sure user is seeing which module/function is the issue
  398. """
  399. class TestModel2(object):
  400. def upload_to(self):
  401. return "somewhere dynamic"
  402. thing = models.FileField(upload_to=upload_to)
  403. with six.assertRaisesRegex(self, ValueError,
  404. '^Could not find function upload_to in migrations.test_writer'):
  405. self.serialize_round_trip(TestModel2.thing)
  406. def test_serialize_managers(self):
  407. self.assertSerializedEqual(models.Manager())
  408. self.assertSerializedResultEqual(
  409. FoodQuerySet.as_manager(),
  410. ('migrations.models.FoodQuerySet.as_manager()', {'import migrations.models'})
  411. )
  412. self.assertSerializedEqual(FoodManager('a', 'b'))
  413. self.assertSerializedEqual(FoodManager('x', 'y', c=3, d=4))
  414. def test_serialize_frozensets(self):
  415. self.assertSerializedEqual(frozenset())
  416. self.assertSerializedEqual(frozenset("let it go"))
  417. def test_serialize_timedelta(self):
  418. self.assertSerializedEqual(datetime.timedelta())
  419. self.assertSerializedEqual(datetime.timedelta(minutes=42))
  420. def test_serialize_functools_partial(self):
  421. value = functools.partial(datetime.timedelta, 1, seconds=2)
  422. result = self.serialize_round_trip(value)
  423. self.assertEqual(result.func, value.func)
  424. self.assertEqual(result.args, value.args)
  425. self.assertEqual(result.keywords, value.keywords)
  426. def test_simple_migration(self):
  427. """
  428. Tests serializing a simple migration.
  429. """
  430. fields = {
  431. 'charfield': models.DateTimeField(default=datetime.datetime.utcnow),
  432. 'datetimefield': models.DateTimeField(default=datetime.datetime.utcnow),
  433. }
  434. options = {
  435. 'verbose_name': 'My model',
  436. 'verbose_name_plural': 'My models',
  437. }
  438. migration = type(str("Migration"), (migrations.Migration,), {
  439. "operations": [
  440. migrations.CreateModel("MyModel", tuple(fields.items()), options, (models.Model,)),
  441. migrations.CreateModel("MyModel2", tuple(fields.items()), bases=(models.Model,)),
  442. migrations.CreateModel(
  443. name="MyModel3", fields=tuple(fields.items()), options=options, bases=(models.Model,)
  444. ),
  445. migrations.DeleteModel("MyModel"),
  446. migrations.AddField("OtherModel", "datetimefield", fields["datetimefield"]),
  447. ],
  448. "dependencies": [("testapp", "some_other_one")],
  449. })
  450. writer = MigrationWriter(migration)
  451. output = writer.as_string()
  452. # It should NOT be unicode.
  453. self.assertIsInstance(output, six.binary_type, "Migration as_string returned unicode")
  454. # We don't test the output formatting - that's too fragile.
  455. # Just make sure it runs for now, and that things look alright.
  456. result = self.safe_exec(output)
  457. self.assertIn("Migration", result)
  458. # In order to preserve compatibility with Python 3.2 unicode literals
  459. # prefix shouldn't be added to strings.
  460. tokens = tokenize.generate_tokens(six.StringIO(str(output)).readline)
  461. for token_type, token_source, (srow, scol), __, line in tokens:
  462. if token_type == tokenize.STRING:
  463. self.assertFalse(
  464. token_source.startswith('u'),
  465. "Unicode literal prefix found at %d:%d: %r" % (
  466. srow, scol, line.strip()
  467. )
  468. )
  469. # Silence warning on Python 2: Not importing directory
  470. # 'tests/migrations/migrations_test_apps/without_init_file/migrations':
  471. # missing __init__.py
  472. @ignore_warnings(category=ImportWarning)
  473. def test_migration_path(self):
  474. test_apps = [
  475. 'migrations.migrations_test_apps.normal',
  476. 'migrations.migrations_test_apps.with_package_model',
  477. 'migrations.migrations_test_apps.without_init_file',
  478. ]
  479. base_dir = os.path.dirname(os.path.dirname(upath(__file__)))
  480. for app in test_apps:
  481. with self.modify_settings(INSTALLED_APPS={'append': app}):
  482. migration = migrations.Migration('0001_initial', app.split('.')[-1])
  483. expected_path = os.path.join(base_dir, *(app.split('.') + ['migrations', '0001_initial.py']))
  484. writer = MigrationWriter(migration)
  485. self.assertEqual(writer.path, expected_path)
  486. def test_custom_operation(self):
  487. migration = type(str("Migration"), (migrations.Migration,), {
  488. "operations": [
  489. custom_migration_operations.operations.TestOperation(),
  490. custom_migration_operations.operations.CreateModel(),
  491. migrations.CreateModel("MyModel", (), {}, (models.Model,)),
  492. custom_migration_operations.more_operations.TestOperation()
  493. ],
  494. "dependencies": []
  495. })
  496. writer = MigrationWriter(migration)
  497. output = writer.as_string()
  498. result = self.safe_exec(output)
  499. self.assertIn("custom_migration_operations", result)
  500. self.assertNotEqual(
  501. result['custom_migration_operations'].operations.TestOperation,
  502. result['custom_migration_operations'].more_operations.TestOperation
  503. )
  504. def test_sorted_imports(self):
  505. """
  506. #24155 - Tests ordering of imports.
  507. """
  508. migration = type(str("Migration"), (migrations.Migration,), {
  509. "operations": [
  510. migrations.AddField("mymodel", "myfield", models.DateTimeField(
  511. default=datetime.datetime(2012, 1, 1, 1, 1, tzinfo=utc),
  512. )),
  513. ]
  514. })
  515. writer = MigrationWriter(migration)
  516. output = writer.as_string().decode('utf-8')
  517. self.assertIn(
  518. "import datetime\n"
  519. "from django.db import migrations, models\n"
  520. "from django.utils.timezone import utc\n",
  521. output
  522. )
  523. def test_migration_file_header_comments(self):
  524. """
  525. Test comments at top of file.
  526. """
  527. migration = type(str("Migration"), (migrations.Migration,), {
  528. "operations": []
  529. })
  530. dt = datetime.datetime(2015, 7, 31, 4, 40, 0, 0, tzinfo=utc)
  531. with mock.patch('django.db.migrations.writer.now', lambda: dt):
  532. writer = MigrationWriter(migration)
  533. output = writer.as_string().decode('utf-8')
  534. self.assertTrue(
  535. output.startswith(
  536. "# -*- coding: utf-8 -*-\n"
  537. "# Generated by Django %(version)s on 2015-07-31 04:40\n" % {
  538. 'version': get_version(),
  539. }
  540. )
  541. )
  542. def test_models_import_omitted(self):
  543. """
  544. django.db.models shouldn't be imported if unused.
  545. """
  546. migration = type(str("Migration"), (migrations.Migration,), {
  547. "operations": [
  548. migrations.AlterModelOptions(
  549. name='model',
  550. options={'verbose_name': 'model', 'verbose_name_plural': 'models'},
  551. ),
  552. ]
  553. })
  554. writer = MigrationWriter(migration)
  555. output = writer.as_string().decode('utf-8')
  556. self.assertIn("from django.db import migrations\n", output)
  557. def test_deconstruct_class_arguments(self):
  558. # Yes, it doesn't make sense to use a class as a default for a
  559. # CharField. It does make sense for custom fields though, for example
  560. # an enumfield that takes the enum class as an argument.
  561. class DeconstructableInstances(object):
  562. def deconstruct(self):
  563. return ('DeconstructableInstances', [], {})
  564. string = MigrationWriter.serialize(models.CharField(default=DeconstructableInstances))[0]
  565. self.assertEqual(string, "models.CharField(default=migrations.test_writer.DeconstructableInstances)")