models.py 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244
  1. from django.db import models
  2. from django.db.migrations.operations.base import Operation, OperationCategory
  3. from django.db.migrations.state import ModelState
  4. from django.db.migrations.utils import field_references, resolve_relation
  5. from django.db.models.options import normalize_together
  6. from django.utils.functional import cached_property
  7. from .fields import AddField, AlterField, FieldOperation, RemoveField, RenameField
  8. def _check_for_duplicates(arg_name, objs):
  9. used_vals = set()
  10. for val in objs:
  11. if val in used_vals:
  12. raise ValueError(
  13. "Found duplicate value %s in CreateModel %s argument." % (val, arg_name)
  14. )
  15. used_vals.add(val)
  16. class ModelOperation(Operation):
  17. def __init__(self, name):
  18. self.name = name
  19. @cached_property
  20. def name_lower(self):
  21. return self.name.lower()
  22. def references_model(self, name, app_label):
  23. return name.lower() == self.name_lower
  24. def reduce(self, operation, app_label):
  25. return super().reduce(operation, app_label) or self.can_reduce_through(
  26. operation, app_label
  27. )
  28. def can_reduce_through(self, operation, app_label):
  29. return not operation.references_model(self.name, app_label)
  30. class CreateModel(ModelOperation):
  31. """Create a model's table."""
  32. category = OperationCategory.ADDITION
  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(
  44. "bases",
  45. (
  46. (
  47. base._meta.label_lower
  48. if hasattr(base, "_meta")
  49. else base.lower() if isinstance(base, str) else base
  50. )
  51. for base in self.bases
  52. ),
  53. )
  54. _check_for_duplicates("managers", (name for name, _ in self.managers))
  55. def deconstruct(self):
  56. kwargs = {
  57. "name": self.name,
  58. "fields": self.fields,
  59. }
  60. if self.options:
  61. kwargs["options"] = self.options
  62. if self.bases and self.bases != (models.Model,):
  63. kwargs["bases"] = self.bases
  64. if self.managers and self.managers != [("objects", models.Manager())]:
  65. kwargs["managers"] = self.managers
  66. return (self.__class__.__qualname__, [], kwargs)
  67. def state_forwards(self, app_label, state):
  68. state.add_model(
  69. ModelState(
  70. app_label,
  71. self.name,
  72. list(self.fields),
  73. dict(self.options),
  74. tuple(self.bases),
  75. list(self.managers),
  76. )
  77. )
  78. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  79. model = to_state.apps.get_model(app_label, self.name)
  80. if self.allow_migrate_model(schema_editor.connection.alias, model):
  81. schema_editor.create_model(model)
  82. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  83. model = from_state.apps.get_model(app_label, self.name)
  84. if self.allow_migrate_model(schema_editor.connection.alias, model):
  85. schema_editor.delete_model(model)
  86. def describe(self):
  87. return "Create %smodel %s" % (
  88. "proxy " if self.options.get("proxy", False) else "",
  89. self.name,
  90. )
  91. @property
  92. def migration_name_fragment(self):
  93. return self.name_lower
  94. def references_model(self, name, app_label):
  95. name_lower = name.lower()
  96. if name_lower == self.name_lower:
  97. return True
  98. # Check we didn't inherit from the model
  99. reference_model_tuple = (app_label, name_lower)
  100. for base in self.bases:
  101. if (
  102. base is not models.Model
  103. and isinstance(base, (models.base.ModelBase, str))
  104. and resolve_relation(base, app_label) == reference_model_tuple
  105. ):
  106. return True
  107. # Check we have no FKs/M2Ms with it
  108. for _name, field in self.fields:
  109. if field_references(
  110. (app_label, self.name_lower), field, reference_model_tuple
  111. ):
  112. return True
  113. return False
  114. def reduce(self, operation, app_label):
  115. if (
  116. isinstance(operation, DeleteModel)
  117. and self.name_lower == operation.name_lower
  118. and not self.options.get("proxy", False)
  119. ):
  120. return []
  121. elif (
  122. isinstance(operation, RenameModel)
  123. and self.name_lower == operation.old_name_lower
  124. ):
  125. return [
  126. CreateModel(
  127. operation.new_name,
  128. fields=self.fields,
  129. options=self.options,
  130. bases=self.bases,
  131. managers=self.managers,
  132. ),
  133. ]
  134. elif (
  135. isinstance(operation, AlterModelOptions)
  136. and self.name_lower == operation.name_lower
  137. ):
  138. options = {**self.options, **operation.options}
  139. for key in operation.ALTER_OPTION_KEYS:
  140. if key not in operation.options:
  141. options.pop(key, None)
  142. return [
  143. CreateModel(
  144. self.name,
  145. fields=self.fields,
  146. options=options,
  147. bases=self.bases,
  148. managers=self.managers,
  149. ),
  150. ]
  151. elif (
  152. isinstance(operation, AlterModelManagers)
  153. and self.name_lower == operation.name_lower
  154. ):
  155. return [
  156. CreateModel(
  157. self.name,
  158. fields=self.fields,
  159. options=self.options,
  160. bases=self.bases,
  161. managers=operation.managers,
  162. ),
  163. ]
  164. elif (
  165. isinstance(operation, AlterTogetherOptionOperation)
  166. and self.name_lower == operation.name_lower
  167. ):
  168. return [
  169. CreateModel(
  170. self.name,
  171. fields=self.fields,
  172. options={
  173. **self.options,
  174. **{operation.option_name: operation.option_value},
  175. },
  176. bases=self.bases,
  177. managers=self.managers,
  178. ),
  179. ]
  180. elif (
  181. isinstance(operation, AlterOrderWithRespectTo)
  182. and self.name_lower == operation.name_lower
  183. ):
  184. return [
  185. CreateModel(
  186. self.name,
  187. fields=self.fields,
  188. options={
  189. **self.options,
  190. "order_with_respect_to": operation.order_with_respect_to,
  191. },
  192. bases=self.bases,
  193. managers=self.managers,
  194. ),
  195. ]
  196. elif (
  197. isinstance(operation, FieldOperation)
  198. and self.name_lower == operation.model_name_lower
  199. ):
  200. if isinstance(operation, AddField):
  201. return [
  202. CreateModel(
  203. self.name,
  204. fields=self.fields + [(operation.name, operation.field)],
  205. options=self.options,
  206. bases=self.bases,
  207. managers=self.managers,
  208. ),
  209. ]
  210. elif isinstance(operation, AlterField):
  211. return [
  212. CreateModel(
  213. self.name,
  214. fields=[
  215. (n, operation.field if n == operation.name else v)
  216. for n, v in self.fields
  217. ],
  218. options=self.options,
  219. bases=self.bases,
  220. managers=self.managers,
  221. ),
  222. ]
  223. elif isinstance(operation, RemoveField):
  224. options = self.options.copy()
  225. for option_name in ("unique_together", "index_together"):
  226. option = options.pop(option_name, None)
  227. if option:
  228. option = set(
  229. filter(
  230. bool,
  231. (
  232. tuple(
  233. f for f in fields if f != operation.name_lower
  234. )
  235. for fields in option
  236. ),
  237. )
  238. )
  239. if option:
  240. options[option_name] = option
  241. order_with_respect_to = options.get("order_with_respect_to")
  242. if order_with_respect_to == operation.name_lower:
  243. del options["order_with_respect_to"]
  244. return [
  245. CreateModel(
  246. self.name,
  247. fields=[
  248. (n, v)
  249. for n, v in self.fields
  250. if n.lower() != operation.name_lower
  251. ],
  252. options=options,
  253. bases=self.bases,
  254. managers=self.managers,
  255. ),
  256. ]
  257. elif isinstance(operation, RenameField):
  258. options = self.options.copy()
  259. for option_name in ("unique_together", "index_together"):
  260. option = options.get(option_name)
  261. if option:
  262. options[option_name] = {
  263. tuple(
  264. operation.new_name if f == operation.old_name else f
  265. for f in fields
  266. )
  267. for fields in option
  268. }
  269. order_with_respect_to = options.get("order_with_respect_to")
  270. if order_with_respect_to == operation.old_name:
  271. options["order_with_respect_to"] = operation.new_name
  272. return [
  273. CreateModel(
  274. self.name,
  275. fields=[
  276. (operation.new_name if n == operation.old_name else n, v)
  277. for n, v in self.fields
  278. ],
  279. options=options,
  280. bases=self.bases,
  281. managers=self.managers,
  282. ),
  283. ]
  284. elif (
  285. isinstance(operation, IndexOperation)
  286. and self.name_lower == operation.model_name_lower
  287. ):
  288. if isinstance(operation, AddIndex):
  289. return [
  290. CreateModel(
  291. self.name,
  292. fields=self.fields,
  293. options={
  294. **self.options,
  295. "indexes": [
  296. *self.options.get("indexes", []),
  297. operation.index,
  298. ],
  299. },
  300. bases=self.bases,
  301. managers=self.managers,
  302. ),
  303. ]
  304. elif isinstance(operation, RemoveIndex):
  305. options_indexes = [
  306. index
  307. for index in self.options.get("indexes", [])
  308. if index.name != operation.name
  309. ]
  310. return [
  311. CreateModel(
  312. self.name,
  313. fields=self.fields,
  314. options={
  315. **self.options,
  316. "indexes": options_indexes,
  317. },
  318. bases=self.bases,
  319. managers=self.managers,
  320. ),
  321. ]
  322. elif isinstance(operation, AddConstraint):
  323. return [
  324. CreateModel(
  325. self.name,
  326. fields=self.fields,
  327. options={
  328. **self.options,
  329. "constraints": [
  330. *self.options.get("constraints", []),
  331. operation.constraint,
  332. ],
  333. },
  334. bases=self.bases,
  335. managers=self.managers,
  336. ),
  337. ]
  338. elif isinstance(operation, RemoveConstraint):
  339. options_constraints = [
  340. constraint
  341. for constraint in self.options.get("constraints", [])
  342. if constraint.name != operation.name
  343. ]
  344. return [
  345. CreateModel(
  346. self.name,
  347. fields=self.fields,
  348. options={
  349. **self.options,
  350. "constraints": options_constraints,
  351. },
  352. bases=self.bases,
  353. managers=self.managers,
  354. ),
  355. ]
  356. return super().reduce(operation, app_label)
  357. class DeleteModel(ModelOperation):
  358. """Drop a model's table."""
  359. category = OperationCategory.REMOVAL
  360. def deconstruct(self):
  361. kwargs = {
  362. "name": self.name,
  363. }
  364. return (self.__class__.__qualname__, [], kwargs)
  365. def state_forwards(self, app_label, state):
  366. state.remove_model(app_label, self.name_lower)
  367. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  368. model = from_state.apps.get_model(app_label, self.name)
  369. if self.allow_migrate_model(schema_editor.connection.alias, model):
  370. schema_editor.delete_model(model)
  371. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  372. model = to_state.apps.get_model(app_label, self.name)
  373. if self.allow_migrate_model(schema_editor.connection.alias, model):
  374. schema_editor.create_model(model)
  375. def references_model(self, name, app_label):
  376. # The deleted model could be referencing the specified model through
  377. # related fields.
  378. return True
  379. def describe(self):
  380. return "Delete model %s" % self.name
  381. @property
  382. def migration_name_fragment(self):
  383. return "delete_%s" % self.name_lower
  384. class RenameModel(ModelOperation):
  385. """Rename a model."""
  386. category = OperationCategory.ALTERATION
  387. def __init__(self, old_name, new_name):
  388. self.old_name = old_name
  389. self.new_name = new_name
  390. super().__init__(old_name)
  391. @cached_property
  392. def old_name_lower(self):
  393. return self.old_name.lower()
  394. @cached_property
  395. def new_name_lower(self):
  396. return self.new_name.lower()
  397. def deconstruct(self):
  398. kwargs = {
  399. "old_name": self.old_name,
  400. "new_name": self.new_name,
  401. }
  402. return (self.__class__.__qualname__, [], kwargs)
  403. def state_forwards(self, app_label, state):
  404. state.rename_model(app_label, self.old_name, self.new_name)
  405. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  406. new_model = to_state.apps.get_model(app_label, self.new_name)
  407. if self.allow_migrate_model(schema_editor.connection.alias, new_model):
  408. old_model = from_state.apps.get_model(app_label, self.old_name)
  409. # Move the main table
  410. schema_editor.alter_db_table(
  411. new_model,
  412. old_model._meta.db_table,
  413. new_model._meta.db_table,
  414. )
  415. # Alter the fields pointing to us
  416. for related_object in old_model._meta.related_objects:
  417. if related_object.related_model == old_model:
  418. model = new_model
  419. related_key = (app_label, self.new_name_lower)
  420. else:
  421. related_key = (
  422. related_object.related_model._meta.app_label,
  423. related_object.related_model._meta.model_name,
  424. )
  425. model = to_state.apps.get_model(*related_key)
  426. to_field = to_state.apps.get_model(*related_key)._meta.get_field(
  427. related_object.field.name
  428. )
  429. schema_editor.alter_field(
  430. model,
  431. related_object.field,
  432. to_field,
  433. )
  434. # Rename M2M fields whose name is based on this model's name.
  435. fields = zip(
  436. old_model._meta.local_many_to_many, new_model._meta.local_many_to_many
  437. )
  438. for old_field, new_field in fields:
  439. # Skip self-referential fields as these are renamed above.
  440. if (
  441. new_field.model == new_field.related_model
  442. or not new_field.remote_field.through._meta.auto_created
  443. ):
  444. continue
  445. # Rename columns and the M2M table.
  446. schema_editor._alter_many_to_many(
  447. new_model,
  448. old_field,
  449. new_field,
  450. strict=False,
  451. )
  452. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  453. self.new_name_lower, self.old_name_lower = (
  454. self.old_name_lower,
  455. self.new_name_lower,
  456. )
  457. self.new_name, self.old_name = self.old_name, self.new_name
  458. self.database_forwards(app_label, schema_editor, from_state, to_state)
  459. self.new_name_lower, self.old_name_lower = (
  460. self.old_name_lower,
  461. self.new_name_lower,
  462. )
  463. self.new_name, self.old_name = self.old_name, self.new_name
  464. def references_model(self, name, app_label):
  465. return (
  466. name.lower() == self.old_name_lower or name.lower() == self.new_name_lower
  467. )
  468. def describe(self):
  469. return "Rename model %s to %s" % (self.old_name, self.new_name)
  470. @property
  471. def migration_name_fragment(self):
  472. return "rename_%s_%s" % (self.old_name_lower, self.new_name_lower)
  473. def reduce(self, operation, app_label):
  474. if (
  475. isinstance(operation, RenameModel)
  476. and self.new_name_lower == operation.old_name_lower
  477. ):
  478. return [
  479. RenameModel(
  480. self.old_name,
  481. operation.new_name,
  482. ),
  483. ]
  484. # Skip `ModelOperation.reduce` as we want to run `references_model`
  485. # against self.new_name.
  486. return super(ModelOperation, self).reduce(
  487. operation, app_label
  488. ) or not operation.references_model(self.new_name, app_label)
  489. class ModelOptionOperation(ModelOperation):
  490. category = OperationCategory.ALTERATION
  491. def reduce(self, operation, app_label):
  492. if (
  493. isinstance(operation, (self.__class__, DeleteModel))
  494. and self.name_lower == operation.name_lower
  495. ):
  496. return [operation]
  497. return super().reduce(operation, app_label)
  498. class AlterModelTable(ModelOptionOperation):
  499. """Rename a model's table."""
  500. def __init__(self, name, table):
  501. self.table = table
  502. super().__init__(name)
  503. def deconstruct(self):
  504. kwargs = {
  505. "name": self.name,
  506. "table": self.table,
  507. }
  508. return (self.__class__.__qualname__, [], kwargs)
  509. def state_forwards(self, app_label, state):
  510. state.alter_model_options(app_label, self.name_lower, {"db_table": self.table})
  511. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  512. new_model = to_state.apps.get_model(app_label, self.name)
  513. if self.allow_migrate_model(schema_editor.connection.alias, new_model):
  514. old_model = from_state.apps.get_model(app_label, self.name)
  515. schema_editor.alter_db_table(
  516. new_model,
  517. old_model._meta.db_table,
  518. new_model._meta.db_table,
  519. )
  520. # Rename M2M fields whose name is based on this model's db_table
  521. for old_field, new_field in zip(
  522. old_model._meta.local_many_to_many, new_model._meta.local_many_to_many
  523. ):
  524. if new_field.remote_field.through._meta.auto_created:
  525. schema_editor.alter_db_table(
  526. new_field.remote_field.through,
  527. old_field.remote_field.through._meta.db_table,
  528. new_field.remote_field.through._meta.db_table,
  529. )
  530. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  531. return self.database_forwards(app_label, schema_editor, from_state, to_state)
  532. def describe(self):
  533. return "Rename table for %s to %s" % (
  534. self.name,
  535. self.table if self.table is not None else "(default)",
  536. )
  537. @property
  538. def migration_name_fragment(self):
  539. return "alter_%s_table" % self.name_lower
  540. class AlterModelTableComment(ModelOptionOperation):
  541. def __init__(self, name, table_comment):
  542. self.table_comment = table_comment
  543. super().__init__(name)
  544. def deconstruct(self):
  545. kwargs = {
  546. "name": self.name,
  547. "table_comment": self.table_comment,
  548. }
  549. return (self.__class__.__qualname__, [], kwargs)
  550. def state_forwards(self, app_label, state):
  551. state.alter_model_options(
  552. app_label, self.name_lower, {"db_table_comment": self.table_comment}
  553. )
  554. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  555. new_model = to_state.apps.get_model(app_label, self.name)
  556. if self.allow_migrate_model(schema_editor.connection.alias, new_model):
  557. old_model = from_state.apps.get_model(app_label, self.name)
  558. schema_editor.alter_db_table_comment(
  559. new_model,
  560. old_model._meta.db_table_comment,
  561. new_model._meta.db_table_comment,
  562. )
  563. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  564. return self.database_forwards(app_label, schema_editor, from_state, to_state)
  565. def describe(self):
  566. return f"Alter {self.name} table comment"
  567. @property
  568. def migration_name_fragment(self):
  569. return f"alter_{self.name_lower}_table_comment"
  570. class AlterTogetherOptionOperation(ModelOptionOperation):
  571. option_name = None
  572. def __init__(self, name, option_value):
  573. if option_value:
  574. option_value = set(normalize_together(option_value))
  575. setattr(self, self.option_name, option_value)
  576. super().__init__(name)
  577. @cached_property
  578. def option_value(self):
  579. return getattr(self, self.option_name)
  580. def deconstruct(self):
  581. kwargs = {
  582. "name": self.name,
  583. self.option_name: self.option_value,
  584. }
  585. return (self.__class__.__qualname__, [], kwargs)
  586. def state_forwards(self, app_label, state):
  587. state.alter_model_options(
  588. app_label,
  589. self.name_lower,
  590. {self.option_name: self.option_value},
  591. )
  592. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  593. new_model = to_state.apps.get_model(app_label, self.name)
  594. if self.allow_migrate_model(schema_editor.connection.alias, new_model):
  595. old_model = from_state.apps.get_model(app_label, self.name)
  596. alter_together = getattr(schema_editor, "alter_%s" % self.option_name)
  597. alter_together(
  598. new_model,
  599. getattr(old_model._meta, self.option_name, set()),
  600. getattr(new_model._meta, self.option_name, set()),
  601. )
  602. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  603. return self.database_forwards(app_label, schema_editor, from_state, to_state)
  604. def references_field(self, model_name, name, app_label):
  605. return self.references_model(model_name, app_label) and (
  606. not self.option_value
  607. or any((name in fields) for fields in self.option_value)
  608. )
  609. def describe(self):
  610. return "Alter %s for %s (%s constraint(s))" % (
  611. self.option_name,
  612. self.name,
  613. len(self.option_value or ""),
  614. )
  615. @property
  616. def migration_name_fragment(self):
  617. return "alter_%s_%s" % (self.name_lower, self.option_name)
  618. def can_reduce_through(self, operation, app_label):
  619. return super().can_reduce_through(operation, app_label) or (
  620. isinstance(operation, AlterTogetherOptionOperation)
  621. and type(operation) is not type(self)
  622. )
  623. class AlterUniqueTogether(AlterTogetherOptionOperation):
  624. """
  625. Change the value of unique_together to the target one.
  626. Input value of unique_together must be a set of tuples.
  627. """
  628. option_name = "unique_together"
  629. def __init__(self, name, unique_together):
  630. super().__init__(name, unique_together)
  631. class AlterIndexTogether(AlterTogetherOptionOperation):
  632. """
  633. Change the value of index_together to the target one.
  634. Input value of index_together must be a set of tuples.
  635. """
  636. option_name = "index_together"
  637. def __init__(self, name, index_together):
  638. super().__init__(name, index_together)
  639. class AlterOrderWithRespectTo(ModelOptionOperation):
  640. """Represent a change with the order_with_respect_to option."""
  641. option_name = "order_with_respect_to"
  642. def __init__(self, name, order_with_respect_to):
  643. self.order_with_respect_to = order_with_respect_to
  644. super().__init__(name)
  645. def deconstruct(self):
  646. kwargs = {
  647. "name": self.name,
  648. "order_with_respect_to": self.order_with_respect_to,
  649. }
  650. return (self.__class__.__qualname__, [], kwargs)
  651. def state_forwards(self, app_label, state):
  652. state.alter_model_options(
  653. app_label,
  654. self.name_lower,
  655. {self.option_name: self.order_with_respect_to},
  656. )
  657. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  658. to_model = to_state.apps.get_model(app_label, self.name)
  659. if self.allow_migrate_model(schema_editor.connection.alias, to_model):
  660. from_model = from_state.apps.get_model(app_label, self.name)
  661. # Remove a field if we need to
  662. if (
  663. from_model._meta.order_with_respect_to
  664. and not to_model._meta.order_with_respect_to
  665. ):
  666. schema_editor.remove_field(
  667. from_model, from_model._meta.get_field("_order")
  668. )
  669. # Add a field if we need to (altering the column is untouched as
  670. # it's likely a rename)
  671. elif (
  672. to_model._meta.order_with_respect_to
  673. and not from_model._meta.order_with_respect_to
  674. ):
  675. field = to_model._meta.get_field("_order")
  676. if not field.has_default():
  677. field.default = 0
  678. schema_editor.add_field(
  679. from_model,
  680. field,
  681. )
  682. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  683. self.database_forwards(app_label, schema_editor, from_state, to_state)
  684. def references_field(self, model_name, name, app_label):
  685. return self.references_model(model_name, app_label) and (
  686. self.order_with_respect_to is None or name == self.order_with_respect_to
  687. )
  688. def describe(self):
  689. return "Set order_with_respect_to on %s to %s" % (
  690. self.name,
  691. self.order_with_respect_to,
  692. )
  693. @property
  694. def migration_name_fragment(self):
  695. return "alter_%s_order_with_respect_to" % self.name_lower
  696. class AlterModelOptions(ModelOptionOperation):
  697. """
  698. Set new model options that don't directly affect the database schema
  699. (like verbose_name, permissions, ordering). Python code in migrations
  700. may still need them.
  701. """
  702. # Model options we want to compare and preserve in an AlterModelOptions op
  703. ALTER_OPTION_KEYS = [
  704. "base_manager_name",
  705. "default_manager_name",
  706. "default_related_name",
  707. "get_latest_by",
  708. "managed",
  709. "ordering",
  710. "permissions",
  711. "default_permissions",
  712. "select_on_save",
  713. "verbose_name",
  714. "verbose_name_plural",
  715. ]
  716. def __init__(self, name, options):
  717. self.options = options
  718. super().__init__(name)
  719. def deconstruct(self):
  720. kwargs = {
  721. "name": self.name,
  722. "options": self.options,
  723. }
  724. return (self.__class__.__qualname__, [], kwargs)
  725. def state_forwards(self, app_label, state):
  726. state.alter_model_options(
  727. app_label,
  728. self.name_lower,
  729. self.options,
  730. self.ALTER_OPTION_KEYS,
  731. )
  732. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  733. pass
  734. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  735. pass
  736. def describe(self):
  737. return "Change Meta options on %s" % self.name
  738. @property
  739. def migration_name_fragment(self):
  740. return "alter_%s_options" % self.name_lower
  741. class AlterModelManagers(ModelOptionOperation):
  742. """Alter the model's managers."""
  743. serialization_expand_args = ["managers"]
  744. def __init__(self, name, managers):
  745. self.managers = managers
  746. super().__init__(name)
  747. def deconstruct(self):
  748. return (self.__class__.__qualname__, [self.name, self.managers], {})
  749. def state_forwards(self, app_label, state):
  750. state.alter_model_managers(app_label, self.name_lower, self.managers)
  751. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  752. pass
  753. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  754. pass
  755. def describe(self):
  756. return "Change managers on %s" % self.name
  757. @property
  758. def migration_name_fragment(self):
  759. return "alter_%s_managers" % self.name_lower
  760. class IndexOperation(Operation):
  761. option_name = "indexes"
  762. @cached_property
  763. def model_name_lower(self):
  764. return self.model_name.lower()
  765. class AddIndex(IndexOperation):
  766. """Add an index on a model."""
  767. category = OperationCategory.ADDITION
  768. def __init__(self, model_name, index):
  769. self.model_name = model_name
  770. if not index.name:
  771. raise ValueError(
  772. "Indexes passed to AddIndex operations require a name "
  773. "argument. %r doesn't have one." % index
  774. )
  775. self.index = index
  776. def state_forwards(self, app_label, state):
  777. state.add_index(app_label, self.model_name_lower, self.index)
  778. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  779. model = to_state.apps.get_model(app_label, self.model_name)
  780. if self.allow_migrate_model(schema_editor.connection.alias, model):
  781. schema_editor.add_index(model, self.index)
  782. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  783. model = from_state.apps.get_model(app_label, self.model_name)
  784. if self.allow_migrate_model(schema_editor.connection.alias, model):
  785. schema_editor.remove_index(model, self.index)
  786. def deconstruct(self):
  787. kwargs = {
  788. "model_name": self.model_name,
  789. "index": self.index,
  790. }
  791. return (
  792. self.__class__.__qualname__,
  793. [],
  794. kwargs,
  795. )
  796. def describe(self):
  797. if self.index.expressions:
  798. return "Create index %s on %s on model %s" % (
  799. self.index.name,
  800. ", ".join([str(expression) for expression in self.index.expressions]),
  801. self.model_name,
  802. )
  803. return "Create index %s on field(s) %s of model %s" % (
  804. self.index.name,
  805. ", ".join(self.index.fields),
  806. self.model_name,
  807. )
  808. @property
  809. def migration_name_fragment(self):
  810. return "%s_%s" % (self.model_name_lower, self.index.name.lower())
  811. def reduce(self, operation, app_label):
  812. if isinstance(operation, RemoveIndex) and self.index.name == operation.name:
  813. return []
  814. if isinstance(operation, RenameIndex) and self.index.name == operation.old_name:
  815. self.index.name = operation.new_name
  816. return [AddIndex(model_name=self.model_name, index=self.index)]
  817. return super().reduce(operation, app_label)
  818. class RemoveIndex(IndexOperation):
  819. """Remove an index from a model."""
  820. category = OperationCategory.REMOVAL
  821. def __init__(self, model_name, name):
  822. self.model_name = model_name
  823. self.name = name
  824. def state_forwards(self, app_label, state):
  825. state.remove_index(app_label, self.model_name_lower, self.name)
  826. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  827. model = from_state.apps.get_model(app_label, self.model_name)
  828. if self.allow_migrate_model(schema_editor.connection.alias, model):
  829. from_model_state = from_state.models[app_label, self.model_name_lower]
  830. index = from_model_state.get_index_by_name(self.name)
  831. schema_editor.remove_index(model, index)
  832. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  833. model = to_state.apps.get_model(app_label, self.model_name)
  834. if self.allow_migrate_model(schema_editor.connection.alias, model):
  835. to_model_state = to_state.models[app_label, self.model_name_lower]
  836. index = to_model_state.get_index_by_name(self.name)
  837. schema_editor.add_index(model, index)
  838. def deconstruct(self):
  839. kwargs = {
  840. "model_name": self.model_name,
  841. "name": self.name,
  842. }
  843. return (
  844. self.__class__.__qualname__,
  845. [],
  846. kwargs,
  847. )
  848. def describe(self):
  849. return "Remove index %s from %s" % (self.name, self.model_name)
  850. @property
  851. def migration_name_fragment(self):
  852. return "remove_%s_%s" % (self.model_name_lower, self.name.lower())
  853. class RenameIndex(IndexOperation):
  854. """Rename an index."""
  855. category = OperationCategory.ALTERATION
  856. def __init__(self, model_name, new_name, old_name=None, old_fields=None):
  857. if not old_name and not old_fields:
  858. raise ValueError(
  859. "RenameIndex requires one of old_name and old_fields arguments to be "
  860. "set."
  861. )
  862. if old_name and old_fields:
  863. raise ValueError(
  864. "RenameIndex.old_name and old_fields are mutually exclusive."
  865. )
  866. self.model_name = model_name
  867. self.new_name = new_name
  868. self.old_name = old_name
  869. self.old_fields = old_fields
  870. @cached_property
  871. def old_name_lower(self):
  872. return self.old_name.lower()
  873. @cached_property
  874. def new_name_lower(self):
  875. return self.new_name.lower()
  876. def deconstruct(self):
  877. kwargs = {
  878. "model_name": self.model_name,
  879. "new_name": self.new_name,
  880. }
  881. if self.old_name:
  882. kwargs["old_name"] = self.old_name
  883. if self.old_fields:
  884. kwargs["old_fields"] = self.old_fields
  885. return (self.__class__.__qualname__, [], kwargs)
  886. def state_forwards(self, app_label, state):
  887. if self.old_fields:
  888. state.add_index(
  889. app_label,
  890. self.model_name_lower,
  891. models.Index(fields=self.old_fields, name=self.new_name),
  892. )
  893. state.remove_model_options(
  894. app_label,
  895. self.model_name_lower,
  896. AlterIndexTogether.option_name,
  897. self.old_fields,
  898. )
  899. else:
  900. state.rename_index(
  901. app_label, self.model_name_lower, self.old_name, self.new_name
  902. )
  903. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  904. model = to_state.apps.get_model(app_label, self.model_name)
  905. if not self.allow_migrate_model(schema_editor.connection.alias, model):
  906. return
  907. if self.old_fields:
  908. from_model = from_state.apps.get_model(app_label, self.model_name)
  909. columns = [
  910. from_model._meta.get_field(field).column for field in self.old_fields
  911. ]
  912. matching_index_name = schema_editor._constraint_names(
  913. from_model,
  914. column_names=columns,
  915. index=True,
  916. unique=False,
  917. )
  918. if len(matching_index_name) != 1:
  919. raise ValueError(
  920. "Found wrong number (%s) of indexes for %s(%s)."
  921. % (
  922. len(matching_index_name),
  923. from_model._meta.db_table,
  924. ", ".join(columns),
  925. )
  926. )
  927. old_index = models.Index(
  928. fields=self.old_fields,
  929. name=matching_index_name[0],
  930. )
  931. else:
  932. from_model_state = from_state.models[app_label, self.model_name_lower]
  933. old_index = from_model_state.get_index_by_name(self.old_name)
  934. # Don't alter when the index name is not changed.
  935. if old_index.name == self.new_name:
  936. return
  937. to_model_state = to_state.models[app_label, self.model_name_lower]
  938. new_index = to_model_state.get_index_by_name(self.new_name)
  939. schema_editor.rename_index(model, old_index, new_index)
  940. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  941. if self.old_fields:
  942. # Backward operation with unnamed index is a no-op.
  943. return
  944. self.new_name_lower, self.old_name_lower = (
  945. self.old_name_lower,
  946. self.new_name_lower,
  947. )
  948. self.new_name, self.old_name = self.old_name, self.new_name
  949. self.database_forwards(app_label, schema_editor, from_state, to_state)
  950. self.new_name_lower, self.old_name_lower = (
  951. self.old_name_lower,
  952. self.new_name_lower,
  953. )
  954. self.new_name, self.old_name = self.old_name, self.new_name
  955. def describe(self):
  956. if self.old_name:
  957. return (
  958. f"Rename index {self.old_name} on {self.model_name} to {self.new_name}"
  959. )
  960. return (
  961. f"Rename unnamed index for {self.old_fields} on {self.model_name} to "
  962. f"{self.new_name}"
  963. )
  964. @property
  965. def migration_name_fragment(self):
  966. if self.old_name:
  967. return "rename_%s_%s" % (self.old_name_lower, self.new_name_lower)
  968. return "rename_%s_%s_%s" % (
  969. self.model_name_lower,
  970. "_".join(self.old_fields),
  971. self.new_name_lower,
  972. )
  973. def reduce(self, operation, app_label):
  974. if (
  975. isinstance(operation, RenameIndex)
  976. and self.model_name_lower == operation.model_name_lower
  977. and operation.old_name
  978. and self.new_name_lower == operation.old_name_lower
  979. ):
  980. return [
  981. RenameIndex(
  982. self.model_name,
  983. new_name=operation.new_name,
  984. old_name=self.old_name,
  985. old_fields=self.old_fields,
  986. )
  987. ]
  988. return super().reduce(operation, app_label)
  989. class AddConstraint(IndexOperation):
  990. category = OperationCategory.ADDITION
  991. option_name = "constraints"
  992. def __init__(self, model_name, constraint):
  993. self.model_name = model_name
  994. self.constraint = constraint
  995. def state_forwards(self, app_label, state):
  996. state.add_constraint(app_label, self.model_name_lower, self.constraint)
  997. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  998. model = to_state.apps.get_model(app_label, self.model_name)
  999. if self.allow_migrate_model(schema_editor.connection.alias, model):
  1000. schema_editor.add_constraint(model, self.constraint)
  1001. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  1002. model = to_state.apps.get_model(app_label, self.model_name)
  1003. if self.allow_migrate_model(schema_editor.connection.alias, model):
  1004. schema_editor.remove_constraint(model, self.constraint)
  1005. def deconstruct(self):
  1006. return (
  1007. self.__class__.__name__,
  1008. [],
  1009. {
  1010. "model_name": self.model_name,
  1011. "constraint": self.constraint,
  1012. },
  1013. )
  1014. def describe(self):
  1015. return "Create constraint %s on model %s" % (
  1016. self.constraint.name,
  1017. self.model_name,
  1018. )
  1019. @property
  1020. def migration_name_fragment(self):
  1021. return "%s_%s" % (self.model_name_lower, self.constraint.name.lower())
  1022. def reduce(self, operation, app_label):
  1023. if (
  1024. isinstance(operation, RemoveConstraint)
  1025. and self.model_name_lower == operation.model_name_lower
  1026. and self.constraint.name == operation.name
  1027. ):
  1028. return []
  1029. return super().reduce(operation, app_label)
  1030. class RemoveConstraint(IndexOperation):
  1031. category = OperationCategory.REMOVAL
  1032. option_name = "constraints"
  1033. def __init__(self, model_name, name):
  1034. self.model_name = model_name
  1035. self.name = name
  1036. def state_forwards(self, app_label, state):
  1037. state.remove_constraint(app_label, self.model_name_lower, self.name)
  1038. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  1039. model = to_state.apps.get_model(app_label, self.model_name)
  1040. if self.allow_migrate_model(schema_editor.connection.alias, model):
  1041. from_model_state = from_state.models[app_label, self.model_name_lower]
  1042. constraint = from_model_state.get_constraint_by_name(self.name)
  1043. schema_editor.remove_constraint(model, constraint)
  1044. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  1045. model = to_state.apps.get_model(app_label, self.model_name)
  1046. if self.allow_migrate_model(schema_editor.connection.alias, model):
  1047. to_model_state = to_state.models[app_label, self.model_name_lower]
  1048. constraint = to_model_state.get_constraint_by_name(self.name)
  1049. schema_editor.add_constraint(model, constraint)
  1050. def deconstruct(self):
  1051. return (
  1052. self.__class__.__name__,
  1053. [],
  1054. {
  1055. "model_name": self.model_name,
  1056. "name": self.name,
  1057. },
  1058. )
  1059. def describe(self):
  1060. return "Remove constraint %s from model %s" % (self.name, self.model_name)
  1061. @property
  1062. def migration_name_fragment(self):
  1063. return "remove_%s_%s" % (self.model_name_lower, self.name.lower())