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 21 KiB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605
  1. from __future__ import unicode_literals
  2. from django.db import models
  3. from django.db.migrations.operations.base import Operation
  4. from django.db.migrations.state import ModelState
  5. from django.db.models.options import normalize_together
  6. from django.utils import six
  7. from django.utils.functional import cached_property
  8. class CreateModel(Operation):
  9. """
  10. Create a model's table.
  11. """
  12. serialization_expand_args = ['fields', 'options', 'managers']
  13. def __init__(self, name, fields, options=None, bases=None, managers=None):
  14. self.name = name
  15. self.fields = fields
  16. self.options = options or {}
  17. self.bases = bases or (models.Model,)
  18. self.managers = managers or []
  19. @cached_property
  20. def name_lower(self):
  21. return self.name.lower()
  22. def deconstruct(self):
  23. kwargs = {
  24. 'name': self.name,
  25. 'fields': self.fields,
  26. }
  27. if self.options:
  28. kwargs['options'] = self.options
  29. if self.bases and self.bases != (models.Model,):
  30. kwargs['bases'] = self.bases
  31. if self.managers and self.managers != [('objects', models.Manager())]:
  32. kwargs['managers'] = self.managers
  33. return (
  34. self.__class__.__name__,
  35. [],
  36. kwargs
  37. )
  38. def state_forwards(self, app_label, state):
  39. state.add_model(ModelState(
  40. app_label,
  41. self.name,
  42. list(self.fields),
  43. dict(self.options),
  44. tuple(self.bases),
  45. list(self.managers),
  46. ))
  47. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  48. model = to_state.apps.get_model(app_label, self.name)
  49. if self.allow_migrate_model(schema_editor.connection.alias, model):
  50. schema_editor.create_model(model)
  51. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  52. model = from_state.apps.get_model(app_label, self.name)
  53. if self.allow_migrate_model(schema_editor.connection.alias, model):
  54. schema_editor.delete_model(model)
  55. def describe(self):
  56. return "Create %smodel %s" % ("proxy " if self.options.get("proxy", False) else "", self.name)
  57. def references_model(self, name, app_label=None):
  58. strings_to_check = [self.name]
  59. # Check we didn't inherit from the model
  60. for base in self.bases:
  61. if isinstance(base, six.string_types):
  62. strings_to_check.append(base.split(".")[-1])
  63. # Check we have no FKs/M2Ms with it
  64. for fname, field in self.fields:
  65. if field.remote_field:
  66. if isinstance(field.remote_field.model, six.string_types):
  67. strings_to_check.append(field.remote_field.model.split(".")[-1])
  68. # Now go over all the strings and compare them
  69. for string in strings_to_check:
  70. if string.lower() == name.lower():
  71. return True
  72. return False
  73. class DeleteModel(Operation):
  74. """
  75. Drops a model's table.
  76. """
  77. def __init__(self, name):
  78. self.name = name
  79. @cached_property
  80. def name_lower(self):
  81. return self.name.lower()
  82. def deconstruct(self):
  83. kwargs = {
  84. 'name': self.name,
  85. }
  86. return (
  87. self.__class__.__name__,
  88. [],
  89. kwargs
  90. )
  91. def state_forwards(self, app_label, state):
  92. state.remove_model(app_label, self.name_lower)
  93. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  94. model = from_state.apps.get_model(app_label, self.name)
  95. if self.allow_migrate_model(schema_editor.connection.alias, model):
  96. schema_editor.delete_model(model)
  97. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  98. model = to_state.apps.get_model(app_label, self.name)
  99. if self.allow_migrate_model(schema_editor.connection.alias, model):
  100. schema_editor.create_model(model)
  101. def references_model(self, name, app_label=None):
  102. return name.lower() == self.name_lower
  103. def describe(self):
  104. return "Delete model %s" % (self.name, )
  105. class RenameModel(Operation):
  106. """
  107. Renames a model.
  108. """
  109. def __init__(self, old_name, new_name):
  110. self.old_name = old_name
  111. self.new_name = new_name
  112. @cached_property
  113. def old_name_lower(self):
  114. return self.old_name.lower()
  115. @cached_property
  116. def new_name_lower(self):
  117. return self.new_name.lower()
  118. def deconstruct(self):
  119. kwargs = {
  120. 'old_name': self.old_name,
  121. 'new_name': self.new_name,
  122. }
  123. return (
  124. self.__class__.__name__,
  125. [],
  126. kwargs
  127. )
  128. def state_forwards(self, app_label, state):
  129. apps = state.apps
  130. model = apps.get_model(app_label, self.old_name)
  131. model._meta.apps = apps
  132. # Get all of the related objects we need to repoint
  133. all_related_objects = (
  134. f for f in model._meta.get_fields(include_hidden=True)
  135. if f.auto_created and not f.concrete and (not f.hidden or f.many_to_many)
  136. )
  137. # Rename the model
  138. state.models[app_label, self.new_name_lower] = state.models[app_label, self.old_name_lower]
  139. state.models[app_label, self.new_name_lower].name = self.new_name
  140. state.remove_model(app_label, self.old_name_lower)
  141. # Repoint the FKs and M2Ms pointing to us
  142. for related_object in all_related_objects:
  143. if related_object.model is not model:
  144. # The model being renamed does not participate in this relation
  145. # directly. Rather, a superclass does.
  146. continue
  147. # Use the new related key for self referential related objects.
  148. if related_object.related_model == model:
  149. related_key = (app_label, self.new_name_lower)
  150. else:
  151. related_key = (
  152. related_object.related_model._meta.app_label,
  153. related_object.related_model._meta.model_name,
  154. )
  155. new_fields = []
  156. for name, field in state.models[related_key].fields:
  157. if name == related_object.field.name:
  158. field = field.clone()
  159. field.remote_field.model = "%s.%s" % (app_label, self.new_name)
  160. new_fields.append((name, field))
  161. state.models[related_key].fields = new_fields
  162. state.reload_model(*related_key)
  163. state.reload_model(app_label, self.new_name_lower)
  164. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  165. new_model = to_state.apps.get_model(app_label, self.new_name)
  166. if self.allow_migrate_model(schema_editor.connection.alias, new_model):
  167. old_model = from_state.apps.get_model(app_label, self.old_name)
  168. # Move the main table
  169. schema_editor.alter_db_table(
  170. new_model,
  171. old_model._meta.db_table,
  172. new_model._meta.db_table,
  173. )
  174. # Alter the fields pointing to us
  175. for related_object in old_model._meta.related_objects:
  176. if related_object.related_model == old_model:
  177. model = new_model
  178. related_key = (app_label, self.new_name_lower)
  179. else:
  180. model = related_object.related_model
  181. related_key = (
  182. related_object.related_model._meta.app_label,
  183. related_object.related_model._meta.model_name,
  184. )
  185. to_field = to_state.apps.get_model(
  186. *related_key
  187. )._meta.get_field(related_object.field.name)
  188. schema_editor.alter_field(
  189. model,
  190. related_object.field,
  191. to_field,
  192. )
  193. # Rename M2M fields whose name is based on this model's name.
  194. fields = zip(old_model._meta.local_many_to_many, new_model._meta.local_many_to_many)
  195. for (old_field, new_field) in fields:
  196. # Skip self-referential fields as these are renamed above.
  197. if new_field.model == new_field.related_model or not new_field.remote_field.through._meta.auto_created:
  198. continue
  199. # Rename the M2M table that's based on this model's name.
  200. old_m2m_model = old_field.remote_field.through
  201. new_m2m_model = new_field.remote_field.through
  202. schema_editor.alter_db_table(
  203. new_m2m_model,
  204. old_m2m_model._meta.db_table,
  205. new_m2m_model._meta.db_table,
  206. )
  207. # Rename the column in the M2M table that's based on this
  208. # model's name.
  209. schema_editor.alter_field(
  210. new_m2m_model,
  211. old_m2m_model._meta.get_field(old_model._meta.model_name),
  212. new_m2m_model._meta.get_field(new_model._meta.model_name),
  213. )
  214. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  215. self.new_name_lower, self.old_name_lower = self.old_name_lower, self.new_name_lower
  216. self.new_name, self.old_name = self.old_name, self.new_name
  217. self.database_forwards(app_label, schema_editor, from_state, to_state)
  218. self.new_name_lower, self.old_name_lower = self.old_name_lower, self.new_name_lower
  219. self.new_name, self.old_name = self.old_name, self.new_name
  220. def references_model(self, name, app_label=None):
  221. return (
  222. name.lower() == self.old_name_lower or
  223. name.lower() == self.new_name_lower
  224. )
  225. def describe(self):
  226. return "Rename model %s to %s" % (self.old_name, self.new_name)
  227. class AlterModelTable(Operation):
  228. """
  229. Renames a model's table
  230. """
  231. def __init__(self, name, table):
  232. self.name = name
  233. self.table = table
  234. @cached_property
  235. def name_lower(self):
  236. return self.name.lower()
  237. def deconstruct(self):
  238. kwargs = {
  239. 'name': self.name,
  240. 'table': self.table,
  241. }
  242. return (
  243. self.__class__.__name__,
  244. [],
  245. kwargs
  246. )
  247. def state_forwards(self, app_label, state):
  248. state.models[app_label, self.name_lower].options["db_table"] = self.table
  249. state.reload_model(app_label, self.name_lower)
  250. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  251. new_model = to_state.apps.get_model(app_label, self.name)
  252. if self.allow_migrate_model(schema_editor.connection.alias, new_model):
  253. old_model = from_state.apps.get_model(app_label, self.name)
  254. schema_editor.alter_db_table(
  255. new_model,
  256. old_model._meta.db_table,
  257. new_model._meta.db_table,
  258. )
  259. # Rename M2M fields whose name is based on this model's db_table
  260. for (old_field, new_field) in zip(old_model._meta.local_many_to_many, new_model._meta.local_many_to_many):
  261. if new_field.remote_field.through._meta.auto_created:
  262. schema_editor.alter_db_table(
  263. new_field.remote_field.through,
  264. old_field.remote_field.through._meta.db_table,
  265. new_field.remote_field.through._meta.db_table,
  266. )
  267. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  268. return self.database_forwards(app_label, schema_editor, from_state, to_state)
  269. def references_model(self, name, app_label=None):
  270. return name.lower() == self.name_lower
  271. def describe(self):
  272. return "Rename table for %s to %s" % (self.name, self.table)
  273. class AlterUniqueTogether(Operation):
  274. """
  275. Changes the value of unique_together to the target one.
  276. Input value of unique_together must be a set of tuples.
  277. """
  278. option_name = "unique_together"
  279. def __init__(self, name, unique_together):
  280. self.name = name
  281. unique_together = normalize_together(unique_together)
  282. self.unique_together = set(tuple(cons) for cons in unique_together)
  283. @cached_property
  284. def name_lower(self):
  285. return self.name.lower()
  286. def deconstruct(self):
  287. kwargs = {
  288. 'name': self.name,
  289. 'unique_together': self.unique_together,
  290. }
  291. return (
  292. self.__class__.__name__,
  293. [],
  294. kwargs
  295. )
  296. def state_forwards(self, app_label, state):
  297. model_state = state.models[app_label, self.name_lower]
  298. model_state.options[self.option_name] = self.unique_together
  299. state.reload_model(app_label, self.name_lower)
  300. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  301. new_model = to_state.apps.get_model(app_label, self.name)
  302. if self.allow_migrate_model(schema_editor.connection.alias, new_model):
  303. old_model = from_state.apps.get_model(app_label, self.name)
  304. schema_editor.alter_unique_together(
  305. new_model,
  306. getattr(old_model._meta, self.option_name, set()),
  307. getattr(new_model._meta, self.option_name, set()),
  308. )
  309. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  310. return self.database_forwards(app_label, schema_editor, from_state, to_state)
  311. def references_model(self, name, app_label=None):
  312. return name.lower() == self.name_lower
  313. def references_field(self, model_name, name, app_label=None):
  314. return (
  315. self.references_model(model_name, app_label) and
  316. (
  317. not self.unique_together or
  318. any((name in together) for together in self.unique_together)
  319. )
  320. )
  321. def describe(self):
  322. return "Alter %s for %s (%s constraint(s))" % (self.option_name, self.name, len(self.unique_together or ''))
  323. class AlterIndexTogether(Operation):
  324. """
  325. Changes the value of index_together to the target one.
  326. Input value of index_together must be a set of tuples.
  327. """
  328. option_name = "index_together"
  329. def __init__(self, name, index_together):
  330. self.name = name
  331. index_together = normalize_together(index_together)
  332. self.index_together = set(tuple(cons) for cons in index_together)
  333. @cached_property
  334. def name_lower(self):
  335. return self.name.lower()
  336. def deconstruct(self):
  337. kwargs = {
  338. 'name': self.name,
  339. 'index_together': self.index_together,
  340. }
  341. return (
  342. self.__class__.__name__,
  343. [],
  344. kwargs
  345. )
  346. def state_forwards(self, app_label, state):
  347. model_state = state.models[app_label, self.name_lower]
  348. model_state.options[self.option_name] = self.index_together
  349. state.reload_model(app_label, self.name_lower)
  350. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  351. new_model = to_state.apps.get_model(app_label, self.name)
  352. if self.allow_migrate_model(schema_editor.connection.alias, new_model):
  353. old_model = from_state.apps.get_model(app_label, self.name)
  354. schema_editor.alter_index_together(
  355. new_model,
  356. getattr(old_model._meta, self.option_name, set()),
  357. getattr(new_model._meta, self.option_name, set()),
  358. )
  359. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  360. return self.database_forwards(app_label, schema_editor, from_state, to_state)
  361. def references_model(self, name, app_label=None):
  362. return name.lower() == self.name_lower
  363. def references_field(self, model_name, name, app_label=None):
  364. return (
  365. self.references_model(model_name, app_label) and
  366. (
  367. not self.index_together or
  368. any((name in together) for together in self.index_together)
  369. )
  370. )
  371. def describe(self):
  372. return "Alter %s for %s (%s constraint(s))" % (self.option_name, self.name, len(self.index_together or ''))
  373. class AlterOrderWithRespectTo(Operation):
  374. """
  375. Represents a change with the order_with_respect_to option.
  376. """
  377. def __init__(self, name, order_with_respect_to):
  378. self.name = name
  379. self.order_with_respect_to = order_with_respect_to
  380. @cached_property
  381. def name_lower(self):
  382. return self.name.lower()
  383. def deconstruct(self):
  384. kwargs = {
  385. 'name': self.name,
  386. 'order_with_respect_to': self.order_with_respect_to,
  387. }
  388. return (
  389. self.__class__.__name__,
  390. [],
  391. kwargs
  392. )
  393. def state_forwards(self, app_label, state):
  394. model_state = state.models[app_label, self.name_lower]
  395. model_state.options['order_with_respect_to'] = self.order_with_respect_to
  396. state.reload_model(app_label, self.name_lower)
  397. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  398. to_model = to_state.apps.get_model(app_label, self.name)
  399. if self.allow_migrate_model(schema_editor.connection.alias, to_model):
  400. from_model = from_state.apps.get_model(app_label, self.name)
  401. # Remove a field if we need to
  402. if from_model._meta.order_with_respect_to and not to_model._meta.order_with_respect_to:
  403. schema_editor.remove_field(from_model, from_model._meta.get_field("_order"))
  404. # Add a field if we need to (altering the column is untouched as
  405. # it's likely a rename)
  406. elif to_model._meta.order_with_respect_to and not from_model._meta.order_with_respect_to:
  407. field = to_model._meta.get_field("_order")
  408. if not field.has_default():
  409. field.default = 0
  410. schema_editor.add_field(
  411. from_model,
  412. field,
  413. )
  414. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  415. self.database_forwards(app_label, schema_editor, from_state, to_state)
  416. def references_model(self, name, app_label=None):
  417. return name.lower() == self.name_lower
  418. def references_field(self, model_name, name, app_label=None):
  419. return (
  420. self.references_model(model_name, app_label) and
  421. (
  422. self.order_with_respect_to is None or
  423. name == self.order_with_respect_to
  424. )
  425. )
  426. def describe(self):
  427. return "Set order_with_respect_to on %s to %s" % (self.name, self.order_with_respect_to)
  428. class AlterModelOptions(Operation):
  429. """
  430. Sets new model options that don't directly affect the database schema
  431. (like verbose_name, permissions, ordering). Python code in migrations
  432. may still need them.
  433. """
  434. # Model options we want to compare and preserve in an AlterModelOptions op
  435. ALTER_OPTION_KEYS = [
  436. "get_latest_by",
  437. "managed",
  438. "ordering",
  439. "permissions",
  440. "default_permissions",
  441. "select_on_save",
  442. "verbose_name",
  443. "verbose_name_plural",
  444. ]
  445. def __init__(self, name, options):
  446. self.name = name
  447. self.options = options
  448. @cached_property
  449. def name_lower(self):
  450. return self.name.lower()
  451. def deconstruct(self):
  452. kwargs = {
  453. 'name': self.name,
  454. 'options': self.options,
  455. }
  456. return (
  457. self.__class__.__name__,
  458. [],
  459. kwargs
  460. )
  461. def state_forwards(self, app_label, state):
  462. model_state = state.models[app_label, self.name_lower]
  463. model_state.options = dict(model_state.options)
  464. model_state.options.update(self.options)
  465. for key in self.ALTER_OPTION_KEYS:
  466. if key not in self.options and key in model_state.options:
  467. del model_state.options[key]
  468. state.reload_model(app_label, self.name_lower)
  469. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  470. pass
  471. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  472. pass
  473. def references_model(self, name, app_label=None):
  474. return name.lower() == self.name_lower
  475. def describe(self):
  476. return "Change Meta options on %s" % (self.name, )
  477. class AlterModelManagers(Operation):
  478. """
  479. Alters the model's managers
  480. """
  481. serialization_expand_args = ['managers']
  482. def __init__(self, name, managers):
  483. self.name = name
  484. self.managers = managers
  485. @cached_property
  486. def name_lower(self):
  487. return self.name.lower()
  488. def deconstruct(self):
  489. return (
  490. self.__class__.__name__,
  491. [self.name, self.managers],
  492. {}
  493. )
  494. def state_forwards(self, app_label, state):
  495. model_state = state.models[app_label, self.name_lower]
  496. model_state.managers = list(self.managers)
  497. state.reload_model(app_label, self.name_lower)
  498. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  499. pass
  500. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  501. pass
  502. def references_model(self, name, app_label=None):
  503. return name.lower() == self.name_lower
  504. def describe(self):
  505. return "Change managers on %s" % (self.name, )