models.py 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917
  1. from django.db import models
  2. from django.db.migrations.operations.base import Operation
  3. from django.db.migrations.state import ModelState
  4. from django.db.models.options import normalize_together
  5. from django.utils.functional import cached_property
  6. from .fields import (
  7. AddField, AlterField, FieldOperation, RemoveField, RenameField,
  8. )
  9. from .utils import field_references, get_references, resolve_relation
  10. def _check_for_duplicates(arg_name, objs):
  11. used_vals = set()
  12. for val in objs:
  13. if val in used_vals:
  14. raise ValueError(
  15. "Found duplicate value %s in CreateModel %s argument." % (val, arg_name)
  16. )
  17. used_vals.add(val)
  18. class ModelOperation(Operation):
  19. def __init__(self, name):
  20. self.name = name
  21. @cached_property
  22. def name_lower(self):
  23. return self.name.lower()
  24. def references_model(self, name, app_label):
  25. return name.lower() == self.name_lower
  26. def reduce(self, operation, app_label):
  27. return (
  28. super().reduce(operation, app_label) or
  29. not operation.references_model(self.name, app_label)
  30. )
  31. class CreateModel(ModelOperation):
  32. """Create a model's table."""
  33. serialization_expand_args = ['fields', 'options', 'managers']
  34. def __init__(self, name, fields, options=None, bases=None, managers=None):
  35. self.fields = fields
  36. self.options = options or {}
  37. self.bases = bases or (models.Model,)
  38. self.managers = managers or []
  39. super().__init__(name)
  40. # Sanity-check that there are no duplicated field names, bases, or
  41. # manager names
  42. _check_for_duplicates('fields', (name for name, _ in self.fields))
  43. _check_for_duplicates('bases', (
  44. base._meta.label_lower if hasattr(base, '_meta') else
  45. base.lower() if isinstance(base, str) else base
  46. for base in self.bases
  47. ))
  48. _check_for_duplicates('managers', (name for name, _ in self.managers))
  49. def deconstruct(self):
  50. kwargs = {
  51. 'name': self.name,
  52. 'fields': self.fields,
  53. }
  54. if self.options:
  55. kwargs['options'] = self.options
  56. if self.bases and self.bases != (models.Model,):
  57. kwargs['bases'] = self.bases
  58. if self.managers and self.managers != [('objects', models.Manager())]:
  59. kwargs['managers'] = self.managers
  60. return (
  61. self.__class__.__qualname__,
  62. [],
  63. kwargs
  64. )
  65. def state_forwards(self, app_label, state):
  66. state.add_model(ModelState(
  67. app_label,
  68. self.name,
  69. list(self.fields),
  70. dict(self.options),
  71. tuple(self.bases),
  72. list(self.managers),
  73. ))
  74. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  75. model = to_state.apps.get_model(app_label, self.name)
  76. if self.allow_migrate_model(schema_editor.connection.alias, model):
  77. schema_editor.create_model(model)
  78. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  79. model = from_state.apps.get_model(app_label, self.name)
  80. if self.allow_migrate_model(schema_editor.connection.alias, model):
  81. schema_editor.delete_model(model)
  82. def describe(self):
  83. return "Create %smodel %s" % ("proxy " if self.options.get("proxy", False) else "", self.name)
  84. @property
  85. def migration_name_fragment(self):
  86. return self.name_lower
  87. def references_model(self, name, app_label):
  88. name_lower = name.lower()
  89. if name_lower == self.name_lower:
  90. return True
  91. # Check we didn't inherit from the model
  92. reference_model_tuple = (app_label, name_lower)
  93. for base in self.bases:
  94. if (base is not models.Model and isinstance(base, (models.base.ModelBase, str)) and
  95. resolve_relation(base, app_label) == reference_model_tuple):
  96. return True
  97. # Check we have no FKs/M2Ms with it
  98. for _name, field in self.fields:
  99. if field_references((app_label, self.name_lower), field, reference_model_tuple):
  100. return True
  101. return False
  102. def reduce(self, operation, app_label):
  103. if (isinstance(operation, DeleteModel) and
  104. self.name_lower == operation.name_lower and
  105. not self.options.get("proxy", False)):
  106. return []
  107. elif isinstance(operation, RenameModel) and self.name_lower == operation.old_name_lower:
  108. return [
  109. CreateModel(
  110. operation.new_name,
  111. fields=self.fields,
  112. options=self.options,
  113. bases=self.bases,
  114. managers=self.managers,
  115. ),
  116. ]
  117. elif isinstance(operation, AlterModelOptions) and self.name_lower == operation.name_lower:
  118. options = {**self.options, **operation.options}
  119. for key in operation.ALTER_OPTION_KEYS:
  120. if key not in operation.options:
  121. options.pop(key, None)
  122. return [
  123. CreateModel(
  124. self.name,
  125. fields=self.fields,
  126. options=options,
  127. bases=self.bases,
  128. managers=self.managers,
  129. ),
  130. ]
  131. elif isinstance(operation, AlterTogetherOptionOperation) and self.name_lower == operation.name_lower:
  132. return [
  133. CreateModel(
  134. self.name,
  135. fields=self.fields,
  136. options={**self.options, **{operation.option_name: operation.option_value}},
  137. bases=self.bases,
  138. managers=self.managers,
  139. ),
  140. ]
  141. elif isinstance(operation, AlterOrderWithRespectTo) and self.name_lower == operation.name_lower:
  142. return [
  143. CreateModel(
  144. self.name,
  145. fields=self.fields,
  146. options={**self.options, 'order_with_respect_to': operation.order_with_respect_to},
  147. bases=self.bases,
  148. managers=self.managers,
  149. ),
  150. ]
  151. elif isinstance(operation, FieldOperation) and self.name_lower == operation.model_name_lower:
  152. if isinstance(operation, AddField):
  153. return [
  154. CreateModel(
  155. self.name,
  156. fields=self.fields + [(operation.name, operation.field)],
  157. options=self.options,
  158. bases=self.bases,
  159. managers=self.managers,
  160. ),
  161. ]
  162. elif isinstance(operation, AlterField):
  163. return [
  164. CreateModel(
  165. self.name,
  166. fields=[
  167. (n, operation.field if n == operation.name else v)
  168. for n, v in self.fields
  169. ],
  170. options=self.options,
  171. bases=self.bases,
  172. managers=self.managers,
  173. ),
  174. ]
  175. elif isinstance(operation, RemoveField):
  176. options = self.options.copy()
  177. for option_name in ('unique_together', 'index_together'):
  178. option = options.pop(option_name, None)
  179. if option:
  180. option = set(filter(bool, (
  181. tuple(f for f in fields if f != operation.name_lower) for fields in option
  182. )))
  183. if option:
  184. options[option_name] = option
  185. order_with_respect_to = options.get('order_with_respect_to')
  186. if order_with_respect_to == operation.name_lower:
  187. del options['order_with_respect_to']
  188. return [
  189. CreateModel(
  190. self.name,
  191. fields=[
  192. (n, v)
  193. for n, v in self.fields
  194. if n.lower() != operation.name_lower
  195. ],
  196. options=options,
  197. bases=self.bases,
  198. managers=self.managers,
  199. ),
  200. ]
  201. elif isinstance(operation, RenameField):
  202. options = self.options.copy()
  203. for option_name in ('unique_together', 'index_together'):
  204. option = options.get(option_name)
  205. if option:
  206. options[option_name] = {
  207. tuple(operation.new_name if f == operation.old_name else f for f in fields)
  208. for fields in option
  209. }
  210. order_with_respect_to = options.get('order_with_respect_to')
  211. if order_with_respect_to == operation.old_name:
  212. options['order_with_respect_to'] = operation.new_name
  213. return [
  214. CreateModel(
  215. self.name,
  216. fields=[
  217. (operation.new_name if n == operation.old_name else n, v)
  218. for n, v in self.fields
  219. ],
  220. options=options,
  221. bases=self.bases,
  222. managers=self.managers,
  223. ),
  224. ]
  225. return super().reduce(operation, app_label)
  226. class DeleteModel(ModelOperation):
  227. """Drop a model's table."""
  228. def deconstruct(self):
  229. kwargs = {
  230. 'name': self.name,
  231. }
  232. return (
  233. self.__class__.__qualname__,
  234. [],
  235. kwargs
  236. )
  237. def state_forwards(self, app_label, state):
  238. state.remove_model(app_label, self.name_lower)
  239. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  240. model = from_state.apps.get_model(app_label, self.name)
  241. if self.allow_migrate_model(schema_editor.connection.alias, model):
  242. schema_editor.delete_model(model)
  243. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  244. model = to_state.apps.get_model(app_label, self.name)
  245. if self.allow_migrate_model(schema_editor.connection.alias, model):
  246. schema_editor.create_model(model)
  247. def references_model(self, name, app_label):
  248. # The deleted model could be referencing the specified model through
  249. # related fields.
  250. return True
  251. def describe(self):
  252. return "Delete model %s" % self.name
  253. @property
  254. def migration_name_fragment(self):
  255. return 'delete_%s' % self.name_lower
  256. class RenameModel(ModelOperation):
  257. """Rename a model."""
  258. def __init__(self, old_name, new_name):
  259. self.old_name = old_name
  260. self.new_name = new_name
  261. super().__init__(old_name)
  262. @cached_property
  263. def old_name_lower(self):
  264. return self.old_name.lower()
  265. @cached_property
  266. def new_name_lower(self):
  267. return self.new_name.lower()
  268. def deconstruct(self):
  269. kwargs = {
  270. 'old_name': self.old_name,
  271. 'new_name': self.new_name,
  272. }
  273. return (
  274. self.__class__.__qualname__,
  275. [],
  276. kwargs
  277. )
  278. def state_forwards(self, app_label, state):
  279. # Add a new model.
  280. renamed_model = state.models[app_label, self.old_name_lower].clone()
  281. renamed_model.name = self.new_name
  282. state.models[app_label, self.new_name_lower] = renamed_model
  283. # Repoint all fields pointing to the old model to the new one.
  284. old_model_tuple = (app_label, self.old_name_lower)
  285. new_remote_model = '%s.%s' % (app_label, self.new_name)
  286. to_reload = set()
  287. for model_state, name, field, reference in get_references(state, old_model_tuple):
  288. changed_field = None
  289. if reference.to:
  290. changed_field = field.clone()
  291. changed_field.remote_field.model = new_remote_model
  292. if reference.through:
  293. if changed_field is None:
  294. changed_field = field.clone()
  295. changed_field.remote_field.through = new_remote_model
  296. if changed_field:
  297. model_state.fields[name] = changed_field
  298. to_reload.add((model_state.app_label, model_state.name_lower))
  299. # Reload models related to old model before removing the old model.
  300. state.reload_models(to_reload, delay=True)
  301. # Remove the old model.
  302. state.remove_model(app_label, self.old_name_lower)
  303. state.reload_model(app_label, self.new_name_lower, delay=True)
  304. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  305. new_model = to_state.apps.get_model(app_label, self.new_name)
  306. if self.allow_migrate_model(schema_editor.connection.alias, new_model):
  307. old_model = from_state.apps.get_model(app_label, self.old_name)
  308. # Move the main table
  309. schema_editor.alter_db_table(
  310. new_model,
  311. old_model._meta.db_table,
  312. new_model._meta.db_table,
  313. )
  314. # Alter the fields pointing to us
  315. for related_object in old_model._meta.related_objects:
  316. if related_object.related_model == old_model:
  317. model = new_model
  318. related_key = (app_label, self.new_name_lower)
  319. else:
  320. model = related_object.related_model
  321. related_key = (
  322. related_object.related_model._meta.app_label,
  323. related_object.related_model._meta.model_name,
  324. )
  325. to_field = to_state.apps.get_model(
  326. *related_key
  327. )._meta.get_field(related_object.field.name)
  328. schema_editor.alter_field(
  329. model,
  330. related_object.field,
  331. to_field,
  332. )
  333. # Rename M2M fields whose name is based on this model's name.
  334. fields = zip(old_model._meta.local_many_to_many, new_model._meta.local_many_to_many)
  335. for (old_field, new_field) in fields:
  336. # Skip self-referential fields as these are renamed above.
  337. if new_field.model == new_field.related_model or not new_field.remote_field.through._meta.auto_created:
  338. continue
  339. # Rename the M2M table that's based on this model's name.
  340. old_m2m_model = old_field.remote_field.through
  341. new_m2m_model = new_field.remote_field.through
  342. schema_editor.alter_db_table(
  343. new_m2m_model,
  344. old_m2m_model._meta.db_table,
  345. new_m2m_model._meta.db_table,
  346. )
  347. # Rename the column in the M2M table that's based on this
  348. # model's name.
  349. schema_editor.alter_field(
  350. new_m2m_model,
  351. old_m2m_model._meta.get_field(old_model._meta.model_name),
  352. new_m2m_model._meta.get_field(new_model._meta.model_name),
  353. )
  354. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  355. self.new_name_lower, self.old_name_lower = self.old_name_lower, self.new_name_lower
  356. self.new_name, self.old_name = self.old_name, self.new_name
  357. self.database_forwards(app_label, schema_editor, from_state, to_state)
  358. self.new_name_lower, self.old_name_lower = self.old_name_lower, self.new_name_lower
  359. self.new_name, self.old_name = self.old_name, self.new_name
  360. def references_model(self, name, app_label):
  361. return (
  362. name.lower() == self.old_name_lower or
  363. name.lower() == self.new_name_lower
  364. )
  365. def describe(self):
  366. return "Rename model %s to %s" % (self.old_name, self.new_name)
  367. @property
  368. def migration_name_fragment(self):
  369. return 'rename_%s_%s' % (self.old_name_lower, self.new_name_lower)
  370. def reduce(self, operation, app_label):
  371. if (isinstance(operation, RenameModel) and
  372. self.new_name_lower == operation.old_name_lower):
  373. return [
  374. RenameModel(
  375. self.old_name,
  376. operation.new_name,
  377. ),
  378. ]
  379. # Skip `ModelOperation.reduce` as we want to run `references_model`
  380. # against self.new_name.
  381. return (
  382. super(ModelOperation, self).reduce(operation, app_label) or
  383. not operation.references_model(self.new_name, app_label)
  384. )
  385. class ModelOptionOperation(ModelOperation):
  386. def reduce(self, operation, app_label):
  387. if isinstance(operation, (self.__class__, DeleteModel)) and self.name_lower == operation.name_lower:
  388. return [operation]
  389. return super().reduce(operation, app_label)
  390. class AlterModelTable(ModelOptionOperation):
  391. """Rename a model's table."""
  392. def __init__(self, name, table):
  393. self.table = table
  394. super().__init__(name)
  395. def deconstruct(self):
  396. kwargs = {
  397. 'name': self.name,
  398. 'table': self.table,
  399. }
  400. return (
  401. self.__class__.__qualname__,
  402. [],
  403. kwargs
  404. )
  405. def state_forwards(self, app_label, state):
  406. state.models[app_label, self.name_lower].options["db_table"] = self.table
  407. state.reload_model(app_label, self.name_lower, delay=True)
  408. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  409. new_model = to_state.apps.get_model(app_label, self.name)
  410. if self.allow_migrate_model(schema_editor.connection.alias, new_model):
  411. old_model = from_state.apps.get_model(app_label, self.name)
  412. schema_editor.alter_db_table(
  413. new_model,
  414. old_model._meta.db_table,
  415. new_model._meta.db_table,
  416. )
  417. # Rename M2M fields whose name is based on this model's db_table
  418. for (old_field, new_field) in zip(old_model._meta.local_many_to_many, new_model._meta.local_many_to_many):
  419. if new_field.remote_field.through._meta.auto_created:
  420. schema_editor.alter_db_table(
  421. new_field.remote_field.through,
  422. old_field.remote_field.through._meta.db_table,
  423. new_field.remote_field.through._meta.db_table,
  424. )
  425. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  426. return self.database_forwards(app_label, schema_editor, from_state, to_state)
  427. def describe(self):
  428. return "Rename table for %s to %s" % (
  429. self.name,
  430. self.table if self.table is not None else "(default)"
  431. )
  432. @property
  433. def migration_name_fragment(self):
  434. return 'alter_%s_table' % self.name_lower
  435. class AlterTogetherOptionOperation(ModelOptionOperation):
  436. option_name = None
  437. def __init__(self, name, option_value):
  438. if option_value:
  439. option_value = set(normalize_together(option_value))
  440. setattr(self, self.option_name, option_value)
  441. super().__init__(name)
  442. @cached_property
  443. def option_value(self):
  444. return getattr(self, self.option_name)
  445. def deconstruct(self):
  446. kwargs = {
  447. 'name': self.name,
  448. self.option_name: self.option_value,
  449. }
  450. return (
  451. self.__class__.__qualname__,
  452. [],
  453. kwargs
  454. )
  455. def state_forwards(self, app_label, state):
  456. model_state = state.models[app_label, self.name_lower]
  457. model_state.options[self.option_name] = self.option_value
  458. state.reload_model(app_label, self.name_lower, delay=True)
  459. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  460. new_model = to_state.apps.get_model(app_label, self.name)
  461. if self.allow_migrate_model(schema_editor.connection.alias, new_model):
  462. old_model = from_state.apps.get_model(app_label, self.name)
  463. alter_together = getattr(schema_editor, 'alter_%s' % self.option_name)
  464. alter_together(
  465. new_model,
  466. getattr(old_model._meta, self.option_name, set()),
  467. getattr(new_model._meta, self.option_name, set()),
  468. )
  469. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  470. return self.database_forwards(app_label, schema_editor, from_state, to_state)
  471. def references_field(self, model_name, name, app_label):
  472. return (
  473. self.references_model(model_name, app_label) and
  474. (
  475. not self.option_value or
  476. any((name in fields) for fields in self.option_value)
  477. )
  478. )
  479. def describe(self):
  480. return "Alter %s for %s (%s constraint(s))" % (self.option_name, self.name, len(self.option_value or ''))
  481. @property
  482. def migration_name_fragment(self):
  483. return 'alter_%s_%s' % (self.name_lower, self.option_name)
  484. class AlterUniqueTogether(AlterTogetherOptionOperation):
  485. """
  486. Change the value of unique_together to the target one.
  487. Input value of unique_together must be a set of tuples.
  488. """
  489. option_name = 'unique_together'
  490. def __init__(self, name, unique_together):
  491. super().__init__(name, unique_together)
  492. class AlterIndexTogether(AlterTogetherOptionOperation):
  493. """
  494. Change the value of index_together to the target one.
  495. Input value of index_together must be a set of tuples.
  496. """
  497. option_name = "index_together"
  498. def __init__(self, name, index_together):
  499. super().__init__(name, index_together)
  500. class AlterOrderWithRespectTo(ModelOptionOperation):
  501. """Represent a change with the order_with_respect_to option."""
  502. option_name = 'order_with_respect_to'
  503. def __init__(self, name, order_with_respect_to):
  504. self.order_with_respect_to = order_with_respect_to
  505. super().__init__(name)
  506. def deconstruct(self):
  507. kwargs = {
  508. 'name': self.name,
  509. 'order_with_respect_to': self.order_with_respect_to,
  510. }
  511. return (
  512. self.__class__.__qualname__,
  513. [],
  514. kwargs
  515. )
  516. def state_forwards(self, app_label, state):
  517. model_state = state.models[app_label, self.name_lower]
  518. model_state.options['order_with_respect_to'] = self.order_with_respect_to
  519. state.reload_model(app_label, self.name_lower, delay=True)
  520. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  521. to_model = to_state.apps.get_model(app_label, self.name)
  522. if self.allow_migrate_model(schema_editor.connection.alias, to_model):
  523. from_model = from_state.apps.get_model(app_label, self.name)
  524. # Remove a field if we need to
  525. if from_model._meta.order_with_respect_to and not to_model._meta.order_with_respect_to:
  526. schema_editor.remove_field(from_model, from_model._meta.get_field("_order"))
  527. # Add a field if we need to (altering the column is untouched as
  528. # it's likely a rename)
  529. elif to_model._meta.order_with_respect_to and not from_model._meta.order_with_respect_to:
  530. field = to_model._meta.get_field("_order")
  531. if not field.has_default():
  532. field.default = 0
  533. schema_editor.add_field(
  534. from_model,
  535. field,
  536. )
  537. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  538. self.database_forwards(app_label, schema_editor, from_state, to_state)
  539. def references_field(self, model_name, name, app_label):
  540. return (
  541. self.references_model(model_name, app_label) and
  542. (
  543. self.order_with_respect_to is None or
  544. name == self.order_with_respect_to
  545. )
  546. )
  547. def describe(self):
  548. return "Set order_with_respect_to on %s to %s" % (self.name, self.order_with_respect_to)
  549. @property
  550. def migration_name_fragment(self):
  551. return 'alter_%s_order_with_respect_to' % self.name_lower
  552. class AlterModelOptions(ModelOptionOperation):
  553. """
  554. Set new model options that don't directly affect the database schema
  555. (like verbose_name, permissions, ordering). Python code in migrations
  556. may still need them.
  557. """
  558. # Model options we want to compare and preserve in an AlterModelOptions op
  559. ALTER_OPTION_KEYS = [
  560. "base_manager_name",
  561. "default_manager_name",
  562. "default_related_name",
  563. "get_latest_by",
  564. "managed",
  565. "ordering",
  566. "permissions",
  567. "default_permissions",
  568. "select_on_save",
  569. "verbose_name",
  570. "verbose_name_plural",
  571. ]
  572. def __init__(self, name, options):
  573. self.options = options
  574. super().__init__(name)
  575. def deconstruct(self):
  576. kwargs = {
  577. 'name': self.name,
  578. 'options': self.options,
  579. }
  580. return (
  581. self.__class__.__qualname__,
  582. [],
  583. kwargs
  584. )
  585. def state_forwards(self, app_label, state):
  586. model_state = state.models[app_label, self.name_lower]
  587. model_state.options = {**model_state.options, **self.options}
  588. for key in self.ALTER_OPTION_KEYS:
  589. if key not in self.options:
  590. model_state.options.pop(key, False)
  591. state.reload_model(app_label, self.name_lower, delay=True)
  592. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  593. pass
  594. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  595. pass
  596. def describe(self):
  597. return "Change Meta options on %s" % self.name
  598. @property
  599. def migration_name_fragment(self):
  600. return 'alter_%s_options' % self.name_lower
  601. class AlterModelManagers(ModelOptionOperation):
  602. """Alter the model's managers."""
  603. serialization_expand_args = ['managers']
  604. def __init__(self, name, managers):
  605. self.managers = managers
  606. super().__init__(name)
  607. def deconstruct(self):
  608. return (
  609. self.__class__.__qualname__,
  610. [self.name, self.managers],
  611. {}
  612. )
  613. def state_forwards(self, app_label, state):
  614. model_state = state.models[app_label, self.name_lower]
  615. model_state.managers = list(self.managers)
  616. state.reload_model(app_label, self.name_lower, delay=True)
  617. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  618. pass
  619. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  620. pass
  621. def describe(self):
  622. return "Change managers on %s" % self.name
  623. @property
  624. def migration_name_fragment(self):
  625. return 'alter_%s_managers' % self.name_lower
  626. class IndexOperation(Operation):
  627. option_name = 'indexes'
  628. @cached_property
  629. def model_name_lower(self):
  630. return self.model_name.lower()
  631. class AddIndex(IndexOperation):
  632. """Add an index on a model."""
  633. def __init__(self, model_name, index):
  634. self.model_name = model_name
  635. if not index.name:
  636. raise ValueError(
  637. "Indexes passed to AddIndex operations require a name "
  638. "argument. %r doesn't have one." % index
  639. )
  640. self.index = index
  641. def state_forwards(self, app_label, state):
  642. model_state = state.models[app_label, self.model_name_lower]
  643. model_state.options[self.option_name] = [*model_state.options[self.option_name], self.index.clone()]
  644. state.reload_model(app_label, self.model_name_lower, delay=True)
  645. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  646. model = to_state.apps.get_model(app_label, self.model_name)
  647. if self.allow_migrate_model(schema_editor.connection.alias, model):
  648. schema_editor.add_index(model, self.index)
  649. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  650. model = from_state.apps.get_model(app_label, self.model_name)
  651. if self.allow_migrate_model(schema_editor.connection.alias, model):
  652. schema_editor.remove_index(model, self.index)
  653. def deconstruct(self):
  654. kwargs = {
  655. 'model_name': self.model_name,
  656. 'index': self.index,
  657. }
  658. return (
  659. self.__class__.__qualname__,
  660. [],
  661. kwargs,
  662. )
  663. def describe(self):
  664. if self.index.expressions:
  665. return 'Create index %s on %s on model %s' % (
  666. self.index.name,
  667. ', '.join([str(expression) for expression in self.index.expressions]),
  668. self.model_name,
  669. )
  670. return 'Create index %s on field(s) %s of model %s' % (
  671. self.index.name,
  672. ', '.join(self.index.fields),
  673. self.model_name,
  674. )
  675. @property
  676. def migration_name_fragment(self):
  677. return '%s_%s' % (self.model_name_lower, self.index.name.lower())
  678. class RemoveIndex(IndexOperation):
  679. """Remove an index from a model."""
  680. def __init__(self, model_name, name):
  681. self.model_name = model_name
  682. self.name = name
  683. def state_forwards(self, app_label, state):
  684. model_state = state.models[app_label, self.model_name_lower]
  685. indexes = model_state.options[self.option_name]
  686. model_state.options[self.option_name] = [idx for idx in indexes if idx.name != self.name]
  687. state.reload_model(app_label, self.model_name_lower, delay=True)
  688. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  689. model = from_state.apps.get_model(app_label, self.model_name)
  690. if self.allow_migrate_model(schema_editor.connection.alias, model):
  691. from_model_state = from_state.models[app_label, self.model_name_lower]
  692. index = from_model_state.get_index_by_name(self.name)
  693. schema_editor.remove_index(model, index)
  694. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  695. model = to_state.apps.get_model(app_label, self.model_name)
  696. if self.allow_migrate_model(schema_editor.connection.alias, model):
  697. to_model_state = to_state.models[app_label, self.model_name_lower]
  698. index = to_model_state.get_index_by_name(self.name)
  699. schema_editor.add_index(model, index)
  700. def deconstruct(self):
  701. kwargs = {
  702. 'model_name': self.model_name,
  703. 'name': self.name,
  704. }
  705. return (
  706. self.__class__.__qualname__,
  707. [],
  708. kwargs,
  709. )
  710. def describe(self):
  711. return 'Remove index %s from %s' % (self.name, self.model_name)
  712. @property
  713. def migration_name_fragment(self):
  714. return 'remove_%s_%s' % (self.model_name_lower, self.name.lower())
  715. class AddConstraint(IndexOperation):
  716. option_name = 'constraints'
  717. def __init__(self, model_name, constraint):
  718. self.model_name = model_name
  719. self.constraint = constraint
  720. def state_forwards(self, app_label, state):
  721. model_state = state.models[app_label, self.model_name_lower]
  722. model_state.options[self.option_name] = [*model_state.options[self.option_name], self.constraint]
  723. state.reload_model(app_label, self.model_name_lower, delay=True)
  724. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  725. model = to_state.apps.get_model(app_label, self.model_name)
  726. if self.allow_migrate_model(schema_editor.connection.alias, model):
  727. schema_editor.add_constraint(model, self.constraint)
  728. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  729. model = to_state.apps.get_model(app_label, self.model_name)
  730. if self.allow_migrate_model(schema_editor.connection.alias, model):
  731. schema_editor.remove_constraint(model, self.constraint)
  732. def deconstruct(self):
  733. return self.__class__.__name__, [], {
  734. 'model_name': self.model_name,
  735. 'constraint': self.constraint,
  736. }
  737. def describe(self):
  738. return 'Create constraint %s on model %s' % (self.constraint.name, self.model_name)
  739. @property
  740. def migration_name_fragment(self):
  741. return '%s_%s' % (self.model_name_lower, self.constraint.name.lower())
  742. class RemoveConstraint(IndexOperation):
  743. option_name = 'constraints'
  744. def __init__(self, model_name, name):
  745. self.model_name = model_name
  746. self.name = name
  747. def state_forwards(self, app_label, state):
  748. model_state = state.models[app_label, self.model_name_lower]
  749. constraints = model_state.options[self.option_name]
  750. model_state.options[self.option_name] = [c for c in constraints if c.name != self.name]
  751. state.reload_model(app_label, self.model_name_lower, delay=True)
  752. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  753. model = to_state.apps.get_model(app_label, self.model_name)
  754. if self.allow_migrate_model(schema_editor.connection.alias, model):
  755. from_model_state = from_state.models[app_label, self.model_name_lower]
  756. constraint = from_model_state.get_constraint_by_name(self.name)
  757. schema_editor.remove_constraint(model, constraint)
  758. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  759. model = to_state.apps.get_model(app_label, self.model_name)
  760. if self.allow_migrate_model(schema_editor.connection.alias, model):
  761. to_model_state = to_state.models[app_label, self.model_name_lower]
  762. constraint = to_model_state.get_constraint_by_name(self.name)
  763. schema_editor.add_constraint(model, constraint)
  764. def deconstruct(self):
  765. return self.__class__.__name__, [], {
  766. 'model_name': self.model_name,
  767. 'name': self.name,
  768. }
  769. def describe(self):
  770. return 'Remove constraint %s from model %s' % (self.name, self.model_name)
  771. @property
  772. def migration_name_fragment(self):
  773. return 'remove_%s_%s' % (self.model_name_lower, self.name.lower())