Take a pair of ProjectStates and compare them to see what the first would need doing to make it match the second (the second usually being the project's current state). Note that this naturally operates on entire projects at a time, as it's likely that changes interact (for exa
| 41 | |
| 42 | |
| 43 | class MigrationAutodetector: |
| 44 | """ |
| 45 | Take a pair of ProjectStates and compare them to see what the first would |
| 46 | need doing to make it match the second (the second usually being the |
| 47 | project's current state). |
| 48 | |
| 49 | Note that this naturally operates on entire projects at a time, |
| 50 | as it's likely that changes interact (for example, you can't |
| 51 | add a ForeignKey without having a migration to add the table it |
| 52 | depends on first). A user interface may offer single-app usage |
| 53 | if it wishes, with the caveat that it may not always be possible. |
| 54 | """ |
| 55 | |
| 56 | def __init__(self, from_state, to_state, questioner=None): |
| 57 | self.from_state = from_state |
| 58 | self.to_state = to_state |
| 59 | self.questioner = questioner or MigrationQuestioner() |
| 60 | self.existing_apps = {app for app, model in from_state.models} |
| 61 | |
| 62 | def changes(self, graph, trim_to_apps=None, convert_apps=None, migration_name=None): |
| 63 | """ |
| 64 | Main entry point to produce a list of applicable changes. |
| 65 | Take a graph to base names on and an optional set of apps |
| 66 | to try and restrict to (restriction is not guaranteed) |
| 67 | """ |
| 68 | changes = self._detect_changes(convert_apps, graph) |
| 69 | changes = self.arrange_for_graph(changes, graph, migration_name) |
| 70 | if trim_to_apps: |
| 71 | changes = self._trim_to_apps(changes, trim_to_apps) |
| 72 | return changes |
| 73 | |
| 74 | def deep_deconstruct(self, obj): |
| 75 | """ |
| 76 | Recursive deconstruction for a field and its arguments. |
| 77 | Used for full comparison for rename/alter; sometimes a single-level |
| 78 | deconstruction will not compare correctly. |
| 79 | """ |
| 80 | if isinstance(obj, list): |
| 81 | return [self.deep_deconstruct(value) for value in obj] |
| 82 | elif isinstance(obj, tuple): |
| 83 | return tuple(self.deep_deconstruct(value) for value in obj) |
| 84 | elif isinstance(obj, dict): |
| 85 | return {key: self.deep_deconstruct(value) for key, value in obj.items()} |
| 86 | elif isinstance(obj, functools.partial): |
| 87 | return ( |
| 88 | obj.func, |
| 89 | self.deep_deconstruct(obj.args), |
| 90 | self.deep_deconstruct(obj.keywords), |
| 91 | ) |
| 92 | elif isinstance(obj, COMPILED_REGEX_TYPE): |
| 93 | return RegexObject(obj) |
| 94 | elif isinstance(obj, type): |
| 95 | # If this is a type that implements 'deconstruct' as an instance |
| 96 | # method, avoid treating this as being deconstructible itself - see |
| 97 | # #22951 |
| 98 | return obj |
| 99 | elif hasattr(obj, "deconstruct"): |
| 100 | deconstructed = obj.deconstruct() |
no outgoing calls