south.creator.actions: 208 total statements, 0.0% covered

Generated: Wed 2013-03-13 10:33 CET

Source file: /media/Envs/Envs/filer-gallery/lib/python2.7/site-packages/south/creator/actions.py

Stats: 0 executed, 202 missed, 6 excluded, 332 ignored

  1. """
  2. Actions - things like 'a model was removed' or 'a field was changed'.
  3. Each one has a class, which can take the action description and insert code
  4. blocks into the forwards() and backwards() methods, in the right place.
  5. """
  6. import sys
  7. from django.db.models.fields.related import RECURSIVE_RELATIONSHIP_CONSTANT
  8. from django.db.models.fields import FieldDoesNotExist, NOT_PROVIDED, CharField, TextField
  9. from south.modelsinspector import value_clean
  10. from south.creator.freezer import remove_useless_attributes, model_key
  11. from south.utils import datetime_utils
  12. class Action(object):
  13. """
  14. Generic base Action class. Contains utility methods for inserting into
  15. the forwards() and backwards() method lists.
  16. """
  17. prepend_forwards = False
  18. prepend_backwards = False
  19. def forwards_code(self):
  20. raise NotImplementedError
  21. def backwards_code(self):
  22. raise NotImplementedError
  23. def add_forwards(self, forwards):
  24. if self.prepend_forwards:
  25. forwards.insert(0, self.forwards_code())
  26. else:
  27. forwards.append(self.forwards_code())
  28. def add_backwards(self, backwards):
  29. if self.prepend_backwards:
  30. backwards.insert(0, self.backwards_code())
  31. else:
  32. backwards.append(self.backwards_code())
  33. def console_line(self):
  34. "Returns the string to print on the console, e.g. ' + Added field foo'"
  35. raise NotImplementedError
  36. @classmethod
  37. def triples_to_defs(cls, fields):
  38. # Turn the (class, args, kwargs) format into a string
  39. for field, triple in fields.items():
  40. fields[field] = cls.triple_to_def(triple)
  41. return fields
  42. @classmethod
  43. def triple_to_def(cls, triple):
  44. "Turns a single triple into a definition."
  45. return "self.gf(%r)(%s)" % (
  46. triple[0], # Field full path
  47. ", ".join(triple[1] + ["%s=%s" % (kwd, val) for kwd, val in triple[2].items()]), # args and kwds
  48. )
  49. class AddModel(Action):
  50. """
  51. Addition of a model. Takes the Model subclass that is being created.
  52. """
  53. FORWARDS_TEMPLATE = '''
  54. # Adding model '%(model_name)s'
  55. db.create_table(%(table_name)r, (
  56. %(field_defs)s
  57. ))
  58. db.send_create_signal(%(app_label)r, [%(model_name)r])'''[1:] + "\n"
  59. BACKWARDS_TEMPLATE = '''
  60. # Deleting model '%(model_name)s'
  61. db.delete_table(%(table_name)r)'''[1:] + "\n"
  62. def __init__(self, model, model_def):
  63. self.model = model
  64. self.model_def = model_def
  65. def console_line(self):
  66. "Returns the string to print on the console, e.g. ' + Added field foo'"
  67. return " + Added model %s.%s" % (
  68. self.model._meta.app_label,
  69. self.model._meta.object_name,
  70. )
  71. def forwards_code(self):
  72. "Produces the code snippet that gets put into forwards()"
  73. field_defs = ",\n ".join([
  74. "(%r, %s)" % (name, defn) for name, defn
  75. in self.triples_to_defs(self.model_def).items()
  76. ]) + ","
  77. return self.FORWARDS_TEMPLATE % {
  78. "model_name": self.model._meta.object_name,
  79. "table_name": self.model._meta.db_table,
  80. "app_label": self.model._meta.app_label,
  81. "field_defs": field_defs,
  82. }
  83. def backwards_code(self):
  84. "Produces the code snippet that gets put into backwards()"
  85. return self.BACKWARDS_TEMPLATE % {
  86. "model_name": self.model._meta.object_name,
  87. "table_name": self.model._meta.db_table,
  88. }
  89. class DeleteModel(AddModel):
  90. """
  91. Deletion of a model. Takes the Model subclass that is being created.
  92. """
  93. def console_line(self):
  94. "Returns the string to print on the console, e.g. ' + Added field foo'"
  95. return " - Deleted model %s.%s" % (
  96. self.model._meta.app_label,
  97. self.model._meta.object_name,
  98. )
  99. def forwards_code(self):
  100. return AddModel.backwards_code(self)
  101. def backwards_code(self):
  102. return AddModel.forwards_code(self)
  103. class _NullIssuesField(object):
  104. """
  105. A field that might need to ask a question about rogue NULL values.
  106. """
  107. allow_third_null_option = False
  108. irreversible = False
  109. IRREVERSIBLE_TEMPLATE = '''
  110. # User chose to not deal with backwards NULL issues for '%(model_name)s.%(field_name)s'
  111. raise RuntimeError("Cannot reverse this migration. '%(model_name)s.%(field_name)s' and its values cannot be restored.")'''
  112. def deal_with_not_null_no_default(self, field, field_def):
  113. # If it's a CharField or TextField that's blank, skip this step.
  114. if isinstance(field, (CharField, TextField)) and field.blank:
  115. field_def[2]['default'] = repr("")
  116. return
  117. # Oh dear. Ask them what to do.
  118. print " ? The field '%s.%s' does not have a default specified, yet is NOT NULL." % (
  119. self.model._meta.object_name,
  120. field.name,
  121. )
  122. print " ? Since you are %s, you MUST specify a default" % self.null_reason
  123. print " ? value to use for existing rows. Would you like to:"
  124. print " ? 1. Quit now, and add a default to the field in models.py"
  125. print " ? 2. Specify a one-off value to use for existing columns now"
  126. if self.allow_third_null_option:
  127. print " ? 3. Disable the backwards migration by raising an exception."
  128. while True:
  129. choice = raw_input(" ? Please select a choice: ")
  130. if choice == "1":
  131. sys.exit(1)
  132. elif choice == "2":
  133. break
  134. elif choice == "3" and self.allow_third_null_option:
  135. break
  136. else:
  137. print " ! Invalid choice."
  138. if choice == "2":
  139. self.add_one_time_default(field, field_def)
  140. elif choice == "3":
  141. self.irreversible = True
  142. def add_one_time_default(self, field, field_def):
  143. # OK, they want to pick their own one-time default. Who are we to refuse?
  144. print " ? Please enter Python code for your one-off default value."
  145. print " ? The datetime module is available, so you can do e.g. datetime.date.today()"
  146. while True:
  147. code = raw_input(" >>> ")
  148. if not code:
  149. print " ! Please enter some code, or 'exit' (with no quotes) to exit."
  150. elif code == "exit":
  151. sys.exit(1)
  152. else:
  153. try:
  154. result = eval(code, {}, {"datetime": datetime_utils})
  155. except (SyntaxError, NameError), e:
  156. print " ! Invalid input: %s" % e
  157. else:
  158. break
  159. # Right, add the default in.
  160. field_def[2]['default'] = value_clean(result)
  161. def irreversable_code(self, field):
  162. return self.IRREVERSIBLE_TEMPLATE % {
  163. "model_name": self.model._meta.object_name,
  164. "table_name": self.model._meta.db_table,
  165. "field_name": field.name,
  166. "field_column": field.column,
  167. }
  168. class AddField(Action, _NullIssuesField):
  169. """
  170. Adds a field to a model. Takes a Model class and the field name.
  171. """
  172. null_reason = "adding this field"
  173. FORWARDS_TEMPLATE = '''
  174. # Adding field '%(model_name)s.%(field_name)s'
  175. db.add_column(%(table_name)r, %(field_name)r,
  176. %(field_def)s,
  177. keep_default=False)'''[1:] + "\n"
  178. BACKWARDS_TEMPLATE = '''
  179. # Deleting field '%(model_name)s.%(field_name)s'
  180. db.delete_column(%(table_name)r, %(field_column)r)'''[1:] + "\n"
  181. def __init__(self, model, field, field_def):
  182. self.model = model
  183. self.field = field
  184. self.field_def = field_def
  185. # See if they've made a NOT NULL column but also have no default (far too common)
  186. is_null = self.field.null
  187. default = (self.field.default is not None) and (self.field.default is not NOT_PROVIDED)
  188. if not is_null and not default:
  189. self.deal_with_not_null_no_default(self.field, self.field_def)
  190. def console_line(self):
  191. "Returns the string to print on the console, e.g. ' + Added field foo'"
  192. return " + Added field %s on %s.%s" % (
  193. self.field.name,
  194. self.model._meta.app_label,
  195. self.model._meta.object_name,
  196. )
  197. def forwards_code(self):
  198. return self.FORWARDS_TEMPLATE % {
  199. "model_name": self.model._meta.object_name,
  200. "table_name": self.model._meta.db_table,
  201. "field_name": self.field.name,
  202. "field_column": self.field.column,
  203. "field_def": self.triple_to_def(self.field_def),
  204. }
  205. def backwards_code(self):
  206. return self.BACKWARDS_TEMPLATE % {
  207. "model_name": self.model._meta.object_name,
  208. "table_name": self.model._meta.db_table,
  209. "field_name": self.field.name,
  210. "field_column": self.field.column,
  211. }
  212. class DeleteField(AddField):
  213. """
  214. Removes a field from a model. Takes a Model class and the field name.
  215. """
  216. null_reason = "removing this field"
  217. allow_third_null_option = True
  218. def console_line(self):
  219. "Returns the string to print on the console, e.g. ' + Added field foo'"
  220. return " - Deleted field %s on %s.%s" % (
  221. self.field.name,
  222. self.model._meta.app_label,
  223. self.model._meta.object_name,
  224. )
  225. def forwards_code(self):
  226. return AddField.backwards_code(self)
  227. def backwards_code(self):
  228. if not self.irreversible:
  229. return AddField.forwards_code(self)
  230. else:
  231. return self.irreversable_code(self.field)
  232. class ChangeField(Action, _NullIssuesField):
  233. """
  234. Changes a field's type/options on a model.
  235. """
  236. null_reason = "making this field non-nullable"
  237. FORWARDS_TEMPLATE = BACKWARDS_TEMPLATE = '''
  238. # Changing field '%(model_name)s.%(field_name)s'
  239. db.alter_column(%(table_name)r, %(field_column)r, %(field_def)s)'''
  240. RENAME_TEMPLATE = '''
  241. # Renaming column for '%(model_name)s.%(field_name)s' to match new field type.
  242. db.rename_column(%(table_name)r, %(old_column)r, %(new_column)r)'''
  243. def __init__(self, model, old_field, new_field, old_def, new_def):
  244. self.model = model
  245. self.old_field = old_field
  246. self.new_field = new_field
  247. self.old_def = old_def
  248. self.new_def = new_def
  249. # See if they've changed a not-null field to be null
  250. new_default = (self.new_field.default is not None) and (self.new_field.default is not NOT_PROVIDED)
  251. old_default = (self.old_field.default is not None) and (self.old_field.default is not NOT_PROVIDED)
  252. if self.old_field.null and not self.new_field.null and not new_default:
  253. self.deal_with_not_null_no_default(self.new_field, self.new_def)
  254. if not self.old_field.null and self.new_field.null and not old_default:
  255. self.null_reason = "making this field nullable"
  256. self.allow_third_null_option = True
  257. self.deal_with_not_null_no_default(self.old_field, self.old_def)
  258. def console_line(self):
  259. "Returns the string to print on the console, e.g. ' + Added field foo'"
  260. return " ~ Changed field %s on %s.%s" % (
  261. self.new_field.name,
  262. self.model._meta.app_label,
  263. self.model._meta.object_name,
  264. )
  265. def _code(self, old_field, new_field, new_def):
  266. output = ""
  267. if self.old_field.column != self.new_field.column:
  268. output += self.RENAME_TEMPLATE % {
  269. "model_name": self.model._meta.object_name,
  270. "table_name": self.model._meta.db_table,
  271. "field_name": new_field.name,
  272. "old_column": old_field.column,
  273. "new_column": new_field.column,
  274. }
  275. output += self.FORWARDS_TEMPLATE % {
  276. "model_name": self.model._meta.object_name,
  277. "table_name": self.model._meta.db_table,
  278. "field_name": new_field.name,
  279. "field_column": new_field.column,
  280. "field_def": self.triple_to_def(new_def),
  281. }
  282. return output
  283. def forwards_code(self):
  284. return self._code(self.old_field, self.new_field, self.new_def)
  285. def backwards_code(self):
  286. if not self.irreversible:
  287. return self._code(self.new_field, self.old_field, self.old_def)
  288. else:
  289. return self.irreversable_code(self.old_field)
  290. class AddUnique(Action):
  291. """
  292. Adds a unique constraint to a model. Takes a Model class and the field names.
  293. """
  294. FORWARDS_TEMPLATE = '''
  295. # Adding unique constraint on '%(model_name)s', fields %(field_names)s
  296. db.create_unique(%(table_name)r, %(fields)r)'''[1:] + "\n"
  297. BACKWARDS_TEMPLATE = '''
  298. # Removing unique constraint on '%(model_name)s', fields %(field_names)s
  299. db.delete_unique(%(table_name)r, %(fields)r)'''[1:] + "\n"
  300. prepend_backwards = True
  301. def __init__(self, model, fields):
  302. self.model = model
  303. self.fields = fields
  304. def console_line(self):
  305. "Returns the string to print on the console, e.g. ' + Added field foo'"
  306. return " + Added unique constraint for %s on %s.%s" % (
  307. [x.name for x in self.fields],
  308. self.model._meta.app_label,
  309. self.model._meta.object_name,
  310. )
  311. def forwards_code(self):
  312. return self.FORWARDS_TEMPLATE % {
  313. "model_name": self.model._meta.object_name,
  314. "table_name": self.model._meta.db_table,
  315. "fields": [field.column for field in self.fields],
  316. "field_names": [field.name for field in self.fields],
  317. }
  318. def backwards_code(self):
  319. return self.BACKWARDS_TEMPLATE % {
  320. "model_name": self.model._meta.object_name,
  321. "table_name": self.model._meta.db_table,
  322. "fields": [field.column for field in self.fields],
  323. "field_names": [field.name for field in self.fields],
  324. }
  325. class DeleteUnique(AddUnique):
  326. """
  327. Removes a unique constraint from a model. Takes a Model class and the field names.
  328. """
  329. prepend_forwards = True
  330. prepend_backwards = False
  331. def console_line(self):
  332. "Returns the string to print on the console, e.g. ' + Added field foo'"
  333. return " - Deleted unique constraint for %s on %s.%s" % (
  334. [x.name for x in self.fields],
  335. self.model._meta.app_label,
  336. self.model._meta.object_name,
  337. )
  338. def forwards_code(self):
  339. return AddUnique.backwards_code(self)
  340. def backwards_code(self):
  341. return AddUnique.forwards_code(self)
  342. class AddIndex(AddUnique):
  343. """
  344. Adds an index to a model field[s]. Takes a Model class and the field names.
  345. """
  346. FORWARDS_TEMPLATE = '''
  347. # Adding index on '%(model_name)s', fields %(field_names)s
  348. db.create_index(%(table_name)r, %(fields)r)'''[1:] + "\n"
  349. BACKWARDS_TEMPLATE = '''
  350. # Removing index on '%(model_name)s', fields %(field_names)s
  351. db.delete_index(%(table_name)r, %(fields)r)'''[1:] + "\n"
  352. def console_line(self):
  353. "Returns the string to print on the console, e.g. ' + Added field foo'"
  354. return " + Added index for %s on %s.%s" % (
  355. [x.name for x in self.fields],
  356. self.model._meta.app_label,
  357. self.model._meta.object_name,
  358. )
  359. class DeleteIndex(AddIndex):
  360. """
  361. Deletes an index off a model field[s]. Takes a Model class and the field names.
  362. """
  363. def console_line(self):
  364. "Returns the string to print on the console, e.g. ' + Added field foo'"
  365. return " + Deleted index for %s on %s.%s" % (
  366. [x.name for x in self.fields],
  367. self.model._meta.app_label,
  368. self.model._meta.object_name,
  369. )
  370. def forwards_code(self):
  371. return AddIndex.backwards_code(self)
  372. def backwards_code(self):
  373. return AddIndex.forwards_code(self)
  374. class AddM2M(Action):
  375. """
  376. Adds a unique constraint to a model. Takes a Model class and the field names.
  377. """
  378. FORWARDS_TEMPLATE = '''
  379. # Adding M2M table for field %(field_name)s on '%(model_name)s'
  380. db.create_table(%(table_name)r, (
  381. ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
  382. (%(left_field)r, models.ForeignKey(orm[%(left_model_key)r], null=False)),
  383. (%(right_field)r, models.ForeignKey(orm[%(right_model_key)r], null=False))
  384. ))
  385. db.create_unique(%(table_name)r, [%(left_column)r, %(right_column)r])'''[1:] + "\n"
  386. BACKWARDS_TEMPLATE = '''
  387. # Removing M2M table for field %(field_name)s on '%(model_name)s'
  388. db.delete_table('%(table_name)s')'''[1:] + "\n"
  389. def __init__(self, model, field):
  390. self.model = model
  391. self.field = field
  392. def console_line(self):
  393. "Returns the string to print on the console, e.g. ' + Added field foo'"
  394. return " + Added M2M table for %s on %s.%s" % (
  395. self.field.name,
  396. self.model._meta.app_label,
  397. self.model._meta.object_name,
  398. )
  399. def forwards_code(self):
  400. return self.FORWARDS_TEMPLATE % {
  401. "model_name": self.model._meta.object_name,
  402. "field_name": self.field.name,
  403. "table_name": self.field.m2m_db_table(),
  404. "left_field": self.field.m2m_column_name()[:-3], # Remove the _id part
  405. "left_column": self.field.m2m_column_name(),
  406. "left_model_key": model_key(self.model),
  407. "right_field": self.field.m2m_reverse_name()[:-3], # Remove the _id part
  408. "right_column": self.field.m2m_reverse_name(),
  409. "right_model_key": model_key(self.field.rel.to),
  410. }
  411. def backwards_code(self):
  412. return self.BACKWARDS_TEMPLATE % {
  413. "model_name": self.model._meta.object_name,
  414. "field_name": self.field.name,
  415. "table_name": self.field.m2m_db_table(),
  416. }
  417. class DeleteM2M(AddM2M):
  418. """
  419. Adds a unique constraint to a model. Takes a Model class and the field names.
  420. """
  421. def console_line(self):
  422. "Returns the string to print on the console, e.g. ' + Added field foo'"
  423. return " - Deleted M2M table for %s on %s.%s" % (
  424. self.field.name,
  425. self.model._meta.app_label,
  426. self.model._meta.object_name,
  427. )
  428. def forwards_code(self):
  429. return AddM2M.backwards_code(self)
  430. def backwards_code(self):
  431. return AddM2M.forwards_code(self)