models.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. from __future__ import unicode_literals
  2. from django.db import models
  3. from django.db.models.options import normalize_together
  4. from django.db.migrations.state import ModelState
  5. from django.db.migrations.operations.base import Operation
  6. from django.utils import six
  7. class CreateModel(Operation):
  8. """
  9. Create a model's table.
  10. """
  11. serialization_expand_args = ['fields', 'options']
  12. def __init__(self, name, fields, options=None, bases=None):
  13. self.name = name
  14. self.fields = fields
  15. self.options = options or {}
  16. self.bases = bases or (models.Model,)
  17. def state_forwards(self, app_label, state):
  18. state.models[app_label, self.name.lower()] = ModelState(
  19. app_label,
  20. self.name,
  21. list(self.fields),
  22. dict(self.options),
  23. tuple(self.bases),
  24. )
  25. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  26. apps = to_state.render()
  27. model = apps.get_model(app_label, self.name)
  28. if self.allowed_to_migrate(schema_editor.connection.alias, model):
  29. schema_editor.create_model(model)
  30. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  31. apps = from_state.render()
  32. model = apps.get_model(app_label, self.name)
  33. if self.allowed_to_migrate(schema_editor.connection.alias, model):
  34. schema_editor.delete_model(model)
  35. def describe(self):
  36. return "Create %smodel %s" % ("proxy " if self.options.get("proxy", False) else "", self.name)
  37. def references_model(self, name, app_label=None):
  38. strings_to_check = [self.name]
  39. # Check we didn't inherit from the model
  40. for base in self.bases:
  41. if isinstance(base, six.string_types):
  42. strings_to_check.append(base.split(".")[-1])
  43. # Check we have no FKs/M2Ms with it
  44. for fname, field in self.fields:
  45. if field.rel:
  46. if isinstance(field.rel.to, six.string_types):
  47. strings_to_check.append(field.rel.to.split(".")[-1])
  48. # Now go over all the strings and compare them
  49. for string in strings_to_check:
  50. if string.lower() == name.lower():
  51. return True
  52. return False
  53. def __eq__(self, other):
  54. return (
  55. (self.__class__ == other.__class__) and
  56. (self.name == other.name) and
  57. (self.options == other.options) and
  58. (self.bases == other.bases) and
  59. ([(k, f.deconstruct()[1:]) for k, f in self.fields] == [(k, f.deconstruct()[1:]) for k, f in other.fields])
  60. )
  61. class DeleteModel(Operation):
  62. """
  63. Drops a model's table.
  64. """
  65. def __init__(self, name):
  66. self.name = name
  67. def state_forwards(self, app_label, state):
  68. del state.models[app_label, self.name.lower()]
  69. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  70. apps = from_state.render()
  71. model = apps.get_model(app_label, self.name)
  72. if self.allowed_to_migrate(schema_editor.connection.alias, model):
  73. schema_editor.delete_model(model)
  74. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  75. apps = to_state.render()
  76. model = apps.get_model(app_label, self.name)
  77. if self.allowed_to_migrate(schema_editor.connection.alias, model):
  78. schema_editor.create_model(model)
  79. def references_model(self, name, app_label=None):
  80. return name.lower() == self.name.lower()
  81. def describe(self):
  82. return "Delete model %s" % (self.name, )
  83. class RenameModel(Operation):
  84. """
  85. Renames a model.
  86. """
  87. reversible = False
  88. def __init__(self, old_name, new_name):
  89. self.old_name = old_name
  90. self.new_name = new_name
  91. def state_forwards(self, app_label, state):
  92. # Get all of the related objects we need to repoint
  93. apps = state.render(skip_cache=True)
  94. model = apps.get_model(app_label, self.old_name)
  95. related_objects = model._meta.get_all_related_objects()
  96. related_m2m_objects = model._meta.get_all_related_many_to_many_objects()
  97. # Rename the model
  98. state.models[app_label, self.new_name.lower()] = state.models[app_label, self.old_name.lower()]
  99. state.models[app_label, self.new_name.lower()].name = self.new_name
  100. del state.models[app_label, self.old_name.lower()]
  101. # Repoint the FKs and M2Ms pointing to us
  102. for related_object in (related_objects + related_m2m_objects):
  103. # Use the new related key for self referential related objects.
  104. if related_object.model == model:
  105. related_key = (app_label, self.new_name.lower())
  106. else:
  107. related_key = (
  108. related_object.model._meta.app_label,
  109. related_object.model._meta.object_name.lower(),
  110. )
  111. new_fields = []
  112. for name, field in state.models[related_key].fields:
  113. if name == related_object.field.name:
  114. field = field.clone()
  115. field.rel.to = "%s.%s" % (app_label, self.new_name)
  116. new_fields.append((name, field))
  117. state.models[related_key].fields = new_fields
  118. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  119. old_apps = from_state.render()
  120. new_apps = to_state.render()
  121. old_model = old_apps.get_model(app_label, self.old_name)
  122. new_model = new_apps.get_model(app_label, self.new_name)
  123. if self.allowed_to_migrate(schema_editor.connection.alias, new_model):
  124. # Move the main table
  125. schema_editor.alter_db_table(
  126. new_model,
  127. old_model._meta.db_table,
  128. new_model._meta.db_table,
  129. )
  130. # Alter the fields pointing to us
  131. related_objects = old_model._meta.get_all_related_objects()
  132. related_m2m_objects = old_model._meta.get_all_related_many_to_many_objects()
  133. for related_object in (related_objects + related_m2m_objects):
  134. if related_object.model == old_model:
  135. model = new_model
  136. related_key = (app_label, self.new_name.lower())
  137. else:
  138. model = related_object.model
  139. related_key = (
  140. related_object.model._meta.app_label,
  141. related_object.model._meta.object_name.lower(),
  142. )
  143. to_field = new_apps.get_model(
  144. *related_key
  145. )._meta.get_field_by_name(related_object.field.name)[0]
  146. schema_editor.alter_field(
  147. model,
  148. related_object.field,
  149. to_field,
  150. )
  151. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  152. self.new_name, self.old_name = self.old_name, self.new_name
  153. self.database_forwards(app_label, schema_editor, from_state, to_state)
  154. self.new_name, self.old_name = self.old_name, self.new_name
  155. def references_model(self, name, app_label=None):
  156. return (
  157. name.lower() == self.old_name.lower() or
  158. name.lower() == self.new_name.lower()
  159. )
  160. def describe(self):
  161. return "Rename model %s to %s" % (self.old_name, self.new_name)
  162. class AlterModelTable(Operation):
  163. """
  164. Renames a model's table
  165. """
  166. def __init__(self, name, table):
  167. self.name = name
  168. self.table = table
  169. def state_forwards(self, app_label, state):
  170. state.models[app_label, self.name.lower()].options["db_table"] = self.table
  171. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  172. old_apps = from_state.render()
  173. new_apps = to_state.render()
  174. old_model = old_apps.get_model(app_label, self.name)
  175. new_model = new_apps.get_model(app_label, self.name)
  176. if self.allowed_to_migrate(schema_editor.connection.alias, new_model):
  177. schema_editor.alter_db_table(
  178. new_model,
  179. old_model._meta.db_table,
  180. new_model._meta.db_table,
  181. )
  182. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  183. return self.database_forwards(app_label, schema_editor, from_state, to_state)
  184. def references_model(self, name, app_label=None):
  185. return name.lower() == self.name.lower()
  186. def describe(self):
  187. return "Rename table for %s to %s" % (self.name, self.table)
  188. class AlterUniqueTogether(Operation):
  189. """
  190. Changes the value of unique_together to the target one.
  191. Input value of unique_together must be a set of tuples.
  192. """
  193. option_name = "unique_together"
  194. def __init__(self, name, unique_together):
  195. self.name = name
  196. unique_together = normalize_together(unique_together)
  197. # need None rather than an empty set to prevent infinite migrations
  198. # after removing unique_together from a model
  199. self.unique_together = set(tuple(cons) for cons in unique_together) or None
  200. def state_forwards(self, app_label, state):
  201. model_state = state.models[app_label, self.name.lower()]
  202. model_state.options[self.option_name] = self.unique_together
  203. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  204. old_apps = from_state.render()
  205. new_apps = to_state.render()
  206. old_model = old_apps.get_model(app_label, self.name)
  207. new_model = new_apps.get_model(app_label, self.name)
  208. if self.allowed_to_migrate(schema_editor.connection.alias, new_model):
  209. schema_editor.alter_unique_together(
  210. new_model,
  211. getattr(old_model._meta, self.option_name, set()),
  212. getattr(new_model._meta, self.option_name, set()),
  213. )
  214. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  215. return self.database_forwards(app_label, schema_editor, from_state, to_state)
  216. def references_model(self, name, app_label=None):
  217. return name.lower() == self.name.lower()
  218. def describe(self):
  219. return "Alter %s for %s (%s constraint(s))" % (self.option_name, self.name, len(self.unique_together or ''))
  220. class AlterIndexTogether(Operation):
  221. """
  222. Changes the value of index_together to the target one.
  223. Input value of index_together must be a set of tuples.
  224. """
  225. option_name = "index_together"
  226. def __init__(self, name, index_together):
  227. self.name = name
  228. index_together = normalize_together(index_together)
  229. # need None rather than an empty set to prevent infinite migrations
  230. # after removing unique_together from a model
  231. self.index_together = set(tuple(cons) for cons in index_together) or None
  232. def state_forwards(self, app_label, state):
  233. model_state = state.models[app_label, self.name.lower()]
  234. model_state.options[self.option_name] = self.index_together
  235. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  236. old_apps = from_state.render()
  237. new_apps = to_state.render()
  238. old_model = old_apps.get_model(app_label, self.name)
  239. new_model = new_apps.get_model(app_label, self.name)
  240. if self.allowed_to_migrate(schema_editor.connection.alias, new_model):
  241. schema_editor.alter_index_together(
  242. new_model,
  243. getattr(old_model._meta, self.option_name, set()),
  244. getattr(new_model._meta, self.option_name, set()),
  245. )
  246. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  247. return self.database_forwards(app_label, schema_editor, from_state, to_state)
  248. def references_model(self, name, app_label=None):
  249. return name.lower() == self.name.lower()
  250. def describe(self):
  251. return "Alter %s for %s (%s constraint(s))" % (self.option_name, self.name, len(self.index_together or ''))
  252. class AlterOrderWithRespectTo(Operation):
  253. """
  254. Represents a change with the order_with_respect_to option.
  255. """
  256. def __init__(self, name, order_with_respect_to):
  257. self.name = name
  258. self.order_with_respect_to = order_with_respect_to
  259. def state_forwards(self, app_label, state):
  260. model_state = state.models[app_label, self.name.lower()]
  261. model_state.options['order_with_respect_to'] = self.order_with_respect_to
  262. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  263. from_model = from_state.render().get_model(app_label, self.name)
  264. to_model = to_state.render().get_model(app_label, self.name)
  265. if self.allowed_to_migrate(schema_editor.connection.alias, to_model):
  266. # Remove a field if we need to
  267. if from_model._meta.order_with_respect_to and not to_model._meta.order_with_respect_to:
  268. schema_editor.remove_field(from_model, from_model._meta.get_field_by_name("_order")[0])
  269. # Add a field if we need to (altering the column is untouched as
  270. # it's likely a rename)
  271. elif to_model._meta.order_with_respect_to and not from_model._meta.order_with_respect_to:
  272. field = to_model._meta.get_field_by_name("_order")[0]
  273. schema_editor.add_field(
  274. from_model,
  275. field,
  276. )
  277. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  278. self.database_forwards(app_label, schema_editor, from_state, to_state)
  279. def references_model(self, name, app_label=None):
  280. return name.lower() == self.name.lower()
  281. def describe(self):
  282. return "Set order_with_respect_to on %s to %s" % (self.name, self.order_with_respect_to)
  283. class AlterModelOptions(Operation):
  284. """
  285. Sets new model options that don't directly affect the database schema
  286. (like verbose_name, permissions, ordering). Python code in migrations
  287. may still need them.
  288. """
  289. # Model options we want to compare and preserve in an AlterModelOptions op
  290. ALTER_OPTION_KEYS = [
  291. "get_latest_by",
  292. "ordering",
  293. "permissions",
  294. "default_permissions",
  295. "select_on_save",
  296. "verbose_name",
  297. "verbose_name_plural",
  298. ]
  299. def __init__(self, name, options):
  300. self.name = name
  301. self.options = options
  302. def state_forwards(self, app_label, state):
  303. model_state = state.models[app_label, self.name.lower()]
  304. model_state.options = dict(model_state.options)
  305. model_state.options.update(self.options)
  306. for key in self.ALTER_OPTION_KEYS:
  307. if key not in self.options and key in model_state.options:
  308. del model_state.options[key]
  309. def database_forwards(self, app_label, schema_editor, from_state, to_state):
  310. pass
  311. def database_backwards(self, app_label, schema_editor, from_state, to_state):
  312. pass
  313. def references_model(self, name, app_label=None):
  314. return name.lower() == self.name.lower()
  315. def describe(self):
  316. return "Change Meta options on %s" % (self.name, )