tests.py 222 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664466546664667466846694670467146724673467446754676467746784679468046814682468346844685468646874688468946904691469246934694469546964697469846994700470147024703470447054706470747084709471047114712471347144715471647174718471947204721472247234724472547264727472847294730473147324733473447354736473747384739474047414742474347444745474647474748474947504751475247534754475547564757475847594760476147624763476447654766476747684769477047714772477347744775477647774778477947804781478247834784478547864787478847894790479147924793479447954796479747984799480048014802480348044805480648074808480948104811481248134814481548164817481848194820482148224823482448254826482748284829483048314832483348344835483648374838483948404841484248434844484548464847484848494850485148524853485448554856485748584859486048614862486348644865486648674868486948704871487248734874487548764877487848794880488148824883488448854886488748884889489048914892489348944895489648974898489949004901490249034904490549064907490849094910491149124913491449154916491749184919492049214922492349244925492649274928492949304931493249334934493549364937493849394940494149424943494449454946494749484949495049514952495349544955495649574958495949604961496249634964496549664967496849694970497149724973497449754976497749784979498049814982498349844985498649874988498949904991499249934994499549964997499849995000500150025003500450055006500750085009501050115012501350145015501650175018501950205021502250235024502550265027502850295030503150325033503450355036503750385039504050415042504350445045504650475048504950505051505250535054505550565057505850595060506150625063506450655066506750685069507050715072507350745075507650775078507950805081508250835084508550865087508850895090509150925093509450955096509750985099510051015102510351045105510651075108510951105111511251135114511551165117511851195120512151225123512451255126512751285129513051315132513351345135513651375138513951405141514251435144514551465147514851495150515151525153515451555156515751585159516051615162516351645165516651675168516951705171517251735174517551765177517851795180518151825183518451855186518751885189519051915192519351945195519651975198519952005201520252035204520552065207520852095210521152125213521452155216521752185219522052215222522352245225522652275228522952305231523252335234523552365237523852395240524152425243524452455246524752485249525052515252525352545255525652575258525952605261526252635264526552665267526852695270527152725273527452755276527752785279528052815282528352845285528652875288528952905291529252935294529552965297529852995300530153025303530453055306530753085309531053115312531353145315531653175318531953205321532253235324532553265327532853295330533153325333533453355336533753385339534053415342534353445345534653475348534953505351535253535354535553565357535853595360536153625363536453655366536753685369537053715372537353745375537653775378537953805381538253835384538553865387538853895390539153925393539453955396539753985399540054015402540354045405540654075408540954105411541254135414541554165417541854195420542154225423542454255426542754285429543054315432543354345435543654375438
  1. import datetime
  2. import itertools
  3. import unittest
  4. from copy import copy
  5. from unittest import mock
  6. from django.core.exceptions import FieldError
  7. from django.core.management.color import no_style
  8. from django.db import (
  9. DatabaseError,
  10. DataError,
  11. IntegrityError,
  12. OperationalError,
  13. connection,
  14. )
  15. from django.db.backends.utils import truncate_name
  16. from django.db.models import (
  17. CASCADE,
  18. PROTECT,
  19. AutoField,
  20. BigAutoField,
  21. BigIntegerField,
  22. BinaryField,
  23. BooleanField,
  24. CharField,
  25. CheckConstraint,
  26. DateField,
  27. DateTimeField,
  28. DecimalField,
  29. DurationField,
  30. F,
  31. FloatField,
  32. ForeignKey,
  33. ForeignObject,
  34. Index,
  35. IntegerField,
  36. JSONField,
  37. ManyToManyField,
  38. Model,
  39. OneToOneField,
  40. OrderBy,
  41. PositiveIntegerField,
  42. Q,
  43. SlugField,
  44. SmallAutoField,
  45. SmallIntegerField,
  46. TextField,
  47. TimeField,
  48. UniqueConstraint,
  49. UUIDField,
  50. Value,
  51. )
  52. from django.db.models.fields.json import KeyTextTransform
  53. from django.db.models.functions import Abs, Cast, Collate, Lower, Random, Upper
  54. from django.db.models.indexes import IndexExpression
  55. from django.db.transaction import TransactionManagementError, atomic
  56. from django.test import (
  57. TransactionTestCase,
  58. ignore_warnings,
  59. skipIfDBFeature,
  60. skipUnlessDBFeature,
  61. )
  62. from django.test.utils import CaptureQueriesContext, isolate_apps, register_lookup
  63. from django.utils.deprecation import RemovedInDjango51Warning
  64. from .fields import CustomManyToManyField, InheritedManyToManyField, MediumBlobField
  65. from .models import (
  66. Author,
  67. AuthorCharFieldWithIndex,
  68. AuthorTextFieldWithIndex,
  69. AuthorWithDefaultHeight,
  70. AuthorWithEvenLongerName,
  71. AuthorWithIndexedName,
  72. AuthorWithUniqueName,
  73. AuthorWithUniqueNameAndBirthday,
  74. Book,
  75. BookForeignObj,
  76. BookWeak,
  77. BookWithLongName,
  78. BookWithO2O,
  79. BookWithoutAuthor,
  80. BookWithSlug,
  81. IntegerPK,
  82. Node,
  83. Note,
  84. NoteRename,
  85. Tag,
  86. TagM2MTest,
  87. TagUniqueRename,
  88. Thing,
  89. UniqueTest,
  90. new_apps,
  91. )
  92. class SchemaTests(TransactionTestCase):
  93. """
  94. Tests for the schema-alteration code.
  95. Be aware that these tests are more liable than most to false results,
  96. as sometimes the code to check if a test has worked is almost as complex
  97. as the code it is testing.
  98. """
  99. available_apps = []
  100. models = [
  101. Author,
  102. AuthorCharFieldWithIndex,
  103. AuthorTextFieldWithIndex,
  104. AuthorWithDefaultHeight,
  105. AuthorWithEvenLongerName,
  106. Book,
  107. BookWeak,
  108. BookWithLongName,
  109. BookWithO2O,
  110. BookWithSlug,
  111. IntegerPK,
  112. Node,
  113. Note,
  114. Tag,
  115. TagM2MTest,
  116. TagUniqueRename,
  117. Thing,
  118. UniqueTest,
  119. ]
  120. # Utility functions
  121. def setUp(self):
  122. # local_models should contain test dependent model classes that will be
  123. # automatically removed from the app cache on test tear down.
  124. self.local_models = []
  125. # isolated_local_models contains models that are in test methods
  126. # decorated with @isolate_apps.
  127. self.isolated_local_models = []
  128. def tearDown(self):
  129. # Delete any tables made for our models
  130. self.delete_tables()
  131. new_apps.clear_cache()
  132. for model in new_apps.get_models():
  133. model._meta._expire_cache()
  134. if "schema" in new_apps.all_models:
  135. for model in self.local_models:
  136. for many_to_many in model._meta.many_to_many:
  137. through = many_to_many.remote_field.through
  138. if through and through._meta.auto_created:
  139. del new_apps.all_models["schema"][through._meta.model_name]
  140. del new_apps.all_models["schema"][model._meta.model_name]
  141. if self.isolated_local_models:
  142. with connection.schema_editor() as editor:
  143. for model in self.isolated_local_models:
  144. editor.delete_model(model)
  145. def delete_tables(self):
  146. "Deletes all model tables for our models for a clean test environment"
  147. converter = connection.introspection.identifier_converter
  148. with connection.schema_editor() as editor:
  149. connection.disable_constraint_checking()
  150. table_names = connection.introspection.table_names()
  151. if connection.features.ignores_table_name_case:
  152. table_names = [table_name.lower() for table_name in table_names]
  153. for model in itertools.chain(SchemaTests.models, self.local_models):
  154. tbl = converter(model._meta.db_table)
  155. if connection.features.ignores_table_name_case:
  156. tbl = tbl.lower()
  157. if tbl in table_names:
  158. editor.delete_model(model)
  159. table_names.remove(tbl)
  160. connection.enable_constraint_checking()
  161. def column_classes(self, model):
  162. with connection.cursor() as cursor:
  163. columns = {
  164. d[0]: (connection.introspection.get_field_type(d[1], d), d)
  165. for d in connection.introspection.get_table_description(
  166. cursor,
  167. model._meta.db_table,
  168. )
  169. }
  170. # SQLite has a different format for field_type
  171. for name, (type, desc) in columns.items():
  172. if isinstance(type, tuple):
  173. columns[name] = (type[0], desc)
  174. return columns
  175. def get_primary_key(self, table):
  176. with connection.cursor() as cursor:
  177. return connection.introspection.get_primary_key_column(cursor, table)
  178. def get_indexes(self, table):
  179. """
  180. Get the indexes on the table using a new cursor.
  181. """
  182. with connection.cursor() as cursor:
  183. return [
  184. c["columns"][0]
  185. for c in connection.introspection.get_constraints(
  186. cursor, table
  187. ).values()
  188. if c["index"] and len(c["columns"]) == 1
  189. ]
  190. def get_uniques(self, table):
  191. with connection.cursor() as cursor:
  192. return [
  193. c["columns"][0]
  194. for c in connection.introspection.get_constraints(
  195. cursor, table
  196. ).values()
  197. if c["unique"] and len(c["columns"]) == 1
  198. ]
  199. def get_constraints(self, table):
  200. """
  201. Get the constraints on a table using a new cursor.
  202. """
  203. with connection.cursor() as cursor:
  204. return connection.introspection.get_constraints(cursor, table)
  205. def get_constraints_for_column(self, model, column_name):
  206. constraints = self.get_constraints(model._meta.db_table)
  207. constraints_for_column = []
  208. for name, details in constraints.items():
  209. if details["columns"] == [column_name]:
  210. constraints_for_column.append(name)
  211. return sorted(constraints_for_column)
  212. def check_added_field_default(
  213. self,
  214. schema_editor,
  215. model,
  216. field,
  217. field_name,
  218. expected_default,
  219. cast_function=None,
  220. ):
  221. with connection.cursor() as cursor:
  222. schema_editor.add_field(model, field)
  223. cursor.execute(
  224. "SELECT {} FROM {};".format(field_name, model._meta.db_table)
  225. )
  226. database_default = cursor.fetchall()[0][0]
  227. if cast_function and type(database_default) is not type(expected_default):
  228. database_default = cast_function(database_default)
  229. self.assertEqual(database_default, expected_default)
  230. def get_constraints_count(self, table, column, fk_to):
  231. """
  232. Return a dict with keys 'fks', 'uniques, and 'indexes' indicating the
  233. number of foreign keys, unique constraints, and indexes on
  234. `table`.`column`. The `fk_to` argument is a 2-tuple specifying the
  235. expected foreign key relationship's (table, column).
  236. """
  237. with connection.cursor() as cursor:
  238. constraints = connection.introspection.get_constraints(cursor, table)
  239. counts = {"fks": 0, "uniques": 0, "indexes": 0}
  240. for c in constraints.values():
  241. if c["columns"] == [column]:
  242. if c["foreign_key"] == fk_to:
  243. counts["fks"] += 1
  244. if c["unique"]:
  245. counts["uniques"] += 1
  246. elif c["index"]:
  247. counts["indexes"] += 1
  248. return counts
  249. def get_column_collation(self, table, column):
  250. with connection.cursor() as cursor:
  251. return next(
  252. f.collation
  253. for f in connection.introspection.get_table_description(cursor, table)
  254. if f.name == column
  255. )
  256. def get_column_comment(self, table, column):
  257. with connection.cursor() as cursor:
  258. return next(
  259. f.comment
  260. for f in connection.introspection.get_table_description(cursor, table)
  261. if f.name == column
  262. )
  263. def get_table_comment(self, table):
  264. with connection.cursor() as cursor:
  265. return next(
  266. t.comment
  267. for t in connection.introspection.get_table_list(cursor)
  268. if t.name == table
  269. )
  270. def assert_column_comment_not_exists(self, table, column):
  271. with connection.cursor() as cursor:
  272. columns = connection.introspection.get_table_description(cursor, table)
  273. self.assertFalse(any([c.name == column and c.comment for c in columns]))
  274. def assertIndexOrder(self, table, index, order):
  275. constraints = self.get_constraints(table)
  276. self.assertIn(index, constraints)
  277. index_orders = constraints[index]["orders"]
  278. self.assertTrue(
  279. all(val == expected for val, expected in zip(index_orders, order))
  280. )
  281. def assertForeignKeyExists(self, model, column, expected_fk_table, field="id"):
  282. """
  283. Fail if the FK constraint on `model.Meta.db_table`.`column` to
  284. `expected_fk_table`.id doesn't exist.
  285. """
  286. if not connection.features.can_introspect_foreign_keys:
  287. return
  288. constraints = self.get_constraints(model._meta.db_table)
  289. constraint_fk = None
  290. for details in constraints.values():
  291. if details["columns"] == [column] and details["foreign_key"]:
  292. constraint_fk = details["foreign_key"]
  293. break
  294. self.assertEqual(constraint_fk, (expected_fk_table, field))
  295. def assertForeignKeyNotExists(self, model, column, expected_fk_table):
  296. if not connection.features.can_introspect_foreign_keys:
  297. return
  298. with self.assertRaises(AssertionError):
  299. self.assertForeignKeyExists(model, column, expected_fk_table)
  300. # Tests
  301. def test_creation_deletion(self):
  302. """
  303. Tries creating a model's table, and then deleting it.
  304. """
  305. with connection.schema_editor() as editor:
  306. # Create the table
  307. editor.create_model(Author)
  308. # The table is there
  309. list(Author.objects.all())
  310. # Clean up that table
  311. editor.delete_model(Author)
  312. # No deferred SQL should be left over.
  313. self.assertEqual(editor.deferred_sql, [])
  314. # The table is gone
  315. with self.assertRaises(DatabaseError):
  316. list(Author.objects.all())
  317. @skipUnlessDBFeature("supports_foreign_keys")
  318. def test_fk(self):
  319. "Creating tables out of FK order, then repointing, works"
  320. # Create the table
  321. with connection.schema_editor() as editor:
  322. editor.create_model(Book)
  323. editor.create_model(Author)
  324. editor.create_model(Tag)
  325. # Initial tables are there
  326. list(Author.objects.all())
  327. list(Book.objects.all())
  328. # Make sure the FK constraint is present
  329. with self.assertRaises(IntegrityError):
  330. Book.objects.create(
  331. author_id=1,
  332. title="Much Ado About Foreign Keys",
  333. pub_date=datetime.datetime.now(),
  334. )
  335. # Repoint the FK constraint
  336. old_field = Book._meta.get_field("author")
  337. new_field = ForeignKey(Tag, CASCADE)
  338. new_field.set_attributes_from_name("author")
  339. with connection.schema_editor() as editor:
  340. editor.alter_field(Book, old_field, new_field, strict=True)
  341. self.assertForeignKeyExists(Book, "author_id", "schema_tag")
  342. @skipUnlessDBFeature("can_create_inline_fk")
  343. def test_inline_fk(self):
  344. # Create some tables.
  345. with connection.schema_editor() as editor:
  346. editor.create_model(Author)
  347. editor.create_model(Book)
  348. editor.create_model(Note)
  349. self.assertForeignKeyNotExists(Note, "book_id", "schema_book")
  350. # Add a foreign key from one to the other.
  351. with connection.schema_editor() as editor:
  352. new_field = ForeignKey(Book, CASCADE)
  353. new_field.set_attributes_from_name("book")
  354. editor.add_field(Note, new_field)
  355. self.assertForeignKeyExists(Note, "book_id", "schema_book")
  356. # Creating a FK field with a constraint uses a single statement without
  357. # a deferred ALTER TABLE.
  358. self.assertFalse(
  359. [
  360. sql
  361. for sql in (str(statement) for statement in editor.deferred_sql)
  362. if sql.startswith("ALTER TABLE") and "ADD CONSTRAINT" in sql
  363. ]
  364. )
  365. @skipUnlessDBFeature("can_create_inline_fk")
  366. def test_add_inline_fk_update_data(self):
  367. with connection.schema_editor() as editor:
  368. editor.create_model(Node)
  369. # Add an inline foreign key and update data in the same transaction.
  370. new_field = ForeignKey(Node, CASCADE, related_name="new_fk", null=True)
  371. new_field.set_attributes_from_name("new_parent_fk")
  372. parent = Node.objects.create()
  373. with connection.schema_editor() as editor:
  374. editor.add_field(Node, new_field)
  375. editor.execute("UPDATE schema_node SET new_parent_fk_id = %s;", [parent.pk])
  376. assertIndex = (
  377. self.assertIn
  378. if connection.features.indexes_foreign_keys
  379. else self.assertNotIn
  380. )
  381. assertIndex("new_parent_fk_id", self.get_indexes(Node._meta.db_table))
  382. @skipUnlessDBFeature(
  383. "can_create_inline_fk",
  384. "allows_multiple_constraints_on_same_fields",
  385. )
  386. @isolate_apps("schema")
  387. def test_add_inline_fk_index_update_data(self):
  388. class Node(Model):
  389. class Meta:
  390. app_label = "schema"
  391. with connection.schema_editor() as editor:
  392. editor.create_model(Node)
  393. # Add an inline foreign key, update data, and an index in the same
  394. # transaction.
  395. new_field = ForeignKey(Node, CASCADE, related_name="new_fk", null=True)
  396. new_field.set_attributes_from_name("new_parent_fk")
  397. parent = Node.objects.create()
  398. with connection.schema_editor() as editor:
  399. editor.add_field(Node, new_field)
  400. Node._meta.add_field(new_field)
  401. editor.execute("UPDATE schema_node SET new_parent_fk_id = %s;", [parent.pk])
  402. editor.add_index(
  403. Node, Index(fields=["new_parent_fk"], name="new_parent_inline_fk_idx")
  404. )
  405. self.assertIn("new_parent_fk_id", self.get_indexes(Node._meta.db_table))
  406. @skipUnlessDBFeature("supports_foreign_keys")
  407. def test_char_field_with_db_index_to_fk(self):
  408. # Create the table
  409. with connection.schema_editor() as editor:
  410. editor.create_model(Author)
  411. editor.create_model(AuthorCharFieldWithIndex)
  412. # Change CharField to FK
  413. old_field = AuthorCharFieldWithIndex._meta.get_field("char_field")
  414. new_field = ForeignKey(Author, CASCADE, blank=True)
  415. new_field.set_attributes_from_name("char_field")
  416. with connection.schema_editor() as editor:
  417. editor.alter_field(
  418. AuthorCharFieldWithIndex, old_field, new_field, strict=True
  419. )
  420. self.assertForeignKeyExists(
  421. AuthorCharFieldWithIndex, "char_field_id", "schema_author"
  422. )
  423. @skipUnlessDBFeature("supports_foreign_keys")
  424. @skipUnlessDBFeature("supports_index_on_text_field")
  425. def test_text_field_with_db_index_to_fk(self):
  426. # Create the table
  427. with connection.schema_editor() as editor:
  428. editor.create_model(Author)
  429. editor.create_model(AuthorTextFieldWithIndex)
  430. # Change TextField to FK
  431. old_field = AuthorTextFieldWithIndex._meta.get_field("text_field")
  432. new_field = ForeignKey(Author, CASCADE, blank=True)
  433. new_field.set_attributes_from_name("text_field")
  434. with connection.schema_editor() as editor:
  435. editor.alter_field(
  436. AuthorTextFieldWithIndex, old_field, new_field, strict=True
  437. )
  438. self.assertForeignKeyExists(
  439. AuthorTextFieldWithIndex, "text_field_id", "schema_author"
  440. )
  441. @isolate_apps("schema")
  442. def test_char_field_pk_to_auto_field(self):
  443. class Foo(Model):
  444. id = CharField(max_length=255, primary_key=True)
  445. class Meta:
  446. app_label = "schema"
  447. with connection.schema_editor() as editor:
  448. editor.create_model(Foo)
  449. self.isolated_local_models = [Foo]
  450. old_field = Foo._meta.get_field("id")
  451. new_field = AutoField(primary_key=True)
  452. new_field.set_attributes_from_name("id")
  453. new_field.model = Foo
  454. with connection.schema_editor() as editor:
  455. editor.alter_field(Foo, old_field, new_field, strict=True)
  456. @skipUnlessDBFeature("supports_foreign_keys")
  457. def test_fk_to_proxy(self):
  458. "Creating a FK to a proxy model creates database constraints."
  459. class AuthorProxy(Author):
  460. class Meta:
  461. app_label = "schema"
  462. apps = new_apps
  463. proxy = True
  464. class AuthorRef(Model):
  465. author = ForeignKey(AuthorProxy, on_delete=CASCADE)
  466. class Meta:
  467. app_label = "schema"
  468. apps = new_apps
  469. self.local_models = [AuthorProxy, AuthorRef]
  470. # Create the table
  471. with connection.schema_editor() as editor:
  472. editor.create_model(Author)
  473. editor.create_model(AuthorRef)
  474. self.assertForeignKeyExists(AuthorRef, "author_id", "schema_author")
  475. @skipUnlessDBFeature("supports_foreign_keys", "can_introspect_foreign_keys")
  476. def test_fk_db_constraint(self):
  477. "The db_constraint parameter is respected"
  478. # Create the table
  479. with connection.schema_editor() as editor:
  480. editor.create_model(Tag)
  481. editor.create_model(Author)
  482. editor.create_model(BookWeak)
  483. # Initial tables are there
  484. list(Author.objects.all())
  485. list(Tag.objects.all())
  486. list(BookWeak.objects.all())
  487. self.assertForeignKeyNotExists(BookWeak, "author_id", "schema_author")
  488. # Make a db_constraint=False FK
  489. new_field = ForeignKey(Tag, CASCADE, db_constraint=False)
  490. new_field.set_attributes_from_name("tag")
  491. with connection.schema_editor() as editor:
  492. editor.add_field(Author, new_field)
  493. self.assertForeignKeyNotExists(Author, "tag_id", "schema_tag")
  494. # Alter to one with a constraint
  495. new_field2 = ForeignKey(Tag, CASCADE)
  496. new_field2.set_attributes_from_name("tag")
  497. with connection.schema_editor() as editor:
  498. editor.alter_field(Author, new_field, new_field2, strict=True)
  499. self.assertForeignKeyExists(Author, "tag_id", "schema_tag")
  500. # Alter to one without a constraint again
  501. new_field2 = ForeignKey(Tag, CASCADE)
  502. new_field2.set_attributes_from_name("tag")
  503. with connection.schema_editor() as editor:
  504. editor.alter_field(Author, new_field2, new_field, strict=True)
  505. self.assertForeignKeyNotExists(Author, "tag_id", "schema_tag")
  506. @isolate_apps("schema")
  507. def test_no_db_constraint_added_during_primary_key_change(self):
  508. """
  509. When a primary key that's pointed to by a ForeignKey with
  510. db_constraint=False is altered, a foreign key constraint isn't added.
  511. """
  512. class Author(Model):
  513. class Meta:
  514. app_label = "schema"
  515. class BookWeak(Model):
  516. author = ForeignKey(Author, CASCADE, db_constraint=False)
  517. class Meta:
  518. app_label = "schema"
  519. with connection.schema_editor() as editor:
  520. editor.create_model(Author)
  521. editor.create_model(BookWeak)
  522. self.assertForeignKeyNotExists(BookWeak, "author_id", "schema_author")
  523. old_field = Author._meta.get_field("id")
  524. new_field = BigAutoField(primary_key=True)
  525. new_field.model = Author
  526. new_field.set_attributes_from_name("id")
  527. # @isolate_apps() and inner models are needed to have the model
  528. # relations populated, otherwise this doesn't act as a regression test.
  529. self.assertEqual(len(new_field.model._meta.related_objects), 1)
  530. with connection.schema_editor() as editor:
  531. editor.alter_field(Author, old_field, new_field, strict=True)
  532. self.assertForeignKeyNotExists(BookWeak, "author_id", "schema_author")
  533. def _test_m2m_db_constraint(self, M2MFieldClass):
  534. class LocalAuthorWithM2M(Model):
  535. name = CharField(max_length=255)
  536. class Meta:
  537. app_label = "schema"
  538. apps = new_apps
  539. self.local_models = [LocalAuthorWithM2M]
  540. # Create the table
  541. with connection.schema_editor() as editor:
  542. editor.create_model(Tag)
  543. editor.create_model(LocalAuthorWithM2M)
  544. # Initial tables are there
  545. list(LocalAuthorWithM2M.objects.all())
  546. list(Tag.objects.all())
  547. # Make a db_constraint=False FK
  548. new_field = M2MFieldClass(Tag, related_name="authors", db_constraint=False)
  549. new_field.contribute_to_class(LocalAuthorWithM2M, "tags")
  550. # Add the field
  551. with connection.schema_editor() as editor:
  552. editor.add_field(LocalAuthorWithM2M, new_field)
  553. self.assertForeignKeyNotExists(
  554. new_field.remote_field.through, "tag_id", "schema_tag"
  555. )
  556. @skipUnlessDBFeature("supports_foreign_keys")
  557. def test_m2m_db_constraint(self):
  558. self._test_m2m_db_constraint(ManyToManyField)
  559. @skipUnlessDBFeature("supports_foreign_keys")
  560. def test_m2m_db_constraint_custom(self):
  561. self._test_m2m_db_constraint(CustomManyToManyField)
  562. @skipUnlessDBFeature("supports_foreign_keys")
  563. def test_m2m_db_constraint_inherited(self):
  564. self._test_m2m_db_constraint(InheritedManyToManyField)
  565. def test_add_field(self):
  566. """
  567. Tests adding fields to models
  568. """
  569. # Create the table
  570. with connection.schema_editor() as editor:
  571. editor.create_model(Author)
  572. # Ensure there's no age field
  573. columns = self.column_classes(Author)
  574. self.assertNotIn("age", columns)
  575. # Add the new field
  576. new_field = IntegerField(null=True)
  577. new_field.set_attributes_from_name("age")
  578. with CaptureQueriesContext(
  579. connection
  580. ) as ctx, connection.schema_editor() as editor:
  581. editor.add_field(Author, new_field)
  582. drop_default_sql = editor.sql_alter_column_no_default % {
  583. "column": editor.quote_name(new_field.name),
  584. }
  585. self.assertFalse(
  586. any(drop_default_sql in query["sql"] for query in ctx.captured_queries)
  587. )
  588. # Table is not rebuilt.
  589. self.assertIs(
  590. any("CREATE TABLE" in query["sql"] for query in ctx.captured_queries), False
  591. )
  592. self.assertIs(
  593. any("DROP TABLE" in query["sql"] for query in ctx.captured_queries), False
  594. )
  595. columns = self.column_classes(Author)
  596. self.assertEqual(
  597. columns["age"][0],
  598. connection.features.introspected_field_types["IntegerField"],
  599. )
  600. self.assertTrue(columns["age"][1][6])
  601. def test_add_field_remove_field(self):
  602. """
  603. Adding a field and removing it removes all deferred sql referring to it.
  604. """
  605. with connection.schema_editor() as editor:
  606. # Create a table with a unique constraint on the slug field.
  607. editor.create_model(Tag)
  608. # Remove the slug column.
  609. editor.remove_field(Tag, Tag._meta.get_field("slug"))
  610. self.assertEqual(editor.deferred_sql, [])
  611. def test_add_field_temp_default(self):
  612. """
  613. Tests adding fields to models with a temporary default
  614. """
  615. # Create the table
  616. with connection.schema_editor() as editor:
  617. editor.create_model(Author)
  618. # Ensure there's no age field
  619. columns = self.column_classes(Author)
  620. self.assertNotIn("age", columns)
  621. # Add some rows of data
  622. Author.objects.create(name="Andrew", height=30)
  623. Author.objects.create(name="Andrea")
  624. # Add a not-null field
  625. new_field = CharField(max_length=30, default="Godwin")
  626. new_field.set_attributes_from_name("surname")
  627. with connection.schema_editor() as editor:
  628. editor.add_field(Author, new_field)
  629. columns = self.column_classes(Author)
  630. self.assertEqual(
  631. columns["surname"][0],
  632. connection.features.introspected_field_types["CharField"],
  633. )
  634. self.assertEqual(
  635. columns["surname"][1][6],
  636. connection.features.interprets_empty_strings_as_nulls,
  637. )
  638. def test_add_field_temp_default_boolean(self):
  639. """
  640. Tests adding fields to models with a temporary default where
  641. the default is False. (#21783)
  642. """
  643. # Create the table
  644. with connection.schema_editor() as editor:
  645. editor.create_model(Author)
  646. # Ensure there's no age field
  647. columns = self.column_classes(Author)
  648. self.assertNotIn("age", columns)
  649. # Add some rows of data
  650. Author.objects.create(name="Andrew", height=30)
  651. Author.objects.create(name="Andrea")
  652. # Add a not-null field
  653. new_field = BooleanField(default=False)
  654. new_field.set_attributes_from_name("awesome")
  655. with connection.schema_editor() as editor:
  656. editor.add_field(Author, new_field)
  657. columns = self.column_classes(Author)
  658. # BooleanField are stored as TINYINT(1) on MySQL.
  659. field_type = columns["awesome"][0]
  660. self.assertEqual(
  661. field_type, connection.features.introspected_field_types["BooleanField"]
  662. )
  663. def test_add_field_default_transform(self):
  664. """
  665. Tests adding fields to models with a default that is not directly
  666. valid in the database (#22581)
  667. """
  668. class TestTransformField(IntegerField):
  669. # Weird field that saves the count of items in its value
  670. def get_default(self):
  671. return self.default
  672. def get_prep_value(self, value):
  673. if value is None:
  674. return 0
  675. return len(value)
  676. # Create the table
  677. with connection.schema_editor() as editor:
  678. editor.create_model(Author)
  679. # Add some rows of data
  680. Author.objects.create(name="Andrew", height=30)
  681. Author.objects.create(name="Andrea")
  682. # Add the field with a default it needs to cast (to string in this case)
  683. new_field = TestTransformField(default={1: 2})
  684. new_field.set_attributes_from_name("thing")
  685. with connection.schema_editor() as editor:
  686. editor.add_field(Author, new_field)
  687. # Ensure the field is there
  688. columns = self.column_classes(Author)
  689. field_type, field_info = columns["thing"]
  690. self.assertEqual(
  691. field_type, connection.features.introspected_field_types["IntegerField"]
  692. )
  693. # Make sure the values were transformed correctly
  694. self.assertEqual(Author.objects.extra(where=["thing = 1"]).count(), 2)
  695. def test_add_field_o2o_nullable(self):
  696. with connection.schema_editor() as editor:
  697. editor.create_model(Author)
  698. editor.create_model(Note)
  699. new_field = OneToOneField(Note, CASCADE, null=True)
  700. new_field.set_attributes_from_name("note")
  701. with connection.schema_editor() as editor:
  702. editor.add_field(Author, new_field)
  703. columns = self.column_classes(Author)
  704. self.assertIn("note_id", columns)
  705. self.assertTrue(columns["note_id"][1][6])
  706. def test_add_field_binary(self):
  707. """
  708. Tests binary fields get a sane default (#22851)
  709. """
  710. # Create the table
  711. with connection.schema_editor() as editor:
  712. editor.create_model(Author)
  713. # Add the new field
  714. new_field = BinaryField(blank=True)
  715. new_field.set_attributes_from_name("bits")
  716. with connection.schema_editor() as editor:
  717. editor.add_field(Author, new_field)
  718. columns = self.column_classes(Author)
  719. # MySQL annoyingly uses the same backend, so it'll come back as one of
  720. # these two types.
  721. self.assertIn(columns["bits"][0], ("BinaryField", "TextField"))
  722. def test_add_field_durationfield_with_default(self):
  723. with connection.schema_editor() as editor:
  724. editor.create_model(Author)
  725. new_field = DurationField(default=datetime.timedelta(minutes=10))
  726. new_field.set_attributes_from_name("duration")
  727. with connection.schema_editor() as editor:
  728. editor.add_field(Author, new_field)
  729. columns = self.column_classes(Author)
  730. self.assertEqual(
  731. columns["duration"][0],
  732. connection.features.introspected_field_types["DurationField"],
  733. )
  734. @unittest.skipUnless(connection.vendor == "mysql", "MySQL specific")
  735. def test_add_binaryfield_mediumblob(self):
  736. """
  737. Test adding a custom-sized binary field on MySQL (#24846).
  738. """
  739. # Create the table
  740. with connection.schema_editor() as editor:
  741. editor.create_model(Author)
  742. # Add the new field with default
  743. new_field = MediumBlobField(blank=True, default=b"123")
  744. new_field.set_attributes_from_name("bits")
  745. with connection.schema_editor() as editor:
  746. editor.add_field(Author, new_field)
  747. columns = self.column_classes(Author)
  748. # Introspection treats BLOBs as TextFields
  749. self.assertEqual(columns["bits"][0], "TextField")
  750. @isolate_apps("schema")
  751. def test_add_auto_field(self):
  752. class AddAutoFieldModel(Model):
  753. name = CharField(max_length=255, primary_key=True)
  754. class Meta:
  755. app_label = "schema"
  756. with connection.schema_editor() as editor:
  757. editor.create_model(AddAutoFieldModel)
  758. self.isolated_local_models = [AddAutoFieldModel]
  759. old_field = AddAutoFieldModel._meta.get_field("name")
  760. new_field = CharField(max_length=255)
  761. new_field.set_attributes_from_name("name")
  762. new_field.model = AddAutoFieldModel
  763. with connection.schema_editor() as editor:
  764. editor.alter_field(AddAutoFieldModel, old_field, new_field)
  765. new_auto_field = AutoField(primary_key=True)
  766. new_auto_field.set_attributes_from_name("id")
  767. new_auto_field.model = AddAutoFieldModel()
  768. with connection.schema_editor() as editor:
  769. editor.add_field(AddAutoFieldModel, new_auto_field)
  770. # Crashes on PostgreSQL when the GENERATED BY suffix is missing.
  771. AddAutoFieldModel.objects.create(name="test")
  772. def test_remove_field(self):
  773. with connection.schema_editor() as editor:
  774. editor.create_model(Author)
  775. with CaptureQueriesContext(connection) as ctx:
  776. editor.remove_field(Author, Author._meta.get_field("name"))
  777. columns = self.column_classes(Author)
  778. self.assertNotIn("name", columns)
  779. if getattr(connection.features, "can_alter_table_drop_column", True):
  780. # Table is not rebuilt.
  781. self.assertIs(
  782. any("CREATE TABLE" in query["sql"] for query in ctx.captured_queries),
  783. False,
  784. )
  785. self.assertIs(
  786. any("DROP TABLE" in query["sql"] for query in ctx.captured_queries),
  787. False,
  788. )
  789. def test_remove_indexed_field(self):
  790. with connection.schema_editor() as editor:
  791. editor.create_model(AuthorCharFieldWithIndex)
  792. with connection.schema_editor() as editor:
  793. editor.remove_field(
  794. AuthorCharFieldWithIndex,
  795. AuthorCharFieldWithIndex._meta.get_field("char_field"),
  796. )
  797. columns = self.column_classes(AuthorCharFieldWithIndex)
  798. self.assertNotIn("char_field", columns)
  799. def test_alter(self):
  800. """
  801. Tests simple altering of fields
  802. """
  803. # Create the table
  804. with connection.schema_editor() as editor:
  805. editor.create_model(Author)
  806. # Ensure the field is right to begin with
  807. columns = self.column_classes(Author)
  808. self.assertEqual(
  809. columns["name"][0],
  810. connection.features.introspected_field_types["CharField"],
  811. )
  812. self.assertEqual(
  813. bool(columns["name"][1][6]),
  814. bool(connection.features.interprets_empty_strings_as_nulls),
  815. )
  816. # Alter the name field to a TextField
  817. old_field = Author._meta.get_field("name")
  818. new_field = TextField(null=True)
  819. new_field.set_attributes_from_name("name")
  820. with connection.schema_editor() as editor:
  821. editor.alter_field(Author, old_field, new_field, strict=True)
  822. columns = self.column_classes(Author)
  823. self.assertEqual(columns["name"][0], "TextField")
  824. self.assertTrue(columns["name"][1][6])
  825. # Change nullability again
  826. new_field2 = TextField(null=False)
  827. new_field2.set_attributes_from_name("name")
  828. with connection.schema_editor() as editor:
  829. editor.alter_field(Author, new_field, new_field2, strict=True)
  830. columns = self.column_classes(Author)
  831. self.assertEqual(columns["name"][0], "TextField")
  832. self.assertEqual(
  833. bool(columns["name"][1][6]),
  834. bool(connection.features.interprets_empty_strings_as_nulls),
  835. )
  836. def test_alter_auto_field_to_integer_field(self):
  837. # Create the table
  838. with connection.schema_editor() as editor:
  839. editor.create_model(Author)
  840. # Change AutoField to IntegerField
  841. old_field = Author._meta.get_field("id")
  842. new_field = IntegerField(primary_key=True)
  843. new_field.set_attributes_from_name("id")
  844. new_field.model = Author
  845. with connection.schema_editor() as editor:
  846. editor.alter_field(Author, old_field, new_field, strict=True)
  847. # Now that ID is an IntegerField, the database raises an error if it
  848. # isn't provided.
  849. if not connection.features.supports_unspecified_pk:
  850. with self.assertRaises(DatabaseError):
  851. Author.objects.create()
  852. def test_alter_auto_field_to_char_field(self):
  853. # Create the table
  854. with connection.schema_editor() as editor:
  855. editor.create_model(Author)
  856. # Change AutoField to CharField
  857. old_field = Author._meta.get_field("id")
  858. new_field = CharField(primary_key=True, max_length=50)
  859. new_field.set_attributes_from_name("id")
  860. new_field.model = Author
  861. with connection.schema_editor() as editor:
  862. editor.alter_field(Author, old_field, new_field, strict=True)
  863. @isolate_apps("schema")
  864. def test_alter_auto_field_quoted_db_column(self):
  865. class Foo(Model):
  866. id = AutoField(primary_key=True, db_column='"quoted_id"')
  867. class Meta:
  868. app_label = "schema"
  869. with connection.schema_editor() as editor:
  870. editor.create_model(Foo)
  871. self.isolated_local_models = [Foo]
  872. old_field = Foo._meta.get_field("id")
  873. new_field = BigAutoField(primary_key=True)
  874. new_field.model = Foo
  875. new_field.db_column = '"quoted_id"'
  876. new_field.set_attributes_from_name("id")
  877. with connection.schema_editor() as editor:
  878. editor.alter_field(Foo, old_field, new_field, strict=True)
  879. Foo.objects.create()
  880. def test_alter_not_unique_field_to_primary_key(self):
  881. # Create the table.
  882. with connection.schema_editor() as editor:
  883. editor.create_model(Author)
  884. # Change UUIDField to primary key.
  885. old_field = Author._meta.get_field("uuid")
  886. new_field = UUIDField(primary_key=True)
  887. new_field.set_attributes_from_name("uuid")
  888. new_field.model = Author
  889. with connection.schema_editor() as editor:
  890. editor.remove_field(Author, Author._meta.get_field("id"))
  891. editor.alter_field(Author, old_field, new_field, strict=True)
  892. # Redundant unique constraint is not added.
  893. count = self.get_constraints_count(
  894. Author._meta.db_table,
  895. Author._meta.get_field("uuid").column,
  896. None,
  897. )
  898. self.assertLessEqual(count["uniques"], 1)
  899. @isolate_apps("schema")
  900. def test_alter_primary_key_quoted_db_table(self):
  901. class Foo(Model):
  902. class Meta:
  903. app_label = "schema"
  904. db_table = '"foo"'
  905. with connection.schema_editor() as editor:
  906. editor.create_model(Foo)
  907. self.isolated_local_models = [Foo]
  908. old_field = Foo._meta.get_field("id")
  909. new_field = BigAutoField(primary_key=True)
  910. new_field.model = Foo
  911. new_field.set_attributes_from_name("id")
  912. with connection.schema_editor() as editor:
  913. editor.alter_field(Foo, old_field, new_field, strict=True)
  914. Foo.objects.create()
  915. def test_alter_text_field(self):
  916. # Regression for "BLOB/TEXT column 'info' can't have a default value")
  917. # on MySQL.
  918. # Create the table
  919. with connection.schema_editor() as editor:
  920. editor.create_model(Note)
  921. old_field = Note._meta.get_field("info")
  922. new_field = TextField(blank=True)
  923. new_field.set_attributes_from_name("info")
  924. with connection.schema_editor() as editor:
  925. editor.alter_field(Note, old_field, new_field, strict=True)
  926. def test_alter_text_field_to_not_null_with_default_value(self):
  927. with connection.schema_editor() as editor:
  928. editor.create_model(Note)
  929. old_field = Note._meta.get_field("address")
  930. new_field = TextField(blank=True, default="", null=False)
  931. new_field.set_attributes_from_name("address")
  932. with connection.schema_editor() as editor:
  933. editor.alter_field(Note, old_field, new_field, strict=True)
  934. @skipUnlessDBFeature("can_defer_constraint_checks", "can_rollback_ddl")
  935. def test_alter_fk_checks_deferred_constraints(self):
  936. """
  937. #25492 - Altering a foreign key's structure and data in the same
  938. transaction.
  939. """
  940. with connection.schema_editor() as editor:
  941. editor.create_model(Node)
  942. old_field = Node._meta.get_field("parent")
  943. new_field = ForeignKey(Node, CASCADE)
  944. new_field.set_attributes_from_name("parent")
  945. parent = Node.objects.create()
  946. with connection.schema_editor() as editor:
  947. # Update the parent FK to create a deferred constraint check.
  948. Node.objects.update(parent=parent)
  949. editor.alter_field(Node, old_field, new_field, strict=True)
  950. @isolate_apps("schema")
  951. def test_alter_null_with_default_value_deferred_constraints(self):
  952. class Publisher(Model):
  953. class Meta:
  954. app_label = "schema"
  955. class Article(Model):
  956. publisher = ForeignKey(Publisher, CASCADE)
  957. title = CharField(max_length=50, null=True)
  958. description = CharField(max_length=100, null=True)
  959. class Meta:
  960. app_label = "schema"
  961. with connection.schema_editor() as editor:
  962. editor.create_model(Publisher)
  963. editor.create_model(Article)
  964. self.isolated_local_models = [Article, Publisher]
  965. publisher = Publisher.objects.create()
  966. Article.objects.create(publisher=publisher)
  967. old_title = Article._meta.get_field("title")
  968. new_title = CharField(max_length=50, null=False, default="")
  969. new_title.set_attributes_from_name("title")
  970. old_description = Article._meta.get_field("description")
  971. new_description = CharField(max_length=100, null=False, default="")
  972. new_description.set_attributes_from_name("description")
  973. with connection.schema_editor() as editor:
  974. editor.alter_field(Article, old_title, new_title, strict=True)
  975. editor.alter_field(Article, old_description, new_description, strict=True)
  976. def test_alter_text_field_to_date_field(self):
  977. """
  978. #25002 - Test conversion of text field to date field.
  979. """
  980. with connection.schema_editor() as editor:
  981. editor.create_model(Note)
  982. Note.objects.create(info="1988-05-05")
  983. old_field = Note._meta.get_field("info")
  984. new_field = DateField(blank=True)
  985. new_field.set_attributes_from_name("info")
  986. with connection.schema_editor() as editor:
  987. editor.alter_field(Note, old_field, new_field, strict=True)
  988. # Make sure the field isn't nullable
  989. columns = self.column_classes(Note)
  990. self.assertFalse(columns["info"][1][6])
  991. def test_alter_text_field_to_datetime_field(self):
  992. """
  993. #25002 - Test conversion of text field to datetime field.
  994. """
  995. with connection.schema_editor() as editor:
  996. editor.create_model(Note)
  997. Note.objects.create(info="1988-05-05 3:16:17.4567")
  998. old_field = Note._meta.get_field("info")
  999. new_field = DateTimeField(blank=True)
  1000. new_field.set_attributes_from_name("info")
  1001. with connection.schema_editor() as editor:
  1002. editor.alter_field(Note, old_field, new_field, strict=True)
  1003. # Make sure the field isn't nullable
  1004. columns = self.column_classes(Note)
  1005. self.assertFalse(columns["info"][1][6])
  1006. def test_alter_text_field_to_time_field(self):
  1007. """
  1008. #25002 - Test conversion of text field to time field.
  1009. """
  1010. with connection.schema_editor() as editor:
  1011. editor.create_model(Note)
  1012. Note.objects.create(info="3:16:17.4567")
  1013. old_field = Note._meta.get_field("info")
  1014. new_field = TimeField(blank=True)
  1015. new_field.set_attributes_from_name("info")
  1016. with connection.schema_editor() as editor:
  1017. editor.alter_field(Note, old_field, new_field, strict=True)
  1018. # Make sure the field isn't nullable
  1019. columns = self.column_classes(Note)
  1020. self.assertFalse(columns["info"][1][6])
  1021. @skipIfDBFeature("interprets_empty_strings_as_nulls")
  1022. def test_alter_textual_field_keep_null_status(self):
  1023. """
  1024. Changing a field type shouldn't affect the not null status.
  1025. """
  1026. with connection.schema_editor() as editor:
  1027. editor.create_model(Note)
  1028. with self.assertRaises(IntegrityError):
  1029. Note.objects.create(info=None)
  1030. old_field = Note._meta.get_field("info")
  1031. new_field = CharField(max_length=50)
  1032. new_field.set_attributes_from_name("info")
  1033. with connection.schema_editor() as editor:
  1034. editor.alter_field(Note, old_field, new_field, strict=True)
  1035. with self.assertRaises(IntegrityError):
  1036. Note.objects.create(info=None)
  1037. @skipUnlessDBFeature("interprets_empty_strings_as_nulls")
  1038. def test_alter_textual_field_not_null_to_null(self):
  1039. """
  1040. Nullability for textual fields is preserved on databases that
  1041. interpret empty strings as NULLs.
  1042. """
  1043. with connection.schema_editor() as editor:
  1044. editor.create_model(Author)
  1045. columns = self.column_classes(Author)
  1046. # Field is nullable.
  1047. self.assertTrue(columns["uuid"][1][6])
  1048. # Change to NOT NULL.
  1049. old_field = Author._meta.get_field("uuid")
  1050. new_field = SlugField(null=False, blank=True)
  1051. new_field.set_attributes_from_name("uuid")
  1052. with connection.schema_editor() as editor:
  1053. editor.alter_field(Author, old_field, new_field, strict=True)
  1054. columns = self.column_classes(Author)
  1055. # Nullability is preserved.
  1056. self.assertTrue(columns["uuid"][1][6])
  1057. def test_alter_numeric_field_keep_null_status(self):
  1058. """
  1059. Changing a field type shouldn't affect the not null status.
  1060. """
  1061. with connection.schema_editor() as editor:
  1062. editor.create_model(UniqueTest)
  1063. with self.assertRaises(IntegrityError):
  1064. UniqueTest.objects.create(year=None, slug="aaa")
  1065. old_field = UniqueTest._meta.get_field("year")
  1066. new_field = BigIntegerField()
  1067. new_field.set_attributes_from_name("year")
  1068. with connection.schema_editor() as editor:
  1069. editor.alter_field(UniqueTest, old_field, new_field, strict=True)
  1070. with self.assertRaises(IntegrityError):
  1071. UniqueTest.objects.create(year=None, slug="bbb")
  1072. def test_alter_null_to_not_null(self):
  1073. """
  1074. #23609 - Tests handling of default values when altering from NULL to NOT NULL.
  1075. """
  1076. # Create the table
  1077. with connection.schema_editor() as editor:
  1078. editor.create_model(Author)
  1079. # Ensure the field is right to begin with
  1080. columns = self.column_classes(Author)
  1081. self.assertTrue(columns["height"][1][6])
  1082. # Create some test data
  1083. Author.objects.create(name="Not null author", height=12)
  1084. Author.objects.create(name="Null author")
  1085. # Verify null value
  1086. self.assertEqual(Author.objects.get(name="Not null author").height, 12)
  1087. self.assertIsNone(Author.objects.get(name="Null author").height)
  1088. # Alter the height field to NOT NULL with default
  1089. old_field = Author._meta.get_field("height")
  1090. new_field = PositiveIntegerField(default=42)
  1091. new_field.set_attributes_from_name("height")
  1092. with connection.schema_editor() as editor:
  1093. editor.alter_field(Author, old_field, new_field, strict=True)
  1094. columns = self.column_classes(Author)
  1095. self.assertFalse(columns["height"][1][6])
  1096. # Verify default value
  1097. self.assertEqual(Author.objects.get(name="Not null author").height, 12)
  1098. self.assertEqual(Author.objects.get(name="Null author").height, 42)
  1099. def test_alter_charfield_to_null(self):
  1100. """
  1101. #24307 - Should skip an alter statement on databases with
  1102. interprets_empty_strings_as_nulls when changing a CharField to null.
  1103. """
  1104. # Create the table
  1105. with connection.schema_editor() as editor:
  1106. editor.create_model(Author)
  1107. # Change the CharField to null
  1108. old_field = Author._meta.get_field("name")
  1109. new_field = copy(old_field)
  1110. new_field.null = True
  1111. with connection.schema_editor() as editor:
  1112. editor.alter_field(Author, old_field, new_field, strict=True)
  1113. @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific")
  1114. def test_alter_char_field_decrease_length(self):
  1115. # Create the table.
  1116. with connection.schema_editor() as editor:
  1117. editor.create_model(Author)
  1118. Author.objects.create(name="x" * 255)
  1119. # Change max_length of CharField.
  1120. old_field = Author._meta.get_field("name")
  1121. new_field = CharField(max_length=254)
  1122. new_field.set_attributes_from_name("name")
  1123. with connection.schema_editor() as editor:
  1124. msg = "value too long for type character varying(254)"
  1125. with self.assertRaisesMessage(DataError, msg):
  1126. editor.alter_field(Author, old_field, new_field, strict=True)
  1127. @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific")
  1128. def test_alter_field_with_custom_db_type(self):
  1129. from django.contrib.postgres.fields import ArrayField
  1130. class Foo(Model):
  1131. field = ArrayField(CharField(max_length=255))
  1132. class Meta:
  1133. app_label = "schema"
  1134. with connection.schema_editor() as editor:
  1135. editor.create_model(Foo)
  1136. self.isolated_local_models = [Foo]
  1137. old_field = Foo._meta.get_field("field")
  1138. new_field = ArrayField(CharField(max_length=16))
  1139. new_field.set_attributes_from_name("field")
  1140. new_field.model = Foo
  1141. with connection.schema_editor() as editor:
  1142. editor.alter_field(Foo, old_field, new_field, strict=True)
  1143. @isolate_apps("schema")
  1144. @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific")
  1145. def test_alter_array_field_decrease_base_field_length(self):
  1146. from django.contrib.postgres.fields import ArrayField
  1147. class ArrayModel(Model):
  1148. field = ArrayField(CharField(max_length=16))
  1149. class Meta:
  1150. app_label = "schema"
  1151. with connection.schema_editor() as editor:
  1152. editor.create_model(ArrayModel)
  1153. self.isolated_local_models = [ArrayModel]
  1154. ArrayModel.objects.create(field=["x" * 16])
  1155. old_field = ArrayModel._meta.get_field("field")
  1156. new_field = ArrayField(CharField(max_length=15))
  1157. new_field.set_attributes_from_name("field")
  1158. new_field.model = ArrayModel
  1159. with connection.schema_editor() as editor:
  1160. msg = "value too long for type character varying(15)"
  1161. with self.assertRaisesMessage(DataError, msg):
  1162. editor.alter_field(ArrayModel, old_field, new_field, strict=True)
  1163. @isolate_apps("schema")
  1164. @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific")
  1165. def test_alter_array_field_decrease_nested_base_field_length(self):
  1166. from django.contrib.postgres.fields import ArrayField
  1167. class ArrayModel(Model):
  1168. field = ArrayField(ArrayField(CharField(max_length=16)))
  1169. class Meta:
  1170. app_label = "schema"
  1171. with connection.schema_editor() as editor:
  1172. editor.create_model(ArrayModel)
  1173. self.isolated_local_models = [ArrayModel]
  1174. ArrayModel.objects.create(field=[["x" * 16]])
  1175. old_field = ArrayModel._meta.get_field("field")
  1176. new_field = ArrayField(ArrayField(CharField(max_length=15)))
  1177. new_field.set_attributes_from_name("field")
  1178. new_field.model = ArrayModel
  1179. with connection.schema_editor() as editor:
  1180. msg = "value too long for type character varying(15)"
  1181. with self.assertRaisesMessage(DataError, msg):
  1182. editor.alter_field(ArrayModel, old_field, new_field, strict=True)
  1183. def _add_ci_collation(self):
  1184. ci_collation = "case_insensitive"
  1185. def drop_collation():
  1186. with connection.cursor() as cursor:
  1187. cursor.execute(f"DROP COLLATION IF EXISTS {ci_collation}")
  1188. with connection.cursor() as cursor:
  1189. cursor.execute(
  1190. f"CREATE COLLATION IF NOT EXISTS {ci_collation} (provider=icu, "
  1191. f"locale='und-u-ks-level2', deterministic=false)"
  1192. )
  1193. self.addCleanup(drop_collation)
  1194. return ci_collation
  1195. @isolate_apps("schema")
  1196. @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific")
  1197. @skipUnlessDBFeature(
  1198. "supports_collation_on_charfield",
  1199. "supports_non_deterministic_collations",
  1200. )
  1201. def test_db_collation_arrayfield(self):
  1202. from django.contrib.postgres.fields import ArrayField
  1203. ci_collation = self._add_ci_collation()
  1204. cs_collation = "en-x-icu"
  1205. class ArrayModel(Model):
  1206. field = ArrayField(CharField(max_length=16, db_collation=ci_collation))
  1207. class Meta:
  1208. app_label = "schema"
  1209. # Create the table.
  1210. with connection.schema_editor() as editor:
  1211. editor.create_model(ArrayModel)
  1212. self.isolated_local_models = [ArrayModel]
  1213. self.assertEqual(
  1214. self.get_column_collation(ArrayModel._meta.db_table, "field"),
  1215. ci_collation,
  1216. )
  1217. # Alter collation.
  1218. old_field = ArrayModel._meta.get_field("field")
  1219. new_field_cs = ArrayField(CharField(max_length=16, db_collation=cs_collation))
  1220. new_field_cs.set_attributes_from_name("field")
  1221. new_field_cs.model = ArrayField
  1222. with connection.schema_editor() as editor:
  1223. editor.alter_field(ArrayModel, old_field, new_field_cs, strict=True)
  1224. self.assertEqual(
  1225. self.get_column_collation(ArrayModel._meta.db_table, "field"),
  1226. cs_collation,
  1227. )
  1228. @isolate_apps("schema")
  1229. @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific")
  1230. @skipUnlessDBFeature(
  1231. "supports_collation_on_charfield",
  1232. "supports_non_deterministic_collations",
  1233. )
  1234. def test_unique_with_collation_charfield(self):
  1235. ci_collation = self._add_ci_collation()
  1236. class CiCharModel(Model):
  1237. field = CharField(max_length=16, db_collation=ci_collation, unique=True)
  1238. class Meta:
  1239. app_label = "schema"
  1240. # Create the table.
  1241. with connection.schema_editor() as editor:
  1242. editor.create_model(CiCharModel)
  1243. self.isolated_local_models = [CiCharModel]
  1244. self.assertEqual(
  1245. self.get_column_collation(CiCharModel._meta.db_table, "field"),
  1246. ci_collation,
  1247. )
  1248. self.assertIn("field", self.get_uniques(CiCharModel._meta.db_table))
  1249. @isolate_apps("schema")
  1250. @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific")
  1251. @skipUnlessDBFeature(
  1252. "supports_collation_on_charfield",
  1253. "supports_non_deterministic_collations",
  1254. )
  1255. def test_relation_to_collation_charfield(self):
  1256. ci_collation = self._add_ci_collation()
  1257. class CiCharModel(Model):
  1258. field = CharField(max_length=16, db_collation=ci_collation, unique=True)
  1259. class Meta:
  1260. app_label = "schema"
  1261. class RelationModel(Model):
  1262. field = OneToOneField(CiCharModel, CASCADE, to_field="field")
  1263. class Meta:
  1264. app_label = "schema"
  1265. # Create the table.
  1266. with connection.schema_editor() as editor:
  1267. editor.create_model(CiCharModel)
  1268. editor.create_model(RelationModel)
  1269. self.isolated_local_models = [CiCharModel, RelationModel]
  1270. self.assertEqual(
  1271. self.get_column_collation(RelationModel._meta.db_table, "field_id"),
  1272. ci_collation,
  1273. )
  1274. self.assertEqual(
  1275. self.get_column_collation(CiCharModel._meta.db_table, "field"),
  1276. ci_collation,
  1277. )
  1278. self.assertIn("field_id", self.get_uniques(RelationModel._meta.db_table))
  1279. def test_alter_textfield_to_null(self):
  1280. """
  1281. #24307 - Should skip an alter statement on databases with
  1282. interprets_empty_strings_as_nulls when changing a TextField to null.
  1283. """
  1284. # Create the table
  1285. with connection.schema_editor() as editor:
  1286. editor.create_model(Note)
  1287. # Change the TextField to null
  1288. old_field = Note._meta.get_field("info")
  1289. new_field = copy(old_field)
  1290. new_field.null = True
  1291. with connection.schema_editor() as editor:
  1292. editor.alter_field(Note, old_field, new_field, strict=True)
  1293. def test_alter_null_to_not_null_keeping_default(self):
  1294. """
  1295. #23738 - Can change a nullable field with default to non-nullable
  1296. with the same default.
  1297. """
  1298. # Create the table
  1299. with connection.schema_editor() as editor:
  1300. editor.create_model(AuthorWithDefaultHeight)
  1301. # Ensure the field is right to begin with
  1302. columns = self.column_classes(AuthorWithDefaultHeight)
  1303. self.assertTrue(columns["height"][1][6])
  1304. # Alter the height field to NOT NULL keeping the previous default
  1305. old_field = AuthorWithDefaultHeight._meta.get_field("height")
  1306. new_field = PositiveIntegerField(default=42)
  1307. new_field.set_attributes_from_name("height")
  1308. with connection.schema_editor() as editor:
  1309. editor.alter_field(
  1310. AuthorWithDefaultHeight, old_field, new_field, strict=True
  1311. )
  1312. columns = self.column_classes(AuthorWithDefaultHeight)
  1313. self.assertFalse(columns["height"][1][6])
  1314. @skipUnlessDBFeature("supports_foreign_keys")
  1315. def test_alter_fk(self):
  1316. """
  1317. Tests altering of FKs
  1318. """
  1319. # Create the table
  1320. with connection.schema_editor() as editor:
  1321. editor.create_model(Author)
  1322. editor.create_model(Book)
  1323. # Ensure the field is right to begin with
  1324. columns = self.column_classes(Book)
  1325. self.assertEqual(
  1326. columns["author_id"][0],
  1327. connection.features.introspected_field_types["IntegerField"],
  1328. )
  1329. self.assertForeignKeyExists(Book, "author_id", "schema_author")
  1330. # Alter the FK
  1331. old_field = Book._meta.get_field("author")
  1332. new_field = ForeignKey(Author, CASCADE, editable=False)
  1333. new_field.set_attributes_from_name("author")
  1334. with connection.schema_editor() as editor:
  1335. editor.alter_field(Book, old_field, new_field, strict=True)
  1336. columns = self.column_classes(Book)
  1337. self.assertEqual(
  1338. columns["author_id"][0],
  1339. connection.features.introspected_field_types["IntegerField"],
  1340. )
  1341. self.assertForeignKeyExists(Book, "author_id", "schema_author")
  1342. @skipUnlessDBFeature("supports_foreign_keys")
  1343. def test_alter_to_fk(self):
  1344. """
  1345. #24447 - Tests adding a FK constraint for an existing column
  1346. """
  1347. class LocalBook(Model):
  1348. author = IntegerField()
  1349. title = CharField(max_length=100, db_index=True)
  1350. pub_date = DateTimeField()
  1351. class Meta:
  1352. app_label = "schema"
  1353. apps = new_apps
  1354. self.local_models = [LocalBook]
  1355. # Create the tables
  1356. with connection.schema_editor() as editor:
  1357. editor.create_model(Author)
  1358. editor.create_model(LocalBook)
  1359. # Ensure no FK constraint exists
  1360. constraints = self.get_constraints(LocalBook._meta.db_table)
  1361. for details in constraints.values():
  1362. if details["foreign_key"]:
  1363. self.fail(
  1364. "Found an unexpected FK constraint to %s" % details["columns"]
  1365. )
  1366. old_field = LocalBook._meta.get_field("author")
  1367. new_field = ForeignKey(Author, CASCADE)
  1368. new_field.set_attributes_from_name("author")
  1369. with connection.schema_editor() as editor:
  1370. editor.alter_field(LocalBook, old_field, new_field, strict=True)
  1371. self.assertForeignKeyExists(LocalBook, "author_id", "schema_author")
  1372. @skipUnlessDBFeature("supports_foreign_keys", "can_introspect_foreign_keys")
  1373. def test_alter_o2o_to_fk(self):
  1374. """
  1375. #24163 - Tests altering of OneToOneField to ForeignKey
  1376. """
  1377. # Create the table
  1378. with connection.schema_editor() as editor:
  1379. editor.create_model(Author)
  1380. editor.create_model(BookWithO2O)
  1381. # Ensure the field is right to begin with
  1382. columns = self.column_classes(BookWithO2O)
  1383. self.assertEqual(
  1384. columns["author_id"][0],
  1385. connection.features.introspected_field_types["IntegerField"],
  1386. )
  1387. # Ensure the field is unique
  1388. author = Author.objects.create(name="Joe")
  1389. BookWithO2O.objects.create(
  1390. author=author, title="Django 1", pub_date=datetime.datetime.now()
  1391. )
  1392. with self.assertRaises(IntegrityError):
  1393. BookWithO2O.objects.create(
  1394. author=author, title="Django 2", pub_date=datetime.datetime.now()
  1395. )
  1396. BookWithO2O.objects.all().delete()
  1397. self.assertForeignKeyExists(BookWithO2O, "author_id", "schema_author")
  1398. # Alter the OneToOneField to ForeignKey
  1399. old_field = BookWithO2O._meta.get_field("author")
  1400. new_field = ForeignKey(Author, CASCADE)
  1401. new_field.set_attributes_from_name("author")
  1402. with connection.schema_editor() as editor:
  1403. editor.alter_field(BookWithO2O, old_field, new_field, strict=True)
  1404. columns = self.column_classes(Book)
  1405. self.assertEqual(
  1406. columns["author_id"][0],
  1407. connection.features.introspected_field_types["IntegerField"],
  1408. )
  1409. # Ensure the field is not unique anymore
  1410. Book.objects.create(
  1411. author=author, title="Django 1", pub_date=datetime.datetime.now()
  1412. )
  1413. Book.objects.create(
  1414. author=author, title="Django 2", pub_date=datetime.datetime.now()
  1415. )
  1416. self.assertForeignKeyExists(Book, "author_id", "schema_author")
  1417. @skipUnlessDBFeature("supports_foreign_keys", "can_introspect_foreign_keys")
  1418. def test_alter_fk_to_o2o(self):
  1419. """
  1420. #24163 - Tests altering of ForeignKey to OneToOneField
  1421. """
  1422. # Create the table
  1423. with connection.schema_editor() as editor:
  1424. editor.create_model(Author)
  1425. editor.create_model(Book)
  1426. # Ensure the field is right to begin with
  1427. columns = self.column_classes(Book)
  1428. self.assertEqual(
  1429. columns["author_id"][0],
  1430. connection.features.introspected_field_types["IntegerField"],
  1431. )
  1432. # Ensure the field is not unique
  1433. author = Author.objects.create(name="Joe")
  1434. Book.objects.create(
  1435. author=author, title="Django 1", pub_date=datetime.datetime.now()
  1436. )
  1437. Book.objects.create(
  1438. author=author, title="Django 2", pub_date=datetime.datetime.now()
  1439. )
  1440. Book.objects.all().delete()
  1441. self.assertForeignKeyExists(Book, "author_id", "schema_author")
  1442. # Alter the ForeignKey to OneToOneField
  1443. old_field = Book._meta.get_field("author")
  1444. new_field = OneToOneField(Author, CASCADE)
  1445. new_field.set_attributes_from_name("author")
  1446. with connection.schema_editor() as editor:
  1447. editor.alter_field(Book, old_field, new_field, strict=True)
  1448. columns = self.column_classes(BookWithO2O)
  1449. self.assertEqual(
  1450. columns["author_id"][0],
  1451. connection.features.introspected_field_types["IntegerField"],
  1452. )
  1453. # Ensure the field is unique now
  1454. BookWithO2O.objects.create(
  1455. author=author, title="Django 1", pub_date=datetime.datetime.now()
  1456. )
  1457. with self.assertRaises(IntegrityError):
  1458. BookWithO2O.objects.create(
  1459. author=author, title="Django 2", pub_date=datetime.datetime.now()
  1460. )
  1461. self.assertForeignKeyExists(BookWithO2O, "author_id", "schema_author")
  1462. def test_alter_field_fk_to_o2o(self):
  1463. with connection.schema_editor() as editor:
  1464. editor.create_model(Author)
  1465. editor.create_model(Book)
  1466. expected_fks = (
  1467. 1
  1468. if connection.features.supports_foreign_keys
  1469. and connection.features.can_introspect_foreign_keys
  1470. else 0
  1471. )
  1472. expected_indexes = 1 if connection.features.indexes_foreign_keys else 0
  1473. # Check the index is right to begin with.
  1474. counts = self.get_constraints_count(
  1475. Book._meta.db_table,
  1476. Book._meta.get_field("author").column,
  1477. (Author._meta.db_table, Author._meta.pk.column),
  1478. )
  1479. self.assertEqual(
  1480. counts,
  1481. {"fks": expected_fks, "uniques": 0, "indexes": expected_indexes},
  1482. )
  1483. old_field = Book._meta.get_field("author")
  1484. new_field = OneToOneField(Author, CASCADE)
  1485. new_field.set_attributes_from_name("author")
  1486. with connection.schema_editor() as editor:
  1487. editor.alter_field(Book, old_field, new_field)
  1488. counts = self.get_constraints_count(
  1489. Book._meta.db_table,
  1490. Book._meta.get_field("author").column,
  1491. (Author._meta.db_table, Author._meta.pk.column),
  1492. )
  1493. # The index on ForeignKey is replaced with a unique constraint for
  1494. # OneToOneField.
  1495. self.assertEqual(counts, {"fks": expected_fks, "uniques": 1, "indexes": 0})
  1496. def test_autofield_to_o2o(self):
  1497. with connection.schema_editor() as editor:
  1498. editor.create_model(Author)
  1499. editor.create_model(Note)
  1500. # Rename the field.
  1501. old_field = Author._meta.get_field("id")
  1502. new_field = AutoField(primary_key=True)
  1503. new_field.set_attributes_from_name("note_ptr")
  1504. new_field.model = Author
  1505. with connection.schema_editor() as editor:
  1506. editor.alter_field(Author, old_field, new_field, strict=True)
  1507. # Alter AutoField to OneToOneField.
  1508. new_field_o2o = OneToOneField(Note, CASCADE)
  1509. new_field_o2o.set_attributes_from_name("note_ptr")
  1510. new_field_o2o.model = Author
  1511. with connection.schema_editor() as editor:
  1512. editor.alter_field(Author, new_field, new_field_o2o, strict=True)
  1513. columns = self.column_classes(Author)
  1514. field_type, _ = columns["note_ptr_id"]
  1515. self.assertEqual(
  1516. field_type, connection.features.introspected_field_types["IntegerField"]
  1517. )
  1518. def test_alter_field_fk_keeps_index(self):
  1519. with connection.schema_editor() as editor:
  1520. editor.create_model(Author)
  1521. editor.create_model(Book)
  1522. expected_fks = (
  1523. 1
  1524. if connection.features.supports_foreign_keys
  1525. and connection.features.can_introspect_foreign_keys
  1526. else 0
  1527. )
  1528. expected_indexes = 1 if connection.features.indexes_foreign_keys else 0
  1529. # Check the index is right to begin with.
  1530. counts = self.get_constraints_count(
  1531. Book._meta.db_table,
  1532. Book._meta.get_field("author").column,
  1533. (Author._meta.db_table, Author._meta.pk.column),
  1534. )
  1535. self.assertEqual(
  1536. counts,
  1537. {"fks": expected_fks, "uniques": 0, "indexes": expected_indexes},
  1538. )
  1539. old_field = Book._meta.get_field("author")
  1540. # on_delete changed from CASCADE.
  1541. new_field = ForeignKey(Author, PROTECT)
  1542. new_field.set_attributes_from_name("author")
  1543. with connection.schema_editor() as editor:
  1544. editor.alter_field(Book, old_field, new_field, strict=True)
  1545. counts = self.get_constraints_count(
  1546. Book._meta.db_table,
  1547. Book._meta.get_field("author").column,
  1548. (Author._meta.db_table, Author._meta.pk.column),
  1549. )
  1550. # The index remains.
  1551. self.assertEqual(
  1552. counts,
  1553. {"fks": expected_fks, "uniques": 0, "indexes": expected_indexes},
  1554. )
  1555. def test_alter_field_o2o_to_fk(self):
  1556. with connection.schema_editor() as editor:
  1557. editor.create_model(Author)
  1558. editor.create_model(BookWithO2O)
  1559. expected_fks = (
  1560. 1
  1561. if connection.features.supports_foreign_keys
  1562. and connection.features.can_introspect_foreign_keys
  1563. else 0
  1564. )
  1565. # Check the unique constraint is right to begin with.
  1566. counts = self.get_constraints_count(
  1567. BookWithO2O._meta.db_table,
  1568. BookWithO2O._meta.get_field("author").column,
  1569. (Author._meta.db_table, Author._meta.pk.column),
  1570. )
  1571. self.assertEqual(counts, {"fks": expected_fks, "uniques": 1, "indexes": 0})
  1572. old_field = BookWithO2O._meta.get_field("author")
  1573. new_field = ForeignKey(Author, CASCADE)
  1574. new_field.set_attributes_from_name("author")
  1575. with connection.schema_editor() as editor:
  1576. editor.alter_field(BookWithO2O, old_field, new_field)
  1577. counts = self.get_constraints_count(
  1578. BookWithO2O._meta.db_table,
  1579. BookWithO2O._meta.get_field("author").column,
  1580. (Author._meta.db_table, Author._meta.pk.column),
  1581. )
  1582. # The unique constraint on OneToOneField is replaced with an index for
  1583. # ForeignKey.
  1584. self.assertEqual(counts, {"fks": expected_fks, "uniques": 0, "indexes": 1})
  1585. def test_alter_field_o2o_keeps_unique(self):
  1586. with connection.schema_editor() as editor:
  1587. editor.create_model(Author)
  1588. editor.create_model(BookWithO2O)
  1589. expected_fks = (
  1590. 1
  1591. if connection.features.supports_foreign_keys
  1592. and connection.features.can_introspect_foreign_keys
  1593. else 0
  1594. )
  1595. # Check the unique constraint is right to begin with.
  1596. counts = self.get_constraints_count(
  1597. BookWithO2O._meta.db_table,
  1598. BookWithO2O._meta.get_field("author").column,
  1599. (Author._meta.db_table, Author._meta.pk.column),
  1600. )
  1601. self.assertEqual(counts, {"fks": expected_fks, "uniques": 1, "indexes": 0})
  1602. old_field = BookWithO2O._meta.get_field("author")
  1603. # on_delete changed from CASCADE.
  1604. new_field = OneToOneField(Author, PROTECT)
  1605. new_field.set_attributes_from_name("author")
  1606. with connection.schema_editor() as editor:
  1607. editor.alter_field(BookWithO2O, old_field, new_field, strict=True)
  1608. counts = self.get_constraints_count(
  1609. BookWithO2O._meta.db_table,
  1610. BookWithO2O._meta.get_field("author").column,
  1611. (Author._meta.db_table, Author._meta.pk.column),
  1612. )
  1613. # The unique constraint remains.
  1614. self.assertEqual(counts, {"fks": expected_fks, "uniques": 1, "indexes": 0})
  1615. @skipUnlessDBFeature("ignores_table_name_case")
  1616. def test_alter_db_table_case(self):
  1617. # Create the table
  1618. with connection.schema_editor() as editor:
  1619. editor.create_model(Author)
  1620. # Alter the case of the table
  1621. old_table_name = Author._meta.db_table
  1622. with connection.schema_editor() as editor:
  1623. editor.alter_db_table(Author, old_table_name, old_table_name.upper())
  1624. def test_alter_implicit_id_to_explicit(self):
  1625. """
  1626. Should be able to convert an implicit "id" field to an explicit "id"
  1627. primary key field.
  1628. """
  1629. with connection.schema_editor() as editor:
  1630. editor.create_model(Author)
  1631. old_field = Author._meta.get_field("id")
  1632. new_field = AutoField(primary_key=True)
  1633. new_field.set_attributes_from_name("id")
  1634. new_field.model = Author
  1635. with connection.schema_editor() as editor:
  1636. editor.alter_field(Author, old_field, new_field, strict=True)
  1637. # This will fail if DROP DEFAULT is inadvertently executed on this
  1638. # field which drops the id sequence, at least on PostgreSQL.
  1639. Author.objects.create(name="Foo")
  1640. Author.objects.create(name="Bar")
  1641. def test_alter_autofield_pk_to_bigautofield_pk(self):
  1642. with connection.schema_editor() as editor:
  1643. editor.create_model(Author)
  1644. old_field = Author._meta.get_field("id")
  1645. new_field = BigAutoField(primary_key=True)
  1646. new_field.set_attributes_from_name("id")
  1647. new_field.model = Author
  1648. with connection.schema_editor() as editor:
  1649. editor.alter_field(Author, old_field, new_field, strict=True)
  1650. Author.objects.create(name="Foo", pk=1)
  1651. with connection.cursor() as cursor:
  1652. sequence_reset_sqls = connection.ops.sequence_reset_sql(
  1653. no_style(), [Author]
  1654. )
  1655. if sequence_reset_sqls:
  1656. cursor.execute(sequence_reset_sqls[0])
  1657. self.assertIsNotNone(Author.objects.create(name="Bar"))
  1658. def test_alter_autofield_pk_to_smallautofield_pk(self):
  1659. with connection.schema_editor() as editor:
  1660. editor.create_model(Author)
  1661. old_field = Author._meta.get_field("id")
  1662. new_field = SmallAutoField(primary_key=True)
  1663. new_field.set_attributes_from_name("id")
  1664. new_field.model = Author
  1665. with connection.schema_editor() as editor:
  1666. editor.alter_field(Author, old_field, new_field, strict=True)
  1667. Author.objects.create(name="Foo", pk=1)
  1668. with connection.cursor() as cursor:
  1669. sequence_reset_sqls = connection.ops.sequence_reset_sql(
  1670. no_style(), [Author]
  1671. )
  1672. if sequence_reset_sqls:
  1673. cursor.execute(sequence_reset_sqls[0])
  1674. self.assertIsNotNone(Author.objects.create(name="Bar"))
  1675. def test_alter_int_pk_to_autofield_pk(self):
  1676. """
  1677. Should be able to rename an IntegerField(primary_key=True) to
  1678. AutoField(primary_key=True).
  1679. """
  1680. with connection.schema_editor() as editor:
  1681. editor.create_model(IntegerPK)
  1682. old_field = IntegerPK._meta.get_field("i")
  1683. new_field = AutoField(primary_key=True)
  1684. new_field.model = IntegerPK
  1685. new_field.set_attributes_from_name("i")
  1686. with connection.schema_editor() as editor:
  1687. editor.alter_field(IntegerPK, old_field, new_field, strict=True)
  1688. # A model representing the updated model.
  1689. class IntegerPKToAutoField(Model):
  1690. i = AutoField(primary_key=True)
  1691. j = IntegerField(unique=True)
  1692. class Meta:
  1693. app_label = "schema"
  1694. apps = new_apps
  1695. db_table = IntegerPK._meta.db_table
  1696. # An id (i) is generated by the database.
  1697. obj = IntegerPKToAutoField.objects.create(j=1)
  1698. self.assertIsNotNone(obj.i)
  1699. def test_alter_int_pk_to_bigautofield_pk(self):
  1700. """
  1701. Should be able to rename an IntegerField(primary_key=True) to
  1702. BigAutoField(primary_key=True).
  1703. """
  1704. with connection.schema_editor() as editor:
  1705. editor.create_model(IntegerPK)
  1706. old_field = IntegerPK._meta.get_field("i")
  1707. new_field = BigAutoField(primary_key=True)
  1708. new_field.model = IntegerPK
  1709. new_field.set_attributes_from_name("i")
  1710. with connection.schema_editor() as editor:
  1711. editor.alter_field(IntegerPK, old_field, new_field, strict=True)
  1712. # A model representing the updated model.
  1713. class IntegerPKToBigAutoField(Model):
  1714. i = BigAutoField(primary_key=True)
  1715. j = IntegerField(unique=True)
  1716. class Meta:
  1717. app_label = "schema"
  1718. apps = new_apps
  1719. db_table = IntegerPK._meta.db_table
  1720. # An id (i) is generated by the database.
  1721. obj = IntegerPKToBigAutoField.objects.create(j=1)
  1722. self.assertIsNotNone(obj.i)
  1723. @isolate_apps("schema")
  1724. def test_alter_smallint_pk_to_smallautofield_pk(self):
  1725. """
  1726. Should be able to rename an SmallIntegerField(primary_key=True) to
  1727. SmallAutoField(primary_key=True).
  1728. """
  1729. class SmallIntegerPK(Model):
  1730. i = SmallIntegerField(primary_key=True)
  1731. class Meta:
  1732. app_label = "schema"
  1733. with connection.schema_editor() as editor:
  1734. editor.create_model(SmallIntegerPK)
  1735. self.isolated_local_models = [SmallIntegerPK]
  1736. old_field = SmallIntegerPK._meta.get_field("i")
  1737. new_field = SmallAutoField(primary_key=True)
  1738. new_field.model = SmallIntegerPK
  1739. new_field.set_attributes_from_name("i")
  1740. with connection.schema_editor() as editor:
  1741. editor.alter_field(SmallIntegerPK, old_field, new_field, strict=True)
  1742. @isolate_apps("schema")
  1743. @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific")
  1744. def test_alter_serial_auto_field_to_bigautofield(self):
  1745. class SerialAutoField(Model):
  1746. id = SmallAutoField(primary_key=True)
  1747. class Meta:
  1748. app_label = "schema"
  1749. table = SerialAutoField._meta.db_table
  1750. column = SerialAutoField._meta.get_field("id").column
  1751. with connection.cursor() as cursor:
  1752. cursor.execute(
  1753. f'CREATE TABLE "{table}" '
  1754. f'("{column}" smallserial NOT NULL PRIMARY KEY)'
  1755. )
  1756. try:
  1757. old_field = SerialAutoField._meta.get_field("id")
  1758. new_field = BigAutoField(primary_key=True)
  1759. new_field.model = SerialAutoField
  1760. new_field.set_attributes_from_name("id")
  1761. with connection.schema_editor() as editor:
  1762. editor.alter_field(SerialAutoField, old_field, new_field, strict=True)
  1763. sequence_name = f"{table}_{column}_seq"
  1764. with connection.cursor() as cursor:
  1765. cursor.execute(
  1766. "SELECT data_type FROM pg_sequences WHERE sequencename = %s",
  1767. [sequence_name],
  1768. )
  1769. row = cursor.fetchone()
  1770. sequence_data_type = row[0] if row and row[0] else None
  1771. self.assertEqual(sequence_data_type, "bigint")
  1772. # Rename the column.
  1773. old_field = new_field
  1774. new_field = AutoField(primary_key=True)
  1775. new_field.model = SerialAutoField
  1776. new_field.set_attributes_from_name("renamed_id")
  1777. with connection.schema_editor() as editor:
  1778. editor.alter_field(SerialAutoField, old_field, new_field, strict=True)
  1779. with connection.cursor() as cursor:
  1780. cursor.execute(
  1781. "SELECT data_type FROM pg_sequences WHERE sequencename = %s",
  1782. [sequence_name],
  1783. )
  1784. row = cursor.fetchone()
  1785. sequence_data_type = row[0] if row and row[0] else None
  1786. self.assertEqual(sequence_data_type, "integer")
  1787. finally:
  1788. with connection.cursor() as cursor:
  1789. cursor.execute(f'DROP TABLE "{table}"')
  1790. def test_alter_int_pk_to_int_unique(self):
  1791. """
  1792. Should be able to rename an IntegerField(primary_key=True) to
  1793. IntegerField(unique=True).
  1794. """
  1795. with connection.schema_editor() as editor:
  1796. editor.create_model(IntegerPK)
  1797. # Delete the old PK
  1798. old_field = IntegerPK._meta.get_field("i")
  1799. new_field = IntegerField(unique=True)
  1800. new_field.model = IntegerPK
  1801. new_field.set_attributes_from_name("i")
  1802. with connection.schema_editor() as editor:
  1803. editor.alter_field(IntegerPK, old_field, new_field, strict=True)
  1804. # The primary key constraint is gone. Result depends on database:
  1805. # 'id' for SQLite, None for others (must not be 'i').
  1806. self.assertIn(self.get_primary_key(IntegerPK._meta.db_table), ("id", None))
  1807. # Set up a model class as it currently stands. The original IntegerPK
  1808. # class is now out of date and some backends make use of the whole
  1809. # model class when modifying a field (such as sqlite3 when remaking a
  1810. # table) so an outdated model class leads to incorrect results.
  1811. class Transitional(Model):
  1812. i = IntegerField(unique=True)
  1813. j = IntegerField(unique=True)
  1814. class Meta:
  1815. app_label = "schema"
  1816. apps = new_apps
  1817. db_table = "INTEGERPK"
  1818. # model requires a new PK
  1819. old_field = Transitional._meta.get_field("j")
  1820. new_field = IntegerField(primary_key=True)
  1821. new_field.model = Transitional
  1822. new_field.set_attributes_from_name("j")
  1823. with connection.schema_editor() as editor:
  1824. editor.alter_field(Transitional, old_field, new_field, strict=True)
  1825. # Create a model class representing the updated model.
  1826. class IntegerUnique(Model):
  1827. i = IntegerField(unique=True)
  1828. j = IntegerField(primary_key=True)
  1829. class Meta:
  1830. app_label = "schema"
  1831. apps = new_apps
  1832. db_table = "INTEGERPK"
  1833. # Ensure unique constraint works.
  1834. IntegerUnique.objects.create(i=1, j=1)
  1835. with self.assertRaises(IntegrityError):
  1836. IntegerUnique.objects.create(i=1, j=2)
  1837. def test_rename(self):
  1838. """
  1839. Tests simple altering of fields
  1840. """
  1841. # Create the table
  1842. with connection.schema_editor() as editor:
  1843. editor.create_model(Author)
  1844. # Ensure the field is right to begin with
  1845. columns = self.column_classes(Author)
  1846. self.assertEqual(
  1847. columns["name"][0],
  1848. connection.features.introspected_field_types["CharField"],
  1849. )
  1850. self.assertNotIn("display_name", columns)
  1851. # Alter the name field's name
  1852. old_field = Author._meta.get_field("name")
  1853. new_field = CharField(max_length=254)
  1854. new_field.set_attributes_from_name("display_name")
  1855. with connection.schema_editor() as editor:
  1856. editor.alter_field(Author, old_field, new_field, strict=True)
  1857. columns = self.column_classes(Author)
  1858. self.assertEqual(
  1859. columns["display_name"][0],
  1860. connection.features.introspected_field_types["CharField"],
  1861. )
  1862. self.assertNotIn("name", columns)
  1863. @isolate_apps("schema")
  1864. def test_rename_referenced_field(self):
  1865. class Author(Model):
  1866. name = CharField(max_length=255, unique=True)
  1867. class Meta:
  1868. app_label = "schema"
  1869. class Book(Model):
  1870. author = ForeignKey(Author, CASCADE, to_field="name")
  1871. class Meta:
  1872. app_label = "schema"
  1873. with connection.schema_editor() as editor:
  1874. editor.create_model(Author)
  1875. editor.create_model(Book)
  1876. new_field = CharField(max_length=255, unique=True)
  1877. new_field.set_attributes_from_name("renamed")
  1878. with connection.schema_editor(
  1879. atomic=connection.features.supports_atomic_references_rename
  1880. ) as editor:
  1881. editor.alter_field(Author, Author._meta.get_field("name"), new_field)
  1882. # Ensure the foreign key reference was updated.
  1883. self.assertForeignKeyExists(Book, "author_id", "schema_author", "renamed")
  1884. @skipIfDBFeature("interprets_empty_strings_as_nulls")
  1885. def test_rename_keep_null_status(self):
  1886. """
  1887. Renaming a field shouldn't affect the not null status.
  1888. """
  1889. with connection.schema_editor() as editor:
  1890. editor.create_model(Note)
  1891. with self.assertRaises(IntegrityError):
  1892. Note.objects.create(info=None)
  1893. old_field = Note._meta.get_field("info")
  1894. new_field = TextField()
  1895. new_field.set_attributes_from_name("detail_info")
  1896. with connection.schema_editor() as editor:
  1897. editor.alter_field(Note, old_field, new_field, strict=True)
  1898. columns = self.column_classes(Note)
  1899. self.assertEqual(columns["detail_info"][0], "TextField")
  1900. self.assertNotIn("info", columns)
  1901. with self.assertRaises(IntegrityError):
  1902. NoteRename.objects.create(detail_info=None)
  1903. @isolate_apps("schema")
  1904. def test_rename_keep_db_default(self):
  1905. """Renaming a field shouldn't affect a database default."""
  1906. class AuthorDbDefault(Model):
  1907. birth_year = IntegerField(db_default=1985)
  1908. class Meta:
  1909. app_label = "schema"
  1910. self.isolated_local_models = [AuthorDbDefault]
  1911. with connection.schema_editor() as editor:
  1912. editor.create_model(AuthorDbDefault)
  1913. columns = self.column_classes(AuthorDbDefault)
  1914. self.assertEqual(columns["birth_year"][1].default, "1985")
  1915. old_field = AuthorDbDefault._meta.get_field("birth_year")
  1916. new_field = IntegerField(db_default=1985)
  1917. new_field.set_attributes_from_name("renamed_year")
  1918. new_field.model = AuthorDbDefault
  1919. with connection.schema_editor(
  1920. atomic=connection.features.supports_atomic_references_rename
  1921. ) as editor:
  1922. editor.alter_field(AuthorDbDefault, old_field, new_field, strict=True)
  1923. columns = self.column_classes(AuthorDbDefault)
  1924. self.assertEqual(columns["renamed_year"][1].default, "1985")
  1925. @skipUnlessDBFeature(
  1926. "supports_column_check_constraints", "can_introspect_check_constraints"
  1927. )
  1928. @isolate_apps("schema")
  1929. def test_rename_field_with_check_to_truncated_name(self):
  1930. class AuthorWithLongColumn(Model):
  1931. field_with_very_looooooong_name = PositiveIntegerField(null=True)
  1932. class Meta:
  1933. app_label = "schema"
  1934. self.isolated_local_models = [AuthorWithLongColumn]
  1935. with connection.schema_editor() as editor:
  1936. editor.create_model(AuthorWithLongColumn)
  1937. old_field = AuthorWithLongColumn._meta.get_field(
  1938. "field_with_very_looooooong_name"
  1939. )
  1940. new_field = PositiveIntegerField(null=True)
  1941. new_field.set_attributes_from_name("renamed_field_with_very_long_name")
  1942. with connection.schema_editor() as editor:
  1943. editor.alter_field(AuthorWithLongColumn, old_field, new_field, strict=True)
  1944. new_field_name = truncate_name(
  1945. new_field.column, connection.ops.max_name_length()
  1946. )
  1947. constraints = self.get_constraints(AuthorWithLongColumn._meta.db_table)
  1948. check_constraints = [
  1949. name
  1950. for name, details in constraints.items()
  1951. if details["columns"] == [new_field_name] and details["check"]
  1952. ]
  1953. self.assertEqual(len(check_constraints), 1)
  1954. def _test_m2m_create(self, M2MFieldClass):
  1955. """
  1956. Tests M2M fields on models during creation
  1957. """
  1958. class LocalBookWithM2M(Model):
  1959. author = ForeignKey(Author, CASCADE)
  1960. title = CharField(max_length=100, db_index=True)
  1961. pub_date = DateTimeField()
  1962. tags = M2MFieldClass("TagM2MTest", related_name="books")
  1963. class Meta:
  1964. app_label = "schema"
  1965. apps = new_apps
  1966. self.local_models = [LocalBookWithM2M]
  1967. # Create the tables
  1968. with connection.schema_editor() as editor:
  1969. editor.create_model(Author)
  1970. editor.create_model(TagM2MTest)
  1971. editor.create_model(LocalBookWithM2M)
  1972. # Ensure there is now an m2m table there
  1973. columns = self.column_classes(
  1974. LocalBookWithM2M._meta.get_field("tags").remote_field.through
  1975. )
  1976. self.assertEqual(
  1977. columns["tagm2mtest_id"][0],
  1978. connection.features.introspected_field_types["IntegerField"],
  1979. )
  1980. def test_m2m_create(self):
  1981. self._test_m2m_create(ManyToManyField)
  1982. def test_m2m_create_custom(self):
  1983. self._test_m2m_create(CustomManyToManyField)
  1984. def test_m2m_create_inherited(self):
  1985. self._test_m2m_create(InheritedManyToManyField)
  1986. def _test_m2m_create_through(self, M2MFieldClass):
  1987. """
  1988. Tests M2M fields on models during creation with through models
  1989. """
  1990. class LocalTagThrough(Model):
  1991. book = ForeignKey("schema.LocalBookWithM2MThrough", CASCADE)
  1992. tag = ForeignKey("schema.TagM2MTest", CASCADE)
  1993. class Meta:
  1994. app_label = "schema"
  1995. apps = new_apps
  1996. class LocalBookWithM2MThrough(Model):
  1997. tags = M2MFieldClass(
  1998. "TagM2MTest", related_name="books", through=LocalTagThrough
  1999. )
  2000. class Meta:
  2001. app_label = "schema"
  2002. apps = new_apps
  2003. self.local_models = [LocalTagThrough, LocalBookWithM2MThrough]
  2004. # Create the tables
  2005. with connection.schema_editor() as editor:
  2006. editor.create_model(LocalTagThrough)
  2007. editor.create_model(TagM2MTest)
  2008. editor.create_model(LocalBookWithM2MThrough)
  2009. # Ensure there is now an m2m table there
  2010. columns = self.column_classes(LocalTagThrough)
  2011. self.assertEqual(
  2012. columns["book_id"][0],
  2013. connection.features.introspected_field_types["IntegerField"],
  2014. )
  2015. self.assertEqual(
  2016. columns["tag_id"][0],
  2017. connection.features.introspected_field_types["IntegerField"],
  2018. )
  2019. def test_m2m_create_through(self):
  2020. self._test_m2m_create_through(ManyToManyField)
  2021. def test_m2m_create_through_custom(self):
  2022. self._test_m2m_create_through(CustomManyToManyField)
  2023. def test_m2m_create_through_inherited(self):
  2024. self._test_m2m_create_through(InheritedManyToManyField)
  2025. def test_m2m_through_remove(self):
  2026. class LocalAuthorNoteThrough(Model):
  2027. book = ForeignKey("schema.Author", CASCADE)
  2028. tag = ForeignKey("self", CASCADE)
  2029. class Meta:
  2030. app_label = "schema"
  2031. apps = new_apps
  2032. class LocalNoteWithM2MThrough(Model):
  2033. authors = ManyToManyField("schema.Author", through=LocalAuthorNoteThrough)
  2034. class Meta:
  2035. app_label = "schema"
  2036. apps = new_apps
  2037. self.local_models = [LocalAuthorNoteThrough, LocalNoteWithM2MThrough]
  2038. # Create the tables.
  2039. with connection.schema_editor() as editor:
  2040. editor.create_model(Author)
  2041. editor.create_model(LocalAuthorNoteThrough)
  2042. editor.create_model(LocalNoteWithM2MThrough)
  2043. # Remove the through parameter.
  2044. old_field = LocalNoteWithM2MThrough._meta.get_field("authors")
  2045. new_field = ManyToManyField("Author")
  2046. new_field.set_attributes_from_name("authors")
  2047. msg = (
  2048. f"Cannot alter field {old_field} into {new_field} - they are not "
  2049. f"compatible types (you cannot alter to or from M2M fields, or add or "
  2050. f"remove through= on M2M fields)"
  2051. )
  2052. with connection.schema_editor() as editor:
  2053. with self.assertRaisesMessage(ValueError, msg):
  2054. editor.alter_field(LocalNoteWithM2MThrough, old_field, new_field)
  2055. def _test_m2m(self, M2MFieldClass):
  2056. """
  2057. Tests adding/removing M2M fields on models
  2058. """
  2059. class LocalAuthorWithM2M(Model):
  2060. name = CharField(max_length=255)
  2061. class Meta:
  2062. app_label = "schema"
  2063. apps = new_apps
  2064. self.local_models = [LocalAuthorWithM2M]
  2065. # Create the tables
  2066. with connection.schema_editor() as editor:
  2067. editor.create_model(LocalAuthorWithM2M)
  2068. editor.create_model(TagM2MTest)
  2069. # Create an M2M field
  2070. new_field = M2MFieldClass("schema.TagM2MTest", related_name="authors")
  2071. new_field.contribute_to_class(LocalAuthorWithM2M, "tags")
  2072. # Ensure there's no m2m table there
  2073. with self.assertRaises(DatabaseError):
  2074. self.column_classes(new_field.remote_field.through)
  2075. # Add the field
  2076. with CaptureQueriesContext(
  2077. connection
  2078. ) as ctx, connection.schema_editor() as editor:
  2079. editor.add_field(LocalAuthorWithM2M, new_field)
  2080. # Table is not rebuilt.
  2081. self.assertEqual(
  2082. len(
  2083. [
  2084. query["sql"]
  2085. for query in ctx.captured_queries
  2086. if "CREATE TABLE" in query["sql"]
  2087. ]
  2088. ),
  2089. 1,
  2090. )
  2091. self.assertIs(
  2092. any("DROP TABLE" in query["sql"] for query in ctx.captured_queries),
  2093. False,
  2094. )
  2095. # Ensure there is now an m2m table there
  2096. columns = self.column_classes(new_field.remote_field.through)
  2097. self.assertEqual(
  2098. columns["tagm2mtest_id"][0],
  2099. connection.features.introspected_field_types["IntegerField"],
  2100. )
  2101. # "Alter" the field. This should not rename the DB table to itself.
  2102. with connection.schema_editor() as editor:
  2103. editor.alter_field(LocalAuthorWithM2M, new_field, new_field, strict=True)
  2104. # Remove the M2M table again
  2105. with connection.schema_editor() as editor:
  2106. editor.remove_field(LocalAuthorWithM2M, new_field)
  2107. # Ensure there's no m2m table there
  2108. with self.assertRaises(DatabaseError):
  2109. self.column_classes(new_field.remote_field.through)
  2110. # Make sure the model state is coherent with the table one now that
  2111. # we've removed the tags field.
  2112. opts = LocalAuthorWithM2M._meta
  2113. opts.local_many_to_many.remove(new_field)
  2114. del new_apps.all_models["schema"][
  2115. new_field.remote_field.through._meta.model_name
  2116. ]
  2117. opts._expire_cache()
  2118. def test_m2m(self):
  2119. self._test_m2m(ManyToManyField)
  2120. def test_m2m_custom(self):
  2121. self._test_m2m(CustomManyToManyField)
  2122. def test_m2m_inherited(self):
  2123. self._test_m2m(InheritedManyToManyField)
  2124. def _test_m2m_through_alter(self, M2MFieldClass):
  2125. """
  2126. Tests altering M2Ms with explicit through models (should no-op)
  2127. """
  2128. class LocalAuthorTag(Model):
  2129. author = ForeignKey("schema.LocalAuthorWithM2MThrough", CASCADE)
  2130. tag = ForeignKey("schema.TagM2MTest", CASCADE)
  2131. class Meta:
  2132. app_label = "schema"
  2133. apps = new_apps
  2134. class LocalAuthorWithM2MThrough(Model):
  2135. name = CharField(max_length=255)
  2136. tags = M2MFieldClass(
  2137. "schema.TagM2MTest", related_name="authors", through=LocalAuthorTag
  2138. )
  2139. class Meta:
  2140. app_label = "schema"
  2141. apps = new_apps
  2142. self.local_models = [LocalAuthorTag, LocalAuthorWithM2MThrough]
  2143. # Create the tables
  2144. with connection.schema_editor() as editor:
  2145. editor.create_model(LocalAuthorTag)
  2146. editor.create_model(LocalAuthorWithM2MThrough)
  2147. editor.create_model(TagM2MTest)
  2148. # Ensure the m2m table is there
  2149. self.assertEqual(len(self.column_classes(LocalAuthorTag)), 3)
  2150. # "Alter" the field's blankness. This should not actually do anything.
  2151. old_field = LocalAuthorWithM2MThrough._meta.get_field("tags")
  2152. new_field = M2MFieldClass(
  2153. "schema.TagM2MTest", related_name="authors", through=LocalAuthorTag
  2154. )
  2155. new_field.contribute_to_class(LocalAuthorWithM2MThrough, "tags")
  2156. with connection.schema_editor() as editor:
  2157. editor.alter_field(
  2158. LocalAuthorWithM2MThrough, old_field, new_field, strict=True
  2159. )
  2160. # Ensure the m2m table is still there
  2161. self.assertEqual(len(self.column_classes(LocalAuthorTag)), 3)
  2162. def test_m2m_through_alter(self):
  2163. self._test_m2m_through_alter(ManyToManyField)
  2164. def test_m2m_through_alter_custom(self):
  2165. self._test_m2m_through_alter(CustomManyToManyField)
  2166. def test_m2m_through_alter_inherited(self):
  2167. self._test_m2m_through_alter(InheritedManyToManyField)
  2168. def _test_m2m_repoint(self, M2MFieldClass):
  2169. """
  2170. Tests repointing M2M fields
  2171. """
  2172. class LocalBookWithM2M(Model):
  2173. author = ForeignKey(Author, CASCADE)
  2174. title = CharField(max_length=100, db_index=True)
  2175. pub_date = DateTimeField()
  2176. tags = M2MFieldClass("TagM2MTest", related_name="books")
  2177. class Meta:
  2178. app_label = "schema"
  2179. apps = new_apps
  2180. self.local_models = [LocalBookWithM2M]
  2181. # Create the tables
  2182. with connection.schema_editor() as editor:
  2183. editor.create_model(Author)
  2184. editor.create_model(LocalBookWithM2M)
  2185. editor.create_model(TagM2MTest)
  2186. editor.create_model(UniqueTest)
  2187. # Ensure the M2M exists and points to TagM2MTest
  2188. if connection.features.supports_foreign_keys:
  2189. self.assertForeignKeyExists(
  2190. LocalBookWithM2M._meta.get_field("tags").remote_field.through,
  2191. "tagm2mtest_id",
  2192. "schema_tagm2mtest",
  2193. )
  2194. # Repoint the M2M
  2195. old_field = LocalBookWithM2M._meta.get_field("tags")
  2196. new_field = M2MFieldClass(UniqueTest)
  2197. new_field.contribute_to_class(LocalBookWithM2M, "uniques")
  2198. with connection.schema_editor() as editor:
  2199. editor.alter_field(LocalBookWithM2M, old_field, new_field, strict=True)
  2200. # Ensure old M2M is gone
  2201. with self.assertRaises(DatabaseError):
  2202. self.column_classes(
  2203. LocalBookWithM2M._meta.get_field("tags").remote_field.through
  2204. )
  2205. # This model looks like the new model and is used for teardown.
  2206. opts = LocalBookWithM2M._meta
  2207. opts.local_many_to_many.remove(old_field)
  2208. # Ensure the new M2M exists and points to UniqueTest
  2209. if connection.features.supports_foreign_keys:
  2210. self.assertForeignKeyExists(
  2211. new_field.remote_field.through, "uniquetest_id", "schema_uniquetest"
  2212. )
  2213. def test_m2m_repoint(self):
  2214. self._test_m2m_repoint(ManyToManyField)
  2215. def test_m2m_repoint_custom(self):
  2216. self._test_m2m_repoint(CustomManyToManyField)
  2217. def test_m2m_repoint_inherited(self):
  2218. self._test_m2m_repoint(InheritedManyToManyField)
  2219. @isolate_apps("schema")
  2220. def test_m2m_rename_field_in_target_model(self):
  2221. class LocalTagM2MTest(Model):
  2222. title = CharField(max_length=255)
  2223. class Meta:
  2224. app_label = "schema"
  2225. class LocalM2M(Model):
  2226. tags = ManyToManyField(LocalTagM2MTest)
  2227. class Meta:
  2228. app_label = "schema"
  2229. # Create the tables.
  2230. with connection.schema_editor() as editor:
  2231. editor.create_model(LocalM2M)
  2232. editor.create_model(LocalTagM2MTest)
  2233. self.isolated_local_models = [LocalM2M, LocalTagM2MTest]
  2234. # Ensure the m2m table is there.
  2235. self.assertEqual(len(self.column_classes(LocalM2M)), 1)
  2236. # Alter a field in LocalTagM2MTest.
  2237. old_field = LocalTagM2MTest._meta.get_field("title")
  2238. new_field = CharField(max_length=254)
  2239. new_field.contribute_to_class(LocalTagM2MTest, "title1")
  2240. # @isolate_apps() and inner models are needed to have the model
  2241. # relations populated, otherwise this doesn't act as a regression test.
  2242. self.assertEqual(len(new_field.model._meta.related_objects), 1)
  2243. with connection.schema_editor() as editor:
  2244. editor.alter_field(LocalTagM2MTest, old_field, new_field, strict=True)
  2245. # Ensure the m2m table is still there.
  2246. self.assertEqual(len(self.column_classes(LocalM2M)), 1)
  2247. @skipUnlessDBFeature(
  2248. "supports_column_check_constraints", "can_introspect_check_constraints"
  2249. )
  2250. def test_check_constraints(self):
  2251. """
  2252. Tests creating/deleting CHECK constraints
  2253. """
  2254. # Create the tables
  2255. with connection.schema_editor() as editor:
  2256. editor.create_model(Author)
  2257. # Ensure the constraint exists
  2258. constraints = self.get_constraints(Author._meta.db_table)
  2259. if not any(
  2260. details["columns"] == ["height"] and details["check"]
  2261. for details in constraints.values()
  2262. ):
  2263. self.fail("No check constraint for height found")
  2264. # Alter the column to remove it
  2265. old_field = Author._meta.get_field("height")
  2266. new_field = IntegerField(null=True, blank=True)
  2267. new_field.set_attributes_from_name("height")
  2268. with connection.schema_editor() as editor:
  2269. editor.alter_field(Author, old_field, new_field, strict=True)
  2270. constraints = self.get_constraints(Author._meta.db_table)
  2271. for details in constraints.values():
  2272. if details["columns"] == ["height"] and details["check"]:
  2273. self.fail("Check constraint for height found")
  2274. # Alter the column to re-add it
  2275. new_field2 = Author._meta.get_field("height")
  2276. with connection.schema_editor() as editor:
  2277. editor.alter_field(Author, new_field, new_field2, strict=True)
  2278. constraints = self.get_constraints(Author._meta.db_table)
  2279. if not any(
  2280. details["columns"] == ["height"] and details["check"]
  2281. for details in constraints.values()
  2282. ):
  2283. self.fail("No check constraint for height found")
  2284. @skipUnlessDBFeature(
  2285. "supports_column_check_constraints", "can_introspect_check_constraints"
  2286. )
  2287. @isolate_apps("schema")
  2288. def test_check_constraint_timedelta_param(self):
  2289. class DurationModel(Model):
  2290. duration = DurationField()
  2291. class Meta:
  2292. app_label = "schema"
  2293. with connection.schema_editor() as editor:
  2294. editor.create_model(DurationModel)
  2295. self.isolated_local_models = [DurationModel]
  2296. constraint_name = "duration_gte_5_minutes"
  2297. constraint = CheckConstraint(
  2298. check=Q(duration__gt=datetime.timedelta(minutes=5)),
  2299. name=constraint_name,
  2300. )
  2301. DurationModel._meta.constraints = [constraint]
  2302. with connection.schema_editor() as editor:
  2303. editor.add_constraint(DurationModel, constraint)
  2304. constraints = self.get_constraints(DurationModel._meta.db_table)
  2305. self.assertIn(constraint_name, constraints)
  2306. with self.assertRaises(IntegrityError), atomic():
  2307. DurationModel.objects.create(duration=datetime.timedelta(minutes=4))
  2308. DurationModel.objects.create(duration=datetime.timedelta(minutes=10))
  2309. @skipUnlessDBFeature(
  2310. "supports_column_check_constraints", "can_introspect_check_constraints"
  2311. )
  2312. def test_remove_field_check_does_not_remove_meta_constraints(self):
  2313. with connection.schema_editor() as editor:
  2314. editor.create_model(Author)
  2315. # Add the custom check constraint
  2316. constraint = CheckConstraint(
  2317. check=Q(height__gte=0), name="author_height_gte_0_check"
  2318. )
  2319. custom_constraint_name = constraint.name
  2320. Author._meta.constraints = [constraint]
  2321. with connection.schema_editor() as editor:
  2322. editor.add_constraint(Author, constraint)
  2323. # Ensure the constraints exist
  2324. constraints = self.get_constraints(Author._meta.db_table)
  2325. self.assertIn(custom_constraint_name, constraints)
  2326. other_constraints = [
  2327. name
  2328. for name, details in constraints.items()
  2329. if details["columns"] == ["height"]
  2330. and details["check"]
  2331. and name != custom_constraint_name
  2332. ]
  2333. self.assertEqual(len(other_constraints), 1)
  2334. # Alter the column to remove field check
  2335. old_field = Author._meta.get_field("height")
  2336. new_field = IntegerField(null=True, blank=True)
  2337. new_field.set_attributes_from_name("height")
  2338. with connection.schema_editor() as editor:
  2339. editor.alter_field(Author, old_field, new_field, strict=True)
  2340. constraints = self.get_constraints(Author._meta.db_table)
  2341. self.assertIn(custom_constraint_name, constraints)
  2342. other_constraints = [
  2343. name
  2344. for name, details in constraints.items()
  2345. if details["columns"] == ["height"]
  2346. and details["check"]
  2347. and name != custom_constraint_name
  2348. ]
  2349. self.assertEqual(len(other_constraints), 0)
  2350. # Alter the column to re-add field check
  2351. new_field2 = Author._meta.get_field("height")
  2352. with connection.schema_editor() as editor:
  2353. editor.alter_field(Author, new_field, new_field2, strict=True)
  2354. constraints = self.get_constraints(Author._meta.db_table)
  2355. self.assertIn(custom_constraint_name, constraints)
  2356. other_constraints = [
  2357. name
  2358. for name, details in constraints.items()
  2359. if details["columns"] == ["height"]
  2360. and details["check"]
  2361. and name != custom_constraint_name
  2362. ]
  2363. self.assertEqual(len(other_constraints), 1)
  2364. # Drop the check constraint
  2365. with connection.schema_editor() as editor:
  2366. Author._meta.constraints = []
  2367. editor.remove_constraint(Author, constraint)
  2368. def test_unique(self):
  2369. """
  2370. Tests removing and adding unique constraints to a single column.
  2371. """
  2372. # Create the table
  2373. with connection.schema_editor() as editor:
  2374. editor.create_model(Tag)
  2375. # Ensure the field is unique to begin with
  2376. Tag.objects.create(title="foo", slug="foo")
  2377. with self.assertRaises(IntegrityError):
  2378. Tag.objects.create(title="bar", slug="foo")
  2379. Tag.objects.all().delete()
  2380. # Alter the slug field to be non-unique
  2381. old_field = Tag._meta.get_field("slug")
  2382. new_field = SlugField(unique=False)
  2383. new_field.set_attributes_from_name("slug")
  2384. with connection.schema_editor() as editor:
  2385. editor.alter_field(Tag, old_field, new_field, strict=True)
  2386. # Ensure the field is no longer unique
  2387. Tag.objects.create(title="foo", slug="foo")
  2388. Tag.objects.create(title="bar", slug="foo")
  2389. Tag.objects.all().delete()
  2390. # Alter the slug field to be unique
  2391. new_field2 = SlugField(unique=True)
  2392. new_field2.set_attributes_from_name("slug")
  2393. with connection.schema_editor() as editor:
  2394. editor.alter_field(Tag, new_field, new_field2, strict=True)
  2395. # Ensure the field is unique again
  2396. Tag.objects.create(title="foo", slug="foo")
  2397. with self.assertRaises(IntegrityError):
  2398. Tag.objects.create(title="bar", slug="foo")
  2399. Tag.objects.all().delete()
  2400. # Rename the field
  2401. new_field3 = SlugField(unique=True)
  2402. new_field3.set_attributes_from_name("slug2")
  2403. with connection.schema_editor() as editor:
  2404. editor.alter_field(Tag, new_field2, new_field3, strict=True)
  2405. # Ensure the field is still unique
  2406. TagUniqueRename.objects.create(title="foo", slug2="foo")
  2407. with self.assertRaises(IntegrityError):
  2408. TagUniqueRename.objects.create(title="bar", slug2="foo")
  2409. Tag.objects.all().delete()
  2410. def test_unique_name_quoting(self):
  2411. old_table_name = TagUniqueRename._meta.db_table
  2412. try:
  2413. with connection.schema_editor() as editor:
  2414. editor.create_model(TagUniqueRename)
  2415. editor.alter_db_table(TagUniqueRename, old_table_name, "unique-table")
  2416. TagUniqueRename._meta.db_table = "unique-table"
  2417. # This fails if the unique index name isn't quoted.
  2418. editor.alter_unique_together(TagUniqueRename, [], (("title", "slug2"),))
  2419. finally:
  2420. with connection.schema_editor() as editor:
  2421. editor.delete_model(TagUniqueRename)
  2422. TagUniqueRename._meta.db_table = old_table_name
  2423. @isolate_apps("schema")
  2424. @skipUnlessDBFeature("supports_foreign_keys")
  2425. def test_unique_no_unnecessary_fk_drops(self):
  2426. """
  2427. If AlterField isn't selective about dropping foreign key constraints
  2428. when modifying a field with a unique constraint, the AlterField
  2429. incorrectly drops and recreates the Book.author foreign key even though
  2430. it doesn't restrict the field being changed (#29193).
  2431. """
  2432. class Author(Model):
  2433. name = CharField(max_length=254, unique=True)
  2434. class Meta:
  2435. app_label = "schema"
  2436. class Book(Model):
  2437. author = ForeignKey(Author, CASCADE)
  2438. class Meta:
  2439. app_label = "schema"
  2440. with connection.schema_editor() as editor:
  2441. editor.create_model(Author)
  2442. editor.create_model(Book)
  2443. new_field = CharField(max_length=255, unique=True)
  2444. new_field.model = Author
  2445. new_field.set_attributes_from_name("name")
  2446. with self.assertLogs("django.db.backends.schema", "DEBUG") as cm:
  2447. with connection.schema_editor() as editor:
  2448. editor.alter_field(Author, Author._meta.get_field("name"), new_field)
  2449. # One SQL statement is executed to alter the field.
  2450. self.assertEqual(len(cm.records), 1)
  2451. @isolate_apps("schema")
  2452. def test_unique_and_reverse_m2m(self):
  2453. """
  2454. AlterField can modify a unique field when there's a reverse M2M
  2455. relation on the model.
  2456. """
  2457. class Tag(Model):
  2458. title = CharField(max_length=255)
  2459. slug = SlugField(unique=True)
  2460. class Meta:
  2461. app_label = "schema"
  2462. class Book(Model):
  2463. tags = ManyToManyField(Tag, related_name="books")
  2464. class Meta:
  2465. app_label = "schema"
  2466. self.isolated_local_models = [Book._meta.get_field("tags").remote_field.through]
  2467. with connection.schema_editor() as editor:
  2468. editor.create_model(Tag)
  2469. editor.create_model(Book)
  2470. new_field = SlugField(max_length=75, unique=True)
  2471. new_field.model = Tag
  2472. new_field.set_attributes_from_name("slug")
  2473. with self.assertLogs("django.db.backends.schema", "DEBUG") as cm:
  2474. with connection.schema_editor() as editor:
  2475. editor.alter_field(Tag, Tag._meta.get_field("slug"), new_field)
  2476. # One SQL statement is executed to alter the field.
  2477. self.assertEqual(len(cm.records), 1)
  2478. # Ensure that the field is still unique.
  2479. Tag.objects.create(title="foo", slug="foo")
  2480. with self.assertRaises(IntegrityError):
  2481. Tag.objects.create(title="bar", slug="foo")
  2482. def test_remove_ignored_unique_constraint_not_create_fk_index(self):
  2483. with connection.schema_editor() as editor:
  2484. editor.create_model(Author)
  2485. editor.create_model(Book)
  2486. constraint = UniqueConstraint(
  2487. "author",
  2488. condition=Q(title__in=["tHGttG", "tRatEotU"]),
  2489. name="book_author_condition_uniq",
  2490. )
  2491. # Add unique constraint.
  2492. with connection.schema_editor() as editor:
  2493. editor.add_constraint(Book, constraint)
  2494. old_constraints = self.get_constraints_for_column(
  2495. Book,
  2496. Book._meta.get_field("author").column,
  2497. )
  2498. # Remove unique constraint.
  2499. with connection.schema_editor() as editor:
  2500. editor.remove_constraint(Book, constraint)
  2501. new_constraints = self.get_constraints_for_column(
  2502. Book,
  2503. Book._meta.get_field("author").column,
  2504. )
  2505. # Redundant foreign key index is not added.
  2506. self.assertEqual(
  2507. len(old_constraints) - 1
  2508. if connection.features.supports_partial_indexes
  2509. else len(old_constraints),
  2510. len(new_constraints),
  2511. )
  2512. @skipUnlessDBFeature("allows_multiple_constraints_on_same_fields")
  2513. def test_remove_field_unique_does_not_remove_meta_constraints(self):
  2514. with connection.schema_editor() as editor:
  2515. editor.create_model(AuthorWithUniqueName)
  2516. self.local_models = [AuthorWithUniqueName]
  2517. # Add the custom unique constraint
  2518. constraint = UniqueConstraint(fields=["name"], name="author_name_uniq")
  2519. custom_constraint_name = constraint.name
  2520. AuthorWithUniqueName._meta.constraints = [constraint]
  2521. with connection.schema_editor() as editor:
  2522. editor.add_constraint(AuthorWithUniqueName, constraint)
  2523. # Ensure the constraints exist
  2524. constraints = self.get_constraints(AuthorWithUniqueName._meta.db_table)
  2525. self.assertIn(custom_constraint_name, constraints)
  2526. other_constraints = [
  2527. name
  2528. for name, details in constraints.items()
  2529. if details["columns"] == ["name"]
  2530. and details["unique"]
  2531. and name != custom_constraint_name
  2532. ]
  2533. self.assertEqual(len(other_constraints), 1)
  2534. # Alter the column to remove field uniqueness
  2535. old_field = AuthorWithUniqueName._meta.get_field("name")
  2536. new_field = CharField(max_length=255)
  2537. new_field.set_attributes_from_name("name")
  2538. with connection.schema_editor() as editor:
  2539. editor.alter_field(AuthorWithUniqueName, old_field, new_field, strict=True)
  2540. constraints = self.get_constraints(AuthorWithUniqueName._meta.db_table)
  2541. self.assertIn(custom_constraint_name, constraints)
  2542. other_constraints = [
  2543. name
  2544. for name, details in constraints.items()
  2545. if details["columns"] == ["name"]
  2546. and details["unique"]
  2547. and name != custom_constraint_name
  2548. ]
  2549. self.assertEqual(len(other_constraints), 0)
  2550. # Alter the column to re-add field uniqueness
  2551. new_field2 = AuthorWithUniqueName._meta.get_field("name")
  2552. with connection.schema_editor() as editor:
  2553. editor.alter_field(AuthorWithUniqueName, new_field, new_field2, strict=True)
  2554. constraints = self.get_constraints(AuthorWithUniqueName._meta.db_table)
  2555. self.assertIn(custom_constraint_name, constraints)
  2556. other_constraints = [
  2557. name
  2558. for name, details in constraints.items()
  2559. if details["columns"] == ["name"]
  2560. and details["unique"]
  2561. and name != custom_constraint_name
  2562. ]
  2563. self.assertEqual(len(other_constraints), 1)
  2564. # Drop the unique constraint
  2565. with connection.schema_editor() as editor:
  2566. AuthorWithUniqueName._meta.constraints = []
  2567. editor.remove_constraint(AuthorWithUniqueName, constraint)
  2568. def test_unique_together(self):
  2569. """
  2570. Tests removing and adding unique_together constraints on a model.
  2571. """
  2572. # Create the table
  2573. with connection.schema_editor() as editor:
  2574. editor.create_model(UniqueTest)
  2575. # Ensure the fields are unique to begin with
  2576. UniqueTest.objects.create(year=2012, slug="foo")
  2577. UniqueTest.objects.create(year=2011, slug="foo")
  2578. UniqueTest.objects.create(year=2011, slug="bar")
  2579. with self.assertRaises(IntegrityError):
  2580. UniqueTest.objects.create(year=2012, slug="foo")
  2581. UniqueTest.objects.all().delete()
  2582. # Alter the model to its non-unique-together companion
  2583. with connection.schema_editor() as editor:
  2584. editor.alter_unique_together(
  2585. UniqueTest, UniqueTest._meta.unique_together, []
  2586. )
  2587. # Ensure the fields are no longer unique
  2588. UniqueTest.objects.create(year=2012, slug="foo")
  2589. UniqueTest.objects.create(year=2012, slug="foo")
  2590. UniqueTest.objects.all().delete()
  2591. # Alter it back
  2592. new_field2 = SlugField(unique=True)
  2593. new_field2.set_attributes_from_name("slug")
  2594. with connection.schema_editor() as editor:
  2595. editor.alter_unique_together(
  2596. UniqueTest, [], UniqueTest._meta.unique_together
  2597. )
  2598. # Ensure the fields are unique again
  2599. UniqueTest.objects.create(year=2012, slug="foo")
  2600. with self.assertRaises(IntegrityError):
  2601. UniqueTest.objects.create(year=2012, slug="foo")
  2602. UniqueTest.objects.all().delete()
  2603. def test_unique_together_with_fk(self):
  2604. """
  2605. Tests removing and adding unique_together constraints that include
  2606. a foreign key.
  2607. """
  2608. # Create the table
  2609. with connection.schema_editor() as editor:
  2610. editor.create_model(Author)
  2611. editor.create_model(Book)
  2612. # Ensure the fields are unique to begin with
  2613. self.assertEqual(Book._meta.unique_together, ())
  2614. # Add the unique_together constraint
  2615. with connection.schema_editor() as editor:
  2616. editor.alter_unique_together(Book, [], [["author", "title"]])
  2617. # Alter it back
  2618. with connection.schema_editor() as editor:
  2619. editor.alter_unique_together(Book, [["author", "title"]], [])
  2620. def test_unique_together_with_fk_with_existing_index(self):
  2621. """
  2622. Tests removing and adding unique_together constraints that include
  2623. a foreign key, where the foreign key is added after the model is
  2624. created.
  2625. """
  2626. # Create the tables
  2627. with connection.schema_editor() as editor:
  2628. editor.create_model(Author)
  2629. editor.create_model(BookWithoutAuthor)
  2630. new_field = ForeignKey(Author, CASCADE)
  2631. new_field.set_attributes_from_name("author")
  2632. editor.add_field(BookWithoutAuthor, new_field)
  2633. # Ensure the fields aren't unique to begin with
  2634. self.assertEqual(Book._meta.unique_together, ())
  2635. # Add the unique_together constraint
  2636. with connection.schema_editor() as editor:
  2637. editor.alter_unique_together(Book, [], [["author", "title"]])
  2638. # Alter it back
  2639. with connection.schema_editor() as editor:
  2640. editor.alter_unique_together(Book, [["author", "title"]], [])
  2641. def _test_composed_index_with_fk(self, index):
  2642. with connection.schema_editor() as editor:
  2643. editor.create_model(Author)
  2644. editor.create_model(Book)
  2645. table = Book._meta.db_table
  2646. self.assertEqual(Book._meta.indexes, [])
  2647. Book._meta.indexes = [index]
  2648. with connection.schema_editor() as editor:
  2649. editor.add_index(Book, index)
  2650. self.assertIn(index.name, self.get_constraints(table))
  2651. Book._meta.indexes = []
  2652. with connection.schema_editor() as editor:
  2653. editor.remove_index(Book, index)
  2654. self.assertNotIn(index.name, self.get_constraints(table))
  2655. def test_composed_index_with_fk(self):
  2656. index = Index(fields=["author", "title"], name="book_author_title_idx")
  2657. self._test_composed_index_with_fk(index)
  2658. def test_composed_desc_index_with_fk(self):
  2659. index = Index(fields=["-author", "title"], name="book_author_title_idx")
  2660. self._test_composed_index_with_fk(index)
  2661. @skipUnlessDBFeature("supports_expression_indexes")
  2662. def test_composed_func_index_with_fk(self):
  2663. index = Index(F("author"), F("title"), name="book_author_title_idx")
  2664. self._test_composed_index_with_fk(index)
  2665. @skipUnlessDBFeature("supports_expression_indexes")
  2666. def test_composed_desc_func_index_with_fk(self):
  2667. index = Index(F("author").desc(), F("title"), name="book_author_title_idx")
  2668. self._test_composed_index_with_fk(index)
  2669. @skipUnlessDBFeature("supports_expression_indexes")
  2670. def test_composed_func_transform_index_with_fk(self):
  2671. index = Index(F("title__lower"), name="book_title_lower_idx")
  2672. with register_lookup(CharField, Lower):
  2673. self._test_composed_index_with_fk(index)
  2674. def _test_composed_constraint_with_fk(self, constraint):
  2675. with connection.schema_editor() as editor:
  2676. editor.create_model(Author)
  2677. editor.create_model(Book)
  2678. table = Book._meta.db_table
  2679. self.assertEqual(Book._meta.constraints, [])
  2680. Book._meta.constraints = [constraint]
  2681. with connection.schema_editor() as editor:
  2682. editor.add_constraint(Book, constraint)
  2683. self.assertIn(constraint.name, self.get_constraints(table))
  2684. Book._meta.constraints = []
  2685. with connection.schema_editor() as editor:
  2686. editor.remove_constraint(Book, constraint)
  2687. self.assertNotIn(constraint.name, self.get_constraints(table))
  2688. def test_composed_constraint_with_fk(self):
  2689. constraint = UniqueConstraint(
  2690. fields=["author", "title"],
  2691. name="book_author_title_uniq",
  2692. )
  2693. self._test_composed_constraint_with_fk(constraint)
  2694. @skipUnlessDBFeature(
  2695. "supports_column_check_constraints", "can_introspect_check_constraints"
  2696. )
  2697. def test_composed_check_constraint_with_fk(self):
  2698. constraint = CheckConstraint(check=Q(author__gt=0), name="book_author_check")
  2699. self._test_composed_constraint_with_fk(constraint)
  2700. @skipUnlessDBFeature("allows_multiple_constraints_on_same_fields")
  2701. def test_remove_unique_together_does_not_remove_meta_constraints(self):
  2702. with connection.schema_editor() as editor:
  2703. editor.create_model(AuthorWithUniqueNameAndBirthday)
  2704. self.local_models = [AuthorWithUniqueNameAndBirthday]
  2705. # Add the custom unique constraint
  2706. constraint = UniqueConstraint(
  2707. fields=["name", "birthday"], name="author_name_birthday_uniq"
  2708. )
  2709. custom_constraint_name = constraint.name
  2710. AuthorWithUniqueNameAndBirthday._meta.constraints = [constraint]
  2711. with connection.schema_editor() as editor:
  2712. editor.add_constraint(AuthorWithUniqueNameAndBirthday, constraint)
  2713. # Ensure the constraints exist
  2714. constraints = self.get_constraints(
  2715. AuthorWithUniqueNameAndBirthday._meta.db_table
  2716. )
  2717. self.assertIn(custom_constraint_name, constraints)
  2718. other_constraints = [
  2719. name
  2720. for name, details in constraints.items()
  2721. if details["columns"] == ["name", "birthday"]
  2722. and details["unique"]
  2723. and name != custom_constraint_name
  2724. ]
  2725. self.assertEqual(len(other_constraints), 1)
  2726. # Remove unique together
  2727. unique_together = AuthorWithUniqueNameAndBirthday._meta.unique_together
  2728. with connection.schema_editor() as editor:
  2729. editor.alter_unique_together(
  2730. AuthorWithUniqueNameAndBirthday, unique_together, []
  2731. )
  2732. constraints = self.get_constraints(
  2733. AuthorWithUniqueNameAndBirthday._meta.db_table
  2734. )
  2735. self.assertIn(custom_constraint_name, constraints)
  2736. other_constraints = [
  2737. name
  2738. for name, details in constraints.items()
  2739. if details["columns"] == ["name", "birthday"]
  2740. and details["unique"]
  2741. and name != custom_constraint_name
  2742. ]
  2743. self.assertEqual(len(other_constraints), 0)
  2744. # Re-add unique together
  2745. with connection.schema_editor() as editor:
  2746. editor.alter_unique_together(
  2747. AuthorWithUniqueNameAndBirthday, [], unique_together
  2748. )
  2749. constraints = self.get_constraints(
  2750. AuthorWithUniqueNameAndBirthday._meta.db_table
  2751. )
  2752. self.assertIn(custom_constraint_name, constraints)
  2753. other_constraints = [
  2754. name
  2755. for name, details in constraints.items()
  2756. if details["columns"] == ["name", "birthday"]
  2757. and details["unique"]
  2758. and name != custom_constraint_name
  2759. ]
  2760. self.assertEqual(len(other_constraints), 1)
  2761. # Drop the unique constraint
  2762. with connection.schema_editor() as editor:
  2763. AuthorWithUniqueNameAndBirthday._meta.constraints = []
  2764. editor.remove_constraint(AuthorWithUniqueNameAndBirthday, constraint)
  2765. def test_unique_constraint(self):
  2766. with connection.schema_editor() as editor:
  2767. editor.create_model(Author)
  2768. constraint = UniqueConstraint(fields=["name"], name="name_uq")
  2769. # Add constraint.
  2770. with connection.schema_editor() as editor:
  2771. editor.add_constraint(Author, constraint)
  2772. sql = constraint.create_sql(Author, editor)
  2773. table = Author._meta.db_table
  2774. self.assertIs(sql.references_table(table), True)
  2775. self.assertIs(sql.references_column(table, "name"), True)
  2776. # Remove constraint.
  2777. with connection.schema_editor() as editor:
  2778. editor.remove_constraint(Author, constraint)
  2779. self.assertNotIn(constraint.name, self.get_constraints(table))
  2780. @skipUnlessDBFeature("supports_expression_indexes")
  2781. def test_func_unique_constraint(self):
  2782. with connection.schema_editor() as editor:
  2783. editor.create_model(Author)
  2784. constraint = UniqueConstraint(Upper("name").desc(), name="func_upper_uq")
  2785. # Add constraint.
  2786. with connection.schema_editor() as editor:
  2787. editor.add_constraint(Author, constraint)
  2788. sql = constraint.create_sql(Author, editor)
  2789. table = Author._meta.db_table
  2790. constraints = self.get_constraints(table)
  2791. if connection.features.supports_index_column_ordering:
  2792. self.assertIndexOrder(table, constraint.name, ["DESC"])
  2793. self.assertIn(constraint.name, constraints)
  2794. self.assertIs(constraints[constraint.name]["unique"], True)
  2795. # SQL contains a database function.
  2796. self.assertIs(sql.references_column(table, "name"), True)
  2797. self.assertIn("UPPER(%s)" % editor.quote_name("name"), str(sql))
  2798. # Remove constraint.
  2799. with connection.schema_editor() as editor:
  2800. editor.remove_constraint(Author, constraint)
  2801. self.assertNotIn(constraint.name, self.get_constraints(table))
  2802. @skipUnlessDBFeature("supports_expression_indexes")
  2803. def test_composite_func_unique_constraint(self):
  2804. with connection.schema_editor() as editor:
  2805. editor.create_model(Author)
  2806. editor.create_model(BookWithSlug)
  2807. constraint = UniqueConstraint(
  2808. Upper("title"),
  2809. Lower("slug"),
  2810. name="func_upper_lower_unq",
  2811. )
  2812. # Add constraint.
  2813. with connection.schema_editor() as editor:
  2814. editor.add_constraint(BookWithSlug, constraint)
  2815. sql = constraint.create_sql(BookWithSlug, editor)
  2816. table = BookWithSlug._meta.db_table
  2817. constraints = self.get_constraints(table)
  2818. self.assertIn(constraint.name, constraints)
  2819. self.assertIs(constraints[constraint.name]["unique"], True)
  2820. # SQL contains database functions.
  2821. self.assertIs(sql.references_column(table, "title"), True)
  2822. self.assertIs(sql.references_column(table, "slug"), True)
  2823. sql = str(sql)
  2824. self.assertIn("UPPER(%s)" % editor.quote_name("title"), sql)
  2825. self.assertIn("LOWER(%s)" % editor.quote_name("slug"), sql)
  2826. self.assertLess(sql.index("UPPER"), sql.index("LOWER"))
  2827. # Remove constraint.
  2828. with connection.schema_editor() as editor:
  2829. editor.remove_constraint(BookWithSlug, constraint)
  2830. self.assertNotIn(constraint.name, self.get_constraints(table))
  2831. @skipUnlessDBFeature("supports_expression_indexes")
  2832. def test_unique_constraint_field_and_expression(self):
  2833. with connection.schema_editor() as editor:
  2834. editor.create_model(Author)
  2835. constraint = UniqueConstraint(
  2836. F("height").desc(),
  2837. "uuid",
  2838. Lower("name").asc(),
  2839. name="func_f_lower_field_unq",
  2840. )
  2841. # Add constraint.
  2842. with connection.schema_editor() as editor:
  2843. editor.add_constraint(Author, constraint)
  2844. sql = constraint.create_sql(Author, editor)
  2845. table = Author._meta.db_table
  2846. if connection.features.supports_index_column_ordering:
  2847. self.assertIndexOrder(table, constraint.name, ["DESC", "ASC", "ASC"])
  2848. constraints = self.get_constraints(table)
  2849. self.assertIs(constraints[constraint.name]["unique"], True)
  2850. self.assertEqual(len(constraints[constraint.name]["columns"]), 3)
  2851. self.assertEqual(constraints[constraint.name]["columns"][1], "uuid")
  2852. # SQL contains database functions and columns.
  2853. self.assertIs(sql.references_column(table, "height"), True)
  2854. self.assertIs(sql.references_column(table, "name"), True)
  2855. self.assertIs(sql.references_column(table, "uuid"), True)
  2856. self.assertIn("LOWER(%s)" % editor.quote_name("name"), str(sql))
  2857. # Remove constraint.
  2858. with connection.schema_editor() as editor:
  2859. editor.remove_constraint(Author, constraint)
  2860. self.assertNotIn(constraint.name, self.get_constraints(table))
  2861. @skipUnlessDBFeature("supports_expression_indexes", "supports_partial_indexes")
  2862. def test_func_unique_constraint_partial(self):
  2863. with connection.schema_editor() as editor:
  2864. editor.create_model(Author)
  2865. constraint = UniqueConstraint(
  2866. Upper("name"),
  2867. name="func_upper_cond_weight_uq",
  2868. condition=Q(weight__isnull=False),
  2869. )
  2870. # Add constraint.
  2871. with connection.schema_editor() as editor:
  2872. editor.add_constraint(Author, constraint)
  2873. sql = constraint.create_sql(Author, editor)
  2874. table = Author._meta.db_table
  2875. constraints = self.get_constraints(table)
  2876. self.assertIn(constraint.name, constraints)
  2877. self.assertIs(constraints[constraint.name]["unique"], True)
  2878. self.assertIs(sql.references_column(table, "name"), True)
  2879. self.assertIn("UPPER(%s)" % editor.quote_name("name"), str(sql))
  2880. self.assertIn(
  2881. "WHERE %s IS NOT NULL" % editor.quote_name("weight"),
  2882. str(sql),
  2883. )
  2884. # Remove constraint.
  2885. with connection.schema_editor() as editor:
  2886. editor.remove_constraint(Author, constraint)
  2887. self.assertNotIn(constraint.name, self.get_constraints(table))
  2888. @skipUnlessDBFeature("supports_expression_indexes", "supports_covering_indexes")
  2889. def test_func_unique_constraint_covering(self):
  2890. with connection.schema_editor() as editor:
  2891. editor.create_model(Author)
  2892. constraint = UniqueConstraint(
  2893. Upper("name"),
  2894. name="func_upper_covering_uq",
  2895. include=["weight", "height"],
  2896. )
  2897. # Add constraint.
  2898. with connection.schema_editor() as editor:
  2899. editor.add_constraint(Author, constraint)
  2900. sql = constraint.create_sql(Author, editor)
  2901. table = Author._meta.db_table
  2902. constraints = self.get_constraints(table)
  2903. self.assertIn(constraint.name, constraints)
  2904. self.assertIs(constraints[constraint.name]["unique"], True)
  2905. self.assertEqual(
  2906. constraints[constraint.name]["columns"],
  2907. [None, "weight", "height"],
  2908. )
  2909. self.assertIs(sql.references_column(table, "name"), True)
  2910. self.assertIs(sql.references_column(table, "weight"), True)
  2911. self.assertIs(sql.references_column(table, "height"), True)
  2912. self.assertIn("UPPER(%s)" % editor.quote_name("name"), str(sql))
  2913. self.assertIn(
  2914. "INCLUDE (%s, %s)"
  2915. % (
  2916. editor.quote_name("weight"),
  2917. editor.quote_name("height"),
  2918. ),
  2919. str(sql),
  2920. )
  2921. # Remove constraint.
  2922. with connection.schema_editor() as editor:
  2923. editor.remove_constraint(Author, constraint)
  2924. self.assertNotIn(constraint.name, self.get_constraints(table))
  2925. @skipUnlessDBFeature("supports_expression_indexes")
  2926. def test_func_unique_constraint_lookups(self):
  2927. with connection.schema_editor() as editor:
  2928. editor.create_model(Author)
  2929. with register_lookup(CharField, Lower), register_lookup(IntegerField, Abs):
  2930. constraint = UniqueConstraint(
  2931. F("name__lower"),
  2932. F("weight__abs"),
  2933. name="func_lower_abs_lookup_uq",
  2934. )
  2935. # Add constraint.
  2936. with connection.schema_editor() as editor:
  2937. editor.add_constraint(Author, constraint)
  2938. sql = constraint.create_sql(Author, editor)
  2939. table = Author._meta.db_table
  2940. constraints = self.get_constraints(table)
  2941. self.assertIn(constraint.name, constraints)
  2942. self.assertIs(constraints[constraint.name]["unique"], True)
  2943. # SQL contains columns.
  2944. self.assertIs(sql.references_column(table, "name"), True)
  2945. self.assertIs(sql.references_column(table, "weight"), True)
  2946. # Remove constraint.
  2947. with connection.schema_editor() as editor:
  2948. editor.remove_constraint(Author, constraint)
  2949. self.assertNotIn(constraint.name, self.get_constraints(table))
  2950. @skipUnlessDBFeature("supports_expression_indexes")
  2951. def test_func_unique_constraint_collate(self):
  2952. collation = connection.features.test_collations.get("non_default")
  2953. if not collation:
  2954. self.skipTest("This backend does not support case-insensitive collations.")
  2955. with connection.schema_editor() as editor:
  2956. editor.create_model(Author)
  2957. editor.create_model(BookWithSlug)
  2958. constraint = UniqueConstraint(
  2959. Collate(F("title"), collation=collation).desc(),
  2960. Collate("slug", collation=collation),
  2961. name="func_collate_uq",
  2962. )
  2963. # Add constraint.
  2964. with connection.schema_editor() as editor:
  2965. editor.add_constraint(BookWithSlug, constraint)
  2966. sql = constraint.create_sql(BookWithSlug, editor)
  2967. table = BookWithSlug._meta.db_table
  2968. constraints = self.get_constraints(table)
  2969. self.assertIn(constraint.name, constraints)
  2970. self.assertIs(constraints[constraint.name]["unique"], True)
  2971. if connection.features.supports_index_column_ordering:
  2972. self.assertIndexOrder(table, constraint.name, ["DESC", "ASC"])
  2973. # SQL contains columns and a collation.
  2974. self.assertIs(sql.references_column(table, "title"), True)
  2975. self.assertIs(sql.references_column(table, "slug"), True)
  2976. self.assertIn("COLLATE %s" % editor.quote_name(collation), str(sql))
  2977. # Remove constraint.
  2978. with connection.schema_editor() as editor:
  2979. editor.remove_constraint(BookWithSlug, constraint)
  2980. self.assertNotIn(constraint.name, self.get_constraints(table))
  2981. @skipIfDBFeature("supports_expression_indexes")
  2982. def test_func_unique_constraint_unsupported(self):
  2983. # UniqueConstraint is ignored on databases that don't support indexes on
  2984. # expressions.
  2985. with connection.schema_editor() as editor:
  2986. editor.create_model(Author)
  2987. constraint = UniqueConstraint(F("name"), name="func_name_uq")
  2988. with connection.schema_editor() as editor, self.assertNumQueries(0):
  2989. self.assertIsNone(editor.add_constraint(Author, constraint))
  2990. self.assertIsNone(editor.remove_constraint(Author, constraint))
  2991. @skipUnlessDBFeature("supports_expression_indexes")
  2992. def test_func_unique_constraint_nonexistent_field(self):
  2993. constraint = UniqueConstraint(Lower("nonexistent"), name="func_nonexistent_uq")
  2994. msg = (
  2995. "Cannot resolve keyword 'nonexistent' into field. Choices are: "
  2996. "height, id, name, uuid, weight"
  2997. )
  2998. with self.assertRaisesMessage(FieldError, msg):
  2999. with connection.schema_editor() as editor:
  3000. editor.add_constraint(Author, constraint)
  3001. @skipUnlessDBFeature("supports_expression_indexes")
  3002. def test_func_unique_constraint_nondeterministic(self):
  3003. with connection.schema_editor() as editor:
  3004. editor.create_model(Author)
  3005. constraint = UniqueConstraint(Random(), name="func_random_uq")
  3006. with connection.schema_editor() as editor:
  3007. with self.assertRaises(DatabaseError):
  3008. editor.add_constraint(Author, constraint)
  3009. @skipUnlessDBFeature("supports_nulls_distinct_unique_constraints")
  3010. def test_unique_constraint_nulls_distinct(self):
  3011. with connection.schema_editor() as editor:
  3012. editor.create_model(Author)
  3013. nulls_distinct = UniqueConstraint(
  3014. F("height"), name="distinct_height", nulls_distinct=True
  3015. )
  3016. nulls_not_distinct = UniqueConstraint(
  3017. F("weight"), name="not_distinct_weight", nulls_distinct=False
  3018. )
  3019. with connection.schema_editor() as editor:
  3020. editor.add_constraint(Author, nulls_distinct)
  3021. editor.add_constraint(Author, nulls_not_distinct)
  3022. Author.objects.create(name="", height=None, weight=None)
  3023. Author.objects.create(name="", height=None, weight=1)
  3024. with self.assertRaises(IntegrityError):
  3025. Author.objects.create(name="", height=1, weight=None)
  3026. with connection.schema_editor() as editor:
  3027. editor.remove_constraint(Author, nulls_distinct)
  3028. editor.remove_constraint(Author, nulls_not_distinct)
  3029. constraints = self.get_constraints(Author._meta.db_table)
  3030. self.assertNotIn(nulls_distinct.name, constraints)
  3031. self.assertNotIn(nulls_not_distinct.name, constraints)
  3032. @skipIfDBFeature("supports_nulls_distinct_unique_constraints")
  3033. def test_unique_constraint_nulls_distinct_unsupported(self):
  3034. # UniqueConstraint is ignored on databases that don't support
  3035. # NULLS [NOT] DISTINCT.
  3036. with connection.schema_editor() as editor:
  3037. editor.create_model(Author)
  3038. constraint = UniqueConstraint(
  3039. F("name"), name="func_name_uq", nulls_distinct=True
  3040. )
  3041. with connection.schema_editor() as editor, self.assertNumQueries(0):
  3042. self.assertIsNone(editor.add_constraint(Author, constraint))
  3043. self.assertIsNone(editor.remove_constraint(Author, constraint))
  3044. @ignore_warnings(category=RemovedInDjango51Warning)
  3045. def test_index_together(self):
  3046. """
  3047. Tests removing and adding index_together constraints on a model.
  3048. """
  3049. # Create the table
  3050. with connection.schema_editor() as editor:
  3051. editor.create_model(Tag)
  3052. # Ensure there's no index on the year/slug columns first
  3053. self.assertIs(
  3054. any(
  3055. c["index"]
  3056. for c in self.get_constraints("schema_tag").values()
  3057. if c["columns"] == ["slug", "title"]
  3058. ),
  3059. False,
  3060. )
  3061. # Alter the model to add an index
  3062. with connection.schema_editor() as editor:
  3063. editor.alter_index_together(Tag, [], [("slug", "title")])
  3064. # Ensure there is now an index
  3065. self.assertIs(
  3066. any(
  3067. c["index"]
  3068. for c in self.get_constraints("schema_tag").values()
  3069. if c["columns"] == ["slug", "title"]
  3070. ),
  3071. True,
  3072. )
  3073. # Alter it back
  3074. new_field2 = SlugField(unique=True)
  3075. new_field2.set_attributes_from_name("slug")
  3076. with connection.schema_editor() as editor:
  3077. editor.alter_index_together(Tag, [("slug", "title")], [])
  3078. # Ensure there's no index
  3079. self.assertIs(
  3080. any(
  3081. c["index"]
  3082. for c in self.get_constraints("schema_tag").values()
  3083. if c["columns"] == ["slug", "title"]
  3084. ),
  3085. False,
  3086. )
  3087. @ignore_warnings(category=RemovedInDjango51Warning)
  3088. def test_index_together_with_fk(self):
  3089. """
  3090. Tests removing and adding index_together constraints that include
  3091. a foreign key.
  3092. """
  3093. # Create the table
  3094. with connection.schema_editor() as editor:
  3095. editor.create_model(Author)
  3096. editor.create_model(Book)
  3097. # Ensure the fields are unique to begin with
  3098. self.assertEqual(Book._meta.index_together, ())
  3099. # Add the unique_together constraint
  3100. with connection.schema_editor() as editor:
  3101. editor.alter_index_together(Book, [], [["author", "title"]])
  3102. # Alter it back
  3103. with connection.schema_editor() as editor:
  3104. editor.alter_index_together(Book, [["author", "title"]], [])
  3105. @ignore_warnings(category=RemovedInDjango51Warning)
  3106. @isolate_apps("schema")
  3107. def test_create_index_together(self):
  3108. """
  3109. Tests creating models with index_together already defined
  3110. """
  3111. class TagIndexed(Model):
  3112. title = CharField(max_length=255)
  3113. slug = SlugField(unique=True)
  3114. class Meta:
  3115. app_label = "schema"
  3116. index_together = [["slug", "title"]]
  3117. # Create the table
  3118. with connection.schema_editor() as editor:
  3119. editor.create_model(TagIndexed)
  3120. self.isolated_local_models = [TagIndexed]
  3121. # Ensure there is an index
  3122. self.assertIs(
  3123. any(
  3124. c["index"]
  3125. for c in self.get_constraints("schema_tagindexed").values()
  3126. if c["columns"] == ["slug", "title"]
  3127. ),
  3128. True,
  3129. )
  3130. @skipUnlessDBFeature("allows_multiple_constraints_on_same_fields")
  3131. @ignore_warnings(category=RemovedInDjango51Warning)
  3132. @isolate_apps("schema")
  3133. def test_remove_index_together_does_not_remove_meta_indexes(self):
  3134. class AuthorWithIndexedNameAndBirthday(Model):
  3135. name = CharField(max_length=255)
  3136. birthday = DateField()
  3137. class Meta:
  3138. app_label = "schema"
  3139. index_together = [["name", "birthday"]]
  3140. with connection.schema_editor() as editor:
  3141. editor.create_model(AuthorWithIndexedNameAndBirthday)
  3142. self.isolated_local_models = [AuthorWithIndexedNameAndBirthday]
  3143. # Add the custom index
  3144. index = Index(fields=["name", "birthday"], name="author_name_birthday_idx")
  3145. custom_index_name = index.name
  3146. AuthorWithIndexedNameAndBirthday._meta.indexes = [index]
  3147. with connection.schema_editor() as editor:
  3148. editor.add_index(AuthorWithIndexedNameAndBirthday, index)
  3149. # Ensure the indexes exist
  3150. constraints = self.get_constraints(
  3151. AuthorWithIndexedNameAndBirthday._meta.db_table
  3152. )
  3153. self.assertIn(custom_index_name, constraints)
  3154. other_constraints = [
  3155. name
  3156. for name, details in constraints.items()
  3157. if details["columns"] == ["name", "birthday"]
  3158. and details["index"]
  3159. and name != custom_index_name
  3160. ]
  3161. self.assertEqual(len(other_constraints), 1)
  3162. # Remove index together
  3163. index_together = AuthorWithIndexedNameAndBirthday._meta.index_together
  3164. with connection.schema_editor() as editor:
  3165. editor.alter_index_together(
  3166. AuthorWithIndexedNameAndBirthday, index_together, []
  3167. )
  3168. constraints = self.get_constraints(
  3169. AuthorWithIndexedNameAndBirthday._meta.db_table
  3170. )
  3171. self.assertIn(custom_index_name, constraints)
  3172. other_constraints = [
  3173. name
  3174. for name, details in constraints.items()
  3175. if details["columns"] == ["name", "birthday"]
  3176. and details["index"]
  3177. and name != custom_index_name
  3178. ]
  3179. self.assertEqual(len(other_constraints), 0)
  3180. # Re-add index together
  3181. with connection.schema_editor() as editor:
  3182. editor.alter_index_together(
  3183. AuthorWithIndexedNameAndBirthday, [], index_together
  3184. )
  3185. constraints = self.get_constraints(
  3186. AuthorWithIndexedNameAndBirthday._meta.db_table
  3187. )
  3188. self.assertIn(custom_index_name, constraints)
  3189. other_constraints = [
  3190. name
  3191. for name, details in constraints.items()
  3192. if details["columns"] == ["name", "birthday"]
  3193. and details["index"]
  3194. and name != custom_index_name
  3195. ]
  3196. self.assertEqual(len(other_constraints), 1)
  3197. # Drop the index
  3198. with connection.schema_editor() as editor:
  3199. AuthorWithIndexedNameAndBirthday._meta.indexes = []
  3200. editor.remove_index(AuthorWithIndexedNameAndBirthday, index)
  3201. @isolate_apps("schema")
  3202. def test_db_table(self):
  3203. """
  3204. Tests renaming of the table
  3205. """
  3206. class Author(Model):
  3207. name = CharField(max_length=255)
  3208. class Meta:
  3209. app_label = "schema"
  3210. class Book(Model):
  3211. author = ForeignKey(Author, CASCADE)
  3212. class Meta:
  3213. app_label = "schema"
  3214. # Create the table and one referring it.
  3215. with connection.schema_editor() as editor:
  3216. editor.create_model(Author)
  3217. editor.create_model(Book)
  3218. # Ensure the table is there to begin with
  3219. columns = self.column_classes(Author)
  3220. self.assertEqual(
  3221. columns["name"][0],
  3222. connection.features.introspected_field_types["CharField"],
  3223. )
  3224. # Alter the table
  3225. with connection.schema_editor(
  3226. atomic=connection.features.supports_atomic_references_rename
  3227. ) as editor:
  3228. editor.alter_db_table(Author, "schema_author", "schema_otherauthor")
  3229. Author._meta.db_table = "schema_otherauthor"
  3230. columns = self.column_classes(Author)
  3231. self.assertEqual(
  3232. columns["name"][0],
  3233. connection.features.introspected_field_types["CharField"],
  3234. )
  3235. # Ensure the foreign key reference was updated
  3236. self.assertForeignKeyExists(Book, "author_id", "schema_otherauthor")
  3237. # Alter the table again
  3238. with connection.schema_editor(
  3239. atomic=connection.features.supports_atomic_references_rename
  3240. ) as editor:
  3241. editor.alter_db_table(Author, "schema_otherauthor", "schema_author")
  3242. # Ensure the table is still there
  3243. Author._meta.db_table = "schema_author"
  3244. columns = self.column_classes(Author)
  3245. self.assertEqual(
  3246. columns["name"][0],
  3247. connection.features.introspected_field_types["CharField"],
  3248. )
  3249. def test_add_remove_index(self):
  3250. """
  3251. Tests index addition and removal
  3252. """
  3253. # Create the table
  3254. with connection.schema_editor() as editor:
  3255. editor.create_model(Author)
  3256. # Ensure the table is there and has no index
  3257. self.assertNotIn("title", self.get_indexes(Author._meta.db_table))
  3258. # Add the index
  3259. index = Index(fields=["name"], name="author_title_idx")
  3260. with connection.schema_editor() as editor:
  3261. editor.add_index(Author, index)
  3262. self.assertIn("name", self.get_indexes(Author._meta.db_table))
  3263. # Drop the index
  3264. with connection.schema_editor() as editor:
  3265. editor.remove_index(Author, index)
  3266. self.assertNotIn("name", self.get_indexes(Author._meta.db_table))
  3267. def test_remove_db_index_doesnt_remove_custom_indexes(self):
  3268. """
  3269. Changing db_index to False doesn't remove indexes from Meta.indexes.
  3270. """
  3271. with connection.schema_editor() as editor:
  3272. editor.create_model(AuthorWithIndexedName)
  3273. self.local_models = [AuthorWithIndexedName]
  3274. # Ensure the table has its index
  3275. self.assertIn("name", self.get_indexes(AuthorWithIndexedName._meta.db_table))
  3276. # Add the custom index
  3277. index = Index(fields=["-name"], name="author_name_idx")
  3278. author_index_name = index.name
  3279. with connection.schema_editor() as editor:
  3280. db_index_name = editor._create_index_name(
  3281. table_name=AuthorWithIndexedName._meta.db_table,
  3282. column_names=("name",),
  3283. )
  3284. try:
  3285. AuthorWithIndexedName._meta.indexes = [index]
  3286. with connection.schema_editor() as editor:
  3287. editor.add_index(AuthorWithIndexedName, index)
  3288. old_constraints = self.get_constraints(AuthorWithIndexedName._meta.db_table)
  3289. self.assertIn(author_index_name, old_constraints)
  3290. self.assertIn(db_index_name, old_constraints)
  3291. # Change name field to db_index=False
  3292. old_field = AuthorWithIndexedName._meta.get_field("name")
  3293. new_field = CharField(max_length=255)
  3294. new_field.set_attributes_from_name("name")
  3295. with connection.schema_editor() as editor:
  3296. editor.alter_field(
  3297. AuthorWithIndexedName, old_field, new_field, strict=True
  3298. )
  3299. new_constraints = self.get_constraints(AuthorWithIndexedName._meta.db_table)
  3300. self.assertNotIn(db_index_name, new_constraints)
  3301. # The index from Meta.indexes is still in the database.
  3302. self.assertIn(author_index_name, new_constraints)
  3303. # Drop the index
  3304. with connection.schema_editor() as editor:
  3305. editor.remove_index(AuthorWithIndexedName, index)
  3306. finally:
  3307. AuthorWithIndexedName._meta.indexes = []
  3308. def test_order_index(self):
  3309. """
  3310. Indexes defined with ordering (ASC/DESC) defined on column
  3311. """
  3312. with connection.schema_editor() as editor:
  3313. editor.create_model(Author)
  3314. # The table doesn't have an index
  3315. self.assertNotIn("title", self.get_indexes(Author._meta.db_table))
  3316. index_name = "author_name_idx"
  3317. # Add the index
  3318. index = Index(fields=["name", "-weight"], name=index_name)
  3319. with connection.schema_editor() as editor:
  3320. editor.add_index(Author, index)
  3321. if connection.features.supports_index_column_ordering:
  3322. self.assertIndexOrder(Author._meta.db_table, index_name, ["ASC", "DESC"])
  3323. # Drop the index
  3324. with connection.schema_editor() as editor:
  3325. editor.remove_index(Author, index)
  3326. def test_indexes(self):
  3327. """
  3328. Tests creation/altering of indexes
  3329. """
  3330. # Create the table
  3331. with connection.schema_editor() as editor:
  3332. editor.create_model(Author)
  3333. editor.create_model(Book)
  3334. # Ensure the table is there and has the right index
  3335. self.assertIn(
  3336. "title",
  3337. self.get_indexes(Book._meta.db_table),
  3338. )
  3339. # Alter to remove the index
  3340. old_field = Book._meta.get_field("title")
  3341. new_field = CharField(max_length=100, db_index=False)
  3342. new_field.set_attributes_from_name("title")
  3343. with connection.schema_editor() as editor:
  3344. editor.alter_field(Book, old_field, new_field, strict=True)
  3345. # Ensure the table is there and has no index
  3346. self.assertNotIn(
  3347. "title",
  3348. self.get_indexes(Book._meta.db_table),
  3349. )
  3350. # Alter to re-add the index
  3351. new_field2 = Book._meta.get_field("title")
  3352. with connection.schema_editor() as editor:
  3353. editor.alter_field(Book, new_field, new_field2, strict=True)
  3354. # Ensure the table is there and has the index again
  3355. self.assertIn(
  3356. "title",
  3357. self.get_indexes(Book._meta.db_table),
  3358. )
  3359. # Add a unique column, verify that creates an implicit index
  3360. new_field3 = BookWithSlug._meta.get_field("slug")
  3361. with connection.schema_editor() as editor:
  3362. editor.add_field(Book, new_field3)
  3363. self.assertIn(
  3364. "slug",
  3365. self.get_uniques(Book._meta.db_table),
  3366. )
  3367. # Remove the unique, check the index goes with it
  3368. new_field4 = CharField(max_length=20, unique=False)
  3369. new_field4.set_attributes_from_name("slug")
  3370. with connection.schema_editor() as editor:
  3371. editor.alter_field(BookWithSlug, new_field3, new_field4, strict=True)
  3372. self.assertNotIn(
  3373. "slug",
  3374. self.get_uniques(Book._meta.db_table),
  3375. )
  3376. def test_text_field_with_db_index(self):
  3377. with connection.schema_editor() as editor:
  3378. editor.create_model(AuthorTextFieldWithIndex)
  3379. # The text_field index is present if the database supports it.
  3380. assertion = (
  3381. self.assertIn
  3382. if connection.features.supports_index_on_text_field
  3383. else self.assertNotIn
  3384. )
  3385. assertion(
  3386. "text_field", self.get_indexes(AuthorTextFieldWithIndex._meta.db_table)
  3387. )
  3388. def _index_expressions_wrappers(self):
  3389. index_expression = IndexExpression()
  3390. index_expression.set_wrapper_classes(connection)
  3391. return ", ".join(
  3392. [
  3393. wrapper_cls.__qualname__
  3394. for wrapper_cls in index_expression.wrapper_classes
  3395. ]
  3396. )
  3397. @skipUnlessDBFeature("supports_expression_indexes")
  3398. def test_func_index_multiple_wrapper_references(self):
  3399. index = Index(OrderBy(F("name").desc(), descending=True), name="name")
  3400. msg = (
  3401. "Multiple references to %s can't be used in an indexed expression."
  3402. % self._index_expressions_wrappers()
  3403. )
  3404. with connection.schema_editor() as editor:
  3405. with self.assertRaisesMessage(ValueError, msg):
  3406. editor.add_index(Author, index)
  3407. @skipUnlessDBFeature("supports_expression_indexes")
  3408. def test_func_index_invalid_topmost_expressions(self):
  3409. index = Index(Upper(F("name").desc()), name="name")
  3410. msg = (
  3411. "%s must be topmost expressions in an indexed expression."
  3412. % self._index_expressions_wrappers()
  3413. )
  3414. with connection.schema_editor() as editor:
  3415. with self.assertRaisesMessage(ValueError, msg):
  3416. editor.add_index(Author, index)
  3417. @skipUnlessDBFeature("supports_expression_indexes")
  3418. def test_func_index(self):
  3419. with connection.schema_editor() as editor:
  3420. editor.create_model(Author)
  3421. index = Index(Lower("name").desc(), name="func_lower_idx")
  3422. # Add index.
  3423. with connection.schema_editor() as editor:
  3424. editor.add_index(Author, index)
  3425. sql = index.create_sql(Author, editor)
  3426. table = Author._meta.db_table
  3427. if connection.features.supports_index_column_ordering:
  3428. self.assertIndexOrder(table, index.name, ["DESC"])
  3429. # SQL contains a database function.
  3430. self.assertIs(sql.references_column(table, "name"), True)
  3431. self.assertIn("LOWER(%s)" % editor.quote_name("name"), str(sql))
  3432. # Remove index.
  3433. with connection.schema_editor() as editor:
  3434. editor.remove_index(Author, index)
  3435. self.assertNotIn(index.name, self.get_constraints(table))
  3436. @skipUnlessDBFeature("supports_expression_indexes")
  3437. def test_func_index_f(self):
  3438. with connection.schema_editor() as editor:
  3439. editor.create_model(Tag)
  3440. index = Index("slug", F("title").desc(), name="func_f_idx")
  3441. # Add index.
  3442. with connection.schema_editor() as editor:
  3443. editor.add_index(Tag, index)
  3444. sql = index.create_sql(Tag, editor)
  3445. table = Tag._meta.db_table
  3446. self.assertIn(index.name, self.get_constraints(table))
  3447. if connection.features.supports_index_column_ordering:
  3448. self.assertIndexOrder(Tag._meta.db_table, index.name, ["ASC", "DESC"])
  3449. # SQL contains columns.
  3450. self.assertIs(sql.references_column(table, "slug"), True)
  3451. self.assertIs(sql.references_column(table, "title"), True)
  3452. # Remove index.
  3453. with connection.schema_editor() as editor:
  3454. editor.remove_index(Tag, index)
  3455. self.assertNotIn(index.name, self.get_constraints(table))
  3456. @skipUnlessDBFeature("supports_expression_indexes")
  3457. def test_func_index_lookups(self):
  3458. with connection.schema_editor() as editor:
  3459. editor.create_model(Author)
  3460. with register_lookup(CharField, Lower), register_lookup(IntegerField, Abs):
  3461. index = Index(
  3462. F("name__lower"),
  3463. F("weight__abs"),
  3464. name="func_lower_abs_lookup_idx",
  3465. )
  3466. # Add index.
  3467. with connection.schema_editor() as editor:
  3468. editor.add_index(Author, index)
  3469. sql = index.create_sql(Author, editor)
  3470. table = Author._meta.db_table
  3471. self.assertIn(index.name, self.get_constraints(table))
  3472. # SQL contains columns.
  3473. self.assertIs(sql.references_column(table, "name"), True)
  3474. self.assertIs(sql.references_column(table, "weight"), True)
  3475. # Remove index.
  3476. with connection.schema_editor() as editor:
  3477. editor.remove_index(Author, index)
  3478. self.assertNotIn(index.name, self.get_constraints(table))
  3479. @skipUnlessDBFeature("supports_expression_indexes")
  3480. def test_composite_func_index(self):
  3481. with connection.schema_editor() as editor:
  3482. editor.create_model(Author)
  3483. index = Index(Lower("name"), Upper("name"), name="func_lower_upper_idx")
  3484. # Add index.
  3485. with connection.schema_editor() as editor:
  3486. editor.add_index(Author, index)
  3487. sql = index.create_sql(Author, editor)
  3488. table = Author._meta.db_table
  3489. self.assertIn(index.name, self.get_constraints(table))
  3490. # SQL contains database functions.
  3491. self.assertIs(sql.references_column(table, "name"), True)
  3492. sql = str(sql)
  3493. self.assertIn("LOWER(%s)" % editor.quote_name("name"), sql)
  3494. self.assertIn("UPPER(%s)" % editor.quote_name("name"), sql)
  3495. self.assertLess(sql.index("LOWER"), sql.index("UPPER"))
  3496. # Remove index.
  3497. with connection.schema_editor() as editor:
  3498. editor.remove_index(Author, index)
  3499. self.assertNotIn(index.name, self.get_constraints(table))
  3500. @skipUnlessDBFeature("supports_expression_indexes")
  3501. def test_composite_func_index_field_and_expression(self):
  3502. with connection.schema_editor() as editor:
  3503. editor.create_model(Author)
  3504. editor.create_model(Book)
  3505. index = Index(
  3506. F("author").desc(),
  3507. Lower("title").asc(),
  3508. "pub_date",
  3509. name="func_f_lower_field_idx",
  3510. )
  3511. # Add index.
  3512. with connection.schema_editor() as editor:
  3513. editor.add_index(Book, index)
  3514. sql = index.create_sql(Book, editor)
  3515. table = Book._meta.db_table
  3516. constraints = self.get_constraints(table)
  3517. if connection.features.supports_index_column_ordering:
  3518. self.assertIndexOrder(table, index.name, ["DESC", "ASC", "ASC"])
  3519. self.assertEqual(len(constraints[index.name]["columns"]), 3)
  3520. self.assertEqual(constraints[index.name]["columns"][2], "pub_date")
  3521. # SQL contains database functions and columns.
  3522. self.assertIs(sql.references_column(table, "author_id"), True)
  3523. self.assertIs(sql.references_column(table, "title"), True)
  3524. self.assertIs(sql.references_column(table, "pub_date"), True)
  3525. self.assertIn("LOWER(%s)" % editor.quote_name("title"), str(sql))
  3526. # Remove index.
  3527. with connection.schema_editor() as editor:
  3528. editor.remove_index(Book, index)
  3529. self.assertNotIn(index.name, self.get_constraints(table))
  3530. @skipUnlessDBFeature("supports_expression_indexes")
  3531. @isolate_apps("schema")
  3532. def test_func_index_f_decimalfield(self):
  3533. class Node(Model):
  3534. value = DecimalField(max_digits=5, decimal_places=2)
  3535. class Meta:
  3536. app_label = "schema"
  3537. with connection.schema_editor() as editor:
  3538. editor.create_model(Node)
  3539. index = Index(F("value"), name="func_f_decimalfield_idx")
  3540. # Add index.
  3541. with connection.schema_editor() as editor:
  3542. editor.add_index(Node, index)
  3543. sql = index.create_sql(Node, editor)
  3544. table = Node._meta.db_table
  3545. self.assertIn(index.name, self.get_constraints(table))
  3546. self.assertIs(sql.references_column(table, "value"), True)
  3547. # SQL doesn't contain casting.
  3548. self.assertNotIn("CAST", str(sql))
  3549. # Remove index.
  3550. with connection.schema_editor() as editor:
  3551. editor.remove_index(Node, index)
  3552. self.assertNotIn(index.name, self.get_constraints(table))
  3553. @skipUnlessDBFeature("supports_expression_indexes")
  3554. def test_func_index_cast(self):
  3555. with connection.schema_editor() as editor:
  3556. editor.create_model(Author)
  3557. index = Index(Cast("weight", FloatField()), name="func_cast_idx")
  3558. # Add index.
  3559. with connection.schema_editor() as editor:
  3560. editor.add_index(Author, index)
  3561. sql = index.create_sql(Author, editor)
  3562. table = Author._meta.db_table
  3563. self.assertIn(index.name, self.get_constraints(table))
  3564. self.assertIs(sql.references_column(table, "weight"), True)
  3565. # Remove index.
  3566. with connection.schema_editor() as editor:
  3567. editor.remove_index(Author, index)
  3568. self.assertNotIn(index.name, self.get_constraints(table))
  3569. @skipUnlessDBFeature("supports_expression_indexes")
  3570. def test_func_index_collate(self):
  3571. collation = connection.features.test_collations.get("non_default")
  3572. if not collation:
  3573. self.skipTest("This backend does not support case-insensitive collations.")
  3574. with connection.schema_editor() as editor:
  3575. editor.create_model(Author)
  3576. editor.create_model(BookWithSlug)
  3577. index = Index(
  3578. Collate(F("title"), collation=collation).desc(),
  3579. Collate("slug", collation=collation),
  3580. name="func_collate_idx",
  3581. )
  3582. # Add index.
  3583. with connection.schema_editor() as editor:
  3584. editor.add_index(BookWithSlug, index)
  3585. sql = index.create_sql(BookWithSlug, editor)
  3586. table = Book._meta.db_table
  3587. self.assertIn(index.name, self.get_constraints(table))
  3588. if connection.features.supports_index_column_ordering:
  3589. self.assertIndexOrder(table, index.name, ["DESC", "ASC"])
  3590. # SQL contains columns and a collation.
  3591. self.assertIs(sql.references_column(table, "title"), True)
  3592. self.assertIs(sql.references_column(table, "slug"), True)
  3593. self.assertIn("COLLATE %s" % editor.quote_name(collation), str(sql))
  3594. # Remove index.
  3595. with connection.schema_editor() as editor:
  3596. editor.remove_index(Book, index)
  3597. self.assertNotIn(index.name, self.get_constraints(table))
  3598. @skipUnlessDBFeature("supports_expression_indexes")
  3599. @skipIfDBFeature("collate_as_index_expression")
  3600. def test_func_index_collate_f_ordered(self):
  3601. collation = connection.features.test_collations.get("non_default")
  3602. if not collation:
  3603. self.skipTest("This backend does not support case-insensitive collations.")
  3604. with connection.schema_editor() as editor:
  3605. editor.create_model(Author)
  3606. index = Index(
  3607. Collate(F("name").desc(), collation=collation),
  3608. name="func_collate_f_desc_idx",
  3609. )
  3610. # Add index.
  3611. with connection.schema_editor() as editor:
  3612. editor.add_index(Author, index)
  3613. sql = index.create_sql(Author, editor)
  3614. table = Author._meta.db_table
  3615. self.assertIn(index.name, self.get_constraints(table))
  3616. if connection.features.supports_index_column_ordering:
  3617. self.assertIndexOrder(table, index.name, ["DESC"])
  3618. # SQL contains columns and a collation.
  3619. self.assertIs(sql.references_column(table, "name"), True)
  3620. self.assertIn("COLLATE %s" % editor.quote_name(collation), str(sql))
  3621. # Remove index.
  3622. with connection.schema_editor() as editor:
  3623. editor.remove_index(Author, index)
  3624. self.assertNotIn(index.name, self.get_constraints(table))
  3625. @skipUnlessDBFeature("supports_expression_indexes")
  3626. def test_func_index_calc(self):
  3627. with connection.schema_editor() as editor:
  3628. editor.create_model(Author)
  3629. index = Index(F("height") / (F("weight") + Value(5)), name="func_calc_idx")
  3630. # Add index.
  3631. with connection.schema_editor() as editor:
  3632. editor.add_index(Author, index)
  3633. sql = index.create_sql(Author, editor)
  3634. table = Author._meta.db_table
  3635. self.assertIn(index.name, self.get_constraints(table))
  3636. # SQL contains columns and expressions.
  3637. self.assertIs(sql.references_column(table, "height"), True)
  3638. self.assertIs(sql.references_column(table, "weight"), True)
  3639. sql = str(sql)
  3640. self.assertIs(
  3641. sql.index(editor.quote_name("height"))
  3642. < sql.index("/")
  3643. < sql.index(editor.quote_name("weight"))
  3644. < sql.index("+")
  3645. < sql.index("5"),
  3646. True,
  3647. )
  3648. # Remove index.
  3649. with connection.schema_editor() as editor:
  3650. editor.remove_index(Author, index)
  3651. self.assertNotIn(index.name, self.get_constraints(table))
  3652. @skipUnlessDBFeature("supports_expression_indexes", "supports_json_field")
  3653. @isolate_apps("schema")
  3654. def test_func_index_json_key_transform(self):
  3655. class JSONModel(Model):
  3656. field = JSONField()
  3657. class Meta:
  3658. app_label = "schema"
  3659. with connection.schema_editor() as editor:
  3660. editor.create_model(JSONModel)
  3661. self.isolated_local_models = [JSONModel]
  3662. index = Index("field__some_key", name="func_json_key_idx")
  3663. with connection.schema_editor() as editor:
  3664. editor.add_index(JSONModel, index)
  3665. sql = index.create_sql(JSONModel, editor)
  3666. table = JSONModel._meta.db_table
  3667. self.assertIn(index.name, self.get_constraints(table))
  3668. self.assertIs(sql.references_column(table, "field"), True)
  3669. with connection.schema_editor() as editor:
  3670. editor.remove_index(JSONModel, index)
  3671. self.assertNotIn(index.name, self.get_constraints(table))
  3672. @skipUnlessDBFeature("supports_expression_indexes", "supports_json_field")
  3673. @isolate_apps("schema")
  3674. def test_func_index_json_key_transform_cast(self):
  3675. class JSONModel(Model):
  3676. field = JSONField()
  3677. class Meta:
  3678. app_label = "schema"
  3679. with connection.schema_editor() as editor:
  3680. editor.create_model(JSONModel)
  3681. self.isolated_local_models = [JSONModel]
  3682. index = Index(
  3683. Cast(KeyTextTransform("some_key", "field"), IntegerField()),
  3684. name="func_json_key_cast_idx",
  3685. )
  3686. with connection.schema_editor() as editor:
  3687. editor.add_index(JSONModel, index)
  3688. sql = index.create_sql(JSONModel, editor)
  3689. table = JSONModel._meta.db_table
  3690. self.assertIn(index.name, self.get_constraints(table))
  3691. self.assertIs(sql.references_column(table, "field"), True)
  3692. with connection.schema_editor() as editor:
  3693. editor.remove_index(JSONModel, index)
  3694. self.assertNotIn(index.name, self.get_constraints(table))
  3695. @skipIfDBFeature("supports_expression_indexes")
  3696. def test_func_index_unsupported(self):
  3697. # Index is ignored on databases that don't support indexes on
  3698. # expressions.
  3699. with connection.schema_editor() as editor:
  3700. editor.create_model(Author)
  3701. index = Index(F("name"), name="random_idx")
  3702. with connection.schema_editor() as editor, self.assertNumQueries(0):
  3703. self.assertIsNone(editor.add_index(Author, index))
  3704. self.assertIsNone(editor.remove_index(Author, index))
  3705. @skipUnlessDBFeature("supports_expression_indexes")
  3706. def test_func_index_nonexistent_field(self):
  3707. index = Index(Lower("nonexistent"), name="func_nonexistent_idx")
  3708. msg = (
  3709. "Cannot resolve keyword 'nonexistent' into field. Choices are: "
  3710. "height, id, name, uuid, weight"
  3711. )
  3712. with self.assertRaisesMessage(FieldError, msg):
  3713. with connection.schema_editor() as editor:
  3714. editor.add_index(Author, index)
  3715. @skipUnlessDBFeature("supports_expression_indexes")
  3716. def test_func_index_nondeterministic(self):
  3717. with connection.schema_editor() as editor:
  3718. editor.create_model(Author)
  3719. index = Index(Random(), name="func_random_idx")
  3720. with connection.schema_editor() as editor:
  3721. with self.assertRaises(DatabaseError):
  3722. editor.add_index(Author, index)
  3723. def test_primary_key(self):
  3724. """
  3725. Tests altering of the primary key
  3726. """
  3727. # Create the table
  3728. with connection.schema_editor() as editor:
  3729. editor.create_model(Tag)
  3730. # Ensure the table is there and has the right PK
  3731. self.assertEqual(self.get_primary_key(Tag._meta.db_table), "id")
  3732. # Alter to change the PK
  3733. id_field = Tag._meta.get_field("id")
  3734. old_field = Tag._meta.get_field("slug")
  3735. new_field = SlugField(primary_key=True)
  3736. new_field.set_attributes_from_name("slug")
  3737. new_field.model = Tag
  3738. with connection.schema_editor() as editor:
  3739. editor.remove_field(Tag, id_field)
  3740. editor.alter_field(Tag, old_field, new_field)
  3741. # Ensure the PK changed
  3742. self.assertNotIn(
  3743. "id",
  3744. self.get_indexes(Tag._meta.db_table),
  3745. )
  3746. self.assertEqual(self.get_primary_key(Tag._meta.db_table), "slug")
  3747. def test_alter_primary_key_the_same_name(self):
  3748. with connection.schema_editor() as editor:
  3749. editor.create_model(Thing)
  3750. old_field = Thing._meta.get_field("when")
  3751. new_field = CharField(max_length=2, primary_key=True)
  3752. new_field.set_attributes_from_name("when")
  3753. new_field.model = Thing
  3754. with connection.schema_editor() as editor:
  3755. editor.alter_field(Thing, old_field, new_field, strict=True)
  3756. self.assertEqual(self.get_primary_key(Thing._meta.db_table), "when")
  3757. with connection.schema_editor() as editor:
  3758. editor.alter_field(Thing, new_field, old_field, strict=True)
  3759. self.assertEqual(self.get_primary_key(Thing._meta.db_table), "when")
  3760. def test_context_manager_exit(self):
  3761. """
  3762. Ensures transaction is correctly closed when an error occurs
  3763. inside a SchemaEditor context.
  3764. """
  3765. class SomeError(Exception):
  3766. pass
  3767. try:
  3768. with connection.schema_editor():
  3769. raise SomeError
  3770. except SomeError:
  3771. self.assertFalse(connection.in_atomic_block)
  3772. @skipIfDBFeature("can_rollback_ddl")
  3773. def test_unsupported_transactional_ddl_disallowed(self):
  3774. message = (
  3775. "Executing DDL statements while in a transaction on databases "
  3776. "that can't perform a rollback is prohibited."
  3777. )
  3778. with atomic(), connection.schema_editor() as editor:
  3779. with self.assertRaisesMessage(TransactionManagementError, message):
  3780. editor.execute(
  3781. editor.sql_create_table % {"table": "foo", "definition": ""}
  3782. )
  3783. @skipUnlessDBFeature("supports_foreign_keys", "indexes_foreign_keys")
  3784. def test_foreign_key_index_long_names_regression(self):
  3785. """
  3786. Regression test for #21497.
  3787. Only affects databases that supports foreign keys.
  3788. """
  3789. # Create the table
  3790. with connection.schema_editor() as editor:
  3791. editor.create_model(AuthorWithEvenLongerName)
  3792. editor.create_model(BookWithLongName)
  3793. # Find the properly shortened column name
  3794. column_name = connection.ops.quote_name(
  3795. "author_foreign_key_with_really_long_field_name_id"
  3796. )
  3797. column_name = column_name[1:-1].lower() # unquote, and, for Oracle, un-upcase
  3798. # Ensure the table is there and has an index on the column
  3799. self.assertIn(
  3800. column_name,
  3801. self.get_indexes(BookWithLongName._meta.db_table),
  3802. )
  3803. @skipUnlessDBFeature("supports_foreign_keys")
  3804. def test_add_foreign_key_long_names(self):
  3805. """
  3806. Regression test for #23009.
  3807. Only affects databases that supports foreign keys.
  3808. """
  3809. # Create the initial tables
  3810. with connection.schema_editor() as editor:
  3811. editor.create_model(AuthorWithEvenLongerName)
  3812. editor.create_model(BookWithLongName)
  3813. # Add a second FK, this would fail due to long ref name before the fix
  3814. new_field = ForeignKey(
  3815. AuthorWithEvenLongerName, CASCADE, related_name="something"
  3816. )
  3817. new_field.set_attributes_from_name(
  3818. "author_other_really_long_named_i_mean_so_long_fk"
  3819. )
  3820. with connection.schema_editor() as editor:
  3821. editor.add_field(BookWithLongName, new_field)
  3822. @isolate_apps("schema")
  3823. @skipUnlessDBFeature("supports_foreign_keys")
  3824. def test_add_foreign_key_quoted_db_table(self):
  3825. class Author(Model):
  3826. class Meta:
  3827. db_table = '"table_author_double_quoted"'
  3828. app_label = "schema"
  3829. class Book(Model):
  3830. author = ForeignKey(Author, CASCADE)
  3831. class Meta:
  3832. app_label = "schema"
  3833. with connection.schema_editor() as editor:
  3834. editor.create_model(Author)
  3835. editor.create_model(Book)
  3836. self.isolated_local_models = [Author]
  3837. if connection.vendor == "mysql":
  3838. self.assertForeignKeyExists(
  3839. Book, "author_id", '"table_author_double_quoted"'
  3840. )
  3841. else:
  3842. self.assertForeignKeyExists(Book, "author_id", "table_author_double_quoted")
  3843. def test_add_foreign_object(self):
  3844. with connection.schema_editor() as editor:
  3845. editor.create_model(BookForeignObj)
  3846. self.local_models = [BookForeignObj]
  3847. new_field = ForeignObject(
  3848. Author, on_delete=CASCADE, from_fields=["author_id"], to_fields=["id"]
  3849. )
  3850. new_field.set_attributes_from_name("author")
  3851. with connection.schema_editor() as editor:
  3852. editor.add_field(BookForeignObj, new_field)
  3853. def test_creation_deletion_reserved_names(self):
  3854. """
  3855. Tries creating a model's table, and then deleting it when it has a
  3856. SQL reserved name.
  3857. """
  3858. # Create the table
  3859. with connection.schema_editor() as editor:
  3860. try:
  3861. editor.create_model(Thing)
  3862. except OperationalError as e:
  3863. self.fail(
  3864. "Errors when applying initial migration for a model "
  3865. "with a table named after an SQL reserved word: %s" % e
  3866. )
  3867. # The table is there
  3868. list(Thing.objects.all())
  3869. # Clean up that table
  3870. with connection.schema_editor() as editor:
  3871. editor.delete_model(Thing)
  3872. # The table is gone
  3873. with self.assertRaises(DatabaseError):
  3874. list(Thing.objects.all())
  3875. def test_remove_constraints_capital_letters(self):
  3876. """
  3877. #23065 - Constraint names must be quoted if they contain capital letters.
  3878. """
  3879. def get_field(*args, field_class=IntegerField, **kwargs):
  3880. kwargs["db_column"] = "CamelCase"
  3881. field = field_class(*args, **kwargs)
  3882. field.set_attributes_from_name("CamelCase")
  3883. return field
  3884. model = Author
  3885. field = get_field()
  3886. table = model._meta.db_table
  3887. column = field.column
  3888. identifier_converter = connection.introspection.identifier_converter
  3889. with connection.schema_editor() as editor:
  3890. editor.create_model(model)
  3891. editor.add_field(model, field)
  3892. constraint_name = "CamelCaseIndex"
  3893. expected_constraint_name = identifier_converter(constraint_name)
  3894. editor.execute(
  3895. editor.sql_create_index
  3896. % {
  3897. "table": editor.quote_name(table),
  3898. "name": editor.quote_name(constraint_name),
  3899. "using": "",
  3900. "columns": editor.quote_name(column),
  3901. "extra": "",
  3902. "condition": "",
  3903. "include": "",
  3904. }
  3905. )
  3906. self.assertIn(
  3907. expected_constraint_name, self.get_constraints(model._meta.db_table)
  3908. )
  3909. editor.alter_field(model, get_field(db_index=True), field, strict=True)
  3910. self.assertNotIn(
  3911. expected_constraint_name, self.get_constraints(model._meta.db_table)
  3912. )
  3913. constraint_name = "CamelCaseUniqConstraint"
  3914. expected_constraint_name = identifier_converter(constraint_name)
  3915. editor.execute(editor._create_unique_sql(model, [field], constraint_name))
  3916. self.assertIn(
  3917. expected_constraint_name, self.get_constraints(model._meta.db_table)
  3918. )
  3919. editor.alter_field(model, get_field(unique=True), field, strict=True)
  3920. self.assertNotIn(
  3921. expected_constraint_name, self.get_constraints(model._meta.db_table)
  3922. )
  3923. if editor.sql_create_fk and connection.features.can_introspect_foreign_keys:
  3924. constraint_name = "CamelCaseFKConstraint"
  3925. expected_constraint_name = identifier_converter(constraint_name)
  3926. editor.execute(
  3927. editor.sql_create_fk
  3928. % {
  3929. "table": editor.quote_name(table),
  3930. "name": editor.quote_name(constraint_name),
  3931. "column": editor.quote_name(column),
  3932. "to_table": editor.quote_name(table),
  3933. "to_column": editor.quote_name(model._meta.auto_field.column),
  3934. "deferrable": connection.ops.deferrable_sql(),
  3935. }
  3936. )
  3937. self.assertIn(
  3938. expected_constraint_name, self.get_constraints(model._meta.db_table)
  3939. )
  3940. editor.alter_field(
  3941. model,
  3942. get_field(Author, CASCADE, field_class=ForeignKey),
  3943. field,
  3944. strict=True,
  3945. )
  3946. self.assertNotIn(
  3947. expected_constraint_name, self.get_constraints(model._meta.db_table)
  3948. )
  3949. def test_add_field_use_effective_default(self):
  3950. """
  3951. #23987 - effective_default() should be used as the field default when
  3952. adding a new field.
  3953. """
  3954. # Create the table
  3955. with connection.schema_editor() as editor:
  3956. editor.create_model(Author)
  3957. # Ensure there's no surname field
  3958. columns = self.column_classes(Author)
  3959. self.assertNotIn("surname", columns)
  3960. # Create a row
  3961. Author.objects.create(name="Anonymous1")
  3962. # Add new CharField to ensure default will be used from effective_default
  3963. new_field = CharField(max_length=15, blank=True)
  3964. new_field.set_attributes_from_name("surname")
  3965. with connection.schema_editor() as editor:
  3966. editor.add_field(Author, new_field)
  3967. # Ensure field was added with the right default
  3968. with connection.cursor() as cursor:
  3969. cursor.execute("SELECT surname FROM schema_author;")
  3970. item = cursor.fetchall()[0]
  3971. self.assertEqual(
  3972. item[0],
  3973. None if connection.features.interprets_empty_strings_as_nulls else "",
  3974. )
  3975. def test_add_field_default_dropped(self):
  3976. # Create the table
  3977. with connection.schema_editor() as editor:
  3978. editor.create_model(Author)
  3979. # Ensure there's no surname field
  3980. columns = self.column_classes(Author)
  3981. self.assertNotIn("surname", columns)
  3982. # Create a row
  3983. Author.objects.create(name="Anonymous1")
  3984. # Add new CharField with a default
  3985. new_field = CharField(max_length=15, blank=True, default="surname default")
  3986. new_field.set_attributes_from_name("surname")
  3987. with connection.schema_editor() as editor:
  3988. editor.add_field(Author, new_field)
  3989. # Ensure field was added with the right default
  3990. with connection.cursor() as cursor:
  3991. cursor.execute("SELECT surname FROM schema_author;")
  3992. item = cursor.fetchall()[0]
  3993. self.assertEqual(item[0], "surname default")
  3994. # And that the default is no longer set in the database.
  3995. field = next(
  3996. f
  3997. for f in connection.introspection.get_table_description(
  3998. cursor, "schema_author"
  3999. )
  4000. if f.name == "surname"
  4001. )
  4002. if connection.features.can_introspect_default:
  4003. self.assertIsNone(field.default)
  4004. def test_add_field_default_nullable(self):
  4005. with connection.schema_editor() as editor:
  4006. editor.create_model(Author)
  4007. # Add new nullable CharField with a default.
  4008. new_field = CharField(max_length=15, blank=True, null=True, default="surname")
  4009. new_field.set_attributes_from_name("surname")
  4010. with connection.schema_editor() as editor:
  4011. editor.add_field(Author, new_field)
  4012. Author.objects.create(name="Anonymous1")
  4013. with connection.cursor() as cursor:
  4014. cursor.execute("SELECT surname FROM schema_author;")
  4015. item = cursor.fetchall()[0]
  4016. self.assertIsNone(item[0])
  4017. field = next(
  4018. f
  4019. for f in connection.introspection.get_table_description(
  4020. cursor,
  4021. "schema_author",
  4022. )
  4023. if f.name == "surname"
  4024. )
  4025. # Field is still nullable.
  4026. self.assertTrue(field.null_ok)
  4027. # The database default is no longer set.
  4028. if connection.features.can_introspect_default:
  4029. self.assertIn(field.default, ["NULL", None])
  4030. def test_add_textfield_default_nullable(self):
  4031. with connection.schema_editor() as editor:
  4032. editor.create_model(Author)
  4033. # Add new nullable TextField with a default.
  4034. new_field = TextField(blank=True, null=True, default="text")
  4035. new_field.set_attributes_from_name("description")
  4036. with connection.schema_editor() as editor:
  4037. editor.add_field(Author, new_field)
  4038. Author.objects.create(name="Anonymous1")
  4039. with connection.cursor() as cursor:
  4040. cursor.execute("SELECT description FROM schema_author;")
  4041. item = cursor.fetchall()[0]
  4042. self.assertIsNone(item[0])
  4043. field = next(
  4044. f
  4045. for f in connection.introspection.get_table_description(
  4046. cursor,
  4047. "schema_author",
  4048. )
  4049. if f.name == "description"
  4050. )
  4051. # Field is still nullable.
  4052. self.assertTrue(field.null_ok)
  4053. # The database default is no longer set.
  4054. if connection.features.can_introspect_default:
  4055. self.assertIn(field.default, ["NULL", None])
  4056. def test_alter_field_default_dropped(self):
  4057. # Create the table
  4058. with connection.schema_editor() as editor:
  4059. editor.create_model(Author)
  4060. # Create a row
  4061. Author.objects.create(name="Anonymous1")
  4062. self.assertIsNone(Author.objects.get().height)
  4063. old_field = Author._meta.get_field("height")
  4064. # The default from the new field is used in updating existing rows.
  4065. new_field = IntegerField(blank=True, default=42)
  4066. new_field.set_attributes_from_name("height")
  4067. with connection.schema_editor() as editor:
  4068. editor.alter_field(Author, old_field, new_field, strict=True)
  4069. self.assertEqual(Author.objects.get().height, 42)
  4070. # The database default should be removed.
  4071. with connection.cursor() as cursor:
  4072. field = next(
  4073. f
  4074. for f in connection.introspection.get_table_description(
  4075. cursor, "schema_author"
  4076. )
  4077. if f.name == "height"
  4078. )
  4079. if connection.features.can_introspect_default:
  4080. self.assertIsNone(field.default)
  4081. def test_alter_field_default_doesnt_perform_queries(self):
  4082. """
  4083. No queries are performed if a field default changes and the field's
  4084. not changing from null to non-null.
  4085. """
  4086. with connection.schema_editor() as editor:
  4087. editor.create_model(AuthorWithDefaultHeight)
  4088. old_field = AuthorWithDefaultHeight._meta.get_field("height")
  4089. new_default = old_field.default * 2
  4090. new_field = PositiveIntegerField(null=True, blank=True, default=new_default)
  4091. new_field.set_attributes_from_name("height")
  4092. with connection.schema_editor() as editor, self.assertNumQueries(0):
  4093. editor.alter_field(
  4094. AuthorWithDefaultHeight, old_field, new_field, strict=True
  4095. )
  4096. @skipUnlessDBFeature("supports_foreign_keys")
  4097. def test_alter_field_fk_attributes_noop(self):
  4098. """
  4099. No queries are performed when changing field attributes that don't
  4100. affect the schema.
  4101. """
  4102. with connection.schema_editor() as editor:
  4103. editor.create_model(Author)
  4104. editor.create_model(Book)
  4105. old_field = Book._meta.get_field("author")
  4106. new_field = ForeignKey(
  4107. Author,
  4108. blank=True,
  4109. editable=False,
  4110. error_messages={"invalid": "error message"},
  4111. help_text="help text",
  4112. limit_choices_to={"limit": "choice"},
  4113. on_delete=PROTECT,
  4114. related_name="related_name",
  4115. related_query_name="related_query_name",
  4116. validators=[lambda x: x],
  4117. verbose_name="verbose name",
  4118. )
  4119. new_field.set_attributes_from_name("author")
  4120. with connection.schema_editor() as editor, self.assertNumQueries(0):
  4121. editor.alter_field(Book, old_field, new_field, strict=True)
  4122. with connection.schema_editor() as editor, self.assertNumQueries(0):
  4123. editor.alter_field(Book, new_field, old_field, strict=True)
  4124. def test_alter_field_choices_noop(self):
  4125. with connection.schema_editor() as editor:
  4126. editor.create_model(Author)
  4127. old_field = Author._meta.get_field("name")
  4128. new_field = CharField(
  4129. choices=(("Jane", "Jane"), ("Joe", "Joe")),
  4130. max_length=255,
  4131. )
  4132. new_field.set_attributes_from_name("name")
  4133. with connection.schema_editor() as editor, self.assertNumQueries(0):
  4134. editor.alter_field(Author, old_field, new_field, strict=True)
  4135. with connection.schema_editor() as editor, self.assertNumQueries(0):
  4136. editor.alter_field(Author, new_field, old_field, strict=True)
  4137. def test_add_textfield_unhashable_default(self):
  4138. # Create the table
  4139. with connection.schema_editor() as editor:
  4140. editor.create_model(Author)
  4141. # Create a row
  4142. Author.objects.create(name="Anonymous1")
  4143. # Create a field that has an unhashable default
  4144. new_field = TextField(default={})
  4145. new_field.set_attributes_from_name("info")
  4146. with connection.schema_editor() as editor:
  4147. editor.add_field(Author, new_field)
  4148. @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific")
  4149. def test_add_indexed_charfield(self):
  4150. field = CharField(max_length=255, db_index=True)
  4151. field.set_attributes_from_name("nom_de_plume")
  4152. with connection.schema_editor() as editor:
  4153. editor.create_model(Author)
  4154. editor.add_field(Author, field)
  4155. # Should create two indexes; one for like operator.
  4156. self.assertEqual(
  4157. self.get_constraints_for_column(Author, "nom_de_plume"),
  4158. [
  4159. "schema_author_nom_de_plume_7570a851",
  4160. "schema_author_nom_de_plume_7570a851_like",
  4161. ],
  4162. )
  4163. @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific")
  4164. def test_add_unique_charfield(self):
  4165. field = CharField(max_length=255, unique=True)
  4166. field.set_attributes_from_name("nom_de_plume")
  4167. with connection.schema_editor() as editor:
  4168. editor.create_model(Author)
  4169. editor.add_field(Author, field)
  4170. # Should create two indexes; one for like operator.
  4171. self.assertEqual(
  4172. self.get_constraints_for_column(Author, "nom_de_plume"),
  4173. [
  4174. "schema_author_nom_de_plume_7570a851_like",
  4175. "schema_author_nom_de_plume_key",
  4176. ],
  4177. )
  4178. @skipUnlessDBFeature("supports_comments")
  4179. def test_add_db_comment_charfield(self):
  4180. comment = "Custom comment"
  4181. field = CharField(max_length=255, db_comment=comment)
  4182. field.set_attributes_from_name("name_with_comment")
  4183. with connection.schema_editor() as editor:
  4184. editor.create_model(Author)
  4185. editor.add_field(Author, field)
  4186. self.assertEqual(
  4187. self.get_column_comment(Author._meta.db_table, "name_with_comment"),
  4188. comment,
  4189. )
  4190. @skipUnlessDBFeature("supports_comments")
  4191. def test_add_db_comment_and_default_charfield(self):
  4192. comment = "Custom comment with default"
  4193. field = CharField(max_length=255, default="Joe Doe", db_comment=comment)
  4194. field.set_attributes_from_name("name_with_comment_default")
  4195. with connection.schema_editor() as editor:
  4196. editor.create_model(Author)
  4197. Author.objects.create(name="Before adding a new field")
  4198. editor.add_field(Author, field)
  4199. self.assertEqual(
  4200. self.get_column_comment(Author._meta.db_table, "name_with_comment_default"),
  4201. comment,
  4202. )
  4203. with connection.cursor() as cursor:
  4204. cursor.execute(
  4205. f"SELECT name_with_comment_default FROM {Author._meta.db_table};"
  4206. )
  4207. for row in cursor.fetchall():
  4208. self.assertEqual(row[0], "Joe Doe")
  4209. @skipUnlessDBFeature("supports_comments")
  4210. def test_alter_db_comment(self):
  4211. with connection.schema_editor() as editor:
  4212. editor.create_model(Author)
  4213. # Add comment.
  4214. old_field = Author._meta.get_field("name")
  4215. new_field = CharField(max_length=255, db_comment="Custom comment")
  4216. new_field.set_attributes_from_name("name")
  4217. with connection.schema_editor() as editor:
  4218. editor.alter_field(Author, old_field, new_field, strict=True)
  4219. self.assertEqual(
  4220. self.get_column_comment(Author._meta.db_table, "name"),
  4221. "Custom comment",
  4222. )
  4223. # Alter comment.
  4224. old_field = new_field
  4225. new_field = CharField(max_length=255, db_comment="New custom comment")
  4226. new_field.set_attributes_from_name("name")
  4227. with connection.schema_editor() as editor:
  4228. editor.alter_field(Author, old_field, new_field, strict=True)
  4229. self.assertEqual(
  4230. self.get_column_comment(Author._meta.db_table, "name"),
  4231. "New custom comment",
  4232. )
  4233. # Remove comment.
  4234. old_field = new_field
  4235. new_field = CharField(max_length=255)
  4236. new_field.set_attributes_from_name("name")
  4237. with connection.schema_editor() as editor:
  4238. editor.alter_field(Author, old_field, new_field, strict=True)
  4239. self.assertIn(
  4240. self.get_column_comment(Author._meta.db_table, "name"),
  4241. [None, ""],
  4242. )
  4243. @skipUnlessDBFeature("supports_comments", "supports_foreign_keys")
  4244. def test_alter_db_comment_foreign_key(self):
  4245. with connection.schema_editor() as editor:
  4246. editor.create_model(Author)
  4247. editor.create_model(Book)
  4248. comment = "FK custom comment"
  4249. old_field = Book._meta.get_field("author")
  4250. new_field = ForeignKey(Author, CASCADE, db_comment=comment)
  4251. new_field.set_attributes_from_name("author")
  4252. with connection.schema_editor() as editor:
  4253. editor.alter_field(Book, old_field, new_field, strict=True)
  4254. self.assertEqual(
  4255. self.get_column_comment(Book._meta.db_table, "author_id"),
  4256. comment,
  4257. )
  4258. @skipUnlessDBFeature("supports_comments")
  4259. def test_alter_field_type_preserve_comment(self):
  4260. with connection.schema_editor() as editor:
  4261. editor.create_model(Author)
  4262. comment = "This is the name."
  4263. old_field = Author._meta.get_field("name")
  4264. new_field = CharField(max_length=255, db_comment=comment)
  4265. new_field.set_attributes_from_name("name")
  4266. new_field.model = Author
  4267. with connection.schema_editor() as editor:
  4268. editor.alter_field(Author, old_field, new_field, strict=True)
  4269. self.assertEqual(
  4270. self.get_column_comment(Author._meta.db_table, "name"),
  4271. comment,
  4272. )
  4273. # Changing a field type should preserve the comment.
  4274. old_field = new_field
  4275. new_field = CharField(max_length=511, db_comment=comment)
  4276. new_field.set_attributes_from_name("name")
  4277. new_field.model = Author
  4278. with connection.schema_editor() as editor:
  4279. editor.alter_field(Author, new_field, old_field, strict=True)
  4280. # Comment is preserved.
  4281. self.assertEqual(
  4282. self.get_column_comment(Author._meta.db_table, "name"),
  4283. comment,
  4284. )
  4285. @isolate_apps("schema")
  4286. @skipUnlessDBFeature("supports_comments")
  4287. def test_db_comment_table(self):
  4288. class ModelWithDbTableComment(Model):
  4289. class Meta:
  4290. app_label = "schema"
  4291. db_table_comment = "Custom table comment"
  4292. with connection.schema_editor() as editor:
  4293. editor.create_model(ModelWithDbTableComment)
  4294. self.isolated_local_models = [ModelWithDbTableComment]
  4295. self.assertEqual(
  4296. self.get_table_comment(ModelWithDbTableComment._meta.db_table),
  4297. "Custom table comment",
  4298. )
  4299. # Alter table comment.
  4300. old_db_table_comment = ModelWithDbTableComment._meta.db_table_comment
  4301. with connection.schema_editor() as editor:
  4302. editor.alter_db_table_comment(
  4303. ModelWithDbTableComment, old_db_table_comment, "New table comment"
  4304. )
  4305. self.assertEqual(
  4306. self.get_table_comment(ModelWithDbTableComment._meta.db_table),
  4307. "New table comment",
  4308. )
  4309. # Remove table comment.
  4310. old_db_table_comment = ModelWithDbTableComment._meta.db_table_comment
  4311. with connection.schema_editor() as editor:
  4312. editor.alter_db_table_comment(
  4313. ModelWithDbTableComment, old_db_table_comment, None
  4314. )
  4315. self.assertIn(
  4316. self.get_table_comment(ModelWithDbTableComment._meta.db_table),
  4317. [None, ""],
  4318. )
  4319. @isolate_apps("schema")
  4320. @skipUnlessDBFeature("supports_comments", "supports_foreign_keys")
  4321. def test_db_comments_from_abstract_model(self):
  4322. class AbstractModelWithDbComments(Model):
  4323. name = CharField(
  4324. max_length=255, db_comment="Custom comment", null=True, blank=True
  4325. )
  4326. class Meta:
  4327. app_label = "schema"
  4328. abstract = True
  4329. db_table_comment = "Custom table comment"
  4330. class ModelWithDbComments(AbstractModelWithDbComments):
  4331. pass
  4332. with connection.schema_editor() as editor:
  4333. editor.create_model(ModelWithDbComments)
  4334. self.isolated_local_models = [ModelWithDbComments]
  4335. self.assertEqual(
  4336. self.get_column_comment(ModelWithDbComments._meta.db_table, "name"),
  4337. "Custom comment",
  4338. )
  4339. self.assertEqual(
  4340. self.get_table_comment(ModelWithDbComments._meta.db_table),
  4341. "Custom table comment",
  4342. )
  4343. @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific")
  4344. def test_alter_field_add_index_to_charfield(self):
  4345. # Create the table and verify no initial indexes.
  4346. with connection.schema_editor() as editor:
  4347. editor.create_model(Author)
  4348. self.assertEqual(self.get_constraints_for_column(Author, "name"), [])
  4349. # Alter to add db_index=True and create 2 indexes.
  4350. old_field = Author._meta.get_field("name")
  4351. new_field = CharField(max_length=255, db_index=True)
  4352. new_field.set_attributes_from_name("name")
  4353. with connection.schema_editor() as editor:
  4354. editor.alter_field(Author, old_field, new_field, strict=True)
  4355. self.assertEqual(
  4356. self.get_constraints_for_column(Author, "name"),
  4357. ["schema_author_name_1fbc5617", "schema_author_name_1fbc5617_like"],
  4358. )
  4359. # Remove db_index=True to drop both indexes.
  4360. with connection.schema_editor() as editor:
  4361. editor.alter_field(Author, new_field, old_field, strict=True)
  4362. self.assertEqual(self.get_constraints_for_column(Author, "name"), [])
  4363. @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific")
  4364. def test_alter_field_add_unique_to_charfield(self):
  4365. # Create the table and verify no initial indexes.
  4366. with connection.schema_editor() as editor:
  4367. editor.create_model(Author)
  4368. self.assertEqual(self.get_constraints_for_column(Author, "name"), [])
  4369. # Alter to add unique=True and create 2 indexes.
  4370. old_field = Author._meta.get_field("name")
  4371. new_field = CharField(max_length=255, unique=True)
  4372. new_field.set_attributes_from_name("name")
  4373. with connection.schema_editor() as editor:
  4374. editor.alter_field(Author, old_field, new_field, strict=True)
  4375. self.assertEqual(
  4376. self.get_constraints_for_column(Author, "name"),
  4377. ["schema_author_name_1fbc5617_like", "schema_author_name_1fbc5617_uniq"],
  4378. )
  4379. # Remove unique=True to drop both indexes.
  4380. with connection.schema_editor() as editor:
  4381. editor.alter_field(Author, new_field, old_field, strict=True)
  4382. self.assertEqual(self.get_constraints_for_column(Author, "name"), [])
  4383. @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific")
  4384. def test_alter_field_add_index_to_textfield(self):
  4385. # Create the table and verify no initial indexes.
  4386. with connection.schema_editor() as editor:
  4387. editor.create_model(Note)
  4388. self.assertEqual(self.get_constraints_for_column(Note, "info"), [])
  4389. # Alter to add db_index=True and create 2 indexes.
  4390. old_field = Note._meta.get_field("info")
  4391. new_field = TextField(db_index=True)
  4392. new_field.set_attributes_from_name("info")
  4393. with connection.schema_editor() as editor:
  4394. editor.alter_field(Note, old_field, new_field, strict=True)
  4395. self.assertEqual(
  4396. self.get_constraints_for_column(Note, "info"),
  4397. ["schema_note_info_4b0ea695", "schema_note_info_4b0ea695_like"],
  4398. )
  4399. # Remove db_index=True to drop both indexes.
  4400. with connection.schema_editor() as editor:
  4401. editor.alter_field(Note, new_field, old_field, strict=True)
  4402. self.assertEqual(self.get_constraints_for_column(Note, "info"), [])
  4403. @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific")
  4404. def test_alter_field_add_unique_to_charfield_with_db_index(self):
  4405. # Create the table and verify initial indexes.
  4406. with connection.schema_editor() as editor:
  4407. editor.create_model(BookWithoutAuthor)
  4408. self.assertEqual(
  4409. self.get_constraints_for_column(BookWithoutAuthor, "title"),
  4410. ["schema_book_title_2dfb2dff", "schema_book_title_2dfb2dff_like"],
  4411. )
  4412. # Alter to add unique=True (should replace the index)
  4413. old_field = BookWithoutAuthor._meta.get_field("title")
  4414. new_field = CharField(max_length=100, db_index=True, unique=True)
  4415. new_field.set_attributes_from_name("title")
  4416. with connection.schema_editor() as editor:
  4417. editor.alter_field(BookWithoutAuthor, old_field, new_field, strict=True)
  4418. self.assertEqual(
  4419. self.get_constraints_for_column(BookWithoutAuthor, "title"),
  4420. ["schema_book_title_2dfb2dff_like", "schema_book_title_2dfb2dff_uniq"],
  4421. )
  4422. # Alter to remove unique=True (should drop unique index)
  4423. new_field2 = CharField(max_length=100, db_index=True)
  4424. new_field2.set_attributes_from_name("title")
  4425. with connection.schema_editor() as editor:
  4426. editor.alter_field(BookWithoutAuthor, new_field, new_field2, strict=True)
  4427. self.assertEqual(
  4428. self.get_constraints_for_column(BookWithoutAuthor, "title"),
  4429. ["schema_book_title_2dfb2dff", "schema_book_title_2dfb2dff_like"],
  4430. )
  4431. @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific")
  4432. def test_alter_field_remove_unique_and_db_index_from_charfield(self):
  4433. # Create the table and verify initial indexes.
  4434. with connection.schema_editor() as editor:
  4435. editor.create_model(BookWithoutAuthor)
  4436. self.assertEqual(
  4437. self.get_constraints_for_column(BookWithoutAuthor, "title"),
  4438. ["schema_book_title_2dfb2dff", "schema_book_title_2dfb2dff_like"],
  4439. )
  4440. # Alter to add unique=True (should replace the index)
  4441. old_field = BookWithoutAuthor._meta.get_field("title")
  4442. new_field = CharField(max_length=100, db_index=True, unique=True)
  4443. new_field.set_attributes_from_name("title")
  4444. with connection.schema_editor() as editor:
  4445. editor.alter_field(BookWithoutAuthor, old_field, new_field, strict=True)
  4446. self.assertEqual(
  4447. self.get_constraints_for_column(BookWithoutAuthor, "title"),
  4448. ["schema_book_title_2dfb2dff_like", "schema_book_title_2dfb2dff_uniq"],
  4449. )
  4450. # Alter to remove both unique=True and db_index=True (should drop all indexes)
  4451. new_field2 = CharField(max_length=100)
  4452. new_field2.set_attributes_from_name("title")
  4453. with connection.schema_editor() as editor:
  4454. editor.alter_field(BookWithoutAuthor, new_field, new_field2, strict=True)
  4455. self.assertEqual(
  4456. self.get_constraints_for_column(BookWithoutAuthor, "title"), []
  4457. )
  4458. @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific")
  4459. def test_alter_field_swap_unique_and_db_index_with_charfield(self):
  4460. # Create the table and verify initial indexes.
  4461. with connection.schema_editor() as editor:
  4462. editor.create_model(BookWithoutAuthor)
  4463. self.assertEqual(
  4464. self.get_constraints_for_column(BookWithoutAuthor, "title"),
  4465. ["schema_book_title_2dfb2dff", "schema_book_title_2dfb2dff_like"],
  4466. )
  4467. # Alter to set unique=True and remove db_index=True (should replace the index)
  4468. old_field = BookWithoutAuthor._meta.get_field("title")
  4469. new_field = CharField(max_length=100, unique=True)
  4470. new_field.set_attributes_from_name("title")
  4471. with connection.schema_editor() as editor:
  4472. editor.alter_field(BookWithoutAuthor, old_field, new_field, strict=True)
  4473. self.assertEqual(
  4474. self.get_constraints_for_column(BookWithoutAuthor, "title"),
  4475. ["schema_book_title_2dfb2dff_like", "schema_book_title_2dfb2dff_uniq"],
  4476. )
  4477. # Alter to set db_index=True and remove unique=True (should restore index)
  4478. new_field2 = CharField(max_length=100, db_index=True)
  4479. new_field2.set_attributes_from_name("title")
  4480. with connection.schema_editor() as editor:
  4481. editor.alter_field(BookWithoutAuthor, new_field, new_field2, strict=True)
  4482. self.assertEqual(
  4483. self.get_constraints_for_column(BookWithoutAuthor, "title"),
  4484. ["schema_book_title_2dfb2dff", "schema_book_title_2dfb2dff_like"],
  4485. )
  4486. @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific")
  4487. def test_alter_field_add_db_index_to_charfield_with_unique(self):
  4488. # Create the table and verify initial indexes.
  4489. with connection.schema_editor() as editor:
  4490. editor.create_model(Tag)
  4491. self.assertEqual(
  4492. self.get_constraints_for_column(Tag, "slug"),
  4493. ["schema_tag_slug_2c418ba3_like", "schema_tag_slug_key"],
  4494. )
  4495. # Alter to add db_index=True
  4496. old_field = Tag._meta.get_field("slug")
  4497. new_field = SlugField(db_index=True, unique=True)
  4498. new_field.set_attributes_from_name("slug")
  4499. with connection.schema_editor() as editor:
  4500. editor.alter_field(Tag, old_field, new_field, strict=True)
  4501. self.assertEqual(
  4502. self.get_constraints_for_column(Tag, "slug"),
  4503. ["schema_tag_slug_2c418ba3_like", "schema_tag_slug_key"],
  4504. )
  4505. # Alter to remove db_index=True
  4506. new_field2 = SlugField(unique=True)
  4507. new_field2.set_attributes_from_name("slug")
  4508. with connection.schema_editor() as editor:
  4509. editor.alter_field(Tag, new_field, new_field2, strict=True)
  4510. self.assertEqual(
  4511. self.get_constraints_for_column(Tag, "slug"),
  4512. ["schema_tag_slug_2c418ba3_like", "schema_tag_slug_key"],
  4513. )
  4514. def test_alter_field_add_index_to_integerfield(self):
  4515. # Create the table and verify no initial indexes.
  4516. with connection.schema_editor() as editor:
  4517. editor.create_model(Author)
  4518. self.assertEqual(self.get_constraints_for_column(Author, "weight"), [])
  4519. # Alter to add db_index=True and create index.
  4520. old_field = Author._meta.get_field("weight")
  4521. new_field = IntegerField(null=True, db_index=True)
  4522. new_field.set_attributes_from_name("weight")
  4523. with connection.schema_editor() as editor:
  4524. editor.alter_field(Author, old_field, new_field, strict=True)
  4525. self.assertEqual(
  4526. self.get_constraints_for_column(Author, "weight"),
  4527. ["schema_author_weight_587740f9"],
  4528. )
  4529. # Remove db_index=True to drop index.
  4530. with connection.schema_editor() as editor:
  4531. editor.alter_field(Author, new_field, old_field, strict=True)
  4532. self.assertEqual(self.get_constraints_for_column(Author, "weight"), [])
  4533. def test_alter_pk_with_self_referential_field(self):
  4534. """
  4535. Changing the primary key field name of a model with a self-referential
  4536. foreign key (#26384).
  4537. """
  4538. with connection.schema_editor() as editor:
  4539. editor.create_model(Node)
  4540. old_field = Node._meta.get_field("node_id")
  4541. new_field = AutoField(primary_key=True)
  4542. new_field.set_attributes_from_name("id")
  4543. with connection.schema_editor() as editor:
  4544. editor.alter_field(Node, old_field, new_field, strict=True)
  4545. self.assertForeignKeyExists(Node, "parent_id", Node._meta.db_table)
  4546. @mock.patch("django.db.backends.base.schema.datetime")
  4547. @mock.patch("django.db.backends.base.schema.timezone")
  4548. def test_add_datefield_and_datetimefield_use_effective_default(
  4549. self, mocked_datetime, mocked_tz
  4550. ):
  4551. """
  4552. effective_default() should be used for DateField, DateTimeField, and
  4553. TimeField if auto_now or auto_now_add is set (#25005).
  4554. """
  4555. now = datetime.datetime(month=1, day=1, year=2000, hour=1, minute=1)
  4556. now_tz = datetime.datetime(
  4557. month=1, day=1, year=2000, hour=1, minute=1, tzinfo=datetime.timezone.utc
  4558. )
  4559. mocked_datetime.now = mock.MagicMock(return_value=now)
  4560. mocked_tz.now = mock.MagicMock(return_value=now_tz)
  4561. # Create the table
  4562. with connection.schema_editor() as editor:
  4563. editor.create_model(Author)
  4564. # Check auto_now/auto_now_add attributes are not defined
  4565. columns = self.column_classes(Author)
  4566. self.assertNotIn("dob_auto_now", columns)
  4567. self.assertNotIn("dob_auto_now_add", columns)
  4568. self.assertNotIn("dtob_auto_now", columns)
  4569. self.assertNotIn("dtob_auto_now_add", columns)
  4570. self.assertNotIn("tob_auto_now", columns)
  4571. self.assertNotIn("tob_auto_now_add", columns)
  4572. # Create a row
  4573. Author.objects.create(name="Anonymous1")
  4574. # Ensure fields were added with the correct defaults
  4575. dob_auto_now = DateField(auto_now=True)
  4576. dob_auto_now.set_attributes_from_name("dob_auto_now")
  4577. self.check_added_field_default(
  4578. editor,
  4579. Author,
  4580. dob_auto_now,
  4581. "dob_auto_now",
  4582. now.date(),
  4583. cast_function=lambda x: x.date(),
  4584. )
  4585. dob_auto_now_add = DateField(auto_now_add=True)
  4586. dob_auto_now_add.set_attributes_from_name("dob_auto_now_add")
  4587. self.check_added_field_default(
  4588. editor,
  4589. Author,
  4590. dob_auto_now_add,
  4591. "dob_auto_now_add",
  4592. now.date(),
  4593. cast_function=lambda x: x.date(),
  4594. )
  4595. dtob_auto_now = DateTimeField(auto_now=True)
  4596. dtob_auto_now.set_attributes_from_name("dtob_auto_now")
  4597. self.check_added_field_default(
  4598. editor,
  4599. Author,
  4600. dtob_auto_now,
  4601. "dtob_auto_now",
  4602. now,
  4603. )
  4604. dt_tm_of_birth_auto_now_add = DateTimeField(auto_now_add=True)
  4605. dt_tm_of_birth_auto_now_add.set_attributes_from_name("dtob_auto_now_add")
  4606. self.check_added_field_default(
  4607. editor,
  4608. Author,
  4609. dt_tm_of_birth_auto_now_add,
  4610. "dtob_auto_now_add",
  4611. now,
  4612. )
  4613. tob_auto_now = TimeField(auto_now=True)
  4614. tob_auto_now.set_attributes_from_name("tob_auto_now")
  4615. self.check_added_field_default(
  4616. editor,
  4617. Author,
  4618. tob_auto_now,
  4619. "tob_auto_now",
  4620. now.time(),
  4621. cast_function=lambda x: x.time(),
  4622. )
  4623. tob_auto_now_add = TimeField(auto_now_add=True)
  4624. tob_auto_now_add.set_attributes_from_name("tob_auto_now_add")
  4625. self.check_added_field_default(
  4626. editor,
  4627. Author,
  4628. tob_auto_now_add,
  4629. "tob_auto_now_add",
  4630. now.time(),
  4631. cast_function=lambda x: x.time(),
  4632. )
  4633. def test_namespaced_db_table_create_index_name(self):
  4634. """
  4635. Table names are stripped of their namespace/schema before being used to
  4636. generate index names.
  4637. """
  4638. with connection.schema_editor() as editor:
  4639. max_name_length = connection.ops.max_name_length() or 200
  4640. namespace = "n" * max_name_length
  4641. table_name = "t" * max_name_length
  4642. namespaced_table_name = '"%s"."%s"' % (namespace, table_name)
  4643. self.assertEqual(
  4644. editor._create_index_name(table_name, []),
  4645. editor._create_index_name(namespaced_table_name, []),
  4646. )
  4647. @unittest.skipUnless(
  4648. connection.vendor == "oracle", "Oracle specific db_table syntax"
  4649. )
  4650. def test_creation_with_db_table_double_quotes(self):
  4651. oracle_user = connection.creation._test_database_user()
  4652. class Student(Model):
  4653. name = CharField(max_length=30)
  4654. class Meta:
  4655. app_label = "schema"
  4656. apps = new_apps
  4657. db_table = '"%s"."DJANGO_STUDENT_TABLE"' % oracle_user
  4658. class Document(Model):
  4659. name = CharField(max_length=30)
  4660. students = ManyToManyField(Student)
  4661. class Meta:
  4662. app_label = "schema"
  4663. apps = new_apps
  4664. db_table = '"%s"."DJANGO_DOCUMENT_TABLE"' % oracle_user
  4665. self.isolated_local_models = [Student, Document]
  4666. with connection.schema_editor() as editor:
  4667. editor.create_model(Student)
  4668. editor.create_model(Document)
  4669. doc = Document.objects.create(name="Test Name")
  4670. student = Student.objects.create(name="Some man")
  4671. doc.students.add(student)
  4672. @isolate_apps("schema")
  4673. @unittest.skipUnless(
  4674. connection.vendor == "postgresql", "PostgreSQL specific db_table syntax."
  4675. )
  4676. def test_namespaced_db_table_foreign_key_reference(self):
  4677. with connection.cursor() as cursor:
  4678. cursor.execute("CREATE SCHEMA django_schema_tests")
  4679. def delete_schema():
  4680. with connection.cursor() as cursor:
  4681. cursor.execute("DROP SCHEMA django_schema_tests CASCADE")
  4682. self.addCleanup(delete_schema)
  4683. class Author(Model):
  4684. class Meta:
  4685. app_label = "schema"
  4686. class Book(Model):
  4687. class Meta:
  4688. app_label = "schema"
  4689. db_table = '"django_schema_tests"."schema_book"'
  4690. author = ForeignKey(Author, CASCADE)
  4691. author.set_attributes_from_name("author")
  4692. with connection.schema_editor() as editor:
  4693. editor.create_model(Author)
  4694. editor.create_model(Book)
  4695. editor.add_field(Book, author)
  4696. def test_rename_table_renames_deferred_sql_references(self):
  4697. atomic_rename = connection.features.supports_atomic_references_rename
  4698. with connection.schema_editor(atomic=atomic_rename) as editor:
  4699. editor.create_model(Author)
  4700. editor.create_model(Book)
  4701. editor.alter_db_table(Author, "schema_author", "schema_renamed_author")
  4702. editor.alter_db_table(Author, "schema_book", "schema_renamed_book")
  4703. try:
  4704. self.assertGreater(len(editor.deferred_sql), 0)
  4705. for statement in editor.deferred_sql:
  4706. self.assertIs(statement.references_table("schema_author"), False)
  4707. self.assertIs(statement.references_table("schema_book"), False)
  4708. finally:
  4709. editor.alter_db_table(Author, "schema_renamed_author", "schema_author")
  4710. editor.alter_db_table(Author, "schema_renamed_book", "schema_book")
  4711. def test_rename_column_renames_deferred_sql_references(self):
  4712. with connection.schema_editor() as editor:
  4713. editor.create_model(Author)
  4714. editor.create_model(Book)
  4715. old_title = Book._meta.get_field("title")
  4716. new_title = CharField(max_length=100, db_index=True)
  4717. new_title.set_attributes_from_name("renamed_title")
  4718. editor.alter_field(Book, old_title, new_title)
  4719. old_author = Book._meta.get_field("author")
  4720. new_author = ForeignKey(Author, CASCADE)
  4721. new_author.set_attributes_from_name("renamed_author")
  4722. editor.alter_field(Book, old_author, new_author)
  4723. self.assertGreater(len(editor.deferred_sql), 0)
  4724. for statement in editor.deferred_sql:
  4725. self.assertIs(statement.references_column("book", "title"), False)
  4726. self.assertIs(statement.references_column("book", "author_id"), False)
  4727. @isolate_apps("schema")
  4728. def test_referenced_field_without_constraint_rename_inside_atomic_block(self):
  4729. """
  4730. Foreign keys without database level constraint don't prevent the field
  4731. they reference from being renamed in an atomic block.
  4732. """
  4733. class Foo(Model):
  4734. field = CharField(max_length=255, unique=True)
  4735. class Meta:
  4736. app_label = "schema"
  4737. class Bar(Model):
  4738. foo = ForeignKey(Foo, CASCADE, to_field="field", db_constraint=False)
  4739. class Meta:
  4740. app_label = "schema"
  4741. self.isolated_local_models = [Foo, Bar]
  4742. with connection.schema_editor() as editor:
  4743. editor.create_model(Foo)
  4744. editor.create_model(Bar)
  4745. new_field = CharField(max_length=255, unique=True)
  4746. new_field.set_attributes_from_name("renamed")
  4747. with connection.schema_editor(atomic=True) as editor:
  4748. editor.alter_field(Foo, Foo._meta.get_field("field"), new_field)
  4749. @isolate_apps("schema")
  4750. def test_referenced_table_without_constraint_rename_inside_atomic_block(self):
  4751. """
  4752. Foreign keys without database level constraint don't prevent the table
  4753. they reference from being renamed in an atomic block.
  4754. """
  4755. class Foo(Model):
  4756. field = CharField(max_length=255, unique=True)
  4757. class Meta:
  4758. app_label = "schema"
  4759. class Bar(Model):
  4760. foo = ForeignKey(Foo, CASCADE, to_field="field", db_constraint=False)
  4761. class Meta:
  4762. app_label = "schema"
  4763. self.isolated_local_models = [Foo, Bar]
  4764. with connection.schema_editor() as editor:
  4765. editor.create_model(Foo)
  4766. editor.create_model(Bar)
  4767. new_field = CharField(max_length=255, unique=True)
  4768. new_field.set_attributes_from_name("renamed")
  4769. with connection.schema_editor(atomic=True) as editor:
  4770. editor.alter_db_table(Foo, Foo._meta.db_table, "renamed_table")
  4771. Foo._meta.db_table = "renamed_table"
  4772. @isolate_apps("schema")
  4773. @skipUnlessDBFeature("supports_collation_on_charfield")
  4774. def test_db_collation_charfield(self):
  4775. collation = connection.features.test_collations.get("non_default")
  4776. if not collation:
  4777. self.skipTest("Language collations are not supported.")
  4778. class Foo(Model):
  4779. field = CharField(max_length=255, db_collation=collation)
  4780. class Meta:
  4781. app_label = "schema"
  4782. self.isolated_local_models = [Foo]
  4783. with connection.schema_editor() as editor:
  4784. editor.create_model(Foo)
  4785. self.assertEqual(
  4786. self.get_column_collation(Foo._meta.db_table, "field"),
  4787. collation,
  4788. )
  4789. @isolate_apps("schema")
  4790. @skipUnlessDBFeature("supports_collation_on_textfield")
  4791. def test_db_collation_textfield(self):
  4792. collation = connection.features.test_collations.get("non_default")
  4793. if not collation:
  4794. self.skipTest("Language collations are not supported.")
  4795. class Foo(Model):
  4796. field = TextField(db_collation=collation)
  4797. class Meta:
  4798. app_label = "schema"
  4799. self.isolated_local_models = [Foo]
  4800. with connection.schema_editor() as editor:
  4801. editor.create_model(Foo)
  4802. self.assertEqual(
  4803. self.get_column_collation(Foo._meta.db_table, "field"),
  4804. collation,
  4805. )
  4806. @skipUnlessDBFeature("supports_collation_on_charfield")
  4807. def test_add_field_db_collation(self):
  4808. collation = connection.features.test_collations.get("non_default")
  4809. if not collation:
  4810. self.skipTest("Language collations are not supported.")
  4811. with connection.schema_editor() as editor:
  4812. editor.create_model(Author)
  4813. new_field = CharField(max_length=255, db_collation=collation)
  4814. new_field.set_attributes_from_name("alias")
  4815. with connection.schema_editor() as editor:
  4816. editor.add_field(Author, new_field)
  4817. columns = self.column_classes(Author)
  4818. self.assertEqual(
  4819. columns["alias"][0],
  4820. connection.features.introspected_field_types["CharField"],
  4821. )
  4822. self.assertEqual(columns["alias"][1][8], collation)
  4823. @skipUnlessDBFeature("supports_collation_on_charfield")
  4824. def test_alter_field_db_collation(self):
  4825. collation = connection.features.test_collations.get("non_default")
  4826. if not collation:
  4827. self.skipTest("Language collations are not supported.")
  4828. with connection.schema_editor() as editor:
  4829. editor.create_model(Author)
  4830. old_field = Author._meta.get_field("name")
  4831. new_field = CharField(max_length=255, db_collation=collation)
  4832. new_field.set_attributes_from_name("name")
  4833. new_field.model = Author
  4834. with connection.schema_editor() as editor:
  4835. editor.alter_field(Author, old_field, new_field, strict=True)
  4836. self.assertEqual(
  4837. self.get_column_collation(Author._meta.db_table, "name"),
  4838. collation,
  4839. )
  4840. with connection.schema_editor() as editor:
  4841. editor.alter_field(Author, new_field, old_field, strict=True)
  4842. self.assertIsNone(self.get_column_collation(Author._meta.db_table, "name"))
  4843. @skipUnlessDBFeature("supports_collation_on_charfield")
  4844. def test_alter_field_type_preserve_db_collation(self):
  4845. collation = connection.features.test_collations.get("non_default")
  4846. if not collation:
  4847. self.skipTest("Language collations are not supported.")
  4848. with connection.schema_editor() as editor:
  4849. editor.create_model(Author)
  4850. old_field = Author._meta.get_field("name")
  4851. new_field = CharField(max_length=255, db_collation=collation)
  4852. new_field.set_attributes_from_name("name")
  4853. new_field.model = Author
  4854. with connection.schema_editor() as editor:
  4855. editor.alter_field(Author, old_field, new_field, strict=True)
  4856. self.assertEqual(
  4857. self.get_column_collation(Author._meta.db_table, "name"),
  4858. collation,
  4859. )
  4860. # Changing a field type should preserve the collation.
  4861. old_field = new_field
  4862. new_field = CharField(max_length=511, db_collation=collation)
  4863. new_field.set_attributes_from_name("name")
  4864. new_field.model = Author
  4865. with connection.schema_editor() as editor:
  4866. editor.alter_field(Author, new_field, old_field, strict=True)
  4867. # Collation is preserved.
  4868. self.assertEqual(
  4869. self.get_column_collation(Author._meta.db_table, "name"),
  4870. collation,
  4871. )
  4872. @skipUnlessDBFeature("supports_collation_on_charfield")
  4873. def test_alter_primary_key_db_collation(self):
  4874. collation = connection.features.test_collations.get("non_default")
  4875. if not collation:
  4876. self.skipTest("Language collations are not supported.")
  4877. with connection.schema_editor() as editor:
  4878. editor.create_model(Thing)
  4879. old_field = Thing._meta.get_field("when")
  4880. new_field = CharField(max_length=1, db_collation=collation, primary_key=True)
  4881. new_field.set_attributes_from_name("when")
  4882. new_field.model = Thing
  4883. with connection.schema_editor() as editor:
  4884. editor.alter_field(Thing, old_field, new_field, strict=True)
  4885. self.assertEqual(self.get_primary_key(Thing._meta.db_table), "when")
  4886. self.assertEqual(
  4887. self.get_column_collation(Thing._meta.db_table, "when"),
  4888. collation,
  4889. )
  4890. with connection.schema_editor() as editor:
  4891. editor.alter_field(Thing, new_field, old_field, strict=True)
  4892. self.assertEqual(self.get_primary_key(Thing._meta.db_table), "when")
  4893. self.assertIsNone(self.get_column_collation(Thing._meta.db_table, "when"))
  4894. @skipUnlessDBFeature(
  4895. "supports_collation_on_charfield", "supports_collation_on_textfield"
  4896. )
  4897. def test_alter_field_type_and_db_collation(self):
  4898. collation = connection.features.test_collations.get("non_default")
  4899. if not collation:
  4900. self.skipTest("Language collations are not supported.")
  4901. with connection.schema_editor() as editor:
  4902. editor.create_model(Note)
  4903. old_field = Note._meta.get_field("info")
  4904. new_field = CharField(max_length=255, db_collation=collation)
  4905. new_field.set_attributes_from_name("info")
  4906. new_field.model = Note
  4907. with connection.schema_editor() as editor:
  4908. editor.alter_field(Note, old_field, new_field, strict=True)
  4909. columns = self.column_classes(Note)
  4910. self.assertEqual(
  4911. columns["info"][0],
  4912. connection.features.introspected_field_types["CharField"],
  4913. )
  4914. self.assertEqual(columns["info"][1][8], collation)
  4915. with connection.schema_editor() as editor:
  4916. editor.alter_field(Note, new_field, old_field, strict=True)
  4917. columns = self.column_classes(Note)
  4918. self.assertEqual(columns["info"][0], "TextField")
  4919. self.assertIsNone(columns["info"][1][8])
  4920. @skipUnlessDBFeature(
  4921. "supports_collation_on_charfield",
  4922. "supports_non_deterministic_collations",
  4923. )
  4924. def test_ci_cs_db_collation(self):
  4925. cs_collation = connection.features.test_collations.get("cs")
  4926. ci_collation = connection.features.test_collations.get("ci")
  4927. try:
  4928. if connection.vendor == "mysql":
  4929. cs_collation = "latin1_general_cs"
  4930. elif connection.vendor == "postgresql":
  4931. cs_collation = "en-x-icu"
  4932. with connection.cursor() as cursor:
  4933. cursor.execute(
  4934. "CREATE COLLATION IF NOT EXISTS case_insensitive "
  4935. "(provider = icu, locale = 'und-u-ks-level2', "
  4936. "deterministic = false)"
  4937. )
  4938. ci_collation = "case_insensitive"
  4939. # Create the table.
  4940. with connection.schema_editor() as editor:
  4941. editor.create_model(Author)
  4942. # Case-insensitive collation.
  4943. old_field = Author._meta.get_field("name")
  4944. new_field_ci = CharField(max_length=255, db_collation=ci_collation)
  4945. new_field_ci.set_attributes_from_name("name")
  4946. new_field_ci.model = Author
  4947. with connection.schema_editor() as editor:
  4948. editor.alter_field(Author, old_field, new_field_ci, strict=True)
  4949. Author.objects.create(name="ANDREW")
  4950. self.assertIs(Author.objects.filter(name="Andrew").exists(), True)
  4951. # Case-sensitive collation.
  4952. new_field_cs = CharField(max_length=255, db_collation=cs_collation)
  4953. new_field_cs.set_attributes_from_name("name")
  4954. new_field_cs.model = Author
  4955. with connection.schema_editor() as editor:
  4956. editor.alter_field(Author, new_field_ci, new_field_cs, strict=True)
  4957. self.assertIs(Author.objects.filter(name="Andrew").exists(), False)
  4958. finally:
  4959. if connection.vendor == "postgresql":
  4960. with connection.cursor() as cursor:
  4961. cursor.execute("DROP COLLATION IF EXISTS case_insensitive")