questioner.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. from __future__ import unicode_literals
  2. import importlib
  3. import os
  4. import sys
  5. from django.apps import apps
  6. from django.utils import datetime_safe, six
  7. from django.utils.six.moves import input
  8. from .loader import MIGRATIONS_MODULE_NAME
  9. class MigrationQuestioner(object):
  10. """
  11. Gives the autodetector responses to questions it might have.
  12. This base class has a built-in noninteractive mode, but the
  13. interactive subclass is what the command-line arguments will use.
  14. """
  15. def __init__(self, defaults=None, specified_apps=None, dry_run=None):
  16. self.defaults = defaults or {}
  17. self.specified_apps = specified_apps or set()
  18. self.dry_run = dry_run
  19. def ask_initial(self, app_label):
  20. "Should we create an initial migration for the app?"
  21. # If it was specified on the command line, definitely true
  22. if app_label in self.specified_apps:
  23. return True
  24. # Otherwise, we look to see if it has a migrations module
  25. # without any Python files in it, apart from __init__.py.
  26. # Apps from the new app template will have these; the python
  27. # file check will ensure we skip South ones.
  28. try:
  29. app_config = apps.get_app_config(app_label)
  30. except LookupError: # It's a fake app.
  31. return self.defaults.get("ask_initial", False)
  32. migrations_import_path = "%s.%s" % (app_config.name, MIGRATIONS_MODULE_NAME)
  33. try:
  34. migrations_module = importlib.import_module(migrations_import_path)
  35. except ImportError:
  36. return self.defaults.get("ask_initial", False)
  37. else:
  38. if hasattr(migrations_module, "__file__"):
  39. filenames = os.listdir(os.path.dirname(migrations_module.__file__))
  40. elif hasattr(migrations_module, "__path__"):
  41. if len(migrations_module.__path__) > 1:
  42. return False
  43. filenames = os.listdir(list(migrations_module.__path__)[0])
  44. return not any(x.endswith(".py") for x in filenames if x != "__init__.py")
  45. def ask_not_null_addition(self, field_name, model_name):
  46. "Adding a NOT NULL field to a model"
  47. # None means quit
  48. return None
  49. def ask_rename(self, model_name, old_name, new_name, field_instance):
  50. "Was this field really renamed?"
  51. return self.defaults.get("ask_rename", False)
  52. def ask_rename_model(self, old_model_state, new_model_state):
  53. "Was this model really renamed?"
  54. return self.defaults.get("ask_rename_model", False)
  55. def ask_merge(self, app_label):
  56. "Do you really want to merge these migrations?"
  57. return self.defaults.get("ask_merge", False)
  58. class InteractiveMigrationQuestioner(MigrationQuestioner):
  59. def _boolean_input(self, question, default=None):
  60. result = input("%s " % question)
  61. if not result and default is not None:
  62. return default
  63. while len(result) < 1 or result[0].lower() not in "yn":
  64. result = input("Please answer yes or no: ")
  65. return result[0].lower() == "y"
  66. def _choice_input(self, question, choices):
  67. print(question)
  68. for i, choice in enumerate(choices):
  69. print(" %s) %s" % (i + 1, choice))
  70. result = input("Select an option: ")
  71. while True:
  72. try:
  73. value = int(result)
  74. if 0 < value <= len(choices):
  75. return value
  76. except ValueError:
  77. pass
  78. result = input("Please select a valid option: ")
  79. def ask_not_null_addition(self, field_name, model_name):
  80. "Adding a NOT NULL field to a model"
  81. if not self.dry_run:
  82. choice = self._choice_input(
  83. "You are trying to add a non-nullable field '%s' to %s without a default;\n" % (field_name, model_name) +
  84. "we can't do that (the database needs something to populate existing rows).\n" +
  85. "Please select a fix:",
  86. [
  87. "Provide a one-off default now (will be set on all existing rows)",
  88. "Quit, and let me add a default in models.py",
  89. ]
  90. )
  91. if choice == 2:
  92. sys.exit(3)
  93. else:
  94. print("Please enter the default value now, as valid Python")
  95. print("The datetime module is available, so you can do e.g. datetime.date.today()")
  96. while True:
  97. if six.PY3:
  98. # Six does not correctly abstract over the fact that
  99. # py3 input returns a unicode string, while py2 raw_input
  100. # returns a bytestring.
  101. code = input(">>> ")
  102. else:
  103. code = input(">>> ").decode(sys.stdin.encoding)
  104. if not code:
  105. print("Please enter some code, or 'exit' (with no quotes) to exit.")
  106. elif code == "exit":
  107. sys.exit(1)
  108. else:
  109. try:
  110. return eval(code, {}, {"datetime": datetime_safe})
  111. except (SyntaxError, NameError) as e:
  112. print("Invalid input: %s" % e)
  113. return None
  114. def ask_rename(self, model_name, old_name, new_name, field_instance):
  115. "Was this field really renamed?"
  116. return self._boolean_input("Did you rename %s.%s to %s.%s (a %s)? [y/N]" % (model_name, old_name, model_name, new_name, field_instance.__class__.__name__), False)
  117. def ask_rename_model(self, old_model_state, new_model_state):
  118. "Was this model really renamed?"
  119. return self._boolean_input("Did you rename the %s.%s model to %s? [y/N]" % (old_model_state.app_label, old_model_state.name, new_model_state.name), False)
  120. def ask_merge(self, app_label):
  121. return self._boolean_input(
  122. "\nMerging will only work if the operations printed above do not conflict\n" +
  123. "with each other (working on different fields or models)\n" +
  124. "Do you want to merge these migration branches? [y/N]",
  125. False,
  126. )