o 3a?@s~ddlZddlZddlmZmZddlmZddlmZddl m Z ddl m Z ddl mZmZmZmZd ZGd d d ZdS) N) import_modulereload)apps)settings)MigrationGraph)MigrationRecorder)AmbiguityErrorBadMigrationErrorInconsistentMigrationHistoryNodeNotFoundError migrationsc@seZdZdZ  dddZeddZdd Zd d Zd d Z ddZ ddZ ddZ ddZ ddZddZd ddZddZdS)!MigrationLoaderal Load migration files from disk and their status from the database. Migration files are expected to live in the "migrations" directory of an app. Their names are entirely unimportant from a code perspective, but will probably follow the 1234_name.py convention. On initialization, this class will scan those directories, and open and read the Python files, looking for a class called Migration, which should inherit from django.db.migrations.Migration. See django.db.migrations.migration for what that looks like. Some migrations will be marked as "replacing" another set of migrations. These are loaded into a separate set of migrations away from the main ones. If all the migrations they replace are either unapplied or missing from disk, then they are injected into the main set, replacing the named migrations. Any dependency pointers to the replaced migrations are re-pointed to the new migration. This does mean that this class MUST also talk to the database as well as to disk, but this is probably fine. We're already not just operating in memory. TFcCs2||_d|_d|_||_||_|r|dSdSN) connectiondisk_migrationsapplied_migrationsignore_no_migrationsreplace_migrations build_graph)selfrloadrrr=/usr/lib/python3/dist-packages/django/db/migrations/loader.py__init__+s zMigrationLoader.__init__cCs4|tjvr tj|dfSt|j}d|tfdfS)z Return the path to the migrations module for the specified app_label and a boolean indicating if the module is specified in settings.MIGRATION_MODULE. T%s.%sF)rMIGRATION_MODULESrget_app_confignameMIGRATIONS_MODULE_NAME)cls app_labelapp_package_namerrrmigrations_module7s  z!MigrationLoader.migrations_modulec Csi|_t|_t|_tD]}||j\}}|dur%|j|jq|t j v}zt |}Wn)t yY}z|r=|j sG|sTt|jdvrT|j|jWYd}~qd}~wwt|dsg|j|jqt|dddur}t|jts}|j|jq|rt||j|jddt|jD}|D]D}d||f} zt | } Wnty}zdt|vrtd | |d}~wwt| d std ||jf| ||j|j|j|f<qqdS) z6Load the migrations from all INSTALLED_APPS from disk.N.__path____file__cSs&h|]\}}}|s|ddvr|qS)rz_~r).0_ris_pkgrrr ls z,MigrationLoader.load_disk..rzbad magic numberz9Couldn't import %r as it appears to be a stale .pyc file. Migrationz-Migration %s in app %s has no Migration class)rsetunmigrated_apps migrated_appsrget_app_configsr#labeladdsysmodulesrModuleNotFoundErrorrrrsplithasattrgetattr isinstancer%listrpkgutil iter_modules ImportErrorstrr r+) r app_config module_nameexplicit was_loadedmoduleemigration_namesmigration_namemigration_pathmigration_modulerrr load_diskDs            zMigrationLoader.load_diskcCs|jj||fS)z6Return the named migration or raise NodeNotFoundError.graphnodes)rr! name_prefixrrr get_migrationszMigrationLoader.get_migrationcCsrg}|jD]\}}||kr||r|||fqt|dkr(td||f|s2td||f|j|dS)zZ Return the migration(s) which match the given app label and name_prefix. rz>There is more than one migration for '%s' with the prefix '%s'z1There no migrations for '%s' with the prefix '%s'r)r startswithappendlenr KeyError)rr!rLresultsmigration_app_labelrErrrget_migration_by_prefixs  z'MigrationLoader.get_migration_by_prefixcCs|ddkr |ddks||jvr|S|d|krdS|d|jvr$dS|d|jvr]z|ddkr=|j|ddWS|j|ddWSty\|jrTYdStd|dwtd|d)Nr __first__ __latest__rz(Dependency on app with no migrations: %szDependency on unknown app: %s)rJr-r. root_nodes leaf_nodes IndexErrorr ValueError)rkey current_apprrr check_keys""   zMigrationLoader.check_keycCs@|jD]}|d|dkr|ddkr|jj|||ddqdS)z Internal dependencies need to be added first to ensure `__first__` dependencies find the correct root node. rrrUTskip_validationN) dependenciesrJadd_dependency)rr[ migrationparentrrradd_internal_dependenciess z)MigrationLoader.add_internal_dependenciescCs|jD]!}|d|dkrq|||d}|dur$|jj|||ddq|jD]}|||d}|dur@|jj|||ddq(dS)NrTr^)r`r]rJra run_before)rr[rbrcchildrrradd_external_dependenciess  z)MigrationLoader.add_external_dependenciesc sjdur i_n tj}|_t_i_jD]\}}j |||j r6|j|<q#jD] \}} ||q<jD] \}} ||qLj rjD]8\}}fdd|j D}t|rw|j|<nj|dt|st|sj||j q_j||j q_zjWnbty}zUi}jD]\}}|j D] }||t|qq|j|vr||jt}tfdd|D} | sddd|D} td|j|jd |jd | |j|d}~wwjdS) z Build a migration dependency graph using both the disk and database. You'll need to rebuild the graph if you apply migrations. This isn't usually a problem as generally migration stuff runs in a one-shot process. Ncsg|]}|jvqSr)r)r'targetrrr sz/MigrationLoader.build_graph..c3s|] }|jjvVqdSrrI)r' candidaterirr sz.MigrationLoader.build_graph..z, css|]}d|VqdS)rNr)r'crrrrlzMigration {0} depends on nonexistent node ('{1}', '{2}'). Django tried to replace migration {1}.{2} with any of [{3}] but wasn't able to because some of the replaced migrations are already applied.rr)rHrrrrrJ replacementsritemsadd_nodereplacesrdrgrallpopanyremove_replaced_nodesremove_replacement_nodevalidate_consistencyr setdefaultr,r1nodegetjoinformatoriginensure_not_cyclic) rrecorderr[rbapplied_statusesexcreverse_replacementsreplaced candidates is_replacedtriesrrirrsf        zMigrationLoader.build_graphc st|}|D]@}||jjvrq |jj|jD]/}|vrI||jvr5tfdd|j|jDr5qt d |d|d|d|d|j qq dS)zs Raise InconsistentMigrationHistory if any applied migrations have unapplied dependencies. c3s|]}|vVqdSrr)r'mappliedrrrl0rnz;MigrationLoader.check_consistent_history..zHMigration {}.{} is applied before its dependency {}.{} on database '{}'.rrN) rrrJrKnode_mapparentsrorsrrr r}alias)rrrrbrcrrrcheck_consistent_history s(  z(MigrationLoader.check_consistent_historycsVit}|jD]\}}|vr|||t|q fdd|DS)z Look through the loaded graph and detect any conflicts - apps with more than one leaf migration. Return a dict of the app labels that conflict with the migration names that conflict. csi|] }|t|qSr)sorted)r'r! seen_appsrr Fsz4MigrationLoader.detect_conflicts..)r,rJrXr1ry)rconflicting_appsr!rErrrdetect_conflicts:s z MigrationLoader.detect_conflictsNcCs|jj||t|jdS)z Return a ProjectState object representing the most recent state that the loaded migrations represent. See graph.make_state() for the meaning of "nodes" and "at_end". )rKat_end real_apps)rJ make_stater9r-)rrKrrrr project_stateHszMigrationLoader.project_statec Csg}d}|D]E\}}|jjd|jd*}|dur#|j|j|jfdd}|s.|j||dd}n|j||dd}Wdn1s@wY||j q|S)z Take a migration plan and return a list of collected SQL statements that represent the best-efforts version of that plan. NT) collect_sqlatomicF)r)r) r schema_editorrrr!rapplyunapplyextend collected_sql)rplan statementsstaterb backwardsrrrrrQs zMigrationLoader.collect_sql)TFT)NT)__name__ __module__ __qualname____doc__r classmethodr#rHrMrTr]rdrgrrrrrrrrrrs$   B  Q  r)r:r2 importlibrr django.appsr django.confrdjango.db.migrations.graphrdjango.db.migrations.recorderr exceptionsr r r r rrrrrrs