From 8a2d6a7c040e52b13c5c48d3ba56642c5849227a Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Wed, 23 Jul 2014 14:21:53 -0700 Subject: [PATCH] Refactor modules into a plugin-based system All modules are now plugins. The config.py calls load_plugin for each plugin it needs (some plugins may load others automatically). Also each plugin keeps its own template folder which gets added to the template search path, so i.e. if the photo plugin is unloaded completely, the URL endpoints won't work either (with the old system, since the HTML templates still existed in the default root the endpoints would still serve pages, just without any Python logic behind them). --- config-sample.py | 19 +++++ .../kirsle_legacy.py => kirsle_legacy.py | 2 +- rophako/app.py | 73 +++++++++++-------- .../{account.py => account/__init__.py} | 0 .../account/templates}/account/login.html | 0 .../account/templates}/account/setup.html | 0 .../modules/{admin.py => admin/__init__.py} | 0 .../admin/templates}/admin/edit_user.html | 0 .../admin/templates}/admin/index.html | 0 .../admin/templates}/admin/users.html | 0 rophako/modules/{blog.py => blog/__init__.py} | 2 + .../blog/templates}/blog/categories.inc.html | 0 .../blog/templates}/blog/delete.html | 0 .../blog/templates}/blog/entry.html | 0 .../blog/templates}/blog/entry.inc.html | 0 .../blog/templates}/blog/index.html | 0 .../blog/templates}/blog/index.inc.html | 0 .../blog/templates}/blog/nav-links.inc.html | 0 .../blog/templates}/blog/sibling-links.html | 0 .../blog/templates}/blog/update.html | 0 .../{comment.py => comment/__init__.py} | 2 + .../comment/templates}/comment/form.inc.html | 0 .../comment/templates}/comment/index.inc.html | 0 .../comment/templates}/comment/preview.html | 0 .../comment/templates}/comment/privacy.html | 0 .../templates}/comment/unsubscribed.html | 0 .../{contact.py => contact/__init__.py} | 0 .../contact/templates}/contact/index.html | 0 .../{emoticons.py => emoticons/__init__.py} | 0 .../emoticons/templates}/emoticons/index.html | 0 .../modules/{photo.py => photo/__init__.py} | 2 + .../photo/templates}/photos/album.html | 0 .../photo/templates}/photos/albums.html | 0 .../templates}/photos/arrange_albums.html | 0 .../templates}/photos/arrange_photos.html | 0 .../photo/templates}/photos/crop.html | 0 .../photo/templates}/photos/delete.html | 0 .../photo/templates}/photos/delete_album.html | 0 .../photo/templates}/photos/edit.html | 0 .../photo/templates}/photos/upload.html | 0 .../photo/templates}/photos/view.html | 0 rophako/plugin.py | 29 ++++++++ 42 files changed, 99 insertions(+), 30 deletions(-) rename rophako/modules/kirsle_legacy.py => kirsle_legacy.py (99%) rename rophako/modules/{account.py => account/__init__.py} (100%) rename rophako/{www => modules/account/templates}/account/login.html (100%) rename rophako/{www => modules/account/templates}/account/setup.html (100%) rename rophako/modules/{admin.py => admin/__init__.py} (100%) rename rophako/{www => modules/admin/templates}/admin/edit_user.html (100%) rename rophako/{www => modules/admin/templates}/admin/index.html (100%) rename rophako/{www => modules/admin/templates}/admin/users.html (100%) rename rophako/modules/{blog.py => blog/__init__.py} (99%) rename rophako/{www => modules/blog/templates}/blog/categories.inc.html (100%) rename rophako/{www => modules/blog/templates}/blog/delete.html (100%) rename rophako/{www => modules/blog/templates}/blog/entry.html (100%) rename rophako/{www => modules/blog/templates}/blog/entry.inc.html (100%) rename rophako/{www => modules/blog/templates}/blog/index.html (100%) rename rophako/{www => modules/blog/templates}/blog/index.inc.html (100%) rename rophako/{www => modules/blog/templates}/blog/nav-links.inc.html (100%) rename rophako/{www => modules/blog/templates}/blog/sibling-links.html (100%) rename rophako/{www => modules/blog/templates}/blog/update.html (100%) rename rophako/modules/{comment.py => comment/__init__.py} (98%) rename rophako/{www => modules/comment/templates}/comment/form.inc.html (100%) rename rophako/{www => modules/comment/templates}/comment/index.inc.html (100%) rename rophako/{www => modules/comment/templates}/comment/preview.html (100%) rename rophako/{www => modules/comment/templates}/comment/privacy.html (100%) rename rophako/{www => modules/comment/templates}/comment/unsubscribed.html (100%) rename rophako/modules/{contact.py => contact/__init__.py} (100%) rename rophako/{www => modules/contact/templates}/contact/index.html (100%) rename rophako/modules/{emoticons.py => emoticons/__init__.py} (100%) rename rophako/{www => modules/emoticons/templates}/emoticons/index.html (100%) rename rophako/modules/{photo.py => photo/__init__.py} (99%) rename rophako/{www => modules/photo/templates}/photos/album.html (100%) rename rophako/{www => modules/photo/templates}/photos/albums.html (100%) rename rophako/{www => modules/photo/templates}/photos/arrange_albums.html (100%) rename rophako/{www => modules/photo/templates}/photos/arrange_photos.html (100%) rename rophako/{www => modules/photo/templates}/photos/crop.html (100%) rename rophako/{www => modules/photo/templates}/photos/delete.html (100%) rename rophako/{www => modules/photo/templates}/photos/delete_album.html (100%) rename rophako/{www => modules/photo/templates}/photos/edit.html (100%) rename rophako/{www => modules/photo/templates}/photos/upload.html (100%) rename rophako/{www => modules/photo/templates}/photos/view.html (100%) create mode 100644 rophako/plugin.py diff --git a/config-sample.py b/config-sample.py index 6946b91..ae1803c 100644 --- a/config-sample.py +++ b/config-sample.py @@ -5,6 +5,7 @@ import os _basedir = os.path.abspath(os.path.dirname(__file__)) import datetime +from rophako.plugin import load_plugin DEBUG = True @@ -116,3 +117,21 @@ COMMENT_TIME_FORMAT = "%A, %B %d %Y @ %I:%M %p" # We use Gravatar for comments if the user provides an e-mail address. Specify # the URL to a fallback image to use in case they don't have a gravatar. COMMENT_DEFAULT_AVATAR = "" + +################################################################################ +## Enabled Plugins ## +################################################################################ + +# Place all the load_plugin calls down here. Some of the plugins need to refer +# to config params above so those need to get declared before the plugin begins +# to initialize itself. +# +# Some plugins will automatically load others as dependencies, i.e. the blog +# and photo will load comment, and comment will load emoticons. But it doesn't +# hurt to list them all out here to be explicit anyway. + +load_plugin("rophako.modules.blog") +load_plugin("rophako.modules.photo") +load_plugin("rophako.modules.comment") +load_plugin("rophako.modules.emoticons") +load_plugin("rophako.modules.contact") \ No newline at end of file diff --git a/rophako/modules/kirsle_legacy.py b/kirsle_legacy.py similarity index 99% rename from rophako/modules/kirsle_legacy.py rename to kirsle_legacy.py index 6af8e14..a5164b8 100644 --- a/rophako/modules/kirsle_legacy.py +++ b/kirsle_legacy.py @@ -8,7 +8,7 @@ import os import json import config -from rophako import app +from rophako.app import app from rophako.utils import template, login_required import rophako.model.blog as Blog import rophako.jsondb as JsonDB diff --git a/rophako/app.py b/rophako/app.py index 29c8714..e386f57 100644 --- a/rophako/app.py +++ b/rophako/app.py @@ -1,3 +1,7 @@ +#!/usr/bin/env python + +"""Flask app for Rophako.""" + from flask import Flask, g, request, session, render_template, send_file, abort from flask_sslify import SSLify import jinja2 @@ -5,13 +9,25 @@ import os.path import time import sys -import config -from rophako import __version__ -import rophako.utils - +# Get the Flask app object ready right away so other modules can import it +# without getting a circular import error. app = Flask(__name__, static_url_path="/.static", ) + +# We use a custom Jinja loader to support multiple template paths for custom +# and default templates. The base list of template paths to check includes +# your custom path (from config.SITE_ROOT), the "rophako/www" path for normal +# pages, and then the blueprint paths for all imported plugins. This list will +# be extended while blueprints are being loaded and passed in below to the +# jinja2.ChoiceLoader. +BLUEPRINT_PATHS = [] + +import config +from rophako import __version__ +from rophako.plugin import load_plugin +import rophako.utils + app.DEBUG = config.DEBUG app.secret_key = config.SECRET_KEY @@ -20,28 +36,32 @@ if config.FORCE_SSL: app.config['SESSION_COOKIE_SECURE'] = True sslify = SSLify(app) -# Load all the blueprints! -from rophako.modules.admin import mod as AdminModule -from rophako.modules.account import mod as AccountModule -from rophako.modules.blog import mod as BlogModule -from rophako.modules.photo import mod as PhotoModule -from rophako.modules.comment import mod as CommentModule -from rophako.modules.emoticons import mod as EmoticonsModule -from rophako.modules.contact import mod as ContactModule -app.register_blueprint(AdminModule) -app.register_blueprint(AccountModule) -app.register_blueprint(BlogModule) -app.register_blueprint(PhotoModule) -app.register_blueprint(CommentModule) -app.register_blueprint(EmoticonsModule) -app.register_blueprint(ContactModule) +# Load all the built-in essential plugins. +load_plugin("rophako.modules.admin") +load_plugin("rophako.modules.account") +# from rophako.modules.admin import mod as AdminModule +# from rophako.modules.account import mod as AccountModule +# from rophako.modules.blog import mod as BlogModule +# from rophako.modules.photo import mod as PhotoModule +# from rophako.modules.comment import mod as CommentModule +# from rophako.modules.emoticons import mod as EmoticonsModule +# from rophako.modules.contact import mod as ContactModule +# app.register_blueprint(AdminModule) +# app.register_blueprint(AccountModule) +# app.register_blueprint(BlogModule) +# app.register_blueprint(PhotoModule) +# app.register_blueprint(CommentModule) +# app.register_blueprint(EmoticonsModule) +# app.register_blueprint(ContactModule) # Custom Jinja handler to support custom- and default-template folders for # rendering templates. -app.jinja_loader = jinja2.ChoiceLoader([ - jinja2.FileSystemLoader(config.SITE_ROOT), # Site specific. - jinja2.FileSystemLoader("rophako/www"), # Default/fall-back -]) +template_paths = [ + config.SITE_ROOT, # Site specific. + "rophako/www", # Default/fall-back +] +template_paths.extend(BLUEPRINT_PATHS) +app.jinja_loader = jinja2.ChoiceLoader([ jinja2.FileSystemLoader(x) for x in template_paths]) app.jinja_env.globals["csrf_token"] = rophako.utils.generate_csrf_token app.jinja_env.globals["include_page"] = rophako.utils.include @@ -152,9 +172,4 @@ def not_found(error): @app.errorhandler(403) def forbidden(error): - return render_template('errors/403.html', **g.info), 403 - - -# Domain specific endpoints. -if config.SITE_NAME == "kirsle.net": - import rophako.modules.kirsle_legacy \ No newline at end of file + return render_template('errors/403.html', **g.info), 403 \ No newline at end of file diff --git a/rophako/modules/account.py b/rophako/modules/account/__init__.py similarity index 100% rename from rophako/modules/account.py rename to rophako/modules/account/__init__.py diff --git a/rophako/www/account/login.html b/rophako/modules/account/templates/account/login.html similarity index 100% rename from rophako/www/account/login.html rename to rophako/modules/account/templates/account/login.html diff --git a/rophako/www/account/setup.html b/rophako/modules/account/templates/account/setup.html similarity index 100% rename from rophako/www/account/setup.html rename to rophako/modules/account/templates/account/setup.html diff --git a/rophako/modules/admin.py b/rophako/modules/admin/__init__.py similarity index 100% rename from rophako/modules/admin.py rename to rophako/modules/admin/__init__.py diff --git a/rophako/www/admin/edit_user.html b/rophako/modules/admin/templates/admin/edit_user.html similarity index 100% rename from rophako/www/admin/edit_user.html rename to rophako/modules/admin/templates/admin/edit_user.html diff --git a/rophako/www/admin/index.html b/rophako/modules/admin/templates/admin/index.html similarity index 100% rename from rophako/www/admin/index.html rename to rophako/modules/admin/templates/admin/index.html diff --git a/rophako/www/admin/users.html b/rophako/modules/admin/templates/admin/users.html similarity index 100% rename from rophako/www/admin/users.html rename to rophako/modules/admin/templates/admin/users.html diff --git a/rophako/modules/blog.py b/rophako/modules/blog/__init__.py similarity index 99% rename from rophako/modules/blog.py rename to rophako/modules/blog/__init__.py index 6e74ce3..061b6a9 100644 --- a/rophako/modules/blog.py +++ b/rophako/modules/blog/__init__.py @@ -14,10 +14,12 @@ import rophako.model.blog as Blog import rophako.model.comment as Comment import rophako.model.emoticons as Emoticons from rophako.utils import template, render_markdown, pretty_time, login_required +from rophako.plugin import load_plugin from rophako.log import logger from config import * mod = Blueprint("blog", __name__, url_prefix="/blog") +load_plugin("rophako.modules.comment") @mod.route("/") def index(): diff --git a/rophako/www/blog/categories.inc.html b/rophako/modules/blog/templates/blog/categories.inc.html similarity index 100% rename from rophako/www/blog/categories.inc.html rename to rophako/modules/blog/templates/blog/categories.inc.html diff --git a/rophako/www/blog/delete.html b/rophako/modules/blog/templates/blog/delete.html similarity index 100% rename from rophako/www/blog/delete.html rename to rophako/modules/blog/templates/blog/delete.html diff --git a/rophako/www/blog/entry.html b/rophako/modules/blog/templates/blog/entry.html similarity index 100% rename from rophako/www/blog/entry.html rename to rophako/modules/blog/templates/blog/entry.html diff --git a/rophako/www/blog/entry.inc.html b/rophako/modules/blog/templates/blog/entry.inc.html similarity index 100% rename from rophako/www/blog/entry.inc.html rename to rophako/modules/blog/templates/blog/entry.inc.html diff --git a/rophako/www/blog/index.html b/rophako/modules/blog/templates/blog/index.html similarity index 100% rename from rophako/www/blog/index.html rename to rophako/modules/blog/templates/blog/index.html diff --git a/rophako/www/blog/index.inc.html b/rophako/modules/blog/templates/blog/index.inc.html similarity index 100% rename from rophako/www/blog/index.inc.html rename to rophako/modules/blog/templates/blog/index.inc.html diff --git a/rophako/www/blog/nav-links.inc.html b/rophako/modules/blog/templates/blog/nav-links.inc.html similarity index 100% rename from rophako/www/blog/nav-links.inc.html rename to rophako/modules/blog/templates/blog/nav-links.inc.html diff --git a/rophako/www/blog/sibling-links.html b/rophako/modules/blog/templates/blog/sibling-links.html similarity index 100% rename from rophako/www/blog/sibling-links.html rename to rophako/modules/blog/templates/blog/sibling-links.html diff --git a/rophako/www/blog/update.html b/rophako/modules/blog/templates/blog/update.html similarity index 100% rename from rophako/www/blog/update.html rename to rophako/modules/blog/templates/blog/update.html diff --git a/rophako/modules/comment.py b/rophako/modules/comment/__init__.py similarity index 98% rename from rophako/modules/comment.py rename to rophako/modules/comment/__init__.py index fae494f..12f6a50 100644 --- a/rophako/modules/comment.py +++ b/rophako/modules/comment/__init__.py @@ -9,10 +9,12 @@ import time import rophako.model.user as User import rophako.model.comment as Comment from rophako.utils import template, pretty_time, login_required, sanitize_name +from rophako.plugin import load_plugin from rophako.log import logger from config import * mod = Blueprint("comment", __name__, url_prefix="/comments") +load_plugin("rophako.modules.emoticons") @mod.route("/") diff --git a/rophako/www/comment/form.inc.html b/rophako/modules/comment/templates/comment/form.inc.html similarity index 100% rename from rophako/www/comment/form.inc.html rename to rophako/modules/comment/templates/comment/form.inc.html diff --git a/rophako/www/comment/index.inc.html b/rophako/modules/comment/templates/comment/index.inc.html similarity index 100% rename from rophako/www/comment/index.inc.html rename to rophako/modules/comment/templates/comment/index.inc.html diff --git a/rophako/www/comment/preview.html b/rophako/modules/comment/templates/comment/preview.html similarity index 100% rename from rophako/www/comment/preview.html rename to rophako/modules/comment/templates/comment/preview.html diff --git a/rophako/www/comment/privacy.html b/rophako/modules/comment/templates/comment/privacy.html similarity index 100% rename from rophako/www/comment/privacy.html rename to rophako/modules/comment/templates/comment/privacy.html diff --git a/rophako/www/comment/unsubscribed.html b/rophako/modules/comment/templates/comment/unsubscribed.html similarity index 100% rename from rophako/www/comment/unsubscribed.html rename to rophako/modules/comment/templates/comment/unsubscribed.html diff --git a/rophako/modules/contact.py b/rophako/modules/contact/__init__.py similarity index 100% rename from rophako/modules/contact.py rename to rophako/modules/contact/__init__.py diff --git a/rophako/www/contact/index.html b/rophako/modules/contact/templates/contact/index.html similarity index 100% rename from rophako/www/contact/index.html rename to rophako/modules/contact/templates/contact/index.html diff --git a/rophako/modules/emoticons.py b/rophako/modules/emoticons/__init__.py similarity index 100% rename from rophako/modules/emoticons.py rename to rophako/modules/emoticons/__init__.py diff --git a/rophako/www/emoticons/index.html b/rophako/modules/emoticons/templates/emoticons/index.html similarity index 100% rename from rophako/www/emoticons/index.html rename to rophako/modules/emoticons/templates/emoticons/index.html diff --git a/rophako/modules/photo.py b/rophako/modules/photo/__init__.py similarity index 99% rename from rophako/modules/photo.py rename to rophako/modules/photo/__init__.py index edfb51a..b33c364 100644 --- a/rophako/modules/photo.py +++ b/rophako/modules/photo/__init__.py @@ -7,10 +7,12 @@ from flask import Blueprint, g, request, redirect, url_for, session, flash import rophako.model.user as User import rophako.model.photo as Photo from rophako.utils import template, pretty_time, login_required, ajax_response +from rophako.plugin import load_plugin from rophako.log import logger from config import * mod = Blueprint("photo", __name__, url_prefix="/photos") +load_plugin("rophako.modules.comment") @mod.route("/") def index(): diff --git a/rophako/www/photos/album.html b/rophako/modules/photo/templates/photos/album.html similarity index 100% rename from rophako/www/photos/album.html rename to rophako/modules/photo/templates/photos/album.html diff --git a/rophako/www/photos/albums.html b/rophako/modules/photo/templates/photos/albums.html similarity index 100% rename from rophako/www/photos/albums.html rename to rophako/modules/photo/templates/photos/albums.html diff --git a/rophako/www/photos/arrange_albums.html b/rophako/modules/photo/templates/photos/arrange_albums.html similarity index 100% rename from rophako/www/photos/arrange_albums.html rename to rophako/modules/photo/templates/photos/arrange_albums.html diff --git a/rophako/www/photos/arrange_photos.html b/rophako/modules/photo/templates/photos/arrange_photos.html similarity index 100% rename from rophako/www/photos/arrange_photos.html rename to rophako/modules/photo/templates/photos/arrange_photos.html diff --git a/rophako/www/photos/crop.html b/rophako/modules/photo/templates/photos/crop.html similarity index 100% rename from rophako/www/photos/crop.html rename to rophako/modules/photo/templates/photos/crop.html diff --git a/rophako/www/photos/delete.html b/rophako/modules/photo/templates/photos/delete.html similarity index 100% rename from rophako/www/photos/delete.html rename to rophako/modules/photo/templates/photos/delete.html diff --git a/rophako/www/photos/delete_album.html b/rophako/modules/photo/templates/photos/delete_album.html similarity index 100% rename from rophako/www/photos/delete_album.html rename to rophako/modules/photo/templates/photos/delete_album.html diff --git a/rophako/www/photos/edit.html b/rophako/modules/photo/templates/photos/edit.html similarity index 100% rename from rophako/www/photos/edit.html rename to rophako/modules/photo/templates/photos/edit.html diff --git a/rophako/www/photos/upload.html b/rophako/modules/photo/templates/photos/upload.html similarity index 100% rename from rophako/www/photos/upload.html rename to rophako/modules/photo/templates/photos/upload.html diff --git a/rophako/www/photos/view.html b/rophako/modules/photo/templates/photos/view.html similarity index 100% rename from rophako/www/photos/view.html rename to rophako/modules/photo/templates/photos/view.html diff --git a/rophako/plugin.py b/rophako/plugin.py new file mode 100644 index 0000000..a287606 --- /dev/null +++ b/rophako/plugin.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python + +"""Dynamic CMS plugin loader.""" + +import os +from importlib import import_module +from rophako.app import app, BLUEPRINT_PATHS + +def load_plugin(name, as_blueprint=True, template_path=None): + """Load a Rophako CMS plugin. + + * `name` is a Python module name, i.e. `rophako.modules.blog` + * `as_blueprint` is True if the module exports a blueprint object called + `mod` that can be attached to the Flask app. Set this value to False if + you simply need to include a Python module that isn't a blueprint. + * `template_path` is a filesystem path where the blueprint's templates + can be found. If not provided, the path is automatically determined + based on the module name, which is suitable for the built-in plugins.""" + module = import_module(name) + if as_blueprint: + mod = getattr(module, "mod") + app.register_blueprint(mod) + + # Get the template path to add to the BLUEPRINT_PATHS. + if template_path is None: + module_path = name.replace(".", "/") + template_path = os.path.join(module_path, "templates") + + BLUEPRINT_PATHS.append(template_path) \ No newline at end of file