console.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. """
  2. Console utility to help manage F.A.B's apps
  3. use:
  4. $ fabmanager --help
  5. """
  6. from io import BytesIO
  7. import os
  8. import shutil
  9. import sys
  10. from zipfile import ZipFile
  11. import click
  12. from . import const as c
  13. try:
  14. # For Python 3.0 and later
  15. from urllib.request import urlopen
  16. except ImportError:
  17. # Fall back to Python 2's urllib2
  18. from urllib2 import urlopen
  19. click.echo(
  20. click.style(
  21. "fabmanager is going to be deprecated in 2.2.X, you can use "
  22. "the same commands on the improved 'flask fab <command>'",
  23. fg="red",
  24. )
  25. )
  26. SQLA_REPO_URL = (
  27. "https://github.com/dpgaspar/Flask-AppBuilder-Skeleton/archive/master.zip"
  28. )
  29. MONGOENGIE_REPO_URL = (
  30. "https://github.com/dpgaspar/Flask-AppBuilder-Skeleton-me/archive/master.zip"
  31. )
  32. ADDON_REPO_URL = (
  33. "https://github.com/dpgaspar/Flask-AppBuilder-Skeleton-AddOn/archive/master.zip"
  34. )
  35. def import_application(app_package, appbuilder):
  36. sys.path.append(os.getcwd())
  37. try:
  38. _app = __import__(app_package)
  39. except Exception as e:
  40. click.echo(
  41. click.style(
  42. "Was unable to import {0} Error: {1}".format(app_package, e), fg="red"
  43. )
  44. )
  45. exit(3)
  46. if hasattr(_app, appbuilder):
  47. return getattr(_app, appbuilder)
  48. else:
  49. click.echo(
  50. click.style(
  51. "There is no appbuilder var on your package, "
  52. "you can use appbuilder parameter to config",
  53. fg="red",
  54. )
  55. )
  56. exit(3)
  57. def echo_header(title):
  58. click.echo(click.style(title, fg="green"))
  59. click.echo(click.style("-" * len(title), fg="green"))
  60. @click.group()
  61. def cli_app():
  62. """
  63. This is a set of commands to ease the creation and maintenance
  64. of your flask-appbuilder applications.
  65. All commands that import your app will assume by default that
  66. you're running on your projects directory just before the app directory.
  67. They will also assume that __init__.py initializes AppBuilder
  68. like this (using a var named appbuilder) just like the skeleton app::
  69. appbuilder = AppBuilder(......)
  70. If you're using different namings use app and appbuilder parameters.
  71. """
  72. pass
  73. @cli_app.command("reset-password")
  74. @click.option("--app", default="app", help="Your application init directory (package)")
  75. @click.option("--appbuilder", default="appbuilder", help="your AppBuilder object")
  76. @click.option(
  77. "--username",
  78. default="admin",
  79. prompt="The username",
  80. help="Resets the password for a particular user.",
  81. )
  82. @click.password_option()
  83. def reset_password(app, appbuilder, username, password):
  84. """
  85. Resets a user's password
  86. """
  87. _appbuilder = import_application(app, appbuilder)
  88. user = _appbuilder.sm.find_user(username=username)
  89. if not user:
  90. click.echo("User {0} not found.".format(username))
  91. else:
  92. _appbuilder.sm.reset_password(user.id, password)
  93. click.echo(click.style("User {0} reseted.".format(username), fg="green"))
  94. @cli_app.command("create-admin")
  95. @click.option("--app", default="app", help="Your application init directory (package)")
  96. @click.option("--appbuilder", default="appbuilder", help="your AppBuilder object")
  97. @click.option("--username", default="admin", prompt="Username")
  98. @click.option("--firstname", default="admin", prompt="User first name")
  99. @click.option("--lastname", default="user", prompt="User last name")
  100. @click.option("--email", default="admin@fab.org", prompt="Email")
  101. @click.password_option()
  102. def create_admin(app, appbuilder, username, firstname, lastname, email, password):
  103. """
  104. Creates an admin user
  105. """
  106. auth_type = {
  107. c.AUTH_DB: "Database Authentications",
  108. c.AUTH_OID: "OpenID Authentication",
  109. c.AUTH_LDAP: "LDAP Authentication",
  110. c.AUTH_REMOTE_USER: "WebServer REMOTE_USER Authentication",
  111. c.AUTH_OAUTH: "OAuth Authentication",
  112. }
  113. _appbuilder = import_application(app, appbuilder)
  114. click.echo(
  115. click.style(
  116. "Recognized {0}.".format(
  117. auth_type.get(_appbuilder.sm.auth_type, "No Auth method")
  118. ),
  119. fg="green",
  120. )
  121. )
  122. role_admin = _appbuilder.sm.find_role(_appbuilder.sm.auth_role_admin)
  123. user = _appbuilder.sm.add_user(
  124. username, firstname, lastname, email, role_admin, password
  125. )
  126. if user:
  127. click.echo(click.style("Admin User {0} created.".format(username), fg="green"))
  128. else:
  129. click.echo(click.style("No user created an error occured", fg="red"))
  130. @cli_app.command("create-user")
  131. @click.option("--app", default="app", help="Your application init directory (package)")
  132. @click.option("--appbuilder", default="appbuilder", help="your AppBuilder object")
  133. @click.option("--role", default="Public", prompt="Role")
  134. @click.option("--username", prompt="Username")
  135. @click.option("--firstname", prompt="User first name")
  136. @click.option("--lastname", prompt="User last name")
  137. @click.option("--email", prompt="Email")
  138. @click.password_option()
  139. def create_user(app, appbuilder, role, username, firstname, lastname, email, password):
  140. """
  141. Create a user
  142. """
  143. _appbuilder = import_application(app, appbuilder)
  144. role_object = _appbuilder.sm.find_role(role)
  145. user = _appbuilder.sm.add_user(
  146. username, firstname, lastname, email, role_object, password
  147. )
  148. if user:
  149. click.echo(click.style("User {0} created.".format(username), fg="green"))
  150. else:
  151. click.echo(click.style("Error! No user created", fg="red"))
  152. @cli_app.command("run")
  153. @click.option("--app", default="app", help="Your application init directory (package)")
  154. @click.option("--appbuilder", default="appbuilder", help="your AppBuilder object")
  155. @click.option("--host", default="0.0.0.0")
  156. @click.option("--port", default=8080)
  157. @click.option("--debug", default=True)
  158. def run(app, appbuilder, host, port, debug):
  159. """
  160. Runs Flask dev web server.
  161. """
  162. _appbuilder = import_application(app, appbuilder)
  163. _appbuilder.get_app.run(host=host, port=port, debug=debug)
  164. @cli_app.command("create-db")
  165. @click.option("--app", default="app", help="Your application init directory (package)")
  166. @click.option("--appbuilder", default="appbuilder", help="your AppBuilder object")
  167. def create_db(app, appbuilder):
  168. """
  169. Create all your database objects (SQLAlchemy specific).
  170. """
  171. from flask_appbuilder.models.sqla import Base
  172. _appbuilder = import_application(app, appbuilder)
  173. engine = _appbuilder.get_session.get_bind(mapper=None, clause=None)
  174. Base.metadata.create_all(engine)
  175. click.echo(click.style("DB objects created", fg="green"))
  176. @cli_app.command("version")
  177. @click.option("--app", default="app", help="Your application init directory (package)")
  178. @click.option("--appbuilder", default="appbuilder", help="your AppBuilder object")
  179. def version(app, appbuilder):
  180. """
  181. Flask-AppBuilder package version
  182. """
  183. _appbuilder = import_application(app, appbuilder)
  184. click.echo(
  185. click.style(
  186. "F.A.B Version: {0}.".format(_appbuilder.version), bg="blue", fg="white"
  187. )
  188. )
  189. @cli_app.command("security-cleanup")
  190. @click.option("--app", default="app", help="Your application init directory (package)")
  191. @click.option("--appbuilder", default="appbuilder", help="your AppBuilder object")
  192. def security_cleanup(app, appbuilder):
  193. """
  194. Cleanup unused permissions from views and roles.
  195. """
  196. _appbuilder = import_application(app, appbuilder)
  197. _appbuilder.security_cleanup()
  198. click.echo(click.style("Finished security cleanup", fg="green"))
  199. @cli_app.command("list-views")
  200. @click.option("--app", default="app", help="Your application init directory (package)")
  201. @click.option("--appbuilder", default="appbuilder", help="your AppBuilder object")
  202. def list_views(app, appbuilder):
  203. """
  204. List all registered views
  205. """
  206. _appbuilder = import_application(app, appbuilder)
  207. echo_header("List of registered views")
  208. for view in _appbuilder.baseviews:
  209. click.echo(
  210. "View:{0} | Route:{1} | Perms:{2}".format(
  211. view.__class__.__name__, view.route_base, view.base_permissions
  212. )
  213. )
  214. @cli_app.command("list-users")
  215. @click.option("--app", default="app", help="Your application init directory (package)")
  216. @click.option("--appbuilder", default="appbuilder", help="your AppBuilder object")
  217. def list_users(app, appbuilder):
  218. """
  219. List all users on the database
  220. """
  221. _appbuilder = import_application(app, appbuilder)
  222. echo_header("List of users")
  223. for user in _appbuilder.sm.get_all_users():
  224. click.echo(
  225. "username:{0} | email:{1} | role:{2}".format(
  226. user.username, user.email, user.roles
  227. )
  228. )
  229. @cli_app.command("babel-extract")
  230. @click.option("--config", default="./babel/babel.cfg")
  231. @click.option("--input", default=".")
  232. @click.option("--output", default="./babel/messages.pot")
  233. @click.option("--target", default="app/translations")
  234. @click.option(
  235. "--keywords", "-k", multiple=True, default=["lazy_gettext", "gettext", "_", "__"]
  236. )
  237. def babel_extract(config, input, output, target, keywords):
  238. """
  239. Babel, Extracts and updates all messages marked for translation
  240. """
  241. click.echo(
  242. click.style(
  243. "Starting Extractions config:{0} input:{1} output:{2} keywords:{3}".format(
  244. config, input, output, keywords
  245. ),
  246. fg="green",
  247. )
  248. )
  249. keywords = " -k ".join(keywords)
  250. os.popen(
  251. "pybabel extract -F {0} -k {1} -o {2} {3}".format(
  252. config, keywords, output, input
  253. )
  254. )
  255. click.echo(click.style("Starting Update target:{0}".format(target), fg="green"))
  256. os.popen("pybabel update -N -i {0} -d {1}".format(output, target))
  257. click.echo(click.style("Finish, you can start your translations", fg="green"))
  258. @cli_app.command("babel-compile")
  259. @click.option(
  260. "--target",
  261. default="app/translations",
  262. help="The target directory where translations reside",
  263. )
  264. def babel_compile(target):
  265. """
  266. Babel, Compiles all translations
  267. """
  268. click.echo(click.style("Starting Compile target:{0}".format(target), fg="green"))
  269. os.popen("pybabel compile -f -d {0}".format(target))
  270. @cli_app.command("create-app")
  271. @click.option(
  272. "--name",
  273. prompt="Your new app name",
  274. help="Your application name, directory will have this name",
  275. )
  276. @click.option(
  277. "--engine",
  278. prompt="Your engine type, SQLAlchemy or MongoEngine",
  279. type=click.Choice(["SQLAlchemy", "MongoEngine"]),
  280. default="SQLAlchemy",
  281. help="Write your engine type",
  282. )
  283. def create_app(name, engine):
  284. """
  285. Create a Skeleton application (needs internet connection to github)
  286. """
  287. try:
  288. if engine.lower() == "sqlalchemy":
  289. url = urlopen(SQLA_REPO_URL)
  290. dirname = "Flask-AppBuilder-Skeleton-master"
  291. elif engine.lower() == "mongoengine":
  292. url = urlopen(MONGOENGIE_REPO_URL)
  293. dirname = "Flask-AppBuilder-Skeleton-me-master"
  294. zipfile = ZipFile(BytesIO(url.read()))
  295. zipfile.extractall()
  296. os.rename(dirname, name)
  297. click.echo(click.style("Downloaded the skeleton app, good coding!", fg="green"))
  298. return True
  299. except Exception as e:
  300. click.echo(click.style("Something went wrong {0}".format(e), fg="red"))
  301. if engine.lower() == "sqlalchemy":
  302. click.echo(
  303. click.style(
  304. "Try downloading from {0}".format(SQLA_REPO_URL), fg="green"
  305. )
  306. )
  307. elif engine.lower() == "mongoengine":
  308. click.echo(
  309. click.style(
  310. "Try downloading from {0}".format(MONGOENGIE_REPO_URL), fg="green"
  311. )
  312. )
  313. return False
  314. @cli_app.command("create-addon")
  315. @click.option(
  316. "--name",
  317. prompt="Your new addon name",
  318. help="Your addon name will be prefixed by fab_addon_, directory will have this name",
  319. )
  320. def create_addon(name):
  321. """
  322. Create a Skeleton AddOn (needs internet connection to github)
  323. """
  324. try:
  325. full_name = "fab_addon_" + name
  326. dirname = "Flask-AppBuilder-Skeleton-AddOn-master"
  327. url = urlopen(ADDON_REPO_URL)
  328. zipfile = ZipFile(BytesIO(url.read()))
  329. zipfile.extractall()
  330. os.rename(dirname, full_name)
  331. addon_path = os.path.join(full_name, full_name)
  332. os.rename(os.path.join(full_name, "fab_addon"), addon_path)
  333. f = open(os.path.join(full_name, "config.py"), "w")
  334. f.write("ADDON_NAME='" + name + "'\n")
  335. f.write("FULL_ADDON_NAME='fab_addon_' + ADDON_NAME\n")
  336. f.close()
  337. click.echo(
  338. click.style("Downloaded the skeleton addon, good coding!", fg="green")
  339. )
  340. return True
  341. except Exception as e:
  342. click.echo(click.style("Something went wrong {0}".format(e), fg="red"))
  343. return False
  344. @cli_app.command("collect-static")
  345. @click.option(
  346. "--static_folder", default="app/static", help="Your projects static folder"
  347. )
  348. def collect_static(static_folder):
  349. """
  350. Copies flask-appbuilder static files to your projects static folder
  351. """
  352. appbuilder_static_path = os.path.join(
  353. os.path.dirname(os.path.abspath(__file__)), "static/appbuilder"
  354. )
  355. app_static_path = os.path.join(os.getcwd(), static_folder)
  356. if not os.path.isdir(app_static_path):
  357. click.echo(
  358. click.style(
  359. "Static folder does not exist creating: %s" % app_static_path,
  360. fg="green",
  361. )
  362. )
  363. os.makedirs(app_static_path)
  364. try:
  365. shutil.copytree(
  366. appbuilder_static_path, os.path.join(app_static_path, "appbuilder")
  367. )
  368. except Exception:
  369. click.echo(
  370. click.style(
  371. "Appbuilder static folder already exists on your project", fg="red"
  372. )
  373. )
  374. def cli():
  375. cli_app()
  376. if __name__ == "__main__":
  377. cli_app()