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).
This commit is contained in:
Noah 2014-07-23 14:21:53 -07:00
parent 0146fea8e0
commit 8a2d6a7c04
42 changed files with 99 additions and 30 deletions

View File

@ -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")

View File

@ -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

View File

@ -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
return render_template('errors/403.html', **g.info), 403

View File

@ -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():

View File

@ -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("/")

View File

@ -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():

29
rophako/plugin.py Normal file
View File

@ -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)