tests.py 234 KB

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