tests.py 70 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655
  1. import datetime
  2. import itertools
  3. import unittest
  4. from copy import copy
  5. from django.db import (
  6. DatabaseError, IntegrityError, OperationalError, connection,
  7. )
  8. from django.db.models import Model
  9. from django.db.models.fields import (
  10. AutoField, BigIntegerField, BinaryField, BooleanField, CharField,
  11. DateField, DateTimeField, IntegerField, PositiveIntegerField, SlugField,
  12. TextField, TimeField,
  13. )
  14. from django.db.models.fields.related import (
  15. ForeignKey, ManyToManyField, OneToOneField,
  16. )
  17. from django.db.transaction import atomic
  18. from django.test import TransactionTestCase, skipIfDBFeature
  19. from .fields import (
  20. CustomManyToManyField, InheritedManyToManyField, MediumBlobField,
  21. )
  22. from .models import (
  23. Author, AuthorWithDefaultHeight, AuthorWithEvenLongerName, Book, BookWeak,
  24. BookWithLongName, BookWithO2O, BookWithoutAuthor, BookWithSlug, IntegerPK,
  25. Note, NoteRename, Tag, TagIndexed, TagM2MTest, TagUniqueRename, Thing,
  26. UniqueTest, new_apps,
  27. )
  28. class SchemaTests(TransactionTestCase):
  29. """
  30. Tests that the schema-alteration code works correctly.
  31. Be aware that these tests are more liable than most to false results,
  32. as sometimes the code to check if a test has worked is almost as complex
  33. as the code it is testing.
  34. """
  35. available_apps = []
  36. models = [
  37. Author, AuthorWithDefaultHeight, AuthorWithEvenLongerName, Book,
  38. BookWeak, BookWithLongName, BookWithO2O, BookWithSlug, IntegerPK, Note,
  39. Tag, TagIndexed, TagM2MTest, TagUniqueRename, Thing, UniqueTest,
  40. ]
  41. # Utility functions
  42. def setUp(self):
  43. # local_models should contain test dependent model classes that will be
  44. # automatically removed from the app cache on test tear down.
  45. self.local_models = []
  46. def tearDown(self):
  47. # Delete any tables made for our models
  48. self.delete_tables()
  49. new_apps.clear_cache()
  50. for model in new_apps.get_models():
  51. model._meta._expire_cache()
  52. if 'schema' in new_apps.all_models:
  53. for model in self.local_models:
  54. del new_apps.all_models['schema'][model._meta.model_name]
  55. def delete_tables(self):
  56. "Deletes all model tables for our models for a clean test environment"
  57. converter = connection.introspection.table_name_converter
  58. with connection.cursor() as cursor:
  59. connection.disable_constraint_checking()
  60. table_names = connection.introspection.table_names(cursor)
  61. for model in itertools.chain(SchemaTests.models, self.local_models):
  62. # Remove any M2M tables first
  63. for field in model._meta.local_many_to_many:
  64. with atomic():
  65. tbl = converter(field.remote_field.through._meta.db_table)
  66. if tbl in table_names:
  67. cursor.execute(connection.schema_editor().sql_delete_table % {
  68. "table": connection.ops.quote_name(tbl),
  69. })
  70. table_names.remove(tbl)
  71. # Then remove the main tables
  72. with atomic():
  73. tbl = converter(model._meta.db_table)
  74. if tbl in table_names:
  75. cursor.execute(connection.schema_editor().sql_delete_table % {
  76. "table": connection.ops.quote_name(tbl),
  77. })
  78. table_names.remove(tbl)
  79. connection.enable_constraint_checking()
  80. def column_classes(self, model):
  81. with connection.cursor() as cursor:
  82. columns = {
  83. d[0]: (connection.introspection.get_field_type(d[1], d), d)
  84. for d in connection.introspection.get_table_description(
  85. cursor,
  86. model._meta.db_table,
  87. )
  88. }
  89. # SQLite has a different format for field_type
  90. for name, (type, desc) in columns.items():
  91. if isinstance(type, tuple):
  92. columns[name] = (type[0], desc)
  93. # SQLite also doesn't error properly
  94. if not columns:
  95. raise DatabaseError("Table does not exist (empty pragma)")
  96. return columns
  97. def get_indexes(self, table):
  98. """
  99. Get the indexes on the table using a new cursor.
  100. """
  101. with connection.cursor() as cursor:
  102. return connection.introspection.get_indexes(cursor, table)
  103. def get_constraints(self, table):
  104. """
  105. Get the constraints on a table using a new cursor.
  106. """
  107. with connection.cursor() as cursor:
  108. return connection.introspection.get_constraints(cursor, table)
  109. # Tests
  110. def test_creation_deletion(self):
  111. """
  112. Tries creating a model's table, and then deleting it.
  113. """
  114. # Create the table
  115. with connection.schema_editor() as editor:
  116. editor.create_model(Author)
  117. # Check that it's there
  118. list(Author.objects.all())
  119. # Clean up that table
  120. with connection.schema_editor() as editor:
  121. editor.delete_model(Author)
  122. # Check that it's gone
  123. self.assertRaises(
  124. DatabaseError,
  125. lambda: list(Author.objects.all()),
  126. )
  127. @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support")
  128. def test_fk(self):
  129. "Tests that creating tables out of FK order, then repointing, works"
  130. # Create the table
  131. with connection.schema_editor() as editor:
  132. editor.create_model(Book)
  133. editor.create_model(Author)
  134. editor.create_model(Tag)
  135. # Check that initial tables are there
  136. list(Author.objects.all())
  137. list(Book.objects.all())
  138. # Make sure the FK constraint is present
  139. with self.assertRaises(IntegrityError):
  140. Book.objects.create(
  141. author_id=1,
  142. title="Much Ado About Foreign Keys",
  143. pub_date=datetime.datetime.now(),
  144. )
  145. # Repoint the FK constraint
  146. old_field = Book._meta.get_field("author")
  147. new_field = ForeignKey(Tag)
  148. new_field.set_attributes_from_name("author")
  149. with connection.schema_editor() as editor:
  150. editor.alter_field(Book, old_field, new_field, strict=True)
  151. # Make sure the new FK constraint is present
  152. constraints = self.get_constraints(Book._meta.db_table)
  153. for name, details in constraints.items():
  154. if details['columns'] == ["author_id"] and details['foreign_key']:
  155. self.assertEqual(details['foreign_key'], ('schema_tag', 'id'))
  156. break
  157. else:
  158. self.fail("No FK constraint for author_id found")
  159. @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support")
  160. def test_fk_db_constraint(self):
  161. "Tests that the db_constraint parameter is respected"
  162. # Create the table
  163. with connection.schema_editor() as editor:
  164. editor.create_model(Tag)
  165. editor.create_model(Author)
  166. editor.create_model(BookWeak)
  167. # Check that initial tables are there
  168. list(Author.objects.all())
  169. list(Tag.objects.all())
  170. list(BookWeak.objects.all())
  171. # Check that BookWeak doesn't have an FK constraint
  172. constraints = self.get_constraints(BookWeak._meta.db_table)
  173. for name, details in constraints.items():
  174. if details['columns'] == ["author_id"] and details['foreign_key']:
  175. self.fail("FK constraint for author_id found")
  176. # Make a db_constraint=False FK
  177. new_field = ForeignKey(Tag, db_constraint=False)
  178. new_field.set_attributes_from_name("tag")
  179. with connection.schema_editor() as editor:
  180. editor.add_field(Author, new_field)
  181. # Make sure no FK constraint is present
  182. constraints = self.get_constraints(Author._meta.db_table)
  183. for name, details in constraints.items():
  184. if details['columns'] == ["tag_id"] and details['foreign_key']:
  185. self.fail("FK constraint for tag_id found")
  186. # Alter to one with a constraint
  187. new_field2 = ForeignKey(Tag)
  188. new_field2.set_attributes_from_name("tag")
  189. with connection.schema_editor() as editor:
  190. editor.alter_field(Author, new_field, new_field2, strict=True)
  191. # Make sure the new FK constraint is present
  192. constraints = self.get_constraints(Author._meta.db_table)
  193. for name, details in constraints.items():
  194. if details['columns'] == ["tag_id"] and details['foreign_key']:
  195. self.assertEqual(details['foreign_key'], ('schema_tag', 'id'))
  196. break
  197. else:
  198. self.fail("No FK constraint for tag_id found")
  199. # Alter to one without a constraint again
  200. new_field2 = ForeignKey(Tag)
  201. new_field2.set_attributes_from_name("tag")
  202. with connection.schema_editor() as editor:
  203. editor.alter_field(Author, new_field2, new_field, strict=True)
  204. # Make sure no FK constraint is present
  205. constraints = self.get_constraints(Author._meta.db_table)
  206. for name, details in constraints.items():
  207. if details['columns'] == ["tag_id"] and details['foreign_key']:
  208. self.fail("FK constraint for tag_id found")
  209. def _test_m2m_db_constraint(self, M2MFieldClass):
  210. class LocalAuthorWithM2M(Model):
  211. name = CharField(max_length=255)
  212. class Meta:
  213. app_label = 'schema'
  214. apps = new_apps
  215. self.local_models = [LocalAuthorWithM2M]
  216. # Create the table
  217. with connection.schema_editor() as editor:
  218. editor.create_model(Tag)
  219. editor.create_model(LocalAuthorWithM2M)
  220. # Check that initial tables are there
  221. list(LocalAuthorWithM2M.objects.all())
  222. list(Tag.objects.all())
  223. # Make a db_constraint=False FK
  224. new_field = M2MFieldClass(Tag, related_name="authors", db_constraint=False)
  225. new_field.contribute_to_class(LocalAuthorWithM2M, "tags")
  226. # Add the field
  227. with connection.schema_editor() as editor:
  228. editor.add_field(LocalAuthorWithM2M, new_field)
  229. # Make sure no FK constraint is present
  230. constraints = self.get_constraints(new_field.remote_field.through._meta.db_table)
  231. for name, details in constraints.items():
  232. if details['columns'] == ["tag_id"] and details['foreign_key']:
  233. self.fail("FK constraint for tag_id found")
  234. @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support")
  235. def test_m2m_db_constraint(self):
  236. self._test_m2m_db_constraint(ManyToManyField)
  237. @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support")
  238. def test_m2m_db_constraint_custom(self):
  239. self._test_m2m_db_constraint(CustomManyToManyField)
  240. @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support")
  241. def test_m2m_db_constraint_inherited(self):
  242. self._test_m2m_db_constraint(InheritedManyToManyField)
  243. def test_add_field(self):
  244. """
  245. Tests adding fields to models
  246. """
  247. # Create the table
  248. with connection.schema_editor() as editor:
  249. editor.create_model(Author)
  250. # Ensure there's no age field
  251. columns = self.column_classes(Author)
  252. self.assertNotIn("age", columns)
  253. # Add the new field
  254. new_field = IntegerField(null=True)
  255. new_field.set_attributes_from_name("age")
  256. with connection.schema_editor() as editor:
  257. editor.add_field(Author, new_field)
  258. # Ensure the field is right afterwards
  259. columns = self.column_classes(Author)
  260. self.assertEqual(columns['age'][0], "IntegerField")
  261. self.assertEqual(columns['age'][1][6], True)
  262. def test_add_field_temp_default(self):
  263. """
  264. Tests adding fields to models with a temporary default
  265. """
  266. # Create the table
  267. with connection.schema_editor() as editor:
  268. editor.create_model(Author)
  269. # Ensure there's no age field
  270. columns = self.column_classes(Author)
  271. self.assertNotIn("age", columns)
  272. # Add some rows of data
  273. Author.objects.create(name="Andrew", height=30)
  274. Author.objects.create(name="Andrea")
  275. # Add a not-null field
  276. new_field = CharField(max_length=30, default="Godwin")
  277. new_field.set_attributes_from_name("surname")
  278. with connection.schema_editor() as editor:
  279. editor.add_field(Author, new_field)
  280. # Ensure the field is right afterwards
  281. columns = self.column_classes(Author)
  282. self.assertEqual(columns['surname'][0], "CharField")
  283. self.assertEqual(columns['surname'][1][6],
  284. connection.features.interprets_empty_strings_as_nulls)
  285. def test_add_field_temp_default_boolean(self):
  286. """
  287. Tests adding fields to models with a temporary default where
  288. the default is False. (#21783)
  289. """
  290. # Create the table
  291. with connection.schema_editor() as editor:
  292. editor.create_model(Author)
  293. # Ensure there's no age field
  294. columns = self.column_classes(Author)
  295. self.assertNotIn("age", columns)
  296. # Add some rows of data
  297. Author.objects.create(name="Andrew", height=30)
  298. Author.objects.create(name="Andrea")
  299. # Add a not-null field
  300. new_field = BooleanField(default=False)
  301. new_field.set_attributes_from_name("awesome")
  302. with connection.schema_editor() as editor:
  303. editor.add_field(Author, new_field)
  304. # Ensure the field is right afterwards
  305. columns = self.column_classes(Author)
  306. # BooleanField are stored as TINYINT(1) on MySQL.
  307. field_type = columns['awesome'][0]
  308. self.assertEqual(field_type, connection.features.introspected_boolean_field_type(new_field, created_separately=True))
  309. def test_add_field_default_transform(self):
  310. """
  311. Tests adding fields to models with a default that is not directly
  312. valid in the database (#22581)
  313. """
  314. class TestTransformField(IntegerField):
  315. # Weird field that saves the count of items in its value
  316. def get_default(self):
  317. return self.default
  318. def get_prep_value(self, value):
  319. if value is None:
  320. return 0
  321. return len(value)
  322. # Create the table
  323. with connection.schema_editor() as editor:
  324. editor.create_model(Author)
  325. # Add some rows of data
  326. Author.objects.create(name="Andrew", height=30)
  327. Author.objects.create(name="Andrea")
  328. # Add the field with a default it needs to cast (to string in this case)
  329. new_field = TestTransformField(default={1: 2})
  330. new_field.set_attributes_from_name("thing")
  331. with connection.schema_editor() as editor:
  332. editor.add_field(Author, new_field)
  333. # Ensure the field is there
  334. columns = self.column_classes(Author)
  335. field_type, field_info = columns['thing']
  336. self.assertEqual(field_type, 'IntegerField')
  337. # Make sure the values were transformed correctly
  338. self.assertEqual(Author.objects.extra(where=["thing = 1"]).count(), 2)
  339. def test_add_field_binary(self):
  340. """
  341. Tests binary fields get a sane default (#22851)
  342. """
  343. # Create the table
  344. with connection.schema_editor() as editor:
  345. editor.create_model(Author)
  346. # Add the new field
  347. new_field = BinaryField(blank=True)
  348. new_field.set_attributes_from_name("bits")
  349. with connection.schema_editor() as editor:
  350. editor.add_field(Author, new_field)
  351. # Ensure the field is right afterwards
  352. columns = self.column_classes(Author)
  353. # MySQL annoyingly uses the same backend, so it'll come back as one of
  354. # these two types.
  355. self.assertIn(columns['bits'][0], ("BinaryField", "TextField"))
  356. @unittest.skipUnless(connection.vendor == 'mysql', "MySQL specific")
  357. def test_add_binaryfield_mediumblob(self):
  358. """
  359. Test adding a custom-sized binary field on MySQL (#24846).
  360. """
  361. # Create the table
  362. with connection.schema_editor() as editor:
  363. editor.create_model(Author)
  364. # Add the new field with default
  365. new_field = MediumBlobField(blank=True, default=b'123')
  366. new_field.set_attributes_from_name('bits')
  367. with connection.schema_editor() as editor:
  368. editor.add_field(Author, new_field)
  369. columns = self.column_classes(Author)
  370. # Introspection treats BLOBs as TextFields
  371. self.assertEqual(columns['bits'][0], "TextField")
  372. def test_alter(self):
  373. """
  374. Tests simple altering of fields
  375. """
  376. # Create the table
  377. with connection.schema_editor() as editor:
  378. editor.create_model(Author)
  379. # Ensure the field is right to begin with
  380. columns = self.column_classes(Author)
  381. self.assertEqual(columns['name'][0], "CharField")
  382. self.assertEqual(bool(columns['name'][1][6]), bool(connection.features.interprets_empty_strings_as_nulls))
  383. # Alter the name field to a TextField
  384. old_field = Author._meta.get_field("name")
  385. new_field = TextField(null=True)
  386. new_field.set_attributes_from_name("name")
  387. with connection.schema_editor() as editor:
  388. editor.alter_field(Author, old_field, new_field, strict=True)
  389. # Ensure the field is right afterwards
  390. columns = self.column_classes(Author)
  391. self.assertEqual(columns['name'][0], "TextField")
  392. self.assertEqual(columns['name'][1][6], True)
  393. # Change nullability again
  394. new_field2 = TextField(null=False)
  395. new_field2.set_attributes_from_name("name")
  396. with connection.schema_editor() as editor:
  397. editor.alter_field(Author, new_field, new_field2, strict=True)
  398. # Ensure the field is right afterwards
  399. columns = self.column_classes(Author)
  400. self.assertEqual(columns['name'][0], "TextField")
  401. self.assertEqual(bool(columns['name'][1][6]), bool(connection.features.interprets_empty_strings_as_nulls))
  402. def test_alter_text_field(self):
  403. # Regression for "BLOB/TEXT column 'info' can't have a default value")
  404. # on MySQL.
  405. # Create the table
  406. with connection.schema_editor() as editor:
  407. editor.create_model(Note)
  408. old_field = Note._meta.get_field("info")
  409. new_field = TextField(blank=True)
  410. new_field.set_attributes_from_name("info")
  411. with connection.schema_editor() as editor:
  412. editor.alter_field(Note, old_field, new_field, strict=True)
  413. def test_alter_text_field_to_date_field(self):
  414. """
  415. #25002 - Test conversion of text field to date field.
  416. """
  417. with connection.schema_editor() as editor:
  418. editor.create_model(Note)
  419. Note.objects.create(info='1988-05-05')
  420. old_field = Note._meta.get_field('info')
  421. new_field = DateField(blank=True)
  422. new_field.set_attributes_from_name('info')
  423. with connection.schema_editor() as editor:
  424. editor.alter_field(Note, old_field, new_field, strict=True)
  425. # Make sure the field isn't nullable
  426. columns = self.column_classes(Note)
  427. self.assertFalse(columns['info'][1][6])
  428. def test_alter_text_field_to_datetime_field(self):
  429. """
  430. #25002 - Test conversion of text field to datetime field.
  431. """
  432. with connection.schema_editor() as editor:
  433. editor.create_model(Note)
  434. Note.objects.create(info='1988-05-05 3:16:17.4567')
  435. old_field = Note._meta.get_field('info')
  436. new_field = DateTimeField(blank=True)
  437. new_field.set_attributes_from_name('info')
  438. with connection.schema_editor() as editor:
  439. editor.alter_field(Note, old_field, new_field, strict=True)
  440. # Make sure the field isn't nullable
  441. columns = self.column_classes(Note)
  442. self.assertFalse(columns['info'][1][6])
  443. def test_alter_text_field_to_time_field(self):
  444. """
  445. #25002 - Test conversion of text field to time field.
  446. """
  447. with connection.schema_editor() as editor:
  448. editor.create_model(Note)
  449. Note.objects.create(info='3:16:17.4567')
  450. old_field = Note._meta.get_field('info')
  451. new_field = TimeField(blank=True)
  452. new_field.set_attributes_from_name('info')
  453. with connection.schema_editor() as editor:
  454. editor.alter_field(Note, old_field, new_field, strict=True)
  455. # Make sure the field isn't nullable
  456. columns = self.column_classes(Note)
  457. self.assertFalse(columns['info'][1][6])
  458. @skipIfDBFeature('interprets_empty_strings_as_nulls')
  459. def test_alter_textual_field_keep_null_status(self):
  460. """
  461. Changing a field type shouldn't affect the not null status.
  462. """
  463. with connection.schema_editor() as editor:
  464. editor.create_model(Note)
  465. with self.assertRaises(IntegrityError):
  466. Note.objects.create(info=None)
  467. old_field = Note._meta.get_field("info")
  468. new_field = CharField(max_length=50)
  469. new_field.set_attributes_from_name("info")
  470. with connection.schema_editor() as editor:
  471. editor.alter_field(Note, old_field, new_field, strict=True)
  472. with self.assertRaises(IntegrityError):
  473. Note.objects.create(info=None)
  474. def test_alter_numeric_field_keep_null_status(self):
  475. """
  476. Changing a field type shouldn't affect the not null status.
  477. """
  478. with connection.schema_editor() as editor:
  479. editor.create_model(UniqueTest)
  480. with self.assertRaises(IntegrityError):
  481. UniqueTest.objects.create(year=None, slug='aaa')
  482. old_field = UniqueTest._meta.get_field("year")
  483. new_field = BigIntegerField()
  484. new_field.set_attributes_from_name("year")
  485. with connection.schema_editor() as editor:
  486. editor.alter_field(UniqueTest, old_field, new_field, strict=True)
  487. with self.assertRaises(IntegrityError):
  488. UniqueTest.objects.create(year=None, slug='bbb')
  489. def test_alter_null_to_not_null(self):
  490. """
  491. #23609 - Tests handling of default values when altering from NULL to NOT NULL.
  492. """
  493. # Create the table
  494. with connection.schema_editor() as editor:
  495. editor.create_model(Author)
  496. # Ensure the field is right to begin with
  497. columns = self.column_classes(Author)
  498. self.assertTrue(columns['height'][1][6])
  499. # Create some test data
  500. Author.objects.create(name='Not null author', height=12)
  501. Author.objects.create(name='Null author')
  502. # Verify null value
  503. self.assertEqual(Author.objects.get(name='Not null author').height, 12)
  504. self.assertIsNone(Author.objects.get(name='Null author').height)
  505. # Alter the height field to NOT NULL with default
  506. old_field = Author._meta.get_field("height")
  507. new_field = PositiveIntegerField(default=42)
  508. new_field.set_attributes_from_name("height")
  509. with connection.schema_editor() as editor:
  510. editor.alter_field(Author, old_field, new_field)
  511. # Ensure the field is right afterwards
  512. columns = self.column_classes(Author)
  513. self.assertFalse(columns['height'][1][6])
  514. # Verify default value
  515. self.assertEqual(Author.objects.get(name='Not null author').height, 12)
  516. self.assertEqual(Author.objects.get(name='Null author').height, 42)
  517. def test_alter_charfield_to_null(self):
  518. """
  519. #24307 - Should skip an alter statement on databases with
  520. interprets_empty_strings_as_null when changing a CharField to null.
  521. """
  522. # Create the table
  523. with connection.schema_editor() as editor:
  524. editor.create_model(Author)
  525. # Change the CharField to null
  526. old_field = Author._meta.get_field('name')
  527. new_field = copy(old_field)
  528. new_field.null = True
  529. with connection.schema_editor() as editor:
  530. editor.alter_field(Author, old_field, new_field)
  531. def test_alter_textfield_to_null(self):
  532. """
  533. #24307 - Should skip an alter statement on databases with
  534. interprets_empty_strings_as_null when changing a TextField to null.
  535. """
  536. # Create the table
  537. with connection.schema_editor() as editor:
  538. editor.create_model(Note)
  539. # Change the TextField to null
  540. old_field = Note._meta.get_field('info')
  541. new_field = copy(old_field)
  542. new_field.null = True
  543. with connection.schema_editor() as editor:
  544. editor.alter_field(Note, old_field, new_field)
  545. @unittest.skipUnless(connection.features.supports_combined_alters, "No combined ALTER support")
  546. def test_alter_null_to_not_null_keeping_default(self):
  547. """
  548. #23738 - Can change a nullable field with default to non-nullable
  549. with the same default.
  550. """
  551. # Create the table
  552. with connection.schema_editor() as editor:
  553. editor.create_model(AuthorWithDefaultHeight)
  554. # Ensure the field is right to begin with
  555. columns = self.column_classes(AuthorWithDefaultHeight)
  556. self.assertTrue(columns['height'][1][6])
  557. # Alter the height field to NOT NULL keeping the previous default
  558. old_field = AuthorWithDefaultHeight._meta.get_field("height")
  559. new_field = PositiveIntegerField(default=42)
  560. new_field.set_attributes_from_name("height")
  561. with connection.schema_editor() as editor:
  562. editor.alter_field(AuthorWithDefaultHeight, old_field, new_field)
  563. # Ensure the field is right afterwards
  564. columns = self.column_classes(AuthorWithDefaultHeight)
  565. self.assertFalse(columns['height'][1][6])
  566. @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support")
  567. def test_alter_fk(self):
  568. """
  569. Tests altering of FKs
  570. """
  571. # Create the table
  572. with connection.schema_editor() as editor:
  573. editor.create_model(Author)
  574. editor.create_model(Book)
  575. # Ensure the field is right to begin with
  576. columns = self.column_classes(Book)
  577. self.assertEqual(columns['author_id'][0], "IntegerField")
  578. # Make sure the FK constraint is present
  579. constraints = self.get_constraints(Book._meta.db_table)
  580. for name, details in constraints.items():
  581. if details['columns'] == ["author_id"] and details['foreign_key']:
  582. self.assertEqual(details['foreign_key'], ('schema_author', 'id'))
  583. break
  584. else:
  585. self.fail("No FK constraint for author_id found")
  586. # Alter the FK
  587. old_field = Book._meta.get_field("author")
  588. new_field = ForeignKey(Author, editable=False)
  589. new_field.set_attributes_from_name("author")
  590. with connection.schema_editor() as editor:
  591. editor.alter_field(Book, old_field, new_field, strict=True)
  592. # Ensure the field is right afterwards
  593. columns = self.column_classes(Book)
  594. self.assertEqual(columns['author_id'][0], "IntegerField")
  595. # Make sure the FK constraint is present
  596. constraints = self.get_constraints(Book._meta.db_table)
  597. for name, details in constraints.items():
  598. if details['columns'] == ["author_id"] and details['foreign_key']:
  599. self.assertEqual(details['foreign_key'], ('schema_author', 'id'))
  600. break
  601. else:
  602. self.fail("No FK constraint for author_id found")
  603. @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support")
  604. def test_alter_to_fk(self):
  605. """
  606. #24447 - Tests adding a FK constraint for an existing column
  607. """
  608. class LocalBook(Model):
  609. author = IntegerField()
  610. title = CharField(max_length=100, db_index=True)
  611. pub_date = DateTimeField()
  612. class Meta:
  613. app_label = 'schema'
  614. apps = new_apps
  615. self.local_models = [LocalBook]
  616. # Create the tables
  617. with connection.schema_editor() as editor:
  618. editor.create_model(Author)
  619. editor.create_model(LocalBook)
  620. # Ensure no FK constraint exists
  621. constraints = self.get_constraints(LocalBook._meta.db_table)
  622. for name, details in constraints.items():
  623. if details['foreign_key']:
  624. self.fail('Found an unexpected FK constraint to %s' % details['columns'])
  625. old_field = LocalBook._meta.get_field("author")
  626. new_field = ForeignKey(Author)
  627. new_field.set_attributes_from_name("author")
  628. with connection.schema_editor() as editor:
  629. editor.alter_field(LocalBook, old_field, new_field, strict=True)
  630. constraints = self.get_constraints(LocalBook._meta.db_table)
  631. # Ensure FK constraint exists
  632. for name, details in constraints.items():
  633. if details['foreign_key'] and details['columns'] == ["author_id"]:
  634. self.assertEqual(details['foreign_key'], ('schema_author', 'id'))
  635. break
  636. else:
  637. self.fail("No FK constraint for author_id found")
  638. @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support")
  639. def test_alter_o2o_to_fk(self):
  640. """
  641. #24163 - Tests altering of OneToOneField to ForeignKey
  642. """
  643. # Create the table
  644. with connection.schema_editor() as editor:
  645. editor.create_model(Author)
  646. editor.create_model(BookWithO2O)
  647. # Ensure the field is right to begin with
  648. columns = self.column_classes(BookWithO2O)
  649. self.assertEqual(columns['author_id'][0], "IntegerField")
  650. # Ensure the field is unique
  651. author = Author.objects.create(name="Joe")
  652. BookWithO2O.objects.create(author=author, title="Django 1", pub_date=datetime.datetime.now())
  653. with self.assertRaises(IntegrityError):
  654. BookWithO2O.objects.create(author=author, title="Django 2", pub_date=datetime.datetime.now())
  655. BookWithO2O.objects.all().delete()
  656. # Make sure the FK constraint is present
  657. constraints = self.get_constraints(BookWithO2O._meta.db_table)
  658. author_is_fk = False
  659. for name, details in constraints.items():
  660. if details['columns'] == ['author_id']:
  661. if details['foreign_key'] and details['foreign_key'] == ('schema_author', 'id'):
  662. author_is_fk = True
  663. self.assertTrue(author_is_fk, "No FK constraint for author_id found")
  664. # Alter the OneToOneField to ForeignKey
  665. old_field = BookWithO2O._meta.get_field("author")
  666. new_field = ForeignKey(Author)
  667. new_field.set_attributes_from_name("author")
  668. with connection.schema_editor() as editor:
  669. editor.alter_field(BookWithO2O, old_field, new_field, strict=True)
  670. # Ensure the field is right afterwards
  671. columns = self.column_classes(Book)
  672. self.assertEqual(columns['author_id'][0], "IntegerField")
  673. # Ensure the field is not unique anymore
  674. Book.objects.create(author=author, title="Django 1", pub_date=datetime.datetime.now())
  675. Book.objects.create(author=author, title="Django 2", pub_date=datetime.datetime.now())
  676. # Make sure the FK constraint is still present
  677. constraints = self.get_constraints(Book._meta.db_table)
  678. author_is_fk = False
  679. for name, details in constraints.items():
  680. if details['columns'] == ['author_id']:
  681. if details['foreign_key'] and details['foreign_key'] == ('schema_author', 'id'):
  682. author_is_fk = True
  683. self.assertTrue(author_is_fk, "No FK constraint for author_id found")
  684. @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support")
  685. def test_alter_fk_to_o2o(self):
  686. """
  687. #24163 - Tests altering of ForeignKey to OneToOneField
  688. """
  689. # Create the table
  690. with connection.schema_editor() as editor:
  691. editor.create_model(Author)
  692. editor.create_model(Book)
  693. # Ensure the field is right to begin with
  694. columns = self.column_classes(Book)
  695. self.assertEqual(columns['author_id'][0], "IntegerField")
  696. # Ensure the field is not unique
  697. author = Author.objects.create(name="Joe")
  698. Book.objects.create(author=author, title="Django 1", pub_date=datetime.datetime.now())
  699. Book.objects.create(author=author, title="Django 2", pub_date=datetime.datetime.now())
  700. Book.objects.all().delete()
  701. # Make sure the FK constraint is present
  702. constraints = self.get_constraints(Book._meta.db_table)
  703. author_is_fk = False
  704. for name, details in constraints.items():
  705. if details['columns'] == ['author_id']:
  706. if details['foreign_key'] and details['foreign_key'] == ('schema_author', 'id'):
  707. author_is_fk = True
  708. self.assertTrue(author_is_fk, "No FK constraint for author_id found")
  709. # Alter the ForeignKey to OneToOneField
  710. old_field = Book._meta.get_field("author")
  711. new_field = OneToOneField(Author)
  712. new_field.set_attributes_from_name("author")
  713. with connection.schema_editor() as editor:
  714. editor.alter_field(Book, old_field, new_field, strict=True)
  715. # Ensure the field is right afterwards
  716. columns = self.column_classes(BookWithO2O)
  717. self.assertEqual(columns['author_id'][0], "IntegerField")
  718. # Ensure the field is unique now
  719. BookWithO2O.objects.create(author=author, title="Django 1", pub_date=datetime.datetime.now())
  720. with self.assertRaises(IntegrityError):
  721. BookWithO2O.objects.create(author=author, title="Django 2", pub_date=datetime.datetime.now())
  722. # Make sure the FK constraint is present
  723. constraints = self.get_constraints(BookWithO2O._meta.db_table)
  724. author_is_fk = False
  725. for name, details in constraints.items():
  726. if details['columns'] == ['author_id']:
  727. if details['foreign_key'] and details['foreign_key'] == ('schema_author', 'id'):
  728. author_is_fk = True
  729. self.assertTrue(author_is_fk, "No FK constraint for author_id found")
  730. def test_alter_implicit_id_to_explicit(self):
  731. """
  732. Should be able to convert an implicit "id" field to an explicit "id"
  733. primary key field.
  734. """
  735. with connection.schema_editor() as editor:
  736. editor.create_model(Author)
  737. old_field = Author._meta.get_field("id")
  738. new_field = IntegerField(primary_key=True)
  739. new_field.set_attributes_from_name("id")
  740. new_field.model = Author
  741. with connection.schema_editor() as editor:
  742. editor.alter_field(Author, old_field, new_field, strict=True)
  743. # This will fail if DROP DEFAULT is inadvertently executed on this
  744. # field which drops the id sequence, at least on PostgreSQL.
  745. Author.objects.create(name='Foo')
  746. def test_alter_int_pk_to_autofield_pk(self):
  747. """
  748. Should be able to rename an IntegerField(primary_key=True) to
  749. AutoField(primary_key=True).
  750. """
  751. with connection.schema_editor() as editor:
  752. editor.create_model(IntegerPK)
  753. old_field = IntegerPK._meta.get_field('i')
  754. new_field = AutoField(primary_key=True)
  755. new_field.model = IntegerPK
  756. new_field.set_attributes_from_name('i')
  757. with connection.schema_editor() as editor:
  758. editor.alter_field(IntegerPK, old_field, new_field, strict=True)
  759. def test_alter_int_pk_to_int_unique(self):
  760. """
  761. Should be able to rename an IntegerField(primary_key=True) to
  762. IntegerField(unique=True).
  763. """
  764. class IntegerUnique(Model):
  765. i = IntegerField(unique=True)
  766. j = IntegerField(primary_key=True)
  767. class Meta:
  768. app_label = 'schema'
  769. apps = new_apps
  770. db_table = 'INTEGERPK'
  771. with connection.schema_editor() as editor:
  772. editor.create_model(IntegerPK)
  773. # model requires a new PK
  774. old_field = IntegerPK._meta.get_field('j')
  775. new_field = IntegerField(primary_key=True)
  776. new_field.model = IntegerPK
  777. new_field.set_attributes_from_name('j')
  778. with connection.schema_editor() as editor:
  779. editor.alter_field(IntegerPK, old_field, new_field, strict=True)
  780. old_field = IntegerPK._meta.get_field('i')
  781. new_field = IntegerField(unique=True)
  782. new_field.model = IntegerPK
  783. new_field.set_attributes_from_name('i')
  784. with connection.schema_editor() as editor:
  785. editor.alter_field(IntegerPK, old_field, new_field, strict=True)
  786. # Ensure unique constraint works.
  787. IntegerUnique.objects.create(i=1, j=1)
  788. with self.assertRaises(IntegrityError):
  789. IntegerUnique.objects.create(i=1, j=2)
  790. def test_rename(self):
  791. """
  792. Tests simple altering of fields
  793. """
  794. # Create the table
  795. with connection.schema_editor() as editor:
  796. editor.create_model(Author)
  797. # Ensure the field is right to begin with
  798. columns = self.column_classes(Author)
  799. self.assertEqual(columns['name'][0], "CharField")
  800. self.assertNotIn("display_name", columns)
  801. # Alter the name field's name
  802. old_field = Author._meta.get_field("name")
  803. new_field = CharField(max_length=254)
  804. new_field.set_attributes_from_name("display_name")
  805. with connection.schema_editor() as editor:
  806. editor.alter_field(Author, old_field, new_field, strict=True)
  807. # Ensure the field is right afterwards
  808. columns = self.column_classes(Author)
  809. self.assertEqual(columns['display_name'][0], "CharField")
  810. self.assertNotIn("name", columns)
  811. @skipIfDBFeature('interprets_empty_strings_as_nulls')
  812. def test_rename_keep_null_status(self):
  813. """
  814. Renaming a field shouldn't affect the not null status.
  815. """
  816. with connection.schema_editor() as editor:
  817. editor.create_model(Note)
  818. with self.assertRaises(IntegrityError):
  819. Note.objects.create(info=None)
  820. old_field = Note._meta.get_field("info")
  821. new_field = TextField()
  822. new_field.set_attributes_from_name("detail_info")
  823. with connection.schema_editor() as editor:
  824. editor.alter_field(Note, old_field, new_field, strict=True)
  825. columns = self.column_classes(Note)
  826. self.assertEqual(columns['detail_info'][0], "TextField")
  827. self.assertNotIn("info", columns)
  828. with self.assertRaises(IntegrityError):
  829. NoteRename.objects.create(detail_info=None)
  830. def _test_m2m_create(self, M2MFieldClass):
  831. """
  832. Tests M2M fields on models during creation
  833. """
  834. class LocalBookWithM2M(Model):
  835. author = ForeignKey(Author)
  836. title = CharField(max_length=100, db_index=True)
  837. pub_date = DateTimeField()
  838. tags = M2MFieldClass("TagM2MTest", related_name="books")
  839. class Meta:
  840. app_label = 'schema'
  841. apps = new_apps
  842. self.local_models = [
  843. LocalBookWithM2M,
  844. LocalBookWithM2M._meta.get_field('tags').remote_field.through,
  845. ]
  846. # Create the tables
  847. with connection.schema_editor() as editor:
  848. editor.create_model(Author)
  849. editor.create_model(TagM2MTest)
  850. editor.create_model(LocalBookWithM2M)
  851. # Ensure there is now an m2m table there
  852. columns = self.column_classes(LocalBookWithM2M._meta.get_field("tags").remote_field.through)
  853. self.assertEqual(columns['tagm2mtest_id'][0], "IntegerField")
  854. def test_m2m_create(self):
  855. self._test_m2m_create(ManyToManyField)
  856. def test_m2m_create_custom(self):
  857. self._test_m2m_create(CustomManyToManyField)
  858. def test_m2m_create_inherited(self):
  859. self._test_m2m_create(InheritedManyToManyField)
  860. def _test_m2m_create_through(self, M2MFieldClass):
  861. """
  862. Tests M2M fields on models during creation with through models
  863. """
  864. class LocalTagThrough(Model):
  865. book = ForeignKey("schema.LocalBookWithM2MThrough")
  866. tag = ForeignKey("schema.TagM2MTest")
  867. class Meta:
  868. app_label = 'schema'
  869. apps = new_apps
  870. class LocalBookWithM2MThrough(Model):
  871. tags = M2MFieldClass("TagM2MTest", related_name="books", through=LocalTagThrough)
  872. class Meta:
  873. app_label = 'schema'
  874. apps = new_apps
  875. self.local_models = [LocalTagThrough, LocalBookWithM2MThrough]
  876. # Create the tables
  877. with connection.schema_editor() as editor:
  878. editor.create_model(LocalTagThrough)
  879. editor.create_model(TagM2MTest)
  880. editor.create_model(LocalBookWithM2MThrough)
  881. # Ensure there is now an m2m table there
  882. columns = self.column_classes(LocalTagThrough)
  883. self.assertEqual(columns['book_id'][0], "IntegerField")
  884. self.assertEqual(columns['tag_id'][0], "IntegerField")
  885. def test_m2m_create_through(self):
  886. self._test_m2m_create_through(ManyToManyField)
  887. def test_m2m_create_through_custom(self):
  888. self._test_m2m_create_through(CustomManyToManyField)
  889. def test_m2m_create_through_inherited(self):
  890. self._test_m2m_create_through(InheritedManyToManyField)
  891. def _test_m2m(self, M2MFieldClass):
  892. """
  893. Tests adding/removing M2M fields on models
  894. """
  895. class LocalAuthorWithM2M(Model):
  896. name = CharField(max_length=255)
  897. class Meta:
  898. app_label = 'schema'
  899. apps = new_apps
  900. self.local_models = [LocalAuthorWithM2M]
  901. # Create the tables
  902. with connection.schema_editor() as editor:
  903. editor.create_model(LocalAuthorWithM2M)
  904. editor.create_model(TagM2MTest)
  905. # Create an M2M field
  906. new_field = M2MFieldClass("schema.TagM2MTest", related_name="authors")
  907. new_field.contribute_to_class(LocalAuthorWithM2M, "tags")
  908. self.local_models += [new_field.remote_field.through]
  909. # Ensure there's no m2m table there
  910. self.assertRaises(DatabaseError, self.column_classes, new_field.remote_field.through)
  911. # Add the field
  912. with connection.schema_editor() as editor:
  913. editor.add_field(LocalAuthorWithM2M, new_field)
  914. # Ensure there is now an m2m table there
  915. columns = self.column_classes(new_field.remote_field.through)
  916. self.assertEqual(columns['tagm2mtest_id'][0], "IntegerField")
  917. # "Alter" the field. This should not rename the DB table to itself.
  918. with connection.schema_editor() as editor:
  919. editor.alter_field(LocalAuthorWithM2M, new_field, new_field)
  920. # Remove the M2M table again
  921. with connection.schema_editor() as editor:
  922. editor.remove_field(LocalAuthorWithM2M, new_field)
  923. # Ensure there's no m2m table there
  924. self.assertRaises(DatabaseError, self.column_classes, new_field.remote_field.through)
  925. def test_m2m(self):
  926. self._test_m2m(ManyToManyField)
  927. def test_m2m_custom(self):
  928. self._test_m2m(CustomManyToManyField)
  929. def test_m2m_inherited(self):
  930. self._test_m2m(InheritedManyToManyField)
  931. def _test_m2m_through_alter(self, M2MFieldClass):
  932. """
  933. Tests altering M2Ms with explicit through models (should no-op)
  934. """
  935. class LocalAuthorTag(Model):
  936. author = ForeignKey("schema.LocalAuthorWithM2MThrough")
  937. tag = ForeignKey("schema.TagM2MTest")
  938. class Meta:
  939. app_label = 'schema'
  940. apps = new_apps
  941. class LocalAuthorWithM2MThrough(Model):
  942. name = CharField(max_length=255)
  943. tags = M2MFieldClass("schema.TagM2MTest", related_name="authors", through=LocalAuthorTag)
  944. class Meta:
  945. app_label = 'schema'
  946. apps = new_apps
  947. self.local_models = [LocalAuthorTag, LocalAuthorWithM2MThrough]
  948. # Create the tables
  949. with connection.schema_editor() as editor:
  950. editor.create_model(LocalAuthorTag)
  951. editor.create_model(LocalAuthorWithM2MThrough)
  952. editor.create_model(TagM2MTest)
  953. # Ensure the m2m table is there
  954. self.assertEqual(len(self.column_classes(LocalAuthorTag)), 3)
  955. # "Alter" the field's blankness. This should not actually do anything.
  956. old_field = LocalAuthorWithM2MThrough._meta.get_field("tags")
  957. new_field = M2MFieldClass("schema.TagM2MTest", related_name="authors", through=LocalAuthorTag)
  958. new_field.contribute_to_class(LocalAuthorWithM2MThrough, "tags")
  959. with connection.schema_editor() as editor:
  960. editor.alter_field(LocalAuthorWithM2MThrough, old_field, new_field)
  961. # Ensure the m2m table is still there
  962. self.assertEqual(len(self.column_classes(LocalAuthorTag)), 3)
  963. def test_m2m_through_alter(self):
  964. self._test_m2m_through_alter(ManyToManyField)
  965. def test_m2m_through_alter_custom(self):
  966. self._test_m2m_through_alter(CustomManyToManyField)
  967. def test_m2m_through_alter_inherited(self):
  968. self._test_m2m_through_alter(InheritedManyToManyField)
  969. def _test_m2m_repoint(self, M2MFieldClass):
  970. """
  971. Tests repointing M2M fields
  972. """
  973. class LocalBookWithM2M(Model):
  974. author = ForeignKey(Author)
  975. title = CharField(max_length=100, db_index=True)
  976. pub_date = DateTimeField()
  977. tags = M2MFieldClass("TagM2MTest", related_name="books")
  978. class Meta:
  979. app_label = 'schema'
  980. apps = new_apps
  981. self.local_models = [
  982. LocalBookWithM2M,
  983. LocalBookWithM2M._meta.get_field('tags').remote_field.through,
  984. ]
  985. # Create the tables
  986. with connection.schema_editor() as editor:
  987. editor.create_model(Author)
  988. editor.create_model(LocalBookWithM2M)
  989. editor.create_model(TagM2MTest)
  990. editor.create_model(UniqueTest)
  991. # Ensure the M2M exists and points to TagM2MTest
  992. constraints = self.get_constraints(LocalBookWithM2M._meta.get_field("tags").remote_field.through._meta.db_table)
  993. if connection.features.supports_foreign_keys:
  994. for name, details in constraints.items():
  995. if details['columns'] == ["tagm2mtest_id"] and details['foreign_key']:
  996. self.assertEqual(details['foreign_key'], ('schema_tagm2mtest', 'id'))
  997. break
  998. else:
  999. self.fail("No FK constraint for tagm2mtest_id found")
  1000. # Repoint the M2M
  1001. old_field = LocalBookWithM2M._meta.get_field("tags")
  1002. new_field = M2MFieldClass(UniqueTest)
  1003. new_field.contribute_to_class(LocalBookWithM2M, "uniques")
  1004. self.local_models += [new_field.remote_field.through]
  1005. with connection.schema_editor() as editor:
  1006. editor.alter_field(LocalBookWithM2M, old_field, new_field)
  1007. # Ensure old M2M is gone
  1008. self.assertRaises(DatabaseError, self.column_classes, LocalBookWithM2M._meta.get_field("tags").remote_field.through)
  1009. # Ensure the new M2M exists and points to UniqueTest
  1010. constraints = self.get_constraints(new_field.remote_field.through._meta.db_table)
  1011. if connection.features.supports_foreign_keys:
  1012. for name, details in constraints.items():
  1013. if details['columns'] == ["uniquetest_id"] and details['foreign_key']:
  1014. self.assertEqual(details['foreign_key'], ('schema_uniquetest', 'id'))
  1015. break
  1016. else:
  1017. self.fail("No FK constraint for uniquetest_id found")
  1018. def test_m2m_repoint(self):
  1019. self._test_m2m_repoint(ManyToManyField)
  1020. def test_m2m_repoint_custom(self):
  1021. self._test_m2m_repoint(CustomManyToManyField)
  1022. def test_m2m_repoint_inherited(self):
  1023. self._test_m2m_repoint(InheritedManyToManyField)
  1024. @unittest.skipUnless(connection.features.supports_column_check_constraints, "No check constraints")
  1025. def test_check_constraints(self):
  1026. """
  1027. Tests creating/deleting CHECK constraints
  1028. """
  1029. # Create the tables
  1030. with connection.schema_editor() as editor:
  1031. editor.create_model(Author)
  1032. # Ensure the constraint exists
  1033. constraints = self.get_constraints(Author._meta.db_table)
  1034. for name, details in constraints.items():
  1035. if details['columns'] == ["height"] and details['check']:
  1036. break
  1037. else:
  1038. self.fail("No check constraint for height found")
  1039. # Alter the column to remove it
  1040. old_field = Author._meta.get_field("height")
  1041. new_field = IntegerField(null=True, blank=True)
  1042. new_field.set_attributes_from_name("height")
  1043. with connection.schema_editor() as editor:
  1044. editor.alter_field(Author, old_field, new_field, strict=True)
  1045. constraints = self.get_constraints(Author._meta.db_table)
  1046. for name, details in constraints.items():
  1047. if details['columns'] == ["height"] and details['check']:
  1048. self.fail("Check constraint for height found")
  1049. # Alter the column to re-add it
  1050. new_field2 = Author._meta.get_field("height")
  1051. with connection.schema_editor() as editor:
  1052. editor.alter_field(Author, new_field, new_field2, strict=True)
  1053. constraints = self.get_constraints(Author._meta.db_table)
  1054. for name, details in constraints.items():
  1055. if details['columns'] == ["height"] and details['check']:
  1056. break
  1057. else:
  1058. self.fail("No check constraint for height found")
  1059. def test_unique(self):
  1060. """
  1061. Tests removing and adding unique constraints to a single column.
  1062. """
  1063. # Create the table
  1064. with connection.schema_editor() as editor:
  1065. editor.create_model(Tag)
  1066. # Ensure the field is unique to begin with
  1067. Tag.objects.create(title="foo", slug="foo")
  1068. self.assertRaises(IntegrityError, Tag.objects.create, title="bar", slug="foo")
  1069. Tag.objects.all().delete()
  1070. # Alter the slug field to be non-unique
  1071. old_field = Tag._meta.get_field("slug")
  1072. new_field = SlugField(unique=False)
  1073. new_field.set_attributes_from_name("slug")
  1074. with connection.schema_editor() as editor:
  1075. editor.alter_field(Tag, old_field, new_field, strict=True)
  1076. # Ensure the field is no longer unique
  1077. Tag.objects.create(title="foo", slug="foo")
  1078. Tag.objects.create(title="bar", slug="foo")
  1079. Tag.objects.all().delete()
  1080. # Alter the slug field to be unique
  1081. new_field2 = SlugField(unique=True)
  1082. new_field2.set_attributes_from_name("slug")
  1083. with connection.schema_editor() as editor:
  1084. editor.alter_field(Tag, new_field, new_field2, strict=True)
  1085. # Ensure the field is unique again
  1086. Tag.objects.create(title="foo", slug="foo")
  1087. self.assertRaises(IntegrityError, Tag.objects.create, title="bar", slug="foo")
  1088. Tag.objects.all().delete()
  1089. # Rename the field
  1090. new_field3 = SlugField(unique=True)
  1091. new_field3.set_attributes_from_name("slug2")
  1092. with connection.schema_editor() as editor:
  1093. editor.alter_field(Tag, new_field2, new_field3, strict=True)
  1094. # Ensure the field is still unique
  1095. TagUniqueRename.objects.create(title="foo", slug2="foo")
  1096. self.assertRaises(IntegrityError, TagUniqueRename.objects.create, title="bar", slug2="foo")
  1097. Tag.objects.all().delete()
  1098. def test_unique_together(self):
  1099. """
  1100. Tests removing and adding unique_together constraints on a model.
  1101. """
  1102. # Create the table
  1103. with connection.schema_editor() as editor:
  1104. editor.create_model(UniqueTest)
  1105. # Ensure the fields are unique to begin with
  1106. UniqueTest.objects.create(year=2012, slug="foo")
  1107. UniqueTest.objects.create(year=2011, slug="foo")
  1108. UniqueTest.objects.create(year=2011, slug="bar")
  1109. self.assertRaises(IntegrityError, UniqueTest.objects.create, year=2012, slug="foo")
  1110. UniqueTest.objects.all().delete()
  1111. # Alter the model to its non-unique-together companion
  1112. with connection.schema_editor() as editor:
  1113. editor.alter_unique_together(UniqueTest, UniqueTest._meta.unique_together, [])
  1114. # Ensure the fields are no longer unique
  1115. UniqueTest.objects.create(year=2012, slug="foo")
  1116. UniqueTest.objects.create(year=2012, slug="foo")
  1117. UniqueTest.objects.all().delete()
  1118. # Alter it back
  1119. new_field2 = SlugField(unique=True)
  1120. new_field2.set_attributes_from_name("slug")
  1121. with connection.schema_editor() as editor:
  1122. editor.alter_unique_together(UniqueTest, [], UniqueTest._meta.unique_together)
  1123. # Ensure the fields are unique again
  1124. UniqueTest.objects.create(year=2012, slug="foo")
  1125. self.assertRaises(IntegrityError, UniqueTest.objects.create, year=2012, slug="foo")
  1126. UniqueTest.objects.all().delete()
  1127. def test_unique_together_with_fk(self):
  1128. """
  1129. Tests removing and adding unique_together constraints that include
  1130. a foreign key.
  1131. """
  1132. # Create the table
  1133. with connection.schema_editor() as editor:
  1134. editor.create_model(Author)
  1135. editor.create_model(Book)
  1136. # Ensure the fields are unique to begin with
  1137. self.assertEqual(Book._meta.unique_together, ())
  1138. # Add the unique_together constraint
  1139. with connection.schema_editor() as editor:
  1140. editor.alter_unique_together(Book, [], [['author', 'title']])
  1141. # Alter it back
  1142. with connection.schema_editor() as editor:
  1143. editor.alter_unique_together(Book, [['author', 'title']], [])
  1144. def test_unique_together_with_fk_with_existing_index(self):
  1145. """
  1146. Tests removing and adding unique_together constraints that include
  1147. a foreign key, where the foreign key is added after the model is
  1148. created.
  1149. """
  1150. # Create the tables
  1151. with connection.schema_editor() as editor:
  1152. editor.create_model(Author)
  1153. editor.create_model(BookWithoutAuthor)
  1154. new_field = ForeignKey(Author)
  1155. new_field.set_attributes_from_name('author')
  1156. editor.add_field(BookWithoutAuthor, new_field)
  1157. # Ensure the fields aren't unique to begin with
  1158. self.assertEqual(Book._meta.unique_together, ())
  1159. # Add the unique_together constraint
  1160. with connection.schema_editor() as editor:
  1161. editor.alter_unique_together(Book, [], [['author', 'title']])
  1162. # Alter it back
  1163. with connection.schema_editor() as editor:
  1164. editor.alter_unique_together(Book, [['author', 'title']], [])
  1165. def test_index_together(self):
  1166. """
  1167. Tests removing and adding index_together constraints on a model.
  1168. """
  1169. # Create the table
  1170. with connection.schema_editor() as editor:
  1171. editor.create_model(Tag)
  1172. # Ensure there's no index on the year/slug columns first
  1173. self.assertEqual(
  1174. False,
  1175. any(
  1176. c["index"]
  1177. for c in self.get_constraints("schema_tag").values()
  1178. if c['columns'] == ["slug", "title"]
  1179. ),
  1180. )
  1181. # Alter the model to add an index
  1182. with connection.schema_editor() as editor:
  1183. editor.alter_index_together(Tag, [], [("slug", "title")])
  1184. # Ensure there is now an index
  1185. self.assertEqual(
  1186. True,
  1187. any(
  1188. c["index"]
  1189. for c in self.get_constraints("schema_tag").values()
  1190. if c['columns'] == ["slug", "title"]
  1191. ),
  1192. )
  1193. # Alter it back
  1194. new_field2 = SlugField(unique=True)
  1195. new_field2.set_attributes_from_name("slug")
  1196. with connection.schema_editor() as editor:
  1197. editor.alter_index_together(Tag, [("slug", "title")], [])
  1198. # Ensure there's no index
  1199. self.assertEqual(
  1200. False,
  1201. any(
  1202. c["index"]
  1203. for c in self.get_constraints("schema_tag").values()
  1204. if c['columns'] == ["slug", "title"]
  1205. ),
  1206. )
  1207. def test_index_together_with_fk(self):
  1208. """
  1209. Tests removing and adding index_together constraints that include
  1210. a foreign key.
  1211. """
  1212. # Create the table
  1213. with connection.schema_editor() as editor:
  1214. editor.create_model(Author)
  1215. editor.create_model(Book)
  1216. # Ensure the fields are unique to begin with
  1217. self.assertEqual(Book._meta.index_together, ())
  1218. # Add the unique_together constraint
  1219. with connection.schema_editor() as editor:
  1220. editor.alter_index_together(Book, [], [['author', 'title']])
  1221. # Alter it back
  1222. with connection.schema_editor() as editor:
  1223. editor.alter_index_together(Book, [['author', 'title']], [])
  1224. def test_create_index_together(self):
  1225. """
  1226. Tests creating models with index_together already defined
  1227. """
  1228. # Create the table
  1229. with connection.schema_editor() as editor:
  1230. editor.create_model(TagIndexed)
  1231. # Ensure there is an index
  1232. self.assertEqual(
  1233. True,
  1234. any(
  1235. c["index"]
  1236. for c in self.get_constraints("schema_tagindexed").values()
  1237. if c['columns'] == ["slug", "title"]
  1238. ),
  1239. )
  1240. def test_db_table(self):
  1241. """
  1242. Tests renaming of the table
  1243. """
  1244. # Create the table
  1245. with connection.schema_editor() as editor:
  1246. editor.create_model(Author)
  1247. # Ensure the table is there to begin with
  1248. columns = self.column_classes(Author)
  1249. self.assertEqual(columns['name'][0], "CharField")
  1250. # Alter the table
  1251. with connection.schema_editor() as editor:
  1252. editor.alter_db_table(Author, "schema_author", "schema_otherauthor")
  1253. # Ensure the table is there afterwards
  1254. Author._meta.db_table = "schema_otherauthor"
  1255. columns = self.column_classes(Author)
  1256. self.assertEqual(columns['name'][0], "CharField")
  1257. # Alter the table again
  1258. with connection.schema_editor() as editor:
  1259. editor.alter_db_table(Author, "schema_otherauthor", "schema_author")
  1260. # Ensure the table is still there
  1261. Author._meta.db_table = "schema_author"
  1262. columns = self.column_classes(Author)
  1263. self.assertEqual(columns['name'][0], "CharField")
  1264. def test_indexes(self):
  1265. """
  1266. Tests creation/altering of indexes
  1267. """
  1268. # Create the table
  1269. with connection.schema_editor() as editor:
  1270. editor.create_model(Author)
  1271. editor.create_model(Book)
  1272. # Ensure the table is there and has the right index
  1273. self.assertIn(
  1274. "title",
  1275. self.get_indexes(Book._meta.db_table),
  1276. )
  1277. # Alter to remove the index
  1278. old_field = Book._meta.get_field("title")
  1279. new_field = CharField(max_length=100, db_index=False)
  1280. new_field.set_attributes_from_name("title")
  1281. with connection.schema_editor() as editor:
  1282. editor.alter_field(Book, old_field, new_field, strict=True)
  1283. # Ensure the table is there and has no index
  1284. self.assertNotIn(
  1285. "title",
  1286. self.get_indexes(Book._meta.db_table),
  1287. )
  1288. # Alter to re-add the index
  1289. new_field2 = Book._meta.get_field("title")
  1290. with connection.schema_editor() as editor:
  1291. editor.alter_field(Book, new_field, new_field2, strict=True)
  1292. # Ensure the table is there and has the index again
  1293. self.assertIn(
  1294. "title",
  1295. self.get_indexes(Book._meta.db_table),
  1296. )
  1297. # Add a unique column, verify that creates an implicit index
  1298. new_field3 = BookWithSlug._meta.get_field("slug")
  1299. with connection.schema_editor() as editor:
  1300. editor.add_field(Book, new_field3)
  1301. self.assertIn(
  1302. "slug",
  1303. self.get_indexes(Book._meta.db_table),
  1304. )
  1305. # Remove the unique, check the index goes with it
  1306. new_field4 = CharField(max_length=20, unique=False)
  1307. new_field4.set_attributes_from_name("slug")
  1308. with connection.schema_editor() as editor:
  1309. editor.alter_field(BookWithSlug, new_field3, new_field4, strict=True)
  1310. self.assertNotIn(
  1311. "slug",
  1312. self.get_indexes(Book._meta.db_table),
  1313. )
  1314. def test_primary_key(self):
  1315. """
  1316. Tests altering of the primary key
  1317. """
  1318. # Create the table
  1319. with connection.schema_editor() as editor:
  1320. editor.create_model(Tag)
  1321. # Ensure the table is there and has the right PK
  1322. self.assertTrue(
  1323. self.get_indexes(Tag._meta.db_table)['id']['primary_key'],
  1324. )
  1325. # Alter to change the PK
  1326. id_field = Tag._meta.get_field("id")
  1327. old_field = Tag._meta.get_field("slug")
  1328. new_field = SlugField(primary_key=True)
  1329. new_field.set_attributes_from_name("slug")
  1330. new_field.model = Tag
  1331. with connection.schema_editor() as editor:
  1332. editor.remove_field(Tag, id_field)
  1333. editor.alter_field(Tag, old_field, new_field)
  1334. # Ensure the PK changed
  1335. self.assertNotIn(
  1336. 'id',
  1337. self.get_indexes(Tag._meta.db_table),
  1338. )
  1339. self.assertTrue(
  1340. self.get_indexes(Tag._meta.db_table)['slug']['primary_key'],
  1341. )
  1342. def test_context_manager_exit(self):
  1343. """
  1344. Ensures transaction is correctly closed when an error occurs
  1345. inside a SchemaEditor context.
  1346. """
  1347. class SomeError(Exception):
  1348. pass
  1349. try:
  1350. with connection.schema_editor():
  1351. raise SomeError
  1352. except SomeError:
  1353. self.assertFalse(connection.in_atomic_block)
  1354. @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support")
  1355. def test_foreign_key_index_long_names_regression(self):
  1356. """
  1357. Regression test for #21497.
  1358. Only affects databases that supports foreign keys.
  1359. """
  1360. # Create the table
  1361. with connection.schema_editor() as editor:
  1362. editor.create_model(AuthorWithEvenLongerName)
  1363. editor.create_model(BookWithLongName)
  1364. # Find the properly shortened column name
  1365. column_name = connection.ops.quote_name("author_foreign_key_with_really_long_field_name_id")
  1366. column_name = column_name[1:-1].lower() # unquote, and, for Oracle, un-upcase
  1367. # Ensure the table is there and has an index on the column
  1368. self.assertIn(
  1369. column_name,
  1370. self.get_indexes(BookWithLongName._meta.db_table),
  1371. )
  1372. @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support")
  1373. def test_add_foreign_key_long_names(self):
  1374. """
  1375. Regression test for #23009.
  1376. Only affects databases that supports foreign keys.
  1377. """
  1378. # Create the initial tables
  1379. with connection.schema_editor() as editor:
  1380. editor.create_model(AuthorWithEvenLongerName)
  1381. editor.create_model(BookWithLongName)
  1382. # Add a second FK, this would fail due to long ref name before the fix
  1383. new_field = ForeignKey(AuthorWithEvenLongerName, related_name="something")
  1384. new_field.set_attributes_from_name("author_other_really_long_named_i_mean_so_long_fk")
  1385. with connection.schema_editor() as editor:
  1386. editor.add_field(BookWithLongName, new_field)
  1387. def test_creation_deletion_reserved_names(self):
  1388. """
  1389. Tries creating a model's table, and then deleting it when it has a
  1390. SQL reserved name.
  1391. """
  1392. # Create the table
  1393. with connection.schema_editor() as editor:
  1394. try:
  1395. editor.create_model(Thing)
  1396. except OperationalError as e:
  1397. self.fail("Errors when applying initial migration for a model "
  1398. "with a table named after a SQL reserved word: %s" % e)
  1399. # Check that it's there
  1400. list(Thing.objects.all())
  1401. # Clean up that table
  1402. with connection.schema_editor() as editor:
  1403. editor.delete_model(Thing)
  1404. # Check that it's gone
  1405. self.assertRaises(
  1406. DatabaseError,
  1407. lambda: list(Thing.objects.all()),
  1408. )
  1409. @unittest.skipUnless(connection.features.supports_foreign_keys, "No FK support")
  1410. def test_remove_constraints_capital_letters(self):
  1411. """
  1412. #23065 - Constraint names must be quoted if they contain capital letters.
  1413. """
  1414. def get_field(*args, **kwargs):
  1415. kwargs['db_column'] = "CamelCase"
  1416. field = kwargs.pop('field_class', IntegerField)(*args, **kwargs)
  1417. field.set_attributes_from_name("CamelCase")
  1418. return field
  1419. model = Author
  1420. field = get_field()
  1421. table = model._meta.db_table
  1422. column = field.column
  1423. with connection.schema_editor() as editor:
  1424. editor.create_model(model)
  1425. editor.add_field(model, field)
  1426. editor.execute(
  1427. editor.sql_create_index % {
  1428. "table": editor.quote_name(table),
  1429. "name": editor.quote_name("CamelCaseIndex"),
  1430. "columns": editor.quote_name(column),
  1431. "extra": "",
  1432. }
  1433. )
  1434. editor.alter_field(model, get_field(db_index=True), field)
  1435. editor.execute(
  1436. editor.sql_create_unique % {
  1437. "table": editor.quote_name(table),
  1438. "name": editor.quote_name("CamelCaseUniqConstraint"),
  1439. "columns": editor.quote_name(field.column),
  1440. }
  1441. )
  1442. editor.alter_field(model, get_field(unique=True), field)
  1443. editor.execute(
  1444. editor.sql_create_fk % {
  1445. "table": editor.quote_name(table),
  1446. "name": editor.quote_name("CamelCaseFKConstraint"),
  1447. "column": editor.quote_name(column),
  1448. "to_table": editor.quote_name(table),
  1449. "to_column": editor.quote_name(model._meta.auto_field.column),
  1450. }
  1451. )
  1452. editor.alter_field(model, get_field(Author, field_class=ForeignKey), field)
  1453. def test_add_field_use_effective_default(self):
  1454. """
  1455. #23987 - effective_default() should be used as the field default when
  1456. adding a new field.
  1457. """
  1458. # Create the table
  1459. with connection.schema_editor() as editor:
  1460. editor.create_model(Author)
  1461. # Ensure there's no surname field
  1462. columns = self.column_classes(Author)
  1463. self.assertNotIn("surname", columns)
  1464. # Create a row
  1465. Author.objects.create(name='Anonymous1')
  1466. # Add new CharField to ensure default will be used from effective_default
  1467. new_field = CharField(max_length=15, blank=True)
  1468. new_field.set_attributes_from_name("surname")
  1469. with connection.schema_editor() as editor:
  1470. editor.add_field(Author, new_field)
  1471. # Ensure field was added with the right default
  1472. with connection.cursor() as cursor:
  1473. cursor.execute("SELECT surname FROM schema_author;")
  1474. item = cursor.fetchall()[0]
  1475. self.assertEqual(item[0], None if connection.features.interprets_empty_strings_as_nulls else '')
  1476. def test_add_field_default_dropped(self):
  1477. # Create the table
  1478. with connection.schema_editor() as editor:
  1479. editor.create_model(Author)
  1480. # Ensure there's no surname field
  1481. columns = self.column_classes(Author)
  1482. self.assertNotIn("surname", columns)
  1483. # Create a row
  1484. Author.objects.create(name='Anonymous1')
  1485. # Add new CharField with a default
  1486. new_field = CharField(max_length=15, blank=True, default='surname default')
  1487. new_field.set_attributes_from_name("surname")
  1488. with connection.schema_editor() as editor:
  1489. editor.add_field(Author, new_field)
  1490. # Ensure field was added with the right default
  1491. with connection.cursor() as cursor:
  1492. cursor.execute("SELECT surname FROM schema_author;")
  1493. item = cursor.fetchall()[0]
  1494. self.assertEqual(item[0], 'surname default')
  1495. # And that the default is no longer set in the database.
  1496. field = next(
  1497. f for f in connection.introspection.get_table_description(cursor, "schema_author")
  1498. if f.name == "surname"
  1499. )
  1500. if connection.features.can_introspect_default:
  1501. self.assertIsNone(field.default)
  1502. def test_alter_field_default_dropped(self):
  1503. # Create the table
  1504. with connection.schema_editor() as editor:
  1505. editor.create_model(Author)
  1506. # Create a row
  1507. Author.objects.create(name='Anonymous1')
  1508. self.assertEqual(Author.objects.get().height, None)
  1509. old_field = Author._meta.get_field('height')
  1510. # The default from the new field is used in updating existing rows.
  1511. new_field = IntegerField(blank=True, default=42)
  1512. new_field.set_attributes_from_name('height')
  1513. with connection.schema_editor() as editor:
  1514. editor.alter_field(Author, old_field, new_field)
  1515. self.assertEqual(Author.objects.get().height, 42)
  1516. # The database default should be removed.
  1517. with connection.cursor() as cursor:
  1518. field = next(
  1519. f for f in connection.introspection.get_table_description(cursor, "schema_author")
  1520. if f.name == "height"
  1521. )
  1522. if connection.features.can_introspect_default:
  1523. self.assertIsNone(field.default)