Compare commits
	
		
			2 Commits
		
	
	
		
			2880076705
			...
			e9fbce0da7
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | e9fbce0da7 | |
|  | 47b838804b | 
|  | @ -50,10 +50,16 @@ If not you need to create user and database manually do (or similar on Windows): | |||
|     ) | sudo mysql | ||||
| 
 | ||||
| Then you can install the database tables, this will update all tables from core + all enabled plugins. | ||||
| *Hint:* The same command can be later used to upgrade the database after plugins or core are updated. | ||||
| And also install all enabled plugins: | ||||
| 
 | ||||
|     $ flaschengeist install | ||||
| 
 | ||||
| *Hint:* To only install the database tables, or upgrade the database after plugins or core are updated later | ||||
| you can use this command: | ||||
| 
 | ||||
|     $ flaschengeist db upgrade heads | ||||
| 
 | ||||
| 
 | ||||
| ## Plugins | ||||
| To only upgrade one plugin (for example the `events` plugin): | ||||
| 
 | ||||
|  | @ -88,6 +94,7 @@ with the difference of the main logger will be forced to output to `stderr` and | |||
| of the CLI will override the logging level you have configured for the main logger. | ||||
| 
 | ||||
|     $ flaschengeist run | ||||
| 
 | ||||
| or with debug messages: | ||||
| 
 | ||||
|     $ flaschengeist run --debug | ||||
|  |  | |||
|  | @ -0,0 +1,4 @@ | |||
| from pathlib import Path | ||||
| 
 | ||||
| 
 | ||||
| alembic_migrations = str(Path(__file__).resolve().parent / "migrations") | ||||
|  | @ -18,14 +18,21 @@ depends_on = None | |||
| 
 | ||||
| 
 | ||||
| def upgrade(): | ||||
|     # ### commands auto generated by Alembic - please adjust! ### | ||||
|     op.create_table( | ||||
|         "plugin_setting", | ||||
|         sa.Column("id", flaschengeist.models.Serial(), nullable=False), | ||||
|         sa.Column("plugin", sa.String(length=127), nullable=True), | ||||
|         sa.Column("name", sa.String(length=127), nullable=False), | ||||
|         sa.Column("value", sa.PickleType(protocol=4), nullable=True), | ||||
|         sa.PrimaryKeyConstraint("id", name=op.f("pk_plugin_setting")), | ||||
|     ) | ||||
|     op.create_table( | ||||
|         "image", | ||||
|         sa.Column("id", flaschengeist.models.Serial(), nullable=False), | ||||
|         sa.Column("filename_", sa.String(length=127), nullable=False), | ||||
|         sa.Column("mimetype_", sa.String(length=30), nullable=False), | ||||
|         sa.Column("thumbnail_", sa.String(length=127), nullable=True), | ||||
|         sa.Column("path_", sa.String(length=127), nullable=True), | ||||
|         sa.Column("filename", sa.String(length=255), nullable=False), | ||||
|         sa.Column("mimetype", sa.String(length=127), nullable=False), | ||||
|         sa.Column("thumbnail", sa.String(length=255), nullable=True), | ||||
|         sa.Column("path", sa.String(length=255), nullable=True), | ||||
|         sa.PrimaryKeyConstraint("id", name=op.f("pk_image")), | ||||
|     ) | ||||
|     op.create_table( | ||||
|  |  | |||
|  | @ -88,12 +88,14 @@ def main(*args, **kwargs): | |||
|     from .export_cmd import export | ||||
|     from .docs_cmd import docs | ||||
|     from .run_cmd import run | ||||
|     from .install_cmd import install | ||||
| 
 | ||||
|     # Override logging level | ||||
|     environ.setdefault("FG_LOGGING", logging.getLevelName(LOGGING_MAX)) | ||||
| 
 | ||||
|     cli.add_command(export) | ||||
|     cli.add_command(docs) | ||||
|     cli.add_command(install) | ||||
|     cli.add_command(plugin) | ||||
|     cli.add_command(run) | ||||
|     cli(*args, **kwargs) | ||||
|  |  | |||
|  | @ -0,0 +1,20 @@ | |||
| import click | ||||
| from click.decorators import pass_context | ||||
| from flask.cli import with_appcontext | ||||
| from flask_migrate import upgrade | ||||
| 
 | ||||
| from flaschengeist.alembic import alembic_migrations | ||||
| from flaschengeist.cli.plugin_cmd import install_plugin_command | ||||
| from flaschengeist.utils.hook import Hook | ||||
| 
 | ||||
| 
 | ||||
| @click.command() | ||||
| @with_appcontext | ||||
| @pass_context | ||||
| @Hook("plugins.installed") | ||||
| def install(ctx): | ||||
|     # Install database | ||||
|     upgrade(alembic_migrations, revision="heads") | ||||
| 
 | ||||
|     # Install plugins | ||||
|     install_plugin_command(ctx, [], True) | ||||
|  | @ -4,7 +4,9 @@ from flask import current_app | |||
| from flask.cli import with_appcontext | ||||
| from importlib_metadata import EntryPoint, entry_points | ||||
| 
 | ||||
| from flaschengeist.database import db | ||||
| from flaschengeist.config import config | ||||
| from flaschengeist.models.user import Permission | ||||
| 
 | ||||
| 
 | ||||
| @click.group() | ||||
|  | @ -12,6 +14,35 @@ def plugin(): | |||
|     pass | ||||
| 
 | ||||
| 
 | ||||
| def install_plugin_command(ctx, plugin, all): | ||||
|     """Install one or more plugins""" | ||||
|     if not all and len(plugin) == 0: | ||||
|         ctx.fail("At least one plugin must be specified, or use `--all` flag.") | ||||
| 
 | ||||
|     if all: | ||||
|         plugins = current_app.config["FG_PLUGINS"] | ||||
|     else: | ||||
|         try: | ||||
|             plugins = {plugin_name: current_app.config["FG_PLUGINS"][plugin_name] for plugin_name in plugin} | ||||
|         except KeyError as e: | ||||
|             ctx.fail(f"Invalid plugin name, could not find >{e.args[0]}<") | ||||
| 
 | ||||
|     for name, plugin in plugins.items(): | ||||
|         click.echo(f"Installing {name}{'.'*(20-len(name))}", nl=False) | ||||
|         # Install permissions | ||||
|         if plugin.permissions: | ||||
|             cur_perm = set(x.name for x in Permission.query.filter(Permission.name.in_(plugin.permissions)).all()) | ||||
|             all_perm = set(plugin.permissions) | ||||
| 
 | ||||
|             add = all_perm - cur_perm | ||||
|             if add: | ||||
|                 db.session.bulk_save_objects([Permission(name=x) for x in all_perm]) | ||||
|                 db.session.commit() | ||||
|         # Custom installation steps | ||||
|         plugin.install() | ||||
|         click.secho(" ok", fg="green") | ||||
| 
 | ||||
| 
 | ||||
| @plugin.command() | ||||
| @click.argument("plugin", nargs=-1, type=str) | ||||
| @click.option("--all", help="Install all enabled plugins", is_flag=True) | ||||
|  | @ -19,20 +50,7 @@ def plugin(): | |||
| @pass_context | ||||
| def install(ctx, plugin, all): | ||||
|     """Install one or more plugins""" | ||||
|     if not all and len(plugin) == 0: | ||||
|         ctx.fail("At least one plugin must be specified, or use `--all` flag.") | ||||
|     if all: | ||||
|         plugins = current_app.config["FG_PLUGINS"].values() | ||||
|     else: | ||||
|         try: | ||||
|             plugins = [current_app.config["FG_PLUGINS"][p] for p in plugin] | ||||
|         except KeyError as e: | ||||
|             ctx.fail(f"Invalid plugin ID, could not find >{e.args[0]}<") | ||||
|     for p in plugins: | ||||
|         name = p.id.split(".")[-1] | ||||
|         click.echo(f"Installing {name}{'.'*(20-len(name))}", nl=False) | ||||
|         p.install() | ||||
|         click.secho(" ok", fg="green") | ||||
|     return install_plugin_command(ctx, plugin, all) | ||||
| 
 | ||||
| 
 | ||||
| @plugin.command() | ||||
|  | @ -44,14 +62,16 @@ def uninstall(ctx: click.Context, plugin): | |||
| 
 | ||||
|     if len(plugin) == 0: | ||||
|         ctx.fail("At least one plugin must be specified") | ||||
| 
 | ||||
|     try: | ||||
|         plugins = [current_app.config["FG_PLUGINS"][p] for p in plugin] | ||||
|         plugins = {plugin_name: current_app.config["FG_PLUGINS"][plugin_name] for plugin_name in plugin} | ||||
|     except KeyError as e: | ||||
|         ctx.fail(f"Invalid plugin ID, could not find >{e.args[0]}<") | ||||
| 
 | ||||
|     if ( | ||||
|         click.prompt( | ||||
|             "You are going to uninstall:\n\n" | ||||
|             f"\t{', '.join([p.id.split('.')[-1] for p in plugins])}\n\n" | ||||
|             f"\t{', '.join([plugin_name for plugin_name in plugins.keys()])}\n\n" | ||||
|             "Are you sure?", | ||||
|             default="n", | ||||
|             show_choices=True, | ||||
|  | @ -60,10 +80,9 @@ def uninstall(ctx: click.Context, plugin): | |||
|         != "y" | ||||
|     ): | ||||
|         ctx.exit() | ||||
|     for p in plugins: | ||||
|         name = p.id.split(".")[-1] | ||||
|     for name, plugin in plugins.items(): | ||||
|         click.echo(f"Uninstalling {name}{'.'*(20-len(name))}", nl=False) | ||||
|         p.uninstall() | ||||
|         plugin.uninstall() | ||||
|         click.secho(" ok", fg="green") | ||||
| 
 | ||||
| 
 | ||||
|  | @ -78,7 +97,7 @@ def ls(enabled, no_header): | |||
|         return p.version | ||||
| 
 | ||||
|     plugins = entry_points(group="flaschengeist.plugins") | ||||
|     enabled_plugins = [key for key, value in config.items() if "enabled" in value] + [config["FLASCHENGEIST"]["auth"]] | ||||
|     enabled_plugins = [key for key, value in config.items() if "enabled" in value and value["enabled"]] + [config["FLASCHENGEIST"]["auth"]] | ||||
|     loaded_plugins = current_app.config["FG_PLUGINS"].keys() | ||||
| 
 | ||||
|     if not no_header: | ||||
|  |  | |||
|  | @ -9,11 +9,11 @@ from ..database import db | |||
| 
 | ||||
| class Image(db.Model, ModelSerializeMixin): | ||||
|     __tablename__ = "image" | ||||
|     id: int = db.Column("id", Serial, primary_key=True) | ||||
|     filename_: str = db.Column(db.String(127), nullable=False) | ||||
|     mimetype_: str = db.Column(db.String(30), nullable=False) | ||||
|     thumbnail_: str = db.Column(db.String(127)) | ||||
|     path_: str = db.Column(db.String(127)) | ||||
|     id: int = db.Column(Serial, primary_key=True) | ||||
|     filename_: str = db.Column("filename", db.String(255), nullable=False) | ||||
|     mimetype_: str = db.Column("mimetype", db.String(127), nullable=False) | ||||
|     thumbnail_: str = db.Column("thumbnail", db.String(255)) | ||||
|     path_: str = db.Column("path", db.String(255)) | ||||
| 
 | ||||
|     def open(self): | ||||
|         return open(self.path_, "rb") | ||||
|  |  | |||
|  | @ -8,6 +8,6 @@ from ..database import db | |||
| class _PluginSetting(db.Model): | ||||
|     __tablename__ = "plugin_setting" | ||||
|     id = db.Column("id", Serial, primary_key=True) | ||||
|     plugin: str = db.Column(db.String(30)) | ||||
|     name: str = db.Column(db.String(30), nullable=False) | ||||
|     plugin: str = db.Column(db.String(127)) | ||||
|     name: str = db.Column(db.String(127), nullable=False) | ||||
|     value: Any = db.Column(db.PickleType(protocol=4)) | ||||
|  |  | |||
|  | @ -126,7 +126,8 @@ class Plugin: | |||
|     def install(self): | ||||
|         """Installation routine | ||||
| 
 | ||||
|         Is always called with Flask application context | ||||
|         Is always called with Flask application context, | ||||
|         it is called after the plugin permissions are installed. | ||||
|         """ | ||||
|         pass | ||||
| 
 | ||||
|  |  | |||
|  | @ -19,7 +19,7 @@ class AuthPlain(AuthPlugin): | |||
|     def install(self): | ||||
|         plugins_installed(self.post_install) | ||||
| 
 | ||||
|     def post_install(self, **kwargs): | ||||
|     def post_install(self, *args, **kwargs): | ||||
|         if User.query.filter(User.deleted == False).count() == 0: | ||||
|             logger.info("Installing admin user") | ||||
|             role = Role.query.filter(Role.name == "Superuser").first() | ||||
|  |  | |||
|  | @ -16,6 +16,7 @@ from . import permissions | |||
| 
 | ||||
| 
 | ||||
| class RolesPlugin(Plugin): | ||||
|     id = "roles" | ||||
|     blueprint = Blueprint("roles", __name__) | ||||
|     permissions = permissions.permissions | ||||
| 
 | ||||
|  |  | |||
|  | @ -39,6 +39,8 @@ def scheduled(id: str, replace=False, **kwargs): | |||
| 
 | ||||
| 
 | ||||
| class SchedulerPlugin(Plugin): | ||||
|     id = "scheduler" | ||||
| 
 | ||||
|     def __init__(self, entry_point, config=None): | ||||
|         super().__init__(entry_point, config) | ||||
|         self.blueprint = Blueprint(self.name, __name__) | ||||
|  |  | |||
|  | @ -18,6 +18,7 @@ from flaschengeist.utils.datetime import from_iso_format | |||
| 
 | ||||
| 
 | ||||
| class UsersPlugin(Plugin): | ||||
|     id = "users" | ||||
|     blueprint = Blueprint("users", __name__) | ||||
|     permissions = permissions.permissions | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue