PageRenderTime 106ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/tests/migrations/test_writer.py

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