models.py 33 KB

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