You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

models.py 33KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878
  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 AlterModelTable(ModelOperation):
  387. """Rename a model's table."""
  388. def __init__(self, name, table):
  389. self.table = table
  390. super().__init__(name)
  391. def deconstruct(self):
  392. kwargs = {
  393. 'name': self.name,
  394. 'table': self.table,
  395. }
  396. return (
  397. self.__class__.__qualname__,
  398. [],
  399. kwargs
  400. )
  401. def state_forwards(self, app_label, state):
  402. state.models[app_label, self.name_lower].options["db_table"] = self.table
  403. state.reload_model(app_label, self.name_lower, delay=True)
  404. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  405. new_model = to_state.apps.get_model(app_label, self.name)
  406. if self.allow_migrate_model(schema_editor.connection.alias, new_model):
  407. old_model = from_state.apps.get_model(app_label, self.name)
  408. schema_editor.alter_db_table(
  409. new_model,
  410. old_model._meta.db_table,
  411. new_model._meta.db_table,
  412. )
  413. # Rename M2M fields whose name is based on this model's db_table
  414. for (old_field, new_field) in zip(old_model._meta.local_many_to_many, new_model._meta.local_many_to_many):
  415. if new_field.remote_field.through._meta.auto_created:
  416. schema_editor.alter_db_table(
  417. new_field.remote_field.through,
  418. old_field.remote_field.through._meta.db_table,
  419. new_field.remote_field.through._meta.db_table,
  420. )
  421. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  422. return self.database_forwards(app_label, schema_editor, from_state, to_state)
  423. def describe(self):
  424. return "Rename table for %s to %s" % (
  425. self.name,
  426. self.table if self.table is not None else "(default)"
  427. )
  428. def reduce(self, operation, app_label=None):
  429. if isinstance(operation, (AlterModelTable, DeleteModel)) and self.name_lower == operation.name_lower:
  430. return [operation]
  431. return super().reduce(operation, app_label=app_label)
  432. class ModelOptionOperation(ModelOperation):
  433. def reduce(self, operation, app_label=None):
  434. if isinstance(operation, (self.__class__, DeleteModel)) and self.name_lower == operation.name_lower:
  435. return [operation]
  436. return super().reduce(operation, app_label=app_label)
  437. class AlterTogetherOptionOperation(ModelOptionOperation):
  438. option_name = None
  439. def __init__(self, name, option_value):
  440. if option_value:
  441. option_value = set(normalize_together(option_value))
  442. setattr(self, self.option_name, option_value)
  443. super().__init__(name)
  444. @cached_property
  445. def option_value(self):
  446. return getattr(self, self.option_name)
  447. def deconstruct(self):
  448. kwargs = {
  449. 'name': self.name,
  450. self.option_name: self.option_value,
  451. }
  452. return (
  453. self.__class__.__qualname__,
  454. [],
  455. kwargs
  456. )
  457. def state_forwards(self, app_label, state):
  458. model_state = state.models[app_label, self.name_lower]
  459. model_state.options[self.option_name] = self.option_value
  460. state.reload_model(app_label, self.name_lower, delay=True)
  461. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  462. new_model = to_state.apps.get_model(app_label, self.name)
  463. if self.allow_migrate_model(schema_editor.connection.alias, new_model):
  464. old_model = from_state.apps.get_model(app_label, self.name)
  465. alter_together = getattr(schema_editor, 'alter_%s' % self.option_name)
  466. alter_together(
  467. new_model,
  468. getattr(old_model._meta, self.option_name, set()),
  469. getattr(new_model._meta, self.option_name, set()),
  470. )
  471. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  472. return self.database_forwards(app_label, schema_editor, from_state, to_state)
  473. def references_field(self, model_name, name, app_label=None):
  474. return (
  475. self.references_model(model_name, app_label) and
  476. (
  477. not self.option_value or
  478. any((name in fields) for fields in self.option_value)
  479. )
  480. )
  481. def describe(self):
  482. return "Alter %s for %s (%s constraint(s))" % (self.option_name, self.name, len(self.option_value or ''))
  483. class AlterUniqueTogether(AlterTogetherOptionOperation):
  484. """
  485. Change the value of unique_together to the target one.
  486. Input value of unique_together must be a set of tuples.
  487. """
  488. option_name = 'unique_together'
  489. def __init__(self, name, unique_together):
  490. super().__init__(name, unique_together)
  491. class AlterIndexTogether(AlterTogetherOptionOperation):
  492. """
  493. Change the value of index_together to the target one.
  494. Input value of index_together must be a set of tuples.
  495. """
  496. option_name = "index_together"
  497. def __init__(self, name, index_together):
  498. super().__init__(name, index_together)
  499. class AlterOrderWithRespectTo(ModelOptionOperation):
  500. """Represent a change with the order_with_respect_to option."""
  501. option_name = 'order_with_respect_to'
  502. def __init__(self, name, order_with_respect_to):
  503. self.order_with_respect_to = order_with_respect_to
  504. super().__init__(name)
  505. def deconstruct(self):
  506. kwargs = {
  507. 'name': self.name,
  508. 'order_with_respect_to': self.order_with_respect_to,
  509. }
  510. return (
  511. self.__class__.__qualname__,
  512. [],
  513. kwargs
  514. )
  515. def state_forwards(self, app_label, state):
  516. model_state = state.models[app_label, self.name_lower]
  517. model_state.options['order_with_respect_to'] = self.order_with_respect_to
  518. state.reload_model(app_label, self.name_lower, delay=True)
  519. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  520. to_model = to_state.apps.get_model(app_label, self.name)
  521. if self.allow_migrate_model(schema_editor.connection.alias, to_model):
  522. from_model = from_state.apps.get_model(app_label, self.name)
  523. # Remove a field if we need to
  524. if from_model._meta.order_with_respect_to and not to_model._meta.order_with_respect_to:
  525. schema_editor.remove_field(from_model, from_model._meta.get_field("_order"))
  526. # Add a field if we need to (altering the column is untouched as
  527. # it's likely a rename)
  528. elif to_model._meta.order_with_respect_to and not from_model._meta.order_with_respect_to:
  529. field = to_model._meta.get_field("_order")
  530. if not field.has_default():
  531. field.default = 0
  532. schema_editor.add_field(
  533. from_model,
  534. field,
  535. )
  536. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  537. self.database_forwards(app_label, schema_editor, from_state, to_state)
  538. def references_field(self, model_name, name, app_label=None):
  539. return (
  540. self.references_model(model_name, app_label) and
  541. (
  542. self.order_with_respect_to is None or
  543. name == self.order_with_respect_to
  544. )
  545. )
  546. def describe(self):
  547. return "Set order_with_respect_to on %s to %s" % (self.name, self.order_with_respect_to)
  548. class AlterModelOptions(ModelOptionOperation):
  549. """
  550. Set new model options that don't directly affect the database schema
  551. (like verbose_name, permissions, ordering). Python code in migrations
  552. may still need them.
  553. """
  554. # Model options we want to compare and preserve in an AlterModelOptions op
  555. ALTER_OPTION_KEYS = [
  556. "base_manager_name",
  557. "default_manager_name",
  558. "default_related_name",
  559. "get_latest_by",
  560. "managed",
  561. "ordering",
  562. "permissions",
  563. "default_permissions",
  564. "select_on_save",
  565. "verbose_name",
  566. "verbose_name_plural",
  567. ]
  568. def __init__(self, name, options):
  569. self.options = options
  570. super().__init__(name)
  571. def deconstruct(self):
  572. kwargs = {
  573. 'name': self.name,
  574. 'options': self.options,
  575. }
  576. return (
  577. self.__class__.__qualname__,
  578. [],
  579. kwargs
  580. )
  581. def state_forwards(self, app_label, state):
  582. model_state = state.models[app_label, self.name_lower]
  583. model_state.options = {**model_state.options, **self.options}
  584. for key in self.ALTER_OPTION_KEYS:
  585. if key not in self.options:
  586. model_state.options.pop(key, False)
  587. state.reload_model(app_label, self.name_lower, delay=True)
  588. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  589. pass
  590. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  591. pass
  592. def describe(self):
  593. return "Change Meta options on %s" % self.name
  594. class AlterModelManagers(ModelOptionOperation):
  595. """Alter the model's managers."""
  596. serialization_expand_args = ['managers']
  597. def __init__(self, name, managers):
  598. self.managers = managers
  599. super().__init__(name)
  600. def deconstruct(self):
  601. return (
  602. self.__class__.__qualname__,
  603. [self.name, self.managers],
  604. {}
  605. )
  606. def state_forwards(self, app_label, state):
  607. model_state = state.models[app_label, self.name_lower]
  608. model_state.managers = list(self.managers)
  609. state.reload_model(app_label, self.name_lower, delay=True)
  610. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  611. pass
  612. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  613. pass
  614. def describe(self):
  615. return "Change managers on %s" % self.name
  616. class IndexOperation(Operation):
  617. option_name = 'indexes'
  618. @cached_property
  619. def model_name_lower(self):
  620. return self.model_name.lower()
  621. class AddIndex(IndexOperation):
  622. """Add an index on a model."""
  623. def __init__(self, model_name, index):
  624. self.model_name = model_name
  625. if not index.name:
  626. raise ValueError(
  627. "Indexes passed to AddIndex operations require a name "
  628. "argument. %r doesn't have one." % index
  629. )
  630. self.index = index
  631. def state_forwards(self, app_label, state):
  632. model_state = state.models[app_label, self.model_name_lower]
  633. model_state.options[self.option_name] = [*model_state.options[self.option_name], self.index.clone()]
  634. state.reload_model(app_label, self.model_name_lower, delay=True)
  635. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  636. model = to_state.apps.get_model(app_label, self.model_name)
  637. if self.allow_migrate_model(schema_editor.connection.alias, model):
  638. schema_editor.add_index(model, self.index)
  639. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  640. model = from_state.apps.get_model(app_label, self.model_name)
  641. if self.allow_migrate_model(schema_editor.connection.alias, model):
  642. schema_editor.remove_index(model, self.index)
  643. def deconstruct(self):
  644. kwargs = {
  645. 'model_name': self.model_name,
  646. 'index': self.index,
  647. }
  648. return (
  649. self.__class__.__qualname__,
  650. [],
  651. kwargs,
  652. )
  653. def describe(self):
  654. return 'Create index %s on field(s) %s of model %s' % (
  655. self.index.name,
  656. ', '.join(self.index.fields),
  657. self.model_name,
  658. )
  659. class RemoveIndex(IndexOperation):
  660. """Remove an index from a model."""
  661. def __init__(self, model_name, name):
  662. self.model_name = model_name
  663. self.name = name
  664. def state_forwards(self, app_label, state):
  665. model_state = state.models[app_label, self.model_name_lower]
  666. indexes = model_state.options[self.option_name]
  667. model_state.options[self.option_name] = [idx for idx in indexes if idx.name != self.name]
  668. state.reload_model(app_label, self.model_name_lower, delay=True)
  669. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  670. model = from_state.apps.get_model(app_label, self.model_name)
  671. if self.allow_migrate_model(schema_editor.connection.alias, model):
  672. from_model_state = from_state.models[app_label, self.model_name_lower]
  673. index = from_model_state.get_index_by_name(self.name)
  674. schema_editor.remove_index(model, index)
  675. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  676. model = to_state.apps.get_model(app_label, self.model_name)
  677. if self.allow_migrate_model(schema_editor.connection.alias, model):
  678. to_model_state = to_state.models[app_label, self.model_name_lower]
  679. index = to_model_state.get_index_by_name(self.name)
  680. schema_editor.add_index(model, index)
  681. def deconstruct(self):
  682. kwargs = {
  683. 'model_name': self.model_name,
  684. 'name': self.name,
  685. }
  686. return (
  687. self.__class__.__qualname__,
  688. [],
  689. kwargs,
  690. )
  691. def describe(self):
  692. return 'Remove index %s from %s' % (self.name, self.model_name)
  693. class AddConstraint(IndexOperation):
  694. option_name = 'constraints'
  695. def __init__(self, model_name, constraint):
  696. self.model_name = model_name
  697. self.constraint = constraint
  698. def state_forwards(self, app_label, state):
  699. model_state = state.models[app_label, self.model_name_lower]
  700. model_state.options[self.option_name] = [*model_state.options[self.option_name], self.constraint]
  701. state.reload_model(app_label, self.model_name_lower, delay=True)
  702. def database_forwards(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.add_constraint(model, self.constraint)
  706. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  707. model = to_state.apps.get_model(app_label, self.model_name)
  708. if self.allow_migrate_model(schema_editor.connection.alias, model):
  709. schema_editor.remove_constraint(model, self.constraint)
  710. def deconstruct(self):
  711. return self.__class__.__name__, [], {
  712. 'model_name': self.model_name,
  713. 'constraint': self.constraint,
  714. }
  715. def describe(self):
  716. return 'Create constraint %s on model %s' % (self.constraint.name, self.model_name)
  717. class RemoveConstraint(IndexOperation):
  718. option_name = 'constraints'
  719. def __init__(self, model_name, name):
  720. self.model_name = model_name
  721. self.name = name
  722. def state_forwards(self, app_label, state):
  723. model_state = state.models[app_label, self.model_name_lower]
  724. constraints = model_state.options[self.option_name]
  725. model_state.options[self.option_name] = [c for c in constraints if c.name != self.name]
  726. state.reload_model(app_label, self.model_name_lower, delay=True)
  727. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  728. model = to_state.apps.get_model(app_label, self.model_name)
  729. if self.allow_migrate_model(schema_editor.connection.alias, model):
  730. from_model_state = from_state.models[app_label, self.model_name_lower]
  731. constraint = from_model_state.get_constraint_by_name(self.name)
  732. schema_editor.remove_constraint(model, constraint)
  733. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  734. model = to_state.apps.get_model(app_label, self.model_name)
  735. if self.allow_migrate_model(schema_editor.connection.alias, model):
  736. to_model_state = to_state.models[app_label, self.model_name_lower]
  737. constraint = to_model_state.get_constraint_by_name(self.name)
  738. schema_editor.add_constraint(model, constraint)
  739. def deconstruct(self):
  740. return self.__class__.__name__, [], {
  741. 'model_name': self.model_name,
  742. 'name': self.name,
  743. }
  744. def describe(self):
  745. return 'Remove constraint %s from model %s' % (self.name, self.model_name)