__init__.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. # -*- coding: utf-8 -*-
  2. """
  3. flaskbb.plugins
  4. ~~~~~~~~~~~~~~~
  5. This module contains the Plugin class used by all Plugins for FlaskBB.
  6. :copyright: (c) 2014 by the FlaskBB Team.
  7. :license: BSD, see LICENSE for more details.
  8. """
  9. import contextlib
  10. import copy
  11. import os
  12. from flask import current_app
  13. from flask import g
  14. from flask_migrate import upgrade, downgrade, migrate
  15. from flask_plugins import Plugin
  16. from flaskbb.extensions import db, migrate as mig
  17. from flaskbb.management.models import SettingsGroup
  18. @contextlib.contextmanager # TODO: Add tests
  19. def plugin_name_migrate(name):
  20. """Migrations in this with block will only apply to models from the named plugin"""
  21. g.plugin_name = name
  22. yield
  23. del g.plugin_name
  24. def db_for_plugin(plugin_name,sqla_instance=None):
  25. """Labels models as belonging to this plugin.
  26. sqla_instance is a valid Flask-SQLAlchemy instance, if None, then the default db is used
  27. Usage:
  28. from flaskbb.plugins import db_for_plugin
  29. db=db_for_plugin(__name__)
  30. mytable = db.Table(...)
  31. class MyModel(db.Model):
  32. ...
  33. """
  34. sqla_instance=sqla_instance or db
  35. new_db = copy.copy(sqla_instance)
  36. def Table(*args, **kwargs):
  37. new_table = sqla_instance.Table(*args, **kwargs)
  38. new_table._plugin = plugin_name
  39. return new_table
  40. new_db.Table = Table
  41. return new_db
  42. @mig.configure
  43. def config_migrate(config):
  44. """Configuration callback for plugins environment"""
  45. plugins = current_app.extensions['plugin_manager'].all_plugins.values()
  46. migration_dirs = [p.get_migration_version_dir() for p in plugins]
  47. if config.get_main_option('version_table') == 'plugins':
  48. config.set_main_option('version_locations', ' '.join(migration_dirs))
  49. return config
  50. class FlaskBBPlugin(Plugin):
  51. #: This is the :class:`SettingsGroup` key - if your the plugin needs to
  52. #: install additional things you must set it, else it won't install
  53. #: anything.
  54. settings_key = None
  55. def resource_filename(self, *names):
  56. "Returns an absolute filename for a plugin resource."
  57. if len(names)==1 and '/' in names[0]:
  58. names=names[0].split('/')
  59. fname= os.path.join(self.path, *names)
  60. if ' ' in fname and not '"' in fname and not "'" in fname:
  61. fname='"%s"'%fname
  62. return fname
  63. def get_migration_version_dir(self):
  64. """Returns path to directory containing the migration version files"""
  65. return self.resource_filename('migration_versions')
  66. def upgrade_database(self, target='head'):
  67. """Updates database to a later version of plugin models.
  68. Default behaviour is to upgrade to latest version"""
  69. plugin_dir = current_app.extensions['plugin_manager'].plugin_folder
  70. upgrade(directory=os.path.join(plugin_dir, '_migration_environment'), revision=self.settings_key + '@' + target)
  71. def downgrade_database(self, target='base'):
  72. """Rolls back database to a previous version of plugin models.
  73. Default behaviour is to remove models completely"""
  74. plugin_dir = current_app.extensions['plugin_manager'].plugin_folder
  75. downgrade(directory=os.path.join(plugin_dir, '_migration_environment'), revision=self.settings_key + '@' + target)
  76. def migrate(self):
  77. """Generates new migration files for a plugin and stores them in
  78. flaskbb/plugins/<plugin_folder>/migration_versions"""
  79. with plugin_name_migrate(self.__module__):
  80. plugin_dir = current_app.extensions['plugin_manager'].plugin_folder
  81. try:
  82. migrate(directory=os.path.join(plugin_dir, '_migration_environment'),
  83. head=self.settings_key)
  84. except Exception as e: # presumably this is the initial migration?
  85. migrate(directory=os.path.join(plugin_dir, '_migration_environment'),
  86. version_path=self.resource_filename('migration_versions'),
  87. branch_label=self.settings_key)
  88. @property
  89. def installable(self):
  90. """Is ``True`` if the Plugin can be installed."""
  91. if self.settings_key is not None:
  92. return True
  93. return False
  94. @property
  95. def uninstallable(self):
  96. """Is ``True`` if the Plugin can be uninstalled."""
  97. if self.installable:
  98. group = SettingsGroup.query. \
  99. filter_by(key=self.settings_key). \
  100. first()
  101. if group and len(group.settings.all()) > 0:
  102. return True
  103. return False
  104. return False
  105. # Some helpers
  106. def register_blueprint(self, blueprint, **kwargs):
  107. """Registers a blueprint.
  108. :param blueprint: The blueprint which should be registered.
  109. """
  110. current_app.register_blueprint(blueprint, **kwargs)
  111. def create_table(self, model, db):
  112. """Creates the relation for the model
  113. :param model: The Model which should be created
  114. :param db: The database instance.
  115. """
  116. if not model.__table__.exists(bind=db.engine):
  117. model.__table__.create(bind=db.engine)
  118. def drop_table(self, model, db):
  119. """Drops the relation for the bounded model.
  120. :param model: The model on which the table is bound.
  121. :param db: The database instance.
  122. """
  123. model.__table__.drop(bind=db.engine)
  124. def create_all_tables(self, models, db):
  125. """A interface for creating all models specified in ``models``.
  126. :param models: A list with models
  127. :param db: The database instance
  128. """
  129. for model in models:
  130. self.create_table(model, db)
  131. def drop_all_tables(self, models, db):
  132. """A interface for dropping all models specified in the
  133. variable ``models``.
  134. :param models: A list with models
  135. :param db: The database instance.
  136. """
  137. for model in models:
  138. self.drop_table(model, db)