Development of an internal social media platform with personalised dashboards for students
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 31KB

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