79 lines
3.1 KiB
Python
79 lines
3.1 KiB
Python
from django.db import connection
|
|
from django.db.migrations import RemoveField
|
|
from django.db.migrations.loader import MigrationLoader
|
|
|
|
|
|
class RemoveFieldState(RemoveField):
|
|
"""
|
|
Remove field from Django's migration state, but not from the database.
|
|
This is essentially the same as RemoveField, but database_forwards and database_backwards methods are modified
|
|
to do nothing.
|
|
"""
|
|
|
|
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
|
pass
|
|
|
|
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
|
pass
|
|
|
|
def describe(self):
|
|
return f"{super().describe()} (state)"
|
|
|
|
@property
|
|
def migration_name_fragment(self):
|
|
return f"{super().migration_name_fragment}_state"
|
|
|
|
|
|
class RemoveFieldDB(RemoveField):
|
|
"""
|
|
Remove field from the database, but not from Django's migration state.
|
|
This is implemented as a custom operation, because Django's RemoveField operation does not support
|
|
removing fields from the database after it has been removed from the state. The workaround is to use the state
|
|
that was in effect before the field was removed from the state (i.e. just before the RemoveFieldState migration).
|
|
"""
|
|
|
|
def __init__(self, model_name, name, remove_state_migration):
|
|
"""
|
|
Specifying "remove_state_migration" allows database operations to run against a particular historical state.
|
|
Example: remove_state_migration = ("alerts", "0014_alertreceivechannel_restricted_at") will "trick" Django
|
|
into thinking that the last applied migration in the "alerts" app is 0013.
|
|
"""
|
|
super().__init__(model_name, name)
|
|
self.remove_state_migration = remove_state_migration
|
|
|
|
def deconstruct(self):
|
|
"""Update serialized representation of the operation."""
|
|
deconstructed = super().deconstruct()
|
|
return (
|
|
deconstructed[0],
|
|
deconstructed[1],
|
|
deconstructed[2] | {"remove_state_migration": self.remove_state_migration}
|
|
)
|
|
|
|
def state_forwards(self, app_label, state):
|
|
"""Skip any state changes."""
|
|
pass
|
|
|
|
def database_forwards(self, app_label, schema_editor, from_state, to_state):
|
|
# use historical state instead of what Django provides
|
|
from_state = self.state_before_remove_state_migration
|
|
|
|
super().database_forwards(app_label, schema_editor, from_state, to_state)
|
|
|
|
def database_backwards(self, app_label, schema_editor, from_state, to_state):
|
|
# use historical state instead of what Django provides
|
|
to_state = self.state_before_remove_state_migration
|
|
|
|
super().database_backwards(app_label, schema_editor, from_state, to_state)
|
|
|
|
def describe(self):
|
|
return f"{super().describe()} (db)"
|
|
|
|
@property
|
|
def migration_name_fragment(self):
|
|
return f"{super().migration_name_fragment}_db"
|
|
|
|
@property
|
|
def state_before_remove_state_migration(self):
|
|
"""Get project state just before migration "remove_state_migration" was applied."""
|
|
return MigrationLoader(connection).project_state(self.remove_state_migration, at_end=False)
|