tests.py 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203
  1. import datetime
  2. import unittest
  3. from django.test import TransactionTestCase
  4. from django.db import connection, DatabaseError, IntegrityError, OperationalError
  5. from django.db.models.fields import (BinaryField, BooleanField, CharField, IntegerField,
  6. PositiveIntegerField, SlugField, TextField)
  7. from django.db.models.fields.related import ManyToManyField, ForeignKey
  8. from django.db.transaction import atomic
  9. from .models import (Author, AuthorWithDefaultHeight, AuthorWithM2M, Book, BookWithLongName,
  10. BookWithSlug, BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename,
  11. UniqueTest, Thing, TagThrough, BookWithM2MThrough, AuthorTag, AuthorWithM2MThrough,
  12. AuthorWithEvenLongerName, BookWeak, Note)
  13. class SchemaTests(TransactionTestCase):
  14. """
  15. Tests that the schema-alteration code works correctly.
  16. Be aware that these tests are more liable than most to false results,
  17. as sometimes the code to check if a test has worked is almost as complex
  18. as the code it is testing.
  19. """
  20. available_apps = []
  21. models = [
  22. Author, AuthorWithM2M, Book, BookWithLongName, BookWithSlug,
  23. BookWithM2M, Tag, TagIndexed, TagM2MTest, TagUniqueRename, UniqueTest,
  24. Thing, TagThrough, BookWithM2MThrough, AuthorWithEvenLongerName,
  25. BookWeak,
  26. ]
  27. # Utility functions
  28. def tearDown(self):
  29. # Delete any tables made for our models
  30. self.delete_tables()
  31. def delete_tables(self):
  32. "Deletes all model tables for our models for a clean test environment"
  33. with connection.cursor() as cursor:
  34. connection.disable_constraint_checking()
  35. table_names = connection.introspection.table_names(cursor)
  36. for model in self.models:
  37. # Remove any M2M tables first
  38. for field in model._meta.local_many_to_many:
  39. with atomic():
  40. tbl = field.rel.through._meta.db_table
  41. if tbl in table_names:
  42. cursor.execute(connection.schema_editor().sql_delete_table % {
  43. "table": connection.ops.quote_name(tbl),
  44. })
  45. table_names.remove(tbl)
  46. # Then remove the main tables
  47. with atomic():
  48. tbl = model._meta.db_table
  49. if tbl in table_names:
  50. cursor.execute(connection.schema_editor().sql_delete_table % {
  51. "table": connection.ops.quote_name(tbl),
  52. })
  53. table_names.remove(tbl)
  54. connection.enable_constraint_checking()
  55. def column_classes(self, model):
  56. with connection.cursor() as cursor:
  57. columns = {
  58. d[0]: (connection.introspection.get_field_type(d[1], d), d)
  59. for d in connection.introspection.get_table_description(
  60. cursor,
  61. model._meta.db_table,
  62. )
  63. }
  64. # SQLite has a different format for field_type
  65. for name, (type, desc) in columns.items():
  66. if isinstance(type, tuple):
  67. columns[name] = (type[0], desc)
  68. # SQLite also doesn't error properly
  69. if not columns:
  70. raise DatabaseError("Table does not exist (empty pragma)")
  71. return columns
  72. def get_indexes(self, table):
  73. """
  74. Get the indexes on the table using a new cursor.
  75. """
  76. with connection.cursor() as cursor:
  77. return connection.introspection.get_indexes(cursor, table)
  78. def get_constraints(self, table):
  79. """
  80. Get the constraints on a table using a new cursor.
  81. """
  82. with connection.cursor() as cursor:
  83. return connection.introspection.get_constraints(cursor, table)
  84. # Tests
  85. def test_creation_deletion(self):
  86. """
  87. Tries creating a model's table, and then deleting it.
  88. """
  89. # Create the table
  90. with connection.schema_editor() as editor:
  91. editor.create_model(Author)
  92. # Check that it's there
  93. list(Author.objects.all())
  94. # Clean up that table
  95. with connection.schema_editor() as editor:
  96. editor.delete_model(Author)
  97. # Check that it's gone
  98. self.assertRaises(
  99. DatabaseError,
  100. lambda: list(Author.objects.all()),
  101. )
  102. @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support")
  103. def test_fk(self):
  104. "Tests that creating tables out of FK order, then repointing, works"
  105. # Create the table
  106. with connection.schema_editor() as editor:
  107. editor.create_model(Book)
  108. editor.create_model(Author)
  109. editor.create_model(Tag)
  110. # Check that initial tables are there
  111. list(Author.objects.all())
  112. list(Book.objects.all())
  113. # Make sure the FK constraint is present
  114. with self.assertRaises(IntegrityError):
  115. Book.objects.create(
  116. author_id=1,
  117. title="Much Ado About Foreign Keys",
  118. pub_date=datetime.datetime.now(),
  119. )
  120. # Repoint the FK constraint
  121. new_field = ForeignKey(Tag)
  122. new_field.set_attributes_from_name("author")
  123. with connection.schema_editor() as editor:
  124. editor.alter_field(
  125. Book,
  126. Book._meta.get_field("author"),
  127. new_field,
  128. strict=True,
  129. )
  130. # Make sure the new FK constraint is present
  131. constraints = self.get_constraints(Book._meta.db_table)
  132. for name, details in constraints.items():
  133. if details['columns'] == ["author_id"] and details['foreign_key']:
  134. self.assertEqual(details['foreign_key'], ('schema_tag', 'id'))
  135. break
  136. else:
  137. self.fail("No FK constraint for author_id found")
  138. @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support")
  139. def test_fk_db_constraint(self):
  140. "Tests that the db_constraint parameter is respected"
  141. # Create the table
  142. with connection.schema_editor() as editor:
  143. editor.create_model(Tag)
  144. editor.create_model(Author)
  145. editor.create_model(BookWeak)
  146. # Check that initial tables are there
  147. list(Author.objects.all())
  148. list(Tag.objects.all())
  149. list(BookWeak.objects.all())
  150. # Check that BookWeak doesn't have an FK constraint
  151. constraints = self.get_constraints(BookWeak._meta.db_table)
  152. for name, details in constraints.items():
  153. if details['columns'] == ["author_id"] and details['foreign_key']:
  154. self.fail("FK constraint for author_id found")
  155. # Make a db_constraint=False FK
  156. new_field = ForeignKey(Tag, db_constraint=False)
  157. new_field.set_attributes_from_name("tag")
  158. with connection.schema_editor() as editor:
  159. editor.add_field(
  160. Author,
  161. new_field,
  162. )
  163. # Make sure no FK constraint is present
  164. constraints = self.get_constraints(Author._meta.db_table)
  165. for name, details in constraints.items():
  166. if details['columns'] == ["tag_id"] and details['foreign_key']:
  167. self.fail("FK constraint for tag_id found")
  168. # Alter to one with a constraint
  169. new_field_2 = ForeignKey(Tag)
  170. new_field_2.set_attributes_from_name("tag")
  171. with connection.schema_editor() as editor:
  172. editor.alter_field(
  173. Author,
  174. new_field,
  175. new_field_2,
  176. strict=True,
  177. )
  178. # Make sure the new FK constraint is present
  179. constraints = self.get_constraints(Author._meta.db_table)
  180. for name, details in constraints.items():
  181. if details['columns'] == ["tag_id"] and details['foreign_key']:
  182. self.assertEqual(details['foreign_key'], ('schema_tag', 'id'))
  183. break
  184. else:
  185. self.fail("No FK constraint for tag_id found")
  186. # Alter to one without a constraint again
  187. new_field_2 = ForeignKey(Tag)
  188. new_field_2.set_attributes_from_name("tag")
  189. with connection.schema_editor() as editor:
  190. editor.alter_field(
  191. Author,
  192. new_field_2,
  193. new_field,
  194. strict=True,
  195. )
  196. # Make sure no FK constraint is present
  197. constraints = self.get_constraints(Author._meta.db_table)
  198. for name, details in constraints.items():
  199. if details['columns'] == ["tag_id"] and details['foreign_key']:
  200. self.fail("FK constraint for tag_id found")
  201. @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support")
  202. def test_m2m_db_constraint(self):
  203. # Create the table
  204. with connection.schema_editor() as editor:
  205. editor.create_model(Tag)
  206. editor.create_model(Author)
  207. # Check that initial tables are there
  208. list(Author.objects.all())
  209. list(Tag.objects.all())
  210. # Make a db_constraint=False FK
  211. new_field = ManyToManyField("schema.Tag", related_name="authors", db_constraint=False)
  212. new_field.contribute_to_class(Author, "tags")
  213. # Add the field
  214. with connection.schema_editor() as editor:
  215. editor.add_field(
  216. Author,
  217. new_field,
  218. )
  219. # Make sure no FK constraint is present
  220. constraints = self.get_constraints(new_field.rel.through._meta.db_table)
  221. for name, details in constraints.items():
  222. if details['columns'] == ["tag_id"] and details['foreign_key']:
  223. self.fail("FK constraint for tag_id found")
  224. def test_add_field(self):
  225. """
  226. Tests adding fields to models
  227. """
  228. # Create the table
  229. with connection.schema_editor() as editor:
  230. editor.create_model(Author)
  231. # Ensure there's no age field
  232. columns = self.column_classes(Author)
  233. self.assertNotIn("age", columns)
  234. # Add the new field
  235. new_field = IntegerField(null=True)
  236. new_field.set_attributes_from_name("age")
  237. with connection.schema_editor() as editor:
  238. editor.add_field(
  239. Author,
  240. new_field,
  241. )
  242. # Ensure the field is right afterwards
  243. columns = self.column_classes(Author)
  244. self.assertEqual(columns['age'][0], "IntegerField")
  245. self.assertEqual(columns['age'][1][6], True)
  246. def test_add_field_temp_default(self):
  247. """
  248. Tests adding fields to models with a temporary default
  249. """
  250. # Create the table
  251. with connection.schema_editor() as editor:
  252. editor.create_model(Author)
  253. # Ensure there's no age field
  254. columns = self.column_classes(Author)
  255. self.assertNotIn("age", columns)
  256. # Add some rows of data
  257. Author.objects.create(name="Andrew", height=30)
  258. Author.objects.create(name="Andrea")
  259. # Add a not-null field
  260. new_field = CharField(max_length=30, default="Godwin")
  261. new_field.set_attributes_from_name("surname")
  262. with connection.schema_editor() as editor:
  263. editor.add_field(
  264. Author,
  265. new_field,
  266. )
  267. # Ensure the field is right afterwards
  268. columns = self.column_classes(Author)
  269. self.assertEqual(columns['surname'][0], "CharField")
  270. self.assertEqual(columns['surname'][1][6],
  271. connection.features.interprets_empty_strings_as_nulls)
  272. def test_add_field_temp_default_boolean(self):
  273. """
  274. Tests adding fields to models with a temporary default where
  275. the default is False. (#21783)
  276. """
  277. # Create the table
  278. with connection.schema_editor() as editor:
  279. editor.create_model(Author)
  280. # Ensure there's no age field
  281. columns = self.column_classes(Author)
  282. self.assertNotIn("age", columns)
  283. # Add some rows of data
  284. Author.objects.create(name="Andrew", height=30)
  285. Author.objects.create(name="Andrea")
  286. # Add a not-null field
  287. new_field = BooleanField(default=False)
  288. new_field.set_attributes_from_name("awesome")
  289. with connection.schema_editor() as editor:
  290. editor.add_field(
  291. Author,
  292. new_field,
  293. )
  294. # Ensure the field is right afterwards
  295. columns = self.column_classes(Author)
  296. # BooleanField are stored as TINYINT(1) on MySQL.
  297. field_type = columns['awesome'][0]
  298. self.assertEqual(field_type, connection.features.introspected_boolean_field_type(new_field, created_separately=True))
  299. def test_add_field_default_transform(self):
  300. """
  301. Tests adding fields to models with a default that is not directly
  302. valid in the database (#22581)
  303. """
  304. class TestTransformField(IntegerField):
  305. # Weird field that saves the count of items in its value
  306. def get_default(self):
  307. return self.default
  308. def get_prep_value(self, value):
  309. if value is None:
  310. return 0
  311. return len(value)
  312. # Create the table
  313. with connection.schema_editor() as editor:
  314. editor.create_model(Author)
  315. # Add some rows of data
  316. Author.objects.create(name="Andrew", height=30)
  317. Author.objects.create(name="Andrea")
  318. # Add the field with a default it needs to cast (to string in this case)
  319. new_field = TestTransformField(default={1: 2})
  320. new_field.set_attributes_from_name("thing")
  321. with connection.schema_editor() as editor:
  322. editor.add_field(
  323. Author,
  324. new_field,
  325. )
  326. # Ensure the field is there
  327. columns = self.column_classes(Author)
  328. field_type, field_info = columns['thing']
  329. self.assertEqual(field_type, 'IntegerField')
  330. # Make sure the values were transformed correctly
  331. self.assertEqual(Author.objects.extra(where=["thing = 1"]).count(), 2)
  332. def test_add_field_binary(self):
  333. """
  334. Tests binary fields get a sane default (#22851)
  335. """
  336. # Create the table
  337. with connection.schema_editor() as editor:
  338. editor.create_model(Author)
  339. # Add the new field
  340. new_field = BinaryField(blank=True)
  341. new_field.set_attributes_from_name("bits")
  342. with connection.schema_editor() as editor:
  343. editor.add_field(
  344. Author,
  345. new_field,
  346. )
  347. # Ensure the field is right afterwards
  348. columns = self.column_classes(Author)
  349. # MySQL annoyingly uses the same backend, so it'll come back as one of
  350. # these two types.
  351. self.assertIn(columns['bits'][0], ("BinaryField", "TextField"))
  352. def test_alter(self):
  353. """
  354. Tests simple altering of fields
  355. """
  356. # Create the table
  357. with connection.schema_editor() as editor:
  358. editor.create_model(Author)
  359. # Ensure the field is right to begin with
  360. columns = self.column_classes(Author)
  361. self.assertEqual(columns['name'][0], "CharField")
  362. self.assertEqual(bool(columns['name'][1][6]), bool(connection.features.interprets_empty_strings_as_nulls))
  363. # Alter the name field to a TextField
  364. new_field = TextField(null=True)
  365. new_field.set_attributes_from_name("name")
  366. with connection.schema_editor() as editor:
  367. editor.alter_field(
  368. Author,
  369. Author._meta.get_field("name"),
  370. new_field,
  371. strict=True,
  372. )
  373. # Ensure the field is right afterwards
  374. columns = self.column_classes(Author)
  375. self.assertEqual(columns['name'][0], "TextField")
  376. self.assertEqual(columns['name'][1][6], True)
  377. # Change nullability again
  378. new_field2 = TextField(null=False)
  379. new_field2.set_attributes_from_name("name")
  380. with connection.schema_editor() as editor:
  381. editor.alter_field(
  382. Author,
  383. new_field,
  384. new_field2,
  385. strict=True,
  386. )
  387. # Ensure the field is right afterwards
  388. columns = self.column_classes(Author)
  389. self.assertEqual(columns['name'][0], "TextField")
  390. self.assertEqual(bool(columns['name'][1][6]), False)
  391. def test_alter_text_field(self):
  392. # Regression for "BLOB/TEXT column 'info' can't have a default value")
  393. # on MySQL.
  394. new_field = TextField(blank=True)
  395. new_field.set_attributes_from_name("info")
  396. with connection.schema_editor() as editor:
  397. editor.alter_field(
  398. Note,
  399. Note._meta.get_field("info"),
  400. new_field,
  401. strict=True,
  402. )
  403. def test_alter_null_to_not_null(self):
  404. """
  405. #23609 - Tests handling of default values when altering from NULL to NOT NULL.
  406. """
  407. # Create the table
  408. with connection.schema_editor() as editor:
  409. editor.create_model(Author)
  410. # Ensure the field is right to begin with
  411. columns = self.column_classes(Author)
  412. self.assertTrue(columns['height'][1][6])
  413. # Create some test data
  414. Author.objects.create(name='Not null author', height=12)
  415. Author.objects.create(name='Null author')
  416. # Verify null value
  417. self.assertEqual(Author.objects.get(name='Not null author').height, 12)
  418. self.assertIsNone(Author.objects.get(name='Null author').height)
  419. # Alter the height field to NOT NULL with default
  420. new_field = PositiveIntegerField(default=42)
  421. new_field.set_attributes_from_name("height")
  422. with connection.schema_editor() as editor:
  423. editor.alter_field(
  424. Author,
  425. Author._meta.get_field("height"),
  426. new_field
  427. )
  428. # Ensure the field is right afterwards
  429. columns = self.column_classes(Author)
  430. self.assertFalse(columns['height'][1][6])
  431. # Verify default value
  432. self.assertEqual(Author.objects.get(name='Not null author').height, 12)
  433. self.assertEqual(Author.objects.get(name='Null author').height, 42)
  434. @unittest.skipUnless(connection.features.supports_combined_alters, "No combined ALTER support")
  435. def test_alter_null_to_not_null_keeping_default(self):
  436. """
  437. #23738 - Can change a nullable field with default to non-nullable
  438. with the same default.
  439. """
  440. # Create the table
  441. with connection.schema_editor() as editor:
  442. editor.create_model(AuthorWithDefaultHeight)
  443. # Ensure the field is right to begin with
  444. columns = self.column_classes(AuthorWithDefaultHeight)
  445. self.assertTrue(columns['height'][1][6])
  446. # Alter the height field to NOT NULL keeping the previous default
  447. new_field = PositiveIntegerField(default=42)
  448. new_field.set_attributes_from_name("height")
  449. with connection.schema_editor() as editor:
  450. editor.alter_field(
  451. AuthorWithDefaultHeight,
  452. AuthorWithDefaultHeight._meta.get_field("height"),
  453. new_field,
  454. )
  455. # Ensure the field is right afterwards
  456. columns = self.column_classes(AuthorWithDefaultHeight)
  457. self.assertFalse(columns['height'][1][6])
  458. @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support")
  459. def test_alter_fk(self):
  460. """
  461. Tests altering of FKs
  462. """
  463. # Create the table
  464. with connection.schema_editor() as editor:
  465. editor.create_model(Author)
  466. editor.create_model(Book)
  467. # Ensure the field is right to begin with
  468. columns = self.column_classes(Book)
  469. self.assertEqual(columns['author_id'][0], "IntegerField")
  470. # Make sure the FK constraint is present
  471. constraints = self.get_constraints(Book._meta.db_table)
  472. for name, details in constraints.items():
  473. if details['columns'] == ["author_id"] and details['foreign_key']:
  474. self.assertEqual(details['foreign_key'], ('schema_author', 'id'))
  475. break
  476. else:
  477. self.fail("No FK constraint for author_id found")
  478. # Alter the FK
  479. new_field = ForeignKey(Author, editable=False)
  480. new_field.set_attributes_from_name("author")
  481. with connection.schema_editor() as editor:
  482. editor.alter_field(
  483. Book,
  484. Book._meta.get_field("author"),
  485. new_field,
  486. strict=True,
  487. )
  488. # Ensure the field is right afterwards
  489. columns = self.column_classes(Book)
  490. self.assertEqual(columns['author_id'][0], "IntegerField")
  491. # Make sure the FK constraint is present
  492. constraints = self.get_constraints(Book._meta.db_table)
  493. for name, details in constraints.items():
  494. if details['columns'] == ["author_id"] and details['foreign_key']:
  495. self.assertEqual(details['foreign_key'], ('schema_author', 'id'))
  496. break
  497. else:
  498. self.fail("No FK constraint for author_id found")
  499. def test_alter_implicit_id_to_explicit(self):
  500. """
  501. Should be able to convert an implicit "id" field to an explicit "id"
  502. primary key field.
  503. """
  504. with connection.schema_editor() as editor:
  505. editor.create_model(Author)
  506. new_field = IntegerField(primary_key=True)
  507. new_field.set_attributes_from_name("id")
  508. new_field.model = Author
  509. with connection.schema_editor() as editor:
  510. editor.alter_field(
  511. Author,
  512. Author._meta.get_field("id"),
  513. new_field,
  514. strict=True,
  515. )
  516. # This will fail if DROP DEFAULT is inadvertently executed on this
  517. # field which drops the id sequence, at least on PostgreSQL.
  518. Author.objects.create(name='Foo')
  519. def test_rename(self):
  520. """
  521. Tests simple altering of fields
  522. """
  523. # Create the table
  524. with connection.schema_editor() as editor:
  525. editor.create_model(Author)
  526. # Ensure the field is right to begin with
  527. columns = self.column_classes(Author)
  528. self.assertEqual(columns['name'][0], "CharField")
  529. self.assertNotIn("display_name", columns)
  530. # Alter the name field's name
  531. new_field = CharField(max_length=254)
  532. new_field.set_attributes_from_name("display_name")
  533. with connection.schema_editor() as editor:
  534. editor.alter_field(
  535. Author,
  536. Author._meta.get_field("name"),
  537. new_field,
  538. strict=True,
  539. )
  540. # Ensure the field is right afterwards
  541. columns = self.column_classes(Author)
  542. self.assertEqual(columns['display_name'][0], "CharField")
  543. self.assertNotIn("name", columns)
  544. def test_m2m_create(self):
  545. """
  546. Tests M2M fields on models during creation
  547. """
  548. # Create the tables
  549. with connection.schema_editor() as editor:
  550. editor.create_model(Author)
  551. editor.create_model(TagM2MTest)
  552. editor.create_model(BookWithM2M)
  553. # Ensure there is now an m2m table there
  554. columns = self.column_classes(BookWithM2M._meta.get_field("tags").rel.through)
  555. self.assertEqual(columns['tagm2mtest_id'][0], "IntegerField")
  556. def test_m2m_create_through(self):
  557. """
  558. Tests M2M fields on models during creation with through models
  559. """
  560. # Create the tables
  561. with connection.schema_editor() as editor:
  562. editor.create_model(TagThrough)
  563. editor.create_model(TagM2MTest)
  564. editor.create_model(BookWithM2MThrough)
  565. # Ensure there is now an m2m table there
  566. columns = self.column_classes(TagThrough)
  567. self.assertEqual(columns['book_id'][0], "IntegerField")
  568. self.assertEqual(columns['tag_id'][0], "IntegerField")
  569. def test_m2m(self):
  570. """
  571. Tests adding/removing M2M fields on models
  572. """
  573. # Create the tables
  574. with connection.schema_editor() as editor:
  575. editor.create_model(AuthorWithM2M)
  576. editor.create_model(TagM2MTest)
  577. # Create an M2M field
  578. new_field = ManyToManyField("schema.TagM2MTest", related_name="authors")
  579. new_field.contribute_to_class(AuthorWithM2M, "tags")
  580. try:
  581. # Ensure there's no m2m table there
  582. self.assertRaises(DatabaseError, self.column_classes, new_field.rel.through)
  583. # Add the field
  584. with connection.schema_editor() as editor:
  585. editor.add_field(
  586. Author,
  587. new_field,
  588. )
  589. # Ensure there is now an m2m table there
  590. columns = self.column_classes(new_field.rel.through)
  591. self.assertEqual(columns['tagm2mtest_id'][0], "IntegerField")
  592. # "Alter" the field. This should not rename the DB table to itself.
  593. with connection.schema_editor() as editor:
  594. editor.alter_field(
  595. Author,
  596. new_field,
  597. new_field,
  598. )
  599. # Remove the M2M table again
  600. with connection.schema_editor() as editor:
  601. editor.remove_field(
  602. Author,
  603. new_field,
  604. )
  605. # Ensure there's no m2m table there
  606. self.assertRaises(DatabaseError, self.column_classes, new_field.rel.through)
  607. finally:
  608. # Cleanup model states
  609. AuthorWithM2M._meta.local_many_to_many.remove(new_field)
  610. def test_m2m_through_alter(self):
  611. """
  612. Tests altering M2Ms with explicit through models (should no-op)
  613. """
  614. # Create the tables
  615. with connection.schema_editor() as editor:
  616. editor.create_model(AuthorTag)
  617. editor.create_model(AuthorWithM2MThrough)
  618. editor.create_model(TagM2MTest)
  619. # Ensure the m2m table is there
  620. self.assertEqual(len(self.column_classes(AuthorTag)), 3)
  621. # "Alter" the field's blankness. This should not actually do anything.
  622. with connection.schema_editor() as editor:
  623. old_field = AuthorWithM2MThrough._meta.get_field("tags")
  624. new_field = ManyToManyField("schema.TagM2MTest", related_name="authors", through="AuthorTag")
  625. new_field.contribute_to_class(AuthorWithM2MThrough, "tags")
  626. editor.alter_field(
  627. Author,
  628. old_field,
  629. new_field,
  630. )
  631. # Ensure the m2m table is still there
  632. self.assertEqual(len(self.column_classes(AuthorTag)), 3)
  633. def test_m2m_repoint(self):
  634. """
  635. Tests repointing M2M fields
  636. """
  637. # Create the tables
  638. with connection.schema_editor() as editor:
  639. editor.create_model(Author)
  640. editor.create_model(BookWithM2M)
  641. editor.create_model(TagM2MTest)
  642. editor.create_model(UniqueTest)
  643. # Ensure the M2M exists and points to TagM2MTest
  644. constraints = self.get_constraints(BookWithM2M._meta.get_field("tags").rel.through._meta.db_table)
  645. if connection.features.supports_foreign_keys:
  646. for name, details in constraints.items():
  647. if details['columns'] == ["tagm2mtest_id"] and details['foreign_key']:
  648. self.assertEqual(details['foreign_key'], ('schema_tagm2mtest', 'id'))
  649. break
  650. else:
  651. self.fail("No FK constraint for tagm2mtest_id found")
  652. # Repoint the M2M
  653. new_field = ManyToManyField(UniqueTest)
  654. new_field.contribute_to_class(BookWithM2M, "uniques")
  655. try:
  656. with connection.schema_editor() as editor:
  657. editor.alter_field(
  658. Author,
  659. BookWithM2M._meta.get_field("tags"),
  660. new_field,
  661. )
  662. # Ensure old M2M is gone
  663. self.assertRaises(DatabaseError, self.column_classes, BookWithM2M._meta.get_field("tags").rel.through)
  664. # Ensure the new M2M exists and points to UniqueTest
  665. constraints = self.get_constraints(new_field.rel.through._meta.db_table)
  666. if connection.features.supports_foreign_keys:
  667. for name, details in constraints.items():
  668. if details['columns'] == ["uniquetest_id"] and details['foreign_key']:
  669. self.assertEqual(details['foreign_key'], ('schema_uniquetest', 'id'))
  670. break
  671. else:
  672. self.fail("No FK constraint for uniquetest_id found")
  673. finally:
  674. # Cleanup through table separately
  675. with connection.schema_editor() as editor:
  676. editor.remove_field(BookWithM2M, BookWithM2M._meta.get_field("uniques"))
  677. # Cleanup model states
  678. BookWithM2M._meta.local_many_to_many.remove(new_field)
  679. BookWithM2M._meta._expire_cache()
  680. @unittest.skipUnless(connection.features.supports_column_check_constraints, "No check constraints")
  681. def test_check_constraints(self):
  682. """
  683. Tests creating/deleting CHECK constraints
  684. """
  685. # Create the tables
  686. with connection.schema_editor() as editor:
  687. editor.create_model(Author)
  688. # Ensure the constraint exists
  689. constraints = self.get_constraints(Author._meta.db_table)
  690. for name, details in constraints.items():
  691. if details['columns'] == ["height"] and details['check']:
  692. break
  693. else:
  694. self.fail("No check constraint for height found")
  695. # Alter the column to remove it
  696. new_field = IntegerField(null=True, blank=True)
  697. new_field.set_attributes_from_name("height")
  698. with connection.schema_editor() as editor:
  699. editor.alter_field(
  700. Author,
  701. Author._meta.get_field("height"),
  702. new_field,
  703. strict=True,
  704. )
  705. constraints = self.get_constraints(Author._meta.db_table)
  706. for name, details in constraints.items():
  707. if details['columns'] == ["height"] and details['check']:
  708. self.fail("Check constraint for height found")
  709. # Alter the column to re-add it
  710. with connection.schema_editor() as editor:
  711. editor.alter_field(
  712. Author,
  713. new_field,
  714. Author._meta.get_field("height"),
  715. strict=True,
  716. )
  717. constraints = self.get_constraints(Author._meta.db_table)
  718. for name, details in constraints.items():
  719. if details['columns'] == ["height"] and details['check']:
  720. break
  721. else:
  722. self.fail("No check constraint for height found")
  723. def test_unique(self):
  724. """
  725. Tests removing and adding unique constraints to a single column.
  726. """
  727. # Create the table
  728. with connection.schema_editor() as editor:
  729. editor.create_model(Tag)
  730. # Ensure the field is unique to begin with
  731. Tag.objects.create(title="foo", slug="foo")
  732. self.assertRaises(IntegrityError, Tag.objects.create, title="bar", slug="foo")
  733. Tag.objects.all().delete()
  734. # Alter the slug field to be non-unique
  735. new_field = SlugField(unique=False)
  736. new_field.set_attributes_from_name("slug")
  737. with connection.schema_editor() as editor:
  738. editor.alter_field(
  739. Tag,
  740. Tag._meta.get_field("slug"),
  741. new_field,
  742. strict=True,
  743. )
  744. # Ensure the field is no longer unique
  745. Tag.objects.create(title="foo", slug="foo")
  746. Tag.objects.create(title="bar", slug="foo")
  747. Tag.objects.all().delete()
  748. # Alter the slug field to be unique
  749. new_new_field = SlugField(unique=True)
  750. new_new_field.set_attributes_from_name("slug")
  751. with connection.schema_editor() as editor:
  752. editor.alter_field(
  753. Tag,
  754. new_field,
  755. new_new_field,
  756. strict=True,
  757. )
  758. # Ensure the field is unique again
  759. Tag.objects.create(title="foo", slug="foo")
  760. self.assertRaises(IntegrityError, Tag.objects.create, title="bar", slug="foo")
  761. Tag.objects.all().delete()
  762. # Rename the field
  763. new_field = SlugField(unique=False)
  764. new_field.set_attributes_from_name("slug2")
  765. with connection.schema_editor() as editor:
  766. editor.alter_field(
  767. Tag,
  768. Tag._meta.get_field("slug"),
  769. TagUniqueRename._meta.get_field("slug2"),
  770. strict=True,
  771. )
  772. # Ensure the field is still unique
  773. TagUniqueRename.objects.create(title="foo", slug2="foo")
  774. self.assertRaises(IntegrityError, TagUniqueRename.objects.create, title="bar", slug2="foo")
  775. Tag.objects.all().delete()
  776. def test_unique_together(self):
  777. """
  778. Tests removing and adding unique_together constraints on a model.
  779. """
  780. # Create the table
  781. with connection.schema_editor() as editor:
  782. editor.create_model(UniqueTest)
  783. # Ensure the fields are unique to begin with
  784. UniqueTest.objects.create(year=2012, slug="foo")
  785. UniqueTest.objects.create(year=2011, slug="foo")
  786. UniqueTest.objects.create(year=2011, slug="bar")
  787. self.assertRaises(IntegrityError, UniqueTest.objects.create, year=2012, slug="foo")
  788. UniqueTest.objects.all().delete()
  789. # Alter the model to its non-unique-together companion
  790. with connection.schema_editor() as editor:
  791. editor.alter_unique_together(
  792. UniqueTest,
  793. UniqueTest._meta.unique_together,
  794. [],
  795. )
  796. # Ensure the fields are no longer unique
  797. UniqueTest.objects.create(year=2012, slug="foo")
  798. UniqueTest.objects.create(year=2012, slug="foo")
  799. UniqueTest.objects.all().delete()
  800. # Alter it back
  801. new_new_field = SlugField(unique=True)
  802. new_new_field.set_attributes_from_name("slug")
  803. with connection.schema_editor() as editor:
  804. editor.alter_unique_together(
  805. UniqueTest,
  806. [],
  807. UniqueTest._meta.unique_together,
  808. )
  809. # Ensure the fields are unique again
  810. UniqueTest.objects.create(year=2012, slug="foo")
  811. self.assertRaises(IntegrityError, UniqueTest.objects.create, year=2012, slug="foo")
  812. UniqueTest.objects.all().delete()
  813. def test_index_together(self):
  814. """
  815. Tests removing and adding index_together constraints on a model.
  816. """
  817. # Create the table
  818. with connection.schema_editor() as editor:
  819. editor.create_model(Tag)
  820. # Ensure there's no index on the year/slug columns first
  821. self.assertEqual(
  822. False,
  823. any(
  824. c["index"]
  825. for c in self.get_constraints("schema_tag").values()
  826. if c['columns'] == ["slug", "title"]
  827. ),
  828. )
  829. # Alter the model to add an index
  830. with connection.schema_editor() as editor:
  831. editor.alter_index_together(
  832. Tag,
  833. [],
  834. [("slug", "title")],
  835. )
  836. # Ensure there is now an index
  837. self.assertEqual(
  838. True,
  839. any(
  840. c["index"]
  841. for c in self.get_constraints("schema_tag").values()
  842. if c['columns'] == ["slug", "title"]
  843. ),
  844. )
  845. # Alter it back
  846. new_new_field = SlugField(unique=True)
  847. new_new_field.set_attributes_from_name("slug")
  848. with connection.schema_editor() as editor:
  849. editor.alter_index_together(
  850. Tag,
  851. [("slug", "title")],
  852. [],
  853. )
  854. # Ensure there's no index
  855. self.assertEqual(
  856. False,
  857. any(
  858. c["index"]
  859. for c in self.get_constraints("schema_tag").values()
  860. if c['columns'] == ["slug", "title"]
  861. ),
  862. )
  863. def test_create_index_together(self):
  864. """
  865. Tests creating models with index_together already defined
  866. """
  867. # Create the table
  868. with connection.schema_editor() as editor:
  869. editor.create_model(TagIndexed)
  870. # Ensure there is an index
  871. self.assertEqual(
  872. True,
  873. any(
  874. c["index"]
  875. for c in self.get_constraints("schema_tagindexed").values()
  876. if c['columns'] == ["slug", "title"]
  877. ),
  878. )
  879. def test_db_table(self):
  880. """
  881. Tests renaming of the table
  882. """
  883. # Create the table
  884. with connection.schema_editor() as editor:
  885. editor.create_model(Author)
  886. # Ensure the table is there to begin with
  887. columns = self.column_classes(Author)
  888. self.assertEqual(columns['name'][0], "CharField")
  889. # Alter the table
  890. with connection.schema_editor() as editor:
  891. editor.alter_db_table(
  892. Author,
  893. "schema_author",
  894. "schema_otherauthor",
  895. )
  896. # Ensure the table is there afterwards
  897. Author._meta.db_table = "schema_otherauthor"
  898. columns = self.column_classes(Author)
  899. self.assertEqual(columns['name'][0], "CharField")
  900. # Alter the table again
  901. with connection.schema_editor() as editor:
  902. editor.alter_db_table(
  903. Author,
  904. "schema_otherauthor",
  905. "schema_author",
  906. )
  907. # Ensure the table is still there
  908. Author._meta.db_table = "schema_author"
  909. columns = self.column_classes(Author)
  910. self.assertEqual(columns['name'][0], "CharField")
  911. def test_indexes(self):
  912. """
  913. Tests creation/altering of indexes
  914. """
  915. # Create the table
  916. with connection.schema_editor() as editor:
  917. editor.create_model(Author)
  918. editor.create_model(Book)
  919. # Ensure the table is there and has the right index
  920. self.assertIn(
  921. "title",
  922. self.get_indexes(Book._meta.db_table),
  923. )
  924. # Alter to remove the index
  925. new_field = CharField(max_length=100, db_index=False)
  926. new_field.set_attributes_from_name("title")
  927. with connection.schema_editor() as editor:
  928. editor.alter_field(
  929. Book,
  930. Book._meta.get_field("title"),
  931. new_field,
  932. strict=True,
  933. )
  934. # Ensure the table is there and has no index
  935. self.assertNotIn(
  936. "title",
  937. self.get_indexes(Book._meta.db_table),
  938. )
  939. # Alter to re-add the index
  940. with connection.schema_editor() as editor:
  941. editor.alter_field(
  942. Book,
  943. new_field,
  944. Book._meta.get_field("title"),
  945. strict=True,
  946. )
  947. # Ensure the table is there and has the index again
  948. self.assertIn(
  949. "title",
  950. self.get_indexes(Book._meta.db_table),
  951. )
  952. # Add a unique column, verify that creates an implicit index
  953. with connection.schema_editor() as editor:
  954. editor.add_field(
  955. Book,
  956. BookWithSlug._meta.get_field("slug"),
  957. )
  958. self.assertIn(
  959. "slug",
  960. self.get_indexes(Book._meta.db_table),
  961. )
  962. # Remove the unique, check the index goes with it
  963. new_field2 = CharField(max_length=20, unique=False)
  964. new_field2.set_attributes_from_name("slug")
  965. with connection.schema_editor() as editor:
  966. editor.alter_field(
  967. BookWithSlug,
  968. BookWithSlug._meta.get_field("slug"),
  969. new_field2,
  970. strict=True,
  971. )
  972. self.assertNotIn(
  973. "slug",
  974. self.get_indexes(Book._meta.db_table),
  975. )
  976. def test_primary_key(self):
  977. """
  978. Tests altering of the primary key
  979. """
  980. # Create the table
  981. with connection.schema_editor() as editor:
  982. editor.create_model(Tag)
  983. # Ensure the table is there and has the right PK
  984. self.assertTrue(
  985. self.get_indexes(Tag._meta.db_table)['id']['primary_key'],
  986. )
  987. # Alter to change the PK
  988. new_field = SlugField(primary_key=True)
  989. new_field.set_attributes_from_name("slug")
  990. new_field.model = Tag
  991. with connection.schema_editor() as editor:
  992. editor.remove_field(Tag, Tag._meta.get_field("id"))
  993. editor.alter_field(
  994. Tag,
  995. Tag._meta.get_field("slug"),
  996. new_field,
  997. )
  998. # Ensure the PK changed
  999. self.assertNotIn(
  1000. 'id',
  1001. self.get_indexes(Tag._meta.db_table),
  1002. )
  1003. self.assertTrue(
  1004. self.get_indexes(Tag._meta.db_table)['slug']['primary_key'],
  1005. )
  1006. def test_context_manager_exit(self):
  1007. """
  1008. Ensures transaction is correctly closed when an error occurs
  1009. inside a SchemaEditor context.
  1010. """
  1011. class SomeError(Exception):
  1012. pass
  1013. try:
  1014. with connection.schema_editor():
  1015. raise SomeError
  1016. except SomeError:
  1017. self.assertFalse(connection.in_atomic_block)
  1018. @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support")
  1019. def test_foreign_key_index_long_names_regression(self):
  1020. """
  1021. Regression test for #21497.
  1022. Only affects databases that supports foreign keys.
  1023. """
  1024. # Create the table
  1025. with connection.schema_editor() as editor:
  1026. editor.create_model(AuthorWithEvenLongerName)
  1027. editor.create_model(BookWithLongName)
  1028. # Find the properly shortened column name
  1029. column_name = connection.ops.quote_name("author_foreign_key_with_really_long_field_name_id")
  1030. column_name = column_name[1:-1].lower() # unquote, and, for Oracle, un-upcase
  1031. # Ensure the table is there and has an index on the column
  1032. self.assertIn(
  1033. column_name,
  1034. self.get_indexes(BookWithLongName._meta.db_table),
  1035. )
  1036. @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support")
  1037. def test_add_foreign_key_long_names(self):
  1038. """
  1039. Regression test for #23009.
  1040. Only affects databases that supports foreign keys.
  1041. """
  1042. # Create the initial tables
  1043. with connection.schema_editor() as editor:
  1044. editor.create_model(AuthorWithEvenLongerName)
  1045. editor.create_model(BookWithLongName)
  1046. # Add a second FK, this would fail due to long ref name before the fix
  1047. new_field = ForeignKey(AuthorWithEvenLongerName, related_name="something")
  1048. new_field.set_attributes_from_name("author_other_really_long_named_i_mean_so_long_fk")
  1049. with connection.schema_editor() as editor:
  1050. editor.add_field(
  1051. BookWithLongName,
  1052. new_field,
  1053. )
  1054. def test_creation_deletion_reserved_names(self):
  1055. """
  1056. Tries creating a model's table, and then deleting it when it has a
  1057. SQL reserved name.
  1058. """
  1059. # Create the table
  1060. with connection.schema_editor() as editor:
  1061. try:
  1062. editor.create_model(Thing)
  1063. except OperationalError as e:
  1064. self.fail("Errors when applying initial migration for a model "
  1065. "with a table named after a SQL reserved word: %s" % e)
  1066. # Check that it's there
  1067. list(Thing.objects.all())
  1068. # Clean up that table
  1069. with connection.schema_editor() as editor:
  1070. editor.delete_model(Thing)
  1071. # Check that it's gone
  1072. self.assertRaises(
  1073. DatabaseError,
  1074. lambda: list(Thing.objects.all()),
  1075. )
  1076. @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support")
  1077. def test_remove_constraints_capital_letters(self):
  1078. """
  1079. #23065 - Constraint names must be quoted if they contain capital letters.
  1080. """
  1081. def get_field(*args, **kwargs):
  1082. kwargs['db_column'] = "CamelCase"
  1083. field = kwargs.pop('field_class', IntegerField)(*args, **kwargs)
  1084. field.set_attributes_from_name("CamelCase")
  1085. return field
  1086. model = Author
  1087. field = get_field()
  1088. table = model._meta.db_table
  1089. column = field.column
  1090. with connection.schema_editor() as editor:
  1091. editor.create_model(model)
  1092. editor.add_field(model, field)
  1093. editor.execute(
  1094. editor.sql_create_index % {
  1095. "table": editor.quote_name(table),
  1096. "name": editor.quote_name("CamelCaseIndex"),
  1097. "columns": editor.quote_name(column),
  1098. "extra": "",
  1099. }
  1100. )
  1101. editor.alter_field(model, get_field(db_index=True), field)
  1102. editor.execute(
  1103. editor.sql_create_unique % {
  1104. "table": editor.quote_name(table),
  1105. "name": editor.quote_name("CamelCaseUniqConstraint"),
  1106. "columns": editor.quote_name(field.column),
  1107. }
  1108. )
  1109. editor.alter_field(model, get_field(unique=True), field)
  1110. editor.execute(
  1111. editor.sql_create_fk % {
  1112. "table": editor.quote_name(table),
  1113. "name": editor.quote_name("CamelCaseFKConstraint"),
  1114. "column": editor.quote_name(column),
  1115. "to_table": editor.quote_name(table),
  1116. "to_column": editor.quote_name(model._meta.auto_field.column),
  1117. }
  1118. )
  1119. editor.alter_field(model, get_field(Author, field_class=ForeignKey), field)
  1120. def test_add_field_use_effective_default(self):
  1121. """
  1122. #23987 - effective_default() should be used as the field default when
  1123. adding a new field.
  1124. """
  1125. # Create the table
  1126. with connection.schema_editor() as editor:
  1127. editor.create_model(Author)
  1128. # Ensure there's no surname field
  1129. columns = self.column_classes(Author)
  1130. self.assertNotIn("surname", columns)
  1131. # Create a row
  1132. Author.objects.create(name='Anonymous1')
  1133. # Add new CharField to ensure default will be used from effective_default
  1134. new_field = CharField(max_length=15, blank=True)
  1135. new_field.set_attributes_from_name("surname")
  1136. with connection.schema_editor() as editor:
  1137. editor.add_field(Author, new_field)
  1138. # Ensure field was added with the right default
  1139. with connection.cursor() as cursor:
  1140. cursor.execute("SELECT surname FROM schema_author;")
  1141. item = cursor.fetchall()[0]
  1142. self.assertEqual(item[0], None if connection.features.interprets_empty_strings_as_nulls else '')