1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290 |
- import functools
- import re
- from unittest import mock
- from django.apps import apps
- from django.conf import settings
- from django.contrib.auth.models import AbstractBaseUser
- from django.core.validators import RegexValidator, validate_slug
- from django.db import connection, migrations, models
- from django.db.migrations.autodetector import MigrationAutodetector
- from django.db.migrations.graph import MigrationGraph
- from django.db.migrations.loader import MigrationLoader
- from django.db.migrations.questioner import MigrationQuestioner
- from django.db.migrations.state import ModelState, ProjectState
- from django.test import SimpleTestCase, TestCase, ignore_warnings, override_settings
- from django.test.utils import isolate_lru_cache
- from django.utils.deprecation import RemovedInDjango51Warning
- from .models import FoodManager, FoodQuerySet
- class DeconstructibleObject:
- """
- A custom deconstructible object.
- """
- def __init__(self, *args, **kwargs):
- self.args = args
- self.kwargs = kwargs
- def deconstruct(self):
- return (self.__module__ + "." + self.__class__.__name__, self.args, self.kwargs)
- class BaseAutodetectorTests(TestCase):
- def repr_changes(self, changes, include_dependencies=False):
- output = ""
- for app_label, migrations_ in sorted(changes.items()):
- output += " %s:\n" % app_label
- for migration in migrations_:
- output += " %s\n" % migration.name
- for operation in migration.operations:
- output += " %s\n" % operation
- if include_dependencies:
- output += " Dependencies:\n"
- if migration.dependencies:
- for dep in migration.dependencies:
- output += " %s\n" % (dep,)
- else:
- output += " None\n"
- return output
- def assertNumberMigrations(self, changes, app_label, number):
- if len(changes.get(app_label, [])) != number:
- self.fail(
- "Incorrect number of migrations (%s) for %s (expected %s)\n%s"
- % (
- len(changes.get(app_label, [])),
- app_label,
- number,
- self.repr_changes(changes),
- )
- )
- def assertMigrationDependencies(self, changes, app_label, position, dependencies):
- if not changes.get(app_label):
- self.fail(
- "No migrations found for %s\n%s"
- % (app_label, self.repr_changes(changes))
- )
- if len(changes[app_label]) < position + 1:
- self.fail(
- "No migration at index %s for %s\n%s"
- % (position, app_label, self.repr_changes(changes))
- )
- migration = changes[app_label][position]
- if set(migration.dependencies) != set(dependencies):
- self.fail(
- "Migration dependencies mismatch for %s.%s (expected %s):\n%s"
- % (
- app_label,
- migration.name,
- dependencies,
- self.repr_changes(changes, include_dependencies=True),
- )
- )
- def assertOperationTypes(self, changes, app_label, position, types):
- if not changes.get(app_label):
- self.fail(
- "No migrations found for %s\n%s"
- % (app_label, self.repr_changes(changes))
- )
- if len(changes[app_label]) < position + 1:
- self.fail(
- "No migration at index %s for %s\n%s"
- % (position, app_label, self.repr_changes(changes))
- )
- migration = changes[app_label][position]
- real_types = [
- operation.__class__.__name__ for operation in migration.operations
- ]
- if types != real_types:
- self.fail(
- "Operation type mismatch for %s.%s (expected %s):\n%s"
- % (
- app_label,
- migration.name,
- types,
- self.repr_changes(changes),
- )
- )
- def assertOperationAttributes(
- self, changes, app_label, position, operation_position, **attrs
- ):
- if not changes.get(app_label):
- self.fail(
- "No migrations found for %s\n%s"
- % (app_label, self.repr_changes(changes))
- )
- if len(changes[app_label]) < position + 1:
- self.fail(
- "No migration at index %s for %s\n%s"
- % (position, app_label, self.repr_changes(changes))
- )
- migration = changes[app_label][position]
- if len(changes[app_label]) < position + 1:
- self.fail(
- "No operation at index %s for %s.%s\n%s"
- % (
- operation_position,
- app_label,
- migration.name,
- self.repr_changes(changes),
- )
- )
- operation = migration.operations[operation_position]
- for attr, value in attrs.items():
- if getattr(operation, attr, None) != value:
- self.fail(
- "Attribute mismatch for %s.%s op #%s, %s (expected %r, got %r):\n%s"
- % (
- app_label,
- migration.name,
- operation_position,
- attr,
- value,
- getattr(operation, attr, None),
- self.repr_changes(changes),
- )
- )
- def assertOperationFieldAttributes(
- self, changes, app_label, position, operation_position, **attrs
- ):
- if not changes.get(app_label):
- self.fail(
- "No migrations found for %s\n%s"
- % (app_label, self.repr_changes(changes))
- )
- if len(changes[app_label]) < position + 1:
- self.fail(
- "No migration at index %s for %s\n%s"
- % (position, app_label, self.repr_changes(changes))
- )
- migration = changes[app_label][position]
- if len(changes[app_label]) < position + 1:
- self.fail(
- "No operation at index %s for %s.%s\n%s"
- % (
- operation_position,
- app_label,
- migration.name,
- self.repr_changes(changes),
- )
- )
- operation = migration.operations[operation_position]
- if not hasattr(operation, "field"):
- self.fail(
- "No field attribute for %s.%s op #%s."
- % (
- app_label,
- migration.name,
- operation_position,
- )
- )
- field = operation.field
- for attr, value in attrs.items():
- if getattr(field, attr, None) != value:
- self.fail(
- "Field attribute mismatch for %s.%s op #%s, field.%s (expected %r, "
- "got %r):\n%s"
- % (
- app_label,
- migration.name,
- operation_position,
- attr,
- value,
- getattr(field, attr, None),
- self.repr_changes(changes),
- )
- )
- def make_project_state(self, model_states):
- "Shortcut to make ProjectStates from lists of predefined models"
- project_state = ProjectState()
- for model_state in model_states:
- project_state.add_model(model_state.clone())
- return project_state
- def get_changes(self, before_states, after_states, questioner=None):
- if not isinstance(before_states, ProjectState):
- before_states = self.make_project_state(before_states)
- if not isinstance(after_states, ProjectState):
- after_states = self.make_project_state(after_states)
- return MigrationAutodetector(
- before_states,
- after_states,
- questioner,
- )._detect_changes()
- class AutodetectorTests(BaseAutodetectorTests):
- """
- Tests the migration autodetector.
- """
- author_empty = ModelState(
- "testapp", "Author", [("id", models.AutoField(primary_key=True))]
- )
- author_name = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200)),
- ],
- )
- author_name_null = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200, null=True)),
- ],
- )
- author_name_longer = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=400)),
- ],
- )
- author_name_renamed = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("names", models.CharField(max_length=200)),
- ],
- )
- author_name_default = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200, default="Ada Lovelace")),
- ],
- )
- author_name_check_constraint = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200)),
- ],
- {
- "constraints": [
- models.CheckConstraint(
- check=models.Q(name__contains="Bob"), name="name_contains_bob"
- )
- ]
- },
- )
- author_dates_of_birth_auto_now = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("date_of_birth", models.DateField(auto_now=True)),
- ("date_time_of_birth", models.DateTimeField(auto_now=True)),
- ("time_of_birth", models.TimeField(auto_now=True)),
- ],
- )
- author_dates_of_birth_auto_now_add = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("date_of_birth", models.DateField(auto_now_add=True)),
- ("date_time_of_birth", models.DateTimeField(auto_now_add=True)),
- ("time_of_birth", models.TimeField(auto_now_add=True)),
- ],
- )
- author_name_deconstructible_1 = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200, default=DeconstructibleObject())),
- ],
- )
- author_name_deconstructible_2 = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200, default=DeconstructibleObject())),
- ],
- )
- author_name_deconstructible_3 = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200, default=models.IntegerField())),
- ],
- )
- author_name_deconstructible_4 = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200, default=models.IntegerField())),
- ],
- )
- author_name_deconstructible_list_1 = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "name",
- models.CharField(
- max_length=200, default=[DeconstructibleObject(), 123]
- ),
- ),
- ],
- )
- author_name_deconstructible_list_2 = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "name",
- models.CharField(
- max_length=200, default=[DeconstructibleObject(), 123]
- ),
- ),
- ],
- )
- author_name_deconstructible_list_3 = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "name",
- models.CharField(
- max_length=200, default=[DeconstructibleObject(), 999]
- ),
- ),
- ],
- )
- author_name_deconstructible_tuple_1 = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "name",
- models.CharField(
- max_length=200, default=(DeconstructibleObject(), 123)
- ),
- ),
- ],
- )
- author_name_deconstructible_tuple_2 = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "name",
- models.CharField(
- max_length=200, default=(DeconstructibleObject(), 123)
- ),
- ),
- ],
- )
- author_name_deconstructible_tuple_3 = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "name",
- models.CharField(
- max_length=200, default=(DeconstructibleObject(), 999)
- ),
- ),
- ],
- )
- author_name_deconstructible_dict_1 = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "name",
- models.CharField(
- max_length=200,
- default={"item": DeconstructibleObject(), "otheritem": 123},
- ),
- ),
- ],
- )
- author_name_deconstructible_dict_2 = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "name",
- models.CharField(
- max_length=200,
- default={"item": DeconstructibleObject(), "otheritem": 123},
- ),
- ),
- ],
- )
- author_name_deconstructible_dict_3 = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "name",
- models.CharField(
- max_length=200,
- default={"item": DeconstructibleObject(), "otheritem": 999},
- ),
- ),
- ],
- )
- author_name_nested_deconstructible_1 = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "name",
- models.CharField(
- max_length=200,
- default=DeconstructibleObject(
- DeconstructibleObject(1),
- (
- DeconstructibleObject("t1"),
- DeconstructibleObject("t2"),
- ),
- a=DeconstructibleObject("A"),
- b=DeconstructibleObject(B=DeconstructibleObject("c")),
- ),
- ),
- ),
- ],
- )
- author_name_nested_deconstructible_2 = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "name",
- models.CharField(
- max_length=200,
- default=DeconstructibleObject(
- DeconstructibleObject(1),
- (
- DeconstructibleObject("t1"),
- DeconstructibleObject("t2"),
- ),
- a=DeconstructibleObject("A"),
- b=DeconstructibleObject(B=DeconstructibleObject("c")),
- ),
- ),
- ),
- ],
- )
- author_name_nested_deconstructible_changed_arg = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "name",
- models.CharField(
- max_length=200,
- default=DeconstructibleObject(
- DeconstructibleObject(1),
- (
- DeconstructibleObject("t1"),
- DeconstructibleObject("t2-changed"),
- ),
- a=DeconstructibleObject("A"),
- b=DeconstructibleObject(B=DeconstructibleObject("c")),
- ),
- ),
- ),
- ],
- )
- author_name_nested_deconstructible_extra_arg = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "name",
- models.CharField(
- max_length=200,
- default=DeconstructibleObject(
- DeconstructibleObject(1),
- (
- DeconstructibleObject("t1"),
- DeconstructibleObject("t2"),
- ),
- None,
- a=DeconstructibleObject("A"),
- b=DeconstructibleObject(B=DeconstructibleObject("c")),
- ),
- ),
- ),
- ],
- )
- author_name_nested_deconstructible_changed_kwarg = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "name",
- models.CharField(
- max_length=200,
- default=DeconstructibleObject(
- DeconstructibleObject(1),
- (
- DeconstructibleObject("t1"),
- DeconstructibleObject("t2"),
- ),
- a=DeconstructibleObject("A"),
- b=DeconstructibleObject(B=DeconstructibleObject("c-changed")),
- ),
- ),
- ),
- ],
- )
- author_name_nested_deconstructible_extra_kwarg = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "name",
- models.CharField(
- max_length=200,
- default=DeconstructibleObject(
- DeconstructibleObject(1),
- (
- DeconstructibleObject("t1"),
- DeconstructibleObject("t2"),
- ),
- a=DeconstructibleObject("A"),
- b=DeconstructibleObject(B=DeconstructibleObject("c")),
- c=None,
- ),
- ),
- ),
- ],
- )
- author_custom_pk = ModelState(
- "testapp", "Author", [("pk_field", models.IntegerField(primary_key=True))]
- )
- author_with_biography_non_blank = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField()),
- ("biography", models.TextField()),
- ],
- )
- author_with_biography_blank = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(blank=True)),
- ("biography", models.TextField(blank=True)),
- ],
- )
- author_with_book = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200)),
- ("book", models.ForeignKey("otherapp.Book", models.CASCADE)),
- ],
- )
- author_with_book_order_wrt = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200)),
- ("book", models.ForeignKey("otherapp.Book", models.CASCADE)),
- ],
- options={"order_with_respect_to": "book"},
- )
- author_renamed_with_book = ModelState(
- "testapp",
- "Writer",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200)),
- ("book", models.ForeignKey("otherapp.Book", models.CASCADE)),
- ],
- )
- author_with_publisher_string = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200)),
- ("publisher_name", models.CharField(max_length=200)),
- ],
- )
- author_with_publisher = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200)),
- ("publisher", models.ForeignKey("testapp.Publisher", models.CASCADE)),
- ],
- )
- author_with_user = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200)),
- ("user", models.ForeignKey("auth.User", models.CASCADE)),
- ],
- )
- author_with_custom_user = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200)),
- ("user", models.ForeignKey("thirdapp.CustomUser", models.CASCADE)),
- ],
- )
- author_proxy = ModelState(
- "testapp", "AuthorProxy", [], {"proxy": True}, ("testapp.author",)
- )
- author_proxy_options = ModelState(
- "testapp",
- "AuthorProxy",
- [],
- {
- "proxy": True,
- "verbose_name": "Super Author",
- },
- ("testapp.author",),
- )
- author_proxy_notproxy = ModelState(
- "testapp", "AuthorProxy", [], {}, ("testapp.author",)
- )
- author_proxy_third = ModelState(
- "thirdapp", "AuthorProxy", [], {"proxy": True}, ("testapp.author",)
- )
- author_proxy_third_notproxy = ModelState(
- "thirdapp", "AuthorProxy", [], {}, ("testapp.author",)
- )
- author_proxy_proxy = ModelState(
- "testapp", "AAuthorProxyProxy", [], {"proxy": True}, ("testapp.authorproxy",)
- )
- author_unmanaged = ModelState(
- "testapp", "AuthorUnmanaged", [], {"managed": False}, ("testapp.author",)
- )
- author_unmanaged_managed = ModelState(
- "testapp", "AuthorUnmanaged", [], {}, ("testapp.author",)
- )
- author_unmanaged_default_pk = ModelState(
- "testapp", "Author", [("id", models.AutoField(primary_key=True))]
- )
- author_unmanaged_custom_pk = ModelState(
- "testapp",
- "Author",
- [
- ("pk_field", models.IntegerField(primary_key=True)),
- ],
- )
- author_with_m2m = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("publishers", models.ManyToManyField("testapp.Publisher")),
- ],
- )
- author_with_m2m_blank = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("publishers", models.ManyToManyField("testapp.Publisher", blank=True)),
- ],
- )
- author_with_m2m_through = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "publishers",
- models.ManyToManyField("testapp.Publisher", through="testapp.Contract"),
- ),
- ],
- )
- author_with_renamed_m2m_through = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "publishers",
- models.ManyToManyField("testapp.Publisher", through="testapp.Deal"),
- ),
- ],
- )
- author_with_former_m2m = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("publishers", models.CharField(max_length=100)),
- ],
- )
- author_with_options = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ],
- {
- "permissions": [("can_hire", "Can hire")],
- "verbose_name": "Authi",
- },
- )
- author_with_db_table_options = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ],
- {"db_table": "author_one"},
- )
- author_with_new_db_table_options = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ],
- {"db_table": "author_two"},
- )
- author_renamed_with_db_table_options = ModelState(
- "testapp",
- "NewAuthor",
- [
- ("id", models.AutoField(primary_key=True)),
- ],
- {"db_table": "author_one"},
- )
- author_renamed_with_new_db_table_options = ModelState(
- "testapp",
- "NewAuthor",
- [
- ("id", models.AutoField(primary_key=True)),
- ],
- {"db_table": "author_three"},
- )
- contract = ModelState(
- "testapp",
- "Contract",
- [
- ("id", models.AutoField(primary_key=True)),
- ("author", models.ForeignKey("testapp.Author", models.CASCADE)),
- ("publisher", models.ForeignKey("testapp.Publisher", models.CASCADE)),
- ],
- )
- contract_renamed = ModelState(
- "testapp",
- "Deal",
- [
- ("id", models.AutoField(primary_key=True)),
- ("author", models.ForeignKey("testapp.Author", models.CASCADE)),
- ("publisher", models.ForeignKey("testapp.Publisher", models.CASCADE)),
- ],
- )
- publisher = ModelState(
- "testapp",
- "Publisher",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=100)),
- ],
- )
- publisher_with_author = ModelState(
- "testapp",
- "Publisher",
- [
- ("id", models.AutoField(primary_key=True)),
- ("author", models.ForeignKey("testapp.Author", models.CASCADE)),
- ("name", models.CharField(max_length=100)),
- ],
- )
- publisher_with_aardvark_author = ModelState(
- "testapp",
- "Publisher",
- [
- ("id", models.AutoField(primary_key=True)),
- ("author", models.ForeignKey("testapp.Aardvark", models.CASCADE)),
- ("name", models.CharField(max_length=100)),
- ],
- )
- publisher_with_book = ModelState(
- "testapp",
- "Publisher",
- [
- ("id", models.AutoField(primary_key=True)),
- ("author", models.ForeignKey("otherapp.Book", models.CASCADE)),
- ("name", models.CharField(max_length=100)),
- ],
- )
- other_pony = ModelState(
- "otherapp",
- "Pony",
- [
- ("id", models.AutoField(primary_key=True)),
- ],
- )
- other_pony_food = ModelState(
- "otherapp",
- "Pony",
- [
- ("id", models.AutoField(primary_key=True)),
- ],
- managers=[
- ("food_qs", FoodQuerySet.as_manager()),
- ("food_mgr", FoodManager("a", "b")),
- ("food_mgr_kwargs", FoodManager("x", "y", 3, 4)),
- ],
- )
- other_stable = ModelState(
- "otherapp", "Stable", [("id", models.AutoField(primary_key=True))]
- )
- third_thing = ModelState(
- "thirdapp", "Thing", [("id", models.AutoField(primary_key=True))]
- )
- book = ModelState(
- "otherapp",
- "Book",
- [
- ("id", models.AutoField(primary_key=True)),
- ("author", models.ForeignKey("testapp.Author", models.CASCADE)),
- ("title", models.CharField(max_length=200)),
- ],
- )
- book_proxy_fk = ModelState(
- "otherapp",
- "Book",
- [
- ("id", models.AutoField(primary_key=True)),
- ("author", models.ForeignKey("thirdapp.AuthorProxy", models.CASCADE)),
- ("title", models.CharField(max_length=200)),
- ],
- )
- book_proxy_proxy_fk = ModelState(
- "otherapp",
- "Book",
- [
- ("id", models.AutoField(primary_key=True)),
- ("author", models.ForeignKey("testapp.AAuthorProxyProxy", models.CASCADE)),
- ],
- )
- book_migrations_fk = ModelState(
- "otherapp",
- "Book",
- [
- ("id", models.AutoField(primary_key=True)),
- ("author", models.ForeignKey("migrations.UnmigratedModel", models.CASCADE)),
- ("title", models.CharField(max_length=200)),
- ],
- )
- book_with_no_author_fk = ModelState(
- "otherapp",
- "Book",
- [
- ("id", models.AutoField(primary_key=True)),
- ("author", models.IntegerField()),
- ("title", models.CharField(max_length=200)),
- ],
- )
- book_with_no_author = ModelState(
- "otherapp",
- "Book",
- [
- ("id", models.AutoField(primary_key=True)),
- ("title", models.CharField(max_length=200)),
- ],
- )
- book_with_author_renamed = ModelState(
- "otherapp",
- "Book",
- [
- ("id", models.AutoField(primary_key=True)),
- ("author", models.ForeignKey("testapp.Writer", models.CASCADE)),
- ("title", models.CharField(max_length=200)),
- ],
- )
- book_with_field_and_author_renamed = ModelState(
- "otherapp",
- "Book",
- [
- ("id", models.AutoField(primary_key=True)),
- ("writer", models.ForeignKey("testapp.Writer", models.CASCADE)),
- ("title", models.CharField(max_length=200)),
- ],
- )
- book_with_multiple_authors = ModelState(
- "otherapp",
- "Book",
- [
- ("id", models.AutoField(primary_key=True)),
- ("authors", models.ManyToManyField("testapp.Author")),
- ("title", models.CharField(max_length=200)),
- ],
- )
- book_with_multiple_authors_through_attribution = ModelState(
- "otherapp",
- "Book",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "authors",
- models.ManyToManyField(
- "testapp.Author", through="otherapp.Attribution"
- ),
- ),
- ("title", models.CharField(max_length=200)),
- ],
- )
- book_indexes = ModelState(
- "otherapp",
- "Book",
- [
- ("id", models.AutoField(primary_key=True)),
- ("author", models.ForeignKey("testapp.Author", models.CASCADE)),
- ("title", models.CharField(max_length=200)),
- ],
- {
- "indexes": [
- models.Index(fields=["author", "title"], name="book_title_author_idx")
- ],
- },
- )
- book_unordered_indexes = ModelState(
- "otherapp",
- "Book",
- [
- ("id", models.AutoField(primary_key=True)),
- ("author", models.ForeignKey("testapp.Author", models.CASCADE)),
- ("title", models.CharField(max_length=200)),
- ],
- {
- "indexes": [
- models.Index(fields=["title", "author"], name="book_author_title_idx")
- ],
- },
- )
- book_unique_together = ModelState(
- "otherapp",
- "Book",
- [
- ("id", models.AutoField(primary_key=True)),
- ("author", models.ForeignKey("testapp.Author", models.CASCADE)),
- ("title", models.CharField(max_length=200)),
- ],
- {
- "unique_together": {("author", "title")},
- },
- )
- book_unique_together_2 = ModelState(
- "otherapp",
- "Book",
- [
- ("id", models.AutoField(primary_key=True)),
- ("author", models.ForeignKey("testapp.Author", models.CASCADE)),
- ("title", models.CharField(max_length=200)),
- ],
- {
- "unique_together": {("title", "author")},
- },
- )
- book_unique_together_3 = ModelState(
- "otherapp",
- "Book",
- [
- ("id", models.AutoField(primary_key=True)),
- ("newfield", models.IntegerField()),
- ("author", models.ForeignKey("testapp.Author", models.CASCADE)),
- ("title", models.CharField(max_length=200)),
- ],
- {
- "unique_together": {("title", "newfield")},
- },
- )
- book_unique_together_4 = ModelState(
- "otherapp",
- "Book",
- [
- ("id", models.AutoField(primary_key=True)),
- ("newfield2", models.IntegerField()),
- ("author", models.ForeignKey("testapp.Author", models.CASCADE)),
- ("title", models.CharField(max_length=200)),
- ],
- {
- "unique_together": {("title", "newfield2")},
- },
- )
- attribution = ModelState(
- "otherapp",
- "Attribution",
- [
- ("id", models.AutoField(primary_key=True)),
- ("author", models.ForeignKey("testapp.Author", models.CASCADE)),
- ("book", models.ForeignKey("otherapp.Book", models.CASCADE)),
- ],
- )
- edition = ModelState(
- "thirdapp",
- "Edition",
- [
- ("id", models.AutoField(primary_key=True)),
- ("book", models.ForeignKey("otherapp.Book", models.CASCADE)),
- ],
- )
- custom_user = ModelState(
- "thirdapp",
- "CustomUser",
- [
- ("id", models.AutoField(primary_key=True)),
- ("username", models.CharField(max_length=255)),
- ],
- bases=(AbstractBaseUser,),
- )
- custom_user_no_inherit = ModelState(
- "thirdapp",
- "CustomUser",
- [
- ("id", models.AutoField(primary_key=True)),
- ("username", models.CharField(max_length=255)),
- ],
- )
- aardvark = ModelState(
- "thirdapp", "Aardvark", [("id", models.AutoField(primary_key=True))]
- )
- aardvark_testapp = ModelState(
- "testapp", "Aardvark", [("id", models.AutoField(primary_key=True))]
- )
- aardvark_based_on_author = ModelState(
- "testapp", "Aardvark", [], bases=("testapp.Author",)
- )
- aardvark_pk_fk_author = ModelState(
- "testapp",
- "Aardvark",
- [
- (
- "id",
- models.OneToOneField(
- "testapp.Author", models.CASCADE, primary_key=True
- ),
- ),
- ],
- )
- knight = ModelState("eggs", "Knight", [("id", models.AutoField(primary_key=True))])
- rabbit = ModelState(
- "eggs",
- "Rabbit",
- [
- ("id", models.AutoField(primary_key=True)),
- ("knight", models.ForeignKey("eggs.Knight", models.CASCADE)),
- ("parent", models.ForeignKey("eggs.Rabbit", models.CASCADE)),
- ],
- {
- "unique_together": {("parent", "knight")},
- "indexes": [
- models.Index(
- fields=["parent", "knight"], name="rabbit_circular_fk_index"
- )
- ],
- },
- )
- def test_arrange_for_graph(self):
- """Tests auto-naming of migrations for graph matching."""
- # Make a fake graph
- graph = MigrationGraph()
- graph.add_node(("testapp", "0001_initial"), None)
- graph.add_node(("testapp", "0002_foobar"), None)
- graph.add_node(("otherapp", "0001_initial"), None)
- graph.add_dependency(
- "testapp.0002_foobar",
- ("testapp", "0002_foobar"),
- ("testapp", "0001_initial"),
- )
- graph.add_dependency(
- "testapp.0002_foobar",
- ("testapp", "0002_foobar"),
- ("otherapp", "0001_initial"),
- )
- # Use project state to make a new migration change set
- before = self.make_project_state([self.publisher, self.other_pony])
- after = self.make_project_state(
- [
- self.author_empty,
- self.publisher,
- self.other_pony,
- self.other_stable,
- ]
- )
- autodetector = MigrationAutodetector(before, after)
- changes = autodetector._detect_changes()
- # Run through arrange_for_graph
- changes = autodetector.arrange_for_graph(changes, graph)
- # Make sure there's a new name, deps match, etc.
- self.assertEqual(changes["testapp"][0].name, "0003_author")
- self.assertEqual(
- changes["testapp"][0].dependencies, [("testapp", "0002_foobar")]
- )
- self.assertEqual(changes["otherapp"][0].name, "0002_stable")
- self.assertEqual(
- changes["otherapp"][0].dependencies, [("otherapp", "0001_initial")]
- )
- def test_arrange_for_graph_with_multiple_initial(self):
- # Make a fake graph.
- graph = MigrationGraph()
- # Use project state to make a new migration change set.
- before = self.make_project_state([])
- after = self.make_project_state(
- [self.author_with_book, self.book, self.attribution]
- )
- autodetector = MigrationAutodetector(
- before, after, MigrationQuestioner({"ask_initial": True})
- )
- changes = autodetector._detect_changes()
- changes = autodetector.arrange_for_graph(changes, graph)
- self.assertEqual(changes["otherapp"][0].name, "0001_initial")
- self.assertEqual(changes["otherapp"][0].dependencies, [])
- self.assertEqual(changes["otherapp"][1].name, "0002_initial")
- self.assertCountEqual(
- changes["otherapp"][1].dependencies,
- [("testapp", "0001_initial"), ("otherapp", "0001_initial")],
- )
- self.assertEqual(changes["testapp"][0].name, "0001_initial")
- self.assertEqual(
- changes["testapp"][0].dependencies, [("otherapp", "0001_initial")]
- )
- def test_trim_apps(self):
- """
- Trim does not remove dependencies but does remove unwanted apps.
- """
- # Use project state to make a new migration change set
- before = self.make_project_state([])
- after = self.make_project_state(
- [self.author_empty, self.other_pony, self.other_stable, self.third_thing]
- )
- autodetector = MigrationAutodetector(
- before, after, MigrationQuestioner({"ask_initial": True})
- )
- changes = autodetector._detect_changes()
- # Run through arrange_for_graph
- graph = MigrationGraph()
- changes = autodetector.arrange_for_graph(changes, graph)
- changes["testapp"][0].dependencies.append(("otherapp", "0001_initial"))
- changes = autodetector._trim_to_apps(changes, {"testapp"})
- # Make sure there's the right set of migrations
- self.assertEqual(changes["testapp"][0].name, "0001_initial")
- self.assertEqual(changes["otherapp"][0].name, "0001_initial")
- self.assertNotIn("thirdapp", changes)
- def test_custom_migration_name(self):
- """Tests custom naming of migrations for graph matching."""
- # Make a fake graph
- graph = MigrationGraph()
- graph.add_node(("testapp", "0001_initial"), None)
- graph.add_node(("testapp", "0002_foobar"), None)
- graph.add_node(("otherapp", "0001_initial"), None)
- graph.add_dependency(
- "testapp.0002_foobar",
- ("testapp", "0002_foobar"),
- ("testapp", "0001_initial"),
- )
- # Use project state to make a new migration change set
- before = self.make_project_state([])
- after = self.make_project_state(
- [self.author_empty, self.other_pony, self.other_stable]
- )
- autodetector = MigrationAutodetector(before, after)
- changes = autodetector._detect_changes()
- # Run through arrange_for_graph
- migration_name = "custom_name"
- changes = autodetector.arrange_for_graph(changes, graph, migration_name)
- # Make sure there's a new name, deps match, etc.
- self.assertEqual(changes["testapp"][0].name, "0003_%s" % migration_name)
- self.assertEqual(
- changes["testapp"][0].dependencies, [("testapp", "0002_foobar")]
- )
- self.assertEqual(changes["otherapp"][0].name, "0002_%s" % migration_name)
- self.assertEqual(
- changes["otherapp"][0].dependencies, [("otherapp", "0001_initial")]
- )
- def test_new_model(self):
- """Tests autodetection of new models."""
- changes = self.get_changes([], [self.other_pony_food])
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(changes, "otherapp", 0, ["CreateModel"])
- self.assertOperationAttributes(changes, "otherapp", 0, 0, name="Pony")
- self.assertEqual(
- [name for name, mgr in changes["otherapp"][0].operations[0].managers],
- ["food_qs", "food_mgr", "food_mgr_kwargs"],
- )
- def test_old_model(self):
- """Tests deletion of old models."""
- changes = self.get_changes([self.author_empty], [])
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["DeleteModel"])
- self.assertOperationAttributes(changes, "testapp", 0, 0, name="Author")
- def test_add_field(self):
- """Tests autodetection of new fields."""
- changes = self.get_changes([self.author_empty], [self.author_name])
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["AddField"])
- self.assertOperationAttributes(changes, "testapp", 0, 0, name="name")
- @mock.patch(
- "django.db.migrations.questioner.MigrationQuestioner.ask_not_null_addition",
- side_effect=AssertionError("Should not have prompted for not null addition"),
- )
- def test_add_date_fields_with_auto_now_not_asking_for_default(
- self, mocked_ask_method
- ):
- changes = self.get_changes(
- [self.author_empty], [self.author_dates_of_birth_auto_now]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(
- changes, "testapp", 0, ["AddField", "AddField", "AddField"]
- )
- self.assertOperationFieldAttributes(changes, "testapp", 0, 0, auto_now=True)
- self.assertOperationFieldAttributes(changes, "testapp", 0, 1, auto_now=True)
- self.assertOperationFieldAttributes(changes, "testapp", 0, 2, auto_now=True)
- @mock.patch(
- "django.db.migrations.questioner.MigrationQuestioner.ask_not_null_addition",
- side_effect=AssertionError("Should not have prompted for not null addition"),
- )
- def test_add_date_fields_with_auto_now_add_not_asking_for_null_addition(
- self, mocked_ask_method
- ):
- changes = self.get_changes(
- [self.author_empty], [self.author_dates_of_birth_auto_now_add]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(
- changes, "testapp", 0, ["AddField", "AddField", "AddField"]
- )
- self.assertOperationFieldAttributes(changes, "testapp", 0, 0, auto_now_add=True)
- self.assertOperationFieldAttributes(changes, "testapp", 0, 1, auto_now_add=True)
- self.assertOperationFieldAttributes(changes, "testapp", 0, 2, auto_now_add=True)
- @mock.patch(
- "django.db.migrations.questioner.MigrationQuestioner.ask_auto_now_add_addition"
- )
- def test_add_date_fields_with_auto_now_add_asking_for_default(
- self, mocked_ask_method
- ):
- changes = self.get_changes(
- [self.author_empty], [self.author_dates_of_birth_auto_now_add]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(
- changes, "testapp", 0, ["AddField", "AddField", "AddField"]
- )
- self.assertOperationFieldAttributes(changes, "testapp", 0, 0, auto_now_add=True)
- self.assertOperationFieldAttributes(changes, "testapp", 0, 1, auto_now_add=True)
- self.assertOperationFieldAttributes(changes, "testapp", 0, 2, auto_now_add=True)
- self.assertEqual(mocked_ask_method.call_count, 3)
- def test_remove_field(self):
- """Tests autodetection of removed fields."""
- changes = self.get_changes([self.author_name], [self.author_empty])
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["RemoveField"])
- self.assertOperationAttributes(changes, "testapp", 0, 0, name="name")
- def test_alter_field(self):
- """Tests autodetection of new fields."""
- changes = self.get_changes([self.author_name], [self.author_name_longer])
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["AlterField"])
- self.assertOperationAttributes(
- changes, "testapp", 0, 0, name="name", preserve_default=True
- )
- def test_supports_functools_partial(self):
- def _content_file_name(instance, filename, key, **kwargs):
- return "{}/{}".format(instance, filename)
- def content_file_name(key, **kwargs):
- return functools.partial(_content_file_name, key, **kwargs)
- # An unchanged partial reference.
- before = [
- ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "file",
- models.FileField(
- max_length=200, upload_to=content_file_name("file")
- ),
- ),
- ],
- )
- ]
- after = [
- ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "file",
- models.FileField(
- max_length=200, upload_to=content_file_name("file")
- ),
- ),
- ],
- )
- ]
- changes = self.get_changes(before, after)
- self.assertNumberMigrations(changes, "testapp", 0)
- # A changed partial reference.
- args_changed = [
- ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "file",
- models.FileField(
- max_length=200, upload_to=content_file_name("other-file")
- ),
- ),
- ],
- )
- ]
- changes = self.get_changes(before, args_changed)
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["AlterField"])
- # Can't use assertOperationFieldAttributes because we need the
- # deconstructed version, i.e., the exploded func/args/keywords rather
- # than the partial: we don't care if it's not the same instance of the
- # partial, only if it's the same source function, args, and keywords.
- value = changes["testapp"][0].operations[0].field.upload_to
- self.assertEqual(
- (_content_file_name, ("other-file",), {}),
- (value.func, value.args, value.keywords),
- )
- kwargs_changed = [
- ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "file",
- models.FileField(
- max_length=200,
- upload_to=content_file_name("file", spam="eggs"),
- ),
- ),
- ],
- )
- ]
- changes = self.get_changes(before, kwargs_changed)
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["AlterField"])
- value = changes["testapp"][0].operations[0].field.upload_to
- self.assertEqual(
- (_content_file_name, ("file",), {"spam": "eggs"}),
- (value.func, value.args, value.keywords),
- )
- @mock.patch(
- "django.db.migrations.questioner.MigrationQuestioner.ask_not_null_alteration",
- side_effect=AssertionError("Should not have prompted for not null addition"),
- )
- def test_alter_field_to_not_null_with_default(self, mocked_ask_method):
- """
- #23609 - Tests autodetection of nullable to non-nullable alterations.
- """
- changes = self.get_changes([self.author_name_null], [self.author_name_default])
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["AlterField"])
- self.assertOperationAttributes(
- changes, "testapp", 0, 0, name="name", preserve_default=True
- )
- self.assertOperationFieldAttributes(
- changes, "testapp", 0, 0, default="Ada Lovelace"
- )
- @mock.patch(
- "django.db.migrations.questioner.MigrationQuestioner.ask_not_null_alteration",
- return_value=models.NOT_PROVIDED,
- )
- def test_alter_field_to_not_null_without_default(self, mocked_ask_method):
- """
- #23609 - Tests autodetection of nullable to non-nullable alterations.
- """
- changes = self.get_changes([self.author_name_null], [self.author_name])
- self.assertEqual(mocked_ask_method.call_count, 1)
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["AlterField"])
- self.assertOperationAttributes(
- changes, "testapp", 0, 0, name="name", preserve_default=True
- )
- self.assertOperationFieldAttributes(
- changes, "testapp", 0, 0, default=models.NOT_PROVIDED
- )
- @mock.patch(
- "django.db.migrations.questioner.MigrationQuestioner.ask_not_null_alteration",
- return_value="Some Name",
- )
- def test_alter_field_to_not_null_oneoff_default(self, mocked_ask_method):
- """
- #23609 - Tests autodetection of nullable to non-nullable alterations.
- """
- changes = self.get_changes([self.author_name_null], [self.author_name])
- self.assertEqual(mocked_ask_method.call_count, 1)
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["AlterField"])
- self.assertOperationAttributes(
- changes, "testapp", 0, 0, name="name", preserve_default=False
- )
- self.assertOperationFieldAttributes(
- changes, "testapp", 0, 0, default="Some Name"
- )
- def test_rename_field(self):
- """Tests autodetection of renamed fields."""
- changes = self.get_changes(
- [self.author_name],
- [self.author_name_renamed],
- MigrationQuestioner({"ask_rename": True}),
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["RenameField"])
- self.assertOperationAttributes(
- changes, "testapp", 0, 0, old_name="name", new_name="names"
- )
- def test_rename_field_foreign_key_to_field(self):
- before = [
- ModelState(
- "app",
- "Foo",
- [
- ("id", models.AutoField(primary_key=True)),
- ("field", models.IntegerField(unique=True)),
- ],
- ),
- ModelState(
- "app",
- "Bar",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "foo",
- models.ForeignKey("app.Foo", models.CASCADE, to_field="field"),
- ),
- ],
- ),
- ]
- after = [
- ModelState(
- "app",
- "Foo",
- [
- ("id", models.AutoField(primary_key=True)),
- ("renamed_field", models.IntegerField(unique=True)),
- ],
- ),
- ModelState(
- "app",
- "Bar",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "foo",
- models.ForeignKey(
- "app.Foo", models.CASCADE, to_field="renamed_field"
- ),
- ),
- ],
- ),
- ]
- changes = self.get_changes(
- before, after, MigrationQuestioner({"ask_rename": True})
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "app", 1)
- self.assertOperationTypes(changes, "app", 0, ["RenameField"])
- self.assertOperationAttributes(
- changes, "app", 0, 0, old_name="field", new_name="renamed_field"
- )
- def test_rename_foreign_object_fields(self):
- fields = ("first", "second")
- renamed_fields = ("first_renamed", "second_renamed")
- before = [
- ModelState(
- "app",
- "Foo",
- [
- ("id", models.AutoField(primary_key=True)),
- ("first", models.IntegerField()),
- ("second", models.IntegerField()),
- ],
- options={"unique_together": {fields}},
- ),
- ModelState(
- "app",
- "Bar",
- [
- ("id", models.AutoField(primary_key=True)),
- ("first", models.IntegerField()),
- ("second", models.IntegerField()),
- (
- "foo",
- models.ForeignObject(
- "app.Foo",
- models.CASCADE,
- from_fields=fields,
- to_fields=fields,
- ),
- ),
- ],
- ),
- ]
- # Case 1: to_fields renames.
- after = [
- ModelState(
- "app",
- "Foo",
- [
- ("id", models.AutoField(primary_key=True)),
- ("first_renamed", models.IntegerField()),
- ("second_renamed", models.IntegerField()),
- ],
- options={"unique_together": {renamed_fields}},
- ),
- ModelState(
- "app",
- "Bar",
- [
- ("id", models.AutoField(primary_key=True)),
- ("first", models.IntegerField()),
- ("second", models.IntegerField()),
- (
- "foo",
- models.ForeignObject(
- "app.Foo",
- models.CASCADE,
- from_fields=fields,
- to_fields=renamed_fields,
- ),
- ),
- ],
- ),
- ]
- changes = self.get_changes(
- before, after, MigrationQuestioner({"ask_rename": True})
- )
- self.assertNumberMigrations(changes, "app", 1)
- self.assertOperationTypes(
- changes, "app", 0, ["RenameField", "RenameField", "AlterUniqueTogether"]
- )
- self.assertOperationAttributes(
- changes,
- "app",
- 0,
- 0,
- model_name="foo",
- old_name="first",
- new_name="first_renamed",
- )
- self.assertOperationAttributes(
- changes,
- "app",
- 0,
- 1,
- model_name="foo",
- old_name="second",
- new_name="second_renamed",
- )
- # Case 2: from_fields renames.
- after = [
- ModelState(
- "app",
- "Foo",
- [
- ("id", models.AutoField(primary_key=True)),
- ("first", models.IntegerField()),
- ("second", models.IntegerField()),
- ],
- options={"unique_together": {fields}},
- ),
- ModelState(
- "app",
- "Bar",
- [
- ("id", models.AutoField(primary_key=True)),
- ("first_renamed", models.IntegerField()),
- ("second_renamed", models.IntegerField()),
- (
- "foo",
- models.ForeignObject(
- "app.Foo",
- models.CASCADE,
- from_fields=renamed_fields,
- to_fields=fields,
- ),
- ),
- ],
- ),
- ]
- changes = self.get_changes(
- before, after, MigrationQuestioner({"ask_rename": True})
- )
- self.assertNumberMigrations(changes, "app", 1)
- self.assertOperationTypes(changes, "app", 0, ["RenameField", "RenameField"])
- self.assertOperationAttributes(
- changes,
- "app",
- 0,
- 0,
- model_name="bar",
- old_name="first",
- new_name="first_renamed",
- )
- self.assertOperationAttributes(
- changes,
- "app",
- 0,
- 1,
- model_name="bar",
- old_name="second",
- new_name="second_renamed",
- )
- def test_rename_referenced_primary_key(self):
- before = [
- ModelState(
- "app",
- "Foo",
- [
- ("id", models.CharField(primary_key=True, serialize=False)),
- ],
- ),
- ModelState(
- "app",
- "Bar",
- [
- ("id", models.AutoField(primary_key=True)),
- ("foo", models.ForeignKey("app.Foo", models.CASCADE)),
- ],
- ),
- ]
- after = [
- ModelState(
- "app",
- "Foo",
- [("renamed_id", models.CharField(primary_key=True, serialize=False))],
- ),
- ModelState(
- "app",
- "Bar",
- [
- ("id", models.AutoField(primary_key=True)),
- ("foo", models.ForeignKey("app.Foo", models.CASCADE)),
- ],
- ),
- ]
- changes = self.get_changes(
- before, after, MigrationQuestioner({"ask_rename": True})
- )
- self.assertNumberMigrations(changes, "app", 1)
- self.assertOperationTypes(changes, "app", 0, ["RenameField"])
- self.assertOperationAttributes(
- changes, "app", 0, 0, old_name="id", new_name="renamed_id"
- )
- def test_rename_field_preserved_db_column(self):
- """
- RenameField is used if a field is renamed and db_column equal to the
- old field's column is added.
- """
- before = [
- ModelState(
- "app",
- "Foo",
- [
- ("id", models.AutoField(primary_key=True)),
- ("field", models.IntegerField()),
- ],
- ),
- ]
- after = [
- ModelState(
- "app",
- "Foo",
- [
- ("id", models.AutoField(primary_key=True)),
- ("renamed_field", models.IntegerField(db_column="field")),
- ],
- ),
- ]
- changes = self.get_changes(
- before, after, MigrationQuestioner({"ask_rename": True})
- )
- self.assertNumberMigrations(changes, "app", 1)
- self.assertOperationTypes(changes, "app", 0, ["AlterField", "RenameField"])
- self.assertOperationAttributes(
- changes,
- "app",
- 0,
- 0,
- model_name="foo",
- name="field",
- )
- self.assertEqual(
- changes["app"][0].operations[0].field.deconstruct(),
- (
- "field",
- "django.db.models.IntegerField",
- [],
- {"db_column": "field"},
- ),
- )
- self.assertOperationAttributes(
- changes,
- "app",
- 0,
- 1,
- model_name="foo",
- old_name="field",
- new_name="renamed_field",
- )
- def test_rename_related_field_preserved_db_column(self):
- before = [
- ModelState(
- "app",
- "Foo",
- [
- ("id", models.AutoField(primary_key=True)),
- ],
- ),
- ModelState(
- "app",
- "Bar",
- [
- ("id", models.AutoField(primary_key=True)),
- ("foo", models.ForeignKey("app.Foo", models.CASCADE)),
- ],
- ),
- ]
- after = [
- ModelState(
- "app",
- "Foo",
- [
- ("id", models.AutoField(primary_key=True)),
- ],
- ),
- ModelState(
- "app",
- "Bar",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "renamed_foo",
- models.ForeignKey(
- "app.Foo", models.CASCADE, db_column="foo_id"
- ),
- ),
- ],
- ),
- ]
- changes = self.get_changes(
- before, after, MigrationQuestioner({"ask_rename": True})
- )
- self.assertNumberMigrations(changes, "app", 1)
- self.assertOperationTypes(changes, "app", 0, ["AlterField", "RenameField"])
- self.assertOperationAttributes(
- changes,
- "app",
- 0,
- 0,
- model_name="bar",
- name="foo",
- )
- self.assertEqual(
- changes["app"][0].operations[0].field.deconstruct(),
- (
- "foo",
- "django.db.models.ForeignKey",
- [],
- {"to": "app.foo", "on_delete": models.CASCADE, "db_column": "foo_id"},
- ),
- )
- self.assertOperationAttributes(
- changes,
- "app",
- 0,
- 1,
- model_name="bar",
- old_name="foo",
- new_name="renamed_foo",
- )
- def test_rename_field_with_renamed_model(self):
- changes = self.get_changes(
- [self.author_name],
- [
- ModelState(
- "testapp",
- "RenamedAuthor",
- [
- ("id", models.AutoField(primary_key=True)),
- ("renamed_name", models.CharField(max_length=200)),
- ],
- ),
- ],
- MigrationQuestioner({"ask_rename_model": True, "ask_rename": True}),
- )
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["RenameModel", "RenameField"])
- self.assertOperationAttributes(
- changes,
- "testapp",
- 0,
- 0,
- old_name="Author",
- new_name="RenamedAuthor",
- )
- self.assertOperationAttributes(
- changes,
- "testapp",
- 0,
- 1,
- old_name="name",
- new_name="renamed_name",
- )
- def test_rename_model(self):
- """Tests autodetection of renamed models."""
- changes = self.get_changes(
- [self.author_with_book, self.book],
- [self.author_renamed_with_book, self.book_with_author_renamed],
- MigrationQuestioner({"ask_rename_model": True}),
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["RenameModel"])
- self.assertOperationAttributes(
- changes, "testapp", 0, 0, old_name="Author", new_name="Writer"
- )
- # Now that RenameModel handles related fields too, there should be
- # no AlterField for the related field.
- self.assertNumberMigrations(changes, "otherapp", 0)
- def test_rename_model_case(self):
- """
- Model name is case-insensitive. Changing case doesn't lead to any
- autodetected operations.
- """
- author_renamed = ModelState(
- "testapp",
- "author",
- [
- ("id", models.AutoField(primary_key=True)),
- ],
- )
- changes = self.get_changes(
- [self.author_empty, self.book],
- [author_renamed, self.book],
- questioner=MigrationQuestioner({"ask_rename_model": True}),
- )
- self.assertNumberMigrations(changes, "testapp", 0)
- self.assertNumberMigrations(changes, "otherapp", 0)
- def test_renamed_referenced_m2m_model_case(self):
- publisher_renamed = ModelState(
- "testapp",
- "publisher",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=100)),
- ],
- )
- changes = self.get_changes(
- [self.publisher, self.author_with_m2m],
- [publisher_renamed, self.author_with_m2m],
- questioner=MigrationQuestioner({"ask_rename_model": True}),
- )
- self.assertNumberMigrations(changes, "testapp", 0)
- self.assertNumberMigrations(changes, "otherapp", 0)
- def test_rename_m2m_through_model(self):
- """
- Tests autodetection of renamed models that are used in M2M relations as
- through models.
- """
- changes = self.get_changes(
- [self.author_with_m2m_through, self.publisher, self.contract],
- [
- self.author_with_renamed_m2m_through,
- self.publisher,
- self.contract_renamed,
- ],
- MigrationQuestioner({"ask_rename_model": True}),
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["RenameModel"])
- self.assertOperationAttributes(
- changes, "testapp", 0, 0, old_name="Contract", new_name="Deal"
- )
- def test_rename_model_with_renamed_rel_field(self):
- """
- Tests autodetection of renamed models while simultaneously renaming one
- of the fields that relate to the renamed model.
- """
- changes = self.get_changes(
- [self.author_with_book, self.book],
- [self.author_renamed_with_book, self.book_with_field_and_author_renamed],
- MigrationQuestioner({"ask_rename": True, "ask_rename_model": True}),
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["RenameModel"])
- self.assertOperationAttributes(
- changes, "testapp", 0, 0, old_name="Author", new_name="Writer"
- )
- # Right number/type of migrations for related field rename?
- # Alter is already taken care of.
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(changes, "otherapp", 0, ["RenameField"])
- self.assertOperationAttributes(
- changes, "otherapp", 0, 0, old_name="author", new_name="writer"
- )
- def test_rename_model_with_fks_in_different_position(self):
- """
- #24537 - The order of fields in a model does not influence
- the RenameModel detection.
- """
- before = [
- ModelState(
- "testapp",
- "EntityA",
- [
- ("id", models.AutoField(primary_key=True)),
- ],
- ),
- ModelState(
- "testapp",
- "EntityB",
- [
- ("id", models.AutoField(primary_key=True)),
- ("some_label", models.CharField(max_length=255)),
- ("entity_a", models.ForeignKey("testapp.EntityA", models.CASCADE)),
- ],
- ),
- ]
- after = [
- ModelState(
- "testapp",
- "EntityA",
- [
- ("id", models.AutoField(primary_key=True)),
- ],
- ),
- ModelState(
- "testapp",
- "RenamedEntityB",
- [
- ("id", models.AutoField(primary_key=True)),
- ("entity_a", models.ForeignKey("testapp.EntityA", models.CASCADE)),
- ("some_label", models.CharField(max_length=255)),
- ],
- ),
- ]
- changes = self.get_changes(
- before, after, MigrationQuestioner({"ask_rename_model": True})
- )
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["RenameModel"])
- self.assertOperationAttributes(
- changes, "testapp", 0, 0, old_name="EntityB", new_name="RenamedEntityB"
- )
- def test_rename_model_reverse_relation_dependencies(self):
- """
- The migration to rename a model pointed to by a foreign key in another
- app must run after the other app's migration that adds the foreign key
- with model's original name. Therefore, the renaming migration has a
- dependency on that other migration.
- """
- before = [
- ModelState(
- "testapp",
- "EntityA",
- [
- ("id", models.AutoField(primary_key=True)),
- ],
- ),
- ModelState(
- "otherapp",
- "EntityB",
- [
- ("id", models.AutoField(primary_key=True)),
- ("entity_a", models.ForeignKey("testapp.EntityA", models.CASCADE)),
- ],
- ),
- ]
- after = [
- ModelState(
- "testapp",
- "RenamedEntityA",
- [
- ("id", models.AutoField(primary_key=True)),
- ],
- ),
- ModelState(
- "otherapp",
- "EntityB",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "entity_a",
- models.ForeignKey("testapp.RenamedEntityA", models.CASCADE),
- ),
- ],
- ),
- ]
- changes = self.get_changes(
- before, after, MigrationQuestioner({"ask_rename_model": True})
- )
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertMigrationDependencies(
- changes, "testapp", 0, [("otherapp", "__first__")]
- )
- self.assertOperationTypes(changes, "testapp", 0, ["RenameModel"])
- self.assertOperationAttributes(
- changes, "testapp", 0, 0, old_name="EntityA", new_name="RenamedEntityA"
- )
- def test_fk_dependency(self):
- """Having a ForeignKey automatically adds a dependency."""
- # Note that testapp (author) has no dependencies,
- # otherapp (book) depends on testapp (author),
- # thirdapp (edition) depends on otherapp (book)
- changes = self.get_changes([], [self.author_name, self.book, self.edition])
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["CreateModel"])
- self.assertOperationAttributes(changes, "testapp", 0, 0, name="Author")
- self.assertMigrationDependencies(changes, "testapp", 0, [])
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(changes, "otherapp", 0, ["CreateModel"])
- self.assertOperationAttributes(changes, "otherapp", 0, 0, name="Book")
- self.assertMigrationDependencies(
- changes, "otherapp", 0, [("testapp", "auto_1")]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "thirdapp", 1)
- self.assertOperationTypes(changes, "thirdapp", 0, ["CreateModel"])
- self.assertOperationAttributes(changes, "thirdapp", 0, 0, name="Edition")
- self.assertMigrationDependencies(
- changes, "thirdapp", 0, [("otherapp", "auto_1")]
- )
- def test_proxy_fk_dependency(self):
- """FK dependencies still work on proxy models."""
- # Note that testapp (author) has no dependencies,
- # otherapp (book) depends on testapp (authorproxy)
- changes = self.get_changes(
- [], [self.author_empty, self.author_proxy_third, self.book_proxy_fk]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["CreateModel"])
- self.assertOperationAttributes(changes, "testapp", 0, 0, name="Author")
- self.assertMigrationDependencies(changes, "testapp", 0, [])
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(changes, "otherapp", 0, ["CreateModel"])
- self.assertOperationAttributes(changes, "otherapp", 0, 0, name="Book")
- self.assertMigrationDependencies(
- changes, "otherapp", 0, [("thirdapp", "auto_1")]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "thirdapp", 1)
- self.assertOperationTypes(changes, "thirdapp", 0, ["CreateModel"])
- self.assertOperationAttributes(changes, "thirdapp", 0, 0, name="AuthorProxy")
- self.assertMigrationDependencies(
- changes, "thirdapp", 0, [("testapp", "auto_1")]
- )
- def test_same_app_no_fk_dependency(self):
- """
- A migration with a FK between two models of the same app
- does not have a dependency to itself.
- """
- changes = self.get_changes([], [self.author_with_publisher, self.publisher])
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["CreateModel", "CreateModel"])
- self.assertOperationAttributes(changes, "testapp", 0, 0, name="Publisher")
- self.assertOperationAttributes(changes, "testapp", 0, 1, name="Author")
- self.assertMigrationDependencies(changes, "testapp", 0, [])
- def test_circular_fk_dependency(self):
- """
- Having a circular ForeignKey dependency automatically
- resolves the situation into 2 migrations on one side and 1 on the other.
- """
- changes = self.get_changes(
- [], [self.author_with_book, self.book, self.publisher_with_book]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["CreateModel", "CreateModel"])
- self.assertOperationAttributes(changes, "testapp", 0, 0, name="Publisher")
- self.assertOperationAttributes(changes, "testapp", 0, 1, name="Author")
- self.assertMigrationDependencies(
- changes, "testapp", 0, [("otherapp", "auto_1")]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "otherapp", 2)
- self.assertOperationTypes(changes, "otherapp", 0, ["CreateModel"])
- self.assertOperationTypes(changes, "otherapp", 1, ["AddField"])
- self.assertMigrationDependencies(changes, "otherapp", 0, [])
- self.assertMigrationDependencies(
- changes, "otherapp", 1, [("otherapp", "auto_1"), ("testapp", "auto_1")]
- )
- # both split migrations should be `initial`
- self.assertTrue(changes["otherapp"][0].initial)
- self.assertTrue(changes["otherapp"][1].initial)
- def test_same_app_circular_fk_dependency(self):
- """
- A migration with a FK between two models of the same app does
- not have a dependency to itself.
- """
- changes = self.get_changes(
- [], [self.author_with_publisher, self.publisher_with_author]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(
- changes, "testapp", 0, ["CreateModel", "CreateModel", "AddField"]
- )
- self.assertOperationAttributes(changes, "testapp", 0, 0, name="Author")
- self.assertOperationAttributes(changes, "testapp", 0, 1, name="Publisher")
- self.assertOperationAttributes(changes, "testapp", 0, 2, name="publisher")
- self.assertMigrationDependencies(changes, "testapp", 0, [])
- def test_same_app_circular_fk_dependency_with_unique_together_and_indexes(self):
- """
- #22275 - A migration with circular FK dependency does not try
- to create unique together constraint and indexes before creating all
- required fields first.
- """
- changes = self.get_changes([], [self.knight, self.rabbit])
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "eggs", 1)
- self.assertOperationTypes(
- changes,
- "eggs",
- 0,
- ["CreateModel", "CreateModel", "AddIndex", "AlterUniqueTogether"],
- )
- self.assertNotIn("unique_together", changes["eggs"][0].operations[0].options)
- self.assertNotIn("unique_together", changes["eggs"][0].operations[1].options)
- self.assertMigrationDependencies(changes, "eggs", 0, [])
- def test_alter_db_table_add(self):
- """Tests detection for adding db_table in model's options."""
- changes = self.get_changes(
- [self.author_empty], [self.author_with_db_table_options]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["AlterModelTable"])
- self.assertOperationAttributes(
- changes, "testapp", 0, 0, name="author", table="author_one"
- )
- def test_alter_db_table_change(self):
- """Tests detection for changing db_table in model's options'."""
- changes = self.get_changes(
- [self.author_with_db_table_options], [self.author_with_new_db_table_options]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["AlterModelTable"])
- self.assertOperationAttributes(
- changes, "testapp", 0, 0, name="author", table="author_two"
- )
- def test_alter_db_table_remove(self):
- """Tests detection for removing db_table in model's options."""
- changes = self.get_changes(
- [self.author_with_db_table_options], [self.author_empty]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["AlterModelTable"])
- self.assertOperationAttributes(
- changes, "testapp", 0, 0, name="author", table=None
- )
- def test_alter_db_table_no_changes(self):
- """
- Alter_db_table doesn't generate a migration if no changes have been made.
- """
- changes = self.get_changes(
- [self.author_with_db_table_options], [self.author_with_db_table_options]
- )
- # Right number of migrations?
- self.assertEqual(len(changes), 0)
- def test_keep_db_table_with_model_change(self):
- """
- Tests when model changes but db_table stays as-is, autodetector must not
- create more than one operation.
- """
- changes = self.get_changes(
- [self.author_with_db_table_options],
- [self.author_renamed_with_db_table_options],
- MigrationQuestioner({"ask_rename_model": True}),
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["RenameModel"])
- self.assertOperationAttributes(
- changes, "testapp", 0, 0, old_name="Author", new_name="NewAuthor"
- )
- def test_alter_db_table_with_model_change(self):
- """
- Tests when model and db_table changes, autodetector must create two
- operations.
- """
- changes = self.get_changes(
- [self.author_with_db_table_options],
- [self.author_renamed_with_new_db_table_options],
- MigrationQuestioner({"ask_rename_model": True}),
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(
- changes, "testapp", 0, ["RenameModel", "AlterModelTable"]
- )
- self.assertOperationAttributes(
- changes, "testapp", 0, 0, old_name="Author", new_name="NewAuthor"
- )
- self.assertOperationAttributes(
- changes, "testapp", 0, 1, name="newauthor", table="author_three"
- )
- def test_identical_regex_doesnt_alter(self):
- from_state = ModelState(
- "testapp",
- "model",
- [
- (
- "id",
- models.AutoField(
- primary_key=True,
- validators=[
- RegexValidator(
- re.compile("^[-a-zA-Z0-9_]+\\Z"),
- "Enter a valid “slug” consisting of letters, numbers, "
- "underscores or hyphens.",
- "invalid",
- )
- ],
- ),
- )
- ],
- )
- to_state = ModelState(
- "testapp",
- "model",
- [("id", models.AutoField(primary_key=True, validators=[validate_slug]))],
- )
- changes = self.get_changes([from_state], [to_state])
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 0)
- def test_different_regex_does_alter(self):
- from_state = ModelState(
- "testapp",
- "model",
- [
- (
- "id",
- models.AutoField(
- primary_key=True,
- validators=[
- RegexValidator(
- re.compile("^[a-z]+\\Z", 32),
- "Enter a valid “slug” consisting of letters, numbers, "
- "underscores or hyphens.",
- "invalid",
- )
- ],
- ),
- )
- ],
- )
- to_state = ModelState(
- "testapp",
- "model",
- [("id", models.AutoField(primary_key=True, validators=[validate_slug]))],
- )
- changes = self.get_changes([from_state], [to_state])
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["AlterField"])
- def test_alter_regex_string_to_compiled_regex(self):
- regex_string = "^[a-z]+$"
- from_state = ModelState(
- "testapp",
- "model",
- [
- (
- "id",
- models.AutoField(
- primary_key=True, validators=[RegexValidator(regex_string)]
- ),
- )
- ],
- )
- to_state = ModelState(
- "testapp",
- "model",
- [
- (
- "id",
- models.AutoField(
- primary_key=True,
- validators=[RegexValidator(re.compile(regex_string))],
- ),
- )
- ],
- )
- changes = self.get_changes([from_state], [to_state])
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["AlterField"])
- def test_empty_unique_together(self):
- """Empty unique_together shouldn't generate a migration."""
- # Explicitly testing for not specified, since this is the case after
- # a CreateModel operation w/o any definition on the original model
- model_state_not_specified = ModelState(
- "a", "model", [("id", models.AutoField(primary_key=True))]
- )
- # Explicitly testing for None, since this was the issue in #23452 after
- # an AlterUniqueTogether operation with e.g. () as value
- model_state_none = ModelState(
- "a",
- "model",
- [("id", models.AutoField(primary_key=True))],
- {
- "unique_together": None,
- },
- )
- # Explicitly testing for the empty set, since we now always have sets.
- # During removal (('col1', 'col2'),) --> () this becomes set([])
- model_state_empty = ModelState(
- "a",
- "model",
- [("id", models.AutoField(primary_key=True))],
- {
- "unique_together": set(),
- },
- )
- def test(from_state, to_state, msg):
- changes = self.get_changes([from_state], [to_state])
- if changes:
- ops = ", ".join(
- o.__class__.__name__ for o in changes["a"][0].operations
- )
- self.fail("Created operation(s) %s from %s" % (ops, msg))
- tests = (
- (
- model_state_not_specified,
- model_state_not_specified,
- '"not specified" to "not specified"',
- ),
- (model_state_not_specified, model_state_none, '"not specified" to "None"'),
- (
- model_state_not_specified,
- model_state_empty,
- '"not specified" to "empty"',
- ),
- (model_state_none, model_state_not_specified, '"None" to "not specified"'),
- (model_state_none, model_state_none, '"None" to "None"'),
- (model_state_none, model_state_empty, '"None" to "empty"'),
- (
- model_state_empty,
- model_state_not_specified,
- '"empty" to "not specified"',
- ),
- (model_state_empty, model_state_none, '"empty" to "None"'),
- (model_state_empty, model_state_empty, '"empty" to "empty"'),
- )
- for t in tests:
- test(*t)
- def test_create_model_with_indexes(self):
- """Test creation of new model with indexes already defined."""
- author = ModelState(
- "otherapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200)),
- ],
- {
- "indexes": [
- models.Index(fields=["name"], name="create_model_with_indexes_idx")
- ]
- },
- )
- changes = self.get_changes([], [author])
- added_index = models.Index(
- fields=["name"], name="create_model_with_indexes_idx"
- )
- # Right number of migrations?
- self.assertEqual(len(changes["otherapp"]), 1)
- # Right number of actions?
- migration = changes["otherapp"][0]
- self.assertEqual(len(migration.operations), 2)
- # Right actions order?
- self.assertOperationTypes(changes, "otherapp", 0, ["CreateModel", "AddIndex"])
- self.assertOperationAttributes(changes, "otherapp", 0, 0, name="Author")
- self.assertOperationAttributes(
- changes, "otherapp", 0, 1, model_name="author", index=added_index
- )
- def test_add_indexes(self):
- """Test change detection of new indexes."""
- changes = self.get_changes(
- [self.author_empty, self.book], [self.author_empty, self.book_indexes]
- )
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(changes, "otherapp", 0, ["AddIndex"])
- added_index = models.Index(
- fields=["author", "title"], name="book_title_author_idx"
- )
- self.assertOperationAttributes(
- changes, "otherapp", 0, 0, model_name="book", index=added_index
- )
- def test_remove_indexes(self):
- """Test change detection of removed indexes."""
- changes = self.get_changes(
- [self.author_empty, self.book_indexes], [self.author_empty, self.book]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(changes, "otherapp", 0, ["RemoveIndex"])
- self.assertOperationAttributes(
- changes, "otherapp", 0, 0, model_name="book", name="book_title_author_idx"
- )
- def test_rename_indexes(self):
- book_renamed_indexes = ModelState(
- "otherapp",
- "Book",
- [
- ("id", models.AutoField(primary_key=True)),
- ("author", models.ForeignKey("testapp.Author", models.CASCADE)),
- ("title", models.CharField(max_length=200)),
- ],
- {
- "indexes": [
- models.Index(
- fields=["author", "title"], name="renamed_book_title_author_idx"
- )
- ],
- },
- )
- changes = self.get_changes(
- [self.author_empty, self.book_indexes],
- [self.author_empty, book_renamed_indexes],
- )
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(changes, "otherapp", 0, ["RenameIndex"])
- self.assertOperationAttributes(
- changes,
- "otherapp",
- 0,
- 0,
- model_name="book",
- new_name="renamed_book_title_author_idx",
- old_name="book_title_author_idx",
- )
- def test_order_fields_indexes(self):
- """Test change detection of reordering of fields in indexes."""
- changes = self.get_changes(
- [self.author_empty, self.book_indexes],
- [self.author_empty, self.book_unordered_indexes],
- )
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(changes, "otherapp", 0, ["RemoveIndex", "AddIndex"])
- self.assertOperationAttributes(
- changes, "otherapp", 0, 0, model_name="book", name="book_title_author_idx"
- )
- added_index = models.Index(
- fields=["title", "author"], name="book_author_title_idx"
- )
- self.assertOperationAttributes(
- changes, "otherapp", 0, 1, model_name="book", index=added_index
- )
- def test_create_model_with_check_constraint(self):
- """Test creation of new model with constraints already defined."""
- author = ModelState(
- "otherapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200)),
- ],
- {
- "constraints": [
- models.CheckConstraint(
- check=models.Q(name__contains="Bob"), name="name_contains_bob"
- )
- ]
- },
- )
- changes = self.get_changes([], [author])
- added_constraint = models.CheckConstraint(
- check=models.Q(name__contains="Bob"), name="name_contains_bob"
- )
- # Right number of migrations?
- self.assertEqual(len(changes["otherapp"]), 1)
- # Right number of actions?
- migration = changes["otherapp"][0]
- self.assertEqual(len(migration.operations), 2)
- # Right actions order?
- self.assertOperationTypes(
- changes, "otherapp", 0, ["CreateModel", "AddConstraint"]
- )
- self.assertOperationAttributes(changes, "otherapp", 0, 0, name="Author")
- self.assertOperationAttributes(
- changes, "otherapp", 0, 1, model_name="author", constraint=added_constraint
- )
- def test_add_constraints(self):
- """Test change detection of new constraints."""
- changes = self.get_changes(
- [self.author_name], [self.author_name_check_constraint]
- )
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["AddConstraint"])
- added_constraint = models.CheckConstraint(
- check=models.Q(name__contains="Bob"), name="name_contains_bob"
- )
- self.assertOperationAttributes(
- changes, "testapp", 0, 0, model_name="author", constraint=added_constraint
- )
- def test_remove_constraints(self):
- """Test change detection of removed constraints."""
- changes = self.get_changes(
- [self.author_name_check_constraint], [self.author_name]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["RemoveConstraint"])
- self.assertOperationAttributes(
- changes, "testapp", 0, 0, model_name="author", name="name_contains_bob"
- )
- def test_add_unique_together(self):
- """Tests unique_together detection."""
- changes = self.get_changes(
- [self.author_empty, self.book],
- [self.author_empty, self.book_unique_together],
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(changes, "otherapp", 0, ["AlterUniqueTogether"])
- self.assertOperationAttributes(
- changes,
- "otherapp",
- 0,
- 0,
- name="book",
- unique_together={("author", "title")},
- )
- def test_remove_unique_together(self):
- """Tests unique_together detection."""
- changes = self.get_changes(
- [self.author_empty, self.book_unique_together],
- [self.author_empty, self.book],
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(changes, "otherapp", 0, ["AlterUniqueTogether"])
- self.assertOperationAttributes(
- changes, "otherapp", 0, 0, name="book", unique_together=set()
- )
- def test_unique_together_remove_fk(self):
- """Tests unique_together and field removal detection & ordering"""
- changes = self.get_changes(
- [self.author_empty, self.book_unique_together],
- [self.author_empty, self.book_with_no_author],
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(
- changes,
- "otherapp",
- 0,
- ["AlterUniqueTogether", "RemoveField"],
- )
- self.assertOperationAttributes(
- changes, "otherapp", 0, 0, name="book", unique_together=set()
- )
- self.assertOperationAttributes(
- changes, "otherapp", 0, 1, model_name="book", name="author"
- )
- def test_unique_together_no_changes(self):
- """
- unique_together doesn't generate a migration if no
- changes have been made.
- """
- changes = self.get_changes(
- [self.author_empty, self.book_unique_together],
- [self.author_empty, self.book_unique_together],
- )
- # Right number of migrations?
- self.assertEqual(len(changes), 0)
- def test_unique_together_ordering(self):
- """
- unique_together also triggers on ordering changes.
- """
- changes = self.get_changes(
- [self.author_empty, self.book_unique_together],
- [self.author_empty, self.book_unique_together_2],
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(
- changes,
- "otherapp",
- 0,
- ["AlterUniqueTogether"],
- )
- self.assertOperationAttributes(
- changes,
- "otherapp",
- 0,
- 0,
- name="book",
- unique_together={("title", "author")},
- )
- def test_add_field_and_unique_together(self):
- """
- Added fields will be created before using them in unique_together.
- """
- changes = self.get_changes(
- [self.author_empty, self.book],
- [self.author_empty, self.book_unique_together_3],
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(
- changes,
- "otherapp",
- 0,
- ["AddField", "AlterUniqueTogether"],
- )
- self.assertOperationAttributes(
- changes,
- "otherapp",
- 0,
- 1,
- name="book",
- unique_together={("title", "newfield")},
- )
- def test_create_model_and_unique_together(self):
- author = ModelState(
- "otherapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200)),
- ],
- )
- book_with_author = ModelState(
- "otherapp",
- "Book",
- [
- ("id", models.AutoField(primary_key=True)),
- ("author", models.ForeignKey("otherapp.Author", models.CASCADE)),
- ("title", models.CharField(max_length=200)),
- ],
- {
- "unique_together": {("title", "author")},
- },
- )
- changes = self.get_changes(
- [self.book_with_no_author], [author, book_with_author]
- )
- # Right number of migrations?
- self.assertEqual(len(changes["otherapp"]), 1)
- # Right number of actions?
- migration = changes["otherapp"][0]
- self.assertEqual(len(migration.operations), 3)
- # Right actions order?
- self.assertOperationTypes(
- changes,
- "otherapp",
- 0,
- ["CreateModel", "AddField", "AlterUniqueTogether"],
- )
- def test_remove_field_and_unique_together(self):
- """
- Removed fields will be removed after updating unique_together.
- """
- changes = self.get_changes(
- [self.author_empty, self.book_unique_together_3],
- [self.author_empty, self.book_unique_together],
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(
- changes,
- "otherapp",
- 0,
- ["AlterUniqueTogether", "RemoveField"],
- )
- self.assertOperationAttributes(
- changes,
- "otherapp",
- 0,
- 0,
- name="book",
- unique_together={("author", "title")},
- )
- self.assertOperationAttributes(
- changes,
- "otherapp",
- 0,
- 1,
- model_name="book",
- name="newfield",
- )
- def test_alter_field_and_unique_together(self):
- """Fields are altered after deleting some unique_together."""
- initial_author = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200)),
- ("age", models.IntegerField(db_index=True)),
- ],
- {
- "unique_together": {("name",)},
- },
- )
- author_reversed_constraints = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200, unique=True)),
- ("age", models.IntegerField()),
- ],
- {
- "unique_together": {("age",)},
- },
- )
- changes = self.get_changes([initial_author], [author_reversed_constraints])
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(
- changes,
- "testapp",
- 0,
- [
- "AlterUniqueTogether",
- "AlterField",
- "AlterField",
- "AlterUniqueTogether",
- ],
- )
- self.assertOperationAttributes(
- changes,
- "testapp",
- 0,
- 0,
- name="author",
- unique_together=set(),
- )
- self.assertOperationAttributes(
- changes,
- "testapp",
- 0,
- 1,
- model_name="author",
- name="age",
- )
- self.assertOperationAttributes(
- changes,
- "testapp",
- 0,
- 2,
- model_name="author",
- name="name",
- )
- self.assertOperationAttributes(
- changes,
- "testapp",
- 0,
- 3,
- name="author",
- unique_together={("age",)},
- )
- def test_partly_alter_unique_together_increase(self):
- initial_author = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200)),
- ("age", models.IntegerField()),
- ],
- {
- "unique_together": {("name",)},
- },
- )
- author_new_constraints = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200)),
- ("age", models.IntegerField()),
- ],
- {
- "unique_together": {("name",), ("age",)},
- },
- )
- changes = self.get_changes([initial_author], [author_new_constraints])
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(
- changes,
- "testapp",
- 0,
- ["AlterUniqueTogether"],
- )
- self.assertOperationAttributes(
- changes,
- "testapp",
- 0,
- 0,
- name="author",
- unique_together={("name",), ("age",)},
- )
- def test_partly_alter_unique_together_decrease(self):
- initial_author = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200)),
- ("age", models.IntegerField()),
- ],
- {
- "unique_together": {("name",), ("age",)},
- },
- )
- author_new_constraints = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200)),
- ("age", models.IntegerField()),
- ],
- {
- "unique_together": {("name",)},
- },
- )
- changes = self.get_changes([initial_author], [author_new_constraints])
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(
- changes,
- "testapp",
- 0,
- ["AlterUniqueTogether"],
- )
- self.assertOperationAttributes(
- changes,
- "testapp",
- 0,
- 0,
- name="author",
- unique_together={("name",)},
- )
- def test_rename_field_and_unique_together(self):
- """Fields are renamed before updating unique_together."""
- changes = self.get_changes(
- [self.author_empty, self.book_unique_together_3],
- [self.author_empty, self.book_unique_together_4],
- MigrationQuestioner({"ask_rename": True}),
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(
- changes,
- "otherapp",
- 0,
- ["RenameField", "AlterUniqueTogether"],
- )
- self.assertOperationAttributes(
- changes,
- "otherapp",
- 0,
- 1,
- name="book",
- unique_together={("title", "newfield2")},
- )
- def test_proxy(self):
- """The autodetector correctly deals with proxy models."""
- # First, we test adding a proxy model
- changes = self.get_changes(
- [self.author_empty], [self.author_empty, self.author_proxy]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["CreateModel"])
- self.assertOperationAttributes(
- changes,
- "testapp",
- 0,
- 0,
- name="AuthorProxy",
- options={"proxy": True, "indexes": [], "constraints": []},
- )
- # Now, we test turning a proxy model into a non-proxy model
- # It should delete the proxy then make the real one
- changes = self.get_changes(
- [self.author_empty, self.author_proxy],
- [self.author_empty, self.author_proxy_notproxy],
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["DeleteModel", "CreateModel"])
- self.assertOperationAttributes(changes, "testapp", 0, 0, name="AuthorProxy")
- self.assertOperationAttributes(
- changes, "testapp", 0, 1, name="AuthorProxy", options={}
- )
- def test_proxy_non_model_parent(self):
- class Mixin:
- pass
- author_proxy_non_model_parent = ModelState(
- "testapp",
- "AuthorProxy",
- [],
- {"proxy": True},
- (Mixin, "testapp.author"),
- )
- changes = self.get_changes(
- [self.author_empty],
- [self.author_empty, author_proxy_non_model_parent],
- )
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["CreateModel"])
- self.assertOperationAttributes(
- changes,
- "testapp",
- 0,
- 0,
- name="AuthorProxy",
- options={"proxy": True, "indexes": [], "constraints": []},
- bases=(Mixin, "testapp.author"),
- )
- def test_proxy_custom_pk(self):
- """
- #23415 - The autodetector must correctly deal with custom FK on proxy
- models.
- """
- # First, we test the default pk field name
- changes = self.get_changes(
- [], [self.author_empty, self.author_proxy_third, self.book_proxy_fk]
- )
- # The model the FK is pointing from and to.
- self.assertEqual(
- changes["otherapp"][0].operations[0].fields[2][1].remote_field.model,
- "thirdapp.AuthorProxy",
- )
- # Now, we test the custom pk field name
- changes = self.get_changes(
- [], [self.author_custom_pk, self.author_proxy_third, self.book_proxy_fk]
- )
- # The model the FK is pointing from and to.
- self.assertEqual(
- changes["otherapp"][0].operations[0].fields[2][1].remote_field.model,
- "thirdapp.AuthorProxy",
- )
- def test_proxy_to_mti_with_fk_to_proxy(self):
- # First, test the pk table and field name.
- to_state = self.make_project_state(
- [self.author_empty, self.author_proxy_third, self.book_proxy_fk],
- )
- changes = self.get_changes([], to_state)
- fk_field = changes["otherapp"][0].operations[0].fields[2][1]
- self.assertEqual(
- to_state.get_concrete_model_key(fk_field.remote_field.model),
- ("testapp", "author"),
- )
- self.assertEqual(fk_field.remote_field.model, "thirdapp.AuthorProxy")
- # Change AuthorProxy to use MTI.
- from_state = to_state.clone()
- to_state = self.make_project_state(
- [self.author_empty, self.author_proxy_third_notproxy, self.book_proxy_fk],
- )
- changes = self.get_changes(from_state, to_state)
- # Right number/type of migrations for the AuthorProxy model?
- self.assertNumberMigrations(changes, "thirdapp", 1)
- self.assertOperationTypes(
- changes, "thirdapp", 0, ["DeleteModel", "CreateModel"]
- )
- # Right number/type of migrations for the Book model with a FK to
- # AuthorProxy?
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(changes, "otherapp", 0, ["AlterField"])
- # otherapp should depend on thirdapp.
- self.assertMigrationDependencies(
- changes, "otherapp", 0, [("thirdapp", "auto_1")]
- )
- # Now, test the pk table and field name.
- fk_field = changes["otherapp"][0].operations[0].field
- self.assertEqual(
- to_state.get_concrete_model_key(fk_field.remote_field.model),
- ("thirdapp", "authorproxy"),
- )
- self.assertEqual(fk_field.remote_field.model, "thirdapp.AuthorProxy")
- def test_proxy_to_mti_with_fk_to_proxy_proxy(self):
- # First, test the pk table and field name.
- to_state = self.make_project_state(
- [
- self.author_empty,
- self.author_proxy,
- self.author_proxy_proxy,
- self.book_proxy_proxy_fk,
- ]
- )
- changes = self.get_changes([], to_state)
- fk_field = changes["otherapp"][0].operations[0].fields[1][1]
- self.assertEqual(
- to_state.get_concrete_model_key(fk_field.remote_field.model),
- ("testapp", "author"),
- )
- self.assertEqual(fk_field.remote_field.model, "testapp.AAuthorProxyProxy")
- # Change AuthorProxy to use MTI. FK still points to AAuthorProxyProxy,
- # a proxy of AuthorProxy.
- from_state = to_state.clone()
- to_state = self.make_project_state(
- [
- self.author_empty,
- self.author_proxy_notproxy,
- self.author_proxy_proxy,
- self.book_proxy_proxy_fk,
- ]
- )
- changes = self.get_changes(from_state, to_state)
- # Right number/type of migrations for the AuthorProxy model?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["DeleteModel", "CreateModel"])
- # Right number/type of migrations for the Book model with a FK to
- # AAuthorProxyProxy?
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(changes, "otherapp", 0, ["AlterField"])
- # otherapp should depend on testapp.
- self.assertMigrationDependencies(
- changes, "otherapp", 0, [("testapp", "auto_1")]
- )
- # Now, test the pk table and field name.
- fk_field = changes["otherapp"][0].operations[0].field
- self.assertEqual(
- to_state.get_concrete_model_key(fk_field.remote_field.model),
- ("testapp", "authorproxy"),
- )
- self.assertEqual(fk_field.remote_field.model, "testapp.AAuthorProxyProxy")
- def test_unmanaged_create(self):
- """The autodetector correctly deals with managed models."""
- # First, we test adding an unmanaged model
- changes = self.get_changes(
- [self.author_empty], [self.author_empty, self.author_unmanaged]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["CreateModel"])
- self.assertOperationAttributes(
- changes, "testapp", 0, 0, name="AuthorUnmanaged", options={"managed": False}
- )
- def test_unmanaged_delete(self):
- changes = self.get_changes(
- [self.author_empty, self.author_unmanaged], [self.author_empty]
- )
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["DeleteModel"])
- def test_unmanaged_to_managed(self):
- # Now, we test turning an unmanaged model into a managed model
- changes = self.get_changes(
- [self.author_empty, self.author_unmanaged],
- [self.author_empty, self.author_unmanaged_managed],
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["AlterModelOptions"])
- self.assertOperationAttributes(
- changes, "testapp", 0, 0, name="authorunmanaged", options={}
- )
- def test_managed_to_unmanaged(self):
- # Now, we turn managed to unmanaged.
- changes = self.get_changes(
- [self.author_empty, self.author_unmanaged_managed],
- [self.author_empty, self.author_unmanaged],
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["AlterModelOptions"])
- self.assertOperationAttributes(
- changes, "testapp", 0, 0, name="authorunmanaged", options={"managed": False}
- )
- def test_unmanaged_custom_pk(self):
- """
- #23415 - The autodetector must correctly deal with custom FK on
- unmanaged models.
- """
- # First, we test the default pk field name
- changes = self.get_changes([], [self.author_unmanaged_default_pk, self.book])
- # The model the FK on the book model points to.
- fk_field = changes["otherapp"][0].operations[0].fields[2][1]
- self.assertEqual(fk_field.remote_field.model, "testapp.Author")
- # Now, we test the custom pk field name
- changes = self.get_changes([], [self.author_unmanaged_custom_pk, self.book])
- # The model the FK on the book model points to.
- fk_field = changes["otherapp"][0].operations[0].fields[2][1]
- self.assertEqual(fk_field.remote_field.model, "testapp.Author")
- @override_settings(AUTH_USER_MODEL="thirdapp.CustomUser")
- def test_swappable(self):
- with isolate_lru_cache(apps.get_swappable_settings_name):
- changes = self.get_changes(
- [self.custom_user], [self.custom_user, self.author_with_custom_user]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["CreateModel"])
- self.assertOperationAttributes(changes, "testapp", 0, 0, name="Author")
- self.assertMigrationDependencies(
- changes, "testapp", 0, [("__setting__", "AUTH_USER_MODEL")]
- )
- def test_swappable_lowercase(self):
- model_state = ModelState(
- "testapp",
- "Document",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "owner",
- models.ForeignKey(
- settings.AUTH_USER_MODEL.lower(),
- models.CASCADE,
- ),
- ),
- ],
- )
- with isolate_lru_cache(apps.get_swappable_settings_name):
- changes = self.get_changes([], [model_state])
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["CreateModel"])
- self.assertOperationAttributes(changes, "testapp", 0, 0, name="Document")
- self.assertMigrationDependencies(
- changes,
- "testapp",
- 0,
- [("__setting__", "AUTH_USER_MODEL")],
- )
- @override_settings(AUTH_USER_MODEL="thirdapp.CustomUser")
- def test_swappable_many_to_many_model_case(self):
- document_lowercase = ModelState(
- "testapp",
- "Document",
- [
- ("id", models.AutoField(primary_key=True)),
- ("owners", models.ManyToManyField(settings.AUTH_USER_MODEL.lower())),
- ],
- )
- document = ModelState(
- "testapp",
- "Document",
- [
- ("id", models.AutoField(primary_key=True)),
- ("owners", models.ManyToManyField(settings.AUTH_USER_MODEL)),
- ],
- )
- with isolate_lru_cache(apps.get_swappable_settings_name):
- changes = self.get_changes(
- [self.custom_user, document_lowercase],
- [self.custom_user, document],
- )
- self.assertEqual(len(changes), 0)
- def test_swappable_changed(self):
- with isolate_lru_cache(apps.get_swappable_settings_name):
- before = self.make_project_state([self.custom_user, self.author_with_user])
- with override_settings(AUTH_USER_MODEL="thirdapp.CustomUser"):
- after = self.make_project_state(
- [self.custom_user, self.author_with_custom_user]
- )
- autodetector = MigrationAutodetector(before, after)
- changes = autodetector._detect_changes()
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["AlterField"])
- self.assertOperationAttributes(
- changes, "testapp", 0, 0, model_name="author", name="user"
- )
- fk_field = changes["testapp"][0].operations[0].field
- self.assertEqual(fk_field.remote_field.model, "thirdapp.CustomUser")
- def test_add_field_with_default(self):
- """#22030 - Adding a field with a default should work."""
- changes = self.get_changes([self.author_empty], [self.author_name_default])
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["AddField"])
- self.assertOperationAttributes(changes, "testapp", 0, 0, name="name")
- def test_custom_deconstructible(self):
- """
- Two instances which deconstruct to the same value aren't considered a
- change.
- """
- changes = self.get_changes(
- [self.author_name_deconstructible_1], [self.author_name_deconstructible_2]
- )
- # Right number of migrations?
- self.assertEqual(len(changes), 0)
- def test_deconstruct_field_kwarg(self):
- """Field instances are handled correctly by nested deconstruction."""
- changes = self.get_changes(
- [self.author_name_deconstructible_3], [self.author_name_deconstructible_4]
- )
- self.assertEqual(changes, {})
- def test_deconstructible_list(self):
- """Nested deconstruction descends into lists."""
- # When lists contain items that deconstruct to identical values, those lists
- # should be considered equal for the purpose of detecting state changes
- # (even if the original items are unequal).
- changes = self.get_changes(
- [self.author_name_deconstructible_list_1],
- [self.author_name_deconstructible_list_2],
- )
- self.assertEqual(changes, {})
- # Legitimate differences within the deconstructed lists should be reported
- # as a change
- changes = self.get_changes(
- [self.author_name_deconstructible_list_1],
- [self.author_name_deconstructible_list_3],
- )
- self.assertEqual(len(changes), 1)
- def test_deconstructible_tuple(self):
- """Nested deconstruction descends into tuples."""
- # When tuples contain items that deconstruct to identical values, those tuples
- # should be considered equal for the purpose of detecting state changes
- # (even if the original items are unequal).
- changes = self.get_changes(
- [self.author_name_deconstructible_tuple_1],
- [self.author_name_deconstructible_tuple_2],
- )
- self.assertEqual(changes, {})
- # Legitimate differences within the deconstructed tuples should be reported
- # as a change
- changes = self.get_changes(
- [self.author_name_deconstructible_tuple_1],
- [self.author_name_deconstructible_tuple_3],
- )
- self.assertEqual(len(changes), 1)
- def test_deconstructible_dict(self):
- """Nested deconstruction descends into dict values."""
- # When dicts contain items whose values deconstruct to identical values,
- # those dicts should be considered equal for the purpose of detecting
- # state changes (even if the original values are unequal).
- changes = self.get_changes(
- [self.author_name_deconstructible_dict_1],
- [self.author_name_deconstructible_dict_2],
- )
- self.assertEqual(changes, {})
- # Legitimate differences within the deconstructed dicts should be reported
- # as a change
- changes = self.get_changes(
- [self.author_name_deconstructible_dict_1],
- [self.author_name_deconstructible_dict_3],
- )
- self.assertEqual(len(changes), 1)
- def test_nested_deconstructible_objects(self):
- """
- Nested deconstruction is applied recursively to the args/kwargs of
- deconstructed objects.
- """
- # If the items within a deconstructed object's args/kwargs have the same
- # deconstructed values - whether or not the items themselves are different
- # instances - then the object as a whole is regarded as unchanged.
- changes = self.get_changes(
- [self.author_name_nested_deconstructible_1],
- [self.author_name_nested_deconstructible_2],
- )
- self.assertEqual(changes, {})
- # Differences that exist solely within the args list of a deconstructed object
- # should be reported as changes
- changes = self.get_changes(
- [self.author_name_nested_deconstructible_1],
- [self.author_name_nested_deconstructible_changed_arg],
- )
- self.assertEqual(len(changes), 1)
- # Additional args should also be reported as a change
- changes = self.get_changes(
- [self.author_name_nested_deconstructible_1],
- [self.author_name_nested_deconstructible_extra_arg],
- )
- self.assertEqual(len(changes), 1)
- # Differences that exist solely within the kwargs dict of a deconstructed object
- # should be reported as changes
- changes = self.get_changes(
- [self.author_name_nested_deconstructible_1],
- [self.author_name_nested_deconstructible_changed_kwarg],
- )
- self.assertEqual(len(changes), 1)
- # Additional kwargs should also be reported as a change
- changes = self.get_changes(
- [self.author_name_nested_deconstructible_1],
- [self.author_name_nested_deconstructible_extra_kwarg],
- )
- self.assertEqual(len(changes), 1)
- def test_deconstruct_type(self):
- """
- #22951 -- Uninstantiated classes with deconstruct are correctly returned
- by deep_deconstruct during serialization.
- """
- author = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "name",
- models.CharField(
- max_length=200,
- # IntegerField intentionally not instantiated.
- default=models.IntegerField,
- ),
- ),
- ],
- )
- changes = self.get_changes([], [author])
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["CreateModel"])
- def test_replace_string_with_foreignkey(self):
- """
- #22300 - Adding an FK in the same "spot" as a deleted CharField should
- work.
- """
- changes = self.get_changes(
- [self.author_with_publisher_string],
- [self.author_with_publisher, self.publisher],
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(
- changes, "testapp", 0, ["CreateModel", "RemoveField", "AddField"]
- )
- self.assertOperationAttributes(changes, "testapp", 0, 0, name="Publisher")
- self.assertOperationAttributes(changes, "testapp", 0, 1, name="publisher_name")
- self.assertOperationAttributes(changes, "testapp", 0, 2, name="publisher")
- def test_foreign_key_removed_before_target_model(self):
- """
- Removing an FK and the model it targets in the same change must remove
- the FK field before the model to maintain consistency.
- """
- changes = self.get_changes(
- [self.author_with_publisher, self.publisher], [self.author_name]
- ) # removes both the model and FK
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["RemoveField", "DeleteModel"])
- self.assertOperationAttributes(changes, "testapp", 0, 0, name="publisher")
- self.assertOperationAttributes(changes, "testapp", 0, 1, name="Publisher")
- @mock.patch(
- "django.db.migrations.questioner.MigrationQuestioner.ask_not_null_addition",
- side_effect=AssertionError("Should not have prompted for not null addition"),
- )
- def test_add_many_to_many(self, mocked_ask_method):
- """#22435 - Adding a ManyToManyField should not prompt for a default."""
- changes = self.get_changes(
- [self.author_empty, self.publisher], [self.author_with_m2m, self.publisher]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["AddField"])
- self.assertOperationAttributes(changes, "testapp", 0, 0, name="publishers")
- def test_alter_many_to_many(self):
- changes = self.get_changes(
- [self.author_with_m2m, self.publisher],
- [self.author_with_m2m_blank, self.publisher],
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["AlterField"])
- self.assertOperationAttributes(changes, "testapp", 0, 0, name="publishers")
- def test_create_with_through_model(self):
- """
- Adding a m2m with a through model and the models that use it should be
- ordered correctly.
- """
- changes = self.get_changes(
- [], [self.author_with_m2m_through, self.publisher, self.contract]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(
- changes,
- "testapp",
- 0,
- [
- "CreateModel",
- "CreateModel",
- "CreateModel",
- "AddField",
- ],
- )
- self.assertOperationAttributes(changes, "testapp", 0, 0, name="Author")
- self.assertOperationAttributes(changes, "testapp", 0, 1, name="Publisher")
- self.assertOperationAttributes(changes, "testapp", 0, 2, name="Contract")
- self.assertOperationAttributes(
- changes, "testapp", 0, 3, model_name="author", name="publishers"
- )
- def test_many_to_many_removed_before_through_model(self):
- """
- Removing a ManyToManyField and the "through" model in the same change
- must remove the field before the model to maintain consistency.
- """
- changes = self.get_changes(
- [
- self.book_with_multiple_authors_through_attribution,
- self.author_name,
- self.attribution,
- ],
- [self.book_with_no_author, self.author_name],
- )
- # Remove both the through model and ManyToMany
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(
- changes, "otherapp", 0, ["RemoveField", "DeleteModel"]
- )
- self.assertOperationAttributes(
- changes, "otherapp", 0, 0, name="authors", model_name="book"
- )
- self.assertOperationAttributes(changes, "otherapp", 0, 1, name="Attribution")
- def test_many_to_many_removed_before_through_model_2(self):
- """
- Removing a model that contains a ManyToManyField and the "through" model
- in the same change must remove the field before the model to maintain
- consistency.
- """
- changes = self.get_changes(
- [
- self.book_with_multiple_authors_through_attribution,
- self.author_name,
- self.attribution,
- ],
- [self.author_name],
- )
- # Remove both the through model and ManyToMany
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(
- changes, "otherapp", 0, ["RemoveField", "DeleteModel", "DeleteModel"]
- )
- self.assertOperationAttributes(
- changes, "otherapp", 0, 0, name="authors", model_name="book"
- )
- self.assertOperationAttributes(changes, "otherapp", 0, 1, name="Attribution")
- self.assertOperationAttributes(changes, "otherapp", 0, 2, name="Book")
- def test_m2m_w_through_multistep_remove(self):
- """
- A model with a m2m field that specifies a "through" model cannot be
- removed in the same migration as that through model as the schema will
- pass through an inconsistent state. The autodetector should produce two
- migrations to avoid this issue.
- """
- changes = self.get_changes(
- [self.author_with_m2m_through, self.publisher, self.contract],
- [self.publisher],
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(
- changes,
- "testapp",
- 0,
- ["RemoveField", "RemoveField", "DeleteModel", "DeleteModel"],
- )
- self.assertOperationAttributes(
- changes, "testapp", 0, 0, name="author", model_name="contract"
- )
- self.assertOperationAttributes(
- changes, "testapp", 0, 1, name="publisher", model_name="contract"
- )
- self.assertOperationAttributes(changes, "testapp", 0, 2, name="Author")
- self.assertOperationAttributes(changes, "testapp", 0, 3, name="Contract")
- def test_concrete_field_changed_to_many_to_many(self):
- """
- #23938 - Changing a concrete field into a ManyToManyField
- first removes the concrete field and then adds the m2m field.
- """
- changes = self.get_changes(
- [self.author_with_former_m2m], [self.author_with_m2m, self.publisher]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(
- changes, "testapp", 0, ["CreateModel", "RemoveField", "AddField"]
- )
- self.assertOperationAttributes(changes, "testapp", 0, 0, name="Publisher")
- self.assertOperationAttributes(
- changes, "testapp", 0, 1, name="publishers", model_name="author"
- )
- self.assertOperationAttributes(
- changes, "testapp", 0, 2, name="publishers", model_name="author"
- )
- def test_many_to_many_changed_to_concrete_field(self):
- """
- #23938 - Changing a ManyToManyField into a concrete field
- first removes the m2m field and then adds the concrete field.
- """
- changes = self.get_changes(
- [self.author_with_m2m, self.publisher], [self.author_with_former_m2m]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(
- changes, "testapp", 0, ["RemoveField", "DeleteModel", "AddField"]
- )
- self.assertOperationAttributes(
- changes, "testapp", 0, 0, name="publishers", model_name="author"
- )
- self.assertOperationAttributes(changes, "testapp", 0, 1, name="Publisher")
- self.assertOperationAttributes(
- changes, "testapp", 0, 2, name="publishers", model_name="author"
- )
- self.assertOperationFieldAttributes(changes, "testapp", 0, 2, max_length=100)
- def test_non_circular_foreignkey_dependency_removal(self):
- """
- If two models with a ForeignKey from one to the other are removed at the
- same time, the autodetector should remove them in the correct order.
- """
- changes = self.get_changes(
- [self.author_with_publisher, self.publisher_with_author], []
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(
- changes, "testapp", 0, ["RemoveField", "DeleteModel", "DeleteModel"]
- )
- self.assertOperationAttributes(
- changes, "testapp", 0, 0, name="author", model_name="publisher"
- )
- self.assertOperationAttributes(changes, "testapp", 0, 1, name="Author")
- self.assertOperationAttributes(changes, "testapp", 0, 2, name="Publisher")
- def test_alter_model_options(self):
- """Changing a model's options should make a change."""
- changes = self.get_changes([self.author_empty], [self.author_with_options])
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["AlterModelOptions"])
- self.assertOperationAttributes(
- changes,
- "testapp",
- 0,
- 0,
- options={
- "permissions": [("can_hire", "Can hire")],
- "verbose_name": "Authi",
- },
- )
- # Changing them back to empty should also make a change
- changes = self.get_changes([self.author_with_options], [self.author_empty])
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["AlterModelOptions"])
- self.assertOperationAttributes(
- changes, "testapp", 0, 0, name="author", options={}
- )
- def test_alter_model_options_proxy(self):
- """Changing a proxy model's options should also make a change."""
- changes = self.get_changes(
- [self.author_proxy, self.author_empty],
- [self.author_proxy_options, self.author_empty],
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["AlterModelOptions"])
- self.assertOperationAttributes(
- changes,
- "testapp",
- 0,
- 0,
- name="authorproxy",
- options={"verbose_name": "Super Author"},
- )
- def test_set_alter_order_with_respect_to(self):
- """Setting order_with_respect_to adds a field."""
- changes = self.get_changes(
- [self.book, self.author_with_book],
- [self.book, self.author_with_book_order_wrt],
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["AlterOrderWithRespectTo"])
- self.assertOperationAttributes(
- changes, "testapp", 0, 0, name="author", order_with_respect_to="book"
- )
- def test_add_alter_order_with_respect_to(self):
- """
- Setting order_with_respect_to when adding the FK too does
- things in the right order.
- """
- changes = self.get_changes(
- [self.author_name], [self.book, self.author_with_book_order_wrt]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(
- changes, "testapp", 0, ["AddField", "AlterOrderWithRespectTo"]
- )
- self.assertOperationAttributes(
- changes, "testapp", 0, 0, model_name="author", name="book"
- )
- self.assertOperationAttributes(
- changes, "testapp", 0, 1, name="author", order_with_respect_to="book"
- )
- def test_remove_alter_order_with_respect_to(self):
- """
- Removing order_with_respect_to when removing the FK too does
- things in the right order.
- """
- changes = self.get_changes(
- [self.book, self.author_with_book_order_wrt], [self.author_name]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(
- changes, "testapp", 0, ["AlterOrderWithRespectTo", "RemoveField"]
- )
- self.assertOperationAttributes(
- changes, "testapp", 0, 0, name="author", order_with_respect_to=None
- )
- self.assertOperationAttributes(
- changes, "testapp", 0, 1, model_name="author", name="book"
- )
- def test_add_model_order_with_respect_to(self):
- """
- Setting order_with_respect_to when adding the whole model
- does things in the right order.
- """
- changes = self.get_changes([], [self.book, self.author_with_book_order_wrt])
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["CreateModel"])
- self.assertOperationAttributes(
- changes,
- "testapp",
- 0,
- 0,
- name="Author",
- options={"order_with_respect_to": "book"},
- )
- self.assertNotIn(
- "_order",
- [name for name, field in changes["testapp"][0].operations[0].fields],
- )
- def test_add_model_order_with_respect_to_unique_together(self):
- changes = self.get_changes(
- [],
- [
- self.book,
- ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200)),
- ("book", models.ForeignKey("otherapp.Book", models.CASCADE)),
- ],
- options={
- "order_with_respect_to": "book",
- "unique_together": {("id", "_order")},
- },
- ),
- ],
- )
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["CreateModel"])
- self.assertOperationAttributes(
- changes,
- "testapp",
- 0,
- 0,
- name="Author",
- options={
- "order_with_respect_to": "book",
- "unique_together": {("id", "_order")},
- },
- )
- def test_add_model_order_with_respect_to_index_constraint(self):
- tests = [
- (
- "AddIndex",
- {
- "indexes": [
- models.Index(fields=["_order"], name="book_order_idx"),
- ]
- },
- ),
- (
- "AddConstraint",
- {
- "constraints": [
- models.CheckConstraint(
- check=models.Q(_order__gt=1),
- name="book_order_gt_1",
- ),
- ]
- },
- ),
- ]
- for operation, extra_option in tests:
- with self.subTest(operation=operation):
- after = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200)),
- ("book", models.ForeignKey("otherapp.Book", models.CASCADE)),
- ],
- options={
- "order_with_respect_to": "book",
- **extra_option,
- },
- )
- changes = self.get_changes([], [self.book, after])
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(
- changes,
- "testapp",
- 0,
- [
- "CreateModel",
- operation,
- ],
- )
- self.assertOperationAttributes(
- changes,
- "testapp",
- 0,
- 0,
- name="Author",
- options={"order_with_respect_to": "book"},
- )
- def test_set_alter_order_with_respect_to_index_constraint_unique_together(self):
- tests = [
- (
- "AddIndex",
- {
- "indexes": [
- models.Index(fields=["_order"], name="book_order_idx"),
- ]
- },
- ),
- (
- "AddConstraint",
- {
- "constraints": [
- models.CheckConstraint(
- check=models.Q(_order__gt=1),
- name="book_order_gt_1",
- ),
- ]
- },
- ),
- ("AlterUniqueTogether", {"unique_together": {("id", "_order")}}),
- ]
- for operation, extra_option in tests:
- with self.subTest(operation=operation):
- after = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200)),
- ("book", models.ForeignKey("otherapp.Book", models.CASCADE)),
- ],
- options={
- "order_with_respect_to": "book",
- **extra_option,
- },
- )
- changes = self.get_changes(
- [self.book, self.author_with_book],
- [self.book, after],
- )
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(
- changes,
- "testapp",
- 0,
- [
- "AlterOrderWithRespectTo",
- operation,
- ],
- )
- def test_alter_model_managers(self):
- """
- Changing the model managers adds a new operation.
- """
- changes = self.get_changes([self.other_pony], [self.other_pony_food])
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(changes, "otherapp", 0, ["AlterModelManagers"])
- self.assertOperationAttributes(changes, "otherapp", 0, 0, name="pony")
- self.assertEqual(
- [name for name, mgr in changes["otherapp"][0].operations[0].managers],
- ["food_qs", "food_mgr", "food_mgr_kwargs"],
- )
- self.assertEqual(
- changes["otherapp"][0].operations[0].managers[1][1].args, ("a", "b", 1, 2)
- )
- self.assertEqual(
- changes["otherapp"][0].operations[0].managers[2][1].args, ("x", "y", 3, 4)
- )
- def test_swappable_first_inheritance(self):
- """Swappable models get their CreateModel first."""
- changes = self.get_changes([], [self.custom_user, self.aardvark])
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "thirdapp", 1)
- self.assertOperationTypes(
- changes, "thirdapp", 0, ["CreateModel", "CreateModel"]
- )
- self.assertOperationAttributes(changes, "thirdapp", 0, 0, name="CustomUser")
- self.assertOperationAttributes(changes, "thirdapp", 0, 1, name="Aardvark")
- def test_default_related_name_option(self):
- model_state = ModelState(
- "app",
- "model",
- [
- ("id", models.AutoField(primary_key=True)),
- ],
- options={"default_related_name": "related_name"},
- )
- changes = self.get_changes([], [model_state])
- self.assertNumberMigrations(changes, "app", 1)
- self.assertOperationTypes(changes, "app", 0, ["CreateModel"])
- self.assertOperationAttributes(
- changes,
- "app",
- 0,
- 0,
- name="model",
- options={"default_related_name": "related_name"},
- )
- altered_model_state = ModelState(
- "app",
- "Model",
- [
- ("id", models.AutoField(primary_key=True)),
- ],
- )
- changes = self.get_changes([model_state], [altered_model_state])
- self.assertNumberMigrations(changes, "app", 1)
- self.assertOperationTypes(changes, "app", 0, ["AlterModelOptions"])
- self.assertOperationAttributes(changes, "app", 0, 0, name="model", options={})
- @override_settings(AUTH_USER_MODEL="thirdapp.CustomUser")
- def test_swappable_first_setting(self):
- """Swappable models get their CreateModel first."""
- with isolate_lru_cache(apps.get_swappable_settings_name):
- changes = self.get_changes([], [self.custom_user_no_inherit, self.aardvark])
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "thirdapp", 1)
- self.assertOperationTypes(
- changes, "thirdapp", 0, ["CreateModel", "CreateModel"]
- )
- self.assertOperationAttributes(changes, "thirdapp", 0, 0, name="CustomUser")
- self.assertOperationAttributes(changes, "thirdapp", 0, 1, name="Aardvark")
- def test_bases_first(self):
- """Bases of other models come first."""
- changes = self.get_changes(
- [], [self.aardvark_based_on_author, self.author_name]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["CreateModel", "CreateModel"])
- self.assertOperationAttributes(changes, "testapp", 0, 0, name="Author")
- self.assertOperationAttributes(changes, "testapp", 0, 1, name="Aardvark")
- def test_bases_first_mixed_case_app_label(self):
- app_label = "MiXedCaseApp"
- changes = self.get_changes(
- [],
- [
- ModelState(
- app_label,
- "owner",
- [
- ("id", models.AutoField(primary_key=True)),
- ],
- ),
- ModelState(
- app_label,
- "place",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "owner",
- models.ForeignKey("MiXedCaseApp.owner", models.CASCADE),
- ),
- ],
- ),
- ModelState(app_label, "restaurant", [], bases=("MiXedCaseApp.place",)),
- ],
- )
- self.assertNumberMigrations(changes, app_label, 1)
- self.assertOperationTypes(
- changes,
- app_label,
- 0,
- [
- "CreateModel",
- "CreateModel",
- "CreateModel",
- ],
- )
- self.assertOperationAttributes(changes, app_label, 0, 0, name="owner")
- self.assertOperationAttributes(changes, app_label, 0, 1, name="place")
- self.assertOperationAttributes(changes, app_label, 0, 2, name="restaurant")
- def test_multiple_bases(self):
- """
- Inheriting models doesn't move *_ptr fields into AddField operations.
- """
- A = ModelState("app", "A", [("a_id", models.AutoField(primary_key=True))])
- B = ModelState("app", "B", [("b_id", models.AutoField(primary_key=True))])
- C = ModelState("app", "C", [], bases=("app.A", "app.B"))
- D = ModelState("app", "D", [], bases=("app.A", "app.B"))
- E = ModelState("app", "E", [], bases=("app.A", "app.B"))
- changes = self.get_changes([], [A, B, C, D, E])
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "app", 1)
- self.assertOperationTypes(
- changes,
- "app",
- 0,
- ["CreateModel", "CreateModel", "CreateModel", "CreateModel", "CreateModel"],
- )
- self.assertOperationAttributes(changes, "app", 0, 0, name="A")
- self.assertOperationAttributes(changes, "app", 0, 1, name="B")
- self.assertOperationAttributes(changes, "app", 0, 2, name="C")
- self.assertOperationAttributes(changes, "app", 0, 3, name="D")
- self.assertOperationAttributes(changes, "app", 0, 4, name="E")
- def test_proxy_bases_first(self):
- """Bases of proxies come first."""
- changes = self.get_changes(
- [], [self.author_empty, self.author_proxy, self.author_proxy_proxy]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(
- changes, "testapp", 0, ["CreateModel", "CreateModel", "CreateModel"]
- )
- self.assertOperationAttributes(changes, "testapp", 0, 0, name="Author")
- self.assertOperationAttributes(changes, "testapp", 0, 1, name="AuthorProxy")
- self.assertOperationAttributes(
- changes, "testapp", 0, 2, name="AAuthorProxyProxy"
- )
- def test_pk_fk_included(self):
- """
- A relation used as the primary key is kept as part of CreateModel.
- """
- changes = self.get_changes([], [self.aardvark_pk_fk_author, self.author_name])
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["CreateModel", "CreateModel"])
- self.assertOperationAttributes(changes, "testapp", 0, 0, name="Author")
- self.assertOperationAttributes(changes, "testapp", 0, 1, name="Aardvark")
- def test_first_dependency(self):
- """
- A dependency to an app with no migrations uses __first__.
- """
- # Load graph
- loader = MigrationLoader(connection)
- before = self.make_project_state([])
- after = self.make_project_state([self.book_migrations_fk])
- after.real_apps = {"migrations"}
- autodetector = MigrationAutodetector(before, after)
- changes = autodetector._detect_changes(graph=loader.graph)
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(changes, "otherapp", 0, ["CreateModel"])
- self.assertOperationAttributes(changes, "otherapp", 0, 0, name="Book")
- self.assertMigrationDependencies(
- changes, "otherapp", 0, [("migrations", "__first__")]
- )
- @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"})
- def test_last_dependency(self):
- """
- A dependency to an app with existing migrations uses the
- last migration of that app.
- """
- # Load graph
- loader = MigrationLoader(connection)
- before = self.make_project_state([])
- after = self.make_project_state([self.book_migrations_fk])
- after.real_apps = {"migrations"}
- autodetector = MigrationAutodetector(before, after)
- changes = autodetector._detect_changes(graph=loader.graph)
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(changes, "otherapp", 0, ["CreateModel"])
- self.assertOperationAttributes(changes, "otherapp", 0, 0, name="Book")
- self.assertMigrationDependencies(
- changes, "otherapp", 0, [("migrations", "0002_second")]
- )
- def test_alter_fk_before_model_deletion(self):
- """
- ForeignKeys are altered _before_ the model they used to
- refer to are deleted.
- """
- changes = self.get_changes(
- [self.author_name, self.publisher_with_author],
- [self.aardvark_testapp, self.publisher_with_aardvark_author],
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(
- changes, "testapp", 0, ["CreateModel", "AlterField", "DeleteModel"]
- )
- self.assertOperationAttributes(changes, "testapp", 0, 0, name="Aardvark")
- self.assertOperationAttributes(changes, "testapp", 0, 1, name="author")
- self.assertOperationAttributes(changes, "testapp", 0, 2, name="Author")
- def test_fk_dependency_other_app(self):
- """
- #23100 - ForeignKeys correctly depend on other apps' models.
- """
- changes = self.get_changes(
- [self.author_name, self.book], [self.author_with_book, self.book]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["AddField"])
- self.assertOperationAttributes(changes, "testapp", 0, 0, name="book")
- self.assertMigrationDependencies(
- changes, "testapp", 0, [("otherapp", "__first__")]
- )
- def test_alter_unique_together_fk_to_m2m(self):
- changes = self.get_changes(
- [self.author_name, self.book_unique_together],
- [
- self.author_name,
- ModelState(
- "otherapp",
- "Book",
- [
- ("id", models.AutoField(primary_key=True)),
- ("author", models.ManyToManyField("testapp.Author")),
- ("title", models.CharField(max_length=200)),
- ],
- ),
- ],
- )
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(
- changes, "otherapp", 0, ["AlterUniqueTogether", "RemoveField", "AddField"]
- )
- self.assertOperationAttributes(
- changes, "otherapp", 0, 0, name="book", unique_together=set()
- )
- self.assertOperationAttributes(
- changes, "otherapp", 0, 1, model_name="book", name="author"
- )
- self.assertOperationAttributes(
- changes, "otherapp", 0, 2, model_name="book", name="author"
- )
- def test_alter_field_to_fk_dependency_other_app(self):
- changes = self.get_changes(
- [self.author_empty, self.book_with_no_author_fk],
- [self.author_empty, self.book],
- )
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(changes, "otherapp", 0, ["AlterField"])
- self.assertMigrationDependencies(
- changes, "otherapp", 0, [("testapp", "__first__")]
- )
- def test_circular_dependency_mixed_addcreate(self):
- """
- #23315 - The dependency resolver knows to put all CreateModel
- before AddField and not become unsolvable.
- """
- address = ModelState(
- "a",
- "Address",
- [
- ("id", models.AutoField(primary_key=True)),
- ("country", models.ForeignKey("b.DeliveryCountry", models.CASCADE)),
- ],
- )
- person = ModelState(
- "a",
- "Person",
- [
- ("id", models.AutoField(primary_key=True)),
- ],
- )
- apackage = ModelState(
- "b",
- "APackage",
- [
- ("id", models.AutoField(primary_key=True)),
- ("person", models.ForeignKey("a.Person", models.CASCADE)),
- ],
- )
- country = ModelState(
- "b",
- "DeliveryCountry",
- [
- ("id", models.AutoField(primary_key=True)),
- ],
- )
- changes = self.get_changes([], [address, person, apackage, country])
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "a", 2)
- self.assertNumberMigrations(changes, "b", 1)
- self.assertOperationTypes(changes, "a", 0, ["CreateModel", "CreateModel"])
- self.assertOperationTypes(changes, "a", 1, ["AddField"])
- self.assertOperationTypes(changes, "b", 0, ["CreateModel", "CreateModel"])
- @override_settings(AUTH_USER_MODEL="a.Tenant")
- def test_circular_dependency_swappable(self):
- """
- #23322 - The dependency resolver knows to explicitly resolve
- swappable models.
- """
- with isolate_lru_cache(apps.get_swappable_settings_name):
- tenant = ModelState(
- "a",
- "Tenant",
- [
- ("id", models.AutoField(primary_key=True)),
- ("primary_address", models.ForeignKey("b.Address", models.CASCADE)),
- ],
- bases=(AbstractBaseUser,),
- )
- address = ModelState(
- "b",
- "Address",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "tenant",
- models.ForeignKey(settings.AUTH_USER_MODEL, models.CASCADE),
- ),
- ],
- )
- changes = self.get_changes([], [address, tenant])
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "a", 2)
- self.assertOperationTypes(changes, "a", 0, ["CreateModel"])
- self.assertOperationTypes(changes, "a", 1, ["AddField"])
- self.assertMigrationDependencies(changes, "a", 0, [])
- self.assertMigrationDependencies(
- changes, "a", 1, [("a", "auto_1"), ("b", "auto_1")]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "b", 1)
- self.assertOperationTypes(changes, "b", 0, ["CreateModel"])
- self.assertMigrationDependencies(
- changes, "b", 0, [("__setting__", "AUTH_USER_MODEL")]
- )
- @override_settings(AUTH_USER_MODEL="b.Tenant")
- def test_circular_dependency_swappable2(self):
- """
- #23322 - The dependency resolver knows to explicitly resolve
- swappable models but with the swappable not being the first migrated
- model.
- """
- with isolate_lru_cache(apps.get_swappable_settings_name):
- address = ModelState(
- "a",
- "Address",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "tenant",
- models.ForeignKey(settings.AUTH_USER_MODEL, models.CASCADE),
- ),
- ],
- )
- tenant = ModelState(
- "b",
- "Tenant",
- [
- ("id", models.AutoField(primary_key=True)),
- ("primary_address", models.ForeignKey("a.Address", models.CASCADE)),
- ],
- bases=(AbstractBaseUser,),
- )
- changes = self.get_changes([], [address, tenant])
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "a", 2)
- self.assertOperationTypes(changes, "a", 0, ["CreateModel"])
- self.assertOperationTypes(changes, "a", 1, ["AddField"])
- self.assertMigrationDependencies(changes, "a", 0, [])
- self.assertMigrationDependencies(
- changes, "a", 1, [("__setting__", "AUTH_USER_MODEL"), ("a", "auto_1")]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "b", 1)
- self.assertOperationTypes(changes, "b", 0, ["CreateModel"])
- self.assertMigrationDependencies(changes, "b", 0, [("a", "auto_1")])
- @override_settings(AUTH_USER_MODEL="a.Person")
- def test_circular_dependency_swappable_self(self):
- """
- #23322 - The dependency resolver knows to explicitly resolve
- swappable models.
- """
- with isolate_lru_cache(apps.get_swappable_settings_name):
- person = ModelState(
- "a",
- "Person",
- [
- ("id", models.AutoField(primary_key=True)),
- (
- "parent1",
- models.ForeignKey(
- settings.AUTH_USER_MODEL,
- models.CASCADE,
- related_name="children",
- ),
- ),
- ],
- )
- changes = self.get_changes([], [person])
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "a", 1)
- self.assertOperationTypes(changes, "a", 0, ["CreateModel"])
- self.assertMigrationDependencies(changes, "a", 0, [])
- @override_settings(AUTH_USER_MODEL="a.User")
- def test_swappable_circular_multi_mti(self):
- with isolate_lru_cache(apps.get_swappable_settings_name):
- parent = ModelState(
- "a",
- "Parent",
- [("user", models.ForeignKey(settings.AUTH_USER_MODEL, models.CASCADE))],
- )
- child = ModelState("a", "Child", [], bases=("a.Parent",))
- user = ModelState("a", "User", [], bases=(AbstractBaseUser, "a.Child"))
- changes = self.get_changes([], [parent, child, user])
- self.assertNumberMigrations(changes, "a", 1)
- self.assertOperationTypes(
- changes, "a", 0, ["CreateModel", "CreateModel", "CreateModel", "AddField"]
- )
- @mock.patch(
- "django.db.migrations.questioner.MigrationQuestioner.ask_not_null_addition",
- side_effect=AssertionError("Should not have prompted for not null addition"),
- )
- def test_add_blank_textfield_and_charfield(self, mocked_ask_method):
- """
- #23405 - Adding a NOT NULL and blank `CharField` or `TextField`
- without default should not prompt for a default.
- """
- changes = self.get_changes(
- [self.author_empty], [self.author_with_biography_blank]
- )
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["AddField", "AddField"])
- self.assertOperationAttributes(changes, "testapp", 0, 0)
- @mock.patch(
- "django.db.migrations.questioner.MigrationQuestioner.ask_not_null_addition"
- )
- def test_add_non_blank_textfield_and_charfield(self, mocked_ask_method):
- """
- #23405 - Adding a NOT NULL and non-blank `CharField` or `TextField`
- without default should prompt for a default.
- """
- changes = self.get_changes(
- [self.author_empty], [self.author_with_biography_non_blank]
- )
- self.assertEqual(mocked_ask_method.call_count, 2)
- # Right number/type of migrations?
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["AddField", "AddField"])
- self.assertOperationAttributes(changes, "testapp", 0, 0)
- def test_mti_inheritance_model_removal(self):
- Animal = ModelState(
- "app",
- "Animal",
- [
- ("id", models.AutoField(primary_key=True)),
- ],
- )
- Dog = ModelState("app", "Dog", [], bases=("app.Animal",))
- changes = self.get_changes([Animal, Dog], [Animal])
- self.assertNumberMigrations(changes, "app", 1)
- self.assertOperationTypes(changes, "app", 0, ["DeleteModel"])
- self.assertOperationAttributes(changes, "app", 0, 0, name="Dog")
- def test_add_model_with_field_removed_from_base_model(self):
- """
- Removing a base field takes place before adding a new inherited model
- that has a field with the same name.
- """
- before = [
- ModelState(
- "app",
- "readable",
- [
- ("id", models.AutoField(primary_key=True)),
- ("title", models.CharField(max_length=200)),
- ],
- ),
- ]
- after = [
- ModelState(
- "app",
- "readable",
- [
- ("id", models.AutoField(primary_key=True)),
- ],
- ),
- ModelState(
- "app",
- "book",
- [
- ("title", models.CharField(max_length=200)),
- ],
- bases=("app.readable",),
- ),
- ]
- changes = self.get_changes(before, after)
- self.assertNumberMigrations(changes, "app", 1)
- self.assertOperationTypes(changes, "app", 0, ["RemoveField", "CreateModel"])
- self.assertOperationAttributes(
- changes, "app", 0, 0, name="title", model_name="readable"
- )
- self.assertOperationAttributes(changes, "app", 0, 1, name="book")
- def test_parse_number(self):
- tests = [
- ("no_number", None),
- ("0001_initial", 1),
- ("0002_model3", 2),
- ("0002_auto_20380101_1112", 2),
- ("0002_squashed_0003", 3),
- ("0002_model2_squashed_0003_other4", 3),
- ("0002_squashed_0003_squashed_0004", 4),
- ("0002_model2_squashed_0003_other4_squashed_0005_other6", 5),
- ("0002_custom_name_20380101_1112_squashed_0003_model", 3),
- ("2_squashed_4", 4),
- ]
- for migration_name, expected_number in tests:
- with self.subTest(migration_name=migration_name):
- self.assertEqual(
- MigrationAutodetector.parse_number(migration_name),
- expected_number,
- )
- def test_add_custom_fk_with_hardcoded_to(self):
- class HardcodedForeignKey(models.ForeignKey):
- def __init__(self, *args, **kwargs):
- kwargs["to"] = "testapp.Author"
- super().__init__(*args, **kwargs)
- def deconstruct(self):
- name, path, args, kwargs = super().deconstruct()
- del kwargs["to"]
- return name, path, args, kwargs
- book_hardcoded_fk_to = ModelState(
- "testapp",
- "Book",
- [
- ("author", HardcodedForeignKey(on_delete=models.CASCADE)),
- ],
- )
- changes = self.get_changes(
- [self.author_empty],
- [self.author_empty, book_hardcoded_fk_to],
- )
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["CreateModel"])
- self.assertOperationAttributes(changes, "testapp", 0, 0, name="Book")
- @ignore_warnings(category=RemovedInDjango51Warning)
- class AutodetectorIndexTogetherTests(BaseAutodetectorTests):
- book_index_together = ModelState(
- "otherapp",
- "Book",
- [
- ("id", models.AutoField(primary_key=True)),
- ("author", models.ForeignKey("testapp.Author", models.CASCADE)),
- ("title", models.CharField(max_length=200)),
- ],
- {
- "index_together": {("author", "title")},
- },
- )
- book_index_together_2 = ModelState(
- "otherapp",
- "Book",
- [
- ("id", models.AutoField(primary_key=True)),
- ("author", models.ForeignKey("testapp.Author", models.CASCADE)),
- ("title", models.CharField(max_length=200)),
- ],
- {
- "index_together": {("title", "author")},
- },
- )
- book_index_together_3 = ModelState(
- "otherapp",
- "Book",
- [
- ("id", models.AutoField(primary_key=True)),
- ("newfield", models.IntegerField()),
- ("author", models.ForeignKey("testapp.Author", models.CASCADE)),
- ("title", models.CharField(max_length=200)),
- ],
- {
- "index_together": {("title", "newfield")},
- },
- )
- book_index_together_4 = ModelState(
- "otherapp",
- "Book",
- [
- ("id", models.AutoField(primary_key=True)),
- ("newfield2", models.IntegerField()),
- ("author", models.ForeignKey("testapp.Author", models.CASCADE)),
- ("title", models.CharField(max_length=200)),
- ],
- {
- "index_together": {("title", "newfield2")},
- },
- )
- def test_empty_index_together(self):
- """Empty index_together shouldn't generate a migration."""
- # Explicitly testing for not specified, since this is the case after
- # a CreateModel operation w/o any definition on the original model
- model_state_not_specified = ModelState(
- "a", "model", [("id", models.AutoField(primary_key=True))]
- )
- # Explicitly testing for None, since this was the issue in #23452 after
- # an AlterIndexTogether operation with e.g. () as value
- model_state_none = ModelState(
- "a",
- "model",
- [("id", models.AutoField(primary_key=True))],
- {
- "index_together": None,
- },
- )
- # Explicitly testing for the empty set, since we now always have sets.
- # During removal (('col1', 'col2'),) --> () this becomes set([])
- model_state_empty = ModelState(
- "a",
- "model",
- [("id", models.AutoField(primary_key=True))],
- {
- "index_together": set(),
- },
- )
- def test(from_state, to_state, msg):
- changes = self.get_changes([from_state], [to_state])
- if changes:
- ops = ", ".join(
- o.__class__.__name__ for o in changes["a"][0].operations
- )
- self.fail("Created operation(s) %s from %s" % (ops, msg))
- tests = (
- (
- model_state_not_specified,
- model_state_not_specified,
- '"not specified" to "not specified"',
- ),
- (model_state_not_specified, model_state_none, '"not specified" to "None"'),
- (
- model_state_not_specified,
- model_state_empty,
- '"not specified" to "empty"',
- ),
- (model_state_none, model_state_not_specified, '"None" to "not specified"'),
- (model_state_none, model_state_none, '"None" to "None"'),
- (model_state_none, model_state_empty, '"None" to "empty"'),
- (
- model_state_empty,
- model_state_not_specified,
- '"empty" to "not specified"',
- ),
- (model_state_empty, model_state_none, '"empty" to "None"'),
- (model_state_empty, model_state_empty, '"empty" to "empty"'),
- )
- for t in tests:
- test(*t)
- def test_rename_index_together_to_index(self):
- changes = self.get_changes(
- [AutodetectorTests.author_empty, self.book_index_together],
- [AutodetectorTests.author_empty, AutodetectorTests.book_indexes],
- )
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(changes, "otherapp", 0, ["RenameIndex"])
- self.assertOperationAttributes(
- changes,
- "otherapp",
- 0,
- 0,
- model_name="book",
- new_name="book_title_author_idx",
- old_fields=("author", "title"),
- )
- def test_rename_index_together_to_index_extra_options(self):
- # Indexes with extra options don't match indexes in index_together.
- book_partial_index = ModelState(
- "otherapp",
- "Book",
- [
- ("id", models.AutoField(primary_key=True)),
- ("author", models.ForeignKey("testapp.Author", models.CASCADE)),
- ("title", models.CharField(max_length=200)),
- ],
- {
- "indexes": [
- models.Index(
- fields=["author", "title"],
- condition=models.Q(title__startswith="The"),
- name="book_title_author_idx",
- )
- ],
- },
- )
- changes = self.get_changes(
- [AutodetectorTests.author_empty, self.book_index_together],
- [AutodetectorTests.author_empty, book_partial_index],
- )
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(
- changes,
- "otherapp",
- 0,
- ["AlterIndexTogether", "AddIndex"],
- )
- def test_rename_index_together_to_index_order_fields(self):
- # Indexes with reordered fields don't match indexes in index_together.
- changes = self.get_changes(
- [AutodetectorTests.author_empty, self.book_index_together],
- [AutodetectorTests.author_empty, AutodetectorTests.book_unordered_indexes],
- )
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(
- changes,
- "otherapp",
- 0,
- ["AlterIndexTogether", "AddIndex"],
- )
- def test_add_index_together(self):
- changes = self.get_changes(
- [AutodetectorTests.author_empty, AutodetectorTests.book],
- [AutodetectorTests.author_empty, self.book_index_together],
- )
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(changes, "otherapp", 0, ["AlterIndexTogether"])
- self.assertOperationAttributes(
- changes, "otherapp", 0, 0, name="book", index_together={("author", "title")}
- )
- def test_remove_index_together(self):
- changes = self.get_changes(
- [AutodetectorTests.author_empty, self.book_index_together],
- [AutodetectorTests.author_empty, AutodetectorTests.book],
- )
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(changes, "otherapp", 0, ["AlterIndexTogether"])
- self.assertOperationAttributes(
- changes, "otherapp", 0, 0, name="book", index_together=set()
- )
- def test_index_together_remove_fk(self):
- changes = self.get_changes(
- [AutodetectorTests.author_empty, self.book_index_together],
- [AutodetectorTests.author_empty, AutodetectorTests.book_with_no_author],
- )
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(
- changes,
- "otherapp",
- 0,
- ["AlterIndexTogether", "RemoveField"],
- )
- self.assertOperationAttributes(
- changes, "otherapp", 0, 0, name="book", index_together=set()
- )
- self.assertOperationAttributes(
- changes, "otherapp", 0, 1, model_name="book", name="author"
- )
- def test_index_together_no_changes(self):
- """
- index_together doesn't generate a migration if no changes have been
- made.
- """
- changes = self.get_changes(
- [AutodetectorTests.author_empty, self.book_index_together],
- [AutodetectorTests.author_empty, self.book_index_together],
- )
- self.assertEqual(len(changes), 0)
- def test_index_together_ordering(self):
- """index_together triggers on ordering changes."""
- changes = self.get_changes(
- [AutodetectorTests.author_empty, self.book_index_together],
- [AutodetectorTests.author_empty, self.book_index_together_2],
- )
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(
- changes,
- "otherapp",
- 0,
- ["AlterIndexTogether"],
- )
- self.assertOperationAttributes(
- changes,
- "otherapp",
- 0,
- 0,
- name="book",
- index_together={("title", "author")},
- )
- def test_add_field_and_index_together(self):
- """
- Added fields will be created before using them in index_together.
- """
- changes = self.get_changes(
- [AutodetectorTests.author_empty, AutodetectorTests.book],
- [AutodetectorTests.author_empty, self.book_index_together_3],
- )
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(
- changes,
- "otherapp",
- 0,
- ["AddField", "AlterIndexTogether"],
- )
- self.assertOperationAttributes(
- changes,
- "otherapp",
- 0,
- 1,
- name="book",
- index_together={("title", "newfield")},
- )
- def test_create_model_and_index_together(self):
- author = ModelState(
- "otherapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200)),
- ],
- )
- book_with_author = ModelState(
- "otherapp",
- "Book",
- [
- ("id", models.AutoField(primary_key=True)),
- ("author", models.ForeignKey("otherapp.Author", models.CASCADE)),
- ("title", models.CharField(max_length=200)),
- ],
- {
- "index_together": {("title", "author")},
- },
- )
- changes = self.get_changes(
- [AutodetectorTests.book_with_no_author], [author, book_with_author]
- )
- self.assertEqual(len(changes["otherapp"]), 1)
- migration = changes["otherapp"][0]
- self.assertEqual(len(migration.operations), 3)
- self.assertOperationTypes(
- changes,
- "otherapp",
- 0,
- ["CreateModel", "AddField", "AlterIndexTogether"],
- )
- def test_remove_field_and_index_together(self):
- """
- Removed fields will be removed after updating index_together.
- """
- changes = self.get_changes(
- [AutodetectorTests.author_empty, self.book_index_together_3],
- [AutodetectorTests.author_empty, self.book_index_together],
- )
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(
- changes,
- "otherapp",
- 0,
- ["AlterIndexTogether", "RemoveField"],
- )
- self.assertOperationAttributes(
- changes,
- "otherapp",
- 0,
- 0,
- name="book",
- index_together={("author", "title")},
- )
- self.assertOperationAttributes(
- changes,
- "otherapp",
- 0,
- 1,
- model_name="book",
- name="newfield",
- )
- def test_alter_field_and_index_together(self):
- """Fields are altered after deleting some index_together."""
- initial_author = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200)),
- ("age", models.IntegerField(db_index=True)),
- ],
- {
- "index_together": {("name",)},
- },
- )
- author_reversed_constraints = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200, unique=True)),
- ("age", models.IntegerField()),
- ],
- {
- "index_together": {("age",)},
- },
- )
- changes = self.get_changes([initial_author], [author_reversed_constraints])
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(
- changes,
- "testapp",
- 0,
- [
- "AlterIndexTogether",
- "AlterField",
- "AlterField",
- "AlterIndexTogether",
- ],
- )
- self.assertOperationAttributes(
- changes,
- "testapp",
- 0,
- 0,
- name="author",
- index_together=set(),
- )
- self.assertOperationAttributes(
- changes,
- "testapp",
- 0,
- 1,
- model_name="author",
- name="age",
- )
- self.assertOperationAttributes(
- changes,
- "testapp",
- 0,
- 2,
- model_name="author",
- name="name",
- )
- self.assertOperationAttributes(
- changes,
- "testapp",
- 0,
- 3,
- name="author",
- index_together={("age",)},
- )
- def test_partly_alter_index_together_increase(self):
- initial_author = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200)),
- ("age", models.IntegerField()),
- ],
- {
- "index_together": {("name",)},
- },
- )
- author_new_constraints = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200)),
- ("age", models.IntegerField()),
- ],
- {
- "index_together": {("name",), ("age",)},
- },
- )
- changes = self.get_changes([initial_author], [author_new_constraints])
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(
- changes,
- "testapp",
- 0,
- ["AlterIndexTogether"],
- )
- self.assertOperationAttributes(
- changes,
- "testapp",
- 0,
- 0,
- name="author",
- index_together={("name",), ("age",)},
- )
- def test_partly_alter_index_together_decrease(self):
- initial_author = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200)),
- ("age", models.IntegerField()),
- ],
- {
- "index_together": {("name",), ("age",)},
- },
- )
- author_new_constraints = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200)),
- ("age", models.IntegerField()),
- ],
- {
- "index_together": {("age",)},
- },
- )
- changes = self.get_changes([initial_author], [author_new_constraints])
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(
- changes,
- "testapp",
- 0,
- ["AlterIndexTogether"],
- )
- self.assertOperationAttributes(
- changes,
- "testapp",
- 0,
- 0,
- name="author",
- index_together={("age",)},
- )
- def test_rename_field_and_index_together(self):
- """Fields are renamed before updating index_together."""
- changes = self.get_changes(
- [AutodetectorTests.author_empty, self.book_index_together_3],
- [AutodetectorTests.author_empty, self.book_index_together_4],
- MigrationQuestioner({"ask_rename": True}),
- )
- self.assertNumberMigrations(changes, "otherapp", 1)
- self.assertOperationTypes(
- changes,
- "otherapp",
- 0,
- ["RenameField", "AlterIndexTogether"],
- )
- self.assertOperationAttributes(
- changes,
- "otherapp",
- 0,
- 1,
- name="book",
- index_together={("title", "newfield2")},
- )
- def test_add_model_order_with_respect_to_index_together(self):
- changes = self.get_changes(
- [],
- [
- AutodetectorTests.book,
- ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200)),
- ("book", models.ForeignKey("otherapp.Book", models.CASCADE)),
- ],
- options={
- "order_with_respect_to": "book",
- "index_together": {("name", "_order")},
- },
- ),
- ],
- )
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(changes, "testapp", 0, ["CreateModel"])
- self.assertOperationAttributes(
- changes,
- "testapp",
- 0,
- 0,
- name="Author",
- options={
- "order_with_respect_to": "book",
- "index_together": {("name", "_order")},
- },
- )
- def test_set_alter_order_with_respect_to_index_together(self):
- after = ModelState(
- "testapp",
- "Author",
- [
- ("id", models.AutoField(primary_key=True)),
- ("name", models.CharField(max_length=200)),
- ("book", models.ForeignKey("otherapp.Book", models.CASCADE)),
- ],
- options={
- "order_with_respect_to": "book",
- "index_together": {("name", "_order")},
- },
- )
- changes = self.get_changes(
- [AutodetectorTests.book, AutodetectorTests.author_with_book],
- [AutodetectorTests.book, after],
- )
- self.assertNumberMigrations(changes, "testapp", 1)
- self.assertOperationTypes(
- changes,
- "testapp",
- 0,
- ["AlterOrderWithRespectTo", "AlterIndexTogether"],
- )
- class MigrationSuggestNameTests(SimpleTestCase):
- def test_no_operations(self):
- class Migration(migrations.Migration):
- operations = []
- migration = Migration("some_migration", "test_app")
- self.assertIs(migration.suggest_name().startswith("auto_"), True)
- def test_no_operations_initial(self):
- class Migration(migrations.Migration):
- initial = True
- operations = []
- migration = Migration("some_migration", "test_app")
- self.assertEqual(migration.suggest_name(), "initial")
- def test_single_operation(self):
- class Migration(migrations.Migration):
- operations = [migrations.CreateModel("Person", fields=[])]
- migration = Migration("0001_initial", "test_app")
- self.assertEqual(migration.suggest_name(), "person")
- class Migration(migrations.Migration):
- operations = [migrations.DeleteModel("Person")]
- migration = Migration("0002_initial", "test_app")
- self.assertEqual(migration.suggest_name(), "delete_person")
- def test_single_operation_long_name(self):
- class Migration(migrations.Migration):
- operations = [migrations.CreateModel("A" * 53, fields=[])]
- migration = Migration("some_migration", "test_app")
- self.assertEqual(migration.suggest_name(), "a" * 53)
- def test_two_operations(self):
- class Migration(migrations.Migration):
- operations = [
- migrations.CreateModel("Person", fields=[]),
- migrations.DeleteModel("Animal"),
- ]
- migration = Migration("some_migration", "test_app")
- self.assertEqual(migration.suggest_name(), "person_delete_animal")
- def test_two_create_models(self):
- class Migration(migrations.Migration):
- operations = [
- migrations.CreateModel("Person", fields=[]),
- migrations.CreateModel("Animal", fields=[]),
- ]
- migration = Migration("0001_initial", "test_app")
- self.assertEqual(migration.suggest_name(), "person_animal")
- def test_two_create_models_with_initial_true(self):
- class Migration(migrations.Migration):
- initial = True
- operations = [
- migrations.CreateModel("Person", fields=[]),
- migrations.CreateModel("Animal", fields=[]),
- ]
- migration = Migration("0001_initial", "test_app")
- self.assertEqual(migration.suggest_name(), "initial")
- def test_many_operations_suffix(self):
- class Migration(migrations.Migration):
- operations = [
- migrations.CreateModel("Person1", fields=[]),
- migrations.CreateModel("Person2", fields=[]),
- migrations.CreateModel("Person3", fields=[]),
- migrations.DeleteModel("Person4"),
- migrations.DeleteModel("Person5"),
- ]
- migration = Migration("some_migration", "test_app")
- self.assertEqual(
- migration.suggest_name(),
- "person1_person2_person3_delete_person4_and_more",
- )
- def test_operation_with_no_suggested_name(self):
- class Migration(migrations.Migration):
- operations = [
- migrations.CreateModel("Person", fields=[]),
- migrations.RunSQL("SELECT 1 FROM person;"),
- ]
- migration = Migration("some_migration", "test_app")
- self.assertIs(migration.suggest_name().startswith("auto_"), True)
- def test_none_name(self):
- class Migration(migrations.Migration):
- operations = [migrations.RunSQL("SELECT 1 FROM person;")]
- migration = Migration("0001_initial", "test_app")
- suggest_name = migration.suggest_name()
- self.assertIs(suggest_name.startswith("auto_"), True)
- def test_none_name_with_initial_true(self):
- class Migration(migrations.Migration):
- initial = True
- operations = [migrations.RunSQL("SELECT 1 FROM person;")]
- migration = Migration("0001_initial", "test_app")
- self.assertEqual(migration.suggest_name(), "initial")
- def test_auto(self):
- migration = migrations.Migration("0001_initial", "test_app")
- suggest_name = migration.suggest_name()
- self.assertIs(suggest_name.startswith("auto_"), True)
|