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 import os
_basedir = os.path.abspath(os.path.dirname(__file__)) _basedir = os.path.abspath(os.path.dirname(__file__))
import datetime import datetime
from rophako.plugin import load_plugin
DEBUG = True 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 # 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. # the URL to a fallback image to use in case they don't have a gravatar.
COMMENT_DEFAULT_AVATAR = "" 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 json
import config import config
from rophako import app from rophako.app import app
from rophako.utils import template, login_required from rophako.utils import template, login_required
import rophako.model.blog as Blog import rophako.model.blog as Blog
import rophako.jsondb as JsonDB 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 import Flask, g, request, session, render_template, send_file, abort
from flask_sslify import SSLify from flask_sslify import SSLify
import jinja2 import jinja2
@ -5,13 +9,25 @@ import os.path
import time import time
import sys import sys
import config # Get the Flask app object ready right away so other modules can import it
from rophako import __version__ # without getting a circular import error.
import rophako.utils
app = Flask(__name__, app = Flask(__name__,
static_url_path="/.static", 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.DEBUG = config.DEBUG
app.secret_key = config.SECRET_KEY app.secret_key = config.SECRET_KEY
@ -20,28 +36,32 @@ if config.FORCE_SSL:
app.config['SESSION_COOKIE_SECURE'] = True app.config['SESSION_COOKIE_SECURE'] = True
sslify = SSLify(app) sslify = SSLify(app)
# Load all the blueprints! # Load all the built-in essential plugins.
from rophako.modules.admin import mod as AdminModule load_plugin("rophako.modules.admin")
from rophako.modules.account import mod as AccountModule load_plugin("rophako.modules.account")
from rophako.modules.blog import mod as BlogModule # from rophako.modules.admin import mod as AdminModule
from rophako.modules.photo import mod as PhotoModule # from rophako.modules.account import mod as AccountModule
from rophako.modules.comment import mod as CommentModule # from rophako.modules.blog import mod as BlogModule
from rophako.modules.emoticons import mod as EmoticonsModule # from rophako.modules.photo import mod as PhotoModule
from rophako.modules.contact import mod as ContactModule # from rophako.modules.comment import mod as CommentModule
app.register_blueprint(AdminModule) # from rophako.modules.emoticons import mod as EmoticonsModule
app.register_blueprint(AccountModule) # from rophako.modules.contact import mod as ContactModule
app.register_blueprint(BlogModule) # app.register_blueprint(AdminModule)
app.register_blueprint(PhotoModule) # app.register_blueprint(AccountModule)
app.register_blueprint(CommentModule) # app.register_blueprint(BlogModule)
app.register_blueprint(EmoticonsModule) # app.register_blueprint(PhotoModule)
app.register_blueprint(ContactModule) # app.register_blueprint(CommentModule)
# app.register_blueprint(EmoticonsModule)
# app.register_blueprint(ContactModule)
# Custom Jinja handler to support custom- and default-template folders for # Custom Jinja handler to support custom- and default-template folders for
# rendering templates. # rendering templates.
app.jinja_loader = jinja2.ChoiceLoader([ template_paths = [
jinja2.FileSystemLoader(config.SITE_ROOT), # Site specific. config.SITE_ROOT, # Site specific.
jinja2.FileSystemLoader("rophako/www"), # Default/fall-back "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["csrf_token"] = rophako.utils.generate_csrf_token
app.jinja_env.globals["include_page"] = rophako.utils.include app.jinja_env.globals["include_page"] = rophako.utils.include
@ -152,9 +172,4 @@ def not_found(error):
@app.errorhandler(403) @app.errorhandler(403)
def forbidden(error): def forbidden(error):
return render_template('errors/403.html', **g.info), 403 return render_template('errors/403.html', **g.info), 403
# Domain specific endpoints.
if config.SITE_NAME == "kirsle.net":
import rophako.modules.kirsle_legacy

View File

@ -14,10 +14,12 @@ import rophako.model.blog as Blog
import rophako.model.comment as Comment import rophako.model.comment as Comment
import rophako.model.emoticons as Emoticons import rophako.model.emoticons as Emoticons
from rophako.utils import template, render_markdown, pretty_time, login_required from rophako.utils import template, render_markdown, pretty_time, login_required
from rophako.plugin import load_plugin
from rophako.log import logger from rophako.log import logger
from config import * from config import *
mod = Blueprint("blog", __name__, url_prefix="/blog") mod = Blueprint("blog", __name__, url_prefix="/blog")
load_plugin("rophako.modules.comment")
@mod.route("/") @mod.route("/")
def index(): def index():

View File

@ -9,10 +9,12 @@ import time
import rophako.model.user as User import rophako.model.user as User
import rophako.model.comment as Comment import rophako.model.comment as Comment
from rophako.utils import template, pretty_time, login_required, sanitize_name from rophako.utils import template, pretty_time, login_required, sanitize_name
from rophako.plugin import load_plugin
from rophako.log import logger from rophako.log import logger
from config import * from config import *
mod = Blueprint("comment", __name__, url_prefix="/comments") mod = Blueprint("comment", __name__, url_prefix="/comments")
load_plugin("rophako.modules.emoticons")
@mod.route("/") @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.user as User
import rophako.model.photo as Photo import rophako.model.photo as Photo
from rophako.utils import template, pretty_time, login_required, ajax_response from rophako.utils import template, pretty_time, login_required, ajax_response
from rophako.plugin import load_plugin
from rophako.log import logger from rophako.log import logger
from config import * from config import *
mod = Blueprint("photo", __name__, url_prefix="/photos") mod = Blueprint("photo", __name__, url_prefix="/photos")
load_plugin("rophako.modules.comment")
@mod.route("/") @mod.route("/")
def index(): 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)